Today: main(), list memory, 3x range() forms, nested loops, while/break logic

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.

How Program Starts Up - main()

Command Line Examples

First run the code, see what the command line arguments 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
$

Command Line Option e.g. -affirm

The "args" To a Program

Command line arguments, or "args", are extra information typed on the line when a program is run. The system is deceptively simple - the command line arguments are the words typed 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

Command line arguments like -affirm often select a mode or option for the run of the program, and these options typically begin with a dash as we have here.

args Python List

In main() args is set up as a Python list. Its contents is the 2 string form of the command line args.

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


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

Preface: have print_affirm() etc. Functions to Call

Functions that do the work in this example, main() will call them. In this case the functions are quite simple, but 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()

main()

How To Write main()

Exercise 1: Make -affirm Work

Make this command line work:

$ python3 affirm.py -affirm Lisa

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])

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.py -hello Bart

Solution code

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

Reminder: Variables

See Guide: Python Variables

List Memory Picture

-Here is some code to append 3 strings into a list
>>> # Build up a list to have 3 string elements
>>> lst = []
>>> lst.append('yo')
>>> lst.append('yo')
>>> lst.append('ma')
>>> lst
['yo', 'yo', 'ma']

Here is the memory picture for this state. Each element in the list can hold anything. Here they each hold a pointer to a string. In this way, each is like a variable, and = can work on each one.

alt: python list, containing 3 pointers to strings

Assignment = is Shallow

What does the following line do?

>>> lst2 = lst

The assignment = does not duplicate or makes a copy of a data structure. Instead the = works in a shallow way, just setting the pointer in the left variable to point to the same thing as the right variable.

Here is a picture after the assignment

alt: lst lst2 both point to the 1 list in memory

>>> lst[2]
'ma'
>>> lst2[2]
'ma'

Mutable List Example

>>> lst[2] = 'Donut'

Here is the memory picture after that change:

alt: lst lst2 both point to list in memory, elem at index 2 changed to point to 'donut'

Now if we look at the value of lst or lst2 .. they both show the changed list, since in fact there is just the one list and they are both pointing to it.

>>> lst
['yo', 'yo', 'donut']
>>> lst2
['yo', 'yo', 'donut']

Don't Need To Draw The Arrows

alt: draw each element of lst as a string inside the list

Lists of Numbers

This code sets up 2 lists of numbers

>>> evens = [2, 4]
>>> odds = [1, 3, 5]

alt: evens is [2, 4], odds is list [1, 3, 5]

Outer: Lists of Lists

A list can be stored inside of an "outer" list. This code sets up the list outer: its first element is the "evens" list, its second element is the "odds" list.

>>> evens = [2, 4]
>>> odds = [1, 3, 5]
>>> outer = []
>>> outer.append(evens)
>>> outer.append(odds)
>>> 
>>> outer
[[2, 4], [1, 3, 5]]
>>>
>>> outer[0]
[2, 4]
>>> outer[1]
[1, 3, 5]

Here is a memory picture of how outer is built.

alt: outer is list len 2, outer[0] points to evens list, outer[1] points to odds


range() function - 3x Forms

range() function - 1 parameter

A classic way to "iterate" over numbers. All languages have some way to do this, and the Python range() functions are an easy and flexible way to do it.

# standard for/i/range structure
for i in range(20):
    print(i)

Variant: reversed()

range() function - 2 parameter

range(5, 10) → [5, 6, 7, 8, 9]
range(5, 6) → [5]
range(5, 5) → []  # i.e. nothing
range(5, 4) → []  # i.e. nothing

range() function - 3 parameter

range(0, 10, 2) → [0, 2, 4, 6, 8]
range(200, 800, 100) → [200, 300, 400, 500, 600, 700]
range(5, 0, -1) -> [5, 4, 3, 2, 1]
range(10, 5, -2) -> [10, 8, 6]
range(10, 10, -2) -> []

In interpreter, try these with list(range(xxx)) form which makes a [ ... ] list of the numbers, making the exact range of numbers very clear:

>>> list(range(10, 5, -2))
[10, 8, 6]

Nested Loops

Nested List / Range Problems

> nested list/range

sixes(n)

Given a non-negative int n, return a list of n multiples of 6 starting with 6 itself, e.g. n=3 returns [6, 12, 18]. -Given a non-negative int n, return a list of the first n multiples of 6, e.g. n=3 returns [6, 12, 18].

