Другие ответы предполагают, что два входных списка являются конечными. Часто, идиоматический код Haskell включает в себя бесконечные списки, поэтому стоит кратко остановиться на том, как создать бесконечный декартово произведение в случае необходимости.
Стандартный подход заключается в использовании диагонализации; писать один вход вдоль верхних и другой вход по левому, мы могли бы написать двухмерную таблицу, которая содержала полное декартово произведение так:
1 2 3 4 ...
a a1 a2 a3 a4 ...
b b1 b2 b3 b4 ...
c c1 c2 c3 c4 ...
d d1 d2 d3 d4 ...
. . . . . .
. . . . . .
. . . . . .
Конечно, работая по любой отдельной строке дадут нам бесконечно элементы до того, как он достигнет следующей строки; Подобным же образом столкновение будет катастрофическим. Но мы можем идти по диагоналям, которые идут вниз и влево, начиная снова немного дальше вправо каждый раз, когда мы достигаем края сетки.
a1
a2
b1
a3
b2
c1
a4
b3
c2
d1
... и так далее. Для того, это дало бы нам:
a1 a2 b1 a3 b2 c1 a4 b3 c2 d1 ...
Чтобы закодировать это в Haskell, мы можем сначала написать версию, которая производит двухмерную таблицу:
cartesian2d :: [a] -> [b] -> [[(a, b)]]
cartesian2d as bs = [[(a, b) | a <- as] | b <- bs]
неэффективный метод диагонализации должны затем сначала итерации по диагоналям, а затем по глубине каждой диагонали, каждый раз вытягивая соответствующий элемент. Для простоты объяснения я предполагаю, что оба входных списка бесконечны, поэтому нам не нужно возиться с проверкой границ.
diagonalBad :: [[a]] -> [a]
diagonalBad xs =
[ xs !! row !! col
| diagonal <- [0..]
, depth <- [0..diagonal]
, let row = depth
col = diagonal - depth
]
Эта реализация немного неудачно: повторный список операций индексации !!
становится все более и более дорогим, как мы идем, что дает довольно плохое асимптотическое представление. Более эффективная реализация возьмет вышеуказанную идею, но реализует ее с помощью молнии. Таким образом, мы разделим нашу бесконечную сетку на три формы, как это:
a1 a2/a3 a4 ...
/
/
b1/b2 b3 b4 ...
/
/
/
c1 c2 c3 c4 ...
---------------------------------
d1 d2 d3 d4 ...
. . . . .
. . . . .
. . . . .
Верхний левый треугольник будет биты мы уже излучаемые; верхний правый четырехугольник будет рядами, которые были частично выпущены, но все равно будут способствовать результату; а нижний прямоугольник будет строками, которые мы еще не начали испускать. Начнем с того, что верхний треугольник и верхний четырехугольник будут пустыми, а нижний прямоугольник будет всей сеткой. На каждом шаге мы можем испускать первый элемент каждой строки в верхнем четырехграннике (по существу перемещая наклонную линию на единицу), а затем добавить одну новую строку из нижнего прямоугольника в верхний четырехугольник (по существу перемещая горизонтальную линию вниз на один).
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]
Хотя это выглядит несколько сложнее, оно значительно более эффективно. Он также обрабатывает проверку границ, которую мы пропустили в более простой версии.
Но вы не должны писать весь этот код самостоятельно, конечно! Вместо этого вы должны использовать пакет universe. В Data.Universe.Helpers
, есть (+*+)
, какие пакеты вместе над cartesian2d
и diagonal
функцией дать лишь декартова операции продукта:
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)]
Вы также можете увидеть диагонали самого, если эта структура становится полезной:
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)]
Если у вас есть много списков продуктов вместе, итерация (+*+)
может несправедливо предубеждать определенные списки; вы можете использовать choices :: [[a]] -> [[a]]
для ваших n-мерных декартовых продуктов.
Спасибо, так просто, но элегантно, очень помогли с другой проблемой :) –
Хорошо также для Erlang, спасибо. Маленькое изменение в синтаксисе: cartProd (Xs, Ys) -> [{X, Y} || X <- Xs, Y <- Ys]. – Dacav