Quicksort Error when sorting large array - objective-c

I get an error message when I try to sort an array of one hundred thousand ints(I've tested the sort and it works for fifty thousand):
warning: could not load any Objective-C class information. This will significantly reduce the quality of type information available.
I also have the compare method iteratively and the same error appears
Why is this error message happening?
Is it possible to sort that list and if so how to fix it?
-(void) quickSort{
return [self quickSort:0 pivot:[self.toSort count]-1 right: [self.toSort count]-1];
}
-(void) quickSort:(int) left pivot:(int) pivot right:(int) right {
if(left < right){
pivot = [self compareToPivot:[[self.toSort objectAtIndex:right] intValue] start:left finish:right position:left];
[self quickSort:pivot+1 pivot:pivot right:right];
[self quickSort:left pivot:pivot right:pivot-1];
}
}
-(int) compareToPivot:(int) pivot start:(int)start finish:(int)finish position:(int)pos{
if(pos == finish){
[self switchNums:start second:finish];
return start;
}
if([[self.toSort objectAtIndex:pos] intValue] <= pivot){
[self switchNums:pos second:start];
return [self compareToPivot:pivot start:start+1 finish:finish position:pos+1];
}
return [self compareToPivot:pivot start:start finish:finish position:pos+1];
}
-(void) switchNums:(int)first second:(int)second{
id temp = [self.toSort objectAtIndex:second];
[self.toSort replaceObjectAtIndex:second withObject:[self.toSort objectAtIndex:first]];
[self.toSort replaceObjectAtIndex:first withObject:temp];
}
Iterative Compare and this runs fine:
-(int) compareToPivot:(int) pivot start:(int)start finish:(int)finish position:(int)pos{
while(pos != finish){
if([[self.toSort objectAtIndex:pos] intValue] <= pivot){
[self switchNums:pos second:start];
start+=1;
}
pos++;
}
[self switchNums:start second:finish];
return start;
}

While Quicksort itself is considered a recursive algorithm, the partition step (your comparetoPivot method) is not meant for recursion. And your implementation of this method is not only recursive, but it also is O(N) as far as stack space is concerned. You only recurse with one less element than before with each recursion into compareToPivot. Blowing up (running out stack) at around 100K elements sounds is to be expected as the default stack is around 500KB.
I used your question as an excuse to practice my Obj-C, which I'm a bit rusty on. But here's the implementation of recursive Quicksort straight out of the classic Algorithms (Cormen et. al.) book and adapted for your toSort array. I haven't tested it all out yet, but you can certainly give it a whirl.
-(int)partition: (NSMutableArray*)arr pivot:(int)pivot right:(int)right
{
int x = [[arr objectAtIndex: pivot] intValue];
int i = pivot;
int j = right;
while (true)
{
while ([[arr objectAtIndex: j] intValue] > x)
{
j--;
}
while ([[arr objectAtIndex: i] intValue] < x)
{
i++;
}
if (i < j) {
id objI = [arr objectAtIndex: i];
id objJ = [arr objectAtIndex: j];
[arr replaceObjectAtIndex:i withObject:objJ];
[arr replaceObjectAtIndex:j withObject:objI];
j--;
i++;
}
else
{
return j;
}
}
}
-(void)quickSort
{
int len = (int)[self.toSort count];
[self quicksort:self.toSort left:0 right:(len-1)];
}
-(void)quicksort: (NSMutableArray*)arr left:(int)left right:(int)right
{
if (left< right)
{
int q = [self partition:arr pivot:left right:right];
[self quicksort: arr left:left right:q];
[self quicksort: arr left:(q+1) right:right];
}
}

Related

How to safely remove object from a nsmutablearray while iterating through it?

I am using a nsmutablearray in loop and want to remove its object (or assign nil) that has just traversed.
But if I am doing so, I get an error as <__NSArrayM: 0x8c3d3a0> was mutated while being enumerated.' . The code is as below
- (TreeNode*)depthLimitedSearch:(TreeNode *)current costLimit:(int)currentCostBound {
NSMutableArray *children=[NSMutableArray arrayWithArray:[current expandNodeToChildren]];
for (TreeNode *s in children) {
if (s.puzzleBox.isFinalPuzzleBox) {//checking for final puzzleBox
return s;
}
/*exploredNodes++;
if (exploredNodes %10000==0) {
NSLog(#"explored nodes for this treshold-%d are %d",currentCostBound,exploredNodes);
}*/
int currentCost =[s.cost intValue]+[s.heuristicsCost intValue];
if (currentCost <= currentCostBound) {
//[s.puzzleBox displayPuzzleBox];
TreeNode *solution = [self depthLimitedSearch:s costLimit:currentCostBound];
if (solution!=nil){//&& (bestSolution ==nil|| [solution.cost intValue] < [bestSolution.cost intValue])) {
bestSolution = solution;
return bestSolution;
}
}else {
if (currentCost < newLimit) {
//NSLog(#"new limit %d", currentCost);
newLimit = currentCost;
}
}
// here I want to free memory used by current child in children
[children removeObject:s]
}
children=nil;
return nil;
}
and I have commented the place where I want to release the space used by the child.
You should not be using a for...in loop if you want to remove elements in the array. Instead, you should use a normal for loop and go backwards in order to make sure you don't skip any items.
for (NSInteger i = items.count - 1; i >= 0; i--) {
if (someCondition) {
[items removeObjectAtIndex:i];
}
}
You can collect the items to be removed in another array and remove them in a single pass afterwards:
NSMutableArray *toRemove = [NSMutableArray array];
for (id candidate in items) {
if (something) {
[toRemove addObject:candidate];
}
}
[items removeObjectsInArray:toRemove];
It’s easier than iterating over indexes by hand, which just asking for off-by-one errors.
Not sure how this plays with your early returns, though.

Efficient way of checking the content of every NSDictionary in NSArray

In my app I'me getting responses from the server and I have to check that I don't create duplicate objects in the NSArray which contains NSDictionaries. Now to check if the objects exists I do this:
for (int i = 0; i < appDelegate.currentUser.userSiteDetailsArray.count; i++){
NSDictionary *tmpDictionary = [appDelegate.currentUser.userSiteDetailsArray objectAtIndex:i];
if ([[tmpDictionary valueForKey:#"webpropID"] isEqualToString:tmpWebproperty.identifier]){
needToCheck = NO;
}
if (i == appDelegate.currentUser.userSiteDetailsArray.count - 1 && ![[tmpDictionary valueForKey:#"webpropID"] isEqualToString:tmpWebproperty.identifier] && needToCheck){
// It means it's the last object we've iterated through and needToCheck is still = YES;
//Doing stuff here
}
}
I set up a BOOL value because this iteration goes numerous times inside a method and I can't use return to stop it. I think there is a better way to perform this check and I would like to hear your suggestions about it.
BOOL needToCheck = YES;
for (int i = 0; i < appDelegate.currentUser.userSiteDetailsArray.count; i++){
NSDictionary *tmpDictionary = [appDelegate.currentUser.userSiteDetailsArray objectAtIndex:i];
if ([[tmpDictionary valueForKey:#"webpropID"] isEqualToString:tmpWebproperty.identifier]){
needToCheck = NO;
break;
}
}
if (needToCheck) {
//Doing stuff here
}
But, as others have said, you can maybe keep a "summary" in a separate NSSet that you check first, vs spinning through all the dictionaries.
NSDictionary *previousThing = nil;
for (NSDictionary *thing in appDelegate.currentUser.userSiteDetailsArray) {
if ([thing[#"webpropID"] isEqualToString:newWebPropertyIdentifier]) {
previousThing = thing;
break;
}
}
if (previousThing == nil) {
// no previous thing
} else {
// duplicate
}

EXC_ARITHMETIC when accessing random elements of NSArray

i'm trying to get the values of an array randomly but i'm getting an error
here is my code so far:
NSMutableArray *validMoves = [[NSMutableArray alloc] init];
for (int i = 0; i < 100; i++){
[validMoves removeAllObjects];
for (TileClass *t in tiles ) {
if ([self blankTile:t] != 0) {
[validMoves addObject:t];
}
}
NSInteger pick = arc4random() % validMoves.count;
[self movePiece:(TileClass *)[validMoves objectAtIndex:pick] withAnimation:NO];
}
The error you're getting (an arithmetic exception) is because validMoves is empty and this leads to a division by zero when you perform the modulus operation.
You have to explicitly check for the case of an empty validMoves array.
Also you should use arc4random_uniform for avoiding modulo bias.
if (validMoves.count > 0) {
NSInteger pick = arc4random_uniform(validMoves.count);
[self movePiece:(TileClass *)[validMoves objectAtIndex:pick] withAnimation:NO];
} else {
// no valid moves, do something reasonable here...
}
As a final remark not that arc4random_uniform(0) returns 0, therefore such case should be avoided or you'll be trying to access the first element of an empty array, which of course will crash your application.

What is faster? Enumeration VS For loop

What is faster in objective C and iphone? self enumeration or for loop?
i have 2 fragments of code to help me compare.
for this example we have as a fact that array is an NSMutableArray with "x" items.
Case 1:
-(void)findItem:(Item*)item
{
Item *temp;
for (int i = 0 ;i<[array count];i++)
{
temp = [array objectAtIndex:i];
if(item.tag == temp.tag)
return;
}
}
Case 2:
-(void)findItem:(Item*)item
{
for(Item *temp in array)
{
if(item.tag == temp.tag)
return;
}
}
it is almost obvious that case2 is faster, is it?
It's called fast enumeration, for a reason.
See: http://cocoawithlove.com/2008/05/fast-enumeration-clarifications.html

What's the Best Way to Shuffle an NSMutableArray?

If you have an NSMutableArray, how do you shuffle the elements randomly?
(I have my own answer for this, which is posted below, but I'm new to Cocoa and I'm interested to know if there is a better way.)
Update: As noted by #Mukesh, as of iOS 10+ and macOS 10.12+, there is an -[NSMutableArray shuffledArray] method that can be used to shuffle. See https://developer.apple.com/documentation/foundation/nsarray/1640855-shuffledarray?language=objc for details. (But note that this creates a new array, rather than shuffling the elements in place.)
I solved this by adding a category to NSMutableArray.
Edit: Removed unnecessary method thanks to answer by Ladd.
Edit: Changed (arc4random() % nElements) to arc4random_uniform(nElements) thanks to answer by Gregory Goltsov and comments by miho and blahdiblah
Edit: Loop improvement, thanks to comment by Ron
Edit: Added check that array is not empty, thanks to comment by Mahesh Agrawal
// NSMutableArray_Shuffling.h
#if TARGET_OS_IPHONE
#import <UIKit/UIKit.h>
#else
#include <Cocoa/Cocoa.h>
#endif
// This category enhances NSMutableArray by providing
// methods to randomly shuffle the elements.
#interface NSMutableArray (Shuffling)
- (void)shuffle;
#end
// NSMutableArray_Shuffling.m
#import "NSMutableArray_Shuffling.h"
#implementation NSMutableArray (Shuffling)
- (void)shuffle
{
NSUInteger count = [self count];
if (count <= 1) return;
for (NSUInteger i = 0; i < count - 1; ++i) {
NSInteger remainingCount = count - i;
NSInteger exchangeIndex = i + arc4random_uniform((u_int32_t )remainingCount);
[self exchangeObjectAtIndex:i withObjectAtIndex:exchangeIndex];
}
}
#end
You don't need the swapObjectAtIndex method. exchangeObjectAtIndex:withObjectAtIndex: already exists.
Since I can't yet comment, I thought I'd contribute a full response. I modified Kristopher Johnson's implementation for my project in a number of ways (really trying to make it as concise as possible), one of them being arc4random_uniform() because it avoids modulo bias.
// NSMutableArray+Shuffling.h
#import <Foundation/Foundation.h>
/** This category enhances NSMutableArray by providing methods to randomly
* shuffle the elements using the Fisher-Yates algorithm.
*/
#interface NSMutableArray (Shuffling)
- (void)shuffle;
#end
// NSMutableArray+Shuffling.m
#import "NSMutableArray+Shuffling.h"
#implementation NSMutableArray (Shuffling)
- (void)shuffle
{
NSUInteger count = [self count];
for (uint i = 0; i < count - 1; ++i)
{
// Select a random element between i and end of array to swap with.
int nElements = count - i;
int n = arc4random_uniform(nElements) + i;
[self exchangeObjectAtIndex:i withObjectAtIndex:n];
}
}
#end
If you import GameplayKit, there is a shuffled API:
https://developer.apple.com/reference/foundation/nsarray/1640855-shuffled
let shuffledArray = array.shuffled()
A slightly improved and concise solution (compared to the top answers).
The algorithm is the same and is described in literature as "Fisher-Yates shuffle".
In Objective-C:
#implementation NSMutableArray (Shuffle)
// Fisher-Yates shuffle
- (void)shuffle
{
for (NSUInteger i = self.count; i > 1; i--)
[self exchangeObjectAtIndex:i - 1 withObjectAtIndex:arc4random_uniform((u_int32_t)i)];
}
#end
In Swift 3.2 and 4.x:
extension Array {
/// Fisher-Yates shuffle
mutating func shuffle() {
for i in stride(from: count - 1, to: 0, by: -1) {
swapAt(i, Int(arc4random_uniform(UInt32(i + 1))))
}
}
}
In Swift 3.0 and 3.1:
extension Array {
/// Fisher-Yates shuffle
mutating func shuffle() {
for i in stride(from: count - 1, to: 0, by: -1) {
let j = Int(arc4random_uniform(UInt32(i + 1)))
(self[i], self[j]) = (self[j], self[i])
}
}
}
Note: A more concise solution in Swift is possible from iOS10 using GameplayKit.
Note: An algorithm for unstable shuffling (with all positions forced to change if count > 1) is also available
This is the simplest and fastest way to shuffle NSArrays or NSMutableArrays
(object puzzles is a NSMutableArray, it contains puzzle objects. I've added to
puzzle object variable index which indicates initial position in array)
int randomSort(id obj1, id obj2, void *context ) {
// returns random number -1 0 1
return (random()%3 - 1);
}
- (void)shuffle {
// call custom sort function
[puzzles sortUsingFunction:randomSort context:nil];
// show in log how is our array sorted
int i = 0;
for (Puzzle * puzzle in puzzles) {
NSLog(#" #%d has index %d", i, puzzle.index);
i++;
}
}
log output:
#0 has index #6
#1 has index #3
#2 has index #9
#3 has index #15
#4 has index #8
#5 has index #0
#6 has index #1
#7 has index #4
#8 has index #7
#9 has index #12
#10 has index #14
#11 has index #16
#12 has index #17
#13 has index #10
#14 has index #11
#15 has index #13
#16 has index #5
#17 has index #2
you may as well compare obj1 with obj2 and decide what you want to return
possible values are:
NSOrderedAscending = -1
NSOrderedSame = 0
NSOrderedDescending = 1
From iOS 10, you can use NSArray shuffled() from GameplayKit. Here is an helper for Array in Swift 3:
import GameplayKit
extension Array {
#available(iOS 10.0, macOS 10.12, tvOS 10.0, *)
func shuffled() -> [Element] {
return (self as NSArray).shuffled() as! [Element]
}
#available(iOS 10.0, macOS 10.12, tvOS 10.0, *)
mutating func shuffle() {
replaceSubrange(0..<count, with: shuffled())
}
}
There is a nice popular library, that has this method as it's part, called SSToolKit in GitHub.
File NSMutableArray+SSToolkitAdditions.h contains shuffle method. You can use it also. Among this, there seem to be tons of useful things.
The main page of this library is here.
If you use this, your code will be like this:
#import <SSCategories.h>
NSMutableArray *tableData = [NSMutableArray arrayWithArray:[temp shuffledArray]];
This library also has a Pod (see CocoaPods)
If elements have repeats.
e.g. array: A A A B B or B B A A A
only solution is: A B A B A
sequenceSelected is an NSMutableArray which stores elements of class obj, which are pointers to some sequence.
- (void)shuffleSequenceSelected {
[sequenceSelected shuffle];
[self shuffleSequenceSelectedLoop];
}
- (void)shuffleSequenceSelectedLoop {
NSUInteger count = sequenceSelected.count;
for (NSUInteger i = 1; i < count-1; i++) {
// Select a random element between i and end of array to swap with.
NSInteger nElements = count - i;
NSInteger n;
if (i < count-2) { // i is between second and second last element
obj *A = [sequenceSelected objectAtIndex:i-1];
obj *B = [sequenceSelected objectAtIndex:i];
if (A == B) { // shuffle if current & previous same
do {
n = arc4random_uniform(nElements) + i;
B = [sequenceSelected objectAtIndex:n];
} while (A == B);
[sequenceSelected exchangeObjectAtIndex:i withObjectAtIndex:n];
}
} else if (i == count-2) { // second last value to be shuffled with last value
obj *A = [sequenceSelected objectAtIndex:i-1];// previous value
obj *B = [sequenceSelected objectAtIndex:i]; // second last value
obj *C = [sequenceSelected lastObject]; // last value
if (A == B && B == C) {
//reshufle
sequenceSelected = [[[sequenceSelected reverseObjectEnumerator] allObjects] mutableCopy];
[self shuffleSequenceSelectedLoop];
return;
}
if (A == B) {
if (B != C) {
[sequenceSelected exchangeObjectAtIndex:i withObjectAtIndex:count-1];
} else {
// reshuffle
sequenceSelected = [[[sequenceSelected reverseObjectEnumerator] allObjects] mutableCopy];
[self shuffleSequenceSelectedLoop];
return;
}
}
}
}
}
NSUInteger randomIndex = arc4random() % [theArray count];
Kristopher Johnson's answer is pretty nice, but it's not totally random.
Given an array of 2 elements, this function returns always the inversed array, because you are generating the range of your random over the rest of the indexes. A more accurate shuffle() function would be like
- (void)shuffle
{
NSUInteger count = [self count];
for (NSUInteger i = 0; i < count; ++i) {
NSInteger exchangeIndex = arc4random_uniform(count);
if (i != exchangeIndex) {
[self exchangeObjectAtIndex:i withObjectAtIndex:exchangeIndex];
}
}
}
Edit: This is not correct. For reference purposes, I did not delete this post. See comments on the reason why this approach is not correct.
Simple code here:
- (NSArray *)shuffledArray:(NSArray *)array
{
return [array sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
if (arc4random() % 2) {
return NSOrderedAscending;
} else {
return NSOrderedDescending;
}
}];
}