เลือกองค์ประกอบแบบสุ่มจากอาร์เรย์


189

สมมติว่าฉันมีอาร์เรย์และฉันต้องการที่จะเลือกองค์ประกอบหนึ่งโดยการสุ่ม

อะไรจะเป็นวิธีที่ง่ายที่สุดในการทำเช่นนี้?

array[random index]วิธีที่ชัดเจนจะเป็น แต่บางทีอาจจะมีอะไรบางอย่างเช่นทับทิมของarray.sample? หรือหากไม่สามารถสร้างวิธีดังกล่าวโดยใช้ส่วนขยายได้


1
คุณลองวิธีการอื่น ๆ แล้วหรือยัง
ฟอร์ดนายอำเภอ

ฉันจะลองarray[random number from 0 to length-1]แต่ฉันไม่สามารถหาวิธีสร้าง int แบบสุ่มได้อย่างรวดเร็วฉันจะถามมันใน stack overflow หากฉันไม่ได้ถูกบล็อก :) ฉันไม่ต้องการสร้างปัญหาให้กับคำถามครึ่งทางเมื่ออาจมี บางอย่างเช่น ruby'sarray.sample
Fela Winkelmolen

1
คุณใช้ arc4random () เหมือนใน Obj-C
Arbitur

ไม่มีคำอธิบายว่าเพราะเหตุใดคำถามของคุณไม่ได้รับข้อเสนอแนะแบบเดียวกันกับ JQuery แต่โดยทั่วไปคุณควรปฏิบัติตามคำแนะนำเหล่านี้เมื่อโพสต์คำถาม จะถามคำถามที่ดีได้อย่างไร . ทำให้ดูเหมือนว่าคุณใช้ความพยายามเพียงเล็กน้อยในการหาวิธีแก้ปัญหาก่อนที่จะขอความช่วยเหลือจากบุคคลอื่น เมื่อฉัน google "เลือกหมายเลขสุ่มอย่างรวดเร็ว" หน้าแรกจะเต็มไปด้วยคำตอบที่แนะนำ arc4random_uniform นอกจากนี้ RTFD ... "อ่านเอกสาร f'ing" เป็นเรื่องที่น่าประหลาดใจที่สามารถตอบคำถามนี้ได้จำนวนมาก
ออสติน A

ขอบคุณสำหรับคำติชมของคุณ ใช่ฉันคิดว่าฉันควรตอบคำถามด้วยตัวเอง แต่มันก็ดูง่ายพอที่จะให้คะแนนชื่อเสียงฟรีแก่คนอื่น และฉันเขียนขึ้นเมื่อไม่แม้แต่เอกสารอย่างเป็นทางการของ Apple ที่เปิดเผยต่อสาธารณะ แต่ก็ไม่มีผลลัพธ์ของ Google ในเวลานั้น แต่คำถามที่ครั้งหนึ่งเคยเป็นที่ -12 ดังนั้นผมค่อนข้างมั่นใจว่ามันจะเป็นบวกในที่สุด :)
ฟีลา Winkelmolen

คำตอบ:


321

Swift 4.2 ขึ้นไป

randomElement()วิธีที่แนะนำใหม่จะถูกสร้างขึ้นในวิธีการในการเก็บโปรโตคอล: มันจะส่งคืนตัวเลือกเพื่อหลีกเลี่ยงกรณีที่ว่างเปล่าที่ฉันคาดไว้

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

หากคุณไม่ได้สร้างอาร์เรย์และไม่รับประกันการนับ> 0 คุณควรทำดังนี้:

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

Swift 4.1 และต่ำกว่า

เพียงตอบคำถามของคุณคุณสามารถทำได้เพื่อให้ได้การเลือกอาเรย์แบบสุ่ม:

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

การหล่อนั้นน่าเกลียด แต่ฉันเชื่อว่าพวกมันจำเป็นต้องใช้เว้นแต่จะมีคนอื่นมีวิธีอื่น


