Add incremented property to IEnumerable by group using LINQ - linq

To simplify the scenario, let's say we have a list of People with FirstName and LastName properties.
Our data looks like this:
Bob Smith
Jane Smith
Todd Grover
Larry Lewis
Jill Lewis
Frank Lewis
The first step would be to add an Integer property that gets incremented for each item:
Bob Smith 1
Jane Smith 2
Todd Grover 3
Larry Lewis 4
Jill Lewis 5
Frank Lewis 6
Ideally, I'd like to reset the counter for every new group to achieve this:
Bob Smith 1
Jane Smith 2
Todd Grover 1
Larry Lewis 1
Jill Lewis 2
Frank Lewis 3
Maybe LINQ isn't appropriate. It just seems like LINQ should be able to do this elegantly.

If you just want a counter that increments with each item:
var query = people
.Select((x, i) => new { x.FirstName, x.LastName, Index = i + 1 });
Or if you want to reset the counter for each group, grouped by LastName:
var query = people
.GroupBy(x => x.LastName)
.Select
(
x => x.Select((y, i) => new { y.FirstName, y.LastName, Index = i + 1 })
)
.SelectMany(x => x);
And to quickly display the query results:
foreach (var item in query)
{
Console.WriteLine(item.FirstName + "\t" + item.LastName + "\t" + item.Index);
}

You can use the second overload of the Select method, which incorporates an index parameter passed to the lambda expression, for example:
var people = new [] {
new { FirstName = "Bob", LastName = "Smith"},
new { FirstName = "Jane", LastName = "Smith"},
new { FirstName = "Todd", LastName = "Grover"},
new { FirstName = "Larry", LastName = "Lewis"},
new { FirstName = "Jill", LastName = "Lewis"},
new { FirstName = "Frank", LastName = "Lewis"},
}.ToList();
people.Select((p, index) => new {
FirstName = p.FirstName,
LastName = p.LastName,
Index = index
}
);
Result:

Assuming there is an integer property already on the record type, and the collection is already sorted, then you can abuse Aggregate (i.e. left-fold), something like this
collection.Aggregate( (old, next) => { if (namesDiffer(old, next)) next.Index = 1 else next.Index = old.Index +1; return next;}, aRecordWithEmptyName);
EDIT -- fixed return value; fingers had been on autopilot.

Related

LINQ: Drill down to value in query results

I am using LINQ for the first time and have the following query:
var query = from c in context.Call
join d in context.CallData on c.CallID = d.CallID
where c.CallID == 123
select new {
c.CallID,
c.CallResult,
d.FieldID,
d.FieldValue
};
This returns data similar to the following
c.CallID c.CallResult d.FieldID d.FieldValue
123 Sale 345 John
123 Sale 346 Doe
123 Sale 347 888-222-3333
How can drill down to the row containing d.FieldID = 346 to get the value of d.FieldValue (346 being the LastName field)? I would like to use this value as the new value of a local variable:
// I know this doesn't work, just showing what I would like to accomplish
string LastName = query.Select(a => a.FieldID = 346).FieldValue;
Conversely, how can I change the value of c.CallResult once I have these results? Since it is a join, can I simply change one row of data or do I have to do a separate query?
For the first question you can move on from your query variable:
var fieldValue = query.Where(x => x.FieldID == 346).Single().FieldValue;
As for your second question: you must apply a change to the original entity object:
var call = context.Find(123);
// or context.Calls.Single(c => c.CallId == 123) if this is not DbContext.
call.CallResult = newValue;
context.SaveChanges();
Do you mean:
string LastName = query
.Where(a => a.FieldID = 346)
.Select(a => a.FieldValue)
.FirstOrDefault();

LINQ get first value in given list

I have a list(mainlist) with the following
PsID Nominprsn
---- ---------
1 James
2 Troy
2 William
3 Mike
4 Jason
4 Hendry
I need to get the first name from a given PsID
so the result would be as such
PsID Nominprsn
---- ---------
1 James
2 Troy
3 Mike
4 Jason
I tried the following but was not successful as I didn't know how to get the first PsID:
var proglis = (from ts in mainlist
group ts by new { ts.PsID, Nominprsn} into grp
select new {grp.Key.PsID grp.Key.Nominprsn}).ToList();
To select the first item from each group use this query:
var query = mainlist.GroupBy(o => o.PsID)
.Select(g => new { PsID = g.Key, g.First().Nominprsn })
.ToList();
This will return the same results you've shown in your question. The result will be an IEnumerable of anonymous types with properties named PsID and Nominprsn.
Here is how you should do it:
var proglis = (from ts in mainlist
group ts by ts.PsID into grp
select new
{
Id = grp.Key,
Name = grp.First().Nominprsn
}).ToList();
Or maybe try Distinct or DistinctBy(o => o.Key.PsID)

Aggregation in LINQ

I am new to LINQ. I am stuck with a very silly problem
Name Subjects Role
---- -------- --------
A Math Student
A English Student
B Math Student
B English Student
C Math Student
C Math Admin
I need result as
Name Subjects Role
---- -------- --------
A Math, English Student
B Math, English Student
C Math Student
C Math Admin
I am confused as to how to go about this problem. This is simple in SQL where I can do a groupby clause and get the comma seperated values via a function.
Can someone please help me out?
Edited: The three columns are from 3 different sources. I have updated the resultant table. Thanks for your help in advance!
I don't have your code but it should look like this:
var grouped = from element in yourList
group element by element.Name into g
select new
{
Name = g.Key,
Subjects = g.Select(e => e.Subject),
// Assuming they are identical when they have the same name
Role = g.First().Role
};
Try this:
var grouped = classes.GroupBy(g => new {Name = g.Name, Role = g.Role}).Select(
s =>
new
{
Name = s.Key,
Subjects = s.Select(x => x.Subject).Aggregate("", (current, se) => current + (", " + se)),
Role = s.Select(x => x.Role).First()
});
var result = grouped.Select(s => new
{
s.Name,
Subjects = s.Subjects.Substring(2),
s.Role
}).ToList();
This will put your subjects in a comma separated string.
Hope this helps.

