Reputation: 1433
I have a bunch of objects in a set of categories. I'd like to know how many of each category of there are.
In another language, I'd make a dictionary, then iterate over the objects, incrementing the appropriate value in the dictionary for each one. However, because I can't store a native numeric type in an NSDictionary in Objective-C, this has me constantly converting back and forth between NSNumber and a numeric type:
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
for (MyObject *obj in objs) {
NSNumber *old = [dictionary objectForKey:obj.category];
NSNumber *new = [NSNumber numberWithInteger:1 + old.integerValue];
[dictionary setObject:new forKey:obj.category];
}
Is there a more efficient way to do this?
Upvotes: 2
Views: 174
Reputation: 84338
You're looking for NSCountedSet
.
NSCountedSet *bag = [[NSCountedSet alloc] init];
for (MyObject *obj in objs) {
[bag addObject:obj.category];
}
for (id category in bag) {
NSLog(@"%d instances of %@", [bag countForObject:category], category);
}
Upvotes: 6
Reputation: 365925
If it really is a bottleneck, you can use the underlying CFDictionaryRef instead of NSMutableDictionary, to create a dictionary that stores integers directly, instead of boxing them in NSNumber values.
Read the documentation on CFDictionaryCreateMutable and CFDictionaryValueCallBacks for details, but the basic idea is that your retain and release do nothing, your description generates an NSNumber on the fly (or just does an stringWithFormat:"%d"), and your equal compares the ints directly.
Here's a sample showing the tricky parts of the code:
#import <CoreFoundation/CoreFoundation.h>
#import <Foundation/Foundation.h>
CFStringRef intdesc(const void *value) {
int i = (int)value;
CFNumberRef n = CFNumberCreate(NULL, kCFNumberIntType, &i);
CFStringRef s = CFCopyDescription(n);
CFRelease(n);
return s;
}
Boolean inteq(const void *value1, const void *value2) {
int i1 = (int)value1, i2 = (int)value2;
return i1 == i2;
}
int main(int argc, char *argv[]) {
CFDictionaryValueCallBacks cb = { 0, NULL, NULL, &intdesc, &inteq };
CFMutableDictionaryRef d =
CFDictionaryCreateMutable(NULL,
0,
&kCFTypeDictionaryKeyCallBacks,
&cb);
CFDictionarySetValue(d, @"Key1", (void *)1);
CFDictionarySetValue(d, @"Key2", (void *)2);
CFStringRef s = CFCopyDescription(d);
NSLog(@"%@", s);
CFRelease(s);
CFRelease(d);
return 0;
}
If you're gong to do a lot of this, you should probably wrap this up in ObjC (especially if you're using ARC), but that's left as an exercise for the reader.
Upvotes: 0
Reputation: 31016
If this turned out to be a big time-waster, you could:
But I certainly agree with the commenter who said, essentially, to measure first.
Upvotes: 0
Reputation: 15788
You could use NSMutableData to store your counters:
int * count;
NSMutableData * num;
NSMutableDictionary * dictionary;
dictionary = [[NSMutableDictionary alloc] init];
for (MyObject *obj in objs)
{
if ((count = [[dictionary objectForKey:obj.category] mutableBytes]) == nil)
{
num = [NSMutableData dataWithCapacity:sizeof(int)];
count = [num mutableBytes];
*count = 0;
[dictionary setObject:num forKey:obj.category];
};
*count += 1;
};
This prevents the dictionary from being modified and a new NSNumber
from being allocated each time a category's count is updated
Upvotes: 1