Can an array be used as a HashMap key? - java

If a HashMap's key is a String[] array:
HashMap<String[], String> pathMap;
Can you access the map by using a newly created String[] array, or does it have to be the same String[] object?
pathMap = new HashMap<>(new String[]{"korey", "docs"}, "/home/korey/docs");
String path = pathMap.get(new String[]{"korey", "docs"});

It will have to be the same object. A HashMap compares keys using equals() and two arrays in Java are equal only if they are the same object.
If you want value equality, then write your own container class that wraps a String[] and provides the appropriate semantics for equals() and hashCode(). In this case, it would be best to make the container immutable, as changing the hash code for an object plays havoc with the hash-based container classes.
EDIT
As others have pointed out, List<String> has the semantics you seem to want for a container object. So you could do something like this:
HashMap<List<String>, String> pathMap;
pathMap.put(
// unmodifiable so key cannot change hash code
Collections.unmodifiableList(Arrays.asList("korey", "docs")),
"/home/korey/docs"
);
// later:
String dir = pathMap.get(Arrays.asList("korey", "docs"));

No, but you can use List<String> which will work as you expect!

Arrays in Java use Object's hashCode() and don't override it (the same thing with equals() and toString()). So no, you cannot shouldn't use arrays as a hashmap key.

You cannot use a plain Java Array as a key in a HashMap. (Well you can, but it won't work as expected.)
But you could write a wrapper class that has a reference to the Array and that also overrides hashCode() and equals().

In most cases, where the Strings inside your array are not pathological and do not include commas followed by a space, you can use Arrays.toString() as a unique key. i.e. your Map would be a Map<String, T>. And the get/put for an array myKeys[] would be
T t = myMap.get(Arrays.toString(myKeys));
myMap.put(Arrays.toString(myKeys), myT);
Obviously you could put in some wrapper code if desired.
A nice side effect is that your key is now immutable. Of course, of you change your array myKeys and then try a get(), you won't find it.
Hashing of Strings is highly optimized. So my guess is that this solution, though it feels a bit slow and kludgy, will be both faster and more memory efficient (less object allocations) than #Ted Hopp solution using an immutable List. Just think about whether Arrays.toString() is unique for your keys. If not, or if there is any doubt, (e.g. the String[] comes from user input) use the List.

Like said you need a wrapper class around your array which overrides equality and hashCode.
e.g.
/**
* We can use this instance as HashKey,
* the same anagram string will refer the same value in the map.
*/
class Anagram implements CharSequence {
private final char[] anagram;
public Anagram(String anagram) {
this.anagram = anagram.toCharArray();
Arrays.sort(this.anagram);
}
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Anagram that = (Anagram) o;
return Arrays.equals(this.anagram, that.anagram);
}
#Override
public int hashCode() {
return Arrays.hashCode(this.anagram);
}
#Override
public int length() {
return anagram.length;
}
#Override
public char charAt(int index) {
return anagram[index];
}
#Override
public CharSequence subSequence(int start, int end) {
return new String(anagram).subSequence(start, end);
}
#Override
public String toString() {
return Arrays.toString(anagram);
}
}
Otherwise declare your map as IdentityHashMap, then the user knows we need to use the same instance for your CRUD.

Ted Hopp is right it will have to be same object.
For information see this example:
public static void main(String[] args) {
HashMap<String[], String> pathMap;
pathMap = new HashMap<String[], String>();
String[] data = new String[]{"korey", "docs"};
pathMap.put(data, "/home/korey/docs");
String path = pathMap.get(data);
System.out.println(path);
}
When you run the above code, it will print "docs".

Since Java 9, you can use Arrays::compare method as a comparator for TreeMap that compares the contents of arrays.
Map<String[], String> map = new TreeMap<>(Arrays::compare);
String[] key1 = {"one", "two"};
String[] key2 = {"one", "two"};
String[] key3 = {"one", "two"};
map.put(key1, "value1");
map.put(key2, "value2");
System.out.println(map.size()); // 1
System.out.println(map.get(key1)); // value2
System.out.println(map.get(key2)); // value2
System.out.println(map.get(key3)); // value2
See also: How to make a Set of arrays in Java?

A running example using the Arrays utility and the hash code it provides:
String[] key1 = { "korey", "docs" };
String value1 = "/home/korey/docs";
HashMap<Integer, String> map = new HashMap<Integer, String>();
map.put(Arrays.hashCode(key1), value1);
System.out.println(map);
{-1122550406=/home/korey/docs}
This approach is useful if your focus is in storing only. Retrieving using the readable (original) key is simple:
String retrievedValue = map.get(Arrays.hashCode(key1));
System.out.println(retrievedValue);
/home/korey/docs

