更快找到第一个空行的方法

Omi*_*iod 32 performance google-sheets google-apps-script

我制作了一个脚本,每隔几个小时就会在Google Apps电子表格中添加一个新行.

这是我为找到第一个空行而做的功能:

function getFirstEmptyRow() {
  var spr = SpreadsheetApp.getActiveSpreadsheet();
  var cell = spr.getRange('a1');
  var ct = 0;
  while ( cell.offset(ct, 0).getValue() != "" ) {
    ct++;
  }
  return (ct);
}
Run Code Online (Sandbox Code Playgroud)

它工作正常,但当达到约100行时,它变得非常慢,甚至十秒.我担心当达到数千行时,它会太慢,可能会超时或更糟.有没有更好的办法?

Don*_*kby 46

Google Apps脚本博客上发布了一篇关于优化电子表格操作的文章,该文章讨论了批量读取和写入操作,这些操作可以真正加快速度.我在100行的电子表格上尝试了你的代码,大约花了7秒钟.通过使用Range.getValues(),批处理版本需要一秒钟.

function getFirstEmptyRow() {
  var spr = SpreadsheetApp.getActiveSpreadsheet();
  var column = spr.getRange('A:A');
  var values = column.getValues(); // get all data in one call
  var ct = 0;
  while ( values[ct][0] != "" ) {
    ct++;
  }
  return (ct);
}
Run Code Online (Sandbox Code Playgroud)

如果电子表格足够大,您可能需要以100或1000行的块来获取数据,而不是抓取整个列.

  • 仅当您可以保证A列中具有空白单元格的行为“空”时,此方法才有效。对于while循环,它也没有停止条件,因此,如果A列中的每个单元都已满,它将(并且确实)引发异常。Serge的适应处理停止条件。 (2认同)
  • 此外,结果为 1。如果第一个“空”行是 10(电子表格计算),则为 9(数组计算)。 (2认同)

Mog*_*dad 35

这个问题现在有超过12K的观点 - 所以现在是时候进行更新了,因为New Sheets的性能特征与Serge进行初始测试时不同.

好消息:整体表现要好得多!

最快的:

与第一次测试一样,只需读取一次工作表的数据,然后在阵列上操作,就可以获得巨大的性能优势.有趣的是,Don的原始功能比Serge测试的修改版本表现得更好.(看起来whilefor这更快,这不符合逻辑.)

样本数据的平均执行时间仅为38毫秒,低于之前的168毫秒.

// Don's array approach - checks first column only
// With added stopping condition & correct result.
// From answer https://stackoverflow.com/a/9102463/1677912
function getFirstEmptyRowByColumnArray() {
  var spr = SpreadsheetApp.getActiveSpreadsheet();
  var column = spr.getRange('A:A');
  var values = column.getValues(); // get all data in one call
  var ct = 0;
  while ( values[ct] && values[ct][0] != "" ) {
    ct++;
  }
  return (ct+1);
}
Run Code Online (Sandbox Code Playgroud)

检测结果:

以下是结果,总结了超过50次迭代的电子表格,包含100行x 3列(填充了Serge的测试功能).

函数名称与下面脚本中的代码匹配.

截图

"第一空行"

最初的问题是找到第一个空行.之前的任何脚本都没有真正实现.许多人只检查一列,这意味着他们可以给出误报结果.其他人只找到所有数据后面的第一行,这意味着错过了非连续数据中的空行.

这是一个符合规范的功能.它被包括在测试中,虽然比闪电般快速的单柱检查器慢,但却以相当可观的68毫秒,正确答案的50%溢价!

/**
 * Mogsdad's "whole row" checker.
 */
function getFirstEmptyRowWholeRow() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var range = sheet.getDataRange();
  var values = range.getValues();
  var row = 0;
  for (var row=0; row<values.length; row++) {
    if (!values[row].join("")) break;
  }
  return (row+1);
}
Run Code Online (Sandbox Code Playgroud)

完整的脚本:

如果您想重复测试,或者将自己的函数添加到混合中作为比较,只需获取整个脚本并在电子表格中使用它.

/**
 * Set up a menu option for ease of use.
 */
function onOpen() {
  var menuEntries = [ {name: "Fill sheet", functionName: "fillSheet"},
                      {name: "test getFirstEmptyRow", functionName: "testTime"}
                     ];
  var sh = SpreadsheetApp.getActiveSpreadsheet();
  sh.addMenu("run tests",menuEntries);
}

/**
 * Test an array of functions, timing execution of each over multiple iterations.
 * Produce stats from the collected data, and present in a "Results" sheet.
 */
