Gruppieren nach in LINQ

1063

Nehmen wir an, wir haben eine Klasse wie:

class Person { 
    internal int PersonID; 
    internal string car; 
}

Jetzt habe ich eine Liste dieser Klasse: List<Person> persons;

Jetzt kann diese Liste mehrere Instanzen mit denselben haben PersonID, zum Beispiel:

persons[0] = new Person { PersonID = 1, car = "Ferrari" }; 
persons[1] = new Person { PersonID = 1, car = "BMW"     }; 
persons[2] = new Person { PersonID = 2, car = "Audi"    }; 

Gibt es eine Möglichkeit, nach der ich gruppieren PersonIDund die Liste aller Autos abrufen kann, die er hat?

Zum Beispiel wäre das erwartete Ergebnis

class Result { 
   int PersonID;
   List<string> cars; 
}

Nach der Gruppierung würde ich also bekommen:

results[0].PersonID = 1; 
List<string> cars = results[0].cars; 

result[1].PersonID = 2; 
List<string> cars = result[1].cars;

Nach dem, was ich bisher getan habe:

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

Könnte mich bitte jemand in die richtige Richtung weisen?

test123
quelle
1
Es gibt ein anderes Beispiel einschließlich Countund Sumhier stackoverflow.com/questions/3414080/…
Entwickler
@ Martin Källman: Ich stimme Chris Walsh zu. Höchstwahrscheinlich hat eine App mit der OP-Klasse "Person (s)" (Tabelle) bereits eine "'normale'" "Person (en)" -Klasse (Tabelle) mit dem Usu. Eigenschaften / Spalten (dh Name, Geschlecht, Geburtsdatum). Die "Person (en)" -Klasse (Tabelle) des OP wäre wahrscheinlich eine untergeordnete Klasse (Tabelle) der "normalen" "Person (en)" -Klasse (Tabelle) (dh eine "OrderItem (s)" -Klasse (Tabelle) ) gegen eine "Order (s)" -Klasse (Tabelle)). Das OP verwendete wahrscheinlich nicht den tatsächlichen Namen, den er verwenden würde, wenn er sich im selben Bereich wie seine "normale" "Person (en)" -Klasse (Tabelle) befand, und / oder hat ihn möglicherweise für diesen Beitrag vereinfacht.
Tom

Antworten:

1734

Absolut - Sie wollen im Grunde:

var results = from p in persons
              group p.car by p.PersonId into g
              select new { PersonId = g.Key, Cars = g.ToList() };

Oder als Ausdruck ohne Abfrage:

var results = persons.GroupBy(
    p => p.PersonId, 
    p => p.car,
    (key, g) => new { PersonId = key, Cars = g.ToList() });

Grundsätzlich ist der Inhalt der Gruppe (wenn als betrachtet IEnumerable<T>) eine Folge von Werten, die in der Projektion ( p.carin diesem Fall) für den angegebenen Schlüssel vorhanden waren.

Weitere Informationen zur Funktionsweise GroupByfinden Sie in meinem Edulinq-Beitrag zum Thema .

(Ich habe umbenannt , PersonIDum PersonIdin den oben genannten, zu folgen .NET - Namenskonventionen .)

Alternativ können Sie Folgendes verwenden Lookup:

var carsByPersonId = persons.ToLookup(p => p.PersonId, p => p.car);

Sie können dann ganz einfach die Autos für jede Person bekommen:

// This will be an empty sequence for any personId not in the lookup
var carsForPerson = carsByPersonId[personId];
Jon Skeet
quelle
11
@ Jon Skeet was ist, wenn ich eine andere Eigenschaft wie Name hinzufügen möchte
user123456
22
@Mohammad: Dann fügst du das in den anonymen Typ ein.
Jon Skeet
7
@ user123456 Hier ist eine gute Erklärung für die Gruppierung nach, es enthält auch ein Beispiel für die Gruppierung nach einem zusammengesetzten Schlüssel:
Gewusst
14
@Mohammad Sie können so etwas tun .GroupBy(p => new {p.Id, p.Name}, p => p, (key, g) => new { PersonId = key.Id, PersonName = key.Name, PersonCount = g.Count()})und Sie erhalten alle Personen, die mit einer ID, einem Namen und einer Anzahl von Vorkommen für jede Person auftreten.
Chris
11
@kame: Ich habe absichtlich die .NET-Namenskonventionen befolgt und im Grunde die Namen des OP korrigiert. Wird das in der Antwort deutlich machen.
Jon Skeet
52
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()/**/}
Tallat
quelle
37
var results = from p in persons
              group p by p.PersonID into g
              select new { PersonID = g.Key, Cars = g.Select(m => m.car) };
Yogendra Paudyal
quelle
32

Sie können dies auch versuchen:

var results= persons.GroupBy(n => new { n.PersonId, n.car})
                .Select(g => new {
                               g.Key.PersonId,
                               g.Key.car)}).ToList();
