UNPKG

useless

Version:

Use Less. Do More. JavaScript on steroids.

845 lines (592 loc) 37.4 kB
"use strict"; const _ = require ('underscore') const O = require ('es7-object-polyfill') const Meta = require ('meta-fields') _.hasStdlib = true /* _.throwsError ======================================================================== */ _.withTest (['stdlib', 'throwsError'], function () { $assertThrows (_.throws ('foo'), 'foo') $assertThrows (_.throwsError ( 'неуловимый Джо'), _.matches ({ message: 'неуловимый Джо' })) $assertThrows (_.throwsError (new Error ('неуловимый Джо')), _.matches ({ message: 'неуловимый Джо' })) }, function () { _.throwsError = _.higherOrder ( _.throwError = function (msg) { throw (msg instanceof Error) ? msg : new Error (msg) }) _.throws = _.higherOrder ( _.throw = function (msg) { throw msg }) _.overrideThis = _.throwsError ('override this') _.notImplemented = _.throwsError ('not implemented') }) /* Abstract _.values ======================================================================== */ _.withTest (['stdlib', 'values2'], function () { $assert (_.values2 (undefined), []) $assert (_.values2 (_.identity), [_.identity]) $assert (_.values2 ('foo'), ['foo']) $assert (_.values2 (['foo', 'bar']), ['foo', 'bar']) $assert (_.values2 ({ f: 'foo', b: 'bar' }), ['foo', 'bar']) }, function () { _.mixin ({ values2: function (x) { if (_.isArrayLike (x)) { return x } else if (_.isStrictlyObject (x)) { return _.values (x) } else if (_.isEmpty (x)) { return [] } else { return [x] } } }) }) /* Map 2.0 ======================================================================== */ /* Semantically-correct abstract map (maps any type of value) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ _.withTest (['stdlib', 'map2'], function () { var plusBar = _.appends ('bar') $assert (_.map2 ( 'foo', plusBar), 'foobar' ) $assert (_.map2 ([ 'foo'], plusBar), [ 'foobar' ]) $assert (_.map2 ({ foo: 'foo' }, plusBar), { foo: 'foobar' }) $assert (Array.from (_.map2 (new Set (['foo', 'bar']), plusBar).values ()), ['foobar', 'barbar']) /* With flipped order of arguments (callback first) */ $assert (_.mapWith (plusBar, { foo: 'foo' }), { foo: 'foobar' }) }, function () { _.mixin ({ map2: function (value, fn, context) { return ( _.isArrayLike (value) ? _.map (value, fn, context) : ( (value instanceof Set) ? _.mapSet (value, fn, context) : ( _.isStrictlyObject (value) ? _.mapValues (value, fn, context) : fn.call (context, value)))) } }) _.mapSet = function (set, fn, ctx) { var out = new Set () for (var x of set) { out.add (fn.call (ctx, x)) } return out } _.mapsWith = _.higherOrder ( _.mapWith = _.flip2 (_.map2)) }) /* Pluck 2.0 ======================================================================== */ _.pluck2 = function (x, prop) { return _.map2 (x, _.property (prop)) } /* Maps one-to-many ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ _.withTest (['stdlib', 'scatter/obj/arr'], function () { $assert (undefined, _.scatter ([], _.noop)) $assert ([1,10, 2,20, 3,30], _.scatter ([1,2,3], function (x, i, return_) { return_ (x); return_ (x * 10) })) $assert ({ 'b': 0, 'a': 1, 'r': 2 }, _.scatter ('bar', function (x, i, return_) { _.each (x.split (''), _.flip (return_)) })) $assert (_.obj (_.noop), _.arr (_.noop), undefined) $assert (_.obj (function (emit) { emit (42, 'foo') emit (43, 'bar') }), { foo: 42, bar: 43 }) $assert (_.arr (function (emit) { emit (42) emit (43, 44) }), [42, [43, 44]]) }, function () { _.mixin ({ scatter: function (obj, elem) { var result = undefined _.map2 (obj, function (x, i) { elem (x, i, function (v, k) { if (arguments.length < 2) { (result = result || []).push (v) } else { (result = result || {})[k] = v } }) }); return result } }) _.obj = function (emitItems) { var x = undefined; emitItems (function (v, k) { (x = x || {})[k] = v }) return x } _.arr = function (emitItems) { var x = undefined; emitItems (function (v ) { (x = x || []).push ((arguments.length < 2) ? v : _.asArray (arguments)) }) return x } }) /* Maps keys (instead of values) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ _.withTest (['stdlib', 'mapKeys'], function () { $assert (_.mapKeys ({ 'foo': [1, 2,{ 'gay': 3 }]}, _.appends ('bar')), { 'foobar': [1, 2,{ 'gaybar': 3 }]}) }, function () { _.mapKeys = function (x, fn) { if (_.isArrayLike (x)) { return _.map (x, _.tails2 (_.mapKeys, fn)) } else if (_.isStrictlyObject (x)) { return _.fromPairs (_.map (O.entries (x), function (kv) { return [fn (kv[0]), _.mapKeys (kv[1], fn)] })) } else { return x } } }) /* Hyper map (deep) #1 — maps leafs ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ _.withTest (['stdlib', 'mapMap'], function () { $assert (_.mapMap ( 7, _.typeOf), 'number') // degenerate cases $assert (_.mapMap ([7], _.typeOf), ['number']) $assert (_.mapMap ( { foo: 7, bar: ['foo', { bar: undefined } ] }, _.typeOf), { foo: 'number', bar: ['string', { bar: 'undefined' } ] }) }, function () { _.mapMap = _.hyperOperator (_.unary, _.map2) }) /* Hyper map (deep) #2 — maps branches & leafs ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ _.withTest (['stdlib', 'hyperMap'], function () { var complexObject = { garply: { bar: { baz: 5 } }, frobni: { foo: [{ bar: { baz: 5 } }] } } /* ----------------- */ var barBazSubstructure = _.matches ({ bar: { baz: 5 } }) var transformedObject = _.hyperMap (complexObject, function (x) { if (barBazSubstructure (x)) { return 'pwned!' } }) $assert (transformedObject, { garply: 'pwned!', frobni: { foo: ['pwned!'] } }) }, function () { _.hyperMap = function (data, op) { return _.hyperOperator (_.unary, function (expr, f) { return op (expr) || _.map2 (expr, f) }) (data, _.identity) } }) /* Abstract _.pairs ======================================================================== */ _.withTest (['stdlib', 'pairs2'], function () { $assert (_.pairs2 (undefined), [[undefined, undefined]]) // TODO: unify semantics with _.values2 $assert (_.pairs2 (_.identity), [[undefined, _.identity]]) $assert (_.pairs2 ('foo'), [[undefined, 'foo']]) $assert (_.pairs2 (['foo', 'bar']), [[ 0, 'foo'], [ 1, 'bar']]) $assert (_.pairs2 ({ 0: 'foo', 1: 'bar' }), [['0', 'foo'], ['1', 'bar']]) }, function () { _.pairs2 = function (x) { return _.scatter (x, function (x, i, return_) { return_ ([i, x]) }) } }) /* Filter 2.0 ======================================================================== */ _.withTest (['stdlib', 'filter 2.0'], function () { var foo = _.equals ('foo') // generic filter behavior for any container type $assert (_.filter2 ( 'foo' , foo), 'foo' ) $assert (_.filter2 ([ 'foo' ], foo), [ 'foo' ]) $assert (_.filter2 ({ f: 'foo' }, foo), { f: 'foo' }) $assert (_.filter2 ( 'foo' , _.not (foo)), undefined) $assert (_.filter2 ([ 'foo' ], _.not (foo)), []) $assert (_.filter2 ({ f: 'foo' }, _.not (foo)), {}) // map behavior, if predicate returns not boolean (mixed-behavior test not needed - although its the expected case of use) $assert (_.filter2 ( 'foo' , _.constant ('bar')), 'bar' ) $assert (_.filter2 ([ 'foo' ], _.constant ('bar')), [ 'bar' ]) $assert (_.filter2 ({ f: 'foo' }, _.constant ('bar')), { f: 'bar' }) // hyper-filter #1 (works on leafs) $assert (_.filterFilter ( { foo: 'foo', bar: [7, 'foo', { bar: 'foo' }] }, _.not (_.equals ('foo'))), { bar: [7, { }] }) // there was a bug $assert (_.hyperFilter ({ foo: /regexp/ }, _.constant (true)).foo instanceof RegExp) }, function () { _.reject2 = function (value, op) { return _.filter2 (value, _.not (op)) } _.filter2 = function (value, op) { if (_.isArrayLike (value)) { var result = [] for (var i = 0, n = value.length; i < n; i++) { var v = value[i], opSays = op (v, i) if (opSays === true) { result.push (v) } else if (opSays !== false) { result.push (opSays) } } return result } else if (_.isStrictlyObject (value)) { var result = {} _.each (Object.keys (value), function (key) { var v = value[key], opSays = op (v, key) if (opSays === true) { result[key] = v } else if (opSays !== false) { result[key] = opSays } }); return result } else { var opSays = op (value) if (opSays === true) { return value } else if (opSays !== false) { return opSays } else { return undefined } } } _.filterFilter = _.hyperOperator (_.unary, _.filter2) _.hyperFilter = function (data, op) { return _.hyperOperator (_.unary, function ( expr, f) { var x = op (expr) return ((x === true) && _.filter2 (expr, f)) || x }) (data, _.identity) } _.hyperReject = function (data, op) { return _.hyperFilter (data, function (x) { var opa = op (x) return _.isBoolean (opa) ? !opa : opa }) } }) /* ------------------------------------------------------------------------ */ _.withTest (['stdlib', 'each 2.0'], function () { var test = function (input) { var output = [] _.each2 (input, function ( x, i, n) { output.push ( [ x, i, n]) }); return output } $assert (test ( 'foo'), [['foo', undefined, 1]]) $assert (test (['foo', 'bar']), [['foo', 0, 2], ['bar', 1, 2]]) $assert (test ({ 'f': 'oo', 'b': 'ar' }), [[ 'oo', 'f', 2], [ 'ar', 'b', 2]]) }, function () { _.each2 = function (x, f) { if ( _.isArrayLike (x)) { for (var i = 0, n = x.length; i < n; i++) f (x[ i ], i, n) } // @hide else if (_.isStrictlyObject (x)) { var k = Object.keys (x); for (var ki, i = 0, n = k.length; i < n; i++) f (x[ki = k[i]], ki, n) } else { f (x, undefined, 1) } } }) /* Reduce on steroids ======================================================================== */ _.withTest (['stdlib', 'reduce 2.0'], function () { $assert (_.reduce2 ( 3, [7, 9 ], _.sum), 19) $assert (_.reduce2 ([ 3, 7, 9 ], _.sum), 19) $assert (_.reduce2 ({ a: 3, b: 7, c: 9 }, _.sum), 19) $assert (_.reduce2 ( 3 + 7 + 9 , _.sum), 19) $assert (_.reduce2 ([1], _.sum), 1) $assert (_.reduce2 ([], _.sum), undefined) $assert ( 1 + 20 + 3 + 4 + 5, _.reduceReduce ([[[1], 20],[3,[ 4, 5]]], function (a, b) { return (_.isNumber (a) && _.isNumber (b)) ? (a + b) : b })) }, function () { /* Because hyperOperator is fractal thing, it is nessesary to define a compatible argument order for _.reduce and its functor operand, as they get melted together to form a generic self-similar routine of a higher order. And that becames kinda "Yodish" when applied to familiar 'reduce'. See how they dont match: 1. _.reduce (value, op, memo) 2. op (memo, value) */ _.reduce2 = function ( _1, _2, _3) { var no_left = arguments.length < 3 var left = _1, rights = _2, op = _3 if (no_left) { left = undefined; rights = _1; op = _2 } _.each2 (rights, function (right, i) { left = no_left ? right : op (left, right); no_left = false }); return left } // @hide _.reduceReduce = function (_1, _2, _3) { var initial = _1, value = _2, op = _3 if (arguments.length < 3) { initial = {}; value = _1; op = _2 } return _.hyperOperator (_.binary, _.reduce2, _.goDeeperAlwaysIfPossible) (initial, value, op) } }) /* Abstract concat ======================================================================== */ _.withTest (['stdlib', 'concat2'], function () { $assert (_.concat ([1,2], [3], [4,5]), [1,2,3,4,5]) $assert (_.concat ( { foo: 1 }, { bar: 2 }), { foo: 1, bar: 2 }) $assert (_.concat ([{ foo: 1 }, { bar: 2 }]), { foo: 1, bar: 2 }) $assert (_.concat (1,2,3), 6) }, function () { _.concat = function (a, b) { var first, rest if (arguments.length === 1) { first = a[0]; rest = a.slice (1) } else { first = a; rest = [].slice.call (arguments, 1) } return _.isArrayLike (first) ? first.concat.apply (first, rest) : _.reduce2 (first, rest, function ( a, b) { if (_.isObject ( a) && _.isObject ( b)) { return _.extend ({}, a, b) } else { return a + b } }) } }) /* Zip 2.0 ======================================================================== */ /* Abstract zip that reduces any types of matrices. ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ _.deferTest (['stdlib', 'zip2'], function () { $assert (_.zip2 ([ 'f', 'o', 'o'], _.concat), 'foo') $assert (_.zip2 ([ ['f', 'b'], ['o', 'a'], ['o', 'r']], _.concat), ['foo', 'bar']) $assert (_.zip2 ([ { foo:'f', bar:'b' }, { foo:'o', bar:'a' }, { foo:'o', bar:'r' }], _.concat), { foo: 'foo', bar: 'bar' }) $assert (_.zip2 ( { foo:'f', bar:'b' }, // passing rows as arguments { foo:'o', bar:'a' }, { foo:'o', bar:'r' }, _.concat), { foo: 'foo', bar: 'bar' }) $assert (_.zip2 (undefined, _.concat), undefined) // degenerate cases $assert (_.zip2 (5, _.concat), 5) $assert (_.zip2 ([], _.concat), []) $assert (_.zip2 (['foo'], _.concat), 'foo') $assert (_.zipObjectsWith ([ { name: 'string' }, { born: 123 }], _.array), { name: ['string', undefined], born: [undefined, 123] }) $assert ([3], _.zipSetsWith ([ new Set ([2,3]), new Set ([3,4])], function (a, b) { return a && b }).asArray) }, function () { _.mixin ({ zipSetsWith: function (sets, fn) { return _.reduce (sets.slice (1), function (memo, obj) { _.each (_.union (( obj && Array.from ( obj.values ())) || [], (memo && Array.from (memo.values ())) || []), function (k) { var zipped = fn ((memo && memo.has (k)) ? k : undefined, (obj && obj.has (k)) ? k : undefined) if (zipped === undefined) { memo.delete (k) } else { memo.add (zipped) } }); return memo }, new Set (sets[0])) }, zipObjectsWith: function (objects, fn) { return _.reduce (objects.slice (1), function (memo, obj) { _.each (_.union (_.keys (obj), _.keys (memo)), function (k) { var zipped = fn (memo && memo[k], obj && obj[k]) if (zipped === undefined) { delete memo[k] } else { memo[k] = zipped } }); return memo }, _.clone (objects[0])) }, zip2: function (rows_, fn_) { var rows = arguments.length === 2 ? rows_ : _.initial (arguments) var fn = arguments.length === 2 ? fn_ : _.last (arguments) if (!_.isArrayLike (rows) || rows.length === 0) { return rows } else { if (_.isArrayLike (rows[0])) { return _.zipWith (rows, fn) } else if (rows[0] instanceof Set) { return _.zipSetsWith (rows, fn) } else if (_.isStrictlyObject (rows[0])) { return _.zipObjectsWith (rows, fn) } else { return _.reduce2 (rows, fn) } } } }) }) /* Hyperzip (deep one). ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ _.withTest (['stdlib', 'zipZip'], function () { $assert (_.zipZip ( { phones: [{ number: 'number' }] }, { phones: [{ number: 333 }] }, _.array), { phones: [{ number: ['number', 333] }] }) $assert (_.zipZip ([{ foo: 7, bar: ['foo', { bar: undefined } ] }, { foo: 'number', bar: ['string', { bar: 'undefined' } ] } ], _.array), { foo: [7, 'number'], bar: [['foo', 'string'], { bar: [undefined, 'undefined'] } ] }) }, function () { _.mixin ({ zipZip: _.hyperOperator (_.binary, _.zip2) }) }) /* Most useful _.extend derivatives ======================================================================== */ _.withTest (['stdlib', 'extend 2.0'], function () { /* Inverted version of _.extend, for humanized narration where it makes sense (not here, but see AOP impl for example of such one) */ [(function () { var input = { foo:1, bar:1 } var plus = { foo:42, qux:1 } var gives = { foo:42, qux:1, bar: 1 } $assert (_.extendWith (plus, input), gives) }) (), /* Higher-order version of _.extend, allows to use it as _.map operator, which cuts shit in typical arrays-of-objects crunching routines */ (function () { var input = [{ bar:1 }, {}] var plus = _.extendsWith ({ foo:42 }) var gives = [{ bar:1, foo:42 }, { foo:42 }] $assert (_.map (input, _.arity1 (plus)), gives) }) (), /* NOW DEPRECATED, USE _.extendedDeep */ (function () { var input = { foo:1, bar: { qux:1 } } var plus = { foo:42, bar: { baz:1 } } var gives = { foo:42, bar: { baz:1, qux:1 }} $assert (_.extend2 (input, plus), gives) }) (), /* Deep version of _.extend, allowing to extend arbitrary levels deep (referentially transparent, so _.extendedDeep instead of _.extendDeep) */ (function () { var input = { foo:1, bar: { qux:1 } } var plus = { foo:42, bar: { baz:1 } } var gives = { foo:42, bar: { baz:1, qux:1 }} $assert (_.extendedDeep (input, plus), gives) $assert (_.extendedDeep ({ foo: new Set ([7]) }, {}).foo instanceof Set) $assert (Array.from (_.extendedDeep ({ foo: new Set ([1,2]) }, { foo: new Set ([2,3]) }).foo.values ()), [1,2,3]) $assert (_.extendedDeep ({ foo: 4 }, { bar: 5 }, { qux: 6 }), { foo: 4, bar: 5, qux: 6 }) // >2 arguments $assert (_.extendedDeep ({ foo: ['a', 'b'] }, { foo: ['c'] }), { foo: ['c'] }) // default array merge semantics (replace) $assert (_.extendedDeep ({ foo: ['a', 'b'] }, { foo: $concat (['c']) }), { foo: ['a', 'b', 'c'] }) // optional merge semantics (concats arrays) }) (), /* Referentially-transparent version (to be used in functional expressions) */ (function () { var x = { foo: 1 } $assert (_.extended (x, { bar: 1 }), { foo: 1, bar: 1 }) $assert ( x, { foo: 1 }) }) () ] }, function () { _.extend = $restArg (_.extend) // Mark as having rest argument (to make _.flip work on that shit) _.extended = $restArg (function () { return _.extend.apply (this, [{}].concat (_.asArray (arguments))) }) // referentially-transparent version _.extendWith = _.flip (_.extend) _.extendsWith = _.flip (_.partial (_.partial, _.flip (_.extend))) // higher order shit Meta.globalTag ('concat') _.extendedDeep = (...args) => _.reduce (args, (a, b) => _.extendedDeep.zipZip (a, b, ($a, $b) => { const a = $untag ($a), b = $untag ($b) return (b === undefined) ? a : (_.isArray (a) && $concat.is ($b)) ? a.concat (b) : b })) _.extendedDeep.zipZip = _.hyperOperator (_.binary, _.zip2, _.goDeeperWhenFirstArgumentIsGood, x => !_.isArray (x) && _.isNonTrivial (x)) _.extend2 = $restArg (function (what) { return _.extend (what, _.reduceRight (arguments, function (right, left) { return _.fromPairs (_.map (_.union (_.keys (left), _.keys (right)), function (key) { var lvalue = left[key] return [key, (key in right) ? (typeof lvalue === 'object' ? _.extend (lvalue, right[key]) : right[key]) : lvalue] }))}, {})) }) }) /* Find 2.0 + Hyperfind ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ _.withTest (['stdlib', 'findFind'], function () { var obj = { x: 1, y: { z: 2 }} $assert (_.findFind ({ foo: 1, bar: [1,2,3] }, _.constant (false)), false) $assert (_.findFind ({ foo: 1, bar: [1,2,3] }, _.equals (2)), 2) $assert (_.findFind ({ foo: { bar: obj } }, _.equals (obj)), obj) }, function () { _.find2 = function (value, pred) { if (_.isArrayLike (value)) { for (var i = 0, n = value.length; i < n; i++) { var x = pred (value[i], i, value) if (typeof x !== 'boolean') { return x } else if (x === true) { return value[i] } } } else if (_.isStrictlyObject (value)) { for (var i = 0, ks = Object.keys (value), n = ks.length; i < n; i++) { var k = ks[i]; var x = pred (value[k], k, value) if (typeof x !== 'boolean') { return x } else if (x === true) { return value[k] } } } } _.findFind = function (obj, pred_) { return _.hyperOperator (_.unary, function (value, pred) { if (_.isArrayLike (value)) { for (var i = 0, n = value.length; i < n; i++) { var x = pred (value[i]) if (typeof x !== 'boolean') { return x } else if (x === true) { return value[i] } } } else if (_.isStrictlyObject (value)) { for (var i = 0, ks = Object.keys (value), n = ks.length; i < n; i++) { var k = ks[i]; var x = pred (value[k]) if (typeof x !== 'boolean') { return x } else if (x === true) { return value[k] } } } var x = pred_ (value) if (typeof x !== 'boolean') { return x } else if (x === true) { return value } return false }) (obj, pred_) } }) /* removes empty contents from any kinds of objects ======================================================================== */ _.withTest (['stdlib', 'nonempty'], function () { var obj = { blank: {}, empty: [], one: 1, none: undefined, nil: null, clear: '', zero: 0, no: false } var arr = [{}, [], 1, undefined, null, '', 0, false] $assert (_.nonempty (obj), { one: 1, zero: 0, no: false }) $assert (_.nonempty (arr), [1, 0, false]) $assert (_.nonempty (null), undefined) $assert (_.nonempty (''), undefined) }, function () { _.nonempty = function (obj) { return _.filter2 (obj, _.isNonempty) } }) /* deep cloning of objects (as _.clone is shallow) ======================================================================== */ _.deferTest (['stdlib', 'cloneDeep'], function () { var Proto = $prototype ({}) var obj = { a: [{ b: { c: 'd' } }], b: {}, c: new Proto (), e: new Date () } var copy = _.cloneDeep (obj) $assert (obj !== copy) // should be distinct references $assert (obj.a !== copy.a) // $assert (obj.b !== copy.b) // $assert (obj.c === copy.c) // should be same instance (should consider prototype instances as atomic value) $assert (obj.e === copy.e) // Date should not be cloned $assert (obj, copy) // structure should not change $assert ( _.cloneDeep ({ foo: new Set () }).foo instanceof Set) $assert (Array.from (_.cloneDeep ({ foo: new Set ([1,2,3]) }).foo.values ()), [1,2,3]) }, function () { _.extend (_, { clone: function (x) { return (x instanceof Set) ? new Set (x) : ((x instanceof Date) ? x : (!_.isObject (x) ? x : (_.isArray (x) ? x.slice () : _.extend ({}, x)))) }, cloneDeep: _.tails2 (_.mapMap, function (value) { return ( _.isStrictlyObject (value) && !_.isPrototypeInstance (value)) ? _.clone (value) : value }) }) }) /* given objects A and B, _.diff subtracts A's structure from B, and returns difference in terms of B ======================================================================== */ _.deferTest (['stdlib', 'diff'], function () { $assert (_.diff ('foo', 'foo'), undefined) $assert (_.diff ('foo', 'bar'), 'bar') $assert (_.diff ({ a: 1, b: 2, c: 3 }, { a: 1, b: 3, d: 4 }), { b: 3, d: 4 }) $assert (_.diff ([1,2,3], [1,2,3]), undefined) $assert (_.diff ([1,'foo',2], [1,2,3]), [2,3]) }, function () { _.hyperMatch = _.hyperOperator (_.binary, function (a, b, pred) { return _.coerceToUndefined (_.nonempty (_.zip2 (a, b, pred))) }) _.diff = _.tails3 (_.hyperMatch, function (a, b) { return ((($atom.unwrap (a) === $atom.unwrap (b)) || (a === $any) || (b === $any)) ? undefined : b) }) }) /* inverse of _.diff (returns similarities) ======================================================================== */ _.deferTest (['stdlib', 'undiff'], function () { $assert (_.undiff ('foo', 'foo'), 'foo') $assert (_.undiff ('foo', 'bar'), undefined) $assert (_.undiff ({ a: 1, b: 2, c: 3 }, { a: 1, b: 3, d: 4 }), { a: 1 }) $assert (_.undiff ([1,2,3], [1,2,3]), [1,2,3]) $assert (_.undiff ([1,2], [1,3]), [1,undefined]) $assert (_.undiff ([1,2], [0,2]), [undefined,2]) }, function () { _.hyperMatch = _.hyperOperator (_.binary, function (a, b, pred) { return _.coerceToUndefined (_.zip2 (a, b, pred)) }) _.undiff = _.tails3 (_.hyperMatch, function (a, b) { return (($atom.unwrap (a) === $atom.unwrap (b)) || (a === $any) || (b === $any)) ? b : undefined }) }) /* Makes { foo: true, bar: true } from ['foo', 'bar'] ======================================================================== */ _.withTest (['stdlib', 'index'], function () { $assert (_.index (['foo', 'bar']), { foo: true, bar: true }) }, function () { _.extend (_, { index: function (list) { var result = {} for (var i = 0, n = list.length; i < n; i++) { result[list[i]] = true } return result } }) }) /* For string wrapping ======================================================================== */ _.withTest (['stdlib', 'quote'], function () { $assert (_.quote ('qux'), 'qux') $assert (_.quote ('qux', '[]'), '[qux]') $assert (_.quote ('qux', '/'), '/qux/') $assert (_.quote ('qux', '{ }'), '{ qux }') $assert (_.quote ('qux', '</>'), '</qux>') $assert (_.quoteWith ('[]', 'qux'), '[qux]') }, function () { _.quote = function (s, pattern_) { var pattern = pattern_ || '' var splitAt = Math.floor (pattern.length / 2 + (pattern.length % 2)) var before = pattern.slice (0, splitAt) var after = pattern.slice (splitAt) || before return before + s + after } _.quoteWith = _.flip2 (_.quote) _.quotesWith = _.higherOrder (_.quoteWith) }) /* _.partition 2.0 ======================================================================== */ _.withTest (['stdlib', 'partition2'], function () { $assert (_.partition2 ( [ 'a', 'b', 'c', undefined, undefined, 42], _.isNonempty), [['a', 'b', 'c'], [undefined, undefined], [42]]) $assert (_.partition3 ([ 'a', 'b', 'c', undefined, undefined, 42], _.typeOf), [{ label: 'string', items: ['a', 'b', 'c'] }, { label: 'undefined', items: [undefined, undefined] }, { label: 'number', items: [42] }]) $assert (_.partition3 ([ { foo: 'a' }, { foo: 'a' }, { foo: 'b' }], 'foo'), [{ foo: 'a', items: [{ foo: 'a' }, { foo: 'a' }] }, { foo: 'b', items: [{ foo: 'b' }] }]) }, function () { _.partition2 = function (arr, pick) { return _.pluck (_.partition3 (arr, pick), 'items') } _.partition3 = function (arr_, pick) { var arr = arr_ || [] var spans = [], span = { label: undefined, items: [arr.first] } const prop = _.isFunction (pick) ? 'label' : pick const pickFn = _.isFunction (pick) ? pick : x => x[pick] _.each (arr, function (x, i) { var value = pickFn (x, i) if ((span[prop] != value) && span.items.length) { spans.push (span = { [prop]: value, items: [x] }) } else { span.items.push (x) } }) return (span.length && spans.push (span)), spans } }) /* Taken from npmjs.com/package/longest-common-substring Props to npmjs.com/~mirkok ======================================================================== */ _.withTest (['stdlib', 'longestCommonSubstring'], function () { $assert ('foo', _.longestCommonSubstring ('foo', 'ffooa')) }, function () { var indexMap = function(list) { var map = {} _.each (list, function(each, i) { map[each] = map[each] || [] map[each].push(i) }) return map } _.longestCommonSubstring = function (a, b) { var where = _.indexOfLongestCommonSubstring (a, b) return (where.length) ? a.substr (where.a, where.length) : undefined } _.indexOfLongestCommonSubstring = function(a, b) { var result = { a:0, b:0, length:0} var indexMapBefore = indexMap(a) var previousOverlap = [] _.each (b, function(eachAfter, indexAfter) { var overlapLength var overlap = [] var indexesBefore = indexMapBefore[eachAfter] || [] _.each (indexesBefore, function(indexBefore) { overlapLength = ((indexBefore && previousOverlap[indexBefore-1]) || 0) + 1; if (overlapLength > result.length) { result.length = overlapLength; result.a = indexBefore - overlapLength + 1; result.b = indexAfter - overlapLength + 1; } overlap[indexBefore] = overlapLength }) previousOverlap = overlap }) return result } }) /* experimental shit (subject to removal) ======================================================================== */ _.key = function (fn) { return function (value, key) { return fn (key) } } _.pickKeys = function (obj, predicate) { return _.pick (obj, function (v, k) { return predicate (k) }) }