Algorithm to get all combinations of k elements from n - c#

I have a list and I want to do some operation on elements of list starting from combination of 2.
Let's say below is my list:
List<string> strArr = new List<string> { "A", "B", "C", "D", "E", "F", "G", "H" };
It will generate below combination if we select 2 elements at a time:-
(A,B) (A,C) (A,D) (A,E) (A,F) (A,G) (A,H) (B,C) (B,D)
and so on
It will generate below combination if we select 3 elements at a time:-
(A,B,C) (A,B,D) (A,B,E) (A,B,F) (A,B,G) (A,B,H) (A,C,D) (A,C,E) (A,C,F) (A,C,G) (A,C,H) (A,D,E) (A,D,F) (A,D,G) (A,D,H) (A,E,F) (A,E,G) (A,E,H) (A,F,G) (A,F,H) (A,G,H) (B,C,D) (B,C,E) (B,C,F)
and so on
Getting these combinations is very easy. I followed Algorithm to return all combinations of k elements from n
and it is giving me exact output.
But I cannot use this code as I have another requirement where I will keep deleting the elements from the list in case they satisfy certain condition and hence number of combinations will keep on reducing. So I don't want to get all the combinations using LINQ as it will be hampering performance in my case.
I thought of doing it below way:
List<string> strArr = new List<string> { "A", "B", "C", "D", "E", "F", "G", "H" };
// Loop for selecting combination of two elements at time
for (int i = 0; i < strArr.Count; i++)
{
for (int j = i + 1; j < strArr.Count; j++)
{
// Writing on Console
// Actually do some operation to check whether these two elements in list needs to be removed or not
Console.Write(strArr[i] + strArr[j]);
Console.WriteLine();
// Check whether current combination of 2 elements need to be removed or not
if (<< condition >>)
{
// Remove both the current elements
// Remove current element of outer loop
strArr.RemoveAt(i);
// Remove current element of inner loop
// Subtracting one as list size is reduced by 1
strArr.RemoveAt(j - 1);
//
i--;
break;
}
}
}
bool isRemoved = false;
// Loop for selecting combination of three elements at time
for (int i = 0; i < strArr.Count; i++)
{
for (int j = i + 1; j < strArr.Count; j++)
{
for (int k = j + 1; k < s.Count; k++)
{
// Writing on Console
// Actually do some operation to check whether these three elements in list needs to be removed or not
Console.Write(strArr[i] + strArr[j] + strArr[k]);
Console.WriteLine();
// Check whether current combination of 3 elements need to be removed or not
if (<< condition >>)
{
// Remove all the three elements
// Remove current element of outer loop
strArr.RemoveAt(i);
// Remove current element of inner loop
// Subtracting 1 as list size is reduced by 1
strArr.RemoveAt(j - 1);
// Subtracting 2 as list size is reduced by 2
strArr.RemoveAt(k - 2);
isRemoved = true;
i--;
break;
}
// If elements are removed then exit from loop with variable j
if (isRemoved)
{
break;
}
}
}
}
// Now make loop for selecting combination of four elements at time
// and keep removing the elements depending upon condition
Removing elements will ensure that I get faster performance and I want to do this operation till the end reaches. I'm unable to think how to keep these deep level for loops in recursion. Can anyone help me in adding these endless for loops in recursion?
Thanks for spending your time in writing solution for me but this is not what I want... I will brief the requirement without code.
Let's say I have list of 10 elements.
I want to select all the combinations starting from group of 2 to 9. Total number of possible combinations will be 1012 if total elements are 10.
Now I want to start evaluating all the combinations in group of 2. Let's say first group (A,B). I will evaluate this group based upon certain conditions, and if that combination sastify the condition then I will remove the elements (A,B) from the list of 10 elements. So I will left with 8 elements in list.
Total number of combinations with remaining 8 elements will be 246 now. I didn't try the combinations (A,C) (A,D) and so on.
But I'm still evaluating the combinations in group of 2. Now I will pick remaining combinations in group of 2... Next combination will be (C,D) (C,E)..Let's say all remaining combinations doesn't satisfy condition of removing them from the list. Then I want to start evaluating combinations in group of 3.
First group of 3 will be (C,D,E)... If it will pass the certain condition then I will remove all the 3 elements from the list and I will be left with only 5 elements. Now I want to run my test of combination of 3 on these 5 elements.
after that group of 4 and so on
I hope you understand the use case now.
Can anyone help me out in implementing above use case?

