if/elseif error with Google Apps Script - google-apps-script

I have an error while coding in google sheets.
It doesn't want to multiply the numbers, just displays them as floats without the suffix.
This is my code:
function nordicImport(a){
var b = a;
var c = b.substring(0,b.length-1);
if(b.contains == "M"){
var c = parseFloat(b);
var d = c * 1000000;
return d;
}else if(b.contains == "K"){
var c = parseFloat(b);
var d = c * 1000;
return d;
}else{
var c = parseFloat(b);
return c;
}
}

I rearranged your code, since I think it is a little bit confusing.
Try this:
function nordicImport(a){
var b = a.substring(0, a.length - 1) // This is the number
var c = a.substring(a.length - 1) // This is the letter
if (c == "M"){
Logger.log(b * 1000000)
return b * 1000000
}
else if (c == "k"){
Logger.log(b * 1000)
return b * 1000
}
}
When you use an if/elseif statement you must use "==" or "===" instead of "=" ("=" is an assignement!).

There is no contains method of string in GAS (that I know of). Also I don't see a need to copy b to a since a is a copy of the value passed as an argument to the function and can be used directly. Also I'm sure there are other mistakes that can be made inputing values but this gives you an idea.
function nordicImport(a){
if( typeof a === "number" ) {
return a;
}
else if( typeof a === "string" ) {
if( a.lastIndexOf("M") === a.length-1 ) {
return parseFloat(a.substring(0,a.length-1))*1000000;
}
else if( a.lastIndexOf("K") === a.length-1 ) {
return parseFloat(a.substring(0,a.length-1))*1000;
}
else {
return "unknown multiple";
}
}
else {
return "unknown type";
}
}

Related

How to use a for loop with .createChoice in Google Apps Script to create a quiz from a sheet?

