可以将一系列键映射到值的数据结构
Posted
技术标签:
【中文标题】可以将一系列键映射到值的数据结构【英文标题】:Data structures that can map a range of keys to a value 【发布时间】:2012-11-04 04:31:35 【问题描述】:我正在尝试找到一种数据结构,它从一系列值中获取特定值并将其映射到键。
例如,我有以下条件:
-
从 1 到 2.9,我想把它映射到 A。
从4到6,我想映射到B。
从6.5到10,我想映射到C。
我的值为 5,我想将它映射到一个键。所以基于上述条件,我应该把它映射到B。
有没有任何人可以推荐给我解决问题的 Java 数据结构?
目前我正在使用只能将值映射到键的哈希表。我试图将值的范围映射到哈希表中存在的特定值。但是,我陷入了将值范围映射到特定值的过程中。所以现在我正在尝试另一种将值范围映射到键的方法。有谁知道我该如何解决这个问题?
编辑:
感谢 Martin Ellis,我决定使用 TreeMap 来解决问题。
【问题讨论】:
Guava 将获得一个 RangeMap 结构,它在 14.0 中几乎完全做到了这一点。 【参考方案1】:其中一种方法是,使用列表实现之一作为键的值。
map.put("A", ArrayList<Integer>);
【讨论】:
嗨。值的范围是双倍的,可以有许多小数位。在这种情况下,我应该将所有可能的值(双精度)添加到作为哈希表/映射值的双精度列表中吗? 但是如何从哈希表/映射中检索密钥? 如果你使用HashMap,你可以使用keySet()。迭代keySet,识别key是否存在,如果有获取值List,如果没有则创建新的list和key。 我正在使用哈希表,之前使用 get() 来获取密钥。尽管我的值是双精度列表,但在这种情况下我可以使用相同的方法吗? 是的,你可以。如果该键没有值,那么您可以创建新列表并添加它。顺便说一句,坚持使用 HashTable 而不是 HashMap 的任何具体原因?docs.oracle.com/javase/6/docs/api/java/util/…【参考方案2】:只需在您的地图中使用一个列表作为值。
Map<String, List<Double>> map = new HashMap<String, List<Double>>();
还有一个建议:
除非您想要同步访问,否则不要使用 Hashtable。
编辑:
哈希表方法是同步的,也就是说,没有两个线程可以在一个时间点访问这些方法。 HashMap 方法未同步。如果您使用 Hashtable,则会对性能造成影响。使用 HashMap 以获得更好的性能。
【讨论】:
嗨。值的范围是双倍的,可以有许多小数位。在这种情况下,我应该将所有可能的值(双精度)添加到作为哈希表/映射值的双精度列表中吗? 谢谢!同步访问是什么意思? 我会说根本不要使用Hashtable
。如果您需要处理并发,最好使用ConcurrentHashMap
,如here 所示,如果您不担心并发(即没有线程的简单Java 程序),您可以使用HashMap
。此外,最好使用List<Double>
而不是ArrayList<Double>
进行定义。见What does it mean to “program to an interface”?。
@LuiggiMendoza 感谢您的建议,我将其更改为 ListHashMap
不适用于将范围映射到值,除非您找到一种方法为范围和其中匹配的单个值生成哈希码。但以下方法可能是您正在寻找的方法
public class RangeMap
static class RangeEntry
private final double lower;
private final double upper;
private final Object value;
public RangeEntry(double lower, double upper, Object mappedValue)
this.lower = lower;
this.upper = upper;
this.value = mappedValue;
public boolean matches(double value)
return value >= lower && value <= upper;
public Object getValue() return value;
private final List<RangeEntry> entries = new ArrayList<RangeEntry>();
public void put(double lower, double upper, Object mappedValue)
entries.add(new RangeEntry(lower, upper, mappedValue));
public Object getValueFor(double key)
for (RangeEntry entry : entries)
if (entry.matches(key))
return entry.getValue();
return null;
你可以的
RangeMap map = new RangeMap();
map.put(1, 2.9, "A");
map.put(4, 6, "B");
map.getValueFor(1.5); // = "A"
map.getValueFor(3.5); // = null
它不是很有效,因为它只是在一个列表上进行迭代,如果你把冲突的范围放在那里,它不会在那种状态下抱怨。只会返回它找到的第一个。
P.S.:像这样的映射会将一系列 keys 映射到一个 value
【讨论】:
【参考方案4】:您的范围是否不重叠?如果是这样,您可以使用 TreeMap:
TreeMap<Double, Character> m = new TreeMap<Double, Character>();
m.put(1.0, 'A');
m.put(2.9, null);
m.put(4.0, 'B');
m.put(6.0, null);
m.put(6.5, 'C');
m.put(10.0, null);
查找逻辑有点复杂,因为您可能想要一个包容性查找(即 2.9 映射到“A”,而不是未定义):
private static <K, V> V mappedValue(TreeMap<K, V> map, K key)
Entry<K, V> e = map.floorEntry(key);
if (e != null && e.getValue() == null)
e = map.lowerEntry(key);
return e == null ? null : e.getValue();
例子:
mappedValue(m, 5) == 'B'
更多结果包括:
0.9 null
1.0 A
1.1 A
2.8 A
2.9 A
3.0 null
6.4 null
6.5 C
6.6 C
9.9 C
10.0 C
10.1 null
【讨论】:
非常感谢您的解决方案!它似乎完全符合我的要求。不幸的是,我似乎无法正确构建树形图。我似乎无法添加新范围。比如'm.put(val_min, "A");m.put(val_max, null);/*对val_min和val_max做一些修改*/m.put(val_min, "B");m.put( val_max, null);',如果我传递一个键为 B 但总是得到 A 的值。你知道为什么吗? 如果您使用的是“A”而不是“A”,那么您需要将Character
替换为String
。还有什么不适合你的吗?
我添加了在我的原始帖子中不起作用的代码的快照。
您可以添加 System.out.println(m) 的结果,以便我可以看到地图的样子吗?一方面,需要检查范围是否重叠。
我在 android 中编码。你知道如何在 Android 调试器中打印出地图吗?【参考方案5】:
这似乎是使用树结构的自然情况。
不幸的是,实现java.util.Map
接口并不实用,因为它指定了返回所有键的方法,而在您的情况下,理论上您拥有大量不切实际的键。
树的每个节点都应该有一个最小键、一个最大键和一个与该范围关联的值。然后,您可以链接到代表下一个更高和下一个较低范围的节点(如果它们存在)。比如:
public class RangeMap<K extends Comparable<K>, V>
protected boolean empty;
protected K lower, upper;
protected V value;
protected RangeMap<K, V> left, right;
public V get(K key)
if (empty)
return null;
if (key.compareTo(lower) < 0)
return left.get(key);
if (key.compareTo(upper) > 0)
return right.get(key);
/* if we get here it is in the range */
return value;
public void put(K from, K to, V val)
if (empty)
lower = from;
upper = to;
value = val;
empty = false;
left = new RangeMap<K,V>();
right = new RangeMap<K,V>();
return;
if (from.compareTo(lower) < 0)
left.put(from, to, val);
return;
if (to.compareTo(upper) > 0)
right.put(from, to, val);
return;
/* here you'd have to put the code to deal with adding an overlapping range,
however you want to handle that. */
public RangeMap()
empty = true;
如果您需要比树提供的更快的查找,您可能需要研究类似跳过列表或开发自己的哈希函数。
【讨论】:
【参考方案6】:这种类型的数据结构称为Interval Tree。 (***页面只展示了区间可能重叠的情况,但可以想象这样一种情况,当您添加新区间时,您希望删除任何重叠区间的映射。谷歌搜索实现,看看是否符合您的需求。
【讨论】:
【参考方案7】:Guava RangeMap provides 开箱即用的专业解决方案:
RangeMap<Integer, String> rangeMap = TreeRangeMap.create();
rangeMap.put(Range.closed(1, 100), "foo"); // [1, 100] => "foo"
rangeMap.put(Range.open(3, 6), "bar"); // [1, 3] => "foo", (3, 6) => "bar", [6, 100] => "foo"
rangeMap.get(42); // returns "foo"
【讨论】:
不知道为什么这个答案得到这么少的支持。看起来对我很有用。请注意: rangeMap.get(3); // 返回 "foo" rangeMap.get(5); // 返回 "bar" rangeMap.get(6); // 返回 "foo" 另请注意 Range.closedOpen() 和 Range.openClosed() 方法返回半开放/封闭范围。【参考方案8】:我认为 Apache commons 包含一些解决此问题的方法。
http://commons.apache.org/proper/commons-net/apidocs/index.html
使用 SubnetUtils.SubnetInfo 的 SubnetUtils
【讨论】:
以上是关于可以将一系列键映射到值的数据结构的主要内容,如果未能解决你的问题,请参考以下文章