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

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

Related

Get object by key with certain value from NSDictionary

I have NSDictionary with objects. The NSDictionary is consist of json(You can see it below). I need to populate my table view with the name by id. And the id can repeat. It means I can have several "name" with id which is equal 0. I should get name by key with certain value from the dictionary. Here is my NSDictionary:
{
name = "smth1";
id = 0;
},
{
name = "smth2";
id = 1;
},
{
name = "smth3";
id = 2;
},
{
name = "smth4";
id = 2;
},
...
For example, I want to get value of key "name" where id is 2. Then I will get name = "smth3" and name = "smth4". Generally, I am trying to populate my table view component with the nested data. How can I do this? Any tips, ideas. Thank you.
Actually you show an array of dictionaries, where each dictionary has 2 keys.
You could use the NSArray function indexOfObjectPassingTest. That code might look like this (starting from an NSArray) :
int idToFind = 2;
NSArray *dictArray = #[#{#"name": #"smth1",
#"id": #(0)},
#{#"name": #"smth2",
#"id": #(1)},
#{#"name": #"smth3",
#"id": #(2)}
];
NSUInteger dictIndex =
[dictArray indexOfObjectPassingTest: ^BOOL(
NSDictionary *dict,
NSUInteger idx,
BOOL *stop) {
return [dict[#"id"] intValue] == idToFind;
}];
if (dictIndex != NSNotFound) {
NSLog(#"Found id %d with name %#", idToFind, dictArray[dictIndex][#"name"]);
}
}
EDIT:
If you need to match all of the items then you need to use the NSArray method indexesOfObjectsPassingTest. That finds all the items in an array that match.
That code would look like this:
int idToFind = 2;
NSArray *dictArray = #[#{#"name": #"smth1",
#"id": #(0)},
#{#"name": #"Fred",
#"id": #(2)},
#{#"name": #"smth2",
#"id": #(1)},
#{#"name": #"smth3",
#"id": #(2)},
];
NSIndexSet *dictIndexes;
dictIndexes =
[dictArray indexesOfObjectsPassingTest: ^BOOL(NSDictionary *dict,
NSUInteger idx,
BOOL *stop)
{
return [dict[#"id"] intValue] == idToFind;
}];
if (dictIndexes.count == 0) {
NSLog(#"No matches found");
}
[dictIndexes enumerateIndexesUsingBlock:^(NSUInteger index,
BOOL * _Nonnull stop)
{
NSLog(#"Found id=%# with name \"%#\" at index %lu",
dictArray[index][#"id"],
dictArray[index][#"name"],
index);
}];
Both approaches above use methods that take a block. The block contains code that you write that returns a BOOL for the item(s) that match your desired search criteria.
Search/sort methods that take blocks are very flexible because you can provide any code you want to do the matching/comparison.
The indexOfObjectPassingTest method searches for a single object in your array and stops when it finds the first match.
In contrast, the indexesOfObjectsPassingTest function will match multiple items. It returns an NSIndexSet, a special class that's used to index into NSArrays.
There is a function enumerateIndexesUsingBlock that invokes a block of code for each index specified in the array.
We could also have used the method objectsAtIndexes to extract only the elements in the array that are listed in the resulting index set, and then used for...in to loop through the items. That code would look like this:
NSArray *filteredArray = [dictArray objectsAtIndexes: dictIndexes];
for (NSDictionary *aDict in filteredArray) {
NSLog(#"Found id=%# with name \"%#\"", aDict[#"id"], aDict[#"name"]);
}
Note that this sort of thing is simpler and cleaner in Swift. We could use a filter statement on the array and provide a closure that selects items that match our search criteria

How can I create an NSMutableDictionary from an NSMutableArray?

I'm trying to figure out the best way to sort an NSMutableDictionary. I have a dictionary of Card keys (i.e. aceSpades) that store Card values (i.e. 14). I have then been using an NSMutableArray to shuffle the 52 Card keys into an array called shuffledCards. Finally I make another array from shuffledCards thats takes a portion (15) of shuffledCards and puts them into an array called computerHand.
The new array computerHand is not good enough because I need to be able to connect the Card values with the Card keys. What I really need to do is create a new NSMutableDictionary for computerHand from the array shuffledCards so that I can sort it by Card values and still be able to retrieve the Card keys.
I'm thinking I need something like this, where currentCard is the first card of the shuffedCards array:
if (currentCard == 1) {
[compHandDictionary setObject:[[highCardDictionary
valueForKey:[shuffledCards objectAtIndex:currentCard]] intValue]
forKey:[cardsShuffled objectAtIndex:currentCard]];
}
However this is not allowed because "int" to "id" is not allowed.
There might be a better way but I have not been able to find anything. Any help would be appreciated.
...
I got this to work by modifying jstevenco's answer. I created two arrays and formed a new dictionary for the computer hand of just the 15 cards. Then to sort I used:
NSArray* sortedKeys = [newDict keysSortedByValueUsingComparator: ^(id obj1, id obj2) {
if ([obj1 integerValue] > [obj2 integerValue]) {
return (NSComparisonResult)NSOrderedAscending;
}
if ([obj1 integerValue] < [obj2 integerValue]) {
return (NSComparisonResult)NSOrderedDescending;
}
return (NSComparisonResult)NSOrderedSame;
}];
Thanks all!
You can sort a dictionary's keys using:
NSArray* sortedKeys = [[dict allKeys] sortedArrayUsingSelector:#selector(caseInsensitiveCompare:)];
If you then want to create a sorted array from this you can use:
NSArray* objects = [dict objectsForKeys:sortedKeys notFoundMarker:[NSNull null]];
Consider using an array of dictionaries, where each dictionary contains the name and value for a given card, for example like so:
NSArray *cards = #[#{#"name" : #"Queen", #"value" : #12},
#{#"name" : #"Jack", #"value" : #11},
#{#"name" : #"Ace", #"value" : #14},
#{#"name" : #"King", #"value" : #13}];
You could then easily sort the cards array as shown below:
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"value" ascending:YES];
NSArray *sortedCards = [cards sortedArrayUsingDescriptors:#[sortDescriptor]];
NSLog(#"\n\n%#\n\n", sortedCards);
Output of the NSLog statement above would be as follows:
(
{
name = Jack;
value = 11;
},
{
name = Queen;
value = 12;
},
{
name = King;
value = 13;
},
{
name = Ace;
value = 14;
}
)
To obtain an array of card names, you could then simply send a valueForKey: message to the sorted array of card dictionaries:
NSArray *sortedNames = [sortedCards valueForKey:#"name"];
NSLog(#"\n\n%#\n\n", sortedNames);
The output of the preceding NSLog statement would be:
(
Jack,
Queen,
King,
Ace
)

Parsing a JSON of unknown Structure Objective-C

I am trying to parse this json and am not sure of parsing this because the keys like "vis-progress-control" might change and I am trying to build a general code which parses this type of a json. I am sure that "type" key will be present in the json structure.
NSDictionary *dict = [sData JSONValue];
NSArray *items = [dict valueForKeyPath:#"assets"];
NSLog(#"%#", items);
for (NSString *key in [[dict objectForKey:#"assets"]allKeys]) {
for (NSString *subKey in [[[dict objectForKey:#"assets"] objectForKey:key]allKeys]) {
NSLog(#"Value at subkey:%# is %#\n",subKey,[[[dict objectForKey:#"assets"]objectForKey:key]objectForKey:subKey]);
}
}
I am using the SBJson Library on Github. My actual issue is How do I access "direction", "degrees" etc when I do not know the "vjs-progress-holder" key?
I have also a widgets array nested within a widgets array. How do I get these values as well?
Read about tree traversal. Sounds like you want to traverse the "tree" of nodes and when you find a particular leaf value you will then traverse back up one level and know the parent is the container you want.
So the idea is that once it's parsed from the JSON, forget it's JSON because now it's just a tree in arrays and dictionaries. Traverse it by getting all the keys in the dictionary via allKeys (returns an array of keys) and then iterate through them getting the associated values (using something like (psuedo code):
for ( NSString * key in allkeysarray) {
NSString * val = [dict objectForKey: key];
if ( [val isEqualToString: #"gradient"] )
{
// now you know that this dictionary is the one you're looking for so you can maybe break out of this loop and just use the keys you know reference these values.
break;
}
}
hopefully that's enough to get you going.
Assuming I'm understanding your objective here, it seems like you want to do something like this?
NSDictionary *outerDict = [sData JSONValue];
NSDictionary *assets = outerDict[#"assets"];
for (NSDictionary *asset in [assets allValues]) {
NSString *type = asset[#"type"];
if ([type isEqualToString:#"gradient"]) {
float degrees = [asset[#"degrees"] floatValue];
// and read whatever other values you need for gradients
}
if ([type isEqualToString:#"image"]) {
// and read the appropriate values for images here...
}
}
So I'll make a different assumption here, which is that you want an array of gradients. So then that looks basically like this:
NSDictionary *outerDict = [sData JSONValue];
NSDictionary *assets = outerDict[#"assets"];
NSMutableArray *gradients = [NSMutableArray array];
for (NSDictionary *asset in [assets allValues]) {
NSString *type = asset[#"type"];
if ([type isEqualToString:#"gradient"]) {
// Add this asset to the list of gradients:
[gradients addObject:asset];
}
if ([type isEqualToString:#"image"]) {
// do something similar for images
}
}
Then, after having done that, if you would like to read the "degrees" field for the 4th gradient, for example, you will find it at gradients[3][#"degrees"]

How can I speed up my array search with an NSPredicate?

I have an array of categories, and another array of category IDs. I want to pull out the categories with matching IDs. At the moment, my code looks a bit like this:
- (NSArray *)categoriesFromArray:(NSArray *)categories withIDs:(NSArray *)categoryIDs {
NSMutableArray *categoriesWithIDs = [NSMutableArray array];
for (SGBCategory *category in categories) {
for (NSNumber *categoryID in categoryIDs) {
if ([category.categoryID isEqual:categoryID]) {
[categoriesWithIDs addObject:category];
break;
}
}
}
return categoriesWithIDs;
}
Ewww, I know. So what I'd like to do is something like SELECT * FROM categories WHERE categories.categoryID in (categoryIDs) does in SQL. I think NSPredicate is the objective-c way of expressing that sort of thing, but I don't know how to get it to do what I want. How can I speed up my array search with an NSPredicate?
return [categories filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"categoryID IN %#", categoryIDs]];
I don't know that it will be any faster, though. It basically has to do the same sort of thing as your code, plus build a predicate.
You can improve your code by making an NSSet from the categoryIDs and using -containsObject: instead of looping over the categoryIDs and calling -isEqual: manually.
I'm not sure about the predicate thing but an immediate optimisation is to put your category ids in a set
- (NSArray *)categoriesFromArray:(NSArray *)categories withIDsFromSet:(NSSet *)categoryIDs {
NSMutableArray *categoriesWithIDs = [NSMutableArray array];
for (SGBCategory *category in categories) {
if ([categoryIDS containsObject: [category categoryID]])
{
[categoriesWithIDs addObject:category];
}
}
return categoriesWithIDs;
}
Edit
If you want to continue using your old method, modify it thusly:
- (NSArray *)categoriesFromArray:(NSArray *)categories withIDs:(NSArray *)categoryIDs {
return [self categoriesFromArray: categories withIDsFromSet: [NSSet setWithArray: categoryIDs]];
}
This will still be vastly more efficient than your original method provided either categories or categoryIDs is largish.
NSPredicate* searchPredicate = [NSPredicate predicateWithFormat:#"categoryID == %f", categoryID];
NSArray *categoriesWithIDs =[categories filteredArrayUsingPredicate:searchPredicate];
return categoriesWithIDs;
This is how you would do what you want i think. Assuming that the category id is a float. change the %f as needed, just as you would with NSLog.

How do I traverse a multi dimensional NSArray?

I have array made from JSON response.
NSLog(#"%#", arrayFromString) gives the following:
{
meta = {
code = 200;
};
response = {
groups = (
{
items = (
{
categories = (
{
icon =
"http://foursquare.com/img/categories/parks_outdoors/default.png";
id = 4bf58dd8d48988d163941735;
and so on...
This code
NSArray *arr = [NSArray arrayWithObject:[arrayFromString valueForKeyPath:#"response.groups.items"]];
gives array with just one element that I cannot iterate through. But if I write it out using NSLog I can see all elements of it.
At the end I would like to have an array of items that I can iterate through to build a datasource for table view for my iPhone app.
How would I accomplish this?
EDIT:
I have resolved my issue by getting values from the nested array (objectAtIndex:0):
for(NSDictionary *ar in [[arrayFromString valueForKeyPath:#"response.groups.items"] objectAtIndex:0]) {
NSLog(#"Array: %#", [ar objectForKey:#"name"]);
}
First, the data structure you get back from the JSON parser is not an array but a dictionary: { key = value; ... } (curly braces).
Second, if you want to access a nested structure like the items, you need to use NSObject's valueForKeyPath: method. This will return an array of all items in your data structure:
NSLog(#"items: %#", [arrayFromString valueForKeyPath:#"response.groups.items"]);
Note that you will loose the notion of groups when retrieving the item objects like this.
Looking at the JSON string you posted, response.groups.items looks to be an array containing one item, a map/dictionary containing one key, "categories." Logging it out to a string is going to traverse the whole tree, but to access it programmatically, you have to walk the tree yourself. Without seeing a more complete example of the JSON, it's hard to say exactly what the right thing to do is here.
EDIT:
Traversing an object graph like this is not that simple; there are multiple different approaches (depth-first, breadth-first, etc,) so it's not necessarily something for which there's going to be a simple API for you to use. I'm not sure if this is the same JSON library that you're using, but, for instance, this is the code from a JSON library that does the work of generating the string that you're seeing. As you can see, it's a bit involved -- certainly not a one-liner or anything.
You could try this, which I present without testing or warranty:
void __Traverse(id object, NSUInteger depth)
{
NSMutableString* indent = [NSMutableString string];
for (NSUInteger i = 0; i < depth; i++) [indent appendString: #"\t"];
id nextObject = nil;
if ([object isKindOfClass: [NSDictionary class]])
{
NSLog(#"%#Dictionary {", indent);
NSEnumerator* keys = [(NSDictionary*)object keyEnumerator];
while (nextObject = [keys nextObject])
{
NSLog(#"%#\tKey: %# Value: ", indent, nextObject);
__Traverse([(NSDictionary*)object objectForKey: nextObject], depth+1);
}
NSLog(#"%#}", indent);
}
else if ([object isKindOfClass: [NSArray class]])
{
NSEnumerator* objects = [(NSArray*)object objectEnumerator];
NSLog(#"%#Array (", indent);
while (nextObject = [objects nextObject])
{
__Traverse(nextObject, depth+1);
}
NSLog(#"%#)", indent);
}
else
{
NSLog(#"%#%#",indent, object);
}
}
void Traverse(id object)
{
__Traverse(object, 0);
}