I've already asked a similar question but this is slightly different so i Thought it would be fine to ask another question.
I'm storing Arrays within Arrays, as well as NSDictionaries. It's a utility kind of application and there is no set structure, the user can enter nested information as much as they require.
I need a method that can scroll through the entire contents of an Array/Dictionary and return the specific dictionary based on a value that I send the method. Here's an example..
NSMutableArray *array = [[NSMutableArray alloc]init];
NSMutableDictionary *enteredItem = [[NSMutableDictionary alloc]init];
[enteredItem setObject:#"i'm a title" forKey:#"title"];
[enteredItem setObject:#"i'm an id" forKey:#"id"];
[enteredItem setObject:#"i'm a description" forKey:#"description"];
[enteredItem setObject:#"i'm a timestamp" forKey:#"timestamp"];
[enteredItem setObject:array forKey:#"items"];
[array addObject:enteredItem];
[array addObject:anotherDictionary];
[array addObject:moreDictionaries];
So in the example above, I would need to find (return) the dictionary that contains "i'm an id".
The similar question I asked is located here.. Searching for a value in an unknown NSMutableArray depth - I'm having trouble returning a dictionary and also comparing the object.
Hopefully my question is clear. Thanks for any help you can offer.
Continuing from the answer, you need to do some modification:
- (id)findDictionaryWithValue:(id)value inArray:(NSArray *)array
{
__block id match = nil;
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
if ([obj isKindOfClass:[NSArray class])
{
match = [self findDictionaryWithValue:value inArray:obj];
}
else if ([obj isKindOfClass:[NSDictionary class])
{
match = [self findDictionaryWithValue:value inDictionary:obj];
}
*stop = (match!=nil);
}];
return match;
}
- (id)findDictionaryWithValue:(id)value inDictionary:(NSDictionary *)dict;
{
__block id match = nil;
[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if([obj isEqual:value]) {
match = dict;
}
else if ([obj isKindOfClass:[NSArray class])
{
match = [self findDictionaryWithValue:value inArray:obj];
}
else if ([obj isKindOfClass:[NSDictionary class])
{
match = [self findDictionaryWithValue:value inDictionary:obj];
}
*stop = (match!=nil);
}];
return match;
}
NSDictionary *resultDict = [self findDictionaryForValue:#"i'm an id" inArray:array];
However, you must be careful with recursion. If there is any circular path, you will get an infinite loop( and stack overflow before crash).
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];
As I have a requirement to add similar objects into the array, I have created new dictionary in such a way.
NSMutableDictionary* existingStepDict = [[[arrayForSteps objectAtIndex:0] mutableCopy] autorelease];
[arrayForSteps addObject:existingStepDict];
[existingStepDict release];
Now, what happens here is that later when I change something in any one of the dictionary, the other one also gets updated. I require both these dictionaries to behave independently.
For that I went through Deep-copy of dictionaries whose code is like this.
NSMutableDictionary* existingStepDict = [[[arrayForSteps objectAtIndex:0] mutableCopy] autorelease];
NSMutableDictionary* destination = [NSMutableDictionary dictionaryWithCapacity:0];
NSDictionary *deepCopy = [[NSDictionary alloc] initWithDictionary:existingStepDict copyItems: YES];
if (deepCopy) {
[destination addEntriesFromDictionary: deepCopy];
[deepCopy release];
}
//add Properties array to Steps Dictionary
[arrayForSteps addObject:destination];
But this too didn't reflect the difference. I know I am making some minor mistake here.
But could some one help me getting my result?
Thanks a lot!
There's an easy way to get a full deepcopy of an NSDictionary o NSArray using the NSCoding (serialization) protocol.
- (id) deepCopy:(id)mutableObject
{
NSData *buffer = [NSKeyedArchiver archivedDataWithRootObject:mutableObject];
return [NSKeyedUnarchiver unarchiveObjectWithData: buffer];
}
In this way you can duplicate any object plus all the obects it contains in a single step.
when I need a mutable deep copy of a NSDictionary I create a Category with this method:
- (NSMutableDictionary *)mutableDeepCopy
{
NSMutableDictionary *returnDict = [[NSMutableDictionary alloc] initWithCapacity:[self count]];
NSArray *keys = [self allKeys];
for (id key in keys) {
id oneValue = [self valueForKey:key];
id oneCopy = nil;
if ([oneValue respondsToSelector:#selector(mutableDeepCopy)]) {
oneCopy = [oneValue mutableDeepCopy];
} else if ([oneValue respondsToSelector:#selector(mutableCopy)]) {
oneCopy = [oneValue mutableCopy];
}
if (oneCopy == nil) {
oneCopy = [oneValue copy];
}
[returnDict setValue:oneCopy forKey:key];
}
return returnDict;
}
EDIT
and searching the web I found this, I haven't tested
NSMutableDictionary *mutableCopy = (NSMutableDictionary *)CFPropertyListCreateDeepCopy(kCFAllocatorDefault, (CFDictionaryRef)originalDictionary, kCFPropertyListMutableContainers);
I want to get the index of an object within the NSMutableArray of categories.
The category object has an attribute "category_title" and I want to be able to get the index by passing the value of category_title.
I have looked through the docs and can't find a simple way to go about this.
NSArray does not guarantee that you can only store one copy of a given object, so you have to make sure that you handle that yourself (or use NSOrderedSet).
That said, there are a couple approaches here. If your category objects implement isEqual: to match category_title, then you can just use -indexOfObject:.
If you can't do that (because the category objects use a different definition of equality), use -indexOfObjectPassingTest:. It takes a block in which you can do whatever test you want to define your "test" - in this case, testing category_title string equality.
Note that these are all declared for NSArray, so you won't see them if you are only looking at the NSMutableArray header/documentation.
EDIT: Code sample. This assumes objects of class CASCategory with an NSString property categoryTitle (I can't bring myself to put underscores in an ivar name :-):
CASCategory *cat1 = [[CASCategory alloc] init];
[cat1 setCategoryTitle:#"foo"];
CASCategory *cat2 = [[CASCategory alloc] init];
[cat2 setCategoryTitle:#"bar"];
CASCategory *cat3 = [[CASCategory alloc] init];
[cat3 setCategoryTitle:#"baz"];
NSMutableArray *array = [NSMutableArray arrayWithObjects:cat1, cat2, cat3, nil];
[cat1 release];
[cat2 release];
[cat3 release];
NSUInteger barIndex = [array indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
if ([[(CASCategory *)obj categoryTitle] isEqualToString:#"bar"]) {
*stop = YES;
return YES;
}
return NO;
}];
if (barIndex != NSNotFound) {
NSLog(#"The title of category at index %lu is %#", barIndex, [[array objectAtIndex:barIndex] categoryTitle]);
}
else {
NSLog(#"Not found");
}
Not sure that I understand the question but something like this might work (assuming the Mutable Array contains objects of Class "Category"):
int indx;
bool chk;
for (Category *aCategory in theArray)
{
chk = ([[aCategory category_title] isEqualToString:#"valOfCategoryTitle"])
if ( chk )
indx = [theArray indexOfObject:aCategory];
}
Try this code much more simpler:-
int f = [yourArray indexOfObject:#"yourString"];
Now I'm trying the following and it works.
- (void)findDictionaryWithValueForKey:(NSString *)name {
for (NSDictionary * set in myArray) {
if ([[set objectForKey:#"title"] isEqualToString:name])
\\do something
}
}
EDIT:
I've added one extra argument to the post of bshirley. Now it looks more flexible.
- (NSDictionary *)findDictionaryWithValue:(NSString*)name forKey:(NSString *)key {
__block BOOL found = NO;
__block NSDictionary *dict = nil;
[self.cardSetsArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
dict = (NSDictionary *)obj;
NSString *title = [dict valueForKey:key];
if ([title isEqualToString:name]) {
found = YES;
*stop = YES;
}
}];
if (found) {
return dict;
} else {
return nil;
}
}
Here's one possible implementation using newer API. (I also modified the method to actually return the value). Provided mostly to demonstrate that API. The assumption is that the title is unique to one dictionary within your array.
- (NSDictionary *)findDictionaryWithValueForKey:(NSString *)name {
// ivar: NSArray *myArray;
__block BOOL found = NO;
__block NSDictionary *dict = nil;
[myArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
dict = (NSDictionary *)obj;
NSString *title = [dict valueForKey:#"title"];
if ([title isEqualToString:name]) {
found = YES;
*stop = YES;
}
}];
if (found) {
return dict;
} else {
return nil;
}
}
Use filteredArrayUsingPredicate: method of the array to get all the dictionaries that satisfy your requirement.
NSPredicate * predicate = [NSPredicate predicateWithFormat:#" title MATCHES[cd] %#", name];
NSArray * matches = [myArray filteredArrayUsingPredicate:predicate];
Now matches is the array of dictionaries that have the title key equal to name.
- (void)findDictionaryWithValueForKey:(NSString)name {
for (NSDictionary * set in myArray) {
NSString *s=[NSString stringWithFormat:#"%#",[set objectForKey:#"title"]];
if ([s isEqualToString:name])
\\do something
}
OR
if (s == name])
\\do something
}
}
I will also suggest this way,it would be better if you use a break statement,
- (void)findDictionaryWithValueForKey:(NSString)name {
for (NSDictionary * set in myArray) {
if ([[set objectForKey:#"title"] isEqualToString:name])
\\do something
break;
}
}
As per the NSArray documentation,
valueForKey:
Returns an array containing the results of invoking valueForKey: using key on each of the array's objects.
- (id)valueForKey:(NSString *)key
Parameters
key
The key to retrieve.
Return Value
The value of the retrieved key.
Discussion
The returned array contains NSNull elements for each object that returns nil.
Availability
* Available in Mac OS X v10.3 and later.
EDIT:
try this,
[myArray valueForKey:#"name"];
//this will return array of values, but this actually differ from what to want
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
}