Есть ли какой-либо встроенный тип коллекции или IEqualityComparer‹T› для коллекции, который основывает равенство на элементах в ней?

Есть ли какой-либо встроенный тип коллекции (IEnumerable<S>) или IEqualityComparer<T> для IEnumerable<S> в структуре, в котором EqualsGetHashCode соответственно) определяется равенством элементов в нем?

Что-то типа:

var x = new SomeCollection { 1, 2, 3 };
var y = new SomeCollection { 1, 2, 3 };

// so that x.Equals(y) -> true 
// and x.Shuffle().Equals(y) -> false

Or a

class SomeComparer<T> : EqalityComparer<IEnumerable<T>> { }

// so that for 
var x = new[] { 1, 2, 3 };
var y = new[] { 1, 2, 3 };
// gives
// new SomeComparer<int>().Equals(x, y) -> true 
// new SomeComparer<int>().Equals(x.Shuffle(), y) -> false

? Мой вопрос: есть ли что-то в фреймворке, которое ведет себя как SomeCollection или SomeComparer<T>, как показано в коде?

Зачем мне это нужно: потому что у меня есть случай для Dictionary<Collection, T>, где часть Key должна быть коллекцией, и ее равенство основано на ее элементах.

Требования:

  1. Коллекция должна быть только простым перечисляемым типом с методом Add
  2. Порядок элементов важен
  3. В коллекции могут быть повторяющиеся элементы

Примечание. Я могу написать свой собственный, это тривиально. Есть много вопросов о том, как SO помогает с этим. Я спрашиваю, есть ли класс в самой структуре.


person nawfal    schedule 08.11.2013    source источник
comment
Кажется, это дубликат один   -  person Wagner DosAnjos    schedule 09.11.2013
comment
@wdosanjos совсем нет. Я только что прошел по этой ссылке. Этот вопрос о том, как сравнить любые две коллекции на предмет равенства. Мой вопрос в том, есть ли коллекция, которая делает это сама по себе. Кроме того, в связанном вопросе порядок не имеет значения.   -  person nawfal    schedule 09.11.2013
comment
Вы ищете Enumerable.SequenceEqual?   -  person Jon    schedule 09.11.2013
comment
@Джон, нет. Я уточню свой вопрос.   -  person nawfal    schedule 09.11.2013
comment
Компаратор, который вы получаете от HashSet<T>.CreateSetComparer(), близок к тому, о чем вы просите, за исключением того, что по очевидным причинам набор хэшей не сохраняет порядок элементов.   -  person    schedule 09.11.2013
comment
@hvd, спасибо все же, узнал кое-что новое! :) На самом деле лень изобретать велосипед, если что-то уже есть..   -  person nawfal    schedule 09.11.2013
comment
Нет, такого класса в фреймворке нет.   -  person Jim Mischel    schedule 09.11.2013
comment
Нет, нет. В любом случае вам лучше реализовать свои собственные и специализироваться на своих коллекциях. Причина - вам нужно будет сделать сравнение быстрым и, следовательно, изменить/обернуть коллекцию на что-то, что имеет вычисление GetHashCode с постоянным временем (и желательно защитить коллекции, используемые в качестве ключей, от модификации). Таким образом, вам понадобится собственный код компаратора для связи с этим классом коллекции/оболочки.   -  person Alexei Levenkov    schedule 09.11.2013


Ответы (3)


Просто будь проще. Просто используйте словарь ctor, который принимает специализированный компаратор IEqualityComparer (просто реализуйте свою логику равенства в компараторе), и все готово. Нет необходимости в специальных типах коллекций и так далее...

См. здесь

person atomaras    schedule 08.11.2013
comment
Смотрите ответ суперкота. Спецколлекция здесь будет лучше для оперативности :) - person nawfal; 27.06.2015

