26

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

例如,我有以下条件:

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

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

是否有任何人可以向我推荐来解决问题的 Java 数据结构?

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

编辑:

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

4

8 回答 8

39

你的范围不重叠吗?如果是这样,您可以使用 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
于 2012-11-15T15:08:37.370 回答
4

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

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"
于 2017-05-24T11:11:10.797 回答
2

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

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

它不是很有效,因为它只是在一个列表上进行迭代,如果你把冲突的范围放在那里,它不会在那种状态下抱怨。只会返回它找到的第一个。

PS:像这样的映射会将一系列映射到一个

于 2012-11-15T15:06:57.583 回答
2

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

不幸的是,实现该接口是不切实际的,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;
    }
}

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

于 2012-11-15T15:28:07.893 回答
2

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

于 2014-07-24T18:32:38.857 回答
0

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

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

还有一个建议

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

编辑:

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

于 2012-11-15T14:44:09.343 回答
0

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

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

使用 SubnetUtils.SubnetInfo 的 SubnetUtils

于 2021-04-15T15:42:44.557 回答
-1

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

map.put("A", ArrayList<Integer>);
于 2012-11-15T14:43:24.807 回答