Linq query explained in plain english - c#

I have a Linq query I'm not 100% sure of, can someone explain it in plain English what is happening
var duplicates = this.BedStays
.GroupBy(i => new { i.PatientClassificationCode, i.BedBandCode, i.BedLevelAddOnInd, i.ServiceCodeTypeCode, i.ServiceCode, i.DayRate })
.Where(g => g.Count() > 1)
.Select(g => g.Key).ToList();

It will give you all duplicate rows which have the same
PatientClassificationCode
BedBandCode
BedLevelAddOnInd
ServiceCodeTypeCode
ServiceCode
DayRate
It does this by grouping rows together which have the same criteria, and then only displaying the groups with more than one row (that is, duplicates).

var duplicates = this.BedStays
...Assigning to a var called "duplicates" from a property in this class called "BedStays."
.GroupBy(
...Grouping by a set of specific traits...
i =>
Called a single BedStay "i" (could have called it instead "aBedStay" or "bedStay" but the author went with "i")...
new { i.PatientClassificationCode, i.BedBandCode, i.BedLevelAddOnInd, i.ServiceCodeTypeCode, i.ServiceCode, i.DayRate })
...Creating a new object to sort by (First by the first member, then by the second, and so on and so forth)...
.Where(g => g.Count() > 1)
...Only picking the bedStay groupings where there's at least one matching member (I believe this is unneeded as all groupings, by definition, will only exist if from at least one grouping definition)...
.Select(g => g.Key).ToList();
...And finally we're only caring about the key, which is the grouping we defined when we new'd up the {i.Property, i.Property, i.Property} object. And we make it a list.

can someone explain it in plain English what is happening
Query is primarily listing all duplicates in your BedStays collection, by grouping on PatientClassificationCode, BedBandCode, BedLevelAddOnInd, ServiceCodeTypeCode, ServiceCode, DayRate fields.

Related

Take list of unique by name Presidents with the oldest date of Governance (if name repeated)

I have a List of Presidents:
var obj = new List<President>();
They have fields Name and BeginningGovernance.
The Name can be repeated in list. The BeginningGovernance cann't repeat.
I need take List of unique by Name Presidents with the oldest BeginningGovernance (if name repeated).
How can I do this with help of Linq?
Try GroupBy:
Group by Name
Pick up the right president among ones with the same Name
Materialize to List "I need take list..."
Implementation:
var result = obj
.GroupBy(president => president.Name)
.Select(chunk => chunk // among presidents who have the same name
.OrderBy(president => president.BeginningGovernance)
.First())
.ToList();
The accepted answer is fine. I'll just point out that you could write a query that is shorter and easier to understand by using this code:
https://github.com/morelinq/MoreLINQ/blob/8fbb540fd140b6d0008120c37abd00e129203cc6/MoreLinq/DistinctBy.cs
Your query would then be
presidents
.OrderBy(p => p.BeginningGovernance)
.DistinctBy(p => p.Name)
.ToList()
which is extremely easy to read and understand.
Exercise: Suppose we swapped the order of the OrderBy and DistinctBy; would the query still be correct? Why or why not?

GroupBy and OrderBy

I'm trying to do a GroupBy and then OrderBy to a list I have. Here is my code so far:
reportList.GroupBy(x => x.Type).ToDictionary(y=>y.Key, z=>z.OrderBy(a=>a.Lost));
With the help of the last question I asked on linq I think the ToDictionary is probably unneeded, but without it I don't know how to access the inner value.
To be clear, I need to GroupBy the Type property and want the inner groups I get to be OrderBy the Lost property (an integer). I want to know if there is a better, more efficient way or at the least better then what I've done.
An explanation and not just an answer would be very much appreciated.
Yes, there is better approach. Do not use random names (x,y,z,a) for variables:
reportList.GroupBy(r => r.Type)
.ToDictionary(g => g.Key, g => g.OrderBy(r => r.Lost));
You can even use long names to make code more descriptive (depends on context in which you are creating query)
reportList.GroupBy(report => report.Type)
.ToDictionary(group => group.Key,
group => group.OrderBy(report => report.Lost));
Your code does basically the following things:
Group elements by type
Convert the GroupBy result into a dictionary where the values of the dictionary are IEnumerables coming from a call to OrderBy
As far as the code correctness it is perfectly fine IMO, but maybe can be improved in term of efficiency (even if depends on your needs).
In fact, with your code, the values of your dictionary are lazily evaluated each time you enumerate them, resulting in a call to OrderBy method.
Probably you could perform it once and store the result in this way:
var dict = reportList
.GroupBy(x => x.Type)
.ToDictionary(y => y.Key, z => z.OrderBy(a => a.Lost).ToList());
// note the ToList call
or in this way:
var dict = reportList.OrderBy(a => a.Lost)
.GroupBy(x => x.Type)
.ToDictionary(y => y.Key, z => z);
// here we order then we group,
// since GroupBy guarantees to preserve the original order
Looks fine to me. If you use an anonymous type instead of a Dictionary, you could probably improve the readability of the code that uses the results of this query.
reportList.GroupBy(r => r.Type)
.Select(g => new { Type = g.Key, Reports = g.OrderBy(r => r.Lost) });

