- Bug
- Data
- We usually think about lots of data => we need containers of data
- Containers are objects that contain other objects
- Python sequence types are containers
- Alias
- Alternative way of referring to an object

What's in a name? That which we call a rose by any other name would smell as sweet.

Aka. array, ArrayList, vector, ...

Mutable (can be changed in-place)

Contiguous memory - not a "linked list"

Sequence type

- Support indexing and slicing:
`[index], [start:stop:step]`

- Aware of their length:
`len(.)`

- Membership testing:
`in`

- Can be iterated over:
`for ... in`

Much more than a C-style array

- Built-in functionality
**In-place**methods- Can be sorted

Optimized for usability, not speed

```
a_list = [3, 54, 2]
empty = [] # or `list()`
```

```
#!/usr/bin/env python3
l = [3, 5, 1, 9]
ordered = list(range(8))
ordered = list(range(3, 8))
ordered = list(range(12, 2, -2))
print(len(l))
print(ordered.sort())
print(ordered)
```

- Don't worry about performance until it becomes a problem
- But: understand the way lists work (contiguous memory)
- Many inserts at beginning vs. reverse -> append -> reverse

```
ITERATIONS = 1000000
l = [7, 9, 3] # initial content
for i in range(ITERATIONS):
l.insert(0, 0) # slow
l = [7, 9, 3] # initial content
l.reverse()
for i in range(ITERATIONS):
l.append(0) # fast
l.reverse()
```

Python assigns object references

```
first = [1, 2, 3]
second = first
second[1] = 50
```

Shallow copy: `list(.)`

, `[:]`

(slicing),
`l.copy()`

```
first = [1, 2, 3]
second = first[:] # or `list(first)` or `first.copy()`
second[1] = 50
```

Tuples are comparable to lists, but immutable

Sequence type

```
a_tuple = (3, 54, 2)
empty = () # or `tuple()`
t2 = (3, ) # creating a tuple with a single element requires trailing comma
```

Allow to name their values

Values can also be accessed with dot notation

```
#!/usr/bin/env python3
from collections import namedtuple
Point = namedtuple('Point', ['x', 'y']) # define new named subclass of tuple
p = Point(11, y=22) # instantiate with positional args or keywords
print(p[0] + p[1]) # indexable like a regular tuple
print(p.x + p.y) # fields also accessible by name
```

Pythonic way to work with "more than one return value"

Multiple values can be assigned at once

```
#!/usr/bin/env python3
result = "Joe, 117"
name, score = result.split(", ")
score = int(score)
```

Seamless way to return "multiple values"

```
#!/usr/bin/env python3
def read_result(result):
name, score = result.split(", ")
return name, int(score) # tuple is created "on the fly"
# ...
result = "Joe, 117"
name, score = read_result(result) # tuple is unpacked into separate variables
```

Also useful for swapping elements:
`a, b = b, a`

Aka. associative array or map

Collection of key-value pairs with unique keys

Similar to lists, but "keys" are used as indices

Key order is guaranteed to be insertion order (since Python 3.7)

Implemented as hash table (aka. hash map)

```
d = {"key1": "value1", "key2": "value2"}
empty = {} # or `dict()`
```

`dict`

```
#!/usr/bin/env python3
d = {}
d["key1"] = "value1"
d["key2"] = "value2"
d["key1"] = "new value"
```

`dict`

```
#!/usr/bin/env python3
d = {"telephone": "+43(0)316/380-1234",
"email": "contact@uni-graz.at",
"office": "Resowi, E3"}
for key in d: # same as `for key in d.keys()`
print(key, "--", d[key])
for value in d.values():
print(value)
for key, value in d.items():
print(key, "--", value)
```

```
#!/usr/bin/env python3
d = {"telephone": "+43(0)316/380-1234",
"email": "contact@uni-graz.at",
"office": "Resowi, E3"}
for key in sorted(d.keys()):
print(key, "--", d[key])
```

Collection of **unique** elements

Sets are useful wherever mathematical sets are useful

```
s1 = {1, 4, 5, 3, 1, 3, 5, 4, 3, 1, 3, 4} # only 4 (distinct) elements
empty = set() # {} would be an empty dictionary
```

**Intersection** of two sets

**Union** (denoted by

```
s1 = {1, 4, 5, 3, 1, 3, 5, 4, 3, 1, 3, 4}
s2 = {7, 8, 3, 9, 2, 6}
intersection = s1 & s2 # s1.intersection(s2)
union = s1 | s2 # s1.union(s2)
difference = s1 - s2 # s1.difference(s2)
```

Rectangular array of entries, arranged in rows and columns

Python does not offer a built-in type to efficiently deal with matrices

NumPy is the most popular third party package adding support for multi-dimensional arrays

```
sudo apt install python3-numpy # global installation
pip install numpy # in virtual environment
```

```
#!/usr/bin/env python3
import numpy as np
print(f'{np.zeros([3,4])=}')
a1 = np.ones([4,3])
print(f'{np.ones([4,3])=}')
print(f'{3 * np.ones([4,3])=}')
a = np.arange(15).reshape(3, 5)
m = np.matrix(np.arange(6).reshape(2, 3))
print(f'matrix {m=}')
print(f'array {a=}')
print(f'{m * a=}')
print(f'transposed:\n{m.T=}')
print(f'inverse:\n{m.I=}')
```

Download np_matrix.py
NumPy is a library important enough to be dealt with in a designated session

and feedback...