假设我有一个任意长度的整数列表,例如我有1,3,5和7的列表。
我想要一个算法从列表中选择X元素的组合。
例如,X = 1将返回:
1
3
5
7
x = 2会返回:
1 + 1
1 + 3
1 + 5
1 + 7
3 + 3
3 + 5
3 + 7
5 + 5
5 + 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]);
}
}
}
这个解决方案没有扩展,因为我必须不断地为另一个元素I拣选另一个for循环。例如,X = 7需要7个嵌套for循环。是否有更好的方法来编写这种不涉及嵌套的方法?
答案 0 :(得分:3)
您可以使用以下内容获取序列的组合:
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);
收率:
1 1
1 3
1 5
1 7
3 3
3 5
3 7
5 7
7 7
要获得每个的总和,您需要汇总结果:
var result = list.Combinations(x).Select(g => g.Aggregate((left, right) => left + right));
产生:
2
4
6
8
6
8
10个
10个
12个
14
答案 1 :(得分:2)
还有一种纯粹的迭代方式来做到这一点。它需要更多的思考和复杂性,但可以非常有效。基本思想是模拟相同的嵌套循环,但跟踪每个嵌套循环的迭代作为循环计数器的数组,它们以与原始嵌套循环代码相同的方式向前迭代。这是一个完整的例子:
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]];
}
}
在迭代结束时,combinedInts
应包含所有总和组合的列表,类似于原始代码或其他递归解决方案。如果您正在使用小集合和小组合,那么这种效率水平是不必要的,您应该更喜欢递归解决方案,这更容易推断正确性。我将此作为思考问题的另一种方式。干杯!
答案 2 :(得分:1)
这对我有用:
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));
通过这个电话,我得到了:
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