9

I would like to make a method where I could give it a list of lengths and it would return all combinations of cartesian coordinates up to those lengths. Easier to explain with an example:

cart [2,5]
Prelude> [ [0,0],[0,1],[0,2],[0,3],[0,4],[1,0],[1,1],[1,2],[1,3],[1,4] ]

cart [2,2,2]
Prelude> [ [0,0,0],[0,0,1],[0,1,0],[0,1,1],[1,0,0],[1,0,1],[1,1,0],[1,1,1] ]

A simple list comprehension won't work because I don't know how long the lists are going to be. While I love Haskell's simplicity for many problems, this is one that I could write procedurally (in C or something) in 5 minutes whereas Haskell gives me an aneurysm!

A solution to this specific problem would help me out a lot; I'd also love to hear about your thought processes when tackling stuff like this.

Rodrigo de Azevedo
  • 1,097
  • 9
  • 17
cspyr0
  • 101
  • 4
  • Wow, thanks Kenny and Dave. I never thought to throw a recursive call into the list comprehension definition - very cool. The version using map and fold is great. I try to use the higher order functions when I can think of a way, so this is a great example to study! – cspyr0 Apr 11 '10 at 08:40
  • as long as you're using higher order functions, know that it should not be cryptic. and using the right functions help get there, `sequence` is what you need here. – yairchu Apr 11 '10 at 09:36
  • Thank you yairchu for both the concise and clear solution and for introducing me to hoogle. How did I do anything without this?! – cspyr0 Apr 11 '10 at 14:19
  • for another interesting tool by ndmitchell check out hlint. it suggests a further shortening to my solution as newacct pointed out. – yairchu Apr 11 '10 at 20:49

3 Answers3

13

Umm..

cart = sequence . map (enumFromTo 0 . subtract 1)

It's reasonable to expect that a [[a]] -> [[a]] function doing what we expect already exists in the library. So if one is not familiar with sequence, finding it is a simple matter of hoogling it.

Edit: as newacct pointed out:

cart = mapM (enumFromTo 0 . subtract 1)

This can also be found by feeding the previous solution to HLint.

yairchu
  • 23,680
  • 7
  • 69
  • 109
12

This can be solved recursively. First, the Cartesian product of nothing is {∅}:

cart [] = [[]]

(Or define just the 1-element form if the empty product is invalid:

cart [x] = [[i] | i <- [0 .. x-1]]

)

Then, the Cartesian product of x:xs can be written as

cart (x:xs) = [i:rest | i <- [0 .. x-1], rest <- cart xs]

In general, if you what to write a function f that requires the list's length N, try to think of a way to make f(N) depends on smaller lists e.g. f(N - 1) only, then solve the base case f(0) or f(1) etc. This transforms the problem into a recursion that can be easily solved.

kennytm
  • 510,854
  • 105
  • 1,084
  • 1,005
7

I bet your procedural solution would involve recursion. Our Haskell solution will involve recursion too.

So, recursion. First the recursive case.

cart (c : cs) = [i : r | i <- [0 .. c-1], r <- rcart]
  where rcart = cart cs

Here we're just saying that for each possible initial co-ordinate, and each possible combination of cartesian co-ordinates from the remaining lengths, we do the obvious thing of combining the co-ordinate with the remaining co-ordinates.

Then the base case.

cart [] = [[]]

You might think cart [] = []. I did at first. But think about what the recursive case requires from the base case.

dave4420
  • 46,404
  • 6
  • 118
  • 152