0

I am trying to achive the following. In google sparesheet I have one sheet with values "AllValues", in another sheet "Randomvalues" I would like to get random values from sheet "AllValues".

I have tried two options, first I tried randbetween formula:

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

It is working, but it refresh/recalculate new values all the time column is changed. Googeled a lot and seems that there is not much to do to freeze already calculated results.

Next I tried function:

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

But this function is not working for me, google sparesheet gives error on line 11, that setVaue is not allowed.

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

Googled this one too, there are lot of suggestion, but I am not very familiar with functions, I did not managed to get it working.

Hope that someone can help me out.

Rubén
  • 34,714
  • 9
  • 70
  • 166
Ziil
  • 341
  • 1
  • 5
  • 24
  • In your situation, do you run the function as a custom function? If it's so, such error occurs. When the custom function is used, when the script is saved even if the script is not modified, the custom function is refreshed. So although I'm not sure about the detail of your situation, for example, how about running the function at the script editor and custom menu? – Tanaike Jan 14 '19 at 22:44
  • Thank you for your comment. I am even not sure do I run it as custom function. This is how I did it: in google sparesheers I selected "Tools" > "Script editor" then I created the script and tried to use it in my google sparesheet cells, typin in "random()" - I am not very familiar with scripting and google sparesheers. – Ziil Jan 15 '19 at 08:30
  • Thank you for replying. In your situation, you are using the function as [a custom function](https://developers.google.com/apps-script/guides/sheets/functions). So for example, how about directly running the function at the script editor like "Run -> Run function -> random()"? – Tanaike Jan 15 '19 at 08:40
  • If I just run > run function > random() - it will show message for a sec about running function and thats it. It will not give any error. – Ziil Jan 15 '19 at 09:23
  • No error means that the script worked. – Tanaike Jan 15 '19 at 23:32
  • Yes, script works, but I am not able to use created function in my google sparesheets as setValue is not allowed. Is there any workaroud? As I understan this is some common issue with custom functions. – Ziil Jan 16 '19 at 07:43
  • If you are using it as a custom function, such error occurs. About this and workaround, I commented at the first comment. If I misunderstand your reply, please tell me. – Tanaike Jan 16 '19 at 07:51
  • Thank you @Tanaike for trying to help. Reading your first comment I dont see any workaround or you meant that there is no workaround? Or maybe there is better way to get random values from one column? – Ziil Jan 16 '19 at 08:05
  • 1
    I apologize for my poor English skill. I had proposed to run the script by the script editor and custom menu as the workaround. By this, the values are put to the cells as the string which is not formula. So the values are not refreshed. And as other workaround, how about using ``OnEdit`` of event trigger? This is also the same with 1st workaround. But the method for running the script is different. – Tanaike Jan 16 '19 at 08:20
  • Thanks @Tanaike, I have been away for a while. But is there any possible way to adjust the function such way that it will work with google sparesheets? PS. your english is fine :) – Ziil Feb 01 '19 at 07:32
  • 1
    There is some discrepancy in the task requirements. At first you've mentioned only 1 source column (range `AllValues!A4:A103`) and only 1 target cell (with formula). But `random()` function takes 4 columns data and writes multiple values to the 6-th column. Be exact, what do you want to see as a stable result (without subsequent recalculations) at the end? – Александр Ермолин Feb 05 '19 at 15:34

2 Answers2

1

There are multiple ways of achieving this goal.

Custom Menu

As mentioned by @Tanaike, you can avoid the recalculation and the formula dependency by using a Custom Menu:

// @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();
}

You then need a function definition matching this name sampleAllValues, and when the user selects the associated menu option, it will be invoked with the permissions of the clicking user (the user will be prompted first to provide consent for access per the script's OAuth scopes).

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

Custom Function

If you still wanted to use a custom function from the RandomValues sheet, e.g.

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

then you would need to return sample instead of write to a specific sheet. Note that custom functions are treated deterministically--they are computed at the time of entry and then only recalculated when the values of their arguments change. Custom functions run with very limited scope, so be sure to review their restrictions. The above usage hints that you might find it useful to allow passing in the number of desired samples, and the values to sample from:

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`
}

No matter which route you take, be sure to review your script's error logging by viewing your script's Stackdriver logs. (View -> Stackdriver Logging)

References:

tehhowch
  • 9,645
  • 4
  • 24
  • 42
  • Thank you @tehhowch for such a great and detailed replay! But seems that I do something wrong. I got the custome menu working, but when I run it, it will not insert any value into "RandomValues" sheet, but script itself seems to be working as it will not give any error as well. I will continue trying to figure out the issue, but maybe you have some suggestions. – Ziil Feb 10 '19 at 19:09
1

Using a formula assumes repeated calculations usually. You cannot prevent them and only can try to return old values instead. This task is not trivial, since any formula cannot refer to the same cell where the result is to be returned (a circular reference occurs). Do not use formulas for single time calculation.

On the other hand, using a script function makes it possible to generate required data directly and only once or on demand. I think, the function below will help you to understand all the neccesary steps for sample source and target ranges.

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

You can run it manually or choose a proper trigger.