# Chapter 4:  Dictionaries and Sets


## Dictionaries:

A **dictionary** is a collection of **key-value pairs**.

*   A **key** is used to identify data.
    
*   A **value** is the actual data associated with that key.
    

Example:

```python
countries = {
    'IN': 'India',
    'GR': 'Germany',
    'MX': 'Mexico',
    'JP': 'Japan'
}
```

Here:

| Key | Value |
| --- | --- |
| `'IN'` | `'India'` |
| `'GR'` | `'Germany'` |
| `'MX'` | `'Mexico'` |
| `'JP'` | `'Japan'` |

**Dictionaries are Mutable**

You can:

*   add items
    
*   modify items
    
*   delete items
    

```python
prices = {'pen': 20}
prices['pen'] = 25

print(prices)
```

Output:

```plaintext
{'pen': 25}
```

**Keys Must Be Unique**

```python
d = {'x': 1, 'x': 100}
print(d)
```

Output:

```plaintext
{'x': 100}
```

The last value replaces the old one.

**Keys Must Be Immutable**

Allowed keys:

*   strings
    
*   integers
    
*   tuples
    

Not allowed:

*   lists
    
*   dictionaries
    

Correct:

```python
d = {1: 'one', 'a': 10, (1,2): 'tuple'}
```

Wrong:

```python
d = {[1,2]: 'list'}   # Error
```

### Accessing Values

Use the key inside square brackets.

```python
countries = {
    'IN': 'India',
    'MX': 'Mexico'
}

print(countries['IN'])
```

Output:

```plaintext
India
```

Example

**Using a list:**

```python
student = ['John', 'M', 'Paris', 21]
```

Problem:  
You must remember positions.

*   name → index 0
    
*   gender → index 1
    
*   city → index 2
    

Better approach → dictionary.

```python
student = {
    'name': 'John',
    'gender': 'M',
    'city': 'Paris',
    'age': 21
}
```

Accessing data becomes easier:

```python
print(student['name'])
print(student['city'])
```

Output:

```plaintext
John
Paris
```

**Adding New Key-Value Pairs**

Syntax:

```python
d[key] = value
```

Example:

```python
prices = {
    'pencil': 10,
    'pen': 20
}

prices['eraser'] = 15

print(prices)
```

Output:

```plaintext
{'pencil': 10, 'pen': 20, 'eraser': 15}
```

**Modifying Existing Values**

```python
prices['pen'] = 25
```

Example:

```python
prices = {'pen': 20}

prices['pen'] = 25

print(prices)
```

Output:

```plaintext
{'pen': 25}
```

### Augmented Assignment

```python
salary = {
    'programmer': 10000,
    'manager': 20000
}

salary['programmer'] += 1000

print(salary)
```

Output:

```plaintext
{'programmer': 11000, 'manager': 20000}
```

### Length of Dictionary

Use `len()`.

```python
prices = {
    'pencil': 10,
    'pen': 20,
    'eraser': 15
}

print(len(prices))
```

Output:

```plaintext
3
```

### KeyError Problem

If the key does not exist:

```python
prices['marker']
```

Output:

```python
KeyError
```

### get() Method

The `get()` method safely retrieves values.

Syntax:

```plaintext
d.get(key)
```

If key is missing → returns `None` instead of error.

Example:

```python
prices = {
    'pen': 20,
    'pencil': 10
}

print(prices.get('pen'))
print(prices.get('marker'))
```

Output:

```plaintext
20
None
```

### Using Default Value in get()

```plaintext
print(prices.get('marker', 0))
```

Output:

```plaintext
0
```

Here:

*   if `'marker'` exists → return its value
    
*   otherwise → return `0`  
    

### Difference Between \[\] and get()

| Method | If key missing |
| --- | --- |
| `d[key]` | Gives `KeyError` |
| `d.get(key)` | Returns `None` |
| `d.get(key, value)` | Returns default value |

### Real-Life Use Cases of Dictionaries

Dictionaries are useful for:

*   student records
    
*   product prices
    
*   employee salaries
    
*   phone books
    
*   configuration settings
    
*   database-like data
    

Example:

```python
phonebook = {
    'Rahul': 98765,
    'Amit': 87654
}

print(phonebook['Rahul'])
```

* * *

## `setdefault()` Method

The `setdefault()` method is used to:

1.  Get the value of a key
    
2.  If the key does not exist, add the key to the dictionary
    

**Syntax**

