DS2000 (Spring 2019, NCH) :: Lecture 5a

0. Administrivia

  1. Due Friday @ 9pm: HW4 (submit via Blackboard)

1. String Functions

Last week we learned about making functions -- now let's see some really great ones built into Python!

In [2]:
mystring = "Mixed Case"

print(mystring)
print(mystring.upper())
print(mystring.lower())
Mixed Case
MIXED CASE
mixed case

Why is this useful? You don't have to care about case...

In [3]:
userinput = "ExiT"
keyword = "exit"

print(userinput == keyword)
print(userinput.lower() == keyword)
False
True

Another really useful ability: stripping whitespace at the beginning/end of a string.

This comes in handy when you ask someone to type a response, as well as (next week) when processing files.

In [4]:
userinput = "   ExiT " # e.g., via input("Enter Command: ")
keyword = "exit"

print(userinput == keyword)
print(userinput.strip().lower() == keyword)
False
True

Lastly, it is possible to compare strings.

For the most part this does what you would expect (alphabetical), but note that case matters (and uppercase is "less than" lowercase).

In [5]:
print("a" < "b")
print("Alligator" < "Armory")

print("A" < "b")
print("a" < "B")
True
True
True
False

2. List Functions

We already saw how to add to lists, what other things can we do?

In [6]:
mylist = [1, 2, 3]

# Functions that make sense for lists of numbers
print(sum(mylist)) # add all the elements
print(min(mylist)) # find the smallest value
print(max(mylist)) # find the biggest value
6
1
3

When sorting and reversing lists there are two version: one that changes a list, the other produces a new list.

In [7]:
mylist = [1, 2, 3]

print(mylist)
print(list(reversed(mylist))) # doesn't change mylist
print(mylist)

print()

mylist.reverse() # changes mylist (doesn't return anything)
print(mylist)
[1, 2, 3]
[3, 2, 1]
[1, 2, 3]

[3, 2, 1]
In [8]:
mylist = [8, 6, 7, 5, 3, 0, 9]

print(mylist)
print(list(sorted(mylist))) # doesn't change mylist
print(mylist)

print()

mylist.sort() # changes mylist (doesn't return anything)
print(mylist)
[8, 6, 7, 5, 3, 0, 9]
[0, 3, 5, 6, 7, 8, 9]
[8, 6, 7, 5, 3, 0, 9]

[0, 3, 5, 6, 7, 8, 9]

Now let's remove elements from a list via the pop function, which you supply an index.

In [9]:
mylist = [1, 2, 3, 4, 5]

print(mylist)
removed = mylist.pop(2)
print(removed)
print(mylist)
[1, 2, 3, 4, 5]
3
[1, 2, 4, 5]

Importantly, when you provide a list to a function, that function can modify the supplied list...

In [10]:
def pop_first(l):
    if len(l) > 0:
        return l.pop(0) # changes the list, returns the first element (now removed)

mylist = [1, 2, 3, 4, 5]

print(mylist)
print(pop_first(mylist))
print(mylist)
[1, 2, 3, 4, 5]
1
[2, 3, 4, 5]

Lastly, we've seen a pattern over and over with functions on lists...

In [11]:
def my_list_func(mylist, check_func, element_func):
    result = []
    for element in mylist:
        if check_func(element):
            result.append(element_func(element))
    return result

def double(x):
    return 2 * x

def nothing(x):
    return x

def yes(x):
    return True

def is_even(x):
    return x % 2 == 0

mylist = [1, 2, 3, 4, 5]

# Example: double all values
print(my_list_func(mylist, yes, double))

# Example: keep only even
print(my_list_func(mylist, is_even, nothing))

# Example: double only even
print(my_list_func(mylist, is_even, double))
[2, 4, 6, 8, 10]
[2, 4]
[4, 8]

A fast way to do this is a list comprehension...

[element_func(x) for x in mylist if check_func(x)]

Note: if check_func(x) is optional

In [12]:
def double(x):
    return 2 * x

def nothing(x):
    return x

def yes(x):
    return True

def is_even(x):
    return x % 2 == 0

mylist = [1, 2, 3, 4, 5]

# Example: double all values
print([2*x for x in mylist])
# print([double(x) for x in mylist]) # either is fine

# Example: keep only even
print([x for x in mylist if x % 2 == 0])
# print([x for x in mylist if is_even(x)]) # either is fine

