Scan Lists of Objects and compare an Object Value - c#

For example I have 3 lists (or more):
List1:
[{store:"store1",item:"item1",price:10},{store:"store1",item:"item2",price:5},{store:"store1",item:"item4",price:100},{store:"store1",item:"item10",price:10}]
List2:
[{store:"store2",item:"item1",price:15},{store:"store2",item:"item2",price:10},{store:"store2",item:"item10",price:110}]
List3:
[{store:"store3",item:"item1",price:5},{store:"store3",item:"item2",price:10},{store:"store3",item:"item10",price:100},{store:"store3",item:"item100",price:1}]
As you can see It's like 3 stores with different items and prices. Not all stores have all items so I would like to make a list by comparing the lists and finding the objects that contain "item1" for example and then choose the cheaper price. And also to compare the lists 1 by one (list 1 with list 2 , list 1 with list 3, list 2 with 1 and list 2 with 3). Do I make any sense?
Any answer is appreciated.
I've tried some things but I just cant understand it (and its for 2 stores):
var result = (from l1 in store1list join l2 in store2list on l1.Symbol equals l2.Symbol orderby l1.Symbol select new
{
store = l1.store,
price = l1.price,
item = l1.item
}).ToList();

You may Union your lists and then GroupBy item, and select ordering each group with price and taking the first one (cheapest) from each group.
var result = List1.Concat(List2).Concat(List3).GroupBy(x => x.item)
.Select(g => g.OrderBy(x=> x.price).First()).ToList();

Related

LINQ sum with grouping, but also get line item value before grouping

