Reputation: 3434
I have a class called Person, with 2 properties Age and Name
I am trying to group the people into 3 different age groups.
I want my end result to be something like this:
YoungGroup (<20 y old):
Person1Name, Age
Person4Name, Age
Person3Name, Age
AdultGroup (20> y <40 old):
Person2Name, Age
Person7Name, Age
Person5Name, Age
SeniorGroup (40> y old):
Person6Name, Age
Person8Name, Age
Person9Name, Age
I want to do it with group clause only (no join, if its even possible?)
This is what I have tried
var multiplegrps = from p in people
group p by p.Age into groups
let age = groups.Key
select new
{
YoungGroup = from p in groups
where age <= 20
group p by p.Age,
AdultGroup = from p in groups
where age >= 21 && age <= 40
group p by p.Age,
SeniorGroup = from p in groups
where age >= 41
group p by p.Age
};
Either it worked, or I dont actually know how to print it:
foreach (var item in multiplegrps)
{
foreach (var i in item.YoungGroup)
{
Console.WriteLine($"{i.Key}");
}
}
I do get the correct keys, but I have no access to the names, and I would have to write 3 internal foreachs for each of the groups
P.S. I want to do it with pure query only, no lambdas no new extension methods
P.S. I solved it and got the idea from @khoroshevj about the use of multiple ternary operators, it was as simple as:
var peopleMultiGrouping = from p in people
let ageSelection =
p.Age < 20
? "Young"
: p.Age >= 20 && p.Age <= 40
? "Adult"
: "Senior"
group p by ageSelection;
foreach (var p in peopleMultiGrouping)
{
Console.WriteLine($"{p.Key}");
foreach (var i in p)
{
Console.WriteLine($" {i.Age}");
}
}
Upvotes: 0
Views: 146
Reputation: 729
Have you tried something like :
var multiplegrps = people.GroupBy(x=> x.age)
.ToDictionary(x=>x.Key,
x=>x.Select(y=>new {
YoungGroup=y.where(z=>z.age<= 20),
AdultGroup=y.Where(z=>z.age >=21 && a.age <= 40),
SeniorGroup=y.Where(z=> z.age >=41)
});
This is just an idea, did not tried it...
Upvotes: 0
Reputation: 3434
I solved it and got the idea from @khoroshevj about the use of multiple ternary operators, it was as simple as:
var peopleMultiGrouping = from p in people
let ageSelection =
p.Age < 20
? "Young"
: p.Age >= 20 && p.Age <= 40
? "Adult"
: "Senior"
group p by ageSelection;
foreach (var p in peopleMultiGrouping)
{
Console.WriteLine($"{p.Key}");
foreach (var i in p)
{
Console.WriteLine($" {i.Age}");
}
}
Upvotes: 0
Reputation: 111
----------
foreach (var item in multiplegrps)
{
foreach (var yItem in item.YoungGroup)
{
if (item.YoungGroup.Count() > 0 && yItem.ElementAt(0) != null)
{
Console.WriteLine("Young : " + yItem.ElementAt(0).Name);
}
}
foreach (var aItem in item.AdultGroup)
{
if (item.AdultGroup.Count() > 0 &&aItem.ElementAt(0) != null)
{
Console.WriteLine("Adult : " + aItem.ElementAt(0).Name);
}
}
foreach (var sItem in item.SeniorGroup)
{
if (item.SeniorGroup.Count() > 0 &&sItem.ElementAt(0) != null)
{
Console.WriteLine("Senior : " + sItem.ElementAt(0).Name);
}
}
}
Upvotes: 0
Reputation: 107247
The final grouped output you seem to be looking for is 3 discrete collections of person, i.e. Young, Adult and Senior persons.
To me, it would make sense to classify each person once, since from a performance point of view, you also won't want to run through all people 3 times (once per group) - the groups are mutually exclusive, and each person can be classified once into the age group:
var classifiedPersons = people
.Select(p => new{
Person = p,
AgeClassification =
p.age <= 20
? "Young"
: p.age <= 40
? "Adult"
: "Senior"
})
.GroupBy(cp => cp.AgeClassification)
.ToDictionary(grp => grp.Key, grp => grp.Select(g => g.Person));
(You could use other classifications other than a string, such as enumeration here too)
You can now access the three groups via the "Young", "Adult" and "Senior" keys.
You can print out the groups generically like so:
foreach (var group in classifiedPersons)
{
Console.WriteLine($"Group: {group.Key}:");
foreach (var person in group.Value)
{
Console.WriteLine($" - {person.Name}:");
}
}
Seemingly you wanted a single anonymous object containing all 3 groups, such as:
var allGroupsInOneObject = new {
YoungPeople = classifiedPersons["Young"],
AdultPeople = classifiedPersons["Adult"],
SeniorPeople = classifiedPersons["Senior"]
};
One Caveat - before accessing the groups via the Dictionary Key, you might just need to ensure that there are any persons present in the group.
Upvotes: 0
Reputation: 1137
You can do something like this
var multiplegrps =
from p in people
let groupName = GetGroupName(p.Age)
group p by groupName
into groups
group groups by groups.Key;
foreach (var group in multiplegrps)
{
foreach (var member in group.SelectMany(i => i))
{
Console.WriteLine($"{group.Key}, {member.Age}, {member.Name}");
}
}
private static string GetGroupName(int age)
{
return age <= 20
? "YoungGroup"
: age <= 40
? "AdultGroup"
: "SeniorGroup";
}
Upvotes: 1
Reputation: 56433
Your solution does indeed work as you expected. The issue is the way you're printing the result.
In the second level loop, i
is of type IGrouping<int, Person>
which basically represents a collection of objects that have a common key.
It clearly has the Key
property as you're accessing it, but it doesn't have a Value
property.
You can either perform another nested loop to iterate over the IGrouping
and then you'll have access to the full Person
properties, or if you want to only keep it at a two-level loop then you can flatten the item.YoungGroup
with SelectMany
into an IEnumerable<Person>
and iterate over it like this:
foreach (var item in multiplegrps)
{
foreach (var person in item.YoungGroup.SelectMany(x => x))
{
Console.WriteLine($"Name: {person.Name}, Age: {person.Age}");
}
}
Upvotes: 0