NSString – Converte apenas para alfabeto puro (ou seja, remove os acentos + pontuação)

Estou tentando comparar nomes sem pontuação, espaços, acentos etc. No momento estou fazendo o seguinte:

-(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; } 

No entanto, preciso fazer isso por centenas de strings e preciso torná-lo mais eficiente. Alguma ideia?

 NSString* finish = [[start componentsSeparatedByCharactersInSet:[[NSCharacterSet letterCharacterSet] invertedSet]] componentsJoinedByString:@""]; 

Antes de usar qualquer uma dessas soluções, não se esqueça de usar decomposedStringWithCanonicalMapping para decompor todas as letras acentuadas. Isso vai virar, por exemplo, é (U + 00E9) para e ‌́ (U + 0065 U + 0301). Então, quando você remover os caracteres não alfanuméricos, as letras não acentuadas permanecerão.

A razão pela qual isso é importante é que você provavelmente não quer, digamos, “dän” e “dün” * serem tratados da mesma forma. Se você remover todas as letras acentuadas, como algumas dessas soluções podem fazer, você terminará com “dn”, então essas sequências serão comparadas como iguais.

Então, você deve decompor-los primeiro, para que você possa despir os acentos e deixar as letras.

* Exemplo do alemão. Obrigado a Joris Weimar por fornecer isso.

Em uma pergunta semelhante, Ole Begemann sugere o uso de stringByFoldingWithOptions: e acredito que esta é a melhor solução aqui:

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

Dependendo da natureza das cadeias que você deseja converter, você pode querer definir uma localidade fixa (por exemplo, inglês) em vez de usar a localidade atual do usuário. Dessa forma, você pode ter certeza de obter os mesmos resultados em todas as máquinas.

Uma precisão importante sobre a resposta de BillyTheKid18756 (que foi corrigida por Luiz, mas não era óbvia na explicação do código):

NÃO USE stringWithCString como um segundo passo para remover acentos, ele pode adicionar caracteres indesejados no final de sua seqüência de caracteres como o NSData não é terminado por NULL (como stringWithCString espera). Ou use-o e adicione um byte NULL adicional ao NSData, como o Luiz fez no código dele.

Eu acho que uma resposta mais simples é replace:

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

De:

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

Se eu pegar de volta o código de BillyTheKid18756, aqui está o código correto completo:

 // 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:@""]; 

Se você estiver tentando comparar strings, use um desses methods. Não tente alterar dados.

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

Você precisa considerar localidade do usuário para fazer as coisas escrever com seqüências de caracteres, especialmente coisas como nomes. Na maioria das linguagens, caracteres como ä e å não são os mesmos que parecem semelhantes. Eles são caracteres inerentemente distintos com significado distinto dos outros, mas as regras e semânticas reais são distintas para cada localidade.

A maneira correta de comparar e classificar strings é considerando a localidade do usuário. Qualquer outra coisa é ingênua, errada e muito dos anos 90. Pare de fazer isso.

Se você está tentando passar dados para um sistema que não pode suportar não-ASCII, bem, isso é apenas uma coisa errada a se fazer. Passe como blobs de dados.

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

Além disso, normalizando suas strings primeiro (veja o post de Peter Hosey) pré-compondo ou decompondo, basicamente escolha uma forma normalizada.

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

Não, não é tão simples e fácil quanto pensamos. Sim, requer uma tomada de decisão informada e cuidadosa. (e um pouco de experiência em idiomas não ingleses ajuda)

Considere o uso da estrutura RegexKit . Você poderia fazer algo como:

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

Considere o uso do NSScanner e, especificamente, dos methods -setCharactersToBeSkipped: (que aceita um NSCharacterSet) e -scanString:intoString: (que aceita uma string e retorna a string verificada por referência).

Você também pode querer acoplar isto com -[NSString localizedCompare:] , ou talvez -[NSString compare:options:] com a opção NSDiacriticInsensitiveSearch . Isso pode simplificar a necessidade de remover / replace os acentos, para que você possa se concentrar na remoção de puncturas, espaço em branco, etc.

Se você deve usar uma abordagem como a apresentada em sua pergunta, use pelo menos um NSMutableString e replaceOccurrencesOfString:withString:options:range: – que será muito mais eficiente do que criar toneladas de cadeias de caracteres autoreleased quase idênticas. Pode ser que apenas reduzir o número de alocações aumente o desempenho “o suficiente” por enquanto.

Para dar um exemplo completo, combinando as respostas de Luiz e Peter, adicionando algumas linhas, você obtém o código abaixo.

O código faz o seguinte:

  1. Cria um conjunto de caracteres aceitos
  2. Transforme letras acentuadas em letras normais
  3. Remover caracteres que não estão no set

Objetivo-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:@""]; 

Exemplo 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("") 

Saída

A saída para ambos os exemplos seria: BuverE! _- 48

Apenas esbarrou nisso, talvez seja tarde demais, mas aqui está o que funcionou para mim:

 // 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]]; 
 @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 

Essas respostas não funcionaram como esperado para mim. Especificamente, decomposedStringWithCanonicalMapping não removeu acentos / umlauts como eu esperava.

Aqui está uma variação do que usei para responder ao resumo:

 // replace accents, umlauts etc with equivalent letter ie 'é' 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; 

Solução de Peter em Swift:

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

Exemplo:

 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" 

Eu queria filtrar tudo, exceto letras e números, então adaptei a implementação de uma categoria na NSString para trabalhar um pouco diferente. Neste exemplo, você especifica uma string com apenas os caracteres que deseja manter e todo o resto é filtrado:

 @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 

Depois de criar suas categorias, usá-las é trivial e você pode usá-las em qualquer NSString:

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

Ou, por exemplo, se você quisesse se livrar de tudo, exceto das vogais:

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

Se você ainda está aprendendo Objective-C e não está usando Categorias, eu encorajo você a testá-las. Eles são o melhor lugar para colocar coisas assim, porque isso dá mais funcionalidade a todos os objects da class que você categoriza.

As categorias simplificam e encapsulam o código que você está adicionando, facilitando a reutilização em todos os seus projetos. É um ótimo recurso do Objective-C!