n_circular_array/
lib.rs

1//! # N Circular Array
2//! An n-dimensional circular array.
3//!
4//! ## Features
5//!
6//! - Fixed dimension arrays of any size.
7//! - Element retrieval by `N` dimensional index, range or slice.
8//! - Element insertion to the front or back of any axis.
9//! - `N` dimensional translation over a source array.
10//! - Element iteration in sequentual or contiguous order.
11//! - Support for external types through `AsRef<[T]>` and `AsMut<[T]>`.
12//! - Thorough testing for arrays of smaller dimensionality.
13//! - No external dependencies.
14//!
15//! ## Mutation
16//!
17//! `n_circular_array` supports the following mutating operations:
18//! - Insert elements to either side of an axis.
19//! - Translate over a source array.
20//! - Mutate individual elements.
21//!
22//! ### Insertion
23//!
24//! Elements are inserted by providing a **row-major** slice or iterator with a length
25//! equal to an **exact** multiple of the given axis length. That is, a call to insert
26//! two rows must be provided **exactly** two rows of elements.
27//!
28//! ```
29//! # use n_circular_array::{CircularArrayVec, CircularIndex, CircularMut};
30//! // A 2-dimensional circular array of 3*3 elements.
31//! let mut array = CircularArrayVec::new([3, 3], vec![
32//!     0, 1, 2,
33//!     3, 4, 5,
34//!     6, 7, 8
35//! ]);
36//!
37//! // Push the elements 9..12 (one row) to the front of axis 1.
38//! array.push_front(1, &[
39//!      9, 10, 11,
40//! ]);
41//! assert_eq!(array.iter().cloned().collect::<Vec<usize>>(), &[
42//!      3,  4,  5,
43//!      6,  7,  8,
44//!      9, 10, 11,
45//! ]);
46//!
47//! // Push the elements 12..18 (two columns) to the front of axis 0.
48//! let axis_len = array.shape()[0];
49//! array.push_front(0, &[12, 13, 14, 15, 16, 17]);
50//! assert_eq!(array.iter().cloned().collect::<Vec<usize>>(), &[
51//!       5, 12, 13,
52//!       8, 14, 15,
53//!      11, 16, 17,
54//! ]);
55//!
56//! // Push the elements 19..22 (one row) to the back of axis 1.
57//! array.push_back(1, &[
58//!      19, 20, 21,
59//! ]);
60//! assert_eq!(array.iter().cloned().collect::<Vec<usize>>(), &[
61//!      19, 20, 21,
62//!       5, 12, 13,
63//!       8, 14, 15,
64//! ]);
65//! ```
66//!
67//! ### Translation
68//!
69//! Translation methods simplify mapping the elements of a *source* array to the circular
70//! array. Translation methods expect the array `origin`, or the position of the
71//! `[0; N]` element within the source array, and a translation on an axis. The provided
72//! `el_fn` function will recieve contiguous `[Range<usize>; N]` slices for mapping
73//! the new elements from the source to the circular array. `CircularArray` **only**
74//! handles slicing and mutation, and translation logic (the current translation, out of
75//! bound translation etc.) must be maintained by the user.
76//!
77//! In the following example, rather than passing the `[Range<usize>; N]` slice to a
78//! 3rd-party crate, we define the source array [`Strides`](strides::Strides),
79//! then call [`Strides::flatten_range`](strides::Strides::flatten_range) to get
80//! a single contiguous range for slicing (requires feature `strides`).
81//! ```
82//! # #[cfg(feature = "strides")] {
83//! # use std::ops::Range;
84//! # use n_circular_array::{CircularArray, CircularIndex, CircularMut, Strides};
85//! // A [5, 5] source array.
86//! let src = [
87//!      0,  1,  2,  3,  4,
88//!      5,  6,  7,  8,  9,
89//!     10, 11, 12, 13, 14,
90//!     15, 16, 17, 18, 19,
91//!     20, 21, 22, 23, 24,
92//! ];
93//! // Strides used for flattening `N` dimensional indices.
94//! let src_strides = Strides::new(&[5, 5]);
95//!
96//! // Slice function.
97//! let el_fn = |mut index: [Range<usize>; 2]| {
98//!     &src[src_strides.flatten_range(index)]
99//! };
100//!
101//! // A [3, 3] circular array positioned at `[0, 0]`.
102//! let mut origin = [0, 0];
103//! let mut dst = CircularArray::new([3, 3], vec![
104//!      0,  1,  2,
105//!      5,  6,  7,
106//!     10, 11, 12
107//! ]);
108//!
109//! // Translate by 2 on axis 0 (Pushes 2 columns to front of axis 0).
110//! let axis = 0;
111//! let n = 2;
112//! dst.translate_front(axis, n, origin, el_fn);
113//! origin[axis] += n as usize;
114//!
115//! assert_eq!(dst.iter().cloned().collect::<Vec<usize>>(), &[
116//!      2,  3,  4,
117//!      7,  8,  9,
118//!     12, 13, 14,
119//! ]);
120//!
121//! // Translate by 1 on axis 1 (Pushes 1 row to front of axis 1).
122//! let axis = 1;
123//! let n = 1;
124//! dst.translate_front(axis, n, origin, el_fn);
125//! origin[axis] += n as usize;
126//!
127//! assert_eq!(dst.iter().cloned().collect::<Vec<usize>>(), &[
128//!      7,  8,  9,
129//!     12, 13, 14,
130//!     17, 18, 19,
131//! ]);
132//!
133//! assert_eq!(origin, [2, 1]);
134//! # }
135//! ```
136//!
137//! ## Indexing and Slicing
138//!
139//! `n_circular_array` supports the following indexing operations:
140//! - Access elements by axis slice.
141//! - Access elements by `N` dimensional slice.
142//! - Access individual elements by index.
143//!
144//! ### Slicing an axis
145//!
146//! All elements of an axis can be iterated over by index or range. Calling
147//! [`CircularIndex::iter_index`] returns an iterator of elements of a shape
148//! equal to the shape of the circular array, with the specified axis set to `1`.
149//! Calling [`CircularIndex::iter_range`] returns an iterator of elements of a
150//! shape equal to the shape of the circular array, with the specified axis set to
151//! the length of the given range.
152//!
153//! ```
154//! # use n_circular_array::{CircularArrayVec, CircularIndex};
155//! // A 3-dimensional circular array of 3*3*2 elements.
156//! let array = CircularArrayVec::new([3, 3, 2], vec![
157//!      0,  1,  2,
158//!      3,  4,  5,
159//!      6,  7,  8,
160//!
161//!      9, 10, 11,
162//!     12, 13, 14,
163//!     15, 16, 17,
164//! ]);
165//!
166//! // Iterate over index 1 of axis 0 (shape [1, 3, 2]).
167//! assert_eq!(array.iter_index(0, 1).cloned().collect::<Vec<usize>>(), &[
168//!      1,
169//!      4,
170//!      7,
171//!
172//!     10,
173//!     13,
174//!     16,
175//! ]);
176//! // Iterate over indices 1..3 of axis 1 (shape [3, 2, 2]).
177//! assert_eq!(array.iter_range(1, 1..3).cloned().collect::<Vec<usize>>(), &[
178//!      3,  4,  5,
179//!      6,  7,  8,
180//!
181//!     12, 13, 14,
182//!     15, 16, 17,
183//! ]);
184//! ```
185//!
186//! ### Slicing the array
187//!
188//! Calling [`CircularIndex::iter_slice`] can be used to iterate over an `N`
189//! dimensional slice of the array. This can be used to limit iteration to an
190//! exact subset of elements.
191//!
192//! ```
193//! # use n_circular_array::{CircularArrayVec, CircularIndex};
194//! // A 3-dimensional circular array of 3*3*2 elements.
195//! let array = CircularArrayVec::new([3, 3, 2], vec![
196//!      0,  1,  2,
197//!      3,  4,  5,
198//!      6,  7,  8,
199//!
200//!      9, 10, 11,
201//!     12, 13, 14,
202//!     15, 16, 17,
203//! ]);
204//!
205//! // Iterate over:
206//! //     - index 1 of axis 0,
207//! //     - range 0..3 of axis 1 (all elements),
208//! //     - index 1 of axis 2.
209//! // (shape [1, 2, 1], equivalent to [2]).
210//! assert_eq!(array.iter_slice([1..2, 0..3, 1..2]).cloned().collect::<Vec<usize>>(), &[
211//!     10,
212//!     13,
213//!     16,
214//! ]);
215//! // Iterate over:
216//! //     - range 0..2 of axis 0,
217//! //     - range 1..3 of axis 1,
218//! //     - index 0 of axis 2.
219//! // (shape [2, 2, 1], equivalent to [2, 2]).
220//! assert_eq!(array.iter_slice([0..2, 1..3, 0..1]).cloned().collect::<Vec<usize>>(), &[
221//!      3,  4,
222//!      6,  7,
223//! ]);
224//! ```
225//!
226//! `n_circular_array` resizing or reshaping functionality can be achieved by using
227//! [`CircularIndex::iter_slice`] and collecting into a new array.
228//!
229//! ```
230//! # use n_circular_array::{CircularArrayVec, CircularIndex};
231//! // A 3-dimensional circular array of 3*3*2 elements.
232//! let array3 = CircularArrayVec::new([3, 3, 2], vec![
233//!      0,  1,  2,
234//!      3,  4,  5,
235//!      6,  7,  8,
236//!
237//!      9, 10, 11,
238//!     12, 13, 14,
239//!     15, 16, 17,
240//! ]);
241//!
242//! // Iterate over:
243//! //     - range 0..2 of axis 0,
244//! //     - range 1..3 of axis 1,
245//! //     - index 0 of axis 2.
246//! // (shape [2, 2, 1], equivalent to [2, 2]).
247//! let iter = array3.iter_slice([0..2, 1..3, 0..1]).cloned();
248//!
249//! // A 2-dimensional circular array of 3*2 elements.
250//! let array2 = CircularArrayVec::from_iter([2, 2], iter);
251//!
252//! assert_eq!(array2.iter().cloned().collect::<Vec<usize>>(), &[
253//!      3,  4,
254//!      6,  7,
255//! ]);
256//! ```
257//!
258//! ### Index and IndexMut
259//!
260//! Finally, `n_circular_array` supports [`std::ops::Index`] and [`std::ops::IndexMut`]
261//! taking an `N` dimensional index (`[usize; N]`) as argument.
262//!
263//! ```
264//! # use n_circular_array::{CircularArrayVec, CircularIndex, CircularMut};
265//! // A 2-dimensional circular array of 3*3 elements.
266//! let mut array = CircularArrayVec::new([3, 3], vec![
267//!     0, 1, 2,
268//!     3, 4, 5,
269//!     6, 7, 8
270//! ]);
271//!
272//! array[[1, 1]] += 10;
273//! assert_eq!(array.iter().cloned().collect::<Vec<usize>>(), &[
274//!     0,  1, 2,
275//!     3, 14, 5,
276//!     6,  7, 8
277//! ]);
278//! ```
279//!
280//! ## Raw and Contiguous Operations
281//!
282//! `n_circular_array` also include operations for iterating over elements of the
283//! array while only offsetting the data of a subset of axes. `_contiguous` suffixed
284//! operations return identical elements to their offset counterpart, however element
285//! order is contiguous (arbitrary for most cases). `_raw` suffixed operations consider
286//! data as having no offset and therefore all elements are contiguous. Both `_contiguous`
287//! and `_raw` operations are more performant than their fully offset counterparts,
288//! although the difference is negligable for smaller arrays.
289//!
290//! ```
291//! # use n_circular_array::{CircularArrayVec, CircularIndex, CircularMut};
292//! // A 2-dimensional circular array of 3*3 elements.
293//! let mut array = CircularArrayVec::new([3, 3, 2], vec![
294//!      0,  1,  2,
295//!      3,  4,  5,
296//!      6,  7,  8,
297//!
298//!      9, 10, 11,
299//!     12, 13, 14,
300//!     15, 16, 17,
301//! ]);
302//!
303//! array.push_front(0, &[100].repeat(6));
304//! array.push_front(1, &[100].repeat(6));
305//!
306//! assert_eq!(array.iter().cloned().collect::<Vec<usize>>(), &[
307//!       4,   5, 100,
308//!       7,   8, 100,
309//!     100, 100, 100,
310//!
311//!      13,  14, 100,
312//!      16,  17, 100,
313//!     100, 100, 100
314//! ]);
315//! // All axes are offset.
316//! assert_eq!(array.iter_index(1, 0).cloned().collect::<Vec<usize>>(), &[
317//!       4,   5, 100,
318//!      13,  14, 100,
319//! ]);
320//! // Identical to above, however element order is arbitrary.
321//! assert_eq!(array.iter_index_contiguous(1, 0).cloned().collect::<Vec<usize>>(), &[
322//!      100,  4,   5,
323//!      100, 13,  14,
324//! ]);
325//!
326//! // Our operation does not care about order.
327//! assert_eq!(array.iter_index_contiguous(0, 0).filter(|val| **val >= 100).count(), 2);
328//! ```
329//!
330//! ## Feature Flags
331//!
332//! Feature | Description
333//! ---|---
334//! `strides` | Exports [`Strides`](strides::Strides) for flattening `N` dimensional indices during translation.
335//!
336//! ## Performance
337//!
338//! Initial version of `n_circular_array` prioritized functionality over performance.
339//! In the current state, performance can be significantly improved. If performance is
340//! found to make `n_circular_array` impractical, please open an issue and optimization
341//! can be prioritized.
342//!
343//! Circular arrays fragment sequentual data over axis bound(s). The following can
344//! improve performance:
345//! - If possible, orient an array where the majority of operations are performed on the
346//! outermost dimension(s). This will allow `n_circular_array` to take contiguous
347//! slices of memory where possible, which can result in operations being reduced to
348//! as little as a single iteration over a contiguous slice, or a single call to
349//! `copy_from_slice` during mutation.
350//! - External types implementing `AsRef<[T]>` and `AsMut<[T]>` can improve performance
351//! over `Vec<T>` or `Box<T>`. If necessary, `AsRef<[T]>` and `AsMut<[T]>` can be delegated
352//! to `unsafe` methods, although this is discouraged.
353//! - For smaller arrays, avoiding a circular array and simply copying (or cloning)
354//! an array window may outperform `n_circular_array`. Benchmark if unsure whether
355//! your use case benefits from `n_circular_array`.
356//!
357#[macro_use]
358mod assertions;
359
360mod array;
361mod array_iter;
362
363mod array_index;
364mod array_mut;
365
366mod index;
367mod index_iter;
368
369mod span;
370mod span_iter;
371
372mod strides;
373
374pub use array::{CircularArray, CircularArrayBox, CircularArrayVec};
375pub use array_index::CircularIndex;
376pub use array_mut::CircularMut;
377
378#[cfg(feature = "strides")]
379pub use strides::Strides;