7

I want to use MultiKeyMap from Apache Collection, because I need a HashMap with two keys and a value. To put elements I do this:

private MultiKeyMap multiKey = new MultiKeyMap();
multiKey.put("key1.1", "key2.1", "value1");

And for get element I do this:

String s = multiKey.get("key1.1");

But the String s cames null... If I pass the two keys, like that:

String s = multiKey.get("key1.1", "key2.1");

The String s cames with values value1...

How can I extend the MultiKeyMap to get the right value when I pass only one of the two keys?

josecampos
  • 851
  • 1
  • 8
  • 25

10 Answers10

5

If you need only one key to get a value you have a plain old HashMap.

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

map.put("key1.1", "value1");
map.put("key2.1", "value1");

And for get element you can do this:

String s = map.get("key1.1"); // s == "value1"

MultiKeyMap is required when both keys must be provided.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
5

If you specify a value with two keys, you are going to need both keys to get it back. The hash function is not designed to return all the possible values that are associated with only one of the two keys. You may need to find a different data structure to do this.

wespiserA
  • 3,131
  • 5
  • 28
  • 36
2

MultiKeyMap is about using tuples as keys, not about matching one value to more than one key. Use a normal map and just put your value twice, with different keys.

Some more caution is needed when removing values. When you remove a value for the first key, do you want to automatically remove other keys with the same value? If so, you need either to loop over all keys and remove those with same value by hand, which could be inefficient, or keep some kind of reverse map to quickly find keys for specific value.

socha23
  • 10,171
  • 2
  • 28
  • 25
2

I don't know exact solution to your problem. But I suggest you to implement it like:

Map<K2, K1> m2;
Map<K1, V>  m1;

And see: How to implement a Map with multiple keys?

Community
  • 1
  • 1
Abhishek bhutra
  • 1,400
  • 1
  • 11
  • 29
1

It seems that you just do not need MultiKeyMap. You need regular map. Using it you can associate the same value with as many keys as you want.

Map<String, String> map = new HashMap<String, String>();
Object value = .....
map.put("key1", value);
map.put("key2", value);

..................

if(map.get("key1") == map.get("key2")) {
    System.out.println("the same value stored under 2 different keys!");
}
Shailendra Madda
  • 20,649
  • 15
  • 100
  • 138
AlexR
  • 114,158
  • 16
  • 130
  • 208
0

Instead of that you can use table data stature from guava.

0

I would suggest to create a separate class for multiple keys:

public class Test {

Map<Shape, Book> test1 = new HashMap<>();
Book book = new Book("A");
test1.put(Shape, book);


private class Shape {
  String id1;
  String id2;
public Shape(String id1, String id2) {
  this.id1 = id1;
  this.id2 = id2;
}
 @Override
 public boolean equals(Object o) {//}
@Override
public int hashCode() {//}
}

}
0

Here is a simple MultiKeyMap implementation that worked for me.

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;

public class MultiMap<K, V> implements Map<K, V>
{
    private class MultiMapEntery implements java.util.Map.Entry<K, V>
    {
        private final K key;
        private V value;

        public MultiMapEntery(K key, V value)
        {
            this.key = key;
            this.value = value;
        }
        @Override
        public K getKey()
        {
            return key;
        }

        @Override
        public V getValue()
        {
            return value;
        }

        @Override
        public V setValue(V value)
        {
            V oldValue = this.value;
            this.value = value;
            return oldValue;
        }
    };

    private final Map<K, String> keyMap = new HashMap<K, String>();
    private final Map<String, Set<K>> inverseKeyMap = new HashMap<String, Set<K>>();
    private final Map<String, V> valueMap = new HashMap<String, V>();

    @Override
    public void clear()
    {
        keyMap.clear();
        inverseKeyMap.clear();
        valueMap.clear();
    }

    @Override
    public boolean containsKey(Object key)
    {
        return keyMap.containsKey(key);
    }

    @Override
    public boolean containsValue(Object value)
    {
        return valueMap.containsValue(value);
    }

    @Override
    public Set<java.util.Map.Entry<K, V>> entrySet()
    {
        Set<java.util.Map.Entry<K, V>> entries = new HashSet<>();
        for(K key : keyMap.keySet())
        {
            V value = valueMap.get(key);
            entries.add(new MultiMapEntery(key, value));
        }
        return entries;
    }

    @Override
    public V get(Object key)
    {
        return valueMap.get(keyMap.get(key));
    }

    @Override
    public boolean isEmpty()
    {
        return valueMap.isEmpty();
    }

    @Override
    public Set<K> keySet()
    {
        return keyMap.keySet();
    }

    @Override
    public V put(K key, V value)
    {
        String id = keyMap.get(key);
        if(id == null)
        {
            id = UUID.randomUUID().toString();
        }
        keyMap.put(key, id);
        Set<K> keys = inverseKeyMap.get(id);
        if(keys == null)
        {
            keys = new HashSet<>();
        }
        keys.add(key);
        inverseKeyMap.put(id, keys);
        valueMap.put(id, value);
        return value;
    }

    public V put(Set<K> keys, V value)
    {
        String id = null;
        for(K key : keys)
        {
            id = keyMap.get(key);
            if(id != null) // one of the keys already exists
            {
                break;
            }
        }

        if(id == null)
        {
            id = UUID.randomUUID().toString();
        }

        for(K key : keys)
        {
            keyMap.put(key, id);
        }
        inverseKeyMap.put(id, keys);
        valueMap.put(id, value);
        return value;
    }

    @Override
    public void putAll(Map<? extends K, ? extends V> map)
    {
        for(java.util.Map.Entry<? extends K, ? extends V> entry : map.entrySet())
        {
            put(entry.getKey(), entry.getValue());
        }
    }

    @Override
    public V remove(Object key)
    {
        String id = keyMap.get(key);
        keyMap.remove(key);
        Set<K> keys = inverseKeyMap.get(id);
        keys.remove(key);
        V value = valueMap.get(id);
        if(keys.size() == 0) // it was the last key, now remove the value
        {
            valueMap.remove(id);
        }
        return value;
    }

    @Override
    public int size()
    {
        return valueMap.size();
    }

    @Override
    public Collection<V> values()
    {
        return valueMap.values();
    }

    public static void main(String[] args)
    {
        MultiMap<String, String> m = new MultiMap<>();
        m.put("a", "v1");
        Set<String> s = new HashSet<>();
        s.add("b");
        s.add("c");
        s.add("d");
        m.put(s, "v2");

        System.out.println("size:"  + m.size());
        System.out.println("keys:"  + m.keySet());
        System.out.println("values:"  + m.values().toString());
        System.out.println("a:" + m.get("a"));
        System.out.println("b:" + m.get("b"));
        System.out.println("c:" + m.get("c"));
        System.out.println("d:" + m.get("d"));

        m.remove("a");

        System.out.println("size:"  + m.size());
        System.out.println("keys:"  + m.keySet());
        System.out.println("values:"  + m.values().toString());
        System.out.println("a:" + m.get("a"));
        System.out.println("b:" + m.get("b"));
        System.out.println("c:" + m.get("c"));
        System.out.println("d:" + m.get("d"));

        s.add("a");
        m.put(s, "v3");

        System.out.println("size:"  + m.size());
        System.out.println("keys:"  + m.keySet());
        System.out.println("values:"  + m.values().toString());

        System.out.println("a:" + m.get("a"));
        System.out.println("b:" + m.get("b"));
        System.out.println("c:" + m.get("c"));
        System.out.println("d:" + m.get("d"));
    }
}
amralieg
  • 113
  • 8
0

A little late, but you probably mean to get every result from the map, that matches the first element only, even though it contains multiple results, ignoring the second key (wildcard effect). Apache's MultiKeyMap is not suitable for this.

You could solve this by creating your own filter functionality using the MultiKey of MultiKeyMap. First, filter out only the relevant MultiKeys (which you get from yourMultiKeyMap.keySet() ) . The following method takes those multiKeys, and the first keys you want to filter on:

private Set<MultiKey<? extends String>> filterMultiKeys(Set<MultiKey<? extends String>> multiKeys, final String... keys) {
    
    final List<String> givenKeys = Arrays.asList(keys);
    
    return multiKeys.stream().filter(multiKey -> {

        final Object[] actualKeys = multiKey.getKeys();
        if (actualKeys.length < givenKeys.size()) {
            
            // Lesser keys, so never a match
            return false;
        }

        final List<Object> trimmedKeys = Arrays.asList(actualKeys).subList(0, givenKeys.size());
        return trimmedKeys.equals(givenKeys);
        
    }).collect(Collectors.toSet());
}

Then, use the resulting MultiKeys to get the results:

    final Set<String> results = filteredKeys.stream().map(multiKey -> yourMultiKeyMap.get(multiKey)).collect(Collectors.toSet());

For bonus points, one could extend or decorate MultiKeyMap and create MyMultiKeyMap , having a method like match(keys...) using the filter functionality.

Kjeld
  • 444
  • 4
  • 14
0

You just can't since it's not the way a MultiKeyMap works. Put the value with separate keys and than try getting it with each key at a time.

João Fernandes
  • 1,101
  • 5
  • 11