4

私は次のようなNSStringを持っています:

@"200hello"

また

@"0 something"

私ができるようにしたいのは、NSStringで最初に出現する数値を取得し、それをintに変換することです。

@"200hello"がint=200になるようにします。

@"0something"はint=0になります。

4

6 に答える 6

20

Steve Ciarcia はかつて、1 つの測定結果は 100 人のエンジニアの意見よりも価値があると言いました。そして、最初で最後の「NSString から int 値を取得する方法」のクックオフが始まります!

候補は次のとおりです: (非常に高い精度の for(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)

このメッセージの別の場所で指摘したように、私はもともとこれを 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 Fast Hex Conversionにあります。基本的に strtoimax を strtol に交換し、[+-0-9] ではない先頭の文字をスキップするコード行を追加しました。(完全な開示: 私は RegexKitLite の作成者です)

「scannerScanInt」と「intValue」の両方に、抽出する数値が文字列の先頭になければならないという問題があります。どちらも先頭の空白をスキップすると思います。

Dave DeLongs の正規表現を '[^\d]*(\d+)' から '\d+' に変更しました。これが本当に必要なすべてであり、起動するためのキャプチャ グループの使用を取り除くことができたからです。

したがって、上記のデータに基づいて、次の推奨事項を提供します。

ここには基本的に 2 つの異なる機能クラスがあります。余分な「もの」を許容し、それでも数値を取得できるもの (characterSet、RegexKitLite マッチャー、および rklIntValue) と、基本的に数値を文字列の最初のものにする必要があり、許容できるものです。多くても、開始時の空白のパディング (scannerScanInt および intValue) です。

このようなことを行うために NSCharacterClass を使用しないでください。この例では、最初の NSCharacterClass のインスタンス化に 16 バイトが使用され、次に逆バージョンに 32 バイト、最後に文字列の結果に 16 バイトが使用されます。汎用の正規表現エンジンが、より少ないメモリ使用量で 2 桁のパーセンテージ マージンでそれを凌駕するという事実は、契約をほぼ確定します。

(私が RegexKitLite を作成したことを覚えておいてください。そのため、適切と思われる任意のサイズの塩の粒で以下を実行してください)。

RegexKitLite は適切なタイミングで有効になり、NSString オブジェクトを返すという事実を考慮して、可能な限り最小量のメモリを使用します。内部ですべての ICU 正規表現エンジンに LRU キャッシュを使用するため、これらのコストは時間の経過とともに償却され、繰り返し使用されます。必要に応じて正規表現を変更するのにも数秒かかります (16 進値? 16 進浮動小数点数? 通貨? 日付? 問題ありません)。

単純なマッチャーの場合、NSScanner を使用してこれらのことを行うべきではないことは明らかです。NSScanner を使用して「scanInt:」を実行することは、単に [aString intValue] を呼び出すことと同じです。同じ警告で同じ結果が生成されます。違いは、プロセスで 32 バイトのメモリを無駄にしながら、NSScanner が同じことを行うのに 5 倍の時間がかかることです.... [aString intValue] (おそらく) はその魔法を実行するために 1 バイトのメモリを必要としません。 strtoimax() (または同等のもの) を呼び出し、文字列の内容を保持するポインターに直接アクセスできるため....

最後の 1 つは「rklIntValue」です。これは、これもわずかに微調整されたバージョンです (上記の「RegexKitLite Fast Hex Conversion」リンク、stackoverflow では 2 回投稿できません)。CoreFoundation を使用して文字列バッファーに直接アクセスしようとしますが、失敗すると、スタックからスペースを割り当てて、文字列のチャンクをそのバッファーにコピーします。これは CPU 上の 3 つの命令すべてを必要とし、malloc() 割り当てのように「リーク」することは基本的に不可能です。したがって、メモリを使用せず、非常に高速に動作します。おまけとして、変換する文字列の基数を strtoXXX() に渡します。10 進数の場合は 10、16 進数の場合は 16 (存在する場合は先頭の 0x を自動的に飲み込む)、または自動検出の場合は 0。任意の「あなたが望むものにたどり着くまで、面白くない」文字(私は-、+、および0-9を選択します). double 値を解析する必要がある場合は、 strtod() のようなものを交換するのも簡単です。strtod() は、ほぼすべての有効な浮動小数点テキスト (NAN、INF、16 進浮動小数点数など) を変換します。

編集:

OPのリクエストにより、テストの実行に使用したコードのトリミングおよび縮小バージョンを次に示します。1 つの注意点: これをまとめているときに、Dave DeLong の元の正規表現がうまく機能しないことに気付きました。問題は、否定された文字セットにあります。セット内のメタ文字シーケンス (つまり、[^\d]+) は、文字セット外での特別な意味ではなく、文字通りの文字を意味します。意図した効果を持つ [^\p{DecimalNumber}]* に置き換えられました。

私はもともとこれを RegexKitLite 単体テスト ハーネスにボルトで固定していたので、GC 用にいくつかの断片を残しました。これについてはすべて忘れていましたが、GC がオンになったときに何が起こるかの短いバージョンは、すべての時間ですが、RegexKitLite double (つまり、2 倍の時間がかかります)。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);
}
于 2009-07-17T07:56:43.197 に答える
3

私はおそらく正規表現を使用します(恒星のRegexKitLiteで実装されています)。次に、次のようになります。

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

正規表現 @"[^\d]*(\d+)" は、「任意の数の数字以外の文字の後に少なくとも 1 つの数字が続く」ことを意味します。

于 2009-07-16T16:30:20.460 に答える
0

私は独自の答えを思いつきました。他の人が提供したものよりも速くて簡単な可能性があります。

私の答えは、数字の開始位置と終了位置を知っていることを前提としています...

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

他の方法でも作業を開始できます。

NSString *myString = @"sss22";
int numberAtEnd = [[myString substringFromIndex:3] intValue];
于 2009-07-17T09:18:10.100 に答える
-1
int i;
NSString* string;
i = [string intValue];
于 2012-04-11T12:09:49.183 に答える