Split List into sublists based on border values - c#

I would like to split up an existing sorted list into multiple sublists, based on the entries of another list.
Let's say I have an array like this:
List<int> myList = [1,3,7,23,56,58,164,185];
and another list, which defines at which places myList should be split:
List<int> borders = [4,59,170];
What's the shortest way to get a nested list where myList is split at the values defined in borders, i.e. like this:
[[1,3],[7,23,56,58],[164],[185]]
I have already solved it by manually looping through the lists, but I can imagine it's easier and shorter using Linq.
EDIT: There is one simplification: numbers can't be the same as the borders, so it's impossible that a number is contained in myList and borders at the same time.

Since you want to group the numbers into different groups, you will want to use GroupBy. The difficulty is only what you use as the key. For this, you can use the largest border value that is smaller than the number. This assumes that borders is sorted though:
List<int> myList = new List<int> { 1, 3, 7, 23, 56, 58, 164, 185 };
List<int> borders = new List<int> { 4, 59, 170 };
var groups = myList.GroupBy(i => borders.LastOrDefault(x => x < i));
foreach (var group in groups)
{
Console.WriteLine("{0}: {1}", group.Key, string.Join(", ", group));
}
This yields the following output:
0: 1, 3
4: 7, 23, 56, 58
59: 164
170: 185
Note that this is not exactly the most efficient solution as it will search for an appropriate border key for every element in myList. If your list is sorted like your example, then it’s more efficient to loop through both at the same time and just match the numbers of myList to the current or next border element. So this solution is O(n * m) while a solution O(n) is possible. On the plus side, this allows myList to be completely unsorted.
For those interested in a O(n) solution, here’s one possible take on it which is just a very general way on grouping sequences:
List<List<int>> groups = new List<List<int>>();
List<int> group = null;
int k = -1;
foreach (int num in myList)
{
if (k < 0 || num > borders[k])
{
group = new List<int>();
groups.Add(group);
k++;
}
group.Add(num);
}

Related

How can I choose the second highest value from a list in c#?

I have a list List<int> myList = new List<int>() { 10, 20, 8, 20, 9, 5, 20, 10 };, I want to choose the second highest value, which is in this case 10. I wrote this code and it works, but I wonder if there is something shorter and better.
List<int> myList = new List<int>() { 10, 20, 8, 20, 9, 5, 20, 10 };
myList = myList.Distinct().ToList();
var descendingOrder = myList.OrderByDescending(i => i);
var sec = descendingOrder.Skip(1).First();
You could just stop using intermediate variables and ToList()
var secondHighest =
myList
.Distinct()
.OrderByDescending(i => i);
.Skip(1)
.First();
This will work the same as your version, but only requires one statement instead of three.
I find it a lot easier to read code list this.
Each LINQ method call on it's own line, and no intermediate variables, especially ones that change (myList is reassigned, which makes it harder to comprehend).
Dave's suggestion to perform all the operations in one pipeline is very good indeed as it avoids:
unnecessary intermediate variables
eagerly creating new collection objects at intermediate steps
reduces clutter.
more readable i.e. it's easier to see what's going on
On the other hand, in terms of efficiency, it might be better to perform two passes over the source list instead of "sorting" the entire list only to take the second item.
var maximum = myList.Max();
var secondMaximum = myList.Where(x => x < maximum).Max();
I think I'd avoid LINQ for this one and just go for a standard "loop over every element, if current is higher than max, push current max to second place, current value to current max"
int sec = int.MinValue;
for(int i =0, m= int.MinValue; i <list.Length; i++)
if(list[i] > m){
sec = m;
m = list[i];
}
Your given logic distincts the values so it looks like 20 is not the second highest in your list even though there are three values that are 20. This is achieve here by the >. If I'd used >= then each 20 would roll the variables and it would behave as if non distincted
If you're interested in performance, test it over a list with a few million entries and pick the one that meets your appetite for readability vs speed
It's not LINQ-y, but it's O(N) and easy to read:
public static int TheSecondMax()
{
List<int> myList = new List<int>() { 10, 20, 8, 20, 9, 5, 20, 10 };
int max = int.MinValue;
int secondMax = int.MinValue;
foreach (var item in myList)
{
if (item > max)
{
max = item;
}
if (item > secondMax && item < max)
{
secondMax = item;
}
}
return secondMax;
}

How to filter a List of int list with condition?

