diff --git a/README.md b/README.md index bc717f3d..f8d22398 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,13 @@ Returns the minimum value in the given *iterable* using natural order. If the it Unlike the built-in [Math.min](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Math/min), this method ignores undefined, null and NaN values; this is useful for ignoring missing data. In addition, elements are compared using natural order rather than numeric order. For example, the minimum of the strings [“20”, “3”] is “20”, while the minimum of the numbers [20, 3] is 3. -See also [scan](#scan) and [extent](#extent). +See also [extent](#extent). + +# d3.minIndex(iterable[, accessor]) · [Source](https://github.com/d3/d3-array/blob/master/src/minIndex.js) + +Returns the index of the minimum value in the given *iterable* using natural order. If the iterable contains no comparable values, returns -1. An optional *accessor* function may be specified, which is equivalent to calling Array.from before computing the minimum value. + +Unlike the built-in [Math.min](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Math/min), this method ignores undefined, null and NaN values; this is useful for ignoring missing data. In addition, elements are compared using natural order rather than numeric order. For example, the minimum of the strings [“20”, “3”] is “20”, while the minimum of the numbers [20, 3] is 3. # d3.max(iterable[, accessor]) · [Source](https://github.com/d3/d3-array/blob/master/src/max.js) @@ -68,7 +74,13 @@ Returns the maximum value in the given *iterable* using natural order. If the it Unlike the built-in [Math.max](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Math/max), this method ignores undefined values; this is useful for ignoring missing data. In addition, elements are compared using natural order rather than numeric order. For example, the maximum of the strings [“20”, “3”] is “3”, while the maximum of the numbers [20, 3] is 20. -See also [scan](#scan) and [extent](#extent). +See also [extent](#extent). + +# d3.maxIndex(iterable[, accessor]) · [Source](https://github.com/d3/d3-array/blob/master/src/maxIndex.js) + +Returns the index of the maximum value in the given *iterable* using natural order. If the iterable contains no comparable values, returns -1. An optional *accessor* function may be specified, which is equivalent to calling Array.from before computing the maximum value. + +Unlike the built-in [Math.max](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Math/max), this method ignores undefined values; this is useful for ignoring missing data. In addition, elements are compared using natural order rather than numeric order. For example, the maximum of the strings [“20”, “3”] is “3”, while the maximum of the numbers [20, 3] is 20. # d3.extent(iterable[, accessor]) · [Source](https://github.com/d3/d3-array/blob/master/src/extent.js) @@ -114,17 +126,33 @@ Returns the standard deviation, defined as the square root of the [bias-correcte Methods for searching arrays for a specific element. -# d3.scan(iterable[, comparator]) · [Source](https://github.com/d3/d3-array/blob/master/src/scan.js) +# d3.least(iterable[, comparator]) · [Source](https://github.com/d3/d3-array/blob/master/src/least.js) -Performs a linear scan of the specified *iterable*, returning the index of the least element according to the specified *comparator*. If the given *iterable* contains no comparable elements (*i.e.*, the comparator returns NaN when comparing each element to itself), returns undefined. If *comparator* is not specified, it defaults to [ascending](#ascending). For example: +Returns the least element of the specified *iterable* according to the specified *comparator*. If the given *iterable* contains no comparable elements (*i.e.*, the comparator returns NaN when comparing each element to itself), returns undefined. If *comparator* is not specified, it defaults to [ascending](#ascending). For example: ```js const array = [{foo: 42}, {foo: 91}]; -d3.scan(array, (a, b) => a.foo - b.foo); // 0 -d3.scan(array, (a, b) => b.foo - a.foo); // 1 +d3.least(array, (a, b) => a.foo - b.foo); // {foo: 42} +d3.least(array, (a, b) => b.foo - a.foo); // {foo: 91} ``` -This function is similar to [min](#min), except it allows the use of a comparator rather than an accessor and it returns the index instead of the accessed value. See also [bisect](#bisect). +This function is similar to [min](#min), except it allows the use of a comparator rather than an accessor. + +# d3.leastIndex(iterable[, comparator]) · [Source](https://github.com/d3/d3-array/blob/master/src/leastIndex.js) + +Returns the index of the least element of the specified *iterable* according to the specified *comparator*. If the given *iterable* contains no comparable elements (*i.e.*, the comparator returns NaN when comparing each element to itself), returns -1. If *comparator* is not specified, it defaults to [ascending](#ascending). For example: + +```js +const array = [{foo: 42}, {foo: 91}]; +d3.leastIndex(array, (a, b) => a.foo - b.foo); // 0 +d3.leastIndex(array, (a, b) => b.foo - a.foo); // 1 +``` + +This function is similar to [minIndex](#minIndex), except it allows the use of a comparator rather than an accessor. + +# d3.scan(iterable[, comparator]) · [Source](https://github.com/d3/d3-array/blob/master/src/scan.js) + +Deprecated; use [leastIndex](#leastIndex) instead. # d3.bisectLeft(array, x[, lo[, hi]]) · [Source](https://github.com/d3/d3-array/blob/master/src/bisect.js) diff --git a/src/extent.js b/src/extent.js index 44402daf..2e3738d6 100644 --- a/src/extent.js +++ b/src/extent.js @@ -2,10 +2,10 @@ export default function(values, valueof) { let min; let max; if (valueof === undefined) { - for (let value of values) { - if (value != null && value >= value) { + for (const value of values) { + if (value != null) { if (min === undefined) { - min = max = value; + if (value >= value) min = max = value; } else { if (min > value) min = value; if (max < value) max = value; @@ -15,9 +15,9 @@ export default function(values, valueof) { } else { let index = -1; for (let value of values) { - if ((value = valueof(value, ++index, values)) != null && value >= value) { + if ((value = valueof(value, ++index, values)) != null) { if (min === undefined) { - min = max = value; + if (value >= value) min = max = value; } else { if (min > value) min = value; if (max < value) max = value; diff --git a/src/index.js b/src/index.js index 927a6625..004ea8e8 100644 --- a/src/index.js +++ b/src/index.js @@ -6,22 +6,26 @@ export {default as descending} from "./descending.js"; export {default as deviation} from "./deviation.js"; export {default as extent} from "./extent.js"; export {default as group} from "./group.js"; -export {default as bin, default as histogram} from "./bin.js"; +export {default as bin, default as histogram} from "./bin.js"; // Deprecated; use bin. export {default as thresholdFreedmanDiaconis} from "./threshold/freedmanDiaconis.js"; export {default as thresholdScott} from "./threshold/scott.js"; export {default as thresholdSturges} from "./threshold/sturges.js"; export {default as max} from "./max.js"; +export {default as maxIndex} from "./maxIndex.js"; export {default as mean} from "./mean.js"; export {default as median} from "./median.js"; export {default as merge} from "./merge.js"; export {default as min} from "./min.js"; +export {default as minIndex} from "./minIndex.js"; export {default as pairs} from "./pairs.js"; export {default as permute} from "./permute.js"; export {default as quantile} from "./quantile.js"; export {default as quickselect} from "./quickselect.js"; export {default as range} from "./range.js"; export {default as rollup} from "./rollup.js"; -export {default as scan} from "./scan.js"; +export {default as least} from "./least.js"; +export {default as leastIndex} from "./leastIndex.js"; +export {default as scan} from "./scan.js"; // Deprecated; use leastIndex. export {default as shuffle} from "./shuffle.js"; export {default as sum} from "./sum.js"; export {default as ticks, tickIncrement, tickStep} from "./ticks.js"; diff --git a/src/least.js b/src/least.js new file mode 100644 index 00000000..53d470bc --- /dev/null +++ b/src/least.js @@ -0,0 +1,15 @@ +import ascending from "./ascending.js"; + +export default function least(values, compare = ascending) { + let min; + let defined = false; + for (const value of values) { + if (defined + ? compare(value, min) < 0 + : compare(value, value) === 0) { + min = value; + defined = true; + } + } + return min; +} diff --git a/src/leastIndex.js b/src/leastIndex.js new file mode 100644 index 00000000..3abf9058 --- /dev/null +++ b/src/leastIndex.js @@ -0,0 +1,17 @@ +import ascending from "./ascending.js"; + +export default function leastIndex(values, compare = ascending) { + let min; + let minIndex = -1; + let index = -1; + for (const value of values) { + ++index; + if (minIndex < 0 + ? compare(value, value) === 0 + : compare(value, min) < 0) { + min = value; + minIndex = index; + } + } + return minIndex; +} diff --git a/src/max.js b/src/max.js index 4ff90e58..ce287368 100644 --- a/src/max.js +++ b/src/max.js @@ -1,10 +1,9 @@ export default function max(values, valueof) { let max; if (valueof === undefined) { - for (let value of values) { + for (const value of values) { if (value != null - && value >= value - && (max === undefined || max < value)) { + && (max < value || (max === undefined && value >= value))) { max = value; } } @@ -12,8 +11,7 @@ export default function max(values, valueof) { let index = -1; for (let value of values) { if ((value = valueof(value, ++index, values)) != null - && value >= value - && (max === undefined || max < value)) { + && (max < value || (max === undefined && value >= value))) { max = value; } } diff --git a/src/maxIndex.js b/src/maxIndex.js new file mode 100644 index 00000000..87da1a2c --- /dev/null +++ b/src/maxIndex.js @@ -0,0 +1,22 @@ +export default function maxIndex(values, valueof) { + let max; + let maxIndex = -1; + let index = -1; + if (valueof === undefined) { + for (const value of values) { + ++index; + if (value != null + && (max < value || (max === undefined && value >= value))) { + max = value, maxIndex = index; + } + } + } else { + for (let value of values) { + if ((value = valueof(value, ++index, values)) != null + && (max < value || (max === undefined && value >= value))) { + max = value, maxIndex = index; + } + } + } + return maxIndex; +} diff --git a/src/min.js b/src/min.js index 018417ed..df88bfb2 100644 --- a/src/min.js +++ b/src/min.js @@ -1,10 +1,9 @@ export default function min(values, valueof) { let min; if (valueof === undefined) { - for (let value of values) { + for (const value of values) { if (value != null - && value >= value - && (min === undefined || min > value)) { + && (min > value || (min === undefined && value >= value))) { min = value; } } @@ -12,8 +11,7 @@ export default function min(values, valueof) { let index = -1; for (let value of values) { if ((value = valueof(value, ++index, values)) != null - && value >= value - && (min === undefined || min > value)) { + && (min > value || (min === undefined && value >= value))) { min = value; } } diff --git a/src/minIndex.js b/src/minIndex.js new file mode 100644 index 00000000..5c07d1ea --- /dev/null +++ b/src/minIndex.js @@ -0,0 +1,22 @@ +export default function minIndex(values, valueof) { + let min; + let minIndex = -1; + let index = -1; + if (valueof === undefined) { + for (const value of values) { + ++index; + if (value != null + && (min > value || (min === undefined && value >= value))) { + min = value, minIndex = index; + } + } + } else { + for (let value of values) { + if ((value = valueof(value, ++index, values)) != null + && (min > value || (min === undefined && value >= value))) { + min = value, minIndex = index; + } + } + } + return minIndex; +} diff --git a/src/scan.js b/src/scan.js index f965311d..9c538f8a 100644 --- a/src/scan.js +++ b/src/scan.js @@ -1,17 +1,6 @@ -import ascending from "./ascending.js"; +import leastIndex from "./leastIndex.js"; -export default function scan(values, compare = ascending) { - let min; - let minIndex; - let index = -1; - for (const value of values) { - ++index; - if (minIndex === undefined - ? compare(value, value) === 0 - : compare(value, min) < 0) { - min = value; - minIndex = index; - } - } - return minIndex; +export default function scan(values, compare) { + const index = leastIndex(values, compare); + return index < 0 ? undefined : index; } diff --git a/test/least-test.js b/test/least-test.js new file mode 100644 index 00000000..531c78b7 --- /dev/null +++ b/test/least-test.js @@ -0,0 +1,58 @@ +var tape = require("tape"), + arrays = require("../"); + +tape("least(array) compares using natural order", function(test) { + test.strictEqual(arrays.least([0, 1]), 0); + test.strictEqual(arrays.least([1, 0]), 0); + test.strictEqual(arrays.least([0, "1"]), 0); + test.strictEqual(arrays.least(["1", 0]), 0); + test.strictEqual(arrays.least(["10", "2"]), "10"); + test.strictEqual(arrays.least(["2", "10"]), "10"); + test.strictEqual(arrays.least(["10", "2", NaN]), "10"); + test.strictEqual(arrays.least([NaN, "10", "2"]), "10"); + test.strictEqual(arrays.least(["2", NaN, "10"]), "10"); + test.strictEqual(arrays.least([2, NaN, 10]), 2); + test.strictEqual(arrays.least([10, 2, NaN]), 2); + test.strictEqual(arrays.least([NaN, 10, 2]), 2); + test.end(); +}); + +tape("least(array, compare) compares using the specified compare function", function(test) { + var a = {name: "a"}, b = {name: "b"}; + test.deepEqual(arrays.least([a, b], function(a, b) { return a.name.localeCompare(b.name); }), {name: "a"}); + test.strictEqual(arrays.least([1, 0], arrays.descending), 1); + test.strictEqual(arrays.least(["1", 0], arrays.descending), "1"); + test.strictEqual(arrays.least(["2", "10"], arrays.descending), "2"); + test.strictEqual(arrays.least(["2", NaN, "10"], arrays.descending), "2"); + test.strictEqual(arrays.least([2, NaN, 10], arrays.descending), 10); + test.end(); +}); + +tape("least(array) returns undefined if the array is empty", function(test) { + test.strictEqual(arrays.least([]), undefined); + test.end(); +}); + +tape("least(array) returns undefined if the array contains only incomparable values", function(test) { + test.strictEqual(arrays.least([NaN, undefined]), undefined); + test.strictEqual(arrays.least([NaN, "foo"], function(a, b) { return a - b; }), undefined); + test.end(); +}); + +tape("least(array) returns the first of equal values", function(test) { + test.deepEqual(arrays.least([2, 2, 1, 1, 0, 0, 0, 3, 0].map(box), ascendingValue), {value: 0, index: 4}); + test.deepEqual(arrays.least([3, 2, 2, 1, 1, 0, 0, 0, 3, 0].map(box), descendingValue), {value: 3, index: 0}); + test.end(); +}); + +function box(value, index) { + return {value, index}; +} + +function ascendingValue(a, b) { + return a.value - b.value; +} + +function descendingValue(a, b) { + return b.value - a.value; +} diff --git a/test/leastIndex-test.js b/test/leastIndex-test.js new file mode 100644 index 00000000..472fcb03 --- /dev/null +++ b/test/leastIndex-test.js @@ -0,0 +1,46 @@ +var tape = require("tape"), + arrays = require("../"); + +tape("leastIndex(array) compares using natural order", function(test) { + test.strictEqual(arrays.leastIndex([0, 1]), 0); + test.strictEqual(arrays.leastIndex([1, 0]), 1); + test.strictEqual(arrays.leastIndex([0, "1"]), 0); + test.strictEqual(arrays.leastIndex(["1", 0]), 1); + test.strictEqual(arrays.leastIndex(["10", "2"]), 0); + test.strictEqual(arrays.leastIndex(["2", "10"]), 1); + test.strictEqual(arrays.leastIndex(["10", "2", NaN]), 0); + test.strictEqual(arrays.leastIndex([NaN, "10", "2"]), 1); + test.strictEqual(arrays.leastIndex(["2", NaN, "10"]), 2); + test.strictEqual(arrays.leastIndex([2, NaN, 10]), 0); + test.strictEqual(arrays.leastIndex([10, 2, NaN]), 1); + test.strictEqual(arrays.leastIndex([NaN, 10, 2]), 2); + test.end(); +}); + +tape("leastIndex(array, compare) compares using the specified compare function", function(test) { + var a = {name: "a"}, b = {name: "b"}; + test.strictEqual(arrays.leastIndex([a, b], function(a, b) { return a.name.localeCompare(b.name); }), 0); + test.strictEqual(arrays.leastIndex([1, 0], arrays.descending), 0); + test.strictEqual(arrays.leastIndex(["1", 0], arrays.descending), 0); + test.strictEqual(arrays.leastIndex(["2", "10"], arrays.descending), 0); + test.strictEqual(arrays.leastIndex(["2", NaN, "10"], arrays.descending), 0); + test.strictEqual(arrays.leastIndex([2, NaN, 10], arrays.descending), 2); + test.end(); +}); + +tape("leastIndex(array) returns -1 if the array is empty", function(test) { + test.strictEqual(arrays.leastIndex([]), -1); + test.end(); +}); + +tape("leastIndex(array) returns -1 if the array contains only incomparable values", function(test) { + test.strictEqual(arrays.leastIndex([NaN, undefined]), -1); + test.strictEqual(arrays.leastIndex([NaN, "foo"], function(a, b) { return a - b; }), -1); + test.end(); +}); + +tape("leastIndex(array) returns the first of equal values", function(test) { + test.strictEqual(arrays.leastIndex([2, 2, 1, 1, 0, 0, 0, 3, 0]), 4); + test.strictEqual(arrays.leastIndex([3, 2, 2, 1, 1, 0, 0, 0, 3, 0], arrays.descending), 0); + test.end(); +}); diff --git a/test/maxIndex-test.js b/test/maxIndex-test.js new file mode 100644 index 00000000..f91b7371 --- /dev/null +++ b/test/maxIndex-test.js @@ -0,0 +1,110 @@ +var tape = require("tape"), + arrays = require("../"); + +tape("maxIndex(array) returns the index of the greatest numeric value for numbers", function(test) { + test.deepEqual(arrays.maxIndex([1]), 0); + test.deepEqual(arrays.maxIndex([5, 1, 2, 3, 4]), 0); + test.deepEqual(arrays.maxIndex([20, 3]), 0); + test.deepEqual(arrays.maxIndex([3, 20]), 1); + test.end(); +}); + +tape("maxIndex(array) returns the greatest lexicographic value for strings", function(test) { + test.deepEqual(arrays.maxIndex(["c", "a", "b"]), 0); + test.deepEqual(arrays.maxIndex(["20", "3"]), 1); + test.deepEqual(arrays.maxIndex(["3", "20"]), 0); + test.end(); +}); + +tape("maxIndex(array) ignores null, undefined and NaN", function(test) { + var o = {valueOf: function() { return NaN; }}; + test.deepEqual(arrays.maxIndex([NaN, 1, 2, 3, 4, 5]), 5); + test.deepEqual(arrays.maxIndex([o, 1, 2, 3, 4, 5]), 5); + test.deepEqual(arrays.maxIndex([1, 2, 3, 4, 5, NaN]), 4); + test.deepEqual(arrays.maxIndex([1, 2, 3, 4, 5, o]), 4); + test.deepEqual(arrays.maxIndex([10, null, 3, undefined, 5, NaN]), 0); + test.deepEqual(arrays.maxIndex([-1, null, -3, undefined, -5, NaN]), 0); + test.end(); +}); + +tape("maxIndex(array) compares heterogenous types as numbers", function(test) { + test.equal(arrays.maxIndex([20, "3"]), 0); + test.equal(arrays.maxIndex(["20", 3]), 0); + test.equal(arrays.maxIndex([3, "20"]), 1); + test.equal(arrays.maxIndex(["3", 20]), 1); + test.end(); +}); + +tape("maxIndex(array) returns -1 if the array contains no numbers", function(test) { + test.equal(arrays.maxIndex([]), -1); + test.equal(arrays.maxIndex([null]), -1); + test.equal(arrays.maxIndex([undefined]), -1); + test.equal(arrays.maxIndex([NaN]), -1); + test.equal(arrays.maxIndex([NaN, NaN]), -1); + test.end(); +}); + +tape("maxIndex(array, f) returns the greatest numeric value for numbers", function(test) { + test.deepEqual(arrays.maxIndex([1].map(box), unbox), 0); + test.deepEqual(arrays.maxIndex([5, 1, 2, 3, 4].map(box), unbox), 0); + test.deepEqual(arrays.maxIndex([20, 3].map(box), unbox), 0); + test.deepEqual(arrays.maxIndex([3, 20].map(box), unbox), 1); + test.end(); +}); + +tape("maxIndex(array, f) returns the greatest lexicographic value for strings", function(test) { + test.deepEqual(arrays.maxIndex(["c", "a", "b"].map(box), unbox), 0); + test.deepEqual(arrays.maxIndex(["20", "3"].map(box), unbox), 1); + test.deepEqual(arrays.maxIndex(["3", "20"].map(box), unbox), 0); + test.end(); +}); + +tape("maxIndex(array, f) ignores null, undefined and NaN", function(test) { + var o = {valueOf: function() { return NaN; }}; + test.deepEqual(arrays.maxIndex([NaN, 1, 2, 3, 4, 5].map(box), unbox), 5); + test.deepEqual(arrays.maxIndex([o, 1, 2, 3, 4, 5].map(box), unbox), 5); + test.deepEqual(arrays.maxIndex([1, 2, 3, 4, 5, NaN].map(box), unbox), 4); + test.deepEqual(arrays.maxIndex([1, 2, 3, 4, 5, o].map(box), unbox), 4); + test.deepEqual(arrays.maxIndex([10, null, 3, undefined, 5, NaN].map(box), unbox), 0); + test.deepEqual(arrays.maxIndex([-1, null, -3, undefined, -5, NaN].map(box), unbox), 0); + test.end(); +}); + +tape("maxIndex(array, f) compares heterogenous types as numbers", function(test) { + test.equal(arrays.maxIndex([20, "3"].map(box), unbox), 0); + test.equal(arrays.maxIndex(["20", 3].map(box), unbox), 0); + test.equal(arrays.maxIndex([3, "20"].map(box), unbox), 1); + test.equal(arrays.maxIndex(["3", 20].map(box), unbox), 1); + test.end(); +}); + +tape("maxIndex(array, f) returns -1 if the array contains no observed values", function(test) { + test.equal(arrays.maxIndex([].map(box), unbox), -1); + test.equal(arrays.maxIndex([null].map(box), unbox), -1); + test.equal(arrays.maxIndex([undefined].map(box), unbox), -1); + test.equal(arrays.maxIndex([NaN].map(box), unbox), -1); + test.equal(arrays.maxIndex([NaN, NaN].map(box), unbox), -1); + test.end(); +}); + +tape("maxIndex(array, f) passes the accessor d, i, and array", function(test) { + var results = [], array = ["a", "b", "c"]; + arrays.maxIndex(array, function(d, i, array) { results.push([d, i, array]); }); + test.deepEqual(results, [["a", 0, array], ["b", 1, array], ["c", 2, array]]); + test.end(); +}); + +tape("maxIndex(array, f) uses the global context", function(test) { + var results = []; + arrays.maxIndex([1, 2], function() { results.push(this); }); + test.deepEqual(results, [global, global]); + test.end(); +}); + +function box(value) { + return {value: value}; +} + +function unbox(box) { + return box.value; +} diff --git a/test/minIndex-test.js b/test/minIndex-test.js new file mode 100644 index 00000000..bbc84bdc --- /dev/null +++ b/test/minIndex-test.js @@ -0,0 +1,110 @@ +var tape = require("tape"), + arrays = require("../"); + +tape("minIndex(array) returns the index of the least numeric value for numbers", function(test) { + test.deepEqual(arrays.minIndex([1]), 0); + test.deepEqual(arrays.minIndex([5, 1, 2, 3, 4]), 1); + test.deepEqual(arrays.minIndex([20, 3]), 1); + test.deepEqual(arrays.minIndex([3, 20]), 0); + test.end(); +}); + +tape("minIndex(array) returns the index of the least lexicographic value for strings", function(test) { + test.deepEqual(arrays.minIndex(["c", "a", "b"]), 1); + test.deepEqual(arrays.minIndex(["20", "3"]), 0); + test.deepEqual(arrays.minIndex(["3", "20"]), 1); + test.end(); +}); + +tape("minIndex(array) ignores null, undefined and NaN", function(test) { + var o = {valueOf: function() { return NaN; }}; + test.deepEqual(arrays.minIndex([NaN, 1, 2, 3, 4, 5]), 1); + test.deepEqual(arrays.minIndex([o, 1, 2, 3, 4, 5]), 1); + test.deepEqual(arrays.minIndex([1, 2, 3, 4, 5, NaN]), 0); + test.deepEqual(arrays.minIndex([1, 2, 3, 4, 5, o]), 0); + test.deepEqual(arrays.minIndex([10, null, 3, undefined, 5, NaN]), 2); + test.deepEqual(arrays.minIndex([-1, null, -3, undefined, -5, NaN]), 4); + test.end(); +}); + +tape("minIndex(array) compares heterogenous types as numbers", function(test) { + test.equal(arrays.minIndex([20, "3"]), 1); + test.equal(arrays.minIndex(["20", 3]), 1); + test.equal(arrays.minIndex([3, "20"]), 0); + test.equal(arrays.minIndex(["3", 20]), 0); + test.end(); +}); + +tape("minIndex(array) returns -1 if the array contains no numbers", function(test) { + test.equal(arrays.minIndex([]), -1); + test.equal(arrays.minIndex([null]), -1); + test.equal(arrays.minIndex([undefined]), -1); + test.equal(arrays.minIndex([NaN]), -1); + test.equal(arrays.minIndex([NaN, NaN]), -1); + test.end(); +}); + +tape("minIndex(array, f) returns the index of the least numeric value for numbers", function(test) { + test.deepEqual(arrays.minIndex([1].map(box), unbox), 0); + test.deepEqual(arrays.minIndex([5, 1, 2, 3, 4].map(box), unbox), 1); + test.deepEqual(arrays.minIndex([20, 3].map(box), unbox), 1); + test.deepEqual(arrays.minIndex([3, 20].map(box), unbox), 0); + test.end(); +}); + +tape("minIndex(array, f) returns the index of the least lexicographic value for strings", function(test) { + test.deepEqual(arrays.minIndex(["c", "a", "b"].map(box), unbox), 1); + test.deepEqual(arrays.minIndex(["20", "3"].map(box), unbox), 0); + test.deepEqual(arrays.minIndex(["3", "20"].map(box), unbox), 1); + test.end(); +}); + +tape("minIndex(array, f) ignores null, undefined and NaN", function(test) { + var o = {valueOf: function() { return NaN; }}; + test.deepEqual(arrays.minIndex([NaN, 1, 2, 3, 4, 5].map(box), unbox), 1); + test.deepEqual(arrays.minIndex([o, 1, 2, 3, 4, 5].map(box), unbox), 1); + test.deepEqual(arrays.minIndex([1, 2, 3, 4, 5, NaN].map(box), unbox), 0); + test.deepEqual(arrays.minIndex([1, 2, 3, 4, 5, o].map(box), unbox), 0); + test.deepEqual(arrays.minIndex([10, null, 3, undefined, 5, NaN].map(box), unbox), 2); + test.deepEqual(arrays.minIndex([-1, null, -3, undefined, -5, NaN].map(box), unbox), 4); + test.end(); +}); + +tape("minIndex(array, f) compares heterogenous types as numbers", function(test) { + test.equal(arrays.minIndex([20, "3"].map(box), unbox), 1); + test.equal(arrays.minIndex(["20", 3].map(box), unbox), 1); + test.equal(arrays.minIndex([3, "20"].map(box), unbox), 0); + test.equal(arrays.minIndex(["3", 20].map(box), unbox), 0); + test.end(); +}); + +tape("minIndex(array, f) returns -1 if the array contains no observed values", function(test) { + test.equal(arrays.minIndex([].map(box), unbox), -1); + test.equal(arrays.minIndex([null].map(box), unbox), -1); + test.equal(arrays.minIndex([undefined].map(box), unbox), -1); + test.equal(arrays.minIndex([NaN].map(box), unbox), -1); + test.equal(arrays.minIndex([NaN, NaN].map(box), unbox), -1); + test.end(); +}); + +tape("minIndex(array, f) passes the accessor d, i, and array", function(test) { + var results = [], array = ["a", "b", "c"]; + arrays.minIndex(array, function(d, i, array) { results.push([d, i, array]); }); + test.deepEqual(results, [["a", 0, array], ["b", 1, array], ["c", 2, array]]); + test.end(); +}); + +tape("minIndex(array, f) uses the global context", function(test) { + var results = []; + arrays.minIndex([1, 2], function() { results.push(this); }); + test.deepEqual(results, [global, global]); + test.end(); +}); + +function box(value) { + return {value: value}; +} + +function unbox(box) { + return box.value; +}