# Sets

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

> How to keep unique values with sets and set operations, and how to add and remove at both ends with queues from dart:collection.

## What Is a Set?

**Set** = a collection where every value is **unique**. The same value can never appear twice.

For example:

- the **different** subjects a student takes
- the roll numbers of students who came to the library this week
- the **different** words used in an essay

Adding a value that is already there does nothing.

**Simple lesson:** a set is like a list that refuses duplicates.

### A Real-World Example

The library keeps a visitor book for the week.

- Asha visits on Monday, Wednesday and Friday.
- The librarian wants to know **who** visited, not how many times.
- So each name is written once, even if the student came three times.

That list of names is a set.

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

Watch the curly braces `{ }` in the output. They mean a set, not a list.

## What You Will Learn

In this lesson, you will learn:

1. What sets are, and how to create them.
2. How to add, remove and search in a set.
3. Set operations: union, intersection and difference.
4. How to convert collections, and make sets that cannot change.
5. How a Queue adds and removes items at both ends.

## Set Fundamentals

### Creating Sets with Set Literals

**Set literal** = values inside `{ }`, separated by commas.

```dart runnable title="A set keeps one copy of each value"
void main() {
  var subjects = {'Maths', 'Science', 'English', 'Maths'};
  print(subjects);
  print(subjects.length);
}
```

`'Maths'` was written twice, but the set keeps it once. The length is 3.

### Typed Sets and Empty Sets

- **Typed set:** `Set<String> houses = {'Red', 'Blue'};`
- **Empty set:** `var visitors = <String>{};`

`{}` on its own makes an empty **Map**, not a set (see **Maps**).

For an empty set, write the type: `<String>{}`, or `Set<String> visitors = {};`.

### Unique Values

- A set checks every new value with `==`.
- If an equal value is already inside, the new one is **not** added.
- The order you added items in is kept.

**Simple lesson:** write a set with `{ }`, and Dart keeps just one copy of each value.

## Set Properties

| Property | Gives |
|---|---|
| `length` | how many items |
| `isEmpty` / `isNotEmpty` | whether there are no items, or some |
| `first` / `last` | the first or last item added |

```dart runnable title="Set properties"
void main() {
  var houses = {'Red', 'Blue', 'Green'};
  print(houses.length);
  print(houses.isEmpty);
  print(houses.isNotEmpty);
  print(houses.first);
  print(houses.last);
}
```

A set has **no index**. You cannot write `houses[0]`. Use `first`, `last` or a loop instead.

**Simple lesson:** a set has a length, a first and a last item, but no index.

## Adding and Removing Elements

### Adding Elements

- **`add(x)`** = adds `x`, and gives `true` if it was new, `false` if it was already there.
- **`addAll([...])`** = adds many values at once.

### Removing Elements

- **`remove(x)`** = removes `x`, and gives `true` if it was there.
- **`removeWhere(test)`** = removes every item that passes the test.
- **`clear()`** = removes everything.

```dart runnable title="Removing from a set"
void main() {
  var clubs = {'Chess', 'Music', 'Art', 'Coding', 'Dance'};
  print(clubs.remove('Art'));
  print(clubs.remove('Cricket'));
  clubs.removeWhere((c) => c.startsWith('D'));
  print(clubs);
  clubs.clear();
  print(clubs);
}
```

**Simple lesson:** `add` and `remove` tell you whether they changed the set, with `true` or `false`.

## Searching Elements

- **`contains(x)`** = `true` if `x` is in the set.
- **`lookup(x)`** = gives back the item in the set that equals `x`, or `null`.

```dart runnable title="Is this student in the chess club?"
void main() {
  var chess = {'Asha', 'Ravi', 'Priya'};
  print(chess.contains('Ravi'));
  print(chess.contains('Kiran'));
  print(chess.lookup('Priya'));
  print(chess.lookup('Kiran'));
}
```

`contains` on a set is **very fast**, even with thousands of items. A list has to check item by item.

