0

I have a requirement in which I need to map multiple determinants to values.

  • Each set of determinants in a given job execution is guaranteed to be unique. The value to be determined doesn't have to be unique but it probably is.

  • Depending on the input to the job execution, this could be either one key, or the combination of two keys, or the combination of n keys that will be mapped to a single value. In practice this n will probably be limited to no more than 5, although it is possible it could exceed that.

  • Each job execution will have a set number of determinants for all inputs (I.e., all inputs will have either 2 determinants, 3 determinants, or n determinants, and will not have a mix).

One key example: foo --> bar

Two keys: foo, bar --> baz

Three keys: foo, bar, baz --> hai

Prior to this, the requirement was that I would only ever map two values to another value. I created an immutable Key class with two member variables and the appropriate override of equals and hashCode.

public class Key {
    String determinant0;
    String determinant1;
    public Key(String d0, d1) {
        determinant0 = d0;
        determinant1 = d1;
    }
    // ..
} 

However, now that I may be dealing with n number of values, I want to take a look at using a list as the key.

Map<List, String> map = new HashMap<List, String>();
map.put(Arrays.asList("foo", "bar", "baz"), "hai");
String determined = map.get(Arrays.AsList("foo","bar","baz"));
assert (determined.equals("hai"));

This question reminds me that it is bad to use a mutable object (like a List) as a key in a map. However, in my application, the key is only set once and is never altered. Here is an alternative from this question that forces it to be immutable:

HashMap<List<String>, String> map;

map.put(
    // unmodifiable so key cannot change hash code
    Collections.unmodifiableList(Arrays.asList("foo", "bar", "baz")),
    "hai"
);

In addition, I could always make a class like the following to prevent mutations on the list:

public class Key {
    List<String> determinants;
    public Key(List<String> determinants) {
        this.determinants = determinants
    }
    @Override
    public boolean equals(Object obj) {
        //...
    }
    @Override
    public int hashCode() {
        //...
    }
}

Key key = new Key(Arrays.asList("foo","bar","baz"));

Using a plain array as the key won't work, because an array's equal method only checks for identity:

Map<String[], String> map = new HashMap<String[], String>();
String[] key = new String[]{"foo", "bar", "baz"}
map.put(key, "hai");
System.out.println(map.get(key)); // null

That could be fixed by the following:

public class Key {
    String[] determinants;
    public Key(String... determinants) {
        this.determinants = determinants;
    }
    @Override
    public boolean equals(Object obj) {
        //...
    }
    @Override
    public int hashCode() {
        //...
    }
}

How about concatting all the determinants together in a string?

public class Key {
    String hash = "";
    public Key(String... determinants) {
        for (String determinant : determinants) {
            hash += determinant + "_";
        }

    }
    @Override
    public boolean equals(Object obj) {
        //...
    }
    @Override
    public int hashCode() {
        //...
    }
}

Which one of these solutions (or another one that I did not propose) is the best suited for these requirements?

Community
  • 1
  • 1
Matthew Moisen
  • 16,701
  • 27
  • 128
  • 231
  • 2
    Side note: a 0-arg list is perfectly valid for a `...` argument. – Powerlord Oct 02 '15 at 19:08
  • I think you should just have different keys in your hashmap with same value. Instead of searching O(n)^2 with a list inside of a list. That is if you search the keys for a key and then use that key with multiple array to search for value – Ya Wang Oct 02 '15 at 19:09
  • Is the order of the keys significant? If not, then a `List` does not model your problem very well. – John Bollinger Oct 02 '15 at 19:10
  • John makes a good point, if the order in which the keys come doesn't matter don't store the key as a Collection of any kind. – Ya Wang Oct 02 '15 at 19:10
  • @YaWang if the order doesn't matter, I think a set collection would be preferable, right? What do you mean by "don't store the key as a collection of any kind" ? – Matthew Moisen Oct 02 '15 at 19:12
  • If the order doesn't matter *and* duplicate keys are not meaningful then a `Set` could work. The `Set` interface defines contents-based requirements for `equals()` and `hashCode()` that all implementations are supposed to support. – John Bollinger Oct 02 '15 at 19:13
  • Well how are you inputting you key in your program? You would have to input an array of some kind if you want your key to be a collection of sorts. How are you getting string array for your key? Is it in another list? – Ya Wang Oct 02 '15 at 19:13
  • @YaWang At run time, the job would load a json array into memory from Mongo containing a list of objects, like `[{"a": "foo", "b": "bar", "c": "baz", "determinant": "hai"}, ...]` (It could also load it from a RDBMS with those four columns). The job would then load this into a data structure like I am proposing, so it would know to map the `(foo, bar, baz) --> hai`. The job then iterates over a CSV file containing the data I want to analyze, with the columns A, B, and C. – Matthew Moisen Oct 02 '15 at 19:20
  • `raise` is not a keyword in Java. Are you an Ada programmer ? :D – Dici Oct 02 '15 at 19:33

1 Answers1

1

As a comment, your question includes too much details and could have been way shorter. Now comes my answer.

I prefer using a wrapper class that completely hides the representation of the class. One thing you can do as a small optimization is storing the hashCode of your keys to prevent computing it every time. The equals method will be called more rarely (each collision in the map) and you can't do much about it :

public class Key {
    private String[] determinants;
    private int hashCode;

    public Key(String... determinants) {
        if (determinants == null || determinants.length == 0) {
            throw new IllegalArgumentException("Please provide at least one value");
        }
        this.determinants = determinants;
        this.hashCode = Objects.hash(determinants);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Key)) return false;
        Key that = (Key) o;
        return Arrays.equals(determinants, that.determinants);
    }

    @Override
    public int hashCode() {
        return hashCode;
    }
}
Dici
  • 25,226
  • 7
  • 41
  • 82