Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
louisremi committed May 14, 2016
0 parents commit ff907e4
Show file tree
Hide file tree
Showing 7 changed files with 336 additions and 0 deletions.
12 changes: 12 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
root = true

[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false
22 changes: 22 additions & 0 deletions .eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"extends": "eslint:recommended",
"env": {
"browser": true,
"node": true,
"amd": true,
"mocha": true,
"es6": true
},
"plugins": [
"mocha"
],
"rules": {
"quotes": [ 2, "single" ],
"no-unused-vars": 2,
"mocha/no-exclusive-tests": 2,
"comma-dangle": 0
},
"globals": {
"expect": false
}
}
33 changes: 33 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Logs
logs
*.log

# Runtime data
pids
*.pid
*.seed

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# node-waf configuration
.lock-wscript

# Dist files
dist/

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules

# Bower
bower_components/
58 changes: 58 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# Memoize Immutable

An efficient function memoizer that uses strict-equality for argument comparison. Ideal with Redux and other immutable environments.

## How is it different from other memoizers?

This memoizer compares arguments by strict-equality,
which means non-primitive arguments (such as objects) are compared by reference.
Most memoizers out-there compare arguments by deep-equality, which requires serializing all arguments,
and is very inefficient when working with Redux and other immutable environments.

## Install

npm install --save memoize-immutable

## Usage

```javascript
var memoize = require('memoize-immutable');

var nbExecs = 0;
var arraySum = function(arr) {
nbExecs++;
return arr.reduce(function(acc, curr) {
return acc + curr;
}, 0);
};
var arraySumMemoized = memoize(arraySum);


var arr1 = [ 1, 2, 3, 4, 5, 6 ];
var copy = arr1;

expect(arraySumMemoized(arr1)).to.equal(21);
expect(nbExecs).to.equal(1);

expect(arraySumMemoized(copy)).to.equal(21);
expect(nbExecs).to.equal(1);

// Of course, you shouldn't mutate the arguments, or else...
arr1.push(7);
expect(arraySumMemoized(arr1)).to.equal(21);
expect(nbExecs).to.equal(1);

var clone = arr1.concat();
expect(arraySumMemoized(clone)).to.equal(28);
expect(nbExecs).to.equal(2);
```

## license

MIT

## Authors

Original problem by [@louis_remi](https://twitter.com/louis_remi),
original solution by [@LasseFister](https://twitter.com/lassefister),
original implementation by [@louis_remi](https://twitter.com/louis_remi).
65 changes: 65 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/* A function memoizer for immutable arguments
* (compares non-primitive arguments by reference to retrieve result from cache)
*/

if (typeof exports === 'object' && typeof exports.nodeName !== 'string' && typeof define !== 'function') {
var define = function (factory) {
factory(require, exports, module);
};
}

define(function (require, exports, module) {

if ( typeof WeakMap === 'undefined' || typeof Map === 'undefined' ) {
throw new Error('This lib requires an implementation of WeakMap and Map');
}

var _idMap = new WeakMap();
var _id = { id: 0 };
// the last three arguments help with testing
module.exports = function memoize(fn, cache, idMap, id) {
if ( !cache ) {
cache = new Map();
}
if ( !idMap ) {
idMap = _idMap;
}
if ( !id ) {
id = _id;
}

return function() {
var aKey = [];
for ( var i = 0; i < arguments.length; i++ ) {
var argType = typeof arguments[i];

// if the argument is not a primitive, get a unique (memoized?) id for it
if (
// typeof null is "object", although we'll consider it as a primitive
arguments[i] !== null &&
( argType === 'object' || argType === 'function' || argType === 'symbol' )
) {
if ( !idMap.has(arguments[i]) ) {
idMap.set(arguments[i], id.id++);
}
aKey.push( idMap.get(arguments[i]) );

// otherwise, add the argument and its type to the aKey
} else {
aKey.push( arguments[i] == null ?
'' + arguments[i] :
argType + '_:::_' + arguments[i]
);
}
}

var sKey = aKey.join('_///_');

if ( !cache.has(sKey) ) {
cache.set( sKey, fn.apply(this, arguments) );
}

return cache.get( sKey );
};
};
});
23 changes: 23 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "memoize-immutable",
"version": "1.0.0",
"description": "A function memoizer that compares arguments by reference. Ideal with Redux and other immutable environments.",
"main": "index.js",
"scripts": {
"test": "mocha test.js"
},
"keywords": [
"immutable",
"memoize",
"redux"
],
"author": "@louis_remi",
"license": "MIT",
"devDependencies": {
"chai": "^3.5.0",
"eslint": "^2.9.0",
"eslint-plugin-mocha": "^2.2.0",
"mocha": "^2.4.5",
"mocha-phantomjs": "^4.0.2"
}
}
123 changes: 123 additions & 0 deletions test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
var expect = require('chai').expect;

var memoize = require('./index');

describe('memoize', function() {
var cache;
var idMap;
var id;

beforeEach(function() {
cache = new Map();
idMap = new Map();
id = { id: 0 };
});

it('should memoize a result when called with 0 args', function(done) {
var nbExecs = 0;
var fn = function() {
return ++nbExecs;
};
var memoizedFn = memoize(fn, cache, idMap, id);

// first execution with empty cache
expect(memoizedFn()).to.equal(1);
expect(nbExecs).to.equal(1);
expect(cache.size).to.equal(1);
expect(cache.get('')).to.equal(1);
expect(idMap.size).to.equal(0);

// second execution
expect(memoizedFn()).to.equal(1);
expect(nbExecs).to.equal(1);
expect(cache.size).to.equal(1);
expect(cache.get('')).to.equal(1);
expect(idMap.size).to.equal(0);

done();
});

it('should memoize a result when called with 1 primitive arg', function(done) {
var nbExecs = 0;
var fn = function() {
return ++nbExecs;
};
var memoizedFn = memoize(fn, cache, idMap, id);
var arg1 = 12;

// first execution with empty cache
expect(memoizedFn(arg1)).to.equal(1);
expect(nbExecs).to.equal(1);
expect(cache.size).to.equal(1);
expect(cache.get('number_:::_12')).to.equal(1);
expect(idMap.size).to.equal(0);

// second execution
expect(memoizedFn(arg1)).to.equal(1);
expect(nbExecs).to.equal(1);
expect(cache.size).to.equal(1);
expect(cache.get('number_:::_12')).to.equal(1);
expect(idMap.size).to.equal(0);

done();
});

it('should memoize a result when called with 1 non-primitive arg', function(done) {
var nbExecs = 0;
var fn = function() {
return ++nbExecs;
};
var memoizedFn = memoize(fn, cache, idMap, id);
var arg1 = {};

// first execution with empty cache
expect(memoizedFn(arg1)).to.equal(1);
expect(nbExecs).to.equal(1);
expect(cache.size).to.equal(1);
expect(cache.get('0')).to.equal(1)
expect(idMap.size).to.equal(1);

// second execution
expect(memoizedFn(arg1)).to.equal(1);
expect(nbExecs).to.equal(1);
expect(cache.size).to.equal(1);
expect(cache.get('0')).to.equal(1)
expect(idMap.size).to.equal(1);

done();
});

it('should memoize a result when called with mixed primitive and non-primitive args', function(done) {
var nbExecs = 0;
var fn = function() {
return ++nbExecs;
};
var memoizedFn = memoize(fn, cache, idMap, id);
var arg1 = {a:'b'};
var arg2 = 'string';
var arg3 = [1,2];
var arg4 = function() {};
var arg5 = 12;
var arg6 = null;
var arg7 = false;
var arg8 = undefined;

// first execution with empty cache
expect(memoizedFn(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)).to.equal(1);
expect(nbExecs).to.equal(1);
expect(cache.size).to.equal(1);
expect(cache.get('0_///_string_:::_string_///_1_///_2_///_number_:::_12_///_null_///_boolean_:::_false_///_undefined'))
.to.equal(1);
expect(idMap.size).to.equal(3);

// second execution
expect(memoizedFn(arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8)).to.equal(1);
expect(nbExecs).to.equal(1);
expect(cache.size).to.equal(1);
expect(cache.get('0_///_string_:::_string_///_1_///_2_///_number_:::_12_///_null_///_boolean_:::_false_///_undefined'))
.to.equal(1);
expect(idMap.size).to.equal(3);

done();
});
});

0 comments on commit ff907e4

Please sign in to comment.