使用本机函数计算 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)&"")
 )

说明

首先,准备好你的数据

  1. 将每项任务排成一行。 包括一个通用任务/项目,用作所有根级任务的父项。
  2. 为每个任务添加一个 ID。
  3. 为每个任务添加对父任务 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))) & ""