Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using LINQ to iterate combinations [duplicate]

Possible Duplicate:
Generating all Possible Combinations
Is there a good LINQ way to do a cartesian product?
How to generate combination of N elements with limited supply of 2 each without explicit nested loops

I have a list of lists, and I want to iterate all the possible combinations where I choose one element from each inner list. This is pretty straightforward if I know at compile-time how many lists there are, but how can I do it when I don't know in advance how many lists there will be?

If I have three lists (and if I know, at compile-time, that there will be exactly three lists), and I want all the combinations of choosing a single element from each of the three lists, I can do that easily with a LINQ query:

var list1 = new[] { 1, 2 };
var list2 = new[] { 3, 4 };
var list3 = new[] { 5, 6 };
var combinations = from item1 in list1
                   from item2 in list2
                   from item3 in list3
                   select new[] { item1, item2, item3 };
// Results:
// {1, 3, 5}
// {1, 3, 6}
// {1, 4, 5}
// {1, 4, 6}
// {2, 3, 5}
// {2, 3, 6}
// {2, 4, 5}
// {2, 4, 6}

But how can I do the same thing when I don't know at compile-time how many lists there will be?

var lists = new[] {
    new[] { 1, 2 },
    new[] { 3, 4 },
    new[] { 5, 6 } };
var combinations = ???;

// This particular example happens to be the same inputs as above, so it
// has the same expected outputs. But there could be two lists instead,
// or four, so the three hard-coded "from" clauses won't work.

It seems like this should actually be doable in LINQ -- SelectMany already does the equivalent of two nested foreach loops, so all I need to do is do a bunch of SelectMany calls and then combine all the results with another SelectMany. Or something. But when it starts getting meta like that, my brain gets all tied in knots. I can't get a handle on how to put the pieces together. I can't even figure out what the generic type arguments to the outer SelectMany call would be.

How can I iterate those lists of lists, and return all the combinations, without knowing at compile-time how many lists there will be?

(Note: everywhere I used arrays above, I'd be fine with using IEnumerable<T> instead. Arrays are easier to write in sample code, but I'm expecting that the output is more likely to be in the form IEnumerable<IEnumerable<int>> rather than the int[][] I show in my sample output above.)

like image 981
Joe White Avatar asked Nov 04 '22 12:11

Joe White


1 Answers

You don't use SelectMany to combine the SelectMany calls; you use Aggregate. Code courtesy of Eric Lippert (answering on a question that's much more specific than this one, but giving a general answer that fits this question as well):

static IEnumerable<IEnumerable<T>> CartesianProduct<T>(
    this IEnumerable<IEnumerable<T>> sequences)
{
    IEnumerable<IEnumerable<T>> emptyProduct = new[] { Enumerable.Empty<T>() };
    return sequences.Aggregate(
        emptyProduct,
        (accumulator, sequence) => 
            from accseq in accumulator 
            from item in sequence 
            select accseq.Concat(new[] {item}) :                         
        );
}

As with all of Eric's answers, he includes a detailed discussion that lays out exactly how and why this works, in terms of the equivalent non-LINQ code.

like image 50
Joe White Avatar answered Dec 05 '22 06:12

Joe White