So I am trying to implement a subset of LISP using JavaScript. I am stuck on two things related to lambdas.
How to implement the ability to create a lambda and at the same time feed it the arguments and have it immediately evaluated? For example:
((lambda(x)(* x 2)) 3)
For now I hard-coded this functionality in my eval-loop like this:
else if (isArray(expr)){
if (expr[0][0] === 'lambda' || expr[0][0] === 'string') {
console.log("This is a special lambda");
var lambdaFunc = evaluate(expr[0], env)
var lambdaArgs = [];
for(var i = 1; i < expr.length; i++){
lambdaArgs.push(expr[i]);
}
return lambdaFunc.apply(this, lambdaArgs);
}
Now this works, and if I write the above lambda with the parameter it will evaluate to 6, however, I am wondering if there is any smarter way to implement this?
If a lambda is instead bound to a symbol, for example:
(define fib (lambda(n)
(if (< n 2) 1
(+ (fib (- n 1))(fib (- n 2)))
)))
In this case, the (define fib) part will be evaluated by the eval-loop first, just as if fib was simply being assigned a number:
else if (expr[0] === 'define') { // (define var value)
console.log(expr + " is a define statement");
var newVar = expr[1];
var newVal = evaluate(expr[2], env);
env.add(newVar, newVal);
return env;
}
And the lambda-function is being created like this:
else if (expr[0] === 'lambda') { // (lambda args body)
console.log(expr + " is a lambda statement");
var args = expr[1];
var body = expr[2];
return createLambda(args, body, env);
}
Separate function to create the lambda:
function createLambda(args, body, env){ // lambda args body
function runLambda(){
var lambdaEnvironment = new environment(env, "lambda environment");
for (var i = 0; i < arguments.length; i++){
lambdaEnvironment.add(args[i], evaluate(arguments[i], env));
}
return evaluate(body, lambdaEnvironment);
}
return runLambda;
}
This works fine for lambdas such as:
(define range (lambda (a b)
(if (= a b) (quote ())
(cons a (range (+ a 1) b)))))
(define fact (lambda (n)
(if (<= n 1) 1
(* n (fact (- n 1))))))
For example, (range 0 10) returns a list from 0 to 10.
But if I try a lambda within a lambda, it does not work. For example:
(define twice (lambda (x) (* 2 x)))
(define repeat (lambda (f) (lambda (x) (f (f x)))))
I would expect the following to return 40:
((repeat twice) 10)
But instead, it returns a list looking like this:
function runLambda(){ var lambdaEnvironment = new environment(env, "lambda
environment"); for (var i = 0; i < arguments.length; i++){
lambdaEnvironment.add(args[i], evaluate(arguments[i], env)); } return
evaluate(body, lambdaEnvironment); },10
Any ideas what might be missing here?
Full JavaScript;
//functions for parsing invoice String
function parse(exp) {
return readFromTokes(tokenize(exp));//code
}
function isNumeric(arg){
return !isNaN(arg);
}
function isArray(obj){
return !!obj && obj.constructor === Array;
}
function readFromTokes(exp){
//Create abstract syntax tree
if (exp.length == 0) {
}
var token = exp.shift();
if (token == '('){
var L = [];
while (exp[0] != ')') {
L.push(readFromTokes(exp));
}
exp.shift(); //remove end paranthesis
return L;
} else {
if (token == ')') {
console.log("Unexpected )");
} else {
return atom(token);
}
}
}
function tokenize(exp){
//Convert a program in form of a string into an array (list)
var re = /\(/g;
var re2 = /\)/g;
exp = exp.replace(re, " ( ");
exp = exp.replace(re2, " ) ");
exp = exp.replace(/\s+/g, ' ');
exp = exp.trim().split(" ");
return exp;
}
function atom(exp){
if (isNumeric(exp)) {
return parseInt(exp); //A number is a number
} else {
return exp; //Everything else is a symbol
}
}
function environment(parentEnvironment, name){
var bindings = [];
var parent = parentEnvironment;
var name = name;
function add(variable, value){
console.log("variable: " + variable + " value: " + value);
bindings.push([variable, value]);
}
function printName(){
console.log(name);
}
function print() {
console.log("printing environment: ")
for (var i = 0; i < bindings.length; i++){
console.log(bindings[i][0] + " " + bindings[i][1]);
}
}
function get(variable){
for (var i = 0; i < bindings.length; i++){
if (variable == bindings[i][0]){
return bindings[i][1];
}
}
if (parent != null){
return parent.get(variable);
} else {
console.log("No such variable");
return false;
}
}
function getParent(){
return parent;
}
this.add = add;
this.get = get;
this.getParent = getParent;
this.print = print;
this.printName = printName;
return this;
}
function addPrimitives(env){
env.add("+", function() {var s = 0; for (var i = 0; i<arguments.length;i++){ s += arguments[i];} return s});
env.add("-", function() {var s = arguments[0]; for (var i = 1; i<arguments.length;i++){ s -= arguments[i];} return s});
env.add("*", function() {var s = 1; for (var i = 0; i<arguments.length;i++){ s *= arguments[i];} return s});
env.add("/", function(x, y) { return x / y });
env.add("false", false);
env.add("true", true);
env.add(">", function(x, y){ return (x > y) });
env.add("<", function(x, y){ return (x < y) });
env.add("=", function(x, y){ return (x === y)});
env.add(">=", function(x, y){ if (x >= y){return true;} else {return false;}});
env.add("<=", function(x, y){ if (x <= y){return true;} else {return false;}});
env.add("eq?", function() {var s = arguments[0]; var t = true; for(var i = 1; i<arguments.length; i++){ if (arguments[i] != s) {t = false }} return t;});
env.add("cons", function(x, y) { var temp = [x]; return temp.concat(y); });
env.add("car", function(x) { return x[0]; });
env.add("cdr", function(x) { return x.slice(1); });
env.add("list", function () { return Array.prototype.slice.call(arguments); });
env.add("list?", function(x) {return isArray(x); });
env.add("null", null);
env.add("null?", function (x) { return (!x || x.length === 0); });
}
function createLambda(args, body, env){ // lambda args body
function runLambda(){
var lambdaEnvironment = new environment(env, "lambda environment");
for (var i = 0; i < arguments.length; i++){
lambdaEnvironment.add(args[i], evaluate(arguments[i], env));
}
return evaluate(body, lambdaEnvironment);
}
return runLambda;
}
function evaluate(expr, env) {
console.log(expr + " has entered evaluate loop");
if (typeof expr === 'string') {
console.log(expr + " is a symbol");
return env.get(expr);
} else if (typeof expr === 'number') {
console.log(expr + " is a number");
return expr;
} else if (expr[0] === 'define') { // (define var value)
console.log(expr + " is a define statement");
var newVar = expr[1];
var newVal = evaluate(expr[2], env);
env.add(newVar, newVal);
return env;
} else if (expr[0] === 'lambda') { // (lambda args body)
console.log(expr + " is a lambda statement");
var args = expr[1];
var body = expr[2];
return createLambda(args, body, env);
} else if (expr[0] === 'quote') {
return expr[1];
} else if (expr[0] === 'cond'){
console.log(expr + " is a conditional");
for (var i = 1; i < expr.length; i++){
var temp = expr[i];
if (evaluate(temp[0], env)) {
console.log(temp[0] + " is evaluated as true");
return evaluate(temp[1], env);
}
}
console.log("no case was evaluated as true");
return;
} else if (expr[0] === 'if') {
console.log(expr + "is an if case");
return function(test, caseyes, caseno, env){
if (test) {
return evaluate(caseyes, env);
} else {
return evaluate(caseno, env);
}
}(evaluate(expr[1], env), expr[2], expr[3], env);
} else if (typeof expr[0] === 'string'){
console.log(expr + " is a function call");
var lispFunc = env.get(expr[0]);
var lispFuncArgs = [];
for(var i = 1; i < expr.length; i++){
lispFuncArgs.push(evaluate(expr[i], env));
}
return lispFunc.apply(this, lispFuncArgs);
} else if (isArray(expr)){
if (expr[0][0] === 'lambda' || expr[0][0] === 'string') {
console.log("This is a special lambda");
var lambdaFunc = evaluate(expr[0], env)
var lambdaArgs= [];
for(var i = 1; i < expr.length; i++){
lambdaArgs.push(expr[i]);
}
return lambdaFunc.apply(this, lambdaArgs);
} else {
console.log(expr + " is a list");
var evaluatedList = [];
for(var i = 0; i < expr.length; i++){
evaluatedList.push(evaluate(expr[i], env));
}
return evaluatedList;
}
} else {
console.log(expr + " cannot be interpreted");
}
}
var globalEnvironment = new environment(null, "Global");
addPrimitives(globalEnvironment);
function start(string) {
return evaluate(parse(string), globalEnvironment);
}
var output = function (string) {
try {
document.getElementById('debugdiv').innerHTML = start(string);
} catch (e) {
document.getElementById('debugdiv').innerHTML = e.name + ': ' + e.message;
}
};
Full HTML;
<html>
<head>
<meta content="text/html; charset=UTF-8" http-equiv="Content-Script-Type" content="text/javascript">
<title>LISP in JavaScript</title>
<script type="text/javascript" src="lisp.js"></script>
</head>
<body>
<form id="repl" name="repl" action="parse(prompt.value)">
lisp==>
<input id="prompt" size="200" value="" name="prompt" maxlength="512">
<br>
<input type=button style="width:60px;height:30px" name="btnEval" value="eval" onclick="output(prompt.value)">
<br>
</form>
<div id="debugdiv" style="background-color:orange;width=100px;height=20px">
</div>
</body>
</html>
Further thoughts, suggestions and comments are of course also welcome.
Thank you!
You don't inspect the structure of the operand to see if it's a lambda you eval the operand. The standard way of eval is to check if it's primitive type, then check for special forms and macros, then eval the operator and operands before applying.
Just remove the part where expr[0][0] === 'lambda' || expr[0][0] === 'string' is true and instead of just returning the evaluation of the form you need to apply the operand:
else if (isArray(expr)){
const fn = evaluate(expr[0], env);
const evaluatedList = [];
for(var i = 1; i < expr.length; i++){
evaluatedList.push(evaluate(expr[i], env));
}
return fn(...evaluatedList);
}
The createLambda is wrong since you are evaluating the arguments in the wrong environment. This would be correct since the arguments are already evaluated:
function createLambda(args, body, env){ // lambda args body
function runLambda(){
const lambdaEnvironment = new environment(env, "lambda environment");
for (let i = 0; i < arguments.length; i++){
lambdaEnvironment.add(args[i], arguments[i]);
}
return evaluate(body, lambdaEnvironment);
}
return runLambda;
}
Related
I am practicing data structures on JavaScript and was writing an algorithm to convert an infix expression to postfix using a stack linkedlist. I am sure of my logic, but I did have to write a isLetter() method which might be causing problems and with precedence()
My result is: Postfix Expression: +21-43--56/
Answer should be: Postfix Expression: 12+34--65-/
My result is: Postfix Expression: *-^^12242
Answer should be: Postfix Expression: 24*221^^-
class Node {
/* Creates a node with the given element and next node */
constructor(e, n) {
this.data = e;
this.next = n;
}
}
class LinkedStack {
/* Creates an empty stack */
constructor() {
this.top = null;
this.size = 0;
}
push = (elem) => {
let v = new Node(elem, this.top);
this.top = v;
this.size++;
};
length = () => {
return this.size;
};
isEmpty = () => {
return this.size === 0;
};
peek = () => {
if (this.isEmpty()) {
console.log("Empty Stack");
}
return this.top.data;
};
pop = () => {
if (this.isEmpty()) {
console.log("Empty Stack");
}
const temp = this.top.data;
this.top = this.top.next;
this.size--;
return temp;
};
toString = () => {
let s = "[";
let cur = null;
if (this.length() > 0) {
cur = this.top;
s += cur.data;
}
if (this.length() > 1) {
for (let i = 1; i <= this.length() - 1; i++) {
cur = cur.next;
s += ", " + cur.data;
}
s += "]";
return s;
}
};
}
class PostfixToInfix {
intoPost = (s = " ") => {
let stack = new LinkedStack();
let output = "";
for (let cur = 0; cur < s.length; cur++) {
let c = s.charAt(cur);
if (this.isLetter(c)) {
output = output + c;
} else if (c === "(") {
stack.push(c);
} else if (c === ")") {
let topToken = stack.peek();
while (topToken != "(") {
output = output + stack.pop();
topToken = stack.peek();
}
stack.pop();
} else {
while (!stack.isEmpty() && this.precedence(stack.peek(), c)) {
output = output + stack.pop();
}
stack.push(c);
}
}
while (!stack.isEmpty()) {
output = output + stack.pop();
}
return output;
};
precedence = (stackV, curV) => {
return this.stackValues(stackV) > this.curValues(curV);
};
isLetter = (char = "") => {
return char.toUpperCase() != char.toLowerCase();
};
stackValues = (c = "") => {
if (c === "(") {
return 0;
} else if (c === "^") {
return 5;
} else if (c === "*" || c === "/" || c === "%") {
return 4;
} else if (c === "+" || c === "-") {
return 2;
}
return 0;
};
curValues = (c = "") => {
if (c === "(") {
return 100;
} else if (c == ")") {
return 0;
} else if (c === "^") {
return 6;
} else if (c === "*" || c === "/" || c === "%") {
return 3;
} else if (c === "+" || c === "-") {
return 1;
}
return 0;
};
}
let pToIn = new PostfixToInfix();
let sample1 = "(((1+2)-(3-4))/(6-5))";
console.log("Infix Expression: " + sample1);
/* My result is: Postfix Expression: +21-43--56/
/* Answer should be: Postfix Expression: 12+34--65-/
*/
console.log("Postfix Expression: " + pToIn.intoPost(sample1));
/* My result is: Postfix Expression: *-^^12242
/* Answer should be: Postfix Expression: 24*221^^-
*/
let sample2 = "2*4-2^2^1";
console.log("Infix Expression: " + sample2);
console.log("Postfix Expression : " + pToIn.intoPost(sample2));
/*
let stack = new LinkedStack();
stack.push(9);
console.log(stack.pop() + " was popped"); // 9 was popped
stack.push(12);
stack.push(15);
stack.push(7);
console.log("Is Stack Empty? " + stack.isEmpty()); // Is Stack Empty? false
console.log("Stack Length: " + stack.length()); // Stack Length: 2
console.log("Top value: " + stack.peek()); // Top value: 15
console.log("Stack Content: " + stack.toString()); // Stack content [15, 12] */
The basic problem is that your input contains digits, which don't qualify as letters using the test in isLetter. You should change that to a different function which also returns true for digits, perhaps by using a regex match.
However, you could just use the default action to ensure that letters and digits (and other unknown characters in the input) get passed to the output. It's a bit inefficient, since it involves pushing them to the stack and immediately popping them, but it dramatically simplifies the inner loop.
Here's a simplified version of your code, which is not perfect (it still doesn't react to syntax errors in the input). I eliminated a bunch of unnecessary functions, including isLetter; note that the reduction loop stops when the incoming character has the same precedence as the character on the top of the stack, which can only happen if the incoming character is a close parenthesis.
class Node {
/* Creates a node with the given element and next node */
constructor(e, n) {
this.data = e;
this.next = n;
}
}
class LinkedStack {
/* Creates an empty stack */
constructor() {
this.top = null;
this.size = 0;
}
push = (elem) => {
let v = new Node(elem, this.top);
this.top = v;
this.size++;
};
length = () => {
return this.size;
};
isEmpty = () => {
return this.size === 0;
};
peek = () => {
if (this.isEmpty()) {
console.log("Empty Stack");
}
return this.top.data;
};
pop = () => {
if (this.isEmpty()) {
console.log("Empty Stack");
}
const temp = this.top.data;
this.top = this.top.next;
this.size--;
return temp;
};
toString = () => {
let s = "[";
let cur = null;
if (this.length() > 0) {
cur = this.top;
s += cur.data;
}
if (this.length() > 1) {
for (let i = 1; i <= this.length() - 1; i++) {
cur = cur.next;
s += ", " + cur.data;
}
s += "]";
return s;
}
};
}
class PostfixToInfix {
intoPost = (s = " ") => {
let stack = new LinkedStack();
stack.push('$');
let output = "";
for (let cur = 0; cur < s.length; cur++) {
let c = s.charAt(cur);
let prec = this.curValues(c);
while (this.stackValues(stack.peek()) > prec) {
output = output + stack.pop();
}
if (c == ')') {
if (stack.peek() != '$') {
stack.pop();
}
} else {
stack.push(c);
}
}
while (stack.peek() != '$') {
output = output + stack.pop();
}
return output;
};
stackValues = (c = "") => {
if (c === "(" || c == '$') {
return 0;
} else if (c === "^") {
return 15;
} else if (c === "*" || c === "/" || c === "%") {
return 14;
} else if (c === "+" || c === "-") {
return 12;
}
return 90;
};
curValues = (c = "") => {
if (c === "(") {
return 100;
} else if (c == ")") {
return 0;
} else if (c === "^") {
return 16;
} else if (c === "*" || c === "/" || c === "%") {
return 13;
} else if (c === "+" || c === "-") {
return 11;
}
return 90;
};
}
let pToIn = new PostfixToInfix();
let sample1 = "(((1+2)-(3-4))/(6-5))";
console.log("Infix Expression: " + sample1);
/* My result is: Postfix Expression: +21-43--56/
/* Answer should be: Postfix Expression: 12+34--65-/
*/
console.log("Postfix Expression: " + pToIn.intoPost(sample1));
/* My result is: Postfix Expression: *-^^12242
/* Answer should be: Postfix Expression: 24*221^^-
*/
let sample2 = "2*4-2^2^1";
console.log("Infix Expression: " + sample2);
console.log("Postfix Expression : " + pToIn.intoPost(sample2));
/*
let stack = new LinkedStack();
stack.push(9);
console.log(stack.pop() + " was popped"); // 9 was popped
stack.push(12);
stack.push(15);
stack.push(7);
console.log("Is Stack Empty? " + stack.isEmpty()); // Is Stack Empty? false
console.log("Stack Length: " + stack.length()); // Stack Length: 2
console.log("Top value: " + stack.peek()); // Top value: 15
console.log("Stack Content: " + stack.toString()); // Stack content [15, 12] */
I have a string that contains digit at the end. I want to increase the digit part by 1 when some actions happened.
e.g.
var myString = 'AA11111'
increaseStringValue(myString)
# myString new value => 'AA11112'
also how can I increase chars when string value reached to 'AA99999' so new value of string will be 'AB11111'?
You can split char and digit parts so you can handle them separately.
like:
function increaseStringValue(str){
let charPart = str.substring(0,2);
let digitPart = str.substring(2);
digitPart = +digitPart+1
if(digitPart >= 99999){
digitPart = 11111;
if(charPart[1] == 'Z'){
if(charPart[0] == 'Z'){
throw 'Overflow happened'
}
charPart = String.fromCharCode(charPart.charCodeAt(0)+1) + 'A'
}else{
charPart = charPart[0] + String.fromCharCode(charPart.charCodeAt(1)+1)
}
}
return charPart + digitPart;
}
increaseStringValue('AA11111'); // 'AA11112'
increaseStringValue('AA99999'); // 'AB11111'
increaseStringValue('AZ99999'); // 'BA11111'
increaseStringValue('ZZ99999'); // Exception: Overflow happened
This links will be helpful for you:
ASCII CODES
what is a method that can be used to increment letters?
Edit:
Following function will be suite for unknown length string with dynamic position of char and digit.
function increaseStringValue(str) {
let charOverFlowed = true;
let result = ""
for (let i = str.length - 1; i >= 0; i--) {
let currentChar = str[i];
if ('123456789'.indexOf(currentChar) !== -1) {
if (charOverFlowed) {
currentChar = +currentChar + 1
charOverFlowed = false;
}
if (currentChar > 9) {
currentChar = 1;
charOverFlowed = true;
}
} else if (charOverFlowed) {
currentChar = String.fromCharCode(currentChar.charCodeAt(0) + 1)
charOverFlowed = false;
if (currentChar > 'Z') {
if(i == 0){
throw 'Overflow Happened'
}
currentChar = 'A'
charOverFlowed = true
}
}
result = currentChar + result;
}
return result;
}
increaseStringValue('AAAACA')
// "AAAACB"
increaseStringValue('AAAACA1111')
// "AAAACA1112"
increaseStringValue('A1')
// "A2"
increaseStringValue('Z')
// Uncaught Overflow Happened
increaseStringValue('A1999')
// "A2111"
function increaseStringValue(myString){
return myString.replace(/\d+/ig, function(a){ return a*1+1;});
}
console.log(increaseStringValue("asg61"));
And for next question:
function increaseStringValue(myString){
return myString.replace(/(A)(\d+)/ig, function(a, b, c){
var r = c*1+1; return r==99999+1?"B11111":"A"+r;
});
}
console.log(increaseStringValue("AA99999"));
And Whole way:
function increaseStringValue(myString){
return myString.replace(/([a-e])(\d+)/ig, function(a, b, c){
var r = c*1+1; return r==99999+1?String.fromCharCode(a.charCodeAt(0)+1)+"11111":b+r;
});
}
console.log(increaseStringValue("AB99999"));
Please find the snippet useful. If this is what you are expecting.
let stringNum = 'AA11111';//initialise string
let clickTriggered = ()=>{
let startString = "AA";
let newNum = ()=>{
let numberPart = stringNum.split("AA")[1];
let lastChar = stringNum[stringNum.length-1];
return Number(numberPart) != NaN || Number(numberPart) <= 99999 ? Number(numberPart)+1 : 11111;
};
stringNum = `${startString}${newNum()}`
console.log(stringNum)
}
<h1 onclick="clickTriggered()">click here</h1>
You can use String#replace and provide your increment logic in the function callback of the string#replace.
const increaseStringValue = (str) => str.replace(/\d+$/, n => n === '99999' ? 11111 : +n + 1);
console.log(increaseStringValue('AA99999'));
console.log(increaseStringValue('AA11315'));
console.log(increaseStringValue('AA11111'));
I solve this with this solution
let app = new Vue({
el: '#app',
data: {
text: "AA995"
},
methods: {
addOneString: function(str) {
var alphabet = 'abcdefghijklmnopqrstuvwxyz',
length = alphabet.length,
result = str,
i = str.length,
value = str;
while(i >= 0) {
var last = str.charAt(--i),
next = '',
carry = false;
if (isNaN(last)) {
index = alphabet.indexOf(last.toLowerCase());
if (index === -1) {
next = last;
carry = true;
}
else {
var isUpperCase = last === last.toUpperCase();
next = alphabet.charAt((index + 1) % length);
if (isUpperCase) {
next = next.toUpperCase();
}
carry = index + 1 >= length;
if (carry && i === 0) {
var added = isUpperCase ? 'A' : 'a';
result = added + next + result.slice(1);
break;
}
}
}
else {
next = +last + 1;
if(next > 9) {
next = 0;
carry = true;
}
if (carry && i === 0) {
result = '1' + next + result.slice(1);
break;
}
}
result = result.slice(0, i) + next + result.slice(i + 1);
if (!carry) {
break;
}
}
console.log("result",result);
if (value !== result ) this.text = result;
}
}
})
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<div class="container" id="app">
<button #click="addOneString(text)">Add one</button>
</br>
<p> {{text}} </p>
</div>
On a MouseEvent instance, we have a property called path, it might look like this:
Does anybody know if there is a reliable way to translate this path array into an XPath? I assume that this path data is the best data to start from? Is there a library I can use to do the conversion?
This library looks promising, but it doesn't use the path property of an event: https://github.com/johannhof/xpath-dom
There's not a unique XPath to a node, so you'll have to decide what's the most appropriate way of constructing a path. Use IDs where available? Numeral position in the document? Position relative to other elements?
See getPathTo() in this answer for one possible approach.
PS: Taken from Javascript get XPath of a node
Another option is to use SelectorGadget from below link
https://dv0akt2986vzh.cloudfront.net/unstable/lib/selectorgadget.js
The actual code for the DOM path is at
https://dv0akt2986vzh.cloudfront.net/stable/lib/dom.js
Usage: on http://google.com
elem = document.getElementById("q")
predict = new DomPredictionHelper()
predict.pathOf(elem)
// gives "body.default-theme.des-mat:nth-child(2) div#_Alw:nth-child(4) form#f:nth-child(2) div#fkbx:nth-child(2) input#q:nth-child(2)"
predict.predictCss([elem],[])
// gives "#q"
CODE if link goes down
// Copyright (c) 2008, 2009 Andrew Cantino
// Copyright (c) 2008, 2009 Kyle Maxwell
function DomPredictionHelper() {};
DomPredictionHelper.prototype = new Object();
DomPredictionHelper.prototype.recursiveNodes = function(e){
var n;
if(e.nodeName && e.parentNode && e != document.body) {
n = this.recursiveNodes(e.parentNode);
} else {
n = new Array();
}
n.push(e);
return n;
};
DomPredictionHelper.prototype.escapeCssNames = function(name) {
if (name) {
try {
return name.replace(/\s*sg_\w+\s*/g, '').replace(/\\/g, '\\\\').
replace(/\./g, '\\.').replace(/#/g, '\\#').replace(/\>/g, '\\>').replace(/\,/g, '\\,').replace(/\:/g, '\\:');
} catch(e) {
console.log('---');
console.log("exception in escapeCssNames");
console.log(name);
console.log('---');
return '';
}
} else {
return '';
}
};
DomPredictionHelper.prototype.childElemNumber = function(elem) {
var count = 0;
while (elem.previousSibling && (elem = elem.previousSibling)) {
if (elem.nodeType == 1) count++;
}
return count;
};
DomPredictionHelper.prototype.pathOf = function(elem){
var nodes = this.recursiveNodes(elem);
var self = this;
var path = "";
for(var i = 0; i < nodes.length; i++) {
var e = nodes[i];
if (e) {
path += e.nodeName.toLowerCase();
var escaped = e.id && self.escapeCssNames(new String(e.id));
if(escaped && escaped.length > 0) path += '#' + escaped;
if(e.className) {
jQuery.each(e.className.split(/ /), function() {
var escaped = self.escapeCssNames(this);
if (this && escaped.length > 0) {
path += '.' + escaped;
}
});
}
path += ':nth-child(' + (self.childElemNumber(e) + 1) + ')';
path += ' '
}
}
if (path.charAt(path.length - 1) == ' ') path = path.substring(0, path.length - 1);
return path;
};
DomPredictionHelper.prototype.commonCss = function(array) {
try {
var dmp = new diff_match_patch();
} catch(e) {
throw "Please include the diff_match_patch library.";
}
if (typeof array == 'undefined' || array.length == 0) return '';
var existing_tokens = {};
var encoded_css_array = this.encodeCssForDiff(array, existing_tokens);
var collective_common = encoded_css_array.pop();
jQuery.each(encoded_css_array, function(e) {
var diff = dmp.diff_main(collective_common, this);
collective_common = '';
jQuery.each(diff, function() {
if (this[0] == 0) collective_common += this[1];
});
});
return this.decodeCss(collective_common, existing_tokens);
};
DomPredictionHelper.prototype.tokenizeCss = function(css_string) {
var skip = false;
var word = '';
var tokens = [];
var css_string = css_string.replace(/,/, ' , ').replace(/\s+/g, ' ');
var length = css_string.length;
var c = '';
for (var i = 0; i < length; i++){
c = css_string[i];
if (skip) {
skip = false;
} else if (c == '\\') {
skip = true;
} else if (c == '.' || c == ' ' || c == '#' || c == '>' || c == ':' || c == ',') {
if (word.length > 0) tokens.push(word);
word = '';
}
word += c;
if (c == ' ' || c == ',') {
tokens.push(word);
word = '';
}
}
if (word.length > 0) tokens.push(word);
return tokens;
};
DomPredictionHelper.prototype.decodeCss = function(string, existing_tokens) {
var inverted = this.invertObject(existing_tokens);
var out = '';
jQuery.each(string.split(''), function() {
out += inverted[this];
});
return this.cleanCss(out);
};
// Encode css paths for diff using unicode codepoints to allow for a large number of tokens.
DomPredictionHelper.prototype.encodeCssForDiff = function(strings, existing_tokens) {
var codepoint = 50;
var self = this;
var strings_out = [];
jQuery.each(strings, function() {
var out = new String();
jQuery.each(self.tokenizeCss(this), function() {
if (!existing_tokens[this]) {
existing_tokens[this] = String.fromCharCode(codepoint++);
}
out += existing_tokens[this];
});
strings_out.push(out);
});
return strings_out;
};
DomPredictionHelper.prototype.simplifyCss = function(css, selected_paths, rejected_paths) {
var self = this;
var parts = self.tokenizeCss(css);
var best_so_far = "";
if (self.selectorGets('all', selected_paths, css) && self.selectorGets('none', rejected_paths, css)) best_so_far = css;
for (var pass = 0; pass < 4; pass++) {
for (var part = 0; part < parts.length; part++) {
var first = parts[part].substring(0,1);
if (self.wouldLeaveFreeFloatingNthChild(parts, part)) continue;
if ((pass == 0 && first == ':') || // :nth-child
(pass == 1 && first != ':' && first != '.' && first != '#' && first != ' ') || // elem, etc.
(pass == 2 && first == '.') || // classes
(pass == 3 && first == '#')) // ids
{
var tmp = parts[part];
parts[part] = '';
var selector = self.cleanCss(parts.join(''));
if (selector == '') {
parts[part] = tmp;
continue;
}
if (self.selectorGets('all', selected_paths, selector) && self.selectorGets('none', rejected_paths, selector)) {
best_so_far = selector;
} else {
parts[part] = tmp;
}
}
}
}
return self.cleanCss(best_so_far);
};
DomPredictionHelper.prototype.wouldLeaveFreeFloatingNthChild = function(parts, part) {
return (((part - 1 >= 0 && parts[part - 1].substring(0, 1) == ':') &&
(part - 2 < 0 || parts[part - 2] == ' ') &&
(part + 1 >= parts.length || parts[part + 1] == ' ')) ||
((part + 1 < parts.length && parts[part + 1].substring(0, 1) == ':') &&
(part + 2 >= parts.length || parts[part + 2] == ' ') &&
(part - 1 < 0 || parts[part - 1] == ' ')));
};
DomPredictionHelper.prototype.cleanCss = function(css) {
return css.replace(/\>/, ' > ').replace(/,/, ' , ').replace(/\s+/g, ' ').replace(/^\s+|\s+$/g, '').replace(/,$/, '');
};
DomPredictionHelper.prototype.getPathsFor = function(arr) {
var self = this;
var out = [];
jQuery.each(arr, function() {
if (this && this.nodeName) {
out.push(self.pathOf(this));
}
})
return out;
};
DomPredictionHelper.prototype.predictCss = function(s, r) {
var self = this;
if (s.length == 0) return '';
var selected_paths = self.getPathsFor(s);
var rejected_paths = self.getPathsFor(r);
var css = self.commonCss(selected_paths);
var simplest = self.simplifyCss(css, selected_paths, rejected_paths);
// Do we get off easy?
if (simplest.length > 0) return simplest;
// Okay, then make a union and possibly try to reduce subsets.
var union = '';
jQuery.each(s, function() {
union = self.pathOf(this) + ", " + union;
});
union = self.cleanCss(union);
return self.simplifyCss(union, selected_paths, rejected_paths);
};
DomPredictionHelper.prototype.fragmentSelector = function(selector) {
var self = this;
var out = [];
jQuery.each(selector.split(/\,/), function() {
var out2 = [];
jQuery.each(self.cleanCss(this).split(/\s+/), function() {
out2.push(self.tokenizeCss(this));
});
out.push(out2);
});
return out;
};
// Everything in the first selector must be present in the second.
DomPredictionHelper.prototype.selectorBlockMatchesSelectorBlock = function(selector_block1, selector_block2) {
for (var j = 0; j < selector_block1.length; j++) {
if (jQuery.inArray(selector_block1[j], selector_block2) == -1) {
return false;
}
}
return true;
};
// Assumes list is an array of complete CSS selectors represented as strings.
DomPredictionHelper.prototype.selectorGets = function(type, list, the_selector) {
var self = this;
var result = true;
if (list.length == 0 && type == 'all') return false;
if (list.length == 0 && type == 'none') return true;
var selectors = self.fragmentSelector(the_selector);
var cleaned_list = [];
jQuery.each(list, function() {
cleaned_list.push(self.fragmentSelector(this)[0]);
});
jQuery.each(selectors, function() {
if (!result) return;
var selector = this;
jQuery.each(cleaned_list, function(pos) {
if (!result || this == '') return;
if (self._selectorGets(this, selector)) {
if (type == 'none') result = false;
cleaned_list[pos] = '';
}
});
});
if (type == 'all' && cleaned_list.join('').length > 0) { // Some candidates didn't get matched.
result = false;
}
return result;
};
DomPredictionHelper.prototype._selectorGets = function(candidate_as_blocks, selector_as_blocks) {
var cannot_match = false;
var position = candidate_as_blocks.length - 1;
for (var i = selector_as_blocks.length - 1; i > -1; i--) {
if (cannot_match) break;
if (i == selector_as_blocks.length - 1) { // First element on right.
// If we don't match the first element, we cannot match.
if (!this.selectorBlockMatchesSelectorBlock(selector_as_blocks[i], candidate_as_blocks[position])) cannot_match = true;
position--;
} else {
var found = false;
while (position > -1 && !found) {
found = this.selectorBlockMatchesSelectorBlock(selector_as_blocks[i], candidate_as_blocks[position]);
position--;
}
if (!found) cannot_match = true;
}
}
return !cannot_match;
};
DomPredictionHelper.prototype.invertObject = function(object) {
var new_object = {};
jQuery.each(object, function(key, value) {
new_object[value] = key;
});
return new_object;
};
DomPredictionHelper.prototype.cssToXPath = function(css_string) {
var tokens = this.tokenizeCss(css_string);
if (tokens[0] && tokens[0] == ' ') tokens.splice(0, 1);
if (tokens[tokens.length - 1] && tokens[tokens.length - 1] == ' ') tokens.splice(tokens.length - 1, 1);
var css_block = [];
var out = "";
for(var i = 0; i < tokens.length; i++) {
if (tokens[i] == ' ') {
out += this.cssToXPathBlockHelper(css_block);
css_block = [];
} else {
css_block.push(tokens[i]);
}
}
return out + this.cssToXPathBlockHelper(css_block);
};
// Process a block (html entity, class(es), id, :nth-child()) of css
DomPredictionHelper.prototype.cssToXPathBlockHelper = function(css_block) {
if (css_block.length == 0) return '//';
var out = '//';
var first = css_block[0].substring(0,1);
if (first == ',') return " | ";
if (jQuery.inArray(first, [':', '#', '.']) != -1) {
out += '*';
}
var expressions = [];
var re = null;
for(var i = 0; i < css_block.length; i++) {
var current = css_block[i];
first = current.substring(0,1);
var rest = current.substring(1);
if (first == ':') {
// We only support :nth-child(n) at the moment.
if (re = rest.match(/^nth-child\((\d+)\)$/))
expressions.push('(((count(preceding-sibling::*) + 1) = ' + re[1] + ') and parent::*)');
} else if (first == '.') {
expressions.push('contains(concat( " ", #class, " " ), concat( " ", "' + rest + '", " " ))');
} else if (first == '#') {
expressions.push('(#id = "' + rest + '")');
} else if (first == ',') {
} else {
out += current;
}
}
if (expressions.length > 0) out += '[';
for (var i = 0; i < expressions.length; i++) {
out += expressions[i];
if (i < expressions.length - 1) out += ' and ';
}
if (expressions.length > 0) out += ']';
return out;
};
I'm trying to simplify this function, as there can be multiple type of data objcts and for each type there is also a male and a female version.
The number and name of the elements in the objects are always identical.
As you see, most of the code is repeating...
function calculate(type, j, value, s) {
for (var i = j; i > 4; i--) {
if (type == 'weight') {
if (s == 'f') {
if (weightFemale.hasOwnProperty(i)) {
var m = weightFemale[i][0],
l = weightFemale[i][1],
s = weightFemale[i][2];
return getcalc( m,l,s );
}
}
else {
if (weightMale.hasOwnProperty(i)) {
var m = weightMale[i][0],
l = weightMale[i][1],
s = weightMale[i][2];
return getcalc( m,l,s );
}
}
}
else if (type == 'length') {
if (s == 'f') {
if (lengthFemale.hasOwnProperty(i)) {
var m = lengthFemale[i][0],
l = lengthFemale[i][1],
s = lengthFemale[i][2],
return getcalc( m,l,s );
}
}
else {
if (lengthMale.hasOwnProperty(i)) {
var m = lengthMale[i][0],
l = lengthMale[i][1],
s = lengthMale[i][2],
return getcalc( m,l,s );
}
}
}
}
return false;
}
How can I simplify the if/else-parts for the type and the sex?
Since you are doing the same thing to each object , just make your conditionals define a single object reference and only call calculation once.
Something like:
var obj;
if (type == 'weight') {
obj = s == 'f' ? weightFemale : weightMale;
} else if (type == 'length') {
obj = s == 'f' ? lengthFemale : lengthMale;
}
if (obj.hasOwnProperty(i)) {
var m = obj[i][0],
l = obj[i][1],
s = obj[i][2];
return getcalc(m, l, s);
}
I would create a own function and create a switch
switch(type) {
case "weight":
getValues();
break;
case "length":
getValues();
break;
}
Ask yourself, what are the parts that are very similar? It looks if .hasOwnProperty() is true, you get m, l, and s from the array and then call getcalc(). Start by extracting that part into a function with the differing pieces being passed in as parameters.
Another pattern is that you're using a particular array based on certain conditions. Getting the array you want can be placed into a function.
Also, not quite related to the question, but you may want to give your variables better names. This makes the code more readable and easier to reason about.
Here's what I came up with:
function getArray(type, s){
if(type == 'weight') {
return s == 'f' ? weightFemale : weightMale;
}
else if(type == 'length') {
return s == 'm' ? lengthFemale : lengthMale;
}
}
function makeCalculation(array, i) {
if(array.hasOwnProperty(i)) {
var m = lengthMale[i][0],
l = lengthMale[i][1],
s = lengthMale[i][2],
return getcalc( m,l,s );
}
}
function calculate(type, j, value, s) {
for (var i = j; i > 4; i--) {
var array = getArray(type, s);
return makeCalculation(array, i);
}
}
Firstly you can create function like this:
function _getcalc(arr, i) {
var m = arr[i][0],
l = arr[i][1],
s = arr[i][2];
return getcalc(m, l, s);
}
Then you can shorten your function like this:
function calculate(type, j, value, s) {
for (var i = j; i > 4; i--) {
switch(type) {
case 'weight':
if (s == 'f' && weightFemale.hasOwnProperty(i)) {
return _getcalc(weightFemale, i);
} else if(weightMale.hasOwnProperty(i)) {
return _getcalc(weightMale, i);
}
break;
case 'length':
if (s == 'f' && lengthFemale.hasOwnProperty(i)) {
return _getcalc(lengthFemale, i);
} else if(lengthMale.hasOwnProperty(i)) {
return _getcalc(lengthMale, i);
}
break;
}
}
return false;
}
I want to be able to pass this function either a number to return as a factorial or a number and an id for an existing element on the page, so that it can return the number as text inside my specified element. Here is my code:
function factorial(num,id){
var f=1
for (var i=2; i<=num; i++) {
f*=i;
}
if (!id) {
return f;
}
else if (id)
var msg= document.getElementById(id);
return {
msg.textContent = num + "! = " + output;
};
}
}
factorial(5,"message");
Set the element if passed then return unconditionally:
function factorial(num, id){
var f = 1;
for (var i=2; i<=num; i++) {
f *= i;
}
if (id) {
document.getElementById(id).textContent = num + "! = " + f;
}
return f;
}
alert(factorial(5));
alert(factorial(5,"message"));