簡體   English   中英

Swift中如何生成隨機數?

[英]How to generate a random number in Swift?

我實現了 Swift 這本書提供了一個隨機數生成器的實現。 復制和粘貼此實現的最佳做法是什么? 或者有沒有我們現在可以使用的庫?

斯威夫特 4.2+

Xcode 10 附帶的 Swift 4.2 為許多數據類型引入了新的易於使用的隨機函數。 您可以對數字類型調用random()方法。

let randomInt = Int.random(in: 0..<6)
let randomDouble = Double.random(in: 2.71828...3.14159)
let randomBool = Bool.random()

arc4random_uniform(n)用於 0 到 n-1 之間的隨機整數。

let diceRoll = Int(arc4random_uniform(6) + 1)

將結果轉換為 Int,這樣您就不必將您的變量顯式鍵入為UInt32 (這似乎不是 Swifty)。

編輯:為 Swift 3.0 更新

arc4random在 Swift 中運行良好,但基本函數僅限於 32 位整數類型(在 iPhone 5S 和現代 Mac 上Int是 64 位)。 這是一個可以用整數文字表示的類型的隨機數的通用函數:

public func arc4random<T: ExpressibleByIntegerLiteral>(_ type: T.Type) -> T {
    var r: T = 0
    arc4random_buf(&r, MemoryLayout<T>.size)
    return r
}

我們可以使用這個新的通用函數來擴展UInt64 ,添加邊界參數並減輕模偏差。 (這是直接從arc4random.c提升的)

public extension UInt64 {
    public static func random(lower: UInt64 = min, upper: UInt64 = max) -> UInt64 {
        var m: UInt64
        let u = upper - lower
        var r = arc4random(UInt64.self)

        if u > UInt64(Int64.max) {
            m = 1 + ~u
        } else {
            m = ((max - (u * 2)) + 1) % u
        }

        while r < m {
            r = arc4random(UInt64.self)
        }

        return (r % u) + lower
    }
}

有了它,我們可以為相同的參數擴展Int64 ,處理溢出:

public extension Int64 {
    public static func random(lower: Int64 = min, upper: Int64 = max) -> Int64 {
        let (s, overflow) = Int64.subtractWithOverflow(upper, lower)
        let u = overflow ? UInt64.max - UInt64(~s) : UInt64(s)
        let r = UInt64.random(upper: u)

        if r > UInt64(Int64.max)  {
            return Int64(r - (UInt64(~lower) + 1))
        } else {
            return Int64(r) + lower
        }
    }
}

為了完成家庭...

private let _wordSize = __WORDSIZE

public extension UInt32 {
    public static func random(lower: UInt32 = min, upper: UInt32 = max) -> UInt32 {
        return arc4random_uniform(upper - lower) + lower
    }
}

public extension Int32 {
    public static func random(lower: Int32 = min, upper: Int32 = max) -> Int32 {
        let r = arc4random_uniform(UInt32(Int64(upper) - Int64(lower)))
        return Int32(Int64(r) + Int64(lower))
    }
}

public extension UInt {
    public static func random(lower: UInt = min, upper: UInt = max) -> UInt {
        switch (_wordSize) {
            case 32: return UInt(UInt32.random(UInt32(lower), upper: UInt32(upper)))
            case 64: return UInt(UInt64.random(UInt64(lower), upper: UInt64(upper)))
            default: return lower
        }
    }
}

public extension Int {
    public static func random(lower: Int = min, upper: Int = max) -> Int {
        switch (_wordSize) {
            case 32: return Int(Int32.random(Int32(lower), upper: Int32(upper)))
            case 64: return Int(Int64.random(Int64(lower), upper: Int64(upper)))
            default: return lower
        }
    }
}

畢竟,我們終於可以做這樣的事情了:

let diceRoll = UInt64.random(lower: 1, upper: 7)

為 Swift 4.2 編輯

從 Swift 4.2 開始,您現在可以使用 Swift 自己的原生函數,而不是使用導入的 C 函數 arc4random_uniform()。

// Generates integers starting with 0 up to, and including, 10
Int.random(in: 0 ... 10)

您也可以使用random(in:)來獲取其他原始值的隨機值; 例如 Int、Double、Float 甚至 Bool。

斯威夫特版本 < 4.2

此方法將在給定的最小值和最大值之間生成一個隨機Int

func randomInt(min: Int, max: Int) -> Int {
    return min + Int(arc4random_uniform(UInt32(max - min + 1)))
}

我使用了這段代碼:

var k: Int = random() % 10;

從 iOS 9 開始,您可以使用新的 GameplayKit 類以多種方式生成隨機數。

您有四種源類型可供選擇:一般隨機源(未命名,由系統選擇它的功能)、線性同余、ARC4 和 Mersenne Twister。 這些可以生成隨機整數、浮點數和布爾值。

在最簡單的層面上,您可以從系統的內置隨機源生成一個隨機數,如下所示:

GKRandomSource.sharedRandom().nextInt()

這會生成一個介於 -2,147,483,648 和 2,147,483,647 之間的數字。 如果你想要一個介於 0 和上限(不包括)之間的數字,你可以使用:

GKRandomSource.sharedRandom().nextIntWithUpperBound(6)

GameplayKit 內置了一些方便的構造函數來處理骰子。 例如,您可以像這樣滾動一個六面骰子:

let d6 = GKRandomDistribution.d6()
d6.nextInt()

另外,您可以使用 GKShuffledDistribution 之類的東西來塑造隨機分布。 這需要更多解釋,但如果您有興趣,可以閱讀我的 GameplayKit 隨機數教程

你可以像在 C 中那樣做:

let randomNumber = arc4random()

randomNumber被推斷為UInt32類型(一個 32 位無符號整數)

使用arc4random_uniform()

用法:

arc4random_uniform(someNumber: UInt32) -> UInt32

這為您提供了0someNumber - 1范圍內的隨機整數。

UInt32的最大值為 4,294,967,295(即2^32 - 1 )。

例子:

  • 硬幣翻轉

     let flip = arc4random_uniform(2) // 0 or 1
  • 擲骰子

     let roll = arc4random_uniform(6) + 1 // 1...6
  • 十月的隨機一天

     let day = arc4random_uniform(31) + 1 // 1...31
  • 1990年代的隨機年份

     let year = 1990 + arc4random_uniform(10)

一般形式:

let number = min + arc4random_uniform(max - min + 1)

其中numbermaxminUInt32

關於什么...

arc4random()

您還可以使用arc4random()獲得一個隨機數,它會產生一個介於 0 和 2^32-1 之間的UInt32 因此,要獲得0x-1之間的隨機數,您可以將其除以x並取余數。 或者換句話說,使用余數運算符 (%)

let number = arc4random() % 5 // 0...4

但是,這會產生輕微的模數偏差(另請參見此處此處),因此建議使用arc4random_uniform()

Int相互轉換

通常可以這樣做以便在IntUInt32之間來回轉換:

let number: Int = 10
let random = Int(arc4random_uniform(UInt32(number)))

但是,問題在於Int在 32 位系統上的范圍為-2,147,483,648...2,147,483,647 ,在 64 位系統上的范圍為-9,223,372,036,854,775,808...9,223,372,036,854,775,807 將此與0...4,294,967,295UInt32范圍進行比較。 UInt32U表示unsigned

考慮以下錯誤:

UInt32(-1) // negative numbers cause integer overflow error
UInt32(4294967296) // numbers greater than 4,294,967,295 cause integer overflow error

因此,您只需要確保您的輸入參數在UInt32范圍內,並且您也不需要超出該范圍的輸出。

10 (0-9) 之間的隨機數示例;

import UIKit

let randomNumber = Int(arc4random_uniform(10))

非常簡單的代碼 - 簡單而簡短。

我已經能夠使用rand()來獲得一個隨機的 CInt。 您可以使用以下方法使其成為 Int :

let myVar: Int = Int(rand())

您可以使用您最喜歡的 C 隨機函數,並在需要時將值轉換為 Int。

