4

Im looking for some help with finding subsets of array.

int[] array = { 1,2,3,5,8,10,15,23};

I have to find all subsets of an array. If sum of the subsets elements equal to any number in array then my counter increment. For example: 1+2=3, 2+3=5, 5+8+10=23, 1+2+5=8, 2+3+8+10=23

public static void Main(string[] args)
    {
        int[] array = { 1, 2, 3, 5, 8, 10, 15, 23 };
        int arrayLength = array.Count();
        int sum = 0;
        int subsetCount = 0;

        for (int i = 0; i < arrayLength; i++)
        {
            for (int j = i + 1; j < arrayLength; j++)
            {
                sum = array[i] + array[j];

                for (int m = j + 1; m < arrayLength; m++)
                {
                    for (int k = 0; k < arrayLength; k++)
                    {
                        if (array[k] == sum)
                        {
                            subsetCount++;
                        }
                    }
                    sum = array[i] + array[j] + array[m];
                }
            }
        }
        Console.WriteLine(subsetCount);
        Console.ReadLine();
    }

I'm ok with 2-elements and 3-elements of subsets. But 4 and above I can't figured out how to solve it?

Any help would be greatly appreciated

Tsagadai
  • 43
  • 1
  • 7
  • One thing I'd suggest doing is write a method to check If the sum of the subsets elements is equal to any number in array. So get a sum and then pass it to that method, have the method return true or false, if true then increment your counter. – DigitalNinja Sep 15 '15 at 00:16
  • 1
    I thought partial answer can be good duplicate - [How to get all subsets of an array](http://stackoverflow.com/questions/999050/how-to-get-all-subsets-of-an-array), but that may be too hard to jump from that to full answer... – Alexei Levenkov Sep 15 '15 at 00:35
  • To the OP: note that if you are dealing with true subsets (i.e. not the contiguous ones your answers are focusing on), the question referenced by commenter @AlexeiLevenkov includes answers that describe efficient, bit-array-based solutions to finding the subsets. These will work better from a memory-footprint perspective than the recursive approach I describe, but will have similar time constraints as the initial array length increases. If appropriate, both solutions are worth being familiar with. If that addresses your concern, consider closing this question as a duplicate of that one. – Peter Duniho Sep 15 '15 at 00:44

4 Answers4

4

You only need two loops to find the sum of all subsets. The outer loop is the starting point of subsets, and the inner loop is calculating the sums of all subsets from that starting point.

With the first index as starting points the subsets are 1+2, 1+2+3, 1+2+3+5 and so on. As you are only interested in the sum of the subsets you can just add one item after the other to get the sum of the subsets.

Then for each sum loop through the items to check for a match:

int[] array = { 1, 2, 3, 5, 8, 10, 15, 23 };
int subsetCount = 0;
for (int i = 0; i < array.Length; i++) {
  int sum = array[i];
  for (int j = i + 1; j < array.Length; j++) {
    sum += array[j];
    for (int k = 0; k < array.Length; k++) {
      if (array[k] == sum) {
        subsetCount++;
      }
    }
  }
}
Console.WriteLine(subsetCount);

Edit:

I assumed that you meant continuous subsets, but from your examples it seems that you also want non-continuous subsets.

Lets's start with the correct solution:

23 = 15+8, 15+5+3, 15+5+2+1, 10+8+5, 10+8+3+2
15 = 10+5, 10+3+2, 8+5+2
10 = 8+2, 5+3+2
 8 = 5+3, 5+2+1
 5 = 3+2
 3 = 2+1

That gives us 14 different subsets that sums up to an item in the set.

You can count the subsets recursively, only keeping track of the sum and number of items in subsets. You don't need the actual subsets, only to know the sum and that there are at least two items in the subset.

The subsets in a set is the first item combined with all subsets in the rest of the set, plus the subsets in the rest of the set. For example the subsets s() of [1,2,3] is 1,s([2,3]) and s([2,3]).

This gives you:

public static int CountSubsets(int[] arr, int start, int len, int sum) {
  int cnt = 0;
  if (start < arr.Length) {
    if (len >= 1 && arr.Contains(sum + arr[start])) cnt++;
    cnt += CountSubsets(arr, start + 1, len + 1, sum + arr[start]);
    cnt += CountSubsets(arr, start + 1, len, sum);
  }
  return cnt;
}

And calling it:

int[] set = { 1, 2, 3, 5, 8, 10, 15, 23 };
Console.WriteLine(CountSubsets(set, 0, 0, 0));

Output:

14
Guffa
  • 687,336
  • 108
  • 737
  • 1,005
  • It feels that number of subsets is smaller than it should be (close to n^2 instead of 2^n) unless I'm missing something in the post (I don't see "sequential" there). – Alexei Levenkov Sep 15 '15 at 00:38
  • Actually I don't see "all proper subsets" either after reading several times - looks good solution - OP needs to add tests and be done.. – Alexei Levenkov Sep 15 '15 at 00:44
  • @AlexeiLevenkov oops. by sequential sums i meant indexes. it will only check `1+2`, `1+2+3`, `1+2+3+5` and not `2+3+8+10` because `5` is between `3` and `8` – M.kazem Akhgary Sep 15 '15 at 00:47
  • 1
    This is only selecting subsections of the array, not subsets. – Enigmativity Sep 15 '15 at 00:48
  • @Enigmativity: You are right, I assumed continuous subsets. I added a solution including non-continuous subsets. – Guffa Sep 15 '15 at 08:06
  • Would not each element also be a subset that matches the rule? In that case there should be 22 solutions. – Enigmativity Sep 15 '15 at 08:33
  • @Enigmativity: Yes, but the examples and the approach in the question suggests that the OP only wants subsets with at least two items. – Guffa Sep 15 '15 at 09:14
  • @Guffa - The OP hasn't been too clear on his requirements. He seems to have asked the question and run away. ;-) – Enigmativity Sep 15 '15 at 09:29
