UNPKG

@ramda/uniqby

Version:

R.uniqBy exported as a module

663 lines (610 loc) 18.3 kB
// Ramda v0.26.1 // https://github.com/ramda/ramda // (c) 2013-2019 Scott Sauyet, Michael Hurley, and David Chambers // Ramda may be freely distributed under the MIT license. (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (factory((global.R = {}))); }(this, (function (exports) { 'use strict'; function _isPlaceholder(a) { return a != null && typeof a === 'object' && a['@@functional/placeholder'] === true; } /** * Optimized internal one-arity curry function. * * @private * @category Function * @param {Function} fn The function to curry. * @return {Function} The curried function. */ function _curry1(fn) { return function f1(a) { if (arguments.length === 0 || _isPlaceholder(a)) { return f1; } else { return fn.apply(this, arguments); } }; } /** * Optimized internal two-arity curry function. * * @private * @category Function * @param {Function} fn The function to curry. * @return {Function} The curried function. */ function _curry2(fn) { return function f2(a, b) { switch (arguments.length) { case 0: return f2; case 1: return _isPlaceholder(a) ? f2 : _curry1(function(_b) { return fn(a, _b); }); default: return _isPlaceholder(a) && _isPlaceholder(b) ? f2 : _isPlaceholder(a) ? _curry1(function(_a) { return fn(_a, b); }) : _isPlaceholder(b) ? _curry1(function(_b) { return fn(a, _b); }) : fn(a, b); } }; } function _arrayFromIterator(iter) { var list = []; var next; while (!(next = iter.next()).done) { list.push(next.value); } return list; } function _includesWith(pred, x, list) { var idx = 0; var len = list.length; while (idx < len) { if (pred(x, list[idx])) { return true; } idx += 1; } return false; } function _functionName(f) { // String(x => x) evaluates to "x => x", so the pattern may not match. var match = String(f).match(/^function (\w*)/); return match == null ? '' : match[1]; } function _has(prop, obj) { return Object.prototype.hasOwnProperty.call(obj, prop); } // Based on https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is function _objectIs(a, b) { // SameValue algorithm if (a === b) { // Steps 1-5, 7-10 // Steps 6.b-6.e: +0 != -0 return a !== 0 || 1 / a === 1 / b; } else { // Step 6.a: NaN == NaN return a !== a && b !== b; } } var _objectIs$1 = typeof Object.is === 'function' ? Object.is : _objectIs; var toString = Object.prototype.toString; var _isArguments = (function() { return toString.call(arguments) === '[object Arguments]' ? function _isArguments(x) { return toString.call(x) === '[object Arguments]'; } : function _isArguments(x) { return _has('callee', x); }; }()); // cover IE < 9 keys issues var hasEnumBug = !({toString: null}).propertyIsEnumerable('toString'); var nonEnumerableProps = [ 'constructor', 'valueOf', 'isPrototypeOf', 'toString', 'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString' ]; // Safari bug var hasArgsEnumBug = (function() { return arguments.propertyIsEnumerable('length'); }()); var contains = function contains(list, item) { var idx = 0; while (idx < list.length) { if (list[idx] === item) { return true; } idx += 1; } return false; }; /** * Returns a list containing the names of all the enumerable own properties of * the supplied object. * Note that the order of the output array is not guaranteed to be consistent * across different JS platforms. * * @func * @memberOf R * @since v0.1.0 * @category Object * @sig {k: v} -> [k] * @param {Object} obj The object to extract properties from * @return {Array} An array of the object's own properties. * @see R.keysIn, R.values * @example * * R.keys({a: 1, b: 2, c: 3}); //=> ['a', 'b', 'c'] */ var keys = typeof Object.keys === 'function' && !hasArgsEnumBug ? _curry1(function keys(obj) { return Object(obj) !== obj ? [] : Object.keys(obj); }) : _curry1(function keys(obj) { if (Object(obj) !== obj) { return []; } var prop, nIdx; var ks = []; var checkArgsLength = hasArgsEnumBug && _isArguments(obj); for (prop in obj) { if (_has(prop, obj) && (!checkArgsLength || prop !== 'length')) { ks[ks.length] = prop; } } if (hasEnumBug) { nIdx = nonEnumerableProps.length - 1; while (nIdx >= 0) { prop = nonEnumerableProps[nIdx]; if (_has(prop, obj) && !contains(ks, prop)) { ks[ks.length] = prop; } nIdx -= 1; } } return ks; }); /** * Gives a single-word string description of the (native) type of a value, * returning such answers as 'Object', 'Number', 'Array', or 'Null'. Does not * attempt to distinguish user Object types any further, reporting them all as * 'Object'. * * @func * @memberOf R * @since v0.8.0 * @category Type * @sig (* -> {*}) -> String * @param {*} val The value to test * @return {String} * @example * * R.type({}); //=> "Object" * R.type(1); //=> "Number" * R.type(false); //=> "Boolean" * R.type('s'); //=> "String" * R.type(null); //=> "Null" * R.type([]); //=> "Array" * R.type(/[A-z]/); //=> "RegExp" * R.type(() => {}); //=> "Function" * R.type(undefined); //=> "Undefined" */ var type = _curry1(function type(val) { return val === null ? 'Null' : val === undefined ? 'Undefined' : Object.prototype.toString.call(val).slice(8, -1); }); /** * private _uniqContentEquals function. * That function is checking equality of 2 iterator contents with 2 assumptions * - iterators lengths are the same * - iterators values are unique * * false-positive result will be returned for comparision of, e.g. * - [1,2,3] and [1,2,3,4] * - [1,1,1] and [1,2,3] * */ function _uniqContentEquals(aIterator, bIterator, stackA, stackB) { var a = _arrayFromIterator(aIterator); var b = _arrayFromIterator(bIterator); function eq(_a, _b) { return _equals(_a, _b, stackA.slice(), stackB.slice()); } // if *a* array contains any element that is not included in *b* return !_includesWith(function(b, aItem) { return !_includesWith(eq, aItem, b); }, b, a); } function _equals(a, b, stackA, stackB) { if (_objectIs$1(a, b)) { return true; } var typeA = type(a); if (typeA !== type(b)) { return false; } if (a == null || b == null) { return false; } if (typeof a['fantasy-land/equals'] === 'function' || typeof b['fantasy-land/equals'] === 'function') { return typeof a['fantasy-land/equals'] === 'function' && a['fantasy-land/equals'](b) && typeof b['fantasy-land/equals'] === 'function' && b['fantasy-land/equals'](a); } if (typeof a.equals === 'function' || typeof b.equals === 'function') { return typeof a.equals === 'function' && a.equals(b) && typeof b.equals === 'function' && b.equals(a); } switch (typeA) { case 'Arguments': case 'Array': case 'Object': if (typeof a.constructor === 'function' && _functionName(a.constructor) === 'Promise') { return a === b; } break; case 'Boolean': case 'Number': case 'String': if (!(typeof a === typeof b && _objectIs$1(a.valueOf(), b.valueOf()))) { return false; } break; case 'Date': if (!_objectIs$1(a.valueOf(), b.valueOf())) { return false; } break; case 'Error': return a.name === b.name && a.message === b.message; case 'RegExp': if (!(a.source === b.source && a.global === b.global && a.ignoreCase === b.ignoreCase && a.multiline === b.multiline && a.sticky === b.sticky && a.unicode === b.unicode)) { return false; } break; } var idx = stackA.length - 1; while (idx >= 0) { if (stackA[idx] === a) { return stackB[idx] === b; } idx -= 1; } switch (typeA) { case 'Map': if (a.size !== b.size) { return false; } return _uniqContentEquals(a.entries(), b.entries(), stackA.concat([a]), stackB.concat([b])); case 'Set': if (a.size !== b.size) { return false; } return _uniqContentEquals(a.values(), b.values(), stackA.concat([a]), stackB.concat([b])); case 'Arguments': case 'Array': case 'Object': case 'Boolean': case 'Number': case 'String': case 'Date': case 'Error': case 'RegExp': case 'Int8Array': case 'Uint8Array': case 'Uint8ClampedArray': case 'Int16Array': case 'Uint16Array': case 'Int32Array': case 'Uint32Array': case 'Float32Array': case 'Float64Array': case 'ArrayBuffer': break; default: // Values of other types are only equal if identical. return false; } var keysA = keys(a); if (keysA.length !== keys(b).length) { return false; } var extendedStackA = stackA.concat([a]); var extendedStackB = stackB.concat([b]); idx = keysA.length - 1; while (idx >= 0) { var key = keysA[idx]; if (!(_has(key, b) && _equals(b[key], a[key], extendedStackA, extendedStackB))) { return false; } idx -= 1; } return true; } /** * Returns `true` if its arguments are equivalent, `false` otherwise. Handles * cyclical data structures. * * Dispatches symmetrically to the `equals` methods of both arguments, if * present. * * @func * @memberOf R * @since v0.15.0 * @category Relation * @sig a -> b -> Boolean * @param {*} a * @param {*} b * @return {Boolean} * @example * * R.equals(1, 1); //=> true * R.equals(1, '1'); //=> false * R.equals([1, 2, 3], [1, 2, 3]); //=> true * * const a = {}; a.v = a; * const b = {}; b.v = b; * R.equals(a, b); //=> true */ var equals = _curry2(function equals(a, b) { return _equals(a, b, [], []); }); function _indexOf(list, a, idx) { var inf, item; // Array.prototype.indexOf doesn't exist below IE9 if (typeof list.indexOf === 'function') { switch (typeof a) { case 'number': if (a === 0) { // manually crawl the list to distinguish between +0 and -0 inf = 1 / a; while (idx < list.length) { item = list[idx]; if (item === 0 && 1 / item === inf) { return idx; } idx += 1; } return -1; } else if (a !== a) { // NaN while (idx < list.length) { item = list[idx]; if (typeof item === 'number' && item !== item) { return idx; } idx += 1; } return -1; } // non-zero numbers can utilise Set return list.indexOf(a, idx); // all these types can utilise Set case 'string': case 'boolean': case 'function': case 'undefined': return list.indexOf(a, idx); case 'object': if (a === null) { // null can utilise Set return list.indexOf(a, idx); } } } // anything else not covered above, defer to R.equals while (idx < list.length) { if (equals(list[idx], a)) { return idx; } idx += 1; } return -1; } function _includes(a, list) { return _indexOf(list, a, 0) >= 0; } function _Set() { /* globals Set */ this._nativeSet = typeof Set === 'function' ? new Set() : null; this._items = {}; } // until we figure out why jsdoc chokes on this // @param item The item to add to the Set // @returns {boolean} true if the item did not exist prior, otherwise false // _Set.prototype.add = function(item) { return !hasOrAdd(item, true, this); }; // // @param item The item to check for existence in the Set // @returns {boolean} true if the item exists in the Set, otherwise false // _Set.prototype.has = function(item) { return hasOrAdd(item, false, this); }; // // Combines the logic for checking whether an item is a member of the set and // for adding a new item to the set. // // @param item The item to check or add to the Set instance. // @param shouldAdd If true, the item will be added to the set if it doesn't // already exist. // @param set The set instance to check or add to. // @return {boolean} true if the item already existed, otherwise false. // function hasOrAdd(item, shouldAdd, set) { var type = typeof item; var prevSize, newSize; switch (type) { case 'string': case 'number': // distinguish between +0 and -0 if (item === 0 && 1 / item === -Infinity) { if (set._items['-0']) { return true; } else { if (shouldAdd) { set._items['-0'] = true; } return false; } } // these types can all utilise the native Set if (set._nativeSet !== null) { if (shouldAdd) { prevSize = set._nativeSet.size; set._nativeSet.add(item); newSize = set._nativeSet.size; return newSize === prevSize; } else { return set._nativeSet.has(item); } } else { if (!(type in set._items)) { if (shouldAdd) { set._items[type] = {}; set._items[type][item] = true; } return false; } else if (item in set._items[type]) { return true; } else { if (shouldAdd) { set._items[type][item] = true; } return false; } } case 'boolean': // set._items['boolean'] holds a two element array // representing [ falseExists, trueExists ] if (type in set._items) { var bIdx = item ? 1 : 0; if (set._items[type][bIdx]) { return true; } else { if (shouldAdd) { set._items[type][bIdx] = true; } return false; } } else { if (shouldAdd) { set._items[type] = item ? [false, true] : [true, false]; } return false; } case 'function': // compare functions for reference equality if (set._nativeSet !== null) { if (shouldAdd) { prevSize = set._nativeSet.size; set._nativeSet.add(item); newSize = set._nativeSet.size; return newSize === prevSize; } else { return set._nativeSet.has(item); } } else { if (!(type in set._items)) { if (shouldAdd) { set._items[type] = [item]; } return false; } if (!_includes(item, set._items[type])) { if (shouldAdd) { set._items[type].push(item); } return false; } return true; } case 'undefined': if (set._items[type]) { return true; } else { if (shouldAdd) { set._items[type] = true; } return false; } case 'object': if (item === null) { if (!set._items['null']) { if (shouldAdd) { set._items['null'] = true; } return false; } return true; } /* falls through */ default: // reduce the search size of heterogeneous sets by creating buckets // for each type. type = Object.prototype.toString.call(item); if (!(type in set._items)) { if (shouldAdd) { set._items[type] = [item]; } return false; } // scan through all previously applied items if (!_includes(item, set._items[type])) { if (shouldAdd) { set._items[type].push(item); } return false; } return true; } } /** * Returns a new list containing only one copy of each element in the original * list, based upon the value returned by applying the supplied function to * each list element. Prefers the first item if the supplied function produces * the same value on two items. [`R.equals`](#equals) is used for comparison. * * @func * @memberOf R * @since v0.16.0 * @category List * @sig (a -> b) -> [a] -> [a] * @param {Function} fn A function used to produce a value to use during comparisons. * @param {Array} list The array to consider. * @return {Array} The list of unique items. * @example * * R.uniqBy(Math.abs, [-1, -5, 2, 10, 1, 2]); //=> [-1, -5, 2, 10] */ var uniqBy = _curry2(function uniqBy(fn, list) { var set = new _Set(); var result = []; var idx = 0; var appliedItem, item; while (idx < list.length) { item = list[idx]; appliedItem = fn(item); if (set.add(appliedItem)) { result.push(item); } idx += 1; } return result; }); exports.uniqBy = uniqBy; Object.defineProperty(exports, '__esModule', { value: true }); })));