# Maps

Course: [Dart](https://www.devtutorials.ai/learn/dart) · Section: Collections
Source: https://www.devtutorials.ai/learn/dart/collections/maps

> How to store key-value pairs, read and change values by key, handle missing keys, and add, remove, search, loop over and transform maps.

## What Is a Map?

**Map** = a collection of **key-value pairs**. You find each value by its key.

**Key** = the label you look up, like `'Maths'`.

**Value** = what the key gives you, like `92`.

For example:

- subject → marks: `'Maths'` → `92`
- roll number → name: `21` → `'Asha'`
- English word → Hindi word: `'water'` → `'paani'`

**Simple lesson:** a map links each key to one value, so you can find a value by its name instead of its position.

### A Real-World Example

Think of a dictionary.

- You do not read it from page 1 to find "elephant".
- You look up the **word** (the key), and read its **meaning** (the value).
- Every word appears **once**. A meaning can repeat.

A Dart map works the same way. Keys are unique, values do not have to be.

Every **Run** box on this page runs on the course's Dart runner in your browser.

Maps print with `{ }` and a colon between each key and its value.

## What You Will Learn

In this lesson, you will learn:

1. What maps and key-value pairs are, and how to create them.
2. How to read and change values by key, and handle missing keys.
3. Map properties, like `keys`, `values` and `entries`.
4. How to add, remove, search and loop over entries.
5. How to transform maps, nest them, and make maps that never change.

## Map Fundamentals

### Creating Maps with Map Literals

**Map literal** = `key: value` pairs inside `{ }`, separated by commas.

```dart runnable title="A map of subjects and marks"
void main() {
  var marks = {'Maths': 92, 'Science': 85, 'English': 78};
  print(marks);
  print(marks.length);
}
```

### Typed Maps

**Typed map** = `Map<KeyType, ValueType>`.

- `Map<String, int>` = text keys, whole-number values.
- `Map<int, String>` = number keys, text values.

```dart runnable title="Different key and value types"
void main() {
  Map<int, String> rollCall = {1: 'Asha', 2: 'Ravi', 3: 'Priya'};
  Map<String, bool> present = {'Asha': true, 'Ravi': false};
  print(rollCall[2]);
  print(present['Ravi']);
}
```

### Empty Maps

```dart
var marks = <String, int>{};
Map<String, int> scores = {};
```

`{}` with no type is a `Map<dynamic, dynamic>`. Add the types so Dart can check what goes in.

**Simple lesson:** write a map with `{ key: value }`, and give it types like `Map<String, int>`.

## Accessing and Updating Values

Press each button and watch the table:

Look at two things:

- **A new key** adds a new row.
- **An existing key** replaces its value. A key can never appear twice.

### Accessing Values by Key

`marks['Maths']` gives the value stored under the key `'Maths'`.

### Updating Values and Adding New Pairs

`marks['Maths'] = 95;`

- If `'Maths'` is already a key, its value is **replaced**.
- If it is not, a new pair is **added**.

```dart runnable title="Reading, changing and adding"
void main() {
  var marks = {'Maths': 92, 'Science': 85};
  print(marks['Maths']);
  marks['Maths'] = 95;
  marks['English'] = 78;
  print(marks);
}
```

### Handling Missing Keys

A key that is not in the map gives `null`. It does **not** crash.

```dart runnable title="Missing keys give null"
void main() {
  var marks = {'Maths': 92, 'Science': 85};
  print(marks['Art']);
  print(marks['Art'] ?? 0);
  print(marks.containsKey('Art'));
}
```

- `marks['Art']` has the type `int?`, because the key might be missing (see **Null Safety**).
- Use `?? 0` for a default value.
- Use `containsKey` to check first.

A map's keys are compared exactly. `'maths'` and `'Maths'` are two different keys.

Pick one spelling for your keys, and keep to it everywhere in your program.

**Simple lesson:** `map[key]` reads or writes one value, and a missing key gives `null`.

## Map Properties

| Property | Gives |
|---|---|
| `length` | how many pairs |
| `isEmpty` / `isNotEmpty` | whether there are no pairs, or some |
| `keys` | all the keys |
| `values` | all the values |
| `entries` | all the pairs, as `MapEntry` objects |

```dart runnable title="Keys, values and entries"
void main() {
  var marks = {'Maths': 92, 'Science': 85, 'English': 78};
  print(marks.keys);
  print(marks.values);
  print(marks.entries);
  print(marks.keys.toList());
  print(marks.isNotEmpty);
}
```

**`MapEntry`** = one pair, with a `.key` and a `.value`.

`keys`, `values` and `entries` are Iterables, so they print with `( )`. Use `toList()` for a list.

**Simple lesson:** `keys` and `values` give the two halves of a map, and `entries` gives the pairs.

## Adding and Changing Elements

| Method | What it does |
|---|---|
| `addAll(other)` | copies every pair from another map |
| `putIfAbsent(key, () => v)` | adds the pair only if the key is missing |
| `update(key, (old) => new)` | changes one value using the old value |
| `updateAll((k, v) => new)` | changes every value |

```dart runnable title="Adding and changing pairs"
void main() {
  var marks = {'Maths': 92, 'Science': 85};
  marks.addAll({'English': 78, 'Hindi': 81});
  marks.putIfAbsent('Maths', () => 50);
  marks.putIfAbsent('Art', () => 70);
  marks.update('Science', (old) => old + 5);
  print(marks);
  marks.updateAll((subject, mark) => mark + 2);
  print(marks);
}
```

- `putIfAbsent('Maths', ...)` did nothing, because `'Maths'` was already there.
- `update` on a missing key crashes. Add `ifAbsent: () => value` to give a value instead.

**Simple lesson:** `putIfAbsent` adds only new keys, and `update` changes a value using its old value.

## Removing Elements

- **`remove(key)`** = removes the pair, and gives back its value (or `null`).
- **`removeWhere((k, v) => test)`** = removes every pair that passes the test.
- **`clear()`** = removes everything.

```dart runnable title="Removing pairs"
void main() {
  var marks = {'Maths': 92, 'Science': 85, 'Art': 30, 'Music': 28};
  print(marks.remove('Science'));
  print(marks.remove('Dance'));
  marks.removeWhere((subject, mark) => mark < 35);
  print(marks);
  marks.clear();
  print(marks);
}
```

**Simple lesson:** remove a pair by its key, or remove many with a test.

## Searching Maps

- **`containsKey(k)`** = is this key in the map?
- **`containsValue(v)`** = is this value in the map?

```dart runnable title="Searching keys and values"
void main() {
  var marks = {'Maths': 92, 'Science': 85, 'English': 92};
  print(marks.containsKey('Maths'));
  print(marks.containsKey('Art'));
  print(marks.containsValue(85));
  print(marks.containsValue(100));
}
```

`containsKey` is very fast. `containsValue` has to check every value, so it is slower on big maps.

**Simple lesson:** look things up by key whenever you can, because keys are what maps are built for.

## Iterating Maps

A map itself is not a list, so you loop over its `keys`, `values` or `entries`.

```dart runnable title="Four ways to go through a map"
void main() {
  var marks = {'Maths': 92, 'Science': 85, 'English': 78};

  for (var subject in marks.keys) {
    print(subject);
  }

  for (var e in marks.entries) {
    print('${e.key} -> ${e.value}');
  }

  marks.forEach((subject, mark) {
    print('$subject: $mark');
  });

  var total = 0;
  for (var m in marks.values) {
    total += m;
  }
  print('Total: $total');
}
```

`forEach` on a map gives **two** values each time: the key and the value.

### Counting with a Map

A map is perfect for counting things:

**Simple lesson:** loop over `entries` or use `forEach` to get each key with its value.

## Map Transformations

### map() and Creating New Maps

**`map((k, v) => MapEntry(newK, newV))`** = a **new** map, made by changing every pair.

```dart runnable title="A new map from an old one"
void main() {
  var marks = {'Maths': 92, 'Science': 85};
  var withGrace = marks.map((subject, mark) => MapEntry(subject, mark + 5));
  print(withGrace);
  var shout = marks.map((subject, mark) => MapEntry(subject.toUpperCase(), mark));
  print(shout);
  print(marks);
}
```

The old map is not changed.

### Building a Map with for

```dart runnable title="A map built from a list"
void main() {
  var names = ['Asha', 'Ravi', 'Priya'];
  var nameLengths = {for (var n in names) n: n.length};
  print(nameLengths);
}
```

### cast()

**`cast<K, V>()`** = treats a map as a map with other types. It is mostly used with JSON data (see **File Handling**).

**Simple lesson:** `map()` with `MapEntry` builds a new map, and `{for ...}` builds one from a list.

## Useful Map Features

### Nested Maps

A value can be another map, or a list:

```dart runnable title="A map inside a map"
void main() {
  Map<String, Map<String, int>> reportCards = {
    'Asha': {'Maths': 92, 'Science': 88},
    'Ravi': {'Maths': 75, 'Science': 81},
  };
  print(reportCards['Asha']!['Maths']);
  for (var student in reportCards.keys) {
    print('$student: ${reportCards[student]}');
  }
}
```

`reportCards['Asha']` may be `null` in Dart's eyes, so `!` says "this key is there" (see **Null Safety**).

### Maps with Objects and Default Values

- Values can be any type, including your own classes (see **Classes**).
- For a default when a key is missing, use `map[key] ?? defaultValue`.

**Simple lesson:** maps can hold lists and other maps, so one variable can hold a whole report card.

## Unmodifiable Maps

```dart runnable expect-error title="A const map cannot change"
void main() {
  const passMarks = {'Maths': 35, 'Science': 35};
  print(passMarks['Maths']);
  passMarks['Art'] = 30;
}
```

- **`const {...}`** = fixed before the program runs.
- **`Map.unmodifiable(x)`** = a locked copy, made while it runs.

**Easy way to remember**

- Find by **position**: List.
- Find by **name**: Map.
- Only **unique** values: Set.

**Simple lesson:** use `const` or `Map.unmodifiable()` when a map must never change.

## TL;DR

1. **Map** = key-value pairs: `{'Maths': 92}`. Keys are unique.
   - Typed: `Map<String, int>`. Empty: `<String, int>{}`.
2. **Read and write:** `map[key]` and `map[key] = value`.
   - A missing key gives `null`. Use `??` or `containsKey`.
3. **Properties:** `length`, `keys`, `values`, `entries` (`MapEntry` with `.key` and `.value`).
4. **Change:** `addAll`, `putIfAbsent`, `update`, `updateAll`, `remove`, `removeWhere`, `clear`.
5. **Loop:** over `keys`, `values` or `entries`, or `forEach((k, v) ...)`.
   - **Transform:** `map((k, v) => MapEntry(...))`. **Locked:** `const`, `Map.unmodifiable()`.

## Common mistakes

| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| `marks[0]` for the first pair | Maps have positions | Maps are found by key | `null`, or a type error. |
| `marks['Art'] + 5` | Every key exists | A missing key gives `null` | A null error. Use `?? 0`. |
| `for (var x in marks)` | A map is a list | Loop over `keys`, `values` or `entries` | An error. |
| `update` on a missing key | It adds the key | It crashes without `ifAbsent` | `Key not in map`. |
| The same key twice in a literal | Both are kept | The last one wins | One pair goes missing. |
| `marks.map((k, v) => v + 1)` | `map` returns values | It must return a `MapEntry` | A type error. |
| Changing a `const` map | Only the name is fixed | A `const` map never changes | An `Unsupported operation` error. |

## Check Yourself

## Exercises

## What to Remember

- A map stores key-value pairs. Keys are unique, values can repeat.
- `map[key]` reads and writes. A missing key gives `null`, so use `??` or `containsKey`.
- `keys`, `values` and `entries` let you loop over a map. `forEach` gives the key and value.
- `putIfAbsent` adds only new keys, and `update` changes a value from its old value.
- `map()` with `MapEntry` builds a new map, and `const` maps never change.

Next: **Strings**. You will learn to search, cut, change and build text.
