How to use an Object array as a key in hashmap - java

I have an object array called Position[] that returns an array of Position objects. I would like to use this as my key for a hashmap for the following: HashMap<Position[],Double> h = new HashMap<>();
I understand that arrays have different hashcodes even if the elements are the same. So I went ahead and tried to override the equals and hashcode. This was my attempt:
public class Key {
private Position p1;
private Position p2;
public Key(Position p1, Position p2){
this.p1 = p1;
this.p2 = p2
}
#Override
public boolean equals(Object object) {
if (!(object instanceof Key)) {
return false;
}
Key newKey = (Key) object;
return this.hashCode()== newKey.hashCode(); //bit of a hack way
}
#Override
public int hashCode(){
int result = 17;
result = 31 * result + this.p1.hashCode();
result = 31 * result + this.p2.hashCode();
return result;
}
}
So I had to change my map to HashMap<Key,Double> However, when ever i go to get the value using the key is still returns null.
An example of what can be passed into they constructor of Key are G2 G4 or E4 E6 ETC.
How would I go about achieving this so that the comparisons actually work?
Thanks.

You can create a special wrapper object to use Position[] as a key in a Map, using Arrays.deepEquals(Object[], Object[]) and Arrays.deepHashCode(Object[]) in the implementations of equals(Object) and hashCode().
import java.util.Arrays;
public final class PositionArrayKey {
private final Position[] array;
public PositionArrayKey(Position[] array) {
this.array = array;
}
#Override
public boolean equals(Object object) {
if (object == this) return true;
if (!(object instanceof PositionArrayKey)) return false;
return Arrays.deepEquals(this.array, ((PositionArrayKey) object).array);
}
#Override
public int hashCode() {
return Arrays.deepHashCode(this.array);
}
}
This enables storing Position[] array instances as keys in map, when wrapped. E.g.
Map<PositionArrayKey, Object> map = new HashMap<>();
map.put(new PositionArrayKey(new Position[]{...}), ...);
Object value = map.get(new PositionArrayKey(new Position[]{...}), ...);
(assuming that both of the Position[] arrays are deeply equal in this example)
Note that for large arrays, performance for invoking equals(Object) and hashCode() may be slow. You can modify the above snippet to cache the result Arrays.deepHashCode(this.array) for larger arrays, if you find it necessary.

Related

Java TreeMap put vs HashMap put, custom Object as key

My objective was to use the TreeMap to make Box key objects sorted by Box.volume attribute while able to put keys distinct by the Box.code. Is it not possible in TreeMap?
As per below test 1, HashMap put works as expected, HashMap keeps both A, B key objects, but in test 2, TreeMap put doesn't treat D as a distinct key, it replaces C's value, note that i used the TreeMap comparator as Box.volume, because i want keys to be sorted by volume in TreeMap.
import java.util.*;
public class MapExample {
public static void main(String[] args) {
//test 1
Box b1 = new Box("A");
Box b2 = new Box("B");
Map<Box, String> hashMap = new HashMap<>();
hashMap.put(b1, "test1");
hashMap.put(b2, "test2");
hashMap.entrySet().stream().forEach(o-> System.out.println(o.getKey().code+":"+o.getValue()));
//output
A:test1
B:test2
//test 2
Box b3 = new Box("C");
Box b4 = new Box("D");
Map<Box, String> treeMap = new TreeMap<>((a,b)-> Integer.compare(a.volume, b.volume));
treeMap.put(b3, "test3");
treeMap.put(b4, "test4");
treeMap.entrySet().stream().forEach(o-> System.out.println(o.getKey().code+":"+o.getValue()));
//output
C:test4
}
}
class Box {
String code;
int volume;
public Box(String code) {
this.code = code;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Box box = (Box) o;
return code.equals(box.code);
}
#Override
public int hashCode() {
return Objects.hash(code);
}
}
Thank you
TreeMap considers 2 keys for which the comparison method returns 0 to be identical, even if they are not equal to each other, so your current TreeMap cannot contain two keys with the same volume.
If you want to keep the ordering by volume and still have multiple keys with the same volume in your Map, change your Comparator's comparison method to compare the Box's codes when the volumes are equal. This way it will only return 0 if the keys are equal.
Map<Box, String> treeMap = new TreeMap<>((a,b)-> a.volume != b.volume ? Integer.compare(a.volume, b.volume) : a.code.compareTo(b.code));
Now the output is:
C:test3
D:test4
b3 and b4 has the same volume, that is 0 (int default value).
For it work, assign a value to the Box volume variables before comparing.

