Dart Stride Iterators

Dart

Introduction

The package stride provides stride iterators and extension methods that make it possible to iterate data structures of type List and Iterable using a custom start point and step size.

In the context of numerical computation it is often useful to store data in multi-dimensional arrays. In Dart, a multi-dimensional array may be represented as a list of lists. To speed up arithmetical operations and minimize memory usage it may be advantageous to store a multi-dimensional array as a flat list. For more details see numerical computation with Dart.

The example below shows how the elements of a 2-dimensional array can be stored as a 1-dimensional array (a Dart list).

2D-Array

In order to access the elements of the column with index 1 (highlighted using an orange rectangle), we need to start the iteration at index 1. To move to the next element we have to use a step size, or stride, that is equal to the number of columns in the 2D-array.

Usage

To use this package, include stride as a dependency in your pubspec.yaml file. The program below demonstrates how to use the extension method stride to iterate lists using a custom step size and start index. Note that the iteration step size must not be zero. A negative step size and suitable start index may be used to iterate in reverse direction.

Tip: When iterating fixed size lists, immutable lists views, or typed lists it makes perfect sense to omit concurrent modification checks by using the method fastStride. The slight performance improvement is evident when iterating very long lists.

import 'dart:typed_data';

import 'package:stride/stride.dart';

main(List<String> args) {
  // 3x3 matrix.
  final array2D = <List<String>>[
    ['e00', 'e01', 'e02'],
    ['e10', 'e11', 'e12'],
    ['e20', 'e21', 'e22'],
  ];

  /// Elements of 3x3 matrix in row major layout.
  final list = ['e00', 'e01', 'e02', 'e10', 'e11', 'e12', 'e20', 'e21', 'e22'];

  final stepSize = 3;
  final startIndex = 1;
  final strideIt0 = list.stride(stepSize, startIndex);

  print('2D array:');
  print(array2D[0]);
  print(array2D[1]);
  print(array2D[2]);
  print('');

  print('Column 1:');
  print(strideIt0);
  print('');

  // Typed list (with fixed length).
  final numericalList =
      Float64List.fromList([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);

  // Omitting concurrent modification checks:
  final strideIt1 = numericalList.fastStride(
    stepSize,
    startIndex
  );

  print('Numerical list:');
  print(numericalList);
  print('');

  print('start index: 1 and step-size: 3:');
  print(strideIt1);
  print('');

  print('start index: 9 and step-size: -3:');
  final reverseStrideIt1 = numericalList.stride(-3, 9);
  print(reverseStrideIt1);
}

Running the program above produces the following console output:

$ dart example/bin/example.dart
2D array:
[e00, e01, e02]
[e10, e11, e12]
[e20, e21, e22]

Column 1:
(e01, e11, e21)

Numerical list:
[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0]

start index: 1 and step-size: 3:
(1.0, 4.0, 7.0, 10.0)

start index: 9 and step-size: -3:
(9.0, 6.0, 3.0, 0.0)

Row Major and Column Major Storage Layout

Consider an N-dimensional array, array_N, with length di along dimension i, where i ∈ [0, n-1]. Let array_1 be a Dart list able to store all d0 · d1 · … · dn-1 elements of array_N.

Let array_N[i0][i1]…[in‑1] be stored at location array_1[s0·i0 + … + sn-1·in-1], where the iteration step sizes, si, depend on the storage order.

For a row major storage order the step sizes are given by:

s0 = d1 · d2 ·   …   · dn-1

s1 = d2 · d3 ·   …   · dn-1

sn-2 = dn-1

sn-1 = 1.

For a column major storage order the step sizes are given by:

s0 = 1

s1 = d0

s2 = d0 · d1

sn-2 = d0 · d1 ·   …   · dn-3

sn-1 = d0 · d1 ·   …   · dn-2

For more information see Row- and column-major order.

Examples

A copy of the program shown in the section above can be found in the folder example.

Features and bugs

Please file feature requests and bugs at the issue tracker.

Libraries

stride