Converting conditional equation from infix to prefix notation - javascript

In our application we allow users to write specific conditions and we allow them express the conditions using such notation:
(1 and 2 and 3 or 4)
Where each numeric number correspond to one specific rule/condition. Now the problem is, how should I convert it, such that the end result is something like this:
{
"$or": [
"$and": [1, 2, 3],
4
]
}
One more example:
(1 or 2 or 3 and 4)
To:
{
"$or": [
1,
2,
"$and": [3, 4]
]
}
I have written 50 over lines of tokenizer that successfully tokenized the statement into tokens and validated using stack/peek algorithm, and the tokens looks like this:
["(", "1", "and", "2", "and", "3", "or", "4", ")"]
And now how should I convert this kind of "infix notation" into "prefix notation" with the rule that and takes precedence over or?
Some pointers or keywords are greatly appreciated! What I have now doesn't really lead me to what I needed at the moment.
Some researches so far:
Smart design of a math parser?
Add missing left parentheses into equation
Equation (expression) parser with precedence?
Infix to postfix notation
Dijkstra's Shunting-yard Algorithm
Infix and postfix algorithm
EDIT
Also, user has the ability to specify any number of parentheses if they insist, such as like:
((1 or 3) and (2 or 4) or 5)
So it get translates to:
{
"$or": [{
$and": [
"$or": [1, 3],
"$or": [2, 4]
},
5
]
}
EDIT 2
I figured out the algorithm. Posted as an answer below. Thanks for helping!

