Chọn một phần tử ngẫu nhiên từ một mảng


189

Giả sử tôi có một mảng và tôi muốn chọn một yếu tố ngẫu nhiên.

Cách đơn giản nhất để làm điều này là gì?

Cách rõ ràng sẽ là array[random index]. Nhưng có lẽ có một cái gì đó giống như ruby array.sample? Hoặc nếu không có thể tạo ra một phương thức như vậy bằng cách sử dụng một phần mở rộng?


1
Bạn đã thử bất kỳ phương pháp khác nhau chưa?
ford quận

Tôi sẽ thử array[random number from 0 to length-1], nhưng tôi không thể tìm ra cách tạo một int ngẫu nhiên trong swift, tôi sẽ hỏi nó khi tràn stack nếu tôi không bị chặn :) Tôi không muốn làm ô nhiễm câu hỏi bằng một nửa giải pháp khi có thể có một cái gì đó giống như rubyarray.sample
Fela Winkelmolen

1
Bạn sử dụng arc4random () như bạn sẽ làm trong Obj-C
Arbitur

Không có lời giải thích cho lý do tại sao câu hỏi của bạn không nhận được phản hồi giống như đối tác JQuery. Nhưng nói chung, bạn nên làm theo các hướng dẫn này khi đăng câu hỏi. Làm thế nào để hỏi một câu hỏi hay? . Làm cho nó giống như bạn bỏ một chút nỗ lực để tìm ra giải pháp trước khi nhờ người khác giúp đỡ. Khi tôi google "chọn số nhanh ngẫu nhiên", trang đầu tiên chứa đầy câu trả lời gợi ý arc4random_uniform. Ngoài ra, RTFD ... "đọc tài liệu f'ing". Thật đáng ngạc nhiên có bao nhiêu câu hỏi có thể được trả lời theo cách này.
Austin A

Cảm ơn bạn đã phản hồi của bạn. Đúng, tôi đoán tôi nên tự trả lời câu hỏi, nhưng có vẻ dễ đến mức thật tuyệt khi cho người khác những điểm danh tiếng gần như miễn phí. Và tôi đã viết nó khi ngay cả các tài liệu chính thức nhanh chóng của Apple không được công khai, chắc chắn không có kết quả nào của Google tại thời điểm đó. Nhưng câu hỏi đã từng ở mức -12, vì vậy tôi khá tự tin rằng cuối cùng nó sẽ tích cực :)
Fela Winkelmolen

Câu trả lời:


320

Swift 4.2 trở lên

Cách tiếp cận mới được đề xuất là một phương thức tích hợp trên giao thức Collection : randomElement(). Nó trả về một tùy chọn để tránh trường hợp trống mà tôi giả định trước đó.

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

Nếu bạn không tạo mảng và không được đảm bảo số lượng> 0, bạn nên làm một cái gì đó như:

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

Swift 4.1 trở xuống

Chỉ cần trả lời câu hỏi của bạn, bạn có thể làm điều này để đạt được lựa chọn mảng ngẫu nhiên:

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

Các vật đúc rất xấu, nhưng tôi tin rằng chúng là bắt buộc trừ khi người khác có cách khác.


4
Tại sao Swift không cung cấp trình tạo số ngẫu nhiên trả về Int? Dòng thứ 2 này có vẻ rất dài dòng chỉ để trả về Int. Có một số lợi thế tính toán / cú pháp để trả về UInt32 trái ngược với Int không? Ngoài ra, tại sao Swift không cung cấp thay thế Int cho chức năng này hoặc cho phép người dùng chỉ định loại số nguyên họ muốn trả về?
Austin A

Để thêm một ghi chú, phương pháp tạo số ngẫu nhiên này có thể ngăn chặn "độ lệch modulo". Tham khảo man arc4randomstackoverflow.com/questions/10984974/ từ
Kent Liau

