Какой лучший способ, чтобы перетасовать в NSMutableArray?
Если у вас есть NSMutableArray
, Как вы перемешать элементы случайным образом?
(у меня есть свой собственный ответ на это, который размещен ниже, но я новичок в Cocoa, и мне интересно узнать, есть ли лучший способ.)
обновление: как отметил @Mukesh, начиная с iOS 10+ и macOS 10.12+, есть -[NSMutableArray shuffledArray]
метод, который может использоваться для перемешивания. См https://developer.apple.com/documentation/foundation/nsarray/1640855-shuffledarray?language=objc для подробности. (Но обратите внимание, что это создает новый массив, а не перетасовывает элементы на месте.)
12 ответов:
вам не нужен метод swapObjectAtIndex. exchangeObjectAtIndex:withObjectAtIndex: уже существует.
я решил эту проблему, добавив категорию В NSMutableArray.
Edit: удален ненужный метод благодаря ответу Ladd.
Edit: изменить
(arc4random() % nElements)
toarc4random_uniform(nElements)
спасибо за ответ Григория Гольцова и комментарии Михо и blahdiblahEdit: улучшение цикла, благодаря комментарию Рона
Edit: добавлена проверка, что массив не пуст, благодаря комментарию Махеша Агравал
// 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
Так как я еще не могу комментировать, я думал, что внесу полный ответ. Я изменил реализацию Кристофера Джонсона для моего проекта несколькими способами (действительно пытаясь сделать его как можно более кратким), одним из которых является
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()
немного улучшенное и краткое решение (по сравнению с верхними ответами).
алгоритм тот же и описывается в литературе как"Фишер-Йейтс перемешать".
In 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
в 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
.
Это самый простой и быстрый способ перетасовать 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
есть хорошая популярная библиотека,, что этот метод, как его часть, называемая SSToolKit в GitHub. Файл NSMutableArray+SSToolkitAdditions.h содержит метод перемешивания. Вы можете использовать его также. Среди этого, кажется, есть тонны полезных вещей.
главная страница этой библиотеки -здесь.
Если вы используете это, ваш код будет выглядеть так:
#import <SSCategories.h> NSMutableArray *tableData = [NSMutableArray arrayWithArray:[temp shuffledArray]];
эта библиотека также имеет стручок (см. CocoaPods)
из iOS 10, вы можете использовать NSArray
shuffled()
от GameplayKit. Вот помощник для массива в 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()) } }
Если элементы повторяются.
например, такие: А А А Б Б Б Б А А а
единственное решение: A B A B A
sequenceSelected
- Это NSMutableArray, который хранит элементы класса obj, которые являются указателями на некоторую последовательность.- (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; } } } } }
ответ Кристофера Джонсона довольно приятно, но это не совсем случайно.
учитывая массив из 2 элементов, эта функция всегда возвращает инвертированный массив, потому что вы генерируете диапазон вашего случайного по остальным индексам. А точнее
Edit:это не правильно. Для справки, я не удалял этот пост. См. комментарии о причине, по которой этот подход не является правильным.
простой код:
- (NSArray *)shuffledArray:(NSArray *)array { return [array sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) { if (arc4random() % 2) { return NSOrderedAscending; } else { return NSOrderedDescending; } }]; }