[Scala] 我也要學正妺寫猜數字遊戲(大誤)。

這篇是早上看到正妺 mosky 的噗浪連結的這一篇 Python 猜數字後,趁著剛剛在等其他程式的結果時,也用 Scala 來練習一下,畢竟之前寫猜數字都是在老俞的課堂上用 C 或 Java 來寫,還真沒試過用 functional programming 的方式來實作咧。

不過因為正妺 mosky 的 Python 版太強大,我的悟性太差沒辦法完全看懂,所以解題的邏輯可能不太一樣。

此外,因為自己學 functional programming 的時候,也遇大很大的障礎,所以我試著在程式碼裡把每一步的思考邏輯以及運算的方式還有範例寫得詳細一點,希望能夠讓不熟悉這種思考方式的朋友也能看懂。其實真的不會很難,只要將他想像成是一連串的轉換就可以了。:P

個人認為,這個例子裡面最難理解的大概就是取得『幾 B』(也就是數字對,但位置不對)的部份,基本上邏輯是這樣的:

  1. 先把正確答案和使用者輸入的答案去掉數字對,位置也對的部份。例如正確答案是1, 2, 3, 4,使用者輸入1, 3, 4, 5,那就是取出正確答案剩下 3, 4, 5,使用者輸入的部份剩下 4, 5
  2. 將 3, 4, 5 和 4, 5 取交集得出 4, 5
  3. 所以 B 的部份就是 2(4 和 5 這兩個數字)
import scala.util.Random

/**
 *  隨機產生不重覆的四位數
 */
def generateAnwser = 
    Random.shuffle((0 to 9).toList)  // 產生一個 List(0, 1, 2..., 9) 然後打亂之後
          .take(4)                   // 取前四個數字,例:List(3,4,1,5)
          .mkString                  // 再結合成字串,例:"3415"


/**
 *  有趣的部份:生出幾 A 幾 B 的訊息
 *
 *  @param  correctAnswer   正確答案
 *  @param  inputAnswer     使用者輸入的答案
 *
 *  Inspired by [mosky's python implement][1].
 *
 *  [1]: http://mosky.tw/archives/178
 *
 */
def compare(correctAnswer: String, inputAnswer: String) = {

    // 如果 Tuple (X, Y) 裡 X == Y 返回 true,不然就返回 false
    def pairIsSame(x: (Char, Char)) = x._1 == x._2

    // 來比對位置對,數字也對的部份
    //
    // 舉例:
    //  - 正確答案: 3, 2, 1, 4
    //  - 輸入答案: 4, 2, 1, 5
    //
    // 方法:
    //
    //  1. 先將正確答案與使用者輸入的東西做配對。
    //
    //     例如:
    //
    //       List(3, 2, 1, 4) zip List(4, 2, 1, 5) 會變成
    //       List((3, 4), (2, 2), (1, 1), (4, 5))
    //
    //  2. 利用上述的 pairIsSame 來過慮出數字對,位置也對的部份。
    //     此例即為:List((2, 2), (1,1)) 
    //  
    val sumA = (correctAnswer zip inputAnswer) filter (pairIsSame)

    // 個人認為這裡是最難理解但也最有趣的地方:取得數字對,但位置不對的部份
    //
    // 方法:
    //
    //  1. 先將正確答案與使用者輸入的東西用 zip 做配對,
    //     例如 List((3, 4), (2, 2), (1, 1), (4, 5))
    //
    //  2. 例用 filterNot 把『數字對,位置也對』的元素 (sumA) 給過慮掉
    //
    //     註:xs filterNot (sumA contains) 讀做『過濾掉 xs 中曾經出現在 sumA 裡面的元素』
    //     此步的結果:List((3, 4), (4, 5))
    //  
    //  3. 將其做 unzip,就會變成 (List(3, 4), List(4, 5))
    //
    //  4. 利用強大的 pattern matching 將 
    //     List(3, 4) 指定為 remainCorrect, 
    //     List(4, 5) 指定為 remainInput
    //
    val (remainCorrect, remainInput) = 
        (correctAnswer zip inputAnswer). // List((3, 4), (2, 2), (1, 1), (4, 5))
        filterNot (sumA contains _).     // List((3, 4), (4, 5))
        unzip                            // (List(3, 4), List(4, 5))

    // 取得幾 B 的部份
    //
    // 超簡單的啦:就是上述 remainCorrect 和 remainInput 的交集嘛!
    // 例如 List(3, 4) intersect List(4, 5) == List(4)
    val sumB = remainCorrect intersect remainInput

    println ("== [info] ==")
    println ("數字對,位置也對:" + sumA)
    println ("正確答案扣掉數字對,位置也對的部份:" + remainCorrect)
    println ("輸入答案扣掉數字對,位置也對的部份:" + remainInput)
    println ("數字對,位置不對:" + sumB)
    println ("============")

    // 返回幾 A 幾 B
    //
    // 分別計算 sumA 和 sumB 內的元素數量即可
    //
    // Scala 不用特別寫 return,每一個 method 的最後一個 expression 就
    // 是該 method 的回傳值,和 Ruby 一樣。^_^
    //
    "%dA%dB" format(sumA.length, sumB.length)
}

// 以下是無趣的東西:
//
// 不斷讀取使用者輸入的猜測,並且與正確答案比對然後告訴使用者幾A幾B
//
def startGame ()
{
    val correctAnswer = generateAnwser

    println ("[debug] answer:" + correctAnswer)

    do {

        print ("Enter your guess:")
        val guess  = readLine()
        val result = compare (correctAnswer, guess)

        // Scala 的 match 運算子,類似 Java/C 的 switch/case,
        // 但又強大到亂七八糟以致於我現在覺得 switch/case 是廢
        // 物的東西。
        //
        // 以後用不到 Pattern Matching 我要怎麼活啊!(誤)
        result match {
            // 如你所見,可以和字串做匹配。
            case "4A0B"  => 
                println ("Bingo!")
                exit()

            // 類似 Java 裡 switch 的 default,但不同的是,
            // 他會把比對的東西 bind 到 otherwise 這個變數上。
            case otherwise =>
                println ("Your guess:%s, status=%s" format(guess, otherwise))
        }

    } while (true)
}

startGame() // Game Start!



回響