I know I can check if a string contains another string like this
NSString *string = #"hello bla bla";
if ([string rangeOfString:#"bla"].location == NSNotFound) {
NSLog(#"string does not contain bla");
} else {
NSLog(#"string contains bla!");
}
But what if I have an NSArray *arary = #[#"one",#"two", #"three", #"four"] and I wanted to check if a string contains either one of these without just loop or have a bunch of or's (|| ). So it would be something like this
if (array contains one or two or three or four) {
//do something
}
But if I have a longer array this becomes tedious so is there another way, without just looping through?
EDIT
I want to check if myArray has any of theses values in valuesArray
valuesArray =#[#"one",#"two", #"three", #"four"];
myArray = [#"I have one head", #"I have two feet", #"I have five fingers"]
OUTPUT
outputArray = #[#"I have one head", #"I have two feet"]
There you go:
NSArray* arrRet = [myArray filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id __nonnull evaluatedObject, NSDictionary<NSString *,id> * __nullable bindings) {
for(NSString* val in valuesArray) {
if ([evaluatedObject rangeOfString:val].location != NSNotFound)
return true;
}
return false;
}]];
arrRet contains exactly the two desired strings.
A little bit more magic later you have your code without writing a loop :P
NSArray* arrRet = [myArray filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary<NSString *,id> * bindings) {
BOOL __block match = false;
[valuesArray enumerateObjectsUsingBlock:^(id __nonnull obj, NSUInteger idx, BOOL * __nonnull stop) {
*stop = match = [evaluatedObject rangeOfString:obj].location != NSNotFound;
}];
return match;
}]];
You could use a NSCompoundPredicate
NSCompoundPredicate *predicate = [NSCompoundPredicate orPredicateWithSubpredicates:subPredicates];
Where your subPredicates must look like
(
SELF CONTAINS[c] "one",
SELF CONTAINS[c] "two",
SELF CONTAINS[c] "three",
SELF CONTAINS[c] "four"
)
To get there from
NSArray *array = #[#"one", #"two", #"three", #"four"]
You could use a for loop, but as you are opposed to that, let's cheat:
by using a category I each NSArray functional mapping, but instead of looping, I use enumerating
#interface NSArray (Map)
-(NSArray *) vs_map:(id(^)(id obj))mapper;
#end
#implementation NSArray (Map)
-(NSArray *)vs_map:(id (^)(id))mapper
{
NSMutableArray *mArray = [#[] mutableCopy];
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
id mapped = mapper(obj);
[mArray addObject:mapped];
}];
return [mArray copy];
}
#end
Now I can create the subPredicates like
NSArray *subPredicates = [arary vs_map:^id(NSString *obj) {
return [NSPredicate predicateWithFormat:#"SELF contains[c] %#", obj];
}];
and create the compound predicate like
NSCompoundPredicate *predicate = [NSCompoundPredicate orPredicateWithSubpredicates:subPredicates];
and use it
BOOL doesContain = [predicate evaluateWithObject:string];
et voilĂ : No (obvious) looping, while there is one hidden in the enumeration and probably in the predicate as-well.
Now with the changed question you basically ask for filtering. You can use the same predicate for that:
NSArray *testarray = #[#"I have one head", #"I have two feet", #"I have five fingers"];
NSArray *arary = #[#"one",#"two", #"three", #"four"];
NSArray *subPredicates = [arary vs_map:^id(NSString *obj) {
return [NSPredicate predicateWithFormat:#"SELF contains[c] %#", obj];
}];
NSCompoundPredicate *predicate = [NSCompoundPredicate orPredicateWithSubpredicates:subPredicates];
NSArray *results = [testarray filteredArrayUsingPredicate:predicate];
results now contains
(
I have one head,
I have two feet
)
the complete code
#import <Foundation/Foundation.h>
#interface NSArray (Map)
-(NSArray *) vs_map:(id(^)(id obj))mapper;
#end
#implementation NSArray (Map)
-(NSArray *)vs_map:(id (^)(id))mapper
{
NSMutableArray *mArray = [#[] mutableCopy];
[self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
id mapped = mapper(obj);
[mArray addObject:mapped];
}];
return [mArray copy];
}
#end
int main(int argc, const char * argv[]) {
#autoreleasepool {
NSArray *testarray = #[#"I have one head", #"I have two feet", #"I have five fingers"];
NSArray *arary = #[#"one",#"two", #"three", #"four"];
NSArray *subPredicates = [arary vs_map:^id(NSString *obj) {
return [NSPredicate predicateWithFormat:#"SELF contains[c] %#", obj];
}];
NSCompoundPredicate *predicate = [NSCompoundPredicate orPredicateWithSubpredicates:subPredicates];
NSArray *results = [testarray filteredArrayUsingPredicate:predicate];
}
return 0;
}
Besides my cheating the my other question, here an idea how really to avoid time costly looping: Use Set computation magic!
created a class 'Sentence', instantiate it with the strings to test
created a 'Word' class that takes a word to search for
overwrite both classes' isEqual: method to match for if a word is in the sentence (use sets there too!)
put those into an array.
from this array create a NS(*)Set object
put all word in a set
execute union on it.
Related
For example:
I have two NSMutableArray. One #[1,2,3,4,5,6,7]. Other have objects like
#[
#{#idObjectToSearch":1, #"name":#"aaaaa", #"surname": #"bbbbb"}, #{#idObjectToSearch":2, #"name":#"aaaaa", #"surname": #"ccccc"},
...
#{#idObjectToSearch":100, #"name":#"aaaaa", #"surname": #"cccdcd"}
];
So how I could extract needed objects from second array more effective way?
You need to use NSPredicate with your second array.
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"idObjectToSearch IN %#", firstArray];
//In above predicate instead of passing `1` you need to pass object from first array that you want.
NSArray *filterArray = [secondArray filteredArrayUsingPredicate:predicate];
//Now access Array objects
if (filterArray.count > 0) {
NSLog(#"%#",filterArray);
}
You can do it like this
NSMutableArray * arrSorted = [NSMutableArray new];
for(int i=0;i<arr.count;i++) {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"idObjectToSearch == %#", firstArray[i]];
NSUInteger index = [secondArray indexOfObjectPassingTest:^(id obj, NSUInteger idx, BOOL *stop) {
return [predicate evaluateWithObject:obj];
}];
if (index != NSNotFound) {
[arrSorted addObject:[arrM objectAtIndex:index]];
}
}
arrSorted will contain your sorted data
How to get the count of elements present in array that contains only string #"one".
NSMutableArray *array = [[NSMutableArray alloc]initWithObject:#"one",#"one",#"two",#"one",#"five",#"one",nil];
How to get the count of array which contains one in it.
There is another solution from the ones mentioned
// Query to find elements which match 'one'
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF contains %#",
#"one"];
// Use the above predicate on your array
// The result will be a `NSArray` so from there we count the elements on this array
NSInteger count = [array filteredArrayUsingPredicate:predicate].count;
// Prints out number of elements
NSLog(#"%li", (long)count);
Many ways to go:
NSMutableArray *array = [[NSMutableArray alloc]initWithObject:#"one",#"one",#"two",#"one",#"five",#"one",nil];
Use blocks:
NSInteger occurrenceCount = [[array indexesOfObjectsPassingTest:^(id obj, NSUInteger idx, BOOL *stop) {return [obj isEqual:#"one"];}] count];
Use loop:
int occurrenceCount = 0;
for(NSString *str in array){
occurrenceCount += ([string isEqualToString:#"one"]?1:0);
}
Use NSCountedSet:
NSCountedSet *countedSet = [[NSCountedSet alloc] initWithArray:array];
NSLog(#"Occurrences of one: %u", [countedSet countForObject:#"one"]);
Use NSPredicate:(as EridB suggested)
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF contains %#",
#"one"];
NSInteger occurrenceCount = [array filteredArrayUsingPredicate:predicate].count;
Check answers here for more detail.
NSArray *array = #[#"one",#"one",#"two",#"one",#"five",#"one"];
NSPredicate *searchCountString= [NSPredicate predicateWithFormat:#"SELF contains %#",#"one"];
NSInteger count = [array filteredArrayUsingPredicate:searchCountString].count;
NSLog(#"%ld",(long)count);
I've a dictionary with a string value and an int key.
i.e.
{1,abc}
{2,bcd}
{3,cde}
I'm filtering it using NSPredicate as follows
NSMutableDictionary *tableDataSearch;
NSArray *searchResults;
NSPredicate *resultPredicate = [NSPredicate predicateWithFormat:#"self contains[cd] %#", searchText];
searchResults = [[tableDataSearch allValues] filteredArrayUsingPredicate:resultPredicate];
it is returning me the array of values which contain the specific word. I want all keys to be returned in the array and search in values like it is searching right now.
any help ?
suppose you have an array of dictionaries myArrayOfDict. and dictionary have differentes keys. this will with gives you all dictionnary where any value contains your string:
NSMutableArray* myArrayOfDict = [#[#{#"key1":#"value1"} , #{#"key2": #"value2"}]mutableCopy];
NSPredicate *resultPredicate = [NSPredicate predicateWithFormat:#"ANY SELF.#allValues contains[cd] %#", #"lue1"];
NSArray *searchResults = [myArrayOfDict filteredArrayUsingPredicate:resultPredicate]; // will get the 2nd diction
The following code filters an NSDictionary based on a substring search on its values:
NSDictionary *data = #{#1 : #"abc",
#2 : #"bcd",
#3 : #"def"};
NSString *searchText = #"bc";
NSMutableDictionary *filteredData = [NSMutableDictionary new];
[data enumerateKeysAndObjectsUsingBlock:^(id key, NSString *obj, BOOL *stop) {
NSRange range = [obj rangeOfString:searchText
options:NSCaseInsensitiveSearch | NSDiacriticInsensitiveSearch];
if (range.location != NSNotFound)
{
[filteredData setObject:obj forKey:key];
}
}];
NSArray *keys = [filteredData allKeys];
If this feels a bit cumbersome, BlocksKit provides some useful extensions to NSDictionary:
NSDictionary *filteredData = [data bk_select:^BOOL(id key, NSString *obj) {
NSRange range = [obj rangeOfString:searchText
options:NSCaseInsensitiveSearch | NSDiacriticInsensitiveSearch];
return range.location != NSNotFound;
}];
And if you prefer to use NSPredicate, you can replace range.location != NSNotFound with:
[resultPredicate evaluateWithObject:obj];
I have an NSArray with objects that have a name property.
I would like filter the array by name
NSString *alphabet = [agencyIndex objectAtIndex:indexPath.section];
//---get all states beginning with the letter---
NSPredicate *predicate =
[NSPredicate predicateWithFormat:#"SELF beginswith[c] %#", alphabet];
NSMutableArray *listSimpl = [[NSMutableArray alloc] init];
for (int i=0; i<[[Database sharedDatabase].agents count]; i++) {
Town *_town = [[Database sharedDatabase].agents objectAtIndex:i];
[listSimpl addObject:_town];
}
NSArray *states = [listSimpl filteredArrayUsingPredicate:predicate];
But I get an error - "Can't do a substring operation with something that isn't a string (lhs = <1, Arrow> rhs = A)"
How can I do this? I would like to filter the array for the first letter in name being 'A'.
Try with following code
NSPredicate *pred = [NSPredicate predicateWithFormat:#"SELF like %#", yourName];
NSArray *filteredArr = [yourArray filteredArrayUsingPredicate:pred];
EDITED :
NSPredicate pattern should be:
NSPredicate *pred =[NSPredicate predicateWithFormat:#"name beginswith[c] %#", alphabet];
Here is one of the basic use of NSPredicate for filtering array .
NSMutableArray *array =
[NSMutableArray arrayWithObjects:#"Nick", #"Ben", #"Adam", #"Melissa", #"arbind", nil];
NSPredicate *sPredicate = [NSPredicate predicateWithFormat:#"SELF contains[c] 'b'"];
NSArray *beginWithB = [array filteredArrayUsingPredicate:sPredicate];
NSLog(#"beginwithB = %#",beginWithB);
NSArray offers another selector for sorting arrays:
NSArray *sortedArray = [array sortedArrayUsingComparator:^NSComparisonResult(Person *first, Person *second) {
return [first.name compare:second.name];
}];
If you want to filter array take a look on this code:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"name == %#", #"qwe"];
NSArray *result = [self.categoryItems filteredArrayUsingPredicate:predicate];
But if you want to sort array take a look on the following functions:
- (NSArray *)sortedArrayUsingFunction:(NSInteger (*)(id, id, void *))comparator context:(void *)context;
- (NSArray *)sortedArrayUsingFunction:(NSInteger (*)(id, id, void *))comparator context:(void *)context hint:(NSData *)hint;
- (NSArray *)sortedArrayUsingSelector:(SEL)comparator;
visit https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Collections/Articles/Arrays.html
use this
[listArray sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
Checkout this library
https://github.com/BadChoice/Collection
It comes with lots of easy array functions to never write a loop again
So you can just do
NSArray* result = [thArray filter:^BOOL(NSString *text) {
return [[name substr:0] isEqualToString:#"A"];
}] sort];
This gets only the texts that start with A sorted alphabetically
If you are doing it with objects:
NSArray* result = [thArray filter:^BOOL(AnObject *object) {
return [[object.name substr:0] isEqualToString:#"A"];
}] sort:#"name"];
I am trying to find the tags inside a NSDictionary inside myAr that matches the criteria of str and I want the result that has only those exact arrays no more nor less. In this example I want only the 2nd NSDictionary of myAr.
I though of trying to achieve this by using a predicate but that always returns empty when i use arrays.
I am trying to filter using an array but this is not working. I was wondering if anyone could tell me what i am doing wrong and how could i achieve my objective. thanks in advance
NSArray * myAr = #[ #{ #"tags": #[#"one",#"two",#"three"],
#"number": #"4"
},
#{ #"tags": #[#"one",#"two"],
#"number":#"4"
},
#{ #"tags": #[#"one",#"two",#"four"],
#"number":#"4"
},
#{ #"tags": #[#"chacho",#"chocho"],
#"number":#"4"
},
#{ #"tags": #[#"one"],
#"number":#"4"
} ];
NSArray* str = #[#"one",#"two"];
NSPredicate* pre = [NSPredicate predicateWithFormat:#"tags CONTAINS %# ",str];
myAr = [myAr filteredArrayUsingPredicate:pre];
NSLog(#"%#",myAr);
If I understand your question correctly, you just have to replace "CONTAINS" by "="
in the predicate:
[NSPredicate predicateWithFormat:#"tags = %# ",str]
This gives an array with all dictionaries where the "tags" value is equal to the
given array str. In your example, it returns an array with the second dictionary
only.
UPDATE: To find all dictionaries where the "tags" value is an array with the
given elements, but independent of the order, the following slightly more
complicated predicate should work:
[NSPredicate predicateWithFormat:#"tags.#count = %d AND SUBQUERY(tags, $t, $t in %#).#count = %d",
[str count], str, [str count]];
UPDATE 2: That was too complicated, the following predicate seems to work as well:
[NSPredicate predicateWithFormat:#"tags.#count = %d AND ALL tags in %#",
[str count], str]
(I have assumed that str contains only different elements.)
For an answer that uses neither a for loop nor predicate format strings, try using a block and make use of NSSet to determine if the set of tags you want to match is equal to a set of the array element's tags. For example:
NSSet* desiredTags = [NSSet setWithObjects:#"one", #"two", nil];
NSPredicate *tagFilterPredicate = [NSPredicate
predicateWithBlock:^BOOL (id data, NSDictionary *bindings) {
NSSet *tags = [NSSet setWithArray:[data objectForKey:#"tags"]];
return [desiredTags isEqual:tags];
}];
NSArray *resultArray = [myArr filteredArrayUsingPredicate:tagFilterPredicate];
Bear in mind that this does allocate a set per iteration. So, if you're looking to avoid allocations, this is not adequate. Otherwise, it at least avoids a format string.
A brute-force way to do this would be to remove your predicate and just enumerate:
NSArray *required = #[#"one", #"two"];
NSMutableArray *matches = [#[] mutableCopy];
[myAr enumerateObjectsUsingBlock:^(NSDictionary *dict, NSUInteger idx, BOOL *stop) {
[dict enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
if ([obj isKindOfClass:[NSArray class]]) {
BOOL match = YES;
for (NSString *item in required) {
if (![obj containsObject:item]) {
match = NO;
break;
}
}
if (match && [(NSArray *)obj count] == required.count) {
[matches addObject:obj];
}
}
}];
}];