suppose I have a list that comes from a database like this:
List<Item> list =
{
new Item
{
TypeID = 2,
Count = 5
},
new Item
{
TypeID = 2,
Count = 7
},
new Item
{
TypeID = 5,
Count = 2
}
};
I would like to sum up all elements with the same TypeID so that I have a final result list with two elements only:
List<Item> list =
{
new Item
{
TypeID = 2,
Count = 12
},
new Item
{
TypeID = 5,
Count = 2
}
};
How can I achive this using LINQ?
Cheers
Simon
list.GroupBy(x=>x.TypeID)
.Select(x=>new Item(){TypeID=x.Key,Count=x.Sum(y=>y.Count) })
.ToList();
You can use GroupBy to group by TypeID first and then do Sum on each group:
var result = list.GroupBy(x => x.TypeID)
.Select(g => new Item() {
TypeId = g.Key,
Count = g.Sum(x => x.Count)
})
.ToList();
Linq extension method Union.
var mergedList = list1.Union(list2).ToList();
Related
I have a List<Returns> as below
Item ManCode ShippedQty
ITM01 A10 1
ITM02 A11 2
ITM01 A10 3
Here the first and 3 rd rows have same values for Item and Mancode. in this case, the both the items should be merged and shippedQty values must be added to 4.
There should be only 2 items in the final list
You can try to use linq GroupBy with Sum function.
list.GroupBy(x=> new{x.Item ,x.ManCode}).Select(
x=> new {
Item = x.key.Item,
ManCode = x.key.ManCode,
ShippedQty = x.Sum(y=>y.ShippedQty)
}
);
var result = items.GroupBy(x => new { x.Item, x.ManCode })
.Select(x => new Returns
{
Item = x.Key.Item,
ManCode = x.Key.ManCode,
ShippedQty = x.Sum(y => y.ShippedQty)
}).ToList();
Just try:
List<Returns> list = new List<Returns>(){ /*populate list here*/ };
list = list
.GroupBy(i => new {i.Item, i.ManCode})
.Select(g => new {g.Key.Item, g.Key.ManCode, g.Sum(i => i.ShippedQty)} );
You could use Linq to group the objects on Item and ManCode and sum the quantity like this:
var data = new List<Element>
{
new Element{ Item ="ITM01", ManCode = "A10", ShippedQty = 1},
new Element{ Item ="ITM02", ManCode = "A11", ShippedQty = 2},
new Element{ Item ="ITM01", ManCode = "A10", ShippedQty = 3},
};
var datagrp = (from q in data
group q by new { q.Item, q.ManCode } into p
select new Element
{ Item = p.Key.Item, ManCode = p.Key.ManCode,
ShippedQty = p.Sum(s => s.ShippedQty)}).ToList();
I have a linq query like followin:
var _transactionsList = TransactionsData
.GroupBy(x => new { x.ItemID, x.Title, x.GalleryURL })
.Select(pr => new TransactionsTabResults
{
ItemID = pr.Key.ItemID,
Title = pr.Key.Title,
GalleryURL = pr.Key.GalleryURL,
ItemPrice = pr.OrderByDescending(a => a.TransactionDate).First().ItemPrice,
TotalSoldItems = pr.Count(),
TotalRevenuePerItem = pr.Sum(y => y.ItemPrice),
AveragePrice = pr.Average(y => y.ItemPrice),
}).ToList();
I'm trying to fetch the total sold items value by grouping it by like this:
ItemID Sales ItemName
1 1 Item1
1 3 Item1
1 5 Item1
1 6 Item1
2 2 Item2
2 2 Item2
2 2 Item2
2 2 Item2
The desired output would be:
ItemID Sales ItemName
1 15 Item1
2 8 Item2
The query above that I wrote gives me wrong values for total sales by saying:
TotalSoldItems = pr.Count(),
How can I count, or sum all the sales of one Item which has unique ID(this is what I'm grouping by)...
What am I doing wrong??
You are using GroubBy wrong way. You create new unique object every time. So your .GroupBy(x => new { x.ItemID, x.Title, x.GalleryURL }) and .Select(x => new { Key = new { x.ItemID, x.Title, x.GalleryURL}, Value =x }) means the same
If you need unique Id then group by Id only
TransactionsData
.GroupBy(x => x.ItemID)
.Select(pr => new TransactionsTabResults
{
ItemID = pr.Key,
Title = pr.First().Title,
GalleryURL = pr.First().GalleryURL,
ItemPrice = pr.OrderByDescending(a => a.TransactionDate).First().ItemPrice,
TotalSoldItems = pr.Count(),
TotalRevenuePerItem = pr.Sum(y => y.ItemPrice),
AveragePrice = pr.Average(y => y.ItemPrice),
}).ToList();
Advice
Optimize your LINQ. You are iterating through collections many times. This is suggested code:
TransactionsData
.GroupBy(x => x.ItemID)
.Select(pr =>
{
var items = x.pr.ToArray;
var sum = items.Sum(y => y.ItemPrice);
return new TransactionsTabResults
{
ItemID = pr.Key,
Title = items[0].Title,
GalleryURL = items[0].GalleryURL,
ItemPrice = pr.Aggregate((max, cur)=>max.TransactionDate<cur.TransactionDate?cur:max).ItemPrice,
TotalSoldItems = items.Length,
TotalRevenuePerItem = sum,
AveragePrice = sum/items.Length,
};
}).ToList();
To illustrate my problem I have created this simple snippet. I have a class Item
public class Item
{
public int GroupID { get; set; }
public int StrategyID { get; set; }
public List<Item> SeedData()
{
return new List<Item>
{
new Item {GroupID = 1, StrategyID = 1 },
new Item {GroupID = 2, StrategyID = 1 },
new Item {GroupID = 3, StrategyID = 2 },
new Item {GroupID = 4, StrategyID = 2 },
new Item {GroupID = 5, StrategyID = 3 },
new Item {GroupID = 1, StrategyID = 3 },
};
}
}
And what I want to check is that this SeedData method is not returning any duplicated GroupID/StrategyID pairs.
So in my Main method I have this:
Item item = new Item();
var data = item.SeedData();
var groupByStrategyIdData = data.GroupBy(g => g.StrategyID).Select(v => v.Select(gr => gr.GroupID)).ToList();
for (var i = 0; i < groupByStrategyIdData.Count; i++)
{
for (var j = i + 1; j < groupByStrategyIdData.Count; j++)
{
Console.WriteLine(groupByStrategyIdData[i].Intersect(groupByStrategyIdData[j]).Any());
}
}
which is working fine but one of the problems is that I have lost the StrategyID so in my real-case scenario I won't be able to say for which StrategyID/GroupID pair I have duplication so I was wondering is it possible to cut-off the LINQ to here:
var groupByStrategyIdData = data.GroupBy(g => g.StrategyID)
and somehow perform the check on this result?
One of the very easy ways would be to do grouping using some identity for your Item. You can override Equals/GetHashCode for your Item or instead write something like:
Item item = new Item();
var data = item.SeedData();
var duplicates = data.GroupBy(x => string.Format("{0}-{1}", x.GroupID, x.StrategyID))
.Where(group => group.Count() > 1)
.Select(group => group.Key)
.ToList();
Please note, that using a string for identity inside of group by is probably not the best way to do grouping.
As of your question about "cutting" the query, you should also be able to do the following:
var groupQuery = data.GroupBy(g => g.StrategyID);
var groupList = groupQuery.Select(grp => grp.ToList()).ToList();
var groupByStrategyIdData = groupQuery.Select(v => v.Select(gr => gr.GroupID)).ToList();
You may be able to do it another way, as follows:
// Check for duplicates
if (data != null)
{
var grp =
data.GroupBy(
g =>
new
{
g.GroupID,
g.StrategyID
},
(key, group) => new
{
GroupID = key.GroupID,
StrategyId = key.StrategyID,
Count = group.Count()
});
if (grp.Any(c => c.Count > 1))
{
Console.WriteLine("Duplicate exists");
// inside the grp object, you can find which GroupID/StrategyID combo have a count > 1
}
}
I want to compare the element of a list of object ,delete the repeated Item and increment the number of the quantity of that Item (C# code ), I don't know if I should use LinQ,For or foreach statement : I have a list of OrderItem I want to delete the OrderItem that have the same FK_ArtikelId and increment the Qantity of the OrderItem . Exp:
for (int i=1 ; i < lot.Count ; i ++)
{
for (j = i + 1; j <= lot.Count; j++)
{
if (lot[i].FK_ArticleId.Equals(lot[j].FK_ArticleId))
{
lot[i].Quantity += lot[j].Quantity;
lot.Remove(lot[j]);
}
}
}
You have to use the GroupBy linq method and process the resulting groups: given the class
public class Article
{
public int FK_ArticleId { get; set; }
public int Quantity { get; set; }
}
and the following list:
var list = new List<Article>()
{
new Article() {FK_ArticleId = 1, Quantity = 10}
, new Article() {FK_ArticleId = 1, Quantity = 10}
, new Article() {FK_ArticleId = 1, Quantity = 10}
, new Article() {FK_ArticleId = 2, Quantity = 100}
, new Article() {FK_ArticleId = 2, Quantity = 100}
, new Article() {FK_ArticleId = 3, Quantity = 1000}
};
The following linq query returns what you need:
list.GroupBy(a => a.FK_ArticleId)
.Select(g => new Article() {FK_ArticleId = g.Key, Quantity = g.Sum(a => a.Quantity)});
// article id 1, quantity 30
// article id 2, quantity 200
// article id 3, quantity 1000
If you don't want to create a new article, you can take the first of the resulting group and set its Quantity to the correct value:
var results = list.GroupBy(a => a.FK_ArticleId)
.Select(g =>
{
var firstArticleOfGroup = g.First();
firstArticleOfGroup.Quantity = g.Sum(a => a.Quantity);
return firstArticleOfGroup;
});
I didn't test but this should give you an idea of the power of linq...
var stuff = lot
.GroupBy(p => p.FK_ArticleId)
.Select(g => g)
.ToList();
This should give you groups of articleIDs whereby you can easily get counts, create new consolidated lists etc.
For starters you can't use foreach because you're modifying the list and the Enumerator will throw an exception. You can do the following with Linq:
var grouped = lot.GroupBy(x => x.FK_ArticleId).ToArray();
foreach(var group in grouped)
{
group.First().Quantity = group.Sum(item => item.Quantity);
}
Now, first item in each group will contain the sum of all the quantities of items with the same FK_ArticleId. Now, to get the results use this:
var results = grouped.Select(g => g.First());
At this point it's purely your decision whether to return the results as a separate collection or insert them into the original list. If you opt for the second approach, don't forget to clear the list first:
list.Clear();
list.AddRange(results);
EDIT
A more elegant solution to accumulating the Quantity property into the first item of each group would be the following:
data.GroupBy(x=>x.FK_ArticleId)
.Select(g => g.Aggregate((acc, item) =>
{
acc.Quantity = item.Quantity;
return acc;
}));
This is what I scrapped in LinqPad:
I have an ICollection of records (userID,itemID,rating) and an IEnumerable items
for a specific userID and each itemID from a set of itemIDs, i need to produce a list of the users rating for the items or 0 if no such record exists. the list should be ordered by the items.
example:
records = [(1,1,2),(1,2,3),(2,3,1)]
items = [3,1]
userID = 1
result = [0,2]
my attempt:
dataset.Where((x) => (x.userID == uID) & items.Contains(x.iID)).Select((x) => x.rating);
it does the job but it doesn't return 0 as default value and it isnt ordered...
i'm new to C# and LINQ, a pointer in the correct direction will be very appreciated.
Thank you.
This does the job:
var records = new int[][] { new int[] { 1, 1, 2 }, new int[] { 1, 2, 3 }, new int[] { 2, 3, 1 } };
var items = new int[] { 3, 1 };
var userId = 1;
var result = items.Select(i =>
{
// When there's a match
if (records.Any(r => r[0] == userId && r[1] == i))
{
// Return all numbers
return records.Where(r => r[0] == userId && r[1] == i).Select(r => r[2]);
}
else
{
// Just return 0
return new int[] { 0 };
}
}).SelectMany(r => r); // flatten the int[][] to int[]
// output
result.ToList().ForEach(i => Console.Write("{0} ", i));
Console.ReadKey(true);
How about:
dataset.Where((x) => (x.userID == uID)).Select((x) => items.Contains(x.iID) ? x.rating : 0)
This does the job. But whether it's maintainable/readable solution is topic for another discussion:
// using your example as pseudo-code input
var records = [(1,1,2),(1,2,3),(2,3,1)];
var items = [3,1];
var userID = 1;
var output = items
.OrderByDescending(i => i)
.GroupJoin(records,
i => i,
r => r.ItemId,
(i, r) => new { ItemId = i, Records = r})
.Select(g => g.Records.FirstOrDefault(r => r.UserId == userId))
.Select(r => r == null ? 0 : r.Rating);
How this query works...
ordering is obvious
the ugly GroupJoin - it joins every element from items with all records that share same ItemId into annonymous type {ItemId, Records}
now we select first record for each entry that matches userId - if none is found, null will be returned (thanks to FirstOrDefault)
last thing we do is check whether we have value (we select Rating) or not - 0
How about this. your question sounds bit like an outer join from SQL, and you can do this with a GroupJoin, SelectMany:
var record1 = new Record() { userID = 1, itemID = 1, rating = 2 };
var record2 = new Record() { userID = 1, itemID = 2, rating = 3 };
var record3 = new Record() { userID = 2, itemID = 3, rating = 1 };
var records = new List<Record> { record1, record2, record3 };
int userID = 1;
var items = new List<int> { 3, 1 };
var results = items
.GroupJoin( records.Where(r => r.userID == userID), item => item, record => record.itemID, (item, record) => new { item, ratings = record.Select(r => r.rating) } )
.OrderBy( itemRating => itemRating.item)
.SelectMany( itemRating => itemRating.ratings.DefaultIfEmpty(), (itemRating, rating) => rating);
To explain what is going on
For each item GroupJoin gets the list of rating (or empty list if no rating) for the specified user
OrderBy is obvious
SelectMany flattens the ratings lists, providing a zero if the ratings list is empty (by DefaultIfEmpty)
Hope this makes sense.
Be aware, if there is more than one rating for an item by a user, they will all appear in the final list.