UNPKG

todomvc

Version:

> Helping you select an MV\* framework

275 lines (234 loc) 7.17 kB
/** MIT License (c) copyright B Cavalier & J Hann */ (function (define) { define(function () { var undef, missing = {}; /** * @constructor * @param identifier {Function} * @param comparator {Function} */ function SortedMap (identifier, comparator) { // identifier is required, comparator is optional this.clear(); /** * Fetches a value item for the given key item or the special object, * missing, if the value item was not found. * @private * @param keyItem * @returns {Object} the value item that was set for the supplied * key item or the special object, missing, if it was not found. */ this._fetch = function (keyItem) { var symbol = identifier(keyItem); return symbol in this._index ? this._index[symbol] : missing; }; /** * Performs a binary search to find the bucket position of a * key item within the key items list. Only used if we have a * comparator. * @private * @param keyItem * @param exactMatch {Boolean} if true, must be an exact match to the key * item, not just the correct position for a key item that sorts * the same. * @returns {Number|Undefined} */ this._pos = function (keyItem, exactMatch) { var pos, sorted, symbol; sorted = this._sorted; symbol = identifier(keyItem); function getKey (pos) { return sorted[pos] ? sorted[pos][0].key : {}; } pos = binarySearch(0, sorted.length, keyItem, getKey, comparator); if (exactMatch) { if (symbol != identifier(sorted[pos][0].key)) { pos = -1; } } return pos; }; this._bucketOffset = function (bucketPos) { var total, i; total = 0; for (i = 0; i < bucketPos; i++) { total += this._sorted[i].length; } return total; }; if (!comparator) { this._pos = function (keyItem, exact) { return exact ? -1 : this._sorted.length; }; } /** * Given a keyItem and its bucket position in the list of key items, * inserts an value item into the bucket of value items. * This method can be overridden by other objects that need to * have objects in the same order as the key values. * @private * @param valueItem * @param keyItem * @param pos * @returns {Number} the absolute position of this item amongst * all items in all buckets. */ this._insert = function (keyItem, pos, valueItem) { var pair, symbol, entry, absPos; // insert into index pair = { key: keyItem, value: valueItem }; symbol = identifier(keyItem); this._index[symbol] = pair; // insert into sorted table if (pos >= 0) { absPos = this._bucketOffset(pos); entry = this._sorted[pos] && this._sorted[pos][0]; // is this a new row (at end of array)? if (!entry) { this._sorted[pos] = [pair]; } // are there already items of the same sort position here? else if (comparator(entry.key, keyItem) == 0) { absPos += this._sorted[pos].push(pair) - 1; } // or do we need to insert a new row? else { this._sorted.splice(pos, 0, [pair]); } } else { absPos = -1; } return absPos; }; /** * Given a key item and its bucket position in the list of key items, * removes a value item from the bucket of value items. * This method can be overridden by other objects that need to * have objects in the same order as the key values. * @private * @param keyItem * @param pos * @returns {Number} the absolute position of this item amongst * all items in all buckets. */ this._remove = function remove (keyItem, pos) { var symbol, entries, i, entry, absPos; symbol = identifier(keyItem); // delete from index delete this._index[symbol]; // delete from sorted table if (pos >= 0) { absPos = this._bucketOffset(pos); entries = this._sorted[pos] || []; i = entries.length; // find it and remove it while ((entry = entries[--i])) { if (symbol == identifier(entry.key)) { entries.splice(i, 1); break; } } absPos += i; // if we removed all pairs at this position if (entries.length == 0) { this._sorted.splice(pos, 1); } } else { absPos = -1; } return absPos; }; this._setComparator = function (newComparator) { var p, pair, pos; comparator = newComparator; this._sorted = []; for (p in this._index) { pair = this._index[p]; pos = this._pos(pair.key); this._insert(pair.key, pos, pair.value); } }; } SortedMap.prototype = { get: function (keyItem) { var pair; pair = this._fetch(keyItem); return pair == missing ? undef : pair.value; }, add: function (keyItem, valueItem) { var pos, absPos; if (arguments.length < 2) throw new Error('SortedMap.add: must supply keyItem and valueItem args'); // don't insert twice. bail if we already have it if (this._fetch(keyItem) != missing) return; // find pos and insert pos = this._pos(keyItem); absPos = this._insert(keyItem, pos, valueItem); return absPos; }, remove: function (keyItem) { var valueItem, pos, absPos; // don't remove if we don't already have it valueItem = this._fetch(keyItem); if (valueItem == missing) return; // find positions and delete pos = this._pos(keyItem, true); absPos = this._remove(keyItem, pos); return absPos; }, forEach: function (lambda) { var i, j, len, len2, entries; for (i = 0, len = this._sorted.length; i < len; i++) { entries = this._sorted[i]; for (j = 0, len2 = entries.length; j < len2; j++) { lambda(entries[j].value, entries[j].key); } } }, clear: function() { // hashmap of object-object pairs this._index = {}; // 2d array of objects this._sorted = []; }, setComparator: function (comparator) { this._setComparator(comparator); } }; return SortedMap; /** * Searches through a list of items, looking for the correct slot * for a new item to be added. * @param min {Number} points at the first possible slot * @param max {Number} points at the slot after the last possible slot * @param item anything comparable via < and > * @param getter {Function} a function to retrieve a item at a specific * slot: function (pos) { return items[pos]; } * @param comparator {Function} function to compare to items. must return * a number. * @returns {Number} returns the slot where the item should be placed * into the list. */ function binarySearch (min, max, item, getter, comparator) { var mid, compare; if (max <= min) return min; do { mid = Math.floor((min + max) / 2); compare = comparator(item, getter(mid)); if (isNaN(compare)) throw new Error('SortedMap: invalid comparator result ' + compare); // if we've narrowed down to a choice of just two slots if (max - min <= 1) { return compare == 0 ? mid : compare > 0 ? max : min; } // don't use mid +/- 1 or we may miss in-between values if (compare > 0) min = mid; else if (compare < 0) max = mid; else return mid; } while (true); } }); }( typeof define == 'function' ? define : function (factory) { module.exports = factory(require); } ));