Summing of Two Arrays in App Scripts (Google Sheets) - javascript

I'm working with a Spreadsheet where I need to get the values of 2 ranges (in arrays) and return the sum of those.
I found code and tried to plug it in, but it seems to be just concatenating the 2 arrays instead of actually summing them up.
The data I'm pulling is from my spreadsheet. The arrays are 5 Columns and 23 rows. The arrays are of the same size.
Here is my function that grabs the values of each arrays. It will then run it through the Arrays_sum function I found and return and update the table with the new totals.
function updateBowl(){
var row = mainSheet.getActiveCell().getRow();
var tagCol = mainSheet.getRange("HP52");
var rowNum = mainSheet.getRange("HO52");
tagCol.setValue("N4:N" + row);
rowNum.setValue(row);
var humpedData = mainSheet.getRange("HL54:HP77").getValues();
var processedTable = mainSheet.getRange("ID54:IH77");
var currentData = processedTable.getValues();
var newTotals = Arrays_sum(humpedData,currentData);
var setNewTotals = processedTable.setValues(newTotals);
Logger.log("New Totals: " + newTotals);
}
This is a function I found that supposedly sums up each array that's plugged into it, but it is not working for me.
function Arrays_sum(array1, array2)
{
var result = [];
var ctr = 0;
var x=0;
if (array1.length === 0)
return "array1 is empty";
if (array2.length === 0)
return "array2 is empty";
while (ctr < array1.length && ctr < array2.length)
{
result.push(array1[ctr] + array2[ctr]);
ctr++;
}
if (ctr === array1.length)
{
for (x = ctr; x < array2.length; x++) {
result.push(array2[x]);
}
}
else
{
for (x = ctr; x < array1.length; x++)
{
result.push(array1[x]);
}
}
return result;
}
Any help would be appreciated!
Edit 1: Pasted picture of the log.
Edit 2: In my log picture the first 1386 value is from the first cell in the FIRST array.
The second 1386 is the first value in SECOND array.
So it seems to concatenating the first row array with the second row array.
For my testing purposes the values are the same (because of lazy) but when I can figure out the array sum, the current values and incoming values will be different.
SOLVED
Coopers answer worked. I'm not sure exactly what I tweaked to get it to work but this is the final working script.
It gets 2 different arrays (of the same size) and sums the values in each cell, then pastes those values into the second array (the current totals).
function updateBowl(){
var row = mainSheet.getActiveCell().getRow();
var tagCol = mainSheet.getRange("HP52");
var rowNum = mainSheet.getRange("HO52");
tagCol.setValue("N4:N" + row);
rowNum.setValue(row);
var humpedData = mainSheet.getRange("HL54:HP77").getValues();
var processedTable = mainSheet.getRange("ID54:IH77");
var currentData = processedTable.getValues();
var newTotals = sumarrays(humpedData,currentData);
var setNewTotals = processedTable.setValues(newTotals);
Logger.log("New Totals: " + newTotals);
}
function sumarrays(arr1,arr2) {
var o=[];
var html='[';
arr1.forEach(function(r,i) {
o[i]=[];
if(i>0){html+=','};
html+='[';
r.forEach(function(c,j){
if(j>0){html+=','};
o[i][j]=arr1[i][j]+arr2[i][j];
html+=o[i][j];
});
html+=']';
});
html+=']';
return o;
SpreadsheetApp.getUi().showModelessDialog(HtmlService.createHtmlOutput(html), 'Output');
}

