I have a list of long values:
300210, 300211, 310210, 310211, ...
I'm looking for the best way to check whether a number is present in a collection. The collection is non mutable and this check can possibly happen hundreds of time per second (it's part of a physics engine collision presolving).
If using an NSArray, I'm to use NSNumbers. These are objects - Is the containsObject: method using hashcodes? Or does it consistently use a value comparison (rather than pointer address)?
How about NSSet? I know it has a member: method to use isEqual: but no practical experience with it.
thanks for your help find the best way to address this.
I would suggest turn on objective-C++ and use std::set. It's much faster then NSSet.
You will need:
in header:
#include <set>
using namespace std;
in code:
set<int> numberCollection;
Using NSArray, if the array contains NSNumber then you can use containsObject:
as they match with the value not with the pointers.
NSNumber *num3 = [NSNumber numberWithInteger:3];
NSArray *array = #[#1, #2, num3, #4, #5];
BOOL isExists = [array containsObject:#3]; // yes
Also with NSSet you can do similar way:
NSSet *set = [NSSet setWithArray:array];
BOOL isExists = [set containsObject:#3];
You can read a great article about Objective-C collections and their performance at objc.ico.
In your case (checking if a collection contains given object) the NSSet is definitely the best choice.
NSSet and its mutable variant NSMutableSet are an unordered collection of objects. Checking for existence is usually an O(1) operation, making this much faster for this use case than NSArray.
Related
this is actually a question that i'd been trying to solve...
i need to implement this functionality in NSSet...
I know how hash tables and sets works.. and NSSet seems to store the pointer to the objects inside the hash table using the HASH as the index of that array... when more than one object falls in that hash.. it uses isEqual to detect which one of the objects is the member we search... that means...
HASH value => gives the index of the array of pointers in the hash table, and each one of those pointers points to an array (or some collection) that holds the objects with that hash (as it iterates over it to detect which object is the member)... this is a fairly common data struct...
My question is... is there a way to retrieve the array of objects that is being pointed by the hash table... i need ALL the objects that have the SAME HASH VALUE inside an NSSet...
i need this in order to quickly process proximity between points...
Is there a way? i know i can use a predicate using
[NSPredicate predicateWithFormat:#"hash == %u",hash];
but this uses an enumeration and is not as fast as it needs to be (real fast)
Is there a way or should i create a HASH TABLE from scratch? (or use core foundation)
Thanks and sorry for the trouble!
if you want the object in an array just call -allObjects, if you want all hashes, then you will have to iterate through them, because they are longs and can't be stored in an NSArray directly.
I had the idea to make a mock object that overrides its own hash, then you could search through an array for the index of this object that is pretending to be your object.
#interface MockHasher : NSObject{
NSUInteger mockHash;
}
#property(assign,nonatomic,getter = hash,setter = setHash:)NSUInteger mockHash;
#end
#implementation MockHasher
#synthesize mockHash;
-(BOOL)isEqual:(id)object{return YES;}
-(BOOL)isEqualTo:(id)object{return YES;}
#end
example:
NSSet * myset = [NSSet setWithObject:#(1)];
MockHasher * mockObject = [[MockHasher new] autorelease];
mockObject.hash = #(1).hash;
NSArray * allObjects = [myset allObjects];
NSUInteger i = [allObjects indexOfObject:mockObject];
id result = [allObjects objectAtIndex:i];
NSLog(#"result = %#",result);
It is fragile, because it is depending on the array asking the object passed in for isEqual: rather than asking the iterated object... I don't know how reliable this is... but it worked in my test.
As of iOS 6.0 and MacOS 10.5, you now have an actual NSHashTable object to work with. Its modeled after NSSet, but instead is it's own thing. Here's some additional Apple documentation on Hash Tables, as well.
I have a NSSet containing many thousands of NSValue objects (wrapping CGPoints). I would like to very quickly find if a given CGPoint value exists in the NSSet. It seems to me that the member: method of an NSSet might do the job here, except that it checks for equality using isEqual:. NSValue objects use isEqualToValue:, and so when I execute the code:
[mySet member:valueToCheck];
it actually causes Xcode to crash.
1) Is there some way to use a custom equality check to make this work for NSValue objects?
2) Is this even the best approach (i.e. is member: quick enough in the first place)? The scenario is that I have a NSSet containing a large number of points representing pixels on the screen (iPad). Later on I need to bombard that set with many thousands of points per second to see if they exist in the set. My approach seems crude for achieving this. I thought about creating something like a huge 2-dimensional bit array, with each index representing a pixel on screen. Once I know the point I'm testing for, I can just jump straight to that point in the array and check for a 1 or 0... does this sound better or worse?
Thanks
Can you get this to a simple reproducible case? For example, I just tried:
NSValue *v = [NSValue valueWithCGPoint:CGPointMake(1, 1)];
NSSet *s = [NSSet setWithObject:v];
NSLog(#"%#", [s member:[NSValue valueWithCGPoint:CGPointMake(1, 1)]]);
But it works just fine.
edit
-isEqual: is not the problem:
NSValue *v1 = [NSValue valueWithPoint:NSMakePoint(1, 1)];
NSValue *v2 = [NSValue valueWithPoint:NSMakePoint(1, 1)];
NSLog(#"%d", [v1 isEqual:v2]); //logs "1"
-hash is not the problem:
NSLog(#"%d", ([v1 hash] == [v2 hash])); //logs "1"
They are different objects:
NSLog(#"%d", (v1 != v2)); //logs "1"
The problem is in your code. Try cleaning and rebuilding.
To answer no. 2:
I don't know how NSSet is implemented internally, but considering that you know you are storing points (with X and Y), I think you would be better by implementing your own partitioning algorithm. Personally I would choose my own implementation over NSSet if you say you have thousands of points.
Storing huge 2-dimensional arrays for each pixel, would probably be the fastest way, but it will kill you in terms of memory consumption. You need something fast, but also lightweight.
There are a lot of algorithms out there and you can find them by searching "spatial partitioning algorithms" on wikipedia or google. It also depends on your programming skills, and how much time you are willing to invest in this.
For example, a pretty simple one would be to implement a quad-tree, where you start by diving your screen(or area) in 4 equal parts. Then if and where is needed, you divide that specific cell also in 4 parts. And you do this until each cell contains a small enough number of points so that you can brute-force test all of them.
You can find a very good description on wiki: http://en.wikipedia.org/wiki/Quadtree
Hope this helps,
[mySet member:valueToCheck] should not be crashing. NSValue's isEqual: works fine when I try it here, and in fact probably calls isEqualToValue: when given another NSValue to compare to. Is valueToCheck really an NSValue, or is it a CGPoint?
There is no way to override the default hash and comparison methods for NSSet. But NSSet is toll-free bridged with CFSetRef, and you can easily specify custom hashing and comparison methods there:
CFSetCallBacks callbacks = kCFTypeSetCallBacks;
callbacks.equal = customEqualFunction;
callbacks.hash = customHashFunction;
NSMutableSet *set = (NSMutableSet *)CFSetCreateMutable(NULL, 0, &callbacks);
The constraints on these functions are presumably the same as on NSObject's hash and isEqual: methods, anything that is equal must have the same hash. The C-style prototypes for customEqualFunction and customHashFunction are described here and here.
One solution would be to subclass NSSet and override member: to do your own comparison. Your own comparison could then simple call isEqualToValue:. Have a look at the subclassing notes in the NSSet documentation.
Another approach would be to add a category to NSValue that implements isEqual:. In this case I'd prefer subclassing because it's a more constrained solution.
It's not just a problem with -isEqual:, you may also have an issue with the -hash method. If you want to use an NSSet, you should probably create a custom class that wraps the CGPoint. -isEqual: is then trivial and -hash could be implemented by some method of combining the bits of both coordinates and then treating them as a NSUInteger.
You'll also want to implement the NSCopying protocol which is also trivial if your points are immutable (just retain and return self in -copyWithZone:).
Well, I've been learning Objective-C for a while now, and I don't get why creating an NSArray would be beneficial for you. It's just a collection of some stuff right? Why can't you just use them without making an NSArray. Or can you use the objects in it in the implementation of every one of your methods (even if it's a local ivar).
So, any help would be appreciated. Thanks guys!
You could make instance variables for every “item” you need:
NSString *str1;
NSString *str2;
NSString *str3;
…but that’s hard to work with, an array is simply more convenient. What would you do if you wanted to print all these strings?
NSLog(#"%#", str1);
NSLog(#"%#", str2);
NSLog(#"%#", str3);
Wouldn’t it be easier to loop over an array?
NSArray *strings = [NSArray arrayWithObjects:#"one", #"two", #"three", nil];
for (NSString *str in strings)
NSLog(#"%#", str);
How about if you have ten, twenty strings? And what if you don’t know how many strings you will need? What if you want to pass all these items to somebody else? Are you going to pass them one by one?
- (void) doSomethingWithString1: (NSString*) str1 andString2: (NSString*) str2…;
Or would you rather pass an array?
- (void) doSomethingWithMyStrings: (NSArray*) strings;
Very often, the NSArray you see is a front for an NSMutableArray working behind the scenes.
Billing it in the interface as an NSArray is just a way to ensure that it cannot be manipulated from without, which could have moderately disastrous consequences. (Thus, when asking for an NSArray from an object, you'll usually get a copy of the NSMutableArray used internally.)
Understanding why creating an NSMutableArray is beneficial is left as an exercise to the reader.
Different contexts.
When you use an NSString it's generally used for a single value. It's quite simple.
NSArray
If you want to loop through many keys/values, use an NSArray. It is not possible to loop through NSStrings because they don't have the order structer that NSArray's do.
Why use NSString
It would be silly to call myArray[0] and foo[0] every time you saved just a value. NSString is simpler than NSArray, since you can easily set a value on a GUI element (without having to call myArray[0])
Why use NSArray
It is more flexible than NSString because NSArray can hold multiple values, which may be needed in certain situations.
Weighing pros and cons
Pros (NSArray):
Can make your code cleaner (call myArray[1] and myArray[0] instead of mystring and foo)
Simplified memory management (easier to release one NSArray than many NSStrings)
Cons (NSArray):
Can make your code very complicated (was that on myArray[0] or 1?)
Might be difficult to know what type of data is in the array (if it's an NSString, it's always going to be a string, an array can hold many different types of data). You wouldn't use 50 tissues to dry yourself off from the shower but use a towel.
So it boils down to one thing. What context are you using?
I need to detect change in NSArray object - that is if some object was added/removed to/from NSArray or was just edited in-place. Are there some integrated NSArray hash functions for this task - or I need to write my own hashing function for NSArray ? Maybe someone has different solution ? Any ideas ?
All objects have a -hash method but not all objects have a good implementation.
NSArray's documentation doesn't define it's result, but testing reveals it returns the length of the array - not very useful:
NSLog(#"%lu", #[#"foo"].hash); // output: 1
NSLog(#"%lu", #[#"foo", #"bar"].hash); // output: 2
NSLog(#"%lu", #[#"hello", #"world"].hash); // output: 2
If performance isn't critical, and if the array contains <NSCoding> objects then you can simply serialise the array to NSData which has a good -hash implementation:
[NSArchiver archivedDataWithRootObject:#[#"foo"]].hash // 194519622
[NSArchiver archivedDataWithRootObject:#[#"foo", #"bar"]].hash // 123459814
[NSArchiver archivedDataWithRootObject:#[#"hello", #"world"]].hash // 215474591
For better performance there should be an answer somewhere explaining how to write your own -hash method. Basically call -hash on every object in the array (assuming the array contains objects that can be hashed reliably) and combine each together mixed in with some simple randomising math.
You could use an NSArrayController, which is Key-Value-Observing compliant. Unfortunately NSArray is only KVC compliant. This way you can easily monitor the array controller's arrangedObjects property. This should solve your problem.
Also, see this question: Key-Value-Observing a to-many relationship in Cocoa
I want to create an NSArray with objects of the same value (say NSNumber all initialized to 1) but the count is based on another variable. There doesn't seem to be a way to do this with any of the intializers for NSArray except for one that deals with C-style array.
Any idea if there is a short way to do this?
This is what I am looking for:
NSArray *array = [[NSArray alloc] initWithObject:[NSNumber numberWithInt:0]
count:anIntVariable];
NSNumber is just one example here, it could essentially be any NSObject.
The tightest code I've been able to write for this is:
id numbers[n];
for (int x = 0; x < n; ++x)
numbers[x] = [NSNumber numberWithInt:0];
id array = [NSArray arrayWithObjects:numbers count:n];
This works because you can create runtime length determined C-arrays with C99 which Xcode uses by default.
If they are all the same value, you could also use memset (though the cast to int is naughty):
id numbers[n];
memset(numbers, (int)[NSNumber numberWithInt:0], n);
id array = [NSArray arrayWithObjects:numbers count:n];
If you know how many objects you need, then this code should work, though I haven't tested it:
id array = [NSArray arrayWithObjects:(id[5]){[NSNumber numberWithInt:0]} count:5];
I can't see any reason why this structure in a non-mutable format would be useful, but I am certain that you have your reasons.
I don't think that you have any choice but to use a NSMutableArray, build it with a for loop, and if it's really important that the result not be mutable, construct a NSArray and use arrayWithArray:
I agree with #mmc, make sure you have a valid reason to have such a structure (instead of just using the same object N times), but I'll assume you do.
There is another way to construct an immutable array which would be slightly faster, but it requires creating a C array of objects and passing it to NSArray's +arrayWithObject:count: method (which returns an autoreleased array, mind you) as follows:
id anObject = [NSNumber numberWithInt:0];
id* buffer = (id*) malloc(sizeof(id) * anIntVariable);
for (int i = 0; i < anIntVariable; i++)
buffer[i] = anObject;
NSArray* array = [NSArray arrayWithObjects:buffer count:anIntVariable];
free(buffer);
You could accomplish the same thing with even trickier pointer math, but the gains are fairly trivial. Comment if you're interested anyway.
Probably the reason there is no such method on NSArray is that the semantics are not well defined. For your case, with an immutable NSNumber, then all the different semantics are equivalent, but imagine if the object you were adding was a mutable object, like NSMutableString for example.
There are three different semantics:
retain — You'd end up with ten pointers to the same mutable string, and changing any one would change all ten.
copy — You'd end up with ten pointers to the same immutable string, or possibly ten different pointers to immeduable strings with the same value, but either way you'd not be able to change any of them.
mutableCopy — You'd end up with ten different mutable string objects, any of which you could change independently.
So Apple could write three variants of the method, or have some sort of parameter to control the semantics, both of which are ugly, so instead they left it to you to write the code. If you want, you can add it as an NSArray category method, just be sure you understand the semantic options and make it clear.
The method:
-(id)initWithArray:(NSArray *)array copyItems:(BOOL)flag
has this same issue.
Quinn's solution using arrayWithObjects:count: is a reasonably good one, probably about the best you can get for the general case. Put it in an NSArray category and that's about as good as it is going to get.