การรวมพจนานุกรมใน C #


493

วิธีที่ดีที่สุดในการผสานพจนานุกรม 2 หรือมากกว่าDictionary<T1,T2>ใน C # คืออะไร (คุณสมบัติ 3.0 เช่น LINQ ใช้ได้)

ฉันกำลังคิดถึงวิธีการที่เป็นลายเซ็นตามบรรทัดของ:

public static Dictionary<TKey,TValue>
                 Merge<TKey,TValue>(Dictionary<TKey,TValue>[] dictionaries);

หรือ

public static Dictionary<TKey,TValue>
                 Merge<TKey,TValue>(IEnumerable<Dictionary<TKey,TValue>> dictionaries);

แก้ไข:มีวิธีแก้ปัญหาที่ยอดเยี่ยมจาก JaredPar และ Jon Skeet แต่ฉันคิดถึงสิ่งที่จัดการกับคีย์ที่ซ้ำกัน ในกรณีที่มีการชนกันมันไม่สำคัญว่าจะบันทึกค่าใดไว้ในคำสั่งตราบเท่าที่มันสอดคล้องกัน


174
ที่ไม่เกี่ยวข้องกัน แต่สำหรับคนที่ต้องการที่จะผสานเพียงสองพจนานุกรมโดยไม่ต้องตรวจสอบคีย์ซ้ำนี้ทำงานอย่าง:dicA.Concat(dicB).ToDictionary(kvp => kvp.Key, kvp => kvp.Value)
Benjol

6
@Benjol คุณสามารถเพิ่มได้ในส่วนคำตอบ
M.Kumaran

4
การผสานของ Clojure ใน C #:dict1.Concat(dict2).GroupBy(p => p.Key).ToDictionary(g => g.Key, g => g.Last().Value)
Bruce

@Benjol: dictA.Concat(dictB.Where(kvp => !dictA.ContainsKey(kvp.Key))).ToDictionary(kvp=> kvp.Key, kvp => kvp.Value)กำจัดข้อมูลซ้ำโดยเลือกรายการจากพจนานุกรมครั้งแรกกับ
Suncat2000

คำตอบ:


320

ส่วนนี้ขึ้นอยู่กับสิ่งที่คุณต้องการให้เกิดขึ้นหากคุณพบซ้ำ ตัวอย่างเช่นคุณสามารถทำได้:

var result = dictionaries.SelectMany(dict => dict)
                         .ToDictionary(pair => pair.Key, pair => pair.Value);

ซึ่งจะทำให้เกิดข้อยกเว้นหากคุณได้รับกุญแจซ้ำซ้อน

แก้ไข: ถ้าคุณใช้ ToLookup คุณจะได้รับการค้นหาซึ่งมีหลายค่าต่อคีย์ จากนั้นคุณสามารถแปลงเป็นพจนานุกรม:

var result = dictionaries.SelectMany(dict => dict)
                         .ToLookup(pair => pair.Key, pair => pair.Value)
                         .ToDictionary(group => group.Key, group => group.First());

มันค่อนข้างน่าเกลียดและไม่มีประสิทธิภาพ - แต่เป็นวิธีที่เร็วที่สุดในการทำโค้ด (ฉันยังไม่ได้ทดสอบเป็นที่ยอมรับ)

คุณสามารถเขียนวิธีการขยาย ToDictionary2 ของคุณเองได้ (ด้วยชื่อที่ดีกว่า แต่ตอนนี้ฉันไม่มีเวลาคิดเลย) - มันไม่ยากที่จะทำเพียงเขียนทับคีย์ที่ซ้ำกัน (หรือเพิกเฉย) บิตที่สำคัญ (ในใจของฉัน) กำลังใช้ SelectMany และตระหนักว่าพจนานุกรมรองรับการวนซ้ำในคู่ของคีย์ / ค่า


3
หากต้องการรวมค่าแทนที่จะนำมาจากพจนานุกรมแรกคุณสามารถแทนที่ group => group.First () ด้วย group => group.SelectMany (value => value) ในคำตอบที่ถูกแก้ไขของ Jon Skeet
andreialecu

3
ตอนนี้ฉันสงสัยว่า GroupBy จะเหมาะกว่า ToLookup หรือไม่ ฉันคิดว่า ILookup เพิ่งเพิ่มตัวทำดัชนีคุณสมบัติขนาดและมีวิธีการด้านบนของการจัดกลุ่ม IGGG - ดังนั้นมันจะต้องเร็วขึ้นเล็กน้อย?
toong

