Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell - Capitalize the first letter of each word in a string without losing white spaces

Tags:

haskell

I'm doing an exercise that requires me to write a function that capitalize all the first letters of the words of a string.

Here is what I did so far:

upperFirst:: String -> String
upperFirst str =  let 
                      upperFirstForEachWord (firstLetter:others) = toUpper firstLetter : map toLower others
                  in unwords  (map upperFirstForEachWord (words str))

And this is how it works:

upperFirst "" =   ""
upperFirst "hello friends!" = "Hello Friends!"

But also:

upperFirst " " =  ""
upperFirst " a a a " = "A A A"

I'm losing the white spaces at the beginning, at the end and the double ones because of the function words.

How can I keep them recursively (without working on all the possible cases)?

Thank you for any help!

like image 890
Fossa Avatar asked Dec 08 '15 15:12

Fossa


People also ask

How do you uppercase a string in Haskell?

You need to use map (map toUpper) . This is because you have [String] instead of String . i.e. map toUpper capitalises a String, by making each letter uppercase, so map (map toUpper) capitalises each String in a list of Strings.

How do you capitalize every letter in a string?

The upper() method converts all lowercase characters in a string into uppercase characters and returns it.

Which function will capitalizes the first letter of a string?

Python String capitalize() method returns a copy of the original string and converts the first character of the string to a capital (uppercase) letter, while making all other characters in the string lowercase letters.


2 Answers

Instead of extracting words, as words does, you want to just split the string so each word beginning is also at the start of a list. As Hao Lian commented, this can be done with splitOn, but the standard groupBy will also do the trick:

import Data.List
import Data.Char

upperFirst = concat
           . map (\(c:cs) -> toUpper c : cs)
           . groupBy (\a b -> isSpace a == isSpace b)

How this works: it groups together chars which are either all spaces, or all non-spaces. It then uppercases the start of each substring (bit inefficient to also do that for the whitespace, but harmless) and then just concatenates all the strings back together.

As user3237465 remarks, the combination of map and concat is very common, and a special case of something even more common. Furthermore, there's a nice little combinator that comes in handy when grouping or sorting by some predicate. Hence you can also write this as

import Control.Monad
import Data.Function

upperFirst = groupBy ((==)`on`isSpace) >=> \(c:cs) -> toUpper c : cs

To top it up, you can use the hip modern way of modifying things, for the uppercasing part:

import Control.Lens

upperFirst = groupBy ((==)`on`isSpace) >=> ix 0 %~ toUpper
like image 76
leftaroundabout Avatar answered Sep 22 '22 12:09

leftaroundabout


Pattern matching is your friend here

import Data.Char

upperFirst :: String -> String
upperFirst (c1:c2:rest) =
    if isSpace c1 && isLower c2
        then c1 : toUpper c2 : upperFirst rest
        else c1 : upperFirst (c2:rest)
upperFirst s = s

The only problem with this function is that the first character won't get capitalized (also affects single-character strings), but if you really need that functionality then just wrap the call to this function up in another that handles those special cases:

upperFirst' = <the implementation above>

upperFirst [] = []
upperFirst [c] = [toUpper c]  -- No-op on non-letters
upperFirst (s:str) = upperFirst' (toUpper s:str)

To test:

> upperFirst "this is a   test  of \t this\n function"
"This Is A   Test  Of \t This\n Function"
like image 40
bheklilr Avatar answered Sep 21 '22 12:09

bheklilr