UNPKG

node-g3

Version:

G3 Framework

701 lines (566 loc) 15.9 kB
/////////////////////////////////////////////////////////////////////////////// // Copyright (C) 2010 AsNet Co., Ltd. // All Rights Reserved. These instructions, statements, computer // programs, and/or related material (collectively, the "Source") // contain unpublished information propietary to AsNet Co., Ltd // which is protected by US federal copyright law and by // international treaties. This Source may NOT be disclosed to // third parties, or be copied or duplicated, in whole or in // part, without the written consent of AsNet Co., Ltd. /////////////////////////////////////////////////////////////////////////////// var oop = require('./oop'), g3Assert = require('./assert'), log = require('./log').Log.getLogger('g3LinkedList'); var g3LinkedList = oop.Base.extend({ /** * The first link in the linked list */ __head: null, /** * The last link in the linked list */ __tail: null, /** * {boolean} True indicates that the same item couldn't be added to the list * twice. */ __bNoDuplicate: false, /** * {boolean} * True indicates that the link mixin could be reused across all possible linked lists */ __bShareLink: false, /** * The unique linked list identified to */ __id: 0, /** * {int} The total item available in the list */ __nCnt: 0, /////////////////////////////////////////////////////////////////////// /** * Initializes a linked list data structure. In order to build the links * among list item, this object need to mixin a pair of <prev, next> pointers. * If the item is only used for maximum one list at a time, the mixin could be * reused. In this case, the argument bShareLink should be set to true. * * @param {boolean} bNoDuplicate * True indicates that the the linked list wouldn't allow the same item * could be addded to the list twice. If this argument is false, the * bShareLink is couldn't be used. It is important to notice that, if * this argument is true, the linked list performance would be a lot * faster since link mixin could stored as in the item itself. The complexity * for searching the link mixin for a given item is O(1). As a result, the * remove(), insertBefore(), insertAfter() functions would have O(1) performance. * * @param {boolean} bShareLink * True indicates that the link mixin should be reused (not need to be unique). */ constructor: function(bNoDuplicate, bShareLink) { if (bNoDuplicate) { this.__bNoDuplicate = true; if (bShareLink) { this.__bShareLink = true; } else { // Gets the unique linked list id this.__id = "__LL_" + g3LinkedList.__idSeq++; } } else { g3Assert.isFalse(bShareLink, "E9390234234") } }, /////////////////////////////////////////////////////////////////////// /** * Gets the total items available in the list * @return {int} The total item */ getCount: function() { return this.__nCnt; }, /** * Determines if the list is empty * @return {boolean} True if the list is empty. Otherwise, return false. */ isEmpty: function() { return this.__nCnt == 0; }, /** * Gets the first item in the linked list * @return {*} The item */ getFirst: function() { var item = this.__head; if (item) return item.data; else return null; }, /** * Gets the last item in the linked list * @return {*} The item */ getLast: function() { var item = this.__tail; if (item) return item.data; else return null; }, /** * Gets the item at the specified index * @param {int} nIndex * The index. If the index is negative, gets the item from the end of the list * @return {*} The item at the specified index */ getByIndex: function(nIndex) { var nCnt = this.__nCnt; g3Assert.isInRange(nIndex, -nCnt, nCnt-1, "E939203232"); var item, i, bSearchTail; bSearchTail = (nIndex < 0 && nIndex > -nCnt/2) || (nIndex > nCnt/2); if (bSearchTail) { if (nIndex > 0) nIndex = nIndex - nCnt; item = this.__tail; for (i=nIndex+1; i<0; i++) { item = item.prev; } } else { if (nIndex < 0) nIndex = nIndex + nCnt; item = this.__head; for (i=0; i<nIndex; i++) { item = item.next; } } return item.data; }, /////////////////////////////////////////////////////////////////////// /** * Adds the specified at the beginning of the list * @param {*} item * The item to add * @return {boolean} Itself for chainability. */ addFirst: function(item) { item = this.__getLink(item, false); this.__nCnt++; var head = this.__head, tail = this.__tail; if (head) { // Makes the current item as head item.next = head; item.prev = null; head.prev = item; this.__head = item; } else { // Makes the current item as both head & tail item.prev = null; item.next = null; this.__head = item; this.__tail = item; } return this; }, /** * Adds the specified item at the beginning of the list * @param {*} item * The item to add * @return {*} Itself for chainability */ addLast: function(item) { item = this.__getLink(item, false); this.__nCnt++; var head = this.__head, tail = this.__tail; if (tail) { // Makes the current item as tail item.next = null; item.prev = tail; tail.next = item; this.__tail = item; } else { // Makes the current item as both head & tail item.prev = null; item.next = null; this.__head = item; this.__tail = item; } return this; }, /////////////////////////////////////////////////////////////////////// // Insert /////////////////////////////////////////////////////////////////////// /** * Inserts the specified item at the specified index * @param {int} nIndex * The insert index. * @param {*} item * The item to insert * @return {g3LinkedList} Itself for chainability */ insertAt: function(nIndex, item) { g3Assert.notNull(item, "E920987654"); if (nIndex == this.__nCnt) { this.addLast(item); } else if (nIndex == 0) { this.addFirst(item); } else { var srcItem = this.getByIndex(nIndex); if (nIndex < 0) { // Inserts after the source item this.insertAfter(srcItem, item); } else { // Inserts before the source item this.insertBefore(srcItem, item); } } return this; }, /** * Inserts the specified item before the specified source item * @param {*} srcItem * The source item * @param {*} item * The item to insert * @return {g3LinkedList} Itself for chainability */ insertBefore: function(srcItem, item) { g3Assert.notNull(item, "E920934234"); g3Assert.notNull(srcItem, "E92304234"); this.__nCnt++; item = this.__getLink(item, false); srcItem = this.__getLink(srcItem, true); var prev = srcItem.prev; item.next = srcItem; srcItem.prev = item; item.prev = prev; if (prev) { prev.next = item; } else { this.__head = item; } return this; }, /** * Inserts the specified item before the specified source item * @param {*} srcItem * The source item * @param {*} item * The item to insert * @return {g3LinkedList} Itself for chainability */ insertAfter: function(srcItem, item) { g3Assert.notNull(item, "E92902912121"); g3Assert.notNull(srcItem, "E23409234234"); this.__nCnt++; item = this.__getLink(item, false); srcItem = this.__getLink(srcItem, true); var next = srcItem.next; item.prev = srcItem; srcItem.next = item; item.next = next; if (next) { next.prev = item; } else { this.__tail = item; } return this; }, /////////////////////////////////////////////////////////////////////// /** * If the list is not empty, * remove and return the first item in the list. * @return {*} The first item */ pollFirst: function() { if (this.__nCnt === 0) { return null; } else { // Remove head element var item = this.__head; this.remove(item); return item.data; } }, /** * If the list is not empty, * remove and return the last item in the list. * @return {*} The last item */ pollLast: function() { if (this.__nCnt === 0) { return null; } else { // Remove head element var item = this.__tail; this.remove(item); return item.data; } }, peekFirst: function() { return this.getFirst(); }, peekLast: function() { return this.getLast(); }, /////////////////////////////////////////////////////////////////////// // Deleting /////////////////////////////////////////////////////////////////////// /** * Removes the specified item from the list. * * @param {*} item * The item to remove * @return {g3LinkedList} Itself for chainability */ remove: function(item) { // TODO this is slow. Hack for now if (!this.contains(item)) return false; item = this.__getLink(item, true); g3Assert.isTrue(this.__nCnt>0, "E923042934234"); this.__nCnt--; if (this.__nCnt == 0) { this.__head = null; this.__tail = null; } else { var prev = item.prev, next = item.next; if (prev) prev.next = next; else this.__head = next; if (next) next.prev = prev; else this.__tail = prev; } item.next = null; item.prev = null; return true; }, /** * Clears the list * @return {g3LinkedList} Itself for chainability. */ clear: function() { this.__nCnt = 0; this.__head = null; this.__tail = null; return this; }, /////////////////////////////////////////////////////////////////////// /** * Reverse the list * @return {g3LinkedList} Itself for chainability */ reverse: function() { var n = this.__nCnt; if (n<2) return this; var cur1 = this.__head.next, cur2 = this.__head, next, i; for (i=0; i<n-1; i++) { next = cur1.next; // Adds the current item (cur1) to the end cur1.next = cur2; cur2.prev = cur1; cur2 = cur1; cur1 = next; } // swap head & tail cur1 = this.__head; this.__head = this.__tail; this.__tail = cur1; this.__tail.next = null; this.__head.prev = null; }, /////////////////////////////////////////////////////////////////////// // Looping /////////////////////////////////////////////////////////////////////// /** * Iterate through all items of the linked list. * * @param {Function} callback * The callback function which would be called at each looping * step. The signature of this function is Function(item). * If this function returns true after being call, the list * will stop looping. * * @return {g3LinkedList} Itself for chainability */ forEach: function(callback, bReverse) { g3Assert.isFunction(callback, "E09897615537"); var item = (bReverse? this.__tail : this.__head), next = null; while (item) { // Caches the next item in the chain // just in case the current item is removed // while processing next = (bReverse? item.prev : item.next); if (callback(item.data)) // Stop looping return this; item = next; } return this; }, /////////////////////////////////////////////////////////////////////// // Searching /////////////////////////////////////////////////////////////////////// /** * Determines if the specified item exists in the list * @param {*} item * The item to check * @return {boolean} True indicates that the specified item exists in * the list. Otherwise return false. */ contains: function(item) { return this.indexOf(item) >= 0; }, /** * Gets the index of the item in the list. * @param {*} item * The item to search for * @return {int} The index of the item */ indexOf: function(item) { var nCnt = this.__nCnt; if (nCnt == 0) { return -1; } else { var cur = this.__head, i; for (i=0; i<nCnt; i++) { if (cur.data === item) return i; cur = cur.next; } return -1; } }, /** * Gets the last index of item from the list. * @param {*} item * The item to search for * @return {int} The index of the item */ lastIndexOf: function(item) { var nCnt = this.__nCnt; if (nCnt == 0) { // No element found return -1; } else { var cur = this.__tail, i; for (i=nCnt-1; i>=0; i--) { if (cur.data === item) return i; cur = cur.prev; } return -1; } }, /////////////////////////////////////////////////////////////////////// // Sorting /////////////////////////////////////////////////////////////////////// /** * Sorts the linked list * @param comparator */ sort: function(comparator) { g3Assert.failTodo("E39022233"); }, /////////////////////////////////////////////////////////////////////// /** * Gets the link mixin for the given item. * @param {*} item * The item */ __getLink: function(item, bExist) { g3Assert.isObject(item, "E93203923232"); var link = null; if (this.__bNoDuplicate) { if (this.__bShareLink) { link = item; if (link.data) return link else if (bExist) { g3Assert.fail("E92493824234"); } } else { var id = this.__id; link = item[id]; if (link) { return link; } else if (bExist) { g3Assert.fail("E829384234"); } link = item[id] = {}; } link.next = null; link.prev = null; link.data = item; } else { if (bExist) { // Will need to search the list to get the link var cur = this.__head; while (cur) { if (cur.data === item) { // Found the link link = cur; break; } cur = cur.next; } g3Assert.notNull(link, "E92903232"); } else { // Creates a new link for the current item link = { prev: null, next: null, data: item }; } } return link; }, /////////////////////////////////////////////////////////////////////// /** * Converts the linked list to array * @return {Object[]} The array of items in the list. */ toArray: function() { var a = []; if (!this.isEmpty()) { this.forEach(function(item) { a.push(item); }); } return a; }, /////////////////////////////////////////////////////////////////////// /** * Dumps the linked list to string. * The result would be similar to Array.toString * @return {String} The textual representation of the linked list */ toString: function() { var sb = new g3.text.StringBuilder(), item = this.__head, nCnt = this.__nCnt, i; sb.append("["); if (nCnt > 0) { for (i=0; i<nCnt-1; i++) { sb.append(item.data); sb.append(","); item = item.next; } sb.append(item.data); } sb.append("]"); return sb.toString(); } }, { /** * {int} The int sequencer used to allocate unique linked list id */ __idSeq: 0 }); module.exports = g3LinkedList;