I am trying to express the classical TDD kata of mastermind in the most idiomatic scala I can. Here is a scalatest, test suite :
package eu.byjean.mastermind
import org.junit.runner.RunWith
import org.scalatest.junit.JUnitRunner
import org.scalatest.FlatSpec
import org.scalatest.matchers.ShouldMatchers
@RunWith(classOf[JUnitRunner])
class MastermindSpec extends FlatSpec with ShouldMatchers {
behavior of "Mastermid"
it should "find no good and no misplaced for a wrong guess" in {
Mastermind.check (guess='red)(secret='blue) should equal (0,0)
}
it should "find all good if guess equals secret of one" in {
Mastermind.check('blue)('blue) should equal (1,0)
}
it should "find one good if guess has one good in a secret of two" in {
Mastermind.check('blue,'red)('blue,'green) should equal (1,0)
Mastermind.check('green,'red)('blue,'red) should equal (1,0)
}
it should "find two good if guess has two good in a secret of three" in {
Mastermind.check('blue,'red,'red)('blue,'green,'red) should equal (2,0)
}
it should "find one misplaced blue in a guess of two" in {
Mastermind.check('blue,'red)('green,'blue) should equal (0,1)
}
it should "find two misplaced in a guess of four" in {
Mastermind.check('blue,'blue,'red,'red)('green,'yellow,'blue,'blue) should equal (0,2)
}
it should "find two misplaced colors in a guess of four" in {
Mastermind.check('green,'blue,'yellow,'red)('green,'yellow,'blue,'blue) should equal (1,2)
}
}
and my current implementation
package eu.byjean.mastermind
object Mastermind {
def check(guess: Symbol*)(secret: Symbol*): (Int, Int) = {
val (goodPairs, badPairs) = guess.zip(secret).partition { case (x, y) => x == y }
val(guessMiss,secMiss)=badPairs.unzip
val misplaced=guessMiss.sortBy(_.toString).zip(secMiss.sortBy(_.toString())).filter{case (x,y)=>x==y}
(goodPairs.size, misplaced.size)
}
In the imperative form, some people employ a map and count for each color the number of occurences of the color in the secret and in the guess (only as far as misplaced go of course). I tried an approach using foldLeft on the badPairs list of tuple, which lead me to variable naming problems and calling map.updated twice in the foldLeft closure which didnt seem right
Any thoughts on how to improve on the above code. It doesn't feel perfect. how would you improve it along the following axis:
- bugs: have I missed something obvious ?
- performance : this version with zip, unzip, sort, zip again most likely isn't the fastest
- readability : how would I define an implicit ordering for Symbol so I don't have to pass _.toString ,
- idiomatic scala: is there globally a more "scala-ish" way of solving this ?
Thanks