Tôi nhận ra cuốn sách Swift cung cấp một triển khai của trình tạo số ngẫu nhiên. Là cách thực hành tốt nhất để sao chép và dán triển khai này trong chương trình của chính mình? Hoặc có một thư viện làm điều này mà chúng ta có thể sử dụng bây giờ?
Tôi nhận ra cuốn sách Swift cung cấp một triển khai của trình tạo số ngẫu nhiên. Là cách thực hành tốt nhất để sao chép và dán triển khai này trong chương trình của chính mình? Hoặc có một thư viện làm điều này mà chúng ta có thể sử dụng bây giờ?
Câu trả lời:
Swift 4.2+
Swift 4.2 được vận chuyển với Xcode 10 giới thiệu các hàm ngẫu nhiên mới dễ sử dụng cho nhiều loại dữ liệu. Bạn có thể gọi random()
phương thức trên các loại số.
let randomInt = Int.random(in: 0..<6)
let randomDouble = Double.random(in: 2.71828...3.14159)
let randomBool = Bool.random()
Sử dụng arc4random_uniform(n)
cho một số nguyên ngẫu nhiên trong khoảng từ 0 đến n-1.
let diceRoll = Int(arc4random_uniform(6) + 1)
Truyền kết quả cho Int để bạn không phải gõ rõ ràng các vars của mình là UInt32
(có vẻ không phải là Swifty).
0
. Trong mã của bạn, diceRoll
có thể được 0
. Chỉ cần nói ...
Int(arc4random_uniform(6)+1)
.
arc3random_uniform(n)
một UInt32(n)
nếu bạn đang sử dụng một giá trị không phải là đã của loại đó.
Chỉnh sửa: Đã cập nhật cho Swift 3.0
arc4random
hoạt động tốt trong Swift, nhưng các chức năng cơ bản được giới hạn ở các loại số nguyên 32 bit ( Int
là 64 bit trên iPhone 5S và máy Mac hiện đại). Đây là một hàm chung cho một số ngẫu nhiên của một loại có thể biểu thị bằng một số nguyên:
public func arc4random<T: ExpressibleByIntegerLiteral>(_ type: T.Type) -> T {
var r: T = 0
arc4random_buf(&r, MemoryLayout<T>.size)
return r
}
Chúng ta có thể sử dụng hàm chung mới này để mở rộng UInt64
, thêm các đối số biên và giảm thiểu sai lệch modulo. (Điều này được nâng thẳng từ 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
}
}
Với điều đó, chúng ta có thể mở rộng Int64
cho các đối số tương tự, xử lý tràn:
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
}
}
}
Để hoàn thành gia đình ...
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
}
}
}
Sau tất cả, cuối cùng chúng ta cũng có thể làm một cái gì đó như thế này:
let diceRoll = UInt64.random(lower: 1, upper: 7)
var r = arc4random(UInt64)
. Xin vui lòng tư vấn những gì bạn có ý nghĩa ở đây?
arc4random
(được xác định trong khối mã đầu tiên) với đối số UInt64
là a Type
.
arc4random_buf
. Mục đích của các phần mở rộng này là để thực hiện chính xác những gì arc4random_uniform
(giảm thiểu sai lệch modulo) ngoại trừ các loại 64 bit.
Chỉnh sửa cho Swift 4.2
Bắt đầu trong Swift 4.2, thay vì sử dụng hàm C đã nhập arc4random_uniform (), giờ đây bạn có thể sử dụng các hàm riêng của Swift.
// Generates integers starting with 0 up to, and including, 10
Int.random(in: 0 ... 10)
Bạn có thể dùng random(in:)
để có được các giá trị ngẫu nhiên cho các giá trị nguyên thủy khác; chẳng hạn như Int, Double, Float và thậm chí Bool.
Phiên bản Swift <4.2
Phương pháp này sẽ tạo ra một Int
giá trị ngẫu nhiên giữa tối thiểu và tối đa nhất định
func randomInt(min: Int, max: Int) -> Int {
return min + Int(arc4random_uniform(UInt32(max - min + 1)))
}
Tôi đã sử dụng mã này:
var k: Int = random() % 10;
random is unavailable in Swift: Use arc4random instead.
Kể từ iOS 9, bạn có thể sử dụng các lớp GameplayKit mới để tạo số ngẫu nhiên theo nhiều cách khác nhau.
Bạn có bốn loại nguồn để chọn: một nguồn ngẫu nhiên chung (chưa được đặt tên, xuống hệ thống để chọn những gì nó làm), đồng quy tuyến tính, ARC4 và Mersenne Twister. Chúng có thể tạo ra ints ngẫu nhiên, float và bools.
Ở cấp độ đơn giản nhất, bạn có thể tạo một số ngẫu nhiên từ nguồn ngẫu nhiên tích hợp sẵn của hệ thống như thế này:
GKRandomSource.sharedRandom().nextInt()
Điều đó tạo ra một số từ -2,147,483,648 đến 2,147,483,647. Nếu bạn muốn một số từ 0 đến giới hạn trên (độc quyền), bạn sẽ sử dụng số này:
GKRandomSource.sharedRandom().nextIntWithUpperBound(6)
GameplayKit có một số hàm tạo tiện lợi được tích hợp để hoạt động với xúc xắc. Ví dụ: bạn có thể lăn một cái chết sáu mặt như thế này:
let d6 = GKRandomDistribution.d6()
d6.nextInt()
Ngoài ra, bạn có thể định hình phân phối ngẫu nhiên bằng cách sử dụng những thứ như GKShuffledDistribution. Điều đó cần giải thích thêm một chút, nhưng nếu bạn quan tâm, bạn có thể đọc hướng dẫn của tôi về các số ngẫu nhiên GameplayKit .
import GameplayKit
. Swift 3 đã thay đổi cú pháp thànhGKRandomSource.sharedRandom().nextInt(upperBound: 6)
Bạn có thể làm điều đó giống như cách bạn làm trong C:
let randomNumber = arc4random()
randomNumber
được suy ra là loại UInt32
(số nguyên không dấu 32 bit)
rand
, arc4random
, drand48
và bạn bè là tất cả trong các Darwin
mô-đun. Nó đã được nhập cho bạn nếu bạn đang xây dựng ứng dụng Cacao, UIKit hoặc Foundation, nhưng bạn sẽ cần đến các import Darwin
sân chơi.
arc4random_uniform()
Sử dụng:
arc4random_uniform(someNumber: UInt32) -> UInt32
Điều này cung cấp cho bạn số nguyên ngẫu nhiên trong phạm vi 0
đểsomeNumber - 1
.
Giá trị tối đa UInt32
là 4.294.967.295 (nghĩa là 2^32 - 1
).
Ví dụ:
Đồng xu lật
let flip = arc4random_uniform(2) // 0 or 1
Cuộn xúc xắc
let roll = arc4random_uniform(6) + 1 // 1...6
Ngày ngẫu nhiên trong tháng 10
let day = arc4random_uniform(31) + 1 // 1...31
Năm ngẫu nhiên trong những năm 1990
let year = 1990 + arc4random_uniform(10)
Hình thức chung:
let number = min + arc4random_uniform(max - min + 1)
nơi number
, max
và min
làUInt32
.
arc4random ()
Bạn cũng có thể nhận được một số ngẫu nhiên bằng cách sử dụng arc4random()
, số này tạo ra UInt32
từ 0 đến 2 ^ 32-1. Do đó, để có được một số ngẫu nhiên giữa 0
và x-1
, bạn có thể chia nó cho x
và lấy phần còn lại. Hay nói cách khác, sử dụng Toán tử còn lại (%) :
let number = arc4random() % 5 // 0...4
Tuy nhiên, điều này tạo ra độ lệch modulo nhẹ (xem thêm ở đây và đây ), vì vậy đó là lý do arc4random_uniform()
được khuyến nghị.
Chuyển đổi sang và từ Int
Thông thường sẽ ổn khi làm một cái gì đó như thế này để chuyển đổi qua lại giữa Int
và UInt32
:
let number: Int = 10
let random = Int(arc4random_uniform(UInt32(number)))
Tuy nhiên, vấn đề là Int
có phạm vi -2,147,483,648...2,147,483,647
trên các hệ thống 32 bit và phạm vi -9,223,372,036,854,775,808...9,223,372,036,854,775,807
trên các hệ thống 64 bit. So sánh điều này với UInt32
phạm vi của 0...4,294,967,295
. Các U
các UInt32
phương tiện chưa được ký .
Hãy xem xét các lỗi sau:
UInt32(-1) // negative numbers cause integer overflow error
UInt32(4294967296) // numbers greater than 4,294,967,295 cause integer overflow error
Vì vậy, bạn chỉ cần chắc chắn rằng các tham số đầu vào của bạn nằm trong UInt32
phạm vi và bạn cũng không cần một đầu ra nằm ngoài phạm vi đó.
Tôi đã có thể chỉ sử dụng rand()
để có được một CInt ngẫu nhiên. Bạn có thể biến nó thành một Int bằng cách sử dụng một cái gì đó như thế này:
let myVar: Int = Int(rand())
Bạn có thể sử dụng hàm ngẫu nhiên C yêu thích của mình và chỉ cần chuyển đổi thành giá trị thành Int nếu cần.
random()
, trả về một Int
thay vì UInt32
- và như @SomeGuy đã đề cập, chỉ cần gọi srandom(arc4random())
một lần bất cứ nơi nào trước khi sử dụng để đảm bảo nó có một hạt giống ngẫu nhiên khác nhau cho mỗi lần thực hiện chương trình của bạn.
Câu trả lời của @ jstn là tốt, nhưng hơi dài dòng. Swift được biết đến như một ngôn ngữ hướng giao thức, vì vậy chúng ta có thể đạt được kết quả tương tự mà không phải triển khai mã soạn sẵn cho mọi lớp trong họ số nguyên, bằng cách thêm một triển khai mặc định cho phần mở rộng giao thức.
public extension ExpressibleByIntegerLiteral {
public static func arc4random() -> Self {
var r: Self = 0
arc4random_buf(&r, MemoryLayout<Self>.size)
return r
}
}
Bây giờ chúng ta có thể làm:
let i = Int.arc4random()
let j = UInt32.arc4random()
và tất cả các lớp số nguyên khác là ok.
Trong Swift 4.2, bạn có thể tạo các số ngẫu nhiên bằng cách gọi random()
phương thức trên bất kỳ loại số nào bạn muốn, cung cấp phạm vi bạn muốn làm việc. Ví dụ: điều này tạo ra một số ngẫu nhiên trong phạm vi từ 1 đến 9, bao gồm cả hai bên
let randInt = Int.random(in: 1..<10)
Ngoài ra với các loại khác
let randFloat = Float.random(in: 1..<20)
let randDouble = Double.random(in: 1...30)
let randCGFloat = CGFloat.random(in: 1...40)
Đây là một thư viện hoạt động tốt 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
}
}
let MAX : UInt32 = 9
let MIN : UInt32 = 1
func randomNumber()
{
var random_number = Int(arc4random_uniform(MAX) + MIN)
print ("random = ", random_number);
}
Tôi muốn thêm vào các câu trả lời hiện có rằng ví dụ về trình tạo số ngẫu nhiên trong sách Swift là Trình tạo công thức tuyến tính (LCG), đây là một câu hỏi bị giới hạn nghiêm trọng và không nên ngoại trừ các ví dụ tầm thường, trong đó chất lượng ngẫu nhiên không Không có vấn đề gì cả. Và LCG không bao giờ nên được sử dụng cho mục đích mật mã .
arc4random()
tốt hơn nhiều và có thể được sử dụng cho hầu hết các mục đích, nhưng một lần nữa không nên được sử dụng cho mục đích mật mã.
Nếu bạn muốn một cái gì đó được đảm bảo an toàn về mật mã, hãy sử dụng SecCopyRandomBytes()
. Lưu ý rằng nếu bạn xây dựng trình tạo số ngẫu nhiên thành một thứ gì đó, người khác có thể sẽ sử dụng nó cho mục đích mã hóa (như mật khẩu, khóa hoặc tạo muối), sau đó bạn vẫn nên cân nhắc sử dụng SecCopyRandomBytes()
, ngay cả khi nhu cầu của bạn không ' t khá yêu cầu điều đó.
Có một bộ API mới:
let randomIntFrom0To10 = Int.random(in: 0 ..< 10)
let randomDouble = Double.random(in: 1 ... 10)
Tất cả các loại số bây giờ có random(in:)
phương thức mất range
.
Nó trả về một số phân bố đồng đều trong phạm vi đó.
TL; DR
Bạn phải sử dụng API C đã nhập (Chúng khác nhau giữa các nền tảng) .
Và hơn thế nữa ...
Điều gì sẽ xảy ra nếu tôi nói với bạn rằng ngẫu nhiên không phải là ngẫu nhiên?
Nếu bạn sử dụng arc4random()
(để tính phần còn lại) như thế nào arc4random() % aNumber
, kết quả sẽ không được phân phối đồng đều giữa 0
và aNumber
. Có một vấn đề gọi là sai lệch Modulo .
Sai lệch modulo
Thông thường, hàm tạo một số ngẫu nhiên giữa 0
và MAX (tùy thuộc vào loại, v.v.) . Để làm một ví dụ nhanh chóng, dễ dàng, giả sử số tối đa là 7
và bạn quan tâm đến một số ngẫu nhiên trong phạm vi 0 ..< 2
(hoặc khoảng [0, 3) nếu bạn thích điều đó) .
Các xác suất cho số cá nhân bao gồm:
Nói cách khác, bạn có nhiều khả năng kết thúc bằng 0 hoặc 1 hơn 2 . Tất nhiên, hãy nhớ rằng điều này cực kỳ đơn giản và số MAX cao hơn nhiều, làm cho nó "công bằng" hơn.
Vấn đề này được giải quyết bởi SE-0202 - Hợp nhất ngẫu nhiên trong Swift 4.2
Không có arc4Random_uniform () trong một số phiên bản Xcode (trong 7.1, nó chạy nhưng không tự động hoàn thành đối với tôi). Bạn có thể làm điều này thay vào đó.
Để tạo một số ngẫu nhiên từ 0-5. Đầu tiên
import GameplayKit
Sau đó
let diceRoll = GKRandomSource.sharedRandom().nextIntWithUpperBound(6)
var randomNumber = Int(arc4random_uniform(UInt32(5)))
Ở đây 5 sẽ đảm bảo rằng số ngẫu nhiên được tạo từ 0 đến 4. Bạn có thể đặt giá trị phù hợp.
Swift 4.2
Tạm biệt để nhập Foundation C lib 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()
Đoạn mã sau sẽ tạo ra một số ngẫu nhiên an toàn trong khoảng từ 0 đến 255:
extension UInt8 {
public static var random: UInt8 {
var number: UInt8 = 0
_ = SecRandomCopyBytes(kSecRandomDefault, 1, &number)
return number
}
}
Bạn gọi nó như thế này:
print(UInt8.random)
Đối với số lượng lớn hơn, nó trở nên phức tạp hơn.
Đây là điều tốt nhất tôi có thể nghĩ ra:
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) }
}
}
Các phương pháp này sử dụng một số ngẫu nhiên bổ sung để xác định có bao nhiêu UInt8
s sẽ được sử dụng để tạo số ngẫu nhiên. Dòng cuối cùng chuyển đổi [UInt8]
thành UInt16
hoặc UInt32
.
Tôi không biết nếu hai người cuối cùng vẫn được coi là thực sự ngẫu nhiên, nhưng bạn có thể điều chỉnh nó theo ý thích của mình :)
Swift 4.2
Swift 4.2 đã bao gồm API số ngẫu nhiên gốc và khá đầy đủ tính năng trong thư viện chuẩn. ( Đề xuất tiến hóa Swift SE-0202 )
let intBetween0to9 = Int.random(in: 0...9)
let doubleBetween0to1 = Double.random(in: 0...1)
Tất cả các loại số có ngẫu nhiên tĩnh (trong :) lấy phạm vi và trả về số ngẫu nhiên trong phạm vi đã cho
Swift 4.2, Xcode 10.1 .
Đối với iOS, macOS và tvOS, bạn có thể sử dụng nguồn ngẫu nhiên trên toàn hệ thống trong khung của Xcode GameKit
. Ở đây bạn có thể tìm thấy GKRandomSource
lớp với sharedRandom()
phương thức lớp của nó :
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()
Hoặc chỉ sử dụng một randomElement()
phương thức trả về một phần tử ngẫu nhiên của bộ sưu tập:
let number: [Int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let randomNumber = number.randomElement()!
print(randomNumber)
Bạn có thể sử dụng GeneratorOf
như thế này:
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())
Tôi sử dụng mã này để tạo một số ngẫu nhiên:
//
// 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, Swift 4
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]
Đừng quên thêm mã giải pháp định hướng toán học (1) tại đây
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]
Đừng quên thêm mã giải pháp (1) và giải pháp (2) tại đây
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]
}