Pilih elemen acak dari array

189

Misalkan saya memiliki array dan saya ingin memilih satu elemen secara acak.

Apa cara paling sederhana untuk melakukan ini?

Cara yang jelas akan terjadi array[random index]. Tapi mungkin ada sesuatu seperti ruby array.sample? Atau jika tidak dapatkah metode seperti itu dibuat dengan menggunakan ekstensi?

Fela Winkelmolen
sumber
1
Sudahkah Anda mencoba metode yang berbeda?
mengarungi prefek
Saya akan mencoba array[random number from 0 to length-1], tetapi saya tidak dapat menemukan cara menghasilkan int acak dalam cepat, saya akan menanyakannya pada stack overflow jika saya tidak diblokir :) Saya tidak ingin mencemari pertanyaan dengan setengah solusi ketika mungkin ada sesuatu seperti rubyarray.sample
Fela Winkelmolen
1
Anda menggunakan arc4random () seperti pada Obj-C
Arbitur
Tidak ada penjelasan mengapa pertanyaan Anda tidak menerima umpan balik yang sama dengan mitra JQuery. Namun secara umum, Anda harus mengikuti pedoman ini saat memposting pertanyaan. Bagaimana cara mengajukan pertanyaan yang bagus? . Buat itu tampak seperti Anda berusaha sedikit untuk mencari solusi sebelum meminta bantuan orang lain. Ketika saya google "memilih swift nomor acak", halaman pertama diisi dengan jawaban yang menyarankan arc4random_uniform. Juga, RTFD ... "baca dokumentasi f'ing". Sungguh mengejutkan berapa banyak pertanyaan yang bisa dijawab dengan cara ini.
Austin A
Terima kasih atas tanggapan Anda. Yap, saya kira saya harus menjawab sendiri pertanyaan itu, tetapi tampaknya cukup mudah bahwa menyenangkan untuk memberi orang lain poin reputasi yang hampir bebas. Dan saya menulisnya ketika bahkan dokumen resmi Apple tidak dipublikasikan, tidak ada hasil Google pada waktu itu. Tapi pertanyaannya adalah pada -12, jadi saya cukup yakin akhirnya akan menjadi positif :)
Fela Winkelmolen

Jawaban:

321

Cepat 4.2 dan lebih tinggi

Pendekatan baru yang disarankan adalah built-in metode pada protokol Koleksi: randomElement(). Ini mengembalikan opsional untuk menghindari kasus kosong yang saya duga sebelumnya.

let array = ["Frodo", "Sam", "Wise", "Gamgee"]
print(array.randomElement()!) // Using ! knowing I have array.count > 0

Jika Anda tidak membuat array dan tidak dijamin jumlah> 0, Anda harus melakukan sesuatu seperti:

if let randomElement = array.randomElement() { 
    print(randomElement)
}

Cepat 4.1 dan di bawah

Hanya untuk menjawab pertanyaan Anda, Anda dapat melakukan ini untuk mencapai pemilihan array acak:

let array = ["Frodo", "sam", "wise", "gamgee"]
let randomIndex = Int(arc4random_uniform(UInt32(array.count)))
print(array[randomIndex])

Pengecorannya jelek, tapi saya yakin itu diperlukan kecuali ada orang lain yang punya cara lain.

