Slide 1

Today: string in/not-in, foreach, start lists, how to write main()


Slide 2

Patterns

When writing a new function, it's natural to draw upon similar functions you have written before. In CS, this is called the "patterns" strategy. You can quickly type in the common part of the pattern, and that code is familiar and easy and reliable. Then concentrate on what is different about the problem at hand.


Slide 3

Accumulate Pattern

For strings we have used an "accumulate" pattern many times. Start art off with the empty string, add something to the end of the result string in the loop.

result = ''

loop
   result += something

return result

Slide 4

Quick Return Pattern

  • Loop over collection, looking for X
    If an element matches X, return answer immediately
  • Last line of function
    Something like: return None
    This is the "not found" case
    Only runs if there was no Xs

Slide 5

Example: first_alpha()

> first_alpha() (in string3 section)

Given a string s, return the first alphabetic char in s, or None if there is no alphabetic char. Demonstrates quick-return pattern.

We are not building a whole new string. In this case, we're finding one char in the string and returning it.

'123abc' -> 'a'
'123-456' -> None

1. Hinges on the fact that return exits the function immediately, so we can nest the return inside a loop or if-statement, structured to return a result immediately if the code finds one.

2. If the code gets to the end of the loop, the condition was never found. Like the Sherlock Holmes - "The Dog that did not bark", the loop never hits a true test. So what can we conclude about the string? In this case, there are no alpha chars in the string.


Slide 6

first_alpha() Solution

def first_alpha(s):
    for i in range(len(s)):
        # 1. Return immediately if found
        if s[i].isalpha():
            return s[i]
            
    # 2. If we get here,
    # there was no alpha char.
    return None

Slide 7

Optional Exercise: has_digit()

> has_digit()

'abc123' -> True

'abc' -> False

Use quick-return strategy, returning True or False.

Given a string s, return True if there is a digit in the string somewhere, False otherwise.

Solution - same pattern as first_alpha(), but returns boolean instead of a char

    for i in range(len(s)):
        if s[i].isdigit():
            # 1. Exit immediately if found
            return True
    # 2. If we get here,
    # there was no digit.
    return False


Slide 8

More String Features - String4

On server see String4 - examples of features below: "foreach" and "in"


Slide 9

Recall: String in Test

  • String in - test if a substring appears in a string
  • Chars must match exactly - upper/lower are considered different
    aka "case sensitive"
  • This is True/False test - use .find() to know where substring is
  • Mnemonic: Python re-uses the word "in" from the for loop
  • Style: don't write code for something that Python has built-in
>>> 'c' in 'abcd'
True
>>> 'bc' in 'abcd'  # works for multiple chars
True
>>> 'bx' in 'abcd'
False
>>> 'A' in 'abcd'   # upper/lower are different
False

Slide 10

Variant: not in

The form not in inverts the in test, True if the substring is not in the big thing. This is readability feature, as "not in" reads very naturally in the code. The "not in" form is preferred for expressing this sort of not-in-there test.

>>> 'x' not in 'abcd'
True
>>> 'bc' not in 'abcd'
False

Say for example we have a string of bad_chars, and we want to print a char if it is not bad. Using the "not in" form, the code reads nicely.

if ch not in bad_chars:
    print(ch)

We could use regular "in", and then add a "not" to its left, like the following form. This works, but the "not in" form is the preferred style and reads better.

if not ch in bad_chars:
    print(ch)

Slide 11

We've Had a Good Run: for i in range(len(s)):

Good old for/i/range .. we've gotten a lot of use out of that to loop over the index numbers for a collection. But there's a new sherif in town!


Slide 12

Loop over chars: for ch in s:

  • There is a second, easier way to loop over the chars in a string
  • for ch in s:
  • aka the "foreach" loop
  • Loops over all the chars in s, left to right
  • You do not get the index number here, just the char
  • No square brackets [ ] in this form
  • The variable name ch or char is common for one character
  • Use this form if you do not need access to index numbers
  • Use the for/i/range form if you need access to index numbers
for ch in s:
    # use ch in here,
    # will be one char from s

Slide 13

Example: double_char2()

> double_char2()

Like earlier double_char, but using foreach.

Solution

def double_char2(s):
    result = ''
    for ch in s:
        result = result + ch + ch
    return result

Slide 14

Example: difference(a, b)

