【问题标题】:NSString - Convert to pure alphabet only (i.e. remove accents+punctuation)NSString - 仅转换为纯字母(即删除重音+标点符号)
【发布时间】:2010-11-16 22:26:44
【问题描述】:

我正在尝试比较没有任何标点符号、空格、重音符号等的名称。 目前我正在做以下事情:

-(NSString*) prepareString:(NSString*)a {
    //remove any accents and punctuation;
    a=[[[NSString alloc] initWithData:[a dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES] encoding:NSASCIIStringEncoding] autorelease];

    a=[a stringByReplacingOccurrencesOfString:@" " withString:@""];
    a=[a stringByReplacingOccurrencesOfString:@"'" withString:@""];
    a=[a stringByReplacingOccurrencesOfString:@"`" withString:@""];
    a=[a stringByReplacingOccurrencesOfString:@"-" withString:@""];
    a=[a stringByReplacingOccurrencesOfString:@"_" withString:@""];
    a=[a lowercaseString];
    return a;
}

但是,我需要为数百个字符串执行此操作,并且需要提高效率。有什么想法吗?

【问题讨论】:

  • 重点是什么?您是否尝试将数据传递到无法处理这些字符的系统?

标签: objective-c regex cocoa string nsstring


【解决方案1】:

为了给出一个完整的例子,结合 Luiz 和 Peter 的答案,添加几行代码,你会得到下面的代码。

代码执行以下操作:

  1. 创建一组可接受的字符
  2. 将重音字母转换为普通字母
  3. 删除不在集合中的字符

目标-C

// The input text
NSString *text = @"BûvérÈ!@$&%^&(*^(_()-*/48";

// Create set of accepted characters
NSMutableCharacterSet *acceptedCharacters = [[NSMutableCharacterSet alloc] init];
[acceptedCharacters formUnionWithCharacterSet:[NSCharacterSet letterCharacterSet]];
[acceptedCharacters formUnionWithCharacterSet:[NSCharacterSet decimalDigitCharacterSet]];
[acceptedCharacters addCharactersInString:@" _-.!"];

