+150
-147
| # Subset API | ||
| Subset export several basic and generalized set operations for JavaScript Arrays. | ||
| The generalized versions work best with the additionally supplied generalized equality and comparison functions. | ||
| This is in general a pure functional library, but a couple of functions does modify the input (`insert` and `delete` only). The generalized versions accept general notions of equality which is explored immediately below. | ||
| ## Generalized Equality | ||
| This function is created primarily for the generalized set operations further down. | ||
| ### $.equality(props..) :: (x, y -> x 'equality on props' y) | ||
| This is a special function that creates an equality testing function based on | ||
| properties to test on. It will return true if and only if all the properties listed | ||
| are the same for both x and y. | ||
| An equality function is a function that takes two elements and return whether or not they are equal. These can be constructed by lambdas in ES6, but two shorthands exist: | ||
| ### $.equality(prop) :: (x, y) -> Boolean | ||
| This function creates an equality function based on a property name. It will return true if and only if the value of the property listed is the same for both x and y. | ||
| ```js | ||
@@ -17,7 +16,12 @@ var lenEquals = $.equality('length'); | ||
| lenEquals([1,3,5], [2,4]); // false | ||
| ``` | ||
| var steve = {name: 'Steve', money: 30000, status: "Awesome"}; | ||
| var peter = {name: 'Peter', money: 30000, status: "Depressed"}; | ||
| var steve2 = {name: 'Clone', money: 30000, status: "Awesome"}; | ||
| var equallyCool = $.equality('money', 'status'); | ||
| ### $.equalityBy(cost) :: (x, y) -> Boolean | ||
| This function creates an equality function based on a cost function. It will return true if and only if `cost(x) === cost(y)`. | ||
| ```js | ||
| var steve = { name: 'Steve', money: 30000, rank: 5 }; | ||
| var peter = { name: 'Peter', money: 30000, rank: 3 }; | ||
| var steve2 = { name: 'Clone', money: 30000, rank: 5 }; | ||
| var equallyCool = $.equalityBy((x) => x.money + '::' + x.status); | ||
| equallyCool(steve, peter); // false | ||
@@ -27,16 +31,16 @@ equallyCool(steve, steve2); // true | ||
| In this case the check is actually comparing two strings with an arbitrary separator (and the value of money is stringified on each side). | ||
| This method of forcing string comparison works quite well in javascript, but if you find this practice barbaric, lambdas is your friend. | ||
| ## Generalized Comparison | ||
| These functions help generate | ||
| [compare functions](https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/sort) | ||
| for `Array.prototype.sort`. | ||
| A comparison function is a function that takes two elements and returns a a positive number if the first is greater, or a negative number if the first is smaller (or zero if no difference). These functions are typically passed to [Array.prototype.sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort). | ||
| ### $.compare([ord]) :: (x, y -> x `compare` y) | ||
| This creates a function which returns the numeric difference between x and y, | ||
| optionally multiplying by minus one if the ord parameter is set to `-1` for | ||
| descending order. The default ord parameter `+1` for ascending order may be omitted. | ||
| These are often created via lambdas in ES6, but a shorthands exist. | ||
| When passed to `Array.prototype.sort`, it will sort the array _numerically_ | ||
| as opposed to the default ECMA behaviour of turning the elements into strings then | ||
| sorting them _lexicographically_. This helps avoid common sorting mistakes: | ||
| ### $.compare([ord]) :: (x, y) -> Number | ||
| This creates a comparison function for numbers, or other types where `x.valueOf()` provides a sensible ordering. | ||
| When pased to `Array.prototype.sort`, it will sort the array _numerically_ as opposed to the default ECMA behaviour of turning the elements into strings, then sorting them _lexicographically_. This helps avoid common sorting mistakes: | ||
| ```js | ||
@@ -50,78 +54,56 @@ [1,4,3,2].sort(); // [1,2,3,4] (expected) | ||
| ### $.compare(costFn [, ord]) :: (x, y -> cost(x) `compare` cost(y)) | ||
| An overloaded variant of compare that takes a cost function to minimize (or any function that you may wish to maximize or minimize). | ||
| The ord parameter is there to optionally reverse the sort. | ||
| The extra `ord` parameter (similarly) defines what order you want values ordered in: | ||
| ```js | ||
| [2, 100, 10, 4].sort($.compare(-1)); // [100, 10, 4, 2] | ||
| ``` | ||
| - `+1` : ascending on cost (x with lowest cost first) | ||
| - `-1` : descending on cost (x with highest cost first) | ||
| Note that you can override the `valueOf` method for a class to gain the ability to use `compare` with any type. | ||
| ### $.compareBy(costFn [, ord]) :: (x, y) -> Number | ||
| An overloaded variant of compare that takes a cost function to minimize (or any function that you may wish to maximize or minimize). | ||
| ```js | ||
| var cost = function (x) { | ||
| return 4*x.a + x.b; | ||
| }; | ||
| var objs = [{a:1, b:0}, {a:0, b:3}]; // first has higher cost | ||
| objs.sort($.compare(cost)); // [{a:0, b:3}, {a:1, b:0}] | ||
| objs.sort($.compare(cost), +1); // [{a:0, b:3}, {a:1, b:0}] | ||
| objs.sort($.compare(cost), +1); // [{a:1, b:0}, {a:0, b:3}] | ||
| var cost = (x) => 4*x.a + x.b; | ||
| var objs = [ { a: 1, b: 0 }, { a: 0, b: 3 } ]; // first has higher cost | ||
| objs.sort($.compareBy(cost)); // [ { a: 0, b: 3 }, { a: 1, b: 0 } ] | ||
| objs.sort($.compareBy(cost, +1)); // [ { a: 0, b: 3 }, { a: 1, b: 0 } ] | ||
| objs.sort($.compareBy(cost, -1)); // [ { a: 1, b: 0 }, { a: 0, b: 3 } ] | ||
| ``` | ||
| The `ord` parameter can be used to optionally reverse the sort. Note that `compareBy` is equivalent to `compare` with the cost function `(x) => +x` (i.e. `valueOf`). | ||
| ### $.comparing(prop [, ord [, ..]]) :: (x, y -> x 'compare props' y) | ||
| This creates a numeric compare function returning the numeric difference between | ||
| any (or: the) included property which is not zero. | ||
| If all properties are identical, it returns zero. | ||
| ### $.comparing(prop [, ord]) :: (x, y) -> Number | ||
| A special case of `compareBy` that compares the values of a property. | ||
| Pass in the name(s) of a (numeric) property the | ||
| elements to be sorted all have, along with the direction of comparison for each | ||
| property: `+1` for ascending (default), `-1` for descending. | ||
| The default last ord parameter can be omitted, but it is recommended included. | ||
| For multiple property sort, the arguments go pairwise: prop1, ord1, prop2, ord2, ... | ||
| making the ord parameters are necessary. | ||
| ```js | ||
| // compare index 1 | ||
| [[1,3], [2,2],[3,4]].sort($.comparing(1)); // [ [2,2], [1,3], [3,4] ] | ||
| var users = [{ id: 1, money: 3 }, { id: 2, money: 0 }, { id: 3, money: 3 }]; | ||
| users.sort($.comparing('money')); | ||
| // [ { id: 2, money: 0 }, { id: 1, money: 3 }, { id: 3, money: 3 } ] | ||
| ``` | ||
| var money = [{id: 1, money: 3}, {id: 2, money: 0}, {id: 3, money: 3}]; | ||
| // sort by money asc. first, then id desc. | ||
| money.sort($.comparing('money', +1, 'id', -1)); | ||
| For anything more advanced than this, you are better of writing a lambda that compares properties pairwise. Due to the nature of `||` in javascript you could actually write the following: | ||
| ```js | ||
| var comparator = (x, y) => (x.money - y.money) || (y.id - x.id); | ||
| users.sort(comparator); | ||
| // [ { id: 2, money: 0 }, { id: 3, money: 3 }, { id: 1, money: 3 } ] | ||
| ``` | ||
| ## Operations | ||
| In the following section, an argument named *cmp* denotes a comparison function | ||
| created by `comparing`, `compare`, or manually. | ||
| An argument named *eq* denotes an equality function created by `equality`, manually, or another module like `eq2` from [interlude](https://github.com/clux/interlude). | ||
| ## Set Operations | ||
| The meat of the module. | ||
| ### $.maximum(xs) :: Number | ||
| ### $.minimum(xs) :: Number | ||
| ### $.maximumBy(cmp, xs) :: xs[maxidx] | ||
| ### $.minimumBy(cmp, xs) :: xs[minidx] | ||
| ### $.indexOfBy(eq, xs, x) :: Number | ||
| A generalized version of `Array.prototype.indexOf` that takes a custom equality test. | ||
| If ordering is not based on a single numeric property, or you want the element | ||
| containing this property, then `$.maximumBy` is appropriate: Pass in a comparison | ||
| function and it will return the element which compares favorably against all | ||
| elements in `xs`. | ||
| To simply get the maximum return value of a property, | ||
| consider collecting up the values first then applying the faster `maximum` function. | ||
| Collecting first is going to be faster, but this implies loosing the association | ||
| between the original element. | ||
| ```js | ||
| $.maximum([1,3,2,5]); // 5 | ||
| var nested = [[1,3,2], [2], [2,3]]; | ||
| $.maximum($.pluck('length', nested)); // 3 | ||
| $.maximumBy($.comparing('length'), nested); // [ 1, 3, 2 ] | ||
| $.indexOfBy((x, y) => x.a === y.a, [ { a: 1 }, { a: 2 } ], { a: 1 }); // 0 | ||
| $.indexOfBy((x, y) => x.a === y.a, [ { a: 1 }, { a: 2 } ], { a: 3 }); // -1 | ||
| ``` | ||
| Note that unlike `$.maximum` which returns `-Infinity` in the case of an empty | ||
| Array, `$.maximumBy` returns `undefined` as this is the only thing possible | ||
| without knowing the structure of the elements in the array. | ||
| Similarly for `$.minimum` and `$.minimumBy`. | ||
| ### $.intersect(xs, ys) :: zs | ||
| The 'intersect' function takes the intersection of two arrays. | ||
| For example, | ||
| The `intersect` function takes the intersection of two arrays. | ||
@@ -138,38 +120,37 @@ ```js | ||
| It is a special case of 'intersectBy', which allows the programmer to | ||
| supply their own equality test. | ||
| It is a special case of `intersectBy` with generic equality (`===`) as the equality test. | ||
| ### $.intersectBy(eq, xs, ys) :: zs | ||
| The non-overloaded version of `intersect`. | ||
| The generalized version of `intersect`. | ||
| ```js | ||
| $.intersectBy($.equality('a'), [{a:1}, {a:4, b:0}], [{a:2}, {a:4, b:1}]); | ||
| $.intersectBy((x, y) => x.a === y.a, [{ a: 1 }, { a: 4, b: 0 }], [{ a: 2 }, { a: 4, b: 1 }]); | ||
| // [ { a: 4, b: 0 } ] | ||
| ``` | ||
| ### $.nub(xs) :: ys | ||
| The nub function removes duplicate elements from an array. | ||
| In particular, it keeps only the first occurrence of each element. | ||
| (The name nub means _essence_.) It exploits the extra performance of `Array.prototype.indexOf` but would otherwise have been a special case of `nubBy`, which allows the programmer to supply their own equality test. | ||
| Note that elements from the first array is chosen if an element exists in the other that is equal. | ||
| ### $.unique(xs) :: ys | ||
| The unique function removes duplicate elements from an array. In particular, it keeps only the first occurrence of each element. | ||
| ```js | ||
| $.nub([1,3,2,4,1,2]); // [ 1, 3, 2, 4 ] | ||
| $.unique([1,3,2,4,1,2]); // [ 1, 3, 2, 4 ] | ||
| ``` | ||
| ### $.nubBy(eq, xs) :: ys | ||
| The generalized version of `nub`. | ||
| It is a special case of `uniqueBy`, with generic equality (`===`) as the equality test. | ||
| ### $.uniqueBy(eq, xs) :: ys | ||
| The generalized version of `unique`. | ||
| ```js | ||
| var notCoprime = $($.gcd, $.gt(1)); | ||
| var primes = $.nubBy(notCoprime, $.range(2, 11)); // [ 2, 3, 5, 7, 11 ] | ||
| var notCoprime = (x, y) => gcd(x, y) > 1; | ||
| var primes = $.uniqueBy(notCoprime, $.range(2, 11)); // [ 2, 3, 5, 7, 11 ] | ||
| ``` | ||
| Here the definition of equality is *a and b have common factors*. | ||
| Note the `range` and `$` functions from [interlude](https://github.com/clux/interlude). | ||
| Here the definition of equality is *a and b have common factors*, and later occurances with common factors are excluded. | ||
| Note the `range` from [interlude](https://github.com/clux/interlude). | ||
| ### $.group(xs) :: ys | ||
| The group function takes an array and returns an array of arrays such that | ||
| the flattened result is equal to `xs`. | ||
| Moreover, each subarray is constructed by grouping the _consecutive_ equal elements | ||
| in `xs`. For example, | ||
| The group function takes an array and returns an array of arrays such that the flattened result is equal to `xs`. | ||
| Moreover, each subarray is constructed by grouping the _consecutive_ equal elements in `xs`. For example, | ||
@@ -180,12 +161,10 @@ ```js | ||
| In particular, if `xs` is sorted, then the result is sorted | ||
| when comparing on the first sub element, i.e. `$.comparing(0)`. | ||
| It is a special case of groupBy, which allows the programmer to supply | ||
| their own equality test. | ||
| In particular, if `xs` is sorted, then the flattened result is sorted. | ||
| It is a special case of `groupBy`, with generic equality (`===`) as the equality test. | ||
| ### $.groupBy(eq, xs) :: ys | ||
| The non-overloaded version of `group`. | ||
| The generalized version of `group`. | ||
| ```js | ||
| $.groupBy($.equality('a'), [{a:1}, {a:4, b:1}, {a:4, b:0}, {a:1}]); | ||
| $.groupBy((x,y) => x.a === y.a, [{ a : 1 }, { a: 4, b: 1 }, { a: 4, b: 0 }, { a: 1 }]); | ||
| // [ [ { a: 1 } ], | ||
@@ -205,10 +184,9 @@ // [ { a: 4, b: 1 }, { a: 4, b: 0 } ], | ||
| but if the first array contains duplicates, so will the result. | ||
| It is a special case of unionBy, which allows the programmer to supply | ||
| their own equality test. | ||
| It is a special case of `unionBy`, with generic equality (`===`) as the equality test. | ||
| ### $.unionBy(eq, xs, ys) :: zs | ||
| The non-overloaded version of `union`. | ||
| The generalized version of `union`. | ||
| ```js | ||
| $.unionBy($.equality('a'), [{a:1},{a:3}], [{a:2},{a:3}]); | ||
| $.unionBy((x,y) => x.a === y.a, [{ a: 1 }, { a: 3 }], [{ a: 2 }, { a: 3 }]); | ||
| // [ { a: 1 }, { a: 3 }, { a: 2 } ] | ||
@@ -218,9 +196,6 @@ ``` | ||
| ### $.difference(xs, ys) :: zs | ||
| Returns the difference between xs and ys; xs \ ys. | ||
| The first occurrence of each element of ys in turn (if any) has been | ||
| removed from xs. Thus `$.difference(ys.concat(xs), ys) equals xs`. | ||
| Returns the difference between `xs` and `ys`, in set notation: `xs \ ys`. | ||
| The first occurrence of each element of `ys` in turn (if any) has been | ||
| removed from `xs`. Thus `$.difference(ys.concat(xs), ys)` equals `xs` (in the `deepEqual` sense). | ||
| It is a special case of differenceBy, which allows the programmer to supply | ||
| their own equality test. | ||
| ```js | ||
@@ -230,7 +205,9 @@ $.difference([1,2,2,3], [2,3,4]); // [ 1, 2 ] | ||
| It is a special case of `differenceBy`, with generic equality (`===`) as the equality test. | ||
| ### $.differenceBy(eq, xs, ys) :: zs | ||
| The non-overloaded version of `difference`. | ||
| The generalized version of `difference`. | ||
| ```js | ||
| $.differenceBy($.equality('a'), [{a:1}, {a:2}], [{a:2}, {a:3}]); | ||
| $.differenceBy((x,y) => x.a === y.a, [{ a: 1 }, { a: 2 }], [{ a: 2 }, { a: 3 }]); | ||
| // [ { a: 1 } ] | ||
@@ -240,28 +217,22 @@ ``` | ||
| ### $.insert(xs, x) :: xs | ||
| The insert function takes an element and an array and inserts the element | ||
| into the array at the last position where it is still less than or equal | ||
| to the next element. In particular, if the array is sorted before the call, | ||
| the result will also be sorted. | ||
| The insert function takes an element `x` and an array `xs` and inserts the element into the array at the last position where it is still less than or equal | ||
| to the next element. In particular, if the array is sorted before the call, the result will also be sorted. | ||
| It is a special case of `insertBy`, | ||
| which allows the programmer to supply their own comparison function. | ||
| ``` | ||
| $.insert([1,2,3,4], 3) | ||
| ```js | ||
| $.insert([1,2,3,4], 3); | ||
| [ 1, 2, 3, 3, 4 ] | ||
| ``` | ||
| It is a special case of `insertBy`, with generic numeric ordering as the comparator. | ||
| ### $.insertBy(cmp, xs, x) :: xs | ||
| The non-overloaded version of `insert`. | ||
| The generalized version of `insert`. | ||
| ```js | ||
| $.insertBy($.comparing('a'), [{a:1}, {a:2}, {a:3}], {a:3, n:1}) | ||
| // [ { a: 1 }, | ||
| // { a: 2 }, | ||
| // { a: 3, n: 1 }, | ||
| // { a: 3 } ] | ||
| $.insertBy($.comparing('a'), [{ a: 1 }, { a: 2 }, { a: 3 }], { a: 3, n: 1 }); | ||
| // [ { a: 1 }, { a: 2 }, { a: 3, n: 1 }, { a: 3 } ] | ||
| ``` | ||
| ### $.delete(xs, x) :: xs | ||
| Removes the first occurrence of `x` from its array argument `xs`. | ||
| Removes the *first* occurrence of `x` from its array argument `xs`. | ||
@@ -272,5 +243,3 @@ ```js | ||
| Behaviourally equivalent to the generalized | ||
| version `deleteBy` with `$.eq2` as the supplied equality test, but | ||
| this special case implementation uses `Array.prototype.indexOf` and is faster. | ||
| It is a special case of `deleteBy`, with generic equality (`===`) as the equality test. | ||
@@ -281,3 +250,3 @@ ### $.deleteBy(eq, xs, x) :: xs | ||
| ```js | ||
| $.deleteBy($.equality('a'), [{a:1},{a:2},{a:3},{a:2}], {a:2}) | ||
| $.deleteBy((x,y) => x.a === y.a, [{ a: 1 }, { a: 2 }, { a: 3 }, { a: 2 }], { a: 2 }); | ||
| // [ { a: 1 }, { a: 3 }, { a: 2 } ] | ||
@@ -287,4 +256,3 @@ ``` | ||
| #### Warning: delete/insert modifies | ||
| For efficiency; `delete`, `insert`, `deleteBy`, `insertBy` all modify the | ||
| passed in array. To return an independent result, modify a shallow copy instead: | ||
| To maintain javascript conventions; `delete`, `insert`, `deleteBy`, `insertBy` all modify the passed in array. To return an independent result, pass in a copy instead: | ||
@@ -301,3 +269,5 @@ ```js | ||
| #### NB2: delete/difference only removes one per request | ||
| Note that `slice` provides a *shallow* copy (objects and arrays inside an array are copied by reference), but this is sufficient for an array of numbers. | ||
| #### Warning: delete/difference only removes one per request | ||
| The delete functions only remove the first instance. | ||
@@ -308,28 +278,61 @@ To delete all, `Array.prototype.filter` is best suited: | ||
| var xs = [1,2,2,3,4]; | ||
| xs.filter($.notElem([2,3])); // [ 1, 4 ] | ||
| xs.filter((x) => [2,3].indexOf(x) < 0); // [ 1, 4 ] | ||
| $.difference(xs, [2,3]); // [ 1, 2, 4 ] | ||
| xs.filter($.neq(2)); // [ 1, 3, 4 ] | ||
| xs.filter((x) => x !== 2); // [ 1, 3, 4 ] | ||
| $.delete(xs, 2); // [ 1, 2, 3, 4 ] | ||
| ``` | ||
| Here `notElem`, and `neq` are simple functions from [interlude](https://github.com/clux/interlude). | ||
| ## Extras | ||
| Max/min helpers and generalized versions are provided. | ||
| ### $.maximum(xs) :: Number | ||
| ### $.minimum(xs) :: Number | ||
| These functions are simple wrapper around `Math.max` and `Math.min` | ||
| ```js | ||
| $.maximum([1,3,2,5]); // 5 | ||
| $.minimum([]); // Infinity | ||
| ``` | ||
| ### $.isSubsetOf(xs, ys [, proper]) :: Boolean | ||
| Checks if `xs` is a subset of `ys`, and optionally, if it is a proper subset. | ||
| ### $.maximumBy(cmp, xs) :: Maximal x | ||
| ### $.minimumBy(cmp, xs) :: Minimal x | ||
| For picking maximal values not based on numeric value, or if you simply want the element containing the maximal value of a property (say), then these functions are appropriate. | ||
| ```js | ||
| var nested = [[1,3,2], [2], [2,3]]; | ||
| $.maximum(nested.map((x) => x.length)); // 3 | ||
| $.maximumBy($.comparing('length'), nested); // [ 1, 3, 2 ] | ||
| ``` | ||
| Note that unlike `maximum` which returns `-Infinity` in the case of an empty | ||
| Array, `maximumBy` has nothing sensible to return since the structure of the type passed in is unknown. | ||
| ```js | ||
| $.minimumBy($.comparing('something'), []); // undefined | ||
| ``` | ||
| ### $.isSubsetOf(xs, ys) :: Boolean | ||
| Checks if `xs` is a subset of `ys`. | ||
| ```js | ||
| $.isSubsetOf([1,2], [1,2,3]); // true | ||
| $.isSubsetOf([1,2,3], [1,2]); // false | ||
| $.isSubsetOf([1,2,3], [1,2,3]); // true | ||
| $.isSubsetOf([1,2,3], [1,2,3], true); // false (not proper subset) | ||
| $.isSubsetOf([1,2,3], [1,2,3]); // true (but not a proper subset) | ||
| ``` | ||
| Note that duplicates count as a separate element, so if you want true set behaviour, ensure that everything passed satisfies `x === $.nub(x)` by either calling it explicitly or modelling it well. | ||
| ### $.isProperSubsetOf(xs, ys) :: Boolean | ||
| Checks if `xs` is a *strict* suset of `ys`. | ||
| ```js | ||
| $.isSubsetOf([1,2,2], [1,2,3]); // false | ||
| $.isSubsetOf([1,2,2], [1,2,2,3]); // true | ||
| $.isSubsetOf($.nub([1,2,2]), [1,2,3]); // true | ||
| $.isProperSubsetOf([1,2], [1,2,3]); // true | ||
| $.isProperSubsetOf([1,2,3], [1,2]); // false | ||
| $.isProperSubsetOf([1,2,3], [1,2,3]); // false | ||
| ``` | ||
| #### NB: isSubset functions assume no duplicates | ||
| For this reason you will see: | ||
| - `[1,2,2]` is counted as a subset of `[1,2]` | ||
| - `[1,2]` is not a proper subset of `[1,2,2]` | ||
| So think of all arguments to these functions as equivalent to being wrapped in `unique`. |
+13
-0
@@ -0,1 +1,14 @@ | ||
| 1.0.0 / 2016-01-27 | ||
| ================== | ||
| * Rewrite for ES6 | ||
| * `indexOfBy` added to exports | ||
| * `isSubsetOf` now assumes inputs have no duplicates | ||
| * `isSubsetOf` third boolean arg to check if proper subset no longer exists | ||
| * `isProperSubsetOf` now exported - and assumes no duplicates | ||
| * `compare` now split into `compare` and `compareBy` | ||
| * `comparing` (variadic version of `compare`) removed | ||
| * `equality` now only takes single argument | ||
| * `equalityBy` added to exports | ||
| * `nub` and `nubBy` renamed to `unique` and `uniqueBy` | ||
| 0.1.7 / 2015-11-15 | ||
@@ -2,0 +15,0 @@ ================== |
+7
-6
@@ -5,3 +5,3 @@ { | ||
| "description": "Generalized set operations and comparisons in the style of Haskell", | ||
| "version": "0.1.7", | ||
| "version": "1.0.0", | ||
| "repository": { | ||
@@ -23,15 +23,16 @@ "type": "git", | ||
| "Ord", | ||
| "ES5" | ||
| "ES6" | ||
| ], | ||
| "main": "subset.js", | ||
| "scripts": { | ||
| "test": "nodeunit --reporter=verbose test/*.js", | ||
| "coverage": "jscoverage subset.js && SUBSET_COV=1 nodeunit --reporter=lcov test" | ||
| "test": "bndg test/*.js", | ||
| "precoverage": "istanbul cover bndg test/*.js", | ||
| "coverage": "cat coverage/lcov.info && rm -rf coverage" | ||
| }, | ||
| "dependencies": {}, | ||
| "devDependencies": { | ||
| "nodeunit": "~0.9.0", | ||
| "jscoverage": "~0.5.4" | ||
| "bandage": "^0.4.2", | ||
| "istanbul": "^0.4.2" | ||
| }, | ||
| "license": "MIT" | ||
| } |
+16
-24
@@ -8,35 +8,28 @@ # Subset | ||
| Subset provides basic and generalized set operations for JavaScript. | ||
| They are inspired by a subset of the interface to Haskell's [Data.List](http://www.haskell.org/ghc/docs/latest/html/libraries/base/Data-List.html), but optimized for JavaScript semantics and performance. | ||
| They are inspired by a subset of the interface to Haskell's [Data.List](https://hackage.haskell.org/package/base/docs/Data-List.html), but optimized for JavaScript semantics. | ||
| The new ES6 `Set` class is not particularly helpful for doing set operations on general objects (as their only version of equality is `===`), and this module provides a general alternative for people who want to do the same-ish things on arrays. | ||
| ## Usage | ||
| Attach it to the short variable of choice: | ||
| Use it with qualified imports with the yet unfinished module `import` syntax or attach it to the short variable of choice. For selling points, here's how it will look with ES7 modules. | ||
| ```javascript | ||
| var $ = require('subset'); | ||
| ``` | ||
| ```js | ||
| import { equality, union, unionBy, intersect, unique, uniqueBy, insert, delete, group } from 'autonomy' | ||
| and fire up the set engine: | ||
| intersect([1,2,3,4], [2,4,6,8]); // [ 2, 4 ] | ||
| ```javascript | ||
| $.union([1,3,5], [4,5,6]); // [ 1, 3, 5, 4, 6 ] | ||
| $.unionBy($.equality('a'), [{a:1},{a:3}], [{a:2},{a:3}]); | ||
| union([1,3,5], [4,5,6]); // [ 1, 3, 5, 4, 6 ] | ||
| unionBy(equality('a'), [{ a: 1 }, { a: 3 }], [{ a: 2 }, { a: 3 }]); | ||
| // [ { a: 1 }, { a: 3 }, { a: 2 } ] | ||
| $.intersect([1,2,3,4], [2,4,6,8]); // [ 2, 4 ] | ||
| unique([1,3,2,4,1,2]); // [ 1, 3, 2, 4 ] | ||
| $.nub([1,3,2,4,1,2]); // [ 1, 3, 2, 4 ] | ||
| var notCoprime = (x, y) => gcd(x, y) > 1; | ||
| var primes = $.uniqueBy(notCoprime, $.range(2, 11)); // [ 2, 3, 5, 7, 11 ] | ||
| $.group([1,2,2,3,5,5,2]); // [ [1], [2,2], [3], [5,5], [2] ] | ||
| group([1,2,2,3,5,5,2]); // [ [1], [2,2], [3], [5,5], [2] ] | ||
| $.insert([1,2,3,4], 3); // [ 1, 2, 3, 3, 4 ] | ||
| insert([1,2,3,4], 3); // [ 1, 2, 3, 3, 4 ] | ||
| $.delete([1,2,3,2,3], 2); // [ 1, 3, 2, 3 ] | ||
| var nested = [[1,3,2], [2], [2,3]]; | ||
| $.maximumBy($.comparing('length'), nested); // [ 1, 3, 2 ] | ||
| nested.sort($.comparing('length')); // [ [ 2 ], [ 2, 3 ], [ 1, 3, 2 ] ] | ||
| [2, 100, 10, 4].sort(); // [10, 100, 2, 4] <- default lexicographical order | ||
| [2, 100, 10, 4].sort($.compare()); // [2, 4, 10, 100] <- sensible numerical order | ||
| delete([1,2,3,2,3], 2); // [ 1, 3, 2, 3 ] | ||
| ``` | ||
@@ -46,6 +39,5 @@ | ||
| Note that it is often useful to get it with the larger utility library: | ||
| [interlude](https://github.com/clux/interlude) for which it was made. | ||
| Note that it is often useful to get it with the larger utility library [interlude](https://github.com/clux/interlude) for which it was made. | ||
| ## License | ||
| MIT-Licensed. See LICENSE file for details. |
+53
-162
@@ -7,77 +7,41 @@ var $ = {}; | ||
| $.equality = function () { | ||
| var pargs = arguments; | ||
| return function (x, y) { | ||
| for (var i = 0, len = pargs.length; i < len; i += 1) { | ||
| if (x[pargs[i]] !== y[pargs[i]]) { | ||
| return false; | ||
| } | ||
| } | ||
| return true; | ||
| }; | ||
| }; | ||
| var eq2 = (x, y) => x === y; // used throughout | ||
| $.equality = (prop) => (x, y) => x[prop] === y[prop]; | ||
| $.equalityBy = (cost) => (x, y) => cost(x) === cost(y); | ||
| $.compare = (c) => (x, y) => (c || 1)*(x - y); | ||
| $.compareBy = (cost, c) => (x, y) => (c || 1)*(cost(x) - cost(y)); | ||
| $.comparing = (prop, c) => (x, y) => (c || 1)*(x[prop] - y[prop]); | ||
| $.compare = function (c, c2) { | ||
| if (typeof c === 'function') { | ||
| c2 = c2 || 1; | ||
| return function (x, y) { | ||
| return c2 * (c(x) - c(y)); | ||
| }; | ||
| } | ||
| c = c || 1; | ||
| return function (x, y) { | ||
| return c * (x - y); | ||
| }; | ||
| }; | ||
| // --------------------------------------------- | ||
| // Max/min + subset checks | ||
| // --------------------------------------------- | ||
| // result of this can be passed directly to Array::sort | ||
| $.comparing = function () { | ||
| var args = arguments; | ||
| return function (x, y) { | ||
| for (var i = 0, len = args.length; i < len; i += 2) { | ||
| var factor = args[i + 1] || 1; | ||
| if (x[args[i]] !== y[args[i]]) { | ||
| return factor * (x[args[i]] - y[args[i]]); | ||
| } | ||
| } | ||
| return 0; | ||
| }; | ||
| }; | ||
| // max/min | ||
| $.maximum = (xs) => Math.max.apply(Math, xs); | ||
| $.minimum = (xs) => Math.min.apply(Math, xs); | ||
| // default equality, like `operators` eq2 | ||
| var eq2 = function (x, y) { | ||
| return x === y; | ||
| // generalized max/min - cannot be called on an empty array | ||
| $.maximumBy = (cmp, xs) => xs.reduce((max, x) => cmp(x, max) > 0 ? x : max, xs[0]); | ||
| $.minimumBy = (cmp, xs) => xs.reduce((min, x) => cmp(x, min) < 0 ? x : min, xs[0]); | ||
| // subset checks - does not account for duplicate elements in xs or ys | ||
| $.isSubsetOf = (xs, ys) => xs.every((x) => ys.indexOf(x) >= 0); | ||
| $.isProperSubsetOf = function (xs, ys) { | ||
| return $.isSubsetOf(xs, ys) && ys.some((y) => xs.indexOf(y) < 0); | ||
| }; | ||
| // --------------------------------------------- | ||
| // Operations | ||
| // Set Operations | ||
| // --------------------------------------------- | ||
| // max/min + generalized | ||
| $.maximum = function (xs) { | ||
| return Math.max.apply(Math, xs); | ||
| }; | ||
| $.minimum = function (xs) { | ||
| return Math.min.apply(Math, xs); | ||
| }; | ||
| $.maximumBy = function (cmp, xs) { | ||
| for (var i = 1, max = xs[0], len = xs.length; i < len; i += 1) { | ||
| if (cmp(xs[i], max) > 0) { | ||
| max = xs[i]; | ||
| // generalized indexOf | ||
| $.indexOfBy = function (eq, xs, x) { | ||
| for (var i = 0, len = xs.length; i < len; i += 1) { | ||
| if (eq(xs[i], x)) { | ||
| return i; | ||
| } | ||
| } | ||
| return max; | ||
| return -1; | ||
| }; | ||
| $.minimumBy = function (cmp, xs) { | ||
| for (var i = 1, min = xs[0], len = xs.length; i < len; i += 1) { | ||
| if (cmp(xs[i], min) < 0) { | ||
| min = xs[i]; | ||
| } | ||
| } | ||
| return min; | ||
| }; | ||
| // Modifying Array operations | ||
@@ -94,19 +58,6 @@ $.insertBy = function (cmp, xs, x) { | ||
| }; | ||
| $.insert = (xs, x) => $.insertBy($.compare(), xs, x); | ||
| $.insert = function (xs, x) { | ||
| return $.insertBy($.compare(), xs, x); | ||
| }; | ||
| $.deleteBy = function (eq, xs, x) { | ||
| for (var i = 0, len = xs.length; i < len; i += 1) { | ||
| if (eq(xs[i], x)) { | ||
| xs.splice(i, 1); | ||
| return xs; | ||
| } | ||
| } | ||
| return xs; | ||
| }; | ||
| $.delete = function (xs, x) { | ||
| var idx = xs.indexOf(x); | ||
| var idx = $.indexOfBy(eq, xs, x); | ||
| if (idx >= 0) { | ||
@@ -117,104 +68,44 @@ xs.splice(idx, 1); | ||
| }; | ||
| $.delete = (xs, x) => $.deleteBy(eq2, xs, x); | ||
| // "Set" operations | ||
| // Pure operations | ||
| $.intersectBy = function (eq, xs, ys) { | ||
| var result = [] | ||
| , xLen = xs.length | ||
| , yLen = ys.length; | ||
| if (xLen && yLen) { | ||
| for (var i = 0; i < xLen; i += 1) { | ||
| var x = xs[i]; | ||
| for (var j = 0; j < yLen; j += 1) { | ||
| if (eq(x, ys[j])) { | ||
| result.push(x); | ||
| break; | ||
| } | ||
| } | ||
| return xs.reduce((acc, x) => { | ||
| if ($.indexOfBy(eq, ys, x) >= 0) { | ||
| acc.push(x); | ||
| } | ||
| } | ||
| return result; | ||
| return acc; | ||
| }, []); | ||
| }; | ||
| $.intersect = (xs, ys) => $.intersectBy(eq2, xs, ys); | ||
| $.intersect = function (xs, ys) { | ||
| return $.intersectBy(eq2, xs, ys); | ||
| }; | ||
| $.nubBy = function (eq, xs) { | ||
| var result = []; | ||
| for (var i = 0, len = xs.length; i < len; i += 1) { | ||
| var keep = true; | ||
| for (var j = 0, resLen = result.length; j < resLen; j += 1) { | ||
| if (eq(result[j], xs[i])) { | ||
| keep = false; | ||
| break; | ||
| } | ||
| $.uniqueBy = function (eq, xs) { | ||
| return xs.reduce((acc, x) => { | ||
| if ($.indexOfBy(eq, acc, x) < 0) { | ||
| acc.push(x); | ||
| } | ||
| if (keep) { | ||
| result.push(xs[i]); | ||
| } | ||
| } | ||
| return result; | ||
| return acc; | ||
| }, []); | ||
| }; | ||
| $.unique = (xs) => $.uniqueBy(eq2, xs); | ||
| // nub, build up a list of unique (w.r.t. equality) | ||
| // elements by checking if current is not 'equal' to anything in the buildup | ||
| // http://jsperf.com/nubnubbytest1 => indexOf clearly beats calling $.nubBy(eq2) | ||
| $.nub = function (xs) { | ||
| $.groupBy = function (eq, xs) { | ||
| var result = []; | ||
| for (var i = 0, len = xs.length; i < len; i += 1) { | ||
| if (result.indexOf(xs[i]) < 0) { | ||
| result.push(xs[i]); | ||
| } | ||
| for (var i = 0, j = 0, len = xs.length; i < len; i = j) { | ||
| for (j = i + 1; j < len && eq(xs[i], xs[j]);) { j += 1; } | ||
| result.push(xs.slice(i, j)); | ||
| } | ||
| return result; | ||
| }; | ||
| $.group = (xs) => $.groupBy(eq2, xs); | ||
| $.groupBy = function (eq, xs) { | ||
| var result = [] | ||
| , j, sub; | ||
| for (var i = 0, len = xs.length; i < len; i = j) { | ||
| sub = [xs[i]]; | ||
| for (j = i + 1; j < len && eq(xs[i], xs[j]); j += 1) { | ||
| sub.push(xs[j]); | ||
| } | ||
| result.push(sub); | ||
| } | ||
| return result; | ||
| }; | ||
| $.group = function (xs) { | ||
| return $.groupBy(eq2, xs); | ||
| }; | ||
| $.unionBy = function (eq, xs, ys) { | ||
| return xs.concat(xs.reduce($.deleteBy.bind(null, eq), $.nubBy(eq, ys))); | ||
| return xs.concat(xs.reduce($.deleteBy.bind(null, eq), $.uniqueBy(eq, ys))); | ||
| }; | ||
| $.union = (xs, ys) => xs.concat(xs.reduce($.delete, $.unique(ys))); | ||
| $.union = function (xs, ys) { | ||
| return xs.concat(xs.reduce($.delete, $.nub(ys))); | ||
| }; | ||
| $.differenceBy = (eq, xs, ys) => ys.reduce($.deleteBy.bind(null, eq), xs.slice()); | ||
| $.difference = (xs, ys) => ys.reduce($.delete, xs.slice()); | ||
| $.differenceBy = function (eq, xs, ys) { | ||
| return ys.reduce($.deleteBy.bind(null, eq), xs.slice()); // reduce a copy | ||
| }; | ||
| $.difference = function (xs, ys) { | ||
| return ys.reduce($.delete, xs.slice()); | ||
| }; | ||
| $.isSubsetOf = function (xs, ys, proper) { | ||
| var parent = ys.slice(); | ||
| for (var i = 0; i < xs.length; i += 1) { | ||
| var x = xs[i] | ||
| , idx = parent.indexOf(x); | ||
| if (idx < 0) { | ||
| return false; | ||
| } | ||
| parent.splice(idx, 1); | ||
| } | ||
| return (proper) ? (parent.length > 0) : true; | ||
| }; | ||
| // end - export | ||
| module.exports = $; |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
1
-50%19636
-0.58%92
-51.58%42
-16%1
Infinity%