2016-03-31 4 views
1

In this question, der Autor bringt eine interessante Programmierfrage: gegeben zwei String, finden Sie mögliche 'verschachtelte' Permutationen von denen, die Reihenfolge der ursprünglichen Strings bewahrt.Interleaving zwei Strings, Erhalt der Reihenfolge: funktionalen Stil

generali ich das Problem zu n Strings anstelle von 2 im Falle des OP, und kam mit:

-- charCandidate is a function that finds possible character from given strings. 
-- input : list of strings 
-- output : a list of tuple, whose first value holds a character 
-- and second value holds the rest of strings with that character removed 
-- i.e ["ab", "cd"] -> [('a', ["b", "cd"])] .. 

charCandidate xs = charCandidate' xs [] 
charCandidate' :: [String] -> [String] -> [(Char, [String])] 
charCandidate' [] _ = []  
charCandidate' ([]:xs) prev = 
    charCandidate' xs prev 
charCandidate' ([email protected](c:rest):xs) prev = 
    (c, prev ++ [rest] ++ xs) : charCandidate' xs (x:prev) 

interleavings :: [String] -> [String] 
interleavings xs = interleavings' xs []  

-- interleavings is a function that repeatedly applies 'charCandidate' function, to consume 
-- the tuple and build permutations. 
-- stops looping if there is no more tuple from charCandidate. 

