# Sets

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

> Unique unordered values: creating sets, add and discard, union, intersection and difference, the whole method list, and frozen sets.

Think about a guest list for a school event.

Each name appears once. Writing a name twice changes nothing — the person is either invited or not. And nobody cares what order the names are in; the only question anyone asks is "is this person on the list?"

That is a **set**: no duplicates, no order, and a very fast way to ask whether something is in it.

Here is that idea in action — five names go in, and only the distinct ones stay:

```python runnable title="Duplicates vanish on their own"
invited = {"Asha", "Ravi", "Asha", "Meera", "Ravi"}

print(len(invited))
print(sorted(invited))
print("Asha" in invited)
```

Five names went in and three came out. The set kept one of each.

A set holds unique values with no order, and answers "is this in here?" very quickly.

## What You Will Learn

In this lesson, you will learn:

- What makes a set different from a list, and when that difference matters
- How to create one, including the empty-set trap
- How to add and remove items safely
- The four set operations, which answer real questions in one line
- The whole method list, and which methods change the set
- What a frozen set is, and when you need one

By the end of this lesson, you will be able to create sets, add and remove items safely, use the four set operations to compare groups, pick the right method for each job, and decide when a frozen set is needed.

## Python Sets

### Introduction to Sets

A **set** is a collection of unique values with no order.

It is the right tool for three jobs: removing duplicates, asking whether something is present, and comparing two groups.

### Creating Sets

There are several ways to make a set, but one of them also solves the common problem of removing duplicates from another collection:

```python runnable title="Three ways to make a set"
a = {78, 85, 60}
b = set([78, 85, 60])
c = set("Asha")

print(sorted(a))
print(sorted(b))
print(sorted(c))
```

### Set Characteristics

Most of a set's characteristics follow from one idea — a set forgets both duplicates and positions:

<div className="tscroll">

| Characteristic | What it means |
|---|---|
| Unique | duplicates are dropped, silently |
| Unordered | there is no first item, and no indexing |
| Changeable | you can add and remove items |
| Hashable items only | numbers, text and tuples — not lists |
| Very fast lookup | `in` does not search item by item |

</div>

### Unique Elements in Sets

Dropping duplicates is half of what a set is. Here it is working on a list:

```python runnable title="Duplicates are dropped"
marks = [78, 85, 78, 60, 85, 78]

unique = set(marks)

print("original:", marks)
print("unique:  ", sorted(unique))
print(len(marks), "->", len(unique))
```

Removing duplicates from a list is the single most common use:

```python runnable title="Unique, and back to a list"
marks = [78, 85, 78, 60]

print(sorted(set(marks)))
print(list(set(marks)) != [])
```

Note the `sorted()`. Turning a set back into a list gives no promise about order, so sort it if the order matters.

### Unordered Nature of Sets

A set has no positions, so there is no indexing and no slicing.

```python runnable expect-error title="A set cannot be indexed"
marks = {78, 85, 60}

print(marks[0])
```

Printing a set shows its items in whatever order it happens to store them. **That order is not something to rely on** — for text it can differ between runs of the program.

```python runnable varies title="The order you see is not promised"
subjects = {"Maths", "Science", "English", "History"}

print(subjects)
print(sorted(subjects))
```

The second line is the one to use when the order matters to a reader.

### Mutable Sets

Although a set has no positions, it can still grow and shrink as items are added and removed:

```python runnable title="Sets can be changed"
marks = {78, 85}

marks.add(60)
marks.discard(78)

print(sorted(marks))
```

### Data Types Supported in Sets

Everything in a set must be **hashable**, which in practice means unchangeable.

```python runnable title="What may go in a set"
mixed = {78, 78.5, "Asha", True, None, (1, 2)}

print(len(mixed))
print(sorted(str(item) for item in mixed))
```

```python runnable expect-error title="A list cannot go in a set"
{[78, 85]}
```

A list could change after it went in, which would leave the set unable to find it again. Use a tuple instead.

One oddity worth seeing once, because it also applies to dictionary keys:

```python runnable title="True and 1 are the same value"
print({1, True, 1.0})
print({0, False})
print(len({1, True}))
```

`True` equals `1`, so the set keeps only one of them.

## Creating Sets

The empty set is the one case that does not work the way you would guess.

### Using Curly Braces

```python runnable title="Curly braces"
marks = {78, 85, 60}

print(sorted(marks), type(marks).__name__)
```

### Using the set() Function

