他の回答では、2 つの入力リストが有限であると仮定しています。慣用的な Haskell コードには無限リストが含まれることが多いため、必要な場合に備えて、無限デカルト積を生成する方法について簡単にコメントしておく価値があります。
標準的なアプローチは、対角化を使用することです。1 つの入力を上部に、もう 1 つの入力を左側に書き出すと、完全なデカルト積を含む 2 次元のテーブルを次のように書くことができます。
1 2 3 4 ...
a a1 a2 a3 a4 ...
b b1 b2 b3 b4 ...
c c1 c2 c3 c4 ...
d d1 d2 d3 d4 ...
. . . . . .
. . . . . .
. . . . . .
もちろん、任意の 1 つの行で作業すると、次の行に到達する前に要素が無限に得られます。同様に、列方向に進むと悲惨なことになります。しかし、グリッドの端に到達するたびに少し右から始めて、左に下る対角線に沿って進むことができます。
a1
a2
b1
a3
b2
c1
a4
b3
c2
d1
...等々。順番に、これは私たちに与えるでしょう:
a1 a2 b1 a3 b2 c1 a4 b3 c2 d1 ...
これを Haskell でコーディングするには、まず 2 次元のテーブルを生成するバージョンを記述します。
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
]
この実装は少し残念です。リストのインデックス作成操作を繰り返すと、処理!!
が進むにつれてコストが高くなり、漸近的なパフォーマンスがかなり低下します。より効率的な実装では、上記のアイデアを使用しますが、zipper を使用して実装します。したがって、無限グリッドを次のように 3 つの形状に分割します。
a1 a2 / a3 a4 ...
/
/
b1 / b2 b3 b4 ...
/
/
/
c1 c2 c3 c4 ...
---------------------------------
d1 d2 d3 d4 ...
. . . . .
. . . . .
. . . . .
左上の三角形は、既に発行したビットです。右上の四角形は、部分的に放出されたが結果に寄与する行になります。一番下の四角形は、まだ出力を開始していない行になります。まず、上の三角形と上の四角形が空になり、下の長方形がグリッド全体になります。各ステップで、上部の四辺形の各行の最初の要素を放出し (基本的に斜線を 1 つ移動)、下部の四角形から上部の四辺形に新しい行を 1 つ追加します (基本的に水平線を 1 つ下に移動します)。 )。
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次元デカルト積のニーズに使用できます。