使用LINQ获取不同数字的组合

4
以下代码根据逻辑返回所有不同的组合,即1、2、3 = 3、2、1 = 2、3、1,因此它只返回该数字集合的一个实例。
但是,我想改变这个逻辑,使它返回所有数字集合的所有实例。
我需要在下面的LINQ查询"GetPowerSet"中做什么才能实现这一点?
    public void GetPowersets()
    {
        List<int> ints = new List<int>()
        {
            1,2,2,3,3
        };

        var results = GetPowerSet(ints);


        List<String> combinations = new List<String>();
        foreach (var result in results)
        {
            StringBuilder sb = new StringBuilder();
            foreach (var intValue in result.OrderBy(x => x))
            {
                sb.Append(intValue + ",");
            }
            combinations.Add(sb.ToString());
        }

        string c1 = string.Join("|", combinations.ToArray()).Replace(",|", "|");
        //c1 = "|1|2|1,2|2|1,2|2,2|1,2,2|3|1,3|2,3|1,2,3|2,3|1,2,3|2,2,3|1,2,2,3|3|1,3|2,3|1,2,3|2,3|1,2,3|2,2,3|1,2,2,3|3,3|1,3,3|2,3,3|1,2,3,3|2,3,3|1,2,3,3|2,2,3,3|1,2,2,3,3,"

    }

    public IEnumerable<IEnumerable<int>> GetPowerSet(List<int> list)
    {
        return from m in Enumerable.Range(0, 1 << list.Count)
                     select
                         from i in Enumerable.Range(0, list.Count)
                         where (m & (1 << i)) != 0
                         select list[i];
    }

这是我尝试实现的最终结果:(没有重复的组合行:例如 3,2,1 和 3,2,1 是相同的。但是 1,2,3 和 3,2,1 不是相同的,两者都应包含在最终结果中)。
1
2
3
1,2
1,3
2,1
2,3
2,2
3,1
3,2
3,3
1,2,3
1,2,2
1,3,2
1,3,3
2,1,3
2,1,2
2,3,1
2,3,2
2,3,3
2,2,1
2,2,3
3,1,2
3,1,3
3,2,1
3,2,2
3,2,3
3,3,1
3,3,2
1,2,3,2
1,2,3,3
1,2,2,3
1,3,2,2
1,3,2,3
1,3,3,2
2,1,3,2
2,1,3,3
2,1,2,3
2,3,1,2
2,3,1,3
2,3,2,1
2,3,2,3
2,3,3,1
2,3,3,2
2,2,1,3
2,2,3,1
2,2,3,3
3,1,2,2
3,1,2,3
3,1,3,2
3,2,1,2
3,2,1,3
3,2,2,1
3,2,2,3
3,2,3,1
3,2,3,2
3,3,1,2
3,3,2,1
3,3,2,2
1,2,3,2,3
1,2,3,3,2
1,2,2,3,3
1,3,2,2,3
1,3,2,3,2
1,3,3,2,2
2,1,3,2,3
2,1,3,3,2
2,1,2,3,3
2,3,1,2,3
2,3,1,3,2
2,3,2,1,3
2,3,2,3,1
2,3,3,1,2
2,3,3,2,1
2,2,1,3,3
2,2,3,1,3
2,2,3,3,1
3,1,2,2,3
3,1,2,3,2
3,1,3,2,2
3,2,1,2,3
3,2,1,3,2
3,2,2,1,3
3,2,2,3,1
3,2,3,1,2
3,2,3,2,1
3,3,1,2,2
3,3,2,1,2
3,3,2,2,1

