Skip to content

Commit 13dab58

Browse files
committed
append: Add try_append_row and try_append_column
For Array2 specifically, and only when the memory layout lines up for efficient and simple append, allow appending rows and/or columns to an array.
1 parent 115b2af commit 13dab58

File tree

4 files changed

+268
-2
lines changed

4 files changed

+268
-2
lines changed

src/data_repr.rs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ use alloc::borrow::ToOwned;
66
use alloc::vec::Vec;
77
use crate::extension::nonnull;
88

9+
use rawpointer::PointerExt;
10+
911
/// Array's representation.
1012
///
1113
/// *Don’t use this type directly—use the type alias
@@ -55,6 +57,37 @@ impl<A> OwnedRepr<A> {
5557
self.ptr
5658
}
5759

60+
/// Return end pointer
61+
pub(crate) fn as_end_nonnull(&self) -> NonNull<A> {
62+
unsafe {
63+
self.ptr.add(self.len)
64+
}
65+
}
66+
67+
/// Reserve `additional` elements; return the new pointer
68+
///
69+
/// ## Safety
70+
///
71+
/// Note that existing pointers into the data are invalidated
72+
#[must_use = "must use new pointer to update existing pointers"]
73+
pub(crate) fn reserve(&mut self, additional: usize) -> NonNull<A> {
74+
self.modify_as_vec(|mut v| {
75+
v.reserve(additional);
76+
v
77+
});
78+
self.as_nonnull_mut()
79+
}
80+
81+
/// Set the valid length of the data
82+
///
83+
/// ## Safety
84+
///
85+
/// The first `new_len` elements of the data should be valid.
86+
pub(crate) unsafe fn set_len(&mut self, new_len: usize) {
87+
debug_assert!(new_len <= self.capacity);
88+
self.len = new_len;
89+
}
90+
5891
/// Cast self into equivalent repr of other element type
5992
///
6093
/// ## Safety
@@ -72,6 +105,11 @@ impl<A> OwnedRepr<A> {
72105
}
73106
}
74107