**Simple lesson:** use `contains` to check membership. It is the main reason to use a set.

## Set Operations

Pick an operation and compare the two clubs:

- **`union(other)`** = everything in **either** set.
- **`intersection(other)`** = only what is in **both** sets.
- **`difference(other)`** = what is in this set but **not** in the other.

```dart runnable title="Comparing two clubs"
void main() {
  var chess = {'Asha', 'Ravi', 'Priya', 'Kiran'};
  var music = {'Priya', 'Kiran', 'Meena', 'Imran'};
  print(chess.union(music));
  print(chess.intersection(music));
  print(chess.difference(music));
  print(music.difference(chess));
}
```

`chess.difference(music)` and `music.difference(chess)` are different. Order matters.

These come from maths class: union (∪), intersection (∩) and difference (−).

**Simple lesson:** `union` joins, `intersection` keeps the shared part, and `difference` takes away.

## Iterating Sets

Sets work with the same loops as lists (see **Loops**):

```dart runnable title="Three ways to visit every item"
void main() {
  var houses = {'Red', 'Blue', 'Green'};

  for (var h in houses) {
    print('$h House');
  }

  houses.forEach((h) => print(h.toUpperCase()));

  var list = houses.toList();
  for (var i = 0; i < list.length; i++) {
    print('$i: ${list[i]}');
  }
}
```

A set has no index, so a counted `for` loop needs `toList()` first.

### Functional Operations

`where`, `map`, `any` and `every` work just like on lists (see **Working with Iterables**):

```dart runnable title="where, map, any and every on a set"
void main() {
  var marks = {45, 82, 91, 30, 67};
  print(marks.where((m) => m >= 50).toSet());
  print(marks.map((m) => m + 5).toSet());
  print(marks.any((m) => m < 35));
  print(marks.every((m) => m > 20));
}
```

**Simple lesson:** loop over a set with `for-in`, and use `where` and `map` just like with lists.

## Converting Collections

### Removing Duplicates

A very common trick: turn a list into a set, and back into a list.

```dart runnable title="Remove duplicate names"
void main() {
  var signUps = ['Asha', 'Ravi', 'Asha', 'Priya', 'Ravi', 'Asha'];
  var unique = signUps.toSet();
  print(unique);
  var cleanList = unique.toList();
  print(cleanList);
  print('${signUps.length} sign-ups, ${cleanList.length} students');
}
```

- **`list.toSet()`** = a set with the duplicates gone.
- **`set.toList()`** = a list, so you can use indexes and sort again.
- **`Set.from(list)`** = another way to make a set from a list.

**Simple lesson:** `toSet()` removes duplicates, and `toList()` turns a set back into a list.

## Unmodifiable Sets

**Unmodifiable set** = a set that can never change.

- **`const {...}`** = fixed while you write the code.
- **`Set.unmodifiable(x)`** = a locked copy, made while the program runs.

```dart runnable expect-error title="A const set cannot change"
void main() {
  const weekend = {'Saturday', 'Sunday'};
  print(weekend.contains('Sunday'));
  weekend.add('Friday');
}
```

Adding to it fails with an `Unsupported operation` error.

**Easy way to remember**

- Order and duplicates matter: **List**.
- Only "is it in there?" matters: **Set**.
- Need to remove duplicates: `list.toSet().toList()`.

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

## What Is a Queue?

**Queue** = a collection where items join at one end and leave from the other.

**FIFO (First In, First Out)** = the first item added is the first item taken out.

For example:

- students waiting at the canteen counter
- print jobs waiting for the school printer
- messages waiting to be sent

A queue can also add and remove at **both** ends, when you need to.

**Simple lesson:** a queue is a line: join at the back, leave from the front.

### A Real-World Example

At the canteen, students stand in a line.

- A new student joins at the **back**.
- The shopkeeper serves the student at the **front**.
- Nobody in the middle is served first.

That is FIFO. Try it:

A queue prints with `{ }`, like a set, but it keeps duplicates and order.

