I can't wrap my head around how to do arbitrary sorting with a NSSortDescriptor.
I want to do something like this:
NSArray *sortAlgorithm = [NSArray arrayWithObjects:#"#", #"#", #"!", #"&", #"r", #"a", nil];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name"
ascending:YES
comparator:
^(id obj1, id obj2) {
NSComparisonResult comparisonResult;
//Some code that uses sortAlgorithm.
return comparisonResult;
}
];
This would sort the objects by the key name so that any key that starts with #, e.g. #home, would come before any key that starts with r, e.g. radical, and that again would come before any key that starts with a, e.g. anything.
The above is just an example. The point is to enable completely arbitrary sorting.
This is to be used for a NSFetchedResultsController.
What would the code for //Some code that uses sortAlgorithm look like?
EDIT:
Code surrounding my attempt to implement a sortDescriptor, as pr. occulus' suggestion:
- (NSFetchedResultsController *)fetchedResultsController {
if (__fetchedResultsController)
return __fetchedResultsController;
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
fetchRequest.entity = [NSEntityDescription entityForName:#"Tag" inManagedObjectContext:self.temporaryManagedObjectContext];
//NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:NO];
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:NO comparator:^(id obj1, id obj2) {
NSArray *sortAlgorithm = [NSArray arrayWithObjects:#"#", #"!", #"#", #".", nil];
NSString *obj1FirstChar = [(NSString *)obj1 substringToIndex:1];
NSString *obj2FirstChar = [(NSString *)obj2 substringToIndex:1];
int idx1 = [sortAlgorithm indexOfObject:obj1FirstChar];
int idx2 = [sortAlgorithm indexOfObject:obj2FirstChar];
if ( idx1 < idx2 )
return NSOrderedAscending;
else if ( idx1 > idx2 )
return NSOrderedDescending;
else
return NSOrderedSame;
}];
fetchRequest.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
fetchRequest.fetchBatchSize = 20;
NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.temporaryManagedObjectContext sectionNameKeyPath:nil cacheName:#"Tags"];
aFetchedResultsController.delegate = self;
self.fetchedResultsController = aFetchedResultsController;
[self.fetchedResultsController performFetch:nil];
return __fetchedResultsController;
}
The commented-out sortDescriptor works.
There is definitely a property called name on objects of entity "Tag". But even if there weren't, that doesn't seem to be the problem. Xcode doesn't seem to even be compiling that line of code (the sortDescriptor), which sounds ridiculous. Breakpoints are working just fine, but aren't breaking on that specific line of code.
You need to pull out the first characters of obj1 and obj2 strings as NSStrings, find their indexes in your arbitrary ordering array, then compare the positions.
Something like this: (put this code in your ^ block)
NSString *obj1FirstChar = [(NSString *)obj1 substringToIndex:1];
NSString *obj2FirstChar = [(NSString *)obj2 substringToIndex:1];
int idx1 = [sortAlgorithm indexOfObject:obj1FirstChar];
int idx2 = [sortAlgorithm indexOfObject:obj2FirstChar];
// NOTE: we haven't dealt with the case where one of the
// chars wasn't found in the array of characters. A strategy
// for this would need to be decided.
if (idx1 == idx2) {
return NSOrderedSame;
}
if (idx1 < idx2) {
return NSOrderedAscending;
}
return NSOrderedDescending;
Not tested, may require a little tweaking. Watch out for upper/lower case character differences.
UPDATE: It seems there are problems with using custom sort descriptors and SQL backed core data stores. Please see this question for more info.
Related
- (NSMutableArray *)sortMethod:(NSArray *)array SortingKey:(NSString *)key Ascending:(BOOL)ascending CaseInsensitiveCompare:(BOOL)caseInCompare
{
if(key && key.length > 0)
{
NSSortDescriptor *sortDescriptor = nil;
if(caseInCompare)
sortDescriptor = [[NSSortDescriptor alloc] initWithKey:key ascending:ascending selector:#selector(localizedCaseInsensitiveCompare:)];
else
sortDescriptor = [[NSSortDescriptor alloc] initWithKey:key ascending:ascending];
NSArray *sortedArray = [array sortedArrayUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
return [NSMutableArray arrayWithArray:sortedArray];
}
return nil;
}
my app is doing case-insensitive search for English language. but its not searching for only one Bulgarian character and that is "д". when we type "Д" in capital, it gives all the searches, but when we type that "д" in small, it is not giving any search results.
please give me a solution on this.
Thanks a lot.
As per the doc when we use custom selector then the objects which are compared against each other should implement the custom selector method and logic to return the result.
You construct instances of NSSortDescriptor by specifying the key path of the property to compare and the order of the sort (ascending or descending).
Optionally, you can also specify a selector to use to perform the comparison, which allows you to specify other comparison selectors, such as localizedStandardCompare: and localizedCaseInsensitiveCompare:. Sorting **raises an exception** if the objects don’t respond to the sort descriptor’s comparison selector.
As explained above we need to implement the sort descriptor’s comparison selector in TaggedDate
Update:
NSSortDescriptor can also be used with comparator instead of expecting the objects to implement the selector.
NSArray* wordsOfVaryingLength = #[#"crow",#"eagle",#"goose"];
NSComparisonResult (^stringLengthCompare)(NSString*, NSString*) = ^NSComparisonResult(NSString *stringOne, NSString *stringTwo) {
NSComparisonResult lengthCompare = NSOrderedSame;
if (stringOne.length < stringTwo.length)
lengthCompare = NSOrderedAscending;
if (stringOne.length > stringTwo.length)
lengthCompare = NSOrderedDescending;
return lengthCompare;
};
NSSortDescriptor *sortByLengthAsc = [NSSortDescriptor sortDescriptorWithKey:nil ascending:YES comparator:stringLengthCompare];
NSLog(#"From shortest word to longest: %#", [wordsOfVaryingLength sortedArrayUsingDescriptors:#[sortByLengthAsc]]);
NSSortDescriptor *sortByLengthDesc = [NSSortDescriptor sortDescriptorWithKey:nil ascending:NO comparator:stringLengthCompare];
NSLog(#"From longest word to shortest: %#", [wordsOfVaryingLength sortedArrayUsingDescriptors:#[sortByLengthDesc]]);
Here stringLengthCompare will have the comparison logic, similarly for the case sensitive or insensitive use case we need to put that logic here, taken from
let descriptor = NSSortDescriptor(key: "title", ascending: true) { (string1, string2) -> ComparisonResult in
guard let s1 = string1 as? String, let s2 = string2 as? String else {
return ComparisonResult.orderedSame
}
if s1.lowercased() < s2.lowercased() {
return ComparisonResult.orderedAscending
} else if s1.lowercased() == s2.lowercased() {
return ComparisonResult.orderedSame
} else {
return ComparisonResult.orderedDescending
}
}
I would like to sort an array using the dictionary values "Name" and "Count". It would be Name alphabetically and split the names up into two groupd based on count.
bigger than 0
Smaller than Equal 0
My current implementation looks like this however it dose not split the groups up correctly.
NSSortDescriptor *sortCountDescriptor = [[NSSortDescriptor alloc] initWithKey:#"count" ascending:NO];
NSSortDescriptor *sortNameDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:YES];
NSArray * sortDescriptors = [NSArray arrayWithObjects:sortCountDescriptor, sortNameDescriptor, nil];
NSArray *sortedArray = [myArrayToSort sortedArrayUsingDescriptors:sortDescriptors];
return [sortedArray mutableCopy];
If by grouping you mean making them separate arrays then you need an NSPredicate instead of NSSortDescriptor for count key.
Try this (from what I understood the array is filled with instances of NSDictionary so I used casting to it. If that assumption is incorrect, the NSPredicate isn't hard to change to some other type or to be made more generic with KVC):
NSSortDescriptor *sortNameDescriptor = [[NSSortDescriptor alloc] initWithKey:#"name" ascending:NO];
NSArray * sortDescriptors = [NSArray arrayWithObjects:sortNameDescriptor, nil];
NSArray *sortedArray = [myArrayToSort sortedArrayUsingDescriptors:#[sortNameDescriptor]];
NSPredicate *zeroOrLessPredicate = [NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) {
if ([[((NSDictionary*)evaluatedObject) objectForKey:#"count"] integerValue] <= 0) {
return YES;
}
else {
return NO;
}
}];
NSArray *zeroOrLessArray = [sortedArray filteredArrayUsingPredicate:zeroOrLessPredicate];
NSPredicate *moreThanZeroPredicate = [NSCompoundPredicate notPredicateWithSubpredicate:zeroOrLessPredicate];
NSArray *moreThanZeroArray = [sortedArray filteredArrayUsingPredicate:moreThanZeroPredicate];
My NSArray contains NSDictionary instances, and in the dictionaries I have orderid.
I want to make them sort in descending order. But it is not sorting.
I have tried this code
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"orderid" ascending:FALSE];
[self.orderArray sortUsingDescriptors:[self.orderArray arrayWithObject:sortDescriptor]];
[sortDescriptor release];
And this code :
[self.orderArray sortUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:#"orderid" ascending:NO]]];
But it didn't worked.
Here is the log
orders : (
{
orderid = 6739;
},
{
orderid = 6740;
},
{
orderid = 6745;
},
{
orderid = 6746;
},
{
orderid = 6748;
},
)
This should work
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"orderid.intValue" ascending:NO ];
[self.orderArray sortUsingDescriptors:#[sortDescriptor]];
I am agree with the #HotLicks this code must work. Are you sure between the sort code and log there is no code. If there is than please add it.
Only problem i see is that You have added your array name instead of NSArray in [self.orderArray sortUsingDescriptors:[self.orderArray arrayWithObject:sortDescriptor]]; this line.
Do it like this :
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"orderid" ascending:FALSE];
[self.orderArray sortUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]]; //Change in this line
[sortDescriptor release];
NSLog(#"self.orderArray : %#",self.orderArray);
You can sort an array using your "custom" comparator.
NSMutableArray *nds = [[NSMutableArray alloc] initWithArray:nodes];
//
[nds sortUsingComparator:^NSComparisonResult(Node *f1,Node *f2){
if (f1.nodeType == FOLDER && f2.nodeType == FILE) {
return NSOrderedAscending;
} else if (f1.nodeType == FILE && f2.nodeType == FOLDER) {
return NSOrderedDescending;
} else if (f1.nodeType == f2.nodeType) {
return [f1.displayName localizedCaseInsensitiveCompare:f2.displayName];
}
//
return [f1.displayName compare:f2.displayName];
}];
This method will traverse the array, taking two objects from the array and comparing them.
The advantage is that the objects can be of any type (class) and you decide the order between the two.
In the above example I want to order:
- folders before files
- folders and files in alphabetical order
So I have an array of custom "Element" objects (hey hold atomic number, chemical symbol, atomic mass, etc...) and I am having trouble sorting them by one of their properties;
Here is the code:
switch (sortDescriptor) {
case 0: {
//Sort the array by "ATOMIC NUMBER"
NSArray *sortedArray = [self.elementsArray sortedArrayUsingComparator:^(id a, id b) {
NSNumber *first = #([(SAMElement *)a atomicNumber]);
NSNumber *second = #([(SAMElement *)b atomicNumber]);
return [first compare:second];
}];
self.elementsArray = [sortedArray mutableCopy];
}
case 1: {
//Sort the array by "ELEMENT NAME"
NSArray *sortedArray = [self.elementsArray sortedArrayUsingComparator:^(id a, id b) {
NSString *first = [(SAMElement *)a elementName];
NSString *second = [(SAMElement *)b elementName];
return [first compare:second];
}];
self.elementsArray = [sortedArray mutableCopy];
}
case 2:{
NSLog(#"sorting by chemical symbol");
//Sort the array by "CHEMICAL SYMBOL"
NSArray *sortedArray = [self.elementsArray sortedArrayUsingComparator:^(id a, id b) {
NSString *first = [(SAMElement *)a chemichalSymbol];
NSString *second = [(SAMElement *)b chemichalSymbol];
return [first compare:second];
}];
self.elementsArray = [sortedArray mutableCopy];
}
case 3: {
//Sort the array by "ATOMIC MASS"
NSArray *sortedArray = [self.elementsArray sortedArrayUsingComparator:^(id a, id b) {
NSNumber *first = [(SAMElement *)a atomicMass];
NSNumber *second = [(SAMElement *)b atomicMass];
return [first compare:second];
}];
self.elementsArray = [sortedArray mutableCopy];
}
default:
break;
}
When is sorts it returns a totally random list of elements. Am i doing something wrong?
The best way to sort an array of objects by some property of the object, its using NSSortDescriptor. In initWithKey, you can set the name of the property that you want to sort.
NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:#"atomicNumber" ascending:NO];
[self.elementsArray sortUsingDescriptors:#[sortDescriptor]];
In your case, just copy this code above in each case section of your switch statement, changing the key for #"elementName" and #"chemichalSymbol".
You can change the ascending value from NO to YES, depending what type of order do you want.
Please, let me know if worked or not.
I'm not seeing the bug immediately, but you're reinventing the wheel here. The correct tool for this is sortedArrayUsingDescriptors:
[self.elementsArray sortedArrayUsingDescriptors:#[
[NSSortDescriptor alloc] initWithKey:#"atomicNumber"] ascending:YES]
]];
Try that and see if it gets rid of your bug. If you're getting random orders, that usually suggests that your comparitor is inconsistent (sometimes A>B and sometimes B>A for the same A&B).
the NSMutableArray I want to sort looks like this:
(
{
"title" = "Bags";
"price" = "$200";
},
{
"title" = "Watches";
"price" = "$40";
},
{
"title" = "Earrings";
"price" = "$1000";
}
)
It's an NSMutableArray which contain a collection of NSMutableArrays. I want to sort it by price first then by title.
NSSortDescriptor *sortByPrices = [[NSSortDescriptor alloc] initWithKey:#"price" ascending:YES];
NSSortDescriptor *sortByTitle = [[NSSortDescriptor alloc] initWithKey:#"title" ascending:YES];
[arrayProduct sortedArrayUsingDescriptors:[NSArray arrayWithObjects:sortByPrices,sortByTitle,nil]];
However, that didn't seems to work, how to sort a nested NSMutableArray?
Try
NSMutableArray *arrayProducts = [#[#{#"price":#"$200",#"title":#"Bags"},#{#"price":#"$40",#"title":#"Watches"},#{#"price":#"$1000",#"title":#"Earrings"}] mutableCopy];
NSSortDescriptor *priceDescriptor = [NSSortDescriptor sortDescriptorWithKey:#""
ascending:YES
comparator:^NSComparisonResult(NSDictionary *dict1, NSDictionary *dict2) {
return [dict1[#"price"] compare:dict2[#"price"] options:NSNumericSearch];
}];
NSSortDescriptor *titleDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"title" ascending:YES];
[arrayProducts sortUsingDescriptors:#[priceDescriptor,titleDescriptor]];
NSLog(#"SortedArray : %#",arrayProducts);
I suppose the error is that price is a string. As such, it isn't compared numerically, but lexicographically. Try sorting the array using a comparator block and parsing the price inside that block instead:
[array sortUsingComparator:^(id _a, id _b) {
NSDictionary *a = _a, *b = _b;
// primary key is the price
int priceA = [[a[#"price"] substringFromIndex:1] intValue];
int priceB = [[b[#"price"] substringFromIndex:1] intValue];
if (priceA < priceB)
return NSOrderedAscending;
else if (priceA > priceB)
return NSOrderedDescending;
else // if the prices are the same, sort by name
return [a[#"title"] compare:b[#"title"]];
}];
Try this
Apple doc
This well help you