108+
fn modify_as_vec(&mut self, f: impl FnOnce(Vec<A>) -> Vec<A>) {
109+
let v = self.take_as_vec();
110+
*self = Self::from(f(v));
111+
}
112+
75113
fn take_as_vec(&mut self) -> Vec<A> {
76114
let capacity = self.capacity;
77115
let len = self.len;

src/impl_owned_array.rs

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11

22
use alloc::vec::Vec;
3+
34
use crate::imp_prelude::*;
5+
use crate::dimension;
6+
use crate::error::{ErrorKind, ShapeError};
7+
use crate::OwnedRepr;
8+
use crate::Zip;
49

510
/// Methods specific to `Array0`.
611
///
@@ -59,3 +64,142 @@ where
5964
self.data.into_vec()
6065
}
6166
}
67+
68+
/// Methods specific to `Array2`.
69+
///
70+
/// ***See also all methods for [`ArrayBase`]***
71+
///
72+
/// [`ArrayBase`]: struct.ArrayBase.html
73+
impl<A> Array<A, Ix2> {
74+
/// Append a row to an array with row major memory layout.
75+
///
76+
/// ***Errors*** with a layout error if the array is not in standard order or
77+
/// if it has holes, even exterior holes (from slicing). <br>
78+
/// ***Errors*** with shape error if the length of the input row does not match
79+
/// the length of the rows in the array. <br>
80+
///
81+
/// The memory layout matters, since it determines in which direction the array can easily
82+
/// grow. Notice that an empty array is compatible both ways. The amortized average
83+
/// complexity of the append is O(m) where *m* is the length of the row.
84+
///
85+
/// ```rust
86+
/// use ndarray::{Array, ArrayView, array};
87+
///
88+
/// // create an empty array and append
89+
/// let mut a = Array::zeros((0, 4));
90+
/// a.try_append_row(ArrayView::from(&[ 1., 2., 3., 4.])).unwrap();
91+
/// a.try_append_row(ArrayView::from(&[-1., -2., -3., -4.])).unwrap();
92+
///
93+
/// assert_eq!(
94+
/// a,
95+
/// array![[ 1., 2., 3., 4.],
96+
/// [-1., -2., -3., -4.]]);
97+
/// ```
98+
pub fn try_append_row(&mut self, row: ArrayView<A, Ix1>) -> Result<(), ShapeError>
99+
where
100+
A: Clone,
101+
{
102+
let row_len = row.len();
103+
if row_len != self.len_of(Axis(1)) {
104+
return Err(ShapeError::from_kind(ErrorKind::IncompatibleShape));
105+
}
106+
let mut res_dim = self.raw_dim();
107+
res_dim[0] += 1;
108+
let new_len = dimension::size_of_shape_checked(&res_dim)?;
109+
110+
// array must be c-contiguous and be "full" (have no exterior holes)
111+
if !self.is_standard_layout() || self.len() != self.data.len() {
112+
return Err(ShapeError::from_kind(ErrorKind::IncompatibleLayout));
113+
}
114+
115+
unsafe {
116+
// grow backing storage and update head ptr
117+
debug_assert_eq!(self.data.as_ptr(), self.as_ptr());
118+
self.ptr = self.data.reserve(row_len); // because we are standard order
119+
120+
// recompute strides - if the array was previously empty, it could have
121+
// zeros in strides.
122+
let strides = res_dim.default_strides();
123+
124+
// copy elements from view to the array now
125+
//
126+
// make a raw view with the new row
127+
// safe because the data was "full"
128+
let tail_ptr = self.data.as_end_nonnull();
129+
let tail_view = RawArrayViewMut::new(tail_ptr, Ix1(row_len), Ix1(1));
130+
131+
struct SetLenOnDrop<'a, A: 'a> {
132+
len: usize,
133+
data: &'a mut OwnedRepr<A>,
134+
}
135+
136+
let mut length_guard = SetLenOnDrop {
137+
len: self.data.len(),
138+
data: &mut self.data,
139+
};
140+
141+
impl<A> Drop for SetLenOnDrop<'_, A> {
142+
fn drop(&mut self) {
143+
unsafe {
144+
self.data.set_len(self.len);
145+
}
146+
}
147+
}
148+
149+
// assign the new elements
150+
Zip::from(tail_view).and(row)
151+
.for_each(|to, from| {
152+
to.write(from.clone());
153+
length_guard.len += 1;
154+
});
155+
156+
drop(length_guard);
157+
158+
// update array dimension
159+
self.strides = strides;
160+
self.dim[0] += 1;
161+
162+
}
163+
// multiple assertions after pointer & dimension update
164+
debug_assert_eq!(self.data.len(), self.len());
165+
debug_assert_eq!(self.len(), new_len);
166+
debug_assert!(self.is_standard_layout());
167+
168+
Ok(())
169+
}
170+
171+
/// Append a column to an array with column major memory layout.
172+
///
173+
/// ***Errors*** with a layout error if the array is not in column major order or
174+
/// if it has holes, even exterior holes (from slicing). <br>
175+
/// ***Errors*** with shape error if the length of the input column does not match
176+
/// the length of the columns in the array.<br>
177+
///
178+
/// The memory layout matters, since it determines in which direction the array can easily
179+
/// grow. Notice that an empty array is compatible both ways. The amortized average
180+
/// complexity of the append is O(m) where *m* is the length of the column.
181+
///
182+
/// ```rust
183+
/// use ndarray::{Array, ArrayView, array};
184+
///
185+
/// // create an empty array and append
186+
/// let mut a = Array::zeros((2, 0));
187+
/// a.try_append_column(ArrayView::from(&[1., 2.])).unwrap();
188+
/// a.try_append_column(ArrayView::from(&[-1., -2.])).unwrap();
189+
///
190+
/// assert_eq!(
191+
/// a,
192+
/// array![[1., -1.],
193+
/// [2., -2.]]);
194+
/// ```
195+
pub fn try_append_column(&mut self, column: ArrayView<A, Ix1>) -> Result<(), ShapeError>
196+
where
197+
A: Clone,
198+
{
199+
self.swap_axes(0, 1);
200+
let ret = self.try_append_row(column);
201+
self.swap_axes(0, 1);
202+
ret
203+
}
204+
}
205+

src/lib.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -235,8 +235,8 @@ pub type Ixs = isize;
235235

236236
/// An *n*-dimensional array.
237237
///
238-
/// The array is a general container of elements. It cannot grow or shrink, but
239-
/// can be sliced into subsets of its data.
238+
/// The array is a general container of elements. It cannot grow or shrink (with some exceptions),
239+
/// but can be sliced into subsets of its data.
240240
/// The array supports arithmetic operations by applying them elementwise.
241241
///
242242
/// In *n*-dimensional we include for example 1-dimensional rows or columns,

