¿Cuál es la Mejor Manera para mezclar un NSMutableArray?
-
09-06-2019 - |
Pregunta
Si usted tiene un NSMutableArray
, ¿cómo se puede mezclar los elementos al azar?
(Yo tengo mi propia respuesta, que se publica a continuación, pero soy nuevo en el Cacao y estoy interesada en saber si hay una mejor manera.)
Actualización:Como se nota por @Mukesh, como de iOS 10+ y macOS 10.12+, hay un -[NSMutableArray shuffledArray]
método que puede ser utilizado para mezclar.Ver 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 la mezcla de elementos en su lugar.)
Solución
Usted no necesita la swapObjectAtIndex método. exchangeObjectAtIndex:withObjectAtIndex: ya existe.
Otros consejos
Lo resuelto por la adición de una categoría a NSMutableArray.
Editar: Eliminado innecesarios método gracias a la respuesta por Ladd.
Editar: Cambiado (arc4random() % nElements)
a arc4random_uniform(nElements)
gracias a la respuesta de Gregory Goltsov y comentarios por miho y blahdiblah
Editar: Bucle de mejora, gracias al comentario de Ron
Editar: Se ha añadido una comprobación de que la matriz no está vacío, gracias a comentarios por 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
Ya no puedo todavía comentario, he pensado que me gustaría contribuir con una respuesta completa.He modificado Kristopher Johnson aplicación para mi proyecto en un número de maneras (realmente tratando de hacer tan conciso como sea posible), siendo uno de ellos arc4random_uniform()
porque evita modulo sesgo.
// 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
A partir de iOS 10 puede utilizar el nuevo shuffled
API:
https://developer.apple.com/reference/foundation/nsarray/1640855-shuffled
let shuffledArray = array.shuffled()
Un mejorado ligeramente y concisa de la solución (en comparación con las principales respuestas).
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: Una forma más concisa solución en Swift es posible a partir de iOS10 el uso de GameplayKit
.
Este es el modo más sencillo y rápido para mezclar NSArrays o NSMutableArrays (rompecabezas de objetos es un NSMutableArray, contiene rompecabezas de objetos.He añadido a rompecabezas de objetos variable índice que indica la posición inicial en la serie)
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++;
}
}
registro de salida:
#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
usted puede comparar obj1 con obj2 y decidir lo que quieres regresar los valores posibles son:
- NSOrderedAscending = -1
- NSOrderedSame = 0
- NSOrderedDescending = 1
Hay una buena biblioteca popular, que tiene este método es llamado SSToolKit en GitHub.Archivo NSMutableArray+SSToolkitAdditions.h contiene shuffle método.Puede utilizar también.Entre esto, parece ser que hay toneladas de cosas útiles.
La página principal de esta biblioteca es aquí.
Si utiliza este método, el código como este:
#import <SSCategories.h>
NSMutableArray *tableData = [NSMutableArray arrayWithArray:[temp shuffledArray]];
Esta biblioteca también tiene un Pod (ver CocoaPods)
A partir de iOS 10, usted puede utilizar NSArray shuffled()
de GameplayKit.Aquí es un auxiliar para la Matriz 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())
}
}
Si los elementos han repite.
por ejemplo,matriz:A a a B B B B B o B B B B B a a a
la única solución es:A B A B A
sequenceSelected
es un NSMutableArray que almacena los elementos de la 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;
}
}
}
}
}
NSUInteger randomIndex = arc4random() % [theArray count];
Kristopher Johnson respuesta es bastante agradable, pero no es totalmente al azar.
Dado un vector de 2 elementos, esta función devuelve siempre el invertida matriz, ya que es la generación de la gama de su aleatorios sobre el resto de los índices.Una forma más precisa shuffle()
la función 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];
}
}
}
Editar: Esto no es correcto. Para fines de referencia, yo no borrar este post.Ver comentarios sobre la razón por la que este enfoque no es correcto.
Simple código aquí:
- (NSArray *)shuffledArray:(NSArray *)array
{
return [array sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
if (arc4random() % 2) {
return NSOrderedAscending;
} else {
return NSOrderedDescending;
}
}];
}