Sorting an NSArray and case precedence - ios

I have a list of managed objects stored in core data. I use these objects to populate a tableview controller that is sectioned according to alphabetical order. The data in these objects is obtained via web service, so I have no control over their case (which really doesn't make much difference in this case).
Most of the data is returned in all caps. I've noticed that, on the rare occasions where the case is NOT all caps, those items do not fall into alphabetic order. In the following code sample, stationIndex is an array of sorted first letters:
for(NSString *character in stationIndex){
NSPredicate *pred = [NSPredicate predicateWithFormat:#"name beginswith[c] %#", character];
// sort the list
NSArray *filteredGaugeList = [[tempGaugeList filteredArrayUsingPredicate:pred] sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
NSString *first = [(Gauge*)a name];
NSString *second = [(Gauge*)b name];
return [first compare:second];
}];
if([filteredGaugeList count] > 0){
[[self allGauges] addObject:filteredGaugeList];
}
}
I'm aware that there is a way to ignore case when using a selector, but in my case, I'm sorting on properties of objects, so I'm assuming I need a comparator. Is there a way to handles case in this situation? Thanks!

You can sort ignoring case in a comparator as well, just use
return [first caseInsensitiveCompare:second];
Alternatively, use a sort descriptor specifying the selector and the sort key:
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:#"name"
ascending:YES
selector:#selector(caseInsensitiveCompare:)];
NSArray *sorted = [array sortedArrayUsingDescriptors:#[sort]];
Remark: To display Core Data objects in a table view, you can also use
NSFetchedResultsController. Then you would add the predicate and the sort
descriptor to the fetch request. A fetched results controller has also methods
to group a table view into sections, and to update a table view automatically
when objects are inserted/deleted/modified.

Related

Objective-C, Sorting an NSMultable array of type class, alphabetically?

I'm trying to sort a property on a type of a mutable array.
However I've only managed to sort a NSString array.
NSMutableArray<DBFILESFileMetadata*> *tmpArray = [[NSMutableArray alloc]init];
for (DBFILESMetadata *entry in entries)
{
//conditions
[tmpArray addObject:fileMetadata];
}
Here's the type / class
https://github.com/dropbox/dropbox-sdk-obj-c/blob/4c99bdf726cf9724adfddc19e71a87a6012eddeb/Source/ObjectiveDropboxOfficial/Shared/Generated/ApiObjects/Files/Headers/DBFILESMetadata.h
I've tried
[yourArray sortUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
and
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES];
[yourArray sortUsingDescriptors:[NSArray arrayWithObject:sort]];
The property is called name.
I've seen answer like this How can I sort an NSMutableArray alphabetically? but I can't seem to get this to work for my scenario.
In order to sort using sortUsingSelector you need to implement your compare method in the objects that you are trying to compare. (So your DBFILESMetadata class would need a compare method - localizedCaseInsensitiveCompare in the code above.)
You should be able too use a sort descriptor as you show in your second attempt. What happens when you do that?
A third way to do it is to use the NSMutableArray sortUsingComparator and write an NSComparator block that compares the 2 objects.
I'm out of practice with Objective-C but a quick Google search found an example, which I adapted to your specific problem:
[entries sortUsingComparator:
^NSComparisonResult(DBFILESMetadata *obj1, DBFILESMetadata *obj2)
{
return [obj1.name localizedCaseInsensitiveCompare: obj2.name];
}];
That should work, although as I say I'm out of practice in Objective-C, and its block syntax is pretty awkward and counter-intutive.

Sorting Array Alphabetically for section tableview

How to sort an array alphabetically to return all objects data instead of single name? And how to set these data in section tableview? Number of rows in section method - how it works, please help me.
You can sort your array with a sort descriptor. in the sort descriptor you set the attrib of the array elements which shall be used for sorting:
NSSortDescriptor *sort = [NSSortDescriptor sortDescriptorWithKey:#"yourAttribInArrayElememts" ascending:YES];
NSArray *sortedArray = [yourArray sortedArrayUsingDescriptors:[NSArray arrayWithObject:sort]];
Create this method inside your Object class
-(NSComparisonResult) compareWithObjectClass: (YourObjectClass *) otherObject
{
return [self.name compare:otherObject.name]
}
Then, in your viewcontroller, you can call sortUsingComparator method to order your array of objects
//Order items Array
[self.itemsArray sortUsingComparator:^NSComparisonResult(YourObjectClass *obj1, YourObjectClass *obj2){
return [obj1compareWithObjectClass:obj2];
}];

nsdictionary with arrays nspredicate filtering

I have a tableview that i want to search through with a searchable. It worked before but when i added sections i got into trouble because i had to change from arrays to dictionary.
So basically i have a NSDictionary that looks like this
{ #"districtA": array with point objects, #"districtB": array with point objects}
I need to filter them based on the point objects.name that is in the arrays. After that i want to create a new nsdictionary with the filtered objects in it.
I tried at least 10 different methods but i can't figure it out so i think this is the only way that i am most positive that should work.
This is the only way i can think of if there is an easier way or more logic way please tell me.
-(void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope {
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.name BEGINSWITH[c] %#",searchText];
//create new array to fill
NSArray *arrayWithFilteredPoints = [[NSArray alloc] init];
//loop through the values and put into an rray based on the predicate
arrayWithFilteredPoints = [NSArray arrayWithObject:[[self.PointList allValues] filteredArrayUsingPredicate:predicate]];
NSMutableDictionary *dict = [#{} mutableCopy];
for (Point *point in arrayWithFilteredPoints) {
if (![dict objectForKey:Point.district])
dict[Point.district] = [#[] mutableCopy];
[dict[Point.district]addObject:Point];
}
self.filteredPointList = dict;
self.filteredDistrictSectionNames = [[dict allKeys] sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];}
This results in a crash, it happens of course where the predicate is used but i don't know how to debug what predicate i should use:
on 'NSInvalidArgumentException', reason: 'Can't do a substring operation with something that isn't a string (lhs = (
West ) rhs = w)'
I have read the comments and you are right. There was something wrong with my code.
I changed the logic, i added some more steps (like creating NSArray without needing it) to make the solution clear
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"SELF.name BEGINSWITH[c] %#",searchText];
//1. create new array to fill only the Points from the dictionary
NSArray *allPoints = [self.PointList allValues];
NSMutableArray *allPointObjects = [[NSMutableArray alloc]init];
for (NSArray *array in allPoints) {
for (Point *point in array) {
[allPointObjects addObject:point];
}
}
//2. loop through allPointObjects and put into an mutablearray based on the predicate
NSArray *arrayWithFilteredPoints = [[NSArray alloc] init];
arrayWithFilteredPoints = [allPointObjects filteredArrayUsingPredicate:predicate];
NSMutableDictionary *dict = [#{} mutableCopy];
for (Point *point in arrayWithFilteredPoints) {
if (![dict objectForKey:point.district])
dict[point.district] = [#[] mutableCopy];
[dict[point.district]addObject:Point];
}
self.filteredPointList = dict;
self.filteredDistrictSectionNames = [[dict allKeys] sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
I wanted a filtered nsdictionary at the end that i can pass back to my tableview that reads the dictionary objects based on the keys (districts)
It seems clear from your description that [self.PointList allValues] is not an array of Point objects but an array of arrays of Point objects. That is the source of your difficulty, including your original crash.
You need to decide what to do about that; for example, if you want just one big array of Point objects, then flatten the array of arrays before you filter. I can't advise you further because it is not obvious to me what ultimate outcome you desire.
EDIT You've now modified your code and I can see more clearly what you're trying to do. You have a dictionary whose values are arrays of points, and you are trying to filter some of the points out of each array. What I would have done is to do that - i.e., run thru the keys, extract each array, filter it, and put it back (or delete the key if the array is now empty). But I can see that what you are doing should work, because you have cleverly put the keys into the points to start with, so you can reconstruct the dictionary structure from that.

Compare alphanumeric strings in Core Data

I have a core data attribute titled 'gLotNumber' which stores alphanumeric strings. Examples include P1, P5, 7, P10 and 11.
I need to provide a way to search this attribute to the user. The way I can think of providing this is by using NSPredicate on core data using:
[fetchRequest1 setPredicate:[NSPredicate predicateWithFormat:#"(gLotNumber >= 'P1') AND (gLotNumber <= 'P15')"]];
This should result in all entries which start with P and range between 1 to 15. The above code doesn't produce accurate results at all.
Do you know how this can be achieved?
I believe this should work
I don't have a coredata setup hence harcoded some array values.
NSArray *array = #[#{#"gLotNumber":#"P1"},#{#"gLotNumber":#"P15"},#{#"gLotNumber":#"P13"},#{#"gLotNumber":#"P0"},#{#"gLotNumber":#"11"},#{#"gLotNumber":#"10"},#{#"gLotNumber":#"P31"},#{#"gLotNumber":#"P013"},#{#"gLotNumber":#"16"},#{#"gLotNumber":#"P38"}];
NSArray *filtered = [array filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"SELF.gLotNumber BEGINSWITH 'P' AND SELF.gLotNumber <= 'P15'"]];
NSSortDescriptor *desc = [NSSortDescriptor sortDescriptorWithKey:#"gLotNumber" ascending:YES comparator:^NSComparisonResult(id obj1,id obj2){
return [obj1 compare:obj2 options:NSNumericSearch];
}];
NSArray *sorted = [filtered sortedArrayUsingDescriptors:#[desc]];
You can set the sort descriptor and predicate to the fetch request Hope this helps

NSSortDescriptor sort a string field as a number with comparator block not working

I have a field full of ids from a third party. The ids are numbers but written to the db as a string.
I want to sort a fetch sorted by this id on the value of the integer. So I'm adding this NSSortDescriptor to the NSFetchRequest.
NSNumberFormatter *numFormatter = [[NSNumberFormatter alloc] init];
[numFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
NSSortDescriptor *sortBy = [[NSSortDescriptor alloc] initWithKey:#"someId" ascending:YES comparator:^(id a, id b) {
return [[numFormatter numberFromString:a] compare:[numFormatter numberFromString:b]];
}];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sortBy]];
But I get results like the the following. These are still sorted as a string, alphabetically.
730275292
73900038
730172867
7350727
830138437
835164
837287901
8338804
930274
9324376
What am I not understanding about using this comparator block?
EDIT May 1 2012 9:20 AM EST
To test whether the comparator block is being used, I tried the following to sort based on the length of the field.
NSSortDescriptor *sortBy = [[NSSortDescriptor alloc] initWithKey:#"fbId" ascending:YES comparator:^(id a, id b) {
if ([a length] < [b length]) {
return NSOrderedAscending;
} else if ([a length] > [b length]) {
return NSOrderedDescending;
} else {
return NSOrderedSame;
}
}];
[fetchRequest setSortDescriptors:[NSArray arrayWithObject:sortBy]];
I'm still getting results sorted by the alphabetically order! So this makes me think the comparator block is not even being used.
716164250
726354466
73900038
739600038
7450727
810138437
801164
801375346
8213997
Try this !
[NSSortDescriptor sortDescriptorWithKey:#"name"
ascending:YES
selector:#selector(localizedStandardCompare:)]
I don't use NSFetchRequest so I can't comment on that specifically, but it appears to be something related to it. The code you use in your comparator block is just fine. I setup an array of the exact numbers you show and then sorted them using your code and everything worked out fine:
NSArray *array = [NSArray arrayWithObjects:#"730275292",
#"73900038",
#"730172867",
#"7350727",
#"830138437",
#"835164",
#"837287901",
#"8338804",
#"930274",
#"9324376", nil];
NSNumberFormatter *numFormatter = [[NSNumberFormatter alloc] init];
[numFormatter setNumberStyle:NSNumberFormatterDecimalStyle];
NSArray *sArray = [array sortedArrayUsingComparator:^(id a, id b) {
return [[numFormatter numberFromString:a] compare:[numFormatter numberFromString:b]];
}];
NSLog(#"%#",sArray);
When the above code runs, I get a log of:
(
835164,
930274,
7350727,
8338804,
9324376,
73900038,
730172867,
730275292,
830138437,
837287901
)
I believe this is the order you're looking for. You might consider taking the results of your fetch request and sorting them in an array after you've received them. I doubt it matters whether an array does the sorting or the fetch request does the sorting. Most likely there's no performance gain of using one over the other.
If you still want NSFetchRequest to do the sorting, then there might be something your missing in order to get it to sort properly. Honestly, I'm not sure since I've not used it.
UPDATE
Quickly looking through the NSFetchRequest docs, I've found that there are some parameters that affect sorting. For example, in the docs for the resultType it gives this message:
You use setResultType: to set the instance type of objects returned
from executing the request—for possible values, see
“NSFetchRequestResultType.” If you set the value to
NSManagedObjectIDResultType, this will demote any sort orderings to
“best efforts” hints if you do not include the property values in the
request.
So it looks as though the return type might be affecting your sorting.

Resources