## Queue Fundamentals

### Importing dart:collection

`Queue` is not built in like `List`. It lives in the **dart:collection** library (see **Libraries**).

```dart
import 'dart:collection';
```

### Creating Queues

```dart runnable title="Your first queue"
import 'dart:collection';

void main() {
  var line = Queue<String>();
  line.add('Asha');
  line.add('Ravi');
  line.add('Priya');
  print(line);

  var printJobs = Queue.of(['Report', 'Timetable']);
  print(printJobs);
}
```

- `Queue<String>()` = an empty queue of text.
- `Queue.of([...])` = a queue that starts with these items.

### Queue vs List

| Question | List | Queue |
|---|---|---|
| Read any item by index? | Yes, `list[3]` | No, use `elementAt(3)` (slow) |
| Remove from the front? | Slow on big lists | Fast |
| Add to the front? | Slow on big lists | Fast |
| Best for | items you look up by position | items you process in order |

A list has to shift every item when you remove the first one. A queue does not.

**Simple lesson:** use a queue when you add at one end and remove from the other, again and again.

## Adding Elements

| Method | What it does |
|---|---|
| `add(x)` | joins at the back |
| `addLast(x)` | joins at the back (same as `add`) |
| `addFirst(x)` | goes straight to the front |
| `addAll([...])` | many items join at the back |

```dart runnable title="Joining the line"
import 'dart:collection';

void main() {
  var line = Queue<String>();
  line.add('Asha');
  line.addLast('Ravi');
  line.addAll(['Priya', 'Kiran']);
  line.addFirst('Teacher');
  print(line);
}
```

`addFirst` lets someone skip to the front, like a teacher at the canteen.

**Simple lesson:** `add` joins at the back, and `addFirst` jumps to the front.

## Accessing Elements

- **`first`** = the item at the front, the next one to leave.
- **`last`** = the item at the back.
- **`elementAt(i)`** = the item at position `i`. It walks the queue, so avoid it in big queues.

```dart runnable title="Who is at the front?"
import 'dart:collection';

void main() {
  var line = Queue.of(['Asha', 'Ravi', 'Priya']);
  print('Next to be served: ${line.first}');
  print('Last in line: ${line.last}');
  print('Second in line: ${line.elementAt(1)}');
}
```

Reading `first` does **not** remove the item. It only looks.

**Simple lesson:** `first` peeks at the front, and `last` peeks at the back.

## Removing Elements

| Method | What it does |
|---|---|
| `removeFirst()` | takes the front item out, and gives it back |
| `removeLast()` | takes the back item out, and gives it back |
| `remove(x)` | takes out the first `x` found, gives `true` or `false` |
| `clear()` | empties the queue |

### An Empty Queue

```dart runnable expect-error title="Nobody is left to serve"
import 'dart:collection';

void main() {
  var line = Queue<String>();
  print(line.isEmpty);
  line.removeFirst();
}
```

The error is `Bad state: No element`. Check `isEmpty` or `isNotEmpty` before removing.

**Simple lesson:** `removeFirst` serves the front item, so always check the queue is not empty first.

## Queue Properties

- **`length`** = how many items are waiting.
- **`isEmpty`** = `true` when nobody is waiting.
- **`isNotEmpty`** = `true` when at least one item is waiting.

```dart runnable title="Serve everyone, one by one"
import 'dart:collection';

void main() {
  var line = Queue.of(['Asha', 'Ravi', 'Priya']);
  print('${line.length} waiting');
  while (line.isNotEmpty) {
    var student = line.removeFirst();
    print('Serving $student, ${line.length} left');
  }
  print(line.isEmpty);
}
```

This `while (queue.isNotEmpty)` pattern is how most queues are processed.

**Simple lesson:** loop with `while (queue.isNotEmpty)` and `removeFirst()` to process every item in order.

## Iterating Queues

You can also loop over a queue **without** removing anything:

