Zulip Chat Archive
Stream: general
Topic: Code Reviews
Shubham Kumar 🦀 (he/him) (Dec 12 2023 at 09:41):
I was looking for a topic related to writing code looking/idiomatic code in lean4, is it ok if I create a new one?
The following code is for AoC Day1 I wasn't sure if I should put in https://leanprover.zulipchat.com/#narrow/stream/113488-general/topic/Advent.20of.20Code.3F if it is I'll close this topic
def dedup [BEq α] (lst : List α) : List α :=
List.foldl (fun acc x =>
match acc with
| [] => x :: acc
| hd :: _ => if hd == x then acc else x :: acc
) [] lst
|> List.reverse
def CharToString (ch : Char) : String :=
String.str "" ch
def removeEmptyStrings (lst : List String) : List String :=
List.filter (fun s => s != "") lst
def splitAtDigitsHelper
(state : String × List String)
(ch : Char) : String × List String :=
let (word, lst) := state
if Char.isDigit ch then
-- if character digit found change word to empty string
-- append character as a string to the list
-- append it before the word that is generated
("", List.append lst [ word, CharToString ch ])
else
-- if character not a digit keep appending character to the word
(String.append word (CharToString ch), lst)
def splitAtDigits (s : String) : List String :=
let initState := ("", [])
String.foldl splitAtDigitsHelper initState s
|> fun (first, lst) => List.append lst [first]
|> removeEmptyStrings
def isStringDigit (s : String) : Bool :=
match String.toNat? s with
| none => false
| some _ => true
def filterListStringDigit (lst : List String) : List String :=
List.filter isStringDigit lst
def eliminateAllExceptFirstLast (lst : List String) : List String :=
let first := List.head? lst
let last := List.getLast? lst
match first, last with
| none, none => []
| some first, some last => [first, last]
| _, _ => []
def ListToNat (n : Nat) (lst : List String) : Nat :=
let StringToNat s :=
match String.toNat? s with
| none => 0
| some n => n
match lst with
| [] => n
| hd :: tl => ListToNat (10 * n + StringToNat hd) tl
def getNumbers (lst : List String) : List Nat :=
List.map (fun s =>
splitAtDigits s |>
filterListStringDigit |>
eliminateAllExceptFirstLast |>
(ListToNat 0)) lst
def toSum (lst : List Nat) : Nat :=
List.foldl (fun acc n => acc + n) 0 lst
def WordToNumber s :=
match s with
| "one" => "1"
| "two" => "2"
| "three" => "3"
| "four" => "4"
| "five" => "5"
| "six" => "6"
| "seven" => "7"
| "eight" => "8"
| "nine" => "9"
| _ => s
def firstPass (listOfList : List (List String)) : List (List String) :=
List.map (List.map WordToNumber) listOfList
def substringExtract (start : Nat) (finish : Nat) (s : String) : String :=
let start := String.Pos.mk start
let finish := String.Pos.mk finish
let substring := String.toSubstring s
Substring.extract substring start finish
|> Substring.toString
def getRest (at_ : Nat) (s : String) : String :=
let at_ := String.Pos.mk at_
let last := String.Pos.mk (String.length s)
let substring := String.toSubstring s
Substring.extract substring at_ last
|> Substring.toString
def getWord (s : String) (startIndex : Nat) (endIndex : Nat) : String :=
substringExtract startIndex endIndex s
def getNDigitWord (s : String) (index : Nat) (N : Nat) : String :=
getWord s index (index + N) |> WordToNumber
def getNDigitIndexedWords (s : String) (N : Nat) : List (Nat × String) :=
List.range (String.length s - 1)
|> List.map (fun index => (index, getNDigitWord s index N))
|> List.filter (fun (_, word) => isStringDigit word)
def get3DigitIndexWords (s : String) : List (Nat × String) :=
getNDigitIndexedWords s 3
def get4DigitIndexWords (s : String) : List (Nat × String) :=
getNDigitIndexedWords s 4
def get5DigitIndexWords (s : String) : List (Nat × String) :=
getNDigitIndexedWords s 5
def concatDigitsInOrder (s : String) : List String :=
let indexedThreeDigitWords := get3DigitIndexWords s
let indexedFourDigitWords := get4DigitIndexWords s
let indexedFiveDigitWords := get5DigitIndexWords s
let indexedDigitWords := indexedThreeDigitWords ++ indexedFourDigitWords ++ indexedFiveDigitWords
List.toArray indexedDigitWords
|> flip Array.qsort (fun (a, _) (b, _) => a < b)
|> Array.toList
|> List.map Prod.snd
def getFirstAndLastOfList? (lst : List α) : Option (α × α) :=
match List.head? lst, List.getLast? lst with
| some x, some y => some (x, y)
| _, _ => none
def secondPass (listOfList : List (List String)) : Nat :=
let f acc digit :=
if isStringDigit digit then
acc ++ [digit]
else
acc ++ concatDigitsInOrder digit |> dedup
let sum acc x :=
let first := String.toNat? (Prod.fst x)
let second := String.toNat? (Prod.snd x)
match first, second with
| some x, some y => acc + (10 * x + y)
| _, _ => acc
listOfList
|> List.map (List.foldl f [])
|> List.filterMap getFirstAndLastOfList?
|> List.foldl sum 0
def day1Part1Fn (path : String) : IO Nat := do
let input <- IO.FS.readFile path
pure (String.splitOn input "\n" |> getNumbers |> toSum)
def day1Part2Fn (path : String) : IO Nat := do
let input <- IO.FS.readFile path
String.splitOn input "\n"
|> List.map splitAtDigits
|> firstPass
|> secondPass
|> pure
Jannis Limperg (Dec 12 2023 at 11:19):
Some suggestions:
- You can use dot notation much more aggressively. For example,
List.filter ... lst
can be written aslst.filter ...
, and similar throughout. ListToNat
andWordToNumber
should belistToNat
andwordToNumber
. Only types are capitalised.- Chains of list transformations, e.g. in
secondPass
, are not as efficient in Lean as they are in Haskell because Lean doesn't optimise away the intermediate lists. Also, arrays are just generally faster in most cases. Probably doesn't matter for this problem, but something to keep in mind.
Shubham Kumar 🦀 (he/him) (Dec 12 2023 at 11:48):
Jannis Limperg said:
Some suggestions:
- You can use dot notation much more aggressively. For example,
List.filter ... lst
can be written aslst.filter ...
, and similar throughout.ListToNat
andWordToNumber
should belistToNat
andwordToNumber
. Only types are capitalised.- Chains of list transformations, e.g. in
secondPass
, are not as efficient in Lean as they are in Haskell because Lean doesn't optimise away the intermediate lists. Also, arrays are just generally faster in most cases. Probably doesn't matter for this problem, but something to keep in mind.
Thanks! I forgot that I've skipped on camel case in those 2 functions
What is the best alternative for writing chain transformations in Lean? Any specifc style that you can suggest?
ohhaimark (Dec 12 2023 at 11:52):
firstPass
and List.map splitAtDigits
could be fused I think.
... |>.map (g ∘ f)
instead of ... |>.map f |>.map g
Shubham Kumar 🦀 (he/him) (Dec 12 2023 at 11:56):
Makes sense, to avoid list unboxing 2 times right?
Thanks for the suggestion :D
ohhaimark said:
firstPass
andList.map splitAtDigits
could be fused I think.
... |>.map (g ∘ f)
instead of... |>.map f |>.map g
Jannis Limperg (Dec 12 2023 at 12:22):
Yeah, hand-fusing the transformations basically. It's ugly but more efficient. You could also explore the monadic for
loops (which can be used in pure code with Id.run do
, where Id
is the identity monad). They let you write your folds like in C, which is arguably more natural for complicated folds.
Kyle Miller (Dec 12 2023 at 17:01):
@Shubham Kumar 🦀 (he/him) Is dedup
just supposed to remove consecutive duplicates, or all duplicates? I noticed #eval dedup [1,2,3,1]
returns [1,2,3,1]
Kyle Miller (Dec 12 2023 at 17:34):
Made some changes and added comments where I did.
comments
Shubham Kumar 🦀 (he/him) (Dec 13 2023 at 07:49):
Kyle Miller said:
Shubham Kumar 🦀 (he/him) Is
dedup
just supposed to remove consecutive duplicates, or all duplicates? I noticed#eval dedup [1,2,3,1]
returns[1,2,3,1]
no I think you caught an error in my logic, I wanted to remove all the duplicates.
Also thanks for the detailed review for the next problem I'll try to include all of them :pray:
Shubham Kumar 🦀 (he/him) (Dec 16 2023 at 12:52):
Is there a better way by using dot notation to write the following function?
let toBlank (str : String) := str.map (fun ch =>
match ch with
| ':' => ' '
| ',' => ' '
| ';' => ' '
| _ => ch
)
Shubham Kumar 🦀 (he/him) (Dec 16 2023 at 13:12):
Also is there a way to use filterMap on Strings, using Substring or String.Iterator? Because essentially I want to filterMap a string but strings are immutable (I'm guessing) so changing in size would not work with the constraint that length remains constant (again guessing)
Alex J. Best (Dec 16 2023 at 13:16):
Not dot notation but you can save your fingers some typing with
let toBlank (str : String) := str.map (
fun
| ':' | ',' | ';' => ' '
| _ => ch
)
Shubham Kumar 🦀 (he/him) (Dec 16 2023 at 13:17):
Alex J. Best said:
Not dot notation but you can save your fingers some typing with
let toBlank (str : String) := str.map ( fun | ':' | ',' | ';' => ' ' | _ => ch )
Thanks @Alex J. Best :pray:
Joachim Breitner (Dec 16 2023 at 13:18):
Did you mean
let toBlank (str : String) := str.map (
fun
| ':' | ',' | ';' => ' '
| ch => ch
)
Also maybe these ugly parentheses aren't needed, didn't check.
Yaël Dillies (Dec 16 2023 at 13:18):
You can use dot notation as such
let toBlank : String := .map fun
| ':' | ',' | ';' => ' '
| ch => ch
Shubham Kumar 🦀 (he/him) (Dec 16 2023 at 13:20):
Yaël Dillies said:
You can use dot notation as such
let toBlank : String := .map fun | ':' | ',' | ';' => ' ' | ch => ch
Thanks @Yaël Dillies !
Shubham Kumar 🦀 (he/him) (Dec 16 2023 at 13:24):
So @Yaël Dillies I am getting an error if I constraint the output type
type mismatch
String.map fun x =>
match x with
| Char.ofNat 58 => Char.ofNat 32
| Char.ofNat 44 => Char.ofNat 32
| Char.ofNat 59 => Char.ofNat 32
| ch => ch
has type
String → String : Type
but is expected to have type
String : Type Lean 4
Yaël Dillies (Dec 16 2023 at 13:25):
Ah sorry, it should of course be String → String
, not String
.
Shubham Kumar 🦀 (he/him) (Dec 16 2023 at 13:25):
Yeah that makes sense! sorry my mistake :smile:
Yaël Dillies (Dec 16 2023 at 13:25):
So either
let toBlank : String → String := .map fun
| ':' | ',' | ';' => ' '
| ch => ch
or
let toBlank := String.map fun
| ':' | ',' | ';' => ' '
| ch => ch
Shubham Kumar 🦀 (he/him) (Dec 16 2023 at 14:07):
so I had a question I was trying to use dot notation for the following function
def filterNoise lst :=
let filterChars := fun
| ';' | ':' | ',' => none
| ch => some ch
let filterString (str : String) : String :=
let charList : List Char :=
str.toList |>.filterMap filterChars
charList.foldl (init := "") String.push
-- instead of using lst.map filterString
List.map filterString lst
But using lst.map filterString
results in the following error
invalid field notation, type is not of the form (C ...) where C is a constant
lst
has type
?m.6 Lean 4
Shubham Kumar 🦀 (he/him) (Dec 16 2023 at 14:07):
why is that? Semantically they should be the same right?
Yaël Dillies (Dec 16 2023 at 14:09):
This just tells you that Lean doesn't know what the type of lst
is, hence it can't look up any dot notation!
Yaël Dillies (Dec 16 2023 at 14:09):
def filterNoise (lst : List _) :=
should work.
Shubham Kumar 🦀 (he/him) (Dec 16 2023 at 14:09):
ah ok, I didn't understand the error. Thanks @Yaël Dillies again :pray:
Last updated: Dec 20 2023 at 11:08 UTC