Shuvo Sarker
quelle
Es ist falsch zurückzugeben die gleiche Liste gruppiert nicht nach
Vikas Gupta
Es funktioniert, ich denke, etwas fehlt, deshalb funktioniert es in Ihrem Code nicht.
Shuvo Sarker
29

Versuchen

persons.GroupBy(x => x.PersonId).Select(x => x)

oder

Um zu überprüfen, ob sich eine Person in Ihrer Liste wiederholt, versuchen Sie es

persons.GroupBy(x => x.PersonId).Where(x => x.Count() > 1).Any(x => x)
Code zuerst
quelle
13

Ich habe ein funktionierendes Codebeispiel mit Abfragesyntax und Methodensyntax erstellt. Ich hoffe es hilft den anderen :)

Sie können den Code auch auf .Net Fiddle hier ausführen:

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);
            }
        }
    }
}

Hier ist das Ergebnis:

1
Ferrari
BMW
2
Audi
-----------
1
Ferrari
BMW
2
Audi

Empfange Yildiz
quelle
Bitte erläutern Sie, was Ihr Code tut. Dies ist nur eine Code-Antwort, die fast eine falsche Antwort ist.
V.7
2

Versuche dies :

var results= persons.GroupBy(n => n.PersonId)
            .Select(g => new {
                           PersonId=g.Key,
                           Cars=g.Select(p=>p.car).ToList())}).ToList();

In Bezug auf die Leistung ist die folgende Vorgehensweise bei der Speichernutzung besser und optimierter (wenn unser Array viel mehr Elemente wie Millionen enthält):

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];
Akazemis
quelle
4
g.Key.PersonId? g.SelectMany?? Sie haben das offensichtlich nicht versucht.
Gert Arnold
Du schreibst Ich habe einige Codes Codes darin bearbeitet und es nicht getestet. Mein Hauptpunkt war der zweite Teil. Trotzdem danke für Ihre Überlegung. Es war zu spät, diesen Code zu bearbeiten, als mir klar wurde, dass er falsch ist. Also ersetzt g.Key g.Key.PersonId und Select anstelle von SelectMany! so chaotisch sorry :)))
Akazemis
2
@akazemis: Ich habe tatsächlich versucht zu erstellen (um Begriffe zu verwenden, die der Domain von OP entsprechen) SortedDictionary <PersonIdInt, SortedDictionary <CarNameString, CarInfoClass>>. Das Beste, was ich mit LINQ erreichen konnte, war IEnumerable <IGrouping <PersonIdInt, Dictionary <CarNameString, PersonIdCarNameXrefClass>>>. Ich habe Ihre forLoop-Methode beendet, die übrigens 2x schneller war. Außerdem würde ich verwenden: a) foreachvs. forund b) TryGetValuevs. ContainsKey(beide für das DRY-Prinzip - in Code & Laufzeit).
Tom
1

Eine alternative Möglichkeit, dies zu tun, könnte darin bestehen, eine bestimmte PersonIdGruppe auszuwählen und sich einer Gruppe anzuschließen mit 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(),
    };

Oder dasselbe mit fließender 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 erstellt eine Liste von Einträgen in der ersten Liste ( PersonIdin unserem Fall Liste von ) mit jeweils einer Gruppe von verbundenen Einträgen in der zweiten Liste (Liste von)persons ).

Dmitry Stepanov
quelle
1

Im folgenden Beispiel wird die GroupBy-Methode verwendet, um Objekte zurückzugeben, nach denen gruppiert ist PersonID.

var results = persons.GroupBy(x => x.PersonID)
              .Select(x => (PersonID: x.Key, Cars: x.Select(p => p.car).ToList())
              ).ToList();

Oder

 var results = persons.GroupBy(
               person => person.PersonID,
               (key, groupPerson) => (PersonID: key, Cars: groupPerson.Select(x => x.car).ToList()));

Oder

 var results = from person in persons
               group person by person.PersonID into groupPerson
               select (PersonID: groupPerson.Key, Cars: groupPerson.Select(x => x.car).ToList());

Oder Sie können verwenden ToLookup. Grundsätzlich ToLookupverwendet EqualityComparer<TKey>.Default, um Schlüssel zu vergleichen und das zu tun, was Sie manuell tun sollten, wenn Sie group by und to dictionary verwenden. Ich denke, es ist in Erinnerung

 ILookup<int, string> results = persons.ToLookup(
            person => person.PersonID,
            person => person.car);
Reza Jenabi
quelle
0

Stellen Sie zunächst Ihr Schlüsselfeld ein. Fügen Sie dann Ihre anderen Felder hinzu:

var results = 
    persons
    .GroupBy(n => n.PersonId)
    .Select(r => new Result {PersonID = r.Key, Cars = r.ToList() })
    .ToList()
user3474287
quelle
Nicht kommentierte Antwort / Code ist nicht die Art und Weise, wie StackOverflow funktioniert ...
V.7