【问题标题】:Finding list of unique subsets of a given list of integers [duplicate]查找给定整数列表的唯一子集列表[重复]
【发布时间】:2016-07-27 05:02:44
【问题描述】:

寻找一种有效的方法来确定整数列表的所有唯一子集。

假设我有一个包含 340 个整数的整数列表。我想要一个所有可能子集的列表(每个子集 5 个元素)。所有提供的整数都是唯一的,结果不应重复其子集中的任何元素。给定输入 1,2,3,4,5,6,7,8,9 的示例我正在寻找输出

  • 1,2,3,4,5
  • 1,2,3,4,6
  • 1,2,3,4,7
  • 1,2,3,4,8
  • 1,2,3,4,9
  • 1,2,3,5,6
  • 1,2,3,5,7
  • ...

我必须在 CSharp 中执行此操作。这可以在 LINQ 中完成吗?

【问题讨论】:

  • 你确定你想要 list - 我们说的是 2,052,469,935,880 种组合,你不能在一个列表中存储这么多元素。
  • 那将是更糟糕的情况。通常我只会使用大约 25-30 个整数。而且我可以将这么多组合存储在一个列表中,因为我正在使用 64 位。
  • 参见例如stackoverflow.com/questions/5023081/find-all-subsets-of-a-liststackoverflow.com/questions/29089992/…(不一定完全重复,但肯定包含足够的信息让您自己取得一些进步,而不是仅仅要求其他人为您完成所有工作)。
  • @IvanStoev 好吧,combinations 将是 340! / (5! (340 - 5)!) 是 int 的 36,760,655,568,但你会说它们不适合(比如说 64 位)物理内存 limit Windows Server 2012 Datacenter LOL跨度>
  • @PeterDuniho 你可以说这是对这个不错的社区wiki 的复制品,但让我补充一下,这里困难的部分是找到一个有效(快速)的实施,这意味着将一些东西从 Linq 移植到数组管理...

标签: c# linq permutation


【解决方案1】:

我已经回答了几个组合问题,并且在任何地方我都使用了一种非递归无分配算法的变体。对于这种情况,它看起来像这样:

public static class Algorithms
{
    public static IEnumerable<T[]> GetCombinations<T>(this T[] input, int N)
    {
        var result = new T[N];
        var indices = new int[N];
        for (int pos = 0, index = 0; ;)
        {
            for (; pos < N; pos++, index++)
            {
                indices[pos] = index;
                result[pos] = input[index];
            }
            yield return result;
            do
            {
                if (pos == 0) yield break;
                index = indices[--pos] + 1;
            }
            while (index > input.Length - N + pos);
        }
    }
}

与其他实现一样,该方法产生一个相同的内部缓冲区,这在您只需要迭代和处理一次结果集时很有用。如果需要存储组合,则需要在存储之前克隆返回的数组。这是您的场景中的示例用法:

var input = Enumerable.Range(1, 20);
var result = input
    .Distinct()
    .ToArray()
    .GetCombinations(5)
    .Select(c => (int[])c.Clone())
    .ToList();

更新:GetCombinations 方法基本上模拟像这样的N 嵌套循环(在伪代码中):

for (int i0 = 0; i00++)
for (int i1 = i0 + 1; i11++)
for (int i2 = i1 + 1; i22++)
...
for (int iN-1 = iN-2 + 1; iN-1N-1++)
产量 { 输入[i0], input[i1], input[i2], ..., input[iN-1] }

【讨论】:

  • 你能添加一些关于你的代码实际在做什么的解释吗?我发现很难理解,可能不仅仅是因为那些相当奇怪的 for 循环! :)
  • lol :) 他们所做的只是模拟嵌套循环,如 for (int i0 = 0; i0 &lt;= M - N; i0++) for (int i1 = i0 + 1; i1 &lt;= M - N + 1; i1++) for (int i2 = i1 + 1; i2 &lt;= M - N + 2; i2++)... 最多 N - 1。
【解决方案2】:

如果是易处理 9 个元素(或最多 25-30 个)和 5 个子集的集合,代码可以基于递归函数

   static void Main(string[] args)
    {
        foreach (var item in ListPerm())
        {
            Console.WriteLine(String.Join(",", item));
        }
        Console.Read();
    }


    private static List<List<int>> ListPerm(HashSet<int> mySet = null, int deep = 5)
    {
        if (mySet == null)
        {
            mySet = initSet(8);
        }
        if (deep <= 0)
        {
            return Enumerable.Empty<List<int>>().ToList();
        }
        List<List<int>> all = new List<List<int>>();
        for (int i = 0; i < mySet.Count - deep + 1; i++)
        {

            if (deep == 1)
            {
                var list = new List<int>() { mySet.ElementAt(i) };
                all.Add(list);
            }
            foreach (var item in ListPerm(new HashSet<int>(mySet.Skip(i+1)), deep - 1))
            {
                var list = new List<int>() { mySet.ElementAt(i) };
                list.AddRange(item);
                all.Add(list);
            }
        }
        return all;
    }

    private static HashSet<int> initSet(int lenght)
    {
        HashSet<int> ret = new HashSet<int>();
        for (int i = 0; i < lenght; i++)
        {
            ret.Add(i * 1  +  1); // just an example...
        };
        return ret;
    }

再造

现在,让我将上面的代码优化成一个更性能 的函数,在我的标准笔记本电脑上,它需要 3.2 秒来获得 30 个整数中的 8 个的组合。

private static int[][] ListPerm(int[] mySet, int deep)
{
    var all = new List<int[]>();
    if (deep == 1)
    {
        return mySet.Select(x => new int[] { x }).ToArray(); 
    }
    else
    {
        var mySubSet = new int[mySet.Length - 1];
        Array.Copy(mySet, 1, mySubSet, 0, mySubSet.Length);
        var perm1st = ListPerm(mySubSet, deep - 1);
        for (int i = 0; i < mySet.Length - deep + 1; i++)
        {
            var permn = perm1st.Select(x =>
                {
                    var z = new int[x.Length + 1];
                    z[0] = mySet[i];
                    x.CopyTo(z, 1);
                    return z;
                }
            );
            all.AddRange(permn);
            int start = Array.FindIndex(perm1st, item => item[0] != mySet[i + 1]);
                if (start > 0)
                {
                    var temp_cpy = new int[perm1st.Length - start][];
                    Array.Copy(perm1st, start, temp_cpy, 0, temp_cpy.Length);
                    perm1st = temp_cpy;
                }
        }
    }
    return all.ToArray();
}

基准测试

这里是 Ivan、my 和社区wiki 算法对 20 中 5 个 int 组合的比较。

结果

wiki 权限:00:00:00.0950055

编写 wiki 权限:00:00:00.0460026

伊万烫发:00:00:00.0400023

写伊万烫发:00:00:00.0260015

我的烫发:00:00:00.0110006

写我的烫发:00:00:00.0300017

测试代码

    var input = Enumerable.Range(1, 20);
    int deep = 5;
    var start = DateTime.Now;


    var wiki =  Algorithms.Combinations(input, deep).ToArray();
    Console.WriteLine("wiki perm: {0}", DateTime.Now - start);
    start = DateTime.Now;
    StreamWriter sw0 = new StreamWriter(@"C:\dev\SO\Algo\perm0.txt", false);
    foreach (var item in wiki)
    {
        sw0.WriteLine(String.Join(",", item));
    }
    sw0.Close();
    Console.WriteLine("writing wiki perm: {0}", DateTime.Now - start);
    start = DateTime.Now;

    start = DateTime.Now;
    var result = input
        .Distinct()
        .ToArray()
        .GetCombinations(deep)
        .Select(c => (int[])c.Clone())
        .ToList();
    Console.WriteLine("Ivan perm: {0}", DateTime.Now - start);
    start = DateTime.Now;
    StreamWriter sw1 = new StreamWriter(@"C:\dev\SO\Algo\perm1.txt", false);
    foreach (var item in result)
    {
        sw1.WriteLine(String.Join(",", item));
    }
    sw1.Close();
    Console.WriteLine("writing Ivan perm: {0}", DateTime.Now - start);
    start = DateTime.Now;


    var myPerm = ListPermSO(input.ToArray(), deep);
    Console.WriteLine("my perm: {0}", DateTime.Now - start);
    start = DateTime.Now;
    StreamWriter sw2 = new StreamWriter(@"C:\dev\SO\Algo\perm2.txt", false);
    foreach (var item in myPerm)
    {
        sw2.WriteLine(String.Join(",", item));
    }
    sw2.Close();
    Console.WriteLine("writing my perm: {0}", DateTime.Now - start);
    Console.Read();

【讨论】:

  • 重新设计后,我觉得这个递归函数现在比Ivan Stoev的解决方案要快一些。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-05-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-26
相关资源
最近更新 更多