Min/Max method for tableview data - objective-c

On my tableview, prior to populating it after a search, I would like to add a minimum distance and a maximum distance of the current search in text fields at the top of the tableview. I can't seem to locate a method to do determine these parameters and my searches will vary from 2 to 50 results. I don't want to do it the hackcish way of just comparing the distance to each item from the current location in a loop. Can anyone point me to a clean method for determining min/max of an element in a mutable array?

If you need some value from every element in an array, then you're going to have to iterate over the array somehow. There's nothing wrong with a loop. Still, there are other ways than explicit loops to go through an array. Perhaps -[NSArray enumerateObjectsUsingBlock:] will make you happy:
__block CGFloat minDistance = INF;
__block CGFloat maxDistance = -INF;
[locationsArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
CGFloat dist = // calculate distance from obj to current location
if( dist < minDistance ){
minDistance = dist;
}
if( dist > maxDistance ){
maxDistance = dist;
}
}];
Other options for enumerating arrays can be found in the Collections Programming Topics document, under Enumeration.
You should also have a look at the Key-Value Collection Operators: [locationsArray valueForKey:#"#max.distance"] will pull out the maximum distance held by any of the members of the array. This may seem like exactly what you're looking for, but there's two things to be aware of. First, the objects have to already have that distance property -- you can't do any calculation as part of this procedure. Second, the property has to be an object (in this case, probably an NSNumber) not a primitive like CGFloat, because valueForKey: is going to send compare: to the result of each property access.

Related

Reordering NSMutableArray by changing its starting index (Circular buffer)

Is there an efficient way to reorder NSMutableArray by changing its starting index? Example: if my array is [A,B,C,D,E], I would like to set 3rd element as starting element, and thus create the array [C,D,E,A,B].
I am doing it by slicing the array into two separate array, and then concatenating them. Is there a more efficient or clean way to do this?
Edit: the following is my current code
NSArray myArray = [self getMyArray]; // [A,B,C,D]
int startingIndex = 2;
NSArray *subArray1 = [myArray subarrayWithRange:NSMakeRange(_startingIndex, [myArray count] - _startingIndex + 1)]; // [C,D]
NSArray *subArray2 = [myArray subarrayWithRange:NSMakeRange(0, _startingIndex - 1)]; //[A,B]
NSMutableArray *reorderedArray = [NSMutableArray arrayWithArray:subArray1];
[reorderedArray addObjectsFromArray: subArray2]; //[C,D,A,B]
The most economical way of rotating NSMutableArray in terms of additional memory is the double-reversion algorithm described in this Q&A, because it does not require any additional storage. The idea is to reverse the entire array, and then reverse the two ranges separately.
In your example the array would be reversed, like this
E D C B A
then the first three elements would be reversed
C D E B A
and finally the tail of the array would be reversed:
C D E A B
Make a helper function for reversing a range, and call it three times:
reverseInPlace(myArray, 0, myArray.count-1);
reverseInPlace(myArray, 0, _startingIndex);
reverseInPlace(myArray, _startingIndex+1, myArray.count-1);
One way to implement reverseInPlace is as follows:
static void reverseInPlace(NSMutableArray *a, int f, int b) {
while (f < b) {
[a exchangeObjectAtIndex:f++ withObjectAtIndex:b--];
}
}
No, NSMutableArray is not documented to behave as a circular buffer (ring buffer), nor does it have operations you'd expect to have in one. Some potentially useful resources for solving this with a data structure where rotations have an O(1) cost:
Answer to a related Objective-C question about ring buffers, linking to CHDataStructures available also as a pod if you are so inclined.
Boost.Circular: a C++ circular buffer which you could wrap into some Objective-C++ for your purposes.
If all you need is to rotate a fixed, predictable length buffer, a circular buffer is a simple data structure to implement yourself. Apple's CoreAudio utility classes also include a general purpose ring buffer (CARingBuffer) you may find useful as a reference for your implementation.
If an in-place O(N) solution is sufficient for your needs, the other answer is a better choice than the varyingly complicated things I'm proposing above.
More efficient and cleaner:
NSRange range = NSMakeRange(0, startingIndex - 1);
NSArray *subArray = [array subarrayWithRange:range];
[array removeObjectsInRange:range];
[array addObjectsFromArray:subArray];

Sort NSManagedObjects from closest to farthest from current location

I have a Core Data model defined with two attributes
(Double) latitude
(Double) longitude
Now, I would like to fetched these objects and sort them depending on how far they are compared to the user's current location. I already know how to get the current location, but what I still can't figure out is how to sort the results depending on two attributes.
I've searched for something similar but I'm still a bit confused.
That'd be great if someone could point me to the right direction.
Thanks
Sorting with a comparator block is quite easy
NSArray *positions = //all fetched positions
CLLocation *currentLocation = // You said that you know how to get this.
positions = [positions sortedArrayUsingComparator: ^(id a, id b) {
CLLocation *locationA = [CLLocation initWithLatitude:a.latitude longitude:a.longitude];
CLLocation *locationB = [CLLocation initWithLatitude:b.latitude longitude:b.longitude];
CLLocationDistance dist_a= [locationA distanceFromLocation: currentLocation];
CLLocationDistance dist_b= [locationB distanceFromLocation: currentLocation];
if ( dist_a < dist_b ) {
return (NSComparisonResult)NSOrderedAscending;
} else if ( dist_a > dist_b) {
return (NSComparisonResult)NSOrderedDescending;
} else {
return (NSComparisonResult)NSOrderedSame;
}
}
As I just learned from lnafziger, you should add that useful hack/workaround¹, he is showing, to this.
¹choose form this words the one, that has the most positive connotation for you
You probably want to convert you long/lat pairs into a geographical distance between points and then sort on that single attribute.
Here's an article on some conversion methods, depending on what approximations you want to accept: http://en.wikipedia.org/wiki/Geographical_distance
Well, you can't.
Not just by sorting lat/long by itself anyway. :)
You will need to have a property that contains the distance from your current location. You can do this by adding a transient property which is calculated as needed or creating another array with the distances (probably easier).
To calculate your distance from your current location, use this method:
CLLocation *currentLocation = // You said that you know how to get this.
CLLocation *storedLocation = [CLLocation initWithLatitude:object.latitude
longitude:object.longitude];
/*
* Calculate distance in meters
* Note that there is a bug in distanceFromLocation and it gives different
* values depending on whether you are going TO or FROM a location.
* The correct distance is the average of the two:
*/
CLLocationDistance *distance1 = [currentLocation distanceFromLocation:storedLocation];
CLLocationDistance *distance2 = [storedLocation distanceFromLocation:currentLocation];
CLLocationDistance *distance = distance1 / 2 + distance2 / 2;

How does one retrieve a random object from an NSSet instance?

I can grab a random value from an array-like structure by retrieving a random index.
How can I grab a random value from an NSSet object that stores NSNumber objects? I couldn't find an instance method of NSSet that retrieves a random value.
In short, you can't directly retrieve a random object from an NSSet.
You either need to turn the set into an array -- into something that has an index that can be randomized -- by re-architecting your code to use an array or you could implement this using this bit of pseudo-code:
randomIndex = ...random-generator....(0 .. [set count]);
__block currentIndex = 0;
__block selectedObj = nil;
[set enumerateObjectsWithOptions:^(id obj, BOOL *stop) {
if (randomIndex == currentIndex) { selectedObj = obj; *stop = YES }
else currentIndex++;
}];
return selectedObj;
Yes -- it iterates the set, potentially the whole set, when grabbing the object. However, that iteration is pretty much what'll happen in the conversion to an NSArray anyway. As long as the set isn't that big and you aren't calling it that often, no big deal.
Whilst I like that #bbum answer will terminate early on some occasions due to the use of stop in the enumeration block.
For readability and ease of remembering what is going on when you revisit this code in the future I would go with his first suggestion of turn the set into an array
NSInteger randomIndex = ..random-generator....(0 .. [set count])
id obj = [set count] > 0 ? [[set allObjects] objectAtIndex:randomIndex] : nil;

iOS indexOfObjectIdenticalTo: issue

I have two arrays, each containing strings. The first array is a list of words, the second array contains alternatives to those words in different languages.
The arrays are matched such that the word at index n in the second array is a translation of the word at index n in the first array.
The words and their translations are displayed in a table view. The user can filter the table view by entering text in a search field. When this is done, I create a filtered array from the first array like this:
- (void)filterContentForSearchText:(NSString*)searchText
[self.filteredarray removeAllObjects];
[firstarray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop)
{
if ([obj compare:searchText options:NSCaseInsensitiveSearch range:NSMakeRange(0, [searchText length])] == NSOrderedSame)
{
idx= [firstarray indexOfObjectIdenticalTo:obj];
NSUInteger maxindex = idx + 50;
for (idx ; (idx < [firstarray count] && idx <= maxindex && idx!= NSNotFound); idx ++)
{
[self.filteredarray addObject:[firstarray objectAtIndex: idx]];
}
*stop = YES;
}
}];
Then, when I am displaying the values in my table view, I use the following code. This is an exerpt from my cellForRowAtIndexPath method. I am trying to get the index from the original array using the object that has been added to the filtered array.
contentForThisRow = [self.filteredarray objectAtIndex:row];
NSUInteger index = [self.firstarray indexOfObjectIdenticalTo:contentForThisRow];
contentForThisRow2 = [self.secondarray objectAtIndex:index];
This works on the simulator, but on the device I will sometimes get repeats of the same entry from the second array. For example, my first array contains the word "hello" three consecutive times, at indexes x, y and z. My second array contains "hei", "heisan" and "hoppsan", which are all translations of "hello", at indexes x, y and z.
On the simulator, I get three cells, each with a different translation. On the device, I get three cells, all with "hei", the first translation. This does not happen for all repeated translations.
Why is this happening, and how can I get around it?
I think the problem is that iOS (on the device) may be using a slightly different optimisation to the emulator somewhere, either in NSString or NSArray. That is a guess.
indexOfObjectIdenticalTo: returns the index of the first object that has the same memory address as the object you are passing in. On the phone it appears to have re-used the identical string objects in your first array when building the filtered array (possibly even when building firstArray), so you are getting the same index value back each time.
A better solution would be to build your filtered array as an array of dictionaries, storing the values from the correct indexes of firstArray and secondArray at that point. You can then use these values directly when populating the cell instead of searching through both arrays again. This should also have some performance benefits.
You would achieve this using the following code. First, inside your loop when you are building the filtered array, instead of adding the object from firstarray, do this:
[self.filteredArray addObject:[NSDictionary dictionaryWithObjectsAndKeys:[firstarray objectAtIndex:idx],#"english",[secondarray objectAtIndex:idx],#"translated",nil];
Then, in your cellForRowAtIndexPath, to get your two content variables:
NSDictionary *rowData = [self.filteredarray objectAtIndex:row];
contentForThisRow = [rowData objectForKey:#"english"];
contentForThisRow2 = [rowData objectForKey:#"translated"];
An even better solution would be to hold your data like this in the first place, and not try to keep two separate arrays synchronised. I imagine if you want to add or alter anything in your two separate files you could quickly get them out of step. However, I feel I've done enough for the day...
else
contentForThisRow = [self.firstarray objectAtIndex:row];
contentForThisRow2 = [self.secondarray objectAtIndex:row];
You see anything wrong with that?

Need help with NSMutableArray solution

Im currently devising a solution for my object BuildingNode *tower which is held inside NSMutableArray *gameObjects, to attack EnemyNode *enemy objects also held inside the gameObjects array.
I have a proposed solution which occasionally causes my game to freeze (temporarily) as the solution employed is quite buggy.
My solution is that each tower object contains its own NSMutableArray *targets which is synthesized from the Tower Class. If an enemy comes into range or out of range of any given tower object, the corrosponding index of the EnemyNode *enemy object from the gameObjects array is saved as an NSNumber into the targets array, or alternatively if the enemy object is out of range, it is removed from the .targets array.
Basically the idea is that the targets array holds the indices of any enemy that is in scope of the tower.
The problem I seem to be facing is that because this tower.targets array is updated dynamically all the time, i believe that if i'm doing some sort of operation on a particular index of tower.targets that then is removed, i get this error:
-[BuildingNode distance]: unrecognized selector sent to instance 0x2dd140
Each BuildingNode *tower has different attacking alogrithms that use the tower.targets array for calling back the sorted/desired enemy.
For example, a random attack style will randomize a number between 0 & [tower.targets count] then I can create a pointer to gameObjects with the corresponding [tower.targets intValue].
Something like this:
EnemyNode *enemy = (EnemyNode *)[gameObjects objectAtIndex:[[tower.targets objectAtIndex:rand]intValue]];
So this will find a random enemy from the potential .targets array and then create a pointer object to an enemy.
I've put in many if statements to ensure that in the case of a .targets index being removed mid-sorting, the operation shouldnt go ahead, thus removing the game failure rate, but it still occurs occassionally.
Heres my code:
Please note that BuildingNode *tower == BuildingNode *build.
This is just a snippet of the iteration inside gameObjects.
//Potential Enemies (Indices) Add and Delete/
if (enemy.distance < build.atk_distance && !enemy.isExploding){
NSNumber *index = [NSNumber numberWithInt:[array indexOfObject:enemy]];
if(![build.targets containsObject:index]){
[build.targets addObject:index];
}
}
else {
NSNumber *index = [NSNumber numberWithInt:[array indexOfObject:enemy]];
if ([build.targets containsObject:index]){
[build.targets removeObject:index];
}
}
}
//Aiming// Nearest Algorithm.
//Will find the nearest enemy to the building
if (enemy.distance < build.atk_distance){
if (!build.isAttacking || build.atk_id == 0){
if ([build.targets count]){
if ([build.atk_style isEqualToString:#"near"]){
int l_dist;
for (int i = 0; i < [build.targets count]; i++){
//Grab the Enemy from Targets
if ([build.targets objectAtIndex:i]){
if([array objectAtIndex:[[build.targets objectAtIndex:i]intValue]]){
EnemyNode *temp = [array objectAtIndex:[[build.targets objectAtIndex:i]intValue]];
if (temp){
int c_dist = temp.distance;
if (!l_dist || c_dist < l_dist){
l_dist = c_dist;
build.atk_id = temp.uniqueID;
build.isAttacking = YES;
}
}
}
}
}
}
}}
Unrecognized selector sent = calling a method on an object that doesn't exist. In this case, you're calling the distance method/property getter on a BuildingNode object.
The only distance I see is on your temp object, which is retrieved by EnemyNode *temp = [array objectAtIndex:[[build.targets objectAtIndex:i]intValue]]; That indicates you are really pulling a BuildingNode object out of the array instead of an EnemyNode. Find out why that happens.