[arrays] Swift에서 배열을 섞는 방법은 무엇입니까?

Swift에서 배열 내 요소를 무작위 화하거나 섞는 방법은 무엇입니까? 예를 들어, 배열이 52 장의 카드로 구성된 경우 , 갑판을 섞기 위해 배열 을 섞고 싶습니다 .



답변

이 답변에서는 Swift 4.2 이상에서 빠르고 균일 한 알고리즘 (Fisher-Yates)으로 셔플하는 방법과 이전의 다양한 Swift 버전에서 동일한 기능을 추가하는 방법에 대해 자세히 설명합니다. 각 Swift 버전의 이름 및 동작은 해당 버전의 mutating 및 nonmutating 정렬 방법과 일치합니다.

스위프트 4.2 이상

shuffleshuffled스위프트 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]

스위프트 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
    }
}

위의 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
    }
}

위의 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
    }
}

용법:

[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]

스위프트 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]

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]


답변

편집 : 다른 답변에서 언급했듯이 Swift 4.2는 최종적으로 난수 생성을 표준 라이브러리에 추가하고 배열 셔플 링을 완료했습니다.

그러나 GameplayKit 의 GKRandom/ GKRandomDistributionsuite는 여전히 새로운 RandomNumberGenerator프로토콜에 유용 할 수 있습니다. 새로운 표준 라이브러리 프로토콜을 준수하기 위해 GameplayKit RNG에 확장을 추가하면 다음 과 같은 이점을 얻을 수 있습니다.

  • 전송 가능한 RNG (테스트에 필요할 때 “임의”시퀀스를 재현 할 수 있음)
  • 속도를위한 견고성을 희생하는 RNG
  • 균일하지 않은 분포를 생성하는 RNG

… 그리고 여전히 Swift의 새로운 “원시”랜덤 API를 사용합니다.

이 답변의 나머지 부분은 RNG 및 / 또는 구형 Swift 컴파일러에서의 사용과 관련이 있습니다.


여기에 좋은 대답이 있으며 조심하지 않으면 자신의 셔플을 작성하는 것이 오류가 발생하기 쉬운 이유에 대한 좋은 예가 있습니다.

iOS 9, macOS 10.11 및 tvOS 9 이상에서는 직접 작성할 필요가 없습니다. 있다 피셔 – 예이츠의 효율적이고 정확한 구현 (이름에도 불구하고, 단지 게임이다) GameplayKit에가.

당신이 독특한 셔플을 원한다면 :

let shuffled = GKRandomSource.sharedRandom().arrayByShufflingObjects(in: array)

셔플 또는 일련의 셔플을 복제하려면 특정 임의 소스를 선택하고 시드하십시오. 예 :

let lcg = GKLinearCongruentialRandomSource(seed: mySeedValue)
let shuffled = lcg.arrayByShufflingObjects(in: array)

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

그러나 형식 보존 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()


답변

에서 스위프트 2.0 , GameplayKit는 구조에 올 수 있습니다! ( iOS9 이상에서 지원 )

import GameplayKit

func shuffle() {
    array = GKRandomSource.sharedRandom().arrayByShufflingObjectsInArray(array)
}


답변

조금 더 짧은 것이 있습니다.

sorted(a) {_, _ in arc4random() % 2 == 0}


답변

촬영 네이트의 알고리즘을 나는 이것이 스위프트 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
    }
}

이제는 MutableCollectionType다음과 같이 사용하면 이러한 방법을 사용할 수 있습니다 Int.Index


답변

필자의 경우 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
    }

}


답변

이것은 스위프트 4
(Xcode 9)에 대한 네이트의 Fisher-Yates 셔플 구현 버전입니다 .

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

변경 사항은 다음과 같습니다.

  • 제약 Indices.Iterator.Element == Index은 이제 Collection프로토콜의 일부이므로 더 이상 확장에 부과 될 필요가 없습니다.
  • swapAt()컬렉션 을 호출 하여 요소를 교환해야합니다 . SE-0173 추가를MutableCollection.swapAt(_:_:) 비교 하십시오 .
  • Element의 별칭입니다 Iterator.Element.