Skip to content

Commit c569f75

Browse files
committed
Add docs about nested Vecs/Arrays
Fixes #609 (assuming we don't want to actually add conversion methods).
1 parent 19a09ca commit c569f75

File tree

1 file changed

+97
-1
lines changed

1 file changed

+97
-1
lines changed

src/lib.rs

+97-1
Original file line numberDiff line numberDiff line change
@@ -939,7 +939,8 @@ pub type Ixs = isize;
939939
/// <sup><a name="req_contig">3</a></sup>Works only if the array is contiguous.
940940
///
941941
/// The table above does not include all the constructors; it only shows
942-
/// conversions to/from `Vec`s/slices. See below for more constructors.
942+
/// conversions to/from `Vec`s/slices. See
943+
/// [below](#constructor-methods-for-owned-arrays) for more constructors.
943944
///
944945
/// [ArrayView::reborrow()]: type.ArrayView.html#method.reborrow
945946
/// [ArrayViewMut::reborrow()]: type.ArrayViewMut.html#method.reborrow
@@ -952,6 +953,101 @@ pub type Ixs = isize;
952953
/// [.view()]: #method.view
953954
/// [.view_mut()]: #method.view_mut
954955
///
956+
/// ### Conversions from Nested `Vec`s/`Array`s
957+
///
958+
/// It's generally a good idea to avoid nested `Vec`/`Array` types, such as
959+
/// `Vec<Vec<A>>` or `Vec<Array2<A>>` because:
960+
///
961+
/// * they require extra heap allocations compared to a single `Array`,
962+
///
963+
/// * they can scatter data all over memory (because of multiple allocations),
964+
///
965+
/// * they cause unnecessary indirection (traversing multiple pointers to reach
966+
/// the data),
967+
///
968+
/// * they don't enforce consistent shape within the nested
969+
/// `Vec`s/`ArrayBase`s, and
970+
///
971+
/// * they are generally more difficult to work with.
972+
///
973+
/// The most common case where users might consider using nested
974+
/// `Vec`s/`Array`s is when creating an array by appending rows/subviews in a
975+
/// loop, where the rows/subviews are computed within the loop. However, there
976+
/// are better ways than using nested `Vec`s/`Array`s.
977+
///
978+
/// If you know ahead-of-time the shape of the final array, the cleanest
979+
/// solution is to allocate the final array before the loop, and then assign
980+
/// the data to it within the loop, like this:
981+
///
982+
/// ```rust
983+
/// use ndarray::{array, Array2, Axis};
984+
///
985+
/// let mut arr = Array2::zeros((2, 3));
986+
/// for (i, mut row) in arr.axis_iter_mut(Axis(0)).enumerate() {
987+
/// // Perform calculations and assign to `row`; this is a trivial example:
988+
/// row.fill(i);
989+
/// }
990+
/// assert_eq!(arr, array![[0, 0, 0], [1, 1, 1]]);
991+
/// ```
992+
///
993+
/// If you don't know ahead-of-time the shape of the final array, then the
994+
/// cleanest solution is generally to append the data to a flat `Vec`, and then
995+
/// convert it to an `Array` at the end with
996+
/// [`::from_shape_vec()`](#method.from_shape_vec). You just have to be careful
997+
/// that the layout of the data (the order of the elements in the flat `Vec`)
998+
/// is correct.
999+
///
1000+
/// ```rust
1001+
/// use ndarray::{array, Array2};
1002+
///
1003+
/// # fn main() -> Result<(), Box<std::error::Error>> {
1004+
/// let ncols = 3;
1005+
/// let mut data = Vec::new();
1006+
/// let mut nrows = 0;
1007+
/// for i in 0..2 {
1008+
/// // Compute `row` and append it to `data`; this is a trivial example:
1009+
/// let row = vec![i; ncols];
1010+
/// data.extend_from_slice(&row);
1011+
/// nrows += 1;
1012+
/// }
1013+
/// let arr = Array2::from_shape_vec((nrows, ncols), data)?;
1014+
/// assert_eq!(arr, array![[0, 0, 0], [1, 1, 1]]);
1015+
/// # Ok(())
1016+
/// # }
1017+
/// ```
1018+
///
1019+
/// If neither of these options works for you, and you really need to convert
1020+
/// nested `Vec`/`Array` instances to an `Array`, the cleanest solution is
1021+
/// generally to use
1022+
/// [`Iterator::flatten()`](https://doc.rust-lang.org/std/iter/trait.Iterator.html#method.flatten)
1023+
/// to get a flat `Vec`, and then convert the `Vec` to an `Array` with
1024+
/// [`::from_shape_vec()`](#method.from_shape_vec), like this:
1025+
///
1026+
/// ```rust
1027+
/// use ndarray::{array, Array2, Array3};
1028+
///
1029+
/// # fn main() -> Result<(), Box<std::error::Error>> {
1030+
/// let nested: Vec<Array2<i32>> = vec![
1031+
/// array![[1, 2, 3], [4, 5, 6]],
1032+
/// array![[7, 8, 9], [10, 11, 12]],
1033+
/// ];
1034+
/// let inner_shape = nested[0].dim();
1035+
/// let shape = (nested.len(), inner_shape.0, inner_shape.1);
1036+
/// let flat: Vec<i32> = nested.iter().flatten().cloned().collect();
1037+
/// let arr = Array3::from_shape_vec(shape, flat)?;
1038+
/// assert_eq!(arr, array![
1039+
/// [[1, 2, 3], [4, 5, 6]],
1040+
/// [[7, 8, 9], [10, 11, 12]],
1041+
/// ]);
1042+
/// # Ok(())
1043+
/// # }
1044+
/// ```
1045+
///
1046+
/// Note that this implementation assumes that the nested `Vec`s are all the
1047+
/// same shape and that the `Vec` is non-empty. Depending on your application,
1048+
/// it may be a good idea to add checks for these assumptions and possibly
1049+
/// choose a different way to handle the empty case.
1050+
///
9551051
// # For implementors
9561052
//
9571053
// All methods must uphold the following constraints:

0 commit comments

Comments
 (0)