NSDictionary case insensitive objectForKey: - objective-c

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.

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;
}

This isn't included for a couple of reasons:
NSDictionary uses hash equality, and for pretty much any good hashing algorithm, any variation in the source string results in a different hash.
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.

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.

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);

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 ;
}];

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.

Related

EXC_BAD_ACCESS while filling in the dictionary (?)

void CountlyRecordEventSegmentationCountSum(const char * key, const char * segmentation, int count, double sum)
{
NSString * seg = CreateNSString(segmentation);
NSArray * entries = [seg componentsSeparatedByString:#"`"];
NSDictionary * dict = [NSDictionary dictionary];
for (id entry in entries)
{
NSArray * keyValue = [entry componentsSeparatedByString:#"|"];
[dict setValue:[keyValue objectAtIndex:1] forKey:[keyValue objectAtIndex:0]];
}
[[Countly sharedInstance] recordEvent:CreateNSString(key) segmentation:dict count:count sum:sum];
}
I put "?" in the title because I'm not entirely sure if the problem is in the code above but that's my best guess. I'm integrating Countly iOS plugin with Unity and one of Countly plugin's methods take NSDictionary * as argument. As I don't know how to send a dictionary from C# to Objective-C I'm storing my dict in a string, sending it to Objective-C and then recreating the dictionary (the code above).
But that's probably even not relevant. I know EXC_BAD_ACCESS usually has something to do with unfreed resources or sth so maybe you can see what I'm doing wrong (I don't know Objective-C at all, just writing a few lines needed by the plugin).
Edit:
From Unity sample:
// Converts C style string to NSString
NSString * CreateNSString (const char * string)
{
if (string)
return [NSString stringWithUTF8String: string];
else
return [NSString stringWithUTF8String: ""];
}
The error you've made is that you are trying to modify immutable version of NSDictionary.
One cannot modify contents of the NSDictionary after it's initialization. You should use NSMutableDictionary instead.
Here is a documentation on NSMutableDictionary.
And here is an example of how to create mutable version of an immutable object that conforms to NSMutableCopying protocol.
You need to be using NSMutableDictionary, you can't modify an NSDictionary.
Also, you should use setObject:forKey: because setValue:forKey: is a KVC method. It happens to do the same thing on an NSMutableDictionary for most keys, but it is marginally slower.
Finally, you should check that [keyValue count] >= 2 before trying to access the objects at indexes 0 and 1.
Edit Also, CreateNSString() looks suspicious. It might be either leaking or prematurely releasing the string. But you need to post the code. In any case, I'd use
seg = [NSString stringWithUTF8String: segment];
or, other appropriate method if segment is not encoded in UTF-8.

Get all keys of an NSDictionary as an NSArray

Is it possible to get all the keys from a specific NSDictionary as a seperate NSArray?
Just use
NSArray*keys=[dict allKeys];
In general, if you wonder if a specific class has a specific method, look up Apple's own documentation. In this case, see NSDictionary class reference. Go through all the methods. You'll discover many useful methods that way.
Yes it's possible. Use allKeys method:
NSDictionary *yourDictionary;
NSArray * yourKeys
yourKeys = [yourDictionary allKeys];
And if you want to get all keys and values, here's what you do:
for (NSString *key in dictionary) {
id value = dictionary[key];
NSLog(#"Value: %# for key: %#", value, key);
}

How to implement Perl hashes in objective-c?

I have been looking through many sites and tutorials, and the Apple documentation, and still haven't found a solution: it seems to me that NSArray, NSDictionary and their mutable counterparts are not at all resembling the simple functionalities of a Perl hash. I hope I am wrong of course.
What I need: a mutable structure of dynamic keys and values (1 key - 1 value, as simple as that)! I mean, I don't know keys in advance and I need to easily check whether a key exists and if it exists retrieve a value or update it, if it does not exist enter the new key with the new value. And I need the values to be floats, not objects nor arrays. After I finished populating the structure I need to be able to retrieve the keys and finally looping through the values by the keys I retrieved.
All of this is easily accomplished in Perl with the following:
my %expenses;
if (exists $expenses{$key}) {
$expenses{$key} += $amount;
} else {
$expenses{$key} = $amount;
}
[...]
Is there someone who could tell me how to implement something similar in objective-c without using primitive types?
Thank you so much for any help.
Fabrizio
The Cocoa and Core Foundation collection classes are generally oriented towards storing objects rather than primitive values; the usual solution to a problem like yours is to wrap the floats in NSNumber objects. And unfortunately, the syntax for getting and setting the objects is more verbose than Perl's
Other than that, an NSMutableDictionary should do exactly what you want. The keys can be any string, or any other object that can be copied (i.e. it conforms to the NSCopying protocol), you can get a list of all keys, and you can check if a key "exists" in the dictionary by simply trying to fetch the corresponding value. The code corresponding to your example could look something like this:
// NB: there no autovivification in Objective-C. Be sure to initialize this somewhere
// before using it!
NSMutableDictionary *expenses;
if ([expenses objectForKey:key]) {
// This *could* be done in one statement. But it would be very long, so I split
// it in two for clarity.
float currentValue = [[expenses objectForKey:key] floatValue];
[expenses setObject:[NSNumber numberWithFloat:currentValue + amount] forKey:key];
} else {
[expenses setObject:[NSNumber numberWithFloat:amount] forKey:key];
}
This works:
NSString *key=#"fish";
NSMutableDictionary *expenses = [NSMutableDictionary dictionary];
float amount=22.0;
[expenses setObject:[NSNumber numberWithFloat:amount/2] forKey:key];
if ([expenses objectForKey:key]) {
[expenses setObject:
[NSNumber numberWithFloat:
[[expenses objectForKey:key] floatValue] + amount] forKey:key];
} else {
[expenses setObject:[NSNumber numberWithFloat:amount] forKey:key];
}
NSLog(#"expenses: %#",expenses);

Save part of NSDictionary

I have a NSDictionary with a NSString and NSArray
I have to save only the NSArray in a variable without knowing the key.
Is this possible?
If I'm understanding you correctly, you have a dictionary that contains both an NSString and an NSArray, and you want to extract just the NSArray, without knowing what the key is.
One way to do that is to look through the dictionary with fast enumeration:
NSString *key;
for(key in someDictionary){
id someObject = [someDictionary objectForKey: key];
}
and then look at the objects to see which one is an NSArray:
if ([someObject isKindOfClass:[NSArray class]]) {
// do something with the array
}
(obligatory warning: explicitly checking an object's class is often a sign of a flawed design. In most cases, you should be checking for behavior (-respondsToSelector), not class identity)

Better alternative for checking existence of NSDictionary's key in NSArray?

I have an NSArray of NSDictionaries. I need to check if there's at least one occurrence of an object for a key of the NSDictionary in the NSArray. I do this by using
int i;
for (i=0;i< [myArray count];i++)
{
if ([[[myArray objectAtIndex: i] objectForKey: myKey] isEqualToString: myString]) {
found = YES;
break;
} else {
found = NO;
}
}
But I have a suspicion that there's a better/faster alternative for it...
Thanks
Yes. Use "fast enumeration", commonly also known as for-in loop:
for (NSDictionary* dict in myArray) {
Also, to compare NSString's, use -isEqualToString:.
if ([[dict objectForKey: myKey] isEqualToString:myString]) {
That said, there is no algorithmic improvement to this (i.e. this method is already the best.)
This is as fast as you can get it with your current data structures. You're doing an O(1) lookup for each dictionary in the array. If you have a huge number of dictionaries, this might get expensive, so you could consider (depending very much on the semantics of your data) keeping a separate lookaside NSSet that contains the set of string objects comprising all the values in the dictionaries. Then you can check once in that set for existence.
Tell us more about the form of the data for more insight...
Also be careful with the == operator with NSStrings. If you're actually checking to see whether the text of the string is equal, you should use -isEqualToString: instead, as your form will just do a reference comparison.
You should use fast enumeration, which will iterate through the objects using a C array behind the scenes. Right now, you're calling both -objectAtIndex: and -count each time through the loop.
You might also check out NSPredicate if myKey is a string. My gut tells me it would be slower, but you never know if it might benefit from internal optimization for NSDictionary:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%# LIKE %#", myKey, myString];
BOOL found = ([[myArray filteredArrayUsingPredicate:predicate] count] > 0);
You could be much more succinct with Key-Value Coding:
[[myArray valueForKey:myKey] containsObject:myString];
It's not necessarily faster (I suspect it will be slower), but speed is not always the primary concern. Whether speed is critical in a particular case is a matter for profiling to decide.
Using == to check string equality might cause unexpected behavior, because you're actually comparing pointers (that can be okay if you're sure that you're dealing with a single string object pointed to by two pointers). isEqualToString: is probably what you want instead.
You can use "fast enumeration" to simplify things slightly:
bool found = NO;
for (NSDictionary *dict in myArray) {
found = [[dict objectForKey:myKey] isEqualToString:myString];
if (found)
break;
}
It's only "faster" in the sense that it's fewer words to write; the execution speed is the same.
After receiving your object, You can check that your received object is "NSArray" or "NSDictionary" or "NSString" etc. You can use following code to verify your object.
if([obj isKindOfClass:[NSArray class]]){
NSLog(#"IS NSArray");
}
else if([obj isKindOfClass:[NSDictionary class]]){
NSLog(#"Is NSDictionary");
}
else
{
NSLog(#"Other");
}
With fast enumeration
BOOL found;
for (NSDictionary *dict in array) {
if ([[dict objectForKey:#"YOURKEY"] isEqualToString:#"YOURVALUE"]) {
found = YES;
break;
} else {
found = NO;
}
}