How to check if an object exists in a LinkedList in java?

A LinkedList contains a set of Integer[]. Each Integer[] in the list has 2 numbers.
Ex of the linked list:
Integer[]{1,2}, Integer[]{2,3}.....
Before adding another Integer[] to this LinkedList, I wanto check if another Integer[] with the same data already exists.
Ex: Object to add = Integer[] {2,3}. But this already exists in the LinkedList.
So I want to avoid adding this.
How to verify that object already exists? Is there an inbuild function that can be used? contains() didnt do the trick.
I think you better use a specific class if you are treating coordinates, as an Integer[] is useless for only two numbers, and will cause some problems with contains() and other List methods like .sort() as well.
You better create a Coordinate class, which will hold the two values:
public class Coordinate{
private int x;
private int y;
//getters and setters, constructor
#Override
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof Coord)) {
return false;
}
Coordinate coord = (Coordinate) o;
return coord.x == x &&
coord.y == y;
}
#Override
public int hashCode() {
int result = 17;
result = 31 * result + x;
result = 31 * result + y;
return result;
}
}
Then you can use:
LinkedList<Coordinate>
Note:
Note that using a Set implementation will be better here, it will prevent having duplicates in the set of coordinates, so we don't need to check for it manually.
Well, you can do it the dumb way:
boolean exists = false;
for (Integer[] integers : list) { // list being the LinkedList
if (Arrays.equals(integers, value)) {
exists = true;
break;
}
}
if (!exists) {
list.add(value);
}
You can use Stream with Set to solve your problem like below:
List<Set<Integer>> list = new LinkedList<>();
list.add(Stream.of(1, 2).collect(Collectors.toSet()));
Set<Integer> s1 = new HashSet<>();
s1.add(1);
s1.add(2);
System.out.println(list.contains(s1));
Set<Integer> s2 = new HashSet<>();
s2.add(1);
s2.add(4);
System.out.println(list.contains(s2));
O/P:
true
false
N.B: You can use ArrayList because yo preserve the sequence as well.
If you really really want to do that with contains() (or have no other choice by whatever reason), you can implement it like that:
final Integer[] newPair = {2, 3};
final boolean exists = values.contains(new Object()
{
// note that List.contains() javadoc explicitly specifies that
// newPair is used as the receiver not the argument for equals()
#Override
public final boolean equals(final Object listElement)
{
final Integer[] otherPair = (Integer[]) listElement;
return Arrays.equals(newPair, otherPair);
}
});

Multiple key on HashMap: it deletes existing values?

