How to Implement a Map with Multiple Keys

How to implement a Map with multiple keys?

Two maps. One Map<K1, V> and one Map<K2, V>. If you must have a single interface, write a wrapper class that implements said methods.

Map with multiple keys

Use a TreeMap instead, this way you can make use a custom comparator for your CustomKey class instead of a Hashcode.

TreeMap<CustomKey, List<value>> map = new TreeMap<CustomKey, List<value>>(myComparator);

eta: instead of creating a comparator class you can make the CustomKey class implement Comparable

Java Map with multiple keys

Just store the value twice:

Map<Object, Value> map = new HashMap<>();
map.put(key1, someValue);
map.put(key2, someValue);

The thing is, it doesn't really matter what type the key is, so use a generic bound that allows both key types - Object is fine.

Note that the parameter type of Map#get() method is just Object anyway, so from a look-up perspective there's no value in having separate maps (the type of the key is only relevant for put()).

How link multiple keys to the same value in the HashMap

If you want to associate a group of keys with the same object, it can be achieved by using a mutable object as a value.

For instance, you can make use of StringBuilder or implement a custom class. It'll be more performant and easier than an approach with implementing your own map which extends HashMap and is able to track these groups of keys and triggers a series of updates for each call of put(), replace() or remove().

Solution with a custom mutable Container can look like this:

HashMap<String, Container<Integer>> map = new HashMap<>();
Container<Integer> commonValue = new Container<>(0);
map.put("x", commonValue);
map.put("y", commonValue);

System.out.println("Value for 'x': " + map.get("x"));
System.out.println("Value for 'y': " + map.get("y"));

commonValue.setValue(10);

System.out.println("Value for 'x': " + map.get("x"));
System.out.println("Value for 'y': " + map.get("y"));

The Container class itself.

public class Container<T> {
private T value;

public Container(T value) {
this.value = value;
}

public T getValue() {
return value;
}

public void setValue(T value) {
this.value = value;
}

@Override
public String toString() {
return String.valueOf(value);
}
}

As I have already said, the alternative is to use a mutable class that is already provided by the JDK. The code is then almost the same:

HashMap<String, StringBuilder> map = new HashMap<>();
StringBuilder commonValue = new StringBuilder("0");
map.put("x", commonValue);
map.put("y", commonValue);

System.out.println("Value for 'x': " + map.get("x"));
System.out.println("Value for 'y': " + map.get("y"));

commonValue.replace(0, commonValue.length(), "10");

System.out.println("Value for 'x': " + map.get("x"));
System.out.println("Value for 'y': " + map.get("y"));

Output (by both versions)

Value for 'x': 0
Value for 'y': 0
Value for 'x': 10
Value for 'y': 10

How to create a HashMap with two keys (Key-Pair, Value)?

There are several options:

2 dimensions

Map of maps

Map<Integer, Map<Integer, V>> map = //...
//...

map.get(2).get(5);

Wrapper key object

public class Key {

private final int x;
private final int y;

public Key(int x, int y) {
this.x = x;
this.y = y;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Key)) return false;
Key key = (Key) o;
return x == key.x && y == key.y;
}

@Override
public int hashCode() {
int result = x;
result = 31 * result + y;
return result;
}

}

Implementing equals() and hashCode() is crucial here. Then you simply use:

Map<Key, V> map = //...

and:

map.get(new Key(2, 5));

Table from Guava

Table<Integer, Integer, V> table = HashBasedTable.create();
//...

table.get(2, 5);

Table uses map of maps underneath.

N dimensions

Notice that special Key class is the only approach that scales to n-dimensions. You might also consider:

Map<List<Integer>, V> map = //...

but that's terrible from performance perspective, as well as readability and correctness (no easy way to enforce list size).

Maybe take a look at Scala where you have tuples and case classes (replacing whole Key class with one-liner).

Map with multiple keys mapped to a single value in Java?

I would create a new class and have these 3 keys as attributes such as

class CompositeKey {
private String key1;
private Integer key2;
private Double key3;

public CompositeKey(String key1, Integer key2, Double key3) {
this.key1 = key1;
this.key2 = key2;
this.key3 = key3;
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((key1 == null) ? 0 : key1.hashCode());
result = prime * result + ((key2 == null) ? 0 : key2.hashCode());
result = prime * result + ((key3 == null) ? 0 : key3.hashCode());
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
CompositeKey other = (CompositeKey) obj;
if (key1 == null) {
if (other.key1 != null)
return false;
} else if (!key1.equals(other.key1))
return false;
if (key2 == null) {
if (other.key2 != null)
return false;
} else if (!key2.equals(other.key2))
return false;
if (key3 == null) {
if (other.key3 != null)
return false;
} else if (!key3.equals(other.key3))
return false;
return true;
}

public String getKey1() {
return key1;
}

public Integer getKey2() {
return key2;
}

public Double getKey3() {
return key3;
}

}

Then I'll create 3 index maps for each key as follow

