问题
I have sort of an unfriendly array of dictionaries that in turn have arrays of data and I am trying to filter the outer array based on any of the inner array passing a predicate. I can't seem to create an NSPredicate to make this work. I started off with:
NSPredicate *lookupPredicate = [NSPredicate predicateWithFormat:
@"row_values.property_id == %@ AND row_values.property_value == %@",
@"47cc67093475061e01000540", @"Male"];
[dataRows filterUsingPredicate:lookupPredicate];
This returns no values. I've tried various forms of ANY but I can't seem to find anything that it will parse. Again, the goal is to only keep those outer array dictionaries where the predicate for ANY of the inner array dictionary content is true. I can see me chewing up a day on figuring out the incantation to make this work... any ideas?
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.";
},
...
}
}
回答1:
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;
}
);
}
)
回答2:
Apple's SUBQUERY documentation is scattered throughout several places.
- One place that describes the documentation/syntax for SUBQUERY is in the NSExpression(forSubquery:usingIteratorVariable:predicate:) documentation:
The string format for a subquery expression is:
SUBQUERY(collection_expression, variable_expression, predicate);
where expression is a predicate expression that evaluates to a collection, variableExpression is an expression which will be used to contain each individual element of collection, and predicate is the predicate used to determine whether the element belongs in the result collection.
For more details and examples, see my answer to a similar question about the documentation for SUBQUERY syntax.
来源:https://stackoverflow.com/questions/9630237/nspredicate-subquery-syntax