patternswiftMinor
Unique random number generation in Swift
Viewed 0 times
randomuniqueswiftnumbergeneration
Problem
In the process of learning Swift, I'm building a lotto numbers generator that generates a random number and appends it into an array, if the number already exists then it should re-generate a random numbers and append that one to the array.
I've come up with this. I want to be able to have a reusable class where I can decide how many numbers I want it to generate.
How would you guys solve this in a simpler way?
I've come up with this. I want to be able to have a reusable class where I can decide how many numbers I want it to generate.
How would you guys solve this in a simpler way?
class EuroMillions {
var randomNumber:Int = 0
var numberArray = [Int]()
func generateRandomNumber (numbers: Int, repetitions: Int) -> (Int, Int, Int) {
for number in 1...6 {
randomNumber = Int(arc4random_uniform(UInt32(numbers)))
checkArray(numbers: numbers)
}
return (randomNumber, numbers, repetitions)
}
func checkArray(numbers: Int) {
if numberArray.contains(randomNumber){
//re-generate
print("Number \(randomNumber) exists!")
randomNumber = Int(arc4random_uniform(UInt32(numbers)))
checkArray(numbers: numbers)
print("Replacing it with \(randomNumber)")
} else {
numberArray.append(randomNumber)
}
}
}
}
let firstNumbers = EuroMillions()
firstNumbers.generateRandomNumber(numbers: 50, repetitions: 6)
let finalNumbers = firstNumbers.numberArray
print(finalNumbers)Solution
Your code made me think of a few improvements I could made: the data workflow is not easy to follow and this complexity is not necessary for this task, and this is what will change in my version. Other than that, I'm just proposing general improvements.
Let's start by listing what we need:
-
a dedicated class
-
generate x times a random number
-
each random number must be unique
-
each random number has a max value
For this, I start by making a class template:
We have what we need: parameters for how many random numbers to generate and a maximum value.
Now how do we populate the array?
First we need to ensure that the maxValue is more or equal the number of repetitions, otherwise the task is not achievable with unique random numbers:
We also need to actually generate a random number:
This will generate a number between 0 and maxValue, the latter included. If you don't want the maxValue being inclusive, remove the
We also need to check if the generated number is not already in our array.
For this, I've chosen to use a
We loop x number of times (number of iterations = number of numbers to get). Since we don't need the index we discard it with
We prepare an Int, and populate this Int with a random value in a while loop.
The
If the number is not in the array, we break out of the inner loop and add it. If already present we continue generating until we have one that isn't in the array.
Another security check is to set the array to empty before generating, just in case the user would use our method several consecutive times (we could also avoid that by returning the result directly instead of populating an array property, and we'll see that later).
Complete code:
Usage:
Possible result:
[7, 12, 0, 9, 24, 35]
This was my main answer to your question.
How could we improve this class a bit more, or have fun, or both?
I would add the possibility to choose different lotteries, for example, and have defaults for them (methods prepared with preset values).
We also should set access privileges to methods and properties.
And I also like to have a printable identifier for the class.
For the identifier I like to use
And for the different lottery modes I would use an enum:
Now we make
I also make the array private so that it can't b
Let's start by listing what we need:
-
a dedicated class
-
generate x times a random number
-
each random number must be unique
-
each random number has a max value
For this, I start by making a class template:
class EuroMillions {
var numbers: [Int] = []
func generateNumbers(repetitions: Int, maxValue: Int) -> [Int] {
// here, generate a random number x times
// then populate the 'numbers' array and return it
return numbers
}
}We have what we need: parameters for how many random numbers to generate and a maximum value.
Now how do we populate the array?
First we need to ensure that the maxValue is more or equal the number of repetitions, otherwise the task is not achievable with unique random numbers:
func generateNumbers(repetitions: Int, maxValue: Int) -> [Int] {
guard maxValue >= repetitions else {
fatalError("maxValue must be >= repetitions")
}
// work to do here
return numbers
}We also need to actually generate a random number:
private func random(maxValue: Int) -> Int {
return Int(arc4random_uniform(UInt32(maxValue + 1)))
}This will generate a number between 0 and maxValue, the latter included. If you don't want the maxValue being inclusive, remove the
+ 1.We also need to check if the generated number is not already in our array.
For this, I've chosen to use a
repeat while loop inside the counting loop and to use contains for checking the array:for _ in 1...repetitions {
var n: Int
repeat {
n = random(maxValue: maxValue)
} while numbers.contains(n)
numbers.append(n)
}We loop x number of times (number of iterations = number of numbers to get). Since we don't need the index we discard it with
_.We prepare an Int, and populate this Int with a random value in a while loop.
The
repeat loop generates a number then checks, in the while, if this value is already in the array with .contains. If the number is not in the array, we break out of the inner loop and add it. If already present we continue generating until we have one that isn't in the array.
Another security check is to set the array to empty before generating, just in case the user would use our method several consecutive times (we could also avoid that by returning the result directly instead of populating an array property, and we'll see that later).
Complete code:
class EuroMillions {
var numbers: [Int] = []
func generateNumbers(repetitions: Int, maxValue: Int) -> [Int] {
guard maxValue >= repetitions else {
fatalError("maxValue must be >= repetitions for the numbers to be unique")
}
numbers = []
for _ in 1...repetitions {
var n: Int
repeat {
n = random(maxValue: maxValue)
} while numbers.contains(n)
numbers.append(n)
}
return numbers
}
private func random(maxValue: Int) -> Int {
return Int(arc4random_uniform(UInt32(maxValue + 1)))
}
}Usage:
let lottery = EuroMillions()
let numbers = lottery.generateNumbers(repetitions: 6, maxValue: 49)
print(numbers)Possible result:
[7, 12, 0, 9, 24, 35]
This was my main answer to your question.
How could we improve this class a bit more, or have fun, or both?
I would add the possibility to choose different lotteries, for example, and have defaults for them (methods prepared with preset values).
We also should set access privileges to methods and properties.
And I also like to have a printable identifier for the class.
For the identifier I like to use
CustomStringConvertible which makes the class printable by adding a description:class EuroMillions: CustomStringConvertible {
// ...
public var description: String {
// return something relevant here
}
}And for the different lottery modes I would use an enum:
public enum LotteryMode: String {
case euroMillions = "Euro Millions"
case lotto = "Lotto"
}
public class Lottery {
private var mode: LotteryMode
public init(mode: LotteryMode = .euroMillions) {
self.mode = mode
}
public func generate() -> [Int] {
switch mode {
case .euroMillions:
return generateNumbers(repetitions: 5, maxValue: 49)
case .lotto:
return generateNumbers(repetitions: 6, maxValue: 49)
}
}
// ...
}Now we make
generateNumbers(repetitions:, maxValue:) private because in this version of the class we only use the new preset method generate(), but this is not mandatory, it's just an example.I also make the array private so that it can't b
Code Snippets
class EuroMillions {
var numbers: [Int] = []
func generateNumbers(repetitions: Int, maxValue: Int) -> [Int] {
// here, generate a random number x times
// then populate the 'numbers' array and return it
return numbers
}
}func generateNumbers(repetitions: Int, maxValue: Int) -> [Int] {
guard maxValue >= repetitions else {
fatalError("maxValue must be >= repetitions")
}
// work to do here
return numbers
}private func random(maxValue: Int) -> Int {
return Int(arc4random_uniform(UInt32(maxValue + 1)))
}for _ in 1...repetitions {
var n: Int
repeat {
n = random(maxValue: maxValue)
} while numbers.contains(n)
numbers.append(n)
}class EuroMillions {
var numbers: [Int] = []
func generateNumbers(repetitions: Int, maxValue: Int) -> [Int] {
guard maxValue >= repetitions else {
fatalError("maxValue must be >= repetitions for the numbers to be unique")
}
numbers = []
for _ in 1...repetitions {
var n: Int
repeat {
n = random(maxValue: maxValue)
} while numbers.contains(n)
numbers.append(n)
}
return numbers
}
private func random(maxValue: Int) -> Int {
return Int(arc4random_uniform(UInt32(maxValue + 1)))
}
}Context
StackExchange Code Review Q#146149, answer score: 5
Revisions (0)
No revisions yet.