Let's suppose if we have a class like:
class Person {
internal int PersonID;
internal string car;
}
I have a list of this class: List<Person> persons;
And this list can have multiple instances with same PersonID
s, for example:
persons[0] = new Person { PersonID = 1, car = "Ferrari" };
persons[1] = new Person { PersonID = 1, car = "BMW" };
persons[2] = new Person { PersonID = 2, car = "Audi" };
Is there a way I can group by PersonID
and get the list of all the cars he has?
For example, the expected result would be
class Result {
int PersonID;
List<string> cars;
}
So after grouping, I would get:
results[0].PersonID = 1;
List<string> cars = results[0].cars;
result[1].PersonID = 2;
List<string> cars = result[1].cars;
From what I have done so far:
var results = from p in persons
group p by p.PersonID into g
select new { PersonID = g.Key, // this is where I am not sure what to do
Could someone please point me in the right direction?
Absolutely - you basically want:
var results = from p in persons
group p.car by p.PersonId into g
select new { PersonId = g.Key, Cars = g.ToList() };
Or as a non-query expression:
var results = persons.GroupBy(
p => p.PersonId,
p => p.car,
(key, g) => new { PersonId = key, Cars = g.ToList() });
Basically the contents of the group (when viewed as an IEnumerable<T>
) is a sequence of whatever values were in the projection (p.car
in this case) present for the given key.
For more on how GroupBy
works, see my Edulinq post on the topic.
(I've renamed PersonID
to PersonId
in the above, to follow .NET naming conventions.)
Alternatively, you could use a Lookup
:
var carsByPersonId = persons.ToLookup(p => p.PersonId, p => p.car);
You can then get the cars for each person very easily:
// This will be an empty sequence for any personId not in the lookup
var carsForPerson = carsByPersonId[personId];
var results = from p in persons
group p by p.PersonID into g
select new { PersonID = g.Key,
/**/car = g.Select(g=>g.car).FirstOrDefault()/**/}
You can also Try this:
var results= persons.GroupBy(n => new { n.PersonId, n.car})
.Select(g => new {
g.Key.PersonId,
g.Key.car)}).ToList();
var results = from p in persons
group p by p.PersonID into g
select new { PersonID = g.Key, Cars = g.Select(m => m.car) };
try
persons.GroupBy(x => x.PersonId).Select(x => x)
or
to check if any person is repeating in your list try
persons.GroupBy(x => x.PersonId).Where(x => x.Count() > 1).Any(x => x)
I have created a working code sample with Query Syntax and Method Syntax. I hope it helps the others :)
You can also run the code on .Net Fiddle here:
using System;
using System.Linq;
using System.Collections.Generic;
class Person
{
public int PersonId;
public string car ;
}
class Result
{
public int PersonId;
public List<string> Cars;
}
public class Program
{
public static void Main()
{
List<Person> persons = new List<Person>()
{
new Person { PersonId = 1, car = "Ferrari" },
new Person { PersonId = 1, car = "BMW" },
new Person { PersonId = 2, car = "Audi"}
};
//With Query Syntax
List<Result> results1 = (
from p in persons
group p by p.PersonId into g
select new Result()
{
PersonId = g.Key,
Cars = g.Select(c => c.car).ToList()
}
).ToList();
foreach (Result item in results1)
{
Console.WriteLine(item.PersonId);
foreach(string car in item.Cars)
{
Console.WriteLine(car);
}
}
Console.WriteLine("-----------");
//Method Syntax
List<Result> results2 = persons
.GroupBy(p => p.PersonId,
(k, c) => new Result()
{
PersonId = k,
Cars = c.Select(cs => cs.car).ToList()
}
).ToList();
foreach (Result item in results2)
{
Console.WriteLine(item.PersonId);
foreach(string car in item.Cars)
{
Console.WriteLine(car);
}
}
}
}
Here is the result:
1 Ferrari BMW 2 Audi ----------- 1 Ferrari BMW 2 Audi
First, set your key field. Then include your other fields:
var results =
persons
.GroupBy(n => n.PersonId)
.Select(r => new Result {PersonID = r.Key, Cars = r.ToList() })
.ToList()
Try this :
var results= persons.GroupBy(n => n.PersonId)
.Select(g => new {
PersonId=g.Key,
Cars=g.Select(p=>p.car).ToList())}).ToList();
But performance-wise the following practice is better and more optimized in memory usage (when our array contains much more items like millions):
var carDic=new Dictionary<int,List<string>>();
for(int i=0;i<persons.length;i++)
{
var person=persons[i];
if(carDic.ContainsKey(person.PersonId))
{
carDic[person.PersonId].Add(person.car);
}
else
{
carDic[person.PersonId]=new List<string>(){person.car};
}
}
//returns the list of cars for PersonId 1
var carList=carDic[1];
g.Key.PersonId
? g.SelectMany
?? You clearly didn't try this.
SortedDictionary <PersonIdInt, SortedDictionary <CarNameString, CarInfoClass>>
. The closest I could get using LINQ was IEnumerable <IGrouping <PersonIdInt, Dictionary <CarNameString, PersonIdCarNameXrefClass>>>
. I ended using your for
loop method which, btw, was 2x faster. Also, I would use: a) foreach
vs. for
and b) TryGetValue
vs. ContainsKey
(both for DRY principle - in code & runtime).
The following example uses the GroupBy method to return objects that are grouped by PersonID
.
var results = persons.GroupBy(x => x.PersonID)
.Select(x => (PersonID: x.Key, Cars: x.Select(p => p.car).ToList())
).ToList();
Or
var results = persons.GroupBy(
person => person.PersonID,
(key, groupPerson) => (PersonID: key, Cars: groupPerson.Select(x => x.car).ToList()));
Or
var results = from person in persons
group person by person.PersonID into groupPerson
select (PersonID: groupPerson.Key, Cars: groupPerson.Select(x => x.car).ToList());
Or you can use ToLookup
, Basically ToLookup
uses EqualityComparer<TKey>
.Default to compare keys and do what you should do manually when using group by and to dictionary. i think it's excuted inmemory
ILookup<int, string> results = persons.ToLookup(
person => person.PersonID,
person => person.car);
An alternative way to do this could be select distinct PersonId
and group join with persons
:
var result =
from id in persons.Select(x => x.PersonId).Distinct()
join p2 in persons on id equals p2.PersonId into gr // apply group join here
select new
{
PersonId = id,
Cars = gr.Select(x => x.Car).ToList(),
};
Or the same with fluent API syntax:
var result = persons.Select(x => x.PersonId).Distinct()
.GroupJoin(persons, id => id, p => p.PersonId, (id, gr) => new
{
PersonId = id,
Cars = gr.Select(x => x.Car).ToList(),
});
GroupJoin produces a list of entries in the first list ( list of PersonId
in our case), each with a group of joined entries in the second list (list of persons
).
Success story sharing
.GroupBy(p => new {p.Id, p.Name}, p => p, (key, g) => new { PersonId = key.Id, PersonName = key.Name, PersonCount = g.Count()})
and you will get all the people that occur with an Id, Name, and a number of occurrences for each person.