Using zip

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.

Showing the Lists

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))

Create 2 Iterables

l1 = L(range(5))
l1
l2 = L(range(6,11))
l2

Zip the 2 Iterables Together

The most common scenario is zipping 2 same-length iterables together:

L(zip(l1,l2))

Zip a Long and Short Iterable Together

It stops when the shortest iterable is exhausted:

L(zip(l1,l2[3:]))

Zip 1 Iterable

With 1 iterable, you get an iterator of tuples:

L(zip(l1))

Zip Nothing

With no args, you get an empty iterator:

L(zip())

Zip Dict Items

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()))

Application: Zip a Dict Tree

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]

Why zip in bfs()?

The zip in k,v = zip(*q) serves to separate node values from their children at each level. Let's see how:

  1. First iteration:

    q = [(1, {2: {4: {}}, 3: {5: {}, 6: {}}})]
    k,v = zip(*q)  # k=(1,), v=({2: {4: {}}, 3: {5: {}, 6: {}}},)
    
  2. Second iteration:

    q = [(2, {4: {}}), (3, {5: {}, 6: {}})]
    k,v = zip(*q)  # k=(2,3), v=({4: {}}, {5: {}, 6: {}})
    
  3. 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.