使用“foreach”方式来做这件事,一旦数字集合变得太大就会导致“内存不足异常”(我预计LINQ不应该有这个问题),如下所示。这按照我想要的方式工作,返回我想要的结果集。但它很慢,存在性能问题。我也愿意听取如何改进它的建议。
public List<List<int>> GetAllCombinationsOfAllSizes(List<int> ints)
{
    List<List<int>> returnResult = new List<List<int>>();

    var distinctInts = ints.Distinct().ToList();
    for (int j = 0; j < distinctInts.Count(); j++)
    {
        var number = distinctInts[j];

        var newList = new List<int>();
        newList.Add(number);
        returnResult.Add(newList);

        var listMinusOneObject = ints.Select(x => x).ToList();
        listMinusOneObject.Remove(listMinusOneObject.Where(x => x == number).First());

        if (listMinusOneObject.Count() > 0)
        {
            _GetAllCombinationsOfAllSizes(listMinusOneObject, newList, ref returnResult);
        }
    }

    return returnResult;
}
public void _GetAllCombinationsOfAllSizes(List<int> ints, List<int> growingList, ref List<List<int>> returnResult)
{
    var distinctInts = ints.Distinct().ToList();
    for (int j = 0; j < distinctInts.Count(); j++)
    {
        var number = distinctInts[j];

        var newList = growingList.ToList();
        newList.Add(number);
        returnResult.Add(newList);

        var listMinusOneObject = ints.Select(x => x).ToList();
        listMinusOneObject.Remove(listMinusOneObject.Where(x => x == number).First());

        if (listMinusOneObject.Count() > 0)
        {
            _GetAllCombinationsOfAllSizes(listMinusOneObject, newList, ref returnResult);
        }
    }

}

我要找的答案是:如何使用LINQ和C#实现我想要的结果集,以一种比我目前发布的“foreach”方式更快、更高效的方式?

4
Eric Lippert撰写了7篇博客,介绍如何解决这个问题 - http://ericlippert.com/2013/04/15/producing-permutations-part-one/。 - Dominic Zukiewicz
Eric Lippert的博客似乎只讨论如何返回所有结果,其中该结果中的总数= 5(对于我的示例)。它似乎没有解决如何返回序列中总数字<5的所有组合。无法解决我的问题。它也没有解决如何避免重复返回的结果:1,2,2,3,3 = 1,2,2,3,3,因此它应该只出现在最终结果列表中的一个实例。 - SED
对于重复检查,您可以将其转换为字符串然后进行检查。这肯定会消除您的重复项。不过性能方面就不太确定了。 - Ethan Turk
@Ethan - 我熟悉可能通过GroupBy语句解决此问题的字符串连接方法。然而,结果集越大,最终结果查询就会呈指数级变慢,因为它必须过滤掉指数级更多的结果,从而需要更长时间来计算。虽然这是问题的部分解决方案,但效率并不高。我希望能够通过其他建议找到更好的方法! :) - SED
你目前的代码似乎返回了所有包括重复项在内的组合。不确定你的问题是什么。你想要去除重复项吗? - Barry West
显示剩余6条评论
3个回答

2

新更新(移除旧代码,性能优于原作者代码,输出结果优秀)

static IEnumerable<int[]> EnumeratePermutations2(int[] ints)
{
    Dictionary<int, int> intCounts = ints.GroupBy(n => n)
                                         .ToDictionary(g => g.Key, g => g.Count());
    int[] distincts = intCounts.Keys.ToArray();
    foreach (int[] permutation in EnumeratePermutations2(new int[0], intCounts, distincts))
        yield return permutation;
}

static IEnumerable<int[]> EnumeratePermutations2(int[] prefix, Dictionary<int, int> intCounts, int[] distincts)
{
    foreach (int n in distincts)
    {
        int[] newPrefix = new int[prefix.Length + 1];
        Array.Copy(prefix, newPrefix, prefix.Length);
        newPrefix[prefix.Length] = n;
        yield return newPrefix;
        intCounts[n]--;
        int[] newDistincts = intCounts[n] > 0
                             ? distincts
                             : distincts.Where(x => x != n).ToArray();
        foreach (int[] permutation in EnumeratePermutations2(newPrefix, intCounts, newDistincts))
            yield return permutation;
        intCounts[n]++;
    }
}

