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);
}
Related
I have created a program that processes data based on a sorted List of ObjectProperties, one of these properties is List of strings. Right now, elements can be sorted based on simple properties like size and rate, but I'd like to be able to sort subsort by List of strings similarity. In other words, I'd like to sort by size, then rate, then the similarity of the List of strings.
lMessages.OrderBy(x => x.Size).ThenBy(x => x.Rate).ThenBy(???)
I can calculate the similarity pretty easily:
private double FindExactMatrixSimilarity(List<string> A, List<string> B)
{
var lSimilarity = A.Intersect(B);
var lUnion = A.Union(B);
if (lUnion.Count() == 0)
return 0;
else
return Convert.ToDouble(lSimilarity.Count()) / Convert.ToDouble(lUnion.Count());
}
I'm getting stuck figuring out how to use this information. It seems like it needs an initial condition. If I know the first object, I can sort just fine. It won't be deterministic, but I don't care. If I want to sort by size, then rate, and then similarity, I don't think I know which object is first in my little group where the sizes and rates match. Am I over-complicating this?
Based on your comments, here's a way I think should work for you when you calculate the similarity based on the first element in each group of messages that has the same size and rate. It's not beautiful, it's not a single Linq statement, but it should work.
I split it into more single commands than necessary for better understanding.
List<Message> finalList = new List<Message>();
// First, group all elements with similar size and rate
var groupedMessages = orderedMessages.GroupBy(m => new { m.Size, m.Rate });
// Now bring them into the correct order by their size and rate
groupedMessages = groupedMessages.OrderBy(gm => gm.Key.Rate).ThenBy(gm => gm.Key.Size);
// Now sort by similarity within each group
foreach (var gm in groupedMessages)
{
List<string> baseStringList = gm.First().StringList;
var orderedGroupEntries = gm.OrderByDescending(
m => FindExactMatrixSimilarity(baseStringList, m.StringList));
// This will add to the result list in the correct order
finalList.AddRange(orderedGroupEntries);
}
Edit: Here's a LINQ only version:
var result = (from m in messageList
group m by new
{
m.Rate,
m.Size
} into groupedMessages
orderby groupedMessages.Key.Rate, groupedMessages.Key.Size
select new List<Message>(
from m in groupedMessages
let baseStringList = groupedMessages.First().StringList
orderby FindExactMatrixSimilarity(baseStringList, m.StringList) descending
select m)
).SelectMany(m => m);
I have two lists:
Products
A list of Product and Warehouse combination containing
prices/quantities etc.
Two seperate results of sql queries.
The second list has a 'ProductId'.
What I'm currently doing is:
foreach(var product in Products)
var productWarehouses = Warehouses.Where(x=> x.ProductId == productId).ToList();
product.Warehouses = productWarehouses;
Thing is, this takes very, very long.
Note: I've tried splitting Products into chunks of lists and using Parallel.Foreach() and Tasks to take the time down - but still takes very long.
Use a Join rather than doing a linear search through the entirety of one collection for each item in the other collection:
var query = from product in Products
join warehouse in Warehouses
on product.productId equals warehouse.ProductId
into warehouses
select new
{
product,
warehouses,
};
Instead of doing this in c#, I would prefer to do it in SQL stored procedure. Get all details from one sql and then iterate though result to create Product list.
Create a Dictionary<int, Product> from your Products where Product.ProductID is used as key:
var pd = Products.ToDictionary(p => p.ProductID, p => p);
then you can iterate over Warehouses and lookup appropriate products fast:
foreach (var wh in Warehouses)
{
pd[wh.ProductId].Warehouses.Add(wh); //supposed Product.Warehouses lists have already been created, if not, add checks and create them as needed.
}
for reporting purposes i wanna split a list of purchase orders into multiple lists. One list for each purchase address. I know it's possible to group the list by purchase address, but my question is how to split the list by this purchase address into multiple lists and use these multiple list to create individual reporting files.
code:
(from o in orders
group o by new {field1, field2, field3, field4} into og
orderby og.Key.field1
select new ViewClass
{
purchaseAddress = og.Key.field1,
product = og.key.field2,
count = og.count
}).ToList()
question: how to split above list into multiple lists for each purchaseAddress?
There's a built-in function that I think does what you want. If I assume that your code is assigned to a variable called query then you can write this:
ILookup<string, ViewClass> queryLists = query.ToLookup(x => x.purchaseAddress);
This essentially creates a list of lists that you access like a dictionary, except that each value is a list of zero or more values. Something like:
IEnumerable<ViewClass> someList = queryLists["Some Address"];
Just turn each group into a List.
select new ViewClass
{
purchaseAddress = og.Key.field1,
product = og.key.field2,
count = og.count,
List = og.ToList()
}).ToList();
Oh, your grouping is one way for entities and another way for pages... just regroup.
List<ViewClass> classes = (
from o in orders
group o by new {field1, field2, field3, field4} into og
orderby og.Key.field1
select new ViewClass
{
purchaseAddress = og.Key.field1,
product = og.key.field2,
count = og.count
}).ToList();
List<List<ViewClass>> regrouped = (
from c in classes
group c by c.purchaseAddress into g
select g.ToList()
).ToList();
Another simple built-in function that you can use is the GroupBy function. It does a similar job as the ToLookup but it means that your new list is IQuerable, not like a dictionary and a few other things (see this article for a good breakdown)
var newList = orders.GroupBy(x => x.field1);
This will return a list of lists grouped by the field(s) you specify.
I have used two LINQ queries to manipulate a datatable and return the data that I want. Effectively, the table has the following columns:
Group Animal Numbers
There can be multiple Animals within a single group, and multiple numbers assigned to a single animal. Initially I want to calculate the range of the numbers for each specific animal, and then calculate an average range for each group. With the output being the group number and then the average range.
I have the following two LINQ queries, that do work, however my question is, can this be merged into a single query, or , as they are distinct operations, should they be kept seperate for readability and testability?
//Calculate range/delta for each number per animal
var query = from data in Data.AsEnumerable()
group data by new { animal = data.Field<string>("animal"), gp = data.Field<string>("group") }
into g
orderby g.Key.gp
select new
{
animal = g.Key.animal,
gp = g.Key.gp,
delta = g.Max(c => Convert.ToDouble(c.Field<string>("numbers")))
- g.Min(c => Convert.ToDouble(c.Field<string>("numbers")))
};
//calculate average range/delta per group
var temp = from q in query
group q by q.gp
into g
select new
{
gp = g.Key,
average = g.Average(c => c.delta)
};
Thanks.
Because you aren't forcing the first query to evaluate with a method like ToArray(), there's no advantage to combining these queries. I would leave them separate for readability.
You could first group the data by group and then, in a nested query, by animal to calculate the average of the deltas:
var result = from data in Data.AsEnumerable()
group data by data.Field<string>("group") into g
select new
{
gp = g.Key,
average = (
from item in g
group item by data.Field<string>("animal") into f
select f.Max(c => double.Parse(c.Field<string>("numbers")))
- f.Min(c => double.Parse(c.Field<string>("numbers")))
).Average()
};
I have a many to many table structure called PropertyPets. It contains a dual primary key consisting of a PropertyID (from a Property table) and one or more PetIDs (from a Pet table).
Next I have a search screen where people can multiple select pets from a jquery multiple select dropdown. Let's say somebody selects Dogs and Cats.
Now, I want to be able to return all properties that contain BOTH dogs and cats in the many to many table, PropertyPets. I'm trying to do this with Linq to Sql.
I've looked at the Contains clause, but it doesn't seem to work for my requirement:
var result = properties.Where(p => search.PetType.Contains(p.PropertyPets));
Here, search.PetType is an int[] array of the Id's for Dog and Cat (which were selected in the multiple select drop down). The problem is first, Contains requires a string not an IEnumerable of type PropertyPet. And second, I need to find the properties that have BOTH dogs and cats and not just simply containing one or the other.
Thank you for any pointers.
You can do this using a nested where clause.
You need to filter p.PropertyPets using contains - return all rows where PetID is in search.PetType.
Then only return rows from properties where all search id's have been found - eg number of rows >= number of serach id's
All together:
var result = from p in properties
where p.PropertyPets.Where(c => search.PetType.Contains(c.PetID)).Count() >= search.PetType.Count()
select p;
For the part where Contains requires a string would not be true, Contains should require an int if your search.PetType is int[]. That means that you need to "convert" p.PropertyPets into an int. To convert p.PropertyPets to IEnumerable<int> you need to select the PropertyID field: p.PropertyPets.Select(propertyPet => propertyPet.PropertyID), but that won't get you a single int as required but a whole bunch. (.First() would give you one int but not solve your problem.
What you really want to do is
var result = properties.Where(p =>
search.PetType.Except(p.PropertyPets.Select(propertyPet =>
propertyPet.PropertyID)).Count() == 0);
But Except is not available in LINQ2SQL.
The best option I can find is to apply Contains for each item in search.PetType.
Something like this:
var result = properties;
foreach(var petType in search.PetType)
{
result = from p in result
where p.PropertyPets.Select(propertyPet =>
propertyPet.PropertyID).Contains(petType)
select p;
}