interleavings' :: [String] -> String -> [String] 
interleavings' xs prev = 
    let candidates = charCandidate xs 
     in case candidates of 
      [] -> [prev] 
      _ -> concat . map (\(char, ys) -> interleavings' ys (prev ++ [char])) $ candidates 

-- test case 
input :: [String] 
input = ["ab", "cd"]  
-- interleavings input == ["abcd","acbd","acdb","cabd","cadb","cdab"] 

es funktioniert, aber ich bin ganz mit dem Code betroffen:

  1. ist es hässlich. kein Punkt-frei!
  2. explizite Rekursion und zusätzliches Funktionsargument prev Staaten zu bewahren
  3. Tupeln als Zwischenform

Wie kann ich das obige Programm neu zu schreiben, um mehr „haskellic“, prägnant, lesbar und anschmiegsamer zu „functional mit Programmierung"?

+4

Ich würde zu weit Punkt-freien Art der Verfolgung nicht empfehlen. Es kann lehrreich sein und sogar Spaß machen, aber wenn man es über einen bestimmten Punkt hinaus nimmt, kann eine punktefreie Programmierung die Dinge weniger lesbar machen, nicht mehr ... – comingstorm

+2

Vermeiden Sie auch Dinge wie '(:) (...) $. .. 'zugunsten von' (...): (...) '. Im obigen Fall brauchst du nicht einmal den zweiten Satz von Parens ... – comingstorm

+0

Ich habe es nicht getestet, aber ich vermute, du wolltest 'charCandidate' schreiben, wo immer du' getCandidate' hast und schreiben willst charCandidate'' überall dort, wo Sie 'charCandidate' in den fünf Zeilen nach der Typdeklaration für' charCandidate' haben. –

Antwort

2

Ich denke, ich würde es so schreiben. Die Hauptidee besteht darin, das Erstellen eines Interleavings als einen nichtdeterministischen Prozess zu behandeln, der eine der Eingabezeichenfolgen auswählt, um das Interleaving und Recurses zu starten.

Bevor wir beginnen, wird es helfen, eine Dienstprogrammfunktion zu haben, die ich unzählige Male benutzt habe. Es bietet eine bequeme Möglichkeit, ein Element aus einer Liste auszuwählen und zu wissen, welches Element es war. Dies ist ein bisschen wie Ihre charCandidate', außer dass es auf einer einzigen Liste zu einer Zeit (und folglich breiter anwendbar ist).

zippers :: [a] -> [([a], a, [a])] 
zippers = go [] where 
    go xs [] = [] 
    go xs (y:ys) = (xs, y, ys) : go (y:xs) ys 

Mit diesem in der Hand ist es leicht, einige nicht-deterministische Wahlen unter Verwendung der Liste monad zu machen. Unsere interleavings Funktion sollte wahrscheinlich einen Typ wie [NonEmpty a] -> [[a]] haben, der verspricht, dass jeder eingehende String mindestens ein Zeichen enthält, aber der syntaktische Overhead von NonEmpty ist zu nervig für eine einfache Übung wie diese, also geben wir nur falsche Antworten wenn diese Vorbedingung verletzt wird. Sie könnten auch in Erwägung ziehen, dies zu einer Hilfsfunktion zu machen und leere Listen aus Ihrer Top-Level-Funktion herauszufiltern, bevor Sie diese ausführen.

interleavings :: [[a]] -> [[a]] 
interleavings [] = [[]] 
interleavings xss = do 
    (xssL, h:xs, xssR) <- zippers xss 
    t <- interleavings ([xs | not (null xs)] ++ xssL ++ xssR) 
    return (h:t) 

Sie können es in GHCI gehen zu sehen:

> interleavings ["abc", "123"] 
["abc123","ab123c","ab12c3","ab1c23","a123bc","a12bc3","a12b3c","a1bc23","a1b23c","a1b2c3","123abc","12abc3","12ab3c","12a3bc","1abc23","1ab23c","1ab2c3","1a23bc","1a2bc3","1a2b3c"] 
> interleavings ["a", "b", "c"] 
["abc","acb","bac","bca","cba","cab"] 
> permutations "abc" -- just for fun, to compare 
["abc","bac","cba","bca","cab","acb"] 
+0

Was ist '[xs | Heu xs] '? – thkang

+0

@thkang Ah, richtig, so: '[e1 | e2] 'ist eine Liste, die das Element" e1 "enthält, wenn" e2 "zu" True "auswertet und eine leere Liste, wenn" e2 "zu" False "führt. Also ist es eine Abkürzung für 'wenn Heu xs dann [xs] else []'. Süß, nicht? Jedenfalls besteht der Zweck darin, die Invariante beizubehalten, dass wir nur nicht leere Listen an "Interleavings" übergeben. –

+0

danke, das war der einzige Teil, den ich nicht verstehen konnte - ich habe über Reißverschlüsse gelesen, aber es ist schön, hier eine echte Anwendung zu sehen (ich bin ziemlich neu in Haskell). – thkang

0

Ein weiterer Ansatz, um die Liste Monade zu verwenden wäre:

interleavings xs ys = interl xs ys ++ interl ys xs where 
    interl [] ys = [ys] 
    interl xs [] = [xs] 
    interl xs ys = do 
    i <- [1..(length xs)] 
    let (h, t) = splitAt i xs 
    map (h ++) (interl ys t) 

So ist der rekursive Teil zwischen den beiden Listen abwechseln, nimm abwechselnd alle 1 bis N Elemente aus jeder Liste und produziere dann alle möglichen Kombinationen daraus. Spaß beim Verwenden der Liste Monade.

Edit: Fehler behoben, wodurch Duplikate

Edit: Antwort auf dfeuer. Es stellte sich heraus, dass es schwierig war, Code im Kommentarfeld zu schreiben. Ein Beispiel für Lösungen, die length nicht aussehen etwas verwenden:

interleavings xs ys = interl xs ys ++ interl ys xs where 
    interl [] ys = [ys] 
    interl xs [] = [xs] 
    interl xs ys = splits xs >>= \(h, t) -> map (h ++) (interl ys t) 

splits [] = [] 
splits (x:xs) = ([x], xs) : map ((h, t) -> (x:h, t)) (splits xs) 

Die Splits Funktion fühlt sich ein wenig umständlich.Es könnte durch die Verwendung von takeWhile oder break in Kombination mit splitAt ersetzt werden, aber diese Lösung endete auch ein bisschen peinlich. Hast du irgendwelche Vorschläge?

(habe ich von der do-Notation befreit es etwas kürzer gerade zu machen)

+0

Kannst du einen Weg sehen, die Länge zu vermeiden? Es ist hier nicht wirklich nötig. – dfeuer

+0

Sie können alle Möglichkeiten, die Liste zu teilen, träge erstellen und dann Elemente daraus zeichnen. Ich habe eine Version ausprobiert, aber sie hat es nicht lesbarer gemacht, und ich bin mir nicht sicher, ob es auch schneller wäre. – dvaergiller

+0

Habe gerade gemerkt, dass diese Lösung der von Daniel Wagner sehr ähnlich ist – dvaergiller

2

Dies ist schnellste Implementierung Ich habe kommen mit so weit. Es verschachtelt eine Liste von Listen paarweise.

Dieses schrecklich hässliche Chaos ist der beste Weg, um zwei Listen zu verschachteln. Es soll asymptotisch optimal sein (was ich glaube es ist); es ist nicht sehr hübsch. Die konstanten Faktoren könnten verbessert werden, indem eine spezielle Warteschlange (wie diejenige, die in Data.List verwendet wird, um inits zu implementieren) anstelle von Sequenzen verwendet wird, aber ich habe nicht das Gefühl, so viele Standardwerte mit aufzunehmen.

{-# LANGUAGE BangPatterns #-} 
import Data.Monoid 
import Data.Foldable (toList) 
import Data.Sequence (Seq, (|>)) 

interleave2 :: [a] -> [a] -> [[a]] 
interleave2 xs ys = interleave2' mempty xs ys [] 

interleave2' :: Seq a -> [a] -> [a] -> [[a]] -> [[a]] 
interleave2' !prefix xs ys rest = 
    (toList prefix ++ xs ++ ys) 
    : interleave2'' prefix xs ys rest 

interleave2'' :: Seq a -> [a] -> [a] -> [[a]] -> [[a]] 
interleave2'' !prefix [] _ = id 
interleave2'' !prefix _ [] = id 
interleave2'' !prefix [email protected](x : xs') [email protected](y : ys') = 
    interleave2' (prefix |> y) xs ys' . 
     interleave2'' (prefix |> x) xs' ys 
0

Die Kombination der besten Ideen aus den vorhandenen Antworten und das Hinzufügen von ein paar meiner eigenen:

import Control.Monad 

interleave [] ys = return ys 
interleave xs [] = return xs 
interleave (x : xs) (y : ys) = 
    fmap (x :) (interleave xs (y : ys)) `mplus` fmap (y :) (interleave (x : xs) ys) 

interleavings :: MonadPlus m => [[a]] -> m [a] 
interleavings = foldM interleave [] 

Dies ist nicht der schnellstmögliche Sie bekommen können, aber es sollte in Bezug auf die allgemeine und einfache gut .

+0

Es ist hübsch, und es war meine ursprüngliche Antwort, aber es hat einige Leistungsprobleme. Links-nested '++' und viele wiederholte 'map'-Anwendungen machen dies langsam. – dfeuer

+0

In der Tat. Solche Ineffizienzen werden jedoch durch die Ausgabegröße in den Schatten gestellt, so dass sie kaum eine Rolle spielen. – Rotsor

+0

Bearbeitet die Antwort leicht, so dass "Interleavings" jetzt für jeden "MonadPlus" funktioniert. Das bedeutet, dass du es schneller machen kannst, indem du eine andere Monade benutzt! – Rotsor

0

Mit foldr über interleave2

interleave :: [[a]] -> [[a]] 
interleave = foldr ((concat .) . map . iL2) [[]] where 
    iL2 [] ys = [ys] 
    iL2 xs [] = [xs] 
    iL2 (x:xs) (y:ys) = map (x:) (iL2 xs (y:ys)) ++ map (y:) (iL2 (x:xs) ys) 
Verwandte Themen