所有以1开头的序列怎么办? - arserbin3
@arserbin3 哎呀,是我的错,我会去修复它。 - Ulugbek Umirov
@UlugbekUmirov 看起来,虽然您的解决方案提供了所需的结果,我的 "GetAllCombinationsOfAllSizes" 也提供了相同的结果,但您的解决方案速度较慢。例如: GetAllCombinationsOfAllSizes({1, 2, 2, 3, 3, 5, 5, 5, 6})EnumeratePermutations({1, 2, 2, 3, 3, 5, 5, 5, 6}).Select(x=>x.ToList()).ToList(),结果表明我的方法只需要0.04秒,而你的方法需要6.95秒。 - SED
@UlugbekUmirov,只是为了好玩,我实现了你的方法,看看代码会发生什么 - 而且没有使用任何 .ToList()(这会使它变慢)-- 结果不仅导致 OutOfMemory 异常,而且还使 Visual Studio 崩溃了哈哈哈哈,所以很遗憾,我担心你的解决方案无法帮助我解决需要更高效的解决方案的问题 ;) - SED
尝试过:1、2、2、3、3、5、5、5、6,而且它崩溃的地方很可能是 EnumeratePermutations({ 1, 2, 2, 3, 3, 5, 5, 5, 6 }).Select(x=>x.ToList()).ToList() 的 ToList 部分。 - SED
显示剩余3条评论

0

根据问题所述:

您的解决方案返回了所有结果集,包括重复项。 您的解决方案没有排除重复项。 您的示例输出排除了一些重复项,但列出了89个数据集。

根据我对组合的理解,应该只有64个带有重复项的组合,即2 ^ List.Count() = 2 ^ 6 = 64个组合。

修订后的答案

我认为这已经接近您想要的结果了。 我创建了一个简短的解决方案,但我认为它可以进行重构和优化以提高速度。以下链接有一些很好的Set类,我认为应该使用:http://www.codeproject.com/Articles/23391/Set-Collections-for-C

另外,我会使用TPL库,它将允许您加快处理速度。 链接:http://msdn.microsoft.com/en-us/library/dd460717(v=vs.110).aspx

我的结果集包含了190个集合。使用以下代码,运行时间约为1.5分钟。

主程序

void Main()
{
    var setList = new List<int>() {1,1,2,3,3,3};
    var setSize = setList.Count();

    var basePowerSet = PowerSet.Generate(setList);
    var results = PowerSet.PS;


    // Results generated in 1 Minute 23 seconds with no errors.

    var sortedSets = new SortedSet<string>();

    foreach( var item in results)
    {
        sortedSets.Add(item.ToString2());
    }

    foreach( var item in sortedSets)
    {
        Console.WriteLine(item);
    }   


}

PowerSet 库

public static class PowerSet
{
    // List with no Exact Duplicates but with Permutations
    public static List<List<int>> PS =  new List<List<int>>();


    // This Method Generates the power set with No Exact Duplicates
    // and stores the values into the Property PS.
    public static List<List<int>> Generate(List<int> setList)
    {
        // Generate Base Data to use for final results
        var setSize = setList.Count();
        var basePowerSet = from m in Enumerable.Range(0, 1 << setSize)
                select
                    from i in Enumerable.Range(0, setSize)
                    where (m & (1 << i)) != 0 
                    select setList[i];

        // Temporary Result Set with Duplicates
        var results = new List<List<int>>();

        // Step thru each set and generate list of Permutations for each
        // Power Set generated above.
        foreach( var item in basePowerSet )
        {
            var size = item.Count();
            var positions = from m in Enumerable.Range(0, size)
                select m;

            var lItem = item.ToList();  

            // If the set has 2 or more elements in the set then generate Permutations
            switch(size)
            {
                case 0:
                case 1:
                    break;
                default:

                    // Permutations generated from Linq Extension defined
                    // in Method Permute()
                    var posList = positions.Permute().ToList();

                    // remove first item which is a duplicate.
                    posList.RemoveAt(0);

                    // Generate new Lists based on all possiable
                    // combinations of the data in the set.
                    var x = new List<List<int>>();
                    foreach(var p in posList)
                    {   
                        var y = new List<int>();
                        foreach(var v in p)
                        {
                            //v.Dump("xxxx");
                            y.Add(lItem[v]);
                        }

                        x.Add(y);

                        // Add New Permutation but
                        // Do not add a duplicate set.
                        AddNonDuplicate(x);
                    }                                       
                    break;
            }

            // Add to Temp Results;
            results.Add(item.ToList());

            // Remove Duplicates
            AddNonDuplicate(results);
        }

        return results;
    }