```python runnable title="set() from other collections"
print(sorted(set([78, 85, 78])))
print(sorted(set((78, 85))))
print(sorted(set("hello")))
print(sorted(set({"maths": 78, "science": 85})))
```

On a dictionary, `set()` takes the keys.

### Empty Sets

This is the trap.

```python runnable title="Empty braces are a dictionary, not a set"
not_a_set = {}
real_set = set()

print(type(not_a_set).__name__)
print(type(real_set).__name__)
```

`{}` was dictionary syntax long before sets existed, so an empty set has to be written `set()`.

```python runnable expect-error title="Where that bites"
collection = {}

collection.add(78)
```

`AttributeError: 'dict' object has no attribute 'add'` — the object was a dictionary all along.

### Creating Sets from Lists and Tuples

```python runnable title="From a list, and back again"
marks = [78, 85, 78, 60]

unique = set(marks)
back_to_list = sorted(unique)

print(unique == {60, 78, 85})
print(back_to_list)
```

## Working with Set Elements

Now that you can make a set, here is how to put items in and take them out.

### Adding Elements with add()

`add()` puts in one item. Adding something already there does nothing, with no complaint.

```python runnable title="add"
marks = {78, 85}

marks.add(60)
marks.add(78)

print(sorted(marks))
```

### Removing Elements with remove()

`remove()` deletes an item and **raises if it is not there**.

```python runnable expect-error title="remove on a missing item"
marks = {78, 85}

marks.remove(78)
print(sorted(marks))

marks.remove(99)
```

### Removing Elements with discard()

`discard()` does the same and says nothing when the item is missing.

```python runnable title="discard never complains"
marks = {78, 85}

marks.discard(78)
marks.discard(99)

print(sorted(marks))
```

Use `remove()` when the item ought to be there and its absence is a bug. Use `discard()` when you simply want it gone.

`pop()` removes an arbitrary item — not the first, because there is no first:

```python runnable title="pop takes whichever item it likes"
marks = {78}

print(marks.pop())
print(marks)
```

```python runnable expect-error title="pop on an empty set"
set().pop()
```

### Clearing a Set

`clear()` empties the whole set at once:

```python runnable title="clear"
marks = {78, 85}

marks.clear()

print(marks, len(marks))
```

### Updating a Set

`update()` adds everything from another collection.

```python runnable title="update"
marks = {78, 85}

marks.update([60, 92])
marks.update({45}, (78,))
marks.update("ab")

print(sorted(str(item) for item in marks))
```

`add()` puts in one item; `update()` puts in each item of whatever you give it — including each character of a string, which surprises people.

`update("Asha")` adds four separate characters, not the word. A string is a
collection of characters, and `update()` takes each item of whatever it is
given. Use `add("Asha")` when you mean the whole word.

## Membership Testing

### Using the in Operator

This is the question sets were built to answer:

```python runnable title="in"
invited = {"Asha", "Ravi", "Meera"}

print("Asha" in invited)
print("Ken" in invited)
print("Ken" not in invited)
```

A list checks items one by one; a set jumps straight to the answer, however large it is.

```python runnable title="The same question, two collections"
big_list = list(range(50000))
big_set = set(big_list)

print(49999 in big_list)
print(49999 in big_set)
print("Both correct — but the set did far less work")
```

For a handful of items the difference does not matter. For thousands, checked repeatedly, it is the difference between quick and unusable.

When a program keeps asking "have I seen this before?", build a set once and ask
it, rather than searching a list every time. That one change turns a slow loop
into a quick one more often than any other single fix.

## Set Operations

The four operations are what make sets worth learning. Each answers a question that would otherwise need a loop.

The examples all use these two groups of students:

```python runnable title="Two groups to compare"
maths = {"Asha", "Ravi", "Meera"}
science = {"Ravi", "Meera", "Ken"}

print("Maths:  ", sorted(maths))
print("Science:", sorted(science))
```

### Union

Everyone in either group.

```python runnable title="union"
maths = {"Asha", "Ravi", "Meera"}
science = {"Ravi", "Meera", "Ken"}

print(sorted(maths.union(science)))
print(sorted(maths | science))
```

### Intersection

Only those in both.

```python runnable title="intersection"
maths = {"Asha", "Ravi", "Meera"}
science = {"Ravi", "Meera", "Ken"}

print(sorted(maths.intersection(science)))
print(sorted(maths & science))
```

### Difference

In the first group and not the second. **Order matters here.**

