Is there a single Apps Script function equivalent to MATCH() with TRUE? - google-apps-script

I need to write some functions that involve the same function as the Sheets function MATCH() with parameter 'sort type' set to TRUE or 1, so that a search for 35 in [10,20,30,40] will yield 2, the index of 30, the next lowest value to 35.
I know I can do this by looping over the array to search, and testing each value against my search value until a value greater than the search value is found, but it seems to me there must be a shorthand way of doing this. We don't have to do this when seeking an exact value; we can just use indexOf(). I was surprised when I first learned that indexOf() does not have a parameter for search type, but can only return a -1 if an exact value is not found.
Is there no function akin to indexOf() that will do this, or is it actually necessary to loop over the array every time you need to do this?

Probably you're looking for the array.find() method. The impelentation could be something like this:
var arr = [10,20,30,40]
// make a copy of the array, reverse it and do find with condition
var value = arr.slice().reverse().find(x => x < 35)
console.log(value) // output --> 30 (first element less than 35 in the reversed array)
var index = arr.indexOf(value)
console.log(index) // output --> 2 (index of the element in the original array)
https://www.w3schools.com/jsref/jsref_find.asp
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
There is another method array.findIndex(). Probably you can use it as well:
var arr = [10,20,30,40]
// find more or equal 35 and return previous index
var index = arr.findIndex(x => x >= 35) - 1
console.log(index) // output --> 2
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex

Try this:
function lfunko(tgt = 35) {
Logger.log([10,20,30,40].reduce((a,c,i) => { a.r = (a.x >= c)? i:a.r;return a;},{x:tgt}).r)
}

Related

Adding a range of numbers ignoring leading character

I have a list of numbers, some of them have leading underscores, some of them don't.
A
B
_12
34
99
_42
Which is the best way of adding up these numbers?
Note: I tried this custom script formula which for some reason doesn"t work (only returns the first item passed in the range), and anyway I guess there should be an easier way just using native GoogleSheet formulas.
function sum_with_underscores(underscored_nums) {
let nums = underscored_nums.map( x => String(x).replace("_", ""))
return nums.reduce((pv, cv) => parseFloat(pv) + parseFloat(cv), 0);
}
=SUM(ARRAYFORMULA(VALUE(SUBSTITUTE(<your_range>, "_", ""))))
Assuming you would like to achieve this result using a script instead of a formula, the code below worked for me.
Make sure to flatten the array before manipulating the data. Hope this helps.
let values = sheet.getDataRange().getValues() // returns array of arrays
let nums = values.flat() // converts [[_12, 34.0], [99.0, _42]] to [_12, 34.0, 99.0, _42]
nums = nums.map( n => {
return typeof(n) === 'number' ? n : parseFloat(n.replace("_", ""));
})
let sum = nums.reduce((acc, num)=> acc + num, 0)
}

Converting cell coordinates without using a column number to column letter method?

I'm trying to figure out what my options are here when I need to use a column number in a formula, and if I really need to write a column number to column letter method to accomplish what I'm trying to do.
See this method I have here:
createFormulas(lookupField, lookupColumns) {
// Iterate through the lookupColumn array
lookupColumns.forEach(value => {
let columnNumber = this.getColumn(this.headers, value);
let range = this.sheet.getRange(2, columnNumber, this.lastRow - 1, 1);
// range.setFormula('=$A2');
range.setFormula('=' + columnNumber + '2' ); // doesn't work obviously
})
}
I'm trying to add formulas in a column based on the column.
this.getColumn() returns the column number based on the column name being passed in.
let range sets the range I want to set the formula in
range.setFormula('=$A2') pastes this formula into range and updates the reference accordingly (i.e., $A3, $A4, etc.). This isn't the formula I ultimately want to use, just a simplified example.
I need to set the column in the reference dynamically, however.
What I have obviously won't work: range.setFormula('=' + columnNumber + '2' );. That would just result in something like 72 where 7 is the column number.
I know I can write a method that will convert the column number into a letter. I'm just surprised there isn't a built in method for doing that or some other native way of accomplishing this.
For example, in Excel VBA I think you can do something like "=" & Cells(2, columnNumber).Address or something like that (been a while, I could be wrong), which should equate to =A2, =A3, =A4, etc. in the range.
So before writing this column number to letter method, I just wanted to check: is that the only way to accomplish what I'm after or is there a native way of handling this that I'm just not seeing?
Actually, was able to do this using .getA1Notation().
Refactored to the following and it works as expected:
createFormulas(lookupField, lookupColumns) {
// Iterate through the lookupColumn array
lookupColumns.forEach(value => {
let columnNumber = this.getColumn(this.headers, value);
let formulaRange = this.sheet.getRange(2, columnNumber, this.lastRow - 1, 1);
let referenceRange = this.sheet.getRange(2, this.idColumn, this.lastRow - 1, 1);
formulaRange.setFormula("=" + referenceRange.getCell(1, 1).getA1Notation());
})
}
Column To Letters
I followed Yuri's path to the numbers to letter functions and I'm a bit baffled that we have forgotten that there are 26 letters in the alphabet and so after looking at the various functions at that reference none of them seem to have worked for me. So here's my replacement:
function colToletters(num) {
let a = " ABCDEFGHIJKLMNOPQRSTUVWXYZ";
if (num < 27) return a[num % a.length];
if (num > 26) {
num--;
let letters = '';
while (num >= 0) {
letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'[num % 26] + letters;
num = Math.floor(num / 26) - 1;
}
return letters;
}
}
This will calculate the column letters for 1 to 1000 and I check all the way to 703 where the letters go to AAA and they look good all the way.
Just in case. Based on https://stackoverflow.com/a/64456745/14265469
function numberToLetters(num) {
// num--; // if you need 1 --> A, 2 --> B, 26 --> Z
let letters = '';
while (num >= 0) {
letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'[num % 26] + letters;
num = (num - num % 26) / 26 - 1;
}
return letters;
}
console.log(numberToLetters(0)); // --> A
console.log(numberToLetters(25)); // --> Z
console.log(numberToLetters(26)); // --> AA