> difference()

Demonstrates both foreach and in

difference(a, b): Given two strings, a and b. Return a version of a, including only those chars which are not in b. Use case-sensitive comparisons. Use a for/ch/s loop.

Solution

def difference(a, b):
    result = ''
    # Look at all chars in a.
    # Check each against b.
    for ch in a:
        if ch not in b:
            result += ch
    return result

Slide 15

Optional Exercise: intersect2(a, b)

> intersect2()



Slide 16

Python Lists

See the guide: Python List for more details about lists

  • A list a linear collection of any type of python value
    The formal name of the type is "list"
  • A very common type, up there with int and string
  • Use list to store many of something
    e.g. a thousand urls - a list of url strings
    e.g. a million temperature readings - a list of float values
  • Things in a list are called "elements"
  • "lst" is a generic list variable name

Slide 17

Lists are like Strings

  • The design of Python tries to be consistent
  • If something works for strings
  • It likely works the same way for lists
  • So you already know most of the list features!
  • Same as string:
    len(my_list) returns the number of elements
    use [i] to access elements
    elements indexed: 0 .. len-1
    foreach loop
    in works
  • Differences:
    Lists can contain any type of element
    Lists are mutable Lists do not have a .find() function (we use .index())

Slide 18

Examples on Server list1

See the "list1" examples on the experimental server

  • list_n() - create list [0, 1, 2, 3, ..n-1] - use range() and append()
  • donut_index() - use "in" and index()
  • list_censor() - use everything

Slide 19

1. List Literal: [1, 2, 3]

Use square brackets [..] to write a list in code (a "literal" list value), separating elements with commas. Python will print out a list value using this same square bracket format.

>>> lst = ['a', 'b', 'c', 'd']
>>> lst
['a', 'b', 'c', 'd']
>>> 

alt: lst points to list of 'a' 'b' 'c'
'd'

"empty list" is just 2 square brackets with nothing within: []


Slide 20

2. Length of list: len(lst)

Use len() function, just like string. As a nice point of consistency, many things that work on strings work the same on lists - they are after all both linear collections using zero-based indexing.

>>> len(lst)
4

Slide 21

3. Square Brackets to access/change element

Use square brackets to access an element in a list. Valid index numbers are 0..len-1. Unlike string, can assign = to change an element within the list.

>>> lst = ['a', 'b', 'c', 'd']
>>> lst[0]
'a'
>>> lst[2]
'c'
>>>
>>> lst[0] = 'aaaa'   # Change an elem
>>> lst
['aaaa', 'b', 'c', 'd']
>>>
>>>
>>> lst[9]
Error:list index out of range
>>>

Slide 22

List Mutable

The big difference from strings is that lists are mutable - lists can be changed. Elements can be added, removed, changed over time. We'll look at four list features.


Slide 23

1. List append()

  • Lists can contain any type (today int, str)
  • lst.append('something') - adds an element to the end of the list
  • The most important list function
  • Modifies the list, returns nothing
  • Common list-build pattern:
# 1. make empty list, then call .append() on it
>>> lst = []         
>>> lst.append(1)
>>> lst.append(2)
>>> lst.append(3)
>>> 
>>> lst
[1, 2, 3]
>>> len(lst)
3
>>> lst[0]
1
>>> lst[2]
3
>>>
>>> # 2. Similar, using loop/range to call .append()
>>> nums = []
>>> for i in range(6):
...     nums.append(i * 10)
... 
>>> nums
[0, 10, 20, 30, 40, 50]
>>> len(nums)
6
>>> nums[5]
50

Slide 24

Common lst.append() Bug

The list.append() function modifies the existing list. It returns None. Therefore the following code pattern will not work, setting lst to None:

# NO does not work
lst = []
lst = lst.append(1)
lst = lst.append(2)

# Correct form
lst = []
lst.append(1)
lst.append(2)
# lst points to changed list

Slide 25

String vs. List .append()

Why do programmers write the incorrect form? Because the immutable string does use that pattern, and so you are kind of used to it:

# This works for string
# x = change(x) pattern
s = 'Hello'
s = s.upper()
s = s + '!'
# s is now 'HELLO!'

Slide 26

Example: list_n()

> list_n()

list_n(n): Given non-negative int n, return a list of the form [0, 1, 2, ... n-1]. e.g. n=4 returns [0, 1, 2, 3] For n=0 return the empty list. Use for/i/range and append().

