【问题标题】:Google Script version of VLookup (More Efficient Method?)VLookup 的 Google Script 版本(更有效的方法?)
【发布时间】:2020-02-17 03:59:29
【问题描述】:

我正在尝试组合一个函数,该函数允许我根据关键列将列的信息从一个工作表拉到另一个工作表。这类似于 excel/google 中的索引匹配或 vlookup。

样本数据:

我的尝试:

function vlookup(importFromSht, importToSht, importFromCompCol, importToCompCol,importFromCol, importToCol){
  var lastImportFromRN = importFromSht.getLastRow();
  var lastImportToRN = importToSht.getLastRow();
  var importFromCompArr =  importFromSht.getRange(2, importFromCompCol, lastImportFromRN, 1).getValues();
  var importToCompArr =  importToSht.getRange(2, importToCompCol, lastImportToRN, 1).getValues();
  var importFromArr =  importFromSht.getRange(2, importFromCol, lastImportFromRN, 1).getValues();
  var importToArr = [];

  for (var i in importToCompArr) {
    for (var j in importFromCompArr){
      if (importToCompArr[i].toString() == importFromCompArr[j].toString()) { 
        importToArr.push(importFromArr[j]);
      }
    }
  }

  //Paste to column
  importToSht.getRange(2,importToCol,importToArr.length,1).setValues(importToArr);
}

定义的参数

  • importFromSht - 我们从中获取值的工作表。
  • importToSht - 工作表值将被复制到。
  • importFromCompCol - 具有要匹配的值的列(数字)。
  • importToCompCol - 具有要匹配的值的列(数字)。
  • importFromCol - 包含需要复制的值的列(数字)。
  • importToCol - 要将值复制到的列(数字)。

要求:

  • 最少有超过 6K 行,可能还有数千行。执行速度很重要。我的方法是正确的方法还是有更有效的方法?我的脚本增加了大约 30 秒的执行时间。
  • 愿意将其称为函数,因为我可以看到自己在此项目的其他领域和其他项目中使用它。

【问题讨论】:

  • importToArr.length 可能是 0。当它根本不起作用时,速度有多重要?
  • @MartinZeitler 是的。这就是正在发生的事情,但我不确定我的代码哪里出错了。肯定有比赛。
  • @MartinZeitler 我补充说这是一项要求,因为我想确保我的方法是有效的,如果不是建议,将不胜感激。
  • 不能将自定义函数命名为vlookup,因为已经有一个具有该名称的内置函数。另请记住,自定义函数的最长执行时间为 30 秒。参考。 developers.google.com/apps-script/guides/sheets/functions。无论如何,如果您的脚本正在运行,那么问题在这里是题外话,但可以在 Code Review 中成为话题。
  • @Rub 不是自定义函数。他正试图从另一个函数调用它

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


【解决方案1】:

问题:

  • 慢速脚本:

    • O(n2):对于数组 1 中的每个元素,数组 2 从上到下迭代。即使在数组 2 中找到匹配项后,循环也不会中断(break),但内部循环会不必要地完成,直到数组 2 结束。

    • getValues() 为同一张工作表的两列请求了两次。与电子表格联系是昂贵的。所以,限制它是必要的。

解决办法:

实现O(n)的一种可能解决方案:

  • 使用数组 1 创建一个新对象,其中键为“要查找的值”。那么对于数组 2 中的每个值,每次都可以直接访问这个对象中的值。

示例脚本:

const ss = SpreadsheetApp.getActive();
/**
 * @param {GoogleAppsScript.Spreadsheet.Sheet} fromSht -Sheet to import from
 * @param {GoogleAppsScript.Spreadsheet.Sheet} toSht -Sheet to import to
 * @param {Number} fromCompCol -Column number of fromSht to compare
 * @param {Number} toCompCol -Column number of toSht to compare
 * @param {Number} fromCol -Column number of fromSht to get result
 * @param {Number} toCol -Column number of toSht to get result
 */
