This is my implementation of a graph, to get the shortest path between A and B.
class Queue {
constructor() {
this.head = null;
this.tail = null;
this.size = 0;
}
offer(item) {
const p = new QueueNode(item);
this.size++;
if (this.head === null) {
this.head = p;
this.tail = p;
return;
}
this.tail.next = p;
this.tail = p;
}
poll() {
if (this.size === 0) {
throw TypeError("Can't deque off an empty queue.");
}
this.size--;
const item = this.head;
this.head = this.head.next;
return item.val;
}
peek() {
if (this.size === 0) {
throw TypeError("Empty Queue.")
}
return this.head.val;
}
isEmpty() {
return this.head === null;
}
}
class QueueNode {
constructor(item) {
this.val = item;
this.next = null;
}
}
class Graph {
constructor(directed = false) {
this.numVertices = 0;
this.directed = directed;
this.dict = {}
}
addEdge(v1, v2, weight = 1) {
let p, q;
if (v1 in this.dict) {
p = this.dict[v1];
} else {
p = new GraphNode(v1);
this.dict[v1] = p;
this.numVertices++;
}
if (v2 in this.dict) {
q = this.dict[v2];
} else {
q = new GraphNode(v2);
this.dict[v2] = q;
this.numVertices++;
}
p.addEdge(q);
if (!this.directed) {
q.addEdge(p);
}
}
stringify() {
for (const [key, value] of Object.entries(this.dict)) {
console.log(`${key}: ${[...value.adjacencySet].map(x => x.data)}`);
}
}
buildDistanceTable(source) {
let p;
if (this.dict[source] === undefined) {
throw TypeError('Vertex not present in graph')
} else {
p = this.dict[source];
}
const distanceTable = {};
for (const [key, value] of Object.entries(this.dict)) {
distanceTable[key] = [-1, -1];
}
distanceTable[p.data] = [0, p.data];
const queue = new Queue();
queue.offer(p);
while (!queue.isEmpty()) {
let curr = queue.poll();
let curr_distance = distanceTable[curr.data][0];
curr.adjacencySet.forEach((item) => {
if (distanceTable[item.data] === -1) {
distanceTable[item.data] = [1 + curr_distance, curr.data];
console.log(distanceTable);
if (item.adjacencySet.length > 0) {
queue.offer(item);
}
}
})
}
return distanceTable;
}
shortestPath(source, destination) {
const distanceTable = this.buildDistanceTable(source);
const path = [destination];
let prev = distanceTable[destination][1];
while (prev !== -1 && prev !== source) {
path.unshift(prev);
prev = distanceTable[prev][1];
}
if (prev === null) {
console.log("There's no path from source to destination");
} else {
path.unshift(source);
path.map(item => {
console.log(item);
});
}
}
}
class GraphNode {
constructor(data) {
this.data = data;
this.adjacencySet = new Set();
}
addEdge(node) {
this.adjacencySet.add(node)
}
}
graph = new Graph(directed = false);
graph.addEdge(0, 1);
graph.addEdge(1, 2);
graph.addEdge(1, 3);
graph.addEdge(2, 3);
graph.addEdge(1, 4);
graph.addEdge(3, 5);
graph.addEdge(5, 4);
graph.addEdge(3, 6);
graph.addEdge(6, 7);
graph.addEdge(0, 7);
graph.stringify();
graph.shortestPath(1, 7);
When I run this it give 1, 7 however that's not the shortest path. What am I doing wrong here.
You have 2 issue in your code (that sabotage the building of the distance table):
You missing index in: if (distanceTable[item.data] === -1) { -> each item in the distance table is of array therefor it need to be: if (distanceTable[item.data][0] === -1) {
Set size in node js checked with size and not length (as in documentation) therefor item.adjacencySet.length is always undefined so you need to change: if (item.adjacencySet.length> 0) { to if (item.adjacencySet.size > 0) {
After those 2 changes your code return me path of 1 -> 0 -> 7
Just small side issue: you missing some ; and "new" before throwing TypeError...
here is the question:
Imagine the String() constructor didn't exist. Create a constructor
function, MyString(), that acts like String() as closely as possible.
You're not allowed to use any built-in string methods or properties,
and remember that String() doesn't exist. You can use this code to
test your constructor:
I created constructor however I have no clue how to re-create split method, how to implement that functionality.
If you could give an idea how to implement split method, I would be grateful
function MyString(str) {
var thisObj = this;
var innerLength = 0;
this.length;
function updateLength() {
innerLength = 0;
for (var i = 0; str[i] != undefined; i++) {
innerLength++;
thisObj[i] = str[i];
}
thisObj.length = innerLength;
}
updateLength();
this.toString = function() {
return str;
}
this.charAt = function(i) {
if (isNaN(parseInt(i))) {
return this[0]
} else {
return this[i]
}
}
this.concat = function(string) {
str += string;
updateLength();
}
this.slice = function(start, end) {
var slicedString = "";
if (start >= 0 && end >= 00) {
for (start; start < end; start++) {
slicedString += str[start];
}
}
return slicedString;
}
this.reverse = function() {
var arr = str.split("");
arr.reverse();
var reversedString = "",
i;
for (i = 0; i < arr.length; i++) {
reversedString += arr[i];
}
return reversedString;
}
}
var ms = new MyString("Hello, I am a string")
console.log(ms.reverse())
You can convert the string to an array and use Array.prototype and RegExp.prototype methods.
this.split = function(re) {
var arr = Array.prototype.slice.call(this.toString());
if (re === "") {
return arr
}
if (re === " ") {
return arr.filter(function(el) {
return /[^\s]/.test(el)
})
}
if (/RegExp/.test(Object.getPrototypeOf(re).constructor)) {
var regexp = re.source;
return arr.filter(function(el) {
return regexp.indexOf(el) === -1
})
}
}
function MyString(str) {
var thisObj = this;
var innerLength = 0;
this.length;
function updateLength() {
innerLength = 0;
for (var i = 0; str[i] != undefined; i++) {
innerLength++;
thisObj[i] = str[i];
}
thisObj.length = innerLength;
}
updateLength();
this.toString = function() {
return str;
}
this.split = function(re) {
var arr = Array.prototype.slice.call(this.toString());
if (re === "") {
return arr
}
if (re === " ") {
return arr.filter(function(el) {
return /[^\s]/.test(el)
})
}
if (/RegExp/.test(Object.getPrototypeOf(re).constructor)) {
var regexp = re.source;
return arr.filter(function(el) {
return regexp.indexOf(el) === -1
})
}
}
this.charAt = function(i) {
if (isNaN(parseInt(i))) {
return this[0]
} else {
return this[i]
}
}
this.concat = function(string) {
str += string;
updateLength();
}
this.slice = function(start, end) {
var slicedString = "";
if (start >= 0 && end >= 00) {
for (start; start < end; start++) {
slicedString += str[start];
}
}
return slicedString;
}
this.reverse = function() {
var arr = str.split("");
arr.reverse();
var reversedString = "",
i;
for (i = 0; i < arr.length; i++) {
reversedString += arr[i];
}
return reversedString;
}
}
var ms = new MyString("Hello, I am a string")
console.log(ms.split(""), ms.split(" "), ms.split(/l/))
Iterate and search:
MyString.prototype.split = function(splitter){
var tmp="", result = [];
for(var i = 0; i < this.length; i++){
for(var offset = 0; offset < this.length && offset < splitter.length;offset++){
if(this[i+offset] !== splitter[offset]) break;
}
if(offset === splitter.length){
result.push( tmp );
tmp="";
i += offset -1;
}else{
tmp+=this[i];
}
}
result.push(tmp);
return result;
};
So now you can do:
new MyString("testtest").split("t") //['','es','','','es','']
In action
I am trying to console every data in a binary tree. My main problem is that I want to implementing in a recursive way. Basically I have this code so far:
this.levelOrder = function (root) {
if (root.data != null) {
console.log(root.data);
if (root.left != null) {
this.levelOrder(root.left);
}
if (root.right != null) {
this.levelOrder(root.right)
}
} else {
return;
}
};
The output is 3 2 1 5 4 7
But It should be 3 2 5 1 4 7. So Basically I am accessing the first child of the node instead of printing all the children first.
Assuming a tree like this,
4
2 6
1 3 5 7
and an object literal
tree = {
data: 4,
left: {
data: 2,
left: {
data: 1,
left: null,
right: null
},
right: {
data: 3,
left: null,
right: null
}
},
right: {
data: 6,
left: {
data: 5,
left: null,
right: null
},
right: {
data: 7,
left: null,
right: null
}
}
};
you could call the function recursively and get first the left part and then the right part of the tree. The algorithm is called depth-first search.
This function uses a single check, because this is sufficient to check first and then to move on.
var depthFirst = function (node) {
if (node) {
console.log(node.data);
depthFirst(node.left);
depthFirst(node.right)
}
},
tree = { data: 4, left: { data: 2, left: { data: 1, left: null, right: null }, right: { data: 3, left: null, right: null } }, right: { data: 6, left: { data: 5, left: null, right: null }, right: { data: 7, left: null, right: null } } };
depthFirst(tree); // 4 2 1 3 6 5 7
For a breadth-first search, an algorithm which is iterating every level of the tree first, you could use this code with the same tree data as above.
var breadthFirst = function (node) {
function bf(queue) {
var newQueue = [];
queue.forEach(function (node) {
console.log(node.data);
node.left && newQueue.push(node.left);
node.right && newQueue.push(node.right);
});
newQueue.length && bf(newQueue);
}
bf([node]);
},
tree = { data: 4, left: { data: 2, left: { data: 1, left: null, right: null }, right: { data: 3, left: null, right: null } }, right: { data: 6, left: { data: 5, left: null, right: null }, right: { data: 7, left: null, right: null } } };
breadthFirst(tree); // 4 2 6 1 3 5 7
Assuming the Node definition is like this:
export default class Node {
constructor(data) {
this.data = data;
this.left = null;
this.right = null;
}
}
And the Tree definition is something like this:
import Node from './Node.js';
export default class BinarySearchTree {
constructor() {
this.root = null;
}
//methods such as search, traversal, insert, etc...
}
You can perform the traversals like this (preferably as methods of the BinarySearchTree) :
inorder(node) {
if (node !== null) {
this.inorder(node.left);
console.log(node.data);
this.inorder(node.right);
}
}
postorder(node) {
if (node !== null) {
this.postorder(node.left);
this.postorder(node.right);
console.log(node.data);
}
}
preorder(node) {
if (node !== null) {
console.log(node.data);
this.preorder(node.left);
this.preorder(node.right);
}
}
If you want to store the result of the traversal, you could pass down some object the methods would append the values to.
--
BONUS
If you want to display the whole tree in a somewhat pleasing manner, I'd suggest using some additional information stored in the nodes and in the state of your app.
First I'd add some additional info to each Node definition (x,y):
export default class Node {
constructor(data) {
this.data = data;
this.left = null;
this.right = null;
this.x = 0;
this.y = 0;
}
}
Then create an empty grid (matrix) based on the current tree data,
createMatrix = (minX, maxX, depth) => {
let displayMatrix = [];
for (let i = 0; i <= depth; i++) {
let line = [];
for (let j = 0; j <= maxX - minX; j++) {
line.push(' ');
}
displayMatrix.push(line);
}
return displayMatrix;
}
Where minX is the smallest X value among all nodes, and maxX is the largest (getting these values could happen while inserting the nodes into the tree, or by traversing the tree and comparing node properties), and then fill it with the node data and the "\", "/" characters between nodes:
fillMatrix(node, matrix, minX, side = "root") {
if(node !== null){
matrix[node.y][node.x + Math.abs(minX)] = node.data;
if(side === "left"){
matrix[node.y-1][node.x + Math.abs(minX)+1] = '/';
}
else if (side === "right"){
matrix[node.y - 1][node.x + Math.abs(minX) - 1] = '\\';
}
this.fillMatrix(node.left, matrix, minX, "left");
this.fillMatrix(node.right, matrix, minX, "right");
}
return matrix;
}
Then this result matrix would hold a " " where there are no 'lines' and no values, hold the numbers where the node.data are, and hold "/" or "\" as the lines between the nodes.
Then you can use this to display it on a canvas or in the console.
For example, here I created a table dynamically, and in every element there is a div, which is formatted differently based on what kind of character it holds:
/* |
h
/ \
d l
/ \ / \
b f j n
/ \ / \ / \ / \
a c e g i k m o
bt.insert("d");
bt.insert("l");
bt.insert("b");
bt.insert("f");
bt.insert("j");
bt.insert("n");
bt.insert("a");
bt.insert("c");
bt.insert("e");
bt.insert("g");
bt.insert("i");
bt.insert("k");
bt.insert("m");
bt.insert("o");
*/
function printTree(inorder, bredthOrder, size) {
size = size || 2;
const total = inorder.length;
const numberOfLevels = Math.ceil(Math.log2(total));
let pointerBOrder = 0;
let blank = " ";
for (let level = 0; level < numberOfLevels; level++) {
let itemPerLevel = Math.pow(2, level);
let out = [];
let branches = [];
let slantDirection = true;//up
for (let itemNumber = 0; itemNumber < itemPerLevel; itemNumber++) {
let item = bredthOrder[pointerBOrder++];
for (let x = 1; x <= inorder.length; x++) {
const ino = inorder[x - 1];
let nInd = size * (x - 1);
if (item == ino) {
out[nInd] = item;
if (item) {
if (bredthOrder[0] == item) {
branches[nInd] = "|";
} else if (slantDirection) {
branches[nInd + 1] = "/";
} else {
if (nInd - 1 >= 0) {
branches[nInd - 1] = "\\";
}
}
slantDirection = !slantDirection;
}
} else {
out[nInd] = out[nInd] || blank;
}
branches[nInd] = branches[nInd] || blank;
for (let fill = 1; fill <= size - 1; fill++) {
out[nInd + fill] = blank;
branches[nInd + fill] = branches[nInd + fill] || blank;
}
}
}
console.log(branches.join(''));
console.log(out.join(''));
out = [];
}
}
class Node {
constructor(value) {
this.left = null;
this.right = null;
this.value = value;
}
}
class BinaryTree {
constructor(value) {
this.root = value == null ? null : new Node(value);
}
insert(value, node) {
if (node == null) {
node = this.root;
}
if (node == null) {
node = new Node(value);
return;
}
if (node.value >= value) {
if (node.left == null) {
node.left = new Node(value);
return;
} else {
this.insert(value, node.left);
return;
}
} else {
if (node.right == null) {
node.right = new Node(value);
return;
} else {
this.insert(value, node.right);
return;
}
}
}
print(node, acc) {
if (node == null) {
return acc;
} else {
if (node.left != null) {
acc = this.print(node.left, acc);
}
acc.push(node.value);
if (node.right != null) {
acc = this.print(node.right, acc);
}
return acc;
}
}
printInOrder() {
return this.print(this.root, []);
}
getSiblings(node) {
if (node == null) {
return [];
}
let acc = [];
if (node.left != null) {
acc.push(node.left);
}
if (node.right != null) {
acc.push(node.right);
}
return acc;
}
printBredthOrder() {
let result = [];
if (this.root == null) {
} else {
let acc = [this.root];
let workingAcc = [this.root];
let tmpAcc = [];
do {
tmpAcc = [];
for (let i = 0; i < workingAcc.length; i++) {
acc = [...acc, workingAcc[i].left];
acc = [...acc, workingAcc[i].right];
let left = this.getSiblings(workingAcc[i].left);
let right = this.getSiblings(workingAcc[i].right);
tmpAcc = [...tmpAcc, ...left];
tmpAcc = [...tmpAcc, ...right];
}
acc = [...acc, ...tmpAcc];
workingAcc = tmpAcc;
} while (tmpAcc.length != 0);
for (let i = 0; i < acc.length; i++) {
result.push(acc[i].value);
}
}
return result;
}
}
let bt = new BinaryTree("h");
bt.insert("d");
bt.insert("l");
bt.insert("b");
bt.insert("f");
bt.insert("j");
bt.insert("n");
bt.insert("a");
bt.insert("c");
bt.insert("e");
bt.insert("g");
bt.insert("i");
bt.insert("k");
bt.insert("m");
bt.insert("o");
let io = bt.printInOrder();
let bo = bt.printBredthOrder();
printTree(io, bo);
// Node Class
class Node {
constructor (val, left=null, right=null) {
this.val = val;
this.left = left;
this.right = right;
}
}
// Binary Tree
const head = new Node(20,
new Node(10,
new Node(5,
new Node(3,
new Node(2,
new Node(1)),
new Node(4)),
new Node(8,
new Node(7,
new Node(6)),
new Node(9))),
new Node(15,
new Node(13,
new Node(11,
null,
new Node(12)),
new Node(14)),
new Node(18,
new Node(16,
null,
new Node(17)),
new Node(19)))),
new Node(30, new Node(25,
new Node(23,
new Node(22,
new Node(21)),
new Node(24)),
new Node(28,
new Node(27,
new Node(26)),
new Node(29))),
new Node(35,
new Node(33,
new Node(31,
null,
new Node(32)),
new Node(34)),
new Node(38,
new Node(36,
null,
new Node(37)),
new Node(39,
null,
new Node(40))))),
);
// VIEW
// 20
// 10 30
// 5 15 | 25 35
// 3 8 | 13 18 || 23 28 | 33 38
// 2 4 | 7 9 || 11 14 | 16 19 ||| 22 24 | 27 29 || 31 34 | 36 39
//1 n | n n || 6 n | n n ||| n 12 | n n || 17 n | n n |||| 21 n | n n || 26 n | n n |||| n 32 | n n || n 37 | n 40
// In Order Tree Traversal
const inOrder = (node) => {
if(node.left !== null) {
inOrder(node.left);
}
console.log(node.val);
if(node.right !== null) {
inOrder(node.right);
}
}
// Pre Order Tree Traversal
const preOrder = (node) => {
console.log(node.val);
if(node.left !== null) {
preOrder(node.left);
}
if(node.right !== null) {
preOrder(node.right);
}
}
// Post Order Tree Traversal
const postOrder = (node) => {
if(node.left !== null) {
postOrder(node.left);
}
if(node.right !== null) {
postOrder(node.right);
}
console.log(node.val);
}
// Node Count Recursively
const nodeCount = (node) => {
if(node.left !== null) {
nodeCount(node.left);
}
if(node.right !== null) {
nodeCount(node.right);
}
count++;
}
// Sum of all Nodes Recursively
const totalValue = (node) => {
if(node.left !== null) {
totalValue(node.left);
}
if(node.right !== null) {
totalValue(node.right);
}
total += node.val;
}
// inOrder(head);
// preOrder(head);
// postOrder(head);
let count = 0;
nodeCount(head)
console.log(count);
let total = 0;
totalValue(head)
console.log(total);
// NOTE
// if the values are continuous between 1/0 and n then the total is simply (n*(n+!))/2
// if the values are continuous between m and n then the total is simply ((n*(n+!))/2) - ((m*(m+!))/2)
I created a list iterator but when trying traverse a list backward the loop runs infinitely. What I did wrong?
function List() {
this.listSize=0;
this.pos=0;
this.dataStore =[];
this.append = append;
this.currPos = currPos;
this.end = end;
this.front = front;
this.length = length;
this.moveTo = moveTo;
this.next = next;
this.prev = prev;
}
function append(element) {this.dataStore[this.listSize++]=element;}
function currPos() {return this.pos;}
function end() {this.pos = this.listSize-1;}
function front() {this.pos =0;}
function length() {return this.listSize;}
function moveTo(position) {this.pos = position;}
function prev() {if(this.pos > 0) --this.pos;}
function next() {if(this.pos < this.listSize) ++this.pos;}
var names = new List();
names.append("A"); names.append("B"); names.append("C");
for(names.end(); names.currPos() >= 0; names.prev()) {console.log(names.getElement());}
Your loop only terminates when the current list position is less than zero, but your .prev() function won't allow that to happen.
To fix it? Well, that's a matter of opinion, but if you're going to the trouble of implementing a list class you might as well make a native .forEach function:
function forEach(callback) {
for (var i = 0; i < this.listSize; ++i)
callback(this.dataStore[i], i);
}
Then you can do:
names.forEach(function(name) { console.log(name); });
I ran into a similar problem when trying to implement a list ADT from the book "Data Structures and Algorithms" and came to find out that the author re-wrote that section in later versions to look like this:
module.exports = List;
function List() {
this.listSize = 0;
this.pos = 0;
this.dataStore = [];
this.clear = clear;
this.find = find;
this.toString = toString;
this.insert = insert;
this.append = append;
this.remove = remove;
this.front = front;
this.end = end;
this.prev = prev;
this.next = next;
this.length = length;
this.currPos = currPos;
this.moveTo = moveTo;
this.getElement = getElement;
this.length = length;
this.contains = contains;
this.hasNext = hasNext;
this.hasPrevious = hasPrevious;
this.insertIf = insertIf;
}
function append(element) {
this.dataStore[this.listSize++] = element;
}
function find(element) {
for (var i = 0; i < this.dataStore.length; ++i) {
if (this.dataStore[i] === element) {
return i;
}
}
return -1;
}
function remove(element) {
var foundAt = this.find(element);
if (foundAt > -1) {
this.dataStore.splice(foundAt, 1);
--this.listSize;
return true;
}
return false;
}
function length() {
return this.listSize;
}
function toString() {
return this.dataStore;
}
function insert(element, after){
var insertPos = this.find(after);
if(insertPos > -1){
this.dataStore.splice(insertPos+1, 0, element);
++this.listSize;
return true;
}
return false;
}
function clear() {
delete this.dataStore;
this.dataStore = [];
this.listSize = this.pos = 0;
}
function contains(element) {
for (var i = 0; i < this.dataStore.length; ++i) {
if(this.dataStore[i] === element) {
return true;
}
}
return false;
}
function front() {
this.pos = 0;
}
function end() {
this.pos = this.listSize-1;
}
function prev() {
return this.dataStore[--this.pos];
}
function next(){
return this.dataStore[this.pos++];
}
function hasNext(){
if (this.pos > this.listSize -1) {
return false;
} else {
return true;
}
}
function hasPrevious() {
if(this.pos <= 0) {
return false;
} else {
return true;
}
}
function currPos() {
return this.pos;
}
function moveTo(position) {
this.pos = position;
}
function getElement() {
return this.dataStore[this.pos];
}
function insertIf(element) {
var greaterThan = true;
for(this.front(); this.hasNext(); ){
if(this.next() > element) {
greaterThan = false;
break;
}
}
console.log(element);
if(greaterThan){
this.append(element);
return true;
} else {
return false;
}
}
Your loops will then look like this:
for (list.front(); list.hasNext();) {
var listItem = list.next();
if(listItem instanceof Customer) {
console.log(listItem.name + ", " + listItem.movie);
} else {
console.log(listItem);
}
}
This has proven to be a much more reliable implementation so you may want to consider switching to this.
You need to change your for loop to:
for(names.end(); names.currPos() > 0; names.prev())
DOM4 compareDocumentPosition
I want to implement compareDocumentPosition. Resig has made a great start at doing just this. I've taken his code and neatened it up
function compareDocumentPosition(other) {
var ret = 0;
if (this.contains) {
if (this !== other && this.contains(other)) {
ret += 16;
}
if (this !== other && other.contains(this)) {
ret += 8;
}
if (this.sourceIndex >= 0 && other.sourceIndex >= 0) {
if (this.sourceIndex < other.sourceIndex) {
ret += 4;
}
if (this.sourceIndex > other.sourceIndex) {
ret += 2;
}
} else {
ret += 1;
}
}
return ret;
}
This works for Element but does not for Text or DocumentFragment. This is because IE8 does not give .sourceIndex on those nodes. (It doesn't give .contains either but I've fixed that problem already)
How do I efficiently write the +=4 and +=2 bit which correspond to DOCUMENT_POSITION_FOLLOWING and DOCUMENT_POSITION_PRECEDING.
For extra reference those two are defined by tree-order which DOM4 defines as
An object A is preceding an object B if A and B are in the same tree and A comes before B in tree order.
An object A is following an object B if A and B are in the same tree and A comes after B in tree order.
The tree order is preorder, depth-first traversal.
Most modern browsers implement this (including IE9). So you only need something that works in IE8 (I don't care about IE6/7, but if it works awesome!)
function recursivelyWalk(nodes, cb) {
for (var i = 0, len = nodes.length; i < len; i++) {
var node = nodes[i];
var ret = cb(node);
if (ret) {
return ret;
}
if (node.childNodes && node.childNodes.length) {
var ret = recursivelyWalk(node.childNodes, cb);
if (ret) {
return ret;
}
}
}
}
function testNodeForComparePosition(node, other) {
if (node === other) {
return true;
}
}
function compareDocumentPosition(other) {
function identifyWhichIsFirst(node) {
if (node === other) {
return "other";
} else if (node === reference) {
return "reference";
}
}
var reference = this,
referenceTop = this,
otherTop = other;
if (this === other) {
return 0;
}
while (referenceTop.parentNode) {
referenceTop = referenceTop.parentNode;
}
while (otherTop.parentNode) {
otherTop = otherTop.parentNode;
}
if (referenceTop !== otherTop) {
return Node.DOCUMENT_POSITION_DISCONNECTED;
}
var children = reference.childNodes;
var ret = recursivelyWalk(
children,
testNodeForComparePosition.bind(null, other)
);
if (ret) {
return Node.DOCUMENT_POSITION_CONTAINED_BY +
Node.DOCUMENT_POSITION_FOLLOWING;
}
var children = other.childNodes;
var ret = recursivelyWalk(
children,
testNodeForComparePosition.bind(null, reference)
);
if (ret) {
return Node.DOCUMENT_POSITION_CONTAINS +
Node.DOCUMENT_POSITION_PRECEDING;
}
var ret = recursivelyWalk(
[referenceTop],
identifyWhichIsFirst
);
if (ret === "other") {
return Node.DOCUMENT_POSITION_PRECEDING;
} else {
return Node.DOCUMENT_POSITION_FOLLOWING;
}
}
I wrote it myself. I thought this implementation was bugged but it was a bug in some other code of mine. Seems pretty solid.
The answer from Raynos is a top start, but is not runnable out of the box. Node.* cannot be found and .bind is not available in IE8.
Here is the code ready for use in Internet Explorer 8:
function recursivelyWalk(nodes, cb) {
for (var i = 0, len = nodes.length; i < len; i++) {
var node = nodes[i];
var ret = cb(node);
if (ret) {
return ret;
}
if (node.childNodes && node.childNodes.length) {
var ret = recursivelyWalk(node.childNodes, cb);
if (ret) {
return ret;
}
}
}
}
function testNodeForComparePosition(node, other) {
if (node === other) {
return true;
}
}
var DOCUMENT_POSITION_DISCONNECTED = 1;
var DOCUMENT_POSITION_PRECEDING = 2;
var DOCUMENT_POSITION_FOLLOWING = 4;
var DOCUMENT_POSITION_CONTAINS = 8;
var DOCUMENT_POSITION_CONTAINED_BY = 16;
function compareDocumentPosition(thisNode, other) {
function identifyWhichIsFirst(node) {
if (node === other) {
return "other";
} else if (node === reference) {
return "reference";
}
}
var reference = thisNode,
referenceTop = thisNode,
otherTop = other;
if (this === other) {
return 0;
}
while (referenceTop.parentNode) {
referenceTop = referenceTop.parentNode;
}
while (otherTop.parentNode) {
otherTop = otherTop.parentNode;
}
if (referenceTop !== otherTop) {
return DOCUMENT_POSITION_DISCONNECTED;
}
var children = reference.childNodes;
var ret = recursivelyWalk(
children,
function(p) {
(function() {
var localOther = other;
return testNodeForComparePosition(localOther, p);
})();
}
);
if (ret) {
return DOCUMENT_POSITION_CONTAINED_BY +
DOCUMENT_POSITION_FOLLOWING;
}
var children = other.childNodes;
var ret = recursivelyWalk(
children,
function(p) {
(function() {
var localOther = reference;
return testNodeForComparePosition(localOther, p);
})();
}
);
if (ret) {
return DOCUMENT_POSITION_CONTAINS +
DOCUMENT_POSITION_PRECEDING;
}
var ret = recursivelyWalk(
[referenceTop],
identifyWhichIsFirst
);
if (ret === "other") {
return DOCUMENT_POSITION_PRECEDING;
} else {
return DOCUMENT_POSITION_FOLLOWING;
}
}
You call it like this:
compareDocumentPosition(sourceElement, elementToTest)
(It's like calling sourceElement.compareDocumentPosition(elementToTest))