การเปรียบเทียบสองคอลเลกชันเพื่อความเท่าเทียมกันโดยไม่คำนึงถึงลำดับของรายการในพวกเขา


162

ฉันต้องการเปรียบเทียบคอลเลกชันสอง (ใน C #) แต่ฉันไม่แน่ใจว่าวิธีที่ดีที่สุดในการใช้งานได้อย่างมีประสิทธิภาพ

ฉันได้อ่านหัวข้ออื่น ๆ เกี่ยวกับEnumerable.SequenceEqualแต่ไม่ใช่สิ่งที่ฉันต้องการ

ในกรณีของฉันคอลเลกชันสองรายการจะเท่ากันถ้าทั้งคู่มีรายการเดียวกัน (ไม่ว่าจะเรียงตามลำดับ)

ตัวอย่าง:

collection1 = {1, 2, 3, 4};
collection2 = {2, 4, 1, 3};

collection1 == collection2; // true

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

if (collection1.Count != collection2.Count)
    return false; // the collections are not equal

foreach (Item item in collection1)
{
    if (!collection2.Contains(item))
        return false; // the collections are not equal
}

foreach (Item item in collection2)
{
    if (!collection1.Contains(item))
        return false; // the collections are not equal
}

return true; // the collections are equal

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

ตัวอย่างที่ฉันนึกได้ก็คือผิด:

collection1 = {1, 2, 3, 3, 4}
collection2 = {1, 2, 2, 3, 4}

ซึ่งจะเท่ากับการใช้งานของฉัน ฉันควรนับจำนวนครั้งที่พบแต่ละรายการแล้วและตรวจสอบให้แน่ใจว่าการนับมีค่าเท่ากันในทั้งสองคอลเลกชัน?


ตัวอย่างอยู่ในรูปแบบของ C # (ลองเรียกมันว่า pseudo-C #) แต่ให้คำตอบในภาษาใดก็ได้ที่คุณต้องการมันไม่สำคัญ

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


1
อัลกอริทึมเป็นอย่างไร คำตอบทั้งหมดที่เกี่ยวข้องโดยการเปรียบเทียบรายการทั่วไปเปรียบเทียบ linq ฯลฯ เราจริง ๆ สัญญากับใครบางคนที่เราจะไม่ใช้อัลกอริทึมเป็นโปรแกรมเมอร์แบบเก่าหรือไม่
นูริ YILMAZ

คุณไม่ได้ตรวจสอบความเท่าเทียมกันที่คุณกำลังตรวจสอบความเท่าเทียมกัน มันเป็น nitpicky แต่มีความแตกต่างที่สำคัญ และเมื่อนานมาแล้ว นี่คือ Q + A ที่ดี
CAD bloke

คุณอาจสนใจโพสต์นี้ซึ่งกล่าวถึงวิธีปรับตามพจนานุกรมที่อธิบายไว้ด้านล่าง ปัญหาหนึ่งเกี่ยวกับวิธีใช้พจนานุกรมที่ง่ายที่สุดคือพวกเขาไม่สามารถจัดการค่า null ได้อย่างถูกต้องเนื่องจากคลาส Dictionary ของ. NET ไม่อนุญาตให้ใช้คีย์ที่มีค่าว่าง
ChaseMedallion

คำตอบ:


112

ปรากฎว่า Microsoft มีสิ่งนี้ครอบคลุมอยู่ในกรอบการทดสอบแล้ว: CollectionAssert.AreEquivalent

หมายเหตุ

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

ใช้ตัวสะท้อนสัญญาณฉันแก้ไขโค้ดที่อยู่เบื้องหลัง AreEquivalent () เพื่อสร้างเครื่องมือเปรียบเทียบความเท่าเทียมกัน มันมีความสมบูรณ์มากกว่าคำตอบที่มีอยู่เนื่องจากคำนึงถึงค่า Null ใช้ IEqualityComparer และมีประสิทธิภาพและการตรวจสอบเคส บวกเป็นMicrosoft :)

public class MultiSetComparer<T> : IEqualityComparer<IEnumerable<T>>
{
    private readonly IEqualityComparer<T> m_comparer;
    public MultiSetComparer(IEqualityComparer<T> comparer = null)
    {
        m_comparer = comparer ?? EqualityComparer<T>.Default;
    }

    public bool Equals(IEnumerable<T> first, IEnumerable<T> second)
    {
        if (first == null)
            return second == null;

        if (second == null)
            return false;

        if (ReferenceEquals(first, second))
            return true;

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

            if (firstCollection.Count == 0)
                return true;
        }