I have implemented my multiple key class as follows:
public class ProbabilityIndex {
private int trueLabel;
private int classifiedLabel;
private int classifierIndex;
public ProbabilityIndex(int trueLabel, int classifiedLabel, int classifierIndex) {
this.trueLabel = trueLabel;
this.classifiedLabel = classifiedLabel;
this.classifierIndex = classifierIndex;
}
#Override
public boolean equals(Object obj) {
if ( !obj instanceof ProbabilityIndex)
return false;
if (obj == this)
return true;
ProbabilityIndex rhs = (ProbabilityIndex) obj;
return new EqualsBuilder().
append(trueLabel, rhs.trueLabel).
append(classifiedLabel, rhs.classifiedLabel).
append(classifierIndex, rhs.classifierIndex).
isEquals();
}
#Override
public int hashCode() {
int hashCode = new HashCodeBuilder(17, 31).
append(trueLabel).
append(classifiedLabel).
append(classifierIndex).
toHashCode();
return hashCode;
}
}
Notice that trueLabel, classifiedLabel and classifierIndex are all either 0 or 1.
Then, I use my key as follows:
ProbabilityIndex key = new ProbabilityIndex(trueLabel, classifiedLabel, classifierIndex);
probabilities.put(key, new Double(value));
where probabilities is declared as follows:
HashMap<ProbabilityIndex, Double> probabilities;
However, different combinations of trueLabel, classifiedLabel and classifierIndex write the tuple in the same position in probabilities, thus overwriting existing tuples.
How can I overcome this issue?
Minimal test case:
HashMap<ProbabilityIndex, Double> map = new HashMap<ProbabilityIndex, Double>();
map.put(new ProbabilityIndex(0, 0, 0), new Double(0.1));
map.put(new ProbabilityIndex(0, 0, 1), new Double(0.2));
map.put(new ProbabilityIndex(0, 1, 0), new Double(0.1));
map.put(new ProbabilityIndex(0, 1, 1), new Double(0.2));
map.put(new ProbabilityIndex(1, 0, 0), new Double(0.1));
This inserts 4 tuples instead of 5.
I can only tell you that the hashtable will never overwrite objects with the same hash code (a hash collision); it will just be less efficient in their retrieval.
The only way to have your entries incorrectly overwritten is by providing an equals method for the key which returns true for different keys.
A bit of further advice not directly related to your problem: if all you have is three two-state variables, then the complete value set for the class has cardinality of just 8. Instead of the complicated hash code builder you use, you could just construct the hash code with three bits, each representing one variable. That would plainly ensure that each state of your object has a distinct hash code.
I have verified your code with the following implementations of hashCode() and equals() (I had to change equals to make your example truly self-contained):
#Override public boolean equals(Object obj) {
if (!(obj instanceof ProbabilityIndex)) return false;
if (obj == this) return true;
ProbabilityIndex rhs = (ProbabilityIndex) obj;
return this.trueLabel == rhs.trueLabel
&& this.classifiedLabel == rhs.classifiedLabel
&& this.classifierIndex == rhs.classifierIndex;
}
#Override public int hashCode() {
return trueLabel | (classifiedLabel << 1) | (classifierIndex << 2);
}
Your test code resulted in a map with five entries.
As a final note, you don't even need a hashtable if its maximum size will be only 8. A plain array of size 8, indexed by the above hash code, would be enough.

Why remove element of Map?

I am filtering the all list which ahve same lat,long in one list and put into an same list and put that list into map My code is as:-
private Collection<List<Applicationdataset>> groupTheList(ArrayList<Applicationdataset> arrayList)
{
Map<Key, List<Applicationdataset>> map = new HashMap<Key, List<Applicationdataset>>();
for(Applicationdataset appSet: arrayList)
{
Key key = new Key(appSet.getLatitude(), appSet.getLongitude());
List<Applicationdataset> list = map.get(key);
if(list == null){
list = new ArrayList<Applicationdataset>();
}
list.add(appSet);
map.put(key, list);
}
return map.values();
}
public class Key {
String _lat;
String _lon;
Key(String lat, String lon) {
_lat = lat;
_lon = lon;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Key key = (Key) o;
if (!_lat.equals(key._lat)) return false;
if (!_lon.equals(key._lon)) return false;
return true;
}
#Override
public int hashCode() {
int result = _lat.hashCode();
result = 31 * result + _lon.hashCode();
return result;
}
}
But When I am debuging my code according to xml which come from web-service there is 2 list which have same lat long and they are saving in same list in amp at time of debuging but when I go next step of debug the element of map which have 2 item list decrease and showing size 1 I am unable to rectify this issue.
Your code looks OK: You've overridden equals() and hashCode() consistently.
Check for whitespace in the lat/lng values as the cause of your problems, perhaps trim() in the constructor:
Key(String lat, String lon) {
_lat = lat.trim();
_lon = lon.trim();
}
Also, you can simplify your code to this:
#Override
public boolean equals(Object o) {
return o instanceof Key
&& _lat.equals(((Key)o)._lat))
&& _lon.equals(((Key)o)._lon));
}
#Override
public int hashCode() {
// String.hashCode() is sufficiently good for this addition to be acceptable
return _lat.hashCode() + _lon.hashCode();
}
Thats a bit hard to understand what you are trying to accomplish. But I believe the issue is that you are using both latitude and longitude in Key hashCode()/equals() implementation thats why second Applicationdataset in your input list replaces the first one in your map object. You should implement the case when related list was already put into map and do not replace it.

