Optimise custom function with multiple args to accept ranges - google-apps-script

The developers guide of google apps suggests to write your function to accept ranges for optimization like this:
function DOUBLE(input) {
if (input.map) { // Test whether input is an array.
return input.map(DOUBLE); // Recurse over array if so.
} else {
return input * 2;
}
}
I wonder how you would that with multiple arguments?
e.g
function DOUBLEandADD(doubleThis, addThis){
return (doubleThis * 2) + addThis;
}
Do you check both at once and how do you iterate over the ranges?
Something like this perhaps?
function DOUBLEandADD(doubleThis, addThis) {
if (doubleThis.map && addThis.map){
var d = Array.from(doubleThis.values());
var a = Array.from(addThis.values());
for (var i = 0; i < d.length(); i++){
return DOUBLEandADD(d[i],a[i]);
}
} else {
return (doubleThis * 2) + addThis;
}
}

You would wrap the the map function in an anonymous function and pass the additional parameters.
function DOUBLEandADD(input,addend) {
if (input.map) {
return input.map(function(i){return DOUBLEandADD(i,addend)});
} else {
return (input * 2) + addend;
}
}

I wouldn't use map in your case. How about this:
function DOUBLEandADD(doubleThis, addThis) {
if (Array.isArray(doubleThis) || Array.isArray(addThis)) {
if (!Array.isArray(doubleThis) ||
!Array.isArray(addThis) ||
doubleThis.length != addThis.length) {
throw new Error("doubleThis and addThis arrays must be same length.");
}
var results = [];
for (var i = 0; i < doubleThis.length; i++) {
results.push((doubleThis[i] * 2) + addThis[i]);
}
return results;
} else {
return (doubleThis * 2) + addThis;
}
}

Related

How can I use Google Script method UrlFetchApp.fetch within an ArrayFormula?

function getContentForAPI(path)
{
var result = {};
result.url = "https://" + HOST + "/rest/api/2/" + path;
result.response = UrlFetchApp.fetch(result.url, API_HEADERS);
result.text = result.response.getContentText();
try { result.data = JSON.parse(result.text); } catch(error) {}
return result;
}
I have a custom formula like such:
/**
* Import data.
*
* #param {string} the ID that belongs to the issue.
* #return The summary.
* #customfunction
*/
function IMPORTDATA(issueID)
{
var details = [];
if (issueID instanceof Array) {
var issueIDs = issueID;
for (i in issueIDs){
for (j in issueIDs[i]){
issueID = issueIDs[i][j];
var content = getContentForAPI("issue/"+issueID);
if (content == undefined || content.data == undefined) {
details.push(["G","H"]);
} else {
details.push(["E", "F"]);
}
}
}
} else {
var content = getContentForAPI("issue/"+issueID);
if (content.data != undefined) {
details.push([content.data.fields.project.name, content.data.fields.summary]);
}
}
return details;
}
This works for a single formula =IMPORTDATA(A2) however when I put it within an ArrayFormula =ARRAYFORMULA(IMPORTDATA(A2:A)) the result is #ERROR however when I comment out UrlFetchApp.fetch() it works so the error seems to be coming from that method. Does anyone know why?

Actionscript display text on screen once an action is performed

So I need to display a text once an action is performed but even though I tried to do so by using dynamic text and labels, I didn't manage to finish my programming due to errors:
var group:RadioButtonGroup= new RadioButtonGroup ("Question1");
var group2:RadioButtonGroup= new RadioButtonGroup ("Question2");
var group3:RadioButtonGroup= new RadioButtonGroup ("Question3");
var group4:RadioButtonGroup= new RadioButtonGroup ("Question4");
var group5:RadioButtonGroup= new RadioButtonGroup ("Question5");
var counterT:int;
var counterF:int;
submit.buttonMode=true;
counterT=0;
counterF=0;
t1.group = group;
f1.group = group;
t2.group=group2;
f2.group=group2;
t3.group=group3;
f3.group=group3;
t4.group=group4;
f4.group=group4;
t5.group=group5;
f5.group=group5;
submit.label="Submit";
submit.addEventListener(MouseEvent.CLICK,submitanswer);
function submitanswer (event:MouseEvent): void {
if (group.selection == t1) {
counterT==counterT+1
}
else
if (group.selection==f1) {
counterF==counterF+1;
}
}
if (group2.selection ==t2) {
counterT==counterT+1
}
else
if (group2.selection==f2) {
counterF==counterF+1
}
if (group3.selection ==t3) {
counterT==counterT+1
}
else
if (group3.selection==f3) {
counterF==counterF+1
}
if (group4.selection ==t4) {
counterT==counterT+1
}
else
if (group4.selection==f4) {
counterF==counterF+1
}
if (group5.selection ==t5) {
counterT==counterT+1
}
else
if (group5.selection==f5) {
counterF==counterF+1
}
The first thing I see is that you are using the == to set a value. You need to use = when setting values. So like this:
if (x == y) {
counter = counter + 1;
}
or you can just use counter++ like this
if (x == y) {
counter++;
}

