对象列表的所有组合。所有对象归结为一个对象

时间:2018-07-03 12:17:18

标签: c# combinations

我在本主题中搜索并找到了很多算法,但没有找到适合的算法。我也没有找到任何我想要更改的人,因此我的问题得以解决。

我需要一个接受列表的函数,然后返回包含所有这些组合的列表的列表。列表应该是所有对象的组合,最多只能是一个单独的对象。

示例:

fun(new List<obj> {objA, objB, objC});

应该返回

public List<List<obj>> fun(List<obj> L){

...

return List{
            List{objA},
            List{objB},
            List{objC},
            List{objA, objB},
            List{objA, objC},
            List{objB, objC},
            List{objA, objB, objC};
}

我不知道名单会多久。

我知道数学表达式

n! / k! (n-k)! + n! /(k-1)! (n-(k-1))! + ... + n! / 1! (n-1)!

其中n是可用对象的数量,k是要合并的对象的数量。 计算结果将是包含在返回的List中的List 的数量

但是,正如我所说,我没有设法在代码中找到一些明智的方法。

我使用c#,所以我更喜欢用这种语言回答。但是欢迎所有帮助。

我看了太多的算法,以至于现在变得迷惑不解了。

2 个答案:

答案 0 :(得分:2)

您可以使用整数值来计算所有组合。

然后,对于每个值,检查数字中的每一位。每个1位表示相应的项包括在组合中。

如果您考虑二进制数是如何工作的,您将了解该算法的工作方式。例如,对于3个项目,您将拥有一个3位的二进制数,该数字从001到111,这3位中的每一个对应于其中一项,如下所示:

001
010
011
100
101
110
111

您应该能够看到我们如何使用每一位来确定该组合中是否包含相应的项目。

这是一个示例实现-如果项数为<= 32:

public static IEnumerable<IEnumerable<T>> Combinations<T>(IList<T> items)
{
    return Combinations(items.Count).Select(comb => comb.Select(index => items[index]));
}

public static IEnumerable<IEnumerable<int>> Combinations(int n)
{
    long m = 1 << n;

    for (long i = 1; i < m; ++i)
        yield return bitIndices((uint)i);
}

static IEnumerable<int> bitIndices(uint n)
{
    uint mask = 1;

    for (int bit = 0; bit < 32; ++bit, mask <<= 1)
        if ((n & mask) != 0)
            yield return bit;
}

您可以使用例如字符A..E的列表进行测试:

IList<char> test = "ABCDE".ToList();

foreach (var comb in Combinations(test))
    Console.WriteLine(string.Concat(comb));

这将输出:

A
B
AB
C
AC
BC
ABC
D
AD
BD
ABD
CD
ACD
BCD
ABCD
E
AE
BE
ABE
CE
ACE
BCE
ABCE
DE
ADE
BDE
ABDE
CDE
ACDE
BCDE
ABCDE

如果要将IEnumerable<IEnumerable<T>>转换为List<List<T>>,只需执行以下操作:

List<List<T>> list = Combinations(inputList).Select(x => x.ToList()).ToList();

例如,对于上面的List<char>,请执行以下操作:

List<List<char>> list = Combinations(test).Select(x => x.ToList()).ToList();

答案 1 :(得分:0)

我不确定性能,但是从可读性的角度来看,这是我想到的最好的方法-使用几个嵌套循环和linq的Skip and Take:

var source = new List<int>() { 1, 2, 3 };
var target = new List<List<int>>();


for(var i = 0; i < source.Count; i++)
{
    for(var j = i; j < source.Count; j++)
    {
        target.Add(new List<int>(source.Skip(i).Take(source.Count - j)));
    }
}

You can see a live demo on rextester