Check if String contains any String from array

1.9k Views Asked by At

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"]
3

There are 3 best solutions below

6
On

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;
}]];
0
On

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;
}
0
On

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.