2008-09-11 14 views
181

Si tiene NSMutableArray, ¿cómo baraja los elementos aleatoriamente?¿Cuál es la mejor manera de barajar un NSMutableArray?

(tengo mi propia respuesta para esto, lo que se publica a continuación, pero soy nuevo en cacao y estoy interesado en saber si hay una manera mejor.)


Actualización: Como señalado por @Mukesh, a partir de iOS 10+ y macOS 10.12+, existe un método -[NSMutableArray shuffledArray] que se puede usar para mezclar. Ver https://developer.apple.com/documentation/foundation/nsarray/1640855-shuffledarray?language=objc para más detalles. (Pero tenga en cuenta que esto crea una nueva matriz, en lugar de mezclar los elementos en su lugar.)

+0

Aquí está una implementación en Swift: http://iosdevelopertips.com/swift-code/swift-shuffle-array-type.html –

+0

Eche un vistazo a esta pregunta: [Problemas del mundo real con barajado ingenuo] (http://stackoverflow.com/questions/96840/real-world-problems-with-naive-shuffling) con respecto a su algoritmo de mezcla. – craigb

+4

Lo mejor actual es [Fisher-Yates] (https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle): 'para (NSUInteger i = self.count; i> 1; i--) [self exchangeObjectAtIndex: i - 1 withObjectAtIndex: arc4random_uniform ((u_int32_t) i)]; ' –

Respuesta

341

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) gracias a contestar por Gregory Goltsov y comentarios de Miho y blahdiblah

Editar: mejora Loop, gracias al comentario de Ron

Editar: cheque Agregado esa 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 
+10

Buena solución. Y sí, como menciona willc2, reemplazar random() con arc4random() es una buena mejora ya que no se requiere siembra. –

+4

@Jason: a veces (por ejemplo, al realizar pruebas), ser capaz de suministrar una semilla es algo bueno. Kristopher: buen algoritmo. Es una implementación del algoritmo de Fisher-Yates: http://en.wikipedia.org/wiki/Fisher-Yates_shuffle – JeremyP

+0

En general, es mejor usar 'arc4random()' en lugar de 'random()'. La calidad de los números es mucho mejor y no es necesario sembrar. – zaph

5

Esta es la forma más sencilla y rápida para mezclar NSArrays o NSMutableArrays (rompecabezas de objetos es un NSMutableArray, que contiene objetos rompecabezas. He añadido a rompecabezas objeto de índice variable que indica la posición inicial en la matriz)

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 del 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 

es posible que así comparar con obj1 obj2 y decidir lo que desea devolver valores posibles son:

  • NSOrderedAscending = -1
  • NSOrderedSame = 0
  • NSOrderedDescending = 1
+1

También para esta solución, use arc4random() o seed. –

+17

Este cambio de ruteo es defectuoso, como Microsoft ha recordado recientemente: http://www.robweir.com/blog/2010/02/microsoft-random-browser-ballot.html. –

+0

De acuerdo, defectuoso porque "la ordenación requiere una definición auto consistente de ordenamiento" como se señala en ese artículo sobre MS. Se ve elegante, pero no lo es. – Jeff

-1
NSUInteger randomIndex = arc4random() % [theArray count]; 
+2

o 'arc4random_uniform ([theArray count])' sería aún mejor, si está disponible en la versión de Mac OS X o iOS que está soportando. –

+1

I dado así, el número se repetirá. –

35

Dado que todavía no puedo comentar, pensé que había aportar 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 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 
+2

Tenga en cuenta que está llamando '[self count]' (un captador de propiedades) dos veces en cada iteración a través del ciclo. Creo que moverlo fuera del circuito vale la pena perder la concisión. –

+1

Y es por eso que todavía prefiero '[método de objeto]' en lugar de 'objeto.metodo': las personas tienden a olvidar que lo último no es tan barato como acceder a un miembro de estructura, viene con el costo de una llamada a método ... muy mal en un bucle. – DarkDust

+0

Gracias por las correcciones - Supuse erróneamente que el conteo se almacenaba en la memoria caché, por alguna razón. Actualizado la respuesta. – gregoltsov

1

Hay una buena biblioteca popular, que tiene este método ya que es parte, llamada SSToolKit in 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 es here.

Si se utiliza este, su código será la siguiente:

#import <SSCategories.h> 
NSMutableArray *tableData = [NSMutableArray arrayWithArray:[temp shuffledArray]]; 

Esta biblioteca también tiene una vaina (ver CocoaPods)

0

Si los elementos tienen repeticiones.

p. Ej. matriz: A A A B B o B B A A A

única solución es: A B A B A

sequenceSelected es un NSMutableArray que almacena elementos de obj clase, 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; 
       } 
      } 
     } 
    } 
} 
+0

usando un 'static' evita trabajar en múltiples instancias: sería mucho más seguro y legible usar dos métodos, uno principal que se mezcla y llama al método secundario, mientras que el método secundario solo se llama a sí mismo y nunca reorganiza. También hay un error de ortografía. –

-1

Kristopher Johnson's answer es bastante agradable, pero no es totalmente aleatorio.

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 más precisa shuffle() 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]; 
     } 
    } 
} 
+0

Creo que el algoritmo que ha sugerido es una "mezcla ingenua". Ver http://blog.codinghorror.com/the-danger-of-naivete/. Creo que mi respuesta tiene un 50% de posibilidades de intercambiar los elementos si solo hay dos: cuando i es cero, arc4random_uniform (2) devolverá 0 o 1, por lo que el elemento zeroth se intercambiará consigo mismo o se intercambiará con el oneth elemento. En la siguiente iteración, cuando i es 1, arc4random (1) siempre devolverá 0, y el i-ésimo elemento siempre se intercambiará consigo mismo, lo cual es ineficiente pero no es incorrecto. (Tal vez la condición de bucle debería ser 'i <(count-1)'.) –

-2

Editar: Esto no es correcto. Como 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; 
     } 
    }]; 
} 
+0

Esta mezcla está defectuosa - http://robweir.com/blog/2010/02/microsoft-random-browser-ballot.html –

4

Después de editar las respuestas más, pensé en compartir una solución ligeramente mejorado y concisa.

El algoritmo es el mismo y se describe en la literatura como "Fisher-Yates shuffle".

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: A more concise solution in Swift is possible from iOS10 using GameplayKit.

Nota : An algorithm for unstable shuffling (with all positions forced to change if count > 1) is also available

+0

¿Qué haría? ¿Cuál es la diferencia entre esto y el algoritmo de Kristopher Johnson? –

+0

@IulianOnofrei, originalmente, el código de Kristopher Johnson no era óptimo y mejoré su respuesta, luego se volvió a editar con un chequeo inicial inútil. Prefiero mi manera concisa de escribirlo. El algoritmo es el mismo y se describe en la literatura como "[mezcla aleatoria de Fisher-Yates] (https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle)". –

1

De IOS 10, puede utilizar NSArray shuffled() from GameplayKit. He aquí un ayudante para la matriz en Swift 3:

import GameplayKit 

extension Array { 
    func shuffled() -> [Element] { 
     return (self as NSArray).shuffled() as! [Element] 
    } 
    mutating func shuffle() { 
     replaceSubrange(0..<count, with: shuffled()) 
    } 
} 
Cuestiones relacionadas