The **while** loop checks the condition before the block is executed, this control structure is also called **pre-test loop**.

In [1]:

```
n = 1000
a = 1
while a ** 3 < n:
print(a ** 3, end=' ') # to print them in one line
a = a + 1
print("end")
```

Let's write a loop that reads numbers until a certain condition is met!

**Example:** Write a code that reads numbers until get 0, then stops and prints the sum of the numbers.

In [2]:

```
n = 0
a = float(input()) # input first
while a != 0:
n = n + a
a = float(input()) # input second
print(n)
```

In this program we repeated a part of the code!

**Example:** Rewrite the previous program **without code repetition**!

In [3]:

```
n = 0
while True: # beginning of the loop
a = float(input()) # input and convert once, no repetition
if a == 0: # halt condition
break # exit out from the loop
n += a # if the loop is continued (same as n = n + a)
print(n)
```

As you can see certain parts of the code is repeated: read the first number than read inside the while loop.

In other languages there is a `do...while`

which checks the condition at the end of the loop. That one is called **post-test loop**. Two typical forms are

`do ... while ...`

or

`do... until ...`

This is missing from python by design. It can be replaced with the following:

```
while True:
<preparation>
if <halt condition>:
break
<further commands>
```

General purpose, commmon programming techniques.

If you have a series of numbers (or any other objects) and you want to collect (or accumulate) some information about them then you can use this paradigm.

**Example:** Calculate the sum of some numbers different from 0. Read the numbers from the standard input, where 0 means the end of sequence.

In [4]:

```
sum = 0 # accumulator variable
while True: # loop
a = float(input()) # input and convert
if a == 0: # halt condition
break
sum += a # summation
print(sum)
```

**Question:** What do we have to get if the sequence of numbers is empty?

**Example:** Multiply the numbers reading from the standard input. What de we have to get when the sequence of numbers is empty? Now, mark the end of the sequence with an empty string, that is, an empty ENTER in the input field. Beware, an empty string cannot be converted to a number!

In [5]:

```
prod = 1 # accumulator
while True: # loop
a = input() # input
if a == "": # halt condition
break
prod *= float(a) # convert and product
print(prod)
```

For a series of objects we wish to count the number of elemnets with a given property.

**Exercise:** In a sequence of integer numbers not containing 0, count the odd numbers. The end of sequence is given by 0.

In [6]:

```
counter = 0 # counter
while True: # loop
a = int(input()) # input and convert
if a == 0: # halt condition
break
if a % 2 == 1: # check the desired property
counter += 1 # increase counter
print(counter)
```

**Example:** Read strings until an empty string arrives and count the words containing the letter `ő`

!

In [7]:

```
counter = 0
while True:
string = input()
if string == "":
break
if "ő" in string:
counter += 1
print(counter)
```

For a given set of objects which are comparable, one wishes to find the best/worst element. E.g. it can be smallest/largest.

**Example:** Find the largest of some non negative
numbers.

In [8]:

```
largest = 0 # default extremum
while True: # halt condition
a = float(input()) # read and convert
if a <= 0: # halt condition
break
if largest < a: # compare to previous extremum
largest = a # update extremum
print("the largest =", largest)
```

**Example:** Change the code for any type of numbers (pozitive or negative). The empty input means the end of sequence.

In [9]:

```
largest = float('-inf') # -∞ is the largest for an empty set
while True: # loop
a = input() # input
if a == "": # halt condition
break
f = float(a) # convert here!
if largest < f: # compare
largest = f # refresh the extremum
print("The largest =", largest)
```

Find out if any of a given set of objects satisfy some condition.

**Example:** Write a prime testing program for integers greater than 1 with searching a divisor up to the square root of the given number.

In [10]:

```
n = int(input()) # read a number
d = 2 # d is iterating over the possible divisors
hit = False # remembers whether the condition is met
while d**2 <= n and not hit: # halt if a divisor is found
if n % d == 0: # is d a divisor?
hit = True # it's a hit
d += 1 # go for the next
if not hit: # n is prime if no divisor found
print("The number {0} is a prime.".format(n))
else:
print("{0} is a composite number.".format(n))
```

For more complicated tasks, you may combine the technics given above. It is not the only way to solve programming exercises but it's a good start. Feel free to experiment with different solutions.

