Unable to sort NSMutableArray of Custom Objects - objective-c

I've got an issue trying to sort an array of custom objects. It's looking as if my arrays aren't even hitting the sorting code, but rather simply just returning the array itself.
I have the following setup:
SearchResult : NSObject
--
Document : SearchResult
Tag : SearchResult
Folder : SearchResult
My code is getting returns as SearchResults then trying to compare them all with a key, name that is defined in the SearchResult implementation.
-(void) parseFolderContents:(NSDictionary *) data
{
NSMutableArray *searchResults = [[NSMutableArray alloc] init];
NSMutableArray *documents = [[NSMutableArray alloc] init];
NSMutableArray *folders = [[NSMutableArray alloc] init];
NSMutableArray *tags = [[NSMutableArray alloc] init];
NSArray *results = [data objectForKey:#"items"];
for (int i = 0; i < [results count]; i++)
{
SearchResult *result = (SearchResult *)[KTParser parseSearchResult:[results objectAtIndex:i]];
if ([result.type isEqualToString:#"document"]){
[documents addObject:result];
}
else if ([result.type isEqualToString:#"folder"])
{
[folders addObject:result];
}
else if ([result.type isEqualToString:#"tag"])
{
[tags addObject:result];
}
}
if ([documents count] > 0)
[searchResults addObject:documents];
if ([folders count] > 0)
[searchResults addObject:folders];
if ([tags count] > 0)
[searchResults addObject:tags];
....
So that's the code used to populate the array, which isn't anything special. I have tried each of these ways to compare the array. None have worked. Does anyone know where I'm going wrong?
First attempt:
NSSortDescriptor *sortDescriptor;
sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name"
ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray *sortedResults;
sortedResults = [searchResults sortedArrayUsingDescriptors:sortDescriptors];
Second attempt:
NSArray *sortedResults;
sortedResults = [searchResults sortedArrayUsingSelector:#selector(compare:)];
(implementing custom compare method on SearchResult/Document.m)
- (NSComparisonResult)compare:(SearchResult *)otherResult{
return [self.name compare:otherResult.name];
}
Third attempt:
sortedResults = (NSMutableArray *)[searchResults sortedArrayUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES selector:#selector(caseInsensitiveCompare:)]]];
Fourth attempt I tried using a block. I even tried putting code in to manipulate the sorting of it, which didn't work either. The array returned was exactly the same as the original:
sortedArray = [searchResults sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
if ([(SearchResult *)a itemId] < 20000 )
return NSOrderedAscending;
else
return NSOrderedDescending;
}];
Anyone have any ideas?

Found out the issue - I was adding arrays as objects instead of adding each object into the array. This was then calling sort on NSArray, instead of my custom objects. Changing the above code from [searchResults addObject:documents]; to [searchResults addObjectsFromArray:documents]; solved the issue!

Related

sort a dictionary by a key from an inner dictionary

i have a dictionary which i want to sort it according to a key from an inner dictionary. Each key in the super dictionary has a dictionary as value. As an example to illustrate what i'm talking about, here is a super dictionary with inner dictionaries respectively to each key.
{
key1 = {count = 2},
key2 = {count = 1}
}
thus the count key has to be the key used for the sorting. For now i know only how to sort arrays and i didn't encounter sorting dictionaries before. Any help will be appreciated.
get the array out of dictionary & sort it
NSArray* values = [myDict allValues];
NSArray* sortedValues = [values sortedArrayUsingSelector:#selector(comparator)];
another way to do it to make NSSortDescriptor
keyDescriptor = [[[NSSortDescriptor alloc] initWithKey:#"count" ascending:YES] autorelease];
sortDescriptors = [NSArray arrayWithObject:keyDescriptor];
sortedArray = [myArray sortedArrayUsingDescriptors:sortDescriptors];
I pulled the code from Sort Descriptor Programming Topics. Also, Key-Value Coding comes into play, in that sortedArrayUsingDescriptors: will send a valueForKey: to each element in myArray, and then use standard comparators to sort the returned values.
Here's one way:
NSDictionary * dict1 = [[NSDictionary alloc]initWithObjectsAndKeys:[NSNumber numberWithInt:2], #"count", nil];
NSDictionary * dict2 = [[NSDictionary alloc]initWithObjectsAndKeys:[NSNumber numberWithInt:3], #"count", nil];
NSArray * arrayOfDictionaries = [[NSArray alloc]initWithObjects:dict1, dict2, nil];
NSArray * sortedArray = [arrayOfDictionaries sortedArrayUsingComparator:^NSComparisonResult(NSDictionary * obj1, NSDictionary * obj2) {
// are points equal?
if ([obj1[#"count"] intValue] != [obj2[#"count"] intValue]) {
// points not equal, compare points
if ([obj1[#"count"] intValue] > [obj2[#"count"] intValue])
return (NSComparisonResult)NSOrderedAscending;
else
return (NSComparisonResult)NSOrderedDescending;
}
else {
return (NSComparisonResult)NSOrderedSame;
}
}];
Let me know how it goes!
Okay, just to add to what others had posted (Thank you!) i also found my own solution which goes like this:
NSArray *keysByFrequency = [object keysSortedByValueUsingComparator:^NSComparisonResult(NSDictionary* obj1, NSDictionary* obj2) {
return [obj1[#"count"] compare:obj2[#"count"]];
}];
NSMutableArray *tags = [[NSMutableArray alloc] init];
for (int i = keysByFrequency.count-1; i >= 0; i--) {
[tags addObject:#{#"tag" : keysByFrequency[i], #"count" : object[keysByFrequency[i]][#"count"], #"type" : object[keysByFrequency[i]][#"type"]}];
}

Sort NSArray of custom objects based on sorting of another NSArray of strings

I have two NSArray objects that I would like to be sorted the same. One contains NSString objects, the other custom Attribute objects. Here is what my "key" NSArray looks like:
// The master order
NSArray *stringOrder = [NSArray arrayWithObjects:#"12", #"10", #"2", nil];
The NSArray with custom objects:
// The array of custom Attribute objects that I want sorted by the stringOrder array
NSMutableArray *items = [[NSMutableArray alloc] init];
Attribute *attribute = nil;
attribute = [[Attribute alloc] init];
attribute.assetID = #"10";
[items addObject:attribute];
attribute = [[Attribute alloc] init];
attribute.assetID = #"12";
[items addObject:attribute];
attribute = [[Attribute alloc] init];
attribute.assetID = #"2";
[items addObject:attribute];
So, what I would like to do is use the stringOrder array to determine the sorting of the items array of custom objects.
How can I do this?
Hereby, I compare directly the index of obj1.assetID in stringOrder with the index of obj2.assetID in stringOrder (using Objective-C literals for #() to transform NSString => NSNumber)
[items sortUsingComparator:^NSComparisonResult(Attribute *obj1, Attribute *obj2) {
return [#([stringOrder indexOfObject:obj1.assetID]) compare:#([stringOrder indexOfObject:obj2.assetID])]
}];
Or without ObjC literals :
[items sortUsingComparator:^NSComparisonResult(Attribute *obj1, Attribute *obj2) {
return [[NSNumber numberWithInt:[stringOrder indexOfObject:obj1.assetID]] compare:[NSNumber numberWithInt:[stringOrder indexOfObject:obj2.assetID]]]
}];
While cwehrungs answer will get the job done, the performance is not great on relatively small arrays.
Here is another method for performing the same kind of sort that is a bit quicker (though still far from perfect):
NSMutableArray *sorted = [NSMutableArray array];
// pre-populate with objects
for (int i = 0; i < stringOrder.count; i++)
{
[sorted addObject:[NSNull null]];
}
// place the items at the correct position
for (Attribute *a in items)
{
NSUInteger idx = [stringOrder indexOfObject:a.assetID];
if (idx != NSNotFound)
{
[sorted setObject:a atIndexedSubscript:idx];
}
}
// finally remove all the unecesarry placeholders if one array was smaller
[sorted removeObject:[NSNull null]];
Comparison
Here are the results form running the two methods on an iPhone 5:
sortUsingComparator:
100 - 0.012 s
1000 - 1.116 s
2000 - 4.405 s
3000 - 9.028 s
prepopulated array
100 - 0.003 s
1000 - 0.236 s
2000 - 0.917 s
3000 - 2.063 s
There are a couple approaches you could take.
You could store your Attribute objects in an NSDictionary, with the keys being the strings in your stringOrder array. Then, you could get a sorted array of the keys and use that to populate whatever view you're using to display them:
NSArray* sortedKeys = [dict keysSortedByValueUsingComparator:^(id obj1, id obj2) {
return [obj1 compareTo:obj2];
}
The other is that you make the sort order an intrinsic property of your Attribute object, so an array of Attributes can be sorted directly. I would only recommend taking this approach if the sort order is actually an intrinsic property of your Attributes object. If it isn't and you do this, you'll wind up storing presentation information where it doesn't belong.
Here's an example:
NSArray* sortedAttrs = [attributes sortedArrayUsingComparator:^(id obj1, id obj2) {
// Perform comparison of Attribute's, ahem, attributes
}
Here is the solution that I came up with that works extremely well. Anyone see performance issues with this?
for (Attribute *a in items) {
int index = [stringOrder indexOfObject:a.assetID];
a.sortOrder = index;
}
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"sortOrder" ascending:YES];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
NSArray *sortedArray = [items sortedArrayUsingDescriptors:sortDescriptors];
Parallel Processing:
Results (quad core):
1. sortme:95 sortby:852345 sorted:95 time:0.052576
2. sortme:54248 sortby:852345 sorted:54243 time:0.264660
-(NSArray *)sortArray:(NSArray *)sortme sortBy:(NSArray *)sortBy{
CFAbsoluteTime time = CFAbsoluteTimeGetCurrent();
NSSet *sortmeSet = [NSSet setWithArray:sortme];
NSMutableDictionary *sortDictionary = [NSMutableDictionary dictionary];
dispatch_queue_t sortDictionaryThread = dispatch_queue_create("my.sortDictionaryThread", DISPATCH_QUEUE_CONCURRENT);
[sortBy enumerateObjectsWithOptions:NSEnumerationConcurrent usingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([sortmeSet containsObject:obj]){
dispatch_barrier_async(sortDictionaryThread, ^{
sortDictionary[obj] = #(idx);
});
}
}];
__block NSArray *sortedArray = nil;
dispatch_barrier_sync(sortDictionaryThread, ^{
sortedArray = [sortDictionary keysSortedByValueUsingSelector:#selector(compare:)];
});
NSLog(#"sortme:%li sortby:%li sorted:%li time:%f",sortme.count,sortBy.count,sortedArray.count, CFAbsoluteTimeGetCurrent() - time);
return sortedArray;
}

Fast way to search the properties of objects in an NSArray

I have an NSArray of custom objects that all have a #property name of type NSString. How can I quickly enumerate through the array and create a new array that contains only the objects that have a specific word in their name property?
For instance:
CustomObject *firstObject = [[CustomObject alloc] init];
firstObject.name = #"dog";
CustomObject *secondObject = [[CustomObject alloc] init];
secondObject.name = #"cat";
CustomObject *thirdObject = [[CustomObject alloc] init];
thirdObject.name = #"dogs are fun";
NSMutableArray *testArray = [NSMutableArray arrayWithObjects:firstObject,
secondObject,
thirdObject,
nil];
// I want to create a new array that contains all objects that have the word
// "dog" in their name property.
I know I could use a for loop like so:
NSMutableArray *newArray = [NSMutableArray array];
for (CustomObject *obj in testArray)
{
if ([obj.name rangeOfString:#"dog"].location == NSNotFound) {
//string wasn't found
}
else {
[newArray addObject:obj];
}
}
But is there a more efficient way? Thanks!
NSString *searchString = #"dog";
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.name contains %#", searchString];
NSArray *filteredArray = [testArray filteredArrayUsingPredicate:predicate];
Please have a look at NSPredicates ! They are highly efficient when you are searching / filtering through array results. This is the documentation!

Getting NSDictionary keys sorted by their respective values

I have an NSMutableDictionary with integer values, and I'd like to get an array of the keys, sorted ascending by their respective values. For example, with this dictionary:
mutableDict = {
"A" = 2,
"B" = 4,
"C" = 3,
"D" = 1,
}
I'd like to end up with the array ["D", "A", "C", "B"]. My real dictionary is much larger than just four items, of course.
The NSDictionary Method keysSortedByValueUsingComparator: should do the trick.
You just need a method returning an NSComparisonResult that compares the object's values.
Your Dictionary is
NSMutableDictionary * myDict;
And your Array is
NSArray *myArray;
myArray = [myDict keysSortedByValueUsingComparator: ^(id obj1, id obj2) {
if ([obj1 integerValue] > [obj2 integerValue]) {
return (NSComparisonResult)NSOrderedDescending;
}
if ([obj1 integerValue] < [obj2 integerValue]) {
return (NSComparisonResult)NSOrderedAscending;
}
return (NSComparisonResult)NSOrderedSame;
}];
Just use NSNumber objects instead of numeric constants.
BTW, this is taken from:
https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/Collections/Articles/Dictionaries.html
NSDictionary has this neat method called allKeys.
If you want the array to be sorted though, keysSortedByValueUsingComparator: should do the trick.
Richard's solution also works but makes some extra calls you don't necessarily need:
// Assuming myDictionary was previously populated with NSNumber values.
NSArray *orderedKeys = [myDictionary keysSortedByValueUsingComparator:^NSComparisonResult(id obj1, id obj2){
return [obj1 compare:obj2];
}];
Here's a solution:
NSDictionary *dictionary; // initialize dictionary
NSArray *sorted = [[dictionary allKeys] sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
return [[dictionary objectForKey:obj1] compare:[dictionary objectForKey:obj2]];
}];
The simplest solution:
[dictionary keysSortedByValueUsingSelector:#selector(compare:)]
Here i have done something like this:
NSMutableArray * weekDays = [[NSMutableArray alloc] initWithObjects:#"Sunday",#"Monday",#"Tuesday",#"Wednesday",#"Thursday",#"Friday",#"Saturday", nil];
NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
NSMutableArray *dictArray = [[NSMutableArray alloc] init];
for(int i = 0; i < [weekDays count]; i++)
{
dict = [NSMutableDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithInt:i],#"WeekDay",[weekDays objectAtIndex:i],#"Name",nil];
[dictArray addObject:dict];
}
NSLog(#"Before Sorting : %#",dictArray);
#try
{
//for using NSSortDescriptor
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"WeekDay" ascending:YES];
NSArray *descriptor = #[sortDescriptor];
NSArray *sortedArray = [dictArray sortedArrayUsingDescriptors:descriptor];
NSLog(#"After Sorting : %#",sortedArray);
//for using predicate
//here i want to sort the value against weekday but only for WeekDay<=5
int count=5;
NSPredicate *Predicate = [NSPredicate predicateWithFormat:#"WeekDay <=%d",count];
NSArray *results = [dictArray filteredArrayUsingPredicate:Predicate];
NSLog(#"After Sorting using predicate : %#",results);
}
#catch (NSException *exception)
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Sorting cant be done because of some error" message:[NSString stringWithFormat:#"%#",exception] delegate:self cancelButtonTitle:#"Ok" otherButtonTitles:nil];
[alert setTag:500];
[alert show];
[alert release];
}

Sort an NSArray with NSDictionary

I'm using NSSortDiscriptors to sort an NSArray.
Every index in the array contains a dictionary with key,value entries. This dictionary also holds another dictionary.
My problem that I want solve is to sort the array based on values on the first dictionary, but also on some values in the dictionary it contains.
The following is the sort method I use for the other values in the array.
- (void)sortArray {
if ([array count] != 0) {
// Reports sortingDescriptors
NSSortDescriptor * city = [[NSSortDescriptor alloc] initWithKey:#"City" ascending:YES];
NSSortDescriptor * name = [[NSSortDescriptor alloc] initWithKey:#"Name" ascending:YES];
NSSortDescriptor * country = [[NSSortDescriptor alloc] initWithKey:#"Country" ascending:YES];
[reportsArray sortUsingDescriptors:[NSArray arrayWithObjects:city, name, country, nil]];
[name release];
[city release];
[country release];
}
}
The array looks like this:
[{name = "";
city = "";
country = "";
date = {
dateAdded = "";
dateRemoved = "";
}
}];
So I also want to sort on, if have value on dateAdded for example.
You can specify a key path when you create the NSSortDescriptor, this way you can sort the NSArray with a NSDictionary.
You might want to check if the dictionary contains a value like that :
// you can't set nil as a value for a key
if([yourDictionary objectForKey:#"yourKey"] == [NSNull null]) { ... }
Then you need to sort remaining objects but the dictionary, to do so, make a copy of your array without the dictionary entry by doing something like :
NSMutableArray *tmpArray = [NSMutableArray arrayWithArray:firstArray];
[tmpArray removeObjectAtIndex:theIndexOfTheDictionary];
// sort your array, don't forget to catch the returned value
NSMutableArray *sortedArray = [tmpArray sortUsingDescriptors:[NSArray arrayWithObjects:city, name, country, nil]];
// finally, put the dictionary back in (if needed)
[sortedArray insertObject:theDictionary atIndex:theIndexYouWant];
Are you saying that the objects in the array have a City, Name and Country property but also a dictionary property and you want to sort on one of the keys in the dictionary? Or are you saying that the entries in the array are dictionaries but sometimes the City, Name or Country key is missing? Or are you saying that some of the entries are dictionaries and some are objects with the listed properties?
In any case, you can get more flexibility by creating a sort descriptor using initWithKey:ascending:comparator:. This allows you to supply a comparator block as the sort function which is more flexible than a straight selector e.g.
NSComparator mySort = ^(id obj1, id obj2)
{
NSComparisonResult ret = NSOrderedSame;
if ([obj1 isKindOfClass: [NSDictionary class]] && ![obj2 isKindOfClass: NSDictionary class]])
{
ret = NSOrderedAscending;
}
else if (![obj1 isKindOfClass: [NSDictionary class]] && [obj2 isKindOfClass: NSDictionary class]])
{
ret = NSOrderedDescending;
}
return ret;
};
NSSortDescriptor* descriptor = [[NSSortDescriptor alloc] initWithKey: #"self" ascending: YES comparator: mySort];
will give you a sort descriptor that sorts the array putting all the NSDictionaries first and other objects afterwards. (self is a key possessed by all NSObjects that returns the object itself).
NSArray *unsortedArray=[NSArray arrayWithObjects:[NSDictionary dictionaryWithObjectsAndKeys:#"anil",#"personInDictionary.lastName" ,nil],[NSDictionary dictionaryWithObjectsAndKeys:#"aneelu",#"personInDictionary.lastName", nil] ,[NSDictionary dictionaryWithObjectsAndKeys:#"kumar",#"anil.lastName", nil] ,nil];
NSSortDescriptor * descriptor = [[[NSSortDescriptor alloc] initWithKey:#"personInDictionary.lastName" ascending:YES] autorelease]; // 1
NSArray * sortedArray = [unsortedArray sortedArrayUsingDescriptors:
[NSArray arrayWithObject:descriptor]];
NSLog(#"sortedArray values %#",sortedArray);
for (id object in [sortedArray valueForKey:#"personInDictionary.lastName"]) {
NSLog(#"sortedArray value %#",object);
}