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!
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.
The upper() method converts all lowercase characters in a string into uppercase characters and returns it.
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.
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
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"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With