Try something like this:
function arraytest() {
const ss=SpreadsheetApp.getActive();
const sh=ss.getSheetByName('Sheet1');
const a1=sh.getRange(1,1,9,9).getValues();
const a2=sh.getRange(1,10,9,9).getValues();
sumarrays(a1,a2);
}
function sumarrays(arr1,arr2) {
var o=[];
var html='[';
arr1.forEach(function(r,i) {
o[i]=[];
if(i>0){html+=','};
html+='[';
r.forEach(function(c,j){
if(j>0){html+=','};
o[i][j]=arr1[i][j]+arr2[i][j];
html+=o[i][j];
});
html+=']';
});
html+=']';
return o;
SpreadsheetApp.getUi().showModelessDialog(HtmlService.createHtmlOutput(html), 'Output');
}
Data:
1,10,19,28,37,46,55,64,73,82,91,100,109,118,127,136,145,154,163,172,181
2,11,20,29,38,47,56,65,74,83,92,101,110,119,128,137,146,155,164,173,182
3,12,21,30,39,48,57,66,75,84,93,102,111,120,129,138,147,156,165,174,183
4,13,22,31,40,49,58,67,76,85,94,103,112,121,130,139,148,157,166,175,184
5,14,23,32,41,50,59,68,77,86,95,104,113,122,131,140,149,158,167,176,185
6,15,24,33,42,51,60,69,78,87,96,105,114,123,132,141,150,159,168,177,186
7,16,25,34,43,52,61,70,79,88,97,106,115,124,133,142,151,160,169,178,187
8,17,26,35,44,53,62,71,80,89,98,107,116,125,134,143,152,161,170,179,188
9,18,27,36,45,54,63,72,81,90,99,108,117,126,135,144,153,162,171,180,189
Output:
[[83,101,119,137,155,173,191,209,227],[85,103,121,139,157,175,193,211,229],[87,105,123,141,159,177,195,213,231],[89,107,125,143,161,179,197,215,233],[91,109,127,145,163,181,199,217,235],[93,111,129,147,165,183,201,219,237],[95,113,131,149,167,185,203,221,239],[97,115,133,151,169,187,205,223,241],[99,117,135,153,171,189,207,225,243]]
You can put constraints on it depending upon how the data is collected.

I hope this script will be an answer and a guide.
You can use this inside your spreadsheet as a normal function. Like this:
=arr_arr(A1:D5,"+",F6:K9)
The code:
/**
* Return the sum of total array one + array two
*
* #param {A1:D10} range - First range to sum.
* #param {"+ - / *"} operator - Operator to use.
* #param {E1:F10} range - Second range to sum.
* #return the sum of all the values
* #customfunction
*/
function arr_arr(range1,op,range2) {
const one = [].concat(...range1);
const two = [].concat(...range2);
const sumOne = one.reduce((a, b) => a + b, 0);
const sumTwo = two.reduce((a, b) => a + b, 0);
let sum = 0;
switch (op) {
case "+":
sum = sumOne + sumTwo;
break;
case "-":
sum = sumOne - sumTwo;
break;
case "*":
sum = sumOne * sumTwo;
break;
case "/":
sum = sumOne / sumTwo;
break;
}
return sum;
}

Related

How to increment set of 2 letters based on word occurrences in the range using GAS?

I got this one that looks hairy to me, but I'm confident you guys can crack it while having fun.
The problem:
Check of Company exists in the range
If not, get the latest ID prefix, which looks like AA, AB, etc
Generate a new prefix, which would be the following, according to item above: AC
If that company occurs more than once, then increment the ID number sufix XX001, XX002, etc.
This is what I've come up with so far:
function generateID() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const clientSheet = ss.getSheetByName('Clients');
const dataRng = clientSheet.getRange(8, 1, clientSheet.getLastRow(), clientSheet.getLastColumn());
const values = dataRng.getValues();
const companies = values.map(e => e[0]);//Gets the company for counting
for (let a = 0; a < values.length; a++) {
let company = values[a][0];
//Counts the number of occurrences of that company in the range
var companyOccurrences = companies.reduce(function (a, b) {
return a + (b == company ? 1 : 0);
}, 0);
if (companyOccurrences > 1) {
let clientIdPrefix = values[a][2].substring(0, 2);//Gets the first 2 letters of the existing company's ID
} else {
//Generate ID prefix, incrementing on the existing ID Prefixes ('AA', 'AB', 'AC'...);
let clientIdPrefix = incrementChar(values[a][2].substring(0,1));
Logger.log('Incremented Prefixes: ' + clientIdPrefix)
}
}
}
//Increment passed letter
var alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('')
function incrementChar(c) {
var index = alphabet.indexOf(c)
if (index == -1) return -1 // or whatever error value you want
return alphabet[index + 1 % alphabet.length]
}
...and this is borrowing from tckmn's answer, which deals with one letter only.
This is the expected result:
This is the link to the file, should anyone want to give it a shot.
Thank you!
In your situation, how about the following modification?
Modified script:
// Please run this function.
function generateID() {
const ss = SpreadsheetApp.getActiveSpreadsheet();
const sheet = ss.getSheetByName('Clients');
const dataRng = sheet.getRange(8, 1, sheet.getLastRow() - 7, 1);
const values = dataRng.getValues();
let temp = "";
let init = "";
let count = 0;
const res = values.map(([a], i) => {
count++;
if (temp != a) {
count = 1;
temp = a;
init = i == 0 ? "AA" : wrapper(init);
}
return [`${init}${count.toString().padStart(3, "0")}`];
});
console.log(res)
sheet.getRange(8, 4, res.length, 1).setValues(res);
}
//Increment
var alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('')
function incrementChar(c) {
var index = alphabet.indexOf(c)
if (index == -1) return -1 // or whatever error value you want
return alphabet[index + 1 % alphabet.length]
}
// I added this function.
function wrapper(str) {
const [a, b] = [...str];
const r1 = incrementChar(a);
const r2 = incrementChar(b);
return (r2 ? [a, r2] : (r1 ? [r1, "A"] : ["over"])).join("");
}
In this modification, I added a wrapper function. This wrapper function uses your showing script of incrementChar.
When this script is run to your sample Spreadsheet, console.log(res) shows [["AA001"],["AA002"],["AA003"],["AA004"],["AA005"],["AB001"],["AB002"],["AB003"],["AC001"]]. And this value is put to the column "D".
Note:
This modified sample is for your provided Spreadsheet. So please be careful this.
Reference:
map()

