Skip to main content

Accessing and Indexing Arrays and Matrices

This guide provides a comprehensive look at how to access and select elements, subregions, and patterns within arrays and matrices. The Linear Algebra module offers a rich set of indexing tools that range from simple element access to sophisticated slicing and fancy indexing operations. Understanding these tools is essential for effective data manipulation and analysis.

Overview of indexing methods

Linear Algebra provides several complementary approaches to accessing container elements:

  • Single element access: Read or modify individual elements using the [] operator
  • Block extraction: Extract rectangular regions using methods like block(), row(), col(), and corner accessors
  • Slice notation: Use Python-style slice syntax for flexible range-based selection
  • Fancy indexing: Select arbitrary rows and columns using lists of indices
  • Mixed indexing: Combine different indexing strategies using select() and set_select()

All indexing operations in Linear Algebra use zero-based indexing, meaning the first element is at index 0, the second at index 1, and so on. This is consistent with most modern programming languages and makes mathematical translations straightforward.

tip

When deciding which indexing method to use, consider:

  • Use single element access when you need to read or modify one value at a time
  • Use block methods when you need rectangular regions and the method name makes your intent clear
  • Use slice notation when you want concise range-based selection with steps or negative indices
  • Use fancy indexing when you need non-contiguous or reordered selections
  • Use mixed indexing (select()) when you need maximum flexibility combining different strategies

Single element access

The most basic form of indexing is accessing individual elements using the [] operator. This operator returns a reference to the element, allowing both read and write operations.

Accessing array and matrix elements

For two-dimensional containers (matrices and 2D arrays), you must always provide two indices: the row index and the column index.

var m = matrix([[1, 2, 3], [4, 5, 6]], as_real)
// m is a 3x2 matrix (3 columns, 2 rows)

var val = m[0, 0] // First row, first column: 1
var val2 = m[2, 1] // Third row, second column: 6

// Modify elements directly
m[0, 0] = 10.0
m[2, 1] = 20.0

Remember that Aleph uses column-major layout when constructing from nested lists, so [[1, 2, 3], [4, 5, 6]] creates a matrix where [1, 2, 3] is the first column (not the first row).

warning

Attempting to use a single index on a 2D container will result in an error:

var m = matrix([[1, 2], [3, 4]])
var val = m[0] // ERROR: 2D containers require two indices

This restriction prevents ambiguity and makes code intent explicit.

Accessing vector elements

Vectors are n×1 matrices (column vectors) with a singleton dimension, which allows single-index access:

var v = vector([1, 2, 3, 4, 5])
var elem = v[0] // First element: 1
var last = v[4] // Last element: 5

// Modify elements
v[0] = 100
v[4] = 500

Row vectors (1×n matrices) also support single-index access:

var row = vector([10, 20, 30]).transposed()
var first = row[0] // 10
row[2] = 35 // Modify third element

The single-index convenience works because the singleton dimension is unambiguous. Under the hood, v[i] is equivalent to v[i, 0] for column vectors and v[0, i] for row vectors.

Index bounds

Linear Algebra performs bounds checking to help catch errors. Accessing an element outside the container's dimensions will trigger an error:

var m = zeros([3, 3], as_real)
// Valid indices: rows [0, 1, 2], columns [0, 1, 2]
// var val = m[3, 0] // ERROR: row index out of bounds
// var val = m[0, 5] // ERROR: column index out of bounds

Reference semantics

The [] operator returns a reference to the element, not a copy. This means modifications through the indexing operator directly affect the original container:

var m = ones([2, 2], as_real)
var ref = m[0, 0] // ref is a reference to m[0,0]
m[0, 0] = 42.0 // Modifies the original
print(m[0, 0]) // 42.0

This reference semantic allows for efficient in-place updates without creating temporary copies.

Block and submatrix extraction

Block extraction methods provide named ways to extract rectangular regions from containers. These methods are self-documenting and make code intent clear, especially when working with standard patterns like extracting rows, columns, or corner blocks.

General blocks

The block(start_row, start_col, num_rows, num_cols) method extracts an arbitrary rectangular region:

var m = matrix([[1,2,3,4], [5,6,7,8], [9,10,11,12]], as_real)
// m is 4x3 (4 columns, 3 rows)

