Compare list to itself with parallel execution - c#

i have following code I used up until now to compare a list of file-entrys to itsef by hash-codes
for (int i = 0; i < fileLists.SourceFileListBefore.Count; i++) // Compare SourceFileList-Files to themselves
{
for (int n = i + 1; n < fileLists.SourceFileListBefore.Count; n++) // Don´t need to do the same comparison twice!
{
if (fileLists.SourceFileListBefore[i].targetNode.IsFile && fileLists.SourceFileListBefore[n].targetNode.IsFile)
if (fileLists.SourceFileListBefore[i].hash == fileLists.SourceFileListBefore[n].hash)
{
// do Something
}
}
}
where SourceFileListBefore is a List
I want to change this code to be able to execute parallel on multiple cores. I thought about doing this with PLINQ, but im completely new to LINQ.
I tried
var duplicate = from entry in fileLists.SourceFileListBefore.AsParallel()
where fileLists.SourceFileListBefore.Any(x => (x.hash == entry.hash) && (x.targetNode.IsFile) && (entry.targetNode.IsFile))
select entry;
but it wont work like this, because I have to execute code for each pair of two hash-code matching entrys. So I would at least have to get a collection of results with x+entry from LINQ, not just one entry. Is that possible with PLINQ?

Why don't you look at optimising your code first?
looking at this statement:
if (fileLists.SourceFileListBefore[i].targetNode.IsFile && fileLists.SourceFileListBefore[n].targetNode.IsFile)
Means you can straight away build1 single list of files where IsFile == true (making the loop smaller already)
secondly,
if (fileLists.SourceFileListBefore[i].hash == fileLists.SourceFileListBefore[n].hash)
Why don't you build a hash lookup of the hashes first.
Then iterate over your filtered list, looking up in the lookup you created, if it contains > 1, it means there is a match as (current node hash + some other node hash). So you only do some work on the matched hashes which is not your node.
I wrote a blog post about it which you can read at # CodePERF[dot]NET -.NET Nested Loops vs Hash Lookups
PLINQ will only be slightly improving a bad solution to your problem.
Added some comparisons:
Total File Count: 16900
TargetNode.IsFile == true: 11900
Files with Duplicate Hashes = 10000 (5000 unique hashes)
Files with triplicate Hashes = 900 (300 unique hashes)
Files with Unique hashes = 1000
And the actual setup method:
[SetUp]
public void TestStup()
{
_sw = new Stopwatch();
_files = new List<File>();
int duplicateHashes = 10000;
int triplicateHashesCount = 900;
int randomCount = 1000;
int nonFileCount = 5000;
for (int i = 0; i < duplicateHashes; i++)
{
var hash = i % (duplicateHashes / 2);
_files.Add(new File {Id = i, Hash = hash.ToString(), TargetNode = new Node {IsFile = true}});
}
for (int i = 0; i < triplicateHashesCount; i++)
{
var hash = int.MaxValue - 100000 - i % (triplicateHashesCount / 3);
_files.Add(new File {Id = i, Hash = hash.ToString(), TargetNode = new Node {IsFile = true}});
}
for (int i = 0; i < randomCount; i++)
{
var hash = int.MaxValue - i;
_files.Add(new File { Id = i, Hash = hash.ToString(), TargetNode = new Node { IsFile = true } });
}
for (int i = 0; i < nonFileCount; i++)
{
var hash = i % (nonFileCount / 2);
_files.Add(new File {Id = i, Hash = hash.ToString(), TargetNode = new Node {IsFile = false}});
}
_matched = 0;
}
Than your current method:
[Test]
public void FindDuplicates()
{
_sw.Start();
for (int i = 0; i < _files.Count; i++) // Compare SourceFileList-Files to themselves
{
for (int n = i + 1; n < _files.Count; n++) // Don´t need to do the same comparison twice!
{
if (_files[i].TargetNode.IsFile && _files[n].TargetNode.IsFile)
if (_files[i].Hash == _files[n].Hash)
{
// Do Work
_matched++;
}
}
}
_sw.Stop();
}
Takes around 7.1 seconds on my machine.
Using lookup to find hashes which appear multiple times takes 21ms.
[Test]
public void FindDuplicatesHash()
{
_sw.Start();
var lookup = _files.Where(f => f.TargetNode.IsFile).ToLookup(f => f.Hash);
foreach (var duplicateFiles in lookup.Where(files => files.Count() > 1))
{
// Do Work for each unique hash, which appears multiple times in _files.
// If you need to do work on each pair, you will need to create pairs from duplicateFiles
// this can be an excercise for you ;-)
_matched++;
}
_sw.Stop();
}
In my test, using PLINQ for counting the lookups, is actually slower (As there is a large cost of dividing lists between threads and aggregating results back)
[Test]
public void FindDuplicatesHashParallel()
{
_sw.Start();
var lookup = _files.Where(f => f.TargetNode.IsFile).ToLookup(f => f.Hash);
_matched = lookup.AsParallel().Where(g => g.Count() > 1).Sum(g => 1);
_sw.Stop();
}
This took 120ms, so almost 6 times as long with my current source list.

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

