The [zip docs](https://docs.python.org/3/library/functions.html#zip) say that `zip(*iterables, strict=False)`: > Iterate over several iterables in parallel, producing tuples with an item from each one.
I use fastcore's L
to show the lists.
from fastcore.all import L
Otherwise the cell output is a zip
object:
zip(range(2))
l1 = L(range(5)) l1
l2 = L(range(6,11)) l2
The most common scenario is zipping 2 same-length iterables together:
L(zip(l1,l2))
It stops when the shortest iterable is exhausted:
L(zip(l1,l2[3:]))
With 1 iterable, you get an iterator of tuples:
L(zip(l1))
With no args, you get an empty iterator:
L(zip())
items
gives you an iterator of tuples, each tuple being a k,v pair:
d = {1: "one", 2: "two", 3: "three"} L(d.items())
Destructuring and zipping that transposes the tuples:
L(zip(*d.items()))
This tree is from Jeremy Howard's bfs tweet:
tree = {1: {2: {4: {}}, 3: {5: {}, 6: {}}}} L(tree)
It represents this:
1
├── 2
│ └── 4
└── 3
├── 5
└── 6
Breadth-first search (BFS) would traverse the tree from shallow to deep, printing each number as: 1 2 3 4 5 6
Jeremy implemented BFS as:
def bfs(q): while q: k,v = zip(*q) print(*k) q = [x for o in v for x in o.items()]
To use it, he calls it with the tree's items:
bfs(tree.items())
The tree's items are
L(tree.items())
L(zip(*tree.items()))
Current node:
L(zip(*tree.items()))[0]
Child nodes:
L(zip(*tree.items()))[1]
The zip
in k,v = zip(*q)
serves to separate node values from their children at each level. Let's see how:
First iteration:
q = [(1, {2: {4: {}}, 3: {5: {}, 6: {}}})]
k,v = zip(*q) # k=(1,), v=({2: {4: {}}, 3: {5: {}, 6: {}}},)
Second iteration:
q = [(2, {4: {}}), (3, {5: {}, 6: {}})]
k,v = zip(*q) # k=(2,3), v=({4: {}}, {5: {}, 6: {}})
Third iteration:
q = [(4, {}), (5, {}), (6, {})]
k,v = zip(*q) # k=(4,5,6), v=({},{},{})
The zip
cleanly separates values (k
) to print from their children (v
) needed for the next level of traversal.