**Example:** How many primes are there under 1000? Put a *find any* (prime test) inside a *counting* code!

In [11]:

```
num = 2 # first number to check
primes = 0 # counter
while num <= 1000: # counting starts here
divisor = 2 # the embedded "find any" starts here
composite = False # hit bool variable
while divisor**2 <= num: # loop for "find any"
if num % divisor == 0: # check for hit
composite = True # set hit variable
break
divisor += 1 # next element to check in "find any"
if not composite: # if found
primes += 1 # then increase the counter
num += 1 # the next number to check
print(primes)
```

Lists are containers for a sequences of objects. For example store numbers:

In [12]:

```
l = [1, 2, 5]
type(l)
```

Out[12]:

Don't call a list object `list`

because it is already the name of the type!

The list can be given with a square bracket, the emelents are comma separated list between the brackets. The elements of the list can be various objects, not necessarily of the same type (even lists).

In [13]:

```
l = [1, 5.3, "dog", [1, 2, 5], 12, "cat"]
```

In [14]:

```
l
```

Out[14]:

A given element can be accessed by index:

In [15]:

```
l = ["a", "b", "c"]
print(l[0], l[1], l[2])
```

As you can see the list indices start with $0$ and goes up to $n-1$ where $n$ is the length of the list.

The connection between indexing from 1 to n and indexing in Python from 0 is explained by this figure:

```
____ ____ ____ ____ ____
| | | | | |
| e1 | e2 | e3 |....| en |
|____|____|____|____|____|
Λ Λ Λ Λ Λ Λ
0 1 2 ... n-1 n
-n 1-n 2-n -1
```

How to access a sublist. Mind that intervals are **inclusive from left and exclusive from right**.

In [16]:

```
l = [0, 1, 2, 3, 4, 5]
print(l[1:3]) # from 1 to 3 (1 is actually the second)
print(l[2:]) # from index 2 to the end
print(l[:3]) # from the beginning up to index 3 (index 3 is not included)
print(l[0:4:2]) # from the start to index 4, take every second element
print(l[-1::-1]) # from last to first with -1 steps (reverse)
```

In [17]:

```
l[-2] # negative index is calculated from the back
```

Out[17]:

You can **concatenate** lists just like strings:

In [18]:

```
print([1, 2, 5] + [8, 5, 3])
```

All the above works for strings (as they are list of characters):

In [19]:

```
s = "trio"
print(s[1:4])
print(s[-2::-1] + s[-1])
print(s[::2])
print(s[1::2])
```

There are differences between strings and lists. One can assign a given value in a list (**mutable**) but not in a string (**immutable**).

In [20]:

```
l = [1, 2, 555]
l[2] = 3
print(l)
```

The same cannot be done with strings:

In [21]:

```
s = "puppy"
s[1] = "a"
print(s)
```

You can manipulate strings but not this way!

We learn some practical functions/methods for lists.

The `range`

object can be used to create a list. It is a **“lazy iterable”** in the sense that it doesn’t generate every number that it “contains” when we create it. Instead it gives those numbers to us as we need them when looping over it. (It is **not** an **iterator**, what we will learn later.)

In [22]:

```
list(range(4))
```

Out[22]:

In [23]:

```
range(4) == [0, 1, 2, 3]
```

Out[23]:

In [24]:

```
list(range(4, 10))
```

Out[24]:

In [25]:

```
list(range(3, 15, 3))
```

Out[25]:

As you can see, it works like list indexing:

```
range(start, end, step)
```

- If you give one parameter (end), then its the last index (zero included, the last excluded)
- If you give two parameters, then its a
`[from...to)`

list - The optional third parameter is the step size (1 by default)

They can be manipulated as lists.

In [26]:

```
range(0, 5, 2)[2]
```

Out[26]:

In [27]:

```
range(1, 10, 2)[1::2]
```

Out[27]:

In [28]:

```
list(range(1, 10, 2)[1::2]) # [1, 3, 5, 7, 9] -> [3, 7]
```

Out[28]:

The `append`

method inserts a new elements at the end:

In [29]:

```
l = [1, 2, 5]
l.append(4)
print(l)
```

In [31]:

```
l = []
while True:
a = input()
if a == "":
break
l.append(a)
print(l)
```

The `insert`