3

This seems a lot like homework to me. So I will answer in that spirit (i.e. rather than write the code, point you in the right direction).

First, it's not really clear what you mean by "subset". Are you talking about contiguous runs of elements from the array? Or do you literally mean treating the array as an unordered set, from which you examine every possible subset?

The latter is significantly harder than the former. So I'm going to assume the former for the moment.

Then, it seems you really have two different problems:

  • Find all subsets of the array and sum each one
  • For a given sum, determine whether it's in the array

The latter is fairly straightforward. If you know these arrays will always be relatively short (and hopefully they will be, otherwise "find all subsets" may take awhile :) ), you can just do a linear search every time you have a new sum to look for.

Alternatively, a more semantically straightforward approach would be to create a HashSet<int> instance once using the members of the array, and then when you want to know if the sum is in the array, just check your set.

For example:

HashSet<int> setOfValues = new HashSet<int>(array);

Then you can just write setOfValues.Contains(sum) to check whether the value held by the variable sum is contained in the array.

As for the first problem, it seems to me that you really should only need two loops:

  • A loop to iterate on the index of the first element of a subset. I.e. you need to enumerate all subsets, first starting with all subsets that start with element 0, then with all subsets that start with element 1, and so on.
  • A loop to iterate on the length of the subset. I.e. having determined the starting index for your current group of subsets, now you want to find all the actual subsets: the first has length 1, the second has length 2, and so on up to the maximum possible length (i.e. the length of the array minus the current 0-based starting index value).


Considering for a moment the alternative possibility — that you are treating the array as an unordered set — then it seems to me an obvious, if brute-force approach, would be to generate the subsets recursively.

In that approach, you would again have a loop to enumerate the subset lengths, starting with 1, up to the total length of the original set. Then, given a length, you need to pick all possible subsets.

You can do this recursively:

  • Given a set, iterate over the elements of the current set (passed in to your recursive method). The current element is the new element of your current subset.
  • If your current subset is now the correct length, sum it and compare to the original set.
  • Otherwise, remove the current element from the current set and recurse.

The easiest implementation of the above will create new copies of the current set for each level of recursion, to pass to the method when it calls itself. This way you don't have to worry about one level of recursion interfering with previous levels.