The following solution will iterate over all possible combinations of elements from the input list, starting with combinations of 2 elements and moving upward from there. If the supplied filter function returns true then the elements chosen are removed from consideration; thus the total number of iterations is reduced as more elements are removed. Results are not tracked automatically; it's up to the caller to track results as required. My example usage to follow will demonstrate how to track results.
public static void PermutateElements<T>(
IEnumerable<T> elements,
Predicate<IEnumerable<T>> filterGroup)
{
var chooseFrom = new LinkedList<T>(elements);
var chosen = new List<T>(chooseFrom.Count);
for (int chooseCount = 2; chooseCount < chooseFrom.Count - 1; chooseCount++)
{
Permutate(chooseFrom, chooseCount, filterGroup, chosen, 0);
}
}
static bool Permutate<T>(LinkedList<T> chooseFrom, int chooseCount,
Predicate<IEnumerable<T>> filterPermutation, IList<T> chosen, int skipLast)
{
int loopCount = chooseFrom.Count;
for (int i = 0; i < loopCount; i++)
{
var choosingNode = chooseFrom.First;
chooseFrom.RemoveFirst();
bool removeChosen = false;
if (i < loopCount - skipLast)
{
chosen.Add(choosingNode.Value);
if (chooseCount == 1)
removeChosen = filterPermutation(chosen);
else
removeChosen = Permutate(chooseFrom, chooseCount - 1, filterPermutation, chosen, skipLast + i);
chosen.RemoveAt(chosen.Count - 1);
}
if (!removeChosen)
chooseFrom.AddLast(choosingNode);
else if (chosen.Count > 0)
return true;
}
return false;
}
The example below uses these functions in order to group letters; we want to take the letters A thru Z and put them into arbitrary groups such that each group contains more consonants than vowels, and contains at least one vowel:
HashSet<char> vowels = new HashSet<char>(new char[] { 'A', 'E', 'I', 'O', 'U', 'Y' });
var results = new List<IEnumerable<char>>();
Predicate<IEnumerable<char>> processGroup = delegate(IEnumerable<char> groupElements)
{
int vowelCount = groupElements.Count(x => vowels.Contains(x));
int consonantCount = groupElements.Count(x => !vowels.Contains(x));
if (vowelCount < consonantCount && vowelCount > 0)
{
results.Add(new List<char>(groupElements));
return true;
}
else
return false;
};
var elements = new char[26];
for (int i = 0; i < elements.Length; i++)
elements[i] = (char)('A' + i);
PermutateElements(elements, processGroup);
The results, which took 3131 iterations to perform (much fewer than iterating over all possible combinations without removal), are as follows:
ABC
DEF
GHI
JKO
PQU
VWY
At this point all vowels were used up, so no more legal combinations were possible.

Not sure if this is exactly what you need, but it may be considered an approach.
namespace ConsoleApplication
{
class Program
{
static void Main(string[] args)
{
List<Tuple<Expression, Expression>> conditions = new List<Tuple<Expression, Expression>>();
// a complex condition, that the current item contains both "B" and "H"
Expression<Func<IEnumerable<string>, bool>> expression1 = item => item.Contains("B") && item.Contains("H");
// an expression which is used to exclude the elements from the list
Expression<Func<string, bool>> expression2 = j => j != "B" && j != "H";
// associate the condition with the exclusion filter
var condition = new Tuple<Expression, Expression>(expression1, expression2);
conditions.Add(condition);
List<string> strArr = new List<string> { "A", "B", "C", "D", "E", "F", "G", "H" };
IEnumerable<IEnumerable<string>> result = Process(strArr, conditions);
}
private static IEnumerable<IEnumerable<string>> Process(IEnumerable<string> strArr, List<Tuple<Expression, Expression>> conditions)
{
List<IEnumerable<string>> response = new List<IEnumerable<string>>();
int k = 0;
for (int i = 1; i <= strArr.Count(); i++)
{
k++;
var r = strArr.Combinations(Math.Min(strArr.Count(), k));
bool stop=false;
foreach (IEnumerable<string> item in r)
{
if (stop)
{
break;
}
foreach (Tuple<Expression, Expression> condition in conditions)
{
if (Enumerable.Repeat<IEnumerable<string>>(item, 1).Any(Evaluate(condition.Item1) as Func<IEnumerable<string>, bool>))
{
var initialCount = strArr.Count();
strArr = strArr.Where(Evaluate(condition.Item2) as Func<string, bool>);
i -= initialCount - strArr.Count();
stop = true;
break;
}
else
{
foreach (var item1 in r)
{
response.Add(item1);
}
}
}
}
}
return response;
}
public static object Evaluate(Expression e)
{
if (e.NodeType == ExpressionType.Constant)
return ((ConstantExpression)e).Value;
return Expression.Lambda(e).Compile().DynamicInvoke();
}
}
public static class Helper
{
public static IEnumerable<IEnumerable<T>> Combinations<T>(this IEnumerable<T> elements, int k)
{
return k == 0 ? new[] { new T[0] } :
elements.SelectMany((e, i) =>
elements.Skip(i + 1).Combinations(k - 1).Select(c => (new[] { e }).Concat(c))
);
}
}
}
I've used this answer as a helper. You can also see that Process method is loosely coupled with the set of conditions (just one in this example).

Here is an algorithm i wrote in c++ for solving a similar problem. You should be able to use it for your purposes if you modify it a bit to work in c#.
void r_nCr(const unsigned int &startNum, const unsigned int &bitVal, const unsigned int &testNum) // Should be called with arguments (2^r)-1, 2^(r-1), 2^(n-1)
{
unsigned int n = (startNum - bitVal) << 1;
n += bitVal ? 1 : 0;
for (unsigned int i = log2(testNum) + 1; i > 0; i--) // Prints combination as a series of 1s and 0s
cout << (n >> (i - 1) & 1);
cout << endl;
if (!(n & testNum) && n != startNum)
r_nCr(n, bitVal, testNum);
if (bitVal && bitVal < testNum)
r_nCr(startNum, bitVal >> 1, testNum);
}
You can see an explanation of how it works here.

Related

Assigning objects from another list to another group of lists

