This commit is contained in:
Hamcha 2024-12-05 13:24:09 +01:00
parent 28036847e2
commit fc530ec5f8
3 changed files with 311 additions and 0 deletions

28
sample/day5-sample.txt Normal file
View file

@ -0,0 +1,28 @@
47|53
97|13
97|61
97|47
75|29
61|13
75|53
29|13
97|29
53|29
61|53
97|53
61|29
47|13
75|47
97|75
47|61
75|61
47|29
75|13
53|13
75,47,61,53,29
97,61,53,29,13
75,29,13
75,97,47,61,53
61,13,29
97,13,75,29,47

119
src/day5_p1.gleam Normal file
View file

@ -0,0 +1,119 @@
import gleam/bool
import gleam/dict.{type Dict}
import gleam/int
import gleam/io
import gleam/iterator
import gleam/list.{contains, filter, fold, group, map, split_while}
import gleam/pair
import gleam/result
import gleam/string
import stdin.{stdin}
pub fn main() {
stdin()
|> read_input
|> calculate
|> io.debug
}
fn calculate(input: #(Dict(Int, List(Int)), List(List(Int)))) {
let #(rules, updates) = input
updates
|> filter(fn(x) { is_valid([], x, rules) })
|> map(middle)
|> fold(0, int.add)
}
fn is_valid(
processed: List(Int),
remaining: List(Int),
rules: Dict(Int, List(Int)),
) {
case remaining {
[] -> True
[x] -> check_rules(processed, x, rules)
[x, ..rest] ->
check_rules(processed, x, rules)
&& is_valid([x, ..processed], rest, rules)
}
}
fn check_rules(processed: List(Int), page: Int, rules: Dict(Int, List(Int))) {
case dict.get(rules, page) {
Ok(before) -> distinct(processed, before)
_ -> True
}
}
// Ensure that none of the needles are in the haystacks
// i.e. none of the pages we need to print after have been processed
fn distinct(haystack: List(Int), needles: List(Int)) -> Bool {
haystack
|> list.any(fn(elem) { needles |> contains(elem) })
|> bool.negate
}
fn middle(update: List(Int)) -> Int {
case update {
[elem] -> elem
[_, ..rest] ->
rest
|> list.reverse
|> list.drop(1)
|> middle
_ -> panic as "uneven list"
}
}
fn read_input(
it: iterator.Iterator(String),
) -> #(Dict(Int, List(Int)), List(List(Int))) {
let #(rules_lines, update_lines) =
it
|> iterator.to_list
|> map(string.trim)
|> filter(not(string.is_empty))
|> split_while(fn(s) { s |> string.contains("|") })
let rules =
rules_lines
|> map(parse_rule)
|> collect_rules
let updates =
update_lines
|> map(parse_update)
#(rules, updates)
}
fn parse_rule(str: String) -> #(Int, Int) {
let assert Ok([a, b]) =
str
|> string.split(on: "|")
|> map(int.parse)
|> result.all
#(a, b)
}
fn collect_rules(rules: List(#(Int, Int))) -> Dict(Int, List(Int)) {
rules
|> group(by: pair.first)
|> dict.map_values(fn(_, v) { v |> map(pair.second) })
}
fn parse_update(str: String) -> List(Int) {
let assert Ok(list) =
str
|> string.split(",")
|> map(int.parse)
|> result.all
list
}
fn not(a: fn(a) -> Bool) {
fn(param: a) -> Bool { !a(param) }
}

164
src/day5_p2.gleam Normal file
View file

@ -0,0 +1,164 @@
import gleam/bool
import gleam/dict.{type Dict}
import gleam/int
import gleam/io
import gleam/iterator
import gleam/list.{contains, filter, fold, group, map, split_while}
import gleam/pair
import gleam/result
import gleam/string
import stdin.{stdin}
pub fn main() {
stdin()
|> read_input
|> calculate
|> io.debug
}
fn calculate(input: #(Dict(Int, List(Int)), List(List(Int)))) {
let #(rules, updates) = input
updates
|> filter(fn(x) { is_valid([], x, rules) |> bool.negate })
|> map(fn(seq) {
seq
|> fix(rules)
|> middle
})
|> fold(0, int.add)
}
fn fix(sequence: List(Int), rules: Dict(Int, List(Int))) -> List(Int) {
fix_loop([], sequence, rules)
}
fn fix_loop(
sequence: List(Int),
remaining: List(Int),
rules: Dict(Int, List(Int)),
) -> List(Int) {
case remaining {
[] -> sequence
_ -> {
// Find next applicable rule
let assert Ok(next) =
remaining
|> map(fn(page) {
case dict.get(rules, page) {
Ok(before) -> #(page, before)
_ -> #(page, [])
}
})
|> list.find(fn(entry) {
let #(_, after) = entry
distinct(remaining, after)
})
|> result.map(pair.first)
let leftover = remaining |> filter(fn(page) { page != next })
let updated_rules = rules |> remove_rule(next)
fix_loop([next, ..sequence], leftover, updated_rules)
}
}
}
fn remove_rule(rules: Dict(Int, List(Int)), page: Int) -> Dict(Int, List(Int)) {
rules
|> dict.map_values(fn(_, v) { v |> filter(fn(rule) { rule != page }) })
|> dict.drop([page])
}
fn is_valid(
processed: List(Int),
remaining: List(Int),
rules: Dict(Int, List(Int)),
) {
case remaining {
[] -> True
[x] -> check_rules(processed, x, rules)
[x, ..rest] ->
check_rules(processed, x, rules)
&& is_valid([x, ..processed], rest, rules)
}
}
fn check_rules(processed: List(Int), page: Int, rules: Dict(Int, List(Int))) {
case dict.get(rules, page) {
Ok(before) -> distinct(processed, before)
_ -> True
}
}
// Ensure that none of the needles are in the haystacks
// i.e. none of the pages we need to print after have been processed
fn distinct(haystack: List(Int), needles: List(Int)) -> Bool {
haystack
|> list.any(fn(elem) { needles |> contains(elem) })
|> bool.negate
}
fn middle(update: List(Int)) -> Int {
case update {
[elem] -> elem
[_, ..rest] ->
rest
|> list.reverse
|> list.drop(1)
|> middle
_ -> panic as "uneven list"
}
}
fn read_input(
it: iterator.Iterator(String),
) -> #(Dict(Int, List(Int)), List(List(Int))) {
let #(rules_lines, update_lines) =
it
|> iterator.to_list
|> map(string.trim)
|> filter(not(string.is_empty))
|> split_while(fn(s) { s |> string.contains("|") })
let rules =
rules_lines
|> map(parse_rule)
|> collect_rules
let updates =
update_lines
|> map(parse_update)
#(rules, updates)
}
fn parse_rule(str: String) -> #(Int, Int) {
let assert Ok([a, b]) =
str
|> string.split(on: "|")
|> map(int.parse)
|> result.all
#(a, b)
}
fn collect_rules(rules: List(#(Int, Int))) -> Dict(Int, List(Int)) {
rules
|> group(by: pair.first)
|> dict.map_values(fn(_, v) { v |> map(pair.second) })
}
fn parse_update(str: String) -> List(Int) {
let assert Ok(list) =
str
|> string.split(",")
|> map(int.parse)
|> result.all
list
}
fn not(a: fn(a) -> Bool) {
fn(param: a) -> Bool { !a(param) }
}