Related

Suspicious call to 'LinkedHashMap.get'

Hello I have the following code
public static LinkedHashMap<Object, String[]> dataMap = new LinkedHashMap<>();
public static void parseDataset(int line){
String[] dataArr = dataMap.get(dataMap.keySet().toArray()[line]);
}
Since the Object I use as a Key is dynamically generated I have no knowledge about it so I have to find it before I can use it to get its value.
This code gives me the warning Suspicious call to 'LinkedHashMap.get', is that a problem and how would I get rid of the warning?
You don't need to use get: instead of converting the keys to an array, use values() instead. This works because values() iterates in the same order as the corresponding keys():
String[] dataArr = (String[]) dataMap.values().toArray()[line];
But you don't need to use toArray() either, which wastefully allocates an array containing all values, from which you only want one: you can just iterate through the values to get the thing you want:
static String[] nthItem(int n) {
int i = 0;
for (String[] value : dataMap.values()) {
if (i == n) return value;
++i;
}
throw new ArrayIndexOutOfBoundsException();
}
Or:
String[] dataArr = dataMap.values().stream().skip(line).findFirst().orElseThrow();
(Existing implementations of this sort of thing can be found in commonly-used libraries, e.g. Guava's Iterables.get)

How to sort TreeMap based on keys where keys are alpha numeric , but we need to sort based on available numbers only?

I have a TreeMap which needs to be sorted based on keys. Which is default property of TreeMap. But in my case I am not able to figure out the Comparator. Following is my Code.
public class Test {
public static void main(String[] args) {
Map<String, String> aMap = new TreeMap<String, String>(new MyComp());
aMap.put("02_file.cql", "test");
aMap.put("01.cql", "test");
aMap.put("04.cql", "test");
aMap.put("3_file.cql", "test");
aMap.put("11_file.cql", "test");
aMap.put("10_file.cql", "test");
aMap.put("0_file.cql", "test");
aMap.put("100_file.cql", "test");
Set<Map.Entry<String,String>> set = aMap.entrySet();
for(Map.Entry<String,String> e : set){
System.out.println(e.getKey() + ":" + e.getValue());
}
}
}
class MyComp implements Comparator<String> {
#Override
public int compare(String str1, String str2) {
return str1.compareTo(str2);
}
}
The output is coming as :
01.cql:test
02_file.cql:test
04.cql:test
0_file.cql:test
100_file.cql:test
10_file.cql:test
11_file.cql:test
3_file.cql:test
Which is not my expected result. I am expecting result like:
0_file.cql:test
01.cql:test
02_file.cql:test
3_file.cql:test
04.cql:test
10_file.cql:test
11_file.cql:test
100_file.cql:test
Which is same as like what NameFileComparator.NAME_COMPARATOR
org.apache.commons.io.comparator.NameFileComparator;
Any suggestion?
The Comparator passed to the TreeMap constructor makes in a some way which compareTo() of String does : a lexicographical comparison.
But you don't want a lexicographical comparison.
In your expected, you want only a numeric comparison.
To achieve it, remove the no digit part of Strings, create two ints from that and compare the ints with Integer.compare(int, int).
class MyComp implements Comparator<String> {
#Override
public int compare(String str1, String str2) {
String notDigit = "[^\\d]";
int int1 = Integer.parseInt(str1.replaceAll(notDigit, ""));
int int2 = Integer.parseInt(str2.replaceAll(notDigit, ""));
return Integer.compare(int1, int2);
}
}
Output by using this Comparator:
0_file.cql:test
01.cql:test
02_file.cql:test
3_file.cql:test
04.cql:test
10_file.cql:test
11_file.cql:test
100_file.cql:test
It's because you are comparing the key as string, so it compares the first character, then the second, and so on.
It seems like what you want is to compare the integer each key starts with.
If you want the comparison done exactly as with apache commons-io NameFileComparator and you are not talking about a massive amount of filenames or concurrent calls to this method...just create a Comparator that delegates to the apache one.
public clas MyComp implements Comparator<String> {
private final Comparator<File> delegate = <init NameFileComparator as needed>;
#Override
public int compare(String str1, String str2) {
return delegate.compare(new File(str1), new File(str2));
}
}
Then use that MyComp on the TreeSet constructor.
Take into consideration File is just an abstraction, it's not a real system file unless you decide to create one. So safe to use.
Adding the disclaimer regarding big amount of filenames, since this approach will generate a new File instance for each filename, which means small amount of memory overhead. Nothing to take into consideration in most of the scenarios.
The comparison operation seems alright. It just compares the string values and apparently the "_" character have a low precedence than numbers. If you want your code to sort your files numerically, then you have to store your numeric part of your filenames as a seperate int-type key.

Java: Composite key in hashmaps

I would like to store a group of objects in a hashmap , where the key shall be a composite of two string values. is there a way to achieve this?
i can simply concatenate the two strings , but im sure there is a better way to do this.
You could have a custom object containing the two strings:
class StringKey {
private String str1;
private String str2;
}
Problem is, you need to determine the equality test and the hash code for two such objects.
Equality could be the match on both strings and the hashcode could be the hashcode of the concatenated members (this is debatable):
class StringKey {
private String str1;
private String str2;
#Override
public boolean equals(Object obj) {
if(obj != null && obj instanceof StringKey) {
StringKey s = (StringKey)obj;
return str1.equals(s.str1) && str2.equals(s.str2);
}
return false;
}
#Override
public int hashCode() {
return (str1 + str2).hashCode();
}
}
You don't need to reinvent the wheel. Simply use the Guava's HashBasedTable<R,C,V> implementation of Table<R,C,V> interface, for your need. Here is an example
Table<String, String, Integer> table = HashBasedTable.create();
table.put("key-1", "lock-1", 50);
table.put("lock-1", "key-1", 100);
System.out.println(table.get("key-1", "lock-1")); //prints 50
System.out.println(table.get("lock-1", "key-1")); //prints 100
table.put("key-1", "lock-1", 150); //replaces 50 with 150
public int hashCode() {
return (str1 + str2).hashCode();
}
This seems to be a terrible way to generate the hashCode: Creating a new string instance every time the hash code is computed is terrible! (Even generating the string instance once and caching the result is poor practice.)
There are a lot of suggestions here:
How do I calculate a good hash code for a list of strings?
public int hashCode() {
final int prime = 31;
int result = 1;
for ( String s : strings ) {
result = result * prime + s.hashCode();
}
return result;
}
For a pair of strings, that becomes:
return string1.hashCode() * 31 + string2.hashCode();
That is a very basic implementation. Lots of advice through the link to suggest better tuned strategies.
Why not create a (say) Pair object, which contains the two strings as members, and then use this as the key ?
e.g.
public class Pair {
private final String str1;
private final String str2;
// this object should be immutable to reliably perform subsequent lookups
}
Don't forget about equals() and hashCode(). See this blog entry for more on HashMaps and keys, including a background on the immutability requirements. If your key isn't immutable, then you can change its components and a subsequent lookup will fail to locate it (this is why immutable objects such as String are good candidates for a key)
You're right that concatenation isn't ideal. For some circumstances it'll work, but it's often an unreliable and fragile solution (e.g. is AB/C a different key from A/BC ?).
I have a similar case. All I do is concatenate the two strings separated by a tilde ( ~ ).
So when the client calls the service function to get the object from the map, it looks like this:
MyObject getMyObject(String key1, String key2) {
String cacheKey = key1 + "~" + key2;
return map.get(cachekey);
}
It is simple, but it works.
I see that many people use nested maps. That is, to map Key1 -> Key2 -> Value (I use the computer science/ aka haskell curring notation for (Key1 x Key2) -> Value mapping which has two arguments and produces a value), you first supply the first key -- this returns you a (partial) map Key2 -> Value, which you unfold in the next step.
For instance,
Map<File, Map<Integer, String>> table = new HashMap(); // maps (File, Int) -> Distance
add(k1, k2, value) {
table2 = table1.get(k1);
if (table2 == null) table2 = table1.add(k1, new HashMap())
table2.add(k2, value)
}
get(k1, k2) {
table2 = table1.get(k1);
return table2.get(k2)
}
I am not sure that it is better or not than the plain composite key construction. You may comment on that.
Reading about the spaguetti/cactus stack I came up with a variant which may serve for this purpose, including the possibility of mapping your keys in any order so that map.lookup("a","b") and map.lookup("b","a") returns the same element. It also works with any number of keys not just two.
I use it as a stack for experimenting with dataflow programming but here is a quick and dirty version which works as a multi key map (it should be improved: Sets instead of arrays should be used to avoid looking up duplicated ocurrences of a key)
public class MultiKeyMap <K,E> {
class Mapping {
E element;
int numKeys;
public Mapping(E element,int numKeys){
this.element = element;
this.numKeys = numKeys;
}
}
class KeySlot{
Mapping parent;
public KeySlot(Mapping mapping) {
parent = mapping;
}
}
class KeySlotList extends LinkedList<KeySlot>{}
class MultiMap extends HashMap<K,KeySlotList>{}
class MappingTrackMap extends HashMap<Mapping,Integer>{}
MultiMap map = new MultiMap();
public void put(E element, K ...keys){
Mapping mapping = new Mapping(element,keys.length);
for(int i=0;i<keys.length;i++){
KeySlot k = new KeySlot(mapping);
KeySlotList l = map.get(keys[i]);
if(l==null){
l = new KeySlotList();
map.put(keys[i], l);
}
l.add(k);
}
}
public E lookup(K ...keys){
MappingTrackMap tmp = new MappingTrackMap();
for(K key:keys){
KeySlotList l = map.get(key);
if(l==null)return null;
for(KeySlot keySlot:l){
Mapping parent = keySlot.parent;
Integer count = tmp.get(parent);
if(parent.numKeys!=keys.length)continue;
if(count == null){
count = parent.numKeys-1;
}else{
count--;
}
if(count == 0){
return parent.element;
}else{
tmp.put(parent, count);
}
}
}
return null;
}
public static void main(String[] args) {
MultiKeyMap<String,String> m = new MultiKeyMap<String,String>();
m.put("brazil", "yellow", "green");
m.put("canada", "red", "white");
m.put("USA", "red" ,"white" ,"blue");
m.put("argentina", "white","blue");
System.out.println(m.lookup("red","white")); // canada
System.out.println(m.lookup("white","red")); // canada
System.out.println(m.lookup("white","red","blue")); // USA
}
}
public static String fakeMapKey(final String... arrayKey) {
String[] keys = arrayKey;
if (keys == null || keys.length == 0)
return null;
if (keys.length == 1)
return keys[0];
String key = "";
for (int i = 0; i < keys.length; i++)
key += "{" + i + "}" + (i == keys.length - 1 ? "" : "{" + keys.length + "}");
keys = Arrays.copyOf(keys, keys.length + 1);
keys[keys.length - 1] = FAKE_KEY_SEPARATOR;
return MessageFormat.format(key, (Object[]) keys);}
public static string FAKE_KEY_SEPARATOR = "~";
INPUT:
fakeMapKey("keyPart1","keyPart2","keyPart3");
OUTPUT: keyPart1~keyPart2~keyPart3
I’d like to mention two options that I don’t think were covered in the other answers. Whether they are good for your purpose you will have to decide yourself.
Map<String, Map<String, YourObject>>
You may use a map of maps, using string 1 as key in the outer map and string 2 as key in each inner map.
I do not think it’s a very nice solution syntax-wise, but it’s simple and I have seen it used in some places. It’s also supposed to be efficient in time and memory, while this shouldn’t be the main reason in 99 % of cases. What I don’t like about it is that we’ve lost the explicit information about the type of the key: it’s only inferred from the code that the effective key is two strings, it’s not clear to read.
Map<YourObject, YourObject>
This is for a special case. I have had this situation more than once, so it’s not more special than that. If your objects contain the two strings used as key and it makes sense to define object equality based on the two, then define equals and hashCode in accordance and use the object as both key and value.
One would have wished to use a Set rather than a Map in this case, but a Java HashSet doesn’t provide any method to retrieve an object form a set based on an equal object. So we do need the map.
One liability is that you need to create a new object in order to do lookup. This goes for the solutions in many of the other answers too.
Link
Jerónimo López: Composite key in HashMaps on the efficiency of the map of maps.

Hash collision in Hashmap

I need a simple scenario to produce a hashing collision in a HashMap. Could someone please provide one.
Is it possible to produce hashing collision if my hashmap keys are immutable?
Regards,
Raju komaturi
You could create your own type and create a bad hash function:
public class BadHash {
private String aString;
public BadHash(String s) {
aString = s;
}
public int hashCode() {
return aString.length();
}
public boolean equals(Object other) {
// boilerplate stuff
BadHash obj = (BadHash) other;
return obj.aString.equals(aString);
}
}
This will make it easy to create a collision.
An example would be:
BadHash a = new BadHash("a", value1);
BadHash b = new BadHash("b", value2);
hashMap.add(a);
hashMap.add(b);
These two entries would collide because a and b hash to the same value even though they are not equal.
Assuming you can change the key class's hash code method.
public int hashCode() {
return 1; // Or any constant value
}
This will make every single key collide.
Can't get much simpler than this:
Map<String, Object> map = new HashMap<String, Object>();
map.put("a", null);
map.put("a", null);
The simplest way is to set the initialCapacity of the HashMap to a low value and start inserting elements.
I suppose you could also design a class such that two objects can return the same hashCode value even though equals would return false.
From what I can see though, there's no way with the default HashMap to get it to tell you if there's a collision.

List Sorting puzzle

Assuming I have
final Iterable<String> unsorted = asList("FOO", "BAR", "PREFA", "ZOO", "PREFZ", "PREFOO");
What can I do to transform this unsorted list into this:
[PREFZ, PREFA, BAR, FOO, PREFOO, ZOO]
(a list which begin with known values that must appears first (here "PREFA" and "PREFZ") and the rest is alphabetically sorted)
I think there are some usefull classes in guava that can make the job (Ordering, Predicates...), but I have not yet found a solution...
I would keep separate lists.
One for known values and unknown values. And sort them separately, when you need them in a one list you can just concatenate them.
knownUnsorted.addAll(unsorted.size - 1, unknonwUnsorted);
I suggest filling List with your values and using Collections.sort(...).
Something like
Collections.sort(myList, new FunkyComparator());
using this:
class FunkyComparator implements Comparator {
private static Map<String,Integer> orderedExceptions =
new HashMap<String,Integer>(){{
put("PREFZ", Integer.valueOf(1));
put("PREFA", Integer.valueOf(2));
}};
public int compare(Object o1, Object o2) {
String s1 = (String) o1;
String s2 = (String) o2;
Integer i1 = orderedExceptions.get(s1);
Integer i2 = orderedExceptions.get(s2);
if (i1 != null && i2 != null) {
return i1 - i2;
}
if (i1 != null) {
return -1;
}
if (i2 != null) {
return +1;
}
return s1.compareTo(s2);
}
}
Note: This is not the most efficient solution. It is just a simple, straightforward solution that gets the job done.
I would first use Collections.sort(list) to sort the list.
Then, I would remove the known items, and add them to the front.
String special = "PREFA";
if (list.remove(special)
list.add(0, special);
Or, if you have a list of array of these values you need in the front you could do:
String[] knownValues = {};
for (String s: knownValues) {
if (list.remove(s))
list.add(0, s);
}
Since I'm a fan of the guava lib, I wanted to find a solution using it. I don't know if it's efficient, neither if you find it as simple as others solution, but it's here:
final Iterable<String> all = asList("FOO", "BAR", "PREFA", "ZOO", "PREFOO", "PREFZ");
final List<String> mustAppearFirst = asList("PREFZ", "PREFA");
final Iterable<String> sorted =
concat(
Ordering.explicit(mustAppearFirst).sortedCopy(filter(all, in(mustAppearFirst))),
Ordering.<String>natural().sortedCopy(filter(all, not(in(mustAppearFirst)))));
You specifically mentioned guava; along with Sylvain M's answer, here's another way (more as an academic exercise and demonstration of guava's flexibility than anything else)
// List is not efficient here; for large problems, something like SkipList
// is more suitable
private static final List<String> KNOWN_INDEXES = asList("PREFZ", "PREFA");
private static final Function<Object, Integer> POSITION_IN_KNOWN_INDEXES
= new Function<Object, Integer>() {
public Integer apply(Object in) {
int index = KNOWN_INDEXES.indexOf(in);
return index == -1 ? null : index;
}
};
...
List<String> values = asList("FOO", "BAR", "PREFA", "ZOO", "PREFZ", "PREFOO");
Collections.sort(values,
Ordering.natural().nullsLast().onResultOf(POSITION_IN_KNOWN_INDEXES).compound(Ordering.natural())
);
So, in other words, sort on natural order of the Integer returned by List.indexOf(), then break ties with natural order of the object itself.
Messy, perhaps, but fun.
I would also use Collections.sort(list) but I think I would use a Comparator and within the comparator you could define your own rules, e.g.
class MyComparator implements Comparator<String> {
public int compare(String o1, String o2) {
// Now you can define the behaviour for your sorting.
// For example your special cases should always come first,
// but if it is not a special case then just use the normal string comparison.
if (o1.equals(SPECIAL_CASE)) {
// Do something special
}
// etc.
return o1.compareTo(o2);
}
}
Then sort by doing:
Collections.sort(list, new MyComparator());

Categories