1
@AustinA, Swift 4.2 DOES có chức năng tạo số ngẫu nhiên gốc được triển khai trên tất cả các loại dữ liệu vô hướng mà bạn có thể mong đợi: Int, Double, Float, UInt32, v.v. Và nó cho phép bạn cung cấp phạm vi mục tiêu cho các giá trị. Rất tiện dụng. Bạn có thể sử dụng mảng [Int.random (0 .. <Array.count)] `trong Swift 4.2
Duncan C

Tôi muốn Swift 4.2 thực hiện một removeRandomElement()chức năng ngoài randomElement(). Nó sẽ được mô hình hóa removeFirst(), nhưng loại bỏ một đối tượng ở một chỉ mục ngẫu nhiên.
Duncan C

@DuncanC Bạn nên tránh 0..<array.count(vì nhiều lý do, những lý do chính là nó không hoạt động đối với các lát và dễ bị lỗi). Bạn có thể làm let randomIndex = array.indices.randomElement(), tiếp theo là let randomElement = array.remove(at: randomIndex). Bạn thậm chí có thể nội tuyến nó để let randomElement = array.remove(at: array.indices.randomElement()).
Alexander - Tái lập Monica

137

Riffing về những gì Lucas nói, bạn có thể tạo một phần mở rộng cho lớp Array như thế này:

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

Ví dụ:

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
Trong swift 2 Tđã được đổi tên thành Element.
GDanger

25
Lưu ý rằng một mảng trống sẽ gây ra sự cố tại đây
Berik

1
@Berik Vâng, bạn có thể trả về một Phần tử tùy chọn và sau đó luôn luôn guardkiểm tra xem mảng đó có trống không và sau đó trả về nil.
Harish

1
Đã đồng ý. Mảng sụp đổ ở ngoài biên để chúng có thể được thực hiện. Gọi vào arc4randomlàm cho bất kỳ hiệu suất tăng hoàn toàn không đáng kể. Tôi đã cập nhật câu trả lời.
Berik

45

Phiên bản 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)))]
    }

}

Điều này có thể sụp đổ với một chỉ số nằm ngoài giới hạn của các bộ sưu tập trong đóstartIndex != 0
dan

21

Trong Swift 2.2, điều này có thể được khái quát hóa để chúng ta có:

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

Đầu tiên, triển khai thuộc tính tĩnh randomcho 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)
    }
}

Sau đó, cho ClosedIntervals với UnsignedIntegerTypegiới hạn:

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

Sau đó (tham gia nhiều hơn một chút), đối với ClosedIntervals có SignedIntegerTypegiới hạn (sử dụng các phương thức trợ giúp được mô tả thêm bên dưới):

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

... nơi unsignedDistanceTo, unsignedDistanceFromMinplusMinIntMaxhelper phương pháp có thể được thực hiện như sau:

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

Cuối cùng, cho tất cả các bộ sưu tập trong đó 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]
    }
}

... có thể được tối ưu hóa một chút cho số nguyên 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

Bạn cũng có thể sử dụng hàm Random () tích hợp sẵn của Swift cho tiện ích mở rộng:

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

Trên thực tế Random () là từ cầu nối thư viện C chuẩn, bạn có thể thấy nó và bạn bè trong Terminal, "man ngẫu nhiên". Nhưng vui mừng bạn chỉ ra sự sẵn có!
David H

1
điều này tạo ra chuỗi ngẫu nhiên giống nhau mỗi lần chạy
iTSangar

1
@iTSangar bạn nói đúng! rand () là một trong những chính xác để sử dụng. Cập nhật câu trả lời của tôi.
NatashaTheRobot

6
Điều này cũng dễ bị sai lệch modulo.
Aidan Gomez

@mattt đã viết một bài viết hay về tạo số ngẫu nhiên . TL; DR bất kỳ gia đình arc4random là một lựa chọn tốt hơn.
elitalon

9

Một đề xuất Swift 3 khác

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

4

Theo người khác trả lời nhưng với sự hỗ trợ của 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]
    }
}

Ví dụ:

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

2

Một thực hiện chức năng thay thế với kiểm tra cho mảng trống.

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

Đây là một phần mở rộng trên Mảng với một kiểm tra mảng trống để an toàn hơn:

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

Bạn có thể sử dụng nó đơn giản như thế này :

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

Nếu bạn thích một Framework cũng có một số tính năng tiện dụng hơn thì hãy kiểm tra HandySwift . Bạn có thể thêm nó vào dự án của mình thông qua Carthage sau đó sử dụng chính xác như trong ví dụ trên:

import HandySwift    

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

Ngoài ra, nó cũng bao gồm một tùy chọn để có được nhiều yếu tố ngẫu nhiên cùng một lúc :

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

2

Swift 3

nhập GameKit

func getRandomMessage() -> String {

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

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

    return messages[randomNumber].description

}

2

Swift 3 - đơn giản dễ sử dụng.

  1. Tạo mảng

    var arrayOfColors = [UIColor.red, UIColor.yellow, UIColor.orange, UIColor.green]
  2. Tạo màu ngẫu nhiên

    let randomColor = arc4random() % UInt32(arrayOfColors.count)
  3. Đặt màu đó cho đối tượng của bạn

    your item = arrayOfColors[Int(randomColor)]

Đây là một ví dụ từ một SpriteKitdự án cập nhật một SKLabelNodecách ngẫu nhiên String:

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

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

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

2

Nếu bạn muốn có thể lấy nhiều hơn một yếu tố ngẫu nhiên ra khỏi mảng của mình mà không có sự trùng lặp , GameplayKit đã bảo vệ bạn:

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

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

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

Bạn có một vài lựa chọn cho sự ngẫu nhiên, xem GKRandomSource :

Các GKARC4RandomSourcelớp học sử dụng một thuật toán tương tự như sử dụng trong gia đình arc4random chức năng C. (Tuy nhiên, các thể hiện của lớp này độc lập với các lệnh gọi đến các hàm arc4random.)

Các GKLinearCongruentialRandomSourcelớp học sử dụng một thuật toán đó là nhanh hơn, nhưng ít ngẫu nhiên, so với các lớp GKARC4RandomSource. (Cụ thể, các bit thấp của các số được tạo lặp lại thường xuyên hơn các bit cao.) Sử dụng nguồn này khi hiệu suất quan trọng hơn khả năng dự đoán mạnh mẽ.

Các GKMersenneTwisterRandomSourcelớp học sử dụng một thuật toán đó là chậm hơn, nhưng nhiều ngẫu nhiên, so với các lớp GKARC4RandomSource. Sử dụng nguồn này khi điều quan trọng là việc bạn sử dụng các số ngẫu nhiên không hiển thị các mẫu lặp lại và hiệu suất ít được quan tâm.


1

Tôi thấy việc sử dụng GKRandomSource. SharedRandom () của GameKit phù hợp nhất với tôi.

import GameKit

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

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

hoặc bạn có thể trả về đối tượng ở chỉ mục ngẫu nhiên được chọn. Hãy chắc chắn rằng hàm trả về một chuỗi trước, sau đó trả về chỉ mục của mảng.

    return array[randomNumber]

Ngắn và đến điểm.


1

Hiện tại có một phương thức tích hợp sẵn Collection:

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

Nếu bạn muốn trích xuất tối đa ncác yếu tố ngẫu nhiên từ bộ sưu tập, bạn có thể thêm tiện ích mở rộng như thế này:

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

Và nếu bạn muốn chúng là duy nhất, bạn có thể sử dụng một Set, nhưng các phần tử của bộ sưu tập phải tuân theo Hashablegiao thức:

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

0

Mã swift3 mới nhất hãy thử nó hoạt động tốt

 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

Tôi đã tìm ra một cách rất khác để làm như vậy bằng cách sử dụng các tính năng mới được giới thiệu trong 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. chúng tôi đã khai báo một hàm với các tham số lấy một chuỗi Chuỗi và trả về Chuỗi.

  2. Sau đó, chúng tôi lấy ArrayOfStrings trong một biến.

  3. Sau đó, chúng ta gọi hàm xáo trộn và lưu nó trong một biến. (Chỉ được hỗ trợ trong 4.2)
  4. Sau đó, chúng tôi khai báo một biến lưu giá trị xáo trộn của tổng số chuỗi.
  5. Cuối cùng, chúng tôi trả về chuỗi xáo trộn ở giá trị chỉ số của CountS.

Về cơ bản, nó xáo trộn mảng chuỗi và sau đó cũng có một lựa chọn ngẫu nhiên về số lượng tổng số đếm và sau đó trả về chỉ số ngẫu nhiên của mảng được xáo trộn.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.