Merge two NSArrays consisting of dictionaries, based on a parameter in dictionaries - objective-c

I have a NSMutableArray and a NSArray. Both consist of elements that are NSDictionarys themselves.
A sample structure of both is as follows:
NSMutableArray
[
{
objectId = 4274;
name = orange;
price = 45;
status = approved;
},
{
objectId = 9035;
name = apple;
price = 56;
status = approved;
},
{
objectId = 7336;
name = banana;
price = 48;
status = approved;
}
.
.
.
.
]
and NSAraay is
NSArray
[
{
objectId = 4274;
name = orange;
price = 106;
status = not_approved;
},
{
objectId = 5503;
name = apple;
price = 56;
status = approved;
}
]
What I want is to merge these two arrays so that, if any element in NSArray has same objectId as any element in NSMutableArray, element in NSArray should overwrite on element in NSMutableArray.
So in this case final merged array should look like this
MergedArray
[
{
objectId = 4274;
name = orange;
price = 106;
status = not_approved;
},
{
objectId = 9035;
name = apple;
price = 56;
status = approved;
},
{
objectId = 7336;
name = banana;
price = 48;
status = approved;
},
{
objectId = 5503;
name = apple;
price = 56;
status = approved;
}
.
.
.
.
]
Only way to this I know is to iterate through both arrays and merge. Is there any better way? Any help will be greatly appreciated.
EDIT:
Following dasblinkenlights suggestion, I did it following way
-(NSMutableArray*)mergeTwoArray:(NSArray*)array1 :(NSArray*)array2
{
//array1 will overwrite on array2
NSSet* parentSet = [NSSet setWithArray:array2];
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
for (NSDictionary *item in parentSet)
[dict setObject: item forKey: [item objectForKey:#"objectId"]];
NSLog(#"initial dictionary is %#",dict);
for (NSDictionary *item in array1)
[dict setObject: item forKey: [item objectForKey:#"objectId"]];
NSLog(#"final dictionary is %# with all values %#", dict,[dict allValues]);
return [NSMutableArray arrayWithArray:[dict allValues]];
}

Since your objectId value can be used as a unique key, you could potentially create an NSMutableDictionary on the side, populate it with NSDictionary objects from the first array using objectId value as the key, go through the second array, do the overwrites, and finally harvest the values of the resultant NSMutableDictionary into your final output.
Note that this approach might be helpful only if your arrays are relatively long (1000+ items). If you deal with 10..100 items, I wouldn't bother, and code two nested loops as you suggested.

I would recommend iterating through both arrays and merging, but first sort them. Once sorted, you can merge two arrays in O(N) time. For most purposes, this is about as fast as you can get, and it requires very little code.
If they are large enough that the sort is a bottleneck, you could get fancy using an NSSet: put the (elements of the) over-riding array in the set first, and then add the elements of the original array. But you would have to implement an isEqual method for your elements. In this case, it would mean that your elements would no longer be NSDictionary, but rather a class that inherits from NSDictionary but implements the isEqual method to compare the object ID fields.
Because NSSet gives amortized constant time access, this would be faster, if the arrays are large, since there is no sort phase.

Related

I don't understand how to use the JSON data in Xcode

I have successfully downloaded and parsed (I think) the JSON data
NSURL *quoteURL = [NSURL URLWithString:#"http://www.qwoatzz.com"];
NSData *jsonData = [NSData dataWithContentsOfURL:quoteURL];
NSError *error = nil;
NSDictionary *dataDictionary = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
However, I'm not too sure how to actually use the JSON data. If the JSON file has an array with two keys, how do I get a value from one of these keys at a specific index (the first one for example) and use that to change the text of a label (I know how to do that, it's just the JSON part I am stuck on)?
2014-10-20 19:46:10.616 Qwoatz-2[3147:454481] dataDictionary : {
count = 10;
"count_total" = 1871;
pages = 188;
posts = (
{
author = "Jason Seifer";
date = "2014-10-20 13:54:11";
id = 24317;
thumbnail = "http://blog.teamtreehouse.com/wp-content/uploads/2014/10/1634685862_92b26b9167_o-150x150.jpg";
title = "What Employers Are Looking For in a Junior Rails Developer";
url = "http://blog.teamtreehouse.com/employers-looking-junior-rails-developer";
},
{
author = "Zac Gordon";
date = "2014-10-16 09:27:38";
id = 24296;
thumbnail = "http://blog.teamtreehouse.com/wp-content/uploads/2014/10/brochure-site-150x150.png";
title = "When is WordPress.com the Right Solution?";
url = "http://blog.teamtreehouse.com/wordpress-com-right-solution-website";
},
{
author = "Gill Carson";
date = "2014-10-15 12:52:43";
id = 24287;
thumbnail = "http://blog.teamtreehouse.com/wp-content/uploads/2014/10/Tahoe_team-Photo-150x150.jpg";
title = "We Are Family – The Whole Team!";
url = "http://blog.teamtreehouse.com/family";
},
{
author = "Jason Seifer";
date = "2014-10-14 15:26:11";
id = 24292;
thumbnail = "http://blog.teamtreehouse.com/wp-content/uploads/2014/10/Chartist-Simple-responsive-charts-2014-10-14-15-24-43-150x150.jpg";
title = "Responsive Charts";
url = "http://blog.teamtreehouse.com/responsive-charts";
},
{
author = "Guil Hernandez";
date = "2014-10-13 09:28:05";
id = 24228;
thumbnail = "http://blog.teamtreehouse.com/wp-content/uploads/2014/09/blend-mode-mult-150x150.jpg";
title = "Cutting-Edge CSS Features You Can Use Today";
url = "http://blog.teamtreehouse.com/cutting-edge-css-features-can-use-today";
},
{
author = "Faye Bridge";
date = "2014-10-10 09:00:45";
id = 24230;
thumbnail = "http://blog.teamtreehouse.com/wp-content/uploads/2014/09/Nick-Bryan-150x150.jpg";
title = "After Just 6 Months Learning Nick is a full-time Web Developer";
url = "http://blog.teamtreehouse.com/6-months-nick-now-full-time-web-developer-major-computing-firm";
},
{
author = "Pasan Premaratne";
date = "2014-10-09 13:59:23";
id = 24250;
thumbnail = "http://blog.teamtreehouse.com/wp-content/uploads/2014/10/Screen-Shot-2014-10-06-at-5.57.16-PM-150x150.png";
title = "Making a Network Request in Swift";
url = "http://blog.teamtreehouse.com/making-network-request-swift";
},
{
author = "Zac Gordon";
date = "2014-10-09 09:21:29";
id = 24278;
thumbnail = "http://blog.teamtreehouse.com/wp-content/uploads/2014/10/wordpress_themes-150x150.jpg";
title = "New Course: WordPress Theme Development";
url = "http://blog.teamtreehouse.com/new-course-wordpress-theme-development";
},
{
author = "Dave McFarland";
date = "2014-10-08 13:47:55";
id = 24255;
thumbnail = "http://blog.teamtreehouse.com/wp-content/uploads/2014/10/Screen-Shot-2014-10-06-at-1.02.40-PM-150x150.png";
title = "How to Install Node.js and NPM on a Mac";
url = "http://blog.teamtreehouse.com/install-node-js-npm-mac";
},
{
author = "Jason Seifer";
date = "2014-10-07 16:15:00";
id = 24273;
thumbnail = "http://blog.teamtreehouse.com/wp-content/uploads/2014/10/html5-device-mockups-150x150.jpg";
title = "Device Mockups";
url = "http://blog.teamtreehouse.com/device-mockups";
}
);
status = ok;
}
An example JSON file that was parsed.
Loop through the dictionary:
for(NSString *key in dataDictionary) {
id myObject = [dataDictionary objectForKey:key];
//do something with myObject
}
An NSDictionary is not an ordered collection, so there is no guarantee that looping through a dictionary as shown above will always loop through the keys in the same order. Apple doesn't provide an ordered dictionary with Cocoa/Cocoa Touch, and generally it is a bad idea to subclass NSDictionary or NSMutableDictionary as they are part of a class cluster.
Looking at the text from your example, posts is actually an array full of dictionaries. Assuming all the keys in your example are constant across the JSON files that you will be fetching, you could retrieve it using
NSArray *posts = [dataDictionary arrayForKey:#"posts"];
This array already appears to be ordered by date. You could then get the title for each post
for(int i = 0; i < [posts count]; i++) {
NSString *title = [((NSDictionary *)(posts[i])) objectForKey:#"title"];
//do something with title
}
1) Do you know the data is an array?
the JSON file has an array with two keys,...
value from one of these keys at a specific index...
This is somewhat of a mixed metaphor for me. When I have a JSON Array or NSArray, I tend to only think of indices (since that how arrays are ordered), and when I have JSON Objects or NSDictionaries, I tend to think of keys.
So, does the return value look like this:
[ "cat", 1, "a" ]
or does the data look like this:
{
"cat": {
"count": 1,
"tag": "a"
}
}
The first example is an Array with 3 elements; the second is an Object with 1 member that itself has 2 members.
2) If the data is correctly parsed as either an NSArray, or NSDictionary ...
Then you simply need to extract the data you want, with the accessors available on either container.
E.g.
NSArray *a = ...
[a firstObject];
[a objectAtIndex:0]; // same as above
NSDictionary *d = ...
d[#"memberName"];
[d objectForKey:#"memberName"]; // same as above
You'll want to actually save that data, or pass it to be processed, instead of just invoking the accessor.
UPDATE: based on the example data updated in the question.
One method is that you could extract the data both a bit manually, and iteratively.
NSDictionary *dataDictionary = ...
NSInteger count = [[dataDictionary objectForKey:#"count"] integerValue];
NSInteger countTotal = [[dataDictionary objectForKey:#"count_total"] integerValue];
NSInteger pagesCount = [[dataDictionary objectForKey:#"pages"] integerValue];
NSString *status = [dataDictionary objectForKey:#"status"];
NSArray *posts = [dataDictionary objectForKey:#"posts"];
for (NSDictionary *post in posts) {
for (NSString *key in post) {
NSLog(#"%#: %#", key, post[key]);
}
}
When you log dataDictionary, unquoted elements that are clearly strings are strings, elements in quotes are strings, integers and other numbers are likely usable numbers, but they may be strings (depends on return format), the date will be a string (and you can use NSDate and NSDateFormatter to pretty print it), status is just a string; for posts, the '(' and ')' wrap an array, and '{','}' wrap dictionaries.
UPDATE 2:
If you really want to do advanced searching, you can use NSPredicate to filter NSDictionary's or NSArray's. For example, something like the following would work:
#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
#autoreleasepool {
NSDictionary *data = #{
#"stuff": #1,
#"posts": #[
#{ #"id": #1, #"title": #"one" },
#{ #"id": #2, #"title": #"two" },
#{ #"id": #3, #"title": #"three" },
#{ #"id": #4, #"title": #"four" },
#{ #"id": #5, #"title": #"five" },
]
};
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.id == %#", #4];
NSString *title = [[data[#"posts"] filteredArrayUsingPredicate:predicate] firstObject];
NSLog(#"title: %#", title);
}
}
prints
title: {
id = 4;
title = four;
}
You can use JSONModel Framework
JSONModel is a data model framework for iOS and OSX. It's written in Objective-C and helps you in several different ways.
You can read more about its key features below:
Rapidly write model code
Validation of the model's input
Atomic data
Type casting between JSON and Obj-C
Built-in data transformers
Custom data transformers
Model cascading
Convert back & forth from/to JSON
Persist model state in memory or file
Create models straight from the Internet
Automatic model compare methods
Note Please make sure your properties name match with key name in JSON
JSONModel Framework
GitHub Link

How to remove elements of NSDictionary

I have NSArray of NSDictionaries I need to extract 2 values or remove the values I don't need from the dictionary in the example below I need to remove id and NumberValue. any of you knows how can I do that?
Array: (
{
customerUS= {
DisplayName = "level";
InternalName = "Number 2";
NumberValue = 1;
id = xwrf
},
customerCAN= {
DisplayName = "PurchaseAmount";
InternalName = "Number 1";
NumberValue = 3500;
id = adf;
};
}
)
I'll really appreciate your help.
First thing, You can not remove/insert/update value in (immutable) NSDictionary/NSArray you need to convert NSDictionary/NSArray to (mutable) NSMutableDictionary/NSMutableArray.
such like
NSArray *myArr = ....;
NSMutableArray *newMutableArr = [myArr mutableCopy];
Then you can change in newMutableArr.
Such like
for(int i = 0 ; i < newMutableArr.count ; i ++)
{
[[newMutableArr objectAtIndex:i] removeObjectForKey:#"id"];
[[newMutableArr objectAtIndex:i] removeObjectForKey:#"NumberValue"];
}
EDITED:
Without Use of for loop and removeObjectForKey, if you have array of dictionary and both are mutable then you can also delete a key and its object from all elements of the array like this:
[newMutableArr makeObjectsPerformSelector:#selector(removeObjectForKey:) withObject:#"id"];
[newMutableArr makeObjectsPerformSelector:#selector(removeObjectForKey:) withObject:#"NumberValue"];
I would advice you to read Apple documents.
For modifying any Collection object after it is created, you need the mutable version.
For NSDictionary we have NSMutableDictionary. Read here.
We have a method for removing objects:
- (void)removeObjectForKey:(id)aKey
There are other methods as well. You can easily refer them in the above mentioned documentation.
Find out removeObjectForKey for deleting record from NSMutabledictionary.
removeObjectForKey pass the key value whatever you have like
all this are your key
DisplayName,
InternalName,
NumberValue,
id
do like this
removeObjectForKey:#"id";
First of all you have to convert the array to mutable array and then you can remove the key-value pairs from dictionary.
NSMutableArray *mutableArray = [yourArray mutableCopy];for(int i=0;i<mutableArray.count;i++){ NSMutableDictionary *outerDictionary = [mutableArray objectAtIndex:i]; for(NSString *key in outerDictionary.allKeys){ NSMutableDictionary *innerDictionary = [outerDictionary objectForKey:key]; [innerDictionary removeObjectForKey:#"id"]; [innerDictionary removeObjectForKey:#"NumberValue"]; }
}

What is the best way to build a one-to-many relationship?

I have an array of Videos objects with, among other things, the properties id and tags.
I want to build a dictionary whose key is a tag and whose value is an array of id's.
For example, some Video objects might look like this:
Video{ id:1, tags:[funny,political,humor] }
Video{ id:2, tags:[political,america] }
I want the result dictionary to look like this:
VideosWithTags["funny":[1]; "political":[1,2]; "humor":[1]; "america":[2]]
Is there a standard algorithm to accomplish this?
Currently I'm doing something like this:
for (NSDictionary *video in videos)
{
NSNumber *videoId = [video objectForKey:#"id"];
NSArray *tags = [video objectForKey:#"tags"];
for (NSString *tag in tags)
{
NSMutableArray *videoIdsForTag = nil;
if ([videosAndTags objectForKey:tag] != nil) //same tag with videoIds already exists
{
videoIdsForTag = [videosAndTags objectForKey:tag];
[videoIdsForTag addObject:videoId];
//add the updated array to the tag key
[videosAndTags setValue:videoIdsForTag forKey:tag];
}
else //tag doesn't exist yet, create it and add the videoId to a new array
{
NSMutableArray *videoIds = [NSMutableArray array];
[videoIds addObject:videoId];
//add the new array to the tag key
[videosAndTags setObject:videoIds forKey:tag];
}
}
}
You can make this look a little cleaner by using the new literal syntax.
I think you could benefit by making your if branches do less work. e.g. You would be better of trying to retrieve the videoIds array then if it doesn't exist - create it and add it to the videosAndTags object and then the code after this point can be consistent with no duplication of logic
for (NSDictionary *video in videos) {
NSNumber *videoId = video[#"id"];
NSArray *tags = video[#"tags"];
for (NSString *tag in tags) {
NSMutableArray *videoIds = videosAndTags[tag];
if (!videoIds) {
videoIds = [NSMutableArray array];
videosAndTags[tag] = videoIds;
}
// This is the only line where I manipulate the array
[videoIds addObject:videoId];
}
}
NSArray* videos =
#[#{ #"id" : #1, #"tags" : #[ #"funny", #"political", #"humor" ] },
#{ #"id" : #2, #"tags" : #[ #"political", #"america" ] } ];
NSMutableDictionary* videosAndTags = [NSMutableDictionary new];
// find distinct union of tags
NSArray* tags = [videos valueForKeyPath: #"#distinctUnionOfArrays.tags"];
// for each unique tag
for( NSString* tag in tags )
{
// filter array so we only have ones that have the right tag
NSPredicate* p = [NSPredicate predicateWithFormat: #"tags contains %#", tag];
videosAndTags[ tag ] = [[videos filteredArrayUsingPredicate: p] valueForKeyPath: #"id"];
}
Here is another approach using NSPredicate and valueForKeyPath.
I don't used them often, but sometimes they can prove to be useful.
(I think they call this the Functional Programming style of things, but I am not so sure)
NSPredicate reference
Key Value Coding

Sort an NSDictionary into an NSMutableArray but keep value and key?

I'm a little confused: trying to take a list of player names and scores that I have in an NSDictionary, and sort them into score order (highest score first). I know I can't sort a Dictionary so I need to move the data into an array first, but in doing so, won't I have to lose one half of each dictionary key/value pair?
For example, let's say I have the following pairs:
Bill / 10000
John / 7500
Stan / 7500
Mark / 5000
If I go and take the scores out and sort them, then retrieve the keys later, won't John and Stan get mixed up since they had identical scores? Might it call one of them twice, etc?
I realise I can't sort the dictionary, but is there a smarter way to do this?
What you could do is to get a sorted array of your players based on their score, and then create a dictionary for each player and append them in another array. Something like this perhaps (I'm using the new literals syntax, use the old one if appropriate):
NSDictionary *scores = #{
#"Stan" : #7500,
#"Mark" : #5000,
#"John" : #7500,
#"Bill" : #10000
};
NSArray *sp = [scores keysSortedByValueUsingComparator:^(id obj1, id obj2){
return [obj2 compare:obj1];
}];
NSMutableArray *players = [NSMutableArray arrayWithCapacity:0];
for (NSString *p in sp) [players addObject:#{ #"name":p, #"score":scores[p] }];
Now your players array is:
(
{
name = Bill;
score = 10000;
},
{
name = John;
score = 7500;
},
{
name = Stan;
score = 7500;
},
{
name = Mark;
score = 5000;
}
)
PS. Is not a good idea to keep a dictionary where the keys are player names, consider that you got 2 different John players... what would happen then? Also a better solution imo would be to create a Player class and keep their data (score, name etc) as properties.
You'll have to create two arrays, one for the score and another for the player. The key point being that the player array is in the same order as the (sorted) score array. The implementation below assumes you keep the score using an NSNumber object, and is not particularly efficient as it sorts the values twice:
(untested)
NSDictionary *dict = ...; // key=player (NSString), value=score (NSNumber).
NSArray *scores = [[dict allValues] sortedArrayUsingComparator:^(id obj1, id obj2) {
return [(NSNumber *)obj2 compare:(NSNumber *)obj1];
}];
NSArray *players = [dict keysSortedByValueUsingComparator:^(id obj1, id obj2) {
return [(NSNumber *)obj2 compare:(NSNumber *)obj1];
}];

Grabbing specific data from an NSDictionary

all info is an __NSCFDictionary and I'm trying to get access just to the "ratings" key (the 3rd key). Any idea how I would go about grabbing the data from that field?
thanks for any help
my NSLog output from NSLog(#"allinfo description %#", allInfo.description); is the following
allinfo description __NSCFDictionary
2012-03-14 11:50:28.056 Project[1173:11603] key = results -> obj = (
{
geometry = {
location = {
lat = "37.787565";
lng = "-122.409593";
};
};
icon = "http://maps.gstatic.com/mapfiles/place_api/icons/bar-71.png";
id = 6ea9093f35b4b0cf5cfa21b7dd40cb28d5aa5c5c;
name = "Ruby Skye";
rating = "3.4";
reference = "CmRfAAAApDkEMtz2k6RQTmdeamVqY3KrXhpyzLoHc7EUDMCCUi3qD6PsYQE8qO2QjiUohpJIFtub_XUcYqPOt_LSUbILHjMzeHiacXDCSSKhybRB4VTy7-Oi72nibqwI3NmxnoxNEhB1I7mUheViH2V2ESkkmHbwGhS1G-LDz0PfJggIt8sZ2YO_BOILkQ";
types = (
"night_club",
bar,
establishment
);
vicinity = "420 Mason Street, San Francisco";
},
{
geometry = {
location = {
lat = "37.790237";
lng = "-122.404676";
};
};
icon = "http://maps.gstatic.com/mapfiles/place_api/icons/bar-71.png";
id = bf9fc5861d0ab56a337ded8d8c4b49e62709e3e5;
name = "The Irish Bank";
rating = "3.7";
reference = "CnRlAAAAs0ggA4T1ri7H68CE7N1IWhfmY6EcED9pMNhvKhrFRUBFJLw73URrqoBpyGB585u7xDwtbmBV53eBj3rXpMlFI52OKz2Uv5-mIKT5pjFXCZJoTiux_SPoWQWFPCYoePyxhl6NFvmLWr-K-Jr7UnapWRIQNSuYGOFKgYg3yHTtoGq1KxoUqT4O-yIncvKbDJz8IygsdL7P2Go";
types = (
bar,
restaurant,
food,
establishment
);
vicinity = "10 Mark Lane, San Francisco";
},
You have a partial console snippet that looks like an NSArray; but it appears to be an NSDictionary when you log [allInfo class].
Can you try this:
[allInfo enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
NSLog(#"key = %# -> obj = %#",key,obj);
}];
This should log the keys and objects and help elucidate the structure of the NSDictionary you are working with.
Update after OP added log output:
// This should log all of the ratings
NSArray *results = [allInfo objectForKey:#"results"];
[results enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSString *ratingStr = [obj objectForKey:#"rating"];
NSLog(#"rating = %#",ratingStr);
}];
The output (allinfo.description) looks like an NSArray with NSDictionaries inside of it but, [allinfo class] says it's an NSDictionary however there doesn't appear to be keys for each element. Apple's documentation specifies this about the keys when calling -description:
If each key in the dictionary is an NSString object, the entries are listed in ascending order by key, otherwise the order in which the entries are listed is undefined.
I'd try using the -allKeys to get an NSArray of all the keys. If they aren't NSStrings then you'll need to figure out what it is in order to access the elements in this dictionary.
The log outPut you posted is an Array.You can access the "rating" by
NSString *ratingValue = [[allInfo.description objectAtIndex:0]objectForKey:#"rating"];