Solution

def list_n(n):
    nums = []
    for i in range(n):
        nums.append(i)
    return nums

Slide 27

2. List "in" / "not in" Tests

  • How to tell if a value is in a list?
    hint: like string!
  • The in operator tests if a value is in a list
  • not in works too, reads nicely, preferred style
>>> lst = ['a', 'b', 'c', 'd']
>>> 'c' in lst
True
>>> 'x' in lst
False
>>> 'x' not in lst  # preferred form to check not-in
True
>>> not 'x' in lst  # equivalent form
True

Slide 28

3. Foreach On List

  • for "foreach" loop works on a list
  • for var in list:
  • Probably the most common loop form
    Many algorithms want to look at every element
  • The variable points to each element in turn, one element per loop
  • No Change do not change the list - add/remove/change - during iteration
    Kind of reasonable rule: how would iteration work if elements left and appeared in the midst of iteration
  • Read the bullet above again – you should not change a list while you are iterating through it.
>>> lst = ['a', 'b', 'c', 'd']
>>> for s in lst:
...   # use s in here
...   print(s)
... 
a
b
c
d

alt: foreach over list, one elem at a
time


Slide 29

Example: intersect(a, b)

> intersect()

intersect(a, b): Given two lists a and b. Construct and return a new list made of all the elements in a which are also in b.

This code is very similar to the string code.


Slide 30

4. list.index(target) - Find Index of Target

  • Similar to str.find(), but with one big difference
  • list.index(target) - returns int index of target if found
  • Flaw: list.index() only works if the target is in the list, failing with an error otherwise
  • Code should check with in first, only call lst.index() if in is True
  • This design is annoying
    It would be easier if lst.index() just returned -1, but it doesn't
  • Variant: list.index(target, start_index) - begin search at start_index instead of 0
>>> lst = ['a', 'b', 'c', 'd']
>>> lst.index('c')
2
>>> lst.index('x')
ValueError: 'x' is not in list
>>> 'x' in lst
False
>>> 'c' in lst
True

Slide 31

Example: donut_index(foods)

> donut_index()

donut_index(foods): Given "foods" list of food name strings. If 'donut' appears in the list return its int index. Otherwise return -1. No loops, use .index(). The solution is quite short.

Solution

def donut_index(foods):
    if 'donut' in foods:
        return foods.index('donut')
    return -1

Slide 32

Optional: list_censor()

> list_censor()

list_censor(n, censor): Given non-negative int n, and a list of "censored" int values, return a list of the form [1, 2, 3, .. n], except omit any numbers which are in the censor list. e.g. n=5 and censor=[1, 3] return [2, 4, 5]. For n=0 return the empty list.

Solution

def list_censor(n, censor):
    nums = []
    for i in range(n):
        # Introduce "num" var since we use it twice
        # Use "in" to check censor
        num = i + 1
        if num not in censor:
            nums.append(num)
    return nums

Slide 33

Style Note: Lists and the letter "s"

As your algorithms grow more complicated, with three or four variables running through your code, it can become difficult to keep straight in your mind which variable, say, is the number and which variable is the list of numbers. Many little bugs have the form of wrong-variable mixups like that.

Therefore, an excellent practice is to name your list variables ending with the letter "s", like "nums" above, or "urls" or "weights". Then when you have an append, or a loop, you can see the singular and plural variables next to each other, reassuring that you have it right.

    url = (some crazy computation)
    urls.append(url)

Or like this

    for url in urls:


Slide 34

Constants in Python

