Sorting NsMutable Array - xcode

I am getting floor names and storing in _floorNames (NSMutableArray) by using
_floorNames=[conferenceHall floorDetails:office];
NSLog(#"floor names=%#",_floorNames);
This will print floor names like this.
GROUND,
THIRD,
FIRST,
SECOND
Now i want to sort that floor names NsMutableArray as
GROUND,
FIRST,
SECOND,
THIRD
I have used
NSSortDescriptor *aa=[[NSSortDescriptor alloc]initWithKey:floor ascending:YES selector:#selector(localizedCaseInsensitiveCompare:)];
NSArray *descriptors = [NSArray arrayWithObjects:aa, nil];
a = [array sortedArrayUsingDescriptors:descriptors];
But it will crash my application.
Any suggestions ...

Try this,
NSArray *arrFloorNames=[[NSArray alloc]initWithObjects:#"GROUND",#"THIRD",#"FIRST",#"SECOND", nil];
arrFloorNames = [arrFloorNames sortedArrayUsingSelector:#selector(localizedCaseInsensitiveCompare:)];
NSLog(#"%#",arrFloorNames);

What you want is you never get!!!
"GROUND, THIRD, FIRST, SECOND" to "GROUND, FIRST, SECOND ,THIRD" is what you want.
No algorithm can sort these strings based on their internal representation in numerals. This is something like, 0..1..2..3. You want to sort these integers written in English. Sorting can be done alphabetically so your result will be as First, Groung, Second, Third.
How to achieve?
You need to create an object of dictionary, with mapped value of Third to 3, second to 2, ground to 0. Then sort those numerical values. And then get Keys based on values(0,1,2etc).

Related

Can I use the [NSArray containsObject:] function on attributes of objects?

Background Information:
I program an iOS application that contains 2 galleries: A local gallery and a server gallery. When the user updates the server gallery and merges it into his local one, the app should only download the new images.
To minimize memory consumption I save the images and fill the arrays with instances of an ImageEntity class with following attributes: fileName, filePath & votingStatus.
I tried to use following logic to check if the image already exists:
for (ImageEntity *imageEntity in self.serverImagesArray) {
if (![self.localImagesArray containsObject:imageEntity]){
[self.localImagesArray addObject:imageEntity];
}
}
But because each entity is a separate object it will always be added. Each entity has a unique fileName though.
Question:
Can I somehow extend the [NSArray containsObject:] function to check if one object in the array has an attribute equal to "someValue"? (When I use Cocoa-Bindings in combination with an ArrayController, I can assign attributes of array elements - I would like to access the attributes similar to this).
I know that I could use compare each entity of the local array to each element on the server array. I would have to do O(n^2) comparisons though and the gallery may contain several hundred images.
Bonus question: Am I already doing this without realizing it? Does anybody have details about Apple's implementation of this function? Is there some fancy implementation or are they just iterating over the array comparing every element?
The way I do it is use valueForKey: in combination with containsObject:. So in your case you should collect all file names of the array and then check if the array contains specific file name you need:
NSArray * fileNames = [fileEntityObjects valueForKey:#"fileName"];
BOOL contains = [fileNames containsObject:#"someFilename.jpg"];
This would work if fileName is property of every object in fileEntityObjects array.
Update
Yes, you can do this with NSPredicate as well:
NSPredicate * predicate = [NSPredicate predicateWithFormat:#"SELF.fileName = %#", "someFileName.jpg"];
NSArray * filteredArray = [fileEntityObjects filteredArrayUsingPredicate:predicate];
Note though that instead of boolean you'd get an array of objects having that filename.
As this question is tagged "performance" I'm adding another answer:
To avoid the n^2 comparisons we have to find a faster way to detect images that are already present. To do this we use a set that can perform lookups very quickly:
NSMutableSet *localFileNames = [NSMutableSet set];
for (ImageEntity *imageEntity in self.localImagesArray)
[localFileNames addObject:imageEntity.fileName];
Then we iterate over the server images as before. The previous containsObject: is replaced by the fast set lookup:
for (ImageEntity *imageEntity in self.serverImagesArray) {
if ([localFileNames member:imageEntity.fileName] == nil)
[self.localImagesArray addObject:imageEntity];
}

Objective-C EXC_BAD_ACCESS

Ok so I've recently decided to try to teach myself Objective-C (I'm interested in iPhone development), however I've never used C or any of its derivatives before, and as such am running into some problems.
I decided to start out by writing a very basic card application that creates a deck of cards, shuffles the deck, and then displays the cards on the screen using UIButtons, however I'm having a problem with my shuffling algorithm. Every time it gets called I get an EXC_BAD_ACCESS error, which I know means there's something desperately wrong with my code, but I just can't figure out what it is.
- (void) randomize {
NSMutableArray *tmpDeck = [[NSMutableArray alloc] init];
for(Card *tmp in _cards) {
BOOL didInsert = NO;
while(!didInsert) {
NSUInteger random = arc4random_uniform(54);
if([[tmpDeck objectAtIndex:random] isEqual:nil]) {
[tmpDeck insertObject:tmp atIndex:random];
didInsert = YES;
}
}
}
_cards = tmpDeck;
_hasBeenRandomized = YES;
}
_cards is a pointer to an NSMutableArray containing the unshuffled deck of card objects, and _hasBeenRandomized is a boolean (obviously) that keeps track of whether or not the deck has been randomized.
I've tried to use the debugger to work out what exactly is going on here, but I can't even step into the method without my program crashing. This leads me to believe that the problem has to come from the very first line, but it's just a straightforward creation of an NSMutableArray, so I don't know how it could be that. This method is being called from within viewDidLoad. This is the entirety of the viewDidLoad method currently.
- (void)viewDidLoad
{
[super viewDidLoad];
_deck = [[Deck alloc] init];
[_deck randomize];
}
Any and all help will be appreciated. Sorry if the answer is dead obvious.
This is because you are trying to insert into an index that doesn't exist yet. You need to initialize the array with as many places in the array as you need for your cards. Either that or use a NSMutableDictionary and just insert the object with the index being the key.
To add another note, calling initWithCapacity on the array wouldn't solve this for you either since this just gives a "hint" at the size. You need the count property of the array to actually be at least as large as the index you are trying to insert. If you wanted to do an array, then you would first need to populate something in each index first. You could define this in the new array literal format or use a for loop that loops the number of times you need (your max index) and insert a dummy object in it's place.
for (int i=0; i< _cards.count; ++i)
{
[tmpDeck insertObject:#"dummy" atIndex:i];
}
Then instead of checking for 'nil' before you replace, you check if it is equal to the dummy object you inserted. This would give you an array that you can insert into any of these indexes. I personally would still probably store them in an NSMutableDictionary. But if you need it in an array for some other purpose then this is a way to do it.
You also will need to be sure to replace the object instead of inserting, otherwise you will just keep adding indexes.
[tmpDeck replaceObjectAtIndex:random withObject:tmp];
If you still get the same error, set a breakpoint in your debugger and check what the random number is and what the count of your array is. If your random number is ever greater than your array count, then you will get this error.

objective-c custom sorting

So, having struggled with this for too long, without much documentation to go on, I thought I'd share my experience for those few who are even noobier than me.
There probably are much cleaner ways of achieving what I did, so please feel free to suggest improvements.
Here's what I wanted to do:
I have an NSArrayController which manages NSManagedObjects (say Thing). These objects have a name property which is of type NSString.
I wanted to sort the array of the array controller using the name of the managed objects. The common way to go about sorting a mutable array of strings should be something like:
[myMutableArrayOfStrings sortUsingSelector:#selector(caseInsensitiveCompare:)];
or:
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"name" ascending:YES selector:#selector(caseInsensitiveCompare:)];
NSArray *descriptorArray = [NSArray arrayWithObject:sortDescriptor];
[myArrayController setSortDescriptors:descriptorArray];
However, if the names happened to be numbers, caseInsensitiveCompare: would sort 11 before 8 (for instance).
Also, I wanted a name of a11b to be sorted AFTER a8b, meaning that I needed to chop the names of the managed objects into groups of digits and non-digit characters, then compare those individually.
What I came up with, was to make a category of NSString.
header file:
#import <Foundation/Foundation.h>
#interface NSString (MyString)
- (NSComparisonResult)myCustomCompare:(NSString *)anotherString;
#end
implementation file:
#import "MyString.h"
#implementation NSString (MyString)
- (NSComparisonResult)myCustomCompare:(NSString *)anotherString {
elaborate chopping up of strings into substrings..
For each of the 2 strings I made an NSMutableArray.
Then I determined if these substrings were NSNumbers, which have their own compare:, or NSStrings (caseInsensitiveCompare:)
Then I return an NSComparisonResult (NSOrderedAscending, NSOrderedSame or NSOrderedDescending)
}
#end
Then, in the implementation file of my Thing ManagedObject, I overrid the
- (NSComparisonResult)compare:(Thing *)anotherThing {
return [self.name myCustomCompare:anotherThing.name];
}
One also needs to import MyString.h in the Thing class.
Now, using
[[myArrayController arrangedObjects] sortUsingSelector:#selector(compare:)];
works like a charm.
Just one thing that's been bugging me. If a Thing's name property would contain decimal numbers, like for instance a1.4b and a1.39b, how would I be able to isolate these from the name? (a1.4b would incorrectly be sorted before a1.39b)
To make things even worse, a user could enter a Thing's name to be 1.3.55 ...

How to sort an NSArray on time interval instead of time of day?

Is there a way to sort on finish times for a race? I have an NSArray with a dictionary of NSStrings and one of the values is race finish time as an NSString. It sorts them all correctly as long as they have the same number of integers, like this: 54.13, 54.32, 54.52... but when one of them is longer, for example: 1:13.42 -- It puts it at the top of the list. How should I sort this so the slower times are at the bottom of the list. Here is what I currently have:
NSSortDescriptor * sortByTime2 = [[NSSortDescriptor alloc] initWithKey:#"raceTime" ascending:YES];
NSArray * descriptors2 = [NSArray arrayWithObject:sortByTime2];
myArray = [myArray sortedArrayUsingDescriptors:descriptors2];
thanks,
R
The strings "raceTime" are just mere string, they don't represent time. When you want to compare these, you need to convert these string to instances of NSData, then sort. You can use NSSortDescriptor and initialize it with a selector which will do the conversion and return the appropriate order.

sorting an NSArray of NSDates

I have an NSArray of NSDate objects and I want to sort them so that today is at 0, yesterday at 1 etc.
Is it ascending or descending, and do i use a function, selector or what?
There are different sort methods for NSArray because there may be different ways you want to sort things. NSSortDescriptors are a general way that give you a lot of options as far as what keys to use in sorting, what selectors you want to use on those keys, and overall order to use, etc. Or you can use functions or comparator blocks instead if your case requires that or if that's more convenient for your particular case.
To answer your first question, if you want today to be first, followed by yesterday then, yes, that is of course descending order.
To sort some dates in descending order you can just do this: (assuming an NSArray full of NSDates called 'dateArray'):
NSSortDescriptor *descriptor = [[NSSortDescriptor alloc] initWithKey:#"self" ascending:NO];
NSArray *descriptors = [NSArray arrayWithObject: descriptor];
[descriptor release];
NSArray *reverseOrder = [dateArray sortedArrayUsingDescriptors:descriptors];
Or, if you are building for iOS 4+ or Snow Leopard+ you can do this:
NSArray *reverseOrderUsingComparator = [dateArray sortedArrayUsingComparator:
^(id obj1, id obj2) {
return [obj2 compare:obj1]; // note reversed comparison here
}];
Try this magic:
Sort array of dates in ascending order.
i.e. dates getting later and later, or to put it another way, dates going into the future.
NSArray ascendingDates = [dates sortedArrayUsingSelector:#selector(compare:)];
Sort array of dates in descending order. (what the question asked)
i.e. dates getting earlier and earlier, dates going into the past or to put it another way: today is at index 0, yesterday at index 1.
NSArray* descendingDates = [[[dates sortedArrayUsingSelector:#selector(compare:)] reverseObjectEnumerator] allObjects];

Resources