NSMutableArrayをシャッフルする最良の方法は何ですか?


187

がある場合NSMutableArray、要素をランダムにシャッフルするにはどうすればよいですか?

(私はこれについて私自身の回答があります。これは以下に掲載されていますが、私はCocoaを初めて使用するので、もっと良い方法があるかどうか知りたいです。)


更新:@Mukeshによって指摘されているように、iOS 10以降およびmacOS 10.12以降では、-[NSMutableArray shuffledArray]シャッフルに使用できる方法があります。詳細については、https://developer.apple.com/documentation/foundation/nsarray/1640855-shuffledarray?language = objcを参照してください。(ただし、これにより、要素を所定の位置に入れ替えるのではなく、新しい配列が作成されることに注意してください。)


この質問を見てください:シャッフルアルゴリズムに関する単純なシャッフルに関する実際の問題
craigb 2008


5
現在のベストはフィッシャーイェイツです。for (NSUInteger i = self.count; i > 1; i--) [self exchangeObjectAtIndex:i - 1 withObjectAtIndex:arc4random_uniform((u_int32_t)i)];
Cœur2015年

2
みんな、iOS 10 ++から、Appleによって与えられたシャッフル配列の新しい概念、この答えを
Mukesh

既存の問題は、メモリ内の新しい場所をアドレス指定する新しいアドレスをAPI返すことArrayです。
TheTiger 2018

回答:



349

NSMutableArrayにカテゴリを追加することでこれを解決しました。

編集:ラッドによる回答のおかげで、不要なメソッドが削除されました。

編集: Gregory Goltsovによる回答とmihoとblahdiblahによるコメント(arc4random() % nElements)へのarc4random_uniform(nElements)感謝に変更

編集:ロンのコメントのおかげでループ改善

編集: 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

10
素晴らしい解決策。そして、そうです、willc2が言及するように、random()をarc4random()で置き換えることは、シードが必要ないため、素晴らしい改善です。
Jason Moore

4
@ジェイソン:時々(たとえば、テスト時)、シードを提供できることは良いことです。クリストファー:素晴らしいアルゴリズム。これは、Fisher-Yatesアルゴリズムの実装です: en.wikipedia.org/wiki/Fisher-Yates_shuffle
JeremyP

4
スーパーマイナーの改善:ループの最後の繰り返しで、私はカウント== - 1.私たちは、それ自体でインデックスiのオブジェクトを交換していることを意味していることはありませんか?常に最後の反復をスキップするようにコードを微調整できますか?
Ron

10
コインが裏返されたのは、元の表とは反対の結果になった場合だけですか。
クリストファージョンソン

4
このシャッフルは微妙にバイアスされています。のarc4random_uniform(nElements)代わりに使用しますarc4random()%nElements。詳細については、arc4randomのマニュアルページこのモジュロバイアスの説明を参照しください。
blahdiblah 2013年

38

まだコメントできないので、全力でお答えしたいと思いました。私はプロジェクトのKristopher Johnsonの実装をいくつかの方法で変更しました(実際にはできるだけ簡潔にするために試みています)。その1つは、モジュロバイアスをarc4random_uniform()回避するためです。

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

2
[self count]ループの各反復で(プロパティゲッター)を2回呼び出していることに注意してください。ループの外に移動することは、簡潔さを失う価値があると思います。
クリストファージョンソン

1
そしてそれが私が今でも[object method]代わりに好む理由ですobject.method:人々は後者が構造体メンバーにアクセスするほど安くはないことを忘れがちです、それはメソッド呼び出しのコストを伴います...ループでは非常に悪いです。
DarkDust 2013年

修正していただきありがとうございます-何らかの理由で、カウントがキャッシュされていると誤って想定しました。回答を更新しました。
gregoltsov 2013年

10

をインポートする場合GameplayKitshuffledAPI があります。

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

let shuffledArray = array.shuffled()

myArrayがあり、その新しいshuffleArrayを作成したいと考えています。Objective-Cを使用するにはどうすればよいですか?
Omkar Jadhav

shuffledArray = [array shuffledArray];
andreacipriani 16

このメソッドはの一部なGameplayKitので、インポートする必要があることに注意してください。
AnthoPak

9

