I need a multi map which keys are case insensitive. is there such implementation in google collections?
Here is a case insensitive version of a ForwardingMap:
public class CaseInsensitiveForwardingMap<V> extends ForwardingMap<String, V>
implements Serializable{
private static final long serialVersionUID = -7741335486707072323L;
// default constructor
public CaseInsensitiveForwardingMap(){
this(new HashMap<String, V>());
}
// constructor with a supplied map
public CaseInsensitiveForwardingMap(final Map<String, V> inner){
this.inner = inner;
}
private final Map<String, V> inner;
#Override
protected Map<String, V> delegate(){
return inner;
}
// convert keys to lower case Strings, preserve null keys
private static String lower(final Object key){
return key == null ? null : key.toString().toLowerCase();
}
#Override
public V get(final Object key){ return inner.get(lower(key)); }
#Override
public void putAll(final Map<? extends String, ? extends V> map){
if(map == null || map.isEmpty()){ inner.putAll(map); }
else{
for(final Entry<? extends String, ? extends V> entry :
map.entrySet()){
inner.put(lower(entry.getKey()), entry.getValue());
}
}
}
#Override
public V remove(final Object object){ return inner.remove(lower(object)); }
#Override
public boolean containsKey(final Object key){
return inner.containsKey(lower(key));
}
#Override
public V put(final String key, final V value){
return inner.put(lower(key), value);
}
}
Using this map, you can create the MultiMap using the Supplier methods in MultiMaps.
Example:
Map<String, Collection<String>> map =
new CaseInsensitiveForwardingMap<Collection<String>>();
Multimap<String, String> caseInsensitiveMultiMap =
Multimaps.newMultimap(map, new Supplier<Collection<String>>(){
#Override
public Collection<String> get(){ return Sets.newHashSet(); }
});
Caveat: keySet() will return lowercase values only, regardless how the keys were entered.
Couldn't you use a Map<String,List<Payload>> and give it a Comparator<String> which did a case-insensitive compare?
It appears that neither Google Collections nor Apache Collection frameworks have a multimap that accepts a Comparator for evaluating key equality.
You could define a case-insensitive String Comparator using a Collator. Then create a TreeMultimap with keys sorted by that Comparator.
No, but presumably you're using String keys? If so, why not just normalise all access to a regular multimap? For the 80% case, that'll be making all calls puts and gets lowercase the key.
For a full discussion of the issues with case-insensitive multimaps, see this google group discussion
Related
I want to make a HashMap which contains HashSets as values and returns an empty HashSet when the key is not found.
public class IsbnHashMap<K,V> extends HashMap<K,V> {
protected V defaultValue;
public IsbnHashMap(V defaultValue) {
this.defaultValue = defaultValue;
}
#Override
public V get(Object k) {
return containsKey(k) ? super.get(k) : defaultValue;
}
}
However my implementation does not work.
private static IsbnHashMap<String, HashSet<String>> isbnToId = new IsbnHashMap<String, HashSet<String>>();
This returns "HashSet cannot be applied". If I try to change K,V in IsbnHashMap to <String, HashSet<String>> I get some funky errors as well. How can I implement this?
First it should be noted that in Java-8 you can use instead:
isbnToId.computeIfAbsent(isbn, k -> new HashSet<>()).add(_id);
Second, if you really want to do something like this in previous Java versions, you'd better to create separate method for this purpose (for example, getOrDefault()) in order not to violate the contract. Third, you need to create new HashSet<>() for every new key. If you return the same instance, it will be shared between given keys. If you don't expect users to modify it, it's better to use unmodifiable Collections.emptySet() as default value. This way users may safely do isbnToId.getOrDefault(isbn).contains(_id), but trying isbnToId.getOrDefault(isbn).add(_id) will result in exception. If you want to support the modification (prior to Java-8), you can, for example, pass the element class to the constructor instead:
public static class MyMap<K, V> extends HashMap<K, V> {
private Class<?> clazz;
public MyMap(Class<?> clazz) {
this.clazz = clazz;
}
public V getOrCompute(K key) {
V v = get(key);
if(v == null) {
try {
v = (V) clazz.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
put(key, v);
}
return v;
}
}
Usage example:
MyMap<String, Set<String>> map = new MyMap<>(HashSet.class);
map.getOrCompute("a").add("b");
map.getOrCompute("a").add("c");
map.getOrCompute("d").add("e");
System.out.println(map); // {a=[b, c], d=[e]}
Here we assume that instantiating the passed class with default constructor is ok. An alternative would be to pass the factory interface which is capable to produce the default values.
As Jon Skeet said ...
private static IsbnHashMap<String, HashSet<String>> isbnToId = new IsbnHashMap<String, HashSet<String>>(new HashSet<String>());
... however, that would return the same default object as Dunni pointed out.
So this will do:
private static HashMap<String, HashSet<String>> isbnToId = new HashMap<String, HashSet<String>>();
public static void coupleIsbnToId(String isbn, String _id) {
if (!isbnToId.containsKey(isbn)) {
isbnToId.put(isbn, new HashSet<String>());
}
isbnToId.get(isbn).add(_id);
}
Folks,
Is there any easy way to add generic class in non generic class.
Basically the cache manager will have map of Cache class which is implemented with proper generics.
But in below class we return (getCache method) Cache via get method it requires explicit cast at callers place how to avoid it.
e.g.
public class CacheManager {
private Map<String, Cache<?,?>> cacheMap = new HashMap<String, Cache<?,?>>();
public Cache<?,?> getCache(String cacheName) {
return cacheMap.get(cacheName);
}
public void addCache(String cacheName,Cache<?,?> cache) {
cacheMap.put(cacheName, cache);
}
}
Short answer: No (as far as I know).
The problem here is that what you are doing is not type-safe in Java at all. Have a look at this example:
import java.util.*;
class ClassCast {
public static void main(String[] args) {
HashMap<String, Pair<?, ?>> map = new HashMap<>();
map.put("test", new Pair<String, Integer>("Hello", 5));
Pair<Double, Double> pair = (Pair<Double, Double>) map.get("test");
}
}
class Pair<T,V> {
T a;
V b;
Pair(T a, V b) {
this.a = a;
this.b = b;
}
}
You would expect a ClassCastException here, but it compiles and runs perfectly fine. The reason for this is that the actual class of Pair<String, Integer> and Pair<Double, Double> is in fact just Pair (after type erasure).
To get type safety you have to implement the "Typesafe heterogeneous container pattern" (explained in detail in Effective Java by Josh Bloch). In short, you have to involve the type parameter in the key of your map. Depending on your needs, you might be able to use a class as key directly, otherwise you might have to make a key object.
Example implementation:
public class CacheManager {
private Map<MultiKey, Cache<?,?>> cacheMap = new HashMap<>();
#SuppressWarnings("unchecked")
public <T,V> Cache<T,V> get(String name, Class<T> t, Class<V> v) {
// Type-safe since types are encoded in key(i.e. map will not
// return something with the wrong type), and key is type-checked
// on insertion.
return (Cache<T,V>) cacheMap.get(new MultiKey(name, t, v));
}
public <T,V> void put(String name, Class<T> t, Class<V> v, Cache<T,V> cache) {
cacheMap.put(new MultiKey(name, t, v), cache);
}
class MultiKey {
Object[] keys;
Integer hash = null;
MultiKey(Object... keys) {
this.keys = keys;
}
#Override
public int hashCode() {
if (hash == null) hash = Arrays.hashCode(keys);
return hash;
}
#Override
public boolean equals(Object o) {
if (o == null || !(o instanceof MultiKey)) return false;
return Arrays.equals(keys, ((MultiKey) o).keys);
}
}
}
Example usage:
CacheManager mng = new CacheManager();
mng.addCache("SI", String.class, Integer.class, new Cache<String, Integer>());
Cache<String, Integer> cache = mng.getCache("SI", String.class, Integer.class);
System.out.println(cache);
It's not pretty, but it is actually type-safe. It can be improved depending on the actual situation though, so you should not use this code as is. For example, if you can get the types from the Cache object you don't need the Class arguments in addCache.
let's imagine this scenario - I would like to use TreeMap in java. It's part of the Colletions framework and the only implementation of the SortedMap interface.
public class MyDictionary extends TreeMap<String, String> {
// some code
}
In order to walk through the entries stored in my Dictionary class I will need a type of Map.Entry. Somewhere in the code (could be a method of the MyDictionary class or even more likely a method in the wrapper class containing a variable of MyDictionary class holding my data) there will be something like:
public void showEntries() {
for (Map.Entry<String, String> e : dictionary) {
System.out.println(e.getKey(), e.getValue()); // do something
}
}
And now the question: is there a way to bind the generic types of Map.Entry to the generic types declared for the TreeMap?
The goal is to have the generic types defined in one place only.
In case I decide to change the type of data held in the TreeMap later I won't have to search all places where I used those types.
The example above is a Proof-Of-Concept. Pls help.
You can make the MyDictionary class generic, with type parameters to match TreeMap.
public class MyDictionary<K, V> extends TreeMap<K, V>
Then you can refer to those type parameters throughout your class. Specifically:
for (Map.Entry<K, V> e : dictionary) {
Or if you know that the key and the value will always be the same type:
public class MyDictionary<E> extends TreeMap<E, E>
and
for (Map.Entry<E, E> e : dictionary) {
This can be achieved by using two adapters - one for Entry and one for Iterator.
First, inside Dictionary, you create your Entry adapter, e.g.:
public static class Entry implements Map.Entry<String, String> {
private Map.Entry<String, String> entry;
Entry(Map.Entry<String, String> entry) {
this.entry = entry;
}
#Override
public String getKey() {
return entry.getKey();
}
#Override
public String getValue() {
return entry.getValue();
}
#Override
public String setValue(String value) {
return entry.setValue(value);
}
}
To be able to use your Dictionary class in the foreach loop, you have to implement Iterable interface. TreeMap does not implement it.
public class Dictionary extends TreeMap<String, String> implements Iterable<Dictionary.Entry>
You can write your iterator() method like this:
#Override
public Iterator<Entry> iterator() {
return new Iterator<Entry>() {
Iterator<Map.Entry<String, String>> wrapped = entrySet().iterator();
#Override
public boolean hasNext() {
return wrapped.hasNext();
}
#Override
public Entry next() {
return new Entry(wrapped.next());
}
#Override
public void remove() {
wrapped.remove();
}
};
}
Now, you can enjoy using foreach loops without generic types:
for (Dictionary.Entry e : dictionary) {
System.out.println(e.getKey() + " " + e.getValue());
}
Is there an existing open source Map implementation for java, which would be a normal key-value map, but would also support multiple values per key? The multimap implementations I've found seem to associate key with collection, which doesn't quite cut it, as I need a drop-in replacement for existing code.
I sense some people saying "you can't do that", so here's an example of one way how it can behave, in a widely used framework, Qt. Here's an excerpt form the docs for QMap class:
If the map contains no item with key key, the function returns a
default-constructed value. If there are multiple items for key in the
map, the value of the most recently inserted one is returned.
My need is quite limited, so at the moment I'm using the hack below, which is adequate, since there are no removals and many values per key are exception, and the duplicate keys getting a bit mangled is not a problem:
public static <V, V2 extends V> String mapMultiPut(
Map<String, V> map,
String key,
V2 value) {
int count = 0;
String tmpKey = key;
while (map.containsKey(tmpKey)) {
++count;
tmpKey = key + '_' + count;
}
map.put(tmpKey, value);
return tmpKey;
}
But I'd like a nicer solution, if one exists...
You could use a ListMultimap along with
Iterables.getLast(listMultiMap.get(key), defaultValue(key))
where you define your own defaultValue method.
This assumes you don't actually need the Map interface in your class.
If you really want a Map you could try this
public abstract class QtMap<K, V> extends ForwardingMap<K, V>
{
private final ListMultimap<K, V> listMultimap = ArrayListMultimap.create();
final Map<K, V> delegate = Maps.<K, Collection<V>, V> transformEntries(listMultimap.asMap(), new EntryTransformer<K, Collection<V>, V>()
{
#Override
public V transformEntry(K key, Collection<V> value)
{
return Iterables.getLast(value, defaultValue(key));
}
});
#Override
protected Map<K, V> delegate()
{
return delegate;
}
#Override
public V put(K key, V value)
{
listMultimap.put(key, value);
return null;
}
#Override
public void putAll(Map<? extends K, ? extends V> map)
{
for (Map.Entry<? extends K, ? extends V> entry : map.entrySet())
{
put(entry.getKey(), entry.getValue());
}
}
#Override
public V get(Object key)
{
return listMultimap.containsKey(key) ? delegate.get(key) : defaultValue(key);
}
protected abstract V defaultValue(Object key);
}
although it's only sketchily tested
Guava libraries have a Multimap which allows more than one value per key :)
Can you suggest a kind of map or similar data structure where we can get both the value and key from each other at equal ease. That is to say, that each may be used to find other.
Java doesn't have a bidirectional map in its standard library.
Use for example BiMap<K, V> from Google Guava .
If you feel it pain importing some third party library.
How about this simple class.
public class BiMap<K,V> {
HashMap<K,V> map = new HashMap<K, V>();
HashMap<V,K> inversedMap = new HashMap<V, K>();
void put(K k, V v) {
map.put(k, v);
inversedMap.put(v, k);
}
V get(K k) {
return map.get(k);
}
K getKey(V v) {
return inversedMap.get(v);
}
}
Make sure K and V class has proper hashCode implementation.
The most common solution is using two maps. You can easily encapsulate them in a class with a friendly interface by extending AbstractMap. (Update: This is how Guava's HashBiMap is implemented: two maps)
Creating a new data structure using nothing but arrays and custom classes has few advantages. The map implementations are lightweight wrappers of a data structure that indexes the keys. Since you need two indexes you might as well use two complete maps.
Also try Apache Commons Collections 4 BidiMap Package.
Google Guava contains a BiMap (BiDirectional Map).
well for the average usecase where you need a Dictionary like that, I see nothing wrong with a KISS solution, just put'ting the key and value vice versa, saving the overhead of a second Map or even library only for that purpose:
myMap.put("apple", "Apfel");
myMap.put("Apfel", "apple");
Based on this answer in this QA and its comments I wrote following. [Will be tested]
Bidirectional Map
import java.util.HashMap;
public class BidirectionalMap<K, V> extends HashMap<K, V> {
private static final long serialVersionUID = 1L;
public HashMap<V, K> inversedMap = new HashMap<V, K>();
public K getKey(V value) {
return inversedMap.get(value);
}
#Override
public int size() {
return this.size();
}
#Override
public boolean isEmpty() {
return this.size() > 0;
}
#Override
public V remove(Object key) {
V val=super.remove(key);
inversedMap.remove(val);
return val;
}
#Override
public V get(Object key) {
return super.get(key);
}
#Override
public V put(K key, V value) {
inversedMap.put(value, key);
return super.put(key, value);
}
}
You can define an enum and define helper method to get key. Performance is way too far better compared to BidiMap.
E.g
public enum Fruit {
APPLE("_apple");
private final String value;
Fruit(String value){
this.value=value;
}
public String getValue(){
return this.value;
}
public static String getKey(String value){
Fruit fruits[] = Fruit.values();
for(Fruit fruit : fruits){
if(value.equals(fruit.value)){
return fruit.name();
}
}
return null; }
}
Based on this tutorial I suggest the following as answer:
public class IdToNames {
public static void main(String[] args){
BidiMap<String, Integer> map = new DualHashBidiMap<>();
map.put("NameA", 100);
map.put("NameB", 200);
System.out.println(map.size()); //2 as expected
System.out.println(map.get("NameA")); //100 as expected
System.out.println(map.getKey(100)); //"NameA" as expected
}
}
Note the problem of duplicated keys and/or values described in this question here