Chapters

Hide chapters

Dart Apprentice: Fundamentals

First Edition · Flutter · Dart 2.18 · VS Code 1.71

Dart Apprentice: Fundamentals

Section 1: 16 chapters
Show chapters Hide chapters

15. Iterables
Written by Jonathan Sande

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

What comes next in the sequence a, b, c,…? You don’t need to think twice to know it’s d. How about 2, 4, 8, 16,…? The next power of two is 32. Again, not much of a challenge. Here’s one that’s a little trickier: O, T, T, F, F,…? What letter comes next? You can find the answer at the end of the chapter if you need it.

All these sequences were iterable, and you were the iterator by providing the next value in the sequence. In this chapter, you’ll learn what iterables and iterators are in Dart, why they’re useful and how to create your own.

What’s an Iterable?

An iterable in Dart is any collection that lets you loop through its elements. In more technical speak, it’s a class that implements the Iterable interface. List and Set are two iterables you’re already familiar with. Not every Dart collection is iterable, though. You learned in the previous chapter that you can’t directly loop over a map. If you want to visit all of a map’s elements, you have to iterate over the keys, values or entries, all of which are iterables.

Reviewing List Iteration

To review, take a look at the following example.

final myList = ['bread', 'cheese', 'milk'];
print(myList);
[bread, cheese, milk]
for (final item in myList) {
  print(item);
}
bread
cheese
milk

Meeting an Iterable

Lists are one specific kind of iterable, but you can also work directly with the Iterable type. These are often accessible as properties of another collection. The example below will demonstrate that.

final reversedIterable = myList.reversed;
print(reversedIterable);
(milk, cheese, bread)

Converting an Iterable to a List

Another way to force Dart to get all the elements from an iterable is to convert the iterable to a list.

final reversedList = reversedIterable.toList();
print(reversedList);
[milk, cheese, bread]

Operations on Iterables

This chapter will cover a few basic operations on iterables. You’ll learn other more advanced operations in Chapter 2, “Anonymous Functions”, in Dart Apprentice: Beyond the Basics.

Creating an Iterable

Trying to directly instantiate an Iterable will give you an error. To see that, write the code below in main:

final myIterable = Iterable();
abstract class Iterable<E> {
  const Iterable();

  // ...
}
Iterable<String> myIterable = ['red', 'blue', 'green'];

Accessing Elements

The way to access a particular element in the collection is to use elementAt with an index.

final thirdElement = myIterable.elementAt(2);
print(thirdElement);
E elementAt(int index) {
  // ...
  int elementIndex = 0;
  for (E element in this) {
    if (index == elementIndex) return element;
    elementIndex++;
  }
  // ...
}

Finding the First and Last Elements

Use first and last to get the first and last elements of an iterable:

final firstElement = myIterable.first;
final lastElement = myIterable.last;

print(firstElement);
print(lastElement);

Getting the Length

Finding the number of elements in a collection is as deceptively simple as finding the last element.

final numberElements = myIterable.length;
print(numberElements);

Other Important Methods on Iterable

The Iterable class has many other important methods you should learn. Here are a few of them:

Exercise

  1. Create a map of key-value pairs.
  2. Make a variable named myIterable and assign it the keys of your map.
  3. Print the third element.
  4. Print the first and last elements.
  5. Print the length of the iterable.
  6. Loop through the iterable with a for-in loop.

Creating an Iterable From Scratch

A great way to learn how iterables work is to make an iterable collection from scratch. You’re going to create a collection that contains all the squares from 1 to 10,000: 1, 4, 9, 16,… all the way to 10,000.

Using a Generator

The functions you’ve seen previously returned at most a single value. A generator is a function that produces multiple values before finishing. With the generator that you’re going to create in this chapter, the values come in the form of an iterable. This is known as a synchronous generator because all the values are available on demand when you need them.

Creating a Synchronous Generator

Add the following function to your project file:

Iterable<int> hundredSquares() sync* {
  for (int i = 1; i <= 100; i++) {
    yield i * i;
  }
}

Running the Code

Now that your generator function is finished, replace the contents of main with the following:

final squares = hundredSquares();
for (int square in squares) {
  print(square);
}
1
4
9
16
25
...
9604
9801
10000

Using an Iterator

Iterables don’t know how to move from element to element within their collections. That’s the job of an iterator. In the previous example, the generator function served the purpose of the iterator, but in this example, you’ll create an iterator using the Iterator class.

Implementing the Iterator

Now, you’ll make an iterator that knows how to find the next squared number in the series of squares.

class SquaredIterator implements Iterator<int> {

  int _index = 0;

  // 1
  @override
  bool moveNext() {
    _index++;
    return _index <= 100;
  }

  // 2
  @override
  int get current => _index * _index;
}

Implementing the Iterable

Now that you have your iterator, you can write the code for your iterable.

class HundredSquares extends Iterable<int> {
  @override
  Iterator<int> get iterator => SquaredIterator();
}

Running the Code

Open the file with your main method again and add the following import at the top:

import 'package:starter/squares.dart';
final squares = HundredSquares();
for (int square in squares) {
  print(square);
}
1
4
9
16
25
...
9604
9801
10000

When to Use Lists, Sets, Maps or Iterables

Each type of collection has its strengths. Here’s some advice about when to use which:

Challenges

Before moving on, here are some challenges to test your knowledge of iterables. It’s best if you try to solve them yourself, but solutions are available in the challenge folder for this chapter if you get stuck.

Challenge 1: Iterating by Hand

  1. Create a list named myList and populate it with four values.
  2. Use myList.iterator to access the iterator.
  3. Manually step through the list using moveNext and print each value using current.

Challenge 2: Fibonacci to Infinity

Create a custom iterable collection that contains all the Fibonacci numbers. Add an optional constructor parameter that will stop iteration after the nth number.

Key Points

  • Iterables are collections in which you can step through each element individually.
  • Iterables are lazy, meaning no work is done to determine the collection elements until you ask for them.
  • Finding length or elementAt may be slow because the iterable calculates them by stepping through the elements one by one.
  • List and Set are iterable collections with additional features.
  • A synchronous generator is a function that returns multiple values on demand.
  • An Iterable uses an Iterator to determine the next element in the collection.

Where to Go From Here?

To explore more about collections and their methods in Dart, browse the contents of the dart:collection library. Also, check out Data Structures & Algorithms in Dart to learn how to build custom collections such as the following:

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now