【问题标题】:Number of Occurrences of a Character in NSStringNSString 中一个字符的出现次数
【发布时间】:2010-10-30 14:24:52
【问题描述】:

我有一个NSStringNSMutableString,想获取特定字符的出现次数。

我需要为相当多的字符(在这种情况下为大写英文字符)执行此操作,所以它会很快。

【问题讨论】:

    标签: string cocoa cocoa-touch nsstring character


    【解决方案1】:

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

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

    【讨论】:

    • 创建一个临时数组。为了速度,应该首选Jacque's answer
    • 这算yourString开头和结尾的空格吗?
    • 速度差异只有在您执行数千次时才有意义。
    • @EthanHolshouser 实际上取决于字符串的长度。根据我的测试,CynicismRising 答案的变体是最快的(有关详细信息,请参阅我的答案)。
    【解决方案2】:

    在 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 倍。

    【讨论】:

    • 这个分类很好用,但我有一个问题:你能不能同时使用 UniChar(来自 CFString.h)和 unichar(来自 NSString)一样?
    • @Bjinse 他们都是unsigned short的typedef,所以是的。
    【解决方案3】:

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

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

    【讨论】:

      【解决方案4】:

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

      NSString *yourString = @"ABCCDEDRFFED"; // For example
      NSScanner *scanner = [NSScanner scannerWithString:yourString];
      
      NSCharacterSet *charactersToCount = [NSCharacterSet characterSetWithCharactersInString:@"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];
      

      【讨论】:

      • 我根本无法让它工作。我正在尝试计算空格数。
      • @lawrence 默认情况下,NSScanner 会忽略空格和空格。
      • 您可以使用nil 作为skipSet 调用setCharactersToBeSkipped:(NSCharacterSet *)skipSet - 并且NSScanner 不会跳过任何字符。
      • 这不会返回2,因为scanCharactersFromSet:intoString: 将在没有匹配到时立即停止。
      【解决方案5】:

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

      NSString *string = @"AAATTC";
      
      NSMutableArray *array = [NSMutableArray array];
      
      [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
      

      【讨论】:

        【解决方案6】:

        不同 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 in one line 的论点,请注意上面给出的最快解决方案也是 one liner。

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

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

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

        【讨论】:

          【解决方案7】:

          您的解决方案对我不起作用,我在循环中添加了一个条件,仅当 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];
             }
          }
          

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

          【讨论】:

          • 不清楚您指的是 Abizern 帖子还是 user251442 帖子。
          【解决方案8】:

          我可能会使用

          NSString rangeOfCharacterFromSet:

          rangeOfCharacterFromSet:options:range::

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

          【讨论】:

          • 如果我正确理解文档,这将给出集合中任何字符的范围。但我需要 每个 字符的计数。
          • 我的想法是保留 char -> count 对的字典,然后在给定索引处获取 char 并增加它在字典中的计数......或者你可以迭代字符串并检查每个字符是否在你的集合中,如果是则增加它的计数
          【解决方案9】:

          扫描仪的示例在 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];
          }
          

          它对我有用,没有崩溃。希望对您有所帮助!

          【讨论】:

            【解决方案10】:

            这是 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 []
                    }
                }
            }
            

            【讨论】:

              猜你喜欢
              • 2011-01-11
              • 2014-11-27
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多