firephil
firephil

Reputation: 865

Scala : simulate Lotto number generator in Range 1 to 45

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

Answers (4)

Andreas Neumann
Andreas Neumann

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

Brian
Brian

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

Justin Pihony
Justin Pihony

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

Michael Zajac
Michael Zajac

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

Related Questions