มีวิธีการในตัวเพื่อเปรียบเทียบคอลเลกชัน?


178

ฉันต้องการเปรียบเทียบเนื้อหาของคอลเลกชันสองสามรายการในวิธีการเท่ากับของฉัน ฉันมีพจนานุกรมและ IList มีวิธีการในการทำเช่นนี้?

แก้ไข: ฉันต้องการเปรียบเทียบพจนานุกรมสองเล่มกับ ILists สองคนดังนั้นฉันคิดว่าความหมายของความเท่าเทียมกันนั้นชัดเจน - หากพจนานุกรมสองเล่มมีคีย์เดียวกันที่แมปกับค่าเดียวกันนั่นก็เท่ากับ


โดยไม่คำนึงถึงคำสั่งหรือไม่ได้สำหรับIList? คำถามไม่ชัดเจน
nawfal

Enumerable.SequenceEqualและISet.SetEqualsจัดเตรียมเวอร์ชันของฟังก์ชันนี้ หากคุณต้องการเป็นผู้ไม่เชื่อเรื่องพระเจ้าสั่งซื้อและทำงานกับคอลเลกชันที่มีซ้ำคุณจะต้องม้วนของคุณเอง ตรวจสอบการใช้งานที่แนะนำในโพสต์นี้
ChaseMedallion

คำตอบ:


185

Enumerable.SequenceEqual

กำหนดว่าสองลำดับจะเท่ากันโดยการเปรียบเทียบองค์ประกอบของพวกเขาโดยใช้ IEqualityComparer (T) ที่ระบุ

คุณไม่สามารถเปรียบเทียบรายการ & พจนานุกรมได้โดยตรง แต่คุณสามารถเปรียบเทียบรายการค่าจากพจนานุกรมกับรายการ


52
ปัญหาคือว่า SequenceEqual คาดว่าองค์ประกอบจะอยู่ในลำดับเดียวกัน คลาส Dictionary ไม่รับประกันลำดับของคีย์หรือค่าเมื่อระบุดังนั้นหากคุณจะใช้ SequenceEqual คุณต้องเรียงลำดับ .Keys และ .Values ​​ก่อน!
Orion Edwards

3
@Orion: ... ถ้าคุณไม่ต้องการตรวจสอบความแตกต่างของการสั่งซื้อแน่นอน :-)
schoetbi

30
@schoetbi: ทำไมคุณต้องการตรวจสอบความแตกต่างของการสั่งซื้อในคอนเทนเนอร์ที่ไม่รับประกันการสั่งซื้อ ?
Matti Virkkunen

4
@schoetbi: มันเป็นการนำองค์ประกอบบางอย่างออกจาก IEnumerable อย่างไรก็ตามพจนานุกรมไม่รับประกันการสั่งซื้อดังนั้น.Keysและ.Valuesอาจส่งคืนคีย์และค่าในลำดับใด ๆ ที่พวกเขารู้สึกและคำสั่งนั้นมีแนวโน้มที่จะเปลี่ยนแปลงเมื่อมีการปรับเปลี่ยนพจนานุกรมเช่นกัน ฉันแนะนำให้คุณอ่านว่าพจนานุกรมคืออะไรและอะไรบ้าง
Matti Virkkunen

5
MS 'TestTools และ NUnit มอบ CollectionAssert.AreEquivalent
tymtam

44

ตามที่คนอื่นแนะนำและสังเกตว่าSequenceEqualมีความอ่อนไหวต่อคำสั่ง เพื่อแก้ปัญหาที่คุณสามารถจัดเรียงพจนานุกรมโดยคีย์ (ซึ่งเป็นเอกลักษณ์และทำให้การจัดเรียงที่มีเสถียรภาพเสมอ) SequenceEqualและการใช้งานแล้ว นิพจน์ต่อไปนี้ตรวจสอบว่าพจนานุกรมสองเล่มมีค่าเท่ากันหรือไม่โดยไม่คำนึงถึงลำดับภายใน:

dictionary1.OrderBy(kvp => kvp.Key).SequenceEqual(dictionary2.OrderBy(kvp => kvp.Key))

แก้ไข:ตามที่ Jeppe Stig Nielsen ชี้ให้เห็นวัตถุบางอย่างมีสิ่งIComparer<T>ที่เข้ากันไม่ได้กับพวกเขาIEqualityComparer<T>ซึ่งให้ผลลัพธ์ที่ไม่ถูกต้อง เมื่อใช้กุญแจกับวัตถุเช่นนี้คุณต้องระบุที่ถูกต้องIComparer<T>สำหรับคีย์เหล่านั้น ตัวอย่างเช่นด้วยคีย์สตริง (ซึ่งมีปัญหานี้) คุณต้องทำสิ่งต่อไปนี้เพื่อให้ได้ผลลัพธ์ที่ถูกต้อง:

dictionary1.OrderBy(kvp => kvp.Key, StringComparer.Ordinal).SequenceEqual(dictionary2.OrderBy(kvp => kvp.Key, StringComparer.Ordinal))

เกิดอะไรขึ้นถ้าประเภทคีย์จะไม่CompareTo? ทางออกของคุณจะระเบิดแล้ว จะเกิดอะไรขึ้นถ้าประเภทคีย์มีตัวเปรียบเทียบเริ่มต้นซึ่งไม่สามารถใช้ร่วมกับตัวเปรียบเทียบความเท่าเทียมกันเริ่มต้นได้ นี่เป็นกรณีที่stringคุณรู้ เป็นตัวอย่างพจนานุกรมเหล่านี้ (ที่มีการเปรียบเทียบความเท่าเทียมเริ่มต้นโดยนัย) จะล้มเหลวการทดสอบของคุณ (ภายใต้ทุกวัฒนธรรม infos ฉันรู้):var dictionary1 = new Dictionary<string, int> { { "Strasse", 10 }, { "Straße", 20 }, }; var dictionary2 = new Dictionary<string, int> { { "Straße", 20 }, { "Strasse", 10 }, };
Jeppe Stig Nielsen

@JeppeStigNielsen: สำหรับความไม่ลงรอยกันระหว่างIComparerและIEqualityComparer- ฉันไม่ได้ตระหนักถึงปัญหานี้น่าสนใจมาก! ฉันอัปเดตคำตอบด้วยวิธีแก้ปัญหาที่เป็นไปได้ เกี่ยวกับการขาดCompareToฉันคิดว่านักพัฒนาควรตรวจสอบให้แน่ใจว่าผู้รับมอบสิทธิ์ให้OrderBy()วิธีการคืนสิ่งที่เทียบเคียง ฉันคิดว่านี่เป็นความจริงสำหรับการใช้งานใด ๆ หรือOrderBy()แม้กระทั่งนอกการเปรียบเทียบพจนานุกรม
Allon Guralnek

15

นอกจากนี้ยังกล่าวถึงSequenceEqualซึ่ง

เป็นจริงถ้าสองรายการมีความยาวเท่ากันและองค์ประกอบที่สอดคล้องกันจะเปรียบเทียบเท่ากันตามตัวเปรียบเทียบ

(ซึ่งอาจเป็นตัวเปรียบเทียบแบบเริ่มต้นเช่นแบบ overriden Equals())

เป็นมูลค่าการกล่าวขวัญว่าใน. Net4 มี SetEqualsบนISetวัตถุซึ่ง

ไม่สนใจลำดับขององค์ประกอบและองค์ประกอบที่ซ้ำกัน

ดังนั้นหากคุณต้องการมีรายการวัตถุ แต่ไม่จำเป็นต้องเรียงตามลำดับให้พิจารณาว่าISet(เช่น a HashSet) อาจเป็นตัวเลือกที่เหมาะสม


7

ลองดูที่ วิธีการEnumerable.SequenceEqual

