objective-c - profesional - trucos con barajas
¿Cuál es la mejor manera de barajar un NSMutableArray? (12)
Si tiene un NSMutableArray
, ¿cómo baraja los elementos aleatoriamente?
(Tengo mi propia respuesta para esto, que se publica a continuación, pero soy nuevo en Cocoa y estoy interesado en saber si hay una mejor manera).
Actualización: Tal como lo señala @Mukesh, a partir de iOS 10+ y macOS 10.12+, existe un método -[NSMutableArray shuffledArray]
que se puede usar para mezclar. Consulte https://developer.apple.com/documentation/foundation/nsarray/1640855-shuffledarray?language=objc para obtener más información. (Pero tenga en cuenta que esto crea una nueva matriz, en lugar de mezclar los elementos en su lugar).
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 es 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
Desde iOS 10 puedes usar la nueva API shuffled
:
https://developer.apple.com/reference/foundation/nsarray/1640855-shuffled
let shuffledArray = array.shuffled()
Desde iOS 10, puedes usar https://developer.apple.com/reference/foundation/nsarray/1640855-shuffled . Aquí hay un ayudante para Array en 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())
}
}
Después de editar las respuestas principales, pensé en compartir una solución levemente mejorada y concisa.
El algoritmo es el mismo y se describe en la literatura como " Fisher-Yates ".
En ObjectiveC:
@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: una solución más concisa en Swift es posible desde iOS10 usando GameplayKit
.
Nota: También está disponible un algoritmo para la mezcla inestable (con todas las posiciones forzadas a cambiar si cuenta> 1)
Editar: Esto no es correcto. A modo de referencia, no eliminé esta publicación. Vea los comentarios sobre la razón por la cual este enfoque no es correcto.
Código simple aquí:
- (NSArray *)shuffledArray:(NSArray *)array
{
return [array sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
if (arc4random() % 2) {
return NSOrderedAscending;
} else {
return NSOrderedDescending;
}
}];
}
Esta es la forma más simple y rápida de barajar NSArrays o NSMutableArrays (los rompecabezas de objetos es un NSMutableArray, contiene objetos de rompecabezas. He añadido al rompecabezas el índice de variables de objetos que indica la posición inicial en el conjunto)
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++;
}
}
salida de registro:
#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
también puede comparar obj1 con obj2 y decidir qué valores de retorno desea devolver:
- NSOrderedAscending = -1
- NSOrderedSame = 0
- NSOrderedDescending = 1
Hay una buena biblioteca popular, que tiene este método, ya que es parte, llamado SSToolKit en GitHub . El archivo NSMutableArray + SSToolkitAdditions.h contiene el método de mezcla aleatoria. Puedes usarlo también Entre esto, parece que hay toneladas de cosas útiles.
La página principal de esta biblioteca está aquí .
Si usa esto, su código será así:
#import <SSCategories.h>
NSMutableArray *tableData = [NSMutableArray arrayWithArray:[temp shuffledArray]];
Esta biblioteca también tiene un Pod (ver CocoaPods)
Lo resolví agregando una categoría a NSMutableArray.
Editar: eliminó el método innecesario gracias a la respuesta de Ladd.
Editar: Cambiado (arc4random() % nElements)
a arc4random_uniform(nElements)
gracias a la respuesta de Gregory Goltsov y los comentarios de miho y blahdiblah
Editar: mejora de bucle, gracias al comentario de Ron
Editar: comprobado adicional 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.
Si los elementos tienen repeticiones.
por ejemplo, matriz: AAABB o BBAAA
La única solución es: ABABA
sequenceSelected
es un NSMutableArray que almacena elementos de clase obj, que son punteros a alguna secuencia.
- (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;
}
}
}
}
}
La respuesta de Kristopher Johnson es bastante buena, pero no es totalmente aleatoria.
Dada una matriz de 2 elementos, esta función devuelve siempre la matriz inversa, porque está generando el rango de su azar sobre el resto de los índices. Una función shuffle()
más precisa sería como
- (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];
}
}
}
NSUInteger randomIndex = arc4random() % [theArray count];