Scala FAQ: How to generate random numbers without duplicate values?

Scala FAQ: Using Scala, how do I generate random numbers without duplicate values, i.e., how do I generate a sequence of random, unique values?

Solution

To show the solution, here’s a Scala 3 function that generates a random sequence of unique integer values:

import scala.util.Random

def generateUniqueRandomNumbers(
    count: Int, 
    minInclusive: Int, 
    maxExclusive: Int
): Vector[Int] =
    require(maxExclusive - minInclusive + 1 >= count, "Range is too small to generate the desired number of unique random numbers")
    val random = Random()
    var numbers = Set.empty[Int]
    while
        numbers.size < count
    do
        numbers += random.nextInt((maxExclusive - minInclusive) + 1) + minInclusive
    numbers.toVector

// call the function and use it like this
val uniqueRandomNumbers = generateUniqueRandomNumbers(20, 1, 25)
println(uniqueRandomNumbers)

Keys to the solution

As shown in the code, the keys to this solution are:

  • Generate the random values using the Scala Random type.
  • Initially put the numbers in a Set; this is because a Set is a type that only contains unique values, which is what we want.
  • At the end of the function I convert the Set to a Vector, but this is optional. You can just return the Set[Int], if you prefer.

Also:

  • The require part of this solution is extremely important. If you don’t include it --- or something similar to it, like your own custom Exception --- you can create an infinite loop.

For example, this use works fine because the count is 5, and the minInclusive and maxExclusive values allow for the creation of five values:

generateUniqueRandomNumbers(5, 1, 5)   // result: Vector(5, 1, 2, 3, 4)

But if the count is larger than the range you’re trying to create, you’ll create an infinite loop without the require statement, as shown in the Scala REPL:

scala> generateUniqueRandomNumbers(10, 1, 5)
java.lang.IllegalArgumentException: requirement failed:
     Range is too small to generate the desired number of unique random numbers
     at scala.Predef$.require(Predef.scala:337)

Handling require’s exception

In idiomatic Scala code — i.e., using Scala best practices — our functions don’t throw exceptions, so wrapping this function in the Try constructor is one way to handle this situation:

import scala.util.{Random, Try}

def generateUniqueRandomNumbers(
    count: Int, 
    minInclusive: Int, 
    maxExclusive: Int
): Try[Vector[Int]] = Try {   //<-- i added Try here
    require(maxExclusive - minInclusive + 1 >= count, "Range is too small to generate the desired number of unique random numbers")
    val random = Random()
    var numbers = Set.empty[Int]
    while
        numbers.size < count
    do
        numbers += random.nextInt((maxExclusive - minInclusive) + 1) + minInclusive
    numbers.toVector
}

The Scala REPL shows how this works:

scala> generateUniqueRandomNumbers(5, 1, 10)
val res0: util.Try[Vector[Int]] = Success(Vector(1, 6, 2, 7, 3))

As you can see in the res0 value, this is the result of that function call:

Success(Vector(1, 6, 2, 7, 3))

Because we now have a Vector wrapped inside a Success value, you’ll want to handle that Success value with something like a match expression:

res0 match
    case Success(seq) => println(seq)
    case Failure(err) => System.err.println(err)

Related information

If you don’t know anything about handling values like Try, Option, or Either with match expressions, see these links: