如何在Swift中对数组进行洗牌?

mpa*_*zer 297 arrays shuffle swift

如何在Swift中随机化或混洗数组中的元素?例如,如果我的阵列由52张扑克牌,我想洗牌的阵列,以洗牌.

Nat*_*ook 608

这个答案详细介绍了如何在Swift 4.2+中使用快速统一的算法(Fisher-Yates),以及如何在各种先前版本的Swift中添加相同的功能.每个Swift版本的命名和行为都与该版本的变异和非变异排序方法相匹配.

Swift 4.2+

shuffle并且shuffled是原生的Swift 4.2.用法示例:

let x = [1, 2, 3].shuffled()
// x == [2, 3, 1]

let fiveStrings = stride(from: 0, through: 100, by: 5).map(String.init).shuffled()
// fiveStrings == ["20", "45", "70", "30", ...]

var numbers = [1, 2, 3, 4]
numbers.shuffle()
// numbers == [3, 2, 1, 4]
Run Code Online (Sandbox Code Playgroud)

Swift 4.0和4.1

这些扩展shuffle()为任何可变集合(数组和不安全的可变缓冲区)添加方法,并shuffled()为任何序列添加方法:

extension MutableCollection {
    /// Shuffles the contents of this collection.
    mutating func shuffle() {
        let c = count
        guard c > 1 else { return }

        for (firstUnshuffled, unshuffledCount) in zip(indices, stride(from: c, to: 1, by: -1)) {
            // Change `Int` in the next line to `IndexDistance` in < Swift 4.1
            let d: Int = numericCast(arc4random_uniform(numericCast(unshuffledCount)))
            let i = index(firstUnshuffled, offsetBy: d)
            swapAt(firstUnshuffled, i)
        }
    }
}

extension Sequence {
    /// Returns an array with the contents of this sequence, shuffled.
    func shuffled() -> [Element] {
        var result = Array(self)
        result.shuffle()
        return result
    }
}
Run Code Online (Sandbox Code Playgroud)

与上面的Swift 4.2示例中的用法相同.


斯威夫特3

这些扩展shuffle()为任何可变集合添加方法,并shuffled()为任何序列添加方法:

extension MutableCollection where Indices.Iterator.Element == Index {
    /// Shuffles the contents of this collection.
    mutating func shuffle() {
        let c = count
        guard c > 1 else { return }

        for (firstUnshuffled , unshuffledCount) in zip(indices, stride(from: c, to: 1, by: -1)) {
            // Change `Int` in the next line to `IndexDistance` in < Swift 3.2
            let d: Int = numericCast(arc4random_uniform(numericCast(unshuffledCount)))
            guard d != 0 else { continue }
            let i = index(firstUnshuffled, offsetBy: d)
            self.swapAt(firstUnshuffled, i)
        }
    }
}

extension Sequence {
    /// Returns an array with the contents of this sequence, shuffled.
    func shuffled() -> [Iterator.Element] {
        var result = Array(self)
        result.shuffle()
        return result
    }
}
Run Code Online (Sandbox Code Playgroud)

与上面的Swift 4.2示例中的用法相同.


斯威夫特2

(过时的语言:从2018年7月开始,你无法使用Swift 2.x在iTunes Connect上发布)

extension MutableCollectionType where Index == Int {
    /// Shuffle the elements of `self` in-place.
    mutating func shuffleInPlace() {
        // empty and single-element collections don't shuffle
        if count < 2 { return }

        for i in startIndex ..< endIndex - 1 {
            let j = Int(arc4random_uniform(UInt32(count - i))) + i
            guard i != j else { continue }
            swap(&self[i], &self[j])
        }
    }
}

