【问题标题】:Data structures that can map a range of keys to a value可以将一系列键映射到值的数据结构
【发布时间】:2012-11-04 04:31:35
【问题描述】:

我正在尝试找到一种数据结构,它从一系列值中获取特定值并将其映射到键。

例如,我有以下条件:

  1. 从 1 到 2.9,我想把它映射到 A。
  2. 从4到6,我想映射到B。
  3. 从6.5到10,我想映射到C。

我的值为 5,我想将它映射到一个键。所以基于上述条件,我应该把它映射到B。

有没有任何人可以推荐给我解决问题的 Java 数据结构?

目前我正在使用只能将值映射到键的哈希表。我试图将值的范围映射到哈希表中存在的特定值。但是,我陷入了将值范围映射到特定值的过程中。所以现在我正在尝试另一种将值范围映射到键的方法。有谁知道我该如何解决这个问题?

编辑:

感谢 Martin Ellis,我决定使用 TreeMap 来解决问题。

【问题讨论】:

  • Guava 将获得一个 RangeMap 结构,它在 14.0 中几乎完全做到了这一点。

标签: java dictionary data-structures range-map


【解决方案1】:

其中一种方法是,使用列表实现之一作为键的值。

map.put("A", ArrayList<Integer>);

【讨论】:

  • 嗨。值的范围是双倍的,可以有许多小数位。在这种情况下,我应该将所有可能的值(双精度)添加到作为哈希表/映射值的双精度列表中吗?
  • 但是如何从哈希表/映射中检索密钥?
  • 如果你使用HashMap,你可以使用keySet()。迭代keySet,识别key是否存在,如果有获取值List,如果没有则创建新的list和key。
  • 我正在使用哈希表,之前使用 get() 来获取密钥。尽管我的值是双精度列表,但在这种情况下我可以使用相同的方法吗?
  • 是的,你可以。如果该键没有值,那么您可以创建新列表并添加它。顺便说一句,坚持使用 HashTable 而不是 HashMap 的任何具体原因?docs.oracle.com/javase/6/docs/api/java/util/…
【解决方案2】:

只需在您的地图中使用一个列表作为值。

Map<String, List<Double>> map = new HashMap<String, List<Double>>();

还有一个建议

除非您想要同步访问,否则不要使用 Hashtable。

编辑:

哈希表方法是同步的,也就是说,没有两个线程可以在一个时间点访问这些方法。 HashMap 方法未同步。如果您使用 Hashtable,则会对性能造成影响。使用 HashMap 以获得更好的性能。

【讨论】:

  • 嗨。值的范围是双倍的,可以有许多小数位。在这种情况下,我应该将所有可能的值(双精度)添加到作为哈希表/映射值的双精度列表中吗?
  • 谢谢!同步访问是什么意思?
  • 我会说根本不要使用Hashtable。如果您需要处理并发,最好使用ConcurrentHashMap,如here 所示,如果您不担心并发(即没有线程的简单Java 程序),您可以使用HashMap。此外,最好使用List&lt;Double&gt; 而不是ArrayList&lt;Double&gt; 进行定义。见What does it mean to “program to an interface”?
  • @LuiggiMendoza 感谢您的建议,我将其更改为 List :)
  • 为什么hashmap优于hashtable?
【解决方案3】:

HashMap 不适用于将范围映射到值,除非您找到一种方法为范围和其中匹配的单个值生成哈希码。但以下方法可能是您正在寻找的方法

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;
    }
}

你可以的

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.:像这样的映射会将一系列 keys 映射到一个 value

【讨论】:

    【解决方案4】:

    您的范围是否不重叠?如果是这样,您可以使用 TreeMap:

    TreeMap<Double, Character> m = new TreeMap<Double, Character>();
    m.put(1.0, 'A');
    m.put(2.9, null);
    m.put(4.0, 'B');
    m.put(6.0, null);
    m.put(6.5, 'C');
    m.put(10.0, null);
    

    查找逻辑有点复杂,因为您可能想要一个包容性查找(即 2.9 映射到“A”,而不是未定义):

    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();
    }
    

    例子:

    mappedValue(m, 5) == 'B'
    

    更多结果包括:

    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
    

    【讨论】:

    • 非常感谢您的解决方案!它似乎完全符合我的要求。不幸的是,我似乎无法正确构建树形图。我似乎无法添加新范围。比如'm.put(val_min, "A");m.put(val_max, null);/*对val_min和val_max做一些修改*/m.put(val_min, "B");m.put( val_max, null);',如果我传递一个键为 B 但总是得到 A 的值。你知道为什么吗?
    • 如果您使用的是“A”而不是“A”,那么您需要将Character 替换为String。还有什么不适合你的吗?
    • 我添加了在我的原始帖子中不起作用的代码的快照。
    • 您可以添加 System.out.println(m) 的结果,以便我可以看到地图的样子吗?一方面,需要检查范围是否重叠。
    • 我在 Android 中编码。你知道如何在 Android 调试器中打印出地图吗?
    【解决方案5】:

    这似乎是使用树结构的自然情况。

    不幸的是,实现java.util.Map 接口并不实用,因为它指定了返回所有键的方法,而在您的情况下,理论上您拥有大量不切实际的键。

    树的每个节点都应该有一个最小键、一个最大键和一个与该范围关联的值。然后,您可以链接到代表下一个更高和下一个较低范围的节点(如果它们存在)。比如:

    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;
        }
    }
    

    如果您需要比树提供的更快的查找,您可能需要研究类似跳过列表或开发自己的哈希函数。

    【讨论】:

      【解决方案6】:

      这种类型的数据结构称为Interval Tree。 (维基百科页面只展示了区间可能重叠的情况,但可以想象这样一种情况,当您添加新区间时,您希望删除任何重叠区间的映射。谷歌搜索实现,看看是否符合您的需求。

      【讨论】:

        【解决方案7】:

        Guava RangeMap provides 开箱即用的专业解决方案:

        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"
        

        【讨论】:

        • 不知道为什么这个答案得到这么少的支持。看起来对我很有用。请注意: rangeMap.get(3); // 返回 "foo" rangeMap.get(5); // 返回 "bar" rangeMap.get(6); // 返回 "foo" 另请注意 Range.closedOpen() 和 Range.openClosed() 方法返回半开放/封闭范围。
        【解决方案8】:

        我认为 Apache commons 包含一些解决此问题的方法。

        http://commons.apache.org/proper/commons-net/apidocs/index.html

        使用 SubnetUtils.SubnetInfo 的 SubnetUtils

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-01-07
          • 1970-01-01
          • 1970-01-01
          • 2015-08-12
          相关资源
          最近更新 更多