This has been a challenge for me...
I have a set of nodes in an XML doc. I need to sort them based on a certain node value. So if I iterate through the nodes, and then the node value matches my criteria, I want it to go to the end.
Problem is, of course as soon as I swap, as nodes are in a live set, the iteration pointer misses one entry of course, as the appendChild is operating on a live-set.
This is my code so far, but as I said, it may miss an entry due to the swapping:
for (var i=1; i <= nElem; i++)
{
var node = getNode(dom,"//item[" + i + "]");
var state = getNodeValue(dom,"//item[" + i + "]/state");
if ((state != 'XX') && (i != nElem))
{
node.parentNode.appendChild(node);
}
}
What I actually want is that all items in state "XX" are at the top.
Has anyone an intelligent idea to this?
Thanks
You could use array.sort() and pass a custom sort routine:
var nodes = getNode(dom, "//item"); gets you an array of items
next, remove the entries in nodes from the dom
do an nodes.sort(sortfunction) where sortfunction is sortfunction(a,b)
implement sortfunction so that it returns
-1 if a shall be lower than b
0 if equal
1 if a shall be higher than b
add the entries of nodes back to the dom
I think, that would do it (as long as I'm not missing something).
Related
I am working on a sheet that contains job description information, including an Overview and Responsibilities. The source data is not exact, but it roughly has the Overview in one cell of a column, and each Responsibility in additional cells in the same column. I am writing the Overview (which I am determining based on character count) in one results column, and building an unordered list with each the Responsibilities into another results column.
My source isn’t always perfect though. I have situations where the first Responsibility is included in the same cell as its corresponding Overview. I can recognize that by the text, and have an indexOf() statement written to do that.
When I use a slice() method, the script is correctly indicating the text that is occurring after the appropriate index. But what I need is to use the splice() method, so that I can remove that text from the source data before creating the results data. However, when I change the statement from slice() to splice(), I’m getting an error: “TypeError: Cannot find function splice in object {beginning text of the cell}”
for(i=0; i<data.length; i++) {
var iRow = data[i];
if(iRow[12].length > 250) { // this is an overview
if(iRow[12].indexOf("What you’ll do")>-1) { // is there a responsibility at the end of the overview?
var startIndex = iRow[12].indexOf("What you’ll do");
// this is the line that works for slice(), but not splice()
var txt = iRow[12].splice(startIndex, 26); // splice out the end of text, starting at the index.
data[writeRow][18] += iRow[12]; // write the overview, without the added responsibility
data[writeRow][19] += "<li>" + txt + "</li>"; // add the extracted responsibility to its list
} else { // these is no responsibility added to the end of the overview
data[writeRow][18] += iRow[12]; // write the overview
}
} else { // this is a responsibility
data[writeRow][19] += "<li>" + iRow[12] + "</li>"; // add it to the list
}
}
There's obviously a lot more going on (defining var data, var writeRow, initiating the , etc) which all works fine. I’m sure that I’m just being an idiot somewhere. But can someone help me figure out why slice() works here, but splice() doesn’t?
splice is an array function. slice is both, an Array function and a String function.
References
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/slice
This has been eating me away. Check CodePen here. I have a function that adds elements to a ruler. Call expose.init(lengthOfPeriod) with an integer to set the length of each period in the ruler. Call expose.addAction(time, team) with an integer and a string == 'HOME' || 'AWAY' to place an action in the ruler.
When the screen is resized, I want something to happen to the elements in the ruler that touch each other (basically collapse into a group).
I have a function 'detectOverlap' that takes 2 params and determines if they are touching or not. I also have a function in the resize event handler that populates an array 'allTouching' with all the elements in the ruler that are touching each other at each resize.
if (actionArray.length > 1) { //don't run if there's only one element in the page
actionArray.reduce(function (prev, next) {
if (detectOverlap(prev, next)) {
if (allTouching.indexOf(prev, next) === -1) allTouching.push(prev, next);
}
return next;
});
If actions are touching each other, I need them to collapse into groups. In order to do this, I need to create an array for each group of actions touching each other. However, I haven't been able to make this happen so far.
Here's some pseudo code:
for (i = 0; i < allTouching.length; i++) {
if (detectOverlap(allTouching[0], alltouching) {
touchingGroup[i] = new Array(allTouching[0], all other elements touched by 0);
do not include any element more than once in touchingGroup[i];
do not include any similar arrays (same elements) in allGroups;
allGroups.push(touchingGroup[i]);
}
}
In short, this would need to loop for all the elements in the allTouching array, and create a new touchingGroup[n] for each new group of actions that touch each other.
This sounds simple in my head, and I'm sure there must be a way to do it without code getting overly complex, but I haven't found it yet.
Any feedback would be appreciated.
It seems your question is only about the grouping, so I will ignore the visualisation aspect and assume that the function detectOverlap is correct.
Then you could create the groups in one for loop. In this snippet I have added simplistic sample data and a mock detectOverlap function that will return true when its two arguments are the same (just for the purpose of the snippet):
// Simplistic mock data and function, just to make the snippet work
var actionArray = [1, 1, 3, 3, 3, 8, 9];
function detectOverlap(a, b) { return a === b; }
// Actual code:
var allGroups = [];
var start = 0;
for (var i = 1; i <= actionArray.length; i++) {
if (i >= actionArray.length || !detectOverlap(actionArray[i-1], actionArray[i])) {
if (i - start > 1) // Remove this condition if you want singletons as well
allGroups.push(actionArray.slice(start, i));
start = i;
}
}
console.log(JSON.stringify(allGroups));
Explanation
The variable start is used as an index in the array, from where the most recently found group should start. That group is not yet actually created, since we do not know where it ends, and so I will call this the "undecided" group. start is initialised at 0 to indicate that the first group will start there.
The loop iterates over the array, but starting at 1. In each iteration it decides whether the "undecided" group (started at start) is complete. The group is considered complete when there is no overlap between the previous and the current element of the array. In that case the previous element is the last element of the "undecided" group. The elements for that group are copied from the array with slice. Note that the second argument of slice is the index of the first element that should not be part of the group. Now that group is stored, and start is put at the current index, where the next (and only) "undecided" group should start.
But as long as the two elements do overlap, no new group should be created (that is why the condition has a !). Instead start remains unchanged, and so this "undecided", "unclosed" group is getting bigger in size.
There is an if just before that slice, which prevents the creation of groups that only contain one element. If you remove that if, then also single elements will be isolated in their own "singleton" groups.
The loop will go up to and including arrayAction.length: this is unusual, since that makes the last i an invalid index. But it is useful, since in that case we still want to finish up the last group that is still "ongoing". So in that case i >= arrayAction.length will be true, and so the detectOverlap function will not be called (because the if condition is already known to be true). The if block will be entered and the final group will be created.
I have an array containing particles (fire, blood, smoke, etc.) in an HTML5 game. All particles have an expiry/lifespan. I'm creating up to 100 particles per frame at 60fps so I want to keep this array as clean as possible so I can loop through it efficiently.
I have heard it's better to use 'splice' rather than 'delete' to remove elements from an array. This makes sense to me as I'd rather not loop through keys of the array that are blank (that 'delete' leaves behind).
However, I tested this out and have a higher, more consistent frame rate if I 'delete' keys rather than splicing them to remove expired particles. The downside is the longer the game runs the longer my particles array gets.
Is there a better solution to this?
If the order of the items in the array doesn't matter, then simply assign the last item in the array to the one you want to overwrite and then delete it by reducing the .length.
function unordered_remove(arr, i) {
if (i <= 0 || i >= arr.length) {
return;
}
if (i < arr.length - 1) {
arr[i] = arr[arr.length-1];
}
arr.length -= 1;
}
This is much faster because it doesn't need to reindex and is good for situations where the order doesn't matter.
When you use delete on an array element, all you are actually doing is setting that array element to undefined. The array will still have the same length. When you use splice, you actually remove that element entirely. The element is removed, and everything after that element is shifted down 1 index.Of the two, delete is going to be faster since your array doesn't have to re-index.
As for performance, if leaving the deleted elements as undefined works, then that is probably the best way to do it. If you are concerned about the array length growing too long, or maybe have to search that array frequently and want to reduce overhead, you could periodically filter out the undefined elements like so:
function filterArr() {
myArr = myArr.filter(function(v) {
return typeof v !== 'undefined';
});
}
var interval = setInterval(filterArr, 5000);
This will give you the best of both worlds. When you need to remove the particles, you use delete to set the elements to undefined, which is faster than removing them in place. Every now and then, you remove them in place to keep your array size lower.
You could improve upon that depending on your requirements. Good luck :)
You'll have way higher performances by packing the array by yourself : less operations AND no need to dispose current array and create a new one (like Array.filter does), so much less garbage collection.
function packArray(tgtArray) {
if (!tgtArray || !tgtArray.length) return;
var srcIndex = 0;
var dstIndex = 0;
var arrayLength = tgtArray.length ;
do {
var currentItem = tgtArray[srcIndex];
if (currentItem.alive) {
if (srcIndex != dstIndex) {
tgtArray[dstIndex] = currentItem ;
}
dstIndex++;
}
srcIndex++;
} while (srcIndex != arrayLength) ;
dstIndex--;
tgtArray.length = dstIndex > 0 ? dstIndex : 0 ;
}
So I have a block of code that looks like this:
stack.forEach(function(element){
//sys.puts(sys.inspect(element, false, null));
console.log('-----element start-----');
console.log(element.type + ':' + element.raw);
console.log('-----element end-----');
if(element.children){
element.children.forEach(function(childElement){
stack.push(childElement);
});
}
});
The issue is that this is not behaving like I would except a stack to behave and wondering if this in an issue with JavaScript itself. The issue I am seeing if that when I call the stack.push for each child to add it to the stack for processing, the initial stack.forEach() does not seem to be picking it up and it is only logging to top level elements. If I try to do another stack.forEach() directly after this one, it then displays the next level of child elements so I know .push is diffidently adding the child elements to the stack.
It almost seems like .forEach() grabs the data as it is and does not get an updates if they happen within the foreach. Is this true for JavaScript? Is there a different way I can accomplish the same thing (be able to process a top level element and all levels of children elements below it)?
Your supposition is correct. The ForEach function takes a snapshot of the array and any updates will not be processed.
You are trying to implement a classic tree traversal algorithm which can most easily be coded as a recursive function as follows :
var stack = [];
function traverse (element) {
//sys.puts (sys.inspect(element, false, null));
console.log ('-----element start-----');
console.log (element.type + ':' + element.raw);
console.log ('-----element end-----');
stack.push (element); // for preorder traversal (parent before children)
if (element.children)
element.children.forEach (traverse);
// stack.push (element); // for postorder traversal (children before parent)
};
traverse (document.body); // for example
Yes, .forEach does only iterate over the range the array initially has, it works with a copy of the .length value. If you want to change that behavior, I'd recommend a classical loop which queries the length property each time:
var stack = […];
for (var i=0; i<stack.length; i++) {
// process stack[i]
stack.push(…);
}
or even a more list-like behavior with
var stack = […];
while (stack.length) {
var cur = stack.shift();
// process cur
stack.push(…); // with variable arguments, you want to stop somewhen :-)
}
If you don't want/need to maintain the stack yourself, a recursive function is the proper choice for tree traversal.
I have a connected, directed, cyclic graph. The task is to discover every single node in the graph without falling into an infinite loop, as a regular tree traversal algorithm will do.
You can assume that I already know what node to start at so as to reach all points in the directed graph, and that for each node I have a function that will return the nodes it directs to. Is there a known algorithm for finding all nodes?
The main issue is really avoiding cycles, and I would love it if there was a way to do that without keeping track of every single node and comparing it with a list of nodes that has already been traversed.
If you need more details, the actual task is to get a list of every named function in JavaScript, including functions that are properties of other objects. So I tried something like the following, as I thought the JS objects' references to each other made a tree (but of course it doesn't):
function __findFunctions(obj){
for (var f in obj){
// for special case of edge with self
if (obj === obj[f]){
continue
}
if (typeof obj[f] === 'function' &&
obj.hasOwnProperty(f) &&
// exclude native functions, we only want user-defined ones
!(/\[(native\scode|object\sfunction)\]/i).test(obj[f].toString()) &&
// exclude functions with __ prefix
!(/^\s*function\s*__/).test(obj[f].toString())
){
document.write(f + "<br/>" + obj[f].toString() + "<hr/>");
}
//alert(typeof obj[f] + "\n" + obj + "\n" + obj[f] + "\n" + f)
__findFunctions(obj[f]);
}
}
__findFunctions(window);
The problem with this code is that it gets stuck in cycles.
I would love it if there was a way to do that without keeping track of every single node and comparing it with a list of nodes that has already been traversed.
It may not be as bad as checking a list of already-traversed nodes. You could, instead, give each node a unique ID of some sort:
// psuedo
id=0;
for each node
node.id = id++;
etc.
Then you can add each node's ID to a hash while you traverse:
var alreadyTraversed = {};
// Traversing a node:
alreadyTraversed[node.id] = true;
And later on, when you need to check whether or not its already been traversed:
if (node.id in alreadyTraversed) // It's already been traversed...
Or, for a really rudimentary solution, simply set some property on each object that you traverse:
node._traversed = true;
// Later:
if (someNode._traversed) // already traversed.
You would need to maintain a list of already visited nodes if you want to avoid cycles.
E.g.
__findFunctions(obj, visited) as your signature
at start do an "in array" test for current obj and return if so.
at start add obj to the visited for subsequent traversals.