CIS 1051 - Temple Rome Spring 2023¶

Intro to Problem solving and¶

Programming in Python¶

LOGO

LOGO

Iteration¶

Prof. Andrea Gallegati

( tuj81353@temple.edu )

Iteration¶

The ability to run a block of statements repeatedly.

  • We saw a kind of iteration, using recursion.
  • We saw another kind, using a for loop.
  • We will see yet another kind, using a while statement.

Return Values¶

It is legal to make more than one assignment to the same variable: it makes an existing variable refer to a new value (stop referring the old one).

In [2]:
x = 5
x
Out[2]:
5
In [3]:
x = 7
x
Out[3]:
7

A common source of confusion is to interpret a Python statement like

a = b

as a mathematical proposition of equality: that a and b are equal.

First:

  • equality is a symmetric relationship
  • assignment is not.

In Python, the statement a = 7 is legal while 7 = a is not.

Secondly: in mathematics, an equality is either true or false for all time.

In Python, assignments can make two variables equal, but they don’t have to stay that way.

In [6]:
a = 5
b = a    # a and b are now equal
a = 3    # a and b are no longer equal
b
Out[6]:
5
In [7]:
a
Out[7]:
3

Reassigning variables is useful, but should use it with caution.

It can make the code difficult to read and debug.

Updating Variables¶

a common kind of reassignment, where the new value of the variable depends on the old.

In [9]:
y = y + 1
---------------------------------------------------------------------------
NameError                                 Traceback (most recent call last)
<ipython-input-9-e8b256858701> in <module>()
----> 1 y = y + 1

NameError: name 'y' is not defined

if that variable did not already exist, we get an error updating it, since Python (of course) evaluates the right side before the assignment itself.

Before the update, we have to initialize that variable with a simple assignment:

In [10]:
x = 0
x = x + 1

Updating by

  • adding 1 is called an increment
  • subtracting 1 is called a decrement.

The while Statement¶

Repeating identical or similar tasks without making errors is something that computers do well and people do poorly.

That is iteration.

We have already discussed couple examples iterating using recursion

  • countdown
  • print_n

Being so common, Python provides language features to make it easier to iterate.

  • One is the for statement
  • Another is the while statement

Here below a countdown version using a while statement:

In [11]:
def countdown(n):
    while n > 0:
        print(n)
        n = n - 1
    print('Blastoff!')
In [11]:
def countdown(n):
    while n > 0:
        print(n)
        n = n - 1
    print('Blastoff!')

We can almost read the while statement as if it were plain English:

“While n is greater than 0, display the value of n and then decrement* n. When you get to 0, display the word Blastoff!”*

More formally:

  1. Determine whether the condition is true or false.
  2. If false, exit the while statement and continue execution at the next statement.
  3. If true, run the body and then go back to step 1.

This flow is a loop because step 3 loops back around to the top.

Here, we can prove that the loop terminates:

  • If n is zero or negative, the loop never runs.
  • Otherwise, n gets smaller each time, getting to 0.

In general, the body of the loop should change the value of some variables to eventually make the condition false and terminate the loop.

Otherwise the loop will repeat forever, that is an infinite loop.

For some other loops, it is not so easy to tell.

In [1]:
def sequence(n):
    while n != 1:
        print(n)
        if n % 2 == 0:        # n is even
            n = n / 2
        else:                 # n is odd
            n = n*3 + 1

The loop will continue until n is 1, which makes the condition false.

Each iteration the program outputs n and checks:

  • If even, it is divided by 2.
  • If odd, it is replaced with n*3 + 1.

For arbitrary values of n, it's hard to say it will ever reach 1, i.e. the program terminates.

For example:

In [2]:
sequence(3)
3
10
5.0
16.0
8.0
4.0
2.0

Sometimes we can prove termination, e.g. if n is a power of two, n will be always even until it reaches 1.

In [3]:
sequence(2**5)
32
16.0
8.0
4.0
2.0

To prove this program terminates (Collatz conjecture) for all n > 0, that's an hard question...

So far, no one has been able to prove it or disprove it!

