Need help looping though 3 loops to append data in Google Scripts - javascript

I am working with Col A, B & C. Col A contains A-E, Col B Contains 1, a, 3, b, 5 and Col C will be where I will store duplicated information (a and b would go into C1 & C2). Any help would be appreciated. In summary; compare A and B for similarity, output result into C
function appendString() {
var range = SpreadsheetApp.getActiveSheet().getRange("A1:A5");
var range2 = SpreadsheetApp.getActiveSheet().getRange("B1:B5");
var range3 = SpreadsheetApp.getActiveSheet().getRange("C1:C5")
var numRows = range.getNumRows();
var x = 0
// var numCols = range.getNumColumns();
j = 1 // row A
k = 2 // row B
m = 3 // row C
n = 1
// First loop though B
for (var i = 1; i <= numRows; i++) {
// set the current value...
var currentValue = range.getCell(i, j).getValue();
// In the log tell us the current value
Logger.log("Set A:" + currentValue);
// Loops though col B to compare to col A
for (var l = 1; l <= numRows; l++) {
// Sets the current value to compare value
var compareValue = range2.getCell(l, j).getValue();
Logger.log("Set B:" + compareValue)
// If the compareValue and the currentValue (match)
if (compareValue === currentValue) {
Logger.log("MATCHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH");
// We will write the result to col C down one row
for (n; n <= x; n++) {
// this makes it only run once'
range3.setValue(currentValue);
Logger.log("Appending.................");
x = n + 3
}
}
}
}
}

I think your problem statement boils down to this: Fill column C with a list of unique values that appear in both column A and B.
There is a built-in javascript Array method Array.indexOf() that makes it very easy to search for matching elements. As the problem is defined, we want to search in a column, so to use that method we need a column to be represented as an Array. The Range.getValues() method allows us to load a whole range of values at once, and delivers them as a two-dimensional array, with rows as the first dimension. We need columns there, and we can achieve that by a matrix transposition.
So here's what we end up with. There isn't a built-in transpose(), so I've included one. As we search for matches, results are stored in an Array C, using the built-in Array.push() method. Finally, array C is treated as a two-dimensional array, transposed, and written out to the sheet in column C.
function recordMatches() {
var range = SpreadsheetApp.getActiveSheet().getRange("A1:B5");
var data = range.getValues();
// For convenience, we'll transpose the data, so
// we can treat columns as javascript arrays.
var transposed = transpose(data);
var A = transposed[0],
B = transposed[1],
C = [];
// Go through A, looking for matches in B - if found, add match to C
for (var i=0; i < A.length; i++) {
if (B.indexOf(A[i]) !== -1) C.push(A[i]);
}
// If any matches were found, write the resulting array to column C
if (C.length > 0) {
var rangeC = SpreadsheetApp.getActiveSheet().getRange(1,3,C.length);
rangeC.setValues(transpose([C]));
}
}
function transpose(a) {
return Object.keys(a[0]).map(function (c) { return a.map(function (r) { return r[c]; }); });
}

Related

Apply array order by only moving

