[Scala] 從最大值建立遞增的 List。

原始的問題

話說我在 Coursera 上修了一門 Functional Programming Principles in Scala 的課,第三週的作業 TweetSet 有一項是要利用一個每次都會回傳最大值的函式,來實作出一個建立遞增的 List 的遞迴函式。

因為常常在 Scala 裡面利用遞迴的方式建立列表,所以我很自然的就寫出了像下面這樣的程式碼:

var mySet = Set(3, 4, 1, 6, 5)

def buildingList(resetSet: Set[Int]): List[Int] = {
  def building(resetSet: Set[Int], accumulator: List[Int]): List[Int] = {
    if (resetSet.isEmpty) {
      accumulator
    } else {
      var max = resetSet.max
      building(resetSet - max, max :: accumulator)
    }
  }
  building(resetSet, Nil)
}

assert(buildingList(mySet) == List(6, 5, 4, 3, 1))  // Ooops!

當然,這樣程式碼做的不是我們想要的東西,由於我們使用的是 prepend(作業裡唯一提供給我們加入新元素到 List 的方法),所以建出來的 List 的順序會和 input 的順序相反。

轉換過後的問題

雖然現在看起來我的腦袋大概生鏽了,不過一開始我真的搞了很久還是不知道怎麼樣讓建出來的 List 和 input 的順序相同。

不過後來我突然發現,這個問題其實和下面的問題一模一樣,如果可以解下面的問題(根本就是每次程設課程教的第一個遞迴函式啊……orz),那麼就可以建立一個和 input 順序一樣的 List 了。

定義一個如下的遞迴函式(不使用 inner function / helper function 或 accumulator),回傳值是 n 的階乘:

def factorial(n: Int): Int = { .... }

也就是說當呼叫 factorial(6) 的時候,應該要回傳 6 * 5 * 4 * 3 * 2 * 1,其中值得注意的是 2 * 1 應該是第一個被實際運算並簡化成整數的運算式。

這個程式碼的模式在最一開始的幾個課程影片中就有,如果可以理解這個範例的話,就會發現其實兩個是一模一樣的問題,只要進行以下的代換就好了:

  1. factorial(n: Int): Int 會變成 buildingList(n: Set[Int]): List[Int]
  2. factorial 的中止條件是 n == 1,但 buildingList 的中止條件是 n 是空集合

然後我們只要再問自己下面的問題:

  1. factorial 中遇到中止條件 (n == 1) 的時候我們返回的是 1,但在 buildingList 中遇到了空集盒的時候,要返回什麼?
  2. factorial 中我們使用 * 這個運算子來連接所有的整數值,在 buildingList 中我們要用什麼來連接所有的整數值。

礙於課程的 honor code,我不能直接貼完整的程式碼,但這樣的提示應該非常夠解決這個問題了。事實上,當我發現這是和遞迴寫階乘同樣的問題的時候,我覺得自己真是蠢斃了。

回響