```plaintext
d.setdefault(key)
```

*   Returns the value of `key`
    
*   If key is missing:
    
    *   inserts key with value `None`
        
    *   returns `None`
        

```plaintext
d.setdefault(key, value)
```

*   Returns existing value if key exists
    
*   Otherwise:
    
    *   inserts key with given value
        
    *   returns that value
        

**Example 1:** Existing Key

```python
prices = {
    'pen': 22,
    'pencil': 10
}

print(prices.setdefault('pen'))
```

Output:

```plaintext
22
```

Dictionary remains unchanged.

**Example 2**: Missing Key

```python
prices = {
    'pen': 22,
    'pencil': 10
}

print(prices.setdefault('eraser'))
print(prices)
```

Output:

```python
None
{'pen': 22, 'pencil': 10, 'eraser': None}
```

**Example 3:** Missing Key with Default Value

```python
prices = {
    'pen': 22
}

print(prices.setdefault('marker', 50))
print(prices)
```

Output:

```plaintext
50
{'pen': 22, 'marker': 50}
```

### Difference Between `get()` and `setdefault()`

| Method | Missing Key Behavior |
| --- | --- |
| `get()` | Returns `None`, does NOT add key |
| `setdefault()` | Returns value AND adds key |

Example:

```python
d = {'a': 1}

d.get('b')
print(d)
```

Output:

```python
{'a': 1}
```

But:

```python
d.setdefault('b')
print(d)
```

Output:

```python
{'a': 1, 'b': None}
```

* * *

## Getting Keys, Values, and Items

Python provides 3 important dictionary methods:

| Method | Purpose |
| --- | --- |
| `keys()` | Returns all keys |
| `values()` | Returns all values |
| `items()` | Returns key-value pairs |

**Example** Dictionary

```python
prices = {
    'pencil': 10,
    'pen': 22,
    'eraser': 12
}
```

### keys()

```python
print(prices.keys())
```

Output:

```python
dict_keys(['pencil', 'pen', 'eraser'])
```

### values()

```python
print(prices.values())
```

Output:

```python
dict_values([10, 22, 12])
```

### items()

```python
print(prices.items())
```

Output:

```python
dict_items([('pencil', 10), ('pen', 22), ('eraser', 12)])
```

### Convert to List

```python
print(list(prices.keys()))
print(list(prices.values()))
print(list(prices.items()))
```

Output:

```python
['pencil', 'pen', 'eraser']

[10, 22, 12]

[('pencil', 10), ('pen', 22), ('eraser', 12)]
```

**Important Note**

These methods return **dictionary view objects**, not actual lists.

Advantages:

*   memory efficient
    
*   dynamic
    
*   automatically reflect dictionary changes  
    

Example:

```python
d = {'a': 1}

k = d.keys()

print(k)

d['b'] = 2

print(k)
```

Output:

```python
dict_keys(['a'])

dict_keys(['a', 'b'])
```

### reversed() with Dictionaries

Python 3.8+ supports reverse iteration.

```python
d = {'a': 10, 'b': 20, 'c': 30}

print(list(reversed(d)))
```

Output:

```python
['c', 'b', 'a']
```

### sorted() with Dictionaries

```python
d = {'b': 20, 'a': 10, 'c': 30}

print(sorted(d.keys()))
```

Output:

```python
['a', 'b', 'c']
```

* * *

## Checking Existence of Keys and Values

Use `in` and `not in`.

**Checking Keys**

```python
prices = {
    'pen': 22,
    'pencil': 10
}

print('pen' in prices)
```

Output:

```plaintext
True
```

### Checking Values

```python
print(22 in prices.values())
```

Output:

```python
True
```

### Checking Key-Value Pair

```plaintext
print(('pen', 22) in prices.items())
```

Output:

```plaintext
True
```

### Summary Table

| Expression | Meaning |
| --- | --- |
| `x in d` | Check key |
| `x in d.keys()` | Check key |
| `x in d.values()` | Check value |
| `(k,v) in d.items()` | Check key-value pair |

* * *

## Comparing Dictionaries

Only `==` and `!=` are supported.

Example

```python
d1 = {'x': 1, 'y': 2}
d2 = {'x': 1, 'y': 2}
d3 = {'x': 10, 'y': 20}

print(d1 == d2)
print(d1 == d3)
```

Output:

```plaintext
True
False
```

* * *

### Comparing Keys

```python
print(d1.keys() == d3.keys())
```

Output:

