views:

319

answers:

2

In my current iPhone project, I have a created a dictionary that groups the sub dictionaries by the first letter of the "Name" key. NSLog returns the following. I would like to create an identical dictionary that only shows the "Name" key under each initial letter key. What is the best way for making a copy of some of the items in the sub dictionaries? The ObjectForKey methods will select only the items for the initial letter (ex: "B" or "C"). Please let me know if I didn't explain this clearly enough. Thanks!

sectionedDictionaryByFirstLetter:{
B =     (
            {
        Name = "B...A Name Starting with B";
        Image = "ImageName1.png";
        Text = "Some Text";
     }
 );
C =     (
            {
        Name = "C...A Name Starting with C";
        Image = "ImageName2.png";
        Text = "Some Text";
     }
 );
N =     (
            {
        Name = "N...A Name Starting with N";
        Image = "ImageName3.png";
        Text = "Some Text";

     },
            {
        Name = "N...A Name Starting with N";
        Image = "ImageName4.png";
        Text = "Some Text";

     },
            {
        Name = "N...A Name Starting with N";
        Image = "ImageName5.png";
        Text = "Some Text";

     }
   );
}

The final result I'm looking for is:

sectionedDictionaryByFirstLetter:{
B =     (
            {
        Name = "B...A Name Starting with B";
     }
 );
C =     (
            {
        Name = "C...A Name Starting with C";
     }
 );
N =     (
            {
        Name = "N...A Name Starting with N";
     },
            {
        Name = "N...A Name Starting with N";
     },
            {
        Name = "N...A Name Starting with N";
     }
   );
}
+1  A: 

Core Foundation has a great function, CFDictionaryApplyFunction (I wish they ported its functionality to NSDictionary too). However, NSDictionary and CFDictionary are "toll-free bridges", meaning that you can cast between them at no cost.

So, the solution would be to create an applier function, SaveName, which would be used in the CFDictionaryApplyFunction above. Example:

void SaveName(const void* key, const void* value, void* context) {
  NSMutableDictionary* result = (NSMutableDictionary*) context;
  NSDictionary* dataDict = (NSDictionary*) value;
  NSString* letterKey = (NSString*) key;
  [result setObject:[dataDict valueForKey:@"Name"] forKey:@letterKey];
}

void main() {

   NSDictionary* exampleDict = .. ;
   NSMutableDictionary* resultDict = [NSMutableDictionary dictionary];
   CFDictionaryApplyFunction((CFDictionaryRef)exampleDict, SaveName, (void*)resultDict);
   // now, the resultDict contains key-value pairs of letter-name!
}

My code assumes that there is only one object in the "value" dictionary. You can change SaveName to take into consideration your own data structure, but that's basically the way to do it.

Aviad Ben Dov
Where are the arrays?
Nikolai Ruhe
@Nikolai: I said I assume there's only one object, and that it could be changed to support other data structures as well.. Also, in your answer, you forgot to add the array later to the "newDict"...
Aviad Ben Dov
+1  A: 
NSMutableDictionary* newDict = [NSMutableDictionary dictionary];
for (NSString* key in sectionedDictionaryByFirstLetter) {
    NSMutableArray* newList = [NSMutableArray array];
    [newDict setObject:newList forKey:key];
    for (NSDictionary* entry in [sectionedDictionaryByFirstLetter objectForKey:key]) {
        NSString* name = [entry objectForKey:@"Name"];
        [newList addObject:[NSDictionray dictionaryWithObject:name forKey:@"Name"]];
    }
}
Nikolai Ruhe
I always thought that the key-enumerator is a waste. It generally means - iterate through the keys, and take each value as a "search" operation later.
Aviad Ben Dov
Thanks for the help. I found a small typos: objectWithKey should be objectForKey.Also, where do the arrays get added to newDict?Thanks!
Jonah
Here's what I ended up doing to make it work:NSString* name = [entry objectForKey:@"Name"];[newList addObject:[NSDictionary dictionaryWithObject:name forKey:@"Name"]];NSString* afirstLetterString = [name substringToIndex:1];[newDict setObject:newList forKey:afirstLetterString];
Jonah
Sorry for the typo/bug. Fixed that.
Nikolai Ruhe
@Aviad Did you actually measure performance on that? I'd really like to know if the Foundation way with the visitor function is so much faster.
Nikolai Ruhe