Skip to content

Commit fecfe4f

Browse files
committed
2023 Day 21
1 parent 371452f commit fecfe4f

File tree

6 files changed

+334
-37
lines changed

6 files changed

+334
-37
lines changed

2023/21.fs

+154-8
Original file line numberDiff line numberDiff line change
@@ -4,24 +4,170 @@ open AdventOfCode
44
open FSharpPlus
55
open FParsec
66

7-
let parser = spaces
7+
let parser = ParseUtils.grid (anyOf ".#S")
88

9-
let solve1 input = 0
9+
let getStoppablePlots steps grid =
10+
//
11+
// simplification: Assume that
12+
// - grid is a square (and length is an odd number)
13+
// - the start is in the middle
14+
// - there is a straight clear path from center to the edge of the tile
15+
//
16+
// The simplifications don't need to hold if the movement is restricted to
17+
// the sinlge tile
18+
//
19+
// the walked cells spread out in a diamond pattern, so we can visualize the
20+
// calculation on individual tiles as follows:
21+
//
22+
// D
23+
// F C F
24+
// F E A E F
25+
// F E A B A E F
26+
// F E A B A B A E F
27+
// C D A B A S A B A C D
28+
// F E A B A B A E F
29+
// F E A B A E F
30+
// F E A E F
31+
// F C F
32+
// D
33+
//
34+
// S: Starting tile
35+
// A: Fully walked tile, Odd number of steps from the start
36+
// B: Fully walked tile, Even number of steps from the start
37+
// C: Potentially partially walked tile, we run the algorithm starting from center
38+
// of the edge of the tile
39+
// D: Partially walked tile (or not at all), we run the algorithm starting from the
40+
// center of the tile
41+
// E: Potentially partially walked tile, we run the algorithm starting from the
42+
// corner of the tile
43+
// F: Partially walked tile (or not at all), we run the algorithm starting from the
44+
// corner of the tile
45+
//
1046

11-
let solve2 input = 0
47+
let tileSize, _ = Array.bounds2d grid
48+
let start = Array.findIndex2d ((=) 'S') grid
49+
let fNeighbors =
50+
Graph.Grid.makeFNeighbors grid (fun _ (_, cell) -> if cell <> '#' then Some 1 else None)
51+
52+
// Calculates number of cells in B and A from above
53+
let (evenCount, oddCount) =
54+
let distances = Graph.flood fNeighbors start
55+
let doCalculate parity =
56+
distances |> Seq.count (fun (_, d) -> d <= steps && d % 2 = parity) |> int64
57+
58+
(doCalculate <| steps % 2), (doCalculate <| 1 - steps % 2)
59+
60+
let fullTilesInSingleLine = (steps - tileSize) / tileSize
61+
62+
// Calculates numbers of A and B tiles from above
63+
let (fullEven, fullOdd) =
64+
let evens = int64 <| fullTilesInSingleLine / 2
65+
let odds = int64 fullTilesInSingleLine - evens
66+
67+
let totalEvens = evens * (2L * evens + 2L) / 2L
68+
let totalOdds = odds * odds // = odds * (2 * odds - 1 + 1) / 2
69+
(4L * totalEvens + 1L, 4L * totalOdds)
70+
71+
let calcWalked pos alreadyWalked =
72+
Graph.flood fNeighbors pos
73+
|> Seq.takeWhile (fun (_, d) -> d + alreadyWalked <= steps)
74+
|> Seq.count (fun (_, d) -> (d + alreadyWalked) % 2 = steps % 2)
75+
76+
// Calculates sums of C and D tiles from above
77+
let (cardinalsInner, cardinalsOuter) =
78+
let midp = fst start
79+
let alreadyWalked = midp + fullTilesInSingleLine * tileSize + 1
80+
81+
[ (0, midp); (midp, 0); (tileSize - 1, midp); (midp, tileSize - 1) ]
82+
|> Seq.map (fun p -> (calcWalked p alreadyWalked, calcWalked p (alreadyWalked + tileSize)))
83+
|> Seq.map (Tuple2.map int64)
84+
|> Seq.reduce Tuple2.add
85+
86+
// Calculates sums of E and F tiles from above
87+
let (edgesInner, edgesOuter) =
88+
let midp = fst start
89+
let alreadyWalked = 2 * (midp + 1) + (fullTilesInSingleLine - 1) * tileSize
90+
91+
[ (0, 0); (0, tileSize - 1); (tileSize - 1, 0); (tileSize - 1, tileSize - 1) ]
92+
|> Seq.map (fun p -> (calcWalked p alreadyWalked, calcWalked p (alreadyWalked + tileSize)))
93+
|> Seq.map (Tuple2.map int64)
94+
|> Seq.reduce Tuple2.add
95+
96+
fullEven * evenCount
97+
+ fullOdd * oddCount
98+
+ cardinalsInner
99+
+ cardinalsOuter
100+
+ (int64 fullTilesInSingleLine) * edgesInner
101+
+ (int64 fullTilesInSingleLine + 1L) * edgesOuter
102+
103+
let solve1 input = getStoppablePlots 64 input
104+
105+
let solve2 input = getStoppablePlots 26501365 input
12106

