Number of occurrences of a substring in an NSString?

Objective CCocoaNsstringSubstring

Objective C Problem Overview


How can I get the number of times an NSString (for example, @"cake") appears in a larger NSString (for example, @"Cheesecake, apple cake, and cherry pie")?

I need to do this on a lot of strings, so whatever method I use would need to be relatively fast.

Thanks!

Objective C Solutions


Solution 1 - Objective C

This isn't tested, but should be a good start.

NSUInteger count = 0, length = [str length];
NSRange range = NSMakeRange(0, length); 
while(range.location != NSNotFound)
{
  range = [str rangeOfString: @"cake" options:0 range:range];
  if(range.location != NSNotFound)
  {
    range = NSMakeRange(range.location + range.length, length - (range.location + range.length));
    count++; 
  }
}

Solution 2 - Objective C

A regex like the one below should do the job without a loop interaction...

Edited

NSString *string = @"Lots of cakes, with a piece of cake.";
NSError *error = NULL;
NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:@"cake" options:NSRegularExpressionCaseInsensitive error:&error];
NSUInteger numberOfMatches = [regex numberOfMatchesInString:string options:0 range:NSMakeRange(0, [string length])];
NSLog(@"Found %i",numberOfMatches);

Only available on iOS 4.x and superiors.

Solution 3 - Objective C

was searching for a better method then mine but here's another example:

NSString *find = @"cake";
NSString *text = @"Cheesecake, apple cake, and cherry pie";

NSInteger strCount = [text length] - [[text stringByReplacingOccurrencesOfString:find withString:@""] length];
strCount /= [find length];

I would like to know which one is more effective.

And I made an NSString category for better usage:

// NSString+CountString.m

@interface NSString (CountString)
- (NSInteger)countOccurencesOfString:(NSString*)searchString;
@end

@implementation NSString (CountString)
- (NSInteger)countOccurencesOfString:(NSString*)searchString {
	NSInteger strCount = [self length] - [[self stringByReplacingOccurrencesOfString:searchString withString:@""] length];
	return strCount / [searchString length];
}
@end

simply call it by:

[text countOccurencesOfString:find];

Optional: you can modify it to search case insensitive by defining options:

Solution 4 - Objective C

There are a couple ways you could do it. You could iteratively call rangeOfString:options:range:, or you could do something like:

NSArray * portions = [aString componentsSeparatedByString:@"cake"];
NSUInteger cakeCount = [portions count] - 1;

EDIT I was thinking about this question again and I wrote a linear-time algorithm to do the searching (linear to the length of the haystack string):

+ (NSUInteger) numberOfOccurrencesOfString:(NSString *)needle inString:(NSString *)haystack {
	const char * rawNeedle = [needle UTF8String];
	NSUInteger needleLength = strlen(rawNeedle);
	
	const char * rawHaystack = [haystack UTF8String];
	NSUInteger haystackLength = strlen(rawHaystack);
	
	NSUInteger needleCount = 0;
	NSUInteger needleIndex = 0;
	for (NSUInteger index = 0; index < haystackLength; ++index) {
		const char thisCharacter = rawHaystack[index];
		if (thisCharacter != rawNeedle[needleIndex]) {
			needleIndex = 0; //they don't match; reset the needle index
		}
		
		//resetting the needle might be the beginning of another match
		if (thisCharacter == rawNeedle[needleIndex]) {
			needleIndex++; //char match
			if (needleIndex >= needleLength) {
				needleCount++; //we completed finding the needle
				needleIndex = 0;
			}
		}
	}
	
	return needleCount;
}

Solution 5 - Objective C

A quicker to type, but probably less efficient solution.

- (int)numberOfOccurencesOfSubstring:(NSString *)substring inString:(NSString*)string
{
    NSArray *components = [string componentsSeparatedByString:substring];
    return components.count-1; // Two substring will create 3 separated strings in the array.
}

Solution 6 - Objective C

