web-dev-qa-db-ja.com

C#2つの辞書のマージ

.NET 3.5を対象としたC#でアプリを開発しています。その中に、アプリ内の特定の要素セットの検証基準を含む2つの同様の辞書があります。両方の辞書は同じ署名を持っています。最初のディクショナリにはデフォルト設定があり、2番目のディクショナリにはいくつかのユーザー定義設定が含まれています。

var default_settings = new Dictionary<string, MyElementSettings>();
var custom_settings = new Dictionary<string, MyElementSettings>();

2つの辞書を組み合わせて、両方の辞書の要素を含む1つにしたいと思います。

私が遭遇している問題は、両方の辞書が同じキー値のいくつかを持っている可能性があるということです。私が望む基本的なルールは、両方のディクショナリを組み合わせることです。custom_settingsにすでにdefault_settingsに存在するキーがある場合、custom_settings値はdefault_settings値を上書きします。私が持っている最善の解決策は、foreachループだけで、キーが他の辞書に存在するかどうかを確認し、存在しない場合は追加します。

foreach (var item in custom_settings)
{
    if (default_settings.ContainsKey(item.Key))
        default_settings[item.Key] = item.Value;
    else
        default_settings.Add(item.Key, item.Value);
}

私はいくつかの基本的なLINQクエリを実行しましたが、さらに高度なものの学習に取り組んでいます。 2つの辞書をマージするクエリをいくつか見てきましたが、ほとんどの場合、重複するキーを持つ要素をグループ化するか、重複するキーのみを持つコレクションのみを返します/ foreachループの動作を模倣するLINQクエリまたは式はありますか使ってます?

21
psubsee2003

2つのポイント:

  1. LINQは副作用の実行には適していません。この場合、クエリを実行するのではなく、既存のコレクションを変更しようとしているので、純粋なLINQソリューションを敬遠します。
  2. 汎用辞書のインデクサーのセッター には、キーが存在しない場合はキーと値のペアを追加する効果があり、存在する場合は値を上書きする効果があります。

プロパティ値を設定するときに、キーがディクショナリにある場合、そのキーに関連付けられている値は、割り当てられた値に置き換えられます。キーがディクショナリにない場合、キーと値がディクショナリに追加されます。

したがって、foreachループは基本的に次と同等です。

foreach (var item in custom_settings)
{
   default_settings[item.Key] = item.Value;
}

すでにかなり簡潔になっているので、LINQがそれほど役立つとは思いません。

43
Ani

これは、Aniの回答に基づいたNice拡張方法です。

public static class DictionaryExtensionMethods
{
    public static void Merge<TKey, TValue>(this Dictionary<TKey, TValue> me, Dictionary<TKey, TValue> merge)
    {
        foreach (var item in merge)
        {
            me[item.Key] = item.Value;
        }
    }
}
9
Daniel Imms

これを頻繁に行う場合は、辞書キーの等価比較子を作成することをお勧めします。

private class KeyEqualityComparer<T, U> : IEqualityComparer<KeyValuePair<T, U>>
{
    public bool Equals(KeyValuePair<T, U> x, KeyValuePair<T, U> y)
    {
        return x.Key.Equals(y.Key);
    }

    public int GetHashCode(KeyValuePair<T, U> obj)
    {
        return obj.Key.GetHashCode();
    }
}

そして、辞書をマージする必要があるときはいつでも、次のことができます

var comparer = new KeyEqualityComparer<string, MyElementSettings>();
dict1 = dict1.Union(dict2,comparer).ToDictionary(a => a.Key, b => b.Value);
2
Dean Chalk

私が最初に選択した答えは、この特定のケースの最良の答えであると思います。少し前に、辞書に変換してマージしたい2つのIEnumerable<>オブジェクトがあった別の同様の状況に陥りました。同様の方法なので、将来誰かを助けるために、ここにそのソリューションを追加すると思いました。両方を辞書に変換して、選択した回答のメソッドを使用するのではなく、新しいアプローチを見つけました。

私は実際に 初期ソリューション をSE-CodeReviewに投稿し、実際にそれをさらに改良するための提案をしました。これが私が使用した最終的なコードです:

public Dictionary<String, Foo> Merge(XElement element1, XElement element2)
{
    IEnumerable<Foo> firstFoos = GetXmlData(element1); // parse 1st set from XML
    IEnumerable<Foo> secondFoos = GetXmlData(element2); // parse 2nd set from XML

    var result = firstFoos.Union(secondFoos).ToDictionary(k=>k.Name, v=>v);

    return result;
}

public class Foo
{
    public String Name { get; }

    // other Properties and Methods
    // .
    // .
    // .

    public override Boolean Equals(Object obj)
    {
        if (obj is Foo)
        {
            return this.Name == ((Foo)obj).Name;            
        }

        return false;
    }
}

これの鍵は、FooEquals()をオーバーライドして、重複と見なすことができるFooオブジェクトを定義する必要があることです。また、重複するオブジェクトを定義するメンバーもDictionary<>キー(この場合はName

FooEquals()をオーバーライドできない場合、他のオプションはConcat()の代わりにGroupBy()Union()を使用することです。

public Dictionary<String, Foo> Merge(XElement element1, XElement element2)
{
    IEnumerable<Foo> firstFoos = GetXmlData(element1); // parse 1st set from XML
    IEnumerable<Foo> secondFoos = GetXmlData(element2); // parse 2nd set from XML

    var result = firstFoos.Concat(secondFoos)
                          .GroupBy(foo => foo.Name)
                          .Select(grp => grp.First())
                          .ToDictionary(k=>k.Name, v=>v);

    return result;
}
1
psubsee2003