Самый эффективный способ перебора всех символов в NSString - PullRequest
49 голосов
/ 11 ноября 2010

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

[aNSString characterAtIndex:index];

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

Ответы [ 8 ]

136 голосов
/ 01 мая 2014

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

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

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

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

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

Для меня решение состоит в том, чтобы использовать перечисление NSString enumerateSubstringsInRange, передав 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

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

Раздел документации по Символам и кластерам графем также может быть полезен для объяснения некоторых из них.

Примечание. Похоже, что некоторые из использованных мною Unicode-строк отключают SO при форматировании в виде кода. Я использовал строки mañana и e magâds.

25 голосов
/ 17 апреля 2011

Ни.Раздел «Оптимизация ваших текстовых манипуляций» «Руководства по производительности Какао» в документации Xcode рекомендует:

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

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

См. Этот ответ переполнения стека приКак удалить пробел из правого конца NSString для примера того, как разрешить rangeOfCharacterFromSet: перебирать символы строки вместо того, чтобы делать это самостоятельно.

19 голосов
/ 11 ноября 2010

Сначала я определенно получу буфер символов, а затем переберу его.

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...
}
2 голосов
/ 27 августа 2014

Хотя технически вы получаете отдельные значения NSString, вот альтернативный подход:

NSRange range = NSMakeRange(0, 1);
for (__unused int i = range.location; range.location < [starring length]; range.location++) {
  NSLog(@"%@", [aNSString substringWithRange:range]);
}

(бит __ неиспользуемый int i необходим для отключения предупреждения компилятора.)

1 голос
/ 11 сентября 2017

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

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

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

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

и в конце используйте

free(buffer);

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

1 голос
/ 17 января 2016

попробуйте перечислить строку с блоками

Создать категорию 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 голосов
/ 24 апреля 2019

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