1

I have an array of keys/values...

NSArray *keysAndValues = @[@"1", @"one", @"2", @"two", @"3", @"three"];

What's the simplest way to convert this to an NSDictionary so that it matches a dictionary that looks like...

NSDictionary *dict = @{@"1" : @"two", @"2" : @"two", @"3" : @"three"};

I have code that just cycles through the array and extracts the key/values, but I was wondering if there's a cleaner way or any built in NSDictionary methods that I'm missing?

jscs
  • 63,694
  • 13
  • 151
  • 195
BeachRunnerFred
  • 18,070
  • 35
  • 139
  • 238
  • 2
    It's a pity you don't have the keys and values inverted, or you could use [NSDictionary dictionaryWithObjectsAndKeys:] – Fernando Mazzon Jan 03 '13 at 19:44
  • 1
    @FernandoMazzon, if BeachRunnerFred did have his keys and values inverted, how exactly would he use `dictionaryWithObjectsAndKeys`? I tried that myself, but `dictionaryWithObjectsAndKeys` asks for a nil-terminated comma-separated list of objects, and I couldn't figure out how to instead use a specified array. – John Sauer Jan 03 '13 at 19:50
  • 2
    To answer your question, I dont think there are any other convenience methods already defined for this purpose. You might have to proceed with your current way of extracting key/values and setting in dictionary. – iDev Jan 03 '13 at 19:58

3 Answers3

3

There is no built-in method in NSDictionary that takes alternate values from an array and break it into 2 sets, one for key and another for value.

You have to do something like this: (This is not compiler tested, but will give you an idea.)

If you want key = 1 and object = one :

for(int i=0;i<keysAndValues.count;i+=2){
    [dict setObject:[keysAndValues objectAtIndex:i+1] forKey:[keysAndValues objectAtIndex:i]];
}
Anoop Vaidya
  • 46,283
  • 15
  • 111
  • 140
  • 3
    You have the keys and values backward. The value is at `i + 1` and the key is at `i`. – rmaddy Jan 03 '13 at 19:45
  • If it is that way, you could have used `dictionaryWithObjectsAndKeys`. The above code is relevant only for this particular example in OP. So it is better to correct the answer itself. – iDev Jan 03 '13 at 19:47
  • Yes, check out his example. – DrummerB Jan 03 '13 at 19:47
  • Since the array is key, value, key, value, your current code is incorrect. – rmaddy Jan 03 '13 at 19:48
  • He wants it the other way around. You can determine that by the way he defined his dictionary using literals: `@{key: value, key: value}`. – DrummerB Jan 03 '13 at 19:49
  • 1
    Given his array, only the 2nd block of code is valid. You should delete the original code. – rmaddy Jan 03 '13 at 19:51
  • 6
    Actually this is not answering the question. His question is `I have code that just cycles through the array and extracts the key/values, but I was wondering if there's a cleaner way or any built in NSDictionary methods that I'm missing?` So I am assuming that he is already doing the above approach. – iDev Jan 03 '13 at 19:59
3

You're not missing anything. There isn't any really convenient way other than looping to do this. Of course any solution would be, at root, looping, but it would be nice if there were some more "filtering" or re-arranging abilities built in to NSArray (/me coughs discreetly and looks at Python list comprehensions).

There's likely to be something useful in this vein in vikingosegundo's arraytools on GitHub, or similar collection-manipulation extensions by others. There's certainly no harm in writing your own category method to do this, though, like I said, there would have to be a loop somewhere in there.

Here's my suggestion, for whatever that's worth. Split the array by enumerating it:

NSMutableArray * keys = [NSMutableArray array];
NSMutableArray * values = [NSMutableArray array];
[array enumerateObjectsUsingBlock:(void (^)(id obj, NSUInteger idx, BOOL *stop)){
    if( idx % 2 ){
        [values addObject:obj];
    else {
        [keys addObject:obj];
    }
}];

NSDictionary * d = [NSDictionary dictionaryWithObjects:values forKeys:keys];

Seems like there should be a better way to do this, such as with an NSIndexSet and objectsAtIndexes:, but there's no convenient way to create an index set with a non-contiguous bunch of indexes.

Something like this, e.g., would be nice:

@implementation NSIndexSet (WSSNonContiguous)


+ (id) WSS_indexSetWithOddIndexesInRange: (NSRange)range
{
    NSMutableIndexSet * s = [NSMutableIndexSet indexSet];

    // If the start of the range is even, start with the next number.
    NSUInteger start = range.location % 2 ? range.location : range.location + 1;
    NSUInteger upper_limit = range.location + range.length;
    for( NSUInteger i = start; i < upper_limit; i += 2 ){
        [s addIndex:i];
    }

    return s;
}

+ (id) WSS_indexSetWithEvenIndexesInRange: (NSRange)range
{
    NSMutableIndexSet * s = [NSMutableIndexSet indexSet];

    // If the start of the range is odd, start with the next number.
    NSUInteger start = range.location % 2 ? range.location + 1 : range.location;
    NSUInteger upper_limit = range.location + range.length;
    for( NSUInteger i = start; i < upper_limit; i += 2 ){
        [s addIndex:i];
    }

    return s;
}

@end

With a helper function:

NSRange rangeOfNumbers(NSUInteger start, NSUInteger end)
{
    return (NSRange){start, end-start};
}

So that you can do this:

NSIndexSet * s = [NSIndexSet WSS_indexSetWithEvenIndexesInRange:rangeOfNumbers(0, 10)];
NSLog(@"%@", s);
// Prints: <NSMutableIndexSet: 0x7fccca4142a0>[number of indexes: 5 (in 5 ranges), indexes: (0 2 4 6 8)]
jscs
  • 63,694
  • 13
  • 151
  • 195
  • That's how I would do it, but it doesn't answer the question: "I was wondering if there's a cleaner way or any built in NSDictionary methods that I'm missing?". – Ramy Al Zuhouri Jan 03 '13 at 20:13
  • Thanks, I realized that later, @RamyAlZuhouri, and I've expanded to address that point more directly. – jscs Jan 03 '13 at 20:15
  • But it's still the best answer IMHO +1. – Ramy Al Zuhouri Jan 03 '13 at 20:16
  • 1
    instead of creating two arrays you could create two `NSMutableIndexSet` and then use `[array objectsAtIndexes:keyIndexSet]`, and `[array objectsAtIndexes:valueIndexSet]`. It should be slightly more efficient. – Gabriele Petronella Jan 03 '13 at 20:29
  • @GabrielePetronella: I mentioned that possibility at the end of my answer. Barring the creation of a category on `NSIndexSet` to make initialization with non-contiguous ranges easier, the sets would have to be created with a regular `for` loop. – jscs Jan 03 '13 at 20:38
  • I saw the mention, what I'm saying that you can use the very same loop to create two index sets instead of two arrays. I suspect that it mighty be slightly better in terms of performance. – Gabriele Petronella Jan 03 '13 at 20:41
  • @GabrielePetronella: I have the opposite suspicion. If you create two index sets first, you do a (granted, relatively fast) `for` loop to build them, and then still have to (internally) loop over the array _twice_ with `objectsAtIndexes:`. However, since neither of us has actually measured, we have only our suspicions to wave at each other. :) – jscs Jan 03 '13 at 20:56
  • You may be right. It was just a suggestion for the sake of completeness of the discussion. ;) – Gabriele Petronella Jan 03 '13 at 20:58
  • 1
    But still loop,iteration,recursion... then why to use 40 odd lines of code, if you can do it in 2 lines of code? Please tell me the advantage... – Anoop Vaidya Jan 04 '13 at 09:05
  • I never realized someone is using my arraytools. I have to review and update them! – vikingosegundo Jan 06 '13 at 19:14
  • @vikingosegundo: Well, to be honest, I've only used them once or twice, but you pointed me to them a while back and it seemed like a good resource to share with others. – jscs Jan 06 '13 at 19:20
  • @JoshCaswell — actually I am not using them as-well. they are meant to show some block capabilities in a more functional array handling. – vikingosegundo Jan 06 '13 at 19:22
  • @vikingosegundo: Gotcha. I can remove the mention if you would rather keep them obscure. – jscs Jan 06 '13 at 19:25
1

I have code that just cycles through the array and extracts the key/values, but I was wondering if there's a cleaner way or any built in NSDictionary methods that I'm missing?

No way out, you have to iterate through all the array.

If you are searching for a cleaner way probably Josh Caswell's answer is the best one, but you still have to iterate over the array.

That would be easy to do using this method:

+ (id)dictionaryWithObjectsAndKeys:(id)firstObject , ... 

But unfortunately there isn't a straight way to create a va_list from an array. See also this question.

Community
  • 1
  • 1
Ramy Al Zuhouri
  • 21,580
  • 26
  • 105
  • 187