第三章的内容为在第二章的假设与前提下,使用递归的思路完成路径图的构建与展开。首先,需要把数据一分为二,一组只有处于顶层的成员,其他的成员为另一组。以下为分组用到的代码:
let
Lv1=
Table.RenameColumns(
Table.SelectRows(
Data,
each [Child] = [Parent]
),
{ "Parent", "Path" }
)
in
Lv1
Child字段与Parent字段相同的成员为最顶层的成员,Table.SelectRows()就是利用这一特点把最顶层的成员从Data表格中分离出来。因为之后的过程中不需要使用Parent字段但是需要构建与Parent字段或者Child字段完全相同的Path字段,所以直接使用Table.RenameColumns()把Parent字段的名称改为“Path”。
let
NonLv1=
Table.SelectRows(
Data,
each [Child] ˂˃ [Parent]
)
in
NonLv1
Child字段与Parent字段不相同的成员不是处于顶层的成员,以上Table.SelectRows()就是利用这一逻辑把其他的成员分为一组。接着,就可以根据分组的结果构建路径图:
let
Source=
List.Buffer(
Recursion(
FilterNonLv1,
FilterLv1,
{FilterLv1}
)
)
in
Source
因为要根据自定义函数Recursion的结果计算最大层数MaxLv,所以需要在该函数的结果外面使用List.Buffer()提高代码的运行效率。自定义函数Recursion()总共有三个参数,以下为该函数的代码:
(lTable as table, rTable as table, Outcome as list ) as list =˃
let
Operation=
Table.Buffer(
Table.SelectColumns(
Table.AddColumn(
Table.ExpandTableColumn(
Table.NestedJoin(
lTable,
"Parent",
rTable,
"Child",
"PrePath",
JoinKind.Inner
),
"PrePath",
{"Path"},
{"PrePath"}
),
"Path",
each
Text.Combine(
{
[PrePath],
"|",
[Child]
}
),
type text
),
{"Path", "Child"}
)
),
Result=
if
Table.IsEmpty(Operation)
then
Outcome
else
@Recursion(
lTable,
Operation,
List.Combine(
{
Outcome,
{Operation}
}
)
)
in
Result
下一层的成员的Parent字段值等于上一层的成员的Child字段值,Recursion()函数利用就是利用这一特征进行内连接。然后,使用Table.IsEmpty()判断内连接的结果是否为一个空表,如果是空表就返回最后一次传入的Outcome串列,否则把Outcome串列与{Operation}进行首尾连接后产生的新串列再次传入Recursion()中(要在同一个函数中调用该函数自身需要使用@进行区分)。由于Recursion()的结果为一串列,并且该串列的元素个数等于最大层数MaxLv,可以使用把该结果代入List.Count()中得到MaxLv:
let
Source = List.Count( Path )
in
Source
在构建好路径图与得到最大层数后,就可以进行路径图的展开:
let
Repetition=
List.Transform(
List.Numbers( 0, MaxLv ),
(x)=˃
if
x = 0
then
let
bList = List.Repeat( {"|"}, MaxLv + 1 )
in
Table.Column(
Table.AddColumn(
Path{0},
"Expansion",
each
Text.Trim(
Text.Combine(
bList,
[Child]
),
"|"
),
type text
),
"Expansion"
)
else if
x ˂ MaxLv - 2
then
let
TempValue = MaxLv - x - 1
in
List.Transform(
Table.ToRows( Path{x} ),
each
Text.Combine(
List.Alternate(
List.Repeat( _, TempValue ),
1,
1,
2
),
"|"
)
)
else if
x = MaxLv - 2
then
List.Transform(
Table.ToRows( Path{x} ),
each Text.Combine( _, "|")
)
else
Table.Column(
Path{x},
"Path"
)
)
in
Repetition
不难发现,第一层,倒数第二层以及最后一层的展开是可以进行特别的处理,而第二层到倒数第三层的处理方法是一样的。第一层的展开思路很简单,对Path{0}表格添加一计算列计算每一行Text({"|", "|", "|", "|", "|"}, "A??????")的结果。第二层到倒数第三层的展开首先需要使用Table.ToRows()把Path{x}表格以行为单位分解为复合串列,然后使用List.Repeat()重复每一个子串列的元素,接着使用List.Alternate()选取需要的元素,最后使用Text.Combine()把选取出来的元素连接为文本。倒数第二层的展开,也是要使用Table.ToRows()把Path{MaxLv-2}表格以行为单位分解为复合串列,由于不需要重复其中的元素,所以直接使用Text.Combine()把串列中的元素连接为文本就完成展开。最后一层不需要展开,直接取出表格Path{MaxLv-1}中的Path列就行了。完成展开后,还需要把展开的结果转化为表格,以下为转换的代码:
let
ToTable=
Table.FromList(
List.Combine( Expansion ),
Splitter.SplitTextByDelimiter("|"),
fnColNames(MaxLv)
)
in
ToTable
因为展开的结果为复合串列,所以需要使用List.Combine()把子串列首尾连接起来。不难发现,连接后的串列中每一个元素为文本,所以应该选取Table.FromList()把该串列转化为表格。fnColNames()为一自定义函数,其结果用来控制表格的列名称,其代码为:
( ColCount as number ) =˃
let
ColNames=
List.Transform(
List.Numbers(
1,
ColCount
),
each
Text.Combine(
{
"Lv",
Text.From(_)
}
)
)
in
ColNames
fnColNames()函数利用List.Transform()历遍{1..MaxLv},使之转化为{"Lv1".."LvMaxLv"}。