Combination of List<List<int>>
Asked Answered
G

9

26

I've a List of this type List> that contains this

List<int> A = new List<int> {1, 2, 3, 4, 5};
List<int> B = new List<int> {0, 1};
List<int> C = new List<int> {6};
List<int> X = new List<int> {....,....};

I want to have all combinations like this

1-0-6
1-1-6
2-0-6
2-1-6
3-0-6

and so on.

According to you is This possibile to resolve using Linq?

Gamba answered 13/2, 2009 at 12:7 Comment(2)
It's a cross product, trust Garry answer, it will do it.Ask
Are the number of dimensions fixed at 3? Or (from the X) is this dynamic?Inclusion
C
39

It's quite similar to this answer I gave to another question:

var combinations = from a in A
                   from b in B
                   from c in C
                   orderby a, b, c
                   select new List<int> { a, b, c };

var x = combinations.ToList();

For a variable number of inputs, now with added generics:

var x = AllCombinationsOf(A, B, C);

public static List<List<T>> AllCombinationsOf<T>(params List<T>[] sets)
{
    // need array bounds checking etc for production
    var combinations = new List<List<T>>();

    // prime the data
    foreach (var value in sets[0])
        combinations.Add(new List<T> { value });

    foreach (var set in sets.Skip(1))
        combinations = AddExtraSet(combinations, set);

    return combinations;
}

private static List<List<T>> AddExtraSet<T>
     (List<List<T>> combinations, List<T> set)
{
    var newCombinations = from value in set
                          from combination in combinations
                          select new List<T>(combination) { value };

    return newCombinations.ToList();
}
Cylix answered 13/2, 2009 at 12:7 Comment(15)
I don't think that works... I believe (from the X) that the OP means that the number of items in the list (and thus the number of dimensions) is dynamicInclusion
Hmmm, it's still possible based on my other references answer, you could just take a paramarray of sets and build it up. I'll ask for clarification in a comment.Cylix
I see you beat me to doing just that!Cylix
Yes guys the number of items is dynamic!Gamba
I see - you repeatedly cross 2 lists at a time in the loop - cute.Inclusion
How Can I modify the function "AllCombinationsOf" to pass all the list and only list<object>?Gamba
AllCombinationsOf will take any number of lists (due to the params) argument, so long as those lists contain the same type (in this case int). So you could call AllCombinationsOf(List<Car>, List<Car>, List<Car>, List<Car>) without changing any code.Cylix
I've tried, it functions. But I need to pass to the function the entire list not only single elements!Gamba
I've understood but i want to pass the entire List<List<Object>>Gamba
I don't understand. You want to pass it the list of combinations in order to create a list of combinations?Cylix
Sorry for my english, I'm Italian. I' ve solved. This example maybe could help you! var A = new List<int>() { 1, 2, 3, 4, 5 }; var B = new List<int>() { 1, 3, 5 }; List<List<int>> L = new List<List<int>>(); L.Add(A); L.Add(B); var X = Combinations.AllCombinationsOf(L.ToArray()); Thanks very much.Gamba
If that's how you need to use it you could change the method to this signature: AllCombinationsOf<T>(List<List<T>> sets) I think it would still work without any other changes to the code. Otherwise what you've done will work, yes.Cylix
The biggest problem I can see with this approach is the number of allocations. With the array version, you don't have this - just the one array. It would be easy to pass in an Action<T[]> to enact on combinations...Inclusion
It is true, I was thinking about it last night. I'll have a think about it. However, it's currently readable (to me) and produces the correct result. So (potentially) obfusticating it in the name of performance may be a waste of time if it works sufficiently well.Cylix
Hi, I need to verify, during combination, if a value is greater than other. What do I have to do? Thanks very much!!!Gamba
I
15

If the number of dimensions is fixed, this is simply SelectMany:

var qry = from a in A
          from b in B
          from c in C
          select new {A=a,B=b,C=c};

However, if the number of dimensions is controlled by the data, you need to use recursion:

