NSString - конвертировать только в чистый алфавит (т.е. удалять акценты + пунктуацию) - PullRequest
26 голосов
/ 05 августа 2009

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

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

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

Ответы [ 13 ]

80 голосов
/ 05 августа 2009
NSString* finish = [[start componentsSeparatedByCharactersInSet:[[NSCharacterSet letterCharacterSet] invertedSet]] componentsJoinedByString:@""];
39 голосов
/ 05 августа 2009

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

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

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

* Пример с немецкого. Спасибо Joris Weimar за предоставленную информацию.

14 голосов
/ 04 декабря 2013

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

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

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

7 голосов
/ 10 декабря 2013

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

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

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

7 голосов
/ 26 июля 2012

Одна важная точность по сравнению с ответом 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È!@$&%^&(*^(_()-*/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:@""];
4 голосов
/ 15 февраля 2012

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

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

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

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

4 голосов
/ 05 августа 2009

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

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

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

4 голосов
/ 05 августа 2009

Рассмотрите возможность использования RegexKit framework . Вы можете сделать что-то вроде:

NSString *searchString      = @"This is neat.";
NSString *regexString       = @"[\W]";
NSString *replaceWithString = @"";
NSString *replacedString    = [searchString stringByReplacingOccurrencesOfRegex:regexString withString:replaceWithString];

NSLog (@"%@", replacedString);
//... Thisisneat
3 голосов
/ 29 июня 2011

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

// 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]];
1 голос
/ 02 декабря 2014

Эти ответы не сработали, как я ожидал. В частности, 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;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...