I have an NSArray of NSDictionaries.
The dictionaries has keys like this: color, number, code, description and size.
Now I have a key and I want to locate that dictionary inside the array, some magic command like:
NSDictionary *oneDict = [get a dictionary from myArray where "code" is equal to "32"];
code is a key, 32 a value.
I know how to enumerate the array and search one by one every dict, but I know objective-c is a bag full of "tricks" and a "magic command" may exist to extract surgically this dictionary from the array in one line.
Any clues?
You can use indexOfObjectPassingTest to get the object index:
NSUInteger index = [myArray indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
NSDictionary* dict = obj;
return [obj[#"code"] intValue] == 32;
}];
If not found, result will be NSNotFound;
This can be done with an NSPredicate simpler:
NSPredicate * predicate = [NSPredicate predicateWithFormat:#"code MATCHES[cd] %#", value];
NSArray *result = [myArray filteredArrayUsingPredicate:predicate];
The result array will hold all NSDictionaries that did match the value.
Take a look at the NSPredicate documentation
It is very powerful
If you intend to do the lookup many times and don't mind spending some time on setup, you could create a dictionary that maps the values of code to the dictionaries that contain those values. i.e.:
NSDictionary * mapping = [ NSDictionary dictionaryWithObjects:myArray forKeys:[ myArray valueForKey:#"code" ] ]
id answer = mapping[#"32" ] ;
This only works if code contains unique values. If the values are not unique:
NSArray * codeValues = [ myArray valueForKey:#"code" ] ;
NSIndexSet indexes = [ codeValues indexesOfObjectsPassingTest:^BOOL(id object){ [ object isEqual:#"32" ] } ] ;
NSArray * matchingDictionaries = [ myArray objectsAtIndexes:indexes ] ;
matchingDictionaries will be an array containing dictionaries where code is #"32".
Replace #"32" in these examples with the value of code you wish to find, &c.
Related
I am working on an iOS app, where I will be getting a JSON Object from server, which will be populated on a UITableView.
User can change values on tableview, Hence resulting in a new JSON.
Now I want to send only delta (Difference of Two JSON Objects) back to server.
I know I can traverse both Objects for finding delta. But just wish to know is there any easy solution for this problem.
Ex:
NSDictionary *dict1 = {#"Name" : "John", #"Deptt" : #"IT"};
NSDictionary *dict2 = {#"Name" : "Mary", #"Deptt" : #"IT"};
Delta = {#"Name" : "Mary"}
Considering new value is Mary for key name;
Thanks In Advance
isEqualToDictionary: Returns a Boolean value that indicates whether the contents of the receiving dictionary are equal to the contents of another given dictionary.
if ([NSDictionary1 isEqualToDictionary:NSDictionary2) {
NSLog(#"The two dictionaries are equal.");
}
Two dictionaries have equal contents if they each hold the same number of entries and, for a given key, the corresponding value objects in each dictionary satisfy the isEqual: test.
Here's how to get all the keys with non-matching values. What to do with those keys is app level question, but the most informative structure would include an array of mismatched values from both dictionaries, as well has handle keys from one that are not present in the other:
NSMutableDictionary *result = [#{} mutableCopy];
// notice that this will neglect keys in dict2 which are not in dict1
for (NSString *key in [dict1 allKeys]) {
id value1 = dict1[key];
id value2 = dict2[key];
if (![value1 equals:value2]) {
// since the values might be mismatched because value2 is nil
value2 = (value2)? value2 : [NSNull null];
result[key] = #[value1, value2];
}
}
// for keys in dict2 that we didn't check because they're not in dict1
NSMutableSet *set1 = [NSMutableSet setWithArray:[dict1 allKeys]];
NSMutableSet *set2 = [NSMutableSet setWithArray:[dict2 allKeys]];
[set2 minusSet:set1]
for (NSString *key in set2) {
result[key] = #[[NSNull null], dict2[key]];
}
There are certainly more economical ways to do it, but this code is optimized for instruction.
Just enumerate through and compare the dictionaries key-by-key. This will output any differences as well as any unmatched keys on either side, you can tweak the logic depending on exactly what you want to include.
- (NSDictionary *)delta:(NSDictionary *)dictionary
{
NSMutableDictionary *result = NSMutableDictionary.dictionary;
// Find objects in self that don't exist or are different in the other dictionary
[self enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
id otherObj = dictionary[key];
if (![obj isEqual:otherObj]) {
result[key] = obj;
}
}];
// Find objects in the other dictionary that don't exist in self
[dictionary enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
id selfObj = self[key];
if (!selfObj) {
result[key] = obj;
}
}];
return result;
}
I have two NSMutableArrays, filled with dictionary objects, like
NSMutableArray * bigArray = #[dictionary1,dictionary2,dictionary3];
NSMutableArray * smallArray = #[dictionary1];
I want to remove the common elements (dictionay1) from the big array
Note: small array is subset of big array and elements are distinct
[bigArray removeObjectsInArray:smallArray];
removes all objects in bigArray that are contained in smallArray.
If each element is distinct (as you say in the comments) then you can filter the array using a predicate that removes all the objects that inside of the smaller subset. Since you are using an array in your question, I assume that the order of the objects matter.
If you need to preserve the order, then you will need to filter the array. Converting to a set will break the order (unless you are able to resort it afterwards.
You are using mutable arrays in your question, so I will do the same. Just be aware that the original array is actually modified:
NSMutableArray *original = [NSMutableArray arrayWithArray:#[#"A", #"E", #"C", #"B", #"D"]];
NSMutableArray *subset = [NSMutableArray arrayWithArray:#[#"C", #"B"]];
NSLog(#"before : %#", original);
[original filterUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
// return YES for objects to keep
return ![subset containsObject:evaluatedObject];
}]];
NSLog(#"after : %#", original);
The output of this code is:
before : (
A,
E,
C,
B,
D
)
after : (
A,
E,
D
)
You can see that the order is preserved.
If you don't want to modify the original array you can produce a new filtered array instead. The small difference is that you call filteredArrayUsingPredicate: instead of filterUsingPredicate:. The predicate is the same:
NSArray *filtered = [original filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
// return YES for objects to keep
return ![subset containsObject:evaluatedObject];
}]];
NSSet is perfect class to do this kind of job. Try that;
NSMutableSet *bigSet = [NSMutableSet setWithArray:bigArray];
[bigSet minusSet:[NSMutableSet setWithArray:smallArray]];
bigArray = [[bigSet allObjects] mutableCopy];
The thing worth to point is that if your array will store duplicate and you convert it to NSSet the duplicate will be removed in that case you shouldn't use it. The second thing is it can change order of the elements in your array.
I have a NSDictionary with currency-codes as keys and as values another NSDictionary inside containing a NSString (currency name) + a NSArray (list of coins):
The goal is to get a NSArray with currency-keys (AED, ARS, ...), sorted by name-value inside.
I know how to sort by keys and values, but can't figure out how to sort by the a value inside a NSDictionary inside a NSDictionary.
The following only gives me a sorted NSArray with the values, but I loose the keys:
NSMutableArray *dictValues = [[self.currencyDict allValues] mutableCopy];
[dictValues sortUsingComparator: (NSComparator)^(NSDictionary *a, NSDictionary *b)
{
NSString *key1 = [a objectForKey: #"name"];
NSString *key2 = [b objectForKey: #"name"];
return [key1 compare: key2];
}
];
In order to convert a dictionary of dictionaries into an array of dictionaries without losing the key, the first step is to put the key into the dictionary. In other words, you need to convert this
Root
AED
name "some name"
objects ...
ARS
name "other name"
objects ...
to this
Root
AED
name "some name"
objects ...
key "AED"
ARS
name "other name"
objects ...
key "ARS"
and then call allValues and sort the resulting array.
I have a NSArray of NSDictionaries. I need the updated NSArray of NSDictionaries with value of one of the dictionary key updated with new value.
Please see below the NSArray structure. I want to update the value for Key3 inside this structure if Key1 matches with some value (e.g. 2). What would the fastest way of doing this. I do not want to use traditional For loop.
[myArray valueForKeyPath:#"#unionOfObjects.Key3"];
<__NSCFArray 0xe70c10>(
{
Key1 = 1;
Key2 = "New Location";
Key3 = (
Data1,
Data2,
Data3
);
},
{
Key1 = 2;
Key2 = "Old Location";
Key3 = (
Data1,
Data2,
Data4
);
}
)
We can solve it by using predicates:
NSArray *dictionaryArray;
NSNumber *key =#2;
NSPredicate *predicate =[NSPredicate predicateWithFormat:#"Key1=%#",key];
NSArray *result =[dictionaryArray filteredArrayUsingPredicate:predicate];
result array now has all dictionaries having (key1 = 2)
Dictionarys can't be edited directly, they must be NSMutableDictionary to edit. Assuming they are NSMutableDictionary instances:
NSMutableDictionary *dic =result[0];
[dic setObject:someObject forKey:#"key3"];
Let's suppose that you already have an array containing all mutable dictionaries. The first step is to get what dictionaries you need to change:
NSPredicate* predicate= [NSPredicate predicateWithFormat: #"Key1=2"];
NSArray* filteredArray= [myArray filteredArrayUsingPredicate: predicate];
The second step is to replace the Key3 value for each object in the array. If you execute a selector on an array, and NSArray doesn't respond to that selector, the selector is performed on it's objects:
[filteredArray performSelector: #selector(setValue:forKey:) withObject: someValue withObject: #"Key3"];
After this statement you don't need to replace any object in myArray, because the filtered array contains the same objects that are in myArray, which are mutable dictionaries.
I would also go for predicates as #santhu stated on his answer. However, I want to point that when searching an unsorted array linear search (that is, looping through every object and checking if it's the wanted one) is optimal. Probably predicates will provide an speed-up due to their internal routines, but a predicate will still execute the "traditional for loop" you want to avoid and thus the speed-up will not be very significant.
There are other searching algorithms which provide a significant speed-up, but they are only valid for sorted databases (unless you have a quantum iPhone ;) ).
If you are interested on this topic, here you can find some types of search algorithms with an analysis of their complexity.
I have a problem which I can't solve for a long time. I have a JSON response from the server which is parsed to NSDictionary lastMsgs as in the image below:
So for example 1323 it's a key and it associated with NSDictionary (which contains keys such as body, subject etc and values). So the problem I need in some way delete an entry which nested NSDictionary value has entry : type = 1. I don't know how to do this. I tried to do this:
NSMutableArray* _ModelVals = [[lastMsgs allValues] mutableCopy];
for (int i =0; i<[_ModelVals count]; i++) {
string_compare = [NSString stringWithFormat:#"%#" , [_ModelVals objectAtIndex:i]];
if ([string_compare rangeOfString:#"type = 1"].location != NSNotFound) {
[_ModelVals removeObjectAtIndex:i];
}
}
But it is work not correctly and delete not all entries which has type = 1. So the question - how can I implement this and delete entry in nested NSDictionary?
There is no value "type = 1" in the dictionary. That's just the log. You get the value of a key in a dictionary using [dict objectForKey:#"key"] or dict[#"key"].
Judging from your log, the type seems to be an NSNumber, not an NSString. Just get the int representation of it (assuming the type is an integer) and use a simple C int to int comparison.
And you can't filter an array like that. You will skip an entry. If you remove an entry, you have to decrease i by 1.
Or use this simpler solution:
NSSet *keys = [lastMsgs keysOfEntriesPassingTest:^BOOL(id key, id obj, BOOL *stop) {
return [obj[#"type"] intValue] == 1;
}];
NSMutableDictionary *dict = [lastMsgs mutableCopy];
[dict removeObjectsForKeys:[keys allObjects]];
This will first collect the keys of all objects (dictionaries) that have a type of 1 and then remove those from a mutable copy of the original dictionary.
You cannot add or remove objects from a collection while enumerating though it. I would create a another array that you can store references to the objects that you want to delete and remove them after you have looped though it.