在某些办公情景中,有时需要把同一列的多个文本替换成新的文本。假设现在需要把如下表格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"。如果当前行的文本为其他值,就会进行该值替换自身的操作。
矛盾大佬,个人提个小意见,就是可不可以在解答问题的时候简述解决问题的思路,个人感觉提纲挈领的讲解会比单纯的代码块有助于深入。
第一种思路是使用List.Accumulate()对Table.ReplaceValue()进行多次调用,而第二种思路则是利用Record使Table.ReplaceValue()的第三个参数能够动态地发生变化,希望这些简述能帮助你理解问题
请问数据量很大时,哪一种思路占用系统资源更小、计算速度快?
没有经过代码化简的方法应该最快,为了化简代码而使用的方法需要测试后才知道哪种更快
第一种思路这样写更容易理解,根据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
第二种思路直接调用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
附件中的Solution2已经移除多余的Text.Replace()
个人感觉最佳的两种写法:
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"
刚才有递归试了下,也可以!
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
这个函数直接搞定。
= List.ReplaceMatchingItems(ProductList[Product],Table.ToRows(Parameter))
YES
这个确实简洁方便,而且应用范围也不错!
给你点赞
为什么不直接将(ii)替换为""?
对于这个例子来说确实是好思路