I am trying to filter a mutable array of objects using NSPredicate and am having trouble access the level that contains the property I would like to filter on.
To give a simplified example consisting of the similar custom objects.
Grandparent
Parent
Child
I have an NSMutableArray of Grandparents and I would like to find all the Grandparent Objects that have GrandChildren of age 10.
Therefore the grandchildren are two levels deep from the root. Child has an age property amongst other things.
ie. Grandparents has an array property Parents and Parents has an array property Children and Children has an integer property age.
The following NSPredicate has returned no results. "SELF.parents.children.age == 10".
I realise that as these are nested collections this predicate is likely the wrong way to go about it but I am stuck as to how to access that level. Perhaps via a Subquery or Collection Operator but I cannot work it out.
One thing to keep in mind is that I obviously still want GrandParents that have multiple Grandchildren of different ages, one of which is aged 10.
The "obvious" solution would be the predicate:
"ANY parents.children.age == 10"
However, the "ANY" operator does not work with nested to-many relationships. Therefore, you need a SUBQUERY:
NSArray *grandParents = your array of GrandParent objects;
NSPredicate *predicate = [NSPredicate
predicateWithFormat:#"SUBQUERY(parents, $p, ANY $p.children.age == 10).#count > 0"];
NSArray *filtered = [grandParents filteredArrayUsingPredicate:predicate];
Remarks:
Using SELF in the predicate is not necessary. filteredArrayUsingPredicate applies the
predicate to each GrandParent object in the array.
The usage of SUBQUERY in predicates seems to be poorly documented. There is one example in the NSExpression class reference. See also Quick Explanation of SUBQUERY in NSPredicate Expression.
In this case, the predicate inside SUBQUERY is applied to each Parent of a single GrandParent. The SUBQUERY returns the parents that have any child aged 10.
So "SUBQUERY(...).#count > 0" evaluates to TRUE if the grandparent has at least one parent that has any child aged 10.
ADDED: I just found out that it can actually be done without SUBQUERY:
NSPredicate *predicate = [NSPredicate
predicateWithFormat:#"ANY parents.#unionOfArrays.children.age == 10"];
works and gives the desired result. (It could be less effective than the SUBQUERY, but I did not test that.)
As an alternative to Martin R's answer, you might consider using a block predicate instead. Something like this:
NSPredicate *predicate = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary * bindings) {
GrandParent *grandparent = evaluatedObject;
for (Parent *parent in grandparent.parents)
for (Child *child in parent.children)
if (child.age == 10)
return YES
return NO;
}];
Assuming GrandParent, Parent and Child are the appropriate class names of the various objects.
Personally I prefer this form, because I always feel with a string predicate that I'm mixing languages in the code, which I think makes it less readable. The choice is obviously up to you though.
Update: Having re-read the question, I now realise that the condition was more complex than I originally thought. I've updated my answer to loop over the parents and children, but Martin R's answer is now clearly a lot simpler. Still this is a possible solution to consider.
Related
More for my interest than anything else.
If you have an Class defined like so...
MyClass
-------
NSString *name
And you put a lot of them into an array (or mutable array). Then you can use a predicate like this...
[NSPredicate predicateWithFormat:#"name = %#", someValue];
to filter the array so that it only contains objects whose names are the value given.
Or sort descriptor like so...
[NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES];
to sort the array by the name field in ascending order.
My question is, if you have an array of strings (or of NSNumbers) can you use similar "format" predicates?
Say for instance you had the array...
#[#"Cat", #"Bat", #"Dog", #"Cow"];
Could you use a "predicateWithFormat" to filter this array of a "sortDescriptorWithKey" to sort it?
I know you can use blocks but just wondering if this is possible?
Sure, you can filter an array of strings, or anything else, with predicateWithFormat:. As for sorting, you would use sortedArrayUsingSelector:, and use whichever selector you want (compare:, caseInsensitiveCompare:, etc.). There are no keys in a simple array, so you couldn't use sortDescriptorWithKey.
A string does not have any keys to use your predicates on or to sort by. You can find every other possible way to sort an array in the apple docs.
I'm currently trying to teach myself Objective-C and was playing around with an exercise where I needed to sort an array.
I managed to complete it using the following code:
NSSortDescriptor * newSortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"title" ascending:TRUE];
NSArray *sortDescriptors = [NSArray arrayWithObject:newSortDescriptor];
[self.theBookStore sortUsingDescriptors:sortDescriptors];
My question is about what is actually happening here. I don't really understand exactly what I've done.
Line 1: I understand here I've created a new object that has a descriptor. This has two parameters, the column I want to sort on and that it is ascending.
Line 2: This is the line I'm confused about. Why do I need an array of sort descriptors? When I read this code I suspect it creates an array with only one row is that correct?
Line 3: I understand that this is calling the sortUsingDescriptors method but again, my confusion is why this function expects an array.
I've read the documentation but I'm really looking for a simple explanation.
Any help is much appreciated
Line 1: I understand here I've created a new object that has a
descriptor. This has two parameters, the column I want to sort on and
that it is ascending.
Really, you've created an object that is a descriptor. It describes how to sort the array.
Line 2: This is the line I'm confused about. Why do I need an array of
sort descriptors? When I read this code I suspect it creates an array
with only one row is that correct?
Right -- you've created an array that contains a single object. You could create an array that has ten or fifteen or eighty-seven sort descriptors, if you really wanted to sort on that many fields. More often, you use one, two, maybe three. So, if you're sorting a list of people, you might add sort descriptors that specify last name and first name. That way, people that have the same last name will be arranged within that group according to their first name.
Line 3: I understand that this is calling the sortUsingDescriptors
method but again, my confusion is why this function expects an array.
Again, it's so that you can have primary, secondary, tertiary (etc.) sort keys. You could have a separate method that takes a single sort descriptor instead of an array for those times when you want to sort on only one key. NSArray doesn't provide that, but you can always add it in a category if you want:
#category NSArray (SingleSortDescriptor)
- (NSArray*)sortUsingDescriptor:(NSSortDescriptor*)descriptor;
#end
#implementation NSArray (SingleSortDescriptor)
- (NSArray*)sortUsingDescriptor:(NSSortDescriptor*)descriptor
{
return [self sortUsingDescriptors:[NSArray arrayWithObject:descriptor]];
}
#end
Line 1: .. yes your right. You are creating a custom object called NSSortDescriptor. That object defines a attribute to sort after. You did enter "title". So the objects in your array-to-sort will be sorted after that property (yourObject.title "kind-of").
Line 2: Because the sorting method (sortUsingDescriptors) always needs a array, you need to create a NSArray with only one object. Okay, ... looks kind of stupid. But makes absolute sense. Lets say you would like to sort after two criteria (lets say "title", then "city").
Line 3: Yes heres must be a array because of sorting after more then one criteria.
And always keep the memory clean:
On line 1 you did allocate/init a NSSortDescriptor.
So clean up after using it (if you are not using ARC).
So add a line:
[newSortDescriptor release];
Multiple sort descriptors would be used to resolve what happens if there are multiple matches. I's a priority order. A second descriptor would tell it what to do if it found two titles the same.
I have an entity matches which has a related to-many entity sets. I want to get a count of how many sets have the attribute 'set_finished' set to YES for a particular match. I'm trying to do this with:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"ANY set_finished == YES"];
NSUInteger numberOfFinishedSets = [[[match valueForKeyPath:#"sets"] filteredArrayUsingPredicate:predicate ] count];
The second line crashes with this error, which I don't understand. Can anyone shed some light on this for me? Thanks.
2010-12-20 13:17:13.814
DartScorer[2154:207] * Terminating
app due to uncaught exception
'NSInvalidArgumentException', reason:
'-[_NSFaultingMutableSet
filteredArrayUsingPredicate:]:
unrecognized selector sent to instance
0x617fb20'
You should use filteredSetUsingPredicate: instead of filteredArrayUsingPredicate since the object is a set, not an array.
_NSFaultingMutableSet is kind of an empty set (if you will print the set you will see that xcode prints the entity name but not its content. it does that because :
Faulting reduces the amount of memory
your application consumes. A fault is
a placeholder object that represents a
managed object that has not yet been
fully realized, or a collection object
that represents a relationship:
A managed object fault is an instance
of the appropriate class, but its
persistent variables are not yet
initialized. A relationship fault is a
subclass of the collection class that
represents the relationship. Faulting
allows Core Data to put boundaries on
the object graph. Because a fault is
not realized, a managed object fault
consumes less memory, and managed
objects related to a fault are not
required to be represented in memory
at all.
i think you should create a Set to hold the objects you want to filter, and then filter the array tou have created.
hope it helps
shani
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"(ANY set_finished == YES).#count"];
NSUInteger numberOfFinishedSets = [[match valueForKeyPath:#"sets"] filteredSetUsingPredicate:predicate];
This will just return the count of how many objects there are instead of actually retrieving the objects from disk and counting them.
Try wrapping the string YES in single quotes "... 'YES' "
(assuming the field contains the strings 'YES' and 'NO' and not 1 | 0)
I have a lot of people NSManagedObjects that I need filtering and was hoping to do it within the initial fetch instead of filtering the array afterwards. I've used selectors in predicates before, but never when fetching NSManagedObjects, for example I have all my employees and then i use this predicate on the NSArray...
[NSPredicate predicateWithFormat:#"SELF isKindOfClass:%#", [Boss class]]
...but now I want to do a bit more math based on different attributes of my objects. I thought I could do something like...
[NSPredicate predicateWithFormat:#"SELF bonusIsAffordable:%f", howMuchMoneyTheCompanyHas];
..where bonusIsAffordable: is a method of my Employee class and would calculate whether I can afford to pay them a bonus. But I get an error...
Unknown/unsupported comparison predicate operator type cocoa
Any ideas what I'm screwing up?
This gets a whole lot easier with Blocks:
NSPredicate *bossPred = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
return [evaluatedObject isKindOfClass:[Boss class]];
}];
You can execute arbitrary code in an NSPredicate only when qualifying objects in memory. In the case of a SQLite-backed NSPersistentStore, the NSPredicate is compiled to SQL and executed on the SQLite query engine. Since SQLite has no knowlege of Objective-C, nor are any objects instantiated, there's no way to execute arbitrary code.
For in-memory queries (against a collection or an in-memory or atomic Core Data store), have a look at NSExpression, particular +[NSExpression expressionForFunction:selectorName:arguments:] and +[NSExpression expressionForBlock:arguments:]. Given such an expression, you can build an NSPredicate programatically.
Your predicate string doesn't tell the predicate object what to do. The method presumably returns a boolean but the predicate doesn't know what to compare that to. You might as well have given it a predicate string of "TRUE" and expected it to know what to do with it.
Try:
[NSPredicate predicateWithFormat:#"(SELF bonusIsAffordable:%f)==YES", howMuchMoneyTheCompanyHas];
is the syntax for the line of code below correct?
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"type == %#",selectedAnimalType];
I want the 'selectedAnimalType' string to be used in a search to display the the user selected.
I ran an NSLog statement for the %# object and it returned what I wanted
NSLog(#"%#",selectedAnimalType);
thanks for any help.
Whether it works depends on what class type and selectedBirdType are. If they are both objects like NSStrings or NSNumbers it will work fine. If not you may have problems.
To see exactly what the predicate is just log the predicate object itself. It will print out the exact, populated i.e. with variables substituted, predicate so you can see exactly what is going on.