[Solved] Custom Key for HashMap

Hey, so I’m using my own datatype for a key in a HashMap. Basically the idea is that you can send either an int or String (or both) to find a value. However, I never find values in my HashMap ???

Here’s what I have for the Key class:

public class Key
{
	public final int code;
	public final String name;

	public Key(int in_code, String in_name)
	{
		code = in_code;
		name = in_name;
	}

	public boolean equals(Object o)
	{
		if(o instanceof Key)
		{
			return code == ((Key) o).code && name.equals(((Key) o).name);
		}
		else if(o instanceof Integer)
		{
			return code == ((Integer) o).intValue();
		}
		else if(o instanceof String)
		{
			return name.equals(((String) o));
		}
		return false;
	}

	public int hashcode()
	{
		return (code << 16) + name.length();
	}
}

Here’s the map:

HashMap map = new HashMap<Key, InputOperation>();

and here is how I would like to use it:


m_map.get(97);
m_map.get("Print_Char");
m_map.get(new Key(97, "Print_Char"));

Edit: added new Key to the third get()…

Can you show us an example of how you’re trying to use this class?

@KevinWorkman updated first post.

What you want is not possible, because a hashmap lookup would first search for the hashcode and then uses equals to find the correct key. For this to work, you would need a key that computes the same hashcode for the int and the string independently.

If this is not to store a billion datasets and you would like to have this kind of lookup, just derive hashmap and add a get(String) and get(int) method that do brute force searches over the entryset. This way you even skip the autoboxing for the int…

bummer, I’ll try that first, if it’s not fast enough I have a couple ideas…thanks

Using that hashCode method (which may or may not be very good), you could do lookups on just ints or Strings like this:


-m_map.get(97);
+m_map.get(new Key(97, "")); // empty string doesn't affect hashcode
-m_map.get("Print_Char");
+m_map.get(new Key(0, "Print_Char")); // ditto with int 0
m_map.get(new Key(97, "Print_Char"));

EDIT: nvm, this won’t work, I was thinking about the wrong thing

The contract for the hashcode() function requires that if two objects are equal, then their two hashcodes must be the same.

The problem is that you’re violating that contract.

For example, you might have this code:

String name = "name";
Key key = new Key(1, name);
System.out.println(key.equals(name)); //true
System.out.println(key.hashCode() == name.hashCode()); //false!

You would have to change your code so that that the contract was always obeyed. Since you want this to be based off of two values, this is going to be pretty annoying.

In fact, you’re already violating the contract for the equals() function, which requires that equality be reflexive. In other words, if a.equals(b), then b.equals(a). Your code doesn’t obey that either.

In the end, this approach probably isn’t going to work. Instead, I’d recommend building two Maps, one that uses Integers as keys, and one that uses Strings as keys.

This is of course the real answer, the polymorphic lookup smells like an ill-advised solution to an XY problem. Why do you need this functionality, OP?

interesting idea.

with “regular” hashmaps this is a bit painful. better to use a hashmap which allows you to define how to compute the hashcode.

like http://fastutil.di.unimi.it/ -> http://fastutil.di.unimi.it/docs/it/unimi/dsi/fastutil/objects/Object2ObjectOpenCustomHashMap.html

you can pass in a http://fastutil.di.unimi.it/docs/it/unimi/dsi/fastutil/Hash.Strategy.html - which defines how to compute [icode]equals[/icode] and [icode]hashcode[/icode]. that way, you can ignore all the hashcode functions of all objects you pass in. they’d be overwritten anyway.

your equals method looks good. now you just need a similar if-else chain for hashcode. win.

The approach doesn’t really make sense. What if I have these keys?

Key key1 = new Key("one", 1);
Key key2 = new Key("one", 2);
Key key3 = new Key("two", 1);
Key key4 = new Key("two", 2);

yourMagicMap.put(key1, "ABC");
yourMagicMap.put(key2, "XYZ");
yourMagicMap.put(key3, "123");
yourMagicMap.put(key4, "456");

You say you want to base your keys off the String or the int. That won’t work. What should happen when I do this?

String x = yourMagicMap.get("one");
String y = yourMagicMap.get(2);

You can have your key based off your String and your int, but not or. If you want it to be or, then you need to maintain two Maps and insert whatever logic you want to handle cases like the above.

it should happen what you define in the hashcode function. since you cannot really overwrite the “build-in” String or Integer methods, use a hashmap like i linked above. but i see what you mean Kevin, that logic just makes no sense. it’s implementation must be paradox.

anyway, cylab is right in the end - use a linear scan and return x values for one key.

kinda curious what danisaur is using that map for. :wink:

@cylab, @KevinWorkman, @basil_, @BurntPizza

(FYI this is for an event driven input system.)

Indeed you’re all right. I rewrote using cylab’s recommendation. As it turns out there can only be one String -> many integers, but not the other way around. So I’ve made two maps HashMap<String, ArrayList> and HashMap<Integer, InputOperation> where InputOperation can have many actions.

The only issue with what I’ve done is I have a triple nested loop for removing actionlisteners out of an InputOperation, which makes it potentially slow. Although I doubt it will ever be an issue(mostly just looks gross).

Thanks!

Can a particular String map to more than one InputOperation?

Yes, this wasn’t originally the case though.

The idea now is that a String(Name) can map to multiple Integers(Event IDs) and Integers can map to multiple InputOperations(ActionListeners)

Gotcha.

You could also have a nested Map, something like this:

Map<String, Map<Integer, InputOperation>> yourMagicMap;

This is not much different from what you already have, but might be worth considering.

Ah, thanks! I’ll look into it.