18

In my app I have a class Person with personId property.
Now I need some data structure to hold a bunch of unique Person objects (unique = different personId)

So I guess I should use NSMutableSet as my data structure, but how do I make the NSMutableSet compare the personId property when adding a person (so I won't add the same person more then ones)?

My goal is to have a collection of unique persons all the time (even if I add two persons with the same id), I want that the NSMutableSet will do all the hard work for me and if I'm adding a person that already exists it won't add it twice.

Eyal
  • 10,777
  • 18
  • 78
  • 130

3 Answers3

34

You can achieve that by understanding how the comparison is made by the NSSet. When you add a new object to the set, isEqual: method is called (it's an NSObject method) against each of the set's elements. So what you can do is override this method and provide a custom comparison like this:

NOTE:

If you override isEqual: method you must override hash method as well

// In your Person.m
- (BOOL)isEqual:(id)anObject
{
    return [self.personId isEqual:anObject.personId]; 
    // If it's an object. Otherwise use a simple comparison like self.personId == anObject.personId
}

- (NSUInteger)hash
{
    return self.personId; //Must be a unique unsigned integer
}
Alladinian
  • 34,483
  • 6
  • 89
  • 91
  • just one thing, if my personId is NSString, how should I override the hash method? – Eyal May 14 '12 at 15:43
  • 1
    Just cast it to an int like this: `[self.personId intValue]`. And for your comparison use: `[self.personId isEqualToString: anObject.personId]` – Alladinian May 14 '12 at 15:45
  • 1
    @Alladinia: for the hash, that is WRONG. unless the string happens to be a syntactically correct integer, zero will be the result; and the hash value almost always having the same value will result in a rotten performance. – mathheadinclouds Jan 27 '14 at 01:45
  • 1
    @mathheadinclouds which part of _must be a unique unsigned integer_ in my code you did not understand? That is why I included the comment (remember, you vote the answers, not the comments). Anyways something like `[self.personId hash]` would obviously suffice in the case hat `personId` is not a number. – Alladinian Jan 27 '14 at 09:13
  • I'm sorry, I wasn't clear in my comment. There is nothing wrong with the answer itself - the issue is with the comment in answer to Eyal's comment, where you suggest getting the hash by casting a string to an int with intValue. – mathheadinclouds Jan 27 '14 at 21:04
  • @mathheadinclouds Understood. But even in the comment there wasn't any clue regarding the form of the string, so _assumed_ to be a string representation of an integer as per the comment on my answer. Having said that, yes I imagine that in the case of a hex uuid for example your suggestion would be true and worth noting. – Alladinian Jan 28 '14 at 09:17
  • If you are comparing the personId property and it is a string, then obviously the hash function should return self.personId.hash. Strings have their own hash. But alternatively, you can use an NSDictionary* instead of an NSSet; use personId as the key and the object itself as the data. First, it means you don't have to override isEqual and hash (which is an unusual thing to do), and it is much more flexible. – gnasher729 Mar 07 '14 at 23:56
  • Thank youuuuu!! So hash is what the app lacks. – KarenAnne May 23 '14 at 03:18
4

I think what you want is an NSMutableDictionary, mapping the personId to the Person object.

NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];

Person *person = something;
[dict setObject:personObj forKey:[NSNumber numberWithInt:[person personId]]];
... etc ...

(don't forget to release dict later somewhere).

In order to find the Person class for the specified personId (12, say), you can simply do:

Person *person = [dict objectForKey:[NSNumber numberWithInt:12]];

Note: that you have to store both the key and the value as objects (you cannot store primitive types).

trojanfoe
  • 120,358
  • 21
  • 212
  • 242
  • please look I explained more in my question – Eyal May 14 '12 at 15:31
  • 1
    @Eyal I think my answer will do what you want; if you add another object with the same `personId` then the old `Person` object will be replaced. – trojanfoe May 14 '12 at 15:33
0

You shouldn't need to worry about this when you're adding items to an array. As long as you're adding a unique object each time, the properties of the object shouldn't matter.

If you're worried about this and want to be doubly sure, use NSMutableSet instead (unless you need to maintain order of each object).

Update

You could use a predicate to check that an object with the same value does not exist before adding it. This would work fine with both NSMutableArray and NSMutableSet.

//create array
NSMutableArray *array = [[NSMutableArray alloc] init];

//create person
Person *newPerson = [[Person alloc] init];

//add person
[array addObject:newPerson]

//check new object exists
if([[array filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"personID == %@", [newPerson personID]]] count] == 0)
{
    //add object
    [array addObject:newPerson];
}

Very rough pseudocode but hopefully you get the gist :)

Zack Brown
  • 5,990
  • 2
  • 41
  • 54
  • My goal is to have a collection of unique persons all the time (even if I add two persons with the same id), I want that the NSMutableSet will do the hard work for me and if I'm adding a person that already exists it won't add it twice.. – Eyal May 14 '12 at 15:26
  • You could use a predicate to check if an object already exists with the same value - see my edits. – Zack Brown May 14 '12 at 15:33
  • NSMutableSet won't automatically check the person id. It will consider that two objects are the same only if they indeed both point to the same exact objects. You're better off with the dictionary as stated below. It will do the replacing of identical personIDs for you. – Owen Hartnett May 14 '12 at 15:37