UNPKG

xduce

Version:

Composable algorithmic transformations library for JavaScript

193 lines (182 loc) 8.59 kB
/* * Copyright (c) 2017 Thomas Otterson * * Permission is hereby granted, free of charge, to any person obtaining a copy of * this software and associated documentation files (the "Software"), to deal in * the Software without restriction, including without limitation the rights to * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of * the Software, and to permit persons to whom the Software is furnished to do so, * subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /** * Transducers for rejecting repeated elements in a collection. * * @module unique * @private */ const { protocols } = require('../modules/protocol'); const { sequence } = require('../modules/transformation'); const { isFunction } = require('../modules/util'); const { sameValueZero } = require('./core'); const p = protocols; /** * A transducer function that is returned by the `{@link module:xduce.transducers.unique|unique}`, * `{@link module:xduce.transducers.uniqueBy|uniqueBy}`, and * `{@link module:xduce.transducers.uniqueWith|uniqueWith}` transducers. * * @private * * @param {function} fn The two-argument comparator function that defines when two values are equal. * @param {module:xduce~transducerObject} xform The transducer object that the new one should be chained to. * @return {module:xduce~transducerObject} A new transducer object chained to the provided transducer object. */ function uniqueTransducer(fn, xform) { const uniques = []; return { [p.init]() { return xform[p.init](); }, [p.step](acc, input) { if (uniques.some(u => fn(input, u))) { return acc; } uniques.push(input); return xform[p.step](acc, input); }, [p.result](value) { return xform[p.result](value); } }; } /** * **Removes all duplicates from a collection, using a comparator function to determine what's unique.** * * Comparisons are made by passing each pair of elements to the function, which must take two parameters and return a * boolean indicating whether or not the values are equal. As an example, the * `{@link module:xduce.transducers.unique|unique}` transducer could be regarded as the same as this transducer, with a * {@link http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero|SameValueZero} function serving as the * comparator. * * If no collection is provided, a function is returned that can be passed to `{@link module:xduce.sequence|sequence}`, * et al. * * Example: * * ``` * // magnitude returns the number of digits in a number * function magnitude(x) { * return Math.floor(Math.log(x) / Math.LN10 + 0.000000001); * } * function comparator(a, b) { * return magnitude(a) === magnitude(b); * } * * // Returns only the first value of each magnitude * const result = uniqueWith([1, 10, 100, 42, 56, 893, 1111, 1000], comparator); * // result = [1, 10, 100, 1111] * ``` * * @memberof module:xduce.transducers * * @param {*} [collection] An optional input collection that is to be transduced. * @param {function} fn A comparator function. This takes two arguments and returns `true` if they're to be regarded as * equal. * @param {object} [ctx] An optional context object which is set to `this` for the function `fn`. This does not work if * `fn` is an arrow function, as they cannot be bound to contexts. * @return {(*|module:xduce~transducerFunction)} If a collection is supplied, then the function returns a new * collection of the same type with all of the elements of the input collection transformed. If no collection is * supplied, a transducer function, suitable for passing to `{@link module:xduce.sequence|sequence}`, * `{@link module:xduce.into|into}`, etc. is returned. */ function uniqueWith(collection, fn, ctx) { const [col, func] = isFunction(collection) ? [null, collection.bind(fn)] : [collection, fn.bind(ctx)]; return col ? sequence(col, uniqueWith(func)) : xform => uniqueTransducer(func, xform); } /** * **Applies a function each element of a collection and removes elements that create duplicate return values.** * * Once the function is applied to the collection elements, a comparison is made using * {@link http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero|SameValueZero}. If a comparison indicates that * the return value from the function for one element is the same as the return value for another element, only the * first element is retained in the output collection. * * Also note that even though the function can cause a completely different value to be compared, the *element* (not * the return value of the function) is what is added to the output collection. * * A very common use for `uniqueBy` is to refer to a particular property in an array of objects. Another is to do a * case-insensitive comparison by passing a function that turns every letter in a string to the same case. However, it * can be used in any number of different ways, depending on the function used. * * If no collection is provided, a function is returned that can be passed to `{@link module:xduce.sequence|sequence}`, * et al. * * ``` * var array = [{x: 1}, {x: 1}, {x: 2}, {x: 3}, {x: 3}, {x: 3}, * {x: 4}, {x: 5}, {x: 3}, {x: 1}, {x: 5}]; * * var result = uniqueBy(array, obj => obj.x); * // result = [{x: 1}, {x: 2}, {x: 3}, {x: 4}, {x: 5}] * * // Comparison is case-insensitive, the duplicate letter retained is the first one that appears * // This is why 'N' is present in the output, not 'n', for example * result = uniqueBy('aNtidiseSTablIshmENtaRianiSM', x => x.toLowerCase()); * // result = 'aNtidseblhmR' * ``` * * @memberof module:xduce.transducers * * @param {*} [collection] An optional input collection that is to be transduced. * @param {function} fn A function of one parameter applied to each element in the input collection before testing the * results for uniqueness. * @param {object} [ctx] An optional context object which is set to `this` for the function `fn`. This does not work if * `fn` is an arrow function, as they cannot be bound to contexts. * @return {(*|module:xduce~transducerFunction)} If a collection is supplied, then the function returns a new * collection of the same type with all of the elements of the input collection transformed. If no collection is * supplied, a transducer function, suitable for passing to `{@link module:xduce.sequence|sequence}`, * `{@link module:xduce.into|into}`, etc. is returned. */ function uniqueBy(collection, fn, ctx) { const [col, func] = isFunction(collection) ? [null, collection.bind(fn)] : [collection, fn.bind(ctx)]; return uniqueWith(col, (a, b) => sameValueZero(func(a), func(b))); } /** * **Removes all duplicates from a collection.** * * Once an element is added to the output collection, an equal element will never be added to the output collection * again. 'Equal' according to this transformer is a * {@link http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero|SameValueZero} comparison. * * If no collection is provided, a function is returned that can be passed to `{@link module:xduce.sequence|sequence}`, * et al. * * ``` * var result = unique([1, 1, 2, 3, 3, 3, 4, 5, 3, 1, 5]); * // result = [1, 2, 3, 4, 5]; * ``` * * @memberof module:xduce.transducers * * @param {*} [collection] An optional input collection that is to be transduced. * @return {(*|module:xduce~transducerFunction)} If a collection is supplied, then the function returns a new * collection of the same type with all of the elements of the input collection transformed. If no collection is * supplied, a transducer function, suitable for passing to `{@link module:xduce.sequence|sequence}`, * `{@link module:xduce.into|into}`, etc. is returned. */ function unique(collection) { return uniqueWith(collection, sameValueZero); } module.exports = { unique, uniqueBy, uniqueWith };