Zdaję sobie sprawę, że książka Swift zapewnia implementację generatora liczb losowych. Czy najlepszą praktyką jest kopiowanie i wklejanie tej implementacji we własnym programie? A może jest biblioteka, z której możemy teraz korzystać?
Zdaję sobie sprawę, że książka Swift zapewnia implementację generatora liczb losowych. Czy najlepszą praktyką jest kopiowanie i wklejanie tej implementacji we własnym programie? A może jest biblioteka, z której możemy teraz korzystać?
Odpowiedzi:
Swift 4.2+
Swift 4.2 dostarczany z Xcode 10 wprowadza nowe łatwe w użyciu losowe funkcje dla wielu typów danych. Możesz wywołać tę random()
metodę dla typów numerycznych.
let randomInt = Int.random(in: 0..<6)
let randomDouble = Double.random(in: 2.71828...3.14159)
let randomBool = Bool.random()
Użyj arc4random_uniform(n)
dla losowej liczby całkowitej od 0 do n-1.
let diceRoll = Int(arc4random_uniform(6) + 1)
Rzuć wynik na Int, abyś nie musiał jawnie wpisywać swoich warów jako UInt32
(co wydaje się nie-Swifty).
0
. W twoim kodzie diceRoll
może być 0
. Tylko mówię ...
Int(arc4random_uniform(6)+1)
.
arc3random_uniform(n)
a, UInt32(n)
jeśli używasz wartości, która nie jest jeszcze tego typu.
Edycja: Zaktualizowano dla Swift 3.0
arc4random
działa dobrze w Swift, ale podstawowe funkcje są ograniczone do 32-bitowych liczb całkowitych ( Int
64-bit na iPhone 5S i współczesnych komputerach Mac). Oto ogólna funkcja dla losowej liczby typu wyrażanego literałami całkowitymi:
public func arc4random<T: ExpressibleByIntegerLiteral>(_ type: T.Type) -> T {
var r: T = 0
arc4random_buf(&r, MemoryLayout<T>.size)
return r
}
Możemy użyć tej nowej funkcji ogólnej do rozszerzenia UInt64
, dodania argumentów granicznych i złagodzenia błędu modulo. (To jest podnoszone prosto z 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
}
}
Dzięki temu możemy przedłużyć Int64
o te same argumenty, zajmując się przepełnieniem:
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
}
}
}
Aby uzupełnić rodzinę ...
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
}
}
}
Po tym wszystkim możemy wreszcie zrobić coś takiego:
let diceRoll = UInt64.random(lower: 1, upper: 7)
var r = arc4random(UInt64)
. Proszę o poradę, co miałeś na myśli?
arc4random
(zdefiniowanej w pierwszym bloku kodu) z argumentem, UInt64
który jest Type
.
arc4random_buf
. Celem tych rozszerzeń jest robienie dokładnie tego, co arc4random_uniform
robi (łagodzenie błędu modulo), z wyjątkiem typów 64-bitowych.
Edycja dla Swift 4.2
Począwszy od Swift 4.2, zamiast korzystać z importowanej funkcji C arc4random_uniform (), możesz teraz używać własnych funkcji natywnych Swift.
// Generates integers starting with 0 up to, and including, 10
Int.random(in: 0 ... 10)
Możesz użyć random(in:)
aby uzyskać losowe wartości również dla innych pierwotnych wartości; takie jak Int, Double, Float, a nawet Bool.
Szybkie wersje <4.2
Ta metoda wygeneruje losową Int
wartość między podanym minimum a maksimum
func randomInt(min: Int, max: Int) -> Int {
return min + Int(arc4random_uniform(UInt32(max - min + 1)))
}
Użyłem tego kodu:
var k: Int = random() % 10;
random is unavailable in Swift: Use arc4random instead.
Począwszy od iOS 9, możesz używać nowych klas GameplayKit do generowania liczb losowych na różne sposoby.
Masz do wyboru cztery typy źródeł: ogólne losowe źródło (bez nazwy, aż do systemu, który wybiera, co robi), liniowy congruential, ARC4 i Mersenne Twister. Mogą generować losowe liczby całkowite, zmiennoprzecinkowe i boole.
Na najprostszym poziomie możesz wygenerować losową liczbę z wbudowanego losowego źródła systemu, jak to:
GKRandomSource.sharedRandom().nextInt()
To generuje liczbę między -2 147 483 648 a 2 147 483 647. Jeśli chcesz liczbę z zakresu od 0 do górnej granicy (wyłącznie), użyj tego:
GKRandomSource.sharedRandom().nextIntWithUpperBound(6)
GameplayKit ma wbudowane kilka wygodnych konstruktorów do pracy z kostkami. Na przykład możesz rzucić sześciościenną kostką w następujący sposób:
let d6 = GKRandomDistribution.d6()
d6.nextInt()
Ponadto możesz kształtować losowy rozkład, używając rzeczy takich jak GKShuffledDistribution. To wymaga trochę więcej wyjaśnienia, ale jeśli jesteś zainteresowany, możesz przeczytać mój poradnik na temat losowych liczb GameplayKit .
import GameplayKit
. Swift 3 zmienił składnię naGKRandomSource.sharedRandom().nextInt(upperBound: 6)
Możesz to zrobić w taki sam sposób, jak w C:
let randomNumber = arc4random()
randomNumber
jest wywnioskowane, że jest typu UInt32
(32-bitowa liczba całkowita bez znaku)
rand
, arc4random
, drand48
i przyjaciele są w Darwin
module. Jest już zaimportowany, jeśli budujesz aplikację Cocoa, UIKit lub Foundation, ale musisz to zrobić import Darwin
na placach zabaw.
arc4random_uniform()
Stosowanie:
arc4random_uniform(someNumber: UInt32) -> UInt32
Daje to losowe liczby całkowite z zakresu 0
do someNumber - 1
.
Maksymalna wartość UInt32
wynosi 4 294 967 295 (to znaczy, 2^32 - 1
).
Przykłady:
Rzut monetą
let flip = arc4random_uniform(2) // 0 or 1
Rzut kostką
let roll = arc4random_uniform(6) + 1 // 1...6
Losowy dzień w październiku
let day = arc4random_uniform(31) + 1 // 1...31
Losowy rok w latach 90
let year = 1990 + arc4random_uniform(10)
Ogólna forma:
let number = min + arc4random_uniform(max - min + 1)
gdzie number
, max
i min
są UInt32
.
arc4random ()
Możesz także uzyskać losową liczbę, używając arc4random()
, co daje UInt32
od 0 do 2 ^ 32-1. Tak więc, aby uzyskać losową liczbę między 0
i x-1
, możesz ją podzielić przez x
i wziąć resztę. Innymi słowy, użyj Remainder Operator (%) :
let number = arc4random() % 5 // 0...4
Powoduje to jednak nieznaczne odchylenie modulo (patrz także tutaj i tutaj ), dlatego arc4random_uniform()
jest zalecane.
Konwersja do i z Int
Zwykle dobrze byłoby zrobić coś takiego, aby przekonwertować między Int
i UInt32
:
let number: Int = 10
let random = Int(arc4random_uniform(UInt32(number)))
Problem polega jednak na tym, że Int
ma on zasięg w -2,147,483,648...2,147,483,647
systemach 32-bitowych i zakres w -9,223,372,036,854,775,808...9,223,372,036,854,775,807
systemach 64-bitowych. Porównaj to z UInt32
zakresem 0...4,294,967,295
. U
Z UInt32
pomocą unsigned .
Rozważ następujące błędy:
UInt32(-1) // negative numbers cause integer overflow error
UInt32(4294967296) // numbers greater than 4,294,967,295 cause integer overflow error
Musisz tylko upewnić się, że parametry wejściowe mieszczą się w UInt32
zakresie i że nie potrzebujesz również wyjścia, które jest poza tym zakresem.
Byłem w stanie po prostu użyć rand()
losowego CInt. Możesz zrobić z tego Int, używając czegoś takiego:
let myVar: Int = Int(rand())
Możesz użyć swojej ulubionej funkcji losowej C. W razie potrzeby wystarczy przekonwertować wartość na Int.
random()
, który zwraca Int
raczej niż UInt32
- i jak wspomniano @SomeGuy, po prostu zadzwoń srandom(arc4random())
raz w dowolnym miejscu przed użyciem, aby upewnić się, że ma on inne, losowe źródło dla każdego wykonania twojego programu.
Odpowiedź @ jstn jest dobra, ale nieco gadatliwa. Swift jest znany jako język zorientowany na protokół, dzięki czemu możemy osiągnąć ten sam wynik bez konieczności implementowania kodu typu Booster dla każdej klasy w rodzinie liczb całkowitych, dodając domyślną implementację rozszerzenia protokołu.
public extension ExpressibleByIntegerLiteral {
public static func arc4random() -> Self {
var r: Self = 0
arc4random_buf(&r, MemoryLayout<Self>.size)
return r
}
}
Teraz możemy zrobić:
let i = Int.arc4random()
let j = UInt32.arc4random()
i wszystkie inne klasy całkowite są w porządku.
W Swift 4.2 możesz generować losowe liczby, wywołując random()
metodę na dowolnym typie liczbowym, podając zakres, z którym chcesz pracować. Na przykład generuje losową liczbę z zakresu od 1 do 9, włącznie po obu stronach
let randInt = Int.random(in: 1..<10)
Również z innymi typami
let randFloat = Float.random(in: 1..<20)
let randDouble = Double.random(in: 1...30)
let randCGFloat = CGFloat.random(in: 1...40)
Oto biblioteka, która dobrze wykonuje to zadanie 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);
}
Chciałbym dodać do istniejących odpowiedzi, że przykład generatora liczb losowych w książce Swift to Generator liniowej zbieżności (LCG), jest on mocno ograniczony i nie powinien być wyjątkiem trywialnych przykładów, w których jakość losowości nie to nie ma znaczenia. I LCG nigdy nie powinien być używany do celów kryptograficznych .
arc4random()
jest znacznie lepszy i może być używany do większości celów, ale ponownie nie powinien być wykorzystywany do celów kryptograficznych.
Jeśli chcesz mieć coś, co gwarantuje bezpieczeństwo kryptograficzne, użyj SecCopyRandomBytes()
. Zauważ, że jeśli w coś wbudujesz generator liczb losowych, ktoś inny może (nie) użyć go do celów kryptograficznych (takich jak generowanie hasła, klucza lub soli), powinieneś rozważyć użycie go SecCopyRandomBytes()
, nawet jeśli twoja potrzeba tego nie robi. całkiem tego wymaga.
Istnieje nowy zestaw interfejsów API:
let randomIntFrom0To10 = Int.random(in: 0 ..< 10)
let randomDouble = Double.random(in: 1 ... 10)
Wszystkie typy numeryczne mają teraz wybraną random(in:)
metodę range
.
Zwraca liczbę równomiernie rozmieszczoną w tym zakresie.
TL; DR
Musisz użyć importowanych interfejsów API C (różnią się one między platformami) .
A ponadto ...
Co jeśli powiem ci, że los nie jest taki losowy?
Jeśli używasz arc4random()
(aby obliczyć resztę) jak arc4random() % aNumber
, wynik nie jest równomiernie rozłożone pomiędzy 0
i aNumber
. Istnieje problem zwany stronniczością Modulo .
Odchylenie modulo
Zwykle funkcja generuje losową liczbę między 0
i MAX (zależy od typu itp . ) . Aby zrobić szybki, łatwy przykład, załóżmy, że maksymalna liczba to 7
i zależy Ci na losowej liczbie w zakresie 0 ..< 2
(lub odstępie [0, 3), jeśli wolisz) .
Te prawdopodobieństwa dla poszczególnych numerów są:
Innymi słowy, bardziej prawdopodobne jest, że skończysz z 0 lub 1 niż 2 . Oczywiście należy pamiętać, że jest to bardzo uproszczone, a liczba MAX jest znacznie wyższa, dzięki czemu jest bardziej „sprawiedliwa”.
Ten problem rozwiązano w SE-0202 - Losowe zjednoczenie w Swift 4.2
Bez arc4Random_uniform () w niektórych wersjach Xcode (w 7.1 działa, ale nie wykonuje autouzupełniania). Możesz to zrobić zamiast tego.
Aby wygenerować losową liczbę od 0-5. Pierwszy
import GameplayKit
Następnie
let diceRoll = GKRandomSource.sharedRandom().nextIntWithUpperBound(6)
var randomNumber = Int(arc4random_uniform(UInt32(5)))
Tutaj 5 upewni się, że liczba losowa zostanie wygenerowana od zera do czterech. Możesz odpowiednio ustawić wartość.
Szybki 4.2
Pa pa, aby zaimportować libellę 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()
Poniższy kod wygeneruje bezpieczną liczbę losową z przedziału od 0 do 255:
extension UInt8 {
public static var random: UInt8 {
var number: UInt8 = 0
_ = SecRandomCopyBytes(kSecRandomDefault, 1, &number)
return number
}
}
Nazywasz to tak:
print(UInt8.random)
W przypadku większych liczb staje się to bardziej skomplikowane.
To najlepsze, co mogłem wymyślić:
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) }
}
}
W tych metodach używa się dodatkowej liczby losowej, aby określić, ile UInt8
s będzie używanych do utworzenia liczby losowej. Ostatnia linia zamienia [UInt8]
się UInt16
lubUInt32
.
Nie wiem, czy dwa ostatnie nadal liczą się jako naprawdę losowe, ale możesz je dostosować do własnych upodobań :)
Szybki 4.2
Swift 4.2 zawiera natywny i dość w pełni funkcjonalny interfejs API liczb losowych w standardowej bibliotece. ( Propozycja Swift Evolution SE-0202 )
let intBetween0to9 = Int.random(in: 0...9)
let doubleBetween0to1 = Double.random(in: 0...1)
Wszystkie typy liczb mają statyczne losowe (w :), które przyjmuje zakres i zwraca liczbę losową z podanego zakresu
Swift 4.2, Xcode 10.1 .
W systemach iOS, macOS i tvOS można używać losowego źródła systemowego w środowisku Xcode GameKit
. Tutaj możesz znaleźć GKRandomSource
klasę za pomocą jej sharedRandom()
metody klasowej:
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()
Lub po prostu użyj randomElement()
metody, która zwraca losowy element kolekcji:
let number: [Int] = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let randomNumber = number.randomElement()!
print(randomNumber)
Możesz użyć w GeneratorOf
ten sposób:
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())
Używam tego kodu, aby wygenerować losową liczbę:
//
// 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]
Nie zapomnij dodać tutaj kodu rozwiązania zorientowanego na matematykę (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]
Nie zapomnij dodać tutaj kodów rozwiązania (1) i rozwiązania (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]
}