(See http://en.wikipedia.org/wiki/Collatz_conjecture.)

break¶

If we need to end a loop just halfway through the body, we can use the break statement to jump out of the loop.

If we want to take input from the user until "done" is entered:

In [4]:
while True:
    line = input('> ')
    if line == 'done':
        break
    print(line)

print('Done!')
> ciao
ciao
> come 
come 
> va?
va?
> done
Done!

Here the loop condition is always True, so the loop runs until it hits the break statement.

  • Each iteration, it prompts the user with an angle bracket.
  • If the user types done, the break statement exits the loop.
  • Otherwise the program echoes whatever the user types and goes back.

This way of writing while loops you can:

  • Check the condition anywhere in the loop (not just at the top).
  • Express the stop condition affirmatively rather than negatively.

“stop when this happens” rather than “keep going until that happens”

... this is quite common: e.g. the game's loop

Square Roots¶

Loops are often used to compute numerical results by iteratively improving an approximate one.

For example, Newton’s method for computing square roots,

$$ y = \dfrac{x + a\,/\,x}{2} $$

finds the square root of $a$, starting with almost any estimate $x$ and computing a better estimate with this formula.

In [5]:
a = 4 ; x = 3
y = (x + a/x) / 2
y
Out[5]:
2.1666666666666665

The result is closer to the correct answer ($\sqrt{4}=2$).

and repeating the calculation with the new estimate, it gets even closer:

In [6]:
x = y
y = (x + a/x) / 2
y
Out[6]:
2.0064102564102564

... a few more updates and the estimate is almost exact!

In [7]:
x = y
y = (x + a/x) / 2
y
Out[7]:
2.0000102400262145
In [8]:
x = y
y = (x + a/x) / 2
y
Out[8]:
2.0000000000262146

We don’t know how many steps it takes, but we know when we get the right answer because the estimate stops changing.

In [9]:
x = y
y = (x + a/x) / 2
y
Out[9]:
2.0

When y == x, we can stop, thus we can rewrite the above example:

In [11]:
a = 4 ; x = 3
while True:
    print(x)
    y = (x + a/x) / 2
    if y == x:
        break
    x = y
3
2.1666666666666665
2.0064102564102564
2.0000102400262145
2.0000000000262146
2.0

It's dangerous to test float equality.

Floating-point values are only approximately right: most rational/irrational numbers can’t be represented exactly with a float.

It's safer to check (using the built-in function abs) the difference between these two floats being less than a tolerance epsilon.

In [12]:
a = 4 ; x = 3 ; epsilon = 0.0000001
while True:
    print(x)
    y = (x + a/x) / 2
    if abs(y-x) < epsilon:
        break
    x = y
3
2.1666666666666665
2.0064102564102564
2.0000102400262145
2.0000000000262146

Algorithms¶

mechanical processes for solving a category of problems (e.g. Newton’s method).

Something that is not an algorithm:

When we learned to multiply single-digit numbers, we memorized the multiplication table, i.e. 100 specific solutions. This knowledge is not algorithmic.

But if we were “lazy”, we might have learned a few tricks: if these tricks are a general solution for multiplying any single-digit number, they are algorithms!

Thus, the techniques we learned for:

  • addition with carrying
  • subtraction with borrowing
  • long division

are all algorithms.

Algorithms do not require any intelligence to carry out.

They are mechanical processes.

According to a simple set of rules, each step follows from the last.

Executing algorithms is boring.

... but designing them is:

  • interesting
  • intellectually challenging
  • central part of computer science

Some of the things that people do naturally, without difficulty or conscious thought, are the hardest to express algorithmically.

Understanding natural language is a good example!

We all do it, but so far nobody explained how, from an algorithmic point of view.

(... sure? See https://platform.openai.com/examples.)

Debugging¶

Writing bigger programs is spending more time debugging.

More code means:

  • more chances to make an error
  • more places for bugs to hide.

To cut your debugging time try “debugging by bisection”.

If there are 100 lines of code, checking them one at a time takes 100 steps, but:

  • Try to break it in half
  • Look at the middle of it
  • Check for an intermediate value (add a print

This immediately tells us if the problem was in the first/second half of the program.

Every time we halve the number of lines to search the bug for, at least in theory.

In practice it's not clear where the “middle of the program” is: don't count lines ... to find the midpoint!

Think about spots where:

  • chances the bug is before/after the check are about the same
  • it's easy to put a check