仅支持半开范围时如何进行包含范围查询(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进行比较 数字基元盒装类型ByteCharacter 等作为键的重载都必须单独编写 需要对toInclusive == Integer.MAX_VALUE进行特殊检查,因为+1会溢出,subMap会抛出IllegalArgumentException: fromKey &gt; toKey 一般来说,这是一个过于丑陋和过于具体的解决方案 String 键呢?或者一些甚至可能不是Comparable&lt;?&gt; 的未知类型?

所以问题是:是否可以编写一个通用的subMapInclusive 方法,该方法采用SortedMap&lt;K,V&gt;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&lt;K,V&gt; 进行包含范围查询是理想的,但是虽然CollectionsSortedMap 提供了实用方法(除其他外),但我们无法享受与NavigableMap 相同的奢华.

相关问题

Writing a synchronized thread-safety wrapper for NavigableMap

在 API 上只提供独占的上限范围查询

这是否突出了独占上限范围查询的问题? 过去,当独占上限是唯一可用的功能时,如何完成包含范围查询?

【问题讨论】:

NavigableMapSortedMap 的替代品,之所以引入是因为后者不支持其中一些操作。 【参考方案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 result = new TreeMap(); result.putAll(map.subMap(from, to));但这并不是最漂亮的东西。否则,您可以尝试使用 NavigableMap ,它直接扩展 SortedMap 并具有带有布尔值的方法(TreeMap 继承自它)。设计师认为 SortedMap 也没有带有布尔值的方法,这似乎是一个糟糕的电话。

以上是关于仅支持半开范围时如何进行包含范围查询(ala SortedMap.subMap)的主要内容,如果未能解决你的问题,请参考以下文章

如何进行查询以仅获取在值范围内具有 N 个数字的结果?

elasticsearch按范围聚合

当基本字符串包含双引号时,仅针对 Swift 中 UILabel 的特定范围的点击手势不起作用

如何使用 Bitmap 支持范围查询

如何使用 Bitmap 支持范围查询

日志查询神器 Kibana简单使用