2008-09-11 17 views
181

NSMutableArrayの場合、どのように要素をランダムにシャッフルしますか?NSMutableArrayをシャッフルする最善の方法は何ですか?

(私は以下掲示され、このための私自身の答えを持っているが、私はココアに新たなんだと私はより良い方法があるかどうかを知るために興味があります。)


更新:として、 @Mukeshが指摘しているように、iOS 10+やmacOS 10.12 +のように、シャッフルに使用できる-[NSMutableArray shuffledArray]メソッドがあります。詳細はhttps://developer.apple.com/documentation/foundation/nsarray/1640855-shuffledarray?language=objcを参照してください。 (ただし、これは要素を配置する代わりに、新しい配列を作成することに注意してください。)

+0

ここには、Swiftの実装があります:http://iosdevelopertips.com/swift-code/swift-shuffle-array-typehtml –

+0

あなたのシャッフルアルゴリズムに関してこの問題を見てみましょう:[実際のシャッフルの問題](http://stackoverflow.com/questions/96840/real-world-problems-with-naive-shuffling) – craigb

+4

現在のベストは[Fisher-Yates](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle)です:for(NSUInteger i = self.count; i> 1; i--) [self exchangeObjectAtIndex:i - 1 withObjectAtIndex:arc4random_uniform((u_int32_t)i)]; ' –

答えて

341

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

編集: Laddの回答により、不要なメソッドが削除されました。

編集:変更(arc4random() % nElements)arc4random_uniform(nElements)のおかげ美穂とblahdiblah

編集によってグレゴリーGoltsovとコメントでお答えしますループ改善、感謝ロン

でコメントする

編集:追加チェックその配列は空ではありません、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

良い解決策。そしてyes、willc2の言及のように、random()をarc4random()に置き換えることは、シーディングが必要ないのですばらしい改善です。 –

+4

@ Jason:時には(テストの際に)、種を供給できることは良いことです。 Kristopher:素敵なアルゴリズム。 Fisher-Yatesアルゴリズムの実装です:http://en.wikipedia.org/wiki/Fisher-Yates_shuffle – JeremyP

+0

一般に、 'random()'の代わりに 'arc4random()'を使う方が良いです。数字の質は非常に良く、播種は必要ありません。 – zaph

5

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

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 

あなたにもobj2のでobj1と比較して、あなたが 可能な値を返すようにしたいかを決めることがあります:

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

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

+17

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

+0

MSについての記事で指摘されているように、「並べ替えには順序付けの自己一貫した定義が必要」という理由で合意していません。エレガントに見えますが、そうではありません。 – Jeff

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

または 'arc4random_uniform([theArray count])'は、サポートしているMac OS XまたはiOSのバージョンで利用可能であれば、さらに優れています。 –

+1

私はこのように与えられた数は繰り返されます。 –

35

私はまだ私は完全な応答に貢献しようと思いました、コメントすることはできませんので。私は多くの方法で(実際にはできるだけ簡潔にしようとしている)私のプロジェクトのためにKristopher Johnsonの実装を変更しました。そのうちの1つはarc4random_uniform()です。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 
+2

ループを通して各繰り返しで '[self count]'(プロパティゲッター)を2回呼び出すことに注意してください。私はそれをループの外に動かすことは、簡潔さを失うことに価値があると思います。 –

+1

それでは、私はまだ 'object.method'の代わりに' [object method] 'を好む理由があります:人々は後で構造体メンバにアクセスするのと同じくらい安価ではないことを忘れる傾向があります。ループで非常に悪い。 – DarkDust

+0

訂正していただきありがとうございます - 何らかの理由でカウントがキャッシュされていると誤って推測されました。答えを更新しました。 – gregoltsov

1

SSToolKit in GitHubという名前のこのメソッドを持つ、有名なライブラリがあります。 ファイルNSMutableArray + SSToolkitAdditions.hにはシャッフルメソッドが含まれています。あなたもそれを使用することができます。この中には、役に立つものがたくさんあるようです。

このライブラリのメインページはhereです。あなたがこれを使用する場合

、あなたのコードは次のようになります。

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

このライブラリはまた、要素が繰り返されている場合は

0

(CocoaPodsを参照)ポッドがあります。

アレイ:A A B B B B又はA A A

唯一のソリューションである:B A B A

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

'static'を使用すると、複数のインスタンスでの作業が妨げられます:2つのメソッドを使用するとずっと安全で読みやすくなります。セカンダリメソッドを呼び出すだけで、セカンダリメソッドは自分自身を呼び出し、再シャッフルしません。また、スペルミスがあります。 –

-1

Kristopher Johnson's answerですが、完全にランダムではありません。

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

私があなたが提案したアルゴリズムは「素朴なシャッフル」だと思います。 http://blog.codinghorror.com/the-danger-of-naivete/を参照してください。私は答えが2つだけの場合、要素を交換する50%の可能性があると思います:iが0の場合、arc4random_uniform(2)は0または1のいずれかを返すので、0番目の要素はそれ自身と交換されるか、素子。次の反復で、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; 
     } 
    }]; 
} 
+0

このシャッフルには欠陥があります - http://robweir.com/blog/2010/02/microsoft-random-browser-ballot.html –

4

トップの答えを編集した後、私は少し改善され、簡潔な解決策を共有する考えました。

アルゴリズムは同じであり、文献では「Fisher-Yates shuffle」と記載されています。 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 
スイフト3.2

および4.x:

スイフト3.0および3.1で
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)))) 
     } 
    } 
} 

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

注:A more concise solution in Swift is possible from iOS10 using GameplayKit.

注:新しいshuffled APIを使用することができますiOSの10からは

+0

これとKristopher Johnsonのアルゴリズムとの違いは? –

+0

@IulianOnofrei、もともと、Kristopher Johnsonのコードは最適ではなかったし、私は彼の答えを改善しました、そしてそれはいくつかの役に立たない初期チェックを加えて再び編集しました。私はそれを書くための私の簡潔な方法を好む。アルゴリズムは同じで、文献では「[Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle)」と記載されています。 –

1

、あなたはNSArray shuffled() from GameplayKitを使用することができます。ここでSwiftのArrayのヘルパーがあります。

import GameplayKit 

extension Array { 
    func shuffled() -> [Element] { 
     return (self as NSArray).shuffled() as! [Element] 
    } 
    mutating func shuffle() { 
     replaceSubrange(0..<count, with: shuffled()) 
    } 
} 
関連する問題