Andere Antworten setzen voraus, dass die beiden Eingabelisten endlich sind. Häufig enthält der idiomatische Haskell-Code unendliche Listen. Daher lohnt es sich, kurz zu erläutern, wie bei Bedarf ein unendliches kartesisches Produkt hergestellt werden kann.
Der Standardansatz ist die Verwendung der Diagonalisierung. Wenn wir die eine Eingabe oben und die andere Eingabe links schreiben, können wir eine zweidimensionale Tabelle schreiben, die das vollständige kartesische Produkt wie folgt enthält:
1 2 3 4 ...
a a1 a2 a3 a4 ...
b b1 b2 b3 b4 ...
c c1 c2 c3 c4 ...
d d1 d2 d3 d4 ...
. . . . . .
. . . . . .
. . . . . .
Wenn wir über eine einzelne Zeile arbeiten, erhalten wir natürlich unendlich viele Elemente, bevor die nächste Zeile erreicht wird. In ähnlicher Weise wäre es katastrophal, säulenweise vorzugehen. Aber wir können entlang von Diagonalen gehen, die nach unten und links gehen, und jedes Mal, wenn wir den Rand des Gitters erreichen, etwas weiter rechts beginnen.
a1
a2
b1
a3
b2
c1
a4
b3
c2
d1
...und so weiter. In der Reihenfolge würde dies uns geben:
a1 a2 b1 a3 b2 c1 a4 b3 c2 d1 ...
Um dies in Haskell zu codieren, können wir zuerst die Version schreiben, die die zweidimensionale Tabelle erzeugt:
cartesian2d :: [a] -> [b] -> [[(a, b)]]
cartesian2d as bs = [[(a, b) | a <- as] | b <- bs]
Eine ineffiziente Methode zur Diagonalisierung besteht darin, zuerst entlang der Diagonalen und dann entlang der Tiefe jeder Diagonale zu iterieren und jedes Mal das entsprechende Element herauszuziehen. Zur Vereinfachung der Erklärung gehe ich davon aus, dass beide Eingabelisten unendlich sind, damit wir nicht mit der Überprüfung der Grenzen herumspielen müssen.
diagonalBad :: [[a]] -> [a]
diagonalBad xs =
[ xs !! row !! col
| diagonal <- [0..]
, depth <- [0..diagonal]
, let row = depth
col = diagonal - depth
]
Diese Implementierung ist etwas unglücklich: Die wiederholte Listenindizierung wird im !!
Laufe der Zeit immer teurer und führt zu einer ziemlich schlechten asymptotischen Leistung. Eine effizientere Implementierung übernimmt die oben genannte Idee, implementiert sie jedoch mithilfe von Reißverschlüssen. Also werden wir unser unendliches Gitter in drei Formen wie diese unterteilen:
a1 a2 / a3 a4 ...
/
/
b1 / b2 b3 b4 ...
/
/
/
c1 c2 c3 c4 ...
d1 d2 d3 d4 ...
. . . . .
. . . . .
. . . . .
Das obere linke Dreieck sind die Bits, die wir bereits ausgegeben haben. Das obere rechte Viereck besteht aus Reihen, die teilweise emittiert wurden, aber dennoch zum Ergebnis beitragen. und das untere Rechteck besteht aus Zeilen, die wir noch nicht ausgegeben haben. Zunächst sind das obere Dreieck und das obere Viereck leer, und das untere Rechteck ist das gesamte Raster. Bei jedem Schritt können wir das erste Element jeder Zeile im oberen Viereck ausgeben (im Wesentlichen die schräge Linie um eins verschieben) und dann eine neue Zeile vom unteren Rechteck zum oberen Viereck hinzufügen (im Wesentlichen die horizontale Linie um eins nach unten verschieben) ).
diagonal :: [[a]] -> [a]
diagonal = go [] where
go upper lower = [h | h:_ <- upper] ++ case lower of
[] -> concat (transpose upper')
row:lower' -> go (row:upper') lower'
where upper' = [t | _:t <- upper]
Dies sieht zwar etwas komplizierter aus, ist aber deutlich effizienter. Es behandelt auch die Grenzen, die überprüft werden, auf die wir in der einfacheren Version gestoßen sind.
Aber Sie sollten diesen ganzen Code natürlich nicht selbst schreiben! Stattdessen sollten Sie das Universumspaket verwenden . In Data.Universe.Helpers
gibt es (+*+)
, was das Obige zusammenfasst cartesian2d
und diagonal
Funktionen, um nur die kartesische Produktoperation zu ergeben:
Data.Universe.Helpers> "abcd" +*+ [1..4]
[('a',1),('a',2),('b',1),('a',3),('b',2),('c',1),('a',4),('b',3),('c',2),('d',1),('b',4),('c',3),('d',2),('c',4),('d',3),('d',4)]
Sie können auch die Diagonalen selbst sehen, wenn diese Struktur nützlich wird:
Data.Universe.Helpers> mapM_ print . diagonals $ cartesian2d "abcd" [1..4]
[('a',1)]
[('a',2),('b',1)]
[('a',3),('b',2),('c',1)]
[('a',4),('b',3),('c',2),('d',1)]
[('b',4),('c',3),('d',2)]
[('c',4),('d',3)]
[('d',4)]
Wenn Sie viele Listen zusammen produzieren müssen, (+*+)
kann das Iterieren bestimmte Listen unfair beeinflussen. Sie können choices :: [[a]] -> [[a]]
für Ihre n-dimensionalen kartesischen Produktanforderungen verwenden.