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);
Related
I'm very new to LINQ and I'm searching to filter an SQL view which has a column called 'Readers' containing several group names separated by '#' (es. "Administrators#HR Group#Employees Group").
Having a list of user's groups, I need to extract all the records where Readers contains at least one of the user's groups.
In other words, the user must see only those records belonging to him.
I found this solution, but I think it's extremely inefficient:
private List<vwFax>getmyFaxes(List<string> myGroups)
{
var myFax = db.vwFax.AsQueryable();
var res = db.vwFax.AsQueryable();
List<vwFax> outRes= new List<vwFax>();
foreach (string elem in myGroups)
{
res = (from a in myFax
where a.Readers.Contains(elem)
select a);
if(res.Count() > 0)
{
outRes.AddRange(res);
}
}
return outRes.ToList();
}
Any help please?
Basically what you are saying in the following query is: For each item in myFax take it only if that item.Readers contains Any (at least 1) of the items in myGroups
outRes = db.myFax.Where(item => myGroups.Any(grp => item.Readers.Contains(grp)));
and in query-syntax:
outRes = from item in db.myFax
where myGroups.Any(grp => item.Readers.Contains(grp))
select item;
I have a 'complex' linq query I would like to improve and to understand.
(from x in tblOrder
orderby x.OrderNo
// where x.Filename is most recent filename for this order
group x by new { x.OrderNo, x.Color } into groupedByColorCode
select new
{
OrderNo = groupedByColorCode.Key.OrderNo,
ProductRef = groupedByColorCode.FirstOrDefault().ProductRef,
Color = groupedByColorCode.Key.Color,
Packing = groupedByColorCode.FirstOrDefault().Packing,
TotalQuantity = groupedByColorCode.Sum(bcc => bcc.OriQty).ToString()
}
x is an Order. I also would like to filter by Filename. Filename is a variable from tblOrder. Actually I would like to keep and keep only the orders from the most recent file.
What 'where' clause should I add to my linq query to be able to filter these last file name.
Thank you
First it's better to use orderby in the end of the query, because sorting will work quicker on the smaller set of data.
Second you should use where in the top of query, it will make smaller your set before grouping and sorting (set it after from line)
At last grouping creates dictionary with Key = new { x.OrderNo, x.Color } (in this keys) and Value = IEnumerable, and then groupedByColorCode becomes IEnumerabler of {Key, Value}. So it should stand in the end before orederby
there is MaxBy() or MinBy() if you need max or min by some criteria
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);
}
I have two IList<Traffic> I need to combine.
Traffic is a simple class:
class Traffic
{
long MegaBits;
DateTime Time;
}
Each IList holds the same Times, and I need a single IList<Traffic>, where I have summed up the MegaBits, but kept the Time as key.
Is this possible using Linq ?
EDIT:
I forgot to mention that Time isn't necessarily unique in any list, multiple Traffic instances may have the same Time.
Also I might run into X lists (more than 2), I should had mentioned that as well - sorry :-(
EXAMPLE:
IEnumerable<IList<Traffic>> trafficFromDifferentNics;
var combinedTraffic = trafficFromDifferentNics
.SelectMany(list => list)
.GroupBy(traffic => traffic.Time)
.Select(grp => new Traffic { Time = grp.Key, MegaBits = grp.Sum(tmp => tmp.MegaBits) });
The example above works, so thanks for your inputs :-)
this sounds more like
var store = firstList.Concat(secondList).Concat(thirdList)/* ... */;
var query = from item in store
group item by item.Time
into groupedItems
select new Traffic
{
MegaBits = groupedItems.Sum(groupedItem => groupedItem.MegaBits),
Time = groupedItems.Key
};
or, with your rework
IEnumerable<IList<Traffic>> stores;
var query = from store in stores
from item in store
group item by item.Time
into groupedItems
select new Traffic
{
MegaBits = groupedItems.Sum(groupedItem => groupedItem.MegaBits),
Time = groupedItems.Key
};
You could combine the items in both lists into a single set, then group on the key to get the sum before transforming back into a new set of Traffic instances.
var result = firstList.Concat(secondList)
.GroupBy(trf => trf.Time, trf => trf.MegaBits)
.Select(grp => new Traffic { Time = grp.Key, MegaBits = grp.Sum()});
That sounds like:
var query = from x in firstList
join y in secondList on x.Time equals y.Time
select new Traffic { MegaBits = x.MegaBits + y.MegaBits,
Time = x.Time };
Note that this will join in a pair-wise fashion, so if there are multiple elements with the same time in each list, you may not get the results you want.
The following method is pretty simple, I'm trying to determine a line-item rate by matching up another property of the line-item with a lookup from a parent object. There's a few things I don't like about it and am looking for elegant solutions to either make the method smaller, more efficient, or both. It works in it's current state and it's not like it's noticeably inefficient or anything. This isn't mission critical or anything, more of a curiosity.
private decimal CalculateLaborTotal()
{
decimal result = 0;
foreach (ExtraWorkOrderLaborItem laborItem in Labor)
{
var rates = (from x in Project.ContractRates where x.ProjectRole.Name == laborItem.ProjectRole.Name select x).ToList();
if (rates != null && rates.Count() > 0)
{
result += laborItem.Hours * rates[0].Rate;
}
}
return result;
}
I like the idea of using List<T>.ForEach(), but I was having some trouble keeping it succinct enough to still be easy to read/maintain. Any thoughts?
Something like this should do it (untested!):
var result =
(from laborItem in Labor
let rate = (from x in Project.ContractRates
where x.ProjectRole.Name == laborItem.ProjectRole.Name
select x).FirstOrDefault()
where rate != null
select laborItem.Hours * rate.Rate).Sum();
Or (assuming only one rate can match) a join would be even neater:
var result =
(from laborItem in Labor
join rate in Project.ContractRates
on laborItem.ProjectRole.Name equals rate.ProjectRole.Name
select laborItem.Hours * rate.Rate).Sum();
Okay, well how about this:
// Lookup from name to IEnumerable<decimal>, assuming Rate is a decimal
var ratesLookup = Project.ContractRates.ToLookup(x => x.ProjectRole.Name,
x => x.Rate);
var query = (from laborItem in Labor
let rate = ratesGroup[laborItem].FirstOrDefault()
select laborItem.Hours * rate).Sum();
The advantage here is that you don't need to look through a potentially large list of contract rates every time - you build the lookup once. That may not be an issue, of course.
Omit the rates != null check - A linq query may be empty but not null.
If you just need the first element of a list, use List.First or List.FirstOrDefault.
There is no advantage in using List<T>.ForEach.