I wanna extract from an array of SKSpriteNode only the elements that intersect with a predeterminated frame. I can do it by a for iteration:
for (SKSpriteNode* Object in Array) {
if (CGRectIntersectsRect(Frame,Object.frame)) {
//extraction code
}
}
However performace of this method seems to be poor,there's a way to do this operation in a faster way? I have tried something like this:
NSPredicate *Predicate = [NSPredicate predicateWithFormat:#"CGRectIntersectsRect(Frame,SELF.frame)"];
NSArray *Results = [Array filteredArrayUsingPredicate:Predicate];
But this create the error "Unable to parse function name 'CGRectIntersectsRect' into supported selector (CGRectIntersectsRect)". What's wrong? Using a predicate instead a for will give me some gain in performance?
Since predicate parser cannot recognize a free-standing C function, you can make a predicate from a block:
NSPredicate *intersects = [NSPredicate predicateWithBlock:^BOOL(id obj, NSDictionary *bindings) {
return CGRectIntersectsRect(Frame, obj.frame);
}];
NSArray *results = [Array filteredArrayUsingPredicate:intersects];
I am not sure about the performance gain in comparison to the loop, though, because the number of comparisons is going to remain unchanged.
Related
In the following reversedArray has three or more strings such as Salads, Meats Appetizer in order.
However, I want to have Meats always to be the first string in the array.
NSPredicate *predicateMain = [NSPredicate predicateWithFormat:
#"(%K == %#)", #"categoryType", #"main"];
NSPredicate *predicateSide = [NSPredicate predicateWithFormat:
#"(%K == %#)", #"categoryType", #"side"];
NSPredicate *orPredicate = [NSCompoundPredicate orPredicateWithSubpredicates:
[NSArray arrayWithObjects:predicateMain, predicateSide,nil]];
NSArray *filteredArray = [foods filteredArrayUsingPredicate:orPredicate];
NSArray *reversedArray = [[[filteredArray valueForKeyPath:
#"#distinctUnionOfObjects.categoryName"]
reverseObjectEnumerator] allObjects];
I can do it via hardcode but I want to know proper way of handling.
To avoid hardcoding you could write a function to reorder the array with any given string as the first string, and utilize that.
For example:
void yourFunctionName(string firstString, NSArray &array){
//Iterate through array
//Check if you've found a string matching firstString
//Put it at the front by moving everything else down one
//Continue to iterate until you've reached the end of the array
}
Here it would be best to pass the array by reference (using the &) so that you modify the array itself and not just a copy of the array values (what you get when you don't pass by reference).
I cannot find a way to get the nth item in an array using an NSPredicate string. For example:
//You cannot touch or modify the code inside this method. You can only use the predicate string param to filter the array.
- (NSArray *)filterUsingNSPredicate:(NSString *)PredicateString
{
NSArray *array = #[
#"firstItem",
#"secondItem",
#"thirdItem",
#"fourthItem",
];
NSPredicate *pred = [NSPredicate predicateWithFormat:PredicateString];
NSArray *filtered = [array filteredArrayUsingPredicate:pred];
NSLog(#"This is the second item in the array! %#",filtered); //Thats the only thing in the array.
return filtered;
}
If you want to get an item, you don't receive an NSArray, because you will only receive an object.
You can use NSPredicate to search by name of an object in your array. But what you say, that is not what you want.
You can use [array objectAtIndex:index];, that returns the object in the position you indicate in index.
If you want the index of an object, you can use [array indexOfObject:object];, that returns the index of the object.
Predicates don't know about array indexes. They only know about the single object that they're presented with at any one time, and whether that object makes the predicate true or false.
The way you're presenting this problem in the comments makes absolutely no sense. If you can get a filtered version of the array, then you can get the array. Here's how you use the method you show to do so:
NSArray * fullList = [theAPI filterUsingNSPredicate:#"TRUEPREDICATE"];
TRUEPREDICATE is a special value for predicate strings that always evaluates to true. When you filter an array with that predicate, the result will be identical to the original.
You now have a reference to the array, and can index into it as you would normally.
You can create predicate with block https://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSPredicate_Class/#//apple_ref/occ/clm/NSPredicate/predicateWithBlock%3A like this to get 6-th element (at index 5 counting from 0):
__block NSInteger index = 0;
NSPredicate *pred = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary<NSString *, id> *bindings)(
return index++ == 5;
)];
Here say I have a array of objects with two attributes:
// array of object
NSArray *objects
// object
NSString *primaryTag;
NSArray *secondaryTag;
Since what I want is when the this object contains the givenTag, it could be passed to a new array called results;
Here is my codes:
NSPredicate *resultPredicate = [NSPredicate predicateWithFormat:#"primaryTag == %# || secondaryTag CONTAINS[c] %#", givenTag, givenTag];
results = [objects filteredArrayUsingPredicate:resultPredicate];
It seems that the primaryTag works well, but the secondaryTag doesn't work, can someone help me out. I am not that familiar with NSPredicate filtering. Thanks in advance.
The most efficient way to do that is with a NSCompoundPredicate like so:
NSArray *subPredicates = #[tag1, tag2, tag3];
NSPredicate *compoundPredicate = [NSCompoundPredicate orPredicateWithSubpredicates:subPredicates];
Your question is a little unclear so you might also want:
andPredicateWithSubpredicates
Depending on exactly what the nature of the result set you are looking for.
See Apple Docs here: NSCompoundPredicate Docs
i implemented the following custom class:
#interface CustomObject : NSObject
#property (copy, nonatomic) NSString *primaryTag;
#property (strong, nonatomic) NSArray *secondaryTag;
#end
and overrode it's description method for the NSLog statement to print something we understand:
- (NSString *)description {
return [NSString stringWithFormat:#"primaryTag: %#, secondaryTag: %#", _primaryTag, [_secondaryTag componentsJoinedByString:#", "]];
}
then i created some objects from the custom class and added them to an array:
NSMutableArray *objects = [NSMutableArray array];
CustomObject *obj1 = [CustomObject new];
obj1.primaryTag = #"stringToSearchFor";
obj1.secondaryTag = #[#"notTheStringToSearchFor", #"somethingElse"];
[objects addObject:obj1];
CustomObject *obj2 = [CustomObject new];
obj2.primaryTag = #"differentString";
obj2.secondaryTag = #[#"nothingWeAreLookingFor"];
[objects addObject:obj2];
CustomObject *obj3 = [CustomObject new];
obj3.primaryTag = #"anotherOne";
obj3.secondaryTag = #[#"whoCaresForThisString", #"stringToSearchFor"];
[objects addObject:obj3];
finally i created a string to search for and the predicate:
NSString *givenTag = #"stringToSearchFor";
NSPredicate *resultPredicate = [NSPredicate predicateWithFormat:#"primaryTag == %# || secondaryTag CONTAINS[c] %#", givenTag, givenTag];
when i log out the result i get the correct results:
NSLog(#"%#", [objects filteredArrayUsingPredicate:resultPredicate]);
logs:
(
"primaryTag: stringToSearchFor, secondaryTag: notTheStringToSearchFor, somethingElse",
"primaryTag: anotherOne, secondaryTag: whoCaresForThisString, stringToSearchFor"
)
which is obj1 and obj3. correct! if it does not work for you there's gotta be something else wrong with your code...
If my understanding of the original question is incorrect, please let me know, and I will adjust my answer.
Problem: You have an array of objects with 2 properties. One is primaryTag, which is a string. The second is an array of secondaryTags, which is a collection of strings. You want to filter all objects where either the primaryTag matches, or where the search string matches one of the secondaryTags.
Answer The proper way to match strings is via MATCHES or CONTAINS.
NSPredicate *pPredicate =
[NSPredicate predicateWithFormat:#"%K CONTAINS[cd] %#",
#"primaryTag", searchString];
NSPredicate *sPredicate =
[NSPredicate
predicateWithFormat:#"SUBQUERY(%K, $st, $st CONTAINS[cd] %#).#count > 0",
#"secondaryTags", searchString];
NSCompoundPredicate *searchPredicate =
[NSCompoundPredicate orPredicateWithSubPredicates:#[ pPredicate, sPredicate ]];
How it works: The first predicate is a straightforward match. You can replace CONTAINS with MATCHES, if that better fits the kind of comparison you wish to make. The [cd] suffix means case-insensitive and diacritic-insensitive. It's normal to include those when searching/filtering, but again, it's up to you. Instead of embedding the property name in the predicate format string, I use %K and a replacement parameter. In production code, that replacement parameter would be a constant.
The second predicate is a little trickier. It uses a SUBQUERY() to filter the secondaryTags array, and returns the object as matching if at least one secondary tag matches the search string. SUBQUERY() is a function with 3 parameters. The first is the collection being searched. The second is a temporary variable that represents each item in the collection, in turn; it is used in the 3rd parameter. The 3rd parameter is a regular predicate. Each item in the collection that matches the filter is included in the output of SUBQUERY(). At the end, the matching secondary tags are counted (via #count), and if the count is greater than zero, the original object is considered to have matched, so will be included in the filtered output.
Finally, we combine these two predicates into one searchPredicate, which can now be used to filter your array of objects.
I seen this issue,
My normal approch is to use the NSPredicate twice,
So that I can track the result at every steps:
Option 1:
NSPredicate *resultPredicate1 = [NSPredicate predicateWithFormat:#"primaryTag == %#", givenTag];
results1 = [objects filteredArrayUsingPredicate:resultPredicate1];
NSPredicate *resultPredicate2 = [NSPredicate predicateWithFormat:#"secondaryTag CONTAINS[c] %#", givenTag];
finalResults = [results1 filteredArrayUsingPredicate:resultPredicate2];
Option 2:
Use NSCompoundPredicate to compound multiple filtering. You can easily find many examples on google and stackOverFlow.
Hope this will help,
Thanks
I have a tableview that i want to search through with a searchable. It worked before but when i added sections i got into trouble because i had to change from arrays to dictionary.
So basically i have a NSDictionary that looks like this
{ #"districtA": array with point objects, #"districtB": array with point objects}
I need to filter them based on the point objects.name that is in the arrays. After that i want to create a new nsdictionary with the filtered objects in it.
I tried at least 10 different methods but i can't figure it out so i think this is the only way that i am most positive that should work.
This is the only way i can think of if there is an easier way or more logic way please tell me.
-(void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.name BEGINSWITH[c] %#",searchText];
//create new array to fill
NSArray *arrayWithFilteredPoints = [[NSArray alloc] init];
//loop through the values and put into an rray based on the predicate
arrayWithFilteredPoints = [NSArray arrayWithObject:[[self.PointList allValues] filteredArrayUsingPredicate:predicate]];
NSMutableDictionary *dict = [#{} mutableCopy];
for (Point *point in arrayWithFilteredPoints) {
if (![dict objectForKey:Point.district])
dict[Point.district] = [#[] mutableCopy];
[dict[Point.district]addObject:Point];
}
self.filteredPointList = dict;
self.filteredDistrictSectionNames = [[dict allKeys] sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];}
This results in a crash, it happens of course where the predicate is used but i don't know how to debug what predicate i should use:
on 'NSInvalidArgumentException', reason: 'Can't do a substring operation with something that isn't a string (lhs = (
West ) rhs = w)'
I have read the comments and you are right. There was something wrong with my code.
I changed the logic, i added some more steps (like creating NSArray without needing it) to make the solution clear
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.name BEGINSWITH[c] %#",searchText];
//1. create new array to fill only the Points from the dictionary
NSArray *allPoints = [self.PointList allValues];
NSMutableArray *allPointObjects = [[NSMutableArray alloc]init];
for (NSArray *array in allPoints) {
for (Point *point in array) {
[allPointObjects addObject:point];
}
}
//2. loop through allPointObjects and put into an mutablearray based on the predicate
NSArray *arrayWithFilteredPoints = [[NSArray alloc] init];
arrayWithFilteredPoints = [allPointObjects filteredArrayUsingPredicate:predicate];
NSMutableDictionary *dict = [#{} mutableCopy];
for (Point *point in arrayWithFilteredPoints) {
if (![dict objectForKey:point.district])
dict[point.district] = [#[] mutableCopy];
[dict[point.district]addObject:Point];
}
self.filteredPointList = dict;
self.filteredDistrictSectionNames = [[dict allKeys] sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
I wanted a filtered nsdictionary at the end that i can pass back to my tableview that reads the dictionary objects based on the keys (districts)
It seems clear from your description that [self.PointList allValues] is not an array of Point objects but an array of arrays of Point objects. That is the source of your difficulty, including your original crash.
You need to decide what to do about that; for example, if you want just one big array of Point objects, then flatten the array of arrays before you filter. I can't advise you further because it is not obvious to me what ultimate outcome you desire.
EDIT You've now modified your code and I can see more clearly what you're trying to do. You have a dictionary whose values are arrays of points, and you are trying to filter some of the points out of each array. What I would have done is to do that - i.e., run thru the keys, extract each array, filter it, and put it back (or delete the key if the array is now empty). But I can see that what you are doing should work, because you have cleverly put the keys into the points to start with, so you can reconstruct the dictionary structure from that.
I have tried some combinations but can't seem to get it right. I am looking to combine line 2 and 3 into one line of code.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name == 'SSM M51 Copperhead'"];
NSArray *searchResults1 = [self.weaponsArray filteredArrayUsingPredicate:predicate];
weapons = [searchResults1 objectAtIndex:0];
if(weapons.range > SSMrange)
SSMrange = weapons.range;
('weapons' is a class).
weapons = [self.weaponsArray filteredArrayUsingPredicate:predicate][0];
This isn't really going to make your code any faster though.
How about defining a method like:
- (Weapon*)firstWeaponMatchingPredicateWithFormat:(NSString*)format
{
NSPredicate *predicate = [NSPredicate predicateWithFormat:format];
NSArray *searchResults1 = [self.weaponsArray filteredArrayUsingPredicate:predicate];
return (searchResults1.count > 0 ? [searchResults1 objectAtIndex:0] : nil);
}
Call with:
Weapon *weapon = [self firstWeaponMatchingPredicateWithFormat:#"name == 'SSM M51 Copperhead'"];
weapon = [[self.weaponsArray filteredArrayUsingPredicate:predicate] objectAtIndex:0];
There is no need to create an array object with the filtered results only to get the first object and throw away the collection.
If you're targeting iOS 4.0 or later, NSArray provides functionality to get the index of the first object passing a test through a block.
NSUInteger indexOfObject = [self.weaponsArray indexOfObjectPassingTest:^(id obj, NSUInteger index, BOOL *stop) {
return [[obj valueForKey:#"name"] isEqualToString:#"SSM M51 Copperhead"];
}];
The block will be executed once for each object and the array will stop processing when the block returns YES. Once you know the index of the object, you can get the value directly from you self.weaponsArray array. Just make sure you check the return value for NSNotFound.
I tried filtering an array of 1,000,000 objects and searching for the both the first and last object using the two approaches. Even when looking for the last object, the block approach was still quicker than using a predicate. I'm guessing due to the saving of not creating a filtered array.