towry
9/11/2015 - 6:52 AM

dynamiclist.js

/**
 * @tovvry
 * DynamicList solve this problem:
 * we push an item and return an index, the 
 * index is used in other place, but at some
 * point, the item is deleted from this list(the 
 * item still exists and the index is needed);
 * this will broken the index of all items that
 * follow the deleted item
 * performance: http://jsperf.com/array-test-slice-vs-delete
 */
function DynamicList () {
  this.head = null;
  this.tail = null;
  this.index = 0;
  this.size = 0;
}

DynamicList.prototype = {
  constructor: DynamicList,

  add: function (data, cb) {
    var item = new Item(data);
    item.index(this.index++);

    if (this.tail) {
      this.tail.next(item);
      this.tail = item;
    } else {
      this.head = this.tail = item;
    }

    if (cb && typeof cb === 'function') {
      cb(item, item.index());
    }
    this.size++;
    return item.index();
  }, 

  remove: function (itemOrIndex, data) {
    var item = this.find(itemOrIndex, data);
    if (!item) {
      return;
    }

    var prev = item.prev();
    var next = item.next();
    
    if (!prev) {
      this.head = next;
      this.head.prev(null);
    } else {
      prev.next(next);
      next.prev(prev);
    }
    if (!next) {
      this.tail = prev;
    }
    if (this.size >= 1) {
      this.size--;
    }
  },

  find: function (itemOrIndex, data) {
    if (!this.head) {
      return;
    }

    var index = typeof itemOrIndex === 'number' ? itemOrIndex : null;
    var item = index === null ? itemOrIndex : null;

    var start, dir = 0;
    if (index) {
      var diffA = index - this.head.index();
      var diffB = this.tail.index() - index;
      if (diffA <= diffB) {
        start = this.head;
        dir = 1;
      } else {
        start = this.tail;
      }
    } else {
      start = this.head;
      dir = 1;
    }

    var iter = start, counter = 0;
    var found;
    while (iter && counter <= this.size) {
      if (data && iter.data() === data) {
        found = iter;
      }
      else if (index && iter.index() === index) {
        found = iter;
      } else if (item && typeof item.index == 'function' && item.index() === iter) {
        found = iter;
      }

      if (found) {
        return found;
      }

      if (dir) {
        iter = iter.next();
      } else {
        iter = iter.prev();
      }
      counter++;
    }

    return found;
  }, 

  length: function () {
    return this.size;
  },

  isEmpty: function () {
    return this.size === 0;
  },

  /**
   * remove all stuff
   */
  empty: function () {
  }
}

function Item (data) {
  this._index = -1;
  this._prev = null;
  this._next = null;

  this._data = data;
}

Item.prototype = {
  constructor: Item,

  index: function (index) {
    if (index) {
      this._index = index;
    } else {
      return this._index;
    }
  }, 

  prev: function (item) {
    if (item) {
      this._prev = item;
      if (item.next() !== this) {
        item.next(this);
      }
    } else {
      return this._prev;
    }
  }, 

  next: function (item) {
    if (item) {
      this._next = item;
      if (item.prev() !== this) {
        item.prev(this);
      }
    } else {
      return this._next;
    }
  },

  data: function (d) {
    if (d) {
      this._data = d;
    }
    else {
      return this._data;
    }
  }, 
}