ndarray
NumPy-friendly multidimensional arrays in C++
|
ndarray is a header-only library; after downloading and unpacking the source, you can start using it immediately simply by adding it to your compiler's include path.
For tests, we use the Cmake build system, but CMake is not necessary to make use of the library.
Array objects have two normal constructors intended for public use, the default constructor and a converting copy constructor. The default constructor creates an "empty" array, with a null data pointer and zero shape. The copy constructor creates a shared view into an existing array.
To create a new non-trivial array, one can use the allocate function, which returns a temporary object implicitly convertible to Array:
The makeVector function here is a variable-argument-length constructor for the Vector object, a fixed-size array class whose int variant is used to specify shape and strides for Array. The appropriate Vector template for a particular Array template is available as the Index typedef within the Array class.
The allocate function can also take an STL allocator as a template argument and/or regular argument:
Note that the type of the allocator does not have to match the type of the array; the allocator's "rebind" member will be used to create the correct allocator when the array is constructed. Furthermore, unlike standard library containers, Array is not templated on its allocator type; after construction, it is impossible to determine how an Array's memory was allocated. An Array constructed by allocate is not generally initialized to any value (do not assume it contains zeros).
Arrays can also be constructed that point to external data:
The 'strides' vector here specifies the space between elements in each dimension; the dot product of the strides vector with an index vector should give the offset of the element with that index from the first element of the array. The 'Owner' type here is a typedef to a boost::shared_ptr, which can take an arbitrary functor as a custom deleter (here, std::free). By defining an appropriate deleter, an array can manage virtually any kind of memory. However, it is also possible to create an array with no reference counting by passing an empty owner (or passing none at all):
In this case, the user is responsible for ensuring that the data pointer provided to the array remains valid during the array's lifetime, and is eventually deallocated later.
Direct Array assignment is shallow:
To actually set the elements of an array, we can use Array::deep():
Scalar assignment and augmented assignment operations are also supported:
The deep() method returns a proxy ArrayRef object, which behaves just like an Array aside from its assignment operators, and is implicitly convertible to Array.
A multidimensional Array behaves like a container of Arrays with lower dimensions, while a one-dimensional Array behaves like a container of elements:
Indexing operations return ArrayRef objects, not Arrays. This allows them to be assigned to without manually calling deep():
For one dimensional arrays, the "Reference" typedef is equivalent to "Element &", while the "Value" typedef is equivalent to "Element". For multidimensional arrays, "Reference" is a lower-dimensional ArrayRef, while "Value" is a lower-dimensional Array.
Array is designed to have lightweight nested iterators, with types provided by the Array::Iterator typedef. For contiguous one-dimensional arrays (Array<T,1,1>), this is a typedef to a simple pointer. The typical pattern to iterate over a 3-dimensional array looks like the following:
As expected, the iterators of multidimensional arrays dereference to lower-dimensional arrays, and the iterators of one-dimensional arrays dereference to elements. With some compilers, it may be advantageous to move the calls to end() outside their loops.
Just like direct indexing, multidimensional array iterators dereference to ArrayRef, not Array.
STL-compliant typedefs "iterator", "const_iterator", "reference", "const_reference", and "value" are also provided, though the const variants are not actually const (because Array provides smart-pointer const-correctness rather than container const-correctness).
Single elements can be extracted from multidimensional arrays by indexing with ndarray::Vector:
General views into an Array are created by passing a ViewDef object to the [] operators of Array, returning a new Array that shares data and owns a reference to the original.
ViewDef involves a lot of template metaprogramming, so the actual template class is an internal detail, and ViewDefs are constructed by calls to the view() function function followed by chained calls to the function call operator, resulting in a syntax that looks like this:
which is equivalent to the Python code:
The value passed to each call specifies how to extract values from that dimension:
Any dimensions which are not specified because the length of the ViewDef expression is smaller than the dimensionality of the parent array will be considered full-dimension selections:
Arrays provide element-wise arithmetic operations that make use of expression templates:
We can simplify the previous example by initializing 'b' with the copy() function, which is simply a shortcut for allocate and assign:
As a rule, ndarray never allocates memory for a new array unless you explicitly tell it to with the allocate() or copy() functions.
Element-wise comparisons are also supported, but not via overloaded operators:
The element-wise comparison functions (equal, not_equal, less, less_equal, greater, greater_equal) and logical operators (logical_and, logical_or, logical_not) are also lazy, and are most useful when used in conjunction with the reduction functions all() and any():
Array does overload the equality and inequality operators, but these compare for "shallow" equality, not element-wise equality: