0

I was asked this in interview. using Google Guava or MultiMap is not an option. I have a class

public class Alpha
{
     String company;
     int local;
     String title;
}

I have many instances of this class (in order of millions). I need to process them and at the end find the unique ones and their duplicates. e.g.

instance --> instance1, instance5, instance7 (instance1 has instance5 and instance7 as duplicates)
instance2 --> instance2 (no duplicates for instance 2)

My code works fine

declare datastructure

HashMap<Alpha,ArrayList<Alpha>> hashmap = new HashMap<Alpha,ArrayList<Alpha>>();

Add instances

for (Alpha x : arr)
{
    ArrayList<Alpha> list = hashmap.get(x); ///<<<<---- doubt about this. comment#1
    if (list == null)
    {
        list = new ArrayList<Alpha>();
        hashmap.put(x, list);
    }
        list.add(x);
}

Print instances and their duplicates.

for (Alpha x : hashmap.keySet())
{
    ArrayList<Alpha> list = hashmap.get(x); //<<< doubt about this. comment#2
    System.out.println(x + "<---->");
    for(Alpha y : list)
    {
        System.out.print(y);
    }
    System.out.println();
}

Question: My code works, but why? when I do hashmap.get(x); (comment#1 in code). it is possible that two different instances might have same hashcode. In that case, I will add 2 different objects to the same List.

When I retrieve, I should get a List which has 2 different instances. (comment#2) and when I iterate over the list, I should see at least one instance which is not duplicate of the key but still exists in the list. I don't. Why?. I tried returning constant value from my hashCode function, it works fine.

If you want to see my implementation of equals and hashCode,let me know.

Bonus question: Any way to optimize it?

Edit:

@Override
    public boolean equals(Object obj) {
        if (obj==null || obj.getClass()!=this.getClass())
            return false;
        if (obj==this)
            return true;
        Alpha guest = (Alpha)obj;
        return guest.getLocal()==this.getLocal()
                && guest.getCompany()  == this.getCompany()
                && guest.getTitle() == this.getTitle();
    }

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + (title==null?0:title.hashCode());
    result = prime * result + local;
    result = prime * result + (company==null?0:company.hashCode());
    return result;
}
Ian McGrath
  • 982
  • 2
  • 10
  • 23
  • Your implementation of the `equals` should remain untouched because it should support the actual definition of equality for your object. You might be able to optimize it by changing your `hashCode` implementation though. Your target is to keep the hash buckets smaller, so that you have less objects for `.equals` check. – Bhesh Gurung Apr 11 '14 at 22:55
  • 2
    Yes, we'd really need to see your equals and hashCode to tell you if it's working correctly. Also *"My code works ... I should see at least one instance which is not duplicate ... I don't"*, so does it work or not? I don't understand. – Radiodef Apr 11 '14 at 22:57
  • This might be helpful - http://stackoverflow.com/a/16629910/738746 – Bhesh Gurung Apr 11 '14 at 23:01
  • 1
    It looks like you're comparing Strings with `==`. Are you aware [that might be wrong](http://stackoverflow.com/questions/513832/how-do-i-compare-strings-in-java)? It depends on where `company` and `title` come from but this may very well be why your equals doesn't work the way you are expecting. – Radiodef Apr 11 '14 at 23:02
  • I don't see any duplicate instance where it should not be. In other words, I see expected/normal behavior. I think my code is wrong, hence the question. – Ian McGrath Apr 11 '14 at 23:02
  • @Radiodef, yes, but in this case, it is ok. They are all in String pool.. Good point, though. – Ian McGrath Apr 11 '14 at 23:04
  • 1
    So are you saying your code actually works correctly but you think it shouldn't? I don't really understand the question then. Why do you think your code is wrong? Besides the 1 thing I already pointed out I don't think there's a problem. – Radiodef Apr 11 '14 at 23:04
  • @Radiodef, Yes. I think the code is wrong because I am still not clear how hashmap works. Where exactly does key.equals() check takes place? when doing get() or when doing put()? – Ian McGrath Apr 11 '14 at 23:07
  • 1
    OK I see what you are asking. I think @LuiggiMendoza has answered your question: the point being that while HashMap uses `hashCode` to determine a location in its internal table, it uses `equals` to determine equality of keys. Two keys with the same hashCode can exist in a HashMap simultaneously if they are unequal. – Radiodef Apr 11 '14 at 23:12
  • 1
    *"Where exactly does key.equals() check takes place? when doing get() or when doing put()?"* `equals` is used all the time: both `get` and `put` might result in a call to `equals`. – Radiodef Apr 11 '14 at 23:15
  • @Radiodef, Thanks, I understand your answer. Could you please post it as an answer? Thanks. – Ian McGrath Apr 11 '14 at 23:17

2 Answers2

3

it is possible that two different instances might have same hashcode

Yes, but hashCode method is used to identify the index to store the element. Two or more keys could have the same hashCode but that's why they are also evaluated using equals.

From Map#containsKey javadoc:

Returns true if this map contains a mapping for the specified key. More formally, returns true if and only if this map contains a mapping for a key k such that (key==null ? k==null : key.equals(k)). (There can be at most one such mapping.)


Some enhancements to your current code:

  • Code oriented to interfaces. Use Map and instantiate it by HashMap. Similar to List and ArrayList.
  • Compare Strings and Objects in general using equals method. == compares references, equals compares the data stored in the Object depending the implementation of this method. So, change the code in Alpha#equals:

    public boolean equals(Object obj) {
        if (obj==null || obj.getClass()!=this.getClass())
            return false;
        if (obj==this)
            return true;
        Alpha guest = (Alpha)obj;
        return guest.getLocal().equals(this.getLocal())
                && guest.getCompany().equals(this.getCompany())
                && guest.getTitle().equals(this.getTitle());
    }
    
  • When navigating through all the elements of a map in pairs, use Map#entrySet instead, you can save the time used by Map#get (since it is supposed to be O(1) you won't save that much but it is better):

    for (Map.Entry<Alpha, List<Alpha>> entry : hashmap.keySet()) {
        List<Alpha> list = entry.getValuee();
        System.out.println(entry.getKey() + "<---->");
        for(Alpha y : list) {
            System.out.print(y);
        }
        System.out.println();
    }
    
Community
  • 1
  • 1
Luiggi Mendoza
  • 85,076
  • 16
  • 154
  • 332
  • So the code is correct? I had doubt about it, thought it would be better to confirm here. Also, any way I can optimize it further? – Ian McGrath Apr 11 '14 at 22:54
  • @IanMcGrath [What does it mean to “program to an interface”?](http://stackoverflow.com/q/383947/1065197) – Luiggi Mendoza Apr 11 '14 at 22:59
  • @Luigi, Good point I will change HashMap> hashmap to Map> hashmap – Ian McGrath Apr 11 '14 at 23:01
  • 1
    And note that [`get`](http://docs.oracle.com/javase/7/docs/api/java/util/Map.html#get%28java.lang.Object%29) uses the same `(key==null ? k==null : key.equals(k))` evaluation as `containsKey`. – Radiodef Apr 11 '14 at 23:09
  • 1
    @LuiggiMendoza, understood. Wow, it is as if the light bulb went on in my brain now. Thank you so much for your patience and answering in detail. – Ian McGrath Apr 11 '14 at 23:18
1

Use equals along with hashCode to solve the collision state.

Steps:

  • First compare on the basis of title in hashCode()
  • If the title is same then look into equals() based on company name to resolve the collision state.

Sample code

class Alpha {
    String company;
    int local;
    String title;

    public Alpha(String company, int local, String title) {
        this.company = company;
        this.local = local;
        this.title = title;
    }

    @Override
    public int hashCode() {
        return title.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if (obj instanceof Alpha) {
            return this.company.equals(((Alpha) obj).company);
        }
        return false;
    }
}

...

Map<Alpha, ArrayList<Alpha>> hashmap = new HashMap<Alpha, ArrayList<Alpha>>();

hashmap.put(new Alpha("a", 1, "t1"), new ArrayList<Alpha>());
hashmap.put(new Alpha("b", 2, "t1"), new ArrayList<Alpha>());
hashmap.put(new Alpha("a", 3, "t1"), new ArrayList<Alpha>());

System.out.println("Size : "+hashmap.size());

Output

Size : 2
Braj
  • 46,415
  • 5
  • 60
  • 76
  • sorry if my question was not clear. I want to understand where is the equal with key done in this case. Thanks. – Ian McGrath Apr 11 '14 at 23:05