I need to create an array from tree elements in Javascript and being a newbie I don't know how to achieve this.
pseudo-code :
function make_array_of_tree_node(tree_node)
{
for (var i = 0; i < tree_node.childCount; i ++) {
var node = tree_node_node.getChild(i);
if (node.type ==0) {
// Here I'd like to put a link (node.title) in an array as an element
} else if (node.type ==6) {
// Here the element is a folder so a I need to browse it
make_array_of_tree_node(node)
}
}
}
// Some code
make_array_of_tree_node(rootNode);
// Here I'd like to have access to the array containing all the elements node.title
You can declare an array like this:
var nodes = [];
Then you can add things to it with:
nodes.push(something);
That adds to the end of the array; in that sense it's kind-of like a list. You can access elements by numeric indexes, starting with zero. The length of the array is maintained for you:
var len = nodes.length;
What you'll probably want to do is make the array another parameter of your function.
edit — To illustrate the pattern, if you've got a recursive function:
function recursive(data, array) {
if ( timeToStop ) {
array.push( data.whatever );
}
else {
recursive(data.subData, array);
}
}
Then you can use a second function to be the real API that other code will use:
function actual(data) {
var array = [];
recursive(data, array); // fills up the array
return array;
}
In JavaScript, furthermore, it's common to place the "recursive" function inside the "actual" function, which makes the recursive part private and keeps the global namespace cleaner.
Related
I have the following script in Google Apps Script:
for(var i=0; i<lastCode; i++) {
var productCode = prodCodesArr[i];
for(var j=0; j<kelliLastCode; j++) {
var kelliProductCode = kelliCodesArr[j];
if(productCode == kelliProductCode) {
Logger.log('match found')
}
}
}
The 2 arrays are created dynamically. So the idea is (and I know there must be MUCH better ways to do this, but I am pretty new to this so bear with me) that I am setting i to the value of the first product code in one array and then looping through the other array whilst storing the product codes in this one to j. Now, I tried logging:
Logger.log(productCode + ' - ' + kelliProductCode);
And this worked and indeed, there were instances where productCode and kelliProduct code matched.
Yet my if statement above does not pick these up.
Again, I'm sure I've botched this entirely but any help would be greatly appreciated...
What's the point of the check? To determine which of your prodCodesArr items are also in kelliCodesArr? Why not parse kelliCodesArr just once, and then use hash lookups instead of array traversal? This will mean that you don't have to use nested for loops, which will scale very poorly as the inner loop size grows. An example (with some checks for assumptions on my part):
function foo() {
const kelliCodes = getKelliCodesArraySomehow();
const productCodes = getProductCodesArraySomehow();
// If these are 2D arrays, note that for `var a = ['help']; var b = ['help'];`
// `a` is never equal to `b` because they are not the exact same object in memory.
if (kelliCodes.length && Array.isArray(kelliCodes[0])) {
throw new TypeError("This SO answer was predicated on `kelliCodes` and `productCodes` being 1D arrays, but they aren't!");
}
const kelliLookup = kelliCodes.reduce(function (obj, kpc, idx) {
if (typeof kpc === 'object') {
console.log({message: "This SO answer assumed kpc was a string", kpc: kpc});
throw new TypeError("You probably want to store a property of this object, not the whole object");
}
obj[kpc] = idx;
return obj;
}, {});
var productsAlsoInKelliCodes = productCodes.filter(function (pc) {
return kelliLookup.hasOwnProperty(pc);
});
productsAlsoInKelliCodes.forEach(function (pc) {
Logger.log("The index of this product code %s in kelliCodes is %s", pc, kelliLookup[pc]);
});
}
If your ___codes arrays are 2D arrays, you should flatten them before comparison, as comparing an Array instance to another Array instance will always return false, even if they contain the same element primitives--they aren't the exact same Array instance:
References
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness
Array#forEach
Array#map
In JS, which is faster: Object's "in" operator or Array's indexof?
Javascript: what lookup is faster: array.indexOf vs object hash?
I'm sure there are more.
Something like this might help you to see what's happening:
function compareA(prodCodesArr,kelliCodesArr) {
var html="";
for(var i=0;i<prodCodesArr.length;i++) {
for(var j=0;j<kelliCodesArr.length;j++) {
if(productCodesArr[i]==kelliCodesArr[j]) {
html+=Utilities.formatString('Matched: %s=%s', productCodesArr[i],kelliCodesArr[j]);
}else{
html+=Utilities.formatString('No-Match: %s=%s', productCodesArr[i],kelliCodesArr[j]);
}
}
}
var userInterface=HtmlService.createHtmlOutput(html);
SpreadsheetApp.getUi().showModelessDialog(userInterface, 'Comparing')
}
I'm currently working on learning recursion and am trying to reimplement the getElementsByClassName function by walking the DOM using recursion. I finally feel like I've grasped the concepts but I'm having issues when I push the matching elements into a results array. Heres my code:
var getElementsByClassName = function(className){
var results = [];
var domWalker = function(node) {
for (var i=0; i<node.children.length; i++) {
if (node.children[i].classList.contains(className)) {
console.log(node.children[i])
results.push(node.children[i])
}
if (node.children[i].children.length > 0) {
domWalker(node.children[i])
}
}
return results;
};
domWalker(document.body);
console.log(results)
};
Basically, I need the results array to hold the matching elements it finds in HTML format like so:
[<div class="title"></div>, <button class="click"></button>]
... yet when I push these elements into my results array they change to the: [div.title, button.click] format.
I added the console.log statement above the call to results.push to see if the results appear in the proper format before they are pushed to the array which they do. The results being pushed to the array are the results I'm looking for, they just appear in the wrong format.
Why is push causing the format of my results to change and how can I get around this issue?
I solved this problem once upon a time. Although I haven't read through your solution, here is mine, heavily commented. I hope it helps:
var getElementsByClassName = function(className, node){
// The empty results array, which gets created whenever the function is
// called.
var results = [];
// Default the node to the document's body if it isn't set. This way, we can
// call the function recursively with child elements, but never have to
// worry about it the first time around.
node = node || document.body;
// If the node contains the class list in question, let's push it into the
// results array.
if (node.classList && node.classList.contains(className)) {
results.push(node);
}
// Now, let's fetch the child nodes of the current node.
var children = node.childNodes;
// If child nodes exist, then we proceed here.
if (children) {
// Let's now loop over all child nodes of the current node in question. This
// way, we'll be able to perform checks on each child node.
for (var i = 0; i < children.length; i++) {
// Fetch the i child node.
var child = children[i];
// At this point, we want to pass the child node back into the function,
// implementing recursion. The same checks above will occur, and if the
// node has the class name, it will be added to the results array from
// that function call.
//
// This returns an array, and we need to merge it with the current set
// of results, so we concat it.
//
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/concat
results = results.concat(getElementsByClassName(className, child));
}
}
// We then return the combined results array from all the recursive function
// calls!
return results;
};
node.children[i] holds a reference to the HTML element
console.log() applies an implicit .toString() method giving what you see.
you need this additional code (to be extended to all possible tagNames you find):
var el = node.children[i];
el = '<' + el.tagName + (el.className ? ' class="' + el.className + '"': '') + '/>';
console.log(el);
results.push(el);
function addNumifnotThere(numer){
var numCent = [];
numCent.forEach(function(){
if(numer in numCent)
console.log("you logged that");
else
numCent.push(numer);
});
return numCent;
}
This is my current code, what its attempting to do is read an array and if there is already an element exits the loop and says "you already logged that", obviously if it cannot find a similar element then it pushes it to the array.
I want this to work dynamically so we cannot know the size of the array beforehand, so the first element passed as an argument should be put into the array, (addNum(1) should have the array print out [1], calling addNum(1) again should print "you already logged that")
However there are two problems with this
1) Trying to push to a new array without any entries means everything is undefined and therefore trying to traverse the array just causes the program to print [].
2) Adding some random elements to the array just to make it work, in this case numCent=[1,2,3] has other issues, mainly that adding a number above 3 causes the code to print incorrect information. In this case addNum(5) should print [1,2,3,5] but instead prints [1,2,3,5,5,5]
I know this has to be a simple mistake but I've been dragging myself too long to not ask for help.
EDIT: Thanks to the many outstanding answers here I have now leanred about the indexOf method, thank you guys so much.
For every non-match you are pushing the number. Use something like this
var numCent = [];
function addNumifnotThere(numer)
{
var index = numCent.indexOf(number);
if(index >=0)
{
console.log("you logged that");
}
else
{
numCent.push(number);
}
return numCent;
}
Use Array.prototype.indexOf
var numCent = [];
function addNum(numer){
if (numCent.indexOf(numer) > -1)
{
console.log("Number already in array");
}
else
{
numCent.push(numer);
}
}
//DEMO CODE, not part of solution
document.querySelector("button").addEventListener("click", function(){
if (document.querySelector("input").value.length > 0)
{
addNum(document.querySelector("input").value);
document.querySelector("div").innerHTML = numCent.join(", ");
}
}, false);
Output
<div id="output"></div>
<input />
<button>Add number</button>
indexOf tests if an element is inside the array and returns its index. If not found it will return -1. You can test for this. You can try it for your self in this snippet. It will only allow you to add a number (or any string, in this example) once.
I also was confused by the newCent array declaration inside the function. I think, based upon the content of your question, you meant this.
If you want the array held in the instance, you can do it like this.
function AddIf(arr){
if( arr || !this.arr ) {
this.arr = arr || [];
}
return function(number) {
if( this.arr.indexOf(number) >= 0 ) {
console.log("Already Present!");
} else {
this.arr.push(number);
}
return this.arr;
}.bind(this);
}
// Usage would be like this:
// var addIf = new AddIf([1, 2, 3]);
// addIf(10); // returns [1, 2, 3, 10]
// addIf(10); // logs "Already Present!", doesn't add 10 to array
This basically returns a function, with this bound to the function being called. If you pass in an initial array, it will use that array to compare to when adding it to the array.
You can catch the return function and call it as you would want to. If you don't call new when invoking however, it will share the same array instance (and have a funky way of being called, AddIf()(10)).
I used fn.bind() to ensure the function gets called in the correct context every time, if you were wondering why I called it like that.
Do do this cleanly, I'd consider prototyping the global Array object and adding a method to push values but only if they're unique to the array. Something like this:
Array.prototype.pushUnique = function (item) {
if (this.indexOf(item) != -1) {
console.log("Item with value of " + item + " already exists in the array."
}
else {
this.push(item);
}
}
If you're not comfortable prototypeing global types like Array, you can build the same thing in a procedural pattern:
function arrayPushUnique (arr, item) {
if (arr.indexOf(item) != -1) {
console.log("Item with value of " + item + " already exists in the array."
}
else {
arr.push(item);
}
}
Then to use it, simply create a new empty array and start pushing things to it.
var numCent = [];
// The Array.prototype method
numCent.pushUnique(number);
// The procedural method
arrayPushUnique(numCent, number);
I pass 2 arrays to a function and want to move a specific entry from one array to another. The moveDatum function itself uses underscorejs' methods reject and filter. My Problem is, the original arrays are not changed, as if I was passing the arrays as value and not as reference. The specific entry is correctly moved, but as I said, the effect is only local. What do I have to change, to have the original arrays change as well?
Call the function:
this.moveDatum(sourceArr, targetArr, id)
Function itself:
function moveDatum(srcDS, trgDS, id) {
var ds = _(srcDS).filter(function(el) {
return el.uid === uid;
});
srcDS = _(srcDS).reject(function(el) {
return el.uid === uid;
});
trgDS.push(ds[0]);
return this;
}
Thanks for the help
As mentioned in the comments, you're assigning srcDS to reference a new array returned by .reject(), and thus losing the reference to the array originally passed in from outside the function.
You need to perform your array operations directly on the original array, perhaps something like this:
function moveDatum(srcDS, trgDS, id) {
var ds;
for (var i = srcDS.length - 1; i >= 0; i--) {
if (srcDS[i].uid === id) {
ds = srcDS[i];
srcDS.splice(i,1);
}
}
trgDS.push(ds);
return this;
}
I've set up the loop to go backwards so that you don't have to worry about the loop index i getting out of sync when .splice() removes items from the array. The backwards loop also means ds ends up referencing the first element in srcDS that matches, which is what I assume you intend since your original code had trgDS.push(ds[0]).
If you happen to know that the array will only ever contain exactly one match then of course it doesn't matter if you go forwards or backwards, and you can add a break inside the if since there's no point continuing the loop once you have a match.
(Also I think you had a typo, you were testing === uid instead of === id.)
Copy over every match before deleting it using methods which modify Arrays, e.g. splice.
function moveDatum(srcDS, trgDS, id) { // you pass an `id`, not `uid`?
var i;
for (i = 0; i < srcDS.length; ++i) {
if (srcDS[i].uid === uid) {
trgDS.push(srcDS[i]);
srcDS.splice(i, 1);
// optionally break here for just the first
i--; // remember; decrement `i` because we need to re-check the same
// index now that the length has changed
}
}
return this;
}
I have a JSON response like this:
var errorLog = "[[\"comp\",\"Please add company name!\"],
[\"zip\",\"Please add zip code!\"],
...
Which I'm deserializing like this:
var log = jQuery.parseJSON(errorLog);
Now I can access elements like this:
log[1][1] > "Please add company name"
Question:
If I have the first value comp, is there a way to directly get the 2nd value by doing:
log[comp][1]
without looping through the whole array.
Thanks for help!
No. Unless the 'value' of the first array (maybe I should say, the first dimension, or the first row), is also it's key. That is, unless it is something like this:
log = {
'comp': 'Please add a company name'
.
.
.
}
Now, log['comp'] or log.comp is legal.
There are two was to do this, but neither avoids a loop. The first is to loop through the array each time you access the items:
var val = '';
for (var i = 0; i < errorLog.length; i++) {
if (errorLog[i][0] === "comp") {
val = errorLog[i][1];
break;
}
}
The other would be to work your array into an object and access it with object notation.
var errors = {};
for (var i = 0; i < errorLog.length; i++) {
errors[errorLog[i][0]] = errorLog[i][1];
}
You could then access the relevant value with errors.comp.
If you're only looking once, the first option is probably better. If you may look more than once, it's probably best to use the second system since (a) you only need to do the loop once, which is more efficient, (b) you don't repeat yourself with the looping code, (c) it's immediately obvious what you're trying to do.
No matter what you are going to loop through the array somehow even it is obscured for you a bit by tools like jQuery.
You could create an object from the array as has been suggested like this:
var objLookup = function(arr, search) {
var o = {}, i, l, first, second;
for (i=0, l=arr.length; i<l; i++) {
first = arr[i][0]; // These variables are for convenience and readability.
second = arr[i][1]; // The function could be rewritten without them.
o[first] = second;
}
return o[search];
}
But the faster solution would be to just loop through the array and return the value as soon as it is found:
var indexLookup = function(arr, search){
var index = -1, i, l;
for (i = 0, l = arr.length; i<l; i++) {
if (arr[i][0] === search) return arr[i][1];
}
return undefined;
}
You could then just use these functions like this in your code so that you don't have to have the looping in the middle of all your code:
var log = [
["comp","Please add company name!"],
["zip","Please add zip code!"]
];
objLookup(log, "zip"); // Please add zip code!
indexLookup(log, "comp"); // Please add company name!
Here is a jsfiddle that shows these in use.
Have you looked at jQuery's grep or inArray method?
See this discussion
Are there any jquery features to query multi-dimensional arrays in a similar fashion to the DOM?