将”B2:H2″类似的范围转换为单元格数组的最佳方式,以及/或将该范围平移。

huangapple go评论61阅读模式
英文:

Optimal way of turning a "B2:H2"-like range into an array of cells, and/or shifting that range

问题

I'll provide translations for the code parts you've shared, excluding the code itself:

Part 1: Checking Empty Cells

//Part 1: 检查空单元格
var rangeArray = rangeToArray(range);
var emptyCells = 0;
for (var o = 0; o < rangeArray.length; o++) {
  if (sheet.getRange(rangeArray[o]).isBlank()) { emptyCells++; }
}

Part 2: Copying Data

//Part 2: 复制数据
gatherIntoListIndexes(); //这生成了listIndexes,这是文档中所有表格的左上角单元格坐标的数组,几乎用于所有操作
var AvailableLine = searchBlank(sheetUTI, listIndexes[4], listIndexes[5], 2); //查找表格中最顶部的空行,返回指定长度(此处为3列)的相应范围
sheetUTI.getRange(range).copyTo(sheetUTI.getRange(AvailableLine), {contentsOnly:true}); //使用App脚本的复制功能,从第一个表格复制到第二个表格
sheetUTI.getRange(rangeShift(AvailableLine, 3, 1)).uncheck(); //rangeShift接受一个范围,按指定的列数(此处为3)和指定的长度(此处为1)移动它,对应着刚刚复制数据旁边要取消选中的2个复选框
sheetUTI.getRange(range).clearContent(); //清除初始表格的数据

Range to Array of Cells Converter

//将范围转换为单元格数组的自定义函数
function rangeToArray(range) {
  var posColon = range.indexOf(":"); //冒号的位置
  var posSecondLetter = 0; //如果存在第二个字母,就是第二个字母的位置,否则是第一个字母的位置
  var posFourthLetter = posColon + 1; //如果存在第四个字母,就是第四个字母的位置,否则是第三个字母的位置
  var posTemp1 = -1;
  var posTemp2 = -1;
  var rangeArray = [];
  //循环遍历所有26个字母,查找与可能的第二个或第四个字母匹配的字母
  for (var k = 0; k < 26; k++) {
    posTemp1 = range.indexOf(letterArray[k], 1);
    posTemp2 = range.indexOf(letterArray[k], posColon + 2);
    if (posTemp1 != -1 && posTemp1 < posColon) {
      //如果找到第二个字母(如果有的话)
      posSecondLetter = posTemp1;
    }
    if (posTemp2 != -1) {
      //如果找到第四个字母(如果有的话)
      posFourthLetter = posTemp2;
    }
  }
  //将第一个单元格的列指示符、第一个单元格的行号、第二个单元格的列指示符和第二个单元格的行号分隔开
  var firstCellColumnIndex = numberOfLetter(range.slice(0, posSecondLetter + 1));
  var secondCellColumnIndex = numberOfLetter(range.slice(posColon + 1, posFourthLetter + 1));
  var firstCellRowIndex = range.slice(posSecondLetter + 1, posColon - posSecondLetter);
  var secondCellRowIndex = range.slice(posFourthLetter + 1, range.length);
  //生成单元格数组,包括它们之间的所有单元格
  for (var row = firstCellRowIndex; row <= secondCellRowIndex; row++) {
    for (var col = firstCellColumnIndex; col <= secondCellColumnIndex; col++) {
      rangeArray.push(letter(col) + row.toString());
    }
  }
  return rangeArray;
}

I hope this helps you understand the code better. If you have any specific questions or need further translations, feel free to ask.

英文:

I'll be giving a bit of context:

Using App script, I'm working on a macro which copies data from a sheet to another. To avoid copying mistakes cascading, I added code to check whether or not the cells are empty. There are 7 cells to check and the first or 5th one may be empty. (they either are both filled, or one of the 2 is blank, if the 2 are blank it's invalid), to simplify I made it so that the ranges are always continuous and on a single line, so only the columns change.

To achieve my goal I did a simple counting loop:

var rangeArray = rangeToArray(range);
var emptyCells = 0;
for (var o = 0; o &lt; rangeArray.length; o++) {
  if (sheet.getRange(rangeArray[o]).isBlank()) { emptyCells ++; }
}

