Generators

Generators #

Introduction #

Generators are functions that lazily produce a sequence of values. A generators return value can either be Iterable for functions that produce values synchronously, or Stream for functions that produce values asynchronously. More on asynchronous programming in Dart in a later chapter.

Synchronous generator #

Iterable<int> countdown(int start) sync* {
  for (var i = start; i >= 0; i--) {
    yield i;
  }
}

void main() {
  for (var value in countdown(10)) {
    print(value);
  }
}

Notice: the sync* in the function definition, which indicates it is a synchronous generator.

A generator yield values instead of returning a single value like a normal function. The generator in the example yield a finite sequence of values, but a generator could also yield an infinite amount of values. For example, you could have a random number generator that yields an endless sequence of random numbers.

Asynchronous generator #

Here it is again, rewritten as an asynchronous function.

Stream<int> countdown(int start) async* {
  for (var i = start; i >= 0; i--) {
    yield i;
  }
}

main() async {
  await for (var value in countdown(10)) {
    print(value);
  }
}

Notice: the async* in the function definition. And that the return type has changed to Stream<int>.

To iterate over the values of an async generator with a for-loop, we need to add async to the method, and await to the for-loop.

A use case for an async generator could be to implement an infinite scrolling page, like the feed on Instagram.

Stream<List<Post>> feed() async* {
  var pageNumber = 1;
  while (true) {
    yield await fetchPage(pageNumber);
    pageNumber++;
  }
}

More on async/await later. If you can’t wait then you can read more about it here.

Challenge #

Write an synchronous generator implementation of fizz buzz.

Fizz buzz is a group word game for children to teach them about division. Players take turns to count incrementally, replacing any number divisible by three with the word “Fizz”, and any number divisible by five with the word “Buzz”, and any number divisible by both 3 and 5 with the word “Fizz Buzz”.

Example #

1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14, Fizz Buzz, 16, 17, Fizz, 19, Buzz, Fizz, 22, 23, Fizz, Buzz, 26, Fizz, 28, 29, Fizz Buzz

Code #

Implement a fizz buzz generator using streams.


{$ begin main.dart $}
class FizzBuzzer {
  Iterable<String> generate() sync* {
    // TODO implement here
  }
}

{$ end main.dart $}
{$ begin solution.dart $}
class FizzBuzzer {
  Iterable<String> generate() sync* {
    var n = 0;
    while (true) {
      n++;
      if (n % 15 == 0) {
        yield "Fizz Buzz";
      } else if (n % 3 == 0) {
        yield "Fizz";
      } else if (n % 5 == 0) {
        yield "Buzz";
      } else {
        yield n.toString();
      }
    }
  }
}

{$ end solution.dart $}
{$ begin test.dart $}
//import "./solution.dart";
//import "../mock_result.dart";
//
//const _result = result;

void main() {
  const expected =
      "1, 2, Fizz, 4, Buzz, Fizz, 7, 8, Fizz, Buzz, 11, Fizz, 13, 14, Fizz Buzz, 16, 17, Fizz, 19, Buzz, Fizz, 22, 23, Fizz, Buzz, 26, Fizz, 28, 29, Fizz Buzz, 31, 32, Fizz, 34, Buzz";
  final Iterable<String> sequence = FizzBuzzer().generate();
  final String actual = sequence.take(35).join(", ");
  final success = actual == expected;
  if (success) {
    _result(true, ["Hurray, you did it 🥳"]);
  } else {
    _result(false, ["Incorrect, try again!"]);
  }
}

{$ end test.dart $}
{$ begin test_import.dart $}
part 'test.dart';
{$ end test_import.dart $}