C #: Словарь, индексируемый списком

#c# #list #dictionary #indexed

#c# #Список #словарь #индексированный

Вопрос:

Я пытаюсь написать программу, в которой словарь индексируется списком. (поверьте мне, я делаю, и да, есть вариант, но мне нравится индексирование по списку). Существует минимальный рабочий (на самом деле не работающий, только одна последняя строка, которая является проблемой) пример:

 using System;
using System.Collections.Generic;

namespace test
{
    class Program
    {
    static void Main(string[] args)
    {
        Dictionary<List<String>, int> h = new Dictionary<List<string>,int>();

        List<String> w = new List<string> {"a"};
        h.Add(w, 1);

        w = new List<string>{"b"};
        h.Add(w,2);

        w = new List<string>{"a"};

        int value = 0;
        h.TryGetValue(w, out value);
        Console.WriteLine(value " " h[w]);
    }
}
  

если кто-то отладит эту программу, он ясно увидит, что в h есть два элемента, но все равно эти элементы недоступны через правильные индексы — h [w]. Я ошибаюсь или происходит что-то странное?

Комментарии:

1. Класс list, вероятно, не производит хэш-код и проверку на равенство на основе его содержимого. Вы проверили, работают ли массивы лучше?

2. Или, может быть, использовать пользовательский класс или Tuple<T, T2....>

3. Нет, массивы ведут себя точно так же.

4. Вероятно, вам понадобится пользовательский IEqualityComparer

5. @LasseV.Karlsen: Они не будут: ideone.com/7Gvvh

Ответ №1:

Проблема с вашим приложением связана с тем, что:

 new List<String> { "a" } != new List<String> { "a" }
  

Равенство для списков проверяет, ссылаются ли две ссылки на один и тот же экземпляр. В данном случае это не так. Вместо этого вы создали два списка с одинаковыми элементами…что не делает их равными.

Вы можете устранить проблему, создав пользовательский инструмент сравнения равенства:

 public class ListEqualityComparer<T> : IEqualityComparer<List<T>>
{
    public bool Equals(List<T> list1, List<T> list2)
    {
        return list1.SequenceEquals(list2);
    }

    public int GetHashCode(List<T> list)
    {
        if(list != null amp;amp; list.Length > 0)
        {
            var hashcode = list[0].GetHashCode();
            for(var i = 1; i <= list.Length; i  )
                hashcode ^= list[i].GetHashCode();

            return hashcode;
        }

        return 0;
    }
}
  

И затем передаем это конструктору Dictionary:

 Dictionary<List<String>, int> h = 
    new Dictionary<List<string>,int>(new ListEqualityComparer<String>());
  

Комментарии:

1. Обычно при сравнении двух ссылочных типов сравнение возвращает true, если обе ссылки указывают на один и тот же объект. Если объекты разные, то даже их содержимое одинаковое, результат будет false.

2. Другим вариантом было бы использование ToArray() и IStructuralEquatable.GetHashCode для более простой (хотя и медленной) реализации. (Или, может быть, с использованием массивов и Dictionary<IStructuralEquatable, int> для начала.)

3. Спасибо! работает как шарм, это всего лишь for(var i = 1; i < list.Length; i )

Ответ №2:

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

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

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

Комментарии:

1. Технически это не указатель, но это правильно. Список<T> не вычисляет хэш-коды на основе содержимого: ideone.com/AEIDO

Ответ №3:

У вас это никогда не сработает, потому что List<T> методы Equals и GetHashCode не учитывают содержимое списка. Если вы хотите использовать коллекцию объектов в качестве ключа, вам нужно реализовать свой собственный тип коллекции, который переопределяет Equals таким образом, чтобы проверять равенство объектов в коллекции (возможно, используя Enumerable.SequenceEqual .)

Комментарии:

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

Ответ №4:

Класс Dictionary использует сравнение ссылок для поиска указанного ключа, поэтому, даже если списки содержат одинаковые элементы, они разные.

Комментарии:

1. в этом конкретном контексте . В общем, это не так, но со списком, который не переопределяет GetHashCode или Equals, это так.