30

I am trying to find a data structure that takes in a particular value from a range of values and map it to a key.

For example, I have the following conditions:

  1. From 1 to 2.9, I want to map it to A.
  2. From 4 to 6, I want to map it to B.
  3. From 6.5 to 10, I want to map it to C.

I have a value of 5 and I would like to map it to a key. So based on the above conditions, I should map it to B.

Is there any data structure in Java that anyone can recommend to me to solve the problem?

Currently I am using a hashtable that can only map a value to a key. I tried to map the range of values to a particular value that exists in the hashtable. However, I got stuck in the mapping of the range of values to a particular value. So now I am trying to do another way of mapping the ranges of values to a key. Does anyone have any idea how I can solve this problem?

EDIT:

Thanks to Martin Ellis, I decided to use TreeMap to solve the problem.

Vadzim
  • 24,954
  • 11
  • 143
  • 151
Sakura
  • 869
  • 4
  • 13
  • 32

8 Answers8

41

Are your ranges non-overlapping? If so you could use a 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);

The lookup logic is a bit complicated by the fact that you probably want an inclusive lookup (i.e. 2.9 maps to 'A', and not undefined):

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

Example:

mappedValue(m, 5) == 'B'

More results include:

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
Martin Ellis
  • 9,603
  • 42
  • 53
  • 1
    Thanks so much for your solution! It seems to fit my requirements perfectly. Unfortunately, I cannot seems to be able to build the treemap properly. I cannot seems to be able to add a new range. For example, 'm.put(val_min, "A");m.put(val_max, null);/* make some changes to val_min and val_max*/m.put(val_min, "B");m.put(val_max, null);', if I pass a value that has a key of B but always get A. Do you have any idea why? – Sakura Nov 15 '12 at 16:04
  • 1
    If you're using "A" and not 'A', then you'd need to replace `Character` with `String`. Is there something else that's not working for you? – Martin Ellis Nov 15 '12 at 16:07
  • I have added a snapshot of the code that is not working in my original post. – Sakura Nov 15 '12 at 16:09
  • Can you add the result of System.out.println(m) so I can see what the map looks like? For one thing, need to check the ranges aren't overlapping. – Martin Ellis Nov 15 '12 at 16:17
  • I am coding in Android. Do you know how I can print out the map in Android debugger? – Sakura Nov 15 '12 at 16:26
  • I'm not familiar with Android development. But if you can print m.toString() somehow, that'd be fine. Alternatively, move the relevant code to a class with no android dependencies, and run it on your usual JVM. – Martin Ellis Nov 15 '12 at 16:29
  • I managed to print out m.toString() and got this: {428.92=null, 429.52=Bb4, 430.12=A4} – Sakura Nov 15 '12 at 16:32
  • 429.52 is the min value of Bb4 and 430.12 is the min value of A4. – Sakura Nov 15 '12 at 16:34
  • Ahh... I just realised my mistake. For Bb4, the formula should be 'freq_min=466.2-((tolerancePercentage/100)*(466.2-440)); freq_max=466.2-((tolerancePercentage/100)*(493.9-466.2));' – Sakura Nov 15 '12 at 16:38
9

Guava RangeMap provides specialized solution out of the box:

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"
Vadzim
  • 24,954
  • 11
  • 143
  • 151
  • 2
    Not sure why this answer got so few upvotes. Looks very useful to me. Just to note: rangeMap.get(3); // returns "foo" rangeMap.get(5); // returns "bar" rangeMap.get(6); // returns "foo" Also note the methods Range.closedOpen() and Range.openClosed() to return semi-open/closed ranges. – martin_wun Jun 01 '21 at 15:52
2

A HashMap will not work for mapping ranges to values unless you find a way to generate a hashcode for ranges and single values in there that matches. But below approach could be what you are looking for

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

You could do

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

It's not very efficient since it's just iterating over a list and it will in that state not complain if you put conflicting ranges in there. Will just return the first it finds.

P.S.: mapping like this would be mapping a range of keys to a value

zapl
  • 63,179
  • 10
  • 123
  • 154
2

This seems like a natural situation to use a tree structure.

