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 an
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
If you import GameplayKit
, there is a shuffled
API:
https://developer.apple.com/reference/foundation/nsarray/1640855-shuffled
let shuffledArray = array.shuffled()
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;
}
}];
}
You don't need the swapObjectAtIndex method. exchangeObjectAtIndex:withObjectAtIndex: already exists.
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
NSUInteger randomIndex = arc4random() % [theArray count];