Thanks for the guides guys, at least I came out with my own solution. Since this is my first time doing mathematical equation parsing, pardon me if I did it wrongly or inefficient, or help me spot the error:
Basically, here are the steps I made it happen:
Before parsing, always validate the pattern. Throw error when something is wrong.
Once validated, we do a infix notation to prefix notation conversion. This step requires "and" takes precedence over "or".
Reverse the given pattern
Do infix to postfix notation conversion. I dumb, I learn from this
Do the reverse again
The infix to prefix should be done at this stage
Build a tree from the prefix notation such that
A node always have, and maximum, two branch
Traverse down until it reach full leaves
Optimize the tree such that it merges similar operators together (such as multiple $and operators with child $and can be merged and form a shorter tree)
Mix with the given criteria set, and all done!!
Working example can be found here: http://jsfiddle.net/chaoszcat/uGKYj/3/
Working code as below:
(function() {
/**
* This is a source example of my original question on
* http://stackoverflow.com/questions/20986255/converting-conditional-equation-from-infix-to-prefix-notation
*
* This is my solution and use it at your own risk
* #author Lionel Chan <chaoszcat[at]gmail.com>
*/
/**
* isNumeric, from jQuery. Duplicated here to make this js code pure
* #param {mix} n Test subject
* #returns {boolean} true if it's numeric
*/
function isNumeric(n) {
return !isNaN(parseFloat(n))&&isFinite(n);
}
/**
* Node class - represent a operator or numeric node
* #param {string} token The token string, operator "and", "or", or numeric value
*/
function Node(token) {
this.parent = null;
this.children = []; //one node has two children at most
this.token = token;
this.is_operator = token === 'and' || token === 'or';
this.is_numeric = !this.is_operator;
this.destroyed = false;
}
Node.prototype = {
isOperator: function() { return this.is_operator;},
isNumeric: function() { return this.is_numeric;},
//While building tree, a node is full if there are two children
isFull: function() {
return this.children.length >= 2;
},
addChild: function(node) {
node.parent = this;
this.children.push(node);
},
hasParent: function() {
return this.parent !== null;
},
indexOfChild: function(node) {
for (var i = 0 ; i < this.children.length ; ++i) {
if (this.children[i] === node) {
return i;
}
}
return -1;
},
removeChild: function(node) {
var idx = this.indexOfChild(node);
if (idx >= 0) {
this.children[idx].parent = null; //remove parent relationship
this.children.splice(idx, 1); //splice it out
}
},
/**
* Pass my children to the target node, and destroy myself
*
* #param {Node} node A target node
*/
passChildrenTo: function(node) {
for (var i = 0 ; i < this.children.length ; ++i) {
node.addChild(this.children[i]);
}
this.destroy();
},
//Destroy this node
destroy: function() {
this.parent.removeChild(this);
this.children = null;
this.destroyed = true;
}
};
/**
* Tree class - node manipulation
* #param {array} prefixTokens The converted, prefix-notated tokens
*/
function Tree(prefixTokens) {
this.buildTree(prefixTokens);
//Optimize tree - so that the tree will merge multiple similar operators together
this.optimize(this.root);
}
Tree.prototype = {
root: null,
//Reference to the deepest operator node in the tree for next attachment point
deepestNode: null,
/**
* Render this tree with given criteria array
* #param {array} crits
* #returns {object} The built criteria
*/
render: function(crits) {
//After optimization, we build the criteria and that's all!
return this.buildCriteria(this.root, crits);
},
/**
* Build criteria from root node. Recursive
*
* #param {Node} node
* #param {array} crits
* #returns {object} of criteria
*/
buildCriteria: function(node, crits) {
var output = {},
label = '$'+node.token;
output[label] = []; //cpnditions array
for (var i = 0 ; i < node.children.length ; ++i) {
if (node.children[i].isOperator()) {
output[label].push(this.buildCriteria(node.children[i], crits));
}else{
output[label].push(crits[node.children[i].token-1]);
}
}
return output;
},
/**
* Optimize the tree, we can simplify nodes with same operator. Recursive
*
* #param {Node} node
* #void
*/
optimize: function(node) {
//note that node.children.length will keep changing since the swapping children will occur midway. Rescan is required
for (var i = 0 ; i < node.children.length ; ++i) {
if (node.children[i].isOperator()) {
this.optimize(node.children[i]);
if (node.children[i].token === node.token) {
node.children[i].passChildrenTo(node);
i = 0; //rescan this level whenever a swap occured
}
}
}
},
/**
* Build tree from raw tokens
* #param {array} tokens
*/
buildTree: function(tokens) {
for (var i = 0 ; i < tokens.length ; ++i) {
this.addNode(new Node(tokens[i]));
}
},
/**
* Add node into tree
*
* #param {Node} node
*/
addNode: function(node) {
//If no root? The first node is root
if (this.root === null) {
this.root = node;
this.deepestNode = node;
return;
}
//if deepestNode is full, traverse up until we find a node with capacity
while(this.deepestNode && this.deepestNode.isFull()) {
this.deepestNode = this.deepestNode.parent;
}
if (this.deepestNode) {
this.deepestNode.addChild(node);
}
//If the current node is an operator, we move the deepestNode cursor to it
if (node.isOperator()) {
this.deepestNode = node;
}
}
};
/**
* Main criteria parser
*/
var CriteriaParser = {
/**
* Convert raw string of pattern (1 and 2 or 3) into the object of criteria pattern
*
* #param {string} str The raw pattern
* #param {array} crits The raw list of criteria
* #returns {String|Boolean}
*/
parse: function(str, crits) {
var tokens = this.tokenize(str),
validationResult = this.validate(tokens, crits),
prefixNotation = '';
//Once succeded, we proceed to convert it to prefix notation
if (validationResult === true) {
prefixNotation = this.infixToPrefix(tokens);
return (new Tree(prefixNotation)).render(crits);
}else{
return validationResult;
}
},
/**
* Convert the infix notation of the pattern (1 and 2 or 3) into prefix notation "or and 1 2 3"
*
* Note:
* - and has higher precedence than or
*
* Steps:
* 1. Reverse the tokens array
* 2. Do infix -> postfix conversion (http://www.cs.arizona.edu/classes/cs227/spring12/infix.pdf, http://scriptasylum.com/tutorials/infix_postfix/algorithms/infix-postfix/index.htm)
* 3. Reverse the result
*
* #param {array} tokens The tokenized tokens
* #returns {array} prefix notation of pattern
*/
infixToPrefix: function(tokens) {
var reversedTokens = tokens.slice(0).reverse(), //slice to clone, so not to disturb the original array
stack = [],
output = [];
//And since it's reversed, please regard "(" as closing bracket, and ")" as opening bracket
do {
var stackTop = stack.length > 0 ? stack[stack.length-1] : null,
token = reversedTokens.shift();
if (token === 'and') {
while(stackTop === 'and') {
output.push(stack.pop());
stackTop = stack.length > 0 ? stack[stack.length-1] : null;
}
stack.push(token);
stackTop = token;
}else if (token === 'or') {
while(stackTop === 'and' || stackTop === 'or') { //and has higher precedence, so it will be popped out
output.push(stack.pop());
stackTop = stack.length > 0 ? stack[stack.length-1] : null;
}
stack.push(token);
stackTop = token;
}else if (token === '(') { //'(' is closing bracket in reversed tokens
while(stackTop !== ')' && stackTop !== undefined) { //keep looping until found a "open - )" bracket
output.push(stack.pop());
stackTop = stack.length > 0 ? stack[stack.length-1] : null;
}
stack.pop(); //remove the open ")" bracket
stackTop = stack.length > 0 ? stack[stack.length-1] : null;
}else if (token === ')') { //')' is opening bracket in reversed tokens
stack.push(token);
}else if (isNumeric(token)) {
output.push(token);
}else if (token === undefined) {
// no more tokens. Just shift everything out from stack
while(stack.length) {
stackTop = stack.pop();
if (stackTop !== undefined && stackTop !== ')') {
output.push(stackTop);
}
}
}
}while(stack.length || reversedTokens.length);
//Reverse output and we are done
return output.reverse();
},
/**
* Tokenized the provided pattern
* #param {string} str The raw pattern from user
* #returns {array} A tokenized array
*/
tokenize: function(str) {
var pattern = str.replace(/\s/g, ''), //remove all the spaces :) not needed
tokens = pattern.split(''),
tokenized = [];
//Tokenize it and verify
var token = null,
next = null;
//attempts to concatenate the "and" and "or" and numerics
while (tokens.length > 0) {
token = tokens.shift();
next = tokens.length > 0 ? tokens[0] : null;
if (token === '(' || token === ')') {
tokenized.push(token);
}else if (token === 'a' && tokens.length >= 2 && tokens[0] === 'n' && tokens[1] === 'd') { //and
tokenized.push(token + tokens.shift() + tokens.shift());
}else if (token === 'o' && tokens.length >= 1 && next === 'r') { //or
tokenized.push(token + tokens.shift());
}else if (isNumeric(token)) {
while(isNumeric(next)) {
token += next;
tokens.shift(); //exhaust it
next = tokens.length > 0 ? tokens[0] : null;
}
tokenized.push(token);
}else{
tokenized.push(token);
}
}
return tokenized;
},
/**
* Attempt to validate tokenized tokens
*
* #param {array} tokens The tokenized tokens
* #param {array} crits The user provided criteria set
* #returns {Boolean|String} Returns boolean true if succeeded, string if error occured
*/
validate: function(tokens, crits) {
var valid = true,
token = null,
stack = [],
nextToken = null,
criteria_count = crits.length;
for (var i = 0 ; i < tokens.length ; ++i) {
token = tokens[i];
nextToken = i < tokens.length - 1 ? tokens[i+1] : null;
if (token === '(') {
stack.push('(');
if (!isNumeric(nextToken) && nextToken !== '(' && nextToken !== ')') {
throw 'Unexpected token "'+nextToken+'"';
}
}else if (token === ')') {
if (stack.length > 0) {
stack.pop();
}else{
throw 'Unexpected closing bracket';
}
if (nextToken !== ')' && nextToken !== 'and' && nextToken !== 'or' && nextToken !== null) {
throw 'Unexpected token "'+nextToken+'"';
}
}else if (token === 'and' || token === 'or') {
if (!isNumeric(nextToken) && nextToken !== '(') {
throw 'Unexpected token "'+nextToken+'"';
}
}else if (isNumeric(token) && token <= criteria_count) {
if (nextToken !== ')' && nextToken !== 'and' && nextToken !== 'or') {
throw 'Unexpected token "'+nextToken+'"';
}
}else{
//anything not recognized, die.
throw 'Unexpected token "'+token+'"';
}
}
//Last step - check if we have all brackets closed
if (valid && stack.length > 0) {
throw 'Missing '+stack.length+' closing bracket';
}
return valid;
}
};
//This is an example pattern and criteria set. Note that pattern numbers must match criteria numbers.
var pattern = '((1 or 3) and (2 or 4) or 5)',
crits = [
1, 2, 3, 4, 5
];
//lazy on the document on load. Just delay
setTimeout(function() {
var result;
try {
result = JSON.stringify(CriteriaParser.parse(pattern, crits), undefined, 4);
}catch(e) {
result = e;
}
var pre = document.createElement('pre');
pre.innerHTML = result;
document.body.appendChild(pre);
}, 10);
})();