```python
True
```

Because both dictionaries have same keys.

**Important**

These operators are NOT supported:

```plaintext
<
>
<=
>=
```

* * *

## Deleting Key-Value Pairs

### Using `del`

```plaintext
prices = {
    'pen': 22,
    'marker': 30
}

del prices['marker']

print(prices)
```

Output:

```plaintext
{'pen': 22}
```

### Using `pop()`

`pop()`:

*   removes item
    
*   returns removed value  
    

Example

```python
prices = {
    'pen': 22,
    'pencil': 10
}

x = prices.pop('pen')

print(x)
print(prices)
```

Output:

```plaintext
22
{'pencil': 10}
```

### Avoiding KeyError in pop()

```plaintext
print(prices.pop('marker', 0))
```

Output:

```plaintext
0
```

### popitem()

Removes and returns the last inserted key-value pair.

```python
d = {
    'a': 1,
    'b': 2
}

print(d.popitem())
```

Output:

```python
('b', 2)
```

### clear()

Removes all items.

```plaintext
d.clear()

print(d)
```

Output:

```plaintext
{}
```

* * *

**Important Difference**

```plaintext
d = {}
```

creates a NEW empty dictionary.

But:

```plaintext
d.clear()
```

empties the SAME dictionary object.

* * *

### Creating Dictionary at Runtime

You can start with an empty dictionary.

```plaintext
prices = {}
```

Then add values dynamically.

**Example** Program

```python
prices = {}

fruit = input("Enter fruit name: ")
price = int(input("Enter price: "))

prices[fruit] = price

print(prices)
```

Sample Run:

```python
Enter fruit name: Apple
Enter price: 50

{'Apple': 50}
```

Multiple Inputs Example

```python
prices = {}

prices['Apple'] = 50
prices['Banana'] = 25
prices['Guava'] = 30

print(prices)
```

Output:

```plaintext
{'Apple': 50, 'Banana': 25, 'Guava': 30}
```

### Quick Revision Table

| Method | Purpose |
| --- | --- |
| `get()` | Safely get value |
| `setdefault()` | Get/add key |
| `keys()` | All keys |
| `values()` | All values |
| `items()` | All key-value pairs |
| `pop()` | Remove by key |
| `popitem()` | Remove last item |
| `clear()` | Empty dictionary |

* * *

## Creating a Dictionary using `dict()`

The `dict()` function can create dictionaries from existing data.  

### **From list of lists**

```python
list1 = [['a', 1], ['b', 2], ['c', 3]]

d1 = dict(list1)

print(d1)
```

Output:

```plaintext
{'a': 1, 'b': 2, 'c': 3}
```

*   First item → key
    
*   Second item → value  
    

### From tuple of tuples

```python
t1 = ('x', 4), ('y', 5), ('z', 6)

d2 = dict(t1)

print(d2)
```

Output:

```plaintext
{'x': 4, 'y': 5, 'z': 6}
```

### From strings of length 2

```python
d = dict(['A1', 'B2', 'C3'])

print(d)
```

Output:

```plaintext
{'A': '1', 'B': '2', 'C': '3'}
```

### Using keyword arguments

```python
d = dict(pen=10, pencil=5, eraser=3)

print(d)
```

Output:

```plaintext
{'pen': 10, 'pencil': 5, 'eraser': 3}
```

Keys become strings automatically.

### Using `zip()`

```python
countries = ['India', 'Japan', 'France']
capitals = ['Delhi', 'Tokyo', 'Paris']

d = dict(zip(countries, capitals))

print(d)
```

Output:

```plaintext
{'India': 'Delhi', 'Japan': 'Tokyo', 'France': 'Paris'}
```

`zip()` joins two sequences together.

### Empty dictionary using `dict()`

```plaintext
d = dict()

print(d)
```

Output:

```plaintext
{}
```

### Creating Dictionary using `fromkeys()`

Syntax:

```plaintext
dict.fromkeys(iterable, value)
```

Creates dictionary with:

*   keys from iterable
    
*   same value for all keys  
    

Example 1

```python
items = ['pen', 'pencil', 'eraser']

d = dict.fromkeys(items, 0)

print(d)
```

Output:

```plaintext
{'pen': 0, 'pencil': 0, 'eraser': 0}
```

Example 2

Without second argument:

```python
d = dict.fromkeys(items)

print(d)
```

Output:

```plaintext
{'pen': None, 'pencil': None, 'eraser': None}
```

