data-structures
Version:
JavaScript data structures written in CoffeeScript.
144 lines (122 loc) • 4.49 kB
text/coffeescript
###
Kind of a stopgap measure for the upcoming [JavaScript
Map](http://wiki.ecmascript.org/doku.php?id=harmony:simple_maps_and_sets)
**Note:** due to JavaScript's limitations, hashing something other than Boolean,
Number, String, Undefined, Null, RegExp, Function requires a hack that inserts a
hidden unique property into the object. This means `set`, `get`, `has` and
`delete` must employ the same object, and not a mere identical copy as in the
case of, say, a string.
## Overview example:
```js
var map = new Map({'alice': 'wonderland', 20: 'ok'});
map.set('20', 5); // => 5
map.get('20'); // => 5
map.has('alice'); // => true
map.delete(20) // => true
var arr = [1, 2];
map.add(arr, 'goody'); // => 'goody'
map.has(arr); // => true
map.has([1, 2]); // => false. Needs to compare by reference
map.forEach(function(key, value) {
console.log(key, value);
});
```
## Properties:
- size: The total number of `(key, value)` pairs.
###
# TODO: maybe change the hashing method for arrays and objects to something less
# hacky. We can recursively dig out their properties' value and join them, but
# that'd make it O(n).
# For hashing special types, e.g. objects, arrays and dates.
SPECIAL_TYPE_KEY_PREFIX = '_mapId_'
class Map
# Class variable and method.
@_mapIdTracker: 0
@_newMapId: -> @_mapIdTracker++
constructor: (objectToMap)->
###
Pass an optional object whose (key, value) pair will be hashed. **Careful**
not to pass something like {5: 'hi', '5': 'hello'}, since JavaScript's
native object behavior will crush the first 5 property before it gets to
constructor.
###
# _content is composed of (key, value) pairs where `value` itself is an
# array of two elements: first being the actual value to store, second being
# the original key, displayed for iteration.
@_content = {}
# Used to track objects and arrays.
@_itemId = 0
@_id = Map._newMapId()
@size = 0
@set key, value for own key, value of objectToMap
# Public. Allow user-defined hash function.
hash: (key, makeHash = no) ->
###
The hash function for hashing keys is public. Feel free to replace it with
your own. The `makeHash` parameter is optional and accepts a boolean
(defaults to `false`) indicating whether or not to produce a new hash (for
the first use, naturally).
_Returns:_ the hash.
###
# [object typeExtracted].
type = _extractDataType key
# Obscure hack to add a secret property to the object, used as key for hash
# map. Reason for doing so on array: [obj1, obj2] would have the same hash
# as [obj3, obj4].
if _isSpecialType key
propertyForMap = SPECIAL_TYPE_KEY_PREFIX + @_id
if makeHash and not key[propertyForMap]
key[propertyForMap] = @_itemId++
# Format: '_hashMapId'
return propertyForMap + '_' + key[propertyForMap]
else return type + '_' + key
set: (key, value) ->
###
_Returns:_ value.
###
if not @has key then @size++
@_content[@hash(key, yes)] = [value, key]
return value
get: (key) ->
###
_Returns:_ value corresponding to the key, or undefined if not found.
###
@_content[@hash key]?[0]
has: (key) ->
###
Check whether a value exists for the key.
_Returns:_ true or false.
###
@hash(key) of @_content
delete: (key) ->
###
Remove the (key, value) pair.
_Returns:_ **true or false**. Unlike most of this library, this method
doesn't return the deleted value. This is so that it conforms to the future
JavaScript `map.delete()`'s behavior.
###
hashedKey = @hash key
if hashedKey of @_content
delete @_content[hashedKey]
if _isSpecialType key then delete key[SPECIAL_TYPE_KEY_PREFIX + @_id]
@size--
return true
return false
forEach: (operation) ->
###
Traverse through the map. Pass a function of the form `fn(key, value)`.
_Returns:_ undefined.
###
operation(value[1], value[0]) for key, value of @_content
# Manual return to avoid CoffeeScript accumulating an array for return.
return
_isSpecialType = (key) ->
simpleHashableTypes = ['Boolean', 'Number', 'String',
'Undefined', 'Null', 'RegExp', 'Function']
type = _extractDataType key
for simpleType in simpleHashableTypes
if type is simpleType then return no
return yes
_extractDataType = (type) ->
Object.prototype.toString.apply(type).match(/\[object (.+)\]/)[1]
module.exports = Map