aboutsummaryrefslogtreecommitdiff
path: root/aoc-2020-gleam/src/days/day23.gleam
blob: 8b58fa721de181d7027e95a0bf6140c3b6bab640 (plain)
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
import gleam/io
import gleam/int
import gleam/bool
import gleam/string as str
import gleam/function as fun
import gleam/iterator as iter
import gleam/map.{Map}
import gleam/list.{Continue, Stop}
import ext/resultx as resx
import ext/iteratorx as iterx

fn parse_input(input: String) -> List(Int) {
  input
  |> str.to_graphemes
  |> list.map(with: fun.compose(int.parse, resx.assert_unwrap))
}

fn play(input: String, bound: Int, moves: Int) -> Map(Int, Int) {
  let assert [first, ..tail] = parse_input(input)
  let assert Ok(second) = list.first(tail)

  tail
  |> list.append(case bound >= 10 {
    True -> list.range(from: 10, to: bound)
    False -> []
  })
  |> build_map(map.from_list([#(first, second)]), first)
  |> move(first, moves, bound)
}

fn move(
  cups: Map(Int, Int),
  current: Int,
  round: Int,
  bound: Int,
) -> Map(Int, Int) {
  use <- bool.guard(when: round == 0, return: cups)

  // Remove nodes from source
  let assert Ok(first) = map.get(cups, current)
  let assert Ok(second) = map.get(cups, first)
  let assert Ok(third) = map.get(cups, second)
  let assert Ok(rest) = map.get(cups, third)
  let cups = map.insert(into: cups, for: current, insert: rest)

  // Insert nodes at destination
  let assert Ok(before) =
    iter.iterate(from: current - 1, with: int.subtract(_, 1))
    |> iter.map(with: fn(n) { resx.assert_unwrap(int.modulo(n - 1, bound)) + 1 })
    |> iter.find(one_that: fn(key) {
      !list.contains([first, second, third], key)
    })
  let assert Ok(after) = map.get(cups, before)
  let cups =
    cups
    |> map.insert(for: before, insert: first)
    |> map.insert(for: third, insert: after)

  cups
  |> move(
    cups
    |> map.get(current)
    |> resx.assert_unwrap,
    round - 1,
    bound,
  )
}

fn build_map(list: List(Int), cups: Map(Int, Int), first: Int) -> Map(Int, Int) {
  case list {
    [] -> map.new()
    [head] -> map.insert(into: cups, for: head, insert: first)
    [head, ..tail] -> {
      let assert Ok(second) = list.first(tail)
      build_map(tail, map.insert(into: cups, for: head, insert: second), first)
    }
  }
}

fn to_result_string(cups: Map(Int, Int)) -> String {
  iterx.unfold_infinitely(
    from: 1,
    with: fn(key) { resx.assert_unwrap(map.get(cups, key)) },
  )
  |> iter.drop(1)
  |> iter.fold_until(
    from: "",
    with: fn(acc, key) {
      use <- bool.guard(when: key == 1, return: Stop(acc))
      Continue(acc <> int.to_string(key))
    },
  )
}

fn part1(input: String) -> String {
  input
  |> play(9, 100)
  |> to_result_string
}

fn part2(input: String) -> Int {
  let finished = play(input, 1_000_000, 10_000_000)
  let assert Ok(first) = map.get(finished, 1)
  let assert Ok(second) = map.get(finished, first)
  first * second
}

pub fn main() -> Nil {
  let test = "389125467"
  let assert "67384529" = part1(test)
  let assert 149_245_887_792 = part2(test)

  let input = "925176834"
  io.println(part1(input))
  io.debug(part2(input))

  Nil
}