subSetSum - powerSum JS . concatenating arrays into subset

With my very poor js knowledge, I am trying to solve the powerSum algorithm.Where I am supposed to return the count of the ways a number X can be expressed as the sum of the Nth powers of unique, natural numbers...
I have -somehow- got to the point where I can see my subsets printed out on the console but I haven't been able to figure out how to concatenate the result of my 'subsetSum' function to my 'subsets' variable so I can return my result as an array of arrays. The only way I get to have any returning value is if I concat my subsets into a STRING. and that is not what I am expecting. Here is my code.
// returns an array with all the results of natural numbers elevated
//to the nth power <= X
function powersLessThan(x,power){
let newArr = [];
for(var i = 1; i < x; i+=1){
var powered = Math.pow(i,power);
if (powered <= x){
newArr.push(powered);
}else if (powered > x){
break;
}
}
return newArr;
}
// returns an array of all the possible combinations of numbers that sum to X
function subsetsSum(numbersArr,target,partialSum){
var sum,n,remaining;
var subsets = [];
partialSum = partialSum || [];
sum = partialSum.reduce(function (a,b){
return a + b;
},0);
if (sum === target){
return partialSum; // this is my base case. datatype = object. Not sure why... ??
}
for (var i = 0; i < numbersArr.length; i+=1){
n = numbersArr[i];
remaining = numbersArr.slice( i + 1);
subsets.concat((subsetsSum(remaining,target,partialSum.concat([n]))));
}
return subsets;
}
console.log(subsetsSum(powersLessThan(100,2),100)); // with this my ooutput
is ' 1,9,16,25,4936,64100' instead of => [[1,9,16,25,49],[64,36],[100]] :/
The final count will be the length of the array above .. when it works..
Thanks for your help.
Change:
return partialSum;
To:
return [partialSum];
And change:
subsets.concat((subsetsSum(remaining,target,partialSum.concat([n]))));
To:
subsets = subsets.concat((subsetsSum(remaining,target,partialSum.concat([n]))));
(You could also just use, subsets.push(...))

javascript using reduce function

