4

I was trying to solve this question:

Given an array of integers with only 3 unique numbers print the numbers in ascending order (with their respective frequencies) in O(n) time.

I wanted to solve it without using the counting sort algorithm, So what I thought is I could just do a for loop and insert the numbers in a HashMap and then loop through the HashMap entrySet and print the required info.

Here is the function:

public static void printOut(int[] arr){
        Map<Integer,Integer> hm=new HashMap<Integer,Integer>();
        for(int num : arr){
            if(hm.containsKey(num)){
                hm.put(num,hm.get(num)+1);
            }else{hm.put(num,1);}
        }
        for(Map.Entry<Integer,Integer> entry : hm.entrySet()){
            System.out.println("Key= "+entry.getKey()+" Value= "+entry.getValue());
        }
}

which did the trick when I my array was: [3, 3, 2, 1, 3, 2, 1]

However the above array should not lead to any collision so I tried to use an array which should lead to collisions, one of the arrays I tested my function with was: [6,6,9,9,6,3,9] yet my function still printed the Keys in ascending order, which got me confused, because I thought when the Key of the HashMap is an Integer the hash code should be hashIndex = key % noOfBuckets so when I have the numbers 6, 9 and 3 as my HashMap keys I thought there would be collisions and my function should print(based on the above used array):

Key= 6 Value= 3
Key= 9 Value= 3
Key= 3 Value= 1 

But instead it printed:

Key= 3 Value= 1
Key= 6 Value= 3
Key= 9 Value= 3

Could anyone please explain to me why I got the right solution to the question I was trying to solve instead of the answer I was expecting?

Thanks.

John Kugelman
  • 349,597
  • 67
  • 533
  • 578
Daniel_Kamel
  • 610
  • 8
  • 29
  • 4
    The iteration order of a `HashMap`'s `entrySet` is undefined. – Jacob G. Oct 04 '19 at 17:49
  • @JacobG. so you mean with a different example it could print the numbers in random order? but I tried testing my function with multiple inputs and it always printed them in ascending order – Daniel_Kamel Oct 04 '19 at 17:50
  • @DanielK Yes, I recommend trying it with more than 16 distinct elements in your input. – Jacob G. Oct 04 '19 at 17:51
  • 1
    @JacobG. you're right, now it makes me thinks [again] on how to solve this question in O(n) time! – Daniel_Kamel Oct 04 '19 at 17:57
  • 1
    If you are guaranteed at most 3 distinct elements, this approach can give you an *O(n)* solution though (note that sorting 3 distinct elements is an *O(1)* operation). – slider Oct 04 '19 at 18:00
  • @slider yes there will always be 3 integers, but I still don't understand why it's printing the numbers in order when it clear that there are collisions – Daniel_Kamel Oct 04 '19 at 18:04
  • The point of `hashIndex = key % noOfBuckets` is not wrong, but ... `noOfBuckets` is 16, initially. When you run it once with `{ 6, 22, 6, 9, 9, 6, 3, 9 }` and once with `{ 22, 6, 6, 9, 9, 6, 3, 9 }`, you'll see that this matches what you (probably) expected. If you think that this will answer your question appropriately, I can extend this to an answer. – Marco13 Oct 06 '19 at 19:15
  • As you know that there will always be 3 distinct elements, I bet it would be faster not to use a `HashMap`, but just a pair of `int[]`s. – Maurice Perry Oct 07 '19 at 09:23

6 Answers6

7
  1. "Collision" term in hash map/hash table is a situation when two different keys has the same hash code. Java implementation of HashMap use List/RB-tree for resolving collisions but when you have literally 3 integer keys this is definitely not your case.
  2. HashMap doesn't guarantee the insertion(or any other) order of elements. There are different another structures like LinkedHashMap or TreeMap that can guarantee some order. But using this structures for your case is a little overhead cause you can sort your collection of 3 elements on the constant time. You can even use array of Map.Entry instead of HashMap for your task.
Serge Harnyk
  • 1,279
  • 10
  • 19
  • what do you mean by "You can even use array of Map.Entry instead of HashMap for your task"? – Daniel_Kamel Oct 07 '19 at 09:55
  • I mean something like Map.Entry[] hm= new Map.Entry[3]; but that was not a recommendation but just a mention that when you have constant number of unique elements(3 in your case) choice of collection doesn't really affect performance cause anyway it would be collection with constant number of elements. :) – Serge Harnyk Oct 07 '19 at 10:12
