Power Query の List.Generate 関数ってなんだよー

システム・コール ジェネレイト・リスト
おそらくというか間違いなく難度が高い関数じゃないかと。でも、使えるようになるとホントできることがガーっと多くなるので強くおススメする関数。ひと通り覚えるまでの過程でいろんなことが理解できたような気がする。

次のリストアイテムを出力する関数に使われる入力は、直前のループで出力されたリストアイテムっていうこと。

# List.Generate
>**List.Generate**
指定された値の初期値関数、条件関数、次の値関数、および省略可能な変換関数に基づいて、リストを生成します。
初期値 <code>initial</code> を生成する指定された 4 つの関数に基づいて、値のリストを生成し、条件 <code>condition</code> に対してテストを行います。成功した場合は、結果を選択し、次の値 <code>next</code> を生成します。省略可能なパラメーター <code>selector</code> を指定することもできます。

```text:構文
List.Generate(
    initial as function,
    condition as function,
    next as function,
    optional selector as nullable function
) as list
```
引数がすべて function という難度高めの仕様である。そしてサンプルも。

```text:Example
// 10 で始まりデクリメントが 1 の、0 を超える値のリストを作成します。
List.Generate(()=>10, each _ > 0, each _ - 1) = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1} // true
```

```text:Example
/*
x と y を含むレコードのリストを生成します (x は値、y はリスト)。
x は、10 未満を保持し、リスト y 内の項目数を表す必要があります。
リストが生成された後は、x の値のみを返します。
*/
List.Generate(
    ()=> [x = 1, y = {}],
    each [x] < 10,
    each
        [
            x = List.Count([y]),
            y = [y] & {x}
        ],
    each [x]
) = {1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9} // true
```

うん、まぁ。こういう記述をすればいいんだなというところから始めて、少し慣れたところで振り返るとよいかもしれない。let 式で記述できなくもないけど、record での記述の方がわかりやすいのでしょう。実際に使ってみるとなるほどってなると思う。引数:selector を外してどのような list が出力されているかを見たらいい。実際使うときには、ループが終了する条件とはなんだろね🙄って考えればいいんじゃないかな。
 