This is most easily done using a two step process.
1) Convert to syntax tree.
2) Convert syntax tree to prefix notation.
A syntax tree is basically the same as your prefix notation, just built using the data structures of your programming language.
The standard method to create a syntax tree is to use a LALR parser generator, which is available for most languages. LALR parsers are fast, powerful, and expressive. A LALR parser generator takes a .y file as input, and outputs a source code file for a parser in the programming language of your choice. So you run the LALR parser generator once to generate your parser.
(All programmers should use learn to use parser generators :). It is also smart to use a standard tokenizer, while I am guessing you have written your own :).)
The following is a .y-file to generate a LALR-parser for your mini-language. Running this .y file though a LALR parser generator will output the source for a LALR parser, which takes tokens as input and outputs a parse-tree (in the variable $root_tree). You need to have defined the parsetree_binaryop datastructure manually elsewhere.
%left AND.
%left OR.
start ::= expr(e). { $root_tree = e; }
expr(r) ::= expr(e1) AND expr(e2). { r = new parsetree_binaryop(e1, OP_AND, e2); }
expr(r) ::= expr(e1) OR expr(e2). { r = new parsetree_binaryop(e1, OP_OR, e2); }
expr(r) ::= LPAR expr(e) RPAR. { r = e; }
The "%left AND" means that AND is left-associative (we could have chosen right too, doesn't matter for AND and OR). That "%left AND" is mentioned before "%left OR" means that AND binds tighter than OR, and the generated parser will therefore do the right thing.
When you have the syntax tree the parser gives you, generating the text representation is easy.
Edit: this seems to be a LALR parser generator which outputs a parser in JavaScript: http://sourceforge.net/projects/jscc/

First define semantics. In your first example you gave (1 and 2 and 3) or 4 interpretation but it can also be 1 and 2 and (3 or 4) so:
{
"$and": [
{"$or": [3,4] },
[1,2]
]
}
Let's assume that and has higher priority. Then just go through list join all terms with and. Next, join all the rest with or.

Related

Why is this code returning a runtime error in line 8

/**
* #param {number[]} nums
* #return {boolean}
*/
var containsDuplicate = function(nums) {
let base = nums.length;
let diff = [...new Set(nums)].length;
if base !== diff return true // <--- this line
else {
return false
}
};
the console is saying there is an error with base in the 8th line. This is Leetcode question #217.
I've tried the following:
changing spelling from length and legnth
tried changing variablenames
tried switching return true and false
and changing the equation to !== instead of ====
Ok here is the syntax of if-else condition:
if(some condition){
do that;
}else{
do that;
}
in your case it should look like that:
if (base !== diff)
return true
else {
return false
}
and make sure when you use !== you checks also data type
I hope that was helpful
Found the answer
I forgot to put () after the if on line 8. Thanks to #tadman.
/**
* #param {number[]} nums
* #return {boolean}
*/
var containsDuplicate = function(nums) {
let base = nums.length;
let diff = [...new Set(nums)].length;
if (base !== diff) return true
else {
return false
}
};

Add Two Number leetcode algo

I was doing following leetCode Problem: https://leetcode.com/problems/add-two-numbers/
And I am not sure why one of my test case is failing
So the question is
You are given two non-empty linked lists representing two non-negative
integers. The digits are stored in reverse order and each of their
nodes contain a single digit. Add the two numbers and return it as a
linked list.
You may assume the two numbers do not contain any leading zero, except
the number 0 itself.
For which I have written following algo
/**
* Definition for singly-linked list.
* function ListNode(val) {
* this.val = val;
* this.next = null;
* }
*/
/**
* #param {ListNode} l1
* #param {ListNode} l2
* #return {ListNode}
*/
const makeLinkedList = (inArr, i) => {
if (i < 0) return null
return { val:inArr[i], next:makeLinkedList(inArr, i-1)}
}
var addTwoNumbers = function(l1, l2) {
let sum = 0
let i = 1
while(l1 || l2) {
if (l1 && l2) {
sum = sum + l1.val*i + l2.val*i
l1 = l1.next
l2 = l2.next
} else {
if (l1) {
sum = l1.val*i + sum
l1 = l1.next
}
if (l2) {
sum = l2.val*i + sum
l2 = l2.next
}
}
i = i*10
}
const sumToString = sum.toLocaleString('fullwide', {useGrouping:false});
return makeLinkedList(sumToString, sumToString.length-1)
};
The reason in the above code I have used while loop instead of recursively calling functions is mainly to make it more optimized.
anyway, For the following input, my test case is failing
[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]
[5,6,4]
i.e my output is coming to be [0,3,NaN,NaN,1] instead of [6,6,4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1]
As a note, leetCode compiler will convert array to linkedlist on input. Can someone help me in figuring out why my input might be failing?
When JavaScript stringifies a number in scientific notation, there will be a + sign for positive exponents. That sequence you see is 1E+30, the NaNs are standing for + and E (because of the reverted order). In fact you could have put a console.log(sum) or console.log(sumToString) and catch the issue without knowing this, just simply seeing what is there.
Not all languages tell you the maximum value they can store without loss in precision, but JavaScript in particular does, Number.MAX_SAFE_INTEGER contains the value 9 007 199 254 740 991 so it is a bit more than 9E+15, far less than 1 + 1E+30 (the longer number).
What you are expected to do is to add the numbers like you have learned in elementary school: add two digits, write one digit, and see if there is an 1 to carry to the next digit-pair you are going to add.
Iterative version:
function makeLinkedList(arr,i){
i=i || 0;
return i<arr.length?{val:arr[i], next:makeLinkedList(arr,i+1)}:null;
}
var addTwoNumbers = function(l1, l2) {
var snt={next:null};
var cur=snt;
var carry=0;
while(l1 || l2 || carry){
cur.next={next:null};
cur=cur.next;
var sum=(l1?l1.val:0)+(l2?l2.val:0)+carry;
if(sum<10){
cur.val=sum;
carry=0;
} else {
cur.val=sum-10;
carry=1;
}
l1=l1?l1.next:null;
l2=l2?l2.next:null;
}
return snt.next;
}
var a=[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1];
var b=[5,6,4];
console.log(addTwoNumbers(makeLinkedList(a),makeLinkedList(b)));
a=[9,9];
b=[1,9];
console.log(addTwoNumbers(makeLinkedList(a),makeLinkedList(b)));
Recursive version:
function makeLinkedList(arr,i){
i=i || 0;
return i<arr.length?{val:arr[i], next:makeLinkedList(arr,i+1)}:null;
}
var addTwoNumbers = function(l1, l2, carry) {
if(!(l1 || l2 || carry))
return null;
carry=carry || 0;
var sum=(l1?l1.val:0)+(l2?l2.val:0)+carry;
return {
val: sum % 10,
next: addTwoNumbers(l1?l1.next:null,l2?l2.next:null,sum>9?1:0)
};
}
var a=[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1];
var b=[5,6,4];
console.log(addTwoNumbers(makeLinkedList(a),makeLinkedList(b)));
a=[9,9];
b=[1,9];
console.log(addTwoNumbers(makeLinkedList(a),makeLinkedList(b)));
Solution for the problem in JavaScript.
var addTwoNumbers = function (l1, l2) {
let reminder = 0;
let l1Node = l1;
let l2Node = l2;
let list = new ListNode(0);
let currentNode = list;
while (l1Node || l2Node) {
const valueL1 = l1Node ? l1Node.val : 0;
const valueL2 = l2Node ? l2Node.val : 0;
let sum = valueL1 + valueL2 + reminder;
reminder = 0;
if (sum > 9) {
reminder = Math.floor(sum / 10);
sum = sum % 10;
}
currentNode.next = new ListNode(sum);
currentNode = currentNode.next;
l1Node = l1Node ? l1Node.next : null;
l2Node = l2Node ? l2Node.next : null;
}
if (reminder != 0) {
currentNode.next = new ListNode(reminder);
currentNode = currentNode.next;
}
return list.next;
};
function ListNode(val, next) {
this.val = (val === undefined ? 0 : val)
this.next = (next === undefined ? null : next)
}
const l1 = new ListNode(2, new ListNode(4, new ListNode(3)));
const l2 = new ListNode(5, new ListNode(6))
const res = addTwoNumbers(l1, l2);
console.log(res);

JavaScript non-recursive Depth First Search to compare, match and return matching values from an unknown number of arrays with unknown length

As the title says, I am in search for a non-recursive solution that accepts an array of objects each containing an array stored in a property named "dataMap" and can be any size and length. The objects stored in the array have two properties: dataMap (type Array) and i (type Number).
I found and read through this other post here on Stack Overflow but am not drawing a parallel to how it pertains to my problem. Non-recursive depth first search algorithm
Example data structure:
Array[
{
dataMap:[...N],
i:0
},
{
dataMap:[...N],
i:0
},
...N
]
Assume (from the above):
"Array" is of any length, unknown at runtime
"dataMap" is of any length, unknown at runtime
"dataMap" data type in each index is arbitrary and can be any data type (In my example I just used numbers to keep it simple)
Edit addendum 1.0:
Target environment for solution deployment is Internet Explorer 10 and ECMASript 5. The solution needs to match all pairs even if they are repeated. In my original recursive solution, if you run the code you will see each matched pair printed regardless if it was a duplicate. This is due to the fact that each of these arrays are representing data sets containing data from other databases that, when matched, I need to grab some pieces of other information about the row and return it. This is just backstory but is not particularly relevant to the question or solution as I tried to simplify it for brevity.
End Edit addendum 1.0:
I have a recursive solution, but after finishing and testing it will only work with the amount (or close to) of objects that are uncommented. If you uncomment the last object in the array and run the program, you will get a stack overflow. I hope it is suitable to read.
var a =
[
{
dataMap:[1,75,7,8,4,2,4,5,6,5,4,34,5,67,7,74,6,3,6,78,8,2,4],
i:0
},
{
dataMap:[2,5,8,6,5,4,6,4,5,76,8,8],
i:0
},
{
dataMap:[1,75,7,8,4,2,4,5,6],
i:0
},
/*{
dataMap:[3,5,7,5,4,3,5,7,56,7,9,6,5,2,2,5],
i:0
}*/
];
(function foo(array,level,compare){
compare[level] = array[level].dataMap[array[level].i];
if(typeof array[level-1] === "undefined"){
// First array
if(array[level].i == array[level].dataMap.length-1){
// were at the end of the first array;
return;
}else{
level++;
array[level].i = 0;
}
}else if(typeof array[level+1] === "undefined"){
// last array
if(array[level].i == array[level].dataMap.length-1){
level--;
//array[level].i++;
}
array[level].i++;
}else{
// somewhere in the middle
if(array[level].i == array[level].dataMap.length-1){
// if at the end
if(array[level+1].i == array[level+1].dataMap.length-1){
//if the array below me is at their end
// go up
level--;
array[level].i++;
}
}else{
level++;
array[level].i = 0;
}
}
var t = true;
for(var z=0;z<compare.length;z++){
if(typeof compare[z+1] !== "undefined" && t != false && compare[z] != compare[z+1]){
t = false;
}
}
if(t){
console.log("FOUND ONE:" + JSON.stringify(compare));
}
foo(array,level,compare);
})(a,0,[]);
The reason for selecting a recursive function is that the matching results from the comparison will eventually be returned to be stored in a variable who is executing this code via a self executing inline function. Recursion is what I thought I needed but clearly see there are memory issues.
jQuery may be used. I chose to use pure JS.
So, is there a way to accomplish what I am asking? My mind goes to breaking it out into functions but wont that cause the same memory issues?
This solution maybe is not the best, but I tried and it works.
var array =
[
{
dataMap:[1,1,75,34,8,4,2,4,5,6,5,4,34,5,67,7,74,6,3,6,78,8,2,4,67],
i:0
},
{
dataMap:[1,2,5,8,6,5,4,6,4,5,76,8,7,67],
i:0
},
{
dataMap:[1,75,8,4,2,4,5,67,6],
i:0
},
{
dataMap:[11,3,5,7,5,54,3,5,7,67,56,7,9,6,5,2,2,5],
i:0
}
];
/**
* Save no duplicated data to compare after
* Set, allow us to add no duplicated data
* https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
*/
addNoDuplicated = ( ( array, noDuplicated ) => {
array.map( ( item ) => {
item.dataMap.map( (data) => {
noDuplicated.add( data );
});
} )
} )
deepSearchAndPrint = ( array, noDuplicated, duplicatedItSelf, accumDuplicated ) => {
addNoDuplicated( array, noDuplicated );
for ( let elem of noDuplicated ) {
accumDuplicated[elem] = [ ];
array.map( ( item , index) => {
duplicatedItSelf[index] = [];
item.dataMap.map( ( data ) => {
// avoid add duplicated
if ( data === elem && duplicatedItSelf[index].indexOf(data) === -1 ) {
duplicatedItSelf[index].push(data);
accumDuplicated[elem].push(data)
}
} )
})
/**
* check if sizes are equal, if they are equal, is a valid case
*/
if ( accumDuplicated[elem].length === array.length ) {
console.log( accumDuplicated[elem] );
}
}
}
deepSearchAndPrint( array, new Set(), { }, { } );
UPDATE
This other solution is using normal loop function and function declarations.
var array =
[
{
dataMap:[1,1],
i:0
},
{
dataMap:[1,3],
i:0
}/*,
{
dataMap:[1,7],
i:0
},
{
dataMap:[3,1],
i:0
}*/
];
/**
* Save all no duplicated data contained into each dataMap array
* noDuplicated: [1, 3]
* This is going to help us to compare
*/
function addNoDuplicated( array, noDuplicated ) {
for ( let item of array ) {
for ( let data of item.dataMap ) {
// if they are primitive data type we use indexOf to check exist
if ( noDuplicated.indexOf( data ) === -1 ) {
noDuplicated.push( data );
}
};
}
}
function deepSearchAndPrint( array, noDuplicated, duplicatedItSelf, accumDuplicated ) {
addNoDuplicated( array, noDuplicated );
/**
* start looping through noDuplicated data array
*/
// you can use normal loop here if you want
for ( let elem of noDuplicated ) {
/**
* create an array to each no repeated element
* noDuplicated: [1, 3]
* accumDuplicated[elem] => 1: [],3: [], ....
*/
accumDuplicated[elem] = [ ];
const arraySize = array.length;
/**
* iterate through our original array data structure
*/
for ( let index = 0; index < arraySize; index++ ) {
duplicatedItSelf[index] = [];
const dataSize = array[index].dataMap.length;
/**
* iterate through each dataMap array
*/
for ( let indexData = 0; indexData < dataSize; indexData++ ) {
let data = array[index].dataMap[indexData];
/**
* avoid add duplicated values into a same array(dataMap)
* e.g
* dataMap:[1,1]
* dataMap:[1,3]
* duplicatedItSelf[0] = [1]
* duplicatedItSelf[1] = [1,3]
*/
if ( data === elem && duplicatedItSelf[index].indexOf(data) === -1 ) {
duplicatedItSelf[index].push(data);
/**
* save into accumDuplicated array according to key of noDuplicated data array
* accumDuplicated[1] = [1]
* accumDuplicated[1] = [1, 1]
*/
accumDuplicated[elem].push(data)
// console.log(accumDuplicated); // uncomment to verify
}
}
}
/**
* if accumDuplicated in its own key has the same length as the general array => << match >>
* accumDuplicated[0] = [1, 1] => = 2
* array = [ {}, {} ] = 2
*/
if ( accumDuplicated[elem].length === array.length ) {
console.log( accumDuplicated[elem] );
}
}
}
deepSearchAndPrint( array, [ ], { }, { } );
I hope that helps you :)

