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
Related
I have a NSArray with a lot of entries. But some of them are twice.
And i want that every entry is only once in the Array.
Have somebody an idea how i can do this?
This is how i've tried it:
NSSet *newsSet = [NSSet setWithArray:news];
newsOrte = [newsSet allObject];
In order to use NSSet effectively the object being stored must conform to the NSObject protocol and implement the hash (reference) and isEqual: (reference) methods.
Please ensure your object implements these methods.
Try this way
NSArray *array=[[NSMutableArray alloc]initWithObjects:#"A",#"B",#"A",#"C",#"A", nil];
NSMutableArray *arr=[NSMutableArray new];
for(id obj in array){
if (![arr containsObject:obj]) {
[arr addObject:obj];
}
}
array=arr;
NSLog(#"==> %#",array);
Also you can do in this way:
NSArray *array=[[NSMutableArray alloc]initWithObjects:#"A",#"B",#"A",#"C",#"A", nil];
NSMutableDictionary *dict=[[NSMutableDictionary alloc]initWithObjects:array forKeys:array];
array=[dict allKeys];
NSLog(#"==> %#",array);
Output :
==> (
A,
B,
C
)
Here is code
NSArray* originalArray = ... // However you fetch it
NSMutableSet* existingNames = [NSMutableSet set];
NSMutableArray* filteredArray = [NSMutableArray array];
for (id object in originalArray) {
if (![existingNames containsObject:[object name]]) {
[existingNames addObject:[object name]];
[filteredArray addObject:object];
}
}
originalArray = [NSArray arrayWithArray:filteredArray];
Hope it helps you..
The title pretty much says it all, but just to clarify: I have an NSMutableDictonary containing several NSMutableArrays. What I would like to do is find any value that is present in multiple arrays (there will not be any duplicates in a single array) and return that value. Can someone please help? Thanks in advance!
Edit: For clarity's sake I will specify some of my variables:
linesMutableDictionary contains a list of Line objects (which are a custom NSObject subclass of mine)
pointsArray is an array inside each Line object and contains the values I am trying to search through.
Basically I am trying to find out which lines share common points (the purpose of my app is geometry based)
- (NSValue*)checkForDupes:(NSMutableDictionary*)dict {
NSMutableArray *derp = [NSMutableArray array];
for (NSString *key in [dict allKeys]) {
Line *temp = (Line*)[dict objectForKey:key];
for (NSValue *val in [temp pointsArray]) {
if ([derp containsObject:val])
return val;
}
[derp addObjectsFromArray:[temp pointsArray]];
}
return nil;
}
this should work
If by duplicates you mean returning YES to isEqual: you could first make an NSSet of all the elements (NSSet cannot, by definition, have duplicates):
NSMutableSet* allElements = [[NSMutableSet alloc] init];
for (NSArray* array in [dictionary allValues]) {
[allElements addObjectsFromArray:array];
}
Now you loop through the elements and check if they are in multiple arrays
NSMutableSet* allDuplicateElements = [[NSMutableSet alloc] init];
for (NSObject* element in allElements) {
NSUInteger count = 0;
for (NSArray* array in [dictionary allValues]) {
if ([array containsObject:element]) count++;
if (count > 1) {
[allDuplicateElements addObject:element];
break;
}
}
}
Then you have your duplicate elements and don't forget to release allElements and allDuplicateElements.
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];
}
How can one deep-copy the contents of an NSMutableDictionary to another NSMutableDictionary?
I do not want to use initWithDictionary:copyItems: method as my dictionaries are already initialized.
This might work:
- (void)addEntriesFromDictionary:(NSDictionary *)otherDictionary
This copies the Keys, But not the values, of an NSDictionary. Of course, If you need a deep copy, take a look at this:
#interface NSDictionary(rross)
- (void)addEntriesFromDictionary:(NSDictionary *)otherDictionary copyItems:(BOOL) copyItems;
#end
#implementation NSDictionary(rross)
- (void)addEntriesFromDictionary:(NSDictionary *)otherDictionary copyItems:(BOOL) copyItems
{
if (copyItems)
{
// get the keys
NSArray *oldKeys = [otherDictionary allKeys];
for (int i = 0; i < oldKeys.count; i++)
{
// add all the new key / value pairs
id key = [oldKeys objectAtIndex:i];
[self setObject:[[[otherDictionary objectForKey:key] copy] autorelease] forKey:[[key copy] autorelease]];
}
}
else
{
[self addEntriesFromOtherDictionary:otherDictionary];
}
}
#end
I have a feeling that this is stupid question, but I'll ask anyway...
I have a collection of NSDictionary objects whose key/value pairs correspond to a custom class I've created, call it MyClass. Is there an easy or "best practice" method for me to basically do something like MyClass * instance = [map NSDictionary properties to MyClass ];? I have a feeling I need to do something with NSCoding or NSKeyedUnarchiver, but rather than stumble through it on my own, I figure someone out there might be able to point me in the right direction.
The -setValuesForKeysWithDictionary: method, along with -dictionaryWithValuesForKeys:, is what you want to use.
Example:
// In your custom class
+ (id)customClassWithProperties:(NSDictionary *)properties {
return [[[self alloc] initWithProperties:properties] autorelease];
}
- (id)initWithProperties:(NSDictionary *)properties {
if (self = [self init]) {
[self setValuesForKeysWithDictionary:properties];
}
return self;
}
// ...and to easily derive the dictionary
NSDictionary *properties = [anObject dictionaryWithValuesForKeys:[anObject allKeys]];
There is no allKeys on NSObject. You'll need to create an extra category on NSObject like below:
NSObject+PropertyArray.h
#interface NSObject (PropertyArray)
- (NSArray *) allKeys;
#end
NSObject+PropertyArray.m
#import <objc/runtime.h>
#implementation NSObject (PropertyArray)
- (NSArray *) allKeys {
Class clazz = [self class];
u_int count;
objc_property_t* properties = class_copyPropertyList(clazz, &count);
NSMutableArray* propertyArray = [NSMutableArray arrayWithCapacity:count];
for (int i = 0; i < count ; i++) {
const char* propertyName = property_getName(properties[i]);
[propertyArray addObject:[NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding]];
}
free(properties);
return [NSArray arrayWithArray:propertyArray];
}
#end
Example:
#import "NSObject+PropertyArray.h"
...
MyObject *obj = [[MyObject alloc] init];
obj.a = #"Hello A"; //setting some values to attributes
obj.b = #"Hello B";
//dictionaryWithValuesForKeys requires keys in NSArray. You can now
//construct such NSArray using `allKeys` from NSObject(PropertyArray) category
NSDictionary *objDict = [obj dictionaryWithValuesForKeys:[obj allKeys]];
//Resurrect MyObject from NSDictionary using setValuesForKeysWithDictionary
MyObject *objResur = [[MyObject alloc] init];
[objResur setValuesForKeysWithDictionary:objDict];
Assuming that your class conforms to the Key-Value Coding protocol, you could use the following: (defined as a category on NSDictionary for convenience):
// myNSDictionaryCategory.h:
#interface NSDictionary (myCategory)
- (void)mapPropertiesToObject:(id)instance
#end
// myNSDictionaryCategory.m:
- (void)mapPropertiesToObject:(id)instance
{
for (NSString * propertyKey in [self allKeys])
{
[instance setValue:[self objectForKey:propertyKey]
forKey:propertyKey];
}
}
And here's how you would use it:
#import "myNSDictionaryCategory.h"
//...
[someDictionary mapPropertiesToObject:someObject];
If your doing this sort of thing chances are your dealing with JSON and you should probably have a look at Mantle
https://github.com/Mantle/Mantle
You will then get a convenient method dictionaryValue
[anObject dictionaryValue];
Just add category for NSObject for getting dictionaryRepresentation from your custom objects (in my case using in JSON serialization only):
// NSObject+JSONSerialize.h
#import <Foundation/Foundation.h>
#interface NSObject(JSONSerialize)
- (NSDictionary *)dictionaryRepresentation;
#end
// NSObject+JSONSerialize.m
#import "NSObject+JSONSerialize.h"
#import <objc/runtime.h>
#implementation NSObject(JSONSerialize)
+ (instancetype)instanceWithDictionary:(NSDictionary *)aDictionary {
return [[self alloc] initWithDictionary:aDictionary];
}
- (instancetype)initWithDictionary:(NSDictionary *)aDictionary {
aDictionary = [aDictionary clean];
self.isReady = NO;
for (NSString* propName in [self allPropertyNames]) {
[self setValue:aDictionary[propName] forKey:propName];
}
//You can add there some custom properties with wrong names like "id"
//[self setValue:aDictionary[#"id"] forKeyPath:#"objectID"];
self.isReady = YES;
return self;
}
- (NSDictionary *)dictionaryRepresentation {
NSMutableDictionary *result = [NSMutableDictionary dictionary];
NSArray *propertyNames = [self allPropertyNames];
id object;
for (NSString *key in propertyNames) {
object = [self valueForKey:key];
if (object) {
[result setObject:object forKey:key];
}
}
return result;
}
- (NSArray *)allPropertyNames {
unsigned count;
objc_property_t *properties = class_copyPropertyList([self class], &count);
NSMutableArray *rv = [NSMutableArray array];
unsigned i;
for (i = 0; i < count; i++) {
objc_property_t property = properties[i];
NSString *name = [NSString stringWithUTF8String:property_getName(property)];
[rv addObject:name];
}
//You can add there some custom properties with wrong names like "id"
//[rv addObject:#"objectID"];
//Example use inside initWithDictionary:
//[self setValue:aDictionary[#"id"] forKeyPath:#"objectID"];
free(properties);
return rv;
}
#end
Also, you can see that my solution will not work with custom objects with nested objects or arrays. For Arrays - just change the lines of code in dictionaryRepresentation method:
if (object) {
if ([object isKindOfClass:[NSArray class]]) {
#autoreleasepool {
NSMutableArray *array = [NSMutableArray array];
for (id item in (NSArray *)object) {
[array addObject:[item dictionaryRepresentation]];
}
[result setObject:array forKey:key];
}
} else {
[result setObject:object forKey:key];
}
}