學習軌道
/
jq
jq
/
練習
/
高分排行榜
高分排行榜

高分排行榜

學習練習

簡介

物件

用其他語言的術語來說,JSON 物件就是「雜湊」、「映射」或「字典」。

JSON 對_物件_的定義如下:

物件是一組無序的名稱/值配對。 物件以{左大括號開始,以}右大括號結束。 每個_名稱_後面接著:冒號,而_名稱/值_配對之間以,逗號分隔。

名稱必須是字串。 名稱_的另一個說法是_鍵。

_值_可以是任何 JSON 型別。 同一個物件裡的不同_值_可以是不同的型別,就像下面這個例子。

{
  "name": "Jane",
  "age": 42,
  "pets": ["cat", "fish"],
  "address": {"street": "123 Main St", "city": "Springfield"}
}

請注意,_最後_一組鍵值配對後面不可以有逗號。

建立物件

用大括號收集名稱/值配對。 雖然名稱必須是字串,但只要名稱類似識別字(由英數字元和底線組成,且開頭不是數字),就不需要加上引號。

{name: "Jane", age: 42}

也可以使用不屬於_類似識別字_的鍵。 只要為它們加上引號就好。

{"first name": "Jane", "last name": "Lopez", age: 42}

如果名稱是某個運算式的結果,這個運算式必須放在括號裡。

$ echo "Jane" | jq -Rc '{.: 42}'
# verbose error message ...

$ echo "Jane" | jq -Rc '{(.): 42}'
{"Jane":42}

索引

用點記法就能從物件取出值。

{name: "Jane", age: 42} | .age    # => 42

如果沒辦法用識別字表示某個鍵,就改用括號記法。

"name" as $key | {name: "Jane", age: 42} | .$key    # => error
"name" as $key | {name: "Jane", age: 42} | .[$key]  # => "Jane"

新增或修改鍵值配對

要在陣列中新增一組鍵值配對,或是更新既有鍵的值,請使用=賦值運算子,並在左邊放一個索引運算式。

{name: "Jane", age: 42} | .sport = "tennis" | .age = 21
# => {
#      "name": "Jane",
#      "age": 21,
#      "sport": "tennis"
#    }

+運算子會_合併_物件。

{Richard: 54} + {Jane: 42}
# => {
#      "Richard": 54,
#      "Jane": 42
#    }

移除鍵

用del函式移除鍵。 它會回傳更新後的物件。

{name: "Jane", age: 42} | del(.age)   # => {"name": "Jane"}

del的參數是索引運算式(使用點記法或括號記法),會對應到物件中的某個鍵。 jq把它稱為路徑運算式。 只給一個字串是不夠的。

{name: "Jane", age: 42} | del(name)        # error: name/0 is not defined
{name: "Jane", age: 42} | del("name")      # error: Invalid path expression with result "name"
{name: "Jane", age: 42} | del(.name)       # OK
{name: "Jane", age: 42} | del(.["name"])   # OK

成員資格

  • 要測試物件裡有沒有某個鍵,請使用has函式。

    {name: "Jane", age: 42} as $example
    |
      ($example | has("name")),    # => true
      ($example | has("sport"))    # => false
    
  • 用in測試某個鍵是否在物件裡。

    {name: "Jane", age: 42} as $example
    |
      ("name"  | in($example)),    # => true
      ("sport" | in($example))     # => false
    

列出所有鍵

用keys函式輸出所有鍵的陣列。

{name: "Jane", age: 42} | keys   # => ["age", "name"]

請注意,keys會_排序_這些鍵。 如果想按原本的順序取出鍵,請改用keys_unsorted。

沒有對等的函式可以列出所有的_值_。 不過.[]篩選器會把物件的值以_串流_的形式輸出,而這個串流可以用[...]陣列建構式接住。

[{first: "Jane", last: "Lopez", status: "awesome!"} | .[]]
# => ["Jane", "Lopez", "awesome!"]

