Skip to content

Commit dadef4d

Browse files
committed
FEAT: 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 61acbd3 commit dadef4d

File tree

4 files changed

+257
-2
lines changed

4 files changed

+257
-2
lines changed

src/data_repr.rs

+44
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,35 @@ 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
68+
///
69+
/// ## Safety
70+
///
71+
/// Note that existing pointers into the data are invalidated
72+
pub(crate) unsafe fn reserve(&mut self, additional: usize) {
73+
self.modify_as_vec(|mut v| {
74+
v.reserve(additional);
75+
v
76+
});
77+
}
78+
79+
/// Set the valid length of the data
80+
///
81+
/// ## Safety
82+
///
83+
/// The first `new_len` elements of the data should be valid.
84+
pub(crate) unsafe fn set_len(&mut self, new_len: usize) {
85+
debug_assert!(new_len <= self.capacity);
86+
self.len = new_len;
87+
}
88+
5889
/// Cast self into equivalent repr of other element type
5990
///
6091
/// ## Safety
@@ -72,6 +103,11 @@ impl<A> OwnedRepr<A> {
72103
}
73104
}
74105

106+
fn modify_as_vec(&mut self, f: impl FnOnce(Vec<A>) -> Vec<A>) {
107+
let v = self.take_as_vec();
108+
self.take_vec_back(f(v))
109+
}
110+
75111
fn take_as_vec(&mut self) -> Vec<A> {
76112
let capacity = self.capacity;
77113
let len = self.len;
@@ -81,6 +117,14 @@ impl<A> OwnedRepr<A> {
81117
Vec::from_raw_parts(self.ptr.as_ptr(), len, capacity)
82118
}
83119
}
120+
121+
fn take_vec_back(&mut self, v: Vec<A>) {
122+
debug_assert_eq!(self.capacity, 0, "can only take_vec_back if empty");
123+
let mut v = ManuallyDrop::new(v);
124+
self.ptr = nonnull::nonnull_from_vec_data(&mut v);
125+
self.len = v.len();
126+
self.capacity = v.capacity();
127+
}
84128
}
85129

86130
impl<A> Clone for OwnedRepr<A>

src/impl_owned_array.rs

+145
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,143 @@ 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(&[0., -2., -3., -4.])).unwrap();
92+
///
93+
/// assert_eq!(
94+
/// a,
95+
/// array![[1., 2., 3., 4.],
96+
/// [0., -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.data.reserve(row_len);
119+
self.ptr = self.data.as_nonnull_mut(); // because we are standard order
120+
121+
// recompute strides - if the array was previously empty, it could have
122+
// zeros in strides.
123+
let strides = res_dim.default_strides();
124+
125+
// copy elements from view to the array now
126+
//
127+
// make a raw view with the new row
128+
// safe because the data was "full"
129+
let tail_ptr = self.data.as_end_nonnull();
130+
let tail_view = RawArrayViewMut::new(tail_ptr, Ix1(row_len), Ix1(1));
131+
132+
struct SetLenOnDrop<'a, A: 'a> {
133+
len: usize,
134+
data: &'a mut OwnedRepr<A>,
135+
}
136+
137+
let mut length_guard = SetLenOnDrop {
138+
len: self.data.len(),
139+
data: &mut self.data,
140+
};
141+
142+
impl<A> Drop for SetLenOnDrop<'_, A> {
143+
fn drop(&mut self) {
144+
unsafe {
145+
self.data.set_len(self.len);
146+
}
147+
}
148+
}
149+
150+
// assign the new elements
151+
Zip::from(tail_view).and(row)
152+
.for_each(|to, from| {
153+
to.write(from.clone());
154+
length_guard.len += 1;
155+
});
156+
157+
drop(length_guard);
158+
159+
// update array dimension
160+
self.strides = strides;
161+
self.dim[0] += 1;
162+
163+
}
164+
// multiple assertions after pointer & dimension update
165+
debug_assert_eq!(self.data.len(), self.len());
166+
debug_assert_eq!(self.len(), new_len);
167+
debug_assert!(self.is_standard_layout());
168+
169+
Ok(())
170+
}
171+
172+
/// Append a column to and array with column major memory layout.
173+
///
174+
/// ***Errors*** with a layout error if the array is not in column major order or
175+
/// if it has holes, even exterior holes (from slicing). <br>
176+
/// ***Errors*** with shape error if the length of the input column does not match
177+
/// the length of the columns in the array.<br>
178+
///
179+
/// The memory layout matters, since it determines in which direction the array can easily
180+
/// grow. Notice that an empty array is compatible both ways. The amortized average
181+
/// complexity of the append is O(m) where *m* is the length of the column.
182+
///
183+
/// ```rust
184+
/// use ndarray::{Array, ArrayView, array};
185+
///
186+
/// // create an empty array and append
187+
/// let mut a = Array::zeros((2, 0));
188+
/// a.try_append_column(ArrayView::from(&[1., 2.])).unwrap();
189+
/// a.try_append_column(ArrayView::from(&[0., -2.])).unwrap();
190+
///
191+
/// assert_eq!(
192+
/// a,
193+
/// array![[1., 0.],
194+
/// [2., -2.]]);
195+
/// ```
196+
pub fn try_append_column(&mut self, column: ArrayView<A, Ix1>) -> Result<(), ShapeError>
197+
where
198+
A: Clone,
199+
{
200+
self.swap_axes(0, 1);
201+
let ret = self.try_append_row(column);
202+
self.swap_axes(0, 1);
203+
ret
204+
}
205+
}
206+

src/lib.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,8 @@ pub type Ixs = isize;
232232

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

tests/append.rs

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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_column() {
58+
let mut a = Array::zeros((4, 0));
59+
a.try_append_column(aview1(&[0., 1., 2., 3.])).unwrap();
60+
a.try_append_column(aview1(&[4., 5., 6., 7.])).unwrap();
61+
assert_eq!(a.shape(), &[4, 2]);
62+
63+
assert_eq!(a.t(),
64+
array![[0., 1., 2., 3.],
65+
[4., 5., 6., 7.]]);
66+
}

0 commit comments

Comments
 (0)