【问题标题】:Convert first number in an NSString into an Integer?将 NSString 中的第一个数字转换为整数?
【发布时间】:2010-11-11 10:41:19
【问题描述】:

我有一个这样的 NSString:

@"200hello"

@"0 something"

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

这样@"200hello" 就会变成 int = 200。

而@"0 something" 将变为 int = 0。

【问题讨论】:

    标签: objective-c cocoa cocoa-touch nsstring


    【解决方案1】:
    int i;
    NSString* string;
    i = [string intValue];
    

    【讨论】:

      【解决方案2】:

      Steve Ciarcia 曾经说过,一个测量结果值得一百多个工程师的意见。所以开始了第一个也是最后一个“如何从 NSString 获取 int 值”的烹饪课!

      以下是竞争者:(使用已代代相传的令人难以置信的高精度 for(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 的私有副本进行测试......恰好在跟踪用户的错误报告时添加了一堆调试内容。上面的计时结果大致相当于在 for() {} 计时循环中调用[valueString flushCachedRegexData];(这实际上是无意调试的东西正在做的事情)。以下结果来自针对可用的最新、未修改的 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 Fast Hex Conversion 找到 RegexKitLite 示例。基本上将 strtoimax 换成了 strtol,并添加了一行代码来跳过不是 [+-0-9] 的前导字符。 (全面披露:我是 RegexKitLite 的作者)

      'scannerScanInt' 和 'intValue' 都存在要提取的数字必须在字符串开头的问题。我认为两者都会跳过任何前导空格。

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

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

      这里基本上有两个不同的能力类别:那些可以容忍额外的“东西”并且仍然可以为您提供数字(characterSet、RegexKitLite 匹配器和 rklIntValue),以及那些基本上需要数字作为第一件事的能力。字符串,在开始时最多容忍一些空白填充(scannerScanInt 和 intValue)。

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

      (请记住,我编写了 RegexKitLite,因此请使用您认为合适的任何大小的盐粒)。

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

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

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

      编辑:

      根据 OP 的要求,这里是我用来执行测试的代码的修剪和缩小版本。需要注意的一件事:将这些放在一起时,我注意到 Dave DeLong 的原始正则表达式并不能很好地工作。问题在于否定字符集——集合内的元字符序列(即 [^\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);
      }
      

      【讨论】:

      • 哇,非常感谢您努力测量性能! +1
      • 也许您可以使用我刚刚添加的我自己的答案的结果进行更新。另外,您能否添加您编写的代码以执行基准测试。
      【解决方案3】:

      我想出了自己的答案,可能比其他人提供的更快、更容易。

      我的回答确实假设您知道数字开始和结束的位置...

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

      你也可以用另一种方式工作:

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

      【讨论】:

        【解决方案4】:
        int value;
        BOOL success = [[NSScanner scannerWithString:@"1000safkaj"] scanInteger:&value];
        

        如果数字不总是在开头:

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

        【讨论】:

        • 这仅在数字是字符串的第一部分时才有效。当然,这就是这个例子所暗示的,但即使数字前有字母,我的回答也会起作用。你的不会。 =)
        • 前后字符的好解决方法。我不知道 NSCharacterSet 上的 -invertedSet 方法。 +1
        • 现在我对你的答案投反对票感到很遗憾。
        • 您始终可以通过再次单击向下箭头来“取消投票”他的答案。
        • 好的,取消投票完成,你说服了我。但是,当人们在 iPhone 电池电量不足时开始建造核电站时,不要来抱怨。
        【解决方案5】:

        如果int值总是在字符串的开头,你可以简单地使用intValue。

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

        【讨论】:

        • 再次,就像@Nikiolai Ruhe 的回答一样,这只有在数字前没有任何字母的情况下才有效。可能是这种情况,但问题是“第一个出现的数字”,这意味着它之前可能有字母。 =)
        • 是的,但是提问者的例子总是在开头显示数字。从技术上讲,提问者混淆了“int”和“Integer”,所以他可能还想使用 -[NSString integerValue] 来代替。
        【解决方案6】:

        我可能会使用正则表达式(用恒星RegexKitLite 实现)。然后它会是这样的:

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

        正则表达式@"[^\d]*(\d+)" 表示“任意数量的非数字字符后跟至少一个数字字符”。

        【讨论】:

        • 好吧,我不会投反对票,因为您的项目中可能有 RegexKitLite,但就我的目的而言,是的,它有点太多了。还是谢谢+1
        • 这里没有反对意见,但我同意 NSScanner 是一种更清洁的方法。特别是因为您必须为不同的数字类型编写新的正则表达式,而与 NSScanner 一样,您可以切换到 scanFloat: 或 scanDouble: 等。
        • @Quinn 看到这个问题,我也立刻想到了 NSScanner。在阅读答案之前。 ;-)
        猜你喜欢
        • 2011-03-10
        • 2014-04-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-09-18
        • 2021-03-14
        相关资源
        最近更新 更多