```python runnable title="difference"
maths = {"Asha", "Ravi", "Meera"}
science = {"Ravi", "Meera", "Ken"}

print(sorted(maths.difference(science)))
print(sorted(science - maths))
```

### Symmetric Difference

In one group or the other, but not both.

```python runnable title="symmetric_difference"
maths = {"Asha", "Ravi", "Meera"}
science = {"Ravi", "Meera", "Ken"}

print(sorted(maths.symmetric_difference(science)))
print(sorted(maths ^ science))
```

All four, side by side:

<div className="tscroll">

| Operation | Operator | Method | Answers |
|---|---|---|---|
| Union | `\|` | `union()` | in either |
| Intersection | `&` | `intersection()` | in both |
| Difference | `-` | `difference()` | in the first only |
| Symmetric difference | `^` | `symmetric_difference()` | in one but not both |

</div>

The methods accept any collection; the operators need both sides to be sets.

```python runnable expect-error title="An operator needs two sets"
marks = {78, 85}

print(sorted(marks.union([60])))

print(marks | [60])
```

### Subset Testing

A subset is a group whose members all appear inside another group:

```python runnable title="issubset"
everyone = {"Asha", "Ravi", "Meera", "Ken"}
maths = {"Asha", "Ravi"}

print(maths.issubset(everyone))
print(maths <= everyone)
print(everyone.issubset(maths))
```

### Superset Testing

A superset is the opposite relationship: it contains every item from another set:

```python runnable title="issuperset"
everyone = {"Asha", "Ravi", "Meera", "Ken"}
maths = {"Asha", "Ravi"}

print(everyone.issuperset(maths))
print(everyone >= maths)
```

A set is both a subset and a superset of itself:

```python runnable title="A set contains itself"
marks = {78, 85}

print(marks.issubset(marks))
print(marks.issuperset(marks))
print(marks < marks)
```

`<` means "a subset, and not equal", which is why the last line is `False`.

### Disjoint Sets

Two sets are **disjoint** when they share nothing.

```python runnable title="isdisjoint"
maths = {"Asha", "Ravi"}
music = {"Ken", "Priya"}
science = {"Ravi", "Ken"}

print(maths.isdisjoint(music))
print(maths.isdisjoint(science))
```

`a.isdisjoint(b)` says the same as `not (a & b)`, and says it more clearly.

## Set Methods

You have now met the methods one at a time. Here is the full list, starting with the ones that change the set:

```python runnable title="The methods that change the set"
marks = {78}

marks.add(85)
print("add:     ", sorted(marks))

marks.update([60, 92])
print("update:  ", sorted(marks))

marks.remove(92)
print("remove:  ", sorted(marks))

marks.discard(99)
print("discard: ", sorted(marks))

copy_of = marks.copy()
marks.clear()
print("clear:   ", marks, "copy still:", sorted(copy_of))
```

Each operation also has an in-place version that changes the set instead of returning a new one:

```python runnable title="The in-place versions"
maths = {"Asha", "Ravi"}
science = {"Ravi", "Ken"}

both = maths.copy()
both.update(science)
print("update (union):", sorted(both))

shared = maths.copy()
shared.intersection_update(science)
print("intersection_update:", sorted(shared))

only_maths = maths.copy()
only_maths.difference_update(science)
print("difference_update:", sorted(only_maths))
```

<div className="tscroll">

| Method | What it does | Changes the set? |
|---|---|---|
| `add(x)` | adds one item | yes |
| `update(xs)` | adds each item of a collection | yes |
| `remove(x)` | deletes `x`, raises if missing | yes |
| `discard(x)` | deletes `x`, silent if missing | yes |
| `pop()` | removes an arbitrary item | yes |
| `clear()` | empties the set | yes |
| `copy()` | a shallow copy | no |
| `union(xs)` | items in either | no |
| `intersection(xs)` | items in both | no |
| `difference(xs)` | items only in this one | no |
| `symmetric_difference(xs)` | items in one but not both | no |

</div>

## Frozen Sets

One more kind of set exists, and it solves the problem you just saw — a set cannot go inside another set.

### Introduction to Frozen Sets

A **frozen set** is a set that cannot be changed — the tuple to a set's list.

### Creating Frozen Sets with frozenset()

```python runnable title="frozenset"
subjects = frozenset(["Maths", "Science"])

print(sorted(subjects))
print(len(subjects), "Maths" in subjects)
```

```python runnable expect-error title="A frozen set refuses to change"
subjects = frozenset(["Maths"])

subjects.add("Science")
```

### Mutable vs Immutable Sets

