[Data, the Humanist's New Best Friend](index.ipynb)
*Class 04*

In this class you are expected to learn:

  1. Import and reuse code
  2. Format, comment and document your code
  3. Control flow execution
  4. Complex data types like lists and dictionaries

Imports

Sometimes you want to make a big library of functions. And you'd like to access some of those functions from another program that you're writing.

If you put your functions in a file called myfuncs.py (using your favourite plain text editor), you can import them into another program like this:

>>> from myfuncs import *

The * here means everything

You could also use:

>>> import myfuncs

But, this adds a namespace. To access a function called dostuff in the file myfunc.py after this style of import, you'd have to type:

>>> myfuncs.dostuff(...)

There is a huge amount of libraries creted by the community around Python. Go and check PyPI (Python Package Index), where all the libraries live.


In [5]:
from mylib import maximum, minimum

Activity

Write your own library called `mylib` with two functions: `maximum`, that takes two values as arguments and returns the greatest from both; and `minimum`, that returns the least. And now place the file `mylib.py` in the same [path](http://cli.learncodethehardway.org/book/) where you are running IPython. If everything is OK, the next cell should work.


In [6]:
from mylib import maximum, minimum
print(minimum(maximum(4, 9), maximum(13, 47)))


9

Python has a variety of built-in functions including max(), min(), sum(), pow(), abs(), sqrt() that do exactly what you expect them to do.

Comments

Comments are very important, so use them in your programs.

They are used to tell you what something does in your own language, and they also are used to disable parts of your program if you need to remove them temporarily. Here's how you use comments in Python:

# A comment, this is so you can read your program later.
# Anything after the # is ignored by python.

print "I could have code like this." # and the comment after is ignored

# You can also use a comment to "disable" or comment out a piece of code:
# print "This won't run."

print "This will run."

When working on bigger projects, and for your own general sanity, you want to add comments to your functions explaining what it does, what parameters it needs, and what it returns. For documenting functions we use the triple quote, """, to enclose the content of the comment, which allows us to write multi-line comments.

Also, for paremeters, we follow the same rule:

def celsius_to_fahrenheit(degrees):
    """
    Compute the value in Fahrenheit of the
    given Celsius degrees.

    :param degres: float or integer with the Celsius degrees
    :return: float expressin Fahrenheit degrees
    """
    return 9.0 / 5.0 * degrees + 32

You may think that documenting code is kinda lame, but it gives you future-proof code against our weak memories. And as a nice side-effect, you can generate beautiful HTML or PDF documentation just by processing your source code, in the same way that IPython will include your docstrings (short for document strings) in its interface. There are plenty of tools to document your code; the official in the Python community is Sphinx, though.


In [20]:
def celsius_to_fahrenheit(degrees):
    """
    Compute the value in Fahrenheit of the
    given Celsius degrees.
    
    :param degrees: float or integer with the Celsius degrees
    :return: float expressing Fahrenheit degrees
    """
    return 9.0 / 5.0 * degrees + 32

In [21]:
celsius_to_fahrenheit(25)  # Put the cursor between the parenthesis and press the Tab key


Out[21]:
77.0

Execution Flow

In order to ensure that a function is defined before its first use, you have to know the order in which statements are executed, which is called the flow of execution. The Python interpreter executes one statement at a time.

To make sense of programs, we need to know which instruction gets executed when.

In a program, the statements get executed in the order in which they appear in the program, top to bottom of the file. Later, we’ll learn how to jump around.

On the other hand, IPython allows you to create cells anywhere in the notebook, but internally, they are all executed one after the other, as well as inside the cells.

But what happens when a function gets called? Let's trace through this program:


In [2]:
from IPython.display import IFrame
IFrame("http://pythontutor.com/iframe-embed.html#code=x+%3D+20%0Ay+%3D+10%0A%0Adef+half(a)%3A%0A++++return+a+/+2%0A%0Adef+divide_half(a,+b)%3A%0A++++half_a+%3D+half(a)%0A++++return+half_a+/+b%0A%0Adivide_half(x,+y)&cumulative=false&heapPrimitives=false&drawParentPointers=false&textReferences=false&showOnlyOutputs=false&py=3&curInstr=0&codeDivWidth=350&codeDivHeight=400", width=800, height=500)


Out[2]:

Try now executing the code in the cell


In [ ]:
x = 20
y = 10

def half(a):
    return a / 2

def divide_half(a, b):
    half_a = half(a)
    return half_a / b

divide_half(x, y)

Activity

Write a function, `distance`, that returns the distance between two points, $P_1$ and $P_2$. The coordinates of the points are given by its components: $P_1 = (x1, y1)$, and $P_1 = (x2, y2)$; `distance` receives *4* parameters. Remember that the formula to calcualte the distance is $d = \sqrt{(x2 - x1)^2 + (y2 - y1)^2}$.
*Hint*: $\sqrt{a}$ is the same that $a^{\frac{1}{2}}$. How do we do exponentiation in Python? And square roots?


In [29]:
def distance(x1, y1, x2, y2):
    return ((x2 - x1) ** 2 + (y2 - y1) ** 2) ** (0.5)
distance(1, 3, 8, 9)


Out[29]:
9.219544457292887

Conditional execution

So far, the only thing we can do is to write statements one after the other. Conditional statements give us this ability to check conditions and change the behavior of the program accordingly.

So, if some expression is True, then do something; otherwise do something else.

The syntax in Python is as follows:

if expression:
    # Code in this block is evaluated only if expression is true
    print("Expression is true!")
else:
    # This statements however, are evaluated only if expression is false
    print("Expression is false!")

The second part, the else, is optional. And it can be extended to support more else-cases.


In [ ]:
number = int(input("Choose a number: "))
if number < 5:
    print("It's lower than 5")
elif number < 8:
    print("It's lower than 8")
else:  # Greater than 5 and greater than or equal to 8
    print("It's at least 8 or greater")

Activity

Write a function, `print_parity`, that prints `"x is even"` when its only parameter, `x` is even, and `"x is odd"` when `x` is odd


In [4]:
def print_parity(x):
    # Writes your code here
print_parity(9)
print_parity(20)


9 is odd
20 is even

And of course, if statements can be nested as deep as you need.


In [6]:
x = 6
if 0 < x:
    if x < 10:
        print("x is a positive single digit.")


x is a positive single digit.

Although we can re-write the same code using only one statement.


In [7]:
if 0 < x and x < 10:
    print("x is a positive single digit.")


x is a positive single digit.

And now even more complicated, without using and.


In [8]:
if 0 < x < 10:
    print("x is a positive single digit.")


x is a positive single digit.

Programming with style

Readability counts since programs are read and modified far more often than they are written.

There is a style guide developed by the Python community known as PEP8, that every Python programmer should use. Besides using a docstring for your functions, we will consider the following:

  • use 4 spaces for indentation (this is not new)
  • lines shouldn't be longer than 80 characteres.
  • imports should go at the top of the file
  • separate function definitions with two blank lines
  • keep function definitions together
  • keep top level statements, including function calls, together at the bottom of the program

*But I don't wanna document my func...*

The while Loop

If the only way we had to repeat tasks were writing the same statement over and over, programming wouldn't be the such awesome and fun thing that indeed is. To repeat blocks of code we use loops, and each repetition is called an iteration.

The first kind of loop, the most basic one, is the while loop, in which all the code is executed while a certain logical condition is met.

Let's see an example.


In [37]:
x = 1
while x < 0:
    print(2*x)
    x += 1

In the last example, the condition is the logical expression x < 10, so x is the variable that need to increment in order to get out of the loop. If we remove it, we will end up with an infinite loop.

In while loops is super important to worry about how to stop them, and we do this by modifying the variables involved in the condition.


In [43]:
def num_digits(n):
    count = 0
    print("count", count)
    print("condition", n)
    while n:
        count = count + 1
        print("count", count)
        n = n // 10  # Integer division
        print("condition", n)
    return count
num_digits(122345678767)


count 0
condition 122345678767
count 1
condition 12234567876
count 2
condition 1223456787
count 3
condition 122345678
count 4
condition 12234567
count 5
condition 1223456
count 6
condition 122345
count 7
condition 12234
count 8
condition 1223
count 9
condition 122
count 10
condition 12
count 11
condition 1
count 12
condition 0
Out[43]:
12

In the function num_digits(), we see that as a condition we just have the variable n. That's possible because, as in if statements, those expressions are actually evaluated as boolean by using internally the bool() casting. So you can also write the function as follows.


In [11]:
def num_digits(n):
    count = 0
    while bool(n):
        count = count + 1
        n = n // 10
    return count
num_digits(123456789)


Out[11]:
9

Well, that's ugly and unnecessary. But what really improves the readibility of your code is to write whole expressions when possible. This is specially important when dealing with None values.


In [44]:
def num_digits(n):
    count = 0
    while n != 0:
        count = count + 1
        n = n // 10
    return count
num_digits(123456789)


Out[44]:
9

Activity

The [Collatz Conjecture](https://en.wikipedia.org/wiki/Collatz_Conjecture) (also known as the $3n+1$ conjecture) is the conjecture that the following process is finite for every natural number: if the number $n$ is even divide it by two, $n/2$, if it is odd multiply it by 3 and add 1, $3n + 1$. Repeat this process untill you get the number 1.

We want a function, `collatz`, that given an arbitrary number, `n`, as the argument, prints all the intermediate values until reach `1`. For example, if the starting value (the argument passed to sequence) is 3, the resulting sequence is 3, 10, 5, 16, 8, 4, 2, 1.


*[XKCD wisdom](http://xkcd.com/710/)*

In [51]:
def collatz(num):
    n = num
    while n != 1:
        print(n, end=", ")  # what does that 'end' argument?
        if n % 2 == 0:  # n is even
            n = n // 2
        else:           # n is odd
            n = (3 * n) + 1
    print(n)

collatz(30)


30, 15, 46, 23, 70, 35, 106, 53, 160, 80, 40, 20, 10, 5, 16, 8, 4, 2, 1

Updating the value of a variable is so so common that Python provides several shorcuts.


In [13]:
count = 0
count += 1
count


Out[13]:
1

In [14]:
count += 1
count


Out[14]:
2

In [15]:
n = 2
n += 5
n


Out[15]:
7

In [16]:
n = 2
n *= 5
n


Out[16]:
10

In [17]:
n -= 4
n


Out[17]:
6

In [18]:
n //= 2  # or n /= 2 for float division
n


Out[18]:
3

In [19]:
n %= 2
n


Out[19]:
1

More on strings

A string is, in reality, a second class citizen in the world of Python types. It's what we call a compound type, because is built upon the basic types we've have seen.

But strings are fun. For example, we can access elements individually by using braces and the index or position in the string. And count the length of a string by using the built-in len(). If we put all together we can actually print all the individual characters that form a string.


In [8]:
doge = "many smart"
length = len(doge)
index = 0  # First element is always at position 0!
while index < length:
    character = doge[index]
    print(index, character)
    index += 1


0 m
1 a
2 n
3 y
4  
5 s
6 m
7 a
8 r
9 t

In [21]:
doge[2:-2]


Out[21]:
'ny sma'

Activity

Experiment with different indices in a string. What happens when we do `doge[-1]`? What if we use a slice like in `doge[2:4]`? What if `doge[:2]`?

BTW, iterables usually support the in operator to check if an element is contained or not.


In [23]:
"clever" in doge


Out[23]:
False

Other times, strings can be used as templates in which to put other values. That's called formating.


In [25]:
"Hi, my name is {name}".format(name="Slim Shady")


Out[25]:
'Hi, my name is Slim Shady'

The for loop

There is a while loop pattern so common, that Python (as usual), already includes it as a built-in feature. In the case of compund objects that you can iterate over, the pattern is called traversal and is abstracted in the form of a for loop.

Strings happend to be iterable, what means that you can iterate through them. So let's see how the above example works with a for loop.


In [26]:
doge = "many smart"
for character in doge:
    print(character)


m
a
n
y
 
s
m
a
r
t

The savings in code, as well as the awesomeness of the for loops, are pretty obvious. And if you just don't get it, don't worry, we will see a lot more for loops.

Activity

Write a function, `mult_tables(n)`, that prints multiplication tables for all numbers up to `n`, which is given as the argument


In [15]:
numInput = int(input("Enter a number: "))
rows = 0
counter = 0
columns = numInput
while (rows < 10): #while the number of rows is less than the number entered, first row is row 0
    rows = rows + 1 #determines the starting number of every row - first row starts with 1
    x = rows
    columns = numInput #resets columns to make sure every row has the same amount
    while (columns > 0): #while the number of columns is less than the number entered
        print(rows, "x", (numInput - columns + 1), "=", x, "\t\t", end="")
        x = x + rows #increments in a multiplication table row are by the first number of the row
        columns = columns-1
    print("\n\n") #new line


Enter a number: 5
1 x 1 = 1 		1 x 2 = 2 		1 x 3 = 3 		1 x 4 = 4 		1 x 5 = 5 		


2 x 1 = 2 		2 x 2 = 4 		2 x 3 = 6 		2 x 4 = 8 		2 x 5 = 10 		


3 x 1 = 3 		3 x 2 = 6 		3 x 3 = 9 		3 x 4 = 12 		3 x 5 = 15 		


4 x 1 = 4 		4 x 2 = 8 		4 x 3 = 12 		4 x 4 = 16 		4 x 5 = 20 		


5 x 1 = 5 		5 x 2 = 10 		5 x 3 = 15 		5 x 4 = 20 		5 x 5 = 25 		


6 x 1 = 6 		6 x 2 = 12 		6 x 3 = 18 		6 x 4 = 24 		6 x 5 = 30 		


7 x 1 = 7 		7 x 2 = 14 		7 x 3 = 21 		7 x 4 = 28 		7 x 5 = 35 		


8 x 1 = 8 		8 x 2 = 16 		8 x 3 = 24 		8 x 4 = 32 		8 x 5 = 40 		


9 x 1 = 9 		9 x 2 = 18 		9 x 3 = 27 		9 x 4 = 36 		9 x 5 = 45 		


10 x 1 = 10 		10 x 2 = 20 		10 x 3 = 30 		10 x 4 = 40 		10 x 5 = 50 		



In [4]:
def mult_table(n):
    count = 1
    while count <= 10:
        multiplication = count * n
        print(count, "x", n, "=", multiplication)
        count += 1

def mult_tables(n):
    count = 1
    while count <= n:
        mult_table(count)
        count += 1
    
mult_tables(3)


1 x 1 = 1
2 x 1 = 2
3 x 1 = 3
4 x 1 = 4
5 x 1 = 5
6 x 1 = 6
7 x 1 = 7
8 x 1 = 8
9 x 1 = 9
10 x 1 = 10
1 x 2 = 2
2 x 2 = 4
3 x 2 = 6
4 x 2 = 8
5 x 2 = 10
6 x 2 = 12
7 x 2 = 14
8 x 2 = 16
9 x 2 = 18
10 x 2 = 20
1 x 3 = 3
2 x 3 = 6
3 x 3 = 9
4 x 3 = 12
5 x 3 = 15
6 x 3 = 18
7 x 3 = 21
8 x 3 = 24
9 x 3 = 27
10 x 3 = 30

Lists

Lists are the the first real data structure. They are intended to store, in a sorted way, any number of any kind of elements.


In [27]:
l = [1, 2, 3]
l


Out[27]:
[1, 2, 3]

In [28]:
m = [1, "WAT!", None]
m


Out[28]:
[1, 'WAT!', None]

And the same slicing and length rules apply to list in order to access their elements.


In [29]:
m[1]


Out[29]:
'WAT!'

In [28]:
len(l) + len(m)


Out[28]:
6

In [30]:
l[8]  # Well, this list is not that long


---------------------------------------------------------------------------
IndexError                                Traceback (most recent call last)
<ipython-input-30-02fa24aba1e7> in <module>()
----> 1 l[8]  # Well, this list is not that long

IndexError: list index out of range

One difference here, unlike strings, lists are mutable, that means that once defined, we can change its values. Let's see how to delete eleents using del.


In [51]:
l = [["Javi", "Spain"], ["Natalia", "Canada"], ["Adriana", "Spain"], ["Nandita", "India"]]
for name, country in l:
    message = "My name is {nom}, and I am from {country}".format(nom=name, country=country)
    print(message)


My name is Javi, and I am from Spain
My name is Natalia, and I am from Canada
My name is Adriana, and I am from Spain
My name is Nandita, and I am from India

In [63]:
l = [["Javi", "Spain"], ["Natalia", "Canada"], ["Adriana", "Spain"], ["Nandita", "India"]]
["Natalia", "Canada"] in l


Out[63]:
True

In [53]:
l = [1, 2, 3, 4, 5, 6, 7, 8]
del l[3:6]
l


Out[53]:
[1, 2, 3, 7, 8]

In [54]:
del l[0]
l


Out[54]:
[2, 3, 7, 8]

In [56]:
string = "hey bro! no worries"
string[5] = "5"


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-56-fe96c75731ee> in <module>()
      1 string = "hey bro! no worries"
----> 2 string[5] = "5"

TypeError: 'str' object does not support item assignment

Lists, like almost everything in Python, are objects, and objects have methods. Methods are functions that only operate over the same object that are invoked from. Better with an example.

The list method append() adds an element to the end of the list.


In [58]:
l.append("I see it now!")
l


Out[58]:
[2, 3, 7, 8, 'I see it now!']

The method pop() removes and returns the last item in the list. If an index is specified, it removes the item at the given position in the list, and return it.


In [32]:
l.pop()


Out[32]:
'I see it now!'

*Pop! Pop!*

And index() gives you the position of an element, if the element is member of the list. And remember, the first position is 0, not 1. In Python we start counting at zero, so the first element is the one that is in the position 0, the second in the position 1, and so no.


In [33]:
l.index(8)


Out[33]:
3

Checking for membership is as expected.


In [34]:
1 in m


Out[34]:
True

And remove() removes the element from the list, again, if it's already a member.


In [35]:
l.remove(2)
l


Out[35]:
[3, 7, 8]

In [36]:
l.remove("not in the list")


---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-36-75d84370031e> in <module>()
----> 1 l.remove("not in the list")

ValueError: list.remove(x): x not in list

The + operator concatenates lists.


In [37]:
a = [1, 2, 3]
b = [4, 5, 6]
c = a + b
c


Out[37]:
[1, 2, 3, 4, 5, 6]

Similarly, the * operator repeats a list a given number of times.


In [38]:
[0] * 4


Out[38]:
[0, 0, 0, 0]

In [39]:
[1, 2, 3] * 3


Out[39]:
[1, 2, 3, 1, 2, 3, 1, 2, 3]

And as I promised, more for loops! Analyze carefully the output of the next cell.


In [40]:
lst = [1, 1.6, False, None, "sup, yo!", ["WAT!", 4]]
for item in lst:
    print(item)


1
1.6
False
None
sup, yo!
['WAT!', 4]

And one last important thing. When you assign lists, the content is not copied. Actually, what you are doing is creating another name for the same value.


In [64]:
l1 = [1, 2, 3]
l2 = l1
l2[1] = "OMG!"  # This is how you can edit an element, remember
# We print l1 and... OMG!
l1


Out[64]:
[1, 'OMG!', 3]

But Python provides you with a way to copy the whole thing, a method for lists called, suprise surprise, copy()


In [42]:
l1 = [1, 2, 3]
l2 = l1.copy()  # An equivalent way would be to use l1[:]
l2[1] = "OMG!"
# Print now l1 and... voilá!
l1


Out[42]:
[1, 2, 3]

Activity

Python `list`s have a method called `join()` that does exactly the opposite that `str`'s method `split()`. Play around to discover what they do.


In [70]:
"This is sentence one. This is sentence two from NYT.".split(".")


Out[70]:
['This is sentence one', ' This is sentence two from NYT', '']

In [75]:
s = ' This is sentence two from NYT    '
s = s.strip(" ")
s


Out[75]:
'This is sentence two from NYT'

The range function

In programming, there is the need for simple integer lists so that we can count things. In Python this is made easy through the range() built-in function.


In [85]:
[i for i in range(0, 11, 2)]


Out[85]:
[0, 2, 4, 6, 8, 10]

In [43]:
for i in range(10):
    print(i)


0
1
2
3
4
5
6
7
8
9

Depending on Python version, may or may not return a list. But anyhow, it always returns something that you can iterate over. It also supports parameters, so you can count up or down, in steps, and starting and ending in specific values. For more, read the docs on range().


In [44]:
for i in range(10, -10, -2):
    print(i)


10
8
6
4
2
0
-2
-4
-6
-8

In [45]:
for i in range(-10, 10, 2):
    print(i)


-10
-8
-6
-4
-2
0
2
4
6
8

Notice that while the first argument in taken into account, the second is not, so the resulting list/iterable won't included it.

Tuples

We have seen that lists can be modified at any time. But what if you want your list to be the same all the time? That means that you want an immutable list, which is called a tuple().


In [46]:
t = (1, 2, "cats")

In [47]:
t[0]


Out[47]:
1

In [48]:
t[0] = 56  # Nah, nah, nah


---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-48-ac4a9f68cc76> in <module>()
----> 1 t[0] = 56  # Nah, nah, nah

TypeError: 'tuple' object does not support item assignment

So, besides the operations to modify lists, the rest of them are also applicable to tuples. And as a side-effect, tuples are way more efficient in memory than lists.

You can even convert tuples into list and back


In [49]:
tuple([1, 2, 3])


Out[49]:
(1, 2, 3)

In [50]:
list((1, 2, 3))


Out[50]:
[1, 2, 3]

In [51]:
print(type(tuple()), type(list()))


<class 'tuple'> <class 'list'>

Activity

Write a function, `count_char(string, char)`, that counts the number of aparitions of the character `char` in the string `string`, and returns it.

For example, if we call `count_char("Mississippi", "s")`, the result must be `4`.


In [5]:
def count_char(string, char):
    result = 0
    for character in string:
        if character.upper() == char.upper():
            result += 1
    return result

count_char("Mississippi", "m")


Out[5]:
1

Dictionaries

All of this takes us to last built-in data type in Python: the dictionary. It's the most complex data structure we've seen so far, yet very powerful and useful.

You can think of a dictionary as a list in which the indices can be any (immutable) value, not just integer numbers, and map other values.

One way to create a dictionary is to start with the empty dictionary and add key-value pairs. The empty dictionary is denoted {}, or we can also use the type dict(). To add new key-value pairs, we use the [] notation, as in lists.


In [6]:
d = {}
d[0] = 'a'
d[1] = 'b'
d


Out[6]:
{0: 'a', 1: 'b'}

Or we can build the dictionary at once, as we do with lists or tuples, by separating each key from the value with a colon, :, and then the pairs with commas, ,.


In [7]:
d = {0: 'a', 1: 'b'}
d


Out[7]:
{0: 'a', 1: 'b'}

Let's create an example dictionary that we can use to translate numbers from English to Spanish.


In [9]:
trans = {
    "one": "uno",
    "two": "dos",
    "three": "tres",
    "four": "cuatro",
    "five": "cinco",
}

In [16]:
trans


Out[16]:
{'four': 'cuatro',
 'five': 'cinco',
 'three': 'tres',
 'two': 'dos',
 'one': 'uno'}

In [10]:
trans["one"]


Out[10]:
'uno'

In [11]:
"Yo quiero {number} hamburguesas".format(number=trans["three"])


Out[11]:
'Yo quiero tres hamburguesas'

Dictionaries have some useful methods.

  • Get the list of keys.

In [15]:
for key in d.keys():
    print(key)


0
1

In [12]:
for key in d.keys():
    print(key)


0
1
  • Get the list of values.

In [17]:
for value in d.values():
    print(value)


a
b
  • Get the list of pairs or items.

In [18]:
for pair in d.items():
    print(pair)


(0, 'a')
(1, 'b')

In [19]:
# Althougg is usually more useful if we split the key and the value
for key, value in d.items():
    print(key, value)


0 a
1 b
  • Check if a dictionary has something indexed under a certain key.

Usually, if you try to retrieve the value associted to a key that doesn't exist in the dictionary, you'll see an error.


In [20]:
d["key"]


---------------------------------------------------------------------------
KeyError                                  Traceback (most recent call last)
<ipython-input-20-7add22e82fd1> in <module>()
----> 1 d["key"]

KeyError: 'key'

To avoid this, you could firts ask if the key already exists in the dictionary, and in that key, just return the value associated with.


In [21]:
"key" in d


Out[21]:
False

In [63]:
key = 0
if key in d:
    print(d[key])
else:
    print("Not here!")


a

The last pattern is so common that Python includes a shorthand for that.


In [22]:
d


Out[22]:
{0: 'a', 1: 'b'}

In [26]:
d.get(5, "not there")


Out[26]:
'not there'

In [64]:
key = 1
d.get(key, "value if d doesn't contain the key")


Out[64]:
'b'

In [65]:
key = "nope"
d.get(key, "value if d doesn't contain the key")


Out[65]:
"value if d doesn't contain the key"

Activity

Write a function, `freq_dict(string)`, that counts the number of aparitions of each letter in the string `string`, and returns a dictionary with letters as keys and their frequencies as values.

For example, if we call `freq_dict("Mississippi")`, the result must be `{'M': 1, 's': 4, 'p': 2, 'i': 4}`


In [27]:
def freq_dict(string):
    result = {}
    for character in string:
        if character in result:
            result[character] += 1
        else:
            result[character] = 1
    return result

freq_dict("Mississippi")


Out[27]:
{'M': 1, 'p': 2, 'i': 4, 's': 4}

For the next class