Grouping duplicate items in a list, and adding their totals - c#

Hello I have a list and I am trying to group duplicates and add their quantity to compare it to a max quantity number. The only problem I'm running into is isolating the duplicates and adding their quantities. I have come to a mental block and just can't figure out the right way to acheive what I am trying. So I was hoping that someone would be able to point me in the right direction and help get me unstuck!
The property that I am checking on for duplicates is the ProductID
double qty = 0;
double totalQty = 0;
bool isQtyValid = true;
List<ShoppingCartDTO> shoppingList = ShoppingCart.Fetch(string.Format("WHERE SessionID='{0}'", Session["ID"]));
foreach (ShoppingCartDTO temp in shoppingList)
{
qty = temp.Quantity;
totalQty += qty;
isQtyValid = getCheckQty(totalQty, temp.ProuductID, temp.CustomerID);
CheckOut.Enabled = isQtyValid;
lblError.Visible = !isQtyValid;
}
If more explaining can be done, I can try to explain better, as well as provide more code if needed. I appreciate anyone's advice and help. Thanks!

If I understood you correctly:
var groupedList = shoppingList.GroupBy( item => item.[the property you want to group by on]);
foreach (var g in groupedList)
{
var sum = g.Sum( i => i.[the property you want to sum]);
}
hope it helps

A.Intersect(B).Count;
would this be able to find out the duplicate item count?
You might also implement IEquatable interface for your ShoppingCartDTO class:
class ShoppingCartDTO : IEquatable<ShoppingCartDTO>
{
}

Old style solution, something like.
List<ShoppingCartDTO> shoppingList = ShoppingCart.Fetch(string.Format("WHERE SessionID='{0}'", Session["ID"]));
Dictionary<String, Double> groups = new Dictionary<String,Double>();
foreach (ShoppingCartDTO temp in shoppingList)
{
String key = String.Format("{0}-{1}", temp.CustomnerID, temp.ProductID);
if (groups.ContainsKey(key))
{
groups[key] += temp.Quantity;
}
else
{
groups.Add(key, temp.Quantity);
}
}

Assuming there's some property that defines your "duplicate" like ProductId:
var results = shoppingList.GroupBy(s => s.ProductID)
.Select(g => new {
ProductID = g.Key,
totalQty = g.Sum(I => i.Quantity)
}
);

You could do something like this:
var groups = shoppingList.GroupBy(e => e.ProductId).ToList();
shoppingList.Clear();
foreach (var group in groups)
{
group.First().Quantity = group.Sum(e => e.Quantity);
shoppingList.Add(group.First());
}
After this runs, shoppingList should contain no duplicates and the Quantity value will have been summed for all duplicates.

Related

Converting two loops to single LINQ query using C#

I searched some links here to change nested loops to single Linq, I tried using those, part of code is not working, I need some expert guidance to fix this,
UPDATE 1:
I guess wasn't clear in my explanation, the loops works fine! as expected, I am getting correct results, but I am doing optimization, instead of using two loops i need the same code to be converted to single linq.
here is the code :
foreach (var ob in all_request_list.Where(x => x.StartDate != x.EndDate)) {
int consq_dates = ob.EndDate.DateDiff(ob.StartDate);
for (int i = 0; i <= consq_dates; i++) {
combined_list.Add(new { ShiftID = ob.ShiftID, SkillID = ob.SkillID, EmployeeID = ob.EmployeeID, AssignDate = ob.StartDate.AddDays(i), ProfileID = ob.ProfileID });
}
}
I have problem adding increment variable i to ob.StartDate.AddDays(i).
any help will be appreciated.
Is this what you're looking for?
var items = from ob in all_request_list
where ob.StartDate != ob.EndDate
let consq_dates = ob.EndDate.DateDiff(ob.StartDate)
from i in Enumerable.Range(0, consq_dates + 1)
select new { ShiftID = ob.ShiftID, SkillID = ob.SkillID, EmployeeID = ob.EmployeeID, AssignDate = ob.StartDate.AddDays(i), ProfileID = ob.ProfileID };
combined_list.AddRange(items);
But: You've code that works. You understand that code. Why do you wan't to change that? BTW: Your two loops will be faster than that linq.
You can use the following Linq:
var items = all_request_list
.Where(x => x.StartDate != x.EndDate)
.SelectMany(x => Enumerable.Range(0, x.EndDate.DateDiff(x.StartDate) + 1)
.Select(y => new { ShiftID = x.ShiftID, SkillID = x.SkillID, EmployeeID = x.EmployeeID, AssignDate = x.StartDate.AddDays(y), ProfileID = x.ProfileID }))
combined_list.AddRange(items);
What it does exactly is Creating an IEumerable<> of results for each item in the all_request_list.Where using Enumerable.Range (This is the part which replaces your for loop), than flattens it using the SelectMany method.
It might be better than a for loop in the terms of readability/maintainability but keep in mind that Linq usually slower than plain loops (tl;dr: Understand what Linq does internally and what it will do in your case).
I don't know exactly what error you're getting, but it could be due to the fact that certain functions cannot be executed inside a linq statement, since it internally translates it to sql. Try this:
foreach (var ob in all_request_list.Where(x => x.StartDate != x.EndDate))
{
int consq_dates = ob.EndDate.DateDiff(ob.StartDate);
for (int i = 0; i <= consq_dates; i++)
{
var newDate = ob.StartDate.AddDays(i);
combined_list.Add(new { ShiftID = ob.ShiftID, SkillID = ob.SkillID, EmployeeID = ob.EmployeeID, AssignDate = newDate , ProfileID = ob.ProfileID });
}
}
If it still gives you an error, could you specify what error you're receiving, such as the name, type, etc.

