Reputation: 865
The following is an imperative solution to a simulation of a Number Lottery from the Range 1 to 45, each time we generate a number n1 the number is removed from the set of possible numbers.
Is it possible to achieve the same in a more functional way ? i.e using map,filter etc
def getNumbers :Array[Int] = {
val range = 1 to 45
var set = range.toSet
var resultSet:Set[Int] = Set()
var current: Int = 0
while(resultSet.size < 5 ){
current = Random.shuffle(set).head // pick the head of the shuffled set
set -= current
resultSet += current
}
resultSet.toArray
}
"edit"
An example pick 3 numbers from the Range 1 to 5
Original Set is {1,2,3,4,5}
{1,2,3,4,5} shuffle(1) picked at random 3
{1,2,4,5} shuffle(2) picked at random 2
{1,4,5} shuffle(3) picked at random 4
original Set becomes {1,5}
numbers picked {3,2,4}
each shuffle randomizes a different SET ! => different probabilities
I would like to see a functional "method" with 5 shuffles not 1 shuffle !
Upvotes: 2
Views: 648
Reputation: 10904
This would simulate the behaviour you wanted:
Draw one, reshuffle data left, draw one and so on.
import scala.util.Random
import scala.annotation.tailrec
def draw(count: Int, data: Set[Int]): Set[Int] = {
@tailrec
def drawRec( accum: Set[Int] ) : Set[Int] =
if (accum.size == count )
accum
else
drawRec( accum + Random.shuffle( (data -- accum).toList ).head )
drawRec( Set() )
}
Example
scala> draw(5, (1 to 45).toSet)
res15: Set[Int] = Set(1, 41, 45, 17, 22)
scala> draw(5, (1 to 45).toSet)
res16: Set[Int] = Set(5, 24, 1, 6, 28)
Upvotes: 0
Reputation: 20285
I prefer @m-z's solution as well and agree with his reasoning about the probability. Reading the source for Random.shuffle
is probably a worth while exercise.
Each iteration removes an element from range
and adds it to the accumulator acc
which is similar to your imperative approach except that we are not mutating collections and counters.
import scala.util.Random._
import scala.annotation.tailrec
def getNumbers(): Array[Int] = {
val range = (1 to 45).toSeq
@tailrec
def getNumbersR(range: Seq[Int], acc: Array[Int], i: Int): Array[Int] = (i, range(nextInt(range.size))) match{
case (i, x) if i < 5 => getNumbersR(range.filter(_ != x), x +: acc, i + 1)
case (i, x) => acc
}
getNumbersR(range, Array[Int](), 0)
}
scala> getNumbers
res78: Array[Int] = Array(4, 36, 41, 20, 14)
Upvotes: 0
Reputation: 67115
I am with m-z on this, however if you REALLY want the functional form of a constant reshuffle, then you want something like this:
import scala.util.Random
import scala.annotation.tailrec
val initialSet = 1 to 45
def lottery(initialSet: Seq[Int], numbersPicked: Int): Set[Int] = {
@tailrec
def internalTailRec(setToUse: Seq[Int], picksLeft: Int, selection: Set[Int]):Set[Int]= {
if(picksLeft == 0) selection
else {
val selected = Random.shuffle(setToUse).head
internalTailRec(setToUse.filter(_ != selected), picksLeft - 1, selection ++ Set(selected))
}
}
internalTailRec(initialSet, numbersPicked, Set())
}
lottery(initialSet, 5)
Upvotes: 1
Reputation: 55569
Sure, it's possible. The collections API has everything you need. What you're looking for is take
, which will take the first n
elements of the collection, or as many elements as the collection has if there are less than n
.
Random.shuffle(1 to 45).take(5).toArray
Upvotes: 7