If I have an NSArray of custom objects (in this case Core Data objects), how would I put all the items of a particular attribute in another NSArray. Is there a way I can use blocks?
For instance, if the class is People, and one of the attributes is age, and there are five objects in the array of people, then the final NSArray would just show the ages:
{ 12, 45, 23, 43, 32 }
Order is not important.
EDIT I have added a blocks based implementation too alongwith the selector based implementation.
What you are looking for is something equivalent to the "map" function from the functional world (something which, unfortunately, is not supported by Cocoa out of the box):
#interface NSArray (FunctionalUtils)
- (NSArray *)mapWithSelector:(SEL)selector;
- (NSArray *)mapWithBlock:(id (^)(id obj))block;
#end
And the implementation:
#implementation NSArray (FunctionalUtils)
- (NSArray *)mapWithSelector:(SEL)selector {
NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:[self count]];
for (id object in self) {
[result addObject:[object performSelector:selector]];
}
return [result autorelease];
}
- (NSArray *)mapWithBlock:(id (^)(id obj))block {
NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:[self count]];
for (id object in self) {
[result addObject:block(object)];
}
return [result autorelease];
}
#end
Now, when you need to "map" from people to their ages, you can just do this:
ages = [people mapWithSelector:#selector(age)];
OR
ages = [people mapWithBlock:^(Person *p) { return [p age]; }];
The result, ages, will be a new NSArray containing just the ages of the people. In general, this will work for any sort of simple mapping operations that you might need.
One caveat: Since it returns an NSArray, the elements inside ages should be NSNumber, not a plain old integer. So for this to work, your -age selector should return an NSNumber, not an int or NSInteger.
Assuming that each object has a method called "age" that returns an NSNumber *, you should be able to do something like the following:
-(NSArray *)createAgeArray:(NSArray *)peopleArray {
NSMutableArray *result = [[NSMutableArray alloc] initWithCapacity:[peopleArray count]];
[peopleArray enumerateObjectsUsingBlock:^(id person, NSUInteger i, BOOL *stop) {
[result addObject:[person age]];
}];
return [result autorelease];
}
Related
I have an NSArray of objects called MMPlace, which has NSArray of MMProduct objects.
How do I get a united NSArray of all MMProduct objects that my Array of MMPlace object contains? Something like NSArray *arr = [array valueForKeyPath:#"#unionOfObjects.products"]; would be nice, though this specific example doesn't work.
You can do this with #unionOfArrays. The bit you were missing is that because the arrays are directly nested, the key on the right of the collection operator must be self:
NSArray *nestedValues = #[#[#1, #2, #3], #[#4, #5, #6]]
NSArray *flattenedValues = [nestedValues valueForKeyPath:#"#unionOfArrays.self"];
// flattenedValues contains #[#1, #2, #3, #4, #5, #6]
Create an NSMutableArray, loop through your original array and call addObjectsFromArray: with each subarray.
I don't think there is an off-the-shelf method that does what you need, but you can easily "flatten" your array in a for loop, and hide the method in a category:
Edit: added a category.
#interface NSArray (flatten)
-(NSArray*) flattenArray;
#end
#implementation NSArray (flatten)
-(NSArray*) flattenArray {
// If inner array has N objects on average, multiply count by N
NSMutableArray *res = [NSMutableArray arrayWithCapacity:self.count];
for (NSArray *element in self) {
[res addObjectsFromArray:element];
}
return res;
}
#end
For this task there is the #unionOfArrays collection operator.
http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/KeyValueCoding/Articles/CollectionOperators.html#//apple_ref/doc/uid/20002176-BAJEAIEE
Here is how I implemented a "flatten" category method that will work on any tree structure. It will take a tree of arbitrary depth and merge it into one long array in order.
- (NSArray *) flattenWithBlock:(NSArray *(^)(id obj))block {
NSMutableArray *newItems = [NSMutableArray array];
for(id subObject in self)
{
if([subObject isKindOfClass:[NSArray class]])
{
[newItems addObjectsFromArray:[subObject flatten:block]];
}
else
[newItems addObject:subObject];
}
return newItems;
}
You could then write a convenience category method which handles the case you described above. This wrapper method will flatten a nd array into a 1d array.
- (NSArray *) flattenArray {
NSArray *newItems = [self flattenWithBlock:^NSArray *(id obj) {
return obj;
}];
return newItems;
}
#interface NSArray (Flatten)
-(NSArray*)flattenedArray;
#end
#implementation NSArray (Flatten)
-(NSArray*)flattenedArray {
NSMutableArray *result = [NSMutableArray arrayWithCapacity:self.count];
for (id thing in self) {
if ([thing isKindOfClass:[NSArray class]]) {
[result addObjectsFromArray:[(NSArray*)thing flattenedArray]];
} else {
[result addObject:thing];
}
}
return [NSArray arrayWithArray:result];
}
#end
Given an NSArray of NSDictionary 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)
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)
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 #
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.
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;
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"];
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).
#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.
Checks for nil—you can use nil to remove objects as you're mapping
Method name better reflects that the method doesn't modify the array it's called on
This is more a style thing but I've IMO improved the argument names of the block
Dot syntax for count
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.
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.
For the purpose of asking this question about ordering. The following MyObject class returns an instance with random generated category names.
I use the following dataSource methods:
numberOfSections accessed with [dataSource count].
titleForSection accessed with [[dataSource objectAtIndex:indexPath.section] valueForKey:#"categoryName"].
numberOfRowsInSection accessed with [[[dataSource objectAtIndex:indexPath.section] valueForKey:#"myObjects"] count].
And finally, the MyObject for each row is accessed with [[[dataSource objectAtIndex:indexPath.section] valueForKey:#"myObjects"] objectAtIndex:indexPath.row] on the cellForRowAtIndexPath method.
I use the following code to create a dataSource that displays 9 section categories, however I'm a little stuck on the ordering of these categories and the data within. Assume there's an NSDate property as part of the MyObject class.
Question: How would I go about using this to display the records in descending order?
- (void)createDatasource
{
NSInteger numberOfObjects = 10;
NSMutableArray *objects = [NSMutableArray arrayWithCapacity:numberOfObjects];
NSMutableArray *categories = [NSMutableArray arrayWithCapacity:numberOfObjects];
for (int i = 0; i < numberOfObjects; i++)
{
MyObject *obj = [[MyObject alloc] init];
[objects addObject:obj];
[categories addObject:obj.category];
[obj release];
}
NSSet *set = [NSSet setWithArray:categories];
NSMutableArray *dataSource = [[NSMutableArray alloc] initWithCapacity:[set count]];
for (NSString *categoryString in set)
{
NSMutableDictionary *mainItem = [[NSMutableDictionary alloc] initWithObjectsAndKeys:nil, #"categoryName", nil, #"myObjects", nil];
NSMutableArray *mainItemMyObjects = [NSMutableArray array];
[mainItem setValue:categoryString forKey:#"categoryName"];
for (MyObject *obj in objects)
{
if ([obj.category isEqualToString:categoryString])
{
[mainItemMyObjects addObject:obj];
}
}
[mainItem setValue:mainItemMyObjects forKey:#"myObjects"];
[dataSource addObject:mainItem];
[mainItem release];
}
NSLog (#"objects = %#\ncategories = %#\nset = %#\ndatasource = %#", objects, categories, set, dataSource);
}
Easiest would be to sort your arrays, using NSMutableArray's sorting mutators or NSArray's sorting methods. Otherwise you'd have to construct some sort of mapping from input indices to dataSource indices for use by the various data source methods.
Edit Requested sample code for sorting, something like this should work. I assume you are wanting to sort everything by a property named date on the MyObject.
// First, sort the myObject mutable array in each category
for (NSDictionary *d in dataSource) {
[[d valueForKey:#"myObjects"] sortUsingComparator:^NSComparisonResult(id o1, id o2){
// Compare dates. NSDate's 'compare:' would do ascending order, so if we just
// reverse the order of comparison they'll come out descending.
return [[o2 date] compare:[o1 date]];
}];
}
// Second, sort the categories by the earliest dated object they contain
[dataSource sortUsingComparator:^NSComparisonResult(id o1, id o2){
// Extract the first object from each category's array, which must be the
// earliest it contains due to the previous sort.
MyObject *myObject1 = [[o1 valueForKey:#"myObjects"] objectAtIndex:0];
MyObject *myObject2 = [[o2 valueForKey:#"myObjects"] objectAtIndex:0];
// Compare dates, as above.
return [[myObject2 date] compare:[myObject1 date]];
}];
I'm storing a bunch of data in a .plist file (in the application documents folder), and it's structured like this:
Dictionary {
"description" = "String Value",
"sections" = Array (
Array (
Number,
...
Number
),
Array (
Number,
...
Number
)
),
"items" = Array (
Array (
Number,
...
Number
),
Array (
Number,
...
Number
)
)
}
If I just retrieve it with
NSMutableDictionary *d = [[NSMutableDictionary alloc] initWithContentsOfFile:plistFile]
I won't be able to replace the number objects, correct?
So I'm recursing through the data right now and forming a mutable version of the whole thing, and it worked in one instance, but now it's telling me mutating method sent to immutable object when the whole thing is mutable.
Is there an easier/better way to do this? If it makes a difference, my data is just integers and booleans.
Instead of writing all that custom class junk, you should use NSPropertyListSerialization. Specifically, see the propertyListWithData:options:format:error: method. Example usage:
NSMutableDictionary *d = [NSPropertyListSerialization propertyListWithData:[NSData dataWithContentsOfFile:#"path/to/file"]
options:NSPropertyListMutableContainers
format:NULL
error:NULL];
This will make all the containers mutable, but keep the leaf nodes (e.g. NSStrings) immutable. There's also an option to make the leaves mutable too.
I usually find it easier to create one or more custom classes to handle loading and saving. This lets you convert the arrays to mutableArrays explicitly:
MyThing.h
#interface MyThing : NSObject
{
NSString * description;
NSMutableArray * sections;
NSMutableArray * items;
}
#property (copy) NSString * description;
#property (readonly) NSMutableArray * sections;
#property (readonly) NSMutableArray * items;
- (void)loadFromFile:(NSString *)path;
- (void)saveToFile:(NSString *)path;
#end
MyThing.m
#implementation MyThing
#synthesize description;
#synthesize sections
#synthesize items;
- (id)init {
if ((self = [super init]) == nil) { return nil; }
sections = [[NSMutableArray alloc] initWithCapacity:0];
items = [[NSMutableArray alloc] initWithCapacity:0];
return self;
}
- (void)dealloc {
[items release];
[sections release];
}
- (void)loadFromFile:(NSString *)path {
NSDictionary * dict = [NSDictionary dictionaryWithContentsOfFile:path];
[self setDescription:[dict objectForKey:#"description"]];
[sections removeAllObjects];
[sections addObjectsFromArray:[dict objectForKey:#"sections"]];
[items removeAllObjects];
[items addObjectsFromArray:[dict objectForKey:#"items"]];
}
- (void)saveToFile:(NSString *)path {
NSDictionary * dict = [NSDictionary dictionaryWithObjectsAndKeys:
description, #"description",
sections, #"sections",
items, #"items",
nil];
[dict writeToFile:path atomically:YES];
}
#end;
With that done, you can encapsulate all of the packaging and unpackaging code in your loadFromFile and saveToFile methods. The major benefit of this approach is that your main program gets a lot simpler, and it allows you to access the elements of your data structure as properties:
MyThing * thing = [[MyThing alloc] init];
[thing loadFromFile:#"..."];
...
thing.description = #"new description";
[thing.sections addObject:someObject];
[thing.items removeObjectAtIndex:4];
...
[thing saveToFile:#"..."];
[thing release];
What you want is a deep mutable copy. Cocoa doesn't include a way to do it. A few people have written such deep-copy implementations before (example).
However, Core Foundation includes the CFPropertyList API, which does have support both for creating deep mutable copies of property list objects as well as reading in property lists from disk as mutable datatypes. (And, of course, Core Foundation's property list types are toll-free bridged with Cocoa's, meaning you don't have to convert between them — an NSArray is a CFArray and vice-versa.)
I'm working on implementing a customized searchBar for a fairly complex table and have come across this code pattern AGAIN. This is a sample from the Beginning iPhone Development book:
- (void)handleSearchForTerm:(NSString *)searchTerm
{
NSMutableArray *sectionsToRemove = [[NSMutableArray alloc] init];
[self resetSearch];
for (NSString *key in self.keys)
{
NSMutableArray *array = [self.names valueForKey:key];
NSMutableArray *toRemove = [[NSMutableArray alloc] init];
for (NSString *name in array)
{
if ([name rangeOfString:searchTerm
options:NSCaseInsensitiveSearch].location == NSNotFound)
[toRemove addObject:name];
}
if ([array count] == [toRemove count])
[sectionsToRemove addObject:key];
[array removeObjectsInArray:toRemove];
[toRemove release];
}
[self.keys removeObjectsInArray:sectionsToRemove];
[sectionsToRemove release];
[table reloadData];
}
The part I'm curious about is the "for (NSString *name in array)" section. What is this doing exactly? It seems to create a string for every item in the array. Also, how does this work with dictionaries?
Thanks!
This construct is a different kind of for loop that runs over items in an Objective-C collection, rather than a C array. The first part defines an object that is being set to one element in the collection each run of the loop, while the second part is the collection to enumerate. For example, the code:
NSArray *array = [NSArray arrayWithObjects:#"foo", #"bar", nil];
for(NSString *string in array) {
NSLog(string);
}
would print:
foo
bar
It's defining an NSString *string that, each run of the loop, gets set to the next object in the NSArray *array.
Similarly, you can use enumeration with instances of NSSet (where the order of objects aren't defined) and NSDictionary (where it will enumerate over keys stored in the dictionary - you can enumerate over the values by enumerating over keys, then calling valueForKey: on the dictionary using that key).
It's extremely similar to the construct in C:
int array[2] = { 0, 1 };
for(int i = 0; i < 2; i++) {
printf("%d\n", array[i]);
}
which prints:
0
1
It's just a syntactical way of making the code more readable and hiding some of the fancy enumeration that goes into listing objects in an NSArray, NSSet, or NSDictionary. More detail is given in the Fast Enumeration section of The Objective-C 2.0 Programming Language document.
This is called fast enumeration. It loops through the array, setting key to each item. It's the same, functionally, as doing this:
NSString *key;
for ( NSInteger i = 0; i < [[ self keys ] count ]; i++ ) {
key = [[ self keys ] objectAtIndex:i ];
NSMutableArray *array = [self.names valueForKey:key];
NSMutableArray *toRemove = [[NSMutableArray alloc] init];
for (NSString *name in array)
{
if ([name rangeOfString:searchTerm
options:NSCaseInsensitiveSearch].location == NSNotFound)
[toRemove addObject:name];
}
if ([array count] == [toRemove count])
[sectionsToRemove addObject:key];
[array removeObjectsInArray:toRemove];
[toRemove release];
}
It's a for loop with one iteration for each key in the dictionary.
The for..in construct is called Fast enumeration. You can read more about it in Objective-C 2.0 Programming Guide.
How it works with an object depends on it's implementation of the NSFastEnumeration protocol. The NSDictionary class reference describes how it works with dictionaries:
On Mac OS X v10.5 and later, NSDictionary supports the NSFastEnumeration protocol. You can use the for…in construct to enumerate the keys of a dictionary, as illustrated in the following example.