尝试简化多个for循环以减少Google Script中的执行时间

时间:2018-04-06 11:01:42

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

我正在编写一个后端到我们用来处理设备分配的电子表格。

我们这样做的方法之一是使用谷歌表单,该表单以以下格式将数据输出到电子表格:

时间戳|职位描述|笔记本电脑|打印机|开始日期|结束日期|几个月涉及|工作时间。

我编写了两个函数来计算作业的持续时间和所涉及的月份,因为所有其他字段都是由用户输入的(并由输入表单验证)。

唯一的问题是,当我调用这两个函数时,它们需要大约15-20秒才能执行。我觉得这很可能是因为我遍历了数据的每一行,但我对Jscript很新,我无法理解如何提高它的效率。我能得到一些建议吗?不一定要寻找代码完美的响应,因为我正在使用它来学习,但是正确的方向上的一些点将不胜感激。

以下代码:

// Calculates the duration of the job in days (including weekends / bank holidays).

function dateCalc() {

    var ss = SpreadsheetApp.getActive()
    var sh = ss.getSheetByName('Form responses')
    var selection = sh.getDataRange()

  // Declares the relevant columns

    var columnResult = 8
    var columnConditional1 = 5
    var columnConditional2 = 6
    var rows = selection.getNumRows()

  // Initiates a loop statement to iterate through the rows. On each row, provided there is a start date and an end date present and the difference hasn't already been calculated,
  // calculates difference and outputs it to the relevant column.

    for (var row=2; row <= rows; row++){

           if (sh.getRange(row,5).getValue() != '' && sh.getRange(row,6).getValue() != '' && sh.getRange (row,8).getValue() == "") {
            var startDate = sh.getRange(row,5).getValue()
            var endDate = sh.getRange(row,6).getValue()
            var dateDiff = Math.floor((endDate-startDate)/(24*3600000))
            sh.getRange(row,columnResult).setValue(dateDiff)
           }
    }
}

function DateMonths(){

  // Pulls in the "Moment.Js" library which allows heavy date manipulation. every mention of "moment" from here on is calling this library.
  // Also sets the moment language to english so dates are read "dd/mm/yyyy" instead of "mm/dd/yyyy"

  eval(UrlFetchApp.fetch('https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.22.0/moment-with-locales.js').getContentText());
  moment.locale('en')

  //Declaring the range of the active data and assigning it to the "rows" variable.

  var ss = SpreadsheetApp.getActive()
  var sh = ss.getSheetByName('Form responses')
  var selection = sh.getDataRange()
  var rows = selection.getNumRows()

  //Same conditional as previous function, but streamlined into a single line using && to denote AND.

  for (var row=2; row <= rows; row++){
    if (sh.getRange(row,5).getValue() != '' && sh.getRange(row,6).getValue() != '' && sh.getRange (row,8).getValue() == "") {

         var startDate = sh.getRange(row,5).getValue()
         var endDate = sh.getRange(row,6).getValue()
         var dateStart = moment(startDate, 'DD-MM-YYYY')
         var dateEnd = moment(endDate, 'DD-MM-YYYY')


         // Creates a blank array timeValues

         var timeValues = [];

         //initiates a while loop under the condition that the start month is less than the end month, and outputs the month in plain english.

         while (dateEnd > dateStart || dateStart.format('M') === dateEnd.format('M')) {
           timeValues.push(dateStart.format('MMMM'));
           dateStart.add(1,'month');

           }
         // outputs the array of months between start and end to the cell in the row concerned. Note that this is OUTSIDE of the while loop, but INSIDE of the for loop to ensure it 
         // only prints the finished array for every row.

         sh.getRange(row,7).setValue(timeValues.join(', '))
     }
  }
}

}

1 个答案:

答案 0 :(得分:0)

您可以进行两项重大改进,其中一项取决于您的数据:

  1. 使用getValues()循环遍历数组,而不是范围。访问范围是very slow
  2. 使用内置函数来操作日期,而不是使用Moment.js。 Utilities.formatDate()getMonth()setMonth()
  3. 如果您每次运行脚本时都要打印大量数据,请考虑使用setValues(),因为setValue()需要更多时间。 (我想你可能一次只能打几行,所以如果你需要的话,我就把它留给你实施。)
  4. 请确保输出都进入正确的列,否则,这应该运行得多,很多更快。

    // Calculates the duration of the job in days (including weekends / bank holidays).
    
    function dateCalc() {
      var ss = SpreadsheetApp.getActive();
      var sh = ss.getSheetByName('Form responses');
      var selection = sh.getRange("A:H"); // Define your range a little more to limit the amount of work done
      var rows = selection.getValues(); // Get the values as an Array[][], rather than having to access the range. MUCH FASTER!!!!
      var columnResult = 8;
      // Initiates a loop statement to iterate through the rows. On each row, provided there is a start date and an end date present and the difference hasn't already been calculated,
      // calculates difference and outputs it to the relevant column.
    
      for (var i=1; i < rows.length; i++){
        var timestamp = rows[i][0];
        if (timestamp == "") {
          // Because the data is submitted in a form, there should be no empty rows. 
          // If we find a row to be empty, as judged by the automatically filled timestamp, 
          // then we will stop looping through the data. No need to look at empty values ¯\_(ツ)_/¯ 
          // If you're not comfortable with this, you can use "continue;" instead. 
          break; 
        }
        var startDate = rows[i][4]; // Arrays are 0-indexed, so we use the Column # - 1 = 4
        var endDate = rows[i][5];
        var dateDiff = rows[i][7];
        // All the sh.getRange().getValue() functions take forever!
        if (startDate != "" && endDate != "" && dateDiff == "") {
          dateDiff = Math.floor((endDate-startDate)/(24*3600000))
          sh.getRange(i+1,columnResult).setValue(dateDiff)
        }
      }
    }
    
    function DateMonths(){  
      //Declaring the range of the active data and assigning it to the "rows" variable.
      var ss = SpreadsheetApp.getActive();
      var sh = ss.getSheetByName('Form responses');
      var selection = sh.getRange("A:H");
      var rows = selection.getValues();
      var columnResult = 7;
      //Same conditional as previous function, but streamlined into a single line using && to denote AND.
      for (var i=1; i < rows.length; i++){
        var timestamp = rows[i][0];
        if (timestamp == "") {
          break;
        }
        var startDate = rows[i][4];
        var endDate = rows[i][5];
        var dateDiff = rows[i][7];
        if (startDate != "" && endDate != "" && dateDiff == "") {
          startDate = sh.getRange(i+1,5).getValue()
          endDate = sh.getRange(i+1,6).getValue()
          var dateStart = new Date(startDate);
          var dateEnd = new Date(endDate);
    
          // Creates a blank array timeValues
          var timeValues = [];
    
          //initiates a while loop under the condition that the start month is less than the end month, and outputs the month in plain english.
          while (dateEnd > dateStart || dateStart.getMonth() === dateEnd.getMonth()) {
            timeValues.push(Utilities.formatDate(dateStart, "GMT", "MMMMM")); 
            dateStart.setMonth(dateStart.getMonth() + 1);
          }
          // outputs the array of months between start and end to the cell in the row concerned. Note that this is OUTSIDE of the while loop, but INSIDE of the for loop to ensure it 
          // only prints the finished array for every row.
    
          sh.getRange(i+1,columnResult).setValue(timeValues.join(', '));
        }
      }
    }