views:

1000

answers:

3

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

+7  A: 

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.

Jason
Thanks for the quick answer. I was hoping for constant lookup times but this isn't too bad. Hopefully my dataset sizes will not be too large. Thanks.
a dictionary is a hashset, it supports constant time lookups.
twolfe18
Ah! I did not know that. I was assuming it was log(N). Thanks for the info.
Nitpick- hash table lookup times are `O(1)` __iff__ every key hashes to a unique value- that is, there are no hash collisions. If two keys hash to the same value, then the implementation needs to deal with this- a common approach is to just use a linked list. Good hash table implementations deal with these details for you, say by dynamically growing the number of hash slots as more items are added to the table so that collisions are statistically unlikely. It's safe to say that a good implementation, such as `NSMutableDictionary`, provides "practically `O(1)`" lookup times.
johne
NSPointerArray directly supports an array of objects with holes in it. If a hashing solution is desired, NSMapTable can support integer keys (through the function based API).
bbum
@bbum In the context of this question wrt/ to sparse arrays, `NSPointerArray` does not not support `array of objects with holes in it.`. From `insertPointer:atIndex:` documentation for the `index` argument - `This value must be less than the count of the receiver.`. `NSPointerArray` is effectively identical to @Jason's `NSMutableArray` / `[NSNull null]` solution, at least for the OP's purposes- you'd just be filling the holes with `NULL`'s and not `[NSNull null]`'s.
johne
NSPointerArray most certainly does support holes. That was one of the whole points of writing the class. You have to set the count first. Whether the internal implementation is a sparse array or a hash or, etc, is an implementation detail.
bbum
And, in fact, it is not implemented as one big hunk of memory...
bbum
+23  A: 

Use an NSPointerArray.

http://developer.apple.com/mac/library/documentation/Cocoa/Reference/Foundation/Classes/NSPointerArray%5FClass/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.

bbum
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'`
johne
That is incorrect. You failed to call -setCount: to set the capacity to a sufficient size.
bbum
A: 

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.

johne
I'm going to go way out on a limb here and suggest that Bbum describes NSPointerArray as a sparse array because he has first-hand knowledge of how it's implemented.
NSResponder
*I will make the axiomatic assumption that this definition from the documentation asserts that this is a "logical" subclass of NSArray.* That would be an incorrect assumption.
bbum
Couldn't we settle this argument by adding a simple category to NSPointerArray:@implementation NSPointerArray (HHAdditions)- (void)setPointer:(void *)item atIndex:(NSUInteger)index{ if (! (index < [self count])) { [self setCount:(index + 1)]; } [self replacePointerAtIndex:index withPointer:item];}@endNow count is set automatically. Items are replaced rather than shifted.
Pierre Bernard