如何获取数组的所有子集?

时间:2009-06-16 00:00:35

标签: c# arrays

给定一个数组:[dog, cat, mouse]

最优雅的创作方式是什么:

[,,]
[,,mouse]
[,cat,]
[,cat,mouse]
[dog,,]
[dog,,mouse]
[dog,cat,]
[dog,cat,mouse]

我需要这个适用于任何大小的数组。

这本质上是一个二进制计数器,其中数组索引代表位。这可能让我使用一些按位运算来计算,但我看不到将它转换为数组索引的好方法。

12 个答案:

答案 0 :(得分:32)

优雅?为什么不Linq呢。

    public static IEnumerable<IEnumerable<T>> SubSetsOf<T>(IEnumerable<T> source)
    {
        if (!source.Any())
            return Enumerable.Repeat(Enumerable.Empty<T>(), 1);

        var element = source.Take(1);

        var haveNots = SubSetsOf(source.Skip(1));
        var haves = haveNots.Select(set => element.Concat(set));

        return haves.Concat(haveNots);
    }

答案 1 :(得分:7)

您可以使用BitArray类轻松访问数字中的位:

string[] animals = { "Dog", "Cat", "Mouse" };
List<string[]> result = new List<string[]>();
int cnt = 1 << animals.Length;
for (int i = 0; i < cnt; i++) {
   string[] item = new string[animals.Length];
   BitArray b = new BitArray(i);
   for (int j = 0; j < item.Length; j++) {
      item[j] = b[j] ? animals[j] : null;
   }
   result.Add(item);
}

答案 2 :(得分:6)

static IEnumerable<IEnumerable<T>> GetSubsets<T>(IList<T> set)
{
    var state = new BitArray(set.Count);
    do
        yield return Enumerable.Range(0, state.Count)
                               .Select(i => state[i] ? set[i] : default(T));
    while (Increment(state));
}

static bool Increment(BitArray flags)
{
    int x = flags.Count - 1; 
    while (x >= 0 && flags[x]) flags[x--] = false ;
    if (x >= 0) flags[x] = true;
    return x >= 0;
}

用法:

foreach(var strings in GetSubsets(new[] { "dog", "cat", "mouse" }))
    Console.WriteLine(string.Join(", ", strings.ToArray()));

答案 3 :(得分:6)

 string[] source = new string[] { "dog", "cat", "mouse" };
 for (int i = 0; i < Math.Pow(2, source.Length); i++)
 {
     string[] combination = new string[source.Length];
     for (int j = 0; j < source.Length; j++)
     {
         if ((i & (1 << (source.Length - j - 1))) != 0)
         {
             combination[j] = source[j];
         }
    }
    Console.WriteLine("[{0}, {1}, {2}]", combination[0], combination[1], combination[2]);
}

答案 4 :(得分:3)

Guffa的回答有我正在搜索的基本功能,但与

一致
BitArray b = new BitArray(i);

对我不起作用,它给出了一个ArgumentOutOfRangeException。这是我稍微调整过的代码:

string[] array = { "A", "B", "C","D" };
int count = 1 << array.Length; // 2^n

for (int i = 0; i < count; i++)
{
    string[] items = new string[array.Length];
    BitArray b = new BitArray(BitConverter.GetBytes(i));
    for (int bit = 0; bit < array.Length; bit++) {
        items[bit] = b[bit] ? array[bit] : "";
    }
    Console.WriteLine(String.Join("",items));
}

答案 5 :(得分:2)

这是一个易于理解的解决方案:

private static void Test()
{
    string[] test = new string[3] { "dog", "cat", "mouse" };

    foreach (var x in Subsets(test))
        Console.WriteLine("[{0}]", string.Join(",", x));
}

public static IEnumerable<T[]> Subsets<T>(T[] source)
{
    int max = 1 << source.Length;
    for (int i = 0; i < max; i++)
    {
        T[] combination = new T[source.Length];

        for (int j = 0; j < source.Length; j++)
        {
            int tailIndex = source.Length - j - 1;
            combination[tailIndex] =
                ((i & (1 << j)) != 0) ? source[tailIndex] : default(T);
        }

        yield return combination;
    }
}

