0

I have a NSMutableDictionary *A (from in.plist). I need to prefix each key in A and in the descendant dictionary of A by a autocremented integer and save the result in a NSMutableDictionary B (saved as out.plist).

What is the best way to achieve this ? Recursivelly ? Loop ? Fastenum ?

PS: We don't know in advance the structure of the dictionary A. It can include dictionary array dictionary ... and so on

Example :

in.plist out.plist - Change only the key http://img11.hostingpics.net/pics/698162Capturedcran201306262025.png

The only diff between the in and out dictionary is the keys name.

I've found a workaround with perl regex directly on the plist file :

cat in.plist | perl -ne 's/<key>/sprintf("<key>%02d -",($INDEX++))/xe;print $_' > out.plist

Works like a charm but a prefer a Objective-c way.

Elfoiros
  • 468
  • 1
  • 7
  • 18

1 Answers1

2

This is a little odd, but here's basically what you want to do. Make a new NSMutableDictionary. Do a depth-first traversal of the original dictionary and every time you come across an key, prepend your number before setting it in the new dictionary. Here's an example implementation (written in browser, so caveat executor):

NSDictionary *numberDict(NSDictionary *original) {
    __block int counter = 1;

    __block id (^processItem)(id);
    processItem = ^(id item) {
        id result = item;
        if ([item isKindOfClass:[NSArray class]]) {
            result = [NSMutableArray array];
            [item enumerateObjectsUsingBlock:^(id arrayElement, NSUInteger index, BOOL *stop) {
                [result addObject:processItem(arrayElement)];
            }];
        } else if ([item isKindOfClass:[NSDictionary class]]) {
            result = [NSMutableDictionary dictionary];
            [item enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) {
                NSString *newKey = [NSString stringWithFormat:@"%d - %@", counter++, key];
                [result setObject:processItem(value) forKey:newKey];
            }];
        }
        return result;
    };
    return processItem(original);
}

Obviously this won't preserve the order of dictionary keys, since dictionary keys don't actually have an order to be preserved, but you've indicated you're aware and are OK with that.

Ramy Al Zuhouri
  • 21,580
  • 26
  • 105
  • 187
Chuck
  • 234,037
  • 30
  • 302
  • 389
  • The syntax should be *id (^processItem)(id) = ^id(id item)* to be correct. But *processItem* is referenced inside itself, to it should be *__block* too (id (^__block processItem)(id) = ^id(id item) ). This creates a retain cycle, I think it's better if you do the recursion on the method and not on the block. +1 however for the nice technique. – Ramy Al Zuhouri Jun 26 '13 at 21:34
  • @RamyAlZuhouri: It's a local block. There can't be a retain cycle. But right on the syntax. And I think doing the recursion on the method would require us to maintain our counter using either some sort of global state, pointers in the parameter list or other extra bookkeeping, which would be messier than this. But I could just not be thinking of it the right way. – Chuck Jun 26 '13 at 21:38
  • Ok on that, but *processItem* should be *__block* too, and that's the reason why he's getting *EXC_BAD_ACCESS*. I saw it thanks to a compiler warning. – Ramy Al Zuhouri Jun 26 '13 at 21:47
  • Btw also using a *__block* counter implies to have a global state, I did it using a pointer argument, the code is IMO more fluid and elegant. – Ramy Al Zuhouri Jun 26 '13 at 22:10
  • @RamyAlZuhouri: Not sure what you mean. The `counter` variable is strictly local to the method. You could run this function in five threads on five different dictionaries simultaneously and get correct results, so I'm not sure in what sense you're using "global." And I guess "elegant" is a matter of personal taste, but as far as I can see the counter is the algorithm's internal state, so exposing it in the function's interface — requiring the caller to pass in some memory for the counter — doesn't seem elegant to me. – Chuck Jun 26 '13 at 22:34
  • I can declare another function which hasn't any parameter, that passes the counter pointer to another hidden function, not declared on the header that does the recursive call on itself without using any block. – Ramy Al Zuhouri Jun 26 '13 at 22:55
  • @RamyAlZuhouri: That's true, but at this point we're just observing that a block is isomorphic to a normal function that takes the captured variables as arguments. Blocks can always be replaced by writing helper functions that do the same thing the block did. – Chuck Jun 26 '13 at 23:58
  • @Chuck Look, there's still a retain cycle, I tested the code. It came out that at the end of recursion you have to set the block pointer to *nil*. Look here: http://stackoverflow.com/questions/17414314/how-to-handle-looping-code-with-blocks/17414384#17414384 and here: http://stackoverflow.com/questions/13090598/recursive-block-retain-cycles/13091475#13091475 – Ramy Al Zuhouri Jul 02 '13 at 11:18
  • @RamyAlZuhouri: Where is the block copied and not released? And why does [bbum think it's OK](http://www.friday.com/bbum/2009/08/29/blocks-tips-tricks/)? – Chuck Jul 02 '13 at 17:20
  • @Chuck The pointer to the block is captured by reference, so there's still a retain cycle. I've also read this bbum post, but it wasn't written at ARC times. – Ramy Al Zuhouri Jul 02 '13 at 17:33
  • @RamyAlZuhouri: But what is keeping the block alive after the method finishes executing? How can a stack-based block even leak? – Chuck Jul 02 '13 at 18:00
  • @Chuck Though it's not documented and I don't know the criteria, it may happen that the block gets directly allocated on the heap and not on the stack. In this case I don't know if it's related to the fact that the block is inside a loop, but look at this answer: http://stackoverflow.com/questions/14026083/objective-c-blocks/14026190#14026190 Also I tested many times the behavior of blocks and it seems that also global blocks are able to survive from their scope, for some reason. – Ramy Al Zuhouri Jul 03 '13 at 00:27