Permutation on array of type "Location", from GoogleMapsAPI NuGet Package

this is my very first post here on StackOverflow so please tell me if I did anything wrong, also english is not my native language, forgive me if there is any gramatical errors.
My question is how can I permutate the items of an array of type "Location", I need to get all possible permutations of waypoints given by the user to then calculate the best route based on time or distance. (I don't want to use the normal route calculation)
I've searched for algorithms but all of them when I put the array of type "Location[]" in the function's parameter I get the error that the object needs to be IEnumerable and I don't know how to convert to that if is even possible, I never worked with IEnumerable.
If it is of any help this is my code for calculating the route:
//Gets the waypoints from a listBox provided by the user, "mode" selects between best time and best distance
//backgroundworker so the UI dont freezes, and return the optimal waypoint order
public Location[] CalcularRota(Location[] waypoints, int mode, BackgroundWorker work, DoWorkEventArgs e)
{
//Declarations
string origem = "";
string destino = "";
Rota[] prop = new Rota[100]; //this index is the number of times the algorithm will be executed, more equals accuracy but much more time to complete
Rota bestDist = new Rota();
Rota bestTime = new Rota();
DirectionService serv = new DirectionService();
DirectionRequest reqs = new DirectionRequest();
DirectionResponse resp;
Random rnd = new Random();
Location[] rndWays;
int dist = 0;
int ti = 0;
bestDist.Distance = 1000000000; //put higher values for the first comparation to be true (end of code)
bestTime.Time = 1000000000;
if (waypoints != null)
{
reqs.Sensor = false;
reqs.Mode = TravelMode.driving;
for (int i = 0; i < prop.Length; i++) //initializes prop
prop[i] = new Rota();
for (int i = 0; i < prop.Length; i++)
{
rndWays = waypoints.OrderBy(x => rnd.Next()).ToArray(); //randomizes the order, I want to get all permutations and then test
//but I dont know how so I've been using randomized
dist = ti = 0;
origem = prop[0].ToString(); //save this particular waypoint's origin and destination
destino = prop[1].ToString();
reqs.Origin = origem;
reqs.Destination = destino;
if (waypoints.Length > 0)
reqs.Waypoints = rndWays;
resp = serv.GetResponse(reqs); //request the route with X order of waypoints to google
if (resp.Status == ServiceResponseStatus.Ok) //wait the response otherwise the program crashes
{
for (int j = 0; j < resp.Routes[0].Legs.Length; j++) //gets the distance and time of this particular order
{
ti += int.Parse(resp.Routes[0].Legs[j].Duration.Value);
dist += int.Parse(resp.Routes[0].Legs[j].Distance.Value);
}
}
prop[i].Origem = origem; //saves this waypoints order details for further comparison
prop[i].Destino = destino;
prop[i].Distance = dist;
prop[i].Time = ti;
prop[i].Order = rndWays;
work.ReportProgress(i); //report the progress
}
for (int i = 0; i < prop.Length; i++) //gets the best distance and time
{
if (bestDist.Distance > prop[i].Distance)
{
bestDist.Distance = prop[i].Distance;
bestDist.Time = prop[i].Time;
bestDist.Order = prop[i].Order;
bestDist.Origem = prop[i].Origem;
bestDist.Destino = prop[i].Destino;
}
if (bestTime.Time > prop[i].Time)
{
bestTime.Distance = prop[i].Distance;
bestTime.Time = prop[i].Time;
bestTime.Order = prop[i].Order;
bestTime.Origem = prop[i].Origem;
bestTime.Destino = prop[i].Destino;
}
}
if (bestDist.Order == bestTime.Order) //if the same waypoint order has the same time and distance
return bestDist.Order; // returns whatever bestDist.Order or bestTime.Order
else if (bestDist.Order != bestTime.Order) //if different returns corresponding to the mode selected
{
if (mode == 1) return bestDist.Order;
if (mode == 2) return bestTime.Order;
}
}
return null;
}
What I want is to permutate the waypoints given and test each permutation, I've been struggling with this for a time, if u guys could help me with any way possible would be great.
Ty.
EDIT.
I found this function here on StackOverflow:
public static bool NextPermutation<T>(T[] elements) where T : IComparable<T>
{
var count = elements.Length;
var done = true;
for (var i = count - 1; i > 0; i--)
{
var curr = elements[i];
// Check if the current element is less than the one before it
if (curr.CompareTo(elements[i - 1]) < 0)
{
continue;
}
// An element bigger than the one before it has been found,
// so this isn't the last lexicographic permutation.
done = false;
// Save the previous (bigger) element in a variable for more efficiency.
var prev = elements[i - 1];
// Have a variable to hold the index of the element to swap
// with the previous element (the to-swap element would be
// the smallest element that comes after the previous element
// and is bigger than the previous element), initializing it
// as the current index of the current item (curr).
var currIndex = i;
// Go through the array from the element after the current one to last
for (var j = i + 1; j < count; j++)
{
// Save into variable for more efficiency
var tmp = elements[j];
// Check if tmp suits the "next swap" conditions:
// Smallest, but bigger than the "prev" element
if (tmp.CompareTo(curr) < 0 && tmp.CompareTo(prev) > 0)
{
curr = tmp;
currIndex = j;
}
}
// Swap the "prev" with the new "curr" (the swap-with element)
elements[currIndex] = prev;
elements[i - 1] = curr;
// Reverse the order of the tail, in order to reset it's lexicographic order
for (var j = count - 1; j > i; j--, i++)
{
var tmp = elements[j];
elements[j] = elements[i];
elements[i] = tmp;
}
// Break since we have got the next permutation
// The reason to have all the logic inside the loop is
// to prevent the need of an extra variable indicating "i" when
// the next needed swap is found (moving "i" outside the loop is a
// bad practice, and isn't very readable, so I preferred not doing
// that as well).
break;
}
// Return whether this has been the last lexicographic permutation.
return done;
}
The usage is:
NextPermutation(array);
Doing this and putting my array (rndWays) as overload I get the following error:
The type 'Google.Maps.Location' cannot be used as type parameter 'T' in the generic type or method 'Form1.NextPermutation< T >(T[])'. There is no implicit reference conversion from 'Google.Maps.Location' to 'System.IComparable< Google.Maps.Location >'.
The problem is that Location does not implement the IComparable interface.
Change:
public static bool NextPermutation<T>(T[] elements) where T : IComparable<T>
to:
public static bool NextPermutation(Location[] elements)
And replace each CompareTo() with your own comparison function.

Grouping by 'equal sums'

I'm trying to take a list of numbers, and put them into >=N groups such that the sums of each groups are approximately (but not necessarily exactly) equal, and 'outliers' can be in a group of their own.
So for a target of 3 groups and an input of something like:
[3, 2, 1, 4, 2, 5]
The output might be:
[[5,1], [4,2], [3,2]]
The respective sums of each group being
6, 6, 5
I think I've got the methodology down, as pseudocode it looks something like this:
let target = Ceil(Sum(Series) / NumberOfTargetGroups) //The ideal size of each group
while (count(UnpickedNumbers) > 0)
let CurrentGroup = new group
while (sum(CurrentGroup) < target)
for each Unpicked in sortDesc(UnpickedNumbers)
if (sum(CurrentGroup) + Unpicked)
Add Unpicked to current group
Remove unpicked from available numbers
What I can't figure out is how to turn that logic into a GroupBy(n => ...) - the reason for wanting to do this being that the list of numbers is actually coming from a property of a series of objects that I want to group in this manner.
Partition is NP-complete problem.
I've preapred snippet:
public IEnumerable<IEnumerable<TObject>> Algo<TObject>(IEnumerable<TObject> source, int groups,
Func<TObject, int> intSelector)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
source = source.OrderByDescending(intSelector);
var evaluated = source as IList<TObject> ?? source.ToList();
if (groups > evaluated.Count())
{
throw new ArgumentException("Invalid group count.");
}
var result = new List<List<TObject>>();
for (var i = 0; i < groups; i++)
{
result.Add(new List<TObject> { evaluated[i] });
}
for (var i = groups; i < evaluated.Count(); i++)
{
var bestIndex = 0;
var bestSum = result[bestIndex].Sum(intSelector);
for (var j = 1; j < result.Count; j++)
{
var sum = result[j].Sum(intSelector);
if (sum < bestSum)
{
bestSum = sum;
bestIndex = j;
}
}
result[bestIndex].Add(evaluated[i]);
}
return result;
}
It is not efficient (there are many ways to optimize it) and the result is not always optimial. But hope that it will be base for your algorithm (maybe approx. is enough for you - test it!).
EDIT:
I've modified snippet for you - you don't have to use GroupBy. Usage:
var widgets = new List<Widget> { W1, W2, etc. };
var result = Algo(widgets, groups: 3, intSelector: widget => widget.Height);

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

