Is there a generic implementation for NavigableMap subMap/headMap/tailMap? - java
I've implemented NavigableMap a few times, but it always seems like a bit more work than it should be. While it's a very large interface, things like java.util.AbstractMap and Guava's ForwardingNavigableMap provide much of the boilerplate. However, neither seems to help with the core implementation of subMap/headMap/tailMap (i.e. aside from the overloading for specifying inclusivity flags). For instance, it seems like firstEntry of a sub/tail map could just call ceilingEntry(minKey) or higherEntry(minKey), depending on whether minKey is inclusive. Does Guava or something else provide an easy way to implement these generically? Alternatively, is there a reason I'm overlooking that such an implementation is not practical?
Guava doesn't currently expose AbstractNavigableMap, but I'm not sure even that would help you significantly. TBH, I don't see where it'd buy you much: firstEntry can always be defined as Iterables.getFirst(entrySet().iterator(), null), whether you're a complete map or a submap or whatever. Guava could conceivably provide a complete implementation of subMap and friends that iterated via higherEntry each time, but that's rarely what you want, compared to a more efficient handwritten iterator implementation.
I think it's possible to implement what I'm looking for, but there doesn't seem to be an existing framework for it. Particularly, I want the NavigableMap interface implemented in terms of get and remove for an arbitrary key and navigation in terms of first, last, next, previous. I'm guessing that part of the reason is that I'm representing an external service interaction rather than an internal data structure. The use case is less common and the performance expectations are different.
The following code is my draft implementation. It's only somewhat tested and is intended as a proof of concept. It uses Guava's Iterators, Ordering, and ForwardingNavigableMap, along with nullness annotations from the Checker Framework.
/**
* Abstract base implementation of {#link NavigableMap} that provides all but
* the following methods:
*
* <ul>
* <li>{#link Map#containsKey(Object)}</li>
* <li>{#link Map#get(Object)}</li>
* <li>{#link Map#remove(Object)}</li>
* <li>{#link SortedMap#comparator()}</li>
* <li>{#link NavigableMap#lowerEntry(Object)}</li>
* <li>{#link NavigableMap#floorEntry(Object)}</li>
* <li>{#link NavigableMap#ceilingEntry(Object)}</li>
* <li>{#link NavigableMap#higherEntry(Object)}</li>
* <li>{#link NavigableMap#firstEntry()}</li>
* <li>{#link NavigableMap#lastEntry()}</li>
* </ul>
*
* Note that {#link Collection#size()} is implemented in terms of iteration
* using {#code firstEntry} and {#code higherEntry}, and therefore executes in
* time proportional to the map size. Subclasses may wish to provide a more
* efficient implementation.
*
* #param <K> the type of keys maintained by this map
* #param <V> the type of mapped values
*/
public abstract class AbstractNavigableMap<K, V> extends AbstractMap<K, V> implements
NavigableMap<K, V> {
#Override
public abstract boolean containsKey(Object key);
#Override
public abstract V get(Object key);
#Override
public abstract V remove(Object key);
#Override
public K firstKey() {
return firstEntry().getKey();
}
#Override
public K lastKey() {
return lastEntry().getKey();
}
#Override
public K lowerKey(K key) {
return lowerEntry(key).getKey();
}
#Override
public K floorKey(K key) {
return floorEntry(key).getKey();
}
#Override
public K ceilingKey(K key) {
return ceilingEntry(key).getKey();
}
#Override
public K higherKey(K key) {
return higherEntry(key).getKey();
}
#Override
public Map.Entry<K, V> pollFirstEntry() {
final Map.Entry<K, V> e = firstEntry();
if (e != null) {
remove(e.getKey());
}
return e;
}
#Override
public Map.Entry<K, V> pollLastEntry() {
final Map.Entry<K, V> e = lastEntry();
if (e != null) {
remove(e.getKey());
}
return e;
}
#Override
public NavigableMap<K, V> descendingMap() {
return new ForwardingNavigableMap<K, V>() {
#Override
protected NavigableMap<K, V> delegate() {
return AbstractNavigableMap.this;
}
#Override
public NavigableMap<K, V> descendingMap() {
return new StandardDescendingMap();
}
}.descendingMap();
}
#Override
public NavigableSet<K> navigableKeySet() {
return new ForwardingNavigableMap<K, V>() {
#Override
protected NavigableMap<K, V> delegate() {
return AbstractNavigableMap.this;
}
#Override
public NavigableSet<K> navigableKeySet() {
return new StandardNavigableKeySet();
}
}.navigableKeySet();
}
#Override
public NavigableSet<K> descendingKeySet() {
return descendingMap().navigableKeySet();
}
#Override
public SortedMap<K, V> subMap(K fromKey, K toKey) {
return subMap(fromKey, true, toKey, false);
}
#Override
public NavigableMap<K, V> subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) {
return NavigableMapSubMap.subMapOf(this, fromKey, fromInclusive, toKey, toInclusive);
}
#Override
public SortedMap<K, V> headMap(K toKey) {
return headMap(toKey, false);
}
#Override
public NavigableMap<K, V> headMap(K toKey, boolean inclusive) {
return NavigableMapSubMap.headMapOf(this, toKey, inclusive);
}
#Override
public SortedMap<K, V> tailMap(K fromKey) {
return tailMap(fromKey, true);
}
#Override
public NavigableMap<K, V> tailMap(K fromKey, boolean inclusive) {
return NavigableMapSubMap.tailMapOf(this, fromKey, inclusive);
}
#Override
public Set<Map.Entry<K, V>> entrySet() {
return new NavigableMapEntrySet<K, V>(this);
}
}
/**
* Provides a {#link NavigableMap} view of a key range of an underlying
* {#link NavigableMap}.
*
* #param <K> the type of keys maintained by this map
* #param <V> the type of mapped values
*/
class NavigableMapSubMap<K, V> extends AbstractNavigableMap<K, V> {
private static final int UNSPECIFIED = 0;
private static final int INCLUSIVE = 1;
private static final int EXCLUSIVE = 2;
private final NavigableMap<K, V> base;
private final Comparator<? super K> comparator;
#NullableDecl
private final K fromKey;
private final int fromBound;
#NullableDecl
private final K toKey;
private final int toBound;
private NavigableMapSubMap(NavigableMap<K, V> base, K fromKey, int fromBound, K toKey,
int toBound) {
this.base = base;
comparator = effectiveComparator(base.comparator());
if (fromBound != UNSPECIFIED && toBound != UNSPECIFIED &&
comparator.compare(fromKey, toKey) > 0) {
throw new IllegalArgumentException("fromKey > toKey");
}
this.fromKey = fromKey;
this.fromBound = fromBound;
this.toKey = toKey;
this.toBound = toBound;
}
#SuppressWarnings({ "unchecked", "rawtypes" })
private static <K> Comparator<? super K> effectiveComparator(
#NullableDecl Comparator<? super K> comparator) {
return comparator != null ? comparator : (Comparator) Ordering.natural();
}
static <K, V> NavigableMap<K, V> subMapOf(NavigableMap<K, V> base, K fromKey,
boolean fromInclusive, K toKey, boolean toInclusive) {
return new NavigableMapSubMap<>(base, fromKey, fromInclusive ? INCLUSIVE : EXCLUSIVE,
toKey, toInclusive ? INCLUSIVE : EXCLUSIVE);
}
static <K, V> NavigableMap<K, V> headMapOf(NavigableMap<K, V> base, K toKey, boolean inclusive) {
return new NavigableMapSubMap<>(base, null, UNSPECIFIED, toKey, inclusive ? INCLUSIVE
: EXCLUSIVE);
}
static <K, V> NavigableMap<K, V> tailMapOf(NavigableMap<K, V> base, K fromKey, boolean inclusive) {
return new NavigableMapSubMap<>(base, fromKey, inclusive ? INCLUSIVE : EXCLUSIVE, null,
UNSPECIFIED);
}
#Override
public Comparator<? super K> comparator() {
return base.comparator();
}
#NullableDecl
private boolean aboveLowerBound(#NullableDecl K key, boolean inclusive) {
if (fromBound != UNSPECIFIED) {
final int cmp = comparator.compare(fromKey, key);
if (fromBound == INCLUSIVE || !inclusive ? cmp > 0 : cmp >= 0) {
return false;
}
}
return true;
}
#NullableDecl
private boolean belowUpperBound(#NullableDecl K key, boolean inclusive) {
if (toBound != UNSPECIFIED) {
final int cmp = comparator.compare(key, toKey);
if (toBound == INCLUSIVE || !inclusive ? cmp > 0 : cmp >= 0) {
return false;
}
}
return true;
}
#NullableDecl
private boolean inBounds(#NullableDecl K key, boolean inclusive) {
return aboveLowerBound(key, inclusive) && belowUpperBound(key, inclusive);
}
#NullableDecl
private Map.Entry<K, V> lowerBound(#NullableDecl Map.Entry<K, V> e) {
if (e != null && fromBound != UNSPECIFIED) {
final int cmp = comparator.compare(fromKey, e.getKey());
if (fromBound == INCLUSIVE ? cmp > 0 : cmp >= 0) {
return null;
}
}
return e;
}
#NullableDecl
private Map.Entry<K, V> upperBound(#NullableDecl Map.Entry<K, V> e) {
if (e != null && toBound != UNSPECIFIED) {
final int cmp = comparator.compare(e.getKey(), toKey);
if (toBound == INCLUSIVE ? cmp > 0 : cmp >= 0) {
return null;
}
}
return e;
}
#NullableDecl
private Map.Entry<K, V> bound(#NullableDecl Map.Entry<K, V> e) {
return lowerBound(upperBound(e));
}
#Override
#SuppressWarnings("unchecked")
public boolean containsKey(Object key) {
return inBounds((K) key, true) && base.containsKey(key);
}
#Override
#SuppressWarnings("unchecked")
public V get(Object key) {
return inBounds((K) key, true) ? base.get(key) : null;
}
#Override
#SuppressWarnings("unchecked")
public V remove(Object key) {
return inBounds((K) key, true) ? base.remove(key) : null;
}
#Override
public Map.Entry<K, V> lowerEntry(K key) {
return bound(base.lowerEntry(key));
}
#Override
public Map.Entry<K, V> floorEntry(K key) {
return bound(base.floorEntry(key));
}
#Override
public Map.Entry<K, V> ceilingEntry(K key) {
return bound(base.floorEntry(key));
}
#Override
public Map.Entry<K, V> higherEntry(K key) {
return bound(base.higherEntry(key));
}
#Override
public Map.Entry<K, V> firstEntry() {
switch (fromBound) {
case UNSPECIFIED:
return base.firstEntry();
case INCLUSIVE:
return upperBound(base.floorEntry(fromKey));
default:
return upperBound(base.higherEntry(fromKey));
}
}
#Override
public Map.Entry<K, V> lastEntry() {
switch (toBound) {
case UNSPECIFIED:
return base.firstEntry();
case INCLUSIVE:
return lowerBound(base.ceilingEntry(toKey));
default:
return lowerBound(base.lowerEntry(toKey));
}
}
private NavigableMap<K, V> subMap(K fromKey, int fromBound, K toKey, int toBound) {
if (fromBound == UNSPECIFIED) {
fromKey = this.fromKey;
fromBound = this.fromBound;
} else if (!inBounds(fromKey, fromBound == INCLUSIVE)) {
throw new IllegalArgumentException("fromKey out of range");
}
if (toBound == UNSPECIFIED) {
toKey = this.toKey;
toBound = this.toBound;
} else if (!inBounds(toKey, toBound == INCLUSIVE)) {
throw new IllegalArgumentException("toKey out of range");
}
return new NavigableMapSubMap<>(base, fromKey, fromBound, toKey, toBound);
}
#Override
public NavigableMap<K, V> subMap(K fromKey, boolean fromInclusive, K toKey, boolean toInclusive) {
return subMap(fromKey, fromInclusive ? INCLUSIVE : EXCLUSIVE, toKey,
toInclusive ? INCLUSIVE : EXCLUSIVE);
}
#Override
public NavigableMap<K, V> headMap(K toKey, boolean inclusive) {
return subMap(null, UNSPECIFIED, toKey, inclusive ? INCLUSIVE : EXCLUSIVE);
}
#Override
public NavigableMap<K, V> tailMap(K fromKey, boolean inclusive) {
return subMap(fromKey, inclusive ? INCLUSIVE : EXCLUSIVE, null, UNSPECIFIED);
}
#Override
public Set<Map.Entry<K, V>> entrySet() {
return new NavigableMapEntrySet<K, V>(this) {
#Override
protected Map.Entry<K, V> higherEntry(Map.Entry<K, V> from) {
// omit unnecessary lower bound check of outer higherEntry
return upperBound(base.higherEntry(from.getKey()));
}
};
}
}
/**
* Provides a {#link Map#entrySet()} implementation based on a
* {#link NavigableMap} using the following methods:
*
* <ul>
* <li>{#link NavigableMap#firstEntry()}</li>
* <li>{#link NavigableMap#higherEntry(Object)}</li>
* <li>{#link Map#get(Object)}</li>
* <li>{#link Map#remove(Object)}</li>
* </ul>
*
* Note that {#link Collection#size()} is implemented in terms of iteration
* using {#code firstEntry} and {#code higherEntry}, and therefore executes in
* time proportional to the map size. Subclasses may wish to provide a more
* efficient implementation.
*
* #param <K> the type of keys maintained by this map
* #param <V> the type of mapped values
*/
public class NavigableMapEntrySet<K, V> extends AbstractSet<Map.Entry<K, V>> {
private final NavigableMap<K, V> map;
/**
* Constructs a {#link NavigableMapEntrySet} for the given map.
*
* #param map the map whose entries are being represented
*/
public NavigableMapEntrySet(NavigableMap<K, V> map) {
this.map = map;
}
/**
* Used by {#link #iterator()} to return an entry associated with the least
* key in this map, or {#code null} if the map is empty. The default
* implementation simply calls {#link NavigableMap#firstEntry()}; subclasses
* may wish to provide a more efficient implementation.
*
* #return an entry with the least key, or null if the map is empty
*/
protected Map.Entry<K, V> firstEntry() {
return map.firstEntry();
}
/**
* Used by {#link #iterator()} to return an entry associated with the least
* key strictly greater than the key of the given entry, or {#code null} if
* there is no such key. The default implementation simply calls
* {#link NavigableMap#higherEntry(Object)} with the key of the given entry;
* subclasses may wish to provide a more efficient implementation.
*
* #param from the reference entry, which must have been returned from this
* entry set
* #return an entry with the least key greater than the given entry, or null
* if there is no such key
*/
protected Map.Entry<K, V> higherEntry(Map.Entry<K, V> from) {
return map.higherEntry(from.getKey());
}
#Override
public Iterator<Map.Entry<K, V>> iterator() {
return new Iterator<Map.Entry<K, V>>() {
#NullableDecl
Map.Entry<K, V> prevEntry = null;
#NullableDecl
Map.Entry<K, V> nextEntry = firstEntry();
#Override
public boolean hasNext() {
return nextEntry != null;
}
#Override
public Map.Entry<K, V> next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
prevEntry = nextEntry;
nextEntry = higherEntry(nextEntry);
return prevEntry;
}
#Override
public void remove() {
if (prevEntry == null) {
throw new IllegalStateException();
}
map.remove(prevEntry.getKey());
prevEntry = null;
}
};
}
#Override
public int size() {
return Iterators.size(iterator());
}
#Override
public boolean contains(Object o) {
if (o instanceof Map.Entry) {
final Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
return Objects.equals(map.get(e.getKey()), e.getValue());
}
return false;
}
#Override
public boolean remove(Object o) {
if (o instanceof Map.Entry) {
final Map.Entry<?, ?> e = (Map.Entry<?, ?>) o;
final Object key = e.getKey();
if (Objects.equals(map.get(key), e.getValue())) {
map.remove(key);
return true;
}
}
return false;
}
}
ForwardingNavigableMap
You must have a NavigableMap in advance if you use ForwardingNavigableMap which uses decorator pattern. So this is not what you need.
AbstractMap
AbstractMap provides skeletal implementation of the Map. This class helps you implement a Map which is interface of NavigableMap, but without 'Navigable logic'. Anyway, NavigableMap is-a Map, so AbstractMap helps to setup a NavigableMap. Compared to ForwardingNavigableMap, I think AbstractMap can be your better choice.
I'd suggest to consider something simpler as NavigableMap is a pretty rich interface and you may end up spending a lot of time on features you may not need. Even a plain Map has tons of methods and AbstractMap hardly helps as it provides only very rudimentary implementations (either inefficient or throwing UnsupportedOperationException).
If you could use List (with sublist for "navigation"), you'd save yourself a lot of work. Anyway, look at guava-testlib for a good testsuite.
If you really need a NavigableMap, I'm afraid, you're out of luck. I guess, there's no suitable base class as it's impossible to write something useful often enough.
For instance, it seems like firstEntry of a sub/tail map could just call ceilingEntry(minKey) or higherEntry(minKey), depending on whether minKey is inclusive.
Yes, but this is a rather simple case. And there may be a faster way for firstEntry for a given implementation.
Related
Hashmap with a .class object as a key
I've created a hashmap with .class objects for keys. Hashmap<Class<? extends MyObject>, Object> mapping = new Hashmap<Class<? extends MyObject>, Object>(); This is all well and fine, but I'm getting strange behaviour that I can only attribute to strangeness with the hash function. Randomly during runtime, iterating through the hashmap will not hit every value; it will miss one or two. I think this may be due to the .class object not being final, and therefore it changes causing it to map to a different hash value. With a different hash value, the hashmap wouldn't be able to correctly correlate the key with the value, thus making it appear to have lost the value. Am I correct that this is what is going on? How can I work around this? Is there a better way to accomplish this form of data structure? Edit: I really thought I was onto something with the hash function thing, but I'll post my real code to try and figure this out. It may be a problem with my implementation of a multimap. I've been using it for quite some time and haven't noticed any issues until recently. /** * My own implementation of a map that maps to a List. If the key is not present, then * the map adds a List with a single entry. Every subsequent addition to the key * is appended to the List. * #author * * #param <T> Key * #param <K> Value */ public class MultiMap<T, K> implements Map<T, List<K>>, Serializable, Iterable<K> { /** * */ private static final long serialVersionUID = 5789101682525659411L; protected HashMap<T, List<K>> set = new HashMap<T, List<K>>(); #Override public void clear() { set = new HashMap<T, List<K>>(); } #Override public boolean containsKey(Object arg0) { return set.containsKey(arg0); } #Override public boolean containsValue(Object arg0) { boolean output = false; for(Iterator<List<K>> iter = set.values().iterator();iter.hasNext();) { List<K> searchColl = iter.next(); for(Iterator<K> iter2 = searchColl.iterator(); iter2.hasNext();) { K value = iter2.next(); if(value == arg0) { output = true; break; } } } return output; } #Override public Set<Entry<T, List<K>>> entrySet() { Set<Entry<T, List<K>>> output = new HashSet<Entry<T,List<K>>>(); for(Iterator<T> iter1 = set.keySet().iterator(); iter1.hasNext();) { T key = iter1.next(); for(Iterator<K> iter2 = set.get(key).iterator(); iter2.hasNext();) { K value = iter2.next(); List<K> input = new ArrayList<K>(); input.add(value); output.add(new AbstractMap.SimpleEntry<T,List<K>>(key, input)); } } return output; } #Override public boolean isEmpty() { return set.isEmpty(); } #Override public Set<T> keySet() { return set.keySet(); } #Override public int size() { return set.size(); } #Override public Collection<List<K>> values() { Collection<List<K>> values = new ArrayList<List<K>>(); for(Iterator<T> iter1 = set.keySet().iterator(); iter1.hasNext();) { T key = iter1.next(); values.add(set.get(key)); } return values; } #Override public List<K> get(Object key) { return set.get(key); } #Override public List<K> put(T key, List<K> value) { return set.put(key, value); } public void putValue(T key, K value) { if(set.containsKey(key)) { set.get(key).add(value); } else { List<K> setval = new ArrayList<K>(); setval.add(value); set.put(key, setval); } } #Override public List<K> remove(Object key) { return set.remove(key); } public K removeValue(Object value) { K valueRemoved = null; for(T key:this.keySet()) { for(K val:this.get(key)) { if(val.equals(value)) { List<K> temp = this.get(key); temp.remove(value); valueRemoved = val; this.put(key, temp); } } } return valueRemoved; } #Override public void putAll(Map<? extends T, ? extends List<K>> m) { for(Iterator<? extends T> iter = m.keySet().iterator(); iter.hasNext();) { T key = iter.next(); set.put(key, m.get(key)); } } #Override public Iterator<K> iterator() { return new MultiMapIterator<K>(this); } } Perhaps there is an issue with my iterator? I'll post that code as well. import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; public class MultiMapIterator<T> implements Iterator<T> { private MultiMap <?, T> map; private Iterator<List<T>> HashIter; private Iterator<T> govIter; private T value; public MultiMapIterator(MultiMap<?, T> map) { this.map = map; HashIter = map.values().iterator(); if(HashIter.hasNext()) { govIter = HashIter.next().iterator(); } if(govIter.hasNext()) { value = govIter.next(); } } #Override public boolean hasNext() { if (govIter.hasNext()) { return true; } else if(HashIter.hasNext()) { govIter = HashIter.next().iterator(); return this.hasNext(); } else { return false; } } #Override public T next() { if(!this.hasNext()) { throw new NoSuchElementException(); } else { value = govIter.next(); return value; } } #Override public void remove() { map.remove(value); } } Sorry for the long tracts of code. Thank you for spending time helping me with this.
You pull the a value out of govIter in the constructor, but never return it. Your iterator remove method is completely wrong. You are iterating values, but calling the map.remove which removes by key. you simply want to call govIter.remove() (unless you need to avoid empty lists, in which case it's more complicated). Your hasNext() method could also have problems depending on whether or not you allow empty Lists values in your multimap.
Iterate over nested maps
I put together these methods to help iterate over nested maps (for another SO question). As you can clearly see, the first two methods are actually almost exactly the same apart from their generics and that one calls iV and the other calls iiV. Is there any way I could fold them into one method, or at least move the clear duplication of the mechanism into one place? If done right it should be possible to iterate over nested maps of any depth. // Iterating across Maps of Maps of Maps. static <K1, K2, K3, V> Iterator<Iterator<Iterator<V>>> iiiV(Map<K1, Map<K2, Map<K3, V>>> mmm) { final Iterator<Map<K2, Map<K3, V>>> mmi = iV(mmm); return new Iterator<Iterator<Iterator<V>>>() { #Override public boolean hasNext() { return mmi.hasNext(); } #Override public Iterator<Iterator<V>> next() { return iiV(mmi.next()); } #Override public void remove() { mmi.remove(); } }; } // Iterating across Maps of Maps. static <K1, K2, V> Iterator<Iterator<V>> iiV(Map<K1, Map<K2, V>> mm) { final Iterator<Map<K2, V>> mi = iV(mm); return new Iterator<Iterator<V>>() { #Override public boolean hasNext() { return mi.hasNext(); } #Override public Iterator<V> next() { return iV(mi.next()); } #Override public void remove() { mi.remove(); } }; } // Iterating across Map values. static <K, V> Iterator<V> iV(final Map<K, V> map) { return iV(map.entrySet().iterator()); } // Iterating across Map.Entries. static <K, V> Iterator<V> iV(final Iterator<Map.Entry<K, V>> mei) { return new Iterator<V>() { #Override public boolean hasNext() { return mei.hasNext(); } #Override public V next() { return mei.next().getValue(); } #Override public void remove() { mei.remove(); } }; }
Why not: static <K, V> Iterator<V> iV(final Iterator<V> mei) { return new Iterator<V>() { #Override public boolean hasNext() { return mei.hasNext(); } #Override public V next() { if (Iterator.class.isAssignableFrom(V.class)) { Iterator it = iV((Iterator)V); return it.hasNext() ? it.next() : ((Iterator)mei.next()).next(); } else return mei.next().getValue(); } #Override public void remove() { mei.remove(); } }; } (Not tested)
A sorted ComputingMap?
How can I construct a SortedMap on top of Guava's computing map (or vice versa)? I want the sorted map keys as well as computing values on-the-fly.
The simplest is probably to use a ConcurrentSkipListMap and the memoizer idiom (see JCiP), rather than relying on the pre-built unsorted types from MapMaker. An example that you could use as a basis is a decorator implementation.
May be you can do something like this.It's not a complete implementation.Just a sample to convey the idea. public class SortedComputingMap<K, V> extends TreeMap<K, V> { private Function<K, V> function; private int maxSize; public SortedComputingMap(int maxSize, Function<K, V> function) { this.function = function; this.maxSize = maxSize; } #Override public V put(K key, V value) { throw new UnsupportedOperationException(); } #Override public void putAll(Map<? extends K, ? extends V> map) { throw new UnsupportedOperationException(); } #Override public V get(Object key) { V tmp = null; K Key = (K) key; if ((tmp = super.get(key)) == null) { super.put(Key, function.apply(Key)); } if (size() > maxSize) pollFirstEntry(); return tmp; } public static void main(String[] args) { Map<Integer, Long> sortedMap = new SortedComputingMap<Integer, Long>(3, new Function<Integer, Long>() { #Override public Long apply(Integer n) { Long fact = 1l; while (n != 0) fact *= n--; return fact; } }); sortedMap.get(12); sortedMap.get(1); sortedMap.get(2); sortedMap.get(5); System.out.println(sortedMap.entrySet()); } }
If you need the thread safety, this could be tricky, but if you don't I'd recommend something close to Emil's suggestion, but using a ForwardingSortedMap rather than extending TreeMap directly.
Guava: Set<K> + Function<K,V> = Map<K,V>?
Is there an idiomatic way to take a Set<K> and a Function<K,V>, and get a Map<K,V> live view? (i.e. the Map is backed by the Set and Function combo, and if e.g. an element is added to the Set, then the corresponding entry also exists in the Map). (see e.g. Collections2.filter for more discussion on live views) What if a live view is not needed? Is there something better than this: public static <K,V> Map<K,V> newMapFrom(Set<K> keys, Function<? super K,V> f) { Map<K,V> map = Maps.newHashMap(); for (K k : keys) { map.put(k, f.apply(k)); } return map; }
Creating a Map from a Set and a Function Here are two classes that should each do the job. The first just shows a map view of the set, while the second can write values back to the set through a special interface. Call Syntax: Map<K,V> immutable = new SetBackedMap<K,V>(Set<K> keys, Function<K,V> func); Map<K,V> mutable = new MutableSetBackedMap<K,V>(Set<K> keys, Function<K,V> func); Where to put this code? Side note: If guava were my library, I'd make them accessible through the Maps class: Map<K,V> immutable = Maps.immutableComputingMap(Set<K> keys, Function<K,V> func); Map<K,V> mutable = Maps.mutableComputingMap(Set<K> keys, Function<K,V> func); Immutable version: I have implemented this as a one-way view: Changes to the set are reflected in the map, but not vice-versa (and you can't change the map anyway, the put(key, value) method isn't implemented). The entrySet() iterator uses the set iterator internally, so it will also inherit the internal iterator's handling of ConcurrentModificationException. Both put(k,v) and entrySet().iterator().remove() will throw UnsupportedOperationException. Values are cached in a WeakHashMap, with no special concurrency handling, i.e. there is no synchronization at any level. This will do for most cases, but if your function is expensive, you might want to add some locking. Code: public class SetBackedMap<K, V> extends AbstractMap<K, V>{ private class MapEntry implements Entry<K, V>{ private final K key; public MapEntry(final K key){ this.key = key; } #Override public K getKey(){ return this.key; } #Override public V getValue(){ V value = SetBackedMap.this.cache.get(this.key); if(value == null){ value = SetBackedMap.this.funk.apply(this.key); SetBackedMap.this.cache.put(this.key, value); } return value; } #Override public V setValue(final V value){ throw new UnsupportedOperationException(); } } private class EntrySet extends AbstractSet<Entry<K, V>>{ public class EntryIterator implements Iterator<Entry<K, V>>{ private final Iterator<K> inner; public EntryIterator(){ this.inner = EntrySet.this.keys.iterator(); } #Override public boolean hasNext(){ return this.inner.hasNext(); } #Override public Map.Entry<K, V> next(){ final K key = this.inner.next(); return new MapEntry(key); } #Override public void remove(){ throw new UnsupportedOperationException(); } } private final Set<K> keys; public EntrySet(final Set<K> keys){ this.keys = keys; } #Override public Iterator<Map.Entry<K, V>> iterator(){ return new EntryIterator(); } #Override public int size(){ return this.keys.size(); } } private final WeakHashMap<K, V> cache; private final Set<Entry<K, V>> entries; private final Function<? super K, ? extends V> funk; public SetBackedMap( final Set<K> keys, Function<? super K, ? extends V> funk){ this.funk = funk; this.cache = new WeakHashMap<K, V>(); this.entries = new EntrySet(keys); } #Override public Set<Map.Entry<K, V>> entrySet(){ return this.entries; } } Test: final Map<Integer, String> map = new SetBackedMap<Integer, String>( new TreeSet<Integer>(Arrays.asList( 1, 2, 4, 8, 16, 32, 64, 128, 256)), new Function<Integer, String>(){ #Override public String apply(final Integer from){ return Integer.toBinaryString(from.intValue()); } }); for(final Map.Entry<Integer, String> entry : map.entrySet()){ System.out.println( "Key: " + entry.getKey() + ", value: " + entry.getValue()); } Output: Key: 1, value: 1 Key: 2, value: 10 Key: 4, value: 100 Key: 8, value: 1000 Key: 16, value: 10000 Key: 32, value: 100000 Key: 64, value: 1000000 Key: 128, value: 10000000 Key: 256, value: 100000000 Mutable Version: While I think it's a good idea to make this one-way, here's a version for Emil that provides a two-way view (it's a variation of Emil's variation of my solution :-)). It requires an extended map interface that I'll call ComputingMap to make clear that this is a map where it doesn't make sense to call put(key, value). Map interface: public interface ComputingMap<K, V> extends Map<K, V>{ boolean removeKey(final K key); boolean addKey(final K key); } Map implementation: public class MutableSetBackedMap<K, V> extends AbstractMap<K, V> implements ComputingMap<K, V>{ public class MapEntry implements Entry<K, V>{ private final K key; public MapEntry(final K key){ this.key = key; } #Override public K getKey(){ return this.key; } #Override public V getValue(){ V value = MutableSetBackedMap.this.cache.get(this.key); if(value == null){ value = MutableSetBackedMap.this.funk.apply(this.key); MutableSetBackedMap.this.cache.put(this.key, value); } return value; } #Override public V setValue(final V value){ throw new UnsupportedOperationException(); } } public class EntrySet extends AbstractSet<Entry<K, V>>{ public class EntryIterator implements Iterator<Entry<K, V>>{ private final Iterator<K> inner; public EntryIterator(){ this.inner = MutableSetBackedMap.this.keys.iterator(); } #Override public boolean hasNext(){ return this.inner.hasNext(); } #Override public Map.Entry<K, V> next(){ final K key = this.inner.next(); return new MapEntry(key); } #Override public void remove(){ throw new UnsupportedOperationException(); } } public EntrySet(){ } #Override public Iterator<Map.Entry<K, V>> iterator(){ return new EntryIterator(); } #Override public int size(){ return MutableSetBackedMap.this.keys.size(); } } private final WeakHashMap<K, V> cache; private final Set<Entry<K, V>> entries; private final Function<? super K, ? extends V> funk; private final Set<K> keys; public MutableSetBackedMap(final Set<K> keys, final Function<? super K, ? extends V> funk){ this.keys = keys; this.funk = funk; this.cache = new WeakHashMap<K, V>(); this.entries = new EntrySet(); } #Override public boolean addKey(final K key){ return this.keys.add(key); } #Override public boolean removeKey(final K key){ return this.keys.remove(key); } #Override public Set<Map.Entry<K, V>> entrySet(){ return this.entries; } } Test: public static void main(final String[] args){ final ComputingMap<Integer, String> map = new MutableSetBackedMap<Integer, String>( new TreeSet<Integer>(Arrays.asList( 1, 2, 4, 8, 16, 32, 64, 128, 256)), new Function<Integer, String>(){ #Override public String apply(final Integer from){ return Integer.toBinaryString(from.intValue()); } }); System.out.println(map); map.addKey(3); map.addKey(217); map.removeKey(8); System.out.println(map); } Output: {1=1, 2=10, 4=100, 8=1000, 16=10000, 32=100000, 64=1000000, 128=10000000, 256=100000000} {1=1, 2=10, 3=11, 4=100, 16=10000, 32=100000, 64=1000000, 128=10000000, 217=11011001, 256=100000000}
Caution. Sean Patrick Floyd's answer, although very useful, has a flaw. A simple one, but took me a while to debug so don't fall in the same trap: the MapEntry class requires equals and hashcode implementations. Here are mine (simple copy from the javadoc). #Override public boolean equals(Object obj) { if (!(obj instanceof Entry)) { return false; } Entry<?, ?> e2 = (Entry<?, ?>) obj; return (getKey() == null ? e2.getKey() == null : getKey().equals(e2.getKey())) && (getValue() == null ? e2.getValue() == null : getValue().equals(e2.getValue())); } #Override public int hashCode() { return (getKey() == null ? 0 : getKey().hashCode()) ^ (getValue() == null ? 0 : getValue().hashCode()); } This reply would be better as a commentary to the relevant answer, but AFAIU I don't have the right to post a comment (or did't find how to!).
Guava 14 now has Maps.asMap for a view of the Set and Maps.toMap for an immutable copy. You can see much of the discussion of the issues involved here: https://github.com/google/guava/issues/56
For the non live view the code exists in lambdaJ with Lambda.map(Set, Converter). Set<K> setKs = new Set<K>(); Converter<K, V> converterKv = new Converter<K,V>{ #Override public V convert(K from){ return null; //Not useful here but you can do whatever you want } } Map<K, V> mapKvs = Lambda.map(setKs, converterKv); I tried my own implementation : http://ideone.com/Kkpcn As said in the comments, I have to extends another class so I just implemented Map, that's why there is so much code. There is a totally useless (or not ?) feature that allows you to change the converter on the fly.
what about Maps.uniqueIndex()
I don't know if this is what you mean by live view.Any way here is my try. public class GuavaTst { public static void main(String[] args) { final Function<String, String> functionToLower = new Function<String, String>() { public String apply (String input) { return input.toLowerCase(); } }; final Set<String> set=new HashSet<String>(); set.add("Hello"); set.add("BYE"); set.add("gOOd"); Map<String, String> testMap = newLiveMap(set,functionToLower); System.out.println("Map :- "+testMap); System.out.println("Set :- "+set); set.add("WoRld"); System.out.println("Map :- "+testMap); System.out.println("Set :- "+set); testMap.put("OMG",""); System.out.println("Map :- "+testMap); System.out.println("Set :- "+set); } static <K,V> Map<K,V> newLiveMap(final Set<K> backEnd,final Function<K,V> fun) { return new HashMap<K,V>(){ #Override public void clear() { backEnd.clear(); } #Override public boolean containsKey(Object key) { return backEnd.contains(key); } #Override public boolean isEmpty() { return backEnd.isEmpty(); } #Override public V put(K key, V value) { backEnd.add(key); return null; } #Override public boolean containsValue(Object value) { for(K s:backEnd) if(fun.apply(s).equals(value)) return true; return false; } #Override public V remove(Object key) { backEnd.remove(key); return null; } #Override public int size() { return backEnd.size(); } #Override public V get(Object key) { return fun.apply((K)key); } #Override public String toString() { StringBuilder b=new StringBuilder(); Iterator<K> itr=backEnd.iterator(); b.append("{"); if(itr.hasNext()) { K key=itr.next(); b.append(key); b.append(":"); b.append(this.get(key)); while(itr.hasNext()) { key=itr.next(); b.append(", "); b.append(key); b.append(":"); b.append(this.get(key)); } } b.append("}"); return b.toString(); } }; } } The implementation is not complete and the overridden functions are not tested but I hope it convey's the idea. UPDATE: I made some small change's to seanizer's answer so that the changes made in map will reflect in the set also. public class SetBackedMap<K, V> extends AbstractMap<K, V> implements SetFunctionMap<K, V>{ public class MapEntry implements Entry<K, V>{ private final K key; public MapEntry(final K key){ this.key = key; } #Override public K getKey(){ return this.key; } #Override public V getValue(){ V value = SetBackedMap.this.cache.get(this.key); if(value == null){ value = SetBackedMap.this.funk.apply(this.key); SetBackedMap.this.cache.put(this.key, value); } return value; } #Override public V setValue(final V value){ throw new UnsupportedOperationException(); } } public class EntrySet extends AbstractSet<Entry<K, V>>{ public class EntryIterator implements Iterator<Entry<K, V>>{ private final Iterator<K> inner; public EntryIterator(){ this.inner = EntrySet.this.keys.iterator(); } #Override public boolean hasNext(){ return this.inner.hasNext(); } #Override public Map.Entry<K, V> next(){ final K key = this.inner.next(); return new MapEntry(key); } #Override public void remove(){ throw new UnsupportedOperationException(); } } private final Set<K> keys; public EntrySet(final Set<K> keys){ this.keys = keys; } #Override public boolean add(Entry<K, V> e) { return keys.add(e.getKey()); } #Override public Iterator<Map.Entry<K, V>> iterator(){ return new EntryIterator(); } #Override public int size(){ return this.keys.size(); } #Override public boolean remove(Object o) { return keys.remove(o); } } private final WeakHashMap<K, V> cache; private final Set<Entry<K, V>> entries; private final Function<K, V> funk; public SetBackedMap(final Set<K> keys, final Function<K, V> funk){ this.funk = funk; this.cache = new WeakHashMap<K, V>(); this.entries = new EntrySet(keys); } #Override public Set<Map.Entry<K, V>> entrySet(){ return this.entries; } public boolean putKey(K key){ return entries.add(new MapEntry(key)); } #Override public boolean removeKey(K key) { cache.remove(key); return entries.remove(key); } } Interface SetFunctionMap: public interface SetFunctionMap<K,V> extends Map<K, V>{ public boolean putKey(K key); public boolean removeKey(K key); } Test Code: public class SetBackedMapTst { public static void main(String[] args) { Set<Integer> set=new TreeSet<Integer>(Arrays.asList( 1, 2, 4, 8, 16)); final SetFunctionMap<Integer, String> map = new SetBackedMap<Integer, String>(set, new Function<Integer, String>(){ #Override public String apply(final Integer from){ return Integer.toBinaryString(from.intValue()); } }); set.add(222); System.out.println("Map: "+map); System.out.println("Set: "+set); map.putKey(112); System.out.println("Map: "+map); System.out.println("Set: "+set); map.removeKey(112); System.out.println("Map: "+map); System.out.println("Set: "+set); } } Output: Map: {1=1, 2=10, 4=100, 8=1000, 16=10000, 222=11011110}//change to set reflected in map Set: [1, 2, 4, 8, 16, 222] Map: {1=1, 2=10, 4=100, 8=1000, 16=10000, 112=1110000, 222=11011110} Set: [1, 2, 4, 8, 16, 112, 222]//change to map reflected in set Map: {1=1, 2=10, 4=100, 8=1000, 16=10000, 222=11011110} Set: [1, 2, 4, 8, 16, 222]//change to map reflected in set
Searching in a TreeMap (Java)
I need to do a search in a map of maps and return the keys this element belong. I think this implementation is very slow, can you help me to optimize it?. I need to use TreeSet and I can't use contains because they use compareTo, and equals/compareTo pair are implemented in an incompatible way and I can't change that. (sorry my bad english) Map<Key, Map<SubKey, Set<Element>>> m = new TreeSet(); public String getKeys(Element element) { for(Entry<Key, Map<SubKey, Set<Element>>> e : m.entrySet()) { mapSubKey = e.getValue(); for(Entry<SubKey, Set<Element>> e2 : mapSubKey.entrySet()) { setElements = e2.getValue(); for(Element elem : setElements) if(elem.equals(element)) return "Key: " + e.getKey() + " SubKey: " + e2.getKey(); } } }
The problem here is that the keys and values are backward. Maps allow one to efficiently find a value (which would be Key and SubKey) associated with a key (Element, in this example). Going backwards is slow. There are bi-directional map implementations, like Google Collections BiMap, that support faster access in both directions—but that would mean replacing TreeMap. Otherwise, maintain two maps, one for each direction.
if you can't use contains, and you're stuck using a Map of Maps, then your only real option is to iterate, as you are doing. alternatively, you could keep a reverse map of Element to Key/SubKey in a separate map, which would make reverse lookups faster. also, if you're not sure that a given Element can exist in only one place, you might want to store and retrieve a List<Element> instead of just an Element
Using TreeMap and TreeSet work properly when compareTo and equals are implemented in such a way that they are compatible with each other. Furthermore, when searching in a Map, only the search for the key will be efficient (for a TreeMap O(log n)). When searching for a value in a Map, the complexity will become linear. There is a way to optimize the search in the inner TreeSet though, when implementing your own Comparator for the Element type. This way you can implement your own compareTo method without changing the Element object itself.
Here is a bidirectional TreeMap (or Bijection over TreeMap). It associates two overloaded TreeMaps Which are tied together. Each one "inverse" constant field points to the other TreeMap. Any change on one TreeMap is automatically reflected on its inverse. As a result, each value is unique. public class BiTreeMap<K, V> extends TreeMap<K, V> { public final BiTreeMap<V, K> inverse; private BiTreeMap(BiTreeMap inverse) { this.inverse = inverse; } public BiTreeMap() { inverse = new BiTreeMap<V, K>(this); } public BiTreeMap(Map<? extends K, ? extends V> m) { inverse = new BiTreeMap<V, K>(this); putAll(m); } public BiTreeMap(Comparator<? super K> comparator) { super(comparator); inverse = new BiTreeMap<V, K>(this); } public BiTreeMap(Comparator<? super K> comparatorK, Comparator<? super V> comparatorV) { super(comparatorK); inverse = new BiTreeMap<V, K>(this, comparatorV); } private BiTreeMap(BiTreeMap<V, K> inverse, Comparator<? super K> comparatorK) { super(comparatorK); this.inverse = inverse; } #Override public V put(K key, V value) { if(key == null || value == null) { throw new NullPointerException(); } V oldValue = super.put(key, value); if (oldValue != null && inverse._compareKeys(value, oldValue) != 0) { inverse._remove(oldValue); } K inverseOldKey = inverse._put(value, key); if (inverseOldKey != null && _compareKeys(key, inverseOldKey) != 0) { super.remove(inverseOldKey); } return oldValue; } private int _compareKeys(K k1, K k2) { Comparator<? super K> c = comparator(); if (c == null) { Comparable<? super K> ck1 = (Comparable<? super K>) k1; return ck1.compareTo(k2); } else { return c.compare(k1, k2); } } private V _put(K key, V value) { return super.put(key, value); } #Override public V remove(Object key) { V value = super.remove(key); inverse._remove(value); return value; } private V _remove(Object key) { return super.remove(key); } #Override public void putAll(Map<? extends K, ? extends V> map) { for (Map.Entry<? extends K, ? extends V> e : map.entrySet()) { K key = e.getKey(); V value = e.getValue(); put(key, value); } } #Override public void clear() { super.clear(); inverse._clear(); } private void _clear() { super.clear(); } public boolean containsValue(Object value) { return inverse.containsKey(value); } #Override public Map.Entry<K, V> pollFirstEntry() { Map.Entry<K, V> entry = super.pollFirstEntry(); inverse._remove(entry.getValue()); return entry; } #Override public Map.Entry<K, V> pollLastEntry() { Map.Entry<K, V> entry = super.pollLastEntry(); inverse._remove(entry.getValue()); return entry; } }