web-dev-qa-db-ja.com

NSArrayと同等のマップ

NSArrayオブジェクト(類似のオブジェクトとキーを含む)のNSDictionaryが与えられた場合、指定されたキーの配列にマップを実行することは可能ですか?たとえば、Rubyでは、次のように実行できます。

array.map(&:name)
71
Stussa

更新:Swiftを使用している場合は、 map を参照してください。


BlocksKit はオプションです:

NSArray *new = [stringArray bk_map:^id(NSString *obj) { 
    return [obj stringByAppendingString:@".png"]; 
}];

アンダースコア は別のオプションです。 map関数があります。これはWebサイトの例です。

NSArray *tweets = Underscore.array(results)
    // Let's make sure that we only operate on NSDictionaries, you never
    // know with these APIs ;-)
    .filter(Underscore.isDictionary)
    // Remove all tweets that are in English
    .reject(^BOOL (NSDictionary *Tweet) {
        return [Tweet[@"iso_language_code"] isEqualToString:@"en"];
    })
    // Create a simple string representation for every Tweet
    .map(^NSString *(NSDictionary *Tweet) {
        NSString *name = Tweet[@"from_user_name"];
        NSString *text = Tweet[@"text"];

        return [NSString stringWithFormat:@"%@: %@", name, text];
    })
    .unwrap;
19
Senseful

数行しか保存されませんが、NSArrayのカテゴリを使用します。ブロックが決してnilを返さないようにする必要がありますが、それ以外は-[NSArray valueForKey:]が機能しない場合の時間の節約になります。

@interface NSArray (Map)

- (NSArray *)mapObjectsUsingBlock:(id (^)(id obj, NSUInteger idx))block;

@end

@implementation NSArray (Map)

- (NSArray *)mapObjectsUsingBlock:(id (^)(id obj, NSUInteger idx))block {
    NSMutableArray *result = [NSMutableArray arrayWithCapacity:[self count]];
    [self enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        [result addObject:block(obj, idx)];
    }];
    return result;
}

@end

使用法は-[NSArray enumerateObjectsWithBlock:]によく似ています:

NSArray *people = @[
                     @{ @"name": @"Bob", @"city": @"Boston" },
                     @{ @"name": @"Rob", @"city": @"Cambridge" },
                     @{ @"name": @"Robert", @"city": @"Somerville" }
                  ];
// per the original question
NSArray *names = [people mapObjectsUsingBlock:^(id obj, NSUInteger idx) {
    return obj[@"name"];
}];
// (Bob, Rob, Robert)

// you can do just about anything in a block
NSArray *fancyNames = [people mapObjectsUsingBlock:^(id obj, NSUInteger idx) {
    return [NSString stringWithFormat:@"%@ of %@", obj[@"name"], obj[@"city"]];
}];
// (Bob of Boston, Rob of Cambridge, Robert of Somerville)
123
Justin Anderson

Rubyのそのビットが何をするのか分かりませんが、NSArrayの -valueForKeyの実装を探しています think 。これにより、-valueForKey:が配列のすべての要素に送信され、結果の配列が返されます。受信配列の要素がNSDictionariesである場合、-valueForKey:-objectForKey:とほぼ同じです。キーが@で始まらない限り機能します

74
JeremyP

他のすべての答えを要約するには:

Ruby(質問のように):

array.map{|o| o.name}

Obj-C( valueForKey を使用):

[array valueForKey:@"name"];

Obj-C(valueForKeyPathを使用、 KVC Collection Operators を参照):

[array valueForKeyPath:@"[collect].name"];

Obj-C( enumerateObjectsUsingBlock を使用):

NSMutableArray *newArray = [NSMutableArray array];
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
     [newArray addObject:[obj name]];
}];

Swift( map で、 closures を参照)

array.map { $0.name }

また、より機能的な方法で配列を処理できるようにするライブラリがいくつかあります。 Cocoa Pods は、他のライブラリをインストールするために推奨されます。

32
tothemario

ValueForKeyPathは良い選択だと思います。

以下に座って非常にクールな例があります。役に立てば幸いです。

http://kickingbear.com/blog/archives/9

いくつかの例:

