學習軌道
/
Factor
Factor
/
練習
/
汽車,組裝!
汽車,組裝!

汽車,組裝!

學習練習

簡介

這個練習要介紹條件判斷,也就是根據一個值,在兩種或多種做法之間做選擇。它建立在你於 Annalyn 的滲透 認識的布林值,以及 貨幣換算 裡的整數運算之上。

比較字詞

這些全都位於math裡(而=則在kernel中):

=  ( x y -- ? )    ! equal
<  ( x y -- ? )    ! less than
<= ( x y -- ? )    ! less than or equal
>  ( x y -- ? )    ! greater than
>= ( x y -- ? )    ! greater than or equal
3 3 = .     ! => t
2 3 < .     ! => t
3 3 <= .    ! => t
3 4 = not . ! => t    (inequality: combine = with not)

zero?(位於math)是常見的0 =測試的簡寫,它會消耗掉數字,並推入它是否為零:

zero? ( n -- ? )    ! t when n is 0

範圍檢查

between?(位於math.order)會測試一個值是否落在包含端點的範圍內。當某個動作取決於數字落在哪個區間時,它就很方便:

between? ( x lo hi -- ? )    ! lo <= x <= hi (inclusive)
5 1 10 between? .    ! => t
0 1 10 between? .    ! => f
10 1 10 between? .   ! => t   (inclusive at both ends)

你經常會看到它被當成cond的述詞使用,例如dup 1 4 between?,用來依範圍而不是單一值來挑選分支。

引述

上面的比較字詞會產生布林值;若要對布林值採取行動,你得把一個或多個引述交給條件判斷。引述是一段用方括號[ ... ]包起來的程式碼。寫下它會把程式碼當成值推入堆疊,而不是執行它,接著像if這樣的字詞就會決定要執行哪個引述。

[ neg ]   ! a quotation that negates the top of the stack
[ ]       ! the empty quotation — does nothing

後面的練習會完整介紹引述;現在,把[ ... ]讀成「這個分支要執行的程式碼」就好。

if、when、unless

if(位於kernel)接受一個布林值和兩個引述。當布林值為真時,它會執行第一個引述;為假時則執行第二個:

if ( ? then-quot else-quot -- )
: abs ( x -- y ) dup 0 < [ neg ] [ ] if ;

when只在布林值為真時執行它的引述;unless則只在為假時執行:

when   ( ? quot -- )
unless ( ? quot -- )

if*、when* 和 unless*

三個 kernel 變體在布林值為真時,會把它當成值得保留的值,這在字詞回傳「那個東西,或f」時很有用:

if*     ( ? true false -- )    ! truthy: true is called WITH ? on stack
when*   ( ? true       -- )    ! truthy: true is called WITH ? on stack
unless* ( ? false      -- )    ! falsy: false runs and pushes a default

if*是雙分支的形式。真分支被呼叫時,值還留在堆疊上;假分支被呼叫時則沒有:

42 [ ] [ "nothing" ] if* .   ! prints 42
f  [ ] [ "nothing" ] if* .   ! prints "nothing"

unless*是典型的「有值就用值,否則用預設值」慣用語。如果值為真,就原封不動;如果值是f,就把值丟掉,並執行引述來推入替代值:

"hello" [ "anonymous" ] unless* .   ! => "hello"
f       [ "anonymous" ] unless* .   ! => "anonymous"

when*是if*的單分支形式:只有當值為真時,它才會執行引述(值仍留在堆疊上),而當值是f時,就直接把值丟掉。當你想把「某個東西,或f」的結果併入一個累計值,又不想費心處理f的情況時,就可以用它:

0 41 [ + ] when* .   ! => 41   (truthy: 41 added to the running total)
0 f  [ + ] when* .   ! => 0    (falsy: f dropped, total left untouched)

cond

當你有好幾個替代動作要從中選擇時,combinators裡的cond是最自然的選擇。它接受一個由{ predicate body }配對組成的陣列,並執行第一個產生真值的述詞所對應的內容:

USING: combinators ;

: classify ( n -- label )
    {
        { [ dup 0 <  ] [ drop "negative" ] }
        { [ dup 0 =  ] [ drop "zero"     ] }
        [ drop "positive" ]
    } cond ;

有幾個細節值得注意:

  • 配對會依序嘗試。第一個符合的勝出。
  • 放在最後、沒有述詞的項目(只有單一引述)會當作預設值。
  • 每個述詞會檢查輸入,但應該讓資料堆疊維持原狀,dup ... <test>是常見的慣用寫法。
  • 被選中的配對,其內容會拿到述詞所看到的同一個堆疊,所以通常一開始會先drop掉輸入,再推入結果。

說明

在這個練習中,你會撰寫程式碼來分析汽車工廠裝配線的生產狀況。裝配線的速度範圍從0(關閉)到10(最高)。

在最低的非零速度(1)下,每小時會生產221輛車。產量會隨速度線性增加,所以速度為4時,產線每小時會生產4 * 221 = 884輛車。不過,速度越高,生產出瑕疵車的機率也越高,而這些車之後必須被丟棄。

你有四項任務。每項任務都會從堆疊取出一個整數參數,也就是裝配線的速度。

1. 回報產線是否正在運轉

定義production-status,讓它在速度為0時回傳"stopped",其他速度則回傳"running":

0 production-status .
! => "stopped"
3 production-status .
! => "running"

2. 計算成功率

定義success-rate,回傳產品在生產時沒有發生錯誤的機率:

  • 0:0.0
  • 1到4:1.0
  • 5到8:0.9
  • 9:0.8
  • 10:0.77
10 success-rate .
! => 0.77

3. 計算每小時的生產率

定義production-rate-per-hour,回傳裝配線每小時的生產率,並將成功率納入考量。

你需要先定義base-speed,也就是常數221。

6 production-rate-per-hour .
! => 1193.4

回傳的值是浮點數。

4. 計算每分鐘生產的可用成品數量

定義working-items-per-minute,回傳每分鐘生產多少輛可用的車。結果是整數,未完成的車不列入計算。

6 working-items-per-minute .
! => 19
透過 GitHub 編輯 連結會在新視窗或分頁中開啟
Factor Exercism

準備好開始 汽車,組裝! 了嗎?

註冊 Exercism,透過 47 個概念163 個練習 和真人引導來學習並精通 Factor,全部免費。