Haskell - 将字符串中每个单词的第一个字母大写,而不会丢失空格

时间:2015-12-08 15:50:48

标签: haskell

我正在做一个练习,要求我编写一个函数来大写字符串单词的所有首字母。

这是我到目前为止所做的:

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

这就是它的工作原理:

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

但是:

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

由于函数words,我在开头,结尾和双重空格中丢失了空格。

如何递归保存它们(不处理所有可能的情况)?

感谢您的帮助!

5 个答案:

答案 0 :(得分:8)

而不是像words那样提取单词,而是只想拆分字符串,这样每个单词的开头也位于列表的开头。正如郝连评论的那样,这可以用splitOn来完成,但标准的groupBy也可以做到这一点:

import Data.List
import Data.Char

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

这是如何工作的:它将所有空格或所有非空格的字符组合在一起。然后它会将每个子字符串的开头大写(对于空白也是有效的,但无害),然后将所有字符串连接在一起。

正如user3237465评论的那样,mapconcat的组合非常常见,并且是even more common的特例。此外,有些a nice little combinator在按某些谓词分组或排序时派上用场。因此,您也可以将其写为

import Control.Monad
import Data.Function

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

要充实,您可以使用the hip modern way修改内容,作为大写部分:

import Control.Lens

upperFirst = groupBy ((==)`on`isSpace) >=> ix 0 %~ toUpper

答案 1 :(得分:4)

模式匹配是你的朋友

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

这个函数的唯一问题是第一个字符不会被大写(也会影响单字符字符串),但如果你真的需要这个功能,那么只需将对此函数的调用包装在另一个处理函数中那些特殊情况:

upperFirst' = <the implementation above>

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

测试:

> upperFirst "this is a   test  of \t this\n function"
"This Is A   Test  Of \t This\n Function"

答案 2 :(得分:2)

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

E.g。

upperFirst "a   test for    upperFirst"

缩减为

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

如果第一个字符串中的符号是空格而第二个字符串中相同位置的符号是小写字母,则将其设为大写,否则返回相同的符号。所以

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

等等。因此结果是"A Test For UpperFirst "

答案 3 :(得分:1)

这个问题是原型扫描:

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

“种子”的初始状态 - 这里是一个空格 - 在开头添加,但尾部在这里是合法的。请参阅scanl here的实现,并将其与其他具有不同优势的扫描函数进行比较。这是一个简单的版本:

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

您可以通过内联编写功能

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

进入定义

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

用空格开始然后取尾:

titlecase = tail . myscan ' '

然后在ghci我看到了

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

或者您可以直接使用我们定义的一般scan

>>> 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  "

答案 4 :(得分:0)

这与Michael's solution非常相似,但我认为mapAccumLscanl更合适:

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)