Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

help with isEquals and hash in iphone

So i'm overriding isEquals and hash to compare custom objects to be able to remove duplicates from a NSArray. The problem is that i'm missing some values in the list which contains no duplicated items, and it seems that my hash or isEquals implementation is wrong. The custom object is a Course object which has some variables like: id and name I'll put the code here:

- (BOOL)isEqual:(id)object {
    if ([object isKindOfClass:[Course self]]) {
        return YES;
    } 
    if(self == object){
        return YES;
    }
    else {
        return NO;
    }
}

- (unsigned)hash {

    NSString *idHash = [NSString stringWithFormat: @"%d", self._id];
    return [idHash hash];
}

Then, after querying the database i put the values in an array and then in a set which should remove the duplicated items like this:

NSMutableSet *noDuplicates = [[NSMutableSet alloc] initWithArray:tempResults];

Can you see what i'm doing wrong in the isEquals or hash implementation?

Thanks a lot.

like image 886
madcoderz Avatar asked Nov 04 '22 19:11

madcoderz


2 Answers

Step 1. Decide which instance variables / state are used to determine equality. It's a good idea to make sure properties exist for them (they can be private properties declared in a class extension if you like).

Step 2. Write a hash function based on those instance variables. If all the properties that count are objects, you can just xor their hashes together. You can also use C ints etc directly.

Step 3. Write isEqual: The normal pattern is probably to first test that both objects are in the class or a subclass of the method in which isEqual: is defined and then to test equality for all the properties.

So if a class Person has a name property (type NSString) and a number property (type int) which together define a unique person, hash might be:

-(NSUInteger) hash
{
    return [[self name] hash] ^ [self number];
}

isEqual: might be

-(BOOL) isEqual: (id) rhs
{
    BOOL ret = NO;
    if ([rhs isKindOfClass: [Person class]]) // do not use [self class]
    {
        ret = [[self name] isEqualToString: [rhs name]] && [self number] == [rhs number];
    } 
    return ret;
}

I don't think it is stated as an explicit requirement in the doc but it is probably assumed that equality is symmetric and transitive i.e.

  • [a isEqual: b] == [b isEqual: a] for all a and b
  • [a isEqual: b] && [b isEqual: c]implies [a isEqual: c] for all a, b, c

So you have to be careful if you override isEqual: for subclasses to make sure it works both ways round. This is also why the comment, do not use [self class] above.

like image 176
JeremyP Avatar answered Nov 12 '22 13:11

JeremyP


Well, your isEqual: implementation really just tests if the two objects are the same class. That's not at all correct. Without knowing the details of your object, I don't know what a good implementation would look like, but it would probably follow the structure

- (BOOL)isEqual:(id)object {
    if ([object isMemberOfClass:[self class]]) {
        // test equality on all your important properties
        // return YES if they all match
    }
    return NO;
}

Similarly, your hash is based on converting an int into a string and taking its hash. You could also just return the int itself as your hash.

like image 25
Lily Ballard Avatar answered Nov 12 '22 14:11

Lily Ballard