web-dev-qa-db-ja.com

Haskellの2つのリストのデカルト積

Haskellで2つのリストのデカルト積を生成したいのですが、どうすればよいかわかりません。デカルト積は、リスト要素のすべての組み合わせを提供します。

xs = [1,2,3]
ys = [4,5,6]

cartProd :: [a] -> [b] -> [(a,b)]
cartProd xs ys ==> [(1,4),(1,5),(1,6),(2,4),(2,5),(2,6),(3,4),(3,5),(3,6)]

これは実際の宿題の質問ではなく、そのような質問とは関係ありませんが、この問題を解決する方法は、私が行き詰まっている問題に役立つかもしれません。

64
Callum Rogers

これはリストの内包表記を使えば非常に簡単です。リストxsysのデカルト積を取得するには、xの各要素xsおよびyの各要素ysのTuple (x,y)を取得するだけです。

これにより、次のリストを理解できます。

cartProd xs ys = [(x,y) | x <- xs, y <- ys]
100
sepp2k

他の回答が指摘しているように、リスト内包表記を使用することは、Haskellでこれを行う最も自然な方法です。

Haskellを学習していて、Monadなどの型クラスに関する直感の開発に取り組みたい場合は、この短い定義が同等である理由を理解するのは楽しい練習です。

import Control.Monad (liftM2)

cartProd :: [a] -> [b] -> [(a, b)]
cartProd = liftM2 (,)

おそらくこれを実際のコードで記述したいとは思わないでしょうが、基本的な考え方は、Haskellで常に目にするものです。私たちはliftM2非モナド関数を持ち上げる(,)からモナド—この場合は具体的にはリストモナドです。

これが意味をなさないか、役に立たない場合は、それを忘れてください。これは、問題を見るための別の方法です。

62
Travis Brown

入力リストが同じタイプの場合、sequenceを使用して(Listモナドを使用して)任意の数のリストのデカルト積を取得できます。これにより、タプルのリストではなくリストのリストが取得されます。

> sequence [[1,2,3],[4,5,6]]
[[1,4],[1,5],[1,6],[2,4],[2,5],[2,6],[3,4],[3,5],[3,6]]
52
newacct

Applicative Functorを使用してこれを行う非常にエレガントな方法があります。

import Control.Applicative

(,) <$> [1,2,3] <*> [4,5,6]
-- [(1,4),(1,5),(1,6),(2,4),(2,5),(2,6),(3,4),(3,5),(3,6)]

基本的な考え方は、「ラップされた」引数に関数を適用することです。

(+) <$> (Just 4) <*> (Just 10)
-- Just 14

リストの場合、関数はすべての組み合わせに適用されるので、あなたがしなければならないのは、それらを「タプル」することです(,)

http://learnyouahaskell.com/functors-applicative-functors-and-monoids#applicative-functors または(より理論的な) http://www.soi.city.ac。 uk /〜ross/papers/Applicative.pdf 詳細.

45
Landei

他の答えは、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
    ]

この実装は少し残念です。リストのインデックス作成操作!!を繰り返すと、コストが高くなり、漸近的なパフォーマンスが非常に悪くなります。より効率的な実装では、上記のアイデアを取り入れますが、ジッパーを使用して実装します。そのため、無限グリッドを次のような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]

これは少し複雑に見えますが、非常に効率的です。また、より単純なバージョンでパントした境界チェックも処理します。

しかし、もちろんこのコードをすべて自分で書くべきではありません!代わりに、 niverse パッケージを使用する必要があります。 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)]

一緒に製品を作成するリストが多数ある場合、(+*+)を繰り返すと特定のリストに不公平なバイアスがかかる可能性があります。 n次元デカルト積のニーズにchoices :: [[a]] -> [[a]]を使用できます。

14
Daniel Wagner

do表記を使用したさらに別の方法:

cartProd :: [a] -> [b] -> [(a,b)]
cartProd xs ys = do x <- xs
                    y <- ys
                    return (x,y)
11
gawi

これを実現するさらに別の方法は、アプリケーションを使用することです。

import Control.Applicative

cartProd :: [a] -> [b] -> [(a,b)]
cartProd xs ys = (,) <$> xs <*> ys
11
Paul

他の人がすでに指摘しているように、正しい方法はリスト内包表記を使用することですが、何らかの理由でリスト内包表記を使用せずにそれを実行したい場合は、これを行うことができます:

cartProd :: [a] -> [b] -> [(a,b)]
cartProd xs [] = []
cartProd [] ys = []
cartProd (x:xs) ys = map (\y -> (x,y)) ys ++ cartProd xs ys
11
Stuart Golodetz

これを行う非常に簡単な方法の1つは、リストの内包表記を使用することです。

cartProd :: [a] -> [b] -> [(a, b)]
cartProd xs ys = [(x, y) | x <- xs, y <- ys]

Haskellの専門家ではありませんが、私はこれをどのように行うのでしょうか(とにかく)。

6

何かのようなもの:

cartProd x y = [(a,b) | a <- x, b <- y]
5
vichle

sequenceingの仕事です。モナドの実装は次のようになります:

cartesian :: [[a]] -> [[a]]
cartesian [] = return []
cartesian (x:xs) = x >>= \x' -> cartesian xs >>= \xs' -> return (x':xs')

*Main> cartesian [[1,2,3],[4,5,6]]
[[1,4],[1,5],[1,6],[2,4],[2,5],[2,6],[3,4],[3,5],[3,6]]

お気づきかもしれませんが、上記は純粋関数によるmapの実装に似ていますが、モナド型です。したがって、それを単純化することができます

cartesian :: [[a]] -> [[a]]
cartesian = mapM id

*Main> cartesian [[1,2,3],[4,5,6]]
[[1,4],[1,5],[1,6],[2,4],[2,5],[2,6],[3,4],[3,5],[3,6]]
2
Redu

再帰的なパターンマッチングのみを使用して、愛好家のためにもう1つの方法を追加するだけです。

cartProd :: [a]->[b]->[(a,b)]
cartProd _ []=[]
cartProd [] _ = []
cartProd (x:xs) (y:ys) = [(x,y)] ++ cartProd [x] ys  ++ cartProd xs ys ++  cartProd xs [y] 
0
Manoj R

以下は、n項デカルト積の私の実装です。

crossProduct :: [[a]] -> [[a]]
crossProduct (axis:[]) = [ [v] | v <- axis ]
crossProduct (axis:rest) = [ v:r | v <- axis, r <- crossProduct rest ]
0