objective-c 打乱 NSMutableArray 的最佳方法是什么?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/56648/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-03 20:44:19  来源:igfitidea点击:

What's the Best Way to Shuffle an NSMutableArray?

objective-ccocoashuffle

提问by Kristopher Johnson

If you have an NSMutableArray, how do you shuffle the elements randomly?

如果你有一个NSMutableArray,你如何随机打乱元素?

(I have my own answer for this, which is posted below, but I'm new to Cocoa and I'm interested to know if there is a better way.)

(对此我有自己的答案,下面发布,但我是 Cocoa 的新手,我很想知道是否有更好的方法。)



Update: As noted by @Mukesh, as of iOS 10+ and macOS 10.12+, there is an -[NSMutableArray shuffledArray]method that can be used to shuffle. See https://developer.apple.com/documentation/foundation/nsarray/1640855-shuffledarray?language=objcfor details. (But note that this creates a new array, rather than shuffling the elements in place.)

更新:正如@Mukesh 所指出的,从 iOS 10+ 和 macOS 10.12+ 开始,有一种-[NSMutableArray shuffledArray]方法可用于随机播放。有关详细信息,请参阅https://developer.apple.com/documentation/foundation/nsarray/1640855-shuffledarray?language=objc。(但请注意,这会创建一个新数组,而不是原地打乱元素。)

采纳答案by Kristopher Johnson

You don't need the swapObjectAtIndex method. exchangeObjectAtIndex:withObjectAtIndex:already exists.

您不需要 swapObjectAtIndex 方法。exchangeObjectAtIndex:withObjectAtIndex:已经存在。

回答by Kristopher Johnson

I solved this by adding a category to NSMutableArray.

我通过向 NSMutableArray 添加一个类别来解决这个问题。

Edit:Removed unnecessary method thanks to answer by Ladd.

编辑:由于 Ladd 的回答,删除了不必要的方法。

Edit:Changed (arc4random() % nElements)to arc4random_uniform(nElements)thanks to answer by Gregory Goltsov and comments by miho and blahdiblah

编辑:更改(arc4random() % nElements)arc4random_uniform(nElements)由格雷戈里Goltsov和评论感谢答案由美穗和blahdiblah

Edit:Loop improvement, thanks to comment by Ron

编辑:循环改进,感谢 Ron 的评论

Edit:Added check that array is not empty, thanks to comment by Mahesh Agrawal

编辑:添加检查数组不为空,感谢 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

回答by gregoltsov

Since I can't yet comment, I thought I'd contribute a full response. I modified Kristopher Johnson's implementation for my project in a number of ways (really trying to make it as concise as possible), one of them being arc4random_uniform()because it avoids modulo bias.

由于我还不能发表评论,我想我会做出完整的回应。我以多种方式为我的项目修改了 Kristopher Johnson 的实现(真的试图使其尽可能简洁),其中之一是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

回答by andreacipriani

If you import GameplayKit, there is a shuffledAPI:

如果您 import GameplayKit,则有一个shuffledAPI:

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

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

let shuffledArray = array.shuffled()

回答by C?ur

A slightly improved and concise solution (compared to the top answers).

一个稍微改进和简洁的解决方案(与顶级答案相比)。

The algorithm is the same and is described in literature as "Fisher-Yates shuffle".

该算法是相同的,在文献中被描述为“ Fisher-Yates shuffle”。

In Objective-C:

在 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

In Swift 3.2 and 4.x:

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

In Swift 3.0 and 3.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])
        }
    }
}

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

注意:在 Swift 中更简洁的解决方案可以从 iOS10 使用GameplayKit.

Note: An algorithm for unstable shuffling (with all positions forced to change if count > 1) is also available

注意:不稳定洗牌的算法(如果计数> 1,所有位置都强制改变)也可用

回答by C?ur

This is the simplest and fastest way to shuffle NSArrays or NSMutableArrays (object puzzles is a NSMutableArray, it contains puzzle objects. I've added to puzzle object variable index which indicates initial position in array)

这是打乱 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++;
    }
}

log output:

日志输出:

 #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

you may as well compare obj1 with obj2 and decide what you want to return possible values are:

您也可以将 obj1 与 obj2 进行比较,并决定要返回的可能值是:

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

回答by Denis Kutlubaev

There is a nice popular library, that has this method as it's part, called SSToolKit in GitHub. File NSMutableArray+SSToolkitAdditions.h contains shuffle method. You can use it also. Among this, there seem to be tons of useful things.

有一个很好的流行库,它有这个方法作为它的一部分,在 GitHub 中称为SSToolKit。文件 NSMutableArray+SSToolkitAdditions.h 包含 shuffle 方法。你也可以使用它。其中,似乎有很多有用的东西。

The main page of this library is here.

这个图书馆的主页在这里

If you use this, your code will be like this:

如果你使用它,你的代码将是这样的:

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

This library also has a Pod (see CocoaPods)

这个库也有一个 Pod(见 CocoaPods)

回答by C?ur

From iOS 10, you can use NSArray shuffled()from GameplayKit. Here is an helper for Array in Swift 3:

从 iOS 10 开始,您可以使用GameplayKit 中的NSArrayshuffled()。这是 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())
    }
}

回答by Gamma-Point

If elements have repeats.

如果元素有重复。

e.g. array: A A A B B or B B A A A

例如阵列:AAAB 或 BBAAA

only solution is: A B A B A

唯一的解决办法是:ABABA

sequenceSelectedis an NSMutableArray which stores elements of class obj, which are pointers to some sequence.

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

回答by fcortes

Kristopher Johnson's answeris pretty nice, but it's not totally random.

克里斯托弗·约翰逊的回答非常好,但并非完全随机。

Given an array of 2 elements, this function returns always the inversed array, because you are generating the range of your random over the rest of the indexes. A more accurate shuffle()function would be like

给定一个包含 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];
       }
   }
}