疊代

  • map_values(filter)函式會把篩選器套用到物件裡的每個_值_。

    {first: "Jane", last: "Lopez", status: "awesome!"}
    | map_values(ascii_upcase)
    # => {"first": "JANE", "last": "LOPEZ", "status": "AWESOME!"}
    
  • 要疊代一個物件,必須先把它轉成由鍵值物件組成的陣列。 to_entries函式就是做這件事的。

    {name: "Jane", age: 42} | to_entries'
    # => [
    #      {
    #        "key": "name",
    #        "value": "Jane"
    #      },
    #      {
    #        "key": "age",
    #        "value": 42
    #      }
    #    ]
    

    到這裡就能使用陣列的疊代函式,例如map。

  • from_entries函式則是反過來:把由鍵值物件組成的陣列轉換成物件。

    [
      {"key":"name", "value":"Jane"},
      {"key":"age", "value":42}
    ] | from_entries                  # =>{"name": "Jane", "age": 42}
    
  • 想把篩選器套用到物件裡的_每一組_鍵值配對,請使用with_entries(filter)函式。

    例如,假設有一個把名稱對應到年齡的物件,就能像下面這樣把鍵和值互換。

    {"Jane": 42, "Richard": 54}
    | with_entries({key: (.value | tostring), value: .key})
    

    輸出

    {
      "42": "Jane",
      "54": "Richard"
    }
    

    with_entries(filter)等同於

    to_entries | map(filter) | from_entries
    

說明

在這個練習中,你要實作一個方式,來記錄當地電動遊樂場最熱門遊戲的最高分。

你要實作 6 個函式,大多與操作一個存放最高分的物件有關。

1. 建立新的最高分板

撰寫一個函式create_score_board,它會建立一個用來當作最高分板的物件。 這個物件的鍵會是玩家名稱,值則是他們的分數。 為了測試,你要在物件中直接加入一筆項目。 這筆初始項目應該包含"The Best Ever"這個玩家名稱,以及1000000這個分數。

create_score_board
# returns an object with one initial entry

2. 將玩家加入分數板

要把玩家加入最高分板,請實作函式add_player。 它接收一個分數板作為輸入,並且需要兩個參數:玩家名稱和玩家的分數。 這個函式會輸出加入新玩家後的分數板物件。

{"José Valim", 486373}
| add_player("Dave Thomas"; 0)
# => {"Dave Thomas": 0, "José Valim": 486373} -- in some order

如果玩家已經在分數板上,就原樣輸出這個分數板。

3. 從分數板移除玩家

如果玩家違反電動遊樂場的規則,就會被手動從最高分板移除。 實作remove_player,它接收一個分數板作為輸入,以及一個參數:要移除的玩家名稱。 這個函式應該從分數板移除指定玩家的項目,並輸出新的分數板。 如果玩家本來就不在分數板上,分數板就不應該有任何變化,應該原樣回傳。

{"Dave Thomas": 0} | remove_player("Dave Thomas")
# => {}

{"Dave Thomas": 0} | remove_player("Rose Fanaras")
# => {"Dave Thomas": 0}

4. 增加玩家的分數

如果玩家在電動遊樂場又完成了一局遊戲,就會有一定數量的分數加到分數板上原本的分數。 實作update_score,它接收一個分數板作為輸入,並需要兩個參數:玩家名稱和要增加的分數。 這個函式應該在更新完成後回傳分數板。

{"Freyja Ćirić": 12771000} | update_score("Freyja Ćirić"; 73)
# => {"Freyja Ćirić": 12771073}

如果玩家還不在分數板上,這個函式應該把他們加入分數板。

5. 套用週一獎勵分數

電動遊樂場在週一會使用另一個分數板。 當天結束時,那個分數板上的每位玩家都會額外獲得 100 分。

實作函式apply_monday_bonus。 這個函式會為分數板上列出的每位玩家加上獎勵分數。

{
  "Dave Thomas": 44,
  "Freyja Ćirić": 539,
  "José Valim": 265
}
| apply_monday_bonus
# => {"Dave Thomas": 144, "Freyja Ćirić": 639, "José Valim": 365}

6. 找出總分

不同的電動遊樂場會互相競爭,看誰擁有最厲害的玩家。 總分最高的遊樂場贏得這份榮耀。

撰寫一個函式total_score。 它接收一個分數板作為輸入,並輸出所有玩家分數的總和。

{
  "Dave Thomas": 44,
  "Freyja Ćirić": 539,
  "José Valim": 265
}
| total_score
# => 848
透過 GitHub 編輯 連結會在新視窗或分頁中開啟
jq Exercism

準備好開始 高分排行榜 了嗎?

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