仅支持半开范围时如何进行包含范围查询(ala SortedMap.subMap)
Posted
技术标签:
【中文标题】仅支持半开范围时如何进行包含范围查询(ala SortedMap.subMap)【英文标题】:How to do inclusive range queries when only half-open range is supported (ala SortedMap.subMap) 【发布时间】:2011-02-20 21:49:36 【问题描述】:开启SortedMap.subMap
这是SortedMap<K,V>.subMap
的API:
SortedMap<K,V> subMap(K fromKey, K toKey)
:返回此映射部分的视图,其键范围从fromKey
(含)到toKey
(不含)。
这种包容性的下限、独占的上限组合(“半开范围”)在 Java 中很流行,虽然它确实有它的好处,但它也有它的怪癖,我们很快就会看到。
下面的sn-p说明了subMap
的简单用法:
static <K,V> SortedMap<K,V> someSortOfSortedMap()
return Collections.synchronizedSortedMap(new TreeMap<K,V>());
//...
SortedMap<Integer,String> map = someSortOfSortedMap();
map.put(1, "One");
map.put(3, "Three");
map.put(5, "Five");
map.put(7, "Seven");
map.put(9, "Nine");
System.out.println(map.subMap(0, 4));
// prints "1=One, 3=Three"
System.out.println(map.subMap(3, 7));
// prints "3=Three, 5=Five"
最后一行很重要:7=Seven
被排除在外,因为 subMap
具有排他性上限。现在假设我们实际上需要一个 inclusive 上限,那么我们可以尝试编写一个这样的实用方法:
static <V> SortedMap<Integer,V>
subMapInclusive(SortedMap<Integer,V> map, int from, int to)
return (to == Integer.MAX_VALUE)
? map.tailMap(from)
: map.subMap(from, to + 1);
然后,继续上面的 sn-p,我们得到:
System.out.println(subMapInclusive(map, 3, 7));
// prints "3=Three, 5=Five, 7=Seven"
map.put(Integer.MAX_VALUE, "Infinity");
System.out.println(subMapInclusive(map, 5, Integer.MAX_VALUE));
// 5=Five, 7=Seven, 9=Nine, 2147483647=Infinity
需要注意几点:
好消息是我们不关心值的类型,但是...subMapInclusive
假定 Integer
密钥为 to + 1
工作。
一个通用版本,也采用例如Long
键是不可能的(请参阅相关问题)
更不用说Long
,我们需要与Long.MAX_VALUE
进行比较
数字基元盒装类型Byte
、Character
等作为键的重载都必须单独编写
需要对toInclusive == Integer.MAX_VALUE
进行特殊检查,因为+1
会溢出,subMap
会抛出IllegalArgumentException: fromKey > toKey
一般来说,这是一个过于丑陋和过于具体的解决方案
String
键呢?或者一些甚至可能不是Comparable<?>
的未知类型?
所以问题是:是否可以编写一个通用的subMapInclusive
方法,该方法采用SortedMap<K,V>
和K fromKey, K toKey
,并执行包含范围的subMap
查询?
相关问题
Are upper bounds of indexed ranges always assumed to be exclusive? Is it possible to write a generic +1 method for numeric box types in Java?开启NavigableMap
应该提到的是NavigableMap.subMap
重载需要两个额外的boolean
变量来表示边界是包含还是排除。如果在SortedMap
中提供了此功能,那么甚至都不会询问以上任何内容。
因此,使用NavigableMap<K,V>
进行包含范围查询是理想的,但是虽然Collections
为SortedMap
提供了实用方法(除其他外),但我们无法享受与NavigableMap
相同的奢华.
相关问题
Writing a synchronized thread-safety wrapper forNavigableMap
在 API 上只提供独占的上限范围查询
这是否突出了独占上限范围查询的问题? 过去,当独占上限是唯一可用的功能时,如何完成包含范围查询?【问题讨论】:
NavigableMap
是SortedMap
的替代品,之所以引入是因为后者不支持其中一些操作。
【参考方案1】:
这是我对通用包容性子图的实现。在这里我假设由于地图是排序的,所以tailmap的时间复杂度会很低,所以诀窍是从尾部开始并查看返回的键,然后根据这些键获取尾部,常规子图,或带有下一个键的子图:
static <K, V> SortedMap<K,V>
subMapInclusive(SortedMap<K,V> map, K from, K to)
if(to == null) return map.tailMap(from);
//What appears at key "to" or later?
Iterator<K> keys = map.tailMap(to).keySet().iterator();
//Nothing, just take tail map.
if(!keys.hasNext()) return map.tailMap(from);
K key = keys.next();
//The first item found isn't to so regular submap will work
if(!to.equals(key)) return map.subMap(from, to);
//to is in the map
//it is not the last key
if(keys.hasNext()) return map.subMap(from, keys.next());
//it is the last key
return map.tailMap(from);
【讨论】:
开箱即用的好想法!【参考方案2】:使用 Guava 的 Maps.filterKeys 怎么样?
Maps.filterKeys(map, Range.closed(0, 4)); //includes 1 and 3
Maps.filterKeys(map, Range.closed(3, 7)); //includes 3, 5, and 7
Range 谓词的参数必须实现 Comparable,但您也可以使用 Predicates.in 来使用集合进行过滤:
Set<Integer> filterSet = Sets.newHashSet();
filterSet.add(3);
filterSet.add(5);
filterSet.add(7);
Maps.filterKeys(map, Predicates.in(filterSet)); //includes 3, 5, and 7
【讨论】:
【参考方案3】:也许你可以这样做:
static <K, V> SortedMap<K,V>
subMapInclusive(SortedMap<K,V> map, K from, K to)
SortedMap<K,V> result = map.subMap(from, to);
V last = map.get(to);
if (last != null) result.put(to, last);
return result;
编辑:TreeMap 似乎也有一个 subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) 方法;也许您可以使用它来代替 SortedMap。
【讨论】:
这行不通,因为 submap 的定义是它是地图的视图,特别是在 API 中:“返回的地图将抛出 IllegalArgumentException 试图在其外部插入一个键范围。”在添加到的情况下,这必然是正确的。 我没想到。如果您不想保留结果的该属性,我认为您可以执行 SortedMap以上是关于仅支持半开范围时如何进行包含范围查询(ala SortedMap.subMap)的主要内容,如果未能解决你的问题,请参考以下文章