How to swap `NSMutableDictionary` key and values in place?
Asked Answered
D

5

9

I have a NSMutableDictionary and I want to swap values & keys. i.e, after swapping values becomes keys and its corresponding keys with become values All keys and values are unique. Looking for an in place solution because size is very big . Also, the keys and values are NSString objects

Dwain answered 15/10, 2013 at 17:44 Comment(2)
Is there a guarantee that there are no keys which duplicate values or vice-versa?Ferrin
Yes, Keys are auto generated id's and values are strings.Dwain
J
16
NSMutableDictionary *d = [NSMutableDictionary dictionaryWithDictionary:@{
                             @"key1" : @"value1",
                             @"key2" : @"value2"}];

for (NSString *key in [d allKeys]) {
    d[d[key]] = key;
    [d removeObjectForKey:key];
}

NSLog(@"%@", d); // => { value1 : key1,
                 //      value2 : key2 }

Assumptions

  • unique values (as they will become keys)
  • values conform to NSCopying (same as above)
  • no value is equal to any key (otherwise colliding names will be lost in the process)
Jonathanjonathon answered 15/10, 2013 at 17:53 Comment(9)
Won't work if one key is identical to another (or any) key's value.Ouzo
@Ouzo of course, but the OP guaranteed key/valye uniqueness in the questionJonathanjonathon
I don't think you want to modify a collection while iterating over it.Ardis
The enumerated collection is [d allKeys], and that is not mutated during the enumeration.Leonaleonanie
Also, this won't work if the value doesn't conforms to the NSCopying protocol.Ardis
@pedro.m, the OP stated that they are NSString, so it conforms to NSCopyingJonathanjonathon
Check my updated answer. It handles all cases. No assumptions made.Ardis
@pedro.m Checked and it looks ok. See my comment below it. By the way I don't know whether it was you, but I'd like to know the reason of the downvote on my answer.Jonathanjonathon
It was because of the NSCopying, but I see you have fixed that so I'll update.Ardis
E
5

Here is another way to invert dictionary. The simplest for me.

NSArray *keys = dictionary.allKeys;
NSArray *values = [dictionary objectsForKeys:keys notFoundMarker:[NSNull null]];
[dictionary removeAllObjects]; // In case of huge data sets release the contents.
NSDictionary *invertedDictionary = [NSDictionary dictionaryWithObjects:keys forKeys:values];
[dictionary setDictionary:invertedDictionary]; // In case you want to use the original dictionary.
Elgar answered 15/10, 2013 at 20:18 Comment(6)
As many of the other answers, it misses the main point: it's not in place.Jonathanjonathon
@GabrielePetronella Sorry. But if you have performance or memory issues, maybe you could just use -allKeysForObject: to do inversed lookup. No allocations, no copying.Elgar
it's not me, it's the OP that requires it in the question.Jonathanjonathon
@GabrielePetronella I see. I understood “in place” little differently. In the end, if you do [d setDictionary:invertedDictionary]; you have it in place, or not? :)Elgar
Not quite, in the sense that you are allocating extra memory for creating invertedDictionary, which is what the OP doesn't want.Jonathanjonathon
@GabrielePetronella Edited the answer. Like this, it should not go up with memory and I'm reusing the original dictionary. In addition, this solution works for immutable dictionaries as well.Elgar
A
-1

EDIT: I had written a few lines of codes to get the OP started into the task of creating his own algorithm. The answer was not well received so I have crafted a full implementation of an algorithm that does what he asks, and goes one step further.

Advantages:

  • Makes no assumptions regarding the contents of the dictionary, for example, the values need not conform to the 'NSCopying' protocol
  • Transverses the whole hierarchy of a collection, swapping all the keys
  • It's fast since it uses recursion and fast enumeration
  • Does not alter the contents of the original dictionary, it creates a brand new one

Code has been implemented through categories to both collections:

@interface NSDictionary (Swapping)

- (NSDictionary *)dictionaryBySwappingKeyWithValue;

@end

@interface NSDictionary (Swapping)

