Lazy cartesian product of multiple sequences (sequence of sequences)
Asked Answered
N

2

8

Can you suggest simpler and clearer way to write this function?

let cartesian_product sequences = 
    let step acc sequence = seq { 
        for x in acc do 
        for y in sequence do 
        yield Seq.append x [y] }
    Seq.fold step (Seq.singleton Seq.empty) sequences 
Nereus answered 27/6, 2011 at 18:9 Comment(2)
See #3334929Musselman
And for a C# solution that is basically the same as this F# solution, see #3094122Heimer
K
2

Less elegant, but (seems to be) faster solution:

let cartesian_product2 sequences = 
    let step acc sequence = seq { 
        for x in acc do 
        for y in sequence do 
        yield seq { yield! x ; yield y } }
    Seq.fold step (Seq.singleton Seq.empty) sequences 

;

> cartesian items |> Seq.length;;
Real: 00:00:00.405, CPU: 00:00:00.405, GC gen0: 37, gen1: 1, gen2: 0
val it : int = 1000000
> cartesian_product2 items |> Seq.length;;
Real: 00:00:00.228, CPU: 00:00:00.234, GC gen0: 18, gen1: 0, gen2: 0
val it : int = 1000000
Kaif answered 28/6, 2011 at 15:22 Comment(3)
This seems faster because you're returning seq<seq<_>> instead of seq<list<_>>, i.e., your inner sequences aren't being evaluated. Try cartesian_product2 items |> Seq.map Seq.toList |> Seq.length.Quadrumanous
@Daniel: Good point. But it's still 2 times faster than the author's solution (while still being lazy, as required).Joke
is this one faster for building the sequence and materializing it? or neither because the test didn't materialize the sub-sequences ?Donothing
Q
2

I benchmarked the function Juliet linked to:

let items = List.init 6 (fun _ -> [0..9])
cart1 items |> Seq.length |> ignore

Real: 00:00:03.324, CPU: 00:00:03.322, GC gen0: 80, gen1: 0, gen2: 0

and a slightly modified (to make it an apples-to-apples comparison) version of yours:

let cartesian items =
  items |> Seq.fold (fun acc s ->
    seq { for x in acc do for y in s do yield x @ [y] }) (Seq.singleton [])

cartesian items |> Seq.length |> ignore

Real: 00:00:00.763, CPU: 00:00:00.780, GC gen0: 37, gen1: 2, gen2: 1

Yours is significantly faster (and causes fewer GCs). Looks to me that what you have is good.

Quadrumanous answered 27/6, 2011 at 20:15 Comment(1)
Old question, but you have a gen 2 collection there. How does that amount to fewer GCs?Halfback
K
2

Less elegant, but (seems to be) faster solution:

let cartesian_product2 sequences = 
    let step acc sequence = seq { 
        for x in acc do 
        for y in sequence do 
        yield seq { yield! x ; yield y } }
    Seq.fold step (Seq.singleton Seq.empty) sequences 

;

> cartesian items |> Seq.length;;
Real: 00:00:00.405, CPU: 00:00:00.405, GC gen0: 37, gen1: 1, gen2: 0
val it : int = 1000000
> cartesian_product2 items |> Seq.length;;
Real: 00:00:00.228, CPU: 00:00:00.234, GC gen0: 18, gen1: 0, gen2: 0
val it : int = 1000000
Kaif answered 28/6, 2011 at 15:22 Comment(3)
This seems faster because you're returning seq<seq<_>> instead of seq<list<_>>, i.e., your inner sequences aren't being evaluated. Try cartesian_product2 items |> Seq.map Seq.toList |> Seq.length.Quadrumanous
@Daniel: Good point. But it's still 2 times faster than the author's solution (while still being lazy, as required).Joke
is this one faster for building the sequence and materializing it? or neither because the test didn't materialize the sub-sequences ?Donothing

© 2022 - 2024 — McMap. All rights reserved.