Do note that this is going to be practical only for relatively small initial sets. It won't take very large initial arrays before your computer doesn't have enough time or memory to complete the search of all possible subsets.

Peter Duniho
  • 68,759
  • 7
  • 102
  • 136
3

First up, you need a method that will return all subsets.

Func<IEnumerable<int>, IEnumerable<IEnumerable<int>>> getAllSubsets = null;
getAllSubsets = xs =>
    (xs == null || !xs.Any())
        ? Enumerable.Empty<IEnumerable<int>>()
        :  xs.Skip(1).Any()
            ? getAllSubsets(xs.Skip(1))
                .SelectMany(ys => new [] { ys, xs.Take(1).Concat(ys) })
            : new [] { Enumerable.Empty<int>(), xs.Take(1) };

So given getAllSubsets(new[] { 1, 2, 3 }) I get:

{  } 
{ 1 } 
{ 2 } 
{ 1, 2 } 
{ 3 } 
{ 1, 3 } 
{ 2, 3 } 
{ 1, 2, 3 } 

Now's it's easy to compute the result you want.

int[] array = { 1,2,3,5,8,10,15,23};

var result =
    getAllSubsets(array)
        .Where(ss => array.Contains(ss.Sum()))
        .Count();

I get 22.

Enigmativity
  • 113,464
  • 11
  • 89
  • 172
  • 1
    It's an impressive answer. Recursive LINQ is not for the faint of heart. But I have two concerns: 1) if the OP really does want true subsets (which is not at all clear to me...the example given in the question could be indicative, or could just be typos), the dupe Alexei pointed out answers the question, and 2) the LINQ-centric approach is likely to completely obfuscate whatever understanding the OP actually would benefit from here. – Peter Duniho Sep 15 '15 at 00:57
  • @PeterDuniho - Thanks. I do like using a LINQ approach, even if it is a little hairy, because, once coded and verified, it separates the generation of data with the querying and filtering. It just makes the code easier to understand if you are sure the subsets part works. The `for` loop approach is often hard to verify the correctness and I think it is actually more obfuscating. I feel it is better to encapsulate the hairy logic into a single recursive LINQ statement. – Enigmativity Sep 15 '15 at 01:03
0

Using a bit of Linq:

int[] array = {1, 2, 3, 5, 8, 10, 15, 23};
var subsets = new List<IEnumerable<int>>();
int counter = 0;

for (int i = 0; i < array.Length; i++)
{
    for (int j = 2; j < array.Length - i; j++)
    {
        if (array.Contains(array.Skip(i).Take(j).ToList().Sum()))
        {
            counter++;
        }
    }
}

Console.WriteLine("Number of subsets:" + counter);

Gives you:

Number of subsets:5

openshac
  • 4,966
  • 5
  • 46
  • 77
  • 1
    If you're going to use LINQ, why bother materializing the subset list? Why not just call `Sum()` right after `Take()` and save yourself all those allocations? – Peter Duniho Sep 15 '15 at 00:41
  • I want to keep the subset so that I can add it to the list. If I called Sum() straight away I think I would lose the consituents. – openshac Sep 15 '15 at 00:43
  • 1
    @openshac but OP's assignment only asks for count... (At very least you should describe what question you are answering than) – Alexei Levenkov Sep 15 '15 at 00:45
  • 1
    This is only selecting subsections of the array, not subsets. – Enigmativity Sep 15 '15 at 00:48
  • 1
    @openshac - You're not selecting subsets - only sections. The OP's question clearly shows subsets, i.e. `1+2+5=8, 2+3+8+10=23`. – Enigmativity Sep 15 '15 at 00:51
  • I think you missed my point. I say you can drop the call to `ToList()` altogether. If you do want to save the subsets, IMHO it is still better to initialize `subset` without it, check `array.Contains(subset.Sum())`, and then _if_ that is true, call `subsets.Add(subset.ToList())` (i.e. don't allocate the new `List` object unless you're sure you need it. It costs an extra iteration of the subset when the sum is in the original, but you are likely to have a lot more sums not in the original set than in it, so that would likely come out ahead. – Peter Duniho Sep 15 '15 at 00:52