2010-11-11 4 views
45

Каков наилучший способ перебора всех символов в NSString? Вы хотите перебрать длину строки и использовать этот метод.Самый эффективный способ перебора всех символов в NSString

[aNSString characterAtIndex:index]; 

или вы хотите, чтобы пользовательский буфер символов основывался на NSString?

ответ

20

Я бы выбрал сначала буфер символов, а затем перебираю его.

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... 
} 
+15

Это правильный способ сделать это, но стоит иметь в виду, что любое преобразование символов в NSString вдоль этих строк будет затрагивать несколько очень сложных фреймов с многобайтовым текстом, и его лучше избегать, когда вообще возможное. (И просто использовать UTF-16 или UTF-32, к сожалению, недостаточно, чтобы обойти все проблемы международного текста, хотя он будет снимать ваши требования к памяти на Луну.) – Chuck

+0

@Chuck, Fair point. –

+0

Зачем вам делать буфер символов? – ma11hew28

24

Ни то, ни другое. "Optimize Your Text Manipulations" section of the "Cocoa Performance Guidelines" in the Xcode Documentation рекомендует:

Если вы хотите перебрать в символов строки, одна из вещей, которые вы не должны делать это с помощью метода characterAtIndex: для получения каждого символа отдельно. Этот метод не предназначен для многократного доступа. Вместо этого рассмотрите выборки всех символов с помощью метода getCharacters:range: и , итерации по байтам напрямую.

Если вы хотите найти строку для определенных символов или подстрок, сделать не перебирать символы один один. Вместо этого следует использовать более высокий уровень методы, такие как rangeOfString:, rangeOfCharacterFromSet: или substringWithRange:, которые оптимизированы для поиска в NSString символов.

Смотрите это Stack Overflow answer on How to remove whitespace from right end of NSString для примера того, как позволить rangeOfCharacterFromSet: итерации над символами строки вместо того, чтобы делать это самостоятельно.

122

Я думаю, что очень важно, чтобы люди понимали, как бороться с юникодом, поэтому я написал ответ монстра, но в духе 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]); 
} 

Еще со мной? Хорошо!

Нынешний принятый ответ, похоже, путает байты с символами/буквами. Это обычная проблема при встрече с unicode, особенно на фоне C. Строки в Objective-C представлены как символы Unicode (unichar), которые намного больше, чем байты, и не должны использоваться со стандартными функциями управления строкой C.

(Edit:. Это не полная история К моему великому стыду, я совершенно забыл объяснить компонуемые персонаж, где «буква» состоит из нескольких кодовых Юникода Это дает вам ситуация, когда у вас может быть одна «буква», разрешающая несколько unichars, которая, в свою очередь, состоит из нескольких байтов. Hoo boy. Подробнее об этом см. на this great answer.)

Правильный ответ на этот вопрос зависит от того, хотите ли вы, чтобы перебрать символов/букв (в отличие от типа char) или байт строки (что тип char фактически означает) , В духе ограничения путаницы я буду использовать термины байт и письмо с этого момента, избегая, возможно, двусмысленного термина character.

Если вы хотите сделать первое и перебрать буквы в строке, вам нужно иметь дело исключительно с 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: ° 
+15

Почему это не лучший ответ в SO в этом году? Почему у этого не больше? Почему никто не построил статую в память о Данииле? Почему в мире так много несправедливости ?! – Morpheu5

+0

Удивительный ответ и объяснение! Просто любопытно, почему вы добавляете +1 при выполнении следующего: 'unichar buffer [len + 1];' – KingPolygon

+1

Чтобы оставить место для нулевого терминатора. :) –

2

Хотя бы технически получать значения индивидуального 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 бит необходим, чтобы отключить предупреждение компилятора.)

+0

Или просто 'for (; range.location <[starring length]; range.location ++)', нет необходимости в '__unused int i'. – mojuba

22

Хотя решение Дэниела, вероятно, будет работать большую часть времени, я думаю, что решение зависит от контекста. Например, у меня есть приложение для орфографии и вам нужно перебирать каждый символ, поскольку он появляется на экране, что может не соответствовать тому, как оно представлено в памяти. Это особенно верно для текста, предоставленного пользователем.

Используя что-то вроде этой категории на 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 

Первый будет создан, если строка находится в предварительно объединенной форме юникода, а позже, если она находится в декоре .

Возможно, вы могли бы избежать этого, используя результат предварительного сопоставления NSStringStringWithCanonicalMapping или precomposedStringWithCompatibilityMapping, но это не обязательно так, как Apple предупреждает в Technical Q&A 1225. Например, строка, такая как e̊gâds (которая я полностью выполнила), все еще производит следующее даже после преобразования в предварительно сложенную форму.

e̊gâds = e, ̊, g, â, d, s 

Раствор для меня использовать enumerateSubstringsInRange проходящего NSStringEnumerationByComposedCharacterSequences NSString как вариант перечисления. Переписав предыдущий пример, чтобы выглядеть следующим образом:

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

, как и ожидалось, что это то, что я хочу.

Раздел документации на Characters and Grapheme Clusters также может быть полезен при объяснении этого.

Примечание. Похоже, что некоторые из строк юникода, которые я использовал, отключены, когда форматируются как код. Струны, которые я использовал, - манана, и e̊gâds.

+1

О, дорогая, я совсем забыл о композитных персонажах. Я добавил примечание и ссылку на ваш ответ в своем. Думаю, урок здесь: текст тяжелый, чуваки. –

1

попытка перечисления строки с блоками

Создать категорию из NSString

.h

@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); 
}]; 
0

Вы не должны использовать

NSUInteger len = [str length]; 
unichar buffer[len+1]; 

вы должны использовать выделение памяти

NSUInteger len = [str length]; 
unichar* buffer = (unichar*) malloc (len+1)*sizeof(unichar); 

и конечного использования

free(buffer); 

для того, чтобы избежать проблем с памятью.