Default value becomes `None`.

Example 3

```python
d = dict.fromkeys(range(5))

print(d)
```

Output:

```plaintext
{0: None, 1: None, 2: None, 3: None, 4: None}
```

* * *

## Combining Dictionaries

**1\. Using** `update()`

```python
d1 = {'apple': 10, 'banana': 20}
d2 = {'banana': 25, 'mango': 30}

d1.update(d2)

print(d1)
```

Output:

```plaintext
{'apple': 10, 'banana': 25, 'mango': 30}
```

*   New keys added
    
*   Existing keys overwritten
    

**2\.** `update()` **with list**

```python
L = [['grapes', 40], ['orange', 50]]

d1.update(L)

print(d1)
```

Output:

```plaintext
{'apple': 10, 'banana': 25, 'mango': 30,
 'grapes': 40, 'orange': 50}
```

**3\.** `update()` **with keyword arguments**

```python
d1.update(lemon=15, papaya=60)

print(d1)
```

Output:

```plaintext
{'apple': 10, 'banana': 25, 'mango': 30,
 'grapes': 40, 'orange': 50,
 'lemon': 15, 'papaya': 60}
```

**Using** `|` **operator (Python 3.9+)**

```python
d1 = {'x': 1, 'y': 2}
d2 = {'y': 100, 'z': 3}

d3 = d1 | d2

print(d3)
```

Output:

```plaintext
{'x': 1, 'y': 100, 'z': 3}
```

Returns a NEW dictionary.

**Using** `|=`

```plaintext
d1 |= d2

print(d1)
```

Output:

```plaintext
{'x': 1, 'y': 100, 'z': 3}
```

Modifies original dictionary.

* * *

## Nested Dictionaries

A dictionary inside another dictionary is called a nested dictionary.

Example 1

```python
student = {
    'name': 'John',
    'age': 21,
    'marks': {
        'Maths': 89,
        'Physics': 78,
        'Chemistry': 91
    }
}
```

## Accessing values

### Whole inner dictionary

```plaintext
print(student['marks'])
```

Output:

```plaintext
{'Maths': 89, 'Physics': 78, 'Chemistry': 91}
```

### Maths marks

```plaintext
print(student['marks']['Maths'])
```

Output:

```plaintext
89
```

### Physics marks

```python
print(student['marks']['Physics'])
```

Output:

```plaintext
78
```

**Example** of Multiple Students

```python
students = {
    101: {
        'name': 'John',
        'city': 'Paris'
    },

    102: {
        'name': 'Dev',
        'city': 'London'
    }
}
```

### Accessing student data

**Entire record**

```python
print(students[101])
```

Output:

```plaintext
{'name': 'John', 'city': 'Paris'}
```

### Only name

```python
print(students[102]['name'])
```

Output:

```plaintext
Dev
```

* * *

## Aliasing and Shallow vs Deep Copy

Dictionaries are mutable like lists.

So:

*   assigning one dictionary to another variable creates aliasing
    
*   changing one dictionary may affect the other
    

### Aliasing in Dictionaries

Example

```python
shop1_prices = {
    'apple': 200,
    'mango': 250,
    'banana': 100
}

shop2_prices = shop1_prices
```

This does NOT create a new dictionary.

Both variables point to the SAME object.

### Changing one dictionary

```python
shop2_prices['apple'] = 150
```

Now check both:

```python
print(shop1_prices)
print(shop2_prices)
```

Output:

```plaintext
{'apple': 150, 'mango': 250, 'banana': 100}
{'apple': 150, 'mango': 250, 'banana': 100}
```

Both changed because of aliasing.

### Why?

Because:

```python
shop2_prices = shop1_prices
```

only copies the reference, not the dictionary itself.

**Checking IDs**

```python
print(id(shop1_prices))
print(id(shop2_prices))
```

Output:

```plaintext
same id number
same id number
```

Both refer to the same object.

### Creating Independent Copy using `copy()`

Use:

```plaintext
dict.copy()
```

Example

```python
shop1_prices = {
    'apple': 200,
    'banana': 100
}

shop2_prices = shop1_prices.copy()
```

Now modify shop2:

```python
shop2_prices['apple'] = 150
```

Check both:

```python
print(shop1_prices)
print(shop2_prices)
```

Output:

```plaintext
{'apple': 200, 'banana': 100}
{'apple': 150, 'banana': 100}
```

Now changes are independent.

### Another Way using `dict()`

```plaintext
shop2_prices = dict(shop1_prices)
```

Also creates a copy.

### Shallow Copy Problem

`copy()` creates only a shallow copy.

This becomes a problem in nested dictionaries.

### Nested Dictionary Example

```python
office1_salary = {
    'manager': 6000,

    'programmer': {
        'Python': 5000,
        'Java': 4000
    }
}
```

Now create copy:

```python
office2_salary = office1_salary.copy()
```

### Modify Inner Dictionary

```python
office1_salary['programmer']['Python'] += 500
```

Now print both:

```plaintext
print(office1_salary)
print(office2_salary)
```

Output:

```python
{
 'manager': 6000,
 'programmer': {'Python': 5500, 'Java': 4000}
}

{
 'manager': 6000,
 'programmer': {'Python': 5500, 'Java': 4000}
}
```

Both changed again.

**Why Did This Happen?**

Because `copy()` only copies the outer dictionary.

Inner nested objects are still shared.

This is called a shallow copy.

* * *

## Shallow Copy

*   outer object is copied
    
*   inner nested objects are shared  
    

**Visual Idea**

```plaintext
Outer dictionary  -> copied
Inner dictionary  -> shared
```

### Checking IDs of Inner Dictionaries

```python
print(id(office1_salary['programmer']))
print(id(office2_salary['programmer']))
```

Output:

```plaintext
same id
same id
```

Same inner dictionary is shared.

## Deep Copy

To fully copy nested structures, use deep copy.

### Using `deepcopy()`

```python
from copy import deepcopy
```

Example

```python
office2_salary = deepcopy(office1_salary)
```

Now modify:

```python
office1_salary['programmer']['Python'] += 500
```

Check both dictionaries.

Now only office1 changes.

### Checking IDs

```python
print(id(office1_salary['programmer']))
print(id(office2_salary['programmer']))
```

Output:

```plaintext
different ids
different ids
```

Now inner dictionaries are different objects.

### Difference Between Shallow and Deep Copy

| Feature | Shallow Copy | Deep Copy |
| --- | --- | --- |
| Outer object copied? | Yes | Yes |
| Inner nested objects copied? | No | Yes |
| Nested objects shared? | Yes | No |
| Safe for nested structures? | No | Yes |

### Important Functions

| Function | Purpose |
| --- | --- |
| `d.copy()` | shallow copy |
| `dict(d)` | shallow copy |
| `deepcopy(d)` | deep copy |

### Important Point

### Aliasing

```python
d2 = d1
```

*   no new dictionary
    
*   both names point to same object
    

## Shallow Copy

```python
d2 = d1.copy()
```

*   outer dictionary copied
    
*   nested objects shared
    

## Deep Copy

```python
from copy import deepcopy

d2 = deepcopy(d1)
```

*     
    everything copied independently  
    

### Quick Summary

| Operation | Result |
| --- | --- |
| `d2 = d1` | aliasing |
| `d1.copy()` | shallow copy |
| `dict(d1)` | shallow copy |
| `deepcopy(d1)` | deep copy |

* * *

# Sets, and Frozensets:

A **set** in Python is a collection used to store **unique values**.

**Why do we need sets?**

Lists and tuples:

*   allow duplicate values
    
*   take more time to search when large
    

Example with a list:

```python
numbers = [1, 2, 3, 2, 1, 4]
```

Duplicates exist here (`1`, `2`).

If you want:

*   fast searching
    
*   only unique values
    

then use a **set**.

**Definition of a Set**

A set is:

*   **unordered** → elements have no fixed position
    
*   **mutable** → you can add/remove items
    
*   stores only **immutable values**
    
*   stores only **unique values**  
    

### Creating Sets: Using curly braces `{}`

```python
big_cities = {'London', 'Paris', 'Tokyo'}
primes = {2, 3, 5, 7}
```

**No Duplicate Values**

```plaintext
s = {1, 2, 2, 3, 1}
print(s)
```

Output:

```plaintext
{1, 2, 3}
```

Duplicates are automatically removed.

### Sets are Unordered

```plaintext
s = {'a', 'b', 'c'}
print(s)
```

The output order may change.

You cannot assume:

*   first element
    
*   second element
    
*   last element
    

### Sets Do Not Support Indexing

❌ Invalid:

```plaintext
s[0]
```

Because sets have no order.

### Membership Testing

The most common use of sets is checking whether an item exists.

