Google Spreadsheet ranking values - javascript

Im currently setting up a decision matrix with Google Spreadsheet and I want to implement a ranking feature for the results. I wouldnt do this, if there were only 4 options, but in my case there are like 32 options available...
I tried to do this with the google script api, but the result is kinda disappointing:
function Ranking() {
var sheet = SpreadsheetApp.getActiveSpreadsheet();
var alldata = sheet.getDataRange().getValues();
var data = [];
var j = 8;
for (var i = 0; i <= 31; i++) {
data [i] = alldata[18][j];
j= j+2;
}
var sorted = data.slice().sort(function(a,b){return b-a});
var ranks = data.slice().map(function(v){return sorted.indexOf(v)+1});
sheet.getRange("I20").setValue(ranks[0]);
sheet.getRange("K20").setValue(ranks[1]);
sheet.getRange("M20").setValue(ranks[2]);
sheet.getRange("O20").setValue(ranks[3]);
sheet.getRange("Q20").setValue(ranks[4]);
sheet.getRange("S20").setValue(ranks[5]);
sheet.getRange("U20").setValue(ranks[6]);
sheet.getRange("W20").setValue(ranks[7]);
sheet.getRange("Y20").setValue(ranks[8]);
sheet.getRange("AA20").setValue(ranks[9]);
sheet.getRange("AC20").setValue(ranks[10]);
sheet.getRange("AE20").setValue(ranks[11]);
sheet.getRange("AG20").setValue(ranks[12]);
sheet.getRange("AI20").setValue(ranks[13]);
sheet.getRange("AK20").setValue(ranks[14]);
sheet.getRange("AM20").setValue(ranks[15]);
sheet.getRange("AO20").setValue(ranks[16]);
sheet.getRange("AQ20").setValue(ranks[17]);
sheet.getRange("AS20").setValue(ranks[18]);
sheet.getRange("AU20").setValue(ranks[19]);
sheet.getRange("AW20").setValue(ranks[20]);
sheet.getRange("AY20").setValue(ranks[21]);
sheet.getRange("BA20").setValue(ranks[22]);
sheet.getRange("BC20").setValue(ranks[23]);
sheet.getRange("BE20").setValue(ranks[24]);
sheet.getRange("BG20").setValue(ranks[25]);
sheet.getRange("BI20").setValue(ranks[26]);
sheet.getRange("BK20").setValue(ranks[27]);
sheet.getRange("BM20").setValue(ranks[28]);
sheet.getRange("BO20").setValue(ranks[29]);
sheet.getRange("BQ20").setValue(ranks[30]);
sheet.getRange("BR20").setValue(ranks[31]);
}
As you can see, the code is really sloppy. Thats probably because I have never done javascript before and my coding knowledge is very basic in general.
Is there a way to implement such a feature directly in the spreadsheet, without the need of the script api?
Thanks in advance for the help!

for(i=0;i<numRanks;i++)
sheet.getRange(20, i*2+9).setValue(ranks[i]);
Basically you just iterate a for loop over the twentieth row every other column.
Function documentation here.

Jason's answer is clear & short - but it's not efficient because of the number of calls to the Spreadsheet Service.
For background about this concern, see Google Apps Script Best Practices. You may also want to look at What is faster: ScriptDb or SpreadsheetApp?.
You've already got an Array of ranks; if you transpose that into a two-dimensional array with the values in rows, then you can write it all out in one setValues() operation.
sheet.getRange("I20:BR20").setValues(transpose([ranks]));
Here's a transpose function, from Google Spreadsheet Script - How to Transpose / Rotate Multi-dimensional Array?.
function transpose(a)
{
return Object.keys(a[0]).map(function (c) { return a.map(function (r) { return r[c]; }); });
}
There is still iteration happening, but it's in the transpose function now, instead of around the setValue() call, so this will run much quicker.

Related

Getting values from multiple onEdit() triggers