    // Custom Method used to compare values in a set to the
    // Final Result Set named PS.
    public static void AddNonDuplicate(List<List<int>> list )
    {
        //list.Dump();
        if(list.Count() == 0)
            return;

        foreach(var item in list)
        {
            bool found = false;
            var mySize = PS.Count();
            if(mySize <= 0)
                PS.Add(item);
            else
                foreach(var psItem in PS)
                {
                    if( item.ToString2() == psItem.ToString2() )
                        found = true;
                }

            if(!found)
                PS.Add(item);
        }
    }

}

扩展库

// My Extension Methods
public static class MyExt
{

    public static IEnumerable<IEnumerable<T>> Permute<T>(this IEnumerable<T> list)
    {
        if (list.Count() == 1)
            return new List<IEnumerable<T>> { list };
        return list
            .Select((a, i1) => 
                        Permute(list.Where((b, i2) => i2 != i1))
                    .Select(b => (new List<T> { a }).Union(b)))
            .SelectMany(c => c);
    }


    public static string ToString2<T>(this List<T> list)
    {
        StringBuilder results = new StringBuilder("{ ");
        var size = list.Count();
        var pos = 1;

        foreach( var i in list )
        {
            results.Append(i.ToString());
            if(pos++!=size)
                results.Append(", ");
        }

        results.Append(" }");               
        return results.ToString().Trim(',');
    }
}

结果

