¿Cuál es la mejor manera de mezclar un NSMutableArray?

Resuelto Kristopher Johnson asked hace 16 años • 12 respuestas

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

Kristopher Johnson avatar Sep 11 '08 21:09 Kristopher Johnson
Aceptado

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
Kristopher Johnson avatar Sep 11 '2008 14:09 Kristopher Johnson

No necesita el método swapObjectAtIndex. exchangeObjectAtIndex:withObjectAtIndex: ya existe.

 avatar Sep 11 '2008 21:09

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
gregoltsov avatar Jun 03 '2012 22:06 gregoltsov

Si importa GameplayKit, hay una shuffledAPI:

https://developer.apple.com/reference/foundation/nsarray/1640855-shuffled

let shuffledArray = array.shuffled()
andreacipriani avatar Nov 15 '2016 15:11 andreacipriani

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)

Cœur avatar Nov 21 '2015 07:11 Cœur