ArrayList as key in HashMap

Would it be possible to add an ArrayList as the key of HashMap. I would like to keep the frequency count of bigrams. The bigram is the key and the value is its frequency.
For each of the bigrams like "he is", I create an ArrayList for it and insert it into the HashMap. But I am not getting the correct output.
public HashMap<ArrayList<String>, Integer> getBigramMap(String word1, String word2) {
HashMap<ArrayList<String>, Integer> hm = new HashMap<ArrayList<String>, Integer>();
ArrayList<String> arrList1 = new ArrayList<String>();
arrList1 = getBigram(word1, word2);
if (hm.get(arrList1) != null) {
hm.put(arrList1, hm.get(arrList1) + 1);
} else {
hm.put(arrList1, 1);
}
System.out.println(hm.get(arrList1));
return hm;
}
public ArrayList<String> getBigram(String word1, String word2) {
ArrayList<String> arrList2 = new ArrayList<String>();
arrList2.add(word1);
arrList2.add(word2);
return arrList2;
}
Yes you can have ArrayLists as a keys in a hash map, but it is a very bad idea since they are mutable.
If you change the ArrayList in any way (or any of its elements), the mapping will basically be lost, since the key won't have the same hashCode as it had when it was inserted.
The rule of thumb is to use only immutable data types as keys in a hash map. As suggested by Alex Stybaev, you probably want to create a Bigram class like this:
final class Bigram {
private final String word1, word2;
public Bigram(String word1, String word2) {
this.word1 = word1;
this.word2 = word2;
}
public String getWord1() {
return word1;
}
public String getWord2() {
return word2;
}
#Override
public int hashCode() {
return word1.hashCode() ^ word2.hashCode();
}
#Override
public boolean equals(Object obj) {
return (obj instanceof Bigram) && ((Bigram) obj).word1.equals(word1)
&& ((Bigram) obj).word2.equals(word2);
}
}
Why can't you use something like this:
class Bigram{
private String firstItem;
private String secondItem;
<getters/setters>
#Override
public int hashCode(){
...
}
#Override
public boolean equals(){
...
}
}
instead of using the dynamic collection for limited number of items (two).
From the documentation:
Note: great care must be exercised if mutable objects are used as map
keys. The behavior of a map is not specified if the value of an object is
changed in a manner that affects equals comparisons while the
object is a key in the map. A special case of this prohibition is that it
is not permissible for a map to contain itself as a key. While it is
permissible for a map to contain itself as a value, extreme caution is
advised: the equals and hashCode methods are no longer
well defined on such a map.
You have to take care when you are using mutable objects as keys for the sake of hashCode and equals.
The bottom line is that it is better to use immutable objects as keys.
Try this ,this will work.
public Map<List, Integer> getBigramMap (String word1,String word2){
Map<List,Integer> hm = new HashMap<List, Integer>();
List<String> arrList1 = new ArrayList<String>();
arrList1 = getBigram(word1, word2);
if(hm.get(arrList1) !=null){
hm.put(arrList1, hm.get(arrList1)+1);
}
else {
hm.put(arrList1, 1);
}
System.out.println(hm.get(arrList1));
return hm;
}
I've come up with this solution. It is obviously not usable in all cases, for example over stepping the hashcodes int capacity, or list.clone() complications(if the input list gets changed, key stays the same as intended, but when the items of List are mutable, cloned list has the same reference to its items, which would result in changing the key itself).
import java.util.ArrayList;
public class ListKey<T> {
private ArrayList<T> list;
public ListKey(ArrayList<T> list) {
this.list = (ArrayList<T>) list.clone();
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
for (int i = 0; i < this.list.size(); i++) {
T item = this.list.get(i);
result = prime * result + ((item == null) ? 0 : item.hashCode());
}
return result;
}
#Override
public boolean equals(Object obj) {
return this.list.equals(obj);
}
}
---------
public static void main(String[] args) {
ArrayList<Float> createFloatList = createFloatList();
ArrayList<Float> createFloatList2 = createFloatList();
Hashtable<ListKey<Float>, String> table = new Hashtable<>();
table.put(new ListKey(createFloatList2), "IT WORKS!");
System.out.println(table.get(createFloatList2));
createFloatList2.add(1f);
System.out.println(table.get(createFloatList2));
createFloatList2.remove(3);
System.out.println(table.get(createFloatList2));
}
public static ArrayList<Float> createFloatList() {
ArrayList<Float> floatee = new ArrayList<>();
floatee.add(34.234f);
floatee.add(new Float(33));
floatee.add(null);
return floatee;
}
Output:
IT WORKS!
null
IT WORKS!
Sure it possible. I suppose the issue in your put. Try obtain key for bigram, increment it, remove entry with this bigram and insert updated value
Unlike Array, List can be used as the key of a HashMap, but it is not a good idea, since we should always try to use an immutable object as the key.
.toString() method getting the String represtenation is a good key choice in many cases, since String is an immuteable object and can prefectly stands for the array or list.
Please check below my code in order to understand if key is ArrayList in Map and how JVM will do it for inputs:
here i write hashCode and equals method for TesthashCodeEquals class.
package com.msq;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class TesthashCodeEquals {
private int a;
private int b;
public TesthashCodeEquals() {
// TODO Auto-generated constructor stub
}
public TesthashCodeEquals(int a, int b) {
super();
this.a = a;
this.b = b;
}
public int getA() {
return a;
}
public void setA(int a) {
this.a = a;
}
public int getB() {
return b;
}
public void setB(int b) {
this.b = b;
}
public int hashCode() {
return this.a + this.b;
}
public boolean equals(Object o) {
if (o instanceof TesthashCodeEquals && o != null) {
TesthashCodeEquals c = (TesthashCodeEquals) o;
return ((this.a == c.a) && (this.b == c.b));
} else
return false;
}
}
public class HasCodeEquals {
public static void main(String[] args) {
Map<List<TesthashCodeEquals>, String> m = new HashMap<>();
List<TesthashCodeEquals> list1=new ArrayList<>();
list1.add(new TesthashCodeEquals(1, 2));
list1.add(new TesthashCodeEquals(3, 4));
List<TesthashCodeEquals> list2=new ArrayList<>();
list2.add(new TesthashCodeEquals(10, 20));
list2.add(new TesthashCodeEquals(30, 40));
List<TesthashCodeEquals> list3=new ArrayList<>();
list3.add(new TesthashCodeEquals(1, 2));
list3.add(new TesthashCodeEquals(3, 4));
m.put(list1, "List1");
m.put(list2, "List2");
m.put(list3, "List3");
for(Map.Entry<List<TesthashCodeEquals>,String> entry:m.entrySet()){
for(TesthashCodeEquals t:entry.getKey()){
System.out.print("value of a: "+t.getA()+", value of b: "+t.getB()+", map value is:"+entry.getValue() );
System.out.println();
}
System.out.println("######################");
}
}
}
.
output:
value of a: 10, value of b: 20, map value is:List2
value of a: 30, value of b: 40, map value is:List2
######################
value of a: 1, value of b: 2, map value is:List3
value of a: 3, value of b: 4, map value is:List3
######################
so this will check the number of objects in List and the values of valriabe in object. if number of objects are same and the values of instance variables is also same then it will consider duplicate key and override the key.
now if i change only the value of object on list3
list3.add(new TesthashCodeEquals(2, 2));
then it will print:
output
value of a: 2, value of b: 2, map value is:List3
value of a: 3, value of b: 4, map value is:List3
######################
value of a: 10, value of b: 20, map value is:List2
value of a: 30, value of b: 40, map value is:List2
######################
value of a: 1, value of b: 2, map value is:List1
value of a: 3, value of b: 4, map value is:List1
######################
so that It always check the number of objects in List and the value of instance variable of object.
thanks
ArrayList.equals() is inherited from java.lang.Object - therefore equals() on ArrayList is independent of the content of the list.
If you want to use an ArrayList as a map key, you will need to override equals() and hashcode() in order to make two arraylists with the same content in the same order return true on a call to equals() and return the same hashcode on a call to hashcode().
Is there any particular reason you have to use an ArrayList as opposed to say a simple String as the key?
edit: Ignore me, as Joachim Sauer pointed out below, I am so wrong it's not even funny.

Categories

Resources