相当于地图的NSArray


75

给定一个NSArrayNSDictionary对象(含类似的对象和密钥)是有可能写出执行地图指定的键的阵列?例如,在Ruby中,可以使用:

array.map(&:name)

1
您希望从通话中获得什么返回值?
jaminguy 2011年

Ruby的地图接近于典型的功能编程思想。给定一个集合,转换集合中的每个对象,然后返回结果集合。
詹姆斯·摩尔

顺便说一句,我写了一个Objective-C库,提供Ruby的map/collect方法NSArraygithub.com/mdippery/collections
mipadi

Answers:


19

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


BlocksKit是一个选项:

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

下划线是另一种选择。有一个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;

9
恕我直言,对Objective-C方法使用点语法并不是一个好主意。这不是Ruby。
2014年

1
@RudolfAdamkovic虽然我原则上同意您的意见,但使用方括号符号表示的可读性较差。
2014年

8
除非您到处都在进行功能操作,否则最好使用NSArray的标准mapObjectsUsingBlock:或valueForKey:方法(在以下答案中建议)。避免再使用一些“丑陋”的Objective-C语法字符并不能证明添加Cocoapods依赖项是合理的。
群居

3
mapObjectsUsingBlock:不是标准函数,而是另一个答案建议的扩展
mcfedr

1
从答案中的“ Underscore”链接的页面似乎不再与Objective-C相关。

130

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

别忘了在传递给的块上检查nil mapObjectsUsingBlock。如果您传入nil块,它将当前崩溃。
DHamrick

7
mikeash.com/pyblog/friday-qa-2009-08-14-practical-blocks.html包含上述的更好的(syntactic)变体,恕我直言。
Manav 2012年

@Justin Anderson您可以在答案中包含上述示例的用法吗?
2013年

2
您正在退回MutableArray。这是更好的做法return [result copy]吗?
罗杰斯先生

1
@abbood如果您希望保留从原始数组到映射数组的索引的1-1映射,建议您返回NSNull。这就是Obj-C对象,用于表示集合类中的nil。NSNull因为Obj-C不会取消装箱,所以这种用法非常不常见且麻烦NSNull != nil。但是,如果您只想从数组中过滤掉某些项目,则可以进行修改mapObjectsUsingBlock以检查是否有零响应,并跳过它们。
贾斯汀·安德森

78

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


就是这样!非常感谢!
2011年

这不是地图的作用。Ruby的map需要一个块,而不仅仅是一个方法调用。这种特殊情况仅适用,因为该块是对单个方法的单个调用。@JustinAnderson的回答与您在Ruby中可以做的非常接近。
詹姆斯·摩尔

2
实际上,此解决方案比您想象的要灵活得多,因为valueForKey:可以通过调用相应的getter来工作。因此,如果您事先知道您可能希望对象执行的各种操作,则可以将所需的吸气剂注入到其中(例如,使用类别),然后使用NSArrayvalueForKey:作为通过数组将调用传递给特定吸气剂的方式到每个对象并获得结果数组。
马特2012年

3
+1; 这是处理问题提问者描述的特定(相当常见)用例的最干净的方法,即使它不是问题标题中要求的完全灵活的映射函数也是如此。对于真正需要map方法的人,请使用贾斯汀·安德森答案中的类别。
Mark Amery

1
我不断回到这个答案,以记住那个名字。我习惯于用其他编程语言来“映射”,以至于我永远都记不起来“ valueForKey”了
tothemario 2015年

38

总结所有其他答案:

Ruby(在问题中):

array.map{|o| o.name}

Obj-C(带有valueForKey):

[array valueForKey:@"name"];

Obj-C(使用valueForKeyPath,请参见KVC集合运算符):

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

Obj-C(带有enumerateObjectsUsingBlock):

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

Swift(带有地图,请参见闭包

array.map { $0.name }

而且,有几个库可让您以更实用的方式处理数组。建议使用CocoaPods安装其他库。


优秀的!非常感谢
穆罕默德·阿米尔·阿里

8

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

4

我不是Ruby专家,所以我不是100%自信地回答正确,但是基于这样的解释,即“映射”对数组中的所有内容都进行了处理,并生成了一个带有结果的新数组,我想想要是这样的:

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)


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. 这更多是一种样式,但是我已经改进了IMO的参数名称
  4. 点语法的计数

0

对于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

对于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

为此有一个特殊的键路径运算符:@unionOfObjects。可能是[collect]从以前的版本替代的。

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

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

Apple关于键值编码中的数组运算符的文档。


-1

Swift引入了新的地图功能。

这是文档中示例

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函数采用闭包,该闭包返回任何类型的值,并将数组中的现有值映射到此新类型的实例。


5
您可以使用一个更简单的示例,例如[1,2,3].map {$0 * 2} //=> [2,4,6]。AND,问题是关于Obj-C NSArray,而不是Swift;)
2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.