- (NSDictionary *)dictionaryBySwappingKeyWithValue
{
    NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithCapacity:self.count];

    [self enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) {
        id newKey = nil;
        if ([value isKindOfClass:[NSDictionary class]]) {
            newKey = [value dictionaryBySwappingKeyWithValue];

        } else if ([value isKindOfClass:[NSArray class]]) {
            newKey = [value arrayBySwappingKeyWithValue];
        } else {
            newKey = value;
        }

        if (![newKey conformsToProtocol:@protocol(NSCopying)]) {
            newKey = [NSValue valueWithNonretainedObject:newKey];
        }

        mutableDictionary[newKey] = key;
    }];

    return [NSDictionary dictionaryWithDictionary:mutableDictionary];
}

@end

and...

@interface NSArray (Swapping)

- (NSArray *)arrayBySwappingKeyWithValue;

@end

@implementation NSArray (Swapping)

- (NSArray *)arrayBySwappingKeyWithValue
{
    NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:self.count];

    [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        if ([obj isKindOfClass:[NSDictionary class]]) {
            NSDictionary *newDict = [obj dictionaryBySwappingKeyWithValue];
            mutableArray[idx] = newDict;
        } else if ([obj isKindOfClass:[NSArray class]]) {
            NSArray *newArray = [obj arrayBySwappingKeyWithValue];
            mutableArray[idx] = newArray;
        } else {
            mutableArray[idx] = obj;
        }
    }];

    return [NSArray arrayWithArray:mutableArray];
}

@end

As an example, assume you have a dictionary with the following structure:

UIView *view = [[UIView alloc] init];
NSDictionary *dict = @{@"1" : @"a",
                       @"2" : @[ @{ @"5" : @"b" } ],
                       @"3" : @{@"6" : @"c"},
                       @"7" : view};

NSDictionary *newDict = [dict dictionaryBySwappingKeyWithValue];

Printing the newDict object in the console will give you this output:

(lldb) po mutableDictionary
{
    a = 1;
    ({b = 5;}) = 2;
    {c = 6;} = 3;
    "<30b50617>" = 7; 
}

As you can see, not only have the keys and values been swapped at the first level of the hierarchy, but deep inside each collection.

"<30b50617>" represents the UIView object wrapped inside a NSValue. Since UIView does not comply to the NSCopying protocol, it needs to be handled this way if you want it to be a key in your collection.

Note: Code was done in a couple of minutes. Let me know if I missed something.

Ardis answered 15/10, 2013 at 17:53 Comment(1)
I didn't go through all the code, but it looks like an overkill for the taskJonathanjonathon
V
-1
for (NSString *key in [myDictionary allKeys]) {
     NSString *value = [responseDataDic objectForKey:key];
     [myDictionary removeObjectForKey:key];
     [myDictionary addObject:key forKey:value];
}

Assumption: No key = value;

Complexity: No extra space required. Will loop through once and replace all key value pairs.

Vallee answered 15/10, 2013 at 20:6 Comment(9)
-1 This doesn't even compile. getObjectForKey does not exist, and you cannot loop through a dictionary like that. That said, even if you fix the obvious errors, the core concept it's identical to the accepted answer, so it's doesn't add any value.Jonathanjonathon
@GabrielePetronella Just a detail: you can loop through the dictionary just like this.Elgar
@GabrielePetronella Sorry about the mistake, I forgot its objectForKey. And I can loop through a dictionary with for(NSStrig *key in myDictionary) where myDictionary is a valid dictionary.Vallee
@iMartin yes my bad, even though it's still a bad idea to mutate a collection while iterating on it.Jonathanjonathon
@GabrielePetronella True, he is missing -copy.Elgar
@iMartin or simply iterate on the key collection as in my answer. That's why I said that this answer will eventually be a clone of mine. ;)Jonathanjonathon
@GabrielePetronella We all know that, but he was just trying :)Elgar
@iMartin fair enough, the answer is still worth a downvote, but I'll be happy to remove it as soon as it's fixed.Jonathanjonathon
@GabrielePetronella i see what you mean. :) it would have to iterate though all the keys of the dictionary and that makes it a replica of your answer.Vallee
F
-2
NSArray* allKeys = [theDict allKeys];
NSArray* allValues = [theDict allValues];
NSMutableDictionary* newDict = [NSMutableDictionary dictionaryWithObjects:allKeys forKeys:allValues];
Ferrin answered 15/10, 2013 at 17:57 Comment(2)
it's not in place, as the OP askedJonathanjonathon
You probably meant to swap allKeys with allValues? In any case, the order of the elements returned by allValues is not defined, therefore this might associate the wrong elements.Leonaleonanie

© 2022 - 2024 — McMap. All rights reserved.