トラック
/
jq
jq
/
演習
/
成績統計
成績統計

成績統計

学習演習

はじめに

Reduce

数値の配列を合計したいとします。 これを実現する方法はたくさんあります。

  • 多くのプログラミング言語では、この問題はアキュムレーター変数を使ったループとして表現されます。

  • この問題は再帰関数として書くこともできます。 擬似コードでは、次のようになります。

    function Add(X, Sum=0):
      if X is empty then
        return Sum
      else
        return Add(rest(X), Sum + first(X))
      end
    end
    

    問題をより小さな部分に分割していくこの方法は、「基底ケースに向かってreduceしていく」とも表現できます。

Reduceは、データ構造のすべての要素を1つの値にまとめる方法です。 この処理は、データ構造を繰り返し走査し、各要素に関数を適用して、蓄積された結果を更新します。

jqでは、この処理はreduceフィルターとして実装されています。 他の言語では、"fold"、"fold-left"、"inject"、"aggregate"などと呼ばれることもあります。

jqのreduce式は次のようになります。

reduce STREAM_EXPRESSION as $var (INITIAL_VALUE; UPDATE_EXPRESSION)
  • STREAM_EXPRESSIONは項目の_ストリーム_で、それぞれが順番に$var変数に格納されます。
  • INITIAL_VALUEは、蓄積された結果の初期値です(「アキュムレーター」と呼ばれます)。
  • UPDATE_EXPRESSIONは、現在の値($var)をアキュムレーターに統合します(「畳み込み」ます)。
    • この式の文脈では、.はアキュムレーターの値です。
    • 式の出力は、次の繰り返しで使うためにアキュムレーターに格納されます。
    • 最後の繰り返しのあと、蓄積された結果がreduceの出力になります。

例を見てみましょう。配列の数値を合計します。 addフィルターはまさにこれを行いますが、ここではその実装方法を見ていきます。

入力として[10, 20, 30, 40]を使い、初期状態を0とすると、各ステップは次のようになります。

# 状態 要素 更新 結果
1 0 10 0 + 10 10
2 10 20 10 + 20 30
3 30 30 30 + 30 60
4 60 40 60 + 40 100

jqの構文では、次のコードのようになります。

0 + 10 | . + 20 | . + 30 | . + 40

これをreduceフィルターで表現できます。

[10, 20, 30, 40] | reduce .[] as $n (0; . + $n)     # => 100
Note

addビルトインは実際にはreduceで実装されていますが、初期状態として"null"を使います(nullにはどんなデータ型でも足し合わせることができます)。

def add: reduce .[] as $x (null; . + $x);

覚えておきたいこと

  • 畳み込みの式では、.はアキュムレーターです。 畳み込み関数の中で参照する必要があるオブジェクトが入力にある場合は、それを変数に格納する必要があります。

    {"apple": 10, "banana": 16, "carrot": 4}
    | . as $obj
    | reduce (keys | .[]) as $key (0; . + $obj[$key])     # => 30
    
  • アキュムレーターはどんなデータ型でもかまいません。 たとえば、配列を逆順にしたい場合があります。

    ["A", "B", "C", "D"]
    | reduce .[] as $elem ([]; [$elem] + .)       # => ["D", "C", "B", "A"]
    

説明

ここでは先生の立場になって考えてみましょう。 1年の終わりに、生徒一人ひとりの成績を数値で付け終えたところです。 あとはこれをアルファベットの成績に変換し、それぞれの成績を取った生徒が何人いるかを数えるだけです。

1. 数値の成績をアルファベットの成績に変換する

letter_grade関数は、数値の成績を入力として受け取り、アルファベットの成績を出力します。 範囲は次のとおりです。

成績 点数
A 90% - 100%
B 80% - 89%
C 70% - 79%
D 60% - 69%
F 0% - 59%

例:

75 | letter_grade   # => "C"

2. アルファベットの成績ごとに生徒の人数を数える

count_letter_grades関数は、生徒の名前と成績を対応させたオブジェクトを受け取ります。 出力は、アルファベットの成績ごとに、その成績を取った生徒の人数を対応させたオブジェクトになります。

例:

{"Joe": 78, "Jane": 93, "Richard": 72} | count_letter_grades
# => {"A": 1, "B": 0, "C": 2, "D": 0, "F": 0}

これを解く方法はいくつかあります。 練習として、reduceフィルターを使ってみましょう。

GitHubで編集する リンクは新しいウィンドウまたはタブで開きます
jq Exercism

成績統計を始める準備はできましたか?

Exercismに登録すれば、12個のコンセプト75個の演習、そして本物の人間によるメンタリングとともに、jqを学んでマスターできます。すべて無料です。