Justifying a paragraph of text in Purescript: Part 3

druchan

druchan

Posted on July 18, 2023

Justifying a paragraph of text in Purescript: Part 3

In the last part, I left here:

All that remains now is getting ValidLines 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 ""
Enter fullscreen mode Exit fullscreen mode

Here's how I approached the validline generation. I was going to rely on an accumulating recursive function (very common in functional/recursive programs):

  1. The function I'm going to write will keep track of a current valid line, the final array of valid lines, and the list of words to process.
  2. 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!
  3. 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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Time to test:

> justify 16 ["This", "is", "an", "example", "of", "text", "justification", "folks,", "okay?"]
["This----is----an","example--of-text","justification","folks,-----okay?"]
Enter fullscreen mode Exit fullscreen mode

We can join this text with "\n" to get a paragraph:

justify 16 ["This", "is", "an", "example", "of", "text", "justification", "folks,", "okay?"] # joinWith "\n"
Enter fullscreen mode Exit fullscreen mode
This----is----an
example--of-text
justification
folks,-----okay?
Enter fullscreen mode Exit fullscreen mode

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.

💖 💪 🙅 🚩
druchan
druchan

Posted on July 18, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related