Nhóm trong LINQ


1063

Hãy giả sử nếu chúng ta có một lớp như:

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

Bây giờ tôi có một danh sách của lớp này: List<Person> persons;

Bây giờ danh sách này có thể có nhiều phiên bản với cùng một PersonIDví dụ:

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

Có cách nào để tôi có thể nhóm lại PersonIDvà lấy danh sách tất cả những chiếc xe anh ấy có không?

Ví dụ, kết quả mong đợi sẽ là

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

Vì vậy, sau khi nhóm, tôi sẽ nhận được:

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

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

Từ những gì tôi đã làm cho đến nay:

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

Làm ơn ai đó có thể chỉ cho tôi đúng hướng?


1
Có một ví dụ khác bao gồm CountSumở đây stackoverflow.com/questions/3414080/ Kẻ
Nhà phát triển

@Martin Källman: Tôi đồng ý với Chris Walsh. Rất có thể một ứng dụng có Lớp "Người (Bảng)" của OP sẽ có Lớp (Bảng) "Người bình thường" "(Người) có" usu. Thuộc tính / Cột (tức là tên, giới tính, DOB). Lớp "Người (Bảng)" của OP sẽ hoàn toàn là Lớp con (Bảng) của Lớp "Bảng" bình thường "" "Người (Bảng)" (tức là Lớp "OrderItem (s)" (Bảng) ) so với Lớp "Đặt hàng" (Bảng)). OP có khả năng không sử dụng tên thật mà anh ấy sử dụng nếu nó ở cùng Phạm vi với Lớp (Bảng) "Người bình thường" "và" hoặc có thể đơn giản hóa nó cho bài đăng này.
Tom

Câu trả lời:


1734

Hoàn toàn - về cơ bản bạn muốn:

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

Hoặc như một biểu thức không truy vấn:

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

Về cơ bản, nội dung của nhóm (khi được xem là một IEnumerable<T>) là một chuỗi gồm bất kỳ giá trị nào có trong phép chiếu ( p.cartrong trường hợp này) hiện diện cho khóa đã cho.

Để biết thêm về cách thức GroupByhoạt động, xem bài viết Edulinq của tôi về chủ đề này .

(Tôi đã đổi tên PersonIDđể PersonIdở trên, theo .NET quy ước đặt tên .)

Ngoài ra, bạn có thể sử dụng Lookup:

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

Sau đó, bạn có thể nhận được những chiếc xe cho mỗi người rất dễ dàng:

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

11
@jon Skeet nếu tôi muốn thêm một tài sản khác như tên
user123456

22
@Mohammad: Sau đó, bạn bao gồm nó trong loại ẩn danh.
Jon Skeet

7
@ user123456 đây là một lời giải thích tốt về nhóm bởi, nó cũng bao gồm một ví dụ về nhóm theo một khóa tổng hợp: Cách: Kết quả truy vấn nhóm (Hướng dẫn lập trình C #)
Mathieu Diepman

14
@Mohammad bạn có thể làm một cái gì đó giống như .GroupBy(p => new {p.Id, p.Name}, p => p, (key, g) => new { PersonId = key.Id, PersonName = key.Name, PersonCount = g.Count()})và bạn sẽ có được tất cả những người xảy ra với một Id, Tên và một số lần xuất hiện cho mỗi người.
Chris

11
@kame: Về cơ bản, tôi đã cố tình tuân theo các quy ước đặt tên .NET, sửa tên của OP. Sẽ làm rõ điều đó trong câu trả lời.
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

Bạn cũng có thể thử điều này:

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

Đó là sai trả về cùng một danh sách không nhóm bởi
Vikas Gupta

Nó hoạt động, tôi nghĩ thiếu một cái gì đó tại sao nó không hoạt động trong mã của bạn.
shuvo sarker

29

thử

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

hoặc là

để kiểm tra xem có ai đang lặp lại trong danh sách của bạn không

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

13

Tôi đã tạo một mẫu mã làm việc với Cú pháp truy vấn và Cú pháp phương thức. Tôi hy vọng nó sẽ giúp những người khác :)

Bạn cũng có thể chạy mã trên .Net Fiddle tại đây:

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

Đây là kết quả:

1
Ferrari
xe BMW
2
Xe ô tô
-----------
1
Ferrari
xe BMW
2
Xe ô tô


Xin vui lòng, giải thích những gì mã của bạn làm vì lợi ích. Đây chỉ là một câu trả lời mã gần như là một câu trả lời sai.
V.7

2

Thử cái này :

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

Nhưng hiệu năng thông minh, cách thực hành sau tốt hơn và được tối ưu hóa hơn trong việc sử dụng bộ nhớ (khi mảng của chúng tôi chứa nhiều mục hơn như hàng triệu):

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?? Bạn rõ ràng đã không thử điều này.
Gert Arnold

bạn viết tôi đã chỉnh sửa một số mã trong đó và không kiểm tra nó. Điểm chính của tôi là phần thứ hai. Nhưng dù sao cũng cảm ơn sự quan tâm của bạn. Đã quá muộn để chỉnh sửa mã khi tôi nhận ra nó sai. vì vậy g.Key thay thế g.Key.PersonId và Chọn thay vì ChọnMany! rất lộn xộn xin lỗi :)))
akazemis

2
@akazemis: Tôi thực sự đã cố gắng tạo (để sử dụng các thuật ngữ tương đương với tên miền của OP) SortedDictionary <PersonIdInt, SortedDictionary <CarNameString, CarInfoClass>>. Gần nhất tôi có thể sử dụng LINQ là IEnumerable <IGrouping <PersonIdInt, Dictionary <CarNameString, PersonIdCarNameXrefClass>>>. Tôi đã kết thúc bằng forphương pháp vòng lặp của bạn , btw, nhanh hơn gấp 2 lần. Ngoài ra, tôi sẽ sử dụng: a) foreachvs. forvà b) TryGetValuevs. ContainsKey(cả hai cho nguyên tắc DRY - trong mã & thời gian chạy).
Tom

1

Một cách khác để làm điều này có thể là chọn riêng biệt PersonIdvà tham gia nhóm với 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(),
    };

Hoặc tương tự với cú pháp API trôi chảy:

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 tạo ra một danh sách các mục trong danh sách đầu tiên (danh sách PersonIdtrong trường hợp của chúng tôi), mỗi mục có một nhóm các mục đã tham gia trong danh sách thứ hai (danh sách persons).


1

Ví dụ sau sử dụng phương thức GroupBy để trả về các đối tượng được nhóm theo PersonID.

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

Hoặc là

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

Hoặc là

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

Hoặc bạn có thể sử dụng ToLookup, Về cơ bản ToLookupsử dụng EqualityComparer<TKey>.Default để so sánh các khóa và làm những gì bạn nên làm thủ công khi sử dụng nhóm theo và từ điển. tôi nghĩ rằng nó đã bị loại bỏ

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

0

Đầu tiên, đặt trường chính của bạn. Sau đó bao gồm các lĩnh vực khác của bạn:

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

Câu trả lời / mã không nhận xét không phải là cách StackOverflow hoạt động ...
V.7
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.