custom HashMap-Key (Java) slows down performance extremely - java

this is my first question here, but i hope i will provide all the needed information.
If NOT, please let me know!
My Problem:
I tried to improve my backtracking-algorithm by adding a HashMap to store the already processed results. For this i created a own class for the key of that HashMap. In there i overwrote the .equals()- and .hashCode()- methods.
But if i try to put this key and it's value into the map, it is taking much time, so that the algorithm becomes even less efficient then the backtrack-algorithm without the map.
To solve that problem, i changed the HashMap-Key to String and addded a .toString()-method to my key-class. This works very fine and it is quite fast. (Strange thing: .toString().hashCode() produces a lot of negative values, but seems to work)
Now my Question:
Is it always slowing down that much, if you create your own key?
I tried to find a answer to that question on my own and the only thin i found was to change .hashCode() or playing with the parameters of the HashMap-Constructor.
I tried both and i exported the produced HashCodes for my test-environment and i did not find any duplicates, though i know, it isn't a "good" method for hash-codes!
Here is a copy of my HashKey-Class (names of variables and methods changed):
public class HashKey {
private final int int0, int1, int2;
public HashKey(int int0, int int1, int int2) {
this.int0 = int0;
this.int1 = int1;
this.int2 = int2;
}
public int getInt0() {
return this.int0;
}
public int getInt1() {
return this.int1;
}
public int getInt2() {
return this.int2;
}
#Override
public int hashCode() {
final int prime1 = 107;
final int prime2 = 227;
final int prime3 = 499;
int result = 1;
result = prime1 * result + this.int2;
result = prime2 * result + this.int1;
result = prime3 * result + this.int0;
return result;
}
#Override
public String toString() {
return "Int0: " + this.int0 + " Int1: " + int1 + " Int2: " + int2;
}
#Override
public boolean equals(Object obj) {
if (obj instanceof HashKey) {
boolean eq0, eq1, eq2;
eq0 = this.int0 == ((HashKey) obj).getInt0();
eq1 = this.int1 == ((HashKey) obj).getInt1();
eq2 = this.int2 == ((HashKey) obj).getInt2();
if (eq0 && eq1 && eq2) {
return true;
}
}
return false;
}
}
And in my main-Class i use this:
HashMap<HashKey, List<Object>> storedResults = new HashMap<HashKey, List<Object>>();
int x1,x2,x3;
Object obj;
// later in a method:
storedResults.put(new HashKey(x1,x2,x3), obj);
If i change the Type of the Key to String and put that String into the Map, it works fine! So the HashKey.hashCode()-method and the rest of the algorithm works fine and is quite fast.
Does anybody know, what i can do to use this HashKey? For this algorithm it is not that important, but i want to know it for future algorithms!
If there are any questions or critics: they are VERY welcome!
Thanks in advance!
Klumbe

Try this:
Simplyfy your equals(..)-method and do not calculate the hashCode more than once.
public final class HashKey {
private final int int0, int1, int2;
private final int hashCode;
public HashKey(int int0, int int1, int int2) {
this.int0 = int0;
this.int1 = int1;
this.int2 = int2;
hashCode=107*int0+227*int1+499*int2;
}
#Override
public final int hashCode() {
return hashCode;
}
#Override
public final boolean equals( finalObject obj) {
if (!obj instanceof HashKey)
retun false;
HashKey other = (HashKey)obj;
return int0 == other.int0 && int1 == other.int1 && int2 == other.int2;
}
}
Referring to the comment of fge I changed the code.

Your class is nearly immutable: all your instance members are final, just the class itself would also need to be.
But as all your instance members are final, you could as well calculate the hash code at build time:
// Add as member:
private final int hashCode;
public HashKey(int int0, int int1, int int2) {
this.int0 = int0;
this.int1 = int1;
this.int2 = int2;
hashCode = // calculate hash code here
}
public int hashCode()
{
return hashCode;
}
By the way, negative hash codes are nothing to worry about. .hashCode() returns an int, after all.

Related