static void Main() {
    List<List<int>> outerList = new List<List<int>>
    {   new List<int>(){1, 2, 3, 4, 5},
        new List<int>(){0, 1},
        new List<int>(){6,3},
        new List<int>(){1,3,5}
    };
    int[] result = new int[outerList.Count];
    Recurse(result, 0, outerList);
}
static void Recurse<TList>(int[] selected, int index,
    IEnumerable<TList> remaining) where TList : IEnumerable<int> {
    IEnumerable<int> nextList = remaining.FirstOrDefault();
    if (nextList == null) {
        StringBuilder sb = new StringBuilder();
        foreach (int i in selected) {
            sb.Append(i).Append(',');
        }
        if (sb.Length > 0) sb.Length--;
        Console.WriteLine(sb);
    } else {
        foreach (int i in nextList) {
            selected[index] = i;
            Recurse(selected, index + 1, remaining.Skip(1));
        }
    }
}
Inclusion answered 13/2, 2009 at 12:16 Comment(1)
I managed it in a different manner that may be more readable depending on your viewpoint. What do you think?Cylix
V
9

How about following way of generating combinations using .Join method?

static void Main()
{
    List<List<int>> collectionOfSeries = new List<List<int>>
                                {   new List<int>(){1, 2, 3, 4, 5},
                                    new List<int>(){0, 1},
                                    new List<int>(){6,3},
                                    new List<int>(){1,3,5}
                                };
    int[] result = new int[collectionOfSeries.Count];

    List<List<int>> combinations = GenerateCombinations(collectionOfSeries);

    Display(combinations); 
}

This Method GenerateCombinations(..) does main work of generating combinations. This method is generic so could be used for generating combinations of any type.

private static List<List<T>> GenerateCombinations<T>(
                                List<List<T>> collectionOfSeries)
{
    List<List<T>> generatedCombinations = 
        collectionOfSeries.Take(1)
                          .FirstOrDefault()
                          .Select(i => (new T[]{i}).ToList())                          
                          .ToList();

    foreach (List<T> series in collectionOfSeries.Skip(1))
    {
        generatedCombinations = 
            generatedCombinations
                  .Join(series as List<T>,
                        combination => true,
                        i => true,
                        (combination, i) =>
                            {
                                List<T> nextLevelCombination = 
                                    new List<T>(combination);
                                nextLevelCombination.Add(i);
                                return nextLevelCombination;
                            }).ToList();

    }

    return generatedCombinations;
}

Display helper..

private static void Display<T>(List<List<T>> generatedCombinations)
{
    int index = 0;
    foreach (var generatedCombination in generatedCombinations)
    {
        Console.Write("{0}\t:", ++index);
        foreach (var i in generatedCombination)
        {
            Console.Write("{0,3}", i);
        }
        Console.WriteLine();
    }
    Console.ReadKey();
}
Valetudinary answered 30/11, 2011 at 17:18 Comment(1)
Excellent solution.Reichel
T
2
//Done in 2 while loops. No recursion required
#include<stdio.h>
#define MAX 100
typedef struct list
{
  int elements[MAX];
}list;
list n[10];
int number,count[10],temp[10];
void print();
int main()
{
  int i,j,mult=1,mult_count;
  printf("Enter the number of lists - ");
  scanf("%d",&number);
  for(i=0;i<number;i++)
  {
    printf("Enter the number of elements - ");
    scanf("%d",&count[i]);
    for(j=0;i<count[i];j++)
    {
      printf("Enter element %d - "j);
      scanf("%d",&n[i].elements[j]);
    }
  }
  for(i=0;i<number;i++)
  temp[i]=0;
  for(i=0;i<number;i++)
  mult*=count[i];
  printf("%d\n",mult);
  mult_count=0;
  while(1)
  {
    print();
    mult_count++;
    if(mult_count==mult)
    break;
    i=0;
    while(1)
    {
      temp[i]++;
      if(temp[i]==count[i])
      {
        temp[i]=0;
        i++;
      }
      else break;
    }
  }
  return 0;
}
void print()
{
  int i;
  for(i=0;i<number;i++)
  {
    printf("%d\n",n[i].elements[temp[i]]);
    printf("\n");
  }
}
Turoff answered 17/6, 2014 at 19:16 Comment(0)
J
2

