3

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"]
15
  • 2
    is there any particular reason why you don`t want to loop? Commented Jun 22, 2015 at 18:44
  • 1
    it would require one loop and you will not find a more performant solution because you basically HAVE to check every element in the array Commented Jun 22, 2015 at 18:45
  • 1
    It doesn't matter. You have to loop through the strings you want to test, and each comparison will be a loop comparing the strings. You can abstract that away, but whatever function you call or regex you use or whatever will still be doing the loops. Did you measure this and make sure this is actually a performance problem for your app? How big is N? Commented Jun 22, 2015 at 18:47
  • 1
    even if you find a solution without writing a loop, you can be damn sure that method internally loops over the array - how else would you check every value? what do you mean by "2 loops" and "running it against an array"? Commented Jun 22, 2015 at 18:48
  • 1
    If possible, why not use or cache your array into an NSDictionary? You can check for an existence of a key/value pair and it would be a constant time operation. This would become more important the more values you're checking against. Commented Jun 22, 2015 at 18:52

3 Answers 3

4

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;
}]];
Sign up to request clarification or add additional context in comments.

6 Comments

hmm, I think my mapping is the nicer cheat, as it also hides the enumeration.
@vikingosegundo do you have any idea if using predicates improves performance - just out of curiousity?
I don't know much about predicate performance, but honestly: In any case it is still too fast for your neurons. If you really want to gain performance see my other answer I just posted: Using set arithmetics.
@vikingosegundo hmm, i think both version are a bit overkill for his usecase, at least the set arithmetic one. and regarding performance i was referring to up scaling - what happens if he tests 1000 strings for 100 substrings, etc. too lazy to try it :P but the predicate answer actually looks interesting +1 :)
Just posting to Stackoverflow to get rid of a for-loop over few (hundred) strings is already overkill! The question and answers are guilty of premature optimization.
|
2

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

Comments

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.