Unfortunately it won't be practical to implement the java.util.Map interface because it specifies a method to return all of the keys, and in your situation you theoretically have an impractically large number of keys.

Each node of your tree should have a minimum key, a maximum key, and a value associated with that range. You can then have links to the nodes representing the next higher and next lower range (if they exist). Something like:

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

If you need faster lookups than the tree can provide, you may want to look into something like a skip list or developing your own hash function.

Samuel Edwin Ward
  • 6,526
  • 3
  • 34
  • 62
2

This type of data structure is called an Interval Tree. (The Wikipedia page only presents the case where intervals may overlap, but one can imagine a case where you want to remove mappings for any overlapped intervals when you add a new interval. Do a Google search for implementations and see if any fit your needs.

Andy
  • 7,885
  • 5
  • 55
  • 61
0

just have an List as a value in your Map.

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

and also one Suggustion:

do not use Hashtable unless you want synchronized access.

EDIT:

Hashtable methods are synchronized, i.e., no two threads can access those methods at a single point of time. HashMap menthods are not Synchronized. If you use Hashtable there will be a performance hit. use HashMap for better performance.

PermGenError
  • 45,977
  • 8
  • 87
  • 106
  • Hi. The range of values are double and can have many decimal places. In that case, should I add all the possible values (in double) into the list of doubles that is the value of the hashtable/map? – Sakura Nov 15 '12 at 14:47
  • Thanks! What do you mean by synchronized access? – Sakura Nov 15 '12 at 14:52
  • I would say to not use `Hashtable` at all. If you need to handle concurrency, you would better use a `ConcurrentHashMap` as shown [here](http://stackoverflow.com/q/510632/1065197), if you're not worried about concurrency (i.e. a simple Java program with no threads) you can use a `HashMap`. Also, it would be better to use `List` instead of `ArrayList` for the definition. See [What does it mean to “program to an interface”?](http://stackoverflow.com/q/383947/1065197). – Luiggi Mendoza Nov 15 '12 at 14:52
  • @LuiggiMendoza thanks for your suggestions, i changed it to List :) – PermGenError Nov 15 '12 at 14:59
  • Why is hashmap preferred over hashtable? – Sakura Nov 15 '12 at 15:05
  • @Sakura HashMap is faster than Hashtable, check my EDIT:) – PermGenError Nov 15 '12 at 15:07
  • @Sakura read the links provided in my comment. Also, check these: [Performance ConcurrentHashmap vs HashMap](http://stackoverflow.com/q/1378310/1065197), [Java 7: HashMap vs ConcurrentHashMap](http://java.dzone.com/articles/java-7-hashmap-vs) – Luiggi Mendoza Nov 15 '12 at 15:10
  • Okay. Thanks for the information. BTW, for adding the list, does that imply that I need new lists for every range? Won't that be quite inefficient? – Sakura Nov 15 '12 at 15:21
0

I think Apache commons includes some solution for this issue.

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

SubnetUtils with usage of SubnetUtils.SubnetInfo

maimArt
  • 389
  • 1
  • 11
-1

One of the way would be, use one of list implementation as value for key.

map.put("A", ArrayList<Integer>);
kosa
  • 65,990
  • 13
  • 130
  • 167
  • Hi. The range of values are double and can have many decimal places. In that case, should I add all the possible values (in double) into the list of doubles that is the value of the hashtable/map? – Sakura Nov 15 '12 at 14:46
  • But how do I retrieve the key from the hashtable/map? – Sakura Nov 15 '12 at 14:49
  • You can use keySet() if you use HashMap. Iterate on keySet, identify key is there or not, if there get values List, if not create new list and key. – kosa Nov 15 '12 at 14:50
  • I am using hashtable and previously used get() to get the key. Can I use the same method in this case although my value is a list of doubles? – Sakura Nov 15 '12 at 14:51
  • yes, you can. If no values for that key, then you can create new list and add it. By the way any specific reason for sticking to HashTable instead of HashMap?http://docs.oracle.com/javase/6/docs/api/java/util/Hashtable.html#get(java.lang.Object) – kosa Nov 15 '12 at 14:53