Zulip Chat Archive
Stream: lean4
Topic: List from user input
Florent Schaffhauser (Dec 21 2023 at 07:50):
Hi!
The following function asks the user to input some numbers in the IO unit, then it constructs a list from it:
def main : IO Unit := do
let stdin ← IO.getStdin
let stdout ← IO.getStdout
stdout.putStrLn "Please enter a list of integers in the form 1, -2, 3 (as many integers as you want, seperated by a comma)."
let input ← stdin.getLine
let list := input.splitOn "," |> List.map (fun s => s.trim.toInt!)
stdout.putStrLn s!"The list you entered is {list}."
However, I am unable to make the same thing works with rationals in place of integers.
I tried to write the following function outside of main
, to help parse rational numbers:
def parseRational (s : String) : Option ℚ :=
match s.splitOn "/" with
| [num, denom] =>
let denomNat := denom.toNat!
if denomNat = 0 then none
else some (Rat.mk' (num.toInt!) denomNat _ _)
| _ => none
Am I on the right track? Any pointers on how to proceed?
A much minor issue is that, in the initial function, I would also like to be able to ask the user to input a list under the form [1, -2, 3]
, as opposed to 1, -2, 3
, but I am having a hard time getting rid of the brackets when parsing the user input (I thought I could do it, but then it also suppressed the entry right next to it...). Is there a standard way to do that? I am also happy to read from a reference if somebody can point me to one :-)
Kyle Miller (Dec 21 2023 at 15:44):
For creating the rational, I'm not sure docs#Rat.mk' should be considered a public interface. You can instead do (num.toInt! / denomNat : ℚ)
, which should cause both the numerator and denominator to be coerced to a rational and then their quotient is taken in the rationals.
Kyle Miller (Dec 21 2023 at 16:06):
Here's an example of using Parsec for parsing your square bracket input. I had to write a combinator for doing comma-separated lists, since there's not much in the core Parsec library already. I also didn't write these definitions as tersely as possible.
import Lean
open Lean (Parsec)
open Lean.Parsec
def parseNat : Parsec Nat := do
let num ← many1Chars digit
return num.toNat!
def parsePosInt : Parsec Int := do
let n ← parseNat
return n
def parseNegInt : Parsec Int := do
skipChar '-'
let n ← parseNat
return -n
def parseInt : Parsec Int := parsePosInt <|> parseNegInt
/--
Parses a comma-separated list of items parsed by `p`.
The commas can have whitespace surrounding them.
-/
partial def parseCommaSep {α : Type} (p : Parsec α) : Parsec (Array α) := do
go #[]
where
go (acc : Array α) : Parsec (Array α) := do
let acc := acc.push (← p)
ws
if (← peek?) == ',' then
skip
ws
go acc
else
return acc
def parseIntArray : Parsec (Array Int) := do
skipChar '['
ws
let arr ← parseCommaSep parseInt
skipChar ']'
return arr
-- Example:
#eval parseIntArray.run "[1,2,-3]"
/- Except.ok #[1, 2, -3] -/
/--
I split this out of `main` to be able to test it with `#eval`.
-/
def processLine (input : String) : IO Unit := do
-- Uses `eof` to guarantee the whole input is read.
let list ← IO.ofExcept <| (parseIntArray <* eof).run input.trim
IO.println s!"The list you entered is {list}."
#eval processLine "[1, -2, 3]"
/-
The list you entered is #[1, -2, 3].
-/
def main : IO Unit := do
let stdin ← IO.getStdin
IO.println "Please enter a list of integers in the form [1, -2, 3] (as many integers as you want, seperated by commas)."
let input ← stdin.getLine
processLine input
Kyle Miller (Dec 21 2023 at 16:11):
Here's a version with rational number parsing:
import Lean
open Lean (Parsec Rat)
open Lean.Parsec
def parseNat : Parsec Nat := do
let num ← many1Chars digit
return num.toNat!
def parsePosInt : Parsec Int := do
let n ← parseNat
return n
def parseNegInt : Parsec Int := do
skipChar '-'
let n ← parseNat
return -n
def parseInt : Parsec Int := parsePosInt <|> parseNegInt
def parseRat : Parsec Rat := do
let num ← parseInt
if (← peek?) == '/' then
skip
let denom ← parseNat
if denom == 0 then
fail "Denominator is 0"
return num / denom
else
return num
/--
Parses a comma-separated list of items parsed by `p`.
The commas can have whitespace surrounding them.
-/
partial def parseCommaSep {α : Type} (p : Parsec α) : Parsec (Array α) := do
go #[]
where
go (acc : Array α) : Parsec (Array α) := do
let acc := acc.push (← p)
ws
if (← peek?) == ',' then
skip
ws
go acc
else
return acc
def parseRatArray : Parsec (Array Rat) := do
skipChar '['
ws
let arr ← parseCommaSep parseRat
skipChar ']'
return arr
-- Example:
#eval toString <| parseRatArray.run "[1,2/3,-3]"
/- "ok: #[1, 2/3, -3]" -/
/--
I split this out of `main` to be able to test it with `#eval`.
-/
def processLine (input : String) : IO Unit := do
-- Uses `eof` to guarantee the whole input is read.
let list ← IO.ofExcept <| (parseRatArray <* eof).run input.trim
IO.println s!"The list you entered is {list}."
#eval processLine "[1, -2/3, 3]"
/-
The list you entered is #[1, -2/3, 3].
-/
def main : IO Unit := do
let stdin ← IO.getStdin
IO.println "Please enter a list of integers in the form [1, -2, 3] (as many integers as you want, seperated by commas)."
let input ← stdin.getLine
processLine input
Florent Schaffhauser (Dec 29 2023 at 09:32):
Wow, that's fantastic, @Kyle Miller , thanks! It will take me time to digest all of this, but I'm very grateful :pray:
Something that was not working for me in my initial code was the input of the empty list, the was not being recognized. As far as I can tell, the above code has the same issue, in spite of the brackets. I will try to fix this :sweat_smile: Thanks again!
Kyle Miller (Dec 29 2023 at 16:20):
@Flo (Florent Schaffhauser) Here seems to be a way to fix the logic so it handles empty lists:
partial def parseCommaSep {α : Type} (p : Parsec α) : Parsec (Array α) :=
(do go #[← p]) <|> pure #[]
where
go (acc : Array α) : Parsec (Array α) := do
ws
if (← peek?) == ',' then
skip
ws
go (acc.push (← p))
else
return acc
The idea is
- see if
p
parses, and if not that means we must be looking at an empty list - if
p
does parse, then we want to look for a sequence of, p
Florent Schaffhauser (Jun 05 2024 at 07:07):
Thanks @Kyle Miller :blush: I lost track of this issue somewhere along the way, but now I'm motivated to try again :+1:
Last updated: May 02 2025 at 03:31 UTC