I would like to do something like this (below) but not sure if there is a formal/optimized syntax to do so?
.Orderby(i => i.Value1)
.Take("Bottom 100 & Top 100")
.Orderby(i => i.Value2);
basically, I want to sort by one variable, then take the top 100 and bottom 100, and then sort those results by another variable.
Any suggestions?
var sorted = list.OrderBy(i => i.Value);
var top100 = sorted.Take(100);
var last100 = sorted.Reverse().Take(100);
var result = top100.Concat(last100).OrderBy(i => i.Value2);
I don't know if you want Concat or Union at the end. Concat will combine all entries of both lists even if there are similar entries which would be the case if your original list contains less than 200 entries. Union would only add stuff from last100 that is not already in top100.
Some things that are not clear but that should be considered:
If list is an IQueryable to a db, it probably is advisable to use ToArray() or ToList(), e.g.
var sorted = list.OrderBy(i => i.Value).ToArray();
at the beginning. This way only one query to the database is done while the rest is done in memory.
The Reverse method is not optimized the way I hoped for, but it shouldn't be a problem, since ordering the list is the real deal here. For the record though, the skip method explained in other answers here is probably a little bit faster but needs to know the number of elements in list.
If list would be a LinkedList or another class implementing IList, the Reverse method could be done in an optimized way.
You can use an extension method like this:
public static IEnumerable<T> TakeFirstAndLast<T>(this IEnumerable<T> source, int count)
{
var first = new List<T>();
var last = new LinkedList<T>();
foreach (var item in source)
{
if (first.Count < count)
first.Add(item);
if (last.Count >= count)
last.RemoveFirst();
last.AddLast(item);
}
return first.Concat(last);
}
(I'm using a LinkedList<T> for last because it can remove items in O(1))
You can use it like this:
.Orderby(i => i.Value1)
.TakeFirstAndLast(100)
.Orderby(i => i.Value2);
Note that it doesn't handle the case where there are less then 200 items: if it's the case, you will get duplicates. You can remove them using Distinct if necessary.
Take the top 100 and bottom 100 separately and union them:
var tempresults = yourenumerable.OrderBy(i => i.Value1);
var results = tempresults.Take(100);
results = results.Union(tempresults.Skip(tempresults.Count() - 100).Take(100))
.OrderBy(i => i.Value2);
You can do it with in one statement also using this .Where overload, if you have the number of elements available:
var elements = ...
var count = elements.Length; // or .Count for list
var result = elements
.OrderBy(i => i.Value1)
.Where((v, i) => i < 100 || i >= count - 100)
.OrderBy(i => i.Value2)
.ToArray(); // evaluate
Here's how it works:
| first 100 elements | middle elements | last 100 elements |
i < 100 i < count - 100 i >= count - 100
You can write your own extension method like Take(), Skip() and other methods from Enumerable class. It will take the numbers of elements and the total length in list as input. Then it will return first and last N elements from the sequence.
var result = yourList.OrderBy(x => x.Value1)
.GetLastAndFirst(100, yourList.Length)
.OrderBy(x => x.Value2)
.ToList();
Here is the extension method:
public static class SOExtensions
{
public static IEnumerable<T> GetLastAndFirst<T>(
this IEnumerable<T> seq, int number, int totalLength
)
{
if (totalLength < number*2)
throw new Exception("List length must be >= (number * 2)");
using (var en = seq.GetEnumerator())
{
int i = 0;
while (en.MoveNext())
{
i++;
if (i <= number || i >= totalLength - number)
yield return en.Current;
}
}
}
}
Related
So suppose we have a parking(represented as a dictionary<int,bool> :
Every parking lot has its id and a boolean(free,filled).
This way:
Dictionary<int,bool> parking..
parking[0]= true // means that the first parking lot is free
My question is i want to get the all sublist of consecutive elements that matchs in a condition : parking-lot is free.
First i can get elements that fits in this condition easy:
parking.Where(X => X.Value).Select(x => x.Key).ToList();
But then using linq operations i dont know how to get the first generated list that matchs in.
Can i do this without thousand of foreach-while loops checking iterating one by one, is there a easier way with linq?
This method gets a list of consecutive free parking lots
data:
0-free,
1-free,
2-filled ,
3-free
The results will be two lists:
First One will contain => 0 ,1
Second One will contain=> 3
These are the list of consecutive of parking lots that are free.
public List<List<int>> ConsecutiveParkingLotFree(int numberOfConsecutive){}
You can always write your own helper function to do things like this. For example
public static IEnumerable<List<T>> GroupSequential<T, TKey>(
this IEnumerable<T> self,
Func<T, bool> condition)
{
var list = new List<T>();
using var enumerator = self.GetEnumerator();
if (enumerator.MoveNext())
{
var current = enumerator.Current;
var oldValue = condition(current);
if (oldValue)
{
list.Add(current);
}
while (enumerator.MoveNext())
{
current = enumerator.Current;
var newValue = condition(current);
if (newValue)
{
list.Add(current);
}
else if (oldValue)
{
yield return list;
list = new List<T>();
}
oldValue = newValue;
}
if (list.Count > 0)
{
yield return list;
}
}
}
This will put all the items with a true-value in a list. When a true->false transition is encountered the list is returned and recreated. I would expect that there are more compact ways to write functions like this, but it should do the job.
You can apply GroupWhile solution here.
parking.Where(X => X.Value)
.Select(x => x.Key)
.GroupWhile((x, y) => y - x == 1)
.ToList()
For example i have a collection like this
var c1 = new Collection<int>{0,0,2,2,2,3,3,4,4,4,4,5,5,6,6,7};
I would like to get result like this
(6,5,4)
You can do:
c1.Distinct()
.OrderByDescending(x => x)
.Skip(1)
.Take(3)
.ToList()
First remove all the duplicates, then sort despondingly. Skip(1) so that the max element is removed. Finally you can take 3 elements from the rest.
In the old days, before LINQ, we might have done this on a sorted collection like you have:
var maxes = new int[4];
var idx = 0;
var max = 0;
foreach(var c in c1)
if(c > max)
max = maxes[(idx++)%4] = c;
At the end of this you'll have an array with 4 max values - the 3 you want, and the one you don't (which is in (idx - 1) % 4). I don't know if I'd use it now, but it's more efficient than a "distinct, then sort, then skip then take" approach as it does its work in a single pass
I need to return the 3 latest elements in a collection... If use Linq e.g. .OrderByDescending(a => a.Year).Take(3) then this is fine as long as the collection contains at least 3 elements. What I want is for it always to return 3, so for example if there are only 2 items then the last item would be a blank/initialised element (ideally where I could configure what was returned)
Is this possible?
You can concatenate the sequence with another (lazily created) sequence of 3 elements:
var result = query
.OrderByDescending(a => a.Year)
.Concat(Enumerable.Range(0, 3).Select(_ => new ResultElement()))
.Take(3);
Or perhaps:
var result = query
.OrderByDescending(a => a.Year)
.Concat(Enumerable.Repeat(new ResultElement(), 3))
.Take(3);
(The latter will end up with duplicate references and will always create an empty element, so I'd probably recommend the former... but it depends on the context. You might want to use Enumerable.Repeat(null, 3) and handle null elements instead.)
You could write your own extension method:
public static IEnumerable<T> TakeAndCreate<T>(this IEnumerable<T> input, int amount, Func<T> defaultElement)
{
int counter = 0;
foreach(T element in input.Take(amount))
{
yield return element;
counter++;
}
for(int i = 0; i < amount - counter; i++)
{
yield return defaultElement.Invoke();
}
}
Usage is
var result = input.OrderByDescending(a => a.Year).TakeAndCreate(3, () => new ResultElement());
One advantage of this solution is that it will create new elements only if they are acutally needed, which might be good for performance if you have a lot of elements to be created or their creation is not trivial.
Online demo: https://dotnetfiddle.net/HHexGd
I'm using the .net sort method for generic lists. For example:
MyList.Sort();
Is it possible to use .net to sort a list with min and max values in mind? For example, if I wanted to sort a list and simultaneously only show list items less than 'value1' but greater than 'value2'.
Something like this:
MyList.Sort.Where(MyList > 50 & MyList < 30);
You could use Linq extensions.
// top of code file
using System.Linq;
// code
var filteredSortedList = MyList.Where(x => x > 30 && x < 50).OrderBy(x => x);
// filters values between 30 and 50 not inclusive
// sorts the results
Note that the original list MyList is not changed, instead the result is assigned to a new variable. Also the actions are deferred until you do something on the result like calling ToList or enumerating it.
I think your example is going to yield zero results as nothing is greater than 50 and less than 30. But assuming that was just a typo, is this what you are after?
List<int> s = new List<int>();
s.Where(r => r > 50 && r < 30).OrderBy(r => r);
Hi you can something like below
var res = MyList.Where(val => val > minVal && val < maxVal).OrderBy(num=>num).ToList();
You don't need to sort your list, orderby will do it for you
After you sorted the list, you can filter it using LINQ. In your case, SkipWhile and TakeWhile seem useful:
List<int> MyList = ....
// sort
MyList.Sort();
// filter:
var result = MyList.SkipWhile(i => i < 30).TakeWhile(i => i < 50).ToList();
I'm not sure (meaning I did not test), but I guess most of the times it's faster to filter first and sort afterwards, so fewer elements have to be sorted. You can use LINQ's Where and OrderBy:
List<int> MyList = ....
var result = MyList.Where(i => i > 30 && i < 50).OrderBy(i => i).ToList();
Note that I assumed your condidtion was a typo, there is no number i < 30 && i > 50.
You can use linq query for that
MyList = MyList
.Where(x => x > 50 && x < 30)
.OrderBy(x => x)
.ToList();
Note that x > 50 && x < 30 will not match any item. So you probably want to write
MyList.Where(x => x > 30 && x < 50).OrderBy(x => x);
this will remove any item below 30 and greater than 50
This version should work on most version of .net platform.
System.Collections.Generic.List<int> items = new List<int>();
Predicate<int> FilterMethod = delegate (int item) { return item < 50 && item > 30; };
items.FindAll(FilterMethod).Sort();
Using a LINQ query (with C#) how would I go about do something like this (pseudocode)?
I'd look to do something like this is in places where, for example, I might generate 1000's of lists of 100's of random (bounded) integers, where I want to track the smallest of them as they're generated.
Best <- null value
Foreach N in Iterations
NewList <- List of 100 randomly generated numbers
If Best is null
Best <- NewList
If Sum(NewList) < Sum(Best)
Best <- NewList
Select Best
I've tried all sorts of things, but I can't really get it working. This isn't for any kind of project or work, just for my own curiosity!
Example of what I was thinking:
let R = new Random()
let Best = Enumerable.Range(0, 100).Select(S => R.Next(-100, 100)).ToArray()
//Where this from clause is acting like a for loop
from N in Iterations
let NewList = Enumerable.Range(0, 100).Select(S => R.Next(-100, 100))
Best = (NewList.Sum() < Best.Sum())? NewList : Best;
select Best
I believe you are looking for fold (aka "reduce") which is known as Aggregate in LINQ.
(IEnumerable.Min/Max are special-cases, but can be written in terms of fold/Aggregate.)
int Max (IEnumerable<int> x) {
return x.Aggregate(int.MinValue, (prev, cur) => prev > cur ? prev : cur);
}
Max(new int[] { 1, 42, 2, 3 }); // 42
Happy coding.
Looks like you're just selecting the minimum value.
var minimum = collection.Min( c => c );
You are effectively finding the minimum value in the collection, if it exists:
int? best = null;
if (collection != null && collection.Length > 0) best = collection.Min();