NSArray *names = [allEmployees valueForKeyPath: @"[collect].{daysOff<10}.name"];
NSArray *albumCovers = [records valueForKeyPath:@"[collect].{artist like 'Bon Iver'}.<NSUnarchiveFromDataTransformerName>.albumCoverImageData"];
6
Steven Jiang

私はRubyエキスパートなので、私は正しく答えているとは100%確信していませんが、 'map'は配列内のすべてに対して何かを行い、新しい配列を生成するという解釈に基づいています結果では、おそらくあなたが望むものは次のようなものだと思います:

NSMutableArray *replacementArray = [NSMutableArray array];

[existingArray enumerateObjectsUsingBlock:
    ^(NSDictionary *dictionary, NSUInteger idx, BOOL *stop)
    {
         NewObjectType *newObject = [something created from 'dictionary' somehow];
         [replacementArray addObject:newObject];
    }
];

そのため、OS X 10.6/iOS 4.0の「ブロック」(より一般的な用語ではクロージャー)の新しいサポートを使用して、アレイ内のすべてのブロックの処理を実行しています。何らかの操作を実行してから、結果を別の配列に追加することを選択しています。

10.5またはiOS 3.xをサポートする場合は、おそらく関連するコードをオブジェクトに配置し、makeObjectsPerformSelectorを使用することを検討することをお勧めします。または、最悪の場合、for(NSDictionary *dictionary in existingArray)を使用して。

4
Tommy
@implementation NSArray (BlockRockinBeats)

- (NSArray*)mappedWithBlock:(id (^)(id obj, NSUInteger idx))block {
    NSMutableArray* result = [NSMutableArray arrayWithCapacity:self.count];
    [self enumerateObjectsUsingBlock:^(id currentObject, NSUInteger index, BOOL *stop) {
        id mappedCurrentObject = block(currentObject, index);
        if (mappedCurrentObject)
        {
            [result addObject:mappedCurrentObject];
        }
    }];
    return result;
}

@end


投稿されたいくつかの回答がわずかに改善されました。

  1. Nilのチェック-nilを使用して、マッピング中にオブジェクトを削除できます
  2. メソッド名は、メソッドが呼び出された配列を変更しないことをよりよく反映しています
  3. これはもっとスタイルのことですが、ブロックの引数名をIMOで改善しました
  4. カウントのドット構文
2
james_womack

Objective-Cの場合、ObjectiveSugarライブラリを次の回答リストに追加します。 https://github.com/supermarin/ObjectiveSugar

さらに、そのキャッチフレーズは「人間向けのObjectiveCの追加。Rubyスタイル。」OPによく合うはずです;-)

私の最も一般的なユースケースは、サーバー呼び出しによって返される辞書をより単純なオブジェクトの配列にマッピングすることです。 NSDictionaryの投稿からNSString IDのNSArrayを取得する:

NSArray *postIds = [results map:^NSString*(NSDictionary* post) {
                       return [post objectForKey:@"post_id"];
                   }];
0
LordParsley

Objective-Cの場合、次の回答リストに高次関数を追加します。 https://github.com/fanpyi/Higher-Order-Functions ;

次のようなJSON配列studentJSONListがあります。

[
    {"number":"100366","name":"Alice","age":14,"score":80,"gender":"female"},
    {"number":"100368","name":"Scarlett","age":15,"score":90,"gender":"female"},
    {"number":"100370","name":"Morgan","age":16,"score":69.5,"gender":"male"},
    {"number":"100359","name":"Taylor","age":14,"score":86,"gender":"female"},
    {"number":"100381","name":"John","age":17,"score":72,"gender":"male"}
]
//studentJSONList map to NSArray<Student *>
NSArray *students = [studentJSONList map:^id(id obj) {
return [[Student alloc]initWithDictionary:obj];
}];

// use reduce to get average score
NSNumber *sum = [students reduce:@0 combine:^id(id accumulator, id item) {
Student *std = (Student *)item;
return @([accumulator floatValue] + std.score);
}];
float averageScore = sum.floatValue/students.count;

// use filter to find all student of score greater than 70
NSArray *greaterthan = [students filter:^BOOL(id obj) {
Student *std = (Student *)obj;
return std.score > 70;
}];

//use contains check students whether contain the student named 'Alice'
BOOL contains = [students contains:^BOOL(id obj) {
Student *std = (Student *)obj;
return [std.name isEqual:@"Alice"];
}];
0
范陆离