aboutsummaryrefslogtreecommitdiff
path: root/aoc2023
diff options
context:
space:
mode:
authorHJ <thechairman@thechairman.info>2023-12-07 01:10:16 -0500
committerHJ <thechairman@thechairman.info>2023-12-07 01:10:16 -0500
commit54d51b1ea90e7367fec566b5c0d04075e651f1bd (patch)
treee26558c9d0f62700609421dbef9e4b1c7af53130 /aoc2023
parent69b46b59e9d5f669b1892f8cab09b662c087b8d3 (diff)
downloadgleam_aoc-54d51b1ea90e7367fec566b5c0d04075e651f1bd.tar.gz
gleam_aoc-54d51b1ea90e7367fec566b5c0d04075e651f1bd.zip
day 7 complete
Diffstat (limited to 'aoc2023')
-rw-r--r--aoc2023/src/day7/.gitignore1
-rw-r--r--aoc2023/src/day7/solve.gleam184
-rw-r--r--aoc2023/test/day7/day7_test.gleam56
3 files changed, 241 insertions, 0 deletions
diff --git a/aoc2023/src/day7/.gitignore b/aoc2023/src/day7/.gitignore
new file mode 100644
index 0000000..ae40cea
--- /dev/null
+++ b/aoc2023/src/day7/.gitignore
@@ -0,0 +1 @@
+input.txt \ No newline at end of file
diff --git a/aoc2023/src/day7/solve.gleam b/aoc2023/src/day7/solve.gleam
new file mode 100644
index 0000000..7c76fc1
--- /dev/null
+++ b/aoc2023/src/day7/solve.gleam
@@ -0,0 +1,184 @@
+import adglent.{First, Second}
+import gleam/bool
+import gleam/function
+import gleam/int
+import gleam/io
+import gleam/list
+import gleam/order.{type Order, Eq, Lt}
+import gleam/string
+
+// Types -------------------------------------------------------------------------------------------
+
+type Hand {
+ Hand(cards: List(Int), wager: Int)
+}
+
+type HandType {
+ FiveOfAKind
+ FourOfAKind
+ FullHouse
+ ThreeOfAKind
+ TwoPair
+ OnePair
+ HighCard
+ Unknown
+}
+
+// Common functions --------------------------------------------------------------------------------
+
+fn hand_rank(hand_rank: HandType) -> Int {
+ case hand_rank {
+ HighCard -> 1
+ OnePair -> 2
+ TwoPair -> 3
+ ThreeOfAKind -> 4
+ FullHouse -> 5
+ FourOfAKind -> 6
+ FiveOfAKind -> 7
+ Unknown -> 100
+ }
+}
+
+fn card_rank(card: String) -> Int {
+ case int.parse(card), card {
+ Ok(n), _ -> n
+ _, "A" -> 14
+ _, "K" -> 13
+ _, "Q" -> 12
+ _, "J" -> 11
+ _, "T" -> 10
+ _, "*" -> 1
+ }
+}
+
+fn parse_hand(str: String) -> Hand {
+ let [cards, wager] = string.split(str, " ")
+ let cards =
+ string.to_graphemes(cards)
+ |> list.map(card_rank)
+ let assert Ok(wager) = int.parse(wager)
+
+ Hand(cards, wager)
+}
+
+fn card_counts(hand: Hand) {
+ hand.cards
+ |> list.sort(int.compare)
+ |> list.chunk(function.identity)
+ |> list.map(list.length)
+ |> list.sort(int.compare)
+}
+
+fn classify_hand(hand: Hand) -> HandType {
+ case list.length(list.unique(hand.cards)), card_counts(hand) {
+ 1, _ -> FiveOfAKind
+ 2, [1, 4] -> FourOfAKind
+ 2, [2, 3] -> FullHouse
+ 3, [1, 1, 3] -> ThreeOfAKind
+ 3, [1, 2, 2] -> TwoPair
+ 4, _ -> OnePair
+ 5, _ -> HighCard
+ _, _ -> Unknown
+ }
+}
+
+// Part 1 ------------------------------------------------------------------------------------------
+
+pub fn part1(input: String) {
+ input
+ |> string.split("\n")
+ |> list.map(parse_hand)
+ |> list.sort(compare_hands)
+ |> list.index_map(fn(i, h) { { i + 1 } * h.wager })
+ |> int.sum
+ |> string.inspect
+}
+
+fn compare_hands(hand1: Hand, hand2: Hand) -> Order {
+ case
+ int.compare(
+ hand_rank(classify_hand(hand1)),
+ hand_rank(classify_hand(hand2)),
+ )
+ {
+ Eq -> compare_top_card(hand1.cards, hand2.cards)
+ other -> other
+ }
+}
+
+fn compare_top_card(cards1: List(Int), cards2: List(Int)) -> Order {
+ use <- bool.guard(cards1 == [] || cards2 == [], Eq)
+ let [c1, ..rest1] = cards1
+ let [c2, ..rest2] = cards2
+ case int.compare(c1, c2) {
+ Eq -> compare_top_card(rest1, rest2)
+ other -> other
+ }
+}
+
+// Part 2 ------------------------------------------------------------------------------------------
+
+pub fn part2(input: String) {
+ input
+ |> string.replace("J", "*")
+ |> string.split("\n")
+ |> list.map(parse_hand)
+ |> list.sort(compare_hands_considering_jokers)
+ |> list.index_map(fn(i, h) { { i + 1 } * h.wager })
+ |> int.sum
+ |> string.inspect
+}
+
+const card_subs = [2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 13, 14]
+
+fn find_best_joker_substitution(hand: Hand) {
+ use acc, card <- list.fold(card_subs, Hand([2, 3, 4, 5, 6], 0))
+ let subbed_cards =
+ list.map(
+ hand.cards,
+ fn(c) {
+ case c {
+ 1 -> card
+ other -> other
+ }
+ },
+ )
+ let subbed_hand = Hand(..hand, cards: subbed_cards)
+ case compare_hands(acc, subbed_hand) {
+ Lt -> subbed_hand
+ _ -> acc
+ }
+}
+
+fn compare_hands_considering_jokers(hand1: Hand, hand2: Hand) -> Order {
+ case
+ int.compare(
+ hand1
+ |> find_best_joker_substitution
+ |> classify_hand
+ |> hand_rank,
+ hand2
+ |> find_best_joker_substitution
+ |> classify_hand
+ |> hand_rank,
+ )
+ {
+ Eq -> compare_top_card(hand1.cards, hand2.cards)
+ other -> other
+ }
+}
+
+pub fn main() {
+ let assert Ok(part) = adglent.get_part()
+ let assert Ok(input) = adglent.get_input("7")
+ case part {
+ First ->
+ part1(input)
+ |> adglent.inspect
+ |> io.println
+ Second ->
+ part2(input)
+ |> adglent.inspect
+ |> io.println
+ }
+}
diff --git a/aoc2023/test/day7/day7_test.gleam b/aoc2023/test/day7/day7_test.gleam
new file mode 100644
index 0000000..f7f8454
--- /dev/null
+++ b/aoc2023/test/day7/day7_test.gleam
@@ -0,0 +1,56 @@
+import gleam/list
+import showtime/tests/should
+import adglent.{type Example, Example}
+import day7/solve
+
+type Problem1AnswerType =
+ String
+
+type Problem2AnswerType =
+ String
+
+/// Add examples for part 1 here:
+/// ```gleam
+///const part1_examples: List(Example(Problem1AnswerType)) = [Example("some input", "")]
+/// ```
+const part1_examples: List(Example(Problem1AnswerType)) = [
+ Example(
+ "32T3K 765
+T55J5 684
+KK677 28
+KTJJT 220
+QQQJA 483",
+ "6440",
+ ),
+]
+
+/// Add examples for part 2 here:
+/// ```gleam
+///const part2_examples: List(Example(Problem2AnswerType)) = [Example("some input", "")]
+/// ```
+const part2_examples: List(Example(Problem2AnswerType)) = [
+ Example(
+ "32T3K 765
+T55J5 684
+KK677 28
+KTJJT 220
+QQQJA 483",
+ "5905",
+ ),
+]
+
+pub fn part1_test() {
+ part1_examples
+ |> should.not_equal([])
+ use example <- list.map(part1_examples)
+ solve.part1(example.input)
+ |> should.equal(example.answer)
+}
+
+pub fn part2_test() {
+ part2_examples
+ |> should.not_equal([])
+ use example <- list.map(part2_examples)
+ solve.part2(example.input)
+ |> should.equal(example.answer)
+}