Skip to content
Snippets Groups Projects
Commit f61ef24e authored by Tobias Koelling's avatar Tobias Koelling
Browse files

MVP fib example

parent 0b39df89
No related branches found
No related tags found
1 merge request!24complexity lecture
...@@ -99,7 +99,7 @@ fib1(30) ...@@ -99,7 +99,7 @@ fib1(30)
## Fibonacci series example (2/3) {.leftalign} ## Fibonacci series example (2/3) {.leftalign}
Alternative implementation Iterative implementation with memory
```{python} ```{python}
#| echo: true #| echo: true
...@@ -120,21 +120,11 @@ def fib2(n): ...@@ -120,21 +120,11 @@ def fib2(n):
fib2(30) fib2(30)
``` ```
$\Rightarrow$ Substantially faster 👉 Substantially faster
::: :::
## Performance Overview
## fib3
$$
\begin{pmatrix}f_{n-1}\\f_{n}\end{pmatrix} = \begin{pmatrix}0 & 1\\1 & 1\end{pmatrix} \begin{pmatrix}f_{n-2}\\f_{n-1}\end{pmatrix}
$$
## Fibonacci series example (3/3)
**Performance Overview**
```{python} ```{python}
import time import time
...@@ -145,10 +135,16 @@ def get_time(f): ...@@ -145,10 +135,16 @@ def get_time(f):
end = time.perf_counter() end = time.perf_counter()
return end - start return end - start
def get_times(f, args, n=1):
if n == 1:
return [get_time(lambda: f(a)) for a in args]
else:
return [min(*t) for t in zip(*[[get_time(lambda: f(a)) for a in args] for _ in range(n)])]
x1 = list(range(30)) x1 = list(range(30))
x2 = list(range(60)) x2 = list(range(60))
y1 = [get_time(lambda: fib1(i)) for i in x1] y1 = get_times(fib1, x1)
y2 = [get_time(lambda: fib2(i)) for i in x2] y2 = get_times(fib2, x2)
plt.plot(x1, y1, label="fib1", ls='--') plt.plot(x1, y1, label="fib1", ls='--')
plt.plot(x2, y2, label="fib2") plt.plot(x2, y2, label="fib2")
plt.yscale('log') plt.yscale('log')
...@@ -158,6 +154,137 @@ plt.legend() ...@@ -158,6 +154,137 @@ plt.legend()
None None
``` ```
## Fibonacci series example (3/3)
$$
\begin{pmatrix}f_{n-1}\\f_{n}\end{pmatrix} = \begin{pmatrix}0 & 1\\1 & 1\end{pmatrix} \begin{pmatrix}f_{n-2}\\f_{n-1}\end{pmatrix}
$$
## vector
```{python}
#| echo: true
class V2:
def __init__(self, x, y):
self.x, self.y = x, y
def __repr__(self):
return f"({self.x} {self.y})"
V2(1, 2)
```
## matrix
$$
\begin{pmatrix}a & b \\ c & d\end{pmatrix} \begin{pmatrix}x\\y\end{pmatrix} = \begin{pmatrix}a x + b y\\c x + d y\end{pmatrix}
$$
```{python}
#| echo: true
class M2:
def __init__(self, a, b, c, d):
self.a, self.b, self.c, self.d = a, b, c, d
def __mul__(s, o): # self, other
if isinstance(o, V2):
return V2(s.a * o.x + s.b * o.y, s.c * o.x + s.d * o.y)
elif isinstance(o, M2):
return M2(s.a * o.a + s.b * o.c, s.a * o.b + s.b * o.d,
s.c * o.a + s.d * o.c, s.c * o.b + s.d * o.d)
```
## test it:
```{python}
#| echo: true
m = M2(0, 1, 1, 1)
m * V2(0, 1)
```
```{python}
#| echo: true
m * m * V2(0, 1)
```
## power function
```{python}
#| echo: true
def power(a: "A", n: int, op: "A,A -> A, assoc" = lambda a, b: a * b):
assert n > 0
if n == 1: return a
if n % 2 == 0: return power(op(a, a), n // 2, op)
return op(power(op(a, a), n // 2, op), a)
power(2, 4)
```
:::{.fragment}
... applied on matrix class:
```{python}
#| echo: true
power(m, 2) * V2(0, 1)
```
:::
## fib3
```{python}
#| echo: true
def fib3(n):
if n < 2: return n
return (power(m, n) * V2(0, 1)).x
[fib3(i) for i in range(10)]
```
```{python}
#| echo: true
%%time
fib3(30)
```
## Performance Overview
```{python}
x2 = list(range(0, 65, 1))
x3 = list(range(0, 65, 1))
y2 = get_times(fib2, x2, 3)
y3 = get_times(fib3, x3, 3)
plt.plot(x1, y1, label="fib1", color="C0", ls="--")
plt.plot(x2, y2, label="fib2", color="C1")
plt.plot(x3, y3, label="fib3", color="C2")
plt.ylim(0, max(y2 + y3) * 1.05)
plt.xlabel("Number n", fontsize=14)
plt.ylabel("Time to compute / s", fontsize=14)
plt.legend()
None
```
👉 Unfortunately slower 😬
## Performance Overview
```{python}
x2 = list(range(0, 2**16+1, 2**9))
x3 = list(range(0, 2**16+1, 2**9))
y2 = get_times(fib2, x2, 3)
y3 = get_times(fib3, x3, 3)
plt.plot(x1, y1, label="fib1", color="C0", ls="--")
plt.plot(x2, y2, label="fib2", color="C1")
plt.plot(x3, y3, label="fib3", color="C2")
plt.ylim(0, max(y2 + y3) * 1.05)
plt.xlabel("Number n", fontsize=14)
plt.ylabel("Time to compute / s", fontsize=14)
plt.legend()
None
```
👉 Faster for **large** numbers
# Takeaway # Takeaway
## different algorithms for the same problem ## different algorithms for the same problem
...@@ -165,6 +292,20 @@ None ...@@ -165,6 +292,20 @@ None
Often there are different algorithms solving the same problem. Often there are different algorithms solving the same problem.
These can come with **very** different complexity ratings. These can come with **very** different complexity ratings.
## scalar factors can matter
Big-$\mathcal{O}$ informs about large problems, especially for small problems, scalar factors may dominate.
## math can be better
*never use algorithms if math can do*
:::{.smaller .fragment}
... and pefer better algorithms if math can't do
:::
## human time matters
## typical tasks ## typical tasks
For certain tasks (e.g. sorting, neighbors, averaging, or really anything...), think about what complexity class is expected. For certain tasks (e.g. sorting, neighbors, averaging, or really anything...), think about what complexity class is expected.
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment