Parent Child Hierarchies (III)

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

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"}。

附件

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注