NSString中字符的出现次数

时间:2009-06-02 06:06:25

标签: string cocoa cocoa-touch nsstring character

我有一个NSStringNSMutableString,希望得到特定字符的出现次数。

我需要为很多字符执行此操作 - 在这种情况下使用大写英文字符 - 所以它很快就会很快。

10 个答案:

答案 0 :(得分:100)

您可以在一行中执行此操作。例如,这会计算空格数:

NSUInteger numberOfOccurrences = [[yourString componentsSeparatedByString:@" "] count] - 1;

答案 1 :(得分:24)

在NSString上尝试此类别:

@implementation NSString (OccurrenceCount)

- (NSUInteger)occurrenceCountOfCharacter:(UniChar)character
{
    CFStringRef selfAsCFStr = (__bridge CFStringRef)self;

    CFStringInlineBuffer inlineBuffer;
    CFIndex length = CFStringGetLength(selfAsCFStr);
    CFStringInitInlineBuffer(selfAsCFStr, &inlineBuffer, CFRangeMake(0, length));

    NSUInteger counter = 0;

    for (CFIndex i = 0; i < length; i++) {
        UniChar c = CFStringGetCharacterFromInlineBuffer(&inlineBuffer, i);
        if (c == character) counter += 1;
    }

    return counter;
}

@end

这个方法比componentsSeparatedByString:方法快约5倍。

答案 2 :(得分:17)

replaceOccurrencesOfString:withString:options:range:将返回NSMutableString中替换的字符数。

[string replaceOccurrencesOfString:@"A" 
                        withString:@"B" 
                           options:NSLiteralSearch 
                             range:NSMakeRange(0, [receiver length])];

答案 3 :(得分:8)

每当您在NSString中查找内容时,请先尝试使用NSScanner

NSString *yourString = @"ABCCDEDRFFED"; // For example
NSScanner *scanner = [NSScanner scannerWithString:yourString];

NSCharacterSet *charactersToCount = @"C" // For example
NSString *charactersFromString;

if (!([scanner scanCharactersFromSet:charactersToCount 
                          intoString:&charactersFromString])) {
    // No characters found
    NSLog(@"No characters found");
}

// should return 2 for this
NSInteger characterCount = [charactersFromString length];

答案 4 :(得分:6)

现在我想到的第一件事就是:NSCountedSet

NSString *string =@"AAATTC";

NSMutableArray *array = [@[] mutableCopy];

[string enumerateSubstringsInRange:NSMakeRange(0, [string length]) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
    [array addObject:substring];
}] ;
NSCountedSet * set = [[NSCountedSet alloc] initWithArray:array];

for (NSString *nucleobase in @[@"C", @"G", @"A", @"T"]){
    NSUInteger count =  [set countForObject:nucleobase];
    NSLog(@"%@: %lu", nucleobase, (unsigned long)count);
}

日志:

C: 1
G: 0
A: 3
T: 2

答案 5 :(得分:2)

你的解决方案对我不起作用,我在循环中添加了一个条件,只有当mainScanner到达字符串的末尾时才增加numberOfChar:

NSString *yourString = @"ABCCDEDRFFED"; // For example
NSScanner *mainScanner = [NSScanner scannerWithString:yourString];
NSString *temp;
NSInteger numberOfChar=0;
while(![mainScanner isAtEnd])
{
   [mainScanner scanUpToString:@"C" intoString:&temp];
   if(![mainScanner isAtEnd]) {
      numberOfChar++;
      [mainScanner scanString:@"C" intoString:nil];
   }
}

请注意,这是一个快速修复,我没有时间做出优雅的解决方案......

答案 6 :(得分:1)

我可能会用

NSString rangeOfCharacterFromSet:

rangeOfCharacterFromSet:options:range::

其中set是您要搜索的字符集。它返回与该组匹配的第一个字符的位置。保留数组或字典并增加字符数,然后重复。

答案 7 :(得分:1)

Scanner的例子在iPhone上崩溃了。我找到了这个解决方案:

NSString *yourString = @"ABCCDEDRFFED"; // For example
NSScanner *mainScanner = [NSScanner scannerWithString:yourString];
NSString *temp;
NSInteger numberOfChar=0;
while(![mainScanner isAtEnd])
{
   [mainScanner scanUpToString:@"C" intoString:&temp];
   numberOfChar++;
   [mainScanner scanString:@"C" intoString:nil];
}

它没有崩溃对我有用。希望它可以提供帮助!

答案 8 :(得分:1)

不同Objective-C解决方案的性能比较。

假设下面的所有方法都是NSString扩展名(在@implementation NSString (CountOfOccurrences)内部)。

作为示例,我使用了一个随机生成的长度为100000000的字符串,其中使用了所有拉丁字符(在Swift中为CharacterSet(charactersIn: "\u{0020}"..."\u{036F}"))。而要计数的字符是@"a"

在发行版配置中的模拟器上对Xcode 10.3执行的测试。

快速解决方案(逐字符等效)

计数字符有两种方法:是否使用NSLiteralSearch。计数将有所不同,并且性能将从根本上受到影响。为了获得最快的结果,我们将执行精确的逐字符等效操作。以下四种解决方案给出了非常接近的性能结果。