var dictionary = new Dictionary<int, string>() {{1, "a"}, {2, "b"}};
var intList = new List<int> {1, 2};
var stringList = new List<string> {"a", "b"};
var test1 = dictionary.Keys.SequenceEqual(intList);
var test2 = dictionary.Values.SequenceEqual(stringList);

13
สิ่งนี้ไม่น่าเชื่อถือเนื่องจาก SequenceEqual คาดว่าค่าจะออกมาจากพจนานุกรมในลำดับที่เชื่อถือได้ - พจนานุกรมไม่รับประกันการสั่งซื้อและพจนานุกรมคีย์อาจออกมาเป็น [2, 1] แทน [1, 2] และการทดสอบของคุณจะล้มเหลว
Orion Edwards

5

.NET ไม่มีเครื่องมือที่มีประสิทธิภาพสำหรับการเปรียบเทียบคอลเลกชัน ฉันได้พัฒนาวิธีแก้ปัญหาง่ายๆที่คุณสามารถหาได้จากลิงค์ด้านล่าง:

http://robertbouillon.com/2010/04/29/comparing-collections-in-net/

สิ่งนี้จะทำการเปรียบเทียบความเท่าเทียมกันโดยไม่คำนึงถึงลำดับ:

var list1 = new[] { "Bill", "Bob", "Sally" };
var list2 = new[] { "Bob", "Bill", "Sally" };
bool isequal = list1.Compare(list2).IsSame;

สิ่งนี้จะตรวจสอบเพื่อดูว่ามีการเพิ่ม / ลบรายการหรือไม่:

var list1 = new[] { "Billy", "Bob" };
var list2 = new[] { "Bob", "Sally" };
var diff = list1.Compare(list2);
var onlyinlist1 = diff.Removed; //Billy
var onlyinlist2 = diff.Added;   //Sally
var inbothlists = diff.Equal;   //Bob

นี่จะดูว่ารายการใดในพจนานุกรมที่มีการเปลี่ยนแปลง:

var original = new Dictionary<int, string>() { { 1, "a" }, { 2, "b" } };
var changed = new Dictionary<int, string>() { { 1, "aaa" }, { 2, "b" } };
var diff = original.Compare(changed, (x, y) => x.Value == y.Value, (x, y) => x.Value == y.Value);
foreach (var item in diff.Different)
  Console.Write("{0} changed to {1}", item.Key.Value, item.Value.Value);
//Will output: a changed to aaa

10
แน่นอนว่า. NET มีเครื่องมือที่มีประสิทธิภาพสำหรับการเปรียบเทียบคอลเลกชัน .Removedเป็นเช่นเดียวกับlist1.Except(list2), .Addedคือlist2.Except(list1), .Equalเป็นlist1.Intersect(list2)และเป็น.Different original.Join(changed, left => left.Key, right => right.Key, (left, right) => left.Value == right.Value)คุณสามารถทำการเปรียบเทียบใด ๆ กับ LINQ
Allon Guralnek