I have the below array
["0,5,p1", "24,29,p2", "78,83,p2", "78,83,p3", "162,167,p2" ]
i want the output as ["5,p1","10,p2","5,p3"] , so p1..3 are video files paying time with start and end time . 0,5 mean p1 profile played for 5 sec and so on.
I want to know what profile take what time in total using ECMA script map,reduce function. Here is what i tried but it doesnt work:
var ca = uniqueArray.reduce(function(pval, elem) {
var spl = elem.split(',');
var difference = Math.round(spl[1] - spl[0]);
return difference;
},elem.split(',')[3]);
I dont think it can be done in one pass, but I could be wrong. I'd go for a 2 step...
Reduce the array to get unique map of pX values
Map the result back to an array in the required format
var input = ["0,5,p1", "24,29,p2", "78,83,p2", "78,83,p3", "162,167,p2" ]
var step1 = input.reduce(function(p,c){
var parts = c.split(",");
if(!p[parts[2]])
p[parts[2]] = 0;
p[parts[2]] += parseInt(parts[1],10) - parseInt(parts[0],10);
return p;
},{});
var result = Object.keys(step1).map(function(e){
return step1[e] + "," + e;
});
console.log(result);
You could use es6 map:
arrayWithNumbers.map(a => {var spl = a.split(','); return (spl[1] - spl[0]) + "," + spl[2]})
For a single loop approach, you could use a hash table for same third parts, like 'p1'. If a hash is given, then update the value with the actual delta.
var array = ["0,5,p1", "24,29,p2", "78,83,p2", "78,83,p3", "162,167,p2"],
hash = Object.create(null),
result = array.reduce(function(r, a) {
var parts = a.split(','),
delta = parts[1] - parts[0],
key = parts[2];
if (!(key in hash)) {
hash[key] = r.push([delta, key].join()) - 1;
return r;
}
r[hash[key]] = [+r[hash[key]].split(',')[0] + delta, key].join();
return r;
}, []);
console.log(result);
I have updated the code. Please check now.
var ca = ["0,5,p1", "24,29,p2", "78,83,p2", "78,83,p3", "162,167,p2" ] .reduce(function(result, elem) {
var spl = elem.split(',');
var difference = Math.round(spl[1] - spl[0]);
var found = false
for (var i = 0 ; i < result.length; i++) {
if (result[i].split(',')[1] == spl[2]) {
result[i] = parseInt(result[i].split(',')[0]) + difference+","+spl[2];
found = true;
}
}
if (!found) result.push(difference+","+spl[2]);
return result;
},[]);
console.log("modified array",ca);

Random Selection from Array into new Array