1。最快的解决方案:适应CynicismRising答案。

使用replaceOccurrencesOfString:withString:options:range:。这是在所有情况下最快的解决方案:即使将NSLiteralSearch替换为kNilOptions,您仍然比pierrot3887扫描仪解决方案要快。

- (NSUInteger)countOccurrencesOfString:(NSString *)stringToFind
{
    return [[NSMutableString stringWithString:self] replaceOccurrencesOfString:stringToFind
                                                                    withString:stringToFind
                                                                       options:NSLiteralSearch
                                                                         range:NSMakeRange(0, self.length)];
}

2。第二快,是对CynicismRising答案的另一种改编。

使用stringByReplacingOccurrencesOfString:withString:options:range:

- (NSUInteger)countOccurrencesOfString:(NSString *)stringToFind
{
    NSString *strippedString = [self stringByReplacingOccurrencesOfString:stringToFind
                                                               withString:@""
                                                                  options:NSLiteralSearch
                                                                    range:NSMakeRange(0, self.length)];
    return (self.length - strippedString.length) / stringToFind.length;
}

3。第三快,雅克解决方案。

使用CFStringGetCharacterFromInlineBuffer。 参见https://stackoverflow.com/a/15947190/1033581

4。第四快,my Swift answer到Objective-C的转换。

使用rangeOfString:options:range:

- (NSUInteger)countOccurrencesOfString:(NSString *)stringToFind
{
    //assert(stringToFind.length);
    NSUInteger count = 0;
    NSRange searchRange = NSMakeRange(0, self.length);
    NSRange foundRange;
    while ((void)(foundRange = [self rangeOfString:stringToFind options:NSLiteralSearch range:searchRange]), foundRange.length) {
        count += 1;
        NSUInteger loc = NSMaxRange(foundRange);
        searchRange = NSMakeRange(loc, self.length - loc);
    }
    return count;
}

慢速解决方案

以下解决方案不使用NSLiteralSearch,并且不执行精确的逐字符等效。前两个可能比快速解决方案慢10倍,而最后一个可能慢100倍。

5。缓慢的解决方案:Pierrot3887答案的适应性

使用scanUpToString:intoString:。不幸的是,NSScanner没有提供精确的逐个字符等效的选项。

- (NSUInteger)countOccurrencesOfString:(NSString *)stringToFind
{
    NSScanner *scanner = [NSScanner scannerWithString:self];
    scanner.charactersToBeSkipped = nil;
    scanner.caseSensitive = YES;
    NSUInteger numberOfOccurrences = 0;
    while (!scanner.isAtEnd) {
        [scanner scanUpToString:stringToFind intoString:nil];
        if (!scanner.isAtEnd) {
            numberOfOccurrences++;
            [scanner scanString:stringToFind intoString:nil];
        }
    }
    return numberOfOccurrences;
}

6。较慢的解决方案:gbaor解决方案

使用componentsSeparatedByString:。关于doable的论点,请注意,上面给出的最快的解决方案也是一个问题。

- (NSUInteger)countOccurrencesOfString:(NSString *)stringToFind
{
    return [self componentsSeparatedByString:stringToFind].count - 1;
}

7。最慢的解决方案:适应vikingosegundo答案

使用enumerateSubstringsInRange:options:usingBlock:

- (NSUInteger)countOccurrencesOfCharacter:(NSString *)characterToFind
{
    __block NSUInteger counter = 0;
    [self enumerateSubstringsInRange:NSMakeRange(0, self.length) options:NSStringEnumerationByComposedCharacterSequences usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
        if ([characterToFind isEqualToString:substring]) counter += 1;
    }];
    return counter;
}

答案 9 :(得分:0)

这是一个Swift 3工作版本,适用于NSRange,Range,String和NSString!享受:)

/// All ranges using NSString and NSRange
/// Is usually used together with NSAttributedString

extension NSString {
    public func ranges(of searchString: String, options: CompareOptions = .literal, searchRange: NSRange? = nil) -> [NSRange] {
        let searchRange = searchRange ?? NSRange(location: 0, length: self.length)
        let subRange = range(of: searchString, options: options, range: searchRange)
        if subRange.location != NSNotFound {

            let nextRangeStart = subRange.location + subRange.length
            let nextRange = NSRange(location: nextRangeStart, length: searchRange.location + searchRange.length - nextRangeStart)
            return [subRange] + ranges(of: searchString, options: options, searchRange: nextRange)
        } else {
            return []
        }
    }
}

/// All ranges using String and Range<Index>
/// Is usually used together with NSAttributedString

extension String {
    public func ranges(of searchString: String, options: CompareOptions = [], searchRange: Range<Index>? = nil ) -> [Range<Index>] {
        if let range = range(of: searchString, options: options, range: searchRange, locale: nil) {

            let nextRange = range.upperBound..<(searchRange?.upperBound ?? endIndex)
            return [range] + ranges(of: searchString, searchRange: nextRange)
        } else {
            return []
        }
    }
}