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:
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.
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.
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.
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
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
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"