# Example: double only even
print([2*x for x in mylist if x % 2 == 0]) # or any combination from above
[2, 4, 6, 8, 10]
[2, 4]
[4, 8]

3. Common to Strings/Lists

We've already done plenty of indexing on strings/lists/tuples, now let's add some powers!

Start with negative indexing, meaning counting from the back...

In [13]:
mylist = [1, 2, 3]
mystring = "abc"

print(mylist[-1])
print(mylist[-2])

print(mystring[-1])
print(mystring[-2])
3
2
c
b

Even more interesting, you can "slice" a list/string using a combination of indexing syntax ([]) and what we learned for the range function: var[start:end:step] (where all are optional; missing first=beginning, missing second=end, missing last=1)

In [14]:
mylist = [1, 2, 3, 4, 5]
mystring = "abcde"

print(mylist[1:3]) # second and third elements

print(mylist[2:]) # start with the third, go till the end
print(mylist[-2:]) # start one from the back, go till the end

print(mystring[::2]) # start at the beginning, go till the end, step by 2

print(mylist[:]) # trick for "cloning" (making a copy)
print(mystring[::-1]) # trick for reversing :)
[2, 3]
[3, 4, 5]
[4, 5]
ace
[1, 2, 3, 4, 5]
edcba

4. Modules

Functions allow us to compartmentalize code, modules allow us to compartmentalize functions, variables, and other things we'll learn about later.

To gain access to a module, use import module_name and then you have access to module_name.whatever...

In [15]:
import string

print(string.ascii_lowercase)
print(string.ascii_uppercase)
print(string.digits)
print(string.punctuation)
abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
0123456789
!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~

There are a couple variations with importing to know about...

In [16]:
from string import ascii_lowercase, ascii_uppercase # just get lower/uppercase, no need to string.

print(ascii_lowercase)
print(ascii_uppercase)
abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
In [17]:
import string as silly_name # rename the module

print(silly_name.digits)
0123456789
In [18]:
from string import punctuation as silly_punctuation # rename the variable

print(silly_punctuation)
!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~
In [19]:
from string import * # get everything, no need to string.

print(digits)
0123456789

Some basic modules to know about...

In [20]:
import math

print(math.pi)
print(math.sqrt(289))
3.141592653589793
17.0
In [21]:
import statistics

print(statistics.mean([1, 6, 4, 2, 5]))
print(statistics.median([1, 6, 4, 2, 5]))
print(statistics.stdev([1, 6, 4, 2, 5]))
3.6
4
2.073644135332772
In [22]:
import random

my_seed = 42
random.seed(my_seed) # allows you to reproduce a sequence of "random" numbers
print(random.random()) # 0-1
print(random.random()) # 0-1
print(random.random()) # 0-1

random.seed(my_seed)
print(random.random())
print(random.random())
print(random.random())

print()

for i in range(20):
    flip = random.randint(0, 1) # flip a coin (0=heads, 1=tails)
    if flip == 0:
        print("heads")
    else:
        print("tails")    
0.6394267984578837
0.025010755222666936
0.27502931836911926
0.6394267984578837
0.025010755222666936
0.27502931836911926

heads
heads
heads
heads
tails
heads
heads
heads
heads
heads
heads
heads
tails
heads
tails
tails
heads
heads
tails
tails

When using/creating functions, it is common to encounter/define default parameters -- meaning they don't have to be supplied and, if they aren't, they have a known value.

In [23]:
def myfunc(a, b=5):
    return a + b

print(myfunc(1, 2))
print(myfunc(1))

# Other examples we've already seen...
print(list(range(10)))
print(list(range(5, 10)))

print("hi,how,are,you".split())
print("hi,how,are,you".split(','))
3
6
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[5, 6, 7, 8, 9]
['hi,how,are,you']
['hi', 'how', 'are', 'you']

Once there are several optional parameters, it becomes easier to provide named arguments...

In [24]:
def myfunc(a, b=5, c=7, d=11):
    return sum((a, b, c, d))

print(myfunc(1))
print(myfunc(1, d=10))
print(myfunc(a=1, b=2, c=3, d=4))
24
23
10

When code is imported, it's not a great idea to execute code (only define functions/variables/etc that can be used by the importing code). SO, when writing code that can be executed (via python myapp.py, it's common to protect it via the following if statement...

In [25]:
def main():
    print('Code to be executed when running via python (but not import)')

if __name__ == "__main__":
    main()
Code to be executed when running via python (but not import)