Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSDictionary case insensitive objectForKey:

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.

like image 465
NNikN Avatar asked Nov 28 '12 14:11

NNikN


3 Answers

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;
}
like image 95
Siddiq Avatar answered Oct 14 '22 08:10

Siddiq


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.

like image 11
Chuck Avatar answered Oct 14 '22 08:10

Chuck


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.

like image 2
al45tair Avatar answered Oct 14 '22 06:10

al45tair