In this blog post about breadth-first traversal, Gibbons talks about implementing BFT with “long zip with” function
JavaScript
x
5
1
lzw :: (a -> a -> a) -> [a] -> [a] -> [a]
2
lzw f x:xs y:ys = f x y : lzw f xs ys
3
lzw _ [] ys = ys
4
lzw _ xs [] = xs
5
Then for instance
JavaScript
1
5
1
data Tree a = EmpT | TN a (Tree a) (Tree a)
2
levels :: Tree a -> [[a]]
3
levels ET = []
4
levels (TN x tl tr) = [x] : lzw (++) (levels tl) (levels tr)
5
My question: My version of
levels
in Python is working, but my lazy version using generators is not. It’s adding an extra[]
level at the end. Why?
I consider this practice for using generators and laziness in Python, and I think the problem is some imprecision in how I’m using yield
, return
, itertools.zip_longest
, etc.
An example of what’s going wrong:
JavaScript
1
25
25
1
>>> my_tree = tree_from_int_segment(10)
2
>>> pprint(my_tree)
3
4
{'left': {'left': {'left': {'left': None, 'right': None, 'val': 0},
5
'right': None,
6
'val': 1},
7
'right': {'left': {'left': None, 'right': None, 'val': 3},
8
'right': None,
9
'val': 4},
10
'val': 2},
11
'right': {'left': {'left': {'left': None, 'right': None, 'val': 6},
12
'right': None,
13
'val': 7},
14
'right': {'left': None, 'right': None, 'val': 9},
15
'val': 8},
16
'val': 5}
17
18
>>> tree_levels(my_tree)
19
20
[[5], [2, 8], [1, 4, 7, 9], [0, 3, 6]]
21
22
>>> list(tree_levels_lazy(my_tree))
23
24
[[5], [2, 8], [1, 4, 7, 9], [0, 3, 6], []]
25
Here’s my code. Thanks in advance!
JavaScript
1
85
85
1
from itertools import islice
2
from itertools import zip_longest
3
4
5
def conditional_apply(g, x, y):
6
if x is None:
7
return y
8
elif y is None:
9
return x
10
else:
11
return g(x, y)
12
13
14
def long_zip_with(f, xs, ys):
15
zs = list(zip_longest(xs, ys))
16
return [conditional_apply(f, x, y) for x, y in zs]
17
18
19
def long_zip_with_lazy(f, xs, ys):
20
zs = zip_longest(xs, ys)
21
for x, y in zs:
22
yield conditional_apply(f, x, y)
23
24
25
def tree_levels(t):
26
if t is None:
27
return []
28
29
a = t["val"]
30
tl = t["left"]
31
tr = t["right"]
32
return [[a]] + long_zip_with(list.__add__, tree_levels(tl), tree_levels(tr))
33
34
35
def tree_levels_lazy(t):
36
if t is None:
37
yield []
38
return
39
40
a = t["val"]
41
tl = t["left"]
42
tr = t["right"]
43
first_level = [a]
44
45
yield first_level
46
for level in long_zip_with_lazy(list.__add__, tree_levels_lazy(tl), tree_levels_lazy(tr)):
47
yield level
48
49
# Rest is just code for building a tree to work with
50
51
52
def split_at(k, xs):
53
it = iter(xs)
54
l = list(islice(it, k))
55
r = list(it)
56
return l, r
57
58
59
def split_list_for_tree(xs):
60
if not xs:
61
return None
62
n = len(xs)
63
xsl_p, xsr = split_at(n//2 + 1, xs)
64
x = xsl_p.pop()
65
return x, xsl_p, xsr
66
67
68
def unfold_tree(seed_to_sprout, init_value):
69
70
def unfolder(seed):
71
sprout = seed_to_sprout(seed)
72
if sprout is None:
73
return None
74
x, seed_l, seed_r = sprout
75
return {"val": x,
76
"left": unfolder(seed_l),
77
"right": unfolder(seed_r)}
78
79
return unfolder(init_value)
80
81
82
def tree_from_int_segment(n):
83
return unfold_tree(split_list_for_tree, list(range(n)))
84
85
Advertisement
Answer
Since the generator version is not concatenating sublists, it should not yield an empty list:
JavaScript
1
6
1
def tree_levels_lazy(t):
2
if t is None:
3
#yield [] <-- remove this
4
return
5
6