1

I am trying to avoid the following code because it is too slow:

 for (var c = 25; c>2; c--){

    if (sheet2.getRange(1,c).getValue() == 0) 

    {sheet2.deleteColumn(c)}

  }

Instead I tried to find a list of columns I want to delete from the array and then set the array. (I recently figure out that deleting rows/columns in a loop is very expensive: google script loop performance)

I found this Removing columns of data in javascript array and try to apply it to my code, but it is not working.

Here is the code.

  var ary = sheet2.getRange(2,1,outData.length+1,outData[0].length).getValues();
  var indexesToRemove = [];
  for (var c = 25; c>2; c--){

    if (sheet2.getRange(1,c).getValue() == 0)

    {
      indexesToRemove.push(c);

    }
  }

The part above works well. What is not working is the function to remove the columns from the array once I found the indexes to remove. The array _row is not what I am looking for. What am I doing wrong?

removeColumns(ary, indexesToRemove);}



function removeColumns(data, indexes) {
    return data.map(function (row) {
        // when we remove columns, the indexing gets off by 1 each time, keep track of how many to adjust
        var indexAdjustment = 0;
        // copy row w/ .slice so we do not modify the original array
        var _row = row.slice();
        indexes.forEach(function (colIndex) {
            // remove column
            _row.splice(colIndex - indexAdjustment, 1);
            // add 1 to adjustment to account for the column we just removed
            indexAdjustment++
        });
        return _row;
      var ss = SpreadsheetApp.getActiveSpreadsheet();
      var sheet2 = ss.getSheetByName('Cache');  
      sheet2.clear();
      sheet2.getRange(2,1,_row.length,_row[0].length).setValues(_row);
    });
}  

BTW, I have also tried this before, but still not working:

  var ary = sheet2.getRange(2,1,outData.length+1,outData[0].length).getValues();
  for (var c = 25; c>2; c--){

    if (sheet2.getRange(1,c).getValue() == 0)

    { ary = ary.map(function(item){
        return item.splice(0,c)});

    }
  }
Filippo
  • 281
  • 2
  • 4
  • 17
  • Can I ask you about the goal you expect? Does your goal include to use Sheets API? – Tanaike Apr 14 '20 at 23:16
  • I would rather not because I am still learning and the API may be an extra complication. However, there may be value to other users to post a solution which involves APIs. I would love to take a look at that too, but please add as many comments as possible in the code =) – Filippo Apr 15 '20 at 00:01
  • Thank you for replying. From your replying, I proposed 3 patterns for achieving your goal. Could you please confirm it? If I misunderstood your question and those were not the directions you expect, I apologize. – Tanaike Apr 15 '20 at 04:59
  • Great thanks!! I will have a look later today – Filippo Apr 15 '20 at 15:58

3 Answers3

2
  • You want to delete the columns that the value of is 0 in the cells C1:Y1.
  • You want to reduce the process cost of the script.
  • You want to achieve this without using Sheets API.

Pattern 1:

In this pattern, at first, the cells which have the value of 0 from the cells C1:Y1 using TextFinder, and the columns are deleted from the retrieved cells using deleteColumn().

Sample script:

const sheet = SpreadsheetApp.getActiveSheet();

sheet.getRange("C1:Y1")
  .createTextFinder(0)
  .matchEntireCell(true)
  .findAll()
  .reverse()
  .forEach(e => sheet.deleteColumn(e.getColumn()));

Pattern 2:

In this pattern, at first, all values are retrieved from "C1" to the last column for the all data rows, and delete the columns in the array and clear the range, and then, the values are put to the sheet. The method for directly processing the retrieved values has already been proposed. So as other pattern, I proposed the method which uses the transpose.

Sample script:

const sheet = SpreadsheetApp.getActiveSheet();

