【问题标题】:Using java map for range searches使用 java map 进行范围搜索
【发布时间】:2010-11-21 19:52:03
【问题描述】:

我有一个用例,如果一个数字在 0-10 之间,它应该返回 0,如果它在 11-20 之间,它应该返回 1 等等

0 => 0-3, (0 and 3 are inclusive)
1 => 4-15, (4 and 15 are inclusive)
2 => 16-40, (16 and 40 are inclusive)
3 => 41-88, (41 and 88 are inclusive)
5 => 89-300 (89 and 300 are inclusive)

我在考虑如何实现并正在考虑 java 地图,但它不允许范围搜索

我对这样的东西很感兴趣,我有一个功能

int foo() {

}

如果 foo 返回 5 ,因为它位于 0 到 10 之间,我会使用 0,如果 foo 返回 25 它会使用 2。

任何想法

编辑:实际上范围并不像 0-10、11-20 那样简单。我希望能够进行范围搜索。很抱歉造成混乱。根据我添加了正确示例的查询,数字是连续的

【问题讨论】:

  • 请提供一个你想做什么的真实例子。
  • 示例中给出的范围不是连续的。如果搜索 4 或 50,您想要 null 结果、上方、下方或最近的范围,还是什么?
  • @mkal:你不断改变需求的方式,你可以成为地狱的经理:-)
  • 抱歉,如果这增加了这么多的混乱:)

标签: java maps


【解决方案1】:

对于范围不统一且存在“漏洞”的更普遍的问题,我可以想到多种可能的解决方案。最简单的是:

  1. 只需为所有有效键值填充一个映射,多个键映射到同一个值。假设您使用 HashMap,这应该是最省时的(O(1) 查找),尽管您在设置时需要做更多的工作并且使用更多空间。
  2. 使用 NavigableMap 并使用floorEntry(key) 进行查找。这应该更节省时间(O(log(N) 查找),但更节省空间。

这是一个使用 NavigableMaps 的解决方案,它允许在映射中出现“洞”。

private static class Range {
   public int upper, value;
   ...
}

NavigableMap<Integer, Range> map = new TreeMap<Integer, Range>();
map.put(0, new Range(3, 0));       // 0..3     => 0
map.put(5, new Range(10, 1));      // 5..10    => 1
map.put(100, new Range(200, 2));   // 100..200 => 2

// To do a lookup for some value in 'key'
Map.Entry<Integer,Range> entry = map.floorEntry(key);
if (entry == null) {
    // too small
} else if (key <= entry.getValue().upper) {
    return entry.getValue().value;
} else {
    // too large or in a hole
}

另一方面,如果没有“漏洞”,解决方案会更简单:

NavigableMap<Integer, Integer> map = new TreeMap<Integer, Integer>();
map.put(0, 0);    // 0..4     => 0
map.put(5, 1);    // 5..10    => 1
map.put(11, 2);   // 11..200  => 2

// To do a lookup for some value in 'key'
if (key < 0 || key > 200) {
    // out of range
} else {
   return map.floorEntry(key).getValue();
}

【讨论】:

  • 这是很好的利用现有的TreeMap做一个简单的范围搜索。请注意,如果存在重叠间隔,则该方法将返回具有最接近查找键的下键的间隔。同样,此方法不支持搜索包含给定搜索键的所有重叠区间,如stackoverflow.com/questions/1580185/… 中所述
  • 如果有重叠的范围,那么这些范围(很可能)被错误地指定了。至少,这是我对这个问题的解读。
【解决方案2】:

伪代码:

  1. 将范围边界存储在一个平面数组中:new int[] {0, 3, 5, 15, 100, 300}
  2. 在数组中进行二进制搜索,就像在数组中插入一个数字一样。见Arrays.binarySearch()
  3. 如果插入点是偶数,则该数字不适合任何范围。
  4. 如果插入点为奇数,则符合相应范围。例如,上述数组中10的插入点为3,位于515之间,属于第二个范围。

【讨论】:

  • 注意:这仅在我们映射到整数 {0, 1, 2, ...} 时才有效。对于更一般的情况,应使用某种地图。
【解决方案3】:

在无法用算术解决的更一般情况下,您可以使用适当的比较器创建 TreeMap。为边界值添加映射,然后使用 ceilingEntry 或 floorEntry 找到合适的匹配项。

【讨论】:

    【解决方案4】:

    我认为你想要的是类似于foo()/10 的东西,但这会使你的范围与你所要求的略有不同。如果它们不遵循简单的模式,您始终可以与“地图”中每个项目的两个端点进行比较。

    【讨论】:

      【解决方案5】:

      我认为最简单的解决方案是将范围上边界的映射添加到范围映射到的值,然后不断增加数字(映射中的键)直到到达映射(即上您的号码所在范围的边界)。

      另一种方法是使用范围内的所有条目填充映射,并为每个条目添加一个映射。

      哪个更有效取决于您是否可能需要重复请求一个范围内的所有数字(使用后一种解决方案),还是只请求一些数字几次(使用第一个)

      【讨论】:

        【解决方案6】:

        基于John Kugelman's answer,我需要类似的东西来进行具有与日期范围相关联的数据的日期范围搜索...这是我如何应用它的示例。 "dates" 是你的键,"rangeData" 是相关的数据。

          long[] dates = new long[] {
            Instant.parse("1900-01-01T00:00:00Z").toEpochMilli(),
            Instant.parse("1950-01-01T00:00:00Z").toEpochMilli(),
            Instant.parse("1960-01-01T00:00:00Z").toEpochMilli(),
            Instant.parse("1970-01-01T00:00:00Z").toEpochMilli(),
            Instant.parse("1980-01-01T00:00:00Z").toEpochMilli(),
            Instant.parse("1990-01-01T00:00:00Z").toEpochMilli(),
            Instant.parse("2000-01-01T00:00:00Z").toEpochMilli(),
            Instant.parse("2010-01-01T00:00:00Z").toEpochMilli()
          };
          String[] rangeData = new String[] {
            "Before 1900 data", "Before 1950 data", "Before 1960 data", 
            "Before 1970 data", "Before 1980 data", "Before 1990 data", 
            "Before 2000 data", "Before 2010 data"
          };
          
          long searchDate = Instant.parse("1949-02-15T00:00:00Z").toEpochMilli();
          int result = Arrays.binarySearch(dates, searchDate);
          
          System.out.println("Result: " + result); 
          if (result == dates.length) {
              System.out.println("Date is after all ranges");
              System.out.println("Data: none");
          }
          else if (result >= 0) {
              System.out.println("Date is an exact match of " + Instant.ofEpochMilli(dates[result]));
              System.out.println("Data: " + rangeData[result]);
          }
          else {
              int resultIndex = Math.abs(result) - 1;
              System.out.println("Date is before idx: "+ resultIndex + " date " + Instant.ofEpochMilli(dates[resultIndex]));
              System.out.println("Data: " + rangeData[resultIndex]);
          }
        

        结果

        Result: -2
        Date is before idx: 1 date 1950-01-01T00:00:00Z
        Data: Before 1950 data
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2014-11-24
          • 2011-06-20
          • 2015-12-01
          • 2016-05-12
          • 2019-08-12
          • 2013-10-13
          • 1970-01-01
          相关资源
          最近更新 更多