9

NSDictionary has objectForKey but it's case-sentive for keys. There is No function available like

- (id)objectForKey:(id)aKey options:(id) options;

where in options you can pass "NSCaseInsensitiveSearch"

To get key's from NSDictionary which is case-insesitive one can use the following code written below.

NNikN
  • 3,720
  • 6
  • 44
  • 86
  • Down votes welcomed with proper justification. – NNikN Nov 28 '12 at 15:09
  • Its a question which self answered and shared. – NNikN Nov 28 '12 at 17:17
  • But, when I ask a question i see a option of Answer your own question. So, thats what I did. By the way your comment on gaining reputation is quite sarcastic. I see many coding styles , you can also try to answer like @Ramy did. Please specify your blog, so I can ask such questions over there. – NNikN Nov 28 '12 at 17:33
  • @Jennis: It might be, it might not be. If you have a good question and find a good answer, that's what Stack Overflow is for. – Chuck Nov 28 '12 at 19:10
  • @Jennis I wanted to share my answer. I found "Answer your own question – share your knowledge, Q&A-style". So I wrote this line "To get key's from NSDictionary which is case-insesitive one can use the following code written below." It's quite strange you having attached well to this site replies like this way. Please look at many question where people have answered their own question to help others. Anyways, thanks for your critics. – NNikN Nov 29 '12 at 13:43

6 Answers6

12

You need to add Category of NSDictionary Class with this functionality

- (id)objectForCaseInsensitiveKey:(NSString *)key {
    NSArray *allKeys = [self allKeys];
    for (NSString *str in allKeys) {
        if ([key caseInsensitiveCompare:str] == NSOrderedSame) {
            return [self objectForKey:str];
        }
    }
    return nil;
}
Siddiq
  • 393
  • 2
  • 8
  • Thats a good idea, of creating a category. But, why do you need to write a for loop. One can you use readily available function which uses NSPredicate. – NNikN Nov 28 '12 at 14:42
  • 9
    ...and you just bumped search complexity from `O(log(N)))` to just `O(N)` – ivanzoid Jan 16 '13 at 06:35
  • @ivanzoid In fact, you just bumped it from `O(1)` to `O(n)`; `NSDictionary` is a hash table, not a tree. – al45tair Sep 22 '16 at 12:40
11

This isn't included for a couple of reasons:

  1. NSDictionary uses hash equality, and for pretty much any good hashing algorithm, any variation in the source string results in a different hash.

  2. More importantly, NSDictionary keys are not strings. Any object that conforms to NSCopying can be a dictionary key, and that includes a whole lot more than strings. What would a case-insensitive comparison of an NSNumber with an NSBezierPath look like?

Many of the answers here offer solutions that amount to transforming the dictionary into an array and iterating over it. That works, and if you just need this as a one-off, that's fine. But that solution is kinda ugly and has bad performance characteristics. If this were something I needed a lot (say, enough to create an NSDictionary category), I would want to solve it properly, at the data structure level.

What you want is a class that wraps an NSDictionary, only allows strings for keys and automatically lowercases keys as they are given (and possibly also remembers the original key if you need a two-way mapping). This would be fairly simple to implement and is a much cleaner design. It's too heavy for a one-off, but if this is something you're doing a lot, I think it's worth doing cleanly.

Chuck
  • 234,037
  • 30
  • 302
  • 389
  • perfect, this is a really a nice answer. I agree to your point objectForKey not being NSString always, but that's most commonly used. I think this what happen's when a objectForKey is called, system calculates the hash for the key passed to the function, and then executes a for loop to match that hash with the available hash and return the value when match is found. please correct me if am wrong. – NNikN Nov 28 '12 at 18:43
  • @andyPaul: It's a little bit more complicated than that. That algorithm would take linear time to find a value for a key (i.e. each additional item in the dictionary adds a certain amount of overhead that makes it take longer on average to match keys and values), but NSDictionary stays fast even with hundreds of thousands of objects. But the core idea, that it works by comparing hashes, is exactly right. (I'm not being vague just to be difficult, by the way. NSDictionary isn't one class, but many different classes with different implementations that all look the same from the outside.) – Chuck Nov 28 '12 at 18:58
  • Lowercasing is not the correct transformation – you need to case fold. – al45tair Sep 22 '16 at 12:39
2

The correct answer is that you should use case-folded keys as dictionary keys. This is not the same as converting them to upper or lower case and it won't destroy the O(1) average case search/insert complexity.

Unfortunately, Cocoa doesn't seem to have an appropriate NSString method to case-fold a string, but Core Foundation has CFStringFold() which you can use for that purpose. Let's write a short function to do the necessary work:

