Jaki jest najlepszy sposób na przetasowanie NSMutableArray?

Jeśli masz NSMutableArray, jak losowo przetasować elementy?

(mam na to własną odpowiedź, która jest zamieszczona poniżej, ale jestem nowy w Cocoa i jestem ciekaw, czy jest lepszy sposób.)


Aktualizacja: jak zauważył @Mukesh, począwszy od iOS 10+ i macOS 10.12+, istnieje metoda -[NSMutableArray shuffledArray], która może być użyta do przetasowania. Zobacz też https://developer.apple.com/documentation/foundation/nsarray/1640855-shuffledarray?language=objc{[9]po szczegóły. (Ale uwaga to tworzy nową tablicę, zamiast tasować elementy w miejscu.)

Author: Kristopher Johnson, 2008-09-11

12 answers

Nie potrzebujesz metody swapObjectAtIndex. exchangeObjectAtIndex: withObjectAtIndex: już istnieje.

 74
Author: HRM,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-12-27 07:08:06

Rozwiązałem to dodając kategorię do NSMutableArray.

Edit: usunięto zbędną metodę dzięki answer by Ladd.

Edit: zmieniono (arc4random() % nElements) na arc4random_uniform(nElements) dzięki odpowiedzi Gregory Goltsov i komentarzom miho i blahdiblah

Edit: poprawa pętli, dzięki komentarzowi Rona

Edit: Dodano sprawdzenie, czy tablica nie jest pusta, dzięki komentarzowi 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
 351
Author: Kristopher Johnson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-01-23 12:32:08

Ponieważ nie mogę jeszcze skomentować, pomyślałem, że dam pełną odpowiedź. Zmodyfikowałem implementację Kristophera Johnsona do mojego projektu na wiele sposobów (naprawdę starałem się, aby była jak najbardziej zwięzła), jednym z nich jest arc4random_uniform(), ponieważ unika 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
 38
Author: gregoltsov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:18:07

Jeśli zaimportujesz GameplayKit, istnieje API shuffled:

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

let shuffledArray = array.shuffled()
 12
Author: andreacipriani,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2019-12-18 15:17:30

Nieco ulepszone i zwięzłe rozwiązanie(w porównaniu do najlepszych odpowiedzi).

Algorytm jest taki sam i jest opisany w literaturze jako " Fisher-Yates shuffle ".

W 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

W Swift 3.2 i 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))))
        }
    }
}

W Swift 3.0 i 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])
        }
    }
}

Uwaga: bardziej zwięzłe rozwiązanie w języku Swift jest możliwe z iOS10 za pomocą GameplayKit.

Uwaga: algorytm niestabilnego tasowania (ze wszystkimi pozycjami zmuszonymi do zmiany, jeśli liczba > 1) jest również dostępna

 10
Author: Cœur,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-11-12 09:57:49

Jest to najprostszy i najszybszy sposób na przetasowanie NSArrays lub NSMutableArrays (object puzzles jest NSMutableArray, zawiera obiekty puzzle. Dodałem do indeks zmiennej obiektowej wskazujący początkową pozycję w tablicy)

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

Wyjście logu:

 #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

Równie dobrze możesz porównać obj1 z obj2 i zdecydować, co chcesz zwrócić możliwe wartości to:

  • NSOrderedAscending = -1
  • NSOrderedSame = 0
  • NSOrderedDescending = 1
 6
Author: ,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-08-19 10:39:21

W GitHub istnieje fajna, popularna biblioteka, której częścią jest ta metoda, o nazwie SSToolKit. Plik NSMutableArray+SSToolkitAdditions.h zawiera metodę shuffle. Możesz go również użyć. Wśród nich wydaje się być mnóstwo przydatnych rzeczy.

Główną stroną tej biblioteki jest Tutaj .

Jeśli użyjesz tego, Twój kod będzie wyglądał następująco:

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

Ta biblioteka ma również Pod (patrz CocoaPods)

 2
Author: Denis Kutlubaev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-09-13 15:26:48

Z iOS 10 możesz używać NSArray shuffled() z GameplayKit. Oto helper dla tablicy w 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())
    }
}
 2
Author: Cœur,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-22 06:35:48

Jeśli elementy mają powtórzenia.

Np. tablica: A A A B B lub B B A A a

Jedynym rozwiązaniem jest: A B A B a

sequenceSelected jest nsmutablearray, który przechowuje elementy klasy obj, które są wskaźnikami do pewnej sekwencji.

- (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;
                }
            }
        }
    }
}
 1
Author: Gamma-Point,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-06-30 07:04:35
NSUInteger randomIndex = arc4random() % [theArray count];
 -1
Author: kal,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-04-26 14:42:19

Odpowiedź Kristophera Johnsona jest całkiem fajna, ale nie całkiem przypadkowa.

Biorąc pod uwagę tablicę składającą się z 2 elementów, funkcja ta zwraca zawsze tablicę odwrotną, ponieważ generujesz zakres swojej losowej liczby względem reszty indeksów. Dokładniejszą funkcją shuffle() będzie

- (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];
       }
   }
}
 -1
Author: fcortes,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:26:15

Edit: to nie jest poprawne. w celach informacyjnych, nie usunąłem tego posta. Zobacz komentarze na temat powodów, dla których takie podejście nie jest poprawne.

Prosty kod tutaj:

- (NSArray *)shuffledArray:(NSArray *)array
{
    return [array sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
        if (arc4random() % 2) {
            return NSOrderedAscending;
        } else {
            return NSOrderedDescending;
        }
    }];
}
 -2
Author: Ultimate Pea,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-03-21 08:21:28