2
@toong: อย่างใดอย่างหนึ่งควรจะดีที่จะซื่อสัตย์ การใช้GroupByอาจมีประสิทธิภาพมากกว่านิดหน่อย - แต่ฉันสงสัยว่ามันจะมีความหมายทั้งสองทาง
Jon Skeet

1
@ โจ: มันง่ายพอที่จะให้ผู้เปรียบเทียบกับToDictionaryวิธีการโทร ฉันต้องการให้คำตอบง่ายขึ้นสำหรับกรณีทั่วไปมากขึ้นและฉันหวังว่าทุกคนที่ต้องการเครื่องมือเปรียบเทียบแบบกำหนดเองสามารถค้นหาโอเวอร์โหลดที่เกี่ยวข้องได้
Jon Skeet

1
@Serge: ฉันขอแนะนำให้คุณถามคำถามใหม่กับข้อกำหนดที่แม่นยำ
Jon Skeet

265

ฉันจะทำเช่นนี้:

dictionaryFrom.ToList().ForEach(x => dictionaryTo.Add(x.Key, x.Value));

ง่ายและสะดวก ตามที่โพสต์บล็อกนี้ก็จะยิ่งเร็วกว่าลูปส่วนใหญ่เป็นการนำพื้นฐานของการเข้าถึงองค์ประกอบดัชนีมากกว่าการแจงนับ(ดูคำตอบนี้)

แน่นอนว่าจะมีข้อยกเว้นหากมีรายการซ้ำดังนั้นคุณจะต้องตรวจสอบก่อนรวม


169
dictionaryFrom.ToList().ForEach(x => dictionaryTo[x.Key] = x.Value)ถ้าซ้ำกันเรื่องการใช้งาน วิธีนี้ค่าจากการdictionaryFromแทนที่ค่าสำหรับคีย์ที่มีอยู่อาจเป็นไปได้
okrumnow

7
สิ่งนี้ไม่น่าจะเร็วกว่าการวนซ้ำ - คุณลืมว่า ToList () จริง ๆ แล้วจะเป็นการคัดลอกพจนานุกรม รหัสได้เร็วขึ้น! ;-)
Søren Boisen

6
มันเร็วกว่า .. เนื่องจาก ToList () จริง ๆ แล้วไม่ได้ทำการคัดลอกพจนานุกรมมันแค่คัดลอกการอ้างอิงขององค์ประกอบภายในพวกมัน โดยทั่วไปวัตถุรายการจะมีที่อยู่ใหม่ในหน่วยความจำวัตถุเหมือนกัน !!
GuruC

4
โพสต์บล็อกหายไป แต่มีเครื่อง Wayback: web.archive.org/web/20150311191313/http://diditwith.net/2006/10/…
CAD bloke

1
อืมดีกว่าคำตอบจอน Skeet ของฉันเข้าใจ
Sнаđошƒаӽ

102

สิ่งนี้จะไม่ระเบิดหากมีหลายคีย์ (คีย์ "righter" แทนที่คีย์ "lefter") สามารถรวมพจนานุกรมจำนวนหนึ่ง (หากต้องการ) และรักษาประเภท (ด้วยข้อ จำกัด ว่าต้องใช้ตัวสร้างสาธารณะที่มีความหมายเริ่มต้น):

public static class DictionaryExtensions
{
    // Works in C#3/VS2008:
    // Returns a new dictionary of this ... others merged leftward.
    // Keeps the type of 'this', which must be default-instantiable.
    // Example: 
    //   result = map.MergeLeft(other1, other2, ...)
    public static T MergeLeft<T,K,V>(this T me, params IDictionary<K,V>[] others)
        where T : IDictionary<K,V>, new()
    {
        T newMap = new T();
        foreach (IDictionary<K,V> src in
            (new List<IDictionary<K,V>> { me }).Concat(others)) {
            // ^-- echk. Not quite there type-system.
            foreach (KeyValuePair<K,V> p in src) {
                newMap[p.Key] = p.Value;
            }
        }
        return newMap;
    }

}

ทำได้ดี. นี่คือสิ่งที่ฉันต้องการ ใช้ยาชื่อสามัญได้ดี ยอมรับว่าไวยากรณ์นั้นค่อนข้างน่าอึดอัดใจ แต่ไม่มีอะไรที่คุณสามารถทำได้
Peter M

2
ฉันชอบวิธีนี้ แต่มีข้อแม้: ถ้าthis T meพจนานุกรมถูกประกาศใช้new Dictionary<string, T>(StringComparer.InvariantCultureIgnoreCase)หรือคล้ายกันพจนานุกรมผลลัพธ์จะไม่คงพฤติกรรมเดิมไว้
João Portela