var b = m.block(1, 1, 2, 2) // 2x2 block starting at (1,1)
// b is [[6,7], [10,11]]

The starting position (start_row, start_col) specifies the top-left corner of the block, and (num_rows, num_cols) specifies the size. All indices are zero-based.

info

Block extraction returns a copy of the selected region, not a view. Modifications to the extracted block do not affect the original matrix:

var m = zeros([4, 4], as_real)
var b = m.block(0, 0, 2, 2)
b.set_ones() // b is now all ones
// m is unchanged (still all zeros)

Row and column extraction

For extracting entire rows or columns, use the dedicated row() and col() methods:

var m = matrix([[1,2,3], [4,5,6], [7,8,9]], as_real)

var r = m.row(1) // Second row: [2,5,8] (1×3 row vector)
var c = m.col(2) // Third column: [7,8,9] (3×1 column vector)

Note that row() returns the row as a row vector (1×n matrix), while col() returns the column as a column vector (n×1 matrix). This preserves the dimensional semantics and allows the extracted vectors to participate in matrix operations correctly.

Multiple rows and columns

To extract multiple consecutive rows or columns, use top_rows(), bottom_rows(), middle_rows(), left_cols(), right_cols(), and middle_cols():

var m = matrix([[1,2,3,4], [5,6,7,8], [9,10,11,12]], as_real)

var top = m.top_rows(2) // First 2 rows
var bottom = m.bottom_rows(1) // Last 1 row
var middle = m.middle_rows(1, 1) // 1 row starting at row 1

var left = m.left_cols(2) // First 2 columns
var right = m.right_cols(1) // Last 1 column
var middle_c = m.middle_cols(1, 2) // 2 columns starting at column 1

The middle_rows(start, count) and middle_cols(start, count) methods extract a specified number of consecutive rows or columns starting from a given position. These methods are particularly useful when working with fields defined over grids or when partitioning matrices in linear algebra applications.

Corner blocks

For extracting rectangular blocks from the corners of a matrix, use the corner accessor methods:

var m = matrix([[1,2,3,4], [5,6,7,8], [9,10,11,12]], as_real)

var tl = m.top_left_corner(2, 2) // [[1,2], [5,6]]
var tr = m.top_right_corner(2, 2) // [[3,4], [7,8]]
var bl = m.bottom_left_corner(2, 2) // [[5,6], [9,10]]
var br = m.bottom_right_corner(2, 2) // [[7,8], [11,12]]

These methods take two arguments: the number of rows and the number of columns to extract. They're especially helpful when working with block-structured matrices, such as partitioned matrices in linear algebra or image processing applications where you need to process corner regions.

tip

When working with structured matrices (e.g., block matrices in linear algebra), using named corner methods makes your code more readable than computing index offsets manually. Compare:

// Less clear
var corner = m.block(m.rows() - 2, m.cols() - 2, 2, 2)

// More clear
var corner = m.bottom_right_corner(2, 2)

Modifying blocks in-place

While block extraction methods return copies, you can modify regions of the original matrix using the corresponding setter methods. These methods allow you to update rectangular regions without extracting and reassigning.

Setting blocks

The set_block(source, start_position) method copies data from a source container into a rectangular region:

var m = zeros([4, 4], as_real)
var data = ones([2, 2], as_real)

m.set_block(data, [1, 1]) // Copy 2x2 ones starting at position (1,1)
// m now has ones in the center, zeros elsewhere

The start_position argument is a list [start_row, start_col] specifying where the top-left corner of the source should be placed. The extent of the region is automatically determined by the shape of the source data. The source container must fit within the target matrix bounds.

Setting rows and columns

To modify entire rows or columns, use set_row() and set_col():

var m = zeros([3, 4], as_real)

var row_data = ones([1, 4], as_real)
var col_data = filled([3, 1], 7.0, as_real)

m.set_row(row_data, 1) // Set second row to all 1.0
m.set_col(col_data, 2) // Set third column to all 7.0

The second argument specifies which row or column to modify (zero-indexed). The data container must have the appropriate shape: row data should be 1×n or n×1, and column data should be n×1 or 1×n, where n matches the target dimension.

Setting multiple rows and columns

