将NSString中的第一个数字转换为整数?

Bro*_*olf 4 cocoa cocoa-touch objective-c nsstring

我有一个像这样的NSString:

@"200hello"
Run Code Online (Sandbox Code Playgroud)

要么

@"0 something"
Run Code Online (Sandbox Code Playgroud)

我想要做的是获取NSString中的第一个出现的数字并将其转换为int.

所以@"200hello"会变成int = 200.

和@"0 something"将成为int = 0.

Nik*_*uhe 30

int value;
BOOL success = [[NSScanner scannerWithString:@"1000safkaj"] scanInteger:&value];
Run Code Online (Sandbox Code Playgroud)

如果数字不总是在开头:

NSCharacterSet* nonDigits = [[NSCharacterSet decimalDigitCharacterSet] invertedSet];
int value = [[@"adfsdg1000safkaj" stringByTrimmingCharactersInSet:nonDigits] intValue];
Run Code Online (Sandbox Code Playgroud)


joh*_*hne 19

Steve Ciarcia曾经说过,单个测量结果的价值超过了100个工程师的意见.所以从第一个开始,最后一个,"如何从NSString获取int值"烹饪!

以下是竞争者:(每次匹配使用的微秒数和使用令人难以置信的高精度的(x = 0; x <100000; x ++){}微基准,这些微基准已经代代相传. getrusage(),通过malloc_size()使用的字节.对于所有情况,要匹配的字符串被标准化为'foo 2020hello',除了那些需要数字在开始的情况.所有转换都标准化为'int'.时间之后的数字是相对于最佳和最差表现者的标准化结果.)

编辑:这些是发布的原始数字,请参阅下面的更新数字.此外,时间来自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)
Run Code Online (Sandbox Code Playgroud)

正如我在此消息中的其他地方所指出的那样,我最初将其投入到我用于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)
Run Code Online (Sandbox Code Playgroud)

这略好于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
Run Code Online (Sandbox Code Playgroud)

这通常有利于大约10%的提升,并且使用起来相当安全(有关更多信息,请参阅RKL文档).虽然我在它...为什么不使用更快的rklIntValue?是否使用外部的第三方非集成通用正则表达式模式匹配引擎击败本机,内置的Foundation方法获得某种奖励?不要相信"正则表达很慢"的炒作.

结束编辑

可以在RegexKitLite快速十六进制转换中找到RegexKitLite示例.基本上交换了strtol的strtoimax,并添加了一行代码来跳过不是[+ -0-9]的主要字符.(完全披露:我是RegexKitLite的作者)

'scannerScanInt'和'intValue'都存在这样的问题,即要提取的数字必须位于字符串的开头.我认为两者都会跳过任何领先的白色空间.

我将Dave DeLongs正则表达式从'[^\d]*(\ d +)'修改为'\ d +',因为这就是所有真正需要的,它设法摆脱了一个捕获组使用来启动.

因此,根据以上数据,我提出以下建议:

这里基本上有两种不同的功能类:那些能够容忍额外'东西'并且仍然可以获得数字(characterSet,RegexKitLite匹配器和rklIntValue)的那些,以及那些基本上需要数字作为字符串中的第一个东西,容忍在开始时最多一些空格填充(scannerSanInt和intValue).

不要使用NSCharacterClass来做这些事情.对于给定的示例,16个字节用于实例化第一个NSCharacterClass,然后是32个字节用于反转版本,最后16个字节用于字符串结果.事实上,通用正则表达式引擎在使用较少内存的同时以超过两位数百分比的优势超过它,这几乎可以达成交易.

(请记住,我写过RegexKitLite,所以请注意以下适合的大小盐粒).

考虑到它返回一个NSString对象这一事实,RegexKitLite转好时并使用尽可能少的内存.由于它在内部对所有ICU正则表达式引擎内容使用LRU缓存,因此这些成本会随着时间的推移和重复使用而摊销.如果需要,还需要几秒钟才能更改正则表达式(十六进制值?十六进制浮点数?货币?日期?没问题.)

对于简单的匹配器,显然你绝对不应该使用NSScanner来做这些事情.使用NSScanner执行'scanInt:'与调用[aString intValue]没什么区别.产生相同的结果具有相同的警告.不同之处在于NSScanner在相同的事情上花了五倍的时间,同时在这个过程中浪费了32个字节的内存....而[aString intValue](可能)不需要一个字节的内存来执行它的魔法 - 它可能只是调用strtoimax()(或等效的),因为它可以直接访问包含字符串内容的指针....

最后一个是'rklIntValue',它只是你可以找到的一个稍微调整过的版本(上面的'RegexKitLite Fast Hex Conversion'链接,stackoverflow不允许我发布两次).它使用CoreFoundation尝试直接访问字符串缓冲区,如果失败,则从堆栈中分配一些空间并将一大块字符串复制到该缓冲区.这需要CPU上的所有三个指令,并且基本上不可能像malloc()分配那样"泄漏".所以它使用零内存,非常非常快.作为额外的奖励,您传递给strtoXXX()要转换的字符串的数字基数.十进制10,十六进制16(如果存在则自动吞下前导0x),或0表示自动检测.这是一个简单的单行代码,可以跳过任何"无趣"角色的指针,直到你得到你想要的东西(我选择 - ,+和0-9).如果你需要解析双值,也很容易交换像strtod()这样的东西.strtod()只转换任何有效的浮点文本:NAN,INF,hex浮动,你可以命名它.

编辑:

根据OP的请求,这是我用来执行测试的修剪和缩小版本的代码.需要注意的是:在将这些放在一起的时候,我注意到Dave DeLongs的原始正则表达并没有完全奏效.问题在于否定的字符集 - 集合中的元字符序列(即,[^\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
Run Code Online (Sandbox Code Playgroud)

#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);
}
Run Code Online (Sandbox Code Playgroud)


zpa*_*ack 8

如果int值始终位于字符串的开头,则可以使用intValue.

NSString *string = @"123hello";
int myInt = [string intValue];
Run Code Online (Sandbox Code Playgroud)