HiveBrain v1.2.0
Get Started
← Back to all entries
patternswiftMinor

Unique random number generation in Swift

Submitted by: @import:stackexchange-codereview··
0
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?

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:

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.