如何使用 LINQ 从一组数字中查找 n 项的所有组合?
Posted
技术标签:
【中文标题】如何使用 LINQ 从一组数字中查找 n 项的所有组合?【英文标题】:How to use LINQ to find all combinations of n items from a set of numbers? 【发布时间】:2015-10-26 00:04:26 【问题描述】:我正在尝试编写一个算法来从一组数字中选择 n 个值的所有组合。
例如,给定集合:1, 2, 3, 7, 8, 9
集合中 2 个值的所有组合是:
(1, 2), (1, 3), (1, 7), (1, 8), (1, 9), (2, 3), (2, 7), (2, 8) , (2, 9), (3, 7), (3, 8), (3, 9), (7, 8), (7, 9), (8, 9)
而 3 是:
(1, 2, 3), (1, 2, 7), (1, 2, 8), (1, 2, 9), (1, 3, 7), (1, 3, 8) , (1, 3, 9), (1, 7, 8), (1, 7, 9), (1, 8, 9), (2, 3, 7), (2, 3, 8), ( 2, 3, 9), (2, 7, 8), (2, 7, 9), (2, 8, 9), (3, 7, 8), (3, 7, 9), (3, 8, 9), (7, 8, 9)
等等!
我目前正在使用方法来产生 2、3 和 4 值组合的返回集,但在我看来,这可以在 LINQ 查询中概括。
感谢您的帮助!
【问题讨论】:
你看过this或this的答案吗? 【参考方案1】:用法:
var results = new[] 1, 2, 3, 4, 5, 6, 7, 8, 9 .DifferentCombinations(3);
代码:
public static class Ex
public static IEnumerable<IEnumerable<T>> DifferentCombinations<T>(this IEnumerable<T> elements, int k)
return k == 0 ? new[] new T[0] :
elements.SelectMany((e, i) =>
elements.Skip(i + 1).DifferentCombinations(k - 1).Select(c => (new[] e).Concat(c)));
【讨论】:
@Dink 我进行了几次测试,这个总是优于我提供的解决方案。它也更简洁。我会选择这个。 感谢 user109260 和 @Jared - 这很棒,它确实简化了事情并且更加灵活。 这个解决方案是递归的,因此它适用于小集合,如问题中提供的示例,但会破坏大集合的堆栈。 我们可以通过移除内存分配来获得不错的速度提升,例如返回 k == 0 ? EnumerableEx.Return(Enumerable.Emptyreturn k == 0 ? Enumerable.Empty<IEnumerable<T>>() : elements.SelectMany((e, i) => elements.Skip(i + 1).DifferentCombinations(k - 1).Select(c => Enumerable.Repeat(e, 1).Concat(c)));
【参考方案2】:
两个答案都不错,但可以通过消除内存分配来加快速度
对于答案 1:现在从 60 计算 5 时快 2.5 倍
编辑:EnumerableEx.Return
来自 System.Interactive 包。
public static IEnumerable<IEnumerable<T>> DifferentCombinations2<T>
(this IEnumerable<T> elements, int k)
return k == 0
? EnumerableEx.Return(Enumerable.Empty<T>())
: elements.SelectMany((e, i) =>
elements.Skip(i + 1)
.DifferentCombinations(k - 1)
.Select(c => EnumerableEx.Return(e).Concat(c)));
答案 2:现在从 60 计算 5 时快 3 倍
static class Combinations
private static void SetIndexes(int[] indexes, int lastIndex, int count)
indexes[lastIndex]++;
if (lastIndex > 0 && indexes[lastIndex] == count)
SetIndexes(indexes, lastIndex - 1, count - 1);
indexes[lastIndex] = indexes[lastIndex - 1] + 1;
private static bool AllPlacesChecked(int[] indexes, int places)
for (int i = indexes.Length - 1; i >= 0; i--)
if (indexes[i] != places)
return false;
places--;
return true;
public static IEnumerable<IEnumerable<T>> GetDifferentCombinations<T>(this IEnumerable<T> c, int count)
var collection = c.ToList();
int listCount = collection.Count();
if (count > listCount)
throw new InvalidOperationException($"nameof(count) is greater than the collection elements.");
int[] indexes = Enumerable.Range(0, count).ToArray();
do
yield return indexes.Select(i => collection[i]).ToList();
SetIndexes(indexes, indexes.Length - 1, listCount);
while (!AllPlacesChecked(indexes, listCount));
这导致答案 2 比答案 1 快 5 倍 5 from 60。
【讨论】:
【参考方案3】:虽然上面的答案非常简洁,但我想出了一个解决方案,根据集合大小可以更快。
static class Combinations
private static void InitIndexes(int[] indexes)
for (int i = 0; i < indexes.Length; i++)
indexes[i] = i;
private static void SetIndexes(int[] indexes, int lastIndex, int count)
indexes[lastIndex]++;
if (lastIndex > 0 && indexes[lastIndex] == count)
SetIndexes(indexes, lastIndex - 1, count - 1);
indexes[lastIndex] = indexes[lastIndex - 1] + 1;
private static List<T> TakeAt<T>(int[] indexes, IEnumerable<T> list)
List<T> selected = new List<T>();
for (int i = 0; i < indexes.Length; i++)
selected.Add(list.ElementAt(indexes[i]));
return selected;
private static bool AllPlacesChecked(int[] indexes, int places)
for (int i = indexes.Length - 1; i >= 0; i--)
if (indexes[i] != places)
return false;
places--;
return true;
public static IEnumerable<List<T>> GetDifferentCombinations<T>(this IEnumerable<T> collection, int count)
int[] indexes = new int[count];
int listCount = collection.Count();
if (count > listCount)
throw new InvalidOperationException($"nameof(count) is greater than the collection elements.");
InitIndexes(indexes);
do
var selected = TakeAt(indexes, collection);
yield return selected;
SetIndexes(indexes, indexes.Length - 1, listCount);
while (!AllPlacesChecked(indexes, listCount));
【讨论】:
同意替代方案。又好又小。上面的 Linq 版本很慢而且非常消耗内存。我更喜欢这个。但是,当您的输入列表具有非唯一索引时,例如 0,0,1,2,3,3,4,5,2 结果中有很多双精度数。如果目的是保持结果子集的唯一性(减少输出!),请考虑***.com/questions/51668174/…以上是关于如何使用 LINQ 从一组数字中查找 n 项的所有组合?的主要内容,如果未能解决你的问题,请参考以下文章
使用 2d/3dsplines 从一组嘈杂的数据点中查找曲率? (C++)