Каков наилучший способ перетащить NSMutableArray?
Если у вас есть NSMutableArray
, как вы произвольно 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.
Редактировать: Удаленный ненужный метод благодаря ответу Лэдда.
Изменить: Изменено (arc4random() % nElements)
до arc4random_uniform(nElements)
благодаря ответу Григория Гольцова и комментариям miho и blahdiblah
Редактировать: улучшение петли, благодаря комментарию Рона
Изменить: добавлена проверка того, что массив не пуст, благодаря комментарию Махеша Агравала
// NSMutableArray_Shuffling.h #if TARGET_OS_IPHONE #import #else #include #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()
поскольку он избегает arc4random_uniform()
смещения .
// NSMutableArray+Shuffling.h #import /** 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()
Немного улучшенное и сжатое решение (по сравнению с верхними ответами).
Алгоритм тот же и описан в литературе как « 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
В 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)
Это самый простой и быстрый способ перетасовать 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 NSMutableArray *tableData = [NSMutableArray arrayWithArray:[temp shuffledArray]];
В этой библиотеке также есть Pod (см. CocoaPods)
Из iOS 10 вы можете использовать NSArray shuffled()
из GameplayKit . Вот помощник для Array в 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..
Если элементы повторяются.
например, массив: AAABB или BBAAA
только решение: ABABA
sequenceSelected
– это NSMutableArray, в котором хранятся элементы classа 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; } } } } }
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; } }]; }