3
หากคุณสร้างmea Dictionary<K,V>คุณสามารถทำได้var newMap = new Dictionary<TKey, TValue>(me, me.Comparer);และหลีกเลี่ยงTพารามิเตอร์ประเภทพิเศษ
Aneves

1
มีคนมีตัวอย่างของวิธีการใช้สัตว์ร้ายนี้หรือไม่?
ทิม

1
@ Tim: ผมเพิ่มตัวอย่างสำหรับสัตว์ดังต่อไปนี้ฉันเพิ่มวิธีการแก้ปัญหาโดย ANeves และผลที่ได้เป็นสัตว์ที่โดดเด่นมากขึ้น :)
Keni

50

วิธีแก้ปัญหาเล็ก ๆ น้อย ๆ คือ:

using System.Collections.Generic;
...
public static Dictionary<TKey, TValue>
    Merge<TKey,TValue>(IEnumerable<Dictionary<TKey, TValue>> dictionaries)
{
    var result = new Dictionary<TKey, TValue>();
    foreach (var dict in dictionaries)
        foreach (var x in dict)
            result[x.Key] = x.Value;
    return result;
}

22

ลองทำสิ่งต่อไปนี้

static Dictionary<TKey, TValue>
    Merge<TKey, TValue>(this IEnumerable<Dictionary<TKey, TValue>> enumerable)
{
    return enumerable.SelectMany(x => x).ToDictionary(x => x.Key, y => y.Value);
}

19
Dictionary<String, String> allTables = new Dictionary<String, String>();
allTables = tables1.Union(tables2).ToDictionary(pair => pair.Key, pair => pair.Value);

1
แค่สงสัยว่า - สหภาพจะไม่ทำงานเหรอ? foreach(var kvp in Message.GetAttachments().Union(mMessage.GetImages()))ใช้ในรหัสการผลิตหากมีข้อเสียโปรดแจ้งให้เราทราบ! :)
Mars Robertson

1
@Michal: Union จะส่งคืนIEnumerable<KeyValuePair<TKey, TValue>>ตำแหน่งที่ความเท่าเทียมกันถูกกำหนดโดยความเท่าเทียมกันของคีย์และค่า (มีการโอเวอร์โหลดเพื่ออนุญาตให้มีทางเลือก) นี่เป็นเรื่องปกติสำหรับลูป foreach ซึ่งเป็นสิ่งที่จำเป็น แต่มีบางกรณีที่คุณต้องการพจนานุกรม
ทิโมธี

15

ฉันไปงานปาร์ตี้ช้ามากและอาจหายไปบางอย่าง แต่ถ้าทั้งคู่ไม่มีคีย์ซ้ำหรืออย่างที่ OP กล่าวว่า "ในกรณีที่มีการชนกัน สอดคล้องกัน "เกิดอะไรขึ้นกับอันนี้ (รวม D2 เข้ากับ D1)

foreach (KeyValuePair<string,int> item in D2)
            {
                 D1[item.Key] = item.Value;
            }

ดูเหมือนง่ายพออาจจะง่ายเกินไปฉันสงสัยว่าฉันขาดอะไรไปหรือเปล่า นี่คือสิ่งที่ฉันใช้ในรหัสบางอย่างที่ฉันรู้ว่าไม่มีคีย์ที่ซ้ำกัน อย่างไรก็ตามฉันยังอยู่ในการทดสอบดังนั้นฉันชอบที่จะรู้ว่าตอนนี้ถ้าฉันมองอะไรบางอย่างแทนที่จะค้นหาในภายหลัง


4
หนึ่งในโซลูชั่นที่สะอาดและอ่านได้มากที่สุด imo และหลีกเลี่ยง ToList () ที่คนอื่นใช้
404

15

งานต่อไปนี้สำหรับฉัน หากมีการซ้ำซ้อนมันจะใช้ค่าของ dictA

public static IDictionary<TKey, TValue> Merge<TKey, TValue>(this IDictionary<TKey, TValue> dictA, IDictionary<TKey, TValue> dictB)
    where TValue : class
{
    return dictA.Keys.Union(dictB.Keys).ToDictionary(k => k, k => dictA.ContainsKey(k) ? dictA[k] : dictB[k]);
}

