Crate n_circular_array

Crate n_circular_array 

Source
Expand description

§N Circular Array

An n-dimensional circular array.

§Features

  • Fixed dimension arrays of any size.
  • Element retrieval by N dimensional index, range or slice.
  • Element insertion to the front or back of any axis.
  • N dimensional translation over a source array.
  • Element iteration in sequentual or contiguous order.
  • Support for external types through AsRef<[T]> and AsMut<[T]>.
  • Thorough testing for arrays of smaller dimensionality.
  • No external dependencies.

§Mutation

n_circular_array supports the following mutating operations:

  • Insert elements to either side of an axis.
  • Translate over a source array.
  • Mutate individual elements.

§Insertion

Elements are inserted by providing a row-major slice or iterator with a length equal to an exact multiple of the given axis length. That is, a call to insert two rows must be provided exactly two rows of elements.

// A 2-dimensional circular array of 3*3 elements.
let mut array = CircularArrayVec::new([3, 3], vec![
    0, 1, 2,
    3, 4, 5,
    6, 7, 8
]);

// Push the elements 9..12 (one row) to the front of axis 1.
array.push_front(1, &[
     9, 10, 11,
]);
assert_eq!(array.iter().cloned().collect::<Vec<usize>>(), &[
     3,  4,  5,
     6,  7,  8,
     9, 10, 11,
]);

// Push the elements 12..18 (two columns) to the front of axis 0.
let axis_len = array.shape()[0];
array.push_front(0, &[12, 13, 14, 15, 16, 17]);
assert_eq!(array.iter().cloned().collect::<Vec<usize>>(), &[
      5, 12, 13,
      8, 14, 15,
     11, 16, 17,
]);

// Push the elements 19..22 (one row) to the back of axis 1.
array.push_back(1, &[
     19, 20, 21,
]);
assert_eq!(array.iter().cloned().collect::<Vec<usize>>(), &[
     19, 20, 21,
      5, 12, 13,
      8, 14, 15,
]);

§Translation

Translation methods simplify mapping the elements of a source array to the circular array. Translation methods expect the array origin, or the position of the [0; N] element within the source array, and a translation on an axis. The provided el_fn function will recieve contiguous [Range<usize>; N] slices for mapping the new elements from the source to the circular array. CircularArray only handles slicing and mutation, and translation logic (the current translation, out of bound translation etc.) must be maintained by the user.

In the following example, rather than passing the [Range<usize>; N] slice to a 3rd-party crate, we define the source array Strides, then call Strides::flatten_range to get a single contiguous range for slicing (requires feature strides).

// A [5, 5] source array.
let src = [
     0,  1,  2,  3,  4,
     5,  6,  7,  8,  9,
    10, 11, 12, 13, 14,
    15, 16, 17, 18, 19,
    20, 21, 22, 23, 24,
];
// Strides used for flattening `N` dimensional indices.
let src_strides = Strides::new(&[5, 5]);

// Slice function.
let el_fn = |mut index: [Range<usize>; 2]| {
    &src[src_strides.flatten_range(index)]
};

// A [3, 3] circular array positioned at `[0, 0]`.
let mut origin = [0, 0];
let mut dst = CircularArray::new([3, 3], vec![
     0,  1,  2,
     5,  6,  7,
    10, 11, 12
]);

// Translate by 2 on axis 0 (Pushes 2 columns to front of axis 0).
let axis = 0;
let n = 2;
dst.translate_front(axis, n, origin, el_fn);
origin[axis] += n as usize;

assert_eq!(dst.iter().cloned().collect::<Vec<usize>>(), &[
     2,  3,  4,
     7,  8,  9,
    12, 13, 14,
]);

// Translate by 1 on axis 1 (Pushes 1 row to front of axis 1).
let axis = 1;
let n = 1;
dst.translate_front(axis, n, origin, el_fn);
origin[axis] += n as usize;

assert_eq!(dst.iter().cloned().collect::<Vec<usize>>(), &[
     7,  8,  9,
    12, 13, 14,
    17, 18, 19,
]);

assert_eq!(origin, [2, 1]);

§Indexing and Slicing

n_circular_array supports the following indexing operations:

  • Access elements by axis slice.
  • Access elements by N dimensional slice.
  • Access individual elements by index.

§Slicing an axis

All elements of an axis can be iterated over by index or range. Calling CircularIndex::iter_index returns an iterator of elements of a shape equal to the shape of the circular array, with the specified axis set to 1. Calling CircularIndex::iter_range returns an iterator of elements of a shape equal to the shape of the circular array, with the specified axis set to the length of the given range.

// A 3-dimensional circular array of 3*3*2 elements.
let array = CircularArrayVec::new([3, 3, 2], vec![
     0,  1,  2,
     3,  4,  5,
     6,  7,  8,

     9, 10, 11,
    12, 13, 14,
    15, 16, 17,
]);

// Iterate over index 1 of axis 0 (shape [1, 3, 2]).
assert_eq!(array.iter_index(0, 1).cloned().collect::<Vec<usize>>(), &[
     1,
     4,
     7,

    10,
    13,
    16,
]);
// Iterate over indices 1..3 of axis 1 (shape [3, 2, 2]).
assert_eq!(array.iter_range(1, 1..3).cloned().collect::<Vec<usize>>(), &[
     3,  4,  5,
     6,  7,  8,

    12, 13, 14,
    15, 16, 17,
]);

§Slicing the array

Calling CircularIndex::iter_slice can be used to iterate over an N dimensional slice of the array. This can be used to limit iteration to an exact subset of elements.

