2017-11-04 2 views
3

Ich schrieb eine Funktion, um Wortnummern für die Verarbeitung in Haskell zu säubern. Es muss in der Lage sein, - in Leerzeichen (d. H. Fünfundvierzig wird fünfundvierzig) zu ändern und jeden anderen Nicht-Buchstaben zu löschen. Ich kann es rekursiv definieren, aber ich möchte wirklich etwas sauberer machen.Wie wenden Sie mehrere Fälle in einem Ersatz in Haskell an?

clean :: String -> String 
clean "" = "" 
clean ('-':cs) = ' ' : clean cs 
clean (c:cs) 
    | isLetter c = c : clean cs 
    | otherwise = clean cs 

Dies führte mich einen benutzerdefinierten Filter zu definieren, und eine von Data.List.Split basierend auf einem Kommentar zu this answer ersetzen zu definieren, da ich bereits Data.List.Split verwenden.

clean :: String -> String 
clean = filter (\c -> isLetter c || c == ' ') . replace "-" " " . filter (/= ' ') 
    where 
    replace :: String -> String -> String -> String 
    replace old new = intercalate new . splitOn old 

Diese Version ist insgesamt noch unordentlicher. Diese Version entfernt auch keine Leerzeichen in der ursprünglichen Zeichenfolge. Ist eine andere Konvention oder etwas eingebaut, die mir erlauben würde, dies mit einem sauberen One-Liner zu tun?

Antwort

6

Eine der leistungsstärksten Funktionen zum Umgang mit Listen ist concatMap (a.k.a. >>=).

clean :: String -> String 
clean = concatMap (\c -> if c == '-' then " " else [c | isLetter c]) 
4

Es gibt zwei Dinge hier:: Sie können Ihre clean Funktion wie so schreiben

  1. Sie alles entfernen müssen, der kein Brief oder Bindestrich ist; und
  2. Als nächstes ersetzen wir Bindestriche durch Leerzeichen.

So können wir dies tun, mit einer Pipeline mit filter und replace:

import Data.Bool(bool) 
    import Data.Char(isLetter) 

    map (\x -> bool ' ' x (x /= '-')) . filter (\x -> isLetter x || x == '-') 
-- \____________ __________________/ \______________ ____________________/ 
--    v          v 
--    (2)         (1) 

Wir Liste Verständnis können die Zuordnung und Filterung zu tun:

import Data.Bool(bool) 
import Data.Char(isLetter) 

clean l = [bool ' ' x (x /= '-') | x <- l, isLetter x || x == '-'] 

Wir auch verwenden können eine einzelne Funktion, und führen Sie zum Beispiel eine concatMap:

import Data.Bool(bool) 
import Data.Char(isLetter) 

concatMap (\x -> bool (bool "" " " (x == '-')) [x] (isLetter x)) 

hier So verketten wir die Abbildung von x zu "" bei x kein Brief ist und der Bindestrich, oder die leere Zeichenkette, falls es keinen Brief noch der Bindestrich oder [x] (so ein 1-char string) in Fall x ist ein Buchstabe.

+0

Danke dafür!Ich bin noch nicht auf 'bool' gestoßen. Es sieht so aus, als ob es im Grunde ein Wenn-sonst ist? – BrainFRZ

+2

@BrainFRZ: Es ist ein * Katamorphismus * von 'Bool', aber für einen booleschen, das funktioniert tatsächlich wie ein' wenn-dann-sonst', mit 'bool '. –

+0

@chi: ja, ich habe es in den Kommentar gemischt, danke :) –

2

Dies ist ein sehr guter Anwendungsfall für do Notation in der Liste Monad.

clean :: String -> String 
clean string = do 
    character <- string   -- For each character in the string... 
    case character of 
    '-'   -> " "  -- If it’s a dash, replace with a space. 
    c | isLetter c -> pure c -- If it’s a letter, return it. 
    _    -> []  -- Otherwise, discard it. 

Dies ist letztlich einfach syntaktischer Zucker für concatMap. pure c kann auch [c] geschrieben werden, wenn Sie bevorzugen; und weniger wichtig, " " kann pure ' ' oder [' '] geschrieben werden. Und als Alternative, können Sie diese besser lesbar mit dem MultiWayIf Erweiterung:

if 
    | character == '-' -> " " 
    | isLetter character -> pure character 
    | otherwise   -> [] 

Schließlich ist zu beachten, dass isLetter kehrt gilt für alle Unicode-Zeichen. Wenn Sie nur an ASCII interessiert sind, können Sie isAscii c && isLetter c oder isAsciiUpper c || isAsciiLower c verwenden.

Verwandte Themen