```python
cities = {'Delhi', 'Paris', 'Tokyo'}

print('Paris' in cities)
print('London' not in cities)
```

Output:

```plaintext
True
True
```

Searching in sets is very fast.

### Empty Set

**Correct way**

```plaintext
s = set()
```

**Wrong way**

```plaintext
s = {}
```

This creates an empty dictionary, not a set.

* * *

### Creating Sets from Other Data Types

### From a String

```python
print(set('HELLO'))
```

Output:

```plaintext
{'H', 'E', 'L', 'O'}
```

Duplicate `L` removed.

### From a List

```python
L = [1, 2, 2, 3, 1]
print(set(L))
```

Output:

```plaintext
{1, 2, 3}
```

**From a Tuple**

```python
t = (10, 20, 10, 30)
print(set(t))
```

Output:

```plaintext
{10, 20, 30}
```

### Sets from Dictionaries

```python
d = {1:'a', 2:'b', 3:'a'}

print(set(d))
```

Output:

```plaintext
{1, 2, 3}
```

Only keys are taken.

To get values:

```python
print(set(d.values()))
```

Output:

```plaintext
{'a', 'b'}
```

* * *

### Creating Sets Using `range()`

```plaintext
odds = set(range(1, 10, 2))
print(odds)
```

Output:

```plaintext
{1, 3, 5, 7, 9}
```

### Using Sets to Remove Duplicates

```python
L = [1, 2, 2, 3, 1, 4]

L = list(set(L))

print(L)
```

Output:

```plaintext
[1, 2, 3, 4]
```

Note:

*   duplicates are removed
    
*   original order may be lost  
    

### Comparing Lists Ignoring Order

```python
L1 = [1, 2, 3, 4]
L2 = [4, 3, 2, 1]

print(set(L1) == set(L2))
```

Output:

```plaintext
True
```

Because both contain the same values.

**When Should You Use a Set?**

Use a set when:

*   duplicates are not needed
    
*   order does not matter
    
*   fast searching is important  
    

Examples:

*   unique usernames
    
*   visited webpages
    
*   unique words in a document
    
*   checking membership quickly
    

### Quick Summary

| Feature | Set |
| --- | --- |
| Ordered | ❌ No |
| Mutable | ✅ Yes |
| Duplicate values allowed | ❌ No |
| Indexing allowed | ❌ No |
| Fast searching | ✅ Yes |

* * *

## Creating Sets in Python — Easy Notes

### 1\. Creating an Empty Set

To create an empty set, use:

```plaintext
s = set()
```

**Why not** `{}` **?**

```plaintext
s = {}
```

This creates an **empty dictionary**, not a set.

Because:

*   `{}` syntax was already used for dictionaries before sets were introduced in Python.
    

So:

| Syntax | Creates |
| --- | --- |
| `set()` | Empty set |
| `{}` | Empty dictionary |

### 2\. Creating Sets from Other Data Types

The `set()` function can convert:

*   strings
    
*   lists
    
*   tuples
    
*   dictionaries  
    into sets.
    

The biggest advantage:  
✅ duplicate values are automatically removed.

### From a String

```plaintext
print(set('HELLO'))
```

Output:

```plaintext
{'H', 'E', 'L', 'O'}
```

Explanation:

*   `'L'` appeared twice
    
*   set keeps only one `'L'`  
    

### From a List

```plaintext
L = [1, 2, 3, 1, 2, 3, 4, 5]

print(set(L))
```

Output:

```plaintext
{1, 2, 3, 4, 5}
```

Duplicates removed automatically.

**From a Tuple**

```plaintext
t = (20, 30, 40, 30, 20)

print(set(t))
```

Output:

```plaintext
{40, 20, 30}
```

Again:

*   duplicate values removed
    
*   order may change  
    

### Important Point About Sets

Sets are **unordered**.

So:

*   original order is not preserved
    
*   output order can change  
    

Example:

```plaintext
{40, 20, 30}
```

could also appear as:

```plaintext
{20, 30, 40}
```

Both are correct.

### 3\. Creating a Set from a Dictionary

Example

```plaintext
d = {1:'a', 2:'b', 3:'c', 4:'a', 5:'c'}

print(set(d))
```

Output:

```plaintext
{1, 2, 3, 4, 5}
```

Explanation:

*   converting a dictionary to a set takes only the **keys**
    
*   values are ignored  
    

### Getting Unique Values from Dictionary

```plaintext
print(set(d.values()))
```

Output:

```plaintext
{'a', 'b', 'c'}
```

