如何使用 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.Empty()) : elements.SelectMany((e, i) => elements.Skip(i + 1).DifferentCombinations2(k - 1).Select(c => EnumerableEx.Return( e).Concat(c)));然而,对于像 (30, 10) 这样的大型组合,替代解决方案仍然更快。 @Phil Nice,我更喜欢这样。我将其改进为仅使用 System.Linq 方法:return k == 0 ? Enumerable.Empty&lt;IEnumerable&lt;T&gt;&gt;() : elements.SelectMany((e, i) =&gt; elements.Skip(i + 1).DifferentCombinations(k - 1).Select(c =&gt; 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++)

在 Javascript 中使用 Map 从一组字谜中查找唯一单词

如何从一组数字计算平均值,中位数,模式和范围

如何用c#从一组数中随机抽取数字?

如何显示可以从一串数字创建的所有IP地址组合?

怎么用EXCEL从一堆数据中找出包含这几个数的地方,顺序不限