2012-02-29 28 views
5

Tôi có từ điển cấu trúc, trong đó một thành viên là danh sách chứa các yếu tố khác nhau áp dụng cho từng mục từ điển.Truy vấn LINQ để tham gia vào danh sách trong cấu trúc

Tôi muốn kết hợp các yếu tố này với từng mục, để lọc chúng và/hoặc nhóm chúng theo phần tử.

Trong SQL, tôi quen với việc tham gia vào các bảng/truy vấn để có được nhiều hàng như mong muốn, nhưng tôi mới sử dụng C#/LINQ. Vì một "cột" có thể là một đối tượng/danh sách đã được liên kết với các mục từ điển thích hợp, tôi tự hỏi làm thế nào tôi có thể sử dụng chúng để thực hiện một phép nối?

Dưới đây là một ví dụ của cấu trúc:

name elements 
item1 list: elementA 
item2 list: elementA, elementB 

Tôi muốn một truy vấn cung cấp cho sản lượng này (count = 3)

name elements 
item1 elementA 
item2 elementA 
item2 elementB 

Đối cuối cùng, nhóm chúng như thế này:

Đây là mã của tôi bắt đầu đếm các mục từ điển.

public struct MyStruct 
    { 
     public string name; 
     public List<string> elements; 
    } 

    private void button1_Click(object sender, EventArgs e) 
    { 
     MyStruct myStruct = new MyStruct(); 
     Dictionary<String, MyStruct> dict = new Dictionary<string, MyStruct>(); 

     // Populate 2 items 
     myStruct.name = "item1"; 
     myStruct.elements = new List<string>(); 
     myStruct.elements.Add("elementA"); 
     dict.Add(myStruct.name, myStruct); 

     myStruct.name = "item2"; 
     myStruct.elements = new List<string>(); 
     myStruct.elements.Add("elementA"); 
     myStruct.elements.Add("elementB"); 
     dict.Add(myStruct.name, myStruct); 


     var q = from t in dict 
       select t; 

     MessageBox.Show(q.Count().ToString()); // Returns 2 
    } 

Chỉnh sửa: Tôi không thực sự cần đầu ra là từ điển. Tôi sử dụng nó để lưu trữ dữ liệu của tôi bởi vì nó hoạt động tốt và ngăn chặn các bản sao (tôi có item.name duy nhất mà tôi lưu trữ như là chìa khóa). Tuy nhiên, với mục đích lọc/nhóm, tôi đoán nó có thể là một danh sách hoặc mảng mà không có vấn đề gì. Tôi luôn có thể làm .ToDictionary nơi key = item.Name sau đó.

+0

@sinanakyazici câu hỏi không xác định rằng đầu ra phải được lưu trữ trong từ điển (và thực sự, khi bạn lưu ý chính xác, trong c chú thích). – phoog

+0

@phoog bạn nói đúng. Tôi hiểu lầm. Vì vậy, tôi đã xóa nhận xét của mình. – sinanakyazici

+0

