@@ -4,24 +4,170 @@ open AdventOfCode
4
4
open FSharpPlus
5
5
open FParsec
6
6
7
- let parser = spaces
7
+ let parser = ParseUtils.grid ( anyOf " .#S " )
8
8
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
+ //
10
46
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 * ( 2 L * evens + 2 L) / 2 L
68
+ let totalOdds = odds * odds // = odds * (2 * odds - 1 + 1) / 2
69
+ ( 4 L * totalEvens + 1 L, 4 L * 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 + 1 L) * edgesOuter
102
+
103
+ let solve1 input = getStoppablePlots 64 input
104
+
105
+ let solve2 input = getStoppablePlots 26501365 input
12
106
13
107
let solution = makeSolution () parser solve1 solve2
14
108
15
109
module Tests =
16
110
open Xunit
17
111
open FsUnit.Xunit
18
112
19
- let input = [| " " |]
113
+ let input =
114
+ [| " ..........."
115
+ " .....###.#."
116
+ " .###.##..#."
117
+ " ..#.#...#.."
118
+ " ....#.#...."
119
+ " .##..S####."
120
+ " .##..#...#."
121
+ " .......##.."
122
+ " .##.#.####."
123
+ " .##..##.##."
124
+ " ..........." |]
20
125
21
126
[<Fact>]
22
127
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 )
24
171
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
0 commit comments