I am using Google Apps Script to generate Google Forms from a Sheet. Questions are in rows and question choices are in columns.
Here is a link to the Google sheet if needed.
It is a straightforward task when using .setChoiceValues(values)
if (questionType == 'CHOICE') {
var choicesForQuestion = [];
for (var j = 4; j < numberColumns; j++)
if (data[i][j] != "")
choicesForQuestion.push(data[i][j]);
form.addMultipleChoiceItem()
.setChoiceValues(choicesForQuestion);
}
However, when I try to use .createChoice(value, isCorrect), the parameters call for value to be a string and isCorrect to be Boolean.
An example without a loop looks like this:
var item = FormApp.getActiveForm().addCheckboxItem();
item.setTitle(data[3][1]);
// Set options and correct answers
item.setChoices([
item.createChoice("chocolate", true),
item.createChoice("vanilla", true),
item.createChoice("rum raisin", false),
item.createChoice("strawberry", true),
item.createChoice("mint", false)
]);
I can not figure out how to add the loop. After reading over other posts, I have tried the following:
if (questionType == 'CHOICE') {
var questionInfo = [];
for (var j = optionsCol; j < maxOptions + 1; j++)
if (data[i][j] != "")
questionInfo.push( form.createChoice(data[i][j], data[i][j + maxOptions]) );
form.addMultipleChoiceItem()
.setChoices(questionInfo);
}
optionsCol is the first column of questions options
maxOptions is how many options are allowed by the sheet (currently 5). The isCorrect information is 5 columns to the right.
However, this not working because the array questionsInfo is empty.
What is the best way to do this?
Probably your issue is related to the method you reference--Form#createChoice--not existing. You need to call MultipleChoiceItem#createChoice, by first creating the item:
/**
* #param {Form} formObj the Google Form Quiz being created
* #param {any[]} data a 1-D array of data for configuring a multiple-choice quiz question
* #param {number} index The index into `data` that specifies the first choice
* #param {number} numChoices The maximum possible number of choices for the new item
*/
function addMCItemToForm_(formObj, data, index, numChoices) {
if (!formObj || !data || !Array.isArray(data)
|| Array.isArray(data[0]) || data.length < (index + 2 * numChoices))
{
console.error({message: "Bad args given", hasForm: !!formObj, info: data,
optionIndex: index, numChoices: numChoices});
throw new Error("Bad arguments given to `addMCItemToForm_` (view on StackDriver)");
}
const title = data[1];
// Shallow-copy the desired half-open interval [index, index + numChoices).
const choices = data.slice(index, index + numChoices);
// Shallow-copy the associated true/false data.
const correctness = data.slice(index + numChoices, index + 2 * numChoices);
const hasAtLeastOneChoice = choices.some(function (c, i) {
return (c && typeof correctness[i] === 'boolean');
});
if (hasAtLeastOneChoice) {
const mc = formObj.addMultipleChoiceItem().setTitle(title);
// Remove empty/unspecified choices.
while (choices[choices.length - 1] === "") {
choices.pop();
}
// Convert to choices for this specific MultipleChoiceItem.
mc.setChoices(choices.map(function (choice, i) {
return mc.createChoice(choice, correctness[i]);
});
} else {
console.warn({message: "Skipped bad mc-item inputs", config: data,
choices: choices, correctness: correctness});
}
}
You would use the above function as described by its JSDoc - pass it a Google Form object instance to create the quiz item in, an array of the details for the question, and the description of the location of choice information within the details array. For example:
function foo() {
const form = FormApp.openById("some id");
const data = SpreadsheetApp.getActive().getSheetByName("Form Initializer")
.getSheetValues(/*row*/, /*col*/, /*numRows*/, /*numCols*/);
data.forEach(function (row) {
var qType = row[0];
...
if (qType === "CHOICE") {
addMCItemToForm_(form, row, optionColumn, numOptions);
} else if (qType === ...
...
}
References
Array#slice
Array#forEach
Array#map
Array#some
I am sure the above answer is very good and works but I am just a beginner and needed a more obvious (plodding) method. I am generating a form from a spreadsheet. Question types can include: short answer (text item), long answer (paragraph), drop down (list item), multiple choice, grid item, and checkbox questions, as well as sections.
I had to be able to randomize the input from the spreadsheet for multiple choice and sort the input for drop downs. I am only allowing one correct answer at this time.
The columns in the question building area of the spreadsheet are: question type, question, is it required, does it have points, hint, correct answer, and unlimited choice columns.
qShtArr: getDataRange of the entire sheet
corrAnsCol: index within the above of the column with the correct answer
begChoiceCol: index within the above of first column with choices
I hope this helps other less skilled coders.
/**
* Build array of choices. One may be identified as correct.
* I have not tried to handle multiple correct answers.
*/
function createChoices(make, qShtArr, r, action) {
// console.log('Begin createChoices - r: ', r);
let retObj = {}, choiceArr = [], corrArr = [], aChoice, numCol, hasCorr;
numCol = qShtArr[r].length - 1; // arrays start at zero
if ((qShtArr[r][corrAnsCol] != '') && (qShtArr[r][corrAnsCol] != null)) {
hasCorr = true;
choiceArr.push([qShtArr[r][corrAnsCol], true]);
for (let c = begChoiceCol ; c < numCol ; c++) {
aChoice = qShtArr[r][c];
if ((aChoice != '') && (aChoice != null)) { /* skip all blank elements */
choiceArr.push([aChoice, false]);
}
} //end for loop for multiple choice options
} else {
hasCorr = false;
for (let c = begChoiceCol ; c < numCol ; c++) {
aChoice = qShtArr[r][c];
if ((aChoice != '') && (aChoice != null)) { /* skip all blank elements */
choiceArr.push(aChoice);
}
} //end for loop for multiple choice options
}
if (action == 'random')
choiceArr = shuffleArrayOrder(choiceArr);
if (action == 'sort')
choiceArr.sort();
console.log('choiceArr: ', JSON.stringify(choiceArr) );
let choices = [], correctArr = [] ;
if (hasCorr) {
for ( let i = 0 ; i < choiceArr.length ; i++ ) {
choices.push(choiceArr[i][0]);
// console.log('choices: ', JSON.stringify(choices) );
correctArr.push(choiceArr[i][1]);
// console.log('correctArr: ', JSON.stringify(correctArr) );
}
make.setChoices(choices.map(function (choice, i) {
return make.createChoice(choice, correctArr[i]);
}));
} else { // no correct answer
if (action == 'columns' ) {
make.setColumns(choiceArr);
} else {
make.setChoices(choiceArr.map(function (choice, i) {
return make.createChoice(choice);
}));
}
}
}

Generate Random Mathematical Functions

This is kind of a weird question, and might not be entirely appropriate for Stack Overflow, but I couldn't find anything about it online, so here it is...
Is there a way (or what is the best way) to generate random mathematical functions? By this I don't mean that I want a function that generates a random number (line an RNG), but rather I want to dynamically create some function which which maps one or more real inputs from a domain to a single output using some mutate-able rules.
For example, in the simplest case, I could just generate a function of the form f(x1,x2) -> Y by applying a random operator to x1 and x2. For example f could be:
f = x1 + x2
or f = x1 - x2
or f = x1 * x2
etc...
However, I would like to be able to include more complex formulas including trigonometry, power functions, pseudorandom constants, possibly some calculus functions, etc... Obviously, I cant just concatenate different chunks in a completely random way, since these functions always need to always have valid syntax.
This isn't for anything crypto-related so it doesnt have to be perfect, but the more entropy the better. It would also be great if there is an easy way to keep track of what operations are being preformed and mutate them.
I'm not sure if anyone has any insights on this, or if it even made sense, but thank you anyway
I would suggest that you try to generate random expression trees; pseudocode (somewhat Scala-inspired) for that might look something like this:
NVars = 2
def generateTree(level) = {
if (level > 100) { generateVarref() }
else {
val choice = randomChoice(4)
switch (choice) {
case 0 => generateVarref()
case 1 => generateConstant()
case 2 => generateUnary(level + 1)
case 3 => generateBinary(level + 1)
}
}
}
def generateVarref() = {
val c = randomChoice(NVars)
VarRef(c)
}
def generateConstant() = {
Number(randomChoice(100))
}
def generateUnary(level) = {
val c = randomChoice(6)
val subexpr = generateTree(level)
switch (c) {
case 0 => Negate(subexpr)
case 1 => Sin(subexpr)
// etc. More unary functions here
}
}
def generateBinary(level) = {
val c = randomChoice(4)
val sub1 = generateTree(level)
val sub2 = generateTree(level)
switch (c) {
case 0 => Plus(sub1, sub2)
case 1 => Minus(sub1, sub2)
case 2 => Times(sub1, sub2)
case 3 => Divide(sub1, sub2)
}
}
Where Plus, Varref, etc are constructors for an expression type that implements a method that will then allow you to evaluate the expression at given values.
Let's assume your functions have 2 variables x1 and x2 (if this assumption is too restrictive just adapt my answer to n variables x1, ..., xn.)
[Start] Generate random polynomial functions
This would entail
modeling polynomials in 2 variables (x1 and x2)
implementing the evaluation of polynomials on (any) particular values of the variables
generating random polynomial functions by taking a random degree (up to a certain max) and random coefficients (inside a given interval)
[Compose] Enable Function Composition
This would entail
implementing the composition of functions so that if, say f, g and h are functions in your model (randomly generated or not), then f(g,h) is also a function in your model.
[Enrich] Add new function families to your model
Here you have to consider (and implement) other types of functions to the one you already have (polynomial): rational, trigonometric, logarithmic, exponential, etc. For every new type, you will have to model them and also, to implement a way of generating random instances of them (much as you did for the polynomials.)
[Generate] Create random functions combining all of the above
Choose some types randomly
For every type, generate a random instance
Compose all the types into a final result.
[Iterate] Go to [Enrich] and add new types of functions
Ditto.
Thanks everyone for the help. What I ended up doing was something along the lines of a parse tree, recursively generating new nodes with 2, 1, or 0 children (for binary or unary operators or constants). You could cap depth by checking Node.getDepth(). Below is some JavaScript code showing this process. I'm not sure how useful it will be but it works pretty much how I had envisioned.
'use strict';
var print = console.log;
function randint(a, b) {
return Math.floor((Math.random() * (b + 1 - a)) + a);
}
function Node(parentNode, numberOfVars,
mode, weight, method, numberOfChildren, varIndex, value) {
this.mode = mode ? mode : randint(0, 3);
this.parent = parentNode;
this.weight = weight ? weight : 1;
if (this.mode == 0) { //constant
this.value = value ? value : 1;
} else if (this.mode == 1) { //variable
this.varIndex = varIndex ? varIndex : randint(0, numberOfVars - 1);
} else if (this.mode == 2) { //binary
this.method = method ? method : Node.binary[randint(0, Node.binary.length - 1)];
} else if (this.mode == 3) { //unary
this.method = method ? method : Node.unary[randint(0, Node.unary.length - 1)];
}
if (numberOfChildren) {
this.children = new Array(numberOfChildren);
} else {
this.children = [];
if (this.mode == 2) { //binary
this.children = [new Node(this, numberOfVars),
new Node(this, numberOfVars)
];
} else if (this.mode == 3) { //unary
this.children = [new Node(this, numberOfVars)];
}
}
//Methods
this.execute = function(top_level_variables) {
print("executing " + this.mode);
var inputs = [];
this.children.forEach(function(child, index) {
print("child index " + index);
inputs.push(child.execute(top_level_variables) * child.weight);
});
print(" inputs = " + inputs);
if (this.mode == 0) {
print(" mode == 0");
return this.constant();
}
if (this.mode == 1) {
print(" mode == 1");
return this.variable(top_level_variables);
}
if (this.mode == 2) {
print(" mode == 2");
return this.method(inputs[0], inputs[1]);
}
if (this.mode == 3) {
print(" mode == 3");
return this.method(inputs[0]);
}
};
var getIndent = function(indent) {
var str = "";
if (indent === 0)
return str;
for (var i = 0; i < indent; i++) {
str += " | ";
}
return str;
};
this.getTree = function(indent) {
if (this.mode == 0) {
print(getIndent(indent) + "(" + this.value + ")");
} else if (this.mode == 1) {
print(getIndent(indent) + "x[" + this.varIndex + "]");
} else if (this.mode == 2) {
print(getIndent(indent) + this.method.name);
this.children[0].getTree(indent + 1);
this.children[1].getTree(indent + 1);
} else if (this.mode == 3) {
print(getIndent(indent) + this.method.name);
this.children[0].getTree(indent + 1);
}
};
this.getStr = function() {
if (this.mode == 0) {
return this.value;
} else if (this.mode == 1) {
return "x[" + this.varIndex + "]";
} else if (this.mode == 2) {
return this.method.name + "( " + this.children[0].getStr() + ", " + this.children[1].getStr() + " )";
} else if (this.mode == 3) {
return this.method.name + "( " + this.children[0].getStr() + " )";
}
};
}
Node.binary = [
function add(a, b) {
return a + b
},
function multiply(a, b) {
return a * b
},
function power(a, b) {
return Math.pow(a, b)
}
];
Node.unary = [
function sin(a) {
return Math.sin(a)
}
];
Node.prototype.constant = function() {
return this.value
};
Node.prototype.variable = function(variables) {
return variables[this.varIndex]
};
//Test
var a = new Node(null, 2, 2);
a.getTree(0);
print(a.getStr())
print(a.getDepth());
var b = a.execute([1, 3]);
print(b);

How do you shuffle values from ng-repeat? (AngularJS)

I tried this fiddle from this, this, this SO, and this.
I still can't make shuffle or random work on my end. My code has stopped here:
signupApp.filter('shuffle', function() {
var shuffledArr = [],
shuffledLength = 0;
return function(arr) {
if (!arr || !arr.length) { return; }
var o = arr.slice(0, arr.length);
if (shuffledLength == arr.length) return shuffledArr;
for(var j, x, i = o.length; i; j = parseInt(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
shuffledArr = o;
shuffledLength = o.length;
return arr.slice(o);
};
});
my ng-repeat
<ul ng-repeat="codes in response | shuffle">
<li ng-if="((codes.branch == formData.branches.alias) && (codes.taken == 0))">
<strong>{{codes.id}}</strong>
{{codes.code}}
</li>
</ul>
Results still do not randomize my outputs. Can someone help?
[UPDATE]: My code works in randomizing ng-repeat:
<span ng-repeat="codes in response">
<strong ng-if="((codes.branch == formData.branches.alias) && (codes.taken == 0))">
{{codes.code}}
</strong>
</span>
When limitTo:1 is added, it no longer displays anything. In my controller, the fisher-yates is run like this:
var shuffleArray = function(array) {
var m = array.length, t, i;
// While there remain elements to shuffle
while (m) {
// Pick a remaining element…
i = Math.floor(Math.random() * m--);
// And swap it with the current element.
t = array[m];
array[m] = array[i];
array[i] = t;
}
return array;
}
http.get("server/fetch.php").success(function(response){
scope.response = response;
shuffleArray(scope.response);
}).error(function() {
scope.response = "error in fetching data";
});
I can't figure out what's wrong with this. When limitTo: is set to 6, it outputs a single random value from the list. I also tried to set it to 10, and it outputs two random values from the list. I'm confused. I just want to limit it to 1 and randomize it.

Funky IE JSON conversions

When running our AngularJS app in IE11 everything looks great in the debugger, but when our app encodes the data as JSON to save to our database, we get bad results.
Our app obtains a record from our database, then some manipulation is done and then the data is saved back to the server from another model.
Here is the data I got back from the server in the setAttendanceGetSInfo() function below:
{"data":{"Start":"2014-10-16T19:36:00Z","End":"2014-10-16T19:37:00Z"},
This is the code used to "convert the data" to 3 properties in our model:
var setAttendanceGetSInfo = function (CourseId, PID) {
return setAttendanceInfo(CourseId, PID)
.then(function (result) {
return $q.all([
$http.get("../api/Axtra/getSInfo/" + model.event.Id),
$http.get("../api/Axtra/GetStartAndEndDateTime/" + aRow.Rid)
]);
}).then(function (result) {
var r = result.data;
var e = Date.fromISO(r.Start);
var f = Date.fromISO(r.End);
angular.extend(model.event, {
examDate: new Date(e).toLocaleDateString(),
examStartTime: (new Date(e)).toLocaleTimeString(),
examEndTime: (new Date(f)).toLocaleTimeString()
});
return result.sInfo;
});
};
fromISO is defined as:
(function(){
var D= new Date('2011-06-02T09:34:29+02:00');
if(!D || +D!== 1307000069000){
Date.fromISO= function(s){
var day, tz,
rx=/^(\d{4}\-\d\d\-\d\d([tT ][\d:\.]*)?)([zZ]|([+\-])(\d\d):(\d\d))?$/,
p= rx.exec(s) || [];
if(p[1]){
day= p[1].split(/\D/);
for(var i= 0, L= day.length; i<L; i++){
day[i]= parseInt(day[i], 10) || 0;
};
day[1]-= 1;
day= new Date(Date.UTC.apply(Date, day));
if(!day.getDate()) return NaN;
if(p[5]){
tz= (parseInt(p[5], 10)*60);
if(p[6]) tz+= parseInt(p[6], 10);
if(p[4]== '+') tz*= -1;
if(tz) day.setUTCMinutes(day.getUTCMinutes()+ tz);
}
return day;
}
return NaN;
}
}
else{
Date.fromISO= function(s){
return new Date(s);
}
}
})()
Take a look at the screenshot of the event model data:
But, if I eval the event model using JSON.stringify(model.event), I get this:
{\"examDate\":\"?10?/?16?/?2014\",\"examStartTime\":\"?2?:?44?:?00? ?PM\",\"examEndTime\":\"?2?:?44?:?00? ?PM\"}
And this is the JSON encoded data that actually got stored on the DB:
"examDate":"¿10¿/¿16¿/¿2014","examStartTime":"¿2¿:¿36¿:¿00¿ ¿PM","examEndTime":"¿2¿:¿37¿:¿00¿ ¿PM"
What is wrong here and how can I fix this? It works exactly as designed in Chrome and Firefox. I have not yet tested on Safari or earlier versions of IE.
The toJSON for the date class isn't defined perfectly the same for all browsers.
(You can see a related question here: Discrepancy in JSON.stringify of date values in different browsers
I would suspect that you have a custom toJSON added to the Date prototype since your date string doesn't match the standard and that is likely where your issue is. Alternatively, you can use the Date toJSON recommended in the above post to solve your issues.
First, I modified the fromISO prototype to this:
(function () {
var D = new Date('2011-06-02T09:34:29+02:00');
if (!D || +D !== 1307000069000) {
Date.fromISO = function (s) {
var D, M = [], hm, min = 0, d2,
Rx = /([\d:]+)(\.\d+)?(Z|(([+\-])(\d\d):(\d\d))?)?$/;
D = s.substring(0, 10).split('-');
if (s.length > 11) {
M = s.substring(11).match(Rx) || [];
if (M[1]) D = D.concat(M[1].split(':'));
if (M[2]) D.push(Math.round(M[2] * 1000));// msec
}
for (var i = 0, L = D.length; i < L; i++) {
D[i] = parseInt(D[i], 10);
}
D[1] -= 1;
while (D.length < 6) D.push(0);
if (M[4]) {
min = parseInt(M[6]) * 60 + parseInt(M[7], 10);// timezone not UTC
if (M[5] == '+') min *= -1;
}
try {
d2 = Date.fromUTCArray(D);
if (min) d2.setUTCMinutes(d2.getUTCMinutes() + min);
}
catch (er) {
// bad input
}
return d2;
}
}
else {
Date.fromISO = function (s) {
return new Date(s);
}
}
Date.fromUTCArray = function (A) {
var D = new Date;
while (A.length < 7) A.push(0);
var T = A.splice(3, A.length);
D.setUTCFullYear.apply(D, A);
D.setUTCHours.apply(D, T);
return D;
}
Date.toJSON = function (key) {
return isFinite(this.valueOf()) ?
this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z' : null;
};
})()
Then I added moment.js and formatted the dates when they get stored:
var SaveAffRow = function () {
// make sure dates on coursedate and event are correct.
var cd = model.a.courseDate;
var ed = model.event.examDate;
var est = model.event.examStartTime;
var eet = model.event.examEndTime;
model.a.courseDate = moment(cd).format("MM/DD/YYYY");
model.event.examDate = moment(ed).format("MM/DD/YYYY");
model.event.examStartTime = moment(est).format("MM/DD/YYYY hh:mm A");
model.event.examEndTime = moment(eet).format("MM/DD/YYYY hh:mm A");
affRow.DocumentsJson = angular.toJson({a: model.a, event: model.event});
var aff = {};
if (affRow.Id != 0)
aff = affRow.$update({ Id: affRow.Id });
else
aff = affRow.$save({ Id: affRow.Id });
return aff;
};
and when they get read (just in case they are messed up already):
var setAttendanceGetSInfo = function (CourseId, PID) {
return setAttendanceInfo(CourseId, PID)
.then(function (result) {
return $q.all([
$http.get("../api/Axtra/getSInfo/" + model.event.Id),
$http.get("../api/Axtra/GetStartAndEndDateTime/" + aRow.Rid)
]);
}).then(function (result) {
var r = result.data;
var e = Date.fromISO(r.Start);
var f = Date.fromISO(r.End);
angular.extend(model.event, {
examDate: moment(e).format("MM/DD/YYYY"),
examStartTime: moment(e).format("MM/DD/YYYY hh:mm A"),
examEndTime: moment(f).format("MM/DD/YYYY hh:mm A")
});
return result.sInfo;
});
};

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;
}
}