【问题标题】:Is there a better way to merge row data based on criteria (Google Apps Script)?有没有更好的方法来根据条件(Google Apps 脚本)合并行数据?
【发布时间】:2021-01-04 14:00:22
【问题描述】:

好的...首先我要说我是自学的 Google Apps 脚本...说得够多了,对吧!?下面的脚本正在运行,但我想优化它或想出另一种方法来实现相同的结果。该脚本采用 18000 行和 86 列数据,并根据 id 列表将它们组合成单行。 id 列表长约 13000 行。简短的版本是这样的......它通过 id 过滤数据,然后检查每一列是否有提交数据的最后一行并返回该单元格。例如:

//sample data
[[311112, 1, 2, 4, 5,"","","","","","", 2, 3],
[323223,"","","","","", 2, 4, 4,"","","",""],
[321321, 1, 2, 4, 5,"","","","","","", 2, 3],
[311112, 4, 1, 6, 7,"", 3,"", 3,"","", 5, 3],
[321233,"","","","","","", 4, 3, 1, 5,"",""],
[321321,"","","","","","","","", 1 ,4,"",""],
[323223,"","","","","", 2, 3,"","","","",""],
[323153,"", 2, 3, 6,"","","","","","","",""],
[321321,"","","","","", 2, 3,"","","","",""],
[321321,"", 5, 3,"", 1,"","","","","","",""]]

//filtered Data by id 321321
[[321321, 1, 2, 4, 5,"","","","","","", 2, 3],
[321321,"","","","","","","","", 1, 4,"",""],
[321321,"","","","","", 2, 3,"","","","",""],
[321321,"", 5, 3,"", 1,"","","","","","",""]]

// returned row is getting the last nonempty value for each column from the filtered data.

[[321321, 1, 5, 3, 5, 1, 2, 3,"", 1, 4, 2, 3]]

脚本完成大约需要 16-18 分钟。有没有更好的方法来完成这个或任何优化建议?

function combineR(startRow, startRange) {
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sheets = ss.getSheets();
  var testSheet = ss.getSheetByName('Raw Scores');
  var cSheet = ss.getSheetByName('Combined Scores');
  var gradingResults = testSheet.getRange(1, 1, testSheet.getLastRow(), testSheet.getLastColumn()).getValues();

  if (startRow > cSheet.getLastRow()) {
    return;
  }

  if (startRow + startRange > cSheet.getLastRow()) {
    startRange = cSheet.getLastRow() - startRow;
  }

  var sID = cSheet.getRange(startRow, 2, startRange).getValues();
  var maxScores = [];
  for (var x = 0; x < sID.length; x++) {
    var filtered = gradingResults.filter(function (dataRow) {
      return dataRow[0] === sID[x][0];
    });

    if (isFinite(filtered)) {
      maxScores.push(['', '', '', '', '', '', '', '', '', '',
        '', '', '', '', '', '', '', '', '', '',
        '', '', '', '', '', '', '', '', '', '',
        '', '', '', '', '', '', '', '', '', '',
        '', '', '', '', '']);
      continue;
    } else {
      maxScores.push(['', getMaxLetter(filtered, 3), lastGraded(filtered, 4), lastGraded(filtered, 5), lastGraded(filtered, 6), lastGraded(filtered, 7), lastGraded(filtered, 8), lastGraded(filtered, 9), lastGraded(filtered, 10), lastGraded(filtered, 11),
        lastGraded(filtered, 12), lastGraded(filtered, 13), lastGraded(filtered, 14), lastGraded(filtered, 15), lastGraded(filtered, 16), lastGraded(filtered, 17), lastGraded(filtered, 18), lastGraded(filtered, 19), lastGraded(filtered, 20), lastGraded(filtered, 21),
        lastGraded(filtered, 22), lastGraded(filtered, 23), lastGraded(filtered, 24), lastGraded(filtered, 25), lastGraded(filtered, 26), lastGraded(filtered, 27), lastGraded(filtered, 28), lastGraded(filtered, 29), lastGraded(filtered, 30), lastGraded(filtered, 31),
        lastGraded(filtered, 32), lastGraded(filtered, 33), lastGraded(filtered, 34), lastGraded(filtered, 35), lastGraded(filtered, 36), lastGraded(filtered, 37), lastGraded(filtered, 38), lastGraded(filtered, 39), lastGraded(filtered, 40), lastGraded(filtered, 41),
        lastGraded(filtered, 42), lastGraded(filtered, 43), lastGraded(filtered, 44), lastGraded(filtered, 45), lastGraded(filtered, 46)]);
    }
  }
  cSheet.getRange(startRow, 11, maxScores.length, maxScores[0].length).setValues(maxScores)
}

function getMaxLetter(arr, idx) {
  var letter = arr.map(function (e) { return e[idx] }).sort().pop();
  return letter;
}

function lastGraded(arr, idx) {
  var newArray = arr.map(function (e) { return e[idx] });
  newArray.reverse();
  for (var x = 0; x < newArray.length; x++) {
    if (typeof newArray[x] == 'number') {
      return newArray[x];
    }
  }
  return '';
}

