我有一个List<Thing> things
,其中需要频繁检索一些Thing
,方法是查找两个变量T1 f1
和T2 f2
的组合,它们是值类型。我现在这样做只是things.Where(t => t.Field1 == f1 && t.Field2 == f2)
。但是,我经常进行大量的查找,需要更有效的方法。
幸运的是,things
不需要删除或添加元素,因此我考虑在构造中解析列表并添加到Dictionary<T1, Lookup<T2, Thing>>
。但是,这感觉很麻烦,特别是添加了解析。如果我需要查找更多字段,它会变得非常毛茸茸。三个字段看起来像Dictionary<T1, Dictionary<T2, Lookup<T3, Thing>>>
。
我的下一个想法是制作Lookup<Tuple<T1,T2,T3,...>,Thing>
。但在这种情况下,我不确定密钥是否真的有效,因为Tuple是一个引用类型。
即使我创建Lookup<ValueType<T1,T2,T3,...>,Thing> things
,查找语句也会像things[new ValueType<T1,T2,T3,...>(f1, f2, f3, ...)]
那样非常难看(我仍然不确定是否可以信任这些密钥)。
是否有一个更优雅的解决方案可以保持哈希表的性能优势,我只需键入类似IEnumerable<Thing> found = things[f1, f2, f3, ...];
的内容?
答案 0 :(得分:3)
Lookup<Tuple<T1,T2,T3,...>,Thing>
将有效,因为Tuple
会覆盖Equals
和GetHashCode
。
要使查找语法不那么难看,可以使用支持类型推断的Tuple.Create
。您的代码变为things[Tuple.Create(f1, f2, f3, ...)]
。如果这仍然太难看,那么添加一个将各个值作为参数的辅助方法是微不足道的。
我还考虑为密钥创建自己的不可变类(或值类型),因此您可以获得干净的字段名称而不是ItemX
。您只需要始终覆盖Equals
和GetHashCode
。
答案 1 :(得分:2)
您可以创建多个查找,然后将它们相交以进行搜索。这是一个有点过于简单的例子,但它应该说明这个想法:
class Test {
public string A { get; set; }
public string B { get; set; }
public string C { get; set; }
}
var list = new List<Test> {
new Test {A = "quick", B = "brown", C = "fox"}
, new Test {A = "jumps", B = "over", C = "the"}
, new Test {A = "lazy", B = "dog", C = "quick"}
, new Test {A = "brown", B = "fox", C = "jumps"}
, new Test {A = "over", B = "the", C = "lazy"}
, new Test {A = "dog", B = "quick", C = "brown"}
, new Test {A = "fox", B = "jumps", C = "over"}
, new Test {A = "the", B = "lazy", C = "dog"}
, new Test {A = "fox", B = "brown", C = "quick"}
, new Test {A = "the", B = "over", C = "jumps"}
, new Test {A = "quick", B = "dog", C = "lazy"}
, new Test {A = "jums", B = "fox", C = "brown"}
, new Test {A = "lazy", B = "the", C = "over"}
, new Test {A = "brown", B = "quick", C = "dog"}
, new Test {A = "over", B = "jumps", C = "fox"}
, new Test {A = "dog", B = "lazy", C = "the"}
};
var byA = list.ToLookup(v => v.A);
var byB = list.ToLookup(v => v.B);
var byC = list.ToLookup(v => v.C);
var all = byA["quick"].Intersect(byB["dog"]);
foreach (var test in all) {
Console.WriteLine("{0} {1} {2}", test.A, test.B, test.C);
}
all = byA["fox"].Intersect(byC["over"]);
foreach (var test in all) {
Console.WriteLine("{0} {1} {2}", test.A, test.B, test.C);
}
打印
quick dog lazy
fox jumps over
答案 2 :(得分:1)
您是否考虑过将哈希表作为键的某种组合使用哈希表?我不太了解您的数据集是否可行。因为密钥需要是唯一的。但是,由于您没有使用哈希表进行添加或删除,因此内存中的查找速度与您可以获得的速度差不多。
答案 3 :(得分:1)
如果我说得对,你可以Hashtable
使用Tuple
,例如:
// populate Hastable
var hash = new Hashtable();
var tuple = Tuple.Create("string", 1, 1.0);
hash.Add(tuple,tuple);
// search for item you want
var anotherTuple = Tuple.Create("string", 1, 1.0);
// result will be tuple declared above
var result = hash[anotherTuple];
更复杂的解决方案(如果需要重复的密钥):
public class Thing
{
public int Value1 { get; set; }
public double Value2 { get; set; }
public string Value3 { get; set; }
// preferable to create own Equals and GetHashCode methods
public Tuple<int, double> GetKey()
{
// create key on fields you want
return Tuple.Create(Value1, Value2);
}
}
使用
var t1 = new Thing() {Value1 = 1, Value2 = 1.0, Value3 = "something"};
var t2 = new Thing() {Value1 = 1, Value2 = 2.0, Value3 = "something"};
var hash = new [] { t1, t2 }.ToLookup(item => item.GetKey());
var criteria = new Thing() { Value1 = 1, Value2 = 2.0, value3 = "bla-bla-bla" };
var r = hash[criteria.GetKey()]; // will give you t1
答案 4 :(得分:0)
The Linq Where或Dictionary of Dictionary of Dictionary可能是你得到的最漂亮的。但这可能更像是一个如何组织数据的问题。
E.G。这绝不是一种访问人员数据的好方法:
people["FirstName"]["LastName"]
通常情况下会更好,所以请尝试使用更简单的密钥。