查找给定整数列表的唯一子集列表

时间:2016-07-17 18:07:49

标签: c# linq permutation

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

假设我有一个包含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中完成吗?

2 个答案:

答案 0 :(得分:2)

我已经回答了几个组合问题,到处都是我使用非递归分配自由算法的变体。对于这种情况,它看起来像这样:

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 i 0 = 0; i 0 &lt; = input.Length - N; i 0 ++)
  for(int i 1 = i 0 + 1; i 1 &lt; = input.Length - N + 1; i 1 ++)
  for(int i 2 = i 1 + 1; i 2 &lt; = input.Length - N + 2; i 2 ++)
  ...
  for(int i N-1 = i N-2 + 1; i N-1 &lt; = input.Length - 1; i <子> N-1 ++)
  yield {input [i 0 ],输入[i 1 ],输入[i 2 ],...,输入[i N-1 ]}

答案 1 :(得分:0)

如果易处理的设置了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个整数的组合。

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's,my和社区wiki算法的比较,用于20中5个整数的组合。

结果

  

wiki perm:00:00:00.0950055

     

写维基烫发:00:00:00.0460026

     

Ivan perm: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();