function testTime() {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  ss.getSheets()[0].activate();
  var iterations = parseInt(Browser.inputBox("Enter # of iterations, min 2:")) || 2;

  var functions = ["getFirstEmptyRowByOffset", "getFirstEmptyRowByColumnArray", "getFirstEmptyRowByCell","getFirstEmptyRowUsingArray", "getFirstEmptyRowWholeRow"]

  var results = [["Iteration"].concat(functions)];
  for (var i=1; i<=iterations; i++) {
    var row = [i];
    for (var fn=0; fn<functions.length; fn++) {
      var starttime = new Date().getTime();
      eval(functions[fn]+"()");
      var endtime = new Date().getTime();
      row.push(endtime-starttime);
    }
    results.push(row);
  }

  Browser.msgBox('Test complete - see Results sheet');
  var resultSheet = SpreadsheetApp.getActive().getSheetByName("Results");
  if (!resultSheet) {
    resultSheet = SpreadsheetApp.getActive().insertSheet("Results");
  }
  else {
    resultSheet.activate();
    resultSheet.clearContents();
  }
  resultSheet.getRange(1, 1, results.length, results[0].length).setValues(results);

  // Add statistical calculations
  var row = results.length+1;
  var rangeA1 = "B2:B"+results.length;
  resultSheet.getRange(row, 1, 3, 1).setValues([["Avg"],["Stddev"],["Trimmed\nMean"]]);
  var formulas = resultSheet.getRange(row, 2, 3, 1);
  formulas.setFormulas(
    [[ "=AVERAGE("+rangeA1+")" ],
     [ "=STDEV("+rangeA1+")" ],
     [ "=AVERAGEIFS("+rangeA1+","+rangeA1+',"<"&B$'+row+"+3*B$"+(row+1)+","+rangeA1+',">"&B$'+row+"-3*B$"+(row+1)+")" ]]);
  formulas.setNumberFormat("##########.");

  for (var col=3; col<=results[0].length;col++) {
    formulas.copyTo(resultSheet.getRange(row, col))
  }

  // Format for readability
  for (var col=1;col<=results[0].length;col++) {
    resultSheet.autoResizeColumn(col)
  }
}

// Omiod's original function.  Checks first column only
// Modified to give correct result.
// question https://stackoverflow.com/questions/6882104
function getFirstEmptyRowByOffset() {
  var spr = SpreadsheetApp.getActiveSpreadsheet();
  var cell = spr.getRange('a1');
  var ct = 0;
  while ( cell.offset(ct, 0).getValue() != "" ) {
    ct++;
  }
  return (ct+1);
}

// Don's array approach - checks first column only.
// With added stopping condition & correct result.
// From answer https://stackoverflow.com/a/9102463/1677912
function getFirstEmptyRowByColumnArray() {
  var spr = SpreadsheetApp.getActiveSpreadsheet();
  var column = spr.getRange('A:A');
  var values = column.getValues(); // get all data in one call
  var ct = 0;
  while ( values[ct] && values[ct][0] != "" ) {
    ct++;
  }
  return (ct+1);
}

// Serge's getFirstEmptyRow, adapted from Omiod's, but
// using getCell instead of offset. Checks first column only.
// Modified to give correct result.
// From answer https://stackoverflow.com/a/18319032/1677912
function getFirstEmptyRowByCell() {
  var spr = SpreadsheetApp.getActiveSpreadsheet();
  var ran = spr.getRange('A:A');
  var arr = []; 
  for (var i=1; i<=ran.getLastRow(); i++){
    if(!ran.getCell(i,1).getValue()){
      break;
    }
  }
  return i;
}

// Serges's adaptation of Don's array answer.  Checks first column only.
// Modified to give correct result.
// From answer https://stackoverflow.com/a/18319032/1677912
function getFirstEmptyRowUsingArray() {
  var sh = SpreadsheetApp.getActiveSpreadsheet();
  var ss = sh.getActiveSheet();
  var data = ss.getDataRange().getValues();
  for(var n=0; n<data.length ;  n++){
    if(data[n][0]==''){n++;break}
  }
  return n+1;
}

/**
 * Mogsdad's "whole row" checker.
 */
function getFirstEmptyRowWholeRow() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var range = sheet.getDataRange();
  var values = range.getValues();
  var row = 0;
  for (var row=0; row<values.length; row++) {
    if (!values[row].join("")) break;
  }
  return (row+1);
}

function fillSheet(){
  var sh = SpreadsheetApp.getActiveSpreadsheet();
  var ss = sh.getActiveSheet();
  for(var r=1;r<1000;++r){
    ss.appendRow(['filling values',r,'not important']);
  }
}

// Function to test the value returned by each contender.
// Use fillSheet() first, then blank out random rows and
// compare results in debugger.
function compareResults() {
  var a = getFirstEmptyRowByOffset(),
      b = getFirstEmptyRowByColumnArray(),
      c = getFirstEmptyRowByCell(),
      d = getFirstEmptyRowUsingArray(),
      e = getFirstEmptyRowWholeRow(),
      f = getFirstEmptyRowWholeRow2();
  debugger;
}
Run Code Online (Sandbox Code Playgroud)


Pet*_*ann 22

它已作为Sheet上的getLastRow方法存在.

