如何用Apple的Swift语言生成一个随机数?


443

我意识到Swift书提供了一个随机数生成器的实现。将这种实现复制并粘贴到自己的程序中的最佳实践是吗?还是有一个可以立即使用的库?

Answers:


466

迅捷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()

7
我不必显式导入达尔文
finneycanhelp 2014年

3
在我的Playground文件中,我需要导入Darwin,因为我没有导入其他任何内容。
DonnaLea 2015年

SoliQuiD:除了省略arc4之后的多余下划线,即arc4random_uniform(5)。
阿里·比德尔

3
警告已经证明 RC4或arc4 与纯随机值是有区别的。因此,尽管arc4(流密码)在密码学上听起来是安全的,但实际上并非如此。
Maarten Bodewes

2
@MaartenBodewes:不再与此答案直接相关,但是在macOS或BSD上,arc4random实际上并不使用RC4密码,尽管它的名称如此。它在macOS上使用系统提供的CSPRNG,在大多数BSD上使用ChaCha20。Swift的默认RNG(在此答案中使用)将其称为macOS上的实现细节,但在每个受支持的平台上使用适当的基础生成器。
斯蒂芬·佳能

496

使用arc4random_uniform(n)为0和n-1之间的随机整数。

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

将结果强制转换为Int,这样您就不必显式地输入vars为UInt32(这似乎是不迅速的)。


7
很简单。我喜欢。赞!但是实际的骰子没有0。在您的代码中,diceRoll可能是0。只是说...
MK Safi 2014年

61
是的,你真的想要Int(arc4random_uniform(6)+1)
Michael Dorst 2014年

5
概率= Int(arc4random_uniform(UInt32(total)))–我也不得不转换为UInt32
bshirley

4
让randomElementInArray = Int(arc4random_uniform(array.count))
cmario

如果您使用的值不是该类型,请不要忘记将参数n强制转换arc3random_uniform(n)为a UInt32(n)
Dave Fontenot

117

编辑: 更新为Swift 3.0

arc4random在Swift中可以很好地工作,但是基本函数仅限于32位整数类型(Int在iPhone 5S和现代Mac上为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)

它不会编译:var r = arc4random(UInt64)。请指教您在这里是什么意思?
Ossir '16

@Ossir对我来说编译很好...这意味着调用参数arc4random(在第一个代码块中定义)的参数UInt64为a Type
jstn

arc4random_buf(以及所有64位扩展名)是否遭受模偏差?
马修·希曼

模数偏差仅在您添加上限时起作用,因此不适用于arc4random_buf。这些扩展的目的是确切地arc4random_uniform执行除64位类型以外的操作(缓解模偏差)。
jstn

使用浮点函数时,如何在可能性范围内包括上限值?因此,可以说我将0.0做为下限,将1.0做为上限。按照这种逻辑,它将给我0.0到0.99999999。但是,我想将1.0包括在内。我该如何实现?
MobileMon

79

为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。

Swift版本<4.2

此方法将Int在给定的最小值和最大值之间生成一个随机值

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

61

我使用以下代码:

var k: Int = random() % 10;

20
您必须先调用srandom(UInt32(time(nil))),否则它将始终返回相同的数字序列
Hola Soy Edu Feliz Navidad 2015年

3
我在random()上阅读了两次苹果文档,但无法收集其用法...我希望他们只是在上面包含了一个简单的代码示例。“ random()函数使用非线性的加性反馈随机数生成器,它采用大小为31个长整数的默认表。它返回从0到(2 31)-1的连续伪随机数。该随机数生成器的周期非常大,大约为16 *((2 31)-1)。” ...非常感谢苹果...我一定会在下一篇论文中引用它。
nocarrier

1
random()有时会导致iPad突然崩溃。如果发生这种情况,请从上方使用arc4random_uniform(6)。如果使用random(),则可以通过在srandomdev()前面添加来创建更多随机值。
JackPearse

1
我收到了编译器错误消息:random is unavailable in Swift: Use arc4random instead.
Mike Keskinov

1
该解决方案具有模偏差:zuttobenkyou.wordpress.com/2012/10/18/…–
rgov

33

从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随机数的教程


1
感谢您的技巧,这是最好的答案之一。要使用这些功能,需要添加import GameplayKit。Swift 3将语法更改为GKRandomSource.sharedRandom().nextInt(upperBound: 6)
petrsyn

7
这个工具包要导入多重?我不想夸大我的代码。
μολὼν.λαβέ

25

您可以像在C中一样进行操作:

let randomNumber = arc4random()

randomNumber推断为类型UInt32(32位无符号整数)


12
附录:randarc4randomdrand48和朋友都是在Darwin模块。如果您要构建Cocoa,UIKit或Foundation应用程序,则已经为您导入了该应用程序,但是您需要import Darwin在操场上。
rickster 2014年

5
并且不要尝试将arc4random()的结果强制转换为Int -在64位平台上可以正常工作,但在32位平台上Ints是32位带符号的,因此您会得到意外的负数数字。这已经使一些人绊倒了,所以我想在这里提到它。
马特·吉布森

23

采用 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(),它会产生UInt320到2 ^ 32-1之间的值。因此,要在0和之间获得一个随机数x-1,可以将其除以x余数。换句话说,请使用余数运算符(%)

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

但是,这会产生轻微的模偏置(另请参见此处此处),因此arc4random_uniform()建议这样做。

往返 Int

通常,为了在和之间来回转换Int,可以执行以下操作UInt32

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

