Преобразовать первое число в NSString в целое число? - PullRequest
4 голосов
/ 16 июля 2009

У меня есть строка NSString так:

@"200hello"

или

@"0 something"

Я хотел бы иметь возможность взять первое встречающееся число в NSString и преобразовать его в целое число.

Так что @ "200hello" станет int = 200.

и @ "0 что-то" станет int = 0.

Ответы [ 6 ]

30 голосов
/ 16 июля 2009
int value;
BOOL success = [[NSScanner scannerWithString:@"1000safkaj"] scanInteger:&value];

Если номер не всегда в начале:

NSCharacterSet* nonDigits = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
int value = [[@"adfsdg1000safkaj" stringByTrimmingCharactersInSet:nonDigits] intValue];
19 голосов
/ 17 июля 2009

Стив Сиарсия однажды сказал, что один измеренный результат стоит более сотни мнений инженеров. И так начинается первый и последний раздел «Как получить значение int из NSString»!

Ниже приведены претенденты: (взятые микросекунды и количество байтов, использованных на совпадение с использованием невероятно высокой точности для (x = 0; x <100000; x ++) {} микропроцессора, передаваемого из поколения в поколение. Время измеряется с помощью getrusage (), байты используются с помощью malloc_size (). Соответствующая строка была нормализована до 'foo 2020hello' для всех случаев, кроме тех, которые требовали, чтобы число было в начале. Все преобразования были нормализованы до 'int' . Два числа после времени являются нормированными результатами относительно лучших и худших показателей.) </p>

РЕДАКТИРОВАТЬ : Это были опубликованные исходные номера, обновленные номера приведены ниже. Кроме того, время от MacBook Pro 2.66 Core 2. Pro

characterSet   time: 1.36803us 12.5 / 1.00 memory: 64 bytes (via Nikolai Ruhe)
original RKL   time: 1.20686us 11.0 / 0.88 memory: 16 bytes (via Dave DeLong)
modified RKL   time: 1.07631us  9.9 / 0.78 memory: 16 bytes (me, changed regex to \d+)
scannerScanInt time: 0.49951us  4.6 / 0.36 memory: 32 bytes (via Nikolai Ruhe)
intValue       time: 0.16739us  1.5 / 0.12 memory:  0 bytes (via zpasternack)
rklIntValue    time: 0.10925us  1.0 / 0.08 memory:  0 bytes (me, modified RKL example)

Как я уже отмечал в этом сообщении, я изначально добавил это в жгут для модульного тестирования, который я использую для RegexKitLite. Что ж, то, что я использую модульное тестирование, означало, что я тестировал свою личную копию RegexKitLite ... в которой, как оказалось, была отлажена куча отладочных материалов при отслеживании сообщения об ошибке от пользователя. Приведенные выше результаты синхронизации примерно эквивалентны вызову [valueString flushCachedRegexData]; внутри цикла синхронизации for () {} (что, по сути, и было сделано непреднамеренным способом отладки). Следующие результаты получены при компиляции с использованием новейшего неизмененного RegexKitLite (3.1):

characterSet   time: 1.36803us 12.5 / 1.00 memory: 64 bytes (via Nikolai Ruhe)
original RKL   time: 0.58446us  5.3 / 0.43 memory: 16 bytes (via Dave DeLong)
modified RKL   time: 0.54628us  5.0 / 0.40 memory: 16 bytes (me, changed regex to \d+)
scannerScanInt time: 0.49951us  4.6 / 0.36 memory: 32 bytes (via Nikolai Ruhe)
intValue       time: 0.16739us  1.5 / 0.12 memory:  0 bytes (via zpasternack)
rklIntValue    time: 0.10925us  1.0 / 0.08 memory:  0 bytes (me, modified RKL example)

Это немного лучше, чем улучшение на 50%. Если вы готовы жить немного опасно, вы можете немного ускорить процесс с опцией -DRKL_FAST_MUTABLE_CHECK время компиляции:

original RKL   time: 0.51188us  4.7 / 0.37 memory: 16 bytes using intValue
modified RKL   time: 0.47665us  4.4 / 0.35 memory: 16 bytes using intValue
original RKL   time: 0.44337us  4.1 / 0.32 memory: 16 bytes using rklIntValue
modified RKL   time: 0.42128us  3.9 / 0.31 memory: 16 bytes using rklIntValue