I want to reorder an array of distinct elements to look like a second array, but only using move operations.
Example:
Index 0 1 2 3 4
Array e b a c d
Order a b c d e
Moving operations (insert at target index):
original e b a c d
0→4 b a c d e
1→0 a b c d e
I would like to minimize the amount of moving operations and get a list of the operations (from/to index) as a result.
My background is that I fetch and sort a list of IDs from a server using javascript. However, I can only update the sorting by single-element "move to index" commands to their API.
Solution 1:
a) use the ordered array and insert all elements at the end
b) or at position 0 in reverse order.
That would require N operations for N elements, though, even for the best case.
Solution 2:
Iterate the ordered array and move elements into ordered position.
Furthermore, the element previously there is move to its desired position.
The latter helps if two letters are swapped, as in e b c d a.
While that reduces the best case scenario to 0 moves, most random cases require >N operations for N elements.
Code example for Solution 2:
function arraymove(arr, fromIndex, toIndex) {
var element = arr[fromIndex];
arr.splice(fromIndex, 1);
arr.splice(toIndex, 0, element);
}
function shuffleArray(array) {
for (var i = array.length - 1; i > 0; i--) {
var j = Math.floor(Math.random() * (i + 1));
var temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
let newOrder = [...Array(20).keys()];
let oldOrder = [...newOrder];
//oldOrder.reverse() //reverse case
shuffleArray(oldOrder); //random case
//arraymove(oldOrder, 0, 99); //end to beginning case
let checkOrder = [...oldOrder];
let moveOrder = [];
for (ind = newOrder.length - 1; ind >= 0; --ind) {
let el = newOrder[ind];
if (checkOrder[ind] != el) {
let oldind = checkOrder.indexOf(el);
let altel = checkOrder[ind];
arraymove(checkOrder, oldind, ind);
moveOrder.push({ el: el, ind: ind, old: oldind });
let altind = checkOrder.indexOf(altel);
if (newOrder[newOrder] != altel) {
let newind = newOrder.indexOf(altel);
arraymove(checkOrder, altind, newind);
moveOrder.push({ el: altel, ind: newind, old: altind });
}
}
}
document.write("oldOrder: ", oldOrder.join(" "),"<br> Moves: ", moveOrder.length);
Is there a better solution, or perhaps an already established algorithm I didn't find?
The example from above is sadly not found by Solution 2.

How to load a multidimensional array using for loop in JS?

[just joined. first post \o/]
I'm working on a 'battleblocks' project idea of mine to help learn JS, where I have a 10x10 css grid of dynamically created divs. They are identifiable from numbers 1 to 100, reading left to right (row 1 has 1,2,3..10, row 2 has 11,12..20 etc). I need to be able to have a nested array of columns that house 10x arrays (columnArray[0] contains 1,11,21..91 - columnArray[1] contains 2,12,22..92 etc). And the same for rows - a row array that has 10x row arrays (rowArray[0] contains 1,2,3..10 - rowArray[1] contains 11,12,13..20 etc).
Ive declared column array globally, but as it stands whatever ive done so far causes a 'aw, snap! something went wrong while displaying this webpage.' error.
loadColsArray();
// load column arrays
function loadColsArray() {
let rowsAr = [];
let count = 0;
for (let c = 1; c <= 10; c++) {
for (let r = 0; r <= 100; r + 10) {
rowsAr[count] = c + r;
count++;
}
columnArray[c - 1] = rowsAr;
count = 0;
rowsAr = [];
}
console.log(columnArray);
}
Any help appreciated.
ps: added code as a snippet, because 'code sample' option broke up my pasted code.
There are a few problems in your code:
The "Aw Snap" is caused by an infinite loop in your code which occurs because you never increment r. You must use r += 10 to increment it by 10.
Since you initialise r to 0, your exit condition must be r < 100, otherwise 11 iterations will occur.
You also need to define columnArray before you use it (it's not defined in the snippet).
Try this:
let columnArray = []; // ←
loadColsArray();
// load column arrays
function loadColsArray() {
let rowsAr = [];
let count = 0;
for (let c = 1; c <= 10; c++) {
for (let r = 0; r < 100; r += 10) { // ←
rowsAr[count] = c + r;
count++;
}
columnArray[c - 1] = rowsAr;
count = 0;
rowsAr = [];
}
console.log(columnArray);
}

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.

Concatenate a dynamic Number of columns Google Script

I need to concatenate x number of columns per project sometimes it's 3 columns others 7 or 5, it just depends
I am trying to this with an array of range column numbers ex [2,5,3]
columns 2,5 3 in that order with a delimiter here |
I have searched but found only static concatenating functions
I have a VBA Macro that works as I need in Excel so I am trying to write it in Google Script
The function runs without erroring but nothing is posted back
From Logger.log() I am kinda close to the proper structure
I get undefined|b|e|c
I want to post back to the last column + 1
I am not sure this is the best way to do this but it what I have
Any help is appreciated, Thanks
colA ColB ColC ColD ColE ColF ColG ColH
a b cc d e f g b|e|c
a2 b2 d2 e2 f2 g2 e2|c2
ect.
Here is what I have:
function TemplateA_n() {
Template_A("A", [2, 4, 6])
}
function Template_A(SshtName, sArr){
var sSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SshtName);
var sR = sSheet.getDataRange();
var sV = sR.getValues();
var sLC = sSheet.getLastColumn();
var sLR = sSheet.getLastRow();
var a = []
//Rows
for (var row = 2; row < sLR; row++){
a[row] =[]
//Columns
for (var col = 0; col < sArr.length ; col++){
if(sV[row][sArr[col]] !== "") {
if(sV[row][sArr[0]] == "") {
a[row][0] = a[row][0] + sV[row][sArr[col]];
Logger.log(a[row][0])
}
else {
a[row][0] = a[row][0] + "|" + sV[row][sArr[col]];
Logger.log(a[row][0])
}
}
}
}
sSheet.getRange(1,sLC + 1,sLR,1);
}
Here is the Macro
Sub ConCatA()
Dim rng As Range, r As Range, i As Long
On Error Resume Next
Set rng = Application.InputBox("Select column(s)", Type:=8)
'Set rng = Range("B1,A1,C1")
On Error GoTo 0
If rng Is Nothing Then Exit Sub
With ActiveSheet.UsedRange
ReDim a(1 To .Rows.Count, 1 To 1)
a(1, 1) = "Concat"
For i = 2 To .Rows.Count
For Each r In rng
If .Cells(i, r.Column) <> "" Then
a(i, 1) = a(i, 1) & IIf(a(i, 1) = "", "", "|") & .Cells(i, r.Column).value
End If
Next r
Next i
With .Offset(, .Columns.Count).Resize(, 1)
.value = a
End With
End With
End Sub
Part 1: Undefined value in output
The reason you get the following ouput: undefined|b|e|c is because the variable a[row][0] is undefined before you assign it any value. So when program runs the following line of code for the first time in the loop it concats the value of sV[row][sArr[col]]to undefined.
a[row][0] = a[row][0] + sV[row][sArr[col]]
All you need to do is assign an empty value to begin with, like so
for (var row = 2; row < sLR; row++){
a[row] =[]
a[row][0] = ""
... your code here
}
Also, since the assignment of values only start from index 2 in the loop, we need to assign index 0 and 1.
a[0] = [""]
a[1] = [""]
This will enable us to input blank values in the sheet when we use setvalues function with this array.
Part 2: Append values to sheet (lastColumn + 1)You define the range to append your data and then set its values, as follows:
var appRange = Sheet.getRange(2,sLC+1,a.length,1)
appRange.setValues(a)
Your final code would look like this:
function Template_A(SshtName, sArr){
var sSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(SshtName);
var sR = sSheet.getDataRange();
var sV = sR.getValues();
var sLC = sSheet.getLastColumn();
var sLR = sSheet.getLastRow();
var a = []
//Rows
a[0]= [""] // Since you are start from index 1, you need to assign a value to index 0
for (var row = 1; row < sLR; row++){ //If you intended to start from 2nd row
// var row = 1 and not 2
a[row] = []
a[row][0] = "" //Empty value for each row
//Columns
for (var col = 0; col < sArr.length ; col++){
if(sV[row][sArr[col]-1] !== "") {
if(a[row][0] == "") { //Check to see if the array is empty
// If yes donot add "|"
a[row][0] = a[row][0] + sV[row][sArr[col]-1];
Logger.log(a[row][0])
}
else {
a[row][0] = a[row][0] + "|" + sV[row][sArr[col]-1];
Logger.log(a[row][0])
}
}
}
}
Logger.log(a)
var appRange = sSheet.getRange(1,sLC+1,a.length,1)
appRange.setValues(a)
}
Final Note: If you intend to skip the first row in your sheet your loop should start with counter 1. Since array index starts from 0 but row numbering in sheet start from 1.

Navigating the columns and rows in pasting array values

I'm trying to get the list of names from the Column A, randomize the list, and then distribute them evenly (as much as possible, including the remainders from division) by the user-specified number of groups.
An example of what I need is like this:
List of Names: A, B, C, D, E, F, G, H, I, J, K
Result of 3 Groups:
Group 1: D, A, F
Group 2: B, H, G, K
Group 3: E, C, I, J
Edited: I've cleaned up the code: I have assigned the list of names to an empty array and randomized the array successfully. Now I've got to figure out how to paste these values in their own columns for each groups. How do I paste the values to the right and down each column also accounting for the remainders (the first values are the headers for each columns):
Column C: Group 1, D, A, F
Column D: Group 2, B, H, G, K
Column E: Group 3, E, C, I, J
This is what I have so far:
function onOpen() {
SpreadsheetApp.getUi() // Or DocumentApp or FormApp.
.createMenu('Custom Menu')
.addItem('Show prompt', 'showPrompt')
.addToUi();
}
function SortNames() {
var ui = SpreadsheetApp.getUi();
var result = ui.prompt(
'How many groups?',
ui.ButtonSet.OK_CANCEL);
// Process the user's response.
var button = result.getSelectedButton();
var groupquantity = result.getResponseText();
if (button == ui.Button.OK) {
// User clicked "OK" - Need to clear the cells from the previous sorting in this step
// Get the last row number of the names list
var Avals = SpreadsheetApp.getActiveSheet().getRange("A1:A").getValues();
var Alast = Avals.filter(String).length;
// Set an empty Array
var ar = [];
/****** In its original order, append the names to the array *****/
for (var i = 2; i < Alast+1; i++) {
var source = 'A' + i;
var Avals = SpreadsheetApp.getActiveSheet().getRange(source).getValues();
ar.push(Avals);
}
/***************************/
/****** Shuffle the array *****/
function shuffle(a) {
var j, x, i;
for (i = a.length; i; i--) {
j = Math.floor(Math.random() * i);
x = a[i - 1];
a[i - 1] = a[j];
a[j] = x;
}
}
shuffle(ar);
/***************************/
/****** Calculates the rounded down # of members per group *****/
var memberspergroup = ar.length / groupquantity;
var memberspergroup = Math.floor(memberspergroup);
/*********************************/
/****** Copy and Paste the rounded down number of members to each groups until
the remainder is 0, then distribute evenly with remaining number of groups *****/
// First Cell location to paste
var pasteloc = "C1";
for (var i = 1; i <= groupquantity; i++) {
SpreadsheetApp.getActiveSheet().getRange(pasteloc).setValue('Group ' + i);
var source = 'A' + i;
var Avals = SpreadsheetApp.getActiveSheet().getRange(source).getValues();
}
/*********************************/
}
else if (button == ui.Button.CANCEL) {
// User clicked "Cancel".
ui.alert('The request has been cancelled');
}
else if (button == ui.Button.CLOSE) {
// User clicked X in the title bar.
ui.alert('You closed the dialog.');
}
}
I have found the solution to my question. It is not in the best shape - it can use an improvement to account for empty cells in the names list. However, it functions properly, and does everything I was looking for:
It assigns the list of names in an array
Randomizes the array
Distributes completely evenly which takes account for remainders (Just like the example I provided above)
function onOpen() {
SpreadsheetApp.getUi() // Or DocumentApp or FormApp.
.createMenu('Custom Menu')
.addItem('Show prompt', 'showPrompt')
.addToUi();
/******closing function onOpen()*********************/
}
function SortNames() {
var ui = SpreadsheetApp.getUi();
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var result = ui.prompt(
'How many groups?',
ui.ButtonSet.OK_CANCEL);
// Process the user's response.
var button = result.getSelectedButton();
var groupquantity = result.getResponseText();
if (button == ui.Button.OK) {
// User clicked "OK"
// Get the last row number of the names list
var Avalues = sheet.getRange("A1:A").getValues();
var Alast = Avalues.filter(String).length;
if(groupquantity > 0 && groupquantity <= Alast)
{
// User inputted a valid group quantity - Need to clear the cells from the previous sorting in this step
var lastRow = SpreadsheetApp.getActiveSheet().getMaxRows();
sheet.getRange('C1:Z'+lastRow).clearContent();
// Set an empty Array
var ar = [];
/****** In its original order, append the names to the array *****/
for (var i = 2; i < Alast+1; i++) {
var source = 'A' + i;
var Avals = sheet.getRange(source).getValues();
ar.push(Avals);
}
/***************************/
/****** Shuffles array *****/
function shuffle(a) {
var j, x, i;
for (i = a.length; i; i--) {
j = Math.floor(Math.random() * i);
x = a[i - 1];
a[i - 1] = a[j];
a[j] = x;
}
/**********closing function shuffle(a)*****************/
}
shuffle(ar);
/****** Calculates the rounded down # of members per group *****/
var memberspergroup = Math.floor(ar.length / groupquantity);
/*********************************/
/****** Main Goal: Copy and Paste the rounded down number of members to each groups until
the remainder is 0, then distribute evenly with remaining number of groups *****/
// 1. Define the first Cell location to paste
var pasteloc = "C1";
// 2. Begin a for-loop: Navigate Horizontally across from the first cell location
for (var i = 1; i <= groupquantity; i++)
{
// 3. Set the Column Headings in the current column, i
sheet.getRange(1,i+2).setValue('Group ' + i);
/************** 4. Fill in the Rows of names for each groups **********/
// List out the values in array "ar" by the expected group qty, until the remainder is zero
if ((ar.length)%(groupquantity-(i-1)) > 0)
{
for (var rows = 2; rows <= memberspergroup+1; rows++)
{
var j = 0;
sheet.getRange(rows,i+2).setValue(ar[j]);
var index = ar.indexOf(ar[j]);
ar.splice(index, 1);
}
}
else
{
var memberspergroup = ar.length/(groupquantity-(i-1))
for (var rows = 2; rows <= memberspergroup+1; rows++)
{
var j = 0;
sheet.getRange(rows,i+2).setValue(ar[j]);
var index = ar.indexOf(ar[j]);
ar.splice(index, 1);
}
}
}
/*********************************/
}
/*****************closing if(groupquantity > 0 && groupquantity <= Alast)****************/
else{
ui.alert("Error: " + '"' + groupquantity + '"' +" is not a proper group quantity")
}
}
else if (button == ui.Button.CANCEL) {
// User clicked "Cancel".
}
else if (button == ui.Button.CLOSE) {
// User clicked X in the title bar.
}
}
I hope this helps!

Categories