Skip to content

Commit f8d169b

Browse files
authored
Draft zigzag traversal post
1 parent 89405aa commit f8d169b

File tree

2 files changed

+79
-0
lines changed

2 files changed

+79
-0
lines changed
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
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+
![zig-zag traversal over a 3x4 grid](/resources/2023-12-16-zigzag-traversal.png){: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+
```
73.4 KB
Loading

0 commit comments

Comments
 (0)