使用本机函数计算 Google 工作表的层次标签
Calculate hierarchical labels for Google Sheets using native functions
使用 Google 表格,我想像这样自动对行编号:
关键是我希望它只使用内置函数。
我有一个实现,其中子项位于单独的列中(例如,"Foo" 在 B 列中,"Bar" 在 C 列中,"Baz" 在 D 列中) .但是,它使用自定义 JavaScript 函数,自定义 JavaScript 函数的计算速度较慢,结合依赖项,可能与较慢的 Internet 连接相结合,意味着我的解决方案可以接管一秒钟每行 (!) 计算。
作为参考,这是我的自定义函数(我想放弃以支持本机代码):
/**
* Calculate the Work Breakdown Structure id for this row.
*
* @param {range} priorIds IDs that precede this one.
* @param {range} names The names for this row.
* @return A WBS string id (e.g. "2.1.5") or an empty string if there are no names.
* @customfunction
*/
function WBS_ID(priorIds,names){
if (Array.isArray(names[0])) names = names[0];
if (!names.join("")) return "";
var lastId,pieces=[];
for (var i=priorIds.length;i-- && !lastId;) lastId=priorIds[i][0];
if (lastId) pieces = (lastId+"").split('.').map(function(s){ return s*1 });
for (var i=0;i<names.length;i++){
if (names[i]){
var s = pieces.concat();
pieces.length=i+1;
pieces[i] = (pieces[i]||0) + 1;
return pieces.join(".");
}
}
}
例如,单元格 A7 将使用公式:
=WBS_ID(A:A6,B7:D7)
...产生结果“1.3.2”
请注意,在上面的示例中,空白行在编号过程中被跳过。不尊重这一点的答案——其中 ID 是根据 ROW()
) 确定地计算的——是可以接受的(甚至可能是可取的)。
编辑:是的,我自己也尝试过这样做。我有一个解决方案使用三个额外的列,我选择不包括在问题中。我已经在 Excel 中编写方程式至少 25 年(以及 Google 电子表格 1 年)。我查看了 Google 电子表格的函数列表,其中 none 突然出现在我面前,使我以前没有想到的事情成为可能。
当问题是编程问题,问题是无法看到如何从 A 点到达 B 点时,我不知道它对 "show what I've done" 有用。我考虑过按期拆分。我一直在寻找 map
等效函数。我知道如何使用 isblank()
和 counta()
.
前言
电子表格内置函数不包含 JavaScript .map
的等效函数。另一种方法是使用电子表格数组处理功能和迭代模式。
“完整的解决方案”可能包括使用内置函数自动将用户输入转换为简单的 table 并返回工作分解结构编号 (WBS)。有些人将用户输入转换为简单的 table 称为“规范化”,但包含此内容会使此 post 对于 Stack Overflow 格式而言太长,因此将重点介绍一个简短的获得WBS的公式。
值得一提的是,使用公式将大型数据集转换为简单的 table 作为连续电子表格计算的一部分,在这种情况下,WBS 会使电子表格变慢刷新。
简答
为了使 WBS 公式简短,首先将用户输入转换为简单的 table,包括任务名称、ID 和父 ID 列,然后使用如下公式:
=ArrayFormula(
IFERROR(
INDEX($D:$D,MATCH($C2,$B:$B,0))
&"."
&COUNTIF($C:$C2,C2),
RANK($B2,FILTER($B:B,LEN($C:$C)=0),TRUE)&"")
)
说明
首先,准备好你的数据
- 将每项任务排成一行。
包括一个通用任务/项目,用作所有根级任务的父项。
- 为每个任务添加一个 ID。
- 为每个任务添加对父任务 ID 的引用。
常规任务/项目留空。
完成上述步骤后,数据应如下所示:
+---+--------------+----+-----------+
| | A | B | C |
+---+--------------+----+-----------+
| 1 | Task | ID | Parent ID |
| 2 | General task | 1 | |
| 3 | Substast 1 | 2 | 1 |
| 4 | Substast 2 | 3 | 1 |
| 5 | Subsubtask 1 | 4 | 2 |
| 6 | Subsubtask 2 | 5 | 2 |
+---+--------------+----+-----------+
备注:这也有助于减少自定义函数所需的处理时间。
其次,将下面的公式添加到D2中,然后根据需要向下填充,
=ArrayFormula(
IFERROR(
INDEX($D:$D,MATCH($C2,$B:$B,0))
&"."
&COUNTIF($C:$C2,C2),
RANK($B2,FILTER($B:B,LEN($C:$C)=0),TRUE)&"")
)
结果应如下所示:
+---+--------------+----+-----------+----------+
| | A | B | C | D |
+---+--------------+----+-----------+----------+
| 1 | Task | ID | Parent ID | WBS |
| 2 | General task | 1 | | 1 |
| 3 | Substast 1 | 2 | 1 | 1.1 |
| 4 | Substast 2 | 3 | 1 | 1.2 |
| 5 | Subsubtask 1 | 4 | 2 | 1.1.1 |
| 6 | Subsubtask 2 | 5 | 2 | 1.1.2 |
+---+--------------+----+-----------+----------+
大声笑这是最长的(并且很可能是组合公式的最不必要的复杂方法)但是因为我认为它确实有效很有趣,只要你在第一行添加一个 1然后在第二行添加:
=if(row()=1,1,if(and(istext(D2),counta(split(A1,"."))=3),left(A1,4)&n(right(A1,1)+1),if(and(isblank(B2),isblank(C2),isblank(D2)),"",if(and(isblank(B2),isblank(C2),isnumber(indirect(address(row()-1,column())))),indirect(address(row()-1,column()))&"."&if(istext(D2),round(max(indirect(address(1,column())&":"&address(row()-1,column())))+0.1,)),if(and(isblank(B2),istext(C2)),round(max(indirect(address(1,column())&":"&address(row()-1,column())))+0.1,2),if(istext(B2),round(max(indirect(address(1,column())&":"&address(row()-1,column())))+1,),))))))
在我看来,我工作了很长的一天 - 把本来应该很简单的事情复杂化似乎是我今天的事:)
这里的答案不允许项目之间有空行,并且需要您在第一个单元格 (A2) 中手动键入“1”。此公式应用于单元格 A3,假设 B、C 和 D 列中最多有三个级别的层次结构。
=IF(
COUNTA(B3), // If there is a value in the 1st column
INDEX(SPLIT(A2,"."),1)+1, // find the 1st part of the prior ID, plus 1
IF( // ...otherwise
COUNTA(C3), // If there's a value in the 2nd column
INDEX(SPLIT(A2,"."),1) // find the 1st part of the prior ID
& "." // add a period and
& IFERROR(INDEX(SPLIT(A2,"."),2),0)+1, // add the 2nd part of the prior ID (or 0), plus 1
INDEX(SPLIT(A2,"."),1) // ...otherwise find the 1st part of the prior ID
& "." // add a period and
& IFERROR(INDEX(SPLIT(A2,"."),2),1) // add the 2nd part of the prior ID or 1 and
& "." // add a period and
& IFERROR(INDEX(SPLIT(A2,"."),3)+1,1) // add the 3rd part of the prior ID (or 0), plus 1
)
) & "" // Ensure the result is a string ("1.2", not 1.2)
没有评论:
=IF(COUNTA(B3),INDEX(SPLIT(A2,"."),1)+1,IF(COUNTA(C3),INDEX(SPLIT(A2,"."),1)& "."& IFERROR(INDEX(SPLIT(A2,"."),2),0)+1,INDEX(SPLIT(A2,"."),1)& "."& IFERROR(INDEX(SPLIT(A2,"."),2),1)& "."& IFERROR(INDEX(SPLIT(A2,"."),3)+1,1))) & ""
使用 Google 表格,我想像这样自动对行编号:
关键是我希望它只使用内置函数。
我有一个实现,其中子项位于单独的列中(例如,"Foo" 在 B 列中,"Bar" 在 C 列中,"Baz" 在 D 列中) .但是,它使用自定义 JavaScript 函数,自定义 JavaScript 函数的计算速度较慢,结合依赖项,可能与较慢的 Internet 连接相结合,意味着我的解决方案可以接管一秒钟每行 (!) 计算。
作为参考,这是我的自定义函数(我想放弃以支持本机代码):
/**
* Calculate the Work Breakdown Structure id for this row.
*
* @param {range} priorIds IDs that precede this one.
* @param {range} names The names for this row.
* @return A WBS string id (e.g. "2.1.5") or an empty string if there are no names.
* @customfunction
*/
function WBS_ID(priorIds,names){
if (Array.isArray(names[0])) names = names[0];
if (!names.join("")) return "";
var lastId,pieces=[];
for (var i=priorIds.length;i-- && !lastId;) lastId=priorIds[i][0];
if (lastId) pieces = (lastId+"").split('.').map(function(s){ return s*1 });
for (var i=0;i<names.length;i++){
if (names[i]){
var s = pieces.concat();
pieces.length=i+1;
pieces[i] = (pieces[i]||0) + 1;
return pieces.join(".");
}
}
}
例如,单元格 A7 将使用公式:
=WBS_ID(A:A6,B7:D7)
...产生结果“1.3.2”
请注意,在上面的示例中,空白行在编号过程中被跳过。不尊重这一点的答案——其中 ID 是根据 ROW()
) 确定地计算的——是可以接受的(甚至可能是可取的)。
编辑:是的,我自己也尝试过这样做。我有一个解决方案使用三个额外的列,我选择不包括在问题中。我已经在 Excel 中编写方程式至少 25 年(以及 Google 电子表格 1 年)。我查看了 Google 电子表格的函数列表,其中 none 突然出现在我面前,使我以前没有想到的事情成为可能。
当问题是编程问题,问题是无法看到如何从 A 点到达 B 点时,我不知道它对 "show what I've done" 有用。我考虑过按期拆分。我一直在寻找 map
等效函数。我知道如何使用 isblank()
和 counta()
.
前言
电子表格内置函数不包含 JavaScript .map
的等效函数。另一种方法是使用电子表格数组处理功能和迭代模式。
“完整的解决方案”可能包括使用内置函数自动将用户输入转换为简单的 table 并返回工作分解结构编号 (WBS)。有些人将用户输入转换为简单的 table 称为“规范化”,但包含此内容会使此 post 对于 Stack Overflow 格式而言太长,因此将重点介绍一个简短的获得WBS的公式。
值得一提的是,使用公式将大型数据集转换为简单的 table 作为连续电子表格计算的一部分,在这种情况下,WBS 会使电子表格变慢刷新。
简答
为了使 WBS 公式简短,首先将用户输入转换为简单的 table,包括任务名称、ID 和父 ID 列,然后使用如下公式:
=ArrayFormula( IFERROR( INDEX($D:$D,MATCH($C2,$B:$B,0)) &"." &COUNTIF($C:$C2,C2), RANK($B2,FILTER($B:B,LEN($C:$C)=0),TRUE)&"") )
说明
首先,准备好你的数据
- 将每项任务排成一行。
包括一个通用任务/项目,用作所有根级任务的父项。 - 为每个任务添加一个 ID。
- 为每个任务添加对父任务 ID 的引用。
常规任务/项目留空。
完成上述步骤后,数据应如下所示:
+---+--------------+----+-----------+ | | A | B | C | +---+--------------+----+-----------+ | 1 | Task | ID | Parent ID | | 2 | General task | 1 | | | 3 | Substast 1 | 2 | 1 | | 4 | Substast 2 | 3 | 1 | | 5 | Subsubtask 1 | 4 | 2 | | 6 | Subsubtask 2 | 5 | 2 | +---+--------------+----+-----------+
备注:这也有助于减少自定义函数所需的处理时间。
其次,将下面的公式添加到D2中,然后根据需要向下填充,
=ArrayFormula( IFERROR( INDEX($D:$D,MATCH($C2,$B:$B,0)) &"." &COUNTIF($C:$C2,C2), RANK($B2,FILTER($B:B,LEN($C:$C)=0),TRUE)&"") )
结果应如下所示:
+---+--------------+----+-----------+----------+ | | A | B | C | D | +---+--------------+----+-----------+----------+ | 1 | Task | ID | Parent ID | WBS | | 2 | General task | 1 | | 1 | | 3 | Substast 1 | 2 | 1 | 1.1 | | 4 | Substast 2 | 3 | 1 | 1.2 | | 5 | Subsubtask 1 | 4 | 2 | 1.1.1 | | 6 | Subsubtask 2 | 5 | 2 | 1.1.2 | +---+--------------+----+-----------+----------+
大声笑这是最长的(并且很可能是组合公式的最不必要的复杂方法)但是因为我认为它确实有效很有趣,只要你在第一行添加一个 1然后在第二行添加:
=if(row()=1,1,if(and(istext(D2),counta(split(A1,"."))=3),left(A1,4)&n(right(A1,1)+1),if(and(isblank(B2),isblank(C2),isblank(D2)),"",if(and(isblank(B2),isblank(C2),isnumber(indirect(address(row()-1,column())))),indirect(address(row()-1,column()))&"."&if(istext(D2),round(max(indirect(address(1,column())&":"&address(row()-1,column())))+0.1,)),if(and(isblank(B2),istext(C2)),round(max(indirect(address(1,column())&":"&address(row()-1,column())))+0.1,2),if(istext(B2),round(max(indirect(address(1,column())&":"&address(row()-1,column())))+1,),))))))
在我看来,我工作了很长的一天 - 把本来应该很简单的事情复杂化似乎是我今天的事:)
这里的答案不允许项目之间有空行,并且需要您在第一个单元格 (A2) 中手动键入“1”。此公式应用于单元格 A3,假设 B、C 和 D 列中最多有三个级别的层次结构。
=IF(
COUNTA(B3), // If there is a value in the 1st column
INDEX(SPLIT(A2,"."),1)+1, // find the 1st part of the prior ID, plus 1
IF( // ...otherwise
COUNTA(C3), // If there's a value in the 2nd column
INDEX(SPLIT(A2,"."),1) // find the 1st part of the prior ID
& "." // add a period and
& IFERROR(INDEX(SPLIT(A2,"."),2),0)+1, // add the 2nd part of the prior ID (or 0), plus 1
INDEX(SPLIT(A2,"."),1) // ...otherwise find the 1st part of the prior ID
& "." // add a period and
& IFERROR(INDEX(SPLIT(A2,"."),2),1) // add the 2nd part of the prior ID or 1 and
& "." // add a period and
& IFERROR(INDEX(SPLIT(A2,"."),3)+1,1) // add the 3rd part of the prior ID (or 0), plus 1
)
) & "" // Ensure the result is a string ("1.2", not 1.2)
没有评论:
=IF(COUNTA(B3),INDEX(SPLIT(A2,"."),1)+1,IF(COUNTA(C3),INDEX(SPLIT(A2,"."),1)& "."& IFERROR(INDEX(SPLIT(A2,"."),2),0)+1,INDEX(SPLIT(A2,"."),1)& "."& IFERROR(INDEX(SPLIT(A2,"."),2),1)& "."& IFERROR(INDEX(SPLIT(A2,"."),3)+1,1))) & ""