Related
I'm looking to sort an array of about 200-300 objects, sorting on a specific key and a given order (asc/desc). The order of results must be consistent and stable.
What would be the best algorithm to use, and could you provide an example of it's implementation in javascript?
Thanks!
It is possible to get a stable sorting from a non-stable sort function.
Before sorting you get the position of all the elements.
In your sort condition, if both elements are equal, then you sort by the position.
Tada! You've got a stable sort.
I've written an article about it on my blog if you want to know more about this technique and how to implement it: http://blog.vjeux.com/2010/javascript/javascript-sorting-table.html
Since you are looking for something stable, the merge sort should do.
http://www.stoimen.com/blog/2010/07/02/friday-algorithms-javascript-merge-sort/
The code can be found at the above website:
function mergeSort(arr)
{
if (arr.length < 2)
return arr;
var middle = parseInt(arr.length / 2);
var left = arr.slice(0, middle);
var right = arr.slice(middle, arr.length);
return merge(mergeSort(left), mergeSort(right));
}
function merge(left, right)
{
var result = [];
while (left.length && right.length) {
if (left[0] <= right[0]) {
result.push(left.shift());
} else {
result.push(right.shift());
}
}
while (left.length)
result.push(left.shift());
while (right.length)
result.push(right.shift());
return result;
}
EDIT:
According to this post, it looks like Array.Sort in some implementations uses a merge sort.
Somewhat shorter version of the same thing using ES2017 features like arrow functions and destructuring:
Function
var stableSort = (arr, compare) => arr
.map((item, index) => ({item, index}))
.sort((a, b) => compare(a.item, b.item) || a.index - b.index)
.map(({item}) => item)
It accepts input array and compare function:
stableSort([5,6,3,2,1], (a, b) => a - b)
It also returns new array instead of making in-place sort like the built-in Array.sort() function.
Test
If we take the following input array, initially sorted by weight:
// sorted by weight
var input = [
{ height: 100, weight: 80 },
{ height: 90, weight: 90 },
{ height: 70, weight: 95 },
{ height: 100, weight: 100 },
{ height: 80, weight: 110 },
{ height: 110, weight: 115 },
{ height: 100, weight: 120 },
{ height: 70, weight: 125 },
{ height: 70, weight: 130 },
{ height: 100, weight: 135 },
{ height: 75, weight: 140 },
{ height: 70, weight: 140 }
]
Then sort it by height using stableSort:
stableSort(input, (a, b) => a.height - b.height)
Results in:
// Items with the same height are still sorted by weight
// which means they preserved their relative order.
var stable = [
{ height: 70, weight: 95 },
{ height: 70, weight: 125 },
{ height: 70, weight: 130 },
{ height: 70, weight: 140 },
{ height: 75, weight: 140 },
{ height: 80, weight: 110 },
{ height: 90, weight: 90 },
{ height: 100, weight: 80 },
{ height: 100, weight: 100 },
{ height: 100, weight: 120 },
{ height: 100, weight: 135 },
{ height: 110, weight: 115 }
]
However sorting the same input array using the built-in Array.sort() (in Chrome/NodeJS):
input.sort((a, b) => a.height - b.height)
Returns:
var unstable = [
{ height: 70, weight: 140 },
{ height: 70, weight: 95 },
{ height: 70, weight: 125 },
{ height: 70, weight: 130 },
{ height: 75, weight: 140 },
{ height: 80, weight: 110 },
{ height: 90, weight: 90 },
{ height: 100, weight: 100 },
{ height: 100, weight: 80 },
{ height: 100, weight: 135 },
{ height: 100, weight: 120 },
{ height: 110, weight: 115 }
]
Resources
Wikipedia
MDN
JSFiddle
Update
Array.prototype.sort is now stable in V8 v7.0 / Chrome 70!
Previously, V8 used an unstable QuickSort for arrays with more than 10 elements. Now, we use the stable TimSort algorithm.
source
I know that this question has been answered for some time, but I happen to have a good stable merge sort implementation for Array and jQuery in my clipboard, so I'll share it in the hopes that some future searchers might find it useful.
It allows you to specify your own comparison function just like the normal Array.sort implementation.
Implementation
// Add stable merge sort to Array and jQuery prototypes
// Note: We wrap it in a closure so it doesn't pollute the global
// namespace, but we don't put it in $(document).ready, since it's
// not dependent on the DOM
(function() {
// expose to Array and jQuery
Array.prototype.mergeSort = jQuery.fn.mergeSort = mergeSort;
function mergeSort(compare) {
var length = this.length,
middle = Math.floor(length / 2);
if (!compare) {
compare = function(left, right) {
if (left < right)
return -1;
if (left == right)
return 0;
else
return 1;
};
}
if (length < 2)
return this;
return merge(
this.slice(0, middle).mergeSort(compare),
this.slice(middle, length).mergeSort(compare),
compare
);
}
function merge(left, right, compare) {
var result = [];
while (left.length > 0 || right.length > 0) {
if (left.length > 0 && right.length > 0) {
if (compare(left[0], right[0]) <= 0) {
result.push(left[0]);
left = left.slice(1);
}
else {
result.push(right[0]);
right = right.slice(1);
}
}
else if (left.length > 0) {
result.push(left[0]);
left = left.slice(1);
}
else if (right.length > 0) {
result.push(right[0]);
right = right.slice(1);
}
}
return result;
}
})();
Example Usage
var sorted = [
'Finger',
'Sandwich',
'sandwich',
'5 pork rinds',
'a guy named Steve',
'some noodles',
'mops and brooms',
'Potato Chip Brand® chips'
].mergeSort(function(left, right) {
lval = left.toLowerCase();
rval = right.toLowerCase();
console.log(lval, rval);
if (lval < rval)
return -1;
else if (lval == rval)
return 0;
else
return 1;
});
sorted == ["5 pork rinds", "a guy named Steve", "Finger", "mops and brooms", "Potato Chip Brand® chips", "Sandwich", "sandwich", "some noodles"];
You can use the following function to perform a stable sort regardless of the native implementation, based on the assertion made in this answer.
Do note that as of ECMAScript 2019, the specification requires that the builtin sort() method perform a stable sort. With that in mind, an explicit stable sort function like the one below is still relevant if you are required to support older browsers that are not specification compliant.
// ECMAScript 5 implementation
function stableSort(array, compareFunction) {
'use strict';
var length = array.length;
var indices = new Uint32Array(length);
var i;
var slice;
// reference values by indices
for (i = 0; i < length; ++i) {
indices[i] = i;
}
// sort with fallback based on indices
indices.sort(function stableCompareFunction(compareFunction, a, b) {
var order = Number(compareFunction(this[a], this[b]));
return order || a - b;
}.bind(array, compareFunction));
slice = array.slice();
// re-order original array to stable sorted values
for (i = 0; i < length; ++i) {
array[i] = slice[indices[i]];
}
return array;
}
// usage
const array = Array(500000).fill().map(() => Number(Math.random().toFixed(4)));
const alwaysEqual = () => 0;
const isUnmoved = (value, index) => value === array[index];
// not guaranteed to be stable before ES2019
console.log(
'sort() stable?',
array.slice().sort(alwaysEqual).every(isUnmoved)
);
// guaranteed to be stable
console.log(
'stableSort() stable?',
stableSort(array.slice(), alwaysEqual).every(isUnmoved)
);
// performance using realistic scenario with unsorted big data
function time(arraySlice, algorithm, compare) {
var start;
var stop;
start = performance.now();
algorithm(arraySlice, compare);
stop = performance.now();
return stop - start;
}
const ascending = (a, b) => a - b;
const msSort = time(array.slice(), (array, compare) => array.sort(compare), ascending);
const msStableSort = time(array.slice(), (array, compare) => stableSort(array, compare), ascending);
console.log('sort()', msSort.toFixed(3), 'ms');
console.log('stableSort()', msStableSort.toFixed(3), 'ms');
console.log('sort() / stableSort()', (100 * msSort / msStableSort).toFixed(3) + '%');
Running the performance tests implemented above, stableSort() appears to run at about 72% of the speed of sort() on version 88 of Google Chrome and Microsoft Edge.
Using .bind() on the inline function within stableSort() used to boost relative performance significantly by avoiding unneeded scoped references on each call.
In practice, this no longer makes a difference since modern engines automatically perform this optimization now, but it is left in the implementation anyway in order to continue improving performance in older browsers which don't ship with this optimization.
The following sorts the supplied array, by applying the supplied compare function, returning the original index comparison when the compare function returns 0:
function stableSort(arr, compare) {
var original = arr.slice(0);
arr.sort(function(a, b){
var result = compare(a, b);
return result === 0 ? original.indexOf(a) - original.indexOf(b) : result;
});
return arr;
}
The example below sorts an array of names by surname, retaining the order of equal surnames:
var names = [
{ surname: "Williams", firstname: "Mary" },
{ surname: "Doe", firstname: "Mary" },
{ surname: "Johnson", firstname: "Alan" },
{ surname: "Doe", firstname: "John" },
{ surname: "White", firstname: "John" },
{ surname: "Doe", firstname: "Sam" }
]
function stableSort(arr, compare) {
var original = arr.slice(0);
arr.sort(function(a, b){
var result = compare(a, b);
return result === 0 ? original.indexOf(a) - original.indexOf(b) : result;
});
return arr;
}
stableSort(names, function(a, b) {
return a.surname > b.surname ? 1 : a.surname < b.surname ? -1 : 0;
})
names.forEach(function(name) {
console.log(name.surname + ', ' + name.firstname);
});
Here's a stable implementation. It works by using the native sort, but in cases where elements compare as equal, you break ties using the original index position.
function stableSort(arr, cmpFunc) {
//wrap the arr elements in wrapper objects, so we can associate them with their origional starting index position
var arrOfWrapper = arr.map(function(elem, idx){
return {elem: elem, idx: idx};
});
//sort the wrappers, breaking sorting ties by using their elements orig index position
arrOfWrapper.sort(function(wrapperA, wrapperB){
var cmpDiff = cmpFunc(wrapperA.elem, wrapperB.elem);
return cmpDiff === 0
? wrapperA.idx - wrapperB.idx
: cmpDiff;
});
//unwrap and return the elements
return arrOfWrapper.map(function(wrapper){
return wrapper.elem;
});
}
a non-thorough test
var res = stableSort([{a:1, b:4}, {a:1, b:5}], function(a, b){
return a.a - b.a;
});
console.log(res);
another answer alluded to this, but didn't post teh codez.
but, its not fast according to my benchmark. I modified a merge sort impl to accept a custom comparator function, and it was much faster.
You can also use Timsort. This is a really complicated algorithm (400+ lines, hence no source code here), so see Wikipedia's description or use one of the existing JavaScript implementations:
GPL 3 implementation. Packaged as Array.prototype.timsort. Appears to be an exact rewrite of Java code.
Public domain implementation Meant as a tutorial, the sample code only shows its use with integers.
Timsort is a highly optimized hybrid of mergesort and shuffle sort and is the default sorting algorithm in Python and in Java (1.7+). It is a complicated algorithm, since it uses different algorithms for many special cases. But as a result it's extremely fast under a wide variety of circumstances.
A simple one mergeSort from http://www.stoimen.com/blog/2010/07/02/friday-algorithms-javascript-merge-sort/
var a = [34, 203, 3, 746, 200, 984, 198, 764, 9];
function mergeSort(arr)
{
if (arr.length < 2)
return arr;
var middle = parseInt(arr.length / 2);
var left = arr.slice(0, middle);
var right = arr.slice(middle, arr.length);
return merge(mergeSort(left), mergeSort(right));
}
function merge(left, right)
{
var result = [];
while (left.length && right.length) {
if (left[0] <= right[0]) {
result.push(left.shift());
} else {
result.push(right.shift());
}
}
while (left.length)
result.push(left.shift());
while (right.length)
result.push(right.shift());
return result;
}
console.log(mergeSort(a));
I have to sort multidimensional arrays by an arbitrary column, and then by another. I use this function to sort:
function sortMDArrayByColumn(ary, sortColumn){
//Adds a sequential number to each row of the array
//This is the part that adds stability to the sort
for(var x=0; x<ary.length; x++){ary[x].index = x;}
ary.sort(function(a,b){
if(a[sortColumn]>b[sortColumn]){return 1;}
if(a[sortColumn]<b[sortColumn]){return -1;}
if(a.index>b.index){
return 1;
}
return -1;
});
}
Notice that ary.sort never returns zero, which is where some implementations of the "sort" function make decisions that might not be right.
This is pretty darn fast, too.
Here's how you could extend JS default Array object with a prototype method utilizing MERGE SORT. This method allows for sorting on a specific key (first parameter) and a given order ('asc'/'desc' as second parameter)
Array.prototype.mergeSort = function(sortKey, direction){
var unsortedArray = this;
if(unsortedArray.length < 2) return unsortedArray;
var middle = Math.floor(unsortedArray.length/2);
var leftSubArray = unsortedArray.slice(0,middle).mergeSort(sortKey, direction);
var rightSubArray = unsortedArray.slice(middle).mergeSort(sortKey, direction);
var sortedArray = merge(leftSubArray, rightSubArray);
return sortedArray;
function merge(left, right) {
var combined = [];
while(left.length>0 && right.length>0){
var leftValue = (sortKey ? left[0][sortKey] : left[0]);
var rightValue = (sortKey ? right[0][sortKey] : right[0]);
combined.push((direction === 'desc' ? leftValue > rightValue : leftValue < rightValue) ? left.shift() : right.shift())
}
return combined.concat(left.length ? left : right)
}
}
You can test this out yourself by dropping the above snippet into your browser console, then trying:
var x = [2,76,23,545,67,-9,12];
x.mergeSort(); //[-9, 2, 12, 23, 67, 76, 545]
x.mergeSort(undefined, 'desc'); //[545, 76, 67, 23, 12, 2, -9]
Or order based on a specific field in an array of objects:
var y = [
{startTime: 100, value: 'cat'},
{startTime: 5, value: 'dog'},
{startTime: 23, value: 'fish'},
{startTime: 288, value: 'pikachu'}
]
y.mergeSort('startTime');
y.mergeSort('startTime', 'desc');
So I needed a stable sort for my React+Redux app, and Vjeux's answer here helped me. However, my (generic) solution seems different than the others I see here so far, so I'm sharing it in case anyone else has a matching use-case:
I really just want to have something similar to the sort() API, where I can pass a comparator function.
Sometimes I can sort in-place, and sometimes my data is immutable (because Redux) and I need a sorted copy instead. So I need a stable sorting function for each use-case.
ES2015.
My solution is to create a typed array of indices, then use a comparison function to sort these indices based on the to-be-sorted array. Then we can use the sorted indices to either sort the original array or create a sorted copy in a single pass. If that's confusing, think of it this way: where you would normally pass a comparison function like:
(a, b) => {
/* some way to compare a and b, returning -1, 0, or 1 */
};
You now instead use:
(i, j) => {
let a = arrayToBeSorted[i], b = arrayToBeSorted[j];
/* some way to compare a and b, returning -1 or 1 */
return i - j; // fallback when a == b
}
Speed is good; it is basically the built-in sorting algorithm is, plus two linear passes in the end, and one extra layer of pointer indirection overhead.
Happy to receive feedback on this approach. Here is my full implementation of it it:
/**
* - `array`: array to be sorted
* - `comparator`: closure that expects indices `i` and `j`, and then
* compares `array[i]` to `array[j]` in some way. To force stability,
* end with `i - j` as the last "comparison".
*
* Example:
* ```
* let array = [{n: 1, s: "b"}, {n: 1, s: "a"}, {n:0, s: "a"}];
* const comparator = (i, j) => {
* const ni = array[i].n, nj = array[j].n;
* return ni < nj ? -1 :
* ni > nj ? 1 :
* i - j;
* };
* stableSortInPlace(array, comparator);
* // ==> [{n:0, s: "a"}, {n:1, s: "b"}, {n:1, s: "a"}]
* ```
*/
function stableSortInPlace(array, comparator) {
return sortFromIndices(array, findIndices(array, comparator));
}
function stableSortedCopy(array, comparator){
let indices = findIndices(array, comparator);
let sortedArray = [];
for (let i = 0; i < array.length; i++){
sortedArray.push(array[indices[i]]);
}
return sortedArray;
}
function findIndices(array, comparator){
// Assumes we don't have to worry about sorting more than
// 4 billion elements; if you know the upper bounds of your
// input you could replace it with a smaller typed array
let indices = new Uint32Array(array.length);
for (let i = 0; i < indices.length; i++) {
indices[i] = i;
}
// after sorting, `indices[i]` gives the index from where
// `array[i]` should take the value from, so to sort
// move the value at at `array[indices[i]]` to `array[i]`
return indices.sort(comparator);
}
// If I'm not mistaken this is O(2n) - each value is moved
// only once (not counting the vacancy temporaries), and
// we also walk through the whole array once more to check
// for each cycle.
function sortFromIndices(array, indices) {
// there might be multiple cycles, so we must
// walk through the whole array to check.
for (let k = 0; k < array.length; k++) {
// advance until we find a value in
// the "wrong" position
if (k !== indices[k]) {
// create vacancy to use "half-swaps" trick,
// props to Andrei Alexandrescu
let v0 = array[k];
let i = k;
let j = indices[k];
while (j !== k) {
// half-swap next value
array[i] = array[j];
// array[i] now contains the value it should have,
// so we update indices[i] to reflect this
indices[i] = i;
// go to next index
i = j;
j = indices[j];
}
// put original array[k] back in
// and update indices
array[i] = v0;
indices[i] = i;
}
}
return array;
}
I know this has been plenty answered. I just wanted to go ahead an post a quick TS implementation for anyone that landed here looking for that.
export function stableSort<T>( array: T[], compareFn: ( a: T, b: T ) => number ): T[] {
const indices = array.map( ( x: T, i: number ) => ( { element: x, index: i } ) );
return indices.sort( ( a, b ) => {
const order = compareFn( a.element, b.element );
return order === 0 ? a.index - b.index : order;
} ).map( x => x.element );
}
The method does no longer run in-place, as the native sort does. I also want to point out that it is not the most efficient. It adds two loops of the order O(n). though sort itself is most likely O(n log(n)) so it's less than that.
Some of the solutions mentioned are more performant, thought this might be less code, also using internal Array.prototype.sort.
(For a Javascript solution, just remove all the types)
According to the v8 dev blog and caniuse.com Array.sort is already stable as required by the spec in modern browsers, so you don't need to roll your own solution.
The only exception I can see is Edge, which should soon move over to chromium and support it as well.
function sort(data){
var result=[];
var array = data;
const array2=data;
const len=array2.length;
for(var i=0;i<=len-1;i++){
var min = Math.min.apply(Math,array)
result.push(min);
var index=array.indexOf(min)
array.splice(index,1);
}
return result;
}
sort([9,8,5,7,9,3,9,243,4,5,6,3,4,2,4,7,4,9,55,66,33,66]);
Counting Sort is faster than merge sort (it performs in O(n) time) and it is intended for use on integers.
Math.counting_sort = function (m) {
var i
var j
var k
var step
var start
var Output
var hash
k = m.length
Output = new Array ()
hash = new Array ()
// start at lowest possible value of m
start = 0
step = 1
// hash all values
i = 0
while ( i < k ) {
var _m = m[i]
hash [_m] = _m
i = i + 1
}
i = 0
j = start
// find all elements within x
while ( i < k ) {
while ( j != hash[j] ) {
j = j + step
}
Output [i] = j
i = i + 1
j = j + step
}
return Output
}
Example:
var uArray = new Array ()<br/>
var sArray = new Array ()<br/><br/>
uArray = [ 10,1,9,2,8,3,7,4,6,5 ]<br/>
sArray = Math.counting_sort ( uArray ) // returns a sorted array
var table =
[
{"name":"Joe", "Level":1},
{"name":"Bob", "Level":1B},
{"name":"Carl", "Level": 7},
{"name":"Mike", "Level": 1A},
{"name":"Sal", "Level": 2}
]
var tableHeaders = scope.SortTableColumnHeaders(table)
scope.SortTableColumnHeaders = function (array) {
var unique = {};
var distinct = [];
for (var i in array) {
if (typeof (unique[array[i].Level]) == "undefined") {
distinct.push(array[i].Level);
}
unique[array[i].Level] = 0;
}
return distinct.sort();
};
I have an object that looks like the above code where I pull out a distinct array like
[1,10,1A,1B,2,7]
where I can have int (1-10) and strings (1A, 1B, 1C)
I want to sort the int's in order first and then the strings second so it will always look like
[1...10,1A,1B,1C]
I tried distinct.sort(), but that doesn't work.
Well, ASCII values are continuous, and numbers are lower than chars from the alphabet, so if you can live with the sum of the ASCII values (that would work with 1A, 1B and so on)
var table = [
{"name":"Joe", "Level": '1'},
{"name":"Bob", "Level": '1B'},
{"name":"Carl", "Level": '7'},
...
]
function sortByCharCode(a,b) {
function charCodeInt(s) {
result = 0;
for (var i=0;i<s.length;i++) {
result += s.charCodeAt(i);
}
return result;
}
a = charCodeInt(a.Level);
b = charCodeInt(b.Level);
if (a < b) return -1;
if (a > b) return 1;
return 0;
}
$scope.sortTable = function() {
$scope.table.sort(sortByCharCode);
}
You can still use the sort method, passing a function to drive your criterias:
table.sort(function(a, b){
var compA = a.Level;
var compB = b.Level;
if(isNaN(compA) && isNaN(compB)){
return compA.localeCompare(compB);
} else if(isNaN(compA)){
return 1;
} else if(isNaN(compB)){
return -1;
} else {
return compA - compB;
}
});
On this sorting you will sort numbers between themselves and the same for strings (1A, 1B ...). When sorting both together, number comes first.
One way to do it would be to enumerate your sort explicitly:
var definedSort = [1,2,3,4,5,6,7,8,9,10,'1A','1B','1C'];
var someArray = ['1A', 4, 8, 5, 1, 9, '1C', '1B'];
someArray.sort(function(a,b) {
return definedSort.indexOf(a) - definedSort.indexOf(b);
});
alert(someArray);
However, given your simple restrictions, you can also implement it in more convoluted ways:
var someArray = ['1A', 4, 8, 5, 1, 9, '1C', '1B'];
someArray.sort(function(a,b) {
return (a - b) || -a || +b || (a > b)
});
alert(someArray);
I have the associative array:
array["sub2"] = 1;
array["sub0"] = -1;
array["sub1"] = 0;
array["sub3"] = 1;
array["sub4"] = 0;
What is the most elegant way to sort (descending) by its values where the result would be an array with the respective indices in this order:
sub2, sub3, sub1, sub4, sub0
Javascript doesn't have "associative arrays" the way you're thinking of them. Instead, you simply have the ability to set object properties using array-like syntax (as in your example), plus the ability to iterate over an object's properties.
The upshot of this is that there is no guarantee as to the order in which you iterate over the properties, so there is nothing like a sort for them. Instead, you'll need to convert your object properties into a "true" array (which does guarantee order). Here's a code snippet for converting an object into an array of two-tuples (two-element arrays), sorting it as you describe, then iterating over it:
var tuples = [];
for (var key in obj) tuples.push([key, obj[key]]);
tuples.sort(function(a, b) {
a = a[1];
b = b[1];
return a < b ? -1 : (a > b ? 1 : 0);
});
for (var i = 0; i < tuples.length; i++) {
var key = tuples[i][0];
var value = tuples[i][1];
// do something with key and value
}
You may find it more natural to wrap this in a function which takes a callback:
function bySortedValue(obj, callback, context) {
var tuples = [];
for (var key in obj) tuples.push([key, obj[key]]);
tuples.sort(function(a, b) {
return a[1] < b[1] ? 1 : a[1] > b[1] ? -1 : 0
});
var length = tuples.length;
while (length--) callback.call(context, tuples[length][0], tuples[length][1]);
}
bySortedValue({
foo: 1,
bar: 7,
baz: 3
}, function(key, value) {
document.getElementById('res').innerHTML += `${key}: ${value}<br>`
});
<p id='res'>Result:<br/><br/><p>
Instead of correcting you on the semantics of an 'associative array', I think this is what you want:
function getSortedKeys(obj) {
var keys = Object.keys(obj);
return keys.sort(function(a,b){return obj[b]-obj[a]});
}
for really old browsers, use this instead:
function getSortedKeys(obj) {
var keys = []; for(var key in obj) keys.push(key);
return keys.sort(function(a,b){return obj[b]-obj[a]});
}
You dump in an object (like yours) and get an array of the keys - eh properties - back, sorted descending by the (numerical) value of the, eh, values of the, eh, object.
This only works if your values are numerical. Tweek the little function(a,b) in there to change the sorting mechanism to work ascending, or work for string values (for example). Left as an exercise for the reader.
Continued discussion & other solutions covered at How to sort an (associative) array by value? with the best solution (for my case) being by saml (quoted below).
Arrays can only have numeric indexes. You'd need to rewrite this as either an Object, or an Array of Objects.
var status = new Array();
status.push({name: 'BOB', val: 10});
status.push({name: 'TOM', val: 3});
status.push({name: 'ROB', val: 22});
status.push({name: 'JON', val: 7});
If you like the status.push method, you can sort it with:
status.sort(function(a,b) {
return a.val - b.val;
});
There really isn't any such thing as an "associative array" in JavaScript. What you've got there is just a plain old object. They work kind-of like associative arrays, of course, and the keys are available but there's no semantics around the order of keys.
You could turn your object into an array of objects (key/value pairs) and sort that:
function sortObj(object, sortFunc) {
var rv = [];
for (var k in object) {
if (object.hasOwnProperty(k)) rv.push({key: k, value: object[k]});
}
rv.sort(function(o1, o2) {
return sortFunc(o1.key, o2.key);
});
return rv;
}
Then you'd call that with a comparator function.
The best approach for the specific case here, in my opinion, is the one commonpike suggested. A little improvement I'd suggest that works in modern browsers is:
// aao is the "associative array" you need to "sort"
Object.keys(aao).sort(function(a,b){return aao[b]-aao[a]});
This could apply easily and work great in the specific case here so you can do:
let aoo={};
aao["sub2"]=1;
aao["sub0"]=-1;
aao["sub1"]=0;
aao["sub3"]=1;
aao["sub4"]=0;
let sk=Object.keys(aao).sort(function(a,b){return aao[b]-aao[a]});
// now you can loop using the sorted keys in `sk` to do stuffs
for (let i=sk.length-1;i>=0;--i){
// do something with sk[i] or aoo[sk[i]]
}
Besides of this, I provide here a more "generic" function you can use to sort even in wider range of situations and that mixes the improvement I just suggested with the approaches of the answers by Ben Blank (sorting also string values) and PopeJohnPaulII (sorting by specific object field/property) and lets you decide if you want an ascendant or descendant order, here it is:
// aao := is the "associative array" you need to "sort"
// comp := is the "field" you want to compare or "" if you have no "fields" and simply need to compare values
// intVal := must be false if you need comparing non-integer values
// desc := set to true will sort keys in descendant order (default sort order is ascendant)
function sortedKeys(aao,comp="",intVal=false,desc=false){
let keys=Object.keys(aao);
if (comp!="") {
if (intVal) {
if (desc) return keys.sort(function(a,b){return aao[b][comp]-aao[a][comp]});
else return keys.sort(function(a,b){return aao[a][comp]-aao[a][comp]});
} else {
if (desc) return keys.sort(function(a,b){return aao[b][comp]<aao[a][comp]?1:aao[b][comp]>aao[a][comp]?-1:0});
else return keys.sort(function(a,b){return aao[a][comp]<aao[b][comp]?1:aao[a][comp]>aao[b][comp]?-1:0});
}
} else {
if (intVal) {
if (desc) return keys.sort(function(a,b){return aao[b]-aao[a]});
else return keys.sort(function(a,b){return aao[a]-aao[b]});
} else {
if (desc) return keys.sort(function(a,b){return aao[b]<aao[a]?1:aao[b]>aao[a]?-1:0});
else return keys.sort(function(a,b){return aao[a]<aao[b]?1:aao[a]>aao[b]?-1:0});
}
}
}
You can test the functionalities trying something like the following code:
let items={};
items['Edward']=21;
items['Sharpe']=37;
items['And']=45;
items['The']=-12;
items['Magnetic']=13;
items['Zeros']=37;
//equivalent to:
//let items={"Edward": 21, "Sharpe": 37, "And": 45, "The": -12, ...};
console.log("1: "+sortedKeys(items));
console.log("2: "+sortedKeys(items,"",false,true));
console.log("3: "+sortedKeys(items,"",true,false));
console.log("4: "+sortedKeys(items,"",true,true));
/* OUTPUT
1: And,Sharpe,Zeros,Edward,Magnetic,The
2: The,Magnetic,Edward,Sharpe,Zeros,And
3: The,Magnetic,Edward,Sharpe,Zeros,And
4: And,Sharpe,Zeros,Edward,Magnetic,The
*/
items={};
items['k1']={name:'Edward',value:21};
items['k2']={name:'Sharpe',value:37};
items['k3']={name:'And',value:45};
items['k4']={name:'The',value:-12};
items['k5']={name:'Magnetic',value:13};
items['k6']={name:'Zeros',value:37};
console.log("1: "+sortedKeys(items,"name"));
console.log("2: "+sortedKeys(items,"name",false,true));
/* OUTPUT
1: k6,k4,k2,k5,k1,k3
2: k3,k1,k5,k2,k4,k6
*/
As I already said, you can loop over sorted keys if you need doing stuffs
let sk=sortedKeys(aoo);
// now you can loop using the sorted keys in `sk` to do stuffs
for (let i=sk.length-1;i>=0;--i){
// do something with sk[i] or aoo[sk[i]]
}
Last, but not least, some useful references to Object.keys and Array.sort
Here is a variation of ben blank's answer, if you don't like tuples.
This saves you a few characters.
var keys = [];
for (var key in sortme) {
keys.push(key);
}
keys.sort(function(k0, k1) {
var a = sortme[k0];
var b = sortme[k1];
return a < b ? -1 : (a > b ? 1 : 0);
});
for (var i = 0; i < keys.length; ++i) {
var key = keys[i];
var value = sortme[key];
// Do something with key and value.
}
No unnecessary complication required...
function sortMapByValue(map)
{
var tupleArray = [];
for (var key in map) tupleArray.push([key, map[key]]);
tupleArray.sort(function (a, b) { return a[1] - b[1] });
return tupleArray;
}
i use $.each of jquery but you can make it with a for loop, an improvement is this:
//.ArraySort(array)
/* Sort an array
*/
ArraySort = function(array, sortFunc){
var tmp = [];
var aSorted=[];
var oSorted={};
for (var k in array) {
if (array.hasOwnProperty(k))
tmp.push({key: k, value: array[k]});
}
tmp.sort(function(o1, o2) {
return sortFunc(o1.value, o2.value);
});
if(Object.prototype.toString.call(array) === '[object Array]'){
$.each(tmp, function(index, value){
aSorted.push(value.value);
});
return aSorted;
}
if(Object.prototype.toString.call(array) === '[object Object]'){
$.each(tmp, function(index, value){
oSorted[value.key]=value.value;
});
return oSorted;
}
};
So now you can do
console.log("ArraySort");
var arr1 = [4,3,6,1,2,8,5,9,9];
var arr2 = {'a':4, 'b':3, 'c':6, 'd':1, 'e':2, 'f':8, 'g':5, 'h':9};
var arr3 = {a: 'green', b: 'brown', c: 'blue', d: 'red'};
var result1 = ArraySort(arr1, function(a,b){return a-b});
var result2 = ArraySort(arr2, function(a,b){return a-b});
var result3 = ArraySort(arr3, function(a,b){return a>b});
console.log(result1);
console.log(result2);
console.log(result3);
Just so it's out there and someone is looking for tuple based sorts.
This will compare the first element of the object in array, than the second element and so on. i.e in the example below, it will compare first by "a", then by "b" and so on.
let arr = [
{a:1, b:2, c:3},
{a:3, b:5, c:1},
{a:2, b:3, c:9},
{a:2, b:5, c:9},
{a:2, b:3, c:10}
]
function getSortedScore(obj) {
var keys = [];
for(var key in obj[0]) keys.push(key);
return obj.sort(function(a,b){
for (var i in keys) {
let k = keys[i];
if (a[k]-b[k] > 0) return -1;
else if (a[k]-b[k] < 0) return 1;
else continue;
};
});
}
console.log(getSortedScore(arr))
OUPUTS
[ { a: 3, b: 5, c: 1 },
{ a: 2, b: 5, c: 9 },
{ a: 2, b: 3, c: 10 },
{ a: 2, b: 3, c: 9 },
{ a: 1, b: 2, c: 3 } ]
A modern approuch to this:
Object.fromEntries(Object.entries(data).sort((a,b)=>b[1]-a[1]).slice(0,5))
P.S: I did an optional slice, you can remove it if you want.
#commonpike's answer is "the right one", but as he goes on to comment...
most browsers nowadays just support Object.keys()
Yeah.. Object.keys() is WAY better.
But what's even better? Duh, it's it in coffeescript!
sortedKeys = (x) -> Object.keys(x).sort (a,b) -> x[a] - x[b]
sortedKeys
'a' : 1
'b' : 3
'c' : 4
'd' : -1
[ 'd', 'a', 'b', 'c' ]
I have two arrays of JavaScript Objects that I'd like to compare to see if they are the same. The objects may not (and most likely will not) be in the same order in each array. Each array shouldn't have any more than 10 objects. I thought jQuery might have an elegant solution to this problem, but I wasn't able to find much online.
I know that a brute nested $.each(array, function(){}) solution could work, but is there any built in function that I'm not aware of?
Thanks.
There is an easy way...
$(arr1).not(arr2).length === 0 && $(arr2).not(arr1).length === 0
If the above returns true, both the arrays are same even if the elements are in different order.
NOTE: This works only for jquery versions < 3.0.0 when using JSON objects
I was also looking for this today and found:
http://www.breakingpar.com/bkp/home.nsf/0/87256B280015193F87256BFB0077DFFD
Don't know if that's a good solution though they do mention some performance considerations taken into account.
I like the idea of a jQuery helper method.
#David I'd rather see your compare method to work like:
jQuery.compare(a, b)
I doesn't make sense to me to be doing:
$(a).compare(b)
where a and b are arrays. Normally when you $(something) you'd be passing a selector string to work with DOM elements.
Also regarding sorting and 'caching' the sorted arrays:
I don't think sorting once at the start of the method instead of every time through the loop is 'caching'. The sort will still happen every time you call compare(b). That's just semantics, but...
for (var i = 0; t[i]; i++) { ...this loop finishes early if your t array contains a false value in it somewhere, so $([1, 2, 3, 4]).compare([1, false, 2, 3]) returns true!
More importantly the array sort() method sorts the array in place, so doing var b = t.sort() ...doesn't create a sorted copy of the original array, it sorts the original array and also assigns a reference to it in b. I don't think the compare method should have side-effects.
It seems what we need to do is to copy the arrays before working on them. The best answer I could find for how to do that in a jQuery way was by none other than John Resig here on SO! What is the most efficient way to deep clone an object in JavaScript? (see comments on his answer for the array version of the object cloning recipe)
In which case I think the code for it would be:
jQuery.extend({
compare: function (arrayA, arrayB) {
if (arrayA.length != arrayB.length) { return false; }
// sort modifies original array
// (which are passed by reference to our method!)
// so clone the arrays before sorting
var a = jQuery.extend(true, [], arrayA);
var b = jQuery.extend(true, [], arrayB);
a.sort();
b.sort();
for (var i = 0, l = a.length; i < l; i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}
});
var a = [1, 2, 3];
var b = [2, 3, 4];
var c = [3, 4, 2];
jQuery.compare(a, b);
// false
jQuery.compare(b, c);
// true
// c is still unsorted [3, 4, 2]
My approach was quite different - I flattened out both collections using JSON.stringify and used a normal string compare to check for equality.
I.e.
var arr1 = [
{Col: 'a', Val: 1},
{Col: 'b', Val: 2},
{Col: 'c', Val: 3}
];
var arr2 = [
{Col: 'x', Val: 24},
{Col: 'y', Val: 25},
{Col: 'z', Val: 26}
];
if(JSON.stringify(arr1) == JSON.stringify(arr2)){
alert('Collections are equal');
}else{
alert('Collections are not equal');
}
NB: Please note that his method assumes that both Collections are sorted in a similar fashion, if not, it would give you a false result!
Convert both array to string and compare
if (JSON.stringify(array1) == JSON.stringify(array2))
{
// your code here
}
I found this discussion because I needed a way to deep compare arrays and objects. Using the examples here, I came up with the following (broken up into 3 methods for clarity):
jQuery.extend({
compare : function (a,b) {
var obj_str = '[object Object]',
arr_str = '[object Array]',
a_type = Object.prototype.toString.apply(a),
b_type = Object.prototype.toString.apply(b);
if ( a_type !== b_type) { return false; }
else if (a_type === obj_str) {
return $.compareObject(a,b);
}
else if (a_type === arr_str) {
return $.compareArray(a,b);
}
return (a === b);
}
});
jQuery.extend({
compareArray: function (arrayA, arrayB) {
var a,b,i,a_type,b_type;
// References to each other?
if (arrayA === arrayB) { return true;}
if (arrayA.length != arrayB.length) { return false; }
// sort modifies original array
// (which are passed by reference to our method!)
// so clone the arrays before sorting
a = jQuery.extend(true, [], arrayA);
b = jQuery.extend(true, [], arrayB);
a.sort();
b.sort();
for (i = 0, l = a.length; i < l; i+=1) {
a_type = Object.prototype.toString.apply(a[i]);
b_type = Object.prototype.toString.apply(b[i]);
if (a_type !== b_type) {
return false;
}
if ($.compare(a[i],b[i]) === false) {
return false;
}
}
return true;
}
});
jQuery.extend({
compareObject : function(objA,objB) {
var i,a_type,b_type;
// Compare if they are references to each other
if (objA === objB) { return true;}
if (Object.keys(objA).length !== Object.keys(objB).length) { return false;}
for (i in objA) {
if (objA.hasOwnProperty(i)) {
if (typeof objB[i] === 'undefined') {
return false;
}
else {
a_type = Object.prototype.toString.apply(objA[i]);
b_type = Object.prototype.toString.apply(objB[i]);
if (a_type !== b_type) {
return false;
}
}
}
if ($.compare(objA[i],objB[i]) === false){
return false;
}
}
return true;
}
});
Testing
var a={a : {a : 1, b: 2}},
b={a : {a : 1, b: 2}},
c={a : {a : 1, b: 3}},
d=[1,2,3],
e=[2,1,3];
console.debug('a and b = ' + $.compare(a,b)); // a and b = true
console.debug('b and b = ' + $.compare(b,b)); // b and b = true
console.debug('b and c = ' + $.compare(b,c)); // b and c = false
console.debug('c and d = ' + $.compare(c,d)); // c and d = false
console.debug('d and e = ' + $.compare(d,e)); // d and e = true
In my case compared arrays contain only numbers and strings. This solution worked for me:
function are_arrs_equal(arr1, arr2){
return arr1.sort().toString() === arr2.sort().toString()
}
Let's test it!
arr1 = [1, 2, 3, 'nik']
arr2 = ['nik', 3, 1, 2]
arr3 = [1, 2, 5]
console.log (are_arrs_equal(arr1, arr2)) //true
console.log (are_arrs_equal(arr1, arr3)) //false
I don't think there's a good "jQuery " way to do this, but if you need efficiency, map one of the arrays by a certain key (one of the unique object fields), and then do comparison by looping through the other array and comparing against the map, or associative array, you just built.
If efficiency is not an issue, just compare every object in A to every object in B. As long as |A| and |B| are small, you should be okay.
Well, if you want to compare only the contents of arrays, there's a useful jQuery function $.inArray()
var arr = [11, "String #1", 14, "String #2"];
var arr_true = ["String #1", 14, "String #2", 11]; // contents are the same as arr
var arr_false = ["String #1", 14, "String #2", 16]; // contents differ
function test(arr_1, arr_2) {
var equal = arr_1.length == arr_2.length; // if array sizes mismatches, then we assume, that they are not equal
if (equal) {
$.each(arr_1, function (foo, val) {
if (!equal) return false;
if ($.inArray(val, arr_2) == -1) {
equal = false;
} else {
equal = true;
}
});
}
return equal;
}
alert('Array contents are the same? ' + test(arr, arr_true)); //- returns true
alert('Array contents are the same? ' + test(arr, arr_false)); //- returns false
Change array to string and compare
var arr = [1,2,3],
arr2 = [1,2,3];
console.log(arr.toString() === arr2.toString());
The nice one liner from Sudhakar R as jQuery global method.
/**
* Compare two arrays if they are equal even if they have different order.
*
* #link https://stackoverflow.com/a/7726509
*/
jQuery.extend({
/**
* #param {array} a
* First array to compare.
* #param {array} b
* Second array to compare.
* #return {boolean}
* True if both arrays are equal, otherwise false.
*/
arrayCompare: function (a, b) {
return $(a).not(b).get().length === 0 && $(b).not(a).get().length === 0;
}
});
I also found this when looking to do some array comparisons with jQuery. In my case I had strings which I knew to be arrays:
var needle = 'apple orange';
var haystack = 'kiwi orange banana apple plum';
But I cared if it was a complete match or only a partial match, so I used something like the following, based off of Sudhakar R's answer:
function compareStrings( needle, haystack ){
var needleArr = needle.split(" "),
haystackArr = haystack.split(" "),
compare = $(haystackArr).not(needleArr).get().length;
if( compare == 0 ){
return 'all';
} else if ( compare == haystackArr.length ) {
return 'none';
} else {
return 'partial';
}
}
If duplicates matter such that [1, 1, 2] should not be equal to [2, 1] but should equal [1, 2, 1], here is a reference counting solution:
const arrayContentsEqual = (arrayA, arrayB) => {
if (arrayA.length !== arrayB.length) {
return false}
const refCount = (function() {
const refCountMap = {};
const refCountFn = (elt, count) => {
refCountMap[elt] = (refCountMap[elt] || 0) + count}
refCountFn.isZero = () => {
for (let elt in refCountMap) {
if (refCountMap[elt] !== 0) {
return false}}
return true}
return refCountFn})()
arrayB.map(eltB => refCount(eltB, 1));
arrayA.map(eltA => refCount(eltA, -1));
return refCount.isZero()}
Here is the fiddle to play with.
var arr1 = [
{name: 'a', Val: 1},
{name: 'b', Val: 2},
{name: 'c', Val: 3}
];
var arr2 = [
{name: 'c', Val: 3},
{name: 'x', Val: 4},
{name: 'y', Val: 5},
{name: 'z', Val: 6}
];
var _isEqual = _.intersectionWith(arr1, arr2, _.isEqual);// common in both array
var _difference1 = _.differenceWith(arr1, arr2, _.isEqual);//difference from array1
var _difference2 = _.differenceWith(arr2, arr1, _.isEqual);//difference from array2
console.log(_isEqual);// common in both array
console.log(_difference1);//difference from array1
console.log(_difference2);//difference from array2
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.5/lodash.js"></script>
Try this
function check(c,d){
var a = c, b = d,flg = 0;
if(a.length == b.length)
{
for(var i=0;i<a.length;i++)
a[i] != b[i] ? flg++ : 0;
}
else
{
flg = 1;
}
return flg = 0;
}
Trying to get the highest and lowest value from an array that I know will contain only integers seems to be harder than I thought.
var numArray = [140000, 104, 99];
numArray = numArray.sort();
console.log(numArray)
I'd expect this to show 99, 104, 140000. Instead it shows 104, 140000, 99. So it seems the sort is handling the values as strings.
Is there a way to get the sort function to actually sort on integer value?
By default, the sort method sorts elements alphabetically. To sort numerically just add a new method which handles numeric sorts (sortNumber, shown below) -
var numArray = [140000, 104, 99];
numArray.sort(function(a, b) {
return a - b;
});
console.log(numArray);
Documentation:
Mozilla Array.prototype.sort() recommends this compare function for arrays that don't contain Infinity or NaN. (Because Infinity - Infinity is NaN, not 0).
Also examples of sorting objects by key.
Just building on all of the above answers, they can also be done in one line like this:
var numArray = [140000, 104, 99];
numArray = numArray.sort(function (a, b) { return a - b; });
//outputs: 99, 104, 140000
I am surprised why everyone recommends to pass a comparator function to sort(), that makes sorting really slow!
To sort numbers, just create any TypedArray:
var numArray = new Float64Array([140000, 104, 99]);
numArray = numArray.sort();
console.log(numArray)
array.sort does a lexicographic sort by default, for a numeric sort, provide your own function. Here's a simple example:
function compareNumbers(a, b)
{
return a - b;
}
numArray.sort(compareNumbers);
Also note that sort works "in place", there's no need for the assignment.
This answer is equivalent to some of the existing answers, but ECMAScript 6 arrow functions provide a much more compact syntax that allows us to define an inline sort function without sacrificing readability:
numArray = numArray.sort((a, b) => a - b);
It is supported in most browsers today.
The reason why the sort function behaves so weird
From the documentation:
[...] the array is sorted according to each character's Unicode code point
value, according to the string conversion of each element.
If you print the unicode point values of the array then it will get clear.
console.log("140000".charCodeAt(0));
console.log("104".charCodeAt(0));
console.log("99".charCodeAt(0));
//Note that we only look at the first index of the number "charCodeAt( 0 )"
This returns: "49, 49, 57".
49 (unicode value of first number at 140000)
49 (unicode value of first number at 104)
57 (unicode value of first number at 99)
Now, because 140000 and 104 returned the same values (49) it cuts the first index and checks again:
console.log("40000".charCodeAt(0));
console.log("04".charCodeAt(0));
//Note that we only look at the first index of the number "charCodeAt( 0 )"
52 (unicode value of first number at 40000)
40 (unicode value of first number at 04)
If we sort this, then we will get:
40 (unicode value of first number at 04)
52 (unicode value of first number at 40000)
so 104 comes before 140000.
So the final result will be:
var numArray = [140000, 104, 99];
numArray = numArray.sort();
console.log(numArray)
104, 140000, 99
Conclusion:
sort() does sorting by only looking at the first index of the numbers. sort() does not care if a whole number is bigger than another, it compares the value of the unicode of the digits, and if there are two equal unicode values, then it checks if there is a next digit and compares it as well.
To sort correctly, you have to pass a compare function to sort() like explained here.
just do .sort((a, b) => a - b) instead of .sort() itself. In addition to that the array is sorted in place. So return value does not matter.
var numArray = [140000, 104, 99];
numArray.sort((a, b) => a - b);
console.log(numArray)
Ascending
arr.sort((a, b) => a - b);
Descending
arr.sort((a, b) => b - a);
Just for fun:
Descending = Ascending + Reverse
arr.sort((a, b) => a - b).reverse();
I agree with aks, however instead of using
return a - b;
You should use
return a > b ? 1 : a < b ? -1 : 0;
The question has already been answered, the shortest way is to use sort() method. But if you're searching for more ways to sort your array of numbers, and you also love cycles, check the following
Insertion sort
Ascending:
var numArray = [140000, 104, 99];
for (var i = 0; i < numArray.length; i++) {
var target = numArray[i];
for (var j = i - 1; j >= 0 && (numArray[j] > target); j--) {
numArray[j+1] = numArray[j];
}
numArray[j+1] = target
}
console.log(numArray);
Descending:
var numArray = [140000, 104, 99];
for (var i = 0; i < numArray.length; i++) {
var target = numArray[i];
for (var j = i - 1; j >= 0 && (numArray[j] < target); j--) {
numArray[j+1] = numArray[j];
}
numArray[j+1] = target
}
console.log(numArray);
Selection sort:
Ascending:
var numArray = [140000, 104, 99];
for (var i = 0; i < numArray.length - 1; i++) {
var min = i;
for (var j = i + 1; j < numArray.length; j++) {
if (numArray[j] < numArray[min]) {
min = j;
}
}
if (min != i) {
var target = numArray[i];
numArray[i] = numArray[min];
numArray[min] = target;
}
}
console.log(numArray);
Descending:
var numArray = [140000, 104, 99];
for (var i = 0; i < numArray.length - 1; i++) {
var min = i;
for (var j = i + 1; j < numArray.length; j++) {
if (numArray[j] > numArray[min]) {
min = j;
}
}
if (min != i) {
var target = numArray[i];
numArray[i] = numArray[min];
numArray[min] = target;
}
}
console.log(numArray);
Have fun
In JavaScript the sort() method's default behaviour is to sort values in an array alphabetically.
To sort by number you have to define a numeric sort function (which is very easy):
...
function sortNumber(a, b)
{
return a - b;
}
numArray = numArray.sort(sortNumber);
Array.sort uses alphabetic sorting by default instead of numeric .
To support numbers, add like following
var numArray = [140000, 104, 99];
numArray.sort((a, b) => a - b); // <-- Ascending
numArray.sort((a, b) => b - a); // <-- Descending
console.log(numArray);
OUTPUT :
Array.prototype.sort() is the go to method for sorting arrays, but there are a couple of issues we need to be aware of.
The sorting order is by default lexicographic and not numeric regardless of the types of values in the array. Even if the array is all numbers, all values will be converted to string and sorted lexicographically.
So should we need to customize the sort() and reverse() method like below.
Referred URL
For sorting numbers inside the array
numArray.sort(function(a, b)
{
return a - b;
});
For reversing numbers inside the array
numArray.sort(function(a, b)
{
return b - a;
});
Referred URL
to handle undefined, null, and NaN: Null behaves like 0, NaN and undefined goes to end.
array = [3, 5, -1, 1, NaN, 6, undefined, 2, null]
array.sort((a,b) => isNaN(a) || a-b)
// [-1, null, 1, 2, 3, 5, 6, NaN, undefined]
The function 'numerically' below serves the purpose of sorting array of numbers numerically in many cases when provided as a callback function:
function numerically(a, b){
return a-b;
}
array.sort(numerically);
But in some rare instances, where array contains very large and negative numbers, an overflow error can occur as the result of a-b gets smaller than the smallest number that JavaScript can cope with.
So a better way of writing numerically function is as follows:
function numerically(a, b){
if(a < b){
return -1;
} else if(a > b){
return 1;
} else {
return 0;
}
}
While not required in JavaScript, if you would like the sort() compareFunction to strictly return -1, 0, or 1 (similar to how the spaceship operator works in PHP), then you can use Math.sign().
The compareFunction below strictly returns -1, 0, or 1:
numArray.sort((a, b) => Math.sign(a - b));
Note: Math.sign() is not supported in Internet Explorer.
The accepted answer and equivalents like numArray.sort((a,b) => a - b) are great when the array contains only numbers without infinities or NaN. They can be extended to handle infinities and NaN like so:
numArray.sort((a,b) => (+a || 0) - (+b || 0) || 0);
This sorts NaN (or any non-number, like 'foo' or {}) as if it were 0. The final || 0 is needed to handle the case where a and b are equal infinities.
For a normal array of elements values only:
function sortArrayOfElements(arrayToSort) {
function compareElements(a, b) {
if (a < b)
return -1;
if (a > b)
return 1;
return 0;
}
return arrayToSort.sort(compareElements);
}
e.g. 1:
var array1 = [1,2,545,676,64,2,24]
**output : [1, 2, 2, 24, 64, 545, 676]**
var array2 = ["v","a",545,676,64,2,"24"]
**output: ["a", "v", 2, "24", 64, 545, 676]**
For an array of objects:
function sortArrayOfObjects(arrayToSort, key) {
function compareObjects(a, b) {
if (a[key] < b[key])
return -1;
if (a[key] > b[key])
return 1;
return 0;
}
return arrayToSort.sort(compareObjects);
}
e.g. 1: var array1= [{"name": "User4", "value": 4},{"name": "User3", "value": 3},{"name": "User2", "value": 2}]
**output : [{"name": "User2", "value": 2},{"name": "User3", "value": 3},{"name": "User4", "value": 4}]**
Update! Scroll to bottom of answer for smartSort prop additive that gives even more fun!Sorts arrays of anything!
My personal favorite form of this function allows for a param for Ascending, or Descending:
function intArraySort(c, a) {
function d(a, b) { return b - a; }
"string" == typeof a && a.toLowerCase();
switch (a) {
default: return c.sort(function(a, b) { return a - b; });
case 1:
case "d":
case "dc":
case "desc":
return c.sort(d)
}
};
Usage as simple as:
var ara = function getArray() {
var a = Math.floor(Math.random()*50)+1, b = [];
for (i=0;i<=a;i++) b.push(Math.floor(Math.random()*50)+1);
return b;
}();
// Ascending
intArraySort(ara);
console.log(ara);
// Descending
intArraySort(ara, 1);
console.log(ara);
// Ascending
intArraySort(ara, 'a');
console.log(ara);
// Descending
intArraySort(ara, 'dc');
console.log(ara);
// Ascending
intArraySort(ara, 'asc');
console.log(ara);
jsFiddle
Or Code Snippet Example Here!
function intArraySort(c, a) {
function d(a, b) { return b - a }
"string" == typeof a && a.toLowerCase();
switch (a) {
default: return c.sort(function(a, b) { return a - b });
case 1:
case "d":
case "dc":
case "desc":
return c.sort(d)
}
};
function tableExample() {
var d = function() {
var a = Math.floor(50 * Math.random()) + 1,
b = [];
for (i = 0; i <= a; i++) b.push(Math.floor(50 * Math.random()) + 1);
return b
},
a = function(a) {
var b = $("<tr/>"),
c = $("<th/>").prependTo(b);
$("<td/>", {
text: intArraySort(d(), a).join(", ")
}).appendTo(b);
switch (a) {
case 1:
case "d":
case "dc":
case "desc":
c.addClass("desc").text("Descending");
break;
default:
c.addClass("asc").text("Ascending")
}
return b
};
return $("tbody").empty().append(a(), a(1), a(), a(1), a(), a(1), a(), a(1), a(), a(1), a(), a(1))
};
tableExample();
table { border-collapse: collapse; }
th, td { border: 1px solid; padding: .25em .5em; vertical-align: top; }
.asc { color: red; }
.desc { color: blue }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<table><tbody></tbody></table>
.smartSort('asc' | 'desc')
Now have even more fun with a sorting method that sorts an array full of multiple items! Doesn't currently cover "associative" (aka, string keys), but it does cover about every type of value! Not only will it sort the multiple values asc or desc accordingly, but it will also maintain constant "position" of "groups" of values. In other words; ints are always first, then come strings, then arrays (yes, i'm making this multidimensional!), then Objects (unfiltered, element, date), & finally undefineds and nulls!
"Why?" you ask. Why not!
Now comes in 2 flavors! The first of which requires newer browsers as it uses Object.defineProperty to add the method to the Array.protoype Object. This allows for ease of natural use, such as: myArray.smartSort('a'). If you need to implement for older browsers, or you simply don't like modifying native Objects, scroll down to Method Only version.
/* begin */
/* KEY NOTE! Requires EcmaScript 5.1 (not compatible with older browsers) */
;;(function(){if(Object.defineProperty&&!Array.prototype.smartSort){var h=function(a,b){if(null==a||void 0==a)return 1;if(null==b||void 0==b)return-1;var c=typeof a,e=c+typeof b;if(/^numbernumber$/ig.test(e))return a-b;if(/^stringstring$/ig.test(e))return a>b;if(/(string|number){2}/ig.test(e))return/string/i.test(c)?1:-1;if(/number/ig.test(e)&&/object/ig.test(e)||/string/ig.test(e)&&/object/ig.test(e))return/object/i.test(c)?1:-1;if(/^objectobject$/ig.test(e)){a instanceof Array&&a.smartSort("a");b instanceof Array&&b.smartSort("a");if(a instanceof Date&&b instanceof Date)return a-b;if(a instanceof Array&&b instanceof Array){var e=Object.keys(a),g=Object.keys(b),e=e.concat(g).smartSort("a"),d;for(d in e)if(c=e[d],a[c]!=b[c])return d=[a[c],b[c]].smartSort("a"),a[c]==d[0]?-1:1;var f=[a[Object.keys(a)[0]],b[Object.keys(b)[0]]].smartSort("a");return a[Object.keys(a)[0]]==f[0]?-1:1}if(a instanceof Element&&b instanceof Element){if(a.tagName==b.tagName)return e=[a.id,b.id].smartSort("a"),a.id==e[0]?1:-1;e=[a.tagName, b.tagName].smartSort("a");return a.tagName==e[0]?1:-1}if(a instanceof Date||b instanceof Date)return a instanceof Date?1:-1;if(a instanceof Array||b instanceof Array)return a instanceof Array?-1:1;e=Object.keys(a);g=Object.keys(b);e.concat(g).smartSort("a");for(c=0;20>c;c++){d=e[c];f=g[c];if(a.hasOwnProperty(d)&&b.hasOwnProperty(f)){if(a[d]instanceof Element&&b[f]instanceof Element){if(a[d].tagName==b[f].tagName)return c=[a[d].id,b[f].id].smartSort("a"),a[d].id==c[0]?-1:1;c=[a[d].tagName,b[f].tagName].smartSort("d"); return a[d].tagName==c[0]?1:-1}if(a[d]instanceof Element||b[f]instanceof Element)return a[d]instanceof Element?1:-1;if(a[d]!=b[f])return c=[a[d],b[f]].smartSort("a"),a[d]==c[0]?-1:1}if(a.hasOwnProperty(d)&&a[d]instanceof Element)return 1;if(b.hasOwnProperty(f)&&b[f]instanceof Element||!a.hasOwnProperty(d))return-1;if(!b.hasOwnProperty(d))return 1}c=[a[Object.keys(a)[0]],b[Object.keys(b)[0]]].smartSort("d");return a[Object.keys(a)[0]]==c[0]?-1:1}g=[a,b].sort();return g[0]>g[1]},k=function(a,b){if(null== a||void 0==a)return 1;if(null==b||void 0==b)return-1;var c=typeof a,e=c+typeof b;if(/^numbernumber$/ig.test(e))return b-a;if(/^stringstring$/ig.test(e))return b>a;if(/(string|number){2}/ig.test(e))return/string/i.test(c)?1:-1;if(/number/ig.test(e)&&/object/ig.test(e)||/string/ig.test(e)&&/object/ig.test(e))return/object/i.test(c)?1:-1;if(/^objectobject$/ig.test(e)){a instanceof Array&&a.smartSort("d");b instanceof Array&&b.smartSort("d");if(a instanceof Date&&b instanceof Date)return b-a;if(a instanceof Array&&b instanceof Array){var e=Object.keys(a),g=Object.keys(b),e=e.concat(g).smartSort("a"),d;for(d in e)if(c=e[d],a[c]!=b[c])return d=[a[c],b[c]].smartSort("d"),a[c]==d[0]?-1:1;var f=[a[Object.keys(a)[0]],b[Object.keys(b)[0]]].smartSort("d");return a[Object.keys(a)[0]]==f[0]?-1:1}if(a instanceof Element&&b instanceof Element){if(a.tagName==b.tagName)return e=[a.id,b.id].smartSort("d"),a.id==e[0]?-1:1;e=[a.tagName,b.tagName].smartSort("d");return a.tagName==e[0]?-1:1}if(a instanceof Date||b instanceof Date)return a instanceof Date?1:-1;if(a instanceof Array||b instanceof Array)return a instanceof Array?-1:1;e=Object.keys(a);g=Object.keys(b);e.concat(g).smartSort("a");for(c=0;20>c;c++){d=e[c];f=g[c];if(a.hasOwnProperty(d)&&b.hasOwnProperty(f)){if(a[d]instanceof Element&&b[f]instanceof Element){if(a[d].tagName==b[f].tagName)return c=[a[d].id,b[f].id].smartSort("d"),a[d].id==c[0]?-1:1;c=[a[d].tagName,b[f].tagName].smartSort("d");return a[d].tagName==c[0]?-1:1}if(a[d]instanceof Element||b[f]instanceof Element)return a[d]instanceof Element?1:-1;if(a[d]!=b[f])return c=[a[d],b[f]].smartSort("d"),a[d]==c[0]?-1:1}if(a.hasOwnProperty(d)&&a[d]instanceof Element)return 1;if(b.hasOwnProperty(f)&&b[f]instanceof Element)return-1;if(!a.hasOwnProperty(d))return 1;if(!b.hasOwnProperty(d))return-1}c=[a[Object.keys(a)[0]],b[Object.keys(b)[0]]].smartSort("d");return a[Object.keys(a)[0]]==c[0]?-1:1}g=[a,b].sort();return g[0]<g[1]};Object.defineProperty(Array.prototype,"smartSort",{value:function(){return arguments&& (!arguments.length||1==arguments.length&&/^a([sc]{2})?$|^d([esc]{3})?$/i.test(arguments[0]))?this.sort(!arguments.length||/^a([sc]{2})?$/i.test(arguments[0])?h:k):this.sort()}})}})();
/* end */
jsFiddle Array.prototype.smartSort('asc|desc')
Use is simple! First make some crazy array like:
window.z = [ 'one', undefined, $('<span />'), 'two', null, 2, $('<div />', { id: 'Thing' }), $('<div />'), 4, $('<header />') ];
z.push(new Date('1/01/2011'));
z.push('three');
z.push(undefined);
z.push([ 'one', 'three', 'four' ]);
z.push([ 'one', 'three', 'five' ]);
z.push({ a: 'a', b: 'b' });
z.push({ name: 'bob', value: 'bill' });
z.push(new Date());
z.push({ john: 'jill', jack: 'june' });
z.push([ 'abc', 'def', [ 'abc', 'def', 'cba' ], [ 'cba', 'def', 'bca' ], 'cba' ]);
z.push([ 'cba', 'def', 'bca' ]);
z.push({ a: 'a', b: 'b', c: 'c' });
z.push({ a: 'a', b: 'b', c: 'd' });
Then simply sort it!
z.smartSort('asc'); // Ascending
z.smartSort('desc'); // Descending
Method Only
Same as the preceding, except as just a simple method!
/* begin */
/* KEY NOTE! Method `smartSort` is appended to native `window` for global use. If you'd prefer a more local scope, simple change `window.smartSort` to `var smartSort` and place inside your class/method */
window.smartSort=function(){if(arguments){var a,b,c;for(c in arguments)arguments[c]instanceof Array&&(a=arguments[c],void 0==b&&(b="a")),"string"==typeof arguments[c]&&(b=/^a([sc]{2})?$/i.test(arguments[c])?"a":"d");if(a instanceof Array)return a.sort("a"==b?smartSort.asc:smartSort.desc)}return this.sort()};smartSort.asc=function(a,b){if(null==a||void 0==a)return 1;if(null==b||void 0==b)return-1;var c=typeof a,e=c+typeof b;if(/^numbernumber$/ig.test(e))return a-b;if(/^stringstring$/ig.test(e))return a> b;if(/(string|number){2}/ig.test(e))return/string/i.test(c)?1:-1;if(/number/ig.test(e)&&/object/ig.test(e)||/string/ig.test(e)&&/object/ig.test(e))return/object/i.test(c)?1:-1;if(/^objectobject$/ig.test(e)){a instanceof Array&&a.sort(smartSort.asc);b instanceof Array&&b.sort(smartSort.asc);if(a instanceof Date&&b instanceof Date)return a-b;if(a instanceof Array&&b instanceof Array){var e=Object.keys(a),g=Object.keys(b),e=smartSort(e.concat(g),"a"),d;for(d in e)if(c=e[d],a[c]!=b[c])return d=smartSort([a[c], b[c]],"a"),a[c]==d[0]?-1:1;var f=smartSort([a[Object.keys(a)[0]],b[Object.keys(b)[0]]],"a");return a[Object.keys(a)[0]]==f[0]?-1:1}if(a instanceof Element&&b instanceof Element){if(a.tagName==b.tagName)return e=smartSort([a.id,b.id],"a"),a.id==e[0]?1:-1;e=smartSort([a.tagName,b.tagName],"a");return a.tagName==e[0]?1:-1}if(a instanceof Date||b instanceof Date)return a instanceof Date?1:-1;if(a instanceof Array||b instanceof Array)return a instanceof Array?-1:1;e=Object.keys(a);g=Object.keys(b);smartSort(e.concat(g), "a");for(c=0;20>c;c++){d=e[c];f=g[c];if(a.hasOwnProperty(d)&&b.hasOwnProperty(f)){if(a[d]instanceof Element&&b[f]instanceof Element){if(a[d].tagName==b[f].tagName)return c=smartSort([a[d].id,b[f].id],"a"),a[d].id==c[0]?-1:1;c=smartSort([a[d].tagName,b[f].tagName],"a");return a[d].tagName==c[0]?-1:1}if(a[d]instanceof Element||b[f]instanceof Element)return a[d]instanceof Element?1:-1;if(a[d]!=b[f])return c=smartSort([a[d],b[f]],"a"),a[d]==c[0]?-1:1}if(a.hasOwnProperty(d)&&a[d]instanceof Element)return 1; if(b.hasOwnProperty(f)&&b[f]instanceof Element||!a.hasOwnProperty(d))return-1;if(!b.hasOwnProperty(d))return 1}c=smartSort([a[Object.keys(a)[0]],b[Object.keys(b)[0]]],"a");return a[Object.keys(a)[0]]==c[0]?1:-1}g=[a,b].sort();return g[0]>g[1]};smartSort.desc=function(a,b){if(null==a||void 0==a)return 1;if(null==b||void 0==b)return-1;var c=typeof a,e=c+typeof b;if(/^numbernumber$/ig.test(e))return b-a;if(/^stringstring$/ig.test(e))return b>a;if(/(string|number){2}/ig.test(e))return/string/i.test(c)? 1:-1;if(/number/ig.test(e)&&/object/ig.test(e)||/string/ig.test(e)&&/object/ig.test(e))return/object/i.test(c)?1:-1;if(/^objectobject$/ig.test(e)){a instanceof Array&&a.sort(smartSort.desc);b instanceof Array&&b.sort(smartSort.desc);if(a instanceof Date&&b instanceof Date)return b-a;if(a instanceof Array&&b instanceof Array){var e=Object.keys(a),g=Object.keys(b),e=smartSort(e.concat(g),"a"),d;for(d in e)if(c=e[d],a[c]!=b[c])return d=smartSort([a[c],b[c]],"d"),a[c]==d[0]?-1:1;var f=smartSort([a[Object.keys(a)[0]], b[Object.keys(b)[0]]],"d");return a[Object.keys(a)[0]]==f[0]?-1:1}if(a instanceof Element&&b instanceof Element){if(a.tagName==b.tagName)return e=smartSort([a.id,b.id],"d"),a.id==e[0]?-1:1;e=smartSort([a.tagName,b.tagName],"d");return a.tagName==e[0]?-1:1}if(a instanceof Date||b instanceof Date)return a instanceof Date?1:-1;if(a instanceof Array||b instanceof Array)return a instanceof Array?-1:1;e=Object.keys(a);g=Object.keys(b);smartSort(e.concat(g),"a");for(c=0;20>c;c++){d=e[c];f=g[c];if(a.hasOwnProperty(d)&& b.hasOwnProperty(f)){if(a[d]instanceof Element&&b[f]instanceof Element){if(a[d].tagName==b[f].tagName)return c=smartSort([a[d].id,b[f].id],"d"),a[d].id==c[0]?-1:1;c=smartSort([a[d].tagName,b[f].tagName],"d");return a[d].tagName==c[0]?-1:1}if(a[d]instanceof Element||b[f]instanceof Element)return a[d]instanceof Element?1:-1;if(a[d]!=b[f])return c=smartSort([a[d],b[f]],"d"),a[d]==c[0]?-1:1}if(a.hasOwnProperty(d)&&a[d]instanceof Element)return 1;if(b.hasOwnProperty(f)&&b[f]instanceof Element)return-1; if(!a.hasOwnProperty(d))return 1;if(!b.hasOwnProperty(d))return-1}c=smartSort([a[Object.keys(a)[0]],b[Object.keys(b)[0]]],"d");return a[Object.keys(a)[0]]==c[0]?-1:1}g=[a,b].sort();return g[0]<g[1]}
/* end */
Use:
z = smartSort(z, 'asc'); // Ascending
z = smartSort(z, 'desc'); // Descending
jsFiddle Method smartSort(Array, "asc|desc")
Try this code:
HTML:
<div id="demo"></div>
JavaScript code:
<script>
(function(){
var points = [40, 100, 1, 5, 25, 10];
document.getElementById("demo").innerHTML = points;
points.sort(function(a, b){return a-b});
document.getElementById("demo").innerHTML = points;
})();
</script>
Try this code as below
var a = [5, 17, 29, 48, 64, 21];
function sortA(arr) {
return arr.sort(function(a, b) {
return a - b;
})
;}
alert(sortA(a));
TypeScript variant
const compareNumbers = (a: number, b: number): number => a - b
myArray.sort(compareNumbers)
In order to create this kind of sort, you have to pass a function that will check which comes first.
define inside the function which value do you wanna check: a.id - a.id
const myJson = [
{ id: 1, name: 'one'},
{ id: 4, name: 'four'},
{ id: 2, name: 'two'},
{ id: 3, name: 'three'}
];
// provide the sort method to check
const myNewSort = myJson.sort(function(a, b) {
return a.id - b.id;
});
console.log('my new sort',myNewSort)
You can sort number array simply by
const num=[13,17,14,19,16];
let temp;
for(let i=0;i<num.length;i++){
for(let j=i+1;j<num.length;j++){
if(num[i]>num[j]){
temp=num[i]
num[i]=num[j]
num[j]=temp
}
}
}
console.log(num);
let grade =[80,100,50,90,40];
grade.sort((x,y)=> x-y);
grade.forEach(element=>console.log(element));
Sort integers > 0, think outside the box:
function sortArray(arr) {
return new Promise((resolve) => {
const result = []
arr.forEach((item) => {
setTimeout(() => {
result.push(item)
if (result.length === arr.length) resolve(result)
}, item)
})
})
}
sortArray([4, 2, 42, 128, 56, 2]).then((result) => {
document.write(JSON.stringify(result))
})
Note that this should not be used productively, .sort() is better suited for this, check the other answers
sort_mixed
Object.defineProperty(Array.prototype,"sort_mixed",{
value: function () { // do not use arrow function
var N = [], L = [];
this.forEach(e => {
Number.isFinite(e) ? N.push(e) : L.push(e);
});
N.sort((a, b) => a - b);
L.sort();
[...N, ...L].forEach((v, i) => this[i] = v);
return this;
})
try a =[1,'u',"V",10,4,"c","A"].sort_mixed(); console.log(a)
If anyone doesn't understand how Array.sort() works with integers, read this answer.
Alphabetical order:
By default, the sort() method sorts the values as strings in alphabetical and ascending order.
const myArray = [104, 140000, 99];
myArray.sort();
console.log(myArray); // output is [104, 140000, 99]
Ascending order with array.sort(compareFunction):
const myArray = [104, 140000, 99];
myArray.sort(function(a, b){
return a - b;
});
console.log(myArray); // output is [99, 104, 140000]
Explanation from w3schools:
compareFunction defines an alternative sort order. The function should return a negative, zero, or positive value, depending on the arguments, like:
function(a, b){return a-b}
When the sort() method compares two values, it sends the values to the compare function, and sorts the values according to the returned (negative, zero, positive) value.
Example:
When comparing 40 and 100, the sort() method calls the compare
function(40,100).
The function calculates 40-100, and returns -60 (a negative value).
The sort function will sort 40 as a value lower than 100.
Descending order with array.sort(compareFunction):
const myArray = [104, 140000, 99];
myArray.sort(function(a, b){
return b - a;
});
console.log(myArray); // output is [140000, 104, 99]
This time we calculated with b - a(i.e., 100-40) which returns a positive value.
You can get height and lowest number simply by using max() and min() in-built function
var numArray = [140000, 104, 99];
console.log(Math.max(...numArray));
console.log(Math.min(...numArray));
If you want to sort in ascending or descending order
numArray.sort((a, b)=> a - b);
Know more
If you need to calculate and sort the largest charCodeAt from a list of string this is the right way.
const arrayLines = '1.1.1.1\n1.0.1.1\n1.1.1.2\n1.1.1.0'.split('\n');
// Response: (4) ['1.0.1.1', '1.1.1.0', '1.1.1.1', '1.1.1.2']
arrayLines.sort((a, b) => {
let a_charCodeSize = 0,
b_charCodeSize = 0;
// Loop true a & b characters and calculate the charCodeAt size.
for (const aChar of a) a_charCodeSize += aChar.charCodeAt(0);
for (const bChar of b) b_charCodeSize += bChar.charCodeAt(0);
return a_charCodeSize - b_charCodeSize;
});