Get all combinations of a list grouped by name

122 views Asked by At

I have the following list of TestParam... This is just a parameter list that is doing to determine how a query is going to be run. In the following case, the expected result would be to be executed against all the combinations of different parameters. Hence, a list of lists, with CustomerId 33 together with each product Id available in the list...

List<TestParam> testList = new List<TestParam>();
        testList.Add(new TestParam() { Name = "CustomerId", Value = "33" });
        testList.Add(new TestParam() { Name = "ProductId", Value = "1" });
        testList.Add(new TestParam() { Name = "ProductId", Value = "2" });
        testList.Add(new TestParam() { Name = "ProductId", Value = "3" });
        testList.Add(new TestParam() { Name = "ProductId", Value = "4" });
        testList.Add(new TestParam() { Name = "ProductId", Value = "5" });
        testList.Add(new TestParam() { Name = "ProductId", Value = "6" });
        testList.Add(new TestParam() { Name = "ProductId", Value = "7" });
        testList.Add(new TestParam() { Name = "ProductId", Value = "8" });

TestParam is a normal encapsulated parameter class having a name and a value...

public class TestParam
    {
        public string Name { get; set; }
        public string Value { get; set; }
    }

The end result would be a list of lists, having CustomerId 33, with all the rest of the products. The same result would be acquired if I had different names and values in the list of TestParam (the above is just an example).

The following code, ends up with several lists depending on the combinations of the list above...

   // First get a list of distinct unique param collections...
    List<string> distinctParameterNames = new List<string>();
    testList.GroupBy(x => x.Name).ForEach(paramName => {
        distinctParameterNames.Add(paramName.Key);
    });

    // Get counts
    List<int> combinationList = new List<int>();
    foreach (var x in distinctParameterNames) { 
        combinationList.Add(testList.Where(y=>y.Name == x).Count());
    }

    // Will contain 2 lists, one having all combinations of parameters named CustomerId, and another with ProductId combinations...
    List<List<TestParam>> parameterList = new List<List<TestParam>>();
    foreach (var x in distinctParameterNames) {

        // Loop 
        List<TestParam> parameter = new List<TestParam>();
        testList.Where(paramName => paramName.Name == x).ForEach(y =>
        {

            parameter.Add(new TestParam() { Name = y.Name, Value = y.Value });

        });

        parameterList.Add(parameter);

    }

It would be an intersect between the list, and the end result will be a list of lists, and each list will have the combinations below... So a run would return (in this case) :

  1. Customer 33, Product Id 1
  2. Customer 33, Product Id 2
  3. Customer 33, Product Id 3
  4. Customer 33, Product Id 4
  5. Customer 33, Product Id 5
  6. Customer 33, Product Id 6
  7. Customer 33, Product Id 7
  8. Customer 33, Product Id 8

What would be the most efficient and generic way to do this?

3

There are 3 answers

1
mhars On

get all the list of customer first like this

var customers = from a in testlist where a.name='customerid'
                select a;

var products = from a in testlist where a.name='productid'
                select a;  

then loop customers

for(var c in customers)
{
    loop products
     for(var p in products)
     {
        var customerproducts = new CustomerProducts{
            Customer = c.Name +' ' + c.Value
            Product = p.Name + ' ' + p.value
         };

       then add it into a list 
     }
}
1
Mez On

The following is the solution that I was looking for...

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();
        }

Usage (continues with my code snippet of the question itself) :

var intersection = AllCombinationsOf(parameterList.ToArray());
0
neleus On

The list needs to be grouped by Name, then it can be joined several times depending on count of groups:

        var groups = testList.GroupBy(_ => _.Name);

        IEnumerable<IEnumerable<TestParam>> result = null;

        foreach (var g in groups)
        {
            var current = g.Select(_ => new[] { _ });
            if (result == null)
            {
                result = current;
                continue;
            }

            result = result.Join(current, _ => true, _ => true, (actual, c) => actual.Concat(c));
        }

        // check result
        foreach (var i in result)
        {
            Console.WriteLine(string.Join(", ", i.Select(_ => string.Format("{0}-{1}", _.Name, _.Value))));
        }