Как создать HashSet‹List‹Int›› с отдельными элементами?

У меня есть HashSet, который содержит несколько списков целых чисел, т.е. HashSet<List<int>>

Чтобы сохранить уникальность, мне в настоящее время приходится делать две вещи: 1. Вручную перебирать существующие списки в поисках дубликатов, используя SequenceEquals. 2. Сортировка отдельных списков так, чтобы SequenceEquals работало в данный момент.

Есть лучший способ сделать это? Существует ли существующий компаратор IEqualityComparer, который я могу предоставить HashSet, чтобы HashSet.Add() мог автоматически обрабатывать уникальность?

var hashSet = new HashSet<List<int>>();

for(/* some condition */)
{
    List<int> list = new List<int>();

    ...

    /* for eliminating duplicate lists */

    list.Sort();

    foreach(var set in hashSet)
    {
        if (list.SequenceEqual(set))
        {
            validPartition = false;
            break;
        }
    }

    if (validPartition)
           newHashSet.Add(list);
}

person Preets    schedule 01.04.2011    source источник
comment
Проверьте ответ Джона Скита здесь: stackoverflow.com/questions/1023424/   -  person Forgotten Semicolon    schedule 01.04.2011
comment
Не могли бы вы предоставить больше информации о том, какую проблему вы на самом деле пытаетесь решить? HashSet<List<int>> кажется маловероятным инструментом для использования.   -  person marcind    schedule 02.04.2011
comment
@marcind, я использую его для ведения списка всех факторизаций числа ... так что для 24 у вас может быть, например, {4, 2, 3} , {2, 2, 6} и т. д. ... алгоритм, который я использую на данный момент создает повторяющиеся наборы, я хотел бы знать, как решить ЭТУ проблему, но, к сожалению, не знаю :-/   -  person Preets    schedule 02.04.2011
comment
Возможно, вы захотите задать это как отдельный вопрос. Должно быть более элегантное решение, чем то, что вы сейчас пытаетесь.   -  person CodesInChaos    schedule 02.04.2011
comment
@CodeInChaos, да, я определенно думаю, что должен! Любое решение будет более элегантным, чем беспорядок, который у меня сейчас есть ;-)   -  person Preets    schedule 02.04.2011
comment
@Preets - это немного не по теме, но из какого пространства имен вы получаете HasSet. Я бы подумал, что это будет в System.Collections.Generic, но у меня есть using System.Collections.Generic, и я не могу использовать List, он кричит на меня за использование HashSet...   -  person kralco626    schedule 07.04.2011
comment
@ kralco626, я использую System.Collections.Generic.HashSet. Боюсь, я не совсем понимаю ваш вопрос. Компилятор просит вас использовать List вместо HashSet?   -  person Preets    schedule 08.04.2011
comment
@Preets - нет, он просто пытается сказать мне, что HashSet не существует в System.Collections.Generic...   -  person kralco626    schedule 08.04.2011


Ответы (5)


Вот возможный компаратор, который сравнивает IEnumerable<T> по его элементам. Вам все еще нужно отсортировать вручную перед добавлением.

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

Этот код будет работать только в .net 4, поскольку он использует общую дисперсию. Если вам нужны более ранние версии, вам нужно либо заменить IEnumerable на List, либо добавить второй общий параметр для типа коллекции.

class SequenceComparer<T>:IEqualityComparer<IEnumerable<T>>
{
    public bool Equals(IEnumerable<T> seq1,IEnumerable<T> seq2)
    {
        return seq1.SequenceEqual(seq2);
    }

    public int GetHashCode(IEnumerable<T> seq)
    {
        int hash=1234567;
        foreach(T elem in seq)
            hash=hash*37+elem.GetHashCode();
        return hash;
    }
}