4
เหตุใด Swift จึงไม่เสนอตัวสร้างตัวเลขสุ่มที่ส่งกลับค่า Int บรรทัดที่ 2 นี้ดูเหมือน verbose มากเพียงแค่คืนค่า Int ที่เลือกแบบสุ่ม มีความได้เปรียบทางคอมพิวเตอร์ / การสร้างประโยคเพื่อคืน UInt32 ซึ่งตรงกันข้ามกับ Int หรือไม่? นอกจากนี้ทำไม Swift จึงไม่เสนอทางเลือก Int สำหรับฟังก์ชันนี้หรืออนุญาตให้ผู้ใช้ระบุจำนวนเต็มชนิดที่พวกเขาต้องการส่งคืน
ออสติน A

หากต้องการเพิ่มโน้ตวิธีสร้างตัวเลขแบบสุ่มนี้สามารถป้องกัน "โมดูโลอคติ" อ้างอิงman arc4randomและstackoverflow.com/questions/10984974/…
Kent Liau

1
@AustinA, Swift 4.2 DOES มีฟังก์ชั่นตัวสร้างตัวเลขสุ่มแบบดั้งเดิมที่นำมาใช้กับชนิดข้อมูลเซนต์คิตส์และเนวิสที่คุณต้องการ: Int, Double, Float, UInt32 เป็นต้นและช่วยให้คุณสามารถกำหนดช่วงเป้าหมายสำหรับค่าได้ มีประโยชน์มาก คุณสามารถใช้อาร์เรย์ [Int.random (0 .. <array.count)] `ใน Swift 4.2
Duncan C

ฉันหวังว่า Swift 4.2 จะนำremoveRandomElement()ฟังก์ชั่นเพิ่มเติมมาrandomElement()ใช้ มันจะเป็นแบบจำลองในremoveFirst()แต่ลบวัตถุที่ดัชนีแบบสุ่ม
Duncan C

@DuncanC คุณควรหลีกเลี่ยง0..<array.count(ด้วยเหตุผลหลายประการสาเหตุหลักที่ทำให้มันไม่สามารถใช้งานได้กับบางส่วน คุณสามารถทำตามด้วยlet randomIndex = array.indices.randomElement() let randomElement = array.remove(at: randomIndex)คุณยังสามารถ inline let randomElement = array.remove(at: array.indices.randomElement())มัน
Alexander - Reinstate Monica

137

การพูดถึงสิ่งที่ลูคัสกล่าวไว้คุณสามารถสร้างส่วนขยายของคลาสอาเรย์เช่นนี้ได้:

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

ตัวอย่างเช่น:

let myArray = [1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 14, 16]
let myItem = myArray.randomItem() // Note: myItem is an Optional<Int>

2
ในสวิฟท์ 2 ได้รับการเปลี่ยนชื่อเป็นT Element
GDanger

25
โปรดสังเกตว่าอาเรย์ที่ว่างเปล่าจะทำให้เกิดการขัดข้องที่นี่
Berik

1
@Berik คุณสามารถส่งคืนอิลิเมนต์ที่เป็นทางเลือกจากนั้นทำการguardตรวจสอบเพื่อดูว่าอาร์เรย์นั้นว่างเปล่าหรือnilไม่
Harish

1
ตกลง อาร์เรย์ชนกับขอบเขตไม่ให้ขอบเขตเพื่อให้สามารถเป็นนักแสดงได้ การโทรเข้ามาarc4randomทำให้ประสิทธิภาพการทำงานเพิ่มขึ้นเล็กน้อย ฉันได้อัพเดตคำตอบแล้ว
Berik

45

รุ่น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)))]
    }

}

สิ่งนี้อาจผิดพลาดโดยมีดัชนีอยู่นอกขอบเขตของคอลเล็กชันที่startIndex != 0
dan

21

ในSwift 2.2สิ่งนี้สามารถทำให้เป็นเรื่องทั่วไปเพื่อที่เราจะได้:

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

ขั้นแรกให้ใช้randomคุณสมบัติแบบคงที่สำหรับ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)
    }
}

จากนั้นสำหรับClosedIntervals ด้วยUnsignedIntegerTypeขอบเขต:

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

จากนั้น (เกี่ยวข้องเล็กน้อย) สำหรับClosedIntervals ที่มีSignedIntegerTypeขอบเขต (ใช้วิธีการช่วยเหลือที่อธิบายไว้ด้านล่าง):

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

... ที่unsignedDistanceTo, unsignedDistanceFromMinและplusMinIntMaxผู้ช่วยวิธีการสามารถดำเนินการดังต่อไปนี้:

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

ในที่สุดสำหรับคอลเลกชันทั้งหมดที่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]
    }
}

... ซึ่งสามารถเพิ่มประสิทธิภาพเล็กน้อยสำหรับจำนวนเต็ม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
    }
}

18

คุณสามารถใช้ฟังก์ชัน random () ในตัวของ Swift ได้เช่นกันสำหรับส่วนขยาย:

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

สุ่มจริง ๆ () มาจากการเชื่อมโยงไลบรารีมาตรฐาน C คุณสามารถเห็นและเพื่อน ๆ ในเทอร์มินัล "คนสุ่ม" แต่ดีใจที่คุณชี้ให้เห็นความพร้อม!
David H

1
สิ่งนี้สร้างลำดับสุ่มที่เหมือนกันในแต่ละครั้งที่รัน
iTSangar

1
@iTSangar คุณพูดถูก! rand () เป็นสิ่งที่ถูกต้องที่จะใช้ กำลังอัปเดตคำตอบของฉัน
NatashaTheRobot

6
นี่คือความอ่อนไหวต่อโมดูโลอคติ
Aidan Gomez

@mattt เขียนบทความที่ดีในการสร้างตัวเลขแบบสุ่ม TL; DR ของตระกูล arc4random เป็นตัวเลือกที่ดีกว่า
elitalon

9

ข้อเสนอแนะอื่น ๆ ของ Swift 3

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

4

ทำตามคำตอบของผู้อื่น แต่ด้วยการสนับสนุน Swift 2

สวิฟท์ 1.x

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

สวิฟท์ 2.x

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

เช่น:

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

2

การประยุกต์ใช้งานทางเลือกพร้อมการตรวจสอบอาเรย์ที่ว่างเปล่า

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

2

ต่อไปนี้เป็นส่วนขยายของอาร์เรย์ที่มีการตรวจสอบอาร์เรย์ว่างเพื่อความปลอดภัยเพิ่มเติม:

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

คุณสามารถใช้งานได้ง่ายอย่างนี้ :

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

ถ้าคุณชอบกรอบที่ยังมีคุณสมบัติที่มีประโยชน์มากขึ้นบางแล้วเช็คเอาท์HandySwift คุณสามารถเพิ่มลงในโครงการของคุณผ่านทาง Carthageจากนั้นใช้อย่างตรงตามตัวอย่างด้านบน:

import HandySwift    

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

นอกจากนี้ยังมีตัวเลือกในการรับองค์ประกอบสุ่มหลายรายการพร้อมกัน:

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


2

Swift 3 - ใช้งานง่ายง่าย

  1. สร้างอาร์เรย์

    var arrayOfColors = [UIColor.red, UIColor.yellow, UIColor.orange, UIColor.green]
  2. สร้างสีแบบสุ่ม

    let randomColor = arc4random() % UInt32(arrayOfColors.count)
  3. กำหนดสีนั้นให้กับวัตถุของคุณ

    your item = arrayOfColors[Int(randomColor)]

นี่คือตัวอย่างจากSpriteKitโครงการที่อัพเดตSKLabelNodeด้วยการสุ่มString:

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

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

    let labelNode = SKLabelNode(text: array[Int(randomNumber)])

2

หากคุณต้องการที่จะได้รับองค์ประกอบสุ่มมากกว่าหนึ่งรายการจากอาเรย์ของคุณโดยไม่ซ้ำซ้อน GameplayKit ได้ครอบคลุม:

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

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

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

คุณมีสองทางเลือกสำหรับการสุ่มเลือกดูGKRandomSource :

GKARC4RandomSourceชั้นใช้ขั้นตอนวิธีการคล้ายกับว่าการจ้างงานในครอบครัว arc4random ของฟังก์ชั่น C (อย่างไรก็ตามอินสแตนซ์ของคลาสนี้ไม่ขึ้นอยู่กับการเรียกไปยังฟังก์ชัน arc4random)

GKLinearCongruentialRandomSourceระดับใช้อัลกอริทึมที่เร็ว แต่สุ่มน้อยกว่าระดับ GKARC4RandomSource (โดยเฉพาะตัวเลขที่สร้างขึ้นจำนวนน้อยจะทำซ้ำบ่อยกว่าบิตสูง) ใช้แหล่งข้อมูลนี้เมื่อประสิทธิภาพมีความสำคัญมากกว่าความคาดเดาไม่ได้ที่แข็งแกร่ง

GKMersenneTwisterRandomSourceชั้นใช้วิธีการที่จะช้า แต่สุ่มมากขึ้นกว่าระดับ GKARC4RandomSource ใช้แหล่งข้อมูลนี้เมื่อเป็นสิ่งสำคัญที่การใช้ตัวเลขสุ่มของคุณไม่แสดงรูปแบบการทำซ้ำและประสิทธิภาพจะลดความกังวลลง


1

ฉันพบว่าการใช้ GKRandomSource.sharedRandom () ของ GameKit นั้นดีที่สุดสำหรับฉัน

import GameKit

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

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

หรือคุณสามารถคืนวัตถุที่ดัชนีสุ่มที่เลือก ตรวจสอบให้แน่ใจว่าฟังก์ชั่นส่งกลับสตริงก่อนแล้วจึงส่งกลับดัชนีของอาร์เรย์

    return array[randomNumber]

สั้นและตรงประเด็น


1

มีวิธีการในตัวในCollectionตอนนี้:

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

หากคุณต้องการแยกnองค์ประกอบแบบสุ่มถึงคอลเลกชันคุณสามารถเพิ่มส่วนขยายเช่นนี้:

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

และถ้าคุณต้องการให้มันไม่เหมือนใครคุณสามารถใช้ a Setได้ แต่องค์ประกอบของคอลเลกชันต้องสอดคล้องกับHashableโปรโตคอล:

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

0

รหัส swift3 ล่าสุดลองใช้งานได้ดี

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

-2

ฉันหาวิธีที่แตกต่างออกไปมากโดยใช้คุณสมบัติใหม่ที่แนะนำใน 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. เราประกาศฟังก์ชั่นที่มีพารามิเตอร์กำลังใช้อาร์เรย์ของสตริงและส่งคืนสตริง

  2. จากนั้นเราก็ใช้ ArrayOfStringsในตัวแปร

  3. จากนั้นเราเรียกฟังก์ชันการสับและเก็บไว้ในตัวแปร (รองรับเฉพาะใน 4.2)
  4. จากนั้นเราจะประกาศตัวแปรที่บันทึกค่าที่สับได้ของจำนวนทั้งหมดของสตริง
  5. สุดท้ายเรากลับสตริงแบบสับที่ค่าดัชนีของ countS

โดยทั่วไปแล้วจะมีการสับเปลี่ยนอาร์เรย์ของสตริงและจากนั้นจะมีการสุ่มเลือกจำนวนของจำนวนทั้งหมดของการนับแล้วส่งกลับดัชนีแบบสุ่มของอาร์เรย์แบบสับ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.