21

Given an array: [dog, cat, mouse]

what is the most elegant way to create:

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

I need this to work for any sized array.

This is essentially a binary counter, where array indices represent bits. This presumably lets me use some bitwise operation to count, but I can't see a nice way of translating this to array indices though.

BartoszKP
  • 34,786
  • 15
  • 102
  • 130
Andrew Bullock
  • 36,616
  • 34
  • 155
  • 231
  • 2
    None of the answers seem to have the elegance you are looking for, what while you wait for an answer, check out http://stackoverflow.com/questions/679203/how-to-find-all-possible-subsets-of-a-given-array – Brandon Bodnar Jun 16 '09 at 00:05
  • All subsets of a set S == the 'power set' of S. http://en.wikipedia.org/wiki/Power_set – mechanical_meat Jun 16 '09 at 00:31

13 Answers13

40

Elegant? Why not Linq it.

    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);
    }
Amy B
  • 108,202
  • 21
  • 135
  • 185
11
 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]);
}
Michael
  • 54,279
  • 5
  • 125
  • 144
9

You can use the BitArray class to easily access the bits in a number:

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);
}
Guffa
  • 687,336
  • 108
  • 737
  • 1,005
7
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;
}

Usage:

foreach(var strings in GetSubsets(new[] { "dog", "cat", "mouse" }))
    Console.WriteLine(string.Join(", ", strings.ToArray()));
Mehrdad Afshari
  • 414,610
  • 91
  • 852
  • 789
  • 1
    That's the answer I would write if I knew C#! But it still could be made nicer with a small change to how Increment works. I'll post it below, because it won't fit into comment. – ilya n. Jun 16 '09 at 00:34
  • I wrote the optimization below :) – ilya n. Jun 16 '09 at 01:13
  • The value of "state" is captured, but by the time the .Select(i => state[i] ... is executed state is all zeros. Need to add a .ToList() after the Select – innominate227 Aug 23 '13 at 14:19
  • @innominate227 You are right about `state` being captured, but it does not make a difference here, as `BitArray` is a reference type anyway. Even if it were not captured, `state` would have referred to the same instance which always be the updated copy with `Increment`. – Mehrdad Afshari Aug 23 '13 at 15:09
6

Guffa's answer had the basic functionality that I was searching, however the line with

BitArray b = new BitArray(i);

did not work for me, it gave an ArgumentOutOfRangeException. Here's my slightly adjusted and working code:

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));
}
Jeankes
  • 159
  • 2
  • 4
  • 12
2

Here's an easy-to-follow solution along the lines of your conception:

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;
    }
}
mqp
  • 70,359
  • 14
  • 95
  • 123
  • 1
    The || source.Length == 0 shouldn't really be there: subsets of empty set exist. If you erase that, it will correctly return empty set. – ilya n. Jun 16 '09 at 01:45
  • Can you please add comments to explain these expressions: `1 << source.Length` `((i & (1 << j)) != 0)` How does it work? – Liran Friedman Apr 18 '18 at 16:54
  • @ilyan. Can you please add comments to explain these lines: `1 << source.Length` and `((i & (1 << j)) != 0)` ? – Liran Friedman Apr 19 '18 at 13:00
2

Here's a solution similar to David B's method, but perhaps more suitable if it's really a requirement that you get back sets with the original number of elements (even if empty):.

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;
}

If you pass in a list of three strings, you'll get back eight lists with three elements each (but some elements will be null).

Ryan Lundy
  • 204,559
  • 37
  • 180
  • 211
1

This is a small change to Mehrdad's solution above:

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);
    }
}

or with pointers

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);
    }
}
Qantas 94 Heavy
  • 15,750
  • 31
  • 68
  • 83
ilya n.
  • 18,398
  • 15
  • 71
  • 89
0

Here is a variant of mqp's answer, that uses as state a BigInteger instead of an int, to avoid overflow for collections containing more than 30 elements:

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);
    }
}
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
0

Easy to understand version (with descriptions)

I assumed that 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;
    }
Cem Tuğut
  • 97
  • 6
0
    int[] arr = new int[] { 1, 2, 3 };
    List<int[]> subsets = new List<int[]>() { new int[] {} };
    for (int i = 0; i < arr.Length; i++)
    {
            foreach (var item in subsets.ToList())
            {

                subsets.Add(item.Concat(new int[] { arr[i] }).ToArray());
            }    
    }
    foreach(var item in subsets)
    {
        Console.WriteLine(String.Join(" ", item));
    }
Tedros
  • 1
  • 1
  • 1
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jul 26 '23 at 11:17
0

I'm not very familiar with C# but I'm sure there's something like:

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

Ok, I've been told the answer above won't work. If you value elegance over efficiency, I would try recursion, in my crappy pseudocode:

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]) 
}
ilya n.
  • 18,398
  • 15
  • 71
  • 89
-1

The way this is written, it is more of a Product (Cartesian product) rather than a list of all subsets.

You have three sets: (Empty,"dog"), (Empty,"cat"),(Empty,"mouse").

There are several posts on general solutions for products. As noted though, since you really just have 2 choices for each axis a single bit can represent the presence or not of the item.

So the total set of sets is all numbers from 0 to 2^N-1. If N < 31 an int will work.

shapiro yaacov
  • 2,308
  • 2
  • 26
  • 39