[Raku] 以自定型別來解 Fizz Buzz

作者:   發佈於:   #rakulang

雖然維基百科上說 Fizz Buzz 是會出現在程式設計師的面試考題,但現在應該沒有甚麼公司真的還在用這個題目來做篩選了吧 😎

姑且還是先把題目定義一下:

請提供一個函式,能將數字 1 至 100 逐一印出,但若碰到 3 的倍數則改印出 Fizz,若碰到 5 的倍數則改印 Buzz,碰到同時爲 3 與 5 的倍數時則改印 Fizz Buzz

直觀解之一就是依序檢查數字是否爲 15、5、3 的倍數,然後做將數字換成不同的字串。檢查順序會影響結果,15 要先檢查,但 3 和 5 兩者的檢查順序則不影響結果:

for 1..100 -> $n {
    # [3]
    say do given ($n) {
        when $_ %% 15 { "Fizz Buzz" }
        when $_ %% 3 { "Fizz" }
        when $_ %% 5 { "Buzz" }
        default { "$_" }
    }
}

在此提供一個重構模式。那就是把「3 的倍數」、「5 的倍數」視爲兩種型別,而「15 的倍數」則可以由前兩者合成.

# [1]
subset Fizzer of Int where { $_ %% 3 }
subset Buzzer of Int where { $_ %% 5 }
# [2]
subset FizzBuzzer of Fizzer where Buzzer;

for 1..100 -> $n {
    say do given ($n) {
        when FizzBuzzer { "Fizz Buzz" }
        when Fizzer { "Fizz" }
        when Buzzer { "Buzz" }
        default { "$_" }
    }
}

[1] 處是定義兩個 Int 的子集合,Fizzer 爲 3 的倍數,Buzzer 爲 5 的倍數。

[2] 處定義的 FizzBuzzer 是「Fizzer 的子集合中,同時又是 Buzzer 者」。也就是同時爲 3 與 5 的倍數的 Int

[3] 處的 given...when 的這段,則是改用方才定義出的三種新的 Int 子集合來改寫。順序一樣是要注意的。

或者,可以不必使用 FizzBuzzer,而是將 when FizzBuzzer 的改爲 when Fizzer & Buzzer

for 1..100 -> $n {
    say do given ($n) {
        when Fizzer & Buzzer { "Fizz Buzz" }
        when Fizzer { "Fizz" }
        when Buzzer { "Buzz" }
        default { "$_" }
    }
}

此處的 & 爲 Junction 算符。表示「前後兩方的條件都要滿足」之意。when Fizzer & Buzzer 就相當於 when ($_ ~~ Fizzer && $_ ~~ Buzzer),也就是 $_ 必須滿足 Fizzer 的條件,同時必須滿足 Buzzer 的條件。

顯然以解 Fizz Buzz 這個練習題來說,任何重構都算是多餘的工程技術了。除了練習以外,沒有別的目的。這種自定型別用在檢查使用者輸入的時候非常的合用。尤其當條件稍微複雜一些的時候,若能將判別式拆解成「幾個集合的交集」這種形式,顯然能在讓程式碼更加容易閱讀。