// A 3-dimensional circular array of 3*3*2 elements.
let array = CircularArrayVec::new([3, 3, 2], vec![
     0,  1,  2,
     3,  4,  5,
     6,  7,  8,

     9, 10, 11,
    12, 13, 14,
    15, 16, 17,
]);

// Iterate over:
//     - index 1 of axis 0,
//     - range 0..3 of axis 1 (all elements),
//     - index 1 of axis 2.
// (shape [1, 2, 1], equivalent to [2]).
assert_eq!(array.iter_slice([1..2, 0..3, 1..2]).cloned().collect::<Vec<usize>>(), &[
    10,
    13,
    16,
]);
// Iterate over:
//     - range 0..2 of axis 0,
//     - range 1..3 of axis 1,
//     - index 0 of axis 2.
// (shape [2, 2, 1], equivalent to [2, 2]).
assert_eq!(array.iter_slice([0..2, 1..3, 0..1]).cloned().collect::<Vec<usize>>(), &[
     3,  4,
     6,  7,
]);

n_circular_array resizing or reshaping functionality can be achieved by using CircularIndex::iter_slice and collecting into a new array.

// A 3-dimensional circular array of 3*3*2 elements.
let array3 = CircularArrayVec::new([3, 3, 2], vec![
     0,  1,  2,
     3,  4,  5,
     6,  7,  8,

     9, 10, 11,
    12, 13, 14,
    15, 16, 17,
]);

// Iterate over:
//     - range 0..2 of axis 0,
//     - range 1..3 of axis 1,
//     - index 0 of axis 2.
// (shape [2, 2, 1], equivalent to [2, 2]).
let iter = array3.iter_slice([0..2, 1..3, 0..1]).cloned();

// A 2-dimensional circular array of 3*2 elements.
let array2 = CircularArrayVec::from_iter([2, 2], iter);

assert_eq!(array2.iter().cloned().collect::<Vec<usize>>(), &[
     3,  4,
     6,  7,
]);

§Index and IndexMut

Finally, n_circular_array supports std::ops::Index and std::ops::IndexMut taking an N dimensional index ([usize; N]) as argument.

// A 2-dimensional circular array of 3*3 elements.
let mut array = CircularArrayVec::new([3, 3], vec![
    0, 1, 2,
    3, 4, 5,
    6, 7, 8
]);

array[[1, 1]] += 10;
assert_eq!(array.iter().cloned().collect::<Vec<usize>>(), &[
    0,  1, 2,
    3, 14, 5,
    6,  7, 8
]);

§Raw and Contiguous Operations

n_circular_array also include operations for iterating over elements of the array while only offsetting the data of a subset of axes. _contiguous suffixed operations return identical elements to their offset counterpart, however element order is contiguous (arbitrary for most cases). _raw suffixed operations consider data as having no offset and therefore all elements are contiguous. Both _contiguous and _raw operations are more performant than their fully offset counterparts, although the difference is negligable for smaller arrays.

// A 2-dimensional circular array of 3*3 elements.
let mut array = CircularArrayVec::new([3, 3, 2], vec![
     0,  1,  2,
     3,  4,  5,
     6,  7,  8,

     9, 10, 11,
    12, 13, 14,
    15, 16, 17,
]);

array.push_front(0, &[100].repeat(6));
array.push_front(1, &[100].repeat(6));

assert_eq!(array.iter().cloned().collect::<Vec<usize>>(), &[
      4,   5, 100,
      7,   8, 100,
    100, 100, 100,

     13,  14, 100,
     16,  17, 100,
    100, 100, 100
]);
// All axes are offset.
assert_eq!(array.iter_index(1, 0).cloned().collect::<Vec<usize>>(), &[
      4,   5, 100,
     13,  14, 100,
]);
// Identical to above, however element order is arbitrary.
assert_eq!(array.iter_index_contiguous(1, 0).cloned().collect::<Vec<usize>>(), &[
     100,  4,   5,
     100, 13,  14,
]);

// Our operation does not care about order.
assert_eq!(array.iter_index_contiguous(0, 0).filter(|val| **val >= 100).count(), 2);

§Feature Flags

FeatureDescription
stridesExports Strides for flattening N dimensional indices during translation.

§Performance

Initial version of n_circular_array prioritized functionality over performance. In the current state, performance can be significantly improved. If performance is found to make n_circular_array impractical, please open an issue and optimization can be prioritized.

Circular arrays fragment sequentual data over axis bound(s). The following can improve performance:

  • If possible, orient an array where the majority of operations are performed on the outermost dimension(s). This will allow n_circular_array to take contiguous slices of memory where possible, which can result in operations being reduced to as little as a single iteration over a contiguous slice, or a single call to copy_from_slice during mutation.
  • External types implementing AsRef<[T]> and AsMut<[T]> can improve performance over Vec<T> or Box<T>. If necessary, AsRef<[T]> and AsMut<[T]> can be delegated to unsafe methods, although this is discouraged.
  • For smaller arrays, avoiding a circular array and simply copying (or cloning) an array window may outperform n_circular_array. Benchmark if unsure whether your use case benefits from n_circular_array.

Structs§

CircularArray
A circular array of N dimensions for elements of type T.

Traits§

CircularIndex
Indexing CircularArray operations.
CircularMut
Mutating CircularArray operations.

Type Aliases§

CircularArrayBox
A CircularArray backed by a Box.
CircularArrayVec
A CircularArray backed by a Vec.