├── src ├── fantasyland │ ├── monad.js │ ├── index.js │ ├── traversable.js │ ├── foldable.js │ ├── monoid.js │ ├── functor.js │ ├── semigroup.js │ ├── chain.js │ └── applicative.js ├── create │ ├── index.js │ ├── one.js │ ├── empty.js │ ├── range.js │ ├── of.js │ ├── from.js │ ├── _fromArray.js │ └── times.js ├── iterator │ ├── index.js │ ├── gen.js │ ├── reverseGen.js │ ├── iterator.js │ └── reverseIterator.js ├── operations │ ├── push.js │ ├── append.js │ ├── index.js │ ├── prepend.js │ ├── reverse.js │ ├── get.js │ ├── find.js │ ├── filter.js │ ├── set.js │ ├── removeAt.js │ ├── insertAt.js │ ├── foldl.js │ ├── foldr.js │ ├── map.js │ ├── indexOf.js │ ├── slice.js │ ├── intersperse.js │ └── caseOf.js ├── constants.js ├── experimental │ ├── readme.md │ ├── LinkedList.js │ ├── Block.js │ └── SkewList.js ├── index.js ├── accessors.js ├── Node.js ├── functional.js ├── constructors.js ├── internal.js ├── operations.js └── append.js ├── docs ├── logo-512.png ├── logo-small.png ├── logo.afdesign └── logo.svg ├── .babelrc ├── test ├── block.test.js ├── functional.test.js ├── RandomAccessLists.test.js ├── fantasyLandCompliance.test.js ├── v2.test.js └── collection.test.js ├── .gitignore ├── README.md ├── LICENSE ├── package.json ├── perf ├── RAL_vs_array.js ├── concatPerf.js ├── blockVsArray.js └── pushPerf.js ├── API.md ├── dist └── rrbit.js └── lib └── index.js /src/fantasyland/monad.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/logo-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wishfoundry/rrbit/HEAD/docs/logo-512.png -------------------------------------------------------------------------------- /docs/logo-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wishfoundry/rrbit/HEAD/docs/logo-small.png -------------------------------------------------------------------------------- /docs/logo.afdesign: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wishfoundry/rrbit/HEAD/docs/logo.afdesign -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"], 3 | "plugins": ["transform-es2015-modules-umd"] 4 | } -------------------------------------------------------------------------------- /src/create/index.js: -------------------------------------------------------------------------------- 1 | import './empty'; 2 | import './one'; 3 | import './of'; 4 | import './from'; 5 | import './range'; 6 | import './times'; 7 | -------------------------------------------------------------------------------- /src/fantasyland/index.js: -------------------------------------------------------------------------------- 1 | import './monoid'; 2 | import './functor' 3 | import './applicative'; 4 | import './chain'; 5 | import './semigroup'; 6 | import './monad'; 7 | -------------------------------------------------------------------------------- /src/create/one.js: -------------------------------------------------------------------------------- 1 | import {Node} from '../Node'; 2 | 3 | Node.one = one; 4 | 5 | // performance shortcut for an array of one 6 | export default function one(item) { 7 | return new Node(0, [ item ], void 0); 8 | } -------------------------------------------------------------------------------- /src/create/empty.js: -------------------------------------------------------------------------------- 1 | import {Node as List, EMPTY} from '../Node'; 2 | 3 | 4 | List.prototype.empty = empty; 5 | 6 | List.empty = empty; 7 | 8 | export default function empty() { 9 | return EMPTY; 10 | } -------------------------------------------------------------------------------- /src/create/range.js: -------------------------------------------------------------------------------- 1 | import times from './times'; 2 | import {Node as List} from '../Node'; 3 | 4 | List.range = range; 5 | 6 | export default function range(from, to) { 7 | var len = to - from; 8 | return times((i) => from + i, len); 9 | } -------------------------------------------------------------------------------- /src/iterator/index.js: -------------------------------------------------------------------------------- 1 | import {Node as List} from '../Node'; 2 | import iterator from './iterator'; 3 | 4 | const $$iter = (Symbol && Symbol.iterator) || "@@iterator"; 5 | 6 | List.prototype[$$iter] = function() { 7 | return iterator(this); 8 | }; -------------------------------------------------------------------------------- /src/iterator/gen.js: -------------------------------------------------------------------------------- 1 | import {tableOf} from '../accessors'; 2 | 3 | 4 | export default function* forward() { 5 | if (isLeaf(this)) { 6 | for (var value of tableOf(this)) { 7 | yield value; 8 | } 9 | } else { 10 | for (var subTable of tableOf(this)) { 11 | yield * subTable; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /src/operations/push.js: -------------------------------------------------------------------------------- 1 | import {push as _push} from '../internal'; 2 | import {curry} from '../functional'; 3 | import {Node as List} from '../Node'; 4 | 5 | 6 | List.prototype.push = function(item) { 7 | return _push(item, this); 8 | }; 9 | 10 | const push = List.push = curry(_push); 11 | 12 | export default push; -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | 2 | // M is the maximal table size. 32 seems fast. E is the allowed increase 3 | // of search steps when concatting to find an index. Lower values will 4 | // decrease balancing, but will increase search steps. 5 | const M = 32; 6 | const E = 2; 7 | 8 | 9 | export { 10 | M, 11 | E 12 | }; 13 | 14 | -------------------------------------------------------------------------------- /src/iterator/reverseGen.js: -------------------------------------------------------------------------------- 1 | import {tableOf, isLeaf} from '../accessors'; 2 | 3 | 4 | export default function* reverse() { 5 | var table = tableOf(this); 6 | var i = table.length; 7 | if (isLeaf(this)) { 8 | while(i--) { 9 | yield table[i]; 10 | } 11 | } else { 12 | while(i--) { 13 | yield * table[i]; 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /src/operations/append.js: -------------------------------------------------------------------------------- 1 | import {curry} from '../functional'; 2 | import {Node as List} from '../Node'; 3 | import {append as _append} from '../append'; 4 | 5 | 6 | 7 | List.prototype.append = function (a, b) { 8 | return _append(a, this); 9 | }; 10 | 11 | const append = List.append = curry(_append); 12 | 13 | export default append; 14 | -------------------------------------------------------------------------------- /src/create/of.js: -------------------------------------------------------------------------------- 1 | import fromArray from './_fromArray'; 2 | import {Node as List, EMPTY} from '../Node'; 3 | import one from './one'; 4 | 5 | List.of = of; 6 | 7 | export default function of(first, ...rest) { 8 | 9 | if (typeof first === 'undefined') 10 | return EMPTY; 11 | 12 | if (rest && rest.length > 0) 13 | return fromArray([first].concat(rest)); 14 | 15 | return one(first); 16 | } -------------------------------------------------------------------------------- /src/operations/index.js: -------------------------------------------------------------------------------- 1 | import './append'; 2 | import './caseOf'; 3 | import './filter'; 4 | import './find'; 5 | import './foldl'; 6 | import './foldr'; 7 | import './get'; 8 | import './indexOf'; 9 | import './insertAt'; 10 | import './intersperse'; 11 | import './map'; 12 | import './prepend'; 13 | import './push'; 14 | import './removeAt'; 15 | import './reverse'; 16 | import './set'; 17 | import './slice'; -------------------------------------------------------------------------------- /src/operations/prepend.js: -------------------------------------------------------------------------------- 1 | import {curry} from '../functional'; 2 | import {Node as List} from '../Node'; 3 | import {append} from '../append'; 4 | 5 | 6 | function _prepend(listA, listB) { 7 | return append(listB, listA); 8 | } 9 | 10 | List.prototype.prepend = function (list) { 11 | return append(list, this); 12 | }; 13 | 14 | const prepend = List.prepend = curry(_prepend); 15 | 16 | export default prepend; -------------------------------------------------------------------------------- /src/experimental/readme.md: -------------------------------------------------------------------------------- 1 | in an effort to find a more performant immutable operations(slice, append, prepend and concat) 2 | on leaves(lists of < 32 length), several experiments exist in this folder to compare 3 | various function/persistent/immutable data structures 4 | 5 | 6 | ### Contents: 7 | - SkewList - an implementation of Random Access Lists 8 | - Block - an experiment with Hash Array Map Trees using 4, 8 and 16 element leafs(instead of the canonical 32) -------------------------------------------------------------------------------- /src/operations/reverse.js: -------------------------------------------------------------------------------- 1 | import {curry} from '../functional'; 2 | import foldr from './foldr'; 3 | import {push} from '../internal'; 4 | import {EMPTY, Node as List} from '../Node'; 5 | 6 | function _reverse(list) { 7 | return foldr((item, newList) => 8 | push(item,newList), EMPTY, list); 9 | } 10 | 11 | List.prototype.reverse = function() { 12 | return reverse(this); 13 | }; 14 | 15 | const reverse = List.reverse = curry(_reverse); 16 | 17 | export default reverse; -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import {Node as List, isNode as isList} from './Node'; 2 | 3 | import './create/index'; 4 | import './operations/index'; 5 | import './fantasyland/index'; 6 | import './iterator/index'; 7 | 8 | 9 | // last minute addons 10 | List.isList = isList; 11 | List.prototype.toArray = function() { 12 | return this.foldl(addTo, [], this); 13 | }; 14 | function addTo(value, array) { 15 | array.push(value); 16 | return array; 17 | } 18 | 19 | export default List; 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/operations/get.js: -------------------------------------------------------------------------------- 1 | import {curry} from '../functional'; 2 | import {length} from '../accessors'; 3 | import {Node as List} from '../Node'; 4 | import {unsafeGet,} from '../internal'; 5 | 6 | 7 | function _get(i, list) { 8 | if (i < 0 || i >= length(list)) { 9 | throw new Error('Index ' + i + ' is out of range'); 10 | } 11 | return unsafeGet(i, list); 12 | } 13 | 14 | List.prototype.get = function(i) { 15 | return _get(i, this); 16 | }; 17 | 18 | const get = List.get = curry(_get); 19 | 20 | export default get; -------------------------------------------------------------------------------- /src/operations/find.js: -------------------------------------------------------------------------------- 1 | import {Node as List} from '../Node'; 2 | import {curry} from '../functional'; 3 | 4 | /** 5 | * 6 | * @param {function}predicate 7 | * @param {List} list 8 | * @return {*} 9 | * @private 10 | */ 11 | function _find(predicate, list) { 12 | for (var item of list) { 13 | if (predicate(item)) 14 | return item; 15 | } 16 | } 17 | 18 | const find = List.find = curry(_find); 19 | 20 | List.prototype.find = function(predicate) { 21 | return _find(predicate, this); 22 | }; 23 | 24 | export default find; -------------------------------------------------------------------------------- /src/operations/filter.js: -------------------------------------------------------------------------------- 1 | import foldr from './foldr'; 2 | import {EMPTY, Node as List} from '../Node'; 3 | import {curry} from '../functional'; 4 | import {push} from '../internal'; 5 | 6 | /** 7 | * return a new list of items that pass test fn 8 | * 9 | * @param {function(T)} fn 10 | * @param {Node} list 11 | * @return {Node} 12 | */ 13 | function _filter(fn, list) { 14 | return foldr((item, acc) => 15 | (fn(item) ? push(item, acc) : acc), EMPTY, list); 16 | } 17 | 18 | List.prototype.filter = function(fn) { 19 | return _filter(fn, this); 20 | }; 21 | 22 | const filter = List.filter = curry(_filter); 23 | 24 | export default filter; -------------------------------------------------------------------------------- /src/operations/set.js: -------------------------------------------------------------------------------- 1 | import {curry} from '../functional'; 2 | import {length} from '../accessors'; 3 | import {unsafeSet} from '../internal'; 4 | import {Node as List} from '../Node'; 5 | 6 | export function _set(i, item, list) { 7 | // if given index is negative, or greater than the length of list 8 | // be nice and don't throw an error 9 | // adding to the end of a list should always use push 10 | if (i < 0 || length(list) <= i) { 11 | return list; 12 | } 13 | return unsafeSet(i, item, list); 14 | } 15 | 16 | List.prototype.set = function(i, item) { 17 | return _set(i, item, this); 18 | }; 19 | 20 | const set = List.set = curry(_set); 21 | 22 | export default set; 23 | -------------------------------------------------------------------------------- /src/operations/removeAt.js: -------------------------------------------------------------------------------- 1 | import {append} from '../append'; 2 | import {sliceRight, sliceLeft} from '../internal'; 3 | import {curry} from '../functional'; 4 | import {Node as List} from '../Node'; 5 | import indexOf from './indexOf'; 6 | 7 | 8 | 9 | function _removeAt(i, list) { 10 | return append(sliceLeft(i - 1, list), sliceRight(i, list)) 11 | } 12 | 13 | function _removeItem(item, list) { 14 | var i = indexOf(item); 15 | return i === -1 ? list : remove(i, list); 16 | } 17 | 18 | const removeAt = List.removeAt = curry(_removeAt); 19 | const removeItem = List.removeItem = curry(_removeItem); 20 | 21 | 22 | export { 23 | removeItem, 24 | removeAt as default 25 | }; 26 | 27 | -------------------------------------------------------------------------------- /test/block.test.js: -------------------------------------------------------------------------------- 1 | import {Block16, Block8, Block4} from '../src/experimental/Block'; 2 | import {expect} from 'chai'; 3 | 4 | describe("collection tests", function() { 5 | var x4, x8, x16; 6 | 7 | it('ranges test', function() { 8 | x4 = Block4.times(i => i, 32); 9 | x8 = Block8.times(i => i, 32); 10 | x16 = Block16.times(i => i, 32); 11 | }); 12 | 13 | it('sum test', function() { 14 | var sum4 = x4.reduceKV((v, i, sum) => sum + i, 0) 15 | expect(sum4).to.equal(496) 16 | 17 | var sum8 = x8.reduceKV((v, i, sum) => sum + i, 0) 18 | expect(sum8).to.equal(496) 19 | 20 | var sum16 = x16.reduceKV((v, i, sum) => sum + i, 0) 21 | expect(sum16).to.equal(496) 22 | 23 | }) 24 | }) -------------------------------------------------------------------------------- /src/fantasyland/traversable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * 4 | */ 5 | 6 | 7 | 8 | 9 | 10 | // List.prototype.traverse = function(point, fn) { 11 | // return this.reduce((ys, x) => 12 | // fn(x).map(x => y => y.concat([x])).ap(ys), point(empty())) 13 | // }; 14 | // 15 | // List.prototype.traverse = function(f, of) { 16 | // return this.map(f).sequence(of); 17 | // }; 18 | // List.prototype.sequence = function(of) { 19 | // return this.foldr(function(m, ma) { 20 | // return m.chain(function(x) { 21 | // if (ma.value.length === 0) return List.pure(x); 22 | // return ma.chain(function(xs) { 23 | // return List.pure(List.of(x).concat(xs)); 24 | // }); 25 | // }) 26 | // }, new List([[]])); 27 | // }; -------------------------------------------------------------------------------- /src/fantasyland/foldable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Foldable 4 | 5 | u.reduce is equivalent to u.reduce((acc, x) => acc.concat([x]), []).reduce 6 | reduce method 7 | 8 | reduce :: Foldable f => f a ~> ((b, a) -> b, b) -> b 9 | A value which has a Foldable must provide a reduce method. The reduce method takes two arguments: 10 | 11 | u.reduce(f, x) 12 | f must be a binary function 13 | 14 | if f is not a function, the behaviour of reduce is unspecified. 15 | The first argument to f must be the same type as x. 16 | f must return a value of the same type as x. 17 | No parts of f's return value should be checked. 18 | x is the initial accumulator value for the reduction 19 | 20 | No parts of x should be checked. 21 | * 22 | */ 23 | 24 | 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # nyc test coverage 18 | .nyc_output 19 | 20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 21 | .grunt 22 | 23 | # node-waf configuration 24 | .lock-wscript 25 | 26 | # Compiled binary addons (http://nodejs.org/api/addons.html) 27 | build/Release 28 | 29 | # Dependency directories 30 | node_modules 31 | jspm_packages 32 | 33 | # Optional npm cache directory 34 | .npm 35 | 36 | # Optional REPL history 37 | .node_repl_history 38 | -------------------------------------------------------------------------------- /src/operations/insertAt.js: -------------------------------------------------------------------------------- 1 | import {append} from '../append'; 2 | import {curry} from '../functional'; 3 | import {sliceLeft, sliceRight} from '../internal' 4 | import {push} from '../internal'; 5 | import {Node as List} from '../Node'; 6 | 7 | /** 8 | * 9 | * @param i 10 | * @param item 11 | * @param list 12 | * @return {Node} 13 | * @private 14 | */ 15 | function _insertAt(i, item, list) { 16 | 17 | // since slice is fast in rrb, try to use it instead of just filter 18 | return append(push(sliceLeft(i, list), item), sliceRight(i, list)) 19 | } 20 | 21 | 22 | const insertAt = List.insertAt = curry(_insertAt); 23 | 24 | List.prototype.insertAt = function(i, item) { 25 | return _insertAt(i, item, this); 26 | }; 27 | 28 | export default insertAt; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | rrbit 3 | 4 | # RRBit 5 | An ultra lightwieght(3kb), fast Immutable vectors/lists/arrays library using the technique 6 | described in the [2012 Paper](https://infoscience.epfl.ch/record/169879/files/RMTrees.pdf) 7 | for Relaxed Radix Balanced(RRB) trees 8 | 9 | 10 | # Deprecated 11 | rrbit is based on the 2012 paper, to see the current work base on the [2015 Paper](https://pdfs.semanticscholar.org/b26a/3dc9050f54a37197ed44711c0e42063e9b96.pdf) 12 | which includes faster prepends, middle updates, event faster appends and more, check out 13 | our one of the sub-projects at [rrbit-org](https://github.com/rrbit-org) -------------------------------------------------------------------------------- /src/fantasyland/monoid.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Monoid 4 | 5 | A value that implements the Monoid specification must also implement the Semigroup specification. 6 | 7 | m.concat(M.empty()) is equivalent to m (right identity) 8 | M.empty().concat(m) is equivalent to m (left identity) 9 | empty method 10 | 11 | empty :: Monoid m => () -> m 12 | A value which has a Monoid must provide an empty function on its type representative: 13 | 14 | M.empty() 15 | Given a value m, one can access its type representative via the constructor property: 16 | 17 | m.constructor.empty() 18 | empty must return a value of the same Monoid 19 | 20 | */ 21 | 22 | import fl from 'fantasy-land'; 23 | import {Node as List} from '../Node'; 24 | import empty from '../create/empty'; 25 | 26 | List[fl.empty] = List.prototype[fl.empty] = empty; -------------------------------------------------------------------------------- /src/operations/foldl.js: -------------------------------------------------------------------------------- 1 | import {curry} from '../functional'; 2 | import {tableOf, isLeaf} from '../accessors'; 3 | import {Node as List} from '../Node'; 4 | 5 | 6 | /** 7 | * fold left(reverse) 8 | * 9 | * @param {function(T, Z)} fn 10 | * @param {Z} accum 11 | * @param {Node} list 12 | * @return {*} 13 | */ 14 | function _foldl(fn, accum, list) { 15 | const table = tableOf(list); 16 | var len = table.length; 17 | if (isLeaf(list)) { 18 | for (var i = 0; len > i; i++) { 19 | accum = fn(table[i], accum); 20 | } 21 | } else { 22 | for (var i = 0; len > i; i++) { 23 | accum = foldl(fn, accum, table[i]); 24 | } 25 | } 26 | return accum; 27 | } 28 | 29 | List.prototype.foldl = function(fn, seed) { 30 | return _foldl(fn, seed, this); 31 | }; 32 | 33 | const foldl = List.foldl = curry(_foldl); 34 | 35 | export default foldl; -------------------------------------------------------------------------------- /src/fantasyland/functor.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Functor 3 | 4 | u.map(a => a) is equivalent to u (identity) 5 | u.map(x => f(g(x))) is equivalent to u.map(g).map(f) (composition) 6 | map method 7 | 8 | map :: Functor f => f a ~> (a -> b) -> f b 9 | A value which has a Functor must provide a map method. The map method takes one argument: 10 | 11 | u.map(f) 12 | f must be a function, 13 | 14 | If f is not a function, the behaviour of map is unspecified. 15 | f can return any value. 16 | No parts of f's return value should be checked. 17 | map must return a value of the same Functor 18 | * 19 | */ 20 | 21 | import fl from 'fantasy-land'; 22 | import {Node as List} from '../Node'; 23 | 24 | List.prototype[fl.map] = function(fn) { 25 | //our standard map provides arguments, but pure functional map is only 1 26 | return this.map((value, i) => fn(value)); 27 | }; -------------------------------------------------------------------------------- /src/operations/foldr.js: -------------------------------------------------------------------------------- 1 | import {curry} from '../functional'; 2 | import {tableOf, isLeaf} from '../accessors'; 3 | import {Node as List} from '../Node'; 4 | 5 | 6 | /** 7 | * fold right 8 | * a.k.a functional style "reduce" 9 | * 10 | * note: standard js reducing fns expect accum first, but this is iteratee first 11 | * 12 | * @param {function(T, Z)}fn 13 | * @param {Z} accum 14 | * @param {Node} list 15 | * @return {*} 16 | */ 17 | function _foldr(fn, accum, list) { 18 | const table = tableOf(list); 19 | var i = table.length; 20 | if (isLeaf(list)) { 21 | while (i--) { 22 | accum = fn(table[i], accum); 23 | } 24 | } else { 25 | while (i--) { 26 | accum = foldr(fn, accum, table[i]); 27 | } 28 | } 29 | return accum; 30 | } 31 | 32 | List.prototype.foldr = List.foldr = function(fn, seed) { 33 | return _foldr(fn, seed, this); 34 | }; 35 | 36 | const foldr = List.foldr = curry(_foldr); 37 | 38 | export default foldr; -------------------------------------------------------------------------------- /src/accessors.js: -------------------------------------------------------------------------------- 1 | import {last} from './functional'; 2 | 3 | /* 4 | * private property accessors 5 | * 6 | * 7 | * 8 | * 9 | * 10 | * 11 | * 12 | */ 13 | 14 | 15 | /** 16 | * get the array containing the lengths of each child node 17 | * @param {Node} list 18 | * @return {Array} 19 | */ 20 | export function lengthsOf(list) { 21 | return list['@@rrb/lengths']; 22 | } 23 | 24 | export function heightOf(list) { 25 | return list['@@rrb/height']; 26 | } 27 | 28 | export function tableOf(list) { 29 | return list['@@rrb/table']; 30 | } 31 | 32 | export function tableLenOf(list) { 33 | return list['@@rrb/table'].length; 34 | } 35 | 36 | 37 | // determine if this is a leaf vs container node 38 | export function isLeaf(node) { 39 | return node['@@rrb/height'] === 0; 40 | } 41 | 42 | // get the # of elements in a rrb list 43 | export function length(list) { 44 | return isLeaf(list) ? list['@@rrb/table'].length : last(list['@@rrb/lengths']); 45 | } -------------------------------------------------------------------------------- /src/create/from.js: -------------------------------------------------------------------------------- 1 | import {Node as List, EMPTY, isNode} from '../Node'; 2 | import fromArray from './_fromArray'; 3 | import times from './times'; 4 | 5 | List.from = from; 6 | 7 | /** 8 | * the default list constructor 9 | * accepts an single native array, varargs, or nothing(if an empty list is desired) 10 | * 11 | */ 12 | export default function from(iterable, mapFn) { 13 | var list = EMPTY; 14 | 15 | if (isNode(iterable)) { 16 | return iterable; 17 | } 18 | 19 | // use more performant, pre-allocation technique when possible 20 | if (Array.isArray(iterable)) { 21 | return !mapFn ? fromArray(iterable) : times((i) => mapFn(iterable[i], i), iterable.length); 22 | } 23 | 24 | // if length is unknown, just use slower push 25 | if (mapFn) { 26 | for (var item of iterable) { 27 | list = list.push(mapFn(item)); 28 | } 29 | } else { 30 | for (var item of iterable) { 31 | list = list.push(item); 32 | } 33 | } 34 | 35 | return list; 36 | } -------------------------------------------------------------------------------- /src/operations/map.js: -------------------------------------------------------------------------------- 1 | import {curry} from '../functional'; 2 | import {tableOf, isLeaf, lengthsOf, heightOf} from '../accessors'; 3 | import {Node as List} from '../Node'; 4 | 5 | function _map(fn, list, from = 0) { 6 | const table = tableOf(list); 7 | const len = table.length; 8 | var tbl = new Array(len); 9 | 10 | // we're micro optimizing for the common use case here, foldr could replace this just fine 11 | // but since we're not changing the length, we can skip over some table reshuffling 12 | if (isLeaf(list)) { 13 | for (var i = 0; len > i; i++) { 14 | tbl[i] = fn(table[i], from + i); 15 | } 16 | } else { 17 | for (var i = 0; len > i; i++) { 18 | tbl[i] = map(fn, table[i], 19 | (i == 0 ? from : from + lengthsOf(list)[i - 1])) 20 | } 21 | } 22 | 23 | 24 | return new List(heightOf(list), tbl, lengthsOf(list)); 25 | } 26 | 27 | List.prototype.map = function(fn) { 28 | return _map(fn, this); 29 | }; 30 | 31 | const map = List.map = curry(_map); 32 | 33 | export default map; -------------------------------------------------------------------------------- /src/operations/indexOf.js: -------------------------------------------------------------------------------- 1 | import {tableOf, isLeaf} from '../accessors'; 2 | import {curry} from '../functional'; 3 | import {Node as List} from '../Node'; 4 | 5 | /** 6 | * 7 | * @param value 8 | * @param list 9 | * @return {*} 10 | */ 11 | function _indexOf(value, list) { 12 | const table = tableOf(list); 13 | var i = table.length; 14 | if (isLeaf(list)) { 15 | while (i--) { 16 | if (table[i] === value) 17 | return i; 18 | } 19 | } else { 20 | while (i--) { 21 | var subI = indexOf(value, table[i]); 22 | if (subI !== -1) 23 | return i + subI; 24 | } 25 | } 26 | return -1; 27 | } 28 | 29 | function _isMember(item, list) { 30 | return indexOf(item, list) !== -1; 31 | } 32 | 33 | 34 | List.prototype.indexOf = function(value) { 35 | return indexOf(value, this); 36 | }; 37 | 38 | List.prototype.isMember = function(item, list) { 39 | return indexOf(item, list) !== -1; 40 | }; 41 | 42 | 43 | const indexOf = List.indexOf = curry(_indexOf); 44 | const isMember = List.isMember = curry(_isMember); 45 | 46 | export { 47 | isMember, 48 | indexOf as default 49 | }; 50 | 51 | -------------------------------------------------------------------------------- /src/fantasyland/semigroup.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * 4 | * Semigroup 5 | 6 | a.concat(b).concat(c) is equivalent to a.concat(b.concat(c)) (associativity) 7 | concat method 8 | 9 | concat :: Semigroup a => a ~> a -> a 10 | A value which has a Semigroup must provide a concat method. The concat method takes one argument: 11 | 12 | s.concat(b) 13 | b must be a value of the same Semigroup 14 | 15 | If b is not the same semigroup, behaviour of concat is unspecified. 16 | concat must return a value of the same Semigroup. 17 | 18 | * 19 | */ 20 | 21 | import fl from 'fantasy-land'; 22 | import {Node as List, isNode} from '../Node'; 23 | import {append} from '../append'; 24 | import {push} from '../internal'; 25 | 26 | function _concat(thing, list) { // Semigroup compat 27 | if (isNode(thing)) 28 | return append(list, thing); // if a semigroup is provided, must return same type 29 | 30 | return push(thing, list); // if not a semigroup, behavior is not specified 31 | } 32 | 33 | function concat(value) { 34 | return _concat(value, this); 35 | } 36 | 37 | List.prototype[fl.concat] = List.prototype.concat = concat; -------------------------------------------------------------------------------- /src/operations/slice.js: -------------------------------------------------------------------------------- 1 | import {isNode, EMPTY, Node as List} from '../Node'; 2 | import {curry} from '../functional'; 3 | import {sliceRight, sliceLeft} from '../internal'; 4 | import {length} from '../accessors'; 5 | 6 | /** 7 | * return a shallow copy of a portion of a list, with supplied "from" and "to"("to" not included) 8 | * 9 | * @param {number} from 10 | * @param {number=} to 11 | * @param {} list 12 | */ 13 | function _slice(from, to, list) { 14 | if (isNode(to)) { 15 | list = to; 16 | to = length(list); 17 | } 18 | const max = length(list); 19 | 20 | if (from >= max) { 21 | return EMPTY; 22 | } 23 | 24 | if (to >= max - 1) { 25 | to = max; 26 | } 27 | 28 | //invert negative numbers 29 | function confine(i) { 30 | return i < 0 ? (i + max) : i; 31 | } 32 | 33 | return sliceLeft(confine(from), sliceRight(confine(to), list)); 34 | } 35 | 36 | // unfortunately we can't curry slice as we're forced to accept current js 37 | // conventions with varying args 38 | const slice = List.slice = _slice; 39 | 40 | List.prototype.slice = function(from, to) { 41 | return _slice(from, to, this); 42 | }; 43 | 44 | export default slice; -------------------------------------------------------------------------------- /src/create/_fromArray.js: -------------------------------------------------------------------------------- 1 | import {Node, EMPTY} from '../Node'; 2 | import {slice} from '../functional'; 3 | import {M} from '../constants'; 4 | import {length} from '../accessors' 5 | 6 | /** 7 | * create an rrb vector from a js array 8 | * note: this is meant for internal use only. public usage should be with List.from() 9 | * 10 | * @param {Array} jsArray 11 | */ 12 | export default function fromArray(jsArray) { 13 | var len = jsArray.length; 14 | if (len === 0) 15 | return EMPTY; 16 | 17 | return _fromArray(jsArray, Math.floor(Math.log(len) / Math.log(M)), 0, len); 18 | 19 | function _fromArray(jsArray, h, from, to) { 20 | if (h === 0) { 21 | return new Node(0, slice(from, to, jsArray), void 0); 22 | } 23 | 24 | var step = Math.pow(M, h); 25 | var len = Math.ceil((to - from) / step); 26 | var table = new Array(len); 27 | var lengths = new Array(len); 28 | for (var i = 0; len > i; i++) { 29 | //todo: trampoline? 30 | table[i] = _fromArray(jsArray, h - 1, from + (i * step), Math.min(from + ((i + 1) * step), to)); 31 | lengths[i] = length(table[i]) + (i > 0 ? lengths[i - 1] : 0); 32 | } 33 | return new Node(h, table, lengths); 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /src/iterator/iterator.js: -------------------------------------------------------------------------------- 1 | import {isLeaf, tableOf} from '../accessors'; 2 | 3 | //iterator value signatures 4 | function done() { return { done: true, value: null} } 5 | function value(val) { return { done: false, value: val }; } 6 | 7 | /** 8 | * create a js iterator for a list 9 | * 10 | * @param {Node} list 11 | * @return {Iterator} 12 | */ 13 | export default function iterator(list) { 14 | return isLeaf(list) ? _leafIterator(list) : _nodeIterator(list); 15 | 16 | function _leafIterator(leaf) { 17 | var table = tableOf(leaf); 18 | var len = table.length; 19 | var i = 0; 20 | 21 | return { 22 | next() { 23 | return len > i ? value(table[i++]) : done(); 24 | } 25 | } 26 | } 27 | 28 | function _nodeIterator(node) { 29 | var table = tableOf(node); 30 | var len = table.length; 31 | var i = 0; 32 | var current = iterator(table[0]); 33 | 34 | return { 35 | next() { 36 | var response = current.next(); 37 | if (!response.done) 38 | return response; 39 | 40 | // current iterator is done, get the next iterator and result 41 | return (++i >= len ? done() : (current = iterator(table[i])).next()); 42 | } 43 | } 44 | } 45 | 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/operations/intersperse.js: -------------------------------------------------------------------------------- 1 | import foldr from './foldr'; 2 | import slice from './slice'; 3 | import {push} from '../internal'; 4 | import {EMPTY, Node as List} from '../Node'; 5 | import {curry} from '../functional'; 6 | import {length} from '../accessors'; 7 | 8 | function tail(list) { 9 | return slice(1, length(list), list); 10 | } 11 | 12 | //pop first element and wrap in a list 13 | function head(list) { 14 | return slice(0,1, list); 15 | } 16 | 17 | function prepend(pre) { 18 | return function fold(value, list) { 19 | return push(value, push(pre, list)); 20 | } 21 | } 22 | 23 | /** 24 | * Inject a value between all members of the list. 25 | * 26 | * ``` 27 | * intersperse(",", ["one", "two", "three"]) == ["one", ",", "two", ",", "three"] 28 | * ``` 29 | * @param separator 30 | * @param {List} list 31 | * @return {List} 32 | * @private 33 | */ 34 | function _intersperse(separator, list) { 35 | if (!length(list)) 36 | return EMPTY; 37 | 38 | return foldr(prepend(separator), head(list), tail(list)) 39 | } 40 | 41 | const intersperse = List.intersperse = curry(_intersperse); 42 | 43 | List.prototype.intersperse = function(separator) { 44 | return intersperse(separator, this); 45 | }; 46 | 47 | export default intersperse; -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /src/iterator/reverseIterator.js: -------------------------------------------------------------------------------- 1 | import {tableOf, isLeaf} from '../accessors'; 2 | 3 | //iterator value signatures 4 | function done() { return { done: true, value: null} } 5 | function value(val) { return { done: false, value: val }; } 6 | 7 | 8 | /** 9 | * iterator that starts from tail and works backwards 10 | * @param list 11 | */ 12 | export default function tailIterator(list) { 13 | throw new Error("feature not yet implemented"); 14 | 15 | return isLeaf(list) ? _leafIterator(list) : _nodeIterator(list); 16 | 17 | 18 | function _leafIterator(leaf) { 19 | var table = tableOf(leaf); 20 | var i = table.length; 21 | 22 | return { 23 | next() { 24 | return --i >= 0 ? value(table[i]) : done(); 25 | } 26 | } 27 | } 28 | 29 | function _nodeIterator(node) { 30 | var table = tableOf(node); 31 | var len = table.length; 32 | var current = tailIterator(table[--len]); //table cannot have 0 length because it's a container, so we're "safe" TM 33 | 34 | return { 35 | next() { 36 | var response = current.next(); 37 | if (!response.done) 38 | return response; 39 | 40 | // current iterator is done, get the next iterator and result 41 | return (--len >= 0 ? (current = tailIterator(table[len])).next() : done() ); 42 | } 43 | } 44 | } 45 | 46 | 47 | } 48 | -------------------------------------------------------------------------------- /src/create/times.js: -------------------------------------------------------------------------------- 1 | import {Node as List, EMPTY} from '../Node'; 2 | import {M} from '../constants'; 3 | import {length} from '../accessors'; 4 | 5 | List.times = times; 6 | 7 | /** 8 | * populate an array using provided function 9 | * 10 | * @param len 11 | * @param {function(number)} fn 12 | * @return {Node} 13 | */ 14 | export default function times(fn, len) { 15 | if (len <= 0) 16 | return EMPTY; 17 | 18 | // just iterating over push() isn't terribly fast... 19 | // we attempt to optimize here by pre-allocating 20 | 21 | var height = Math.floor( Math.log(len) / Math.log(M) ); 22 | return populate(fn, height, 0, len); 23 | 24 | function populate(func, h, from, to) { 25 | 26 | if (h === 0) { //leaf node 27 | return populateLeaf(func, from, to); 28 | } 29 | 30 | // populate container node 31 | var step = Math.pow(M, h); 32 | var len = Math.ceil((to - from) / step); 33 | var table = new Array(len); 34 | var lengths = new Array(len); 35 | for (var i = 0; len > i; i++) { 36 | // todo: trampoline? 37 | table[i] = populate(func, h - 1, from + (i * step), Math.min(from + ((i + 1) * step), to)); 38 | lengths[i] = length(table[i]) + (i > 0 ? lengths[i-1] : 0); 39 | } 40 | return new List(h, table, lengths); 41 | } 42 | 43 | function populateLeaf(fn, from, to) { 44 | var len = (to - from) % (M + 1); 45 | var table = new Array(len); 46 | for (var i = 0; len > i; i++) { 47 | table[i] = fn(from + i); 48 | } 49 | return new List(0, table, void 0); 50 | } 51 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rrbit", 3 | "version": "1.0.0", 4 | "description": "an immutable list lib using rrb trees", 5 | "main": "lib/index.js", 6 | "jsnext:main": "src/index.js", 7 | "directories": { 8 | "test": "test" 9 | }, 10 | "dependencies": { 11 | "fantasy-land": "^2.1.0" 12 | }, 13 | "devDependencies": { 14 | "babel-cli": "^6.18.0", 15 | "babel-core": "^6.18.2", 16 | "babel-plugin-transform-es2015-modules-umd": "^6.18.0", 17 | "babel-polyfill": "^6.18.2", 18 | "babel-preset-es2015": "^6.18.0", 19 | "babel-preset-stage-0": "^6.16.0", 20 | "benchmark": "^2.1.2", 21 | "chai": "^3.5.0", 22 | "fantasy-check": "^0.3.2", 23 | "immutable": "^3.8.1", 24 | "immutable-array-methods": "^1.5.0", 25 | "mocha": "^3.1.2", 26 | "mori": "^0.3.2", 27 | "rollup": "^0.36.3", 28 | "seamless-immutable": "^6.3.0", 29 | "uglify-js": "^2.7.4" 30 | }, 31 | "scripts": { 32 | "test": "mocha --compilers js:babel-core/register", 33 | "build": "./node_modules/.bin/babel src/index.js | ./node_modules/.bin/uglifyjs > dist/rrbit.js", 34 | "build-node": "./node_modules/.bin/rollup src/index.js --output lib/index.js --format cjs", 35 | "build-ral": "./node_modules/.bin/rollup src/SkewList.js --output lib/ral.js --format cjs", 36 | "build-block": "./node_modules/.bin/rollup src/Block.js --output lib/block.js --format cjs" 37 | }, 38 | "keywords": [ 39 | "immutable", 40 | "list", 41 | "collection", 42 | "rrb", 43 | "tree" 44 | ], 45 | "author": "ben.greer", 46 | "license": "WTFPL" 47 | } 48 | -------------------------------------------------------------------------------- /src/fantasyland/chain.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Chain 4 | 5 | A value that implements the Chain specification must also implement the Apply specification. 6 | 7 | m.chain(f).chain(g) is equivalent to m.chain(x => f(x).chain(g)) (associativity) 8 | chain method 9 | 10 | chain :: Chain m => m a ~> (a -> m b) -> m b 11 | A value which has a Chain must provide a chain method. The chain method takes one argument: 12 | 13 | m.chain(f) 14 | f must be a function which returns a value 15 | 16 | If f is not a function, the behaviour of chain is unspecified. 17 | f must return a value of the same Chain 18 | chain must return a value of the same Chain 19 | * 20 | */ 21 | 22 | import fl from 'fantasy-land'; 23 | import {Node as List, EMPTY, isNode} from '../Node'; 24 | import foldr from '../operations/foldr'; 25 | import {append} from '../append'; 26 | import {push} from '../internal'; 27 | 28 | function _concat(thing, list) { // Semigroup compat 29 | if (isNode(thing)) 30 | return append(list, thing); // if a semigroup is provided, must return same type 31 | 32 | return push(thing, list); // if not a semigroup, behavior is not specified 33 | } 34 | 35 | function chain(f) { 36 | //note: this is single level concat, but most functional level concat 37 | // rules define this as fully recursive... do we need to? 38 | return foldr((value, acc) => 39 | _concat(f(value), acc), EMPTY, this); 40 | 41 | // recursive concat ??? 42 | 43 | // function _concat(list) { 44 | // if (list.length === 0) 45 | // return empty(); 46 | // return list.head().concat(_concat(list.tail())); 47 | // } 48 | // 49 | // return _concat(this.map(f)); 50 | } 51 | 52 | List.prototype[fl.chain] = List.prototype.chain = List.prototype.flatMap = chain; -------------------------------------------------------------------------------- /perf/RAL_vs_array.js: -------------------------------------------------------------------------------- 1 | var Benchmark = require('benchmark'); 2 | var suite = Benchmark.Suite('push items to list performance'); 3 | require('babel-polyfill'); 4 | var runSuite = require('./runSuite'); 5 | var List = require('../lib/ral').default; 6 | 7 | 8 | function objLookup(obj) { 9 | for (var i = 0; 16 > i; i++) { 10 | var x = obj[i] + obj[i] + obj[i] + obj[i] + obj[i] 11 | } 12 | } 13 | function randomBetween(min, max) { 14 | return Math.floor(Math.random() * (max - min + 1) + min); 15 | } 16 | 17 | function rand(max) { 18 | return randomBetween(0, max) 19 | } 20 | 21 | 22 | function range(max) { 23 | var i = 0, 24 | list = List(); 25 | 26 | while (max > i) { 27 | list = list.unshift(i++); 28 | } 29 | return list; 30 | } 31 | 32 | function aRange(len) { 33 | return new Array(len).map((v, i) => i); 34 | } 35 | 36 | 37 | var RAL_LIST32 = range(32); 38 | var ARRAY32 = aRange(32); 39 | /** 40 | * test array of 32, as that's what our i 41 | */ 42 | 43 | 44 | 45 | suite 46 | .add('Array: update 32', function() { 47 | var ll = ARRAY32.slice(); 48 | var n = rand(32); 49 | ll.splice(n, 1, n); 50 | }) 51 | .add('RAL: update 32', function() { 52 | var n = rand(32); 53 | var ll = RAL_LIST32.set(n, n) 54 | }) 55 | .add('Array: push 32', function() { 56 | var list = []; 57 | var i = 32; 58 | while (i--) { 59 | list = list.slice(); 60 | list.push(i) 61 | } 62 | }) 63 | .add('RAL: push 32', function() { 64 | var list = List(); 65 | var i = 32; 66 | while (i--) 67 | list = list.unshift(i); 68 | }) 69 | .add('Array: get 32', function() { 70 | var x = ARRAY32[rand(32)] 71 | }) 72 | .add('RAL: get 32', function() { 73 | var x = RAL_LIST32.nth(rand(32)) 74 | }) 75 | 76 | ; 77 | 78 | runSuite(suite); 79 | -------------------------------------------------------------------------------- /src/Node.js: -------------------------------------------------------------------------------- 1 | import {length} from './accessors'; 2 | /** 3 | * an RRB tree has two data types: 4 | * 5 | * Leaf 6 | * - height is always 0 7 | * - table is an collection of values 8 | * 9 | * Parent 10 | * - height is always greater than 0 11 | * - table is collection of child nodes 12 | * - lengths is cache of accumulated lengths of children 13 | * 14 | * height and table are mandatory, lengths may be null 15 | * 16 | */ 17 | 18 | /** 19 | * 20 | * @param {Array} table 21 | * @return {Node} 22 | * @constructor 23 | */ 24 | export function Leaf(table) { 25 | return new Node(0, table, void 0); 26 | } 27 | 28 | /** 29 | * 30 | * @param {Number} height 31 | * @param {Array} table 32 | * @param {Array} lengths 33 | * @return {Node} 34 | * @constructor 35 | */ 36 | export function Parent(height, table, lengths) { 37 | return new Node(height, table, lengths); 38 | } 39 | 40 | /** 41 | * The base list class 42 | * @param {number} height 43 | * @param {Array} table 44 | * @param {Array} lengths 45 | * @constructor 46 | */ 47 | export function Node(height, table, lengths) { 48 | this['@@rrb/lengths'] = lengths; 49 | this['@@rrb/height'] = height; 50 | this['@@rrb/table'] = table; 51 | } 52 | 53 | Node.prototype.isEmpty = () => false; // small optimization because all empty lists are the same element 54 | Node.prototype.size = function() { return length(this); }; 55 | 56 | Object.defineProperty(Node.prototype, 'length', { 57 | get() { 58 | return this.size(); 59 | }, 60 | set(value) { 61 | //do nothing 62 | } 63 | }); 64 | 65 | export const EMPTY = Object.freeze(Object.assign(new Node(0, [], void 0), { 66 | isEmpty() { return true; }, 67 | size() { return 0; } 68 | })); 69 | 70 | export function isListNode(item) { 71 | return item instanceof Node; 72 | } 73 | 74 | export function isNode(item) { 75 | return item instanceof Node; 76 | } 77 | 78 | export function isLeaf(node) { 79 | return node['@@rrb/height'] === 0; 80 | } 81 | 82 | export function isParent(node) { 83 | return node['@@rrb/height'] > 0; 84 | } -------------------------------------------------------------------------------- /src/fantasyland/applicative.js: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * Apply 4 | 5 | A value that implements the Apply specification must also implement the Functor specification. 6 | 7 | v.ap(u.ap(a.map(f => g => x => f(g(x))))) is equivalent to v.ap(u).ap(a) (composition) 8 | ap method 9 | 10 | ap :: Apply f => f a ~> f (a -> b) -> f b 11 | A value which has an Apply must provide an ap method. The ap method takes one argument: 12 | 13 | a.ap(b) 14 | b must be an Apply of a function, 15 | 16 | If b does not represent a function, the behaviour of ap is unspecified. 17 | a must be an Apply of any value 18 | 19 | ap must apply the function in Apply b to the value in Apply a 20 | 21 | No parts of return value of that function should be checked. 22 | 23 | 24 | 25 | Applicative 26 | 27 | A value that implements the Applicative specification must also implement the Apply specification. 28 | 29 | v.ap(A.of(x => x)) is equivalent to v (identity) 30 | A.of(x).ap(A.of(f)) is equivalent to A.of(f(x)) (homomorphism) 31 | A.of(y).ap(u) is equivalent to u.ap(A.of(f => f(y))) (interchange) 32 | of method 33 | 34 | of :: Applicative f => a -> f a 35 | A value which has an Applicative must provide an of function on its type representative. The of function takes one argument: 36 | 37 | F.of(a) 38 | Given a value f, one can access its type representative via the constructor property: 39 | 40 | f.constructor.of(a) 41 | of must provide a value of the same Applicative 42 | 43 | No parts of a should be checked 44 | * 45 | */ 46 | 47 | import fl from 'fantasy-land'; 48 | import {Node as List} from '../Node'; 49 | import one from '../create/one'; 50 | 51 | 52 | function ofOne(item) { 53 | return one(item); 54 | } 55 | 56 | function ap(values) { 57 | return this.map(fn => values.map(fn)); 58 | } 59 | 60 | // required on all instances for Applicative compat 61 | List.prototype.of = List.prototype[fl.of] = ofOne; 62 | List[fl.ap] = List.prototype.ap = List.prototype[fl.ap] = ap; 63 | 64 | 65 | // List.prototype.ap = List.prototype[fl.ap] = function ap(other) { 66 | // return this.map(f => other.map(x => f(x))).flatten() 67 | // }; -------------------------------------------------------------------------------- /test/functional.test.js: -------------------------------------------------------------------------------- 1 | import {expect} from 'chai'; 2 | import { 3 | curry, 4 | compose, 5 | always, 6 | apply, 7 | defaultTo, 8 | attr} from '../src/functional'; 9 | 10 | function double(n) { 11 | return n * 2 12 | } 13 | 14 | function inc(n) { 15 | return n + 1 16 | } 17 | 18 | function half(n) { 19 | return div(n, 2) 20 | } 21 | 22 | function div(a, b) { 23 | return a / b 24 | } 25 | 26 | function add(a, b) { 27 | return a + b; 28 | } 29 | 30 | describe("function composition tests", function() { 31 | 32 | describe("curry", function() { 33 | it('expect a function as first arg', () => { 34 | var add1 = curry(add)(1); 35 | 36 | expect(add1(1)).to.equal(2); 37 | }); 38 | }); 39 | 40 | describe('compose', function() { 41 | 42 | 43 | 44 | it('executes composed functions right-to-left', function() { 45 | 46 | var composed = compose(double, inc, half); // composed(x) = dub(inc(half(x))) 47 | 48 | expect(composed(2)).to.equal(4); // if this gives 2.5 the order is wrong 49 | }); 50 | 51 | it('may have an outermost function taking more than one argument', function() { 52 | 53 | var composed = compose(inc, div); // composed(x, y) = x/y +1 54 | 55 | expect(composed(10, 2)).to.equal(6); // 10/2 +1 = 5+1 = 6 56 | }); 57 | 58 | 59 | it('can compose one function to give just that function', function() { 60 | 61 | expect(compose(div)(20, 5)).to.equal(4); 62 | }); 63 | 64 | it('gives an identity function when making a composition of zero functions', function() { 65 | var id = compose(); 66 | 67 | expect(id(2)).to.equal(2); 68 | }); 69 | }); 70 | 71 | describe('attr', function() { 72 | it('can get a value from an object at the named key', function() { 73 | var getA = attr('A'); 74 | 75 | expect( getA({A:'B'}) ).to.equal( 'B' ); 76 | }); 77 | 78 | it('can get the length of a string', function() { 79 | var getLength = attr('length'); 80 | 81 | expect( getLength("hello") ).to.equal( 5 ); 82 | }); 83 | 84 | it('can get a numbered array element out', function() { 85 | var getLength = attr(0); 86 | 87 | expect( getLength(['a','b','c']) ).to.equal( 'a' ); 88 | }); 89 | }); 90 | }); -------------------------------------------------------------------------------- /src/operations/caseOf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A pattern match/guard helper for functional style switch statements 3 | * 4 | * accepts an array or object of function case handlers 5 | * 6 | * array style, read each functions # of arguments and selects that case when 7 | * list length matches, or uses the last array item when none 8 | * 9 | * object style, uses matches on object's 'key' as the length 10 | * default case key is '_' 11 | * 12 | * ``` 13 | * //example using array syntax(last item is "default" fallback) 14 | * let getLast = List.switch([ 15 | * () => [], 16 | * (a) => [a], 17 | * (_, b) => [b], 18 | * (...items) => [items[items.length]] 19 | * ]) 20 | * ``` 21 | * 22 | * ``` 23 | * //example using object syntax("_" is "default" fallback) 24 | * let add1 = List.switch([ 25 | * "0": () => [], 26 | * "1": (a) => [a + 1], 27 | * "_": (...items) => items.map(i => i + a) 28 | * ]) 29 | * ``` 30 | * 31 | * 32 | * @param {Object|Array}patterns 33 | * @return {Function} 34 | */ 35 | import {Node as List} from '../Node'; 36 | import {last} from '../functional'; 37 | 38 | function arrayCaseSwitch(patterns) { 39 | /** 40 | * @param {List} list 41 | */ 42 | return function(list) { 43 | var len = list.length; 44 | 45 | for (var i = 0, l = patterns.length; l > i; i++) { 46 | var fn = patterns[i]; 47 | if (fn.length === len); 48 | return fn.call(null, ...list.slice(0, i)); 49 | } 50 | 51 | // if we didn't find a match, assum the last function is the "default" case 52 | return last(patterns).call(null, ...list); 53 | } 54 | } 55 | 56 | 57 | function objectCaseSwitch(patterns) { 58 | /** 59 | * @param {List} list 60 | */ 61 | return function(list) { 62 | var len = list.length; 63 | 64 | var fn = patterns[len]; 65 | if (fn) 66 | return fn.call(null, ...list.slice(0, len)); 67 | 68 | let fallback = patterns["_"] || patterns["*"]; 69 | 70 | if (fallback) 71 | return fallback.call(null, ...list.slice(0, len)); 72 | } 73 | } 74 | 75 | 76 | List.caseOf = function(patterns) { 77 | if (Array.isArray(patterns)) { 78 | return arrayCaseSwitch(patterns); 79 | } 80 | 81 | if (typeof patterns == "object") { 82 | return objectCaseSwitch(patterns); 83 | } 84 | 85 | throw new TypeError("invalid switch descriptor provided") 86 | }; -------------------------------------------------------------------------------- /src/experimental/LinkedList.js: -------------------------------------------------------------------------------- 1 | 2 | // common core for both forward and reverse iterating linked lists 3 | function SingleLinkedList(data, len, next) { 4 | this.data = data; 5 | this.link = next; 6 | this.length = len; 7 | } 8 | 9 | function one(value) { 10 | return new SingleLinkedList(value, 1, null) 11 | } 12 | 13 | function reverseOne(value) { 14 | return new SingleLinkedList(value, -1, null) 15 | } 16 | 17 | function add(value, list) { 18 | var len = list.length < 0 ? list.length - 1 : list.length + 1 19 | return new SingleLinkedList(value, len, list) 20 | } 21 | 22 | function nth(i, list, notFound) { 23 | if (0 > i) //if negative 24 | i = i + list.length; 25 | 26 | if (i > list.length) 27 | return notFound; 28 | 29 | while (list) { 30 | if ((list.length -1) == i) 31 | return list.data; 32 | 33 | list = list.link; 34 | } 35 | 36 | return notFound; 37 | } 38 | // easy, drop the tail items until the length is n 39 | function take(n, list) { 40 | while(list && list.length > n) { 41 | list = list.link; 42 | } 43 | return list 44 | } 45 | 46 | // harder, have to drop head by first walking back from end 47 | function drop(n, list) { 48 | if (n >= list.length) return; 49 | var newLen = list.length - n; 50 | var temp = new Array(newLen); 51 | while(newLen) { 52 | temp[--i] = list.data; 53 | list = list.link 54 | } 55 | // for (var i = 0; newLen > i; i++) { 56 | // temp[i] = list.data; 57 | // list = list.link 58 | // } 59 | return fromArray(temp) 60 | } 61 | 62 | function fromArray(arr) { 63 | if (!arr.length) return; 64 | var list = one(arr[0]); 65 | for (var i = 1, l = arr.length; l > i; i++) { 66 | list = add(arr[i], list) 67 | } 68 | return list; 69 | } 70 | function toArray(list) { 71 | var i = 0; 72 | var arr = new Array(list.length); 73 | 74 | while (list) { 75 | arr[i++] = list.data; 76 | list = list.link; 77 | } 78 | return arr; 79 | } 80 | 81 | function toArrayReverse(list) { 82 | var i = list.length - 1; 83 | var arr = new Array(i); 84 | 85 | while (list) { 86 | arr[i--] = list.data; 87 | list = list.link; 88 | } 89 | return arr; 90 | } 91 | 92 | 93 | // module.exports = { 94 | // one: one, 95 | // add: add, 96 | // nth: nth, 97 | // take: take, 98 | // drop: drop, 99 | // toArray: toArray 100 | // } 101 | 102 | export { 103 | one, 104 | add, 105 | nth, 106 | take, 107 | drop, 108 | toArray 109 | } -------------------------------------------------------------------------------- /test/RandomAccessLists.test.js: -------------------------------------------------------------------------------- 1 | import { expect } from 'chai'; 2 | // import 'babel-polyfill'; 3 | 4 | import List from '../src/experimental/SkewList'; 5 | 6 | function range(from, to) { 7 | var list = new List(); 8 | while (to--) { 9 | list = list.unshift(i); 10 | if (to === from) return list; 11 | } 12 | return list; 13 | } 14 | 15 | describe("Random Access Lists", function() { 16 | 17 | it("basic sanity test", function() { 18 | var list = new List(); 19 | list = list.unshift("a") 20 | }); 21 | 22 | describe("ranges in order", function() { 23 | var list = new List(); 24 | var iList = new List(); 25 | 26 | it("can produce a range in order", function() { 27 | list = range(0, 10000); 28 | 29 | var i = 10000; 30 | while (i--) { 31 | iList = iList.unshift(i + "i"); 32 | } 33 | }); 34 | 35 | 36 | it("can get items in their order", function() { 37 | for (var i = 0; i < 10000; i++) { 38 | expect(list.nth(i)).to.equal(i) 39 | } 40 | 41 | for (var i = 0; i < 10000; i++) { 42 | expect(iList.nth(i)).to.equal(i + "i") 43 | } 44 | }); 45 | 46 | it("can iterate in order", function() { 47 | var i = 0; 48 | for (var item of list) { 49 | expect(item).to.equal(i++) 50 | } 51 | }); 52 | 53 | it("can iterate in reverse order", function() { 54 | var i = 10000; 55 | for (var item of list.reverse()) { 56 | expect(item).to.equal(--i) 57 | } 58 | }); 59 | 60 | it("can calc it's length", function() { 61 | expect(list.length).to.equal(10000) 62 | }); 63 | }); 64 | 65 | describe("mutation tests", function() { 66 | var list = range(0, 10); 67 | var list1k = range(0, 1000); 68 | var sf = new SForest(); 69 | 70 | for (var i = 0; i < 10; i++) { 71 | sf = sf.cons(i); 72 | } 73 | 74 | it("is in order", function() { 75 | expect(list.get(0)).to.equal(0); 76 | expect(list.get(5)).to.equal(5); 77 | expect(list.get(9)).to.equal(9); 78 | }); 79 | 80 | it("can minimally update", function() { 81 | var l = list.set(5, "tada!"); 82 | expect(l.get(5)).to.equal("tada!"); 83 | }); 84 | 85 | it("support mapping", function() { 86 | var i = 0 87 | , list2 = list.map(i => i + "i"); 88 | for(var item of list2) { 89 | expect(item).to.equal((i++) + "i") 90 | } 91 | }); 92 | 93 | it("supports reducing", function() { 94 | var add = (a, b) => a + b; 95 | 96 | expect(list.reduce(add, 0)).to.equal(45); 97 | expect(list1k.reduce(add, 0)).to.equal(499500); 98 | }) 99 | }) 100 | 101 | }); -------------------------------------------------------------------------------- /docs/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/functional.js: -------------------------------------------------------------------------------- 1 | const _slice = Array.prototype.slice; 2 | export function slice(from, to, array) { 3 | return _slice.call(array, from, to); 4 | } 5 | 6 | export function compose() { 7 | var funcs = _slice.call(arguments); 8 | 9 | if (funcs.length == 0) 10 | return identity; 11 | 12 | return function () { 13 | var val = apply(funcs[funcs.length - 1], arguments); 14 | for (var i = funcs.length - 2; i > -1; i--) { 15 | val = funcs[i].call(undefined, val) 16 | } 17 | return val 18 | } 19 | } 20 | 21 | export function curry(fn) { 22 | 23 | return function currydFn() { 24 | return _currify(fn, _slice.call(arguments), fn.length - arguments.length) 25 | } 26 | } 27 | 28 | function _currify(fn, args, remain) { 29 | if (remain < 1) 30 | return apply(fn, args); 31 | 32 | return function() { 33 | args = args.slice(0, fn.length-1).concat(_slice.call(arguments, 0)); 34 | return _currify(fn, args, remain - arguments.length); 35 | } 36 | } 37 | 38 | export function apply(fn, args) { 39 | var len = args.length; 40 | 41 | if (len === 0) return fn(); 42 | if (len === 1) return fn(args[0]); 43 | if (len === 2) return fn(args[0], args[1]); 44 | if (len === 3) return fn(args[0], args[1], args[2]); 45 | if (len === 4) return fn(args[0], args[1], args[2], args[3]); 46 | if (len === 5) return fn(args[0], args[1], args[2], args[3], args[4]); 47 | if (len === 6) return fn(args[0], args[1], args[2], args[3], args[4], args[5]); 48 | if (len === 7) return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6]); 49 | if (len === 8) return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]); 50 | if (len === 9) return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]); 51 | if (len === 10)return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]); 52 | 53 | return fn.apply(undefined, args); 54 | } 55 | 56 | export function flip(fn){ 57 | return (a, b) => fn(b, a); 58 | } 59 | 60 | 61 | export function always() { return true; } 62 | 63 | export function identity(value) { return value; } 64 | 65 | export const attr = curry(function attr(key, obj) { 66 | return obj[key]; 67 | }); 68 | 69 | // clone an array 70 | export function copy(array) { 71 | return _slice.call(array, 0); 72 | } 73 | 74 | export function defaultTo(dflt) { 75 | return function(value) { 76 | return typeof value == "undefined" || value === null || isNaN(value) ? dflt : value; 77 | } 78 | } 79 | 80 | export function last(list) { 81 | return list[list.length - 1]; 82 | } 83 | 84 | export function first(list) { 85 | return list[0]; 86 | } 87 | 88 | function nth(offset, list) { 89 | return list[ offset < 0 ? list.length + offset : offset ]; 90 | } 91 | 92 | export function setLast(value, jsArray) { 93 | jsArray[jsArray.length - 1] = value; 94 | return jsArray; 95 | } -------------------------------------------------------------------------------- /API.md: -------------------------------------------------------------------------------- 1 | 2 | ## Creating Lists: 3 | 4 | ### create from arguments 5 | ```javascript 6 | import List from 'rrbit' 7 | const list = List.of(1,2,3,4) //-> [1,2,3,4] 8 | ``` 9 | 10 | ### create from an iterable 11 | ```javascript 12 | import List from 'rrbit' 13 | const list = List.from([1,2,3,4]); //-> [1,2,3,4] 14 | // or from a map 15 | const list = List.from(new Map([['a', 1], ['b', 2], ['c', 2]])); 16 | ``` 17 | 18 | ### create an empty list 19 | ```javascript 20 | import List from 'rrbit' 21 | const list = List.empty(); 22 | const list2 = List.empty(); 23 | // all empty lists, no matter how they are created, are equal 24 | list === list2 //-> true 25 | 26 | ``` 27 | 28 | ### create a range, using an input function 29 | ```javascript 30 | import List from 'rrbit' 31 | const timesTwo = (i) => i * 2 32 | const list = List.times(timesTwo, 5) // -> [2,4,6,8,10] 33 | ``` 34 | 35 | 36 | 37 | 38 | ## operations 39 | 40 | 41 | #### get size of a list 42 | `List.lengthOf(List)` 43 | ```javascript 44 | import List from 'rrbit' 45 | 46 | const list1 = List.of(1,2,3); 47 | const size = List.lengthOf(list1); //-> 3a 48 | const size2 = list1.length; //-> 3 49 | ``` 50 | 51 | #### adding items to a list 52 | set an element at a particular index, returns an updated array 53 | if the index is out of range, the array is unaltered 54 | 55 | `List.push(Any, List)` 56 | ```javascript 57 | import List from 'rrbit' 58 | 59 | const list = List.push(item, list); 60 | const list2 = list.push(item); 61 | ``` 62 | 63 | ### join two lists together 64 | also know as `concat` is some languages. 65 | note: does 'not' recursively concat children 66 | 67 | `List.append(List, List)` 68 | ```javascript 69 | import List from 'rrbit' 70 | 71 | const list1 = List.of(1,2,3); 72 | const list2 = List.of(4,5,6); 73 | const list3 = List.of(7,8,9); 74 | 75 | const list = List.append(list1, list2); //-> [1,2,3,4,5,6] 76 | //or 77 | const other = list.append(list3); //-> [1,2,3,4,5,6,7,8,9] 78 | ``` 79 | 80 | 81 | ### get an item in a list 82 | `List.get(Number)` 83 | ```javascript 84 | import List from 'rrbit' 85 | const list = List.of('a','b','c'); 86 | 87 | var item = list.get(2); //-> 'b 88 | ``` 89 | 90 | ### set an item in a list 91 | `List.set(Number, Any)` 92 | ```javascript 93 | import List from 'rrbit' 94 | const list = List.of('a','b','c'); 95 | 96 | var item = list.set(2, 'd'); //-> ['a','d','c'] 97 | ``` 98 | 99 | ### transform a list 100 | `List.map(Function, List)` 101 | ```javascript 102 | import List from 'rrbit' 103 | // note the map fn is value 104 | const double = (x, i) => x + x 105 | 106 | const list = List.of('a','b','c'); 107 | const lis2 = List.map(double, list); //-> ['aa','bb ,'cc'] 108 | const list3 = list2.map(double); //-> ['aaaa','bbbb ,'cccc'] 109 | ``` 110 | 111 | ### slice 112 | get a subsection of a list, the `start` and end are a 0 based index of the list. 113 | the slice is up to, but not including `end`. 114 | both start and end can be negative 115 | 116 | List.slice 117 | List.filter 118 | List.foldr 119 | List.foldl 120 | List.reverse -------------------------------------------------------------------------------- /test/fantasyLandCompliance.test.js: -------------------------------------------------------------------------------- 1 | import {λ} from 'fantasy-check' 2 | import {describe, it} from 'mocha' 3 | import {expect} from 'chai' 4 | import assert from 'assert' 5 | import * as Functor from 'fantasy-land/laws/functor' 6 | import * as Chain from 'fantasy-land/laws/chain' 7 | import * as Apply from 'fantasy-land/laws/apply' 8 | import * as Applicative from 'fantasy-land/laws/applicative' 9 | import * as Semigroup from 'fantasy-land/laws/semigroup' 10 | import * as Monoid from 'fantasy-land/laws/monoid' 11 | import fl from 'fantasy-land' 12 | import List from '../src/index' 13 | 14 | const id = x => x 15 | 16 | export function assertSame (listA, ListB) { 17 | assert(listA.length == listB.length, "lists not same length"); 18 | 19 | listA.map((item, i) => 20 | assert.strictEqual(item, listB.get(i))); 21 | } 22 | 23 | describe("fantasy-land laws compliance", function() { 24 | 25 | describe('functor', () => { 26 | it('should satisfy identity', () => { 27 | return Functor.identity(List.of, assertSame, {}) 28 | }); 29 | 30 | it('should satisfy composition', () => { 31 | const f = x => x + 'f'; 32 | const g = x => x + 'g'; 33 | return Functor.composition(List.of, assertSame, f, g, 'x') 34 | }); 35 | 36 | it('should be covered', () => { 37 | return List.of()[fl.map](id) 38 | }); 39 | }); 40 | 41 | describe('apply', () => { 42 | it('should satisfy composition', () => { 43 | return Apply.composition(List.of, assertSame, {}) 44 | }); 45 | 46 | it('should be covered', () => { 47 | return List.of()[fl.ap](List.of(id)) 48 | }) 49 | }); 50 | 51 | describe('applicative', () => { 52 | it('should satisfy identity', () => { 53 | return Applicative.identity(List, assertSame, {}) 54 | }); 55 | 56 | it('should satisfy homomorphism', () => { 57 | return Applicative.homomorphism(List, assertSame, {}) 58 | }); 59 | 60 | it('should satisfy interchange', () => { 61 | return Applicative.interchange(List, assertSame, {}) 62 | }); 63 | 64 | it('should be covered', () => { 65 | var fn = List[fl.of];// << technically, it might be 66 | return List.of(undefined) 67 | }); 68 | }); 69 | 70 | describe('chain', () => { 71 | it('should satisfy associativity', () => { 72 | return Chain.associativity(List.of, assertSame, {}) 73 | }); 74 | 75 | it('should be covered', () => { 76 | return List.of()[fl.chain](List.of) 77 | }); 78 | }); 79 | 80 | describe('semigroup', () => { 81 | it('should satisfy associativity', () => { 82 | return Semigroup.associativity(List.of, assertSame, {}) 83 | }); 84 | 85 | it('should be covered', () => { 86 | return List.of()[fl.concat](List.of()) 87 | }); 88 | }); 89 | 90 | describe('monoid', () => { 91 | it('should satisfy rightIdentity', () => { 92 | return Monoid.rightIdentity(List, assertSame, {}) 93 | }); 94 | 95 | it('should satisfy leftIdentity', () => { 96 | return Monoid.leftIdentity(List, assertSame, {}) 97 | }); 98 | 99 | it('should be covered', () => { 100 | var emt = List[fl.empty]; 101 | return emt().concat(List.of()) 102 | }); 103 | }) 104 | }); -------------------------------------------------------------------------------- /perf/concatPerf.js: -------------------------------------------------------------------------------- 1 | // appending two lists performance - 100000 2 | // ------------------------------------------------------- 3 | // immutable-js 39.05 op/s ± 1.43% (51 samples) 4 | // seamless-immutable 13.68 op/s ± 3.54% (38 samples) 5 | // native concat 1099.11 op/s ± 7.06% (72 samples) 6 | // native push 228.72 op/s ± 6.64% (75 samples) 7 | // mori 18741394.72 op/s ± 1.01% (90 samples) 8 | // v1 199971.94 op/s ± 0.90% (91 samples) 9 | // v2(focus) 35464.07 op/s ± 2.80% (88 samples) 10 | // ------------------------------------------------------- 11 | 12 | 13 | var Benchmark = require('benchmark'); 14 | var Imm = require('immutable'); 15 | var seamless = require('seamless-immutable'); 16 | var mori = require('mori'); 17 | var iam = require('immutable-array-methods'); 18 | var List = require('../lib') 19 | var v2 = require('../lib/v2') 20 | 21 | 22 | var SML = times(64); 23 | var MED = times(1024); 24 | var LRG = times(100000); 25 | // var LRG = times(64); 26 | 27 | function times(size) { 28 | return new Array(size).map(function(v,i) { return i; }) 29 | } 30 | function range(size) { 31 | var vec = v2.empty(); 32 | 33 | for (var i = 0; size > i; i++) { 34 | vec = v2.append(i, vec); 35 | } 36 | return vec; 37 | } 38 | var BASE = { 39 | 'immutable': Imm.fromJS(LRG), 40 | 'seamless': seamless.from(LRG), 41 | 'mori': mori.toClj(LRG), 42 | 'rrb': List.from(LRG), 43 | 'v2': range(LRG.length) 44 | 45 | } 46 | 47 | function padl(n, s) { 48 | while(s.length < n) { 49 | s += ' '; 50 | } 51 | return s; 52 | } 53 | function padr(n, s) { while (s.length < n) { s = ' ' + s; } return s;} 54 | 55 | var suite = Benchmark.Suite('appending two lists performance'); 56 | 57 | 58 | 59 | 60 | suite 61 | .add("immutable-js", function() { 62 | var list = BASE.immutable.concat(BASE.immutable) 63 | }) 64 | .add('seamless-immutable', function() { 65 | var list = BASE.seamless.concat(BASE.seamless) 66 | }) 67 | .add('native concat', function() { 68 | var list = LRG.concat(LRG); 69 | }) 70 | .add('native push', function() { 71 | var list = LRG.slice(0); 72 | for (var i = 0, len = LRG.length; len > i; i++) { 73 | list.push(LRG[i]) 74 | } 75 | }) 76 | .add('mori', function() { 77 | var list = mori.concat(BASE.mori, BASE.mori) 78 | }) 79 | .add('v1', function() { 80 | var list = List.append(BASE.rrb, BASE.rrb); 81 | }) 82 | .add('v2', function() { 83 | var list = v2.appendAll(BASE.v2, BASE.v2); 84 | }); 85 | 86 | 87 | function runSuite(suite) { 88 | return suite 89 | .on('start', function() { 90 | console.log(this.name); 91 | console.log('-------------------------------------------------------'); 92 | }) 93 | .on('cycle', function logResults(e) { 94 | var t = e.target; 95 | 96 | 97 | if(t.failure) { 98 | console.error(padl(10, t.name) + 'FAILED: ' + e.target.failure); 99 | } else { 100 | var result = padl(18, t.name) 101 | + padr(13, t.hz.toFixed(2) + ' op/s') 102 | + ' \xb1' + padr(7, t.stats.rme.toFixed(2) + '%') 103 | + padr(15, ' (' + t.stats.sample.length + ' samples)'); 104 | 105 | console.log(result); 106 | } 107 | }) 108 | .on('complete', function() { 109 | console.log('-------------------------------------------------------'); 110 | }) 111 | .run(); 112 | } 113 | 114 | runSuite(suite); 115 | -------------------------------------------------------------------------------- /perf/blockVsArray.js: -------------------------------------------------------------------------------- 1 | var Benchmark = require('benchmark'); 2 | var suite = Benchmark.Suite('push items to list performance'); 3 | require('babel-polyfill'); 4 | var runSuite = require('./runSuite'); 5 | var Block = require('../lib/block'); 6 | var LinkedList = require('../src/experimental/LinkedList'); 7 | 8 | var Block4 = Block.Block4; 9 | var Block8 = Block.Block8; 10 | var Block16 = Block.Block16; 11 | 12 | 13 | function randomBetween(min, max) { 14 | return Math.floor(Math.random() * (max - min + 1) + min); 15 | } 16 | 17 | function rand(max) { 18 | return randomBetween(0, max) 19 | } 20 | 21 | 22 | var LIST4 = Block4.times(i => i, 32); 23 | var LIST8 = Block8.times(i => i, 32); 24 | var LIST16 = Block16.times(i => i, 32); 25 | var ARRAY32 = Array.apply(0, Array(32)).map((_, i) => i) 26 | /** 27 | * test array of 32, as that's what our i 28 | */ 29 | 30 | // current results: 31 | // Array: push 32 164318.83 op/s ± 2.17% (81 samples) 32 | // Block16: push 32 212407.96 op/s ± 1.99% (82 samples) 33 | // LinkedList: push 32 1633893.77 op/s ± 1.27% (84 samples) 34 | // Array: mutable push 32 6082137.68 op/s ± 1.35% (83 samples) 35 | 36 | 37 | suite 38 | // .add('Array: update 32', function() { 39 | // var ll = ARRAY32.slice(); 40 | // var n = rand(32); 41 | // ll.splice(n, 1, n); 42 | // }) 43 | // .add('Block4: update ', function() { 44 | // var n = rand(32); 45 | // var ll = LIST4.update(n, n) 46 | // }) 47 | // .add('Block8: update ', function() { 48 | // var n = rand(32); 49 | // var ll = LIST8.update(n, n) 50 | // }) 51 | // .add('Block16: update ', function() { 52 | // var n = rand(32); 53 | // var ll = LIST16.update(n, n) 54 | // }) 55 | .add('Array: push 32', function() { 56 | var list = []; 57 | var i = 32; 58 | while (i--) { 59 | list = list.slice(); 60 | list.push(i) 61 | } 62 | }) 63 | // .add('Array: push 2x16', function() { 64 | // // check if two list of 16 are faster 65 | // var list = []; 66 | // var i = 16; 67 | // while (i--) { 68 | // list = list.slice(); 69 | // list.push(i) 70 | // } 71 | // i = 16; 72 | // while (i--) { 73 | // list = list.slice(); 74 | // list.push(i) 75 | // } 76 | // }) 77 | // .add('Block4: push 32', function() { 78 | // var list = Block4.empty(); 79 | // var i = 32; 80 | // while (i--) 81 | // list = list.append(i); 82 | // }) 83 | // .add('Block8: push 32', function() { 84 | // var list = Block8.empty(); 85 | // var i = 32; 86 | // while (i--) { 87 | // list = list.append(i); 88 | // } 89 | // }) 90 | .add('Block16: push 32', function() { 91 | var list = Block16.empty(); 92 | var i = 16; 93 | while (i--) 94 | list = list.append(i); 95 | }) 96 | .add('LinkedList: push 32', function() { 97 | var list = LinkedList.one(32) 98 | var i = 31; 99 | while (i--) 100 | list = LinkedList.add(i, list); 101 | 102 | list = LinkedList.toArray(list); 103 | }) 104 | 105 | /* to know fastest possible */ 106 | .add('Array: mutable push 32', function() { 107 | var list = []; 108 | var i = 32; 109 | while (i--) { 110 | list.push(i) 111 | } 112 | }) 113 | 114 | 115 | // .add('Array: get 32', function() { 116 | // var x = ARRAY32[rand(32)] 117 | // }) 118 | // .add('Block4: get 32', function() { 119 | // var x = LIST4.nth(rand(32)) 120 | // }) 121 | // .add('Block8: get 32', function() { 122 | // var x = LIST8.nth(rand(32)) 123 | // }) 124 | // .add('Block16: get 32', function() { 125 | // var x = LIST16.nth(rand(32)) 126 | // }) 127 | 128 | ; 129 | 130 | runSuite(suite); 131 | -------------------------------------------------------------------------------- /src/constructors.js: -------------------------------------------------------------------------------- 1 | import {Node,EMPTY, isNode} from './Node'; 2 | import {slice, identity} from './functional'; 3 | import {M} from './constants'; 4 | import {length} from './accessors' 5 | 6 | function _leaf(items) { 7 | return new Node(0, items, void 0); 8 | } 9 | 10 | /** 11 | * create an rrb vector from a js array 12 | * 13 | * @param {Array} jsArray 14 | */ 15 | function fromArray(jsArray) { 16 | var len = jsArray.length; 17 | if (len === 0) 18 | return EMPTY; 19 | 20 | return _fromArray(jsArray, Math.floor(Math.log(len) / Math.log(M)), 0, len); 21 | 22 | function _fromArray(jsArray, h, from, to) { 23 | if (h === 0) { 24 | return new Node(0, slice(from, to, jsArray), void 0); 25 | } 26 | 27 | var step = Math.pow(M, h); 28 | var len = Math.ceil((to - from) / step); 29 | var table = new Array(len); 30 | var lengths = new Array(len); 31 | for (var i = 0; len > i; i++) { 32 | //todo: trampoline? 33 | table[i] = _fromArray(jsArray, h - 1, from + (i * step), Math.min(from + ((i + 1) * step), to)); 34 | lengths[i] = length(table[i]) + (i > 0 ? lengths[i - 1] : 0); 35 | } 36 | return new Node(h, table, lengths); 37 | } 38 | 39 | } 40 | 41 | // all empty nodes are equal 42 | export function empty() { 43 | return EMPTY; 44 | } 45 | 46 | // perf shortcut for an array of one 47 | export function one(item) { 48 | return _leaf([ item ]); 49 | } 50 | 51 | /** 52 | * the default list constructor 53 | * accepts an single native array, varargs, or nothing(if an empty list is desired) 54 | * 55 | */ 56 | export function from(iterable, mapFn) { 57 | var list = EMPTY; 58 | 59 | if (isNode(iterable)) { 60 | return iterable; 61 | } 62 | 63 | // use more performant, pre-allocation technique when possible 64 | if (Array.isArray(iterable)) { 65 | return !mapFn ? fromArray(iterable) : times((i) => mapFn(iterable[i], i), iterable.length); 66 | } 67 | 68 | // if length is unknown, just use push 69 | if (mapFn) { 70 | for (var item of iterable) { 71 | list = list.push(mapFn(item)); 72 | } 73 | } else { 74 | for (var item of iterable) { 75 | list = list.push(item); 76 | } 77 | } 78 | 79 | return list; 80 | } 81 | 82 | export function of(first, ...rest) { 83 | 84 | if (typeof first === 'undefined') 85 | return EMPTY; 86 | 87 | if (rest && rest.length > 0) 88 | return fromArray([first].concat(rest)); 89 | 90 | return one(first); 91 | } 92 | 93 | 94 | /** 95 | * populate an array using provided function 96 | * 97 | * @param len 98 | * @param {function(number)} fn 99 | * @return {Node} 100 | */ 101 | export function times(fn, len) { 102 | if (len <= 0) 103 | return empty(); 104 | 105 | // just iterating over push() isn't terribly fast... 106 | // we attempt to optimize here by pre-allocating 107 | 108 | var height = Math.floor( Math.log(len) / Math.log(M) ); 109 | return populate(fn, height, 0, len); 110 | 111 | function populate(func, h, from, to) { 112 | 113 | if (h === 0) { //leaf node 114 | return populateLeaf(func, from, to); 115 | } 116 | 117 | // populate container node 118 | var step = Math.pow(M, h); 119 | var len = Math.ceil((to - from) / step); 120 | var table = new Array(len); 121 | var lengths = new Array(len); 122 | for (var i = 0; len > i; i++) { 123 | // todo: trampoline? 124 | table[i] = populate(func, h - 1, from + (i * step), Math.min(from + ((i + 1) * step), to)); 125 | lengths[i] = length(table[i]) + (i > 0 ? lengths[i-1] : 0); 126 | } 127 | return new Node(h, table, lengths); 128 | } 129 | 130 | function populateLeaf(fn, from, to) { 131 | var len = (to - from) % (M + 1); 132 | var table = new Array(len); 133 | for (var i = 0; len > i; i++) { 134 | table[i] = fn(from + i); 135 | } 136 | return _leaf(table); 137 | } 138 | } 139 | 140 | export function range(from, to) { 141 | var len = to - from; 142 | return times((i) => from + i, len); 143 | } -------------------------------------------------------------------------------- /dist/rrbit.js: -------------------------------------------------------------------------------- 1 | (function(global,factory){if(typeof define==="function"&&define.amd){define(["exports","./accessors","./constructors","./Node","./iterator","./operations","fantasy-land","./_append"],factory)}else if(typeof exports!=="undefined"){factory(exports,require("./accessors"),require("./constructors"),require("./Node"),require("./iterator"),require("./operations"),require("fantasy-land"),require("./_append"))}else{var mod={exports:{}};factory(mod.exports,global.accessors,global.constructors,global.Node,global.iterator,global.operations,global.fantasyLand,global._append);global.index=mod.exports}})(this,function(exports,_accessors,_constructors,_Node,_iterator,_operations,_fantasyLand,_append2){"use strict";Object.defineProperty(exports,"__esModule",{value:true});var _iterator2=_interopRequireDefault(_iterator);var ops=_interopRequireWildcard(_operations);var _fantasyLand2=_interopRequireDefault(_fantasyLand);function _interopRequireWildcard(obj){if(obj&&obj.__esModule){return obj}else{var newObj={};if(obj!=null){for(var key in obj){if(Object.prototype.hasOwnProperty.call(obj,key))newObj[key]=obj[key]}}newObj.default=obj;return newObj}}function _interopRequireDefault(obj){return obj&&obj.__esModule?obj:{default:obj}}var push=ops.push,append=ops.append,get=ops.get,set=ops.set,map=ops.map,slice=ops.slice,filter=ops.filter,foldr=ops.foldr,foldl=ops.foldl,insertAt=ops.insertAt,removeAt=ops.removeAt,removeItem=ops.removeItem,indexOf=ops.indexOf,reverse=ops.reverse;var $$iter=Symbol.iterator||"@@iterator";var proto=_Node.Node.prototype;_Node.Node.of=_constructors.of;_Node.Node.from=_constructors.from;_Node.Node.empty=_constructors.empty;_Node.Node.times=_constructors.times;_Node.Node.lengthOf=_accessors.length;_Node.Node.push=push;_Node.Node.append=append;_Node.Node._append=_append2.append;_Node.Node.get=get;_Node.Node.set=set;_Node.Node.map=map;_Node.Node.slice=slice;_Node.Node.filter=filter;_Node.Node.foldr=foldr;_Node.Node.foldl=foldl;_Node.Node.reverse=reverse;_Node.Node.removeAt=removeAt;_Node.Node.removeItem=removeItem;_Node.Node.indexOf=indexOf;proto[$$iter]=function(){return(0,_iterator2.default)(this)};proto.values=function(){return(0,_iterator2.default)(this)};proto.valuesInReverse=function(){return(0,_iterator.tailIterator)(this)};proto.isEmpty=function(){return false};proto.size=function(){return(0,_accessors.length)(this)};proto.append=function(list){return append(this,list)};proto.prepend=function(list){return append(list,this)};proto.push=function(item){return push(item,this)};proto.get=function(i){return get(i,this)};proto.set=function(i,item){return set(i,item,this)};proto.foldl=function(fn,acc){return foldl(fn,acc,this)};proto.foldr=function(fn,acc){return foldr(fn,acc,this)};proto.slice=function(){var from=arguments.length>0&&arguments[0]!==undefined?arguments[0]:0;var to=arguments[1];return slice(from,to||this.length,this)};proto.head=function(){return this.get(0)};proto.tail=function(){return this.slice(1)};proto.filter=function(fn){return filter(fn,this)};proto.reverse=function(){return reverse(this)};proto.toArray=function(){return foldl(addTo,[],this)};function addTo(value,array){array.push(value);return array}proto.of=function(item){return(0,_constructors.one)(item)};proto.chain=proto.concatMap=function(f){return foldr(function(value,acc){return _concat(f(value),acc)},_Node.EMPTY,this)};proto.ap=function(values){return this.map(function(fn){return values.map(fn)})};proto.map=function(fn){return map(fn,this)};proto.empty=_constructors.empty;function _concat(thing,list){if((0,_Node.isListNode)(thing))return append(list,thing);return push(thing,list)}proto.concat=function(value){return _concat(value,this)};proto[_fantasyLand2.default.of]=_constructors.of;_Node.Node[_fantasyLand2.default.of]=_constructors.of;proto[_fantasyLand2.default.empty]=_constructors.empty;_Node.Node[_fantasyLand2.default.empty]=_constructors.empty;proto[_fantasyLand2.default.ap]=proto.ap;_Node.Node[_fantasyLand2.default.ap]=proto.ap;proto[_fantasyLand2.default.map]=proto.map;_Node.Node[_fantasyLand2.default.map]=proto.map;proto[_fantasyLand2.default.chain]=proto.chain;_Node.Node[_fantasyLand2.default.chain]=proto.chain;proto[_fantasyLand2.default.concat]=proto.concat;_Node.Node[_fantasyLand2.default.concat]=proto.concat;Object.freeze(_Node.EMPTY);exports.default=_Node.Node}); 2 | -------------------------------------------------------------------------------- /test/v2.test.js: -------------------------------------------------------------------------------- 1 | import {empty, one} from '../src/v2/_constructors'; 2 | import {append} from '../src/v2/append'; 3 | import {prepend} from '../src/v2/prepend'; 4 | import nth from '../src/v2/nth'; 5 | import concat from '../src/v2/appendAll'; 6 | import {expect} from 'chai'; 7 | 8 | var DEPTHS = [ 9 | 32, // 0 depth (leaf only) 10 | 1024, // 1 depth (default min depth) 11 | 32768, // 2 depth 12 | 1048576, // 3 depth (1M) 13 | 33554432, // 4 depth (33.5M) 14 | 1073741824 // 5 depth (1B) usually will cause out-of-memory by this point in current JS engines 15 | ] 16 | 17 | function chunk(str, size) { 18 | var len = str.length, 19 | chunks = [] 20 | for (var i = 0, len = str.length; len > i; i += size) { 21 | chunks.push(str.substring(i, i + size)); 22 | } 23 | return chunks; 24 | } 25 | 26 | function pretty(number) { 27 | return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); 28 | } 29 | 30 | function range(from, to) { 31 | var len = to - from; 32 | var vec = empty(); 33 | 34 | for (var i = 0; len > i; i++) { 35 | vec = append(i + from, vec); 36 | } 37 | 38 | return vec; 39 | } 40 | 41 | describe("rrb with focus tests", function() { 42 | 43 | describe.skip("basic construction tests", function() { 44 | var none; 45 | var uno; 46 | 47 | it('constructor tests', function() { 48 | none = empty(); 49 | uno = one(10); 50 | }); 51 | 52 | function testSize(MAX, timeout) { 53 | it(`append ${pretty(MAX)} test`, function() { 54 | this.timeout(timeout || 2000) 55 | var vec = empty(); 56 | for (var i = 0; MAX > i; i++) { 57 | vec = append(i, vec); 58 | } 59 | }); 60 | } 61 | 62 | for (var MAX of [32, 1024, 32768, 1048576]) { 63 | testSize(MAX, 1000) 64 | } 65 | 66 | // testSize(33554432, 4000); 67 | // testSize(1073741824, 4000); 68 | 69 | it.skip('append 1,000,000 native test', function() { 70 | var vec = []; 71 | for (var i = 0; 1000000 > i; i++) { 72 | vec.push(i); 73 | } 74 | }); 75 | }); 76 | 77 | describe.skip('prepend tests', function() { 78 | var pvec = empty(); 79 | // var MAX = 10000; 80 | var MAX = 100; 81 | 82 | it(`prepend ${pretty(MAX)} test`, function() { 83 | //* 84 | for (var i = 0; MAX > i; i++) { 85 | pvec = prepend(i, pvec); 86 | } 87 | /*/ 88 | try { 89 | for (var i = 0; MAX > i; i++) { 90 | pvec = prepend(i, pvec); 91 | } 92 | } catch (e) { 93 | throw new Error(JSON.stringify(pvec)) 94 | } 95 | //*/ 96 | 97 | }); 98 | 99 | it(`prepend ${pretty(MAX)} ordering test`, function() { 100 | for (var i = MAX; i--;) { 101 | expect(nth(i, pvec, 'missing')).to.equal(i); 102 | } 103 | }); 104 | 105 | it.skip('prepend 1,000,000 native test', function() { 106 | this.timeout(5000) 107 | // var MAX = 1000000;// stop-the-world sec 108 | var MAX = 500000; // 2.8 sec 109 | // var MAX = 100000; // 2.8 sec 110 | // var MAX = 10000; // 13 msec 111 | // var MAX = 1000; // 0.13 sec 112 | var vec = []; 113 | for (var i = 0; MAX > i; i++) { 114 | vec.unshift(i); 115 | } 116 | }); 117 | }) 118 | 119 | describe.skip('ordered get/set confirmation', function() { 120 | 121 | it('retrieves 10000 items in same order as inserted', function() { 122 | var MAX = 10000 123 | var vec = empty(); 124 | 125 | for (var i = 0; MAX > i; i++) { 126 | vec = append(i, vec); 127 | } 128 | 129 | for (var i = 0; MAX > i; i++) { 130 | expect(nth(i, vec)).to.equal(i); 131 | } 132 | expect(vec.endIndex).to.equal(MAX) 133 | }) 134 | }) 135 | 136 | describe('concat tests', function() { 137 | 138 | function testConcatWithLength(size, tOut = 2000) { 139 | it(`joins two lists of ${size} together`, function() { 140 | this.timeout(tOut) 141 | var vec = empty(); 142 | 143 | for (var i = 0; size > i; i++) { 144 | vec = append(i, vec); 145 | } 146 | 147 | var joined = concat(vec, vec); 148 | expect(joined.endIndex).to.equal(size * 2) 149 | 150 | for (var i = 0; size > i; i++) { 151 | expect(nth(i, joined)).to.equal(i); 152 | expect(nth(i + size, joined)).to.equal(i); 153 | } 154 | }) 155 | } 156 | 157 | testConcatWithLength(32); 158 | testConcatWithLength(DEPTHS[1]); 159 | testConcatWithLength(DEPTHS[2]); 160 | testConcatWithLength(DEPTHS[3], 5000); 161 | 162 | 163 | }) 164 | 165 | }) -------------------------------------------------------------------------------- /perf/pushPerf.js: -------------------------------------------------------------------------------- 1 | // push items to list performance - 1k 2 | // ------------------------------------------------------- 3 | // native push(mutable) 210587.92 op/s ± 1.71% (88 samples) 4 | // native slice + push 1057.54 op/s ± 1.07% (89 samples) 5 | // immutable-js 1791.59 op/s ± 1.23% (89 samples) 6 | // immutable-array-methods 1697.13 op/s ± 2.07% (88 samples) 7 | // mori 29024.12 op/s ± 1.39% (86 samples) 8 | // v1 rrbit 382.12 op/s ± 1.34% (85 samples) 9 | // v2 rrbit(builder mode) 30265.77 op/s ± 1.13% (87 samples) 10 | // v2 rrbit 3990.70 op/s ± 1.57% (87 samples) 11 | // v3 rrbit 22771.10 op/s ± 1.41% (91 samples) 12 | // v3 rrb(builder mode) 162258.14 op/s ± 1.46% (88 samples) 13 | // ------------------------------------------------------- 14 | 15 | 16 | // push items to list performance - 10k 17 | // ------------------------------------------------------- 18 | // immutable-js 192.50 op/s ± 1.35% (78 samples) 19 | // imm methods 24.71 op/s ± 1.45% (44 samples) 20 | // native 18556.40 op/s ± 1.48% (86 samples) 21 | // native slice()+push() 10.94 op/s ± 1.32% (31 samples) 22 | // mori 2551.32 op/s ± 1.12% (83 samples) 23 | // List 34.67 op/s ± 2.07% (59 samples) 24 | // focusable(fast) 1049.71 op/s ± 1.13% (88 samples) 25 | // focusable 373.49 op/s ± 1.35% (84 samples) 26 | // _rrb 151.19 op/s ± 1.08% (75 samples) 27 | // ------------------------------------------------------- 28 | 29 | 30 | 31 | // push items to list performance - 100k 32 | // ------------------------------------------------------- 33 | // immutable-js 16.09 op/s ± 2.27% (43 samples) 34 | // imm methods 0.08 op/s ± 9.86% (5 samples) 35 | // native 1003.87 op/s ± 2.89% (76 samples) 36 | // native slice()+push() 0.02 op/s ± 5.09% (5 samples) 37 | // mori 236.24 op/s ± 1.64% (78 samples) 38 | // List 3.16 op/s ± 3.30% (12 samples) 39 | // focusable(fast) 93.93 op/s ± 3.94% (68 samples) 40 | // focusable 30.17 op/s ± 3.88% (53 samples) 41 | // _rrb 10.86 op/s ± 2.82% (31 samples) 42 | // ------------------------------------------------------- 43 | 44 | 45 | var Benchmark = require('benchmark'); 46 | var Imm = require('immutable'); 47 | var seamless = require('seamless-immutable'); 48 | var mori = require('mori'); 49 | var iam = require('immutable-array-methods'); 50 | var List = require('../lib'); 51 | var runSuite = require('./runSuite'); 52 | var rrb = require('../src/scrap/rrb'); 53 | var v2 = require('../lib/v2') 54 | var v3 = require('../lib/v3') 55 | 56 | 57 | 58 | 59 | var SIZE = 1000; 60 | 61 | var suite = Benchmark.Suite('push items to list performance'); 62 | 63 | 64 | 65 | suite 66 | .add('native push(mutable) ', function() { 67 | var list = []; 68 | for (var i = 0; SIZE > i; i++) { 69 | list.push(i); 70 | } 71 | }) 72 | .add('native slice + push ', function() { 73 | var list = []; 74 | for (var i = 0; SIZE > i; i++) { 75 | list = list.slice(0); 76 | list.push(i); 77 | } 78 | }) 79 | .add("immutable-js", function() { 80 | var list = Imm.List(); 81 | for (var i = 0; SIZE > i; i++) { 82 | list = list.push(i); 83 | } 84 | }) 85 | // too slow to measure 86 | // .add('seamless-immutable', function() { 87 | // var list = seamless.from([1]); 88 | // 89 | // for (var i = 0; SIZE > i; i++) { 90 | // list = list.concat(1); 91 | // } 92 | // }) 93 | .add('immutable-array-methods', function() { 94 | var list = []; 95 | for (var i = 0; SIZE > i; i++) { 96 | list = iam.push(list, i) 97 | } 98 | }) 99 | .add('mori', function() { 100 | var list = mori.list(); 101 | for (var i = 0; SIZE > i; i++) { 102 | list = mori.conj(list, i); 103 | } 104 | }) 105 | .add('v1 rrbit', function() { 106 | var list = List.empty(); 107 | for (var i = 0; SIZE > i; i++) { 108 | list = List.push(i, list) 109 | } 110 | }) 111 | .add('v2 rrbit(builder mode)', function() { 112 | //expected builder performance 113 | var list = v2.empty(); 114 | for (var i = 0; SIZE > i; i++) { 115 | list = v2.appendǃ(i, list) 116 | } 117 | }) 118 | .add('v2 rrbit', function() { 119 | var list = v2.empty(); 120 | for (var i = 0; SIZE > i; i++) { 121 | list = v2.append(i, list) 122 | } 123 | }) 124 | .add('v3 rrbit', function() { 125 | var list = v3.empty(); 126 | for (var i = 0; SIZE > i; i++) { 127 | list = v3.append(i, list) 128 | } 129 | }) 130 | .add('v3 rrb(builder mode)', function() { 131 | var list = v3.empty(); 132 | for (var i = 0; SIZE > i; i++) { 133 | list = v3.appendǃ(i, list) 134 | } 135 | }) 136 | // .add('List range', function() { 137 | // var list = List.range(0, SIZE); 138 | // }) 139 | // .add('List + native', function() { 140 | // var list = []; 141 | // for (var i = 0; SIZE > i; i++) { 142 | // list.push(i, list) 143 | // } 144 | // List.from(list) 145 | // }) 146 | // .add('List Array() prealloc', function() { 147 | // var list = List.from(new Array(SIZE)); 148 | // }) 149 | // .add('_rrb', function() { 150 | // var list = rrb.empty(); 151 | // for (var i = 0; SIZE > i; i++) { 152 | // list = rrb.push(i, list) 153 | // } 154 | // }); 155 | 156 | 157 | 158 | runSuite(suite); 159 | -------------------------------------------------------------------------------- /src/experimental/Block.js: -------------------------------------------------------------------------------- 1 | /** 2 | * for lengths less than 32, we fall back to native js arrays as they seem faster 3 | * however, we might be able to find a graph type with efficient persistent updates 4 | * in this size range 5 | * 6 | * if 32 is is divisible by 4 and 8, these are the most logical branching sizes 7 | * 8 | * goals: 9 | * - optimize for 32 wide arrays or less 10 | * - optimize for fast append 11 | * - optimize for fast prepend? 12 | * - optimize for fast slice 13 | * - optimize for fast concat 14 | * 15 | */ 16 | 17 | // in order to get this to optimize in v8, we need to always pass an array 18 | function setǃ(name, value, obj) { 19 | obj[name] = value; 20 | return obj; 21 | } 22 | 23 | 24 | function BlockFactory(LEAFWIDTH) { 25 | if (LEAFWIDTH & (LEAFWIDTH - 1) !== 0) { 26 | throw new Error('width must be a power of 2'); 27 | } 28 | var ROOTWIDTH = 32 / LEAFWIDTH; 29 | var ROOTBIT = Math.log2(LEAFWIDTH); 30 | var LEAFBIT = LEAFWIDTH - 1; 31 | 32 | // bit operations explained: 33 | // bitshift divide. determine which root to plug our value in 34 | // i >> ROOTBIT 35 | // bitshift modulo, determine which slot in leaf minus the root offset to set value 36 | // i & LEAFBIT 37 | 38 | function emptyTree(length) { 39 | var len = length >> ROOTBIT; 40 | var tree = new Array(len) 41 | 42 | // use a switch for performance 43 | switch (len) { 44 | case 8: 45 | tree[7] = new Array(LEAFWIDTH); 46 | case 7: 47 | tree[6] = new Array(LEAFWIDTH); 48 | case 6: 49 | tree[5] = new Array(LEAFWIDTH); 50 | case 5: 51 | tree[4] = new Array(LEAFWIDTH); 52 | case 4: 53 | tree[3] = new Array(LEAFWIDTH); 54 | case 3: 55 | tree[2] = new Array(LEAFWIDTH); 56 | case 2: 57 | tree[1] = new Array(LEAFWIDTH); 58 | case 1: 59 | case 0: 60 | tree[0] = new Array(LEAFWIDTH); 61 | break; 62 | default: 63 | // technically, we should never reach here as we expect 32 max length 64 | // but might as well make the data structure complete... 65 | for (var i = 0; len > i; i++) { 66 | tree[i] = new Array(LEAFWIDTH); 67 | } 68 | } 69 | return tree; 70 | } 71 | 72 | function Block(length, tree) { 73 | 74 | this.length = length; 75 | this.tree = tree; 76 | } 77 | 78 | Block.of = function of(iterable) { 79 | var tree = new Array(ROOTWIDTH), 80 | i = 0; 81 | 82 | for (var item of iterable) { 83 | if (i & LEAFBIT === 0) 84 | tree.push(new Array(LEAFWIDTH)); 85 | 86 | tree[i >> ROOTBIT][i & LEAFBIT] = item; 87 | i++; 88 | } 89 | 90 | return new Block(i, tree); 91 | } 92 | 93 | Block.empty = function(len = 0) { 94 | return new Block(len, emptyTree(len)); 95 | } 96 | 97 | Block.one = function(value) { 98 | var tree = emptyTree(0) 99 | tree[0][0] = value; 100 | return new Block(0, tree); 101 | } 102 | 103 | Block.times = function times(fn, len) { 104 | var tree = emptyTree(len), 105 | leaf; 106 | 107 | var n = 0; 108 | for (var i = 0; len > i; i++) { 109 | n = i & LEAFBIT; 110 | 111 | if (n === 0) 112 | leaf = tree[i >> ROOTBIT]; 113 | 114 | leaf[n] = fn(i); 115 | } 116 | 117 | return new Block(len, tree); 118 | } 119 | 120 | Object.assign(Block.prototype, { 121 | nth(i) { 122 | return this.tree[i >> ROOTBIT][i & LEAFBIT]; 123 | }, 124 | 125 | appendAll(iterable) { 126 | var tree = this.tree.slice(0), 127 | i = 0; 128 | 129 | for (var item of iterable) { 130 | if (i & LEAFBIT === 0) 131 | tree.push(new Array(LEAFWIDTH)); 132 | 133 | tree[i >> ROOTBIT][i & LEAFBIT] = item; 134 | i++; 135 | } 136 | 137 | return new Block(i, tree); 138 | }, 139 | 140 | append(value) { 141 | var newTree = this.tree.slice(0); 142 | var len = this.length + 1; 143 | var n = len >> ROOTBIT; 144 | 145 | // if full, start a new array 146 | var leaf = (len & LEAFBIT === 0) ? 147 | [value] : setǃ(len & LEAFBIT, value, (newTree[n] || []).slice(0)); 148 | 149 | return new Block(len, setǃ(n, leaf, newTree)); 150 | }, 151 | 152 | prepend(value) { 153 | // we can optimize this by adding a startIndex/offset property 154 | var src = this.tree; 155 | var len = this.length + 1; 156 | var tree = emptyTree(len); 157 | 158 | for (var i = 1, n = 0; len > i; i++, n++) { 159 | // optimize current leaf; 160 | tree[i >> ROOTBIT][i & LEAFBIT] = src[n >> ROOTBIT][n & LEAFBIT]; 161 | } 162 | tree[0][0] = value; 163 | return new Block(len, tree); 164 | }, 165 | 166 | // immutable update 167 | update(i, value) { 168 | var newTree = this.tree.slice(0); 169 | var n = i >> ROOTBIT; 170 | var leaf = newTree[n] || [] 171 | return new Block(this.length, setǃ(n, setǃ(i & LEAFBIT, value, leaf.slice(0)), newTree)); 172 | }, 173 | 174 | // mutate in place update 175 | updateǃ(i, value) { 176 | this.tree[i >> ROOTBIT][i & LEAFBIT] = value; 177 | }, 178 | 179 | // since this graph is meant for performance, we're not going to validate inputs or handle negatives 180 | slice(start, end) { 181 | var len = end - start; 182 | if ((start & LEAFBIT) === 0) { 183 | // we can copy whole blocks 184 | } 185 | // random number, just copy everything 186 | var src = this.tree; 187 | var tree = emptyTree(len); 188 | for (var i = 0, n = start; len > i; i++, n++) { 189 | //todo: optimize for current leaf 190 | tree[i >> ROOTBIT][i & LEAFBIT] = src[n >> ROOTBIT][n & LEAFBIT]; 191 | } 192 | return new Block8(len, tree); 193 | }, 194 | 195 | reduceKV(fn, init) { 196 | var tree = this.tree, 197 | leaf; 198 | 199 | var n = 0; 200 | for (var i = 0, l = this.length; l > i; i++) { 201 | n = i & LEAFBIT; 202 | if (n === 0) 203 | leaf = tree[i >> ROOTBIT]; 204 | 205 | init = fn(leaf[n], i, init); 206 | } 207 | 208 | return init 209 | } 210 | }); 211 | 212 | return Block; 213 | 214 | } 215 | 216 | const Block4 = BlockFactory(4); 217 | const Block8 = BlockFactory(8); 218 | const Block16 = BlockFactory(16); 219 | 220 | export { 221 | Block16, 222 | Block8, 223 | Block4, 224 | BlockFactory 225 | } -------------------------------------------------------------------------------- /src/internal.js: -------------------------------------------------------------------------------- 1 | import {isLeaf, tableOf, tableLenOf, heightOf, lengthsOf, length} from './accessors'; 2 | import {Node} from './Node'; 3 | import {last, setLast, copy, first} from './functional'; 4 | import {M, E} from './constants' 5 | /** 6 | * private util operations rrb lists use 7 | * 8 | * 9 | * 10 | * 11 | * 12 | */ 13 | 14 | // Recursively creates a tree with a given height containing 15 | // only the given item. 16 | export function createNodeWithHeight(item, height) { 17 | if (height === 0) { 18 | return new Node(0, [item], void 0); 19 | } 20 | return new Node(height, [createNodeWithHeight(item, height - 1)], [1]); 21 | } 22 | 23 | 24 | export function sliceLeft(from, list) { 25 | if (from === 0) 26 | return list; 27 | 28 | const listTable = tableOf(list); 29 | // Handle leaf level. 30 | if (isLeaf(list)) 31 | return new Node(0, listTable.slice(from, listTable.length + 1), void 0); 32 | 33 | // Slice the left recursively. 34 | var left = findSlot(from, list); 35 | var sliced = sliceLeft(from - (left > 0 ? lengthsOf(list)[left - 1] : 0), listTable[left]); 36 | 37 | // Maybe the a node is not even needed, as sliced contains the whole slice. 38 | if (left === listTable.length - 1) { 39 | return sliced; 40 | } 41 | 42 | // Create new node. 43 | var tbl = listTable.slice(left, listTable.length + 1); 44 | tbl[0] = sliced; 45 | var lengths = new Array(listTable.length - left); 46 | var len = 0; 47 | for (var i = 0; i < tbl.length; i++) { 48 | len += length(tbl[i]); 49 | lengths[i] = len; 50 | } 51 | 52 | return new Node(heightOf(list), tbl, lengths); 53 | } 54 | 55 | export function sliceRight(to, list) { 56 | if (to === length(list)) 57 | return list; 58 | 59 | const listTable = tableOf(list); 60 | // Handle leaf level. 61 | if (isLeaf(list)) 62 | return new Node(0, listTable.slice(0, to), void 0); 63 | 64 | // Slice the right recursively. 65 | var right = findSlot(to, list); 66 | var sliced = sliceRight(to - (right > 0 ? lengthsOf(list)[right - 1] : 0), listTable[right]); 67 | 68 | // Maybe the a node is not even needed, as sliced contains the whole slice. 69 | if (right === 0) 70 | return sliced; 71 | 72 | // Create new node. 73 | var lengths = lengthsOf(list).slice(0, right); 74 | var tbl = listTable.slice(0, right); 75 | if (tableOf(sliced).length > 0) { 76 | tbl[right] = sliced; 77 | lengths[right] = length(sliced) + (right > 0 ? lengths[right - 1] : 0); 78 | } 79 | return new Node(heightOf(list), tbl, lengths); 80 | } 81 | 82 | // Calculates in which slot of "table" the item probably is, then 83 | // find the exact slot via forward searching in "lengths". Returns the index. 84 | function findSlot(i, list) { 85 | var slot = i >> (5 * heightOf(list)); 86 | while (lengthsOf(list)[slot] <= i) { 87 | slot++; 88 | } 89 | return slot; 90 | } 91 | 92 | 93 | export function unsafeGet(i, list) { 94 | for (var x = heightOf(list); x > 0; x--) { 95 | var slot = i >> (x * 5); 96 | while (lengthsOf(list)[slot] <= i) { 97 | slot++; 98 | } 99 | if (slot > 0) { 100 | i -= lengthsOf(list)[slot - 1]; 101 | } 102 | list = tableOf(list)[slot]; 103 | } 104 | return tableOf(list)[i]; 105 | } 106 | 107 | export function unsafeSet(i, item, list) { 108 | list = nodeCopy(list); 109 | 110 | if (isLeaf(list)) { 111 | tableOf(list)[i] = item; 112 | } else { 113 | var slot = findSlot(i, list); 114 | if (slot > 0) { 115 | i -= lengthsOf(list)[slot - 1]; 116 | } 117 | tableOf(list)[slot] = unsafeSet(i, item, tableOf(list)[slot]); 118 | } 119 | return list; 120 | } 121 | 122 | 123 | 124 | 125 | 126 | // Navigation functions 127 | export function lastSlot(node) { 128 | return last(tableOf(node)); 129 | } 130 | 131 | export function firstSlot(node) { 132 | return first(tableOf(node)); 133 | } 134 | 135 | 136 | 137 | // Copies a node for updating. Note that you should not use this if 138 | // only updating only one of "table" or "lengths" for performance reasons. 139 | export function nodeCopy(a) { 140 | return new Node(heightOf(a), copy(tableOf(a)), (isLeaf(a) ? void 0: copy(lengthsOf(a))) ); 141 | } 142 | 143 | // Recursively creates a tree that contains the given tree. 144 | export function parentise(tree, height) { 145 | return height === heightOf(tree) ? tree : new Node(height, [parentise(tree, height - 1)], [length(tree)]); 146 | } 147 | 148 | // Emphasizes blood brotherhood beneath two trees. 149 | export function siblise(a, b) { 150 | return new Node(a['@@rrb/height'] + 1, [a, b], [length(a), length(a) + length(b)]); 151 | } 152 | 153 | /** 154 | * Recursively tries to push an item to the bottom-right most 155 | * tree possible. If there is no space left for the item, 156 | * null will be returned. 157 | * @param {*} item 158 | * @param {Node} list 159 | * @return {Node|null} 160 | */ 161 | export function pushIfSpace(item, list) { 162 | // Handle recursion stop at leaf level. 163 | if (isLeaf(list)) { 164 | if (tableOf(list).length < M) { 165 | return new Node(0, tableOf(list).concat(item), void 0); 166 | } 167 | 168 | return null; 169 | } 170 | 171 | // Recursively push 172 | var pushed = pushIfSpace(item, lastSlot(list)); 173 | 174 | // There was space in the bottom right tree, so the slot will 175 | // be updated. 176 | if (pushed !== null) { 177 | var newA = nodeCopy(list); 178 | setLast(pushed, tableOf(newA)); 179 | setLast(last(lengthsOf(newA)) + 1, lengthsOf(newA)); 180 | return newA; 181 | } 182 | 183 | // When there was no space left, check if there is space left 184 | // for a new slot with a tree which contains only the item 185 | // at the bottom. 186 | if (tableLenOf(list) < M) { 187 | var newSlot = createNodeWithHeight(item, heightOf(list) - 1); 188 | 189 | return new Node(heightOf(list), 190 | tableOf(list).concat(newSlot), 191 | lengthsOf(list).concat(last(lengthsOf(list)) + length(newSlot)) 192 | ); 193 | } else { 194 | return null; 195 | } 196 | } 197 | 198 | 199 | export function push(item, list) { 200 | 201 | return pushIfSpace(item, list) || siblise(list, createNodeWithHeight(item, heightOf(list))); 202 | } 203 | 204 | 205 | 206 | -------------------------------------------------------------------------------- /src/operations.js: -------------------------------------------------------------------------------- 1 | 2 | import {curry} from './functional'; 3 | 4 | import {lengthsOf, heightOf, tableOf, isLeaf, length} from './accessors'; 5 | import {Node, EMPTY, isListNode} from './Node'; 6 | import { 7 | sliceLeft, 8 | sliceRight, 9 | unsafeGet, 10 | unsafeSet, 11 | siblise, 12 | pushIfSpace, 13 | createNodeWithHeight } from './internal'; 14 | 15 | import {append} from './append' 16 | 17 | // A RRB-Tree has two distinct data types. 18 | // Leaf -> "height" is always 0 19 | // "table" is an array of elements 20 | // Node -> "height" is always greater than 0 21 | // "table" is an array of child nodes 22 | // "lengths" is an array of accumulated lengths of the child nodes 23 | 24 | 25 | 26 | 27 | 28 | 29 | // == public api list operations ====================================================================================== 30 | 31 | export function push(item, list) { 32 | 33 | return pushIfSpace(item, list) || siblise(list, createNodeWithHeight(item, heightOf(list))); 34 | } 35 | 36 | export function reverse(list) { 37 | return foldr((item, newList) => 38 | push(item,newList), EMPTY, list); 39 | } 40 | 41 | 42 | /** 43 | * concat listB onto listA 44 | * 45 | * The paper describes an optimized version, but we'll cheat with this for now 46 | * 47 | * @param listA 48 | * @param listB 49 | * @return {*} 50 | */ 51 | // export function append(listA, listB) { 52 | // return foldl(push, listA, listB); 53 | // } 54 | 55 | export {append} 56 | 57 | export function prepend(listA, listB) { 58 | return append(listB, listA); 59 | } 60 | 61 | 62 | export function get(i, list) { 63 | if (i < 0 || i >= length(list)) { 64 | throw new Error('Index ' + i + ' is out of range'); 65 | } 66 | return unsafeGet(i, list); 67 | } 68 | 69 | 70 | export function set(i, item, list) { 71 | // if given index is negative, or greater than the length of list 72 | // be nice and don't throw an error 73 | // adding to the end of a list should always use push 74 | if (i < 0 || length(list) <= i) { 75 | return list; 76 | } 77 | return unsafeSet(i, item, list); 78 | } 79 | 80 | export function insertAt(i, item, list) { 81 | // since slice is fast in rrb, try to use it instead of just filter 82 | return append(push(sliceLeft(i, list), item), sliceRight(i, list)) 83 | 84 | } 85 | 86 | export function removeAt(i, list) { 87 | return append(sliceLeft(i -1, list), sliceRight(i, list)) 88 | } 89 | 90 | export function removeItem(item, list) { 91 | var i = indexOf(item); 92 | return i === -1 ? list : remove(i, list); 93 | } 94 | 95 | 96 | export const map = curry(function map(fn, list, from = 0) { 97 | const table = tableOf(list); 98 | const len = table.length; 99 | var tbl = new Array(len); 100 | 101 | // we're micro optimizing for the common use case here, foldr could replace this just fine 102 | // but since we're not changing the length, we can skip over some table reshuffling 103 | if (isLeaf(list)) { 104 | for (var i = 0; len > i; i++) { 105 | tbl[i] = fn(table[i], from + i); 106 | } 107 | } else { 108 | for (var i = 0; len > i; i++) { 109 | tbl[i] = map(fn, table[i], 110 | (i == 0 ? from : from + lengthsOf(list)[i - 1])) 111 | } 112 | } 113 | 114 | 115 | return new Node(heightOf(list), tbl, lengthsOf(list)); 116 | }); 117 | 118 | // todo: add a forEach method? 119 | // not really sure we need to add support for side effects since we have iterator support 120 | 121 | 122 | /** 123 | * fold left(reverse) 124 | * 125 | * @param {function(T, Z)} fn 126 | * @param {Z} accum 127 | * @param {Node} list 128 | * @return {*} 129 | */ 130 | export const foldl = curry(function foldl(fn, accum, list) { 131 | const table = tableOf(list); 132 | var len = table.length; 133 | if (isLeaf(list)) { 134 | for (var i = 0; len > i; i++) { 135 | accum = fn(table[i], accum); 136 | } 137 | } else { 138 | for (var i = 0; len > i; i++) { 139 | accum = foldl(fn, accum, table[i]); 140 | } 141 | } 142 | return accum; 143 | }); 144 | 145 | 146 | 147 | /** 148 | * fold right 149 | * a.k.a functional style "reduce" 150 | * 151 | * note: standard js reducing fns expect accum first, but this is iteratee first 152 | * 153 | * @param {function(T, Z)}fn 154 | * @param {Z} accum 155 | * @param {Node} list 156 | * @return {*} 157 | */ 158 | export const foldr = curry(function foldr(fn, accum, list) { 159 | const table = tableOf(list); 160 | var i = table.length; 161 | if (isLeaf(list)) { 162 | while (i--) { 163 | accum = fn(table[i], accum); 164 | } 165 | } else { 166 | while (i--) { 167 | accum = foldr(fn, accum, table[i]); 168 | } 169 | } 170 | return accum; 171 | }); 172 | 173 | 174 | 175 | 176 | /** 177 | * return a new list of items that pass test fn 178 | * 179 | * @param {function(T)} fn 180 | * @param {Node} list 181 | * @return {Node} 182 | */ 183 | export const filter = curry(function filter(fn, list) { 184 | return foldr((item, acc) => 185 | (fn(item) ? push(item, acc) : acc), EMPTY, list); 186 | }); 187 | 188 | 189 | 190 | /** 191 | * return a shallow copy of a portion of a list, with supplied "from" and "to"("to" not included) 192 | * 193 | * @param from 194 | * @param to 195 | * @param list 196 | */ 197 | export function slice(from, to, list) { 198 | 199 | var max = length(list); 200 | if (from >= max){ 201 | return empty; 202 | } 203 | if (to >= max - 1){ 204 | to = max; 205 | } 206 | //invert negative numbers 207 | function confine(i) { 208 | return i < 0 ? (i + max) : i; 209 | } 210 | 211 | if (isListNode(to)) { 212 | list = to; 213 | to = length(list); 214 | } 215 | 216 | return sliceLeft(confine(from), sliceRight(confine(to), list)); 217 | } 218 | 219 | function find(predicate, list) { 220 | for (var item of list) { 221 | if (predicate(item)) 222 | return item; 223 | } 224 | } 225 | 226 | export function indexOf(value, list) { 227 | const table = tableOf(list); 228 | var i = table.length; 229 | if (isLeaf(list)) { 230 | while (i--) { 231 | if (table[i] === value) 232 | return i; 233 | } 234 | } else { 235 | while (i--) { 236 | var subI = indexOf(value, table[i]); 237 | if (subI !== -1) 238 | return i + subI; 239 | } 240 | } 241 | return -1; 242 | } 243 | 244 | export function isMember(item, list) { 245 | return indexOf(item, list) !== -1; 246 | } 247 | 248 | export function intersperse(separator, list) { 249 | 250 | } 251 | 252 | 253 | 254 | 255 | 256 | -------------------------------------------------------------------------------- /test/collection.test.js: -------------------------------------------------------------------------------- 1 | import List from '../src/index'; 2 | import {expect} from 'chai'; 3 | 4 | describe("collection tests", function() { 5 | 6 | // a 1 indexed array 7 | var SIXTY_FOUR = [...(new Array(64)).keys()]; 8 | 9 | it("dummy value test", function() { 10 | expect(SIXTY_FOUR.length).to.equal(64); 11 | expect(SIXTY_FOUR[0]).to.equal(0); 12 | expect(SIXTY_FOUR[32]).to.equal(32); 13 | expect(SIXTY_FOUR[64]).to.equal(undefined); 14 | 15 | }); 16 | 17 | describe("can construct lists", function() { 18 | 19 | it("can construct empty lists", function() { 20 | expect(() => { 21 | var l = List.empty(); 22 | }).to.not.throw(); 23 | 24 | }); 25 | 26 | it("can construct list using of()", function() { 27 | expect(() => { 28 | // 29 | var list = List.of(1, 2, 3, 4, 5); 30 | 31 | 32 | }).to.not.throw(); 33 | }); 34 | 35 | it("can construct list from a native array", function() { 36 | expect(() => { 37 | // 38 | var list = List.of([1, 2, 3, 4, 5]); 39 | 40 | 41 | }).to.not.throw(); 42 | }); 43 | 44 | it("can construct large lists", function() { 45 | // List trees segment internally every 32 item 46 | // we need to test if it works the same when sgemented 47 | 48 | var list; 49 | expect(() => { 50 | list = List.from(SIXTY_FOUR); 51 | 52 | expect(list.toArray()).to.eql(SIXTY_FOUR); 53 | }).to.not.throw(); 54 | }); 55 | 56 | }); 57 | 58 | it("can convert from an array", function() { 59 | var list = List.from([1, 2, 3, 4]); 60 | 61 | var values = []; 62 | list.map(function(value, i) { 63 | values.push(value) 64 | 65 | }, list); 66 | 67 | expect(values).to.eql([1, 2, 3, 4]); 68 | 69 | }); 70 | 71 | it("can convert to an array", function() { 72 | expect(List.of(1, 2, 3, 4, 5, 6).toArray()).to.eql([1, 2, 3, 4, 5, 6]); 73 | }); 74 | 75 | it("can convert empty list to an array", function() { 76 | expect(List.empty().toArray()).to.eql([]); 77 | }); 78 | 79 | it("can reverse the order of a list", function() { 80 | var list = List.of(1, 2, 3, 4, 5, 6); 81 | expect(list.reverse().toArray()).to.eql([6, 5, 4, 3, 2, 1]) 82 | }); 83 | 84 | it("can get by 0 index", function() { 85 | var list = List.of("a", "b", "c", "d"); 86 | expect(list.get(0)).to.eql("a"); 87 | expect(list.get(1)).to.eql("b"); 88 | expect(list.get(2)).to.eql("c"); 89 | expect(list.get(3)).to.eql("d"); 90 | 91 | expect(List.get(0, list)).to.eql("a"); 92 | expect(List.get(1, list)).to.eql("b"); 93 | expect(List.get(2, list)).to.eql("c"); 94 | expect(List.get(3, list)).to.eql("d"); 95 | }); 96 | 97 | it("can map over a list", function() { 98 | var list = List.of(1, 2, 3, 4, 5, 6); 99 | 100 | expect(List.map(v => v + "i", list).toArray()).to.eql(['1i', '2i', '3i', '4i', '5i', '6i']); 101 | }); 102 | 103 | it("can use js iterators over the list", function() { 104 | var list = List.of(SIXTY_FOUR); 105 | var result = []; 106 | 107 | for (var value of list) { 108 | result.push(value); 109 | } 110 | 111 | expect(list.toArray()).to.eql(result); 112 | }); 113 | 114 | it("can slice a list", function() { 115 | expect(List.of(1, 2, 3, 4, 5).slice(2).toArray()).to.eql([3, 4, 5]) 116 | }); 117 | 118 | it("can show length", function() { 119 | var list = List.of(1, 2, 3, 4, 5, 6); 120 | 121 | expect(list.length).to.equal(6); 122 | expect(list.size()).to.equal(6); 123 | }); 124 | 125 | function pow(num, prev) { 126 | return Math.pow(num, prev) 127 | } 128 | 129 | function divide(num, prev) { 130 | return prev / num 131 | } 132 | 133 | describe('foldR', function() { 134 | 135 | it("can fold list where order doesnt matter", function() { 136 | function add(n, m) { 137 | return n + m; 138 | } 139 | 140 | var list = List.of(1, 2, 3, 4); 141 | var sum = List.foldr(add, 0, list); 142 | 143 | expect(sum).to.equal(1 + 2 + 3 + 4); 144 | }); 145 | 146 | it("can fold list in the correct order", function() { 147 | function fn(name, param) { 148 | return name + '(' + param + ')' 149 | } 150 | 151 | // if order were wrong, might give c(b(a(x))) 152 | expect(List.foldr(fn, 'x', List.of('a', 'b', 'c'))).to.equal('a(b(c(x)))'); 153 | }); 154 | }); 155 | 156 | describe('foldL', function() { 157 | 158 | it("can fold list where order doesnt matter", function() { 159 | function add(n, m) { 160 | return n + m; 161 | } 162 | 163 | var list = List.of(1, 2, 3, 4); 164 | var sum = List.foldl(add, 0, list); 165 | 166 | expect(sum).to.equal(1 + 2 + 3 + 4); 167 | }); 168 | 169 | it("can fold list in the correct order", function() { 170 | function fn(name, param) { 171 | return name + '(' + param + ')' 172 | } 173 | 174 | expect(List.foldl(fn, 'x', List.of('a', 'b', 'c'))).to.equal('c(b(a(x)))'); 175 | }); 176 | }); 177 | 178 | describe('slice', function() { 179 | var list = List.of(1,2,3,4,5,6,7,8,9,10); 180 | var big = List.from(SIXTY_FOUR); 181 | 182 | it("can slice lists", function() { 183 | var front = List.slice(0, 5, list); 184 | expect(front.length).to.equal(5); 185 | 186 | var back = List.slice(5, 10, list); 187 | expect(back.length).to.equal(5); 188 | }); 189 | 190 | it("can slice large lists", function() { 191 | 192 | expect(big.length).to.equal(64); 193 | 194 | var forw = List.slice(0, big.length / 2, big); 195 | expect(forw.size()).to.equal(32, "fail"); 196 | 197 | var aft = List.slice(big.length / 2, big); 198 | expect(aft.length).to.equal(32, "really fail"); 199 | }); 200 | 201 | it("can slice without a end arg", function() { 202 | var tail = List.slice(5, list); 203 | expect(tail.length).to.equal(5); 204 | }); 205 | }); 206 | 207 | describe('append two lists to each other', function() { 208 | var list = List.of(1,2,3,4,5,6,7,8,9,10); 209 | var big = List.of(...SIXTY_FOUR); 210 | 211 | it("can append to list together in correct order", function() { 212 | var joined = List.append(big, list); 213 | 214 | expect(joined.length).to.equal(74); 215 | 216 | expect(joined.get(73)).to.equal(10); 217 | expect(joined.get(64)).to.equal(1); 218 | expect(joined.get(54)).to.equal(54); 219 | }); 220 | 221 | it("can prepend two lists to each other", function() { 222 | var joined = list.prepend(big); 223 | 224 | expect(joined.length).to.equal(74); 225 | 226 | expect(joined.get(73)).to.equal(10); 227 | expect(joined.get(64)).to.equal(1); 228 | expect(joined.get(54)).to.equal(54); 229 | }); 230 | }); 231 | 232 | describe("errors with multiples of 32", function() { 233 | 234 | it("can work append powers of 32", function() { 235 | var k1 = List.range(0,1000); 236 | k1.append(List.range(0,1000)); 237 | 238 | }); 239 | 240 | it("can work append powers of 32", function() { 241 | var len = 32; 242 | var k32 = List.times(v => v, len); 243 | 244 | List.append(k32.slice(1, 32), k32.slice(len + 1, -1)) 245 | 246 | }); 247 | 248 | it("slice repeatedly", function() { 249 | var n = 31; 250 | 251 | var x = List.range(0, 40 * n); 252 | x = List.slice(n, 4 * n, x); 253 | x = List.slice(n, 3 * n, x); 254 | x = List.slice(n, 2 * n, x); 255 | x = List.slice(n, n, x); 256 | 257 | }); 258 | 259 | it("powers of 32", function() { 260 | var x = List.append(List.one(1), List.range(0, Math.pow(32,2) - 32 + 1)); 261 | x = List.append(List.one(1), List.range(0, Math.pow(32,3) - 32 + 1)); 262 | x = List.append(List.one(1), List.range(0, Math.pow(32,4) - 32 + 1)); 263 | }); 264 | 265 | it("smaller powers of 32", function() { 266 | var x = List.append(List.one(1), List.range(0, 960 + 2)); 267 | x = List.append(List.one(1), List.range(0, 963 + 1)); 268 | }); 269 | }) 270 | }); -------------------------------------------------------------------------------- /src/append.js: -------------------------------------------------------------------------------- 1 | import {E, M} from './constants'; 2 | import {tableOf, tableLenOf, heightOf, length, lengthsOf, isLeaf} from './accessors'; 3 | import {parentise, siblise, nodeCopy, firstSlot, lastSlot} from './internal'; 4 | import {Node} from './Node'; 5 | 6 | /** 7 | * append/concat to lists together 8 | * 9 | * this is an attempt to optimize only one use case 10 | * not compatible with current code base 11 | * 12 | * 13 | * still need to verify this is worth the perf benefits 14 | * 15 | * 16 | * 17 | * 18 | * 19 | */ 20 | 21 | 22 | /** 23 | * join to lists together(concat) 24 | * 25 | * @param {Node} a 26 | * @param {Node} b 27 | * @return {Node} 28 | */ 29 | export function append(a, b) { 30 | var aTable = a['@@rrb/table']; 31 | var bTable = b['@@rrb/table']; 32 | var aTableLen = aTable.length; 33 | var bTableLen = bTable.length; 34 | 35 | if (aTableLen === 0) return b; 36 | if (bTableLen === 0) return a; 37 | 38 | var [a2, b2] = __append(a, b); 39 | var a2Table = a2['@@rrb/table']; 40 | var b2Table = b2['@@rrb/table']; 41 | var a2TableLen = a2Table.length; 42 | var b2TableLen = b2Table.length; 43 | 44 | // Check if both nodes can be crunshed together. 45 | if (a2TableLen + b2TableLen <= M) { 46 | if (a2Table.length === 0) return b2; 47 | if (b2Table.length === 0) return a2; 48 | 49 | // Adjust .table and .lengths 50 | a2['@@rrb/table'] = a2Table.concat(b2Table); 51 | if (a2['@@rrb/height'] > 0) { 52 | var len = length(a2); 53 | var lengths = lengthsOf(b2); 54 | for (var i = 0, l = lengths.length; i < l; i++) { 55 | lengths[i] += len; 56 | } 57 | a2['@@rrb/lengths'] = a2['@@rrb/lengths'].concat(lengths); 58 | } 59 | 60 | return a2; 61 | } 62 | 63 | if (a2['@@rrb/height'] > 0) { 64 | var toRemove = calcToRemove(a, b); 65 | if (toRemove > E) { 66 | [a2, b2] = shuffle(a2, b2, toRemove); 67 | } 68 | } 69 | 70 | return siblise(a2, b2); 71 | } 72 | 73 | /** 74 | * Returns an array of two nodes; right and left. One node _may_ be empty. 75 | * @param {Node} a 76 | * @param {Node} b 77 | * @return {Array} 78 | * @private 79 | */ 80 | function __append(a, b) { 81 | var aHeight = a['@@rrb/height']; 82 | var bHeight = b['@@rrb/height']; 83 | 84 | if (aHeight == 0 && bHeight == 0) { 85 | return [a, b]; 86 | } 87 | 88 | if (aHeight !== 1 || bHeight !== 1) { 89 | if (aHeight === bHeight) { 90 | a = nodeCopy(a); 91 | b = nodeCopy(b); 92 | var tuple = __append(lastSlot(a), firstSlot(b)); 93 | var a0 = tuple[0]; 94 | var b0 = tuple[1]; 95 | 96 | insertRight(a, b0); 97 | insertLeft(b, a0); 98 | 99 | } else if (aHeight > bHeight) { 100 | a = nodeCopy(a); 101 | var tuple = __append(lastSlot(a), b); 102 | var a0 = tuple[0]; 103 | var b0 = tuple[1]; 104 | 105 | insertRight(a, a0); 106 | b = parentise(b0, b0['@@rrb/height'] + 1); 107 | } else { 108 | b = nodeCopy(b); 109 | var tuple = __append(a, firstSlot(b)); 110 | 111 | var left = tableLenOf(tuple[0]) === 0 ? 0 : 1; 112 | var right = left === 0 ? 1 : 0; 113 | insertLeft(b, tuple[left]); 114 | a = parentise(tuple[right], tuple[right]['@@rrb/height'] + 1); 115 | } 116 | } 117 | 118 | // Check if balancing is needed and return based on that. 119 | if (tableLenOf(a) === 0 || tableLenOf(b) === 0) { 120 | return [a, b]; 121 | } 122 | 123 | var toRemove = calcToRemove(a, b); 124 | if (toRemove <= E) { 125 | return [a, b]; 126 | } 127 | return shuffle(a, b, toRemove); 128 | } 129 | 130 | // Helperfunctions for __append. Replaces a child node at the side of the parent. 131 | function insertRight(parent, node) { 132 | var index = tableLenOf(parent) - 1; 133 | tableOf(parent)[index] = node; 134 | lengthsOf(parent)[index] = length(node) + (index > 0 ? lengthsOf(parent)[index - 1] : 0); 135 | } 136 | 137 | function insertLeft(parent, node) { 138 | var lengths = lengthsOf(parent); 139 | var table = tableOf(parent); 140 | 141 | if (tableLenOf(node) > 0) { 142 | table[0] = node; 143 | lengths[0] = length(node); 144 | 145 | var len = length(table[0]); 146 | for (var i = 1, l = lengths.length; l > i; i++) { 147 | lengths[i] = len = (len += length(table[i])); 148 | } 149 | 150 | } else { 151 | table.shift(); 152 | for (var i = 1, l = lengths.length; l > i; i++) { 153 | lengths[i] = lengths[i] - lengths[0]; 154 | } 155 | lengths.shift(); 156 | } 157 | } 158 | 159 | 160 | /** 161 | * Returns an array of two balanced nodes. 162 | * @param {Node} a 163 | * @param {Node} b 164 | * @param {number} toRemove 165 | * @return {Array} 166 | */ 167 | function shuffle(a, b, toRemove) { 168 | var newA = allocate(heightOf(a), Math.min(M, tableLenOf(a) + tableLenOf(b) - toRemove)); 169 | var newB = allocate(heightOf(a), tableLenOf(newA) - (tableLenOf(a) + tableLenOf(b) - toRemove)); 170 | 171 | // Skip the slots with size M. More precise: copy the slot references 172 | // to the new node 173 | var read = 0; 174 | while (tableOf(getEither(tableOf(a), tableOf(a), read)).length % M === 0) { 175 | setEither(tableOf(newA), tableOf(newB), read, getEither(tableOf(a), tableOf(b), read)); 176 | setEither(lengthsOf(newA), lengthsOf(newB), read, getEither(lengthsOf(a), lengthsOf(b), read)); 177 | read++; 178 | } 179 | 180 | // Pulling items from left to right, caching in a slot before writing 181 | // it into the new nodes. 182 | var write = read; 183 | var slot = allocate(heightOf(a) - 1, 0); 184 | var from = 0; 185 | 186 | // If the current slot is still containing data, then there will be at 187 | // least one more write, so we do not break this loop yet. 188 | while (read - write - (tableLenOf(slot) > 0 ? 1 : 0) < toRemove) { 189 | // Find out the max possible items for copying. 190 | var source = getEither(tableOf(a), tableOf(b), read); 191 | var to = Math.min(M - tableLenOf(slot), tableLenOf(source)); 192 | 193 | // Copy and adjust size table. 194 | slot['@@rrb/table'] = tableOf(slot).concat(tableOf(source).slice(from, to)); 195 | if (slot['@@rrb/height'] > 0) { 196 | var lengths = lengthsOf(slot); 197 | var len = lengths.length; 198 | for (var i = len; i < len + to - from; i++) { 199 | lengths[i] = length(tableOf(slot)[i]); 200 | lengths[i] += (i > 0 ? lengthsOf(slot)[i - 1] : 0); 201 | } 202 | } 203 | 204 | from += to; 205 | 206 | // Only proceed to next slots[i] if the current one was 207 | // fully copied. 208 | if (tableLenOf(source) <= to) { 209 | read++; 210 | from = 0; 211 | } 212 | 213 | // Only create a new slot if the current one is filled up. 214 | if (tableLenOf(slot) === M) { 215 | saveSlot(newA, newB, write, slot); 216 | slot = allocate(heightOf(a) - 1, 0); 217 | write++; 218 | } 219 | } 220 | 221 | // Cleanup after the loop. Copy the last slot into the new nodes. 222 | if (tableLenOf(slot) > 0) { 223 | saveSlot(newA, newB, write, slot); 224 | write++; 225 | } 226 | 227 | // Shift the untouched slots to the left 228 | while (read < tableLenOf(a) + tableLenOf(b)) { 229 | saveSlot(newA, newB, write, getEither(tableOf(a), tableOf(b), read)); 230 | read++; 231 | write++; 232 | } 233 | 234 | return [newA, newB]; 235 | } 236 | 237 | // Creates a node or leaf with a given length at their arrays for performance. 238 | // Is only used by shuffle. 239 | function allocate(height, length) { 240 | if (height > 0) 241 | return new Node(length, new Array(length), new Array(length)); 242 | 243 | return new Node(0, new Array(length), void 0); 244 | } 245 | 246 | /** 247 | * helper for setting picking a slot between to nodes 248 | * @param {Node} aList - a non-leaf node 249 | * @param {Node} bList - a non-leaf node 250 | * @param {number} index 251 | * @param {Node} slot 252 | */ 253 | function saveSlot(aList, bList, index, slot) { 254 | setEither(tableOf(aList), tableOf(bList), index, slot); 255 | 256 | var isInFirst = (index === 0 || index === lengthsOf(aList).length); 257 | var len = isInFirst ? 0 : getEither(lengthsOf(aList), lengthsOf(aList), index - 1); 258 | 259 | setEither(lengthsOf(aList), lengthsOf(bList), index, len + length(slot)); 260 | } 261 | 262 | // getEither, setEither and saveSlot are helpers for accessing elements over two arrays. 263 | function getEither(a, b, i) { 264 | return i < a.length ? a[i] : b[i - a.length]; 265 | } 266 | 267 | function setEither(a, b, i, value) { 268 | if (i < a.length) { 269 | a[i] = value; 270 | } else { 271 | b[i - a.length] = value; 272 | } 273 | } 274 | 275 | /** 276 | * Returns the extra search steps for E. Refer to the paper. 277 | * 278 | * @param {Node} a - a non leaf node 279 | * @param {Node} b - a non leaf node 280 | * @return {number} 281 | */ 282 | function calcToRemove(a, b) { 283 | var aTable = a['@@rrb/table']; 284 | var bTable = b['@@rrb/table']; 285 | var subLengths = 0; 286 | subLengths += heightOf(a) == 0 ? 0 : sumOfLengths(aTable); 287 | subLengths += heightOf(b) == 0 ? 0 : sumOfLengths(bTable); 288 | 289 | return (aTable.length + bTable.length) - (Math.floor((subLengths - 1) / M) + 1); 290 | } 291 | 292 | function sumOfLengths(table) { 293 | var sum = 0; 294 | var len = table.length; 295 | for (var i = 0; len > i; i++) 296 | sum += table[i]['@@rrb/table'].length; 297 | 298 | return sum; 299 | } -------------------------------------------------------------------------------- /src/experimental/SkewList.js: -------------------------------------------------------------------------------- 1 | /** 2 | * an implementation of okasaki's Skewed Binary Access List(a.k.a. random access list) 3 | * 4 | * http://citeseer.ist.psu.edu/viewdoc/download?doi=10.1.1.55.5156&rep=rep1&type=pdf 5 | * 6 | * a higher performing alternative to Cons lists with 7 | * > O(1) time cons, head, tail operations 8 | * > O(n) time lookup, update operations 9 | * 10 | * 11 | */ 12 | export default function SkewList(nodes) { 13 | if (!(this instanceof SkewList)) 14 | return new SkewList(nodes); 15 | 16 | this.nodes = nodes || []; 17 | } 18 | 19 | function Node(size, value, right, left) { 20 | this.size = size; 21 | this.value = value; 22 | this.right = right; 23 | this.left = left; 24 | } 25 | 26 | // = constructors ==================================================== 27 | 28 | function of(...args) { 29 | return from(args); 30 | } 31 | 32 | function from(iterable) { 33 | var list = new SkewList(); 34 | for (var item of iterable) 35 | list = cons(item, list); 36 | 37 | return list 38 | } 39 | 40 | SkewList.of = of; 41 | SkewList.from = from; 42 | 43 | function _list(array) { 44 | var list = new SkewList(); 45 | if (array) 46 | list.nodes = array; 47 | 48 | return list; 49 | } 50 | 51 | // = helpers ========================================================= 52 | 53 | function _fixLength(len, list) { 54 | if (!len) 55 | return 0; 56 | 57 | if (len < 0) 58 | return lengthOf(list) + len; 59 | // since length is calculated, we won't check if higher than bounds 60 | return len; 61 | } 62 | 63 | function _updateNode(i, value, tree) { 64 | if (i === 0) 65 | return new Node(tree.size, value, tree.right, tree.left); 66 | 67 | 68 | var leastOnRight = (1 + tree.size) / 2 69 | , isOnLeft = i < leastOnRight 70 | , right = !isOnLeft ? _updateNode(i - leastOnRight, value, tree.right) : tree.right 71 | , left = isOnLeft ? _updateNode(i - 1, value, tree.left ) : tree.left; 72 | 73 | return new Node(tree.size, tree.value, right, left); 74 | } 75 | 76 | function _mapNode(fn, tree) { 77 | if (tree.size === 1) 78 | return new Node(1, fn(tree.value), null, null); 79 | 80 | var value = fn(tree.value) 81 | , left = _mapNode(fn, tree.left) 82 | , right = _mapNode(fn, tree.right); 83 | 84 | return new Node(tree.size, value, right, left); 85 | } 86 | 87 | // = operations ====================================================== 88 | 89 | export function lengthOf(list) { 90 | var len = 0 91 | , nodes = list.nodes 92 | , ll = nodes.length; 93 | 94 | for (var i = 0; ll > i; i++) 95 | len += nodes[i].size 96 | 97 | return len; 98 | } 99 | 100 | /** 101 | * append value to beginning of list and return a new list 102 | * 103 | * @param {T} value 104 | * @param {SkewList} list 105 | * @returns {SkewList} 106 | */ 107 | export function cons(value, list) { 108 | var nodes = list.nodes; 109 | if (nodes.length < 2 || (nodes[0].size != nodes[1].size)) { 110 | return _list([new Node(1, value, null, null), ...nodes]) 111 | } 112 | 113 | var node = new Node((nodes[0].size * 2) + 1, value, nodes[1], nodes[0]); 114 | return _list([node, ...nodes.slice(2)]); 115 | } 116 | 117 | /** 118 | * get the value at index or, 119 | * if out of range return notFound 120 | * 121 | * (bring your own Maybe/Error types) 122 | * 123 | * @param {Number} i 124 | * @param {SkewList} list 125 | * @param notFound 126 | * @returns {T|notFound} 127 | */ 128 | export function nth(i, list, notFound) { 129 | i = _fixLength(i, list); 130 | 131 | var nodes = list.nodes; 132 | for (var node of nodes) { 133 | if (node.size <= i) { 134 | i -= node.size; 135 | continue; 136 | } 137 | while (i > 0) { 138 | if (i >= (1 + node.size) / 2) { // Go right, -2^i 139 | i -= (1 + node.size) / 2; 140 | node = node.right; 141 | } else { // Go left, -1 142 | i -= 1; 143 | node = node.left; 144 | } 145 | } 146 | return node.value; 147 | } 148 | return notFound; 149 | } 150 | 151 | /** 152 | * create a new list and set the value at index 153 | * 154 | * @param {number} i 155 | * @param {T} value 156 | * @param {SkewList} list 157 | * @returns {SkewList} 158 | */ 159 | export function update(i, value, list) { 160 | i = _fixLength(i); 161 | var nodes = list.nodes, 162 | len = nodes.length; 163 | 164 | for (var n = 0; len > n; n++) { 165 | var node = nodes[n]; 166 | if (!(i < node.size)) { 167 | i -= node.size; 168 | continue; 169 | } 170 | var items = nodes.slice(); 171 | items.splice(n, 1, _updateNode(i, value, node)); 172 | 173 | return _list(items); 174 | } 175 | return list; // nothing found so ??? 176 | } 177 | 178 | 179 | /** 180 | * return a new list without the first item 181 | * if list is empty, return notFound 182 | * 183 | * @param {SkewList} list 184 | * @param {*} notFound 185 | * @return {SkewList|notFound} 186 | */ 187 | export function tail(list, notFound) { 188 | if (isEmpty(list)) 189 | return notFound; 190 | 191 | var nodes = list.nodes; 192 | var [first, ...rest] = nodes; 193 | 194 | if (first.size == 1) 195 | return _list(rest); 196 | 197 | return _list([first.left, first.right, rest]); 198 | } 199 | 200 | 201 | /** 202 | * 203 | * (bring your own Maybe/Error types) 204 | * 205 | * @param {SkewList} list 206 | * @param notFound 207 | * @return {T|notFound} 208 | */ 209 | export function head(list, notFound) { 210 | return isEmpty(list) ? notFound : list.nodes[0].value; 211 | 212 | } 213 | 214 | 215 | export function isEmpty(list) { 216 | return list.nodes.length === 0; 217 | } 218 | 219 | 220 | /** 221 | * optimized map routine 222 | * does not pass index(functional style signature), reuses existing array "shape" 223 | * 224 | * to get a more js like map(e.g. with function(T, Number) signature), use kvMap 225 | * 226 | * @param {function(T)} fn 227 | * @param {SkewList} list 228 | */ 229 | export function map(fn, list) { 230 | var nodes = list.nodes 231 | , len = nodes.length 232 | , roots = new Array(len); 233 | for (var i = 0; len > i; i++) { 234 | roots[i] = _mapNode(fn, nodes[i]); 235 | } 236 | return _list(roots); 237 | } 238 | 239 | /** 240 | * 241 | * @param {function(T, Number)} fn 242 | * @param {SkewList} list 243 | * @return {SkewList} 244 | */ 245 | export function kvMap(fn, list) { 246 | // since we can only cons to the front of a list, 247 | // we iterate through the list backwards to maintain proper ordering 248 | return kvReduceRight((acc, i, value) => 249 | acc.unshift(fn(value, i)), _list(), list); 250 | } 251 | 252 | /** 253 | * 254 | * @param {function(seed, Number, T)} fn 255 | * @param {*} seed 256 | * @param {SkewList} list 257 | * @return {seed} 258 | */ 259 | export function kvReduce(fn, seed, list) { 260 | var i = 0, 261 | inc = () => i++; 262 | 263 | for (var item of list) { 264 | seed = fn(seed, inc(), item); 265 | } 266 | return seed; 267 | } 268 | 269 | /** 270 | * reduce in reverse order 271 | * @param {function(seed, Number, T)} fn 272 | * @param {*} seed 273 | * @param {SkewList} list 274 | * @return {seed} 275 | */ 276 | export function kvReduceRight(fn, seed, list) { 277 | var len = lengthOf(list); 278 | for (var item of reverseIter(list)) { 279 | seed = fn(seed, len--, item); 280 | } 281 | return seed; 282 | } 283 | 284 | // = iterators ===================================================================== 285 | 286 | function* iterTree(node) { 287 | yield node.value; 288 | if (node.left) 289 | yield * iterTree(node.left); 290 | 291 | if (node.right) 292 | yield * iterTree(node.right); 293 | } 294 | 295 | export function* iterator(list) { 296 | var nodes = list.nodes; 297 | for (var node of nodes) { 298 | yield * iterTree(node); 299 | } 300 | } 301 | 302 | function* _revTreeIter(node) { 303 | if (node.right) 304 | yield * _revTreeIter(node.right); 305 | 306 | if (node.left) 307 | yield * _revTreeIter(node.left); 308 | 309 | yield node.value; 310 | } 311 | 312 | export function* reverseIter(list) { 313 | var nodes = list.nodes; 314 | var i = nodes.length; 315 | while(i--) { 316 | yield * _revTreeIter(nodes[i]); 317 | } 318 | } 319 | 320 | 321 | 322 | 323 | 324 | var proto = SkewList.prototype; 325 | proto[Symbol.iterator] = function() { return iterator(this); }; 326 | proto.get = function(i, notFound) { return nth(i, this, notFound); }; 327 | proto.nth = function(i, notFound) { return nth(i, this, notFound); }; 328 | proto.set = function(i, value) { return update(i, value, this); }; 329 | proto.head = function(notFound) { return head(this, notFound); }; 330 | proto.isEmpty = function() { return isEmpty(this); }; 331 | proto.map = function(fn) { return map(fn, this); }; 332 | proto.tail = function() { return tail(this); }; 333 | proto.head = function() { return head(this); }; 334 | proto.unshift = proto.cons = function(value) { return cons(value, this); }; 335 | proto.reverse = function() { return reverseIter(this); }; 336 | 337 | proto.reduce = function(fn, value) { 338 | for (var item of this) { 339 | value = fn(value, item); 340 | } 341 | return value; 342 | }; 343 | 344 | 345 | SkewList.empty = proto.empty = () => new SkewList(); 346 | 347 | Object.defineProperty(proto, 'length', { 348 | configurable: false, 349 | get: function() { 350 | //caching length calc 351 | if (typeof this['@@length'] == 'undefined') { 352 | this['@@length'] = lengthOf(this); 353 | } 354 | 355 | return this['@@length']; 356 | } 357 | }); 358 | 359 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'default' in ex) ? ex['default'] : ex; } 4 | 5 | var fl = _interopDefault(require('fantasy-land')); 6 | 7 | const _slice = Array.prototype.slice; 8 | function slice(from, to, array) { 9 | return _slice.call(array, from, to); 10 | } 11 | 12 | 13 | 14 | function curry(fn) { 15 | 16 | return function currydFn() { 17 | return _currify(fn, _slice.call(arguments), fn.length - arguments.length) 18 | } 19 | } 20 | 21 | function _currify(fn, args, remain) { 22 | if (remain < 1) 23 | return apply(fn, args); 24 | 25 | return function() { 26 | args = args.slice(0, fn.length-1).concat(_slice.call(arguments, 0)); 27 | return _currify(fn, args, remain - arguments.length); 28 | } 29 | } 30 | 31 | function apply(fn, args) { 32 | var len = args.length; 33 | 34 | if (len === 0) return fn(); 35 | if (len === 1) return fn(args[0]); 36 | if (len === 2) return fn(args[0], args[1]); 37 | if (len === 3) return fn(args[0], args[1], args[2]); 38 | if (len === 4) return fn(args[0], args[1], args[2], args[3]); 39 | if (len === 5) return fn(args[0], args[1], args[2], args[3], args[4]); 40 | if (len === 6) return fn(args[0], args[1], args[2], args[3], args[4], args[5]); 41 | if (len === 7) return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6]); 42 | if (len === 8) return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]); 43 | if (len === 9) return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8]); 44 | if (len === 10)return fn(args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8], args[9]); 45 | 46 | return fn.apply(undefined, args); 47 | } 48 | 49 | 50 | 51 | 52 | 53 | 54 | function identity(value) { return value; } 55 | 56 | 57 | 58 | // clone an array 59 | function copy(array) { 60 | return _slice.call(array, 0); 61 | } 62 | 63 | 64 | 65 | function last(list) { 66 | return list[list.length - 1]; 67 | } 68 | 69 | function first(list) { 70 | return list[0]; 71 | } 72 | 73 | function setLast(value, jsArray) { 74 | jsArray[jsArray.length - 1] = value; 75 | return jsArray; 76 | } 77 | 78 | /* 79 | * private property accessors 80 | * 81 | * 82 | * 83 | * 84 | * 85 | * 86 | * 87 | */ 88 | 89 | 90 | /** 91 | * get the array containing the lengths of each child node 92 | * @param {Node} list 93 | * @return {Array} 94 | */ 95 | function lengthsOf(list) { 96 | return list['@@rrb/lengths']; 97 | } 98 | 99 | function heightOf(list) { 100 | return list['@@rrb/height']; 101 | } 102 | 103 | function tableOf(list) { 104 | return list['@@rrb/table']; 105 | } 106 | 107 | function tableLenOf(list) { 108 | return list['@@rrb/table'].length; 109 | } 110 | 111 | 112 | // determine if this is a leaf vs container node 113 | function isLeaf$1(node) { 114 | return node['@@rrb/height'] === 0; 115 | } 116 | 117 | // get the # of elements in a rrb list 118 | function length(list) { 119 | return isLeaf$1(list) ? list['@@rrb/table'].length : last(list['@@rrb/lengths']); 120 | } 121 | 122 | /** 123 | * an RRB tree has two data types: 124 | * 125 | * Leaf 126 | * - height is always 0 127 | * - table is an collection of values 128 | * 129 | * Parent 130 | * - height is always greater than 0 131 | * - table is collection of child nodes 132 | * - lengths is cache of accumulated lengths of children 133 | * 134 | * height and table are mandatory, lengths may be null 135 | * 136 | */ 137 | 138 | /** 139 | * 140 | * @param {Array} table 141 | * @return {Node} 142 | * @constructor 143 | */ 144 | 145 | 146 | /** 147 | * 148 | * @param {Number} height 149 | * @param {Array} table 150 | * @param {Array} lengths 151 | * @return {Node} 152 | * @constructor 153 | */ 154 | 155 | 156 | /** 157 | * The base list class 158 | * @param {number} height 159 | * @param {Array} table 160 | * @param {Array} lengths 161 | * @constructor 162 | */ 163 | function Node(height, table, lengths) { 164 | this['@@rrb/lengths'] = lengths; 165 | this['@@rrb/height'] = height; 166 | this['@@rrb/table'] = table; 167 | } 168 | 169 | Node.prototype.isEmpty = () => false; // small optimization because all empty lists are the same element 170 | Node.prototype.size = function() { return length(this); }; 171 | 172 | Object.defineProperty(Node.prototype, 'length', { 173 | get() { 174 | return this.size(); 175 | }, 176 | set(value) { 177 | //do nothing 178 | } 179 | }); 180 | 181 | const EMPTY = Object.freeze(Object.assign(new Node(0, [], void 0), { 182 | isEmpty() { return true; }, 183 | size() { return 0; } 184 | })); 185 | 186 | 187 | 188 | function isNode(item) { 189 | return item instanceof Node; 190 | } 191 | 192 | Node.prototype.empty = empty; 193 | 194 | Node.empty = empty; 195 | 196 | function empty() { 197 | return EMPTY; 198 | } 199 | 200 | Node.one = one; 201 | 202 | // performance shortcut for an array of one 203 | function one(item) { 204 | return new Node(0, [ item ], void 0); 205 | } 206 | 207 | // M is the maximal table size. 32 seems fast. E is the allowed increase 208 | // of search steps when concatting to find an index. Lower values will 209 | // decrease balancing, but will increase search steps. 210 | const M = 32; 211 | const E = 2; 212 | 213 | /** 214 | * create an rrb vector from a js array 215 | * note: this is meant for internal use only. public usage should be with List.from() 216 | * 217 | * @param {Array} jsArray 218 | */ 219 | function fromArray(jsArray) { 220 | var len = jsArray.length; 221 | if (len === 0) 222 | return EMPTY; 223 | 224 | return _fromArray(jsArray, Math.floor(Math.log(len) / Math.log(M)), 0, len); 225 | 226 | function _fromArray(jsArray, h, from, to) { 227 | if (h === 0) { 228 | return new Node(0, slice(from, to, jsArray), void 0); 229 | } 230 | 231 | var step = Math.pow(M, h); 232 | var len = Math.ceil((to - from) / step); 233 | var table = new Array(len); 234 | var lengths = new Array(len); 235 | for (var i = 0; len > i; i++) { 236 | //todo: trampoline? 237 | table[i] = _fromArray(jsArray, h - 1, from + (i * step), Math.min(from + ((i + 1) * step), to)); 238 | lengths[i] = length(table[i]) + (i > 0 ? lengths[i - 1] : 0); 239 | } 240 | return new Node(h, table, lengths); 241 | } 242 | 243 | } 244 | 245 | Node.of = of; 246 | 247 | function of(first, ...rest) { 248 | 249 | if (typeof first === 'undefined') 250 | return EMPTY; 251 | 252 | if (rest && rest.length > 0) 253 | return fromArray([first].concat(rest)); 254 | 255 | return one(first); 256 | } 257 | 258 | Node.times = times; 259 | 260 | /** 261 | * populate an array using provided function 262 | * 263 | * @param len 264 | * @param {function(number)} fn 265 | * @return {Node} 266 | */ 267 | function times(fn, len) { 268 | if (len <= 0) 269 | return EMPTY; 270 | 271 | // just iterating over push() isn't terribly fast... 272 | // we attempt to optimize here by pre-allocating 273 | 274 | var height = Math.floor( Math.log(len) / Math.log(M) ); 275 | return populate(fn, height, 0, len); 276 | 277 | function populate(func, h, from, to) { 278 | 279 | if (h === 0) { //leaf node 280 | return populateLeaf(func, from, to); 281 | } 282 | 283 | // populate container node 284 | var step = Math.pow(M, h); 285 | var len = Math.ceil((to - from) / step); 286 | var table = new Array(len); 287 | var lengths = new Array(len); 288 | for (var i = 0; len > i; i++) { 289 | // todo: trampoline? 290 | table[i] = populate(func, h - 1, from + (i * step), Math.min(from + ((i + 1) * step), to)); 291 | lengths[i] = length(table[i]) + (i > 0 ? lengths[i-1] : 0); 292 | } 293 | return new Node(h, table, lengths); 294 | } 295 | 296 | function populateLeaf(fn, from, to) { 297 | var len = (to - from) % (M + 1); 298 | var table = new Array(len); 299 | for (var i = 0; len > i; i++) { 300 | table[i] = fn(from + i); 301 | } 302 | return new Node(0, table, void 0); 303 | } 304 | } 305 | 306 | Node.from = from; 307 | 308 | /** 309 | * the default list constructor 310 | * accepts an single native array, varargs, or nothing(if an empty list is desired) 311 | * 312 | */ 313 | function from(iterable, mapFn) { 314 | var list = EMPTY; 315 | 316 | if (isNode(iterable)) { 317 | return iterable; 318 | } 319 | 320 | // use more performant, pre-allocation technique when possible 321 | if (Array.isArray(iterable)) { 322 | return !mapFn ? fromArray(iterable) : times((i) => mapFn(iterable[i], i), iterable.length); 323 | } 324 | 325 | // if length is unknown, just use slower push 326 | if (mapFn) { 327 | for (var item of iterable) { 328 | list = list.push(mapFn(item)); 329 | } 330 | } else { 331 | for (var item of iterable) { 332 | list = list.push(item); 333 | } 334 | } 335 | 336 | return list; 337 | } 338 | 339 | Node.range = range; 340 | 341 | function range(from, to) { 342 | var len = to - from; 343 | return times((i) => from + i, len); 344 | } 345 | 346 | /** 347 | * private util operations rrb lists use 348 | * 349 | * 350 | * 351 | * 352 | * 353 | */ 354 | 355 | // Recursively creates a tree with a given height containing 356 | // only the given item. 357 | function createNodeWithHeight(item, height) { 358 | if (height === 0) { 359 | return new Node(0, [item], void 0); 360 | } 361 | return new Node(height, [createNodeWithHeight(item, height - 1)], [1]); 362 | } 363 | 364 | 365 | function sliceLeft(from, list) { 366 | if (from === 0) 367 | return list; 368 | 369 | const listTable = tableOf(list); 370 | // Handle leaf level. 371 | if (isLeaf$1(list)) 372 | return new Node(0, listTable.slice(from, listTable.length + 1), void 0); 373 | 374 | // Slice the left recursively. 375 | var left = findSlot(from, list); 376 | var sliced = sliceLeft(from - (left > 0 ? lengthsOf(list)[left - 1] : 0), listTable[left]); 377 | 378 | // Maybe the a node is not even needed, as sliced contains the whole slice. 379 | if (left === listTable.length - 1) { 380 | return sliced; 381 | } 382 | 383 | // Create new node. 384 | var tbl = listTable.slice(left, listTable.length + 1); 385 | tbl[0] = sliced; 386 | var lengths = new Array(listTable.length - left); 387 | var len = 0; 388 | for (var i = 0; i < tbl.length; i++) { 389 | len += length(tbl[i]); 390 | lengths[i] = len; 391 | } 392 | 393 | return new Node(heightOf(list), tbl, lengths); 394 | } 395 | 396 | function sliceRight(to, list) { 397 | if (to === length(list)) 398 | return list; 399 | 400 | const listTable = tableOf(list); 401 | // Handle leaf level. 402 | if (isLeaf$1(list)) 403 | return new Node(0, listTable.slice(0, to), void 0); 404 | 405 | // Slice the right recursively. 406 | var right = findSlot(to, list); 407 | var sliced = sliceRight(to - (right > 0 ? lengthsOf(list)[right - 1] : 0), listTable[right]); 408 | 409 | // Maybe the a node is not even needed, as sliced contains the whole slice. 410 | if (right === 0) 411 | return sliced; 412 | 413 | // Create new node. 414 | var lengths = lengthsOf(list).slice(0, right); 415 | var tbl = listTable.slice(0, right); 416 | if (tableOf(sliced).length > 0) { 417 | tbl[right] = sliced; 418 | lengths[right] = length(sliced) + (right > 0 ? lengths[right - 1] : 0); 419 | } 420 | return new Node(heightOf(list), tbl, lengths); 421 | } 422 | 423 | // Calculates in which slot of "table" the item probably is, then 424 | // find the exact slot via forward searching in "lengths". Returns the index. 425 | function findSlot(i, list) { 426 | var slot = i >> (5 * heightOf(list)); 427 | while (lengthsOf(list)[slot] <= i) { 428 | slot++; 429 | } 430 | return slot; 431 | } 432 | 433 | 434 | function unsafeGet(i, list) { 435 | for (var x = heightOf(list); x > 0; x--) { 436 | var slot = i >> (x * 5); 437 | while (lengthsOf(list)[slot] <= i) { 438 | slot++; 439 | } 440 | if (slot > 0) { 441 | i -= lengthsOf(list)[slot - 1]; 442 | } 443 | list = tableOf(list)[slot]; 444 | } 445 | return tableOf(list)[i]; 446 | } 447 | 448 | function unsafeSet(i, item, list) { 449 | list = nodeCopy(list); 450 | 451 | if (isLeaf$1(list)) { 452 | tableOf(list)[i] = item; 453 | } else { 454 | var slot = findSlot(i, list); 455 | if (slot > 0) { 456 | i -= lengthsOf(list)[slot - 1]; 457 | } 458 | tableOf(list)[slot] = unsafeSet(i, item, tableOf(list)[slot]); 459 | } 460 | return list; 461 | } 462 | 463 | 464 | 465 | 466 | 467 | // Navigation functions 468 | function lastSlot(node) { 469 | return last(tableOf(node)); 470 | } 471 | 472 | function firstSlot(node) { 473 | return first(tableOf(node)); 474 | } 475 | 476 | 477 | 478 | // Copies a node for updating. Note that you should not use this if 479 | // only updating only one of "table" or "lengths" for performance reasons. 480 | function nodeCopy(a) { 481 | return new Node(heightOf(a), copy(tableOf(a)), (isLeaf$1(a) ? void 0: copy(lengthsOf(a))) ); 482 | } 483 | 484 | // Recursively creates a tree that contains the given tree. 485 | function parentise(tree, height) { 486 | return height === heightOf(tree) ? tree : new Node(height, [parentise(tree, height - 1)], [length(tree)]); 487 | } 488 | 489 | // Emphasizes blood brotherhood beneath two trees. 490 | function siblise(a, b) { 491 | return new Node(a['@@rrb/height'] + 1, [a, b], [length(a), length(a) + length(b)]); 492 | } 493 | 494 | /** 495 | * Recursively tries to push an item to the bottom-right most 496 | * tree possible. If there is no space left for the item, 497 | * null will be returned. 498 | * @param {*} item 499 | * @param {Node} list 500 | * @return {Node|null} 501 | */ 502 | function pushIfSpace(item, list) { 503 | // Handle recursion stop at leaf level. 504 | if (isLeaf$1(list)) { 505 | if (tableOf(list).length < M) { 506 | return new Node(0, tableOf(list).concat(item), void 0); 507 | } 508 | 509 | return null; 510 | } 511 | 512 | // Recursively push 513 | var pushed = pushIfSpace(item, lastSlot(list)); 514 | 515 | // There was space in the bottom right tree, so the slot will 516 | // be updated. 517 | if (pushed !== null) { 518 | var newA = nodeCopy(list); 519 | setLast(pushed, tableOf(newA)); 520 | setLast(last(lengthsOf(newA)) + 1, lengthsOf(newA)); 521 | return newA; 522 | } 523 | 524 | // When there was no space left, check if there is space left 525 | // for a new slot with a tree which contains only the item 526 | // at the bottom. 527 | if (tableLenOf(list) < M) { 528 | var newSlot = createNodeWithHeight(item, heightOf(list) - 1); 529 | 530 | return new Node(heightOf(list), 531 | tableOf(list).concat(newSlot), 532 | lengthsOf(list).concat(last(lengthsOf(list)) + length(newSlot)) 533 | ); 534 | } else { 535 | return null; 536 | } 537 | } 538 | 539 | 540 | function push(item, list) { 541 | 542 | return pushIfSpace(item, list) || siblise(list, createNodeWithHeight(item, heightOf(list))); 543 | } 544 | 545 | /** 546 | * append/concat to lists together 547 | * 548 | * this is an attempt to optimize only one use case 549 | * not compatible with current code base 550 | * 551 | * 552 | * still need to verify this is worth the perf benefits 553 | * 554 | * 555 | * 556 | * 557 | * 558 | */ 559 | 560 | 561 | /** 562 | * join to lists together(concat) 563 | * 564 | * @param {Node} a 565 | * @param {Node} b 566 | * @return {Node} 567 | */ 568 | function append$2(a, b) { 569 | var aTable = a['@@rrb/table']; 570 | var bTable = b['@@rrb/table']; 571 | var aTableLen = aTable.length; 572 | var bTableLen = bTable.length; 573 | 574 | if (aTableLen === 0) return b; 575 | if (bTableLen === 0) return a; 576 | 577 | var [a2, b2] = __append(a, b); 578 | var a2Table = a2['@@rrb/table']; 579 | var b2Table = b2['@@rrb/table']; 580 | var a2TableLen = a2Table.length; 581 | var b2TableLen = b2Table.length; 582 | 583 | // Check if both nodes can be crunshed together. 584 | if (a2TableLen + b2TableLen <= M) { 585 | if (a2Table.length === 0) return b2; 586 | if (b2Table.length === 0) return a2; 587 | 588 | // Adjust .table and .lengths 589 | a2['@@rrb/table'] = a2Table.concat(b2Table); 590 | if (a2['@@rrb/height'] > 0) { 591 | var len = length(a2); 592 | var lengths = lengthsOf(b2); 593 | for (var i = 0, l = lengths.length; i < l; i++) { 594 | lengths[i] += len; 595 | } 596 | a2['@@rrb/lengths'] = a2['@@rrb/lengths'].concat(lengths); 597 | } 598 | 599 | return a2; 600 | } 601 | 602 | if (a2['@@rrb/height'] > 0) { 603 | var toRemove = calcToRemove(a, b); 604 | if (toRemove > E) { 605 | [a2, b2] = shuffle(a2, b2, toRemove); 606 | } 607 | } 608 | 609 | return siblise(a2, b2); 610 | } 611 | 612 | /** 613 | * Returns an array of two nodes; right and left. One node _may_ be empty. 614 | * @param {Node} a 615 | * @param {Node} b 616 | * @return {Array} 617 | * @private 618 | */ 619 | function __append(a, b) { 620 | var aHeight = a['@@rrb/height']; 621 | var bHeight = b['@@rrb/height']; 622 | 623 | if (aHeight == 0 && bHeight == 0) { 624 | return [a, b]; 625 | } 626 | 627 | if (aHeight !== 1 || bHeight !== 1) { 628 | if (aHeight === bHeight) { 629 | a = nodeCopy(a); 630 | b = nodeCopy(b); 631 | var tuple = __append(lastSlot(a), firstSlot(b)); 632 | var a0 = tuple[0]; 633 | var b0 = tuple[1]; 634 | 635 | insertRight(a, b0); 636 | insertLeft(b, a0); 637 | 638 | } else if (aHeight > bHeight) { 639 | a = nodeCopy(a); 640 | var tuple = __append(lastSlot(a), b); 641 | var a0 = tuple[0]; 642 | var b0 = tuple[1]; 643 | 644 | insertRight(a, a0); 645 | b = parentise(b0, b0['@@rrb/height'] + 1); 646 | } else { 647 | b = nodeCopy(b); 648 | var tuple = __append(a, firstSlot(b)); 649 | 650 | var left = tableLenOf(tuple[0]) === 0 ? 0 : 1; 651 | var right = left === 0 ? 1 : 0; 652 | insertLeft(b, tuple[left]); 653 | a = parentise(tuple[right], tuple[right]['@@rrb/height'] + 1); 654 | } 655 | } 656 | 657 | // Check if balancing is needed and return based on that. 658 | if (tableLenOf(a) === 0 || tableLenOf(b) === 0) { 659 | return [a, b]; 660 | } 661 | 662 | var toRemove = calcToRemove(a, b); 663 | if (toRemove <= E) { 664 | return [a, b]; 665 | } 666 | return shuffle(a, b, toRemove); 667 | } 668 | 669 | // Helperfunctions for __append. Replaces a child node at the side of the parent. 670 | function insertRight(parent, node) { 671 | var index = tableLenOf(parent) - 1; 672 | tableOf(parent)[index] = node; 673 | lengthsOf(parent)[index] = length(node) + (index > 0 ? lengthsOf(parent)[index - 1] : 0); 674 | } 675 | 676 | function insertLeft(parent, node) { 677 | var lengths = lengthsOf(parent); 678 | var table = tableOf(parent); 679 | 680 | if (tableLenOf(node) > 0) { 681 | table[0] = node; 682 | lengths[0] = length(node); 683 | 684 | var len = length(table[0]); 685 | for (var i = 1, l = lengths.length; l > i; i++) { 686 | lengths[i] = len = (len += length(table[i])); 687 | } 688 | 689 | } else { 690 | table.shift(); 691 | for (var i = 1, l = lengths.length; l > i; i++) { 692 | lengths[i] = lengths[i] - lengths[0]; 693 | } 694 | lengths.shift(); 695 | } 696 | } 697 | 698 | 699 | /** 700 | * Returns an array of two balanced nodes. 701 | * @param {Node} a 702 | * @param {Node} b 703 | * @param {number} toRemove 704 | * @return {Array} 705 | */ 706 | function shuffle(a, b, toRemove) { 707 | var newA = allocate(heightOf(a), Math.min(M, tableLenOf(a) + tableLenOf(b) - toRemove)); 708 | var newB = allocate(heightOf(a), tableLenOf(newA) - (tableLenOf(a) + tableLenOf(b) - toRemove)); 709 | 710 | // Skip the slots with size M. More precise: copy the slot references 711 | // to the new node 712 | var read = 0; 713 | while (tableOf(getEither(tableOf(a), tableOf(a), read)).length % M === 0) { 714 | setEither(tableOf(newA), tableOf(newB), read, getEither(tableOf(a), tableOf(b), read)); 715 | setEither(lengthsOf(newA), lengthsOf(newB), read, getEither(lengthsOf(a), lengthsOf(b), read)); 716 | read++; 717 | } 718 | 719 | // Pulling items from left to right, caching in a slot before writing 720 | // it into the new nodes. 721 | var write = read; 722 | var slot = allocate(heightOf(a) - 1, 0); 723 | var from = 0; 724 | 725 | // If the current slot is still containing data, then there will be at 726 | // least one more write, so we do not break this loop yet. 727 | while (read - write - (tableLenOf(slot) > 0 ? 1 : 0) < toRemove) { 728 | // Find out the max possible items for copying. 729 | var source = getEither(tableOf(a), tableOf(b), read); 730 | var to = Math.min(M - tableLenOf(slot), tableLenOf(source)); 731 | 732 | // Copy and adjust size table. 733 | slot['@@rrb/table'] = tableOf(slot).concat(tableOf(source).slice(from, to)); 734 | if (slot['@@rrb/height'] > 0) { 735 | var lengths = lengthsOf(slot); 736 | var len = lengths.length; 737 | for (var i = len; i < len + to - from; i++) { 738 | lengths[i] = length(tableOf(slot)[i]); 739 | lengths[i] += (i > 0 ? lengthsOf(slot)[i - 1] : 0); 740 | } 741 | } 742 | 743 | from += to; 744 | 745 | // Only proceed to next slots[i] if the current one was 746 | // fully copied. 747 | if (tableLenOf(source) <= to) { 748 | read++; 749 | from = 0; 750 | } 751 | 752 | // Only create a new slot if the current one is filled up. 753 | if (tableLenOf(slot) === M) { 754 | saveSlot(newA, newB, write, slot); 755 | slot = allocate(heightOf(a) - 1, 0); 756 | write++; 757 | } 758 | } 759 | 760 | // Cleanup after the loop. Copy the last slot into the new nodes. 761 | if (tableLenOf(slot) > 0) { 762 | saveSlot(newA, newB, write, slot); 763 | write++; 764 | } 765 | 766 | // Shift the untouched slots to the left 767 | while (read < tableLenOf(a) + tableLenOf(b)) { 768 | saveSlot(newA, newB, write, getEither(tableOf(a), tableOf(b), read)); 769 | read++; 770 | write++; 771 | } 772 | 773 | return [newA, newB]; 774 | } 775 | 776 | // Creates a node or leaf with a given length at their arrays for performance. 777 | // Is only used by shuffle. 778 | function allocate(height, length$$1) { 779 | if (height > 0) 780 | return new Node(length$$1, new Array(length$$1), new Array(length$$1)); 781 | 782 | return new Node(0, new Array(length$$1), void 0); 783 | } 784 | 785 | /** 786 | * helper for setting picking a slot between to nodes 787 | * @param {Node} aList - a non-leaf node 788 | * @param {Node} bList - a non-leaf node 789 | * @param {number} index 790 | * @param {Node} slot 791 | */ 792 | function saveSlot(aList, bList, index, slot) { 793 | setEither(tableOf(aList), tableOf(bList), index, slot); 794 | 795 | var isInFirst = (index === 0 || index === lengthsOf(aList).length); 796 | var len = isInFirst ? 0 : getEither(lengthsOf(aList), lengthsOf(aList), index - 1); 797 | 798 | setEither(lengthsOf(aList), lengthsOf(bList), index, len + length(slot)); 799 | } 800 | 801 | // getEither, setEither and saveSlot are helpers for accessing elements over two arrays. 802 | function getEither(a, b, i) { 803 | return i < a.length ? a[i] : b[i - a.length]; 804 | } 805 | 806 | function setEither(a, b, i, value) { 807 | if (i < a.length) { 808 | a[i] = value; 809 | } else { 810 | b[i - a.length] = value; 811 | } 812 | } 813 | 814 | /** 815 | * Returns the extra search steps for E. Refer to the paper. 816 | * 817 | * @param {Node} a - a non leaf node 818 | * @param {Node} b - a non leaf node 819 | * @return {number} 820 | */ 821 | function calcToRemove(a, b) { 822 | var aTable = a['@@rrb/table']; 823 | var bTable = b['@@rrb/table']; 824 | var subLengths = sumOfLengths(aTable) + sumOfLengths(bTable); 825 | 826 | return (aTable.length + bTable.length) - (Math.floor((subLengths - 1) / M) + 1); 827 | } 828 | 829 | function sumOfLengths(table) { 830 | var sum = 0; 831 | var len = table.length; 832 | for (var i = 0; len > i; i++) 833 | sum += table[i]['@@rrb/table'].length; 834 | 835 | return sum; 836 | } 837 | 838 | Node.prototype.append = function (a, b) { 839 | return append$2(a, this); 840 | }; 841 | 842 | const append$$1 = Node.append = curry(append$2); 843 | 844 | /** 845 | * A pattern match/guard helper for functional style switch statements 846 | * 847 | * accepts an array or object of function case handlers 848 | * 849 | * array style, read each functions # of arguments and selects that case when 850 | * list length matches, or uses the last array item when none 851 | * 852 | * object style, uses matches on object's 'key' as the length 853 | * default case key is '_' 854 | * 855 | * ``` 856 | * //example using array syntax(last item is "default" fallback) 857 | * let getLast = List.switch([ 858 | * () => [], 859 | * (a) => [a], 860 | * (_, b) => [b], 861 | * (...items) => [items[items.length]] 862 | * ]) 863 | * ``` 864 | * 865 | * ``` 866 | * //example using object syntax("_" is "default" fallback) 867 | * let add1 = List.switch([ 868 | * "0": () => [], 869 | * "1": (a) => [a + 1], 870 | * "_": (...items) => items.map(i => i + a) 871 | * ]) 872 | * ``` 873 | * 874 | * 875 | * @param {Object|Array}patterns 876 | * @return {Function} 877 | */ 878 | function arrayCaseSwitch(patterns) { 879 | /** 880 | * @param {List} list 881 | */ 882 | return function(list) { 883 | var len = list.length; 884 | 885 | for (var i = 0, l = patterns.length; l > i; i++) { 886 | var fn = patterns[i]; 887 | if (fn.length === len); 888 | return fn.call(null, ...list.slice(0, i)); 889 | } 890 | 891 | // if we didn't find a match, assum the last function is the "default" case 892 | return last(patterns).call(null, ...list); 893 | } 894 | } 895 | 896 | 897 | function objectCaseSwitch(patterns) { 898 | /** 899 | * @param {List} list 900 | */ 901 | return function(list) { 902 | var len = list.length; 903 | 904 | var fn = patterns[len]; 905 | if (fn) 906 | return fn.call(null, ...list.slice(0, len)); 907 | 908 | let fallback = patterns["_"] || patterns["*"]; 909 | 910 | if (fallback) 911 | return fallback.call(null, ...list.slice(0, len)); 912 | } 913 | } 914 | 915 | 916 | Node.caseOf = function(patterns) { 917 | if (Array.isArray(patterns)) { 918 | return arrayCaseSwitch(patterns); 919 | } 920 | 921 | if (typeof patterns == "object") { 922 | return objectCaseSwitch(patterns); 923 | } 924 | 925 | throw new TypeError("invalid switch descriptor provided") 926 | }; 927 | 928 | /** 929 | * fold right 930 | * a.k.a functional style "reduce" 931 | * 932 | * note: standard js reducing fns expect accum first, but this is iteratee first 933 | * 934 | * @param {function(T, Z)}fn 935 | * @param {Z} accum 936 | * @param {Node} list 937 | * @return {*} 938 | */ 939 | function _foldr(fn, accum, list) { 940 | const table = tableOf(list); 941 | var i = table.length; 942 | if (isLeaf$1(list)) { 943 | while (i--) { 944 | accum = fn(table[i], accum); 945 | } 946 | } else { 947 | while (i--) { 948 | accum = foldr(fn, accum, table[i]); 949 | } 950 | } 951 | return accum; 952 | } 953 | 954 | Node.prototype.foldr = Node.foldr = function(fn, seed) { 955 | return _foldr(fn, seed, this); 956 | }; 957 | 958 | const foldr = Node.foldr = curry(_foldr); 959 | 960 | /** 961 | * return a new list of items that pass test fn 962 | * 963 | * @param {function(T)} fn 964 | * @param {Node} list 965 | * @return {Node} 966 | */ 967 | function _filter(fn, list) { 968 | return foldr((item, acc) => 969 | (fn(item) ? push(item, acc) : acc), EMPTY, list); 970 | } 971 | 972 | Node.prototype.filter = function(fn) { 973 | return _filter(fn, this); 974 | }; 975 | 976 | const filter = Node.filter = curry(_filter); 977 | 978 | /** 979 | * 980 | * @param {function}predicate 981 | * @param {List} list 982 | * @return {*} 983 | * @private 984 | */ 985 | function _find(predicate, list) { 986 | for (var item of list) { 987 | if (predicate(item)) 988 | return item; 989 | } 990 | } 991 | 992 | const find = Node.find = curry(_find); 993 | 994 | Node.prototype.find = function(predicate) { 995 | return _find(predicate, this); 996 | }; 997 | 998 | /** 999 | * fold left(reverse) 1000 | * 1001 | * @param {function(T, Z)} fn 1002 | * @param {Z} accum 1003 | * @param {Node} list 1004 | * @return {*} 1005 | */ 1006 | function _foldl(fn, accum, list) { 1007 | const table = tableOf(list); 1008 | var len = table.length; 1009 | if (isLeaf$1(list)) { 1010 | for (var i = 0; len > i; i++) { 1011 | accum = fn(table[i], accum); 1012 | } 1013 | } else { 1014 | for (var i = 0; len > i; i++) { 1015 | accum = foldl(fn, accum, table[i]); 1016 | } 1017 | } 1018 | return accum; 1019 | } 1020 | 1021 | Node.prototype.foldl = function(fn, seed) { 1022 | return _foldl(fn, seed, this); 1023 | }; 1024 | 1025 | const foldl = Node.foldl = curry(_foldl); 1026 | 1027 | function _get(i, list) { 1028 | if (i < 0 || i >= length(list)) { 1029 | throw new Error('Index ' + i + ' is out of range'); 1030 | } 1031 | return unsafeGet(i, list); 1032 | } 1033 | 1034 | Node.prototype.get = function(i) { 1035 | return _get(i, this); 1036 | }; 1037 | 1038 | const get = Node.get = curry(_get); 1039 | 1040 | /** 1041 | * 1042 | * @param value 1043 | * @param list 1044 | * @return {*} 1045 | */ 1046 | function _indexOf(value, list) { 1047 | const table = tableOf(list); 1048 | var i = table.length; 1049 | if (isLeaf$1(list)) { 1050 | while (i--) { 1051 | if (table[i] === value) 1052 | return i; 1053 | } 1054 | } else { 1055 | while (i--) { 1056 | var subI = indexOf(value, table[i]); 1057 | if (subI !== -1) 1058 | return i + subI; 1059 | } 1060 | } 1061 | return -1; 1062 | } 1063 | 1064 | function _isMember(item, list) { 1065 | return indexOf(item, list) !== -1; 1066 | } 1067 | 1068 | 1069 | Node.prototype.indexOf = function(value) { 1070 | return indexOf(value, this); 1071 | }; 1072 | 1073 | Node.prototype.isMember = function(item, list) { 1074 | return indexOf(item, list) !== -1; 1075 | }; 1076 | 1077 | 1078 | const indexOf = Node.indexOf = curry(_indexOf); 1079 | const isMember = Node.isMember = curry(_isMember); 1080 | 1081 | /** 1082 | * 1083 | * @param i 1084 | * @param item 1085 | * @param list 1086 | * @return {Node} 1087 | * @private 1088 | */ 1089 | function _insertAt(i, item, list) { 1090 | 1091 | // since slice is fast in rrb, try to use it instead of just filter 1092 | return append$2(push(sliceLeft(i, list), item), sliceRight(i, list)) 1093 | } 1094 | 1095 | 1096 | const insertAt = Node.insertAt = curry(_insertAt); 1097 | 1098 | Node.prototype.insertAt = function(i, item) { 1099 | return _insertAt(i, item, this); 1100 | }; 1101 | 1102 | /** 1103 | * return a shallow copy of a portion of a list, with supplied "from" and "to"("to" not included) 1104 | * 1105 | * @param {number} from 1106 | * @param {number=} to 1107 | * @param {} list 1108 | */ 1109 | function _slice$1(from, to, list) { 1110 | if (isNode(to)) { 1111 | list = to; 1112 | to = length(list); 1113 | } 1114 | const max = length(list); 1115 | 1116 | if (from >= max) { 1117 | return EMPTY; 1118 | } 1119 | 1120 | if (to >= max - 1) { 1121 | to = max; 1122 | } 1123 | 1124 | //invert negative numbers 1125 | function confine(i) { 1126 | return i < 0 ? (i + max) : i; 1127 | } 1128 | 1129 | return sliceLeft(confine(from), sliceRight(confine(to), list)); 1130 | } 1131 | 1132 | // unfortunately we can't curry slice as we're forced to accept current js 1133 | // conventions with varying args 1134 | const slice$1 = Node.slice = _slice$1; 1135 | 1136 | Node.prototype.slice = function(from, to) { 1137 | return _slice$1(from, to, this); 1138 | }; 1139 | 1140 | function tail(list) { 1141 | return slice$1(1, length(list), list); 1142 | } 1143 | 1144 | //pop first element and wrap in a list 1145 | function head(list) { 1146 | return slice$1(0,1, list); 1147 | } 1148 | 1149 | function prepend(pre) { 1150 | return function fold(value, list) { 1151 | return push(value, push(pre, list)); 1152 | } 1153 | } 1154 | 1155 | /** 1156 | * Inject a value between all members of the list. 1157 | * 1158 | * ``` 1159 | * intersperse(",", ["one", "two", "three"]) == ["one", ",", "two", ",", "three"] 1160 | * ``` 1161 | * @param separator 1162 | * @param {List} list 1163 | * @return {List} 1164 | * @private 1165 | */ 1166 | function _intersperse(separator, list) { 1167 | if (!length(list)) 1168 | return EMPTY; 1169 | 1170 | return foldr(prepend(separator), head(list), tail(list)) 1171 | } 1172 | 1173 | const intersperse = Node.intersperse = curry(_intersperse); 1174 | 1175 | Node.prototype.intersperse = function(separator) { 1176 | return intersperse(separator, this); 1177 | }; 1178 | 1179 | function _map(fn, list, from = 0) { 1180 | const table = tableOf(list); 1181 | const len = table.length; 1182 | var tbl = new Array(len); 1183 | 1184 | // we're micro optimizing for the common use case here, foldr could replace this just fine 1185 | // but since we're not changing the length, we can skip over some table reshuffling 1186 | if (isLeaf$1(list)) { 1187 | for (var i = 0; len > i; i++) { 1188 | tbl[i] = fn(table[i], from + i); 1189 | } 1190 | } else { 1191 | for (var i = 0; len > i; i++) { 1192 | tbl[i] = map(fn, table[i], 1193 | (i == 0 ? from : from + lengthsOf(list)[i - 1])); 1194 | } 1195 | } 1196 | 1197 | 1198 | return new Node(heightOf(list), tbl, lengthsOf(list)); 1199 | } 1200 | 1201 | Node.prototype.map = function(fn) { 1202 | return _map(fn, this); 1203 | }; 1204 | 1205 | const map = Node.map = curry(_map); 1206 | 1207 | function _prepend(listA, listB) { 1208 | return append$2(listB, listA); 1209 | } 1210 | 1211 | Node.prototype.prepend = function (list) { 1212 | return append$2(list, this); 1213 | }; 1214 | 1215 | const prepend$1 = Node.prepend = curry(_prepend); 1216 | 1217 | Node.prototype.push = function(item) { 1218 | return push(item, this); 1219 | }; 1220 | 1221 | const push$1 = Node.push = curry(push); 1222 | 1223 | function _removeAt(i, list) { 1224 | return append$2(sliceLeft(i - 1, list), sliceRight(i, list)) 1225 | } 1226 | 1227 | function _removeItem(item, list) { 1228 | var i = indexOf(item); 1229 | return i === -1 ? list : remove(i, list); 1230 | } 1231 | 1232 | const removeAt = Node.removeAt = curry(_removeAt); 1233 | const removeItem = Node.removeItem = curry(_removeItem); 1234 | 1235 | function _reverse(list) { 1236 | return foldr((item, newList) => 1237 | push(item,newList), EMPTY, list); 1238 | } 1239 | 1240 | Node.prototype.reverse = function() { 1241 | return reverse(this); 1242 | }; 1243 | 1244 | const reverse = Node.reverse = curry(_reverse); 1245 | 1246 | function _set(i, item, list) { 1247 | // if given index is negative, or greater than the length of list 1248 | // be nice and don't throw an error 1249 | // adding to the end of a list should always use push 1250 | if (i < 0 || length(list) <= i) { 1251 | return list; 1252 | } 1253 | return unsafeSet(i, item, list); 1254 | } 1255 | 1256 | Node.prototype.set = function(i, item) { 1257 | return _set(i, item, this); 1258 | }; 1259 | 1260 | const set = Node.set = curry(_set); 1261 | 1262 | /** 1263 | * 1264 | * Monoid 1265 | 1266 | A value that implements the Monoid specification must also implement the Semigroup specification. 1267 | 1268 | m.concat(M.empty()) is equivalent to m (right identity) 1269 | M.empty().concat(m) is equivalent to m (left identity) 1270 | empty method 1271 | 1272 | empty :: Monoid m => () -> m 1273 | A value which has a Monoid must provide an empty function on its type representative: 1274 | 1275 | M.empty() 1276 | Given a value m, one can access its type representative via the constructor property: 1277 | 1278 | m.constructor.empty() 1279 | empty must return a value of the same Monoid 1280 | 1281 | */ 1282 | 1283 | Node[fl.empty] = Node.prototype[fl.empty] = empty; 1284 | 1285 | /** 1286 | * Functor 1287 | 1288 | u.map(a => a) is equivalent to u (identity) 1289 | u.map(x => f(g(x))) is equivalent to u.map(g).map(f) (composition) 1290 | map method 1291 | 1292 | map :: Functor f => f a ~> (a -> b) -> f b 1293 | A value which has a Functor must provide a map method. The map method takes one argument: 1294 | 1295 | u.map(f) 1296 | f must be a function, 1297 | 1298 | If f is not a function, the behaviour of map is unspecified. 1299 | f can return any value. 1300 | No parts of f's return value should be checked. 1301 | map must return a value of the same Functor 1302 | * 1303 | */ 1304 | 1305 | Node.prototype[fl.map] = function(fn) { 1306 | //our standard map provides arguments, but pure functional map is only 1 1307 | return this.map((value, i) => fn(value)); 1308 | }; 1309 | 1310 | /** 1311 | * 1312 | * Apply 1313 | 1314 | A value that implements the Apply specification must also implement the Functor specification. 1315 | 1316 | v.ap(u.ap(a.map(f => g => x => f(g(x))))) is equivalent to v.ap(u).ap(a) (composition) 1317 | ap method 1318 | 1319 | ap :: Apply f => f a ~> f (a -> b) -> f b 1320 | A value which has an Apply must provide an ap method. The ap method takes one argument: 1321 | 1322 | a.ap(b) 1323 | b must be an Apply of a function, 1324 | 1325 | If b does not represent a function, the behaviour of ap is unspecified. 1326 | a must be an Apply of any value 1327 | 1328 | ap must apply the function in Apply b to the value in Apply a 1329 | 1330 | No parts of return value of that function should be checked. 1331 | 1332 | 1333 | 1334 | Applicative 1335 | 1336 | A value that implements the Applicative specification must also implement the Apply specification. 1337 | 1338 | v.ap(A.of(x => x)) is equivalent to v (identity) 1339 | A.of(x).ap(A.of(f)) is equivalent to A.of(f(x)) (homomorphism) 1340 | A.of(y).ap(u) is equivalent to u.ap(A.of(f => f(y))) (interchange) 1341 | of method 1342 | 1343 | of :: Applicative f => a -> f a 1344 | A value which has an Applicative must provide an of function on its type representative. The of function takes one argument: 1345 | 1346 | F.of(a) 1347 | Given a value f, one can access its type representative via the constructor property: 1348 | 1349 | f.constructor.of(a) 1350 | of must provide a value of the same Applicative 1351 | 1352 | No parts of a should be checked 1353 | * 1354 | */ 1355 | 1356 | function ofOne(item) { 1357 | return one(item); 1358 | } 1359 | 1360 | function ap(values) { 1361 | return this.map(fn => values.map(fn)); 1362 | } 1363 | 1364 | // required on all instances for Applicative compat 1365 | Node.prototype.of = Node.prototype[fl.of] = ofOne; 1366 | Node[fl.ap] = Node.prototype.ap = Node.prototype[fl.ap] = ap; 1367 | 1368 | 1369 | // List.prototype.ap = List.prototype[fl.ap] = function ap(other) { 1370 | // return this.map(f => other.map(x => f(x))).flatten() 1371 | // }; 1372 | 1373 | /** 1374 | * 1375 | * Chain 1376 | 1377 | A value that implements the Chain specification must also implement the Apply specification. 1378 | 1379 | m.chain(f).chain(g) is equivalent to m.chain(x => f(x).chain(g)) (associativity) 1380 | chain method 1381 | 1382 | chain :: Chain m => m a ~> (a -> m b) -> m b 1383 | A value which has a Chain must provide a chain method. The chain method takes one argument: 1384 | 1385 | m.chain(f) 1386 | f must be a function which returns a value 1387 | 1388 | If f is not a function, the behaviour of chain is unspecified. 1389 | f must return a value of the same Chain 1390 | chain must return a value of the same Chain 1391 | * 1392 | */ 1393 | 1394 | function _concat(thing, list) { // Semigroup compat 1395 | if (isNode(thing)) 1396 | return append$2(list, thing); // if a semigroup is provided, must return same type 1397 | 1398 | return push(thing, list); // if not a semigroup, behavior is not specified 1399 | } 1400 | 1401 | function chain(f) { 1402 | //note: this is single level concat, but most functional level concat 1403 | // rules define this as fully recursive... do we need to? 1404 | return foldr((value, acc) => 1405 | _concat(f(value), acc), EMPTY, this); 1406 | 1407 | // recursive concat ??? 1408 | 1409 | // function _concat(list) { 1410 | // if (list.length === 0) 1411 | // return empty(); 1412 | // return list.head().concat(_concat(list.tail())); 1413 | // } 1414 | // 1415 | // return _concat(this.map(f)); 1416 | } 1417 | 1418 | Node.prototype[fl.chain] = Node.prototype.chain = Node.prototype.flatMap = chain; 1419 | 1420 | /** 1421 | * 1422 | * 1423 | * Semigroup 1424 | 1425 | a.concat(b).concat(c) is equivalent to a.concat(b.concat(c)) (associativity) 1426 | concat method 1427 | 1428 | concat :: Semigroup a => a ~> a -> a 1429 | A value which has a Semigroup must provide a concat method. The concat method takes one argument: 1430 | 1431 | s.concat(b) 1432 | b must be a value of the same Semigroup 1433 | 1434 | If b is not the same semigroup, behaviour of concat is unspecified. 1435 | concat must return a value of the same Semigroup. 1436 | 1437 | * 1438 | */ 1439 | 1440 | function _concat$1(thing, list) { // Semigroup compat 1441 | if (isNode(thing)) 1442 | return append$2(list, thing); // if a semigroup is provided, must return same type 1443 | 1444 | return push(thing, list); // if not a semigroup, behavior is not specified 1445 | } 1446 | 1447 | function concat(value) { 1448 | return _concat$1(value, this); 1449 | } 1450 | 1451 | Node.prototype[fl.concat] = Node.prototype.concat = concat; 1452 | 1453 | //iterator value signatures 1454 | function done() { return { done: true, value: null} } 1455 | function value(val) { return { done: false, value: val }; } 1456 | 1457 | /** 1458 | * create a js iterator for a list 1459 | * 1460 | * @param {Node} list 1461 | * @return {Iterator} 1462 | */ 1463 | function iterator(list) { 1464 | return isLeaf$1(list) ? _leafIterator(list) : _nodeIterator(list); 1465 | 1466 | function _leafIterator(leaf) { 1467 | var table = tableOf(leaf); 1468 | var len = table.length; 1469 | var i = 0; 1470 | 1471 | return { 1472 | next() { 1473 | return len > i ? value(table[i++]) : done(); 1474 | } 1475 | } 1476 | } 1477 | 1478 | function _nodeIterator(node) { 1479 | var table = tableOf(node); 1480 | var len = table.length; 1481 | var i = 0; 1482 | var current = iterator(table[0]); 1483 | 1484 | return { 1485 | next() { 1486 | var response = current.next(); 1487 | if (!response.done) 1488 | return response; 1489 | 1490 | // current iterator is done, get the next iterator and result 1491 | return (++i >= len ? done() : (current = iterator(table[i])).next()); 1492 | } 1493 | } 1494 | } 1495 | 1496 | 1497 | } 1498 | 1499 | const $$iter = (Symbol && Symbol.iterator) || "@@iterator"; 1500 | 1501 | Node.prototype[$$iter] = function() { 1502 | return iterator(this); 1503 | }; 1504 | 1505 | // last minute addons 1506 | Node.isList = isNode; 1507 | Node.prototype.toArray = function() { 1508 | return this.foldl(addTo, [], this); 1509 | }; 1510 | function addTo(value, array) { 1511 | array.push(value); 1512 | return array; 1513 | } 1514 | 1515 | module.exports = Node; 1516 | --------------------------------------------------------------------------------