Duplicate values (`'a'`, `'c'`) are removed.

### 4\. Creating Sets Using `range()`

```plaintext
odds = set(range(1, 20, 2))

print(odds)
```

Output:

```plaintext
{1, 3, 5, 7, 9, 11, 13, 15, 17, 19}
```

Explanation:

*   `range(1, 20, 2)` generates odd numbers
    
*   `set()` converts them into a set
    

### 5\. Removing Duplicates from a List

Very common use of sets.

```plaintext
L = [1, 2, 2, 3, 1, 4]

L = list(set(L))

print(L)
```

Possible Output:

```plaintext
[1, 2, 3, 4]
```

Explanation:

1.  `set(L)` removes duplicates
    
2.  `list()` converts it back to a list
    

⚠ Order may change.

### 6\. Comparing Lists Ignoring Order

**Normal List Comparison**

```plaintext
L1 = [1, 2, 3, 4]
L2 = [3, 2, 4, 1]

print(L1 == L2)
```

Output:

```plaintext
False
```

Because list comparison checks:

*   values
    
*   AND positions
    

### Using Sets for Order-Neutral Comparison

```plaintext
print(set(L1) == set(L2))
```

Output:

```plaintext
True
```

Because sets only check:

*   unique elements
    
*   not order  
    

### Quick Summary

| Operation | Example |
| --- | --- |
| Empty set | `set()` |
| Empty dictionary | `{}` |
| Remove duplicates | `set(L)` |
| Set from string | `set('HELLO')` |
| Set from list | `set([1,2,2])` |
| Set from tuple | `set((1,2,2))` |
| Dictionary keys as set | `set(d)` |
| Dictionary values as set | `set(d.values())` |
| Compare ignoring order | `set(L1) == set(L2)` |

* * *

## Adding and Removing Elements in Sets

Sets are mutable, so we can:

*   add elements
    
*   remove elements
    
*   clear all elements
    

**Set Methods**

| Method | Purpose |
| --- | --- |
| `s.add(x)` | Adds item `x` |
| `s.pop()` | Removes a random item |
| `s.remove(x)` | Removes `x`, gives error if not found |
| `s.discard(x)` | Removes `x`, no error if not found |
| `s.clear()` | Removes all elements |

### 1\. `add()` Method

Used to add an element to a set.

```plaintext
cities = {'Delhi', 'Mumbai'}

cities.add('Paris')

print(cities)
```

Possible Output:

```plaintext
{'Delhi', 'Mumbai', 'Paris'}
```

***Important Points:***

**Duplicate values are ignored**

```plaintext
s = {1, 2, 3}

s.add(2)

print(s)
```

Output:

```plaintext
{1, 2, 3}
```

No duplicate added.

## Only immutable values can be added

✅ Allowed:

```plaintext
s.add(10)
s.add('Hello')
s.add((1, 2))
```

❌ Not allowed:

```plaintext
s.add([1, 2])
```

Error:

```plaintext
TypeError
```

Because lists are mutable.

### 2\. `remove()` Method

Removes a specified element.

```plaintext
cities = {'Delhi', 'Mumbai', 'Paris'}

cities.remove('Paris')

print(cities)
```

Output:

```plaintext
{'Delhi', 'Mumbai'}
```

* * *

## If element does not exist

```plaintext
cities.remove('Tokyo')
```

Output:

```plaintext
KeyError
```

### 3\. `discard()` Method

Also removes an element.

Difference:

*   `discard()` does NOT give error if item is missing.
    

```plaintext
cities.discard('Tokyo')
```

No error occurs.

* * *

### Difference Between `remove()` and `discard()`

| Method | Missing Element |
| --- | --- |
| `remove()` | Gives `KeyError` |
| `discard()` | No error |

### 4\. `pop()` Method

Removes and returns a random element.

```python
cities = {'Delhi', 'Mumbai', 'Paris'}

x = cities.pop()

print(x)
print(cities)
```

Possible Output:

```python
Paris
{'Delhi', 'Mumbai'}
```

⚠ Since sets are unordered, the removed item is arbitrary.

### 5\. `clear()` Method

Removes all elements.

```python
s = {1, 2, 3}

s.clear()

print(s)
```

Output:

```python
set()
```

### Built-in Functions on Sets

These functions work on sets too:

```python
len()
sum()
max()
min()
sorted()
all()
any()
```

Example:

```python
s = {10, 20, 30}

print(len(s))
print(sum(s))
print(max(s))
```

Output:

```plaintext
3
60
30
```

* * *

## Comparing Sets

### 1\. `isdisjoint()`

Checks whether two sets have common elements.

```python
s1 = {1, 2, 3}
s2 = {4, 5, 6}
s3 = {3, 4, 5}

print(s1.isdisjoint(s2))
print(s1.isdisjoint(s3))
```

Output:

```plaintext
True
False
```

### 2\. Equality of Sets

Two sets are equal if they contain the same elements.

```python
s1 = {1, 2, 3}
s2 = {3, 2, 1}

print(s1 == s2)
```

Output:

```plaintext
True
```

Order does not matter.

### 3\. Subset and Superset

## Subset

If every element of `s1` exists in `s2`, then:

```plaintext
s1 <= s2
```

or

```python
s1.issubset(s2)
```

Example:

```python
s1 = {1, 2}
s2 = {1, 2, 3, 4}

print(s1 <= s2)
```

Output:

```plaintext
True
```

### Superset

If `s1` contains all elements of `s2`, then:

```python
s1 >= s2
```

or

```plaintext
s1.issuperset(s2)
```

Example:

```python
print(s2 >= s1)
```

Output:

```plaintext
True
```

### Proper Subset `<`

Means:

*   subset
    
*   but not equal  
    

```plaintext
s1 = {1, 2}
s2 = {1, 2, 3}

print(s1 < s2)
```

Output:

```plaintext
True
```

### Proper Superset `>`

```plaintext
print(s2 > s1)
```

Output:

```plaintext
True
```

* * *

## Set Operations

These operations come from mathematics.

| Operation | Operator |
| --- | --- |
| Union | \` |
| Intersection | `&` |
| Difference | `-` |
| Symmetric Difference | `^` |

### 1\. Union `|`

Combines all unique elements.

```plaintext
A = {1, 2, 3}
B = {3, 4, 5}

print(A | B)
```

Output:

```plaintext
{1, 2, 3, 4, 5}
```

### 2\. Intersection `&`

Common elements only.

```plaintext
print(A & B)
```

Output:

```plaintext
{3}
```

### 3\. Difference `-`

Elements in first set but not in second.

```plaintext
print(A - B)
```

Output:

```plaintext
{1, 2}
```

### 4\. Symmetric Difference `^`

Elements in either set but not both.

```plaintext
print(A ^ B)
```

Output:

```python
{1, 2, 4, 5}
```

### Non-Mutating Operations

These operations:

*   create a new set
    
*   do not change original sets  
    

```python
A = {1, 2, 3}
B = {3, 4, 5}

C = A | B
```

`A` and `B` remain unchanged.

### Mutating Versions

| Method | Operator |
| --- | --- |
| `update()` | \` |
| `intersection_update()` | `&=` |
| `difference_update()` | `-=` |
| `symmetric_difference_update()` | `^=` |

These modify the original set.

Example:

```python
A = {1, 2, 3}
B = {3, 4, 5}

A &= B

print(A)
```

Output:

```plaintext
{3}
```

* * *

### Set Operations on Lists and Strings

```python
x = [1, 2, 3]
y = [3, 4, 5]

print(set(x) | set(y))
```

Output:

```python
{1, 2, 3, 4, 5}
```

### Set Operations on Dictionary Views

```python
d1 = {'a': 10, 'b': 20}
d2 = {'a': 10, 'c': 30}

print(d1.keys() - d2.keys())
```

Output:

```plaintext
{'b'}
```

* * *

## Frozenset

A `frozenset` is an immutable set.

Once created:

*   cannot add
    
*   cannot remove
    
*   cannot update
    

* * *

### Creating a Frozenset

```python
s = frozenset([1, 2, 3])

print(s)
```

Output:

```python
frozenset({1, 2, 3})
```

**Why Use Frozenset?**

Because it is immutable:

*   it can be used as dictionary keys
    
*   it can be stored inside another set
    

Normal sets cannot.

### Methods Not Allowed

❌ Invalid:

```python
s.add(4)
s.remove(2)
```

Because frozensets cannot change.

### Quick Summary

| Feature | set | frozenset |
| --- | --- | --- |
| Mutable | ✅ Yes | ❌ No |
| Add/remove allowed | ✅ Yes | ❌ No |
| Can be dictionary key | ❌ No | ✅ Yes |
| Supports set operations | ✅ Yes | ✅ Yes |
