jqのむだづかいー最大値篇

[JSON](https://datatracker.ietf.org/doc/html/rfc8259)処理のコマンドラインツールである[jq](https://stedolan.github.io/jq/)は、JSONテキストを1行でちゃくっと解析、変換するときに使うものです。複雑なことをするときは、`sed`や`awk`など他のユーティリティを併用するものです。

とは言え、`jq`にも変数定義などプログラミング言語らしき機能が備わっているので、「やればできんじゃねぇ?」と野望を募らせてしまうこともあります。

今日のお題は、次のような数値の配列を受け取り、その最大値を求めるというものです。

```json
[1, 8, 3, 4, 10, 3, 2, 7, 5]
```


### Bash

`Bash`なら`sort -nr`でしょう。邪魔な角括弧は`tr`で省けます。また、行志向な`sort`に合わせるため、`sed`でカンマを改行に置き換えます。逆順にソートしてしまえば、あとは`head`で最初の行を取ってくるだけです。

```bash
$ echo '[1, 8, 3, 4, 10, 3, 2, 7, 5]' | tr -d '[]' | sed 's/,/\n/g' | sort -nr | head -n 1
10
```


### jq（シンプル）

入力がJSONなので、`jq`の方が簡単です。しかも、配列の最大値を取ってくる`max`関数もあります。

```bash
$ echo '[1, 8, 3, 4, 10, 3, 2, 7, 5]' | jq 'max'
10
```

ただ、配列要素に数値以外が含まれていると、期待外れの動きをします。型を混ぜた配列でソートすると、文字列はどんな数値よりも大きいと判断されるからです。

```bash
$ echo '[1, 8, "3", 4, 10, 3, "2", 7, 5]' | jq 'max'     # 文字列表記の数字混じり
"3"                                                      # 期待外れ
```

こういう場合は、先に`tonumber`で文字列を数値に変換します。

ただ、`tonumber`は配列を受け取ってくれないので、イテレーション（`.[]`）をかけて要素単位で処理します（数値はそのまま数値として出力されます）。反対に、`max`は配列しか受け付けないので、イテレーションの結果を再度配列に戻さなければなりません。これは、イテレーションと数値化の部分を配列化の`[]`でくくります。

```bash
$ echo '[1, 8, "3", 4, 10, 3, "2", 7, 5]' | jq '[.[] | tonumber] | max'
10
```

あ、でも、こう書くくらいなら高階関数な`map`の方がスタイリッシュですね。

```bash
$ echo '[1, 8, "3", 4, 10, 3, "2", 7, 5]' | jq 'map(tonumber) | max'
10
```

### jq（あえてreduce）

オリジナルのお題は、`max()`などの関数を使わず、手づからループを組め、でした。というわけで、`reduce`を使います。

「関数を使うな」なのに高階関数な`reduce`ではずるくないかという説もありますが、中間結果を記憶するループマシンとして使っているだけなので、許してもらいます。

```bash
$ echo '[1, 8, 3, 4, 10, 3, 2, 7, 5]' | \
  jq 'reduce .[] as $x(-infinite; if $x > . then $x else . end)'
10
```

`reduce ... as $x`は、入力された配列の要素をループしながら、逐次、変数`$x`にセットします（Pythonなら`for x in ...`に相当）。

第1引数はループ変数`.`の初期値です。ここではその都度の最大値を収容するのに使います。最大値を探すので、最初は`infinite`の負数からスタートします。

第2引数は`.`の更新式です。以降、配列要素`$x`とこの`.`を比較し、要素の方が大きければ、`.`をその値と入れ替えます。そうでなければ、以前の最大値をそのまま保持します（`.`）。`if`文は`if-then-else-end`の形式です。

`reduce`はループを終了すると、最後の`.`の値を出力します。


### おわりに

最初の`max`の箇所はさておき、わざわざ`reduce`を使うことはないですよね。でも、そういうお題だったのです。ちなみに、オリジナルは「JavaScriptで書け」でした。こうですかね。

```javascript
function maximum(arr) {
  let maxx = Number.NEGATIVE_INFINITY;
  arr.forEach(elem => {
    if (maxx < elem) {
      maxx = elem;
    }
  });
  return maxx;
}
```


### 参考

- [./jq](https://stedolan.github.io/jq/) - `jq`のオフィシャルサイトです（英文）。
- [man page of tr](https://linuxjm.osdn.jp/html/GNU_textutils/man1/tr.1.html) - Unixコマンドの`tr`のman pageです。
- [man page of sed](https://linuxjm.osdn.jp/html/GNU_sed/man1/sed.1.html) - Unixコマンドの`sed`のman pageです。
- [man page of sort](https://linuxjm.osdn.jp/html/GNU_coreutils/man1/sort.1.html) - Unixコマンドの`tr`のman pageです。
- [man page of head](https://linuxjm.osdn.jp/html/GNU_coreutils/man1/head.1.html) - Unixコマンドの`tr`のman pageです。
- [jqハンドブックーNetOps/DevOps必携のJSONパーザ](http://www.cutt.co.jp/book/978-4-87783-491-3.html) - `max`や`reduce`はこちらでも紹介しています。【[出版社](http://www.cutt.co.jp/book/978-4-87783-491-3.html) | [honto](https://honto.jp/netstore/pd-book_31037910.html) | [amazon.co.jp](https://www.amazon.co.jp/jq/dp/4877834915) | [ヨドバシカメラ](https://www.yodobashi.com/product/100000009003450000/) 】。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2712175/20272c85-f084-557b-189f-aa7128e283ab.png)