// 3 index maps
// List<CompositeKey> not just CompositeKey because one key say k1 can be in combination of 2 different set of k2 and k3.
Map<String, List<CompositeKey>> key1Index = new HashMap<>();
Map<Integer, List<CompositeKey>> key2Index = new HashMap<>();
Map<Double, List<CompositeKey>> key3Index = new HashMap<>();

Then the data map where actual data will be kept

Map<CompositeKey, Object> dataMap = new HashMap<>();

Now, say you want to add key1,key2,key3 against Object. Create CompositeKey object as key

CompositeKey compositeKey = new CompositeKey(key1, key2, key3);

// Change value accordingly below
dataMap.put(compositeKey, new Object());

Also, update the index maps so that you can use them to look up later

key1Index.computeIfAbsent(key1, k -> new ArrayList<>()).add(compositeKey);
key2Index.computeIfAbsent(key2, k -> new ArrayList<>()).add(compositeKey);
key3Index.computeIfAbsent(key3, k -> new ArrayList<>()).add(compositeKey);

Finally, you can now,

// Search by single key, say key1
List<CompositeKey> list = key1Index.get(key1);
List<Object> result = getResult(list, dataMap);

// Search by two keys, say key 1 and key 2
List<CompositeKey> key1IndexResult = key1Index.get(key1);
List<CompositeKey> key1Key2List = key1IndexResult.stream()
.filter(ck -> ck.getKey2().equals(key2))
.collect(Collectors.toList());

List<Object> key1Key2Result = getResult(key1Key2List, dataMap);

// Search by all 3 keys
CompositeKey allKeys = new CompositeKey(key1, key2, key3);
List<Object> allKeysResult = getResult(Collections.singletonList(allKeys), dataMap);

Utility method used:

private List<Object> getResult(List<CompositeKey> list, Map<CompositeKey, Object> dataMap) {
return list.stream().map(dataMap::get)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}

Working code online link: https://onlinegdb.com/rk2kszLMN

Multiple Keys to Single Value Map Java

The Table data structure in Guava seems to meet you requirement of refering to a value by a pair of objects.

How can I implement a fast map having multiple keys?

Constant look up requires a hash map. You can use a the boost::unordered_map (or tr1). The key would be the combined hash of the int and the void pointer.

HashMap with multiple values under the same key

You could:

  1. Use a map that has a list as the value. Map<KeyType, List<ValueType>>.
  2. Create a new wrapper class and place instances of this wrapper in the map. Map<KeyType, WrapperType>.
  3. Use a tuple like class (saves creating lots of wrappers). Map<KeyType, Tuple<Value1Type, Value2Type>>.
  4. Use mulitple maps side-by-side.


Examples

1. Map with list as the value

// create our map
Map<String, List<Person>> peopleByForename = new HashMap<>();

// populate it
List<Person> people = new ArrayList<>();
people.add(new Person("Bob Smith"));
people.add(new Person("Bob Jones"));
peopleByForename.put("Bob", people);

// read from it
List<Person> bobs = peopleByForename["Bob"];
Person bob1 = bobs[0];
Person bob2 = bobs[1];

The disadvantage with this approach is that the list is not bound to exactly two values.

2. Using wrapper class

// define our wrapper
class Wrapper {
public Wrapper(Person person1, Person person2) {
this.person1 = person1;
this.person2 = person2;
}

public Person getPerson1() { return this.person1; }
public Person getPerson2() { return this.person2; }

private Person person1;
private Person person2;
}

// create our map
Map<String, Wrapper> peopleByForename = new HashMap<>();

// populate it
peopleByForename.put("Bob", new Wrapper(new Person("Bob Smith"),
new Person("Bob Jones"));

// read from it
Wrapper bobs = peopleByForename.get("Bob");
Person bob1 = bobs.getPerson1();
Person bob2 = bobs.getPerson2();

The disadvantage to this approach is that you have to write a lot of boiler-plate code for all of these very simple container classes.

3. Using a tuple

// you'll have to write or download a Tuple class in Java, (.NET ships with one)

// create our map
Map<String, Tuple2<Person, Person> peopleByForename = new HashMap<>();

// populate it
peopleByForename.put("Bob", new Tuple2(new Person("Bob Smith",
new Person("Bob Jones"));

// read from it
Tuple<Person, Person> bobs = peopleByForename["Bob"];
Person bob1 = bobs.Item1;
Person bob2 = bobs.Item2;

This is the best solution in my opinion.

4. Multiple maps

// create our maps
Map<String, Person> firstPersonByForename = new HashMap<>();
Map<String, Person> secondPersonByForename = new HashMap<>();

// populate them
firstPersonByForename.put("Bob", new Person("Bob Smith"));
secondPersonByForename.put("Bob", new Person("Bob Jones"));

// read from them
Person bob1 = firstPersonByForename["Bob"];
Person bob2 = secondPersonByForename["Bob"];

The disadvantage of this solution is that it's not obvious that the two maps are related, a programmatic error could see the two maps get out of sync.



Related Topics



Leave a reply



Submit