// Turn accented letters into normal letters (optional)
NSData *sanitizedData = [text dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSString *sanitizedText = [NSString stringWithCString:[sanitizedData bytes] encoding:NSASCIIStringEncoding];

// Remove characters not in the set
NSString* output = [[sanitizedText componentsSeparatedByCharactersInSet:[acceptedCharacters invertedSet]] componentsJoinedByString:@""];

Swift (2.2) 示例

let text = "BûvérÈ!@$&%^&(*^(_()-*/48"

// Create set of accepted characters
let acceptedCharacters = NSMutableCharacterSet()
acceptedCharacters.formUnionWithCharacterSet(NSCharacterSet.letterCharacterSet())
acceptedCharacters.formUnionWithCharacterSet(NSCharacterSet.decimalDigitCharacterSet())
acceptedCharacters.addCharactersInString(" _-.!")

// Turn accented letters into normal letters (optional)
let sanitizedData = text.dataUsingEncoding(NSASCIIStringEncoding, allowLossyConversion: true)
let sanitizedText = String(data: sanitizedData!, encoding: NSASCIIStringEncoding)

// Remove characters not in the set
let components = sanitizedText!.componentsSeparatedByCharactersInSet(acceptedCharacters.invertedSet)
let output = components.joinWithSeparator("")

输出

两个示例的输出都是:BuverE!_-48

【讨论】:

    【解决方案2】:

    Peter 在 Swift 中的解决方案:

    let newString = oldString.componentsSeparatedByCharactersInSet(NSCharacterSet.letterCharacterSet().invertedSet).joinWithSeparator("")
    

    例子:

    let oldString = "Jo_ - h !. nn y"
    // "Jo_ - h !. nn y"
    oldString.componentsSeparatedByCharactersInSet(NSCharacterSet.letterCharacterSet().invertedSet)
    // ["Jo", "h", "nn", "y"]
    oldString.componentsSeparatedByCharactersInSet(NSCharacterSet.letterCharacterSet().invertedSet).joinWithSeparator("")
    // "Johnny"
    

    【讨论】:

      【解决方案3】:

      我想过滤掉除字母和数字之外的所有内容,因此我调整了 Lorean 在 NSString 上的 Category 实现,使其工作方式有所不同。在此示例中,您指定了一个字符串,其中仅包含您想要保留的字符,而其他所有内容都被过滤掉了:

      @interface NSString (PraxCategories)
      + (NSString *)lettersAndNumbers;
      - (NSString*)stringByKeepingOnlyLettersAndNumbers;
      - (NSString*)stringByKeepingOnlyCharactersInString:(NSString *)string;
      @end
      
      
      @implementation NSString (PraxCategories)
      
      + (NSString *)lettersAndNumbers { return @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; }
      
      - (NSString*)stringByKeepingOnlyLettersAndNumbers {
          return [self stringByKeepingOnlyCharactersInString:[NSString lettersAndNumbers]];
      }
      
      - (NSString*)stringByKeepingOnlyCharactersInString:(NSString *)string {
          NSCharacterSet *characterSet = [NSCharacterSet characterSetWithCharactersInString:string];
          NSMutableString * mutableString = @"".mutableCopy;
          for (int i = 0; i < [self length]; i++){
              char character = [self characterAtIndex:i];
              if([characterSet characterIsMember:character]) [mutableString appendFormat:@"%c", character];
          }
          return mutableString.copy;
      }
      
      @end
      

      一旦你创建了你的类别,使用它们就很简单了,你可以在 any NSString 上使用它们:

      NSString *string = someStringValueThatYouWantToFilter;
      
      string = [string stringByKeepingOnlyLettersAndNumbers];
      

      或者,例如,如果您想去掉除元音之外的所有内容:

      string = [string stringByKeepingOnlyCharactersInString:@"aeiouAEIOU"];
      

      如果您仍在学习 Objective-C 并且没有使用类别,我鼓励您尝试一下。它们是放置此类内容的最佳位置,因为它为您分类的类的所有对象提供了更多功能。

      类别可简化和封装您添加的代码,使其易于在您的所有项目中重复使用。这是 Objective-C 的一大特色!

      【讨论】:

        【解决方案4】:

        这些答案对我来说没有按预期工作。具体来说,decomposedStringWithCanonicalMapping 没有像我预期的那样去除重音/变音符号。

        这是我用来回答简报的变体:

        // replace accents, umlauts etc with equivalent letter i.e 'é' becomes 'e'.
        // Always use en_GB (or a locale without the characters you wish to strip) as locale, no matter which language we're taking as input
        NSString *processedString = [string stringByFoldingWithOptions: NSDiacriticInsensitiveSearch locale: [NSLocale localeWithLocaleIdentifier: @"en_GB"]];
        // remove non-letters
        processedString = [[processedString componentsSeparatedByCharactersInSet:[[NSCharacterSet letterCharacterSet] invertedSet]] componentsJoinedByString:@""];
        // trim whitespace
        processedString = [processedString stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceCharacterSet]];
        return processedString;
        

        【讨论】:

          【解决方案5】:

          如果您尝试比较字符串,请使用以下方法之一。不要尝试更改数据。

          - (NSComparisonResult)localizedCompare:(NSString *)aString
          - (NSComparisonResult)localizedCaseInsensitiveCompare:(NSString *)aString
          - (NSComparisonResult)compare:(NSString *)aString options:(NSStringCompareOptions)mask range:(NSRange)range locale:(id)locale
          

          您需要考虑用户区域设置才能使用字符串编写内容,尤其是名称之类的内容。 在大多数语言中,ä 和 å 之类的字符除了看起来相似之外并不相同。它们本质上是不同的字符,其含义与其他字符不同,但实际规则和语义因每个语言环境而异。

          比较和排序字符串的正确方法是考虑用户的语言环境。其他任何事情都是幼稚的,错误的,非常 1990 年代。别这样了。

          如果您尝试将数据传递到不支持非 ASCII 的系统,那么这是错误的做法。将其作为数据 blob 传递。

          https://developer.apple.com/library/ios/documentation/cocoa/Conceptual/Strings/Articles/SearchingStrings.html

          加上首先规范化你的字符串(参见 Peter Hosey 的帖子)预组合或分解,基本上选择一个规范化的形式。

          - (NSString *)decomposedStringWithCanonicalMapping
          - (NSString *)decomposedStringWithCompatibilityMapping
          - (NSString *)precomposedStringWithCanonicalMapping
          - (NSString *)precomposedStringWithCompatibilityMapping
          

          不,这并不像我们想象的那么简单和容易。 是的,它需要知情和谨慎的决策。 (以及一些非英语语言经验会有所帮助)

          【讨论】:

          • 我完全同意。如果您了解其他语言,简单的替换或正则表达式没有意义。代码不应该直接包含特定于语言的字符,例如要替换的字符数组等。如果本机不支持,请尝试查找库。幸运的是,obj c 对本地化提供了很好的支持。
          • API 中一些最好的语言支持。
          【解决方案6】:

          On a similar question, Ole Begemann suggests using stringByFoldingWithOptions: 我相信这是这里最好的解决方案:

          NSString *accentedString = @"ÁlgeBra";
          NSString *unaccentedString = [accentedString stringByFoldingWithOptions:NSDiacriticInsensitiveSearch locale:[NSLocale currentLocale]];
          

          根据您要转换的字符串的性质,您可能希望设置一个固定的语言环境(例如英语),而不是使用用户的当前语言环境。这样,您就可以确保在每台机器上获得相同的结果。

          【讨论】:

            【解决方案7】:
            @interface NSString (Filtering)
                - (NSString*)stringByFilteringCharacters:(NSCharacterSet*)charSet;
            @end
            
            @implementation NSString (Filtering)
                - (NSString*)stringByFilteringCharacters:(NSCharacterSet*)charSet {
                  NSMutableString * mutString = [NSMutableString stringWithCapacity:[self length]];
                  for (int i = 0; i < [self length]; i++){
                    char c = [self characterAtIndex:i];
                    if(![charSet characterIsMember:c]) [mutString appendFormat:@"%c", c];
                  }
                  return [NSString stringWithString:mutString];
                }
            @end
            

            【讨论】:

            • 我喜欢你的回答,但我对其进行了调整,使其工作方式有所不同,使用允许的字符串而不是不允许的字符集。
            【解决方案8】:

            BillyTheKid18756 的答案有一个重要的精确度(Luiz 对此进行了纠正,但在代码的解释中并不明显):

            请勿使用 stringWithCString 作为删除重音的第二步,它可以在字符串末尾添加不需要的字符,因为 NSData 不是以 NULL 结尾的(正如 stringWithCString 所期望的那样)。 或者使用它并向您的 NSData 添加一个额外的 NULL 字节,就像 Luiz 在他的代码中所做的那样。

            我认为更简单的答案是替换:

            NSString *sanitizedText = [NSString stringWithCString:[sanitizedData bytes] encoding:NSASCIIStringEncoding];
            

            作者:

            NSString *sanitizedText = [[[NSString alloc] initWithData:sanitizedData encoding:NSASCIIStringEncoding] autorelease];
            

            如果我收回BillyTheKid18756的代码,这里是完整正确的代码:

            // The input text
            NSString *text = @"BûvérÈ!@$&%^&(*^(_()-*/48";
            
            // Defining what characters to accept
            NSMutableCharacterSet *acceptedCharacters = [[NSMutableCharacterSet alloc] init];
            [acceptedCharacters formUnionWithCharacterSet:[NSCharacterSet letterCharacterSet]];
            [acceptedCharacters formUnionWithCharacterSet:[NSCharacterSet decimalDigitCharacterSet]];
            [acceptedCharacters addCharactersInString:@" _-.!"];
            
            // Turn accented letters into normal letters (optional)
            NSData *sanitizedData = [text dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
            // Corrected back-conversion from NSData to NSString
            NSString *sanitizedText = [[[NSString alloc] initWithData:sanitizedData encoding:NSASCIIStringEncoding] autorelease];
            
            // Removing unaccepted characters
            NSString* output = [[sanitizedText componentsSeparatedByCharactersInSet:[acceptedCharacters invertedSet]] componentsJoinedByString:@""];
            

            【讨论】:

              【解决方案9】:

              在使用任何这些解决方案之前,不要忘记使用decomposedStringWithCanonicalMapping 来分解任何重音字母。例如,这会将 é (U+00E9) 变成 e ‌́ (U+0065 U+0301)。然后,当您去掉非字母数字字符时,非重音字母将保留。

              这很重要的原因是您可能不希望“dän”和“dün”* 被视为相同。如果你去掉所有重音字母,就像这些解决方案中的一些可能做的那样,你最终会得到“dn”,所以这些字符串会比较相等。

              所以,你应该先把它们分解,这样你就可以去掉重音,留下字母。

              *来自德语的示例。感谢 Joris Weimar 提供。

              【讨论】:

              • 我认为 Peter 试图展示 2 个具有相同字母和不同口音的单词。 :-)
              • 有趣的德国例子。 :D 这不是德语(丹麦语在德语中是“dänisch”),但它仍然是概述问题的一个很好的例子。 dict.leo.org/#/search=Danish
              • 所以英语中常见的误解是假设它们实际上是同一个字母,但有不同的口音。在英语中,它们通常被认为是这样的,但考虑到适当的语言环境,它们在其他语言环境中是不同的字母。这就是这个问题的内在问题。这是一种幼稚且错误的排序方法。
              【解决方案10】:

              刚刚碰到这个,也许为时已晚,但这里对我有用:

              // text is the input string, and this just removes accents from the letters
              
              // lossy encoding turns accented letters into normal letters
              NSMutableData *sanitizedData = [text dataUsingEncoding:NSASCIIStringEncoding
                                                allowLossyConversion:YES];
              
              // increase length by 1 adds a 0 byte (increaseLengthBy 
              // guarantees to fill the new space with 0s), effectively turning 
              // sanitizedData into a c-string
              [sanitizedData increaseLengthBy:1];
              
              // now we just create a string with the c-string in sanitizedData
              NSString *final = [NSString stringWithCString:[sanitizedData bytes]];
              

              【讨论】:

              • 请注意,这确实有效,但稍作调整:dataUsingEncoding 返回 NSData,而不是 NSMutableData,因此您必须这样做 [[[text dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES] mutableCopy] autorelease]
              • 这也将删除所有非 ASCII 字母,如 'жопень'
              • 太棒了!你让我成为了一天的男人。由于不推荐使用 stringWithCString,因此您必须改用 stringWithCString:encoding。我也使用了 NSASCIIStringEncoding,效果很好!
              • [sanitizedData increaseLengthBy:1];正在使应用程序崩溃
              【解决方案11】:

              考虑使用NSScanner,特别是方法-setCharactersToBeSkipped:(接受NSCharacterSet)和-scanString:intoString:(接受字符串并通过引用返回扫描的字符串)。

              您可能还想将此与-[NSString localizedCompare:] 结合使用,或者可能将-[NSString compare:options:]NSDiacriticInsensitiveSearch 选项结合使用。这可以简化删除/替换重音符号的工作,因此您可以专注于删除标点符号、空格等。

              如果您必须使用您在问题中提出的方法,至少使用 NSMutableString 和 replaceOccurrencesOfString:withString:options:range: — 这比创建大量几乎相同的自动释放字符串更有效。可能只是减少分配的数量会暂时“足够”提高性能。

              【讨论】:

                【解决方案12】:
                NSString* finish = [[start componentsSeparatedByCharactersInSet:[[NSCharacterSet letterCharacterSet] invertedSet]] componentsJoinedByString:@""];
                

                【讨论】:

                • 刚刚记录了 letterCharacterSet 的内容 - 它接缝包含重音 - 这是一个 20 个字符的 sn-p opqrstuvwxyzªµºÀÁÂÃÄ 这是我使用的代码:gist.github.com/rsaunders100/6160147
                • Swift 中,因为componentsJoinedByString 确实存在但不同:let finish = "".join(start.componentsSeparatedByCharactersInSet(NSCharacterSet.letterCharacterSet().invertedSet))
                • 太棒了!我正在将文件名与字符串进行比较,例如 é 失败了。补救方法是创建一个只包含你想要的东西的集合:let name = "".join(theString.componentsSeparatedByCharactersInSet(NSCharacterSet(charactersInString: "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM").invertedSet))
                【解决方案13】:

                考虑使用RegexKit framework。你可以这样做:

                NSString *searchString      = @"This is neat.";
                NSString *regexString       = @"[\W]";
                NSString *replaceWithString = @"";
                NSString *replacedString    = [searchString stringByReplacingOccurrencesOfRegex:regexString withString:replaceWithString];
                
                NSLog (@"%@", replacedString);
                //... Thisisneat
                

                【讨论】:

                • 如何使用正则表达式删除所有标点符号而无需多个语句?我试图避免多次遍历字符串。
                • 您只需要遍历原始字符串一次。正则表达式(“正则表达式”)一次删除所有标点符号,将所有非字母数字字符替换为空白(“”)。
                猜你喜欢
                • 2012-10-25
                • 2019-07-31
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2013-08-26
                • 2012-04-13
                • 2011-12-09
                • 2014-04-29
                相关资源
                最近更新 更多