Card with multiple keys

I'm trying to implement a map like

Map<<key1, key2>, List<value>> 

The card must contain 2 keys, and the corresponding value is a list. I want to add entries to the same list if alteast one key value is equal For example, consider the following entries

 R1[key1, key2] R2[key1, null/empty] - Key1 is equal R3[null/empty, key2] - Key2 is equal R4[key1, key2] - Key1 and Key2 both are equal. 

all must be inserted in the same list as

 Key = <Key1,Key2> Value = <R1, R2, R3, R4> 

I cannot use the Guava table or commons MulitKeyMap (I do not want to include the whole library just for this).

I tried to implement a class (which I can use as a key) that will have both the key1 attribute and key2 as the attribute, but implementing an effective hash code that does not take into account key1 and key2 seems to be a bit (or maybe a lot) complicated

 public class Key { private int key1; private int key2; @Override public int hashCode() { final int prime = 31; int result = 1; // Cant include key1 and key2 in hashcode /* result = prime * result + key1; result = prime * result + key2;*/ return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Key other = (Key) obj; if(key2 and other.key2 both not blank/null){ // pseudo code if (key2 == other.key2) return true; } if(key1 and other.key1 both not blank/null){ //pseudo code if (key1 == other.key1) return true; } return true; } } 

It will work if I use the same hash code for everyone, but it will affect performance since I have thousands of records.


EDIT:
I canโ€™t use nested Maps , for example

 Map<key1, Map< key2, List<value>>> 

Because some entries may contain only one key.

  R1[key1, key2] - Have both keys R2[key1, null/empty] - Key1 is equal R3[null/empty, key2] - Key1 is missing and key2 is equal 

Here R3 does not have key1 and, therefore, cannot be inserted in the same place as R1 and R2


EDIT 2:

I also want to keep the order of interaction.

+6
source share
9 answers

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

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

eta: instead of creating a comparator class, you can force the CustomKey class to implement Comparable

+1
source

Cards, by definition, have 1 key for each value.

You may have a map card, or your key may be an object with two fields, but it is about as close as you can get.

Map Card:

 Map myMap<key, Map<otherkey, value>> 

Custom object

 public class MapKey { public Object keyFirstPart; public Object keySecondPart; // You'll need to implement equals, hashcode, etc } Map myyMap <MapKey, value> 
+4
source

If HashMap behavior is required, I would create two Maps and do the magic when processing collections (I would also recommend using Sets for this ...):

 public class MyMap<K1, K2, V> { Map<K1, Collection<V>> map1; Map<K2, Collection<V>> map2; //have to add to both lists put(K1 k1, K2 k2, V v) { addToCollection(map1, k1, v); addToCollection(map2, k2, v); } //notice T param <T> void addToCollection(Map<T, Collection<V>> map, T key, V value ) { Collection<V> collection= map.get(key); if(collection==null) { collection= new HashSet<V>(); map.put(key, collection); } collection.add(value ); } public Collection<V> get(K1 k1, K2 k2) { Collection<V> toReturn = new HashSet<V>(); Collection<V> coll1 = map1.get(k1); if(coll1!=null) { toReturn.addAll(coll1); } Collection<V> coll2 = map2.get(k2); if(coll2!=null) { toReturn.addAll(coll2); } return toReturn; } } 
+1
source

Try it....

Create a class for the key to the card

 public class MapKey { private Object key1; private Object key2; @Override public boolean equals(Object object) { boolean equals = false; if (((MapKey) object).key1 == null && ((MapKey) object).key2 == null) { equals = true; } if (((MapKey) object).key1.equals(this.key1) && ((MapKey) object).key2.equals(this.key2)) { equals = true; } if (((MapKey) object).key1 == null && ((MapKey) object).key2.equals(this.key2)) { equals = true; } if (((MapKey) object).key1.equals(this.key1) && ((MapKey) object).key2 == null) { equals = true; } return equals; } @Override public int hashCode() { return 1; } public Object getKey1() { return key1; } public void setKey1(Object key1) { this.key1 = key1; } public Object getKey2() { return key2; } public void setKey2(Object key2) { this.key2 = key2; } } 

In the above class, you can change the DataTypes key1 and key2 as needed. Here is the main class that will execute the required logic

 import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class MapWithTwoKeys { private static final Map<MapKey, List<Object>> mapWithTwoKeys = new HashMap<MapKey, List<Object>>(); public static void main(String[] args) { // Create first map entry with key <A,B>. MapKey mapKey1 = new MapKey(); mapKey1.setKey1("A"); mapKey1.setKey2("B"); List<Object> list1 = new ArrayList<Object>(); list1.add("List1 Entry"); put(mapKey1, list1); // Create second map entry with key <A,B>, append value. MapKey mapKey2 = new MapKey(); mapKey2.setKey1("A"); mapKey2.setKey2("B"); List<Object> list2 = new ArrayList<Object>(); list2.add("List2 Entry"); put(mapKey2, list2); // Create third map entry with key <A,>. MapKey mapKey3 = new MapKey(); mapKey3.setKey1("A"); mapKey3.setKey2(""); List<Object> list3 = new ArrayList<Object>(); list3.add("List3 Entry"); put(mapKey3, list3); // Create forth map entry with key <,>. MapKey mapKey4 = new MapKey(); mapKey4.setKey1(""); mapKey4.setKey2(""); List<Object> list4 = new ArrayList<Object>(); list4.add("List4 Entry"); put(mapKey4, list4); // Create forth map entry with key <,B>. MapKey mapKey5 = new MapKey(); mapKey5.setKey1(""); mapKey5.setKey2("B"); List<Object> list5 = new ArrayList<Object>(); list5.add("List5 Entry"); put(mapKey5, list5); for (Map.Entry<MapKey, List<Object>> entry : mapWithTwoKeys.entrySet()) { System.out.println("MapKey Key: <" + entry.getKey().getKey1() + "," + entry.getKey().getKey2() + ">"); System.out.println("Value: " + entry.getValue()); System.out.println("---------------------------------------"); } } /** * Custom put method for the map. * @param mapKey2 (MapKey... the key object of the Map). * @param list (List of Object... the value of the Map). */ private static void put(MapKey mapKey2, List<Object> list) { if (mapWithTwoKeys.get(mapKey2) == null) { mapWithTwoKeys.put(mapKey2, new ArrayList<Object>()); } mapWithTwoKeys.get(mapKey2).add(list); } } 

The code is pretty straightforward. Let me know if this satisfies your requirements.

+1
source

I think the solution below will work for you - I used the MyKey.java object as the HashMap key. It contains both keys and a hash code. The hash code will be used to determine the list of values โ€‹โ€‹for the various key combinations listed in the question. This hash code is generated when you first register both keys. It is stored for each key, so even if any of the keys is null, you will get the same hash code.

MultiKeyMap.java => Extends the HashMap and overrides 'put' and 'get' method. populateHashKey () - This method will generate / return the same hash code for a different combination of the desired keys.

NOTE. Insertion order supported by Arraylist. Also, all values โ€‹โ€‹for each key combination will be saved in the same list on the map.

 package test.map; public class MyKey { private String myKey1; private String myKey2; private int hashKey; public MyKey(String key1, String key2) { this.myKey1 = key1; this.myKey2 = key2; } /** * @return the myKey1 */ public String getMyKey1() { return this.myKey1; } /** * @param tmpMyKey1 the myKey1 to set */ public void setMyKey1(String tmpMyKey1) { this.myKey1 = tmpMyKey1; } /** * @return the myKey2 */ public String getMyKey2() { return this.myKey2; } /** * @param tmpMyKey2 the myKey2 to set */ public void setMyKey2(String tmpMyKey2) { this.myKey2 = tmpMyKey2; } /** * Returns the hash key. */ @Override public int hashCode() { return this.hashKey; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; MyKey other = (MyKey) obj; if(checkEqual(this.myKey1, other.myKey1) || checkEqual(this.myKey2, other.myKey2)) { return true; } return false; } /* * Checks whether key1 equals key2. */ private boolean checkEqual(String key1, String key2) { if(key1 != null && key2 != null) { return key1.equals(key2); } return false; } /** * @return the hashKey */ public int getHashKey() { return this.hashKey; } /** * @param tmpHashKey the hashKey to set */ public void setHashKey(int tmpHashKey) { this.hashKey = tmpHashKey; } } 

MultiKeyMap.java -

  package test.map; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class MultiKeyMap extends HashMap<MyKey, List<String>>{ private static final long serialVersionUID = 3523468186955908397L; Map<String, Integer> hashKeyMap = new HashMap<String, Integer>(); /* * Adds single value in the List of values against the key */ public List<String> addValue(MyKey tmpKey, String tmpValue) { populateHashKey(tmpKey); List<String> orgValue = null; if(tmpKey.getHashKey() != -1) { orgValue = super.get(tmpKey); if(orgValue == null) { orgValue = new ArrayList<String>(); super.put(tmpKey, orgValue); } orgValue.add(tmpValue); } return orgValue; } @Override public List<String> put(MyKey tmpKey, List<String> tmpValue) { populateHashKey(tmpKey); List<String> orgValue = null; if(tmpKey.getHashKey() != -1) { orgValue = super.get(tmpKey); if(orgValue == null) { orgValue = new ArrayList<String>(); super.put(tmpKey, orgValue); } orgValue.addAll(tmpValue); } return orgValue; } @Override public List<String> get(Object tmpKey) { if(!(tmpKey instanceof MyKey)) { return null; } MyKey key = (MyKey) tmpKey; populateHashKey(key); return super.get(key); } /** * Populates the hashKey generated for the MyKey combination. If the both Key1 and Key 2 are not null and its hash key is not generated * earlier then it will generate the hash key using both keys and stores it in class level map 'hashKeyMap' against both keys. * @param tmpKey */ public void populateHashKey(MyKey tmpKey) { int hashKey = -1; if(tmpKey.getMyKey1() != null && this.hashKeyMap.containsKey(tmpKey.getMyKey1()+"_")) { hashKey = this.hashKeyMap.get(tmpKey.getMyKey1()+"_"); } else if(tmpKey.getMyKey2() != null && this.hashKeyMap.containsKey("_"+tmpKey.getMyKey2())) { hashKey = this.hashKeyMap.get("_"+tmpKey.getMyKey2()); } /* * Assumption - While insertion you will always add first value with Key1 and Key2 both as not null. Hash key will be build only * when both keys are not null and its not generated earlier. */ if(hashKey == -1 && tmpKey.getMyKey1() != null && tmpKey.getMyKey2() != null) { hashKey = buildHashKey(tmpKey); this.hashKeyMap.put(tmpKey.getMyKey1()+"_", hashKey); this.hashKeyMap.put("_"+tmpKey.getMyKey2(), hashKey); } tmpKey.setHashKey(hashKey); } public int buildHashKey(MyKey tmpKey) { final int prime = 31; int result = 1; result = prime * result + ((tmpKey.getMyKey1() == null) ? 0 : tmpKey.getMyKey1().hashCode()); result = prime * result + ((tmpKey.getMyKey1() == null) ? 0 : tmpKey.getMyKey1().hashCode()); return result; } } 

Testing Class -

 import test.map.MultiKeyMap; import test.map.MyKey; public class TestMultiKeyMap { public static void main(String[] args) { System.out.println("=====Add values for each type of key=========="); MyKey regKey = new MyKey("Key1", "Key2"); MultiKeyMap myMap = new MultiKeyMap(); //Register the MyKey having both keys as NOT null. System.out.println("Entry 1:"+myMap.addValue(regKey, "Key Reg")); MyKey key1 = new MyKey("Key1", null); //Add value against MyKey with only Key2 System.out.println("Entry 2:"+myMap.addValue(key1, "Key1")); MyKey key2 = new MyKey(null, "Key2"); //Add value against MyKey with only Key1 System.out.println("Entry 3:"+myMap.addValue(key2, "Key2")); MyKey bothKey = new MyKey("Key1", "Key2"); //Add value against MyKey with only Key1 System.out.println("Entry 4:"+myMap.addValue(bothKey, "both keys")); System.out.println("=====Retrieve values for each type of key=========="); MyKey getKey1 = new MyKey("Key1", null); System.out.println("Values for Key1:"+myMap.get(getKey1)); MyKey getKey2 = new MyKey(null, "Key2"); System.out.println("Values for Key2:"+myMap.get(getKey2)); MyKey getBothKey = new MyKey("Key1", "Key2"); System.out.println("Values for both keys:"+myMap.get(getBothKey)); } } 

Output -

 =====Add values for each type of key========== Entry 1:[Key Reg] Entry 2:[Key Reg, Key1] Entry 3:[Key Reg, Key1, Key2] Entry 4:[Key Reg, Key1, Key2, both keys] =====Retrieve values for each type of key========== Values for Key1:[Key Reg, Key1, Key2, both keys] Values for Key2:[Key Reg, Key1, Key2, both keys] Values for both keys:[Key Reg, Key1, Key2, both keys] 
+1
source

Check the hash code of only key1, if it matches, then we still check the equals method.

 @Override public int hashCode() { return key1.hashCode(); } @Override public boolean equals(Object obj) { Key k = (Key)obj; // Generic equals code goes here if(this.key1.equals(k.key1) && this.key2.equals(k.key2) ) return true; return false; } 
0
source

Basically, the idea to achieve this is to match the keys by value itself.

So you can have an internal card that does this (here I have a set of keys, not just 2).

 Map<V, Set<K>> keySetMap = new HashMap<V, Set<K>>(); 

So your Map implementation might look something like this:

 public class MultiKeyMap<K, V> extends LinkedHashMap<K, V> { private static final long serialVersionUID = 1L; private Map<V, Set<K>> keySetMap = new HashMap<V, Set<K>>(); @Override public V put(K key, V value) { V v = null; Set<K> keySet = keySetMap.get(value); if(keySet == null) { keySet = new LinkedHashSet<K>(); keySetMap.put(value, keySet); } keySet.add(key); v = super.put(key, value); // update the old keys to reference the new value Set<K> oldKeySet = keySetMap.get(v); if(oldKeySet != null) { for(K k : oldKeySet) { super.put(k, value); } } return v; } } 

This works great for simple (immutable) objects:

 @Test public void multiKeyMapString() { MultiKeyMap<String, String> m = new MultiKeyMap<String, String>(); m.put("1", "A"); m.put("2", "B"); for(Entry<String, String> e : m.entrySet()) { System.out.println("K=" + e.getKey() + ", V=" + e.getValue().toString()); } m.put("3", "A"); System.out.println("----"); for(Entry<String, String> e : m.entrySet()) { System.out.println("K=" + e.getKey() + ", V=" + e.getValue().toString()); } m.put("4", "C"); System.out.println("----"); for(Entry<String, String> e : m.entrySet()) { System.out.println("K=" + e.getKey() + ", V=" + e.getValue().toString()); } m.put("3", "D"); System.out.println("----"); for(Entry<String, String> e : m.entrySet()) { System.out.println("K=" + e.getKey() + ", V=" + e.getValue().toString()); } System.out.println("----"); System.out.println("values=" + m.values()); System.out.println(); System.out.println(); } 

with the above test, the result will look like

 K=1, V=A K=2, V=B ---- K=1, V=A K=2, V=B K=3, V=A ---- K=1, V=A K=2, V=B K=3, V=A K=4, V=C ---- K=1, V=D K=2, V=B K=3, V=D K=4, V=C ---- values=[D, B, C] 

As you can see in the latest release, the 1 key now displays the D value because the value previously displayed by the 3 symbol was the same as the one that was mapped to 1 in the previous step.

But it becomes difficult if you want to put a list (or any mutable object) on your map, because if you change the list (add / remove an element), then there will be another hashCode in the list, like the one used to match the previous keys:

 @Test public void multiKeyMapList() { List<String> l = new ArrayList<String>(); l.add("foo"); l.add("bar"); MultiKeyMap<String, List<String>> m = new MultiKeyMap<String, List<String>>(); m.put("1", l); m.put("2", l); for(Entry<String, List<String>> e : m.entrySet()) { System.out.println("K=" + e.getKey() + ", V=" + e.getValue().toString()); } m.get("1").add("foobar"); m.put("3", l); System.out.println("----"); for(Entry<String, List<String>> e : m.entrySet()) { System.out.println("K=" + e.getKey() + ", V=" + e.getValue().toString()); } l = new ArrayList<String>(); l.add("bla"); m.put("4", l); System.out.println("----"); for(Entry<String, List<String>> e : m.entrySet()) { System.out.println("K=" + e.getKey() + ", V=" + e.getValue().toString()); } m.put("3", l); System.out.println("----"); for(Entry<String, List<String>> e : m.entrySet()) { System.out.println("K=" + e.getKey() + ", V=" + e.getValue().toString()); } System.out.println("----"); System.out.println("values=" + m.values()); } 

In the above test, it will output something like this:

 K=1, V=[foo, bar] K=2, V=[foo, bar] ---- K=1, V=[foo, bar, foobar] K=2, V=[foo, bar, foobar] K=3, V=[foo, bar, foobar] ---- K=1, V=[foo, bar, foobar] K=2, V=[foo, bar, foobar] K=3, V=[foo, bar, foobar] K=4, V=[bla] ---- K=1, V=[foo, bar, foobar] K=2, V=[foo, bar, foobar] K=3, V=[bla] K=4, V=[bla] ---- values=[[foo, bar, foobar], [bla]] 

As you can see, the value displayed by 1 and 2 is not updated, after only the key 3 turned to display a different value. The reason is that hashCode resultng from [foo, bar] is different from [foo, bar, foobar] , which causes Map#get not return the correct result. To handle this, you need to get a set of keys compared to the actual value.

 public class MultiKeyMap<K, V> extends LinkedHashMap<K, V> { private static final long serialVersionUID = 1L; private Map<V, Set<K>> keySetMap = new HashMap<V, Set<K>>(); @Override public V put(K key, V value) { V v = null; Set<K> keySet = keySetMap.get(value); if (keySet == null) { keySet = new LinkedHashSet<K>(); keySetMap.put(value, keySet); } keySet.add(key); v = super.put(key, value); // update the old keys to reference the new value for (K k : getKeySetByValue(v)) { super.put(k, value); } return v; } @Override public Collection<V> values() { // distinct values return new LinkedHashSet<V>(super.values()); } private Set<K> getKeySetByValue(V v) { Set<K> set = null; if (v != null) { for (Map.Entry<V, Set<K>> e : keySetMap.entrySet()) { if (v.equals(e.getKey())) { set = e.getValue(); break; } } } return set == null ? Collections.<K> emptySet() : set; } } 

Now running both tests again gives the following result:

For simple (immutable) objects

 K=1, V=A K=2, V=B ---- K=1, V=A K=2, V=B K=3, V=A ---- K=1, V=A K=2, V=B K=3, V=A K=4, V=C ---- K=1, V=D K=2, V=B K=3, V=D K=4, V=C ---- values=[D, B, C] 

For an object that can change

 K=1, V=[foo, bar] K=2, V=[foo, bar] ---- K=1, V=[foo, bar, foobar] K=2, V=[foo, bar, foobar] K=3, V=[foo, bar, foobar] ---- K=1, V=[foo, bar, foobar] K=2, V=[foo, bar, foobar] K=3, V=[foo, bar, foobar] K=4, V=[bla] ---- K=1, V=[bla] K=2, V=[bla] K=3, V=[bla] K=4, V=[bla] ---- values=[[bla]] 

Hope this helps you find a way to implement your map. Instead of expanding your existing implementation, implement the Map interface so that you can provide implantation for all of your methods regarding your contracts and have an implementation of your choice as a member to handle the actual mapping.

0
source

Please use the excellent utility classes EqualsBuilder and HashCodeBuilder from the Apache Commons Lang library. Example:

 public class Person { private String name; private int age; // ... public int hashCode() { return new HashCodeBuilder(17, 31). // two randomly chosen prime numbers // if deriving: appendSuper(super.hashCode()). append(name). append(age). toHashCode(); } public boolean equals(Object obj) { if (obj == null) return false; if (obj == this) return true; if (!(obj instanceof Person)) return false; Person rhs = (Person) obj; return new EqualsBuilder(). // if deriving: appendSuper(super.equals(obj)). append(name, rhs.name). append(age, rhs.age). isEquals(); } } 

A source

0
source

Create another Map that contains the key relationship-> intermediateKey. The intermediate key may be a GUID or something else that is automatically generated and guaranteed to be unique.

  Map<String, GUID> first = new HashMap<String, GUID>(); first.put(key1, guid1); first.put(key2, guid1); Map<GUID, ValueType> second = new HashMap<GUID, ValueType>(); second.put(guid1, value1); 

Alternatively (although I find it much more complex and less flexible), you can play with the keys. If key1.equals(key2) (and therefore key2.equals(key1) && (key1.hashCode () == key2.hashCode ) then Map.get (key1) will return the same value than Map.get (key2) `.

-1
source

Source: https://habr.com/ru/post/955238/


All Articles