Sorting an NSMutableArray containing matching "pairs" of values - iphone

I'd prefer not to change the way anArray is designed because it is also used elsewhere as a dataSource for a UITableView. The odd number values are "user names", and the even number values are their matching "dates". (The "pairs" must remain together.)
How would I sort "by user name"?
How would I sort "by date"?
Should I be using sortUsingFunction or sortUsingSelector?
-(void) test
{
NSMutableArray *anArray = [NSMutableArray arrayWithObjects:#"Zeke", #"01-Jan-2010", #"Bob", #"02-Jan-2010", #"Fred", #"03-Jan-2010", #"Susan", #"04-Jan-2010", #"Kim", #"05-Jan-2010", #"Debbie", #"06-Jan-2010", nil];
[anArray sortUsingFunction:SortByDate context:(void *)context];
}
NSComparisonResult SortByDate(NSMutableArray *a1, NSMutableArray *a2, void *context)
{
// What goes here to keep the pair-values "together"?
}

Why aren'e you using a NSDictionary? You can have user names as keys and the dates as corresponding values. Then if you want to sort on user names, just use [dictObj allkeys] to get an array containing just the keys. Sort them as you prefer and then when displaying display the sorted array of keys and fetch the corresponding value for the key and display along side. Similarly, you can get all the values using [dictObj allValues] sort them and display them along with keys.

I'd prefer not to change the way anArray is designed because it is also used elsewhere as a dataSource for a UITableView
Does that mean cells alternate between displaying usernames and dates, or are you multiplying/dividing by 2?
Either way, you need to fix your model. The bit where you go "oops, sorting doesn't work" should be a big hint that you haven't chosen a good representation of your data. Adding a workaround will only lead to more tears later.
I'd suggest something like this:
Create a class to represent your (username,date) tuple.
Add -compareByDate: and -compareByUsername: methods.
[array sortUsingSelector:#selector(compareByDate:)]
If you want to display usernames and dates in alternating cells, then it's still easy to do, but I'd simply use a taller cell and save myself some pain.
Note: The overhead of Objective-C method calls means that you can get a significant performance increase by using sortUsingComparator: and defining appropriate functions (you can stick the function between #implmentation and #end to let them access protected/private ivars) but it's not really worth doing this unless you've determined that sorting is a bottleneck.

Something like this could work:
#class SortWrapper: NSObject
#property (copy,readwrite,atomic) NSString* name;
#property (copy,readwrite,atomic) NSDate* date;
#end
...
- (void)test
{
NSArray* names = #[#"Alice", #"Bob", #"Cameron"];
NSArray* dates = #[aliceDOB, bobDOB, cameronDOB]; // NSDate objects
// turn into wrapped objects for sorting
NSMutableArray* wrappedObjects = [NSMutableArray array];
for ( int i=0; i<names.count; i++ ) {
SortWrapper* wrapped = [[SortWrapper alloc] init];
wrapped.name = names[i];
wrapped.date = dates[i];
[wrappedObjects addObject:wrapped];
}
// to sort by date:
NSArray* sortedByDate = [wrappedObjects sortedArrayUsingComparator:^NSComparisonResult(SortWrapper* obj1, SortWrapper* obj2) {
return [obj1.date compare:obj2.date];
}];
// to sort by name:
NSArray* sortedByName = [wrappedObjects sortedArrayUsingComparator:^NSComparisonResult(SortWrapper* obj1, SortWrapper* obj2) {
return [obj1.name compare:obj2.name];
}];
}

Related

Sorting IBOutletCollection Array of TextFields According to frame.origin.y

I'm trying to sort array of textFields according to frame.origin.y. But when I ran simulator it got stuck. Any Idea?
IBOutletCollection(UITextField) NSArray *textFields
My code :
-(NSMutableArray *)bubbleSort:(NSMutableArray *) unsortedArray{
NSInteger i,j;
//NSLog(#"%#",unsortedArray);
for(i=0;i<unsortedArray.count;i++)
{
for(j=0;j<i;j++)
{
if(((UITextField *)[unsortedArray objectAtIndex:i]).frame.origin.y > ((UITextField *)[unsortedArray objectAtIndex:j]).frame.origin.y)
{
UITextField *temp=[unsortedArray objectAtIndex:i];
[unsortedArray insertObject:[unsortedArray objectAtIndex:j] atIndex:i];
[unsortedArray insertObject:temp atIndex:j];
}
}
}
//NSLog(#"%#",unsortedArray);
return unsortedArray;
}
Like Learner said, you just keep on inserting object in your array, so you never leave your outer for loop. To swap two elements, you can use
- (void)exchangeObjectAtIndex:(NSUInteger)idx1 withObjectAtIndex:(NSUInteger)idx2
so in your case, inside your if, you can simply write
[unsortedArray exchangeObjectAtIndex:i withObjectAtIndex:j];
Reference
look like this will keep on inserting object in array. so unsortedArray.count will keep increasing. in my opinion you need to remove object first and than insert it to upper rank or lower rank depending on algorithm.
se documentation for NSMutableArray insert object function.
https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSMutableArray_Class/Reference/Reference.html

iPhone Table View: Making Sections, UILocalizedIndexedCollation selector

I'm having trouble making the sections in a UITableView. I've looked at the documentation for UILocalizedIndexedCollation as well as this sample code project:
https://developer.apple.com/library/ios/#samplecode/TableViewSuite/Listings/3_SimpleIndexedTableView_Classes_RootViewController_m.html#//apple_ref/doc/uid/DTS40007318-3_SimpleIndexedTableView_Classes_RootViewController_m-DontLinkElementID_18
What I have below is basically a straight copy/paste from the sample project. However, the sample project uses a custom object (TimeZoneWrapper.h) and then places the object in the correct section based on the object's instance variable (TimeZoneWrapper.localeName). However, I'm not using custom objects. I'm using just a bunch of regular NSStrings. So my question is what method on NSString should I pass to the #selector() to compare and place the string in the correct section array?
Currently, I'm calling NSString's copy method as a temporary hack to get things working (which it does), but I'm not sure if this is correct. A little explanation would be much appreciated!
- (void)configureSections {
// Get the current collation and keep a reference to it.
self.collation = [UILocalizedIndexedCollation currentCollation];
NSInteger index, sectionTitlesCount = [[collation sectionTitles] count]; // sectionTitles are A, B, C, etc.
NSMutableArray *newSectionsArray = [[NSMutableArray alloc] initWithCapacity:sectionTitlesCount];
// Set up the sections array: elements are mutable arrays that will contain the locations for that section.
for (index = 0; index < sectionTitlesCount; index++) {
NSMutableArray *array = [[NSMutableArray alloc] init];
[newSectionsArray addObject:array];
}
// Segregate the loctions into the appropriate arrays.
for (NSString *location in locationList) {
// Ask the collation which section number the location belongs in, based on its locale name.
NSInteger sectionNumber = [collation sectionForObject:location collationStringSelector:#selector(/* what do I put here? */)];
// Get the array for the section.
NSMutableArray *sectionLocations = [newSectionsArray objectAtIndex:sectionNumber];
// Add the location to the section.
[sectionLocations addObject:location];
}
// Now that all the data's in place, each section array needs to be sorted.
for (index = 0; index < sectionTitlesCount; index++) {
NSMutableArray *locationsArrayForSection = [newSectionsArray objectAtIndex:index];
// If the table view or its contents were editable, you would make a mutable copy here.
NSArray *sortedLocationsArrayForSection = [collation sortedArrayFromArray:locationsArrayForSection collationStringSelector:#selector(/* what do I put here */)];
// Replace the existing array with the sorted array.
[newSectionsArray replaceObjectAtIndex:index withObject:sortedLocationsArrayForSection];
}
self.sectionsArray = newSectionsArray;
}
Thanks in advance!
You should use #selector(self).
Using #selector(copy) will cause memory leaks in your project

How to store NSTimeInterval values into a NSMutableArray?

I have a requirement where i have a Video that is played using MPMediaPlayerController. Along with the video i have two buttons where i need to capture the current playback time when the button are clicked and store all the relevant clicks individually. I am able to get the current playback time of the video using "currentPlaybackTime" property which returns NSTimeInterval. But can someone help me in how to store all the NSTimeInterval values into an NSMutableDictionary. I have tried the following ways:
-(void)onClickOfGood {
NSLog(#"The current playback time in good:%g",moviePlayerController.currentPlaybackTime);
currentPlaybackTime = moviePlayerController.currentPlaybackTime;
//NSArray *arrayContainsGoodClicks = [[NSArray alloc]initWithObjects:currentPlaybackTime, nil ];
NSNumber *goodTimeIntervals = [NSNumber numberWithDouble:currentPlaybackTime];
NSMutableArray *arrayContainsGoodClicks = [[NSMutableArray alloc]initWithObjects:goodTimeIntervals,nil ];
NSLog(#"The total count of Array is: %i",[arrayContainsGoodClicks count]);}
But everytime after the click of good button i am getting the Array count as only 1. Can someone please throw a light on where i am going wrong?
But everytime after the click of good button i am getting the Array count as only 1.
This is not surprising, considering that you are creating a brand-new NSMutableArray on the previous line.
To fix this, you need to make NSMutableArray *arrayContainsGoodClicks an instance variable (AKA ivar), initialize it to [NSMutableArray array] in your designated initializer, and then use
[arrayContainsGoodClicks addObject:goodTimeIntervals];
to add objects to the array.
If you are looking to use NSMutableDictionary instead, the strategy would be identical, except you would need to decide on an object that you would like to use as unique keys to your NSDictionary. Also remember that NSMutableDictionary is not ordered, so you might need to take care of sorting each time you display your dictionary items to users.
You need to create arrayContainsGoodClicks only once (in init method for example) and then add value to this array in your button handler:
//.h
NSMutableArray *arrayContainsGoodClicks;
//.m - init
arrayContainsGoodClicks = [NSMutableArray array];
//.m - button handler
[arrayContainsGoodClicks addObject:goodTimeIntervals];
You need to create your array and store it as a ivar.
#property (retain, nonatomic) NSMutableArray *clicksArray;
...
#synthesize clicksArray;
Now in your -init method create the array like..
self.clicksArray = [NSMutableArray array];
And add the object to the array each time so your -onClickOfGood would become something like...
...
[self.clicksArray addObject: goodTimeIntervals];
NSLog(#"The total count of Array is: %i",[self.clicksArray count]);

replaceObjectAtIndex array problems

Been searching for the answer to this for a while now and I think due to the nature of my array set up, I may be searching for the wrong answer!
I have a class which handles adding items to my array:
// Item.h
#interface Item : NSObject {
NSString *name;
NSNumber *seconds;
}
#property(nonatomic,copy) NSString *name;
#property(nonatomic,copy) NSNumber *seconds;
- (id)initWithName:(NSString *)n seconds:(NSNumber *)sec;
#end
and...
//item.m
#implementation Item
#synthesize name, seconds;
- (id)initWithName:(NSString *)n seconds:(NSNumber *)sec {
self.name = n;
self.seconds = sec;
return self;
}
#end
So to add an item, I use
Item *item1 = [[Item alloc] initWithName:#"runnerA" seconds:[NSNumber numberWithInt:780]];
I have some code which allows a user to edit a textfield (runner name) and the time which is a UIdatepicker set to hours and minutes. In the save method, that's working fine. It's the UPDATE that I cannot get to work. I've tried alsorts! Here's the code at the moment...
mainAppDelegate *appDelegate = (mainAppDelegate *)[[UIApplication sharedApplication] delegate];
Item *item = [[Item alloc] initWithName:inputName.text seconds:[NSNumber numberWithInt:secs]];
[appDelegate.arrItems replaceObjectAtIndex:rowBeingEdited withObject:item];
The above is simply adding a new item to the array (which is what I don't want). I'm not sure how to replace values. At the function, I have the row I need to update (rowBeingEdited) and the fields inputName.text and secs are both OK. (NSLog out confirms this).
How do I use the replaceObjectAtIndex to actually replace it with the values?! It's driving me mad now!!
Since you are simply trying to edit a particular row, why not use those property accessors that you already have set up in Item? It would look something like this:
Item *item = (Item *)[appDelegate.arrItems objectAtIndex:rowBeingEdited];
[item setName:inputName.text];
[item setSeconds:[NSNumber numberWithInt:secs]];
An a side note, are you using garbage collection, or do you manually release the Item objects that you create when adding items to the array? If you are doing it manually, it should look like this:
Item *item1 = [[Item alloc] initWithName:#"runnerA"
seconds:[NSNumber numberWithInt:780]];
[appDelegate.arrItems addObject:item1];
[item1 release];
This follows the rule of thumb: if you alloc, copy or retain anything, you must also release it. Note that this works because the array will retain the item when it is added.
Are you using NSArray or NSMutableArray?
Assuming you are using NSMutableArray, how did you initialize and populate the array in the first place?
For example, it's not enough to use -initWithCapacity: or +arrayWithCapacity: which only sets aside space. You have to use -addObject: for the first round of population, before you can use -replaceObjectAtIndex:withObject::
Note that NSArray objects are not like C arrays. That is, even though you specify a size when you create an array, the specified size is regarded as a “hint”; the actual size of the array is still 0. This means that you cannot insert an object at an index greater than the current count of an array. For example, if an array contains two objects, its size is 2, so you can add objects at indices 0, 1, or 2. Index 3 is illegal and out of bounds; if you try to add an object at index 3 (when the size of the array is 2), NSMutableArray raises an exception.

Objective C: How do I make an NSMutableSet of NSMutableDictionaries, and have them be unique based on one of the dictionary values?

The reason I'm asking this is because right now I use an NSMutableSet of integers and a corresponding NSMutableArray to store the rest of the data, but I think I'm not using the language right. Here's my code:
In the init function:
self.markerList = [[NSMutableArray alloc] init];
self.markerIdSet = [[NSMutableSet alloc] init];
And the function that updates the set and array:
- (void) addMarker: (RMMarker*)marker AtLatLong:(CLLocationCoordinate2D)point {
if (![self.markerIdSet member: [node objectForKey:#"id"]]) {
[self.markerIdSet addObject:[node objectForKey:#"id"]];
[self.markerList addObject:marker];
}
}
I would be a bit wary of having a mutable object as part of a key in a set as if the object changes its hash code etc could change but the set would not know about it ( unless you make the addMarker remve the object and then add it back with the new hash code)
However in your example can't you just use a dictionary (or am I missing something?)
self.markerDict = [[NSMutableDictionary alloc]init];
- (void) addMarker: (RMMarker*)marker AtLatLong:(CLLocationCoordinate2D)point {
if ([nil != [self.markerDict [node objectForKey:#"id"]]) {
[self.markerDict setValue:marker forKey:[node objectForKey:#"id"]];
}
}
The NSMutableSet contains unique elements. Uniqueness is determined by the hash and isEquals methods, so in order to do what you want, you should either subclass or delegate an NSMutableDictionary and in your own concrete class implement these methods so that the specific key you require is the equality and hash factor.
Don't make an NSMutableSet of NSMutableDictionary directly; make a custom class that has an NSMutableDictionary property. This custom class should implement -isEqual: and -hash in terms of your id—i.e. this object will be equal to another object if the ids are equal, and two objects with the same id will have the same hash.
More documentation with regard to -isEqual: and -hash.