@jstn 的回答很好,但有點冗長。 Swift 被稱為面向協議的語言,因此我們可以通過添加協議擴展的默認實現來實現相同的結果,而無需為整數系列中的每個類實現樣板代碼。

public extension ExpressibleByIntegerLiteral {
    public static func arc4random() -> Self {
        var r: Self = 0
        arc4random_buf(&r, MemoryLayout<Self>.size)
        return r
    }
}

現在我們可以這樣做:

let i = Int.arc4random()
let j = UInt32.arc4random()

並且所有其他整數類都可以。

Swift 4.2中,您可以通過對所需的任何數字類型調用random()方法來生成隨機數,並提供您想要使用的范圍。 例如,這會生成 1 到 9 范圍內的隨機數,包括兩邊

let randInt = Int.random(in: 1..<10)

也與其他類型

let randFloat = Float.random(in: 1..<20)
let randDouble = Double.random(in: 1...30)
let randCGFloat = CGFloat.random(in: 1...40)

更新日期:2022 年 6 月 9 日。

斯威夫特 5.7

假設我們有一個數組:

let numbers: [Int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]


對於 iOS 和 macOS,您可以在 Xcode 的框架GameKit中使用系統范圍的隨機源 在這里您可以找到GKRandomSource類及其sharedRandom()類方法:

import GameKit

private func randomNumberGenerator() -> Int {
    let rand = GKRandomSource.sharedRandom().nextInt(upperBound: numbers.count)
    return numbers[rand]
}

randomNumberGenerator()


您還可以使用randomElement()方法返回集合的隨機元素:

let randomNumber = numbers.randomElement()!
print(randomNumber)


或使用arc4random_uniform() 注意這個方法返回UInt32類型。

let generator = Int(arc4random_uniform(11))
print(generator)


而且,當然,我們可以使用makeIterator()方法,該方法返回集合元素的迭代器。

let iterator: Int = (1...10).makeIterator().shuffled().first!
print(iterator)


您在此處看到的最后一個示例在static func random(in range: ClosedRange<Int>) -> Int的幫助下返回指定范圍內的隨機值。

let randomizer = Int.random(in: 1...10)
print(randomizer)


偽隨機雙數生成器drand48()返回一個介於 0.0 和 1.0 之間的值。

import Foundation

let randomInt = Int(drand48() * 10)

這是一個做得很好的庫https://github.com/thellimist/SwiftRandom

public extension Int {
    /// SwiftRandom extension
    public static func random(lower: Int = 0, _ upper: Int = 100) -> Int {
        return lower + Int(arc4random_uniform(UInt32(upper - lower + 1)))
    }
}

public extension Double {
    /// SwiftRandom extension
    public static func random(lower: Double = 0, _ upper: Double = 100) -> Double {
        return (Double(arc4random()) / 0xFFFFFFFF) * (upper - lower) + lower
    }
}

public extension Float {
    /// SwiftRandom extension
    public static func random(lower: Float = 0, _ upper: Float = 100) -> Float {
        return (Float(arc4random()) / 0xFFFFFFFF) * (upper - lower) + lower
    }
}

public extension CGFloat {
    /// SwiftRandom extension
    public static func random(lower: CGFloat = 0, _ upper: CGFloat = 1) -> CGFloat {
        return CGFloat(Float(arc4random()) / Float(UINT32_MAX)) * (upper - lower) + lower
    }
}

Swift 4.2 開始

有一組新的 API:

let randomIntFrom0To10 = Int.random(in: 0 ..< 10)
let randomDouble = Double.random(in: 1 ... 10)
  • 所有數字類型現在都有接受rangerandom(in:)方法。

  • 它返回一個均勻分布在該范圍內的數字。


TL;博士

那么,“好”的舊方法有什么問題?

  1. 您必須使用導入的C API (它們在平台之間有所不同)

  2. 而且……

如果我告訴你隨機不是那么隨機怎么辦?

如果您使用arc4random() (計算余數) ,如arc4random() % aNumber ,則結果不會0aNumber之間均勻分布。 有一個叫做模偏差的問題。

模偏置

通常,該函數會生成一個介於0MAX之間的隨機數(取決於類型等) 舉一個簡單、快速的例子,假設最大數是7 ,並且您關心0 ..< 2 (或區間 [0, 3) 如果您願意的話)范圍內的隨機數。

個別數字的概率是:

  • 0:3/8 = 37.5%
  • 1:3/8 = 37.5%
  • 2:2/8 = 25%

換句話說,你更有可能最終得到01而不是2 當然,請記住,這是非常簡化的,並且MAX數字要高得多,使其更加“公平”。

SE-0202 - Swift 4.2中的隨機統一解決了這個問題

 let MAX : UInt32 = 9
 let MIN : UInt32 = 1

    func randomNumber()
{
    var random_number = Int(arc4random_uniform(MAX) + MIN)
    print ("random = ", random_number);
}
var randomNumber = Int(arc4random_uniform(UInt32(5)))

這里 5 將確保從零到四生成隨機數。 您可以相應地設置該值。

我想在現有答案中補充一點,Swift 書中的隨機數生成器示例是線性同余生成器(LCG),它是一個嚴重受限的示例,除了必須瑣碎的示例之外不應如此,其中隨機性的質量不一點都不重要。 並且LCG 永遠不應該用於加密目的

arc4random()要好得多,可以用於大多數目的,但同樣不應用於加密目的。

如果您想要保證加密安全的東西,請使用SecCopyRandomBytes() 請注意,如果您將隨機數生成器構建到某個東西中,其他人可能最終(錯誤)將其用於加密目的(例如密碼、密鑰或鹽生成),那么您應該考慮使用SecCopyRandomBytes()無論如何,即使您的需要並不完全需要。

在某些版本的 Xcode 中沒有 arc4Random_uniform() (在 7.1 中它運行但不會自動完成)。 您可以改為這樣做。

生成一個 0-5 的隨機數。 第一的

import GameplayKit

然后

let diceRoll = GKRandomSource.sharedRandom().nextIntWithUpperBound(6)

斯威夫特 4.2

再見,導入 Foundation C 庫arc4random_uniform()

// 1  
let digit = Int.random(in: 0..<10)

// 2
if let anotherDigit = (0..<10).randomElement() {
  print(anotherDigit)
} else {
  print("Empty range.")
}

// 3
let double = Double.random(in: 0..<1)
let float = Float.random(in: 0..<1)
let cgFloat = CGFloat.random(in: 0..<1)
let bool = Bool.random()
  1. 您使用 random(in:) 從范圍中生成隨機數字。
  2. 如果范圍為空,則 randomElement() 返回 nil,因此您解開返回的 Int? 如果讓。
  3. 您使用 random(in:) 生成隨機 Double、Float 或 CGFloat 並使用 random() 返回隨機 Bool。

更多@官方

以下代碼將生成 0 到 255 之間的安全隨機數:

extension UInt8 {
  public static var random: UInt8 {
    var number: UInt8 = 0
    _ = SecRandomCopyBytes(kSecRandomDefault, 1, &number)
    return number
  }
}

你這樣稱呼它:

print(UInt8.random)

對於更大的數字,它變得更加復雜。
這是我能想到的最好的:

extension UInt16 {
  public static var random: UInt16 {
    let count = Int(UInt8.random % 2) + 1
    var numbers = [UInt8](repeating: 0, count: 2)
    _ = SecRandomCopyBytes(kSecRandomDefault, count, &numbers)
    return numbers.reversed().reduce(0) { $0 << 8 + UInt16($1) }
  }
}

extension UInt32 {
  public static var random: UInt32 {
    let count = Int(UInt8.random % 4) + 1
    var numbers = [UInt8](repeating: 0, count: 4)
    _ = SecRandomCopyBytes(kSecRandomDefault, count, &numbers)
    return numbers.reversed().reduce(0) { $0 << 8 + UInt32($1) }
  }
}

