M言語のList.Accumulateで累計を作る

LWP | M言語のList.Accumulateで累計を作る

M言語のList.Accumulateで累計を作る

図解1|合計は一つ、累計は明細ごと

三つの受注明細から、合計一つと各明細までの累計三つへ分かれる関係図

図1|最後の合計だけでなく、各明細までにいくら積み上がったかを見る。

解説1|合計は一つ、累計は明細ごと

ストーリー

フナさん「合計は14,000円ですね。ただ、受注メモと照合するので、各明細までの累計も欲しいんです。」

シャケモン「では、順番に足すたびに結果を残しましょう。同じ三つの金額でも、欲しい答えの形が違いますね。」

全国規模の中華食材卸・淡水社の、架空の受注明細を使う。一行は一つの受注明細で、下表の順1〜3を今回の計算順とする。金額は数量×単価で求めた受注額。税・送料・値引きは含めず、売上確定額とも区別する。

入力例と期待結果

順

明細

商品・規格

数量×単価

金額

累計

1

O001-1

干ししいたけ500g

6袋×1,200円

7,200円

7,200円

2

O001-2

豆板醤1kg

4本×800円

3,200円

10,400円

3

O002-1

干ししいたけ500g

3袋×1,200円

3,600円

14,000円

合計は14,000円という一つの数値。累計は7,200円、10,400円、14,000円という三つの数値になる。袋と本の数量をまとめて足すのではなく、単位が円でそろった金額を足している。

読むポイント

M言語では、この金額列を {7200, 3200, 3600} というListで表せる。この記事では、小さなListを直接書いて、前の結果を次の計算へ渡す仕組みを確かめる。

図解2|前の状態と今回の金額から、次の状態へ

List.Accumulateの入力List、初期状態、二引数の関数と、0から14000までの状態遷移

図2|関数が受け取る二つの値と、返す一つの値を追う。

解説2|前の状態と今回の金額から、次の状態へ

ストーリー

シャケモン「List.Accumulateには、金額のList、最初の状態、次の状態を作る関数を渡します。」

フナさん「2件目なら、そこまでの7,200円と、今回の3,200円を受け取るんですね。」

状態とは、次の計算へ持ち越す値のこと。ここでは「ここまでの合計」を数値一つで持つ。関数は、現在の状態と今回の要素を受け取り、次の状態を返す。初回だけは、まだ前の計算結果がないので初期状態の0を使う。

関数の役割

引数

この例で渡すもの

意味

第1引数

{7200, 3200, 3600}

順に取り出す金額

第2引数

0

計算を始める前の状態

第3引数

(a_state, a_value) => a_state + a_value

二つの値から次の状態を返す関数

計算結果

回

現在の状態

今回の金額

返す次の状態

1

0

7,200

7,200

2

7,200

3,200

10,400

3

10,400

3,600

14,000

矢印 => の右側が返す値を表す。a_stateとa_valueは、この例で付けた引数名である。Listの各要素を独立に変換するList.Transformと違い、前の計算結果も次の計算の材料になる。

図解3|数値を持ち越すと、戻るのは最後の合計

途中の状態7200と10400が次へ渡され、最後の状態14000だけが関数全体から返る図

図3|途中で計算された値が、そのまま出力の一覧になるわけではない。

解説3|数値を持ち越すと、戻るのは最後の合計

ストーリー

フナさん「計算中には10,400円もありますが、出力されるのは最後の14,000円だけなんですね。」

シャケモン「List.Accumulateが返すのは最後の状態です。今は数値一つを次へ渡しているので、答えも14,000だけになります。」

実行するコード

ExcelのPower Queryで空のクエリを作り、詳細エディターの内容を次の式へ置き換える。外部ファイルやテーブルは参照しない。

01

let

02

    order_amounts = {7200, 3200, 3600},

03

    order_total = List.Accumulate(

04

        order_amounts,

05

        0,

06

        (a_state as number, a_value as number) as number =>

07

            a_state + a_value

08

    )

09

in

10

    order_total

計算結果

戻り値は数値の14000。7,200と10,400も計算には使われるが、最後の状態の中には残っていない。各明細の累計を欲しい場合は、状態の持ち方を変える必要がある。

読むポイント

合計一つだけなら、List.Sum({7200, 3200, 3600})の方が目的を直接表せる。ここでList.Accumulateを使うのは、「前の状態から次の状態を作る」仕組みを理解し、次の図で途中の結果も残すためである。

図解4|状態をListにして、累計を末尾へ残す

初期状態のListに7200、10400、14000を順に追加し、先頭の0を除いて三つの累計を得る図

