Qt-like multivalue map for Java? - java

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 :)

Related

own MultiMap implementation, problems with put

For the purpose of learning I try to make a MultiMap implementation. (and for avoiding relying on other libraries for a library I make).
It doesn't have to be perfect.
At the moment I have this:
class MultiMap<k, v> implements Map {
HashMap<k, List<v>> hMap = new HashMap<k, List<v>>();
public MultiMap () {
}
Followed by all #Override methods from Map.
One is like this:
#Override
public Object get(Object o) {
return hMap.get(o);
}
I have problems with this one:
#Override
public Object put(Object o, Object o2) {
// will return a list
Object toReturn = get(o);
if(hMap.containsValue(o)) {
// is this even possible?
(List<v>)(List<?>)get(o); // <<< problem: "Syntax error on token(s), misplaced construct(s)"
// ^ next .add(o2);
}
// etc.
return toReturn;
}
Is it possible to get a List out of the get method?
You define class like this class MultiMap<k, v> implements Map<k, List<v>>
and then add new method public List<v> put(k key, v value) like that.
public List<v> put(k key, v value) {
List<v> list = get(key);
if(list == null)
list = new ArrayList<>();
list.add(value);
return list;
}

Generic HashMap with Lists and putIfAbsent

In Java, I want to add a getOrAdd method to a regular map, just like putIfAbsent on a ConcurrentHashMap.
Furthermore, for a certain key I want to store a list of items. Here's my attempt:
public class ListMap<K, V> extends HashMap<K, V> {
private HashMap<K, List<V>> map;
public ListMap() {
map = new HashMap<K, List<V>>();
}
public List<V> getOrAdd(K key) {
if (map.containsKey(key)) {
return map.get(key);
} else {
List<V> l = new ArrayList<V>();
map.put(key, l);
return l;
}
}
}
However, if someone wanted to iterate over a ListMap, he would need to cast the values explictly.
ListMap<Integer, MyClass> listMap = new ListMap<Integer, MyClass>();
for (Map.Entry<Integer, MyClass> entry : listMap.entrySet()) {
List<MyClass> val = (List<MyClass>) entry.getValue();
}
Is there a way of extending the HashMap class by some methods without creating a subclass? ( I've seen this in C#)
How can the ListMap class be modified such that one can get a ListMaps's value (List) without casting?
Instance of your class will be also HashMap so you don't need to, or even shouldn't add another field just to support getOrAdd method because other inherited and not overridden methods will not be referring to map field but to this instance.
So instead of adding separate field
private HashMap<K, List<V>> map;
change extending type of your ListMap to
public class ListMap<K, V> extends HashMap<K, List<V>>
^^^^^^^
and change your getOrAdd method to not use map field but this
public List<V> getOrAdd(K key) {
if (containsKey(key)) {
return get(key);
} else {
List<V> l = new ArrayList<V>();
put(key, l);
return l;
}
}
This change will let you use your map like
ListMap<Integer, String> listMap = new ListMap<Integer, String>();
for (Map.Entry<Integer, List<String>> entry : listMap.entrySet()) {
List<String> val = entry.getValue();//NO CASTING NEEDED
}
You can just extend HashMap like this:
public class ListMap<K, V> extends HashMap<K, List<V>> {
...
}

Reference type dependency - declare Map.Entry to be dependent on generic types declared for TreeMap

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

Bidirectional Map

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

is there a case insensitive multimap in google collections

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

Categories

Resources