LINQ: Efficient way to get sum of all child items - c#

I have the following for each loop to get sum of all child objects. Is there a better way using LINQ?
Purchase p1 = db.Purchases.FirstOrDefault(p => p.PurchaseId == 1);
int total = 0;
foreach (SellingItem item in p1.SellingItems)
{
total = total + Convert.ToInt32(item.Price);
}
REFERENCE:
How to get the sum of the volume of highest and lowest priced items in linq
Using the ALL operator in linq to filter child items of EntitySet

Sounds like you just want:
// Any reason for FirstOrDefault rather than SingleOrDefault?
var purchase = db.Purchases.FirstOrDefault(p => p.PurchaseId == 1);
if (purchase != null)
{
var total = purchase.SellingItems
.Sum(x => Convert.ToInt32(x.Price));
...
}
// TODO: Work out what to do if there aren't any such purchases
Why do you need the conversion of the price though? What type is Price, and why isn't it already the right type? (And does that really want to be int rather than decimal?)

p1.SellingItems.Sum(p => p.Price)

Try using the Linq Sum method:
Purchase p1 = db.Purchases.FirstOrDefault(p => p.PurchaseId == 1);
int total = p1.SellingItems.Sum(item => Convert.ToInt32(item.Price));
It is not more efficient, in that it will not be any faster. But it is more concise.

Related

Get list of objects whose field has maximum value

Suppose I have this class
public class Person {
public string name;
public int age;
//...
}
Suppose I have an array of Person:
Person[] personArray;
How can I get the list of Person with the biggest age within personArray using Linq?
I'm trying this but I wish there was a one-liner to perform this task:
public List<Person> GetBiggestAgeList(){
var sortedPeople = personArray.OrderByDescending(person => person.age).ToList();
int maxAge = sortedPeople[0].age;
List<Person> answer = new List<Person>();
for(int i = 0; i < sortedPeople.Count; ++i){
if(sortedPeople[i].age == maxAge) answer.Add(sortedPeople[i]);
else break;
}
return answer;
}
One option would be
var opa = personArray.OrderByDescending(x=>x.age).FirstOrDefault();
to get all opas
var allOpas = personArray.Where(x=>x.age == opa.age);
One liner would be:
var allOpas2 = personArray.OrderByDescending(x=>x.age).GroupBy(x=>x.age).FirstOrDefault().ToList();
Several options to accomplish this:
Option 1
using Linq .Max() documentation
// structured
var max = personArray.Max(inner => inner.Age);
var list = personArray.Where(p => p.Age == max);
// ...or in an one-liner
var list = personArray.Where(p => p.Age == personArray.Max(inner => inner.Age));
Option 2
using Linq .GroupBy() + .FirstOrDefault() documentation
// this will first order your list
// then group by all the ages and take the first group because this is the group of the persons with the highest age.
var list = personArray.OrderByDescending(p => p.Age)
.GroupBy(p => p.Age)
.FirstOrDefault()
.ToList();
Here you can find a working example dotnet fiddle
I would recommand the Option 1 with the .Max() is more efficient and faster than Option 2 as you can see in the dotnet fiddle. To have it really fastest use Option 1 as two liner and resolve the .Max() first and then do the .Where(..).
If you're looking for a simple one-liner and don't mind adding an external dependency, MoreLINQ has an extension method (MaxBy) that will give you what you are looking for. Documentation
var people = MoreLinq.MoreEnumerable.MaxBy(personArray, x => x.Age).ToArray();
Otherwise, the following one-liner will do the job.
var people = personArray.Where(x => x.Age == personArray.Max(x => x.Age)).ToArray();
Another option is to split it into two queries.
var max = personArray.Max(x => x.Age); // Find maximum age
var people = personArray.Where(x => x.Age == max).ToArray(); // Find people with maximum age

Loop to check for duplicate strings

I want to create a loop to check a list of titles for duplicates.
I currently have this:
var productTitles = SeleniumContext.Driver.FindElements(By.XPath(ComparisonTableElements.ProductTitle));
foreach (var x in productTitles)
{
var title = x.Text;
productTitles = SeleniumContext.Driver.FindElements(By.XPath(ComparisonTableElements.ProductTitle));
foreach (var y in productTitles.Skip(productTitles.IndexOf(x) + 1))
{
if (title == y.Text)
{
Assert.Fail("Found duplicate product in the table");
}
}
}
But this is taken the item I skip out of the array for the next loop so item 2 never checks it's the same as item 1, it moves straight to item 3.
I was under the impression that skip just passed over the index you pass in rather than removing it from the list.
You can use GroupBy:
var anyDuplicates = SeleniumContext
.Driver
.FindElements(By.XPath(ComparisonTableElements.ProductTitle))
.GroupBy(p => p.Text, p => p)
.Any(g => g.Count() > 1);
Assert.That(anyDuplicates, Is.False);
or Distinct:
var productTitles = SeleniumContext
.Driver
.FindElements(By.XPath(ComparisonTableElements.ProductTitle))
.Select(p => p.Text)
.ToArray();
var distinctProductTitles = productTitles.Distinct().ToArray();
Assert.AreEqual(productTitles.Length, distinctProductTitles.Length);
Or, if it is enough to find a first duplicate without counting all of them it's better to use a HashSet<T>:
var titles = new HashSet<string>();
foreach (var title in SeleniumContext
.Driver
.FindElements(By.XPath(ComparisonTableElements.ProductTitle))
.Select(p => p.Text))
{
if (!titles.Add(title))
{
Assert.Fail("Found duplicate product in the table");
}
}
All approaches are better in terms of computational complexity (O(n)) than what you propose (O(n2)).
You don't need a loop. Simply use the Where() function to find all same titles, and if there is more than one, then they're duplicates:
var productTitles = SeleniumContext.Driver.FindElements(By.XPath(ComparisonTableElements.ProductTitle));
foreach(var x in productTitles) {
if (productTitles.Where(y => x.Text == y.Text).Count() > 1) {
Assert.Fail("Found duplicate product in the table");
}
}
I would try a slightly different way since you only need to check for duplicates in a one-dimensional array.
You only have to check the previous element with the next element within the array/collection so using Linq to iterate through all of the items seems a bit unnecessary.
Here's a piece of code to better understand:
var productTitles = SeleniumContext.Driver.FindElements(By.XPath(ComparisonTableElements.ProductTitle))
for ( int i = 0; i < productionTitles.Length; i++ )
{
var currentObject = productionTitles[i];
for ( int j = i + 1; j < productionTitles.Length; j++ )
{
if ( currentObject.Title == productionTitles[j].Title )
{
// here's your duplicate
}
}
}
Since you've checked that item at index 0 is not the same as item placed at index 3 there's no need to check that again when you're at index 3. The items will remain the same.
The Skip(IEnumerable, n) method returns an IEnumerable that doesn't "contain" the n first element of the IEnumerable it's called on.
Also I don't know what sort of behaviour could arise from this, but I wouldn't assign a new IEnumerable to the variable over which the foreach is being executed.
Here's another possible solution with LINQ:
int i = 0;
foreach (var x in productTitles)
{
var possibleDuplicate = productTitles.Skip(i++).Find((y) => y.title == x.title);
//if possibleDuplicate is not default value of type
//do stuff here
}
This goes without saying, but the best solution for you will depend on what you are trying to do. Also, I think the Skip method call is more trouble than it's worth, as I'm pretty sure it will most certainly make the search less eficient.

Assigning a 'day counter' for objects with DateTime?

I have a list of Order objects with a property OrderDate of type DateTime and I am trying to assign a 'DayCounter' for these objects that represents what order of the day it was, for example one day, I have 5 orders so every order gets a counter from 1 up to 5.
Here is what I tried:
orders.GroupBy(order => order.OrderDate.DayOfYear + order.OrderDate.Year)
.SelectMany(group =>
{
var count = 1;
group.Select(order =>
{
order.DayCounter = count;
count++;
return order;
});
return group;
});
the Order objects I get from this code all have DayCounter of 0
Any help would be appreciated
LINQ is not for modifying data, it is for selecting and projecting data. Your Select never gets run because Select is a lazy method and you never iterate over it. Use a normal foreach instead.
var groups = orders.GroupBy(order => order.OrderDate.Date);
foreach (var grouping in groups)
{
int orderCount = 1;
foreach (var order in grouping)
{
order.DayCounter = orderCount;
orderCount++;
}
}
I also changed your grouping key to be a more reliable seperator OrderDate.Date, your old method would consider days that are a year minus one day apart to be the same day.
Try using the overload of .Select which takes the index as a second parameter.
orders.GroupBy(order => order.OrderDate.DayOfYear + order.OrderDate.Year)
.SelectMany(group =>
{
group.Select((order,idx) =>
{
order.DayCounter = idx + 1;
return order;
});
return group;
});

Query Nested Dictionary

I was curious if anyone had a good way to solving this problem efficiently. I currently have the following object.
Dictionary<int, Dictionary<double, CustomStruct>>
struct CustomStruct
{
double value1;
double value2;
...
}
Given that I know the 'int' I want to access, I need to know how to return the 'double key' for the dictionary that has the lowest sum of (value1 + value2). Any help would be greatly appreciated. I was trying to use Linq, but any method would be appreciated.
var result = dict[someInt].MinBy(kvp => kvp.Value.value1 + kvp.Value.value2).Key;
using the MinBy Extension Method from the awesome MoreLINQ project.
Using just plain LINQ:
Dictionary<int, Dictionary<double, CustomStruct>> dict = ...;
int id = ...;
var minimum =
(from kvp in dict[id]
// group the keys (double) by their sums
group kvp.Key by kvp.Value.value1 + kvp.Value.value2 into g
orderby g.Key // sort group keys (sums) in ascending order
select g.First()) // select the first key (double) in the group
.First(); // return first key in the sorted collection of keys
Whenever you want to get the minimum or maximum item using plain LINQ, you usually have to do it using ith a combination of GroupBy(), OrderBy() and First()/Last() to get it.
A Dictionary<TKey,TValue> is also a sequence of KeyValuePair<TKey,TValue>. You can select the KeyValuePair with the least sum of values and and get its key.
Using pure LINQ to Objects:
dict[someInt].OrderBy(item => item.Value.value1 + item.Value.value2)
.FirstOrDefault()
.Select(item => item.Key);
Here is the non LINQ way. It is not shorter than its LINQ counterparts but it is much more efficient because it does no sorting like most LINQ solutions which may turn out expensive if the collection is large.
The MinBy solution from dtb is a good one but it requires an external library. I do like LINQ a lot but sometimes you should remind yourself that a foreach loop with a few local variables is not archaic or an error.
CustomStruct Min(Dictionary<double, CustomStruct> input)
{
CustomStruct lret = default(CustomStruct);
double lastSum = double.MaxValue;
foreach (var kvp in input)
{
var other = kvp.Value;
var newSum = other.value1 + other.value2;
if (newSum < lastSum)
{
lastSum = newSum;
lret = other;
}
}
return lret;
}
If you want to use the LINQ method without using an extern library you can create your own MinBy like this one:
public static class Extensions
{
public static T MinBy<T>(this IEnumerable<T> coll, Func<T,double> criteria)
{
T lret = default(T);
double last = double.MaxValue;
foreach (var v in coll)
{
var newLast = criteria(v);
if (newLast < last)
{
last = newLast;
lret = v;
}
}
return lret;
}
}
It is not as efficient as the first one but it does the job and is more reusable and composable as the first one. Your solution with Aggregate is innovative but requires recalculation of the sum of the current best match for every item the current best match is compared to because you carry not enough state between the aggregate calls.
Thanks for all the help guys, found out this way too:
dict[int].Aggregate(
(seed, o) =>
{
var v = seed.Value.TotalCut + seed.Value.TotalFill;
var k = o.Value.TotalCut + o.Value.TotalFill;
return v < k ? seed : o;
}).Key;

Searching with Linq

I have a collection of objects, each with an int Frame property. Given an int, I want to find the object in the collection that has the closest Frame.
Here is what I'm doing so far:
public static void Search(int frameNumber)
{
var differences = (from rec in _records
select new { FrameDiff = Math.Abs(rec.Frame - frameNumber), Record = rec }).OrderBy(x => x.FrameDiff);
var closestRecord = differences.FirstOrDefault().Record;
//continue work...
}
This is great and everything, except there are 200,000 items in my collection and I call this method very frequently. Is there a relatively easy, more efficient way to do this?
var closestRecord = _records.MinBy(rec => Math.Abs(rec.Frame - frameNumber));
using MinBy from MoreLINQ.
What you might want to try is to store the frames in a datastructure that's sorted by Frame. Then you can do a binary search when you need to find the closest one to a given frameNumber.
I don't know that I would use LINQ for this, at least not with an orderby.
static Record FindClosestRecord(IEnumerable<Record> records, int number)
{
Record closest = null;
int leastDifference = int.MaxValue;
foreach (Record record in records)
{
int difference = Math.Abs(number - record.Frame);
if (difference == 0)
{
return record; // exact match, return early
}
else if (difference < leastDifference)
{
leastDifference = difference;
closest = record;
}
}
return closest;
}
you can combine your statements into one ala:
var closestRecord = (from rec in _records
select new { FrameDiff = Math.Abs(rec.Frame - frameNumber),
Record = rec }).OrderBy(x => x.FrameDiff).FirstOrDefault().Record;
Maybe you could divide your big itemlist in 5 - 10 smaller lists that are ordered by their Framediff or something ?
this way the search is faster if you know in which list you need to search

Categories