Haskell - the capital letter of each letter in a string without losing spaces

I am doing an exercise that requires me to write a function that uses all the first letters of the words in a string.

Here is what I have done so far:

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

And here is how it works:

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

But also:

 upperFirst " " = "" upperFirst " aaa " = "AAA" 

I lose white spaces at the beginning, at the end and in doubles because of the words function.

How can I save them recursively (without work in all possible cases)?

Thanks for the help!

+5
source share
5 answers

Instead of extracting words like words , you just want to split the line so that each beginning of the word is also at the top of the list. As Hao Layan 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 (\ab -> isSpace a == isSpace b) 

How it works: It combines characters that are either spaces or disjoint. Then it pauses the beginning of each substring (the bit is inefficient and also does this for spaces, but is harmless), and then simply concatenates all the lines back together.

As user notes concat , the combination of map and concat very common, and a special case of something even more concat , a beautiful little combinator that comes in handy when grouping or sorting by some predicate. Therefore, you can also write this as

 import Control.Monad import Data.Function upperFirst = groupBy ((==)`on`isSpace) >=> \(c:cs) -> toUpper c : cs 

To replenish it, you can use the modern way to modify things, for the top:

 import Control.Lens upperFirst = groupBy ((==)`on`isSpace) >=> ix 0 %~ toUpper 
+7
source

Matching the pattern 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 will not receive capital letters (also affects single-character strings), but if you really need this functionality, just wrap the call to this function in another that handles these special cases:

 upperFirst' = <the implementation above> upperFirst [] = [] upperFirst [c] = [toUpper c] -- No-op on non-letters upperFirst (s:str) = upperFirst' (toUpper s:str) 

To check:

 > upperFirst "this is a test of \t this\n function" "This Is A Test Of \t This\n Function" 
+4
source
 import Data.Char upperFirst :: String -> String upperFirst s = zipWith upper (' ':s) s where upper c1 c2 | isSpace c1 && isLower c2 = toUpper c2 upper c1 c2 = c2 

eg.

 upperFirst "a test for upperFirst" 

comes down to

 zipWith upper " a test for upperFirst " "a test for upperFirst " 

If the character in the first line is a space, and the character in the same position in the second line is a lowercase letter, then make it uppercase, otherwise return it. So

  • upper ' ' 'a' = 'A'
  • upper 'a' ' ' = ' '
  • upper ' ' 't' = 'T'
  • upper 't' 'e' = 'e'

etc. Therefore, the result is "A Test For UpperFirst " .

+2
source

This problem is a prototype scan:

 >>> scanl (\old new -> if isSpace old then toUpper new else new) ' ' " hello world " " Hello World " 

The initial state of the "seed" - here the space - is added at the beginning, but the tail is finished here. See the scanl implementation here and compare it with other scan functions that have different benefits. Here is a simple version:

 scan op state [] = state : [] scan op state (x:xs) = state : scan op (op state x) xs 

You can write your function by inserting

 op old new = if isSpace old then toUpper new else new 

in definition

 myscan state [] = state : [] myscan state (x:xs) = state : myscan (if isSpace state then toUpper x else x) xs 

starting with a space and then taking the tail:

 titlecase = tail . myscan ' ' 

Then in ghci I see

 >>> titlecase " hello world " " Hello World " 

Or you can just use the generic scan we defined

 >>> let op old new = if isSpace old then toUpper new else new >>> scan op ' ' " hello world " " Hello World " >>> tail $ scan op ' ' " hello world " " Hello World " 
+1
source

This is very similar to Michael's solution , but I think mapAccumL better suited than scanl :

 upperFirst :: Traversable t => t Char -> t Char upperFirst = snd . mapAccumL go True where go lastSpace x | isSpace x = (True, x) | lastSpace = (,) False $! toUpper x | otherwise = (False, x) 
0
source

Source: https://habr.com/ru/post/1237669/


All Articles