|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: "Nice Zigzag Traversal with Itertools" |
| 4 | +date: 2023-12-16 |
| 5 | +no_toc: true |
| 6 | +--- |
| 7 | + |
| 8 | +Just a cute Python snippet I put together today and thought worth sharing. |
| 9 | +Needed to fill in a matrix in from the corner outwards, iterating down successive diagonals. |
| 10 | +You might also run into this problem doing a zigzag traversal of a nested list-of-lists. |
| 11 | + |
| 12 | +{:style="width: 40%;display:block; margin-left:auto; margin-right:auto;"} |
| 13 | + |
| 14 | +Itertools comes to the rescue with a nice one liner to generate the `(row, col)` coordinate sequence. |
| 15 | + |
| 16 | +## Ctrl-C Ctrl-V |
| 17 | + |
| 18 | +For a `dim`x`dim` square matrix, |
| 19 | +```python |
| 20 | +sorted(itertools.product(range(dim), repeat=2), key=sum) |
| 21 | +``` |
| 22 | + |
| 23 | +In the arbitrary `nrow`x`ncol`case, |
| 24 | +```python |
| 25 | +sorted(itertools.product(range(nrow), range(ncol)), key=sum) |
| 26 | +``` |
| 27 | + |
| 28 | +## How does this work? |
| 29 | + |
| 30 | +In iterating over the matrix, we want to see all combinations of row and column indices --- this is a product. |
| 31 | +Sort by `(row, col)` coordinate sums to get ascending diagonals. |
| 32 | +For example, `(0, 0)` has sum `0` so will be ordered first. |
| 33 | +The coordinates, `(0, 1)` and `(1, 0)` both have sum `1` so will be ordered next. |
| 34 | + |
| 35 | +The stability of `sort` and the iteration order of `product` gets us the correct ordering along same-sum diagonals. |
| 36 | + |
| 37 | +## Examples |
| 38 | + |
| 39 | +For a square matrix with `dim = 3`, |
| 40 | +```python |
| 41 | +>>> import itertools as it |
| 42 | +>>> sorted(it.product(range(3), repeat=2), key=sum) |
| 43 | +[(0, 0), (0, 1), (1, 0), (0, 2), (1, 1), (2, 0), (1, 2), (2, 1), (2, 2)] |
| 44 | +``` |
| 45 | + |
| 46 | +For a matrix with `nrow=2` and `ncol=3`, |
| 47 | +```python |
| 48 | +>>> sorted(it.product(range(2), range(3)), key=sum) |
| 49 | +[(0, 0), (0, 1), (1, 0), (0, 2), (1, 1), (1, 2)] |
| 50 | +``` |
| 51 | + |
| 52 | +## Bonus: Chunked Diagonals |
| 53 | + |
| 54 | +To take successive diagonal coordinate sequences as separate chunks, just group by coordinates' sums. |
| 55 | + |
| 56 | +```python |
| 57 | +>>> import itertools as it |
| 58 | +>>> for diagonal, coords in it.groupby( |
| 59 | +... sorted(it.product(range(3), repeat=2), key=sum), |
| 60 | +... key=sum, |
| 61 | +...): |
| 62 | +... print(f"{diagonal=}") |
| 63 | +... print("coords=", [*coords], "\n") |
| 64 | +... |
| 65 | +diagonal=0 |
| 66 | +coords= [(0, 0)] |
| 67 | + |
| 68 | +diagonal=1 |
| 69 | +coords= [(0, 1), (1, 0)] |
| 70 | + |
| 71 | +diagonal=2 |
| 72 | +coords= [(0, 2), (1, 1), (2, 0)] |
| 73 | + |
| 74 | +diagonal=3 |
| 75 | +coords= [(1, 2), (2, 1)] |
| 76 | + |
| 77 | +diagonal=4 |
| 78 | +coords= [(2, 2)] |
| 79 | +``` |
0 commit comments