I have seen this question answered here, but I have an additional question that is related.
Im trying to achieve:
the same thing, but, with the output being a selection of more than 1 number, the above works fine if you only want a single value returned.
How can I return (x) amount of outputs #7 in this case into a new var or array ...? Some guidance on best practice will also be appreciated ;)
Thanks a bunch....
Just for fun,
Objective:
Create a teeny weenie web App that returns 7 variable numbers in a range [ 1 - 49 ] into an array.
`
Think return a list of Lotto Numbers
Create new array from selection using _underscore.js [Sample function]
**** I know this is easier, but im trying to get an understanding
of using Vanilla JS to accomplish this
_.sample([1, 2, 3, 4, 5, 6], 3); => [1, 6, 2]
var getLoot = Array.from(Array(50).keys()) // Create array of 49 numbers.
console.info(getLoot);
var pick = getLoot[Math.floor(Math.random() * getLoot.length)];
pick;
// pick returns a single which is fine if you want a single but, ..
// I want something like this :
var pick = function() {
// code that will return 7 numbers from the array into a new Array
// will randomize every time pick is called...
}
If you want to return more than just 1 value you can store your results into a data structure like an array. Here is a solution to the problem
assuming you can pass in your array of 50 numbers into the pick() funciton.:
var getRandomArbitrary = function(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
var pick = function(fiftyNumberArray, numberOfValuesWanted) {
var randomNums = [];
for(var i = 0; i < numberOfValuesWanted; i++) {
randomNums.push(
fiftyNumberArray[getRandomArbitrary(0, 50)]
);
}
return randomNums;
};
var fiftyNumbers = [] // <- set your array of fifty numbers
pick(fiftyNumbers, 7);
Javascript's Math.random() will return a value in between 0 and 1 (exclusive). So to get an index scaled up to the correct value to look into your array, you would want to multiply that by the formula (max - min) + min
You can use Array.prototype.splice(), Math.floor(), Math.random(), for loop to remove elements from input array, return an array containing pseudo randomly picked index from input array without duplicate indexes being selected.
function rand(n) {
var res = []; var a = Array.from(Array(n).keys());
for (;a.length;res.push(a.splice(Math.floor(Math.random()*a.length),1)[0]));
return res
}
console.log(rand(50));
One good way of doing this job is shuffling the array and picking the first n values. Such as;
function shuffle(a){
var i = a.length,
j,
tmp;
while (i > 1) {
j = Math.floor(Math.random()*i--);
tmp = a[i];
a[i] = a[j];
a[j] = tmp;
}
return a;
};
var arr = Array(50).fill().map((_,i) => i+1); //[1,2,3...49,50]
randoms = shuffle(arr).slice(0,7) // to get 7 random numbers from arrary arr
console.log(randoms)
This is probably what you want.
$(function()
{
var lol = [1,4,5,6,7,8,9];
function getPart(array,number)
{
var part = [],
index;
while(true)
{
if(part.length == number)
{
break;
}
index = $.random(0,part.length);
part.push(lol.splice(index,1));
}
return part;
}
});
$.random = function(min,max,filter)
{
var i,
n = Math.floor(Math.random()*(max-min+1)+min);
if(filter != undefined && filter.constructor == Array)
{
for(i=filter.length-1;i>-1;i--)
{
if(n == filter[i])
{
n = Math.floor(Math.random()*(max-min+1)+min)
i = filter.length;
}
}
}
return n;
}

insert item in javascript array and sort

Let's say I have an array
var test = new Array()
the values in test are 3,6,9,11,20
if I then have a variable
var id = 5
how can I insert 5 between 3 and 6 in the array?
or do I just insert it wherever and then sort the array?
Thanks in advance.
edit:
I have the following code:
function gup( filter )
{
filter = filter.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
var regexS = "[\\?&]"+filter+"=([^&#]*)";
var regex = new RegExp( regexS );
var results = regex.exec( window.location.href );
if( results == null )
return "";
else
return results[1];
}
var queryString = gup("SelectedID");
var hrefs = new Array();
$('.table404').children().children().each(function(){
var link = ($(this).find('a').attr('href'));
var startIndex = link.indexOf(",'");
var endIndex = link.indexOf("');");
if ( startIndex >= 0 && endIndex >= 0 ) {
var linkID = link.substring( startIndex+2, endIndex );
hrefs.push(linkID);
hrefs.push(queryString);
hrefs.sort()
}
alert(hrefs);
});
for each item inserted into the array I get an alert with the ID but for every item I get one 1 (the current queryString value), so the last pop up looks something like
1,1,1,1,1,2,4,6,7,8
Why do I get a new pop up for every item inserted into the array? I get the querystring value once for every other item inserted into the array. What do I have to do to get one pop up with the complete array?
You can use a binary searach to find an insertion point, if you array is large enough:
Below is a quick code with tests. (Warning: not thoroughly tested). Also the array has to be a sorted array.
Once you have an insertion point, just use the Array.splice function to insert at that index.
/**
* Find insertion point for a value val, as specified by the comparator
* (a function)
* #param sortedArr The sorted array
* #param val The value for which to find an insertion point (index) in the array
* #param comparator The comparator function to compare two values
*/
function findInsertionPoint(sortedArr, val, comparator) {
var low = 0, high = sortedArr.length;
var mid = -1, c = 0;
while(low < high) {
mid = parseInt((low + high)/2);
c = comparator(sortedArr[mid], val);
if(c < 0) {
low = mid + 1;
}else if(c > 0) {
high = mid;
}else {
return mid;
}
//alert("mid=" + mid + ", c=" + c + ", low=" + low + ", high=" + high);
}
return low;
}
/**
* A simple number comparator
*/
function numComparator(val1, val2) {
// Suggested b #James
return val1 - val2;
}
// TESTS --------------------------------
var arr = [0,1,3,6,9,11,20];
var idx = findInsertionPoint(arr, 2, numComparator);
arr.splice(idx, 0, 2);
alert(arr); // will alert [0,1,2,3,6,9,11,20]
var arr2 = [0,1,3,6,9,11,20];
var idx2 = findInsertionPoint(arr2, -1, numComparator);
arr2.splice(idx2, 0, -1);
alert(arr2); // will alert [-1,0,1,3,6,9,11,20]
If you have different objects, the only thing you need to do is provide appropriate comparator function.
Or if the array is really small and if you are especially lazy today, you can just do:
test.push(2).sort();
test.push(2); test.sort();

Categories