3

As mentioned already in above answer by @Serge Harnyk

HashMap doesn't guarantee the insertion(or any other) order of elements.

I ran the above code you had shared in the question with the array [66,66,69,69,66,63,63,69] and the output was

Key= 66 Value= 3
Key= 69 Value= 3
Key= 63 Value= 2

Here, you can see that the output is not in sorted order. Another array for which the entrySet() didn't return the elements in sorted order was [10,5,5,10,10,5,10000000]

Key= 5 Value= 3
Key= 10000000 Value= 1
Key= 10 Value= 3

So, as the documentation of HashMap specifies the order of elements returned by entrySet() or keySet() of HashMap is not guaranteed to be in insertion/sorted order.

The the hash index against which a key is to be hashed is decided based on the hash code of that particular key generated by the hashCode() function implemented in HashMap. You can find the hash code of a key using the .hashCode() function

for(Map.Entry<Integer,Integer> entry : hm.entrySet()) {
    System.out.println("key= "+entry.getKey()+" has Hash Code= "+entry.getKey().hashCode()); 
}

Array [66,66,69,69,66,63,63,69] had the output

key= 66 has Hash Code= 66
key= 69 has Hash Code= 69
key= 63 has Hash Code= 63

Array [10,5,5,10,10,5,10000000] had the output

key= 5 has Hash Code= 5
key= 10000000 has Hash Code= 10000000
key= 10 has Hash Code= 10

From these you can see that for Integer keys, the hash code is not hashIndex = key % noOfBuckets. Furthermore, you can define your own implementation of the hashCode() method and use it against the HashMap. You can find a detailed explanation implementing your custom hashCode() function here.

refer. https://www.geeksforgeeks.org/internal-working-of-hashmap-java/

AnonymousFox
  • 118
  • 7
3

This is mere coincidence and the order from EntrySet and KeySet is not guaranteed. Infact hashmap itself doesn't guarantee insertion order and this will order can also change during rehashing.

Now you are trying to insert primitive int in hashmap as key which internally does autoboxing and Integer object will be inserted as key.

Integer hashcode function is

public static int hashCode(int value) {
    return value;
} 

Means it just return directly the value, in your case 6,9 and 3

Then this hashcode is used internally by hashmap for computing to get index position

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

As you can see the bitwise operator with the given values will return 0 and exponential on those values will lead to same index poistion of 3 which leads to collision. So, hashmap then will store this using either LinkedList (prior java8) and in tree(java8 and its later version).

While iterating via keySet or entrySet, the order won't be guaranteed and so the order which you are getting is mere coincidence.

2

Could anyone please explain to me why I got the right solution to the question I was trying to solve instead of the answer I was expecting?

It's a coincidence

UPDATE

As already mentioned by others, a HashMap doesn't guarantee any order while iterating it's content, unlike TreeMap that is sorted, or LinkHashMap that conserves the order of entry. So yes, if your elements are sorted, it's just a coincidence.

Maurice Perry
  • 9,261
  • 2
  • 12
  • 24
0

excerpt from Implementation notes for HashMap:

Tree bins (i.e., bins whose elements are all TreeNodes) are
ordered primarily by hashCode, but in the case of ties, if two
elements are of the same "class C implements Comparable",
type then their compareTo method is used for ordering.

The later is the case for the Integer class: implements Comparable<Integer>.

The hashcode of an Integer is always its integer value. And because a collision only happens, when two different entries get the same hashcode, there will be no collisions for Integers.
The ordering of the nodes in the table depends on the hashcode. Could it be, that the Integer-keys get presorted?

user207421
  • 305,947
  • 44
  • 307
  • 483
Kaplan
  • 2,572
  • 13
  • 14
-2

I am not sure what you exactly mean by "collisions", but as already pointed out if you read the documentation of HashMap (https://docs.oracle.com/javase/8/docs/api/java/util/HashMap.html) there is no declaration about the ordering :

"This class makes no guarantees as to the order of the map; in particular, it does not guarantee that the order will remain constant over time."

And later on "To ameliorate impact, when keys are Comparable, this class may use comparison order among keys to help break ties.".

You use at last Integers as keys, which are implementing the Comparable interface, that means that the HashMap implementation may order their keys - which it indeed does.

You could also use a Hash implementation where the order of the keys is pre-defined and therefor predictable, like a SortedMap.