Zulip Chat Archive

Stream: general

Topic: Advent of Code 2023


Gareth Ma (Dec 01 2023 at 11:14):

I wrote a program that should solve AoC2023 Day 1, but it is way too slow. Any suggestions on how to optimise part 2? I don't even know what's slow about this. It took 16 seconds to run on twovgtprdzcjjzkq3ffsbcblnpq and there are 1000 lines in the input file, and I would like to get it to under a minute :) Is it because I am using recursive definitions?

import Mathlib.Tactic

open String Tactic

def parseDigit (c : Char) :  := if '0'  c  c  '9' then c.val.toNat - 0x30 else 0

def parseNatAux : List Char  
| [] => 0
| x :: xs => (parseNatAux xs) * 10 + parseDigit x

def parseNat : String   := fun s  parseNatAux (s.toList.reverse)

def getInput : IO (Array String) := do
  let dir := "<cwd>"
  return IO.FS.lines (dir ++ "/in")

def words : List String := [
  "zero", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
  "0", "1", "2", "3", "4", "5", "6", "7", "8", "9"
]

def List.getSuffix : List α  List (List α)
| [] => [[]]
| x :: xs => (x :: xs) :: getSuffix xs

def String.getSuffix (s : String) : List String := (List.getSuffix s.toList).map List.asString

def List.enumerate (l : List α) : List ( × α) := (List.range l.length).zip l

def List.IsPrefix' : List α  List α  Prop
| [], _ => True
| _, [] => False
| (x::xs), (y::ys) => (x = y)  List.IsPrefix' xs ys

def String.IsPrefix' : String  String  Prop
  | d1⟩, d2 => List.IsPrefix' d1 d2

