I'm having issues iterating over a HashMap in Processing.js. When calling the iterator, while(it.hasNext()) is never entered.
As a sanity check, I tried to skip the iterator and instead convert the keys to an array and iterate over those by index. That didn't work either. I printed out the following:
myHashMap.size(); // outputs 4
myHashMap.keySet().size(); // outputs 4
myHashMap.keySet().toArray().length; // outputs 0
I would expect the final line to also output 4, as they previous calls did. Am I understanding something incorrectly? Thanks!
EDIT
Here is a full example of the problem I was running into. Turned out to be a problem with using a float in my HashCode, though I still don't understand why that causes a mismatch in elements when converting the keySet to an array.
class Vertex {
float x;
float y;
public Vertex(float x, float y) {
this.x = x;
this.y = y;
}
public int hashCode() {
int hash = 17;
hash = ((hash + x) << 5) - (hash + x);
hash = ((hash + y) << 5) - (hash + y);
return hash
}
public boolean equals(Object obj) {
Vertex other = (Vertex) obj;
return (x == obj.x && y == obj.y);
}
}
HashMap<Vertex, String> tmp = new HashMap<Vertex, String>();
Vertex a = new Vertex(1.1, 2.2);
Vertex b = new Vertex(1, 1);
tmp.put(a, "A");
tmp.put(b, "B");
println("1, " + tmp.size()); // outputs 2
println("2, " + tmp.keySet().size()); // outputs 2
println("3, " + tmp.keySet().toArray().length); // outputs 1
I think I finally figured it out. This took some head-scratching. Nice question.
Moral of the story: I think you've stumbled into a bit of weirdness with the hashCode()
function. The hashCode()
function has to return an int
value, but you're returning a float
value.
To fix your problem, cast the hash
value into an int
before returning it!
public int hashCode() {
int hash = 17;
hash = ((hash + x) << 5) - (hash + x);
hash = ((hash + y) << 5) - (hash + y);
return (int)hash;
}
That might seem strange and unnecessary, so here's a longer explanation:
Notice that x
and y
are float
values, so when you use them in your hash
calculations, the result becomes a float
as well. You can prove this by printing out the value of hash
before you return it.
Java would complain about this. You can prove this by switching to Java mode and trying to run your program. But JavaScript isn't as strict with its types, so it lets your shoot yourself in the foot. Something I commonly do is program in Java mode to get its error checking, and then deploy using JavaScript mode.
Anyway, inside the HashMap
class, the result of the hashCode()
function is eventually used as an index in an array. You can view that yourself in the Processing.js source file:
//this is in the HashMap class
function getBucketIndex(key) {
var index = virtHashCode(key) % buckets.length;
return index < 0 ? buckets.length + index : index;
}
function virtHashCode(obj) {
if (obj.hashCode instanceof Function) {
return obj.hashCode();
}
//other code omitted to keep this short
}
And that might be fine, because strangely enough JavaScript is okay with decimal places in array indexes. But the problem is the implementation of HashSet
's Iterator
:
function Iterator(conversion, removeItem) {
var bucketIndex = 0;
var itemIndex = -1;
var endOfBuckets = false;
var currentItem;
function findNext() {
while (!endOfBuckets) {
++itemIndex;
if (bucketIndex >= buckets.length) {
endOfBuckets = true;
} else if (buckets[bucketIndex] === undef || itemIndex >= buckets[bucketIndex].length) {
itemIndex = -1;
++bucketIndex;
} else {
return;
}
}
}
//more code
Check out that findNext()
function. It's looping through the indexes of the array, but it's incrementing by one each time. So any keys that are put into decimal-place indexes will be skipped!
That's why your iterator skips one of your objects (the one with a decimal place in the value returned from hashCode()
). And that's why toArray()
fails as well, since that function uses an Iterator
under the hood.
I wouldn't really call this a bug, since the problem is caused by returning a float
from a function that says it will return an int
. JavaScript doesn't really need hashCode()
functions the same way Java does, so this implementation of HashMap
and Iterator
are pretty reasonable. But you do have to make sure you're returning an int
from the hashCode()
function.
By the way, here's a smaller example if you feel like playing around:
class Thing {
int myThing;
public Thing(int myThing) {
this.myThing = myThing;
}
public int hashCode(){
return myThing;
}
public boolean equals(Object obj) {
Thing other = (Thing) obj;
return (myThing == other.myThing);
}
}
void setup() {
HashMap<Thing, String> map = new HashMap<Thing, String>();
map.put(new Thing(1), "A");
map.put(new Thing(2.5), "B"); //uh oh!
map.put(new Thing(3), "C");
println("1, " + map.size()); // outputs 3
println("2, " + map.keySet().size()); // outputs 3
println("3, " + map.keySet().toArray().length); // outputs 2
}