更有效的“删除重复”功能

时间:2018-01-24 18:01:40

标签: javascript google-apps-script google-sheets

我管理的Google表格列表有时超过10,000行。对于行数最多约5,000的工作表,下面提到的删除重复项功能可以正常工作。但是对于5,000以上的任何东西,我都会收到“超出最长执行时间”的错误。我将非常感谢有关如何使代码更高效的一些说明,即使对于具有10k +行的工作表,它也可以顺利运行。

function removeDuplicates() {
  var sheet = SpreadsheetApp.getActiveSheet();
  var data = sheet.getDataRange().getValues();
  var newData = new Array();
  for(i in data){
    var row = data[i];
    var duplicate = false;
    for(j in newData){
      if(row.join() == newData[j].join()){
        duplicate = true;
      }
    }
    if(!duplicate){
      newData.push(row);
    }
  }
  sheet.clearContents();
  sheet.getRange(1, 1, newData.length, newData[0].length).setValues(newData);
}

1 个答案:

答案 0 :(得分:5)

有一些事情会让你的代码变慢。让我们看看你的两个for循环:

for (i in data) {
  var row = data[i];
  var duplicate = false;

  for (j in newData){
    if (row.join() == newData[j].join()) {
      duplicate = true;
    }
  }

  if (!duplicate) {
    newData.push(row);
  }
}

从表面上看,您正在做正确的事情:对于原始数据中的每一行,检查新数据是否已有匹配的行。如果没有,请将行添加到新数据中。但是,在此过程中,您需要做很多额外的工作。

例如,考虑一下这样一个事实,即data中的一行在newData中只有一行匹配。但是在你的内部for循环中,在找到一个匹配后,它仍然继续检查newData中的其余行。对此的解决方案是在break;之后添加duplicate = true;以停止迭代。

还要考虑对于任何给定的jnewData[j].join()的值始终相同。假设您在data中有100行,并且没有重复(最坏的情况)。当你的功能完成时,你已经计算了newData[0].join() 99次,newData[1].join() 98次...总而言之,你已经完成了近5,000次计算以获得相同的99次值。对此的解决方案是memoization,您可以存储计算结果,以避免以后再次进行相同的计算。

即使您进行了这两项更改,您的代码time complexity仍然是O(n²)。如果你有100行数据,在最坏的情况下,内循环将运行4,950次。对于10,000行,这个数字约为5000万。

然而,我们可以做到这一点是 O n )时间,如果我们摆脱内部循环并重新形成外部循环,如下所示:

var seen = {};

for (var i in data) {
  var row = data[i];
  var key = row.join();

  if (key in seen) {
    continue;
  }
  seen[key] = true;
  newData.push(row);
}

此处,我们不会在每次迭代中检查newData的每一行中是否匹配row的行,而是将我们所见过的每一行存储到对象{{1 }}。然后在每次迭代中,我们只需要检查seen是否具有匹配seen的密钥,我们可以在几乎恒定的时间内执行的操作,或者 O 1 < / em>的)。 1

作为一个完整的功能,这就是它的样子:

row

您会看到,而不是使用function removeDuplicates_() { const startTime = new Date(); const sheet = SpreadsheetApp.getActiveSheet(); const data = sheet.getDataRange().getValues(); const numRows = data.length; const newData = []; const seen = {}; for (var i = 0, row, key; i < numRows && (row = data[i]); i++) { key = JSON.stringify(row); if (key in seen) { continue; } seen[key] = true; newData.push(row); } sheet.clearContents(); sheet.getRange(1, 1, newData.length, newData[0].length).setValues(newData); // Show summary const secs = (new Date() - startTime) / 1000; SpreadsheetApp.getActiveSpreadsheet().toast( Utilities.formatString('Processed %d rows in %.2f seconds (%.1f rows/sec); %d deleted', numRows, secs, numRows / secs, numRows - newData.length), 'Remove duplicates', -1); } function onOpen() { SpreadsheetApp.getActive().addMenu('Scripts', [ { name: 'Remove duplicates', functionName: 'removeDuplicates_' } ]); } 此代码使用row.join(),因为JSON.stringify(row)是脆弱的(例如row.join())。 ['a,b', 'c'].join() == ['a', 'b,c'].join()不是免费的,但对我们来说这是一个很好的妥协。

在我的测试中,这会处理一个简单的电子表格,其中包含50,000行和2列,时间超过8秒,或者每秒约6,000行。