NSArray Equivalent of Map
Solution 1
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;
Solution 2
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)
Solution 3
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 @
Solution 4
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.
Solution 5
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"];
Stussa
Updated on October 25, 2020Comments
-
Stussa over 3 years
Given an
NSArray
ofNSDictionary
objects (containing similar objects and keys) is it possible to write perform a map to an array of specified key? For example, in Ruby it can be done with:array.map(&:name)
-
Stussa almost 13 yearsIt does exactly that! Thanks so much!
-
DHamrick about 12 yearsDon't forget to check for nil on the block you are passing in to
mapObjectsUsingBlock
. It will currently crash if you pass in a nil block. -
Manav over 11 yearsmikeash.com/pyblog/friday-qa-2009-08-14-practical-blocks.html contains a better (syntactic) variant of the above, IMHO.
-
James Moore over 11 yearsThis isn't what map does. Ruby's map takes a block, not just a single method call. This particular case only works because the block is a single call to a single method. Answer from @JustinAnderson is much closer to what you can do in Ruby.
-
matt over 11 yearsActually, this solution is far more flexible than you might suppose, because
valueForKey:
works by calling the corresponding getter. Thus, if you know in advance the various things you might want an object to do, you can inject the needed getters into it (e.g. with a category) and then use NSArray'svalueForKey:
as a way of passing a call to a particular getter through the array to each object and getting an array of the results. -
abbood about 11 years@Justin Anderson can you please include in your answer a sample usage of the above?
-
Mark Amery almost 11 years+1; this is the cleanest way to handle the particular (fairly common) use case that the question asker described, even if it's not a fully flexible map function as asked for in the question title. For people who genuinely need a
map
method, use the category from Justin Anderson's answer instead. -
ArpitM almost 11 yearsYou're returning a
MutableArray
back. Is it better practice to doreturn [result copy]
? -
Justin Anderson almost 11 years@MrRogers
result copy
would potentially reduce memory usage, but there's a time cost to copying the array. For large arrays that could be pretty expensive. The other benefit of making the response truly immutable is that you would get runtime exceptions if you tried to mutate it, but the return type of the method is immutable, so you'll get build warnings either way if you try to treat the response as mutable. -
ArpitM almost 11 years@JustinAnderson
copy
returns an immutable array. Isn't that what one would expect if that's the return type of the method? There is a speed hit though. Thanks! -
Justin Anderson almost 11 years@MrRogers There's no expectation or implication about the actual mutability of the returned object, just that you should treat it as though it is immutable. A return type of
NSArray
is just a promise that a response will be anNSArray
or an instance of a subclass of it. It's like how you need to treat anid
response as anNSObject
until you've tested it withrespondsToSelector
and the like before you can expect to treat it as something more specific. -
abbood over 10 yearsfollow up question here: what to do when I would like to skip an object in the internal enumerator
-
Justin Anderson over 10 years@abbood If you're looking to keep a 1-1 mapping of the indexes from your original array to your mapped array, I'd suggest returning
NSNull
. That's the Obj-C object for representing nil in collection classes.NSNull
is pretty uncommon and a hassle to use though, since Obj-C doesn't do unboxing, soNSNull != nil
. But if you want to just filter some items out of the array, you could modifymapObjectsUsingBlock
to check for nil responses from the block and skip them. -
Rudolf Adamkovič about 10 yearsIMHO, using dot syntax for Objective-C methods is not a good idea. This is not Ruby.
-
11684 almost 10 years@RudolfAdamkovic While I do agree with you in principle, doing this with bracket notation would be less readable.
-
Gregarious over 9 yearsUnless you're doing functional operations all over the place, it's a much better idea to use the NSArray's standard mapObjectsUsingBlock: or valueForKey: methods (suggested in the answers below). Avoiding a few more characters of "ugly" Objective-C syntax doesn't justify adding a Cocoapods dependency.
-
tothemario about 9 yearsYou could use a simpler example, like
[1,2,3].map {$0 * 2} //=> [2,4,6]
. AND, the question is about Obj-C NSArray, not Swift ;) -
tothemario about 9 yearsI keep coming back to this answer to remember that name. I'm so used to "map" in other programming languages that I never can remember "valueForKey"
-
mcfedr about 8 years
mapObjectsUsingBlock:
isn't a standard function, but an extension suggested by another answer -
J2N about 8 yearsAgreed with @Gregarious, we should never go against coding standards just to make it "prettier"
-
Muhammad Aamir Ali almost 8 yearsExcellent! Thank you so much
-
Pang almost 6 yearsPage linked from "Underscore" in answer seems not related to Objective-C any more.
-
uchuugaka almost 5 yearsUse
__block
on variables that declared outside the block but need to be accessed inside the block. -
Justin Anderson almost 5 years@uchuugaka __block doesn't apply here. There are no outside variables being captured and no state to persist beyond the block's return value.