I'm trying to implement a method that will take a list of lists and return a the cartesian product of these lists.
Here's what I have so far:
(defn cart
([] '())
([l1] (map list l1))
([l1 l2]
(map
(fn f[x] (map
(fn g [y] (list x y))
l2))
l1)
)
)
(defn cartesian-product [& lists]
(reduce cart lists)
)
;test cases
(println (cartesian-product '(a b) '(c d))) ; ((a c) (a d) (b c) (b d))
(println (cartesian-product ())) ;()
(println (cartesian-product '(0 1))) ; ((0) (1))
(println (cartesian-product '(0 1) '(0 1))) ; ((0 0) (0 1) (1 0) (1 1))
(println (apply cartesian-product (take 4 (repeat (range 2))))) ;((0 0 0 0) (0 0 0 1) (0 0 1 0) (0 0 1 1) (0 1 0 0) (0 1 0 1) (0 1 1 0) (0 1 1 1) (1 0 0 0) (1 0 0 1) (1 0 1 0) (1 0 1 1) (1 1 0 0) (1 1 0 1) (1 1 1 0) (1 1 1 1))
The problem is my solution is really 'brackety'.
(((a c) (a d)) ((b c) (b d)))
()
(0 1)
(((0 0) (0 1)) ((1 0) (1 1)))
(((((((0 0) (0 1)) 0) (((0 0) (0 1)) 1)) 0) (((((0 0) (0 1)) 0) (((0 0) (0 1)) 1)) 1)) ((((((1 0) (1 1)) 0) (((1 0) (1 1)) 1)) 0) (((((1 0) (1 1)) 0) (((1 0) (1 1)) 1)) 1)))
I tried adding
(apply concat(reduce cart lists))
but then I get a crash like so:
((a c) (a d) (b c) (b d))
()
IllegalArgumentException Don't know how to create ISeq from: java.lang.Long clojure.lang.RT.seqFrom (RT.java:494)
So, I think I'm close but missing something. However since I'm so new to clojure and functional programming I could be on the completely wrong track. Please help! :)
Personally, I would use amalloy's
for
solution. My general rule of thumb is that if my loop can be expressed as a singlemap
/filter
/etc call with a simple function argument (so a function name or shortfn
/#()
form), its better to use the function. As soon as it gets more complex than that, afor
expression is far easier to read. In particular,for
is far better than nested maps. That said, if I didn't usefor
here, this is how I'd write the function:Things to note: First, there's no need for the reduce. Recursion can handle it just fine.
Second, only two cases. We can call the function just fine on an empty list, so all we care about is empty vs non-empty.
Third, as amalloy explained, the correct value of
(cart)
is'(())
. This is actually rather subtle, and I reliably mess this up when I write a function like this. If you walk through a simple case very carefully, you should be able to see why that value makes the recursion work.Fourth, I generally don't like to use
fn
. This is more of a personal preference, but I always use#()
,partial
, orcomp
if I can get away with it.#()
is definitely idiomatic for smaller functions, though the other two are a bit less common.Fifth, some style notes. The biggest issue is indentation. The best suggestion here is to find an editor that auto-indents lisp code. Auto-indentation is one of the most important things for your editor to provide, since it makes it blindingly obvious when your parens don't match up. Also, closing parens never go on their own line,
fn
s don't need internal names unless you are planning on recursing, and I generally have a few more newlines than you do. I like to think that my code above is reasonably decently styled, and as another example, here is how I would format your code:I would check https://github.com/clojure/math.combinatorics it has
(combo/cartesian-product [1 2] [3 4]) ;;=> ((1 3) (1 4) (2 3) (2 4))
I know I'm late to the party -- I just wanted to add a different approach, for the sake of completeness.
Compared to amalloy's approach, it is lazy too (the parameter lists are eagerly evaluated, though) and slightly faster when all results are required (I tested them both with the demo code below), however it is prone to stack overflow (much like the underlying
for
comprehension it generates and evaluates) as the number of lists increases. Also, keep in mind thateval
has a limit to the size of the code it can be passed to.Consider first a single instance of the problem: You want to find the cartesian product of
[:a :b :c]
and'(1 2 3)
. The obvious solution is to use afor
comprehension, like this:Now, the question is: Is it possible to generalize this in a way that works with an arbitrary number of lists? The answer here is affirmative. This is what the following macro does:
Essentially, you have the compiler generate the appropriate
for
comprehension for you. Converting this to a function is pretty straightforward, but there is a small catch:Lists that are left unquoted are treated as function calls, which is why quoting
%2
is necessary here.Online Demo:
For most purposes Alan's answer is great as you get a lazy comprehension, and a lazy seq will not cause a stack overflow as you realize its members, even if you do not use (recur).
I was interested in trying to craft the tail recursive version with explicit recur, not the least of which because laziness wasn't going to be of any help in my application, but also for fun and giggles:
For the sake of comparison, in the spirit of the original
This is a lot easier to do as a for-comprehension than by trying to work out the recursion manually:
The base case is obvious (it needs to be a list containing the empty list, not the empty list itself, since there is one way to take a cartesian product of no lists). In the recursive case, you just iterate over each element
x
of the first collection, and then over each cartesian product of the rest of the lists, prepending thex
you've chosen.Note that it's important to write the two clauses of the
for
comprehension in this slightly unnatural order: swapping them results in a substantial slowdown. The reason for this is to avoid duplicating work. The body of the second binding will be evaluated once for each item in the first binding, which (if you wrote the clauses in the wrong order) would mean many wasted copies of the expensive recursive clause. If you wish to be extra careful, you can make it clear that the two clauses are independent, by instead writing: