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;