-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit eda6b16
Showing
6 changed files
with
248 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
node_modules | ||
.nyc_output | ||
coverage |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
language: node_js | ||
node_js: | ||
- '1' | ||
- '2' | ||
- '3' | ||
- '4' | ||
- '4' | ||
- '5' | ||
- '6' | ||
- '7' | ||
script: "npm run test-travis" | ||
# Send coverage data to Coveralls | ||
after_script: "cat ./coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
member-berry | ||
=== | ||
|
||
[![NPM version][npm-image]][npm-url] | ||
[![Build status][travis-image]][travis-url] | ||
[![Test coverage][coveralls-image]][coveralls-url] | ||
[![Downloads][downloads-image]][downloads-url] | ||
|
||
### Memoize a function of n args, O(n) recall, and no memory leaks. | ||
|
||
This function is similar to lodash.memoize, the main difference | ||
is that it memoizes any number of arguments, makes sure not to | ||
leak any memory while maintaining a complexity of | ||
O(`arguments.length`). | ||
|
||
## Usage | ||
|
||
```js | ||
import memeberBerry from 'member-berry'; | ||
var hashCode = function(str) { | ||
var hash = 0, i, chr, len; | ||
if (str.length === 0) return hash; | ||
for (i = 0, len = str.length; i < len; i++) { | ||
chr = str.charCodeAt(i); | ||
hash = ((hash << 5) - hash) + chr; | ||
hash |= 0; // Convert to 32bit integer | ||
} | ||
return hash; | ||
}; | ||
function computeHash() { | ||
console.log('doing a slow calculation'); | ||
var hash = 0 | ||
for (var i = 0; i < arguments.length; i++) { | ||
hash = hashCode(hash + arguments[index]) | ||
} | ||
return hash; | ||
} | ||
var memoized = memberBerry(computeHash); | ||
memoized("test") // calculates | ||
memoized("test") // doesn't recalculate | ||
|
||
var obj = {}; | ||
memoized(obj) // calculates | ||
memoized(obj) // doesn't recalculate | ||
|
||
memoized("test", obj) // calculates | ||
memoized("test", obj) // doesn't recalculate | ||
``` | ||
|
||
### Implementation | ||
The technique used to achive O(n) lookup, is to use a trie-like | ||
data structure to store the cached values. Here's a basic | ||
snippet with accompanying explaination: | ||
|
||
|
||
```js | ||
var concat = function(a, b, c) { return a + b + c; }; | ||
var memoized = memberBerry(concat); | ||
|
||
memoized(1, 2, 3) | ||
/* memoized internal cache looks something like this: | ||
{ | ||
1: { | ||
2: { | ||
3: { | ||
result: "123" | ||
} | ||
} | ||
} | ||
} | ||
*/ | ||
``` | ||
|
||
`member-berry` uses weakmaps to avoid holding onto object | ||
references longer than needed. Weakmaps can't use primitive | ||
values as keys so there's also a "wrapped" object associated | ||
with primitives. | ||
|
||
[npm-image]: https://img.shields.io/npm/v/member-berry.svg?style=flat-square | ||
[npm-url]: https://npmjs.org/package/member-berry | ||
[travis-image]: https://img.shields.io/travis/kolodny/member-berry.svg?style=flat-square | ||
[travis-url]: https://travis-ci.org/kolodny/member-berry | ||
[coveralls-image]: https://img.shields.io/coveralls/kolodny/member-berry.svg?style=flat-square | ||
[coveralls-url]: https://coveralls.io/r/kolodny/member-berry | ||
[downloads-image]: http://img.shields.io/npm/dm/member-berry.svg?style=flat-square | ||
[downloads-url]: https://npmjs.org/package/member-berry |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
module.exports = memberBerry; | ||
|
||
var resultObject = {}; | ||
function memberBerry(fn) { | ||
var wrappedPrimitives = {}; | ||
var map = new WeakMap(); | ||
return function() { | ||
var currentMap = map; | ||
for (var index = 0; index < arguments.length; index++) { | ||
var arg = arguments[index]; | ||
if (typeof arg !== 'object') { | ||
var key = (typeof arg) + arg | ||
if (!wrappedPrimitives[key]) wrappedPrimitives[key] = {}; | ||
arg = wrappedPrimitives[key]; | ||
} | ||
var nextMap = currentMap.get(arg); | ||
if (!nextMap) { | ||
nextMap = new WeakMap(); | ||
currentMap.set(arg, nextMap); | ||
} | ||
currentMap = nextMap; | ||
} | ||
if (!currentMap.has(resultObject)) { | ||
currentMap.set(resultObject, fn.apply(null, arguments)); | ||
} | ||
return currentMap.get(resultObject); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
{ | ||
"name": "member-berry", | ||
"version": "1.0.0", | ||
"description": "Memoize a function of n args, O(n) recall, and no memory leaks", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "mocha", | ||
"test-debug": "mocha --inspect --debug-brk", | ||
"test-cov": "nyc npm test && nyc report --reporter=lcov", | ||
"test-travis": "nyc npm test && nyc report --reporter=lcov" | ||
}, | ||
"keywords": [ | ||
"memoize" | ||
], | ||
"author": "Moshe Kolodny", | ||
"license": "MIT", | ||
"devDependencies": { | ||
"coveralls": "^2.12.0", | ||
"expect": "^1.20.2", | ||
"mocha": "^3.2.0", | ||
"nyc": "^10.1.2" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
var memeberBerry = require('./') | ||
var expect = require('expect') | ||
|
||
describe('memeber-berry', function() { | ||
var id; | ||
var func; | ||
var membered; | ||
|
||
beforeEach(function() { | ||
id = 0; | ||
func = function() { return ++id; }; | ||
membered = memeberBerry(func); | ||
}); | ||
|
||
it('members one obj', function() { | ||
var obj = {}; | ||
expect(membered(obj)).toEqual(1); | ||
expect(membered(obj)).toEqual(1, 'ooh, I member'); | ||
}); | ||
|
||
it('members two different objs', function() { | ||
var obj1 = {}; | ||
var obj2 = {}; | ||
expect(membered(obj1)).toEqual(1); | ||
expect(membered(obj2)).toEqual(2); | ||
expect(membered(obj1)).toEqual(1, 'ooh, I member'); | ||
expect(membered(obj2)).toEqual(2, 'ooh, I member'); | ||
}); | ||
|
||
it('members multiple args', function() { | ||
var obj1 = {}; | ||
var obj2 = {}; | ||
expect(membered(obj1, obj2)).toEqual(1); | ||
expect(membered(obj1, obj2)).toEqual(1, 'ooh, I member'); | ||
}); | ||
|
||
it('members different multiple args', function() { | ||
var obj1 = {}; | ||
var obj2 = {}; | ||
expect(membered(obj1, obj2)).toEqual(1); | ||
expect(membered(obj2, obj1)).toEqual(2); | ||
expect(membered(obj1, obj2)).toEqual(1, 'ooh, I member'); | ||
expect(membered(obj2, obj1)).toEqual(2, 'ooh, I member'); | ||
}); | ||
|
||
it('members different similar primitives', function() { | ||
expect(membered(1)).toEqual(1); | ||
expect(membered(1)).toEqual(1, 'ooh, I member'); | ||
expect(membered('1')).toEqual(2); | ||
}); | ||
|
||
it('is O(1)', function() { | ||
var originalWeakMap = WeakMap; | ||
var operations = 0; | ||
WeakMap = function() { | ||
var wm = new originalWeakMap(); | ||
return { | ||
has: function(key) { | ||
operations++; | ||
return wm.has(key); | ||
}, | ||
get: function(key) { | ||
operations++; | ||
return wm.get(key); | ||
}, | ||
set: function(key, value) { | ||
operations++; | ||
return wm.set(key, value); | ||
}, | ||
} | ||
}; | ||
membered = memeberBerry(func); | ||
var lastArgs; | ||
func = function() { | ||
lastArgs = [].slice.call(arguments); | ||
return ++id; | ||
}; | ||
var arraySameContents = function(arr1, arr2) { | ||
expect(arr1.length).toEqual(arr2.length); | ||
for (var index = 0; index < arr1.length; index++) { | ||
expect(arr1[index]).toBe(arr2[index]); | ||
} | ||
}; | ||
membered = memeberBerry(func); | ||
var objects = Array(100).join('.').split('.').map(function() { return {}; }); | ||
expect(membered.apply(null, objects)).toEqual(1); | ||
expect(membered.apply(null, objects)).toEqual(1, 'ooh, I member'); | ||
arraySameContents(objects, lastArgs); | ||
expect(operations).toBeLessThan(310); | ||
expect(membered.apply(null, objects.concat({}))).toEqual(2); | ||
expect(lastArgs.length).toNotEqual(objects.length); | ||
expect(operations).toBeLessThan(420); | ||
}); | ||
|
||
}) |