Skip to content

Commit f1d13d3

Browse files
committed
FEAT: .try_append_array() to append array to array
Allow appending an array along an axis. This only succeeds if the axis is an appropriate growing axis, where this can be done without moving existing elements.
1 parent 4f58d2e commit f1d13d3

File tree

2 files changed

+164
-0
lines changed

2 files changed

+164
-0
lines changed

src/impl_owned_array.rs

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,3 +204,144 @@ impl<A> Array<A, Ix2> {
204204
}
205205
}
206206

207+
impl<A, D> Array<A, D>
208+
where D: Dimension
209+
{
210+
/// Append a row to an array with row major memory layout.
211+
///
212+
/// ***Errors*** with a layout error if the array is not in standard order or
213+
/// if it has holes, even exterior holes (from slicing). <br>
214+
/// ***Errors*** with shape error if the length of the input row does not match
215+
/// the length of the rows in the array. <br>
216+
///
217+
/// The memory layout matters, since it determines in which direction the array can easily
218+
/// grow. Notice that an empty array is compatible both ways. The amortized average
219+
/// complexity of the append is O(m) where *m* is the length of the row.
220+
///
221+
/// ```rust
222+
/// use ndarray::{Array, ArrayView, array};
223+
///
224+
/// // create an empty array and append
225+
/// let mut a = Array::zeros((0, 4));
226+
/// a.try_append_row(ArrayView::from(&[1., 2., 3., 4.])).unwrap();
227+
/// a.try_append_row(ArrayView::from(&[0., -2., -3., -4.])).unwrap();
228+
///
229+
/// assert_eq!(
230+
/// a,
231+
/// array![[1., 2., 3., 4.],
232+
/// [0., -2., -3., -4.]]);
233+
/// ```
234+
pub fn try_append_array(&mut self, axis: Axis, array: ArrayView<A, D>)
235+
-> Result<(), ShapeError>
236+
where
237+
A: Clone,
238+
D: RemoveAxis,
239+
{
240+
if self.ndim() == 0 {
241+
return Err(ShapeError::from_kind(ErrorKind::IncompatibleShape));
242+
}
243+
244+
let remaining_shape = self.raw_dim().remove_axis(axis);
245+
let array_rem_shape = array.raw_dim().remove_axis(axis);
246+
247+
if remaining_shape != array_rem_shape {
248+
return Err(ShapeError::from_kind(ErrorKind::IncompatibleShape));
249+
}
250+
251+
let len_to_append = array.len();
252+
if len_to_append == 0 {
253+
return Ok(());
254+
}
255+
256+
let array_shape = array.raw_dim();
257+
let mut res_dim = self.raw_dim();
258+
res_dim[axis.index()] += array_shape[axis.index()];
259+
let new_len = dimension::size_of_shape_checked(&res_dim)?;
260+
261+
let self_is_empty = self.is_empty();
262+
263+
// array must be empty or have `axis` as the outermost (longest stride)
264+
// axis
265+
if !(self_is_empty ||
266+
self.axes().max_by_key(|ax| ax.stride).map(|ax| ax.axis) == Some(axis))
267+
{
268+
return Err(ShapeError::from_kind(ErrorKind::IncompatibleLayout));
269+
}
270+
271+
// array must be be "full" (have no exterior holes)
272+
if self.len() != self.data.len() {
273+
return Err(ShapeError::from_kind(ErrorKind::IncompatibleLayout));
274+
}
275+
let strides = if self_is_empty {
276+
// recompute strides - if the array was previously empty, it could have
277+
// zeros in strides.
278+
res_dim.default_strides()
279+
} else {
280+
self.strides.clone()
281+
};
282+
283+
unsafe {
284+
// grow backing storage and update head ptr
285+
debug_assert_eq!(self.data.as_ptr(), self.as_ptr());
286+
self.data.reserve(len_to_append);
287+
self.ptr = self.data.as_nonnull_mut(); // because we are standard order
288+
289+
// copy elements from view to the array now
290+
//
291+
// make a raw view with the new row
292+
// safe because the data was "full"
293+
let tail_ptr = self.data.as_end_nonnull();
294+
let tail_view = RawArrayViewMut::new(tail_ptr, array_shape, strides.clone());
295+
296+
struct SetLenOnDrop<'a, A: 'a> {
297+
len: usize,
298+
data: &'a mut OwnedRepr<A>,
299+
}
300+
301+
let mut length_guard = SetLenOnDrop {
302+
len: self.data.len(),
303+
data: &mut self.data,
304+
};
305+
306+
impl<A> Drop for SetLenOnDrop<'_, A> {
307+
fn drop(&mut self) {
308+
unsafe {
309+
self.data.set_len(self.len);
310+
}
311+
}
312+
}
313+
314+
// we have a problem here XXX
315+
//
316+
// To be robust for panics and drop the right elements, we want
317+
// to fill the tail in-order, so that we can drop the right elements on
318+
// panic. Don't know how to achieve that.
319+
//
320+
// It might be easier to retrace our steps in a scope guard to drop the right
321+
// elements.. (PartialArray style).
322+
//
323+
// assign the new elements
324+
Zip::from(tail_view).and(array)
325+
.for_each(|to, from| {
326+
to.write(from.clone());
327+
length_guard.len += 1;
328+
});
329+
330+
//length_guard.len += len_to_append;
331+
dbg!(len_to_append);
332+
drop(length_guard);
333+
334+
// update array dimension
335+
self.strides = strides;
336+
self.dim = res_dim;
337+
dbg!(&self.dim);
338+
339+
}
340+
// multiple assertions after pointer & dimension update
341+
debug_assert_eq!(self.data.len(), self.len());
342+
debug_assert_eq!(self.len(), new_len);
343+
debug_assert!(self.is_standard_layout());
344+
345+
Ok(())
346+
}
347+
}

tests/append.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,26 @@ fn append_column() {
8282
array![[0., 1., 2., 3.],
8383
[4., 5., 6., 7.]]);
8484
}
85+
86+
#[test]
87+
fn append_array1() {
88+
let mut a = Array::zeros((0, 4));
89+
a.try_append_array(Axis(0), aview2(&[[0., 1., 2., 3.]])).unwrap();
90+
println!("{:?}", a);
91+
a.try_append_array(Axis(0), aview2(&[[4., 5., 6., 7.]])).unwrap();
92+
println!("{:?}", a);
93+
//a.try_append_column(aview1(&[4., 5., 6., 7.])).unwrap();
94+
//assert_eq!(a.shape(), &[4, 2]);
95+
96+
assert_eq!(a,
97+
array![[0., 1., 2., 3.],
98+
[4., 5., 6., 7.]]);
99+
100+
a.try_append_array(Axis(0), aview2(&[[5., 5., 4., 4.], [3., 3., 2., 2.]])).unwrap();
101+
println!("{:?}", a);
102+
assert_eq!(a,
103+
array![[0., 1., 2., 3.],
104+
[4., 5., 6., 7.],
105+
[5., 5., 4., 4.],
106+
[3., 3., 2., 2.]]);
107+
}

0 commit comments

Comments
 (0)