トラック
/
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を書きます。 このオブジェクトのキーはプレイヤーの名前、値はそのスコアになります。 テストのために、オブジェクトに1つのエントリーを直接含めておきます。 この最初のエントリーは、プレイヤー名が"The Best Ever"、スコアが1000000です。

create_score_board
# returns an object with one initial entry

2. スコアボードにプレイヤーを追加する

ハイスコアボードにプレイヤーを追加するには、関数add_playerを実装します。 これはスコアボードを入力として受け取り、プレイヤー名とプレイヤーのスコアの2つを入力として受け取ります。 この関数は、新しいプレイヤーを追加したスコアボードのオブジェクトを出力します。

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

プレイヤーがすでにボード上にいる場合は、ボードをそのまま出力します。

3. スコアボードからプレイヤーを削除する

プレイヤーがアーケードホールのルールに違反した場合、そのプレイヤーは手動でハイスコアボードから削除されます。 remove_playerを実装します。これはボードを入力として受け取り、削除するプレイヤーの名前という1つの入力を受け取ります。 この関数は、指定されたプレイヤーのエントリーをボードから削除し、新しいボードを出力します。 プレイヤーがそもそもボード上にいなかった場合は、ボードには何も起きず、そのまま返されます。

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

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

4. プレイヤーのスコアを増やす

プレイヤーがアーケードホールで別のゲームを終えると、ボード上の以前のスコアに一定のポイントが加算されます。 update_scoreを実装します。これはスコアボードを入力として受け取り、プレイヤー名と加算するスコアの2つを入力として受け取ります。 この関数は、更新が完了したあとのスコアボードを返します。

{"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を学んでマスターできます。すべて無料です。