OCTAVE: Checking existence of an element of a cell array

I am using Octave 4.0.0.
I define A{1, 1} = 'qwe', but when I check existence of A{1, 1}, as in
exist("A{1,1}")
or
exist("A{1,1}", "var")
it returns 0.
How can I check its existence?
To check if an array has element say 3, 5, you need to verify that the array has at least 3 rows and 5 columns:
all(size(A) >= [3, 5])
You can of course check if variable A exists at all before-hand, and also is a cell array. A complete solution might be something like
function b = is_element(name, varargin)
b = false;
if ~evalin(['exists("' name '")'], 'caller')
return;
end
if ~strcmp(evalin(['class(' name ')'], 'caller'), 'cell')
return;
end
if evalin(['ndim(' name ')'], 'caller') ~= nargin - 1
return;
end
b = all(evalin(['size(' name ')'], 'caller') >= cell2mat(varargin))
endfunction
This function accepts a variable name and the multi-dimensional index you are interested in. It returns 1 if the object exists as a cell array of sufficient dimensionality and size to contain the requested element.

Generate auto increment id from JSON schema faker

I'm looking for way to generate data by JSON schema faker js with IDs incremented from 0.
When I'm trying to use autoIncrement parameter in schema, I get valid values, but this auto increment is started from random number.
Is that possible to do that with this package?
I didn't find an official solution to the problem, but here is a workaround.
json-schema-faker's source code for generating auto-incremented integers (node_modules\json-schema-faker\lib\index.js) explains why it starts from a random integer:
// safe auto-increment values
container.define('autoIncrement', function (value, schema) {
if (!this.offset) {
var min = schema.minimum || 1;
var max = min + env.MAX_NUMBER;
this.offset = random$1.number(min, max);
}
if (value === true) {
return this.offset++;
}
return schema;
});
It is the if (!this.offset) branch that sets up the initial value. To achieve our goal, we can modify the code inside the branch like this:
if (!this.offset) {
var min = schema.minimum || 1;
// var max = min + env.MAX_NUMBER;
// this.offset = random$1.number(min, max);
this.offset = min;
}
When minimum is specified in the schema, its value will be used as the starting point. Otherwise, 1 is used instead.
It is also noteworthy that, if you specify minimum with an extremely large number, the auto-incrementation will no longer be "safe".
For anyone searching for a more current answer, you can now set an 'initialOffset' value within the schema which acts as a start value

Difficulty splitting array and returning a value from within it; Javascript

I have got an array that consists of strings. I have made a function that searches the array based on the search term parameter. However, when i run the code it only ever outputs the string at index 0 of the array. I want it to return the corresponding url in the array when a search is run.
Any help would be very much appreciated. Thanks in advance.
So you are trying to return URL based on the String after the ~?
Do the line
arrayOfURL[i].toLowerCase().split('~')[i];
seem weird to you? Imagine as i increases, eg. i = 4
arrayOfURL[4].toLowerCase().split('~')[4];
Does that last [4] make sense?
I am guessing the reason it never got past the first element is because the code actually erroring out on that part.
I think what you want is (likewise for the return line, you'll want [0]
arrayOfURL[i].toLowerCase().split('~')[1];
I would also take a look at
if (z >= searchtoLower)
what are you trying to compare there?
The problem may be in the second i param:
var z = arrayOfURL[i].toLowerCase().split('~')[i];
The string will be splitted into 2 parts (index 0, 1). Why did you select part i?
This is a correct version of your program:
var arrayOfURL = [
"http://www.google.co.uk~Google is a search engine.",
"http://www.yahoo.co.uk~Yahoo is another search engine.",
"http://bing.com~Bing is a decision engine."
];
function findURL(arrayOfURL,search)
{
var searchtoLower = search.toLowerCase();
for (var i = 0; i < arrayOfURL.length; i++)
{
var z = arrayOfURL[i].toLowerCase().split('~')[1];
if (z.indexOf(searchtoLower) != -1)
return arrayOfURL[i];
}
return "Nothing Found!";
}
findURL(arrayOfURL,"decision")
I hope it can help you.
I think you should be doing
var terms = arrayOfURL[i].toLowerCase().split('~');
if(0 <= terms[1].indexOf(searchToLower))
// ^ ^
// | |-- 0 <= indexOf method determines
// | if searchToLower is a substring of terms[1]
// |
// |-- term[1] gets the part after the first "~"
and
return terms[0]; //terms[0] is the part before the first "~"
I would also consider returning null or the empty string "" in case of failure (instead of returning the arbritrary "Nothing Found!" message)