cappedarray
Version:
A real array that automatically removes items to maintain the capped length
291 lines (200 loc) • 8.03 kB
Markdown
cappedarray
===========
## Basic usage
```js
var cappedArray = require('cappedarray')
// Create array with a length cap of 3
var log = cappedArray(3) // []
// log.lengthCap === 3
// Add some items
log.push(1) // [1]
log.push(2) // [1, 2]
log.push(3) // [1, 2, 3]
// Add more items than the length cap
log.push(4) // [2, 3, 4] - Too many items, The 1 is pushed off the front
log.push(5) // [3, 4, 5]
log.unshift(6) // [6, 3, 4] - The 5 is unshifted off the back
// Add a lot more than the cap length
for (var i = 0; i < 50; i++) {
log.push(i)
}
// Still 3 items
console.log(log.length)
> 3
console.log(log)
> [47, 48, 49]
```
## Installation
```bash
$ npm install cappedarray
```
## How it works
The capped array is just a normal array with the `push` and `unshift` functions overridden and an extra `lengthCap` property.
The functions and lengthCap are set to enumerable `false` so they do not show up in forEach loops, Object.keys, etc...
```js
var recentItems = cappedArray(5)
recentItems.push('foo')
recentItems.push('bar')
var util = require('util')
util.isArray(recentItems) // true
Array.isArray(recentItems) // true
Object.keys(recentItems) // ['0', '1']
JSON.stringify(recentItems) // '["foo","bar"]'
```
## Further Examples
#### Multiple arguments
```js
// Create array with a length cap of 5
var log = cappedArray(5) // []
// Multiple arguments to push and unshift work as expected.
log.unshift('a', 'b', 'c', 'd', 'e', 'f') // ['a', 'b', 'c', 'd', 'e'] - Too long, 'f' has already popped off the end.
// If this was a 'push' the 'f' would have stayed and the 'a' would have gone.
log.unshift('h') // ['h', 'a', 'b', 'c', 'd']
log.unshift.apply(log, ['i', 'j', 'k']) // ['i', 'j', 'k', 'h', 'a']
log.push('l', 'm') // ['k', 'h', 'a', 'l', 'm']
```
#### The length cap can be changed on the fly.
```js
// Create array with a length cap of 5
var recentItems = cappedArray(5)
recentItems.push(1, 2, 3, 4, 5, 6, 7) // [3, 4, 5, 6, 7]
// Need to keep more recent items
recentItems.lengthCap = 8 // [3, 4, 5, 6, 7] Still only 5 items
recentItems.push(8, 9, 10, 11) // [4, 5, 6, 7, 8, 9, 10, 11]
```
When changing the length cap to a lower value than the existing item count, the extra items will **not** be automatically removed. It is unknown whether the first or last items should be removed until `push` or `unshift` is called afterward.
```js
// I've changed my mind. There's too many items now, lower the cap.
recentItems.lengthCap = 6 // [4, 5, 6, 7, 8, 9, 10, 11]
// More items than the lengthCap?
console.log(recentItems.length)
> 8
console.log(recentItems.lengthCap)
> 6
```
Passing no arguments to either `push` or `unshift` will reduce the array to the capped length.
```js
// I want to keep the end of the array.
recentItems.push() // [6, 7, 8, 9, 10, 11]
// That has sorted it!
console.log(recentItems.length)
> 6
console.log(recentItems.lengthCap)
> 6
```
## Capping existing Arrays
```js
var cap = require('cappedarray').cap
var messages = [{msg: 'First!'}, {msg: 'Second'}, {msg: 'Third'}]
// Cap the array to 5 items
cap(messages, 5)
messages.push({msg: 'Forth'}, {msg: 'Fith'}, {msg: 'Sixth'})
console.log(log)
> [ { msg: 'Second' },
{ msg: 'Third' },
{ msg: 'Forth' },
{ msg: 'Fith' },
{ msg: 'Sixth' } ]
```
If the second argument is missing the cap is set to the length of the array or the lengthCap property if it exists.
```js
var numerosEnFrancais = cap(['un', 'deux', 'trois'])
// Both the cap and length are the same
console.log(numerosEnFrancais.length + ' === ' numerosEnFrancais.lengthCap)
> 3 === 3
// This can be used to loop through an array repeatedly
numerosEnFrancais.push(numerosEnFrancais[0]) // ['deux', 'trois', 'un']
numerosEnFrancais.push(numerosEnFrancais[0]) // ['trois', 'un', 'deux']
numerosEnFrancais.push(numerosEnFrancais[0]) // ['un', 'deux', 'trois']
var top5 = ['High Fidelity']
// This...
top5.lengthCap = 5
cap(top5) // Uses the existing lengthCap property for the length cap
// ...is the same as
cap(top5, 5)
```
Like changing the lengthCap above, capping an existing array with more items than the length cap will **not** automatically remove the extra items. Use `push` or `unshift` with no arguments to reduce the array to the capped length.
```js
var alphabet = ['a', 'b', 'c', 'd', etc..., 'x', 'y', 'z']
console.log(alphabet.length)
> 26
// Cap it to 13 items...
cap(alphabet, 13)
// ...but it still has 26 items.
console.log(alphabet.length)
> 26
console.log(alphabet.lengthCap)
> 13
// removes extra items from the front.
cap.push()
console.log(alphabet.length)
> 13
console.log(alphabet)
> ['n', 'o', 'p', 'q', 'r', 's', etc..., 'x', 'y', 'z']
// Lets try that again with unshift
var alphabet = ['a', 'b', 'c', 'd', etc..., 'x', 'y', 'z']
// cap returns the array so we can do this. Unshift removes extra items from the back.
cap(alphabet, 13).unshift()
console.log(alphabet.length)
> 13
console.log(alphabet)
> ['a', 'b', 'c', 'd', 'e', 'f', etc..., 'k', 'l', 'm']
```
## Uncap
Use uncap to turn the array back to normal.
```js
var cap = require('cappedarray').cap
var uncap = require('cappedarray').uncap
var cycleNumbers = cap([1, 2, 3, 4, 5]) // Sets lengthCap to length: 5
cycleNumbers.push(cycleNumbers[0]) // [2, 3, 4, 5, 1]
cycleNumbers.push(cycleNumbers[0]) // [3, 4, 5, 1, 2]
cycleNumbers.push(cycleNumbers[0]) // [4, 5, 1, 2, 3]
uncap(cycleNumbers)
// The cycle is broken
cycleNumbers.push(cycleNumbers[0]) // [4, 5, 1, 2, 3, 4]
cycleNumbers.push(cycleNumbers[0]) // [4, 5, 1, 2, 3, 4, 4]
cycleNumbers.push(cycleNumbers[0]) // [4, 5, 1, 2, 3, 4, 4, 4]
```
## Clone
Copy the values into a new capped array with the same lengthCap
```js
var cap = require('cappedarray').cap
var clone = require('cappedarray').clone
var capped1 = cap([1, 2, 3])
var capped2 = clone(capped1)
capped2.push(4)
console.log(capped1, capped2)
> [1, 2, 3] [2, 3, 4]
// Clone is just a wrapper for
cap(capped.slice(), capped.lengthCap)
```
## Gotchas
* Behavior maybe unexpected if a capped array is passed to a library for modification.
* lengthCap must be a non negative integer. Other values will throw 'Invalid array lengthCap'. This mimics the behavior of assigning array.length.
* `push` and `unshift` return the length of the array, **not** the array, in line with the existing behavior.
```js
// array1 is not an array
var array1 = cap([1, 2, 3, 4, 5, 6], 3).push()
console.log(array1)
> 3
// This works
var array2 = cap([1, 2, 3, 4, 5, 6], 3)
array2.push()
console.log(array2)
> [4, 5, 6]
// or this
var array3
( array3 = cap([1, 2, 3, 4, 5, 6], 3) ).push()
console.log(array3)
> [4, 5, 6]
```
* Arrays can be made longer than lengthCap by using splice, assigning to a numbered key or using the Array prototype functions with `call` or `apply`.
```js
var array1 = cap([1, 2, 3], 3) // [1, 2, 3] length = 3, lengthCap = 3
array1.splice(1, 0, 1.1, 1.2, 1.3) // [1, 1.1, 1.2, 1.3, 2, 3] length = 6, lengthCap = 3
var array2 = cap([1, 2, 3], 3) // [1, 2, 3] length = 3, lengthCap = 3
array2[3] = 4 // [1, 2, 3, 4] length = 4, lengthCap = 3
var array3 = cap([1, 2, 3], 3) // [1, 2, 3] length = 3, lengthCap = 3
Array.prototype.push.call(array3, 4) // [1, 2, 3, 4] length = 4, lengthCap = 3
[].push.apply(array3, [5, 6]) // [1, 2, 3, 4, 5, 6] length = 6, lengthCap = 3
```