Parse out concatenate columns into separate rows - javascript

Hi guys im using google app script trying to get the data from google form to transpose from raw data to sorted table. But my code is not working. Im trying to do a custom function. and call for =columnSplit(A1:B2, 2, ",").
This is what i have:
the dates all on column A and Concatenate numbers at number B.
9/10/17 13:30:00 1234,4567,8910
9/11/17 12:34:00 0987,6543,21
what i want to get:
9/10/17 13:30:00 1234
9/10/17 13:30:00 4567
9/10/17 13:30:00 8910
9/11/17 12:34:00 0987
9/11/17 12:34:00 6543
I took my reference from here: How to split and transpose results over 2 columns
function columnSplit(reference, index, delimiter) {
var input = reference;
var output = [];
if (input.constructor !== Array) {
input = [[input]];
}
for (var i = 0; i < input.length; i++) {
var parts = input[i][index - 1].toString().split(delimiter);
for (var j = 0; j < parts.length; j++) {
var copy = input[i].slice(0);
copy[index - 1] = parts[j].trim();
output.push(copy);
}
}
return output
}

This works:
function R2C(a1,idx,dlm){
var vA=a1;
var oA=[];
for(var i=0;i<vA.length;i++){
var tA=vA[i][idx].toString().split(dlm);
var oidx=(idx==1)?0:1;
for(k=0;k<tA.length;k++){
if(idx==0){
oA.push([tA[k],vA[i][1]]);
}else{
oA.push([vA[i][0],tA[k]]);
}
}
}
return oA;
}
These are my spreadsheets which show how to use it:
For this function a1 is a selected range. idx is either 0 or 1. dlm is the delimiter.

Related

Google Appscript transpose dynamic data group from one column

I've been jogging my brain trying to figure out how to write this script to transpose data from one sheet to another from a pretty dirty sheet.
There are other questions like this but none seem to be like my particular use case.
This is how the sheet is currently structured (somewhat):
The biggest issue here is I have no concrete idea how many rows a particular group of data will be, But I know there are always a bunch of blank rows between each group of data.
I found a script that took me half way:
function myFunction() {
//Get values of all nonEmpty cells
var ss = SpreadsheetApp.getActiveSheet();
var values = ss.getRange("D:D").getValues().filter(String);
//Create object with 3 columns max
var pasteValues = [];
var row = ["","",""];
for (i = 1; i<values.length+1; i++){
row.splice((i%3)-1,1,values[i-1]);
if(i%3 == 0){
pasteValues.push(row);
var row = ["","",""]
}
}
if(row != []){
pasteValues.push(row)
}
//Paste the object in columns A to C
ss.getRange(1,1,pasteValues.length,pasteValues[0].length).setValues(pasteValues);
}
But in that case the asker dataset was fixed. I can loosely say that the max number of rows each group would have is 10(this is an assumption after browsing 3000 rows of the sheet...but if the script can know this automatically then it would be more dynamic). So with that in mind...and after butchering the script...I came up with this...which in no way works how it should currently(not all the data is being copied):
function myFunction() {
var copyfrom = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyfrom')
var copyto = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyto')
var values = copyfrom.getRange("A:A").getValues().filter(Array);
var pasteValues = [];
var row = [];
for (i = 1; i<values.length; i++){
if(values[i] != ""){
row.push(values[i])
}
Logger.log(row);
if(i%10 == 0){
pasteValues.push(row);
row = []
}
}
if(row != []){
pasteValues.push(row)
}
copyto.getRange(1,1,pasteValues.length,pasteValues[0].length).setValues(pasteValues);
}
I'm pretty sure I should maybe still be using array.splice() but haven't been successful trying to implement it achieve what i want, here's how the transposed sheet should look:
Info:
Each group of addresses inside the "copyfrom" sheet would be separated by at least 1 blank line
The length of an address group is not static, some can have 5 rows, others can have 8, but address groups are always separated by blank rows
Any help is appreciated
You are right to iterate all input values, and I can suggest the similar code:
function myFunction() {
var copyfrom = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyfrom')
var copyto = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyto')
var values = copyfrom.getRange("A:A").getValues();
var pasteValues = [[]]; // keep result data here
values.forEach(function(v) { // Iterate all input values
// The last row to be filled in currently
var row = pasteValues[pasteValues.length - 1];
if (v[0]) {
row.push(v[0]);
} else if (row.length > 0) {
while (row.length < 10) {
row.push(''); // Adjust row length
}
pasteValues.push([]);
}
});
if (pasteValues[pasteValues.length - 1].length == 0) pasteValues.pop();
copyto.getRange(1, 1, pasteValues.length, pasteValues[0].length).setValues(pasteValues);
}
Solution:
Assuming that every new row begins with Name, you can use this script to rearrange the column:
function myFunction() {
var copyfrom = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyFrom');
var copyto = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('copyTo');
var lastRow = copyfrom.getLastRow();
var values = copyfrom.getRange(1,1,lastRow).getValues().filter(Array);
var pasteValues = [];
var row = [];
var maxLen = 1;
// rearrange rows
for (i = 0; i < values.length; i++) {
if (values[i] == "Name" && i > 0) {
pasteValues.push(row);
row = [values[i]];
}
else if (values[i] != "") {
row.push(values[i]);
if (row.length > maxLen) {
maxLen = row.length;
}
}
}
pasteValues.push(row);
// append spaces to make the row lengths the same
for (j = 0; j < pasteValues.length; j++) {
while (pasteValues[j].length < maxLen) {
pasteValues[j].push('');
}
}
copyto.getRange(1,1,pasteValues.length,maxLen).setValues(pasteValues);
}
Sample I/O:
As far as I can tell, there is no way to get the columns to line up in the output since you don't have any way to tell the difference between, for example, an "address 2" and a "City".
However, as far as merely grouping and transposing each address. This one formula, in one cell, in the tab here called MK.Help will work from the data you provided. It will work for as many contacts as you have.
=ARRAYFORMULA(QUERY(QUERY({A:A,IFERROR(LOOKUP(ROW(A:A),FILTER(ROW(A:A),A:A="")),0),COUNTIFS(IFERROR(LOOKUP(ROW(A:A),FILTER(ROW(A:A),A:A="")),0),IFERROR(LOOKUP(ROW(A:A),FILTER(ROW(A:A),A:A="")),0),A:A,"<>",ROW(A:A),"<="&ROW(A:A))},"select MAX(Col1) where Col1<>'' group by Col2 pivot Col3",0),"offset 1",0))

