开发者

NSPredicate to match "any entry in an NSDatabase with value that contains a string"

开发者 https://www.devze.com 2023-02-22 06:45 出处:网络
I have an array of dictionaries, similar to the following: ( { Black = \"?\"; Date = \"????.??.??\"; Result = \"*\";

I have an array of dictionaries, similar to the following:

(
        {
            Black = "?";
            Date = "????.??.??";
            Result = "*";
            SourceDate = "2007.10.24";
            White = "Mating pattern #1";
        },
        {
            Black = "?";
            Date = "????.??.??";
            Result = "*";
            SourceDate = "2008.10.24";
            White = "About this Publication";
        }
)

I want to offer the user the ability to search for text either within just the "White" and "Black" fields, or within any field. I've got an NSPredicate for doing just the specific fields:


    predicate = [NSPredicate 
                    predicateWithFormat:@"self.Black contains[cd] %@ or self.White contains[cd] %@",
                        searchText, searchText];
    [filteredGames addObjectsFromArray:[games filteredArrayUsingPred开发者_开发百科icate:predicate]];

I can't think of how to phrase a predicate that will return me the dictionaries for which any of the objects within match the text. i.e. I could search for "2007" and it would return the first dictionary but not the second. I tried "self.*" which I didn't really expect to work and also "ANY self.allValues" which I was more hopeful about. I don't actually know in advance what the keys will be, hence needing something less specific.

Any suggestions?


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 on SELF (an NSDictionary) and return an NSArray of all the keys in the dictionary
  • SUBQUERY(allKeys, $k, SELF[$k] contains[cd] %@) - This will iterate over every item in allKeys, with each successive item being placed into the $k variable. For each item, it will execute SELF[$k] contains %@. This will basically end up doing: [theDictionary objectForKey:$k] contains[cd] %@. If this returns YES, 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).


if you want to match a object in the dictionary the you can do this by using a for loop but it may be a time consuming task -

for(int i=0; i< [array count]; i++)
{

    NSDictionary *dic = [array objectAtIndex:i];

    if([[dic objectForKey:@"Black"] isEqualToString:@"2007"])
    {
    //here is the match
    }
}
0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号