简体   繁体   English

从列表中选择不同数量的项目组合

[英]Pick a varying number of item combinations from a List

Assume I have a list of integers of any length, for an example I have the list of 1,3,5 and 7. 假设我有一个任意长度的整数列表,例如我有1,3,5和7的列表。

I would like an algorithm to pick a combination of X elements from the list. 我想要一个算法从列表中选择X元素的组合。

For example, X = 1 would return: 例如,X = 1将返回:

1 1

3 3

5

7 7

x = 2 would return: x = 2会返回:

1 + 1 1 + 1

1 + 3 1 + 3

1 + 5 1 + 5

1 + 7 1 + 7

3 + 3 3 + 3

3 + 5 3 + 5

3 + 7 3 + 7

5 + 5 5 + 5

5 + 7 5 + 7

7 + 7 7 + 7

var listOfInts = new List<int> { 1, 3, 5, 7 };
var combinedInts = new List<int>();

// x = 1 solution
// This is only picking one item from the list. 
for (int i = 0; i < listOfInts.Count(); i++)
{
    combinedInts.Add(listOfInts[i]);
}

// x = 2 solution
// This is how to pick two. I wrap it around another for loop.
for (int i = 0; i < listOfInts.Count(); i++)
{
    for (int j = i; j < listOfInts.Count(); j++)
    {
        combinedInts.Add(listOfInts[i] + listOfInts[j]);
    }
}

// x = 3 solution
// If I go up another level I have to wrap it around another for loop. This solution won't scale.
for (int i = 0; i < listOfInts.Count(); i++)
{
    for (int j = i; j < listOfInts.Count(); j++)
    {
        for (int k = j; k < listOfInts.Count(); k++)
        {
            combinedInts.Add(listOfInts[i] + listOfInts[j] + listOfInts[k]);
        }
    }
}

This solution doesn't scale as I have to continually wrap around another for loop for each number of element I'm picking. 这个解决方案没有扩展,因为我必须不断地为我正在挑选的每个元素包围另一个for循环。 For example X = 7 would need 7-nested for loops. 例如,X = 7需要7个嵌套for循环。 Is there a better way to write this method that doesn't involve nesting for loops? 有没有更好的方法来编写这个不涉及嵌套for循环的方法?

You can use the following to get combinations of the sequences: 您可以使用以下内容来获取序列的组合

public static class LinqHelper
{
    public static IEnumerable<IEnumerable<T>> Combinations<T>(this IEnumerable<T> elements, int? k = null)
    {
        if (!k.HasValue)
            k = elements.Count();

        return k == 0 ? new[] { new T[0] } :
           elements.SelectMany((e, i) => elements.Skip(i).Combinations(k - 1).Select(c => (new[] { e }).Concat(c)));
    }
}

var list = new List<int> { 1, 3, 5, 7 };

int x = 2; //Change to 3, 4, 5, etc

var result = list.Combinations(x);

Yields: 产量:

1 1 1 1
1 3 1 3
1 5 1 5
1 7 1 7
3 3 3 3
3 5 3 5
3 7 3 7
5 7 5 7
7 7 7 7

To get the sum of each one, you'd aggregate the result: 要获得每个的总和,您需要汇总结果:

var result = list.Combinations(x).Select(g => g.Aggregate((left, right) => left + right));

Which produces: 哪个产生:

2 2
4 4
6 6
8 8
6 6
8 8
10 10
10 10
12 12
14 14

There is also a purely iterative way to do this. 还有一种纯粹的迭代方式来做到这一点。 It requires a great deal more thought and complexity, but can be made very efficient. 它需要更多的思考和复杂性,但可以非常有效。 The basic idea is to simulate the same nested loops, but track the iterations of each nested loop as an array of loop counters , which are iterated forward in the same manner as the original nested loop code. 基本思想是模拟相同的嵌套循环,但将每个嵌套循环的迭代作为循环计数器数组进行跟踪, 循环计数器循环以与原始嵌套循环代码相同的方式向前迭代。 Here is a fully working example: 这是一个完整的例子:

var listOfInts = new List<int> { 1, 3, 5, 7 };
var combinedInts = new List<int>();

var numInts = listOfInts.Count;
var numElements = 5; // number of "nested loops", or ints selected in each combination
var loopCounters = new int[numElements]; // make one loop counter for each "nested loop"
var lastCounter = numElements - 1; // iterate the right-most counter by default

// maintain current sum in a variable for efficiency, since most of the time
// it is changing only by the value of one loop counter change.
var tempSum = listOfInts[0] * numElements;

