Read part one here. Read part two here.
In the last part, I left here:
All that remains now is getting
ValidLine
s out of the given array of words.
I just got around to writing the function that converts an array of strings into an array of valid lines that I can then pass/process through the validLineToParaLine
function I wrote in part two.
validLineToParaLine :: Int -> ValidLine -> String
validLineToParaLine maxLen (ValidLine xs) =
specialIntercalate xs (makeSpaces maxLen (ValidLine xs))
# Str.joinWith ""
Here's how I approached the validline generation. I was going to rely on an accumulating recursive function (very common in functional/recursive programs):
- The function I'm going to write will keep track of a current valid line, a final array of valid lines, and the list of words to process.
- As a base case, if the list of words to process is empty, it's simply going to concatenate the final array of valid lines and the current valid line and return the whole thing. That will be my final valid line list!
- If the list of words to process is not empty:
- I'm going to pick the first element from the words list
- and then I'm going to add it temporarily to the current valid line that the function is carrying around
- and check if the total length of this temporary valid line is less than the max-length
- if yes, I will recurse again on the function, passing as the list of words to process the tail of the list (because I've already picked out the first element)
- if not, I will just add whatever's current valid line to the final valid lines list, and recurse on the function, passing in existing list of words (because I did not use the first element) and also emptying out the current valid line because a new valid line is going to form.
In code:
listToValidLines :: Int -> Array String -> Array ValidLine
listToValidLines maxlen xs = helper (ValidLine []) [] xs
where
helper :: ValidLine -> Array ValidLine -> Array String -> Array ValidLine
helper (ValidLine acc) final [] = snoc final (ValidLine acc)
helper (ValidLine acc) final ys =
case head ys of
Maybe.Nothing -> helper (ValidLine acc) final (Maybe.fromMaybe [] $ tail ys)
Maybe.Just wrd ->
let
tempValidLine = snoc acc wrd
lengthTempValidLine = totalCharLength $ ValidLine tempValidLine
in
if lengthTempValidLine > maxlen
then helper (ValidLine []) (snoc final (ValidLine acc)) ys
else helper (ValidLine tempValidLine) final (Maybe.fromMaybe [] $ tail ys)
There's a bit of a Maybe
wrangling because I'm using head
and tail
, but it's OK. The code is safer.
Now that I have a function that converts an array of words to an array of valid lines, I can simply map over this list to generate a list of justified lines!
justify :: Int -> Array String -> Array String
justify maxWidth = listToValidLines maxWidth >>> map (validLineToParaLine maxWidth)
maxWidth
is the same as max length of a line.
I could've written it this way too:
justify :: Int -> Array String -> Array String
justify maxWidth xs = listToValidLines maxWidth xs # map (validLineToParaLine maxWidth)
Time to test:
> justify 16 ["This", "is", "an", "example", "of", "text", "justification", "folks,", "okay?"]
["This----is----an","example--of-text","justification","folks,-----okay?"]
We can join this text with "\n" to get a paragraph:
justify 16 ["This", "is", "an", "example", "of", "text", "justification", "folks,", "okay?"] # joinWith "\n"
This----is----an
example--of-text
justification
folks,-----okay?
The last rule in the puzzle was:
the last line can be left-aligned so just one space between the words is fine.
I think there are a couple of ways to accomplish this.
I could have used an indexed map in justify
function to not justify the last item in the array of valid lines.
Or I could simply use regex to replace all multilpe spaces (ie, one or more spaces) with just one space in the last line.
Those are trivial, so leaving it here for now.
You can play around with the full-code here.