I have this List<List<int>>:
{{1,2},{1,3},{1,4},{2,3},{2,4},{3,4}}
In this list there are 6 list, which contain numbers from 1 to 4, and the occurrence of each number is 3;
I want to filter it in order to get:
{{1,2}{1,3}{2,4}{3,4}}
here the occurrence of each number is 2;
the lists are generated dynamically and I want to be able to filter also dynamically, base on the occurrence;
Edit-More Details
I need to count how many times a number is contain in the List<List<int>>, for the above example is 3. Then I want to exclude lists from the List<List<int>> in order to reduce the number of times from 3 to 2,
The main issue for me was to find a way to not block my computer :), and also to get each number appear for 2 times (mandatory);
Well if it's always a combination of 2 numbers, and they have to appear N times on the list, it means that depending on the N You gonna have:
4 (different digits) x 2 (times hey have to appear) = 8 digits = 4 pairs
4 x 3 (times) = 12 = 6 (pairs)
4 x 4 = 16 = 8 pairs
That means - that from 6 pairs we know we must select 4 pairs that best match the criteria
so based on the basic combinatorics (https://www.khanacademy.org/math/probability/probability-and-combinatorics-topic/permutations/v/permutation-formula)
we have a 6!/2! = (6*5*4*3*2*1)/(2*1)= 360 possible permutations
basically You can have 360 different ways how You put the the second list together.
because it doesn't matter how You arrange the items in the list (the order of items in the list) then the number of possible combinations is 6!/(2!*4!) = 15
https://www.khanacademy.org/math/probability/probability-and-combinatorics-topic/combinations-combinatorics/v/combination-formula
so the thing is - you have 15 possible answers to Your question.
Which means - you only need to loop over it for 15 times.
There are only 15 ways to chose 4 items out of the list of 6
seems like this is a solution to Your - "killing the machine" question.
so next question - how do we find all the possible 'combination'
Let's define all the possible items that we can pick from the input array
for example 1-st, 2-nd, 3-rd and 4-th..
1,2,3,4....... 1,2,3,5...... 1,2,3,6 ...
All the combinations would be (from here https://stackoverflow.com/a/10629938/444149)
static IEnumerable<IEnumerable<T>> GetKCombs<T>(IEnumerable<T> list, int length) where T : IComparable
{
if (length == 1) return list.Select(t => new T[] { t });
return GetKCombs(list, length - 1)
.SelectMany(t => list.Where(o => o.CompareTo(t.Last()) > 0),
(t1, t2) => t1.Concat(new T[] { t2 }));
}
and invoke with (because there are 6 items to pick from, who's indexed are 0,1,2,3,4 and 5)
var possiblePicks = GetKCombs(new List<int> { 0, 1, 2, 3, 4, 5 }, 4);
we get 15 possible combinations
so now - we try taking 4 elements out of the first list, and check if they match the criteria.. if not.. then take another combination
var data = new List<List<int>>
{
new List<int> { 1,2 },
new List<int> { 1,3 },
new List<int> { 1,4 },
new List<int> { 2,3 },
new List<int> { 2,4 },
new List<int> { 3,4 }
};
foreach (var picks in possiblePicks)
{
var listToTest = new List<List<int>>(4);
foreach (var i in picks)
listToTest.Add(data[i]);
var ok = Check(listToTest, 2);
if (ok)
break;
}
private bool Check(List<List<int>> listToTest, int limit)
{
Dictionary<int, int> ret = new Dictionary<int, int>();
foreach (var inputElem in listToTest)
{
foreach (var z in inputElem)
{
var returnCount = ret.ContainsKey(z) ? ret[z] : 0;
if (!ret.ContainsKey(z))
ret.Add(z, returnCount + 1);
else
ret[z]++;
}
}
return ret.All(p => p.Value == limit);
}
I'm sure this can be further optimized to minimize the amount of iterations other the 'listToTest'
Also, this is a lazy implementation (Ienumerable) - so if it so happens that the very first (or second) combination is successful, it stop iterating.
I accepted the Marty's answer because fixed my issue, any way trying to use his method for larger lists, I found my self blocking again my computer so I start looking for another method and I end it up with this one:
var main = new List<HashSet<int>> {
new HashSet<int> {1,2},
new HashSet<int> {1,3},
new HashSet<int> {1,4},
new HashSet<int> {2,3},
new HashSet<int> {2,4},
new HashSet<int> {3,4} };
var items = new HashSet<int>(from l in main from p in l select p); //=>{1,2,3,4}
for (int i =main.Count-1;i-->0; )
{
var occurence=items.Select(a=> main.Where(x => x.Contains(a)).Count()).ToList();
var occurenceSum = 0;
foreach(var j in main[i])
{
occurenceSum += occurence[j - 1];
if (occurenceSum==6) //if both items have occurence=3, then the sum=6, then I can remove that list!
{
main.RemoveAt(i);
}
}
}

Contain a number just once in an array

How do I make an array contain a number just once?From any random numbers added to a textbox i need to first convert them to ints then with the separator i make the program understand the separate ints but how do I an int to be counted in the result list of ints just once?
You could start with using a HashSet as the collection type (.Net 3.5 and higher). This will disallow duplicate values. Then, if you can't use the HashSet as is, you can call .ToArray on it.
That is:
HashSet<int>
You can use Distinct method
input.Split(new char[]{separator},StringSplitOptions.RemoveEmptyEntries)
.Select(x=>int.Parse(x))
.Distinct();
If you want to avoid LINQ
var array=input.Split(new char[]{separator},StringSplitOptions.RemoveEmptyEntries);
var set=new HashSet<int>();
foreach(var x in array)set.Add(int.Parse(x));
var unique=set.ToArray();
Use LINQ's Distinct method to remove the duplicates:
var numbers = new[] { 1, 2, 2, 3 };
numbers = numbers.Distinct().ToArray(); // 1, 2, 3
You can use Array.Contains to check if an item already exists in an array. I'd suggest using a generic collection such as a List or Dictionary instead, though.
Check LINQ method Distinct
Example use:
List<int> ages = new List<int> { 21, 46, 46, 55, 17, 21, 55, 55 };
IEnumerable<int> distinctAges = ages.Distinct();
Console.WriteLine("Distinct ages:");
foreach (int age in distinctAges)
{
Console.WriteLine(age);
}
/*
This code produces the following output:
Distinct ages:
21
46
55
17
*/

Top 5 values from three given arrays

Recently i faced a question in
C#,question is:-
There are three int arrays
Array1={88,65,09,888,87}
Array2={1,49,921,13,33}
Array2={22,44,66,88,110}
Now i have to get array of highest 5 from all these three arrays.What is the most optimized way of doing this in c#?
The way i can think of is take an array of size 15 and add array elements of all three arrays and sort it n get last 5.
An easy way with LINQ:
int[] top5 = array1.Concat(array2).Concat(array3).OrderByDescending(i => i).Take(5).ToArray();
An optimal way:
List<int> highests = new List<int>(); // Keep the current top 5 sorted
// Traverse each array. No need to put them together in an int[][]..it's just for simplicity
foreach (int[] array in new int[][] { array1, array2, array3 }) {
foreach (int i in array) {
int index = highests.BinarySearch(i); // where should i be?
if (highests.Count < 5) { // if not 5 yet, add anyway
if (index < 0) {
highests.Insert(~index, i);
} else { //add (duplicate)
highests.Insert(index, i);
}
}
else if (index < 0) { // not in top-5 yet, add
highests.Insert(~index, i);
highests.RemoveAt(0);
} else if (index > 0) { // already in top-5, add (duplicate)
highests.Insert(index, i);
highests.RemoveAt(0);
}
}
}
Keep a sorted list of the top-5 and traverse each array just once.
You may even check the lowest of the top-5 each time, avoiding the BinarySearch:
List<int> highests = new List<int>();
foreach (int[] array in new int[][] { array1, array2, array3 }) {
foreach (int i in array) {
int index = highests.BinarySearch(i);
if (highests.Count < 5) { // if not 5 yet, add anyway
if (index < 0) {
highests.Insert(~index, i);
} else { //add (duplicate)
highests.Insert(index, i);
}
} else if (highests.First() < i) { // if larger than lowest top-5
if (index < 0) { // not in top-5 yet, add
highests.Insert(~index, i);
highests.RemoveAt(0);
} else { // already in top-5, add (duplicate)
highests.Insert(index, i);
highests.RemoveAt(0);
}
}
}
}
The most optimized way for a fixed K=5 is gong through all arrays five times, picking the highest element not taken so far on each pass. You need to mark the element that you take in order to skip it on subsequent passes. This has the complexity of O(N1+N2+N3) (you go through all N1+N2+N3 elements five times), which is as fast as it can get.
You can combine the arrays using LINQ, sort them, then reverse.
int[] a1 = new int[] { 1, 10, 2, 9 };
int[] a2 = new int[] { 3, 8, 4, 7 };
int[] a3 = new int[] { 2, 9, 8, 4 };
int[] a4 = a1.Concat(a2).Concat(a3).ToArray();
Array.Sort(a4);
Array.Reverse(a4);
for (int i = 0; i < 5; i++)
{
Console.WriteLine(a4[i].ToString());
}
Console.ReadLine();
Prints: 10, 9, 9, 8, 8 from the sample I provided as input for the arrays.
Maybe you could have an array of 5 elements which would be the "max values" array.
Initially fill it with the first 5 values, which in your case would just be the first array. Then loop through the rest of the values. For each value, check it against the 5 max values from least to greatest. If you find the current value from the main list is greater than the value in the max values array, insert it above that element in the array, which would push the last element out. At the end you should have an array of the 5 max values.
For three arrays of length N1,N2,N3, the fastest way should be combining the 3 arrays, and then finding the (N1+N2+N3-4)th order statistic using modified quick sort.
In the resultant array, the elements with indices (N1+N2+N3-5) to the maximum (N1+N2+N3-1) should be your 5 largest. You can also sort them later.
The time complexity of this approach is O(N1+N2+N3) on average.
Here are the two ways for doing this task. The first one is using only basic types. This is the most efficient way, with no extra loop, no extra comparison, and no extra memory consumption. You just pass the index of elements that need to be matched with another one and calculate which is the next index to be matched for each given array.
First Way -
http://www.dotnetbull.com/2013/09/find-max-top-5-number-from-3-sorted-array.html
Second Way -
int[] Array1 = { 09, 65, 87, 89, 888 };
int[] Array2 = { 1, 13, 33, 49, 921 };
int[] Array3 = { 22, 44, 66, 88, 110 };
int [] MergeArr = Array1.Concat(Array2).Concat(Array3).ToArray();
Array.Sort(MergeArr);
int [] Top5Number = MergeArr.Reverse().Take(5).ToArray()
Taken From -
Find max top 5 number from three given sorted array
Short answer: Use a SortedList from Sorted Collection Types in .NET as a min-heap.
Explanation:
From the first array, add 5 elements to this SortedList/min-heap;
Now iterate through all the rest of the elements of arrays:
If an array element is bigger than the smallest element in min-heap then remove the min element and push this array element in the heap;
Else, continue to next array element;
In the end, your min-heap has the 5 biggest elements of all arrays.
Complexity: Takes Log k time to find the minimum when you have a SortedList of k elements. Multiply that by total elements in all arrays because you are going to perform this 'find minimum operation' that many times.
Brings us to overall complexity of O(n * Log k) where n is the total number of elements in all your arrays and k is the number of highest numbers you want.

Check two List<int>'s for the same numbers

I have two List's which I want to check for corresponding numbers.
for example
List<int> a = new List<int>(){1, 2, 3, 4, 5};
List<int> b = new List<int>() {0, 4, 8, 12};
Should give the result 4.
Is there an easy way to do this without too much looping through the lists?
I'm on 3.0 for the project where I need this so no Linq.
You can use the .net 3.5 .Intersect() extension method:-
List<int> a = new List<int>() { 1, 2, 3, 4, 5 };
List<int> b = new List<int>() { 0, 4, 8, 12 };
List<int> common = a.Intersect(b).ToList();
Jeff Richter's excellent PowerCollections has Set with Intersections. Works all the way back to .NET 2.0.
http://www.codeplex.com/PowerCollections
Set<int> set1 = new Set<int>(new[]{1,2,3,4,5});
Set<int> set2 = new Set<int>(new[]{0,4,8,12});
Set<int> set3 = set1.Intersection(set2);
You could do it the way that LINQ does it, effectively - with a set. Now before 3.5 we haven't got a proper set type, so you'd need to use a Dictionary<int,int> or something like that:
Create a Dictionary<int, int> and populate it from list a using the element as both the key and the value for the entry. (The value in the entry really doesn't matter at all.)
Create a new list for the intersections (or write this as an iterator block, whatever).
Iterate through list b, and check with dictionary.ContainsKey: if it does, add an entry to the list or yield it.
That should be O(N+M) (i.e. linear in both list sizes)
Note that that will give you repeated entries if list b contains duplicates. If you wanted to avoid that, you could always change the value of the dictionary entry when you first see it in list b.
You can sort the second list and loop through the first one and for each value do a binary search on the second one.
If both lists are sorted, you can easily do this in O(n) time by doing a modified merge from merge-sort, simply "remove"(step a counter past) the lower of the two leading numbers, if they are ever equal, save that number to the result list and "remove" both of them. it takes less than n(1) + n(2) steps. This is of course assuming they are sorted. But sorting of integer arrays isn't exactly expensive O(n log(n))... I think. If you'd like I can throw together some code on how to do this, but the idea is pretty simple.
Tested on 3.0
List<int> a = new List<int>() { 1, 2, 3, 4, 5, 12, 13 };
List<int> b = new List<int>() { 0, 4, 8, 12 };
List<int> intersection = new List<int>();
Dictionary<int, int> dictionary = new Dictionary<int, int>();
a.ForEach(x => { if(!dictionary.ContainsKey(x))dictionary.Add(x, 0); });
b.ForEach(x => { if(dictionary.ContainsKey(x)) dictionary[x]++; });
foreach(var item in dictionary)
{
if(item.Value > 0)
intersection.Add(item.Key);
}
In comment to question author said that there will be
Max 15 in the first list and 20 in the
second list
In this case I wouldn't bother with optimizations and use List.Contains.
For larger lists hash can be used to take advantage of O(1) lookup that leads to O(N+M) algorithm as Jon noted.
Hash requires additional space. To reduce memory usage we should hash shortest list.
List<int> a = new List<int>() { 1, 2, 3, 4, 5 };
List<int> b = new List<int>() { 0, 4, 8, 12 };
List<int> shortestList;
List<int> longestList;
if (a.Count > b.Count)
{
shortestList = b;
longestList = a;
}
else
{
shortestList = a;
longestList = b;
}
Dictionary<int, bool> dict = new Dictionary<int, bool>();
shortestList.ForEach(x => dict.Add(x, true));
foreach (int i in longestList)
{
if (dict.ContainsKey(i))
{
Console.WriteLine(i);
}
}
var c = a.Intersect(b);
This only works in 3.5 saw your requirement my apologies.
The method recommended by ocdecio is a good one if you're going to implement it from scratch. Looking at the time complexity compared to the nieve method we see:
Sort/binary search method:
T ~= O(n log n) + O(n) * O(log n) ~= O(n log n)
Looping through both lists (nieve method):
T ~= O(n) * O(n) ~= O(n ^ 2)
There may be a quicker method, but I am not aware of it. Hopefully that should justify choosing his method.
(Previous answer - changed IndexOf to Contains, as IndexOf casts to an array first)
Seeing as it's two small lists the code below should be fine. Not sure if there's a library with an intersection method like Java has (although List isn't a set so it wouldn't work), I know as someone pointed out the PowerCollection library has one.
List<int> a = new List<int>() {1, 2, 3, 4, 5};
List<int> b = new List<int>() {0, 4, 8, 12};
List<int> result = new List<int>();
for (int i=0;i < a.Count;i++)
{
if (b.Contains(a[i]))
result.Add(a[i]);
}
foreach (int i in result)
Console.WriteLine(i);
Update 2: HashSet was a dumb answer as it's 3.5 not 3.0
Update: HashSet seems like the obvious answer:
// Method 2 - HashSet from System.Core
HashSet<int> aSet = new HashSet<int>(a);
HashSet<int> bSet = new HashSet<int>(b);
aSet.IntersectWith(bSet);
foreach (int i in aSet)
Console.WriteLine(i);
Here is a method that removed duplicate strings. Change this to accomidate int and it will work fine.
public List<string> removeDuplicates(List<string> inputList)
{
Dictionary<string, int> uniqueStore = new Dictionary<string, int>();
List<string> finalList = new List<string>();
foreach (string currValue in inputList)
{
if (!uniqueStore.ContainsKey(currValue))
{
uniqueStore.Add(currValue, 0);
finalList.Add(currValue);
}
}
return finalList;
}
Update: Sorry, I am actually combining the lists and then removing duplicates. I am passing the combined list to this method. Not exactly what you are looking for.
Wow. The answers thus far look very complicated. Why not just use :
List<int> a = new List<int>() { 1, 2, 3, 4, 5, 12, 13 };
List<int> b = new List<int>() { 0, 4, 8, 12 };
...
public List<int> Dups(List<int> a, List<int> b)
{
List<int> ret = new List<int>();
foreach (int x in b)
{
if (a.Contains(x))
{
ret.add(x);
}
}
return ret;
}
This seems much more straight-forward to me... unless I've missed part of the question. Which is entirely possible.

Categories

Resources