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();
Related
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 iterate through all items in a subtree.
Therefore, I want to put all childItems of an predecessor into an array. I tried:
var successors =
TabWebContext.MenuItemSet.Where(m => m.PredecessorId == parentId).ToArray();
I also tried it without .ToArray() and with .ToList().
I have the following loop: while (successors.Count()>0){...} (or rather .Length>0).
The condition is never true.
What do you think am I making wrong? I know there are elements (if I do .FirstOrDefault(), there is at least one element found.
Why do you feel that it needs to be in an array to iterate through it? You could iterate the list like this:
foreach (var successor in TabWebContext.MenuItemSet.Where(m => m.PredecessorId == parentId))
{
// do stuff with 'successor'
}
Just to show an example of how select all from data source works with where clause.
int[] numbers = { 2, 34, 23, 11 }; //data source >> can be EF or ADO.NET
var result = numbers.Where(n => n <= 20).ToList(); // select all with filteration
foreach(int i in result ) //just to loop and
{
Console.WriteLine(i);
}
output :
22
11
so there is no issue can be seen in var successors =
TabWebContext.MenuItemSet.Where(m => m.PredecessorId == parentId).ToArray();
if you do facing an error then show that in your question.
I have a list which contains ids and values and I need to remove ids duplication. I am looking for an efficiently way preferable in LINQ, instead of my loop and if condition. Thank you for any help and advise.
var list = new List<Tuple<int, double>>();
Current values:
1, 3.6
1, 3.8
2, 5.6
3, 8.1
Wished values:
1, 3.6
2, 5.6
3, 8.1
for (int i = 0; i < list.Count - 1; i++)
{
if (list[i].Item1 == list[i + 1].Item1)
list.RemoveAt(i+ 1);
}
If Id and Values are same as the other one. It will remove that item from list.
distinctList = list.Distinct().ToList();
If you are okay with converting the Tuple to Dictionary:
Try This: If Only Id's are duplicate removes that item from list. It will not consider the value duplication.
var distinctDictionary = list.GroupBy(id => id.Item1)
.Select(group => group.First())
.ToDictionary(id => id.Item1, val => val.Item2);
Look at the Screen shots:
Solution 1:
Solution 2:
Given your opinion that LINQ is generally more readable / maintainable and is generally equitable to efficiency, I present the following solution, which uses LINQ, and (IMHO compared to others presented so far) is more efficient in execution as well -
list = list.Where((entry, i) => i == 0 || entry.Item1 != list[i - 1].Item1).ToList();
Why are you a List with tuples? With the requested functionality I would use a Dictionary so you won't have duplicates.
DistinctByKey = list.Select(x => x.Keys).Distinct();
DistinctByValue= DistinctByKey.Select(x => x.Values).Distinct();
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;
}
}
}
}
Right now, I have 2 lists(_pumpOneSpm and _pumpTwoSpm) that are the result of a database query. Now, I've been using LINQ to populate lists for me to work with for calculations. Something similar to this:
var spmOne = _pumpOneSpm.Where((t, i) => _date[i].Equals(date) &&
DateTime.Compare(_time[i], DateTime.Parse(start)) > 0 &&
DateTime.Compare(_time[i], DateTime.Parse(end)) < 0).ToList();
This has been fine, because I've been pulling data from one list to add specific data into another list. But now, I need pull data from two lists to add to one list. I'm curious if there is a way to do this with LINQ, or if I should just iterate through with a for loop? Here's what I've got with the for loop:
for (var i = 0; i < _pumpOneSpm.Count; i++)
{
if (_date[i].Equals(date))
{
if (DateTime.Compare(_time[i], DateTime.Parse(start)) > 0)
{
if (DateTime.Compare(_time[i], DateTime.Parse(end)) < 0)
{
spmOne.Add(_pumpOneSpm[i]);
spmOne.Add(_pumpTwoSpm[i]);
}
}
}
}
This works, and does what I want. But I'm just trying to make it a bit more efficient and faster. I could use two lines similar to the first code block for each list, but that means I'm iterating through twice. So, to reiterate my question, is it possible to use a LINQ command to pull data from two lists at the same time to populate another list, or should I stick with my for loop? Thanks for any and all help.
EDIT
I failed to mention, that the purpose of the function utilizing these steps is to fine the MAX of _pumpOneSpm & _pumpTwoSpm. Not the max of each, but the max between them. Thus initially I was adding them into a single list and just calling spmOne.Max(). But with two LINQ queries like above, I'm running an if statement to compare the two maxes, and then return the greater of the two. So, technically they don't NEED to be in one list, I just thought it'd be easier to handle if they were.
So the simplest way to do this would be something like this:
var resultList = list1.Concat(list2).ToList();
Note that this will be slightly different than your for loop (the order of the items won't be the same). It will have all items from one list followed by all items from another, rather than having the first of the first, the first of the second, etc. If that's important you could still do it with LINQ, but it would take a few extra method calls.
You've mentioned performance; it's worth noting that you aren't going to get significantly better, performance wise, than your for loop. You could get code that's shorter; code that's more readable, and that will perform about the same. There simply isn't room to improve very much more though in terms of runtime speed.
It's also worth noting that you can combine the two method; it's not even a bad idea. You can use LINQ to filter the two input sequences (using Where) and then foreach over the results to add them to the list (or use AddRange).
var query1 = _pumpOneSpm.Where((t, i) => _date[i].Equals(date) &&
DateTime.Compare(_time[i], DateTime.Parse(start)) > 0 &&
DateTime.Compare(_time[i], DateTime.Parse(end)) < 0);
var query2 = ...
List<T> results = new List<T>(query1);
results.AddRange(query2);
So, to reiterate my question, is it possible to use a LINQ command to
pull data from two lists at the same time to populate another list, or
should I stick with my for loop?
Merge the lists:
var mergedList = list1.Union(list2).ToList();
Optionally if you want, then filter:
var filtered = mergedList.Where( p => ... filter ... );
And for the Max:
var max = filtered.Max();
NOTE:
As OP says there's no need from order not for not-duplicates so Union will be OK.
You can use the SelectMany method
http://msdn.microsoft.com/en-us/library/bb548748.aspx
You can achieve similar functionality to the for loop with Enumerable.Range. SelectMany then allows you to add multiple items each iteration and then flatten the resulting sequence.
spmOne = Enumerable
.Range(0, _pumpOneSpm.Count)
.Where(index => _date[index].Equals(date) &&
DateTime.Compare(_time[index], DateTime.Parse(start)) > 0 &&
DateTime.Compare(_time[index], DateTime.Parse(end)) < 0)
.SelectMany(index => new [] { _pumpOneSpm[index], _pumpTwoSpm[index] })
.ToList();
Here is the query syntax version:
spmOne = (from index in Enumerable.Range(0, _pumpOneSpm.Count)
where _date[index].Equals(date) &&
DateTime.Compare(_time[index], DateTime.Parse(start)) > 0 &&
DateTime.Compare(_time[index], DateTime.Parse(end)) < 0
from pumpSpm in new [] { _pumpOneSpm[index], _pumpTwoSpm[index] }
select pumpSpm).ToList();
If you want to avoid using indexes you can Zip everything together.
spmOne = _pumpOneSpm
.Zip(_pumpTwoSpm, (pump1, pump2) => new { pump1, pump2 } )
.Zip(_date, (x, pumpDate) => new { x.pump1, x.pump2, date = pumpDate })
.Zip(_time, (x, time) => new { x.pump1, x.pump2, x.date, time })
.Where(x => x.date.Equals(date) &&
DateTime.Compare(x.time, DateTime.Parse(start)) > 0 &&
DateTime.Compare(x.time, DateTime.Parse(end)) < 0)
.SelectMany(x => new [] { x.pump1, x.pump2 })
.ToList();
There is one small difference in functionality: if any of the sequences are shorter than _pumpOneSpm, the resulting sequence will be the length of the shortest sequence, and no exception will be thrown about an index being out of range.