LoginSignup
11
5

More than 1 year has passed since last update.

List.Generateの使い方

Last updated at Posted at 2021-10-10

Power Query歴、約3年の狸です。
@PowerBIxyz さんの名記事がある中で今更何を書くか、ということなんですが、ともかくも、基本的なところを書いてみました。
Power Queryでの関数の書き方なんか知らん、という方は実例に行く前に、準備事項をお読みください。

List.Generateでできること

存在意義

Power Queryには、For文、Do ~ Loop文と言ったものがありません。
代わりに、いくつかの関数1で、ループ処理を行うことができます。
その中でも、List.Generateは最も自由度が高い計算と言えます。2

戻り値

この関数の戻り値は、ループ各回の実行結果を全部を含んだリストです。
過程の結果が要らない場合は、リストの最後の要素を指定すればよいです。
例えば、SomeListというリストを得た場合に、下記のように書きます。

List.Last(SomeList)
もしくは
List.Reverse(SomeList){0}

リストとは

Power Queryでいうリストというのは配列みたいなものです。
ただし、Power Queryでは一度、値を定義したら、その値を変更することができません。(immutable)
つまり、「リストaの中の値を変更する」ことはできないので、「リストaの中の値を変更して、リストbとして定義する」ことになります。

リストの例
a={1,3,5,7,9} //要素が5つのリストにaという名前を付ける
a{0}      //=1

公式reference
リスト

不変性(immutability)

再帰との関係

Power Queryでは再帰関数の定義もできます。ただ、末尾再帰の最適化がされていないようです。
なので、全般、List.Generateで書いた方がよいと考えています。(今後は分かりませんが、今のところ)
※再帰関数を書く場合は、関数の名前の前に@をつける記法となります。
詳しくは公式referenceを読んでください。この記事では触れません。

準備事項 関数の定義ができること

最低限、Power Queryでの関数の定義ができないといけないです。
List.Generateを使う上では、引数0個と引数1個の2種類の関数を作れる必要があります。

引数が1個の関数

定義例

下記3つは同じ意味で、「引数に1を加えて返す」関数です。

  • (x)=> x + 1
  • (_)=> _ + 1
  • each _ + 1


実行した結果も確認したい方はこちらをクリック。

image.png
image.png
image.png

引数名に係る注意

上記の定義例で用いたxというのは引数につけた、ただの名前です。
あまり凝らずに、スペースなしの英語、アルファベットもしくは日本語を使うことをお勧めします。aとかbとかappleでもbananaでも、今日の昼飯でもいいわけです。

※引数名の1字目に数字を使うとか、引数名の中に記号を使ったりすると、特殊な記法が必要になりますので、避けます。

引数が0個の関数

やや引っかかりやすいのですが、引数0個の関数というものも定義できます。
1個の時の違って、書き方はこれ1通りです。
eachと書くと、関数ではあっても、引数0にはならないので注意してください。

()=> 100 //OK:引数0の関数
each 100 //NG:引数1の関数!(引数を計算に使ってないだけ)

↓初期値をeachで書くとこうなります。「Expression.Error: 0 引数は、1 を必要とする関数に渡されました。」というエラーメッセ―ジが出ます。定義した関数1個の引数を必要とするが、何も渡されなかったから実行できず、エラーになったということです。
image.png

※List.Generateでなくても、引数を渡さなければ同じエラーが出ます。
image.png

例1:もっとも単純な例

関数の定義自体がよく分からない場合は、準備事項 関数の定義ができることから読んでください。

実例

List.Generateじゃなくてもできるじゃないか、ということは置いといて、簡単な計算を書いています。
なお、便宜的に各引数に日本語を当てていますが、説明の便宜上の呼び名で、公式なものではありません。

List.Generate(
                 ()=> 0,          //---初期値(initial)
                 (x)=> x <= 10,   //---継続条件(condition)
                 (x)=> x + 1      //---各回の処理(next)
            )

image.png

解説

初期値(initial)

引数なしの関数を入れます。何も受けとる引数がないからです。だからといって、関数でない固定値を入れてしまうと、下記のようなエラーになります。
image.png

継続条件(condition)

次に生成しようとする値(予定値)を引数として受け取り、true/falseを返す関数を書きます。
trueである場合は予定値が値として確定し、次の予定値の計算に進みます。3
falseの場合は予定値は捨てられ、その前までの値でリストが確定します。
初期値も継続条件を満たさないといけないことに注意してください。

各回の処理(next)

確定済みの、現在の値を引数として受け取り、何らかの値を返す関数を書きます。

実行順序

