Olov Lassus

Let's create us some Dart ranges

21 Nov 2011

We recognize the good ol’ for loop from the C-family of programming languages. It is available with the same familiar syntax and semantics in Dart, too:

for (int i = 0; i < 10; i++) {
  print(i);
}

In addition to that, Dart has a for-in loop, similar to Java in semantics (iterator(), hasNext() and next()). for-in is used to conveniently loop over any Iterable sequence, such as a List.

for (int i in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]) {
  print(i);
}

If you’re into Python then you know that over there you for loop over sequences exclusively, and use xrange extensively.

for i in xrange(0, 10):
    print(i)

xrange is quite convenient and prevents a lot of variable name stuttering. It takes up to three parameters: start, stop and step. Python xranges exclude the stop endpoint and step is 1 by default. start is 0 by default so you’ll often see xrange(len(seq)) used in Python code to iterate over sequence indices.

Back to Dart. While good ol’ for loops have been working fine for the last few decades or so, perhaps something xrange-like could be fun here too. We’ve already got for-in and iterators after all. Let’s create a Range class that implements the Iterable interface with a corresponding RangeIterator and we’ll be able to do for-in range, with top-level function range being a wrapper around the Range constructor. We’ll include both endpoints so range(0, 9) means [0, 1, .. 9]. Again step defaults to 1 so range(0, 9) is the same as range(0, 9, 1).

for (int i in range(0, 9)) {
  print(i);
}

That looks swell. No surprise it has already been discussed on dart-misc. We can also pass a range to anything that expects an Iterable. Here’s how to generate the List [0, 2, .. 8] using the List.from constructor:

var list = new List<int>.from(range(0, 8, 2));

Iterating over indices is done like so:

var str = "hellojed";
for (int i in range(0, str.length - 1)) {
  print("$i: ${str[i]}");
}

We’ll add a top-level function indices to sweeten it a bit:

for (int i in indices(str)) {
  print("$i: ${str[i]}");
}

We can make range more useful still. The Collection interface (implemented by List and Set among others) contains length, every, filter, forEach, isEmpty and some. Let’s implement those too. Now we can pass around ranges anywhere a Collection is expected, for example to addAll:

var queue = new Queue<int>();
// ...
queue.addAll(range(0, 9));

How about a List with the numbers [0, 1, .. 50] that are perfect squares:

bool isSquare(int n) => Math.pow(Math.sqrt(n).round(), 2) == n;
var squares = range(0, 50).filter(isSquare);

Or the other way around? map isn’t part of Collection but let’s add it anyways:

var squares = range(0, 7).map((e) => e * e);

The original example can also be rewritten like this, using forEach:

range(0, 9).forEach(print);

Not too shabby, all in all. Good ol’ for loops have less overhead than their for-in range equivalent but the difference is nothing to write home about, especially if your loop body does something useful. Your personal style is probably more important - as for myself I consider range a nice complement.

Check out the tests for more examples. The implementation should be easy to follow if you want to learn how to create a Collection of your own. If you want a little Dart exercise then change Range so that it only implements Iterable<int> and remove everything Collection-specific. Then add it back again guided by the unit tests, this time with your own code. Or start from scratch altogether.

Oh and the Dart for-in loop is just (very convenient) syntactic sugar of something similar to this:

for (Iterator it = range(0, 9).iterator(); it.hasNext(); ) {
  int i = it.next();
  print(i);
}

dart-range source available on github.

Show comments (Dart misc)


Follow me on Twitter

« Dart syntax highlighting support for Pygments    ↑ Home     »