const range = sheet.getRange(1, 3, sheet.getLastRow(), sheet.getLastColumn() - 2);
const values = range.getValues();
const t = values[0].reduce((ar, r, i) => {
  if (r != 0) ar.push(values.map(c => c[i]));
  return ar;
}, []);
const v = t[0].map((_, i) => t.map(c => c[i]));
range.clearContent();
sheet.getRange(1, 3, v.length, v[0].length).setValues(v);

Pattern 3:

In this pattern, the request body for the batchUpdate method of Sheets API is created using the 1st row values, and the request body is used for requesting to Sheets API. By this, several columns can be deleted by one API call.

Before you run the script, please enable Sheets API at Advanced Google services.

Sample script:

const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
const sheet = spreadsheet.getActiveSheet();
const sheetId = sheet.getSheetId();

// Create rerequests for DeleteDimensionRequest.
const requests = sheet.getRange("C1:Y1")
  .createTextFinder(0)
  .matchEntireCell(true)
  .findAll()
  .reverse()
  .map(e => {
    const col = e.getColumn();
    return {deleteDimension: {range: {sheetId: sheetId, dimension: "COLUMNS", startIndex: col - 1, endIndex: col}}}
  });

// Request to the batchUpdate method using the request body.
Sheets.Spreadsheets.batchUpdate({requests: requests}, spreadsheet.getId());
  • In this case, requests is created using the method of pattern 1. Each request is as follows. You can see about this structure at the document.

    {
      "deleteDimension": {
        "range": {
          "sheetId": "###",
          "dimension": "COLUMNS",
          "startIndex": ##,
          "endIndex": ##
        }
      }
    }
    

References:

Tanaike
  • 105,090
  • 8
  • 51
  • 83
  • Quick question: Pattern 1 is similar to what I had in my code above where you deleteColumn() in a loop. Hence it is an expensive operation. Correct? – Filippo Apr 15 '20 at 18:21
  • @Filippo Thank you for replying. Yes. But it is a bit lower than your 1st script. In the pattern 1, the cells which have `0` are directly retrieved by TextFinder. By this, the loop counter is less than your script. And also, in your 1st script, you retrieve the value from the cell each loop. From this situation, the cost of the pattern 1 is a bit lower than yours. But I think that the pattern 2 and 3 will be much lower cost than the pattern 1. How about other patterns? – Tanaike Apr 15 '20 at 22:25
  • I tried "pattern 2" and it worked! I could not understand it at all because I do not know Javascript, but it works! I will need to find the time to get a Javascript course so that I can understand those complicated functions. – Filippo Apr 15 '20 at 22:32
  • @Filippo Thank you for replying. About Javascript, there are a lot of sample scripts in Stackoverflow. Those are the valuable information. I think that those are useful for learning it. I can also study it from them. – Tanaike Apr 15 '20 at 23:00
1
function runOne() {
  var d=0;
  var ss=SpreadsheetApp.getActive();
  var sh=ss.getActiveSheet();
  var hA=sh.getRange(1,1,1,sh.getLastColumn()).getValues()[0];//header array
  var vs=sh.getRange(2,1,sh.getLastRow()-1,sh.getLastColumn()).getValues();//data array
  vs.forEach(function(r,j){
    var d=0;
    hA.forEach(function(h,i){
      if(h==0)r.splice(i-d++,1);//removes elements in columns whose headers are == 0
    });
  });
  Logger.log(vs);
}
Cooper
  • 36,005
  • 6
  • 17
  • 42
  • Hi, Thank you for your answer. You are deleting columns one by one within the array which is what is causing the low performance. I am trying to remove the columns without using the deleteColumn() inside the loop. – Filippo Apr 14 '20 at 23:08
  • Take a look now. Just deleting the values in those columns in the array. – Cooper Apr 14 '20 at 23:51
  • Thanks, I will check it tomorrow, I will need to adapt it to my existing codes and variables. It will take me a while since I am new to this :) – Filippo Apr 15 '20 at 00:04
0

Try using Sheets Advanced Service and batchUpdate.

Related

Reference

Rubén
  • 24,097
  • 9
  • 55
  • 116