使用值范围作为键的字典对象

Posted

技术标签:

【中文标题】使用值范围作为键的字典对象【英文标题】:A dictionary object that uses ranges of values for keys 【发布时间】:2011-01-09 23:44:43 【问题描述】:

我需要一种专门的字典。我的用例是这样的:用户想要指定值的范围(范围也可以是一个点)并将值分配给特定的范围。然后,我们希望使用单个值作为键来执行查找。如果此单个值出现在某个范围内,那么我们将返回与该范围关联的值。

例如:

// represents the keyed value
struct Interval

    public int Min;
    public int Max;


// some code elsewhere in the program
var dictionary = new Dictionary<Interval, double>();
dictionary.Add(new Interval  Min = 0, Max = 10 , 9.0);
var result = dictionary[1];
if (result == 9.0) JumpForJoy();

这显然只是一些代码来说明我在寻找什么。有谁知道实现这种事情的算法?如果可以,请他们指点我吗?

我已经尝试实现自定义 IEqualityComparer 对象并在 Interval 上重载 Equals() 和 GetHashCode() 但到目前为止无济于事。不过可能是我做错了什么。

【问题讨论】:

您必须实现自己的自定义集合。我认为您无法使用标准 Dictionary 类满足您的要求。 因为你的区间界限是整数,如果你的域足够小并且没有两个区间重叠,你可以只使用一个双精度数组。在您的示例中,索引 0 到 10 处的数组元素将设置为 9.0。然后查找是 O(1)。 我会说正确覆盖 Equals 会给你正确的结果,但这意味着你不能有两个键在字典中重叠在一起 也许是 SortedSet 【参考方案1】:

字典不是您描述的操作的合适数据结构。

如果要求间隔永远不会重叠,那么您可以构建一个排序的间隔列表并对其进行二进制搜索。

如果间隔可以重叠,那么您将面临更难解决的问题。为了有效地解决这个问题,您需要构建一个区间树:

http://en.wikipedia.org/wiki/Interval_tree

这是一个众所周知的数据结构。请参阅“算法简介”或任何其他关于数据结构的体面的本科教材。

【讨论】:

在我的模拟中间隔不允许重叠,所以我会坚持使用 SortedList。感谢埃里克的建议! 虽然 Jeffrey Cameron 的评论已经很老了,但 SortedList 目前没有快速查找最近的键操作是毫无价值的。此时,List 类和 List.BinarySearch 方法可以提供快速的最近键查找。【参考方案2】:

这仅在间隔不重叠时才有效。而且您的主要问题似乎是将单个(键)值转换为间隔。

我会围绕 SortedList 编写一个包装器。 SortedList.Keys.IndexOf() 会为您找到一个索引,该索引可用于验证间隔是否有效,然后使用它。

【讨论】:

我刚刚尝试使用标准的 SortedList 和自定义比较器(检查间隔是否相交。效果很好! 确实,如果要求间隔不重叠,那么问题就很简单了;您可以只对排序列表进行二进制搜索。如果允许间隔重叠,那么您将遇到一个更困难的问题。【参考方案3】:

这并不完全是你想要的,但我认为它可能是你所期望的最接近的。

你当然可以做得比这更好(我之前喝酒了吗?)。但是你不得不承认它很好很简单。

var map = new Dictionary<Func<double, bool>, double>()

     d => d >= 0.0 && d <= 10.0, 9.0 
;

var key = map.Keys.Single(test => test(1.0))
var value = map[key];

【讨论】:

耐人​​寻味,没想到用函数做按键……!我使用 Dictionary 的目的是进行 O(1) 查找,因为该表将被多次查询。有什么方法可以加快查找速度? 这是 O(n) 在查找。你可以做得更好。 @Eric - 最终尴尬的混合在一起。 :)【参考方案4】:

我通过确保集合是连续的,其中间隔永远不会重叠并且它们之间永远不会有间隙,从而解决了类似的问题。每个区间被定义为一个下边界,如果任何值等于或大于该边界并且小于下一个区间的下边界,则任何值都位于该区间中。低于最低边界的任何东西都是一个特殊的箱子。

