Dart Stride Iterators

Dart

Introduction

In the context of numerical computation, data is often represented as multi-dimensional arrays. In Dart, a multi-dimensional array can be represented as a list of lists. To speed up arithmetical operations and minimize memory usage it can be advantageous to flatten multi-dimensional arrays (see Numerical computation).

The example below shows the elements of a 2-dimensional array stored as a 1-dimensional array (a Dart list) using a row major layout.

2D-Array

In order to access the elements of the column with index 1 (highlighted using an orange rectangle) one would need to start the iteration at index 1 and then use a step size of 3 (the number of columns in the 2D-array) to move to the next element.

The package stride provides stride iterators and extension methods on List and Iterable that can accomplish precisely the task described above.

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 and especially typed lists it makes perfect sense to disable concurrent modification checks. 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]);

  final strideIt1 = numericalList.stride(
    stepSize,
    startIndex,
    false,   // <-----------    Disabling concurrent modification checks.
  );

  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, false);
  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
Enables iterating Dart lists and iterables using a custom step size and start index.