I have a list of type Cars like below List<Car> cars:
ID
Colour
Make
Model
Km
1
Red
BMW
M3
1000
2
Red
BMW
318i
1000
3
Black
Mercedes
C200
1000
4
Black
Mercedes
E200
1000
5
White
Mercedes
CLA200
1000
6
White
Mercedes
E200
1000
7
White
Mercedes
E200
2000
8
White
Mercedes
E200
3000
Now what I want to do is, using LINQ (or maybe another way if not possible with LINQ) I want to group them by Colour, Make, Model and then add another column and assign to list of another type (<CarDetail>) which will give the weighted average of the line in the group by.
As an example, on last 3 Cars, the new column will show (Total Km for that group = 1000+2000+3000 = 6000).
6 => 1000/6000
7 => 2000/6000
8 => 3000/6000
What I have tried so far is :
List<CarDetail> carDetails =
cars.
GroupBy(x => new
{
x.Colour,
x.Make,
x.Model
}).
Select(h => new CarDetail
{
Colour= h.Key.Colour,
Make= h.Key.Make,
Model= h.Key.Model,
WeightedKm = x.Km / h.Sum(x => x.Km )
}).ToList();
h.Sum(x => x.Km ) will bring the sum of Km.s but trying to use "x.Km" surely doesn't work.
Can anyone help on how to get this list please?
A grouping is conceptually a list of lists. It seems you want to unpack this to a simple list, using an aggregation of the sub list along the way. The process for turning a list of lists into a list is a SelectMany
var carDetails = cars
.GroupBy(c => new
{
c.Colour,
c.Make,
c.Model
})
.SelectMany(g => g, (subList, aCarInTheSublist) => new CarDetail
{
Colour= aCarInTheSublist.Colour,
Make= aCarInTheSublist.Make,
Model= aCarInTheSublist.Model,
WeightedKm = (double)aCarInTheSublist.Km / subList.Sum(aCar => aCar.Km ) //cast to double because int math 1000/3000 = 0
}).ToList();
Attacking it with a Select will run into trouble because what you're selecting is the group i.e. the outer list. Taking this simpler example:
A, 1
A, 2
A, 3
B, 1
If you group by the letter, your output only has 2 rows:
A -> {A,1} {A,2} {A,3}
B -> {B,1}
so you'll struggle to get back to the 4 rows you want, because the Select is only run twice.
SelectMany will visit each entry in the group (i.e. the two groups A and B, the A group having 3 sub-entries) and visit each one of the sub entries, so for the A group (a single group with 3 members, A1, A2 and A3) it will result in projecting 3 elements out. Crucially because, whilst it visits each of A1, A2 and A3, access to the group A as a whole is still available so you can sum it all the KMs in it
It's perhaps not very efficient, because we're summing up the group repeatedly but LINQ doesn't always win efficiency contests :)
You could consider trading CPU time in summing for memory in remembering the sum by putting the sums in a dictionary:
var d = cars
.GroupBy(c => new
{
c.Colour,
c.Make,
c.Model
})
.ToDictionary(g=>g.Key, g=>g.Sum(c=>c.Km));
var result = cars
.Select(c => new CarDetail
{
Colour= c.Colour,
Make= c.Make,
Model= c.Model,
WeightedKm = (double)c.Km / d[new{c.Colour,c.Make,c.Model}]
}).ToList();
Side note, when using LINQ try and keep aliases representative of what the list entry is. cars is a list of car, so Select(c => to help keep straight that it's a car.
A GroupBy(c=>...).Select(g=> reminds you that you're grouping up cars and then Select is operating on a grouping. Each item within the grouping is a Car, so you might like Select(gc=> for "group of cars", and Select(gc=> gc.Select(c=> the second select is operating on an enumerable of Car, so reverting to c helps clarify that

Get list of items where their ID is equal to some Values - linq c#

Here is a query:
from order in db.tblCustomerBuys
where selectedProducts.Contains(order.ProductID)
select order.CustomerID;
selectedProducts is a list containing some target products IDs, for example it is { 1, 2, 3}.
The query above will return customerIDs where they have bought one of the selectedProducts. for example if someone has bought product 1 or 2, its ID will be in result.
But I need to collect CustomerIDs where they have bought all of the products. for example if someone has bought product 1 AND 2 AND 3 then it will be in result.
How to edit this query?
the tblCustomerBuys are like this:
CustomerID - ID of Customer
ProductID - the product which the customer has bought
something like this:
CustomerID ProdcutID
---------------------------
110 1
110 2
112 3
112 3
115 5
Updated:
due to answers I should do grouping, for some reason I should use this type of query:
var ID = from order in db.tblCustomerBuys
group order by order.CustomerID into g
where (selectedProducts.All(selProdID => g.Select(order => order.ProductID).Contains(selProdID)))
select g.Key;
but it will give this error:
Local sequence cannot be used in LINQ to SQL implementations of query operators except the Contains operator.
The updated query is the general LINQ solution of the issue.
But since your query provider does not support mixing the in memory sequences with database tables inside the query (other than Contains which is translated to SQL IN (value_list)), you need an alternative equivalent approach of All method, which could be to count the (distinct) matches and compare to the selected items count.
If the { CustomerID, ProductID } combination is unique in tblCustomerBuys, then the query could be as follows:
var selectedCount = selectedProducts.Distinct().Count();
var customerIDs =
from order in db.tblCustomerBuys
group order by order.CustomerID into customerOrders
where customerOrders.Where(order => selectedProducts.Contains(order.ProductID))
.Count() == selectedCount
select customerOrders.Key;
And if it's not unique, use the following criteria:
where customerOrders.Where(order => selectedProducts.Contains(order.ProductID))
.Select(order => order.ProductID).Distinct().Count() == selectedCount
As your question is written, it is a bit difficult to understand your structure. If I have understood correctly, you have an enumerable selectedProducts, which contains several Ids. You also have an enumeration of order objects, which have two properties we care about, ProductId and CustomerId, which are integers.
In this case, this should do the job:
ver result = db.tblCustomerBuys.GroupBy(order => order.CustomerId)
.Where(group => !selectedProducts.Except(group).Any())
.Select(group => group.Key);
What we are doing here is we are grouping all the customers together by their CustomerId, so that we can treat each customer as a single value. Then we are treating group as a superset of selectedProducts, and using a a piece of linq trickery commonly used to check if one enumeration is a subset of another. We filter db.tblCustomerBuys based on that, and then select the CustomerId of each order that matches.
You can use Any condition of Linq.
Step 1 : Create list of int where all required product id is stored
Step 2: Use Any condition of linq to compare from that list
List<int> selectedProducts = new List<int>() { 1,2 } // This list will contain required product ID
db.tblCustomerBuys.where(o=> selectedProducts .Any(p => p == o.ProductID)).select (o=>o.order.CustomerID); // This will return all customerid who bought productID 1 or 2

How does GroupBy in LINQ work?

I originally have a dictionary of <string, List<ProviderSummary>> called rowsDictionary
Now for each key of that dictionary I group its list of values by some criteria as below:
Dictionary<string, List<ProviderSummary>> providerGroups = rowsDictionary.ToDictionary(
x => x.Key,
v => v.Value.GroupBy(x => new { x.GroupID, x.GroupFin, x.ZipCode })
.Select(x => x.First())
.ToList());
so for example if key["1234"] originally had 6 items in its list of values, now it may have two items based on that grouping. My question and confusion is what happens to the rest of the values? ( those four) and what values will go in to these two lists that are returned for the group?
Group by works by taking whatever you are grouping and putting it into a collection of items that match the key you specify in your group by clause.
If you have the following data:
Member name Group code
Betty 123
Mildred 123
Charli 456
Mattilda 456
And the following query
var query = from m in members
group m by m.GroupCode into membersByGroupCode
select membersByGroupCode;
The group by will return the following results:
You wouldn’t typically want to just select the grouping directly. What if we just want the group code and the member names without all of the other superfluous data?
We just need to perform a select to get the data that we are after:
var query = from m in members
group m by m.GroupCode into membersByGroupCode
let memberNames = from m2 in membersByGroupCode
select m2.Name
select new
{
GroupCode = membersByGroupCode.Key,
MemberNames = memberNames
};
Which returns the following results:
 
What values will go in to the lists that are returned for the group?
The first for each group, because you do:
.Select(x => x.First())
What happens to the rest of the values?
They will not be projected into your target dictionary.
Your LINQ group by query takes the original list, performs additional grouping on it, and then prunes the list based on that grouping.
Consider a situation where a single list contains these items:
GroupID GroupFin ZipCode Name
------- -------- ------- ----
1 1 94111 A
1 1 94111 B
1 1 94111 C
1 1 94111 D
1 2 94110 E
1 2 94110 F
Group by would make two groups out of this list of six:
GroupID=1 GroupFin=1 ZipCode=94111
GroupID=1 GroupFin=2 ZipCode=94110
The first group would contain providers A, B, C, and D; the second group would contain E and F.
The next thing that your query does is applying First. This operation picks the initial item from the group; in this case, it would be A and E. The remaining items are thrown ignored.

Combine two items in a list

I need a way to reduce a list, or calculate a "Total." I have a class, lets call it Prod. Prod contains 4 values. One is the name of the product, the id, a serial number, and a quantity. Basically I have one product but 2 different serial numbers. So when I get my data back from my query I have 2 items which I want to treat as a single item. How can I go about using LINQ or something else (I cannot foreach over them. There are many more class members and that would take a while plus look terrible). I want to be able to take the 2 instances and combine their serial numbers (not add just Serail1 - Serial 2) and also calculate the quantities together.
I think what you want is the Linq grouping function (see GroupBy - Simple 3). This should give you a list of serial numbers and their quantity count:
public void Linq42()
{
List<Prod> products = GetProductList();
var serialCombined =
from p in products
group p by p.SerialNumber into g
select new { SerialNumber = g.Key, Total = g.Count() };
}
Use the join operator and place them in a Tuple. You can then call more LINQ on the tuples or iterate over them.
var combinedProducts =
from product1 in products1
join product2 in products2 on product1.serial equals product2.serial
select Tuple.Create(product1, product2);
// Use LINQ to calculate a total
combinedProducts.Sum(combined => combined.Item1.count * combined.Item2.price);
// Or use foreach to iterate over the tuples
foreach (var combined in combinedProducts) {
Console.WriteLine("{0} and {1}", combined.Item1.name, combined.Item2.name);
}

LINQ Query To Select Between Unlike Generic Lists

I have two generic lists where I want to run a couple of Linq queries to find out:
Are any of lists A items found in list B
Are all of lists A items found in list B
Here are the lists:
var ListA = new List<long>()
var ListB = new List<MyObject>()
MyObject is defined as:
public class MyObject
{
public long ItemId { get; set; }
// ... Other stuff...
}
I am trying to determine two things (two queries): 1. Do any of the longs in ListA match any of the MyObject.ItemId in ListB? And 2. Can all of the longs in ListA be found in ListB?
ListA and ListB can be different lengths. For number 2, I would need all of ListA's items found in ListB, but not vice-versa. I hope this makes sense.
Thanks,
-Scott
First, you only care about the ItemIds in ListB, so:
var bIDs = ListB.Select(x => x.ItemId);
To answer the first part of your question, I would approach this by finding the intersection of the two lists (the set of all items they share). If it has at least one element in it, then there is overlap between the two.
var sharedIds = ListA.Intersect(bIDs);
if (sharedIds.Any())
// list A contains at least one ItemID which ListB contains
As for the second part, you want to see if list A is a subset of list B. Searching for this, Stack Overflow presents a clean solution:
if (!ListA.Except(bIDs).Any())
// yes, list A is a subset of list B
This snippet works because ListA.Except(bIDs) finds the elements that ListA has that bIDs doesn't. If this is empty, then ListA doesn't contain anything that bIDs doesn't. Thus, everything that is in ListA is also in bIDs.
Here's an example: A = {1, 2}; B = {1, 2, 3}. A is a subset of B. A.Except(B) gives you an empty set - B has both 1 and 2, so can't be in the resulting list, and there isn't anything left in B. So when A is a subset of B, A.Except(B).Any() gives false, as there are no elements in the result; so we obviously negate it if we want to handle that case.
For completeness, if we swap A and B round such that A is not a subset of B: A = {1, 2, 3}; B = {1, 2}, then A.Except(B) gives {3}. It can't contain 1 or 2, because B contains 1 and 2. But B doesn't contain 3, so A.Except(B) can contain it. As {3} contains one element, it isn't empty, so A.Except(B).Any() is true. Negated, it is false if A is not a subset of B.
My explanation is a little terse; if you want to look things up further (and I recommend you do - a little set theory can go a long way), A.Except(B) is LINQ's name for the set difference, or relative set complement. Wikibooks has a decent introduction to set theory if you are so inclined.
var value1 =
(
from itemA in ListA
where ListB.Any(itemB => itemB.ItemID == itemA)
select item
).Count();
var value2 = value1 == ListA.Count();
To just test the conditions, assuming you extract a list of ItemIds into listB:
bool inListA = listA.Any(x => listB.Contains(x));
bool allInListB = listA.All(x => listB.Contains(x));
To test in place without extracting a separate list if ItemIds
bool inListA = listA.Any(x => listB.Select(b => b.ItemId).Contains(x));
bool allInListB = listA.All(x => listB.Select(b => b.ItemId).Contains(x));
If you need to answer all three questions at the same time then it's likely that a pure LINQ solution won't be optimal, since the individual queries will each need to perform the same intersection operation. Do the intersection once, and then use that result to answer your three questions:
var tempSet = new HashSet<long>(ListA);
int uniqueAItemCount = tempSet.Count;
// 2b. "I would need all of ListA's items found in ListB, but not vice-versa."
tempSet.IntersectWith(ListB.Select(x => x.ItemId));
// tempSet now contains all items from ListA also found in ListB
// we can use this result to answer the other two questions...
// 1. "Do any of the longs in ListA match any of the MyObject.ItemId in ListB?"
bool anyAFoundInB = tempSet.Count > 0;
// 2a. "Can all of the longs in ListA be found in ListB?"
bool allAFoundInB = tempSet.Count == uniqueAItemCount;

Categories

Resources