var firstEmptyRow = SpreadsheetApp.getActiveSpreadsheet().getLastRow() + 1;
Run Code Online (Sandbox Code Playgroud)

参考https://developers.google.com/apps-script/class_sheet#getLastRow

  • 这个问题是,如果列A有10行而列B有100,则返回100.要获取列的最后一行,您必须遍历内容(据我所知) (4认同)
  • 嗯,那么为什么不是`SpreadsheetApp.getActiveSpreadsheet().getRange('A:A').getLastRow()+ 1`? (2认同)

Ser*_*sas 8

看到这个有5k视图的老帖子我首先查看了"最佳答案"并且对其内容感到非常惊讶......这确实是一个非常缓慢的过程!当我看到Don Kirkby的答案时,我感觉更好,阵列方法确实更有效率!

但效率更高?

所以我在1000行的电子表格上写了这个小测试代码,结果如下:(不错!......不需要告诉哪一个是...)

在此输入图像描述 在此输入图像描述

这是我使用的代码:

function onOpen() {
  var menuEntries = [ {name: "test method 1", functionName: "getFirstEmptyRow"},
                      {name: "test method 2 (array)", functionName: "getFirstEmptyRowUsingArray"}
                     ];
  var sh = SpreadsheetApp.getActiveSpreadsheet();
  sh.addMenu("run tests",menuEntries);
}

function getFirstEmptyRow() {
  var time = new Date().getTime();
  var spr = SpreadsheetApp.getActiveSpreadsheet();
  var ran = spr.getRange('A:A');
  for (var i= ran.getLastRow(); i>0; i--){
    if(ran.getCell(i,1).getValue()){
      break;
    }
  }
  Browser.msgBox('lastRow = '+Number(i+1)+'  duration = '+Number(new Date().getTime()-time)+' mS');
}

function getFirstEmptyRowUsingArray() {
  var time = new Date().getTime();
  var sh = SpreadsheetApp.getActiveSpreadsheet();
  var ss = sh.getActiveSheet();
  var data = ss.getDataRange().getValues();
  for(var n =data.length ; n<0 ;  n--){
    if(data[n][0]!=''){n++;break}
  }
  Browser.msgBox('lastRow = '+n+'  duration = '+Number(new Date().getTime()-time)+' mS');
}

function fillSheet(){
  var sh = SpreadsheetApp.getActiveSpreadsheet();
  var ss = sh.getActiveSheet();
  for(var r=1;r<1000;++r){
    ss.appendRow(['filling values',r,'not important']);
  }
}
Run Code Online (Sandbox Code Playgroud)

并且测试电子表格自己尝试:-)


编辑:

根据Mogsdad的评论,我应该提到这些函数名称确实是一个糟糕的选择......它应该是一个getLastNonEmptyCellInColumnAWithPlentyOfSpaceBelow()不是很优雅的东西(是吗?),但更准确和与它实际返回的内容一致.

评论:

无论如何,我的观点是显示两种方法的执行速度,它显然做到了(不是吗?;-)


Nic*_*olo 7

我知道这是一个旧线程,这里有一些非常聪明的方法。

我使用脚本

var firstEmptyRow = SpreadsheetApp.getActiveSpreadsheet().getLastRow() + 1;
Run Code Online (Sandbox Code Playgroud)

如果我需要第一个完全空的行。

如果我需要列中的第一个空单元格,我会执行以下操作。

  • 我的第一行通常是标题行。
  • 我的第二行是隐藏行,每个单元格都有公式

    =COUNTA(A3:A)
    
    Run Code Online (Sandbox Code Playgroud)

    whereA替换为列字母。

  • 我的脚本只是读取这个值。与脚本方法相比,这更新得非常快。

有一次这不起作用,那就是当我允许空单元格拆分列时。我还不需要修复这个问题,我怀疑一个可能来自COUNTIF,或一个组合函数或许多其他内置函数之一。

编辑: COUNTA确实处理范围内的空白单元格,因此对“一次这不起作用”的担忧并不是真正的问题。(这可能是“新表格”的新行为。)


小智 5

为什么不使用appendRow

var spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
spreadsheet.appendRow(['this is in column A', 'column B']);
Run Code Online (Sandbox Code Playgroud)

  • @Jpsy 您可以从礼貌培训中受益。随意质疑旧答案或问题的有效性 - 毕竟,这些答案来自旧表格产品,事情可能已经改变。但他们没有——而且你没有任何证据支持你的煽动性声明。[这个剪辑清楚地展示了 appendRow() 的问题](http://i.stack.imgur.com/AM1h1.gif):它没有找到 [FIRST](http://dictionary.cambridge.org/dictionary /english/first) 空行。(包含内容的最后一行之后的第一行,是的 - 但同样,这不是我们所要求的。) (3认同)
  • 为什么不?因为它没有回答这个问题——“第一个空行”还没有被识别出来。相反,一个新行被添加到电子表格的底部,即使它上面有“空”行。 (2认同)