Как использовать ExceptWith с типом HashSet в C#? - PullRequest
0 голосов
/ 17 марта 2020
HashSet<ReadOnlyCollection<int>> test1 = new HashSet<ReadOnlyCollection<int>> ();
for (int i = 0; i < 10; i++) {
    List<int> temp = new List<int> ();
    for (int j = 1; j < 2; j++) {
        temp.Add (i);
        temp.Add (j);
    }
    test1.Add (temp.AsReadOnly ());
}

Здесь test1 - это {[0,1], [1,1], [2,1], [3,1], [4,1], [5,1], [6,1] ], [7,1], [8,1], [9,1]}

HashSet<ReadOnlyCollection<int>> test2 = new HashSet<ReadOnlyCollection<int>> ();
for (int i = 5; i < 10; i++) {
    List<int> temp = new List<int> ();
    for (int j = 1; j < 2; j++) {
        temp.Add (i);
        temp.Add (j);
    }
    test2.Add (temp.AsReadOnly ());
}

Здесь test2 - {[5,1], [6,1], [7,1] , [8,1], [9,1]}

test1.ExceptWith(test2);

После этого я хочу, чтобы test1 был {[0,1], [1,1], [2,1], [3,1], [4,1]}, но это дает мне оригинальный тест1.
Как решить эту проблему? Или есть другой способ сделать то же самое? Спасибо!

Ответы [ 2 ]

2 голосов
/ 17 марта 2020

Объекты в c# обычно сравниваются по ссылке , а не по значению . Это означает, что new object() != new object(). Точно так же new List<int>() { 1 } != new List<int>() { 1 }. Структуры и примитивы, с другой стороны, сравниваются по значению , а не по ссылке.

Некоторые объекты переопределяют свой метод равенства вместо сравнения значений. Например, строки: new string(new[] { 'a', 'b', 'c'}) == "abc", даже если object.ReferenceEquals(new string(new[] { 'a', 'b', 'c'}), "abc") == false.

Но коллекции, списки, массивы и т. Д. c. не делайте. Не зря - сравнивая два списка целых, что вы хотите сравнить? Точные элементы, независимо от порядка? Точные элементы в порядке? Сумма элементов? Там не один ответ, который подходит всем. И часто вам, возможно, захочется проверить, есть ли у вас один и тот же объект.

При работе с коллекциями или LINQ вы часто можете указать пользовательский «компаратор», который будет обрабатывать сравнения так, как вы хотите. Затем методы сбора данных используют этот «компаратор» всякий раз, когда ему нужно сравнить два элемента.

Очень простой компаратор, работающий на ReadOnlyCollection<T>, может выглядеть следующим образом:

class ROCollectionComparer<T> : IEqualityComparer<IReadOnlyCollection<T>>
{
    private readonly IEqualityComparer<T> elementComparer;

    public ROCollectionComparer() : this(EqualityComparer<T>.Default) {}
    public ROCollectionComparer(IEqualityComparer<T> elementComparer) {
        this.elementComparer = elementComparer;
    }

    public bool Equals(IReadOnlyCollection<T> x, IReadOnlyCollection<T> y)
    {
        if(x== null && y == null) return true;
        if(x == null || y == null) return false;
        if(object.ReferenceEquals(x, y)) return true;

        return x.Count == y.Count && 
            x.SequenceEqual(y, elementComparer);
    }

    public int GetHashCode(IReadOnlyCollection<T> obj)
    {       
        // simplistic implementation - but should OK-ish when just looking for equality
        return (obj.Count, obj.Count == 0 ? 0 : elementComparer.GetHashCode(obj.First())).GetHashCode();
    }
}

И затем Вы можете сравнить поведение проверки на равенство по умолчанию и вашей пользовательской проверки:

var std = new HashSet<int[]>(new[] { new[] { 1, 2 }, new[] { 2, 2}});
std.ExceptWith(new[] { new[] { 2, 2}});
std.Dump();

var custom = new HashSet<int[]>(new[] { new[] { 1, 2 }, new[] { 2, 2 } }, new ROCollectionComparer<int>());
custom.ExceptWith(new[] { new[] { 2, 2 }});
custom.ExceptWith(new[] { new int[] { }});
custom.Dump();

Все это можно проверить в этой скрипке .

0 голосов
/ 17 марта 2020

Здесь у вас есть реализация ExceptWith:

https://github.com/microsoft/referencesource/blob/3b1eaf5203992df69de44c783a3eda37d3d4cd10/System.Core/System/Collections/Generic/HashSet.cs#L532

На самом деле это:

 // remove every element in other from this
 foreach (T element in other) {
    Remove(element);
 }

И Remove реализация:

https://github.com/microsoft/referencesource/blob/3b1eaf5203992df69de44c783a3eda37d3d4cd10/System.Core/System/Collections/Generic/HashSet.cs#L287

 if (m_slots[i].hashCode == hashCode && m_comparer.Equals(m_slots[i].value, item)) {

Так что, если хеш-код не совпадает, Remove ничего не сделает.

Небольшой тест чтобы доказать, что хеш-код не совпадает:

    List<int> temp = new List<int> ();
     temp.Add(1);
     temp.Add(2);

    HashSet<ReadOnlyCollection<int>> test1 = new HashSet<ReadOnlyCollection<int>> ();
    HashSet<ReadOnlyCollection<int>> test2 = new HashSet<ReadOnlyCollection<int>> ();
    test1.Add (temp.AsReadOnly ());
    test2.Add (temp.AsReadOnly ());

    Console.WriteLine(test1.First().GetHashCode() == test2.First().GetHashCode());
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...