Reputation: 24685
Say that I have a set of numbers:
Group1 = 10, Group2 = 15, Group3 = 20, Group4 = 30
I want to output the summation of all subsets of numbers
10 + 15 = 25
10 + 15 + 20 = 45
10 + 15 + 20 + 30 = 75
15 + 20 = 35
15 + 20 + 30 = 65
20 + 30 = 50
10 + 20 = 30
10 + 30 = 40
10 + 20 + 30 = 60
... (assumed the rest is typed out)
Each of these groups will have a name, so I would want to print out the names used in the calculation before the result:
Group1 + Group2 = 25
How to do such a thing?
EDIT: to JacobM who edited tags, this is NOT homework and would appreciate an ask before you start editing it as such. I am actually at a customer site who is trying to balance a set of numbers, and the result is coming up incorrectly. My thought was to identify which group of numbers is equal to the delta between the 2 sets, and that would identify the problem directly.
Note: this would be float values, not integers.
EDIT2: added arbitrary so that it is understood that I can not just type this out once with a bunch of string.format's .. I could easily use excel at that point.
Upvotes: 5
Views: 1610
Reputation: 9985
If Group is a custom data type you can overload the +
, -
, *
, /
, =
, ==
, !=
and subsequently +=
, -=
, *=
, and /=
operators as shown here: MSDN: Operator Overloading Tutorial
If your data type is a native data type: int
(Int32
), long
, decimal
, double
, or float
you can do the operations you have.
To output the summation of your numbers you can use:
String.Format("{0} + {1} = {2}", Group1, Group2, (Group1 + Group2));
or
String.Format("{0} + {1} + {2} = {3}", Group1, Group2, Group3, (Group1 + Group2 + Group3));
Finally if in those examples Group is a custom data type, you would also have to overload the ToString()
method so that it can display properly.
<bleepzter/>
OK, Part 2 - OO Algorithm Design?
So lets say you have the following:
public class Set: List<float>
{
public Set():base(){}
public static Set operator+(Set set1, Set set2)
{
Set result = new Set();
result.AddRange(set1.ToArray());
result.AddRange(set2.ToArray());
return result;
}
public float Sum
{
get
{
if( this.Count == 0 )
return 0F;
return this.Sum();
}
}
public override string ToString()
{
string formatString = string.Empty;
string result = string.Empty;
for(int i=0; i<this.Count; i++)
{
formatString += "{" + i.ToString() + "} + ";
}
formatString = result.TrimEnd((" +").ToCharArray()); // remove the last "+ ";
float[] values = this.ToArray();
result = String.Format(formatString, values);
return String.Format("{0} = {1}", result, this.Sum);
}
}
The object Set will have a Sum
property, as well as a ToString()
method that will display the sum and all of its content.
Upvotes: 0
Reputation: 46
Okay, the last one wasn't as straightforward as I thought. I actually tested it this time, and it gives the correct results.
void PrintInner( string output, float total, List<KeyValuePair<string, float>> children )
{
var parent = children[0];
var innerChildren = new List<KeyValuePair<string, float>>();
innerChildren.AddRange( children );
innerChildren.Remove( parent );
output += parent.Key + ":" + parent.Value.ToString();
total += parent.Value;
if( output != "" ) // Will prevent outputting "Group1:10 = 10", comment out if desired.
Console.WriteLine( output + " = " + total.ToString() );
output += " + ";
while( innerChildren.Count > 0 )
{
PrintInner( output, total, innerChildren );
innerChildren.RemoveAt( 0 );
}
}
void PrintAll()
{
var items = new List<KeyValuePair<string,float>>()
{
new KeyValuePair<string,float>>( "Group1", 10 ),
new KeyValuePair<string,float>>( "Group2", 15 ),
new KeyValuePair<string,float>>( "Group3", 20 ),
new KeyValuePair<string,float>>( "Group4", 30 )
}
while( items.Count > 0 )
{
PrintInner( "", 0, items );
items.RemoveAt( 0 );
}
}
Upvotes: 0
Reputation: 10940
Well as already said the key to your solution lies in getting all the possible combinations! You could put something like this in a static class to register it as an extension method:
public static IEnumerable<IEnumerable<T>> Combinations<T>(this IEnumerable<T> elements, int length = -1)
{
switch (length)
{
case -1:
foreach (var combination in Enumerable.Range(1, elements.Count()).Select(count => elements.Combinations(count)).SelectMany(c => c))
yield return combination;
break;
case 0:
yield return new T[0];
break;
default:
if (length < -1) throw new ArgumentOutOfRangeException("length");
foreach (var combination in
elements
.SelectMany((element, index) =>
elements
.Skip(index + 1)
.Combinations(length - 1)
.Select(previous => (new[] { element }).Concat(previous))))
yield return combination;
break;
}
}
... and use it like this:
static void Main(string[] args)
{
var groups = new[]
{
new Tuple<string, int>("Group1", 15),
new Tuple<string, int>("Group2", 5),
new Tuple<string, int>("Group3", 17),
};
foreach (var sum in groups
.Combinations()
.Select(x =>
string.Join(" + ", x.Select(tuple => tuple.Item1)) +
" = " +
x.Sum(tuple => tuple.Item2)))
{
Console.WriteLine(sum);
}
Console.ReadLine();
}
Output:
Group1 = 15
Group2 = 5
Group3 = 17
Group1 + Group2 = 20
Group1 + Group3 = 32
Group2 + Group3 = 22
Group1 + Group2 + Group3 = 37
Upvotes: 0
Reputation: 660098
My thought was to identify which group of numbers is equal to the delta between the 2 sets, and that would identify the problem directly.
The problem "given an integer s, and a set of integers, does any non-empty subset of the set sum to s?" is known as the "subset sum problem". It is extremely well studied, and it is NP-Complete. (See this link for a related problem.)
That is to say it is amongst the hardest problems to solve in a reasonable amount of time. It is widely believed (though at present not proved) that no polynomial-time algorithm can possibly exist for this problem. The best you can do is something like O(2^n) for a set containing n elements.
(I note that your problem is in floats, not integers. It doesn't really matter, as long as you correctly handle the comparison of the calculated sum to the target sum to handle any rounding error that might have accrued in doing the sum.)
For a small number of elements -- you say you have only 15 or so in the set -- your best bet is to just try them all exhaustively. Here's how you do that.
The trick is to realize that there is one subset for each integer from 0 to 2^n. If you look at those numbers in binary:
0000
0001
0010
0011
...
each one corresponds to a subset. The first has no members. The second has just group 1. The third has just group 2. The fourth has group 1 and group 2. And so on.
The pseudocode is easy enough:
for each integer i from 1 to 2^n
{
sum = 0;
for each integer b from 1 to n
{
if the bth bit of i is on then sum = sum + group(b)
}
if sum == target then print out i in binary and quit
}
quit with no solution
Obviously this is O(n 2^n). If you can find an algorithm that always does better than O(c^n), or prove that you cannot find such an algorithm then you'll be famous forever.
The Wikipedia article has a better algorithm that gives an answer much faster most but not all of the time. I would go with the naive algorithm first since it will only take you a few minutes to code up; if it is unacceptably slow then go for the faster, more complex algorithm.
Upvotes: 6
Reputation: 2524
This matches every possible combination...
static void Main(string[] args)
{
Dictionary<string, float> groups = new Dictionary<string, float>();
groups.Add("Group1", 10);
groups.Add("Group2", 15);
groups.Add("Group3", 20);
groups.Add("Group4", 30);
for (int i=0; i < groups.Count - 1; i++)
{
Iterate(groups, i, 0, "");
}
Console.Read();
}
private static void Iterate(Dictionary<string, float> groups, int k, float sum, string s)
{
KeyValuePair<string, float> g = groups.ElementAt(k);
if (string.IsNullOrEmpty(s))
{
s = g.Key;
}
else
{
s += " + " + g.Key;
Console.WriteLine(s + " = " + (sum + g.Value));
}
for (int i = k + 1; i < groups.Count; i++)
{
Iterate(groups, i, sum + g.Value, s);
}
}
Upvotes: 2
Reputation: 7819
This is a fairly classic combination problem. See this post for more details:
Algorithm to return all combinations of k elements from n
Effectively what you want to do is iterate from N-choose-1 through N-choose-N and calculate the sums of each subset.
Upvotes: 0
Reputation: 55427
Here's my 10 cents. It uses the notion that I think @DK was hinting at. You take an integer and convert it to a binary number that represents a bitmask of groups to add. 1
means add it, 0
means skip it. Its in VB but should be convertible to C# pretty easily.
'//Create the group of numbers
Dim Groups As New List(Of Integer)({10, 15, 20, 30})
'//Find the total number groups (Same as 2^Groups.Count() - 1 but reads better for me)
Dim MaxCount = Convert.ToInt32(New String("1"c, Groups.Count), 2)
'//Will hold our string representation of the current bitmask (0011, 1010, etc)
Dim Bits As String
'//Will hold our current total
Dim Total As Integer
'//Will hold the names of the groups added
Dim TextPart As List(Of String)
'//Loop through all possible combination
For I = 0 To MaxCount
'//Create our bitmask
Bits = Convert.ToString(I, 2).PadLeft(Groups.Count, "0")
'//Make sure we have got at least 2 groups
If Bits.Count(Function(ch) ch = "1"c) <= 1 Then Continue For
'//Re-initialize our group array
TextPart = New List(Of String)
'//Reset our total
Total = 0
'//Loop through each bit
For C = 0 To Bits.Count - 1
'//If its a 1, add it
If Bits(C) = "1"c Then
Total += Groups(C)
TextPart.Add("Group" & (C + 1))
End If
Next
'/Output
Trace.WriteLine(Join(TextPart.ToArray(), " + ") & " = " & Total)
Next
Outputs:
Group3 + Group4 = 50
Group2 + Group4 = 45
Group2 + Group3 = 35
Group2 + Group3 + Group4 = 65
Group1 + Group4 = 40
Group1 + Group3 = 30
Group1 + Group3 + Group4 = 60
Group1 + Group2 = 25
Group1 + Group2 + Group4 = 55
Group1 + Group2 + Group3 = 45
Group1 + Group2 + Group3 + Group4 = 75
Upvotes: 0
Reputation: 13372
I've asked a question about converting an integer to byte representation to solve a problem similar to this.
Converting integer to a bit representation
Upvotes: 0