tests/append.rs

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
2+
use ndarray::prelude::*;
3+
use ndarray::{ShapeError, ErrorKind};
4+
5+
#[test]
6+
fn append_row() {
7+
let mut a = Array::zeros((0, 4));
8+
a.try_append_row(aview1(&[0., 1., 2., 3.])).unwrap();
9+
a.try_append_row(aview1(&[4., 5., 6., 7.])).unwrap();
10+
assert_eq!(a.shape(), &[2, 4]);
11+
12+
assert_eq!(a,
13+
array![[0., 1., 2., 3.],
14+
[4., 5., 6., 7.]]);
15+
16+
assert_eq!(a.try_append_row(aview1(&[1.])),
17+
Err(ShapeError::from_kind(ErrorKind::IncompatibleShape)));
18+
assert_eq!(a.try_append_column(aview1(&[1.])),
19+
Err(ShapeError::from_kind(ErrorKind::IncompatibleShape)));
20+
assert_eq!(a.try_append_column(aview1(&[1., 2.])),
21+
Err(ShapeError::from_kind(ErrorKind::IncompatibleLayout)));
22+
}
23+
24+
#[test]
25+
fn append_row_error() {
26+
let mut a = Array::zeros((3, 4));
27+
28+
assert_eq!(a.try_append_row(aview1(&[1.])),
29+
Err(ShapeError::from_kind(ErrorKind::IncompatibleShape)));
30+
assert_eq!(a.try_append_column(aview1(&[1.])),
31+
Err(ShapeError::from_kind(ErrorKind::IncompatibleShape)));
32+
assert_eq!(a.try_append_column(aview1(&[1., 2., 3.])),
33+
Err(ShapeError::from_kind(ErrorKind::IncompatibleLayout)));
34+
}
35+
36+
#[test]
37+
fn append_row_existing() {
38+
let mut a = Array::zeros((1, 4));
39+
a.try_append_row(aview1(&[0., 1., 2., 3.])).unwrap();
40+
a.try_append_row(aview1(&[4., 5., 6., 7.])).unwrap();
41+
assert_eq!(a.shape(), &[3, 4]);
42+
43+
assert_eq!(a,
44+
array![[0., 0., 0., 0.],
45+
[0., 1., 2., 3.],
46+
[4., 5., 6., 7.]]);
47+
48+
assert_eq!(a.try_append_row(aview1(&[1.])),
49+
Err(ShapeError::from_kind(ErrorKind::IncompatibleShape)));
50+
assert_eq!(a.try_append_column(aview1(&[1.])),
51+
Err(ShapeError::from_kind(ErrorKind::IncompatibleShape)));
52+
assert_eq!(a.try_append_column(aview1(&[1., 2., 3.])),
53+
Err(ShapeError::from_kind(ErrorKind::IncompatibleLayout)));
54+
}
55+
56+
#[test]
57+
fn append_row_col_len_1() {
58+
// Test appending 1 row and then cols from shape 1 x 1
59+
let mut a = Array::zeros((1, 1));
60+
a.try_append_row(aview1(&[1.])).unwrap(); // shape 2 x 1
61+
a.try_append_column(aview1(&[2., 3.])).unwrap(); // shape 2 x 2
62+
assert_eq!(a.try_append_row(aview1(&[1.])),
63+
Err(ShapeError::from_kind(ErrorKind::IncompatibleShape)));
64+
assert_eq!(a.try_append_row(aview1(&[1., 2.])),
65+
Err(ShapeError::from_kind(ErrorKind::IncompatibleLayout)));
66+
a.try_append_column(aview1(&[4., 5.])).unwrap(); // shape 2 x 3
67+
assert_eq!(a.shape(), &[2, 3]);
68+
69+
assert_eq!(a,
70+
array![[0., 2., 4.],
71+
[1., 3., 5.]]);
72+
}
73+
74+
#[test]
75+
fn append_column() {
76+
let mut a = Array::zeros((4, 0));
77+
a.try_append_column(aview1(&[0., 1., 2., 3.])).unwrap();
78+
a.try_append_column(aview1(&[4., 5., 6., 7.])).unwrap();
79+
assert_eq!(a.shape(), &[4, 2]);
80+
81+
assert_eq!(a.t(),
82+
array![[0., 1., 2., 3.],
83+
[4., 5., 6., 7.]]);
84+
}

0 commit comments

Comments
 (0)