I have a list of something.
public List<Objects> obj;
The objects in this list need to be added to these other lists.
public List<Objects> objGroup1, objGroup2, objGroup3, objGroup4;
I assign them right now by doing this.
void AssignToGroups()
{
for(int i = 0; i < obj.Count ; i++)
{
//Need the first 4 for group 1 next 4 for group 2 and so on...
if(i < 4)
{
objGroup1.Add(obj[i]);
}
else if(i >= 4 && i < 8)
{
objGroup2.Add(obj[i]);
}
else if (i >= 8 && i < 12)
{
objGroup3.Add(obj[i]);
}
else if (i >= 12 && i < 16)
{
objGroup4.Add(obj[i]);
}
}
}
I'm planning on expanding and my method for grouping objects right now will fill my screen with endless if and else statements.
4 objects need to be assigned to each groups.
The objects in the group gets them in their order of arrangement.
e.g. group1 gets obj 1-4. group 2 get obj 5-8 and so on...
Does anyone have a better method of grouping objects like this?
You can utilize the Skip and Take methods.
You'll need the using System.Linq;:
objGroup1 = obj.Take(4).ToList(); //edited: use ToList() to keep the list format
objGroup2 = obj.Skip(4).Take(4).ToList();
objGroup3 = obj.Skip(8).Take(4).ToList();
objGroup4 = obj.Skip(12).Take(4).ToList();
objGroup5 = obj.Skip(16).Take(4).ToList();
Let me know if it works, since I am not able to test it now, except for the syntax.
You can also group the obj before Take(), such as
var orderedobj = obj.OrderBy(i => "some order criteria").ToList();
objGroup1 = orderedobj.Take(4);
...
I referenced my answer on How to get first N elements of a list in C#?.
EDIT:
In case you somehow do not want to use Linq, you can also use GetRange
objGroup1 = obj.GetRange(0, 4);
objGroup2 = obj.GetRange(3, 4); //since GetRange(index, count) has index starting from 0 instead of 1
objGroup3 = obj.GetRange(7, 4); //count stays the same since we always want 4 elements
objGroup4 = obj.GetRange(11, 4);
objGroup5 = obj.GetRange(15, 4);
Using Keyur's excellent answer, you could create a method that will create the groups for you, based on any source list, with a configurable group size:
private static List<List<object>> AssignToGroups(List<object> source, int groupSize)
{
var groups = new List<List<object>>();
if (source == null || groupSize < 1) return groups;
for (int i = 0; i < source.Count / groupSize; i++)
{
groups.Add(source.Skip(groupSize * i).Take(groupSize).ToList());
}
return groups;
}
Usage
private static void Main()
{
var mainList = new List<object>
{
"one", "two", "three", "four","five",
"six","seven","eight","nine","ten",
"eleven", "twelve", "thirteen", "fourteen","fifteen",
"sixteen","seventeen","eightteen","nineteen","twenty",
"twentyone", "twentytwo", "twentythree", "twentyfour","twentyfive",
"twentysix","twentyseven","twentyeight","twentynine","thirty",
"thirtyone", "thirtytwo", "thirtythree", "thirtyfour","thirtyfive",
"thirtysix","thirtyseven","thirtyeight","thirtynine","forty",
};
var groups = AssignToGroups(mainList, 4);
for (var i = 0; i < groups.Count; i++)
{
Console.WriteLine($"Group #{i + 1}: {string.Join(", ", groups[i])}");
}
Console.WriteLine("\nDone!\nPress any key to exit...");
Console.ReadKey();
}
Output

Sequence and Rangify list of book indexes

I'm coding a MS word based register tool for my office project where application will do complex SR based on Nummer (title numbers, Keywords and Law name) and create a register for each input word file.
Currently application is coded and 90% completed, for latest change request by client, I need following to be added to application.
currently I'm having list of title numbers like
1, 2, 3, 3.1, 3.2,3.3,3.4,4,5,6, 7.1.1,7.1.2,7.1.3
The requirement is to sort them in correct order ascending and Rangify the near numbers.
For example form the above :
1,2,3
should be rangify as:
1-3
3.1,3.2,3.3,3.4
should be rangify as:
3.1-3.4
and
4,5,6
as
4-5
and
7.1.1,7.1.2,7.1.3
as
7.1.1-7.1.3
Ultimately in above list, items should be sequenced and Rangify as bellow :
1-3, 3.1-3.4, 4-6, 7.1.1-7.1.3
I tried separating items by number of levels and adding them to sorted list and checking distance and make them in to one test range yet that didn't work our for me)
Then with some googling I found following c# function yet this function is for integers only
IEnumerable<string> Rangify(IList<int> input) {
for (int i = 0; i < input.Count; ) {
var start = input[i];
int size = 1;
while (++i < input.Count && input[i] == start + size)
size++;
if (size == 1)
yield return start.ToString();
else if (size == 2) {
yield return start.ToString();
yield return (start + 1).ToString();
} else if (size > 2)
yield return start + " - " + (start + size - 1);
}
}
So can someone please instruct me to get a solution for this.
Thanks
You can do something like this :
private static List<string> SortTitleNums(List<string> titleNums)
{
// list that'll hold the result of current operation
List<string> result = new List<string>();
// sorts the input array
titleNums.Sort();
// field that will indicate start and end of a sequence
bool sequenceStarted = false;
for (int i = 0; i < titleNums.Count - 1; i++)
{
// checks if the value is greater than current value by 1 to find sequence
if (Convert.ToInt32(titleNums[i + 1].Replace(".", "")) - Convert.ToInt32(titleNums[i].Replace(".", "")) == 1)
// if sequence is found we add this value to the result list and change sequnceStarted field to true.
{ if (!sequenceStarted) { result.Add(titleNums[i] + "-"); sequenceStarted = true; } }
// if sequence is found and next value does not refer to the sequence than we append the record with current value and change
//value for sequenceStarted field to false. If sequence not found than we just add the number.
else if (sequenceStarted) { result[result.Count - 1] += titleNums[i]; sequenceStarted = false; } else result.Add(titleNums[i]);
}
return result;
}
Example of usage :
static void Main()
{
List<string> titleNums = new List<string>()
{
"1", "2", "6", "3", "3.1", "3.2", "3.3", "8", "7.1.1", "7.1.2", "8.1.1", "7.1.3", "7.2.1",
};
titleNums = SortTitleNums(titleNums);
foreach (var item in titleNums)
Console.WriteLine(item);
Console.ReadKey();
}
Output : "1-3", "3.1-3.3", "6", "7.1.1-7.1.3", "7.2.1", "8"