GroupBy and Sum

I read a lot of GroupBy + Sum topics but I didn't understand how to use it.
I have a list of contacts, and in this list, i want to get the state (which appears more).
So my code is:
contacts.GroupBy(i => i.Address.State.ToUpperInvariant());
In this GroupBy, I want to know the state that appears more (and remove the case of "" because empty state is not important to me).
How do I do it?
I was thinking in something like this:
contacts.GroupBy(i => i.Address.State.ToUpperInvariant()).Select(i => i.Max());
Thanks in advance!
You want something like:
var counts = contacts
.Where(c => c.State != string.Empty)
.GroupBy(i => i.Address.State, StringComparer.OrdinalIgnoreCase)
.Select(grp => new { State = grp.Key, Count = grp.Count());
GroupBy returns an IEnumerable<IGrouping<TKey, TSource>>. Since IGrouping<TKey, TSource> implements IEnumerable<TSource>, you can use the Count extension method to get the number of elements in the group.

LINQ group by and order by in C#

I need to convert my city list into group by state and order by city within it.
I tried below one but not able to get it right. Would appreciate any help on this.
cities.GroupBy(g => g.state).Select(o => o.OrderBy(c => c.cityname));
Try below code
cities.GroupBy(g => g.state)
.Select(o =>new {
State = o.Key,
Cities = o.OrderBy(c => c.cityname).Tolist()})
.Tolist();
cits.OrderBy(d => d.cityname).GroupBy(d => d.state).SelectMany(g => g).ToList();
1 - Order by cityname first.
2 - Then group them according to state. Since you order first, groups are still ordered with respect to cityname property.
3 - Convert to single list. Otherwise, you will end up with list of groups.
Should work. I also advice using camel case notation for naming your variables.
The ToLookup function may give you what you need.
cities.ToLookup(c => c.state, c => c.city);
This will create an IGrouping<string, string> where you can iterate through the Key values (states) and operate on a set of city values.
To sort it first, just do cities.OrderBy(c => c.state).ThenBy(c => c.city).
Do the orderby first:
cities.OrderBy(c=>c.cityname).GroupBy (c => c.state);
You might want to order the states to so.
cities.OrderBy(c=>c.cityname).GroupBy (c => c.state).OrderBy (g => g.Key);

Query an XML using LINQ and excluding where an Attribute value is equal to that of an Element

I have a LINQ query against an XML, that gives me a list of nested lists, each sublist being a list of an elements("row") attributes.
var items = loadbodies.Descendants("row").Select(a => a.Attributes().Select(b => b.Value).ToList()).ToList();
This works as intended but, what I actually need to is query this against another list of values so as not to have sublists added where one of the elements attributes("messageID") is on the second list. I can do this for one value but need to check it against the entire second list.
The query to exclude a single sublist by a single hardcoded value from the second list is below.
var items = loadbodies.Descendants("row").Where(c => (string)c.Attribute("messageID") != "avaluefromthesecondlist").Select(a => a.Attributes().Select(b => b.Value).ToList()).ToList();
Any help would be much appreciated.
Just use Contains. Note that splitting lines helps readability considerably:
var ids = ...; // Some sequence of ids, e.g. a List<string> or HashSet<string>
var items = loadbodies
.Descendants("row")
.Where(row => ids.Contains((string) row.Attribute("messageId")))
.Select(a => a.Attributes()
.Select(b => b.Value)
.ToList())
.ToList();
Note that you could use a Join call too... but so long as you've got relatively few IDs, this should be fine.

Categories