I'm developing an iPhone application.
I use a NSDictionary to store city's names as key, and population as value. I want to search the keys using lowercase.
I've using this:
NSDictionary *dict;
[dict objectForKey:[[city stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] lowercaseString]];
But, it doesn't work.
I know, I can do a for, convert keys to lowercase and compare with city.
Is there any other way to do that? Maybe, with a NSDictionary method.
UPDATE The NSDictionary is loaded from a property list.
Thank you.
I use this method in an NSDictionary category.
#implementation NSDictionary (MyCategory)
- (NSDictionary *)dictionaryWithLowercaseKeys {
NSMutableDictionary *result = [NSMutableDictionary dictionaryWithCapacity:0];
NSString *key;
for (key in self) {
[result setObject:[self objectForKey:key] forKey:[key lowercaseString]];
}
return result;
}
#end
Although I'm still not clear on what you want, this loop with search for keys case insensitively. Getting the value of that key is then trivial.
for key in dict
{
if ([key caseInsensitiveCompare: #"Whatever"] == NSOrderedSame)
NSLog(#"They are equal.");
}
I'd say create a second dictionary. After you load from the property set, loop through that dictionary and insert objects into the second dictionary. Convert the keys to lowercase as you go. Then release the first dictionary.
The above answers only work for a flat dictionary without any nesting whatsoever. I created a category that creates a copy of an existing NSDictionary with every key converted to lower case.
The header: NSDictionary+LowercaseKeys.h
#import <Foundation/Foundation.h>
#interface NSDictionary (LowercaseKeys)
/*
Recursive algorithm to find all nested dictionary keys and create an NSMutableDictionary copy with all keys converted to lowercase.
Returns an NSMutableDictionary with all keys and nested keys converted to lowercase.
*/
+ (NSMutableDictionary *)dictionaryWithLowercaseKeysFromDictionary:(NSDictionary *)dictionary;
/*
Convienience method to create a new lowercase dictionary object an existing NSDictionary instance
Returns an NSMutableDictionary with all keys and nested keys converted to lowercase.
*/
- (NSMutableDictionary *)dictionaryWithLowercaseKeys;
#end
The Implementation: NSDictionary+LowercaseKeys.m
#import "NSDictionary+LowercaseKeys.h"
#implementation NSDictionary (LowercaseKeys)
/*
Recursive algorithm to find all nested dictionary keys and create an NSMutableDictionary copy with all keys converted to lowercase
Returns an NSMutableDictionary with all keys and nested keys converted to lowercase.
*/
+ (NSMutableDictionary *)dictionaryWithLowercaseKeysFromDictionary:(NSDictionary *)dictionary
{
NSMutableDictionary *resultDict = [NSMutableDictionary dictionaryWithCapacity:[dictionary count]];
[dictionary enumerateKeysAndObjectsUsingBlock:^(NSString *key, id obj, BOOL *stop) {
// There are 3 types of objects to consider, NSDictionary, NSArray and everything else
id resultObj;
if ([obj isKindOfClass:NSDictionary.class])
{
// Recursively dig deeper into this nested dictionary
resultObj = [NSMutableDictionary dictionaryWithLowercaseKeysFromDictionary:obj];
}
else if ([obj isKindOfClass:NSArray.class])
{
/*
Iterate over this nested NSArray. Recursively convert any NSDictionary objects to the lowercase version.
If the array contains another array then continue to recursively dig deeper.
*/
resultObj = [NSMutableArray arrayWithCapacity:[obj count]];
for (id arrayObj in obj)
{
if ([arrayObj isKindOfClass:NSDictionary.class])
[resultObj addObject:[NSMutableDictionary dictionaryWithLowercaseKeysFromDictionary:arrayObj]];
else if ([arrayObj isKindOfClass:NSArray.class])
[resultObj addObject:[NSMutableDictionary arrayWithLowercaseKeysForDictionaryArray:arrayObj]];
else
[resultObj addObject:arrayObj];
}
}
else
{
// The object is not an NSDictionary or NSArray so keep the object as is
resultObj = obj;
}
// The result object has been converted and can be added to the dictionary. Note this object may be nested inside a larger dictionary.
[resultDict setObject:resultObj forKey:[key lowercaseString]];
}];
return resultDict;
}
/*
Convienience method to create a new dictionary object with all lowercase keys from an existing instance
*/
- (NSMutableDictionary *)dictionaryWithLowercaseKeys
{
return [NSMutableDictionary dictionaryWithLowercaseKeysFromDictionary:self];
}
#pragma mark - Private helpers
/*
Convert NSDictionary keys to lower case when embedded in an NSArray
*/
+ (NSMutableArray *)arrayWithLowercaseKeysForDictionaryArray:(NSArray *)dictionaryArray
{
NSMutableArray *resultArray = [NSMutableArray arrayWithCapacity:[dictionaryArray count]];
for (id eachObj in dictionaryArray)
{
if ([eachObj isKindOfClass:NSDictionary.class])
[resultArray addObject:[NSMutableDictionary dictionaryWithLowercaseKeysFromDictionary:eachObj]];
else if ([eachObj isKindOfClass:NSArray.class])
[resultArray addObject:[NSMutableDictionary arrayWithLowercaseKeysForDictionaryArray:eachObj]];
}
return resultArray;
}
#end
Related
I have a NSMutableDictionary and I want to swap values & keys. i.e, after swapping values becomes keys and its corresponding keys with become values All keys and values are unique. Looking for an in place solution because size is very big . Also, the keys and values are NSString objects
NSMutableDictionary *d = [NSMutableDictionary dictionaryWithDictionary:#{
#"key1" : #"value1",
#"key2" : #"value2"}];
for (NSString *key in [d allKeys]) {
d[d[key]] = key;
[d removeObjectForKey:key];
}
NSLog(#"%#", d); // => { value1 : key1,
// value2 : key2 }
Assumptions
unique values (as they will become keys)
values conform to NSCopying (same as above)
no value is equal to any key (otherwise colliding names will be lost in the process)
Here is another way to invert dictionary. The simplest for me.
NSArray *keys = dictionary.allKeys;
NSArray *values = [dictionary objectsForKeys:keys notFoundMarker:[NSNull null]];
[dictionary removeAllObjects]; // In case of huge data sets release the contents.
NSDictionary *invertedDictionary = [NSDictionary dictionaryWithObjects:keys forKeys:values];
[dictionary setDictionary:invertedDictionary]; // In case you want to use the original dictionary.
EDIT: I had written a few lines of codes to get the OP started into the task of creating his own algorithm. The answer was not well received so I have crafted a full implementation of an algorithm that does what he asks, and goes one step further.
Advantages:
Makes no assumptions regarding the contents of the dictionary, for example, the values need not conform to the 'NSCopying' protocol
Transverses the whole hierarchy of a collection, swapping all the keys
It's fast since it uses recursion and fast enumeration
Does not alter the contents of the original dictionary, it creates a brand new one
Code has been implemented through categories to both collections:
#interface NSDictionary (Swapping)
- (NSDictionary *)dictionaryBySwappingKeyWithValue;
#end
#interface NSDictionary (Swapping)
- (NSDictionary *)dictionaryBySwappingKeyWithValue
{
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithCapacity:self.count];
[self enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) {
id newKey = nil;
if ([value isKindOfClass:[NSDictionary class]]) {
newKey = [value dictionaryBySwappingKeyWithValue];
} else if ([value isKindOfClass:[NSArray class]]) {
newKey = [value arrayBySwappingKeyWithValue];
} else {
newKey = value;
}
if (![newKey conformsToProtocol:#protocol(NSCopying)]) {
newKey = [NSValue valueWithNonretainedObject:newKey];
}
mutableDictionary[newKey] = key;
}];
return [NSDictionary dictionaryWithDictionary:mutableDictionary];
}
#end
and...
#interface NSArray (Swapping)
- (NSArray *)arrayBySwappingKeyWithValue;
#end
#implementation NSArray (Swapping)
- (NSArray *)arrayBySwappingKeyWithValue
{
NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:self.count];
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([obj isKindOfClass:[NSDictionary class]]) {
NSDictionary *newDict = [obj dictionaryBySwappingKeyWithValue];
mutableArray[idx] = newDict;
} else if ([obj isKindOfClass:[NSArray class]]) {
NSArray *newArray = [obj arrayBySwappingKeyWithValue];
mutableArray[idx] = newArray;
} else {
mutableArray[idx] = obj;
}
}];
return [NSArray arrayWithArray:mutableArray];
}
#end
As an example, assume you have a dictionary with the following structure:
UIView *view = [[UIView alloc] init];
NSDictionary *dict = #{#"1" : #"a",
#"2" : #[ #{ #"5" : #"b" } ],
#"3" : #{#"6" : #"c"},
#"7" : view};
NSDictionary *newDict = [dict dictionaryBySwappingKeyWithValue];
Printing the newDict object in the console will give you this output:
(lldb) po mutableDictionary
{
a = 1;
({b = 5;}) = 2;
{c = 6;} = 3;
"<30b50617>" = 7;
}
As you can see, not only have the keys and values been swapped at the first level of the hierarchy, but deep inside each collection.
"<30b50617>" represents the UIView object wrapped inside a NSValue. Since UIView does not comply to the NSCopying protocol, it needs to be handled this way if you want it to be a key in your collection.
Note: Code was done in a couple of minutes. Let me know if I missed something.
for (NSString *key in [myDictionary allKeys]) {
NSString *value = [responseDataDic objectForKey:key];
[myDictionary removeObjectForKey:key];
[myDictionary addObject:key forKey:value];
}
Assumption:
No key = value;
Complexity:
No extra space required. Will loop through once and replace all key value pairs.
NSArray* allKeys = [theDict allKeys];
NSArray* allValues = [theDict allValues];
NSMutableDictionary* newDict = [NSMutableDictionary dictionaryWithObjects:allKeys forKeys:allValues];
Is there a way to grab a totally random key in the NSDictionary?
NSString *key = [enumeratorForKeysInDictionary nextObject];
I have this code which iterates over the dictionary in a non-random way.
Should I add all the keys to an NSSet and then pull randomly from there?
Is there a more efficient way to do this?
See this:
NSArray *array = [dictionary allKeys];
int random = arc4random()%[array count];
NSString *key = [array objectAtIndex:random];
NSArray* allKeys = [dictionary allKeys];
id randomKey = allKeys[arc4random_uniform([allKeys count])];
id randomObject = dictionary[randomKey];
You can also try something like this:
NSArray* allKeys = [myDictionary allKeys];
Then you can c method rand() to get a random index in the above NSArray to get the random key.
Same idea here, using a random index into keys, but a few improvements: (1) a dictionary category, (2) a block enumerator, like the native, (3) most important - to enumerate randomly, we must eliminate keys already visited. This will visit each key randomly, exactly once (unless the caller sets stop=YES in the block):
//
// NSDictionary+RandBlockEnum.h
//
#import <Foundation/Foundation.h>
#interface NSDictionary (RandBlockEnum)
- (void)enumerateKeysAndObjectsRandomlyUsingBlock:(void (^)(id, id, BOOL *))block;
#end
//
// NSDictionary+RandBlockEnum.m
//
#import "NSDictionary+RandBlockEnum.h"
#implementation NSDictionary (RandBlockEnum)
- (void)enumerateKeysAndObjectsRandomlyUsingBlock:(void (^)(id, id, BOOL *))block {
NSMutableArray *keys = [NSMutableArray arrayWithArray:[self allKeys]];
BOOL stop = NO;;
while (keys.count && !stop) {
id randomKey = keys[arc4random_uniform(keys.count)];
block(randomKey, self[randomKey], &stop);
[keys removeObject:randomKey];
}
}
#end
Call it like this:
#import "NSDictionary+RandBlockEnum.h"
NSDictionary *dict = #{ #"k1" : #"o1", #"k2" : #"o2", #"k3" : #"o3" };
[dict enumerateKeysAndObjectsRandomlyUsingBlock:^(id key, id object, BOOL *stop) {
NSLog(#"%#, %#", key, object);
}];
Modern, one-line version of #BestCoder's answer:
Objective-C
dictionary.allKeys[arc4random_uniform(dictionary.allKeys.count)]
Swift
Array(dictionary.keys)[Int(arc4random_uniform(UInt32(dictionary.keys.count)))]
You can create a set and then grab any object from the set.
NSSet *set = [NSSet setWithArray:[myDictionary allKeys]];
NSString *randomKey = [set anyObject];
SWIFT 4
you have a dictionary
let ads = _segueDataFromHomeToGuessMatch_NSType[Constants.Matches.ads] as! NSDictionary
get a random key value like this
let rd_ad_key = ads.allKeys[Int(arc4random_uniform(UInt32(ads.count)))] as! String
get value of the random key like this
let rd_ad_value = ads[rd_ad_key] as! String
Currently I am using the following method to validate the data for not being Null.
if ([[response objectForKey:#"field"] class] != [NSNull class])
NSString *temp = [response objectForKey:#"field"];
else
NSString *temp = #"";
Problem comes when the response Dictionary contains hundreds of attributes (and respective values). I need to add this kind of condition to each and every element for the dictionary.
Any other way around to accomplish?
Any Suggestion for making any change to the web service (except not inserting the null value to database)?
Any Idea, Anyone ??
What I've done is put a category on NSDictionary
#interface NSDictionary (CategoryName)
/**
* Returns the object for the given key, if it is in the dictionary, else nil.
* This is useful when using SBJSON, as that will return [NSNull null] if the value was 'null' in the parsed JSON.
* #param The key to use
* #return The object or, if the object was not set in the dictionary or was NSNull, nil
*/
- (id)objectOrNilForKey:(id)aKey;
#end
#implementation NSDictionary (CategoryName)
- (id)objectOrNilForKey:(id)aKey {
id object = [self objectForKey:aKey];
return [object isEqual:[NSNull null]] ? nil : object;
}
#end
Then you can just use
[response objectOrNilForKey:#"field"];
You can modify this to return a blank string if you'd like.
First a minor point: your test is not idiomatic, you should use
if (![[response objectForKey:#"field"] isEqual: [NSNull null]])
If you want all keys in your dictionary that have a value of [NSNull null] to be reset to the empty string, the easiest way to fix it is
for (id key in [response allKeysForObject: [NSNull null]])
{
[response setObject: #"" forKey: key];
}
The above assumes response is a mutable dictionary.
However, I think you really need to review your design. You shouldn't be allowing [NSNull null] values at all if they are not allowed in the database.
It's not quite clear for me what you need but:
If you need to check whether the value for key is not NULL you can do this:
for(NSString* key in dict) {
if( ![dict valueForKey: key] ) {
[dict setValue: #"" forKey: key];
}
}
If you have some set of required keys, you can create static array and then do this:
static NSArray* req_keys = [[NSArray alloc] initWithObjects: #"k1", #"k2", #"k3", #"k4", nil];
Then in the method where you check your data:
NSMutableSet* s = [NSMutableSet setWithArray: req_keys];
NSSet* s2 = [NSSet setWithArray: [d allKeys]];
[s minusSet: s2];
if( s.count ) {
NSString* err_str = #"Error. These fields are empty: ";
for(NSString* field in s) {
err_str = [err_str stringByAppendingFormat: #"%# ", field];
}
NSLog(#"%#", err_str);
}
static inline NSDictionary* DictionaryRemovingNulls(NSDictionary *aDictionary) {
NSMutableDictionary *returnValue = [[NSMutableDictionary alloc] initWithDictionary:aDictionary];
for (id key in [aDictionary allKeysForObject: [NSNull null]]) {
[returnValue setObject: #"" forKey: key];
}
return returnValue;
}
response = DictionaryRemovingNulls(response);
What I'm trying to do is search an array of dictionaries for a specific target dictionary, and if found, replace the actual dictionary in the original array with the target dictionary. The search algorithim works, but the copying of dictionaries doesn't. The main line in question is the one that says:
tempDict=targetDict;
I was hoping that tempDict would be a pointer to the original dictionary from the original array, but when trying to log the author name, I get "moe" instead of "steve".
-(void)viewDidAppear
{
[actualDictionary setObject:#"test" forKey:#"mainNote"];
[actualDictionary setObject:#"moe" forKey:#"authorName"];
[targetDictionary setObject:#"test" forKey:#"mainNote"];
[targetDictionary setObject:#"steve" forKey:#"authorName"];
[arrayOfNotes addObject:actualDictionary];
[self beginSearchWithMainArray];
}
-(void)beginSearchWithMainArray;
{
[self searchArray:arrayOfNotes forDict:targetDictionary];
}
-(void)searchArray:(NSMutableArray*)array forDict:(NSMutableDictionary*)targetDict
{
NSString *targetText=[targetDict objectForKey:#"mainNote"];
for(int i=0;i<[array count];i++)
{
NSMutableDictionary *tempDict=[array objectAtIndex:i];
NSString *possibleText=[tempDict objectForKey:#"mainNote"];
if([possibleText isEqualToString:targetText])
{
//found match, replace tempDict with targetDict
tempDict=targetDict;
NSLog(#"found match");
NSString *authorName=[[arrayOfNotes objectAtIndex:0] objectForKey:#"authorName"];
NSLog(#"%#", authorName); //should be steve
return;
}
//no match, search sub notes
...
}
}
replace
tempDict=targetDict;
with
[array replaceObjectAtIndex:i withObject:targetDict];
or
[tempDict setDictionary:targetDict]
tempDict is a pointer to a NSMutableDictionary, but assign it to another instance doesn't means change the content of the previous instance
you have to modify what "pointer point to" not the "pointer", thats why you can use setDictionary: to do the "assignment"
tempDict is just a reference to the matched element of the array. Changing its value will not modify the array. Replace
tempDict=targetDict;
to
[array replaceObjectAtIndex:i withObject:targetDict];
I am having a NSMutableDictionary. I have to dynamically rename any Key in the dictionary to a new value, in my code.. I can't find any built-in API to do this..
How can I do this? Is there any built-in API available to do this?
Thanks everyone..
// assumes that olkdey and newkey won't be the same; they can't as
// constants... but...
[dict setObject: [dict objectForKey: #"oldkey"] forKey: #"newkey"];
[dict removeObjectForKey: #"oldkey"];
Think about what "directly editing an existing key" means. A dictionary is a hash; it hashes the contents of the keys to find a value.
What happens if you were to change the contents of a key? The key would need to be rehashed (and the dictionary's internal structures re-balanced) or the value would no longer be retrievable.
Why do you want to edit the contents of a key in the first place? I.e. what problem does that solve that the above does not?
This should work:
- (void) renameKey:(id<NSCopying>)oldKey toKey:(id<NSCopying>)newKey{
NSObject *object = [dictionary objectForKey:oldKey];
[object retain];
[dictionary removeObjectForKey:oldKey];
[dictionary setObject:object forKey:newKey];
[object release];
}
This does exactly the same as bbum's answer but, if you remove the old key first (like in this example) then you have to retain the object temporarily otherwise it might get deallocated in the way ;)
Conclusion: Unless you need explicitly to remove the old key first do as bbum.
#interface NSMutableDictionary (KAKeyRenaming)
- (void)ka_replaceKey:(id)oldKey withKey:(id)newKey;
#end
#implementation NSMutableDictionary (KAKeyRenaming)
- (void)ka_replaceKey:(id)oldKey withKey:(id)newKey
{
id value = [self objectForKey:oldKey];
if (value) {
[self setObject:value forKey:newKey];
[self removeObjectForKey:oldKey];
}
}
#end
This also handles the case where the dictionary doesn't have a value for the key nicely.
I have to navigate a complete JSON response object that holds fields, sub-dictionaries and sub-arrays. All because one of the JSON fields is called "return" which is an iOS reserved word, so can't be used with the JSONModel Cocoa Pod.
Here's the code:
+ (id) sanitizeJSON:(id) dictIn {
if (dictIn) //check not null
{
// if it's a dictionary item
if ([dictIn isKindOfClass:[NSDictionary class]])
{
NSMutableDictionary *dictOut = [dictIn mutableCopy];
// Do the fix replace "return" with "not_return"
if ([dictOut objectForKey: #"return"])
{[dictOut setObject: [dictIn objectForKey: #"return"] forKey: #"not_return"];
[dictOut removeObjectForKey: #"return"];}
// Continue the recursive walk through
NSArray*keys=[dictOut allKeys]; //get all the keys
for (int n=0;n<keys.count;n++)
{
NSString *key = [keys objectAtIndex:n];
//NSLog(#"key=%# value=%#", key, [dictOut objectForKey:key]);
if (([[dictOut objectForKey:key] isKindOfClass:[NSDictionary class]]) || ([[dictOut objectForKey:key] isKindOfClass:[NSArray class]]))
{
// recursive call
id sanitizedObject = [self sanitizeJSON:[dictOut objectForKey:key]];
[dictOut removeObjectForKey: key];
[dictOut setObject:sanitizedObject forKey:key];
// replace returned (poss modified) item with this one
}
}
return dictOut; //return dict
}
else if ([dictIn isKindOfClass:[NSArray class]]) //Or if it's an array item
{
NSMutableArray *tempArray = [dictIn mutableCopy];
// Do the recursive walk across the array
for (int n=0;n< tempArray.count; n++)
{
// if array item is dictionary
if (([[tempArray objectAtIndex:n] isKindOfClass:[NSDictionary class]]) || ([[tempArray objectAtIndex:n] isKindOfClass:[NSArray class]]))
{
// recursive call
id sanitizedObject = [self sanitizeJSON:[tempArray objectAtIndex:n]];
// replace with the possibly modified item
[tempArray replaceObjectAtIndex:n withObject:sanitizedObject];
}
}
return tempArray; //return array
}
return dictIn; //Not nil or dict or array
}
else
return dictIn; //return nil
}