Самый эффективный способ перебора всех символов в NSString
Какой лучший способ перебрать все символы в NSString? Вы хотите зациклить длину строки и использовать метод.
[aNSString characterAtIndex:index];
или вы хотите использовать буфер символов на основе NSString?
7 ответов
Сначала я определенно получу буфер символов, а затем переберу это.
NSString *someString = ...
unsigned int len = [someString length];
char buffer[len];
//This way:
strncpy(buffer, [someString UTF8String]);
//Or this way (preferred):
[someString getCharacters:buffer range:NSMakeRange(0, len)];
for(int i = 0; i < len; ++i) {
char current = buffer[i];
//do something with current...
}
Я думаю, что важно, чтобы люди понимали, как обращаться с юникодом, поэтому я в итоге написал ответ монстра, но в духе tl;dr я начну с фрагмента, который должен работать нормально. Если вы хотите узнать подробности (что вам следует!), Пожалуйста, продолжайте читать после фрагмента.
NSUInteger len = [str length];
unichar buffer[len+1];
[str getCharacters:buffer range:NSMakeRange(0, len)];
NSLog(@"getCharacters:range: with unichar buffer");
for(int i = 0; i < len; i++) {
NSLog(@"%C", buffer[i]);
}
Все еще со мной? Хорошо!
Текущий принятый ответ, кажется, путает байты с символами / буквами. Это распространенная проблема при обнаружении юникода, особенно на фоне Си. Строки в Objective-C представлены в виде символов Юникода (unichar
), которые намного больше байтов и не должны использоваться со стандартными функциями манипуляции со строками Си.
(Редактировать: это не полная история! К моему большому стыду, я полностью забыл учесть составные символы, где "буква" состоит из нескольких кодовых точек Юникода. Это дает вам ситуацию, когда вы можете иметь одну " письмо ", разрешающее к нескольким unichars, которые в свою очередь являются несколькими байтами каждый. Hoo boy. Пожалуйста, обратитесь к этому великому ответу для деталей об этом.)
Правильный ответ на вопрос зависит от того, хотите ли вы перебирать символы / буквы (в отличие от типа char
) или байты строки (какой тип char
на самом деле значит). В духе ограничения путаницы я буду использовать термины " байт" и " буква", избегая, возможно, двусмысленного термина " символ".
Если вы хотите сделать первое и перебрать буквы в строке, вам нужно иметь дело исключительно с unichars (извините, но мы в будущем, вы не можете больше игнорировать это). Найти количество букв легко, это свойство длины строки. Вот пример фрагмента (такой же, как и выше):
NSUInteger len = [str length];
unichar buffer[len+1];
[str getCharacters:buffer range:NSMakeRange(0, len)];
NSLog(@"getCharacters:range: with unichar buffer");
for(int i = 0; i < len; i++) {
NSLog(@"%C", buffer[i]);
}
Если, с другой стороны, вы хотите перебирать байты в строке, это начинает усложняться, и результат будет полностью зависеть от кодировки, которую вы решите использовать. Приличный выбор по умолчанию - UTF8, так что я покажу это.
Делая это, вы должны выяснить, сколько байтов будет в результате полученной строки UTF8, шаг, на котором легко ошибиться и использовать строку -length
, Одна из основных причин, по которой это очень легко сделать неправильно, особенно для американского разработчика, заключается в том, что строка с буквами, попадающими в 7-битный спектр ASCII, будет иметь одинаковую длину в байтах и буквах. Это связано с тем, что UTF8 кодирует 7-битные буквы ASCII одним байтом, поэтому простая тестовая строка и простой текст на английском языке могут работать отлично.
Правильный способ сделать это - использовать метод -lengthOfBytesUsingEncoding:NSUTF8StringEncoding
(или другое кодирование), выделите буфер такой длины, затем преобразуйте строку в ту же кодировку с -cStringUsingEncoding:
и скопировать его в этот буфер. Пример кода здесь:
NSUInteger byteLength = [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
char proper_c_buffer[byteLength+1];
strncpy(proper_c_buffer, [str cStringUsingEncoding:NSUTF8StringEncoding], byteLength);
NSLog(@"strncpy with proper length");
for(int i = 0; i < byteLength; i++) {
NSLog(@"%c", proper_c_buffer[i]);
}
Просто для того, чтобы понять, почему важно сохранять ясность, я покажу пример кода, который обрабатывает эту итерацию четырьмя различными способами: два неправильных и два правильных. Это код:
#import <Foundation/Foundation.h>
int main() {
NSString *str = @"буква";
NSUInteger len = [str length];
// Try to store unicode letters in a char array. This will fail horribly
// because getCharacters:range: takes a unichar array and will probably
// overflow or do other terrible things. (the compiler will warn you here,
// but warnings get ignored)
char c_buffer[len+1];
[str getCharacters:c_buffer range:NSMakeRange(0, len)];
NSLog(@"getCharacters:range: with char buffer");
for(int i = 0; i < len; i++) {
NSLog(@"Byte %d: %c", i, c_buffer[i]);
}
// Copy the UTF string into a char array, but use the amount of letters
// as the buffer size, which will truncate many non-ASCII strings.
strncpy(c_buffer, [str UTF8String], len);
NSLog(@"strncpy with UTF8String");
for(int i = 0; i < len; i++) {
NSLog(@"Byte %d: %c", i, c_buffer[i]);
}
// Do It Right (tm) for accessing letters by making a unichar buffer with
// the proper letter length
unichar buffer[len+1];
[str getCharacters:buffer range:NSMakeRange(0, len)];
NSLog(@"getCharacters:range: with unichar buffer");
for(int i = 0; i < len; i++) {
NSLog(@"Letter %d: %C", i, buffer[i]);
}
// Do It Right (tm) for accessing bytes, by using the proper
// encoding-handling methods
NSUInteger byteLength = [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
char proper_c_buffer[byteLength+1];
const char *utf8_buffer = [str cStringUsingEncoding:NSUTF8StringEncoding];
// We copy here because the documentation tells us the string can disappear
// under us and we should copy it. Just to be safe
strncpy(proper_c_buffer, utf8_buffer, byteLength);
NSLog(@"strncpy with proper length");
for(int i = 0; i < byteLength; i++) {
NSLog(@"Byte %d: %c", i, proper_c_buffer[i]);
}
return 0;
}
Выполнение этого кода приведет к выводу следующего (с обрезанным символом NSLog), показывающего, насколько точно могут быть представления байтов и букв (два последних вывода):
getCharacters:range: with char buffer
Byte 0: 1
Byte 1:
Byte 2: C
Byte 3:
Byte 4: :
strncpy with UTF8String
Byte 0: Ð
Byte 1: ±
Byte 2: Ñ
Byte 3:
Byte 4: Ð
getCharacters:range: with unichar buffer
Letter 0: б
Letter 1: у
Letter 2: к
Letter 3: в
Letter 4: а
strncpy with proper length
Byte 0: Ð
Byte 1: ±
Byte 2: Ñ
Byte 3:
Byte 4: Ð
Byte 5: º
Byte 6: Ð
Byte 7: ²
Byte 8: Ð
Byte 9: °
Хотя решение Даниэля, вероятно, будет работать большую часть времени, я думаю, что решение зависит от контекста. Например, у меня есть приложение для проверки орфографии, и мне нужно перебирать каждый символ в том виде, в котором он отображается на экране, что может не соответствовать тому, как он представлен в памяти. Это особенно верно для текста, предоставленного пользователем.
Используя что-то вроде этой категории на NSString:
- (void) dumpChars
{
NSMutableArray *chars = [NSMutableArray array];
NSUInteger len = [self length];
unichar buffer[len+1];
[self getCharacters: buffer range: NSMakeRange(0, len)];
for (int i=0; i<len; i++) {
[chars addObject: [NSString stringWithFormat: @"%C", buffer[i]]];
}
NSLog(@"%@ = %@", self, [chars componentsJoinedByString: @", "]);
}
И если дать ему слово, подобное маньяне, это может привести к:
mañana = m, a, ñ, a, n, a
Но это может так же легко привести к:
mañana = m, a, n, ̃, a, n, a
Первый будет получен, если строка находится в предварительно скомпонованной форме Unicode, а второй - если она будет в разложенной форме.
Вы можете подумать, что этого можно избежать, используя результат precomposedStringWithCanonicalMapping NSString или precomposedStringWithCompatibilityMapping, но это не обязательно так, как Apple предупреждает в Технических вопросах и ответах 1225. Например, строка вроде e̊gâds
(который я полностью придумал) все еще производит следующее даже после преобразования в предварительно составленную форму.
e̊gâds = e, ̊, g, â, d, s
Решение для меня состоит в том, чтобы использовать enumerateSubstringsInRange NSString, передавая NSStringEnumerationByComposedCharacterSequence в качестве опции перечисления. Переписать предыдущий пример так:
- (void) dumpSequences
{
NSMutableArray *chars = [NSMutableArray array];
[self enumerateSubstringsInRange: NSMakeRange(0, [self length]) options: NSStringEnumerationByComposedCharacterSequences
usingBlock: ^(NSString *inSubstring, NSRange inSubstringRange, NSRange inEnclosingRange, BOOL *outStop) {
[chars addObject: inSubstring];
}];
NSLog(@"%@ = %@", self, [chars componentsJoinedByString: @", "]);
}
Если мы будем кормить эту версию e̊gâds
тогда мы получим
e̊gâds = e̊, g, â, d, s
как и ожидалось, чего я и хочу.
Раздел документации по персонажам и графемным кластерам также может помочь объяснить некоторые из них.
Примечание. Похоже, что некоторые из использованных мною юникодных строк отключаются SO при форматировании в виде кода. Строки, которые я использовал, - это mañana и e̊gâds.
Ни. Раздел "Оптимизация ваших текстовых манипуляций" "Руководства по производительности Какао" в Документации Xcode рекомендует:
Если вы хотите перебрать символы строки, вам не следует использовать
characterAtIndex:
метод для получения каждого символа в отдельности. Этот метод не предназначен для повторного доступа. Вместо этого рассмотрите выборку символов сразу, используяgetCharacters:range:
метод и перебирая байты напрямую.Если вы хотите найти в строке определенные символы или подстроки, не перебирайте символы один за другим. Вместо этого используйте методы более высокого уровня, такие как
rangeOfString:
,rangeOfCharacterFromSet:
, или жеsubstringWithRange:
, которые оптимизированы для поискаNSString
персонажи.
Смотрите этот ответ переполнения стека на Как удалить пробелы с правого концаNSString
для примера того, как позволить rangeOfCharacterFromSet:
перебирайте символы строки вместо того, чтобы делать это самостоятельно.
Это немного другое решение вопроса, но я подумал, может быть, это кому-нибудь пригодится. То, что я хотел, было на самом деле повторять как фактический символ Unicode в NSString. Итак, я нашел это решение:
NSString * str = @"hello ";
NSRange range = NSMakeRange(0, str.length);
[str enumerateSubstringsInRange:range
options:NSStringEnumerationByComposedCharacterSequences
usingBlock:^(NSString *substring, NSRange substringRange,
NSRange enclosingRange, BOOL *stop)
{
NSLog(@"%@", substring);
}];
Попробуйте перечислить строку с блоками
Создать категорию NSString
.час
@interface NSString (Category)
- (void)enumerateCharactersUsingBlock:(void (^)(NSString *character, NSInteger idx, bool *stop))block;
@end
.m
@implementation NSString (Category)
- (void)enumerateCharactersUsingBlock:(void (^)(NSString *character, NSInteger idx, bool *stop))block
{
bool _stop = NO;
for(NSInteger i = 0; i < [self length] && !_stop; i++)
{
NSString *character = [self substringWithRange:NSMakeRange(i, 1)];
block(character, i, &_stop);
}
}
@end
пример
NSString *string = @"Hello World";
[string enumerateCharactersUsingBlock:^(NSString *character, NSInteger idx, bool *stop) {
NSLog(@"char %@, i: %li",character, (long)idx);
}];
Вы не должны использовать
NSUInteger len = [str length];
unichar buffer[len+1];
Вы должны использовать выделение памяти
NSUInteger len = [str length];
unichar* buffer = (unichar*) malloc (len+1)*sizeof(unichar);
и в конце концов использовать
free(buffer);
во избежание проблем с памятью.
Хотя технически вы получите отдельные значения NSString, вот альтернативный подход:
NSRange range = NSMakeRange(0, 1);
for (__unused int i = range.location; range.location < [starring length]; range.location++) {
NSLog(@"%@", [aNSString substringWithRange:range]);
}
(Бит __unused int i необходим для отключения предупреждения компилятора.)