        return !HaveMismatchedElement(first, second);
    }

    private bool HaveMismatchedElement(IEnumerable<T> first, IEnumerable<T> second)
    {
        int firstNullCount;
        int secondNullCount;

        var firstElementCounts = GetElementCounts(first, out firstNullCount);
        var secondElementCounts = GetElementCounts(second, out secondNullCount);

        if (firstNullCount != secondNullCount || firstElementCounts.Count != secondElementCounts.Count)
            return true;

        foreach (var kvp in firstElementCounts)
        {
            var firstElementCount = kvp.Value;
            int secondElementCount;
            secondElementCounts.TryGetValue(kvp.Key, out secondElementCount);

            if (firstElementCount != secondElementCount)
                return true;
        }

        return false;
    }

    private Dictionary<T, int> GetElementCounts(IEnumerable<T> enumerable, out int nullCount)
    {
        var dictionary = new Dictionary<T, int>(m_comparer);
        nullCount = 0;

        foreach (T element in enumerable)
        {
            if (element == null)
            {
                nullCount++;
            }
            else
            {
                int num;
                dictionary.TryGetValue(element, out num);
                num++;
                dictionary[element] = num;
            }
        }

        return dictionary;
    }

    public int GetHashCode(IEnumerable<T> enumerable)
    {
        if (enumerable == null) throw new ArgumentNullException(nameof(enumerable));

        int hash = 17;

        foreach (T val in enumerable.OrderBy(x => x))
            hash = hash * 23 + (val?.GetHashCode() ?? 42);

        return hash;
    }
}

ตัวอย่างการใช้งาน:

var set = new HashSet<IEnumerable<int>>(new[] {new[]{1,2,3}}, new MultiSetComparer<int>());
Console.WriteLine(set.Contains(new [] {3,2,1})); //true
Console.WriteLine(set.Contains(new [] {1, 2, 3, 3})); //false

หรือถ้าคุณต้องการเปรียบเทียบสองคอลเลกชันโดยตรง:

var comp = new MultiSetComparer<string>();
Console.WriteLine(comp.Equals(new[] {"a","b","c"}, new[] {"a","c","b"})); //true
Console.WriteLine(comp.Equals(new[] {"a","b","c"}, new[] {"a","b"})); //false

สุดท้ายคุณสามารถใช้เครื่องมือเปรียบเทียบความเท่าเทียมกันที่คุณเลือกได้

var strcomp = new MultiSetComparer<string>(StringComparer.OrdinalIgnoreCase);
Console.WriteLine(strcomp.Equals(new[] {"a", "b"}, new []{"B", "A"})); //true

7
ฉันไม่แน่ใจ 100% แต่ฉันคิดว่าคำตอบของคุณละเมิดข้อกำหนดการใช้งานของ Microsoft กับวิศวกรรมย้อนกลับ
เอียนดัลลัส

1
สวัสดี Ohad โปรดอ่านการถกเถียงที่ยาวนานต่อไปนี้ในหัวข้อstackoverflow.com/questions/371328/ ...... ถ้าคุณเปลี่ยนแฮชโค้ดของวัตถุในขณะที่แฮชเซ็ตมันจะถูกขัดจังหวะด้วยการกระทำที่เหมาะสมของแฮชเซ็ตและอาจทำให้เกิดข้อยกเว้น กฎมีดังต่อไปนี้: หากวัตถุสองอันเท่ากับ - พวกเขาจะต้องมีรหัสแฮชเดียวกัน หากวัตถุสองชิ้นมีแฮชโค้ดเดียวกัน - มันไม่จำเป็นที่จะต้องเท่ากัน Hashcode ต้องคงเดิมตลอดอายุการใช้งานของวัตถุ! นั่นเป็นเหตุผลที่คุณผลักดันให้ ICompareable และ IEqualrity
James Roeiter

2
@JamesRoeiter ความคิดเห็นของฉันอาจทำให้เข้าใจผิด เมื่อพจนานุกรมพบ hashcode ที่มีอยู่แล้วมันจะตรวจสอบความเท่าเทียมกันที่แท้จริงด้วยEqualityComparer(ทั้งที่คุณให้มาหรือEqualityComparer.Defaultคุณสามารถตรวจสอบ Reflector หรือแหล่งอ้างอิงเพื่อตรวจสอบสิ่งนี้) จริงถ้าวัตถุมีการเปลี่ยนแปลง (และโดยเฉพาะการเปลี่ยนแปลงแฮชโค้ดของพวกเขา) ในขณะที่วิธีนี้กำลังทำงานอยู่ผลลัพธ์ที่ไม่คาดคิด แต่นั่นก็หมายความว่าวิธีนี้ไม่ปลอดภัยสำหรับเธรดในบริบทนี้
Ohad Schneider

