本篇講解 List.Generate 函數(shù)的用法。這個函數(shù)的功能是用于生成 list,可以是單值,也可以是結(jié)構(gòu)化類型,比較靈活,使用起來有一定難度。
List.Generate(
initial as function,
condition as function,
next as function,
optional selector as nullable function) as list
4 個參數(shù)都是函數(shù),參數(shù)的含義如下:
-
initial: 通過這個函數(shù)構(gòu)造一個單值或者結(jié)構(gòu)化類型的數(shù)據(jù),單值或者結(jié)構(gòu)化類型的值作為結(jié)果 list 的第一項。第一個函數(shù)作為后面幾個函數(shù)的參數(shù)。 -
condition: 退出循環(huán)的條件。如果函數(shù)返回值為 false,則退出循環(huán);如果函數(shù)的返回值為 true,將當(dāng)前項加入到結(jié)果 list 中。接受第一個函數(shù)為參數(shù)。 -
next:如何構(gòu)造結(jié)果 list 下一項,該參數(shù)接受第一個函數(shù)為參數(shù)。 -
selector:這是唯一一個可選的參數(shù),提供將結(jié)果 list 進行改變的機制。如果不設(shè)置該參數(shù),則第二個參數(shù)返回值為 false 時退出循環(huán),將當(dāng)前的結(jié)果 list 作為函數(shù)的返回值。
有一段 python 語法偽代碼,可以讓我們更好的理解函數(shù)的功能。這段代碼來自:Loops in Power Query M language
def List.Generate(start, condition, next, transform=None):
results = list()
item = initial()
while condition(item) == True:
results.append(item)
item = next(item)
if selector is not None:
output = list()
for item in results:
output.append(selector(item))
else:
output = results
return output
還是覺得比較抽象吧。接下來通過一個例子來幫助我們理解。假設(shè)我們想輸出數(shù)字 1 到 10,用 List.Generate 來實現(xiàn)。在 Power Query 中創(chuàng)建一個空查詢,進入高級編輯器,在高級編輯器中輸入下面的代碼:
let
Source = List.Generate(
() => 1,
(x) => x <= 10,
(x) => x+1
)
in
Source
查詢編輯器顯示的結(jié)果如下:
對這個例子解釋一下:
step 1: 通過函數(shù)構(gòu)建一個單值數(shù)據(jù) 1
step 2: 將 1 傳入第二個參數(shù)進行判斷是否小于 10,因為 1 小于 10, 所以 1 被加入結(jié)果 list,即結(jié)果 list 為 {1}
step 3: 將 1 傳入第三個參數(shù),第三個參數(shù)執(zhí)行 1 + 1 運算,結(jié)果變?yōu)?2
step 4: 將 2 傳入第二個參數(shù)進行判斷,執(zhí)行后續(xù)的循環(huán)...
現(xiàn)在將示例修改一下,展示第 4 個參數(shù)的作用。
let
Source = List.Generate(
() => 1,
(x) => x <= 10,
(x) => x+1,
(x) => "第 " & Text.From(x) & " 項"
)
in
Source
前面 3 個參數(shù)對單值 x 進行判斷和改變,第 4 個參數(shù)構(gòu)建一個字段串作為結(jié)果 list 進行輸出。在查詢編輯器中的顯示如下:
List.Generate 的有用之處還是在于結(jié)構(gòu)化類型的構(gòu)建。基于我在參考部分列出的文章示例,我對文中的示例進行了改編,假設(shè)根據(jù)員工在不同 team 的異動記錄,計算出在各 team 的起止日期,結(jié)束日期為在下個 team 的開始日期 - 1:

基于剛才對
List.Generate 的介紹,我們直接看 M 語言腳本代碼:
let
Source = Employees,
InputData = Table.Sort(
Source,{{"EmployeeName", Order.Ascending}, {"Date", Order.Ascending}}),
DoReplace = List.Generate(
()=> [Employee="", Team="", StartDate=null, EndDate=null, Counter=0],
each [Counter]<=Table.RowCount(InputData), // condition
each [
Employee=InputData{[Counter]}[EmployeeName],
Team=InputData{[Counter]}[Team],
StartDate = InputData{[Counter]}[Date],
EndDate =
let
CurrentRowEmployee = InputData{[Counter]}[EmployeeName],
NextRowEmployee = InputData{[Counter]+1}[EmployeeName],
NextRowDate = if NextRowEmployee = null then
null
else if CurrentRowEmployee = NextRowEmployee then
try InputData{[Counter]+1}[Date] otherwise null
else null,
MyDate = try Date.AddDays(NextRowDate, -1) otherwise null
in
MyDate,
Counter=[Counter]+1],
// Our list will be a list of these records:
each [[Employee], [Team], [StartDate], [EndDate]] // transform
), // end of List.Generate
// Convert our list of records into a table
ConvertedtoTable = Table.FromList(
DoReplace, Splitter.SplitByNothing(), null, null, ExtraValues.Error),
// Expand it out
ExpandedColumn1 = Table.ExpandRecordColumn(
ConvertedtoTable,
"Column1",
{"Employee", "Team", "StartDate", "EndDate"},
{"Employee", "Team", "StartDate", "EndDate"}),
// Remove nulls
FilteredRows = Table.SelectRows(ExpandedColumn1, each ([StartDate] <> null))
in
FilteredRows
- 設(shè)置 counter,循環(huán)
Table.RowCount(InputData)次 - 第 1 個參數(shù)(initial)構(gòu)造一個空的 record:
()=> [Employee="", Team="", StartDate=null, EndDate=null, Counter=0]
- 循環(huán)的時候,每次構(gòu)造一個 record 類型的對象:
[Employee = xxx, Team = xxx, StartDate = xxx, EndDate = xxx, Counter+1]
Counter 作為記數(shù)用,在第 4 個參數(shù)中,只保留有用的部分。
- 最后通過
Table.FromList將 list 轉(zhuǎn)換成 table,并對結(jié)構(gòu)化列進行展開。
示例數(shù)據(jù)
github -List.Generate Demo.xlsx