2025-01-28 10:02:58 -05:00
|
|
|
def String.words (s : String) :=
|
2025-01-28 10:25:57 -05:00
|
|
|
s.split Char.isWhitespace |>.filter (¬·.isEmpty) |>.toArray
|
2025-01-28 10:02:58 -05:00
|
|
|
def String.lines (s : String) :=
|
2025-01-28 10:25:57 -05:00
|
|
|
s.splitOn "\n" |>.filter (¬·.isEmpty) |>.toArray
|
2025-01-28 10:02:58 -05:00
|
|
|
|
2025-01-28 10:25:57 -05:00
|
|
|
-- TODO: remove trailing
|
|
|
|
def Array.unlines (xs : Array String) : String := Id.run do
|
|
|
|
let mut out := ""
|
|
|
|
for line in xs do
|
|
|
|
out := out ++ line ++ "\n"
|
|
|
|
return out
|
2025-01-28 10:02:58 -05:00
|
|
|
|
2025-01-28 10:25:57 -05:00
|
|
|
def String.replicate : Nat → Char → String
|
|
|
|
| 0, _ => ""
|
|
|
|
| n+1, x => replicate n x |>.push x
|
|
|
|
|
|
|
|
def sep (xs space : Array String)
|
|
|
|
: String := Id.run do
|
|
|
|
let mut out := ""
|
|
|
|
let mut i := 0
|
|
|
|
while h : i < xs.size do
|
2025-01-28 11:31:50 -05:00
|
|
|
out := out ++ xs[i]
|
2025-01-28 10:25:57 -05:00
|
|
|
if let some space := space[i]?
|
2025-01-28 11:31:50 -05:00
|
|
|
then out := out ++ space
|
2025-01-28 10:25:57 -05:00
|
|
|
i := i + 1
|
|
|
|
return out
|
|
|
|
|
|
|
|
def justify : Nat → Array String → String
|
|
|
|
| _, #[word] => word
|
2025-01-28 10:02:58 -05:00
|
|
|
| width, words =>
|
2025-01-28 10:25:57 -05:00
|
|
|
let wordsLength := words.foldr (·.length + ·) 0
|
2025-01-28 10:02:58 -05:00
|
|
|
let spacesNeeded := width - wordsLength
|
2025-01-28 10:25:57 -05:00
|
|
|
let numSpaces := words.size - 1
|
2025-01-28 10:02:58 -05:00
|
|
|
let smallSpaceLen := spacesNeeded / numSpaces
|
2025-01-28 10:25:57 -05:00
|
|
|
let smallSpace := String.replicate smallSpaceLen ' '
|
2025-01-28 10:02:58 -05:00
|
|
|
let largeSpaces := spacesNeeded % numSpaces
|
|
|
|
let smallSpaces := numSpaces - largeSpaces
|
2025-01-28 10:25:57 -05:00
|
|
|
let largeSpace := smallSpace.push ' '
|
2025-01-28 11:31:50 -05:00
|
|
|
let spaces := Array.mkArray largeSpaces largeSpace
|
|
|
|
++ Array.mkArray smallSpaces smallSpace
|
2025-01-28 10:25:57 -05:00
|
|
|
sep words spaces
|
2025-01-28 10:02:58 -05:00
|
|
|
|
2025-01-28 10:25:57 -05:00
|
|
|
def breakUp (width : Nat) (words : Array String) : Array (Array String) :=
|
2025-01-28 10:02:58 -05:00
|
|
|
let (_, line, lines) := words.foldl
|
|
|
|
(λ(left, line, lines) word ↦
|
|
|
|
if word.length < left
|
2025-01-28 10:25:57 -05:00
|
|
|
then (left - (word.length + 1), line.push word, lines)
|
|
|
|
else (width - word.length, #[word], lines.push line))
|
|
|
|
(width, #[], #[])
|
|
|
|
lines.push line
|
2025-01-28 10:02:58 -05:00
|
|
|
|
|
|
|
def solve (width : Nat) :=
|
2025-01-28 10:25:57 -05:00
|
|
|
Array.unlines ∘ Array.map (justify width) ∘ breakUp width ∘ String.words
|
2025-01-28 10:02:58 -05:00
|
|
|
|
|
|
|
def usage := "Usage: justify WIDTH"
|
|
|
|
def fail : IO UInt32 := do IO.println usage; return 1
|
|
|
|
|
|
|
|
def main : List String → IO UInt32
|
|
|
|
| [width] =>
|
|
|
|
if let some width := width.toNat?
|
|
|
|
then do
|
|
|
|
let stdin ← IO.getStdin
|
2025-01-28 20:57:29 -05:00
|
|
|
let mut remainder : Array String := #[]
|
2025-01-28 10:02:58 -05:00
|
|
|
while true do
|
|
|
|
let line ← stdin.getLine
|
|
|
|
if line.isEmpty then break
|
2025-01-28 20:57:29 -05:00
|
|
|
let lines := breakUp width (remainder ++ line.words)
|
|
|
|
if h : lines.size > 0 then
|
|
|
|
remainder := lines[lines.size-1]
|
|
|
|
for line in lines[0:lines.size-1] do
|
|
|
|
IO.println $ justify width line
|
|
|
|
IO.println $ String.intercalate " " remainder.toList
|
2025-01-28 10:02:58 -05:00
|
|
|
return 0
|
|
|
|
else fail
|
|
|
|
| _ => fail
|