这在一定程度上简化了问题。然后,我们还通过实施二分切优化关键搜索。很遗憾,我无法分享代码。

【讨论】:

【参考方案5】:

我会做一个小的 Interval 类,就像这样:

public class Interval

    public int Start get; set;
    public int End get; set;
    public int Step get; set;
    public double Value get; set;

    public WriteToDictionary(Dictionary<int, double> dict)
    
        for(int i = Start; i < End; i += Step)
        
            dict.Add(i, Value);
        
    

所以您仍然可以在字典中正常查找。也许您还应该在调用 Add() 之前执行一些检查,或者如果字典中已经有任何值,则执行某种回滚。

【讨论】:

【参考方案6】:

您可以在Open Geospatial Library 中找到区间树的Java flavored C# 实现。它需要一些小的调整来解决你的问题,它也可以真正使用一些 C# 化。

它是开源的,但我不知道在什么许可下。

【讨论】:

【参考方案7】:

我为 Dictionaryfunc 调整了一些想法,例如“ChaosPandion”在他之前的帖子中给了我这个想法。 我仍然解决了编码问题,但如果我尝试重构 我有一个惊人的问题/错误/缺乏理解:

Dictionary<Func<string, double, bool>, double> map = new Dictionary<Func<string, double, bool>, double>()

         (a, b) => a == "2018" && b == 4, 815.72,
         (a, b) => a == "2018" && b == 6, 715.72
;

所做的是,我用“2018”(年)和 4(月)之类的搜索来调用地图,结果是双值 815,72。 当我检查唯一的地图条目时,它们看起来像这样:

map working unique keys

这就是原始行为,到目前为止一切正常。 然后我尝试将其重构为:

Dictionary<Func<string, double, bool>, double> map = 
new Dictionary<Func<string, double, bool>, double>();

WS22(map, values2018, "2018");



private void WS22(Dictionary<Func<string, double, bool>, double> map, double[] valuesByYear, string strYear)

          int iMonth = 1;


             // step by step this works:
             map.Add((a, b) => (a == strYear) && (b == 1), dValue);
             map.Add((a, b) => (a == strYear) && (b == 2), dValue);



           // do it more elegant...
           foreach (double dValue in valuesByYear)
           

             //this does not work: exception after second iteration of foreach run
              map.Add((a, b) => (a == strYear) && (b == iMonth), dValue );
              iMonth+=1; 
          

this works: (i use b==1 and b==2)

this does not work (map not working exception on add item on second iteration)

所以我认为问题在于,地图在添加到地图字典时没有唯一键。问题是,我没有看到我的错误,为什么 b==1 有效而 b==iMonth 无效。

感谢任何帮助,让我大开眼界:)

【讨论】:

【参考方案8】:

您可以在codeplex 上查看此处的 powercollections,其中包含可以满足您需求的集合。

希望这会有所帮助, 最好的祝福, 汤姆。

【讨论】:

那是什么集合类型? @Jorn Schou-Rode: MultiDictionary, 'MultiDictionary 类将值与键相关联。与字典不同,每个键可以有多个与其关联的值。索引 MultiDictionary 时,您检索的不是与键关联的单个值,而是检索值的枚举。' 这毫无意义,因为 OP 想要将一个值区间映射到一个值。所以它是需要由多个值组成的键,以太区间边界或该区间中的所有值。 @Frank:根据文档,“MultiDictionary”和“构造时,您可以选择允许同一个值与键关联多次,或者只关联一次时间。”,其中一种方法是“AddMany”,这意味着“添加与键关联的新值。如果允许重复值,则此方法总是将新的键值对添加到字典中。如果不允许重复值,并且 key 已经具有等于与其关联的值之一的值,则替换该值,并且与 key 关联的值的数量不变。' 多字典不是表示区间树的合适数据结构。

以上是关于使用值范围作为键的字典对象的主要内容,如果未能解决你的问题,请参考以下文章

C#开发的OpenRA使用自定义字典的比较函数

C#开发的OpenRA使用自定义字典的比较函数

字典使所有键的值等于最近的键/值添加

从字典键中提取值 - 然后组合成一个对象

Redis之字典

在 Python 中使用 None 值删除字典中键的正确方法