Domanda

Se hai un NSMutableArray, come mescoli gli elementi in modo casuale?

(Ho la mia risposta a questo, che è pubblicata di seguito, ma sono nuovo a Cocoa e sono interessato a sapere se esiste un modo migliore.)


Aggiornamento:Come notato da @Mukesh, a partire da iOS 10+ e macOS 10.12+, esiste un -[NSMutableArray shuffledArray] metodo che può essere utilizzato per mescolare.Vedere https://developer.apple.com/documentation/foundation/nsarray/1640855-shuffledarray?lingual=objc per dettagli.(Ma tieni presente che questo crea un nuovo array, invece di mescolare gli elementi al loro posto.)

È stato utile?

Soluzione

Non è necessario il metodo swapObjectAtIndex. scambioObjectAtIndex: conObjectAtIndex: esiste già.

Altri suggerimenti

Ho risolto questo problema aggiungendo una categoria a NSMutableArray.

Modificare: Rimosso il metodo non necessario grazie alla risposta di Ladd.

Modificare: Cambiato (arc4random() % nElements) A arc4random_uniform(nElements) grazie alla risposta di Gregory Goltsov e ai commenti di miho e blahdiblah

Modificare: Miglioramento del loop, grazie al commento di Ron

Modificare: Aggiunto controllo che l'array non sia vuoto, grazie al commento di 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

Dato che non posso ancora commentare, ho pensato di contribuire con una risposta completa.Ho modificato l'implementazione di Kristopher Johnson per il mio progetto in diversi modi (cercando davvero di renderlo il più conciso possibile), uno dei quali è arc4random_uniform() perché evita distorsione del modulo.

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

Da iOS 10 puoi utilizzare il nuovo shuffled API:

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

let shuffledArray = array.shuffled()

Una soluzione leggermente migliorata e concisa (rispetto alle risposte principali).

L'algoritmo è lo stesso ed è descritto in letteratura come "Mescolamento Fisher-Yates".

Nell'Obiettivo 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

In Swift 3.2 e 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))))
        }
    }
}

In Swift 3.0 e 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 soluzione più concisa in Swift è possibile da iOS10 utilizzando GameplayKit.

Nota: È disponibile anche un algoritmo per il mescolamento instabile (con tutte le posizioni costrette a cambiare se il conteggio > 1).

Questo è il modo più semplice e veloce per mescolare nsarrays o nsmutablearrays (gli enigmi di oggetti è un nsmutablearray, contiene oggetti puzzle.Ho aggiunto l'indice della variabile oggetto puzzle che indica la posizione iniziale in array)

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++;
    }
}

output 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

Puoi anche confrontare OBJ1 con OBJ2 e decidere cosa vuoi restituire possibili valori:

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

C'è una bella libreria popolare, che ha questo metodo come parte, chiamata SSToolKit in GitHub.Il file NSMutableArray+SSToolkitAdditions.h contiene il metodo shuffle.Puoi usarlo anche tu.Tra questi, sembrano esserci tantissime cose utili.

La pagina principale di questa libreria è Qui.

Se lo usi, il tuo codice sarà così:

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

Questa libreria ha anche un Pod (vedi CocoaPods)

Da iOS 10, puoi utilizzare NSArray shuffled() da GameplayKit.Ecco un aiuto per Array in 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())
    }
}

Se gli elementi hanno ripetizioni.

per esempio.vettore:A A A B B oppure B B A A A

l'unica soluzione è:A B A B A

sequenceSelected è un NSMutableArray che memorizza elementi della classe obj, che sono puntatori a una sequenza.

- (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];

La risposta di Kristopher Johnson è piuttosto carino, ma non è del tutto casuale.

Dato un array di 2 elementi, questa funzione restituisce sempre l'array invertito, perché stai generando l'intervallo del tuo random sul resto degli indici.Uno più accurato shuffle() la funzione sarebbe come

- (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];
       }
   }
}

Modificare: Questo non è corretto. A scopo di riferimento, non ho eliminato questo post.Vedi i commenti sul motivo per cui questo approccio non è corretto.

Codice semplice qui:

- (NSArray *)shuffledArray:(NSArray *)array
{
    return [array sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
        if (arc4random() % 2) {
            return NSOrderedAscending;
        } else {
            return NSOrderedDescending;
        }
    }];
}
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top