web-dev-qa-db-ja.com

Swiftの配列からランダムな要素を取得します

私は次のような配列を持っています:

var names: String = [ "Peter", "Steve", "Max", "Sandra", "Roman", "Julia" ]

その配列から3つのランダムな要素を取得したいと思います。私はC#から来ていますが、Swiftどこから始めればよいかわかりません。たとえば、最初に配列をシャッフルしてから、最初の3つの項目を選択する必要があると思いますか?

次の拡張子でシャッフルしようとしました:

extension Array
{
    mutating func shuffle()
    {
        for _ in 0..<10
        {
            sort { (_,_) in arc4random() < arc4random() }
        }
    }
}

ただし、「shuffle()」の場所で「 '()'は '[Int]'に変換できません」と表示されます。

私が使用する要素の数を選ぶために:

var randomPicks = names[0..<4];

これまでのところ良さそうです。

シャッフルする方法は?または、誰かがこれのためのより良い/よりエレガントな解決策を持っていますか?

14
Patric

Xcode11•Swift 5.1

extension Collection {
    func choose(_ n: Int) -> ArraySlice<Element> { shuffled().prefix(n) }
}

遊び場のテスト

var alphabet = ["A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"]
let shuffledAlphabet = alphabet.shuffled()  // "O", "X", "L", "D", "N", "K", "R", "E", "S", "Z", "I", "T", "H", "C", "U", "B", "W", "M", "Q", "Y", "V", "A", "G", "P", "F", "J"]
let letter = alphabet.randomElement()  // "D"
var numbers = Array(0...9)
let shuffledNumbers = numbers.shuffled()
shuffledNumbers                              // [8, 9, 3, 6, 0, 1, 4, 2, 5, 7]
numbers            // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
numbers.shuffle() // mutate it  [6, 0, 2, 3, 9, 1, 5, 7, 4, 8]
numbers            // [6, 0, 2, 3, 9, 1, 5, 7, 4, 8]
let pick3numbers = numbers.choose(3)  // [8, 9, 2]

extension RangeReplaceableCollection {
    /// Returns a new Collection shuffled
    var shuffled: Self {
        var elements = self
        return elements.shuffledInPlace()
    }
    /// Shuffles this Collection in place
    @discardableResult
    mutating func shuffledInPlace() -> Self  {
        indices.dropLast().forEach {
            let subSequence = self[$0...$0]
            let distance = self.distance(from: startIndex, to: $0)
            let index = self.index(indices[..<self.index(endIndex, offsetBy: -distance)].randomElement()!, offsetBy: distance)
            replaceSubrange($0...$0, with: self[index...index])
            replaceSubrange(index...index, with: subSequence)
        }
        return self
    }
    func choose(_ n: Int) -> SubSequence { shuffled.prefix(n) }
}

var alphabetString = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
let shuffledAlphabetString = alphabetString.shuffled  // "DRGXNSJLFQHPUZTBKVMYAWEICO"
let character = alphabetString.randomElement()  // "K"
alphabetString.shuffledInPlace() // mutate it  "WYQVBLGZKPFUJTHOXERADMCINS"
alphabetString            // "WYQVBLGZKPFUJTHOXERADMCINS"
let pick3Characters = alphabetString.choose(3)  // "VYA"
32
Leo Dabus

または、誰かがこれのためのより良い/よりエレガントな解決策を持っていますか?

私がやります。完全なシャッフルに対してカウント1arc4random_uniform操作を行う受け入れられた回答よりもアルゴリズム的に優れているため、narc4random_uniform操作でn値を選択するだけです。

そして実際、私はtwo受け入れられた答えよりもうまくやる方法を得ました:

より良い解決策

extension Array {
    /// Picks `n` random elements (straightforward approach)
    subscript (randomPick n: Int) -> [Element] {
        var indices = [Int](0..<count)
        var randoms = [Int]()
        for _ in 0..<n {
            randoms.append(indices.remove(at: Int(arc4random_uniform(UInt32(indices.count)))))
        }
        return randoms.map { self[$0] }
    }
}

最良の解決策

次のソリューションは、前のソリューションの2倍高速です。

Swift 3.0および3.1の場合

extension Array {
    /// Picks `n` random elements (partial Fisher-Yates shuffle approach)
    subscript (randomPick n: Int) -> [Element] {
        var copy = self
        for i in stride(from: count - 1, to: count - n - 1, by: -1) {
            let j = Int(arc4random_uniform(UInt32(i + 1)))
            if j != i {
                swap(&copy[i], &copy[j])
            }
        }
        return Array(copy.suffix(n))
    }
}

Swift 3.2および4.xの場合

extension Array {
    /// Picks `n` random elements (partial Fisher-Yates shuffle approach)
    subscript (randomPick n: Int) -> [Element] {
        var copy = self
        for i in stride(from: count - 1, to: count - n - 1, by: -1) {
            copy.swapAt(i, Int(arc4random_uniform(UInt32(i + 1))))
        }
        return Array(copy.suffix(n))
    }
}

使用法:

let digits = Array(0...9)  // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
let pick3digits = digits[randomPick: 3]  // [8, 9, 0]
14
Cœur

Swift 4.1以下

let playlist = ["Nothing Else Matters", "Stairway to Heaven", "I Want to Break Free", "Yesterday"]
let index = Int(arc4random_uniform(UInt32(playlist.count)))
let song = playlist[index]

Swift 4.2以降

if let song = playlist.randomElement() {
  print(song)
} else {
  print("Empty playlist.")
}
1

Arc4random()を使用して、配列から3つの要素を選択することもできます。このようなもの:

extension Array {
    func getRandomElements() -> (T, T, T) {
        return (self[Int(arc4random()) % Int(count)],
                self[Int(arc4random()) % Int(count)],
                self[Int(arc4random()) % Int(count)])
    }
}

let names = ["Peter", "Steve", "Max", "Sandra", "Roman", "Julia"]
names.getRandomElements()

これは単なる例です。関数にロジックを含めて、それぞれに異なる名前を付けることもできます。

1
akshay

配列に拡張機能を定義できます。

extension Array {
    func pick(_ n: Int) -> [Element] {
        guard count >= n else {
            fatalError("The count has to be at least \(n)")
        }
        guard n >= 0 else {
            fatalError("The number of elements to be picked must be positive")
        }

        let shuffledIndices = indices.shuffled().prefix(upTo: n)
        return shuffledIndices.map {self[$0]}
    }
}

[ "Peter", "Steve", "Max", "Sandra", "Roman", "Julia" ].pick(3)

初期配列に重複がある可能性があり、値に一意性が必要な場合:

extension Array where Element: Hashable {
    func pickUniqueInValue(_ n: Int) -> [Element] {
        let set: Set<Element> = Set(self)
        guard set.count >= n else {
            fatalError("The array has to have at least \(n) unique values")
        }
        guard n >= 0 else {
            fatalError("The number of elements to be picked must be positive")
        }

        return Array(set.prefix(upTo: set.index(set.startIndex, offsetBy: n)))
    }
}

[ "Peter", "Steve", "Max", "Sandra", "Roman", "Julia" ].pickUniqueInValue(3)
0
ielyamani