0

Aim: To obtain an NSArray containing unique keys for given NSDictionary(s) using elegant code

Example Code with Current Working Solution:

NSArray *data = [[NSArray alloc] initWithObjects:
                 [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:1], @"a", [NSNumber numberWithInt:2], @"b", nil],
                 [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:3], @"b", [NSNumber numberWithInt:4], @"c", nil],
                 [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:5], @"a", [NSNumber numberWithInt:6], @"c", nil],
                 [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:7], @"b", [NSNumber numberWithInt:8], @"a", nil],
                 [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:8], @"c", [NSNumber numberWithInt:9], @"b", nil],
                 nil];

// create an NSArray of all the dictionary keys within the NSArray *data
NSMutableSet *setKeys = [[NSMutableSet alloc] init]; 
for (int i=0; i<[data count]; i++) {
    [setKeys addObjectsFromArray:[[data objectAtIndex:i] allKeys]];
}
NSArray *arrayKeys = [setKeys allObjects];
NSLog(@"arrayKeys: %@", arrayKeys);

Which returns the desired array of keys:

2012-06-11 16:52:57.351 test.kvc[6497:403] arrayKeys: (
    a,
    b,
    c
)

Question: Is there a more elegant way of approaching this? Surely there must be some KVC approach that can get all the keys without having to iterate through the array? I've been looking at Apple Developer Documentation and can't see a solution. Any ideas? (looking at purely elegance of code rather than performance).

So Over It
  • 3,668
  • 3
  • 35
  • 46

3 Answers3

8

Normally you could use KVC by doing something like this:

NSArray *uniqueKeys = [data valueForKeyPath:@"@distinctUnionOfArrays.allKeys";

However NSDictionary overrides the valueForKey: selector which is used by the KVC internals, so this will not work correctly.

The documentation for NSDictionary's valueForKey: method tells us that:

If key does not start with “@”, invokes objectForKey:. If key does start with “@”, strips the “@” and invokes [super valueForKey:] with the rest of the key.

So we just insert an @ before allKeys:

NSArray *uniqueKeys = [data valueForKeyPath:@"@distinctUnionOfArrays.@allKeys"];

And we get what we want:

(lldb) po [data valueForKeyPath:@"@distinctUnionOfArrays.@allKeys"]
(id) $14 = 0x07bb2fc0 <__NSArrayI 0x7bb2fc0>(
c,
a,
b
)
Mike Weller
  • 45,401
  • 15
  • 131
  • 151
0

This is less ugly, and possibly marginally faster, I suppose:

NSMutableSet *setKeys = [[NSMutableSet alloc] init]; 
for (NSDictionary* dict in data) {
    for (id key in [dict keyEnumerator]) {
        [setKeys addObject:key];
    }
}

But you're not doing a particularly common operation, so I wouldn't expect to find some incredibly elegant method. If that's what you want, go learn Haskell.

Kurt Revis
  • 27,695
  • 5
  • 68
  • 74
  • Thanks for the code. Objective-C is my current language to learn - so Haskell will have to wait ;) My philosophy is that you should always try and be elegant in whatever language you use. – So Over It Jun 11 '12 at 07:34
0

You could try this:

NSMutableSet *setKeys = [[NSMutableSet alloc] init]; 

for(NSDictionary *dict in data) {
    [setKeys addObjectsFromArray:[dict allKeys]];
}

NSArray *arrayKeys = [setKeys allObjects];

If you prefer blocks you could use this:

[data enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    [setKeys addObjectsFromArray:[obj allKeys]];
}];
mttrb
  • 8,297
  • 3
  • 35
  • 57