Underscore with count for duplicate values in angular json array

Can anyone please tell me how to put count in duplicate values in angular JSON array:
My actual array is given below:
$scope.datas.resultsOrder =['Data1','Data2','Data3','Data3','Data4','Data4'];
in the above array Data3 and Data4 is repeating twice, so i need it to come as Data3_1, Data3_2, Data4_1, Data4_2 order within that array like as shown below:
$scope.datas.resultsOrder =['Data1','Data2','Data3_1',
'Data3_2','Data4_1','Data4_2'];
Also the values within that array are dynamic values and not static
Can anyone please tell me some solution for this?
I like UnderscoreJS for these kind of problems. In underscoreJS you can do something like this:
function uniq(array) {
var grouped = _.groupBy(array);
return _.reduce(grouped, function(result, x) {
if(x.length > 1) {
_.each(x, function(val, key) {
result.push(val + '_' + (key + 1));
});
} else {
result.push(x[0]);
}
return result;
},[]);
}
uniq(['Data1','Data2','Data3','Data3','Data4','Data4']);
// ["Data1", "Data2", "Data3_1", "Data3_2", "Data4_1", "Data4_2"]
You can do this:
function transform(arr) {
var c = {};
for (var i = 0; i < arr.length; i++) {
var ar = arr[i];
if(! (ar in c) ) {
c[ar] = 0;
}
c[ar]++;
}
var res = []
;
for(var d in c) {
if(c.hasOwnProperty(d)) {
var l = c[d]
;
if(l === 1) {
res.push(d);
continue;
}
for(var i = 0; i < l; i++) {
res.push(d + '_' + (i + 1));
}
}
}
return res;
}
$scope.datas.resultsOrder = transform(passTheArrayHere);
Note: No guarantee for order.

How can I use a custom function with FILTER?

I have a custom function defined that extracts part of an address from a string:
/*
* Return the number preceding 'N' in an address
* '445 N 400 E' => '445'
* '1083 E 500 N' => '500'
*/
function NorthAddress(address) {
if (!address) return null;
else {
var North = new RegExp('([0-9]+)[\\s]+N');
var match = address.match(North);
if (match && match.length >= 2) {
return match[1];
}
return null;
}
}
I want to use this function as one of the conditions in a call to FILTER(...) in the spreadsheet where I have these addresses stored:
=FILTER('Sheet 1'!A:A, NorthAddress('Sheet 1'!B:B) >= 450))
But when I call NorthAddress like this, it gets an array of all the values in column B and I can't for the life of me find any documentation as to how I need to handle that. The most obvious way (to me) doesn't seem to work: iterate over the array calling NorthAddress on each value, and return an array of the results.
What does my function need to return for FILTER to work as expected?
When a custom function is called passing a multi-cell range, it receives a matrix of values (2d array), it's doesn't matter if the range is a single column or a single row, it's always a matrix. And you should return a matrix as well.
Anyway, I would not use a custom function to this, as there is already the native spreadsheet formulas: RegexMatch, RegexExtract and RegexReplace formulas. To get the "if match" behavior, just wrap them in a IfError formula.
It doesn't work because address is, if you pass only one cell as arg a string, a range, a matrix of string.
So you return a string, FILTER use a boolean array to filter data, so the condition of your filter is string < number.
You just have to convert the string to a number when you returning a value
/*
* Return the number preceding 'N' in an address
* '445 N 400 E' => '445'
* '1083 E 500 N' => '500'
*/
function NorthAddress(address) {
if(typeof address == "string"){
if (!address) return "#N/A";
else {
var North = new RegExp('([0-9]+)[\\s]+N');
var match = address.match(North);
if (match && match.length >= 2) {
return parseInt(match[1]);
}
return "#N/A";
}
} else {
var matrix = new Array();
for(var i = 0; i<address.length; i++){
matrix[i] = new Array();
for(var j = 0; j<address[i].length; j++){
var North = new RegExp('([0-9]+)[\\s]+N');
var match = address[i][j].match(North);
if (match && match.length >= 2) {
matrix[i].push(parseInt(match[1]));
}
}
}
return matrix;
}
}
Hope this will help.
I will add this as an answer, because I found the custom function returns an error if numerical values are passed in the referenced cell or range when toString() is not invoked:
function NorthAddress(address) {
if (!address) return null;
else {
if (address.constructor == Array) {
var result = address;
}
else {
var result = [[address]];
}
var north = new RegExp('([0-9]+)[\\s]+N');
var match;
for (var i = 0; i < result.length; i++) {
for (var j = 0; j < result[0].length; j++) {
match = result[i][j].toString().match(north);
if (match && match.length >= 2) {
result[i][j] = parseInt(match[1]);
}
else {
result[i][j] = null;
}
}
}
return result;
}
}

