Data structures that can map a range of keys to a value
我试图找到一个数据结构,它从一系列值中获取一个特定的值,并将其映射到一个键。
例如,我有以下条件:
我有一个值5,我想把它映射到一个键。所以根据上面的条件,我应该把它映射到b。
在Java中有任何数据结构,任何人都可以建议我解决这个问题吗?
目前我使用的哈希表只能将值映射到键。我试图将值的范围映射到哈希表中存在的特定值。但是,我陷入了将值的范围映射到特定值的过程中。所以现在我尝试用另一种方法将值的范围映射到键。有人知道我如何解决这个问题吗?
编辑:
多亏了马丁·埃利斯,我决定用Treemap来解决这个问题。
您的范围不重叠吗?如果可以,可以使用treemap:
1 2 3 4 5 6 7 |
查找逻辑有点复杂,因为您可能需要包含查找(即2.9映射到"a",而不是未定义):
1 2 3 4 5 6 7 | 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(); } |
例子:
1 | mappedValue(m, 5) == 'B' |
更多结果包括:
1 2 3 4 5 6 7 8 9 10 11 12 | 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 |
这种类型的数据结构称为间隔树。(wikipedia页面只显示间隔可能重叠的情况,但是可以想象在添加新间隔时,您希望删除任何重叠间隔的映射。在谷歌上搜索实现,看看是否符合你的需要。
这似乎是使用树结构的自然情况。
不幸的是,实现
树的每个节点都应该有一个最小键、一个最大键和一个与该范围相关联的值。然后,您可以拥有指向表示下一个更高和下一个更低范围(如果存在)的节点的链接。类似:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 | 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; } } |
如果您需要比树提供的更快的查找,您可能需要查找类似于跳过列表或开发自己的哈希函数的内容。
Guava Rangemap提供开箱即用的专业解决方案:
1 2 3 4 5 | 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" |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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; } } |
你可以做到
1 2 3 4 5 6 | 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.:这样的映射将把一系列键映射到一个值
只需在地图中有一个列表作为值。
还有一个建议:
除非需要同步访问,否则不要使用哈希表。
编辑:
哈希表方法是同步的,即没有两个线程可以在单个时间点访问这些方法。哈希映射方法不同步。如果使用hashtable,则会影响性能。使用hashmap以获得更好的性能。
其中一种方法是,使用其中一个列表实现作为键的值。
1 | map.put("A", ArrayList<Integer>); |