Если вы можете, может быть лучше определить свой собственный неизменяемый класс коллекции, который принимает IEqualityComparer<T> в качестве параметра конструктора и имеет его члены Equals и GetHashCode() в цепочке с членами базовой коллекции, чем пытаться определить IEqualityComparer<T> для этой цели. Среди прочего, ваш класс неизменяемой коллекции сможет кэшировать собственное хеш-значение и, возможно, хеш-значения для содержащихся в нем элементов. Это ускорит не только вызовы GetHashCode() коллекции, но и сравнение двух коллекций. Если хэш-коды двух коллекций не равны, нет смысла что-либо дополнительно проверять; даже если хэш-коды двух коллекций равны, может быть целесообразно проверить, совпадают ли хэш-коды соответствующих элементов, прежде чем проверять сами элементы на равенство [обратите внимание, что в целом использование теста хэш-кода в качестве раннего выхода перед проверкой равенства не является особенно полезно, потому что самый медленный случай Equals (где элементы совпадают) — это тот, где хеш-коды все равно будут совпадать; однако здесь, если совпадают все элементы, кроме последнего, проверка хэш-кода элементов может обнаружить несоответствие до того, как будет потрачено время на детальную проверку каждого элемента.

Начиная с .NET 4.0, стало возможным написать IEqualityComparer<T>, который мог бы получить преимущество в производительности неизменяемого класса коллекции, который кэширует хеш-значения, используя ConditionalWeakTable для сопоставления коллекций с объектами, которые будут кэшировать информацию о них. Тем не менее, если кто-то не может использовать собственный класс неизменяемой коллекции, я думаю, что такой класс в любом случае, вероятно, будет лучше, чем IEqualityComparer<T> в этом сценарии.

person supercat    schedule 16.11.2013
comment
even if two collections' hashcodes are equal, it may be worthwhile to check that the hashcodes of corresponding items match before testing the items themselves for equality Я считаю, что проверка на равенство элементов в коллекции будет более эффективной, чем проверка их хэша. Является ли вычисление хэша вообще (я имею в виду для большинства общих случаев) быстрее, чем прямая проверка на равенство? - person nawfal; 27.06.2015
comment
@nawfal: если хеш-код объекта не вычислялся с момента последнего изменения, вычисление хэш-кода обычно будет дороже, чем сравнение элемента на равенство. Если объект будет сравниваться только с одним другим объектом и никогда больше ни с чем другим, дополнительные усилия будут потрачены впустую. С другой стороны, если объекты, для которых вычисление хэш-кода было бы дорогостоящим, кэшируют свои хеш-значения, то сравнения хэш-кода после первого будут относительно дешевыми. Таким образом, дополнительные затраты на сравнения после первого будут незначительными, и... - person supercat; 27.06.2015
comment
... время, которое обычно требуется для сравнения вещей, которые почти, но не совсем равны, может значительно сократиться. Преимущества сравнения кэшированных хэш-кодов будут зависеть от частоты, с которой сравниваются почти одинаковые элементы. Многие приложения не будут выполнять много таких сравнений, но преимущества каждого из них могут быть очень значительными; если приложение выполняет много таких сравнений, общая выгода может быть действительно огромной. - person supercat; 27.06.2015
comment
Я получаю преимущество кэширования хеш-значений, что и делают словари. Я хочу сказать, что при сравнении двух Collection‹T›, у которых есть собственный кэшированный хэш (как вы рекомендуете), если хэши равны, то сравнение элементов в самой коллекции будет проще, чем сравнение хэшей элементов. Большинство общих объектов .NET не имеют кэшированных хэшей. Все они вычисляются при вызове GetHashCode. - person nawfal; 27.06.2015
comment
@nawfal: большинство универсальных типов .NET изменяемы и основывают равенство на идентичности, а не на содержимом. Однако эффективное использование неизменяемых вложенных коллекций в таких вещах, как словари, требует, чтобы объекты с дорогими для вычисления хэш-кодами кэшировали их. Если код имеет коллекцию, содержащую определенные объекты, и хочет построить из нее коллекцию, содержащую эти объекты плюс еще один, то, если ничего не кэширует хэш-код, вызов GetHashCode один раз для каждой из внешних коллекций приведет к каждой внутренней объекту приходится дважды вычислять свой хеш-код. - person supercat; 27.06.2015
comment
Хм, я понял тебя. Это будет зависеть от того, что пользователь хочет оптимизировать, память или процессор. - person nawfal; 27.06.2015
comment
Если объект настолько велик, что хеширование может занять некоторое время, стоимость дополнительных четырех байтов для хранения кэшированного значения хеш-функции, скорее всего, будет тривиальной по сравнению с ним. Более важный вопрос заключается в том, насколько вероятно, что хеш-значение потребуется ноль раз, ровно один раз или более одного раза. Резервирование места для кэшированного хеш-значения добавит небольшую стоимость, если оно никогда не понадобится, и немного большую стоимость, если оно понадобится ровно один раз; это окупится, когда хеш понадобится больше одного раза; в некоторых случаях даже дважды может быть большая отдача, а более чем в два раза огромная. - person supercat; 27.06.2015

Я не верю, что такое существует. Мне нужно было сравнить содержимое двух словарей на равенство, и я написал это некоторое время назад.

public class DictionaryComparer<TKey, TValue> : EqualityComparer<IDictionary<TKey, TValue>>
{
    public DictionaryComparer()
    {
    }
    public override bool Equals(IDictionary<TKey, TValue> x, IDictionary<TKey, TValue> y)
    {
        // early-exit checks
        if (object.ReferenceEquals(x, y))
            return true;

        if (null == x || y == null)
            return false;

        if (x.Count != y.Count)
            return false;

        // check keys are the same
        foreach (TKey k in x.Keys)
            if (!y.ContainsKey(k))
                return false;

        // check values are the same
        foreach (TKey k in x.Keys)
        {
            TValue v = x[k];
            if (object.ReferenceEquals(v, null))
                return object.ReferenceEquals(y[k], null);

            if (!v.Equals(y[k]))
                return false;
        }
        return true;
    }

    public override int GetHashCode(IDictionary<TKey, TValue> obj)
    {
        if (obj == null)
            return 0;

        int hash = 0;

        foreach (KeyValuePair<TKey, TValue> pair in obj)
        {
            int key = pair.Key.GetHashCode(); // key cannot be null
            int value = pair.Value != null ? pair.Value.GetHashCode() : 0;
            hash ^= ShiftAndWrap(key, 2) ^ value;
        }

        return hash;
    }

    private static int ShiftAndWrap(int value, int positions)
    {
        positions = positions & 0x1F;

        // Save the existing bit pattern, but interpret it as an unsigned integer. 
        uint number = BitConverter.ToUInt32(BitConverter.GetBytes(value), 0);
        // Preserve the bits to be discarded. 
        uint wrapped = number >> (32 - positions);
        // Shift and wrap the discarded bits. 
        return BitConverter.ToInt32(BitConverter.GetBytes((number << positions) | wrapped), 0);
    }
}
person dkackman    schedule 08.11.2013
comment
Это кажется полезным, но зачем дважды перебирать ключи x? Не могли бы вы объединить проверку наличия у y каждого ключа x с проверками связанных с ними значений в одном цикле? - person Sven Grosen; 09.11.2013
comment
Я не могу использовать это, потому что словари не о порядке. - person nawfal; 09.11.2013
comment
Это тоже очень неэффективно. Почему две петли, как говорит @ledbutter. Почему вы перечисляете только ключи, а затем выполняете поиск, а не перечисляете весь словарь? Также вы можете избежать упаковки значений, если они являются типами значений. - person nawfal; 09.11.2013
comment
@nawful, потому что, если словари не используют общие ключи, они не равны, поэтому неравенство можно устранить без извлечения и сравнения значений. - person dkackman; 09.11.2013
comment
@dkackman это хороший момент, только если вы не извлекаете значения во втором цикле, а это не так. Вы делаете все тот же поиск снова. y.ContainsKey(key) — это тот же поиск, что и y[key], только во втором случае возвращается значение. Если вы перечисляете сам словарь, вы можете вообще избежать поиска x[key] и в одном цикле!. Пожалуйста, отметьте меня как @nawfal, чтобы я был в курсе ваших ответов. - person nawfal; 11.11.2013