Cartesian product of two or more lists
- Task
Show one or more idiomatic ways of generating the Cartesian product of two arbitrary lists in your language.
Demonstrate that your function/method correctly returns:
- {1, 2} × {3, 4} = {(1, 3), (1, 4), (2, 3), (2, 4)}
and, in contrast:
- {3, 4} × {1, 2} = {(3, 1), (3, 2), (4, 1), (4, 2)}
Also demonstrate, using your function/method, that the product of an empty list with any other list is empty.
- {1, 2} × {} = {}
- {} × {1, 2} = {}
For extra credit, show or write a function returning the n-ary product of an arbitrary number of lists, each of arbitrary length. Your function might, for example, accept a single argument which is itself a list of lists, and return the n-ary product of those lists.
Use your n-ary Cartesian product function to show the following products:
- {1776, 1789} × {7, 12} × {4, 14, 23} × {0, 1}}
- {1, 2, 3} × {30} × {500, 100}
- {1, 2, 3} × {} × {500, 100}
AppleScript
<lang AppleScript>-- CARTESIAN PRODUCTS ---------------------------------------------------------
-- Two lists:
-- cartProd :: [a] -> [b] -> [(a, b)] on cartProd(xs, ys)
script on |λ|(x) script on |λ|(y) x, y end |λ| end script concatMap(result, ys) end |λ| end script concatMap(result, xs)
end cartProd
-- N-ary – a function over a list of lists:
-- cartProdNary :: a -> a on cartProdNary(xss)
script on |λ|(xs, accs) script on |λ|(x) script on |λ|(a) {x & a} end |λ| end script concatMap(result, accs) end |λ| end script concatMap(result, xs) end |λ| end script foldr(result, {{}}, xss)
end cartProdNary
-- TESTS ---------------------------------------------------------------------- on run
set baseExamples to unlines(map(show, ¬ [cartProd({1, 2}, {3, 4}), ¬ cartProd({3, 4}, {1, 2}), ¬ cartProd({1, 2}, {}), ¬ cartProd({}, {1, 2})])) set naryA to unlines(map(show, ¬ cartProdNary([{1776, 1789}, {7, 12}, {4, 14, 23}, {0, 1}]))) set naryB to show(cartProdNary([{1, 2, 3}, {30}, {500, 100}])) set naryC to show(cartProdNary([{1, 2, 3}, {}, {500, 100}])) intercalate(linefeed & linefeed, {baseExamples, naryA, naryB, naryC})
end run
-- GENERIC FUNCTIONS ----------------------------------------------------------
-- concatMap :: (a -> [b]) -> [a] -> [b] on concatMap(f, xs)
set lst to {} set lng to length of xs tell mReturn(f) repeat with i from 1 to lng set lst to (lst & |λ|(item i of xs, i, xs)) end repeat end tell return lst
end concatMap
-- foldr :: (a -> b -> a) -> a -> [b] -> a on foldr(f, startValue, xs)
tell mReturn(f) set v to startValue set lng to length of xs repeat with i from lng to 1 by -1 set v to |λ|(v, item i of xs, i, xs) end repeat return v end tell
end foldr
-- intercalate :: Text -> [Text] -> Text on intercalate(strText, lstText)
set {dlm, my text item delimiters} to {my text item delimiters, strText} set strJoined to lstText as text set my text item delimiters to dlm return strJoined
end intercalate
-- map :: (a -> b) -> [a] -> [b] on map(f, xs)
tell mReturn(f) set lng to length of xs set lst to {} repeat with i from 1 to lng set end of lst to |λ|(item i of xs, i, xs) end repeat return lst end tell
end map
-- Lift 2nd class handler function into 1st class script wrapper -- mReturn :: Handler -> Script on mReturn(f)
if class of f is script then f else script property |λ| : f end script end if
end mReturn
-- show :: a -> String on show(e)
set c to class of e if c = list then script serialized on |λ|(v) show(v) end |λ| end script "[" & intercalate(", ", map(serialized, e)) & "]" else if c = record then script showField on |λ|(kv) set {k, ev} to kv "\"" & k & "\":" & show(ev) end |λ| end script "{" & intercalate(", ", ¬ map(showField, zip(allKeys(e), allValues(e)))) & "}" else if c = date then "\"" & iso8601Z(e) & "\"" else if c = text then "\"" & e & "\"" else if (c = integer or c = real) then e as text else if c = class then "null" else try e as text on error ("«" & c as text) & "»" end try end if
end show
-- unlines :: [String] -> String on unlines(xs)
intercalate(linefeed, xs)
end unlines</lang>
- Output:
[[1, 3], [1, 4], [2, 3], [2, 4]] [[3, 1], [3, 2], [4, 1], [4, 2]] [] [] [0, 4, 7, 1776] [0, 4, 7, 1789] [0, 4, 12, 1776] [0, 4, 12, 1789] [0, 14, 7, 1776] [0, 14, 7, 1789] [0, 14, 12, 1776] [0, 14, 12, 1789] [0, 23, 7, 1776] [0, 23, 7, 1789] [0, 23, 12, 1776] [0, 23, 12, 1789] [1, 4, 7, 1776] [1, 4, 7, 1789] [1, 4, 12, 1776] [1, 4, 12, 1789] [1, 14, 7, 1776] [1, 14, 7, 1789] [1, 14, 12, 1776] [1, 14, 12, 1789] [1, 23, 7, 1776] [1, 23, 7, 1789] [1, 23, 12, 1776] [1, 23, 12, 1789] [[500, 30, 1], [500, 30, 2], [500, 30, 3], [100, 30, 1], [100, 30, 2], [100, 30, 3]] []
Haskell
Various routes can be taken to Cartesian products in Haskell. For the product of two lists we could write: <lang Haskell>cartProd :: [a] -> [a] -> [(a, a)] cartProd xs ys =
[ (x, y) | x <- xs , y <- ys ]</lang>
Or, more directly: <lang Haskell>cartProd :: [a] -> [a] -> [(a, a)] cartProd xs ys = xs >>= \x -> ys >>= \y -> [(x, y)]</lang>
We might test either of these with: <lang haskell>main :: IO () main =
mapM_ print $ uncurry cartProd <$> [([1, 2], [3, 4]), ([3, 4], [1, 2]), ([1, 2], []), ([], [1, 2])]</lang>
- Output:
[(1,3),(1,4),(2,3),(2,4)] [(3,1),(3,2),(4,1),(4,2)] [] []
For the n-ary Cartesian product of an arbitrary number of lists, we could apply the Prelude's standard sequence function to a list of lists, or we could define ourselves an equivalent function over a list of lists in terms of a fold:
For example as: <lang haskell>foldr (\xs as -> xs >>= \x -> as >>= \a -> [x : a]) [[]]</lang> or, equivalently, as: <lang haskell>foldr
(\xs as -> [ x : a | x <- xs , a <- as ]) [[]]</lang>
<lang haskell>main :: IO () main = do
mapM_ print $ sequence [[1776, 1789], [7,12], [4, 14, 23], [0,1]] putStrLn "" print $ sequence [[1,2,3], [30], [500, 100]] putStrLn "" print $ sequence [[1,2,3], [], [500, 100]]</lang>
- Output:
[1776,7,4,0] [1776,7,4,1] [1776,7,14,0] [1776,7,14,1] [1776,7,23,0] [1776,7,23,1] [1776,12,4,0] [1776,12,4,1] [1776,12,14,0] [1776,12,14,1] [1776,12,23,0] [1776,12,23,1] [1789,7,4,0] [1789,7,4,1] [1789,7,14,0] [1789,7,14,1] [1789,7,23,0] [1789,7,23,1] [1789,12,4,0] [1789,12,4,1] [1789,12,14,0] [1789,12,14,1] [1789,12,23,0] [1789,12,23,1] [[1,30,500],[1,30,100],[2,30,500],[2,30,100],[3,30,500],[3,30,100]] []
JavaScript
ES6
For the Cartesian product of just two lists: <lang JavaScript>(() => {
// CARTESIAN PRODUCT OF TWO LISTS -----------------------------------------
// cartProd :: [a] -> [b] -> a, b const cartProd = (xs, ys) => concatMap((x => concatMap(y => [ [x, y] ], ys)), xs);
// GENERIC FUNCTIONS ------------------------------------------------------
// concatMap :: (a -> [b]) -> [a] -> [b] const concatMap = (f, xs) => [].concat.apply([], xs.map(f));
// map :: (a -> b) -> [a] -> [b] const map = (f, xs) => xs.map(f);
// show :: a -> String const show = x => JSON.stringify(x); //, null, 2);
// unlines :: [String] -> String const unlines = xs => xs.join('\n');
// TEST ------------------------------------------------------------------- return unlines(map(show, [ cartProd([1, 2], [3, 4]), cartProd([3, 4], [1, 2]), cartProd([1, 2], []), cartProd([], [1, 2]), ]));
})();</lang>
- Output:
[[1,3],[1,4],[2,3],[2,4]] [[3,1],[3,2],[4,1],[4,2]] [] []
For the n-ary Cartesian product over a list of lists: <lang JavaScript>(() => {
// n-ary Cartesian product of a list of lists
// cartProdN :: a -> a const cartProdN = lists => foldr((xs, as) => bind(xs, x => bind(as, a => [x.concat(a)])), [ [] ], lists);
// GENERIC FUNCTIONS ------------------------------------------------------
// concatMap :: [a] -> (a -> [b]) -> [b] const bind = (xs, f) => [].concat.apply([], xs.map(f));
// foldr (a -> b -> b) -> b -> [a] -> b const foldr = (f, a, xs) => xs.reduceRight(f, a);
// intercalate :: String -> [a] -> String const intercalate = (s, xs) => xs.join(s);
// map :: (a -> b) -> [a] -> [b] const map = (f, xs) => xs.map(f);
// show :: a -> String const show = x => JSON.stringify(x);
// unlines :: [String] -> String const unlines = xs => xs.join('\n');
// TEST ------------------------------------------------------------------- return intercalate('\n\n', [unlines(map(show, cartProdN([ [1776, 1789], [7, 12], [4, 14, 23], [0, 1] ]))), show(cartProdN([ [1, 2, 3], [30], [50, 100] ])), show(cartProdN([ [1, 2, 3], [], [50, 100] ])) ])
})();</lang>
- Output:
[0,4,7,1776] [0,4,7,1789] [0,4,12,1776] [0,4,12,1789] [0,14,7,1776] [0,14,7,1789] [0,14,12,1776] [0,14,12,1789] [0,23,7,1776] [0,23,7,1789] [0,23,12,1776] [0,23,12,1789] [1,4,7,1776] [1,4,7,1789] [1,4,12,1776] [1,4,12,1789] [1,14,7,1776] [1,14,7,1789] [1,14,12,1776] [1,14,12,1789] [1,23,7,1776] [1,23,7,1789] [1,23,12,1776] [1,23,12,1789] [[50,30,1],[50,30,2],[50,30,3],[100,30,1],[100,30,2],[100,30,3]] []
Perl 6
Nominally the cross meta operator X does this, but doesn't gracefully handle the case of an empty list. We can easily wrap it in a subroutine with appropriate filtering however.
<lang perl6>sub cartesian-product (**@list) { ( so none(@list».elems) == 0 ) ?? [X] @list !! () }
- Testing various Cartesian products
for
( (1, 2), (3, 4) ), ( (3, 4), (1, 2) ), ( (1, 2), ( ) ), ( ( ), ( 1, 2 ) ), ( (1776, 1789), (7, 12), (4, 14, 23), (0, 1) ), ( (1, 2, 3), (30), (500, 100) ), ( (1, 2, 3), (), (500, 100) ) -> $list { say "\nLists: { $list.perl }\nCartesian Product:"; say cartesian-product( |$list ).List.perl; }</lang>
- Output:
Lists: $((1, 2), (3, 4)) Cartesian Product: ((1, 3), (1, 4), (2, 3), (2, 4)) Lists: $((3, 4), (1, 2)) Cartesian Product: ((3, 1), (3, 2), (4, 1), (4, 2)) Lists: $((1, 2), ()) Cartesian Product: () Lists: $((), (1, 2)) Cartesian Product: () Lists: $((1776, 1789), (7, 12), (4, 14, 23), (0, 1)) Cartesian Product: ((1776, 7, 4, 0), (1776, 7, 4, 1), (1776, 7, 14, 0), (1776, 7, 14, 1), (1776, 7, 23, 0), (1776, 7, 23, 1), (1776, 12, 4, 0), (1776, 12, 4, 1), (1776, 12, 14, 0), (1776, 12, 14, 1), (1776, 12, 23, 0), (1776, 12, 23, 1), (1789, 7, 4, 0), (1789, 7, 4, 1), (1789, 7, 14, 0), (1789, 7, 14, 1), (1789, 7, 23, 0), (1789, 7, 23, 1), (1789, 12, 4, 0), (1789, 12, 4, 1), (1789, 12, 14, 0), (1789, 12, 14, 1), (1789, 12, 23, 0), (1789, 12, 23, 1)) Lists: $((1, 2, 3), 30, (500, 100)) Cartesian Product: ((1, 30, 500), (1, 30, 100), (2, 30, 500), (2, 30, 100), (3, 30, 500), (3, 30, 100)) Lists: $((1, 2, 3), (), (500, 100)) Cartesian Product: ()