```dart runnable title="Looking at the whole line"
import 'dart:collection';

void main() {
  var line = Queue.of(['Asha', 'Ravi', 'Priya']);

  for (var s in line) {
    print('Waiting: $s');
  }

  line.forEach((s) => print(s.toUpperCase()));

  var asList = line.toList();
  for (var i = 0; i < asList.length; i++) {
    print('${i + 1}. ${asList[i]}');
  }
}
```

- **`for-in`** and **`forEach()`** visit items from front to back.
- A queue has no index, so a counted loop needs `toList()` first.

Do not add or remove items **inside** a `for-in` loop over the same queue.

Dart stops with a `ConcurrentModificationError`. Use `while (isNotEmpty)` with `removeFirst()` instead.

**Simple lesson:** `for-in` looks at every item, and `removeFirst` in a `while` loop processes them.

## Common Use Cases

### Breadth-First Search

**Breadth-first search** = visit the start, then everything one step away, then everything two steps away, and so on.

```dart runnable title="Who gets the school notice, level by level?"
import 'dart:collection';

void main() {
  var tellsTo = {
    'Principal': ['Teacher A', 'Teacher B'],
    'Teacher A': ['Asha', 'Ravi'],
    'Teacher B': ['Priya'],
  };
  var queue = Queue.of(['Principal']);
  while (queue.isNotEmpty) {
    var person = queue.removeFirst();
    print(person);
    queue.addAll(tellsTo[person] ?? []);
  }
}
```

The principal is first, then both teachers, then the students. A queue keeps that order.

**Easy way to remember**

- **Queue** = canteen line: first in, first out.
- `add` = join the back. `removeFirst` = serve the front.
- Need an index? Use a **List** instead.

**Simple lesson:** use a queue whenever things must be handled in the order they arrived.

## TL;DR

1. **Set** = unique values: `{'Asha', 'Ravi'}`. Empty: `<String>{}`.
2. `add` returns `false` for a duplicate. `contains` is fast.
3. **union**, **intersection**, **difference** compare two sets.
4. `list.toSet()` removes duplicates. `Set.unmodifiable` cannot change.
5. **Queue** (from `dart:collection`): `addFirst`, `addLast`, `removeFirst`, `removeLast`.

## Common mistakes

| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| `var s = {};` for a set | `{}` is an empty set | `{}` is an empty Map | Set methods like `add` behave like a Map's. |
| `houses[0]` | Sets have indexes | Use `first`, or `toList()` first | An error. |
| Expecting duplicates to be counted | A set counts every add | A set keeps one copy | `length` is smaller than expected. |
| Mixing up difference order | `a.difference(b)` is the same as `b.difference(a)` | The order matters | The wrong students are listed. |
| Ignoring `add`'s result | `add` always adds | It gives `false` for a duplicate | You miss that a value was already there. |
| Changing a `const` set | `const` only fixes the name | A `const` set never changes | An `Unsupported operation` error. |
| No `import 'dart:collection';` | Queue is built in | It lives in dart:collection | `Undefined name 'Queue'`. |
| `queue[0]` | Queues have indexes | Use `first`, or `elementAt` | An error. |
| `removeFirst()` on an empty queue | It gives `null` | It crashes | `Bad state: No element`. |
| Changing the queue in its `for-in` | The loop sees the change | Use `while (isNotEmpty)` | `ConcurrentModificationError`. |
| A queue for lookups by position | Queues are like lists | Use a List for indexes | Slow code. |
| Reading `first` to take the item | `first` removes it | `first` only looks | The item is processed twice. |

## Check Yourself

## Exercises

## What to Remember

- A set never holds the same value twice.
- `{}` alone is a map, so write `<Type>{}` for an empty set.
- Use union, intersection and difference to compare groups.
- `toSet()` is the quickest way to remove duplicates from a list.
- A Queue is the right tool when you add and remove at both ends.

Next: **Maps**. You will learn to store pairs, like a subject and its marks, and find any value by its key.