例1の場合は下記のように実行されます。

  1. 「初期値」関数を実行して、予定値0を得る。
  2. 予定値0を「継続条件」関数に入れ、真偽値として、trueを得る。
  3. 予定値0が確定値になる。
  4. 確定値0を「各回の処理」関数に入れ、予定値1を得る。
  5. 予定値1を「継続条件」関数に入れ、真偽値として、trueを得る。
  6. 予定値1が確定値になる。

以下略

例2:よくある例

レコードを使えば、欲しい値と別の情報を持つことができ、書きやすくなります。

レコードについて

レコード(record)というのはいわゆる連想配列のようなもので、名前の付いた値の集まりのことです。
名前と値の1セットをフィールドと呼びます。

recordの例
b=[年=2021,月=12,日=25] //レコードにbという名前を付ける。改行も可能。
b[年]           //=2021

公式reference(レコードについて)

実例

レコードを使うと、計算内容をフィールドに分けることができるので、各回での実施内容を、複雑な計算を読みやすく記述することができます。

List.Generate(
    ()=> [i = 0, value = 1],     //初期値
    (x)=> x[i] <= 10,         //継続条件
    //各回の処理
    (x)=> [
        i = x[i] + 1,
        value = x[value] + Number.Power(2,i)
    ],
    (x)=> x[value]        //最終出力値
)


※実行結果(クリックで開きます)

コードは縮めて書いています。
image.png

実例の解説

基本的な3引数については例1で説明していますので、重複する部分は省きます。

初期値(initial)

引数なしで、レコードを返す関数を入れます。
決まり、というより、レコード処理をする上で、後続の処理がレコードを前提としていますので、それと同じ構造でないと、計算できないためです。

継続条件(condition)

引数として渡される予定値はレコードですので、今回は、レコードを引数に取る関数を書かないといけません。
(x)=> x[i] <= 10という関数は、引数xがレコードであり、その中のフィールドiについて、10以下かどうかについてtrue/falseを返す、という意味です。

各回の処理(next)

こちらも同様に、引数として渡される前回の確定値はレコードですので、レコードを引数に取る関数を書かないといけません。

例2の各回の処理(再掲)
(x)=> [
        i = x[i] + 1,
        value = x[value] + Number.Power(2,i)
    ]

フィールドvalueは、前回の確定値のフィールドvalueに2の累乗を足す計算をしています。2の累乗の指数としてi(つまり、x[i] + 1)を指定しています。
x[i]iを混同しないよう注意してください。

  • x[i]:前回の確定値のフィールドi
  • i今作成しようとしているレコードのフィールドi

最終出力値(selector)

今回は例1で紹介した3引数に加え、「最終出力値」という引数を使いました。レコードを使って書く場合は知っておくべき引数です。
今回のようにレコードの中の出力するフィールドを指定する、というのが主な使い方になります。
(x)=> x[value]とすることで、各回の確定値のレコードの中のフィールドvalueだけが戻り値となります。

なお、無い場合はエラーになるわけではなく、リストの要素となる戻り値がレコードになるだけです。一度試してみるとよいと思います。
image.png

実行順序

レコード値を使うと、計算の様子が例1とは異なってきます。
これはPower Queryで遅延評価(lazy evaluation)が行われることに起因する、と僕は解釈しています。

  1. 「初期値」関数の中のフィールドiを計算して、予定値0を得る。
  2. フィールドiの予定値0を「継続条件」関数に入れ、真偽値として、trueを得る。
  3. 0番目の値のフィールドiの確定値が0になる。
  4. 「初期値」関数の中のフィールドvalueを計算して、0番目の値のフィールドvalueの確定値が1になる。
  5. 0番目の確定値(レコード)を「各回の処理」関数に入れ、予定値1を得る。
  6. 予定値1を「継続条件」関数に入れ、真偽値として、trueを得る。
  7. 予定値1が確定値になる。

試した環境

  • MS365のExcel:バージョン2109
  • Power Queryのバージョン:2.97.321.0 64 ビット

その他公式reference


  1. 他にはList.Accumulate関数、List.Transform関数があります。これらは計算対象のリストを引数とし、そのリストの各要素を引数として計算するfor each的な処理となります。また、Table.TransformRows関数、Table.TransformColumns関数なども、テーブルの各行、各列を処理するもので、ループ処理の一種と言えます。 

  2. @PowerBIxyz さんのList.Generateの記事「Power Query の List.Generate 関数ってなんだよー」では、List.Accumulateとの比較もされています。 

  3. ここでは各回で1個の値しか計算しない場合を前提とした説明になっています。レコードの場合は、若干話が異なってきます。 

11
5
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
5