I know this question might sound wierd. But i am a noob to google sheets and trigger operations.Here's the problem, Lets say I have two cols in a sheet col_Link and col_value. col_link contains a link to another sheet and second one is obvious. I have to get the value from the col_link first and perform some operations then while performing second operation I still need this col_Link's link.
Now i have a installed onEdit() trigger which performs this operation but Im stuck on this little thing.
Code:
var c = e.range.getColumn();
var r = e.range.getRow();
var val = e.range.getValue();
if (c==1){
Link_operation(val,row,col); // this val contains link.
}
else if(c==2){
Value_operation(link,val,row,col);
// here val is some value. I want this function to somehow also has the link from col_Link.
}
Since the value can only contain either the link or the value.
What should be my approach to get this? Should I change the way I'm approaching this problem or is it possible to achieve multiple values like this?
Will appreciate any help.
Your question is a little vague and probably should include a screenshot or better detail of what exactly is your expected outcome.
If you want to get multiple values from an onEdit(e) trigger that weren't in the edited range then as master suggested, offsetting is the way to do it.
function onEdit(e) {
var sheet = SpreadsheetApp.getActiveSheet();
var c = e.range.getColumn();
var r = e.range.getRow();
var val = e.range.getValue();
var link = sheet.getRange(r, c - 1).getValue();
Logger.log(link);
}
In this example if the cell B6 is edited then the value in A6 will be logged.

Copying & Modifying specific values from each Form submit when copying to another sheet

I got a situation where 2 independent parties need access to the same data, but their back-end that uses the data needs a different format, e.g. one party needs to see "Soccer", the other needs to see "1".
This input comes from various users through a Google Form.
I previously ran a time interval script that would copy over all data from sheet 1, then copy it to sheet 2, then a double loop would kick (using var i) in to search all columns and replace specific strings on sheet 2 with a numeric value.
This script worked, but due to it's inefficiency it started to crash once I started to have more data.
So I am trying to reduce the dataset by just handling the data that comes from each individual Form Submit.
I tried 2 different approaches:
1) Grab values to copy, modify values, then copy to new sheet
2) Grab values to copy, copy to new sheet, then modify these new values (lastrow)
I know the copy bit works in both scripts I wrote but my modification does not, when using the debugger it shows me the modified data is same as the original and this is in fact what happens the copy is identical.
I realize I am probably making a very basic mistake, but I am blind as to what it is. Searching for similar threads on Stackoverflow and other sites I did not come to a resolution.
Current code:
function onFormSubmit(e){
var responses = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
var projects = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet2");
var row = responses.getLastRow();
var col = responses.getLastColumn();
var copy = responses.getRange(row, 1, 1, col)
var modify = copy.getValues();
for (var i=0; i < modify.length; i++){
if (modify [i] == "SearchString1"){
modify [i] = "1";
}
else if (modify [i] == "SearchString2"){
modify [i] = "0";
}
else if (modify [i] == "SearchString3"){
modify [i] = "1";
}
projects.appendRow(modify);
}
}
This code will copy the data, but not modify it.
I had a variation where i used .setvalues but that resulted in the same end result.
Solved, it was all about location identifiers.
The getvalues created a double array and the append was in the loop instead of outside of it. I had to specify the append and modification better.
Fixed code:
for (var i=0; i < modify[0].length; i++){
if (modify[0][i] == "SearchString1"){
modify[0][i] = "1";
}
else if (modify[0][i] == "SearchString2"){
modify[0][i] = "0";
}
else if (modify[0][i] == "SearchString3"){
modify[0][i] = "1";
}
}
projects.appendRow(modify[0]);
}

Custom function for Google Spreadsheet - handling the array passed from the spreadsheet

