Gruplandırma ölçütü: LINQ


1061

Diyelim ki bir sınıfımız varsa:

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

Şimdi bu sınıfın bir listesi var: List<Person> persons;

Şimdi bu listede aynı PersonID'lara sahip birden fazla örnek olabilir , örneğin:

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

Gruplandırıp sahip olduğu PersonIDtüm arabaların listesini almamın bir yolu var mı?

Örneğin, beklenen sonuç

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

Böylece gruplandırdıktan sonra şunu elde ederim:

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

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

Şimdiye kadar yaptığımdan:

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

Birisi beni doğru yöne yönlendirebilir mi?


1
Dahil Countve Sumburada başka bir örnek var stackoverflow.com/questions/3414080/…
Geliştirici

@ Martin Källman: Chris Walsh ile aynı fikirdeyim. Büyük olasılıkla OP'nin "Kişi (ler)" Sınıfı (Tablo) olan bir uygulama zaten usu'ya sahip "normal" "" Kişi (ler) "Sınıfı (Tablo) içerecektir. Özellikler / Sütunlar (yani ad, cinsiyet, DOB). OP'nin "Kişi (ler)" Sınıfı (Tablo) prolly "" normal "" "Kişi (ler)" Sınıf (Tablo) (yani bir "OrderItem (s)" Sınıfının bir Çocuk Sınıfı (Tablo) olacaktır. ) ve "Sipariş (ler)" Sınıfı (Tablo)). OP, "" normal "" "Kişi (ler)" Sınıfı (Tablo) ile aynı Kapsamdaysa kullanacağı gerçek adı kullanmıyordu ve / veya bu yazı için basitleştirmiş olabilir.
Tom

Yanıtlar:


1732

Kesinlikle - temel olarak şunu istersiniz:

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

Veya sorgu olmayan bir ifade olarak:

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

Temel olarak grubun içeriği (an olarak görüntülendiğinde IEnumerable<T>), p.carverilen anahtar için mevcut olan projeksiyonda ( bu durumda) bulunan değerlerin bir sırasıdır .

Nasıl GroupByçalıştığı hakkında daha fazla bilgi için konuyla ilgili Edulinq gönderime bakın .

(Ben yeniden adlandırdık PersonIDiçin PersonIdtakip etmek, yukarıda .NET adlandırma kurallarını .)

Alternatif olarak, aşağıdakileri kullanabilirsiniz Lookup:

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

Daha sonra her kişi için arabaları çok kolay bir şekilde alabilirsiniz:

// This will be an empty sequence for any personId not in the lookup
var carsForPerson = carsByPersonId[personId];

11
@jon Skeet isim gibi başka bir özellik eklemek
istersem

22
@Mohammad: O zaman bunu isimsiz tipe dahil edersiniz.
Jon Skeet

7
@ user123456 İşte gruplandırma için iyi bir açıklama, ayrıca bir bileşik anahtarla gruplama örneği de içerir: Nasıl yapılır: Grup Sorgu Sonuçları (C # Programlama Kılavuzu)
Mathieu Diepman

14
@Mohammad gibi bir şey yapabilirsiniz .GroupBy(p => new {p.Id, p.Name}, p => p, (key, g) => new { PersonId = key.Id, PersonName = key.Name, PersonCount = g.Count()})ve bir Kimlik, İsim ve her kişi için bir dizi olay ile ortaya çıkan tüm insanları alacaksınız.
Chris

11
@kame: Kasıtlı olarak, OP adlarını düzelterek .NET adlandırma kurallarını izliyordum. Cevapta bunu netleştirecek.
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()/**/}

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

32

Bunu da deneyebilirsiniz:

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

Yanlış liste aynı grup tarafından gruplandırmıyor yanlış
Vikas Gupta

İşi, bir şey eksik olduğunu düşünüyorum bu yüzden kodunuzda çalışmıyor.
shuvo sarker

28

Deneyin

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

veya

listenizde herhangi bir kişinin tekrarlayıp tekrarlanmadığını kontrol etmek için

persons.GroupBy(x => x.PersonId).Where(x => x.Count() > 1).Any(x => x)

13

Ben Sorgu Sözdizimi ve Yöntem Sözdizimi ile bir çalışma kodu örneği oluşturduk. Umarım diğerlerine yardımcı olur :)

Ayrıca kodu .Net Fiddle üzerinde de çalıştırabilirsiniz:

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

İşte sonuç:

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


Lütfen, kodunuzun uğruna ne yaptığını açıklayın. Bu, sadece yanlış bir cevap olan bir kod cevabıdır.
V.7

2

Bunu dene :

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

Ancak performans açısından aşağıdaki uygulama bellek kullanımında daha iyi ve daha optimize edilmiştir (dizimiz milyonlarca gibi çok daha fazla öğe içerdiğinde):

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];

4
g.Key.PersonId? g.SelectMany?? Bunu açıkça denemedin.
Gert Arnold

yazıyorsunuz bazı kodları düzenledim ve test etmedim. Asıl mesele ikinci bölümdü. Ama yine de ilginiz için teşekkürler. Yanlış olduğunu fark ettiğimde bu kodu düzenlemek için çok geçti. g.Key, g.Key.PersonId ve SelectMany yerine Select'in yerini alır! çok dağınık üzgünüm :)))
akazemiş

2
@akazemis: Aslında yaratmaya çalışıyordum (OP'nin alanına eşdeğer terimler kullanmaya) SortedDictionary <PersonIdInt, SortedDictionary <CarNameString, CarInfoClass>>. LINQ kullanarak alabildiğim en yakın oldu IEnumerable <IGrouping <PersonIdInt, Dictionary <CarNameString, PersonIdCarNameXrefClass>>>. Ben forbtw, 2 kat daha hızlı olan döngü yöntemini kullanarak sona erdi . Ayrıca, kullanacağım: a) foreachvs. forve b) TryGetValuevs. ContainsKey(her ikisi de KURU prensibi için - kod ve çalışma zamanında).
Tom

1

Bunu yapmanın alternatif bir yolu, farklı PersonIdve grup katılımını seçmek olabilir 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(),
    };

Ya da akıcı API sözdizimi ile aynı:

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

GroupJoinPersonId , her biri ikinci listede (listesinin persons) birleştirilmiş girişleri içeren ilk listede ( bizim durumumuzun listesi) girişlerin bir listesini üretir .


1

Aşağıdaki örnek, gruplandırılan nesneleri döndürmek için GroupBy yöntemini kullanır PersonID.

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

Veya

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

Veya

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

Veya ToLookup, anahtarları karşılaştırmak ve grupla ve sözlüğe kullanırken manuel olarak yapmanız gerekenleri yapmak ToLookupiçin EqualityComparer<TKey>.Default öğesini kullanabilirsiniz. bence anıların hafızası

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

0

İlk olarak, anahtar alanınızı ayarlayın. Ardından diğer alanlarınızı ekleyin:

var results = 
    persons
    .GroupBy(n => n.PersonId)
    .Select(r => new Result {PersonID = r.Key, Cars = r.ToList() })
    .ToList()

Yorumlanmamış cevap / kod StackOverflow nasıl çalışır ...
V.7
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.