Here is a version done as an extension to NSString (same idea as Matthew Flaschen's answer):

@interface NSString (my_substr_search)
- (unsigned) countOccurencesOf: (NSString *)subString;
@end
@implementation NSString (my_substring_search)
- (unsigned) countOccurencesOf: (NSString *)subString {
    unsigned count = 0;
    unsigned myLength = [self length];
    NSRange uncheckedRange = NSMakeRange(0, myLength);
    for(;;) {
        NSRange foundAtRange = [self rangeOfString:subString
                                           options:0
                                             range:uncheckedRange];
        if (foundAtRange.location == NSNotFound) return count;
        unsigned newLocation = NSMaxRange(foundAtRange); 
        uncheckedRange = NSMakeRange(newLocation, myLength-newLocation);
        count++;
    }
}
@end
<somewhere> {
    NSString *haystack = @"Cheesecake, apple cake, and cherry pie";
    NSString *needle = @"cake";
    unsigned count = [haystack countOccurencesOf: needle];
    NSLog(@"found %u time%@", count, count == 1 ? @"" : @"s");
}

Solution 7 - Objective C

If you want to count words, not just substrings, then use CFStringTokenizer.

Solution 8 - Objective C

Here's another version as a category on NSString:

-(NSUInteger) countOccurrencesOfSubstring:(NSString *) substring {
    if ([self length] == 0 || [substring length] == 0)
        return 0;

    NSInteger result = -1;
    NSRange range = NSMakeRange(0, 0);
    do {
        ++result;
        range = NSMakeRange(range.location + range.length,
                            self.length - (range.location + range.length));
        range = [self rangeOfString:substring options:0 range:range];
    } while (range.location != NSNotFound);
    return result;
}

Solution 9 - Objective C

Swift solution would be:

var numberOfSubstringAppearance = 0
let length = count(text)
var range: Range? = Range(start: text.startIndex, end: advance(text.startIndex, length))
    
while range != nil {
        
    range = text.rangeOfString(substring, options: NSStringCompareOptions.allZeros, range: range, locale: nil)
        
    if let rangeUnwrapped = range {
            
        let remainingLength = length - distance(text.startIndex, rangeUnwrapped.endIndex)
        range = Range(start: rangeUnwrapped.endIndex, end: advance(rangeUnwrapped.endIndex, remainingLength))
        numberOfSubstringAppearance++
     }
}

Solution 10 - Objective C

Matthew Flaschen's answer was a good start for me. Here is what I ended up using in the form of a method. I took a slightly different approach to the loop. This has been tested with empty strings passed to stringToCount and text and with the stringToCount occurring as the first and/or last characters in text.

I use this method regularly to count paragraphs in the passed text (ie. stringToCount = @"\r").

Hope this of use to someone.

	- (int)countString:(NSString *)stringToCount inText:(NSString *)text{
        int foundCount=0;
        NSRange range = NSMakeRange(0, text.length);
        range = [text rangeOfString:stringToCount options:NSCaseInsensitiveSearch range:range locale:nil];
        while (range.location != NSNotFound) {
	        foundCount++;
	        range = NSMakeRange(range.location+range.length, text.length-(range.location+range.length));
	        range = [text rangeOfString:stringToCount options:NSCaseInsensitiveSearch range:range locale:nil];
        }

        return foundCount;
   }

Example call assuming the method is in a class named myHelperClass...

int foundCount = [myHelperClass countString:@"n" inText:@"Now is the time for all good men to come to the aid of their country"];

Solution 11 - Objective C

for(int i =0;i<htmlsource1.length-search.length;i++){
  range = NSMakeRange(i,search.length);
  checker = [htmlsource1 substringWithRange:range];
  
  if ([search isEqualToString:checker]) {
   count++;
 
  }
  
 }

Solution 12 - Objective C

No built-in method. I'd suggest returning a c-string and using a common c-string style algorithm for substring counting... if you really need this to be fast.

If you want to stay in Objective C, this link might help. It describes the basic substring search for NSString. If you work with the ranges, adjust and count, then you'll have a "pure" Objective C solution... albeit, slow.

Solution 13 - Objective C

-(IBAction)search:(id)sender{
     
  int  maincount = 0;
    for (int i=0; i<[self.txtfmainStr.text length]; i++) {
        char c =[self.substr.text characterAtIndex:0];
        char cMain =[self.txtfmainStr.text characterAtIndex:i];
        if (c == cMain) {
          int  k=i;
            int count=0;
            for (int j = 0; j<[self.substr.text length]; j++) {
                
                if (k ==[self.txtfmainStr.text length]) {
                    break;
                }
                
                if ([self.txtfmainStr.text characterAtIndex:k]==[self.substr.text characterAtIndex:j]) {
                    
                    count++;
                }                
                
                if (count==[self.substr.text length]) {
                    maincount++;
                }
                
                k++;
            }
            
            
        }
        
        NSLog(@"%d",maincount);
    }
    
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Questionigul222View Question on Stackoverflow
Solution 1 - Objective CMatthew FlaschenView Answer on Stackoverflow
Solution 2 - Objective CgwdpView Answer on Stackoverflow
Solution 3 - Objective Cuser207616View Answer on Stackoverflow
Solution 4 - Objective CDave DeLongView Answer on Stackoverflow
Solution 5 - Objective CDashView Answer on Stackoverflow
Solution 6 - Objective CChris JohnsenView Answer on Stackoverflow
Solution 7 - Objective CPeter HoseyView Answer on Stackoverflow
Solution 8 - Objective CpaulmelnikowView Answer on Stackoverflow
Solution 9 - Objective CriikView Answer on Stackoverflow
Solution 10 - Objective Cuser278859View Answer on Stackoverflow
Solution 11 - Objective CmuhomaniaView Answer on Stackoverflow
Solution 12 - Objective Cpestilence669View Answer on Stackoverflow
Solution 13 - Objective Ch.kishanView Answer on Stackoverflow