Ignoring empty cells in Google Sheets custom script - javascript

I am trying to create a formula to calculate Net Promoter Score on Google Sheets. I have the formula working but only when I specify the exact range. My issue is that this specific sheet will grow with data over time and I do not want to have to keep reselecting the range. What I want to do is select the entire row and just let it auto-update the NPS score. My issue with this approach is every empty cell is considered a zero which is screwing up my percentages. How can I make my function ignore the empty cells???
Here is my attempt:
/**
This is a custom formula that calculates the Net Promoter Score.
#customFunction
*/
function NPS(numArr) {
var detractors = new Array();
var passive = new Array();
var promoters = new Array();
var i = 0;
for (i = 0; i < numArr.length; i++) {
if (isNaN(numArr[i])) {
console.log(numArr[i]);
} else {
if (numArr[i] >= 9) {
promoters.push(numArr[i]);
} else if (numArr[i] === 7 || numArr[i] === 8) {
passive.push(numArr[i]);
} else if (numArr[i] <= 6) {
detractors.push(numArr[i]);
}
}
}
var promoPercentage = promoters.length / numArr.length;
var detractorsPercentage = detractors.length / numArr.length;
return (promoPercentage - detractorsPercentage) * 100;
}

You can use JavaScript filter [1] function to filter the empty values from the array you're getting (numArr). Also, notice that you're selecting a range of cells so the argument will be a 2D array [2], where each value is a "row" array filled with the column values for that row, in case you just want the first value of each row (for a one column range like A1:A25) you need to access the first element of each "row" array to get the actual value:
function NPS(numArr) {
var detractors = new Array();
var passive = new Array();
var promoters = new Array();
var i = 0;
//Filter empty elements
numArr = numArr.filter(function(element) {
return element[0] !== '';
})
for (i = 0; i < numArr.length; i++) {
if (isNaN(numArr[i][0])) {
console.log(numArr[i][0]);
} else {
if (numArr[i][0] >= 9) {
promoters.push(numArr[i][0]);
} else if (numArr[i][0] === 7 || numArr[i][0] === 8) {
passive.push(numArr[i][0]);
} else if (numArr[i][0] <= 6) {
detractors.push(numArr[i][0]);
}
}
}
var promoPercentage = promoters.length / numArr.length;
var detractorsPercentage = detractors.length / numArr.length;
return (promoPercentage - detractorsPercentage) * 100;
}
[1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter
[2] https://developers.google.com/apps-script/guides/sheets/functions#arguments

Related

Google Script: Multiple conditions per unique ID

The script would count the number of times all VIN numbers are repeated & if all parts have arrived for that VIN Number (Car).
In example, if the VIN number is repeated 5 times then that means there are five parts going to arrive, so then the next step would be to check the arrived column for such VIN if there are 5 "Yes" values then
(VIN number repeated) 5/5 (Number of "Yes" values)
would trigger it to change the [Master] tab Parts Order column to "Yes" for that particular VIN number.
User would manually update the [Parts] tab, arrived column with either "Yes" or leave blank. (If blank then part has not arrived.)
See link for google sheet Template:
https://docs.google.com/spreadsheets/d/1wlGV_QCWpRwmI5FWiOli6lXuzRATy_Goygp3lhtm-Ek/edit?usp=sharing
My attempt:
Special function to get the last value of a column:
function getLastRowSpecial(range){
var rowNum = 0;
var blank = false;
for(var row = 0; row < range.length; row++){
if(range[row][0] === "" && !blank){
rowNum = row;
blank = true;
}else if(range[row][0] !== ""){
blank = false;
};
};
return rowNum;
};
Here I was able to count the number of times each VIN Number appears, but I was unable to count the number of "Yes" values for each unique VIN number. This needs to be dynamic. My approach at the end was not dynamic. Regarding in particular the, var number
Main Script:
/** ---------------------- SPREAD SHEETS ---------------------- **/
var masterS = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Master");
var partS = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Parts");
/** ----------------- Selecting Range of Cells ----------------- **/
/** ----- Parts Spread Sheet ----- **/
/** VIN to Match */
var vinPartOrderRangeP = partS.getRange("C5:C");
var vinPartValuesP = vinPartOrderRangeP.getValues();
var columnCheckPartVINP = getLastRowSpecial(vinPartValuesP);
var partVINDataRangeP = partS.getRange(5, 3, columnCheckPartVINP, 1);
var partsVinSetP = partVINDataRangeP.getValues();
/** Part Arrived */
var partOrderedRangeP = partS.getRange("N5:N");
var partOrderedValuesP = partOrderedRangeP.getValues();
var partOrderedValuesCorrectLengthP = partOrderedValuesP.splice(0,partsVinSetP.length);
/** Combining VINs with Parts Arrived */
var vinPartsArrivedP = [];
vinPartsArrivedP.push(partsVinSetP,partOrderedValuesCorrectLengthP);
/** ----- Master Spread Sheet ----- **/
/** VIN to Match */
var vinPartOrderRangeM = masterS.getRange("B5:B");
var vinPartValuesM = vinPartOrderRangeM.getValues();
var columnCheckPartVINM = getLastRowSpecial(vinPartValuesM);
var partVINDataRangeM = masterS.getRange(5, 2, columnCheckPartVINM, 1);
var partsVinSetM = partVINDataRangeM.getValues();
/** Part Arrived */
var partPastRangeM = masterS.getRange("I5:I");
var partPastValuesM = partPastRangeM.getValues();
/** ---- For-Loop getting Number of Parts that need to Arrive ---- **/
var vinNumber = [], arrivalPartsRequired = [], prev;
for (var i = 0; i < vinPartsArrivedP[0].length; i++) {
if (vinPartsArrivedP[0][i][0] !== prev) {
vinNumber.push(vinPartsArrivedP[0][i][0]);
arrivalPartsRequired.push(1);
} else {
arrivalPartsRequired[arrivalPartsRequired.length - 1]++;
}
prev = vinPartsArrivedP[0][i][0];
}
console.log('[' + vinNumber[0] + ']','[' + arrivalPartsRequired[0] + ']')
/**
* Now we can say arrivalPartsRequired has the number of Yes's we need
* per each VIN number.
**/
console.log(vinPartsArrivedP[0][3][0])
var number = 0;
var number2 = 0;
var number3 = 0;
var number4 = 0;
var number5 = 0;
for (var j = 0; j < partsVinSetM.length; j++) {
};
for (var k=0; k<vinPartsArrivedP[0].length; k++){
if(vinNumber[0] == vinPartsArrivedP[0][k][0]){
number++
for (var i=0; i<partOrderedValuesP[0].length; i++){
for (var j = 0; j < partOrderedValuesP[0].length; j++) {
if (partOrderedValuesP[i][j] == 'Yes') {
console.log(i);
return i+1;
}
}
return -1;
}
}
if(vinNumber[1] == vinPartsArrivedP[0][k][0]){
number2++
}
if(vinNumber[2] == vinPartsArrivedP[0][k][0]){
number3++
}
if(vinNumber[3] == vinPartsArrivedP[0][k][0]){
number4++
}
if(vinNumber[4] == vinPartsArrivedP[0][k][0]){
number5++
}
};
console.log(number);
console.log(number2);
console.log(number3);
console.log(number4);
console.log(number5);
I believe your goal as follows.
You want to retrieve the values from the columns "C" and "N" from "Parts" sheet, and want to check whether the number of same VIN # at the column "C" and the number of yes at the column "N".
When both numbers are the same, you want to put yes to the column "I" of "Master" sheet.
You want to achieve this using Google Apps Script.
In this case, in order to achieve your goal, how about the following flow?
Retrieve values from "Parts" sheet.
Create an object for checking the number of VIN # and yes.
Create an array for putting to the "Master" sheet.
Put the values to the "Master" sheet.
When this flow is reflected to a sample script, it becomes as follows.
Sample script:
function sample() {
// 1. Retrieve values from "Master" sheet.
var masterS = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Master");
var valuesOfParts = partS.getRange("C5:N" + partS.getLastRow()).getValues();
// 2. Create an object for checking the number of `VIN #` and `yes`.
var obj = valuesOfParts.reduce((o, [c,,,,,,,,,,,n]) => {
if (o[c]) {
o[c].c += 1;
if (n == "yes") o[c].yes += 1;
} else {
o[c] = {c: 1, yes: n == "yes" ? 1 : 0};
}
return o;
}, {});
// 3. Create an array for putting to the "Master" sheet.
var partS = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Parts");
var rangeOfMaster = masterS.getRange("B5:B" + masterS.getLastRow());
var valuesOfMaster = rangeOfMaster.getValues().map(([b]) => [obj[b] && obj[b].c == obj[b].yes ? "yes" : ""]);
// 4. Put the values to the "Master" sheet.
rangeOfMaster.offset(0, 7).setValues(valuesOfMaster);
}
References:
reduce()
map()

I want to write a Google App Script to automate calculating late days in Google Sheet

I want to write a code that automate the late days calculation. Here is how it should work
If the score of date after (ex: date 10) - score of day before (date 9)> 0 => the score of date after (120) will be assigned "late for 3 days"
On the other hand, that date after will keep looking and substracting from the next date before until it gets "> 0" result.
The example is illustrated in attached image.
Here is the code that I currently have and it didnt work:
function myFunction() {
var app = SpreadsheetApp;
var activeSheet = app.geActiveSpreadsheet().getActiveSheet();
for (var i=2; i <= 8; i++){
var scoreafterCell = activeSheet.getRange(i;2).getValue();
var scorebeforeCell= activeSheet.getRange(i++;2).getValue();
scoreDiff= scoreafterCell- scorebeforeCell;
if(scoreDiff > 0) {
activeSheet.getRange(i,3).setValue(3);
} else for (var k=0; k++) {
do {
lateDays= k+=3;
activeSheet.getRange(i,3).setValue(lateDays);
}
while (scoreDiff = 0);
}
}
}
Hope to have some inputs soon since this is very important for my work!!! Thank you.
Since there is no clear pattern above, I did a hack to achieve what you want. This only checks the scores and doesn't check the dates as lateDays won't be dependent on that.
Please see the code below.
function generateLateDays() {
var activeSheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var lastRow = activeSheet.getLastRow();
var scores = activeSheet.getRange('B2:B' + lastRow).getValues().flat();
var output = activeSheet.getRange('C2:C' + lastRow);
var lateDays = [];
while(scores.length > 0) {
// get how many occurences does the first element have
occurence = scores.filter(value => value === scores[0]).length;
// remove current item in advance, we will only be checking number of occurences
// exact value will not be checked, so scores should be in descending order
scores = scores.filter(score => score !== scores[0]);
// contains per score late days
var temp = [];
for(var j = 0; j < occurence; j++) {
if(scores.length > 0) {
// e.g. 3 duplicates will make temp 3, 4, 5
temp.push(j + 3);
}
else {
// when last element is up, push 0
temp.push(0);
}
}
// e.g. temp reverse will be 5, 4, 3
temp.reverse();
// add to final output
lateDays.push(temp);
}
// convert 2d array to 1d array
lateDays = lateDays.flat();
for(var i = 0; i < lateDays.length; i++) {
// convert all elements into an array for setValues
lateDays[i] = [].concat(lateDays[i]);
}
output.setValues(lateDays);
}
Note that this will work only on the order your sample sheet works. I haven't tried doing it ascending. Since your sheet score descending, I made this to work that way.
If you have any questions, feel free to ask below.
Below is my suggested answer. Check it out:
function getLateDates() {
var activeSheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var scores = activeSheet.getRange('B2:B10').getValues().flat();
var output = activeSheet.getRange('D2:D10');
/**
* Set LateDates[_] as array of 3,
* except the last value = 0 ( at t = 0 )
*
*/
let LateDates = [
...Array.from({ length: scores.length - 1 }).map((x) => 3),
0,
];
/** Check the diff of current score with each of the next score,
* if diff > 0 --> break --> LateDates[current] = 3
* if diff = 0 --> +1 to LateDates[current], and continue w the next score
*
*/
for (var current = 0; current < scores.length; current++) {
for (var next = current + 1; next < scores.length; next++) {
let d = scores[current] - scores[next];
if (d > 0) {
break;
} else {
LateDates[current]++;
}
}
}
/**
* Reduce the LateDates to fit setValues format
*/
let result = LateDates.reduce((a,c)=> ([...a,[c]]),[])
output.setValues(result)
//console.log('result', result);
}
Notice I'm using your date order (descending).
If it's ascending, switch 0 to top of LateDates[] instead:
let LateDates = [
0,
...Array.from({ length: scores.length - 1 }).map((x) => 3),
];

Google Script working as 2 separate scripts but not inside the same function

Basically I have a script that is in 4 blocks:
1. Copies within a range each row provided it meets a criteria
2. Removes all empty rows
3. Sets all numbers as percentage
4. Applies conditional cell formatting to one of the columns
The 4th part is the one that is causing me issues. The script runs without any error message AND block 4 works perfectly fine if it's in another script alone with the same variables defined but as soon as it is inside the same function as the others it simply doesn't run without any error message of any kind.
Tried changing the name of the variables to single use ones to ensure it wasn't because one of the "var" was modified above it, removing the "else if" to keep only an "if" in the loop, moving it around to other parts of the script but if the block 1 is in the script then block 4 won't apply (will apply if it is only with 2 & 3.
2 & 3 which follow the same structure work well with 1.
Does any one have any clue what's wrong with my script ? :)
Each block is commented with what it does
function copy() {
//Set variables & criterion to choose which rows to copy
var s = SpreadsheetApp.openByUrl('https://docs.google.com/spreadsheets/d/1bEiLWsbFszcsz0tlQudMBgTk5uviyv_wDx7fFa8txFM/edit');
var ssSource = s.getSheetByName('Variations');
var ssDest = s.getSheetByName('Email');
var lastRowSource = ssSource.getLastRow();
var lastRowDest = ssDest.getLastRow();
var lastColSource = ssSource.getLastColumn()
var criteria = 0;
var titles = ssSource.getRange(1,1,1, lastColSource).getValues()
//Copies the range
ssDest.getRange(1,1,1, lastColSource).setValues(titles)
for (var i = 2; i < lastRowSource; i++ ) {
var test = ssSource.getRange(i ,1);
Logger.log(test.getValue()+ ' <? ' + criteria);
if (ssSource.getRange(i ,6).getValue() > criteria) {
ssSource.getRange(i ,1,1,ssSource.getLastColumn()).copyTo(ssDest.getRange(i ,1,1,ssSource.getLastColumn()), {contentsOnly:true}); // copy/paste content only
}
}
//Removes empty rows
var data = ssDest.getDataRange().getValues();
var targetData = new Array();
for(n=0;n<data.length;++n){
if(data[n].join().replace(/,/g,'')!=''){ targetData.push(data[n])};
Logger.log(data[n].join().replace(/,/g,''))
}
ssDest.getDataRange().clear();
ssDest.getRange(1,1,targetData.length,targetData[0].length).setValues(targetData);
//Formats numbers as percentages
var rangePercent = ssDest.getRange(1,1,ssDest.getLastRow(),ssDest.getLastColumn());
var rowsPercent = rangePercent.getNumRows();
var colsPercent = rangePercent.getNumColumns();
for(var rowPercent = 1; rowPercent <= rowsPercent; rowPercent++) {
for(var colPercent = 1; colPercent <= colsPercent; colPercent++) {
var cellPercent = rangePercent.getCell(rowPercent, colPercent);
var valuePercent = cellPercent.getValue();
if(typeof(valuePercent) == 'number') {
cellPercent.setNumberFormat("##.#%");
}
}
}
//Adds conditional background colours
for (var z = 2; z < lastRowDest+1;z++) {
var avgCpc = 4;
var rangeColour = ssDest.getRange(z,avgCpc);
var dataColour = rangeColour.getValue()
if (dataColour < 0) {
ssDest.getRange(z,avgCpc).setBackground('#d9ead3')
}
else if (dataColour > 0) {
ssDest.getRange(z,avgCpc).setBackground('#f4cccc')
}
}
//Centers Values
}
The problem you're having is your code has performance issues because you're calling too many times methods such as getRange() and getValue() inside various loops, therefore Apps Script can't keep up with all those calls. Please check Best Practices.
Having said that, I modified your code in order to make it more efficient. Besides your copy function, I added two more functions to make the code more readable.
function copy
As before this function sets the variables, but now it calls two other functions, which are setPositiveCostValues and formatCells
function copy() {
//Set variables & criterion to choose which rows to copy
var ss = SpreadsheetApp.openByUrl('your-url');
var ssSource = ss.getSheetByName('Variations');
var ssDest = ss.getSheetByName('Email');
// set the title
var titles = ssSource.getRange(1,1,1, ssSource.getLastColumn()).getValues();
ssDest.getRange(1,1,1, ssSource.getLastColumn()).setValues(titles);
// get the positive values you want from the cost col
var positiveValues = setPositiveCostValues(ssSource, ssDest, ssSource.getLastRow());
// fomrat the cells you want as percentage and set the color
formatCells(ssDest, positiveValues);
}
function setPositiveCostValues
This will take the values where the cost is positive and it will get rip off of the cells with empty values and "n/a" values.
function setPositiveCostValues(ssSource,ssDest, lastRowSource){
var postiveCost = ssSource.getRange(2, 1, lastRowSource, 6).getValues();
// this loop will clean the empty elements and the ones that only have n/a
for (var i = postiveCost.length - 1; i >= 0; i--) {
if (postiveCost[i][0]) {
postiveCost.splice(i + 1, postiveCost.length - (i + 1));
postiveCost = postiveCost.filter(function(el){ return el != 'n/a'})
break;
}
}
return postiveCost;
}
function formatCells
This will format the cells in the cost col as a percentage and will set the right color in your avgCpc col.
function formatCells(ssDest, postiveCost){
var avgCpc = 4, cost = 6, row = 2, criteria = 0;
// iterate over the array and depending on the criteria format the cells
postiveCost.forEach(function(el){
if(el[cost - 1] > criteria){
var ssDestRange = ssDest.getRange(row, 1, 1, cost);
ssDestRange.setValues([el]);
ssDestRange.getCell(1, cost).setNumberFormat("##.#%");
// set the color depending on the avgCpc value condition
if(el[avgCpc - 1] < criteria) ssDest.getRange(row, avgCpc).setBackground('#d9ead3');
else ssDest.getRange(row, avgCpc).setBackground('#f4cccc');
row++;
}
});
}
Code
Your whole code now it will look like this:
function copy() {
//Set variables & criterion to choose which rows to copy
var ss = SpreadsheetApp.openByUrl('your-url');
var ssSource = ss.getSheetByName('Variations');
var ssDest = ss.getSheetByName('Email');
// set the title
var titles = ssSource.getRange(1,1,1, ssSource.getLastColumn()).getValues();
ssDest.getRange(1,1,1, ssSource.getLastColumn()).setValues(titles);
// get the positive values you want from the cost col
var positiveValues = setPositiveCostValues(ssSource, ssDest, ssSource.getLastRow());
// fomrat the cells you want as percentage and set the color
formatCells(ssDest, positiveValues);
}
function setPositiveCostValues(ssSource,ssDest, lastRowSource){
var postiveCost = ssSource.getRange(2, 1, lastRowSource, 6).getValues();
// this loop will clean the empty elements and the ones that only have n/a
for (var i = postiveCost.length - 1; i >= 0; i--) {
if (postiveCost[i][0]) {
postiveCost.splice(i + 1, postiveCost.length - (i + 1));
postiveCost = postiveCost.filter(function(el){ return el != 'n/a'})
break;
}
}
return postiveCost;
}
function formatCells(ssDest, postiveCost){
var avgCpc = 4, cost = 6, row = 2, criteria = 0;
// iterate over the array and depending on the criteria format the cells
postiveCost.forEach(function(el){
if(el[cost - 1] > criteria){
var ssDestRange = ssDest.getRange(row, 1, 1, cost);
ssDestRange.setValues([el]);
ssDestRange.getCell(1, cost).setNumberFormat("##.#%");
// set the color depending on the avgCpc value condition
if(el[avgCpc - 1] < criteria) ssDest.getRange(row, avgCpc).setBackground('#d9ead3');
else ssDest.getRange(row, avgCpc).setBackground('#f4cccc');
row++;
}
});
}

How to automate randomizing 46 names to create 46 x 6 unique rows and columns in Google sheet?

I am working with automation in Google sheet. Can you help me?
This problem is for sending surveys to 46 people. Each people needs to rate 5 people from those 46 people.
Requirements:
1. 1 rater, for 5 uniques ratees
2. No duplicate name per row (it should be 6 unique names in a row)
3. No duplicate name per column (it should be 46 unique names per column)
Expected output is for us to create 46x6 random names with no duplicates in row and columns.
-
-
Flow:
If a unique matrix across and below can be created, then it's values can be used as keys to the actual name array.
Create a 2D number array with length = number of rows
Loop through required number of columns and rows
Create a temporary array (tempCol) to store current column data
Fill the array with random numbers
Use indexOf to figure out if any random numbers are already present in the currentrow/ current column, if so, get a new random number.
In random cases, where it's impossible to fill up the temporary column with unique random numbers across and below, delete the temporary column and redo this iteration.
Snippet:
function getRandUniqMatrix(numCols, numRows) {
var maxIter = 1000; //Worst case number of iterations, after which the loop and tempCol resets
var output = Array.apply(null, Array(numRows)).map(function(_, i) {
return [i++]; //[[0],[1],[2],...]
});
var currRandNum;
var getRandom = function() {
currRandNum = Math.floor(Math.random() * numRows);
}; //get random number within numRows
while (numCols--) {//loop through columns
getRandom();
for (
var row = 0, tempCol = [], iter = 0;
row < numRows;
++row, getRandom()
) {//loop through rows
if (//unique condition check
!~output[row].indexOf(currRandNum) &&
!~tempCol.indexOf(currRandNum)
) {
tempCol.push(currRandNum);
} else {
--row;
++iter;
if (iter > maxIter) {//reset loop
iter = 0;
tempCol = [];
row = -1;
}
}
}
output.forEach(function(e, i) {//push tempCol to output
e.push(tempCol[i]);
});
}
return output;
}
console.info(getRandUniqMatrix(6, 46));
var data1d = data.map(function(e){return e[0]});
var finalArr = getRandUniqMatrix(6, 46).map(function(row){return row.map(function(col){return data1d[col]})});
destSheet.getRange(1,1,finalArr.length, finalArr[0].length).setValues(finalArr);
The OP wants to create a review matrix in which the names of the reviewed employees are chosen at random, the reviewer cannot review themselves, and the matrix is completed for 46 employees.
Based on previous code, this version builds an array of employee names for each row, in which the name of the reviewer is not included in the array. Five names are chosen at random and applied to the reviewer. The loop then repeats through each of the 46 employees.
For example, in the first round of reviews, "name01" is omitted from the array of employees from which the "reviewees" are randomly chosen. In the second round, "name01" is included, but "name02" is excluded from the array of employees. And so on, such that in each case, the array of employees used for the random selection of five reviews is always 45 names in length, and excludes the name of the reviewer.
The random selection of names to be rated does not ensure an equal and even distribution of reviews among employees. Though each employee will conduct 5 reviews, some employees are reviewed more than 5 times, some less than 5 times, and (depending on the alignment of the sun, the moon and the stars) it is possible that some may not be selected for review.
function s05648755803(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheetname = "Sheet3";
var sheet = ss.getSheetByName(sheetname);
// some variables
var randomcount = 30; // how many random names
var rowstart = 7; // ignore row 1 - the header row
var width = 5; // how many names in each row - 1/rater plus 5/ratee
var thelastrow = sheet.getLastRow();
//Logger.log("DEBUG:last row = "+thelastrow)
// get the employee names
var employeecount = thelastrow-rowstart+1;
//Logger.log("DEBUG: employee count = "+employeecount);//DEBUG
// get the data
var datarange = sheet.getRange(rowstart, 1, thelastrow - rowstart+1);
//Logger.log("DEBUG: range = "+datarange.getA1Notation());//DEBUG
var data = datarange.getValues();
//Logger.log("data length = "+data.length);
//Logger.log(data);
var counter = 0;
var newarray = [];
for (c = 0;c<46;c++){
counter = c;
for (i=0;i<data.length;i++){
if(i!=counter){
newarray.push(data[i]);
}
}
//Logger.log(newarray);
var rowdata = [];
var results = selectRandomElements(newarray, 5);
Logger.log(results)
rowdata.push(results);
var newrange = sheet.getRange(rowstart+c, 3, 1, 5);
newrange.setValues(rowdata);
// clear the arrays for the next loop
var newarray=[];
var rowdata = []
}
}
/*
// selectRandomElements and getRandomInt
// Credit: Vidar S. Ramdal
// https://webapps.stackexchange.com/a/102666/196152
*/
function selectRandomElements(fromValueRows, count) {
var pickedRows = []; // This will hold the selected rows
for (var i = 0; i < count && fromValueRows.length > 0; i++) {
var pickedIndex = getRandomInt(0, fromValueRows.length);
// Pick the element at position pickedIndex, and remove it from fromValueRows. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice
var pickedRow = fromValueRows.splice(pickedIndex, 1)[0];
// Add the selected row to our result array
pickedRows.push(pickedRow);
}
return pickedRows;
}
function getRandomInt(min,
max) { // From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
}
Screenshot#1
Screenshot#2
Try this. Satisfies all the three requirements.
HTML/JS:
<html>
<title>Unique Employees</title>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.0/jquery.min.js"></script>
</head>
<table id="survey_table" border="1" width="85%" cellspacing="0">
<thead>
<th>Rater</th>
<th>Ratee1</th>
<th>Ratee2</th>
<th>Ratee3</th>
<th>Ratee4</th>
<th>Ratee5</th>
</thead>
<tbody id="table_body">
</tbody>
</table>
<script type="text/javascript">
function arrayRemove(arr, value) {
return arr.filter(function(ele) {
return ele != value;
});
}
function getRandomInt(rm_row, rm_col) {
var temp_arr = [];
for (var k = 1; k <= 46; k++) {
temp_arr.push(k);
}
for (var k = 0; k < rm_row.length; k++) {
temp_arr = arrayRemove(temp_arr, rm_row[k]);
}
for (var k = 0; k < rm_col.length; k++) {
temp_arr = arrayRemove(temp_arr, rm_col[k]);
}
var rand = temp_arr[Math.floor(Math.random() * temp_arr.length)];
return rand;
}
function exclude_num(row_unq, col_unq) {
var rand_int = getRandomInt(row_unq, col_unq);
if (!row_unq.includes(rand_int) && !col_unq.includes(rand_int)) {
arr_row.push(rand_int);
return rand_int;
} else {
return exclude_num(arr_row, arr_cols);
}
}
for (var i = 1; i <= 46; i++) {
var arr_row = [];
arr_row.push(i);
var table_html = '<tr id="Row' + i + '">';
for (var j = 1; j <= 6; j++)
{
if (j == 1) {
table_html += '<td class="Column' + j + ' cells_unq">' + i + '</td>';
} else {
var arr_cols = []
$('.Column' + j).each(function() {
arr_cols.push(Number($(this).text()));
});
var num = exclude_num(arr_row, arr_cols);
table_html += '<td class="Column' + j + ' cells_unq">' + num + '</td>';
}
}
table_html += '</tr>';
var row_html = $('#table_body').html();
$('#table_body').html(row_html + table_html);
}
$('.cells_unq').each(function() {
temp_text = $(this).text();
$(this).text('Name' + temp_text);
});
</script>
<style type="text/css">
td {
text-align: center;
}
</style>
</html>

Google sheets unique multiple columns and output multiple columns

I need some help working through this problem. I have multiple columns of data and I want to make it so that I only keep unique values and return the items to their respective columns.
1 2 3 6
1 1 4 7
2 3 5 8
would end up like this:
1 3 4 6
2 5 7
8
Right now I can do with one column using the =unique() function but I want to be able to put a new column of data and it would only spit out the unique items from that into the new table.
This is an attempt at doing it with an array formula: assumes cells do not contain negative numbers, commas or pipe symbols.
=ArrayFormula(transpose(split(transpose(split(join(",",text(unique(transpose(split(textjoin(",",true,{transpose(A1:D3),-transpose(column(A1:D3))}),","))),"0;|")),"|")),",")))
Also works with full-column references
=ArrayFormula(transpose(split(transpose(split(join(",",text(unique(transpose(split(textjoin(",",true,{transpose(A:D),-transpose(column(A:D))}),","))),"0;|")),"|")),",")))
var sheet = SpreadsheetApp.getActive().getSheetByName("Sheet5");
var sheet2 = SpreadsheetApp.getActive().getSheetByName("Sheet6");
var info = sheet.getDataRange().getValues();
var lastRow = sheet.getLastRow();
var lastCol = sheet.getLastColumn();
var seen = {}; // make object acts as a hash table
var data = info; // make array same size as original array that has the entire sheet
for (var x = 0; x < info[x].length; x++){
for (var i = 0; i < info.length; i++) {
if (!(info[i][x] in seen)) { // if item is not in seen
data[i][x] = info[i][x]; // put item in location
seen[data[i][x]] = true;}
else {
data[i][x] = "";}}} // if its not then add blank item to array
The previous answer had a join limit of 50,000 characters, so it had its own limit. this option helps for bigger sets of data. I think it could be tweaked still and improved
Paste these scripts in the script editor.
function onOpen() {
SpreadsheetApp.getUi().createMenu('My Menu')
.addItem('Show uniques', 'onlyShowUniques')
.addToUi()
}
function onlyShowUniques() {
var r, d, u, t, row, i, j;
r = SpreadsheetApp.getActive().getActiveRange();
d = transpose(r.getValues());
u = [];
t = [];
for (var i = 0, rl = d.length; i < rl; i++) {
row = []
for (var j = 0, cl = d[0].length; j < cl; j++) {
if (d[i][j] && (!~u.indexOf(d[i][j]) || i == 0 && j == 0)) {
u.push(d[i][j])
row.push(d[i][j])
} else {
row.push(null)
}
row.sort(function (a, b) {
return (a === null) - (b === null) || +(a > b) || -(a < b);
})
}
t.push(row)
}
r.setValues(transpose(t))
}
function transpose(array) {
return Object.keys(array[0])
.map(function (col) {
return array.map(function (row) {
return row[col];
});
});
}
Reopen the spreadsheet and see if an extra menu-item ('My Menu') is created.
Select the range you want to clear of duplicates.
Go to the menu item and select 'Show uniques'.
See if that brings about the expected output.

Categories