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 aSet
is a type that only contains unique values, which is what we want. - At the end of the function I convert the
Set
to aVector
, but this is optional. You can just return theSet[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 customException
--- 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)
this post is sponsored by my books: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |
Related information
If you don’t know anything about handling values like Try
, Option
, or Either
with match
expressions, see these links: