Related
I have a recursivce function that takes a dom tree and converts it to JSON.
However I want to exclude any nodes that have a specific data attribute data-exclude
const htmlToJSON = node => {
const exclude = node.attributes?.getNamedItem('data-exclude');
if (!exclude) {
let obj = {
nodeType: node.nodeType
};
if (node.tagName) {
obj.tagName = node.tagName.toLowerCase();
} else if (node.nodeName) {
obj.nodeName = node.nodeName;
}
if (node.nodeValue) {
obj.nodeValue = node.nodeValue;
}
let attrs = node.attributes;
if (attrs) {
length = attrs.length;
const arr = (obj.attributes = new Array(length));
for (let i = 0; i < length; i++) {
const attr = attrs[i];
arr[i] = [attr.nodeName, attr.nodeValue];
}
}
let childNodes = node.childNodes;
if (childNodes && childNodes.length) {
let arr = (obj.childNodes = []);
for (let i = 0; i < childNodes.length; i++) {
arr[i] = htmlToJSON(childNodes[i]);
}
}
return obj;
}
};
const parser = new DOMParser();
const { body } = parser.parseFromString(page, 'text/html');
let jsonOutput = htmlToJSON(body);
console.log(jsonOutput);
I am clearly missing something with the way I am excluding because when I log the results it is returning undefined instead of just excluding it.
It's most likely because you're not returning anything from htmlToJSON in the case of "exclude == true". Notice how your lambda function doesn't have a "return " in that case. So the function will by default return "undefined."
And if you fill an array element with "undefined" it becomes a sparse array. So those elements in the array with "undefined" values become interpreted as "empty" slots by console.log() when printing the contents of any array to the console.
Update: I tried your code and, yup, my explanation above is correct. However, if you don't care about implicitly returning undefined from your htmlToJSON(), then you can just modify your inner for loop:
for (let i = 0; i < childNodes.length; i++) {
let json = htmlToJSON(childNodes[i]);
json && arr.push(json);
}
This way, only if json is truthy, will it add an element to the arr array.
I tried this code with your original function, and also with a modified version that returns null if exclude == true, and both ways worked.
Here's a Codepen example.
Did not execute the code. As far as I can see htmlToJSON will return obj or undefined. If exclude is truthy, the function will return undef, thats what you are seeing.
Change your for loop:
for (let i = 0, temp; i < childNodes.length; i++) {
temp = htmlToJSON(childNodes[i]);
temp && (arr[i] = temp);
}
that way you make sure if temp is defined you assign, otherwise not.
Another option is to use Array.prototype.filter on the resultant array.
I'm trying to solve the problem below:
A linked list is given such that each node contains an additional random pointer which could point to any node in the list or null.
Return a deep copy of the list.
The Linked List is represented in the input/output as a list of n nodes. Each node is represented as a pair of [val, random_index] where:
val: an integer representing Node.val
random_index: the index of the node (range from 0 to n-1) where random pointer points to, or null if it does not point to any node.
Input: head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
Output: [[7,null],[13,0],[11,4],[10,2],[1,0]]
My idea was loop the head and create a new node with the same value and set it to my ans Node. It will be a copy because I'm creating a new one. Then, I loop again for the random pointer which looks in my ans Node the node with the value that I should point to. It works perfectly for the input above. However, if I have an input with duplicate values, the findeNode method might return an invalid Node. For example it fails for the input below:
[[3,null],[5,17],[4,null],[-9,6],[-10,3],[5,15],[0,11],[6,null],[-6,16],[3,16],[-6,11],[9,12],[-2,1],[-3,11],[-1,10],[2,11],[-3,null],[-9,7],[-2,4],[-8,null],[5,null]]
My output:
[[3,null],[5,3],[4,null],[-9,null],[-10,3],[5,15],[0,11],[6,null],[-6,13],[3,13],[-6,11],[9,12],[-2,1],[-3,11],[-1,8],[2,11],[-3,null],[-9,7],[-2,4],[-8,null],[5,null]]
Correct output:
[[3,null],[5,17],[4,null],[-9,6],[-10,3],[5,15],[0,11],[6,null],[-6,16],[3,16],[-6,11],[9,12],[-2,1],[-3,11],[-1,10],[2,11],[-3,null],[-9,7],[-2,4],[-8,null],[5,null]]
Node definition:
function Node(val, next, random) {
this.val = val;
this.next = next;
this.random = random;
};
My code:
var copyRandomList = function(head) {
if(!head) return head;
let ans = new Node(0);
let temp = ans;
let cur = head;
while(cur) {
const node = new Node(cur.val);
temp.next = node;
cur = cur.next;
temp = temp.next;
}
cur = head;
temp = ans.next;
while(cur) {
const randomNode = cur.random;
if(!randomNode) {
temp.random = null;
} else {
const node = findNode(ans, randomNode.val);
temp.random = node;
}
cur = cur.next;
temp = temp.next;
}
return ans.next;
};
const findNode = (list, val) => {
if(!val)
return null;
while(list) {
if(list.val === val)
return list;
list = list.next;
}
return null;
}
I am have a set of boolean value as following
var prodcat1 = true;
var prodcat2 = false;
var prodcat3 = false;
var prodcat4 = false;
var prodcat5 = false;
var prodcat6 = false;
var prodcat7 = false;
var prodcat8 = false;
var prodcat9 = false;
var prodcat10 = true;
How can I convert all the true variable into one array as following.
var array = ["prodcat1", "prodcat10"]
You somewhat painted yourself in a corner by using variable names like prodcat5, because you can't really iterate through them, without using the dreaded eval() function.
let arr = [];
for (let i=1; i<=10; i++) {
if (eval("prodcat" + i)) {
arr.push("prodcat" + i);
}
}
This is quite a bad way of doing it, eval() can, in general, present a security risk.
Another solution is, perhaps better (if you're running the code in a browser), is what ThatBrianDude came up with (look below), by (ab)using the window object.
But all these solutions are flawed, because the problem can be easily avoided. A much better solution is to have an array called prodcat and storing values in it like this:
prodcat[0] = true;
prodcat[1] = false;
/* etc... */
Then you can easily iterate through them.
What you are doing here is very wrong. Its possible yes, but you arent leveraging what arrays are made for.
To anwser your question anyway, you could to it like this:
let trueArray = [];
for(let i = 1; i <=10; i++){
if(window["prodcat" + i])
trueArray.push("prodcat" + i)
}
console.log(trueArray)
Your way of using variable and then storing variable name in array is not optimal. In this way you manually need to push variable names if true.
var prodcat1 = true;
var prodcat2 = false;
var prodcat3 = false;
.. ... ...
.. ... ...
var prodcat10 = true;
var myArr = []
//now test for values and push in myArr manually
if (prodcat1) {
myArr.push('prodcat1')
}
if (prodcat2) {
myArr.push('prodcat2')
}
The other approach is using eval. But eval is very unpredictable and should be avoided.
for (let i = 1; i <= 10; i++) {
if (eval('prodcat' + i)) {
myArr.push('prodcat' + i)
}
}
The best approach as per me would be using an object to store your values.
var myProdcats = {
prodcat1: true,
prodcat2: false
}
var myArray = Object.keys(myProdcats).filter(prodcat => myProdcats[prodcat]) // ["prodcat1", "prodcat2"]
You can do:
const prodcat1 = true;
const prodcat2 = false;
const prodcat3 = false;
const prodcat4 = false;
const prodcat5 = false;
const prodcat6 = false;
const prodcat7 = false;
const prodcat8 = false;
const prodcat9 = false;
const prodcat10 = true;
const result = Array
.from({length: 10}, (v, i) => i + 1)
.reduce((a, c) => eval(`prodcat${c}`) ? [...a, `prodcat${c}`] : a, []);
console.log(result);
let arr = [], arr2=[];
for (let i=1; i<=10; i++) {
if (eval("prodcat" + i)) {
arr.push(eval("prodcat" + i));
}else arr2.push(eval("prodcat" + i));
}
would be your solution for now
I currently have an object that adds itself to an array whenever a new one is created. Eventually, I want to remove all of the references in the array so I can add new ones.
I've created an object method (this.removeFromArray()) that looks for itself in the array and splices itself out. removeAll() runs a for loop that makes each object in the array run removeFromArray(), so I expect that when I try to read out the items in the array, I should get nothing.
Instead, depending on the amount of objects created, I get one or two left behind. How can I fix this and have all objects in the array cleared out?
var objArray = [];
function obj(name) {
objArray.push(this);
console.log("Created "+name);
this.name = name;
this.removeFromArray = function() {
objArray.splice(
objArray.findIndex(function(e) {
return e == this;
}),
1
);
}
}
function removeAll() {
for (var i = 0; i <= objArray.length - 1; i++) {
objArray[i].removeFromArray();
}
}
var foo = new obj("foo");
var bar = new obj("bar");
var cat = new obj("cat");
var dog = new obj("dog");
var bird = new obj("bird");
removeAll();
for (var i = 0; i <= objArray.length-1; i++) { //Check the values in the array for leftovers
console.log(objArray[i].name);
}
//Expected nothing in the console but the creation messages, got foo and bar instead
If you want to simply delete all the created object, edit removeAll() function like below:
Note that you have to create a variable for objArray.length, not directly put the objArray.length to for() loop.
function removeAll() {
var len = objArray.length;
for (var i = 0; i <= len - 1; i++) {
objArray.splice(0,1);
}
}
better way to achieve this would be to utilize inheritance through prototype. it is better than creating a function inside the constructor object.
var objArray = [];
function Obj(name) {
this.name = name;
objArray.push(this);
}
Obj.prototype.removeFromArray = function() {
var i = -1,
len = objArray.length,
removed = null;
while (++i < len) {
if (objArray[i] === this) {
removed = objArray.splice(i, 1);
removed = null; //nullify to free memory, though not that necessary
break;
}
}
};
Obj.prototype.removeAll = function() {
var len = objArray.length,
removed = null;
//note that i started from the last item to remove to avoid index out of range error
while (--len >= 0) {
removed = objArray.splice(len, 1);
removed = null; //nullify to free memory, though not that necessary
}
};
I have prototypes to recreate how array methods work, pop/push/shift/etc, and I would like to extend the functionality to do the following:
Push/Pop/shift/unshift multiple values
array.push(0);
array.push(1);
array.push(2);
expect(array.pop()).to.be(2);
expect(array.pop()).to.be(1);
expect(array.pop()).to.be(0);
Push/Pop/unshift/etc single values
array.push(0);
array.push(1);
expect([0,1]);
array.pop(1);
expect([0]);
My assumption is that I would need a global array variable to store the elements. Is that the right?
Here is my code:
var mainArray = []; // array no longer destroyed after fn() runs
function YourArray(value) {
this.arr = mainArray; // looks to global for elements | function?
this.index = 0;
var l = mainArray.length;
if(this.arr === 'undefined')
mainArray += value; // add value if array is empty
else
for(var i = 0; i < l ; i++) // check array length
mainArray += mainArray[i] = value; // create array index & val
return this.arr;
}
YourArray.prototype.push = function( value ) {
this.arr[ this.index++ ] = value;
return this;
};
YourArray.prototype.pop = function( value ) {
this.arr[ this.index-- ] = value;
return this;
};
var arr = new YourArray();
arr.push(2);
console.log(mainArray);
My assumption is that I would need a global array variable to store
the elements. Is that the right?
No. That is not right.
You want each array object to have its own, independent set of data. Otherwise, how can you have multiple arrays in your program?
function YourArray(value) {
this.arr = []; // This is the data belonging to this instance.
this.index = 0;
if(typeof(value) != 'undefined')) {
this.arr = [value];
this.index = 1;
}
}
////////////////////////////////////
// Add prototype methods here
///////////////////////////////////
var array1 = new YourArray();
var array2 = new YourArray();
array1.push(2);
array1.push(4);
array2.push(3);
array2.push(9);
// Demonstrate that the values of one array
// are unaffected by the values of a different array
expect(array1.pop()).to.be(4);
expect(array2.pop()).to.be(9);
It's a bit late for this party, admitted but it nagged me. Is there no easy (for some larger values of "easy") way to do it in one global array?
The standard array functions work as in the following rough(!) sketch:
function AnotherArray() {
this.arr = [];
// points to end of array
this.index = 0;
if(arguments.length > 0) {
for(var i=0;i<arguments.length;i++){
// adapt if you want deep copies of objects
// and/or take a given array's elements as
// individual elements
this.arr[i] = arguments[i];
this.index++;
}
}
}
AnotherArray.prototype.push = function() {
// checks and balances ommitted
for(var i=0;i<arguments.length;i++){
this.arr[ this.index++ ] = arguments[i];
}
return this;
};
AnotherArray.prototype.pop = function() {
this.index--;
return this;
};
AnotherArray.prototype.unshift = function() {
// checks and balances ommitted
var tmp = [];
var alen = arguments.length;
for(var i=0;i<this.index;i++){
tmp[i] = this.arr[i];
}
for(var i=0;i<alen;i++){
this.arr[i] = arguments[i];
this.index++;
}
for(var i=0;i<tmp.length + alen;i++){
this.arr[i + alen] = tmp[i];
}
return this;
};
AnotherArray.prototype.shift = function() {
var tmp = [];
for(var i=1;i<this.index;i++){
tmp[i - 1] = this.arr[i];
}
this.arr = tmp;
this.index--;
return this;
};
AnotherArray.prototype.isAnotherArray = function() {
return true;
}
AnotherArray.prototype.clear = function() {
this.arr = [];
this.index = 0;
}
AnotherArray.prototype.fill = function(value,length) {
var len = 0;
if(arguments.length > 1)
len = length;
for(var i=0;i<this.index + len;i++){
this.arr[i] = value;
}
if(len != 0)
this.index += len;
return this;
}
// to simplify this example
AnotherArray.prototype.toString = function() {
var delimiter = arguments.length > 0 ? arguments[0] : ",";
var output = "";
for(var i=0;i<this.index;i++){
output += this.arr[i];
if(i < this.index - 1)
output += delimiter;
}
return output;
}
var yaa = new AnotherArray(1,2,3);
yaa.toString(); // 1,2,3
yaa.push(4,5,6).toString(); // 1,2,3,4,5,6
yaa.pop().toString(); // 1,2,3,4,5
yaa.unshift(-1,0).toString(); // -1,0,1,2,3,4,5
yaa.shift().toString(); // 0,1,2,3,4,5
var yaa2 = new AnotherArray();
yaa2.fill(1,10).toString(); // 1,1,1,1,1,1,1,1,1,1
Quite simple and forward and took only about 20 minutes to write (yes, I'm a slow typist). I would exchange the native JavaScript array in this.arr with a double-linked list if the content can be arbitrary JavaScript objects which would make shift and unshift a bit less memory hungry but that is obviously more complex and slower, too.
But to the main problem, the global array. If we want to use several individual chunks of the same array we need to have information about the starts and ends of the individual parts. Example:
var globalArray = [];
var globalIndex = [[0,0]];
function YetAnotherArry(){
// starts at the end of the last one
this.start = globalIndex[globalIndex.length-1][1];
this.index = this.start;
// position of the information in the global index
this.pos = globalIndex.length;
globalIndex[globalIndex.length] = [this.start,this.index];
}
So far, so well. We can handle the first array without any problems. We can even make a second one but the moment the first one wants to expand its array we get in trouble: there is no space for that. The start of the second array is the end of the first one, without any gap.
One simple solution is to use an array of arrays
globalArray = [
["first subarray"],
["second subarray"],
...
];
We can than reuse what we already wrote in that case
var globalArray = [];
function YetAnotherArray(){
// open a new array
globalArray[globalArray.length] = [];
// point to that array
this.arr = globalArray[globalArray.length - 1];
this.index = 0;
}
YetAnotherArray.prototype.push = function() {
for(var i=0;i<arguments.length;i++){
this.arr[ this.index++ ] = arguments[i];
}
return this;
};
// and so on
But for every new YetAnotherArray you add another array to the global array pool and every array you abandon is still there and uses memory. You need to manage your arrays and delete every YetAnotherArray you don't need anymore and you have to delete it fully to allow the GC to do its thing.
That will leave nothing but gaps in the global array. You can leave it as it is but if you want to use and delete thousands you are left with a very sparse global array at the end. Or you can clean up. Problem:
var globalArray = [];
function YetAnotherArray(){
// add a new subarray to the end of the global array
globalArray[globalArray.length] = [];
this.arr = globalArray[globalArray.length - 1];
this.index = 0;
this.pos = globalArray.length - 1;
}
YetAnotherArray.prototype.push = function() {
for(var i=0;i<arguments.length;i++){
this.arr[ this.index++ ] = arguments[i];
}
return this;
};
YetAnotherArray.prototype.toString = function() {
var delimiter = arguments.length > 0 ? arguments[0] : ",";
var output = "";
for(var i=0;i<this.index;i++){
output += this.arr[i];
if(i < this.index - 1)
output += delimiter;
}
return output;
}
// we need a method to delete an instance
YetAnotherArray.prototype.clear = function() {
globalArray[this.pos] = null;
this.arr = null;
this.index = null;
};
YetAnotherArray.delete = function(arr){
arr.clear();
delete(arr);
};
// probably won't work, just a hint in case of asynch. use
var mutex = false;
YetAnotherArray.gc = function() {
var glen, indexof, next_index, sub_len;
indexof = function(arr,start){
for(var i = start;i<arr.length;i++){
if (arr[i] == null || arr[i] == undefined)
return i;
}
return -1;
};
mutex = true;
glen = globalArray.length;
sublen = 0;
for(var i = 0;i<glen;i++){
if(globalArray[i] == null || globalArray[i] == undefined){
next_index = indexof(globalArray,i);
if(next_index == -1){
break;
}
else {
globalArray[i] = globalArray[next_index + 1];
globalArray[next_index + 1] = null;
sublen++;
}
}
}
globalArray.length -= sublen - 1;
mutex = false;
};
var yaa_1 = new YetAnotherArray();
var yaa_2 = new YetAnotherArray();
var yaa_3 = new YetAnotherArray();
var yaa_4 = new YetAnotherArray();
yaa_1.push(1,2,3,4,5,6,7,8,9).toString(); // 1,2,3,4,5,6,7,8,9
yaa_2.push(11,12,13,14,15,16).toString(); // 11,12,13,14,15,16
yaa_3.push(21,22,23,24,25,26,27,28,29).toString();// 21,22,23,24,25,26,27,28,29
yaa_4.push(311,312,313,314,315,316).toString(); // 311,312,313,314,315,316
globalArray.join("\n");
/*
1,2,3,4,5,6,7,8,9
11,12,13,14,15,16
21,22,23,24,25,26,27,28,29
311,312,313,314,315,316
*/
YetAnotherArray.delete(yaa_2);
globalArray.join("\n");
/*
1,2,3,4,5,6,7,8,9
21,22,23,24,25,26,27,28,29
311,312,313,314,315,316
*/
YetAnotherArray.gc();
globalArray.join("\n");
/*
1,2,3,4,5,6,7,8,9
21,22,23,24,25,26,27,28,29
311,312,313,314,315,316
*/
But, as you might have guessed already: it doesn't work.
YetAnotherArray.delete(yaa_3); // yaa_3 was 21,22,23,24,25,26,27,28,29
globalArray.join("\n");
/*
1,2,3,4,5,6,7,8,9
21,22,23,24,25,26,27,28,29
*/
We would need another array to keep all positions. Actual implementation as an exercise for the reader but if you want to implement a JavaScript like array, that is for arbitrary content you really, really, really should use a doubly-linked list. Or a b-tree. A b+-tree maybe?
Oh, btw: yes, you can do it quite easily with a {key:value} object, but that would have squeezed all the fun out of the job, wouldn't it? ;-)