diff --git a/README.md b/README.md index a918343..c755978 100644 --- a/README.md +++ b/README.md @@ -119,6 +119,26 @@ Immutable.from([1, 2, 3]); Immutable([1, 2, 3]) ``` +## Immutable.sortBy + +JavaScript's Array `sort()` is a mutating function and its use will throw an ImmutableError. This has been done rather than override the `sort()` method with one that returns an immutable Array. The rationale for this is that returning an immutable array from the `sort()` function would be a surprising change to the expected behavior for developers unfamiliar with immutables. However, a convenient method for creating a sorted immutable array from an unsorted one is still desirable. This function closes this gap. + +```javascript +var array = Immutable([1, 3, 2]); +var sortedArray = Immutable.sortBy(array); +// returns Immutable([1, 2, 3]); +``` + +This function also accepts an optional sorting function as per the original `sort()`: + +```javascript +var array = Immutable([1, 3, 2]); +var sortedArray = Immutable.sortBy(array, function(a, b) { + return b - a; +}); +// returns Immutable([3, 2, 1]); +``` + ## Immutable Array Like a regular Array, but immutable! You can construct these by passing diff --git a/src/seamless-immutable.js b/src/seamless-immutable.js index 9e458cc..a656dfd 100644 --- a/src/seamless-immutable.js +++ b/src/seamless-immutable.js @@ -577,6 +577,42 @@ function immutableInit(config) { return result; } + function sortBy(array, sorter) { + var result = [], i, length, rearranged; + + if (!(array instanceof Array)) { + throw new TypeError("The first argument to Immutable#sortBy must be an array."); + } + + for (i = 0, length = array.length; i < length; i++) { + result.push(array[i]); + } + result.sort(sorter); + + // Test if elements of the array have been rearranged + rearranged = false; + for (i = 0, length = array.length; i < length; i++) { + if(result[i] !== array[i]) { + // Check if both are NaN. Type checks catch cases where isNaN returns true for strings. + if(!(typeof result[i] === "number" && isNaN(result[i]) && typeof array[i] === "number" && isNaN(array[i]))) { + rearranged = true; + } + } + } + + // If the elements haven't been rearranged, return the original immutable + if(rearranged) { + return makeImmutableArray(result); + } else { + // Ensure that the returned array is immutable + if(isImmutable(array)) { + return array; + } else { + return makeImmutableArray(array); + } + } + } + // Creates plain object to be used for cloning function instantiatePlainObject() { return {}; @@ -719,6 +755,7 @@ function immutableInit(config) { Immutable.getIn = toStatic(getIn); Immutable.flatMap = toStatic(flatMap); Immutable.asObject = toStatic(asObject); + Immutable.sortBy = sortBy; if (!globalConfig.use_static) { Immutable.static = immutableInit({ use_static: true diff --git a/test/ImmutableArray.spec.js b/test/ImmutableArray.spec.js index 8518aa0..744e745 100644 --- a/test/ImmutableArray.spec.js +++ b/test/ImmutableArray.spec.js @@ -5,6 +5,7 @@ var testAsMutable = require("./ImmutableArray/test-asMutable.js"); var testSet = require("./ImmutableArray/test-set.js"); var testUpdate = require("./ImmutableArray/test-update.js"); var testGetIn = require("./ImmutableArray/test-getIn.js"); +var testSortBy = require("./ImmutableArray/test-sortBy"); var devBuild = require("../seamless-immutable.development.js"); var prodBuild = require("../seamless-immutable.production.min.js"); var getTestUtils = require("./TestUtils.js"); @@ -25,6 +26,7 @@ var getTestUtils = require("./TestUtils.js"); testSet(config); testUpdate(config); testGetIn(config); + testSortBy(config); }); }); }); diff --git a/test/ImmutableArray/test-sortBy.js b/test/ImmutableArray/test-sortBy.js new file mode 100644 index 0000000..4af0954 --- /dev/null +++ b/test/ImmutableArray/test-sortBy.js @@ -0,0 +1,58 @@ +var JSC = require("jscheck"); +var getTestUtils = require("../TestUtils.js"); +var assert = require("chai").assert; + +module.exports = function(config) { + var Immutable = config.implementation; + var TestUtils = getTestUtils(Immutable); + var check = TestUtils.check; + + function dummySorter(a, b) { + if(b > a) { + return 1; + } else if(b < a) { + return -1; + } else { + return 0; + } + } + + describe("#sortBy", function() { + it("produces a sorted immutable array", function() { + check(100, [JSC.array(JSC.integer(4), JSC.any())], function(array) { + var immutable = Immutable(array); + var mutable = array.slice(); + + var resultImmutable = Immutable.sortBy(immutable); + var resultMutable = mutable.slice(); + resultMutable.sort(); + + TestUtils.assertJsonEqual(resultImmutable, resultMutable); + }); + }); + + it("produces a sorted immutable array when provided with a sorter function", function() { + check(100, [JSC.array(JSC.integer(4), JSC.any())], function(array) { + var immutable = Immutable(array); + var mutable = array.slice(); + + var resultImmutable = Immutable.sortBy(immutable, dummySorter); + var resultMutable = mutable.slice(); + resultMutable.sort(dummySorter); + + TestUtils.assertJsonEqual(resultImmutable, resultMutable); + }); + }); + + it("returns the same array if it is already sorted", function() { + check(100, [JSC.array(JSC.integer(4), JSC.any())], function(array) { + var mutable = array.slice().sort(); + var immutable = Immutable(mutable); + + var resultImmutable = Immutable.sortBy(immutable); + + assert.strictEqual(resultImmutable, immutable); + }); + }); + }); +};