Google sparesheet formula/function 无需重新计算即可获得随机值(randbetween)

Google sparesheet formula/function to get random values without recalculating(randbetween)

我正在努力实现以下目标。在 google sparesheet 中,我有一个 sheet 的值为 "AllValues",在另一个 sheet "Randomvalues" 中,我想从 sheet "AllValues".

我尝试了两种选择,首先我尝试了 randbetween 公式:

=INDEX(AllValues!A4:A103,RANDBETWEEN(1,COUNTA(AllValues!A4:A103)),1)

它正在工作,但它 refresh/recalculate 新值一直在更改列。谷歌搜索了很多,似乎没有什么可以冻结已经计算出的结果。

接下来我尝试了函数:

    function random() {
      var sss = SpreadsheetApp.getActiveSpreadsheet();
      var ss = sss.getSheetByName('Values'); //the sheet that has the data
      var range = ss.getRange(1,1,ss.getLastRow(), 4); //the range you need: 4 columns on all row which are available
      var data = range.getValues();

      for(var i = 0; i < data.length; i++) 
      { 
        var j = Math.floor(Math.random()*(data[i].length)); //method of randomization
        var element = data[i][j]; // The element which is randomizely choose
        ss.getRange(i+1, 6).setValue(element); 
      }
    }

但是这个函数对我不起作用,google sparesheet 在第 11 行给出错误,setVaue 是不允许的。

第 11 行:ss.getRange(i+1, 6).setValue(element);

这个也google了一下,有很多建议,但是我对功能不是很熟悉,没能搞定。

希望有人能帮助我。

有多种方法可以实现这个目标。

自定义菜单

如@Tanaike 所述,您可以使用自定义菜单避免重新计算和公式依赖:

// @OnlyCurrentDoc
// Create a function that binds the "simple trigger" for the open event:
function onOpen(e) {
  // Add a menu to the UI with the function we want to be able to invoke.
  const ui = SpreadsheetApp.getUi();
  ui.createMenu("Randomizer")
    .addItem("Sample from 'AllValues' sheet", "sampleAllValues")
    .addToUi();
}

然后你需要一个匹配这个名字的函数定义sampleAllValues,当用户选择关联的菜单选项时,它会在点击用户的权限下被调用(首先会提示用户提供根据脚本的 OAuth 范围同意访问。

function sampleAllValues() {
  const wb = SpreadsheetApp.getActive();
  const destination = wb.getSheetByName("RandomValues");
  const source = wb.getSheetByName("AllValues");
  if (!source || !destination)
    throw new Error("Missing required sheets 'RandomValues' and 'AllValues'");

  // Create a flat array of all non-empty values in all rows and columns of the source sheet.
  const data = source.getDataRange().getValues().reduce(function (compiled, row) {
    var vals = row.filter(function (val) { return val !== ""; });
    if (vals.length)
      Array.prototype.push.apply(compiled, vals);
    return compiled;
  }, []);

  // Sample the smaller of 50 elements or 10% of the data, without replacement.
  const sample = [];
  var sampleSize = Math.min(50, Math.floor(data.length * .1));
  while (sampleSize-- > 0)
  {
    var choice = Math.floor(Math.random() * data.length);
    Array.prototype.push.apply(sample, data.splice(choice, 1));
  }

  // If we have any samples collected, write them to the destination sheet.
  if (sample.length)
  {
    destination.getDataRange().clearContent();
    // Write a 2D column array.
    destination.getRange(1, 1, sample.length, 1)
      .setValues(sample.map(function (element) { return [ element ]; }));
    // Write a 2D row array
    // destination.getRange(1, 1, 1, sample.length)
    //   .setValues( [sample] );
  }
}

自定义函数

如果您仍想使用 RandomValues sheet 中的自定义函数,例如

RandomValues!A1: =sampleAllValues(50, AllValues!A1:A)

那么您需要 return sample 而不是写入特定的 sheet。请注意,自定义函数被确定性地对待——它们在输入时计算,然后仅在其参数值发生变化时才重新计算。范围非常有限的自定义函数 运行,因此请务必查看它们的限制。 上面的用法提示您可能会发现允许传入所需样本的数量以及要从中采样的值很有用:

function sampleAllValues(sampleSize, value2Darray) {
  const data = value2Darray.reduce(function (compiled, row) {
    /* as above */
  }, []);
  /* sample as above */
  return sample; // Must be 2D row or 2D column array, or a single primitive e.g. `1`
}

无论您采用哪种方式,请务必通过查看脚本的 Stackdriver 日志来检查脚本的错误日志记录。 (查看 -> Stackdriver 日志记录)

参考文献:

使用公式通常假设重复计算。您无法阻止它们,只能尝试 return 旧值。此任务并不简单,因为任何公式都不能引用要 returned 结果的同一单元格(发生循环引用)。请勿使用公式进行单次计算。

另一方面,使用脚本函数可以直接生成所需的数据,并且只生成一次或按需生成。我认为,下面的函数将帮助您了解样本源和目标范围的所有必要步骤。

function random() {
  var source = "AllValues!A4:A103",
      target = "RandomValues!F2:F22";
  var ss = SpreadsheetApp.getActiveSpreadsheet();
  var sourceValues = ss.getRange(source).getValues(),
      targetRange = ss.getRange(target),
      targetValues = [];
  while (targetValues.length < targetRange.getHeight()) {
    var randomIndex = Math.floor(Math.random() * sourceValues.length);
    targetValues.push(sourceValues[randomIndex]);
  }
  targetRange.setValues(targetValues);
}

您可以运行手动或选择合​​适的触发器。