What's the proper collection for indexing with several objects?

Suppose I have this array:
double[][] Q = new double[n1][n2];
I could index the values of Q by using int indexes, such as Q[2][1]. But in my case, n1 is a byte[], not an int. I still know the possible values of n1 (e.g, all possible combinations of the array values). What collection should I use instead of an array?
HashMap<byte[], Double>[] Q = new HashMap[n2];
This was my solution, but I'm not sure it is adequate. To index, I can do
byte[] n1 = {1,0,6,1,4,2,5,1};
Q[1].get(n1);
Is there a better way to do this? Something that is more performant? I think having an array of HashMaps is not ideal, but can I add that int to my key? How?
As stated in the comments,
The problem with using an array as a hashmap key (aside from the fact that it's mutable) is that arrays don't calculate their hashcode based on their contents; so you couldn't actually look up a value in the map unless you have the actual key instance.
So how do I use an array as an indexing key? A stupid solution would be to always convert it to String before using it, but I'm sure there are better and proper solutions.
I think the best way to go for your problem is to define a complex key class, which basically consists of your byte[] and int, write reliable hashCode and equals-Methods for this class, and use it as a key to the HashMap.
This way, you can use the runtime and memory efficiency of a hashmap and encapsulate the complexity of the key in a separate class.
Code to illustrate:
public static class MyKey {
private int i;
private byte[] b;
private MyKey(int i, byte[] b) {
this.i = i;
this.b = b;
}
public static MyKey of(int i, byte ... b) {
return new MyKey(i, b);
}
#Override
public int hashCode() { // Autogenerated from eclipse
final int prime = 31;
int result = 1;
result = prime * result + Arrays.hashCode(b);
result = prime * result + i;
return result;
}
#Override
public boolean equals(Object obj) { // Autogenerated from eclipse
if(this == obj)
return true;
if(obj == null)
return false;
if(getClass() != obj.getClass())
return false;
MyKey other = (MyKey) obj;
if(!Arrays.equals(b, other.b))
return false;
if(i != other.i)
return false;
return true;
}
}
public static void main(String[] args) {
HashMap<MyKey, Double> valueMap = new HashMap<>();
valueMap.put(MyKey.of(1, (byte)2, (byte)4, (byte)7), 0.1);
valueMap.put(MyKey.of(1, new byte[] { 3, 8, 14 }), 0.1);
}

Java map value comparisons

This could be the entirely wrong usage for the datatype, but I'm trying to create a Map-Set of some sort, so before insertion into the map I'm trying to use
//Material is custom class
Map<Integer, Material> x = TreeMap();
if(!x.containsValue(new Material))
x.put(int val, new Material);
My expectation of this is that it would compare two objects generated from the same data would return true, but it appears that this is not the case. Why is that, and is there an existing solution other than iterating through the entire map to find out if any element contains a Material where all fields are the same as the incoming?
Having a well defined class Material
public class Material extends Comparable<Material> {
final int a;
final String b;
...
public Material(int a, String b, ...) {
this.a = a;
this.b = b;
}
#Override
int compareTo(Material other) {
int cmp = Integer.compare(a, other.a);
if (cmp == 0) {
cmp = b.compareTo(other.b);
}
...
return cmp;
}
#Override
public boolean equals(Object other) {
if (other == null || !(other instanceof Material)) {
return false;
}
return compareTo((Material) other) == 0;
}
#Override int hashCode() {
return ...;
}
A Set suffices:
Set<Material> materials = new HashSet<>(); // hashCode+equals
Set<Material> materials = new TreeSet<>(); // compareTo
Now I have made the fields of Material final so the object is immutable, as changing would play havoc with the sets ordering.
For a mutable quality, like quantity:
Map<Material, Integer> stock = new HashMap<>();
Material material = new Material(...);
stock.put(material, 100);
int quantity = stock.getOrDefault(material, 0); // Java 8
int quantity = 0; // Java 7
{
Integer q = stock.get(material);
if (q != null) {
quantity = q;
}
}

How to compare both strings and float in compareTo

I am trying to compare both strings and float in the compareTo method but I'm not sure what my final value is going to be.
Below is the compareTo method that I have implemented so far:
ObjectClass otherObj = (ObjectClass)o;
float f1 = this.getValue();
float f2 = otherObj.getValue();
int retValue = Float.compare(f1,f2);
String code1 = this.getCode();
String code2 = otherObj.getCode();
int retValue2 = code1.compareTo(code2);
int finalRet = ??
return finalRet;
if the input is
hashMap.put(new ObjectClass ("20030122", 0.019f), "20030122");
hashMap.put(new ObjectClass ("20030123", 0.019f), "20030123");
hashMap.put(new ObjectClass ("20030124", 0.011f), "20030124");
my output should be in this order
"20030123", 0.019f
"20030122", 0.019f
"20030124", 0.011f
In order to allow your class to be comparable you must implement in it interface Comparable
When the comparison should be based on more then single class member. You compare it sequentially when result of previous was equal to zero. By sequence order you specify the final ordering.
class MyObject implements Comparable<MyObject> {
String message;
long value;
#Override
public int compareTo(MyObject that) {
if(that == null) {
return -1;
}
if(this == that) {
return 0;
}
int result = this.message.compareTo(that.message);
if(result == 0) {
result = Long.compare(this.value,that.value);
}
return result;
}
}
The above example will result with
"20030122", 0.019f
"20030123", 0.019f
"20030124", 0.011f

Interesting HashMap implementation (build 1.7.0_25-b17)

I have developed a garbage collector friendly String cache for my Android game. Its purpose is to handle Strings for ints. I made a silly mistake implementing it but the bug never disclosed itself in desktop. In Android, however, the cache started returning funny Strings at once:
class IntStringCache {
private final Map<IntStringCache.IntCacheKey, String> cachedStrings = new HashMap<IntStringCache.IntCacheKey, String>();
private final IntCacheKey tempIntCacheKey = new IntCacheKey(0);
public String getStringFor(int i) {
tempIntCacheKey.setIntValue(i);
String stringValue = cachedStrings.get(tempIntCacheKey);
if (stringValue == null) {
stringValue = String.valueOf(i);
// ERROR - putting the same object instead of new IntCachKey(i)
cachedStrings.put(tempIntCacheKey, stringValue);
}
return stringValue;
}
public int getSize() {
return cachedStrings.size();
}
private class IntCacheKey {
private int intValue;
private IntCacheKey(int intValue) {
this.intValue = intValue;
}
private void setIntValue(int intValue) {
this.intValue = intValue;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + getOuterType().hashCode();
result = prime * result + intValue;
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
IntCacheKey other = (IntCacheKey) obj;
if (!getOuterType().equals(other.getOuterType()))
return false;
if (intValue != other.intValue)
return false;
return true;
}
private IntStringCache getOuterType() {
return IntStringCache.this;
}
}
And the tests all of which pass:
public class IntStringCacheTest {
private IntStringCache intStringCache = new IntStringCache();
#Test
public void shouldCacheString() {
// given
int i = 1;
// when
String s1 = intStringCache.getStringFor(i);
String s2 = intStringCache.getStringFor(i);
// then
assertThat(s1).isNotNull();
assertThat(s1).isEqualTo(String.valueOf(i));
assertThat(s1).isSameAs(s2);
}
#Test
public void shouldCacheTwoValues() {
// given
int i1 = 1;
int i2 = 2;
int expectedCacheSize = 2;
// when
String s1 = intStringCache.getStringFor(i1);
String s2 = intStringCache.getStringFor(i2);
// then
assertThat(intStringCache.getSize()).isEqualTo(expectedCacheSize);
assertThat(s1).isSameAs(intStringCache.getStringFor(i1));
assertThat(s2).isSameAs(intStringCache.getStringFor(i2));
}
}
Note:
assertThat(String.valueOf(1)).isSameAs(String.valueOf(1));
fails.
The fact that the second test passes is interesting as, with the bug, there should be one key in the map that gets updated. This may be explained with hashCode() that could make the same key go into two different buckets inside HashMap. But how is it possible that the same key (even if in two buckets) returns the same two Stings? It seems that even though there is a bug in the code the HashMap does the job correctly.
My Android Java implementation, on the other hand, returns wrong number Strings with this bug at once.
You should consider replacing this entire class with SparseArray or its Support Library equivalent SparseArrayCompat (if you need it on <3.0 devices) as they are specifically designed to map integers to objects in a memory efficient way.

compareTo() method java is acting weird

hi im having trouble getting this to work im getting an error here with my object comparison...how could I cast the inches to a string ( i never used compare to with anything other than strings) , or use comparison operators to compare the intigers,
Object comparison = this.inches.compareTo(obj.inches);
here is my code so far
import java.io.*;
import java.util.*;
import java.lang.Integer;
import java.lang.reflect.Array;
public class Distance implements Comparable<Distance> {
private static final String HashCodeUtil = null;
private int feet;
private int inches;
private final int DEFAULT_FT = 1;
private final int DEFAULT_IN = 1;
public Distance(){
feet = DEFAULT_FT;
inches = DEFAULT_IN;
}
public Distance(int ft, int in){
feet = ft;
inches = in;
}
public void setFeet(int ft){
try {
if(ft<0){
throw new CustomException("Distance is not negative");
}
}
catch(CustomException c){
System.err.println(c);
feet =ft;
}
}
public int getFeet(){
return feet;
}
public void setInches(int in){
try
{
if (in<0)
throw new CustomException("Distance is not negative");
//inches = in;
}
catch(CustomException c)
{
System.err.println(c);
inches = in;
}
}
public int getInches(){
return inches;
}
public String toString (){
return "<" + feet + ":" + inches + ">";
}
public Distance add(Distance m){
Distance n = new Distance();
n.inches = this.inches + m.inches;
n.feet = this.feet + m.feet;
while(n.inches>12){
n.inches = n.inches - 12;
n.feet++;
}
return n;
}
public Distance subtract(Distance f){
Distance m = new Distance();
m.inches = this.inches - f.inches;
m.feet = this.feet - f.feet;
while(m.inches<0){
m.inches = m.inches - 12;
feet--;
}
return m;
}
#Override
public int compareTo(Distance obj) {
// TODO Auto-generated method stub
final int BEFORE = -1;
final int EQUAL = 0;
final int AFTER = 1;
if (this == obj) return EQUAL;
if(this.DEFAULT_IN < obj.DEFAULT_FT) return BEFORE;
if(this.DEFAULT_IN > obj.DEFAULT_FT) return AFTER;
Object comparison = this.inches.compareTo(obj.inches);
if (this.inches == obj.inches) return compareTo(null);
assert this.equals(obj) : "compareTo inconsistent with equals";
return EQUAL;
}
#Override public boolean equals( Object obj){
if (obj != null) return false;
if (!(obj intanceof Distance)) return false;
Distance that = (Distance)obj;
( this.feet == that.feet &&
this.inches == that.inches);
return true;
else
return false;
}
#Override public int hashCode(int, int) {
int result = HashCodeUtil.inches;
result = HashCodeUtil.hash(result, inches );
result = HashCodeUtil.hash(result, feet);
ruturn result;
}
You're comparing object references. Try to compare object value; either override hashCode() or compare field values.
#Override
public int compareTo(Distance obj) {
....
if (this == obj) return EQUAL; <--- This
...
}
With this line:
Object comparison = this.inches.compareTo(obj.inches);
you are trying to dereference an int, a primitive type. The compiler should be giving you an error: you can only dereference Objects using the dot .
I'm not sure what you want this compareTo code to do, but it is at this point, to compare primitive types, that you should be using ==:
if (this.inches == obj.inches) return compareTo(null);
Be aware that in this line: if (this == obj) return EQUAL; you are comparing object references, which might or might not be what you want. Since your class doesn't override the equals method, this comparison is equivalent to this.equals(obj).

Categories