學習軌道
/
jq
jq
/
練習
/
遙控車
遙控車

遙控車

學習練習

簡介

函式

你可以在jq中定義自己的自訂函式,來封裝你需要的任何邏輯。 _函式_的行為就像內建函式一樣:它們接收一個輸入,並輸出零個、一個或多個輸出。

定義函式

你可以用以下語法定義一個jq函式:

# no arguments
def funcname: expression;

# or with arguments
def funcname(args): expression;
  • 以def關鍵字開頭,
  • 函式主體前有一個冒號,
  • 主體由單一運算式組成,
  • 以分號結尾,
  • 和jq語法的其他部分一樣,你可以自由使用空白來提升可讀性。

函式要放在哪裡

_函式_必須先定義才能使用:下面這樣會出錯:

def A: B(10);
def B(n): n + 1;
A
# => error: B/1 is not defined

這表示你必須把_函式_放在jq程式碼的最前面,也就是「主要」運算式之前。

巢狀函式

_函式_可以巢狀:

def A:
    def B(n): n + 1;
    B(10)
    ;
A
# => 11

在這裡,B_函式_只在A的主體內可見。

作用域

一個_函式_會為變數和巢狀函式引入新的作用域。

引數

函式_的引數是用_分號_分隔,不是逗號。 例如,一個接收一個數字,接著加上一個數字再乘上一個數字的_函式:

def add_mul(adder; multiplier): (. + adder) * multiplier;

10 | add_mul(5; 4)    # => 60
Note

會需要分號,是因為逗號在jq裡已經有用途了:它是一個用來串接串流的運算子。

如果用逗號取代分號,就會嘗試對一個_單引數_的add_mul函式做兩次呼叫,但這個函式不存在,所以第一次嘗試呼叫時就會失敗:

10 | add_mul(5, 4)
# error: add_mul/1 is not defined
引數是_運算式_

函式的_引數_是篩選器,不是值。 從這個角度來看,它們的行為就像其他語言所說的回呼一樣:

以add_mul函式為例:

10 | add_mul(. + 5; . - 2)    # => 200

這裡發生了什麼事?

  • adder引數拿到的是_運算式_. + 5
    • 當函式執行. + adder時,就會變成. + . + 5
    • 因為. == 10,結果會是 25
  • 同樣地,multiplier引數是運算式. - 2
    • 結果會是 8
    • 然後結果就是25 * 8 == 200
引數當作值

有時你會想把_引數_「具體化」成一個變數:

def my_func(arg):
    arg as $arg
    | other stuff ...
;

這有個簡寫:

def my_func($arg):
    other stuff ...
;

注意,這只是「語法糖」:沒有$的arg名稱,在_函式_裡仍然在作用域內。

參數數量

_函式_有參數數量,也就是它們接收的_引數_個數。

函式_可以用同一個名稱搭配不同的_參數數量。 內建的range函式就是個例子:range/1、range/2和range/3都可以並存。

這在定義透過引數攜帶狀態的遞迴函式時很有用。 例如map_可以_這樣實作:

def my_map($accumulator; func):
    if length == 0
        then $accumulator
        else first as $elem | .[1:] | my_map($accumulator + [$elem | func]; func)
    end
    ;

def my_map(func):
    my_map([]; func)
    ;

[1, 2, 3, 4] | my_map(. * 10)   # => [10, 20, 30, 40]

遞迴

jq會進行尾呼叫最佳化,但只限於參數數量為 0 的函式。

模組

jq模組是一個只包含函式的檔案。 模組會用include或import指令引入到jq程式中。

說明

在這道練習裡,你會操作一台遙控車,這是你終於存夠錢買下的喔。

車子一開始的電量是滿的(100%)。 每次你用遙控器駕駛車子時,它會前進 20 公尺,並消耗 1% 的電量。 車子的暱稱要等到它被建立之後才會知道。

這台遙控車有一個酷炫的 LED 顯示器,會顯示兩項資訊:

  • 它總共行駛的距離,顯示為:"<METERS> meters"。
  • 剩餘的電量,顯示為:"Battery at <PERCENTAGE>%"。

如果電量為 0%,你就不能再駕駛這台車,電池顯示器會顯示"Battery empty"。

1. 建立一台全新的遙控車

實作new_remote_control_car/0函式,回傳一個全新的遙控車物件:

new_remote_control_car
# => {
#      "battery_percentage": 100,
#      "distance_driven_in_meters": 0,
#      "nickname": null
#    }

2. 建立一台帶有暱稱的全新遙控車

實作new_remote_control_car/1函式,回傳一個帶有指定暱稱的全新遙控車物件:

new_remote_control_car("Blue")
# => {
#      "battery_percentage": 100,
#      "distance_driven_in_meters": 0,
#      "nickname": "Blue"
#    }

3. 顯示距離

實作display_distance/0函式,它會接收一個車子物件作為輸入,並輸出 LED 顯示器上顯示的距離字串:

new_remote_control_car | display_distance
# => "0 meters"

4. 顯示電量百分比

實作display_battery/0函式,它會接收一個車子物件作為輸入,並輸出 LED 顯示器上顯示的電量百分比字串:

new_remote_control_car | display_battery
# => "Battery at 100%"

如果電量為 0%,電池顯示器會顯示 "Battery empty"。

5. 駕駛會改變電量與行駛距離

實作drive/0函式,它會:

  • 接收一個車子物件作為輸入
  • 將行駛的公尺數增加 20
  • 消耗 1% 的電量
  • 輸出修改後的車子物件
new_remote_control_car("Red") | drive
# => {
#      "battery_percentage": 99,
#      "distance_driven_in_meters": 20,
#      "nickname": "Red"
#    }

6. 處理電池沒電時駕駛的情況

更新drive/0函式,讓它在電池耗盡(0%)時,既不增加行駛距離,也不減少電量百分比:

{
  battery_percentage: 0,
  distance_driven_in_meters: 2000,
  nickname: "Red"
} | drive
# => {
#      "battery_percentage": 0,
#      "distance_driven_in_meters": 2000,
#      "nickname": "Red"
#    }
透過 GitHub 編輯 連結會在新視窗或分頁中開啟
jq Exercism

準備好開始 遙控車 了嗎?

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