// we are finished when the left/outer-most counter has looped past number of ints
while (loopCounters[0] < numInts) {
    // you can use this to verify the output is iterating correctly:
    // Console.WriteLine(string.Join(",", loopCounters.Select(x => listOfInts[x])) + ": " + loopCounters.Select(x => listOfInts[x]).Sum() + "; " + tempSum);

    combinedInts.Add(tempSum);

    tempSum -= listOfInts[loopCounters[lastCounter]];
    loopCounters[lastCounter]++;
    if (loopCounters[lastCounter] < numInts) tempSum += listOfInts[loopCounters[lastCounter]];

    // if last element reached in inner-most counter, increment previous counter(s).
    while (lastCounter > 0 && loopCounters[lastCounter] == numInts) {
        lastCounter--;
        tempSum -= listOfInts[loopCounters[lastCounter]];
        loopCounters[lastCounter]++;
        if (loopCounters[lastCounter] < numInts) tempSum += listOfInts[loopCounters[lastCounter]];
    }

    // if a previous counter was advanced, reset all future counters to same
    // starting number to start iteration forward again.
    while (lastCounter < numElements - 1) {
        lastCounter++;
        if (loopCounters[lastCounter] < numInts) tempSum -= listOfInts[loopCounters[lastCounter]];
        loopCounters[lastCounter] = loopCounters[lastCounter - 1];
        if (loopCounters[lastCounter] < numInts) tempSum += listOfInts[loopCounters[lastCounter]];
    }
}

At the end of the iteration, combinedInts should contains a list of all sum combinations, similar to the original code or the other recursive solutions. 在迭代结束时, combinedInts应包含所有总和组合的列表,类似于原始代码或其他递归解决方案。 If you are working with small sets, and small combinations, then this level of efficiency is unnecessary and you should prefer a recursive solution which is easier to reason about correctness. 如果您正在使用小集合和小组合,那么这种效率水平是不必要的,您应该更喜欢递归解决方案,这更容易推断正确性。 I present this as an alternative way to think about the problem. 我将此作为思考问题的另一种方式。 Cheers! 干杯!

This works for me: 这对我有用:

Func<IEnumerable<int>, int, IEnumerable<IEnumerable<int>>> generate = null;
generate = (xs, n) =>
    (xs == null || !xs.Any())
        ? Enumerable.Empty<IEnumerable<int>>()
        : n == 1
            ? xs.Select(x => new [] { x })
            : xs.SelectMany(x => generate(xs, n - 1).Select(ys => ys.Concat(new [] { x })));

int[] array = { 1, 3, 5, 7, };

var results =
    generate(array, 3)
        .Select(xs => String.Join("+", xs));

With this call I get: 通过这个电话我得到:

1+1+1, 3+1+1, 5+1+1, 7+1+1, 1+3+1, 3+3+1, 5+3+1, 7+3+1, 1+5+1, 3+5+1, 5+5+1, 7+5+1, 1+7+1, 3+7+1, 5+7+1, 7+7+1, 1+1+3, 3+1+3, 5+1+3, 7+1+3, 1+3+3, 3+3+3, 5+3+3, 7+3+3, 1+5+3, 3+5+3, 5+5+3, 7+5+3, 1+7+3, 3+7+3, 5+7+3, 7+7+3, 1+1+5, 3+1+5, 5+1+5, 7+1+5, 1+3+5, 3+3+5, 5+3+5, 7+3+5, 1+5+5, 3+5+5, 5+5+5, 7+5+5, 1+7+5, 3+7+5, 5+7+5, 7+7+5, 1+1+7, 3+1+7, 5+1+7, 7+1+7, 1+3+7, 3+3+7, 5+3+7, 7+3+7, 1+5+7, 3+5+7, 5+5+7, 7+5+7, 1+7+7, 3+7+7, 5+7+7,7+7+7 1 + 1 + 1,3 + 1 + 1,5 + 1 + 1,7 + 1 + 1,1 + 3 + 1,3 + 3 + 1,5 + 3 + 1,7 + 3 + 1,1 + 5 + 1,3 + 5 + 1,5 + 5 + 1,7 + 5 + 1,1 + 7 + 1,3 + 7 + 1,5 + 7 + 1,7 + 7 + 1,1 + 1 + 3,3 + 1 + 3,5 + 1 + 3,7 + 1 + 3,1 + 3 + 3,3 + 3 + 3,5 + 3 + 3,7 + 3 + 3,1 + 5 + 3, 3 + 5 + 3,5 + 5 + 3,7 + 5 + 3,1 + 7 + 3,3 + 7 + 3,5 + 7 + 3,7 + 7 + 3,1 + 1 + 5,3 + 1 + 5,5 + 1 + 5,7 + 1 + 5,1 + 3 + 5,3 + 3 + 5,5 + 3 + 5,7 + 3 + 5,1 + 5 + 5,3 + 5 + 5,5 + 5 + 5,7 + 5 + 5,1 + 7 + 5,3 + 7 + 5,5 + 7 + 5,7 + 7 + 5,1 + 1 + 7,3 + 1 + 7, 5 + 1 + 7,7 + 1 + 7,1 + 3 + 7,3 + 3 + 7,5 + 3 + 7,7 + 3 + 7,1 + 5 + 7,3 + 5 + 7,5 + 5 + 7,7 + 5 + 7,1 + 7 + 7,3 + 7 + 7,5 + 7 + 7,7 + 7 + 7

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM