¿Cuál es la mejor manera de mezclar un NSMutableArray?
Si tienes un NSMutableArray
, ¿cómo se mezclan los elementos al azar?
(Tengo mi propia respuesta para esto, que se publica a continuación, pero soy nuevo en Cocoa y me interesa saber si hay una manera mejor).
Actualización: como señaló @Mukesh, a partir de iOS 10+ y macOS 10.12+, existe un -[NSMutableArray shuffledArray]
método que se puede utilizar para mezclar. Consulte https://developer.apple.com/documentation/foundation/nsarray/1640855-shuffledarray?language=objc para obtener más detalles. (Pero tenga en cuenta que esto crea una nueva matriz, en lugar de mezclar los elementos en su lugar).
Resolví esto agregando una categoría a NSMutableArray.
Editar: Se eliminó el método innecesario gracias a la respuesta de Ladd.
Editar: cambiado (arc4random() % nElements)
a arc4random_uniform(nElements)
agradecimiento por la respuesta de Gregory Goltsov y comentarios de miho y blahdiblah
Editar: mejora del bucle, gracias al comentario de Ron
Editar: se agregó verificación de que la matriz no esté vacía, gracias al comentario de 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
No necesita el método swapObjectAtIndex. exchangeObjectAtIndex:withObjectAtIndex: ya existe.
Como todavía no puedo comentar, pensé en contribuir con una respuesta completa. Modifiqué la implementación de Kristopher Johnson para mi proyecto de varias maneras (realmente tratando de hacerlo lo más conciso posible), una de ellas arc4random_uniform()
porque evita el sesgo de módulo .
// 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
Si importa GameplayKit
, hay una shuffled
API:
https://developer.apple.com/reference/foundation/nsarray/1640855-shuffled
let shuffledArray = array.shuffled()
Una solución concisa y ligeramente mejorada (en comparación con las respuestas principales).
El algoritmo es el mismo y se describe en la literatura como " Fisher-Yates shuffle ".
En 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
En Swift 3.2 y 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))))
}
}
}
En Swift 3.0 y 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])
}
}
}
Nota: Es posible encontrar una solución más concisa en Swift desde iOS10 usando GameplayKit
.
Nota: También está disponible un algoritmo para barajado inestable (con todas las posiciones obligadas a cambiar si el conteo > 1)