NSString – преобразовать только в чистый алфавит (т.е. удалить акценты + пунктуацию)

Я пытаюсь сравнивать имена без каких-либо знаков препинания, пробелов, акцентов и т. Д. На данный момент я делаю следующее:

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

Тем не менее, мне нужно сделать это для сотен строк, и мне нужно сделать это более эффективным. Есть идеи?

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

Прежде чем использовать какое-либо из этих решений, не забудьте использовать decomposedStringWithCanonicalMapping для разложения любых акцентированных букв. Это превратит, например, é (U + 00E9) в e (U + 0065 U + 0301). Затем, когда вы удаляете не буквенно-цифровые символы, оставшиеся буквы остаются без изменений.

Причина, по которой это важно, заключается в том, что вы, вероятно, не хотите, скажем, «dän» и «dün» * рассматриваться как одно и то же. Если вы удалите все буквы с акцентом, как это может сделать некоторые из этих решений, вы получите «dn», поэтому эти строки будут сравниваться как равные.

Итак, сначала вы должны разложить их, чтобы вы могли разделить акценты и оставить буквы.

* Пример от немецкого. Благодаря Джорису Веймару за его предоставление.

По аналогичному вопросу Оле Бегеманн предлагает использовать stringByFoldingWithOptions: и я считаю, что это лучшее решение здесь:

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

В зависимости от характера строк, которые вы хотите преобразовать, вы можете установить фиксированный языковой стандарт (например, английский) вместо использования текущего языкового стандарта пользователя. Таким образом, вы можете получить одинаковые результаты на каждой машине.

Одна важная точность по сравнению с ответом BillyTheKid18756 (который был исправлен Луизом, но это не было очевидно в объяснении кода):

НЕ ИСПОЛЬЗУЙТЕ stringWithCString в качестве второго шага для удаления акцентов, он может добавлять нежелательные символы в конце строки, поскольку NSData не завершается NULL (как ожидает stringWithCString). Или используйте его и добавьте дополнительный NULL-байт в NSData, как это сделал Луис в своем коде.

Я думаю, что более простой ответ – заменить:

 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È[email protected]$&%^&(*^(_()-*/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:@""]; 

Если вы пытаетесь сравнить строки, используйте один из этих методов. Не пытайтесь изменять данные.

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

Вам НЕОБХОДИМО рассматривать пользовательский язык, чтобы делать что-то, писать со строками, особенно такими, как имена. На большинстве языков такие символы, как ä и å, не отличаются от аналогичных. Они являются неотъемлемо отличными персонажами со значением, отличным от других, но фактические правила и семантика различны для каждой локали.

Правильный способ сравнения и сортировки строк – это рассмотреть локаль пользователя. Все остальное наивно, неправильно и очень 1990-х. Прекратите делать это.

Если вы пытаетесь передать данные в систему, которая не может поддерживать не-ASCII, это просто неправильно. Передайте его как данные.

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

Плюс, сначала нормализуя свои строки (см. Сообщение Питера Хосея), предварительно компонуя или разлагая, в основном выбирайте нормализованную форму.

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

Нет, это не так просто и легко, как мы склонны думать. Да, это требует осознанного и тщательного принятия решений. (и немного опыта, не связанного с английским языком)

Подумайте об использовании структуры RegexKit . Вы могли бы сделать что-то вроде:

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

Рассмотрим использование NSScanner и, в частности, методы -setCharactersToBeSkipped: (который принимает NSCharacterSet) и -scanString:intoString: (который принимает строку и возвращает отсканированную строку по ссылке).

Вы также можете связать это с помощью -[NSString localizedCompare:] или, возможно, -[NSString compare:options:] с опцией NSDiacriticInsensitiveSearch . Это может упростить удаление / замену акцентов, поэтому вы можете сосредоточиться на удалении пропусков, пробелов и т. Д.

Если вы должны использовать подход, как вы представили в своем вопросе, по крайней мере, используйте NSMutableString и replaceOccurrencesOfString:withString:options:range: – это будет намного эффективнее, чем создание тонны почти идентичных строк с автореализацией. Может быть, просто сокращение количества распределений покажет «достаточную» производительность.

Чтобы дать полный пример, объединив ответы Луиса и Питера, добавив несколько строк, вы получите код ниже.

Код выполняет следующие действия:

  1. Создает набор принятых символов
  2. Поверните акцентированные буквы в обычные буквы
  3. Удалить символы не в наборе

Objective-C

 // The input text NSString *text = @"BûvérÈ[email protected]$&%^&(*^(_()-*/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È[email protected]$&%^&(*^(_()-*/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

Просто натолкнулся на это, может быть, слишком поздно, но вот что сработало для меня:

 // 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 

Эти ответы не работали так, как ожидалось для меня. В частности, decomposedStringWithCanonicalMapping не разделил акценты / умлауты, как я ожидал.

Вот вариант того, что я использовал, который отвечает на краткое изложение:

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

Решение Питера в Свифт:

 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" 

Я хотел отфильтровать все, кроме букв и цифр, поэтому я адаптировал реализацию Lorean к категории на NSString, чтобы работать немного по-другому. В этом примере вы указываете строку с только символами, которые хотите сохранить, и все остальное отфильтровано:

 @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 

После того, как вы создали свои категории, использование их тривиально, и вы можете использовать их на любом NSString:

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

Или, например, если вы хотите избавиться от всего, кроме гласных:

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

Если вы все еще изучаете Objective-C и не используете категории, я рекомендую вам попробовать их. Они - лучшее место для таких вещей, потому что это дает больше функциональности всем объектам classа, который вы classифицируете.

Категории упрощают и инкапсулируют код, который вы добавляете, что упрощает повторное использование во всех ваших проектах. Это замечательная особенность Objective-C!

  • Индикатор выполнения командной строки в Java
  • Быстрый и простой способ объединения элементов массива с разделителем (противоположность split) в Java
  • Как я могу разделить несколько соединенных слов?
  • Команды командной строки запуска
  • Удалить символы после определенного символа в строке, а затем удалить подстроку?
  • Объявления загружаются, но не отображаются?
  • Как работает конкатенация двух строковых литералов?
  • Java: разделение запятой строки, но игнорирование запятых в кавычках
  • Что означает символ \ 0 в строке C?
  • Вставка символа в определенном месте в строке
  • Как получить первые n символов строки без проверки размера или выхода за пределы?
  • Давайте будем гением компьютера.