A 列有重复的 Id 需要合并

B 列具有作为最终合并产品的唯一值

【问题讨论】:

  • 请提供两张纸的截图示例。
  • 在您的示例中,我不了解示例数据如何产生返回的行。你能澄清一下吗?也许屏幕截图会对此有所帮助。
  • 目前,这是通过按 Id 过滤数据然后获取该特定列并构建一个查找最后一个非空条目的列表来完成的。在我的示例中,除第一个元素外,每个集合中的第一个元素都是空白的,因此它返回 1,因为它是“最后一个”包含数据的非空提交。清如泥……对吧?
  • @TheMaster 添加了截图。
  • 我能理解你的top sample table的逻辑。但不幸的是,我无法理解您的 2 张图片的差异。可以问一下具体情况吗?

标签: javascript arrays performance google-apps-script optimization


【解决方案1】:

问题:

脚本似乎有各种问题,但主要问题似乎是多次使用各种索引调用lastGraded 函数。这对mapreverse 以及每个索引的其他所有操作都有效,而且会耗费时间。

解决办法:

鉴于您的样本数据,我建议采用以下方法:

  • 获取1个二维数组中的所有输入数据

  • Reduce 输入数据到Map。该地图将每个idkey 匹配,并将与key 匹配的所有行作为每个key 的二维array。这将以内存为代价大大提高性能/速度。这比按每个 id 过滤数组要好,因为,

    • 您只循环输入数组一次
    • arr.filter 必须为每个 id 循环数组
  • 一旦缩减为map,则反向循环遍历map 中的每个array,以查找最后一行中的每个元素以查找非空元素。

示例 sn-p:

const arrMain =
  //sample data
  [
    [311112, 1, 2, 4, 5, '', '', '', '', '', '', 2, 3],
    [323223, '', '', '', '', '', 2, 4, 4, '', '', '', ''],
    [321321, 1, 2, 4, 5, '', '', '', '', '', '', 2, 3],
    [311112, 4, 1, 6, 7, '', 3, '', 3, '', '', 5, 3],
    [321233, '', '', '', '', '', '', 4, 3, 1, 5, '', ''],
    [321321, '', '', '', '', '', '', '', '', 1, 4, '', ''],
    [323223, '', '', '', '', '', 2, 3, '', '', '', '', ''],
    [323153, '', 2, 3, 6, '', '', '', '', '', '', '', ''],
    [321321, '', '', '', '', '', 2, 3, '', '', '', '', ''],
    [321321, '', 5, 3, '', 1, '', '', '', '', '', '', ''],
  ];

//reduce input array to a map of id=>rows
const map = arrMain.reduce((map, row) => {
  if (!map.has(row[0])) map.set(row[0], [row]);
  else map.get(row[0]).push(row);
  return map;
}, new Map());

const out = [];
map.forEach(arr2d => {
  const l = arr2d.length - 1,
    lastRow = arr2d[l].slice(0);
  //iterate lastrow of this id's column elements
  for (let j = 0; j < lastRow.length; ++j) {
    if (lastRow[j] === '') {
      //iterate each row of this id
      for (let i = l; i >= 0; --i) {
        if (arr2d[i][j] !== '') {
          lastRow[j] = arr2d[i][j];
          break;
        }
      }
    }
  }
  out.push(lastRow);
});
console.log(out);

【讨论】:

  • 谢谢!我不知道 .filter 在运行时成本如此之高。 Google Apps 脚本没有从您的示例中运行几件事,但我能够遍历您的逻辑并修改我的脚本以使其正常工作。从 .filter 切换到 .reduce 已将运行时间从 16.5 分钟减少到 9.75 分钟。这更容易接受!再次感谢您花时间指导我!
  • @OneInAMillion filter 不是主要成本。它是这样的:maxScores.push(['', getMaxLetter(filtered, 3), lastGraded(filtered, 4), lastGraded(filtered, 5), lastGraded(filtered, 6), lastGraded(filtered, 7), lastGraded(filtered, 8), lastGraded(filtered, 9), lastGraded(filtered, 10), lastGraded(filtered, 11), lastGraded(filtered, 12), lastGrade.... 对于每个 lastGraded 调用,您不必要地使用 mapping 和 reverseing 和 looping 相同的数组。如果您使用我的方法或至少找到一种方法只调用 lastGraded 一次,您的脚本将以秒为单位运行
  • @OneInAMillion 您所说的 Google Apps 脚本没有从您的示例中运行一些东西是什么意思?升级到 v8,如果您还没有完成。我的示例与应用脚本 v8 引擎完全兼容。
  • 显然我还有很多东西要学。 V8 并没有像你说的那样出错。
  • 好的,我刚刚将它应用到我的数据中,哇!我花了 16 到 18 分钟处理的事情现在减少到 55 到 75 秒!惊人的!!!你分明就是大师!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-11-15
  • 2017-02-06
  • 2013-12-09
  • 1970-01-01
  • 1970-01-01
  • 2010-11-11
  • 1970-01-01
相关资源
最近更新 更多