NSArray等效地图

时间:2011-05-25 16:20:52

标签: iphone objective-c cocoa-touch cocoa macos

给定NSArrayNSDictionary个对象(包含类似的对象和键)是否可以编写执行映射到指定键的数组?例如,在Ruby中可以使用:

array.map(&:name)

11 个答案:

答案 0 :(得分:123)

它只保存了几行,但我在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)

答案 1 :(得分:74)

我不知道Ruby会做什么,但我认为你正在寻找NSArray的-valueForKey:实现。这会将-valueForKey:发送到数组的每个元素,并返回结果数组。如果接收数组中的元素是NSDictionaries,-valueForKey:几乎与-objectForKey:相同。只要密钥不以@

开头,它就会起作用

答案 2 :(得分:31)

总结所有其他答案:

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安装其他库。

答案 3 :(得分:18)

更新:如果您使用的是Swift,请参阅map


BlocksKit是一个选项:

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

Underscore是另一种选择。有一个map函数,这是网站上的一个例子:

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;

答案 4 :(得分:6)

我认为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"];

答案 5 :(得分:4)

我不是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中使用对'blocks'的新支持(在更一般的说法中是闭包)来执行数组中所有内容的块中的东西。您选择执行某些操作,然后将结果添加到单独的数组中。

如果您希望支持10.5或iOS 3.x,您可能希望将相关代码放入对象并使用makeObjectsPerformSelector:或者,最糟糕的是,使用{{1进行数组的手动迭代}}

答案 6 :(得分:2)

@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删除对象
  2. 方法名称更好地反映了该方法不会修改它在
  3. 上调用的数组
  4. 这更像是一种风格,但我已经IMO改进了块的参数名称
  5. 计数
  6. 的点语法

答案 7 :(得分:0)

对于Objective-C,我会将ObjectiveSugar库添加到这个答案列表中:https://github.com/supermarin/ObjectiveSugar

另外,它的标语是#34;人类的ObjectiveC添加。 Ruby风格。&#34;哪个应该适合OP; - )

我最常见的用例是将服务器调用返回的字典映射到更简单的对象数组,例如从您的NSDictionary帖子获取NSArray的NSString ID:

response=>{"create"=>{"_index"=>"logs-2017.02.08", "_type"=>"json", "_id"=>"AVoeNgdhD5iEO87EVF_n", "status" =>400, "error"=> "type"=>"mapper_parsing_exception", "reason"=>"failed to parse [request]", "caused_by"=>{"type"=>"illegal_argument_exception", "reason"=>"unknown property [requestId]" }}}}, :level=>:warn}

答案 8 :(得分:0)

对于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"];
}];

答案 9 :(得分:0)

对此有一个特殊的键路径运算符:@unionOfObjects。可能是它取代了先前版本中的[collect]

想象一个具有Transaction属性的payee类:

NSArray *payees = [self.transactions valueForKeyPath:@"@unionOfObjects.payee"];

Array Operators in Key-Value coding上的Apple文档。

答案 10 :(得分:-1)

Swift推出了一个新的地图功能。

这是example from the documentation

let digitNames = [
    0: "Zero", 1: "One", 2: "Two",   3: "Three", 4: "Four",
    5: "Five", 6: "Six", 7: "Seven", 8: "Eight", 9: "Nine"
]
let numbers = [16, 58, 510]

let strings = numbers.map {
    (var number) -> String in
    var output = ""
    while number > 0 {
        output = digitNames[number % 10]! + output
        number /= 10
    }
    return output
}
// strings is inferred to be of type String[]
// its value is ["OneSix", "FiveEight", "FiveOneZero"]

map函数接受一个闭包,它返回任何类型的值,并将数组中的现有值映射到这个新类型的实例。