For setting multiple consecutive rows or columns, use set_top_rows(), set_bottom_rows(), set_middle_rows(), set_left_cols(), set_right_cols(), and set_middle_cols():

var m = zeros([5, 5], as_real)

// Set first 2 rows
m.set_top_rows(ones([2, 5], as_real), 2)

// Set last 2 columns
m.set_right_cols(filled([5, 2], 5.0, as_real), 2)

// Set 2 rows starting at row 2
var middle_data = filled([2, 5], 3.0, as_real)
m.set_middle_rows(middle_data, 2)

// Set 3 columns starting at column 1
var middle_cols_data = filled([5, 3], 7.0, as_real)
m.set_middle_cols(middle_cols_data, 1)

For set_middle_rows() and set_middle_cols(), the starting index is specified as the second argument, and the number of rows or columns is automatically determined from the shape of the source data.

warning

The source data must have compatible dimensions with the target region. Setting data into a position that would extend beyond the matrix bounds will result in an error:

var m = zeros([4, 4], as_real)
var data = ones([3, 3], as_real)
// m.set_block(data, [3, 3]) // ERROR: would extend beyond [4,4]

Setting corners

You can modify corner blocks using the corner setter methods. These methods take the size of the corner region as additional parameters:

var m = zeros([5, 5], as_real)

// Set 2x2 top-left corner
m.set_top_left_corner(ones([2, 2], as_real), 2, 2)

// Set 2x2 bottom-right corner
m.set_bottom_right_corner(filled([2, 2], 9.0, as_real), 2, 2)

// Set 3x2 top-right corner
m.set_top_right_corner(filled([3, 2], 5.0, as_real), 3, 2)

// Set 2x3 bottom-left corner
m.set_bottom_left_corner(filled([2, 3], 7.0, as_real), 2, 3)

The corner setter methods take three arguments: the source data, the number of rows, and the number of columns. The position of the corner is implicit (e.g., top-left starts at (0,0), bottom-right ends at (rows-1, cols-1)).

Slice notation

Slice notation provides a concise and expressive way to select ranges of rows and columns using Python-style syntax. Slices are particularly powerful when combined with step values and negative indices.

Basic slicing

The fundamental slice syntax is [start:stop], which selects elements from start (inclusive) to stop (exclusive):

var m = matrix([[1,2,3,4], [5,6,7,8], [9,10,11,12]], as_real)

// Select rows 0-1 (stops before 2), all columns
var sub1 = m[0:2, :]

// Select all rows, columns 1-2 (stops before 3)
var sub2 = m[:, 1:3]

// Select rows 1-2, columns 0-2
var sub3 = m[1:3, 0:2]

The colon : by itself means "all elements in this dimension". You can think of : as shorthand for 0:n, where n is the size of that dimension.

Omitting bounds

Both start and stop can be omitted, with sensible defaults:

var m = matrix([[1,2,3,4], [5,6,7,8], [9,10,11,12]], as_real)

var from_start = m[:2, :] // Rows 0-1: equivalent to [0:2, :]
var to_end = m[1:, :] // Rows 1 to end: equivalent to [1:3, :]
var all_elements = m[:, :] // All rows and columns: equivalent to m
  • Omitting start defaults to 0 (beginning of dimension)
  • Omitting stop defaults to the dimension size (end of dimension)
  • Omitting both gives you all elements in that dimension

Step values

Adding a step value allows you to select every nth element: [start:stop:step]

var m = matrix([[1,2,3,4,5], [6,7,8,9,10], [11,12,13,14,15]], as_real)

// Every other row, all columns
var even_rows = m[0::2, :] // Rows 0, 2

// All rows, every other column starting from 1
var odd_cols = m[:, 1::2] // Columns 1, 3

// Every other row and column
var sparse = m[::2, ::2] // Checkerboard pattern

The step value must be positive (negative steps are not supported for forward slicing).

Negative indices

Negative indices count backward from the end: -1 refers to the last element, -2 to the second-to-last, and so on.

var m = matrix([[1,2,3,4], [5,6,7,8], [9,10,11,12]], as_real)

// Last row, all columns
var last_row = m[-1:, :]

// All rows, last column
var last_col = m[:, -1:]

