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

Data structures that can map a range of keys to a value

我试图找到一个数据结构,它从一系列值中获取一个特定的值,并将其映射到一个键。

例如,我有以下条件:

  • 从1到2.9,我想把它映射到A。
  • 从4点到6点,我想把它映射到B点。
  • 从6.5到10,我想把它映射到C。
  • 我有一个值5,我想把它映射到一个键。所以根据上面的条件,我应该把它映射到b。

    在Java中有任何数据结构,任何人都可以建议我解决这个问题吗?

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

    编辑:

    多亏了马丁·埃利斯,我决定用Treemap来解决这个问题。


    您的范围不重叠吗?如果可以,可以使用treemap:

    1
    2
    3
    4
    5
    6
    7
    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",而不是未定义):

    1
    2
    3
    4
    5
    6
    7
    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();
    }

    例子:

    1
    mappedValue(m, 5) == 'B'

    更多结果包括:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    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


    这种类型的数据结构称为间隔树。(wikipedia页面只显示间隔可能重叠的情况,但是可以想象在添加新间隔时,您希望删除任何重叠间隔的映射。在谷歌上搜索实现,看看是否符合你的需要。


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

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

    树的每个节点都应该有一个最小键、一个最大键和一个与该范围相关联的值。然后,您可以拥有指向表示下一个更高和下一个更低范围(如果存在)的节点的链接。类似:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    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;
        }
    }

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


    Guava Rangemap提供开箱即用的专业解决方案:

    1
    2
    3
    4
    5
    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"

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    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;
        }
    }

    你可以做到

    1
    2
    3
    4
    5
    6
    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.:这样的映射将把一系列键映射到一个值


    只需在地图中有一个列表作为值。

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

    还有一个建议:

    除非需要同步访问,否则不要使用哈希表。

    编辑:

    哈希表方法是同步的,即没有两个线程可以在单个时间点访问这些方法。哈希映射方法不同步。如果使用hashtable,则会影响性能。使用hashmap以获得更好的性能。


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

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