How can I swap keys and values in NSDictionary? - objective-c

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.

Related

Xcode Sort Arrays by size in objectKey

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"];

NSDictionary order of keys and values

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]];

how to create a NSDictionary consisting of NSArray of NString for key and bunch of int for the value?

here is the problem :
NSArray * alphabets = [NSArray arrayWithObjects:#"a",#"b",#"c",#"d",#"e",#"f",#"g",#"h",#"i",#"j",#"k",#"l",#"m",#"n",#"o",#"p",#"q",#"r",#"s",#"t",#"u",#"v",#"w",#"x",#"y",#"z",nil];
NSDictionary * alphaToNum = [NSDictionary dictionaryWithObject:alphabets forKey:1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26];
//I know above line meant to not work I just brought it here as an example
how can I create a dictionary just like above which is working?
You can only use objects as keys or values in a NSDictionary, so you have to use NSNumber as wrapper for your integer key.
NSMutableDictionary *alphaToNum = [NSMutableDictionary dictionary];
NSArray *alphabet = [NSArray arrayWithObjects:#"a",#"b",#"c",#"d",#"e",#"f",#"g",#"h",#"i",#"j",#"k",#"l",#"m",#"n",#"o",#"p",#"q",#"r",#"s",#"t",#"u",#"v",#"w",#"x",#"y",#"z",nil];
NSInteger index = 0;
for(NSString *character in alphabet)
{
index ++;
[alphaToNum setObject:character forKey:[NSNumber numberWithInteger:index]];
}

Cocoa: Any downside to using NSSet as key in NSMutableDictionary?

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.

NSMutable Dictionary adding objects

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.