and only copy if the value of emptyCells is < 2 (it is presumming the cells they leave empty are the correct ones but I can't reasonably account for all human errors).

(Please ignore the var, I tried using let, but for obscure reasons App Script doesn't support it)

range is the range in the A1 format such as "B2:H2",

emptyCells is the counter of empty cells,

sheet.getRange(rangeArray[o]).isBlank()) returns true when the cell is blank, false otherwise.

rangeToArray(range) however is where my question lies in, it's a custom function I made which turns range into an array of cells, such as "B2:H2" → ["B2", "C2", "D2", "E2", "F2", "G2", "H2"]

Because it has to account for ranges anywhere between A1 and AZ999..., it has to work for both 1 letter and 2 letters column indexes on both the 1st and 2nd cell indexes as well as for any finite row indexes.

The code displayed below works, however, It's having about 1.8 +/- 0.2 seconds execution time which is slow, even for a Google Sheet macro. (simplier macros on that sheet run between 0.3 and 1.4 seconds)
I'm not a JS dev, I started learning it a little over a week ago specificaly for that project; so it's the best I could do after spending a few days on the matter but I have no idea how good or bad that actualy is. If anyone has suggestions for improvements or see obvious optimisations, please go ahead I'm all hears.

P.S.: I already tried adding an if statement to break out of the for loop in as soon as both the 2nd and 4th letters are found but it actualy increases execution time by 0.2 seconds on average.

My range to array of cells converter is as follows:

function rangeToArray(range) {
  var posColon = range.indexOf(&quot;:&quot;); // position of the colon
  var posSecondLetter = 0; // position of the 2nd letter if there is one, the 1st one otherwise
  var posFourthLetter = posColon+1; // position of the 4th letter if there is one, the 3rd one otherwise
  var posTemp1 = -1;
  var posTemp2 = -1;
  var rangeArray = [];
  // Loops through all 26 letters to find if it matches with a potential 2nd or 4th letter
  for (var k = 0; k &lt; 26; k++) {
    posTemp1 = range.indexOf(letterArray[k], 1);
    posTemp2 = range.indexOf(letterArray[k], posColon+2);
    if (posTemp1 != -1 &amp;&amp; posTemp1 &lt; posColon) {
      // it found what the 2nd letter is if there is one
      posSecondLetter = posTemp1;
    }
    if (posTemp2 != -1) {
      // it found what the 4th letter is if there is one
      posFourthLetter = posTemp2;
    }
  }
  // isolate the according column indicators of the 1st and 2nd cell as well as their row numbers
  var firstCellColumnIndex = numberOfLetter(range.slice(0, posSecondLetter+1));
  var secondCellColumnIndex = numberOfLetter(range.slice(posColon+1, posFourthLetter+1));
  var firstCellRowIndex = range.slice(posSecondLetter+1, posColon-posSecondLetter);
  var secondCellRowIndex = range.slice(posFourthLetter+1, range.length);
  //generating the array of cell inbetween and including them
  for (var row = firstCellRowIndex; row &lt;= secondCellRowIndex; l++) {
    for (var col = firstCellColumnIndex; col  &lt;= secondCellColumnIndex; m++) {
      rangeArray.push(letter(col)+row.toString());
    }
  }
  return rangeArray;
}

letterArray is just an array of all capital letters in alphabetical order, I made it for 6 other functions to convert from numerical index to alphabetical since unlike Ada, JS can't count in alphabetical indexes, so it came in handy there.

numberOfLetter() is a custom function which takes a capital letter character or pair of characters and returns their corresponding numerical index, the reciprocal of letter(). ("A" → 1, "AZ" → 52)

letter() is a custom function which takes a numerical index and returns their corresponding alphabetical index. (1 → "A", 52 → "AZ")

I doubt there's much to improve in letter() or numberOfLetter(), unless there's a trick I don't know about for loops but here they are if you wish.

function letter(number) {
  if (number &lt;= 26) { //indexes from A to Z
    return listeLettres[number-1];
  } else if ((26 &lt; number) &amp;&amp; (number &lt;= 52)) { //indexes from AA to AZ
    return &quot;A&quot;+listeLettres[number-27];
  }
}

function numberOfLetter(letter) {
  for (var i = 0; i &lt; 26; i++) { //indexes from A to Z
    if (letter == listeLettres[i]) {
      return i+1;
    }
  }
  for (var j = 0; j &lt; 26; j++) { //indexes from AA to AZ
    if (letter == &quot;A&quot;+listeLettres[j]) {
      return 26+j+1;
    }
  }
}

(I'm aware the multiple returns are bad practice but considering I'm forced to have all variables as global thanks to App Script I'd rather limit unecessery intermediary variables where I can + it also seems to shorten execution time and the lower I can get that, the happier I am xD)

EDIT: 21/03

People seem confused by the title and what/why exactly I'm trying to do things. So, a more "accurate title" would be "how can I optimise X program to reduce execution time" where that program is this:

function transfertUTI(range) {
//Part 1: Checking if no more than 3 cells are empty so that it doesn&#39;t execute if it&#39;s not filled properly
  var rangeArray = rangeToArray(range); //Converts range to array of cells
  var emptyCells = 0;
  for (var q = 0; q &lt; rangeArray.length; q++) {
    if (sheetUTI.getRange(rangeArray[q]).isBlank()) {
      emptyCells++; // counts how many cells in the range are empty
    }
  }
  if (emptyCells &lt;= 2) {
//Part 2: Actualy do the program
    gatherIntoListIndexes(); //This generates listIndexes which is an array with the coordonates of the topleftmost cell of all tables in the document which is used for nearly everything
    var AvailableLine = searchBlank(sheetUTI, listIndexes[4], listIndexes[5], 2); //this looks for the topmost empty line in the table it&#39;s transfering to, and returns the corresponding range at the specified range length, in that case 3 columns long
    sheetUTI.getRange(range).copyTo(sheetUTI.getRange(AvailableLine), {contentsOnly:true}); //the copy function of app script which copies from the first table to the second table
    sheetUTI.getRange(rangeShift(AvailableLine, 3, 1)).uncheck(); //rangeShift takes in a range, shifts it by as many columns as specified (here 3), and reduces its length as specified (here 1), which corresponds to the 2 checkboxes to uncheck next to where the data was just copied
    sheetUTI.getRange(range).clearContent(); //Empties the data on the initial table

  }
}

However, the reason for my question being the way it is and the title being what it is, is that from my PoV the only serious optimisation possible in that code is in the 1st part (rangeToArray + checking for empty cells loop) and the way to deal around the constraints of the .CopyTo() function that is how I have to first copy and then uncheck the checkboxes.
And both of those are 2 key functions that are rangeToArray() and rangeShift() which use the same core algorithm to work.

They divide the range into 4 parts, the first letter cluster, first number cluster, second letter cluster and second number cluster; which corresponds respectively to the 1st cell's column index, 1st cell's row index, 2nd cell's column index and 2nd cell's row index, which I can manipulate independantly to get what I want.

In rangeToArray() it's so that I can generate the according array of cells, and in rangeShift() it's so that I can manipulate the indexes individualy and regenerate a shifted range from an input range regardless of its details.

So a way to optimise one may optimise both and/or be more optimal than just using both as is. And that's why I considered it more appropriate to call it that directly rather than "Can X be optimised ?", given I can't realy phrase X in a way that includes all the important details and isn't a paragraph long.

Anyway, if you want explanations about that function:
It's a macro triggered by a button, or a checkbox to work on mobile via the onEdit() function. Its purpose is to transfer the data of a container from the table of those that stay on the terminal to the one of those who will be loaded in a freighter. (for now both tables are on the same sheet (sheetUTI) however they may be splitted into 2 sheets)
Since there are as many buttons as there are lines (each line is a container), the function needs as input the range of the line in question to transfer the data of the right line.
There are 3 columns of data, so a container's data may be the range &quot;J6:L6&quot; for exemple.

Then, in the table where it's transfered to, there's 2 additional columns added at the end which indicate the loading progress. Those need to be unchecked (reset) when data is transfered.

(Here's rangeShift so that you can see how the only difference is the return's content)

function rangeShift(range, shift, length) {
  var posColon = range.indexOf(&quot;:&quot;);
  var posSecondLetter = 0;
  var posFourthLetter = posColon+1;
  var posTemp1 = -1;
  var posTemp2 = -1;
  for (var k = 0; k &lt; 26; k++) {
    posTemp1 = range.indexOf(listeLettres[k], 1);
    postemp2 = range.indexOf(listeLettres[k], posdp+2);
    if (posTemp1 != -1 &amp;&amp; posTemp1 &lt; posColon) {
      posSecondLetter = posTemp1;
    }
    if (posTemp2 != -1) {
      posFourthLetter = posTemp2;
    }
  }
//1st cell&#39;s column index
  return letter(numberOfLetter(range.slice(0, posSecondLetter+1))+shift)+
//1st cell&#39;s row index
range.slice(posSecondLetter+1, posColon-posSecondLetter)+
&quot;:&quot;+
//2nd cell&#39;s column index
letter(numberOfLetter(range.slice(posColon+1, posFourthLetter+1))+shift-length)+
//2nd cell&#39;s row index
range.slice(posFourthLetter+1, range.length);
}

答案1

得分: 0

function loadRange(name="Sheet1", row=5, col=5, rg="B2:H2") {
const ss = SpreadsheetApp.getActive();
const ssh = ss.getSheetByName("Sheet0");
const vs = ssh.getRange(rg).getValues();
const dsh = ss.getSheetByName(name);
dsh.getRange(row, col, vs.length, vs[0].length).setValues(vs);
}

英文:
function loadRange(name=&quot;Sheet1&quot;,row=5,col=5,rg=&quot;B2:H2&quot;) {
const ss = SpreadsheetApp.getActive();
const ssh = ss.getSheetByName(&quot;Sheet0&quot;);
const vs = ssh.getRange(rg).getValues();
const dsh = ss.getSheetByName(name);
dsh.getRange(row,col,vs.length,vs[0].length).setValues(vs);
}

答案2

得分: 0

I have to agree with TheMaster. Seems like a lot of work for nothing.

I creat a sheet as shown.

将”B2:H2″类似的范围转换为单元格数组的最佳方式,以及/或将该范围平移。

Then a test script. Notice I get the first element of the 2D array from getValues()[0]. Using =&gt; arrow function

function test() {
try {
let spread = SpreadsheetApp.getActiveSpreadsheet();
let sheet = spread.getSheetByName("Sheet1");
let range = "A1:J1";
let values = sheet.getRange(range).getValues()[0];
let emptyCells = 0;
values.forEach(cell => {
if (cell === "") emptyCells++;
}
);
console.log(emptyCells);
}
catch (err) {
console.log(err);
}
}

Using traditiona function function(cell) {

function test() {
try {
let spread = SpreadsheetApp.getActiveSpreadsheet();
let sheet = spread.getSheetByName("Sheet1");
let range = "A1:J1";
let values = sheet.getRange(range).getValues()[0];
let emptyCells = 0;
values.forEach(function (cell) {
if (cell === "") emptyCells++;
}
);
console.log(emptyCells);
}
catch (err) {
console.log(err);
}
}

And the execution log shows 2 empty cells.

6:19:36 AM Notice Execution started
6:19:36 AM Info 2
6:19:36 AM Notice Execution completed
英文:

I have to agree with TheMaster. Seems like a lot of work for nothing.

I creat a sheet as shown.

将”B2:H2″类似的范围转换为单元格数组的最佳方式,以及/或将该范围平移。

Then a test script. Notice I get the first element of the 2D array from getValues()[0]. Using =&gt; arrow function

function test() {
try {
let spread = SpreadsheetApp.getActiveSpreadsheet();
let sheet = spread.getSheetByName(&quot;Sheet1&quot;);
let range =&quot;A1:J1&quot;;
let values = sheet.getRange(range).getValues()[0];
let emptyCells = 0;
values.forEach( cell =&gt; {
if( cell === &quot;&quot; ) emptyCells++;
}
);
console.log(emptyCells);
}
catch(err) {
console.log(err);
}
}

Using traditiona function function(cell) {

function test() {
try {
let spread = SpreadsheetApp.getActiveSpreadsheet();
let sheet = spread.getSheetByName(&quot;Sheet1&quot;);
let range =&quot;A1:J1&quot;;
let values = sheet.getRange(range).getValues()[0];
let emptyCells = 0;
values.forEach( function (cell) {
if( cell === &quot;&quot; ) emptyCells++;
}
);
console.log(emptyCells);
}
catch(err) {
console.log(err);
}
}

And the execution log shows 2 empty cells.

6:19:36 AM	Notice	Execution started
6:19:36 AM	Info	2
6:19:36 AM	Notice	Execution completed

huangapple
  • 本文由 发表于 2023年3月21日 02:49:16
  • 转载请务必保留本文链接:https://go.coder-hub.com/75794184-2.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定