Transferring data by date from email

I'm fairly new to coding in Google Script, and with Javascript. Basically what I'm trying to do is make a script to update data on a table in a spreadsheet. I have the script to import the email as a CSV, but I'm struggling with transferring the data from the email to the table by matching up the dates. Essentially what I would like the script to do is emulate a vlookup and paste the values from the emails CSV file to the table.
I made a copy of the file as an example of what I'm trying to do. I'm trying to transfer the yellow section of columns A and B of the Data tab to the matching yellow section columns A and B. And if there is no data for the dates then I would like the empty dates to be 0.
https://docs.google.com/spreadsheets/d/1uK3sCUFvcW6lgk962jgTN-yZox-lF8-Z0wm7Zhh-i8I/edit?usp=sharing
Thanks!
This two functions will accomplish your objectives. createArray(hight, width, filling) is just a workaround to create an array of the exact size of the Destination table. moveDates() is the one that compares the timestamps of the Data table with the ones on Destination; and will write down the values of the row if they match, and a zero if they don't.
This second function will first declare a bunch of variables that will save ranges and values for both sheets. After that, it will read all the dates of both tables. Later, it will run through the Destination table searching for coincidences and saving them on the newData array. Finally, the code will write down the newData. I've tested this code on your spreadsheet and it works perfectly.
function createArray(hight, width, filling) {
var array = [];
for (var i = 0; i < hight; i++) {
array[i] = [];
for (var j = 0; j < width; j++) {
array[i][j] = filling;
}
}
return array;
}
function moveDates() {
var dataSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName('Data');
var destinationSheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(
'Destination');
var dataRange = dataSheet.getRange(5, 1, 6, 3);
var destinationRange = destinationSheet.getRange(2, 1, 11, 3);
var newDataRange = destinationSheet.getRange(2, 2, 11, 2)
var data = dataRange.getValues();
var destination = destinationRange.getValues();
var dataDates = [];
var destinationDates = [];
var newData = createArray(11, 2, 0);
for (var i = 0; i < data.length; i++) {
dataDates.push(new Date(data[i][0]));
}
for (var i = 0; i < destination.length; i++) {
destinationDates.push(new Date(destination[i][0]));
}
for (var i = 0; i < destination.length; i++) {
for (var j = 0; j < data.length; j++) {
if (destinationDates[i].getTime() === dataDates[j].getTime()) {
newData[i][0] = data[j][1];
newData[i][1] = data[j][2];
}
}
}
newDataRange.setValues(newData);
}
If you need more information or clarifications I'll be happy to help you.

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.

Writing an array in Google Spreadsheets

I'm trying to learn javascript, so I decided to code a script in Google Apss Script to list all emails with attachment. Until now, I have this code:
function listaAnexos() {
// var doc = DocumentApp.create('Relatório do Gmail V2');
var plan = SpreadsheetApp.create('Relatorio Gmail');
var conversas = GmailApp.search('has:attachment', 0, 10)
var tamfinal = 0;
if (conversas.length > 0) {
var tam = 0
var emails = GmailApp.getMessagesForThreads(conversas);
var cont = 0;
for (var i = 0 ; i < emails.length; i++) {
for (var j = 0; j < emails[i].length; j++) {
var anexos = emails[i][j].getAttachments();
for (var k = 0; k < anexos.length; k++) {
var tam = tam + anexos[k].getSize();
}
}
var msginicial = conversas[i].getMessages()[0];
if (tam > 0) {
val = [i, msginicial.getSubject(), tam];
planRange = plan.getRange('A1:C1');
planRange.setValue(val);
// doc.getBody().appendParagraph('A conversa "' + msginicial.getSubject() + '" possui ' + tam + 'bytes em anexos.');
}
var tamfinal = tamfinal + tam;
var tam = 0;
}
}
}
listaAnexos();
It works, but with 2 problems:
1) It writes the three val values at A1, B1 and C1. But I want to write i in A1, msginicial.getSubject() in B1 and tam in C1.
2) How can I change the range interactively? Write the first email in A1:C1, the second in A2:C2 ...
I know that are 2 very basic questions, but didn't found on google :(
Problem 1: Make sure you use the right method for the range. You've used Range.setValue() which accepts a value as input, and modifies the content of the range using that one value. You should have used Range.setValues(), which expects an array and modifies a range of the same dimensions as the array. (The array must be a two-dimensional array, even if you're only touching one row.)
val = [[i, msginicial.getSubject(), tam]];
planRange = plan.getRange('A1:C1');
planRange.setValues(val);
Problem 2: (I assume you mean 'programmatically' or 'automatically', not 'interactively'.) You can either use row and column numbers in a loop say, with getRange(row, column, numRows, numColumns), or build the range string using javascript string methods.

string occurrences in a string

I'm am working on a script to count the number of times a certain string (in this case, coordinates) occur in a string. I currently have the following:
if (game_data.mode == "incomings") {
var table = document.getElementById("incomings_table");
var rows = table.getElementsByTagName("tr");
var headers = rows[0].getElementsByTagName("th");
var allcoord = new Array(rows.length);
for (i = 1; i < rows.length - 1; i++) {
cells = rows[i].getElementsByTagName("td");
var contents = (cells[1].textContent);
contents = contents.split(/\(/);
contents = contents[contents.length - 1].split(/\)/)[0];
allcoord[i - 1] = contents
}}
So now I have my variable allcoords. If I alert this, it looks like this (depending on the number of coordinates there are on the page):
584|521,590|519,594|513,594|513,590|517,594|513,592|517,590|517,594|513,590|519,,
My goal is that, for each coordinate, it saves how many times that coordinate occurs on the page. I can't seem to figure out how to do so though, so any help would be much appreciated.
you can use regular expression like this
"124682895579215".match(/2/g).length;
It will give you the count of expression
So you can pick say first co-ordinate 584 while iterating then you can use the regular expression to check the count
and just additional information
You can use indexOf to check if string present
I would not handle this as strings. Like, the table, is an array of arrays and those strings you're looking for, are in fact coordinates. Soooo... I made a fiddle, but let's look at the code first.
// Let's have a type for the coordinates
function Coords(x, y) {
this.x = parseInt(x);
this.y = parseInt(y);
return this;
}
// So that we can extend the type as we need
Coords.prototype.CountMatches = function(arr){
// Counts how many times the given Coordinates occur in the given array
var count = 0;
for(var i = 0; i < arr.length; i++){
if (this.x === arr[i].x && this.y === arr[i].y) count++;
}
return count;
};
// Also, since we decided to handle coordinates
// let's have a method to convert a string to Coords.
String.prototype.ToCoords = function () {
var matches = this.match(/[(]{1}(\d+)[|]{1}(\d+)[)]{1}/);
var nums = [];
for (var i = 1; i < matches.length; i++) {
nums.push(matches[i]);
}
return new Coords(nums[0], nums[1]);
};
// Now that we have our types set, let's have an array to store all the coords
var allCoords = [];
// And some fake data for the 'table'
var rows = [
{ td: '04.shovel (633|455) C46' },
{ td: 'Fruits kata misdragingen (590|519)' },
{ td: 'monster magnet (665|506) C56' },
{ td: 'slayer (660|496) C46' },
{ td: 'Fruits kata misdragingen (590|517)' }
];
// Just like you did, we loop through the 'table'
for (var i = 0; i < rows.length; i++) {
var td = rows[i].td; //<-this would be your td text content
// Once we get the string from first td, we use String.prototype.ToCoords
// to convert it to type Coords
allCoords.push(td.ToCoords());
}
// Now we have all the data set up, so let's have one test coordinate
var testCoords = new Coords(660, 496);
// And we use the Coords.prototype.CountMatches on the allCoords array to get the count
var count = testCoords.CountMatches(allCoords);
// count = 1, since slayer is in there
Use the .indexOf() method and count every time it does not return -1, and on each increment pass the previous index value +1 as the new start parameter.
You can use the split method.
string.split('517,594').length-1 would return 2
(where string is '584|521,590|519,594|513,594|513,590|517,594|513,592|517,590|517,594|513,590|519')

Categories