2

What I want to do, is create an NSMutableSet, the purpose of which would be to count how many pairs of unique data there are.

Basically, I have two mutable arrays; xCoordinates and yCoordinates, and a custom object called XYPoint. Each X coordinate and Y coordinate at coinciding indices combine to make a point on a cartesian plane. For example, at index 2, there may be in the xCoordinates array, the number 4 and in the yCoordinates array, the number 8, making the point (4, 8).

Now, to the crux of the question, what I want to do is check how many unique points are there. I was planning on using an NSMutableSet to do it. I.e:

for (int i = 0; i < [xCoordinates count]; i++) {

        XYPoint *newXY = [[XYPoint alloc] init];
        newXY.xCoordinate = [xCoordinates objectAtIndex:i];
        newXY.yCoordinate = [yCoordinates objectAtIndex:i];

        if ([distinct containsObject:newXY] == NO) {

            [distinct addObject:newXY];

        }

    }

Unfortunately, that doesn't work. Is there a way to say;

if (there isn't an object in the set with an identical X coordinate property and Y coordinate property){

    Add one to the set;

}

?

Bernie
  • 1,489
  • 1
  • 16
  • 27
  • i guess set only allows unique values, it will not add duplicate values in it. for detail please [refer this doc](https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSMutableSet_Class/Reference/NSMutableSet.html) – Dipen Panchasara Apr 23 '13 at 06:23
  • 1
    Check this question/answer: http://stackoverflow.com/questions/10586218/objective-c-nsmutableset-unique-object-property – mbogh Apr 23 '13 at 06:26

4 Answers4

7

NSSet uses isEqual when testing for membership.
isEqual: and hash are part of the NSObject protocol.

If your XYPoint class derives from NSObject it inherits the default isEqual: implementation which is based on pointer equality. It compares the memory addresses to test if 2 objects are the same.
As your comparison criterion is location, you have to override isEqual: in your XYPoint class and return YES if the x/y coordinates of your 2 objects are the same.

Also have a look at the Collections Programming Topics. There is also a very detailed post about equality and hashing by Mike Ash.

Update
As JeremyP points out in the comments, you should always provide an implementation of hash when overriding isEqual:. Details are explained in the Mike Ash article above.
There is also a question discussing good hash functions for coordinates here on Stack Overflow.

Thomas Zoechling
  • 34,177
  • 3
  • 81
  • 112
2

Here's an enhanced version of Rakesh's proposal.

It doesn't suffer the subtleties of number-to-string conversion, plus it omits the redundant conditional.

It uses the common NSValue point wrapper instead of your custom class XYPoint.

for (NSUInteger i = 0; i < [xCoordinates count]; ++i) {
    CGPoint p = { [xCoordinates[i] floatValue], [yCoordinates[i] floatValue] };
   [distinct addObject:[NSValue valueWithCGPoint:p]];
}
Nikolai Ruhe
  • 81,520
  • 17
  • 180
  • 200
  • Just a note. `valueWithCGPoint:` is only available in iOS. Anything similar in cocoa? – Rakesh Apr 23 '13 at 11:39
  • Btw this does not check for uniqueness. – Rakesh Apr 23 '13 at 11:47
  • 1
    @Rakesh OS X has `valueWithPoint:`. The generic way of putting CGPoints into an NSValue is described here: http://stackoverflow.com/a/2577651/104790 – Nikolai Ruhe Apr 23 '13 at 14:54
  • @Rakesh I thought it was clear that `distinct` is an `NSMutableSet` (that cares for the uniqueness). That's why I called the conditional redundant. – Nikolai Ruhe Apr 23 '13 at 14:58
  • Yes. But when the CGPoints are created this way the equality between objects are not satisfied and the points with same co-ordinates are added to the `distinct` set. So the OP's problem still remains. – Rakesh Apr 23 '13 at 18:48
  • @Rakesh How do you come to this conclusion? Apple's documentation says: "For NSValue objects, the class, type, and contents are compared to determine equality". – Nikolai Ruhe Apr 23 '13 at 20:35
  • Well when I tried out the code it added two identical(in values) objects. Maybe I did something wrong. Don't have access to a mac right now. Will let you know. – Rakesh Apr 23 '13 at 21:08
  • It was my mistake indeed. Had made a NSSet reference and had passed an NSArray object into it. Neat answer!! :) – Rakesh Apr 24 '13 at 15:26
2

Expanding on weichsel's answer which is the best here, the class implementation would look something like this:

@interface XYCoordinate : NSObject
-(id) initWithX: (NSNumber*) newX andY: (NSNumber*) newY;
@property (readonly, copy) NSNumber* x;
@property (readonly, copy) NDNumber* y;
@end

@implementation XYCoordinate

@synthesize x = _x;
@synthesize y = _y;

-(id) initWithX: (NSNumber*) newX andY: (NSNumber*) newY
{
    self = [super init];
    if (self != nil)
    {
         [self setX: newX];
         [self setY: newY];
    }
    return self;
}

-(BOOL) isEqual: (id) somethingElse
{
    BOOL ret = NO;
    if ([somethingElse isKindOfClass: [XYCoordinate class]])
    {
        ret = [[self x] isEqual: [somethingElse x]] && [[self y] isEqual: [somethingElse y]]
    }
    return ret;
}

-(NSUInteger) hash
{
     return [[self x] hash] + [[self y] hash];  // Probably a rubbish hash function, but it will do
}
@end
Community
  • 1
  • 1
JeremyP
  • 84,577
  • 15
  • 123
  • 161
0

Off the top of my head maybe some operation that yields a unique result would be enough for your particular case (maybe not the most efficient solution though).

for (int i = 0; i < [xCoordinates count]; i++) {

    NSString *xStr = [[xCoordinates objectAtIndex:i] stringValue];
    NSString *yStr = [[yCoordinates objectAtIndex:i] stringValue];
    NSString *coordStr = [NSString stringWithFormat:@"%@ %@",xStr,yStr]; //edited
    if (![distinct containsObject:coordStr]) {
       [distinct addObject:coordStr];
    }
}

That should do I guess. Your solution was not working cause each time, a new object was created and wouldn't be equal. But for NSString like above thats not the case. Just a quick solution.

Rakesh
  • 3,370
  • 2
  • 23
  • 41