1
@JamesRoeiter สมมติว่า x และ y เป็นสองวัตถุที่เราต้องการเปรียบเทียบ หากพวกเขามีแฮชโค้ดที่แตกต่างกันเรารู้ว่ามันต่างกัน (เพราะไอเท็มเท่ากันมีแฮชโคดเท่ากัน) และการใช้งานด้านบนนั้นถูกต้อง หากมีแฮชโค้ดเดียวกันการใช้พจนานุกรมจะตรวจสอบความเท่าเทียมกันจริงโดยใช้ที่ระบุEqualityComparer(หรือEqualityComparer.Defaultหากไม่มีการระบุไว้) และการนำไปใช้นั้นถูกต้องอีกครั้ง
Ohad Schneider

1
@CADbloke วิธีการจะต้องมีชื่อEqualsเพราะIEqualityComparer<T>อินเตอร์เฟซ สิ่งที่คุณควรดูคือชื่อของตัวเปรียบเทียบ ในกรณีนี้มันMultiSetComparerทำให้รู้สึก
Ohad Schneider

98

วิธีแก้ปัญหาที่ง่ายและมีประสิทธิภาพพอสมควรคือการจัดเรียงคอลเลกชันทั้งสองจากนั้นเปรียบเทียบเพื่อความเท่าเทียมกัน:

bool equal = collection1.OrderBy(i => i).SequenceEqual(
                 collection2.OrderBy(i => i));

อัลกอริทึมนี้คือ O (N * logN) ในขณะที่โซลูชันของคุณด้านบนคือ O (N ^ 2)

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


1
คุณเพียงแค่ต้องเพิ่มการใช้ System.Linq; เป็นคนแรกที่จะทำให้มันทำงาน
จูเนียร์Mayhé

หากรหัสนี้อยู่ในลูปและ collection1 ได้รับการอัพเดตและ collection2 ยังคงไม่ถูกแตะต้องสังเกตว่าแม้ว่าทั้งสองคอลเลกชันจะมีวัตถุเดียวกันดีบักเกอร์จะแสดงเท็จสำหรับตัวแปร "เท่ากับ" นี้
จูเนียร์Mayhé

5
@Chaulky - ฉันเชื่อว่า OrderBy เป็นสิ่งจำเป็น ดู: dotnetfiddle.net/jA8iwE
Brett

คำตอบอื่นที่เรียกว่า "เหนือ" คืออะไร อาจเป็นไปได้stackoverflow.com/a/50465/3195477 ?
UuDdLrLrSs

32

สร้างพจนานุกรม "dict" จากนั้นสำหรับสมาชิกแต่ละคนในกลุ่มแรกให้ทำ dict [member] ++;

จากนั้นวนซ้ำคอลเล็กชันที่สองด้วยวิธีเดียวกัน แต่สำหรับสมาชิกแต่ละคนจะเลือก [สมาชิก] -

ในตอนท้ายวนสมาชิกทั้งหมดในพจนานุกรม:

    private bool SetEqual (List<int> left, List<int> right) {

        if (left.Count != right.Count)
            return false;

        Dictionary<int, int> dict = new Dictionary<int, int>();

        foreach (int member in left) {
            if (dict.ContainsKey(member) == false)
                dict[member] = 1;
            else
                dict[member]++;
        }

        foreach (int member in right) {
            if (dict.ContainsKey(member) == false)
                return false;
            else
                dict[member]--;
        }

        foreach (KeyValuePair<int, int> kvp in dict) {
            if (kvp.Value != 0)
                return false;
        }

        return true;

    }

แก้ไข: เท่าที่ฉันสามารถบอกได้ว่านี่เป็นลำดับเดียวกับอัลกอริทึมที่มีประสิทธิภาพที่สุด อัลกอริทึมนี้คือ O (N) สมมติว่าพจนานุกรมใช้การค้นหา O (1)


นี่คือสิ่งที่ฉันต้องการ อย่างไรก็ตามฉันต้องการที่จะทำเช่นนี้แม้ว่าฉันจะไม่ได้ใช้จำนวนเต็ม ฉันต้องการใช้วัตถุอ้างอิง แต่พวกมันทำงานไม่ถูกต้องเหมือนเป็นกุญแจในพจนานุกรม
mbillard

โมโนคำถามของคุณเป็นสิ่งที่สงสัยถ้ารายการของคุณไม่ได้เปรียบเทียบ หากไม่สามารถใช้เป็นกุญแจในพจนานุกรมจะไม่มีวิธีแก้ไข
skolima

1
ฉันคิดว่าโมโนหมายความว่ากุญแจไม่สามารถจัดเรียงได้ แต่โซลูชันของ Daniel นั้นตั้งใจไว้อย่างชัดเจนว่าจะนำไปใช้กับ hashtable ไม่ใช่ tree และจะทำงานได้ตราบใดที่ยังมีการทดสอบความเท่าเทียมและฟังก์ชัน hash
erickson

