Search through NSArray for string - search

I would like to search through my NSArray for a certain string.
Example:
NSArray has the objects: "dog", "cat", "fat dog", "thing", "another thing", "heck here's another thing"
I want to search for the word "another" and put the results into one array, and have the other, non results, into another array that can be filtered further.

If the strings inside the array are known to be distinct, you can use sets. NSSet is faster then NSArray on large inputs:
NSArray * inputArray = [NSMutableArray arrayWithObjects:#"one", #"two", #"one again", nil];
NSMutableSet * matches = [NSMutableSet setWithArray:inputArray];
[matches filterUsingPredicate:[NSPredicate predicateWithFormat:#"SELF contains[c] 'one'"]];
NSMutableSet * notmatches = [NSMutableSet setWithArray:inputArray];
[notmatches minusSet:matches];

Not tested so might have a syntax error, but you'll get the idea.
NSArray* inputArray = [NSArray arrayWithObjects:#"dog", #"cat", #"fat dog", #"thing", #"another thing", #"heck here's another thing", nil];
NSMutableArray* containsAnother = [NSMutableArray array];
NSMutableArray* doesntContainAnother = [NSMutableArray array];
for (NSString* item in inputArray)
{
if ([item rangeOfString:#"another"].location != NSNotFound)
[containsAnother addObject:item];
else
[doesntContainAnother addObject:item];
}

It would not work because as per document "indexOfObjectIdenticalTo:" returns the index of the first object that has the same memory address as the object you are passing in.
you need to traverse through your array and compare.

Related

Is a subquery needed for my nspredicate to work properly?

I have an array of custom objects that have beds (1,2,3), fireplace (yes or no), den (yes or no) and ceiling heights (9-11,11-14,14-16). I need to allow filtering based on any/all/none of the items being selected to filter by. So a user may want to see 1 & 2 beds, den, fireplace and 9-11 foot ceilings. Or just 1 & 2 beds. My current predicate works for some of these. But it doesn't match all - only some. I am thinking I need a subquery. How to create a nested(?) subquery based on an array of filters?
Right now, the user selects buttons and those are matched against Filters and I use those to create my predicate.
Current predicate
Filters is an array of keys and predicate strings like 'beds, 1' and 'ceilings, 9-11'
`NSMutableArray *subPredicates = [NSMutableArray array];
for (Filter*fil in filters) {
NSPredicate *unitsPredicate = [NSPredicate predicateWithFormat:#"%K == %#", fil.key, fil.predicate];
[subPredicates addObject:bedsPredicate];
}
NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:subPredicates];
NSLog(#"homes: %#", [searchArray filteredArrayUsingPredicate:predicate]);
NSArray *ar = [searchArray filteredArrayUsingPredicate:predicate];
I'd like to allow someone to pick any of the criteria and return appropriate data.
Subqueries are used with to-many relationships. If you want to filter multiple values then the class of fil.predicate should be an array (or set) of values. The predicate format is %K IN %#, for example
for (Filter*fil in filters) {
NSPredicate *unitsPredicate;
if ([fil.predicate isKindOfClass:[NSArray class]])
unitsPredicate = [NSPredicate predicateWithFormat:#"%K IN %#", fil.key, fil.predicate];
else
unitsPredicate = [NSPredicate predicateWithFormat:#"%K == %#", fil.key, fil.predicate];
[subPredicates addObject:bedsPredicate];
}
NSPredicate *predicate = [NSCompoundPredicate andPredicateWithSubpredicates:subPredicates];
See Aggregate Operations in Predicate Programming Guide.

predicate subquery to return items by matching tags

I have a many-to-many relationship between two entities; Item and Tag. I'm trying to create a predicate to take the selectedItem and return a ranking of items based on how many similar tags they have. So far I've tried:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SUBQUERY(itemToTag, $item, $item in %#).#count > 0", selectedItem.itemToTag];
Any other iterations that have failed. It currently only returns the selectedItem in the list. I've found little on Subquery. Is there a guru out there that can help me refine this?
Thanks in advance for the help!
Edited 9June
The good news is with Dan's code I'm able to populate the tableview with items! Unfortunately ranking numbers are 0.
Solution
I originally tried searching for tags by ID rather than name. Note the two predicate options in 'rankingExpressionDescriptionForTags:' I do not have unique identifier to my tags and use the second of the two options. Thanks Dan!
A predicate is only the beginning.
First take a look at THIS VERY similar question.
Assuming your model has an Item and Tag entities, related in a many-to-many relationship:
Item.tags <<-->> Tag.items
The answer:
- (NSExpressionDescription*) rankingExpressionDescriptionForTags:(NSSet*)tags
{
NSPredicate* p = [NSPredicate predicateWithFormat:#"SUBQUERY(tags,$t,$t IN %#).#count > 0",tags];
//if your tags are not unique (meaning you only like to match the names of tags)
//change the predicate to:
//p = [NSPredicate predicateWithFormat:#"SUBQUERY(tags,$t,$t.tagName IN %#).#count > 0",[tags valueForKey:#"tagName"]];
NSExpression* rankExpresion = [(NSComparisonPredicate*)p2 leftExpression];
NSExpressionDescription* rankExpDesc = [[NSExpressionDescription alloc] init];
rankExpDesc.name = #"ranking";
rankExpDesc.expression = rankExpresion;
rankExpDesc.expressionResultType = NSInteger64AttributeType;
return rankExpDesc;
}
- (NSExpressionDescription*) objectIDExpressionDescription
{
NSExpressionDescription* expDesc = [[NSExpressionDescription alloc] init];
expDesc.name = #"objectID";
expDesc.expressionResultType = NSObjectIDAttributeType;
expDesc.expression = [NSExpression expressionForEvaluatedObject];
return expDesc;
}
- (NSFetchRequest*) rankingRequestForItem:(NSManagedObject*)item
{
NSFetchRequest* r = [NSFetchRequest fetchRequestWithEntityName:#"Item"];
NSPredicate* p = [NSPredicate predicateWithFormat:#"SELF != %#",item.objectID];
r.resultType = NSDictionaryResultType;
r.propertiesToFetch = #[[self objectIDExpressionDescription],
[self rankingExpressionDescriptionForTags:[item mutableSetValueForKey:#"tags"]]];
r.predicate = p;
return r;
}
Note:
The resulting array contains dictionaries
(AFAIK) You will have to sort the resulting array in-memory after the fetch if you like to sort by ranking
You cannot use a fetched results controller to track changes in these objects
You can use a FRC to display these items

Split NSString into multiple entries to NSMutableDictionary as key/value?

Ok, so I have this problem I have been spinning my head around for some time now.
I have a NSString like the following:
NSString* foo = #"Brand: [Ford], Model: [Focus], Color: [black]";
which I would like to transfer into a NSMutableDictionary with Brand, Model, Color as keys and Ford, Focus, black as values (without the brackets [] ), but cannot seem to find any solution to this. How do I come about accomplishing the given scenario?
Edit:
Right now I use
NSArray *stringComponents = [foo componentsSeparatedByString#","];
which gives me an array like
stringComponents = [#"Brand: [Ford]",
#"Model: [Focus]",
#"Color: [black]",];
that I need to get into the syntax proposed by Sam, but how?
NSDictionary is basically just an array that stores the values of the objects and keys, so you'd do something such as:
NSArray *keys = [NSArray arrayWithObjects:#"Brand", #"Model", #"Color", nil];
NSArray *objects = [NSArray arrayWithObjects:#"Ford", #"Focus", #"black", nil];
NSDictionary *dictionary = [NSDictionary dictionaryWithObjects:objects
forKeys:keys];

NSFetchedResultsController predicate NOT filtering part of it (using ANY, IN and BETWEEN in the same fechRequest's predicate)

I've searched a lot here and couldn't find a solution to my situation:
I have a TableViewController that uses NSFetchedResultsController to display data from CoreData.
The model has an entity "Places" that has a to-many relationship called "Types" (and an inverse one, also to-many relationship).
In a first TableViewController I display the objects from entity "types" (each place can belong to more than one type, and one type can have more than one place). When the user taps on a row it calls a new TableViewController that will show objects from entity "Places" related to "Types" using a NSFetchedResultsController.
I know I could just use:
NSSet = [aType valueForKey:#"Places"];
However, I really want to use the NSFetchedResultsController and all its benefits.
Well, at the NSFetchedResultsController accessor method I was able to recreate this relationship by using:
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Places"
inManagedObjectContext:self.managedObjectContext];
[fetchRequest setEntity:entity];
NSDictionary *types = [NSDictionary dictionaryWithObjectsAndKeys:self.theTypes, #"TYPES", nil];
NSPredicate *predicateAny = [NSPredicate
predicateWithFormat:#"ANY types IN $TYPES"];
NSPredicate *predicate = [predicateAny predicateWithSubstitutionVariables:types];
This code works fine as it returns "Places" that are related to the "Types" I want (those were hold inside the property theTypes). I use a property (theTypes), in the TableViewController to hold all "Types" objects one selected at the original tableViewController.
The problem is that the entity "Places" has a property named "distance" that I also need to use as a filter inside the NSPredicate, like this:
NSNumber *radious = [NSNumber numberWithDouble:10000.00];
NSPredicate *pred = [NSPredicate predicateWithFormat:#"distance BETWEEN {0 , %#}", radious];
NSDictionary *types = [NSDictionary dictionaryWithObjectsAndKeys:self.theTypes, #"TYPES", nil];
NSPredicate *predicateAny = [NSPredicate
predicateWithFormat:#"ANY types IN $TYPES"];
NSPredicate *predicate = [predicateAny predicateWithSubstitutionVariables:types];
NSPredicate *thePred = [NSCompoundPredicate andPredicateWithSubpredicates:
[NSArray arrayWithObjects:predicate, pred, nil]];
[fetchRequest setPredicate:thePred];
Here's where the problem occurs: the "distance" filter appears to be simply ignored by the predicate at the fetchRequest. The resulted fetch always has Places that don't match the #"distance BETWEEN {0 , %#} clause.
Can anyone help me figure out what I am missing here?
Thanks a lot!
Daniel
This predicate:
NSPredicate *predicateAny = [NSPredicate
predicateWithFormat:#"ANY types IN $TYPES",
radious];
Doesn't make sense because you don't use the radious variable in this predicate. Not sure if that is the source of the problem but you should clean it up anyway.
This predicate:
NSPredicate *pred = [NSPredicate predicateWithFormat:#"distance BETWEEN {0 , %#}", radious];
... could probably be:
NSPredicate *pred = [NSPredicate predicateWithFormat:#"distance <=%#", radious];
... unless you have the possiblity of a negative radious value. It will be much faster that way.

Core Data fetches based on properties of 'ordered' relationships

My app has smart folder like functionality: a predicate is setup with a NSPredicateEditor and used to fill the folder with a fetch request.
The entity used in the search has a to-many relationship. The relationship is ordered, in the sense that an index is stored in the destination entity for sorting purposes.
My problem is that I would like to build in a rule based on the last values in the ordered relationship, but I can't figure out how to build a predicate to do this, because the relationship is not an array. Core data doesn't actually know about the order.
I have a readonly property on the class that returns the ordered items, but this doesn't seem to help with the fetch request because the property is not available in the core data store.
The only option I can think of is to de-normalize and store the last items in the relationship ordered in a separate property. Is that the only solution?
Well, assuming I have understood the problem correctly, I'd do it like this. Lets say you've got two entities, TopEntity has a (NSString *)name property and a to-many relationship to MyEntity which has a (NSString *)data property and (NSInteger)order property.
Lets say you want the TopEntity objects which match a given string, and whose MyEntity orders are satisfy a certain condition, then you can do it with two predicates and an NSFetchRequest like so....
NSManagedObjectContext *context = [self managedObjectContext];
// Create some top level entities
TopEntity *aTop = [TopEntity insertInManagedObjectContext:context];
aTop.name = #"This is Your Name";
TopEntity *bTop = [TopEntity insertInManagedObjectContext:context];
bTop.name = #"This aint a Name";
TopEntity *cTop = [TopEntity insertInManagedObjectContext:context];
cTop.name = #"This is My Name";
// Add some data
NSInteger i, len = 30;
for(i=0; i<len; i++) {
// Create a new object
MyEntity *entity = [MyEntity insertInManagedObjectContext:context];
entity.orderValue = i;
entity.data = [NSString stringWithFormat:#"This is some data: %d", i];
if(i < 10) {
[aTop addObjectsObject:entity];
[entity addTopObject:aTop];
} else if (i < 20) {
[bTop addObjectsObject:entity];
[entity addTopObject:bTop];
} else {
[cTop addObjectsObject:entity];
[entity addTopObject:cTop];
}
}
// Save the context
NSError *error = nil;
[context save:&error];
// A predicate to match against the top objects
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name BEGINSWITH %#", #"This is"];
// A predicate to match against the to-many objects
NSPredicate *secondPredicate = [NSPredicate predicateWithFormat:#"ANY objects.order < %d", 5];
NSFetchRequest *fetch = [[NSFetchRequest alloc] init];
[fetch setEntity:[NSEntityDescription entityForName:#"TopEntity" inManagedObjectContext:context]];
[fetch setPredicate:predicate];
NSArray *result = [[context executeFetchRequest:fetch error:&error] filteredArrayUsingPredicate:secondPredicate];
for(TopEntity *entity in result) {
NSLog(#"entity name: %#", entity.name);
}
So, essentially you can just wrap the results of your fetch request with another predicate and use the ANY keyword.
I've got no idea how efficient that is, but it works for this case. Running the above will output "This is Your Name" i.e. it matches the first TopEntity.
I don't think there's a way to limit to n results in a predicate, only at the fetch request level.
Aside from referencing the last n items in a relationship as you mentioned, you might try a boolean attribute "lastN" and flip them on/off when you curate the order of the list (say, during user-initiated sort or drag-and-drop reordering).
Alternatively, you could create a separate fetch request for each searched thing that sorts by your sort key, ordered descending, and is limited (via -setFetchLimit: ) to n results.
Tracking this as a relationship or an attribute is somewhat "messy" whereas the fetch limit is more expensive (because of multiple round trips). If your reordering is done by one-off user actions, it might be better performance-wise to use the relationship or attribute approach since the work is amortized rather than done all at once in a series of fetches. I haven't found a better way myself and will follow this one closely. :-)

Resources