2012-02-29 9 views
5

structの辞書があります。ここで、1つのメンバーは、各辞書項目に適用されるさまざまな要素を含むリストです。構造体内のリストと結合するLinqクエリ

これらの要素をフィルタリングしたり、要素ごとにグループ化するために、これらの要素を各項目に結合したいと考えています。

SQLでは、テーブル/クエリに参加して複数の行を取得するのに慣れていますが、C#/ Linqの新機能です。 「列」はすでに適切な辞書項目に関連付けられているオブジェクト/リストになる可能性があるので、それらを使用して結合を実行する方法を知りたいですか?ここで

は、構造物のサンプルです:私はこの出力を与えるクエリを希望

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

最終的にするために

name elements 
item1 elementA 
item2 elementA 
item2 elementB 

、(= 3カウント)このようにそれらをグループ化:

ここで私のコードは辞書項目を数え始めます。

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 
    } 

編集:私は実際に出力が辞書である必要はありません。私はそれがうまく動作し、重複を防ぐためにデータを保存するために使用しました(私はキーとして保存する一意のitem.nameを持っています)。しかし、フィルタリング/グループ化の目的のために、それは問題なしでリストまたは配列になる可能性があります。私はいつでも行うことができます。ToDictionary where key = item.Name where後で。

+0

あなたは正しく注意として出力はC OUT、確かに、辞書に格納(とされなければならないことを規定していません注釈してください)。 – phoog

+0

@phoogあなたは正しいです。私は誤解する。だから私は私のコメントを削除しました。 – sinanakyazici

+0

何らかの理由で@sinanakyazici私の電話のブラウザで私のコメントを削除(また編集)できません:( – phoog

答えて

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

ここでのメソッドはEnumerable.SelectManyです。これらの値が等しいので、あなたはまた、代わりにt.Keyの、上記t.Value.nameを使用することができることを

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

はEDIT

注:拡張メソッドの構文を使用しました。

ここでは何が起こっていますか?

クエリの理解の構文はおそらく理解しやすいでしょう。あなたは何が起こっているかを見るために同等のイテレータブロックを書くことができます。私たちは、しかし、単に匿名型であることを行うことはできませんので、我々は返すように型を宣言されます:

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

方法(本当にがここで何が起こっているかまたは、)拡張メソッドの構文の?

(これはhttps://stackoverflow.com/a/2704795/385844でエリックリペットのポストによって部分的に触発され、私はその後、私がいることを読んで、はるかに複雑な説明があったが、この思い付いた:)

我々はNameElementを宣言しないようにしたいとしましょうタイプ。私たちは関数を渡すことによって匿名型を使うことができます。引数リスト(string1, string2)で定義されて - - と返すラムダ式(string1, string2) => new { name = string1, element = string2 }は2つの文字列を取る関数を表し

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

:これまで

var q = GetResults(dict); 

:私たちは、このからの呼び出しを変更したいです式new { name = string1, element = string2 }で定義された文字列で初期化された匿名型のインスタンス。

対応する実装は、このされています

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

型推論私たちは名前でTを指定せずにこの関数を呼び出すことができます。これは便利です。(C#プログラマとして認識している限り)使用しているタイプには名前がありません:匿名です。変数tは今pairある

注、型パラメータTとの混同を避けるために、そしてvは「要素」のために、今eです。また、最初のパラメータの型を基底型の1つ、IEnumerable<KeyValuePair<string, MyStruct>>に変更しました。それはより洗練されていますが、この方法がより有用になり、最終的に役立ちます。タイプがディクショナリタイプではなくなったので、パラメータの名前をdictからpairsに変更しました。

これをさらに一般化することができます。 2番目のforeachは、キーと値のペアをタイプTのシーケンスに投影する効果があります。その全体の効果は、単一の関数でカプセル化できます。代理人タイプはFunc<KeyValuePair<string, MyStruct>, T>になります。

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

今、私たちは簡単に署名を変更することができます:

最初のステップは、私たちが resultSelectorデリゲートを呼び出すために Select方法を使用して、シーケンスに要素 pairを変換し、単一の文を持っているので、メソッドをリファクタリングすることです
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; 
} 

コールサイトは次のようになりました。ラムダ式は、今はその署名を変更したときに、我々はメソッド本体から取り外しロジック組み込んでどのように気付か:(以下冗長とその実装)メソッドは、より便利にするために

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

を、のとタイプKeyValuePair<string, MyStruct>に代わっ型パラメータ、TSource。私たちは、同時にいくつかの他の名前に変更されます:

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

をそして、ちょうど蹴りのために、我々は、拡張メソッドを作ってあげる:

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

そしてそこにあなたがそれを持っている:SelectMany!さて、この関数の名前はまだ間違っています。実際の実装には、ソースシーケンスとセレクタ関数がnullではないという検証が含まれていますが、それがコアロジックです。

からMSDNSelectMany "は、シーケンスの各要素をIEnumerableに投影し、結果のシーケンスを1つのシーケンスにフラット化します。

+0

最初の答えで、私は取得します。 'MyStruct'型の式は、クエリ式の後続のfrom節で許可されていません'SelectMany'の呼び出しでタイプ推論が失敗しました。 – mtone

+0

2番目に、 'MyStruct'に 'Select'の定義がなく、拡張メソッド 'Select'の定義が含まれていません'MyStruct'型の最初の引数が見つかりました – mtone

+0

@mtone .elements!editing ...申し訳ありません。 – phoog

0

他の辞書を使用するとどうなりますか?

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

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

、あなたがカウントを取得するために、新しい辞書を照会することができ、それはそれを持っていたアイテムを持っている各要素のため、キーとしての要素を持っています。したがって、必要な要素がいくつあるか調べることができます

+0

2番目の辞書のキーが一意ではないため、これは機能しません。 – phoog

1

これは、配列を単一の配列に平坦化して一意の値を数えます。以下の辞書使用

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

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

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

を、私はこの出力を得る:

A: 3 
B: 2 
+0

2番目の辞書はキーが一意でないため作成できません。 – phoog

+0

それを指摘していただきありがとうございます、私は長期的な目標を解決するために私の答えを更新しました。 – Despertar

+0

ありがとうございます、これは実際には適切なグループ化カウントを提供します。今のところ、私は2段階で展開することを好むと思うが(展開してからグループ化する)、確かにこれを覚えておく。再度、感謝します! – 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

ありがとうございました!これは機能し、非常に読みやすいです。私は確かにそれをさらに働かせます。 – mtone

0

あなたは、構造体の単純なコレクションからではなく、あなたの辞書から開始することもできます。

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

これが返されます。

名前がカウント
ElementA 2
ElementB 1

0

後/旋回をグループ化しているもの、あなたがしている、これは、LINQのグループ化を活用し、完全に辞書を避けることによって、より宣言的に行うことができる場合:質問@sinanakyazici

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

ところで、この回答はLINQPadで書かれています。ダンプコールは、LINQPadの出力表示方法です。 – devgeezer

関連する問題