I have an undetermined size for a dataset based on unique integer keys.
I would like to use an NSMutableArray for fast lookup since all my keys are integer based.
I want to do this.
NSMutableArray* data = [NSMutableArray array]; // just create with 0 size
then later people will start throwing data at me with integer indexes (all unique) so I just want to do something like this...
if ([data count] < index)
[data resize:index]; // ? how do you resize
and have the array resized so that i can then do...
[data insertObject:obj atIndex:index];
with all the slots between last size and new size being zero which will eventually be filled in later.
So my question is how do I resize an existing NSMutableArray?
Thanks,
Roman
Use an NSPointerArray.
http://developer.apple.com/mac/library/documentation/Cocoa/Reference/Foundation/Classes/NSPointerArray_Class/Introduction/Introduction.html
NSPointerArray is a mutable collection
modeled after NSArray but it can also
hold NULL values, which can be
inserted or extracted (and which
contribute to the object’s count).
Moreover, unlike traditional arrays,
you can set the count of the array
directly. In a garbage collected
environment, if you specify a zeroing
weak memory configuration, if an
element is collected it is replaced by
a NULL value.
If you were to use a dictionary like solution, use NSMapTable. It allows integer keys. The NSMutableDictionary based solution recommended has a tremendous amount of overhead related to all of the boxing & unboxing of integer keys.
It sounds like your needs would be better met with an NSMutableDictionary. You will need to wrap the ints into NSNumber objects as follows:
-(void)addItem:(int)key value:(id)obj
{
[data setObject:obj forKey:[NSNumber numberWithInt:key]];
}
-(id)getItem:(int)key
{
return [data objectForKey:[NSNumber numberWithInt:key]];
}
There's no easy was to enlarge the size of an NSMutableArray, since you cannot have nil objects in the in-between slots. You can, however, use [NSNull null] as a 'filler' to create the appearance of a sparse array.
As in Jason's answer, an NSMutableDictionary seems to be the best approach. It adds the overhead of converting the index values to and from NSNumbers, but this is a classic space/time trade off.
In my implementation I also included an NSIndexSet to make traversing the sparse array much simpler.
See https://github.com/LavaSlider/DSSparseArray
I have to disagree with bbum's answer on this. A NSPointerArray is an array, not a sparse array, and there are important differences between the two.
I strongly recommend that bbums solution not be used.
The documentation for NSPointerArray is available here.
Cocoa already has an array object as defined by the NSArray class. NSPointerArray inherits from NSObject, so it is not a direct subclass of NSArray. However, the NSPointerArray documentation defines the class as such:
NSPointerArray is a mutable collection modeled after NSArray but it can also hold NULL values
I will make the axiomatic assumption that this definition from the documentation asserts that this is a "logical" subclass of NSArray.
Definitions-
A "general" array is: a collection of items, each of which has a unique index number associated with it.
An array, without qualifications, is: A "general" array where the indexes of the items have the following properties: Indexes for items in the array begin at 0 and increase sequentially. All items in the array contains an index number less than the number of items in the array. Adding an item to an array must be at index + 1 of the last item in the array, or an item can be inserted in between two existing item index numbers which causes the index number of all subsequent items to be incremented by one. An item at an existing index number can be replaced by another item and this operation does not change the index numbers of the existing operations. Therefore, insert and replace are two distinct operations.
A sparse array is: A "general" array where the index number of the first item can begin at any number and the index number of subsequent items added to the array has no relation to or restrictions based on other items in the array. Inserting an item in to a sparse array does not effect the index number of other items in the array. Inserting an item and replacing an item are typically synonymous in most implementations. The count of the number of items in the sparse array has no relationship to the index numbers of the items in the sparse array.
These definitions make certain predictions about the behavior of a "black box" array that are testable. For simplicity, we'll focus on the following relationship:
In an array, the index number of all the items in the array is less than the count of the number of items in the array. While this may be true of a sparse array, it is not a requirement.
In a comment to bbum, I stated the following:
a NSPointerArray is not a sparse array, nor does it behave like one. You still have to fill all the unused indexes with NULL pointers. Output from [pointerArray insertPointer:#"test" atIndex:17]; on a freshly instantiated NSPointerArray:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSConcretePointerArray insertPointer:atIndex:]: attempt to insert pointer at index 17 beyond bounds 0'
It is stated, without proving, the the behavior of NSPointerArray above violates the very definition of a sparse array. This part of the error message is revealing: attempt to insert pointer at index 17 beyond bounds 0', in particular the part about having to add the first new item at index 0.
bbum then comments:
That is incorrect. You failed to call -setCount: to set the capacity to a sufficient size.
It is non-sensical to "set the count" of the number of items in a sparse array. If NSPointerArray was a sparse array, one would expect that after adding the first item at index 17, the count of the number of items in the NSPointerArray would be one. However, following bbums advice, the number of items in the NSPointerArray after adding the first items is 18, not 1.
QED- It is shown that a NSPointerArray is in fact an array, and for the purposes of this discussion, a NSArray.
Additionally, bbum makes the following additional comments:
NSPointerArray most certainly does support holes.
This is provably false. An array requires all items contained in it to contain something, even if that something is 'nothing'. This is not true of a sparse array. This is the very definition of a 'hole' for the purposes of this discussion. A NSPointerArray does not contain holes in the sparse array sense of the term.
That was one of the whole points of writing the class. You have to set the count first.
It is provably non-sensical to "set the count" of a sparse array.
Whether the internal implementation is a sparse array or a hash or, etc, is an implementation detail.
This is true. However, the documentation for NSPointerArray does not make any reference to how it implements or manages its array of items. Furthermore, it does not state anywhere that a NSPointerArray "efficiently manages an array of NULL pointers."
QED- bbum is depending on the undocumented behavior that a NSPointerArray efficiently handles NULL pointers via a sparse array internally. Being undocumented behavior, this behavior can change at any time, or may not even apply to all uses of the NSPointerArray. A change in this behavior would be catastrophic if the highest index number stored in it are sufficiently large (~ 2^26).
And, in fact, it is not implemented as one big hunk of memory...
Again, this is a private implementation detail that is undocumented. It is extremely poor programming practice to depend on this type of behavior.
Related
How do you fill a NSMutableArray with a set capacity for later use?
Basically I want to set up a NSMutableArray to act as a map for my game objects, so I have this line...
gameObjects = [[NSMutableArray alloc] initWithCapacity:mapWidth*mapHeight];
Which I had hoped would create and fill my MutableArray so I can get then access it with this kind of index...
int ii = (cellY*mapWidth)+cellX;
NSDictionary *currentObject = [gameObjects objectAtIndex:ii];
But I just learned initWithCapacity doesn't fill the array, so should I create blank objects to fill it with, or is there a Null that I can fill it with? Also would I do that with 2 for loops or is there an instruction something like "initWith:myObject" ?
I want to be able to check at a certain index within the array to see if there's an object there or not, so I need to be able to acces that index point, and I can only do that if there's something there or I get an out of bounds error.
I'll be using this NSMutableArray pretty much as a grid of objects, it's a 1 dimensional array organised as a 2 dimensional array, so I need to be able to fill it with mapWidth*mapHeight of something, and then calculate the index and do a check on that index within the array.
I've looked on here and googled but couldn't find anything like what I'm asking.
Thanks for any advice.
I think what you are looking for is [NSNull null]. It is exactly what you want- a placeholder value.
You can find more information on the topic in this question.
initWithCapacity is just a performance optimization -- it has no effect on the array behavior, it just keeps the code "under the covers" from having to repeatedly enlarge the internal array as you add more entries.
So if you want a "pre-allocated" array, you'd need to fill it with NSNull objects or some such. You can then use isKindOfClass to tell if the object is the right type, or simply == compare the entry to [NSNull null]. (Since there's only ever one NSNull object it always has the same address).
(Or you could use a C-style array of pointers with nil values for empty slots.)
But you might be better off using an NSMutableDictionary instead -- no need to pre-fill, and if the element isn't there you get a nil pointer back. For keys use a NSNumber object that corresponds to what would have been your array index.
initWithCapacity only hints to NSMutableArray that it should support this many objects. It won't actually have any objects in it until you add them. Besides, every entry in the array is a pointer to an object, not a struct like you'd normally have in a standard c array.
You need to change how you're thinking about the problem. If you don't add an object to the array, it's not in there. So either you pre-fill the array with "empty" objects as you've said, which is weird. Or you can add the objects as you need them.
Say I have a collection of "node" instances. An integer property call zIndex will be used to group them.
What are the pros/cons for storing them in :
1) An array of arrays
2) A dictionary of arrays
In pseudo code, I would describe the expected result like this:
zBuffer[100] = [node1, node 2];
zBuffer[105] = [playerNode, collectable1];
zBuffer[110] = [foreground1, foreground2];
And I'm wondering about what zBuffers should be; Must NSArrays only be used for sequential read/write? Like not using non-continuous indexes?
I tried with an NSMutableArray:
[zBuffer objectAtIndex:zOrder]
But it fails if the array contains no data for that index (like out-of-bound exception).
Thanks for your advices!
J
As far as I can see, one of your requirements is that the indexes you use to access zBuffer be not contiguous (100, 105, 100). In this case, I would not use an array for that, since the indexes you can use with an array must be less than the count of elements of the array (if you have 3 elements, then indexes range from 0 to 2).
Instead I would use NSMutableDictionary, where you can use the zIndex key as a "name" for groups of objects you are looking for.
This suggestion does not take into account any other requirements that you might have, especially concerning complexity and the kind of operations you are going to carry through on your collection of nodes (beyond accessing them through zIndex).
You could actually provide both. It looks like what you want to have is a sparse array: so you look up objects by index, but it's permissible for there not to be an object at a certain index. So you could make that.
I'd do that by creating an NSMutableArray subclass that implements the primitive methods documented. Internally, your subclass would use an NSMutableDictionary for storage, with numbers (the "filled" indices) as keys. -objectAtIndex: returns either the object with that number as its key or nil if the array is empty at that point.
There are some ambiguities in this use of the array contract that it's up to you to decide how to address:
does count return 1+(highest index in use), or the number of objects in the array?
the enumerator and fast enumeration patterns never expect to see nil, so you need to come up with an enumerator that always returns an object (but lets me see what index it's at) if you want users of your class to enumerator over the array.
you won't be able to initialise it with the +arrayWithObjects: (id) firstObject,... pattern of initialisers because they use nil as a sentinel.
Is there any way to manually control which index we want to add our data to with an NSMutableArray? In PHP, for example, we can do:
$foo[2] = "fafa";
$foo[8] = "haha";
Can the manual assigning of indexing be done on NSMutableArray?
NSMutableArrays are not sparse -- they cannot have gaps where no objects are stored. It is possible to insert an object at any index from 0 to length - 1, using insertObject:atIndex: (as Edu mentioned), but other than that you can only append objects (using addObject:).
If you need to associate objects with arbitrary integers, you can use an NSMapTable -- the functional interface allows integer keys, or simply use an NSMutableDictionary, with NSNumber keys (as drewag suggested).
See also: How to do sparse array in Cocoa
Just because it's not entirely clear to me that TeamStar wants a sparse array, I'll just mention that you can assign objects to specific indexes within an NSMutableArray – so long as the array is already long enough – using -replaceObjectAtIndex:withObject: and the other -replace… methods.
No that is not possible. I would suggest using an NSMutableDictionary instead. You can use NSNumbers as indexes.
- (void)insertObject:(id)anObject atIndex:(NSUInteger)index
Important Raises an NSRangeException if index is greater than the number of elements in the array.
Short:
I need to find core data objects by a key, which holds a unique immutable array (fixed length, but chosen at runtime) of arbitrary objects (for which not only element membership, but also element order determines uniqueness). NSManagedObject however forbids overriding [isEqual:]. Now what?
Long:
I have an entity (see diagram image for entity "…Link") in my Core Data model for which I have to guarantee uniqueness based on an attribute key ("tuple"). So far so good.
The entity's unique attribute however has to be an NSArray.
And to make things a bit more difficult I neither know the class type of the tuple's elements.
Nor do I know the tuple's element count. Well, actually the count is the same for every tuple (per core data context at least), but not known before the app runs.
There must only ever be one instance of my link entity with a given tuple.
And for obvious reason only ever one tuple instance with a given array of arbitrary objects.
Whereas two tuples are to be considered equal if [tuple_1 isEqual:tuple_n] returns YES. NSManagedObject forbids the overriding of [isEqual:] and [hash] though, otherwise things would be pretty much a piece of cake.
"…Tuple" objects are created together with their array of tokens (via a convenience method) and are immutable (and so is each "…Token" and its data attribute). (think of "…Tuple" as a "…Link"'s dictionary key.)
"…Tuple" implements "- (NSArray *)tokens;", which returnes a neatly ordered array of tokens, based on the "order" keys of "…TokenOrder". (Tuples are expected to contain at most 5 elements.)
I however expect to have tens of thousands (potentially even more in some edge cases) of "…Link" objects, which I have to (frequently) find based on their "tuple" attribute.
Sadly I couldn't find any article (let alone solution) for such a scenario in any literature or the web.
Any ideas?
A possible solution I've come up with so far would be:
Narrow amount of elements to compare
by tuple by adding another attribute
to "…Tuple" called "tupleHash",
which is pre-calculated on
object creation via: Snippet 1
Query with NSPredicate for objects of matching tupleHash (narrowing down the list of candidates quite a bit).
Find "…Link" featuring given tuple in narrowed candidate list by: Snippet 1
Snippet 1:
NSUInteger tupleHash = [[self class] hash];
for (id token in self.tokens) {
tupleHash ^= [token.data hash];
}
Snippet 2:
__block NSArray *tupleTokens = someTokens;
NSArray *filteredEntries = [narrowedCandidates filteredArrayUsingPredicate:
[NSPredicate predicateWithBlock: ^(id evaluatedObject, NSDictionary *bindings) {
return [evaluatedObject.tuple.tokens isEqualToArray:tupleTokens];
}]];
(Sorry, markdown appears to oppose mixing of lists with code snippets.)
Good idea of or just insane?
Thanks in advance!
I strongly suggest that you calculate a hash for your objects and store it in your database.
Your second snippet will seriously hurt performance, that's for sure.
Update:
You don't need to use the hash method of NSArray.
To calculate the hash, you can perform a SHA1 or MD5 on the array values, concatenated. There are many algorithms for hashing, these are just two.
You can create a category for NSArray, say myHash to make the code reusable.
As recommended in a comment by Joe Blow I'm just gonna go with SQLite. Core Data simply appears to be the wrong tool here.
Benefits:
Fast thanks to SQL's column indexing
No object allocation/initialization on SELECT, prior to returning the results. (which Core Data would require for attribute checks)
Easily query link tuples using JOINs.
Easy use of SQLite's JOIN, GROUP BY, ORDER BY, etc
Little to no wrapper code thanks to EGODatabase (FMDB-inspired SQLite Objective-C wrapper)
I have two arrays in Objective C and I need to find what index something is so I can insert it in the same place. For instance, lets say I have a "name array" and an "age array". How do I find out what index "charlie" is in the "name array" so I know where to insert his age in the "age" array?
Thanks
-[NSArray indexOfObject:] would seem to be the logical choice.
In Cocoa, parallel arrays are a path to doom and ruination. You can't use them effectively with Bindings, so you'll have to write a lot of glue code instead, as if Bindings didn't exist. Moreover, you're killing off any future AppleScript/Scripting Bridge support you may intend to have before you even begin to implement it.
The correct way is to create a model class with name and age properties, and have a single array of instances of that class. Then, to find an item by name or age, use NSPredicate to filter the array, and indexOfObjectIdenticalTo: to find the index of each item from the filtered array in the main array.
The difference between indexOfObject: and indexOfObjectIdenticalTo: is that the former will send isEqual: messages to determine whether each object is the one it's looking for, whereas the latter will only look for the specific object you passed in. Thus, you can use indexOfObject: with an object that isn't in the array but is equal to one that is, in order to find the equal object in the array.
You might just want to use an NSDictionary, too, if you're doing lookups based on strings.