Обычно это хорошо для увеличения еще на 10%, и его довольно безопасно использовать (для получения дополнительной информации см. Документацию по RKL). И пока я занимался этим ... почему бы не использовать более быстрый rklIntValue? Есть ли какой-то приз за то, что вы победили нативные встроенные методы Foundation с использованием внешнего стороннего неинтегрированного механизма сопоставления с регулярными выражениями общего назначения? Не верьте обману, что «регулярные выражения медленны».

КОНЕЦ РЕДАКТИРОВАНИЯ

Пример RegexKitLite можно найти по адресу Быстрое шестнадцатеричное преобразование RegexKitLite . В основном поменял strtoimax на strtol и добавил строку кода для пропуска ведущих символов, которые не были [+ -0-9]. (полное раскрытие: я автор RegexKitLite)

Как 'scannerScanInt', так и 'intValue' страдают от проблемы, заключающейся в том, что извлекаемое число должно быть в начале строки. Я думаю, что оба пропустят любой ведущий пробел.

Я изменил регулярное выражение Дэйва Делонга с '[^ \ d] * (\ d +)' на просто '\ d +', потому что это все, что действительно нужно, и ему удается избавиться от использования группы захвата для загрузки.

Итак, исходя из приведенных выше данных, я предлагаю следующие рекомендации:

Здесь есть в основном два разных класса возможностей: те, которые могут допустить дополнительные «вещи» и все же получить вам число (персонажи соответствия, RegexKitLite и rklIntValue), и те, которые в основном нуждаются в числе, чтобы быть самым первым в строка, допускающая не более некоторого заполнения пробелов в начале (scannerScanInt и intValue).

Не используйте NSCharacterClass для подобных вещей. В данном примере 16 байтов используются для создания первого NSCharacterClass, затем 32 байта для инвертированной версии и, наконец, 16 байтов для строкового результата. Тот факт, что движок регулярных выражений общего назначения превосходит его с двузначным процентным запасом при использовании меньшего количества памяти, в значительной степени закрывает дело.

(имейте в виду, что я написал RegexKitLite, поэтому возьмите следующее с зерном соли любого размера, которое вы считаете подходящим).

RegexKitLite превращается в хорошие времена и использует наименьший возможный объем памяти, учитывая тот факт, что он возвращает объект NSString. Поскольку он использует внутреннюю кэш-память LRU для всех компонентов механизма регулярных выражений ICU, эти затраты амортизируются с течением времени и повторяются. Также потребуется несколько секунд, чтобы изменить регулярное выражение, если возникнет необходимость (шестнадцатеричные значения? Hex float? Валюта? Даты? Нет проблем.)

Для простых сопоставителей должно быть очевидно, что вам определенно НЕ следует использовать NSScanner для подобных вещей. Использование NSScanner для выполнения 'scanInt:' ничем не отличается от простого вызова [aString intValue]. Производят одинаковые результаты с одинаковыми оговорками. Разница в том, что NSScanner тратит на ПЯТЬ раз больше одной и той же вещи, тратя впустую 32 байта памяти в процессе .... в то время как [aString intValue] (вероятно) не требует одного байта памяти для выполнения своей магии - он, вероятно, просто вызывает strtoimax () (или эквивалентный) и, поскольку он имеет прямой доступ к указателю, содержащему содержимое строк ....

Последним является rklIntValue, который опять-таки представляет собой слегка измененную версию того, что вы можете найти по ссылке (ссылка на быстрое шестнадцатеричное преобразование RegexKitLite выше, stackoverflow не позволит мне опубликовать его дважды). Он использует CoreFoundation, чтобы попытаться получить прямой доступ к буферу строк, и в противном случае выделяет некоторое пространство из стека и копирует часть строки в этот буфер. Это занимает все, о-о, три инструкции на процессоре и принципиально невозможно «просочиться», как распределение malloc (). Так что он использует нулевую память и работает очень и очень быстро. В качестве дополнительного бонуса вы передаете strtoXXX () числовую базу строки для преобразования. 10 для десятичной дроби, 16 для шестнадцатеричной (автоматически проглатывая ведущий 0x, если присутствует), или 0 для автоматического определения. Это тривиальная строка кода, позволяющая пропустить указатель на любые «неинтересные» символы, пока вы не доберетесь до нужного (я выбираю -, + и 0-9). Также тривиально поменять местами что-то вроде strtod (), если вам нужно разобрать двойные значения. strtod () преобразует практически любой допустимый текст с плавающей запятой: NAN, INF, шестнадцатеричные числа с плавающей запятой, вы называете это.

EDIT:

В соответствии с запросом OP вот урезанная и уменьшенная версия кода, который я использовал для выполнения тестов. Одна вещь, которая стоит заметить: собирая все это вместе, я заметил, что оригинальное регулярное выражение Dave DeLongs не совсем работало. Проблема заключается в том, что отрицательные наборы символов - последовательности мета-символов внутри наборов (т. Е. [^ \ D] +) означают буквальный символ, а не специальное значение, которое они имеют вне набора символов. Заменяется на [^ \ p {DecimalNumber}] *, что дает ожидаемый эффект.

Изначально я прикрутил этот материал к жгуту для тестирования модулей RegexKitLite, поэтому я оставил несколько кусочков для GC. Я забыл обо всем этом, но краткая версия того, что происходит при включении GC, - это время всего, НО RegexKitLite double (то есть занимает в два раза больше времени). RKL длится примерно на 75% дольше (и это потребовало огромных, нетривиальных усилий, когда я его разрабатывал). Время rklIntValue остается неизменным.

Компилировать с

shell% gcc -DNS_BLOCK_ASSERTIONS -mdynamic-no-pic -std=gnu99 -O -o stackOverflow stackOverflow.m RegexKitLite.m -framework Foundation -licucore -lauto
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>
#include <stdint.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <objc/objc-auto.h>
#include <malloc/malloc.h>

#import <Foundation/Foundation.h>
#import "RegexKitLite.h"

static double cpuTimeUsed(void);
static double cpuTimeUsed(void) {
  struct rusage currentRusage;

  getrusage(RUSAGE_SELF, &currentRusage);
  double userCPUTime   = ((((double)currentRusage.ru_utime.tv_sec) * 1000000.0) + ((double)currentRusage.ru_utime.tv_usec)) / 1000000.0;
  double systemCPUTime = ((((double)currentRusage.ru_stime.tv_sec) * 1000000.0) + ((double)currentRusage.ru_stime.tv_usec)) / 1000000.0;
  double CPUTime = userCPUTime + systemCPUTime;
  return(CPUTime);
}

@interface NSString (IntConversion)
-(int)rklIntValue;
@end

@implementation NSString (IntConversion)

-(int)rklIntValue
{
  CFStringRef cfSelf = (CFStringRef)self;
  UInt8 buffer[64];
  const char *cptr, *optr;
  char c;

  if((cptr = optr = CFStringGetCStringPtr(cfSelf, kCFStringEncodingMacRoman)) == NULL) {
    CFRange range     = CFRangeMake(0L, CFStringGetLength(cfSelf));
    CFIndex usedBytes = 0L;
    CFStringGetBytes(cfSelf, range, kCFStringEncodingUTF8, '?', false, buffer, 60L, &usedBytes);
    buffer[usedBytes] = 0U;
    cptr = optr       = (const char *)buffer;
  }

  while(((cptr - optr) < 60) && (!((((c = *cptr) >= '0') && (c <= '9')) || (c == '-') || (c == '+'))) ) { cptr++; }
  return((int)strtoimax(cptr, NULL, 0));
}

@end