// Last two rows, last two columns
var bottom_right = m[-2:, -2:]

// All rows except the last
var without_last = m[:-1, :]

Negative indices are particularly useful when you don't know (or don't want to hardcode) the matrix dimensions. They make your code more generic and adaptable to different matrix sizes.

Reversing with negative steps

Using a negative step reverses the selection order:

var m = matrix([[1,2,3,4], [5,6,7,8], [9,10,11,12]], as_real)

// Reverse all rows, all columns
var reversed_rows = m[-1::-1, :]

// All rows, reverse all columns
var reversed_cols = m[:, -1::-1]

// Reverse both dimensions
var fully_reversed = m[-1::-1, -1::-1]

When using negative steps, you typically start from -1 (the last element) and omit the stop value (to go all the way to the beginning).

tip

Reversing with slices is particularly useful for:

  • Flipping images or data along one or both axes
  • Iterating through data in reverse order
  • Creating reversed copies for algorithms that need both forward and backward passes
warning

Slice indexing returns copies, not views. You cannot assign to a slice using the bracket notation to modify the original matrix:

var m = zeros([4, 4], as_real)
// m[0:2, 0:2] = filled([2, 2], 5.0, as_real) // ERROR: cannot assign to slice

To modify a slice of the original matrix, use set_select() with Slice objects (see the Mixed indexing section below), or use the dedicated setter methods like set_block(), set_top_rows(), etc.

Fancy indexing

Fancy indexing allows you to select arbitrary rows and columns using lists of indices. Unlike slices, which select contiguous ranges, fancy indexing can select elements in any order and can repeat indices to duplicate data.

Basic fancy indexing

To use fancy indexing, provide lists of integers for both dimensions:

var m = matrix([[1,2,3,4], [5,6,7,8], [9,10,11,12]], as_real)

// Select rows 2 and 0 (in that order), columns 1 and 2
var sub1 = m[[2, 0], [1, 2]]
// Result: [[ 7, 5], [11, 9]]

// Select row 1 twice, columns 0 and 2
var sub2 = m[[1, 1], [0, 2]]
// Result: [[ 2, 2], [10,10]]

The order of indices matters: [2, 0] first selects row 2, then row 0, producing a result where row 2 appears first. Repeating an index creates duplicate rows or columns in the result.

Reordering data

Fancy indexing is particularly useful for reordering rows or columns:

var m = matrix([[1,2,3], [4,5,6], [7,8,9]], as_real)

// Reverse row order
var reversed = m[[2, 1, 0], [0, 1, 2]]

// Swap first and last columns
var swapped = m[[0, 1, 2], [2, 1, 0]]

// Arbitrary permutation
var permuted = m[[1, 2, 0], [2, 0, 1]]

This is more flexible than slicing with negative steps because you can specify any arbitrary permutation, not just reversals.

Selecting non-contiguous elements

Unlike slices, fancy indexing can select non-contiguous rows or columns that don't follow a regular pattern:

var m = matrix([[1,2,3,4,5], [6,7,8,9,10], [11,12,13,14,15], [16,17,18,19,20]], as_real)

// Select rows 0, 2, and 3 (irregular pattern - not achievable with slices)
var sub = m[[0, 2, 3], [0, 1, 2, 3]]

// Select specific columns with irregular spacing: 0, 1, 4
var cols = m[[0, 1, 2, 3], [0, 1, 3]]

// Both dimensions with irregular patterns
var irregular = m[[0, 2, 3], [0, 1, 3]]

This is useful when you have a specific subset of features or samples you want to extract, such as selecting specific features based on importance scores or extracting particular samples from a dataset.

Creating duplicates

Repeating indices in the index list creates duplicates of rows or columns:

var m = matrix([[1,2], [3,4]], as_real)

// Duplicate the first row three times
var dup_rows = m[[0, 0, 0], [0, 1]]
// Result: [[1,2], [1,2], [1,2]]

// Create a 3x3 by repeating columns
var dup_cols = m[[0, 1], [0, 1, 0]]
// Result: [[1,2,1], [3,4,3]]

This technique is useful for data augmentation, creating repeated baseline samples, or implementing certain matrix operations like broadcasting patterns.

warning

