├── .eslintrc.json ├── .github ├── eslint.json └── workflows │ └── node.js.yml ├── .gitignore ├── LICENSE ├── README.md ├── img └── histogram.png ├── package.json ├── rollup.config.js ├── src ├── array.js ├── ascending.js ├── bin.js ├── bisect.js ├── bisector.js ├── blur.js ├── constant.js ├── count.js ├── cross.js ├── cumsum.js ├── descending.js ├── deviation.js ├── difference.js ├── disjoint.js ├── every.js ├── extent.js ├── filter.js ├── fsum.js ├── greatest.js ├── greatestIndex.js ├── group.js ├── groupSort.js ├── identity.js ├── index.js ├── intersection.js ├── least.js ├── leastIndex.js ├── map.js ├── max.js ├── maxIndex.js ├── mean.js ├── median.js ├── merge.js ├── min.js ├── minIndex.js ├── mode.js ├── nice.js ├── number.js ├── pairs.js ├── permute.js ├── quantile.js ├── quickselect.js ├── range.js ├── rank.js ├── reduce.js ├── reverse.js ├── scan.js ├── shuffle.js ├── some.js ├── sort.js ├── subset.js ├── sum.js ├── superset.js ├── threshold │ ├── freedmanDiaconis.js │ ├── scott.js │ └── sturges.js ├── ticks.js ├── transpose.js ├── union.js ├── variance.js └── zip.js ├── test ├── .eslintrc.json ├── OneTimeNumber.js ├── ascending-test.js ├── asserts.js ├── bin-test.js ├── bisect-test.js ├── bisector-test.js ├── blur-test.js ├── count-test.js ├── cross-test.js ├── cumsum-test.js ├── data │ ├── athletes.csv │ └── barley.json ├── descending-test.js ├── deviation-test.js ├── difference-test.js ├── disjoint-test.js ├── every-test.js ├── extent-test.js ├── fcumsum-test.js ├── filter-test.js ├── flatGroup-test.js ├── flatRollup-test.js ├── fsum-test.js ├── greatest-test.js ├── greatestIndex-test.js ├── group-test.js ├── groupSort-test.js ├── groups-test.js ├── index-test.js ├── intersection-test.js ├── least-test.js ├── leastIndex-test.js ├── map-test.js ├── max-test.js ├── maxIndex-test.js ├── mean-test.js ├── median-test.js ├── merge-test.js ├── min-test.js ├── minIndex-test.js ├── mode-test.js ├── nice-test.js ├── pairs-test.js ├── permute-test.js ├── quantile-test.js ├── quickselect-test.js ├── range-test.js ├── rank-test.js ├── reduce-test.js ├── reverse-test.js ├── rollup-test.js ├── rollups-test.js ├── scan-test.js ├── shuffle-test.js ├── some-test.js ├── sort-test.js ├── subset-test.js ├── sum-test.js ├── superset-test.js ├── threshold │ ├── freedmanDiaconic-test.js │ ├── scott-test.js │ └── sturges-test.js ├── tickIncrement-test.js ├── tickStep-test.js ├── ticks-test.js ├── transpose-test.js ├── union-test.js ├── variance-test.js └── zip-test.js └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "parserOptions": { 4 | "sourceType": "module", 5 | "ecmaVersion": 8 6 | }, 7 | "env": { 8 | "es6": true 9 | }, 10 | "rules": { 11 | "no-cond-assign": 0, 12 | "no-constant-condition": 0 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.github/eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "problemMatcher": [ 3 | { 4 | "owner": "eslint-compact", 5 | "pattern": [ 6 | { 7 | "regexp": "^(.+):\\sline\\s(\\d+),\\scol\\s(\\d+),\\s(Error|Warning|Info)\\s-\\s(.+)\\s\\((.+)\\)$", 8 | "file": 1, 9 | "line": 2, 10 | "column": 3, 11 | "severity": 4, 12 | "message": 5, 13 | "code": 6 14 | } 15 | ] 16 | } 17 | ] 18 | } 19 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 2 | 3 | name: Node.js CI 4 | 5 | on: 6 | push: 7 | branches: [ main ] 8 | pull_request: 9 | branches: [ main ] 10 | 11 | jobs: 12 | build: 13 | 14 | runs-on: ubuntu-latest 15 | 16 | strategy: 17 | matrix: 18 | node-version: [14.x] 19 | 20 | steps: 21 | - uses: actions/checkout@v2 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v1 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | - run: yarn --frozen-lockfile 27 | - run: | 28 | echo ::add-matcher::.github/eslint.json 29 | yarn run eslint src test --format=compact 30 | - run: yarn test 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.sublime-workspace 2 | .DS_Store 3 | dist/ 4 | node_modules 5 | npm-debug.log 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2010-2023 Mike Bostock 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any purpose 4 | with or without fee is hereby granted, provided that the above copyright notice 5 | and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND 9 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 11 | OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER 12 | TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF 13 | THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # d3-array 2 | 3 | 4 | 5 | Array manipulation, ordering, searching, summarizing, etc. 6 | 7 | ## Resources 8 | 9 | - [Documentation](https://d3js.org/d3-array) 10 | - [Examples](https://observablehq.com/collection/@d3/d3-array) 11 | - [Releases](https://github.com/d3/d3-array/releases) 12 | - [Getting help](https://d3js.org/community) 13 | -------------------------------------------------------------------------------- /img/histogram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/d3/d3-array/be0ae0d2b36ab91b833294ad2cfc5d5905acbd0f/img/histogram.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "d3-array", 3 | "version": "3.2.4", 4 | "description": "Array manipulation, ordering, searching, summarizing, etc.", 5 | "homepage": "https://d3js.org/d3-array/", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/d3/d3-array.git" 9 | }, 10 | "keywords": [ 11 | "d3", 12 | "d3-module", 13 | "histogram", 14 | "bisect", 15 | "shuffle", 16 | "statistics", 17 | "search", 18 | "sort", 19 | "array" 20 | ], 21 | "license": "ISC", 22 | "author": { 23 | "name": "Mike Bostock", 24 | "url": "http://bost.ocks.org/mike" 25 | }, 26 | "type": "module", 27 | "files": [ 28 | "dist/**/*.js", 29 | "src/**/*.js" 30 | ], 31 | "module": "src/index.js", 32 | "main": "src/index.js", 33 | "jsdelivr": "dist/d3-array.min.js", 34 | "unpkg": "dist/d3-array.min.js", 35 | "exports": { 36 | "umd": "./dist/d3-array.min.js", 37 | "default": "./src/index.js" 38 | }, 39 | "sideEffects": false, 40 | "dependencies": { 41 | "internmap": "1 - 2" 42 | }, 43 | "devDependencies": { 44 | "@rollup/plugin-node-resolve": "15", 45 | "d3-dsv": "3", 46 | "d3-random": "2 - 3", 47 | "eslint": "8", 48 | "jsdom": "21", 49 | "mocha": "10", 50 | "rollup": "3", 51 | "rollup-plugin-terser": "7" 52 | }, 53 | "scripts": { 54 | "test": "mocha 'test/**/*-test.js' && eslint src test", 55 | "prepublishOnly": "rm -rf dist && rollup -c", 56 | "postpublish": "git push && git push --tags && cd ../d3.github.com && git pull && cp ../${npm_package_name}/dist/${npm_package_name}.js ${npm_package_name}.v${npm_package_version%%.*}.js && cp ../${npm_package_name}/dist/${npm_package_name}.min.js ${npm_package_name}.v${npm_package_version%%.*}.min.js && git add ${npm_package_name}.v${npm_package_version%%.*}.js ${npm_package_name}.v${npm_package_version%%.*}.min.js && git commit -m \"${npm_package_name} ${npm_package_version}\" && git push && cd -" 57 | }, 58 | "engines": { 59 | "node": ">=12" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import {nodeResolve} from "@rollup/plugin-node-resolve"; 2 | import {readFileSync} from "fs"; 3 | import {terser} from "rollup-plugin-terser"; 4 | import meta from "./package.json" assert {type: "json"}; 5 | 6 | // Extract copyrights from the LICENSE. 7 | const copyright = readFileSync("./LICENSE", "utf-8") 8 | .split(/\n/g) 9 | .filter(line => /^copyright\s+/i.test(line)) 10 | .map(line => line.replace(/^copyright\s+/i, "")) 11 | .join(", "); 12 | 13 | const config = { 14 | input: "src/index.js", 15 | external: Object.keys(meta.dependencies || {}).filter(key => /^d3-/.test(key)), 16 | output: { 17 | file: `dist/${meta.name}.js`, 18 | name: "d3", 19 | format: "umd", 20 | indent: false, 21 | extend: true, 22 | banner: `// ${meta.homepage} v${meta.version} Copyright ${copyright}`, 23 | globals: Object.assign({}, ...Object.keys(meta.dependencies || {}).filter(key => /^d3-/.test(key)).map(key => ({[key]: "d3"}))) 24 | }, 25 | plugins: [ 26 | nodeResolve() 27 | ] 28 | }; 29 | 30 | export default [ 31 | config, 32 | { 33 | ...config, 34 | output: { 35 | ...config.output, 36 | file: `dist/${meta.name}.min.js` 37 | }, 38 | plugins: [ 39 | ...config.plugins, 40 | terser({ 41 | output: { 42 | preamble: config.output.banner 43 | }, 44 | mangle: { 45 | reserved: [ 46 | "InternMap", 47 | "InternSet" 48 | ] 49 | } 50 | }) 51 | ] 52 | } 53 | ]; 54 | -------------------------------------------------------------------------------- /src/array.js: -------------------------------------------------------------------------------- 1 | var array = Array.prototype; 2 | 3 | export var slice = array.slice; 4 | export var map = array.map; 5 | -------------------------------------------------------------------------------- /src/ascending.js: -------------------------------------------------------------------------------- 1 | export default function ascending(a, b) { 2 | return a == null || b == null ? NaN : a < b ? -1 : a > b ? 1 : a >= b ? 0 : NaN; 3 | } 4 | -------------------------------------------------------------------------------- /src/bin.js: -------------------------------------------------------------------------------- 1 | import {slice} from "./array.js"; 2 | import bisect from "./bisect.js"; 3 | import constant from "./constant.js"; 4 | import extent from "./extent.js"; 5 | import identity from "./identity.js"; 6 | import nice from "./nice.js"; 7 | import ticks, {tickIncrement} from "./ticks.js"; 8 | import sturges from "./threshold/sturges.js"; 9 | 10 | export default function bin() { 11 | var value = identity, 12 | domain = extent, 13 | threshold = sturges; 14 | 15 | function histogram(data) { 16 | if (!Array.isArray(data)) data = Array.from(data); 17 | 18 | var i, 19 | n = data.length, 20 | x, 21 | step, 22 | values = new Array(n); 23 | 24 | for (i = 0; i < n; ++i) { 25 | values[i] = value(data[i], i, data); 26 | } 27 | 28 | var xz = domain(values), 29 | x0 = xz[0], 30 | x1 = xz[1], 31 | tz = threshold(values, x0, x1); 32 | 33 | // Convert number of thresholds into uniform thresholds, and nice the 34 | // default domain accordingly. 35 | if (!Array.isArray(tz)) { 36 | const max = x1, tn = +tz; 37 | if (domain === extent) [x0, x1] = nice(x0, x1, tn); 38 | tz = ticks(x0, x1, tn); 39 | 40 | // If the domain is aligned with the first tick (which it will by 41 | // default), then we can use quantization rather than bisection to bin 42 | // values, which is substantially faster. 43 | if (tz[0] <= x0) step = tickIncrement(x0, x1, tn); 44 | 45 | // If the last threshold is coincident with the domain’s upper bound, the 46 | // last bin will be zero-width. If the default domain is used, and this 47 | // last threshold is coincident with the maximum input value, we can 48 | // extend the niced upper bound by one tick to ensure uniform bin widths; 49 | // otherwise, we simply remove the last threshold. Note that we don’t 50 | // coerce values or the domain to numbers, and thus must be careful to 51 | // compare order (>=) rather than strict equality (===)! 52 | if (tz[tz.length - 1] >= x1) { 53 | if (max >= x1 && domain === extent) { 54 | const step = tickIncrement(x0, x1, tn); 55 | if (isFinite(step)) { 56 | if (step > 0) { 57 | x1 = (Math.floor(x1 / step) + 1) * step; 58 | } else if (step < 0) { 59 | x1 = (Math.ceil(x1 * -step) + 1) / -step; 60 | } 61 | } 62 | } else { 63 | tz.pop(); 64 | } 65 | } 66 | } 67 | 68 | // Remove any thresholds outside the domain. 69 | // Be careful not to mutate an array owned by the user! 70 | var m = tz.length, a = 0, b = m; 71 | while (tz[a] <= x0) ++a; 72 | while (tz[b - 1] > x1) --b; 73 | if (a || b < m) tz = tz.slice(a, b), m = b - a; 74 | 75 | var bins = new Array(m + 1), 76 | bin; 77 | 78 | // Initialize bins. 79 | for (i = 0; i <= m; ++i) { 80 | bin = bins[i] = []; 81 | bin.x0 = i > 0 ? tz[i - 1] : x0; 82 | bin.x1 = i < m ? tz[i] : x1; 83 | } 84 | 85 | // Assign data to bins by value, ignoring any outside the domain. 86 | if (isFinite(step)) { 87 | if (step > 0) { 88 | for (i = 0; i < n; ++i) { 89 | if ((x = values[i]) != null && x0 <= x && x <= x1) { 90 | bins[Math.min(m, Math.floor((x - x0) / step))].push(data[i]); 91 | } 92 | } 93 | } else if (step < 0) { 94 | for (i = 0; i < n; ++i) { 95 | if ((x = values[i]) != null && x0 <= x && x <= x1) { 96 | const j = Math.floor((x0 - x) * step); 97 | bins[Math.min(m, j + (tz[j] <= x))].push(data[i]); // handle off-by-one due to rounding 98 | } 99 | } 100 | } 101 | } else { 102 | for (i = 0; i < n; ++i) { 103 | if ((x = values[i]) != null && x0 <= x && x <= x1) { 104 | bins[bisect(tz, x, 0, m)].push(data[i]); 105 | } 106 | } 107 | } 108 | 109 | return bins; 110 | } 111 | 112 | histogram.value = function(_) { 113 | return arguments.length ? (value = typeof _ === "function" ? _ : constant(_), histogram) : value; 114 | }; 115 | 116 | histogram.domain = function(_) { 117 | return arguments.length ? (domain = typeof _ === "function" ? _ : constant([_[0], _[1]]), histogram) : domain; 118 | }; 119 | 120 | histogram.thresholds = function(_) { 121 | return arguments.length ? (threshold = typeof _ === "function" ? _ : constant(Array.isArray(_) ? slice.call(_) : _), histogram) : threshold; 122 | }; 123 | 124 | return histogram; 125 | } 126 | -------------------------------------------------------------------------------- /src/bisect.js: -------------------------------------------------------------------------------- 1 | import ascending from "./ascending.js"; 2 | import bisector from "./bisector.js"; 3 | import number from "./number.js"; 4 | 5 | const ascendingBisect = bisector(ascending); 6 | export const bisectRight = ascendingBisect.right; 7 | export const bisectLeft = ascendingBisect.left; 8 | export const bisectCenter = bisector(number).center; 9 | export default bisectRight; 10 | -------------------------------------------------------------------------------- /src/bisector.js: -------------------------------------------------------------------------------- 1 | import ascending from "./ascending.js"; 2 | import descending from "./descending.js"; 3 | 4 | export default function bisector(f) { 5 | let compare1, compare2, delta; 6 | 7 | // If an accessor is specified, promote it to a comparator. In this case we 8 | // can test whether the search value is (self-) comparable. We can’t do this 9 | // for a comparator (except for specific, known comparators) because we can’t 10 | // tell if the comparator is symmetric, and an asymmetric comparator can’t be 11 | // used to test whether a single value is comparable. 12 | if (f.length !== 2) { 13 | compare1 = ascending; 14 | compare2 = (d, x) => ascending(f(d), x); 15 | delta = (d, x) => f(d) - x; 16 | } else { 17 | compare1 = f === ascending || f === descending ? f : zero; 18 | compare2 = f; 19 | delta = f; 20 | } 21 | 22 | function left(a, x, lo = 0, hi = a.length) { 23 | if (lo < hi) { 24 | if (compare1(x, x) !== 0) return hi; 25 | do { 26 | const mid = (lo + hi) >>> 1; 27 | if (compare2(a[mid], x) < 0) lo = mid + 1; 28 | else hi = mid; 29 | } while (lo < hi); 30 | } 31 | return lo; 32 | } 33 | 34 | function right(a, x, lo = 0, hi = a.length) { 35 | if (lo < hi) { 36 | if (compare1(x, x) !== 0) return hi; 37 | do { 38 | const mid = (lo + hi) >>> 1; 39 | if (compare2(a[mid], x) <= 0) lo = mid + 1; 40 | else hi = mid; 41 | } while (lo < hi); 42 | } 43 | return lo; 44 | } 45 | 46 | function center(a, x, lo = 0, hi = a.length) { 47 | const i = left(a, x, lo, hi - 1); 48 | return i > lo && delta(a[i - 1], x) > -delta(a[i], x) ? i - 1 : i; 49 | } 50 | 51 | return {left, center, right}; 52 | } 53 | 54 | function zero() { 55 | return 0; 56 | } 57 | -------------------------------------------------------------------------------- /src/blur.js: -------------------------------------------------------------------------------- 1 | export function blur(values, r) { 2 | if (!((r = +r) >= 0)) throw new RangeError("invalid r"); 3 | let length = values.length; 4 | if (!((length = Math.floor(length)) >= 0)) throw new RangeError("invalid length"); 5 | if (!length || !r) return values; 6 | const blur = blurf(r); 7 | const temp = values.slice(); 8 | blur(values, temp, 0, length, 1); 9 | blur(temp, values, 0, length, 1); 10 | blur(values, temp, 0, length, 1); 11 | return values; 12 | } 13 | 14 | export const blur2 = Blur2(blurf); 15 | 16 | export const blurImage = Blur2(blurfImage); 17 | 18 | function Blur2(blur) { 19 | return function(data, rx, ry = rx) { 20 | if (!((rx = +rx) >= 0)) throw new RangeError("invalid rx"); 21 | if (!((ry = +ry) >= 0)) throw new RangeError("invalid ry"); 22 | let {data: values, width, height} = data; 23 | if (!((width = Math.floor(width)) >= 0)) throw new RangeError("invalid width"); 24 | if (!((height = Math.floor(height !== undefined ? height : values.length / width)) >= 0)) throw new RangeError("invalid height"); 25 | if (!width || !height || (!rx && !ry)) return data; 26 | const blurx = rx && blur(rx); 27 | const blury = ry && blur(ry); 28 | const temp = values.slice(); 29 | if (blurx && blury) { 30 | blurh(blurx, temp, values, width, height); 31 | blurh(blurx, values, temp, width, height); 32 | blurh(blurx, temp, values, width, height); 33 | blurv(blury, values, temp, width, height); 34 | blurv(blury, temp, values, width, height); 35 | blurv(blury, values, temp, width, height); 36 | } else if (blurx) { 37 | blurh(blurx, values, temp, width, height); 38 | blurh(blurx, temp, values, width, height); 39 | blurh(blurx, values, temp, width, height); 40 | } else if (blury) { 41 | blurv(blury, values, temp, width, height); 42 | blurv(blury, temp, values, width, height); 43 | blurv(blury, values, temp, width, height); 44 | } 45 | return data; 46 | }; 47 | } 48 | 49 | function blurh(blur, T, S, w, h) { 50 | for (let y = 0, n = w * h; y < n;) { 51 | blur(T, S, y, y += w, 1); 52 | } 53 | } 54 | 55 | function blurv(blur, T, S, w, h) { 56 | for (let x = 0, n = w * h; x < w; ++x) { 57 | blur(T, S, x, x + n, w); 58 | } 59 | } 60 | 61 | function blurfImage(radius) { 62 | const blur = blurf(radius); 63 | return (T, S, start, stop, step) => { 64 | start <<= 2, stop <<= 2, step <<= 2; 65 | blur(T, S, start + 0, stop + 0, step); 66 | blur(T, S, start + 1, stop + 1, step); 67 | blur(T, S, start + 2, stop + 2, step); 68 | blur(T, S, start + 3, stop + 3, step); 69 | }; 70 | } 71 | 72 | // Given a target array T, a source array S, sets each value T[i] to the average 73 | // of {S[i - r], …, S[i], …, S[i + r]}, where r = ⌊radius⌋, start <= i < stop, 74 | // for each i, i + step, i + 2 * step, etc., and where S[j] is clamped between 75 | // S[start] (inclusive) and S[stop] (exclusive). If the given radius is not an 76 | // integer, S[i - r - 1] and S[i + r + 1] are added to the sum, each weighted 77 | // according to r - ⌊radius⌋. 78 | function blurf(radius) { 79 | const radius0 = Math.floor(radius); 80 | if (radius0 === radius) return bluri(radius); 81 | const t = radius - radius0; 82 | const w = 2 * radius + 1; 83 | return (T, S, start, stop, step) => { // stop must be aligned! 84 | if (!((stop -= step) >= start)) return; // inclusive stop 85 | let sum = radius0 * S[start]; 86 | const s0 = step * radius0; 87 | const s1 = s0 + step; 88 | for (let i = start, j = start + s0; i < j; i += step) { 89 | sum += S[Math.min(stop, i)]; 90 | } 91 | for (let i = start, j = stop; i <= j; i += step) { 92 | sum += S[Math.min(stop, i + s0)]; 93 | T[i] = (sum + t * (S[Math.max(start, i - s1)] + S[Math.min(stop, i + s1)])) / w; 94 | sum -= S[Math.max(start, i - s0)]; 95 | } 96 | }; 97 | } 98 | 99 | // Like blurf, but optimized for integer radius. 100 | function bluri(radius) { 101 | const w = 2 * radius + 1; 102 | return (T, S, start, stop, step) => { // stop must be aligned! 103 | if (!((stop -= step) >= start)) return; // inclusive stop 104 | let sum = radius * S[start]; 105 | const s = step * radius; 106 | for (let i = start, j = start + s; i < j; i += step) { 107 | sum += S[Math.min(stop, i)]; 108 | } 109 | for (let i = start, j = stop; i <= j; i += step) { 110 | sum += S[Math.min(stop, i + s)]; 111 | T[i] = sum / w; 112 | sum -= S[Math.max(start, i - s)]; 113 | } 114 | }; 115 | } 116 | -------------------------------------------------------------------------------- /src/constant.js: -------------------------------------------------------------------------------- 1 | export default function constant(x) { 2 | return () => x; 3 | } 4 | -------------------------------------------------------------------------------- /src/count.js: -------------------------------------------------------------------------------- 1 | export default function count(values, valueof) { 2 | let count = 0; 3 | if (valueof === undefined) { 4 | for (let value of values) { 5 | if (value != null && (value = +value) >= value) { 6 | ++count; 7 | } 8 | } 9 | } else { 10 | let index = -1; 11 | for (let value of values) { 12 | if ((value = valueof(value, ++index, values)) != null && (value = +value) >= value) { 13 | ++count; 14 | } 15 | } 16 | } 17 | return count; 18 | } 19 | -------------------------------------------------------------------------------- /src/cross.js: -------------------------------------------------------------------------------- 1 | function length(array) { 2 | return array.length | 0; 3 | } 4 | 5 | function empty(length) { 6 | return !(length > 0); 7 | } 8 | 9 | function arrayify(values) { 10 | return typeof values !== "object" || "length" in values ? values : Array.from(values); 11 | } 12 | 13 | function reducer(reduce) { 14 | return values => reduce(...values); 15 | } 16 | 17 | export default function cross(...values) { 18 | const reduce = typeof values[values.length - 1] === "function" && reducer(values.pop()); 19 | values = values.map(arrayify); 20 | const lengths = values.map(length); 21 | const j = values.length - 1; 22 | const index = new Array(j + 1).fill(0); 23 | const product = []; 24 | if (j < 0 || lengths.some(empty)) return product; 25 | while (true) { 26 | product.push(index.map((j, i) => values[i][j])); 27 | let i = j; 28 | while (++index[i] === lengths[i]) { 29 | if (i === 0) return reduce ? product.map(reduce) : product; 30 | index[i--] = 0; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/cumsum.js: -------------------------------------------------------------------------------- 1 | export default function cumsum(values, valueof) { 2 | var sum = 0, index = 0; 3 | return Float64Array.from(values, valueof === undefined 4 | ? v => (sum += +v || 0) 5 | : v => (sum += +valueof(v, index++, values) || 0)); 6 | } -------------------------------------------------------------------------------- /src/descending.js: -------------------------------------------------------------------------------- 1 | export default function descending(a, b) { 2 | return a == null || b == null ? NaN 3 | : b < a ? -1 4 | : b > a ? 1 5 | : b >= a ? 0 6 | : NaN; 7 | } 8 | -------------------------------------------------------------------------------- /src/deviation.js: -------------------------------------------------------------------------------- 1 | import variance from "./variance.js"; 2 | 3 | export default function deviation(values, valueof) { 4 | const v = variance(values, valueof); 5 | return v ? Math.sqrt(v) : v; 6 | } 7 | -------------------------------------------------------------------------------- /src/difference.js: -------------------------------------------------------------------------------- 1 | import {InternSet} from "internmap"; 2 | 3 | export default function difference(values, ...others) { 4 | values = new InternSet(values); 5 | for (const other of others) { 6 | for (const value of other) { 7 | values.delete(value); 8 | } 9 | } 10 | return values; 11 | } 12 | -------------------------------------------------------------------------------- /src/disjoint.js: -------------------------------------------------------------------------------- 1 | import {InternSet} from "internmap"; 2 | 3 | export default function disjoint(values, other) { 4 | const iterator = other[Symbol.iterator](), set = new InternSet(); 5 | for (const v of values) { 6 | if (set.has(v)) return false; 7 | let value, done; 8 | while (({value, done} = iterator.next())) { 9 | if (done) break; 10 | if (Object.is(v, value)) return false; 11 | set.add(value); 12 | } 13 | } 14 | return true; 15 | } 16 | -------------------------------------------------------------------------------- /src/every.js: -------------------------------------------------------------------------------- 1 | export default function every(values, test) { 2 | if (typeof test !== "function") throw new TypeError("test is not a function"); 3 | let index = -1; 4 | for (const value of values) { 5 | if (!test(value, ++index, values)) { 6 | return false; 7 | } 8 | } 9 | return true; 10 | } 11 | -------------------------------------------------------------------------------- /src/extent.js: -------------------------------------------------------------------------------- 1 | export default function extent(values, valueof) { 2 | let min; 3 | let max; 4 | if (valueof === undefined) { 5 | for (const value of values) { 6 | if (value != null) { 7 | if (min === undefined) { 8 | if (value >= value) min = max = value; 9 | } else { 10 | if (min > value) min = value; 11 | if (max < value) max = value; 12 | } 13 | } 14 | } 15 | } else { 16 | let index = -1; 17 | for (let value of values) { 18 | if ((value = valueof(value, ++index, values)) != null) { 19 | if (min === undefined) { 20 | if (value >= value) min = max = value; 21 | } else { 22 | if (min > value) min = value; 23 | if (max < value) max = value; 24 | } 25 | } 26 | } 27 | } 28 | return [min, max]; 29 | } 30 | -------------------------------------------------------------------------------- /src/filter.js: -------------------------------------------------------------------------------- 1 | export default function filter(values, test) { 2 | if (typeof test !== "function") throw new TypeError("test is not a function"); 3 | const array = []; 4 | let index = -1; 5 | for (const value of values) { 6 | if (test(value, ++index, values)) { 7 | array.push(value); 8 | } 9 | } 10 | return array; 11 | } 12 | -------------------------------------------------------------------------------- /src/fsum.js: -------------------------------------------------------------------------------- 1 | // https://github.com/python/cpython/blob/a74eea238f5baba15797e2e8b570d153bc8690a7/Modules/mathmodule.c#L1423 2 | export class Adder { 3 | constructor() { 4 | this._partials = new Float64Array(32); 5 | this._n = 0; 6 | } 7 | add(x) { 8 | const p = this._partials; 9 | let i = 0; 10 | for (let j = 0; j < this._n && j < 32; j++) { 11 | const y = p[j], 12 | hi = x + y, 13 | lo = Math.abs(x) < Math.abs(y) ? x - (hi - y) : y - (hi - x); 14 | if (lo) p[i++] = lo; 15 | x = hi; 16 | } 17 | p[i] = x; 18 | this._n = i + 1; 19 | return this; 20 | } 21 | valueOf() { 22 | const p = this._partials; 23 | let n = this._n, x, y, lo, hi = 0; 24 | if (n > 0) { 25 | hi = p[--n]; 26 | while (n > 0) { 27 | x = hi; 28 | y = p[--n]; 29 | hi = x + y; 30 | lo = y - (hi - x); 31 | if (lo) break; 32 | } 33 | if (n > 0 && ((lo < 0 && p[n - 1] < 0) || (lo > 0 && p[n - 1] > 0))) { 34 | y = lo * 2; 35 | x = hi + y; 36 | if (y == x - hi) hi = x; 37 | } 38 | } 39 | return hi; 40 | } 41 | } 42 | 43 | export function fsum(values, valueof) { 44 | const adder = new Adder(); 45 | if (valueof === undefined) { 46 | for (let value of values) { 47 | if (value = +value) { 48 | adder.add(value); 49 | } 50 | } 51 | } else { 52 | let index = -1; 53 | for (let value of values) { 54 | if (value = +valueof(value, ++index, values)) { 55 | adder.add(value); 56 | } 57 | } 58 | } 59 | return +adder; 60 | } 61 | 62 | export function fcumsum(values, valueof) { 63 | const adder = new Adder(); 64 | let index = -1; 65 | return Float64Array.from(values, valueof === undefined 66 | ? v => adder.add(+v || 0) 67 | : v => adder.add(+valueof(v, ++index, values) || 0) 68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /src/greatest.js: -------------------------------------------------------------------------------- 1 | import ascending from "./ascending.js"; 2 | 3 | export default function greatest(values, compare = ascending) { 4 | let max; 5 | let defined = false; 6 | if (compare.length === 1) { 7 | let maxValue; 8 | for (const element of values) { 9 | const value = compare(element); 10 | if (defined 11 | ? ascending(value, maxValue) > 0 12 | : ascending(value, value) === 0) { 13 | max = element; 14 | maxValue = value; 15 | defined = true; 16 | } 17 | } 18 | } else { 19 | for (const value of values) { 20 | if (defined 21 | ? compare(value, max) > 0 22 | : compare(value, value) === 0) { 23 | max = value; 24 | defined = true; 25 | } 26 | } 27 | } 28 | return max; 29 | } 30 | -------------------------------------------------------------------------------- /src/greatestIndex.js: -------------------------------------------------------------------------------- 1 | import ascending from "./ascending.js"; 2 | import maxIndex from "./maxIndex.js"; 3 | 4 | export default function greatestIndex(values, compare = ascending) { 5 | if (compare.length === 1) return maxIndex(values, compare); 6 | let maxValue; 7 | let max = -1; 8 | let index = -1; 9 | for (const value of values) { 10 | ++index; 11 | if (max < 0 12 | ? compare(value, value) === 0 13 | : compare(value, maxValue) > 0) { 14 | maxValue = value; 15 | max = index; 16 | } 17 | } 18 | return max; 19 | } 20 | -------------------------------------------------------------------------------- /src/group.js: -------------------------------------------------------------------------------- 1 | import {InternMap} from "internmap"; 2 | import identity from "./identity.js"; 3 | 4 | export default function group(values, ...keys) { 5 | return nest(values, identity, identity, keys); 6 | } 7 | 8 | export function groups(values, ...keys) { 9 | return nest(values, Array.from, identity, keys); 10 | } 11 | 12 | function flatten(groups, keys) { 13 | for (let i = 1, n = keys.length; i < n; ++i) { 14 | groups = groups.flatMap(g => g.pop().map(([key, value]) => [...g, key, value])); 15 | } 16 | return groups; 17 | } 18 | 19 | export function flatGroup(values, ...keys) { 20 | return flatten(groups(values, ...keys), keys); 21 | } 22 | 23 | export function flatRollup(values, reduce, ...keys) { 24 | return flatten(rollups(values, reduce, ...keys), keys); 25 | } 26 | 27 | export function rollup(values, reduce, ...keys) { 28 | return nest(values, identity, reduce, keys); 29 | } 30 | 31 | export function rollups(values, reduce, ...keys) { 32 | return nest(values, Array.from, reduce, keys); 33 | } 34 | 35 | export function index(values, ...keys) { 36 | return nest(values, identity, unique, keys); 37 | } 38 | 39 | export function indexes(values, ...keys) { 40 | return nest(values, Array.from, unique, keys); 41 | } 42 | 43 | function unique(values) { 44 | if (values.length !== 1) throw new Error("duplicate key"); 45 | return values[0]; 46 | } 47 | 48 | function nest(values, map, reduce, keys) { 49 | return (function regroup(values, i) { 50 | if (i >= keys.length) return reduce(values); 51 | const groups = new InternMap(); 52 | const keyof = keys[i++]; 53 | let index = -1; 54 | for (const value of values) { 55 | const key = keyof(value, ++index, values); 56 | const group = groups.get(key); 57 | if (group) group.push(value); 58 | else groups.set(key, [value]); 59 | } 60 | for (const [key, values] of groups) { 61 | groups.set(key, regroup(values, i)); 62 | } 63 | return map(groups); 64 | })(values, 0); 65 | } 66 | -------------------------------------------------------------------------------- /src/groupSort.js: -------------------------------------------------------------------------------- 1 | import ascending from "./ascending.js"; 2 | import group, {rollup} from "./group.js"; 3 | import sort from "./sort.js"; 4 | 5 | export default function groupSort(values, reduce, key) { 6 | return (reduce.length !== 2 7 | ? sort(rollup(values, reduce, key), (([ak, av], [bk, bv]) => ascending(av, bv) || ascending(ak, bk))) 8 | : sort(group(values, key), (([ak, av], [bk, bv]) => reduce(av, bv) || ascending(ak, bk)))) 9 | .map(([key]) => key); 10 | } 11 | -------------------------------------------------------------------------------- /src/identity.js: -------------------------------------------------------------------------------- 1 | export default function identity(x) { 2 | return x; 3 | } 4 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | export {default as bisect, bisectRight, bisectLeft, bisectCenter} from "./bisect.js"; 2 | export {default as ascending} from "./ascending.js"; 3 | export {default as bisector} from "./bisector.js"; 4 | export {blur, blur2, blurImage} from "./blur.js"; 5 | export {default as count} from "./count.js"; 6 | export {default as cross} from "./cross.js"; 7 | export {default as cumsum} from "./cumsum.js"; 8 | export {default as descending} from "./descending.js"; 9 | export {default as deviation} from "./deviation.js"; 10 | export {default as extent} from "./extent.js"; 11 | export {Adder, fsum, fcumsum} from "./fsum.js"; 12 | export {default as group, flatGroup, flatRollup, groups, index, indexes, rollup, rollups} from "./group.js"; 13 | export {default as groupSort} from "./groupSort.js"; 14 | export {default as bin, default as histogram} from "./bin.js"; // Deprecated; use bin. 15 | export {default as thresholdFreedmanDiaconis} from "./threshold/freedmanDiaconis.js"; 16 | export {default as thresholdScott} from "./threshold/scott.js"; 17 | export {default as thresholdSturges} from "./threshold/sturges.js"; 18 | export {default as max} from "./max.js"; 19 | export {default as maxIndex} from "./maxIndex.js"; 20 | export {default as mean} from "./mean.js"; 21 | export {default as median, medianIndex} from "./median.js"; 22 | export {default as merge} from "./merge.js"; 23 | export {default as min} from "./min.js"; 24 | export {default as minIndex} from "./minIndex.js"; 25 | export {default as mode} from "./mode.js"; 26 | export {default as nice} from "./nice.js"; 27 | export {default as pairs} from "./pairs.js"; 28 | export {default as permute} from "./permute.js"; 29 | export {default as quantile, quantileIndex, quantileSorted} from "./quantile.js"; 30 | export {default as quickselect} from "./quickselect.js"; 31 | export {default as range} from "./range.js"; 32 | export {default as rank} from "./rank.js"; 33 | export {default as least} from "./least.js"; 34 | export {default as leastIndex} from "./leastIndex.js"; 35 | export {default as greatest} from "./greatest.js"; 36 | export {default as greatestIndex} from "./greatestIndex.js"; 37 | export {default as scan} from "./scan.js"; // Deprecated; use leastIndex. 38 | export {default as shuffle, shuffler} from "./shuffle.js"; 39 | export {default as sum} from "./sum.js"; 40 | export {default as ticks, tickIncrement, tickStep} from "./ticks.js"; 41 | export {default as transpose} from "./transpose.js"; 42 | export {default as variance} from "./variance.js"; 43 | export {default as zip} from "./zip.js"; 44 | export {default as every} from "./every.js"; 45 | export {default as some} from "./some.js"; 46 | export {default as filter} from "./filter.js"; 47 | export {default as map} from "./map.js"; 48 | export {default as reduce} from "./reduce.js"; 49 | export {default as reverse} from "./reverse.js"; 50 | export {default as sort} from "./sort.js"; 51 | export {default as difference} from "./difference.js"; 52 | export {default as disjoint} from "./disjoint.js"; 53 | export {default as intersection} from "./intersection.js"; 54 | export {default as subset} from "./subset.js"; 55 | export {default as superset} from "./superset.js"; 56 | export {default as union} from "./union.js"; 57 | export {InternMap, InternSet} from "internmap"; 58 | -------------------------------------------------------------------------------- /src/intersection.js: -------------------------------------------------------------------------------- 1 | import {InternSet} from "internmap"; 2 | 3 | export default function intersection(values, ...others) { 4 | values = new InternSet(values); 5 | others = others.map(set); 6 | out: for (const value of values) { 7 | for (const other of others) { 8 | if (!other.has(value)) { 9 | values.delete(value); 10 | continue out; 11 | } 12 | } 13 | } 14 | return values; 15 | } 16 | 17 | function set(values) { 18 | return values instanceof InternSet ? values : new InternSet(values); 19 | } 20 | -------------------------------------------------------------------------------- /src/least.js: -------------------------------------------------------------------------------- 1 | import ascending from "./ascending.js"; 2 | 3 | export default function least(values, compare = ascending) { 4 | let min; 5 | let defined = false; 6 | if (compare.length === 1) { 7 | let minValue; 8 | for (const element of values) { 9 | const value = compare(element); 10 | if (defined 11 | ? ascending(value, minValue) < 0 12 | : ascending(value, value) === 0) { 13 | min = element; 14 | minValue = value; 15 | defined = true; 16 | } 17 | } 18 | } else { 19 | for (const value of values) { 20 | if (defined 21 | ? compare(value, min) < 0 22 | : compare(value, value) === 0) { 23 | min = value; 24 | defined = true; 25 | } 26 | } 27 | } 28 | return min; 29 | } 30 | -------------------------------------------------------------------------------- /src/leastIndex.js: -------------------------------------------------------------------------------- 1 | import ascending from "./ascending.js"; 2 | import minIndex from "./minIndex.js"; 3 | 4 | export default function leastIndex(values, compare = ascending) { 5 | if (compare.length === 1) return minIndex(values, compare); 6 | let minValue; 7 | let min = -1; 8 | let index = -1; 9 | for (const value of values) { 10 | ++index; 11 | if (min < 0 12 | ? compare(value, value) === 0 13 | : compare(value, minValue) < 0) { 14 | minValue = value; 15 | min = index; 16 | } 17 | } 18 | return min; 19 | } 20 | -------------------------------------------------------------------------------- /src/map.js: -------------------------------------------------------------------------------- 1 | export default function map(values, mapper) { 2 | if (typeof values[Symbol.iterator] !== "function") throw new TypeError("values is not iterable"); 3 | if (typeof mapper !== "function") throw new TypeError("mapper is not a function"); 4 | return Array.from(values, (value, index) => mapper(value, index, values)); 5 | } 6 | -------------------------------------------------------------------------------- /src/max.js: -------------------------------------------------------------------------------- 1 | export default function max(values, valueof) { 2 | let max; 3 | if (valueof === undefined) { 4 | for (const value of values) { 5 | if (value != null 6 | && (max < value || (max === undefined && value >= value))) { 7 | max = value; 8 | } 9 | } 10 | } else { 11 | let index = -1; 12 | for (let value of values) { 13 | if ((value = valueof(value, ++index, values)) != null 14 | && (max < value || (max === undefined && value >= value))) { 15 | max = value; 16 | } 17 | } 18 | } 19 | return max; 20 | } 21 | -------------------------------------------------------------------------------- /src/maxIndex.js: -------------------------------------------------------------------------------- 1 | export default function maxIndex(values, valueof) { 2 | let max; 3 | let maxIndex = -1; 4 | let index = -1; 5 | if (valueof === undefined) { 6 | for (const value of values) { 7 | ++index; 8 | if (value != null 9 | && (max < value || (max === undefined && value >= value))) { 10 | max = value, maxIndex = index; 11 | } 12 | } 13 | } else { 14 | for (let value of values) { 15 | if ((value = valueof(value, ++index, values)) != null 16 | && (max < value || (max === undefined && value >= value))) { 17 | max = value, maxIndex = index; 18 | } 19 | } 20 | } 21 | return maxIndex; 22 | } 23 | -------------------------------------------------------------------------------- /src/mean.js: -------------------------------------------------------------------------------- 1 | export default function mean(values, valueof) { 2 | let count = 0; 3 | let sum = 0; 4 | if (valueof === undefined) { 5 | for (let value of values) { 6 | if (value != null && (value = +value) >= value) { 7 | ++count, sum += value; 8 | } 9 | } 10 | } else { 11 | let index = -1; 12 | for (let value of values) { 13 | if ((value = valueof(value, ++index, values)) != null && (value = +value) >= value) { 14 | ++count, sum += value; 15 | } 16 | } 17 | } 18 | if (count) return sum / count; 19 | } 20 | -------------------------------------------------------------------------------- /src/median.js: -------------------------------------------------------------------------------- 1 | import quantile, {quantileIndex} from "./quantile.js"; 2 | 3 | export default function median(values, valueof) { 4 | return quantile(values, 0.5, valueof); 5 | } 6 | 7 | export function medianIndex(values, valueof) { 8 | return quantileIndex(values, 0.5, valueof); 9 | } 10 | -------------------------------------------------------------------------------- /src/merge.js: -------------------------------------------------------------------------------- 1 | function* flatten(arrays) { 2 | for (const array of arrays) { 3 | yield* array; 4 | } 5 | } 6 | 7 | export default function merge(arrays) { 8 | return Array.from(flatten(arrays)); 9 | } 10 | -------------------------------------------------------------------------------- /src/min.js: -------------------------------------------------------------------------------- 1 | export default function min(values, valueof) { 2 | let min; 3 | if (valueof === undefined) { 4 | for (const value of values) { 5 | if (value != null 6 | && (min > value || (min === undefined && value >= value))) { 7 | min = value; 8 | } 9 | } 10 | } else { 11 | let index = -1; 12 | for (let value of values) { 13 | if ((value = valueof(value, ++index, values)) != null 14 | && (min > value || (min === undefined && value >= value))) { 15 | min = value; 16 | } 17 | } 18 | } 19 | return min; 20 | } 21 | -------------------------------------------------------------------------------- /src/minIndex.js: -------------------------------------------------------------------------------- 1 | export default function minIndex(values, valueof) { 2 | let min; 3 | let minIndex = -1; 4 | let index = -1; 5 | if (valueof === undefined) { 6 | for (const value of values) { 7 | ++index; 8 | if (value != null 9 | && (min > value || (min === undefined && value >= value))) { 10 | min = value, minIndex = index; 11 | } 12 | } 13 | } else { 14 | for (let value of values) { 15 | if ((value = valueof(value, ++index, values)) != null 16 | && (min > value || (min === undefined && value >= value))) { 17 | min = value, minIndex = index; 18 | } 19 | } 20 | } 21 | return minIndex; 22 | } 23 | -------------------------------------------------------------------------------- /src/mode.js: -------------------------------------------------------------------------------- 1 | import {InternMap} from "internmap"; 2 | 3 | export default function mode(values, valueof) { 4 | const counts = new InternMap(); 5 | if (valueof === undefined) { 6 | for (let value of values) { 7 | if (value != null && value >= value) { 8 | counts.set(value, (counts.get(value) || 0) + 1); 9 | } 10 | } 11 | } else { 12 | let index = -1; 13 | for (let value of values) { 14 | if ((value = valueof(value, ++index, values)) != null && value >= value) { 15 | counts.set(value, (counts.get(value) || 0) + 1); 16 | } 17 | } 18 | } 19 | let modeValue; 20 | let modeCount = 0; 21 | for (const [value, count] of counts) { 22 | if (count > modeCount) { 23 | modeCount = count; 24 | modeValue = value; 25 | } 26 | } 27 | return modeValue; 28 | } 29 | -------------------------------------------------------------------------------- /src/nice.js: -------------------------------------------------------------------------------- 1 | import {tickIncrement} from "./ticks.js"; 2 | 3 | export default function nice(start, stop, count) { 4 | let prestep; 5 | while (true) { 6 | const step = tickIncrement(start, stop, count); 7 | if (step === prestep || step === 0 || !isFinite(step)) { 8 | return [start, stop]; 9 | } else if (step > 0) { 10 | start = Math.floor(start / step) * step; 11 | stop = Math.ceil(stop / step) * step; 12 | } else if (step < 0) { 13 | start = Math.ceil(start * step) / step; 14 | stop = Math.floor(stop * step) / step; 15 | } 16 | prestep = step; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/number.js: -------------------------------------------------------------------------------- 1 | export default function number(x) { 2 | return x === null ? NaN : +x; 3 | } 4 | 5 | export function* numbers(values, valueof) { 6 | if (valueof === undefined) { 7 | for (let value of values) { 8 | if (value != null && (value = +value) >= value) { 9 | yield value; 10 | } 11 | } 12 | } else { 13 | let index = -1; 14 | for (let value of values) { 15 | if ((value = valueof(value, ++index, values)) != null && (value = +value) >= value) { 16 | yield value; 17 | } 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/pairs.js: -------------------------------------------------------------------------------- 1 | export default function pairs(values, pairof = pair) { 2 | const pairs = []; 3 | let previous; 4 | let first = false; 5 | for (const value of values) { 6 | if (first) pairs.push(pairof(previous, value)); 7 | previous = value; 8 | first = true; 9 | } 10 | return pairs; 11 | } 12 | 13 | export function pair(a, b) { 14 | return [a, b]; 15 | } 16 | -------------------------------------------------------------------------------- /src/permute.js: -------------------------------------------------------------------------------- 1 | export default function permute(source, keys) { 2 | return Array.from(keys, key => source[key]); 3 | } 4 | -------------------------------------------------------------------------------- /src/quantile.js: -------------------------------------------------------------------------------- 1 | import max from "./max.js"; 2 | import maxIndex from "./maxIndex.js"; 3 | import min from "./min.js"; 4 | import minIndex from "./minIndex.js"; 5 | import quickselect from "./quickselect.js"; 6 | import number, {numbers} from "./number.js"; 7 | import {ascendingDefined} from "./sort.js"; 8 | import greatest from "./greatest.js"; 9 | 10 | export default function quantile(values, p, valueof) { 11 | values = Float64Array.from(numbers(values, valueof)); 12 | if (!(n = values.length) || isNaN(p = +p)) return; 13 | if (p <= 0 || n < 2) return min(values); 14 | if (p >= 1) return max(values); 15 | var n, 16 | i = (n - 1) * p, 17 | i0 = Math.floor(i), 18 | value0 = max(quickselect(values, i0).subarray(0, i0 + 1)), 19 | value1 = min(values.subarray(i0 + 1)); 20 | return value0 + (value1 - value0) * (i - i0); 21 | } 22 | 23 | export function quantileSorted(values, p, valueof = number) { 24 | if (!(n = values.length) || isNaN(p = +p)) return; 25 | if (p <= 0 || n < 2) return +valueof(values[0], 0, values); 26 | if (p >= 1) return +valueof(values[n - 1], n - 1, values); 27 | var n, 28 | i = (n - 1) * p, 29 | i0 = Math.floor(i), 30 | value0 = +valueof(values[i0], i0, values), 31 | value1 = +valueof(values[i0 + 1], i0 + 1, values); 32 | return value0 + (value1 - value0) * (i - i0); 33 | } 34 | 35 | export function quantileIndex(values, p, valueof = number) { 36 | if (isNaN(p = +p)) return; 37 | numbers = Float64Array.from(values, (_, i) => number(valueof(values[i], i, values))); 38 | if (p <= 0) return minIndex(numbers); 39 | if (p >= 1) return maxIndex(numbers); 40 | var numbers, 41 | index = Uint32Array.from(values, (_, i) => i), 42 | j = numbers.length - 1, 43 | i = Math.floor(j * p); 44 | quickselect(index, i, 0, j, (i, j) => ascendingDefined(numbers[i], numbers[j])); 45 | i = greatest(index.subarray(0, i + 1), (i) => numbers[i]); 46 | return i >= 0 ? i : -1; 47 | } 48 | -------------------------------------------------------------------------------- /src/quickselect.js: -------------------------------------------------------------------------------- 1 | import {ascendingDefined, compareDefined} from "./sort.js"; 2 | 3 | // Based on https://github.com/mourner/quickselect 4 | // ISC license, Copyright 2018 Vladimir Agafonkin. 5 | export default function quickselect(array, k, left = 0, right = Infinity, compare) { 6 | k = Math.floor(k); 7 | left = Math.floor(Math.max(0, left)); 8 | right = Math.floor(Math.min(array.length - 1, right)); 9 | 10 | if (!(left <= k && k <= right)) return array; 11 | 12 | compare = compare === undefined ? ascendingDefined : compareDefined(compare); 13 | 14 | while (right > left) { 15 | if (right - left > 600) { 16 | const n = right - left + 1; 17 | const m = k - left + 1; 18 | const z = Math.log(n); 19 | const s = 0.5 * Math.exp(2 * z / 3); 20 | const sd = 0.5 * Math.sqrt(z * s * (n - s) / n) * (m - n / 2 < 0 ? -1 : 1); 21 | const newLeft = Math.max(left, Math.floor(k - m * s / n + sd)); 22 | const newRight = Math.min(right, Math.floor(k + (n - m) * s / n + sd)); 23 | quickselect(array, k, newLeft, newRight, compare); 24 | } 25 | 26 | const t = array[k]; 27 | let i = left; 28 | let j = right; 29 | 30 | swap(array, left, k); 31 | if (compare(array[right], t) > 0) swap(array, left, right); 32 | 33 | while (i < j) { 34 | swap(array, i, j), ++i, --j; 35 | while (compare(array[i], t) < 0) ++i; 36 | while (compare(array[j], t) > 0) --j; 37 | } 38 | 39 | if (compare(array[left], t) === 0) swap(array, left, j); 40 | else ++j, swap(array, j, right); 41 | 42 | if (j <= k) left = j + 1; 43 | if (k <= j) right = j - 1; 44 | } 45 | 46 | return array; 47 | } 48 | 49 | function swap(array, i, j) { 50 | const t = array[i]; 51 | array[i] = array[j]; 52 | array[j] = t; 53 | } 54 | -------------------------------------------------------------------------------- /src/range.js: -------------------------------------------------------------------------------- 1 | export default function range(start, stop, step) { 2 | start = +start, stop = +stop, step = (n = arguments.length) < 2 ? (stop = start, start = 0, 1) : n < 3 ? 1 : +step; 3 | 4 | var i = -1, 5 | n = Math.max(0, Math.ceil((stop - start) / step)) | 0, 6 | range = new Array(n); 7 | 8 | while (++i < n) { 9 | range[i] = start + i * step; 10 | } 11 | 12 | return range; 13 | } 14 | -------------------------------------------------------------------------------- /src/rank.js: -------------------------------------------------------------------------------- 1 | import ascending from "./ascending.js"; 2 | import {ascendingDefined, compareDefined} from "./sort.js"; 3 | 4 | export default function rank(values, valueof = ascending) { 5 | if (typeof values[Symbol.iterator] !== "function") throw new TypeError("values is not iterable"); 6 | let V = Array.from(values); 7 | const R = new Float64Array(V.length); 8 | if (valueof.length !== 2) V = V.map(valueof), valueof = ascending; 9 | const compareIndex = (i, j) => valueof(V[i], V[j]); 10 | let k, r; 11 | values = Uint32Array.from(V, (_, i) => i); 12 | // Risky chaining due to Safari 14 https://github.com/d3/d3-array/issues/123 13 | values.sort(valueof === ascending ? (i, j) => ascendingDefined(V[i], V[j]) : compareDefined(compareIndex)); 14 | values.forEach((j, i) => { 15 | const c = compareIndex(j, k === undefined ? j : k); 16 | if (c >= 0) { 17 | if (k === undefined || c > 0) k = j, r = i; 18 | R[j] = r; 19 | } else { 20 | R[j] = NaN; 21 | } 22 | }); 23 | return R; 24 | } 25 | -------------------------------------------------------------------------------- /src/reduce.js: -------------------------------------------------------------------------------- 1 | export default function reduce(values, reducer, value) { 2 | if (typeof reducer !== "function") throw new TypeError("reducer is not a function"); 3 | const iterator = values[Symbol.iterator](); 4 | let done, next, index = -1; 5 | if (arguments.length < 3) { 6 | ({done, value} = iterator.next()); 7 | if (done) return; 8 | ++index; 9 | } 10 | while (({done, value: next} = iterator.next()), !done) { 11 | value = reducer(value, next, ++index, values); 12 | } 13 | return value; 14 | } 15 | -------------------------------------------------------------------------------- /src/reverse.js: -------------------------------------------------------------------------------- 1 | export default function reverse(values) { 2 | if (typeof values[Symbol.iterator] !== "function") throw new TypeError("values is not iterable"); 3 | return Array.from(values).reverse(); 4 | } 5 | -------------------------------------------------------------------------------- /src/scan.js: -------------------------------------------------------------------------------- 1 | import leastIndex from "./leastIndex.js"; 2 | 3 | export default function scan(values, compare) { 4 | const index = leastIndex(values, compare); 5 | return index < 0 ? undefined : index; 6 | } 7 | -------------------------------------------------------------------------------- /src/shuffle.js: -------------------------------------------------------------------------------- 1 | export default shuffler(Math.random); 2 | 3 | export function shuffler(random) { 4 | return function shuffle(array, i0 = 0, i1 = array.length) { 5 | let m = i1 - (i0 = +i0); 6 | while (m) { 7 | const i = random() * m-- | 0, t = array[m + i0]; 8 | array[m + i0] = array[i + i0]; 9 | array[i + i0] = t; 10 | } 11 | return array; 12 | }; 13 | } 14 | -------------------------------------------------------------------------------- /src/some.js: -------------------------------------------------------------------------------- 1 | export default function some(values, test) { 2 | if (typeof test !== "function") throw new TypeError("test is not a function"); 3 | let index = -1; 4 | for (const value of values) { 5 | if (test(value, ++index, values)) { 6 | return true; 7 | } 8 | } 9 | return false; 10 | } 11 | -------------------------------------------------------------------------------- /src/sort.js: -------------------------------------------------------------------------------- 1 | import ascending from "./ascending.js"; 2 | import permute from "./permute.js"; 3 | 4 | export default function sort(values, ...F) { 5 | if (typeof values[Symbol.iterator] !== "function") throw new TypeError("values is not iterable"); 6 | values = Array.from(values); 7 | let [f] = F; 8 | if ((f && f.length !== 2) || F.length > 1) { 9 | const index = Uint32Array.from(values, (d, i) => i); 10 | if (F.length > 1) { 11 | F = F.map(f => values.map(f)); 12 | index.sort((i, j) => { 13 | for (const f of F) { 14 | const c = ascendingDefined(f[i], f[j]); 15 | if (c) return c; 16 | } 17 | }); 18 | } else { 19 | f = values.map(f); 20 | index.sort((i, j) => ascendingDefined(f[i], f[j])); 21 | } 22 | return permute(values, index); 23 | } 24 | return values.sort(compareDefined(f)); 25 | } 26 | 27 | export function compareDefined(compare = ascending) { 28 | if (compare === ascending) return ascendingDefined; 29 | if (typeof compare !== "function") throw new TypeError("compare is not a function"); 30 | return (a, b) => { 31 | const x = compare(a, b); 32 | if (x || x === 0) return x; 33 | return (compare(b, b) === 0) - (compare(a, a) === 0); 34 | }; 35 | } 36 | 37 | export function ascendingDefined(a, b) { 38 | return (a == null || !(a >= a)) - (b == null || !(b >= b)) || (a < b ? -1 : a > b ? 1 : 0); 39 | } 40 | -------------------------------------------------------------------------------- /src/subset.js: -------------------------------------------------------------------------------- 1 | import superset from "./superset.js"; 2 | 3 | export default function subset(values, other) { 4 | return superset(other, values); 5 | } 6 | -------------------------------------------------------------------------------- /src/sum.js: -------------------------------------------------------------------------------- 1 | export default function sum(values, valueof) { 2 | let sum = 0; 3 | if (valueof === undefined) { 4 | for (let value of values) { 5 | if (value = +value) { 6 | sum += value; 7 | } 8 | } 9 | } else { 10 | let index = -1; 11 | for (let value of values) { 12 | if (value = +valueof(value, ++index, values)) { 13 | sum += value; 14 | } 15 | } 16 | } 17 | return sum; 18 | } 19 | -------------------------------------------------------------------------------- /src/superset.js: -------------------------------------------------------------------------------- 1 | export default function superset(values, other) { 2 | const iterator = values[Symbol.iterator](), set = new Set(); 3 | for (const o of other) { 4 | const io = intern(o); 5 | if (set.has(io)) continue; 6 | let value, done; 7 | while (({value, done} = iterator.next())) { 8 | if (done) return false; 9 | const ivalue = intern(value); 10 | set.add(ivalue); 11 | if (Object.is(io, ivalue)) break; 12 | } 13 | } 14 | return true; 15 | } 16 | 17 | function intern(value) { 18 | return value !== null && typeof value === "object" ? value.valueOf() : value; 19 | } 20 | -------------------------------------------------------------------------------- /src/threshold/freedmanDiaconis.js: -------------------------------------------------------------------------------- 1 | import count from "../count.js"; 2 | import quantile from "../quantile.js"; 3 | 4 | export default function thresholdFreedmanDiaconis(values, min, max) { 5 | const c = count(values), d = quantile(values, 0.75) - quantile(values, 0.25); 6 | return c && d ? Math.ceil((max - min) / (2 * d * Math.pow(c, -1 / 3))) : 1; 7 | } 8 | -------------------------------------------------------------------------------- /src/threshold/scott.js: -------------------------------------------------------------------------------- 1 | import count from "../count.js"; 2 | import deviation from "../deviation.js"; 3 | 4 | export default function thresholdScott(values, min, max) { 5 | const c = count(values), d = deviation(values); 6 | return c && d ? Math.ceil((max - min) * Math.cbrt(c) / (3.49 * d)) : 1; 7 | } 8 | -------------------------------------------------------------------------------- /src/threshold/sturges.js: -------------------------------------------------------------------------------- 1 | import count from "../count.js"; 2 | 3 | export default function thresholdSturges(values) { 4 | return Math.max(1, Math.ceil(Math.log(count(values)) / Math.LN2) + 1); 5 | } 6 | -------------------------------------------------------------------------------- /src/ticks.js: -------------------------------------------------------------------------------- 1 | const e10 = Math.sqrt(50), 2 | e5 = Math.sqrt(10), 3 | e2 = Math.sqrt(2); 4 | 5 | function tickSpec(start, stop, count) { 6 | const step = (stop - start) / Math.max(0, count), 7 | power = Math.floor(Math.log10(step)), 8 | error = step / Math.pow(10, power), 9 | factor = error >= e10 ? 10 : error >= e5 ? 5 : error >= e2 ? 2 : 1; 10 | let i1, i2, inc; 11 | if (power < 0) { 12 | inc = Math.pow(10, -power) / factor; 13 | i1 = Math.round(start * inc); 14 | i2 = Math.round(stop * inc); 15 | if (i1 / inc < start) ++i1; 16 | if (i2 / inc > stop) --i2; 17 | inc = -inc; 18 | } else { 19 | inc = Math.pow(10, power) * factor; 20 | i1 = Math.round(start / inc); 21 | i2 = Math.round(stop / inc); 22 | if (i1 * inc < start) ++i1; 23 | if (i2 * inc > stop) --i2; 24 | } 25 | if (i2 < i1 && 0.5 <= count && count < 2) return tickSpec(start, stop, count * 2); 26 | return [i1, i2, inc]; 27 | } 28 | 29 | export default function ticks(start, stop, count) { 30 | stop = +stop, start = +start, count = +count; 31 | if (!(count > 0)) return []; 32 | if (start === stop) return [start]; 33 | const reverse = stop < start, [i1, i2, inc] = reverse ? tickSpec(stop, start, count) : tickSpec(start, stop, count); 34 | if (!(i2 >= i1)) return []; 35 | const n = i2 - i1 + 1, ticks = new Array(n); 36 | if (reverse) { 37 | if (inc < 0) for (let i = 0; i < n; ++i) ticks[i] = (i2 - i) / -inc; 38 | else for (let i = 0; i < n; ++i) ticks[i] = (i2 - i) * inc; 39 | } else { 40 | if (inc < 0) for (let i = 0; i < n; ++i) ticks[i] = (i1 + i) / -inc; 41 | else for (let i = 0; i < n; ++i) ticks[i] = (i1 + i) * inc; 42 | } 43 | return ticks; 44 | } 45 | 46 | export function tickIncrement(start, stop, count) { 47 | stop = +stop, start = +start, count = +count; 48 | return tickSpec(start, stop, count)[2]; 49 | } 50 | 51 | export function tickStep(start, stop, count) { 52 | stop = +stop, start = +start, count = +count; 53 | const reverse = stop < start, inc = reverse ? tickIncrement(stop, start, count) : tickIncrement(start, stop, count); 54 | return (reverse ? -1 : 1) * (inc < 0 ? 1 / -inc : inc); 55 | } 56 | -------------------------------------------------------------------------------- /src/transpose.js: -------------------------------------------------------------------------------- 1 | import min from "./min.js"; 2 | 3 | export default function transpose(matrix) { 4 | if (!(n = matrix.length)) return []; 5 | for (var i = -1, m = min(matrix, length), transpose = new Array(m); ++i < m;) { 6 | for (var j = -1, n, row = transpose[i] = new Array(n); ++j < n;) { 7 | row[j] = matrix[j][i]; 8 | } 9 | } 10 | return transpose; 11 | } 12 | 13 | function length(d) { 14 | return d.length; 15 | } 16 | -------------------------------------------------------------------------------- /src/union.js: -------------------------------------------------------------------------------- 1 | import {InternSet} from "internmap"; 2 | 3 | export default function union(...others) { 4 | const set = new InternSet(); 5 | for (const other of others) { 6 | for (const o of other) { 7 | set.add(o); 8 | } 9 | } 10 | return set; 11 | } 12 | -------------------------------------------------------------------------------- /src/variance.js: -------------------------------------------------------------------------------- 1 | export default function variance(values, valueof) { 2 | let count = 0; 3 | let delta; 4 | let mean = 0; 5 | let sum = 0; 6 | if (valueof === undefined) { 7 | for (let value of values) { 8 | if (value != null && (value = +value) >= value) { 9 | delta = value - mean; 10 | mean += delta / ++count; 11 | sum += delta * (value - mean); 12 | } 13 | } 14 | } else { 15 | let index = -1; 16 | for (let value of values) { 17 | if ((value = valueof(value, ++index, values)) != null && (value = +value) >= value) { 18 | delta = value - mean; 19 | mean += delta / ++count; 20 | sum += delta * (value - mean); 21 | } 22 | } 23 | } 24 | if (count > 1) return sum / (count - 1); 25 | } 26 | -------------------------------------------------------------------------------- /src/zip.js: -------------------------------------------------------------------------------- 1 | import transpose from "./transpose.js"; 2 | 3 | export default function zip() { 4 | return transpose(arguments); 5 | } 6 | -------------------------------------------------------------------------------- /test/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "parserOptions": { 4 | "sourceType": "module", 5 | "ecmaVersion": 8 6 | }, 7 | "env": { 8 | "es6": true, 9 | "mocha": true 10 | }, 11 | "rules": { 12 | "no-sparse-arrays": 0 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /test/OneTimeNumber.js: -------------------------------------------------------------------------------- 1 | export function OneTimeNumber(value) { 2 | this.value = value; 3 | } 4 | 5 | OneTimeNumber.prototype.valueOf = function() { 6 | var v = this.value; 7 | this.value = NaN; 8 | return v; 9 | }; 10 | -------------------------------------------------------------------------------- /test/ascending-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {ascending} from "../src/index.js"; 3 | 4 | it("ascending(a, b) returns a negative number if a < b", () => { 5 | assert(ascending(0, 1) < 0); 6 | assert(ascending("a", "b") < 0); 7 | }); 8 | 9 | it("ascending(a, b) returns a positive number if a > b", () => { 10 | assert(ascending(1, 0) > 0); 11 | assert(ascending("b", "a") > 0); 12 | }); 13 | 14 | it("ascending(a, b) returns zero if a >= b and a <= b", () => { 15 | assert.strictEqual(ascending(0, 0), 0); 16 | assert.strictEqual(ascending("a", "a"), 0); 17 | assert.strictEqual(ascending("0", 0), 0); 18 | assert.strictEqual(ascending(0, "0"), 0); 19 | }); 20 | 21 | it("ascending(a, b) returns NaN if a and b are not comparable", () => { 22 | assert(isNaN(ascending(0, undefined))); 23 | assert(isNaN(ascending(undefined, 0))); 24 | assert(isNaN(ascending(undefined, undefined))); 25 | assert(isNaN(ascending(0, NaN))); 26 | assert(isNaN(ascending(NaN, 0))); 27 | assert(isNaN(ascending(NaN, NaN))); 28 | }); 29 | -------------------------------------------------------------------------------- /test/asserts.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {InternSet} from "internmap"; 3 | 4 | export function assertSetEqual(actual, expected) { 5 | assert(actual instanceof Set); 6 | expected = new InternSet(expected); 7 | for (const a of actual) assert(expected.has(a), `unexpected ${a}`); 8 | for (const e of expected) assert(actual.has(e), `expected ${e}`); 9 | } 10 | -------------------------------------------------------------------------------- /test/bisect-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {bisect, bisectLeft, bisectRight} from "../src/index.js"; 3 | 4 | it("bisect is an alias for bisectRight", () => { 5 | assert.strictEqual(bisect, bisectRight); 6 | }); 7 | 8 | it("bisectLeft(array, value) returns the index of an exact match", () => { 9 | const numbers = [1, 2, 3]; 10 | assert.strictEqual(bisectLeft(numbers, 1), 0); 11 | assert.strictEqual(bisectLeft(numbers, 2), 1); 12 | assert.strictEqual(bisectLeft(numbers, 3), 2); 13 | }); 14 | 15 | it("bisectLeft(array, value) returns the index of the first match", () => { 16 | const numbers = [1, 2, 2, 3]; 17 | assert.strictEqual(bisectLeft(numbers, 1), 0); 18 | assert.strictEqual(bisectLeft(numbers, 2), 1); 19 | assert.strictEqual(bisectLeft(numbers, 3), 3); 20 | }); 21 | 22 | it("bisectLeft(empty, value) returns zero", () => { 23 | assert.strictEqual(bisectLeft([], 1), 0); 24 | }); 25 | 26 | it("bisectLeft(array, value) returns the insertion point of a non-exact match", () => { 27 | const numbers = [1, 2, 3]; 28 | assert.strictEqual(bisectLeft(numbers, 0.5), 0); 29 | assert.strictEqual(bisectLeft(numbers, 1.5), 1); 30 | assert.strictEqual(bisectLeft(numbers, 2.5), 2); 31 | assert.strictEqual(bisectLeft(numbers, 3.5), 3); 32 | }); 33 | 34 | it("bisectLeft(array, value) has undefined behavior if the search value is unorderable", () => { 35 | const numbers = [1, 2, 3]; 36 | bisectLeft(numbers, new Date(NaN)); // who knows what this will return! 37 | bisectLeft(numbers, undefined); 38 | bisectLeft(numbers, NaN); 39 | }); 40 | 41 | it("bisectLeft(array, value, lo) observes the specified lower bound", () => { 42 | const numbers = [1, 2, 3, 4, 5]; 43 | assert.strictEqual(bisectLeft(numbers, 0, 2), 2); 44 | assert.strictEqual(bisectLeft(numbers, 1, 2), 2); 45 | assert.strictEqual(bisectLeft(numbers, 2, 2), 2); 46 | assert.strictEqual(bisectLeft(numbers, 3, 2), 2); 47 | assert.strictEqual(bisectLeft(numbers, 4, 2), 3); 48 | assert.strictEqual(bisectLeft(numbers, 5, 2), 4); 49 | assert.strictEqual(bisectLeft(numbers, 6, 2), 5); 50 | }); 51 | 52 | it("bisectLeft(array, value, lo, hi) observes the specified bounds", () => { 53 | const numbers = [1, 2, 3, 4, 5]; 54 | assert.strictEqual(bisectLeft(numbers, 0, 2, 3), 2); 55 | assert.strictEqual(bisectLeft(numbers, 1, 2, 3), 2); 56 | assert.strictEqual(bisectLeft(numbers, 2, 2, 3), 2); 57 | assert.strictEqual(bisectLeft(numbers, 3, 2, 3), 2); 58 | assert.strictEqual(bisectLeft(numbers, 4, 2, 3), 3); 59 | assert.strictEqual(bisectLeft(numbers, 5, 2, 3), 3); 60 | assert.strictEqual(bisectLeft(numbers, 6, 2, 3), 3); 61 | }); 62 | 63 | it("bisectLeft(array, value) handles large sparse d3", () => { 64 | const numbers = []; 65 | let i = 1 << 30; 66 | numbers[i++] = 1; 67 | numbers[i++] = 2; 68 | numbers[i++] = 3; 69 | numbers[i++] = 4; 70 | numbers[i++] = 5; 71 | assert.strictEqual(bisectLeft(numbers, 0, i - 5, i), i - 5); 72 | assert.strictEqual(bisectLeft(numbers, 1, i - 5, i), i - 5); 73 | assert.strictEqual(bisectLeft(numbers, 2, i - 5, i), i - 4); 74 | assert.strictEqual(bisectLeft(numbers, 3, i - 5, i), i - 3); 75 | assert.strictEqual(bisectLeft(numbers, 4, i - 5, i), i - 2); 76 | assert.strictEqual(bisectLeft(numbers, 5, i - 5, i), i - 1); 77 | assert.strictEqual(bisectLeft(numbers, 6, i - 5, i), i - 0); 78 | }); 79 | 80 | it("bisectRight(array, value) returns the index after an exact match", () => { 81 | const numbers = [1, 2, 3]; 82 | assert.strictEqual(bisectRight(numbers, 1), 1); 83 | assert.strictEqual(bisectRight(numbers, 2), 2); 84 | assert.strictEqual(bisectRight(numbers, 3), 3); 85 | }); 86 | 87 | it("bisectRight(array, value) returns the index after the last match", () => { 88 | const numbers = [1, 2, 2, 3]; 89 | assert.strictEqual(bisectRight(numbers, 1), 1); 90 | assert.strictEqual(bisectRight(numbers, 2), 3); 91 | assert.strictEqual(bisectRight(numbers, 3), 4); 92 | }); 93 | 94 | it("bisectRight(empty, value) returns zero", () => { 95 | assert.strictEqual(bisectRight([], 1), 0); 96 | }); 97 | 98 | it("bisectRight(array, value) returns the insertion point of a non-exact match", () => { 99 | const numbers = [1, 2, 3]; 100 | assert.strictEqual(bisectRight(numbers, 0.5), 0); 101 | assert.strictEqual(bisectRight(numbers, 1.5), 1); 102 | assert.strictEqual(bisectRight(numbers, 2.5), 2); 103 | assert.strictEqual(bisectRight(numbers, 3.5), 3); 104 | }); 105 | 106 | it("bisectRight(array, value, lo) observes the specified lower bound", () => { 107 | const numbers = [1, 2, 3, 4, 5]; 108 | assert.strictEqual(bisectRight(numbers, 0, 2), 2); 109 | assert.strictEqual(bisectRight(numbers, 1, 2), 2); 110 | assert.strictEqual(bisectRight(numbers, 2, 2), 2); 111 | assert.strictEqual(bisectRight(numbers, 3, 2), 3); 112 | assert.strictEqual(bisectRight(numbers, 4, 2), 4); 113 | assert.strictEqual(bisectRight(numbers, 5, 2), 5); 114 | assert.strictEqual(bisectRight(numbers, 6, 2), 5); 115 | }); 116 | 117 | it("bisectRight(array, value, lo, hi) observes the specified bounds", () => { 118 | const numbers = [1, 2, 3, 4, 5]; 119 | assert.strictEqual(bisectRight(numbers, 0, 2, 3), 2); 120 | assert.strictEqual(bisectRight(numbers, 1, 2, 3), 2); 121 | assert.strictEqual(bisectRight(numbers, 2, 2, 3), 2); 122 | assert.strictEqual(bisectRight(numbers, 3, 2, 3), 3); 123 | assert.strictEqual(bisectRight(numbers, 4, 2, 3), 3); 124 | assert.strictEqual(bisectRight(numbers, 5, 2, 3), 3); 125 | assert.strictEqual(bisectRight(numbers, 6, 2, 3), 3); 126 | }); 127 | 128 | it("bisectRight(array, value) handles large sparse d3", () => { 129 | const numbers = []; 130 | let i = 1 << 30; 131 | numbers[i++] = 1; 132 | numbers[i++] = 2; 133 | numbers[i++] = 3; 134 | numbers[i++] = 4; 135 | numbers[i++] = 5; 136 | assert.strictEqual(bisectRight(numbers, 0, i - 5, i), i - 5); 137 | assert.strictEqual(bisectRight(numbers, 1, i - 5, i), i - 4); 138 | assert.strictEqual(bisectRight(numbers, 2, i - 5, i), i - 3); 139 | assert.strictEqual(bisectRight(numbers, 3, i - 5, i), i - 2); 140 | assert.strictEqual(bisectRight(numbers, 4, i - 5, i), i - 1); 141 | assert.strictEqual(bisectRight(numbers, 5, i - 5, i), i - 0); 142 | assert.strictEqual(bisectRight(numbers, 6, i - 5, i), i - 0); 143 | }); 144 | 145 | it("bisectLeft(array, value, lo, hi) keeps non-comparable values to the right", () => { 146 | const values = [1, 2, null, undefined, NaN]; 147 | assert.strictEqual(bisectLeft(values, 1), 0); 148 | assert.strictEqual(bisectLeft(values, 2), 1); 149 | assert.strictEqual(bisectLeft(values, null), 5); 150 | assert.strictEqual(bisectLeft(values, undefined), 5); 151 | assert.strictEqual(bisectLeft(values, NaN), 5); 152 | }); 153 | 154 | it("bisectLeft(array, value, lo, hi) keeps comparable values to the left", () => { 155 | const values = [null, undefined, NaN]; 156 | assert.strictEqual(bisectLeft(values, 1), 0); 157 | assert.strictEqual(bisectLeft(values, 2), 0); 158 | }); 159 | 160 | it("bisectRight(array, value, lo, hi) keeps non-comparable values to the right", () => { 161 | const values = [1, 2, null, undefined]; 162 | assert.strictEqual(bisectRight(values, 1), 1); 163 | assert.strictEqual(bisectRight(values, 2), 2); 164 | assert.strictEqual(bisectRight(values, null), 4); 165 | assert.strictEqual(bisectRight(values, undefined), 4); 166 | assert.strictEqual(bisectRight(values, NaN), 4); 167 | }); 168 | -------------------------------------------------------------------------------- /test/count-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {count} from "../src/index.js"; 3 | 4 | it("count() accepts an iterable", () => { 5 | assert.deepStrictEqual(count([1, 2]), 2); 6 | assert.deepStrictEqual(count(new Set([1, 2])), 2); 7 | assert.deepStrictEqual(count(generate(1, 2)), 2); 8 | }); 9 | 10 | it("count() ignores NaN, null", () => { 11 | assert.deepStrictEqual(count([NaN, null, 0, 1]), 2); 12 | }); 13 | 14 | it("count() coerces to a number", () => { 15 | assert.deepStrictEqual(count(["1", " 2", "Fred"]), 2); 16 | }); 17 | 18 | it("count() accepts an accessor", () => { 19 | assert.deepStrictEqual(count([{v:NaN}, {}, {v:0}, {v:1}], d => d.v), 2); 20 | assert.deepStrictEqual(count([{n: "Alice", age: NaN}, {n: "Bob", age: 18}, {n: "Other"}], d => d.age), 1); 21 | }); 22 | 23 | function* generate(...values) { 24 | yield* values; 25 | } 26 | -------------------------------------------------------------------------------- /test/cross-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {cross} from "../src/index.js"; 3 | 4 | it("cross() returns an empty array", () => { 5 | assert.deepStrictEqual(cross(), []); 6 | }); 7 | 8 | it("cross([]) returns an empty array", () => { 9 | assert.deepStrictEqual(cross([]), []); 10 | }); 11 | 12 | it("cross([1, 2], []) returns an empty array", () => { 13 | assert.deepStrictEqual(cross([1, 2], []), []); 14 | }); 15 | 16 | it("cross({length: weird}) returns an empty array", () => { 17 | assert.deepStrictEqual(cross({length: NaN}), []); 18 | assert.deepStrictEqual(cross({length: 0.5}), []); 19 | assert.deepStrictEqual(cross({length: -1}), []); 20 | assert.deepStrictEqual(cross({length: undefined}), []); 21 | }); 22 | 23 | it("cross(...strings) returns the expected result", () => { 24 | assert.deepStrictEqual(cross("foo", "bar", (a, b) => a + b), ["fb", "fa", "fr", "ob", "oa", "or", "ob", "oa", "or"]); 25 | }); 26 | 27 | it("cross(a) returns the expected result", () => { 28 | assert.deepStrictEqual(cross([1, 2]), [[1], [2]]); 29 | }); 30 | 31 | it("cross(a, b) returns Cartesian product a×b", () => { 32 | assert.deepStrictEqual(cross([1, 2], ["x", "y"]), [[1, "x"], [1, "y"], [2, "x"], [2, "y"]]); 33 | }); 34 | 35 | it("cross(a, b, c) returns Cartesian product a×b×c", () => { 36 | assert.deepStrictEqual(cross([1, 2], [3, 4], [5, 6, 7]), [ 37 | [1, 3, 5], 38 | [1, 3, 6], 39 | [1, 3, 7], 40 | [1, 4, 5], 41 | [1, 4, 6], 42 | [1, 4, 7], 43 | [2, 3, 5], 44 | [2, 3, 6], 45 | [2, 3, 7], 46 | [2, 4, 5], 47 | [2, 4, 6], 48 | [2, 4, 7] 49 | ]); 50 | }); 51 | 52 | it("cross(a, b, f) invokes the specified function for each pair", () => { 53 | assert.deepStrictEqual(cross([1, 2], ["x", "y"], (a, b) => a + b), ["1x", "1y", "2x", "2y"]); 54 | }); 55 | 56 | it("cross(a, b, c, f) invokes the specified function for each triple", () => { 57 | assert.deepStrictEqual(cross([1, 2], [3, 4], [5, 6, 7], (a, b, c) => a + b + c), [9, 10, 11, 10, 11, 12, 10, 11, 12, 11, 12, 13]); 58 | }); 59 | 60 | it("cross(a, b) returns Cartesian product a×b of generators", () => { 61 | assert.deepStrictEqual(cross(generate(1, 2), generate("x", "y")), [[1, "x"], [1, "y"], [2, "x"], [2, "y"]]); 62 | }); 63 | 64 | function* generate(...values) { 65 | yield* values; 66 | } 67 | -------------------------------------------------------------------------------- /test/cumsum-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {cumsum} from "../src/index.js"; 3 | 4 | it("cumsum(array) returns the cumulative sum of the specified numbers", () => { 5 | assert.deepStrictEqual(Array.from(cumsum([1])), [1]); 6 | assert.deepStrictEqual(Array.from(cumsum([5, 1, 2, 3, 4])), [5, 6, 8, 11, 15]); 7 | assert.deepStrictEqual(Array.from(cumsum([20, 3])), [20, 23]); 8 | assert.deepStrictEqual(Array.from(cumsum([3, 20])), [3, 23]); 9 | }); 10 | 11 | it("cumsum(array) observes values that can be coerced to numbers", () => { 12 | assert.deepStrictEqual(Array.from(cumsum(["20", "3"])), [20, 23]); 13 | assert.deepStrictEqual(Array.from(cumsum(["3", "20"])), [3, 23]); 14 | assert.deepStrictEqual(Array.from(cumsum(["3", 20])), [3, 23]); 15 | assert.deepStrictEqual(Array.from(cumsum([20, "3"])), [20, 23]); 16 | assert.deepStrictEqual(Array.from(cumsum([3, "20"])), [3, 23]); 17 | assert.deepStrictEqual(Array.from(cumsum(["20", 3])), [20, 23]); 18 | }); 19 | 20 | it("cumsum(array) ignores non-numeric values", () => { 21 | assert.deepStrictEqual(Array.from(cumsum(["a", "b", "c"])), [0, 0, 0]); 22 | assert.deepStrictEqual(Array.from(cumsum(["a", 1, "2"])), [0, 1, 3]); 23 | }); 24 | 25 | it("cumsum(array) ignores null, undefined and NaN", () => { 26 | assert.deepStrictEqual(Array.from(cumsum([NaN, 1, 2, 3, 4, 5])), [0, 1, 3, 6, 10, 15]); 27 | assert.deepStrictEqual(Array.from(cumsum([1, 2, 3, 4, 5, NaN])), [1, 3, 6, 10, 15, 15]); 28 | assert.deepStrictEqual(Array.from(cumsum([10, null, 3, undefined, 5, NaN])), [10, 10, 13, 13, 18, 18]); 29 | }); 30 | 31 | it("cumsum(array) returns zeros if there are no numbers", () => { 32 | assert.deepStrictEqual(Array.from(cumsum([])), []); 33 | assert.deepStrictEqual(Array.from(cumsum([NaN])), [0]); 34 | assert.deepStrictEqual(Array.from(cumsum([undefined])), [0]); 35 | assert.deepStrictEqual(Array.from(cumsum([undefined, NaN])), [0, 0]); 36 | assert.deepStrictEqual(Array.from(cumsum([undefined, NaN, {}])), [0, 0, 0]); 37 | }); 38 | 39 | it("cumsum(array, f) returns the cumsum of the specified numbers", () => { 40 | assert.deepStrictEqual(Array.from(cumsum([1].map(box), unbox)), [1]); 41 | assert.deepStrictEqual(Array.from(cumsum([5, 1, 2, 3, 4].map(box), unbox)), [5, 6, 8, 11, 15]); 42 | assert.deepStrictEqual(Array.from(cumsum([20, 3].map(box), unbox)), [20, 23]); 43 | assert.deepStrictEqual(Array.from(cumsum([3, 20].map(box), unbox)), [3, 23]); 44 | }); 45 | 46 | it("cumsum(array, f) observes values that can be coerced to numbers", () => { 47 | assert.deepStrictEqual(Array.from(cumsum(["20", "3"].map(box), unbox)), [20, 23]); 48 | assert.deepStrictEqual(Array.from(cumsum(["3", "20"].map(box), unbox)), [3, 23]); 49 | assert.deepStrictEqual(Array.from(cumsum(["3", 20].map(box), unbox)), [3, 23]); 50 | assert.deepStrictEqual(Array.from(cumsum([20, "3"].map(box), unbox)), [20, 23]); 51 | assert.deepStrictEqual(Array.from(cumsum([3, "20"].map(box), unbox)), [3, 23]); 52 | assert.deepStrictEqual(Array.from(cumsum(["20", 3].map(box), unbox)), [20, 23]); 53 | }); 54 | 55 | it("cumsum(array, f) ignores non-numeric values", () => { 56 | assert.deepStrictEqual(Array.from(cumsum(["a", "b", "c"].map(box), unbox)), [0, 0, 0]); 57 | assert.deepStrictEqual(Array.from(cumsum(["a", 1, "2"].map(box), unbox)), [0, 1, 3]); 58 | }); 59 | 60 | it("cumsum(array, f) ignores null, undefined and NaN", () => { 61 | assert.deepStrictEqual(Array.from(cumsum([NaN, 1, 2, 3, 4, 5].map(box), unbox)), [0, 1, 3, 6, 10, 15]); 62 | assert.deepStrictEqual(Array.from(cumsum([1, 2, 3, 4, 5, NaN].map(box), unbox)), [1, 3, 6, 10, 15, 15]); 63 | assert.deepStrictEqual(Array.from(cumsum([10, null, 3, undefined, 5, NaN].map(box), unbox)), [10, 10, 13, 13, 18, 18]); 64 | }); 65 | 66 | it("cumsum(array, f) returns zeros if there are no numbers", () => { 67 | assert.deepStrictEqual(Array.from(cumsum([].map(box), unbox)), []); 68 | assert.deepStrictEqual(Array.from(cumsum([NaN].map(box), unbox)), [0]); 69 | assert.deepStrictEqual(Array.from(cumsum([undefined].map(box), unbox)), [0]); 70 | assert.deepStrictEqual(Array.from(cumsum([undefined, NaN].map(box), unbox)), [0, 0]); 71 | assert.deepStrictEqual(Array.from(cumsum([undefined, NaN, {}].map(box), unbox)), [0, 0, 0]); 72 | }); 73 | 74 | it("cumsum(array, f) passes the accessor d, i, and array", () => { 75 | const results = []; 76 | const array = ["a", "b", "c"]; 77 | cumsum(array, (d, i, array) => results.push([d, i, array])); 78 | assert.deepStrictEqual(results, [["a", 0, array], ["b", 1, array], ["c", 2, array]]); 79 | }); 80 | 81 | it("cumsum(array, f) uses the undefined context", () => { 82 | const results = []; 83 | cumsum([1, 2], function() { results.push(this); }); 84 | assert.deepStrictEqual(results, [undefined, undefined]); 85 | }); 86 | 87 | function box(value) { 88 | return {value: value}; 89 | } 90 | 91 | function unbox(box) { 92 | return box.value; 93 | } 94 | -------------------------------------------------------------------------------- /test/descending-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {descending} from "../src/index.js"; 3 | 4 | it("descending(a, b) returns a positive number if a < b", () => { 5 | assert(descending(0, 1) > 0); 6 | assert(descending("a", "b") > 0); 7 | }); 8 | 9 | it("descending(a, b) returns a negative number if a > b", () => { 10 | assert(descending(1, 0) < 0); 11 | assert(descending("b", "a") < 0); 12 | }); 13 | 14 | it("descending(a, b) returns zero if a >= b and a <= b", () => { 15 | assert.strictEqual(descending(0, 0), 0); 16 | assert.strictEqual(descending("a", "a"), 0); 17 | assert.strictEqual(descending("0", 0), 0); 18 | assert.strictEqual(descending(0, "0"), 0); 19 | }); 20 | 21 | it("descending(a, b) returns NaN if a and b are not comparable", () => { 22 | assert(isNaN(descending(0, undefined))); 23 | assert(isNaN(descending(undefined, 0))); 24 | assert(isNaN(descending(undefined, undefined))); 25 | assert(isNaN(descending(0, NaN))); 26 | assert(isNaN(descending(NaN, 0))); 27 | assert(isNaN(descending(NaN, NaN))); 28 | }); 29 | -------------------------------------------------------------------------------- /test/deviation-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {deviation} from "../src/index.js"; 3 | 4 | it("deviation(array) returns the standard deviation of the specified numbers", () => { 5 | assert.strictEqual(deviation([1, 1, 1, 1, 1]), 0); 6 | assert.strictEqual(deviation([5, 1, 2, 3, 4]), Math.sqrt(2.5)); 7 | assert.strictEqual(deviation([20, 3]), Math.sqrt(144.5)); 8 | assert.strictEqual(deviation([3, 20]), Math.sqrt(144.5)); 9 | }); 10 | 11 | it("deviation(array) ignores null, undefined and NaN", () => { 12 | assert.strictEqual(deviation([NaN, 1, 2, 3, 4, 5]), Math.sqrt(2.5)); 13 | assert.strictEqual(deviation([1, 2, 3, 4, 5, NaN]), Math.sqrt(2.5)); 14 | assert.strictEqual(deviation([10, null, 3, undefined, 5, NaN]), Math.sqrt(13)); 15 | }); 16 | 17 | it("deviation(array) can handle large numbers without overflowing", () => { 18 | assert.strictEqual(deviation([Number.MAX_VALUE, Number.MAX_VALUE]), 0); 19 | assert.strictEqual(deviation([-Number.MAX_VALUE, -Number.MAX_VALUE]), 0); 20 | }); 21 | 22 | it("deviation(array) returns undefined if the array has fewer than two numbers", () => { 23 | assert.strictEqual(deviation([1]), undefined); 24 | assert.strictEqual(deviation([]), undefined); 25 | assert.strictEqual(deviation([null]), undefined); 26 | assert.strictEqual(deviation([undefined]), undefined); 27 | assert.strictEqual(deviation([NaN]), undefined); 28 | assert.strictEqual(deviation([NaN, NaN]), undefined); 29 | }); 30 | 31 | it("deviation(array, f) returns the deviation of the specified numbers", () => { 32 | assert.strictEqual(deviation([5, 1, 2, 3, 4].map(box), unbox), Math.sqrt(2.5)); 33 | assert.strictEqual(deviation([20, 3].map(box), unbox), Math.sqrt(144.5)); 34 | assert.strictEqual(deviation([3, 20].map(box), unbox), Math.sqrt(144.5)); 35 | }); 36 | 37 | it("deviation(array, f) ignores null, undefined and NaN", () => { 38 | assert.strictEqual(deviation([NaN, 1, 2, 3, 4, 5].map(box), unbox), Math.sqrt(2.5)); 39 | assert.strictEqual(deviation([1, 2, 3, 4, 5, NaN].map(box), unbox), Math.sqrt(2.5)); 40 | assert.strictEqual(deviation([10, null, 3, undefined, 5, NaN].map(box), unbox), Math.sqrt(13)); 41 | }); 42 | 43 | it("deviation(array, f) can handle large numbers without overflowing", () => { 44 | assert.strictEqual(deviation([Number.MAX_VALUE, Number.MAX_VALUE].map(box), unbox), 0); 45 | assert.strictEqual(deviation([-Number.MAX_VALUE, -Number.MAX_VALUE].map(box), unbox), 0); 46 | }); 47 | 48 | it("deviation(array, f) returns undefined if the array has fewer than two numbers", () => { 49 | assert.strictEqual(deviation([1].map(box), unbox), undefined); 50 | assert.strictEqual(deviation([].map(box), unbox), undefined); 51 | assert.strictEqual(deviation([null].map(box), unbox), undefined); 52 | assert.strictEqual(deviation([undefined].map(box), unbox), undefined); 53 | assert.strictEqual(deviation([NaN].map(box), unbox), undefined); 54 | assert.strictEqual(deviation([NaN, NaN].map(box), unbox), undefined); 55 | }); 56 | 57 | function box(value) { 58 | return {value: value}; 59 | } 60 | 61 | function unbox(box) { 62 | return box.value; 63 | } 64 | -------------------------------------------------------------------------------- /test/difference-test.js: -------------------------------------------------------------------------------- 1 | import {difference} from "../src/index.js"; 2 | import {assertSetEqual} from "./asserts.js"; 3 | 4 | it("difference(values, other) returns a set of values", () => { 5 | assertSetEqual(difference([1, 2, 3], [2, 1]), [3]); 6 | assertSetEqual(difference([1, 2], [2, 3, 1]), []); 7 | assertSetEqual(difference([2, 1, 3], [4, 3, 1]), [2]); 8 | }); 9 | 10 | it("difference(...values) accepts iterables", () => { 11 | assertSetEqual(difference(new Set([1, 2, 3]), new Set([1])), [2, 3]); 12 | }); 13 | 14 | it("difference(values, other) performs interning", () => { 15 | assertSetEqual(difference([new Date("2021-01-01"), new Date("2021-01-02"), new Date("2021-01-03")], [new Date("2021-01-02"), new Date("2021-01-01")]), [new Date("2021-01-03")]); 16 | assertSetEqual(difference([new Date("2021-01-01"), new Date("2021-01-02")], [new Date("2021-01-02"), new Date("2021-01-03"), new Date("2021-01-01")]), []); 17 | assertSetEqual(difference([new Date("2021-01-02"), new Date("2021-01-01"), new Date("2021-01-03")], [new Date("2021-01-04"), new Date("2021-01-03"), new Date("2021-01-01")]), [new Date("2021-01-02")]); 18 | }); 19 | -------------------------------------------------------------------------------- /test/disjoint-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {disjoint} from "../src/index.js"; 3 | 4 | it("disjoint(values, other) returns true if sets are disjoint", () => { 5 | assert.strictEqual(disjoint([1], [2]), true); 6 | assert.strictEqual(disjoint([2, 3], [3, 4]), false); 7 | assert.strictEqual(disjoint([1], []), true); 8 | }); 9 | 10 | it("disjoint(values, other) allows values to be infinite", () => { 11 | assert.strictEqual(disjoint(odds(), [0, 2, 4, 5]), false); 12 | }); 13 | 14 | it("disjoint(values, other) allows other to be infinite", () => { 15 | assert.strictEqual(disjoint([2], repeat(1, 3, 2)), false); 16 | }); 17 | 18 | it("disjoint(values, other) performs interning", () => { 19 | assert.strictEqual(disjoint([new Date("2021-01-01")], [new Date("2021-01-02")]), true); 20 | assert.strictEqual(disjoint([new Date("2021-01-02"), new Date("2021-01-03")], [new Date("2021-01-03"), new Date("2021-01-04")]), false); 21 | assert.strictEqual(disjoint([new Date("2021-01-01")], []), true); 22 | }); 23 | 24 | function* odds() { 25 | for (let i = 1; true; i += 2) { 26 | yield i; 27 | } 28 | } 29 | 30 | function* repeat(...values) { 31 | while (true) { 32 | yield* values; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/every-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {every} from "../src/index.js"; 3 | 4 | it("every(values, test) returns true if all tests pass", () => { 5 | assert.strictEqual(every([1, 2, 3, 2, 1], x => x & 1), false); 6 | assert.strictEqual(every([1, 2, 3, 2, 1], x => x >= 1), true); 7 | }); 8 | 9 | it("every(values, test) returns true if values is empty", () => { 10 | assert.strictEqual(every([], () => false), true); 11 | }); 12 | 13 | it("every(values, test) accepts an iterable", () => { 14 | assert.strictEqual(every(new Set([1, 2, 3, 2, 1]), x => x >= 1), true); 15 | assert.strictEqual(every((function*() { yield* [1, 2, 3, 2, 1]; })(), x => x >= 1), true); 16 | assert.strictEqual(every(Uint8Array.of(1, 2, 3, 2, 1), x => x >= 1), true); 17 | }); 18 | 19 | it("every(values, test) enforces that test is a function", () => { 20 | assert.throws(() => every([]), TypeError); 21 | }); 22 | 23 | it("every(values, test) enforces that values is iterable", () => { 24 | assert.throws(() => every({}, () => true), TypeError); 25 | }); 26 | 27 | it("every(values, test) passes test (value, index, values)", () => { 28 | const calls = []; 29 | const values = new Set([5, 4, 3, 2, 1]); 30 | every(values, function() { return calls.push([this, ...arguments]); }); 31 | assert.deepStrictEqual(calls, [ 32 | [undefined, 5, 0, values], 33 | [undefined, 4, 1, values], 34 | [undefined, 3, 2, values], 35 | [undefined, 2, 3, values], 36 | [undefined, 1, 4, values] 37 | ]); 38 | }); 39 | 40 | it("every(values, test) short-circuts when test returns falsey", () => { 41 | let calls = 0; 42 | assert.strictEqual(every([1, 2, 3], x => (++calls, x < 2)), false); 43 | assert.strictEqual(calls, 2); 44 | assert.strictEqual(every([1, 2, 3], x => (++calls, x - 2)), false); 45 | assert.strictEqual(calls, 4); 46 | }); 47 | 48 | it("every(values, test) does not skip sparse elements", () => { 49 | assert.deepStrictEqual(every([, 1, 2,, ], x => x === undefined || x >=1), true); 50 | assert.deepStrictEqual(every([, 1, 2,, ], x => x >=1), false); 51 | }); 52 | -------------------------------------------------------------------------------- /test/extent-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {extent} from "../src/index.js"; 3 | 4 | it("extent(array) returns the least and greatest numeric values for numbers", () => { 5 | assert.deepStrictEqual(extent([1]), [1, 1]); 6 | assert.deepStrictEqual(extent([5, 1, 2, 3, 4]), [1, 5]); 7 | assert.deepStrictEqual(extent([20, 3]), [3, 20]); 8 | assert.deepStrictEqual(extent([3, 20]), [3, 20]); 9 | }); 10 | 11 | it("extent(array) returns the least and greatest lexicographic value for strings", () => { 12 | assert.deepStrictEqual(extent(["c", "a", "b"]), ["a", "c"]); 13 | assert.deepStrictEqual(extent(["20", "3"]), ["20", "3"]); 14 | assert.deepStrictEqual(extent(["3", "20"]), ["20", "3"]); 15 | }); 16 | 17 | it("extent(array) ignores null, undefined and NaN", () => { 18 | const o = {valueOf: () => NaN}; 19 | assert.deepStrictEqual(extent([NaN, 1, 2, 3, 4, 5]), [1, 5]); 20 | assert.deepStrictEqual(extent([o, 1, 2, 3, 4, 5]), [1, 5]); 21 | assert.deepStrictEqual(extent([1, 2, 3, 4, 5, NaN]), [1, 5]); 22 | assert.deepStrictEqual(extent([1, 2, 3, 4, 5, o]), [1, 5]); 23 | assert.deepStrictEqual(extent([10, null, 3, undefined, 5, NaN]), [3, 10]); 24 | assert.deepStrictEqual(extent([-1, null, -3, undefined, -5, NaN]), [-5, -1]); 25 | }); 26 | 27 | it("extent(array) compares heterogenous types as numbers", () => { 28 | assert.deepStrictEqual(extent([20, "3"]), ["3", 20]); 29 | assert.deepStrictEqual(extent(["20", 3]), [3, "20"]); 30 | assert.deepStrictEqual(extent([3, "20"]), [3, "20"]); 31 | assert.deepStrictEqual(extent(["3", 20]), ["3", 20]); 32 | }); 33 | 34 | it("extent(array) returns undefined if the array contains no numbers", () => { 35 | assert.deepStrictEqual(extent([]), [undefined, undefined]); 36 | assert.deepStrictEqual(extent([null]), [undefined, undefined]); 37 | assert.deepStrictEqual(extent([undefined]), [undefined, undefined]); 38 | assert.deepStrictEqual(extent([NaN]), [undefined, undefined]); 39 | assert.deepStrictEqual(extent([NaN, NaN]), [undefined, undefined]); 40 | }); 41 | 42 | it("extent(array, f) returns the least and greatest numeric value for numbers", () => { 43 | assert.deepStrictEqual(extent([1].map(box), unbox), [1, 1]); 44 | assert.deepStrictEqual(extent([5, 1, 2, 3, 4].map(box), unbox), [1, 5]); 45 | assert.deepStrictEqual(extent([20, 3].map(box), unbox), [3, 20]); 46 | assert.deepStrictEqual(extent([3, 20].map(box), unbox), [3, 20]); 47 | }); 48 | 49 | it("extent(array, f) returns the least and greatest lexicographic value for strings", () => { 50 | assert.deepStrictEqual(extent(["c", "a", "b"].map(box), unbox), ["a", "c"]); 51 | assert.deepStrictEqual(extent(["20", "3"].map(box), unbox), ["20", "3"]); 52 | assert.deepStrictEqual(extent(["3", "20"].map(box), unbox), ["20", "3"]); 53 | }); 54 | 55 | it("extent(array, f) ignores null, undefined and NaN", () => { 56 | const o = {valueOf: () => NaN}; 57 | assert.deepStrictEqual(extent([NaN, 1, 2, 3, 4, 5].map(box), unbox), [1, 5]); 58 | assert.deepStrictEqual(extent([o, 1, 2, 3, 4, 5].map(box), unbox), [1, 5]); 59 | assert.deepStrictEqual(extent([1, 2, 3, 4, 5, NaN].map(box), unbox), [1, 5]); 60 | assert.deepStrictEqual(extent([1, 2, 3, 4, 5, o].map(box), unbox), [1, 5]); 61 | assert.deepStrictEqual(extent([10, null, 3, undefined, 5, NaN].map(box), unbox), [3, 10]); 62 | assert.deepStrictEqual(extent([-1, null, -3, undefined, -5, NaN].map(box), unbox), [-5, -1]); 63 | }); 64 | 65 | it("extent(array, f) compares heterogenous types as numbers", () => { 66 | assert.deepStrictEqual(extent([20, "3"].map(box), unbox), ["3", 20]); 67 | assert.deepStrictEqual(extent(["20", 3].map(box), unbox), [3, "20"]); 68 | assert.deepStrictEqual(extent([3, "20"].map(box), unbox), [3, "20"]); 69 | assert.deepStrictEqual(extent(["3", 20].map(box), unbox), ["3", 20]); 70 | }); 71 | 72 | it("extent(array, f) returns undefined if the array contains no observed values", () => { 73 | assert.deepStrictEqual(extent([].map(box), unbox), [undefined, undefined]); 74 | assert.deepStrictEqual(extent([null].map(box), unbox), [undefined, undefined]); 75 | assert.deepStrictEqual(extent([undefined].map(box), unbox), [undefined, undefined]); 76 | assert.deepStrictEqual(extent([NaN].map(box), unbox), [undefined, undefined]); 77 | assert.deepStrictEqual(extent([NaN, NaN].map(box), unbox), [undefined, undefined]); 78 | }); 79 | 80 | it("extent(array, f) passes the accessor d, i, and array", () => { 81 | const results = []; 82 | const array = ["a", "b", "c"]; 83 | extent(array, (d, i, array) => results.push([d, i, array])); 84 | assert.deepStrictEqual(results, [["a", 0, array], ["b", 1, array], ["c", 2, array]]); 85 | }); 86 | 87 | it("extent(array, f) uses the undefined context", () => { 88 | const results = []; 89 | extent([1, 2], function() { results.push(this); }); 90 | assert.deepStrictEqual(results, [undefined, undefined]); 91 | }); 92 | 93 | function box(value) { 94 | return {value: value}; 95 | } 96 | 97 | function unbox(box) { 98 | return box.value; 99 | } 100 | -------------------------------------------------------------------------------- /test/fcumsum-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {cumsum, fcumsum} from "../src/index.js"; 3 | 4 | it("fcumsum(array) returns a Float64Array of the expected length", () => { 5 | const A = [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]; 6 | const R = cumsum(A); 7 | assert(R instanceof Float64Array); 8 | assert.strictEqual(R.length, A.length); 9 | }); 10 | 11 | it("fcumsum(array) is an exact cumsum", () => { 12 | assert.strictEqual(lastc([0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]), 1); 13 | assert.strictEqual(lastc([0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, -0.3, -0.3, -0.3, -0.3, -0.3, -0.3, -0.3, -0.3, -0.3, -0.3]), 0); 14 | assert.strictEqual(lastc(["20", "3"].map(box), unbox), 23); 15 | }); 16 | 17 | it("fcumsum(array) returns the fsum of the specified numbers", () => { 18 | assert.strictEqual(lastc([1]), 1); 19 | assert.strictEqual(lastc([5, 1, 2, 3, 4]), 15); 20 | assert.strictEqual(lastc([20, 3]), 23); 21 | assert.strictEqual(lastc([3, 20]), 23); 22 | }); 23 | 24 | it("fcumsum(array) observes values that can be coerced to numbers", () => { 25 | assert.strictEqual(lastc(["20", "3"]), 23); 26 | assert.strictEqual(lastc(["3", "20"]), 23); 27 | assert.strictEqual(lastc(["3", 20]), 23); 28 | assert.strictEqual(lastc([20, "3"]), 23); 29 | assert.strictEqual(lastc([3, "20"]), 23); 30 | assert.strictEqual(lastc(["20", 3]), 23); 31 | }); 32 | 33 | it("fcumsum(array) ignores non-numeric values", () => { 34 | assert.strictEqual(lastc(["a", "b", "c"]), 0); 35 | assert.strictEqual(lastc(["a", 1, "2"]), 3); 36 | }); 37 | 38 | it("fcumsum(array) ignores null, undefined and NaN", () => { 39 | assert.strictEqual(lastc([NaN, 1, 2, 3, 4, 5]), 15); 40 | assert.strictEqual(lastc([1, 2, 3, 4, 5, NaN]), 15); 41 | assert.strictEqual(lastc([10, null, 3, undefined, 5, NaN]), 18); 42 | }); 43 | 44 | it("fcumsum(array) returns an array of zeros if there are no numbers", () => { 45 | assert.deepStrictEqual(Array.from(fcumsum([])), []); 46 | assert.deepStrictEqual(Array.from(fcumsum([NaN])), [0]); 47 | assert.deepStrictEqual(Array.from(fcumsum([undefined])), [0]); 48 | assert.deepStrictEqual(Array.from(fcumsum([undefined, NaN])), [0, 0]); 49 | assert.deepStrictEqual(Array.from(fcumsum([undefined, NaN, {}])), [0, 0, 0]); 50 | }); 51 | 52 | it("fcumsum(array, f) returns the fsum of the specified numbers", () => { 53 | assert.strictEqual(lastc([1].map(box), unbox), 1); 54 | assert.strictEqual(lastc([5, 1, 2, 3, 4].map(box), unbox), 15); 55 | assert.strictEqual(lastc([20, 3].map(box), unbox), 23); 56 | assert.strictEqual(lastc([3, 20].map(box), unbox), 23); 57 | }); 58 | 59 | it("fcumsum(array, f) observes values that can be coerced to numbers", () => { 60 | assert.strictEqual(lastc(["20", "3"].map(box), unbox), 23); 61 | assert.strictEqual(lastc(["3", "20"].map(box), unbox), 23); 62 | assert.strictEqual(lastc(["3", 20].map(box), unbox), 23); 63 | assert.strictEqual(lastc([20, "3"].map(box), unbox), 23); 64 | assert.strictEqual(lastc([3, "20"].map(box), unbox), 23); 65 | assert.strictEqual(lastc(["20", 3].map(box), unbox), 23); 66 | }); 67 | 68 | it("fcumsum(array, f) ignores non-numeric values", () => { 69 | assert.strictEqual(lastc(["a", "b", "c"].map(box), unbox), 0); 70 | assert.strictEqual(lastc(["a", 1, "2"].map(box), unbox), 3); 71 | }); 72 | 73 | it("fcumsum(array, f) ignores null, undefined and NaN", () => { 74 | assert.strictEqual(lastc([NaN, 1, 2, 3, 4, 5].map(box), unbox), 15); 75 | assert.strictEqual(lastc([1, 2, 3, 4, 5, NaN].map(box), unbox), 15); 76 | assert.strictEqual(lastc([10, null, 3, undefined, 5, NaN].map(box), unbox), 18); 77 | }); 78 | 79 | it("fcumsum(array, f) returns zero if there are no numbers", () => { 80 | assert.deepStrictEqual(Array.from(fcumsum([].map(box), unbox)), []); 81 | assert.deepStrictEqual(Array.from(fcumsum([NaN].map(box), unbox)), [0]); 82 | assert.deepStrictEqual(Array.from(fcumsum([undefined].map(box), unbox)), [0]); 83 | assert.deepStrictEqual(Array.from(fcumsum([undefined, NaN].map(box), unbox)), [0, 0]); 84 | assert.deepStrictEqual(Array.from(fcumsum([undefined, NaN, {}].map(box), unbox)), [0, 0, 0]); 85 | }); 86 | 87 | it("fcumsum(array, f) passes the accessor d, i, and array", () => { 88 | const results = []; 89 | const array = ["a", "b", "c"]; 90 | lastc(array, (d, i, array) => results.push([d, i, array])); 91 | assert.deepStrictEqual(results, [["a", 0, array], ["b", 1, array], ["c", 2, array]]); 92 | }); 93 | 94 | it("fcumsum(array, f) uses the undefined context", () => { 95 | const results = []; 96 | lastc([1, 2], function() { results.push(this); }); 97 | assert.deepStrictEqual(results, [undefined, undefined]); 98 | }); 99 | 100 | function box(value) { 101 | return {value: value}; 102 | } 103 | 104 | function unbox(box) { 105 | return box.value; 106 | } 107 | 108 | function lastc(values, valueof) { 109 | const array = fcumsum(values, valueof); 110 | return array[array.length -1]; 111 | } 112 | -------------------------------------------------------------------------------- /test/filter-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import * as d3 from "../src/index.js"; 3 | 4 | it("filter(values, test) returns the values that pass the test", () => { 5 | assert.deepStrictEqual(d3.filter([1, 2, 3, 2, 1], x => x & 1), [1, 3, 1]); 6 | }); 7 | 8 | it("filter(values, test) accepts an iterable", () => { 9 | assert.deepStrictEqual(d3.filter(new Set([1, 2, 3, 2, 1]), x => x & 1), [1, 3]); 10 | assert.deepStrictEqual(d3.filter((function*() { yield* [1, 2, 3, 2, 1]; })(), x => x & 1), [1, 3, 1]); 11 | }); 12 | 13 | it("filter(values, test) accepts a typed array", () => { 14 | assert.deepStrictEqual(d3.filter(Uint8Array.of(1, 2, 3, 2, 1), x => x & 1), [1, 3, 1]); 15 | }); 16 | 17 | it("filter(values, test) enforces that test is a function", () => { 18 | assert.throws(() => d3.filter([]), TypeError); 19 | }); 20 | 21 | it("filter(values, test) enforces that values is iterable", () => { 22 | assert.throws(() => d3.filter({}, () => true), TypeError); 23 | }); 24 | 25 | it("filter(values, test) passes test (value, index, values)", () => { 26 | const calls = []; 27 | const values = new Set([5, 4, 3, 2, 1]); 28 | d3.filter(values, function() { calls.push([this, ...arguments]); }); 29 | assert.deepStrictEqual(calls, [ 30 | [undefined, 5, 0, values], 31 | [undefined, 4, 1, values], 32 | [undefined, 3, 2, values], 33 | [undefined, 2, 3, values], 34 | [undefined, 1, 4, values] 35 | ]); 36 | }); 37 | 38 | it("filter(values, test) does not skip sparse elements", () => { 39 | assert.deepStrictEqual(d3.filter([, 1, 2,, ], () => true), [undefined, 1, 2, undefined]); 40 | }); 41 | -------------------------------------------------------------------------------- /test/flatGroup-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {flatGroup} from "../src/index.js"; 3 | 4 | const data = [ 5 | {name: "jim", amount: "34.0", date: "11/12/2015"}, 6 | {name: "carl", amount: "120.11", date: "11/12/2015"}, 7 | {name: "stacy", amount: "12.01", date: "01/04/2016"}, 8 | {name: "stacy", amount: "34.05", date: "01/04/2016"} 9 | ]; 10 | 11 | it("flatGroup(data, accessor, accessor) returns the expected array", () => { 12 | assert.deepStrictEqual( 13 | flatGroup(data, d => d.name, d => d.amount), 14 | [ 15 | ['jim', '34.0', [{name: 'jim', amount: '34.0', date: '11/12/2015'}]], 16 | ['carl', '120.11', [{name: 'carl', amount: '120.11', date: '11/12/2015'}]], 17 | ['stacy', '12.01', [{name: 'stacy', amount: '12.01', date: '01/04/2016'}]], 18 | ['stacy', '34.05', [{name: 'stacy', amount: '34.05', date: '01/04/2016'}]] 19 | ] 20 | ); 21 | }); 22 | -------------------------------------------------------------------------------- /test/flatRollup-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {flatRollup} from "../src/index.js"; 3 | 4 | const data = [ 5 | {name: "jim", amount: "34.0", date: "11/12/2015"}, 6 | {name: "carl", amount: "120.11", date: "11/12/2015"}, 7 | {name: "stacy", amount: "12.01", date: "01/04/2016"}, 8 | {name: "stacy", amount: "34.05", date: "01/04/2016"} 9 | ]; 10 | 11 | it("flatRollup(data, reduce, accessor) returns the expected array", () => { 12 | assert.deepStrictEqual( 13 | flatRollup(data, v => v.length, d => d.name), 14 | [ 15 | ['jim', 1], 16 | ['carl', 1], 17 | ['stacy', 2] 18 | ] 19 | ); 20 | }); 21 | 22 | it("flatRollup(data, reduce, accessor, accessor) returns the expected array", () => { 23 | assert.deepStrictEqual( 24 | flatRollup(data, v => v.length, d => d.name, d => d.amount), 25 | [ 26 | ['jim', '34.0', 1], 27 | ['carl', '120.11', 1], 28 | ['stacy', '12.01', 1], 29 | ['stacy', '34.05', 1] 30 | ] 31 | ); 32 | }); 33 | -------------------------------------------------------------------------------- /test/fsum-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {Adder, fsum} from "../src/index.js"; 3 | 4 | it("new Adder() returns an Adder", () => { 5 | assert.strictEqual(typeof new Adder().add, "function"); 6 | assert.strictEqual(typeof new Adder().valueOf, "function"); 7 | }); 8 | 9 | it("+adder can be applied several times", () => { 10 | const adder = new Adder(); 11 | for (let i = 0; i < 10; ++i) adder.add(0.1); 12 | assert.strictEqual(+adder, 1); 13 | assert.strictEqual(+adder, 1); 14 | }); 15 | 16 | it("fsum(array) is an exact sum", () => { 17 | assert.strictEqual(fsum([.1, .1, .1, .1, .1, .1, .1, .1, .1, .1]), 1); 18 | assert.strictEqual(fsum([.3, .3, .3, .3, .3, .3, .3, .3, .3, .3, -.3, -.3, -.3, -.3, -.3, -.3, -.3, -.3, -.3, -.3]), 0); 19 | assert.strictEqual(fsum(["20", "3"].map(box), unbox), 23); 20 | }); 21 | 22 | it("fsum(array) returns the fsum of the specified numbers", () => { 23 | assert.strictEqual(fsum([1]), 1); 24 | assert.strictEqual(fsum([5, 1, 2, 3, 4]), 15); 25 | assert.strictEqual(fsum([20, 3]), 23); 26 | assert.strictEqual(fsum([3, 20]), 23); 27 | }); 28 | 29 | it("fsum(array) observes values that can be coerced to numbers", () => { 30 | assert.strictEqual(fsum(["20", "3"]), 23); 31 | assert.strictEqual(fsum(["3", "20"]), 23); 32 | assert.strictEqual(fsum(["3", 20]), 23); 33 | assert.strictEqual(fsum([20, "3"]), 23); 34 | assert.strictEqual(fsum([3, "20"]), 23); 35 | assert.strictEqual(fsum(["20", 3]), 23); 36 | }); 37 | 38 | it("fsum(array) ignores non-numeric values", () => { 39 | assert.strictEqual(fsum(["a", "b", "c"]), 0); 40 | assert.strictEqual(fsum(["a", 1, "2"]), 3); 41 | }); 42 | 43 | it("fsum(array) ignores null, undefined and NaN", () => { 44 | assert.strictEqual(fsum([NaN, 1, 2, 3, 4, 5]), 15); 45 | assert.strictEqual(fsum([1, 2, 3, 4, 5, NaN]), 15); 46 | assert.strictEqual(fsum([10, null, 3, undefined, 5, NaN]), 18); 47 | }); 48 | 49 | it("fsum(array) returns zero if there are no numbers", () => { 50 | assert.strictEqual(fsum([]), 0); 51 | assert.strictEqual(fsum([NaN]), 0); 52 | assert.strictEqual(fsum([undefined]), 0); 53 | assert.strictEqual(fsum([undefined, NaN]), 0); 54 | assert.strictEqual(fsum([undefined, NaN, {}]), 0); 55 | }); 56 | 57 | it("fsum(array, f) returns the fsum of the specified numbers", () => { 58 | assert.strictEqual(fsum([1].map(box), unbox), 1); 59 | assert.strictEqual(fsum([5, 1, 2, 3, 4].map(box), unbox), 15); 60 | assert.strictEqual(fsum([20, 3].map(box), unbox), 23); 61 | assert.strictEqual(fsum([3, 20].map(box), unbox), 23); 62 | }); 63 | 64 | it("fsum(array, f) observes values that can be coerced to numbers", () => { 65 | assert.strictEqual(fsum(["20", "3"].map(box), unbox), 23); 66 | assert.strictEqual(fsum(["3", "20"].map(box), unbox), 23); 67 | assert.strictEqual(fsum(["3", 20].map(box), unbox), 23); 68 | assert.strictEqual(fsum([20, "3"].map(box), unbox), 23); 69 | assert.strictEqual(fsum([3, "20"].map(box), unbox), 23); 70 | assert.strictEqual(fsum(["20", 3].map(box), unbox), 23); 71 | }); 72 | 73 | it("fsum(array, f) ignores non-numeric values", () => { 74 | assert.strictEqual(fsum(["a", "b", "c"].map(box), unbox), 0); 75 | assert.strictEqual(fsum(["a", 1, "2"].map(box), unbox), 3); 76 | }); 77 | 78 | it("fsum(array, f) ignores null, undefined and NaN", () => { 79 | assert.strictEqual(fsum([NaN, 1, 2, 3, 4, 5].map(box), unbox), 15); 80 | assert.strictEqual(fsum([1, 2, 3, 4, 5, NaN].map(box), unbox), 15); 81 | assert.strictEqual(fsum([10, null, 3, undefined, 5, NaN].map(box), unbox), 18); 82 | }); 83 | 84 | it("fsum(array, f) returns zero if there are no numbers", () => { 85 | assert.strictEqual(fsum([].map(box), unbox), 0); 86 | assert.strictEqual(fsum([NaN].map(box), unbox), 0); 87 | assert.strictEqual(fsum([undefined].map(box), unbox), 0); 88 | assert.strictEqual(fsum([undefined, NaN].map(box), unbox), 0); 89 | assert.strictEqual(fsum([undefined, NaN, {}].map(box), unbox), 0); 90 | }); 91 | 92 | it("fsum(array, f) passes the accessor d, i, and array", () => { 93 | const results = []; 94 | const array = ["a", "b", "c"]; 95 | fsum(array, (d, i, array) => results.push([d, i, array])); 96 | assert.deepStrictEqual(results, [["a", 0, array], ["b", 1, array], ["c", 2, array]]); 97 | }); 98 | 99 | it("fsum(array, f) uses the undefined context", () => { 100 | const results = []; 101 | fsum([1, 2], function() { results.push(this); }); 102 | assert.deepStrictEqual(results, [undefined, undefined]); 103 | }); 104 | 105 | function box(value) { 106 | return {value: value}; 107 | } 108 | 109 | function unbox(box) { 110 | return box.value; 111 | } 112 | -------------------------------------------------------------------------------- /test/greatest-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {descending, greatest} from "../src/index.js"; 3 | 4 | it("greatest(array) compares using natural order", () => { 5 | assert.strictEqual(greatest([0, 1]), 1); 6 | assert.strictEqual(greatest([1, 0]), 1); 7 | assert.strictEqual(greatest([0, "1"]), "1"); 8 | assert.strictEqual(greatest(["1", 0]), "1"); 9 | assert.strictEqual(greatest(["10", "2"]), "2"); 10 | assert.strictEqual(greatest(["2", "10"]), "2"); 11 | assert.strictEqual(greatest(["10", "2", NaN]), "2"); 12 | assert.strictEqual(greatest([NaN, "10", "2"]), "2"); 13 | assert.strictEqual(greatest(["2", NaN, "10"]), "2"); 14 | assert.strictEqual(greatest([2, NaN, 10]), 10); 15 | assert.strictEqual(greatest([10, 2, NaN]), 10); 16 | assert.strictEqual(greatest([NaN, 10, 2]), 10); 17 | }); 18 | 19 | it("greatest(array, compare) compares using the specified compare function", () => { 20 | const a = {name: "a"}, b = {name: "b"}; 21 | assert.deepStrictEqual(greatest([a, b], (a, b) => a.name.localeCompare(b.name)), {name: "b"}); 22 | assert.strictEqual(greatest([1, 0], descending), 0); 23 | assert.strictEqual(greatest(["1", 0], descending), 0); 24 | assert.strictEqual(greatest(["2", "10"], descending), "10"); 25 | assert.strictEqual(greatest(["2", NaN, "10"], descending), "10"); 26 | assert.strictEqual(greatest([2, NaN, 10], descending), 2); 27 | }); 28 | 29 | it("greatest(array, accessor) uses the specified accessor function", () => { 30 | const a = {name: "a", v: 42}, b = {name: "b", v: 0.42}; 31 | assert.deepStrictEqual(greatest([a, b], d => d.name), b); 32 | assert.deepStrictEqual(greatest([a, b], d => d.v), a); 33 | }); 34 | 35 | it("greatest(array) returns undefined if the array is empty", () => { 36 | assert.strictEqual(greatest([]), undefined); 37 | }); 38 | 39 | it("greatest(array) returns undefined if the array contains only incomparable values", () => { 40 | assert.strictEqual(greatest([NaN, undefined]), undefined); 41 | assert.strictEqual(greatest([NaN, "foo"], (a, b) => a - b), undefined); 42 | }); 43 | 44 | it("greatest(array) returns the first of equal values", () => { 45 | assert.deepStrictEqual(greatest([2, 2, 1, 1, 0, 0, 0, 3, 0].map(box), descendingValue), {value: 0, index: 4}); 46 | assert.deepStrictEqual(greatest([3, 2, 2, 1, 1, 0, 0, 0, 3, 0].map(box), ascendingValue), {value: 3, index: 0}); 47 | }); 48 | 49 | it("greatest(array) ignores null and undefined", () => { 50 | assert.deepStrictEqual(greatest([null, -2, undefined]), -2); 51 | }); 52 | 53 | it("greatest(array, accessor) ignores null and undefined", () => { 54 | assert.deepStrictEqual(greatest([null, -2, undefined], d => d), -2); 55 | }); 56 | 57 | function box(value, index) { 58 | return {value, index}; 59 | } 60 | 61 | function ascendingValue(a, b) { 62 | return a.value - b.value; 63 | } 64 | 65 | function descendingValue(a, b) { 66 | return b.value - a.value; 67 | } 68 | -------------------------------------------------------------------------------- /test/greatestIndex-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {ascending, descending, greatestIndex} from "../src/index.js"; 3 | 4 | it("greatestIndex(array) compares using natural order", () => { 5 | assert.strictEqual(greatestIndex([0, 1]), 1); 6 | assert.strictEqual(greatestIndex([1, 0]), 0); 7 | assert.strictEqual(greatestIndex([0, "1"]), 1); 8 | assert.strictEqual(greatestIndex(["1", 0]), 0); 9 | assert.strictEqual(greatestIndex(["10", "2"]), 1); 10 | assert.strictEqual(greatestIndex(["2", "10"]), 0); 11 | assert.strictEqual(greatestIndex(["10", "2", NaN]), 1); 12 | assert.strictEqual(greatestIndex([NaN, "10", "2"]), 2); 13 | assert.strictEqual(greatestIndex(["2", NaN, "10"]), 0); 14 | assert.strictEqual(greatestIndex([2, NaN, 10]), 2); 15 | assert.strictEqual(greatestIndex([10, 2, NaN]), 0); 16 | assert.strictEqual(greatestIndex([NaN, 10, 2]), 1); 17 | }); 18 | 19 | it("greatestIndex(array, compare) compares using the specified compare function", () => { 20 | const a = {name: "a"}, b = {name: "b"}; 21 | assert.strictEqual(greatestIndex([a, b], (a, b) => a.name.localeCompare(b.name)), 1); 22 | assert.strictEqual(greatestIndex([1, 0], ascending), 0); 23 | assert.strictEqual(greatestIndex(["1", 0], ascending), 0); 24 | assert.strictEqual(greatestIndex(["2", "10"], ascending), 0); 25 | assert.strictEqual(greatestIndex(["2", NaN, "10"], ascending), 0); 26 | assert.strictEqual(greatestIndex([2, NaN, 10], ascending), 2); 27 | }); 28 | 29 | it("greatestIndex(array, accessor) uses the specified accessor function", () => { 30 | const a = {name: "a", v: 42}, b = {name: "b", v: 0.42}; 31 | assert.deepStrictEqual(greatestIndex([a, b], d => d.name), 1); 32 | assert.deepStrictEqual(greatestIndex([a, b], d => d.v), 0); 33 | }); 34 | 35 | it("greatestIndex(array) returns -1 if the array is empty", () => { 36 | assert.strictEqual(greatestIndex([]), -1); 37 | }); 38 | 39 | it("greatestIndex(array) returns -1 if the array contains only incomparable values", () => { 40 | assert.strictEqual(greatestIndex([NaN, undefined]), -1); 41 | assert.strictEqual(greatestIndex([NaN, "foo"], (a, b) => a - b), -1); 42 | }); 43 | 44 | it("greatestIndex(array) returns the first of equal values", () => { 45 | assert.strictEqual(greatestIndex([-2, -2, -1, -1, 0, 0, 0, -3, 0]), 4); 46 | assert.strictEqual(greatestIndex([-3, -2, -2, -1, -1, 0, 0, 0, -3, 0], descending), 0); 47 | }); 48 | 49 | it("greatestIndex(array) ignores null and undefined", () => { 50 | assert.deepStrictEqual(greatestIndex([null, -2, undefined]), 1); 51 | }); 52 | 53 | it("greatestIndex(array, accessor) ignores null and undefined", () => { 54 | assert.deepStrictEqual(greatestIndex([null, -2, undefined], d => d), 1); 55 | }); 56 | -------------------------------------------------------------------------------- /test/group-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {group} from "../src/index.js"; 3 | 4 | const data = [ 5 | {name: "jim", amount: "34.0", date: "11/12/2015"}, 6 | {name: "carl", amount: "120.11", date: "11/12/2015"}, 7 | {name: "stacy", amount: "12.01", date: "01/04/2016"}, 8 | {name: "stacy", amount: "34.05", date: "01/04/2016"} 9 | ]; 10 | 11 | it("group(data, accessor) returns the expected map", () => { 12 | assert.deepStrictEqual( 13 | entries(group(data, d => d.name), 1), 14 | [ 15 | [ 16 | "jim", 17 | [ 18 | { 19 | "name": "jim", 20 | "amount": "34.0", 21 | "date": "11/12/2015" 22 | } 23 | ] 24 | ], 25 | [ 26 | "carl", 27 | [ 28 | { 29 | "name": "carl", 30 | "amount": "120.11", 31 | "date": "11/12/2015" 32 | } 33 | ] 34 | ], 35 | [ 36 | "stacy", 37 | [ 38 | { 39 | "name": "stacy", 40 | "amount": "12.01", 41 | "date": "01/04/2016" 42 | }, 43 | { 44 | "name": "stacy", 45 | "amount": "34.05", 46 | "date": "01/04/2016" 47 | } 48 | ] 49 | ] 50 | ] 51 | ); 52 | }); 53 | 54 | it("group(data, accessor, accessor) returns the expected map", () => { 55 | assert.deepStrictEqual( 56 | entries(group(data, d => d.name, d => d.amount), 2), 57 | [ 58 | [ 59 | "jim", 60 | [ 61 | [ 62 | "34.0", 63 | [ 64 | { 65 | "name": "jim", 66 | "amount": "34.0", 67 | "date": "11/12/2015" 68 | } 69 | ] 70 | ] 71 | ] 72 | ], 73 | [ 74 | "carl", 75 | [ 76 | [ 77 | "120.11", 78 | [ 79 | { 80 | "name": "carl", 81 | "amount": "120.11", 82 | "date": "11/12/2015" 83 | } 84 | ] 85 | ] 86 | ] 87 | ], 88 | [ 89 | "stacy", 90 | [ 91 | [ 92 | "12.01", 93 | [ 94 | { 95 | "name": "stacy", 96 | "amount": "12.01", 97 | "date": "01/04/2016" 98 | } 99 | ] 100 | ], 101 | [ 102 | "34.05", 103 | [ 104 | { 105 | "name": "stacy", 106 | "amount": "34.05", 107 | "date": "01/04/2016" 108 | } 109 | ] 110 | ] 111 | ] 112 | ] 113 | ] 114 | ); 115 | }); 116 | 117 | it("group(data, accessor) interns keys", () => { 118 | const a1 = new Date(Date.UTC(2001, 0, 1)); 119 | const a2 = new Date(Date.UTC(2001, 0, 1)); 120 | const b = new Date(Date.UTC(2002, 0, 1)); 121 | const map = group([[a1, 1], [a2, 2], [b, 3]], ([date]) => date); 122 | assert.deepStrictEqual(map.get(a1), [[a1, 1], [a2, 2]]); 123 | assert.deepStrictEqual(map.get(a2), [[a1, 1], [a2, 2]]); 124 | assert.deepStrictEqual(map.get(b), [[b, 3]]); 125 | assert.deepStrictEqual(map.get(+a1), [[a1, 1], [a2, 2]]); 126 | assert.deepStrictEqual(map.get(+a2), [[a1, 1], [a2, 2]]); 127 | assert.deepStrictEqual(map.get(+b), [[b, 3]]); 128 | assert.strictEqual([...map.keys()][0], a1); 129 | assert.strictEqual([...map.keys()][1], b); 130 | }); 131 | 132 | function entries(map, depth) { 133 | if (depth > 0) { 134 | return Array.from(map, ([k, v]) => [k, entries(v, depth - 1)]); 135 | } else { 136 | return map; 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /test/groupSort-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {readFileSync} from "fs"; 3 | import {ascending, descending, groupSort, median} from "../src/index.js"; 4 | 5 | const barley = JSON.parse(readFileSync("./test/data/barley.json")); 6 | 7 | it("groupSort(data, reduce, key) returns sorted keys when reduce is an accessor", () => { 8 | assert.deepStrictEqual( 9 | groupSort(barley, g => median(g, d => d.yield), d => d.variety), 10 | ["Svansota", "No. 462", "Manchuria", "No. 475", "Velvet", "Peatland", "Glabron", "No. 457", "Wisconsin No. 38", "Trebi"] 11 | ); 12 | assert.deepStrictEqual( 13 | groupSort(barley, g => -median(g, d => d.yield), d => d.variety), 14 | ["Trebi", "Wisconsin No. 38", "No. 457", "Glabron", "Peatland", "Velvet", "No. 475", "Manchuria", "No. 462", "Svansota"] 15 | ); 16 | assert.deepStrictEqual( 17 | groupSort(barley, g => median(g, d => -d.yield), d => d.variety), 18 | ["Trebi", "Wisconsin No. 38", "No. 457", "Glabron", "Peatland", "Velvet", "No. 475", "Manchuria", "No. 462", "Svansota"] 19 | ); 20 | assert.deepStrictEqual( 21 | groupSort(barley, g => median(g, d => d.yield), d => d.site), 22 | ["Grand Rapids", "Duluth", "University Farm", "Morris", "Crookston", "Waseca"] 23 | ); 24 | assert.deepStrictEqual( 25 | groupSort(barley, g => -median(g, d => d.yield), d => d.site), 26 | ["Waseca", "Crookston", "Morris", "University Farm", "Duluth", "Grand Rapids"] 27 | ); 28 | assert.deepStrictEqual( 29 | groupSort(barley, g => median(g, d => -d.yield), d => d.site), 30 | ["Waseca", "Crookston", "Morris", "University Farm", "Duluth", "Grand Rapids"] 31 | ); 32 | }); 33 | 34 | it("groupSort(data, reduce, key) returns sorted keys when reduce is a comparator", () => { 35 | assert.deepStrictEqual( 36 | groupSort(barley, (a, b) => ascending(median(a, d => d.yield), median(b, d => d.yield)), d => d.variety), 37 | ["Svansota", "No. 462", "Manchuria", "No. 475", "Velvet", "Peatland", "Glabron", "No. 457", "Wisconsin No. 38", "Trebi"] 38 | ); 39 | assert.deepStrictEqual( 40 | groupSort(barley, (a, b) => descending(median(a, d => d.yield), median(b, d => d.yield)), d => d.variety), 41 | ["Trebi", "Wisconsin No. 38", "No. 457", "Glabron", "Peatland", "Velvet", "No. 475", "Manchuria", "No. 462", "Svansota"] 42 | ); 43 | assert.deepStrictEqual( 44 | groupSort(barley, (a, b) => ascending(median(a, d => d.yield), median(b, d => d.yield)), d => d.site), 45 | ["Grand Rapids", "Duluth", "University Farm", "Morris", "Crookston", "Waseca"] 46 | ); 47 | assert.deepStrictEqual( 48 | groupSort(barley, (a, b) => descending(median(a, d => d.yield), median(b, d => d.yield)), d => d.site), 49 | ["Waseca", "Crookston", "Morris", "University Farm", "Duluth", "Grand Rapids"] 50 | ); 51 | }); 52 | -------------------------------------------------------------------------------- /test/groups-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {groups} from "../src/index.js"; 3 | 4 | const data = [ 5 | {name: "jim", amount: "34.0", date: "11/12/2015"}, 6 | {name: "carl", amount: "120.11", date: "11/12/2015"}, 7 | {name: "stacy", amount: "12.01", date: "01/04/2016"}, 8 | {name: "stacy", amount: "34.05", date: "01/04/2016"} 9 | ]; 10 | 11 | it("groups(data, accessor) returns the expected array", () => { 12 | assert.deepStrictEqual( 13 | groups(data, d => d.name), 14 | [ 15 | [ 16 | "jim", 17 | [ 18 | { 19 | "name": "jim", 20 | "amount": "34.0", 21 | "date": "11/12/2015" 22 | } 23 | ] 24 | ], 25 | [ 26 | "carl", 27 | [ 28 | { 29 | "name": "carl", 30 | "amount": "120.11", 31 | "date": "11/12/2015" 32 | } 33 | ] 34 | ], 35 | [ 36 | "stacy", 37 | [ 38 | { 39 | "name": "stacy", 40 | "amount": "12.01", 41 | "date": "01/04/2016" 42 | }, 43 | { 44 | "name": "stacy", 45 | "amount": "34.05", 46 | "date": "01/04/2016" 47 | } 48 | ] 49 | ] 50 | ] 51 | ); 52 | }); 53 | 54 | it("groups(data, accessor, accessor) returns the expected array", () => { 55 | assert.deepStrictEqual( 56 | groups(data, d => d.name, d => d.amount), 57 | [ 58 | [ 59 | "jim", 60 | [ 61 | [ 62 | "34.0", 63 | [ 64 | { 65 | "name": "jim", 66 | "amount": "34.0", 67 | "date": "11/12/2015" 68 | } 69 | ] 70 | ] 71 | ] 72 | ], 73 | [ 74 | "carl", 75 | [ 76 | [ 77 | "120.11", 78 | [ 79 | { 80 | "name": "carl", 81 | "amount": "120.11", 82 | "date": "11/12/2015" 83 | } 84 | ] 85 | ] 86 | ] 87 | ], 88 | [ 89 | "stacy", 90 | [ 91 | [ 92 | "12.01", 93 | [ 94 | { 95 | "name": "stacy", 96 | "amount": "12.01", 97 | "date": "01/04/2016" 98 | } 99 | ] 100 | ], 101 | [ 102 | "34.05", 103 | [ 104 | { 105 | "name": "stacy", 106 | "amount": "34.05", 107 | "date": "01/04/2016" 108 | } 109 | ] 110 | ] 111 | ] 112 | ] 113 | ] 114 | ); 115 | }); 116 | -------------------------------------------------------------------------------- /test/index-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {index, indexes} from "../src/index.js"; 3 | 4 | const data = [ 5 | {name: "jim", amount: 34.0, date: "11/12/2015"}, 6 | {name: "carl", amount: 120.11, date: "11/12/2015"}, 7 | {name: "stacy", amount: 12.01, date: "01/04/2016"}, 8 | {name: "stacy", amount: 34.05, date: "01/04/2016"} 9 | ]; 10 | 11 | it("indexes(data, ...keys) returns the expected nested arrays", () => { 12 | assert.deepStrictEqual( 13 | indexes(data, d => d.amount), 14 | [ 15 | [34.0, {name: "jim", amount: 34.0, date: "11/12/2015"}], 16 | [120.11, {name: "carl", amount: 120.11, date: "11/12/2015"}], 17 | [12.01, {name: "stacy", amount: 12.01, date: "01/04/2016"}], 18 | [34.05, {name: "stacy", amount: 34.05, date: "01/04/2016"}] 19 | ] 20 | ); 21 | assert.deepStrictEqual( 22 | indexes(data, d => d.name, d => d.amount), 23 | [ 24 | [ 25 | "jim", 26 | [ 27 | [34.0, {name: "jim", amount: 34.0, date: "11/12/2015"}] 28 | ] 29 | ], 30 | [ 31 | "carl", 32 | [ 33 | [120.11, {name: "carl", amount: 120.11, date: "11/12/2015"}] 34 | ] 35 | ], 36 | [ 37 | "stacy", 38 | [ 39 | [12.01, {name: "stacy", amount: 12.01, date: "01/04/2016"}], 40 | [34.05, {name: "stacy", amount: 34.05, date: "01/04/2016"}] 41 | ] 42 | ] 43 | ] 44 | ); 45 | }); 46 | 47 | it("index(data, ...keys) returns the expected map", () => { 48 | assert.deepStrictEqual( 49 | entries(index(data, d => d.amount), 1), 50 | indexes(data, d => d.amount) 51 | ); 52 | assert.deepStrictEqual( 53 | entries(index(data, d => d.name, d => d.amount), 2), 54 | indexes(data, d => d.name, d => d.amount) 55 | ); 56 | }); 57 | 58 | it("index(data, ...keys) throws on a non-unique key", () => { 59 | assert.throws(() => index(data, d => d.name)); 60 | assert.throws(() => index(data, d => d.date)); 61 | }); 62 | 63 | function entries(map, depth) { 64 | return depth > 0 65 | ? Array.from(map, ([k, v]) => [k, entries(v, depth - 1)]) 66 | : map; 67 | } 68 | -------------------------------------------------------------------------------- /test/intersection-test.js: -------------------------------------------------------------------------------- 1 | import {intersection} from "../src/index.js"; 2 | import {assertSetEqual} from "./asserts.js"; 3 | 4 | it("intersection(values) returns a set of values", () => { 5 | assertSetEqual(intersection([1, 2, 3, 2, 1]), [1, 2, 3]); 6 | }); 7 | 8 | it("intersection(values, other) returns a set of values", () => { 9 | assertSetEqual(intersection([1, 2], [2, 3, 1]), [1, 2]); 10 | assertSetEqual(intersection([2, 1, 3], [4, 3, 1]), [1, 3]); 11 | }); 12 | 13 | it("intersection(...values) returns a set of values", () => { 14 | assertSetEqual(intersection([1, 2], [2, 1], [2, 3]), [2]); 15 | }); 16 | 17 | it("intersection(...values) accepts iterables", () => { 18 | assertSetEqual(intersection(new Set([1, 2, 3])), [1, 2, 3]); 19 | }); 20 | 21 | it("intersection(...values) performs interning", () => { 22 | assertSetEqual(intersection([new Date("2021-01-01"), new Date("2021-01-03")], [new Date("2021-01-01"), new Date("2021-01-02")]), [new Date("2021-01-01")]); 23 | }); 24 | -------------------------------------------------------------------------------- /test/least-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {descending, least} from "../src/index.js"; 3 | 4 | it("least(array) compares using natural order", () => { 5 | assert.strictEqual(least([0, 1]), 0); 6 | assert.strictEqual(least([1, 0]), 0); 7 | assert.strictEqual(least([0, "1"]), 0); 8 | assert.strictEqual(least(["1", 0]), 0); 9 | assert.strictEqual(least(["10", "2"]), "10"); 10 | assert.strictEqual(least(["2", "10"]), "10"); 11 | assert.strictEqual(least(["10", "2", NaN]), "10"); 12 | assert.strictEqual(least([NaN, "10", "2"]), "10"); 13 | assert.strictEqual(least(["2", NaN, "10"]), "10"); 14 | assert.strictEqual(least([2, NaN, 10]), 2); 15 | assert.strictEqual(least([10, 2, NaN]), 2); 16 | assert.strictEqual(least([NaN, 10, 2]), 2); 17 | }); 18 | 19 | it("least(array, compare) compares using the specified compare function", () => { 20 | const a = {name: "a"}, b = {name: "b"}; 21 | assert.deepStrictEqual(least([a, b], (a, b) => a.name.localeCompare(b.name)), {name: "a"}); 22 | assert.strictEqual(least([1, 0], descending), 1); 23 | assert.strictEqual(least(["1", 0], descending), "1"); 24 | assert.strictEqual(least(["2", "10"], descending), "2"); 25 | assert.strictEqual(least(["2", NaN, "10"], descending), "2"); 26 | assert.strictEqual(least([2, NaN, 10], descending), 10); 27 | }); 28 | 29 | it("least(array, accessor) uses the specified accessor function", () => { 30 | const a = {name: "a", v: 42}, b = {name: "b", v: 0.42}; 31 | assert.deepStrictEqual(least([a, b], d => d.name), a); 32 | assert.deepStrictEqual(least([a, b], d => d.v), b); 33 | }); 34 | 35 | it("least(array) returns undefined if the array is empty", () => { 36 | assert.strictEqual(least([]), undefined); 37 | }); 38 | 39 | it("least(array) returns undefined if the array contains only incomparable values", () => { 40 | assert.strictEqual(least([NaN, undefined]), undefined); 41 | assert.strictEqual(least([NaN, "foo"], (a, b) => a - b), undefined); 42 | }); 43 | 44 | it("least(array) returns the first of equal values", () => { 45 | assert.deepStrictEqual(least([2, 2, 1, 1, 0, 0, 0, 3, 0].map(box), ascendingValue), {value: 0, index: 4}); 46 | assert.deepStrictEqual(least([3, 2, 2, 1, 1, 0, 0, 0, 3, 0].map(box), descendingValue), {value: 3, index: 0}); 47 | }); 48 | 49 | it("least(array) ignores null and undefined", () => { 50 | assert.deepStrictEqual(least([null, 2, undefined]), 2); 51 | }); 52 | 53 | it("least(array, accessor) ignores null and undefined", () => { 54 | assert.deepStrictEqual(least([null, 2, undefined], d => d), 2); 55 | }); 56 | 57 | function box(value, index) { 58 | return {value, index}; 59 | } 60 | 61 | function ascendingValue(a, b) { 62 | return a.value - b.value; 63 | } 64 | 65 | function descendingValue(a, b) { 66 | return b.value - a.value; 67 | } 68 | -------------------------------------------------------------------------------- /test/leastIndex-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {descending, leastIndex} from "../src/index.js"; 3 | 4 | it("leastIndex(array) compares using natural order", () => { 5 | assert.strictEqual(leastIndex([0, 1]), 0); 6 | assert.strictEqual(leastIndex([1, 0]), 1); 7 | assert.strictEqual(leastIndex([0, "1"]), 0); 8 | assert.strictEqual(leastIndex(["1", 0]), 1); 9 | assert.strictEqual(leastIndex(["10", "2"]), 0); 10 | assert.strictEqual(leastIndex(["2", "10"]), 1); 11 | assert.strictEqual(leastIndex(["10", "2", NaN]), 0); 12 | assert.strictEqual(leastIndex([NaN, "10", "2"]), 1); 13 | assert.strictEqual(leastIndex(["2", NaN, "10"]), 2); 14 | assert.strictEqual(leastIndex([2, NaN, 10]), 0); 15 | assert.strictEqual(leastIndex([10, 2, NaN]), 1); 16 | assert.strictEqual(leastIndex([NaN, 10, 2]), 2); 17 | }); 18 | 19 | it("leastIndex(array, compare) compares using the specified compare function", () => { 20 | const a = {name: "a"}, b = {name: "b"}; 21 | assert.strictEqual(leastIndex([a, b], (a, b) => a.name.localeCompare(b.name)), 0); 22 | assert.strictEqual(leastIndex([1, 0], descending), 0); 23 | assert.strictEqual(leastIndex(["1", 0], descending), 0); 24 | assert.strictEqual(leastIndex(["2", "10"], descending), 0); 25 | assert.strictEqual(leastIndex(["2", NaN, "10"], descending), 0); 26 | assert.strictEqual(leastIndex([2, NaN, 10], descending), 2); 27 | }); 28 | 29 | it("leastIndex(array, accessor) uses the specified accessor function", () => { 30 | const a = {name: "a", v: 42}, b = {name: "b", v: 0.42}; 31 | assert.deepStrictEqual(leastIndex([a, b], d => d.name), 0); 32 | assert.deepStrictEqual(leastIndex([a, b], d => d.v), 1); 33 | }); 34 | 35 | it("leastIndex(array) returns -1 if the array is empty", () => { 36 | assert.strictEqual(leastIndex([]), -1); 37 | }); 38 | 39 | it("leastIndex(array) returns -1 if the array contains only incomparable values", () => { 40 | assert.strictEqual(leastIndex([NaN, undefined]), -1); 41 | assert.strictEqual(leastIndex([NaN, "foo"], (a, b) => a - b), -1); 42 | }); 43 | 44 | it("leastIndex(array) returns the first of equal values", () => { 45 | assert.strictEqual(leastIndex([2, 2, 1, 1, 0, 0, 0, 3, 0]), 4); 46 | assert.strictEqual(leastIndex([3, 2, 2, 1, 1, 0, 0, 0, 3, 0], descending), 0); 47 | }); 48 | 49 | it("leastIndex(array) ignores null and undefined", () => { 50 | assert.deepStrictEqual(leastIndex([null, 2, undefined]), 1); 51 | }); 52 | 53 | it("leastIndex(array, accessor) ignores null and undefined", () => { 54 | assert.deepStrictEqual(leastIndex([null, 2, undefined], d => d), 1); 55 | }); 56 | -------------------------------------------------------------------------------- /test/map-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {map} from "../src/index.js"; 3 | 4 | it("map(values, mapper) returns the mapped values", () => { 5 | assert.deepStrictEqual(map([1, 2, 3, 2, 1], x => x * 2), [2, 4, 6, 4, 2]); 6 | }); 7 | 8 | it("map(values, mapper) accepts an iterable", () => { 9 | assert.deepStrictEqual(map(new Set([1, 2, 3, 2, 1]), x => x * 2), [2, 4, 6]); 10 | assert.deepStrictEqual(map((function*() { yield* [1, 2, 3, 2, 1]; })(), x => x * 2), [2, 4, 6, 4, 2]); 11 | }); 12 | 13 | it("map(values, mapper) accepts a typed array", () => { 14 | assert.deepStrictEqual(map(Uint8Array.of(1, 2, 3, 2, 1), x => x * 2), [2, 4, 6, 4, 2]); 15 | }); 16 | 17 | it("map(values, mapper) enforces that test is a function", () => { 18 | assert.throws(() => map([]), TypeError); 19 | }); 20 | 21 | it("map(values, mapper) enforces that values is iterable", () => { 22 | assert.throws(() => map({}, () => true), TypeError); 23 | }); 24 | 25 | it("map(values, mapper) passes test (value, index, values)", () => { 26 | const calls = []; 27 | const values = new Set([5, 4, 3, 2, 1]); 28 | map(values, function() { calls.push([this, ...arguments]); }); 29 | assert.deepStrictEqual(calls, [ 30 | [undefined, 5, 0, values], 31 | [undefined, 4, 1, values], 32 | [undefined, 3, 2, values], 33 | [undefined, 2, 3, values], 34 | [undefined, 1, 4, values] 35 | ]); 36 | }); 37 | 38 | it("map(values, mapper) does not skip sparse elements", () => { 39 | assert.deepStrictEqual(map([, 1, 2,, ], x => x * 2), [NaN, 2, 4, NaN]); 40 | }); 41 | -------------------------------------------------------------------------------- /test/max-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {max} from "../src/index.js"; 3 | 4 | it("max(array) returns the greatest numeric value for numbers", () => { 5 | assert.deepStrictEqual(max([1]), 1); 6 | assert.deepStrictEqual(max([5, 1, 2, 3, 4]), 5); 7 | assert.deepStrictEqual(max([20, 3]), 20); 8 | assert.deepStrictEqual(max([3, 20]), 20); 9 | }); 10 | 11 | it("max(array) returns the greatest lexicographic value for strings", () => { 12 | assert.deepStrictEqual(max(["c", "a", "b"]), "c"); 13 | assert.deepStrictEqual(max(["20", "3"]), "3"); 14 | assert.deepStrictEqual(max(["3", "20"]), "3"); 15 | }); 16 | 17 | it("max(array) ignores null, undefined and NaN", () => { 18 | const o = {valueOf: () => NaN}; 19 | assert.deepStrictEqual(max([NaN, 1, 2, 3, 4, 5]), 5); 20 | assert.deepStrictEqual(max([o, 1, 2, 3, 4, 5]), 5); 21 | assert.deepStrictEqual(max([1, 2, 3, 4, 5, NaN]), 5); 22 | assert.deepStrictEqual(max([1, 2, 3, 4, 5, o]), 5); 23 | assert.deepStrictEqual(max([10, null, 3, undefined, 5, NaN]), 10); 24 | assert.deepStrictEqual(max([-1, null, -3, undefined, -5, NaN]), -1); 25 | }); 26 | 27 | it("max(array) compares heterogenous types as numbers", () => { 28 | assert.strictEqual(max([20, "3"]), 20); 29 | assert.strictEqual(max(["20", 3]), "20"); 30 | assert.strictEqual(max([3, "20"]), "20"); 31 | assert.strictEqual(max(["3", 20]), 20); 32 | }); 33 | 34 | it("max(array) returns undefined if the array contains no numbers", () => { 35 | assert.strictEqual(max([]), undefined); 36 | assert.strictEqual(max([null]), undefined); 37 | assert.strictEqual(max([undefined]), undefined); 38 | assert.strictEqual(max([NaN]), undefined); 39 | assert.strictEqual(max([NaN, NaN]), undefined); 40 | }); 41 | 42 | it("max(array, f) returns the greatest numeric value for numbers", () => { 43 | assert.deepStrictEqual(max([1].map(box), unbox), 1); 44 | assert.deepStrictEqual(max([5, 1, 2, 3, 4].map(box), unbox), 5); 45 | assert.deepStrictEqual(max([20, 3].map(box), unbox), 20); 46 | assert.deepStrictEqual(max([3, 20].map(box), unbox), 20); 47 | }); 48 | 49 | it("max(array, f) returns the greatest lexicographic value for strings", () => { 50 | assert.deepStrictEqual(max(["c", "a", "b"].map(box), unbox), "c"); 51 | assert.deepStrictEqual(max(["20", "3"].map(box), unbox), "3"); 52 | assert.deepStrictEqual(max(["3", "20"].map(box), unbox), "3"); 53 | }); 54 | 55 | it("max(array, f) ignores null, undefined and NaN", () => { 56 | const o = {valueOf: () => NaN}; 57 | assert.deepStrictEqual(max([NaN, 1, 2, 3, 4, 5].map(box), unbox), 5); 58 | assert.deepStrictEqual(max([o, 1, 2, 3, 4, 5].map(box), unbox), 5); 59 | assert.deepStrictEqual(max([1, 2, 3, 4, 5, NaN].map(box), unbox), 5); 60 | assert.deepStrictEqual(max([1, 2, 3, 4, 5, o].map(box), unbox), 5); 61 | assert.deepStrictEqual(max([10, null, 3, undefined, 5, NaN].map(box), unbox), 10); 62 | assert.deepStrictEqual(max([-1, null, -3, undefined, -5, NaN].map(box), unbox), -1); 63 | }); 64 | 65 | it("max(array, f) compares heterogenous types as numbers", () => { 66 | assert.strictEqual(max([20, "3"].map(box), unbox), 20); 67 | assert.strictEqual(max(["20", 3].map(box), unbox), "20"); 68 | assert.strictEqual(max([3, "20"].map(box), unbox), "20"); 69 | assert.strictEqual(max(["3", 20].map(box), unbox), 20); 70 | }); 71 | 72 | it("max(array, f) returns undefined if the array contains no observed values", () => { 73 | assert.strictEqual(max([].map(box), unbox), undefined); 74 | assert.strictEqual(max([null].map(box), unbox), undefined); 75 | assert.strictEqual(max([undefined].map(box), unbox), undefined); 76 | assert.strictEqual(max([NaN].map(box), unbox), undefined); 77 | assert.strictEqual(max([NaN, NaN].map(box), unbox), undefined); 78 | }); 79 | 80 | it("max(array, f) passes the accessor d, i, and array", () => { 81 | const results = []; 82 | const array = ["a", "b", "c"]; 83 | max(array, (d, i, array) => results.push([d, i, array])); 84 | assert.deepStrictEqual(results, [["a", 0, array], ["b", 1, array], ["c", 2, array]]); 85 | }); 86 | 87 | it("max(array, f) uses the undefined context", () => { 88 | const results = []; 89 | max([1, 2], function() { results.push(this); }); 90 | assert.deepStrictEqual(results, [undefined, undefined]); 91 | }); 92 | 93 | function box(value) { 94 | return {value: value}; 95 | } 96 | 97 | function unbox(box) { 98 | return box.value; 99 | } 100 | -------------------------------------------------------------------------------- /test/maxIndex-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {maxIndex} from "../src/index.js"; 3 | 4 | it("maxIndex(array) returns the index of the greatest numeric value for numbers", () => { 5 | assert.deepStrictEqual(maxIndex([1]), 0); 6 | assert.deepStrictEqual(maxIndex([5, 1, 2, 3, 4]), 0); 7 | assert.deepStrictEqual(maxIndex([20, 3]), 0); 8 | assert.deepStrictEqual(maxIndex([3, 20]), 1); 9 | }); 10 | 11 | it("maxIndex(array) returns the greatest lexicographic value for strings", () => { 12 | assert.deepStrictEqual(maxIndex(["c", "a", "b"]), 0); 13 | assert.deepStrictEqual(maxIndex(["20", "3"]), 1); 14 | assert.deepStrictEqual(maxIndex(["3", "20"]), 0); 15 | }); 16 | 17 | it("maxIndex(array) ignores null, undefined and NaN", () => { 18 | const o = {valueOf: () => NaN}; 19 | assert.deepStrictEqual(maxIndex([NaN, 1, 2, 3, 4, 5]), 5); 20 | assert.deepStrictEqual(maxIndex([o, 1, 2, 3, 4, 5]), 5); 21 | assert.deepStrictEqual(maxIndex([1, 2, 3, 4, 5, NaN]), 4); 22 | assert.deepStrictEqual(maxIndex([1, 2, 3, 4, 5, o]), 4); 23 | assert.deepStrictEqual(maxIndex([10, null, 3, undefined, 5, NaN]), 0); 24 | assert.deepStrictEqual(maxIndex([-1, null, -3, undefined, -5, NaN]), 0); 25 | }); 26 | 27 | it("maxIndex(array) compares heterogenous types as numbers", () => { 28 | assert.strictEqual(maxIndex([20, "3"]), 0); 29 | assert.strictEqual(maxIndex(["20", 3]), 0); 30 | assert.strictEqual(maxIndex([3, "20"]), 1); 31 | assert.strictEqual(maxIndex(["3", 20]), 1); 32 | }); 33 | 34 | it("maxIndex(array) returns -1 if the array contains no numbers", () => { 35 | assert.strictEqual(maxIndex([]), -1); 36 | assert.strictEqual(maxIndex([null]), -1); 37 | assert.strictEqual(maxIndex([undefined]), -1); 38 | assert.strictEqual(maxIndex([NaN]), -1); 39 | assert.strictEqual(maxIndex([NaN, NaN]), -1); 40 | }); 41 | 42 | it("maxIndex(array, f) returns the greatest numeric value for numbers", () => { 43 | assert.deepStrictEqual(maxIndex([1].map(box), unbox), 0); 44 | assert.deepStrictEqual(maxIndex([5, 1, 2, 3, 4].map(box), unbox), 0); 45 | assert.deepStrictEqual(maxIndex([20, 3].map(box), unbox), 0); 46 | assert.deepStrictEqual(maxIndex([3, 20].map(box), unbox), 1); 47 | }); 48 | 49 | it("maxIndex(array, f) returns the greatest lexicographic value for strings", () => { 50 | assert.deepStrictEqual(maxIndex(["c", "a", "b"].map(box), unbox), 0); 51 | assert.deepStrictEqual(maxIndex(["20", "3"].map(box), unbox), 1); 52 | assert.deepStrictEqual(maxIndex(["3", "20"].map(box), unbox), 0); 53 | }); 54 | 55 | it("maxIndex(array, f) ignores null, undefined and NaN", () => { 56 | const o = {valueOf: () => NaN}; 57 | assert.deepStrictEqual(maxIndex([NaN, 1, 2, 3, 4, 5].map(box), unbox), 5); 58 | assert.deepStrictEqual(maxIndex([o, 1, 2, 3, 4, 5].map(box), unbox), 5); 59 | assert.deepStrictEqual(maxIndex([1, 2, 3, 4, 5, NaN].map(box), unbox), 4); 60 | assert.deepStrictEqual(maxIndex([1, 2, 3, 4, 5, o].map(box), unbox), 4); 61 | assert.deepStrictEqual(maxIndex([10, null, 3, undefined, 5, NaN].map(box), unbox), 0); 62 | assert.deepStrictEqual(maxIndex([-1, null, -3, undefined, -5, NaN].map(box), unbox), 0); 63 | }); 64 | 65 | it("maxIndex(array, f) compares heterogenous types as numbers", () => { 66 | assert.strictEqual(maxIndex([20, "3"].map(box), unbox), 0); 67 | assert.strictEqual(maxIndex(["20", 3].map(box), unbox), 0); 68 | assert.strictEqual(maxIndex([3, "20"].map(box), unbox), 1); 69 | assert.strictEqual(maxIndex(["3", 20].map(box), unbox), 1); 70 | }); 71 | 72 | it("maxIndex(array, f) returns -1 if the array contains no observed values", () => { 73 | assert.strictEqual(maxIndex([].map(box), unbox), -1); 74 | assert.strictEqual(maxIndex([null].map(box), unbox), -1); 75 | assert.strictEqual(maxIndex([undefined].map(box), unbox), -1); 76 | assert.strictEqual(maxIndex([NaN].map(box), unbox), -1); 77 | assert.strictEqual(maxIndex([NaN, NaN].map(box), unbox), -1); 78 | }); 79 | 80 | it("maxIndex(array, f) passes the accessor d, i, and array", () => { 81 | const results = []; 82 | const array = ["a", "b", "c"]; 83 | maxIndex(array, (d, i, array) => results.push([d, i, array])); 84 | assert.deepStrictEqual(results, [["a", 0, array], ["b", 1, array], ["c", 2, array]]); 85 | }); 86 | 87 | it("maxIndex(array, f) uses the undefined context", () => { 88 | const results = []; 89 | maxIndex([1, 2], function() { results.push(this); }); 90 | assert.deepStrictEqual(results, [undefined, undefined]); 91 | }); 92 | 93 | function box(value) { 94 | return {value: value}; 95 | } 96 | 97 | function unbox(box) { 98 | return box.value; 99 | } 100 | -------------------------------------------------------------------------------- /test/mean-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {mean} from "../src/index.js"; 3 | import {OneTimeNumber} from "./OneTimeNumber.js"; 4 | 5 | it("mean(array) returns the mean value for numbers", () => { 6 | assert.strictEqual(mean([1]), 1); 7 | assert.strictEqual(mean([5, 1, 2, 3, 4]), 3); 8 | assert.strictEqual(mean([20, 3]), 11.5); 9 | assert.strictEqual(mean([3, 20]), 11.5); 10 | }); 11 | 12 | it("mean(array) ignores null, undefined and NaN", () => { 13 | assert.strictEqual(mean([NaN, 1, 2, 3, 4, 5]), 3); 14 | assert.strictEqual(mean([1, 2, 3, 4, 5, NaN]), 3); 15 | assert.strictEqual(mean([10, null, 3, undefined, 5, NaN]), 6); 16 | }); 17 | 18 | it("mean(array) returns undefined if the array contains no observed values", () => { 19 | assert.strictEqual(mean([]), undefined); 20 | assert.strictEqual(mean([null]), undefined); 21 | assert.strictEqual(mean([undefined]), undefined); 22 | assert.strictEqual(mean([NaN]), undefined); 23 | assert.strictEqual(mean([NaN, NaN]), undefined); 24 | }); 25 | 26 | it("mean(array) coerces values to numbers", () => { 27 | assert.strictEqual(mean(["1"]), 1); 28 | assert.strictEqual(mean(["5", "1", "2", "3", "4"]), 3); 29 | assert.strictEqual(mean(["20", "3"]), 11.5); 30 | assert.strictEqual(mean(["3", "20"]), 11.5); 31 | }); 32 | 33 | it("mean(array) coerces values exactly once", () => { 34 | const numbers = [1, new OneTimeNumber(3)]; 35 | assert.strictEqual(mean(numbers), 2); 36 | assert.strictEqual(mean(numbers), 1); 37 | }); 38 | 39 | it("mean(array, f) returns the mean value for numbers", () => { 40 | assert.strictEqual(mean([1].map(box), unbox), 1); 41 | assert.strictEqual(mean([5, 1, 2, 3, 4].map(box), unbox), 3); 42 | assert.strictEqual(mean([20, 3].map(box), unbox), 11.5); 43 | assert.strictEqual(mean([3, 20].map(box), unbox), 11.5); 44 | }); 45 | 46 | it("mean(array, f) ignores null, undefined and NaN", () => { 47 | assert.strictEqual(mean([NaN, 1, 2, 3, 4, 5].map(box), unbox), 3); 48 | assert.strictEqual(mean([1, 2, 3, 4, 5, NaN].map(box), unbox), 3); 49 | assert.strictEqual(mean([10, null, 3, undefined, 5, NaN].map(box), unbox), 6); 50 | }); 51 | 52 | it("mean(array, f) returns undefined if the array contains no observed values", () => { 53 | assert.strictEqual(mean([].map(box), unbox), undefined); 54 | assert.strictEqual(mean([null].map(box), unbox), undefined); 55 | assert.strictEqual(mean([undefined].map(box), unbox), undefined); 56 | assert.strictEqual(mean([NaN].map(box), unbox), undefined); 57 | assert.strictEqual(mean([NaN, NaN].map(box), unbox), undefined); 58 | }); 59 | 60 | it("mean(array, f) coerces values to numbers", () => { 61 | assert.strictEqual(mean(["1"].map(box), unbox), 1); 62 | assert.strictEqual(mean(["5", "1", "2", "3", "4"].map(box), unbox), 3); 63 | assert.strictEqual(mean(["20", "3"].map(box), unbox), 11.5); 64 | assert.strictEqual(mean(["3", "20"].map(box), unbox), 11.5); 65 | }); 66 | 67 | it("mean(array, f) coerces values exactly once", () => { 68 | const numbers = [1, new OneTimeNumber(3)].map(box); 69 | assert.strictEqual(mean(numbers, unbox), 2); 70 | assert.strictEqual(mean(numbers, unbox), 1); 71 | }); 72 | 73 | it("mean(array, f) passes the accessor d, i, and array", () => { 74 | const results = []; 75 | const strings = ["a", "b", "c"]; 76 | mean(strings, (d, i, array) => results.push([d, i, array])); 77 | assert.deepStrictEqual(results, [["a", 0, strings], ["b", 1, strings], ["c", 2, strings]]); 78 | }); 79 | 80 | it("mean(array, f) uses the undefined context", () => { 81 | const results = []; 82 | mean([1, 2], function() { results.push(this); }); 83 | assert.deepStrictEqual(results, [undefined, undefined]); 84 | }); 85 | 86 | function box(value) { 87 | return {value: value}; 88 | } 89 | 90 | function unbox(box) { 91 | return box.value; 92 | } 93 | -------------------------------------------------------------------------------- /test/median-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {median, medianIndex} from "../src/index.js"; 3 | import {OneTimeNumber} from "./OneTimeNumber.js"; 4 | 5 | it("median(array) returns the median value for numbers", () => { 6 | assert.strictEqual(median([1]), 1); 7 | assert.strictEqual(median([5, 1, 2, 3]), 2.5); 8 | assert.strictEqual(median([5, 1, 2, 3, 4]), 3); 9 | assert.strictEqual(median([20, 3]), 11.5); 10 | assert.strictEqual(median([3, 20]), 11.5); 11 | }); 12 | 13 | it("median(array) ignores null, undefined and NaN", () => { 14 | assert.strictEqual(median([NaN, 1, 2, 3, 4, 5]), 3); 15 | assert.strictEqual(median([1, 2, 3, 4, 5, NaN]), 3); 16 | assert.strictEqual(median([10, null, 3, undefined, 5, NaN]), 5); 17 | }); 18 | 19 | it("median(array) can handle large numbers without overflowing", () => { 20 | assert.strictEqual(median([Number.MAX_VALUE, Number.MAX_VALUE]), Number.MAX_VALUE); 21 | assert.strictEqual(median([-Number.MAX_VALUE, -Number.MAX_VALUE]), -Number.MAX_VALUE); 22 | }); 23 | 24 | it("median(array) returns undefined if the array contains no observed values", () => { 25 | assert.strictEqual(median([]), undefined); 26 | assert.strictEqual(median([null]), undefined); 27 | assert.strictEqual(median([undefined]), undefined); 28 | assert.strictEqual(median([NaN]), undefined); 29 | assert.strictEqual(median([NaN, NaN]), undefined); 30 | }); 31 | 32 | it("median(array) coerces strings to numbers", () => { 33 | assert.strictEqual(median(["1"]), 1); 34 | assert.strictEqual(median(["5", "1", "2", "3", "4"]), 3); 35 | assert.strictEqual(median(["20", "3"]), 11.5); 36 | assert.strictEqual(median(["3", "20"]), 11.5); 37 | assert.strictEqual(median(["2", "3", "20"]), 3); 38 | assert.strictEqual(median(["20", "3", "2"]), 3); 39 | }); 40 | 41 | it("median(array) coerces values exactly once", () => { 42 | const array = [1, new OneTimeNumber(3)]; 43 | assert.strictEqual(median(array), 2); 44 | assert.strictEqual(median(array), 1); 45 | }); 46 | 47 | it("median(array, f) returns the median value for numbers", () => { 48 | assert.strictEqual(median([1].map(box), unbox), 1); 49 | assert.strictEqual(median([5, 1, 2, 3, 4].map(box), unbox), 3); 50 | assert.strictEqual(median([20, 3].map(box), unbox), 11.5); 51 | assert.strictEqual(median([3, 20].map(box), unbox), 11.5); 52 | }); 53 | 54 | it("median(array, f) ignores null, undefined and NaN", () => { 55 | assert.strictEqual(median([NaN, 1, 2, 3, 4, 5].map(box), unbox), 3); 56 | assert.strictEqual(median([1, 2, 3, 4, 5, NaN].map(box), unbox), 3); 57 | assert.strictEqual(median([10, null, 3, undefined, 5, NaN].map(box), unbox), 5); 58 | }); 59 | 60 | it("median(array, f) can handle large numbers without overflowing", () => { 61 | assert.strictEqual(median([Number.MAX_VALUE, Number.MAX_VALUE].map(box), unbox), Number.MAX_VALUE); 62 | assert.strictEqual(median([-Number.MAX_VALUE, -Number.MAX_VALUE].map(box), unbox), -Number.MAX_VALUE); 63 | }); 64 | 65 | it("median(array, f) returns undefined if the array contains no observed values", () => { 66 | assert.strictEqual(median([].map(box), unbox), undefined); 67 | assert.strictEqual(median([null].map(box), unbox), undefined); 68 | assert.strictEqual(median([undefined].map(box), unbox), undefined); 69 | assert.strictEqual(median([NaN].map(box), unbox), undefined); 70 | assert.strictEqual(median([NaN, NaN].map(box), unbox), undefined); 71 | }); 72 | 73 | it("median(array, f) coerces strings to numbers", () => { 74 | assert.strictEqual(median(["1"].map(box), unbox), 1); 75 | assert.strictEqual(median(["5", "1", "2", "3", "4"].map(box), unbox), 3); 76 | assert.strictEqual(median(["20", "3"].map(box), unbox), 11.5); 77 | assert.strictEqual(median(["3", "20"].map(box), unbox), 11.5); 78 | assert.strictEqual(median(["2", "3", "20"].map(box), unbox), 3); 79 | assert.strictEqual(median(["20", "3", "2"].map(box), unbox), 3); 80 | }); 81 | 82 | it("median(array, f) coerces values exactly once", () => { 83 | const array = [1, new OneTimeNumber(3)].map(box); 84 | assert.strictEqual(median(array, unbox), 2); 85 | assert.strictEqual(median(array, unbox), 1); 86 | }); 87 | 88 | it("median(array, f) passes the accessor d, i, and array", () => { 89 | const results = []; 90 | const array = ["a", "b", "c"]; 91 | median(array, (d, i, array) => results.push([d, i, array])); 92 | assert.deepStrictEqual(results, [["a", 0, array], ["b", 1, array], ["c", 2, array]]); 93 | }); 94 | 95 | it("median(array, f) uses the undefined context", () => { 96 | const results = []; 97 | median([1, 2], function() { results.push(this); }); 98 | assert.deepStrictEqual(results, [undefined, undefined]); 99 | }); 100 | 101 | it("medianIndex(array) returns the index", () => { 102 | assert.deepStrictEqual(medianIndex([1, 2]), 0); 103 | assert.deepStrictEqual(medianIndex([1, 2, 3]), 1); 104 | assert.deepStrictEqual(medianIndex([1, 3, 2]), 2); 105 | assert.deepStrictEqual(medianIndex([2, 3, 1]), 0); 106 | assert.deepStrictEqual(medianIndex([1]), 0); 107 | assert.deepStrictEqual(medianIndex([]), -1); 108 | }); 109 | 110 | 111 | function box(value) { 112 | return {value: value}; 113 | } 114 | 115 | function unbox(box) { 116 | return box.value; 117 | } 118 | -------------------------------------------------------------------------------- /test/merge-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {merge} from "../src/index.js"; 3 | 4 | it("merge(arrays) merges an array of arrays", () => { 5 | const a = {}, b = {}, c = {}, d = {}, e = {}, f = {}; 6 | assert.deepStrictEqual(merge([[a], [b, c], [d, e, f]]), [a, b, c, d, e, f]); 7 | }); 8 | 9 | it("merge(arrays) returns a new array when zero arrays are passed", () => { 10 | const input = []; 11 | const output = merge(input); 12 | assert.deepStrictEqual(output, []); 13 | input.push([0.1]); 14 | assert.deepStrictEqual(input, [[0.1]]); 15 | assert.deepStrictEqual(output, []); 16 | }); 17 | 18 | it("merge(arrays) returns a new array when one array is passed", () => { 19 | const input = [[1, 2, 3]]; 20 | const output = merge(input); 21 | assert.deepStrictEqual(output, [1, 2, 3]); 22 | input.push([4.1]); 23 | input[0].push(3.1); 24 | assert.deepStrictEqual(input, [[1, 2, 3, 3.1], [4.1]]); 25 | assert.deepStrictEqual(output, [1, 2, 3]); 26 | }); 27 | 28 | it("merge(arrays) returns a new array when two or more arrays are passed", () => { 29 | const input = [[1, 2, 3], [4, 5], [6]]; 30 | const output = merge(input); 31 | assert.deepStrictEqual(output, [1, 2, 3, 4, 5, 6]); 32 | input.push([7.1]); 33 | input[0].push(3.1); 34 | input[1].push(5.1); 35 | input[2].push(6.1); 36 | assert.deepStrictEqual(input, [[1, 2, 3, 3.1], [4, 5, 5.1], [6, 6.1], [7.1]]); 37 | assert.deepStrictEqual(output, [1, 2, 3, 4, 5, 6]); 38 | }); 39 | 40 | it("merge(arrays) does not modify the input arrays", () => { 41 | const input = [[1, 2, 3], [4, 5], [6]]; 42 | merge(input); 43 | assert.deepStrictEqual(input, [[1, 2, 3], [4, 5], [6]]); 44 | }); 45 | -------------------------------------------------------------------------------- /test/min-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {min} from "../src/index.js"; 3 | 4 | it("min(array) returns the least numeric value for numbers", () => { 5 | assert.deepStrictEqual(min([1]), 1); 6 | assert.deepStrictEqual(min([5, 1, 2, 3, 4]), 1); 7 | assert.deepStrictEqual(min([20, 3]), 3); 8 | assert.deepStrictEqual(min([3, 20]), 3); 9 | }); 10 | 11 | it("min(array) returns the least lexicographic value for strings", () => { 12 | assert.deepStrictEqual(min(["c", "a", "b"]), "a"); 13 | assert.deepStrictEqual(min(["20", "3"]), "20"); 14 | assert.deepStrictEqual(min(["3", "20"]), "20"); 15 | }); 16 | 17 | it("min(array) ignores null, undefined and NaN", () => { 18 | const o = {valueOf: () => NaN}; 19 | assert.deepStrictEqual(min([NaN, 1, 2, 3, 4, 5]), 1); 20 | assert.deepStrictEqual(min([o, 1, 2, 3, 4, 5]), 1); 21 | assert.deepStrictEqual(min([1, 2, 3, 4, 5, NaN]), 1); 22 | assert.deepStrictEqual(min([1, 2, 3, 4, 5, o]), 1); 23 | assert.deepStrictEqual(min([10, null, 3, undefined, 5, NaN]), 3); 24 | assert.deepStrictEqual(min([-1, null, -3, undefined, -5, NaN]), -5); 25 | }); 26 | 27 | it("min(array) compares heterogenous types as numbers", () => { 28 | assert.strictEqual(min([20, "3"]), "3"); 29 | assert.strictEqual(min(["20", 3]), 3); 30 | assert.strictEqual(min([3, "20"]), 3); 31 | assert.strictEqual(min(["3", 20]), "3"); 32 | }); 33 | 34 | it("min(array) returns undefined if the array contains no numbers", () => { 35 | assert.strictEqual(min([]), undefined); 36 | assert.strictEqual(min([null]), undefined); 37 | assert.strictEqual(min([undefined]), undefined); 38 | assert.strictEqual(min([NaN]), undefined); 39 | assert.strictEqual(min([NaN, NaN]), undefined); 40 | }); 41 | 42 | it("min(array, f) returns the least numeric value for numbers", () => { 43 | assert.deepStrictEqual(min([1].map(box), unbox), 1); 44 | assert.deepStrictEqual(min([5, 1, 2, 3, 4].map(box), unbox), 1); 45 | assert.deepStrictEqual(min([20, 3].map(box), unbox), 3); 46 | assert.deepStrictEqual(min([3, 20].map(box), unbox), 3); 47 | }); 48 | 49 | it("min(array, f) returns the least lexicographic value for strings", () => { 50 | assert.deepStrictEqual(min(["c", "a", "b"].map(box), unbox), "a"); 51 | assert.deepStrictEqual(min(["20", "3"].map(box), unbox), "20"); 52 | assert.deepStrictEqual(min(["3", "20"].map(box), unbox), "20"); 53 | }); 54 | 55 | it("min(array, f) ignores null, undefined and NaN", () => { 56 | const o = {valueOf: () => NaN}; 57 | assert.deepStrictEqual(min([NaN, 1, 2, 3, 4, 5].map(box), unbox), 1); 58 | assert.deepStrictEqual(min([o, 1, 2, 3, 4, 5].map(box), unbox), 1); 59 | assert.deepStrictEqual(min([1, 2, 3, 4, 5, NaN].map(box), unbox), 1); 60 | assert.deepStrictEqual(min([1, 2, 3, 4, 5, o].map(box), unbox), 1); 61 | assert.deepStrictEqual(min([10, null, 3, undefined, 5, NaN].map(box), unbox), 3); 62 | assert.deepStrictEqual(min([-1, null, -3, undefined, -5, NaN].map(box), unbox), -5); 63 | }); 64 | 65 | it("min(array, f) compares heterogenous types as numbers", () => { 66 | assert.strictEqual(min([20, "3"].map(box), unbox), "3"); 67 | assert.strictEqual(min(["20", 3].map(box), unbox), 3); 68 | assert.strictEqual(min([3, "20"].map(box), unbox), 3); 69 | assert.strictEqual(min(["3", 20].map(box), unbox), "3"); 70 | }); 71 | 72 | it("min(array, f) returns undefined if the array contains no observed values", () => { 73 | assert.strictEqual(min([].map(box), unbox), undefined); 74 | assert.strictEqual(min([null].map(box), unbox), undefined); 75 | assert.strictEqual(min([undefined].map(box), unbox), undefined); 76 | assert.strictEqual(min([NaN].map(box), unbox), undefined); 77 | assert.strictEqual(min([NaN, NaN].map(box), unbox), undefined); 78 | }); 79 | 80 | it("min(array, f) passes the accessor d, i, and array", () => { 81 | const results = []; 82 | const array = ["a", "b", "c"]; 83 | min(array, (d, i, array) => results.push([d, i, array])); 84 | assert.deepStrictEqual(results, [["a", 0, array], ["b", 1, array], ["c", 2, array]]); 85 | }); 86 | 87 | it("min(array, f) uses the undefined context", () => { 88 | const results = []; 89 | min([1, 2], function() { results.push(this); }); 90 | assert.deepStrictEqual(results, [undefined, undefined]); 91 | }); 92 | 93 | function box(value) { 94 | return {value: value}; 95 | } 96 | 97 | function unbox(box) { 98 | return box.value; 99 | } 100 | -------------------------------------------------------------------------------- /test/minIndex-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {minIndex} from "../src/index.js"; 3 | 4 | it("minIndex(array) returns the index of the least numeric value for numbers", () => { 5 | assert.deepStrictEqual(minIndex([1]), 0); 6 | assert.deepStrictEqual(minIndex([5, 1, 2, 3, 4]), 1); 7 | assert.deepStrictEqual(minIndex([20, 3]), 1); 8 | assert.deepStrictEqual(minIndex([3, 20]), 0); 9 | }); 10 | 11 | it("minIndex(array) returns the index of the least lexicographic value for strings", () => { 12 | assert.deepStrictEqual(minIndex(["c", "a", "b"]), 1); 13 | assert.deepStrictEqual(minIndex(["20", "3"]), 0); 14 | assert.deepStrictEqual(minIndex(["3", "20"]), 1); 15 | }); 16 | 17 | it("minIndex(array) ignores null, undefined and NaN", () => { 18 | const o = {valueOf: () => NaN}; 19 | assert.deepStrictEqual(minIndex([NaN, 1, 2, 3, 4, 5]), 1); 20 | assert.deepStrictEqual(minIndex([o, 1, 2, 3, 4, 5]), 1); 21 | assert.deepStrictEqual(minIndex([1, 2, 3, 4, 5, NaN]), 0); 22 | assert.deepStrictEqual(minIndex([1, 2, 3, 4, 5, o]), 0); 23 | assert.deepStrictEqual(minIndex([10, null, 3, undefined, 5, NaN]), 2); 24 | assert.deepStrictEqual(minIndex([-1, null, -3, undefined, -5, NaN]), 4); 25 | }); 26 | 27 | it("minIndex(array) compares heterogenous types as numbers", () => { 28 | assert.strictEqual(minIndex([20, "3"]), 1); 29 | assert.strictEqual(minIndex(["20", 3]), 1); 30 | assert.strictEqual(minIndex([3, "20"]), 0); 31 | assert.strictEqual(minIndex(["3", 20]), 0); 32 | }); 33 | 34 | it("minIndex(array) returns -1 if the array contains no numbers", () => { 35 | assert.strictEqual(minIndex([]), -1); 36 | assert.strictEqual(minIndex([null]), -1); 37 | assert.strictEqual(minIndex([undefined]), -1); 38 | assert.strictEqual(minIndex([NaN]), -1); 39 | assert.strictEqual(minIndex([NaN, NaN]), -1); 40 | }); 41 | 42 | it("minIndex(array, f) returns the index of the least numeric value for numbers", () => { 43 | assert.deepStrictEqual(minIndex([1].map(box), unbox), 0); 44 | assert.deepStrictEqual(minIndex([5, 1, 2, 3, 4].map(box), unbox), 1); 45 | assert.deepStrictEqual(minIndex([20, 3].map(box), unbox), 1); 46 | assert.deepStrictEqual(minIndex([3, 20].map(box), unbox), 0); 47 | }); 48 | 49 | it("minIndex(array, f) returns the index of the least lexicographic value for strings", () => { 50 | assert.deepStrictEqual(minIndex(["c", "a", "b"].map(box), unbox), 1); 51 | assert.deepStrictEqual(minIndex(["20", "3"].map(box), unbox), 0); 52 | assert.deepStrictEqual(minIndex(["3", "20"].map(box), unbox), 1); 53 | }); 54 | 55 | it("minIndex(array, f) ignores null, undefined and NaN", () => { 56 | const o = {valueOf: () => NaN}; 57 | assert.deepStrictEqual(minIndex([NaN, 1, 2, 3, 4, 5].map(box), unbox), 1); 58 | assert.deepStrictEqual(minIndex([o, 1, 2, 3, 4, 5].map(box), unbox), 1); 59 | assert.deepStrictEqual(minIndex([1, 2, 3, 4, 5, NaN].map(box), unbox), 0); 60 | assert.deepStrictEqual(minIndex([1, 2, 3, 4, 5, o].map(box), unbox), 0); 61 | assert.deepStrictEqual(minIndex([10, null, 3, undefined, 5, NaN].map(box), unbox), 2); 62 | assert.deepStrictEqual(minIndex([-1, null, -3, undefined, -5, NaN].map(box), unbox), 4); 63 | }); 64 | 65 | it("minIndex(array, f) compares heterogenous types as numbers", () => { 66 | assert.strictEqual(minIndex([20, "3"].map(box), unbox), 1); 67 | assert.strictEqual(minIndex(["20", 3].map(box), unbox), 1); 68 | assert.strictEqual(minIndex([3, "20"].map(box), unbox), 0); 69 | assert.strictEqual(minIndex(["3", 20].map(box), unbox), 0); 70 | }); 71 | 72 | it("minIndex(array, f) returns -1 if the array contains no observed values", () => { 73 | assert.strictEqual(minIndex([].map(box), unbox), -1); 74 | assert.strictEqual(minIndex([null].map(box), unbox), -1); 75 | assert.strictEqual(minIndex([undefined].map(box), unbox), -1); 76 | assert.strictEqual(minIndex([NaN].map(box), unbox), -1); 77 | assert.strictEqual(minIndex([NaN, NaN].map(box), unbox), -1); 78 | }); 79 | 80 | it("minIndex(array, f) passes the accessor d, i, and array", () => { 81 | const results = []; 82 | const array = ["a", "b", "c"]; 83 | minIndex(array, (d, i, array) => results.push([d, i, array])); 84 | assert.deepStrictEqual(results, [["a", 0, array], ["b", 1, array], ["c", 2, array]]); 85 | }); 86 | 87 | it("minIndex(array, f) uses the undefined context", () => { 88 | const results = []; 89 | minIndex([1, 2], function() { results.push(this); }); 90 | assert.deepStrictEqual(results, [undefined, undefined]); 91 | }); 92 | 93 | function box(value) { 94 | return {value: value}; 95 | } 96 | 97 | function unbox(box) { 98 | return box.value; 99 | } 100 | -------------------------------------------------------------------------------- /test/mode-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {mode} from "../src/index.js"; 3 | 4 | it("mode(array) returns the most frequent value for numbers", () => { 5 | assert.strictEqual(mode([1]), 1); 6 | assert.strictEqual(mode([5, 1, 1, 3, 4]), 1); 7 | }); 8 | 9 | it("mode(array) returns the most frequent value for strings", () => { 10 | assert.strictEqual(mode(["1"]), "1"); 11 | assert.strictEqual(mode(["5", "1", "1", "3", "4"]), "1"); 12 | }); 13 | 14 | it("mode(array) returns the most frequent value for heterogenous types", () => { 15 | assert.strictEqual(mode(["1"]), "1"); 16 | assert.strictEqual(mode(["5", "1", "1", 2, 2, "2", 1, 1, 1, "3", "4"]), 1); 17 | assert.strictEqual(mode(["5", 2, 2, "2", "2", 1, 1, 1, "3", "4"]), 1); 18 | }); 19 | 20 | it("mode(array) returns the first of the most frequent values", () => { 21 | assert.strictEqual(mode(["5", "1", "1", "2", "2", "3", "4"]), "1"); 22 | }); 23 | 24 | it("mode(array) ignores null, undefined and NaN", () => { 25 | const o = {valueOf: () => NaN}; 26 | assert.strictEqual(mode([NaN, 1, 1, 3, 4, 5]), 1); 27 | assert.strictEqual(mode([o, 1, null, null, 1, null]), 1); 28 | assert.strictEqual(mode([1, NaN, NaN, 1, 5, NaN]), 1); 29 | assert.strictEqual(mode([1, o, o, 1, 5, o]), 1); 30 | assert.strictEqual(mode([1, undefined, undefined, 1, 5, undefined]), 1); 31 | }); 32 | 33 | it("mode(array) returns undefined if the array contains no comparable values", () => { 34 | assert.strictEqual(mode([]), undefined); 35 | assert.strictEqual(mode([null]), undefined); 36 | assert.strictEqual(mode([undefined]), undefined); 37 | assert.strictEqual(mode([NaN]), undefined); 38 | assert.strictEqual(mode([NaN, NaN]), undefined); 39 | }); 40 | 41 | it("mode(array, f) returns the most frequent value for numbers", () => { 42 | assert.strictEqual(mode([1].map(box), unbox), 1); 43 | assert.strictEqual(mode([5, 1, 1, 3, 4].map(box), unbox), 1); 44 | }); 45 | 46 | it("mode(array, f) returns the most frequent value for strings", () => { 47 | assert.strictEqual(mode(["1"].map(box), unbox), "1"); 48 | assert.strictEqual(mode(["5", "1", "1", "3", "4"].map(box), unbox), "1"); 49 | }); 50 | 51 | it("mode(array, f) returns the most frequent value for heterogenous types", () => { 52 | assert.strictEqual(mode(["1"].map(box), unbox), "1"); 53 | assert.strictEqual(mode(["5", "1", "1", 2, 2, "2", 1, 1, 1, "3", "4"].map(box), unbox), 1); 54 | }); 55 | 56 | it("mode(array, f) returns the first of the most frequent values", () => { 57 | assert.strictEqual(mode(["5", "1", "1", "2", "2", "3", "4"].map(box), unbox), "1"); 58 | }); 59 | 60 | it("mode(array, f) ignores null, undefined and NaN", () => { 61 | const o = {valueOf: () => NaN}; 62 | assert.strictEqual(mode([NaN, 1, 1, 3, 4, 5].map(box), unbox), 1); 63 | assert.strictEqual(mode([o, 1, null, null, 1, null].map(box), unbox), 1); 64 | assert.strictEqual(mode([1, NaN, NaN, 1, 5, NaN].map(box), unbox), 1); 65 | assert.strictEqual(mode([1, o, o, 1, 5, o].map(box), unbox), 1); 66 | assert.strictEqual(mode([1, undefined, undefined, 1, 5, undefined].map(box), unbox), 1); 67 | }); 68 | 69 | it("mode(array, f) returns undefined if the array contains no comparable values", () => { 70 | assert.strictEqual(mode([].map(box), unbox), undefined); 71 | assert.strictEqual(mode([null].map(box), unbox), undefined); 72 | assert.strictEqual(mode([undefined].map(box), unbox), undefined); 73 | assert.strictEqual(mode([NaN].map(box), unbox), undefined); 74 | assert.strictEqual(mode([NaN, NaN].map(box), unbox), undefined); 75 | }); 76 | 77 | it("mode(array, f) passes the accessor d, i, and array", () => { 78 | const results = []; 79 | const array = ["a", "b", "c"]; 80 | mode(array, (d, i, array) => results.push([d, i, array])); 81 | assert.deepStrictEqual(results, [["a", 0, array], ["b", 1, array], ["c", 2, array]]); 82 | }); 83 | 84 | it("mode(array, f) uses the undefined context", () => { 85 | const results = []; 86 | mode([1, 2], function() { results.push(this); }); 87 | assert.deepStrictEqual(results, [undefined, undefined]); 88 | }); 89 | 90 | function box(value) { 91 | return {value: value}; 92 | } 93 | 94 | function unbox(box) { 95 | return box.value; 96 | } 97 | -------------------------------------------------------------------------------- /test/nice-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {nice} from "../src/index.js"; 3 | 4 | it("nice(start, stop, count) returns [start, stop] if any argument is NaN", () => { 5 | assert.deepStrictEqual(nice(NaN, 1, 1), [NaN, 1]); 6 | assert.deepStrictEqual(nice(0, NaN, 1), [0, NaN]); 7 | assert.deepStrictEqual(nice(0, 1, NaN), [0, 1]); 8 | assert.deepStrictEqual(nice(NaN, NaN, 1), [NaN, NaN]); 9 | assert.deepStrictEqual(nice(0, NaN, NaN), [0, NaN]); 10 | assert.deepStrictEqual(nice(NaN, 1, NaN), [NaN, 1]); 11 | assert.deepStrictEqual(nice(NaN, NaN, NaN), [NaN, NaN]); 12 | }); 13 | 14 | it("nice(start, stop, count) returns [start, stop] if start === stop", () => { 15 | assert.deepStrictEqual(nice(1, 1, -1), [1, 1]); 16 | assert.deepStrictEqual(nice(1, 1, 0), [1, 1]); 17 | assert.deepStrictEqual(nice(1, 1, NaN), [1, 1]); 18 | assert.deepStrictEqual(nice(1, 1, 1), [1, 1]); 19 | assert.deepStrictEqual(nice(1, 1, 10), [1, 1]); 20 | }); 21 | 22 | it("nice(start, stop, count) returns [start, stop] if count is not positive", () => { 23 | assert.deepStrictEqual(nice(0, 1, -1), [0, 1]); 24 | assert.deepStrictEqual(nice(0, 1, 0), [0, 1]); 25 | }); 26 | 27 | it("nice(start, stop, count) returns [start, stop] if count is infinity", () => { 28 | assert.deepStrictEqual(nice(0, 1, Infinity), [0, 1]); 29 | }); 30 | 31 | it("nice(start, stop, count) returns the expected values", () => { 32 | assert.deepStrictEqual(nice(0.132, 0.876, 1000), [0.132, 0.876]); 33 | assert.deepStrictEqual(nice(0.132, 0.876, 100), [0.13, 0.88]); 34 | assert.deepStrictEqual(nice(0.132, 0.876, 30), [0.12, 0.88]); 35 | assert.deepStrictEqual(nice(0.132, 0.876, 10), [0.1, 0.9]); 36 | assert.deepStrictEqual(nice(0.132, 0.876, 6), [0.1, 0.9]); 37 | assert.deepStrictEqual(nice(0.132, 0.876, 5), [0, 1]); 38 | assert.deepStrictEqual(nice(0.132, 0.876, 1), [0, 1]); 39 | assert.deepStrictEqual(nice(132, 876, 1000), [132, 876]); 40 | assert.deepStrictEqual(nice(132, 876, 100), [130, 880]); 41 | assert.deepStrictEqual(nice(132, 876, 30), [120, 880]); 42 | assert.deepStrictEqual(nice(132, 876, 10), [100, 900]); 43 | assert.deepStrictEqual(nice(132, 876, 6), [100, 900]); 44 | assert.deepStrictEqual(nice(132, 876, 5), [0, 1000]); 45 | assert.deepStrictEqual(nice(132, 876, 1), [0, 1000]); 46 | }); 47 | -------------------------------------------------------------------------------- /test/pairs-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {pairs} from "../src/index.js"; 3 | 4 | it("pairs(array) returns the empty array if input array has fewer than two elements", () => { 5 | assert.deepStrictEqual(pairs([]), []); 6 | assert.deepStrictEqual(pairs([1]), []); 7 | }); 8 | 9 | it("pairs(array) returns pairs of adjacent elements in the given array", () => { 10 | const a = {}, b = {}, c = {}, d = {}; 11 | assert.deepStrictEqual(pairs([1, 2]), [[1, 2]]); 12 | assert.deepStrictEqual(pairs([1, 2, 3]), [[1, 2], [2, 3]]); 13 | assert.deepStrictEqual(pairs([a, b, c, d]), [[a, b], [b, c], [c, d]]); 14 | }); 15 | 16 | it("pairs(array, f) invokes the function f for each pair of adjacent elements", () => { 17 | assert.deepStrictEqual(pairs([1, 3, 7], (a, b) => b - a), [2, 4]); 18 | }); 19 | 20 | it("pairs(array) includes null or undefined elements in pairs", () => { 21 | assert.deepStrictEqual(pairs([1, null, 2]), [[1, null], [null, 2]]); 22 | assert.deepStrictEqual(pairs([1, 2, undefined]), [[1, 2], [2, undefined]]); 23 | }); 24 | -------------------------------------------------------------------------------- /test/permute-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {permute} from "../src/index.js"; 3 | 4 | it("permute(…) permutes according to the specified index", () => { 5 | assert.deepStrictEqual(permute([3, 4, 5], [2, 1, 0]), [5, 4, 3]); 6 | assert.deepStrictEqual(permute([3, 4, 5], [2, 0, 1]), [5, 3, 4]); 7 | assert.deepStrictEqual(permute([3, 4, 5], [0, 1, 2]), [3, 4, 5]); 8 | }); 9 | 10 | it("permute(…) does not modify the input array", () => { 11 | const input = [3, 4, 5]; 12 | permute(input, [2, 1, 0]); 13 | assert.deepStrictEqual(input, [3, 4, 5]); 14 | }); 15 | 16 | it("permute(…) can duplicate input values", () => { 17 | assert.deepStrictEqual(permute([3, 4, 5], [0, 1, 0]), [3, 4, 3]); 18 | assert.deepStrictEqual(permute([3, 4, 5], [2, 2, 2]), [5, 5, 5]); 19 | assert.deepStrictEqual(permute([3, 4, 5], [0, 1, 1]), [3, 4, 4]); 20 | }); 21 | 22 | it("permute(…) can return more elements", () => { 23 | assert.deepStrictEqual(permute([3, 4, 5], [0, 0, 1, 2]), [3, 3, 4, 5]); 24 | assert.deepStrictEqual(permute([3, 4, 5], [0, 1, 1, 1]), [3, 4, 4, 4]); 25 | }); 26 | 27 | it("permute(…) can return fewer elements", () => { 28 | assert.deepStrictEqual(permute([3, 4, 5], [0]), [3]); 29 | assert.deepStrictEqual(permute([3, 4, 5], [1, 2]), [4, 5]); 30 | assert.deepStrictEqual(permute([3, 4, 5], []), []); 31 | }); 32 | 33 | it("permute(…) can return undefined elements", () => { 34 | assert.deepStrictEqual(permute([3, 4, 5], [10]), [undefined]); 35 | assert.deepStrictEqual(permute([3, 4, 5], [-1]), [undefined]); 36 | assert.deepStrictEqual(permute([3, 4, 5], [0, -1]), [3, undefined]); 37 | }); 38 | 39 | it("permute(…) can take an object as the source", () => { 40 | assert.deepStrictEqual(permute({foo: 1, bar: 2}, ["bar", "foo"]), [2, 1]); 41 | }); 42 | 43 | it("permute(…) can take a typed array as the source", () => { 44 | assert.deepStrictEqual(permute(Float32Array.of(1, 2), [0, 0, 1, 0]), [1, 1, 2, 1]); 45 | assert.strictEqual(Array.isArray(permute(Float32Array.of(1, 2), [0])), true); 46 | }); 47 | 48 | it("permute(…) can take an iterable as the keys", () => { 49 | assert.deepStrictEqual(permute({foo: 1, bar: 2}, new Set(["bar", "foo"])), [2, 1]); 50 | }); 51 | -------------------------------------------------------------------------------- /test/quantile-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {quantile, quantileIndex, quantileSorted} from "../src/index.js"; 3 | 4 | it("quantileSorted(array, p) requires sorted numeric input, quantile doesn't", () => { 5 | assert.strictEqual(quantileSorted([1, 2, 3, 4], 0), 1); 6 | assert.strictEqual(quantileSorted([1, 2, 3, 4], 1), 4); 7 | assert.strictEqual(quantileSorted([4, 3, 2, 1], 0), 4); 8 | assert.strictEqual(quantileSorted([4, 3, 2, 1], 1), 1); 9 | assert.strictEqual(quantile([1, 2, 3, 4], 0), 1); 10 | assert.strictEqual(quantile([1, 2, 3, 4], 1), 4); 11 | assert.strictEqual(quantile([4, 3, 2, 1], 0), 1); 12 | assert.strictEqual(quantile([4, 3, 2, 1], 1), 4); 13 | }); 14 | 15 | it("quantile() accepts an iterable", () => { 16 | assert.strictEqual(quantile(new Set([1, 2, 3, 4]), 1), 4); 17 | }); 18 | 19 | it("quantile(array, p) uses the R-7 method", () => { 20 | const even = [3, 6, 7, 8, 8, 10, 13, 15, 16, 20]; 21 | assert.strictEqual(quantile(even, 0), 3); 22 | assert.strictEqual(quantile(even, 0.25), 7.25); 23 | assert.strictEqual(quantile(even, 0.5), 9); 24 | assert.strictEqual(quantile(even, 0.75), 14.5); 25 | assert.strictEqual(quantile(even, 1), 20); 26 | const odd = [3, 6, 7, 8, 8, 9, 10, 13, 15, 16, 20]; 27 | assert.strictEqual(quantile(odd, 0), 3); 28 | assert.strictEqual(quantile(odd, 0.25), 7.5); 29 | assert.strictEqual(quantile(odd, 0.5), 9); 30 | assert.strictEqual(quantile(odd, 0.75), 14); 31 | assert.strictEqual(quantile(odd, 1), 20); 32 | }); 33 | 34 | it("quantile(array, p) coerces values to numbers", () => { 35 | const strings = ["1", "2", "3", "4"]; 36 | assert.strictEqual(quantile(strings, 1 / 3), 2); 37 | assert.strictEqual(quantile(strings, 1 / 2), 2.5); 38 | assert.strictEqual(quantile(strings, 2 / 3), 3); 39 | const dates = [new Date(Date.UTC(2011, 0, 1)), new Date(Date.UTC(2012, 0, 1))]; 40 | assert.strictEqual(quantile(dates, 0), +new Date(Date.UTC(2011, 0, 1))); 41 | assert.strictEqual(quantile(dates, 1 / 2), +new Date(Date.UTC(2011, 6, 2, 12))); 42 | assert.strictEqual(quantile(dates, 1), +new Date(Date.UTC(2012, 0, 1))); 43 | }); 44 | 45 | it("quantile(array, p) returns an exact value for integer p-values", () => { 46 | const data = [1, 2, 3, 4]; 47 | assert.strictEqual(quantile(data, 1 / 3), 2); 48 | assert.strictEqual(quantile(data, 2 / 3), 3); 49 | }); 50 | 51 | it("quantile(array, p) returns the expected value for integer or fractional p", () => { 52 | const data = [3, 1, 2, 4, 0]; 53 | assert.strictEqual(quantile(data, 0 / 4), 0); 54 | assert.strictEqual(quantile(data, 0.1 / 4), 0.1); 55 | assert.strictEqual(quantile(data, 1 / 4), 1); 56 | assert.strictEqual(quantile(data, 1.5 / 4), 1.5); 57 | assert.strictEqual(quantile(data, 2 / 4), 2); 58 | assert.strictEqual(quantile(data, 2.5 / 4), 2.5); 59 | assert.strictEqual(quantile(data, 3 / 4), 3); 60 | assert.strictEqual(quantile(data, 3.2 / 4), 3.2); 61 | assert.strictEqual(quantile(data, 4 / 4), 4); 62 | }); 63 | 64 | it("quantile(array, p) returns the first value for p = 0", () => { 65 | const data = [1, 2, 3, 4]; 66 | assert.strictEqual(quantile(data, 0), 1); 67 | }); 68 | 69 | it("quantile(array, p) returns the last value for p = 1", () => { 70 | const data = [1, 2, 3, 4]; 71 | assert.strictEqual(quantile(data, 1), 4); 72 | }); 73 | 74 | it("quantile(array, p) returns undefined if p is not a number", () => { 75 | assert.strictEqual(quantile([1, 2, 3]), undefined); 76 | assert.strictEqual(quantile([1, 2, 3], "no"), undefined); 77 | assert.strictEqual(quantile([1, 2, 3], NaN), undefined); 78 | assert.strictEqual(quantile([1, 2, 3], null), 1); // +null is 0 79 | }); 80 | 81 | it("quantile(array, p, f) observes the specified accessor", () => { 82 | assert.strictEqual(quantile([1, 2, 3, 4].map(box), 0.5, unbox), 2.5); 83 | assert.strictEqual(quantile([1, 2, 3, 4].map(box), 0, unbox), 1); 84 | assert.strictEqual(quantile([1, 2, 3, 4].map(box), 1, unbox), 4); 85 | assert.strictEqual(quantile([2].map(box), 0, unbox), 2); 86 | assert.strictEqual(quantile([2].map(box), 0.5, unbox), 2); 87 | assert.strictEqual(quantile([2].map(box), 1, unbox), 2); 88 | assert.strictEqual(quantile([], 0, unbox), undefined); 89 | assert.strictEqual(quantile([], 0.5, unbox), undefined); 90 | assert.strictEqual(quantile([], 1, unbox), undefined); 91 | }); 92 | 93 | it("quantileIndex(array, p) returns the index", () => { 94 | assert.deepStrictEqual(quantileIndex([1, 2], 0.2), 0); 95 | assert.deepStrictEqual(quantileIndex([1, 2, 3], 0.2), 0); 96 | assert.deepStrictEqual(quantileIndex([1, 3, 2], 0.2), 0); 97 | assert.deepStrictEqual(quantileIndex([2, 3, 1], 0.2), 2); 98 | assert.deepStrictEqual(quantileIndex([1], 0.2), 0); 99 | assert.deepStrictEqual(quantileIndex([], 0.2), -1); 100 | }); 101 | 102 | it("quantileIndex(array, 0) returns the minimum index", () => { 103 | assert.deepStrictEqual(quantileIndex([1, 2], 0), 0); 104 | assert.deepStrictEqual(quantileIndex([1, 2, 3], 0), 0); 105 | assert.deepStrictEqual(quantileIndex([1, 3, 2], 0), 0); 106 | assert.deepStrictEqual(quantileIndex([2, 3, 1], 0), 2); 107 | assert.deepStrictEqual(quantileIndex([1], 0), 0); 108 | assert.deepStrictEqual(quantileIndex([], 0), -1); 109 | }); 110 | 111 | it("quantileIndex(array, 1) returns the maximum index", () => { 112 | assert.deepStrictEqual(quantileIndex([1, 2], 1), 1); 113 | assert.deepStrictEqual(quantileIndex([1, 2, 3], 1), 2); 114 | assert.deepStrictEqual(quantileIndex([1, 3, 2], 1), 1); 115 | assert.deepStrictEqual(quantileIndex([2, 3, 1], 1), 1); 116 | assert.deepStrictEqual(quantileIndex([1], 1), 0); 117 | assert.deepStrictEqual(quantileIndex([], 1), -1); 118 | }); 119 | 120 | it("quantileIndex(array, 0.5) handles undefined values", () => { 121 | assert.deepStrictEqual(quantileIndex([1, 1, 1, null, 2, 3, 3, 3], 0.5), 4); 122 | assert.deepStrictEqual(quantileIndex([1, 1, 1, null, 2, 3, 3, 3], 0.5, (d) => d), 4); 123 | }); 124 | 125 | it("quantileIndex(array, 0.5) returns the first of equivalent values", () => { 126 | assert.deepStrictEqual(quantileIndex([1, 1, 1, 2, 2, 3, 3, 3], 0.5), 4); 127 | }); 128 | 129 | function box(value) { 130 | return {value: value}; 131 | } 132 | 133 | function unbox(box) { 134 | return box.value; 135 | } 136 | -------------------------------------------------------------------------------- /test/quickselect-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {quickselect} from "../src/index.js"; 3 | 4 | it("quickselect(array, k) does nothing if k is not a number", () => { 5 | const array = [3, 1, 2]; 6 | assert.strictEqual(quickselect(array), array); 7 | assert.deepStrictEqual(array, [3, 1, 2]); 8 | assert.strictEqual(quickselect(array, NaN), array); 9 | assert.deepStrictEqual(array, [3, 1, 2]); 10 | assert.strictEqual(quickselect(array, "no"), array); 11 | assert.deepStrictEqual(array, [3, 1, 2]); 12 | assert.strictEqual(quickselect(array, undefined), array); 13 | assert.deepStrictEqual(array, [3, 1, 2]); 14 | assert.strictEqual(quickselect(array, null), array); // coerced to zero 15 | assert.deepStrictEqual(array, [1, 2, 3]); 16 | }); 17 | 18 | it("quickselect(array, k) does nothing if k is less than left", () => { 19 | const array = [3, 1, 2]; 20 | assert.strictEqual(quickselect(array, -1), array); 21 | assert.deepStrictEqual(array, [3, 1, 2]); 22 | assert.strictEqual(quickselect(array, -0.5), array); 23 | assert.deepStrictEqual(array, [3, 1, 2]); 24 | }); 25 | 26 | it("quickselect(array, k) does nothing if k is greater than right", () => { 27 | const array = [3, 1, 2]; 28 | assert.strictEqual(quickselect(array, 3), array); 29 | assert.deepStrictEqual(array, [3, 1, 2]); 30 | assert.strictEqual(quickselect(array, 3.4), array); 31 | assert.deepStrictEqual(array, [3, 1, 2]); 32 | }); 33 | 34 | it("quickselect(array, k) implicitly floors k, left, and right", () => { 35 | assert.deepStrictEqual(quickselect([3, 1, 2], 0.5), [1, 2, 3]); 36 | assert.deepStrictEqual(quickselect([3, 1, 2, 5, 4], 4.1), [3, 1, 2, 4, 5]); 37 | assert.deepStrictEqual(quickselect([3, 1, 2], 0, 0.5), [1, 2, 3]); 38 | assert.deepStrictEqual(quickselect([3, 1, 2], 0, 0, 2.5), [1, 2, 3]); 39 | }); 40 | -------------------------------------------------------------------------------- /test/range-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {range} from "../src/index.js"; 3 | 4 | it("range(stop) returns [0, 1, 2, … stop - 1]", () => { 5 | assert.deepStrictEqual(range(5), [0, 1, 2, 3, 4]); 6 | assert.deepStrictEqual(range(2.01), [0, 1, 2]); 7 | assert.deepStrictEqual(range(1), [0]); 8 | assert.deepStrictEqual(range(.5), [0]); 9 | }); 10 | 11 | it("range(stop) returns an empty array if stop <= 0", () => { 12 | assert.deepStrictEqual(range(0), []); 13 | assert.deepStrictEqual(range(-0.5), []); 14 | assert.deepStrictEqual(range(-1), []); 15 | }); 16 | 17 | it("range(stop) returns an empty array if stop is NaN", () => { 18 | assert.deepStrictEqual(range(NaN), []); 19 | assert.deepStrictEqual(range(), []); 20 | }); 21 | 22 | it("range(start, stop) returns [start, start + 1, … stop - 1]", () => { 23 | assert.deepStrictEqual(range(0, 5), [0, 1, 2, 3, 4]); 24 | assert.deepStrictEqual(range(2, 5), [2, 3, 4]); 25 | assert.deepStrictEqual(range(2.5, 5), [2.5, 3.5, 4.5]); 26 | assert.deepStrictEqual(range(-1, 3), [-1, 0, 1, 2]); 27 | }); 28 | 29 | it("range(start, stop) returns an empty array if start or stop is NaN", () => { 30 | assert.deepStrictEqual(range(0, NaN), []); 31 | assert.deepStrictEqual(range(1, NaN), []); 32 | assert.deepStrictEqual(range(-1, NaN), []); 33 | assert.deepStrictEqual(range(0, undefined), []); 34 | assert.deepStrictEqual(range(1, undefined), []); 35 | assert.deepStrictEqual(range(-1, undefined), []); 36 | assert.deepStrictEqual(range(NaN, 0), []); 37 | assert.deepStrictEqual(range(NaN, 1), []); 38 | assert.deepStrictEqual(range(NaN, -1), []); 39 | assert.deepStrictEqual(range(undefined, 0), []); 40 | assert.deepStrictEqual(range(undefined, 1), []); 41 | assert.deepStrictEqual(range(undefined, -1), []); 42 | assert.deepStrictEqual(range(NaN, NaN), []); 43 | assert.deepStrictEqual(range(undefined, undefined), []); 44 | }); 45 | 46 | it("range(start, stop) returns an empty array if start >= stop", () => { 47 | assert.deepStrictEqual(range(0, 0), []); 48 | assert.deepStrictEqual(range(5, 5), []); 49 | assert.deepStrictEqual(range(6, 5), []); 50 | assert.deepStrictEqual(range(10, 10), []); 51 | assert.deepStrictEqual(range(20, 10), []); 52 | }); 53 | 54 | it("range(start, stop, step) returns [start, start + step, start + 2 * step, … stop - step]", () => { 55 | assert.deepStrictEqual(range(0, 5, 1), [0, 1, 2, 3, 4]); 56 | assert.deepStrictEqual(range(0, 5, 2), [0, 2, 4]); 57 | assert.deepStrictEqual(range(2, 5, 2), [2, 4]); 58 | assert.deepStrictEqual(range(-1, 3, 2), [-1, 1]); 59 | }); 60 | 61 | it("range(start, stop, step) allows a negative step", () => { 62 | assert.deepStrictEqual(range(5, 0, -1), [5, 4, 3, 2, 1]); 63 | assert.deepStrictEqual(range(5, 0, -2), [5, 3, 1]); 64 | assert.deepStrictEqual(range(5, 2, -2), [5, 3]); 65 | assert.deepStrictEqual(range(3, -1, -2), [3, 1]); 66 | }); 67 | 68 | it("range(start, stop, step) returns an empty array if start >= stop and step > 0", () => { 69 | assert.deepStrictEqual(range(5, 5, 2), []); 70 | assert.deepStrictEqual(range(6, 5, 2), []); 71 | assert.deepStrictEqual(range(10, 10, 1), []); 72 | assert.deepStrictEqual(range(10, 10, 0.5), []); 73 | assert.deepStrictEqual(range(0, 0, 1), []); 74 | assert.deepStrictEqual(range(0, 0, 0.5), []); 75 | assert.deepStrictEqual(range(20, 10, 2), []); 76 | assert.deepStrictEqual(range(20, 10, 1), []); 77 | assert.deepStrictEqual(range(20, 10, 0.5), []); 78 | }); 79 | 80 | it("range(start, stop, step) returns an empty array if start >= stop and step < 0", () => { 81 | assert.deepStrictEqual(range(5, 5, -2), []); 82 | assert.deepStrictEqual(range(5, 6, -2), []); 83 | assert.deepStrictEqual(range(10, 10, -1), []); 84 | assert.deepStrictEqual(range(10, 10, -0.5), []); 85 | assert.deepStrictEqual(range(0, 0, -1), []); 86 | assert.deepStrictEqual(range(0, 0, -0.5), []); 87 | assert.deepStrictEqual(range(10, 20, -2), []); 88 | assert.deepStrictEqual(range(10, 20, -1), []); 89 | assert.deepStrictEqual(range(10, 20, -0.5), []); 90 | }); 91 | 92 | it("range(start, stop, step) returns an empty array if start, stop or step is NaN", () => { 93 | assert.deepStrictEqual(range(NaN, 3, 2), []); 94 | assert.deepStrictEqual(range(3, NaN, 2), []); 95 | assert.deepStrictEqual(range(0, 5, NaN), []); 96 | assert.deepStrictEqual(range(NaN, NaN, NaN), []); 97 | assert.deepStrictEqual(range(NaN, NaN, NaN), []); 98 | assert.deepStrictEqual(range(undefined, undefined, undefined), []); 99 | assert.deepStrictEqual(range(0, 10, NaN), []); 100 | assert.deepStrictEqual(range(10, 0, NaN), []); 101 | assert.deepStrictEqual(range(0, 10, undefined), []); 102 | assert.deepStrictEqual(range(10, 0, undefined), []); 103 | }); 104 | 105 | it("range(start, stop, step) returns an empty array if step is zero", () => { 106 | assert.deepStrictEqual(range(0, 5, 0), []); 107 | }); 108 | 109 | it("range(start, stop, step) returns exactly [start + step * i, …] for fractional steps", () => { 110 | assert.deepStrictEqual(range(0, 0.5, 0.1), [0 + 0.1 * 0, 0 + 0.1 * 1, 0 + 0.1 * 2, 0 + 0.1 * 3, 0 + 0.1 * 4]); 111 | assert.deepStrictEqual(range(0.5, 0, -0.1), [0.5 - 0.1 * 0, 0.5 - 0.1 * 1, 0.5 - 0.1 * 2, 0.5 - 0.1 * 3, 0.5 - 0.1 * 4]); 112 | assert.deepStrictEqual(range(-2, -1.2, 0.1), [-2 + 0.1 * 0, -2 + 0.1 * 1, -2 + 0.1 * 2, -2 + 0.1 * 3, -2 + 0.1 * 4, -2 + 0.1 * 5, -2 + 0.1 * 6, -2 + 0.1 * 7]); 113 | assert.deepStrictEqual(range(-1.2, -2, -0.1), [-1.2 - 0.1 * 0, -1.2 - 0.1 * 1, -1.2 - 0.1 * 2, -1.2 - 0.1 * 3, -1.2 - 0.1 * 4, -1.2 - 0.1 * 5, -1.2 - 0.1 * 6, -1.2 - 0.1 * 7]); 114 | }); 115 | 116 | it("range(start, stop, step) returns exactly [start + step * i, …] for very small fractional steps", () => { 117 | assert.deepStrictEqual(range(2.1e-31, 5e-31, 1.1e-31), [2.1e-31 + 1.1e-31 * 0, 2.1e-31 + 1.1e-31 * 1, 2.1e-31 + 1.1e-31 * 2]); 118 | assert.deepStrictEqual(range(5e-31, 2.1e-31, -1.1e-31), [5e-31 - 1.1e-31 * 0, 5e-31 - 1.1e-31 * 1, 5e-31 - 1.1e-31 * 2]); 119 | }); 120 | 121 | it("range(start, stop, step) returns exactly [start + step * i, …] for very large fractional steps", () => { 122 | assert.deepStrictEqual(range(1e300, 2e300, 0.3e300), [1e300 + 0.3e300 * 0, 1e300 + 0.3e300 * 1, 1e300 + 0.3e300 * 2, 1e300 + 0.3e300 * 3]); 123 | assert.deepStrictEqual(range(2e300, 1e300, -0.3e300), [2e300 - 0.3e300 * 0, 2e300 - 0.3e300 * 1, 2e300 - 0.3e300 * 2, 2e300 - 0.3e300 * 3]); 124 | }); 125 | `` 126 | -------------------------------------------------------------------------------- /test/rank-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import ascending from "../src/ascending.js"; 3 | import descending from "../src/descending.js"; 4 | import rank from "../src/rank.js"; 5 | 6 | it("rank(numbers) returns the rank of numbers", () => { 7 | assert.deepStrictEqual(rank([1000, 10, 0]), Float64Array.of(2, 1, 0)); 8 | assert.deepStrictEqual(rank([1.2, 1.1, 1.2, 1.0, 1.5, 1.2]), Float64Array.of(2, 1, 2, 0, 5, 2)); 9 | }); 10 | 11 | it("rank(strings) returns the rank of letters", () => { 12 | assert.deepStrictEqual(rank([..."EDGFCBA"]), Float64Array.of(4, 3, 6, 5, 2, 1, 0)); 13 | assert.deepStrictEqual(rank("EDGFCBA"), Float64Array.of(4, 3, 6, 5, 2, 1, 0)); 14 | }); 15 | 16 | it("rank(dates) returns the rank of Dates", () => { 17 | assert.deepStrictEqual(rank([new Date("2000-01-01"), new Date("2000-01-01"), new Date("1999-01-01"), new Date("2001-01-01")]), Float64Array.of(1, 1, 0, 3)); 18 | }); 19 | 20 | it("rank(iterator) accepts an iterator", () => { 21 | assert.deepStrictEqual(rank(new Set(["B", "C", "A"])), Float64Array.of(1, 2, 0)); 22 | }); 23 | 24 | it("rank(undefineds) ranks undefined as NaN", () => { 25 | assert.deepStrictEqual(rank([1.2, 1.1, undefined, 1.0, undefined, 1.5]), Float64Array.of(2, 1, NaN, 0, NaN, 3)); 26 | assert.deepStrictEqual(rank([, null, , 1.2, 1.1, undefined, 1.0, NaN, 1.5]), Float64Array.of(NaN, NaN, NaN, 2, 1, NaN, 0, NaN, 3)); 27 | }); 28 | 29 | it("rank(values, valueof) accepts an accessor", () => { 30 | assert.deepStrictEqual(rank([{x: 3}, {x: 1}, {x: 2}, {x: 4}, {}], d => d.x), Float64Array.of(2, 0, 1, 3, NaN)); 31 | }); 32 | 33 | it("rank(values, compare) accepts a comparator", () => { 34 | assert.deepStrictEqual(rank([{x: 3}, {x: 1}, {x: 2}, {x: 4}, {}], (a, b) => a.x - b.x), Float64Array.of(2, 0, 1, 3, NaN)); 35 | assert.deepStrictEqual(rank([{x: 3}, {x: 1}, {x: 2}, {x: 4}, {}], (a, b) => b.x - a.x), Float64Array.of(1, 3, 2, 0, NaN)); 36 | assert.deepStrictEqual(rank(["aa", "ba", "bc", "bb", "ca"], (a, b) => ascending(a[0], b[0]) || ascending(a[1], b[1])), Float64Array.of(0, 1, 3, 2, 4)); 37 | assert.deepStrictEqual(rank(["A", null, "B", "C", "D"], descending), Float64Array.of(3, NaN, 2, 1, 0)); 38 | }); 39 | 40 | it("rank(values) computes the ties as expected", () => { 41 | assert.deepStrictEqual(rank(["a", "b", "b", "b", "c"]), Float64Array.of(0, 1, 1, 1, 4)); 42 | assert.deepStrictEqual(rank(["a", "b", "b", "b", "b", "c"]), Float64Array.of(0, 1, 1, 1, 1, 5)); 43 | }); 44 | 45 | it("rank(values) handles NaNs as expected", () => { 46 | assert.deepStrictEqual(rank(["a", "b", "b", "b", "c", null]), Float64Array.of(0, 1, 1, 1, 4, NaN)); 47 | assert.deepStrictEqual(rank(["a", "b", "b", "b", "b", "c", null]), Float64Array.of(0, 1, 1, 1, 1, 5, NaN)); 48 | }); 49 | -------------------------------------------------------------------------------- /test/reduce-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {reduce} from "../src/index.js"; 3 | 4 | it("reduce(values, reducer) returns the reduced value", () => { 5 | assert.strictEqual(reduce([1, 2, 3, 2, 1], (p, v) => p + v), 9); 6 | assert.strictEqual(reduce([1, 2], (p, v) => p + v), 3); 7 | assert.strictEqual(reduce([1], (p, v) => p + v), 1); 8 | assert.strictEqual(reduce([], (p, v) => p + v), undefined); 9 | }); 10 | 11 | it("reduce(values, reducer, initial) returns the reduced value", () => { 12 | assert.strictEqual(reduce([1, 2, 3, 2, 1], (p, v) => p + v, 0), 9); 13 | assert.strictEqual(reduce([1], (p, v) => p + v, 0), 1); 14 | assert.strictEqual(reduce([], (p, v) => p + v, 0), 0); 15 | assert.deepStrictEqual(reduce([1, 2, 3, 2, 1], (p, v) => p.concat(v), []), [1, 2, 3, 2, 1]); 16 | }); 17 | 18 | it("reduce(values, reducer) accepts an iterable", () => { 19 | assert.strictEqual(reduce(new Set([1, 2, 3, 2, 1]), (p, v) => p + v), 6); 20 | assert.strictEqual(reduce((function*() { yield* [1, 2, 3, 2, 1]; })(), (p, v) => p + v), 9); 21 | assert.strictEqual(reduce(Uint8Array.of(1, 2, 3, 2, 1), (p, v) => p + v), 9); 22 | }); 23 | 24 | it("reduce(values, reducer) enforces that test is a function", () => { 25 | assert.throws(() => reduce([]), TypeError); 26 | }); 27 | 28 | it("reduce(values, reducer) enforces that values is iterable", () => { 29 | assert.throws(() => reduce({}, () => true), TypeError); 30 | }); 31 | 32 | it("reduce(values, reducer) passes reducer (reduced, value, index, values)", () => { 33 | const calls = []; 34 | const values = new Set([5, 4, 3, 2, 1]); 35 | reduce(values, function(p, v) { calls.push([this, ...arguments]); return p + v; }); 36 | assert.deepStrictEqual(calls, [ 37 | [undefined, 5, 4, 1, values], 38 | [undefined, 9, 3, 2, values], 39 | [undefined, 12, 2, 3, values], 40 | [undefined, 14, 1, 4, values] 41 | ]); 42 | }); 43 | 44 | it("reduce(values, reducer, initial) passes reducer (reduced, value, index, values)", () => { 45 | const calls = []; 46 | const values = new Set([5, 4, 3, 2, 1]); 47 | reduce(values, function(p, v) { calls.push([this, ...arguments]); return p + v; }, 0); 48 | assert.deepStrictEqual(calls, [ 49 | [undefined, 0, 5, 0, values], 50 | [undefined, 5, 4, 1, values], 51 | [undefined, 9, 3, 2, values], 52 | [undefined, 12, 2, 3, values], 53 | [undefined, 14, 1, 4, values] 54 | ]); 55 | }); 56 | 57 | it("reduce(values, reducer, initial) does not skip sparse elements", () => { 58 | assert.strictEqual(reduce([, 1, 2,, ], (p, v) => p + (v === undefined ? -1 : v), 0), 1); 59 | }); 60 | -------------------------------------------------------------------------------- /test/reverse-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {reverse} from "../src/index.js"; 3 | 4 | it("reverse(values) returns a reversed copy", () => { 5 | const input = [1, 3, 2, 5, 4]; 6 | assert.deepStrictEqual(reverse(input), [4, 5, 2, 3, 1]); 7 | assert.deepStrictEqual(input, [1, 3, 2, 5, 4]); // does not mutate 8 | }); 9 | 10 | it("reverse(values) returns an array", () => { 11 | assert.strictEqual(Array.isArray(reverse(Uint8Array.of(1, 2))), true); 12 | }); 13 | 14 | it("reverse(values) accepts an iterable", () => { 15 | assert.deepStrictEqual(reverse(new Set([1, 2, 3, 2, 1])), [3, 2, 1]); 16 | assert.deepStrictEqual(reverse((function*() { yield* [1, 3, 2, 5, 4]; })()), [4, 5, 2, 3, 1]); 17 | assert.deepStrictEqual(reverse(Uint8Array.of(1, 3, 2, 5, 4)), [4, 5, 2, 3, 1]); 18 | }); 19 | 20 | it("reverse(values) enforces that values is iterable", () => { 21 | assert.throws(() => reverse({}), TypeError); 22 | }); 23 | 24 | it("reverse(values) does not skip sparse elements", () => { 25 | assert.deepStrictEqual(reverse([, 1, 2,, ]), [undefined, 2, 1, undefined]); 26 | }); 27 | -------------------------------------------------------------------------------- /test/rollup-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {rollup, sum} from "../src/index.js"; 3 | 4 | const data = [ 5 | {name: "jim", amount: "3400", date: "11/12/2015"}, 6 | {name: "carl", amount: "12011", date: "11/12/2015"}, 7 | {name: "stacy", amount: "1201", date: "01/04/2016"}, 8 | {name: "stacy", amount: "3405", date: "01/04/2016"} 9 | ]; 10 | 11 | it("rollup(data, reduce, accessor) returns the expected map", () => { 12 | assert.deepStrictEqual( 13 | entries(rollup(data, v => v.length, d => d.name), 1), 14 | [ 15 | ["jim", 1], 16 | ["carl", 1], 17 | ["stacy", 2] 18 | ] 19 | ); 20 | assert.deepStrictEqual( 21 | entries(rollup(data, v => sum(v, d => d.amount), d => d.name), 1), 22 | [ 23 | ["jim", 3400], 24 | ["carl", 12011], 25 | ["stacy", 4606] 26 | ] 27 | ); 28 | }); 29 | 30 | it("rollup(data, reduce, accessor, accessor) returns the expected map", () => { 31 | assert.deepStrictEqual( 32 | entries(rollup(data, v => v.length, d => d.name, d => d.amount), 2), 33 | [ 34 | [ 35 | "jim", 36 | [ 37 | ["3400", 1] 38 | ] 39 | ], 40 | [ 41 | "carl", 42 | [ 43 | ["12011", 1] 44 | ] 45 | ], 46 | [ 47 | "stacy", 48 | [ 49 | ["1201", 1], 50 | ["3405", 1] 51 | ] 52 | ] 53 | ] 54 | ); 55 | }); 56 | 57 | function entries(map, depth) { 58 | if (depth > 0) { 59 | return Array.from(map, ([k, v]) => [k, entries(v, depth - 1)]); 60 | } else { 61 | return map; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /test/rollups-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {rollups, sum} from "../src/index.js"; 3 | 4 | const data = [ 5 | {name: "jim", amount: "3400", date: "11/12/2015"}, 6 | {name: "carl", amount: "12011", date: "11/12/2015"}, 7 | {name: "stacy", amount: "1201", date: "01/04/2016"}, 8 | {name: "stacy", amount: "3405", date: "01/04/2016"} 9 | ]; 10 | 11 | it("rollups(data, reduce, accessor) returns the expected array", () => { 12 | assert.deepStrictEqual( 13 | rollups(data, v => v.length, d => d.name), 14 | [ 15 | ["jim", 1], 16 | ["carl", 1], 17 | ["stacy", 2] 18 | ] 19 | ); 20 | assert.deepStrictEqual( 21 | rollups(data, v => sum(v, d => d.amount), d => d.name), 22 | [ 23 | ["jim", 3400], 24 | ["carl", 12011], 25 | ["stacy", 4606] 26 | ] 27 | ); 28 | }); 29 | 30 | it("rollups(data, reduce, accessor, accessor) returns the expected array", () => { 31 | assert.deepStrictEqual( 32 | rollups(data, v => v.length, d => d.name, d => d.amount), 33 | [ 34 | [ 35 | "jim", 36 | [ 37 | ["3400", 1] 38 | ] 39 | ], 40 | [ 41 | "carl", 42 | [ 43 | ["12011", 1] 44 | ] 45 | ], 46 | [ 47 | "stacy", 48 | [ 49 | ["1201", 1], 50 | ["3405", 1] 51 | ] 52 | ] 53 | ] 54 | ); 55 | }); 56 | -------------------------------------------------------------------------------- /test/scan-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {descending, scan} from "../src/index.js"; 3 | 4 | it("scan(array) compares using natural order", () => { 5 | assert.strictEqual(scan([0, 1]), 0); 6 | assert.strictEqual(scan([1, 0]), 1); 7 | assert.strictEqual(scan([0, "1"]), 0); 8 | assert.strictEqual(scan(["1", 0]), 1); 9 | assert.strictEqual(scan(["10", "2"]), 0); 10 | assert.strictEqual(scan(["2", "10"]), 1); 11 | assert.strictEqual(scan(["10", "2", NaN]), 0); 12 | assert.strictEqual(scan([NaN, "10", "2"]), 1); 13 | assert.strictEqual(scan(["2", NaN, "10"]), 2); 14 | assert.strictEqual(scan([2, NaN, 10]), 0); 15 | assert.strictEqual(scan([10, 2, NaN]), 1); 16 | assert.strictEqual(scan([NaN, 10, 2]), 2); 17 | }); 18 | 19 | it("scan(array, compare) compares using the specified compare function", () => { 20 | var a = {name: "a"}, b = {name: "b"}; 21 | assert.strictEqual(scan([a, b], (a, b) => a.name.localeCompare(b.name)), 0); 22 | assert.strictEqual(scan([1, 0], descending), 0); 23 | assert.strictEqual(scan(["1", 0], descending), 0); 24 | assert.strictEqual(scan(["2", "10"], descending), 0); 25 | assert.strictEqual(scan(["2", NaN, "10"], descending), 0); 26 | assert.strictEqual(scan([2, NaN, 10], descending), 2); 27 | }); 28 | 29 | it("scan(array) returns undefined if the array is empty", () => { 30 | assert.strictEqual(scan([]), undefined); 31 | }); 32 | 33 | it("scan(array) returns undefined if the array contains only incomparable values", () => { 34 | assert.strictEqual(scan([NaN, undefined]), undefined); 35 | assert.strictEqual(scan([NaN, "foo"], (a, b) => a - b), undefined); 36 | }); 37 | 38 | it("scan(array) returns the first of equal values", () => { 39 | assert.strictEqual(scan([2, 2, 1, 1, 0, 0, 0, 3, 0]), 4); 40 | assert.strictEqual(scan([3, 2, 2, 1, 1, 0, 0, 0, 3, 0], descending), 0); 41 | }); 42 | -------------------------------------------------------------------------------- /test/shuffle-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {randomLcg} from "d3-random"; 3 | import {pairs, shuffler} from "../src/index.js"; 4 | 5 | it("shuffle(array) shuffles the array in-place", () => { 6 | const numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; 7 | const shuffle = shuffler(randomLcg(0.9051667019185816)); 8 | assert.strictEqual(shuffle(numbers), numbers); 9 | assert(pairs(numbers).some(([a, b]) => a > b)); // shuffled 10 | }); 11 | 12 | it("shuffler(random)(array) shuffles the array in-place", () => { 13 | const numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; 14 | const shuffle = shuffler(randomLcg(0.9051667019185816)); 15 | assert.strictEqual(shuffle(numbers), numbers); 16 | assert.deepStrictEqual(numbers, [7, 4, 5, 3, 9, 0, 6, 1, 2, 8]); 17 | }); 18 | 19 | it("shuffler(random)(array, start) shuffles the subset array[start:] in-place", () => { 20 | const numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; 21 | const shuffle = shuffler(randomLcg(0.9051667019185816)); 22 | assert.strictEqual(shuffle(numbers, 4), numbers); 23 | assert.deepStrictEqual(numbers, [0, 1, 2, 3, 8, 7, 6, 4, 5, 9]); 24 | }); 25 | 26 | it("shuffler(random)(array, start, end) shuffles the subset array[start:end] in-place", () => { 27 | const numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; 28 | const shuffle = shuffler(randomLcg(0.9051667019185816)); 29 | assert.strictEqual(shuffle(numbers, 3, 8), numbers); 30 | assert.deepStrictEqual(numbers, [0, 1, 2, 5, 6, 3, 4, 7, 8, 9]); 31 | }); 32 | -------------------------------------------------------------------------------- /test/some-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {some} from "../src/index.js"; 3 | 4 | it("some(values, test) returns true if any test passes", () => { 5 | assert.strictEqual(some([1, 2, 3, 2, 1], x => x & 1), true); 6 | assert.strictEqual(some([1, 2, 3, 2, 1], x => x > 3), false); 7 | }); 8 | 9 | it("some(values, test) returns false if values is empty", () => { 10 | assert.strictEqual(some([], () => true), false); 11 | }); 12 | 13 | it("some(values, test) accepts an iterable", () => { 14 | assert.strictEqual(some(new Set([1, 2, 3, 2, 1]), x => x >= 3), true); 15 | assert.strictEqual(some((function*() { yield* [1, 2, 3, 2, 1]; })(), x => x >= 3), true); 16 | assert.strictEqual(some(Uint8Array.of(1, 2, 3, 2, 1), x => x >= 3), true); 17 | }); 18 | 19 | it("some(values, test) enforces that test is a function", () => { 20 | assert.throws(() => some([]), TypeError); 21 | }); 22 | 23 | it("some(values, test) enforces that values is iterable", () => { 24 | assert.throws(() => some({}, () => true), TypeError); 25 | }); 26 | 27 | it("some(values, test) passes test (value, index, values)", () => { 28 | const calls = []; 29 | const values = new Set([5, 4, 3, 2, 1]); 30 | some(values, function() { calls.push([this, ...arguments]); }); 31 | assert.deepStrictEqual(calls, [ 32 | [undefined, 5, 0, values], 33 | [undefined, 4, 1, values], 34 | [undefined, 3, 2, values], 35 | [undefined, 2, 3, values], 36 | [undefined, 1, 4, values] 37 | ]); 38 | }); 39 | 40 | it("some(values, test) short-circuts when test returns truthy", () => { 41 | let calls = 0; 42 | assert.strictEqual(some([1, 2, 3], x => (++calls, x >= 2)), true); 43 | assert.strictEqual(calls, 2); 44 | assert.strictEqual(some([1, 2, 3], x => (++calls, x - 1)), true); 45 | assert.strictEqual(calls, 4); 46 | }); 47 | 48 | it("some(values, test) does not skip sparse elements", () => { 49 | assert.deepStrictEqual(some([, 1, 2,, ], x => x === undefined), true); 50 | }); 51 | -------------------------------------------------------------------------------- /test/sort-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {ascending, descending, sort} from "../src/index.js"; 3 | 4 | it("sort(values) returns a sorted copy", () => { 5 | const input = [1, 3, 2, 5, 4]; 6 | assert.deepStrictEqual(sort(input), [1, 2, 3, 4, 5]); 7 | assert.deepStrictEqual(input, [1, 3, 2, 5, 4]); // does not mutate 8 | }); 9 | 10 | it("sort(values) defaults to ascending, not lexicographic", () => { 11 | const input = [1, "10", 2]; 12 | assert.deepStrictEqual(sort(input), [1, 2, "10"]); 13 | }); 14 | 15 | // Per ECMAScript specification §23.1.3.27.1, undefined values are not passed to 16 | // the comparator; they are always put at the end of the sorted array. 17 | // https://262.ecma-international.org/12.0/#sec-sortcompare 18 | it("sort(values) puts non-orderable values last, followed by undefined", () => { 19 | const date = new Date(NaN); 20 | const input = [undefined, 1, null, 0, NaN, "10", date, 2]; 21 | assert.deepStrictEqual(sort(input), [0, 1, 2, "10", null, NaN, date, undefined]); 22 | }); 23 | 24 | it("sort(values, comparator) puts non-orderable values last, followed by undefined", () => { 25 | const date = new Date(NaN); 26 | const input = [undefined, 1, null, 0, NaN, "10", date, 2]; 27 | assert.deepStrictEqual(sort(input, ascending), [0, 1, 2, "10", null, NaN, date, undefined]); 28 | assert.deepStrictEqual(sort(input, descending), ["10", 2, 1, 0, null, NaN, date, undefined]); 29 | }); 30 | 31 | // However we don't implement this spec when using an accessor 32 | it("sort(values, accessor) puts non-orderable values last", () => { 33 | const date = new Date(NaN); 34 | const input = [undefined, 1, null, 0, NaN, "10", date, 2]; 35 | assert.deepStrictEqual(sort(input, d => d), [0, 1, 2, "10", undefined, null, NaN, date]); 36 | assert.deepStrictEqual(sort(input, d => d && -d), ["10", 2, 1, 0, undefined, null, NaN, date]); 37 | }); 38 | 39 | it("sort(values, accessor) uses the specified accessor in natural order", () => { 40 | assert.deepStrictEqual(sort([1, 3, 2, 5, 4], d => d), [1, 2, 3, 4, 5]); 41 | assert.deepStrictEqual(sort([1, 3, 2, 5, 4], d => -d), [5, 4, 3, 2, 1]); 42 | }); 43 | 44 | it("sort(values, ...accessors) accepts multiple accessors", () => { 45 | assert.deepStrictEqual(sort([[1, 0], [2, 1], [2, 0], [1, 1], [3, 0]], ([x]) => x, ([, y]) => y), [[1, 0], [1, 1], [2, 0], [2, 1], [3, 0]]); 46 | assert.deepStrictEqual(sort([{x: 1, y: 0}, {x: 2, y: 1}, {x: 2, y: 0}, {x: 1, y: 1}, {x: 3, y: 0}], ({x}) => x, ({y}) => y), [{x: 1, y: 0}, {x: 1, y: 1}, {x: 2, y: 0}, {x: 2, y: 1}, {x: 3, y: 0}]); 47 | }); 48 | 49 | it("sort(values, comparator) uses the specified comparator", () => { 50 | assert.deepStrictEqual(sort([1, 3, 2, 5, 4], descending), [5, 4, 3, 2, 1]); 51 | }); 52 | 53 | it("sort(values) returns an array", () => { 54 | assert.strictEqual(Array.isArray(sort(Uint8Array.of(1, 2))), true); 55 | }); 56 | 57 | it("sort(values) accepts an iterable", () => { 58 | assert.deepStrictEqual(sort(new Set([1, 3, 2, 1, 2])), [1, 2, 3]); 59 | assert.deepStrictEqual(sort((function*() { yield* [1, 3, 2, 5, 4]; })()), [1, 2, 3, 4, 5]); 60 | assert.deepStrictEqual(sort(Uint8Array.of(1, 3, 2, 5, 4)), [1, 2, 3, 4, 5]); 61 | }); 62 | 63 | it("sort(values) enforces that values is iterable", () => { 64 | assert.throws(() => sort({}), {name: "TypeError", message: /is not iterable/}); 65 | }); 66 | 67 | it("sort(values, comparator) enforces that comparator is a function", () => { 68 | assert.throws(() => sort([], {}), {name: "TypeError", message: /is not a function/}); 69 | assert.throws(() => sort([], null), {name: "TypeError", message: /is not a function/}); 70 | }); 71 | 72 | it("sort(values) does not skip sparse elements", () => { 73 | assert.deepStrictEqual(sort([, 1, 2,, ]), [1, 2, undefined, undefined]); 74 | }); 75 | -------------------------------------------------------------------------------- /test/subset-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {subset} from "../src/index.js"; 3 | 4 | it("subset(values, other) returns true if values is a subset of others", () => { 5 | assert.strictEqual(subset([2], [1, 2]), true); 6 | assert.strictEqual(subset([3, 4], [2, 3]), false); 7 | assert.strictEqual(subset([], [1]), true); 8 | }); 9 | 10 | it("subset(values, other) performs interning", () => { 11 | assert.strictEqual(subset([new Date("2021-01-02")], [new Date("2021-01-01"), new Date("2021-01-02")]), true); 12 | assert.strictEqual(subset([new Date("2021-01-03"), new Date("2021-01-04")], [new Date("2021-01-02"), new Date("2021-01-03")]), false); 13 | assert.strictEqual(subset([], [new Date("2021-01-01")]), true); 14 | }); 15 | -------------------------------------------------------------------------------- /test/sum-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {sum} from "../src/index.js"; 3 | 4 | it("sum(array) returns the sum of the specified numbers", () => { 5 | assert.strictEqual(sum([1]), 1); 6 | assert.strictEqual(sum([5, 1, 2, 3, 4]), 15); 7 | assert.strictEqual(sum([20, 3]), 23); 8 | assert.strictEqual(sum([3, 20]), 23); 9 | }); 10 | 11 | it("sum(array) observes values that can be coerced to numbers", () => { 12 | assert.strictEqual(sum(["20", "3"]), 23); 13 | assert.strictEqual(sum(["3", "20"]), 23); 14 | assert.strictEqual(sum(["3", 20]), 23); 15 | assert.strictEqual(sum([20, "3"]), 23); 16 | assert.strictEqual(sum([3, "20"]), 23); 17 | assert.strictEqual(sum(["20", 3]), 23); 18 | }); 19 | 20 | it("sum(array) ignores non-numeric values", () => { 21 | assert.strictEqual(sum(["a", "b", "c"]), 0); 22 | assert.strictEqual(sum(["a", 1, "2"]), 3); 23 | }); 24 | 25 | it("sum(array) ignores null, undefined and NaN", () => { 26 | assert.strictEqual(sum([NaN, 1, 2, 3, 4, 5]), 15); 27 | assert.strictEqual(sum([1, 2, 3, 4, 5, NaN]), 15); 28 | assert.strictEqual(sum([10, null, 3, undefined, 5, NaN]), 18); 29 | }); 30 | 31 | it("sum(array) returns zero if there are no numbers", () => { 32 | assert.strictEqual(sum([]), 0); 33 | assert.strictEqual(sum([NaN]), 0); 34 | assert.strictEqual(sum([undefined]), 0); 35 | assert.strictEqual(sum([undefined, NaN]), 0); 36 | assert.strictEqual(sum([undefined, NaN, {}]), 0); 37 | }); 38 | 39 | it("sum(array, f) returns the sum of the specified numbers", () => { 40 | assert.strictEqual(sum([1].map(box), unbox), 1); 41 | assert.strictEqual(sum([5, 1, 2, 3, 4].map(box), unbox), 15); 42 | assert.strictEqual(sum([20, 3].map(box), unbox), 23); 43 | assert.strictEqual(sum([3, 20].map(box), unbox), 23); 44 | }); 45 | 46 | it("sum(array, f) observes values that can be coerced to numbers", () => { 47 | assert.strictEqual(sum(["20", "3"].map(box), unbox), 23); 48 | assert.strictEqual(sum(["3", "20"].map(box), unbox), 23); 49 | assert.strictEqual(sum(["3", 20].map(box), unbox), 23); 50 | assert.strictEqual(sum([20, "3"].map(box), unbox), 23); 51 | assert.strictEqual(sum([3, "20"].map(box), unbox), 23); 52 | assert.strictEqual(sum(["20", 3].map(box), unbox), 23); 53 | }); 54 | 55 | it("sum(array, f) ignores non-numeric values", () => { 56 | assert.strictEqual(sum(["a", "b", "c"].map(box), unbox), 0); 57 | assert.strictEqual(sum(["a", 1, "2"].map(box), unbox), 3); 58 | }); 59 | 60 | it("sum(array, f) ignores null, undefined and NaN", () => { 61 | assert.strictEqual(sum([NaN, 1, 2, 3, 4, 5].map(box), unbox), 15); 62 | assert.strictEqual(sum([1, 2, 3, 4, 5, NaN].map(box), unbox), 15); 63 | assert.strictEqual(sum([10, null, 3, undefined, 5, NaN].map(box), unbox), 18); 64 | }); 65 | 66 | it("sum(array, f) returns zero if there are no numbers", () => { 67 | assert.strictEqual(sum([].map(box), unbox), 0); 68 | assert.strictEqual(sum([NaN].map(box), unbox), 0); 69 | assert.strictEqual(sum([undefined].map(box), unbox), 0); 70 | assert.strictEqual(sum([undefined, NaN].map(box), unbox), 0); 71 | assert.strictEqual(sum([undefined, NaN, {}].map(box), unbox), 0); 72 | }); 73 | 74 | it("sum(array, f) passes the accessor d, i, and array", () => { 75 | const results = []; 76 | const array = ["a", "b", "c"]; 77 | sum(array, (d, i, array) => results.push([d, i, array])); 78 | assert.deepStrictEqual(results, [["a", 0, array], ["b", 1, array], ["c", 2, array]]); 79 | }); 80 | 81 | it("sum(array, f) uses the undefined context", () => { 82 | const results = []; 83 | sum([1, 2], function() { results.push(this); }); 84 | assert.deepStrictEqual(results, [undefined, undefined]); 85 | }); 86 | 87 | function box(value) { 88 | return {value: value}; 89 | } 90 | 91 | function unbox(box) { 92 | return box.value; 93 | } 94 | -------------------------------------------------------------------------------- /test/superset-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {superset} from "../src/index.js"; 3 | 4 | it("superset(values, other) returns true if values is a superset of others", () => { 5 | assert.strictEqual(superset([1, 2], [2]), true); 6 | assert.strictEqual(superset([2, 3], [3, 4]), false); 7 | assert.strictEqual(superset([1], []), true); 8 | }); 9 | 10 | it("superset(values, other) allows values to be infinite", () => { 11 | assert.strictEqual(superset(odds(), [1, 3, 5]), true); 12 | }); 13 | 14 | it("superset(values, other) allows other to be infinite", () => { 15 | assert.strictEqual(superset([1, 3, 5], repeat(1, 3, 2)), false); 16 | }); 17 | 18 | it("superset(values, other) performs interning", () => { 19 | assert.strictEqual(superset([new Date("2021-01-01"), new Date("2021-01-02")], [new Date("2021-01-02")]), true); 20 | assert.strictEqual(superset([new Date("2021-01-02"), new Date("2021-01-03")], [new Date("2021-01-03"), new Date("2021-01-04")]), false); 21 | assert.strictEqual(superset([new Date("2021-01-01")], []), true); 22 | }); 23 | 24 | function* odds() { 25 | for (let i = 1; true; i += 2) { 26 | yield i; 27 | } 28 | } 29 | 30 | function* repeat(...values) { 31 | while (true) { 32 | yield* values; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /test/threshold/freedmanDiaconic-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {thresholdFreedmanDiaconis} from "../../src/index.js"; 3 | 4 | it("thresholdFreedmanDiaconis(values, min, max) returns the expected result", () => { 5 | assert.strictEqual(thresholdFreedmanDiaconis([4, 3, 2, 1, NaN], 1, 4), 2); 6 | }); 7 | 8 | it("thresholdFreedmanDiaconis(values, min, max) handles values with zero deviation", () => { 9 | assert.strictEqual(thresholdFreedmanDiaconis([1, 1, 1, 1], 1, 4), 1); 10 | }); 11 | 12 | it("thresholdFreedmanDiaconis(values, min, max) handles single-value arrays", () => { 13 | assert.strictEqual(thresholdFreedmanDiaconis([1], 1, 4), 1); 14 | }); 15 | 16 | it("thresholdFreedmanDiaconis(values, min, max) handles empty arrays", () => { 17 | assert.strictEqual(thresholdFreedmanDiaconis([], 1, 4), 1); 18 | }); 19 | -------------------------------------------------------------------------------- /test/threshold/scott-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {thresholdScott} from "../../src/index.js"; 3 | 4 | it("thresholdScott(values, min, max) returns the expected result", () => { 5 | assert.strictEqual(thresholdScott([4, 3, 2, 1, NaN], 1, 4), 2); 6 | }); 7 | 8 | it("thresholdScott(values, min, max) handles values with zero deviation", () => { 9 | assert.strictEqual(thresholdScott([1, 1, 1, 1], 1, 4), 1); 10 | }); 11 | 12 | it("thresholdScott(values, min, max) handles single-value arrays", () => { 13 | assert.strictEqual(thresholdScott([1], 1, 4), 1); 14 | }); 15 | 16 | it("thresholdScott(values, min, max) handles empty arrays", () => { 17 | assert.strictEqual(thresholdScott([], 1, 4), 1); 18 | }); 19 | -------------------------------------------------------------------------------- /test/threshold/sturges-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {thresholdSturges} from "../../src/index.js"; 3 | 4 | it("thresholdSturges(values, min, max) returns the expected result", () => { 5 | assert.strictEqual(thresholdSturges([4, 3, 2, 1, NaN], 1, 4), 3); 6 | assert.strictEqual(thresholdSturges([1], 1, 4), 1); 7 | }); 8 | 9 | it("thresholdSturges(values, min, max) handles empty arrays", () => { 10 | assert.strictEqual(thresholdSturges([], 1, 4), 1); 11 | }); 12 | -------------------------------------------------------------------------------- /test/tickIncrement-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {tickIncrement} from "../src/index.js"; 3 | 4 | it("tickIncrement(start, stop, count) returns NaN if any argument is NaN", () => { 5 | assert(isNaN(tickIncrement(NaN, 1, 1))); 6 | assert(isNaN(tickIncrement(0, NaN, 1))); 7 | assert(isNaN(tickIncrement(0, 1, NaN))); 8 | assert(isNaN(tickIncrement(NaN, NaN, 1))); 9 | assert(isNaN(tickIncrement(0, NaN, NaN))); 10 | assert(isNaN(tickIncrement(NaN, 1, NaN))); 11 | assert(isNaN(tickIncrement(NaN, NaN, NaN))); 12 | }); 13 | 14 | it("tickIncrement(start, stop, count) returns NaN or -Infinity if start === stop", () => { 15 | assert(isNaN(tickIncrement(1, 1, -1))); 16 | assert(isNaN(tickIncrement(1, 1, 0))); 17 | assert(isNaN(tickIncrement(1, 1, NaN))); 18 | assert.strictEqual(tickIncrement(1, 1, 1), -Infinity); 19 | assert.strictEqual(tickIncrement(1, 1, 10), -Infinity); 20 | }); 21 | 22 | it("tickIncrement(start, stop, count) returns 0 or Infinity if count is not positive", () => { 23 | assert.strictEqual(tickIncrement(0, 1, -1), Infinity); 24 | assert.strictEqual(tickIncrement(0, 1, 0), Infinity); 25 | }); 26 | 27 | it("tickIncrement(start, stop, count) returns -Infinity if count is infinity", () => { 28 | assert.strictEqual(tickIncrement(0, 1, Infinity), -Infinity); 29 | }); 30 | 31 | it("tickIncrement(start, stop, count) returns approximately count + 1 tickIncrement when start < stop", () => { 32 | assert.strictEqual(tickIncrement( 0, 1, 10), -10); 33 | assert.strictEqual(tickIncrement( 0, 1, 9), -10); 34 | assert.strictEqual(tickIncrement( 0, 1, 8), -10); 35 | assert.strictEqual(tickIncrement( 0, 1, 7), -5); 36 | assert.strictEqual(tickIncrement( 0, 1, 6), -5); 37 | assert.strictEqual(tickIncrement( 0, 1, 5), -5); 38 | assert.strictEqual(tickIncrement( 0, 1, 4), -5); 39 | assert.strictEqual(tickIncrement( 0, 1, 3), -2); 40 | assert.strictEqual(tickIncrement( 0, 1, 2), -2); 41 | assert.strictEqual(tickIncrement( 0, 1, 1), 1); 42 | assert.strictEqual(tickIncrement( 0, 10, 10), 1); 43 | assert.strictEqual(tickIncrement( 0, 10, 9), 1); 44 | assert.strictEqual(tickIncrement( 0, 10, 8), 1); 45 | assert.strictEqual(tickIncrement( 0, 10, 7), 2); 46 | assert.strictEqual(tickIncrement( 0, 10, 6), 2); 47 | assert.strictEqual(tickIncrement( 0, 10, 5), 2); 48 | assert.strictEqual(tickIncrement( 0, 10, 4), 2); 49 | assert.strictEqual(tickIncrement( 0, 10, 3), 5); 50 | assert.strictEqual(tickIncrement( 0, 10, 2), 5); 51 | assert.strictEqual(tickIncrement( 0, 10, 1), 10); 52 | assert.strictEqual(tickIncrement(-10, 10, 10), 2); 53 | assert.strictEqual(tickIncrement(-10, 10, 9), 2); 54 | assert.strictEqual(tickIncrement(-10, 10, 8), 2); 55 | assert.strictEqual(tickIncrement(-10, 10, 7), 2); 56 | assert.strictEqual(tickIncrement(-10, 10, 6), 5); 57 | assert.strictEqual(tickIncrement(-10, 10, 5), 5); 58 | assert.strictEqual(tickIncrement(-10, 10, 4), 5); 59 | assert.strictEqual(tickIncrement(-10, 10, 3), 5); 60 | assert.strictEqual(tickIncrement(-10, 10, 2), 10); 61 | assert.strictEqual(tickIncrement(-10, 10, 1), 20); 62 | }); 63 | -------------------------------------------------------------------------------- /test/tickStep-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {tickStep} from "../src/index.js"; 3 | 4 | it("tickStep(start, stop, count) returns NaN if any argument is NaN", () => { 5 | assert(isNaN(tickStep(NaN, 1, 1))); 6 | assert(isNaN(tickStep(0, NaN, 1))); 7 | assert(isNaN(tickStep(0, 1, NaN))); 8 | assert(isNaN(tickStep(NaN, NaN, 1))); 9 | assert(isNaN(tickStep(0, NaN, NaN))); 10 | assert(isNaN(tickStep(NaN, 1, NaN))); 11 | assert(isNaN(tickStep(NaN, NaN, NaN))); 12 | }); 13 | 14 | it("tickStep(start, stop, count) returns NaN or 0 if start === stop", () => { 15 | assert(isNaN(tickStep(1, 1, -1))); 16 | assert(isNaN(tickStep(1, 1, 0))); 17 | assert(isNaN(tickStep(1, 1, NaN))); 18 | assert.strictEqual(tickStep(1, 1, 1), 0); 19 | assert.strictEqual(tickStep(1, 1, 10), 0); 20 | }); 21 | 22 | it("tickStep(start, stop, count) returns 0 or Infinity if count is not positive", () => { 23 | assert.strictEqual(tickStep(0, 1, -1), Infinity); 24 | assert.strictEqual(tickStep(0, 1, 0), Infinity); 25 | }); 26 | 27 | it("tickStep(start, stop, count) returns 0 if count is infinity", () => { 28 | assert.strictEqual(tickStep(0, 1, Infinity), 0); 29 | }); 30 | 31 | it("tickStep(start, stop, count) returns approximately count + 1 tickStep when start < stop", () => { 32 | assert.strictEqual(tickStep( 0, 1, 10), 0.1); 33 | assert.strictEqual(tickStep( 0, 1, 9), 0.1); 34 | assert.strictEqual(tickStep( 0, 1, 8), 0.1); 35 | assert.strictEqual(tickStep( 0, 1, 7), 0.2); 36 | assert.strictEqual(tickStep( 0, 1, 6), 0.2); 37 | assert.strictEqual(tickStep( 0, 1, 5), 0.2); 38 | assert.strictEqual(tickStep( 0, 1, 4), 0.2); 39 | assert.strictEqual(tickStep( 0, 1, 3), 0.5); 40 | assert.strictEqual(tickStep( 0, 1, 2), 0.5); 41 | assert.strictEqual(tickStep( 0, 1, 1), 1.0); 42 | assert.strictEqual(tickStep( 0, 10, 10), 1); 43 | assert.strictEqual(tickStep( 0, 10, 9), 1); 44 | assert.strictEqual(tickStep( 0, 10, 8), 1); 45 | assert.strictEqual(tickStep( 0, 10, 7), 2); 46 | assert.strictEqual(tickStep( 0, 10, 6), 2); 47 | assert.strictEqual(tickStep( 0, 10, 5), 2); 48 | assert.strictEqual(tickStep( 0, 10, 4), 2); 49 | assert.strictEqual(tickStep( 0, 10, 3), 5); 50 | assert.strictEqual(tickStep( 0, 10, 2), 5); 51 | assert.strictEqual(tickStep( 0, 10, 1), 10); 52 | assert.strictEqual(tickStep(-10, 10, 10), 2); 53 | assert.strictEqual(tickStep(-10, 10, 9), 2); 54 | assert.strictEqual(tickStep(-10, 10, 8), 2); 55 | assert.strictEqual(tickStep(-10, 10, 7), 2); 56 | assert.strictEqual(tickStep(-10, 10, 6), 5); 57 | assert.strictEqual(tickStep(-10, 10, 5), 5); 58 | assert.strictEqual(tickStep(-10, 10, 4), 5); 59 | assert.strictEqual(tickStep(-10, 10, 3), 5); 60 | assert.strictEqual(tickStep(-10, 10, 2), 10); 61 | assert.strictEqual(tickStep(-10, 10, 1), 20); 62 | }); 63 | 64 | it("tickStep(start, stop, count) returns -tickStep(stop, start, count)", () => { 65 | assert.strictEqual(tickStep( 0, 1, 10), -tickStep( 1, 0, 10)); 66 | assert.strictEqual(tickStep( 0, 1, 9), -tickStep( 1, 0, 9)); 67 | assert.strictEqual(tickStep( 0, 1, 8), -tickStep( 1, 0, 8)); 68 | assert.strictEqual(tickStep( 0, 1, 7), -tickStep( 1, 0, 7)); 69 | assert.strictEqual(tickStep( 0, 1, 6), -tickStep( 1, 0, 6)); 70 | assert.strictEqual(tickStep( 0, 1, 5), -tickStep( 1, 0, 5)); 71 | assert.strictEqual(tickStep( 0, 1, 4), -tickStep( 1, 0, 4)); 72 | assert.strictEqual(tickStep( 0, 1, 3), -tickStep( 1, 0, 3)); 73 | assert.strictEqual(tickStep( 0, 1, 2), -tickStep( 1, 0, 2)); 74 | assert.strictEqual(tickStep( 0, 1, 1), -tickStep( 1, 0, 1)); 75 | assert.strictEqual(tickStep( 0, 10, 10), -tickStep(10, 0, 10)); 76 | assert.strictEqual(tickStep( 0, 10, 9), -tickStep(10, 0, 9)); 77 | assert.strictEqual(tickStep( 0, 10, 8), -tickStep(10, 0, 8)); 78 | assert.strictEqual(tickStep( 0, 10, 7), -tickStep(10, 0, 7)); 79 | assert.strictEqual(tickStep( 0, 10, 6), -tickStep(10, 0, 6)); 80 | assert.strictEqual(tickStep( 0, 10, 5), -tickStep(10, 0, 5)); 81 | assert.strictEqual(tickStep( 0, 10, 4), -tickStep(10, 0, 4)); 82 | assert.strictEqual(tickStep( 0, 10, 3), -tickStep(10, 0, 3)); 83 | assert.strictEqual(tickStep( 0, 10, 2), -tickStep(10, 0, 2)); 84 | assert.strictEqual(tickStep( 0, 10, 1), -tickStep(10, 0, 1)); 85 | assert.strictEqual(tickStep(-10, 10, 10), -tickStep(10, -10, 10)); 86 | assert.strictEqual(tickStep(-10, 10, 9), -tickStep(10, -10, 9)); 87 | assert.strictEqual(tickStep(-10, 10, 8), -tickStep(10, -10, 8)); 88 | assert.strictEqual(tickStep(-10, 10, 7), -tickStep(10, -10, 7)); 89 | assert.strictEqual(tickStep(-10, 10, 6), -tickStep(10, -10, 6)); 90 | assert.strictEqual(tickStep(-10, 10, 5), -tickStep(10, -10, 5)); 91 | assert.strictEqual(tickStep(-10, 10, 4), -tickStep(10, -10, 4)); 92 | assert.strictEqual(tickStep(-10, 10, 3), -tickStep(10, -10, 3)); 93 | assert.strictEqual(tickStep(-10, 10, 2), -tickStep(10, -10, 2)); 94 | assert.strictEqual(tickStep(-10, 10, 1), -tickStep(10, -10, 1)); 95 | }); 96 | -------------------------------------------------------------------------------- /test/transpose-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {transpose} from "../src/index.js"; 3 | 4 | it("transpose([]) and transpose([[]]) return an empty array", () => { 5 | assert.deepStrictEqual(transpose([]), []); 6 | assert.deepStrictEqual(transpose([[]]), []); 7 | }); 8 | 9 | it("transpose([[a, b, …]]) returns [[a], [b], …]", () => { 10 | assert.deepStrictEqual(transpose([[1, 2, 3, 4, 5]]), [[1], [2], [3], [4], [5]]); 11 | }); 12 | 13 | it("transpose([[a1, b1, …], [a2, b2, …]]) returns [[a1, a2], [b1, b2], …]", () => { 14 | assert.deepStrictEqual(transpose([[1, 2], [3, 4]]), [[1, 3], [2, 4]]); 15 | assert.deepStrictEqual(transpose([[1, 2, 3, 4, 5], [2, 4, 6, 8, 10]]), [[1, 2], [2, 4], [3, 6], [4, 8], [5, 10]]); 16 | }); 17 | 18 | it("transpose([[a1, b1, …], [a2, b2, …], [a3, b3, …]]) returns [[a1, a2, a3], [b1, b2, b3], …]", () => { 19 | assert.deepStrictEqual(transpose([[1, 2, 3], [4, 5, 6], [7, 8, 9]]), [[1, 4, 7], [2, 5, 8], [3, 6, 9]]); 20 | }); 21 | 22 | it("transpose(…) ignores extra elements given an irregular matrix", () => { 23 | assert.deepStrictEqual(transpose([[1, 2], [3, 4], [5, 6, 7]]), [[1, 3, 5], [2, 4, 6]]); 24 | }); 25 | 26 | it("transpose(…) returns a copy", () => { 27 | const matrix = [[1, 2], [3, 4]]; 28 | const t = transpose(matrix); 29 | matrix[0][0] = matrix[0][1] = matrix[1][0] = matrix[1][1] = 0; 30 | assert.deepStrictEqual(t, [[1, 3], [2, 4]]); 31 | }); 32 | -------------------------------------------------------------------------------- /test/union-test.js: -------------------------------------------------------------------------------- 1 | import {union} from "../src/index.js"; 2 | import {assertSetEqual} from "./asserts.js"; 3 | 4 | it("union(values) returns a set of values", () => { 5 | assertSetEqual(union([1, 2, 3, 2, 1]), [1, 2, 3]); 6 | }); 7 | 8 | it("union(values, other) returns a set of values", () => { 9 | assertSetEqual(union([1, 2], [2, 3, 1]), [1, 2, 3]); 10 | }); 11 | 12 | it("union(...values) returns a set of values", () => { 13 | assertSetEqual(union([1], [2], [2, 3], [1]), [1, 2, 3]); 14 | }); 15 | 16 | it("union(...values) accepts iterables", () => { 17 | assertSetEqual(union(new Set([1, 2, 3])), [1, 2, 3]); 18 | assertSetEqual(union(Uint8Array.of(1, 2, 3)), [1, 2, 3]); 19 | }); 20 | 21 | it("union(...values) performs interning", () => { 22 | assertSetEqual(union([new Date("2021-01-01"), new Date("2021-01-01"), new Date("2021-01-02")]), [new Date("2021-01-01"), new Date("2021-01-02")]); 23 | assertSetEqual(union([new Date("2021-01-01"), new Date("2021-01-03")], [new Date("2021-01-01"), new Date("2021-01-02")]), [new Date("2021-01-01"), new Date("2021-01-02"), new Date("2021-01-03")]); 24 | }); 25 | -------------------------------------------------------------------------------- /test/variance-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {variance} from "../src/index.js"; 3 | 4 | it("variance(array) returns the variance of the specified numbers", () => { 5 | assert.strictEqual(variance([5, 1, 2, 3, 4]), 2.5); 6 | assert.strictEqual(variance([20, 3]), 144.5); 7 | assert.strictEqual(variance([3, 20]), 144.5); 8 | }); 9 | 10 | it("variance(array) ignores null, undefined and NaN", () => { 11 | assert.strictEqual(variance([NaN, 1, 2, 3, 4, 5]), 2.5); 12 | assert.strictEqual(variance([1, 2, 3, 4, 5, NaN]), 2.5); 13 | assert.strictEqual(variance([10, null, 3, undefined, 5, NaN]), 13); 14 | }); 15 | 16 | it("variance(array) can handle large numbers without overflowing", () => { 17 | assert.strictEqual(variance([Number.MAX_VALUE, Number.MAX_VALUE]), 0); 18 | assert.strictEqual(variance([-Number.MAX_VALUE, -Number.MAX_VALUE]), 0); 19 | }); 20 | 21 | it("variance(array) returns undefined if the array has fewer than two numbers", () => { 22 | assert.strictEqual(variance([1]), undefined); 23 | assert.strictEqual(variance([]), undefined); 24 | assert.strictEqual(variance([null]), undefined); 25 | assert.strictEqual(variance([undefined]), undefined); 26 | assert.strictEqual(variance([NaN]), undefined); 27 | assert.strictEqual(variance([NaN, NaN]), undefined); 28 | }); 29 | 30 | it("variance(array, f) returns the variance of the specified numbers", () => { 31 | assert.strictEqual(variance([5, 1, 2, 3, 4].map(box), unbox), 2.5); 32 | assert.strictEqual(variance([20, 3].map(box), unbox), 144.5); 33 | assert.strictEqual(variance([3, 20].map(box), unbox), 144.5); 34 | }); 35 | 36 | it("variance(array, f) ignores null, undefined and NaN", () => { 37 | assert.strictEqual(variance([NaN, 1, 2, 3, 4, 5].map(box), unbox), 2.5); 38 | assert.strictEqual(variance([1, 2, 3, 4, 5, NaN].map(box), unbox), 2.5); 39 | assert.strictEqual(variance([10, null, 3, undefined, 5, NaN].map(box), unbox), 13); 40 | }); 41 | 42 | it("variance(array, f) can handle large numbers without overflowing", () => { 43 | assert.strictEqual(variance([Number.MAX_VALUE, Number.MAX_VALUE].map(box), unbox), 0); 44 | assert.strictEqual(variance([-Number.MAX_VALUE, -Number.MAX_VALUE].map(box), unbox), 0); 45 | }); 46 | 47 | it("variance(array, f) returns undefined if the array has fewer than two numbers", () => { 48 | assert.strictEqual(variance([1].map(box), unbox), undefined); 49 | assert.strictEqual(variance([].map(box), unbox), undefined); 50 | assert.strictEqual(variance([null].map(box), unbox), undefined); 51 | assert.strictEqual(variance([undefined].map(box), unbox), undefined); 52 | assert.strictEqual(variance([NaN].map(box), unbox), undefined); 53 | assert.strictEqual(variance([NaN, NaN].map(box), unbox), undefined); 54 | }); 55 | 56 | it("variance(array, f) passes the accessor d, i, and array", () => { 57 | const results = []; 58 | const array = ["a", "b", "c"]; 59 | variance(array, (d, i, array) => results.push([d, i, array])); 60 | assert.deepStrictEqual(results, [["a", 0, array], ["b", 1, array], ["c", 2, array]]); 61 | }); 62 | 63 | it("variance(array, f) uses the undefined context", () => { 64 | const results = []; 65 | variance([1, 2], function() { results.push(this); }); 66 | assert.deepStrictEqual(results, [undefined, undefined]); 67 | }); 68 | 69 | function box(value) { 70 | return {value: value}; 71 | } 72 | 73 | function unbox(box) { 74 | return box.value; 75 | } 76 | -------------------------------------------------------------------------------- /test/zip-test.js: -------------------------------------------------------------------------------- 1 | import assert from "assert"; 2 | import {zip} from "../src/index.js"; 3 | 4 | it("zip() and zip([]) return an empty array", () => { 5 | assert.deepStrictEqual(zip(), []); 6 | assert.deepStrictEqual(zip([]), []); 7 | }); 8 | 9 | it("zip([a, b, …]) returns [[a], [b], …]", () => { 10 | assert.deepStrictEqual(zip([1, 2, 3, 4, 5]), [[1], [2], [3], [4], [5]]); 11 | }); 12 | 13 | it("zip([a1, b1, …], [a2, b2, …]) returns [[a1, a2], [b1, b2], …]", () => { 14 | assert.deepStrictEqual(zip([1, 2], [3, 4]), [[1, 3], [2, 4]]); 15 | assert.deepStrictEqual(zip([1, 2, 3, 4, 5], [2, 4, 6, 8, 10]), [[1, 2], [2, 4], [3, 6], [4, 8], [5, 10]]); 16 | }); 17 | 18 | it("zip([a1, b1, …], [a2, b2, …], [a3, b3, …]) returns [[a1, a2, a3], [b1, b2, b3], …]", () => { 19 | assert.deepStrictEqual(zip([1, 2, 3], [4, 5, 6], [7, 8, 9]), [[1, 4, 7], [2, 5, 8], [3, 6, 9]]); 20 | }); 21 | 22 | it("zip(…) ignores extra elements given an irregular matrix", () => { 23 | assert.deepStrictEqual(zip([1, 2], [3, 4], [5, 6, 7]), [[1, 3, 5], [2, 4, 6]]); 24 | }); 25 | --------------------------------------------------------------------------------