40秒で把握するCollectionのavg()

Laravel11公式ドキュメントのCollectionメソッド avg()についてみていく。段階的に見ていくが、LaravelのCollectionについて把握されていない方は、以下の記事を先に見るとこの記事が読みやすくなるかも知れない。

LaravelのCollectionに触れる

この記事で得られること

  • avg()の概要
  • avg()の実装コードを見る体験

メソッドの概要を見る

Laravel – The PHP Framework For Web Artisans

公式ドキュメントを日本語訳してからの引用

この**avg**メソッドは、指定されたキーの平均値を返します。

$average = collect([
    ['foo' => 10],
    ['foo' => 10],
    ['foo' => 20],
    ['foo' => 40]
])->avg('foo');
 
// 20
 
$average = collect([1, 1, 2, 4])->avg();
 
// 2
  • 引数にキーを指定するとキーの値として数値が格納されているコレクションの平均値を返す
  • 値のみが格納されているコレクションの場合は引数の指定は不要

メソッドのソースを見る

概要は何となく掴めてきたので早速ソースを見ていきます。

vendor/laravel/framework/src/Illuminate/Collections/Traits/EnumeratesValues.php(178行目)

    /**
     * Get the average value of a given key.
     *
     * @param  (callable(TValue): float|int)|string|null  $callback
     * @return float|int|null
     */
    public function avg($callback = null)
    {
        $callback = $this->valueRetriever($callback);

        $reduced = $this->reduce(static function (&$reduce, $value) use ($callback) {
            if (! is_null($resolved = $callback($value))) {
                $reduce[0] += $resolved;
                $reduce[1]++;
            }

            return $reduce;
        }, [0, 0]);

        return $reduced[1] ? $reduced[0] / $reduced[1] : null;
    }

パッと見で読み取れる情報

  1. $this→valueRetriever($callback) でコールバック関数を加工?している
  2. collectionのreduce() を使っておそらく足し算を行なっている
  3. 最後に足したものを足したものの数で割って平均を出している

一つのavg()に見た感じでも二つのcollectionメソッドが使われているのが分かる。ちょっと複雑そう。

CursorAI(GPT4)に聞いて分かったことも交えつつ見ていく。

$callback = $this->valueRetriever($callback);

GPT4によると$callbackには、どの値を平均するかを決めるためのルールが入るよう。

そして、valueRetriever()については定義へとジャンプすると以下のことが分かった

  • valueRetrieverの中でuseAsCallable()が使われている
  • useAsCallable()の中では文字列かコールバック関数かを判定する処理をしている

読み取れること

  • 文字列の場合はキー名が直接指定されているのでそのままキー名に紐づく値の平均を求める。
  • コールバック関数の場合はどの値を平均するかのルールに基づいて平均を求める

という解釈で問題ないと思う。

$reduced = $this->reduce(static function (&$reduce, $value) use ($callback) {
            if (! is_null($resolved = $callback($value))) {
                $reduce[0] += $resolved;
                $reduce[1]++;
            }

            return $reduce;
        }, [0, 0]);

GPT4によると$callbackを使って値を取り出す方法を設定しているよう。

👉&${変数名} はリファレンス渡しと言って関数内で引数を可変にすることが出来るというものです。

今回の場合は$reduceが可変になっている。$resolvedにはコールバック関数により解決されたキー名の値が入り、$reduce[0]に足している。$reduce[1]で何回目の加算かをカウントしている。ちなみにreduce()の方でforeachが使われている。

箇条書きにすると

  • reduceをリファレンス渡しすることで可変にしている
  • $reduce[0] にはコールバック関数で解決されたキー名の値が足されたものが入る
  • $reduce[1]には値が足された回数が入る
  • 最後に値の合計と値の数がreturn $reduce として返される
return $reduced[1] ? $reduced[0] / $reduced[1] : null;

avg()の返り値として値の数($reduce[1])が存在すれば、値の合計($reduce[0])を値の数($reduce[1])で割ったもの(平均)を返す。値の数($reduce[1])が存在しなけばnullを返す。

以上の処理により、avg()では平均を返している。

まとめ

コレクションのavgメソッドは以下のような特徴を持つ

  • 引数に文字列が指定されれば、文字列をキー名として値の平均を返す
  • 引数にコールバック関数が指定されれば、コールバック関数に基づいて平均を返す
  • その実装はvalueRetriever()での引数チェックとreduce()を用いて行われている

ソースコードを追っていると追った先で出会ったソースコードの解析に気を取られて何のソースコードを解析していたかを見失うことがある事を知った。

おまけ CursorAI(GPT-4)に聞いたときに使った質問文

1ラウンド目

下記はLaravelのCollectionクラス内の記述です。
コードの解説を以下の点を踏まえてお願いします。
- 段階的に行う
- 小学生でも分かるような言葉を使う
- コードの引用を混じえる

"""
    /**
     * Get the average value of a given key.
     *
     * @param  (callable(TValue): float|int)|string|null  $callback
     * @return float|int|null
     */
    public function avg($callback = null)
    {
        $callback = $this->valueRetriever($callback);

        $reduced = $this->reduce(static function (&$reduce, $value) use ($callback) {
            if (! is_null($resolved = $callback($value))) {
                $reduce[0] += $resolved;
                $reduce[1]++;
            }

            return $reduce;
        }, [0, 0]);

        return $reduced[1] ? $reduced[0] / $reduced[1] : null;
    }
 
"""

コメント

タイトルとURLをコピーしました