Call a method on every word in NSString
Asked Answered
M

5

5

I would like to loop through an NSString and call a custom function on every word that has certain criterion (For example, "has 2 'L's"). I was wondering what the best way of approaching that was. Should I use Find/Replace patterns? Blocks?

-(NSString *)convert:(NSString *)wordToConvert{
    /// This I have already written
    Return finalWord;
}

-(NSString *) method:(NSString *) sentenceContainingWords{
    // match every word that meets the criteria (for example the 2Ls) and replace it with what convert: does. 
}
Mer answered 23/6, 2012 at 16:14 Comment(0)
U
1

The two ways I know of looping an array that will work for you are as follows:

NSArray *words = [sentence componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];

for (NSString *word in words)
{
    NSString *transformedWord = [obj method:word];
}

and

NSArray *words = [sentence componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];

[words enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id word, NSUInteger idx, BOOL *stop){
    NSString *transformedWord = [obj method:word];
}];

The other method, –makeObjectsPerformSelector:withObject:, won't work for you. It expects to be able to call [word method:obj] which is backwards from what you expect.

Unsuitable answered 23/6, 2012 at 16:48 Comment(1)
For some languages like Chinese, Japanese, German, whitespace is not obvious limiter between words, hence Ken approach is the proper one.Elwandaelwee
O
20

To enumerate the words in a string, you should use -[NSString enumerateSubstringsInRange:options:usingBlock:] with NSStringEnumerationByWords and NSStringEnumerationLocalized. All of the other methods listed use a means of identifying words which may not be locale-appropriate or correspond to the system definition. For example, two words separated by a comma but not whitespace (e.g. "foo,bar") would not be treated as separate words by any of the other answers, but they are in Cocoa text views.

[aString enumerateSubstringsInRange:NSMakeRange(0, [aString length])
                            options:NSStringEnumerationByWords | NSStringEnumerationLocalized
                         usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop){
    if ([substring rangeOfString:@"ll" options:NSCaseInsensitiveSearch].location != NSNotFound)
        /* do whatever */;
}];

As documented for -enumerateSubstringsInRange:options:usingBlock:, if you call it on a mutable string, you can safely mutate the string being enumerated within the enclosingRange. So, if you want to replace the matching words, you can with something like [aString replaceCharactersInRange:substringRange withString:replacementString].

Orsino answered 23/6, 2012 at 18:15 Comment(0)
S
1

If you could write your criteria with regular expressions, then you could probably do a regular expression matching to fetch these words and then pass them to your convert: method.

You could also do a split of string into an array of words using componentsSeparatedByString: or componentsSeparatedByCharactersInSet:, then go over the words in the array and detect if they fit your criteria somehow. If they fit, then pass them to convert:.

Hope this helps.

Snowcap answered 23/6, 2012 at 16:33 Comment(0)
U
1

The two ways I know of looping an array that will work for you are as follows:

NSArray *words = [sentence componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];

for (NSString *word in words)
{
    NSString *transformedWord = [obj method:word];
}

and

NSArray *words = [sentence componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];

[words enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id word, NSUInteger idx, BOOL *stop){
    NSString *transformedWord = [obj method:word];
}];

The other method, –makeObjectsPerformSelector:withObject:, won't work for you. It expects to be able to call [word method:obj] which is backwards from what you expect.

Unsuitable answered 23/6, 2012 at 16:48 Comment(1)
For some languages like Chinese, Japanese, German, whitespace is not obvious limiter between words, hence Ken approach is the proper one.Elwandaelwee
H
0

As of iOS 12/macOS 10.14 the recommended way to do this is with the Natural Language framework.

For example:

import NaturalLanguage

let myString = "..."

let tokeniser = NLTokenizer(unit: .word)
tokeniser.string = myString

tokeniser.enumerateTokens(in: myString.startIndex..<myString.endIndex) { wordRange, attributes in
    performActionOnWord(myString[wordRange])
    return true // or return false to stop enumeration
}

Using NLTokenizer also has the benefit of allowing you to optionally specify the language of the string beforehand:

tokeniser.setLanguage(.hebrew)
Historiated answered 23/8, 2022 at 0:8 Comment(0)
P
-1

I would recommend using a while loop to go through the string like this.

NSRange spaceRange = [sentenceContainingWords rangeOfString:@" "];
NSRange previousRange = (NSRange){0,0};
do {
   NSString *wordString;
   wordString = [sentenceContainingWord substringWithRange:(NSRange){previousRange.location+1,(spaceRange.location-1)-(previousRange.location+1)}];
   //use the +1's to not include the spaces in the strings
   [self convert:wordString];
   previousRange = spaceRange;
   spaceRange = [sentenceContainingWords rangeOfString:@" "];
} while(spaceRange.location != NSNotFound);

This code would probably need to be rewritten because its pretty rough, but you should get the idea.

Edit: Just saw Jacob Gorban's post, you should definitely do it like that.

Perfidious answered 23/6, 2012 at 16:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.