Accessing vector elements

Different applications need to access vector elements in different ways. Extreme Numerics.NET accommodates these needs by providing four different options.

Vector indexers

The Vector<T> class has an Item[Int32] property. Several types can be used as indexes: integers, ranges, masks, and predicates.

Accessing single elements

The index is zero based. If the index is greater than or equal to the length, an exception of type IndexOutOfRangeException is thrown.

C#
var v = Vector.Create(1, 2, 4, 8, 16);
// This prints "2":
Console.WriteLine("v[1] = {0}", v[1]);
v[1] = 7;
// This prints "[1 7 4 8 16]":
Console.WriteLine("v = {0}", v);

Indexed range access

Some vector operations operate on a range of elements of a vector.

The Range structure

A Range structure represents a range of indices. The following table illustrates the different ways to specify a range:

Expression

Description

new Range(startIndex, endIndex)

A range that runs from startIndex up to and including endIndex.

new Range(startIndex, endIndex, stride)

A range that runs from startIndex to endIndex with an increments of stride.

Range.All

A range that spans the entire vector.

StartIndex, EndIndex and Stride are properties of the Range structure and can be set directly. StartIndex is guaranteed to be the first index in the range. In the second example, the last index in the range is the largest number of the form StartIndex + kStride that is less than or equal to EndIndex. The Stride can also be negative. In this case, the last index in the range is the smallest number of the form StartIndex + kStride that is greater than or equal to EndIndex.

The special range Range.All can be used to represent a range over an entire row, column, or vector, without having to specify the EndIndex explicitly.

Using ranges as an index

The indexer property of Vector<T> is overloaded to allow a Range structure as an index. When getting this property, it returns a vector that spans the range specified by the index:

C#
var v = Vector.Create(0, 1, 2, 3, 4, 5, 6);
// This prints '[2, 3]':
Console.WriteLine("v[new Range(2, 3)] = {0}", v[new Range(2, 3)]);
// This prints '[2, 4]':
Console.WriteLine("v[new Range(2, 5, 2)] = {0}", v[new Range(2, 5, 2)]);
// This prints '[0, 1, 2, 3, 4, 5, 6]':
Console.WriteLine("v[Range.All] = {0}", v[Range.All]);

The first example gets the 3rd and 4th elements of the vector. (Indexes are 0-based!) The second example gets the elements with index 2 and 4. The last example gets the entire vector.

When setting this property, the elements in the range are set to the elements of the right-hand side.

C#
var v = Vector.Create(6);
var w = Vector.Create(1, 2, 3);
v[new Range(2, 4)] = w;
// v -> [0, 0, 1, 2, 3, 0]:
Console.WriteLine("v = {0}", v);
var x = Vector.Create(6);
// x is the reverse of v:
x[new Range(5, 0, -1)] = v;
// x -> [0, 3, 2, 1, 0, 0]
Console.WriteLine("x = {0}", x);

The first example sets the 3rd to 5th elements of a vector to the elements of w. The second example sets the reverse range of x equal to v. This, in effect, sets x equal to the reverse of the vector.

The SetValues(T, Range) method sets all elements in a range to a single value. In F#, assignment to the indexer can be used for this purpose.

Boolean masks and predicates

Using a boolean vector as an index selects those elements for which the corresponding element in the mask is true. The mask must have the same length as the vector it indexes. Another option is to use a predicate: a delegate that maps a value to a boolean. In this case, the values for which the predicate returns true are selected.

The SetValues(T, Range) method sets all elements in a range to a single value. In F#, assignment to the indexer can be used for this purpose.

In the example below, we first select and negate the larger elements in a vector. We then set the elements that are less than zero equal to 99:

C#
var v = Vector.Create(1, 5, 2, 6, 3, 7, 4);
var big = Vector.GreaterThan(v, 4);
v[big] = -v[big];
// Alternative: v[big].NegateInPlace();
// v -> [ 1, -5, 2, -6, 3, -7, 4 ]
v.SetValues(99, x => x < 0);
// v -> [ 1, 99, 2, 99, 3, 99, 4 ]

Slices

Although the indexers are very powerful and offer a lot of flexibility, they are limited in one important way: the mutability is always inherited from the parent vector. In situations where more control is needed, one of the overloads of the GetSlice method may be used instead. These methods are identical to their indexer counterparts, but add an optional argument of type Intent.

C#
var v = Vector.Create(6, i => i, ArrayMutability.Immutable);
// This would fail:
// v[2] = 99;
var s1 = v.GetSlice(2, 3, Intent.WritableCopy);
// s1 -> [2, 3], writable
s1[0] = 99;
// s1 -> [99, 3]
Console.WriteLine("s1 = {0}", s1);
var s2 = v.GetSlice(2, 4, 2);
// s2 -> [2, 4]
Console.WriteLine("s2 = {0}", s2);
var s3 = v.GetSlice(6, 0, -1);
// s3 -> [6, 5, 4, 3, 2, 1, 0]
var s4 = v.GetSlice(new Range(6, 0, -1));
// s4 -> [6, 5, 4, 3, 2, 1, 0]

Enumeration

The Vector<T> class implements the IEnumerable<T> interface. This lets you iterate through the elements in a for-each loop:

C#
double ProductOfElements(Vector<double> v)
{
    double product = 1.0;
    foreach (double element in v)
        product *= element;
    return product;
}