function vlookup_2(
  fromSht = ss.getSheetByName('Sheet1'),
  toSht = ss.getSheetByName('Sheet2'),
  fromCompCol = 1,
  toCompCol = 1,
  fromCol = 2,
  toCol = 2
) {
  const toShtLr = toSht.getLastRow();
  const toCompArr = toSht.getRange(2, toCompCol, toShtLr - 1, 1).getValues();
  const fromArr = fromSht.getDataRange().getValues();
  fromCompCol--;
  fromCol--;

  /*Create a hash object of fromSheet*/
  const obj1 = fromArr.reduce((obj, row) => {
    let el = row[fromCompCol];
    el in obj ? null : (obj[el] = row[fromCol]);
    return obj;
  }, {});

  //Paste to column
  toSht
    .getRange(2, toCol, toShtLr - 1, 1)
    .setValues(toCompArr.map(row => (row[0] in obj1 ? [obj1[row[0]]] : [null])));
}

性能:

  • ~5s 为 sheet1 中的 10000 行和 sheet 2 中的 10000 行

参考资料:

【讨论】:

  • 注意:你必须enable v8否则。
  • 缩短了 30 秒。太棒了!
  • 如果我错了,请纠正我,但这不是比我需要的列多吗?获取值是否比获取所有数据并稍后粘贴要差两倍?
  • 减少数据大小是好的,但不会以两次 getValues() 调用为代价。大数据的一次调用优于小数据的两次调用。测试并查看。使用console.time()
【解决方案2】:

不幸的是,Apps Script 中恐怕没有内置函数。

但是,我已经使用 100 个值尝试了您的自定义函数,并且运行时间不到 3 秒。我还用 1000 个值运行它,运行时间约为 40 秒。它并不理想,但它始终如一地工作。这是我使用的代码:

    var sheet = SpreadsheetApp.getActiveSpreadsheet();
    var importFromSht = SpreadsheetApp.getActive().getSheetByName('Sheet1'); //sheet we are grabbing the values of
    var importToSht = SpreadsheetApp.getActive().getSheetByName('Sheet2');  //sheet we are pasting our values
    var importFromCompCol = 2; // Column (number) that has values to match on.
    var importToCompCol = 2; // Column (number) that has values to match on.
    var importFromCol = 1; // Column (number) that contains value that needs to be copied.
    var importToCol = 1; // Column (number) to copy value to.
    
    function customVlookup (){
      var lastImportFromRN = importFromSht.getLastRow();
      var lastImportToRN = importToSht.getLastRow();
      var importFromCompArr =  importFromSht.getRange(1, importFromCompCol, lastImportFromRN, 1).getValues();
      var importToCompArr =  importToSht.getRange(1, importToCompCol, lastImportToRN, 1).getValues();
      var importFromArr =  importFromSht.getRange(1, importFromCol, lastImportFromRN, 1).getValues();
      var importToArr = [];
    
      for (var i in importToCompArr) {
        for (var j in importFromCompArr){
          if (importToCompArr[i].toString() == importFromCompArr[j].toString()) { 
            importToArr.push(importFromArr[j]);
          }
        }
      }
      //Paste to column
      importToSht.getRange(1,importToCol,importToArr.length,1).setValues(importToArr);
    }

另一种更有效的方法是使用IMPORTRANGE(第一个工作表的URL)到一个中间工作表交换第一个工作表的列,然后在第二个工作表上执行VLOOKUP。这将比在 Apps 脚本中执行更有效,因为它不在内存中运行,并且您避免了超出自定义函数执行时间的问题。

如果您想将其保留为 Apps 脚本自定义函数 Here,请参考文档中有关如何提高函数效率的一些建议。

【讨论】:

    猜你喜欢
    • 2022-11-15
    • 2012-07-14
    • 1970-01-01
    • 2023-03-10
    • 1970-01-01
    • 1970-01-01
    • 2018-03-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多