Split a collection of double by size of its contents

I have a collection of numbers (Collection) and it can be any size and contain negative and positive numbers. I am trying to split it up based on some criteria. starting at the first number in the collection I want to make a collection while that number is above -180 and below 180. Any numbers above 180 will go in a new collection or any numbers below -180 will go in an new collection. If the numbers become within the acceptable parameters again those will go in a new collection again. the problem is the collections need to stay in order.
For example.
Take a collection of 100:
the first 50 is between 180 and -180.
the next 20 are below -180
the next 20 are above 180
the last 10 are between 180 and -180
From the collection above I should now have 4 separate collection in the same order as the original 1 collection.
First collection numbers in original order between 180 and -180
second collection numbers in original order below -180
third collection numbers in original order above 180
fourth collection numbers in original order between 180 and -180
I have made an attempt, what I have doesn't work and is a nasty mess of if statements. I don't know linq very well but I think there may be a more elegant solution using that. Can anyone help me out here either with showing me how to create a linq statement or suggestions on how to get my if statements to work if that is the best way.
Collection<Tuple<Collection<double>, int>> collectionOfDataSets = new Collection<Tuple<Collection<double>, int>>();
Collection<double> newDataSet = new Collection<double>();
for (int i = 0; i < dataSet.Count; i++) {
if (dataSet[i] < 180 && dataSet[i] > -180) {
newDataSet.Add(dataSet[i]);
} else {
Tuple<Collection<double>, int> lastEntry = collectionOfDataSets.LastOrDefault(b => b.Item2 == i--);
if (lastEntry != null){
lastEntry.Item1.Add(dataSet[i]);
}
double lastInLastCollection = collectionOfDataSets.ElementAtOrDefault(collectionOfDataSets.Count).Item1.Last();
if (newDataSet.Count > 0 && lastInLastCollection!= dataSet[i]){
collectionOfDataSets.Add(new Tuple<Collection<double>, int>(newDataSet, i));
}
newDataSet = new Collection<double>();
}
}
Thank you in advance for any assistance.
Your example is complicated. I'll first state and solve a simpler problem, then use the same method to solve your original problem.
I want to split a list of numbers into contiguous groups of even and odd numbers. For example, given the list 2,2,4,3,6,2 I would split it into three groups [2,2,4], [3], [6,2]
This can be done concisely with a GroupAdjacentBy method
> var numbers = new List<int>{2,2,4,3,6,2};
> numbers.GroupAdjacentBy(x => x % 2)
[[2,2,4], [3], [6,2]]
To solve your problem, simply replace the even-odd classifying function above with your classification function:
> var points = new List<int>{-180,180};
> var f = new Func<int,int>(x => points.BinarySearch(x));
> var numbers = new List<int>{6,-50,100,190,200,20};
> numbers.GroupAdjacentBy(f)
[[6,-50,100], [190,200], [20]]
If you need the collections to be updated as soon as the values change why don;t you use properties? Something like
// your original collection
public IList<double> OriginalValues; //= new List<double> { -1000, 5, 7 1000 };
public IList<double> BelowMinus180
{
get { return OriginalValues.Where(x => x < -180).ToList().AsReadOnly(); }
}
public IList<double> BetweenMinus180And180
{
get { return OriginalValues.Where(x => x >= -180 && x <= 180).ToList().AsReadOnly(); }
}
public IList<double> Above180
{
get { return OriginalValues.Where(x => x > 180).ToList().AsReadOnly(); }
}
public static List<List<T>> PartitionBy<T>(this IEnumerable<T> seq, Func<T, bool> predicate)
{
bool lastPass = true;
return seq.Aggregate(new List<List<T>>(), (partitions, item) =>
{
bool inc = predicate(item);
if (inc == lastPass)
{
if (partitions.Count == 0)
{
partitions.Add(new List<T>());
}
partitions.Last().Add(item);
}
else
{
partitions.Add(new List<T> { item });
}
lastPass = inc;
return partitions;
});
}
You can then use:
List<List<double>> segments = newDataSet.PartitionBy(d => d > -180 && d < 180);
How about this possible solution using two passes. In the first pass we find the indices were a change occurs, and in the second pass we do the actual partitioning.
First an auxiliary method to determine the category:
protected int DetermineCategory(double number)
{
if (number < 180 && number > -180)
return 0;
else if (number < -180)
return 1;
else
return 2;
}
And then the actual algorithm:
List<int> indices = new List<int>();
int currentCategory = -1;
for (int i = 0; i < numbers.Count; i++)
{
int newCat = DetermineCategory(numbers[i]);
if (newCat != currentCategory)
{
indices.Add(i);
currentCategory = newCat;
}
}
List<List<double>> collections = new List<List<double>>(indices.Count);
for (int i = 1; i < indices.Count; ++i)
collections.Add(new List<double>(
numbers.Skip(indices[i - 1]).Take(indices[i] - indices[i - 1])));
Here is a new answer based on the new info you provided. I hope this time I will be closer to what you need
public IEnumerable<IList<double>> GetCollectionOfCollections(IList<double> values, IList<double> boundries)
{
var ordered = values.OrderBy(x => x).ToList();
for (int i = 0; i < boundries.Count; i++)
{
var collection = ordered.Where(x => x < boundries[i]).ToList();
if (collection.Count > 0)
{
ordered = ordered.Except(collection).ToList();
yield return collection.ToList();
}
}
if (ordered.Count() > 0)
{
yield return ordered;
}
}
One method with linq. Untested but should work
var firstSet = dataSet.TakeWhile(x=>x>-180&&x<180);
var totalCount = firstSet.Count();
var secondSet = dataSet.Skip(totalCount).TakeWhile(x=>x<-180);
totalCount+=secondSet.Count();
var thirdSet = dataSet.Skip(totalCount).TakeWhile(x=>x>180);
totalCount += thirdSet.Count();
var fourthSet = dataSet.Skip(totalCount);

Categories

Resources