Using string tuples as key for HashMap

19,832

Solution 1

Arrays in Java don't provide hashCode() and equals(Object) methods, so they aren't appropriate as map keys. What you could use instead is Arrays.asList(string1, string1, etc) which would give you an immutable List, which all the methods needed for a Map's key.

Solution 2

HashMaps use Object.hashCode() to create the hash. This, by default, uses a hash of the object that is unique for each instance - but doesn't look into any contents.

You migth want to create a tuple that overrides hashCode() and, in addition to that, is immutable once created:

public class Tuple<T> {
    private final T[] contents;

    public Tuple (T[] contents) {
        if (contents.length != 2)
            throw new IllegalArgumentException();
        this.contents = contents;
    }

    public T[] getContents () {
        return this.contents.clone();
    }

    @Override
    public int hashCode () {
        return Arrays.deepHashCode(this.contents);
    }

    @Override
    public boolean equals (Object other) {
        return Arrays.deepEquals(this.contents, other.getContents());
    }

    @Override
    public String toString () {
        return Arrays.deepToString(this.contents);
    }
}

[Edit]: Note that, if mutable objects are used instead of strings, the getter must perform a deep copy, not just a simple clone() to ensure immutability.

Solution 3

You could use Arrays.toString(myArray) as your key.

Share:
19,832
Nullpoet
Author by

Nullpoet

Updated on June 09, 2022

Comments

  • Nullpoet
    Nullpoet almost 2 years

    I need Java equivalent for following Python:

    In [1]: d = {}
    In [2]: k = ("x","2")
    In [3]: d[k] = 1
    In [4]: print d[("x","y")]
    
    1
    

    Python has tuples which are hashable. I tried following in Java unsuccessfully:

    Map<String[], Integer> d = new HashMap<String[], Integer>();
    String[] k = new String[]{"x", "y"};
    d.put(k, 1);
    System.out.println(d.get(k));
    System.out.println(d.get(new String[]{"x", "y"}));
    

    It outputs:

    1
    null
    

    This means reference to String[] is getting hashed instead of the value.

    An inefficient way I can think of is concatenating elements from String[] into a single String.

    Is there a better way?

  • Johannes H.
    Johannes H. over 10 years
    While possible, I'd recommend to create an immutable list out of that. Unfortunately Java doesn't provide one, but Guava for example does. Of course you could always write one on your own, too.
  • user949300
    user949300 over 10 years
    Why clone every call to hashcode()?
  • Johannes H.
    Johannes H. over 10 years
    @user949300: I don't. I only clone the content array in the getter to make sure no changes are made to it, so the tuple is immutable.
  • Johannes H.
    Johannes H. over 10 years
    That's even more dirty than the approach suggested by the op (which is concatenating the strings instead of using the array)
  • Johannes H.
    Johannes H. over 10 years
    Not sure BTW how adding up the hashcodes of both contents behaves. Maybe a more elaborate hashCode function is needed here. Any comments on this?
  • Harald K
    Harald K over 10 years
    @Johannes What's wrong with Collections.unmodifiableList(list)?
  • Johannes H.
    Johannes H. over 10 years
    @haraldK: See the description of ImmutableList in Guava: Unlike Collections#unmodifiableList, which is a view of a separate collection that can still change, an instance of ImmutableList contains its own private data and will never change. So, in short terms, the difference is: if you still got an reference to the original list, you can still modify it if you used .unmodifiableList()
  • Bimalesh Jha
    Bimalesh Jha over 10 years
    should override equals() also and make that T[] instance field as private final for completeness :)
  • Johannes H.
    Johannes H. over 10 years
    @BimaleshJha: agreed, edited. Hope I didn't make any boo-boos in that quick and dirty edit ;)
  • Harald K
    Harald K over 10 years
    @Johannes Valid point. :-)
  • Holger
    Holger over 10 years
    @Johannes H.: Since the creator of the List has the full control over the creation a Collections.unmodifiableList(Arrays.asList(varargs)) or Collections.unmodifiableList(Arrays.asList(array.clone())) is sufficient. There’s no reason to replace this code adding 3rd party library dependencies as this code is safe and the 3rd party library still wouldn’t prevent other code from using mutable lists.
  • user949300
    user949300 over 10 years
    Why "dirtier"? First, there's little coding, and you end up with an immutable String. And the String is bounded by [] and delimited by commas, so it is unlikely to accidentally match. e.g. OP's scheme doesn't work for "dog","ate" vs. "do","gate". Mine does.