STATES = ['CA, 'NY', 'NV', 'KY', 'OK']
  • Simple form name=value at far left, not within a def
  • This is a type of "global" variable
  • A variable not inside a function
  • In this case it is a constant for the program
  • Functions can just refer to STATES to get its value
  • Python Convention: upper case means its a constant
  • Style: it's a read-only value, code should not modify it

Slide 35

ALPHABET Constant in HW4

# provided ALPHABET constant - list of the regular alphabet
# in lowercase. Refer to this simply as ALPHABET in your code.
# This list should not be modified.
ALPHABET = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']

...

def foo():
    for ch in ALPHABET:  # this works
        print(ch)


Slide 36

main() Function

You have called your code from the command line. The special function main() is the first function to run in a python program, and its job is looking at the command line arguments and figuring out what to do. With HW4, it's time for you to write your own main(). It's easier than you might think.


Slide 37

How Do Command Line Arguments Work? How to write main() code?

For details see guide: Python main() (uses affirm example too)

affirm.zip Example/exercise of main() command line args. You can do it yourself here, or just watch the examples below to see the ideas.


Slide 38

First Run To See The Arg

First run the code, see what the command line arguments (aka "args") do:
-affirm and -hello options (aka "flags")

$ python3 affirm.py -affirm Lisa
Everything is coming up Lisa
$ python3 affirm.py -affirm Bart
Looking good Bart
$ python3 affirm.py -affirm Maggie
Today is the day for Maggie
$ python3 affirm.py -hello Bob
Hello Bob
$

Slide 39

Command Line Option e.g. -affirm

  • Often a command lines select certain options or modes of the program
  • e.g. -affirm tells the program to print an affirmation
  • The convention is that each option starts with a dash, e.g. -affirm
  • These are also known as "flags"

Slide 40

The "args" To a Program

The command line arguments, or "args", are the extra words you type on the command line to tell the program what to do. The system is deceptively simple - the command line arguments are just the words after the program.py on the command line, separated from each other by spaces. So in this command line:

$ python3 affirm.py -affirm Lisa

The words -affirm and Lisa are the 2 command line args.

alt: -affirm and Lisa are the args


Slide 41

args Python List

When a Python program starts running, typically the run begins in a special function named main(). This function can look at the command line arguments, and figure out what other functions to call.

In our main() code the variable args is set up as a Python list containing the command line args.

$ python3 affirm.py -affirm Lisa
   ....
   e.g. args = ['-affirm', 'Lisa']


$ python3 affirm.py -affirm Bart
    ....
    e.g. args = ['-affirm', 'Bart']

Slide 42

Background: have print_affirm() etc. Functions to Call

For this example, say we have functions print_affirm(name) and print_hello(name) which are already written. The main() function will look at the args and call the appropriate functions. The key pattern is that main() can call functions, passing in the right data as their parameters.

Functions to call:

def print_affirm(name):
    """
    Given name, print a random affirmation for that name.
    """
    affirmation = random.choice(AFFIRMATIONS)
    print(affirmation, name)


def print_hello(name):
    """
    Given name, print 'Hello' with that name.
    """
    print('Hello', name)


def print_n_copies(n, name):
    """
    Given int n and name, print n copies of that name.
    """
    for i in range(n):
        # Print each copy of the name with space instead of \n after it.
        print(name, end=' ')
    # Print a single \n after the whole thing.
    print()

Slide 43

How To Write main()

  • args is a Python list of the args typed on the command line
  • Each typed arg is a string in the list
  • If-statements in main() look at args
  • One if-statement for each option/flag
  • Demo: in main() try print(args) to see the args list in action
  • (affirm.py code is done, use affirm-exercise.py to write the code)
  • Look at len(args)
  • Look at args[0]
  • Call functions based on args
    Pass in right data for each parameter

Slide 44

Exercise 1: Make -affirm Work

Make this command line work, editing the file affirm-exercise.py:

$ python3 affirm-exercise.py -affirm Lisa
  • Put in print(args) first to see what the args list looks like
  • len(args) - the number of args
  • args[0] - the first arg
  • Strategy: check if there are 2 args and if first arg is '-affirm'
  • The name to print is in args[1]

Solution code

def main():
    args = sys.argv[1:]
    ....
    ....
    # 1. Check for the -affirm arg pattern:
    #   python3 affirm.py -affirm Bart
    #   e.g. args[0] is '-affirm' and args[1] is 'Bart'
    if len(args) == 2 and args[0] == '-affirm':
        print_affirm(args[1])

Slide 45

Exercise 2: Make -hello Work

Write if-logic in main() to looking for the following command line form, call print_hello(name), passing in correct string.

$ python3 affirm-exercise.py -hello Bart

Solution code

    if len(args) == 2 and args[0] == '-hello':
        print_hello(args[1])

Slide 46

Exercise 3 (optional): Make -n Work

In this case, the function to call is print_n_copies(n, name), where n is an int param, and name is the name to print. Note that the command line args are all strings.

$ python3 affirm-exercise.py -n 10000 Hermione