但问题是,Int有一系列的-2,147,483,648...2,147,483,64732个系统和一系列的-9,223,372,036,854,775,808...9,223,372,036,854,775,80764个系统。将此与的UInt32范围进行比较0...4,294,967,295。该UUInt32手段无符号

请考虑以下错误:

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

因此,您只需要确保输入参数在此UInt32范围内,并且也不需要超出该范围的输出即可。


19

例如,随机数介于10(0-9)之间;

import UIKit

let randomNumber = Int(arc4random_uniform(10))

非常简单的代码-简单而简短。


16

我已经能够用来rand()获取随机CInt。您可以使用以下方法将其设置为Int:

let myVar: Int = Int(rand())

您可以使用自己喜欢的C随机函数,并根据需要将其转换为Int值。


是的,否则类型转换可能是一件棘手的事情,让Int构造函数处理它确实很省力。
Kzrbill 2014年

4
请注意,如果在使用rand()之前不调用srand(...)(仅调用一次),则每次执行程序之间的数字顺序将始终完全相同。如果您不希望这样做,请使用arc4random()
SomeGuy 2014年

2
您还可以使用random(),它返回一个Int而不是UInt32-就像提到的@SomeGuy一样,srandom(arc4random())在使用它之前只需在任意位置调用一次,以确保它对于程序的每次执行都具有不同的随机种子。
brittlewis14年

1
谁能评论rand()vs arc4random_uniform()?
鲁宾·马丁内斯(Ruben Martinez)2014年

16

@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()

其他所有整数类都可以。


11

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)

9

这是一个很好地完成工作的库 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
    }
}

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

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

6

我想补充一下现有的答案,Swift书中的随机数生成器示例是线性同余生成器(LCG),它是一个受严格限制的示例,除了必须具备的琐碎示例(随机性不没关系。而且一个LCG不应该被用于密码的目的

arc4random()更好,可以用于大多数目的,但同样不应用于加密目的。

如果您想要某种可以保证密码安全的东西,请使用SecCopyRandomBytes()。请注意,如果您将某个随机数生成器构建到某个东西中,那么其他人可能最终会(误用)该数字生成器用于加密目的(例如密码,密钥或盐生成),那么无论您SecCopyRandomBytes()是否需要,都应考虑使用它。完全不需要。


6

Swift 4.2起

有一组新的API:

let randomIntFrom0To10 = Int.random(in: 0 ..< 10)
let randomDouble = Double.random(in: 1 ... 10)
  • 现在,所有数字类型都有采用的random(in:)方法range

  • 它返回在该范围内均匀分布的数字。


TL; DR

那么,“好的”旧方法有什么问题?

  1. 您必须使用导入的C API (它们在平台之间是不同的)

  2. 而且...

如果我告诉您随机不是那样怎么办?

如果使用arc4random() (计算余数)一样arc4random() % aNumber,则结果不会0和之间均匀分布aNumber。有一个问题叫做模偏差

模偏差

通常情况下,该函数生成之间的随机数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雨燕4.2


5

在某些Xcode版本中没有arc4Random_uniform()(在7.1中可以运行,但对我而言不会自动完成)。您可以改为这样做。

生成0-5之间的随机数。第一

import GameplayKit

然后

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

5
var randomNumber = Int(arc4random_uniform(UInt32(5)))

这里5将确保随机数是从零到四生成的。您可以相应地设置值。


1
如果传递5,它将从零到4返回5种可能的结果。0 ... 4
Leo Dabus

3

斯威夫特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。

更多@ cn.pngtree.com


2

以下代码将产生一个介于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将使用多少s来创建随机数。最后一行将转换[UInt8]UInt16UInt32

我不知道后两个是否仍算是真正随机的,但是您可以根据自己的喜好调整它:)


为此,您巧妙地避免了由模+1引入的偏差。您可能会警告读者为什么这么做。
jww

很有意思,我并不是真的认为模偏差可能在这里起作用。可能获得少量的机会与获得大量的机会并不相同。
Zyphrax


1

雨燕4.2,Xcode的10.1

对于iOS,macOS和tvOS,您可以在Xcode的框架中使用系统范围的随机源GameKit。在这里,您可以找到GKRandomSource带有其sharedRandom()类方法的类:

import GameKit

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

func randomGenerator() -> Int {
    let random = GKRandomSource.sharedRandom().nextInt(upperBound: number.count)
    return number[random]
}
randomGenerator()

或仅使用randomElement()返回集合的随机元素的方法:

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

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

0

您可以这样使用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())

3
斐波那契如何随机?
Nikolai Ruhe

嗨,尼古拉,这个代码块是旧版本的Swift 1.2。如果您尝试新的Swift 2.0。这不会工作。
Durul Dalkanat 2015年

2
我了解,但对我来说,它仍然像是斐波那契生成器,而不是问题中要求的随机数。
Nikolai Ruhe 2015年

0

我使用以下代码生成一个随机数:

//
//  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]
    }
}

2
欢迎来到SO。不建议使用仅代码的答案-请编辑您的答案以解释该代码为何回答问题以及其工作方式。有关更多信息,请参见stackoverflow.com/help/how-to-answer。
蒂姆·马隆

2
请围绕您的答案提供一些背景信息,并欢迎您使用stackoverflow。:)
乔纳森·尤斯塔斯

0

细节

xCode 9.1,Swift 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
}

溶液的使用(1)

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

溶液的使用(2)

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

样品结果

在此处输入图片说明


2
这个答案是题外话。问题是如何生成一个随机数。不是如何制作一个随机数库。嘘。
安德鲁·保罗·西蒙斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.