Skip to content

Commit

Permalink
Added a "memoizeWithCacheSize" function, to set a cache size greater …
Browse files Browse the repository at this point in the history
…than 1.
  • Loading branch information
ndbroadbent committed Mar 29, 2017
1 parent fb8dd53 commit 8837c03
Show file tree
Hide file tree
Showing 3 changed files with 231 additions and 2 deletions.
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,37 @@ function defaultEqualityCheck(currentVal, previousVal) {

`defaultMemoize` can be used with `createSelectorCreator` to [customize the `equalityCheck` function](#customize-equalitycheck-for-defaultmemoize).

### memoizeWithCacheSize(func, equalityCheck = defaultEqualityCheck, cacheSize = 2)

`memoizeWithCacheSize` memoizes the function passed in the func parameter. You can specify a custom cache size, to store the last n results. When the cache is full, it will replace the oldest result with the new result. (It does not care about order in which results are accessed, only created.)

`memoizeWithCacheSize` also determines if an argument has changed by calling the `equalityCheck` function.

`memoizeWithCacheSize` can be used with `createSelectorCreator`:

```js
import { createSelectorCreator, memoizeWithCacheSize, defaultEqualityCheck } from 'reselect'

// create a "selector creator" that uses memoizeWithCacheSize instead of defaultMemoize
const createCustomCacheSizeSelector = createSelectorCreator(
memoizeWithCacheSize,
defaultEqualityCheck,
3 // cacheSize argument
)

// use the new "selector creator" to create a selector
const customSelector = customSelectorCreator(
input1,
input2,
resultFunc
)

// Clear all results from the cache
customSelector.memoizedResultFunc.clearCache()
```



### createSelectorCreator(memoize, ...memoizeOptions)

`createSelectorCreator` can be used to make a customized version of `createSelector`.
Expand Down
53 changes: 52 additions & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
function defaultEqualityCheck(a, b) {
export function defaultEqualityCheck(a, b) {
return a === b
}

Expand Down Expand Up @@ -33,6 +33,56 @@ export function defaultMemoize(func, equalityCheck = defaultEqualityCheck) {
}
}

export function memoizeWithCacheSize(func, equalityCheck = defaultEqualityCheck, cacheSize = 2) {
if (cacheSize < 1) throw new Error('cacheSize cannot be less than 1!')
// Just use defaultMemoize, since it's a tiny bit faster.
if (cacheSize === 1) return defaultMemoize(func, equalityCheck)

let argsArr, resultsArr, resultsLength, lastIndex, endIndex, i, j

const clearCache = () => {
argsArr = new Array(cacheSize)
resultsArr = new Array(cacheSize)
resultsLength = 0
lastIndex = cacheSize
endIndex = cacheSize
}
clearCache()

// we reference arguments instead of spreading them for performance reasons
const memoizedResultFunc = function () {
for (i = lastIndex; i < endIndex; i++) {
// Use modulus to cycle through the array, starting at lastIndex
j = i % cacheSize

if (areArgumentsShallowlyEqual(equalityCheck, argsArr[j], arguments)) {
return resultsArr[j]
}
}

if (lastIndex === 0) {
lastIndex = cacheSize - 1
} else {
if (resultsLength < cacheSize) resultsLength++
lastIndex--
}
endIndex = lastIndex + resultsLength

argsArr[lastIndex] = arguments
// apply arguments instead of spreading for performance.
return resultsArr[lastIndex] = func.apply(null, arguments)
}

// Mostly for tests.
memoizedResultFunc.getArgsArr = () => argsArr
memoizedResultFunc.getResultsArr = () => resultsArr
memoizedResultFunc.getLastIndex = () => lastIndex
memoizedResultFunc.getResultsLength = () => resultsLength
memoizedResultFunc.clearCache = clearCache

return memoizedResultFunc
}

function getDependencies(funcs) {
const dependencies = Array.isArray(funcs[0]) ? funcs[0] : funcs

Expand Down Expand Up @@ -79,6 +129,7 @@ export function createSelectorCreator(memoize, ...memoizeOptions) {
})

selector.resultFunc = resultFunc
selector.memoizedResultFunc = memoizedResultFunc
selector.recomputations = () => recomputations
selector.resetRecomputations = () => recomputations = 0
return selector
Expand Down
149 changes: 148 additions & 1 deletion test/test_selector.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
// TODO: Add test for React Redux connect function

import chai from 'chai'
import { createSelector, createSelectorCreator, defaultMemoize, createStructuredSelector } from '../src/index'
import {
createSelector, createSelectorCreator, defaultMemoize,
memoizeWithCacheSize, createStructuredSelector
} from '../src/index'
import { default as lodashMemoize } from 'lodash.memoize'

const assert = chai.assert
Expand Down Expand Up @@ -414,4 +417,148 @@ suite('selector', () => {
)
assert.equal(selector.resultFunc, lastFunction)
})
test('memoizeWithCacheSize with cacheSize 2', () => {
const createSelectorWithCacheSize2 = createSelectorCreator(memoizeWithCacheSize, void 0, 2 )

const selector = createSelectorWithCacheSize2([
(state) => state.num
], (num) => num * num)

assert.equal(selector({ num: 1 }), 1) // expect recomputation
assert.equal(selector.memoizedResultFunc.getLastIndex(), 1)

assert.equal(selector({ num: 2 }), 4) // expect recomputation
assert.equal(selector.memoizedResultFunc.getLastIndex(), 0)

assert.equal(selector({ num: 3 }), 9) // expect recomputation
assert.equal(selector.memoizedResultFunc.getLastIndex(), 1)
assert.equal(selector.recomputations(), 3)

assert.equal(selector({ num: 2 }), 4) // expect cache
assert.equal(selector.recomputations(), 3)
assert.equal(selector.memoizedResultFunc.getLastIndex(), 1)

assert.equal(selector({ num: 4 }), 16) // expect recomputation
assert.equal(selector.memoizedResultFunc.getLastIndex(), 0)
assert.equal(selector.recomputations(), 4)

assert.equal(selector.memoizedResultFunc.getArgsArr()[0][0], 4)
assert.equal(selector.memoizedResultFunc.getArgsArr()[1][0], 3)

assert.equal(selector({ num: 4 }), 16) // expect cache
assert.equal(selector.recomputations(), 4)
assert.equal(selector({ num: 3 }), 9) // expect cache
assert.equal(selector.recomputations(), 4)
assert.equal(selector({ num: 3 }), 9) // expect cache
assert.equal(selector.recomputations(), 4)
assert.equal(selector.memoizedResultFunc.getLastIndex(), 0)

assert.equal(selector({ num: 2 }), 4) // expect recomputation
assert.equal(selector.recomputations(), 5)
assert.equal(selector.memoizedResultFunc.getLastIndex(), 1)

assert.equal(selector.memoizedResultFunc.getArgsArr()[0][0], 4)
assert.equal(selector.memoizedResultFunc.getArgsArr()[1][0], 2)

assert.equal(selector({ num: 4 }), 16) // expect cache
assert.equal(selector.recomputations(), 5)

assert.equal(selector({ num: 3 }), 9) // expect recomputation
assert.equal(selector.recomputations(), 6)
assert.equal(selector.memoizedResultFunc.getLastIndex(), 0)
assert.equal(selector({ num: 3 }), 9) // expect cache
assert.equal(selector.recomputations(), 6)

assert.equal(selector.memoizedResultFunc.getArgsArr().length, 2)
})
test('memoizeWithCacheSize with cacheSize 5', () => {
const createSelectorWithCacheSize5 = createSelectorCreator(memoizeWithCacheSize, void 0, 5)

const selector = createSelectorWithCacheSize5([
(state) => state.num
], (num) => num * num)

assert.equal(selector({ num: 1 }), 1) // expect recomputation
assert.equal(selector.memoizedResultFunc.getLastIndex(), 4)
assert.equal(selector.memoizedResultFunc.getResultsLength(), 1)
assert.equal(selector({ num: 2 }), 4) // expect recomputation
assert.equal(selector.memoizedResultFunc.getLastIndex(), 3)
assert.equal(selector({ num: 3 }), 9) // expect recomputation
assert.equal(selector.memoizedResultFunc.getLastIndex(), 2)
assert.equal(selector({ num: 4 }), 16) // expect recomputation
assert.equal(selector.memoizedResultFunc.getLastIndex(), 1)
assert.equal(selector({ num: 5 }), 25) // expect recomputation
assert.equal(selector.memoizedResultFunc.getLastIndex(), 0)
assert.equal(selector.memoizedResultFunc.getResultsLength(), 5)
assert.equal(selector.recomputations(), 5)

assert.deepEqual(
selector.memoizedResultFunc.getArgsArr().map(args => args[0]),
[ 5, 4, 3, 2, 1 ])

assert.equal(selector({ num: 2 }), 4) // expect cache
assert.equal(selector.recomputations(), 5)
assert.equal(selector.memoizedResultFunc.getLastIndex(), 0)
assert.deepEqual(
selector.memoizedResultFunc.getArgsArr().map(args => args[0]),
[ 5, 4, 3, 2, 1 ])

assert.equal(selector({ num: 6 }), 36) // expect recomputation
assert.equal(selector.recomputations(), 6)
assert.equal(selector.memoizedResultFunc.getLastIndex(), 4)
assert.deepEqual(
selector.memoizedResultFunc.getArgsArr().map(args => args[0]),
[ 5, 4, 3, 2, 6 ])
assert.equal(selector.memoizedResultFunc.getArgsArr()[5], void 0)

assert.equal(selector({ num: 1 }), 1) // expect recomputation
assert.equal(selector.recomputations(), 7)
assert.deepEqual(
selector.memoizedResultFunc.getArgsArr().map(args => args[0]),
[ 5, 4, 3, 1, 6 ])
assert.equal(selector.memoizedResultFunc.getArgsArr()[5], void 0)

assert.equal(selector({ num: 1 }), 1) // expect cache
assert.deepEqual(
selector.memoizedResultFunc.getArgsArr().map(args => args[0]),
[ 5, 4, 3, 1, 6 ])
assert.equal(selector.recomputations(), 7)

selector.memoizedResultFunc.clearCache()
assert.equal(selector.memoizedResultFunc.getResultsLength(), 0)
assert.equal(selector.memoizedResultFunc.getLastIndex(), 5)

assert.equal(selector({ num: 5 }), 25) // expect recomputation
assert.equal(selector.memoizedResultFunc.getLastIndex(), 4)
assert.equal(selector.memoizedResultFunc.getResultsLength(), 1)
assert.equal(selector({ num: 4 }), 16) // expect recomputation
assert.equal(selector.memoizedResultFunc.getLastIndex(), 3)
assert.equal(selector.memoizedResultFunc.getResultsLength(), 2)
assert.equal(selector({ num: 3 }), 9) // expect recomputation
assert.equal(selector.memoizedResultFunc.getLastIndex(), 2)
assert.equal(selector.memoizedResultFunc.getResultsLength(), 3)
assert.equal(selector({ num: 2 }), 4) // expect recomputation
assert.equal(selector.memoizedResultFunc.getLastIndex(), 1)
assert.equal(selector({ num: 1 }), 1) // expect recomputation
assert.equal(selector.memoizedResultFunc.getLastIndex(), 0)
assert.equal(selector.recomputations(), 12)
assert.equal(selector({ num: 6 }), 36) // expect recomputation
assert.equal(selector.memoizedResultFunc.getLastIndex(), 4)
assert.equal(selector.recomputations(), 13)
assert.equal(selector({ num: 7 }), 49) // expect recomputation
assert.equal(selector.memoizedResultFunc.getLastIndex(), 3)
assert.equal(selector.recomputations(), 14)

assert.equal(selector({ num: 3 }), 9) // expect cache
assert.equal(selector.recomputations(), 14)

assert.equal(selector({ num: 10 }), 100) // expect recomputation
assert.equal(selector.recomputations(), 15)
assert.equal(selector.memoizedResultFunc.getLastIndex(), 2)

assert.deepEqual(
selector.memoizedResultFunc.getArgsArr().map(args => args[0]),
[ 1, 2, 10, 7, 6 ])
assert.equal(selector.memoizedResultFunc.getArgsArr()[5], void 0)
})
})

0 comments on commit 8837c03

Please sign in to comment.