ExZarr.Indexing (ExZarr v1.1.0)
View SourceAdvanced indexing operations for ExZarr arrays.
This module provides comprehensive indexing support including:
- Basic slicing with step support
- Negative index handling
- Fancy indexing (integer arrays)
- Boolean indexing (masks)
- Block indexing (chunk-aligned access)
Index Normalization
Indices can be negative (counting from the end) and are automatically normalized to positive values:
normalize_index(-1, 10) # => 9
normalize_index(-5, 10) # => 5
normalize_index(5, 10) # => 5Slice Specifications
Slices can include start, stop, and step:
%{start: 0, stop: 10, step: 1} # Every element from 0 to 9
%{start: 0, stop: 10, step: 2} # Every other element
%{start: 10, stop: 0, step: -1} # Reverse order
Summary
Functions
Computes chunk-aligned block boundaries for efficient access.
Extracts indices from a boolean mask.
Normalizes an index to a positive value within bounds.
Normalizes a slice specification to have explicit start, stop, and step values.
Converts a multidimensional slice specification to start/stop tuples for each dimension.
Computes the effective slice range from a normalized slice specification.
Computes the size (number of elements) of a slice.
Validates boolean indexing with a mask.
Validates fancy indexing with integer arrays.
Types
Functions
@spec compute_block_indices(non_neg_integer(), non_neg_integer(), pos_integer()) :: [ {non_neg_integer(), non_neg_integer()} ]
Computes chunk-aligned block boundaries for efficient access.
Given a start/stop range and chunk size, computes the blocks that fully or partially overlap with the range. Returns block boundaries that are chunk-aligned.
Examples
iex> ExZarr.Indexing.compute_block_indices(5, 25, 10)
[{0, 10}, {10, 20}, {20, 30}]
iex> ExZarr.Indexing.compute_block_indices(10, 20, 10)
[{10, 20}]
Extracts indices from a boolean mask.
Returns a list of indices where the mask is true.
Examples
iex> mask = {true, false, true, false, true}
iex> ExZarr.Indexing.mask_to_indices(mask)
[0, 2, 4]
@spec normalize_index(index(), pos_integer()) :: {:ok, non_neg_integer()} | {:error, atom()}
Normalizes an index to a positive value within bounds.
Handles negative indices by counting from the end of the dimension. Validates that the index is within the valid range [0, size).
Examples
iex> ExZarr.Indexing.normalize_index(5, 10)
{:ok, 5}
iex> ExZarr.Indexing.normalize_index(-1, 10)
{:ok, 9}
iex> ExZarr.Indexing.normalize_index(-5, 10)
{:ok, 5}
iex> ExZarr.Indexing.normalize_index(15, 10)
{:error, :index_out_of_bounds}
@spec normalize_slice(slice_spec(), pos_integer()) :: {:ok, slice_spec()} | {:error, term()}
Normalizes a slice specification to have explicit start, stop, and step values.
Fills in defaults for missing values:
- start defaults to 0 (or size-1 for negative step)
- stop defaults to size (or -1 for negative step)
- step defaults to 1
Handles negative indices and validates the slice is within bounds.
Examples
iex> ExZarr.Indexing.normalize_slice(%{start: 0, stop: 10, step: 1}, 20)
{:ok, %{start: 0, stop: 10, step: 1}}
iex> ExZarr.Indexing.normalize_slice(%{start: -5, stop: nil, step: 1}, 20)
{:ok, %{start: 15, stop: 20, step: 1}}
iex> ExZarr.Indexing.normalize_slice(%{start: nil, stop: nil, step: -1}, 20)
{:ok, %{start: 19, stop: -1, step: -1}}
@spec parse_slice_spec([index_spec()], tuple()) :: {:ok, {list(), list()}} | {:error, term()}
Converts a multidimensional slice specification to start/stop tuples for each dimension.
Takes a list of index specifications (integers, slices, or arrays) and converts them to normalized start/stop tuples per dimension.
Examples
iex> ExZarr.Indexing.parse_slice_spec([5, %{start: 0, stop: 10, step: 1}], {100, 100})
{:ok, {[5], [%{start: 0, stop: 10, step: 1}]}}
@spec slice_indices(slice_spec()) :: [integer()]
Computes the effective slice range from a normalized slice specification.
Returns a list of indices that the slice covers, handling step correctly. This is useful for computing the actual size of a slice result.
Examples
iex> ExZarr.Indexing.slice_indices(%{start: 0, stop: 10, step: 1})
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
iex> ExZarr.Indexing.slice_indices(%{start: 0, stop: 10, step: 2})
[0, 2, 4, 6, 8]
iex> ExZarr.Indexing.slice_indices(%{start: 10, stop: 0, step: -2})
[10, 8, 6, 4, 2]
@spec slice_size(slice_spec()) :: non_neg_integer()
Computes the size (number of elements) of a slice.
Examples
iex> ExZarr.Indexing.slice_size(%{start: 0, stop: 10, step: 1})
10
iex> ExZarr.Indexing.slice_size(%{start: 0, stop: 10, step: 2})
5
iex> ExZarr.Indexing.slice_size(%{start: 10, stop: 0, step: -1})
10
@spec validate_boolean_mask(tuple(), pos_integer()) :: :ok | {:error, term()}
Validates boolean indexing with a mask.
Ensures the mask has the same shape as the dimension being indexed.
Examples
iex> mask = {true, false, true, false, true}
iex> ExZarr.Indexing.validate_boolean_mask(mask, 5)
:ok
iex> mask = {true, false, true}
iex> ExZarr.Indexing.validate_boolean_mask(mask, 5)
{:error, :mask_size_mismatch}
@spec validate_fancy_indices([integer()], pos_integer()) :: :ok | {:error, term()}
Validates fancy indexing with integer arrays.
Ensures all indices in the array are within bounds for the dimension.
Examples
iex> ExZarr.Indexing.validate_fancy_indices([0, 5, 9], 10)
:ok
iex> ExZarr.Indexing.validate_fancy_indices([0, 5, 15], 10)
{:error, :index_out_of_bounds}