Comparing two NSDictionaries and Find Difference - ios

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

Related

iOS binary search code crash above 256 items

I've used iOS default code to search string in custom dictionary object which exist in array, Means array of dictionary items.
NSRange searchRange = NSMakeRange(0, artistThumbnail.count);
index = [artistThumbnail indexOfObject:artist
inSortedRange:searchRange
options:NSBinarySearchingLastEqual
usingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
NSString *artistName = (NSString *)obj2;
NSDictionary *artistThumbnail = (NSDictionary *)obj1;
NSString *artistToCompare = artistThumbnail[_artist_thumb];
NSComparisonResult result = [artistToCompare compare:artistName];
return result;
}];
When I used same code with less then equal to 256 objects code work fine no crash, both Obj1 and Obj2 have the dictionary object, but when array have >256 items then it crash and I've noticed that obj1 and obj2 not the same as the previously always come in NSDictionary format.
The problem is that your comparison object is of a different type than your array elements. If you type the array properly as NSArray<NSDictionary *> * you get a warning saying that parameter indexOfObject should be of type NSDictionary *.
Furthermore, you cannot rely on the artist object always is passed as obj2 to the comparison function. The documentation does not say anything about this, so you should assume that it can be passed as obj1 sometimes and obj2 other times.
To solve it you can embed the artist in a dictionary to make it the same format as an array element like this #{ _artist_thumb: artist }. Then you retrieve the name from both parameters the same way.
NSRange searchRange = NSMakeRange(0, artistThumbnail.count);
index = [artistThumbnail indexOfObject:#{ _artist_thumb: artist }
inSortedRange:searchRange
options:NSBinarySearchingLastEqual
usingComparator:^NSComparisonResult(id _Nonnull obj1, id _Nonnull obj2) {
NSString *artistName = ((NSDictionary *)obj2)[_artist_thumb];
NSDictionary *artistThumbnail = (NSDictionary *)obj1;
NSString *artistToCompare = artistThumbnail[_artist_thumb];
NSComparisonResult result = [artistToCompare compare:artistName];
return result;
}];

ordering array with another array

