Permalink
Browse files

Convert SkipList to coffee

  • Loading branch information...
1 parent 7fe3142 commit 6284e0586db87ead1ceb98d60ce3750b3225ca3a @nathansobo committed Apr 12, 2012
Showing with 162 additions and 207 deletions.
  1. +162 −0 lib/assets/javascripts/monarch/util/skip_list.coffee
  2. +0 −207 lib/assets/javascripts/monarch/util/skip_list.js
@@ -0,0 +1,162 @@
+class Monarch.Util.SkipList
+ constructor: (@comparator=@defaultComparator) ->
+ @maxLevels = 8
+ @p = 0.25
+ @currentLevel = 0
+
+ # javascript has built in infinities, but we need to compare our infinities against
+ # any kind of key, including non-numbers
+ @minusInfinity = {}
+ @plusInfinity = {}
+
+ @head = new Monarch.Util.SkipListNode(@maxLevels, @minusInfinity, undefined)
+ @nil = new Monarch.Util.SkipListNode(@maxLevels, @plusInfinity, undefined)
+ for i in [0...@maxLevels]
+ @head.pointer[i] = @nil
+ @head.distance[i] = 1
+
+ insert: (key, value=key) ->
+ next = @buildNextArray()
+ nextDistance = @buildNextDistanceArray()
+ closestNode = @findClosestNode(key, next, nextDistance)
+
+ # if the key is a duplicate, replace its value. otherwise insert a node
+ if closestNode.key == key
+ closestNode.value = value
+ else
+ level = @randomLevel()
+
+ # if the overall level in increasing, set the new level and fill in the next array with @head for the new levels
+ if level > @currentLevel
+ next[i] = @head for i in [@currentLevel + 1..level]
+ @currentLevel = level
+
+ # create a new node and insert it by updating pointers at every level
+ newNode = new Monarch.Util.SkipListNode(level, key, value)
+ steps = 0
+ for i in [0..level]
+ prevNode = next[i]
+ newNode.pointer[i] = prevNode.pointer[i]
+ prevNode.pointer[i] = newNode
+ newNode.distance[i] = prevNode.distance[i] - steps
+ prevNode.distance[i] = steps + 1
+ steps += nextDistance[i]
+
+ maxLevels = @maxLevels
+ for i in [level + 1...maxLevels]
+ next[i].distance[i] += 1
+
+ _.sum(nextDistance)
+
+ insertAll: (array) ->
+ @insert(element) for element in array
+
+ remove: (key) ->
+ next = @buildNextArray()
+ nextDistance = @buildNextDistanceArray()
+ cursor = @findClosestNode(key, next, nextDistance)
+
+ if @compare(cursor.key, key) == 0
+ for i in [0..@currentLevel]
+ if next[i].pointer[i] == cursor
+ next[i].pointer[i] = cursor.pointer[i]
+ next[i].distance[i] += cursor.distance[i] - 1
+ else
+ next[i].distance[i] -= 1
+
+ # Check if we have to lower level
+ @currentLevel-- while @currentLevel > 0 && @head.pointer[@currentLevel] == @nil
+
+ _.sum(nextDistance)
+ else
+ -1
+
+ find: (key) ->
+ cursor = @findClosestNode(key)
+ if @compare(cursor.key, key) == 0
+ cursor.value
+ else
+ undefined
+
+ indexOf: (key) ->
+ nextDistance = @buildNextDistanceArray()
+ cursor = @findClosestNode(key, null, nextDistance)
+ if @compare(cursor.key, key) == 0
+ _.sum(nextDistance)
+ else
+ -1
+
+ at: (index) ->
+ index += 1
+ cursor = @head
+
+ for i in [@currentLevel..0]
+ while cursor.distance[i] <= index
+ index -= cursor.distance[i]
+ cursor = cursor.pointer[i]
+
+ if cursor == @nil
+ undefined
+ else
+ cursor.value
+
+ keys: ->
+ keys = []
+ cursor = @head.pointer[0]
+ while cursor != @nil
+ keys.push(cursor.key)
+ cursor = cursor.pointer[0]
+ keys
+
+ values: ->
+ values = []
+ cursor = @head.pointer[0]
+ while cursor != @nil
+ values.push(cursor.value)
+ cursor = cursor.pointer[0]
+ values
+
+ compare: (a, b) ->
+ if (a == @minusInfinity) then return (if (b == @minusInfinity) then 0 else -1)
+ if (b == @minusInfinity) then return (if (a == @minusInfinity) then 0 else 1)
+ if (a == @plusInfinity) then return (if (b == @plusInfinity) then 0 else 1)
+ if (b == @plusInfinity) then return (if (a == @plusInfinity) then 0 else -1)
+ return @comparator(a, b)
+
+ defaultComparator: (a, b) ->
+ return -1 if a < b
+ return 1 if a > b
+ 0
+
+ findClosestNode: (key, next, nextDistance) ->
+ # search the skiplist in a stairstep descent, following the highest path that doesn't overshoot the key
+ cursor = @head
+ for i in [@currentLevel..0]
+ # move forward as far as possible while keeping the cursor node's key less than the inserted key
+ while @compare(cursor.pointer[i].key, key) < 0
+ nextDistance?[i] += cursor.distance[i]
+ cursor = cursor.pointer[i]
+ # when the next link would be bigger than our key, drop a level
+ # before we do, note that this is the last node we visited at level i in the next array
+ next?[i] = cursor
+
+ # advance to the next node... it is the nearest node whose key is >= the search key
+ cursor.pointer[0]
+
+ randomLevel: ->
+ maxLevels = @maxLevels
+ level = 0
+ level++ while Math.random() < @p and level < maxLevels - 1
+ level
+
+ buildNextArray: ->
+ next = new Array(@maxLevels)
+ for i in [0...@maxLevels]
+ next[i] = @head
+ next
+
+ buildNextDistanceArray: ->
+ nextDistance = new Array(@maxLevels)
+ for i in [0...@maxLevels]
+ nextDistance[i] = 0
+ nextDistance
@@ -1,207 +0,0 @@
-(function(Monarch) {
- Monarch.Util.SkipList = new JS.Class('Monarch.Util.SkipList', {
- initialize: function(comparator) {
- this.comparator = comparator || this.defaultComparator;
- this.maxLevels = 8;
- this.p = 0.25;
- this.currentLevel = 0;
-
- this.minusInfinity = {};
- this.plusInfinity = {};
-
- this.head = new Monarch.Util.SkipListNode(this.maxLevels, this.minusInfinity, undefined);
- this.nil = new Monarch.Util.SkipListNode(this.maxLevels, this.plusInfinity, undefined);
- for (var i = 0; i < this.maxLevels; i++) {
- this.head.pointer[i] = this.nil;
- this.head.distance[i] = 1;
- }
- },
-
- insert: function(key, value) {
- if (!value) value = key;
- var next = this.buildNextArray();
- var nextDistance = this.buildNextDistanceArray();
- var closestNode = this.findClosestNode(key, next, nextDistance);
-
- // if the key is a duplicate, replace its value. otherwise insert a node
- if (closestNode.key == key) {
- closestNode.value = value;
- } else {
- var level = this.randomLevel();
-
- // if the overall level in increasing, set the new level and fill in the next array with this.head for the new levels
- if (level > this.currentLevel) {
- for (i = this.currentLevel + 1; i <= level; i++) next[i] = this.head;
- this.currentLevel = level;
- }
-
- // create a new node and insert it by updating pointers at every level
- var newNode = new Monarch.Util.SkipListNode(level, key, value);
- var steps = 0;
- for (var i = 0; i <= level; i++) {
- var prevNode = next[i];
- newNode.pointer[i] = prevNode.pointer[i];
- prevNode.pointer[i] = newNode;
- newNode.distance[i] = prevNode.distance[i] - steps;
- prevNode.distance[i] = steps + 1
- steps += nextDistance[i];
- }
-
- var maxLevels = this.maxLevels;
- for (var i = level + 1; i < maxLevels; i++) {
- next[i].distance[i] += 1;
- }
-
- return _.sum(nextDistance);
- }
- },
-
- insertAll: function(array) {
- _.each(array, function(elt) {
- this.insert(elt);
- }, this);
- },
-
- remove: function(key) {
- var next = this.buildNextArray();
- var nextDistance = this.buildNextDistanceArray();
- var cursor = this.findClosestNode(key, next, nextDistance);
-
- if (this.compare(cursor.key, key) === 0) {
- for (var i = 0; i <= this.currentLevel; i++) {
- if (next[i].pointer[i] === cursor) {
- next[i].pointer[i] = cursor.pointer[i];
- next[i].distance[i] += cursor.distance[i] - 1;
- } else {
- next[i].distance[i] -= 1;
- }
- }
-
- // Check if we have to lower level
- while (this.currentLevel > 0 && this.head.pointer[this.currentLevel] === this.nil) {
- this.currentLevel--;
- }
-
- return _.sum(nextDistance);
- } else {
- return -1;
- }
- },
-
- find: function(key) {
- var cursor = this.findClosestNode(key);
- if (this.compare(cursor.key, key) === 0) {
- return cursor.value;
- } else {
- return undefined;
- }
- },
-
- indexOf: function(key) {
- var nextDistance = this.buildNextDistanceArray();
- var cursor = this.findClosestNode(key, null, nextDistance);
- if (this.compare(cursor.key, key) === 0) {
- return _.sum(nextDistance);
- } else {
- return -1;
- }
- },
-
- at: function(index) {
- index += 1;
- var cursor = this.head;
-
- for (var i = this.currentLevel; i >= 0; i--) {
- while (cursor.distance[i] <= index) {
- index -= cursor.distance[i];
- cursor = cursor.pointer[i];
- }
- }
-
- if (cursor === this.nil) {
- return undefined;
- } else {
- return cursor.value;
- }
- },
-
- keys: function() {
- var keys = [];
- var cursor = this.head.pointer[0];
- while(cursor !== this.nil) {
- keys.push(cursor.key);
- cursor = cursor.pointer[0];
- }
- return keys;
- },
-
- values: function() {
- var values = [];
- var cursor = this.head.pointer[0];
- while(cursor !== this.nil) {
- values.push(cursor.value);
- cursor = cursor.pointer[0];
- }
- return values;
- },
-
- compare: function(a, b) {
- if (a === this.minusInfinity) return (b === this.minusInfinity) ? 0 : -1;
- if (b === this.minusInfinity) return (a === this.minusInfinity) ? 0 : 1;
- if (a === this.plusInfinity) return (b === this.plusInfinity) ? 0 : 1;
- if (b === this.plusInfinity) return (a === this.plusInfinity) ? 0 : -1;
- return this.comparator(a, b);
- },
-
- defaultComparator: function(a, b) {
- if (a < b) return - 1;
- if (a > b) return 1;
- return 0;
- },
-
- // private
-
- findClosestNode: function(key, next, nextDistance) {
- // search the skiplist in a stairstep descent, following the highest path that doesn't overshoot the key
- var cursor = this.head;
- for (var i = this.currentLevel; i >= 0; i--) {
- // move forward as far as possible while keeping the cursor node's key less than the inserted key
- while (this.compare(cursor.pointer[i].key, key) < 0) {
- if (nextDistance) nextDistance[i] += cursor.distance[i];
- cursor = cursor.pointer[i];
- }
- // when the next link would be bigger than our key, drop a level
- // before we do, note that this is the last node we visited at level i in the next array
- if (next) next[i] = cursor;
- }
-
- // advance to the next node... it is the nearest node whose key is >= the search key
- return cursor.pointer[0];
- },
-
- buildNextArray: function() {
- var maxLevels = this.maxLevels;
- var next = new Array(this.maxLevels);
- for (var i = 0; i < maxLevels; i++) {
- next[i] = this.head;
- }
- return next;
- },
-
- buildNextDistanceArray: function() {
- var maxLevels = this.maxLevels;
- var nextDistance = new Array(this.maxLevels);
- for (var i = 0; i < maxLevels; i++) {
- nextDistance[i] = 0;
- }
- return nextDistance;
- },
-
- randomLevel: function() {
- var maxLevels = this.maxLevels;
- var level = 0;
- while(Math.random() < this.p && level < maxLevels - 1) level++;
- return level;
- }
- });
-})(Monarch);

0 comments on commit 6284e05

Please sign in to comment.