図4|最後の状態の中に、必要な途中結果をすべて含める。

解説4|状態をListにして、累計を末尾へ残す

ストーリー

シャケモン「今度は状態をListにします。末尾の累計へ今回の金額を足して、新しい累計を末尾へ加えましょう。」

フナさん「最後の状態が一覧なら、途中までの金額もまとめて受け取れますね。」

実行するコード

01

let

02

    order_amounts = {7200, 3200, 3600},

03

    running_history = List.Accumulate(

04

        order_amounts,

05

        {0},

06

        (a_state as list, a_value as number) as list =>

07

            a_state & {List.Last(a_state) + a_value}

08

    ),

09

    running_totals = List.Skip(running_history, 1)

10

in

11

    running_totals

関数の役割と結果

List.Last(a_state)は状態Listの末尾を取り出す。たとえば2回目は、状態が {0, 7200}、今回の金額が3200。末尾の7200へ足して10400を作る。

波括弧で {10400} という一要素のListにし、& で今までのListへ連結すると、次の状態は {0, 7200, 10400} になる。既存のListの要素を書き換える処理ではなく、新しいListを返す式である。

最終状態は {0, 7200, 10400, 14000}。先頭の0は開始用の値なので、List.Skipで一つ除く。戻り値は {7200, 10400, 14000} となり、三つの明細と件数がそろう。

図解5|順序が変われば、途中の累計も変わる

同じ三金額を異なる順で足す比較と、空Listでは初期状態だけが残る境界例

図5|最後の合計の一致と、各行の累計の一致を分けて確認する。

解説5|順序が変われば、途中の累計も変わる

ストーリー

フナさん「最終合計だけ合っていても、並び順が違えば受注メモの途中の金額とは合いません。」

シャケモン「表から金額Listを取り出す前に、何の順で累計を出すか決める必要がありますね。」

入力例と結果

図4のコードでorder_amountsだけを変更すると、次の結果になる。

入力List

戻る累計List

最後の値

{7200, 3200, 3600}

{7200, 10400, 14000}

14000

{3600, 7200, 3200}

{3600, 10800, 14000}

14000

{}

{}

要素なし

実際の表を使う場合は、明細順などのキーで必要な順序を確定してから金額列を取り出す。同じキーの行が複数あるなら、その間の順序も決めておく。集計後の表示順だけ変えても、計算時の順序を直したことにはならない。

空Listでは何が起きるか

入力が {} なら、次の状態を作る関数は一度も呼ばれない。List.Accumulateは初期状態の {0} を返し、List.Skipがその一要素を除くため、結果は {} になる。実在しない明細の累計0を出力しない。

読むポイント

この例の入力は数値だけで、nullや数値に変換できない文字列は対象外。実務へ組み込む前に、欠損・エラーの扱いを決める。また、Listを繰り返し連結する小さな教材例であり、大量データでの最速手法を示すものではない。

図解6|ExcelのSCANと、返す値の違いを比べる

Mは履歴Listを最後の状態として返し、ExcelのSCANは各回の中間結果をスピルする対照図

図6|同じ累計が得られても、途中結果を返す仕組みは異なる。

解説6|ExcelのSCANと、返す値の違いを比べる

ストーリー

フナさん「Excelの表でも、この三つの累計を並べられますか。」

シャケモン「SCANを使えます。こちらは各回の中間結果を配列で返すので、履歴Listを自分で連結する必要がありません。」

入力例と数式

ExcelのB1を「受注金額」、B2:B4を順に7200、3200、3600とする。C1は「累計」とし、C2:C4を空けて、C2だけに次の式を入れる。

=LET(_amounts,B2:B4,_running_totals,SCAN(0,_amounts,LAMBDA(a_total,a_amount,a_total+a_amount)),_running_totals)

LETは入力範囲と計算結果に名前を付けている。SCANの初期値は0。a_totalがここまでの合計、a_amountが今回の金額に当たる。結果はC2:C4へ7,200、10,400、14,000とスピルする。C3やC4へ式をコピーする必要はない。

結果の違い

方法

返す値

累計を残す方法

MのList.Accumulate

最後の状態

状態Listへ各回の累計を含める

ExcelのSCAN

各回の中間結果の配列

関数が中間結果を配列で返す

SCANはExcel for Microsoft 365・Excel 2024で利用できる。数式の再計算とPower Queryの更新は別の仕組みである。どちらも今回の三明細と各行の累計を照合してから利用する。

出典

Microsoft Learn:List.Accumulate / Microsoft Support:SCAN。2026-11-03確認。会社・取引・価格は説明用の架空例。