int main(int argc __attribute__((unused)), char *argv[] __attribute__((unused))) {
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

#ifdef __OBJC_GC__
  objc_start_collector_thread();
  objc_clear_stack(OBJC_CLEAR_RESIDENT_STACK);
  objc_collect(OBJC_EXHAUSTIVE_COLLECTION | OBJC_WAIT_UNTIL_DONE);
#endif

  BOOL gcEnabled = ([objc_getClass("NSGarbageCollector") defaultCollector] != NULL) ? YES : NO;
  NSLog(@"Garbage Collection is: %@", gcEnabled ? @"ON" : @"OFF");
  NSLog(@"Architecture: %@", (sizeof(void *) == 4UL) ? @"32-bit" : @"64-bit");

  double      startTime = 0.0, csTime = 0.0, reTime = 0.0, re2Time = 0.0, ivTime = 0.0, scTime = 0.0, rklTime = 0.0;
  NSString   *valueString = @"foo 2020hello", *value2String = @"2020hello";
  NSString   *reRegex = @"[^\\p{DecimalNumber}]*(\\d+)", *re2Regex = @"\\d+";
  int         value = 0;
  NSUInteger  x = 0UL;

  {
    NSCharacterSet *digits      = [NSCharacterSet decimalDigitCharacterSet];
    NSCharacterSet *nonDigits   = [digits invertedSet];
    NSScanner      *scanner     = [NSScanner scannerWithString:value2String];
    NSString       *csIntString = [valueString stringByTrimmingCharactersInSet:nonDigits];
    NSString       *reString    = [valueString stringByMatching:reRegex capture:1L];
    NSString       *re2String   = [valueString stringByMatching:re2Regex];

    [scanner scanInt:&value];

    NSLog(@"digits      : %p, size: %lu", digits, malloc_size(digits));
    NSLog(@"nonDigits   : %p, size: %lu", nonDigits, malloc_size(nonDigits));
    NSLog(@"scanner     : %p, size: %lu, int: %d", scanner, malloc_size(scanner), value);
    NSLog(@"csIntString : %p, size: %lu, '%@' int: %d", csIntString, malloc_size(csIntString), csIntString, [csIntString intValue]);
    NSLog(@"reString    : %p, size: %lu, '%@' int: %d", reString, malloc_size(reString), reString, [reString intValue]);
    NSLog(@"re2String   : %p, size: %lu, '%@' int: %d", re2String, malloc_size(re2String), re2String, [re2String intValue]);
    NSLog(@"intValue    : %d", [value2String intValue]);
    NSLog(@"rklIntValue : %d", [valueString rklIntValue]);
  }

  for(x = 0UL, startTime = cpuTimeUsed(); x < 100000UL; x++) { value = [[valueString stringByTrimmingCharactersInSet:[[NSCharacterSet decimalDigitCharacterSet] invertedSet]] intValue]; } csTime = (cpuTimeUsed() - startTime) / (double)x;
  for(x = 0UL, startTime = cpuTimeUsed(); x < 100000UL; x++) { value =  [[valueString stringByMatching:reRegex capture:1L] intValue]; } reTime = (cpuTimeUsed() - startTime) / (double)x;
  for(x = 0UL, startTime = cpuTimeUsed(); x < 100000UL; x++) { value =  [[valueString stringByMatching:re2Regex] intValue]; } re2Time = (cpuTimeUsed() - startTime) / (double)x;
  for(x = 0UL, startTime = cpuTimeUsed(); x < 100000UL; x++) { value =  [valueString rklIntValue]; } rklTime = (cpuTimeUsed() - startTime) / (double)x;
  for(x = 0UL, startTime = cpuTimeUsed(); x < 100000UL; x++) { value = [value2String intValue]; } ivTime = (cpuTimeUsed() - startTime) / (double)x;
  for(x = 0UL, startTime = cpuTimeUsed(); x < 100000UL; x++) { [[NSScanner scannerWithString:value2String] scanInt:&value]; } scTime = (cpuTimeUsed() - startTime) / (double)x;

  NSLog(@"csTime : %.5lfus", csTime * 1000000.0);
  NSLog(@"reTime : %.5lfus", reTime * 1000000.0);
  NSLog(@"re2Time: %.5lfus", re2Time * 1000000.0);
  NSLog(@"scTime : %.5lfus", scTime * 1000000.0);
  NSLog(@"ivTime : %.5lfus", ivTime * 1000000.0);
  NSLog(@"rklTime: %.5lfus", rklTime * 1000000.0);

  [NSString clearStringCache];
  [pool release]; pool = NULL;

  return(0);
}
8 голосов
/ 16 июля 2009

Если значение int всегда находится в начале строки, вы можете просто использовать intValue.

NSString *string = @"123hello";
int myInt = [string intValue];
3 голосов
/ 16 июля 2009

Возможно, я бы использовал регулярное выражение (реализовано с помощью звездного RegexKitLite ). Тогда это будет что-то вроде:

#import "RegexKitLite.h"
NSString * original = @"foo 220hello";
NSString * number = [original stringByMatching:@"[^\\d]*(\\d+)" capture:1];
return [number integerValue];

Регулярное выражение @ "[^ \ d] * (\ d +)" означает "любое количество нечисловых символов, за которыми следует хотя бы один числовой символ".

0 голосов
/ 11 апреля 2012
int i;
NSString* string;
i = [string intValue];
0 голосов
/ 17 июля 2009

Я пришел с собственным ответом, потенциально быстрее и проще, чем другие.

Мой ответ предполагает, что вы знаете позицию, с которой начинается и заканчивается число ...

NSString *myString = @"21sss";
int numberAtStart = [[myString substringToIndex:2] intValue];

Вы можете работать и другим способом:

NSString *myString = @"sss22";
int numberAtEnd = [[myString substringFromIndex:3] intValue];
...