3
การแก้ไข: คือ.Different original.Join(changed, left => left.Key, right => right.Key, (left, right) => new { Key = left.Key, NewValue = right.Value, Different = left.Value == right.Value).Where(d => d.Different)และคุณยังสามารถเพิ่มได้OldValue = left.Valueหากคุณต้องการค่าเก่าเช่นกัน
Allon Guralnek

3
@AllonGuralnek คำแนะนำของคุณดี แต่พวกเขาไม่จัดการกรณีที่รายการไม่ได้เป็นชุดจริง - โดยที่รายการมีวัตถุเดียวกันหลายครั้ง การเปรียบเทียบ {1, 2} และ {1, 2, 2} จะไม่ส่งคืนอะไรที่เพิ่ม / ลบ
Niall Connaughton


4

ฉันไม่รู้เกี่ยวกับวิธี Enumerable.SequenceEqual (คุณเรียนรู้บางอย่างทุกวัน .... ) แต่ฉันจะแนะนำให้ใช้วิธีการขยาย; บางสิ่งเช่นนี้

    public static bool IsEqual(this List<int> InternalList, List<int> ExternalList)
    {
        if (InternalList.Count != ExternalList.Count)
        {
            return false;
        }
        else
        {
            for (int i = 0; i < InternalList.Count; i++)
            {
                if (InternalList[i] != ExternalList[i])
                    return false;
            }
        }

        return true;

    }

น่าสนใจพอหลังจากอ่าน 2 วินาทีเกี่ยวกับ SequenceEqual ดูเหมือนว่า Microsoft ได้สร้างฟังก์ชันที่ฉันอธิบายให้คุณแล้ว


4

นี่ไม่ใช่การตอบคำถามของคุณโดยตรง แต่ทั้ง TestTools ของ MS และ NUnit จะให้

 CollectionAssert.AreEquivalent

สิ่งไหนที่คุณต้องการ


กำลังมองหาสำหรับการทดสอบนี้ NUnit ของฉัน
blem

1

เพื่อเปรียบเทียบคอลเลกชันคุณยังสามารถใช้ LINQ Enumerable.Intersectส่งคืนคู่ทั้งหมดที่เท่ากัน คุณสามารถเปรียบเทียบพจนานุกรมสองพจนานุกรมนี้:

(dict1.Count == dict2.Count) && dict1.Intersect(dict2).Count() == dict1.Count

จำเป็นต้องทำการเปรียบเทียบครั้งแรกเนื่องจากdict2สามารถมีคีย์ทั้งหมดจากdict1และอื่น ๆ

นอกจากนี้คุณยังสามารถใช้ความคิดเกี่ยวกับรูปแบบที่หลากหลายEnumerable.ExceptและEnumerable.Unionนำไปสู่ผลลัพธ์ที่คล้ายกัน แต่สามารถใช้เพื่อกำหนดความแตกต่างที่แน่นอนระหว่างชุด


1

ตัวอย่างเกี่ยวกับสิ่งนี้:

 static void Main()
{
    // Create a dictionary and add several elements to it.
    var dict = new Dictionary<string, int>();
    dict.Add("cat", 2);
    dict.Add("dog", 3);
    dict.Add("x", 4);

    // Create another dictionary.
    var dict2 = new Dictionary<string, int>();
    dict2.Add("cat", 2);
    dict2.Add("dog", 3);
    dict2.Add("x", 4);

    // Test for equality.
    bool equal = false;
    if (dict.Count == dict2.Count) // Require equal count.
    {
        equal = true;
        foreach (var pair in dict)
        {
            int value;
            if (dict2.TryGetValue(pair.Key, out value))
            {
                // Require value be equal.
                if (value != pair.Value)
                {
                    equal = false;
                    break;
                }
            }
            else
            {
                // Require key be present.
                equal = false;
                break;
            }
        }
    }
    Console.WriteLine(equal);
}

มารยาท: https://www.dotnetperls.com/dictionary-equals


value! = pair.Value ทำการเปรียบเทียบการอ้างอิงใช้ Equals แทน
kofifus

1

สำหรับคอลเลกชันที่สั่งซื้อ (List, Array) ใช้ SequenceEqual

สำหรับการใช้ HashSet SetEquals

สำหรับพจนานุกรมคุณสามารถทำได้:

namespace System.Collections.Generic {
  public static class ExtensionMethods {
    public static bool DictionaryEquals<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> d1, IReadOnlyDictionary<TKey, TValue> d2) {
      if (object.ReferenceEquals(d1, d2)) return true; 
      if (d2 is null || d1.Count != d2.Count) return false;
      foreach (var (d1key, d1value) in d1) {
        if (!d2.TryGetValue(d1key, out TValue d2value)) return false;
        if (!d1value.Equals(d2value)) return false;
      }
      return true;
    }
  }
}

(โซลูชันที่ปรับให้เหมาะสมที่สุดจะใช้การเรียงลำดับ แต่จะต้องใช้IComparable<TValue>)


0

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


0

