How to unpivot a table using Google Apps Script? - javascript

I have a table in a spreadsheet which I want to unpivot using google apps script: each one of the month rows of the original table have to become multiple rows in the new table. The problem is the code doesn't produce the expected result.
Insted of creating arrays that ends like this (each line of the table ends with one different month):
[[...,'April'],[...,'September'],[...,'December']]
It's producing this (each line ends with the last month value of that line in the original table):
[[...,'December'],[...,'December'],[...,'December']]
Can someone see the mistake?
function myFunction() {
var ay_datos = [
['State', 'Month1', 'Month2', 'Month3', 'Number of months', 'Month'],
['California', 'April', 'September', 'December', 3, ''],
['Texas', 'January', 'March', '', 2, ''],
];
var ay_new = [
['State', 'Month1', 'Month2', 'Month3', 'Number of months', 'Month'],
];
for (i = 1; i < ay_datos.length; i++) {
var num_months = ay_datos[i][4];
var ay_linea = ay_datos[i];
for (j = 0; j < num_months; j++) {
ay_linea[5] = ay_linea[1 + j];
ay_new.push(ay_linea);
}
}
}

You're pushing the same array each time in a loop. Any modifications done to the array will reflect on all references of the same array. Use slice to copy a array:
ay_linea[5] = ay_linea[1 + j];
ay_new.push(ay_linea.slice(0));
Live snippet:
function myFunction() {
const ay_datos = [
['State', 'Month1', 'Month2', 'Month3', 'Number of months', 'Month'],
['California', 'April', 'September', 'December', 3, ''],
['Texas', 'January', 'March', '', 2, ''],
];
const ay_new = [
['State', 'Month1', 'Month2', 'Month3', 'Number of months', 'Month'],
];
for (let i = 1; i < ay_datos.length; i++) {
const num_months = ay_datos[i][4];
const ay_linea = ay_datos[i];
for (let j = 0; j < num_months; j++) {
ay_linea[5] = ay_linea[1 + j];
ay_new.push(ay_linea.slice(0));
}
}
return ay_new;
}
console.log(myFunction());

Related

How to create 2d array using "for" loop in Javascript?

I need to write a program that creates a 2d array in variable "numbers" in rows (5) and columns (4). The elements of the array have to be consecutive integers starting at 1 and end at 20. I have to use "for" loop.
[ 1, 2, 3, 4 ],
[ 5, 6, 7, 8 ],
[ 9, 10, 11, 12 ],
[ 13, 14, 15, 16 ],
[ 17, 18, 19, 20 ],
So I came up with that:
const numbers = [];
const columns = 4;
const rows = 5;
for (let i = 0; i < rows; i++) {
numbers [i] = [];
for (let j = 0; j < columns; j++){
numbers [i][j] = j + 1;
}
}
console.log(numbers);
But the result of this is five identical rows, like this:
[ 1, 2, 3, 4 ],
[ 1, 2, 3, 4 ],
[ 1, 2, 3, 4 ],
[ 1, 2, 3, 4 ],
[ 1, 2, 3, 4 ]
Do you have any idea how to fix it? How to make second row starting from 5?
Here is some updated code. You need to add i*columns to every value
const numbers = [];
const columns = 4;
const rows = 5;
for (let i = 0; i < rows; i++) {
numbers[i] = [];
for (let j = 0; j < columns; j++){
numbers[i][j] = j + 1 + (i*columns);
}
}
console.log(numbers);
Looks like in the second loop, you should do numbers [i][j] = j * i; instead
Every time the outer for loop starts a new iteration, j is reset back to 0, which is why you keep getting rows starting with 1.
To fix this, you could declare a variable outside of the for loops that tracks the current number, and use that instead of j like so:
const numbers = [];
const columns = 4;
const rows = 5;
let currNum = 0;
for (let i = 0; i < rows; i++) {
numbers [i] = [];
for (let j = 0; j < columns; j++){
currNum++;
numbers [i][j] = currNum;
}
}
console.log(numbers);

Calculating the total of values in JS function and displaying it on a row

I have a custom script I'm using in google sheets to pull data from a sheet and to display on a different sheet. I want it to calculate the sum of hours (the last values in the arrays) and display it in the "week x total x" row pushed by the function. I cant seem to figure out how to do this.
Here is the function
var array = [
['123', 'x', 3, 3],
['123', 'x', 2, 3],
['123', 'x', 3, 3],
['123', 'x', 4, 3],
['123', 'x', 2, 3],
['123', 'x', 4, 3],
['123', 'x', 4, 3],
['123', 'x', 3, 3],
['123', 'x', 3, 3],
['123', 'x', 3, 3],
]
function sortByWeek(array) {
for (var i = 1; i < array.length; i++) {
// sort tasks by week number
for (var j = 0; j < i; j++) {
if (array[i][2] < array[j][2]) {
var x = array[i];
array[i] = array[j];
array[j] = x;
}
}
}
// insert rows between different weeks;
var arrayFinal = [];
let same = array[0][2];
let total = 0;
for (var e = 0; e < array.length; e++) {
// If week num is same as previous -> add item
if (array[e][2] === same) {
total = total + array[e][3];
arrayFinal.push(array[e]);
}
// If week num is no the same as previous -> add rows -> add item
else {
// If not first -> add rows
if (!(e === 0)) {
var rows = [
["Week " + same + " total " + total]
];
for (var s = 0; s < rows.length; s++) {
arrayFinal.push(rows[s]);
}
total = 0;
}
// add item
arrayFinal.push(array[e]);
}
same = array[e][2];
}
for (var s = 0; s < rows.length; s++) {
arrayFinal.push(rows[s]);
}
for (var z = 0; z < arrayFinal.length; z++) {
console.log(arrayFinal[z].toString());
}
}
This is what it gives me
123,x,2,3
123,x,2,3
Week 2 total 6
123,x,3,3
123,x,3,3
123,x,3,3
123,x,3,3
123,x,3,3
Week 3 total 12
123,x,4,3
123,x,4,3
123,x,4,3
Week 3 total 12
But this is what i want it to give:
123,x,2,3
123,x,2,3
Week 2 total 6
123,x,3,3
123,x,3,3
123,x,3,3
123,x,3,3
123,x,3,3
Week 3 total 15
123,x,4,3
123,x,4,3
123,x,4,3
Week 3 total 9
I have been staring this for too long and there must be super simple answer to this but i cant see it.
You can do this in a much shorter way if you use Array.sort() and Array.reduce()`:
var arr = [
['123', 'x', 3, 3],
['123', 'x', 2, 3],
['123', 'x', 3, 3],
['123', 'x', 4, 3],
['123', 'x', 2, 3],
['123', 'x', 4, 3],
['123', 'x', 4, 3],
['123', 'x', 3, 3],
['123', 'x', 3, 3],
['123', 'x', 3, 3] ];
const res=arr
.sort(([,,a],[,,b])=>a-b)
.reduce((sum=>(a,c,i,ar)=>{
a.push(c.join(",")); // to turn it into a string
sum+=c[3]
if (!ar[i+1]||ar[i+1][2]!=c[2]){
a.push("week "+c[2]+" total "+sum);
sum=0;
}
return a;
})(0), []);
console.log(res.join("\n")) // to turn the res array into a string
I applied a little trick in the reduce function: instead of directly supplying a callback function as a first argument it I placed an IIFE there that creates a scope for the function that is to be returned as the actual callback function. In this scope the variable sum is initialised to 0. sum is available in all subsequent calls of the callback function and is used for the weekly summation.
Whenever the "next entry" (ar[i+1]) of the array does not exist or its element ar[i+1][2] differs from the current one (c[2]) a text line with the sum is inserted into the array a and sum is reset to 0 again.
additional remark:
Your own script can be repaired by changing the else part with the immediately following for loop from your version into this:
// If week num is not the same as previous -> add rows -> add item
else {
// ** "if not first"-test is obsolete!
arrayFinal.push(["Week " + same + " total " + total]);
total = array[e][3]; // ** NOT 0!
// add item
arrayFinal.push(array[e]);
}
same = array[e][2];
}
// ** do not use the old "rows" array here!
arrayFinal.push(["Week " + same + " total " + total]);

Infinity loop error using the depth-first search algorithm

I'm trying to create a Boggle Solver program and I am having errors with my depth-first-search function. After I iterate through the 'visited' array using a for-loop, my function should return and start going through my trie again. Instead, it continues to print the value that is found in the visited array. The code is displayed below.
// Sample Boggle Dictionary
var boggle_dxctionary = ['apple', 'pickle', 'side',
'sick', 'moo', 'cat',
'cats', 'man', 'super',
'antman', 'godzilla', 'dog',
'dot', 'sine', 'cos',
'signal', 'bitcoin', 'cool',
'kick', 'zapper'
];
// Sample Boggle Board
var boggle_board = [
['c', 'n', 't', ],
['d', 'a', 't', ],
['o', 'o', 'm', ],
];
var column_length = boggle_board[0].length;
var row_length = boggle_board.length;
var trie_node = {
'valid': false,
'next': {}
};
var neighbors_delta = [
[-1, -1],
[-1, 0],
[-1, 1],
[0, -1],
[0, 1],
[1, -1],
[1, 0],
[1, 1],
];
function generate_trie(word, node)
{
if (!(word))
{
return;
}
if ((word[0] in node) == false)
{
node[word[0]] = { 'valid': (word.length == 1),'next': {}};
}
generate_trie(word.slice(1, ), node[word[0]]);
}
function build_trie(boggle_dxct, trie) {
for (var word = 0; word < boggle_dxct.length; word++) {
generate_trie(boggle_dxct[word], trie);
}
return trie;
}
function get_neighbors(row, column)
{
var neighbors = [];
for (var neighbor = 0; neighbor < neighbors_delta.length; neighbor++)
{
var new_row = row + neighbors_delta[neighbor][0];
var new_column = column + neighbors_delta[neighbor][1];
if (new_row >= row_length || new_column >= column_length || new_row < 0 || new_column < 0)
{
continue;
}
neighbors.push([new_row, new_column]);
}
return neighbors;
}
function depth_first_search(row, column, visited, trie, current_word, found_words, board)
{
var row_column_pair = [row, column];
for (var i = 0; i < visited.length; i++) # Infinity loop error
{
var a = visited[i][0];
var b = visited[i][1];
if (row == a && column == b)
{
console.log(a,b);
return;
}
}
var letter = board[row][column];
visited.push(row_column_pair);
if (letter in trie)
{
current_word = current_word + letter;
console.log(current_word)
if (trie[letter]['valid'])
{
console.log("Found word", current_word, "at", row_column_pair);
found_words.push(current_word);
//console.log(visited);
}
var neighbors = get_neighbors(row, column);
for (n = 0; n < neighbors.length; n++)
{
depth_first_search(neighbors[n][0], neighbors[n][1], visited.slice(0), trie[letter], current_word, found_words, board);
}
}
}
function main(trie_node, board) {
trie_node = build_trie(boggle_dxctionary, trie_node);
var found_words = [];
for (r = 0; r < row_length; r++) {
for (c = 0; c < column_length; c++)
{
var visited = [];
depth_first_search(r, c, visited, trie_node, '', found_words, board);
}
}
console.log(found_words);
}
main(trie_node, boggle_board);
The problem in your code is in depth_first_search where you did not define n as a local variable, but as implicit global variable.
Your code will run fine when you replace this:
for (n = 0; n < neighbors.length; n++)
with:
for (var n = 0; n < neighbors.length; n++)
NB: I assume there was a little typo in the input, where "moo" should really be "mood".
I suggest to use a more simplified trie without overhead.
Then iterate the given board and check the actual position and by handing over the root node of trie.
A word is found if end property exists.
function check(i, j, t, found = '') {
const letter = board[i][j];
if (!t[letter]) return;
found += letter
if (t[letter].end) {
words.push(found);
}
for (let [x, y] of neighbors) {
if (i + x < 0 || i + x >= rows || j + y < 0 || j + y >= cols) continue;
check(i + x, j + y, t[letter], found);
}
}
const
dictionary = ['apple', 'pickle', 'side', 'sick', 'moo', 'cat', 'cats', 'man', 'super', 'antman', 'godzilla', 'dog', 'dot', 'sine', 'cos', 'signal', 'bitcoin', 'cool', 'kick', 'zapper'],
board = [['c', 'n', 't'], ['d', 'a', 't'], ['o', 'o', 'm']],
rows = board.length,
cols = board[0].length,
neighbors = [[-1, -1], [-1, 0], [-1, 1], [0, -1], [0, 1], [1, -1], [1, 0], [1, 1]],
trie = dictionary.reduce((trie, word) => {
[...word].reduce((t, c) => t[c] = t[c] || {}, trie).end = true;
return trie;
}, {}),
words = [];
for (let i = 0; i < rows; i++) {
for (let j = 0; j < cols; j++) {
check(i, j, trie);
}
}
console.log(words);
console.log(trie);
.as-console-wrapper { max-height: 100% !important; top: 0; }

finding available combinations (permutations?) of two arrays

Having a hard time googling for this since I'm not sure what the concepts are called, and all of the "combinations of two arrays/groups" SO posts are not giving me the output I would expect.
Example arrays:
var array1 = ['Bob', 'Tina'];
var array2 = [0, 100];
I can find possible combinations with nested looping through both arrays. But that would give me an output like:
var array1 = ['Bob', 'Tina'];
var array2 = [0, 100];
var options = []
array1.forEach(function (name) {
array2.forEach(function (number) {
options.push([name, number])
})
})
console.log(options);
> [ [ 'Bob', 0 ], [ 'Bob', 100 ], [ 'Tina', 0 ], [ 'Tina', 100 ] ]
this post (Creating Combinations in JavaScript) gives me the output above
But what I'm really looking for would give me arrangements/combinations like this:
[
[['Bob', 0], ['Tina', 0]],
[['Bob', 0], ['Tina', 100]],
[['Bob', 100], ['Tina', 0]],
[['Bob', 100], ['Tina', 100]]
]
And it would need to be able to scale with longer arrays, but 2x2 is the easiest example.
This cartesian example here (Matrix combinations of two arrays in javascript) also gave me broken out strings and not correlated arrangements:
[ { '0': 'B', '1': 'o', '2': 'b' },
{ '0': 'B', '1': 'o', '2': 'b' },
{ '0': 'T', '1': 'i', '2': 'n', '3': 'a' },
{ '0': 'T', '1': 'i', '2': 'n', '3': 'a' } ]
I have been looking through google and SO but I'm hitting roadblocks because I'm not sure what I'm actually looking for.
I hope this might help you to get your actual combination:
var array1 = ['Bob', 'Tina'];
var array2 = [0, 100];
var resultArray = []
for (var i = 0; i < array1.length; i++) {
for (var j = 0; j < array2.length; j++) {
var tempArray = [];
tempArray.push(array1[i]);
tempArray.push(array2[j]);
resultArray.push(tempArray);
}
}
for (var i = 0; i < array2.length; i++) {
for (var j = 0; j < array1.length; j++) {
var tempArray = [];
tempArray.push(array1[j]);
tempArray.push(array2[i]);
resultArray.push(tempArray);
}
}
console.log(resultArray);
I'll give you some hints that you might find helpful. First, I assume you know how to iterate over an array:
var array1 = ['Bob', 'Tina'];
for (var i = 0; i < array1.length; i++) {
// you can access the ith element using array1[i]
// for example:
alert(array1[i]);
}
I also assume you know how to execute nested looping:
var array1 = ['Bob', 'Tina'];
var array2 = [0, 100];
for (var i = 0; i < array1.length; i++) {
for (var j = 0; j < array2.length; j++) {
alert(array1[i] + " " + array2[j]):
}
}
What you may be missing is how to construct a new array:
var five = 5;
var six = 6;
var myArray = [five, six];
You also may be missing the ability to push a new array onto an existing "master" array:
var five = 5;
var six = 6;
var masterArray = [];
for (var i = 0; i < 10; i++) {
masterArray.push([five, six]);
}
Hope this helps, and good luck!
This solves the problem, but don't know how efficient it is - I'll bet there's much better ways of doing it.
var array1 = ['Bob', 'Tina', 'Sam'];
var array2 = [0, 100, 200];
var resultCount = Math.pow(array2.length, array1.length);
var results = new Array(resultCount);
for(var i = 0; i < resultCount; i++) {
results[i] = new Array(array1.length);
}
for(var row = 0; row < resultCount; row++) {
for(var column = 0; column < array1.length; column++) {
var result = row * array1.length + column;
var category = array1.length * Math.pow( array2.length, array1.length - 1 - column);
var idx = ~~(result / category) % array2.length;
results[row][column] = [ array1[column], array2[idx] ];
}
}
console.log(results);
I think you could actually do this in terms of number/string manipulation by thinking of array2 as a radix (each value maps to a digit) and array1 as the number of digits. As long as array2 has 36 or less elements you could convert every number from 0 up to the number of combinations as a string (Number.toString(radix)) and then convert each char from the resulting string back to an index into your arrays.

Returning a Range with some rows having merged columns in Google Appscript

I am to create a custom function for Google Sheets using Google AppScript. I have a set of data that my custom function will summarize and display the summary on another sheet.
I am able to create my on algorithm for the summary and return a range like this:
Current
Now I understand that an Appscript function returns a two dimensional array, but my problem is how do I merge some of the cells Using Appscript? Whenever there's a month? Like this:
Target
For the example above,
the range I will be returning from my custom function will be
[
['January', ''],
['Project 1', '10 Hours'],
['Project 2', '20 Hours'],
['Project 3', '30 Hours'],
['Project 4', '40 Hours'],
['Project 5', '50 Hours'],
['February', ''],
['Project 1', '10 Hours'],
['Project 2', '20 Hours'],
['Project 3', '30 Hours'],
['Project 4', '40 Hours'],
['Project 5', '50 Hours']
]
So how do I tell AppScript to tell GooglSheets that ['January', ''], and ['February', ''], are cells to be merged?
If there's a function in Excel that can do this, I am also open to other methods. :)
Thanks
This example copy table from Sheet1 to Sheet2 to 3 row and 4 column.
function myFunction() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet1 = ss.getSheets()[0];
var data = sheet1.getDataRange().getValues();
var mergeRows = [];
for(var i = 0; i < data.length; i++) {
if (data[i][1] == "") {
mergeRows.push(i);
}
}
var startRow = 3;
var startCol = 4;
var sheet2 = ss.getSheets()[1];
sheet2.getRange(startRow, startCol, data.length, data[0].length).setValues(data);
for(var i = 0; i < mergeRows.length; i++) {
sheet2.getRange(startRow + mergeRows[i], startCol, 1, 2).merge().setHorizontalAlignment('center');
}
}
You would have to use the merge() or mergeAcross() method.
Something like
sheet.getRange('A1:B1').merge();
sheet.getRange('A7:B7').merge();

Categories