เพิ่มขึ้นแน่นอนสำหรับความช่วยเหลือ แต่ไม่ได้รับการยอมรับเพราะมันหายไปเป็นจุดสำคัญ (ซึ่งฉันครอบคลุมในคำตอบของฉัน)
mbillard

1
FWIW คุณสามารถทำให้ลูป foreach ล่าสุดและส่งคืนคำสั่งง่ายขึ้นด้วย:return dict.All(kvp => kvp.Value == 0);
Tyson Williams

18

นี่คือ (ได้รับอิทธิพลอย่างมากจาก D.Jennings) การใช้งานทั่วไปของวิธีเปรียบเทียบ (ใน C #)

/// <summary>
/// Represents a service used to compare two collections for equality.
/// </summary>
/// <typeparam name="T">The type of the items in the collections.</typeparam>
public class CollectionComparer<T>
{
    /// <summary>
    /// Compares the content of two collections for equality.
    /// </summary>
    /// <param name="foo">The first collection.</param>
    /// <param name="bar">The second collection.</param>
    /// <returns>True if both collections have the same content, false otherwise.</returns>
    public bool Execute(ICollection<T> foo, ICollection<T> bar)
    {
        // Declare a dictionary to count the occurence of the items in the collection
        Dictionary<T, int> itemCounts = new Dictionary<T,int>();

        // Increase the count for each occurence of the item in the first collection
        foreach (T item in foo)
        {
            if (itemCounts.ContainsKey(item))
            {
                itemCounts[item]++;
            }
            else
            {
                itemCounts[item] = 1;
            }
        }

        // Wrap the keys in a searchable list
        List<T> keys = new List<T>(itemCounts.Keys);

        // Decrease the count for each occurence of the item in the second collection
        foreach (T item in bar)
        {
            // Try to find a key for the item
            // The keys of a dictionary are compared by reference, so we have to
            // find the original key that is equivalent to the "item"
            // You may want to override ".Equals" to define what it means for
            // two "T" objects to be equal
            T key = keys.Find(
                delegate(T listKey)
                {
                    return listKey.Equals(item);
                });

            // Check if a key was found
            if(key != null)
            {
                itemCounts[key]--;
            }
            else
            {
                // There was no occurence of this item in the first collection, thus the collections are not equal
                return false;
            }
        }

        // The count of each item should be 0 if the contents of the collections are equal
        foreach (int value in itemCounts.Values)
        {
            if (value != 0)
            {
                return false;
            }
        }

        // The collections are equal
        return true;
    }
}

12
เป็นงานที่ดี แต่หมายเหตุ: 1. ตรงกันข้ามกับวิธีแก้ปัญหาของ Daniel Jennings นี่ไม่ใช่ O (N) แต่ค่อนข้าง O (N ^ 2) เนื่องจากฟังก์ชั่น find ภายในลูป foreach บนคอลเลคชั่น; 2. คุณสามารถสรุปวิธีการยอมรับ IEnumerable <T> แทน ICollection <T> โดยไม่ต้องแก้ไขโค้ดอีก
Ohad Schneider

The keys of a dictionary are compared by reference, so we have to find the original key that is equivalent to the "item"- นี่ไม่เป็นความจริง. อัลกอริทึมนั้นตั้งอยู่บนสมมติฐานที่ผิดและในขณะที่ใช้งานมันไม่มีประสิทธิภาพมากนัก
Antonín Lejsek

10

คุณสามารถใช้HashSet ดูวิธีSetEquals


2
แน่นอนว่าการใช้ HashSet จะถือว่าไม่มีการซ้ำซ้อน แต่ถ้าเป็นเช่นนั้น HashSet จะเป็นวิธีที่ดีที่สุด
Mark Cidade

7

ถ้าคุณใช้Shouldlyคุณสามารถใช้ ShouldAllBe กับมี

collection1 = {1, 2, 3, 4};
collection2 = {2, 4, 1, 3};

collection1.ShouldAllBe(item=>collection2.Contains(item)); // true

และในที่สุดคุณสามารถเขียนส่วนขยาย

public static class ShouldlyIEnumerableExtensions
{
    public static void ShouldEquivalentTo<T>(this IEnumerable<T> list, IEnumerable<T> equivalent)
    {
        list.ShouldAllBe(l => equivalent.Contains(l));
    }
}

UPDATE

มีพารามิเตอร์ทางเลือกในวิธีShouldBe

collection1.ShouldBe(collection2, ignoreOrder: true); // true

1
ฉันเพิ่งพบรุ่นล่าสุดที่มีพารามิเตอร์bool ignoreOrderในวิธีShouldBe
Pier-Lionel Sgard

5

แก้ไข: ฉันรู้ทันทีที่ฉันโพสต์ว่าสิ่งนี้ใช้ได้กับฉากเท่านั้น - มันจะไม่จัดการกับคอลเลกชันที่มีรายการที่ซ้ำกัน ตัวอย่างเช่น {1, 1, 2} และ {2, 2, 1} จะได้รับการพิจารณาเท่ากันจากมุมมองของอัลกอริทึมนี้ หากคอลเลกชันของคุณเป็นเซต (หรือสามารถวัดความเท่ากันได้) ฉันหวังว่าคุณจะพบว่ามีประโยชน์ด้านล่าง

โซลูชันที่ฉันใช้คือ:

return c1.Count == c2.Count && c1.Intersect(c2).Count() == c1.Count;

Linq ทำสิ่งที่พจนานุกรมภายใต้หน้าปกดังนั้นนี่คือ O (N) (โปรดทราบว่ามันเป็น O (1) หากคอลเล็กชันมีขนาดไม่เท่ากัน)

ฉันตรวจสอบสติโดยใช้วิธี "SetEqual" ที่แนะนำโดย Daniel วิธี OrderBy / SequenceEquals ที่ Igor แนะนำและคำแนะนำของฉัน ผลลัพธ์ด้านล่างแสดง O (N * LogN) สำหรับ Igor และ O (N) สำหรับเหมืองและ Daniel's

ฉันคิดว่าความเรียบง่ายของโค้ดตัดกัน Linq ทำให้เป็นโซลูชันที่เป็นที่นิยมมากกว่า

__Test Latency(ms)__
N, SetEquals, OrderBy, Intersect    
1024, 0, 0, 0    
2048, 0, 0, 0    
4096, 31.2468, 0, 0    
8192, 62.4936, 0, 0    
16384, 156.234, 15.6234, 0    
32768, 312.468, 15.6234, 46.8702    
65536, 640.5594, 46.8702, 31.2468    
131072, 1312.3656, 93.7404, 203.1042    
262144, 3765.2394, 187.4808, 187.4808    
524288, 5718.1644, 374.9616, 406.2084    
1048576, 11420.7054, 734.2998, 718.6764    
2097152, 35090.1564, 1515.4698, 1484.223

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

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

เมธอด Intersect ส่งคืนคอลเล็กชันที่ชัดเจน รับ a = {1,1,2} และ b = {2,2,1}, a.Intersect (b) .Count ()! = a.Count ซึ่งทำให้การแสดงออกของคุณกลับเท็จอย่างถูกต้อง {1,2} .Count! = {1,1,2} .Count ดูลิงค์ [/ link] (โปรดทราบว่าทั้งสองฝ่ายมีความชัดเจนก่อนการเปรียบเทียบ)
Griffin

5

ในกรณีที่ไม่มีการทำซ้ำและไม่มีคำสั่ง EqualityComparer ต่อไปนี้สามารถใช้เพื่ออนุญาตการรวบรวมเป็นคีย์พจนานุกรม:

public class SetComparer<T> : IEqualityComparer<IEnumerable<T>> 
where T:IComparable<T>
{
    public bool Equals(IEnumerable<T> first, IEnumerable<T> second)
    {
        if (first == second)
            return true;
        if ((first == null) || (second == null))
            return false;
        return first.ToHashSet().SetEquals(second);
    }

    public int GetHashCode(IEnumerable<T> enumerable)
    {
        int hash = 17;

        foreach (T val in enumerable.OrderBy(x => x))
            hash = hash * 23 + val.GetHashCode();

        return hash;
    }
}

นี่คือการติดตั้ง ToHashSet () ที่ฉันใช้ อัลกอริทึมรหัสกัญชามาจากการมีผลบังคับใช้ Java (โดยวิธีการของจอนสกีต)


อะไรคือจุดของ Serializable สำหรับคลาส Comparer? : o คุณยังสามารถเปลี่ยนอินพุตเป็นISet<T>แสดงความหมายสำหรับชุด (เช่นไม่มีการทำซ้ำ)
nawfal

@nawfal ขอบคุณไม่รู้ว่าฉันคิดอย่างไรเมื่อฉันทำเครื่องหมายเป็น Serializable ... สำหรับISetความคิดที่นี่คือการปฏิบัติIEnumerableตามชุด (เพราะคุณได้IEnumerableเริ่มต้นด้วย) แม้ว่าการพิจารณา 0 upvotes มากกว่า 5 ปีที่อาจไม่ใช่ความคิดที่คมชัดที่สุด: P
Ohad Schneider

4
static bool SetsContainSameElements<T>(IEnumerable<T> set1, IEnumerable<T> set2) {
    var setXOR = new HashSet<T>(set1);
    setXOR.SymmetricExceptWith(set2);
    return (setXOR.Count == 0);
}

โซลูชันต้องการ. NET 3.5 และSystem.Collections.Genericเนมสเปซ ตามที่ไมโครซอฟท์ , SymmetricExceptWithเป็นO (n + m)การดำเนินงานที่มีnแทนจำนวนขององค์ประกอบในชุดแรกและmแทนจำนวนขององค์ประกอบในครั้งที่สอง คุณสามารถเพิ่มตัวเปรียบเทียบความเท่าเทียมกันลงในฟังก์ชันนี้ได้เสมอหากจำเป็น


3

ทำไมไม่ใช้. ยกเว้น ()

// Create the IEnumerable data sources.
string[] names1 = System.IO.File.ReadAllLines(@"../../../names1.txt");
string[] names2 = System.IO.File.ReadAllLines(@"../../../names2.txt");
// Create the query. Note that method syntax must be used here.
IEnumerable<string> differenceQuery =   names1.Except(names2);
// Execute the query.
Console.WriteLine("The following lines are in names1.txt but not names2.txt");
foreach (string s in differenceQuery)
     Console.WriteLine(s);

http://msdn.microsoft.com/en-us/library/bb397894.aspx


2
Exceptจะไม่ทำงานสำหรับการนับรายการที่ซ้ำกัน มันจะส่งคืนค่าจริงสำหรับชุด {1,2,2} และ {1,1,2}
Cristian Diaconescu

@CristiDiaconescu คุณสามารถทำ ".Distinct ()" ก่อนที่จะลบรายการที่ซ้ำกัน
Korayem

[1, 1, 2] != [1, 2, 2]สหกรณ์จะขอ การใช้Distinctจะทำให้พวกเขาดูเท่ากัน
Cristian Diaconescu

2

โพสต์ซ้ำกันของแปลก แต่ตรวจสอบแก้ปัญหาของฉันสำหรับการเปรียบเทียบคอลเลกชัน มันค่อนข้างง่าย:

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

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

โพสต์ต้นฉบับที่นี่


1

เอริกเกือบขวา: ตั้งแต่ที่คุณต้องการเพื่อให้ตรงกับที่ได้จากจำนวนของรายการที่ซ้ำกันคุณต้องการกระเป๋า ใน Java ดูเหมือนว่า:

(new HashBag(collection1)).equals(new HashBag(collection2))

ฉันแน่ใจว่า C # มีการติดตั้งในตัว ฉันจะใช้มันก่อน หากประสิทธิภาพเป็นปัญหาคุณสามารถใช้ชุดการนำไปใช้ต่างกันได้ตลอดเวลา แต่ใช้อินเทอร์เฟซชุดเดียวกัน


1

นี่คือตัวแปรส่วนขยายของฉันสำหรับคำตอบของ ohadsc ในกรณีที่มีประโยชน์กับใครบางคน

static public class EnumerableExtensions 
{
    static public bool IsEquivalentTo<T>(this IEnumerable<T> first, IEnumerable<T> second)
    {
        if ((first == null) != (second == null))
            return false;

        if (!object.ReferenceEquals(first, second) && (first != null))
        {
            if (first.Count() != second.Count())
                return false;

            if ((first.Count() != 0) && HaveMismatchedElement<T>(first, second))
                return false;
        }

        return true;
    }

    private static bool HaveMismatchedElement<T>(IEnumerable<T> first, IEnumerable<T> second)
    {
        int firstCount;
        int secondCount;

        var firstElementCounts = GetElementCounts<T>(first, out firstCount);
        var secondElementCounts = GetElementCounts<T>(second, out secondCount);

        if (firstCount != secondCount)
            return true;

        foreach (var kvp in firstElementCounts)
        {
            firstCount = kvp.Value;
            secondElementCounts.TryGetValue(kvp.Key, out secondCount);

            if (firstCount != secondCount)
                return true;
        }

        return false;
    }

    private static Dictionary<T, int> GetElementCounts<T>(IEnumerable<T> enumerable, out int nullCount)
    {
        var dictionary = new Dictionary<T, int>();
        nullCount = 0;

        foreach (T element in enumerable)
        {
            if (element == null)
            {
                nullCount++;
            }
            else
            {
                int num;
                dictionary.TryGetValue(element, out num);
                num++;
                dictionary[element] = num;
            }
        }

        return dictionary;
    }

    static private int GetHashCode<T>(IEnumerable<T> enumerable)
    {
        int hash = 17;

        foreach (T val in enumerable.OrderBy(x => x))
            hash = hash * 23 + val.GetHashCode();

        return hash;
    }
}

ความคิดนี้มีประสิทธิภาพเพียงใด
nawfal

ฉันใช้สิ่งนี้กับคอลเล็กชันขนาดเล็กเท่านั้นดังนั้นจึงไม่ได้คิดถึงความซับซ้อนของ Big-O หรือทำการเปรียบเทียบ HaveMismatchedElements เพียงอย่างเดียวคือ O (M * N) ดังนั้นจึงอาจทำงานได้ไม่ดีสำหรับคอลเลกชันขนาดใหญ่
Eric J.

หากIEnumerable<T>เป็นคำสั่งแล้วการโทรCount()ไม่ใช่ความคิดที่ดี คำตอบดั้งเดิมของ Ohad คือการตรวจสอบว่าICollection<T>เป็นความคิดที่ดีกว่า
nawfal

1

นี่คือวิธีการแก้ปัญหาที่มีการปรับปรุงในช่วงนี้

public static bool HasSameElementsAs<T>(
        this IEnumerable<T> first, 
        IEnumerable<T> second, 
        IEqualityComparer<T> comparer = null)
    {
        var firstMap = first
            .GroupBy(x => x, comparer)
            .ToDictionary(x => x.Key, x => x.Count(), comparer);

        var secondMap = second
            .GroupBy(x => x, comparer)
            .ToDictionary(x => x.Key, x => x.Count(), comparer);

        if (firstMap.Keys.Count != secondMap.Keys.Count)
            return false;

        if (firstMap.Keys.Any(k1 => !secondMap.ContainsKey(k1)))
            return false;

        return firstMap.Keys.All(x => firstMap[x] == secondMap[x]);
    }

0

มีวิธีแก้ไขปัญหานี้มากมาย หากคุณไม่สนใจเรื่องซ้ำคุณไม่ต้องเรียงลำดับทั้งคู่ ก่อนอื่นตรวจสอบให้แน่ใจว่ามีจำนวนรายการเท่ากัน หลังจากนั้นจัดเรียงหนึ่งในคอลเลกชัน จากนั้นให้ค้นหาแต่ละรายการจากคอลเล็กชันที่สองในคอลเล็กชันที่เรียงลำดับ หากคุณไม่พบรายการที่กำหนดหยุดและกลับเท็จ ความซับซ้อนของสิ่งนี้: - การเรียงลำดับคอลเลกชันแรก: N Log (N) - ค้นหาแต่ละรายการจากวินาทีเป็นครั้งแรก: Nเข้าสู่ระบบ (N) เพื่อให้คุณจบลงด้วย 2 * N * LOG (N) สมมติว่าพวกเขาจับคู่และคุณค้นหาทุกอย่าง สิ่งนี้คล้ายกับความซับซ้อนของการเรียงลำดับทั้งสอง นอกจากนี้ยังช่วยให้คุณได้รับประโยชน์ที่จะหยุดก่อนหน้านี้หากมีความแตกต่าง อย่างไรก็ตามโปรดจำไว้ว่าหากทั้งสองถูกเรียงลำดับก่อนที่คุณจะก้าวเข้าสู่การเปรียบเทียบนี้และคุณลองจัดเรียงโดยใช้บางสิ่งบางอย่างเช่น qsort การเรียงลำดับจะมีราคาแพงกว่า มีการเพิ่มประสิทธิภาพสำหรับสิ่งนี้ อีกทางเลือกหนึ่งซึ่งเหมาะอย่างยิ่งสำหรับคอลเล็กชั่นขนาดเล็กที่คุณทราบช่วงขององค์ประกอบคือการใช้ดัชนีบิทมาสค์ สิ่งนี้จะทำให้คุณมีประสิทธิภาพ O (n) อีกทางเลือกหนึ่งคือการใช้แฮชและค้นหามัน สำหรับคอลเลกชันขนาดเล็กมักจะดีกว่าการเรียงลำดับหรือดัชนี bitmask Hashtable มีข้อเสียของสถานที่ที่แย่กว่านั้นโปรดจำไว้ อีกครั้งนั่นก็ต่อเมื่อคุณสวม ' ไม่ต้องสนใจเรื่องซ้ำซ้อน หากคุณต้องการบัญชีที่ซ้ำกันไปด้วยการเรียงลำดับทั้งสอง


0

ในหลายกรณีคำตอบที่เหมาะสมคือ Igor Ostrovsky คำตอบอื่น ๆ จะขึ้นอยู่กับรหัสแฮชของออบเจ็กต์ แต่เมื่อคุณสร้างรหัสแฮชสำหรับวัตถุที่คุณทำขึ้นอยู่กับเขตข้อมูล IMMUTABLE ของเขาเท่านั้น - เช่นเขตข้อมูลรหัสวัตถุ (ในกรณีของเอนทิตีฐานข้อมูล) - เหตุใดจึงสำคัญที่ต้องแทนที่ GetHashCode เมื่อวิธี Equals ถูกแทนที่

ซึ่งหมายความว่าหากคุณเปรียบเทียบสองคอลเลกชันผลลัพธ์อาจเป็นจริงของวิธีการเปรียบเทียบแม้ว่าฟิลด์ของรายการต่าง ๆ จะไม่เท่ากัน ในการเปรียบเทียบคอลเล็กชั่นคุณต้องใช้วิธีการของ Igor และใช้ IEqualirity

โปรดอ่านความคิดเห็นของฉันและ mr.Schnider บนโพสต์ที่ได้รับการโหวตมากที่สุดของเขา

เจมส์


0

เพื่อให้สามารถซ้ำกันในIEnumerable<T>(ถ้าชุดไม่ได้เป็นที่น่าพอใจ \ เป็นไปได้) และ "การสั่งซื้อไม่สนใจ" .GroupBy()คุณควรจะสามารถที่จะใช้

ฉันไม่ใช่ผู้เชี่ยวชาญเกี่ยวกับการวัดความซับซ้อน แต่ความเข้าใจพื้นฐานของฉันคือสิ่งนี้ควรเป็น O (n) ผมเข้าใจ O (n ^ 2) ว่ามาจากการดำเนินการดำเนินการ O (n) ภายในอีก O (n) ListA.Where(a => ListB.Contains(a)).ToList()การดำเนินการเช่น ทุกรายการใน ListB จะถูกประเมินเพื่อความเท่าเทียมกันกับแต่ละรายการใน ListA

อย่างที่ฉันพูดความเข้าใจของฉันเกี่ยวกับความซับซ้อนมี จำกัด ดังนั้นฉันต้องแก้ไขสิ่งนี้ถ้าฉันผิด

public static bool IsSameAs<T, TKey>(this IEnumerable<T> source, IEnumerable<T> target, Expression<Func<T, TKey>> keySelectorExpression)
    {
        // check the object
        if (source == null && target == null) return true;
        if (source == null || target == null) return false;

        var sourceList = source.ToList();
        var targetList = target.ToList();

        // check the list count :: { 1,1,1 } != { 1,1,1,1 }
        if (sourceList.Count != targetList.Count) return false;

        var keySelector = keySelectorExpression.Compile();
        var groupedSourceList = sourceList.GroupBy(keySelector).ToList();
        var groupedTargetList = targetList.GroupBy(keySelector).ToList();

        // check that the number of grouptings match :: { 1,1,2,3,4 } != { 1,1,2,3,4,5 }
        var groupCountIsSame = groupedSourceList.Count == groupedTargetList.Count;
        if (!groupCountIsSame) return false;

        // check that the count of each group in source has the same count in target :: for values { 1,1,2,3,4 } & { 1,1,1,2,3,4 }
        // key:count
        // { 1:2, 2:1, 3:1, 4:1 } != { 1:3, 2:1, 3:1, 4:1 }
        var countsMissmatch = groupedSourceList.Any(sourceGroup =>
                                                        {
                                                            var targetGroup = groupedTargetList.Single(y => y.Key.Equals(sourceGroup.Key));
                                                            return sourceGroup.Count() != targetGroup.Count();
                                                        });
        return !countsMissmatch;
    }

0

นี้เป็นวิธีง่ายๆกองกำลังIEnumerable's IComparableประเภททั่วไปในการดำเนินการ เพราะ OrderByความหมายของ

หากคุณไม่ต้องการตั้งสมมติฐานดังกล่าว แต่ยังต้องการใช้โซลูชันนี้คุณสามารถใช้รหัสต่อไปนี้:

bool equal = collection1.OrderBy(i => i?.GetHashCode())
   .SequenceEqual(collection2.OrderBy(i => i?.GetHashCode()));

0

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

การใช้งาน:

using Microsoft.VisualStudio.TestTools.UnitTesting;

// define collection1, collection2, ...

Assert.Equal(collection1.OrderBy(c=>c).ToCsv(), collection2.OrderBy(c=>c).ToCsv());

วิธีการขยายผู้ช่วยเหลือ:

public static string ToCsv<T>(
    this IEnumerable<T> values,
    Func<T, string> selector,
    string joinSeparator = ",")
{
    if (selector == null)
    {
        if (typeof(T) == typeof(Int16) ||
            typeof(T) == typeof(Int32) ||
            typeof(T) == typeof(Int64))
        {
            selector = (v) => Convert.ToInt64(v).ToStringInvariant();
        }
        else if (typeof(T) == typeof(decimal))
        {
            selector = (v) => Convert.ToDecimal(v).ToStringInvariant();
        }
        else if (typeof(T) == typeof(float) ||
                typeof(T) == typeof(double))
        {
            selector = (v) => Convert.ToDouble(v).ToString(CultureInfo.InvariantCulture);
        }
        else
        {
            selector = (v) => v.ToString();
        }
    }

    return String.Join(joinSeparator, values.Select(v => selector(v)));
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.