Comparing 3 arrays of various NSObject Subclasses - objective-c

I have an NSDictionary which contains 3 NSArrays,
- posts
- comments
- likes.
And in each array are consistent NSObject Subclasses:
- Post
- Comment
- Like
Usually, I would just put all objects from these three arrays into one array and compare them using the same variable which they all contain, but in this case, Posts has the variable dateOfUpload and Like and Comment have the same variable, date.
How can I compare the objects from these three arrays using the variable date and dateOfUpload to create one big array of all objects in descending date?

I would make them all implement method like:
-(NSComparsionResult) compareByDate : (NSObject *) obj;
Of course you'll need to implement this in different way in each class.
Then make one big array from all the tree arrays and call
[myArray sortUsingSelector:#selector(compareByDate:)];

Another way to do this is to add all your objects in one big array and sort using a block as follows:
// Create the array with all the objects
NSMutableArray *stuff = [NSMutableArray arrayWithArray:posts.allValues];
[stuff addObjectsFromArray:comments.allValues];
[stuff addObjectsFromArray:likes.allValues];
// Sort it by using a block
NSArray *sortedStuff = [stuff sortedArrayUsingComparator:^(id obj1, id obj2) {
NSDate *date1 = [obj1 respondsToSelector:#selector(date)]? [obj1 date] : [obj1 dateOfUpload];
NSDate *date2 = [obj2 respondsToSelector:#selector(date)]? [obj2 date] : [obj2 dateOfUpload];
return [date2 compare:date1]; // Objects are reversed to get descending order
}];

Related

Objective-C NSArray remove object duplicates based on function

It is clear from this question that there are many ways to remove duplicates from an NSArray when the array's elements are primitive types, or when the elements are perfect duplicates. But, is there a way to remove duplicates based on a transformation applied to each element, as is permitted in Underscore.js's uniq function, rather than by simply comparing the whole elements? And if a manual implementation would be difficult to optimize, is there an efficient system-provided method (or 3rd party library algorithm) for accomplishing this that I am missing?
A simple approach:
NSMutableArray* someArray = something;
for (int i = someArray.count - 1; i > 0; i--) {
MyObject* myObject = someArray[i];
for (int j = 0; j < i; j++) {
MyObject* myOtherObject = someArray[j];
if ([myObject isSortaEqual:myOtherObject]) {
[someArray removeObjectAtIndex:i];
break;
}
}
}
Yes, it's N-squared, but that's not a biggie unless the array is fairly large.
If you want to redefine what equality means for your objects, then consider overriding -hash and -isEqual:. Then you can create an NSSet from your array if order is irrelevant, or an NSOrderedSet if it is relevant. Here's an example of a Person class where I want the name of the person to determine object equality.
#interface Person
#property (nonatomic, copy) NSString *name;
#end
#implementation Person
- (BOOL)isEqual:(id)object
{
Person *otherPerson = (Person *)object;
return [self.name isEqualToString:otherPerson.name];
}
- (NSUInteger)hash
{
return [self.name hash];
}
#end
Uniquing them now is rather easy:
NSArray *people = ...;
// If ordered is irrelevant, use an NSSet
NSSet *uniquePeople = [NSSet setWithArray:people];
// Otherwise use an NSOrderedSet
NSOrderedSet *uniquePeople = [NSOrderedSet orderedSetWithArray:people];
Absolutely. You are looking for a way to pass your own method for testing for uniqueness (at least, that's what the uniq function you refer to does).
indexesOfObjectsPassingTest: will allow you to pass your own block to determine uniqueness. The result will be an NSIndexSet of all the objects in the array that matched your test. With that you can derive a new array. The block you are passing is roughly equivalent to the Underscore iterator passed to uniq.
The sister method, indexesOfObjectsWithOptions:passingTest: also allows you to specify enumeration options (i.e. concurrent, reverse order, etc.).
As you mention in your question, there are lots of ways to accomplish this. NSExpressions with blocks, Key-value coding collections operators, etc. could be used for this as well. indexesOfObjectsPassingTest: is probably the closest to what you seem to be looking for, though you can do much the same thing (with a lot more typing) using expressions.
I just came up against this problem, so I wrote a category on NSArray:
#interface NSArray (RemovingDuplicates)
- (NSArray *)arrayByRemovingDuplicatesAccordingToKey:(id (^)(id obj))keyBlock;
#end
#implementation NSArray (RemovingDuplicates)
- (NSArray *)arrayByRemovingDuplicatesAccordingToKey:(id (^)(id obj))keyBlock
{
NSMutableDictionary *temp = [NSMutableDictionary dictionaryWithCapacity:[self count]];
for (NSString *item in self) {
temp[keyBlock(item)] = item;
}
return [temp allValues];
}
#end
You can use it like this (this example removes duplicate words, ignoring case):
NSArray *someArray = #[ #"dave", #"Dave", #"Bob", #"shona", #"bob", #"dave", #"jim" ];
NSLog(#"result: %#", [someArray arrayByRemovingDuplicatesAccordingToKey:^(id obj){
return [obj lowercaseString];
}]);
Output:
2015-02-17 17:44:10.268 Untitled[4043:7711273] result: (
dave,
shona,
jim,
bob
)
The 'key' is a block that returns an identifier used to compare the objects. So if you wanted to remove Person objects according to their name, you'd pass ^(id obj){ return [obj name]; }.
This solution is O(n), so is suitable to large arrays, but doesn't preserve order.

writing a plist in objective c

I know there are many topics with similar issues, but I have not been able to find a topic addressing my question.
I want to store a plist of highscores.
Every entry of highscores must have two elements
an NSString* and an int.
I want to store the top 20 high scores (pairs of strings and ints) and do that in a plist.
I start with:
NSMutableArray *arr = [[NSMutableArray alloc] initWithContentsOfFile: [[NSBundle mainBundle] pathForResource:#"Mylist" ofType:#"plist"]];
I want the item 0 of the array to be a dictionary, where I can insert key value pairs of
(string, int)
How do I do that?
You can always call [arr addObject:score];, sort it, and remove the final item until there are 10.
To sort:
[arr sortUsingComparator:^(id firstObject, id secondObject) {
NSDictionary *firstDict = (NSDictionary *)firstObject;
NSDictionary *secondDict = (NSDictionary *)secondObject;
int firstScore = [[firstDict objectForKey:#"score"] intValue];
int secondScore = [[secondDict objectForKey:#"score"] intValue];
return firstScore < secondScore ? NSOrderedAscending : firstScore > secondScore : NSOrderedDescending : NSOrderedSame;
}];
If you want the scores to be the other way around, change the '>' to '<' and vice-versa. To keep the list down to 10:
while ([arr count] > 10) {
[arr removeLastObject];
}
You may have to sort when you load from your plist. For 10 scores the performance hit will be minimal, so I suggest you do it just in case.
Property List Serialization
You will want to make notice of: the mutability option, as your method probably returns immutable arrays...
storing in a plist is done with the writeToFile:... or writeToURL:... methods
[arr insertObject:[NSMutableDictionary dictionary] atIndex:0];

NSMutableArray contains Objects

I have to check whether an NSMutableArray contains an object multiple times (for e.g. the array contains 1,2,3,1,4), I want to know how many times 1 is present in the array. I am aware of containsObject: but how to use it for this kind of check?
NSCountedSet may help as you want to track how many times a duplicate value occurs.
http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSCountedSet_Class/Reference/Reference.html#//apple_ref/occ/cl/NSCountedSet
A quick way would be to convert it to an NSSet and then back to an array. NSSets cannot contain duplicates. Alternatively copy the values one by one into a new array using a loop, and each time check that the new array does not contain a copy of the object before adding it.
It depends on your object types, but if they can be used as keys for an NSDictionary, I would create an NSMutableDictionary that points to NSNumber objects containing counts for each object instance. Something like:
NSArray *array = whatever;
NSMutableDictionary *d = [NSMutableDictionary dictionaryWithCapacity:array.count];
for ( id obj in array )
{
NSNumber *number = [d objectForKey:obj];
if ( number == nil )
{
[d setObject:[NSNumber numberWithInt:1] forKey:obj];
}
else
{
[d setObject:[NSNumber numberWithInt:([number intValue]+1) forKey:obj];
}
}
At the end of this code, you are left with an NSDictionary where the keys are your original objects and the values are NSNumbers that contain the number of times that key exists in the original.

How to use NSComparator?

I would like to know if the below question is possible using NSComparator or not?
I have two arrays; both hold data models. I have a property named rank in the data model. Now I want to compare both arrays and want to know if one of them holds higher ranked data models.
If Yes I would like to get NSComparisonResult = NSOrderedAscending.
By the way I'm using another approach here: is "total of each data Model's rank in array and if the total is greater than second array's data Model's total rank."
Yes, it would look something like this:
NSArray *someArray = /* however you get an array */
NSArray *sortedArray = [someArray sortedArrayUsingComparator:^(id obj1, id obj2) {
NSNumber *rank1 = [obj1 valueForKeyPath:#"#sum.rank"];
NSNumber *rank2 = [obj2 valueForKeyPath:#"#sum.rank"];
return (NSComparisonResult)[rank1 compare:rank2];
}];
(updated to show actually using the comparator)

Removing duplicates from array based on a property in Objective-C

I have an array with custom objects. Each array item has a field named "name". Now I want to remove duplicate entries based on this name value.
How should I go about achieving this?
I do not know of any standard way to to do this provided by the frameworks. So you will have to do it in code. Something like this should be doable:
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];
}
}
You might have to actually write this filtering method yourself:
#interface NSArray (CustomFiltering)
#end
#implementation NSArray (CustomFiltering)
- (NSArray *) filterObjectsByKey:(NSString *) key {
NSMutableSet *tempValues = [[NSMutableSet alloc] init];
NSMutableArray *ret = [NSMutableArray array];
for(id obj in self) {
if(! [tempValues containsObject:[obj valueForKey:key]]) {
[tempValues addObject:[obj valueForKey:key]];
[ret addObject:obj];
}
}
[tempValues release];
return ret;
}
#end
I know this is an old question but here is another possibility, depending on what you need.
Apple does provide a way to do this -- Key-Value Coding Collection Operators.
Object operators let you act on a collection. In this case, you want:
#distinctUnionOfObjects
The #distinctUnionOfObjects operator returns an array containing the distinct objects in the property specified by the key path to the right of the operator.
NSArray *distinctArray = [arrayWithDuplicates valueForKeyPath:#"#distinctUnionOfObjects.name"];
In your case, though, you want the whole object. So what you'd have to do is two-fold:
1) Use #distinctUnionOfArrays instead. E.g. If you had these custom objects coming from other collections, use #distinctUnionOfArray.myCollectionOfObjects
2) Implement isEqual: on those objects to return if their .name's are equal
I'm going to get flak for this...
You can convert your array into a dictionary. Not sure how efficient this is, depends on the implementation and comparison call, but it does use a hash map.
//Get unique entries
NSArray *myArray = #[#"Hello", #"World", #"Hello"];
NSDictionary *uniq = [NSDictionary dictionaryWithObjects:myArray forKeys:myArray];
NSLog(#"%#", uniq.allKeys);
*Note, this may change the order of your array.
If you'd like your custom NSObject subclasses to be considered equal when their names are equal you may implement isEqual: and hash. This will allow you to add of the objects to an NSSet/NSMutableSet (a set of distinct objects).
You may then easily create a sorted NSArray by using NSSet's sortedArrayUsingDescriptors:method.
MikeAsh wrote a pretty solid piece about implementing custom equality: Friday Q&A 2010-06-18: Implementing Equality and Hashing
If you are worried about the order
NSArray * newArray =
[[NSOrderedSet orderedSetWithArray:oldArray] array]; **// iOS 5.0 and later**
It is quite simple in one line
NSArray *duplicateList = ...
If you don't care about elements order then (unordered)
NSArray *withoutDUP1 = [[NSSet setWithArray:duplicateList] allObjects];
Keep the elements in order then (ordered)
NSArray *withoutDUP2 = [[NSOrderedSet orderedSetWithArray:duplicateList] array];
Implement isEqual to make your objects comparable:
#interface SomeObject (Equality)
#end
#implementation SomeObject (Equality)
- (BOOL)isEqual:(SomeObject*)other
{
return self.hash == other.hash;
}
- (NSUInteger)hash
{
return self.name;///your case
}
#end
How to use:
- (NSArray*)distinctObjectsFromArray:(NSArray*)array
{
return [array valueForKeyPath:#"#distinctUnionOfObjects.self"];
}