# Dictionaries + More Lists

Tips for navigating the slides:
• Press O or Escape for overview mode.
• Visit this link for a nice printable version
• Press the copy icon on the upper right of code blocks to copy the code

### Class outline:

• Dictionaries
• List diagrams
• Slicing
• Built-ins for iterables
• Recursive exercises

## Dictionaries

### Dictionaries

A `dict` is a mapping of key-value pairs

``````
states = {
"CA": "California",
"DE": "Delaware",
"NY": "New York",
"TX": "Texas",
"WY": "Wyoming"
}
``````

Dictionaries support similar operations as lists/strings:

``````
>>> len(states)
5
``````
``````
>>> "CA" in states
True
``````
``````
>>> "ZZ" in states
False
``````

### Dictionary access

``````
words = {
"más": "more",
"otro": "other",
"agua": "water"
}
``````

Ways to access a value by key:

``````
>>> words["otro"]
'other'
``````
``````
>>> first_word = "agua"
>>> words[first_word]
'water'
``````
``````
>>> words["pavo"]
KeyError: pavo
``````
``````
>>> words.get("pavo", "🤔")
'🤔'
``````

### Dictionary rules

• All keys in a dictionary are distinct (there can only be one value per key)
• A key cannot be a list or dictionary (or any other mutable type)
• The values can be any type, however!
``````
spiders = {
"smeringopus": {
"length": 7
},
"holocnemus pluchei": {
"name": "Marbled cellar spider",
"length": (5, 7)
}
}
``````

### Dictionary iteration

``````
insects = {"spiders": 8, "centipedes": 100, "bees": 6}
for name in insects:
print(insects[name])
``````

What will be the order of items?

``````
8 100 6
``````

Keys are iterated over in the order they are first added.

### Dictionary comprehensions

General syntax:

``````
{key: value for <name> in <iter exp>}
``````

Example:

``````
{x: x*x for x in range(3,6)}
``````

### Exercise: Prune

``````
def prune(d, keys):
"""Return a copy of D which only contains key/value pairs
whose keys are also in KEYS.
>>> prune({"a": 1, "b": 2, "c": 3, "d": 4}, ["a", "b", "c"])
{'a': 1, 'b': 2, 'c': 3}
"""

``````

### Exercise: Prune (Solution)

``````
def prune(d, keys):
"""Return a copy of D which only contains key/value pairs
whose keys are also in KEYS.
>>> prune({"a": 1, "b": 2, "c": 3, "d": 4}, ["a", "b", "c"])
{'a': 1, 'b': 2, 'c': 3}
"""
return {k: d[k] for k in keys}
``````

### Exercise: Index

``````
def index(keys, values, match):
"""Return a dictionary from keys k to a list of values v for which
match(k, v) is a true value.

>>> index([7, 9, 11], range(30, 50), lambda k, v: v % k == 0)
{7: [35, 42, 49], 9: [36, 45], 11: [33, 44]}
"""

``````

### Exercise: Index (solution)

``````
def index(keys, values, match):
"""Return a dictionary from keys k to a list of values v for which
match(k, v) is a true value.

>>> index([7, 9, 11], range(30, 50), lambda k, v: v % k == 0)
{7: [35, 42, 49], 9: [36, 45], 11: [33, 44]}
"""
return {k: [v for v in values if match(k, v)] for k in keys}
``````

### Nested data

Many useful way to combine lists and dicts:

 Lists of lists `[ [1, 2], [3, 4] ]` Dicts of dicts `{"name": "Brazilian Breads", "location": {"lat": 37.8, "lng": -122}}` Dicts of lists `{"heights": [89, 97], "ages": [6, 8]}` Lists of dicts `[{"title": "Ponyo", "year": 2009}, {"title": "Totoro", "year": 1993}]`

## Slicing

### Slicing syntax

Slicing a list creates a new list with a subsequence of the original list.

``````
letters = ["A", "B", "C", "D", "E", "F"]
#   0    1    2    3    4    5

sublist1 = letters[1:]    # ['B', 'C', 'D', 'E', 'F']
sublist2 = letters[1:4]   # ['B', 'C', 'D']
``````

Slicing also works for strings.

``````
compound_word = "cortaúñas"

word1 = compound_word[:5]    # "corta"
word2 = compound_word[5:]   # "úñas"
``````

Negatives indices and steps can also be specified.

### Copying whole lists

Slicing a whole list copies a list:

``````
listA = [2, 3]
listB = listA

listC = listA[:]
listA[0] = 4
listB[1] = 5
``````

`list()` creates a new list containing existing elements from any iterable:

``````
listA = [2, 3]
listB = listA

listC = list(listA)
listA[0] = 4
listB[1] = 5
``````

Python3 provides more ways in the copy module.

## Built-in functions for iterables

### Functions that process iterables

The following built-in functions work for lists, strings, dicts, and any other iterable data type.

