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.)

¿Fue útil?

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.

Nota: Un algoritmo para inestables arrastrando los pies (con todas las posiciones forzadas a cambiar si cuenta > 1) también está disponible

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;
        }
    }];
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top