-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDay13.fs
147 lines (114 loc) · 3.97 KB
/
Day13.fs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
module aoc24.Day13
open FSharp.Text.RegexProvider
open FSharp.Text.RegexExtensions
type Point = int64 * int64
module Point =
let inline op f ((a, b): Point) ((c, d): Point) = (f a c, f b d)
let inline both f ((ax, ay): Point) ((bx, by): Point) = f ax bx && f ay by
let inline one f ((ax, ay): Point) ((bx, by): Point) = f ax bx || f ay by
let inline (.+) a b = Point.op (+) a b
let inline (.-) a b = Point.op (-) a b
let inline (.*) ((x, y): Point) i = Point(x * i, y * i)
let inline (.<<) a b = Point.both (<) a b
let inline (.<) a b = Point.one (<) a b
let inline (.>>) a b = Point.both (>) a b
let inline (.>) a b = Point.one (>) a b
type R =
Regex<"Button A: X\+(?<ax>\d+), Y\+(?<ay>\d+)\\n\
Button B: X\+(?<bx>\d+), Y\+(?<by>\d+)\\n\
Prize: X=(?<px>\d+), Y=(?<py>\d+)">
type Machine =
{ ButtonA: Point
ButtonB: Point
Prize: Point }
let parse input =
input
|> R().TypedMatches
|> Seq.map (fun m ->
{ ButtonA = Point(m.ax.AsInt, m.ay.AsInt)
ButtonB = Point(m.bx.AsInt, m.by.AsInt)
Prize = Point(m.px.AsInt, m.py.AsInt) })
|> Seq.toArray
let findCheapestCombination machine =
let rec bCount i =
let target = machine.ButtonB .* i
if target .<< machine.Prize then bCount (i + 1L) else i
let b = bCount 0
let rec solve a b =
let target = machine.ButtonA .* a .+ machine.ButtonB .* b
if target = machine.Prize then Some(a, b)
elif a > 100 then None
elif target .> machine.Prize then solve a (b - 1L)
else solve (a + 1L) b
solve 0 (b |> min 100)
// wolfram alpha query: solve a_1*a + b_1*b = p_1, a_2*a + b_2*b = p_2 for a and b
// ->
// a = (b_2 p_1 - b_1 p_2)/(a_1 b_2 - a_2 b_1)
// b = (a_2 p_1 - a_1 p_2)/(a_2 b_1 - a_1 b_2)
// with a_2 b_1!=a_1 b_2 and b_2 !=0
let findCheapestCombinationFast machine =
let f = TupleEx.map float
let a1, a2 = f machine.ButtonA
let b1, b2 = f machine.ButtonB
let p1, p2 = f machine.Prize
if a2 * b2 = a1 * b2 || b2 = 0 then // wolfram alpha says that's bad.
None
else
let a = (b2 * p1 - b1 * p2) / (a1 * b2 - a2 * b1)
let b = (a2 * p1 - a1 * p2) / (a2 * b1 - a1 * b2)
let isInt = System.Double.IsInteger
if a >= 0 && b >= 0 && isInt a && isInt b then
Some(int64 a, int64 b)
else
None
let part1 input =
parse input
|> Array.choose findCheapestCombination
|> Array.sumBy (fun (a, b) -> a * 3L + b)
let part2 input =
parse input
|> Array.map (fun m ->
{ m with
Prize = m.Prize |> TupleEx.map ((+) 10000000000000L) })
|> Array.choose findCheapestCombinationFast
|> Array.sumBy (fun (a, b) -> a * 3L + b)
let run = runReadAllText part1 part2
module tests =
open Swensen.Unquote
open Xunit
let example =
"Button A: X+94, Y+34\n\
Button B: X+22, Y+67\n\
Prize: X=8400, Y=5400\n\
\n\
Button A: X+26, Y+66\n\
Button B: X+67, Y+21\n\
Prize: X=12748, Y=12176\n\
\n\
Button A: X+17, Y+86\n\
Button B: X+84, Y+37\n\
Prize: X=7870, Y=6450\n\
\n\
Button A: X+69, Y+23\n\
Button B: X+27, Y+71\n\
Prize: X=18641, Y=10279"
[<Fact>]
let ``Part 1 example`` () = part1 example =! 480L
[<Fact>]
let ``Part 2 example`` () = part2 example =! 875318608908L
let example1Machine =
{ ButtonA = Point(94, 34)
ButtonB = Point(22, 67)
Prize = Point(8400, 5400) }
[<Fact>]
let ``parse example`` () =
let parsed = example |> parse
parsed.Length =! 4
parsed[0] =! example1Machine
[<Fact>]
let ``check solution`` () =
let button1Vec = example1Machine.ButtonA .* 80
let button2Vec = example1Machine.ButtonB .* 40
button1Vec .+ button2Vec =! example1Machine.Prize
example1Machine.ButtonA .* 80 .+ example1Machine.ButtonB .* 40
=! example1Machine.Prize