instance [DecidableEq α] {s t : List α} : Decidable (List.IsPrefix' s t) := by
  induction' s with s s' hs generalizing t
  <;> induction' t with t t' _
  <;> try simp only [IsPrefix', List.IsPrefix']
  · exact decidableTrue
  · exact decidableTrue
  · exact decidableFalse
  · apply And.decidable (dq := ?_)
    exact hs

instance {s t : String} : Decidable (IsPrefix' s t) := by unfold IsPrefix' ; infer_instance

def part1 : IO  := do
  let mut res := 0
  for line in getInput do
    let chars := line.toList.filter Char.isDigit
    let val := [chars.head!, chars.getLast!].asString
    res := res + parseNat val
  return res

def part2 : IO  := do
  let mut res := 0
  let input  getInput
  let words' := List.enumerate words
  for line in input.toList.take 1 do
    let lineSuffix' := List.enumerate (getSuffix line)
    let mut tmp : List ( × ) := []
    for (i, suf) in lineSuffix' do
      for (j, num) in words' do
        if num.IsPrefix' suf then
          tmp := (i, j % 10) :: tmp
    res := res + tmp.getLast!.snd * 10 + tmp.head!.snd
  return res

set_option profiler true
set_option trace.profiler true

/- #eval part1 -/
#eval part2
/- #print part2 -/

Gareth Ma (Dec 01 2023 at 11:15):

Also if any of these are in Lean / Mathlib already that will be nice. In particular, String.IsSuffix exists but they write it with an \exists which isn't decidable.

Mauricio Collares (Dec 01 2023 at 11:17):

I wonder if we should have a separate stream for AoC, if only to avoid spoilers.

Eric Wieser (Dec 01 2023 at 11:17):

Compiling it to C will likely make it faster

Gareth Ma (Dec 01 2023 at 11:17):

I have waited 6 hours before posting, but if creating a new thread is possible that'll be great too :)

Gareth Ma (Dec 01 2023 at 11:19):

Eric Wieser said:

Compiling it to C will likely make it faster

How can I do that?

Eric Wieser (Dec 01 2023 at 12:06):

You should be able to add it as an exe in the lakefile

Damiano Testa (Dec 01 2023 at 13:03):

Gareth, I did not look carefully at your code, but

  • docs#String.isPrefixOf exists and is likely more optimised than yours;
  • I reversed the strings and used the same function twice (and reversed the English words, of course!) -- not sure about the net effect of reversal vs scanning from the back.

And this is the running time of my solution:

$ time lake env lean Advents/day01.lean

[Part 1 answer]
[Part 2 answer]

real    0m0,725s
user    0m0,635s
sys 0m0,090s

Damiano Testa (Dec 01 2023 at 13:04):

(My project only depend on Std, in case it matters.)

Damiano Testa (Dec 01 2023 at 13:05):

Mauricio Collares said:

I wonder if we should have a separate stream for AoC, if only to avoid spoilers.

I like the idea of a new thread: waiting some time may not be enough, due to timezones/other commitments that someone might have! :smile:

Damiano Testa (Dec 01 2023 at 13:13):

Header

Gareth Ma (Dec 01 2023 at 14:03):

Thanks for the comments, I rewrote it based on your code and Arthur's (from another server) and got it down to 225ms :)

Jujian Zhang (Dec 02 2023 at 14:20):

there is also a simpler solution just by pattern matching

pattern-matching

Shubham Kumar 🦀 (he/him) (Dec 05 2023 at 05:49):

I had a question, while I was trying to run day1 I ran into a type inference problem
So my day1.lean file looks like this

def isDigit (ch : Char) : Bool := Char.isDigit ch

def CharToNat (ch : Char) : Nat :=
  Char.toNat ch - Char.toNat ' ' - 16

def filterNumbers (s : String) : List Nat :=
  String.toList s
  |> List.filter isDigit
  |> List.map CharToNat

but when I try to print the list (my Main.lean)

import Aoc2023

def numbers :=
  List.map filterNumbers ["1abc2", "pqr3stu8vwx", "a1b2c3d4e5f", "treb7uchet"]

def main : IO Unit :=
  let n := List.map List.head numbers
  IO.println s!"{n}"

The type infered for filterNumbers is String -> String instead of String -> List Nat

What am I doing wrong?

Kyle Miller (Dec 05 2023 at 05:53):

Where are you seeing String -> String?

I'm seeing that filterNumbers is String -> List Nat and numbers is List (List Nat)

Kyle Miller (Dec 05 2023 at 05:55):

Maybe you're looking for List.head! instead of List.head?

This fixes the type error: let n := List.map List.head! numbers

Shubham Kumar 🦀 (he/him) (Dec 05 2023 at 05:57):

so lake build gives the following error

 lake build
Building Main
error: > LEAN_PATH=./build/lib LD_LIBRARY_PATH=./build/lib /home/sk/.elan/toolchains/leanprover--lean4---nightly-2022-10-18/bin/lean ./././Main.lean -R ././. -o ./build/lib/Main.olean -i ./build/lib/Main.ilean -c ./build/ir/Main.c
error: stdout:
./././Main.lean:8:13: error: typeclass instance problem is stuck, it is often due to metavariables
  ToString (List ?m.64)
error: external command `/home/sk/.elan/toolchains/leanprover--lean4---nightly-2022-10-18/bin/lean` exited with code 1

but the lsp is showing the type signature in numbers as String -> String I cannot screenshot it because as soon as I try to screenshot it vanishes

Shubham Kumar 🦀 (he/him) (Dec 05 2023 at 05:57):

I'll try List.head!

Shubham Kumar 🦀 (he/him) (Dec 05 2023 at 05:59):

The lsp shows a problem even though the lake build commands compiles it without any issues
Screenshot-from-2023-12-05-11-28-24.png

Kyle Miller (Dec 05 2023 at 05:59):

I'm not sure how that can be -- have you pasted exactly the code you have?

Shubham Kumar 🦀 (he/him) (Dec 05 2023 at 05:59):

 lake build
Building Main
Compiling Main
Linking aoc_2023

aoc_2023_lean on  main [?] on   (ap-southeast-2) ./build/bin/aoc_2023
[1, 3, 1, 7]

Gareth Ma (Dec 05 2023 at 06:00):

Have you tried restarting the lsp or just reopening the file

Shubham Kumar 🦀 (he/him) (Dec 05 2023 at 06:00):

I'm trying to keep it all in a structure
Screenshot-from-2023-12-05-11-30-08.png

Shubham Kumar 🦀 (he/him) (Dec 05 2023 at 06:01):

I'll try restarting the lsp

Gareth Ma said:

Have you tried restarting the lsp or just reopening the file

Shubham Kumar 🦀 (he/him) (Dec 05 2023 at 06:02):

Yeah restarting the lsp fixed it, sorry

Kyle Miller (Dec 05 2023 at 06:03):

A useful command is "restart file" too. It reloads all the dependencies of a file. Maybe you had an older version of filterNumbers still loaded in Main.lean?

Kyle Miller (Dec 05 2023 at 06:03):

(on Mac, the keybinding is Cmd-shift-x)

Shubham Kumar 🦀 (he/him) (Dec 05 2023 at 10:02):

Thanks!

Shubham Kumar 🦀 (he/him) (Dec 05 2023 at 11:09):

is there a way to convert List (IO Nat) -> IO Unit?
right now I have the following code

import Aoc2023

def getNumbers lst :=
  List.map filterNumbers lst
  |> List.filter (fun lst => not (List.isEmpty lst))
  |> List.map (fun lst => List.head! lst * 10 + List.getLast! lst)
  |> List.foldr (fun n acc => acc + n) 0

def fileToNumbers (path : String) : IO Nat := do
  let input <- IO.FS.readFile path
  pure (String.splitOn input "\n" |> getNumbers)

def paths := [
 "/home/sk/aoc_2023_lean/Aoc2023/input_day1_part1.txt"
]

def main : IO Unit := do
  let result := List.map fileToNumbers paths

and I want to print result

Mario Carneiro (Dec 05 2023 at 11:11):

maybe use List.mapM instead to get a List Nat and then print that instead?

Shubham Kumar 🦀 (he/him) (Dec 05 2023 at 11:12):

oh is there a List.mapM I didn't know, thanks @Mario Carneiro !

Shubham Kumar 🦀 (he/him) (Dec 06 2023 at 14:21):

Hi I was having some issues with pattern matching Substring but the my question is tangential to that error.
The following error message states

 lake build
Building Aoc2023.Day1
error: > LEAN_PATH=./build/lib LD_LIBRARY_PATH=./build/lib /home/sk/.elan/toolchains/leanprover--lean4---nightly-2022-10-18/bin/lean ./././Aoc2023/Day1.lean -R ././. -o ./build/lib/Aoc2023/Day1.olean -i ./build/lib/Aoc2023/Day1.ilean -c ./build/ir/Aoc2023/Day1.c
error: stdout:
./././Aoc2023/Day1.lean:42:2: error: failed to compile pattern matching, stuck at
  remaining variables: [x✝:(String)]
  alternatives:
    [] |- ["one"] => h_1 ()
    [] |- ["two"] => h_2 ()
    [] |- ["six"] => h_3 ()
  examples:_

I want to know what [] |- ["one"] => h_1() means

Shubham Kumar 🦀 (he/him) (Dec 06 2023 at 14:21):

and the other pattern matches that are also mentioned

Notification Bot (Dec 07 2023 at 08:06):

2 messages were moved here from #general > Advent of Code? by Shubham Kumar 🦀 (he/him).


Last updated: Dec 20 2023 at 11:08 UTC