【问题标题】:Google sparesheet formula/function to get random values without recalculating(randbetween)谷歌备用表公式/函数无需重新计算即可获得随机值(randbetween)
【发布时间】:2019-01-14 20:23:20
【问题描述】:

我正在努力实现以下目标。在谷歌备用表中,我有一张值为“AllValues”的工作表,在另一张工作表“Randomvalues”中,我想从工作表“AllValues”中获取随机值。

我尝试了两个选项,首先我尝试了 randbetween 公式:

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

它正在工作,但它会在所有时间列更改时刷新/重新计算新值。谷歌了很多,似乎没有什么可以冻结已经计算的结果。

接下来我尝试了函数:

    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); 
      }
    }

但是这个功能对我不起作用,谷歌备用表在第 11 行给出错误,不允许 setVaue。

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

google了这个,有很多建议,但是我对功能不是很熟悉,我没有设法让它工作。

希望有人可以帮助我。

【问题讨论】:

  • 在您的情况下,您是否将该函数作为自定义函数运行?如果是这样,就会发生这样的错误。使用自定义函数时,即使脚本没有修改,脚本保存时也会刷新自定义函数。所以虽然我不确定你的情况的细节,例如,在脚本编辑器和自定义菜单中运行该功能怎么样?
  • 感谢您的评论。我什至不确定我是否将它作为自定义函数运行。这就是我的做法:在谷歌备用我选择“工具”>“脚本编辑器”然后我创建了脚本并尝试在我的谷歌备用表单元格中使用它,在“随机()”中输入 - 我不是很熟悉脚本和谷歌备用。
  • 感谢您的回复。在您的情况下,您使用的函数是a custom function。那么例如,直接在脚本编辑器中运行函数,如“运行->运行函数->随机()”怎么样?
  • 我为我糟糕的英语水平道歉。我曾建议通过脚本编辑器和自定义菜单运行脚本作为解决方法。这样,值将作为不是公式的字符串放入单元格中。所以值不会刷新。作为其他解决方法,使用事件触发器的OnEdit 怎么样?这也与第一个解决方法相同。但运行脚本的方法不同。
  • 任务要求有些出入。起初,您只提到了 1 个源列(范围 AllValues!A4:A103)和 1 个目标单元格(带有公式)。但是random() 函数需要 4 列数据并将多个值写入第 6 列。准确地说,您希望最后看到的稳定结果(无需后续重新计算)是什么?

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


【解决方案1】:

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

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

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);
}

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

【讨论】:

    【解决方案2】:

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

    自定义菜单

    正如@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 表中的自定义函数,例如

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

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

    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 日志记录)

    参考资料:

    【讨论】:

    • 感谢@tehhowch 提供如此精彩而详细的重播!但似乎我做错了什么。我让自定义菜单正常工作,但是当我运行它时,它不会在“RandomValues”表中插入任何值,但脚本本身似乎正在工作,因为它也不会给出任何错误。我将继续尝试找出问题所在,但也许您有一些建议。
    猜你喜欢
    • 2021-09-11
    • 1970-01-01
    • 2016-11-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多