2016-10-13 2 views
3

Gibt es eine Möglichkeit, String in Haskell bei der letzten Vorkommen von gegebenem Zeichen in 2 Listen zu teilen? Zum Beispiel möchte ich die Liste "a b c d e" auf Leerzeichen in ("a b c d", "e") aufteilen. Vielen Dank für die Antworten.Haskell Split String beim letzten Auftreten

+2

'breakLastSpace str = (reverse (drop 1 y), umgekehrtes x) where (x, y) = break (== '') $ reverse str ist eine relativ naive Implementierung. –

+0

Beachten Sie auch, dass Sie Ihre Zeichenfolge auf Leerzeichen durch die Funktion 'words' teilen können. – suffi

+0

Beachten Sie, dass' text' 'breakOnEnd :: Text -> Text -> (Text, Text)' so dass ' T.breakOnEnd "" "abcde" 'gibt das gewünschte Ergebnis (" abcd "," e ")'. – Michael

Antwort

5

Ich bin mir nicht sicher, warum die vorgeschlagenen Lösungen so kompliziert sind. Nur ein zwei Traversierungen benötigt:

splitLast :: Eq a => a -> [a] -> Either [a] ([a],[a]) 
splitLast c' = foldr go (Left []) 
    where 
     go c (Right (f,b)) = Right (c:f,b) 
     go c (Left s) | c' == c = Right ([],s) 
         | otherwise = Left (c:s) 

Hinweis: Dies ist Gesamt und eindeutig sein Scheitern bedeutet. Wenn eine Aufteilung nicht möglich ist (da das angegebene Zeichen nicht in der Zeichenfolge enthalten ist), wird eine Left mit der ursprünglichen Liste zurückgegeben.Andernfalls gibt es eine Right mit den zwei Komponenten zurück.

+1

Dies ist vielleicht die einfachste, die ich bisher gesehen habe, aber es führt tatsächlich zwei Durchgänge, eine Dekonstruktion der Eingabe und eine andere Konstruktion der Ausgabe. Es ist daher strenger als notwendig und langsamer für sehr lange Listen. Sie können dies beheben, aber das Risiko von Speicherlecks zu vermeiden könnte ein bisschen schwierig sein. – dfeuer

+0

@dfeuer Danke für die Korrektur. Ich vergesse immer, dass die Struktur wieder aufgebaut werden muss. Die Alternative, die du erwähnst, wäre eine linke Falte? – Alec

+0

Hrmmm ... Was ich dachte, wird nicht funktionieren. Ich denke, der einzige faule Weg ist, ein Stück anzusammeln und abzuwarten, ob es am Ende befestigt werden soll. – dfeuer

2

Es ist nicht schön, aber es funktioniert:

import Data.List 
f :: Char -> String -> (String, String) 
f char str = let n = findIndex (==char) (reverse str) in 
       case n of 
        Nothing -> (str, []) 
        Just n -> splitAt (length str - n -1) str 

ich meine f 'e' "a b c d e" = ("a b c d ", "e"), aber ich würde mich nicht, dass der Raum Hinter zuzuschneiden.

+0

Könnte es auch machen 'a -> [a] -> ([a], [a])', nein? –

+0

@chris martin Sicher, tun Sie, wie Sie möchten, ich wollte nur klar machen, wie Sie diese Funktion in diesem Kontext verwenden, indem Sie diese Typensignatur bereitstellen. – suffi

1

kann ich die folgende Lösung vor:

splitLast list elem = (reverse $ snd reversedSplit, reverse $ fst reversedSplit) 
    where 
    reversedSplit = span (/= elem) $ reverse list 

wahrscheinlich nicht die schnellste (zwei unnötige umkehrt), aber Ich mag es Einfachheit ist.

Wenn Sie auf Entfernen der Raum bestehen wir aufteilen, können Sie gehen für:

import qualified Data.List as List 
splitLast list elem = splitAt (last $ List.elemIndices elem list) list 

jedoch diese Version, dass davon ausgegangen, mindestens ein Element sein, das Muster entspricht. Wenn Sie diese Annahme nicht mögen, wird der Code etwas länger (aber keine Doppel Umkehrungen hier):

import qualified Data.List as List 
splitLast list elem = splitAt index list where 
    index = if null indices then 0 else last indices 
    indices = List.elemIndices elem list 

Natürlich Wahl zu Beginn der Spaltung ist willkürlich und wahrscheinlich am Ende Aufspaltung wäre intuitiv für Sie - dann können Sie einfach 0 durch length list

2

ersetzen Ich würde mit mehr Musterabgleich gehen.

import Data.List 

splitLast = contract . words 
    where contract [] = ("", "") 
      contract [x] = (x, "") 
      contract [x,y] = (x, y) 
      contract (x:y:rest) = contract $ intercalate " " [x,y] : rest 

Für lange Listen verbinden wir einfach die ersten beiden Strings mit einem Leerzeichen und versuchen die kürzere Liste erneut. Sobald die Länge auf 2 reduziert ist, geben wir nur das Saitenpaar zurück.

(x, "") schien eine vernünftige Wahl für Strings ohne Leerzeichen, aber ich nehme an, Sie könnten ("", x) stattdessen zurückgeben.

Es ist nicht klar, dass ("", "") die beste Wahl für leere Zeichenfolgen ist, aber es scheint eine sinnvolle Alternative zu einem Fehler zu sein oder den Rückgabetyp zu etwas wie Maybe (String, String) zu ändern.

1

Meine Idee ist es, bei jedem Auftreten zu teilen und dann die ersten Teile vom letzten Teil zu trennen.

Spitz:

import Control.Arrow -- (&&&) 
import Data.List  -- intercalate 
import Data.List.Split -- splitOn 
breakOnLast :: Eq a => a -> [a] -> ([a], [a]) 
breakOnLast x = (intercalate x . init &&& last) . splitOn x 

Punkt frei:

liftA2 (.) ((&&& last) . (. init) . intercalate) splitOn 

(.) <$> ((&&&) <$> ((.) <$> pure init <*> intercalate) <*> pure last) <*> splitOn 
+4

Ich bin mir nicht sicher, dass die point-free-Versionen sogar erwähnenswert sind. – chepner

+0

Es sollte erwähnt werden, dass 'Data.List.Split' vom' Split'-Paket bereitgestellt wird. – chepner

+1

Ich mag Punkt-freie Versionen zu finden, und ich mag sie, also gebe ich ihnen :) – erisco