{  }
{ 1 }
{ 1, 1 }
{ 1, 1, 2 }
{ 1, 1, 2, 3 }
{ 1, 1, 2, 3, 3 }
{ 1, 1, 2, 3, 3, 3 }
{ 1, 1, 3 }
{ 1, 1, 3, 2 }
{ 1, 1, 3, 2, 3 }
{ 1, 1, 3, 2, 3, 3 }
{ 1, 1, 3, 3 }
{ 1, 1, 3, 3, 2 }
{ 1, 1, 3, 3, 2, 3 }
{ 1, 1, 3, 3, 3 }
{ 1, 1, 3, 3, 3, 2 }
{ 1, 2 }
{ 1, 2, 1 }
{ 1, 2, 1, 3 }
{ 1, 2, 1, 3, 3 }
{ 1, 2, 1, 3, 3, 3 }
{ 1, 2, 3 }
{ 1, 2, 3, 1 }
{ 1, 2, 3, 1, 3 }
{ 1, 2, 3, 1, 3, 3 }
{ 1, 2, 3, 3 }
{ 1, 2, 3, 3, 1 }
{ 1, 2, 3, 3, 1, 3 }
{ 1, 2, 3, 3, 3 }
{ 1, 2, 3, 3, 3, 1 }
{ 1, 3 }
{ 1, 3, 1 }
{ 1, 3, 1, 2 }
{ 1, 3, 1, 2, 3 }
{ 1, 3, 1, 2, 3, 3 }
{ 1, 3, 1, 3 }
{ 1, 3, 1, 3, 2 }
{ 1, 3, 1, 3, 2, 3 }
{ 1, 3, 1, 3, 3 }
{ 1, 3, 1, 3, 3, 2 }
{ 1, 3, 2 }
{ 1, 3, 2, 1 }
{ 1, 3, 2, 1, 3 }
{ 1, 3, 2, 1, 3, 3 }
{ 1, 3, 2, 3 }
{ 1, 3, 2, 3, 1 }
{ 1, 3, 2, 3, 1, 3 }
{ 1, 3, 2, 3, 3 }
{ 1, 3, 2, 3, 3, 1 }
{ 1, 3, 3 }
{ 1, 3, 3, 1 }
{ 1, 3, 3, 1, 2 }
{ 1, 3, 3, 1, 2, 3 }
{ 1, 3, 3, 1, 3 }
{ 1, 3, 3, 1, 3, 2 }
{ 1, 3, 3, 2 }
{ 1, 3, 3, 2, 1 }
{ 1, 3, 3, 2, 1, 3 }
{ 1, 3, 3, 2, 3 }
{ 1, 3, 3, 2, 3, 1 }
{ 1, 3, 3, 3 }
{ 1, 3, 3, 3, 1 }
{ 1, 3, 3, 3, 1, 2 }
{ 1, 3, 3, 3, 2 }
{ 1, 3, 3, 3, 2, 1 }
{ 2 }
{ 2, 1 }
{ 2, 1, 1 }
{ 2, 1, 1, 3 }
{ 2, 1, 1, 3, 3 }
{ 2, 1, 1, 3, 3, 3 }
{ 2, 1, 3 }
{ 2, 1, 3, 1 }
{ 2, 1, 3, 1, 3 }
{ 2, 1, 3, 1, 3, 3 }
{ 2, 1, 3, 3 }
{ 2, 1, 3, 3, 1 }
{ 2, 1, 3, 3, 1, 3 }
{ 2, 1, 3, 3, 3 }
{ 2, 1, 3, 3, 3, 1 }
{ 2, 3 }
{ 2, 3, 1 }
{ 2, 3, 1, 1 }
{ 2, 3, 1, 1, 3 }
{ 2, 3, 1, 1, 3, 3 }
{ 2, 3, 1, 3 }
{ 2, 3, 1, 3, 1 }
{ 2, 3, 1, 3, 1, 3 }
{ 2, 3, 1, 3, 3 }
{ 2, 3, 1, 3, 3, 1 }
{ 2, 3, 3 }
{ 2, 3, 3, 1 }
{ 2, 3, 3, 1, 1 }
{ 2, 3, 3, 1, 1, 3 }
{ 2, 3, 3, 1, 3 }
{ 2, 3, 3, 1, 3, 1 }
{ 2, 3, 3, 3 }
{ 2, 3, 3, 3, 1 }
{ 2, 3, 3, 3, 1, 1 }
{ 3 }
{ 3, 1 }
{ 3, 1, 1 }
{ 3, 1, 1, 2 }
{ 3, 1, 1, 2, 3 }
{ 3, 1, 1, 2, 3, 3 }
{ 3, 1, 1, 3 }
{ 3, 1, 1, 3, 2 }
{ 3, 1, 1, 3, 2, 3 }
{ 3, 1, 1, 3, 3 }
{ 3, 1, 1, 3, 3, 2 }
{ 3, 1, 2 }
{ 3, 1, 2, 1 }
{ 3, 1, 2, 1, 3 }
{ 3, 1, 2, 1, 3, 3 }
{ 3, 1, 2, 3 }
{ 3, 1, 2, 3, 1 }
{ 3, 1, 2, 3, 1, 3 }
{ 3, 1, 2, 3, 3 }
{ 3, 1, 2, 3, 3, 1 }
{ 3, 1, 3 }
{ 3, 1, 3, 1 }
{ 3, 1, 3, 1, 2 }
{ 3, 1, 3, 1, 2, 3 }
{ 3, 1, 3, 1, 3 }
{ 3, 1, 3, 1, 3, 2 }
{ 3, 1, 3, 2 }
{ 3, 1, 3, 2, 1 }
{ 3, 1, 3, 2, 1, 3 }
{ 3, 1, 3, 2, 3 }
{ 3, 1, 3, 2, 3, 1 }
{ 3, 1, 3, 3 }
{ 3, 1, 3, 3, 1 }
{ 3, 1, 3, 3, 1, 2 }
{ 3, 1, 3, 3, 2 }
{ 3, 1, 3, 3, 2, 1 }
{ 3, 2 }
{ 3, 2, 1 }
{ 3, 2, 1, 1 }
{ 3, 2, 1, 1, 3 }
{ 3, 2, 1, 1, 3, 3 }
{ 3, 2, 1, 3 }
{ 3, 2, 1, 3, 1 }
{ 3, 2, 1, 3, 1, 3 }
{ 3, 2, 1, 3, 3 }
{ 3, 2, 1, 3, 3, 1 }
{ 3, 2, 3 }
{ 3, 2, 3, 1 }
{ 3, 2, 3, 1, 1 }
{ 3, 2, 3, 1, 1, 3 }
{ 3, 2, 3, 1, 3 }
{ 3, 2, 3, 1, 3, 1 }
{ 3, 2, 3, 3 }
{ 3, 2, 3, 3, 1 }
{ 3, 2, 3, 3, 1, 1 }
{ 3, 3 }
{ 3, 3, 1 }
{ 3, 3, 1, 1 }
{ 3, 3, 1, 1, 2 }
{ 3, 3, 1, 1, 2, 3 }
{ 3, 3, 1, 1, 3 }
{ 3, 3, 1, 1, 3, 2 }
{ 3, 3, 1, 2 }
{ 3, 3, 1, 2, 1 }
{ 3, 3, 1, 2, 1, 3 }
{ 3, 3, 1, 2, 3 }
{ 3, 3, 1, 2, 3, 1 }
{ 3, 3, 1, 3 }
{ 3, 3, 1, 3, 1 }
{ 3, 3, 1, 3, 1, 2 }
{ 3, 3, 1, 3, 2 }
{ 3, 3, 1, 3, 2, 1 }
{ 3, 3, 2 }
{ 3, 3, 2, 1 }
{ 3, 3, 2, 1, 1 }
{ 3, 3, 2, 1, 1, 3 }
{ 3, 3, 2, 1, 3 }
{ 3, 3, 2, 1, 3, 1 }
{ 3, 3, 2, 3 }
{ 3, 3, 2, 3, 1 }
{ 3, 3, 2, 3, 1, 1 }
{ 3, 3, 3 }
{ 3, 3, 3, 1 }
{ 3, 3, 3, 1, 1 }
{ 3, 3, 3, 1, 1, 2 }
{ 3, 3, 3, 1, 2 }
{ 3, 3, 3, 1, 2, 1 }
{ 3, 3, 3, 2 }
{ 3, 3, 3, 2, 1 }
{ 3, 3, 3, 2, 1, 1 }

当前代码返回1,2,3,但不返回3,2,1--这是“错误”的:我也希望它能返回3,2,1。 - SED
好的,我想我明白了。所以对于这个例子,排列组合的数量将是6的阶乘,即720组数据。这正确吗? - Barry West
@SED 我已经修改了我的答案。我生成了189个项,不应该包含任何完全重复的项,但包括所有排列组合。结果可能因数据集而异。 - Barry West

0

我没有修改你的GetPowerSet函数,而是创建了一个SetComparer来过滤重复项。

public class SetComparer : IEqualityComparer<IEnumerable<int>>
{
    public bool Equals(IEnumerable<int> x, IEnumerable<int> y)
    {
        return Object.ReferenceEquals(x, y) || (x != null && y != null && x.SequenceEqual(y));
    }

    public int GetHashCode(IEnumerable<int> set)
    {
        if (set == null) return 0;

        //if you only want one of these 1,2,3 vs 3,2,1
        //plug .OrderBy(x => x) before the Aggregate
        return set.Aggregate(19, (s,i) => s * 31 + i);
    }
}

只需将 .Distinct(new SetComparer()) 添加到结果链中或在你的 GetPowerSet select 语句的末尾即可。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接