Function Description
`sum(iterable, start)` Returns the sum of values in `iterable`, initializing sum to `start`
`all(iterable)` Return `True` if all elements of `iterable` are true (or if `iterable` is empty)
`any(iterable)` Return `True` if any element of `iterable` is true. Return `False` if `iterable` is empty.
`max(iterable, key=None)` Return the max value in `iterable`
`min(iterable, key=None)` Return the min value in `iterable`

### Examples with sum/any/all

``````
sum([73, 89, 74, 95], 0)  # 331
``````
``````
all([True, True, True, True])     # True
any([False, False, False, True])  # True

all([x < 5 for x in range(5)])    # True

perfect_square = lambda x: x == round(x ** 0.5) ** 2
any([perfect_square(x) for x in range(50, 60)]) # False
``````

### Examples with max/min

``````
max([73, 89, 74, 95])         # 95
max(["C+", "B+", "C", "A"])   # C+
max(range(10))                # 9
``````

A key function can decide how to compare each value:

``````
coords = [ [37, -144], [-22, -115], [56, -163] ]
max(coords, key=lambda coord: coord[0])  # [56, -163]
min(coords, key=lambda coord: coord[0])  #  [-22, -115]
``````
``````
gymnasts = [ ["Brittany", 9.15, 9.4, 9.3, 9.2],
["Lea", 9, 8.8, 9.1, 9.5],
["Maya", 9.2, 8.7, 9.2, 8.8] ]
min(gymnasts, key=lambda scores: min(scores[1:]))    # ["Maya", ...]
max(gymnasts, key=lambda scores: sum(scores[1:], 0)) # ["Brittany", ...]
``````

## Recursion exercises

### Helper functions

If a recursive function needs to keep track of more state than the arguments of the original function, you may need a helper function.

``````
def fUnKyCaSe(text):
"""
>>> fUnKyCaSe("wats up")
'wAtS Up'
"""

def toggle_case(letter, should_up_case):
return letter.upper() if should_up_case else letter.lower()

def up_down(text, should_up_case):
if len(text) == 1:
else:
up_down(text[1:], not should_up_case)

return up_down(text, False)

``````

### Recursively sum a list

Let's code this up recursively:

``````
def sum_nums(nums):
"""Returns the sum of the numbers in NUMS.
>>> sum_nums([6, 24, 1984])
2014
>>> sum_nums([-32, 0, 32])
0
"""

``````

Docstrings typically would not specify whether an approach was recursive or iterative, since that is an implementation detail.

However, we'll make it clear in assignments and exam questions.

### Recursively sum a list (solution)

``````
def sum_nums(nums):
"""Returns the sum of the numbers in NUMS.
>>> sum_nums([6, 24, 1984])
2014
>>> sum_nums([-32, 0, 32])
0
"""
if nums == []:
return 0
else:
return nums[0] + sum_nums( nums[1:] )
``````

When recursively processing lists, the base case is often the empty list and the recursive case is often all-but-the-first items.

### Recursively reversing a string

``````
def reverse(s):
"""Returns a string with the letters of S
in the inverse order.
>>> reverse('ward')
'draw'
"""
``````

Breaking it down into subproblems:

``````
reverse("ward") = reverse("ard") + "w"
reverse("ard") = reverse("rd") + "a"
reverse("rd") = reverse("d") + "r"
reverse("d") = "d"
``````

### Recursively reversing a string (solution)

``````
def reverse(s):
"""Returns a string with the letters of S
in the inverse order.
>>> reverse('ward')
'draw'
"""
if len(s) == 1:
return s
else:
return reverse(s[1:]) + s[0]
``````

When recursively processing strings, the base case is typically an empty string or single-character string, and the recursive case is often all-but-the-first characters.

### Exercise: Reversing a number

``````
def reverse(n):
"""Returns N with the digits reversed.
>>> reverse_digits(123)
321
"""
``````

See walkthrough video here

### Recursion on different data types

Data type Base case condition Current item Recursive case argument
Numbers `== 0`
`== 1`
`n` `n - 1`
Numbers (Digits) `== 0` `< 10` `n % 10` `n // 10`
Lists `== []`
`len(L) == 0`
`L[0]`
`L[-1]`
`L[1:]`
`L[:-1]`
Strings `== ''`
`len(S) == 1`
`S[0]` `S[1:]`
`S[:-1]`

## List diagrams

### Lists in environment diagrams

Lists are represented as a row of index-labeled adjacent boxes, one per element.

``````
pair = [1, 2]
``````

### Nested lists in environment diagrams

Each box either contains a primitive value or points to a compound value.

``````
matrix = [ [1, 2, 0, 4], [0, 1, 3, -1], [0, 0, 1, 8] ]
``````

### Nested lists in environment diagrams

A very nested list:

``````
worst_list = [ [1, 2],
[],
[ [3, False, None], [4, lambda: 5]]]
``````

## Python Project of The Day!

### Sea Level Rise

Sea Level Rise, by Douwe Osinga: Visualize sea levels and population density on interactive maps.

Technologies used: Python (notebook) with PIL/numpy/Rasterio, HTML/CSS/JS with PanZoom
(Github repository)