void Main()
{
    var hashSet = new HashSet<List<int>>(new SequenceComparer<int>());

    List<int> test=new int[]{1,3,2}.ToList();
    test.Sort();
    hashSet.Add(test);

    List<int> test2=new int[]{3,2,1}.ToList();
    test2.Sort();       
    hashSet.Contains(test2).Dump();
}
person CodesInChaos    schedule 01.04.2011
comment
@downvoter Не могли бы вы объяснить, в чем проблема с этим решением, чтобы я мог его исправить/улучшить? - person CodesInChaos; 02.04.2011
comment
(не мой отрицательный голос) Довольно близко, но отсутствует сортировка либо перед добавлением, либо в Equals - person Rune FS; 02.04.2011
comment
Потому я и объяснил в начале, что ему все равно нужно сортировать вручную. - person CodesInChaos; 02.04.2011
comment
@CodeInChaose Я согласен с выбранным вами дизайном. Кажется пустой сортировкой всех уже отсортированных списков. Это был комментарий к вашему (теперь обновленному) файлу Main(). (и для меня это не стоило того, чтобы голосовать в первую очередь) - person Rune FS; 02.04.2011
comment
Наличие .Sort() в Main лучше иллюстрирует, как его использовать. Так что это было хорошее предложение :) - person CodesInChaos; 02.04.2011
comment
@CodeInChaos Спасибо! Это сработало просто отлично. Хотя я немного смущен реализацией Equals. Не уверен, что я имею смысл, но почему бы нам просто не сравнить хэш-коды в equals? Является ли вызов SequenceEqual в Equals() практически одинаковым? - person Preets; 02.04.2011
comment
@CodeInChaose Сортировка в худшем случае O (n2), Equals в худшем случае O (n), а GetHashCode всегда O (n), действительно ли это лучший способ? - person Magnus; 02.04.2011
comment
@Magus Сортировка в среднем составляет O (n * log (n)) , и вы все равно не можете опуститься ниже O (n) при добавлении произвольного списка. Хэш-код обычно вычисляется один раз, а равенства будут вычисляться так часто, как хэш-код сталкивается внутри таблицы, то есть O (1). Есть некоторые возможные улучшения, но я сомневаюсь, что они необходимы на практике. Я считаю, что это хороший компромисс между читабельностью/размером кода и производительностью. - person CodesInChaos; 02.04.2011
comment
Идти с этим, поскольку поддержание новой структуры данных для хранения хэш-кодов (хотя и более эффективной) кажется излишним для моего приложения. - person Preets; 02.04.2011
comment
@CodeInChaos - я только что предположил, что при добавлении чего-либо в HashSet хэш-код каждого существующего элемента будет проверяться непосредственно, когда вы добавляете что-то еще. Это не так (я подтвердил быстрый тест), поэтому вы правы, когда говорите, что каждый хэш-код следует проверять только один раз. Так что в основном мой ответ излишен. В то же время это интересно и немного тревожно, так как это означает, что HashSet<T> может быть легко поврежден — изменение элемента по внешней ссылке, а затем вставка элемента с хэш-кодом, совпадающим со старым для измененного элемента, не удастся. - person Jamie Treworgy; 02.04.2011
comment
В контракте хэш-набора/словаря явно указано, что ни равенство, ни хэш-код не могут измениться, пока объект находится в наборе. Обычно вы переопределяете equals/hashcode только для неизменяемых объектов. И даже если hashset не хранит хэш, он будет поврежден из-за изменения хэша, поскольку хеш определяет ведро, поэтому с измененным хэшем он смотрит не в то ведро. - person CodesInChaos; 02.04.2011
comment
Привет, как я могу заставить это работать с двойным? мне нужно это! - person Dang D. Khanh; 13.07.2020

Это начинается неправильно, это должно быть HashSet<ReadOnlyCollection<>>, потому что вы не можете позволить спискам изменяться и делать предикат set недействительным. Затем это позволяет вам вычислять хэш-код за O (n) при добавлении коллекции в набор. И тест O (n), чтобы проверить, находится ли он уже в наборе с очень необычным наихудшим случаем O (n ^ 2), если все хеши окажутся равными. Сохраните вычисленный хэш вместе с коллекцией.

person Hans Passant    schedule 01.04.2011
comment
Не похоже, что ReadOnlyCollection гарантирует неизменность. И если этот набор не представлен в общедоступном API, изменчивость не имеет значения. Хранение вычисленного хэша также не так важно, так как я думаю, что HashSet<T> уже хранит хэши для уже содержащихся в нем элементов. - person CodesInChaos; 02.04.2011
comment
Это делает ReadOnlyCollection‹int›. Сохранять ли его или создавать производный класс, который переопределяет Equals+GetHashCode, зависит от OP. - person Hans Passant; 02.04.2011
comment
Я имел в виду, что если вы сами не создадите ReadOnlyCollection, посторонний человек все равно будет иметь ссылку на базовый IList и может изменить этот список, что затем отразится в ReadOnlyCollection. Если вы контролируете создание ReadOnlyCollection, вы можете гарантировать (поверхностную) неизменность. (И на глубокой неизменности) - person CodesInChaos; 02.04.2011

