### Ice Breaker
Scavenger Hunt! Each group will have roughly 5 minutes to find as many items in the scavenger hunt list as possible. Whichever group collectively has the most items from [this list](https://docs.google.com/document/d/1oAJ6hsGooXkll3FOWDwhFYTrhtdDUCvMbTi16tzHtRY/edit) wins!
### Objective
By the end of this class, you will:
- Learn how to use a new feature of Python:list comprehensions, set comprehensions, and dictionary comprehensions.
- Understand the purpose of using comprehensions.
Follow along using this [Colab](https://colab.research.google.com/drive/18M8pfCoSbmfiiWFW9LMgEQCsMDbyZA75?usp=sharing) link.
**Discussion:**
Suppose we have a list of integers and our goal is to find the even numbers and store them into a new one. We can use the list below as an example:
```python
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
```
In your groups, take a few minutes to discuss and come up with a solution to this problem.
<details>
<summary><b>Hint!</b></summary>
Hint: We can use our understanding of loops, conditionals, boolean comparison operators, and arithmetic.
</details>
<br>
<details>
<summary><b>Think, then click!</b></summary>
```python
# Given list of integers
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Create an empty list to store the even numbers
even_numbers = []
# Using a for loop and if statement to find even numbers
for num in numbers:
if num % 2 == 0:
#checks that the remainder when num is divided by 2 is 0
even_numbers.append(num)
print(even_numbers) # Output: [2, 4, 6, 8, 10]
```
</details>
<br>
Using loops and if statements solves the problem, but is there a more concise way to accomplish this with fewer lines of code?
The answer is yes! We can use list comprehensions!
## List Comprehensions:
So, what are list comprehensions?
List comprehensions are a concise and effective way to create lists in Python. They allow you to generate a new list by applying an expression to each item in an existing collection, such as a list, tuple, or string, transferring the result of the expression to the new list. The general syntax for list comprehensions are as follows:
```python
new_list = [expression for item in iterable if condition]
```
Here's a step-by-step breakdown:
1. `expression`: The operation or calculation to be performed on each item.
2. `item`: The variable that takes each value from the iterable (Python calls collections that it can iterate through "iterables").
3. `iterable`: The existing list, tuple, string, or any other structure or data you want to loop through.
4. `if condition` (optional): A condition that filters the items before they are included in the new list.
Let's try to solve our problem using list comprehensions.
**Problem:** Suppose we have a list of integers and we want to find all the even numbers and store them in a new one.
```python
# Given list of integers
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Using list comprehension to find even numbers and put them in a new list
even_numbers = [num for num in numbers if num % 2 == 0]
print(even_numbers)
# Output: [2, 4, 6, 8, 10]
```
As you can see all we need is one line of code. Magic, huh?
List comprehensions require shorter syntax and are useful for filtering and transforming data effectively.
So far, we've used list comprehensions to filter data, but we can also use them to manipulate data.
For instance, let's say we have a list of words, and we want to create a new list where each word is capitalized.
Here's the list of strings we are working with:
```python =
words = ["apple", "banana", "orange", "grape", "kiwi"]
```
**Discusion:** Now, how can we achieve this using list comprehensions?
To capitalize words in Python, we can use the built-in function `capitalize()`.
As a reminder, the syntax for creating list comprehensions is
```python
new_list = [expression for item in iterable if condition]
```
Here are a few things to think about:
1. What do we want our expression to be? In other words, what operation do we need to perform on each item in the list?
2. In this case, what is our iterable?
3. Recall that the if-condition part is optional. Do we need it in this case?
<details>
<summary><b>Think, then click!</b></summary>
```python =
words = ["apple", "banana", "orange", "grape", "kiwi"]
capitalized_words = [word.capitalize() for word in words]
print(capitalized_words)
```
</details>
<br>
### Practice Time!
Now it is your turn to solve a few more problems using list comprehensions.
You will be divided into 3 groups, and each group will solve one problem using list comprehensions.
### Problem 1:
Imagine you work for an online retail store, and you are tasked with calculating discounted prices for a list of products during a seasonal sale. The company is offering a 20% discount on all products. You have a list of original prices for various items, and you need to generate a new list with the discounted prices using list comprehension.
Original Prices:
```python =
original_prices = [100, 50, 75, 120, 30, 200]
```
<details>
<summary><b>Think, then click!</b></summary>
```python =
# Applying a 20% discount using list comprehension
discounted_prices = [price * 0.8 for price in original_prices]
print(discounted_prices)
```
</details>
<br>
### Problem 2:
Your task is to generate a new list that contains the squares of only the even numbers from this list:
```python =
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
```
<details>
<summary><b>Think, then click!</b></summary>
```python =
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Generating a list of squares of even numbers
squares_of_even = [num ** 2 for num in numbers if num % 2 == 0]
print(squares_of_even) # Output: [4, 16, 36, 64, 100]
```
</details>
<br>
### Problem 3:
You want to filter out "apple" from the list.
Here is the list:
```python=
fruits = ["apple", "banana", "cherry", "kiwi", "mango"]
```
<details>
<summary><b>Think, then click!</b></summary>
```python =
fruits = ["apple", "banana", "cherry", "kiwi", "mango"]
newlist = [x for x in fruits if x != "apple"]
print(newlist)
```
</details>
<br>
## Set Comprehensions:
Set comprehensions are similar to list comprehensions but create sets instead. The only difference in syntax is that we use curly braces `{}` to represent a set.
```python
new_set = {expression for item in iterable if condition}
```
### Practice Time!
Let's do some practice problems!
Suppose we have have a string of letters that are mixed between uppercase and lowercase, and we want to contain all of the unique lowercase letters in a seperate structure. How can we do this?
<details>
<summary><b>Think, then click!</b></summary>
Since we know that sets only contain unique data, we can simply use set comprehension to filter out uppercase letters! We need to somehow iterate through all the characters and check that our new set will only contain those that are lowercase.
</details>
<br>
Take a minute or two to try writing the code for yourself using the string given below. We'll share results afterward!
```python
"iLoVEmooSaiccomPUTerSCIence"
```
<details>
<summary><b>Solution Below!</b></summary>
```python
#Checks if each character in string is equal to the lowercase version of itself, and therefore, lowercase
lower_set = {char for char in mixed_string if char == char.lower()}
print(lower_set) #Output: {'a', 'o', 'n', 'e', 'c', 'r', 'i', 'm'}
```
Note that sets store unique elements, but unlike lists, they don't preserve the order. The 'i' isn't the first element in the set being printed out.
</details>
<br>
Now, uniqueness might cause problems, right? Let's explore one more challenge to understand the consequences of using a set rather than a list.
Now, using the list of words given below, how can we create a set containing the lengths of the words? Try this in your groups, then we'll share!
```python
word_list = ["fiber","enthralled","boat","rapture","amazingly","shuttle"]
```
<details>
<summary><b>Solution Below!</b></summary>
```python
len_set = {len(words) for words in word_list}
print(len_set) #Output:{4, 5, 7, 9, 10}
```
</details>
There are two problems with using sets for this specific problem:
1. **Duplicate Elimination:** The set comprehension automatically removes duplicate lengths from the `word_list`, which we may or may not find useful. In this case, the words "rapture" and "shuttle" both have a length of 7, but the set only contains one instance of that length. If we want to keep both instances, this definitely creates a problem!
2. **Loss of Order:** Sets, by nature, do not preserve the order of elements. In the original `word_list`, the words were ordered as ["fiber", "enthralled", "boat", "rapture", "amazingly", "shuttle"]. However, when we create the set `len_set`, we lose the original order. The set only contains unique lengths but in an arbitrary order, in this case, {4, 5, 7, 9, 10}.
If we were to use list comprehensions for this problem instead:
```python
# List comprehension
len_list = [len(word) for word in word_list]
print(len_list) # Output: [5, 10, 4, 7, 9, 7]
```
We can easily see the length of each word.
So, what does this mean? As a computer scientist, the data structure that you find most relevant or useful depends on your needs! Being flexible and understanding what best suits your code can go a long way.
There's an additional (slightly more challenging) practice problem in the Colab. We can work on it if there's time. You can also try it on your own or with us in office hours!
## Dictionary Comprehensions:
Dictionary comprehensions allow us to create dictionaries in a concise way. The syntax is as follows:
```python
new_dict = {key_expression: value_expression for item in iterable if condition}
```
Here's a breakdown of the two new terms:
1. `key_expression`: Similar to list and set comprehensions, we can also think of this as an operation or calculation to be performed except specifically for the key.
2. `value_expression`: The same concept as above, except for the value instead of the key.
Let's see dictionary comprehensions in action. Using the following list of names, how can we create a dictionary containing each name and its length?
```python
names = ["Muhiim", "Kam", "Melyssa", "Dior", "Nicole", "Tim"]
```
<details>
<summary><b>Hint</b></summary>
Think of this in terms of each aspect of dictionary comprehensions (i.e. the key expression, value expression, etc.) What specific iterable are we using? How? Do we need to perform any operations on the item? If so, on the key or value (or both)?
</details>
<br>
<details>
<summary><b>Think, then click!</b></summary>
Using dictionary comprehensions (suprise!), we can iterate through the list, set each name (item) from our list as a key, and use the length of the name as the corresponding value (which serves as the value expression).
</details>
<br>
Now, we can model this with the code.
```python
name_lengths = {name: len(name) for name in names}
print(name_lengths) #Output: {'Muhiim': 6, 'Kam': 3, 'Melyssa': 7, 'Dior': 4, 'Nicole': 6, 'Tim': 3}
```
Note that we performed the `len()` value expression on the `name` item, and used each version of the `name` item as a different key.
### Practice Time!
Meet with your groups again and work on your practice problem. Each group will get a chance to share their solution, then we'll look at the code created by the TAs.
### Problem 1:
Given a list of temperatures in Celsius, work with your group to create a dictionary where the keys are the Celsius temperatures and the values are the temperatures converted to Fahrenheit.
```python =
celsius_temps = [10, 30, 0, 50, 40, 20]
```
Here is the formula for converting Celsius to Fahrenheit: **°F = (°C × 9/5) + 32**
<details>
<summary><b>Think, then click!</b></summary>
```python =
conversion_dict = {cels : cels * 9/5 + 32 for cels in celsius_temps}
print(conversion_dict) #Ouput : {10: 50.0, 30: 86.0, 0: 32.0, 50: 122.0, 40: 104.0, 20: 68.0}
```
</details>
<br>
### Problem 2:
Create a dictionary where the keys are the odd numbers 1 through 10, and the values are their cubes.
<details>
<summary><b>Think, then click!</b></summary>
```python =
num_range = range(1,11)
cubes_dict = {num: num ** 3 for num in num_range if num % 2 !=0}
print(cubes_dict) #Output: {
1: 1,
3: 27,
5: 125,
7: 343,
9: 729
}
```
</details>
<br>
### Problem 3:
Using the list of words, create a dictionary where the key is the first letter and the value is the word. Only do this for words that are more than 3 letters.
```python=
word_list = ["spectacular", "radish", "ate", "ok", "argue", "sit"]
```
<details>
<summary><b>Think, then click!</b></summary>
```python=
filter_words = {word[0] : word for word in word_list if len(word)>3}
print(filter_words) #Output:{'s': 'spectacular', 'r': 'radish', 'a': 'argue'}
```
</details>
<br>
**Bonus — thinking further**
Using the same list from problem 3, let's consider what happens if there is more than one word with the same first letter that also meets our critera for the new dictionary. Do you think that each word still gets included?
Using the updated list, run your comprehension again and take note of the result.
```python=
word_list_2 = ["spectacular","amazing", "radish", "ate", "ok", "argue", "sit", "smart"]
```
<details>
<summary><b>Think, then click!</b></summary>
```python=
filter_words = {word[0] : word for word in word_list_2 if len(word)>3}
print(filter_words) #Output:{'s': 'smart', 'r': 'radish', 'a': 'argue'}
```
</details>
<br>
We can see that there is an issue! The dictionary is not correctly capturing all the words with keys 'a' and 's' since dictionary keys must be unique, and any duplicate keys in the comprehension will overwrite previous ones. From our hashing lessons, we know that this is the result of the code not accounting for collisions. The job of dictionaries is to map a key to a given value. If we want multiple values for a key, that's *OUR* job to make it happen.
**Discussion:** How can we solve this issue? You'll want to think back to how we can use another data structure as the value so that we can store multiple words within it that begin with the same letter. If this sounds confusing, come to hours and work with us!
There's also another bonus challenge for dictionary comprehensions that you can try in the Colab.
Comprehensions can make your code more concise and readable, and they are often preferred over traditional loops when you need to create new data structures based on existing ones. So, which do you prefer: loops or comprehensions?