(read this presentation only with code samples!)
Calculate partially invalid string with operations: "28*32***32**39"
Imperative style = actions that change state from initial state to result
expr, res = "28*32***32**39", 1
for t in expr.split("*"):
if t != "":
res *= int(t)
print res
"28*32***32**39", 1
"28", 1
"32", 28
"", 896
"", 896
"32", 896
"", 28672
"39", 28672
1118208
Functional style = apply transformation (and compositions)
from operator import mul
expr = "28*32***32**39"
print reduce(mul, map(int, filter(bool, expr.split("*"))), 1)
"28*32***32**39"
["28","32","","","32","","39"]
["28","32","32","39"]
[28,32,32,39]
1118208
(our focus in red)
In programming language theory, lazy evaluation or call-by-need is an evaluation strategy which delays the evaluation of an expression until its value is needed (non-strict evaluation) and which also avoids repeated evaluations (sharing)
Benefits:
We need: create new list, change element at position
In [1]: update
Out[1]: function __main__.update
In [2]: update([1,2,3], (0, 10))
Out[2]: [10, 2, 3]
In [3]: update([1,2,3], (0, 10), (1, 20), (3, 30))
Out[3]: [10, 20, 3]
Implementation. Think twice!
def update(container, *pairs):
"""
Create copy of given container, chagning one (or more) elements.
Creating of new list is necessary here, cause we don't want to
deal with mutable data structure in current situation - it
will be too problematic to keep everything working with many
map/reduce operations if data will be mutable.
"""
def pair(left, right):
pos, el = right
# todo: use takewhile/dropwhile or write "split-at" helper
return [(s if i != pos else el) for i, s in enumerate(left)]
return reduce(pair, pairs, container)
We need: wrap generator to "reuse" yields
In [4]: def gen():
...: yield 1
...: yield 2
...: yield 3
...:
In [5]: g = gen()
In [6]: list(g)
Out[6]: [1, 2, 3]
In [7]: list(g)
Out[7]: []
In [11]: glazy = lazy(gen)
In [12]: list(glazy)
Out[12]: [1, 2, 3]
In [13]: list(glazy)
Out[13]: [1, 2, 3]
Implementation
class lazy:
def __init__(self, origin, state=None):
self._origin = origin() if callable(origin) else origin
self._state = state or []
self._finished = False
def __iter__(self):
return self if not self._finished else iter(self._state)
def __next__(self):
try:
n = next(self._origin)
except StopIteration as e:
self._finished = True
raise e
else:
self._state.append(n)
return n
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
Usage:
Prelude> take 10 fibs
[0,1,1,2,3,5,8,13,21,34]
Prelude> take 20 fibs
[0,1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181]
user> (def fib (lazy-cat [0 1] (map + fib (rest fib))))
user> (take 10 fib)
(0 1 1 2 3 5 8 13 21 34)
user> (take 20 fib)
(0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181)
def fibs: Stream[Int] =
0 #:: 1 #:: fibs.zip(fibs.tail).map{case (a,b) => a + b}
Usage:
scala> fibs(10)
res0: Int = 55
scala> fibs.take(10).toArray
res1: Array[Int] = Array(0, 1, 1, 2, 3, 5, 8, 13, 21, 34)
scala> fibs.take(20).toArray
res2: Array[Int] = Array(0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181)
def fib():
a, b = 0, 1
while True:
yield a
a, b = b, a+b
Usage:
>>> from itertools import islice
>>> print list(islice(fib(), 10))
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
>>> print list(islice(fib(), 20))
[0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181]
Lazy, imperative
Our goal:
f = Stream()
fib = f << [0, 1] << map(add, f, drop(1, f))
assert list(take(10, fib)) == [0,1,1,2,3,5,8,13,21,34]
assert fib[20] == 6765
assert fib[30:35] == [832040,1346269,2178309,3524578,5702887]
Lazy, declarative
Stream implementation:
https://github.com/kachayev/talks/kharkivpy#6