Lucas Derraugh
sumber
4
Mengapa Swift tidak menawarkan generator nomor acak yang mengembalikan Int? Baris ke-2 ini tampaknya sangat verbose hanya untuk mengembalikan Int yang dipilih secara acak. Apakah ada beberapa keuntungan komputasi / sintaksis untuk mengembalikan UInt32 sebagai lawan dari Int? Juga, mengapa Swift tidak menawarkan alternatif Int untuk fungsi ini atau mengizinkan pengguna untuk menentukan tipe integer apa yang ingin mereka kembalikan?
Austin A
Untuk menambahkan catatan, metode pembangkit angka acak ini dapat mencegah "modulo bias". Referensikan man arc4randomdan stackoverflow.com/questions/10984974/...
Kent Liau
1
@AustinA, Swift 4.2 TIDAK memiliki fungsi penghasil angka acak asli yang diimplementasikan pada semua tipe data skalar yang mungkin Anda harapkan: Int, Double, Float, UInt32, dll. Dan itu memungkinkan Anda memberikan rentang target untuk nilai-nilai tersebut. Sangat berguna. Anda dapat menggunakan array [Int.random (0 .. <array.count)] `di Swift 4.2
Duncan C
Saya berharap Swift 4.2 mengimplementasikan removeRandomElement()fungsi selain randomElement(). Itu akan dimodelkan removeFirst(), tetapi menghapus objek pada indeks acak.
Duncan C
@DuncanC Anda harus menghindari 0..<array.count(karena berbagai alasan, yang utama adalah itu tidak bekerja untuk irisan, dan rentan kesalahannya). Anda bisa melakukannya let randomIndex = array.indices.randomElement(), diikuti oleh let randomElement = array.remove(at: randomIndex). Anda bahkan dapat mengikutinya let randomElement = array.remove(at: array.indices.randomElement()).
Alexander - Pasang kembali Monica
137

Mengetahui apa yang dikatakan Lucas, Anda bisa membuat ekstensi ke kelas Array seperti ini:

extension Array {
    func randomItem() -> Element? {
        if isEmpty { return nil }
        let index = Int(arc4random_uniform(UInt32(self.count)))
        return self[index]
    }
}

Sebagai contoh:

let myArray = [1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16]
let myItem = myArray.randomItem() // Note: myItem is an Optional<Int>
Phae Deepsky
sumber
2
Dalam cepat 2 Ttelah diubah namanya menjadi Element.
GDanger
25
Perhatikan bahwa array kosong akan menyebabkan crash di sini
Berik
1
@Berik Nah Anda bisa mengembalikan Elemen opsional dan kemudian selalu melakukan guardpemeriksaan untuk melihat apakah array kosong lalu kembali nil.
Harish
1
Sepakat. Array crash pada out-of-bound sehingga mereka dapat menjadi performant. Memanggil ke dalam arc4randommembuat setiap keuntungan kinerja sama sekali tidak signifikan. Saya sudah memperbarui jawabannya.
Berik
45

Versi Swift 4 :

extension Collection where Index == Int {

    /**
     Picks a random element of the collection.

     - returns: A random element of the collection.
     */
    func randomElement() -> Iterator.Element? {
        return isEmpty ? nil : self[Int(arc4random_uniform(UInt32(endIndex)))]
    }

}
Andrey Gordeev
sumber
Ini dapat macet dengan indeks di luar batas pada koleksi di manastartIndex != 0
dan
21

Di Swift 2.2 ini dapat digeneralisasi sehingga kita memiliki:

UInt.random
UInt8.random
UInt16.random
UInt32.random
UInt64.random
UIntMax.random

// closed intervals:

(-3...3).random
(Int.min...Int.max).random

// and collections, which return optionals since they can be empty:

(1..<4).sample
[1,2,3].sample
"abc".characters.sample
["a": 1, "b": 2, "c": 3].sample

Pertama, menerapkan randomproperti statis untuk UnsignedIntegerTypes:

import Darwin

func sizeof <T> (_: () -> T) -> Int { // sizeof return type without calling
    return sizeof(T.self)
}

let ARC4Foot: Int = sizeof(arc4random)

extension UnsignedIntegerType {
    static var max: Self { // sadly `max` is not required by the protocol
        return ~0
    }
    static var random: Self {
        let foot = sizeof(Self)
        guard foot > ARC4Foot else {
            return numericCast(arc4random() & numericCast(max))
        }
        var r = UIntMax(arc4random())
        for i in 1..<(foot / ARC4Foot) {
            r |= UIntMax(arc4random()) << UIntMax(8 * ARC4Foot * i)
        }
        return numericCast(r)
    }
}

Lalu, untuk ClosedIntervals dengan UnsignedIntegerTypebatasan:

extension ClosedInterval where Bound : UnsignedIntegerType {
    var random: Bound {
        guard start > 0 || end < Bound.max else { return Bound.random }
        return start + (Bound.random % (end - start + 1))
    }
}

Kemudian (sedikit lebih terlibat), untuk ClosedIntervals dengan SignedIntegerTypebatasan (menggunakan metode pembantu dijelaskan lebih lanjut di bawah):

extension ClosedInterval where Bound : SignedIntegerType {
    var random: Bound {
        let foot = sizeof(Bound)
        let distance = start.unsignedDistanceTo(end)
        guard foot > 4 else { // optimisation: use UInt32.random if sufficient
            let off: UInt32
            if distance < numericCast(UInt32.max) {
                off = UInt32.random % numericCast(distance + 1)
            } else {
                off = UInt32.random
            }
            return numericCast(start.toIntMax() + numericCast(off))
        }
        guard distance < UIntMax.max else {
            return numericCast(IntMax(bitPattern: UIntMax.random))
        }
        let off = UIntMax.random % (distance + 1)
        let x = (off + start.unsignedDistanceFromMin).plusMinIntMax
        return numericCast(x)
    }
}

... di mana unsignedDistanceTo, unsignedDistanceFromMindan plusMinIntMaxmetode pembantu dapat diimplementasikan sebagai berikut:

extension SignedIntegerType {
    func unsignedDistanceTo(other: Self) -> UIntMax {
        let _self = self.toIntMax()
        let other = other.toIntMax()
        let (start, end) = _self < other ? (_self, other) : (other, _self)
        if start == IntMax.min && end == IntMax.max {
            return UIntMax.max
        }
        if start < 0 && end >= 0 {
            let s = start == IntMax.min ? UIntMax(Int.max) + 1 : UIntMax(-start)
            return s + UIntMax(end)
        }
        return UIntMax(end - start)
    }
    var unsignedDistanceFromMin: UIntMax {
        return IntMax.min.unsignedDistanceTo(self.toIntMax())
    }
}

extension UIntMax {
    var plusMinIntMax: IntMax {
        if self > UIntMax(IntMax.max) { return IntMax(self - UIntMax(IntMax.max) - 1) }
        else { return IntMax.min + IntMax(self) }
    }
}

Akhirnya, untuk semua koleksi di mana Index.Distance == Int:

extension CollectionType where Index.Distance == Int {
    var sample: Generator.Element? {
        if isEmpty { return nil }
        let end = UInt(count) - 1
        let add = (0...end).random
        let idx = startIndex.advancedBy(Int(add))
        return self[idx]
    }
}

... yang dapat dioptimalkan sedikit untuk integer Ranges:

extension Range where Element : SignedIntegerType {
    var sample: Element? {
        guard startIndex < endIndex else { return nil }
        let i: ClosedInterval = startIndex...endIndex.predecessor()
        return i.random
    }
}

extension Range where Element : UnsignedIntegerType {
    var sample: Element? {
        guard startIndex < endIndex else { return nil }
        let i: ClosedInterval = startIndex...endIndex.predecessor()
        return i.random
    }
}
milos
sumber
18

Anda dapat menggunakan fungsi acak () bawaan Swift juga untuk ekstensi:

extension Array {
    func sample() -> Element {
        let randomIndex = Int(rand()) % count
        return self[randomIndex]
    }
}

let array = [1, 2, 3, 4]

array.sample() // 2
array.sample() // 2
array.sample() // 3
array.sample() // 3

array.sample() // 1
array.sample() // 1
array.sample() // 3
array.sample() // 1
NatashaTheRobot
sumber
Sebenarnya random () berasal dari bridging library Standard C, Anda dapat melihatnya dan teman-teman di Terminal, "man random". Tapi senang Anda menunjukkan ketersediaan!
David H
1
ini menghasilkan urutan acak yang sama setiap kali dijalankan
iTSangar
1
@ iTSangar kamu benar! rand () adalah yang benar untuk digunakan. Memperbarui jawaban saya.
NatashaTheRobot
6
Ini juga rentan terhadap bias modulo.
Aidan Gomez
@mattt menulis artikel yang bagus tentang menghasilkan angka acak . TL; DR salah satu dari keluarga arc4random adalah pilihan yang lebih baik.
Elitalon
9

Saran Swift 3 lainnya

private extension Array {
    var randomElement: Element {
        let index = Int(arc4random_uniform(UInt32(count)))
        return self[index]
    }
}
penjara
sumber
4

Mengikuti orang lain menjawab tetapi dengan dukungan Swift 2.

Swift 1.x

extension Array {
    func sample() -> T {
        let index = Int(arc4random_uniform(UInt32(self.count)))
        return self[index]
    }
}

Swift 2.x

extension Array {
    func sample() -> Element {
        let index = Int(arc4random_uniform(UInt32(self.count)))
        return self[index]
    }
}

Misalnya:

let arr = [2, 3, 5, 7, 9, 11, 13, 17, 19, 23, 29, 31]
let randomSample = arr.sample()
Aidan Gomez
sumber
2

Implementasi fungsional alternatif dengan memeriksa array kosong.

func randomArrayItem<T>(array: [T]) -> T? {
  if array.isEmpty { return nil }
  let randomIndex = Int(arc4random_uniform(UInt32(array.count)))
  return array[randomIndex]
}

randomArrayItem([1,2,3])
Evgenii
sumber
2

Berikut ini ekstensi pada Array dengan pemeriksaan array kosong untuk keamanan lebih lanjut:

extension Array {
    func sample() -> Element? {
        if self.isEmpty { return nil }
        let randomInt = Int(arc4random_uniform(UInt32(self.count)))
        return self[randomInt]
    }
}

Anda dapat menggunakannya sesederhana ini :

let digits = Array(0...9)
digits.sample() // => 6

Jika Anda lebih suka Kerangka yang juga memiliki beberapa fitur yang lebih berguna maka checkout HandySwift . Anda dapat menambahkannya ke proyek Anda melalui Kartago kemudian menggunakannya persis seperti pada contoh di atas:

import HandySwift    

let digits = Array(0...9)
digits.sample() // => 8

Selain itu juga termasuk opsi untuk mendapatkan beberapa elemen acak sekaligus :

digits.sample(size: 3) // => [8, 0, 7]
Astaga
sumber
2

Cepat 3

impor GameKit

func getRandomMessage() -> String {

    let messages = ["one", "two", "three"]

    let randomNumber = GKRandomSource.sharedRandom().nextInt(upperBound: messages.count)

    return messages[randomNumber].description

}
Mempesona
sumber
2

Swift 3 - sederhana mudah digunakan.

  1. Buat Array

    var arrayOfColors = [UIColor.red, UIColor.yellow, UIColor.orange, UIColor.green]
  2. Buat Warna Acak

    let randomColor = arc4random() % UInt32(arrayOfColors.count)
  3. Atur warna itu ke objek Anda

    your item = arrayOfColors[Int(randomColor)]

Berikut adalah contoh dari SpriteKitproyek yang memperbarui SKLabelNodedengan acak String:

    let array = ["one","two","three","four","five"]

    let randomNumber = arc4random() % UInt32(array.count)

    let labelNode = SKLabelNode(text: array[Int(randomNumber)])
Timmy Sorensen
sumber
2

Jika Anda ingin bisa mendapatkan lebih dari satu elemen acak dari array Anda tanpa duplikat , GameplayKit telah Anda liput:

import GameplayKit
let array = ["one", "two", "three", "four"]

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

let firstRandom = shuffled[0]
let secondRandom = shuffled[1]

Anda memiliki beberapa pilihan untuk keacakan, lihat GKRandomSource :

The GKARC4RandomSourcekelas menggunakan algoritma yang sama dengan yang digunakan dalam keluarga arc4random fungsi C. (Namun, instance dari kelas ini independen dari panggilan ke fungsi arc4random.)

Itu GKLinearCongruentialRandomSource kelas menggunakan algoritma yang lebih cepat, tetapi kurang acak, dari kelas GKARC4RandomSource. (Secara khusus, bit rendah dari angka yang dihasilkan berulang lebih sering daripada bit tinggi.) Gunakan sumber ini ketika kinerja lebih penting daripada ketidakpastian yang kuat.

The GKMersenneTwisterRandomSourcekelas menggunakan algoritma yang lebih lambat, tetapi lebih acak, dari kelas GKARC4RandomSource. Gunakan sumber ini ketika penting bahwa penggunaan angka acak Anda tidak menunjukkan pola berulang dan kinerja kurang diperhatikan.

bcattle
sumber
1

Saya menemukan menggunakan GKRandomSource.shareRandom () GameKit yang terbaik untuk saya.

import GameKit

let array = ["random1", "random2", "random3"]

func getRandomIndex() -> Int {
    let randomNumber = GKRandomSource.sharedRandom().nextIntWithUpperBound(array.count)
    return randomNumber

atau Anda bisa mengembalikan objek pada indeks acak yang dipilih. Pastikan fungsi mengembalikan sebuah String terlebih dahulu, dan kemudian mengembalikan indeks array.

    return array[randomNumber]

Singkat dan langsung ke inti nya.

djames04
sumber
1

Ada metode bawaan Collectionsekarang:

let foods = ["🍕", "🍔", "🍣", "🍝"]
let myDinner = foods.randomElement()

Jika Anda ingin mengekstrak hingga nelemen acak dari koleksi, Anda dapat menambahkan ekstensi seperti ini:

extension Collection {
    func randomElements(_ count: Int) -> [Element] {
        var shuffledIterator = shuffled().makeIterator()
        return (0..<count).compactMap { _ in shuffledIterator.next() }
    }
}

Dan jika Anda ingin mereka menjadi unik, Anda dapat menggunakan a Set, tetapi elemen koleksi harus sesuai dengan Hashableprotokol:

extension Collection where Element: Hashable {
    func randomUniqueElements(_ count: Int) -> [Element] {
        var shuffledIterator = Set(shuffled()).makeIterator()
        return (0..<count).compactMap { _ in shuffledIterator.next() }
    }
}
Gigisommo
sumber
0

Kode swift3 terbaru mencobanya berfungsi dengan baik

 let imagesArray = ["image1.png","image2.png","image3.png","image4.png"]

        var randomNum: UInt32 = 0
        randomNum = arc4random_uniform(UInt32(imagesArray.count))
        wheelBackgroundImageView.image = UIImage(named: imagesArray[Int(randomNum)])
Gangireddy Rami Reddy
sumber
-2

Saya menemukan cara yang sangat berbeda untuk melakukannya dengan menggunakan fitur-fitur baru yang diperkenalkan di Swift 4.2.

// 👇🏼 - 1 
public func shufflePrintArray(ArrayOfStrings: [String]) -> String {
// - 2 
       let strings = ArrayOfStrings
//- 3
       var stringans =  strings.shuffled()
// - 4
        var countS = Int.random(in: 0..<strings.count)
// - 5
        return stringans[countS] 
}

  1. kami mendeklarasikan fungsi dengan parameter mengambil array dari Strings dan mengembalikan sebuah String.

  2. Kemudian kita mengambil ArrayOfStrings dalam sebuah variabel.

  3. Kemudian kita memanggil fungsi shuffled dan menyimpannya dalam sebuah variabel. (Hanya didukung di 4.2)
  4. Kemudian kami mendeklarasikan variabel yang menyimpan nilai acak dari jumlah total String.
  5. Terakhir kami mengembalikan string yang dikocok pada nilai indeks countS.

Ini pada dasarnya mengocok array string dan kemudian juga memiliki pilihan acak dari jumlah total jumlah dan kemudian mengembalikan indeks acak dari array yang dikocok.

Nachos dan Cheetos
sumber