Why isn't array Intersection in google spreadsheet working as expected? - google-apps-script

This is my first time using google spreadsheets, and I'm trying to accomplish something in the functions. I know it's javascript (A language I'm not entirely familiar with). I'm trying to take a range from two tables, and get the intersection location of each, and fill in missing values for rows that already should exist.
For example, assuming the following two sheets:
Sheet1
+-----------+----------+--------+---------+
| Fruit | Color | Weight | isApple |
+-----------+----------+--------+---------+
| Banana | Yellow | 3 | no |
| Orange | Orange | 3 | no |
| Apple | Red | 2 | yes |
| Pineapple | Brownish | 5 | no |
+-----------+----------+--------+---------+
Sheet2
+-----------+----------+--------+---------+
| Fruit | Color | Weight | isApple |
+-----------+----------+--------+---------+
| Banana | | | |
| Apple | | | |
| Pear | | | |
| Watermelon| | | |
+-----------+----------+--------+---------+
I want to find the intersections of the Fruit row, and fill in the color, weight, and isApple, of each one we know.
I wrote something I felt confident should work to get the intersection of range1, and it returns an empty array for some reason. When I test this in Apps Script Editor, it seems to work fine.
//assuming range1 = Sheet1!A2:A5 and range2=Sheet2!A2:A5
function intersection(range1, range2) {
var i = 0;
var j = 0;
var matches = new Array();
while(i < range1.length){
if(range2.toString().includes(range1[i].toString())){
matches.push(i);
}
i++
}
return matches;
}
I would expect this to return an array of [0,2] since the 0th element Banana, and the 1st element Apple from Sheet1, exist in sheet 2.
I would then use that data to fill in the rows for Apple, and Banana from the information in Sheet2.
I'm not at that second part yet, since I can't seem to get the sheet to even find the intersection.
My end goal expected output would be that Sheet 2 is changed to:
Sheet2
+-----------+----------+--------+---------+
| Fruit | Color | Weight | isApple |
+-----------+----------+--------+---------+
| Banana | Yellow | 3 | no |
| Apple | Red | 2 | yes |
| Pear | | | |
| Watermelon| | | |
+-----------+----------+--------+---------+

I believe your goal as follows.
You want to achieve the result from "Sheet1" and "Sheet2" as shown in your question.
You want to achieve this using the custom function.
For this, how about this answer?
Modification points:
In your script, I thought that you are using the custom function of =intersection(Sheet1!A2:A5,Sheet2!A2:A5). In this case, the arguments of range1 and range2 of function intersection(range1, range2) { are [["Banana"],["Orange"],["Apple"],["Pineapple"]] and [["Banana"],["Apple"],["Pear"],["Watermelon"]], which are 2 dimensional arrays, respectively. Namely, the values on the sheet are sent to the arguments. In order to achieve your goal, I would like to propose to use the custom function like =intersection(Sheet1!A2:D5,Sheet2!A2:A5). By this, the values from "Sheet1" can be used in the custom function.
In your script, the value of [0,2] is retrieved as matches. This is the indexes of range1. But in this case, it is required to also know the indexes matching to range2. This has already mentioned in your question. In this case, how about the following flow?
In order to achieve your goal, I would like to propose the following flow.
From the values of "Sheet1", create an object for searching values of the column "A".
From the values of "Sheet2", create an result array using the created object.
The sample script reflected above flow is as follows.
Sample script:
Please copy and paste the following script, and out the custom function =intersection(Sheet1!A2:D5,Sheet2!A2:A5) to the cell "B2" of "Sheet2". By this, the result that you showed at the bottom of your question is obtained.
function intersection(values1, values2) {
const obj = values1.reduce((o, [a, ...bcd]) => Object.assign(o, {[a]: bcd}), {});
return values2.map(([a]) => obj[a] ? obj[a] : [""]);
}
Result:
References:
Custom Functions in Google Sheets
reduce()
Object.assign()
Spread syntax (...)
map()

Related

How to use a custom function with an ArrayFormula

I want to write a function that can be used inside an ArrayFormula. My table is like this:
| A | B | C |
1| a | | |
2| b | | |
3| c | | |
First I wrote a simple function to return the input (so I know it works inside the ArrayFormula):
function retAddress(cell){
return cell;
}
On B1 I wrote =ArrayFormula(retAddress(address(row(B:B),column(A:A),4))) and apparently it worked as expected, it returned each address, like this:
| A | B | C |
1| a | A1| |
2| b | A2| |
3| c | A3| |
Now, on column C, I wanted to return the values of column A, so I wrote a function like this:
function retValue(cell){
var cellRang = SpreadsheetApp.getActive().getRange(cell);
return cellRang.getValue();
}
And on C1 I wrote =ArrayFormula(retValue(address(row(B:B),column(A:A),4))) but it gives me error Exception: Range not found (line 2)., which is the line with getRange(cell) method.
If I write the function without ArrayFormula like this:
On C1, =retValue(address(row(C1),column(A:A),4))
On C2, =retValue(address(row(C2),column(A:A),4))
On C3, =retValue(address(row(C3),column(A:A),4))
I get the expected result:
| A | B | C |
1| a | A1| a |
2| b | A2| b |
3| c | A3| c |
So, how to make it work in ArrayFormula?
Issue:
SpreadsheetApp.getActive().getRange(cell)
cell is array if you provide a array input. getRange method expects a single string as input.
Solution:
map the array to single value
References:
Custom Function#Optimization
Best practices
Snippet#1:
function retValue(cell){
if(cell.map) {
return cell.map(retValue);
} else {
var cellRang = SpreadsheetApp.getActive().getRange(cell);
return cellRang.getValue();
}
}
Snippet#2:
Note that in the previous snippet you're calling getValue() 1 time per each cell in the input array. This is extremely slow. Better way is to call it as a batch:
=retValues("A1:B4")
function retValues(cell){//modified
var cellRang = SpreadsheetApp.getActive().getRange(cell);
return cellRang.getValues();//modified
}
Note that:
Only 1 call to getValues() is made.
Formula returns a array without explicit use of =ARRAYFORMULA(). All custom formulas are by default, array formulas, but they need to be configured to return values as arrays in apps script.

Google script Multiple criteria in same row and validation of value in previous row

I'm starting with a script that someone here graciously helped with and need to build onto it and do not know where to start. Here is the current script:
function yourFunction(){
var ss=SpreadsheetApp.getActive();
var sh=ss.getSheetByName('Sheet1');
var rg=sh.getDataRange();//columns are fruit,status and then cost.
var vA=rg.getValues();
for(var i=1;i<vA.length;i++){
if(vA[i][0].toString()=='Apple' && vA[i][1].toString()=='Ripe' && vA[i][2].toString=='Large' && vA[i][4].toString=''){
vA[i][4]=5.5;
}
}
rg.setValues(vA);//This writes all of the data at one time.
}
What I would like to add to this is a second set of criteria that looks at another column value = Lot Number(Column D). Assuming that the current Lot Number is the same as the previous row's and where all the above match, each additional rows will be a set value 3. But if the value of the Lot Number before the current row is not the same, then the value is 5. In what I've read, there may need to be some looping condition in this so the calculations don't keep going on and on. Any help here would be much appreciated. Thanks!
Here is a link to a basic format of the spreadsheet Test Script
Here's an updated function that demonstrates the general approach for what I believe you're describing.
var COLUMNS = {
FRUIT: 0,
STATUS: 1,
SIZE: 2,
LOT_NUMBERr: 3,
COST: 4,
NEW_VALUE: 5,
}
function updateValues() {
var sheet = SpreadsheetApp.getActive().getSheetByName('Sheet3');
var range = sheet.getDataRange();
var values = range.getValues();
var previousLotNumber = -1;
for(var i = 1; i < values.length; i++){
if (values[i][COLUMNS.FRUIT] == 'Apple'
&& values[i][COLUMNS.STATUS] == 'Ripe') {
values[i][COLUMNS.COST] = 5.5;
}
if (previousLotNumber == values[i][COLUMNS.LOT_NUMBER]) {
values[i][COLUMNS.NEW_VALUE] = 3;
} else {
values[i][COLUMNS.NEW_VALUE] = 5;
}
previousLotNumber = values[i][COLUMNS.LOT_NUMBER];
}
range.setValues(values);
}
After running this function, this sheet looks like the following:
+--------+-----------+--------+------------+------+-----------+
| Fruit | Status | Size | Lot Number | Cost | New Value |
+--------+-----------+--------+------------+------+-----------+
| Apple | Ripe | Large | 101 | 5.5 | 5 |
| Apple | Ripe | Medium | 101 | 5.5 | 3 |
| Apple | Ripe | Large | 103 | 5.5 | 5 |
| Apple | Not Ready | Large | 102 | | 5 |
| Apple | Not Ready | Medium | 101 | | 5 |
| Banana | Ripe | Large | 201 | | 5 |
| Orange | Ripe | Large | 301 | | 5 |
| Orange | Not Ready | Medium | 301 | | 3 |
| Pear | Ripe | Large | 401 | | 5 |
+--------+-----------+--------+------------+------+-----------+
A few notes:
use descriptive variable names
use a set of constants to provide descriptive names for the columns
for your ask, all you need to do is use a variable to store the previous rows lot number, no additional looping complexity required

Google Sheets: Change the value of a cell if some conditions are met

In google sheets, I would like to change the contents of a cell if certain conditions are met.
Given the following table:
+---+-----+-------+----+-----+
| | A | B | C | D |
+---+-----+-------+----+-----+
| 1 | Bob | Bobby | AU | yes |
| 2 | Sam | Sammy | AU | yes |
| 3 | Tim | Timmy | AU | yes |
| 4 | Jim | Jimmy | AU | yes |
| 5 | Sam | Sammy | KR | no |
| 6 | Jim | Jimmy | AU | no |
+---+-----+-------+----+-----+
First, I want to check if the value in D is "no".
If it is "no", I want to take the values on the same row in A, B and C and find them in another row.
If I find them, I want to go to that row and change the value of D to a "no".
If I don't find them, I want to do nothing.
Example:
Jim Jimmy AU is "no" in row 6.
A,B,C in row 6 = A,B,C in row 4.
Therefore, I want to change "yes" in row 4 to "no".
Sam Sammy AU is "yes" in row 2.
Sam Sammy KR is "no" in row 5.
A,B,C in row 2 /= A,B,C in row 5 (AU vs KR).
Therefore, I don't change "yes" in row 2.
I have googled extensively and tried combinations of LOOKUP, ADDRESS, FIND, SEARCH, COUNTIF etc but just can't figure this one out.
Is it even possible? I really need it to be possible... ha.
I'm also open to just blanking the "yes" rows all together or deleting them or something. I just need to filter out "no"s from the list, ultimately.
Thank you!
A script for this requirement makes sense because ColumnD cannot contain a value (eg yes) and a formula in the same cell. Also if required for more than 'once off'. However with helper columns and an inserted header row simple formulae (E1 and F1 copied down to suit) should work:
=A1&"|"&B1&"|"&C1 creates a key from ColumnsA:C
=VLOOKUP(E1,G:G,1,0) checks for the presence of each key in a lookup table
=filter(E:E,D:D="no") creates the lookup table
in conjunction with filtering on ColumnD to select yes and ColumnF to exclude #N/A and manually changing the first yes in ColumnD to no and copying down.

How to automatically set column values from copied csv line on Google Sheets?

I had this lines generated from a bash script,
2016,Feb,1
2016,Feb,2
2016,Feb,3
I want to copy it, maybe per line, and paste to google sheet.
And when I past it, it should automatically set the values based on the comma delimiter.
Something like this in Google sheet after pasting:
| year | month | day |
| 2016 | Feb | 1 |
Use SPLIT.
If cell A1 has 2016,Feb,1 as value and you add to cell B2 the following formula
=SPLIT(A1,",")
you will see as result
| A | B | D | F |
---+------------+-------+-------+-------+
1 | 2016,Feb,1| 2016|Feb | 1|
---+------------+-------+-------+-------+
Then
Fill down until you get copied the above formula upto the last row.
Select columns B to F.
Copy and paste over the same range to get rid of the formulas.
Delete the column A.
Add the column headers.

Google Spreadsheet Check From What Cell VLOOKUP() Returns

Is there a way to return the cell from which VLOOKUP() gets its value?
For instance:
________A_____|__B__|
1 | Mouse | 1 |
2 | Keyboard | 2 |
3 | Headset | 3 |
4 | HDD | 4 |
=VLOOKUP("Mouse",A1:B4,2,FALSE) --would return "1".
Is there a way to make it return "B1" instead?
=ADDRESS(MATCH("Mouse",A1:A4,0),2, 1)
How this works...
The address part captures the cell address and is as follows
=Address(row,column,abs)
The match function returns Row number, 2 is the second coloumn - you will have to change as per your requirement. The abs (absolute) can be 1,2,3,4 or omitted....try changing it to understand....