web-dev-qa-db-ja.com

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

ただし、これを何百もの文字列に対して行う必要があり、これをより効率的にする必要があります。何か案は?

26
deelo
NSString* finish = [[start componentsSeparatedByCharactersInSet:[[NSCharacterSet letterCharacterSet] invertedSet]] componentsJoinedByString:@""];
80
Peter N Lewis

これらのソリューションを使用する前に、アクセント付きの文字を分解するためにdecomposedStringWithCanonicalMappingを使用することを忘れないでください。これにより、たとえば、é(U + 00E9)がe‌́(U + 0065 U + 0301)に変わります。次に、英数字以外の文字を削除すると、アクセントのない文字が残ります。

これが重要である理由は、たとえば「dän」と「dün」*を同じものとして扱いたくないからです。これらの解決策のいくつかが行うように、アクセント付きの文字をすべて削除すると、「dn」になってしまうため、これらの文字列は等しいものとして比較されます。

したがって、最初にそれらを分解して、アクセントを取り除き、文字を残すことができるようにする必要があります。

*ドイツ語からの例。それを提供してくれたJorisWeimarに感謝します。

39
Peter Hosey

同様の質問で、Ole BegemannはstringByFoldingWithOptions: の使用を提案しており、これがここでの最善の解決策であると信じています。

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

変換する文字列の性質によっては、ユーザーの現在のロケールを使用する代わりに、固定ロケール(英語など)を設定することをお勧めします。そうすれば、すべてのマシンで同じ結果を確実に得ることができます。

14
Sophie Alpert

文字列を比較しようとしている場合は、これらの方法のいずれかを使用してください。データを変更しようとしないでください。

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

さらに、最初に文字列を正規化して(Peter Hoseyの投稿を参照)、事前構成または分解し、基本的に正規化された形式を選択します。

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

いいえ、私たちが考えるほど単純で簡単ではありません。はい、情報に基づいた慎重な意思決定が必要です。 (そして英語以外の言語の経験が少し役立ちます)

7
uchuugaka

BillyTheKid18756の回答に対する1つの重要な精度(Luizによって修正されましたが、コードの説明では明らかではありませんでした):

使用しないでくださいstringWithCStringアクセントを削除する2番目のステップとして、NSDataがNULLで終了していないため(stringWithCStringが予期するように)、文字列の最後に不要な文字を追加できます。または、Luizがコードで行ったように、それを使用してNSDataにNULLバイトを追加します。

より簡単な答えは、置き換えることだと思います。

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

ルイスとピーターの回答を組み合わせて数行追加することで完全な例を示すために、以下のコードを取得します。

コードは次のことを行います:

  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
Vegard

RegexKitフレームワーク の使用を検討してください。あなたは次のようなことをすることができます:

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

NSLog (@"%@", replacedString);
//... Thisisneat
4
Alex Reynolds

NSScanner 、具体的にはメソッド -setCharactersToBeSkipped: (NSCharacterSetを受け入れる)および -scanString:intoString: (文字列であり、スキャンされた文字列を参照によって返します)。

これを -[NSString localizedCompare:] と組み合わせたり、 -[NSString compare:options:]NSDiacriticInsensitiveSearch オプションを組み合わせたりすることもできます。これにより、アクセントの削除/置換が簡単になるため、句読点や空白などの削除に集中できます。

質問で提示したようなアプローチを使用する必要がある場合は、少なくともNSMutableStringとreplaceOccurrencesOfString:withString:options:range:を使用してください。これは、ほぼ同一の自動解放された文字列を大量に作成するよりもはるかに効率的です。割り当ての数を減らすだけで、当面は「十分に」パフォーマンスが向上する可能性があります。

4
Quinn Taylor

これにぶつかっただけで、おそらく手遅れですが、これが私のために働いたものです:

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

これらの答えは私にとって期待通りに機能しませんでした。具体的には、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;
1
Tricky
@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
1
lorean

SwiftでのPeterのソリューション:

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"
0
Babac