method inserts a new element to a given place:

In [32]:

```
l = [1, 2, 5]
l.insert(1, 'X')
print(l)
```

The `pop`

method erases an element with a **given index**. This function return the deleted element. Calling with empty argument list delete the last element.

In [33]:

```
print(l.pop(1))
print(l)
```

In [34]:

```
print(l.pop())
print(l)
```

The `remove`

method erases the first occurrance of the **given element**:

In [35]:

```
l = [1, 2, 3, 2, 2, 5]
l.remove(2)
print(l)
```

In [36]:

```
while 2 in l:
l.remove(2)
print(l)
```

The `del`

command is deleting elements with **given indices**:

In [37]:

```
l = list(range(8))
del l[::2]
print(l)
del l[1::2]
print(l)
del l[:] # delete everythin, like l.clear()
print(l)
```

The `len`

function gives the length:

In [38]:

```
l = [1, 2, 5]
print(len(l))
```

The `count`

method gives how many times occurrs a given element:

In [39]:

```
l = [1, 2, 3, 4, 1, 2, 1, 4, 5, 1, 3, 2, 4]
print(l.count(1), l.count(4), l.count(15))
```

The `sort`

method sorts changing the list.
The `sorted`

function construct a new (sorted) list.

In [40]:

```
l = [1, 2, 3, 4, 1, 2, 1, 4, 5, 1, 3, 2, 4]
l.sort()
print(l)
```

In [41]:

```
l = [1, 2, 3, 4, 1, 2, 1, 4, 5, 1, 3, 2, 4]
sorted(l)
```

Out[41]:

In [42]:

```
l
```

Out[42]:

In Python the `for`

loop and lists are hand-in-hand. A for loop can iterate through lists (and other iterable objects):

```
for <loop variable> in <iterable object>:
<statements>
else:
<statements>
```

In [43]:

```
l = ["puppy", "cat", "mouse", "cicada"]
for e in l:
print(e, end=" -> ")
print("...")
```

One can iterate **index-wise** instead of **element-wise**. Use the `range`

in that case:

In [44]:

```
# element-wise
l = [2, 3, 5, 7, 11]
for i in l:
print(i, end=" ")
```

In [45]:

```
# index-wise
l = [2, 3, 5, 7, 11]
for i in range(len(l)):
print(l[i], end=" ")
```

In [46]:

```
# print index and element
l = [2, 3, 5, 7, 11]
for i in range(len(l)):
print("index:", i, "element:", l[i])
```

What happen when we have an `else`

-branch in the `for`

-loop?

In [47]:

```
for e in range(0,10,2):
if e % 2 == 1:
break
print(e, end=" ")
else: # the else-branch is exetuted at the end
print("all are even") # as we never jumped out from the for-loop
```

In [48]:

```
l = [0, 2, 4, 5, 6, 8] # there is an odd number in this list
for e in l:
if e % 2 == 1:
break
print(e, end=" ")
else: # now the else-barnch is not executed
print("all are even")
```

In [49]:

```
l = [1, 2, 5]
n = 0
for e in l:
n += e
print(n)
```

In [50]:

```
l = [1, 2, 5, 6, 4, 6, 7, 8]
c = 0
for e in l:
if e % 3 == 0:
c += 1
print(c)
```

In [51]:

```
l = [1, 2, 5, 6, 15, 4, 6, 7, 8]
largest = l[0] # for the case when the list is surely not empty
for e in l[1:]:
if largest < e:
largest = e
print(largest)
```

In [52]:

```
lista = [0, 2, 4, 5, 6, 8]
for elem in lista:
if elem % 2 == 1:
print("there is an odd")
break
else:
print("all are even")
```

**Example:** Let's count how many `"e"`

letters are in those strings which contain the letter `"a"`

!

In [53]:

```
words = ["puppy", "elf", "cat", "elephant", "littlecat"]
c = 0
for word in words:
if "a" in word:
for i in range(len(word)):
if word[i] == "e":
c += 1
print(c)
```

With the use of the `count`

method, you only have to use a counting paradigm:

In [54]:

```
words = ["puppy", "elf", "cat", "elephant", "littlecat"]
c = 0
for word in words:
if "a" in word:
c += word.count("e")
print(c)
```

In [ ]:

```
```