Result of JOIN is longer than the limit of 50,000 characters - google-apps-script

I am trying to combine text from column A and match it with each possibility of column B.I used the formulas:
in C1:
=transpose(split(join("", arrayformula(rept(filter(A1:A, len(A1:A))&char(9999), counta(B1:B)))), char(9999)))
in D1:
=transpose(split(rept(join(char(9999), filter(B1:B, len(B1:B)))&char(9999), counta(A1:A)), char(9999)))
but when I use it in my list I get these errors in C1 and D1 respectively;
Text result of JOIN is longer than the limit of 50000 characters
Text result of REPT is longer than the limit of 32000 characters
I tested this out with a smaller list of just:
a b c 1 2
and managed to get my list to generate this after combining the two cells:
a 1
a 2
a 3
b 1
b 2
b 3
but the list I am combining has a lot more text in each of the columns.
Any suggestions on how to combine my lists as shown above but with 132 possibilities in column A and 52 possibilities in column B?
Each line has between 70 and 150 characters of text in each row.

Go to menu Tools → Script Editor...
Paste this code:
function crossJoin(arr1, arr2, delim) {
delim = delim || '';
var result = [];
var row = [];
for (var i = 0; i < arr1.length; i++) {
for (var j = 0; j < arr2.length; j++) {
row = [];
row.push('' + arr1[0,i] + delim + arr2[0,j]);
result.push(row);
}
}
return result;
}
Save project.
Use it as regular function in spreadsheet:
=crossJoin(A1:A132,B1:B52)
Optionaly use delimeter:
=crossJoin(A1:A132,B1:B52, "-")

Related

Finding matches from 2 ranges and replacing matches with a specific value, any way to optimize?

This is the sample sheet.
From this
1 Search area Bounty list Bullet
2 a i z a b c abc
3 e b d d e f def
4 y f h g h i ghi
5
6 1 2 3 4 5 6 7 8
7 Column #
To this
1 Search area Bounty list Bullet
2 abc ghi z a b c abc
3 def abc def d e f def
4 y def ghi g h i ghi
5
6 1 2 3 4 5 6 7 8
7 Column #
It will take a value "bounty" from the "bounty list" starting from (2,5) or "a", search around the "Search Area" in a sequence, from a, i, z, e, b, d, y, f, h. Then if it finds a cell or multiple cells that equals the value of "bounty", then it will place the value of "bullet" from column 8 on the current "bounty" row to those cells. The process will repeat in the sequence of a, b, c, d, e, f, g, h, i on the "bounty list". Both process moves to the left, and down.
function menuItem1()
{
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var target = sheet.getDataRange().getValues();
for (var BountyRow = 2; BountyRow<target.length; BountyRow++)//switching rows in bounty list//
{
var bullet = sheet.getRange(BountyRow, 8).getValue(); //cell value to paste on targets//
for (var BountyColumn = 5; BountyColumn<8; BountyColumn++) //switching columns in bounty list//
{
var bounty = sheet.getRange(BountyRow, BountyColumn).getValue(); // cell value to search for//
if (bounty !=0)
{
for (var SearchRow = 1; SearchRow<target.length; SearchRow++) //switching row on search area//
{
for(var SearchColumn = 0; SearchColumn<4;SearchColumn++)//switching column on search area//
{
if(target[SearchRow][SearchColumn] == bounty) //if search target is found//
{
var found = target[SearchRow][SearchColumn];
sheet.getRange(SearchRow+1, SearchColumn+1).setValue(bullet);
Logger.log((found)+ " in "+"row"+(SearchRow+1)+", column"+(SearchColumn+1));
}
}
}
}
}
}
}
It involves thousands of searches which always use more than a minute and I was wondering if there is a more efficient way to do it?
In order to optimize your code you need to do two things:
Instead of using getValue() and setValue() for each cell (which makes you code slow)
retrieve all your bounty list and search are data once, with getValues()
assign the values to an array
replace matches within the the array
set the updated array values back into the range with setValues()
Make use of indexOf() and map()
to find matches and replace them more efficiently
Sample:
function menuItem1(){
var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet();
var lastRow=sheet.getLastRow();
var searchValues=sheet.getRange(2,1,lastRow-2+1,3).getValues();
var bountyValues=sheet.getRange(2,5,lastRow-2+1,3).getValues();
var bulletValues=sheet.getRange(2,8,lastRow-2+1,1).getValues();
for (var i = 0; i<bountyValues.length; i++){
for (var j = 0; j<bountyValues[0].length; j++){
if (bountyValues[i][j] !=0){
replaceValues(searchValues, bountyValues[i][j], bulletValues[i][0]);
}
}
}
sheet.getRange(2,1,lastRow-2+1,3).setValues(searchValues)
}
function replaceValues(search, bounty, bullet) {
for(var k=0;k<search.length;k++){
search[k]=search[k].map(function(search) {
var regex=new RegExp("\\b"+bounty+"\\b","g");
return search.toString().replace(regex, bullet);
});
}
}

