映射的 NSArray 等效

给定 NSDictionary对象的 NSArray(包含相似的对象和键) ,是否可以将执行映射写入指定键的数组?例如,在 Ruby 中,可以使用:

array.map(&:name)
52637 次浏览

I've no idea what that bit of Ruby does but I think you are looking for NSArray's implementation of -valueForKey:. This sends -valueForKey: to every element of the array and returns an array of the results. If the elements in the receiving array are NSDictionaries, -valueForKey: is nearly the same as -objectForKey:. It will work as long as the key doesn't start with an @

I'm no Ruby expert so I'm not 100% confident I'm answering correctly, but based on the interpretation that 'map' does something to everything in the array and produces a new array with the results, I think what you probably want is something like:

NSMutableArray *replacementArray = [NSMutableArray array];


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

So you're using the new support for 'blocks' (which are closures in more general parlance) in OS X 10.6/iOS 4.0 to perform the stuff in the block on everything in the array. You're choosing to do some operation and then add the result to a separate array.

If you're looking to support 10.5 or iOS 3.x, you probably want to look into putting the relevant code into the object and using makeObjectsPerformSelector: or, at worst, doing a manual iteration of the array using for(NSDictionary *dictionary in existingArray).

It only saves a couple lines, but I use a category on NSArray. You need to ensure your block never returns nil, but other than that it's a time saver for cases where -[NSArray valueForKey:] won't work.

@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

Usage is much like -[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)
@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


A slight improvement upon a couple of the answers posted.

  1. Checks for nil—you can use nil to remove objects as you're mapping
  2. Method name better reflects that the method doesn't modify the array it's called on
  3. This is more a style thing but I've IMO improved the argument names of the block
  4. Dot syntax for count

Update: If you're using Swift, see map.


BlocksKit is an option:

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

Underscore is another option. There is a map function, here is an example from the website:

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;

Swift introduces a new map function.

Here is an 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"]

The map function takes a closure which returns a value of any type and maps the existing values in the array to instances of this new type.

I think valueForKeyPath is a good choice.

Sit below has very cool examples. Hopes it is helpful.

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

Some example:

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

To summarize all other answers:

Ruby (as in the question):

array.map{|o| o.name}

Obj-C (with valueForKey):

[array valueForKey:@"name"];

Obj-C (with valueForKeyPath, see KVC Collection Operators):

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

Obj-C (with enumerateObjectsUsingBlock):

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

Swift (with map, see closures)

array.map { $0.name }

And, there are a couple of libraries that allow you to handle arrays in a more functional way. CocoaPods is recommended to install other libraries.

For Objective-C, I would add the ObjectiveSugar library to this list of answers: https://github.com/supermarin/ObjectiveSugar

Plus, its tagline is "ObjectiveC additions for humans. Ruby style." which should suit OP well ;-)

My most common use-case is mapping an dictionary returned by a server call to an array of simpler objects e.g. getting an NSArray of NSString IDs from your NSDictionary posts:

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

For Objective-C, I would add the Higher-Order-Functions to this list of answers: https://github.com/fanpyi/Higher-Order-Functions;

There is a JSON array studentJSONList like this:

[
{"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"];
}];

There is a special key-path operator for this: @unionOfObjects. Probably it replaced [collect] from previous versions.

Imagine a Transaction class with payee property:

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

Apple docs on Array Operators in Key-Value coding.