1//// Authored-by: Claude Opus 4.5 via Crush
2
3import gleam/int
4import gleam/list
5import gleam/result
6import gleam/string
7
8pub type Rotation {
9 Left(Int)
10 Right(Int)
11}
12
13fn parse_rotation(s: String) -> Result(Rotation, Nil) {
14 let s = string.trim(s)
15 case string.first(s) {
16 Ok("L") -> s |> string.drop_start(1) |> int.parse |> result.map(Left)
17 Ok("R") -> s |> string.drop_start(1) |> int.parse |> result.map(Right)
18 _ -> Error(Nil)
19 }
20}
21
22fn apply_rotation(position: Int, rotation: Rotation) -> Int {
23 let new_pos = case rotation {
24 Left(n) -> position - n
25 Right(n) -> position + n
26 }
27 // Wrap around 0-99 (100 positions)
28 { { new_pos % 100 } + 100 } % 100
29}
30
31pub fn part1(input: List(String)) -> Int {
32 let rotations = list.filter_map(input, parse_rotation)
33 let #(zero_count, _final_pos) =
34 list.fold(rotations, #(0, 50), fn(acc, rotation) {
35 let #(count, pos) = acc
36 let new_pos = apply_rotation(pos, rotation)
37 let new_count = case new_pos {
38 0 -> count + 1
39 _ -> count
40 }
41 #(new_count, new_pos)
42 })
43 zero_count
44}
45
46fn count_zeros_crossed(position: Int, rotation: Rotation) -> Int {
47 let #(steps, going_right) = case rotation {
48 Left(n) -> #(n, False)
49 Right(n) -> #(n, True)
50 }
51
52 case going_right {
53 True -> {
54 // Right: hit 0 when (position + k) % 100 == 0
55 let first_hit = { 100 - position } % 100
56 case first_hit {
57 0 -> steps / 100
58 f if f <= steps -> { steps - f } / 100 + 1
59 _ -> 0
60 }
61 }
62 False -> {
63 // Left: hit 0 when (position - k) % 100 == 0, i.e., k == position
64 case position {
65 0 -> steps / 100
66 p if p <= steps -> { steps - p } / 100 + 1
67 _ -> 0
68 }
69 }
70 }
71}
72
73pub fn part2(input: List(String)) -> Int {
74 let rotations = list.filter_map(input, parse_rotation)
75 let #(zero_count, _final_pos) =
76 list.fold(rotations, #(0, 50), fn(acc, rotation) {
77 let #(count, pos) = acc
78 let zeros = count_zeros_crossed(pos, rotation)
79 let new_pos = apply_rotation(pos, rotation)
80 #(count + zeros, new_pos)
81 })
82 zero_count
83}