可以将一系列键映射到值的数据结构

Posted

技术标签:

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

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

例如,我有以下条件:

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

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

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

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

编辑:

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

【问题讨论】:

Guava 将获得一个 RangeMap 结构,它在 14.0 中几乎完全做到了这一点。 【参考方案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

【讨论】:

以上是关于可以将一系列键映射到值的数据结构的主要内容,如果未能解决你的问题,请参考以下文章

转载java中Map的详解

JavaScript的面向对象

java Map接口

JavaEE基础(十八)/集合

Map简介

java中常用的数据结构--Map