subset
Advanced tools
Comparing version 0.1.7 to 1.0.0
297
api.md
# 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`. |
@@ -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 @@ ================== |
@@ -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" | ||
} |
@@ -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. |
215
subset.js
@@ -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
19636
92
42
1