NSString *foldedString(NSString *s, NSLocale *locale)
{
  CFMutableStringRef ret = CFStringCreateMutableCopy(kCFAllocatorDefault, 0,
                                                     (__bridge CFStringRef)s);
  CFStringNormalize(ret, kCFStringNormalizationFormD);
  CFStringFold(ret, kCFCompareCaseInsensitive, (__bridge CFLocaleRef)locale);
  return (__bridge_transfer NSString *)ret;
}

Note that the locale argument is important. If you specify NULL, you will get the current system locale. This will be fine in most cases, but Turkish users might be surprised that "I" matches "i" rather than "ı". You might therefore want to pass [NSLocale currentLocale], and if you're saving the results you might also want to save the locale identifier and create the locale from that.

So, when adding to the dictionary, you now need to do

[dict setObject:obj forKey:foldedString(myKey, locale)];

and to look up again

[dict objectForKey:foldedString(myKey, locale)];

One final observation is that you might wish to store the case-folded keys alongside the original values, then you don't have to fold them on every access to the dictionary.

al45tair
  • 4,405
  • 23
  • 30
1

In the code written below, I search for a actual key for a input key. So , if input key=@"naMe" then the actual key=@"name".

NSDictionary *dic=[NSDictionary dictionaryWithObjectsAndKeys:@"John",@"Name",@"123456",@"empId", nil];


NSString *key=@"naMe";
NSString *name=[dic objectForKey:key];

if(name==nil){
    NSPredicate *searchPred=[NSPredicate predicateWithFormat:@"self LIKE[cd] %@",key];
    NSArray *searchedKeys=[[dic allKeys] filteredArrayUsingPredicate:searchPred];

    if(searchedKeys.count>0){
        name=[dic objectForKey:[searchedKeys objectAtIndex:0]];

    }
}

NSLog(@"Name = %@",name);
NNikN
  • 3,720
  • 6
  • 44
  • 86
  • You can also do like this. http://stackoverflow.com/questions/6135000/case-insensitive-search-in-nsmutabledictionary – Paramasivan Samuttiram Nov 28 '12 at 14:41
  • This is complicated and slow; it starts by constructing an array containing all the keys in the dictionary `O(n)`, then has to parse the `NSPredicate` and filter the resulting array. – al45tair Sep 22 '16 at 12:42
0

Many answers are correct, but here's a more example:

    NSDictionary* dict= @{ @"hello" : @"Hey" };
    NSArray* keys= [dict allKeys];
    NSUInteger index=[keys indexOfObjectPassingTest:  ^BOOL (id obj, NSUInteger index, BOOL* stop)
     {
         if( [obj caseInsensitiveCompare: @"Hello"]==NSOrderedSame)
         {
             *stop= YES;
             return YES;
         }
         else
         {
             return NO;
         }
     }];

Personally I find this method easier, but everyone has his programming style.

EDIT

A less readable but shorter solution:

    NSDictionary* dict= @{ @"hello" : @"Hey" };
    NSArray* keys= [dict allKeys];
    NSUInteger index=[keys indexOfObjectPassingTest:  ^BOOL (id obj, NSUInteger index, BOOL* stop)
     {
         return *stop= [obj caseInsensitiveCompare: @"Hello"]==NSOrderedSame ;

     }];
Ramy Al Zuhouri
  • 21,580
  • 26
  • 105
  • 187
  • look at the number of line's the code you have written takes. But, yes you can write the entire block in a line, but will be very difficult to understand. – NNikN Nov 28 '12 at 15:08
  • There are many lines, but the amount of code is very small. I wouldn't return [obj caseInsensitiveCompare: @"Hello"]==NSOrderedSame just because before returning the value I must set *stop to YES, so that single line would be like you say, hardly readable. – Ramy Al Zuhouri Nov 28 '12 at 17:08
  • This is another `O(n)` answer. `NSDictionary` accesses are `O(1)`. At least it isn't using `NSPredicate`, I suppose. – al45tair Sep 22 '16 at 12:43
0

If you are only storing into, and retrieving from, the NSDictionary in one place (maybe two or three), you could use

[myString lowercaseString]

in both. The more rigorous answers are useful if the dictionary object is used all over your code.

Yusuf X
  • 14,513
  • 5
  • 35
  • 47
  • This is actually a much better answer than most of the longer ones above. The only mistake is that lowercasing won't work as expected in all cases (you really should case-fold the string instead). – al45tair Sep 22 '16 at 12:44