Essay - Published: 2024.12.05 | advent-of-code | build | create | fsharp |
DISCLOSURE: If you buy through affiliate links, I may earn a small commission. (disclosures)
In this post I'll share my solution to Advent of Code 2024 - Day 5 using F#.
HAMINIONs Members get full access to the project files so you can git clone and run it yourself.
Prompt: https://adventofcode.com/2024/day/5
Given:
Ask:
SubProblems
Solutions:
Code:
type Day5Input =
{
NumberToValuesAfterIt: IDictionary<int, int list>
Pages: int list list
}
let read_input
(file_name: string)
: Day5Input
=
let lines =
Utils.read_all_input_file_lines
file_name
let x_to_vals_after_it =
lines
|> Seq.filter (fun l -> l.Contains("|"))
|> Seq.map(fun l -> l.Split("|"))
|> Seq.map(fun vals -> (vals[0], vals[1]))
|> Seq.groupBy(fun (x, y) -> x)
|> Seq.map (fun (x, groups) ->
let just_ys =
groups
|> Seq.map (fun g -> int (snd g))
|> Seq.toList
((int x), just_ys)
)
|> dict
let all_pages =
lines
|> Seq.filter(fun l -> l.Contains(","))
|> Seq.map(fun l -> l.Split(","))
|> Seq.map(fun vals ->
vals
|> Seq.map (fun v -> int v)
|> Seq.toList)
|> Seq.toList
{
NumberToValuesAfterIt = x_to_vals_after_it
Pages = all_pages
}
let is_page_in_order
(page_to_pages_after_it: IDictionary<int, int list>)
(pages: int list)
: bool
=
let mutable pages_to_left = Set.ofList [pages[0]]
let mutable is_valid = true
for i = 1 to pages.Length - 1 do
let current_page = pages[i]
match page_to_pages_after_it.TryGetValue current_page with
| false, _ -> ()
| true, pages_that_must_come_later ->
let page_in_wrong_order =
pages_that_must_come_later
|> Seq.exists (fun p ->
pages_to_left.Contains(p)
)
if page_in_wrong_order then
is_valid <- false
pages_to_left <- pages_to_left.Add(current_page)
is_valid
let solve_part1
(file_name: string)
=
printfn "Day 5.1: Solving"
let input = read_input file_name
let is_in_order =
is_page_in_order input.NumberToValuesAfterIt
let total =
input.Pages
|> Seq.choose (fun page ->
match is_in_order (page |> Seq.toList) with
| true -> Some page
| false -> None
)
|> Seq.map (fun valid_pages ->
valid_pages[valid_pages.Length / 2]
)
|> Seq.sum
printfn "Day 5.1: Total: %A" total
My Answer -> Accepted
Prompt: https://adventofcode.com/2024/day/5
Given:
Ask:
Subproblems:
Solutions:
Code:
type Day5Input2 =
{
NumberToValuesAfterIt: IDictionary<int, int Set>
NumberToValuesBeforeIt: IDictionary<int, int Set>
Pages: int list list
}
let read_input2
(file_name: string)
: Day5Input2
=
let lines =
Utils.read_all_input_file_lines
file_name
let all_x_y_pairs =
lines
|> Seq.filter (fun l -> l.Contains("|"))
|> Seq.map(fun l -> l.Split("|"))
|> Seq.map(fun vals -> (vals[0], vals[1]))
let x_to_vals_after_it =
all_x_y_pairs
|> Seq.groupBy(fun (x, y) -> x)
|> Seq.map (fun (x, groups) ->
let just_ys =
groups
|> Seq.map (fun g -> int (snd g))
|> Seq.toList
|> Set.ofList
((int x), just_ys)
)
|> dict
let y_to_vals_before_it =
all_x_y_pairs
|> Seq.groupBy(fun (x, y) -> y)
|> Seq.map (fun (y, groups) ->
let just_xs =
groups
|> Seq.map (fun g -> int (fst g))
|> Seq.toList
|> Set.ofList
((int y), just_xs)
)
|> dict
let all_pages =
lines
|> Seq.filter(fun l -> l.Contains(","))
|> Seq.map(fun l -> l.Split(","))
|> Seq.map(fun vals ->
vals
|> Seq.map (fun v -> int v)
|> Seq.toList)
|> Seq.toList
{
NumberToValuesAfterIt = x_to_vals_after_it
NumberToValuesBeforeIt = y_to_vals_before_it
Pages = all_pages
}
let create_valid_from_invalid
(input: Day5Input2)
(invalid_pages: int list)
: int list
=
invalid_pages
|> List.sortWith( fun x y ->
if x = y
then 0
else
let x_before_y = (
(match input.NumberToValuesAfterIt.TryGetValue x with
| false, _ -> false
| true, rules ->
rules.Contains(y))
|| (
match input.NumberToValuesBeforeIt.TryGetValue y with
| false, _ -> false
| true, rules ->
rules.Contains(x)
)
)
if x_before_y
then -1
else
let y_before_x = (
(match input.NumberToValuesAfterIt.TryGetValue y with
| false, _ -> false
| true, rules ->
rules.Contains(x))
|| (
match input.NumberToValuesBeforeIt.TryGetValue x with
| false, _ -> false
| true, rules ->
rules.Contains(y)
)
)
if y_before_x
then 1
else
0
)
let is_page_in_order2
(page_to_pages_after_it: IDictionary<int, int Set>)
(pages: int list)
: bool
=
let mutable pages_to_left = Set.ofList [pages[0]]
let mutable is_valid = true
for i = 1 to pages.Length - 1 do
let current_page = pages[i]
match page_to_pages_after_it.TryGetValue current_page with
| false, _ -> ()
| true, pages_that_must_come_later ->
let page_in_wrong_order =
pages_that_must_come_later
|> Seq.exists (fun p ->
pages_to_left.Contains(p)
)
if page_in_wrong_order then
is_valid <- false
pages_to_left <- pages_to_left.Add(current_page)
is_valid
let solve_part2
(file_name: string)
=
printfn "Day 5.2: Solving"
let input = read_input2 file_name
let is_in_order =
is_page_in_order2 input.NumberToValuesAfterIt
let total =
input.Pages
|> Seq.choose (fun page ->
match is_in_order (page |> Seq.toList) with
| true -> None
| false -> Some page
)
|> Seq.map (fun invalid_pages ->
create_valid_from_invalid
input
invalid_pages
)
|> Seq.map (fun valid_pages ->
assert is_in_order valid_pages
valid_pages[valid_pages.Length / 2]
)
|> Seq.sum
printfn "Day 5.2: Total: %A" total
I enjoyed this problem a lot - I haven't run into an IRL situation in awhile where the size of input processing was prohibitively expensive so this was a nice reminder that hey it totally is worth doing the napkin math to make sure you're not doing O(n!) calcs. By the same token though n and n^2 often lead to vastly simpler algorithms and for many data sizes it makes sense to bias simplicity over "optimal". But n! is another matter =)
If you liked this post - please leave a like, comment, and/or share to your network for the algorithm.
You might also like:
The best way to support my work is to like / comment / share for the algorithm and subscribe for future updates.