簡體   English   中英

Haskell - 將字符串中每個單詞的第一個字母大寫,而不會丟失空格

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

我正在做一個練習,要求我編寫一個函數來大寫字符串的所有首字母。

這是我到目前為止所做的:

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的緣故,我在開頭,結尾處都會丟失白色空格。

如何遞歸保存它們(不處理所有可能的情況)?

感謝您的任何幫助!

您不想像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的組合非常普遍,而且特殊情況更為常見 此外,還有一個很好的小組合器 ,在用一些謂詞進行分組或排序時派上用場。 因此你也可以這樣寫

import Control.Monad
import Data.Function

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

最重要的是,你可以使用時髦的現代修改方式,對於大寫部分:

import Control.Lens

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

模式匹配是你的朋友

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

例如

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 "

這個問題是原型掃描:

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

“種子”的初始狀態 - 這里是一個空格 - 在開頭添加,但尾部在這里是合法的。 請參閱此處scanl實現,並將其與其他具有不同優勢的掃描功能進行比較。 這是一個簡單的版本:

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  "

這非常類似於邁克爾的解決方案 ,但我認為mapAccumL是不是更適合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)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM