Linq select distinct values with comparison - linq

I have a list of objects. E.g. List<coin> where they contain a string(denom) and int(year).
If the list contains:
"Quarter", 1954
"Quarter", 1990
"Penny", 1925
"Nickel", 1900
"Nickel", 2000
How can I get a resultant list where it contains the unique values with just the most recent year? E.g.:
"Quarter", 1990
"Penny", 1925
"Nickel", 2000

You can do this by grouping by name, then either ordering and taking the first result, or by using something like MaxBy from MoreLINQ:
var query = coins.GroupBy(x => x.Name)
.Select(g => g.MaxBy(x => x.Year));
or
var query = coins.GroupBy(x => x.Name)
.Select(g => g.OrderByDescending(x => x.Year).First());

You can do this using group by like:
var query = from coin in myList
group coin by coin.Name into grouped
select new
{
Name = grouped.Key
Year = grouped.Max(x => x.Year)
};
For another sample like this, check out "max - grouped" in the 101 Linq examples: http://msdn.microsoft.com/en-us/vcsharp/aa336747#maxGrouped

var coins = new Coin[] { ... };
var recentCoins =
from coin in coins
group coin by coin.Denom into g
select new
{
Denom = g.Key,
MostRecentYear = g.Max(c => c.Year)
};

Related

linq avg on grouped counts

Please consider this simple class
public class SEANCE
{
int ID_SEANCE {get;set;}
DateTime SEA_DATE {get;set;}
}
It's for a fitness management app, the SEANCE class is the session table for the members.
Here's my starting query to show the number of the sessions grouped by day Hour
var lst = ctx.SEANCES
.GroupBy(o => o.SEA_DATE.Hour)
.Select(g => new ChartData()
{
DT = new DateTime(2017, 01, 01, g.Key, 0, 0),
VALUE = g.Count()
});
return lst.OrderBy(a => a.DT).ToList();
as you can see, it returns the total number of sessions grouped by hour.
What I want instead is the average number of sessions by day and grouped by hour, and that's where I am a little lost :p
here what i mean
for example at 18, 1500 is corresponding to the total number of sessions at that time.
what i want instead is the average number by day
hope it make sense now :)
ctx.SEANCES.Count() / lst.Count(), but that seems like database source so better to use the result:
var groups = ctx.SEANCES
.GroupBy(s => s.SEA_DATE.Hour)
.OrderBy(g => g.Key)
.Select(g => new ChartData()
{
DT = g.First().SEA_DATE,
VALUE = g.Count()
})
.ToList();
var averageByHour = groups.Average(c => c.VALUE);
var averageByDay = groups.GroupBy(s => s.DT.Day).Average(g => g.Sum(c => c.VALUE));

How to find all rows of items that have a part in common using LINQ?

I need to return all records (items) that has a part (X) so I can use that in a group or .GroupBy afterwards
Using this summary data:
ItemName PartName
1 A
1 B
2 A
3 C
So Item1 has two parts (A,B), etc...
I need a LINQ query that will
- find all items that have part A (i.e items 1 and 2)
- return all rows for all these items
1 A
1 B
2 A
Notice that the end result returned the row (1 B) because Item1 has PartA and so I need to get back all rows for Item1.
I was looking at something like:
let items = from data in summary where data.PartName == A select new { data.ItemName } // to get all the items I need
But then, now that I have that list I need to use it to get all the rows for all items listed, and I can't seem to figure it out ...
Actual Source Code (for reference):
NOTE:
Recipe = ITEM
Ingredient = PART
(I was just trying to make it simpler)
ViewFullRecipeGrouping = (
from data in ViewRecipeSummary
group data by data.RecipeName into recipeGroup
let fullIngredientGroups = recipeGroup.GroupBy(x => x.IngredientName)
select new ViewFullRecipe()
{
RecipeName = recipeGroup.Key,
RecipeIngredients = (
from ingredientGroup in fullIngredientGroups
select new GroupIngredient()
{
IngredientName = ingredientGroup.Key
}
).ToList(),
ViewGroupRecipes = (
from data in ViewRecipeSummary
// this is where I am looking to add the new logic to define something I can then use within the next select statement that has the right data based on the information I got earlier in this query.
let a = ViewRecipeSummary.GroupBy(x => x.RecipeName)
.Where(g => g.Any(x => x.IngredientName == recipeGroup.Key))
.Select(g => new ViewRecipe()
{
RecipeName = g.Key,
IngredientName = g.Select(x => x.IngredientName)
})
select new GroupRecipe()
{
// use the new stuff here
}).ToList(),
}).ToList();
Any help would be much appreciated.
Thanks,
I believe this does what you want:
var data = /* enumerable containing rows in your table */;
var part = "X";
var items = new HashSet<int>(data
.Where(x => x.PartName == part)
.Select(x => x.ItemName));
var query = data.Where(x => items.Contains(x.ItemName));
If I understand your comment at the end, I believe this also does what you want:
var query = data
.GroupBy(x => x.ItemName)
.Where(g => g.Any(x => x.PartName == part))
.Select(g => new
{
ItemName = g.Key,
PartNames = g.Select(x => x.PartName)
});

LINQ Max Date with group by

I have a requirement to sort a list by 1) the number of times a distinct item appears and then 2) if the count of two distinct rows is the same, the most recently used date of that group.
My group by function and sorting by count is working without the date:
(from x in data
group x by new { x.Col1, x.Col2, x.Col3}
into g
let count = g.Count()
select new
{
g.Key.Col1,
g.Key.Col2,
g.Key.Col3,
count
}).OrderByDescending(x => x.count)
However, I have been unable to successfully add the date sort. I was trying to add the date column as an aggregate in the group by expression, but that doesn't work.
(from x in data
group x by new { x.Col1, x.Col2, x.Col3, MaxDate = x.CreatedDateTime.Max()}
into g
let count = g.Count()
select new
{
g.Key.Col1,
g.Key.Col2,
g.Key.Col3,
count,
g.Key.MaxDate
}).OrderByDescending(x => x.count).ThenByDescending(x => x.MaxDate)
I get why it doesn't work, I just can't think of another route to add the secondary sort. Any ideas are appreciated!
This is what you're looking for (I think)
from x in data
group x by new { x.Col1, x.Col2, x.Col3}
into g
let count = g.Count()
select new
{
g.Key.Col1,
g.Key.Col2,
g.Key.Col3,
MaxDate = g.Select(x => x.CreatedDateTime)
.OrderByDescending(d => d).FirstOrDefault(),
count
}).OrderByDescending(x => x.count).ThenByDescending(x => x.MaxDate)
How about:
var ordered = Elev8SnaTowelTables
.GroupBy(x => new { x.Col1, x.Col2, x.Col3 })
.OrderByDescending(g => g.Count())
.ThenByDescending(g => g.Max(x => x.CreatedDateTime));

Aggregate functions in LINQ

I have the following LINQ conditional where clause query that produces a result of weights:
From this, I'd like to take the result set and join on another table, tblPurchases
var result = weights.Join(getsuppliersproducts.tblPurchases,
w => new { w.MemberId, w.MemberName, w.LocationId, w.UnitId },
p => new { p.MemberId, p.MemberName, p.LocationId, p.UnitId },
(w, p) => p);
In this second table, I have two columns I would like to perform an aggreagte function on, a sum on PurchaseQuantity and a count of UnitID.
So in its raw format, tblPurchases would look like so:
MemberID LocationID UnitId SupplierID SupplierStatus Purchases
1 1 ab Sup1 Live 10
1 1 abc Sup1 Live 10
1 1 abcd Sup2 Dead 50
From my results data set, I would like the output to look like so:
MemberID LocationID SupplierID SupplierStatus UnitIdCount Total Purchases
1 1 Sup1 Live 2 50
Also, with these amendments, can I still return this to a List?
How do I implement this using LINQ? I have tried, and failed miserably.
(To those who have seen my previous posts, I'm trying to cover all angles so I can fully understand the concept of what is going on in both SQL and LINQ)
That query will return an IEnumerable where each of the Purchases matches the MemberId, MemberName, LocationId and UnitId in the original Weights query. You can only easily do one aggregate at a time, so
var result = weights.Join(getsuppliersproducts.tblPurchases,
w => new { w.MemberId, w.MemberName, w.LocationId, w.UnitId },
p => new { p.MemberId, p.MemberName, p.LocationId, p.UnitId },
(w, p) => p).ToList();
Int32 count = result.Count();
Double quantity = result.Sum(p => p.PurchaseQuantity);
Is that what you're trying to do?
EDIT, after your reply of I would like to reutrn a list of tblPurchases with two new columns, the sum of Purchase Quantity and count of unit ID.
This gives a flat output:
var query = Weights.GroupJoin(
Purchases,
w => new {w.MemberId, w.LocationId},
p => new {p.MemberId, p.LocationId},
(w,p) => new {w.MemberId, w.LocationId, Count = p.Count(), Sum = p.Sum(x => x.Purchases)} );
Note that at the point we do the (w, p) => new {} that w is a single Weight and p is a list of Purchases matching that weight, so you can still keep all of teh (hierarchical) data:
var query = Weights.GroupJoin(
Purchases,
w => new {w.MemberId, w.LocationId},
p => new {p.MemberId, p.LocationId},
(w,p) => new {w.MemberId, w.LocationId, Count = p.Count(), Sum = p.Sum(x => x.Purchases), Purchases = p} );

Removing values from a returned linq query

HI there I am hoping for some help with a query I have.
I have this query
var group =
from r in CustomerItem
group r by r.StoreItemID into g
select new { StoreItemID = g.Key,
ItemCount = g.Count(),
ItemAmount = Customer.Sum(cr => cr.ItemAmount),
RedeemedAmount = Customer.Sum(x => x.RedeemedAmount)
};
I am returning my results to a list so I can bind it listbox.
I have a property called EntryType which is an int. There are 2 available numbers 1 or 2
Lets say I had 3 items that my query is working with
2 of them had the EntryType = 1 and the 3rd had EntryType2. The first records had a ItemAmount of 55.00 and the 3rd had a ItemAmount of 50.00
How can I group using something simlar to above but minus the ItemAmount of 50.00 from the grouped amount to return 60.00?
Any help would be great!!
It's not really clear what the question is - are you just trying to ignore all items with an entry type of 2? To put it another way, you only want to keep entries with an entry type of 1? If so, just add a where clause:
var group = from r in CustomerItem
where r.EntryType == 1
group r by r.StoreItemID into g
select new {
StoreItemID = g.Key, ItemCount = g.Count(),
ItemAmount = Customer.Sum(cr => cr.ItemAmount),
RedeemedAmount = Customer.Sum(x => x.RedeemedAmount)
};
Change ItemAmount = ... to:
ItemAmount =
g.Where(x => x.EntryType == 1).Sum(cr => cr.ItemAmount) -
g.Where(x => x.EntryType == 2).Sum(cr => cr.ItemAmount),
I changed Customer to g because this seems to be an error, but it's not clear to me from your question what you mean here, so maybe this change is not what you want.
A slightly more concise method is to use test the entry type in the sum and use the ternary operator to choose whether to add the positive or negative value:
ItemAmount = g.Sum(cr => cr.EntryType == 1 ? cr.ItemAmount : -cr.ItemAmount),
This gives the value of 60.00 as you required.

Resources