I am trying to build a simple script to work with a Google Spreadsheet. The spreadsheet takes input from a Google Form, so there is a series of values in the spreadsheet like this:
My goal is to write a script that would strip the number from each form input in a user-specified range, then add all the numbers to provide a single score. So, for example, the user could type =sumColumns(H2:K2) in a cell, and it would return the sum of the scores (for the sample screenshot I posted, it would return the result of 3+3+0+3, 9).
Here is the code that I wrote to do this:
function sumColumns(values) {
var sum = 0;
for(var i = 0; i <= values.length; i++){
var input = values[0][i];
var x = input.toString();
var y = x.charAt(0);
var num = parseInt(y);
sum += num;
}
return sum;
}
The problem is that it only ever seems to add two values together. So, when I put =sumColumns(H2:K2) in a cell in the spreadsheet, it only returns 6. Also, on line 3, if I change it from i <= values.length to i < values.length it only adds one number, so that I get 3 as a result. My guess is that I am misunderstanding the way that the Google Spreadsheet values are passed to the function, but I have been completely unable to make it work. I'd really appreciate any help!
Oops - edited & saved the question, wrote an answer - and forgot to save it. I let Serge beat me to it! And, as usual, Serge's answer works well (with integer values). But you did ask about how things worked, so here you go.
When you give a Custom Function a range as a parameter, H2:K2 in this case, the function receives a two-dimensional array, equivalent to the return value of Range.getValues(). You can test this easily, by (temporarily) changing your function to return a JSON representation of the parameter:
function sumColumns(values) {
return JSON.stringify(values); // For debugging, just return string showing values
...
Here's what you'll see in the cell that contains =sumColumns(H2:K2):
[["3 (Rarely)","3 (Frequently)","0 (Never)","3 (Frequently)"]]
That's showing an Array enclosed by [ .. ], with another Array inside, also enclosed by square brackets, and that array has four elements. If we change the range to be H2:K3 instead, we get this (with whitespace added for clarity):
[
["3 (Rarely)","3 (Frequently)","0 (Never)","3 (Frequently)"],
["","","",""]
]
Now that you know that, it's easy to see why your function was giving the results it did.
First, for(var i = 0; i <= values.length; i++) is using the wrong array bounds to loop over, since values.length will tell us how many rows are in values. In H2:K2, that length is 1. Instead, we need to be looping over the columns in the first row (values[0]), with its 4 cells.
You were wondering about < vs <= for this loop - we do need to use < since it's a 0-based index, and .length returns a count of elements. So we end up with:
for (var i=0; i < values[0].length; i++){ ... }
Using parseInt() is a good choice, and works well for the values in your spreadsheet. It can be improved, though, by ensuring that any String values have leading non-numeric values stripped first - parseInt() can then find an Integer inside a string.
function sumColumns(values) {
return JSON.stringify(values); // For debugging, just return string showing values
var sum = 0;
for(var i = 0; i < values[0].length; i++){
var input = new String(values[0][i])
.replace( /^\D+/g, ''); // Strip any leading non-digits
sum += parseInt(input);
}
return sum;
}
I'm not good with custom function because I never use them but it seems that values is not really an array...
Comments in italic :
Hmmm embarrassing ... my first though was that it had to be a 2D array but I logged val[0] in my test and it returned an 'undefined' error... I must have mistyped something at that moment... Anyway, that's why I looked for a way around handling data as a string and using split and regex.
As usual with Mogsdad's answers you have an answer and all the explanations that go with it ;-) and, as often with him too, you get a better answer than mine.
(one restriction though (#Mogsdad) your comment about non integer values could be applied to your code as well... you simply strip out any decimal value with parseInt()...:-)
That said, your use case was well described and in the limits of this example both code should work as expected, Mogsdad's one being more 'academic' and programmatically correct.
end of comment.
Using this trick below it works as expected for any input range (1 or more row and columns):
function sumCol(val) { // returns the sum of all numeric values in range
var values = val.toString().split(',');
var sum = 0;
for(var n=0;n<values.length;++n){
sum+=Number(values[n].replace(/[^0-9+.]/ig,''));
}
return sum;
}
I changed also the number extraction mode to make it more universal.

Array adds dimension when randomized

Allo'
I'm working on a little project of mine and part of it involves taking a two dimensional array already created and randomizing it.
So I have something which looks like this:
foo = [[1,2],[3,4],[5,6],[7,8]];
randomizeFoo = function(){
var randomizedFoo = [];
newFoo = foo;
for(i = 0; i < newFoo.length; i++){
count = Math.random() * newFoo.length;
randomizedFoo.push(newFoo.slice(count, count + 1));
}
return randomizedFoo;
};
This does indeed randomize the array but I end up with something like this:
randomizedFoo = [[[7,8]],[[1,2]],[[5,6]],[[3,4]]]
My nice neat 2D array is now a 3D array with the lowest level arrays now burred under an extra level. I realize that this is not really that big a deal and the rest of my code just needs to compensate but it bugs me for 2 reasons:
It's extra complexity and that's never good.
I don't like my code doing things without me knowing the reason why.
Anybody have any ideas as to why it's doing this? I put a 2D array in, I want a 2D array back out again.
It's because you are using slice. Just use count as the index into foo. As in : randomizedFoo.push(foo[count]);
Make sure you make count an int first.
You can take the script from this answer and use map with it:
foo = range(0, foo.length-1, true).map(function(i) {
return foo[i];
});
Demo: http://jsbin.com/ayepeh/1/edit (ctrl + enter to refresh)

Google Docs - spreadsheet loop

This one might be a bit basic and easy to answer, but I've been pulling out my hair for a while now!
I've built the following code - which is semi-pseudo as I can't find the right way to make things work!
var s = "Test";
function onEdit(event)
{
var ss = event.source.getActiveSheet();
var r = event.source.getActiveRange();
if (ss.getName() == s)
{
results = {"Currently On": 0, "Next Up": 0, "On Hold": 0, "Waiting on someone else": 0, "zDone": 0};
last = ss.getMaxRows();
start = ss.getRange("F3:"+last).getValues();
var output = "J11";
for (x=0;x<start.length;x++)
{
results[start[x]]++;
}
for (y=0;y<results.length;y++)
{
row = ss.getRow(output);
row.value = results[y];
output++;
}
}
}
I've got an example of the data in this image
The basic idea is to run through all the possible categories of each task and keep a numeric list on the side of how many of each there are. I'd also like to make it dynamic (so I don't have to hard code in the list of categories) but I'm more interested in just making it work for the moment.
The Google Apps debugger is very frustrating!
Thanks for your help all!
Firstly, this particular use case would be easily achievable with a spreadsheet formula, eg:
=QUERY(A2:F;"select F, count(A) where F != '' group by F label count(A) 'Count'";1)
but there may be a reason why you want to do this with GAS.
So secondly, this is where I think there may be some syntax issues:
last = ss.getMaxRows();
I would just use var last = ss.getLastRow() here.
start = ss.getRange("F3:"+last).getValues();
The range reference would evaluate to something like "F3:100", which is a valid reference in GSheets (don't know about whether GAS can handle it), but nevertheless you really want something like "F3:F100", so I would use var start = ss.getRange("F3:F"+last).getValues();.
results[start[x]]++;
When you create an array from a getValues() call it is a 2D array, so you would need to use results[start[x][0]]++;.
With the next loop and the output variable, I must admit I'm a bit lost with what you're doing there. How did you want your result table laid out?
You have
output = "J11";
And then you do
ss.getRow(output);
output++;
This is invalid.First of all, ss is a Sheet under which there is not getRow method. So, what you should really be doing is something like this
var row = 11 ;
var col = 10 ; //Col J
for (y=0;y<results.length;y++)
{
ss.getRange(row,col,1,1).setValue(results[y]);
row++:
}
Like AdamL, I suggest that this is better handled within the native capability of the spreadsheet. Seems to me that you want a pivot table, which would update dynamically. Alternatively a formula like =countif(F:F,"Currently On" ) would meet your immediate request. =Unique(F:F) will give you the list of categories in an array

Categories

Resources