ไม่เพราะกรอบไม่ทราบวิธีการเปรียบเทียบเนื้อหาของรายการของคุณ

ดูที่นี้:

http://blogs.msdn.com/abhinaba/archive/2005/10/11/479537.aspx


3
ไม่มีตัวเลือกหลายตัวที่บอกกรอบการเปรียบเทียบองค์ประกอบหรือไม่ IComparer<T>เอาชนะobject.Equals, IEquatable<T>, IComparable<T>...
สเตฟาน Steinegger

0
public bool CompareStringLists(List<string> list1, List<string> list2)
{
    if (list1.Count != list2.Count) return false;

    foreach(string item in list1)
    {
        if (!list2.Contains(item)) return false;
    }

    return true;
}

0

อย่างน้อยก็ฉันก็เชื่อเช่นนั้น เหตุผลเบื้องหลังคือความเท่าเทียมกันของคอลเลกชันอาจเป็นพฤติกรรมที่ผู้ใช้กำหนด

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

{1, 2, 3, 4}
{4, 3, 2, 1}

พวกเขาเท่ากันหรือไม่? คุณต้องรู้ แต่ฉันไม่รู้ว่ามุมมองของคุณคืออะไร

คอลเลกชันจะไม่มีการเรียงลำดับแนวคิดโดยค่าเริ่มต้นจนกว่าอัลกอริทึมจะมีกฎการเรียงลำดับ สิ่งเดียวกันกับที่เซิร์ฟเวอร์ SQL จะให้ความสนใจคือเมื่อคุณพยายามทำการแบ่งหน้าคุณต้องจัดเตรียมกฎการเรียงลำดับ:

https://docs.microsoft.com/en-US/sql/t-sql/queries/select-order-by-clause-transact-sql?view=sql-server-2017

อีกสองคอลเล็กชัน:

{1, 2, 3, 4}
{1, 1, 1, 2, 2, 3, 4}

อีกครั้งพวกเขาเท่ากันหรือไม่? คุณบอกฉัน ..

องค์ประกอบการทำซ้ำของคอลเลกชันมีบทบาทในสถานการณ์ที่แตกต่างกันและบางคอลเลกชันเช่น Dictionary<TKey, TValue>ไม่อนุญาตให้มีองค์ประกอบซ้ำ

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

ในกรณีทั่วไปEnumerable.SequenceEqualก็ดีพอ แต่มันกลับเท็จในกรณีต่อไปนี้:

var a = new Dictionary<String, int> { { "2", 2 }, { "1", 1 }, };
var b = new Dictionary<String, int> { { "1", 1 }, { "2", 2 }, };
Debug.Print("{0}", a.SequenceEqual(b)); // false

ฉันอ่านคำตอบสำหรับคำถามเช่นนี้ (คุณอาจgoogleสำหรับพวกเขา) และสิ่งที่ฉันจะใช้โดยทั่วไป:

public static class CollectionExtensions {
    public static bool Represents<T>(this IEnumerable<T> first, IEnumerable<T> second) {
        if(object.ReferenceEquals(first, second)) {
            return true;
        }

        if(first is IOrderedEnumerable<T> && second is IOrderedEnumerable<T>) {
            return Enumerable.SequenceEqual(first, second);
        }

        if(first is ICollection<T> && second is ICollection<T>) {
            if(first.Count()!=second.Count()) {
                return false;
            }
        }

        first=first.OrderBy(x => x.GetHashCode());
        second=second.OrderBy(x => x.GetHashCode());
        return CollectionExtensions.Represents(first, second);
    }
}

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

  • GetHashCode()เป็นเพียงการสั่งซื้อไม่ได้สำหรับความเท่าเทียมกัน; ฉันคิดว่ามันเพียงพอในกรณีนี้

  • Count() จะไม่แจกแจงคอลเลกชันและตกอยู่ในการดำเนินการของ ICollection<T>.Count

  • ถ้าการอ้างอิงเท่ากันมันก็แค่ Boris

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