Qual é a melhor maneira de embaralhar um NSMutableArray?

Se você tem um NSMutableArray , como você embaralha os elementos aleatoriamente?

(Eu tenho minha própria resposta para isso, que está postada abaixo, mas eu sou novo no Cocoa e estou interessado em saber se existe uma maneira melhor.)


Atualização: Conforme observado por @Mukesh, a partir do iOS 10+ e do macOS 10.12+, existe um método -[NSMutableArray shuffledArray] que pode ser usado para reproduzir aleatoriamente. Consulte https://developer.apple.com/documentation/foundation/nsarray/1640855-shuffledarray?language=objc para obter detalhes. (Mas observe que isso cria uma nova matriz, em vez de embaralhar os elementos no lugar.)

Você não precisa do método swapObjectAtIndex. exchangeObjectAtIndex: withObjectAtIndex: já existe.

Eu resolvi isso adicionando uma categoria ao NSMutableArray.

Edit: Removido o método desnecessário graças a resposta de Ladd.

Editar: Alterado (arc4random() % nElements) para arc4random_uniform(nElements) graças à resposta de Gregory Goltsov e comentários por miho e blahdiblah

Editar: Melhoria de loop, graças ao comentário de Ron

Editar: Adicionado verifique que a matriz não está vazia, graças ao comentário por Mahesh Agrawal

 // NSMutableArray_Shuffling.h #if TARGET_OS_IPHONE #import  #else #include  #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 

Como ainda não posso comentar, pensei em contribuir com uma resposta completa. Eu modifiquei a implementação de Kristopher Johnson para o meu projeto de várias maneiras (realmente tentando torná-lo o mais conciso possível), uma delas sendo arc4random_uniform() porque evita o viés do módulo .

 // NSMutableArray+Shuffling.h #import  /** 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 

No iOS 10, você pode usar a nova API shuffled :

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

 let shuffledArray = array.shuffled() 

Uma solução ligeiramente melhorada e concisa (em comparação com as respostas principais).

O algoritmo é o mesmo e é descrito na literatura como ” Fisher-Yates shuffle “.

Em 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 

No 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)))) } } } 

No 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: Uma solução mais concisa no Swift é possível a partir do iOS10 usando o GameplayKit .

Nota: Um algoritmo para shuffling instável (com todas as posições forçadas a mudar se contar> 1) também está disponível

Esta é a maneira mais simples e rápida de embaralhar NSArrays ou NSMutableArrays (quebra-cabeças de objects é um NSMutableArray, contém objects de quebra-cabeça. Eu adicionei ao índice de variável de object de quebra-cabeça que indica a posição inicial na 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++; } } 

saída de log:

  #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 

você pode também comparar obj1 com obj2 e decidir o que você deseja retornar valores possíveis são:

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

Existe uma boa biblioteca popular, que tem esse método como parte, chamada SSToolKit no GitHub . O arquivo NSMutableArray + SSToolkitAdditions.h contém o método shuffle. Você pode usá-lo também. Entre isso, parece haver toneladas de coisas úteis.

A página principal desta biblioteca está aqui .

Se você usar isso, seu código será assim:

 #import  NSMutableArray *tableData = [NSMutableArray arrayWithArray:[temp shuffledArray]]; 

Esta biblioteca também tem um Pod (veja CocoaPods)

No iOS 10, você pode usar o NSArray shuffled() no GameplayKit . Aqui está um ajudante para Array no 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.. 

Se elementos tiverem repetições.

por exemplo, matriz: AAABB ou BBAAA

A única solução é: ABABA

sequenceSelected é um NSMutableArray que armazena elementos da class obj, que são pointers para alguma sequência.

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

A resposta de Kristopher Johnson é bem legal, mas não é totalmente aleatória.

Dada uma matriz de 2 elementos, essa function retorna sempre a matriz inversa, porque você está gerando o intervalo do seu random sobre o resto dos índices. Uma function shuffle() mais precisa seria 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: isso não está correto. Para fins de referência, não excluí esta postagem. Veja os comentários sobre o motivo pelo qual essa abordagem não está correta.

Código simples aqui:

 - (NSArray *)shuffledArray:(NSArray *)array { return [array sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { if (arc4random() % 2) { return NSOrderedAscending; } else { return NSOrderedDescending; } }]; }