使用值范围作为键的字典对象
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字典不是您描述的操作的合适数据结构。
如果要求间隔永远不会重叠,那么您可以构建一个排序的间隔列表并对其进行二进制搜索。
如果间隔可以重叠,那么您将面临更难解决的问题。为了有效地解决这个问题,您需要构建一个区间树:
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】:我为 Dictionary 和 func 调整了一些想法,例如“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以上是关于使用值范围作为键的字典对象的主要内容,如果未能解决你的问题,请参考以下文章