LINQ Join With Multiple Where Clause

I am struggling once again so any help would be gratefully received.
I have the following LINQ that pulls back a list of data:
public static List<tblWeight> GetWeights(string memberid, string locationid, string buyer, string subcategory, string product)
{
MyEntity getweights = new MyEntity ();
var r = (from p in getweights.tblWeights
where p.MemberId == memberid &&
p.LocationId == locationid
select p);
if (buyer != "Not Specified")
r = r.Where(p => p.UnitUserField1 == buyer);
if (subcategory != "Not Specified")
r = r.Where(p => p.UnitUserField2 == subcategory);
if (product != "Not Specified")
r = r.Where(p => p.IDDesc == product);
return r.ToList();
}
Lovely!
What I would like to do now is based upon this result set and the unit IDs (IDDesc), I then go to tblPurchase, pull back a few columns from tblPurchases and group the columns.
So for example, we have tblWeight looking like so:
MemberID LocationID Buyer SubCategory IDDesc
1 1 Cat1 Sub1 ab
1 1 Cat1 Sub1 abc
1 1 Cat1 Sub2 abcd
The user makes a search for Sub1 in subcategory and the above LINQ does the trick and pulls back the first two rows from above. Fine.
What I need the LINQ to do now is to go to tblPurchases:
MemberID LocationID IDDesc SupplierID SupplierStatus
1 1 ab Sup1 Live
1 1 abc Sup1 Live
1 1 abcd Sup2 Dead
And then pull back the following result so it is joined on MemberID, LocationID and IDDesc but just selects tblPurchases.
Sup1 Live (or all columns in tblPurchases, just grouped/distinct)
I have tried to add in a join and on but no matter how many different variations, I still come across the red squiggle of doom!!!
If anyone can help, beer/kiss is on offer again.
The following LINQ query should do what you want:
var result = from w in tblWeight
where w.SubCategory == "Sub1"
join p in tblPurchases on
new { w.MemberID, w.LocationID, w.IDDesc } equals
new { p.MemberID, p.LocationID, p.IDDesc }
group p by new { p.SupplierID, p.SupplierStatus } into pg
select pg.Key;
The variable result is a list containing tuples of SupplierID and SupplierStatus.
If you also want to put the conditional parts in there, it gets a little more complicated. Here's how to do it:
var weights = from w in tblWeight
select w;
weights = weights.Where(w => w.SubCategory == "Sub1");
// You can add additional where clauses here.
// Now join with tblPurchases and group by SupplierID and SupplierStatus.
var result =
weights.Join(tblPurchases,
w => new { w.MemberID, w.LocationID, w.IDDesc },
p => new { p.MemberID, p.LocationID, p.IDDesc },
(w, p) => p)
.GroupBy(p => new { p.SupplierID, p.SupplierStatus },
(k, ps) => new
{
k.SupplierID,
k.SupplierStatus,
TotalQty = ps.Sum(p => p.PurchaseQty)
});

How to access grouped values returned by a linq query

I've got the following code:
List<Person> people = new List<Person>
{
new Person{ Id = 1, Name = "Bob"},
new Person{ Id = 2, Name = "Joe"},
new Person{ Id = 3, Name = "Bob"}
};
var peopleGroupedByName = from p in people
group p by p.Name;
//get all groups where the number of people in the group is > 1
For the life of me I can't figure out how to work with the values returned by the linq query to be able to then filter all of the groups that were returned so that I only have the groups that have more than one item in them.
At the moment I'm banging my head against a wall and I can't quite think of what keywords to use in a google search in order to figure it out for myself.
I'd really appreciate any help on how to do this in Linq cause it seems like it should be very simple to do.
List<Person> people = new List<Person> {
new Person{ Id = 1, Name = "Bob"},
new Person{ Id = 2, Name = "Joe"},
new Person{ Id = 3, Name = "Bob"}
};
var peopleGroupedByName = from p in people
group p by p.Name into peopleGroup
where peopleGroup.Count() > 1
select peopleGroup;
//get all groups where the number of people in the group is > 1
Alternatively, where peopleGroup.Skip(1).Any() as Mehrdad has suggested will generally provide better performance with Linq to Objects since Count() iterates over the entire group's contents, and Skip(1).Any() merely over the first 2 elements - (see his comment for details; Count is fine for group-by clauses).
Aside: For readability, I prefer consistently using either the .GroupBy(... extension method syntax or the group ... by ... into ... query syntax but not both.
var peopleGroupedByName = people.GroupBy(p => p.Name)
.Where(g => g.Count() > 1);
var peopleGroupedByName = from p in people
group p by p.Name into g
where g.Count() > 1
select g;
This is actually quite easy.
var filtererGroups = people
.GroupBy(p => p.Name)
.Where(grp => grp.Count() > 1);
To filter by key you would do something like that.
var filtererGroups = people
.GroupBy(p => p.Name)
.Where(grp => grp.Key == "Bob");

Resources