|
1 | 1 | namespace GameOfLife |
2 | 2 |
|
3 | | -def countNeighbors (x : Nat) (y : Nat) (matrix : Array (Array Bool)) : Nat := |
| 3 | +/- |
| 4 | + The neighbors of a cell at (x, y) are at (x - 1, y - 1) ... (x + 1, y + 1) |
| 5 | + But those values might be out of bounds. So this exercise is all about bounds checking. |
| 6 | +
|
| 7 | + The main idea here is to use arr[idx]? to automate this check. |
| 8 | + It returns an Option where an invalid index would result in none. |
| 9 | +
|
| 10 | + However, idx must be a Nat and in Lean 0 - 1 == 0. Even making a round trip from Int won't solve this. |
| 11 | + One way to approach the problem is do the lower half of bounds checking manually. This is what `getBounds` do. |
| 12 | + Another way would be to erase duplicates coordinates by using, for example, List.removeDup. |
| 13 | +
|
| 14 | + The notation (a...=b) is a Std.Rcc, a type of Range inclusive in both a and b. |
| 15 | + It is roughly equivalent to [a:b+1], but [x:y] only works with natural numbers and is exclusive on y. |
| 16 | +-/ |
| 17 | +def getBounds (x : Nat) (y : Nat) : List Nat × List Nat := |
4 | 18 | let (dxs, dys) := match x, y with |
5 | | - | 0, 0 => ((x...=x+1), (y...=y+1)) |
6 | | - | 0, ny + 1 => ((x...=x+1), (ny...=y+1)) |
7 | | - | nx + 1, 0 => ((nx...=x+1), (y...=y+1)) |
8 | | - | nx + 1, ny + 1 => ((nx...=x+1), (ny...=y+1)) |
9 | | - dxs.toList.foldr (fun i1 => dys.toList.foldr (fun i2 is => is.push (i1, i2))) #[] |
10 | | - |> (·.filterMap (fun (ox, oy) => if ox == x && oy == y then none else matrix[ox]? >>= (·[oy]?))) |
| 19 | + | 0, 0 => (x...=x+1, y...=y+1) |
| 20 | + | 0, ny + 1 => (x...=x+1, ny...=y+1) |
| 21 | + | nx + 1, 0 => (nx...=x+1, y...=y+1) |
| 22 | + | nx + 1, ny + 1 => (nx...=x+1, ny...=y+1) |
| 23 | + (dxs.toList, dys.toList) |
| 24 | + |
| 25 | +/- |
| 26 | + Bounds are transformed to an Array of coordinates |
| 27 | +-/ |
| 28 | +def getNeighbors (x : Nat) (y : Nat) : Array (Nat × Nat) := |
| 29 | + let (dxs, dys) := getBounds x y |
| 30 | + dxs.foldr (fun i1 => dys.foldr (fun i2 is => is.push (i1, i2))) #[] |
| 31 | + |
| 32 | +/- |
| 33 | + This removes the original (x, y) from the neighbors and performs upper bound checking |
| 34 | +
|
| 35 | + The notation >>= is equivalent to monadic bind. For an Option, `x >>= f` is equivalent to: |
| 36 | +
|
| 37 | + ``` |
| 38 | + match x with |
| 39 | + | none => none |
| 40 | + | some v => some (f v) |
| 41 | + ``` |
| 42 | +-/ |
| 43 | +def getValidNeighbors (x : Nat) (y : Nat) (matrix : Array (Array Bool)) : Array Bool := |
| 44 | + let neighbors := getNeighbors x y |
| 45 | + neighbors.filterMap (fun (ox, oy) => if ox == x && oy == y then none else matrix[ox]? >>= (·[oy]?)) |
| 46 | + |
| 47 | +def countLiveNeighbors (x : Nat) (y : Nat) (matrix : Array (Array Bool)) : Nat := |
| 48 | + getValidNeighbors x y matrix |
11 | 49 | |> (·.count true) |
12 | 50 |
|
13 | 51 | def tick (matrix : Array (Array Bool)) : Array (Array Bool) := |
14 | 52 | matrix.mapIdx (fun x row => row.mapIdx (fun y e => |
15 | | - match countNeighbors x y matrix with |
| 53 | + match countLiveNeighbors x y matrix with |
16 | 54 | | 3 => true |
17 | 55 | | 2 => e |
18 | 56 | | _ => false |
|
0 commit comments