Both dimensions must use fancy indexing (lists) when using the bracket [] notation. You cannot mix regular indices or slices with fancy indexing:

var m = matrix([[1,2,3], [4,5,6]])
// var sub = m[0, [1, 2]] // ERROR: can't mix index and list
// var sub = m[0:2, [0, 2]] // ERROR: can't mix slice and list

To mix different indexing types, use the select() method described in the next section.

Mixed indexing with select()

While the bracket notation [] requires uniform indexing (all dimensions must use the same type), the select() method provides maximum flexibility by allowing you to mix regular indices, slices, and lists in a single operation.

The select() method requires using the explicit Slice constructor rather than the colon syntax. The Slice constructor takes (start, stop, step) arguments, all of which are optional. Here are the equivalences between the colon notation and the Slice constructor:

// Colon notation          Slice constructor equivalent
m[1:3, :] // Slice(1, 3), Slice()
m[:, 0:2] // Slice(), Slice(0, 2)
m[::2, 1::2] // Slice(0, m.rows(), 2), Slice(1, m.cols(), 2)
m[-2:, :-1] // Slice(-2, m.rows()), Slice(0, -1)
m[:, :] // Slice(), Slice()
info

The bracket notation [] provides concise colon-based slice syntax but is limited to pure slice operations or pure fancy indexing. The select() method requires explicit Slice constructors but allows mixing different indexing types (slices, lists, and regular indices) in a single operation.

Mixing indices and slices

Combine a regular index (selecting a single row/column) with a slice (selecting a range):

var m = matrix([[1,2,3,4], [5,6,7,8], [9,10,11,12]], as_real)

// First row, all columns
var first_row = m.select([0, Slice()])

// All rows, second column
var second_col = m.select([Slice(), 1])

// Second row, columns 1-3
var sub = m.select([1, Slice(1, 4)])

This is particularly useful when you want to extract a single row or column but use slice notation for the other dimension, making your intent clearer than using row() or col() methods.

Mixing slices and lists

Combine a slice (selecting a range) with a list (fancy indexing):

var m = matrix([[1,2,3,4], [5,6,7,8], [9,10,11,12], [13,14,15,16]], as_real)

// Rows 0-1, columns 2 and 0
var sub1 = m.select([Slice(0, 2), [2, 0]])

// Rows 1 and 3, all columns
var sub2 = m.select([[1, 3], Slice()])

// Last two rows, specific columns
var sub3 = m.select([Slice(-2, m.rows()), [0, 2, 3]])

This combination is powerful when you want to select a contiguous range in one dimension and specific, possibly non-contiguous elements in the other.

Mixing indices and lists

Combine a regular index with a list:

var m = matrix([[1,2,3,4], [5,6,7,8], [9,10,11,12]], as_real)

// Row 1, columns 0 and 2
var sub1 = m.select([1, [0, 2]])

// Rows 0 and 2, column 1
var sub2 = m.select([[0, 2], 1])

This is useful when you want to extract specific elements from a single row or column.

Complex patterns

The select() method shines when you need complex selection patterns that combine multiple indexing strategies:

var m = matrix([[1,2,3,4,5], [6,7,8,9,10], [11,12,13,14,15], [16,17,18,19,20]], as_real)

// Rows 1 and 3, every other column starting from 0
var pattern1 = m.select([[1, 3], Slice(0, m.cols(), 2)])

// Every other row, specific columns
var pattern2 = m.select([Slice(0, m.rows(), 2), [1, 3, 3]])

// Last row, first and last column
var pattern3 = m.select([3, [0, 3]])

These complex patterns are common in data analysis when you need to extract specific features from samples, or when working with structured data where rows and columns have different selection criteria.

In-place modification with set_select()

Just as select() extracts elements, set_select() modifies them in-place:

var m = zeros([5, 5], as_real)

// Set first row, columns 1-3 to 5.0
m.set_select(filled([1, 3], 5.0, as_real), [0, Slice(1, 4)])

// Set rows 1 and 3, column 2 to 7.0
m.set_select(filled([2, 1], 7.0, as_real), [[1, 3], 2])

// Set last two rows, first and last columns to 9.0
m.set_select(filled([2, 2], 9.0, as_real), [Slice(-2, m.rows()), [0, 4]])