Combination Algorithm

Length = input Long(can be 2550, 2880, 2568, etc)
List<long> = {618, 350, 308, 300, 250, 232, 200, 128}
The program takes a long value, for that particular long value we have to find the possible combination from the above list which when added give me a input result(same value can be used twice). There can be a difference of +/- 30.
Largest numbers have to be used most.
Ex:Length = 868
For this combinations can be
Combination 1 = 618 + 250
Combination 2 = 308 + 232 + 200 +128
Correct Combination would be Combination 1
But there should also be different combinations.
public static void Main(string[] args)
{
//subtotal list
List<int> totals = new List<int>(new int[] { 618, 350, 308, 300, 250, 232, 200, 128 });
// get matches
List<int[]> results = KnapSack.MatchTotal(2682, totals);
// print results
foreach (var result in results)
{
Console.WriteLine(string.Join(",", result));
}
Console.WriteLine("Done.");
}
internal static List<int[]> MatchTotal(int theTotal, List<int> subTotals)
{
List<int[]> results = new List<int[]>();
while (subTotals.Contains(theTotal))
{
results.Add(new int[1] { theTotal });
subTotals.Remove(theTotal);
}
if (subTotals.Count == 0)
return results;
subTotals.Sort();
double mostNegativeNumber = subTotals[0];
if (mostNegativeNumber > 0)
mostNegativeNumber = 0;
if (mostNegativeNumber == 0)
subTotals.RemoveAll(d => d > theTotal);
for (int choose = 0; choose <= subTotals.Count; choose++)
{
IEnumerable<IEnumerable<int>> combos = Combination.Combinations(subTotals.AsEnumerable(), choose);
results.AddRange(from combo in combos where combo.Sum() == theTotal select combo.ToArray());
}
return results;
}
public static class Combination
{
public static IEnumerable<IEnumerable<T>> Combinations<T>(this IEnumerable<T> elements, int choose)
{
return choose == 0 ?
new[] { new T[0] } :
elements.SelectMany((element, i) =>
elements.Skip(i + 1).Combinations(choose - 1).Select(combo => (new[] { element }).Concat(combo)));
}
}
I Have used the above code, can it be more simplified, Again here also i get unique values. A value can be used any number of times. But the largest number has to be given the most priority.
I have a validation to check whether the total of the sum is greater than the input value. The logic fails even there..
The algorithm you have shown assumes that the list is sorted in ascending order. If not, then you shall first have to sort the list in O(nlogn) time and then execute the algorithm.
Also, it assumes that you are only considering combinations of pairs and you exit on the first match.
If you want to find all combinations, then instead of "break", just output the combination and increment startIndex or decrement endIndex.
Moreover, you should check for ranges (targetSum - 30 to targetSum + 30) rather than just the exact value because the problem says that a margin of error is allowed.
This is the best solution according to me because its complexity is O(nlogn + n) including the sorting.
V4 - Recursive Method, using Stack structure instead of stack frames on thread
It works (tested in VS), but there could be some bugs remaining.
static int Threshold = 30;
private static Stack<long> RecursiveMethod(long target)
{
Stack<long> Combination = new Stack<long>(establishedValues.Count); //Can grow bigger, as big as (target / min(establishedValues)) values
Stack<int> Index = new Stack<int>(establishedValues.Count); //Can grow bigger
int lowerBound = 0;
int dimensionIndex = lowerBound;
long fail = -1 * Threshold;
while (true)
{
long thisVal = establishedValues[dimensionIndex];
dimensionIndex++;
long afterApplied = target - thisVal;
if (afterApplied < fail)
lowerBound = dimensionIndex;
else
{
target = afterApplied;
Combination.Push(thisVal);
if (target <= Threshold)
return Combination;
Index.Push(dimensionIndex);
dimensionIndex = lowerBound;
}
if (dimensionIndex >= establishedValues.Count)
{
if (Index.Count == 0)
return null; //No possible combinations
dimensionIndex = Index.Pop();
lowerBound = dimensionIndex;
target += Combination.Pop();
}
}
}
Maybe V3 - Suggestion for Ordered solution trying every combination
Although this isn't chosen as the answer for the related question, I believe this is a good approach - https://stackoverflow.com/a/17258033/887092(, otherwise you could try the chosen answer (although the output for that is only 2 items in set being summed, rather than up to n items)) - it will enumerate every option including multiples of the same value. V2 works but would be slightly less efficient than an ordered solution, as the same failing-attempt will likely be attempted multiple times.
V2 - Random Selection - Will be able to reuse the same number twice
I'm a fan of using random for "intelligence", allowing the computer to brute force the solution. It's also easy to distribute - as there is no state dependence between two threads trying at the same time for example.
static int Threshold = 30;
public static List<long> RandomMethod(long Target)
{
List<long> Combinations = new List<long>();
Random rnd = new Random();
//Assuming establishedValues is sorted
int LowerBound = 0;
long runningSum = Target;
while (true)
{
int newLowerBound = FindLowerBound(LowerBound, runningSum);
if (newLowerBound == -1)
{
//No more beneficial values to work with, reset
runningSum = Target;
Combinations.Clear();
LowerBound = 0;
continue;
}
LowerBound = newLowerBound;
int rIndex = rnd.Next(LowerBound, establishedValues.Count);
long val = establishedValues[rIndex];
runningSum -= val;
Combinations.Add(val);
if (Math.Abs(runningSum) <= 30)
return Combinations;
}
}
static int FindLowerBound(int currentLowerBound, long runningSum)
{
//Adjust lower bound, so we're not randomly trying a number that's too high
for (int i = currentLowerBound; i < establishedValues.Count; i++)
{
//Factor in the threshold, because an end aggregate which exceeds by 20 is better than underperforming by 21.
if ((establishedValues[i] - Threshold) < runningSum)
{
return i;
}
}
return -1;
}
V1 - Ordered selection - Will not be able to reuse the same number twice
Add this very handy extension function (uses a binary algorithm to find all combinations):
//Make sure you put this in a static class inside System namespace
public static IEnumerable<List<T>> EachCombination<T>(this List<T> allValues)
{
var collection = new List<List<T>>();
for (int counter = 0; counter < (1 << allValues.Count); ++counter)
{
List<T> combination = new List<T>();
for (int i = 0; i < allValues.Count; ++i)
{
if ((counter & (1 << i)) == 0)
combination.Add(allValues[i]);
}
if (combination.Count == 0)
continue;
yield return combination;
}
}
Use the function
static List<long> establishedValues = new List<long>() {618, 350, 308, 300, 250, 232, 200, 128, 180, 118, 155};
//Return is a list of the values which sum to equal the target. Null if not found.
List<long> FindFirstCombination(long target)
{
foreach (var combination in establishedValues.EachCombination())
{
//if (combination.Sum() == target)
if (Math.Abs(combination.Sum() - target) <= 30) //Plus or minus tolerance for difference
return combination;
}
return null; //Or you could throw an exception
}
Test the solution
var target = 858;
var result = FindFirstCombination(target);
bool success = (result != null && result.Sum() == target);
//TODO: for loop with random selection of numbers from the establishedValues, Sum and test through FindFirstCombination