Using vlookup with repeated interval + split

I have a list with purchase codes and product serial number and would like to know the purchase price of each product.
In a second list, I have a column with the purchase code, the serial number of a comma-separated product group in another column, and, in the third column, the price of each product group.
I am using the formula below, but when the codes in column "E" are repeated, the formula does not work:
=IF(HLOOKUP(B2;SPLIT(VLOOKUP(A2;$E$1:$F$1000;2;FALSE);", ";TRUE);1;FALSE)<>"#N/A";VLOOKUP(A2;$E$1:$G$5;3;FALSE);"")
Example
Test Spreadsheet
You could use a query
=query(E:G,"select G where E='"&A2&"' and F contains '"&B2&"' limit 1 label G ''")
provided none of the IDs in column B can be a substring of another ID.
I took the liberty of writing the Apps Script code for what you need, this is it:
function myFunction() {
var ss = SpreadsheetApp.getActiveSheet();
var data = ss.getRange("B2:B" + ss.getLastRow()).getValues(); // IDs
var list = ss.getRange("F2:G" + ss.getLastRow()).getValues(); // ID List
var prices = [];
for (var i = 0; i < data.length; i++) {
for (var j = 0; j < list.length; j++){
if (list[j][0].indexOf(data[i][0]) >- 1){
prices.push([list[j][1]]);
break;
}
}
}
ss.getRange("C2:C" + ss.getLastRow()).setValues(prices);
}
it does the check based on the ID List values and the ID itself, it's not doing anything with the cod value.

How to sum up the values in each column and ranking each column name by sum of values in google sheet

I have a google sheet with values that is getting populated
A B C D E F G H
Top scorers Date Player l Player 2 Player 3 Player 4
13 Jan 2019 1 1 1
20 Jan 2019 2 1 1
the idea is: each match day I will enter the date of the match and number of goals that each player scored, if new player score I will just put his name in new column and number of goal on that date. If any player not score that day, I will just leave that cell blank.
Then I want to populate first column "Top scorers" with ranking of Player scored. Expected result will look like this:
A B C D E F G H
Top scorers Date Player l Player 2 Player 3 Player 4
Player 1: 3 13 Jan 2019 1 1 1
Player 2: 2 20 Jan 2019 2 1 1
Player 3: 1
Player 4: 1
It will automatically updated with new data input. How could I make this? I have a look at Pivot Table but looks like it is hard to archive this result.
Sample sheet.
According to the description of what you're trying to accomplish and the Google Sheet shared.
You need to break your problem down into several subtasks:
Selecting all the ranges you'll later need (Players, Dates, Range where you'll write your scores, range where your top scorers will be displayed)
Adding up each player's goals from all the games to get their total score
Sorting the players according to their total score
Create strings to write into your topscorer column
Write these strings into the topscorer column
My solution is pretty verbose but it seems to work. Please don't hesitate to ask if you have any questions or if clarifications are needed.
function testMe() {
var ID = ''; // ID of your Document
var name = ''; // Name of your sheet
var sourceSheet = SpreadsheetApp.openById(ID); // Selects your Source Spreadsheet by its id
var ssheet = sourceSheet.getSheetByName(name); // Selects your Source Sheet by its name
var scoreRange = ssheet.getRange(2, 3, (sourceSheet.getLastRow() -1), (sourceSheet.getLastColumn() -2)); // Selects the range in which you will enter your scores
var dateRange = ssheet.getRange(2,2,(sourceSheet.getLastRow() -1)); // Selects the range for which player names in row 1
var playerRange = ssheet.getRange(1,3,1,(sourceSheet.getLastColumn() -2)); // selects the range for which dates were entered in column
var topScorerRange = ssheet.getRange(2,1,scoreRange.getNumColumns()); // selects the range where your topscorer output will end up
var numberOfPlayers = playerRange.getNumColumns(); // Gets the number of players you've already entered in row 1
var numberOfGames = playerRange.getNumRows(); // Gets the number of games whose dates you've already entered in Column B
function sortAndUpdateTopScorers() {
var array = scoreRange.getValues();
var totalPlayers = scoreRange.getNumColumns();
var totalGames = scoreRange.getNumRows();
var playerScores = [];
// iterate through the scoreRange and count up each players total score
for (var i = 0; i < totalPlayers; i++) {
var currentPlayer = 0;
for (var j = 0; j < totalGames; j++) {
currentPlayer += array[j][i];
}
playerScores.push([currentPlayer]);
}
// Combine the names of the players and their total score in order to create the strings for your topscorers column
for (var v = 0; v < numberOfPlayers; v++) {
playerScores[v].push(playerRange.getValues()[0][v] + ": " + playerScores[v]);
};
// Sort those strings according to their score
playerScores.sort(function(a,b) {
return b[0]-a[0]
});
// Remove the score value so only the string remains in the array
for (var x = 0; x < playerScores.length; x++) {
playerScores[x].shift();
}
// Write the content of the array into your topscorers column
topScorerRange.setValues(playerScores);
};
sortAndUpdateTopScorers();
};

Custom function output in rows instead of columns

The following script returns the values from the for loop in rows. Is there anyway to return the results into the adjacent columns instead?
function readMessage(msgId){
var url = "https://api.pipedrive.com/v1/mailbox/mailThreads/";
var token = "/mailMessages?api_token=token";
Logger.log("readMessage called with msgId: " + msgId);
if (msgId){ // make sure there is a msgId in the cell
var rows = []
var response = UrlFetchApp.fetch(url+msgId+token);
var dataAll = JSON.parse(response.getContentText());
var dataSet = dataAll;
var rows = [], data;
for (var i = 0; i < dataSet.data.length; i++) {
data = dataSet.data[i];
rows.push([data.body_url]);
}
Logger.log( JSON.stringify(rows,null,2) );
return rows;
}
Current output is-
A B C D
1 =readMessage(msgId)
2 "blah blah"
3 "blah blah"
What I want is-
A B C D
1 =readMessage(msgId) "blah blah" "blah blah"
2
3
Google Apps Script data is always in the form of a 2D array. It's helpful to have visualization.
A B C
1 [[A1, B1, C1],
2 [A2, B2, C2]]
in order to return a row of data the function should return
[[A,B,C]] // 3 columns of data in one row
in order to return a column of data you can use either the full 2D array or a single array
[[1],[2],[3]]
OR
[1,2,3]
The issue is the formatting of the data you are returning. Try this rows.push(data.body_url); instead:
for (var i = 0; i < dataSet.data.length; i++) {
data = dataSet.data[i];
rows.push(data.body_url); //Brackets [] have been removed to insert data as a row.
}
If you return data like this: [[1],[3],[6]] the result will be a column of values:
1
3
6
If you data has this format: [2,4,8] you get a row of values:
2 | 4 | 6
Source: Custom Function

Find cell in another tab

This is a multi-part question: I'm at a loss to explain it succinctly in one clear statement. I'll try to clean it up after getting some feedback:
In Google Docs, I would like to search a different tab (in the same spreadsheet) to find a cell which contains specific data. The data may be moved around, making it impossible to use a static cell reference.
//if 'Sheet2' of my spreadsheet contains the following...
A B C D
1 - - - -
2 - foo bar 6
3 - - - -
//...then some magical function would return C2
=getCell( 'Sheet2', "bar" )
Now that we've got this cell, I want to get the values of the adjacent cells on the same row.
//this would return "foo":
=getLeft( getCell( 'Sheet2', "bar" ) )
//and this would return 6
=getRight( getCell( 'Sheet2', "bar" ) )
I was able to get a function working that satisfies the above task. However, it is PAINFULLY SLOW! I'm using the function in about 100+ places, so this makes the sheet timeout on calculation every time I change something.
Can anyone suggest how to get the same functionality, but with much better performance?
function getCell( sheetname, item, row_offset, default_string )
{
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(sheetname);
var lastrow = sheet.getLastRow();
var lastcol = sheet.getLastColumn();
for( var c = 1; c <= lastcol; c++ )
{
for( var r = 1; r <= lastrow; r++ )
{
var range = sheet.getRange(r, c);
if( range.getValue() == item )
{
//item found! See if we can get the requested cell...
c = c + row_offset; //adjust column using given offset
if( c < 1 || c > lastcol )
return "E.offset";
else
return sheet.getRange(r,c).getValue();
}
}
}
return default_string;
}
Could you add a row and a column before your data range as follows:
A B C D E F G
1 "bar"
2 - - - -
3 - foo bar 6
4 - - - -
You have a cell where you have the cell content you are looking for, let's say in G1. In cell A2 you place the formula:
=COUNTIF(B2:E2,$G$1)
Which will check row A for the a match on the cell G1. Fill down the other cells in row A and cell A3 will show "1".
Use a similar formula for cell B1:
=COUNTIF(B2:B4,$G$1)
And fill across the row. Cell D1 will show a 1.
Then, use INDEX() with MATCH() to find the contents of the cells on either side:
Left:
=INDEX($B$2:$E$4,MATCH(1,$A$2:$A$4,0),MATCH(1,$B$1:$E$1,0)-1)
Right:
=INDEX($B$2:$E$4,MATCH(1,$A$2:$A$4,0),MATCH(1,$B$1:$E$1,0)+1)
No scripting required!