わずかに改善された簡潔なソリューション(上位の回答と比較して)。

アルゴリズムは同じであり、文献では「Fisher-Yates shuffle」として説明されています。

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

Swift 3.2および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))))
        }
    }
}

Swift 3.0および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])
        }
    }
}

注:Swiftのより簡潔なソリューションは、iOS10からを使用して可能GameplayKitです。

注:不安定なシャッフル(count> 1の場合、すべての位置が強制的に変更される)のアルゴリズムも利用可能です


これとクリストファー・ジョンソンのアルゴリズムの違いは何ですか?
Iulian Onofrei

@IulianOnofrei、元々、Kristopher Johnsonのコードは最適ではなかったので、私は彼の答えを改善しました。簡潔な書き方が好きです。アルゴリズムは同じであり、文献では「Fisher-Yates shuffle」として説明されています。
・クール

6

これは、NSArraysまたはNSMutableArraysをシャッフルする最も簡単で最速の方法です(オブジェクトパズルはNSMutableArrayであり、パズルオブジェクトが含まれています。パズルオブジェクトの変数インデックスに追加し、配列の初期位置を示します)

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

ログ出力:

 #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

obj1とobj2を比較して、可能な値を返したいものを決定することもできます。

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

1
また、このソリューションでは、arc4random()またはシードを使用します。
Johan Kool

17
このシャッフルには欠陥があります–マイクロソフトが最近思い出したように:robweir.com/blog/2010/02/microsoft-random-browser-ballot.html
Raphael Schweikert

MSに関するその記事で指摘されているように、「並べ替えには順序の一貫性のある定義が必要である」ため、同意して欠陥があります。エレガントに見えますが、そうではありません。
ジェフ

2

GitHubのSSToolKitと呼ばれる、このメソッドが含まれている人気の高いライブラリがあります。。ファイルNSMutableArray + SSToolkitAdditions.hにはshuffleメソッドが含まれています。あなたもそれを使うことができます。この中には、便利なものがたくさんあるようです。

このライブラリのメインページはこちらです。

これを使用すると、コードは次のようになります。

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

このライブラリにはポッドもあります(CocoaPodsを参照)


2

iOS 10以降では、GameplayKitのNSArray shuffled()使用できます。Swift 3のArrayのヘルパーは次のとおりです。

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

1

要素に繰り返しがある場合。

例:配列:AAABBまたはBBAAA

唯一の解決策は:ABABA

sequenceSelected いくつかのシーケンスへのポインタであるobjクラスの要素を格納するNSMutableArrayです。

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

を使用すると、static複数のインスタンスでの作業が妨げられます。2つのメソッドを使用する方がはるかに安全で読みやすくなります。メインのメソッドは、シャッフルを実行して2次メソッドを呼び出します。また、スペルミスもあります。
・クール

-1
NSUInteger randomIndex = arc4random() % [theArray count];

2
またはarc4random_uniform([theArray count])、サポートしているMac OS XまたはiOSのバージョンで利用できる場合はさらに良いでしょう。
クリストファージョンソン

1
私たちはこのように与えた数は繰り返されます。
Vineesh TP 2012

-1

クリストファー・ジョンソンの答えはかなりいいですが、それは完全にランダムではありません。

2つの要素の配列を指定すると、残りのインデックスに対してランダムな範囲を生成するため、この関数は常に逆の配列を返します。より正確なshuffle()関数は次のようになります

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

あなたが提案したアルゴリズムは「素朴なシャッフル」だと思います。blog.codinghorror.com/the-danger-of-naiveteを参照してください。私の答えは2つしかない場合に要素を交換する50%の確率があると思います。iが0の場合、arc4random_uniform(2)は0または1を返すため、0番目の要素はそれ自体と交換されるか、1番目と交換されます素子。次の反復では、iが1の場合、arc4random(1)は常に0を返し、i番目の要素は常にそれ自体と交換されます。これは非効率的ですが、誤りではありません。(たぶんループ条件はであるべきですi < (count-1)。)
クリストファー・ジョンソン

-2

編集:これは正しくありません。参考のため、この投稿は削除しませんでした。このアプローチが正しくない理由についてのコメントを参照してください。

ここに簡単なコード:

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.