Преобразовать первое число в NSString в целое число?


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

@"200hello"

Или

@"0 something"

Что я хотел бы сделать, так это взять первое встречающееся число в NSString и преобразовать его в int.

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

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

6 4

6 ответов:

int value;
BOOL success = [[NSScanner scannerWithString:@"1000safkaj"] scanInteger:&value];

Если число не всегда находится в начале:

NSCharacterSet* nonDigits = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
int value = [[@"adfsdg1000safkaj" stringByTrimmingCharactersInSet:nonDigits] intValue];

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

Следующие претенденты: (взятые микросекунды и количество байтов, используемых в каждом матче, используя невероятно высокую точность для(x=0; x

править: это были оригинальные номера, опубликованные, смотрите ниже обновленные номера. Кроме того, времена от 2.66 Core2 macbook 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 тоже? Есть ли какой-то приз за избиение туземца, встроенный Методы основания с использованием внешнего, стороннего, неинтегрированного механизма сопоставления шаблонов регулярных выражений общего назначения? Не верьте шумихе о том, что"регексы медленные".

КОНЕЦ ПРАВКИ

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

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

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

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

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

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

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

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

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

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

Правка:

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

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

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

NSString *string = @"123hello";
int myInt = [string intValue];

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

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

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

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

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

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

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

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