How to test for equality in ArrayBuffer, DataView, and TypedArray

Is there a way how to test if two JavaScript ArrayBuffers are equal? I would like to write test for message composing method. The only way I found is to convert the ArrayBuffer to string and then compare. Did I miss something?
Following code is giving false, even if I think that it should be true:
(function() {
'use strict';
/* Fill buffer with data of Verse header and user_auth
* command */
var buf_pos = 0;
var name_len = 6
var message_len = 4 + 1 + 1 + 1 + name_len + 1;
var buf = new ArrayBuffer(message_len);
var view = new DataView(buf);
/* Verse header starts with version */
view.setUint8(buf_pos, 1 << 4); /* First 4 bits are reserved for version of protocol */
buf_pos += 2;
/* The lenght of the message */
view.setUint16(buf_pos, message_len);
buf_pos += 2;
buf_pos = 0;
var buf2 = new ArrayBuffer(message_len);
var view2 = new DataView(buf);
/* Verse header starts with version */
view2.setUint8(buf_pos, 1 << 4); /* First 4 bits are reserved for version of protocol */
buf_pos += 2;
/* The lenght of the message */
view2.setUint16(buf_pos, message_len);
buf_pos += 2;
if(buf == buf2){
console.log('true');
}
else{
console.log('false');
}
}());
If I try to compare view and view2 it's false again.
You cannot compare two objects directly in JavaScript using == or ===.
These operators will only check the equality of references (i.e. if expressions reference the same object).
You can, however, use DataView or ArrayView objects to retrieve values of specific parts of ArrayBuffer objects and check them.
If you want to check headers:
if ( view1.getUint8 (0) == view2.getUint8 (0)
&& view1.getUint16(2) == view2.getUint16(2)) ...
Or if you want to check the globality of your buffers:
function equal (buf1, buf2)
{
if (buf1.byteLength != buf2.byteLength) return false;
var dv1 = new Int8Array(buf1);
var dv2 = new Int8Array(buf2);
for (var i = 0 ; i != buf1.byteLength ; i++)
{
if (dv1[i] != dv2[i]) return false;
}
return true;
}
If you want to implement a complex data structure based on ArrayBuffer, I suggest creating your own class, or else you will have to resort to cumbersome raw DataView / ArrayView instances each time you will want to move a matchstick in and out of the structure.
In general javascript, you currently have to compare two ArrayBuffer objects by wrapping each with a TypedArray, then manually iterating over each element and doing element-wise equality.
If the underlying buffer is 2 or 4-byte memory-aligned then you can make a significant optimization by employing Uint16 or Uint32 typed-arrays for the comparison.
/**
* compare two binary arrays for equality
* #param {(ArrayBuffer|ArrayBufferView)} a
* #param {(ArrayBuffer|ArrayBufferView)} b
*/
function equal(a, b) {
if (a instanceof ArrayBuffer) a = new Uint8Array(a, 0);
if (b instanceof ArrayBuffer) b = new Uint8Array(b, 0);
if (a.byteLength != b.byteLength) return false;
if (aligned32(a) && aligned32(b))
return equal32(a, b);
if (aligned16(a) && aligned16(b))
return equal16(a, b);
return equal8(a, b);
}
function equal8(a, b) {
const ua = new Uint8Array(a.buffer, a.byteOffset, a.byteLength);
const ub = new Uint8Array(b.buffer, b.byteOffset, b.byteLength);
return compare(ua, ub);
}
function equal16(a, b) {
const ua = new Uint16Array(a.buffer, a.byteOffset, a.byteLength / 2);
const ub = new Uint16Array(b.buffer, b.byteOffset, b.byteLength / 2);
return compare(ua, ub);
}
function equal32(a, b) {
const ua = new Uint32Array(a.buffer, a.byteOffset, a.byteLength / 4);
const ub = new Uint32Array(b.buffer, b.byteOffset, b.byteLength / 4);
return compare(ua, ub);
}
function compare(a, b) {
for (let i = a.length; -1 < i; i -= 1) {
if ((a[i] !== b[i])) return false;
}
return true;
}
function aligned16(a) {
return (a.byteOffset % 2 === 0) && (a.byteLength % 2 === 0);
}
function aligned32(a) {
return (a.byteOffset % 4 === 0) && (a.byteLength % 4 === 0);
}
and called via:
equal(buf1, buf2)
here are the performance tests for 1-, 2-, 4-byte aligned memory.
Alternatives:
You may also get more performance with WASM, but its possible the cost of transferring the data to the heap may negate the comparison benefit.
Within Node.JS you may get more performance with Buffer as it will have native code: Buffer.from(buf1, 0).equals(Buffer.from(buf2, 0))
In today's V8, DataView should now be "usable for performance-critical real-world applications" — https://v8.dev/blog/dataview
The functions below test equality based on the objects you already have instantiated. If you already have TypedArray objects, you could compare them directly without creating additional DataView objects for them (someone is welcome to measure performance for both options).
// compare ArrayBuffers
function arrayBuffersAreEqual(a, b) {
return dataViewsAreEqual(new DataView(a), new DataView(b));
}
// compare DataViews
function dataViewsAreEqual(a, b) {
if (a.byteLength !== b.byteLength) return false;
for (let i=0; i < a.byteLength; i++) {
if (a.getUint8(i) !== b.getUint8(i)) return false;
}
return true;
}
// compare TypedArrays
function typedArraysAreEqual(a, b) {
if (a.byteLength !== b.byteLength) return false;
return a.every((val, i) => val === b[i]);
}
To test for equality between two TypedArrays, consider using the every method, which exits as soon as an inconsistency is found:
const a = Uint8Array.from([0,1,2,3]);
const b = Uint8Array.from([0,1,2,3]);
const c = Uint8Array.from([0,1,2,3,4]);
const areEqual = (first, second) =>
first.length === second.length && first.every((value, index) => value === second[index]);
console.log(areEqual(a, b));
console.log(areEqual(a, c));
This is less expensive than alternatives (like toString() comparisons) which iterate over the remaining array even after a difference is found.
I wrote these functions to compare the most normal data types. It works with ArrayBuffer, TypedArray, DataView, Node.js Buffer and any normal Array with byte data (0-255).
// It will not copy any underlying buffers, instead it will create a view into them.
function dataToUint8Array(data) {
let uint8array
if (data instanceof ArrayBuffer || Array.isArray(data)) {
uint8array = new Uint8Array(data)
} else if (data instanceof Buffer) { // Node.js Buffer
uint8array = new Uint8Array(data.buffer, data.byteOffset, data.length)
} else if (ArrayBuffer.isView(data)) { // DataView, TypedArray or Node.js Buffer
uint8array = new Uint8Array(data.buffer, data.byteOffset, data.byteLength)
} else {
throw Error('Data is not an ArrayBuffer, TypedArray, DataView or a Node.js Buffer.')
}
return uint8array
}
function compareData(a, b) {
a = dataToUint8Array(a); b = dataToUint8Array(b)
if (a.byteLength != b.byteLength) return false
return a.every((val, i) => val == b[i])
}
You can always convert the arrays into strings and compare them. E.g.
let a = new Uint8Array([1, 2, 3, 4]);
let b = new Uint8Array([1, 2, 3, 4]);
if (a.toString() == b.toString()) {
console.log("Yes");
} else {
console.log("No");
}

