NSMutableArray をシャッフルする最良の方法は何ですか?
-
09-06-2019 - |
質問
持っている場合は、 NSMutableArray
, 、要素をランダムにシャッフルするにはどうすればよいですか?
(これについては私自身の答えがあり、以下に掲載していますが、私は Cocoa を初めて使用するため、より良い方法があるかどうか知りたいと思っています。)
アップデート:@Mukesh が指摘したように、iOS 10 以降および macOS 10.12 以降では、 -[NSMutableArray shuffledArray]
シャッフルに使用できるメソッド。見る https://developer.apple.com/documentation/foundation/nsarray/1640855-shuffledarray? language=objc 詳細については。(ただし、これにより、要素を所定の位置でシャッフルするのではなく、新しい配列が作成されることに注意してください。)
解決
swapObjectAtIndex メソッドは必要ありません。 ExchangeObjectAtIndex:withObjectAtIndex: もう存在している。
他のヒント
NSMutableArray にカテゴリを追加することでこれを解決しました。
編集: Ladd の回答のおかげで不要なメソッドを削除しました。
編集: かわった (arc4random() % nElements)
に arc4random_uniform(nElements)
Gregory Goltsov による回答と miho と blahdiblah によるコメントに感謝します
編集: Ron のコメントのおかげでループが改善されました
編集: 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
まだコメントできないので、完全な回答を投稿したいと思いました。私は自分のプロジェクト用に 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
iOS 10からは新しい機能が使えるようになります。 shuffled
API:
https://developer.apple.com/reference/foundation/nsarray/1640855-shuffled
let shuffledArray = array.shuffled()
わずかに改善された簡潔なソリューション (上位の回答と比較して)。
アルゴリズムは同じであり、文献では「フィッシャー・イェーツのシャッフル".
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])
}
}
}
注記: iOS10 からは、Swift でのより簡潔なソリューションが可能です。 GameplayKit
.
注記: 不安定なシャッフル (カウント > 1 の場合にすべての位置が強制的に変更される) のアルゴリズムも利用可能です
これは、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
このメソッドを一部として備えた、と呼ばれる素晴らしい人気のあるライブラリがあります。 GitHub の SSToolKit. 。ファイル NSMutableArray+SSToolkitAdditions.h にはシャッフル メソッドが含まれています。こちらもお使いいただけます。この中には便利なものがたくさんあるようです。
この図書館のメインページは、 ここ.
これを使用すると、コードは次のようになります。
#import <SSCategories.h>
NSMutableArray *tableData = [NSMutableArray arrayWithArray:[temp shuffledArray]];
このライブラリにはポッドもあります (CocoaPods を参照)
iOS10から使えるようになりました 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())
}
}
要素に繰り返しがある場合。
例えば配列:A A A B B または B B A A 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;
}
}
}
}
}
NSUInteger randomIndex = arc4random() % [theArray count];
クリストファー・ジョンソンの答え かなり素晴らしいですが、完全にランダムではありません。
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];
}
}
}
編集: これは正しくありません。 参考までに、この投稿は削除しませんでした。このアプローチが正しくない理由については、コメントを参照してください。
簡単なコードは次のとおりです。
- (NSArray *)shuffledArray:(NSArray *)array
{
return [array sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
if (arc4random() % 2) {
return NSOrderedAscending;
} else {
return NSOrderedDescending;
}
}];
}