backwards(n)

For practice, use reversed()

Given a non-negative int n, return a list of n multiples of 10 in decreasing order, so n = 4 returns [40, 30, 20, 10]

triangle(n)

Given a non-negative int n. Create and return a list of lists like this: [[1], [1, 2], [1, 2, 3] .... [1, 2, 3, .. n]]. There will be n lists inside the outer list. Use nested range loops. One approach: write outer loop to set a "top" variable which varies the top number for each inner list.

Sketch output for n=3. Use this to think about outer and inner loops:

[
 [1],
 [1, 2], 
 [1. 2. 3],
]

Solution

def triangle(n):
    outer = []
    # Outer loop, one iteration per inner list
    for top in range(1, n + 1):
        # Inner loop, create 1 inner list
        # 1..top
        inner = []
        for i in range(1, top + 1):
            inner.append(i)
        outer.append(inner)
    return outer

hundreds(n)

Extra problem for practice.

Consider the sequence 100, 200, 300, ... n*100. For each 'x00' multiple of 100 in that sequence, make a list of the 10 numbers leading up to that number, e.g. [91, 92, 93, 94, 95, 96, 97, 98, 99, 100]. Return a big list of all these lists as elements, first the list of 100, then 200, and so on. N = 2 yields [[91, 92, 93, 94, 95, 96, 97, 98, 99, 100], [191, 192, 193, 194, 195, 196, 197, 198, 199, 200]]. Use nested range loops.


While Loop Break Problems

> loop/break/parse problems

1. Loop Break - censored()

Censor problem: censored(n, censor): Given a non-negative int n. Return a list of the ints [1, 2, 3, ... n]. Except as soon as a number in the given censor list is seen, end the list without that number, so censored(10, [5, 4]) returns [1, 2, 3]. Use "break"

Solution

def censored(n, censor):
    nums = []
    for i in range(1, n + 1):
        if i in censor:  # Key: if-break
            break
        nums.append(i)
    return nums

2. Control i Within Range With = ? Nope!

def censored(n, censor):
    nums = []
    for i in range(1, n + 1):
        if i  in censor:
            i = n + 1  # let's be done with this loop
        else:
            result.append(i)
    return nums

Aside: continue

3. while Equivalent of for/range

i = 0         # 1. init
while i < n:  # 2. test
    # use i
    i += 1    # 3. increment, bottom of loop

while_double()

double_char() written as a while (using a range() is easier for this problem, so just demonstrating what while would look like)

def while_double(s):
    result = ''
    i = 0
    while i < len(s):
        result += s[i] + s[i]
        i += 1
    return result

s.find(target, start) - 2 Param Form

With 2 params, start index indicates where to begin scanning for the target. The default start index is 0

>>> s = 'xx[abc[xx'
>>> s.find('[')     # start = 0, the default
2
>>> s.find('[', 2)  # start = 2, no help
2
>>> s.find('[', 3)  # start = 3, find next one
6

4. all_lefts()

Given string s. Return a list of all the '[' strings in s. Use s.find() within a while loop to find all the '['.

Use search as index variable, marking current position of search within s - this will be a stereotypical pattern for searching through a string.

In loop - how to find each '['? How to end the loop when there is no '['? Important at bottom of loop, advance variable for next iteration ("search" in this case)

Solution

def all_lefts(s):
    search = 0
    result = []
    while search < len(s):
        left = s.find('[', search)
        if left == -1:
            break
        result.append(s[left:left + 1])
        # Key: set up var at end of loop
        search = left + 1
    return result

5. all_brackets()

This pulls it all together, solving a realistic problem.

Given string s. Return a list of all the 'abc' strings for each '[abc]' substring within s. For each left bracket, find the right bracket that follows it. End the search if no left or right bracket is found. Use s.find() within a while loop.

Start with the all_brackets() code. Add code to find the right-bracket following each left bracket. As before: how to terminate the loop? Remember to advance search variable at bottom of loop.

Solution

def all_brackets(s):
    search = 0
    result = []
    while search < len(s):
        left = s.find('[', search)
        if left == -1:
            break
        # Your code here
        pass
        right = s.find(']', left + 1)
        if right == -1:
            break
        result.append(s[left + 1:right])
        # Key: set up var at end of loop
        search = right + 1
    return result