Is there any downside to using NSSet as key in NSMutableDictionary, any gotchas to be aware of, any huge performance hits?
I think keys are copied in Cocoa containers, does it mean NSSet is copied to dictionary? Or is there some optimization that retains the NSSet in this case?
Related to Can a NSDictionary take in NSSet as key?
Example code:
NSMutableDictionary * dict = [NSMutableDictionary dictionary];
NSSet * set;
set = [NSSet setWithObjects:#"a", #"b", #"c", #"d", nil];
[dict setObject:#"1" forKey:set];
set = [NSSet setWithObjects:#"b", #"c", #"d", #"e", nil];
[dict setObject:#"2" forKey:set];
id key;
NSEnumerator * enumerator = [dict keyEnumerator];
while ((key = [enumerator nextObject]))
NSLog(#"%# : %#", key, [dict objectForKey:key]);
set = [NSSet setWithObjects:#"c", #"b", #"e", #"d", nil];
NSString * value = [dict objectForKey:set];
NSLog(#"set: %# : key: %#", set, value);
Outputs:
2009-12-08 15:42:17.885 x[4989] (d, e, b, c) : 2
2009-12-08 15:42:17.887 x[4989] (d, a, b, c) : 1
2009-12-08 15:42:17.887 x[4989] set: (d, e, b, c) : key: 2
I think keys are copied in Cocoa containers, does it mean NSSet is copied to dictionary? Or is there some optimization that retains the NSSet in this case?
NSDictionaries do copy their keys.
An immutable set will probably respond to copy by returning itself retained, making the “copy” practically free.
A mutable set will respond to copy by returning a copy of itself, which is why using mutable objects as keys is generally a bad idea (you won't be able to find the original after mutating it because it no longer compares equal to the key in the dictionary).
Ooh. Yes. There is a big performance downside. It happens that -[NSSet hash] is implemented as [set count]. That means that if all your sets have 2 objects, say, then they all have the same hash, and the collection will perform very poorly.
Related
I want to know how to invert a NSDictionary.
I've seen some crazy code like
NSDictionary *dict = ...;
NSDictionary *swapped = [NSDictionary dictionaryWithObjects:dict.allKeys forKeys:dict.allValues];
which is according to documentation not safe at all since the order of allValues and allKeys is not guaranteed.
NSDictionary *dict = ...;
NSMutableDictionary *swapped = [NSMutableDictionary new];
[dictionary enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) {
swapped[value] = key;
}];
Note that the values should also conform to the NSCopying protocol.
You're right, that code is crazy, but there are two ways to get an array of the values in the order given by an array of keys:
NSArray * keys = [dict allKeys];
NSArray * vals = [dict objectsForKeys:keys notFoundMarker:nil];
NSDictionary * inverseDict = [NSDictionary dictionaryWithObjects:keys
forKeys:vals];
Or
NSUInteger count = [dict count];
id keys[count];
id vals[count];
[dict getObjects:vals andKeys:keys];
NSDictionary * inverseDict = [NSDictionary dictionaryWithObjects:keys
forKeys:vals
count:count];
The former is obviously a lot nicer. As noted in hfossli's answer, the objects that were values in the original dictionary must conform to NSCopying in order to be used as keys in the inversion.
So I have an object of type 'id friendData'stored in a singleton class 'coreData' from which I produce a mutable array for object key "data" as follows:
NSMutableArray *friends = [_coreData.friendData objectForKey:#"data"];
I then establish a dictionary which takes user parameters using keys "id" and "name", as well as a NSMutable array *scores which is obtained by a HTTP post request.
NSMutableDictionary *directory = [NSMutableDictionary dictionary];
[directory setObject:[friends valueForKey:#"id"] forKey:#"id"];
[directory setObject:[friends valueForKey:#"name"] forKey:#"name"];
[directory setObject:scores forKey:#"score"];
I am wanting to order object scores from highest to lowest for the purposes of a scoreboard, but it's my understanding that rearranging within a dictionary won't maintain the same order for the objects 'id' and 'name'. Is this infact possible, or is it better to reintroduce the *scores object to *friends under an appropriate key, and apply a sort algorith? If so, how? Any help, including example code and possible sort procedure would be great!
Just sort scores array before setting it into the dictionary. I am not sure what are exactly the kind of objects you have in scores, Im guessing they are plain NStrings. In that case this should work:
NSArray *sortedArray = [scores sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
NSInteger first = [(NSString *)a intValue];
NSDate *second = [(NSString *)b intValue];
return [first compare:second];
}];
NSMutableDictionary *directory = [NSMutableDictionary dictionary];
[directory setObject:[friends valueForKey:#"id"] forKey:#"id"];
[directory setObject:[friends valueForKey:#"name"] forKey:#"name"];
[directory setObject:sortedScoresArray forKey:#"score"];
I know that in NSDictionary, there are no guarantees about the order of items when enumerating.
However, is it safe to expect that [[myDictionary allValues] objectAtIndex:index] will always be the matching value of key [[myDictionary allKeys] objectAtIndex:index]? (supposing that the dictionary is immutable).
Even if there is a very good chance that the two are going to be in the same order, Apple can change it at any time, because there is simply no guarantee of the order.
If you need two NSArrays, one with keys and one with values, with "parallel" indexes, a better approach to this would be building these arrays yourself by enumerating key-value pairs of the dictionary, and adding them to the two arrays that you build:
NSMutableArray *keys = [NSMutableArray array];
NSMutableArray *vals = [NSMutableArray vals];
[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
[keys addObject:key];
[vals addObject:obj];
}];
You can also get keys separately, and then get values in an array with parallel indexing by calling objectsForKeys:notFoundMarker: method:
NSArray *keys = [dict allKeys];
// In the call below, the marker [NSNull null] will not be used, because we know
// that all keys will be present in the dictionary.
NSArray *vals = [dict objectsForKeys:keys notFoundMarker:[NSNull null]];
Just curious if there is a way to find the number of keys in a NSMutableDictionary? Also is there a way to access each key in turn and find its value, or do I need to access the data manually via the predefined keys?
(i.e.)
myTown = [cryo objectForKey: #"town"];
myZip = [cryo objectForKey: #"HT6 4HT"];
myEmail = [cryo objectForKey: #"pink#grapefruit.xxx"];
I guess I am thinking using a wildcard or something for the key?
gary
-[NSMutableDictionary count]
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
NSLog(#"%d", [[dict allKeys] count]);
[dict allKeys] gives you the list of all the current keys.
Is there a more efficient way to add objects to an NSMutable Dictionary than simple iteration?
Example:
// Create the dictionary
NSMutableDictionary *myMutableDictionary = [NSMutableDictionary dictionary];
// Add the entries
[myMutableDictionary setObject:#"Stack Overflow" forKey:#"http://stackoverflow.com"];
[myMutableDictionary setObject:#"SlashDotOrg" forKey:#"http://www.slashdot.org"];
[myMutableDictionary setObject:#"Oracle" forKey:#"http://www.oracle.com"];
Just curious, I'm sure that this is the way it has to be done.
NSDictionary *entry = [NSDictionary dictionaryWithObjectsAndKeys:
[NSNumber numberWithDouble:acceleration.x], #"x",
[NSNumber numberWithDouble:acceleration.y], #"y",
[NSNumber numberWithDouble:acceleration.z], #"z",
[NSDate date], #"date",
nil];
If you have all the objects and keys beforehand you can initialize it using NSDictionary's:
dictionaryWithObjects:forKeys:
Of course this will give you immutable dictionary not mutable. It depends on your usage which one you need, you can get a mutable copy from NSDictionary but it seems easier just to use your original code in that case:
NSDictionary * dic = [NSDictionary dictionaryWith....];
NSMutableDictionary * md = [dic mutableCopy];
... use md ...
[md release];
Allow me to add some information to people that are starting.
It is possible to create a NSDictionary with a more friendly syntax with objective-c literals:
NSDictionary *dict = #{
key1 : object1,
key2 : object2,
key3 : object3 };
NSMutableDictionary *example = [[NSMutableDictionary alloc]initWithObjectsAndKeys:#5,#"burgers",#3, #"milkShakes",nil];
The objects come before the keys.