Кортежи (или массивы) как ключи словаря в C#
Я пытаюсь сделать таблицу поиска словаря в C#. Мне нужно решить 3-кортеж значений в одну строку. Я пробовал использовать массивы в качестве ключей, но это не сработало, и я не знаю, что еще делать. На данный момент я рассматриваю возможность создания словаря словарей словарей, но это, вероятно, было бы не очень красиво смотреть, хотя именно так я бы сделал это в javascript.
7 ответов:
Если вы находитесь на .NET 4.0 использовать Кортеж:
lookup = new Dictionary<Tuple<TypeA, TypeB, TypeC>, string>();Если вы не можете определить Кортеж и использовать его в качестве ключа. Кортеж должен переопределить GetHashCode, Equals и IEquatable:
struct Tuple<T, U, W> : IEquatable<Tuple<T,U,W>> { readonly T first; readonly U second; readonly W third; public Tuple(T first, U second, W third) { this.first = first; this.second = second; this.third = third; } public T First { get { return first; } } public U Second { get { return second; } } public W Third { get { return third; } } public override int GetHashCode() { return first.GetHashCode() ^ second.GetHashCode() ^ third.GetHashCode(); } public override bool Equals(object obj) { if (obj == null || GetType() != obj.GetType()) { return false; } return Equals((Tuple<T, U, W>)obj); } public bool Equals(Tuple<T, U, W> other) { return other.first.Equals(first) && other.second.Equals(second) && other.third.Equals(third); } }
между кортежем и вложенными словарями, основанными на подходах, почти всегда лучше идти на кортеж.
С точки зрения ремонтопригодности,
его гораздо легче реализовать функциональность, которая выглядит так:
var myDict = new Dictionary<Tuple<TypeA, TypeB, TypeC>, string>();чем
var myDict = new Dictionary<TypeA, Dictionary<TypeB, Dictionary<TypeC, string>>>();со стороны вызываемого абонента. Во втором случае каждое добавление, поиск, удаление и т. д. требуют действия над более чем одним словарем.
кроме того, если ваш составной ключ требует еще одного (или меньше) поля в будущем, вам нужно будет изменить код значительной партии во втором случае (вложенный словарь), так как вы должны добавить дополнительные вложенные словари и последующие проверки.
С точки зрения производительности, лучший вывод, который вы можете сделать, это измерить его самостоятельно. Но есть несколько теоретических ограничений, которые вы можете рассмотреть заранее:
в случае вложенного словаря наличие дополнительного словаря для каждого ключа (внешнего и внутреннего) будет иметь некоторые накладные расходы памяти (больше, чем при создании кортежа).
в случае вложенного словаря каждое основное действие, такое как добавление, обновление, поиск, удаление и т. д., должно выполняться в двух словарях. Теперь есть случай, когда вложенный словарный подход может быть быстрее, т. е. когда данные просматриваются отсутствует, так как промежуточные словари могут обходить полное вычисление и сравнение хэш-кода, но затем снова он должен быть синхронизирован, чтобы быть уверенным. При наличии данных он должен быть медленнее, так как поиск должен выполняться дважды (или трижды в зависимости от вложенности).
Что касается подхода кортежей, кортежи .NET не являются наиболее эффективными, когда они предназначены для использования в качестве ключей в наборах, так как его
EqualsиGetHashCodeреализация вызывает бокс для значения типы.Я бы пошел с кортежем на основе словаря, но если я хочу больше производительности, я бы использовал свой собственный кортеж с лучшей реализацией.
на стороне Примечание, несколько косметики может сделать словарь прохладно:
индексатор стиль вызовов может быть намного чище и интуитивно понятным. Например,
string foo = dict[a, b, c]; //lookup dict[a, b, c] = ""; //update/insertionпоэтому выставляйте необходимые индексаторы в своем классе словаря, который внутренне обрабатывает вставки и поиски.
кроме того, применить соответствующую
IEnumerableинтерфейс и предоставитьAdd(TypeA, TypeB, TypeC, string)метод, который даст вам синтаксис инициализатора коллекции, как:new MultiKeyDictionary<TypeA, TypeB, TypeC, string> { { a, b, c, null }, ... };
хороший, чистый, быстрый, легкий и читаемый способ:
просто сделайте простой ключевой класс, производный от кортежа.
добавить что-то подобное:
public sealed class myKey : Tuple<TypeA, TypeB, TypeC> { public myKey(TypeA dataA, TypeB dataB, TypeC dataC) : base (dataA, dataB, dataC) { } public TypeA DataA { get { return Item1; } } public TypeB DataB { get { return Item2; } } public TypeC DataC { get { return Item3; } } }Так что вы можете использовать его со словарем:
var myDictinaryData = new Dictionary<myKey, string>() { {new myKey(1, 2, 3), "data123"}, {new myKey(4, 5, 6), "data456"}, {new myKey(7, 8, 9), "data789"} };
- вы также можете использовать его в договорах
- как ключ для соединения или группировки в linq
- идя таким образом, вы никогда не ошибетесь порядок Item1, Item2, Item3 ...
- вы нет нужно запомнить или заглянуть в код, чтобы понять, куда идти, чтобы получить что-то
- нет необходимости переопределять IStructuralEquatable, IStructuralComparable, IComparable, ITuple они все уже здесь
Если по какой-то причине вы действительно хотите избежать создания собственного класса кортежа или использования встроенного в .NET 4.0, возможен еще один подход; вы можете объединить три ключевых значения вместе в одно значение.
например, если три значения являются целочисленными типами вместе, не принимая более 64 бит, вы можете объединить их в
ulong.в худшем случае вы всегда можете использовать строку, если вы убедитесь, что три компонента в ней разделены некоторые символы или последовательности, которые не встречаются внутри компонентов ключа, например, с тремя числами вы можете попробовать:
string.Format("{0}#{1}#{2}", key1, key2, key3)очевидно, что в этом подходе есть некоторые накладные расходы на композицию, но в зависимости от того, что вы используете для этого, это может быть достаточно тривиально, чтобы не заботиться об этом.
Я бы перегрузил Ваш кортеж с правильным GetHashCode, и просто использовать его в качестве ключа.
пока вы перегружаете правильные методы, вы должны увидеть достойную производительность.
вот .NET Кортеж для справки:
[Serializable] public class Tuple<T1, T2, T3> : IStructuralEquatable, IStructuralComparable, IComparable, ITuple { private readonly T1 m_Item1; private readonly T2 m_Item2; private readonly T3 m_Item3; public T1 Item1 { get { return m_Item1; } } public T2 Item2 { get { return m_Item2; } } public T3 Item3 { get { return m_Item3; } } public Tuple(T1 item1, T2 item2, T3 item3) { m_Item1 = item1; m_Item2 = item2; m_Item3 = item3; } public override Boolean Equals(Object obj) { return ((IStructuralEquatable) this).Equals(obj, EqualityComparer<Object>.Default);; } Boolean IStructuralEquatable.Equals(Object other, IEqualityComparer comparer) { if (other == null) return false; Tuple<T1, T2, T3> objTuple = other as Tuple<T1, T2, T3>; if (objTuple == null) { return false; } return comparer.Equals(m_Item1, objTuple.m_Item1) && comparer.Equals(m_Item2, objTuple.m_Item2) && comparer.Equals(m_Item3, objTuple.m_Item3); } Int32 IComparable.CompareTo(Object obj) { return ((IStructuralComparable) this).CompareTo(obj, Comparer<Object>.Default); } Int32 IStructuralComparable.CompareTo(Object other, IComparer comparer) { if (other == null) return 1; Tuple<T1, T2, T3> objTuple = other as Tuple<T1, T2, T3>; if (objTuple == null) { throw new ArgumentException(Environment.GetResourceString("ArgumentException_TupleIncorrectType", this.GetType().ToString()), "other"); } int c = 0; c = comparer.Compare(m_Item1, objTuple.m_Item1); if (c != 0) return c; c = comparer.Compare(m_Item2, objTuple.m_Item2); if (c != 0) return c; return comparer.Compare(m_Item3, objTuple.m_Item3); } public override int GetHashCode() { return ((IStructuralEquatable) this).GetHashCode(EqualityComparer<Object>.Default); } Int32 IStructuralEquatable.GetHashCode(IEqualityComparer comparer) { return Tuple.CombineHashCodes(comparer.GetHashCode(m_Item1), comparer.GetHashCode(m_Item2), comparer.GetHashCode(m_Item3)); } Int32 ITuple.GetHashCode(IEqualityComparer comparer) { return ((IStructuralEquatable) this).GetHashCode(comparer); } public override string ToString() { StringBuilder sb = new StringBuilder(); sb.Append("("); return ((ITuple)this).ToString(sb); } string ITuple.ToString(StringBuilder sb) { sb.Append(m_Item1); sb.Append(", "); sb.Append(m_Item2); sb.Append(", "); sb.Append(m_Item3); sb.Append(")"); return sb.ToString(); } int ITuple.Size { get { return 3; } } }
Если ваш потребляющий код может обойтись с интерфейсом IDictionary, вместо словаря, мой инстинкт состоял бы в том, чтобы использовать SortedDictionary с пользовательским компаратором массива, т. е.:
class ArrayComparer<T> : IComparer<IList<T>> where T : IComparable<T> { public int Compare(IList<T> x, IList<T> y) { int compare = 0; for (int n = 0; n < x.Count && n < y.Count; ++n) { compare = x[n].CompareTo(y[n]); } return compare; } }и создать таким образом (используя int[] только для конкретного примера):
var dictionary = new SortedDictionary<int[], string>(new ArrayComparer<int>());
Comments