javascript split function not working

I have this following code.
var oldBeforeUnload = window.onbeforeunload;
window.onbeforeunload = function()
{
if(modifiedItems && modifiedItems != null)
{
var modifiedItemsArr = modifiedItems.split(",");
if(window.showModalDialog)
{
window.returnValue = modifiedItemsArr;
}
else
{
if (window.opener && window.opener.setFieldValue)
{
window.opener.setFieldValue(modifiedItemsArr);
}
}
}
return oldBeforeUnload();
};
when I split is run in IE it is throwing an
error : object doesnt support property or method split.
In ff the its exiting without any log.
on alert(modifiedItems) //the output is Ljava.lang.object;#c14d9
Can any one tell why is the split not working or is my modifiedItem wrong.
modifiedItems must be a string in order to use split.
alert(typeof modifiedItems);
var oldBeforeUnload = window.onbeforeunload;
window.onbeforeunload = function()
{
if(modifiedItems && modifiedItems != null)
{
alert(typeof modifiedItems);
var modifiedItemsArr = modifiedItems.split(",");
if(window.showModalDialog)
{
window.returnValue = modifiedItemsArr;
}
else
{
if (window.opener && window.opener.setFieldValue)
{
window.opener.setFieldValue(modifiedItemsArr);
}
}
}
return oldBeforeUnload();
};
variable "modifiedItems" should be a string for split function to work. In your case alert(modifiedItems) should alert the string value.I would suggest you to check "modifiedItems".
split() is a function in string object.
Split is a method that can be call on strings. It would seem as if you are trying to split an object not a string. Below is a correct and incorrect usage:
"a,s,d".split(",") // returns ['a','s','d']
{obj:true}.split(",") // will throw the error you are seeing becuase it is an object
To confirm this you can use the following:
console.log(typeof modifiedItems) // <- will output the vaiable type
modifiedItems should be an object, which you can not split, get the value for what you are trying to split out of modified items. On a different note, you are using the window namespace where it doesn't look like you need to be. Take a look at some of the links below to determine if you really should be using the window object.
Is setting properties on the Window object considered bad practice?
why attach to window [edited]
Use this cross browser method that will make everything work.
This script can be found out for downloading at:
http://blog.stevenlevithan.com/archives/cross-browser-split
It always has worked for me.
/*!
* Cross-Browser Split 1.1.1
* Copyright 2007-2012 Steven Levithan <stevenlevithan.com>
* Available under the MIT License
* ECMAScript compliant, uniform cross-browser split method
*/
/**
* Splits a string into an array of strings using a regex or string separator. Matches of the
* separator are not included in the result array. However, if `separator` is a regex that contains
* capturing groups, backreferences are spliced into the result each time `separator` is matched.
* Fixes browser bugs compared to the native `String.prototype.split` and can be used reliably
* cross-browser.
* #param {String} str String to split.
* #param {RegExp|String} separator Regex or string to use for separating the string.
* #param {Number} [limit] Maximum number of items to include in the result array.
* #returns {Array} Array of substrings.
* #example
*
* // Basic use
* split('a b c d', ' ');
* // -> ['a', 'b', 'c', 'd']
*
* // With limit
* split('a b c d', ' ', 2);
* // -> ['a', 'b']
*
* // Backreferences in result array
* split('..word1 word2..', /([a-z]+)(\d+)/i);
* // -> ['..', 'word', '1', ' ', 'word', '2', '..']
*/
var split;
// Avoid running twice; that would break the `nativeSplit` reference
split = split || function (undef) {
var nativeSplit = String.prototype.split,
compliantExecNpcg = /()??/.exec("")[1] === undef, // NPCG: nonparticipating capturing group
self;
self = function (str, separator, limit) {
// If `separator` is not a regex, use `nativeSplit`
if (Object.prototype.toString.call(separator) !== "[object RegExp]") {
return nativeSplit.call(str, separator, limit);
}
var output = [],
flags = (separator.ignoreCase ? "i" : "") +
(separator.multiline ? "m" : "") +
(separator.extended ? "x" : "") + // Proposed for ES6
(separator.sticky ? "y" : ""), // Firefox 3+
lastLastIndex = 0,
// Make `global` and avoid `lastIndex` issues by working with a copy
separator = new RegExp(separator.source, flags + "g"),
separator2, match, lastIndex, lastLength;
str += ""; // Type-convert
if (!compliantExecNpcg) {
// Doesn't need flags gy, but they don't hurt
separator2 = new RegExp("^" + separator.source + "$(?!\\s)", flags);
}
/* Values for `limit`, per the spec:
* If undefined: 4294967295 // Math.pow(2, 32) - 1
* If 0, Infinity, or NaN: 0
* If positive number: limit = Math.floor(limit); if (limit > 4294967295) limit -= 4294967296;
* If negative number: 4294967296 - Math.floor(Math.abs(limit))
* If other: Type-convert, then use the above rules
*/
limit = limit === undef ?
-1 >>> 0 : // Math.pow(2, 32) - 1
limit >>> 0; // ToUint32(limit)
while (match = separator.exec(str)) {
// `separator.lastIndex` is not reliable cross-browser
lastIndex = match.index + match[0].length;
if (lastIndex > lastLastIndex) {
output.push(str.slice(lastLastIndex, match.index));
// Fix browsers whose `exec` methods don't consistently return `undefined` for
// nonparticipating capturing groups
if (!compliantExecNpcg && match.length > 1) {
match[0].replace(separator2, function () {
for (var i = 1; i < arguments.length - 2; i++) {
if (arguments[i] === undef) {
match[i] = undef;
}
}
});
}
if (match.length > 1 && match.index < str.length) {
Array.prototype.push.apply(output, match.slice(1));
}
lastLength = match[0].length;
lastLastIndex = lastIndex;
if (output.length >= limit) {
break;
}
}
if (separator.lastIndex === match.index) {
separator.lastIndex++; // Avoid an infinite loop
}
}
if (lastLastIndex === str.length) {
if (lastLength || !separator.test("")) {
output.push("");
}
} else {
output.push(str.slice(lastLastIndex));
}
return output.length > limit ? output.slice(0, limit) : output;
};
// For convenience
String.prototype.split = function (separator, limit) {
return self(this, separator, limit);
};
return self;
}();

Categories

Resources