I have the following categoryNames array.
And now, I have categoryTempElements and it has cName property. I need to know how to order categoryTempElements with a order of categoryNames.
UPDATE: I have added sortOrder property to Category object and tried the following but order does not change.
for (Category* a in categoryTempElements) {
int index = (int)[categoryNames indexOfObject:a.cName];
a.sortOrder = index;
}
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"sortOrder" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray *sortedArray = [categoryTempElements sortedArrayUsingDescriptors:sortDescriptors];
Another way could be without using sortedArrayUsingComparator, using two for-loops. Declare a new Mutable array called sortedCategoryElements and compare the categoryNames in categoryTempElements, If matches add it to a new array sortedCategoryElements:
NSMutableArray *sortedCategoryElements = [NSMutableArray new];
for (NSString *name in categoryNames) {
for (Category *category in categoryTempElements) {
if (name == category.cName) {
[sortedCategoryElements addObject:category];
break;
}
}
}
I tried with your set of data, it worked for me.
Hope it helps!
You need first convert your categoryNames array into dictionary with NSString key and NSNumber int value, the value will be the order in the array
//this is example code, this will be your first array (reference value array)
NSArray * array = #[#"prueba",#"prueba2",#"prueba3"];
//first you need convert this array in NSDictionary
NSMutableDictionary * arrayDict = [NSMutableDictionary dictionary];
int counter = 0;
for (NSString * value in array) {
if(arrayDict[value] == nil)
{
arrayDict[value] = [NSNumber numberWithInt:counter];
}
counter++;
}
After that then you can get the value and order with sortedArrayUsingComparator method, something like this
//this is an example of your second array categoryTempElements
NSArray * arrayOfObjs = #[[testObject testObjectWithName:#"prueba3"],[testObject testObjectWithName:#"prueba"],[testObject testObjectWithName:#"prueba2"]];
NSArray * sorted = [arrayOfObjs sortedArrayUsingComparator:^NSComparisonResult(testObject * _Nonnull obj1, testObject * _Nonnull obj2) {
if([((NSNumber*)arrayDict[obj1.cName]) intValue] < [((NSNumber*)arrayDict[obj2.cName]) intValue]){
return NSOrderedAscending;
}
if([((NSNumber*)arrayDict[obj1.cName]) intValue] > [((NSNumber*)arrayDict[obj2.cName]) intValue]){
return NSOrderedDescending;
}
return NSOrderedSame;
}];
for (testObject * obj in sorted) {
NSLog(#"%#",obj.cName);
}
And voila in sorted you will have your array of object sorted by your first array NSString order
Hope this helps
Create a dictionary with cName as the key and the Category object as the value. Then iterate over the categoryNames array, and build another array by using each item in categoryNames as the key. The resulting array should be sorted in the same order as categoryNames.
NSArray has a method sortedArrayUsingComparator: which sorts an array using the ordering implemented by the block you pass. This block, of type NSComparator, is passed references to two elements of your array and you must return the order of those two elements.
And now, I have categoryTempElements and it has cName property.
So your block will be passed two categoryTempElements, you need to access the cName property of each, and compare the resulting two values...
I need to know how to order categoryTempElements with a order of categoryNames
by the position, i.e. the index, of those values in your categoryNames array. The method indexOfObject: provides that index for you.
So put that together and your problem is solved.
HTH

Finding distinct array elements based on dictionary key

I have two arrays of key-value pairs. Both these arrays contain different key-value pairs. I want to find elements in the first array that are not part of the second array based on a particular key.
Example:
1st Array - [{id=1, name="foo"},
{id=2, name="bar"}]
2nd Array - [{id=2, name="abc"},
{id=1, name="xyz"}]
Is there a way I can implement the same?
Right now I enumerate through the two arrays like so:
for (NSDictionary *eachPlayer in 1stArray) {
for (NSDictionary *eachPrediction in 2ndArray) {
if (eachPrediction[kId] != eachPlayer[kId]) {
[self.predictPlayerArray addObject:eachPlayer];
}
}
}
But this fails in the above case and adds both the values to the predictionPlayerArray - in the first iteration it adds 1 and in the forth iteration it adds 2. How do I prevent that from happening?
Thanks.
EDIT
I seem to have solved it this way. Not the best solution but it seems to be working:
for (NSDictionary *eachPlayer in arrayOne) {
for (NSDictionary *eachPrediction in arrayTwo) {
if (eachPrediction[kId] == eachPlayer[kId]) {
if ([self.predictPlayerArray containsObject:eachPlayer]) {
[self.predictPlayerArray removeObject:eachPlayer];
}
break;
}
else {
[self.predictPlayerArray addObject:eachPlayer];
}
self.predictPlayerArray = [self.predictPlayerArray valueForKeyPath:#"#distinctUnionOfObjects.self"];
}
}
Something like this should do:
NSArray *array1 = #[#{#"1":#"foo"},#{#"2":#"bar"},#{#"3":#"abc"}];
NSArray *array2 = #[#{#"2":#"abc"},#{#"1":#"abc"},#{#"4":#"foo"}];
NSMutableSet *result = [NSMutableSet new];
for (NSDictionary *dict1 in array1){
[dict1 enumerateKeysAndObjectsUsingBlock:^(id key1, id obj1, BOOL *stop1) {
for (NSDictionary *dict2 in array2) {
[dict2 enumerateKeysAndObjectsUsingBlock:^(id key2, id obj2, BOOL *stop2) {
if ([obj2 isEqual:obj1]){
[result addObject:#{key1:obj1}];
*stop2 = YES;
}
}];
}
}];
}
NSLog(#"result %#", result);
As you has nested dictionaries you should iterate also in them and finally store the result in a set that would prevent to have duplicate entries (if you use a NSMutableArray you will have twice {3:abc})
The log output is:
2015-02-03 13:53:07.897 test[19425:407184] result {(
{
1 = foo;
},
{
3 = abc;
}
)}

How to loop NSDictionary obtained from JSON?

How can i loop through the following dictionary obtained from JSON? How can i loop to get only the id 0001, 0002?
{
0001 = {
userName = "a";
photo = "";
};
0002 = {
userName = "b";
photo = "";
};
}
You loop thru the NSDictionary keys:
NSArray *keys = [dictionary allKey];
for (id *key in keys ) {
NSDictionary *userPhoto = [dictionary objectForKey:key];
// here you can either parse the object to a custom class
// or just add it to an array.
}
Or use the fast enumeration directly on the NSDictionary:
for (id *key in dictionary ) {
NSDictionary *userPhoto = [dictionary objectForKey:key];
// here you can either parse the object to a custom class
// or just add it to an array.
}
Per key you can retrieve the object.
or use the enumerateKeysAndObjectsUsingBlock:
[dictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
// Here you can access the object and key directly.
}
Try this way...
Get all keys
NSArray *a=[yourDictionary allKeys];
NSArray *keys = [dictionary allKeys];
Try this. You will get all keys in an array. And then you can get them in NSString accordingly .
Another alternative is using the enumerateKeysAndObjectsUsingBlock: api to enumerate the keys and objects,
Usage is pretty simple,
[dictionary enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
NSLog(#"Key: %#, Value:%#",key,obj);
if([key isEqualToString:#"0001"]) {
//Do something
}
// etc.
}];
Hope that helps!
I found the answer. I already tried with the following code but it is giving all the data.
Because the json i got is in the worng format.
for (NSString *key in Dict) {}

How to delete an entry from NSDictionary which is the element of NSDictionary?

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.

Resources