NSPredicate to match any entry in an NSDatabase with value that contains a string
If all of the dictionaries have the same set of keys, then you could do something pretty simple:
NSArray *keys = ...; //the list of keys that all of the dictionaries contain
NSMutableArray *subpredicates = [NSMutableArray array];
for (NSString *key in keys) {
NSPredicate *subpredicate = [NSPredicate predicateWithFormat:@"%K contains[cd] %@", key, searchText];
[subpredicates addObject:subpredicate];
}
NSPredicate *filter = [NSCompoundPredicate orPredicateWithSubpredicates:subpredicates];
Then you can use filter
to filter your NSArray
(using -filteredArrayUsingPredicate
).
If, on the other hand, you have an array of arbitrary dictionaries that all have different keys, you'd need to something a bit more perverse:
NSPredicate *filter = [NSPredicate predicateWithFormat:@"SUBQUERY(FUNCTION(SELF, 'allKeys'), $k, SELF[$k] contains[cd] %@).@count > 0", searchText];
A bit about what this is doing:
FUNCTION(SELF, 'allKeys')
- this will execute-allKeys
onSELF
(anNSDictionary
) and return anNSArray
of all the keys in the dictionarySUBQUERY(allKeys, $k, SELF[$k] contains[cd] %@)
- This will iterate over every item inallKeys
, with each successive item being placed into the$k
variable. For each item, it will executeSELF[$k] contains %@
. This will basically end up doing:[theDictionary objectForKey:$k] contains[cd] %@
. If this returnsYES
, then the$k
item will be aggregated into a new array.SUBQUERY(...).@count > 0
- after finding all of the keys that correspond to values that contain your search text, we check and see if there were any. If there were (ie, the size of the array is larger than 0), then the overall dictionary will be part of the final, filtered array.
I recommend going with the first approach, if at all possible. SUBQUERY
and FUNCTION
are a bit arcane, and the first is much easier to understand.
And here's another way, which you actually almost had in your question. Instead of doing ANY SELF.allValues contains[cd] %@
, you can do ANY FUNCTION(SELF, 'allValues') contains[cd] %@
. This is equivalent to my SUBQUERY
madness, but much simpler. Kudos to you for thinking of using ANY
(I usually forget that it exists).
EDIT
The reason SELF.allValues
doesn't work, is that this is interpreted as a keypath, and -[NSDictionary valueForKey:]
is supposed to be the same as -[NSDictionary objectForKey:]
. The catch here is that if you prefix the key with @
, then it forwards on to [super valueForKey:]
, which will do what you're expecting. So you could really do:
ANY SELF.@allValues contains[cd] %@
Or simply:
ANY @allValues contains[cd] %@
And this will work (and is the best and simplest approach).
need help on a simple predicate to match any word in a string to a property
Another way of doing this (and I just learnt this myself as a result of your question) is to use subqueries. Check this SO question
for more details. You can use subqueries in the following manner –
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"SUBQUERY(%@, $str, SELF CONTAINS[cd] $str).@count != 0", words];
NSLog(@"%@", [array filteredArrayUsingPredicate:predicate]);
This seems to work as I've tested it myself but this could also be the arcane & obscure way that Dave has mentioned as it finds no mention in the Predicate Programming Guide.
The format for a SUBQUERY
can be found here
. It's the same link that you will find in the question linked earlier.
NSPredicate subquery syntax
Man, "unfriendly" is an understatement on that array!
OK, I think I figured this out:
NSArray *dataRows = @[
@{ @"row" : @"1",
@"row_values" : @[
@{ @"property_id" : @"47cc67093475061e01000542",
@"property_value" : @"Mr." },
@{ @"property_id" : @"47cc67093475061e01000540",
@"property_value" : @"Male" }
]
},
@{ @"row" : @"2",
@"row_values" : @[
@{ @"property_id" : @"47cc67093475061e01000542",
@"property_value" : @"Ms." },
@{ @"property_id" : @"47cc67093475061e01000540",
@"property_value" : @"Female" }
]
}
];
NSPredicate *p = [NSPredicate predicateWithFormat:@"SUBQUERY(row_values, $rv, $rv.property_id = %@ AND $rv.property_value = %@).@count > 0", @"47cc67093475061e01000540", @"Male"];
NSArray *filtered = [dataRows filteredArrayUsingPredicate:p];
So let's see what this predicate is doing.
Start with the outer-most level:
SUBQUERY([stuff]).@count > 0
A
SUBQUERY
returns an array of objects. We're going to run thisSUBQUERY
on everyNSDictionary
in thedataRows
array, and we want to aggregate all of the dictionaries where theSUBQUERY
on that dictionary returns something. So we run theSUBQUERY
, and then (since it returns a collection), ask it for how many items were in it (.@count
) and see if that's greater than 0. If it is, then the top-level dictionary will be in the final filtered array.Dig in to the
SUBQUERY
:SUBQUERY(row_values, $rv, $rv.property_id = %@ AND $rv.property_value = %@)
There are three parameters to every
SUBQUERY
: A key path, a variable, and a predicate. The key path is the property of the object that we're going to be iterating. Since theSUBQUERY
is being evaluated on the outer-most dictionaries, we're going to ask for the@"row_values"
of that dictionary and get back an array. TheSUBQUERY
will then iterate over the items in therow_values
collection.The variable is what we're going to call each item in the collection. In this case, it's simply going to be
$rv
(shorthand for "row value"). In our case, each$rv
will be anNSDictionary
, since therow_values
"property" is an array of dictionaries.Finally, the predicate is going to be executed, with the
$rv
getting replaced for each dictionary in turn. In this case, we want to see if the dictionary has a certainproperty_id
and a certainproperty_value
. If it does, it will be aggregated into a new array, and that is the array that will be returned from theSUBQUERY
.So to say it a different way, the
SUBQUERY
is going to build an array of all the row_values that have aproperty_id
andproperty_value
of what we're looking for.
And finally, when I run this code, I get:
(
{
row = 1;
"row_values" = (
{
"property_id" = 47cc67093475061e01000542;
"property_value" = "Mr.";
},
{
"property_id" = 47cc67093475061e01000540;
"property_value" = Male;
}
);
}
)
NSPredicate with (match OR match) AND match
[NSPredicate predicateWithFormat:@"(isActive == YES AND (class == %@ OR class == %@))", [TestClass1 class], [TestClass2 class]];
NSPredicate get Values of NSDictionary by selecting Keys with Beginswith
I would based on your comments use a Subquery then:
predicateRecs = [NSPredicate predicateWithFormat:@"SUBQUERY(FUNCTION(SELF, 'allKeys'), $k, SELF[$k] beginswith[cd] %@ AND SELF[$k] == %@).@count > 0", @"Key",@"0001"];
Dave DeLong is the predicate master:
http://www.mactech.com/sites/default/files/Dave_DeLong-Power_Of_Predicates.pdf
and of course:
https://stackoverflow.com/a/5570510/312312
NSPredicate not filtering as desired
After raising a TSI with Apple (well, who uses those things anyway?) they said I simply needed to use MATCHES
instead of BEGINSWITH
, which is only used for string matching - whereas I am trying to match on a regex.
My predicate should have therefore read:
NSPredicate *predicate = [NSPredicate predicateWithFormat: @"SELF MATCHES[cd] %@", pattern];
Filter NSDictionary of NSDictionaries using NSPredicate
You could get allKeys
from the dictionary, then filter that array using the ENDSWITH @2x.jpg
predicate. Then you can use objectsForKeys:notFoundMarker:
to get the matching dictionaries. Then filter that array using the PictureExists = 0
predicate. Then use KVC
to get the @sum.PictureSize
.
How to get specifying key's value array?
Give this a try:
[array valueForKey:specifyingKey]
Hope this helps!
Related Topics
How to Get Current Location Using Cllocationmanager in iOS
Cgcontextsetfillcolorwithcolor: Invalid Context 0X0
How to Change an iOS Device Volume Programmatically
How to Detect When User Used Password Autofill on a Uitextfield
iOS Ibeacon: How to Get All of Proximityuuid Programmatically
Why Is a Https Nsurlsession Connection Only Challenged Once Per Domain
Accessing Objective-C Base Class's Instance Variables from a Swift Class
Avplayer Uitapgesturerecognizer Not Working
How to Detect Fullscreen Mode Using Avplayerviewcontroller in Swift
Nspredicate to Match "Any Entry in an Nsdatabase with Value That Contains a String"
How to Find Out the Objective-C Generics Type
Xcode Creates Wrong IPA Folder Structure
How to Load Multiple Storyboard Files Depending on iOS Version? (5 and 6)
Xcode /Podfile.Lock: No Such File
Fbsdkaccesstoken Currentaccesstoken Is Not Being Updated After Log In
Get Google Contacts Using API on iOS
Why Self.Locationmanager Stopupdatinglocation Doesn't Stop Location Update