多重文本替代

在某些办公情景中,有时需要把同一列的多个文本替换成新的文本。假设现在需要把如下表格ProductList中Product列的"A(ii)", "B(ii)"以及"C(ii)"分别替换成"A", "B"以及"C":

对于这个问题,最直接的解决办法是进行三次常规替代,以下为该思路的代码:

let
    Step1 = 
        Table.ReplaceValue(
            ProductList,
            "A(ii)",
            "A",
            Replacer.ReplaceText,
            {"Product"}
        ),
    Step2 = 
        Table.ReplaceValue(
            Step1,
            "B(ii)",
            "B",
            Replacer.ReplaceText,
            {"Product"}
        ),
    Step3 = 
        Table.ReplaceValue(
            Step2,
            "C(ii)",
            "C",
            Replacer.ReplaceText,
            {"Product"}
        )
in
    Step3

如果需要被替换的值不止三种,代码的长度可能就会变得十分长。在《发票号展开》中,施阳老师已经介绍过如何使用List.Accumulate()化简高度相似的重复代码,以下为沿用该思路的代码:

let
    ToRows = Table.ToRows( Parameter ),
    
    Outcome =
        List.Accumulate(
            ToRows,
            ProductList,
            (x, y) ⇒
                Table.ReplaceValue(
                    x,
                    y{0},
                    y{1},
                    Replacer.ReplaceText,
                    {"Product"}
                )
        )
in
    Outcome

以下为代码中Parameter表格的图像:

代码中的ToRows步骤,把Parameter表格转化为{{"A(ii)", "A"}, {"B(ii)", "B"}, {"C(ii)", "C"}},此结果将会被用作List.Accmulate()的第一个参数。然后在下一步中,List.Accumulate会对ProductList进行Text.ReplaceValue操作,把Product列中所有的"A(ii)"替换成"A"。List.Accumulate()会对替换文本后的表格再进行类似的操作把"B(ii)"和"C(ii)"分别替换为"B"和"C"。除了以上思路,其实还可以利用记录(Record)的特点解决以上问题,以下为第二种思路的代码:

let
    TableToCols = Table.ToColumns( Parameter ),
    ListToRecord =
        Record.FromList(
            TableToCols{1},
            TableToCols{0}
        ),
    Outcome =
        Table.ReplaceValue(
            ProductList,
            each [Product],
            each
                Record.FieldOrDefault(
                    ListToRecord,
                    [Product],
                    [Product]
                ),
            Replacer.ReplaceText,
            {"Product"}
        )
in
    Outcome

要理解第二种思路,首先要了解由Parameter表格转化而来的ListToRecord,以下为ListToRecord的截图:

上图说明了ListToRecord其实是文本旧值与新值存在一一对应关系的数据结构。利用ListToRecord,如果当前行的文本为"A(ii)", "B(ii)"或者"C(ii)",就会被分别替换为"A", "B"或者"C"。如果当前行的文本为其他值,就会进行该值替换自身的操作。

附件

15 Replies to “多重文本替代”

  1. 矛盾大佬,个人提个小意见,就是可不可以在解答问题的时候简述解决问题的思路,个人感觉提纲挈领的讲解会比单纯的代码块有助于深入。

    1. 第一种思路是使用List.Accumulate()对Table.ReplaceValue()进行多次调用,而第二种思路则是利用Record使Table.ReplaceValue()的第三个参数能够动态地发生变化,希望这些简述能帮助你理解问题

    1. 没有经过代码化简的方法应该最快,为了化简代码而使用的方法需要测试后才知道哪种更快

  2. 第一种思路这样写更容易理解,根据Parameter的行数调用List.Accumulate,每次调用用当前行号current做为Table.ReplaceValue的参数:
    let
    Outcome =
    List.Accumulate(
    List.Numbers(0, Table.RowCount(Parameter)),
    ProductList,
    (state, current)=>
    Table.ReplaceValue(
    state,
    Parameter[Old_Value]{current},
    Parameter[New_Value]{current},
    Replacer.ReplaceText,
    {"Product"}
    )
    )
    in
    Outcome

  3. 第二种思路直接调用Record.FieldOrDefault就好了,Table.ReplaceValue放在这里感觉没必要:
    let
    TableToRecord = Record.FromTable(Table.RenameColumns(Parameter,{{"Old_Value","Name"},{"New_Value","Value"}})),

    Outcome =
    Table.TransformColumns(
    ProductList,
    {
    "Product",
    each
    Record.FieldOrDefault(
    TableToRecord ,
    _,
    _
    ),
    type text
    }
    )
    in
    Outcome

  4. 个人感觉最佳的两种写法:
    let
    //添加列这样写比较直观,而且可以在内置界面里编辑,缺点是不能部分替换和多重替换
    #"Added Custom" = Table.AddColumn(ProductList, "Custom", each Record.FieldOrDefault(
    [
    #"A(ii)"="A",
    #"B(ii)"="B",
    #"C(ii)"="C"
    ],
    [Product],[Product]))
    ,
    //自建replacer接受list做为需要替换的多个字符,这里用内嵌函数,也可以写成公共函数来调用
    ReplaceMultText = (inputText, oldValues, newValues, optional i) =>
    let
    i = if (i = null) then 0 else i,
    outputText =
    if i < List.Count(oldValues) then
    //递归调用,也可以写成循环
    @ReplaceMultText(
    Replacer.ReplaceText(inputText,oldValues{i},newValues{i}),
    oldValues,newValues,i+1)
    else inputText
    in
    outputText,
    //通过参数类型可以看出Table.ReplaceValue已经预留了扩展空间
    //Table.ReplaceValue(table as table, oldValue as any, newValue as any, replacer as function, columnsToSearch as list) as table
    #"Replaced Value" = Table.ReplaceValue(
    #"Added Custom",
    {"A(ii)","B(ii)","C(ii)"}, //oldValue、newValue类型是any,实际是传给replacer处理
    {"A","B","C"}, // 这里用list把需要替换的多个字符传给replacer
    ReplaceMultText, //这个参数改为调用自建的replacer
    {"Product"}
    )
    in
    #"Replaced Value"

  5. 刚才有递归试了下,也可以!

    let
    Source = ProductList,
    A = Table.ToRows(Parameter),
    //Tolist = List.Transform(Table.ToColumns(Source){0},each Text.Replace(_,A{2}{0},A{2}{1})),
    Len = List.Count(A),
    MyFun =(n)=> if n = 1 then List.Transform(Table.ToColumns(Source){0}, each Text.Replace(_,A{0}{0},A{0}{1})) else
    List.Transform(@MyFun(n-1),each Text.Replace(_,A{n-1}{0},A{n-1}{1})),
    B = MyFun(Len)
    in
    B

发表回复

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