Everything that only reads the set still works:

```python runnable title="Everything that reads still works"
a = frozenset([78, 85, 60])
b = frozenset([85, 92])

print(sorted(a | b))
print(sorted(a & b))
print(a.issubset({78, 85, 60, 92}))
print(type(a | b).__name__)
```

Every operation that **returns** something works. Only the ones that change it are missing.

### Use Cases of Frozen Sets

The main one: a frozen set can go inside another set, or be a dictionary key. An ordinary set cannot.

```python runnable expect-error title="A set cannot contain a set"
{{78, 85}}
```

```python runnable title="A frozen set can"
groups = {frozenset(["Asha", "Ravi"]), frozenset(["Meera"])}

print(len(groups))

timetable = {
    frozenset(["Monday", "Wednesday"]): "Maths",
    frozenset(["Tuesday"]): "Science",
}

print(timetable[frozenset(["Wednesday", "Monday"])])
```

The last line matters: the key was looked up with the days in a different order and still found, because a set has no order.

The other use is a constant that nobody can change by accident:

```python runnable title="A constant collection"
SCHOOL_DAYS = frozenset(["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"])

def try_to_add(days):
    try:
        days.add("Saturday")
        return "changed it"
    except AttributeError:
        return "could not change it"

print(try_to_add(SCHOOL_DAYS))
print(len(SCHOOL_DAYS))
```

## Built-in Functions with Sets

```python runnable title="The built-ins that work on a set"
marks = {78, 85, 60, 92}

print("len:   ", len(marks))
print("max:   ", max(marks))
print("min:   ", min(marks))
print("sum:   ", sum(marks))
print("sorted:", sorted(marks))
print("set:   ", sorted(set([78, 78, 85])))
```

`sorted()` is the usual way to show a set to a person, because it fixes the order.

```python runnable title="zip on sets is a bad idea"
subjects = ["Maths", "Science"]
marks = [78, 85]

print(list(zip(subjects, marks)))
print(dict(zip(subjects, marks)))
```

`zip()` pairs things up by position, and a set has no positions — so zipping two sets pairs values arbitrarily. Use lists or tuples when order matters.

## Common mistakes

Before moving on, here are some of the mistakes beginners make with sets.

Each row is one mistake. The middle column is what you will actually see on screen.

<div className="tscroll">

| Mistake | What you see | Why it happens | Fix |
|---|---|---|---|
| `{}` for an empty set | `AttributeError: 'dict' object has no attribute 'add'` | `{}` is an empty dictionary | `set()` |
| Indexing a set | `TypeError: 'set' object is not subscriptable` | A set has no positions | Convert with `sorted()` or `list()` |
| Relying on the printed order | Different order on a different run | A set's order is not promised | `sorted()` before printing |
| `remove()` on a missing item | `KeyError` | `remove()` raises; `discard()` does not | Use `discard()`, or check with `in` |
| A list inside a set | `TypeError: unhashable type: 'list'` | Items must be unchangeable | Use a tuple |
| A set inside a set | `TypeError: unhashable type: 'set'` | A set can change, so it cannot be hashed | Use `frozenset` |
| `add()` where `update()` was meant | The whole collection added as one item | `add` adds exactly one thing | `update` to add each item |
| `update()` with a string | Each character added separately | A string is a collection of characters | `add("Asha")` for the whole word |
| Expecting duplicates to be reported | They vanish silently | Dropping duplicates is the point | Compare `len()` before and after |
| An operator with a list on one side | `TypeError: unsupported operand type(s)` | Operators need two sets | Use the method, which accepts any collection |
| Expecting `pop()` to take the first item | Some other item is removed | There is no first item | Use a list if order matters |

</div>

## What to Remember

- **A set holds unique values with no order**, and answers `in` very quickly.
- Duplicates are dropped silently, which is what makes `set(marks)` the usual way to find unique values.
- **`{}` is an empty dictionary. An empty set is `set()`.**
- A set has no positions, so no indexing and no slicing.
- **Never rely on the printed order** — use `sorted()` whenever a reader will see it.
- Items must be hashable: numbers, text and tuples, never lists or sets.
- `add()` adds one item; `update()` adds each item of a collection, including each character of a string.
- `remove()` raises when the item is missing; `discard()` does not.
- `|` union, `&` intersection, `-` difference, `^` symmetric difference — and the methods accept any collection while the operators need two sets.
- A **frozen set** cannot change, so it can be a dictionary key or go inside another set.

Next: **Dictionary**