@sinanakyazici vì lý do nào đó tôi không thể xóa (cũng không chỉnh sửa) bình luận của mình trên trình duyệt của điện thoại: ( – phoog

Trả lời

3
var q = from t in dict 
    from v in t.Value.elements 
    select new { name = t.Key, element = v }; 

Phương pháp ở đây là Enumerable.SelectMany. Sử dụng cú pháp phương pháp khuyến nông:

var q = dict.SelectMany(t => t.Value.elements.Select(v => new { name = t.Key, element = v })); 

EDIT

Lưu ý rằng bạn cũng có thể sử dụng t.Value.name trên, thay vì t.Key, vì những giá trị đều bình đẳng.

Vì vậy, những gì đang xảy ra ở đây?

Cú pháp truy vấn-hiểu có lẽ là dễ hiểu nhất; bạn có thể viết một khối lặp tương đương để xem những gì đang xảy ra. Chúng ta không thể làm điều đó chỉ đơn giản với một loại vô danh, tuy nhiên, vì vậy chúng tôi sẽ công bố một loại trở lại:

class NameElement 
{ 
    public string name { get; set; } 
    public string element { get; set; } 
} 
IEnumerable<NameElement> GetResults(Dictionary<string, MyStruct> dict) 
{ 
    foreach (KeyValuePair<string, MyStruct> t in dict) 
     foreach (string v in t.Value.elements) 
      yield return new NameElement { name = t.Key, element = v }; 
} 

Làm thế nào về cú pháp phương pháp khuyến nông (hoặc, là những gì thực sự xảy ra ở đây)?

(Điều này được lấy cảm hứng từ một phần qua đường bưu điện Eric Lippert tại https://stackoverflow.com/a/2704795/385844; Tôi đã có một lời giải thích phức tạp hơn nhiều, sau đó tôi đọc đó, và đến với điều này :)

Hãy nói rằng chúng ta muốn tránh khai báo NameElement kiểu. Chúng ta có thể sử dụng một kiểu ẩn danh bằng cách truyền vào một hàm.Chúng tôi muốn thay đổi cuộc gọi từ này:

var q = GetResults(dict); 

này:

var q = GetResults(dict, (string1, string2) => new { name = string1, element = string2 }); 

Biểu thức lambda (string1, string2) => new { name = string1, element = string2 } đại diện cho một chức năng mà mất 2 dây - được xác định bởi danh sách đối số (string1, string2) - và trả về một thể hiện của loại ẩn danh được khởi tạo bằng các chuỗi đó - được xác định bởi biểu thức new { name = string1, element = string2 }.

Việc thực hiện tương ứng là:

IEnumerable<T> GetResults<T>(
    IEnumerable<KeyValuePair<string, MyStruct>> pairs, 
    Func<string, string, T> resultSelector) 
{ 
    foreach (KeyValuePair<string, MyStruct> pair in pairs) 
     foreach (string e in pair.Value.elements) 
      yield return resultSelector.Invoke(t.Key, v); 
} 

Loại suy luận cho phép chúng ta gọi hàm này mà không chỉ định T theo tên. Đó là tiện dụng, bởi vì (theo như chúng ta biết là lập trình viên C#), kiểu chúng ta đang sử dụng không có tên: nó là vô danh.

Lưu ý rằng biến t tại là pair, để tránh nhầm lẫn với các tham số kiểu T, và v tại là e, vì "yếu tố". Chúng tôi cũng đã thay đổi loại tham số đầu tiên thành một trong các loại cơ sở của nó, IEnumerable<KeyValuePair<string, MyStruct>>. It's wordier, nhưng nó làm cho phương pháp hữu ích hơn, và nó sẽ rất hữu ích cuối cùng. Vì loại không còn là loại từ điển, chúng tôi cũng đã thay đổi tên của thông số từ dict thành pairs.

Chúng tôi có thể khái quát hóa điều này thêm. Thứ hai foreach có tác dụng chiếu cặp khóa-giá trị vào một chuỗi kiểu T. Toàn bộ hiệu ứng đó có thể được đóng gói trong một hàm duy nhất; loại đại biểu sẽ là Func<KeyValuePair<string, MyStruct>, T>. Bước đầu tiên là để cấu trúc lại phương pháp này vì vậy chúng tôi có một tuyên bố đơn có thể chuyển đổi các yếu tố pair vào một chuỗi, bằng cách sử dụng phương pháp Select để gọi resultSelector đại biểu:

IEnumerable<T> GetResults<T>(
    IEnumerable<KeyValuePair<string, MyStruct>> pairs, 
    Func<string, string, T> resultSelector) 
{ 
    foreach (KeyValuePair<string, MyStruct> pair in pairs) 
     foreach (T result in pair.Value.elements.Select(e => resultSelector.Invoke(pair.Key, e)) 
      yield return result; 
} 

Bây giờ chúng ta có thể dễ dàng thay đổi chữ ký:

IEnumerable<T> GetResults<T>(
    IEnumerable<KeyValuePair<string, MyStruct>> pairs, 
    Func<KeyValuePair<string, MyStruct>, IEnumerable<T>> resultSelector) 
{ 
    foreach (KeyValuePair<string, MyStruct> pair in pairs) 
     foreach (T result in resultSelector.Invoke(pair)) 
      yield return result; 
} 

Trang web gọi bây giờ trông giống như sau; nhận thấy như thế nào biểu thức lambda hiện nay kết hợp logic mà chúng ta loại bỏ ra khỏi cơ thể phương pháp khi chúng ta thay đổi chữ ký của mình:

var q = GetResults(dict, pair => pair.Value.elements.Select(e => new { name = pair.Key, element = e })); 

Để thực hiện phương pháp này hữu dụng hơn (và thực hiện nó ít tiết), chúng ta hãy thay thế các loại KeyValuePair<string, MyStruct> với một nhập thông số, TSource. Chúng tôi sẽ thay đổi một số tên khác cùng một lúc:

T  -> TResult 
pairs -> sourceSequence 
pair -> sourceElement 

Và, chỉ cần cho đá, chúng tôi sẽ làm cho nó trở thành một phương pháp khuyến nông:

static IEnumerable<TResult> GetResults<TSource, TResult>(
    this IEnumerable<TSource> sourceSequence, 
    Func<TSource, IEnumerable<TResult>> resultSelector) 
{ 
    foreach (TSource sourceElement in sourceSequence) 
     foreach (T result in resultSelector.Invoke(pair)) 
      yield return result; 
} 

Và có bạn có nó: SelectMany! Vâng, chức năng vẫn còn có tên sai, và việc thực hiện thực tế bao gồm xác nhận rằng chuỗi nguồn và chức năng chọn là không null, nhưng đó là logic cốt lõi.

Từ MSDN: SelectMany "chiếu từng phần tử của chuỗi lên IE và làm phẳng chuỗi kết quả thành một chuỗi."

+0

Với câu trả lời đầu tiên của mình, tôi nhận được biểu thức kiểu 'MyStruct' không được phép trong mệnh đề tiếp theo trong biểu thức truy vấn với kiểu nguồn 'Dictionary '. Kiểu suy luận kiểu không thành công trong lời gọi tới 'SelectMany' – mtone

+0

Và thứ hai, tôi nhận được 'MyStruct' không chứa định nghĩa 'Chọn' và không có phương thức mở rộng 'Chọn' chấp nhận một đối số đầu tiên của loại 'MyStruct' có thể được tìm thấy – mtone

+0

@mtone Tôi quên gọi .elements! chỉnh sửa ... xin lỗi – phoog

0

Điều gì sẽ xảy ra nếu bạn sử dụng một từ điển khác cho điều đó.

Dictionary<String, string> dict2 = new Dictionary<string, string>(); 

dict.foreach(item => item.elements.foreach(elem => dict2.Add(elem,item.name))); 

sau đó bạn có thể truy vấn từ điển mới để nhận số đếm, phần tử này có phần tử là yếu tố then chốt cho mỗi phần tử có các mục có nó. Vì vậy, bạn có thể tìm thấy có bao nhiêu mục có phần tử bạn muốn

+0

Điều đó sẽ không hoạt động vì các phím trong từ điển thứ hai sẽ không phải là duy nhất. – phoog

1

Làm phẳng mảng này thành một mảng duy nhất, sau đó đếm các giá trị duy nhất.

var groups = dictionary 
    .SelectMany(o => o.Value) 
    .GroupBy(o => o); 

foreach (var g in groups) 
    Console.WriteLine(g.Key + ": " + g.Count()); 

Sử dụng từ điển sau:

Dictionary<string, string[]> dictionary = new Dictionary<string, string[]>(); 
dictionary.Add("One", new string[] { "A" }); 
dictionary.Add("Two", new string[] {"A", "B" }); 
dictionary.Add("Three", new string[] { "A", "B" }); 

tôi nhận được kết quả này:

A: 3 
B: 2 
+0

Bạn không thể tạo từ điển thứ hai vì các khóa của nó sẽ không phải là duy nhất. – phoog

+0

Cảm ơn bạn đã chỉ ra điều đó, tôi đã cập nhật câu trả lời của mình để giải quyết mục tiêu dài hạn. – Despertar

+0

Cảm ơn bạn, điều này thực sự cung cấp số lượng nhóm thích hợp. Bây giờ, tôi nghĩ rằng tôi thích làm nó trong 2 bước (mở rộng, sau đó nhóm), nhưng tôi chắc chắn sẽ giữ điều này trong tâm trí. Cảm ơn một lần nữa! – mtone

1
/* Will return 
name elements 
item1 elementA 
item2 elementA 
item2 elementB 
*/ 
var res = dict 
    .Values 
    .SelectMany(m => m.elements.Select(e => new {m.name, element= e})) 
    .ToArray(); 

/* Will return 
element count 
ElementA 2 
ElementB 1 
*/ 
var res2 = res 
    .GroupBy(r => r.element) 
    .Select(g => new {element = g.Key, count = g.Count()}) 
    .ToArray(); 
+0

Cảm ơn rất nhiều! Điều này làm việc, và rất dễ đọc. Tôi chắc chắn sẽ làm việc với nó hơn nữa. – mtone

0

Bạn có thể muốn bắt đầu từ một bộ sưu tập đơn giản của cấu trúc nhưng từ từ điển của bạn:

var q = from t in dict.Values 
      from el in t.Elements 
      group el by el into eNameGroup 
      select new { Name = eNameGroup.Key, Count = eNameGroup.Count() }; 

này trả về:

Tên Đếm
ElementA 2
ElementB 1

0

Nếu những gì bạn đang sau khi được nhóm/pivoting, điều này có thể được thực hiện khai báo nhiều hơn bằng cách tận dụng nhóm LINQ và tránh từ điển hoàn toàn :

void Main() 
{ 
    var items = new MyStruct[] { 
     new MyStruct { name = "item1", elements = new List<string> { "elementA" }}, 
     new MyStruct { name = "item2", elements = new List<string> { "elementA", "elementB" }}}; 

    var groupedByElement = 
     from item in items 
     from element in item.elements 
     group item by element; 

    groupedByElement.Dump(); // items grouped by element value, (pivoted) 

    var elementsWithCount = 
     from gj in groupedByElement 
     select new { element = gj.Key, count = gj.Count() }; 

    elementsWithCount.Dump(); 
    // element, count 
    // elementA, 2 
    // elementB, 1 
} 

public struct MyStruct 
{ 
    public string name; 
    public List<string> elements; 
} 
+0

BTW, câu trả lời này được viết bằng LINQPad.Các cuộc gọi Dump là cách hiển thị đầu ra của LINQPad. – devgeezer

Các vấn đề liên quan