Get Rank from list of object depending upon some condition

I have list of object of class which contain totalScore as one property.I want to get rank of Team depending upon totalscore of team.Here is the list of object I called it as List data= new List();
so data contain object of scoreboard class with total score property.
I need rank of team depending upon totalscore.Here is the code that I try but it give result like Rank=1,2,2,3 but I need Rank=1,2,2,4 like this.
data.OrderByDescending(x => x.totalScore).GroupBy(x => x.totalScore)
.SelectMany((g, i) => g.Select(e => new { data = e.Rank = i + 1 }))
.ToList();
The data list contain unique team but there total score may be same so same totalscore team must be in one rank. please help me!
If you need to update the list in-place:
int i = 0;
decimal? prevValue = null;
foreach(var item in data.OrderByDescending(x => x.totalScore))
{
item.Rank = prevValue == item.totalScore ? i : ++i;
prevValue = item.totalScore;
}
A different notation (which I prefer for readability) but essentially the same answer as provided by user3185569.
var i = 1;
var results = (from d in data orderby d.totalScore descending select new { Obj = d, Rank = i++ } ).ToList();

avoid loop to generate nullable collection, increase performance

I've been using Stopwatch and it looks like the below query is very expensive in terms of performance, even though what I already have below I find most optimal based on various reading (change foreach loop with for, use arrays instead of collection, using anonymous type not to take the whole table from DB). Is there a way to make it faster? I need to fill the prices array, which needs to be nullable. I'm not sure if I'm missing something?
public float?[] getPricesOfGivenProducts(string[] lookupProducts)
{
var idsAndPrices = from r in myReadings select
new { ProductId = r.ProductId, Price = r.Price };
float?[] prices = new float?[lookupProducts.Length];
for(int i=0;i<lookupProducts.Length;i++)
{
string id = lookupProducts[i];
if (idsAndPrices.Any(r => r.ProductId == id))
{
prices[i] = idsAndPrices.Where(p => p.ProductId == id)
.Select(a=>a.Price).FirstOrDefault();
}
else
{
prices[i] = null;
}
}
return prices;
}
It's likely every time you call idsAndPrices.Any(r => r.ProductId == id), you are hitting the database, because you haven't materialized the result (.ToList() would somewhat fix it). That's probably the main cause of the bad performance. However, simply loading it all into memory still means you're searching the list for a productID every time (twice per product, in fact).
Use a Dictionary when you're trying to do lookups.
public float?[] getPricesOfGivenProducts(string[] lookupProducts)
{
var idsAndPrices = myReadings.ToDictionary(r => r.ProductId, r => r.Price);
float?[] prices = new float?[lookupProducts.Length];
for (int i = 0; i < lookupProducts.Length; i++)
{
string id = lookupProducts[i];
if (idsAndPrices.ContainsKey(id))
{
prices[i] = idsAndPrices[id];
}
else
{
prices[i] = null;
}
}
return prices;
}
To improve this further, we can identify that we only care about products passed to us in the array. So let's not load the entire database:
var idsAndPrices = myReadings
.Where(r => lookupProducts.Contains(r.ProductId))
.ToDictionary(r => r.ProductId, r => r.Price);
Now, we might want to avoid the 'return null price if we can't find the product' scenario. Perhaps the validity of the product id should be handled elsewhere. In that case, we can make the method a lot simpler (and we won't have to rely on having the array in order, either):
public Dictionary<string, float> getPricesOfGivenProducts(string[] lookupProducts)
{
return myReadings
.Where(r => lookupProducts.Contains(r.ProductId))
.ToDictionary(r => r.ProductId, r => r.Price);
}
And a note unrelated to performance, you should use decimal for money
Assuming that idsAndPrices is an IEnumerable<T>, you should make it's initialization:
var idsAndPrices = (from r in myReadings select
new { ProductId = r.ProductId, Price = r.Price })
.ToList();
It's likely that the calls to:
idsAndPrices.Any(r => r.ProductId == id)
and:
idsAndPrices.Where(p => p.ProductId == id)
..are causing the IEnumerable<T> to be evaluated every time it's called.
Based on
using anonymous type not to take the whole table from DB
I assume myReadings is the database table and
var idsAndPrices =
from r in myReadings
select new { ProductId = r.ProductId, Price = r.Price };
is the database query.
Your implementation is far from optimal (I would rather say quite inefficient) because the above query is executed twice per each element of lookupProducts array - idsAndPrices.Any(...) and idsAndPrices.Where(...) statements.
The optimal way I see is to filter as much as possible the database query, and then use the most efficient LINQ to Objects method for correlating two in memory sequences - join, in your case left outer join:
var dbQuery =
from r in myReadings
where lookupProducts.Contains(r.ProductId)
select new { ProductId = r.ProductId, Price = r.Price };
var query =
from p in lookupProducts
join r in dbQuery on p equals r.ProductId into rGroup
from r in rGroup.DefaultIfEmpty().Take(1)
select r?.Price;
var result = query.ToArray();
The Any and FirstOrDefault are O(n) and redundant. You can get a 50% speed up just by removing theAll call. FirstOrDefault will give you back a null, so use it to get a product object (remove the Select). If you want to really speed it up you should just loop through the products and check if prices[p.ProductId] != null before setting prices[p.ProductId] = p.Price.
bit of extra code code there
var idsAndPrices = (from r in myReadings select
new { ProductId = r.ProductId, Price = r.Price })
.ToList();
for(int i=0;i<lookupProducts.Length;i++)
{
string id = lookupProducts[i];
prices[i] = idsAndPrices.FirstOrDefault(p => p.ProductId == id);
}
better yet
Dictionary<Int, Float?> dp = new Dictionary<Int, Float?>();
foreach(var reading in myReadings)
dp.add(r.ProductId, r.Price);
for(int i=0;i<lookupProducts.Length;i++)
{
string id = lookupProducts[i];
if(dp.Contains(id)
prices[i] = dp[id];
else
prices[i] = null;
}

C# List grouping and assigning a value

I have a list of Orders. This list contains multiple orders for the same item, see the table below.
I then want to assign each item that is the same (i.e. ABC) the same block ID. So ABC would have a block ID of 1 & each GHJ would have a block ID of 2 etc. What is the best way of doing this?
Currently I order the list by Order ID and then have a for loop and check if the current Order ID is equal to the next Order ID if so assign the two the same block ID. Is there a better way of doing this using linq or any other approach?
Order ID Block ID
ABC
ABC
ABC
GHJ
GHJ
GHJ
MNO
MNO
You can do this that way, it will assign same blockid for same orderid
var ordered = listOrder.GroupBy(x => x.OrderId).ToList();
for (int i = 0; i < ordered.Count(); i++)
{
ordered[i].ForEach(x=>x.BlockId=i+1);
}
it will group orders by orderid then assign each group next blockid. Note that it won't be done fully in linq, because linq is for querying not changing data.
Always depends of what better means for you in this context.
There are a bunch of possible solutions to this trivial problem.
On top of my head, I could think of:
var blockId = 1;
foreach(var grp in yourOrders.GroupBy(o => o.OrderId))
{
foreach(var order in grp)
{
order.BlockId = blockId;
}
blockId++;
}
or (be more "linqy"):
foreach(var t in yourOrders.GroupBy(o => o.OrderId).Zip(Enumerable.Range(1, Int32.MaxValue), (grp, bid) => new {grp, bid}))
{
foreach(var order in t.grp)
{
order.BlockId = t.bid;
}
}
or (can you still follow the code?):
var orders = yourOrders.GroupBy(o => o.OrderId)
.Zip(Enumerable.Range(1, Int16.MaxValue), (grp, id) => new {orders = grp, id})
.SelectMany(grp => grp.orders, (grp, order) => new {order, grp.id});
foreach(var item in orders)
{
item.order.BlockId = item.id;
}
or (probably the closest to a simple for loop):
Order prev = null;
blockId = 1;
foreach (var order in yourOrders.OrderBy(o => o.OrderId))
{
order.BlockId = (prev == null || prev.OrderId == order.OrderId) ?
blockId :
++blockId;
prev = order;
}
Linq? Yes.
Better than a simple loop? Uhmmmm....
Using Linq will not magically make your code better. Surely, it can make it often more declarative/readable/faster (in terms of lazy evaluation), but sure enough you can make otherwise fine imperative loops unreadable if you try to force the use of Linq just because Linq.
As a side note:
if you want to have feedback on working code, you can ask at codereview.stackexchange.com

LINQ suming nested groups in one statement

I am converting some code to LINQ, at the same time exploring to what extent LINQ can accomplish.
Can the following code be condensed into a single LINQ query or method?
Dictionary<string, ItemPack> consolidated = new Dictionary<string, ItemPack>();
foreach (var product in Products)
{
foreach (var smallpack in product.ItemPacks)
{
ItemPack bigpack;
if (consolidated.TryGetValue(smallpack.ItemCode, out bigpack))
{
// the big pack quantity += quantity for making one product * the number of that product
bigpack.Quantity += smallpack.Quantity * product.Quantity;
// References: we make sure that the small pack is using the Item in the big pack.
// otherwise there will be 2 occurance of the same Item
smallpack.Item = bigpack.Item;
}
else
{
bigpack = new ItemPack(smallpack); // Copy constructor
bigpack.Quantity = smallpack.Quantity * product.Quantity;
consolidated.Add(smallpack.ItemCode, bigpack);
}
}
}
return consolidated;
In English, each product is made up of several items of different quantities. These items are grouped by item code and are packs into smallpacks. These smallpacks are shipped together as a unit product. There are many different products. A single item can be used in different product.
I now have a list of products and the quantity required for each for shipment. I want a LINQ statement to consolidate a flat list of items and their quantities.
I have gotten this far, but it looks like it does not work:
var packsQuery = from product in Products
from smallpack in product.ItemPacks
select new {Item = smallpack.Item, Quantity = smallpack.Quantity * product.Quantity};
foreach (var pack in packsQuery)
{
consolidated.Add(pack.Item.ItemCode, new ItemPack(pack.Item, pack.Quantity));
}
If I group first, then I cannot select item for its quantity. If I select first, then I lose the grouping. Chicken and egg story?
EDIT:
Useful note: smallpack is of type ItemPack which looks like this
public class ItemPack
{
Item { get; } // The item in this pack, which *must* be a shared reference across all objects that uses this Item. So that change in Item properties are updated everywhere it is used. e.g. Price.
ItemCode { get; } // The item code
Quantity { get; } // The number of such Item in this pack.
}
var query = (from product in Products
from smallPack in product.ItemPacks
select new
{
ItemCode = smallPack.ItemCode,
Item = smallPack.Item,
Quantity = smallPack.Quantity * product.Quantity,
})
.GroupBy(p => p.ItemCode)
.Select(p => new
{
ItemCode = p.Key,
Item = p.FirstOrDefault(),
Quantity = p.Sum(x=>x.Quantity)
})
.ToDictionary(p=>p.ItemCode);
Thanks for putting me in the right direction. I managed to work out the full query syntax version:
var query = from product in Products
from smallpack in product.ItemPacks
select new {
Item = smallpack.Item,
Quantity = smallpack.Quantity * product.Quantity
} into mediumpack
group mediumpack by mediumpack.Item.ItemCode into bigpack
select new {
Item = bigpack.First().Item, // shared reference
Quantity = bigpack.Sum(a => a.Quantity);
}
query.ToDictionary(...);
Any comments as to whether this is fine?

Categories

Resources