extension CollectionType {
    /// Return a copy of `self` with its elements shuffled.
    func shuffle() -> [Generator.Element] {
        var list = Array(self)
        list.shuffleInPlace()
        return list
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

[1, 2, 3].shuffle()
// [2, 3, 1]

let fiveStrings = 0.stride(through: 100, by: 5).map(String.init).shuffle()
// ["20", "45", "70", "30", ...]

var numbers = [1, 2, 3, 4]
numbers.shuffleInPlace()
// [3, 2, 1, 4]
Run Code Online (Sandbox Code Playgroud)

Swift 1.2

(过时的语言:从2018年7月开始,你不能使用Swift 1.x在iTunes Connect上发布)

shuffle 作为变异数组方法

这个扩展可以让你Array在适当的位置改变一个可变实例:

extension Array {
    mutating func shuffle() {
        if count < 2 { return }
        for i in 0..<(count - 1) {
            let j = Int(arc4random_uniform(UInt32(count - i))) + i
            swap(&self[i], &self[j])
        }
    }
}
var numbers = [1, 2, 3, 4, 5, 6, 7, 8]
numbers.shuffle()                     // e.g., numbers == [6, 1, 8, 3, 2, 4, 7, 5]
Run Code Online (Sandbox Code Playgroud)

shuffled 作为非变异数组方法

此扩展将允许您检索Array实例的随机副本:

extension Array {
    func shuffled() -> [T] {
        if count < 2 { return self }
        var list = self
        for i in 0..<(list.count - 1) {
            let j = Int(arc4random_uniform(UInt32(list.count - i))) + i
            swap(&list[i], &list[j])
        }
        return list
    }
}
let numbers = [1, 2, 3, 4, 5, 6, 7, 8]
let mixedup = numbers.shuffled()     // e.g., mixedup == [6, 1, 8, 3, 2, 4, 7, 5]
Run Code Online (Sandbox Code Playgroud)

  • @Jan:是的,在交换之前添加`guard i!= j else {continue}`.我提出了雷达,但新的行为是故意的. (3认同)
  • 实际上,如果集合索引不是从零开始,那么`shuffleInPlace`会崩溃,例如对于数组切片.`for in in 0 .. <count - 1`应该是`for startIndex .. <endIndex - 1`(然后转换为Swift 3几乎变得微不足道). (3认同)
  • @RobertBrax好问题 - 更新答案! (2认同)
  • 那些是实际输出 - Fisher-Yates应该返回源的无偏随机排列,因此不要求特定元素应该移动.**是*保证没有元素移动多次,但有时"移动"是相同的索引.最简单的情况是考虑`[1,2] .shuffled()` - 每次都返回`[2,1]`? (2认同)

ric*_*ter 129

编辑:正如其他答案所述,Swift 4.2 最终将随机数生成添加到标准库中,完成数组重排.

但是,GameplayKit中的GKRandom/ GKRandomDistributionsuite仍然可以用于新RandomNumberGenerator协议 - 如果您向GameplayKit RNG添加扩展以符合新的标准库协议,您可以轻松获得:

  • 可发送的RNG(可在需要进行测试时重现"随机"序列)
  • RNG牺牲了速度的稳健性
  • 产生非均匀分布的RNG

...并且仍然使用Swift中漂亮的新"本机"随机API.

本答案的其余部分涉及此类RNG和/或它们在较旧的Swift编译器中的使用.


这里有一些很好的答案,以及一些很好的例子,说明如果你不小心,写你自己的shuffle可能容易出错.

在iOS 9,macOS 10.11和tvOS 9(或更高版本)中,您不必编写自己的.在GameplayKit中有一个高效,正确的Fisher-Yates实现(尽管名称不仅仅适用于游戏).

如果你只想要一个独特的shuffle:

let shuffled = GKRandomSource.sharedRandom().arrayByShufflingObjects(in: array)
Run Code Online (Sandbox Code Playgroud)

如果您希望能够复制shuffle或一系列shuffle,请选择并播种特定的随机源; 例如

let lcg = GKLinearCongruentialRandomSource(seed: mySeedValue)
let shuffled = lcg.arrayByShufflingObjects(in: array)
Run Code Online (Sandbox Code Playgroud)

在iOS 10/macOS 10.12/tvOS 10中,还有一个方便的语法,可以通过扩展来进行混洗NSArray.当然,当你使用Swift时Array它会有点麻烦(它在返回Swift时会失去它的元素类型):

let shuffled1 = (array as NSArray).shuffled(using: random) // -> [Any]
let shuffled2 = (array as NSArray).shuffled() // use default random source
Run Code Online (Sandbox Code Playgroud)

但是为它创建一个类型保留的Swift包装器非常容易:

extension Array {
    func shuffled(using source: GKRandomSource) -> [Element] {
        return (self as NSArray).shuffled(using: source) as! [Element]
    }
    func shuffled() -> [Element] {
        return (self as NSArray).shuffled() as! [Element]
    }
}
let shuffled3 = array.shuffled(using: random)
let shuffled4 = array.shuffled()
Run Code Online (Sandbox Code Playgroud)

  • 让我想知道在GameplayKit中可以找到我从未探索过的其他有用的实用程序! (6认同)
  • 图搜索,树搜索,规则系统... [很多东西](https://developer.apple.com/library/prerelease/ios/documentation/GameplayKit/Reference/GameplayKit_Framework/index.html#//apple_ref/doc/uid/TP40015199)这对游戏设计和其他方面都很有帮助. (6认同)
  • 在Swift 3/iOS 10中,这已经改为:`let shuffled = lcg.arrayByShufflingObjects(in:array)` (5认同)

blu*_*ere 30

Swift 2.0中,GameplayKit可能会来救援!(iOS9或更高版本支持)

import GameplayKit

func shuffle() {
    array = GKRandomSource.sharedRandom().arrayByShufflingObjectsInArray(array)
}
Run Code Online (Sandbox Code Playgroud)

  • 导入GameplayKit只是为了得到洗牌阵列听起来不是一个好主意 (5认同)
  • 您还可以将导入范围简单地"导入GameplayKit.GKRandomSource" (3认同)
  • 为什么?它是系统的一部分,不会添加到二进制文件中. (2认同)

Jea*_*nan 26

这可能有点短:

sorted(a) {_, _ in arc4random() % 2 == 0}
Run Code Online (Sandbox Code Playgroud)

  • 正如pjs在回答另一个非常相似的答案时指出的那样,这将不会产生均匀的结果分布.使用[Fisher-Yates Shuffle](http://en.wikipedia.org/wiki/Fisher-Yates_shuffle),如Nate Cook的回答所示. (9认同)
  • 这里的任何数学家都要证实或反驳? (2认同)

swi*_*ynx 10

从 swift 4.2 开始,有两个方便的函数:

// shuffles the array in place
myArray.shuffle()
Run Code Online (Sandbox Code Playgroud)

// generates a new array with shuffled elements of the old array
let newArray = myArray.shuffled()
Run Code Online (Sandbox Code Playgroud)


Chr*_*ner 7

采用Nate的算法,我想看看Swift 2和协议扩展的外观.

这就是我提出的.

extension MutableCollectionType where Self.Index == Int {
    mutating func shuffleInPlace() {
        let c = self.count
        for i in 0..<(c - 1) {
            let j = Int(arc4random_uniform(UInt32(c - i))) + i
            swap(&self[i], &self[j])
        }
    }
}

extension MutableCollectionType where Self.Index == Int {
    func shuffle() -> Self {
        var r = self
        let c = self.count
        for i in 0..<(c - 1) {
            let j = Int(arc4random_uniform(UInt32(c - i))) + i
            swap(&r[i], &r[j])
        }
        return r
    }
}
Run Code Online (Sandbox Code Playgroud)

现在,任何人MutableCollectionType都可以使用这些方法,因为它Int用作Index


Kaz*_*awa 6

在我的例子中,我遇到了在Array中交换对象的一些问题.然后我挠挠头去重新发明轮子.

// swift 3.0 ready
extension Array {

    func shuffled() -> [Element] {
        var results = [Element]()
        var indexes = (0 ..< count).map { $0 }
        while indexes.count > 0 {
            let indexOfIndexes = Int(arc4random_uniform(UInt32(indexes.count)))
            let index = indexes[indexOfIndexes]
            results.append(self[index])
            indexes.remove(at: indexOfIndexes)
        }
        return results
    }

}
Run Code Online (Sandbox Code Playgroud)


Mar*_*n R 5

这是NateSwift 4 (Xcode 9)实现Fisher-Yates shuffle的一个版本.

extension MutableCollection {
    /// Shuffle the elements of `self` in-place.
    mutating func shuffle() {
        for i in indices.dropLast() {
            let diff = distance(from: i, to: endIndex)
            let j = index(i, offsetBy: numericCast(arc4random_uniform(numericCast(diff))))
            swapAt(i, j)
        }
    }
}

extension Collection {
    /// Return a copy of `self` with its elements shuffled
    func shuffled() -> [Element] {
        var list = Array(self)
        list.shuffle()
        return list
    }
}
Run Code Online (Sandbox Code Playgroud)

变化是:

  • 约束Indices.Iterator.Element == Index现在是Collection协议的一部分,不再需要强加于扩展.
  • 交换元素必须通过调用swapAt()集合来完成,比较SE-0173 AddMutableCollection.swapAt(_:_:).
  • Element是别名Iterator.Element.