Great solution from Abhijeet Nagre. Small improvement in case when some serie is empty or series are empty.

List<List<T>> generatedCombinations = 
    collectionOfSeries.Where(l => l.Any())
                      .Take(1)
                      .DefaultIfEmpty(new List<T>())
                      .First()
                      .Select(i => (new T[]{i}).ToList())                          
                      .ToList();
Jenellejenesia answered 1/12, 2017 at 7:51 Comment(0)
T
1

Just for fun:

using CSScriptLibrary;
using System;
using System.Collections.Generic;

namespace LinqStringTest
{
    public class Program
    {
        static void Main(string[] args)
        {

            var lists = new List<List<int>>() {
                new List<int> { 0, 1, 2, 3 },
                new List<int> { 4, 5 },
                new List<int> { 6, 7 },
                new List<int> { 10,11,12 },
            };
            var code = GetCode(lists);
            AsmHelper scriptAsm = new AsmHelper(CSScript.LoadCode(code));

            var result = (IEnumerable<dynamic>)scriptAsm.Invoke("Script.LinqCombine", lists);

            foreach (var item in result)
            {
                Console.WriteLine(item);
            }

            Console.ReadLine();
        }

        private static string GetCode(List<List<int>> listsToCombine)
        {
            var froms = "";
            var selects = "";

            for (int i = 0; i < listsToCombine.Count; i++)
            {
                froms += string.Format("from d{0} in lists[{0}]{1}", i, Environment.NewLine);
                selects += string.Format("D{0} = d{0},", i);
            }

            return @"using System;
              using System.Linq;
              using System.Collections.Generic;
              public class Script
              {
                  public static IEnumerable<dynamic> LinqCombine(List<List<int>> lists)
                  {
                        var x = " + froms + @"
                                select new { " + selects + @" };
                        return x;
                  }
              }";
        }
    }
}
Territerrible answered 17/8, 2016 at 15:23 Comment(0)
D
1
    public static List<List<string>> CrossProduct(List<List<string>> s)
    {
        if (!s.Any())
            return new List<List<string>>();

        var c1 = s.First();
        var cRest = s.Skip(1).ToList();
        var sss = from v1 in c1
                  from vRest in CrossProduct(cRest)
                  select (new[] { v1 }.Concat(vRest)).ToList();
        var r = sss.ToList();
        return r;
    }
Durian answered 15/9, 2017 at 20:56 Comment(0)
I
1

Solution without Linq and recursion:

private List<List<T>> GetAllCombinations<T>(List<List<T>> source)
{
    List<List<T>> result = new List<List<T>>();

    foreach (var value in source[0])
    {
        result.Add(new List<T> { value });
    }

    for (int i = 1; i < source.Count; i++)
    {
        var resultCount = result.Count;

        for (int j = 1; j < source[i].Count; j++)
        {
            for (var k = 0; k < resultCount; k++)
            {
                result.Add(new List<T>(result[k]));
            }
        }

        var t = (result.Count / source[i].Count);

        for (int j = 0; j < source[i].Count; j++)
        {
            for (int k = 0; k < t; k++)
            {
                result[j * t + k].Add(source[i][j]);
            }
        }
    }

    return result;
}
Imogene answered 4/1, 2021 at 9:8 Comment(0)
V
0

Generates all combinations from the input lists

var combinations = AllCombinationsOf(A, B, C);

public static IEnumerable<List<T>> AllCombinationsOf<T>(params List<T>[] inputs)
{
    var seed = Enumerable.Repeat(new List<T>(), 1);
    return inputs.Aggregate(seed, CreateCombinations);
}

private static IEnumerable<List<T>> CreateCombinations<T>(IEnumerable<List<T>> oldCombinations, List<T> newValues)
    =>  from value in newValues
        from combination in oldCombinations
        select new List<T>(combination) {value};

Simplified version of this answer by @Garry Shutler.

Try it online! (test cases included along with the question's example)

Variola answered 24/12, 2020 at 21:3 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.