Trying to create a function which extracts a URL from an array. JavaScript

So basically I would like to create a function that when alerted, returns the URL from an array (in this case the array is declared as 'websites'). The function has two parameters 'websites' and 'searchTerm'.
I'm struggling to make the function behave, so that when i type yahoo or google or bing in the searchTerm parameter for the function; I want it to return the corresponding URL.
Any help or support would be greatly appreciated.
Sorry if I have not made myself clear in my explanation, if this is the case, let me know and I will try and be clearer in my explanation.
Thanks in advance!
Try something more like:
var websites = {google: 'www.google.com', yahoo: 'www.yahoo.com'};
function filterURL(websites,searchTerm)
{
return websites[searchTerm] || 'www.defaultsearchwebstirehere.com';
}
** Update following comment **
Build up your websites object like so (where input is your array of key values seperated by pipe characters):
var websites = {};
for (var i = 0; i < input.length; i++) {
var siteToSearchTerm = input[i].split('|');
websites[siteToSearchTerm[1]] = siteToSearchTerm[0];
}
Here is how:
var websites = ["www.google.com|Google" , "www.yahoo.com|Yahoo" , "www.bing.com|Bing"];
function filterURL(websites,searchTerm)
{
for (var i = 0; i < websites.length; i++) {
if (websites[i].split('|')[1] === searchTerm) {
return websites[i].split('|')[0];
}
}
}
Working Example
You can also validate and improve function:
function filterURL(websites,searchTerm)
{
if (typeof websites != 'Array' || ! searchTerm) return false;
for (var i = 0; i < websites.length; i++) {
if (websites[i].split('|')[1] === searchTerm) {
return websites[i].split('|')[0];
}
}
return false;
}
Why not just use an object?
var websites = {
Google: 'www.google.com',
Yahoo: 'www.yahoo.com'
};
function filterURL(sites, searchTerm) {
if (sites[searchTerm]) {
return sites[searchTerm];
} else {
// What do you want to do when it can't be found?
}
}
alert(filterURL(websites, 'Google')); // alerts 'www.google.com'
You should really be using a hash-table like structure so that you don't have to search through the whole array every time. Something like this:
var websites = {
"Google": "www.google.com",
"Yahoo": "www.yahoo.com",
"Bing": "www.bing.com"
};
function filterURL(websites, searchTerm) {
if (websites[searchTerm] !== undefined)
return websites[searchTerm];
else
return null;
}
I'm not sure why you want to use an array for this, as what you're really doing fits a key-value pair better; however, here's how I'd do it:
function filterURL(websites, searchTerm) {
var i = 0,
parts;
for (i = 0; i < websites.length; i++) {
parts = websites[i].split("|");
if (parts[1].toLowerCase() === searchTerm) {
return parts[0];
}
}
}
But consider if you used a proper JavaScript Object instead:
var websites = {
Google: "www.google.com",
Yahoo: "www.yahoo.com",
Bing: "www.bing.com"
}
// Now it's much simpler:
function filterURL(websites, searchTerm) {
// key has first letter capitalized…
return websites[searchTerm.charAt(0).toUpperCase() + searchTerm.slice(1).toLowerCase()];
}