---
## Learning Objectives
*After this lesson, you will be able to:*
- Perform common actions with sets.
- Perform common actions with tuples.
- Know when to use different data structures.
---
## Discussion: Lists
Here are some lists:
```python
unique_colors = ["red", "yellow", "red", "green", "red", "yellow"]
subscribed_emails = ["mary@gmail.com", "opal@gmail.com", "mary@gmail.com", "sayed@gmail.com"]
```
What could be a problem here?
---
## Introducing Sets
Lists:
```python
unique_colors_list = ["red", "yellow", "red", "green", "red", "yellow"]
subscribed_emails_list = ["mary@gmail.com", "opal@gmail.com", "mary@gmail.com", "sayed@gmail.com"]
```
Sets: an unordered collection of unique elements. These are powerful because set theory operations can be applied to them in O(1) time.
```python
unique_colors_set = {"green", "yellow", "red"}
subscribed_emails_set = {"mary@gmail.com", "opal@gmail.com", "sayed@gmail.com"}
```
- Notice the `{}` versus the `[]`.
---
## How Can We Make a Set?
If we explicitly cast a set from a list, Python removes duplicates automatically.
```python
my_set = set(a_list_to_convert)
# In action:
unique_colors_list = ["red", "yellow", "red", "green", "red", "yellow"]
unique_colors_set = set(unique_colors_list)
# => {"green", "yellow", "red"}
# In action:
unique_colors_set_2 = set(["red", "yellow", "red", "green", "red", "yellow"])
# => {"green", "yellow", "red"}
```
We can make a set directly using curly braces:
```python
colors = {"red", "orange", "yellow", "green", "blue", "indigo", "violet"}
```
---
## Important Note: Sets
Lists are always in the same order:
- `my_list = ["green", "yellow", "red"]` is always going to be`["green", "yellow", "red"]`
- `my_list[0]` is always `"green"`; `my_list[1]` is always `"yellow"`; `my_list[2]` is always `"red"`.
Sets are not like this! Similar to dictionaries, they're not ordered.
- `my_set = {"green", "yellow", "red"}` could later be `{"red", "yellow", "green"}`!
- Note that python defaults to displaying an ascending sort of a set. Although this is displayed to the user when the variable is called via the interpreter, _the order cannot be relied upon_.
We **cannot** do: `my_set[0]` - sets are unordered, and a `TypeError`
exception will be thrown.
---
## We Do: Creating a Set from a List
Let's pull up a new `set_practice.py` file and make some sets!
- Make a list `clothing_list` containing the main color of your classmates' clothing.
- Using `clothing_list`, make a set named `clothing_set`.
- Use a `for` loop to print out both `clothing_list` and `clothing_set`.
- Try to print an index!
---
## We Do: Adding to a Set
How do we add more to a set?
```python
# In a list:
clothing_list.append("red")
# In a set
clothing_set.add("red")
```
`add` vs `append` - this is because we can't guarantee it's going at the end!
Let's add a few colors to `clothing_list` and `clothing_set` and then print them.
- What happens if you add a duplicate?
---
## We Do: Removing from a List and a Set
Remember, lists are always the same order and sets are not!
- With the set `{"green", "yellow", "red"}`, `my_set[0]` could be `green`, `red`, or `yellow`.
So thus, we need to be careful about removal:
```python
# In a list:
clothing_list.pop() # Removes and returns the last item in the list.
clothing_list.pop(0) # Removes and returns a specific (here, the first) item in the list.
# In a set
clothing_set.pop() # Removes and returns an arbitrary element. This is probably not what you want to do in most cases.
clothing_set.pop(0) # Python throws a TypeError error (pop takes no arguments)
clothing_set.remove('red') # This removes an element from the set directly, and raises KeyError if the element is not present. This is the most common use.
clothing_set.discard('red') # This removes an element from the set directly, but does NOT throw a KeyError if the element is not present in the set.
```
---
## We Do: Set Intersection
- One thing that sets are _really_ good at is _relational algebra_ (set theory). This is a fancy word for a SQL join, or comparing elements between two sets.
- Sets are really good at this because they use the same [hashing trick](https://en.wikipedia.org/wiki/Hash_table) under the hood that dictionaries use and makes them so fast.
Let's start by making two sets:
```python
set1 = {1, 2, 3, 4, 5}
set2 = {4, 5, 6, 7, 8}
```
---
## We Do: Set Intersection
Now let's use the `.intersection()` set method to find out _what elements these two sets have in common_:
```python
set1.intersection(set2)
```
Yields the result:
```python
{4, 5}
```
- This makes sense, the two sets share the elements `4` and `5`.
- Note that this is commutative, meaning we could also write `set2.intersection(set1)` and receive the same result.
---
## We Do: Set Differences
- Instead of finding _elements in common_ between two sets, we can also find _their differences_.
- To do this, we use `.difference()`.
- Note that this method _is not commutative_, meaning _order matters_.
- The difference is what is contained in the _first_ set, _but not in the second set_.
```python
set1.difference(set2)
```
Yields the result:
```python
{1, 2, 3}
```
```python
set2.difference(set1)
```
Yields the result:
```python
{6, 7, 8}
```
---
## Quick Review: Sets vs. Lists
**Lists**:
- The original, normal object.
- Created with `[]`.
- `append()`, `insert(index)`, `pop()`, `pop(index)`.
- Duplicates and mutable.
**Sets**:
- Lists without duplicates.
- Created with `{}` or with `set(my_list)`.
- `add()` and `remove(element)`.
## Quick Review: Sets vs. Lists
```python
### Creation ###
# List
my_list = ["red", "yellow", "green", "red"]
# Sets
my_set = {"red", "yellow", "green"}
my_set2 = set(my_list)
my_set = set(a_list_to_convert)
### Appending a New Value ###
my_list.append("blue")
my_set.add("blue")
### Appending a Duplicate ###
my_list.append("blue")
# => my_list = ["red", "yellow", "green", "red", "blue", "blue"]
my_set.add("blue")
# => my_set = {"red", "yellow", "green", "blue"}
### Removing items: ###
my_list.pop(1)
my_set.remove("red")
```
---
## Discussion: Immutability Thoughts
A set is a type of list which doesn't allow duplicates.
What if, instead, we have a list we don't want to change?
```python
rainbow_colors = ["red", "orange", "yellow", "green", "blue", "indigo", "violet"]
```
We **don't** want:
```python
rainbow_colors[0] = "gray"
## Gray's not in the rainbow!
rainbow_colors.pop()
## We can't lose violet!
rainbow_colors.append("pink")
# Pink's not in the rainbow!
```
We want `rainbow_colors` to be **immutable** - the list _cannot_ be changed.
How we do that in Python?
---
## Introducing: Tuples
**Sets** have the following properties:
- No duplicates
- Mutable
**Tuples** are similar to a list with the following differences:
- Allows duplicates (same as a list)
- Once assigned, the tuple _cannot be changed_
```python
rainbow_colors_tuple = ("red", "orange", "yellow", "green", "blue", "indigo", "violet")
```
When should you use a tuple?
- When you need data protection through immutability (note: all Python variables are public).
- When you never want to change the list.
- Tuples are sometimes wrapped in a class namespace to simulate what would be done with a [const structure](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/how-to-define-constants) in a C-based language.
- This is a way of holding appconstants, or constants your program will use (like the API endpoint of a server, credentials, etc.)
---
## Tuple Syntax
- Created with parentheses `()`.
- Access values via indices (like regular lists, but *not* like sets).
```python
rainbow_colors_tuple = ("red", "orange", "yellow", "green", "blue", "indigo", "violet")
print(rainbow_colors[1])
# Prints "orange"
```
- Tuples can be printed with a `for` loop (just like a set or list!).
```python
rainbow_colors_tuple = ("red", "orange", "yellow", "green", "blue", "indigo", "violet")
for color in rainbow_colors_tuple:
print(color)
```
---
## We Do: Tuples
Let's declare a tuple named `seasons` and set it to have the values `fall`, `winter`, `spring`, and `summer`. We'll print the tuple and each value. Then we'll try to reassign them (we can't)!
---
## Quick Review: Sets, Tuples, Lists
**List**:
- The original, normal object: `["red", "red", "yellow", "green"]`.
- Has duplicates; mutable: `append()`, `insert(index)`, `pop()`, `pop(index)`
**Set**:
- List without duplicates: `{"red", "yellow", "green"}`.
- Mutable: `add()` and `remove(element)`
**Tuple**:
- Has duplicates, but immutable: You can't change it!
- `("red", "red", "yellow", "green")` will *always* be `("red", "red", "yellow", "green")`.
---
## Quick Review: Sets, Tuples, Lists
```python
### Creation ###
# List
my_list = ["red", "yellow", "green", "red"]
# Sets
my_set = {"red", "yellow", "green"}
my_set2 = set(my_list))
my_set = set(a_list_to_convert)
# Tuples
my_tuple = ("red", "yellow", "green")
### Appending a New Value ###
my_list.append("blue")
my_set.add("blue")
# Tuples -> You can't!
### Removing items: ###
my_list.pop(1)
my_set.remove("red")
# Tuples -> You can't!
```
---
## Introducing Types
Variables certainly can hold a lot!
- Sets, tuples, and lists are easily confused.
- `type()` tells us what a variable is: set, tuple, list, dictionary, integer, string - anything!
Try it:
---
## You Do: List Types Practice
Create a local file, `sets_tuples.py`. In it:
- Create a list (`[]`), set (`{}`), and tuple (`()`) of some of your favorite foods.
- Create a second set from the list.
Next, in every list type that you can:
- Add `"pizza"` anywhere; append `"eggs"` to the end.
- Remove `"pizza"`.
- Re-assign the element at index `1` to be `"popcorn"`.
- Remove the element at index `2` and re-insert it at index `0`.
- Print the element at index `0`.
Print your final lists using a loop, then print their types. Don't throw an error!
---
## Summary and Q&A
We've learned two new types of lists:
Sets:
- A mutable list without duplicates.
- Handy for storing emails, usernames, and other unique elements.
```python
email_set = {'my_email@gmail.com', 'second_email@yahoo.com', "third_email@hotmail.com"}
```
Tuples:
- An immutable list that allows duplicates.
- Handy for storing anything that won't change.
```python
rainbow_tuple = ("red", "orange", "yellow", "green", "blue", "indigo", "violet")
```
---
## Additional Reading
- [Repl.it that recaps Tuples](https://repl.it/@GAcoding/python-programming-tuple-practice?lite=true)
- [Python Count Occurrences of Letters, Words and Numbers in Strings and Lists-Video](https://www.youtube.com/watch?v=szIFFw_Xl_M)
- [Storing Multiple Values in Lists](https://swcarpentry.github.io/python-novice-inflammation/03-lists/)
- [Sets and Frozen Sets](https://www.python-course.eu/sets_frozensets.php)
- [Sets](https://www.learnpython.org/en/Sets)
- [Python Tuple](https://www.programiz.com/python-programming/tuple)
- [Tuples](http://openbookproject.net/thinkcs/python/english3e/tuples.html)
- [Strings, Lists, Tuples, and Dictionaries Video](https://www.youtube.com/watch?v=19EfbO5D_8s)
- [Python Data Structures: Lists, Tuples, Sets, and Dictionaries Video](https://www.youtube.com/watch?v=R-HLU9Fl5ug)