ouija_bh

Skiplist with demo

Oct 11th, 2025 (edited)
187
0
Never
4
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
JavaScript 13.10 KB | Source Code | 0 0
  1. /* An implementation of skiplist. It has list methods get, set, add, absorb,
  2.  * remove, truncate, getNdx, @@iterator, size, clear, and toString. Ported
  3.  * from Open Data Structures by Pat Morin. Includes a short demonstration of
  4.  * those methods.
  5.  * Note: absorb (Morin Exercise 4..12) and truncate (Morin Exercise 4..11)
  6.  * are non-standard and have time complexity slightly higher than O(log n).
  7.  */
  8.  
  9. /** Node in a skiplist. credit: Morin SkiplistList.java. */
  10. class SLNode {
  11.   /** constructor()
  12.    * @param {any} val a value
  13.    * @param {number} ht height of this node
  14.    */
  15.   constructor(val, ht) {
  16.     this.val = val;
  17.     this.nx = new Array(ht + 1);  // of SLNode
  18.     this.len = new Array(ht + 1);  // of number
  19.   }
  20.  
  21.   /** height()
  22.    * @returns {number} the height of this node
  23.    */
  24.   height() {
  25.     return this.nx.length - 1;
  26.   }
  27. }
  28.  
  29. /** Properties of a skiplist. */
  30. class SLProps {
  31.   sl; // the SkiplistList
  32.   sn; // the SLNode on the left side of the skiplist
  33. }
  34.  
  35. /** Iterator of skiplist. credit: Morin SkiplistList.java. */
  36. class SLIterator {
  37.   #node; // SLNode
  38.   #i;  // number
  39.   #rm; // boolean
  40.   /** constructor() Create an iterator that takes O(log n) time per step.
  41.    * @param {SLProps} sp properties of the skiplist
  42.    */
  43.   constructor(sp) {
  44.     this.sp = sp;
  45.     this.#node = sp.sn;
  46.     this.#i = -1;
  47.     this.#rm = false;
  48.   }
  49.  
  50.   /** hasNext()
  51.    * @returns {boolean}
  52.    */
  53.   hasNext() {
  54.     return this.#node.nx[0] != undefined;
  55.   }
  56.  
  57.   /** next()
  58.    * @returns {any}
  59.    */
  60.   next() {
  61.     if (this.#node.nx[0] == undefined) { throw new Error("no such element"); }
  62.     this.#node = this.#node.nx[0];
  63.     this.#i++;
  64.     this.#rm = true;
  65.     return this.#node.val;
  66.   }
  67.  
  68.   remove() {
  69.     if (!this.#rm) { throw new Error("illegal state"); }
  70.     this.sp.sl.remove(this.#i);
  71.     this.#i--;
  72.     this.#rm = false;
  73.   }
  74. }
  75.  
  76. /** rand() Generate random number x such that 0 <= x < num.
  77.  * @param {number} num upper bound (exclusive)
  78.  * @returns {number}
  79.  */
  80. function rand(num) {
  81.   return Math.floor(Math.random() * num);
  82. }
  83.  
  84. /** A skiplist where all the standard operations take O(log n) time.
  85.  *  credit: Morin SkiplistList.java.
  86.  */
  87. class SkiplistList {
  88.   constructor() {
  89.     this.nEl = 0;  // the number of elements stored in the skiplist
  90.     this.sn = new SLNode(undefined, 32);
  91.     this.maxHt = 0; // the maximum height of any element
  92.     this.sp = new SLProps();
  93.     this.sp.sn = this.sn;
  94.     this.sp.sl = this;
  95.   }
  96.  
  97.   /** #findPred() Find the node that precedes list index i in the skiplist.
  98.    * @param {number} i the index i
  99.    * @returns {SLNode} the predecessor of the node at index i or the final
  100.    *              node if i exceeds size() - 1.
  101.    */
  102.   #findPred(i) {
  103.     var node = this.sn; // SLNode
  104.     var ht = this.maxHt;
  105.     var j = -1;   // index of the current node in list 0
  106.     while (ht >= 0) {
  107.       while (node.nx[ht] != undefined && j + node.len[ht] < i) {
  108.         j += node.len[ht];
  109.         node = node.nx[ht];
  110.       }
  111.       ht--;
  112.     }
  113.     return node;
  114.   }
  115.  
  116.   /** get()
  117.    * @param {number} i the index i
  118.    * @returns {any} value at index i
  119.    */
  120.   get(i) {
  121.     if (i < 0 || i > this.nEl - 1) { throw new Error("index out of bounds"); }
  122.     return this.#findPred(i).nx[0].val;
  123.   }
  124.  
  125.   /** set()
  126.    * @param {number} i the index i
  127.    * @param {any} val value to store at index i
  128.    * @returns {any} previous value at index i
  129.    */
  130.   set(i, val) {
  131.     if (i < 0 || i > this.nEl - 1) { throw new Error("index out of bounds"); }
  132.     var node = this.#findPred(i).nx[0]; // SLNode
  133.     var old = node.val;
  134.     node.val = val;
  135.     return old;
  136.   }
  137.  
  138.   /** #add() Insert a new node into the skiplist.
  139.    * @param {number} i the index of the new node
  140.    * @param {SLNode} nwNd the node to insert
  141.    * @returns {SLNode} the node that precedes nwNd in the skiplist
  142.    */
  143.   #add(i, nwNd) {
  144.     var node = this.sn;
  145.     var newHt = nwNd.height();
  146.     var ht = this.maxHt;
  147.     var j = -1; // index of node
  148.     while (ht >= 0) {
  149.       while (node.nx[ht] != undefined && j + node.len[ht] < i) {
  150.         j += node.len[ht];
  151.         node = node.nx[ht];
  152.       }
  153.       node.len[ht]++; // accounts for new node in list 0
  154.       if (ht <= newHt) {
  155.         nwNd.nx[ht] = node.nx[ht];
  156.         node.nx[ht] = nwNd;
  157.         nwNd.len[ht] = node.len[ht] - (i - j);
  158.         node.len[ht] = i - j;
  159.       }
  160.       ht--;
  161.     }
  162.     this.nEl++;
  163.     return node;
  164.   }
  165.  
  166.   /** #pickHeight() Simulate repeatedly tossing a coin until it comes up tails.
  167.    * @returns {number} the number of coin tosses - 1 (max 32)
  168.    */
  169.   #pickHeight() {
  170.     var z = rand(Number.MAX_SAFE_INTEGER);
  171.     var k = 0, m = 1;
  172.     while ((z & m) != 0) {
  173.       k++;
  174.       m = m << 1;
  175.     }
  176.     return k;
  177.   }
  178.  
  179.   /** add() Insert a value into the skiplist.
  180.    * @param {number} i the index of the new node
  181.    * @param {any} val the value to insert
  182.    */
  183.   add(i, val) {
  184.     if (i < 0 || i > this.nEl) { throw new Error("index out of bounds"); }
  185.     var nwNd = new SLNode(val, this.#pickHeight());
  186.     if (nwNd.height() > this.maxHt) { this.maxHt = nwNd.height(); }
  187.     this.#add(i, nwNd);
  188.   }
  189.  
  190.   /** absorb() Append another skiplist to this skiplist, leaving the other
  191.    *  skiplist empty.
  192.    * @param {SkiplistList} aSL skiplist to absorb
  193.    */
  194.   absorb(aSL) {
  195.     var node = this.sn;
  196.     var ht = this.maxHt;
  197.     var j = -1;
  198.     while (ht >= 0) {
  199.       if (ht > 0 && ht <= aSL.maxHt) {
  200.         if (node.nx[ht] == undefined) {
  201.           node.nx[ht] = aSL.sn.nx[ht];
  202.           aSL.sn.nx[ht] = undefined;
  203.           node.len[ht] = aSL.sn.len[ht] + this.nEl - (j + 1);
  204.           aSL.sn.len[ht] = 0;
  205.           ht--;
  206.         } else {
  207.           while (node.nx[ht] != undefined) {
  208.             j += node.len[ht];
  209.             node = node.nx[ht];
  210.           }
  211.         }
  212.       } else {
  213.         while (node.nx[ht] != undefined) {
  214.           j += node.len[ht];
  215.           node = node.nx[ht];
  216.         }
  217.         ht--;
  218.       }
  219.     }
  220.     node.nx[0] = aSL.sn.nx[0];
  221.     aSL.sn.nx[0] = undefined;
  222.     node.len[0] = aSL.sn.len[0];
  223.     aSL.sn.len[0] = 0;
  224.     if (this.maxHt < aSL.maxHt) {
  225.       for (ht = this.maxHt + 1; ht > 0 && ht <= aSL.maxHt; ht++) {
  226.         this.maxHt++;
  227.         this.sn.nx[this.maxHt] = aSL.sn.nx[ht];
  228.         aSL.sn.nx[ht] = undefined;
  229.         this.sn.len[this.maxHt] = aSL.sn.len[ht] + this.nEl;
  230.         aSL.sn.len[ht] = 0;
  231.       }
  232.     }
  233.     this.nEl += aSL.nEl;
  234.     aSL.maxHt = 0;
  235.     aSL.nEl = 0;
  236.   }
  237.  
  238.   /** remove() Remove a node from the skiplist.
  239.    * @param {number} i the index i
  240.    * @returns {any} the value removed
  241.    */
  242.   remove(i) {
  243.     if (i < 0 || i > this.nEl - 1) { throw new Error("index out of bounds"); }
  244.     var val = undefined;
  245.     var node = this.sn;
  246.     var ht = this.maxHt;
  247.     var j = -1; // index of node
  248.     while (ht >= 0) {
  249.       while (node.nx[ht] != undefined && j + node.len[ht] < i) {
  250.         j += node.len[ht];
  251.         node = node.nx[ht];
  252.       }
  253.       node.len[ht]--;  // for the node we are removing
  254.       if (j + node.len[ht] + 1 == i && node.nx[ht] != undefined) {
  255.         val = node.nx[ht].val;
  256.         node.len[ht] += node.nx[ht].len[ht];
  257.         node.nx[ht] = node.nx[ht].nx[ht];
  258.         if (node == this.sn && node.nx[ht] == undefined) { this.maxHt--; }
  259.       }
  260.       ht--;
  261.     }
  262.     this.nEl--;
  263.     return val;
  264.   }
  265.  
  266.   /** truncate() Truncate this skiplist so that it contains elements sl[0..i - 1]
  267.    *  and return a new skiplist with elements sl[i..size() - 1].
  268.    * @param {number} i the index i
  269.    * @returns {SkiplistList} skiplist with elements sl[i..size() - 1]
  270.    */
  271.   truncate(i) {
  272.     if (i < 0 || i > this.nEl - 1) { throw new Error("index out of bounds"); }
  273.     var nwL = new SkiplistList();
  274.     var node = this.sn;
  275.     var ht = this.maxHt;
  276.     var j = -1;
  277.     while (ht >= 0) {
  278.       while (node.nx[ht] != undefined && j + node.len[ht] < i) {
  279.         j += node.len[ht];
  280.         node = node.nx[ht];
  281.       }
  282.       if (node == this.sn && node.len[ht] > i) {
  283.         node.len[ht] -= i;
  284.         nwL.sn.nx.copyWithin(1, 0);
  285.         nwL.sn.nx[0] = node.nx[this.maxHt];
  286.         node.nx[this.maxHt] = undefined;
  287.         nwL.sn.len.copyWithin(1, 0);
  288.         nwL.sn.len[0] = node.len[this.maxHt];
  289.         node.len[this.maxHt] = 0;
  290.         nwL.maxHt++;
  291.         this.maxHt--;
  292.       } else if (j == i - 1) {
  293.         nwL.sn.nx.copyWithin(1, 0);
  294.         nwL.sn.nx[0] = node.nx.pop();
  295.         nwL.sn.len.copyWithin(1, 0);
  296.         nwL.sn.len[0] = node.len.pop();
  297.         nwL.maxHt++;
  298.       } else if (j + node.len[ht] >= i) {
  299.         node.nx.pop();
  300.         node.len.pop();
  301.       }
  302.       ht--;
  303.     }
  304.     nwL.nEl = this.nEl - i;
  305.     this.nEl = i;
  306.     return nwL;
  307.   }
  308.  
  309.   /** getNdx() Get the list index of a value.
  310.    * @param {any} val value to find
  311.    * @returns {number} index if found, -1 if not found
  312.    */
  313.   getNdx(val) {
  314.     var node = this.sn, i = -1;
  315.     while (node.nx[0] != undefined) {
  316.       node = node.nx[0];
  317.       i++;
  318.       if (node.val == val) { return i; }
  319.     }
  320.     return -1;
  321.   }
  322.  
  323.   /** @@iterator() Get an iterator of this list. */
  324.   *[Symbol.iterator]() {
  325.     var iter = new SLIterator(this.sp);
  326.     while (iter.hasNext()) { yield iter.next(); }
  327.   }
  328.  
  329.   /** size()
  330.    * @returns {number} the number of elements in this list
  331.    */
  332.   size() {
  333.     return this.nEl;
  334.   }
  335.  
  336.   /** clear() Remove all elements from this list. */
  337.   clear() {
  338.     this.sn.nx.fill(undefined, 0, this.maxHt + 1);
  339.     this.sn.len.fill(0, 0, this.maxHt + 1);
  340.     this.maxHt = 0;
  341.     this.nEl = 0;
  342.   }
  343.  
  344.   /** toString()
  345.    * @returns {string} list values formatted as array
  346.    */
  347.   toString() {
  348.     return JSON.stringify([...this]);
  349.   }
  350. }
  351.  
  352. /** compare() Compare values. This will work for string and number types.
  353.  * @param {any} a a value
  354.  * @param {any} b another value
  355.  * @returns {number} -1 if a < b, 0 if a == b, 1 if a > b
  356.  */
  357. function compare(a, b) {
  358.   if (a < b) { return -1; }
  359.   else if (a > b) { return 1; }
  360.   return 0;
  361. }
  362.  
  363. /** swap() Swap values at list indexes i and j in the skiplist.
  364.  *  credit: Morin Algorithms.java.
  365.  * @param {SkiplistList} sl the skiplist
  366.  * @param {number} i the index i
  367.  * @param {number} j the index j
  368.  */
  369. function swap(sl, i, j) {
  370.   var val = sl.set(i, sl.get(j));
  371.   sl.set(j, val);
  372. }
  373.  
  374. /** quickSort() Run quicksort on the sublist sl[i], ...,sl[i + num - 1].
  375.  *  credit: Morin Algorithms.java.
  376.  * @param {SkiplistList} sl the skiplist
  377.  * @param {number} i the index to start sort from
  378.  * @param {number} num the number of elements to sort
  379.  */
  380. function quickSort(sl, i, num) {
  381.   if (num <= 1) { return; }
  382.   var val = sl.get(i + rand(num));
  383.   var p = i - 1, j = i, q = i + num;
  384.   // sl[i..p] < val, sl[p + 1..q - 1] == val, sl[q..i + num - 1] > val
  385.   while (j < q) {
  386.     var cmp = compare(sl.get(j), val);
  387.     if (cmp < 0) {        // move to beginning of list
  388.       swap(sl, j++, ++p);
  389.     } else if (cmp > 0) { // move to end of list
  390.       swap(sl, j, --q);
  391.     } else {              // keep in the middle
  392.       j++;
  393.     }
  394.   }
  395.   quickSort(sl, i, p - i + 1);
  396.   quickSort(sl, q, num - (q - i));
  397. }
  398.  
  399. /** assertSL() Assert that sizes and values of list and array are equal.
  400.  * @param {SkiplistList} sl the skiplist
  401.  * @param {Array} arr the array
  402.  */
  403. function assertSL(sl, arr) {
  404.   if (sl.size() == arr.length) {
  405.     var i = 0;
  406.     for (const val of sl) {
  407.       if (compare(val, arr[i]) != 0) { throw new Error("assert: value mismatch"); }
  408.       i++;
  409.     }
  410.   } else {
  411.     throw new Error("assert: size mismatch");
  412.   }
  413. }
  414.  
  415.  
  416. var sL = new SkiplistList(), aSL = new SkiplistList();
  417. var num = 100, nVals = new Array(), midNV = Math.floor(num / 2);
  418.  
  419. for (var i = 0; i < midNV; i++) {   // add at end
  420.   nVals[i] = rand(num * 10);
  421.   aSL.add(i, nVals[i]);
  422. }
  423. console.log("--added " + midNV + " random numbers--");
  424. for (var i = midNV; i < num; i++) { // add at front
  425.   nVals.unshift(rand(num * 10));
  426.   sL.add(0, nVals[0]);
  427. }
  428. sL.absorb(aSL);
  429. console.log("--absorbed " + (num - midNV) + " random numbers--");
  430. console.log(sL.toString());
  431. assertSL(sL, nVals);
  432.  
  433. console.log("getNdx(" + (num * 10) + ") = " + sL.getNdx(num * 10));
  434. var val = sL.get(midNV);
  435. console.log("getNdx(" + val + ") = " + sL.getNdx(val));
  436.  
  437. nVals.sort(compare);
  438. quickSort(sL, 0, sL.size());
  439. console.log("--sorted " + num + " numbers using quicksort--");
  440. assertSL(sL, nVals);
  441. console.log(sL.toString());
  442.  
  443. var front = sL.get(0), mid = sL.get(midNV), end = sL.get(num - 1);
  444. console.log("getNdx(" + front + ") = " + sL.getNdx(front));
  445. console.log("getNdx(" + end + ") = " + sL.getNdx(end));
  446.  
  447. sL.remove(sL.size() - 1);
  448. sL.remove(midNV);
  449. sL.remove(0);
  450. nVals = nVals.slice(1, midNV).concat(nVals.slice(midNV + 1, num - 1));
  451. console.log("--removed end, middle, and front numbers (" + end + ", " + mid + ", " + front + ")--");
  452. assertSL(sL, nVals);
  453. console.log(sL.toString());
  454. console.log("size = " + sL.size());
  455.  
  456. var rmL = sL.truncate(Math.floor(sL.size() / 2));
  457. console.log("--truncated at middle--");
  458. console.log("sl = " + sL.toString());
  459. console.log("rm = " + rmL.toString());
  460.  
  461. sL.clear();
  462. console.log("--clear--");
  463. console.log("sl = " + sL.toString());
  464.  
  465. /** @version 1.0d */
Advertisement
Comments
  • ouija_bh
    27 days
    Comment was deleted
  • ouija_bh
    27 days
    Comment was deleted
  • ouija_bh
    22 days
    Comment was deleted
  • ouija_bh
    20 days
    # text 0.10 KB | 0 0
    1. Changes in 1.0d:
    2. - absorb() now drains the other skiplist correctly
    3. - clear() has improved performance
Add Comment
Please, Sign In to add comment