[JavaScript] 求解任意n個集合的笛卡爾積

weixin_33895657發表於2016-07-13

假設我們有兩個集合,A={1, 2, 3}B={'a', 'b'}
我們怎樣求解這兩個集合的笛卡爾積呢?

A × B = {(1, 'a'), (1, 'b'), (2, 'a'), (2, 'b'), (3, 'a'), (3, 'b')}

我們想到Haskell中的list comprehension正好有這種功能。

(1)list comprehension

ghci> [(x, y) | x <- [1, 2, 3], y <- ['a','b']]
ghci> [(1,'a'), (1,'b'), (2,'a'), (2,'b'), (3,'a'), (3,'b')]

(2)do-notaion

list comprehension實際上是do-notation的語法糖,

do
    x <- [1, 2, 3]
    y <- ['a','b']
    return (x, y)

(3)函式>>=

然而,do-notation也是一個語法糖,

[1, 2, 3] >>= (\x -> ['a', 'b'] >>= (\y -> return (x, y)))

可是>>=是什麼呢?它是一個高階函式,我們來看list monad的定義,

instance Monad [] where
    return x = [x]
    xs >>= f = concat (map f xs)
    fail _ = []

其中,

concat :: [[a]] -> [a]
map :: (a -> b) -> [a] -> [b]

(4)我們來證明一下

[1, 2, 3] >>= (\x -> ['a', 'b'] >>= (\y -> return (x, y)))
concat $ map (\x -> ['a', 'b'] >>= (\y -> return (x, y))) [1, 2, 3]

我們先看

(\x -> ['a', 'b'] >>= (\y -> return (x, y))) 1
['a', 'b'] >>= (\y -> return (1, y))
concat $ map (\y -> return (1, y)) ['a', 'b']

我們再看

(\y -> return (1, y)) 'a'
return (1, 'a')
[(1, 'a')]

然後,後退一步

concat $ map (\y -> return (1, y)) ['a', 'b']
concat [[(1, 'a')], [(1, 'b')]]
[(1, 'a'), (1, 'b')]

再後退一步

concat $ map (\x -> ['a', 'b'] >>= (\y -> return (x, y))) [1, 2, 3]
concat [[(1, 'a'), (1, 'b')], [(2, 'a'), (2, 'b')], [(3, 'a'), (3, 'b')]]
[(1,'a'), (1,'b'), (2,'a'), (2,'b'), (3,'a'), (3,'b')]

證畢。

(5)用JavaScript來實現

先把>>=都去掉,

[1, 2, 3] >>= (\x -> ['a', 'b'] >>= (\y -> return (x, y)))
concat $ map (\x -> ['a', 'b'] >>= (\y -> return (x, y))) [1, 2, 3]
concat $ map (\x -> concat $ map (\y -> return (x, y)) ['a', 'b']) [1, 2, 3]

然後,分別實現concatmap

function concat(array) {
    return [].concat.apply([], array);
}
function map(fn, array) {
    return [].map.call(array, fn);
}

concat(map(
    x=>concat(map(
        y=>[[x,y]], 
        ['a', 'b']
    )),
    [1, 2, 3]
));

簡化一下:

function flatMap(fn, array) {
    return concat(map(fn, array));
}

flatMap(
    x=>flatMap(
        y=>[[x,y]], 
        ['a', 'b']
    ),
    [1, 2, 3]
);

(6)任意n個集合的笛卡爾積

export default function cartesianProduct(sets) {
    let head = sets.shift();
    if (sets.length === 0) {
        return map(
            item => [item],
            head
        );
    }

    let tailProduct = cartesianProduct(sets);
    return flatMap(
        item => flatMap(
            items => [[item, ...items]],
            tailProduct
        ),
        head
    );
}

function concat(array) {
    return [].concat.apply([], array);
}

function map(fn, array) {
    return [].map.call(array, fn);
}

function flatMap(fn, array) {
    return concat(map(fn, array));
}

測試,

cartesianProduct([[1, 2, 3],['a', 'b']])
=== [[1, "a"], [1, "b"], [2, "a"], [2, "b"], [3, "a"], [3, "b"]]

相關文章