第三章的内容为在第二章的假设与前提下,使用递归的思路完成路径图的构建与展开。首先,需要把数据一分为二,一组只有处于顶层的成员,其他的成员为另一组。以下为分组用到的代码:
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"}。