轨道
/
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,全部免费。