How can I obtain all the possible combination of a subset?

Consider this List<string>
List<string> data = new List<string>();
data.Add("Text1");
data.Add("Text2");
data.Add("Text3");
data.Add("Text4");
The problem I had was: how can I get every combination of a subset of the list?
Kinda like this:
#Subset Dimension 4
Text1;Text2;Text3;Text4
#Subset Dimension 3
Text1;Text2;Text3;
Text1;Text2;Text4;
Text1;Text3;Text4;
Text2;Text3;Text4;
#Subset Dimension 2
Text1;Text2;
Text1;Text3;
Text1;Text4;
Text2;Text3;
Text2;Text4;
#Subset Dimension 1
Text1;
Text2;
Text3;
Text4;
I came up with a decent solution which a think is worth to share here.
Similar logic as Abaco's answer, different implementation....
foreach (var ss in data.SubSets_LB())
{
Console.WriteLine(String.Join("; ",ss));
}
public static class SO_EXTENSIONS
{
public static IEnumerable<IEnumerable<T>> SubSets_LB<T>(
this IEnumerable<T> enumerable)
{
List<T> list = enumerable.ToList();
ulong upper = (ulong)1 << list.Count;
for (ulong i = 0; i < upper; i++)
{
List<T> l = new List<T>(list.Count);
for (int j = 0; j < sizeof(ulong) * 8; j++)
{
if (((ulong)1 << j) >= upper) break;
if (((i >> j) & 1) == 1)
{
l.Add(list[j]);
}
}
yield return l;
}
}
}
I think, the answers in this question need some performance tests. I'll give it a go. It is community wiki, feel free to update it.
void PerfTest()
{
var list = Enumerable.Range(0, 21).ToList();
var t1 = GetDurationInMs(list.SubSets_LB);
var t2 = GetDurationInMs(list.SubSets_Jodrell2);
var t3 = GetDurationInMs(() => list.CalcCombinations(20));
Console.WriteLine("{0}\n{1}\n{2}", t1, t2, t3);
}
long GetDurationInMs(Func<IEnumerable<IEnumerable<int>>> fxn)
{
fxn(); //JIT???
var count = 0;
var sw = Stopwatch.StartNew();
foreach (var ss in fxn())
{
count = ss.Sum();
}
return sw.ElapsedMilliseconds;
}
OUTPUT:
1281
1604 (_Jodrell not _Jodrell2)
6817
Jodrell's Update
I've built in release mode, i.e. optimizations on. When I run via Visual Studio I don't get a consistent bias between 1 or 2, but after repeated runs LB's answer wins, I get answers approaching something like,
1190
1260
more
but if I run the test harness from the command line, not via Visual Studio, I get results more like this
987
879
still more
EDIT
I've accepted the performance gauntlet, what follows is my amalgamation that takes the best of all answers. In my testing, it seems to have the best performance yet.
public static IEnumerable<IEnumerable<T>> SubSets_Jodrell2<T>(
this IEnumerable<T> source)
{
var list = source.ToList();
var limit = (ulong)(1 << list.Count);
for (var i = limit; i > 0; i--)
{
yield return list.SubSet(i);
}
}
private static IEnumerable<T> SubSet<T>(
this IList<T> source, ulong bits)
{
for (var i = 0; i < source.Count; i++)
{
if (((bits >> i) & 1) == 1)
{
yield return source[i];
}
}
}
Same idea again, almost the same as L.B's answer but my own interpretation.
I avoid the use of an internal List and Math.Pow.
public static IEnumerable<IEnumerable<T>> SubSets_Jodrell(
this IEnumerable<T> source)
{
var count = source.Count();
if (count > 64)
{
throw new OverflowException("Not Supported ...");
}
var limit = (ulong)(1 << count) - 2;
for (var i = limit; i > 0; i--)
{
yield return source.SubSet(i);
}
}
private static IEnumerable<T> SubSet<T>(
this IEnumerable<T> source,
ulong bits)
{
var check = (ulong)1;
foreach (var t in source)
{
if ((bits & check) > 0)
{
yield return t;
}
check <<= 1;
}
}
You'll note that these methods don't work with more than 64 elements in the intial set but it starts to take a while then anyhow.
I developed a simple ExtensionMethod for lists:
/// <summary>
/// Obtain all the combinations of the elements contained in a list
/// </summary>
/// <param name="subsetDimension">Subset Dimension</param>
/// <returns>IEnumerable containing all the differents subsets</returns>
public static IEnumerable<List<T>> CalcCombinations<T>(this List<T> list, int subsetDimension)
{
//First of all we will create a binary matrix. The dimension of a single row
//must be the dimension of list
//on which we are working (we need a 0 or a 1 for every single element) so row
//dimension is to obtain a row-length = list.count we have to
//populate the matrix with the first 2^list.Count binary numbers
int rowDimension = Convert.ToInt32(Math.Pow(2, list.Count));
//Now we start counting! We will fill our matrix with every number from 1
//(0 is meaningless) to rowDimension
//we are creating binary mask, hence the name
List<int[]> combinationMasks = new List<int[]>();
for (int i = 1; i < rowDimension; i++)
{
//I'll grab the binary rapresentation of the number
string binaryString = Convert.ToString(i, 2);
//I'll initialize an array of the apropriate dimension
int[] mask = new int[list.Count];
//Now, we have to convert our string in a array of 0 and 1, so first we
//obtain an array of int then we have to copy it inside our mask
//(which have the appropriate dimension), the Reverse()
//is used because of the behaviour of CopyTo()
binaryString.Select(x => x == '0' ? 0 : 1).Reverse().ToArray().CopyTo(mask, 0);
//Why should we keep masks of a dimension which isn't the one of the subset?
// We have to filter it then!
if (mask.Sum() == subsetDimension) combinationMasks.Add(mask);
}
//And now we apply the matrix to our list
foreach (int[] mask in combinationMasks)
{
List<T> temporaryList = new List<T>(list);
//Executes the cycle in reverse order to avoid index out of bound
for (int iter = mask.Length - 1; iter >= 0; iter--)
{
//Whenever a 0 is found the correspondent item is removed from the list
if (mask[iter] == 0)
temporaryList.RemoveAt(iter);
}
yield return temporaryList;
}
}
}
So considering the example in the question:
# Row Dimension of 4 (list.Count)
Binary Numbers to 2^4
# Binary Matrix
0 0 0 1 => skip
0 0 1 0 => skip
[...]
0 1 1 1 => added // Text2;Text3;Text4
[...]
1 0 1 1 => added // Text1;Text3;Text4
1 1 0 0 => skip
1 1 0 1 => added // Text1;Text2;Text4
1 1 1 0 => added // Text1;Text2;Text3
1 1 1 1 => skip
Hope this can help someone :)
If you need clarification or you want to contribute feel free to add answers or comments (which one is more appropriate).

in C#, how do I order items in a list where the "largest" values are in the middle of the list

I have been stumped on this one for a while. I want to take a List and order the list such that the Products with the largest Price end up in the middle of the list. And I also want to do the opposite, i.e. make sure that the items with the largest price end up on the outer boundaries of the list.
Imagine a data structure like this.. 1,2,3,4,5,6,7,8,9,10
In the first scenario I need to get back 1,3,5,7,9,10,8,6,4,2
In the second scenario I need to get back 10,8,6,4,2,1,3,5,7,9
The list may have upwards of 250 items, the numbers will not be evenly distributed, and they will not be sequential, and I wanted to minimize copying. The numbers will be contained in Product objects, and not simple primitive integers.
Is there a simple solution that I am not seeing?
Any thoughts.
So for those of you wondering what I am up to, I am ordering items based on calculated font size. Here is the code that I went with...
The Implementation...
private void Reorder()
{
var tempList = new LinkedList<DisplayTag>();
bool even = true;
foreach (var tag in this) {
if (even)
tempList.AddLast(tag);
else
tempList.AddFirst(tag);
even = !even;
}
this.Clear();
this.AddRange(tempList);
}
The Test...
[TestCase(DisplayTagOrder.SmallestToLargest, Result=new[]{10,14,18,22,26,30})]
[TestCase(DisplayTagOrder.LargestToSmallest, Result=new[]{30,26,22,18,14,10})]
[TestCase(DisplayTagOrder.LargestInTheMiddle, Result = new[] { 10, 18, 26, 30, 22, 14 })]
[TestCase(DisplayTagOrder.LargestOnTheEnds, Result = new[] { 30, 22, 14, 10, 18, 26 })]
public int[] CalculateFontSize_Orders_Tags_Appropriately(DisplayTagOrder sortOrder)
{
list.CloudOrder = sortOrder;
list.CalculateFontSize();
var result = (from displayTag in list select displayTag.FontSize).ToArray();
return result;
}
The Usage...
public void CalculateFontSize()
{
GetMaximumRange();
GetMinimunRange();
CalculateDelta();
this.ForEach((displayTag) => CalculateFontSize(displayTag));
OrderByFontSize();
}
private void OrderByFontSize()
{
switch (CloudOrder) {
case DisplayTagOrder.SmallestToLargest:
this.Sort((arg1, arg2) => arg1.FontSize.CompareTo(arg2.FontSize));
break;
case DisplayTagOrder.LargestToSmallest:
this.Sort(new LargestFirstComparer());
break;
case DisplayTagOrder.LargestInTheMiddle:
this.Sort(new LargestFirstComparer());
Reorder();
break;
case DisplayTagOrder.LargestOnTheEnds:
this.Sort();
Reorder();
break;
}
}
The appropriate data structure is a LinkedList because it allows you to efficiently add to either end:
LinkedList<int> result = new LinkedList<int>();
int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
Array.Sort(array);
bool odd = true;
foreach (var x in array)
{
if (odd)
result.AddLast(x);
else
result.AddFirst(x);
odd = !odd;
}
foreach (int item in result)
Console.Write("{0} ", item);
No extra copying steps, no reversing steps, ... just a small overhead per node for storage.
C# Iterator version
(Very simple code to satisfy all conditions.)
One function to rule them all! Doesn't use intermediate storage collection (see yield keyword). Orders the large numbers either to the middle, or to the sides depending on the argument. It's implemented as a C# iterator
// Pass forward sorted array for large middle numbers,
// or reverse sorted array for large side numbers.
//
public static IEnumerable<long> CurveOrder(long[] nums) {
if (nums == null || nums.Length == 0)
yield break; // Nothing to do.
// Move forward every two.
for (int i = 0; i < nums.Length; i+=2)
yield return nums[i];
// Move backward every other two. Note: Length%2 makes sure we're on the correct offset.
for (int i = nums.Length-1 - nums.Length%2; i >= 0; i-=2)
yield return nums[i];
}
Example Usage
For example with array long[] nums = { 1,2,3,4,5,6,7,8,9,10,11 };
Start with forward sort order, to bump high numbers into the middle.
Array.Sort(nums); //forward sort
// Array argument will be: { 1,2,3,4,5,6,7,8,9,10,11 };
long[] arrLargeMiddle = CurveOrder(nums).ToArray();
Produces: 1 3 5 7 9 11 10 8 6 4 2
Or, Start with reverse sort order, to push high numbers to sides.
Array.Reverse(nums); //reverse sort
// Array argument will be: { 11,10,9,8,7,6,5,4,3,2,1 };
long[] arrLargeSides = CurveOrder(nums).ToArray();
Produces: 11 9 7 5 3 1 2 4 6 8 10
Significant namespaces are:
using System;
using System.Collections.Generic;
using System.Linq;
Note: The iterator leaves the decision up to the caller about whether or not to use intermediate storage. The caller might simply be issuing a foreach loop over the results instead.
Extension Method Option
Optionally change the static method header to use the this modifier public static IEnumerable<long> CurveOrder(this long[] nums) { and put it inside a static class in your namespace;
Then call the order method directly on any long[ ] array instance like so:
Array.Reverse(nums); //reverse sort
// Array argument will be: { 11,10,9,8,7,6,5,4,3,2,1 };
long[] arrLargeSides = nums.CurveOrder().ToArray();
Just some (unneeded) syntactic sugar to mix things up a bit for fun. This can be applied to any answers to your question that take an array argument.
I might go for something like this
static T[] SortFromMiddleOut<T, U>(IList<T> list, Func<T, U> orderSelector, bool largestInside) where U : IComparable<U>
{
T[] sortedArray = new T[list.Count];
bool add = false;
int index = (list.Count / 2);
int iterations = 0;
IOrderedEnumerable<T> orderedList;
if (largestInside)
orderedList = list.OrderByDescending(orderSelector);
else
orderedList = list.OrderBy(orderSelector);
foreach (T item in orderedList)
{
sortedArray[index] = item;
if (add)
index += ++iterations;
else
index -= ++iterations;
add = !add;
}
return sortedArray;
}
Sample invocations:
int[] array = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
int[] sortedArray = SortFromMiddleOut(array, i => i, false);
foreach (int item in sortedArray)
Console.Write("{0} ", item);
Console.Write("\n");
sortedArray = SortFromMiddleOut(array, i => i, true);
foreach (int item in sortedArray)
Console.Write("{0} ", item);
With it being generic, it could be a list of Foo and the order selector could be f => f.Name or whatever you want to throw at it.
The fastest (but not the clearest) solution is probably to simply calculate the new index for each element:
Array.Sort(array);
int length = array.Length;
int middle = length / 2;
int[] result2 = new int[length];
for (int i = 0; i < array.Length; i++)
{
result2[middle + (1 - 2 * (i % 2)) * ((i + 1) / 2)] = array[i];
}
Something like this?
public IEnumerable<int> SortToMiddle(IEnumerable<int> input)
{
var sorted = new List<int>(input);
sorted.Sort();
var firstHalf = new List<int>();
var secondHalf = new List<int>();
var sendToFirst = true;
foreach (var current in sorted)
{
if (sendToFirst)
{
firstHalf.Add(current);
}
else
{
secondHalf.Add(current);
}
sendToFirst = !sendToFirst;
}
//to get the highest values on the outside just reverse
//the first list instead of the second
secondHalf.Reverse();
return firstHalf.Concat(secondHalf);
}
For your specific (general) case (assuming unique keys):
public static IEnumerable<T> SortToMiddle<T, TU>(IEnumerable<T> input, Func<T, TU> getSortKey)
{
var sorted = new List<TU>(input.Select(getSortKey));
sorted.Sort();
var firstHalf = new List<TU>();
var secondHalf = new List<TU>();
var sendToFirst = true;
foreach (var current in sorted)
{
if (sendToFirst)
{
firstHalf.Add(current);
}
else
{
secondHalf.Add(current);
}
sendToFirst = !sendToFirst;
}
//to get the highest values on the outside just reverse
//the first list instead of the second
secondHalf.Reverse();
sorted = new List<TU>(firstHalf.Concat(secondHalf));
//This assumes the sort keys are unique - if not, the implementation
//needs to use a SortedList<TU, T>
return sorted.Select(s => input.First(t => s.Equals(getSortKey(t))));
}
And assuming non-unique keys:
public static IEnumerable<T> SortToMiddle<T, TU>(IEnumerable<T> input, Func<T, TU> getSortKey)
{
var sendToFirst = true;
var sorted = new SortedList<TU, T>(input.ToDictionary(getSortKey, t => t));
var firstHalf = new SortedList<TU, T>();
var secondHalf = new SortedList<TU, T>();
foreach (var current in sorted)
{
if (sendToFirst)
{
firstHalf.Add(current.Key, current.Value);
}
else
{
secondHalf.Add(current.Key, current.Value);
}
sendToFirst = !sendToFirst;
}
//to get the highest values on the outside just reverse
//the first list instead of the second
secondHalf.Reverse();
return(firstHalf.Concat(secondHalf)).Select(kvp => kvp.Value);
}
Simplest solution - order the list descending, create two new lists, into the first place every odd-indexed item, into the other every even indexed item. Reverse the first list then append the second to the first.
Okay, I'm not going to question your sanity here since I'm sure you wouldn't be asking the question if there weren't a good reason :-)
Here's how I'd approach it. Create a sorted list, then simply create another list by processing the keys in order, alternately inserting before and appending, something like:
sortedlist = list.sort (descending)
biginmiddle = new list()
state = append
foreach item in sortedlist:
if state == append:
biginmiddle.append (item)
state = prepend
else:
biginmiddle.insert (0, item)
state = append
This will give you a list where the big items are in the middle. Other items will fan out from the middle (in alternating directions) as needed:
1, 3, 5, 7, 9, 10, 8, 6, 4, 2
To get a list where the larger elements are at the ends, just replace the initial sort with an ascending one.
The sorted and final lists can just be pointers to the actual items (since you state they're not simple integers) - this will minimise both extra storage requirements and copying.
Maybe its not the best solution, but here's a nifty way...
Let Product[] parr be your array.
Disclaimer It's java, my C# is rusty.
Untested code, but you get the idea.
int plen = parr.length
int [] indices = new int[plen];
for(int i = 0; i < (plen/2); i ++)
indices[i] = 2*i + 1; // Line1
for(int i = (plen/2); i < plen; i++)
indices[i] = 2*(plen-i); // Line2
for(int i = 0; i < plen; i++)
{
if(i != indices[i])
swap(parr[i], parr[indices[i]]);
}
The second case, Something like this?
int plen = parr.length
int [] indices = new int[plen];
for(int i = 0; i <= (plen/2); i ++)
indices[i] = (plen^1) - 2*i;
for(int i = 0; i < (plen/2); i++)
indices[i+(plen/2)+1] = 2*i + 1;
for(int i = 0; i < plen; i++)
{
if(i != indices[i])
swap(parr[i], parr[indices[i]]);
}

Categories

Resources