13107
let solution = makeSolution () parser solve1 solve2
14108

15109
module Tests =
16110
open Xunit
17111
open FsUnit.Xunit
18112

19-
let input = [| "" |]
113+
let input =
114+
[| "..........."
115+
".....###.#."
116+
".###.##..#."
117+
"..#.#...#.."
118+
"....#.#...."
119+
".##..S####."
120+
".##..#...#."
121+
".......##.."
122+
".##.#.####."
123+
".##..##.##."
124+
"..........." |]
20125

21126
[<Fact>]
22127
let ``Example part 1`` () =
23-
testPart1 solution input |> should equal 0
128+
parseTestInput parser input |> getStoppablePlots 6 |> should equal 16
129+
130+
let input2 =
131+
[| "..........."
132+
"..#.....##."
133+
".#.......#."
134+
"....#......"
135+
"....#.#...."
136+
".....S....."
137+
"...##..#..."
138+
"......#...."
139+
".#.......#."
140+
"..#.....##."
141+
"..........." |]
142+
143+
[<Theory>]
144+
[<InlineData(5)>]
145+
[<InlineData(6)>]
146+
[<InlineData(11)>]
147+
[<InlineData(12)>]
148+
[<InlineData(13)>]
149+
[<InlineData(16)>]
150+
[<InlineData(17)>]
151+
[<InlineData(22)>]
152+
[<InlineData(23)>]
153+
[<InlineData(24)>]
154+
let ``Part 2 using part 1 - steps `` steps =
155+
let input = parseTestInput parser input2
156+
157+
let naiveAlg steps grid =
158+
let start = Array.findIndex2d ((=) 'S') grid
159+
160+
let fNeighbors pos =
161+
let dims = Array.bounds2d grid
162+
163+
Tuple2.neighbors4 pos
164+
|> Seq.choose (fun p ->
165+
let pp = Tuple2.map2 Math.modulo p dims
166+
if Array.item2dp pp grid <> '#' then Some(p, 1) else None)
167+
168+
let distances = Graph.flood fNeighbors start
169+
170+
distances |> Seq.takeWhile (snd >> flip (<=) steps) |> Seq.count (fun (_, d) -> d % 2 = steps % 2)
24171

25-
// [<Fact>]
26-
// let ``Example part 2`` () =
27-
// testPart2 solution input |> should equal 0
172+
let expected = naiveAlg steps input
173+
getStoppablePlots steps input |> should equal <| int64 expected

2023/Program.fs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
module Program
22

33
[<EntryPoint>]
4-
let main args = Runner.run 2023 args
4+
let main args = Runner.run 2023 args

0 commit comments

Comments
 (0)