Самый эффективный способ перебора всех символов в NSString

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

[aNSString characterAtIndex:index];

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

Ответ 1

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

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

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

(Изменить): Это не полная история! К моему большому стыду, я полностью забыл учитывать составные символы, где "письмо" состоит из нескольких кодовых страниц юникода. дает вам ситуацию, когда у вас может быть одно "письмо", разрешающее несколько unichars, которые, в свою очередь, имеют несколько байтов каждый. Hoo boy. Пожалуйста, обратитесь к этому отличному ответу подробности об этом.)

Правильный ответ на вопрос зависит от того, хотите ли вы перебирать символы/буквы (в отличие от типа 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 cruft), показывающим точно, КАК разные байтовые и буквенные представления могут быть (два последних выхода):

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: °

Ответ 3

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

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

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

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

Ответ 4

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

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

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

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

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

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

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

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

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

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

Ответ 5

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

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

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

Ответ 6

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

Создать категорию 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);
}];

Ответ 7

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

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

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

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

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

free(buffer);

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

Ответ 8

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