使用java map进行范围搜索

时间:2009-08-21 23:37:40

标签: java maps

我有一个用例,如果一个数字在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那么简单。我希望能够进行范围搜索。对此感到抱歉。根据我添加了正确示例的查询,数字是连续的

6 个答案:

答案 0 :(得分:76)

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

  1. 只需为所有有效键值填充Map,并将多个键映射到相同的值。假设您使用HashMaps,这应该是最有效的时间(O(1)查找),尽管您在设置时有更多工作并且您使用更多空间。
  2. 使用NavigableMap并使用floorEntry(key)进行查找。这应该是更少的时间效率(O(log(N)查找),但更节省空间。
  3. 这是一个使用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();
    }
    

答案 1 :(得分:11)

的伪代码:

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

答案 2 :(得分:4)

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

答案 3 :(得分:0)

我认为你想要的是foo()/10的内容,但这会让你的范围略微偏离你的要求。如果它们不遵循简单的模式,您可以随时与“地图”中的每个项目进行两个端点的比较。

答案 4 :(得分:0)

我认为最简单的解决方案是从范围的上边界添加映射到范围映射到的值,然后继续增加数字(地图中的键)直到达到映射(这是上层)你的号码范围的边界。)

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

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

答案 5 :(得分:0)

基于 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