字典支持重复的多维键?

Posted

技术标签:

【中文标题】字典支持重复的多维键?【英文标题】:Dictionary supporting duplicate, multidimensional keys? 【发布时间】:2012-08-20 06:23:13 【问题描述】:

我有一个List<Thing> things,其中需要经常通过查找两个变量T1 f1T2 f2 的组合来检索多个Thing,这两个变量是值类型。我现在这样做的方式就是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, ...]; 的内容?

【问题讨论】:

您是否考虑过在内存数据库中使用 SQLite 之类的东西? Thing 是否具有标识属性(ID、PrimaryKey 或其他)? C# Multi-key Generic Dictionary 您可以使用Tuples 作为字典键,因为它们是不可变的。如 MSDN 页面 (msdn.microsoft.com/en-us/library/xfhwa508.aspx) 所述,字典键的规则是,只要将其用作键,键的值就不能更改。该实现似乎使用哈希码。由于元组不会改变,并且随着时间的推移可能会产生相同的哈希码,所以它应该可以作为键。另请参阅:***.com/questions/1483059/… 这是一个相关问题,提供了一些很好的信息:***.com/questions/955982/… 【参考方案1】:

如果我没听错,您可以将HashtableTuple 一起使用,示例如下:

        // 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

【讨论】:

重复键失败,为什么使用非通用哈希表? 我不认为这个集合应该包含相同的项目。 Hastable - 用于代码简化 不幸的是,我的要求需要重复键【参考方案2】:

Lookup&lt;Tuple&lt;T1,T2,T3,...&gt;,Thing&gt; 会起作用,因为Tuple 会覆盖EqualsGetHashCode

为了使查找语法不那么难看,您可以使用支持类型推断的Tuple.Create。您的代码变为things[Tuple.Create(f1, f2, f3, ...)]。如果这仍然太难看,添加一个将各个值作为参数的辅助方法是微不足道的。

我还考虑为键创建自己的不可变类(或值类型),这样您就可以得到干净的字段名称,而不是 ItemX。您只需始终覆盖EqualsGetHashCode

【讨论】:

也许我当时正在做某事。这似乎是一个简单、整洁的解决方案。我不确定你的意思是“所以你得到干净的字段名称而不是ItemX”?以及如何覆盖EqualsGetHashCode?我不知道这些实现应该是什么样子。 如果您使用 Tuple 类,则会出现 ItemX。我们的想法是创建自己的类,其属性名称比 Item1Item2 等更好。 Tuple 生成很多哈希冲突。它不适合作为密钥。【参考方案3】:

Linq Where 或 Dictionary of Dictionaries 可能是您将获得的最漂亮的。但这可能更多是您如何组织数据的问题。

例如这永远不会是访问人员数据的好方法:

people["FirstName"]["LastName"] 

这通常会更好,所以试着想出一个更简单的键。

【讨论】:

【参考方案4】:

您可以创建多个查找,然后将它们相交以进行搜索。这是一个有点过于简单的例子,但它应该能说明这个想法:

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

【讨论】:

如果您搜索的最稀有的单词足够稀有,则可以很快。如果不是,可能会很慢。 @CodesInChaos 确实如此,如果单词分布不好,您将无法获得太多加速。虽然我想你仍然会击败帖子顶部描述的“全面扫描”方法。 一个轻微的变体是使用查找最稀有的单词,然后从那里使用Where 向下过滤。可能会稍微快一些并且占用更少的内存。 有趣的解决方案。不过,在对数据集的变化进行彻底测试之前,我无法确定速度的一致性。【参考方案5】:

您是否考虑过使用带有某种字段组合的哈希表作为键?我对您的数据集知之甚少,无法说明这是否可行。因为密钥需要是唯一的。但是,由于您没有使用哈希表在内存中进行添加或删除操作,因此您可以获得的速度差不多。

【讨论】:

他显然做到了,元组是字段的组合,Lookup 是哈希表。

以上是关于字典支持重复的多维键?的主要内容,如果未能解决你的问题,请参考以下文章

展平多维数组连接键[重复]

PHP多维数组搜索并获取键的数组[重复]

在不循环多维数组PHP的情况下获取第一个子数组键[重复]

具有重复键的字典 [重复]

.NET 字典中的重复键?

获取嵌套字典的所有键[重复]