Dikelompokkan oleh dalam LINQ

1063

Misalkan jika kita memiliki kelas seperti:

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

Sekarang saya memiliki daftar kelas ini: List<Person> persons;

Sekarang daftar ini dapat memiliki beberapa instance dengan yang sama PersonID, misalnya:

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

Apakah ada cara saya dapat mengelompokkan berdasarkan PersonIDdan mendapatkan daftar semua mobil yang dimilikinya?

Misalnya, hasil yang diharapkan adalah

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

Jadi setelah pengelompokan, saya akan mendapatkan:

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

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

Dari apa yang telah saya lakukan sejauh ini:

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

Bisakah seseorang tolong tunjukkan saya ke arah yang benar?

test123
sumber
1
Ada contoh lain termasuk Countdan di Sumsini stackoverflow.com/questions/3414080/…
Pengembang
@ Martin Källman: Saya setuju dengan Chris Walsh. Kemungkinan besar sebuah aplikasi yang memiliki Kelas (Tabel) "Orang (s)" OP sudah memiliki "(normal)" "Kelas" (Orang) "yang memiliki usu. Properti / Kolom (yaitu nama, jenis kelamin, DOB). "Kelas (Tabel)" OP (Tabel) akan menjadi Kelas Anak (Tabel) dari Kelas (Tabel) "Tabel" (")" (normal) "(yaitu)" (normal) "Kelas (Tabel) ) vs. Kelas "Tabel" (Pesanan) OP kemungkinan tidak menggunakan nama sebenarnya yang akan ia gunakan jika berada dalam Lingkup yang sama dengan Kelas (Tabel) "(normal)" "dan / atau mungkin disederhanakan untuk posting ini.
Tom

Jawaban:

1735

Tentu - Anda pada dasarnya ingin:

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

Atau sebagai ekspresi non-permintaan:

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

Pada dasarnya isi grup (bila dilihat sebagai IEnumerable<T>) adalah urutan nilai apa pun yang ada dalam proyeksi ( p.cardalam hal ini) hadir untuk kunci yang diberikan.

Untuk lebih lanjut tentang cara GroupBykerjanya, lihat posting Edulinq saya pada topik .

(Saya telah berganti nama PersonIDmenjadi PersonIddi atas, untuk mengikuti konvensi penamaan .NET )

Atau, Anda dapat menggunakan Lookup:

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

Anda kemudian dapat memperoleh mobil untuk setiap orang dengan sangat mudah:

// This will be an empty sequence for any personId not in the lookup
var carsForPerson = carsByPersonId[personId];
Jon Skeet
sumber
11
@jon Skeet bagaimana jika saya ingin menambahkan properti lain seperti nama
user123456
22
@Mohammad: Kemudian Anda memasukkannya ke dalam jenis anonim.
Jon Skeet
7
@ user123456 inilah penjelasan yang baik tentang grup oleh, juga termasuk contoh pengelompokan dengan kunci komposit: Cara: Hasil Kueri Grup (Panduan Pemrograman C #)
Mathieu Diepman
14
@Mohammad Anda dapat melakukan sesuatu seperti .GroupBy(p => new {p.Id, p.Name}, p => p, (key, g) => new { PersonId = key.Id, PersonName = key.Name, PersonCount = g.Count()})dan Anda akan mendapatkan semua orang yang muncul dengan Id, Nama, dan sejumlah kejadian untuk setiap orang.
Chris
11
@ame: Saya sengaja mengikuti konvensi penamaan .NET, memperbaiki nama OP, pada dasarnya. Akan memperjelas jawabannya.
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
sumber
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
sumber
32

Anda juga dapat Coba ini:

var results= persons.GroupBy(n => new { n.PersonId, n.car})
                .Select(g => new {
                               g.Key.PersonId,
                               g.Key.car)}).ToList();
shuvo sarker
sumber
Itu salah mengembalikan daftar yang sama tidak dikelompokkan oleh
Vikas Gupta
Itu bekerja, saya pikir ada sesuatu yang hilang itu sebabnya itu tidak berfungsi dalam kode Anda.
shuvo sarker
29

mencoba

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

atau

untuk memeriksa apakah ada orang yang mengulang dalam daftar Anda, cobalah

persons.GroupBy(x => x.PersonId).Where(x => x.Count() > 1).Any(x => x)
Kode Pertama
sumber
13

Saya telah membuat contoh kode kerja dengan Query Syntax dan Method Syntax. Saya harap ini membantu yang lain :)

Anda juga dapat menjalankan kode pada .Net Fiddle di sini:

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

Inilah hasilnya:

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

Terima Yildiz
sumber
Tolong, jelaskan apa yang kode Anda lakukan demi. Ini hanya jawaban kode yang hampir merupakan jawaban yang salah.
V.7
2

Coba ini :

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

Namun berdasarkan kinerja, praktik berikut ini lebih baik dan lebih dioptimalkan dalam penggunaan memori (ketika array kami berisi lebih banyak item seperti jutaan):

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
sumber
4
g.Key.PersonId? g.SelectMany?? Anda jelas tidak mencoba ini.
Gert Arnold
Anda menulis saya mengedit beberapa kode kode di dalamnya dan tidak mengujinya. Poin utama saya adalah bagian kedua. Tapi bagaimanapun, terima kasih atas pertimbangan Anda. Sudah terlambat untuk mengedit kode itu ketika saya menyadari itu salah. jadi g.Key menggantikan g.Key.PersonId, dan Pilih daripada SelectMany! jadi berantakan maaf :)))
akazemis
2
@akazemis: Saya sebenarnya mencoba membuat (menggunakan istilah yang setara dengan domain OP) SortedDictionary <PersonIdInt, SortedDictionary <CarNameString, CarInfoClass>>. Yang paling dekat saya bisa menggunakan LINQ adalah IEnumerable <IGrouping <PersonIdInt, Dictionary <CarNameString, PersonIdCarNameXrefClass>>>. Saya berakhir menggunakan formetode loop Anda yang, btw, 2x lebih cepat. Juga, saya akan menggunakan: a) foreachvs. fordan b) TryGetValuevs. ContainsKey(keduanya untuk prinsip KERING - dalam kode & runtime).
Tom
1

Cara alternatif untuk melakukan ini dapat memilih PersonIdgrup berbeda dan bergabung dengan 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(),
    };

Atau sama dengan sintaks API yang lancar:

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 menghasilkan daftar entri dalam daftar pertama (daftar PersonIddalam kasus kami), masing-masing dengan sekelompok entri yang tergabung dalam daftar kedua (daftar persons).

Dmitry Stepanov
sumber
1

Contoh berikut menggunakan metode GroupBy untuk mengembalikan objek yang dikelompokkan berdasarkan PersonID.

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

Atau

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

Atau

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

Atau Anda dapat menggunakan ToLookup, Pada dasarnya ToLookupmenggunakan. EqualityComparer<TKey>Kesalahan untuk membandingkan kunci dan melakukan apa yang harus Anda lakukan secara manual saat menggunakan grup oleh dan ke kamus. Saya pikir itu dikecam di memori

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

Pertama, setel bidang kunci Anda. Kemudian sertakan bidang Anda yang lain:

var results = 
    persons
    .GroupBy(n => n.PersonId)
    .Select(r => new Result {PersonID = r.Key, Cars = r.ToList() })
    .ToList()
pengguna3474287
sumber
Jawaban / kode yang tidak dikomentari bukanlah cara kerja StackOverflow ...
V.7