The first argument to set_select() is the source data, and the second is the selection specification (a list combining indices, slices, and fancy index lists). The source data must have the same shape as the selected region.

tip

Use select() and set_select() when:

  • You need to combine different indexing strategies in a single operation
  • You need to assign to fancy-indexed selections (the [] operator doesn't support assignment with fancy indexing)
  • The bracket notation [] doesn't support your use case (mixed indexing types)
  • You're programmatically generating selection criteria and want a uniform interface

Use bracket notation [] when:

  • You're doing pure slicing (but use set_select() for assignment)
  • You're doing pure fancy indexing for extraction (but use set_select() for assignment)
  • You want more concise syntax for reading data

Working with vector data

Vectors (n×1 or 1×n matrices) have special considerations for indexing because they have a singleton dimension.

Single-index convenience

Vectors support single-index access as a convenience, since the singleton dimension is unambiguous:

var v = vector([1, 2, 3, 4, 5])

// Single-index access
var first = v[0] // 1
var third = v[2] // 3
var last = v[4] // 5

// Equivalent two-index access
var first2 = v[0, 0] // Also 1
var third2 = v[2, 0] // Also 3

For row vectors, single-index access works similarly:

var row = vector([10, 20, 30]).transposed()
var val = row[1] // 20
var val2 = row[0, 1] // Also 20

Slicing vectors

You can slice vectors to extract sub-vectors. Even though vectors support single-index access for individual elements, slicing requires two-index notation with both dimensions using slices:

var v = vector([1, 2, 3, 4, 5, 6])

// Extract elements 2-4 (both dimensions must be slices)
var sub = v[2:5, :] // [3, 4, 5]

// Last three elements
var tail = v[-3:, :] // [4, 5, 6]

// Every other element
var even = v[::2, :] // [1, 3, 5]

The slice syntax must include both dimensions (e.g., [2:5, :]), where : represents the singleton dimension. Both dimensions must use slice notation (you cannot mix slices with indices or lists in the [] operator). The result maintains the vector structure (singleton dimension).

Fancy indexing on vectors

Fancy indexing works with vectors. Since both dimensions must use the same indexing type (you cannot mix fancy indexing with slices in the [] operator), specify the singleton dimension as a regular index:

var v = vector([10, 20, 30, 40, 50])

// Select specific elements (column vector: use [0] for the column dimension)
var selection = v[[0, 2, 4], [0]] // [10, 30, 50]

// Reverse order
var reversed = v[[4, 3, 2, 1, 0], [0]] // [50, 40, 30, 20, 10]

// Duplicate elements
var dups = v[[0, 0, 1, 1], [0]] // [10, 10, 20, 20]
info

Even though vectors support single-index notation, they remain 2D matrices internally. The shape of a column vector created with vector([1, 2, 3]) is [3, 1], not [3]. This distinction matters when performing matrix operations:

var v = vector([1, 2, 3])
print(v.shape()) // [3, 1] - still 2D
print(v.is_vector()) // true - but recognized as vector

Performance considerations

Understanding the performance characteristics of different indexing operations helps you write efficient code.

Copies vs views

All indexing operations in Linear Algebra return copies, not views:

var m = ones([100, 100], as_real);
var sub = m[0:50, 0:50]; // Creates a 50x50 copy
sub.set_zero(); // Modifies the copy, not m
// m is unchanged (still all ones)

This copy semantic ensures safety and prevents subtle bugs from shared data, but it does have a memory and performance cost. If you need to modify the original matrix, use setter methods like set_block() or set_select():

var m = ones([100, 100], as_real);
m.set_block(zeros([50, 50], as_real), [0, 0]); // Modifies m in-place

In-place operations are faster

When you need to modify a region of a matrix, using setter methods directly is more efficient than extracting, modifying, and reassigning:

// Less efficient: creates temporary copies
var m = ones([1000, 1000], as_real);
var block = m.block(0, 0, 100, 100);
block.set_zero();
m.set_block(block, [0, 0]);

// More efficient: modifies in-place directly
var m2 = ones([1000, 1000], as_real);
m2.set_block(zeros([100, 100], as_real), [0, 0]);

Large selections

When selecting large regions, consider whether you actually need a copy or whether you can process the data in-place:

// If you just need to process data in a region:
var m = random([10000, 10000], as_real);

// Instead of:
// var region = m[1000:9000, 1000:9000] // 8000x8000 copy
// var result = some_function(region)

// Consider using temporary views if the API supports it, or process in chunks

For very large matrices, extracting large blocks can be memory-intensive. When possible, operate on smaller chunks or use in-place operations.

Fancy indexing overhead

Fancy indexing with lists has more overhead than simple slicing because the indices need to be processed individually:

var m = random([1000, 1000], as_real);

// Faster: contiguous slice
var sub1 = m[100:200, 100:200];

// Slower: fancy indexing
var indices = [100..200]; // List of 100 indices
var sub2 = m[indices, indices];

When your selection is contiguous, prefer slicing. Reserve fancy indexing for cases where you truly need non-contiguous or reordered selections.

Common patterns and idioms

Here are some frequently used indexing patterns that demonstrate effective usage of the indexing tools.

Swapping rows or columns

Swap two rows or columns using fancy indexing:

var m = matrix([[1,2,3], [4,5,6], [7,8,9]], as_real)

// Swap rows 0 and 2
var swapped_rows = m[[2, 1, 0], [0, 1, 2]]

// Swap columns 0 and 2
var swapped_cols = m[[0, 1, 2], [2, 1, 0]]

Or use assignment for in-place swapping:

var m = matrix([[1,2,3], [4,5,6], [7,8,9]], as_real)

// Swap rows 0 and 2 in-place (requires temporary storage)
var temp = m.row(0)
m.set_row(m.row(2), 0)
m.set_row(temp, 2)

Removing rows or columns

Select all rows except specific ones:

var m = matrix([[1,2], [3,4], [5,6], [7,8]], as_real).transposed()

// Remove row 1: select rows [0, 2, 3]
var without_row1 = m[[0, 2, 3], [0, 1]]

// Using slicing (if removing from the end)
var without_last = m[:-1, :]

For more complex removals, you'll need to construct the index list programmatically:

// To remove row i from a matrix with n rows:
// indices = [0..i] + [i+1..n]

Extracting submatrices with masks

When you have a boolean mask indicating which rows or columns to select, convert it to index lists:

// Suppose you have determined which rows to keep
var keep_rows = [0, 2, 4, 6] // Indices of rows to select
var keep_cols = [1, 3] // Indices of columns to select

var m = random([8, 5], as_real)
var filtered = m[keep_rows, keep_cols]

This pattern is common in data filtering and feature selection.

Block-wise operations

Process a matrix in blocks for memory efficiency or algorithm requirements:

var m = random([1000, 1000], as_real);
var block_size = 100

// Process the matrix in 100x100 blocks
for (i : [0:10]) {
for (j : [0:10]) {
var start_row = i * block_size
var start_col = j * block_size
var block = m.block(start_row, start_col, block_size, block_size)

// Process block...
var processed = some_operation(block)
m.set_block(processed, [start_row, start_col])
}
}

Circular/periodic indexing

For periodic boundary conditions, you can use modulo arithmetic with fancy indexing:

var m = matrix([[1,2,3], [4,5,6], [7,8,9]], as_real)

// Access with wrap-around: indices [0, 1, 2, 0, 1]
var wrapped_indices = [0, 1, 2, 0, 1]
var periodic = m[wrapped_indices, [0, 1, 2]]

This is useful in signal processing and simulations with periodic boundaries.

Summary

The Linear Algebra module provides a comprehensive suite of indexing tools that cover everything from simple element access to sophisticated selection patterns:

  • Element access ([]) for reading and writing individual elements
  • Block methods (block(), row(), col(), corners) for named rectangular extractions
  • Slice notation ([start:stop:step]) for concise range-based selection with steps and negative indices
  • Fancy indexing ([[i, j, k], ...]) for arbitrary, reordered, and duplicated selections
  • Mixed indexing (select(), set_select()) for maximum flexibility combining different strategies

All extraction operations return copies, ensuring safety but requiring careful use of in-place methods for efficiency. Understanding when to use each tool and how they interact enables you to write clear, efficient code for accessing and manipulating your matrix and array data.