Match multiple strings in multiple NSArray - iphone

I need to select stories from a NSArray of XML by matching a string from an XML element against a list of strings in another NSArray
The XML contains stories, each story has three criteria, say 'Fruit', 'Veg', 'Spice', each containing a single phrase. A sample story might look like:
<story>
<title>I love cooking</title>
<fruit>Oranges</fruit>
<veg>Cauliflower</veg>
<spice>Mixed spice</spice>
<blurb>losts of text and stuff....</blurb>
</story>
I have three dictionaries of key/value pairs in a NSMutableDictionary generated from a pList
<Fruit:dictionary>
'Ripe bananas' : 1
'Green bananas' : 0
<Veg:dictionary>
'Green beans' : 1
'Cauliflower' : 0
<Spice:dictionary>
'Nutmeg' : 1
'Mixed spice' : 0
I don't know what the keys will be, and I need to match the tag in the story against the keys.
i.e. story fruit tag:'Ripe bananas' MATCHES 'Ripe bananas' in list of fruit keys
I can build three arrays of the keys using
NSMutableDictionary *fruitTagsDict = [prefsDictionary objectForKey:#"Fruits"];
NSArray *fruitTags = [fruitTagsDict allKeys];
I loop through the story XML extracting a tag
for (id myArrayElement in storyArray) {
NSString *fruitString = [NSString stringWithString:[myArrayElement fruit]];
//BOOL isTheObjectThere = [issueTags containsObject:fruitString];
NSString *vegString = [NSString stringWithString:[myArrayElement veg]];
NSString *spiceString = [NSString stringWithString:[myArrayElement spice]];
//if ([[fruitTags objectAtIndex:row] isEqualToString:fruitString]) {
//NSLog(#"Yo %#", fruitString);
// ADD TO A NEW ARRAY OF MATCHING STORIES
//}
// Fails because row is undeclared
}
Then I start to glaze out.
The isTheObjectThere line produces nil then crashes at end of loop
I've looked at:
Filter entire NSDictionaries out of NSArray based on multiple keys
Making the Code check to see if the Text in a Text box matches any of the Strings in an NSArray
It seems predicate is the answer but frankly I getting confused.
What I need to do in metacode
repeat with stories
if storyFruitTag is in fruitTagArray
OR storyVegTag is in vegTagArray
OR storySpiceTag is in spiceTagArray
Add to new array of matching stories
Hopefully I've explained enough to get some pointers, I looked into NSMutableSet and Intersect (Xcode: Compare two NSMutableArrays) but the power of too much information got to me

Here's a simple way to determine whether there are matches using key paths:
if ([prefsDict valueForKeyPath:[NSString stringWithFormat:#"Fruit.%#", storyFruitTag]] ||
[prefsDict valueForKeyPath:[NSString stringWithFormat:#"Veg.%#", storyVegTag]] ||
[prefsDict valueForKeyPath:[NSString stringWithFormat:#"Spice.%#", storySpiceTag]]) {
// one of the story's tags matches a key in one of the corresponding dictionaries
}

Related

Set up a NSMutableArray of objects to match a string value

I'm using the value of two sliders as the first search criteria.
Now I want a second criteria. I have a
NSMutableArray *suitsArray;
which is containing up to 10 words, or it could be empty (0 words). Example of array:
2012-08-12 03:13:14.825 App[4595:f803] The content of array is(
mushroom,
grill,
pizza
)
What I want is this:
If suitsArray is like above, containing words mushroom, grill and pizza
and the "Suits" value in a dictionary is: "This wine suits mushroom dishes, grilled food, pizza and chicken."
This dictionary is added to searchResultsArray, same with all other dictionaries where "Suits" value containing words mushroom, grill and pizza.
But if suitsArray is empty of objects, skip this critera.
But I'm not sure how to write the code for it. Can you help me on it?
-(IBAction)searchButtonPressed:(id)sender{
resultObjectsArray = [NSMutableArray array];
for(NSDictionary *object in allObjectsArray)
{
NSString *objectPrice = [object objectForKey:#"75 cl price"];
NSString *suitsString = // the words in suitsArray
NSString *objectSuits = [object objectForKey:#"Suits"];
BOOL priceConditionGood = YES;
if (minPrisSlider.value <= maxPrisSlider.value && (winePrice.floatValue < minPrisSlider.value || winePrice.floatValue > maxPrisSlider.value))
priceConditionGood = NO;
if (priceConditionGood)
[resultObjectsArray addObject:object];
}
ResultsTableViewController *nextController = [[self storyboard] instantiateViewControllerWithIdentifier:#"ResultsController"];
nextController.objectsArray = [[NSMutableArray alloc]initWithArray:resultObjectsArray];
[self.navigationController pushViewController:nextController animated:YES];
}
It would be better to see if suitsArray contains the string objectSuits, rather than using rangeOfString.
if ([suitsArray containsObject:objectSuits]);
[resultObjectsArray addObject:object];
After edit: I think I understand now. This method looks at each word in suitsArray, and if it finds any of them in objectSuits, then object is added to the resultObjectArray.
for (NSString *aWord in suitsArray) {
if ([objectSuits rangeOfString:aWord].length == aWord.length) {
[resultObjectArray addObject:objectSuits];
break;
}
}
Second Edit: After further thought, the above code has a flaw in the logic that may or may not be important -- because it uses rangeOfString it will find words inside other words. So, if objectSuits was "This wine goes well with cheesecake" and suitsArray contained the word cheese, it would find a match (I can't imagine a wine that would be good with cheese would be good with cheesecake). So, here is a better solution, I think, that breaks objectSuit up into individual words and puts them into a set. suitsArray is also converted to a set, so that we can use intersectsSet: to find if there are any common words.
NSCharacterSet *sepSet = [NSCharacterSet characterSetWithCharactersInString:#" ,.;"];
NSArray *words = [objectSuits componentsSeparatedByCharactersInSet:sepSet];
NSSet *objectSuitsWords = [NSSet setWithArray:words];
NSSet *suitsSet = [NSSet setWithArray:suitsArray];
BOOL ans = [suitsSet intersectsSet:objectSuitsWords];
So, if ans is 1, then that object should be added to the results array. Notice that sepSet starts with a space and includes a comma, period, and semicolon. There might be other things you might want to include, but I think this should work in most cases.

Why does SBJson JSON parsing only get the last key of interest?

I am using the following JSON: http://www.kb.dk/tekst/mobil/aabningstider_en.json
When I try to parse it by the key "location" as such:
// get response in the form of a utf-8 encoded json string
NSString *jsonString = [[[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding] stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]];
// get most parent node from json string
NSDictionary *json = [jsonString JSONValue];
// get key-path from jason up to the point of json object
NSDictionary *locations = [json objectForKey:#"location"];
NSLog( #"%#", locations );
// iterate through all of the location objects in the json
for (NSDictionary *loc in locations )
{
// pull library name from the json object
NSString *name = [loc valueForKey:#"name"];
// add library data table arrays respectively
[ libraryNames addObject: ( ( name == nil | name.length > 0 ) ? name : #"UnNamed" ) ];
}
When I print the the object locations via NSLog:
{
address = "Universitetsparken 4, 3. etage, 2100 K\U00f8benhavn \U00d8";
desc = "";
lastUpdated = "";
latlng = "55.703124,12.559596";
link = "http://www.farma.ku.dk/index.php?id=3742";
name = "Faculty of Pharmaceutical Sciences Library";
parts = {
part = {
hour = {
day = "5.June Constitution Day (Denmark)";
open = Closed;
};
hours = {
hour = {
day = Friday;
open = "10-16";
};
};
name = main;
};
};
}
Which is only the last value for the "location" keys. Am I doing something wrong?
I tried validating the JSON via http://jsonlint.com/, however when I'd put in the JSON URL as above, it said "valid" - still only the last "locations" key was shown", however if I copy-paste it, it will not validate the JSON, and has to be fixed by removing new-lines from the string.
Also, when i try to parse the JSON and get the "name" fields, I get the following exception:
2012-05-08 15:37:04.941 iPhone App Tabbed[563:f803] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<__NSCFString 0x68bfe70> valueForUndefinedKey:]: this class is not key value coding-compliant for the key name.'
*** First throw call stack:
(0x13dc052 0x156dd0a 0x13dbf11 0x9d2f0e 0x941841 0x940ca9 0x4593 0xf964e 0x114b89 0x1149bd 0x112f8a 0x112e2f 0x1148f4 0x13ddec9 0x365c2 0x3655a 0x25b569 0x13ddec9 0x365c2 0x3655a 0xdbb76 0xdc03f 0xdbbab 0x25dd1f 0x13ddec9 0x365c2 0x3655a 0xdbb76 0xdc03f 0xdb2fe 0x5ba30 0x5bc56 0x42384 0x35aa9 0x12c6fa9 0x13b01c5 0x1315022 0x131390a 0x1312db4 0x1312ccb 0x12c5879 0x12c593e 0x33a9b 0x281d 0x2785)
terminate called throwing an exception(lldb)
It would make more sense if the "locations" tag was an array object enclosed by square brackets ([]), however right now it's only an sequence of normal key-value pairs... Sadly, that's the JSON I have to work with.
Please help and thanks a great deal! :)
Sincerely,
Piotr.
The JSON you've got to work with may be valid, but it doesn't make much sense. It has one big dictionary with the location key repeated many times. Most JSON parser will simply return the last value for the repeated key. It would be best if you could change the structure to use an array instead, but if you cannot there's still hope. You can read the stream and stuff the values from the location keys into an array as they come out of it. This is how you'd do that:
#interface BadJsonHelper : NSObject
#property(strong) NSMutableArray *accumulator;
#end
#implementation BadJsonHelper
- (void)parser:(SBJsonStreamParser *)parser foundArray:(NSArray *)array {
// void
}
- (void)parser:(SBJsonStreamParser *)parser foundObject:(NSDictionary *)dict {
[accumulator addObject:dict];
}
#end
You can drop that little helper class at the top of your file, outside the #implementation section of the class where you're doing your work. (There's no need for the #interface and #implementation being in different files.)
In your code, you would use it like this:
BadJsonHelper *helper = [[BadJsonHelper alloc] init];
helper.accumulator = [NSMutableArray array];
SBJsonStreamParserAdapter *adapter = [[SBJsonStreamParserAdapter new] init];
adapter.delegate = helper;
adapter.levelsToSkip = 1;
SBJsonStreamParser *parser = [[SBJsonStreamParser alloc] init];
parser.delegate = adapter;
switch ([parser parse: responseData]) {
case SBJsonStreamParserComplete:
NSLog(#"%#", helper.accumulator);
break;
case SBJsonStreamParserWaitingForData:
NSLog(#"Didn't get all the JSON yet...");
break;
case SBJsonStreamParserError:
NSLog(#"Error: %#", parser.error);
break;
}
This example was originally adapted from the following test:
https://github.com/stig/json-framework/blob/master/Tests/StreamParserIntegrationTest.m
Update: I created a fully functional example project that loads the JSON asynchronously and parses it. This is available from github.
The JSON is valid, however there is a basic problem regarding the definition of the array of items.
Instead of defining an array of locations using brackets, the JSON redefines the same location key/value pair over and over again. In other words JSON initially says the value of location is the collection with name "The Black Diamond", but immediately after it redefines it with the collection with name "Faculty Library of Humanities" and so on till the last location Faculty of Pharmaceutical Sciences Library".
The same is true for parts and hours.
If you can't fix the result of the JSON and you really need to get it working you may want to modify the JSON removing the "location" keys and adding brackets properly.
Edit
Alternatively you may use an NSScanner and process the JSON result manually. Kinda hacky but it will work as long as the JSON format doesn't change significantly.
Edit
This snipped of code should do the work...
NSString *jsonString = [[[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding] stringByTrimmingCharactersInSet:[NSCharacterSet newlineCharacterSet]];
int indx = 1;
for (;;)
{
NSRange locationRange = [jsonString rangeOfString:#"\"location\":"];
if (locationRange.location == NSNotFound) break;
jsonString = [jsonString stringByReplacingCharactersInRange:locationRange
withString:[NSString stringWithFormat:#"\"location%d\":", indx++]];
}
NSDictionary *locations = [json objectForKey:#"location"];
As you can see, the result of JSON parsing by SBJson is a NSDictionary. A dictionary contains key/value pairs, and the keys are unique identifiers for the pairs.
The JSON data you need to handle is valid but not a good one. Per RFC 4627 - 2.2:
An object structure is represented as a pair of curly brackets surrounding zero or more name/value pairs (or members). A name is a string. A single colon comes after each name, separating the name from the value. A single comma separates a value from a following name. The names within an object SHOULD be unique.
Things like jQuery can parse the JSON also, but the result is the same as SBJson (the last one as the one). See Do JSON keys need to be unique?.
It is not a MUST, but it's still not a good practice. It would be much easier if you are able to change the structure of the JSON data on the server side (or even on the client side after receiving it) rather than parsing it as is.

xcode iPhone array and dictionary [noob]

I'm sorry for this (probably very) noob question, but i've been asked about this and can't see what's wrong (i'm java tought..)
This is what I have, data is loaded via JSON:
NSDictionary *myvalues = [myres objectForKey:#"0"];
this is the content if I output via NSLog:
({id = "1a";myval = 5;},
{id = "2b";myval="24.6";})
how do I iterate through myvalues and how do I get the values id and myval? Something like this i'm getting stuck:
for (NSArray* myvals_array in myvalues)
First it looks like the returned value is an Array, the content inside of the parentheses() denotes this. So I would try and set it as such instead of a Dictionary. Then you can enumerate through the array of dictionary's and get each dictionary inside:
for (id object in myvalues) {
NSDictionary *currentObject = (NSDictionary*)object;
NSString *myID = [currentObject valueForKey:#"id"];
NSString *myValue = [currentObject valueForKey:#"myval"];
NSLog(#"ID:%# VALUE:%#",myID,myValue);
}
This will enumerate through the array and create a dictionary for each entry, then get the values for each of the two elements inside. I just NSLog() them here but you can do whatever you want with the values.

Update original NSMutableArray after filtering with NSPredicate

I have recently started programming for the iOS Platform but now I need some help figuring out how to do 'something':
For my application I fetch some JSON data and put this data as objects into an Array
This Array is written to my own PLIST file (in the docs directory)
Now when the users starts a sync action I:
Fetch the data from the PLIST
Get the timestamp for a certain object in the Array that came from the PLIST
Use timestamp in new JSON request (for the new data)
So far so good.
Now for my (current) problem -> After receiving the new data (JSON req) I wish to update the timestamp of this 'certain' object in the array (and write this to the Plist).
Using an NSPredicate I am able to find the right set of data within the main Array (stampArr).
NSString *documentsDir = [NSHomeDirectory()stringByAppendingPathComponent:#"Documents"];
NSString *plistPath = [documentsDir stringByAppendingPathComponent:#"stamps.plist"];
NSMutableArray *stampArr = [[NSMutableArray alloc] initWithContentsOfFile:plistPath];
NSPredicate *filter = [NSPredicate predicateWithFormat:#"eventid = 1"];
NSMutableArray *filteredStampArr = [stampArr filteredArrayUsingPredicate:filter];
But now, after I update the filteredStampArr, I want to update the main Array with the data from the filtered Array.
In other words, I need to update the object from the Array with the new 'timestamp' (field of object).
I could off course use something like [stampArr addObject: [filteredStampArr copy]] after changing the filterd array but that would just create a duplicate of the information. I wish to overwrite the original object.
Somehow (I think) I need a 'pointer' that tells me the location of the data in the original array so that I can change the data directly in the main array?
(I hope my questions is clear - If not please say so)
Get the item, find it's index in stampArr and replace it with the newItem.
NSArray *filteredStampArr = [stampArr filteredArrayUsingPredicate:filter];
id item = [filteredStampArr objectAtIndex:0]; // id because the type of the item is not known
NSUInteger itemIndex = [stampArr indexOfObject:item];
[stampArr replaceObjectAtIndex:itemIndex withObject:newItem];
When you get filteredArray, you can directly update objects in it (not replace) and thay willbe uopdated in main array.
Read the API carefully!
try:
[stampArr filterUsingPredicate:];

add two values with [[object valueForKey:#""] componentsJoinedByString:#"\n"];

hy everyone,
i really need a helping hand here.
here is my problem:
i have an array with objects which each has two attributes:
1 is an NSSTring name
2 is an NSSNumber price
now i want to run through the array and add all the values to a new string:
the problem is i need the following output.
name (from object 1) : price (from object 1) then new line (\n)
name (from object 2) : price (from object 2) then new line (\n) ... and so on...
would be great if someone could help me here.
with kind regards, thomas
Try this:
// This contains your objects with names and prices;
NSArray *productArray;
...
// |listOfProducts| will be your string with all names and prices.
NSMutableString *listOfProducts = [[NSMutableString alloc] init];
// Build the list of products
for (Product *product in productArray) {
[listOfProducts appendFormat:#"%#: $%0.2f\n", product.name, product.price];
}