Saturday, April 16, 2011

Scala Tinkering Day

One of my peers at work, and a great source of Scala tips, Dave Briccetti asks potential new hires to solve a fairly simple dice throwing problem.  He's written a blog post describing the problem.  I figured it was time to give it a try. 

My first attempt was fairly complicated, but worked:

package scalaplayground.dice

import scala.collection.mutable.MutableList
import scala.util.Random

object DiceRoller {

  def main(args: Array[String]): Unit = {
    if (args.length != 1) exit(255)
    println("Rolling 2 dice " + args(0) + " times.")

    val resultList = new MutableList[Int]()
    for (i <- 0 to args(0).toInt) {
      resultList += SixSidedDie.roll(2)
    }

    println(new DiceHistogram[SixSidedDie](SixSidedDie,2, resultList).toString)
   
  }

  abstract class Dice {
  val sides = 0
  def roll: Int = {
    val r = new Random
    r.nextInt(sides) + 1
  }
  def roll(numTimes:Int):Int = {
    if (numTimes == 0) {
      return 0
    } else {
      return roll + roll(numTimes-1)
    }
  }

}
class SixSidedDie extends Dice{}
object SixSidedDie extends SixSidedDie {
  override val sides = 6
}


class DiceHistogram[D <: Dice](dice:D, numDice:Int, rollResults:MutableList[Int]) {
  val min = numDice
  val max = dice.sides * numDice

  override def toString():String = {
    var str = ""
  for (i <- min to max) {
      str += i + ": "
      rollResults.filter(r => r == i).foreach(x => str += "x")
      str += "\n"
    }
    str
  }

}
}
Since it was too complex, I went back to the drawing board and eventually came up with a very much simplified and single purpose solution that I'm pretty proud of.  Using "yield" (which I tend to forget about) removed the need for that ugly MutableList stuff, the subtype for six sided die was overkill, and a little filtering and mapping simplified the output quite a bit.   Here's what I came up with:

package scalaplayground.dice

import scala.util.Random

object TwoSixSidedDiceRoller {
  def main(args: Array[String]): Unit = {
    if (args.length != 1) {
      System.err.println("Usage: DiceRoller numRolls")
    } else {
      val r = new Random
      val rolls = for (i <- 1 to args(0).toInt) yield r.nextInt(6) + r.nextInt(6) + 2
      for (i <- 2 to 12) {println(i + ": " + rolls.filter(r => r == i).map(x => "x").mkString)}
    }
  }
}
I also wanted to tackle a the more general purpose problem for all of the Dungeons and Dragons players out there.  This version allows you to specify how many dice to roll and how many sides the dice have.

package scalaplayground.dice

import scala.util.Random

object DiceRoller {

  def main(args: Array[String]): Unit = {
    if (args.length != 3) {
      System.err.println("Usage: DiceRoller2 numDice numRolls numSidesOnDice")
    } else {

      val numDice = args(0).toInt
      val numRolls = args(1).toInt
      val numSidesOnDice = args(2).toInt

      println("Rolling " + numDice + " " + numSidesOnDice + "-sided dice " + numRolls + " times.")

      val d = new Die(numSidesOnDice)
      val rolls = for (i <- 0 to numRolls) yield d.roll(numDice)
      for (i <- numDice to numSidesOnDice*numDice) {
        println(i + ": " + rolls.filter(r => r == i).map(x => "x").mkString)
      }
    }
  }

  class Die(sides:Int = 6) {
    val r = new Random

    def roll:Int = {r.nextInt(sides) + 1}
    def roll(numTimes:Int):Int = {
      numTimes match {
        case 0 => 0
        case _ => roll + roll(numTimes-1)
      }
    }
  }
}
Though this problem seems simple, I learned a lot about Scala working on the solution.  I continue to be amazed by how elegant Scala can be.  Thanks Dave!


No comments:

Post a Comment

Note: Only a member of this blog may post a comment.