Другие ответы предполагают, что два списка ввода конечны. Часто идиоматический код на 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-мерных картезианских потребностей.