Coding challenge ("light" edition)
This coding challenge is a bit different from the previous one ([1] [2]) because it is shorter and also a bit more closely tied to Java.
A School has either a name (“Stanford”) or a nickname (“UCSD”) or both.
Your task is to write equals() and hashCode() methods for the School class that satisfy the following requirement: two Schools are identical if they either have the same name or the same nickname.
Comments (16)
public boolean equals(Object o) { return o instanceof School && equals((School) o); } public boolean equals(School s) { //assuming name, nickname !=null return name.equals(s.name) || nickname.equals(s.nickname); } public int hashCode() { return 0; } You cannot return anything but a constant from hashCode. Given the rules we have. hash(somename, nickname) == hash(somename, anynickname) -> the hash function is independent of nickname. and hash(name, somenickname) == hash(anyname, somenickname) -> the hash function is independent of name. -> which leeds us to conclude that the hash function must be independent of both name and nickname. Hence we need to return a constant.
Simple implementation, but has very bad performance if you use as a key in a HashMap... public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } School school = (School) o; return (name == null ? school.name == null : name.equals(school.name)) // same name || (nickname == null ? school.nickname == null : nickname.equals(school.nickname)); // or same nickname } public int hashCode() { return 0; }
Technically correct, horribly inefficient: https://gist.github.com/snellm/4951711 (null and type checking omitted for clarity)
The relation implemented by the equals() method should be an equality relation, but your requirement violates the transitivity rule so cannot be correctly implemented. Consider these instances of School: A = (Name1,Nick1) B = (Name2,Nick1) C = (Name2,Nick2) With the required rule we will have: A.equals(B) B.equals(C) !A.equals(C) If we want to implement the two methods we will be forced to do the following: class School { String name; String nick; public boolean equals(Object o) { if(o==null) return false; if(!o instanceof School) return false; School os = (School)o; return (this.name !=null && this.name.equals(os.name)) || (this.nick != null && this.nick.equals(os.nick)); } public int hashcode() { return 0; } } Since given any couple of Schools it's possible to find a third one equals to both of them, all the instances need to have the same hashcode.
Here's my answer: https://gist.github.com/jqno/4952526
To mix up the hashCode() results a little you could replace Michael's hashCode() implementation with the following. private static Random random = new Random(); private static HashMap hashes = new HashMap(); @Override public int hashCode() { final Integer nameHash = hashes.get(name); final Integer nickHash = hashes.get(nickname); final int result; if(nameHash==null && nickHash==null) { result = random.nextInt(); hashes.put(name, result); hashes.put(nickname, result); } else if(nameHash!=null && nickHash==null) { result = nameHash; hashes.put(nickname,nameHash); } else if(nameHash==null && nickHash!=null) { result = nickHash; hashes.put(name,nickHash); } else if(nameHash.equals(nickHash)) { result = nameHash; } else { throw new IllegalStateException("Both name and nick already have different hashes!"); } return result; } Obviously we could move the logic into the constructor for speed, if we're allowed to assume School is immutable. I don't see how to avoid the IllegalStateException without making hashCode() results inconsistent over time. Also it'll obviously chew up memory as more and more Schools are compared.
https://gist.github.com/peakpg/4953299 Here's my stab at this in Ruby, using the equivalent methods (eql? and hash). Two comments: 1. eql? will work with any other object that with methods nickname and name. 2. I'm not doing null or type checking.
Given that equals is required to be transitive and you don't have an "and only if" clause: @Override public boolean equals(Object obj) { return obj instanceof School; } @Override public int hashCode() { return 0; }
About the argument on performances if School is used as key in an hashmap, the problem isn't about the performance, it's about random behavior. Consider this thre elements: A = (Name1, Nick1) B = (Name1, Nick2) C = (Name2, Nick2) If I do: 1: m = new HashMap(); 2: m.put(A, V1) 3: m.put(B, V2) 4: m.put(C, V3) how many entries will have the map? Since after line 3 the only entry in the will be (A, V2) and is not equals to C the answer is 2 ((A, V2), (C, V3)). But what if I do: 1: m = new HashMap(); 2: m.put(B, V2) 3: m.put(A, V1) 4: m.put(C, V3) In this case the map will have only one entry: (B, V3). Moreover I think that this behavior is a consequence of implementation details and is not required from specification. Implementing the equal method this way is a violation of the equals contract (the transitivity is mandatory), so we cannot expect that the library will work very well with this class.
I agree with Andrea. There's no way to make this work correctly w.r.t. transitivity. It breaks the contract, and this will lead to subtle bugs that are hard to find. And as long as equals is incorrect, any concerns about its performance are irrelevant. Implementing equals is harder than it looks. I wrote a little tool that can help test equals and hashCode: http://code.google.com/p/equalsverifier/ :)
Seems reasonable to me. Of course the problem left some wiggle room, so I documented my assumptions at the top: https://gist.github.com/abatkin/4960322
[...] I’m happy to see mostly correct answers to the School coding challenge. [...]
case class School(name: Option[String], nick: Option[String]) The problem is ambiguous in that no equality value is specified in the case that the names match but the nicknames conflict. Absence of a nickname/name is treated as an independent value to avoid incoherent properties like what is mentioned in previous comments.
@Dan: Really? I thought this was pretty clear: "two Schools are identical if they either have the same name or the same nickname"
Another option, btw, would be to do something like the following: case class School(names: Either[String, String]) This makes it very clear that we are *only* considering either the nickname or the name, but not both. It also enforces that at least one of them is specified.
So more than one school will never use the same nickname? What about all those "Aggies"? What is a real application of this problem where name is not unique? Okay, challenge on. Here's my second and final answer. hashCode and equals are both correct and School equality is that they match in either name or nickname. https://gist.github.com/jimwhite/5123370
As I was coding that good answer to the challenge I realized that it was indeed a legitimately good pattern and have added a more general solution to that gist. The essence of the challenge is an object with more than one, all equally good and transitively closed, identifying key. The confusing thing (to me anyway) in this was the way the problem was posed. It is very simple when designing in terms of graphs but I'm still uncertain of why/when I would think of this when doing object-oriented design.