NSArrayとバイナリサーチ

@interface AData : NSObject
@property (retain) NSNumber *number;
@property (retain) NSData *data;
@end

こういうクラスのインスタンスが多量にNSArrayに入れられてあるとしましょう。
この集合の部分集合に対しとあるアクションを起こします。
ただし、部分集合は静的には決まらず、動的にしかもnumberの集合として示されるとします。
簡単にいうと、提示したnumberと合致するインスタンスに対してだけアクションを起こしなさいよ、ってことです。


一番無難な方法はNSArrayをindexでソートしてバイナリサーチで取り出していく方法です。
ソートは -[NSArray sortedArrayUsingSelector:]を使うよりも
この場合は -[NSArray sortedArrayUsingDescriptors:]を使っちゃう方がラクチンです。
あとはこれをバイナイリサーチで、、、メソッドがありません!


ない物は作る!ではなくて、まずCoreFoundationを見てみるです。
はい、ありました。これ。
CFIndex CFArrayBSearchValues(CFArrayRef theArray, CFRange range, const void *value, CFComparatorFunction comparator, void *context);
どうしても作る!って言う人は作っちゃってください。個人的にはComparatorにblocks使う奴希望!


じゃ、説明
theArray 探す奴が入ってる配列
range 探す範囲
value 探しもの
comparator 探し方
context 探すヒント
返値

  1. あったらそのindex
  2. なかった時 探し物を超える最小のindex
  3. 探し物より小さい物しかなかった時 探す範囲の最大index+1

たとえば、

index 0 1 2 3 4 5 6 7 8
value 3 5 9 12 16 34 123 234 1234

から

  1. 12を探すと3が返ってきます
  2. 50を探すと6が返ってきます
  3. 10000を探すと9が返ってきます。

2)が厄介です。一度値を取り出してみないと確認出来ません。


CFComparatorFunctionの定義は

typedef CFComparisonResult (*CFComparatorFunction)(const void *val1, const void *val2, void *context);

こんな感じになってます。CFArrayBSearchValuesでcontextを渡すとこの関数のcontextにそれが渡されます。


上のクラスなら

CFComparisonResult compareAData(const void *val1, const void *val2, void *context)
{
    NSNumber *data1Index;
    NSNumner *data2Index;
    
    if([(id)val1 isKindOfClass:[AData class]]) {
        AData *data1 = (id)val1;
        data1Index = data1.number;
    } else {
        data1Index = (id)val1;
    }
    if([(id)val2 isKindOfClass:[AData class]]) {
        AData *data1 = (id)val2;
        data2Index = data2.number;
    } else {
        data2Index = (id)val2;
    }
    return [data1Index compare:data2Index];
}

てな感じになります。

こんな感じで使います。
あ、ちなみにNSArrayとCFArrayRefはToll-free bridgeで繋がってるのでそのままキャストして渡せます。

NSArray *sortedArray;    // ここにADataのインスタンスがいっぱい入ってます。
NSNumber *target = [NSNumber numberWithInteger:10];    // numberが10のインスタンスを探します。
CFIndex count = [sortedArray count];
CFRange range = CFRangeMake(0, count);
AData *data = nil;

CFIndex index = CFArrayBSearchValues((CFArrayRef)sortedArray,range, target, compareAData, NULL);
if(index < count) {
    data = [sortedArray objetAtIndex:index];
} else {
    // ない!
}
if(NSOrderedSame != [data.number compare:target]) {
    // ない!
}