Getting Familiar with Dart

Collections and Iterables


Learning Objectives

  • You know how to create and manipulate lists, maps, and sets in Dart.
  • You know how to use type annotations to define the type of data that a collection can hold.
  • You know how to use collections as function parameters and return values.
  • You know how to iterate over collections using iterables and know common methods like forEach, where, and map.

Most programming languages provide a set of data structures that can be used to manipulate collections of data. The most commonly used data structures include lists (arrays), maps (key-value pairs), and sets. In Dart, these data structures are available as part of the standard library.

Common data collections

The common data collections in Dart include lists, maps, and sets.

Lists and sets

Lists are created using [] and sets are created using {}. The following program shows how to create a list and a set in Dart.

Run the program to see the output

Lists and sets have a similar syntax, but lists allow duplicate values, while sets do not. Both also have methods for adding, removing, and clearing values, as shown below.

Run the program to see the output

Lists can be turned into sets and vice versa using the toSet and toList methods. The following example demonstrates this.

Run the program to see the output

Both lists and sets have a length, and values in lists can be accessed with the indexing operator []. Sets on the other hand are not indexed. Iterating over values in a list can be done using a for loop and an index, or using a for loop and the in keyword. For loop and an index does not work for sets, and when iterated over with a for loop, the in keyword is used.

The following example shows iterating over the values in both a list and a set.

Run the program to see the output

Loading Exercise...

Maps

Maps are created using {}, where each key-value pair is separated by a colon. The following program shows how to create a map in Dart.

Run the program to see the output

Maps have a remove and clear, and a [key] = value shorthand for adding key-value pairs. The following example demonstrates these.

Run the program to see the output

To iterate over a map, the for ... in loop is used, where the loop variable is a MapEntry object. The following example demonstrates iterating over a map.

Run the program to see the output

Loading Exercise...

Type inference

Dart uses type inference to infer the data type of a variable at compile time. However, there are times when the type of a variable cannot be inferred, or when the type of a variable needs to be flexible.

As an example, if a set does not contain values during creation, the type must be explicitly stated during creation, or otherwise Dart will infer the type as Map. This is demonstrated in the following example.

Run the program to see the output

When you run the above program, you will see the type of the data collection as well as the type of data that the collection can hold. In the above example, the type of data is dynamic. To specify the type of data that the collection can hold, type annotations are used.

Type annotations

The type of data that a collection can hold can be defined using type annotations. Type annotations are provided within smaller < than and bigger than > symbols, and the data type within the symbols. In the following example, we create a list that contains only integers, a map that has integers as keys and strings as values, and a set that can only contain strings.

Run the program to see the output

Using type annotations also leads to a situation, where we do not explixitly need to declare the type Set, as the type can be inferred from the type annotation that shows that there are individual values. This is demonstrated in the following example.

Run the program to see the output

Defining the type annotations increases type-safety, which then allows providing type-specific warnings and errors to the developer. As an example, compiling or running the following application shows an error indicating that a string cannot be assigned to the parameter type int — this stems from us attempting to place a string into a list that can only contain integers.

Run the program to see the output

Collections as function parameters and return values

When using collections as function parameters or return values, the type of the data collection and the type of data that the collection can hold should be explicitly defined.

For lists, the type is List, for maps, the type is Map, and for sets, the type is Set. Type annotations are placed immediately after the data type using the smaller than and larger than symbols, which then include the data type. As an example, the following outlines a function that could be used to print a list that contains integers.

Run the program to see the output

Similarly, the following example outlines a function that could be used to create a map that contains integers as keys and strings as values.

Run the program to see the output

There are some cases, where the data type of a collection is not declared. As an example, if we would create a function that can be used to swap two items in a list, it likely would not be meaningful to restrict the type of data that the list can contain. The following example outlines such a function.

Run the program to see the output

As a reminder, we previously learned that Dart is pass-by-value. The above example outlines a program where the value that is passed is a reference to the list. As the parameter list in the function swap holds a reference as the value, the function has access to the list that is being referenced, and can thus manipulate it.

Collections as iterables

Collections are iterable, which means that objects in the collection can be iterated over one at a time. In Dart, this functionality is implemented through the Iterable-class, which provides the functionality for iterating over — or going over the contents of — a collection.

Both List and Set implement the Iterable, which makes it easy to iterate over the values in the collections. Iterating over values in a Map is also possible, although due to maps containing key-value -pairs, iteration is somewhat different.

Here, we briefly visit some of the methods used when working with iterables: where, map, and fold.

Filtering with where

When iterating over a collection, we can filter the iterated values using the where method. The method returns an iterable, which allows using its methods to decide what to do with the remaining values. In the following example, the where method is used to limit the iterable to values that are larger than 1.

Run the program to see the output

The where method returns an iterable, which means that they can be further iterated over. An iterable can also be turned into a list or a set using the toList and toSet methods. The following example demonstrates this.

Run the program to see the output

Maps cannot be directly iterated over using the where method, as the method is not available for maps. Instead, we can iterate over the entries using the entries property, which has an iterable collection of MapEntry objects with key and value properties. The following example demonstrates how to filter a map based on the value of the key-value pair.

Run the program to see the output

There is no toMap method in an iterable, but the named constructor fromEntries of Map can be used to create a map from an iterable of MapEntry objects. The following example demonstrates this.

Run the program to see the output

Iterators also have firstWhere and lastWhere methods that return the first or last element that satisfy a given condition. The following example demonstrates how to find the first odd number from a list.

Run the program to see the output

The methods firstWhere and lastWhere also have an optional parameter orElse, which is a function that is called when no element satisfies the condition. The following example demonstrates how to find the first odd number from a list, and if no odd number is found, the value -1 is returned.

Run the program to see the output

Loading Exercise...

Transforming with map

Another useful method when handling iterables is map, which allows transforming values from one to another. In the following example, we multiply numbers in an iterable by two and then print them.

Run the program to see the output

Folding an iterable

Iterables can be reduced to a single value using the fold function. The fold function iteratively combines values of the iterable, leading to a single value. The function takes two parameters: a starting value and a two-parameter function describing how values in the iterable should be combined.

The following function demonstrates merging a list containing strings to a single string variable.

Run the program to see the output

Dart also has a function join that can be used to concatenate strings in a list. The join function takes a separator as an argument, which is placed between the strings in the list.

Similarly, iterables containing numbers could be processed. The following example demonstrates calculating the sum of numbers in a list.

Run the program to see the output

Loading Exercise...