這些方法使用一個額外的隨機數來確定將使用多少UInt8來創建隨機數。 最后一行將[UInt8]轉換為UInt16UInt32

我不知道最后兩個是否仍然算作真正隨機的,但您可以根據自己的喜好對其進行調整:)

斯威夫特 4.2

Swift 4.2 在標准庫中包含了一個原生且功能相當全面的隨機數 API。 Swift Evolution 提案 SE-0202

let intBetween0to9 = Int.random(in: 0...9) 
let doubleBetween0to1 = Double.random(in: 0...1)

所有數字類型都有靜態隨機數(in:) ,它接受范圍並返回給定范圍內的隨機數

Xcode 14, swift 5

public extension Array where Element == Int {
    static func generateNonRepeatedRandom(size: Int) -> [Int] {
        guard size > 0 else {
            return [Int]()
        }
        return Array(0..<size).shuffled()
    }
}

如何使用:

let array = Array.generateNonRepeatedRandom(size: 15)
print(array)

Output

在此處輸入圖像描述

您可以像這樣使用GeneratorOf

var fibs = ArraySlice([1, 1])
var fibGenerator = GeneratorOf{
    _ -> Int? in
    fibs.append(fibs.reduce(0, combine:+))
    return fibs.removeAtIndex(0)
}

println(fibGenerator.next())
println(fibGenerator.next())
println(fibGenerator.next())
println(fibGenerator.next())
println(fibGenerator.next())
println(fibGenerator.next())

我使用此代碼生成一個隨機數:

//
//  FactModel.swift
//  Collection
//
//  Created by Ahmadreza Shamimi on 6/11/16.
//  Copyright © 2016 Ahmadreza Shamimi. All rights reserved.
//

import GameKit

struct FactModel {

    let fun  = ["I love swift","My name is Ahmadreza","I love coding" ,"I love PHP","My name is ALireza","I love Coding too"]


    func getRandomNumber() -> String {

        let randomNumber  = GKRandomSource.sharedRandom().nextIntWithUpperBound(fun.count)

        return fun[randomNumber]
    }
}

細節

xCode 9.1,斯威夫特 4

面向數學的解決方案 (1)

import Foundation

class Random {

    subscript<T>(_ min: T, _ max: T) -> T where T : BinaryInteger {
        get {
            return rand(min-1, max+1)
        }
    }
}

let rand = Random()

func rand<T>(_ min: T, _ max: T) -> T where T : BinaryInteger {
    let _min = min + 1
    let difference = max - _min
    return T(arc4random_uniform(UInt32(difference))) + _min
}

溶液的使用(一)

let x = rand(-5, 5)       // x = [-4, -3, -2, -1, 0, 1, 2, 3, 4]
let x = rand[0, 10]       // x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

面向程序員的解決方案 (2)

不要忘記在此處添加面向數學的解決方案 (1) 代碼

import Foundation

extension CountableRange where Bound : BinaryInteger {

    var random: Bound {
        return rand(lowerBound-1, upperBound)
    }
}

extension CountableClosedRange where Bound : BinaryInteger {

    var random: Bound {
        return rand[lowerBound, upperBound]
    }
}

溶液的使用(二)

let x = (-8..<2).random           // x = [-8, -7, -6, -5, -4, -3, -2, -1, 0, 1]
let x = (0..<10).random           // x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
let x = (-10 ... -2).random       // x = [-10, -9, -8, -7, -6, -5, -4, -3, -2]

完整樣本

不要忘記在此處添加解決方案(1)和解決方案(2)代碼

private func generateRandNums(closure:()->(Int)) {

    var allNums = Set<Int>()
    for _ in 0..<100 {
        allNums.insert(closure())
    }
    print(allNums.sorted{ $0 < $1 })
}

generateRandNums {
    (-8..<2).random
}

generateRandNums {
    (0..<10).random
}

generateRandNums {
    (-10 ... -2).random
}

generateRandNums {
    rand(-5, 5)
}
generateRandNums {
    rand[0, 10]
}

樣本結果

在此處輸入圖像描述

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM