Google script - In the following code, instead of 'includes', how to do 'not includes' - if-statement

Sample sheet
This is the code I have now, and what I want to do is also bring in all data from C where text does not include '250p' in col A.
const sS = SpreadsheetApp.getActiveSpreadsheet()
function grabData() {
const sheetIn = sS.getSheetByName('data')
const sheetOut = sS.getSheetByName('Desired Outcome')
const range = 'A2:B'
/* Grab all the data from columns A and B and filter it */
const values = sheetIn.getRange(range).getValues().filter(n => n[0])
/* Retrieve only the names if it containes 250p */
/* In format [[a], [b], ...] */
const parsedValues = values.map((arr) => {
const [type, name] = arr
if (type.toLowerCase().includes('250p')) {
return name.split('\n')
}
})
.filter(n => n)
.flat()
.map(n => [n])
/* Add the values to the Desired Outcome Sheet */
sheetOut
.getRange(sheetOut.getLastRow() + 1, 1, parsedValues.length)
.setValues(parsedValues)
}
I tried doing:
if (!type.toLowerCase().includes('250p')) {
if (type.whenTextDoesNotContain('250p')) {
But on both occasions I get that, that is not a function.

I believe your goal is as follows.
Your Spreadsheet has the values in the columns "A" and "C".
You want to check the column "A". When the value of column "A" doesn't include the text of 250p, you want to copy the values from the column "C" to the column "A" of the destination sheet. In this case, you want to split the values by \n.
Modification points:
In your script, in order to retrieve the values from the columns "A" and "C", I thought that const range = 'A2:B' should be const range = 'A2:C' + sheetIn.getLastRow();, and also const [type, name] = arr is const [type, , name] = arr.
In order to retrieve the rows that 250p is not included in the column "A", I modified your if statement to if (!type.toLowerCase().includes('250p')) {.
When these points are reflected to your script, it becomes as follows.
Modified script:
const sS = SpreadsheetApp.getActiveSpreadsheet();
function grabData() {
const sheetIn = sS.getSheetByName('data');
const sheetOut = sS.getSheetByName('Desired Outcome');
const range = 'A2:C' + sheetIn.getLastRow();
const values = sheetIn.getRange(range).getValues();
const parsedValues = values.map((arr) => {
const [type, , name] = arr;
if (!type.toLowerCase().includes('250p')) {
return name.split('\n');
}
})
.filter(n => n)
.flat()
.map(n => [n]);
sheetOut
.getRange(sheetOut.getLastRow() + 1, 1, parsedValues.length)
.setValues(parsedValues);
}
If you want to retrieve the rows that 250p is included in the column "A", please modify if (!type.toLowerCase().includes('250p')) { to if (type.toLowerCase().includes('250p')) {.
Note:
In this modified script, your provided Spreadsheet is used. So, when you change the Spreadsheet, this modified script might not be able to be used. Please be careful about this.

Related

ArrayFormula For Join A Range Column

I want to use ArrayFormula for JoinText for multiple columns, From Column A to Column H. I already have Google App Script for it, and it works.
var sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("DISPOSISI");
const rowNo = sheet.getLastRow();
const colStaff1 = 1;
const colStaff8 = 8;
const colJoinStaff = 9;
// =============
// TEXT JOIN :
// =============
const cellStaff1 = sheet.getRange(rowNo,colStaff1).getA1Notation();
const cellStaff8 = sheet.getRange(rowNo,colStaff8).getA1Notation();
sheet.getRange(rowNo,colJoinStaff).setValue(sheet.getRange(rowNo,colJoinStaff).setFormula('TEXTJOIN(", ";TRUE;'+cellStaff1+':'+cellStaff8+')').getValue());`
Every I added one new Row, I want the result will appear with ArrayFormula.
This is my formula :
=arrayformula(if(row(A:A)=1;"JOIN VALUE WITH COMMA";ARRAYFORMULA(IF((A:A)="";"";ARRAYFORMULA(TEXTJOIN(", ";TRUE;(A:A):(H:H)))))))
But it does not work.
My Spreadsheet
try:
={"JOIN VALUE WITH COMMA";
INDEX(REGEXREPLACE(TRIM(FLATTEN(QUERY(TRANSPOSE(
IF(A2:H="";;A2:H&","));;9^9))); ",$"; ))}
or maybe:
={"JOIN VALUE WITH COMMA";
INDEX(REGEXREPLACE(TRIM(FLATTEN(QUERY(TRANSPOSE(
IF(A2:H="";;A1:H1&": "&A2:H&","));;9^9))); ",$"; ))}

How to emulate the formula Proper on AppScript for a specific column?

I found the following code to emulate the proper formula, but it has a wrong ( maybe outdated) syntax, and as far as i understood, it should applies to all columns of a given sheet.
function PROPER_CASE(str) {
if (typeof str != "string")
throw `Expected string but got a ${typeof str} value.`;
str = str.toLowerCase();
var arr = str.split(/.-:?—/ );
return arr.reduce(function(val, current) {
return val += (current.charAt(0).toUpperCase() + current.slice(1));
}, "");
}
Here's an example of the input :
A
B
C
D
ColumnA
ColumnB
ColumnC
ColumnD
EXCEL ACTION LIMIMTED (毅添有限公司)
207/2018
n/a
without-proper
Hang Wo Holdings
205/2015
35/2020
without-proper
central southwood limited
308/2019
n/a
without-proper
This would be the desired output:
ColumnA ColumnB ColumnC COlumnD
Excel Action Limited (毅添有限公司) 207/2018 n/a without-proper
Hang Wo Holdings 205/2015 35/2020 without-proper
Central Southwood Limited 308/2019 n/a without-proper
And this is the error output of that function :
Erro
Expected string but got a undefined value.
PROPER_CASE # macros.gs:115
This is the only way I can see of reproducing you results. I don't see how to avoid captalizing the first letter of the last two columns with avoiding them:
function lfunko() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Sheet0");
if (sh.getLastRow() > 4) {
sh.getRange(6, 1, sh.getLastRow() - 5, sh.getLastColumn()).clearContent();
SpreadsheetApp.flush();
}
const vs = sh.getDataRange().getDisplayValues().map((r, i) => {
return r.map((c, j) => {
if (i > 0 && j < 1) {
let arr = c.toString().toLowerCase().split(/.-:?-/g);
return arr.reduce((val, current) => {
//Logger.log(current)
return val += current.charAt(0).toUpperCase() + current.slice(1);
}, '');
} else {
return c;
}
});
});
Logger.log(JSON.stringify(vs))
sh.getRange(sh.getLastRow() + 2, 1, vs.length, vs[0].length).setValues(vs);
}
A
B
C
D
Data
ColumnA
ColumnB
ColumnC
ColumnD
EXCEL ACTION LIMIMTED (毅添有限公司)
207/2018
n/a
without-proper
Hang Wo Holdings
205/2015
35/2020
without-proper
central southwood limited
308/2019
n/a
without-proper
Outpput
ColumnA
ColumnB
ColumnC
ColumnD
Excel action limimted (毅添有限公司)
207/2018
n/a
without-proper
Hang wo holdings
205/2015
35/2020
without-proper
Central southwood limited
308/2019
n/a
without-proper
I have tested your code and it works fine. It does convert the input string into a proper case.
However, take note that in Google Sheets, when you get values, your data is in 2D Array or Nested Array.
So to apply this to your Spreadsheet after getting the values you will have to target the column you want to replace and loop through each string in the array. You will then have to setValues() back to the specified range to replace it in the spreadsheet.
Solution 1:
Try:
With your function, try adding this script to apply to your spreadsheet.
function setToColumn(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var dataRange = sheet.getRange(1,1,sheet.getLastRow()); //2ND Parameter is the column, replace if you want to edit different column
var allData = dataRange.getValues().flat();
var properData = []
allData.forEach(function(data){
properData.push([PROPER_CASE(data)])
});
dataRange.setValues(properData);
}
From:
Result:
Solution 2:
If you don't mind using different script which only needs one function you may use the script below:
function properCase() {
var ss = SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getActiveSheet();
var dataRange = sheet.getRange(1,1,sheet.getLastRow()); //2ND Parameter is the column, replace if you want to edit different column (1 = Column A, 2 = Column B)
var allData = dataRange.getValues().flat();
var properData = []
allData.forEach(function(data){
properData.push([data.toLowerCase().replace(/\b[a-z]/ig, function(match) {return match.toUpperCase()})]);
});
dataRange.setValues(properData);
}
Reference for Solution 2:
Apps script how to format a cell to Proper Text (Case)

Google script insert value in a column if another column has a specific value

as basic as this may sound I am having difficulty writing this. I have two columns with checkboxes in a sheet(main) and I want to be able to checkbox(true) column 'O' if column 'm' has a checkmark after I am done with the sheet(macro button).
Thanks for any input.
If M is true set O to true
function lfunko() {
const ss = SpreadsheetApp.getActive();
const sh = ss.getSheetByName("Sheet0");
const [hA,...vs] = sh.getDataRange().getValues();
vs.forEach((r,i) => {
if(r[12] == "TRUE") {
sh.getRange(i + 2, 15).setValue("TRUE");
}
})
}

Fuzzy matching in Google Sheets

Trying to compare two columns in GoogleSheets with this formula in Column C:
=if(A1=B1,"","Mismatch")
Works fine, but I'm getting a lot of false positives:
A.
B
C
MARY JO
Mary Jo
JAY, TIM
TIM JAY
Mismatch
Sam Ron
Sam Ron
Mismatch
Jack *Ma
Jack MA
Mismatch
Any ideas how to work this?
This uses a score based approach to determine a match. You can determine what is/isn't a match based on that score:
Score Formula = getMatchScore(A1,B1)
Match Formula = if(C1<.7,"mismatch",)
function getMatchScore(strA, strB, ignoreCase=true) {
strA = String(strA);
strB = String(strB)
const toLowerCase = ignoreCase ? str => str.toLowerCase() : str => str;
const splitWords = str => str.split(/\b/);
let [maxLenStr, minLenStr] = strA.length > strB.length ? [strA, strB] : [strB, strA];
maxLenStr = toLowerCase(maxLenStr);
minLenStr = toLowerCase(minLenStr);
const maxLength = maxLenStr.length;
const minLength = minLenStr.length;
const lenScore = minLength / maxLength;
const orderScore = Array.from(maxLenStr).reduce(
(oldItem, nItem, index) => nItem === minLenStr[index] ? oldItem + 1 : oldItem, 0
) / maxLength;
const maxKeyWords = splitWords(maxLenStr);
const minKeyWords = splitWords(minLenStr);
const keywordScore = minKeyWords.reduce(({ score, searchWord }, nItem) => {
const newSearchWord = searchWord?.replace(new RegExp(nItem, ignoreCase ? 'i' : ''), '');
score += searchWord.length != newSearchWord.length ? 1: 0;
return { score, searchWord: newSearchWord };
}, { score: 0, searchWord: maxLenStr }).score / minKeyWords.length;
const sortedMaxLenStr = Array.from(maxKeyWords.sort().join(''));
const sortedMinLenStr = Array.from(minKeyWords.sort().join(''));
const charScore = sortedMaxLenStr.reduce((oldItem, nItem, index) => {
const surroundingChars = [sortedMinLenStr[index-1], sortedMinLenStr[index], sortedMinLenStr[index+1]]
.filter(char => char != undefined);
return surroundingChars.includes(nItem)? oldItem + 1 : oldItem
}, 0) / maxLength;
const score = (lenScore * .15) + (orderScore * .25) + (charScore * .25) + (keywordScore * .35);
return score;
}
try:
=ARRAYFORMULA(IFERROR(IF(LEN(
REGEXREPLACE(REGEXREPLACE(LOWER(A1:A), "[^a-z ]", ),
LOWER("["&B1:B&"]"), ))>0, "mismatch", )))
Implementing fuzzy matching via Google Sheets formula would be difficult. I would recommend using a custom formula for this one or a full blown script (both via Google Apps Script) if you want to populate all rows at once.
Custom Formula:
function fuzzyMatch(string1, string2) {
string1 = string1.toLowerCase()
string2 = string2.toLowerCase();
var n = -1;
for(i = 0; char = string2[i]; i++)
if (!~(n = string1.indexOf(char, n + 1)))
return 'Mismatch';
};
What this does is compare if the 2nd string's characters order is found in the same order as the first string. See sample data below for the case where it will return mismatch.
Output:
Note:
Last row is a mismatch as 2nd string have r in it that isn't found at the first string thus correct order is not met.
If this didn't meet your test cases, add a more definitive list that will show the expected output of the formula/function so this can be adjusted, or see player0's answer which solely uses Google Sheets formula and is less stricter with the conditions.
Reference:
https://stackoverflow.com/a/15252131/17842569
The main limitation of traditional fuzzy matching is that it doesn’t take into consideration similarities outside of the strings. Topic clustering requires semantic understanding. Goodlookup is a smart function for spreadsheet users that gets very close to semantic understanding. It’s a pre-trained model that has the intuition of GPT-3 and the join capabilities of fuzzy matching. Use it like vlookup or index match to speed up your topic clustering work in google sheets.
https://www.goodlookup.com/

google sheets Get the last non-empty cell in a ROW

this is the simplified version of my data.
How can I calculate E and F columns? the address format of E column is not important.
put this custom formula to E2 =last_item_index(A2:D)
and here is the custom function code:
/**
* #customfunction
*/
function last_item_index(range) {
const colCount = range[0].length
const lastItemIndices = range.map(row=>{
const indexFirstNonEmpty = row.reverse().findIndex(cell=>cell)
const indexLastNonEmpty = colCount - 1 - indexFirstNonEmpty
return indexFirstNonEmpty>=0? [indexLastNonEmpty, row[indexFirstNonEmpty]] : ['','']
})
return lastItemIndices
}
The value returned in column E will be the zero-based index of the last non-empty item. You can easy convert it to A1-formatted range with ADDRESS function.
try:
=INDEX(IFNA(REGEXEXTRACT(" "&TRIM(FLATTEN(QUERY(TRANSPOSE(IF(A2:D="",,
ADDRESS(ROW(A2:A), COLUMN(A:D), 4))),,9^9))), " (.{1,3}\d+)$")))
and:
=INDEX(IFNA(SUBSTITUTE(REGEXEXTRACT(" "&TRIM(FLATTEN(QUERY(TRANSPOSE(IF(A2:D="",,
SUBSTITUTE(A2:D, " ", "♦"))),,9^9))), "((?:[^ ]+ *){1})$"), "♦", " ")))