Есть ли причина, по которой вы не просто используете массив? int[] будет работать лучше. Также я предполагаю, что списки содержат дубликаты, иначе вы бы просто использовали наборы и не имели бы проблем.

Похоже, что их содержимое не изменится (сильно) после того, как они будут добавлены в HashSet. В конце концов, вам придется использовать компаратор, который использует SequenceEqual. Но вам не нужно делать это каждый раз. Вместо этого или выполнения экспоненциального числа сравнений последовательностей (например, по мере роста хеш-набора выполняется SequenceEqual для каждого существующего члена) — если вы создаете хороший хэш-код заранее, вам, возможно, придется делать очень мало таких сравнений. Хотя накладные расходы на создание хорошего хэш-кода, вероятно, примерно такие же, как на создание SequenceEqual, вы делаете это только один раз для каждого списка.

Таким образом, в первый раз, когда вы работаете с определенным List<int>, вы должны сгенерировать хэш на основе упорядоченной последовательности чисел и кэшировать его. Затем при следующем сравнении списка можно будет использовать кэшированное значение. Я не уверен, как вы могли бы сделать это с помощью компаратора (может быть, статического словаря?) - но вы могли бы реализовать оболочку List, которая делает это легко.

Вот основная идея. Вам нужно быть осторожным, чтобы убедиться, что он не является хрупким (например, убедитесь, что вы аннулируете любой кешированный хеш-код при изменении членов), но не похоже, что это будет типичная ситуация для того, как вы используете это.

public class FasterComparingList<T>: IList<T>, IList, ... 
    /// whatever you need to implement
{
   // Implement your interfaces against InnerList
   // Any methods that change members of the list need to
   // set _LongHash=null to force it to be regenerated
   public List<T> InnerList { ... lazy load a List }
   public int GetHashCode()
   {
       if (_LongHash==null) {
           _LongHash=GetLongHash();
       }
       return (int)_LongHash;
   }
   private int? _LongHash=null;
   public bool Equals(FasterComparingList<T> list)
   {
       if (InnerList.Count==list.Count) {
           return true;
       }
       // you could also cache the sorted state and skip this if a list hasn't
       // changed since the last sort
       // not sure if native `List` does
       list.Sort();
       InnerList.Sort();
       return InnerList.SequenceEqual(list);
   }
   protected int GetLongHash()
   {
       return .....
       // something to create a reasonably good hash code -- which depends on the 
       // data. Adding all the numbers is probably fine, even if it fails a couple 
       // percent of the time you're still orders of magnitude ahead of sequence
       // compare each time
   } 
}

Если списки не будут меняться после добавления, это должно быть очень быстро. Даже в ситуациях, когда списки могут часто меняться, время создания нового хеш-кода, скорее всего, не сильно отличается (если вообще больше), чем время сравнения последовательностей.

person Jamie Treworgy    schedule 01.04.2011
comment
Нет особой причины использовать List‹›, я не знал, что int[] работает лучше. Спасибо! И ваше предположение верно, в списках есть дубликаты, поэтому я не использую наборы. - person Preets; 02.04.2011
comment
Как правило, более простая конструкция, вероятно, будет быстрее, чем более сложная, если только вы не делаете что-то, что зависит от какого-либо аспекта этой сложной структуры (например, связанный список будет намного быстрее вставлять элементы, чем несвязанный список) . Длинный и короткий мой многословный ответ заключается в том, что вы должны использовать конструкцию, которая может кэшировать хэш-коды. Поскольку сравнивать списки или создавать что-то, что может их однозначно идентифицировать, дорого, и вы делаете это много раз с одними и теми же объектами, просто настройте что-то, что запомнит этот уникальный идентификатор. - person Jamie Treworgy; 02.04.2011

Если вы не укажете IEQualityComparer, то будут использоваться типы по умолчанию, поэтому я думаю, что вам нужно будет создать собственную реализацию IEQualityComparer и передать ее конструктору вашего HashSet. Вот хороший пример.

person BrandonZeider    schedule 01.04.2011

При сравнении хэш-наборов списков у вас всегда есть один вариант: вместо сравнения каждого элемента вы сортируете списки, соединяете их с помощью запятой и сравниваете сгенерированные строки.

Таким образом, в этом случае, когда вы создаете собственный компаратор вместо перебора элементов и вычисления пользовательской хэш-функции, вы можете применить эту логику.

person Jack Sparrow    schedule 30.10.2019