我正在尝试在C#中制作一个字典查找表。 我需要将3个值的元组解析为一个字符串。 我尝试使用数组作为键,但那行不通,我不知道该怎么办了。 现在我考虑制作一个字典的嵌套字典的字典,但那可能不太好看,尽管这是我在javascript中做的方式。
我正在尝试在C#中制作一个字典查找表。 我需要将3个值的元组解析为一个字符串。 我尝试使用数组作为键,但那行不通,我不知道该怎么办了。 现在我考虑制作一个字典的嵌套字典的字典,但那可能不太好看,尽管这是我在javascript中做的方式。
lookup = new Dictionary<Tuple<TypeA, TypeB, TypeC>, string>();
如果不行,您可以定义一个Tuple
并将其用作键。该元组需要重写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);
}
}
如果你在使用C# 7,考虑使用值元组作为复合键。通常情况下,值元组性能比传统的引用元组(Tuple<T1, …>
)更好,因为值元组是值类型(结构体),而不是引用类型,所以它们避免了内存分配和垃圾收集成本。此外,它们提供更简洁和更直观的语法,如果需要,可以为它们的字段指定名称。它们还实现了字典所需的IEquatable<T>
接口。
var dict = new Dictionary<(int PersonId, int LocationId, int SubjectId), string>();
dict.Add((3, 6, 9), "ABC");
dict.Add((PersonId: 4, LocationId: 9, SubjectId: 10), "XYZ");
var personIds = dict.Keys.Select(k => k.PersonId).Distinct().ToList();
its much easier to implement a functionality that looks like:
var myDict = new Dictionary<Tuple<TypeA, TypeB, TypeC>, string>();
than
var myDict = new Dictionary<TypeA, Dictionary<TypeB, Dictionary<TypeC, string>>>();
from the callee side. In the second case each addition, lookup, removal etc require action on more than one dictionary.
Furthermore, if your composite key require one more (or less) field in future, you will need to change code a significant lot in the second case (nested dictionary) since you have to add further nested dictionaries and subsequent checks.
在嵌套字典的情况下,为每个键(外部和内部)添加一个额外的字典会产生一些内存开销(比创建元组更多)。
在嵌套字典的情况下,每个基本操作(如添加、更新、查找、删除等)都需要在两个字典中进行。现在有一种情况,嵌套字典方法可能更快,即当要查找的数据不存在时,因为中间字典可以绕过完整的哈希码计算和比较,但再次需要计时以确保。在存在数据的情况下,它应该更慢,因为需要执行两次查找(或者根据嵌套程度执行三次查找)。
关于元组方法,.NET元组在作为集合中的键使用时不是最高效的,因为其Equals
和GetHashCode
实现会对值类型进行装箱。
我会选择基于元组的字典,但如果我想要更好的性能,我会使用自己的元组并进行更好的实现。
顺便说一下,有些化妆品可以让字典变得很酷:
Indexer style calls can be a lot cleaner and intuitive. For eg,
string foo = dict[a, b, c]; //lookup
dict[a, b, c] = ""; //update/insertion
So expose necessary indexers in your dictionary class which internally handles the insertions and lookups.
Also, implement a suitable IEnumerable
interface and provide an Add(TypeA, TypeB, TypeC, string)
method which would give you collection initializer syntax, like:
new MultiKeyDictionary<TypeA, TypeB, TypeC, string>
{
{ a, b, c, null },
...
};
string foo = dict[a][b][c]
? - Steven Randsa
和b
获取值4
,则可以将其设置为标准字典,并添加类似于dict[a] = 4
和dict[b] = 4
的值。如果从逻辑上讲,您的a
和b
应该是一个单元,则可能没有意义。在这种情况下,您可以定义一个自定义的IEqualityComparer
,如果它们的任何属性相等,则将两个键实例视为相等。所有这些都可以通过反射来通用地完成。 - nawfaldict[a]
和 dict[a, b]
,以便可以以两种方式查询。请提出一个新问题,以便人们能够更好地帮助您。评论区超出了您的问题范围。 - nawfal简洁、干净、快速、易读的方法是:
添加类似于以下内容:
public sealed class myKey : Tuple<TypeA, TypeB, TypeC>
{
public myKey(TypeA dataA, TypeB dataB, TypeC dataC) : base (dataA, dataB, dataC) { }
public TypeA DataA => Item1;
public TypeB DataB => Item2;
public TypeC DataC => 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"}
};
public TypeA DataA => Item1;
。 - andrewtatham如果出于某种原因,您真的想避免创建自己的元组类或使用内置在.NET 4.0中的元组类,则还有一种可能的方法;您可以将这三个关键值合并为一个单一的值。
例如,如果这三个值是整数类型,总共不超过64位,您可以将它们组合成一个ulong
。
最坏的情况下,您始终可以使用字符串,只要确保其中的三个组件用某个字符或序列分隔开来,该字符或序列不会出现在密钥的组件中,例如,对于三个数字,您可以尝试:
string.Format("{0}#{1}#{2}", key1, key2, key3)
显然,这种方法存在一定的组合开销,但是根据您的使用情况,这可能微不足道,不需要太在意它。
JavaScriptSerializer
结合使用,为你连接字符串和/或整数类型的 数组。这样,你就不需要自己想出分隔符字符了。 - binkikey1
,key2
,key3
)中包含分隔符("#"
)的字符串,情况可能会变得非常混乱。 - Greg这里是.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;
}
}
}
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;
}
}
var dictionary = new SortedDictionary<int[], string>(new ArrayComparer<int>());
class StructuralEqualityComparer<T> : EqualityComparer<T[]>
{
public override bool Equals(T[] x, T[] y)
{
return StructuralComparisons.StructuralEqualityComparer
.Equals(x, y);
}
public override int GetHashCode(T[] obj)
{
return StructuralComparisons.StructuralEqualityComparer
.GetHashCode(obj);
}
}
然后像这样使用它:
var dict = new Dictionary<object[], SomeOtherObject>(new StructuralEqualityComparer<object>())
这个字典将正确调用数组的最后(我相信)8个元素的GetHashCode。这已经足够了,因为哈希码不是唯一的,但我们需要字典来获取它们。还需要一些代码来将它们组合起来。
Dictionary<TypeSearch, (bool a, bool b)> SearchConditions = new Dictionary<TypeSearch, (bool a, bool b)>
{
{ TypeSearch.Neither, (false , false ) },
{ TypeSearch.OnlySearch, (true , false ) },
{ TypeSearch.OnlyPosition, (false , true ) },
{ TypeSearch.BothThem, (true , true ) }
};
像这样的搜索
private TypeSearch GetTypeSearch(string _search, string _position) => SearchConditions
.FirstOrDefault(t => t.Value == (string.IsNullOrEmpty(_search), string.IsNullOrEmpty(_position))).Key;
GetHashCode
实现不太好,因为它在字段排列置换下是不变的。 - CodesInChaosnew object()
会等于另一个new object()
?它不只是使用直接引用比较... 试一下:bool test = new Tuple<int, string>(1, "foo").Equals(new Tuple<int, string>(1, "Foo".ToLower()));
- Mike Marynowski