2015-12-08 6 views
5

Ich mache eine Übung, bei der ich eine Funktion schreiben muss, die alle Anfangsbuchstaben der Wörter einer Zeichenfolge groß schreibt. HierHaskell - Groß-/Kleinschreibung des ersten Buchstabens jedes Worts in einer Zeichenfolge

ist, was ich bisher getan:

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

Und so funktioniert es:

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

Aber auch:

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

Ich verliere die weißen Räume am Anfang, am Ende und die doppelten wegen der Funktion words.

Wie kann ich sie rekursiv behalten (ohne an allen möglichen Fällen zu arbeiten)?

Vielen Dank für jede Hilfe!

+2

Ich denke, jede Lösung mit 'Worten' leidet darunter:' unwords' und 'words' sind nicht perfekte Umkehrungen voneinander und wi ll immer führende/nachfolgende Whitespace zusätzlich zu Double/Triple/etc. Räume. Versuchen Sie, eine Lösung ohne "Wörter" oder "Unwörter" zu schreiben. Alternativ, siehe ['splitOn'] (https://hackage.haskell.org/package/split-0.2.2/docs/Data-List-Split.html), was diesbezüglich vorsichtiger ist. – hao

+1

Versuchen Sie, eine rekursive Lösung zu schreiben, die der 'map' -Funktion ähnelt, aber auch ein Stück des Zustands' shallNextNonWhitespaceCharBeCapitalized :: Bool' durchläuft, dann sehen Sie, ob Sie in Data.List eine Funktion höherer Ordnung finden können, die diesen Rekursionstyp erfasst . – jberryman

Antwort

4

Pattern Matching ist dein Freund hier

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 

Das einzige Problem mit dieser Funktion besteht darin, dass das erste Zeichen nicht aktiviert bekommen (auch Single-Zeichenkette betrifft), aber wenn Sie wirklich diese Funktionalität benötigen dann wickelt nur den Aufruf dieser Funktion nach oben in einer anderen, die diese Sonderfälle behandelt:

upperFirst' = <the implementation above> 

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

zum Test:

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

Eine andere Möglichkeit zum Umbrechen ist 'drop 1. obererFirst '. (":)". – dfeuer

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 

z.

upperFirst "a test for upperFirst" 

reduziert sich auf

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

Wenn ein Symbol in der ersten Zeichenfolge ein Raum ist und ein Symbol in der gleichen Position in der zweiten Saite ist ein Kleinbuchstabe, dann machen Sie es in Großbuchstaben, sonst kehrt die gleich. So

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

und so weiter. Daher ist das Ergebnis "A Test For UpperFirst ".

+0

Dadurch werden Tabulatoren und Zeilenumbrüche durch Leerzeichen ersetzt. – dfeuer

+0

@dfeuer, [wird es?] (Http://ideone.com/6iX7sZ) – user3237465

+0

Ups! Es tut uns leid! Ich bin verwirrt. – dfeuer

7

Anstatt Wörter zu extrahieren, wie words, möchten Sie nur splitten die Zeichenfolge, so dass jedes Wort Anfang auch am Anfang einer Liste ist. Als Hao Lian kommentiert, kann dies mit splitOn getan werden, aber die Standard-groupBy wird auch den Trick tun:

import Data.List 
import Data.Char 

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

Wie das funktioniert: es Gruppen zusammen Zeichen, die entweder alle Räume oder alle nicht-Räume sind.Dann wird der Anfang jeder Teilzeichenkette groß geschrieben (ein bisschen ineffizient, um das auch für das Leerzeichen zu tun, aber harmlos), und dann werden alle Zeichenketten wieder zusammengefügt.

Als Benutzer3237465 Bemerkungen, die Kombination von map und concat ist sehr üblich, und ein Sonderfall von etwas even more common. Darüber hinaus gibt es a nice little combinator, die nützlich beim Gruppieren oder Sortieren nach einem Prädikat ist. Daher können Sie auch dies als

schreiben
import Control.Monad 
import Data.Function 

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

es nach oben, du the hip modern way modifizieren Dinge, für die großgeschrieben Teil verwenden:

import Control.Lens 

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

Nice one. Warum nicht "concatMap"? – user3237465

+0

@ user3237465: Ich bezweifle, dass es Klarheit bringt, aber ich mag es. – leftaroundabout

1

Dieses Problem ist ein prototypisches Scan:

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

Der Anfangszustand des 'Seed' - hier ein Leerzeichen - wird am Anfang hinzugefügt, aber Schwanz ist hier legitim. Sehen Sie die Implementierung von scanlhere, und vergleichen Sie es mit den anderen Scan-Funktionen, die verschiedene Vorteile haben. Hier ist eine einfache Version:

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

Sie Ihre Funktion schreiben können inlining

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

in die Definition

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

beginnend mit Leerzeichen und dann mit dem Schwanz:

titlecase = tail . myscan ' ' 

Die n in GHCI sehe ich

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

Oder Sie können einfach direkt verwenden die allgemeine scan wir

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

Dies ist sehr ähnlich zu Michael's solution, aber ich denke, mapAccumL ist eine bessere Passform als 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) 
Verwandte Themen