答案 6 :(得分:2)

这是一个类似于David B的方法的解决方案,但如果真的要求你使用原始数量的元素(即使是空的)返回集合,也许更合适:。

static public List<List<T>> GetSubsets<T>(IEnumerable<T> originalList)
{
    if (originalList.Count() == 0)
        return new List<List<T>>() { new List<T>() };

    var setsFound = new List<List<T>>();
    foreach (var list in GetSubsets(originalList.Skip(1)))
    {                
        setsFound.Add(originalList.Take(1).Concat(list).ToList());
        setsFound.Add(new List<T>() { default(T) }.Concat(list).ToList());
    }
    return setsFound;
}

如果您传入三个字符串的列表,您将获得八个列表,每个列表包含三个元素(但有些元素将为空)。

答案 7 :(得分:0)

我对C#不是很熟悉,但我确信有类似的东西:

// input: Array A
foreach S in AllSubsetsOf1ToN(A.Length): 
    print (S.toArray().map(lambda x |> A[x]));

好的,我被告知上面的答案是行不通的。如果你重视优雅而不是效率,我会尝试递归,用我糟糕的伪代码:

Array_Of_Sets subsets(Array a) 
{
    if (a.length == 0) 
         return [new Set();] // emptyset
    return subsets(a[1:]) + subsets(a[1:]) . map(lambda x |> x.add a[0]) 
}

答案 8 :(得分:0)

这是Mehrdad上述解决方案的一个小改动:

static IEnumerable<T[]> GetSubsets<T>(T[] set) {
    bool[] state = new bool[set.Length+1];
    for (int x; !state[set.Length]; state[x] = true ) {
        yield return Enumerable.Range(0, state.Length)
                               .Where(i => state[i])
                               .Select(i => set[i])
                               .ToArray();
        for (x = 0; state[x]; state[x++] = false);
    }
}

或指针

static IEnumerable<T[]> GetSubsets<T>(T[] set) {
    bool[] state = new bool[set.Length+1];
    for (bool *x; !state[set.Length]; *x = true ) {
        yield return Enumerable.Range(0, state.Length)
                               .Where(i => state[i])
                               .Select(i => set[i])
                               .ToArray();
        for (x = state; *x; *x++ = false);
    }
}

答案 9 :(得分:0)

这里是mqp的answer的变体,它使用BigInteger而不是int作为状态,以避免包含30个以上元素的集合的溢出:

using System.Numerics;

public static IEnumerable<IEnumerable<T>> GetSubsets<T>(IList<T> source)
{
    BigInteger combinations = BigInteger.One << source.Count;
    for (BigInteger i = 0; i < combinations; i++)
    {
        yield return Enumerable.Range(0, source.Count)
            .Select(j => (i & (BigInteger.One << j)) != 0 ? source[j] : default);
    }
}

答案 10 :(得分:0)

易于理解的版本(带有说明)

我认为source = {1,2,3,4}

public static IEnumerable<IEnumerable<T>> GetSubSets<T>(IEnumerable<T> source)
    {
        var result = new List<IEnumerable<T>>() { new List<T>() }; // empty cluster  added

        for (int i = 0; i < source.Count(); i++)
        {
            var elem = source.Skip(i).Take(1);

            // for elem = 2
            // and currently result = [ [],[1] ]
            var matchUps = result.Select(x => x.Concat(elem));
            //then matchUps => [ [2],[1,2] ]

             result = result.Concat(matchUps).ToList();
            //  matchUps and result concat operation
            // finally result = [ [],[1],[2],[1,2] ]
        }
        return result;
    }

答案 11 :(得分:-1)

此编写方式​​更像是一个乘积(笛卡尔乘积),而不是所有子集的列表。

您有三套:(Empty,"dog"), (Empty,"cat"),(Empty,"mouse")

关于产品的一般解决方案,有几篇文章。如前所述,由于每个轴实际上只有2个选择,因此一位可以代表该项目的存在与否。

因此,集合的总集合是从02^N-1的所有数字。如果N < 31可以使用int。