【问题标题】: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

标签: c# .net dictionary lookup


【解决方案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 关联的值的数量不变。'
          • 多字典不是表示区间树的合适数据结构。
          猜你喜欢
          • 2017-03-23
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-12-17
          • 2019-11-03
          • 1970-01-01
          • 2012-08-31
          • 2011-10-09
          相关资源
          最近更新 更多