NSSet of NSNumbers - member method is always nil
Asked Answered
G

4

6


I want to have a simple NSSet which is loaded with some NSNumbers and then find out if those numbers are already added in the set or not. When I do this:

NSMutableSet *set = [[NSMutableSet alloc] init];
NSNumber *num1 = [NSNumber numberWithInt:5];
NSNumber *num2 = [NSNumber numberWithInt:5];
[set addObject:num1];
if([set member:num2]){
   // something...
}

The problem is that the member always returns nil (if is false), even if those numbers are same. isEqual method returns true. So

if([num1 isEqual:num2]){
   // correct
}

works...
In docs I read that member method uses isEqual so I don't know what the problem is... Thanks for any advice.

Gallbladder answered 8/11, 2011 at 14:42 Comment(4)
if your code is correctly pasted - you didn't add object num2 to your setZed
are you sure.. your code is working perfectly fine for me!Osteoclast
@Zed yes I'm sure, the point is that I want to check if number 5 is already in the set, but I have to use nsnumber because they are objects, so I want to check if nsnumber object with value of 5 is already in the set (num1 with value 5 is, so it should return true)Gallbladder
@Miraaj well I copied that from xcode and it did as I described... I'm not really sure whyGallbladder
W
2

The problem is, that you are checking if num1 and num2 are the same object. And they are not. They just have the same value but they are two different object with the same value.

So what you are checking with member is whether they have the same address in memory.

Edit: You should use compare to check if the values of the numbers are the same!

Weigela answered 8/11, 2011 at 14:47 Comment(4)
"whether they have the same address in memory " is untrue; according to the member: docs: "If the set contains an object equal to object (as determined by isEqual:) then that object (typically this will be object), otherwise nil."Madel
You could both be right... I seem to recall that NSNumber has a sneaky optimisation where it has static representations of the first few integers. In this case the memory address and isEqual would work.Irrigate
Sure, but it shouldn't return false either way.Madel
I could use compare method to compare 2 NSNumbers, but I want to check if 1 NSNumber is in the set of NSNumbers, so should I loop through the set and check 1 by 1 or is there any other method that actually works as described in docs?Gallbladder
S
6

When you use NSArray you need to compare the values instead of the objects themselves. Instead of using NSArray, you can use NSMutableIndexSet. This allows you to compare the values with a few less steps.

//during init, add some numbers
NSMutableIndexSet *tSet = [[NSMutableIndexSet alloc] init];
NSUInteger exampleValue = 7;
[tSet addIndex:exampleValue];
exampleValue = 42;
[tSet addIndex:exampleValue];

//...later on
//look to see if numbers are there
if ([tSet containsIndex:7]){
    NSLog(@"7 exists!");
}
if ([tSet containsIndex:8]){
    NSLog(@"8 exists!");
}

Output: 7 exists!

Stockholder answered 11/6, 2015 at 0:56 Comment(0)
C
4

It is necessary to compare the values of the NSNumbers, not the objects.

You can use objectsPassingTest:

Example:

NSMutableSet *set = [[NSMutableSet alloc] init];
NSNumber *num1 = [NSNumber numberWithInt:5];
NSNumber *num2 = [NSNumber numberWithInt:5];
NSNumber *num3 = [NSNumber numberWithInt:3];
[set addObject:num1];

NSSet *filteredSet;
filteredSet = [set objectsPassingTest:^(id obj,BOOL *stop){
    return [obj isEqualToNumber:num2];
}];
NSLog(@"Contains num2: %@", (filteredSet.count == 1) ? @"YES" : @"NO");

filteredSet = [set objectsPassingTest:^(id obj,BOOL *stop){
    return [obj isEqualToNumber:num3];
}];
NSLog(@"Contains num3: %@", (filteredSet.count == 1) ? @"YES" : @"NO");

NSLog output:

Contains num2: YES
Contains num3: NO

Or if using predicates is desired:

filteredSet = [set filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"SELF == %@", num2]];
NSLog(@"Contains num2: %@", (filteredSet.count == 1) ? @"YES" : @"NO");

filteredSet = [set filteredSetUsingPredicate:[NSPredicate predicateWithFormat:@"SELF == %@", num3]];
NSLog(@"Contains num3: %@", (filteredSet.count == 1) ? @"YES" : @"NO");
Crispation answered 8/11, 2011 at 15:25 Comment(0)
W
2

The problem is, that you are checking if num1 and num2 are the same object. And they are not. They just have the same value but they are two different object with the same value.

So what you are checking with member is whether they have the same address in memory.

Edit: You should use compare to check if the values of the numbers are the same!

Weigela answered 8/11, 2011 at 14:47 Comment(4)
"whether they have the same address in memory " is untrue; according to the member: docs: "If the set contains an object equal to object (as determined by isEqual:) then that object (typically this will be object), otherwise nil."Madel
You could both be right... I seem to recall that NSNumber has a sneaky optimisation where it has static representations of the first few integers. In this case the memory address and isEqual would work.Irrigate
Sure, but it shouldn't return false either way.Madel
I could use compare method to compare 2 NSNumbers, but I want to check if 1 NSNumber is in the set of NSNumbers, so should I loop through the set and check 1 by 1 or is there any other method that actually works as described in docs?Gallbladder
A
2

The code works since Hello word is written to the log when I run it

 NSMutableSet *set = [[NSMutableSet alloc] init];
 NSNumber *num1 = [NSNumber numberWithInt:5];
 NSNumber *num2 = [NSNumber numberWithInt:5];
 [set addObject:num1];
 if([set member:num2]){
     NSLog(@"Hello, world");
  }
Anestassia answered 18/2, 2013 at 17:10 Comment(6)
This is not quite valid since if I change the value of *num1 to 6 after I have added it to the set and then ask is [set member num1] and [set member num2] both return true.Distributive
@RyanHeitner, How can you change an NSNumber-object? They are immutable!Anestassia
NSNumber *num1 = [NSNumber numberWithInt:5]; [set addObject:num1]; num1 = [NSNumber numberWithInt:6];Distributive
@RyanHeitner, after the second assignment, num1 points to a completely different object with a different address and a different content from the num1 once stored in the set.Anestassia
using Zaph's solution above you still get the expected results and with your solution you get (at least for me) an unexpected result.Distributive
@RyanHeitner, this is your code, as I understand it: NSMutableSet *set = [[NSMutableSet alloc ] init]; NSNumber *num1 = [NSNumber numberWithInt:5]; [set addObject:num1]; num1 = [NSNumber numberWithInt:6]; NSLog(@"testing 5 => %@", [set member:[NSNumber numberWithInt:5] ]); NSLog(@"testing num1 => %@", [set member:num1 ]); And it outputs testing 5 => 5 and testing num1 => (null), as expected.Anestassia

© 2022 - 2024 — McMap. All rights reserved.