@EsbenSkovPedersen ฉันไม่คิดว่าฉันได้พบว่าเมื่อฉันตอบนี้ (ใหม่ C # dev)
อีธาน Reesor

ฉันต้องลบ 'ตำแหน่ง TValue: class' เพื่อใช้ Guid เป็นค่า
om471987

@ om471987 ใช่ Guid ไม่ได้เป็นคลาสดังนั้นจึงเหมาะสม ฉันคิดว่าเดิมฉันมีปัญหาบางอย่างเมื่อฉันไม่ได้เพิ่มประโยคนั้น จำไม่ได้ว่าทำไม
Ethan Reesor

10

นี่คือฟังก์ชั่นตัวช่วยที่ฉันใช้:

using System.Collections.Generic;
namespace HelperMethods
{
    public static class MergeDictionaries
    {
        public static void Merge<TKey, TValue>(this IDictionary<TKey, TValue> first, IDictionary<TKey, TValue> second)
        {
            if (second == null || first == null) return;
            foreach (var item in second) 
                if (!first.ContainsKey(item.Key)) 
                    first.Add(item.Key, item.Value);
        }
    }
}

มีคำตอบที่ดีมากมายที่ระบุไว้ที่นี่ แต่ฉันชอบความเรียบง่ายของมันมากที่สุด เพียงแค่การตั้งค่าส่วนตัวของฉัน
jason

3
if(first == null)จากนั้นตรรกะนี้ไร้ประโยชน์เพราะfirstไม่ได้refและไม่ได้ถูกส่งกลับ และเป็นไปไม่ได้refเพราะมันถูกประกาศเป็นอินสแตนซ์ของอินเทอร์เฟซ คุณต้องลบการตรวจสอบข้อผิดพลาดหรือประกาศเป็นอินสแตนซ์ของชั้นเรียนหรือเพียงแค่ส่งคืนพจนานุกรมใหม่ (โดยไม่มีวิธีการขยาย)
Grault

@Jesdisciple - ลบปัญหาตามคำแนะนำของคุณ
Andrew Harry

8

ตัวเลือกที่ 1:ขึ้นอยู่กับสิ่งที่คุณต้องการให้เกิดขึ้นหากคุณแน่ใจว่าคุณไม่มีคีย์ซ้ำในพจนานุกรมทั้งสอง กว่าที่คุณสามารถทำได้:

var result = dictionary1.Union(dictionary2).ToDictionary(k => k.Key, v => v.Value)

หมายเหตุ: การทำเช่นนี้จะทำให้เกิดข้อผิดพลาดหากคุณได้รับคีย์ซ้ำในพจนานุกรม

ตัวเลือกที่ 2:หากคุณสามารถมีคีย์ที่ซ้ำกันได้คุณจะต้องจัดการคีย์ที่ซ้ำกันด้วยการใช้คำสั่ง where

var result = dictionary1.Union(dictionary2.Where(k => !dictionary1.ContainsKey(k.Key))).ToDictionary(k => k.Key, v => v.Value)

บันทึก :มันจะไม่ได้รับรหัสซ้ำกัน หากจะมีคีย์ที่ซ้ำกันกว่าจะได้รับคีย์ dictionary1

ตัวเลือก 3:หากคุณต้องการใช้ ToLookup จากนั้นคุณจะได้รับการค้นหาซึ่งมีหลายค่าต่อคีย์ คุณสามารถแปลงการค้นหานั้นเป็นพจนานุกรม:

var result = dictionaries.SelectMany(dict => dict)
                         .ToLookup(pair => pair.Key, pair => pair.Value)
                         .ToDictionary(group => group.Key, group => group.First());

6

วิธีการเกี่ยวกับการเพิ่ม paramsน้ำหนักเกิน?

นอกจากนี้คุณควรพิมพ์IDictionaryเพื่อความยืดหยุ่นสูงสุด

public static IDictionary<TKey, TValue> Merge<TKey, TValue>(IEnumerable<IDictionary<TKey, TValue>> dictionaries)
{
    // ...
}

public static IDictionary<TKey, TValue> Merge<TKey, TValue>(params IDictionary<TKey, TValue>[] dictionaries)
{
    return Merge((IEnumerable<TKey, TValue>) dictionaries);
}

4
เขาเป็นคนที่เพิ่มคำตอบอื่น ๆ ที่นี่โดยสังเกตว่าคุณสามารถทำให้ชั้นเรียน DictionaryExtensions ยิ่งกว่า บางทีคำถามนี้ควรกลายเป็นวิกิหรือเป็นคลาสที่เช็คอินเพื่อคอมไพล์ ...
Chris Moschini

5

เมื่อพิจารณาถึงประสิทธิภาพของการค้นหาและลบคีย์พจนานุกรมในพจนานุกรมเนื่องจากการดำเนินการแฮชและเมื่อพิจารณาถึงการใช้ถ้อยคำของคำถามเป็นวิธีที่ดีที่สุดฉันคิดว่าด้านล่างเป็นวิธีที่ถูกต้องสมบูรณ์แบบ

    public static void MergeOverwrite<T1, T2>(this IDictionary<T1, T2> dictionary, IDictionary<T1, T2> newElements)
    {
        if (newElements == null) return;

        foreach (var e in newElements)
        {
            dictionary.Remove(e.Key); //or if you don't want to overwrite do (if !.Contains()
            dictionary.Add(e);
        }
    }

หรือถ้าคุณกำลังทำงานในแอปพลิเคชันแบบมัลติเธรดและพจนานุกรมของคุณจำเป็นต้องมีเธรดที่ปลอดภัยอยู่ดีคุณควรทำสิ่งนี้:

    public static void MergeOverwrite<T1, T2>(this ConcurrentDictionary<T1, T2> dictionary, IDictionary<T1, T2> newElements)
    {
        if (newElements == null || newElements.Count == 0) return;

        foreach (var ne in newElements)
        {
            dictionary.AddOrUpdate(ne.Key, ne.Value, (key, value) => value);
        }
    }

จากนั้นคุณสามารถปิดสิ่งนี้เพื่อให้จัดการกับพจนานุกรมได้ ไม่ว่าคุณกำลังมองเกี่ยวกับ ~ O (3n) (เงื่อนไขทั้งหมดสมบูรณ์แบบ) เนื่องจาก.Add()จะทำเพิ่มเติมไม่จำเป็น แต่ฟรีจริงContains()หลังฉาก ฉันไม่คิดว่ามันจะดีขึ้นมาก

หากคุณต้องการ จำกัด การทำงานเพิ่มเติมในคอลเลกชันขนาดใหญ่คุณควรสรุปผลรวมCountของแต่ละพจนานุกรมที่คุณกำลังจะผสานและตั้งค่าความจุของพจนานุกรมเป้าหมายให้เท่านั้นซึ่งจะช่วยลดต้นทุนการปรับขนาดในภายหลัง ดังนั้นผลิตภัณฑ์สุดท้ายคือสิ่งนี้ ...

    public static IDictionary<T1, T2> MergeAllOverwrite<T1, T2>(IList<IDictionary<T1, T2>> allDictionaries)
    {
        var initSize = allDictionaries.Sum(d => d.Count);
        var resultDictionary = new Dictionary<T1, T2>(initSize);
        allDictionaries.ForEach(resultDictionary.MergeOverwrite);
        return resultDictionary;
    }

โปรดทราบว่าฉันใช้IList<T>วิธีนี้ ... ส่วนใหญ่เป็นเพราะถ้าคุณใช้เวลาIEnumerable<T>คุณได้เปิดตัวเองถึงหลายชุดในชุดเดียวกันซึ่งอาจมีราคาแพงมากถ้าคุณได้รับคอลเลกชันของพจนานุกรมจาก LINQ รอการตัดบัญชี คำให้การ.


4

ตามคำตอบข้างต้น แต่เพิ่มพารามิเตอร์ Func เพื่อให้ผู้โทรจัดการรายการที่ซ้ำกัน:

public static Dictionary<TKey, TValue> Merge<TKey, TValue>(this IEnumerable<Dictionary<TKey, TValue>> dicts, 
                                                           Func<IGrouping<TKey, TValue>, TValue> resolveDuplicates)
{
    if (resolveDuplicates == null)
        resolveDuplicates = new Func<IGrouping<TKey, TValue>, TValue>(group => group.First());

    return dicts.SelectMany<Dictionary<TKey, TValue>, KeyValuePair<TKey, TValue>>(dict => dict)
                .ToLookup(pair => pair.Key, pair => pair.Value)
                .ToDictionary(group => group.Key, group => resolveDuplicates(group));
}

4

ตอนนี้ปาร์ตี้ค่อนข้างตายแล้ว แต่นี่คือ user166390 เวอร์ชั่น "ปรับปรุง" ที่เข้ามาในไลบรารีส่วนขยายของฉัน นอกเหนือจากรายละเอียดบางอย่างแล้วฉันได้เพิ่มผู้ร่วมประชุมเพื่อคำนวณมูลค่าที่ผสาน

/// <summary>
/// Merges a dictionary against an array of other dictionaries.
/// </summary>
/// <typeparam name="TResult">The type of the resulting dictionary.</typeparam>
/// <typeparam name="TKey">The type of the key in the resulting dictionary.</typeparam>
/// <typeparam name="TValue">The type of the value in the resulting dictionary.</typeparam>
/// <param name="source">The source dictionary.</param>
/// <param name="mergeBehavior">A delegate returning the merged value. (Parameters in order: The current key, The current value, The previous value)</param>
/// <param name="mergers">Dictionaries to merge against.</param>
/// <returns>The merged dictionary.</returns>
public static TResult MergeLeft<TResult, TKey, TValue>(
    this TResult source,
    Func<TKey, TValue, TValue, TValue> mergeBehavior,
    params IDictionary<TKey, TValue>[] mergers)
    where TResult : IDictionary<TKey, TValue>, new()
{
    var result = new TResult();
    var sources = new List<IDictionary<TKey, TValue>> { source }
        .Concat(mergers);

    foreach (var kv in sources.SelectMany(src => src))
    {
        TValue previousValue;
        result.TryGetValue(kv.Key, out previousValue);
        result[kv.Key] = mergeBehavior(kv.Key, kv.Value, previousValue);
    }

    return result;
}

2

@Tim: ควรเป็นความคิดเห็น แต่ความคิดเห็นไม่อนุญาตให้มีการแก้ไขรหัส

Dictionary<string, string> t1 = new Dictionary<string, string>();
t1.Add("a", "aaa");
Dictionary<string, string> t2 = new Dictionary<string, string>();
t2.Add("b", "bee");
Dictionary<string, string> t3 = new Dictionary<string, string>();
t3.Add("c", "cee");
t3.Add("d", "dee");
t3.Add("b", "bee");
Dictionary<string, string> merged = t1.MergeLeft(t2, t2, t3);

หมายเหตุ: ฉันใช้การแก้ไขโดย @ ANeves กับโซลูชันโดย @Andrew Orsich ดังนั้น MergeLeft จึงมีลักษณะดังนี้:

public static Dictionary<K, V> MergeLeft<K, V>(this Dictionary<K, V> me, params IDictionary<K, V>[] others)
    {
        var newMap = new Dictionary<K, V>(me, me.Comparer);
        foreach (IDictionary<K, V> src in
            (new List<IDictionary<K, V>> { me }).Concat(others))
        {
            // ^-- echk. Not quite there type-system.
            foreach (KeyValuePair<K, V> p in src)
            {
                newMap[p.Key] = p.Value;
            }
        }
        return newMap;
    }

ใกล้กับสิ่งที่ฉันเขียนเอง ofc นี้จะใช้งานได้กับDictionaryประเภทเท่านั้น สามารถเพิ่มเกินพิกัดสำหรับประเภทพจนานุกรมอื่น ( ConcurrentDictionary, ReadOnlyDictionaryและอื่น ๆ ) ฉันไม่คิดว่าการสร้างรายการใหม่และ concat เป็นสิ่งที่จำเป็นเป็นการส่วนตัว ฉันเพิ่งทำซ้ำอาร์เรย์ params ก่อนจากนั้นทำซ้ำ KVP ของพจนานุกรมแต่ละรายการ ฉันไม่เห็นการย้อนกลับ
บดขยี้

คุณสามารถใช้ IDictionary แทนพจนานุกรม
Mariusz Jamro

คุณจะเสมอจะได้รับวัตถุแม้ว่าคุณจะใช้วิธีขยายบนDictionary ConcurrentDictionaryซึ่งอาจนำไปสู่การติดตามข้อผิดพลาดได้ยาก
Marcel

2

ฉันรู้ว่านี่เป็นคำถามเก่า แต่เนื่องจากเรามี LINQ คุณสามารถทำได้ในบรรทัดเดียวเช่นนี้

Dictionary<T1,T2> merged;
Dictionary<T1,T2> mergee;
mergee.ToList().ForEach(kvp => merged.Add(kvp.Key, kvp.Value));

หรือ

mergee.ToList().ForEach(kvp => merged.Append(kvp));

ขออภัยสำหรับ @Cruces downvote แต่ตัวอย่างที่ซ้ำกันคำตอบแรกของคุณstackoverflow.com/a/6695211/704808และตัวอย่างที่สองของคุณเพียงแค่ช่วยให้ทิ้ง IEnumerable ขยาย <KeyValuePair <สตริงสตริง >> mergeeหลังจากท้ายแต่ละรายการจาก
ฝาย

2

กลัวที่จะเห็นคำตอบที่ซับซ้อนเป็นเรื่องใหม่สำหรับ C #

นี่คือคำตอบง่ายๆ
การผสาน d1, d2 และอื่น ๆ .. พจนานุกรมและจัดการคีย์ที่ทับซ้อนกัน ("b" ในตัวอย่างด้านล่าง):

ตัวอย่างที่ 1

{
    // 2 dictionaries,  "b" key is common with different values

    var d1 = new Dictionary<string, int>() { { "a", 10 }, { "b", 21 } };
    var d2 = new Dictionary<string, int>() { { "c", 30 }, { "b", 22 } };

    var result1 = d1.Concat(d2).GroupBy(ele => ele.Key).ToDictionary(ele => ele.Key, ele => ele.First().Value);
    // result1 is  a=10, b=21, c=30    That is, took the "b" value of the first dictionary

    var result2 = d1.Concat(d2).GroupBy(ele => ele.Key).ToDictionary(ele => ele.Key, ele => ele.Last().Value);
    // result2 is  a=10, b=22, c=30    That is, took the "b" value of the last dictionary
}

ตัวอย่างที่ 2

{
    // 3 dictionaries,  "b" key is common with different values

    var d1 = new Dictionary<string, int>() { { "a", 10 }, { "b", 21 } };
    var d2 = new Dictionary<string, int>() { { "c", 30 }, { "b", 22 } };
    var d3 = new Dictionary<string, int>() { { "d", 40 }, { "b", 23 } };

    var result1 = d1.Concat(d2).Concat(d3).GroupBy(ele => ele.Key).ToDictionary(ele => ele.Key, ele => ele.First().Value);
    // result1 is  a=10, b=21, c=30, d=40    That is, took the "b" value of the first dictionary

    var result2 = d1.Concat(d2).Concat(d3).GroupBy(ele => ele.Key).ToDictionary(ele => ele.Key, ele => ele.Last().Value);
    // result2 is  a=10, b=23, c=30, d=40    That is, took the "b" value of the last dictionary
}

สำหรับสถานการณ์ที่ซับซ้อนมากขึ้นดูคำตอบอื่น ๆ
หวังว่าจะช่วย


2
using System.Collections.Generic;
using System.Linq;

public static class DictionaryExtensions
{
    public enum MergeKind { SkipDuplicates, OverwriteDuplicates }
    public static void Merge<K, V>(this IDictionary<K, V> target, IDictionary<K, V> source, MergeKind kind = MergeKind.SkipDuplicates) =>
        source.ToList().ForEach(_ => { if (kind == MergeKind.OverwriteDuplicates || !target.ContainsKey(_.Key)) target[_.Key] = _.Value; });
}

คุณสามารถข้าม / เพิกเฉย (ค่าเริ่มต้น) หรือเขียนทับรายการที่ซ้ำกัน: และบ็อบเป็นลุงของคุณหากคุณไม่ได้ยุ่งเกี่ยวกับประสิทธิภาพของ Linq มากเกินไป แต่ต้องการรหัสที่สามารถบำรุงรักษาได้อย่างแน่นหนาแทนในกรณีนี้คุณสามารถลบ MergeKind.SkipDuplicates ทางเลือกสำหรับผู้โทรและทำให้ผู้พัฒนารับรู้ว่าผลลัพธ์จะเป็นอย่างไร!


คำตอบที่ได้จากการกลั่นด้านล่างโดยไม่ต้องใช้ enum แบบไบนารีที่ไม่จำเป็นเมื่อบูลจะทำ ...
14251

2

โปรดทราบว่าหากคุณใช้วิธีการขยายที่เรียกว่า 'เพิ่ม' คุณจะต้องใช้ตัวเริ่มต้นการรวบรวมเพื่อรวมพจนานุกรมให้มากที่สุดเท่าที่ต้องการเช่นนี้:

public static void Add<K, V>(this Dictionary<K, V> d, Dictionary<K, V> other) {
  foreach (var kvp in other)
  {
    if (!d.ContainsKey(kvp.Key))
    {
      d.Add(kvp.Key, kvp.Value);
    }
  }
}


var s0 = new Dictionary<string, string> {
  { "A", "X"}
};
var s1 = new Dictionary<string, string> {
  { "A", "X" },
  { "B", "Y" }
};
// Combine as many dictionaries and key pairs as needed
var a = new Dictionary<string, string> {
  s0, s1, s0, s1, s1, { "C", "Z" }
};

1

การผสานโดยใช้วิธีการขยาย จะไม่ส่งข้อยกเว้นเมื่อมีคีย์ซ้ำ แต่แทนที่คีย์เหล่านั้นด้วยคีย์จากพจนานุกรมที่สอง

internal static class DictionaryExtensions
{
    public static Dictionary<T1, T2> Merge<T1, T2>(this Dictionary<T1, T2> first, Dictionary<T1, T2> second)
    {
        if (first == null) throw new ArgumentNullException("first");
        if (second == null) throw new ArgumentNullException("second");

        var merged = new Dictionary<T1, T2>();
        first.ToList().ForEach(kv => merged[kv.Key] = kv.Value);
        second.ToList().ForEach(kv => merged[kv.Key] = kv.Value);

        return merged;
    }
}

การใช้งาน:

Dictionary<string, string> merged = first.Merge(second);

1

ประยุกต์ใช้ง่ายกว่าเปรียบเทียบกับคำตอบก่อนหน้าของฉันกับค่าเริ่มต้นของการผสานแบบไม่ทำลายถ้ามีอยู่หรือเขียนทับทั้งหมดถ้าเป็นจริงแทนที่จะใช้ enum มันยังคงเหมาะสมกับความต้องการของฉันเองโดยไม่ต้องใช้รหัสที่นักเล่นต้องการ:

using System.Collections.Generic;
using System.Linq;

public static partial class Extensions
{
    public static void Merge<K, V>(this IDictionary<K, V> target, IDictionary<K, V> source, bool overwrite = false)
    {
        source.ToList().ForEach(_ => {
            if ((!target.ContainsKey(_.Key)) || overwrite)
                target[_.Key] = _.Value;
        });
    }
}

0

การผสานโดยใช้รายการEqualityComparerที่แมปเพื่อเปรียบเทียบกับค่า / ประเภทอื่น ที่นี่เราจะ map จากKeyValuePair(ประเภทรายการเมื่อแจงพจนานุกรม) Keyเพื่อ

public class MappedEqualityComparer<T,U> : EqualityComparer<T>
{
    Func<T,U> _map;

    public MappedEqualityComparer(Func<T,U> map)
    {
        _map = map;
    }

    public override bool Equals(T x, T y)
    {
        return EqualityComparer<U>.Default.Equals(_map(x), _map(y));
    }

    public override int GetHashCode(T obj)
    {
        return _map(obj).GetHashCode();
    }
}

การใช้งาน:

// if dictA and dictB are of type Dictionary<int,string>
var dict = dictA.Concat(dictB)
                .Distinct(new MappedEqualityComparer<KeyValuePair<int,string>,int>(item => item.Key))
                .ToDictionary(item => item.Key, item=> item.Value);

0

หรือ :

public static IDictionary<TKey, TValue> Merge<TKey, TValue>( IDictionary<TKey, TValue> x, IDictionary<TKey, TValue> y)
    {
        return x
            .Except(x.Join(y, z => z.Key, z => z.Key, (a, b) => a))
            .Concat(y)
            .ToDictionary(z => z.Key, z => z.Value);
    }

ผลลัพธ์คือการรวมกันที่สำหรับรายการที่ซ้ำกัน "y" ชนะ


0
public static IDictionary<K, V> AddRange<K, V>(this IDictionary<K, V> one, IDictionary<K, V> two)
        {
            foreach (var kvp in two)
            {
                if (one.ContainsKey(kvp.Key))
                    one[kvp.Key] = two[kvp.Key];
                else
                    one.Add(kvp.Key, kvp.Value);
            }
            return one;
        }

0

เวอร์ชันจาก @ user166390 ตอบด้วยIEqualityComparerพารามิเตอร์ที่เพิ่มเพื่ออนุญาตการเปรียบเทียบคีย์ที่ไม่คำนึงถึงขนาดตัวพิมพ์

    public static T MergeLeft<T, K, V>(this T me, params Dictionary<K, V>[] others)
        where T : Dictionary<K, V>, new()
    {
        return me.MergeLeft(me.Comparer, others);
    }

    public static T MergeLeft<T, K, V>(this T me, IEqualityComparer<K> comparer, params Dictionary<K, V>[] others)
        where T : Dictionary<K, V>, new()
    {
        T newMap = Activator.CreateInstance(typeof(T), new object[] { comparer }) as T;

        foreach (Dictionary<K, V> src in 
            (new List<Dictionary<K, V>> { me }).Concat(others))
        {
            // ^-- echk. Not quite there type-system.
            foreach (KeyValuePair<K, V> p in src)
            {
                newMap[p.Key] = p.Value;
            }
        }
        return newMap;
    }

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.