Как генерировать случайное число на языке Apple Swift?
Я понимаю, что книга Свифта предоставила реализацию генератора случайных чисел. Является ли наилучшей практикой копировать и вставлять эту реализацию в собственную программу? Или есть библиотека, которая делает это, что мы можем использовать сейчас?
26 ответов
Используйте стандартные библиотечные функции для высококачественных случайных чисел: arc4random()
или же arc4random_uniform()
так же, как в Objective-C.
Они в Darwin
модуль, так что если вы не импортировали AppKit
, UIKit
, или же Foundation
(который импортирует его для вас), вам нужно будет import Darwin
,
Swift 4.2
Swift 4.2, поставляемый с Xcode 10, представляет новые простые в использовании случайные функции для многих типов данных. Вы можете позвонить 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-битными целочисленными типами (Int
64-битный на iPhone 5S и современных Mac). Вот обобщенная функция для случайного числа типа, выражаемого целочисленным литералом:
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, вместо использования импортированной C-функции arc4random_uniform(), теперь вы можете использовать собственные встроенные функции Swift.
// 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)))
}
Начиная с iOS 9, вы можете использовать новые классы GameplayKit для генерации случайных чисел различными способами.
У вас есть четыре типа источников на выбор: общий случайный источник (без имени, вплоть до системы, чтобы выбрать, что он делает), линейный конгруэнтный, ARC4 и Mersenne Twister. Они могут генерировать случайные целые числа, числа с плавающей запятой и значения типа bools.
На простейшем уровне вы можете сгенерировать случайное число из встроенного в систему случайного источника, например:
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
Это дает вам случайные целые числа в диапазоне 0
в someNumber - 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)
где number
, max
, а также min
являются UInt32
,
Как насчет...
arc4random ()
Вы также можете получить случайное число с помощью arc4random()
, который производит UInt32
между 0 и 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,647
на 32-битных системах и ряде -9,223,372,036,854,775,808...9,223,372,036,854,775,807
на 64-битных системах. Сравните это с UInt32
диапазон 0...4,294,967,295
, U
из UInt32
означает без знака.
Рассмотрим следующие ошибки:
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))
Очень простой код - простой и короткий.
Ответ @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()
и все другие целочисленные классы в порядке.
Я был в состоянии просто использовать rand()
чтобы получить случайный CInt. Вы можете сделать это Int, используя что-то вроде этого:
let myVar: Int = Int(rand())
Вы можете использовать вашу любимую C-случайную функцию и просто преобразовать ее в значение Int, если это необходимо.
Swift 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)
В 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)
Начиная со Swift 4.2
Существует новый набор API:
let randomIntFrom0To10 = Int.random(in: 0 ..< 10)
let randomDouble = Double.random(in: 1 ... 10)
Все числовые типы теперь имеют
random(in:)
метод, который принимаетrange
,Возвращает число, равномерно распределенное в этом диапазоне.
TL; DR
Ну, что не так с "старым добрым" способом?
Вы должны использовать импортированныеC API-интерфейсы (они отличаются для разных платформ).
И более того...
Что если я скажу вам, что случайность не такая уж случайная?
Если вы используетеarc4random()
(чтобы рассчитать остаток) какarc4random() % aNumber
результатне распределяется равномерно между 0
а такжеaNumber
, Существует проблема, которая называется смещением по модулю.
Модульный уклон
Обычно функция генерирует случайное число между 0
и MAX (зависит от типа и т. д.). Чтобы сделать быстрый, простой пример, скажем, максимальное число 7
и вы заботитесь о случайном числе в диапазоне 0 ..< 2
(или интервал [0, 3), если вы предпочитаете это).
Вероятности для отдельных чисел:
- 0: 3/8 = 37,5%
- 1: 3/8 = 37,5%
- 2: 2/8 = 25%
Другими словами, у вас больше шансов получить 0 или 1, чем 2. Конечно, нужно помнить, что это чрезвычайно упрощено, а число MAX намного выше, что делает его более "справедливым".
Эта проблема решается с помощью SE-0202 - Случайное объединение в Swift 4.2
Вот библиотека, которая хорошо выполняет свою работу 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);
}
Я хотел бы добавить к существующим ответам, что пример генератора случайных чисел в книге Свифта - это Линейный генератор конгруэнтности (LCG), он строго ограничен и не должен быть исключением, за исключением простых тривиальных примеров, где качество случайности не не имеет значения вообще. И LCG никогда не должен использоваться в криптографических целях.
arc4random()
намного лучше и может использоваться для большинства целей, но опять же не должен использоваться в криптографических целях.
Если вы хотите что-то гарантированно криптографически безопасное, используйте SecCopyRandomBytes()
, Обратите внимание, что если вы встраиваете генератор случайных чисел во что-то, кто-то другой может (неправильно) использовать его для криптографических целей (таких как генерация пароля, ключа или соли), тогда вам следует рассмотреть возможность использования SecCopyRandomBytes()
в любом случае, даже если ваша потребность не требует этого.
var randomNumber = Int(arc4random_uniform(UInt32(**5**)))
Здесь 5 будет гарантировать, что случайное число генерируется от нуля до пяти. Вы можете установить значение соответственно.
Swift 4.2
Пока, чтобы импортировать Фонд 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()
- Вы используете random(in:) для генерации случайных цифр из диапазонов.
- randomElement () возвращает nil, если диапазон пуст, так что вы разворачиваете возвращенный Int? с, если позволено.
- Вы используете random(in:) для генерации случайного Double, Float или CGFloat и random () для возврата случайного Bool.
Без arc4Random_uniform() в некоторых версиях Xcode(в 7.1 он запускается, но не выполняет автозаполнение для меня). Вы можете сделать это вместо этого.
Для генерации случайного числа от 0-5. Первый
import GameplayKit
затем
let diceRoll = GKRandomSource.sharedRandom().nextIntWithUpperBound(6)
Следующий код создаст безопасное случайное число от 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]
в UInt16
или же UInt32
,
Я не знаю, считаются ли последние два действительно случайными, но вы можете настроить их по своему вкусу:)
Swift 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, быстрый 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)
Выход
подробности
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)
Не забудьте добавить код Math-ориентированного решения (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]
}
Пример результата
Я использую этот код для генерации случайного числа:
//
// 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]
}
}
Ты можешь использовать 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())