Javascript 更高效的';删除重复项';功能

Javascript 更高效的';删除重复项';功能,javascript,google-apps-script,google-sheets,Javascript,Google Apps Script,Google Sheets,我管理的谷歌工作表列表有时超过10000行。对于行数不超过5000的图纸,下面提到的“删除重复项”功能可以很好地工作。但对于超过5000的任何内容,我都会收到“超出最大执行时间”错误。如果您能给我一些指导,告诉我如何提高代码的效率,使其即使对于10k+行的工作表也能顺利运行,我将不胜感激 function removeDuplicates() { var sheet = SpreadsheetApp.getActiveSheet(); var data = sheet.getDataRa

我管理的谷歌工作表列表有时超过10000行。对于行数不超过5000的图纸,下面提到的“删除重复项”功能可以很好地工作。但对于超过5000的任何内容,我都会收到“超出最大执行时间”错误。如果您能给我一些指导,告诉我如何提高代码的效率,使其即使对于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);
}

有两件事会让你的代码变慢。让我们看看你的两个
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
中的其余行。解决这个问题的办法是添加一个
中断之后<代码>重复=真
停止迭代

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

即使您做了这两个更改,您的代码仍然是。如果有100行数据,在最坏的情况下,内部循环将运行4950次。对于10000行,这个数字大约是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
的每一行,而是将看到的每一行存储为对象
seed
中的一个键。然后在每次迭代中,我们只需检查
seen
是否有一个键匹配
,这是一个我们可以在几乎恒定的时间内完成的操作,或者O(1)

作为一个完整的函数,它看起来是这样的:

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_' }
  ]);
}
函数移除的副本{
const startTime=新日期();
const sheet=SpreadsheetApp.getActiveSheet();
const data=sheet.getDataRange().getValues();
const numRows=data.length;
常量newData=[];
const seen={};
对于(变量i=0,行,键;i
您将看到,这段代码使用的不是
row.join()
,而是
JSON.stringify(row)
,因为
row.join()
是脆弱的(
['a,b','c'].join()==['a','b,c'].join()
,例如)<代码>JSON.stringify
不是免费的,但对于我们的目的来说,它是一个很好的折衷方案


在我的测试中,这将在8秒钟多一点的时间内处理一个包含50000行和2列的简单电子表格,即每秒6000行左右。

您可以设置一个
中断之后<代码>重复=真我不确定哪一个更有效,但您可以尝试使用indexOf()。@pnuts,事实上,我没有,因为我不知道存在任何索引。你能告诉我到哪里去吗?你可以用setsNote,即“输入对象”(输入对象)需要O(log(N))时间,而不是O(1)。所以整个算法取O(N*log(N)),而不是O(N)。如果我们能在固定时间内做到这一点,那将是一场相当大的革命:D@IvanKuckirSource?@jordan您应该显示源,它是O(1)。如果你学过计算机科学,很明显这是不可能的。当然,存在完美的散列函数和氨化结构,但它们仍然不是O(1)。@Jordan运行它是一个散列图(chrome只能优化重复模式,而这种情况显然不是)。如果天气好的话,这将需要O(1)。实际情况稍低,但仍然不如O(log(n))(理论上最坏的情况是O(n))。请看@JordanRunning似乎是V8使用的。我非常惊讶,因为红黑树通常用于此目的(例如在.Net中或在C++中用于std::structures)。总之,它们都是log(N)结构。