aboutsummaryrefslogtreecommitdiff
path: root/aoc2023/src/day4/solve.gleam
blob: 98b4d820d2c4d0432f77574b9a2f9453921f2e28 (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
119
120
121
122
123
124
125
126
127
import adglent.{First, Second}
import gleam/io
import gleam/string
import gleam/int
import gleam/list
import gleam/dict
import gleam/result
import gleam/option.{None, Some}

type Card {
  Card(number: Int, winning: List(Int), has: List(Int))
}

fn numbers_to_list(str: String) -> List(Int) {
  str
  |> string.split(" ")
  |> list.map(int.parse)
  |> result.values()
}

fn parse_card(card: String) -> Card {
  let assert Ok(#("Card" <> n_str, rest)) = string.split_once(card, ": ")
  let assert Ok(#(winning_str, has_str)) = string.split_once(rest, " | ")
  let assert Ok(n) =
    n_str
    |> string.trim
    |> int.parse

  let winning = numbers_to_list(winning_str)
  let has = numbers_to_list(has_str)

  Card(number: n, winning: winning, has: has)
}

fn win_points(n) {
  case n {
    0 -> 0
    1 -> 1
    n -> 2 * win_points(n - 1)
  }
}

fn count_wins(card: Card) {
  list.fold(
    card.has,
    0,
    fn(acc, c) {
      case list.contains(card.winning, c) {
        True -> acc + 1
        False -> acc
      }
    },
  )
}

pub fn part1(input: String) {
  input
  |> string.split("\n")
  |> list.fold(
    0,
    fn(acc, c) {
      c
      |> parse_card
      |> count_wins
      |> win_points
      |> int.add(acc)
    },
  )
}

fn win_more_cards(cards: List(String), card_count: dict.Dict(Int, Int)) {
  case cards {
    [] ->
      card_count
      |> dict.values
      |> int.sum
    [raw_card, ..rest] -> {
      let card = parse_card(raw_card)
      let wins = count_wins(card)
      case wins {
        0 -> win_more_cards(rest, card_count)
        n -> {
          let assert Ok(bonus) = dict.get(card_count, card.number)
          let won_cards = list.range(card.number + 1, card.number + n)
          {
            use acc, n <- list.fold(won_cards, card_count)
            use c <- dict.update(acc, n)
            case c {
              Some(i) -> i + bonus
              None -> panic as "won a card that doesn't exist in the card pile"
            }
          }
          |> win_more_cards(rest, _)
        }
      }
    }
  }
}

pub fn part2(input: String) {
  let raw_cards =
    input
    |> string.split("\n")

  let card_count =
    list.range(1, list.length(raw_cards))
    |> list.map(fn(n) { #(n, 1) })
    |> dict.from_list()

  raw_cards
  |> win_more_cards(card_count)
}

pub fn main() {
  let assert Ok(part) = adglent.get_part()
  let assert Ok(input) = adglent.get_input("4")
  case part {
    First ->
      part1(input)
      |> adglent.inspect
      |> io.println
    Second ->
      part2(input)
      |> adglent.inspect
      |> io.println
  }
}