6

I am new to f#

I am try to calculate the Cartesian products of a list of numbers. I "borrowed" this.

let xs = [1..99]
let ys = [1..99]
seq {for x in xs do for y in ys do yield x * y}

Is there a better or more elegant way?

Gary

Gary
  • 1,847
  • 2
  • 14
  • 22
  • Related question here: http://stackoverflow.com/questions/482866/f-cross-product-of-two-lists – Benjol Jun 02 '09 at 06:56

3 Answers3

9

Another possibiltiy to tackle the problem based on functionality provided by the List module would be:

let xs = [1..99]
let ys = [1..99]
let zs = xs |> List.collect (fun x -> ys |> List.map (fun y -> x*y))

which avoids the extra calls to .concat and should also do the job.

But I'd stick with your solution. It should be the most readable which is a real matchwinner. (Just try to read the codes out loud. Yours is perfectly understandable and Noldorins or mine are not.)

leen
  • 8,568
  • 3
  • 23
  • 18
  • 1
    Ah, I was wondering where the `mapconcat` method went! It seems it was just renamed to `collect` in the newest version. – Noldorin Jun 01 '09 at 18:28
  • Regarding how understandable/readable of each of our solutions is, I think this is somewhat subjective. We have certainly provided more typically "functional" style solutions, whereas the OP suggested a (perfectly good) solution that uses typically "imperative" constructs. I suspect someone from a functional background would find either of our methods clearer. – Noldorin Jun 01 '09 at 20:29
7

Disclaimer: I don't have a machine with the current F# installed, so I can't test my code. Basically, though, if you steal sequence from Haskell, you can write your program as

let cartesian = sequence >> List.map product

and run it as

cartesian [[1..99]; [1..99]]

Here's how to write sequence. It's a generalised version of the sequence expression you wrote. It just handles an unlimited number of lists: { for x in xs do for y in ys do for z in zs ... yield [x;y;z;...] }.

let rec sequence = function
  | [] -> Seq.singleton []
  | (l::ls) -> seq { for x in l do for xs in sequence ls do yield (x::xs) }
// also you'll need product to do the multiplication
let product = Seq.fold_left1 ( * )

Then you can write your program as

let cartesian xs ys = [xs; ys] |> sequence |> List.map product
// ... or one-argument, point-free style:
let cartesian' = sequence >> Seq.map product

You might have to change some Seqs to Lists.

However, the number of people who can guess the meaning of your non-general list comprehension is probably a lot more than will recognise the name sequence, so you're probably better off with the list comprehension. sequence comes in handy any time you want to run a whole list of computation expressions, though.

berdario
  • 1,851
  • 18
  • 29
Nathan Shively-Sanders
  • 18,329
  • 4
  • 46
  • 56
2

There is indeed a slightly more elegant way (at least in the functional sense) to calculate the Cartesian products, which uses the functions that exist within the List class. (There's no need to involve sequences or loops here, at least not directly.)

Try this:

let xs = [1..99]
let ys = [1..99]
xs |> List.map(fun x -> ys |> List.map(fun y -> x * y)) |> List.concat

Slightly longer admittedly, though more functional in style, it would seem.

Noldorin
  • 144,213
  • 56
  • 264
  • 302
  • 1
    Actually the code in the question is closer to a list comprehension than your code, in my opinion. Yours is more like the desugared version of a list comprehension. – Ganesh Sittampalam Jun 01 '09 at 21:10
  • Yeah, I didn't mean list comprehensions actually - I'm not sure there's a special name for it, but I meant the List.* functions. – Noldorin Jun 02 '09 at 03:19
  • Can you elaborate on what you mean by "there is no need to involve sequences", I don't understand what the benefits are of using List.map over Seq.map? – ninegrid Jun 02 '09 at 21:40
  • 1
    @thinkhard: The Seq type in F# is actually just an alias for IEnumerable in .NET, with a few additional functions (e.g. `map`). By using `Seq.map`, you're actually iterating over the list as you would using an `IEnumerable`, whereas when using `List.map` you're treating it specifically like as a linked list, thus not adding the overhead (and indirection) of iterables. – Noldorin Jun 02 '09 at 21:56