Repository: rust-ndarray/ndarray
Pull request #990 (2021-05-01 19:53:57)

Rename append_row/column to push_row/column; add method push
Follow up to #932 by renaming append methods for consistency, as outlined in https://github.com/rust-ndarray/ndarray/pull/932#issuecomment-820843384 :

```rust
// On 2-D arrays
fn push_row(&mut self, row: ArrayView<A, D::Smaller>) {}
fn push_column(&mut self, column: ArrayView<A, D::Smaller>) {}

// On n-D arrays
fn push(&mut self, axis: Axis, array: ArrayView<A, D::Smaller>) {}
fn append(&mut self, axis: Axis, array: ArrayView<A, D>) {}
```

This means we have two similar methods - push and append.


Patch:
diff --git a/src/impl_owned_array.rs b/src/impl_owned_array.rs
index 8d8963c1..63af6391 100644
--- a/src/impl_owned_array.rs
+++ b/src/impl_owned_array.rs
@@ -99,15 +99,15 @@ impl<A> Array<A, Ix2> {
     ///
     /// // create an empty array and append
     /// let mut a = Array::zeros((0, 4));
-    /// a.append_row(ArrayView::from(&[ 1.,  2.,  3.,  4.])).unwrap();
-    /// a.append_row(ArrayView::from(&[-1., -2., -3., -4.])).unwrap();
+    /// a.push_row(ArrayView::from(&[ 1.,  2.,  3.,  4.])).unwrap();
+    /// a.push_row(ArrayView::from(&[-1., -2., -3., -4.])).unwrap();
     ///
     /// assert_eq!(
     ///     a,
     ///     array![[ 1.,  2.,  3.,  4.],
     ///            [-1., -2., -3., -4.]]);
     /// ```
-    pub fn append_row(&mut self, row: ArrayView<A, Ix1>) -> Result<(), ShapeError>
+    pub fn push_row(&mut self, row: ArrayView<A, Ix1>) -> Result<(), ShapeError>
     where
         A: Clone,
     {
@@ -136,15 +136,15 @@ impl<A> Array<A, Ix2> {
     ///
     /// // create an empty array and append
     /// let mut a = Array::zeros((2, 0));
-    /// a.append_column(ArrayView::from(&[1., 2.])).unwrap();
-    /// a.append_column(ArrayView::from(&[-1., -2.])).unwrap();
+    /// a.push_column(ArrayView::from(&[1., 2.])).unwrap();
+    /// a.push_column(ArrayView::from(&[-1., -2.])).unwrap();
     ///
     /// assert_eq!(
     ///     a,
     ///     array![[1., -1.],
     ///            [2., -2.]]);
     /// ```
-    pub fn append_column(&mut self, column: ArrayView<A, Ix1>) -> Result<(), ShapeError>
+    pub fn push_column(&mut self, column: ArrayView<A, Ix1>) -> Result<(), ShapeError>
     where
         A: Clone,
     {
@@ -278,8 +278,66 @@ impl<A, D> Array<A, D>
         }
     }
 
+    /// Append an array to the array along an axis
+    ///
+    /// Where the item to push to the array has one dimension less than the `self` array. This
+    /// method is equivalent to `self.append(axis, array.insert_axis(axis))`.
+    ///
+    /// The axis-to-append-to `axis` must be the array's "growing axis" for this operation
+    /// to succeed. The growing axis is the outermost or last-visited when elements are visited in
+    /// memory order:
+    ///
+    /// `axis` must be the growing axis of the current array, an axis with length 0 or 1.
+    ///
+    /// - This is the 0th axis for standard layout arrays
+    /// - This is the *n*-1 th axis for fortran layout arrays
+    /// - If the array is empty (the axis or any other has length 0) or if `axis`
+    ///   has length 1, then the array can always be appended.
+    ///
+    /// ***Errors*** with a shape error if the shape of self does not match the array-to-append;
+    /// all axes *except* the axis along which it being appended matter for this check.
+    ///
+    /// The memory layout of the `self` array matters for ensuring that the append is efficient.
+    /// Appending automatically changes memory layout of the array so that it is appended to
+    /// along the "growing axis".
+    ///
+    /// Ensure appending is efficient by for example starting from an empty array and/or always
+    /// appending to an array along the same axis.
+    ///
+    /// The amortized average complexity of the append, when appending along its growing axis, is
+    /// O(*m*) where *m* is the length of the row.
+    ///
+    /// The memory layout of the argument `array` does not matter to the same extent.
+    ///
+    /// ```rust
+    /// use ndarray::{Array, ArrayView, array, Axis};
+    ///
+    /// // create an empty array and push rows to it
+    /// let mut a = Array::zeros((0, 4));
+    /// let ones  = ArrayView::from(&[1.; 4]);
+    /// let zeros = ArrayView::from(&[0.; 4]);
+    /// a.push(Axis(0), ones).unwrap();
+    /// a.push(Axis(0), zeros).unwrap();
+    /// a.push(Axis(0), ones).unwrap();
+    ///
+    /// assert_eq!(
+    ///     a,
+    ///     array![[1., 1., 1., 1.],
+    ///            [0., 0., 0., 0.],
+    ///            [1., 1., 1., 1.]]);
+    /// ```
+    pub fn push(&mut self, axis: Axis, array: ArrayView<A, D::Smaller>)
+        -> Result<(), ShapeError>
+    where
+        A: Clone,
+        D: RemoveAxis,
+    {
+        // same-dimensionality conversion
+        self.append(axis, array.insert_axis(axis).into_dimensionality::<D>().unwrap())
+    }
+
 
-    /// Append an array to the array
+    /// Append an array to the array along an axis
     ///
     /// The axis-to-append-to `axis` must be the array's "growing axis" for this operation
     /// to succeed. The growing axis is the outermost or last-visited when elements are visited in
@@ -338,8 +396,10 @@ impl<A, D> Array<A, D>
         }
 
         let current_axis_len = self.len_of(axis);
-        let remaining_shape = self.raw_dim().remove_axis(axis);
-        let array_rem_shape = array.raw_dim().remove_axis(axis);
+        let self_dim = self.raw_dim();
+        let array_dim = array.raw_dim();
+        let remaining_shape = self_dim.remove_axis(axis);
+        let array_rem_shape = array_dim.remove_axis(axis);
 
         if remaining_shape != array_rem_shape {
             return Err(ShapeError::from_kind(ErrorKind::IncompatibleShape));
@@ -347,9 +407,8 @@ impl<A, D> Array<A, D>
 
         let len_to_append = array.len();
 
-        let array_shape = array.raw_dim();
-        let mut res_dim = self.raw_dim();
-        res_dim[axis.index()] += array_shape[axis.index()];
+        let mut res_dim = self_dim;
+        res_dim[axis.index()] += array_dim[axis.index()];
         let new_len = dimension::size_of_shape_checked(&res_dim)?;
 
         if len_to_append == 0 {
@@ -468,7 +527,7 @@ impl<A, D> Array<A, D>
 
             // With > 0 strides, the current end of data is the correct base pointer for tail_view
             let tail_ptr = self.data.as_end_nonnull();
-            let mut tail_view = RawArrayViewMut::new(tail_ptr, array_shape, tail_strides);
+            let mut tail_view = RawArrayViewMut::new(tail_ptr, array_dim, tail_strides);
 
             if tail_view.ndim() > 1 {
                 sort_axes_in_default_order_tandem(&mut tail_view, &mut array);


Test patch:
diff --git a/tests/append.rs b/tests/append.rs
index 6306b2ec..dcebcf50 100644
--- a/tests/append.rs
+++ b/tests/append.rs
@@ -3,21 +3,21 @@ use ndarray::prelude::*;
 use ndarray::{ShapeError, ErrorKind};
 
 #[test]
-fn append_row() {
+fn push_row() {
     let mut a = Array::zeros((0, 4));
-    a.append_row(aview1(&[0., 1., 2., 3.])).unwrap();
-    a.append_row(aview1(&[4., 5., 6., 7.])).unwrap();
+    a.push_row(aview1(&[0., 1., 2., 3.])).unwrap();
+    a.push_row(aview1(&[4., 5., 6., 7.])).unwrap();
     assert_eq!(a.shape(), &[2, 4]);
 
     assert_eq!(a,
         array![[0., 1., 2., 3.],
                [4., 5., 6., 7.]]);
 
-    assert_eq!(a.append_row(aview1(&[1.])),
+    assert_eq!(a.push_row(aview1(&[1.])),
         Err(ShapeError::from_kind(ErrorKind::IncompatibleShape)));
-    assert_eq!(a.append_column(aview1(&[1.])),
+    assert_eq!(a.push_column(aview1(&[1.])),
         Err(ShapeError::from_kind(ErrorKind::IncompatibleShape)));
-    assert_eq!(a.append_column(aview1(&[1., 2.])),
+    assert_eq!(a.push_column(aview1(&[1., 2.])),
         Ok(()));
     assert_eq!(a,
         array![[0., 1., 2., 3., 1.],
@@ -25,10 +25,10 @@ fn append_row() {
 }
 
 #[test]
-fn append_row_wrong_layout() {
+fn push_row_wrong_layout() {
     let mut a = Array::zeros((0, 4));
-    a.append_row(aview1(&[0., 1., 2., 3.])).unwrap();
-    a.append_row(aview1(&[4., 5., 6., 7.])).unwrap();
+    a.push_row(aview1(&[0., 1., 2., 3.])).unwrap();
+    a.push_row(aview1(&[4., 5., 6., 7.])).unwrap();
     assert_eq!(a.shape(), &[2, 4]);
 
     assert_eq!(a,
@@ -38,7 +38,7 @@ fn append_row_wrong_layout() {
 
     // Changing the memory layout to fit the next append
     let mut a2 = a.clone();
-    a2.append_column(aview1(&[1., 2.])).unwrap();
+    a2.push_column(aview1(&[1., 2.])).unwrap();
     assert_eq!(a2,
         array![[0., 1., 2., 3., 1.],
                [4., 5., 6., 7., 2.]]);
@@ -51,17 +51,17 @@ fn append_row_wrong_layout() {
     dim[1] = 0;
     let mut b = Array::zeros(dim);
     b.append(Axis(1), a.view()).unwrap();
-    assert_eq!(b.append_column(aview1(&[1., 2.])), Ok(()));
+    assert_eq!(b.push_column(aview1(&[1., 2.])), Ok(()));
     assert_eq!(b,
         array![[0., 1., 2., 3., 1.],
                [4., 5., 6., 7., 2.]]);
 }
 
 #[test]
-fn append_row_neg_stride_1() {
+fn push_row_neg_stride_1() {
     let mut a = Array::zeros((0, 4));
-    a.append_row(aview1(&[0., 1., 2., 3.])).unwrap();
-    a.append_row(aview1(&[4., 5., 6., 7.])).unwrap();
+    a.push_row(aview1(&[0., 1., 2., 3.])).unwrap();
+    a.push_row(aview1(&[4., 5., 6., 7.])).unwrap();
     assert_eq!(a.shape(), &[2, 4]);
 
     assert_eq!(a,
@@ -75,7 +75,7 @@ fn append_row_neg_stride_1() {
     let mut a2 = a.clone();
     println!("a = {:?}", a);
     println!("a2 = {:?}", a2);
-    a2.append_column(aview1(&[1., 2.])).unwrap();
+    a2.push_column(aview1(&[1., 2.])).unwrap();
     assert_eq!(a2,
         array![[4., 5., 6., 7., 1.],
                [0., 1., 2., 3., 2.]]);
@@ -83,7 +83,7 @@ fn append_row_neg_stride_1() {
 
     a.invert_axis(Axis(1));
     let mut a3 = a.clone();
-    a3.append_row(aview1(&[4., 5., 6., 7.])).unwrap();
+    a3.push_row(aview1(&[4., 5., 6., 7.])).unwrap();
     assert_eq!(a3,
         array![[7., 6., 5., 4.],
                [3., 2., 1., 0.],
@@ -92,7 +92,7 @@ fn append_row_neg_stride_1() {
 
     a.invert_axis(Axis(0));
     let mut a4 = a.clone();
-    a4.append_row(aview1(&[4., 5., 6., 7.])).unwrap();
+    a4.push_row(aview1(&[4., 5., 6., 7.])).unwrap();
     assert_eq!(a4,
         array![[3., 2., 1., 0.],
                [7., 6., 5., 4.],
@@ -101,10 +101,10 @@ fn append_row_neg_stride_1() {
 }
 
 #[test]
-fn append_row_neg_stride_2() {
+fn push_row_neg_stride_2() {
     let mut a = Array::zeros((0, 4));
-    a.append_row(aview1(&[0., 1., 2., 3.])).unwrap();
-    a.append_row(aview1(&[4., 5., 6., 7.])).unwrap();
+    a.push_row(aview1(&[0., 1., 2., 3.])).unwrap();
+    a.push_row(aview1(&[4., 5., 6., 7.])).unwrap();
     assert_eq!(a.shape(), &[2, 4]);
 
     assert_eq!(a,
@@ -118,7 +118,7 @@ fn append_row_neg_stride_2() {
     let mut a2 = a.clone();
     println!("a = {:?}", a);
     println!("a2 = {:?}", a2);
-    a2.append_column(aview1(&[1., 2.])).unwrap();
+    a2.push_column(aview1(&[1., 2.])).unwrap();
     assert_eq!(a2,
         array![[3., 2., 1., 0., 1.],
                [7., 6., 5., 4., 2.]]);
@@ -126,7 +126,7 @@ fn append_row_neg_stride_2() {
 
     a.invert_axis(Axis(0));
     let mut a3 = a.clone();
-    a3.append_row(aview1(&[4., 5., 6., 7.])).unwrap();
+    a3.push_row(aview1(&[4., 5., 6., 7.])).unwrap();
     assert_eq!(a3,
         array![[7., 6., 5., 4.],
                [3., 2., 1., 0.],
@@ -135,7 +135,7 @@ fn append_row_neg_stride_2() {
 
     a.invert_axis(Axis(1));
     let mut a4 = a.clone();
-    a4.append_row(aview1(&[4., 5., 6., 7.])).unwrap();
+    a4.push_row(aview1(&[4., 5., 6., 7.])).unwrap();
     assert_eq!(a4,
         array![[4., 5., 6., 7.],
                [0., 1., 2., 3.],
@@ -144,14 +144,14 @@ fn append_row_neg_stride_2() {
 }
 
 #[test]
-fn append_row_error() {
+fn push_row_error() {
     let mut a = Array::zeros((3, 4));
 
-    assert_eq!(a.append_row(aview1(&[1.])),
+    assert_eq!(a.push_row(aview1(&[1.])),
         Err(ShapeError::from_kind(ErrorKind::IncompatibleShape)));
-    assert_eq!(a.append_column(aview1(&[1.])),
+    assert_eq!(a.push_column(aview1(&[1.])),
         Err(ShapeError::from_kind(ErrorKind::IncompatibleShape)));
-    assert_eq!(a.append_column(aview1(&[1., 2., 3.])),
+    assert_eq!(a.push_column(aview1(&[1., 2., 3.])),
         Ok(()));
     assert_eq!(a.t(),
         array![[0., 0., 0.],
@@ -162,10 +162,10 @@ fn append_row_error() {
 }
 
 #[test]
-fn append_row_existing() {
+fn push_row_existing() {
     let mut a = Array::zeros((1, 4));
-    a.append_row(aview1(&[0., 1., 2., 3.])).unwrap();
-    a.append_row(aview1(&[4., 5., 6., 7.])).unwrap();
+    a.push_row(aview1(&[0., 1., 2., 3.])).unwrap();
+    a.push_row(aview1(&[4., 5., 6., 7.])).unwrap();
     assert_eq!(a.shape(), &[3, 4]);
 
     assert_eq!(a,
@@ -173,11 +173,11 @@ fn append_row_existing() {
                [0., 1., 2., 3.],
                [4., 5., 6., 7.]]);
 
-    assert_eq!(a.append_row(aview1(&[1.])),
+    assert_eq!(a.push_row(aview1(&[1.])),
         Err(ShapeError::from_kind(ErrorKind::IncompatibleShape)));
-    assert_eq!(a.append_column(aview1(&[1.])),
+    assert_eq!(a.push_column(aview1(&[1.])),
         Err(ShapeError::from_kind(ErrorKind::IncompatibleShape)));
-    assert_eq!(a.append_column(aview1(&[1., 2., 3.])),
+    assert_eq!(a.push_column(aview1(&[1., 2., 3.])),
         Ok(()));
     assert_eq!(a,
         array![[0., 0., 0., 0., 1.],
@@ -186,15 +186,15 @@ fn append_row_existing() {
 }
 
 #[test]
-fn append_row_col_len_1() {
+fn push_row_col_len_1() {
     // Test appending 1 row and then cols from shape 1 x 1
     let mut a = Array::zeros((1, 1));
-    a.append_row(aview1(&[1.])).unwrap(); // shape 2 x 1
-    a.append_column(aview1(&[2., 3.])).unwrap(); // shape 2 x 2
-    assert_eq!(a.append_row(aview1(&[1.])),
+    a.push_row(aview1(&[1.])).unwrap(); // shape 2 x 1
+    a.push_column(aview1(&[2., 3.])).unwrap(); // shape 2 x 2
+    assert_eq!(a.push_row(aview1(&[1.])),
         Err(ShapeError::from_kind(ErrorKind::IncompatibleShape)));
-    //assert_eq!(a.append_row(aview1(&[1., 2.])), Err(ShapeError::from_kind(ErrorKind::IncompatibleLayout)));
-    a.append_column(aview1(&[4., 5.])).unwrap(); // shape 2 x 3
+    //assert_eq!(a.push_row(aview1(&[1., 2.])), Err(ShapeError::from_kind(ErrorKind::IncompatibleLayout)));
+    a.push_column(aview1(&[4., 5.])).unwrap(); // shape 2 x 3
     assert_eq!(a.shape(), &[2, 3]);
 
     assert_eq!(a,
@@ -203,10 +203,10 @@ fn append_row_col_len_1() {
 }
 
 #[test]
-fn append_column() {
+fn push_column() {
     let mut a = Array::zeros((4, 0));
-    a.append_column(aview1(&[0., 1., 2., 3.])).unwrap();
-    a.append_column(aview1(&[4., 5., 6., 7.])).unwrap();
+    a.push_column(aview1(&[0., 1., 2., 3.])).unwrap();
+    a.push_column(aview1(&[4., 5., 6., 7.])).unwrap();
     assert_eq!(a.shape(), &[4, 2]);
 
     assert_eq!(a.t(),
@@ -221,7 +221,7 @@ fn append_array1() {
     println!("{:?}", a);
     a.append(Axis(0), aview2(&[[4., 5., 6., 7.]])).unwrap();
     println!("{:?}", a);
-    //a.append_column(aview1(&[4., 5., 6., 7.])).unwrap();
+    //a.push_column(aview1(&[4., 5., 6., 7.])).unwrap();
     //assert_eq!(a.shape(), &[4, 2]);
 
     assert_eq!(a,
@@ -347,18 +347,18 @@ fn test_append_zero_size() {
 }
 
 #[test]
-fn append_row_neg_stride_3() {
+fn push_row_neg_stride_3() {
     let mut a = Array::zeros((0, 4));
-    a.append_row(aview1(&[0., 1., 2., 3.])).unwrap();
+    a.push_row(aview1(&[0., 1., 2., 3.])).unwrap();
     a.invert_axis(Axis(1));
-    a.append_row(aview1(&[4., 5., 6., 7.])).unwrap();
+    a.push_row(aview1(&[4., 5., 6., 7.])).unwrap();
     assert_eq!(a.shape(), &[2, 4]);
     assert_eq!(a, array![[3., 2., 1., 0.], [4., 5., 6., 7.]]);
     assert_eq!(a.strides(), &[4, -1]);
 }
 
 #[test]
-fn append_row_ignore_strides_length_one_axes() {
+fn push_row_ignore_strides_length_one_axes() {
     let strides = &[0, 1, 10, 20];
     for invert in &[vec![], vec![0], vec![1], vec![0, 1]] {
         for &stride0 in strides {
@@ -368,7 +368,7 @@ fn append_row_ignore_strides_length_one_axes() {
                 for &ax in invert {
                     a.invert_axis(Axis(ax));
                 }
-                a.append_row(aview1(&[1.])).unwrap();
+                a.push_row(aview1(&[1.])).unwrap();
                 assert_eq!(a.shape(), &[2, 1]);
                 assert_eq!(a, array![[0.], [1.]]);
                 assert_eq!(a.stride_of(Axis(0)), 1);
@@ -376,3 +376,28 @@ fn append_row_ignore_strides_length_one_axes() {
         }
     }
 }
+
+#[test]
+#[should_panic(expected = "IncompatibleShape")]
+fn zero_dimensional_error1() {
+    let mut a = Array::zeros(()).into_dyn();
+    a.append(Axis(0), arr0(0).into_dyn().view()).unwrap();
+}
+
+#[test]
+#[should_panic(expected = "IncompatibleShape")]
+fn zero_dimensional_error2() {
+    let mut a = Array::zeros(()).into_dyn();
+    a.push(Axis(0), arr0(0).into_dyn().view()).unwrap();
+}
+
+#[test]
+fn zero_dimensional_ok() {
+    let mut a = Array::zeros(0);
+    let one = aview0(&1);
+    let two = aview0(&2);
+    a.push(Axis(0), two).unwrap();
+    a.push(Axis(0), one).unwrap();
+    a.push(Axis(0), one).unwrap();
+    assert_eq!(a, array![2, 1, 1]);
+}