で、"each" キーワードってなんなのかって話は以前ポストした。
[ときおり出てくる "each" キーワードと "_" (アンダースコア) とは](https://qiita.com/PowerBIxyz/items/6668537a576592a04bcf)


#チャレンジ
## 素数のリスト
ふるいにかけた結果にふるいをかけていくロジックをそのままに。ちょうどよいかなと思ったので。

```text:手書きで数字を消し込む感じで
let
    Limit = 100,
    Source = {2 .. Limit}, 
    GeneratedList = List.Generate(
        () => [n = 0, items = Source, condition = true, remove = {}],
        each [condition],
        each
            [
                n = [n] + 1,
                items = List.RemoveItems([items], remove),
                next = [items]{[n]},
                remove = List.Generate(
                    () => next * 2,
                    each _ <= Limit,
                    each _ + next
                ),
                condition = Number.Power(next, 2) < Limit 
            ],
        each [items]
        // each
        //     Record.TransformFields(
        //         [[n],[items],[condition],[remove]],
        //         {
        //             {"items", each Text.Combine(List.Transform(_, each Text.From(_)),",")},
        //             {"remove", each Text.Combine(List.Transform(_, each Text.From(_)),",")}
        //         }
        //     )
    ),
    LastItem = List.Last(GeneratedList)
    // LastItem = Table.FromRecords(GeneratedList)
in
    LastItem
```

|n|[n]|items|next|remove|condition||
|:--:|:--:|:--:|:--:|:--:|:--:|:--|
|0||2,3,4,5,6,7,8,9,10|||true|initial|
|1|0|2,3,5,7,9|2|4,6,8,10|true|
|2|1|2,3,5,7|3|6,9|true|



リストアイテムを List.RemoveItems で消し込むのは遅いので、List.Select でフィルタに記述しなおして。
Number.Mod で割り切れる数値を順次検査。

```text:範囲を拡げてもたぶん大丈夫
let
    Limit = 5000000,
    Source = {2 .. Limit},
    GeneratedList = List.Generate(
        () => [n = 0, items = Source, condition = true],
        each [condition],
        each
            [
                n = [n] + 1,
                next = [items]{[n]},
                items = List.Select(
                    [items],
                    each _ = next or Number.Mod(_, next) <> 0),
                condition = Number.Power(next, 2) < Limit 
            ],
        each [items]
    ),
    LastItem = List.Last(GeneratedList)
in
    LastItem
```

```text:偶数を先に除外しておけばちょっと速くなる
let
    Limit = 5000000,
    Source = List.Generate(()=>3, each _ <= Limit, each _ + 2),
    // Source = {2 .. Limit},
    GeneratedList = List.Generate(
        () => [n = 0, items = Source, condition = true],
        each [condition],
        each
            [
                n = [n] + 1,
                next = [items]{[n]},
                items = List.Select(
                    [items],
                    each _ = next or Number.Mod(_, next) <> 0),
                condition = Number.Power(next, 2) < Limit 
            ],
        each [items]
    ),
    LastItem = {2} & List.Last(GeneratedList)
in
    LastItem
```

最後の方まであってるかどうか確認してないんですけどね。

## テーブルに累計列を追加する
[Power Query の List.Accumulate 関数ってなんだよー](https://qiita.com/PowerBIxyz/items/292c3cfa8a163e7a2ace) と同じお題

Excel ワークシート上のテーブルにある[列1]の値の累積値を追加する感じで。要は行をまたいで列の集計結果を追加したいということ。

|列1|累計|
|--:|--:|
|1|1|
|2|3|
|3|5|
|..|..|
|10000|50005000|
|..|..|

```text:100万行でもまぁ問題ない
let
    Source = Excel.CurrentWorkbook(){[Name="Table1"]}[Content],
    ChangedType = Table.TransformColumnTypes(Source,{{"列1", Int64.Type}}),
    KeptFirstRows = Table.FirstN(ChangedType, 1000000),
    BufferedRows = List.Buffer(Table.ToRecords(KeptFirstRows)),
    Custom1 = List.Generate(
        () => [Index = 0, 累積 = 0],
        each [Index] <= List.Count(BufferedRows),
        each
            [
                Index = [Index] + 1,
                列1 = BufferedRows{[Index]}[列1],
                累積 = [累積] + 列1
            ],
        each [[列1],[累積]]
    ),
    Custom2 = Table.FromRecords(
        List.Skip(Custom1),
        Value.Type(Table.AddColumn(KeptFirstRows, "累積", each null, Int64.Type))
        // コレデキナイExcelアルヨ
    )
in
    Custom2
```

データソースが Excel ワークシート上のテーブルだから stream を気にしていなくて、テーブル各行をレコードとして list に変換後 List.Buffer。record として取り廻して終了。列の追加も List.Genarate の ループの中で済ましてしまえば、Table.AddColumn の必要はなくなる。

#思ったこと🙄
いずれも"繰り返し"する関数なのだけど、

||繰り返し|出力の型||
|:--|:----:|:--:|:--|
|**List.Generate**|Do Loop|list|出力は List.Accumulate でいうところの state の list|
|**List.Accumulate**|For Each|any|stateを出力に含めようとする使い方には向かない|
ということいいかなと思ってる。で、再帰呼び出しするよりは List.Generate で こなした方が速さ的にも有利だと思う。ガツガツといろいろ試していたんだけどスタックオーバーフローが起きやすいので再帰呼び出しはおススメしない感じ。

# その他
- [Power BI 勉強会 - connpass](https://powerbi.connpass.com/)
- [Power BI Desktop を使い始めるには](http://qiita.com/PowerBIxyz/items/6c4735cb5b5712e70981)
- [Excel で Power Query を使い始めるには](http://qiita.com/PowerBIxyz/items/52e563037b855a099ca9)

# おまけ
んー、やっぱり列名に依存しない記述は大変だ。

```text
let
    Source = Excel.CurrentWorkbook(){[Name="テーブル1"]}[Content],
    ChangedType = Table.TransformColumnTypes(Source,{{"列1", Int64.Type}, {"列2", Int64.Type}, {"列3", Int64.Type}}),
    KeptFirstRows = Table.FirstN(ChangedType,20000),
    Seed = Record.FromList(
        List.Repeat({0},Table.ColumnCount(KeptFirstRows)),
        Table.ColumnNames(KeptFirstRows)
    ),
    SeedFieldNames = Record.FieldNames(Seed),
    BufferedRows = List.Buffer(Table.ToRecords(KeptFirstRows)),
    Custom1 = List.Generate(
        ()=> Record.AddField([Index = 0], "Value", Seed),
        each [Index] <= List.Count(BufferedRows),
        each
            [
                Index = [Index] + 1,
                CurrentValue = BufferedRows{[Index]},
                Value = Record.FromList(
                    List.Transform(
                        Table.ToColumns(
                            Table.FromRecords({CurrentValue,[Value]})
                        ),
                        each List.Sum(_)
                    ),
                    SeedFieldNames
                )
            ],
        each [Value]
    ),
    Custom2 = Table.FromRecords(
        List.Skip(Custom1),
        Value.Type(KeptFirstRows)
    )
in
    Custom2
```