Zulip Chat Archive
Stream: general
Topic: Advent of Code 2024 - Day 2
James Sully (Dec 03 2024 at 14:56):
Here's my solution. Feedback appreciated, and please post your own solution!
import Utils
abbrev Report := List Nat
abbrev Input := List Report
namespace Day02
def parseNatList (line : String) : Option (List Nat) :=
line.split (Char.isWhitespace)
|>.mapM String.toNat?
def parseInput (s : String) : Option Input :=
s.lines.mapM parseNatList
def reportSafe (report : Report) : Bool :=
let differences : List Int :=
List.zipWith Int.subNatNat report report.tail
differences.all (·.natAbs ≤ 3) &&
(differences.all (· > 0) || differences.all (· < 0))
def part1 : Input → Nat := List.countP reportSafe
def removals (l : List α) : List (List α) :=
List.range l.length
|>.map (l.eraseIdx ·)
def reportSafeDampener (report : Report) : Bool :=
reportSafe report || (removals report).any reportSafe
def part2 := List.countP reportSafeDampener
Ayhon (Dec 03 2024 at 14:59):
Here's my solution for day 2, minus the runner
import Lean.Data.RBMap
import Batteries.Data.List
namespace Day2
abbrev Input := List (List Int)
def parse_input(ls: List String): Input :=
let ls := ls.filter (not ∘ String.isEmpty)
let tokens := ls.map
(·.split Char.isWhitespace |> List.filter (not ∘ String.isEmpty))
tokens.map (·.map (String.toInt!))
namespace Part1
def List.window[Inhabited α](width: Nat)(ls: List α): List (List α) :=
let n := ls.length - width + 1
List.range n
|>.map (fun start =>
List.range width
|>.map (fun i => ls[start + i]!)
)
def valid (ls: List Int): Bool :=
let inc := compare ls[0]! ls[1]!
open Part1 in
ls.window 2
|>.all (fun row =>
/- dbg_trace s!"row={row}" -/
let diff := (row[0]! - row[1]!).natAbs
compare row[0]! row[1]! == inc && 1 <= diff && diff <= 3)
def solve(board: Input): Int := board.filter valid |>.length
end Part1
namespace Part2
def valid (ls: List Int): Bool :=
let n := ls.length
List.range n
|>.any (fun i =>
Part1.valid (ls.eraseIdx i)
)
def solve(board: Input): Int :=
board.filter valid |>.length
end Part2
James Sully (Dec 03 2024 at 15:10):
nice, I bet the general sliding windows function will come in handy
Ayhon (Dec 03 2024 at 15:10):
Yes, I was looking for that functionality in Batteries
. I found List.toChunks
, which seems to almost be what I want, but not quite. Perhaps we could add it to Batteries
? If people would be interested I don't mind researching what's the necessary process to do so.
Edward van de Meent (Dec 03 2024 at 15:13):
Ayhon (Dec 03 2024 at 15:15):
Edward van de Meent said:
That seems to be more like List.toChunks
. I was looking for something that allows me to access each element of a list, together with the next element in the sequence
Ayhon (Dec 03 2024 at 15:16):
splitBy (·==·) [1, 1, 2, 2, 2, 3, 2] = [[1, 1], [2, 2, 2], [3], [2]]
withWindow 2 [1, 1, 2, 2, 2, 3, 2] = [[1, 1], [1, 2], [2, 2], [2, 2], [2, 3], [3, 2]]
withWindow 3 [1, 1, 2, 2, 2, 3, 2] = [[1, 1, 2], [1, 2, 2], [2, 2, 2], [2, 2, 3], [2, 3, 2]]
James Sully (Dec 03 2024 at 15:24):
It's unfortunate List doesn't have a monad instance for perf reasons. I miss list comprehensions and do notation, I find my
def removals (l : List α) : List (List α) :=
List.range l.length
|>.map (l.eraseIdx ·)
much less readable than something like
[l.eraseIdx i | i <- List.range l.length]
Jannis Limperg (Dec 03 2024 at 15:28):
You can add a local instance if you don't mind the questionable performance.
James Sully (Dec 03 2024 at 15:30):
for sure. that would go against my aesthetic sensibilities a little though. I think it's good to work with the standard idioms of a language, rather than fighting it
Mario Carneiro (Dec 03 2024 at 15:31):
it is a functor, so you can use <$>
notation instead of .map
... not sure if that is any better though
James Sully (Dec 03 2024 at 15:33):
yeah
James Sully (Dec 03 2024 at 15:34):
I think one issue is building the list List.range l.length
, rather than iterating over a range [0:l.length]
directly somehow
Mario Carneiro (Dec 03 2024 at 15:35):
you can use List.enum
to get access to the index
James Sully (Dec 03 2024 at 15:37):
I don't think that's quite doing what I want in this case, since I don't want the element at the same time, I just want to delete there
James Sully (Dec 03 2024 at 15:39):
Nat.foldM
is certainly enough power, but probably unwieldy
Mario Carneiro (Dec 03 2024 at 15:41):
if you are going for clear code this is probably fine, if you want maximum performance you probably shouldn't be using lists to begin with
James Sully (Dec 03 2024 at 15:42):
yeah obviously maximum bikeshedding here
Mario Carneiro (Dec 03 2024 at 15:42):
and if you want to prove properties this is probably also the easiest to reason about
James Sully (Dec 03 2024 at 15:44):
would be cool to be able to write [:l.length].map (l.eraseIdx ·)
though
Ayhon (Dec 03 2024 at 16:02):
Idk about implicit conversions, but a .toList
or similar for Std.Range
would be nice. List.range
doesn't handle start and step.
Kyle Miller (Dec 03 2024 at 16:14):
https://leanprover.zulipchat.com/#narrow/channel/113489-new-members/topic/List.20Nat.20versus.20.22List.20Nat.3A.20.20Type.20.28max.20o.20m.2E2332.29.20.3F/near/485505008 has some up-to-date code for list comprehension notation if you want. With it you could write [l.eraseIdx i | for i in List.range l.length]
Kim Morrison (Dec 03 2024 at 22:58):
You could also write window
as
def List.window [Inhabited α] (width: Nat) (ls: List α) : List (List α) :=
if h : ls.length < width then
[]
else
let n := ls.length - width + 1
List.ofFn fun (start : Fin n) =>
List.ofFn fun (i : Fin width) => ls[start.1 + i.1]
Notice there is no runtime bounds check here anymore. This will also make proving easier.
Kim Morrison (Dec 03 2024 at 22:58):
There's probably a good trick to avoid the if
statement.
Kim Morrison (Dec 03 2024 at 23:01):
If you really cared about keeping track of sizes you would define Vector.window : (k : Nat) → Vector n α → Vector (n + 1 - k) (Vector α k)
.
JJ (Jan 03 2025 at 07:10):
wrote some kind of horrifically imperative code for today
def safe (report : List Nat) : Bool :=
(increasing_safely report) || (increasing_safely report.reverse)
where increasing_safely (report : List Nat) : Bool := Id.run do
let mut prev := report.head!
for i in report.tail! do
if (i < prev + 1) || (prev + 3 < i) then
return false
else
prev <- i
true
def dampen (report : List Nat) : List (List Nat) := Id.run do
let mut (head, tail, result) := ([], report, [report])
for elem in report do
tail <- tail.tail
result <- result.concat (head.append tail)
head <- head.concat elem
result
def solution (input : String) : String := Id.run do
let reports :=
input.stripSuffix "\n" |>.splitOn "\n"
|>.map (·.splitOn |>.map String.toNat!)
let mut (p1, p2) := (0, 0)
for report in reports do
p1 <- p1 + (if safe report then 1 else 0)
p2 <- p2 + (if (dampen report).any safe then 1 else 0)
s!"{p1}, {p2}"
Patrick Massot (Jan 03 2025 at 21:26):
There is nothing horrific with Lean code written in imperative style using do
. Lean itself is mostly written like this.
Kyle Miller (Jan 03 2025 at 21:34):
Here's a bit of manual pretty printing on my part. Note that the performance of List.concat
is linear. If you are always concatenating, it's probably better to use Array, which has (amortized) constant-time pushing to the end.
def safe (report : Array Nat) : Bool :=
increasing_safely report || increasing_safely report.reverse
where
increasing_safely (report : Array Nat) : Bool := Id.run do
let mut prev := report[0]!
for i in report[1:] do
if i < prev + 1 || prev + 3 < i then
return false
else
prev <- i
true
def dampen (report : Array Nat) : Array (Array Nat) := Id.run do
let mut head := #[]
let mut result := #[report]
for h : i in [0:report.size] do
let elem := report[i]
result <- result.push (head ++ report[i:].toArray)
head <- head.push elem
result
def solution (input : String) : String := Id.run do
let reports :=
input.stripSuffix "\n" |>.splitOn "\n"
|>.map (·.splitOn |>.map String.toNat! |>.toArray)
let mut p1 := 0
let mut p2 := 0
for report in reports do
if safe report then
p1 <- p1 + 1
if (dampen report).any safe then
p2 <- p2 + 1
s!"{p1}, {p2}"
(This is completely untested! I'm just editing directly in Zulip.)
Last updated: May 02 2025 at 03:31 UTC