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
I have a multidimensional array. The primary array is an array of
[publicationID][publication_name][ownderID][owner_name]
What I am trying to do is sort the array by owner_name and then by publication_name. I know in JavaScript you have Array.sort(), into which you can put a custom function, in my case i have:
function mysortfunction(a, b) {
var x = a[3].toLowerCase();
var y = b[3].toLowerCase();
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
}
This is fine for just sorting on the one column, namely owner_name, but how do I modify it to sort on owner_name, then publication_name?
If owner names differ, sort by them. Otherwise, use publication name for tiebreaker.
function mysortfunction(a, b) {
var o1 = a[3].toLowerCase();
var o2 = b[3].toLowerCase();
var p1 = a[1].toLowerCase();
var p2 = b[1].toLowerCase();
if (o1 < o2) return -1;
if (o1 > o2) return 1;
if (p1 < p2) return -1;
if (p1 > p2) return 1;
return 0;
}
I think what you're looking for is thenBy.js: https://github.com/Teun/thenBy.js
It allows you to use the standard Array.sort, but with firstBy().thenBy().thenBy() style.
An example can be seen here.
A good way to sort on many fields that are strings is to use toLocaleCompare and the boolean operator ||.
Something like:
// Sorting record releases by name and then by title.
releases.sort((oldRelease, newRelease) => {
const compareName = oldRelease.name.localeCompare(newRelease.name);
const compareTitle = oldRelease.title.localeCompare(newRelease.title);
return compareName || compareTitle;
})
If you wanted to sort on more fields, you could simply chain them off the return statement with more boolean operators.
Came across a need to do SQL-style mixed asc and desc object array sorts by keys.
kennebec's solution above helped me get to this:
Array.prototype.keySort = function(keys) {
keys = keys || {};
// via
// https://stackoverflow.com/questions/5223/length-of-javascript-object-ie-associative-array
var obLen = function(obj) {
var size = 0, key;
for (key in obj) {
if (obj.hasOwnProperty(key))
size++;
}
return size;
};
// avoiding using Object.keys because I guess did it have IE8 issues?
// else var obIx = function(obj, ix){ return Object.keys(obj)[ix]; } or
// whatever
var obIx = function(obj, ix) {
var size = 0, key;
for (key in obj) {
if (obj.hasOwnProperty(key)) {
if (size == ix)
return key;
size++;
}
}
return false;
};
var keySort = function(a, b, d) {
d = d !== null ? d : 1;
// a = a.toLowerCase(); // this breaks numbers
// b = b.toLowerCase();
if (a == b)
return 0;
return a > b ? 1 * d : -1 * d;
};
var KL = obLen(keys);
if (!KL)
return this.sort(keySort);
for ( var k in keys) {
// asc unless desc or skip
keys[k] =
keys[k] == 'desc' || keys[k] == -1 ? -1
: (keys[k] == 'skip' || keys[k] === 0 ? 0
: 1);
}
this.sort(function(a, b) {
var sorted = 0, ix = 0;
while (sorted === 0 && ix < KL) {
var k = obIx(keys, ix);
if (k) {
var dir = keys[k];
sorted = keySort(a[k], b[k], dir);
ix++;
}
}
return sorted;
});
return this;
};
sample usage:
var obja = [
{USER:"bob", SCORE:2000, TIME:32, AGE:16, COUNTRY:"US"},
{USER:"jane", SCORE:4000, TIME:35, AGE:16, COUNTRY:"DE"},
{USER:"tim", SCORE:1000, TIME:30, AGE:17, COUNTRY:"UK"},
{USER:"mary", SCORE:1500, TIME:31, AGE:19, COUNTRY:"PL"},
{USER:"joe", SCORE:2500, TIME:33, AGE:18, COUNTRY:"US"},
{USER:"sally", SCORE:2000, TIME:30, AGE:16, COUNTRY:"CA"},
{USER:"yuri", SCORE:3000, TIME:34, AGE:19, COUNTRY:"RU"},
{USER:"anita", SCORE:2500, TIME:32, AGE:17, COUNTRY:"LV"},
{USER:"mark", SCORE:2000, TIME:30, AGE:18, COUNTRY:"DE"},
{USER:"amy", SCORE:1500, TIME:29, AGE:19, COUNTRY:"UK"}
];
var sorto = {
SCORE:"desc",TIME:"asc", AGE:"asc"
};
obja.keySort(sorto);
yields the following:
0: { USER: jane; SCORE: 4000; TIME: 35; AGE: 16; COUNTRY: DE; }
1: { USER: yuri; SCORE: 3000; TIME: 34; AGE: 19; COUNTRY: RU; }
2: { USER: anita; SCORE: 2500; TIME: 32; AGE: 17; COUNTRY: LV; }
3: { USER: joe; SCORE: 2500; TIME: 33; AGE: 18; COUNTRY: US; }
4: { USER: sally; SCORE: 2000; TIME: 30; AGE: 16; COUNTRY: CA; }
5: { USER: mark; SCORE: 2000; TIME: 30; AGE: 18; COUNTRY: DE; }
6: { USER: bob; SCORE: 2000; TIME: 32; AGE: 16; COUNTRY: US; }
7: { USER: amy; SCORE: 1500; TIME: 29; AGE: 19; COUNTRY: UK; }
8: { USER: mary; SCORE: 1500; TIME: 31; AGE: 19; COUNTRY: PL; }
9: { USER: tim; SCORE: 1000; TIME: 30; AGE: 17; COUNTRY: UK; }
keySort: { }
(using a print function from here)
here is a jsbin example.
edit: cleaned up and posted as mksort.js on github.
This is handy for alpha sorts of all sizes.
Pass it the indexes you want to sort by, in order, as arguments.
Array.prototype.deepSortAlpha= function(){
var itm, L=arguments.length, order=arguments;
var alphaSort= function(a, b){
a= a.toLowerCase();
b= b.toLowerCase();
if(a== b) return 0;
return a> b? 1:-1;
}
if(!L) return this.sort(alphaSort);
this.sort(function(a, b){
var tem= 0, indx=0;
while(tem==0 && indx<L){
itm=order[indx];
tem= alphaSort(a[itm], b[itm]);
indx+=1;
}
return tem;
});
return this;
}
var arr= [[ "Nilesh","Karmshil"], ["Pranjal","Deka"], ["Susants","Ghosh"],
["Shiv","Shankar"], ["Javid","Ghosh"], ["Shaher","Banu"], ["Javid","Rashid"]];
arr.deepSortAlpha(1,0);
I suggest to use a built in comparer and chain the wanted sort order with logical or ||.
function customSort(a, b) {
return a[3].localeCompare(b[3]) || a[1].localeCompare(b[1]);
}
Working example:
var array = [
[0, 'Aluminium', 0, 'Francis'],
[1, 'Argon', 1, 'Ada'],
[2, 'Brom', 2, 'John'],
[3, 'Cadmium', 3, 'Marie'],
[4, 'Fluor', 3, 'Marie'],
[5, 'Gold', 1, 'Ada'],
[6, 'Kupfer', 4, 'Ines'],
[7, 'Krypton', 4, 'Joe'],
[8, 'Sauerstoff', 3, 'Marie'],
[9, 'Zink', 5, 'Max']
];
array.sort(function (a, b) {
return a[3].localeCompare(b[3]) || a[1].localeCompare(b[1]);
});
document.write('<pre>');
array.forEach(function (a) {
document.write(JSON.stringify(a) + '<br>');
});
You could concat the 2 variables together into a sortkey and use that for your comparison.
list.sort(function(a,b){
var aCat = a.var1 + a.var2;
var bCat = b.var1 + b.var2;
return (aCat > bCat ? 1 : aCat < bCat ? -1 : 0);
});
I found multisotr. This is simple, powerfull and small library for multiple sorting. I was need to sort an array of objects with dynamics sorting criteria:
const criteria = ['name', 'speciality']
const data = [
{ name: 'Mike', speciality: 'JS', age: 22 },
{ name: 'Tom', speciality: 'Java', age: 30 },
{ name: 'Mike', speciality: 'PHP', age: 40 },
{ name: 'Abby', speciality: 'Design', age: 20 },
]
const sorted = multisort(data, criteria)
console.log(sorted)
<script src="https://cdn.rawgit.com/peterkhayes/multisort/master/multisort.js"></script>
This library more mutch powerful, that was my case. Try it.
String Appending Method
You can sort by multiple values simply by appending the values into a string and comparing the strings. It is helpful to add a split key character to prevent runoff from one key to the next.
Example
const arr = [
{ a: 1, b: 'a', c: 3 },
{ a: 2, b: 'a', c: 5 },
{ a: 1, b: 'b', c: 4 },
{ a: 2, b: 'a', c: 4 }
]
function sortBy (arr, keys, splitKeyChar='~') {
return arr.sort((i1,i2) => {
const sortStr1 = keys.reduce((str, key) => str + splitKeyChar+i1[key], '')
const sortStr2 = keys.reduce((str, key) => str + splitKeyChar+i2[key], '')
return sortStr1.localeCompare(sortStr2)
})
}
console.log(sortBy(arr, ['a', 'b', 'c']))
Recursion Method
You can also use Recursion to do this. It is a bit more complex than the String Appending Method but it allows you to do ASC and DESC on the key level. I'm commenting on each section as it is a bit more complex.
There are a few commented out tests to show and verify the sorting works with a mixture of order and default order.
Example
const arr = [
{ a: 1, b: 'a', c: 3 },
{ a: 2, b: 'a', c: 5 },
{ a: 1, b: 'b', c: 4 },
{ a: 2, b: 'a', c: 4 }
]
function sortBy (arr, keys) {
return arr.sort(function sort (i1,i2, sKeys=keys) {
// Get order and key based on structure
const compareKey = (sKeys[0].key) ? sKeys[0].key : sKeys[0];
const order = sKeys[0].order || 'ASC'; // ASC || DESC
// Calculate compare value and modify based on order
let compareValue = i1[compareKey].toString().localeCompare(i2[compareKey].toString())
compareValue = (order.toUpperCase() === 'DESC') ? compareValue * -1 : compareValue
// See if the next key needs to be considered
const checkNextKey = compareValue === 0 && sKeys.length !== 1
// Return compare value
return (checkNextKey) ? sort(i1, i2, sKeys.slice(1)): compareValue;
})
}
// console.log(sortBy(arr, ['a', 'b', 'c']))
console.log(sortBy(arr, [{key:'a',order:'desc'}, 'b', 'c']))
// console.log(sortBy(arr, ['a', 'b', {key:'c',order:'desc'}]))
// console.log(sortBy(arr, ['a', {key:'b',order:'desc'}, 'c']))
// console.log(sortBy(arr, [{key:'a',order:'asc'}, {key:'b',order:'desc'}, {key:'c',order:'desc'}]))
Try this:
t.sort( (a,b)=> a[3].localeCompare(b[3]) || a[1].localeCompare(b[1]) );
let t = [
//[publicationID, publication_name, ownderID, owner_name ]
[1, 'ZBC', 3, 'John Smith'],
[2, 'FBC', 5, 'Mike Tyson'],
[3, 'ABC', 7, 'Donald Duck'],
[4, 'DBC', 1, 'Michael Jackson'],
[5, 'XYZ', 2, 'Michael Jackson'],
[6, 'BBC', 4, 'Michael Jackson'],
];
// owner_name subarrray index = 3
// publication_name subarrray index = 1
t.sort( (a,b)=> a[3].localeCompare(b[3]) || a[1].localeCompare(b[1]) );
console.log(t.join('\n'));
I assume that your data in array let t = [ [publicationID, publication_name, ownderID, owner_name ], ... ] where index of owner_name = 3 and publication_name =1.
I was working with ng-grid and needed to to multiple column sorting on an array of records returned from an API, so I came up with this nifty, dynamic multi-sort function.
First of all, ng-grid fires an "event" for "ngGridSorted" and passes this structure back, describing the sort:
sortData = {
columns: DOM Element,
directions: [], //Array of string values desc or asc. Each index relating to the same index of fields
fields: [], //Array of string values
};
So I built a function that will dynamically generate a sort function based on the sortData as shown above (Don't be scared by the scroll bar! It's only about 50 lines long! Also, I'm sorry about the slop. It prevented a horizontal scrollbar!):
function SortingFunction(sortData)
{
this.sortData = sortData;
this.sort = function(a, b)
{
var retval = 0;
if(this.sortData.fields.length)
{
var i = 0;
/*
Determine if there is a column that both entities (a and b)
have that are not exactly equal. The first one that we find
will be the column we sort on. If a valid column is not
located, then we will return 0 (equal).
*/
while( ( !a.hasOwnProperty(this.sortData.fields[i])
|| !b.hasOwnProperty(this.sortData.fields[i])
|| (a.hasOwnProperty(this.sortData.fields[i])
&& b.hasOwnProperty(this.sortData.fields[i])
&& a[this.sortData.fields[i]] === b[this.sortData.fields[i]])
) && i < this.sortData.fields.length){
i++;
}
if(i < this.sortData.fields.length)
{
/*
A valid column was located for both entities
in the SortData. Now perform the sort.
*/
if(this.sortData.directions
&& i < this.sortData.directions.length
&& this.sortData.directions[i] === 'desc')
{
if(a[this.sortData.fields[i]] > b[this.sortData.fields[i]])
retval = -1;
else if(a[this.sortData.fields[i]] < b[this.sortData.fields[i]])
retval = 1;
}
else
{
if(a[this.sortData.fields[i]] < b[this.sortData.fields[i]])
retval = -1;
else if(a[this.sortData.fields[i]] > b[this.sortData.fields[i]])
retval = 1;
}
}
}
return retval;
}.bind(this);
}
I then sort the results of my API (results) like so:
results.sort(new SortingFunction(sortData).sort);
I hope somebody else enjoys this solution as much as I do! Thanks!
I had a similar problem while displaying memory pool blocks from the output of some virtual DOM h-functions composition. Basically I faced to the same problem as sorting multi-criteria data like scoring results from players around the world.
I have noticed that multi-criteria sorting is:
- sort by the first column
- if equal, sort by the second
- if equal, sort by the third
- etc... nesting and nesting if-else
And if you don't care, you could fail quickly in a if-else nesting hell... like callback hell of promises...
What about if we write a "predicate" function to decide if which part of alternative using ? The predicate is simply :
// useful for chaining test
const decide = (test, other) => test === 0 ? other : test
Now after having written your classifying tests (byCountrySize, byAge, byGameType, byScore, byLevel...) whatever who need, you can weight your tests (1 = asc, -1 = desc, 0 = disable), put them in an array, and apply a reducing 'decide' function like this:
const multisort = (s1, s2) => {
const bcs = -1 * byCountrySize(s1, s2) // -1 = desc
const ba = 1 *byAge(s1, s2)
const bgt = 0 * byGameType(s1, s2) // 0 = doesn't matter
const bs = 1 * byScore(s1, s2)
const bl = -1 * byLevel(s1, s2) // -1 = desc
// ... other weights and criterias
// array order matters !
return [bcs, ba, bgt, bs, bl].reduce((acc, val) => decide(val, acc), 0)
}
// invoke [].sort with custom sort...
scores.sort(multisort)
And voila ! It's up to you to define your own criterias / weights / orders... but you get the idea. Hope this helps !
EDIT:
* ensure that there is a total sorting order on each column
* be aware of not having dependencies between columns orders, and no circular dependencies
if, not, sorting can be unstable !
function multiSort() {
var args =$.makeArray( arguments ),
sortOrder=1, prop='', aa='', b='';
return function (a, b) {
for (var i=0; i<args.length; i++){
if(args[i][0]==='-'){
prop=args[i].substr(1)
sortOrder=-1
}
else{sortOrder=1; prop=args[i]}
aa = a[prop].toLowerCase()
bb = b[prop].toLowerCase()
if (aa < bb) return -1 * sortOrder;
if (aa > bb) return 1 * sortOrder;
}
return 0
}
}
empArray.sort(multiSort( 'lastname','firstname')) Reverse with '-lastname'
My own library for working with ES6 iterables (blinq) allows (among other things) easy multi-level sorting
const blinq = window.blinq.blinq
// or import { blinq } from 'blinq'
// or const { blinq } = require('blinq')
const dates = [{
day: 1, month: 10, year: 2000
},
{
day: 1, month: 1, year: 2000
},
{
day: 2, month: 1, year: 2000
},
{
day: 1, month: 1, year: 1999
},
{
day: 1, month: 1, year: 2000
}
]
const sortedDates = blinq(dates)
.orderBy(x => x.year)
.thenBy(x => x.month)
.thenBy(x => x.day);
console.log(sortedDates.toArray())
// or console.log([...sortedDates])
<script src="https://cdn.jsdelivr.net/npm/blinq#2.0.2"></script>
I have just published to npm a micro-library called sort-helper (source on github). The idea is to import the helper by to create the comparison function for sort array method through the syntax items.sort(by(column, ...otherColumns)), with several way to express the columns to sort by:
By key: persons.sort(by('lastName', 'firstName')),
By selector: dates.sort(by(x => x.toISOString())),
In descending order: [3, 2, 4, 1].sort(by(desc(n => n))) → [3, 2, 1, 0],
Ignoring case: ['B', 'D', 'c', 'a'].sort(by(ignoreCase(x => x))).join('') → 'aBcD'.
It's similar to the nice thenBy mentioned in this answer but with the following differences that may be more to the taste of some:
An approach more functional than object-oriented (see thenBy fluent API),
A syntax a bit terser and still as much readable, natural almost like SQL.
Fully implemented in TypeScript, to benefit from type safety and type expressivity.
Sourced from GitHub
function sortMethodAsc(a, b) {
return a == b ? 0 : a > b ? 1 : -1;
}
function sortMethodWithDirection(direction) {
if (direction === undefined || direction == "asc") {
return sortMethodAsc;
} else {
return function(a, b) {
return -sortMethodAsc(a, b);
}
}
}
function sortMethodWithDirectionByColumn(columnName, direction){
const sortMethod = sortMethodWithDirection(direction)
return function(a, b){
return sortMethod(a[columnName], b[columnName]);
}
}
function sortMethodWithDirectionMultiColumn(sortArray) {
//sample of sortArray
// sortArray = [
// { column: "column5", direction: "asc" },
// { column: "column3", direction: "desc" }
// ]
const sortMethodsForColumn = (sortArray || []).map( item => sortMethodWithDirectionByColumn(item.column, item.direction) );
return function(a,b) {
let sorted = 0;
let index = 0;
while (sorted === 0 && index < sortMethodsForColumn.length) {
sorted = sortMethodsForColumn[index++](a,b);
}
return sorted;
}
}
//=============================================
//=============================================
//=============================================
//test
var data = [
{"CountryName":"Aruba","CountryCode":"ABW","GNI":280},{
"CountryName":"Afghanistan","CountryCode":"ABW","GNI":280},{"CountryName":"Angola","CountryCode":"AGO","GNI":280},{"CountryName":"Albania","CountryCode":"ALB","GNI":4320},
{"CountryName":"Arab World","CountryCode":"ARB","GNI":280},{"CountryName":"United Arab Emirates","CountryCode":"ARE","GNI":39130},
{"CountryName":"Argentina","CountryCode":"ARG","GNI":13030},{"CountryName":"Armenia","CountryCode":"ARM","GNI":3990},{"CountryName":"American Samoa","CountryCode":"ASM","GNI":280},
{"CountryName":"Antigua and Barbuda","CountryCode":"ATG","GNI":13810},{"CountryName":"Australia","CountryCode":"AUS","GNI":51360},
{"CountryName":"Austria","CountryCode":"AUT","GNI":45440},{"CountryName":"Azerbaijan","CountryCode":"AZE","GNI":4080},{"CountryName":"Burundi","CountryCode":"BDI","GNI":280},
{"CountryName":"Belgium","CountryCode":"BEL","GNI":41790},{"CountryName":"Benin","CountryCode":"BEN","GNI":800},{"CountryName":"Burkina Faso","CountryCode":"BFA","GNI":590},
{"CountryName":"Bangladesh","CountryCode":"BGD","GNI":1470},{"CountryName":"Bulgaria","CountryCode":"BGR","GNI":7860},{"CountryName":"Bahrain","CountryCode":"BHR","GNI":21150},
{"CountryName":"Bosnia and Herzegovina","CountryCode":"BIH","GNI":4910},{"CountryName":"Belarus","CountryCode":"BLR","GNI":5280},
{"CountryName":"Belize","CountryCode":"BLZ","GNI":4390},{"CountryName":"Bolivia","CountryCode":"BOL","GNI":3130},{"CountryName":"Brazil","CountryCode":"BRA","GNI":8600},
{"CountryName":"Barbados","CountryCode":"BRB","GNI":15270},{"CountryName":"Brunei Darussalam","CountryCode":"BRN","GNI":29600},
{"CountryName":"Bhutan","CountryCode":"BTN","GNI":2660},{"CountryName":"Botswana","CountryCode":"BWA","GNI":6730},
{"CountryName":"Central African Republic","CountryCode":"CAF","GNI":390},{"CountryName":"Canada","CountryCode":"CAN","GNI":42870},
{"CountryName":"Central Europe and the Baltics","CountryCode":"CEB","GNI":13009},{"CountryName":"Switzerland","CountryCode":"CHE","GNI":80560},
{"CountryName":"Chile","CountryCode":"CHL","GNI":13610},{"CountryName":"China","CountryCode":"CHN","GNI":8690},{"CountryName":"Cote d'Ivoire","CountryCode":"CIV","GNI":1580},
{"CountryName":"Cameroon","CountryCode":"CMR","GNI":1370},{"CountryName":"Colombia","CountryCode":"COL","GNI":5890},{"CountryName":"Comoros","CountryCode":"COM","GNI":1280},
{"CountryName":"Cabo Verde","CountryCode":"CPV","GNI":3030},{"CountryName":"Costa Rica","CountryCode":"CRI","GNI":11120},
{"CountryName":"Caribbean small states","CountryCode":"CSS","GNI":8909},{"CountryName":"Cyprus","CountryCode":"CYP","GNI":23720},
{"CountryName":"Czech Republic","CountryCode":"CZE","GNI":18160},{"CountryName":"Germany","CountryCode":"DEU","GNI":43490},
{"CountryName":"Djibouti","CountryCode":"DJI","GNI":1880},{"CountryName":"Dominica","CountryCode":"DMA","GNI":6590},{"CountryName":"Denmark","CountryCode":"DNK","GNI":55220},
{"CountryName":"Dominican Republic","CountryCode":"DOM","GNI":6630},{"CountryName":"Algeria","CountryCode":"DZA","GNI":3940},
{"CountryName":"East Asia & Pacific (excluding high income)","CountryCode":"EAP","GNI":6987},{"CountryName":"Early-demographic dividend","CountryCode":"EAR","GNI":3352},
{"CountryName":"East Asia & Pacific","CountryCode":"EAS","GNI":10171},{"CountryName":"Europe & Central Asia (excluding high income)","CountryCode":"ECA","GNI":7375},
{"CountryName":"Europe & Central Asia","CountryCode":"ECS","GNI":22656},{"CountryName":"Ecuador","CountryCode":"ECU","GNI":5920},
{"CountryName":"Euro area","CountryCode":"EMU","GNI":35645},{"CountryName":"Spain","CountryCode":"ESP","GNI":27180},{"CountryName":"Estonia","CountryCode":"EST","GNI":18190},
{"CountryName":"Ethiopia","CountryCode":"ETH","GNI":740},{"CountryName":"European Union","CountryCode":"EUU","GNI":32784},
{"CountryName":"Fragile and conflict affected situations","CountryCode":"FCS","GNI":1510},{"CountryName":"Finland","CountryCode":"FIN","GNI":44580},
{"CountryName":"Fiji","CountryCode":"FJI","GNI":4970},{"CountryName":"France","CountryCode":"FRA","GNI":37970},{"CountryName":"Gabon","CountryCode":"GAB","GNI":6650},
{"CountryName":"United Kingdom","CountryCode":"GBR","GNI":40530},{"CountryName":"Georgia","CountryCode":"GEO","GNI":3780},{"CountryName":"Ghana","CountryCode":"GHA","GNI":1880},
{"CountryName":"Guinea","CountryCode":"GIN","GNI":790},{"CountryName":"Guinea-Bissau","CountryCode":"GNB","GNI":660},
{"CountryName":"Equatorial Guinea","CountryCode":"GNQ","GNI":7050},{"CountryName":"Greece","CountryCode":"GRC","GNI":18090},
{"CountryName":"Grenada","CountryCode":"GRD","GNI":9180},{"CountryName":"Guatemala","CountryCode":"GTM","GNI":4060},{"CountryName":"Guyana","CountryCode":"GUY","GNI":4500},
{"CountryName":"High income","CountryCode":"HIC","GNI":40142},{"CountryName":"Honduras","CountryCode":"HND","GNI":2250},{"CountryName":"Heavily indebted poor countries (HIPC)","CountryCode":"HPC","GNI":904},{"CountryName":"Croatia","CountryCode":"HRV","GNI":12570},{"CountryName":"Haiti","CountryCode":"HTI","GNI":760},{"CountryName":"Hungary","CountryCode":"HUN","GNI":12870},{"CountryName":"IBRD only","CountryCode":"IBD","GNI":5745},{"CountryName":"IDA & IBRD total","CountryCode":"IBT","GNI":4620},{"CountryName":"IDA total","CountryCode":"IDA","GNI":1313},{"CountryName":"IDA blend","CountryCode":"IDB","GNI":1791},
{"CountryName":"Indonesia","CountryCode":"IDN","GNI":3540},{"CountryName":"IDA only","CountryCode":"IDX","GNI":1074},{"CountryName":"India","CountryCode":"IND","GNI":1800},{"CountryName":"Ireland","CountryCode":"IRL","GNI":55290},{"CountryName":"Iraq","CountryCode":"IRQ","GNI":4630},{"CountryName":"Iceland","CountryCode":"ISL","GNI":60830},{"CountryName":"Israel","CountryCode":"ISR","GNI":37270},{"CountryName":"Italy","CountryCode":"ITA","GNI":31020},{"CountryName":"Jamaica","CountryCode":"JAM","GNI":4760},{"CountryName":"Jordan","CountryCode":"JOR","GNI":3980},{"CountryName":"Japan","CountryCode":"JPN","GNI":38550},{"CountryName":"Kazakhstan","CountryCode":"KAZ","GNI":7970},{"CountryName":"Kenya","CountryCode":"KEN","GNI":1460},{"CountryName":"Kyrgyz Republic","CountryCode":"KGZ","GNI":1130},
{"CountryName":"Cambodia","CountryCode":"KHM","GNI":1230},{"CountryName":"Kiribati","CountryCode":"KIR","GNI":3010},{"CountryName":"St. Kitts and Nevis","CountryCode":"KNA","GNI":16240},{"CountryName":"Kuwait","CountryCode":"KWT","GNI":31430},{"CountryName":"Latin America & Caribbean (excluding high income)","CountryCode":"LAC","GNI":7470},{"CountryName":"Lao PDR","CountryCode":"LAO","GNI":2270},{"CountryName":"Lebanon","CountryCode":"LBN","GNI":8400},{"CountryName":"Liberia","CountryCode":"LBR","GNI":620},{"CountryName":"Libya","CountryCode":"LBY","GNI":5500},{"CountryName":"St. Lucia","CountryCode":"LCA","GNI":8830},{"CountryName":"Latin America & Caribbean","CountryCode":"LCN","GNI":8251},{"CountryName":"Least developed countries: UN classification","CountryCode":"LDC","GNI":1011},{"CountryName":"Low income","CountryCode":"LIC","GNI":774},{"CountryName":"Sri Lanka","CountryCode":"LKA","GNI":3850},{"CountryName":"Lower middle income","CountryCode":"LMC","GNI":2118},{"CountryName":"Low & middle income","CountryCode":"LMY","GNI":4455},{"CountryName":"Lesotho","CountryCode":"LSO","GNI":1210},{"CountryName":"Late-demographic dividend","CountryCode":"LTE","GNI":8518},{"CountryName":"Lithuania","CountryCode":"LTU","GNI":15200},{"CountryName":"Luxembourg","CountryCode":"LUX","GNI":70260},{"CountryName":"Latvia","CountryCode":"LVA","GNI":14740},{"CountryName":"Morocco","CountryCode":"MAR","GNI":2860},{"CountryName":"Moldova","CountryCode":"MDA","GNI":2200},{"CountryName":"Madagascar","CountryCode":"MDG","GNI":400},{"CountryName":"Maldives","CountryCode":"MDV","GNI":9760},
{"CountryName":"Middle East & North Africa","CountryCode":"MEA","GNI":7236},{"CountryName":"Mexico","CountryCode":"MEX","GNI":8610},{"CountryName":"Marshall Islands","CountryCode":"MHL","GNI":4840},{"CountryName":"Middle income","CountryCode":"MIC","GNI":4942},{"CountryName":"Mali","CountryCode":"MLI","GNI":770},
{"CountryName":"Malta","CountryCode":"MLT","GNI":24080},{"CountryName":"Myanmar","CountryCode":"MMR","GNI":1210},{"CountryName":"Middle East & North Africa (excluding high income)","CountryCode":"MNA","GNI":3832},{"CountryName":"Montenegro","CountryCode":"MNE","GNI":7400},{"CountryName":"Mongolia","CountryCode":"MNG","GNI":3270},{"CountryName":"Mozambique","CountryCode":"MOZ","GNI":420},{"CountryName":"Mauritania","CountryCode":"MRT","GNI":1100},{"CountryName":"Mauritius","CountryCode":"MUS","GNI":10130},{"CountryName":"Malawi","CountryCode":"MWI","GNI":320},{"CountryName":"Malaysia","CountryCode":"MYS","GNI":9650},{"CountryName":"North America","CountryCode":"NAC","GNI":56721},{"CountryName":"Namibia","CountryCode":"NAM","GNI":4570},{"CountryName":"Niger","CountryCode":"NER","GNI":360},{"CountryName":"Nigeria","CountryCode":"NGA","GNI":2100},
{"CountryName":"Nicaragua","CountryCode":"NIC","GNI":2130},{"CountryName":"Netherlands","CountryCode":"NLD","GNI":46180},{"CountryName":"Norway","CountryCode":"NOR","GNI":75990},{"CountryName":"Nepal","CountryCode":"NPL","GNI":800},{"CountryName":"Nauru","CountryCode":"NRU","GNI":10220},{"CountryName":"New Zealand","CountryCode":"NZL","GNI":38970},{"CountryName":"OECD members","CountryCode":"OED","GNI":37273},{"CountryName":"Oman","CountryCode":"OMN","GNI":14440},{"CountryName":"Other small states","CountryCode":"OSS","GNI":12199},{"CountryName":"Pakistan","CountryCode":"PAK","GNI":1580},{"CountryName":"Panama","CountryCode":"PAN","GNI":13280},{"CountryName":"Peru","CountryCode":"PER","GNI":5960},{"CountryName":"Philippines","CountryCode":"PHL","GNI":3660},{"CountryName":"Palau","CountryCode":"PLW","GNI":12700},{"CountryName":"Papua New Guinea","CountryCode":"PNG","GNI":2340},{"CountryName":"Poland","CountryCode":"POL","GNI":12730},{"CountryName":"Pre-demographic dividend","CountryCode":"PRE","GNI":1379},{"CountryName":"Portugal","CountryCode":"PRT","GNI":19820},{"CountryName":"Paraguay","CountryCode":"PRY","GNI":5470},{"CountryName":"West Bank and Gaza","CountryCode":"PSE","GNI":3180},{"CountryName":"Pacific island small states","CountryCode":"PSS","GNI":3793},{"CountryName":"Post-demographic dividend","CountryCode":"PST","GNI":41609},{"CountryName":"Qatar","CountryCode":"QAT","GNI":60510},{"CountryName":"Romania","CountryCode":"ROU","GNI":10000},{"CountryName":"Russian Federation","CountryCode":"RUS","GNI":9230},{"CountryName":"Rwanda","CountryCode":"RWA","GNI":720},{"CountryName":"South Asia","CountryCode":"SAS","GNI":1729},{"CountryName":"Saudi Arabia","CountryCode":"SAU","GNI":20090},{"CountryName":"Sudan","CountryCode":"SDN","GNI":2380},{"CountryName":"Senegal","CountryCode":"SEN","GNI":1240},{"CountryName":"Singapore","CountryCode":"SGP","GNI":54530},{"CountryName":"Solomon Islands","CountryCode":"SLB","GNI":1920},{"CountryName":"Sierra Leone","CountryCode":"SLE","GNI":510},{"CountryName":"El Salvador","CountryCode":"SLV","GNI":3560},{"CountryName":"Serbia","CountryCode":"SRB","GNI":5180},{"CountryName":"Sub-Saharan Africa (excluding high income)","CountryCode":"SSA","GNI":1485},{"CountryName":"Sub-Saharan Africa","CountryCode":"SSF","GNI":1486},{"CountryName":"Small states","CountryCode":"SST","GNI":11099},{"CountryName":"Sao Tome and Principe","CountryCode":"STP","GNI":1770},{"CountryName":"Suriname","CountryCode":"SUR","GNI":5150},{"CountryName":"Slovak Republic","CountryCode":"SVK","GNI":16610},{"CountryName":"Slovenia","CountryCode":"SVN","GNI":22000},{"CountryName":"Sweden","CountryCode":"SWE","GNI":52590},{"CountryName":"Eswatini","CountryCode":"SWZ","GNI":2950},{"CountryName":"Seychelles","CountryCode":"SYC","GNI":14170},{"CountryName":"Chad","CountryCode":"TCD","GNI":640},{"CountryName":"East Asia & Pacific (IDA & IBRD countries)","CountryCode":"TEA","GNI":7061},
{"CountryName":"Europe & Central Asia (IDA & IBRD countries)","CountryCode":"TEC","GNI":7866},{"CountryName":"Togo","CountryCode":"TGO","GNI":610},{"CountryName":"Thailand","CountryCode":"THA","GNI":5950},{"CountryName":"Tajikistan","CountryCode":"TJK","GNI":990},{"CountryName":"Turkmenistan","CountryCode":"TKM","GNI":6380},{"CountryName":"Latin America & the Caribbean (IDA & IBRD countries)","CountryCode":"TLA","GNI":8179},{"CountryName":"Timor-Leste","CountryCode":"TLS","GNI":1790},{"CountryName":"Middle East & North Africa (IDA & IBRD countries)","CountryCode":"TMN","GNI":3839},{"CountryName":"Tonga","CountryCode":"TON","GNI":4010},{"CountryName":"South Asia (IDA & IBRD)","CountryCode":"TSA","GNI":1729},
{"CountryName":"Sub-Saharan Africa (IDA & IBRD countries)","CountryCode":"TSS","GNI":1486},{"CountryName":"Trinidad and Tobago","CountryCode":"TTO","GNI":15340},{"CountryName":"Tunisia","CountryCode":"TUN","GNI":3490},{"CountryName":"Turkey","CountryCode":"TUR","GNI":10940},{"CountryName":"Tuvalu","CountryCode":"TUV","GNI":4970},{"CountryName":"Tanzania","CountryCode":"TZA","GNI":910},{"CountryName":"Uganda","CountryCode":"UGA","GNI":600},{"CountryName":"Ukraine","CountryCode":"UKR","GNI":2390},{"CountryName":"Upper middle income","CountryCode":"UMC","GNI":8197},{"CountryName":"Uruguay","CountryCode":"URY","GNI":15250},{"CountryName":"United States","CountryCode":"USA","GNI":58270},{"CountryName":"Uzbekistan","CountryCode":"UZB","GNI":2000},{"CountryName":"St. Vincent and the Grenadines","CountryCode":"VCT","GNI":7390},{"CountryName":"Vietnam","CountryCode":"VNM","GNI":2160},{"CountryName":"Vanuatu","CountryCode":"VUT","GNI":2920},{"CountryName":"World","CountryCode":"WLD","GNI":10371},{"CountryName":"Samoa","CountryCode":"WSM","GNI":4090},{"CountryName":"Kosovo","CountryCode":"XKX","GNI":3900},
{"CountryName":"South Africa","CountryCode":"ZAF","GNI":5430},{"CountryName":"Zambia","CountryCode":"ZMB","GNI":1290},{"CountryName":"Zimbabwe","CountryCode":"ZWE","GNI":1170},
{"CountryName":"Zimbabwe","CountryCode":"ZWE","GNI":1171}];
const sortMethod = sortMethodWithDirectionMultiColumn(
[
{ column: "GNI", direction: "asc" },
{ column: "CountryCode", direction: "desc" }
]
);
let sortedData = data.sort(sortMethod);
console.log("sorted by: 1)column:GNI-asc, 2)column:CountryCode-desc")
console.table(sortedData);
console.log(sortedData);
I need this for a small project I'm working on, so performance is not a priority.
I have two arrays, main array I want to be sorted, and array of sorting rules. I loop that rules array inside sorting callback function, and try to exit that loop as soon as possible.
I use multiplier in order to convert -1 to 1 depending on weather I'm sorting a property in ascending or descending order.
let array = [
{fullName: 'Michael Schumacher', sport: 'Formula 1'},
{fullName: 'Michael Jordan', sport: 'Basketball'},
{fullName: 'Damon Hill', sport: 'Formula 1'},
{fullName: 'Kobe Bryant', sport: 'Basketball'},
{fullName: 'Lebron James', sport: 'Basketball'},
{fullName: 'Lewis Hamilton', sport: 'Formula 1'},
];
const sortArray = (array, options) => {
if (!Array.isArray(options)) {
options = [{ key: options, order: 'asc' }];
}
options.forEach(item => {
item.multiplier = item.order != 'desc' ? -1 : 1;
});
return array.sort((firstItem, secondItem) => {
for (item of options) {
const { key, multiplier } = item;
const firstValue = firstItem[key];
const secondValue = secondItem[key];
if (firstValue != secondValue) {
return multiplier * (firstValue < secondValue ? 1 : -1);
}
}
return 0;
});
}
console.log('Original array');
console.log([...array]);
sortArray(array, 'sport');
console.log('Sorted by sport only (ascending, implicit, keeping the same order of athletes)');
console.log([...array]);
sortArray(array, [{key: 'sport'}, {key: 'fullName', order: 'desc'}]);
console.log('Sorted by sport (ascending, implicit), and by fullName (descending)');
console.log(array);
To simplify the understanding
The sort method compares numbers, if below 0, it sorts it to the let, if above zero it sorts it to the right.
So to add multi level sorting, check if the match === 0, then further sort it.
See example below
['a/b/c', 'a long piece of text/b', 'apple/b'].sort((a, b) => {
const asc = a.split('/').length - b.split('/').length
return asc
})
// outputs ['a long piece of text/b', 'apple/b', 'a/b/c']
['a/b/c', 'a long piece of text/b', 'apple/b'].sort((a, b) => {
const asc = a.split('/').length - b.split('/').length
return asc === 0 ? a.length - b.length : asc
})
// outputs: 'apple/b', 'a long piece of text/b', 'a/b/c'
I see a lot of complicated solutions, so I'll paste here what I'm using:
assignedIssues.sort((a, b) => {
let order = sortByText(a.assignee?.username, b.assignee?.username)
if (order === 0) order = sort(a.labels, b.labels, statusLabels)
if (order === 0) order = sort(a.labels, b.labels, priorityLabels)
if (order === 0) order = sortByText(a.web_url, b.web_url)
return order
})
I think that this is much more readable, let you implement any custom sorting function for each level, without calling all unnecessarily.
Assuming you want to sort by multiple indexes, and assuming that you don't know the type of each field (string, number, or null).
You can create a function to sort with as many indexes as you like.
const compareWithType = (a, b) => {
if (typeof a === 'string') return a.localeCompare(b);
if (typeof a === 'number') return a - b;
return (!!a) - (!!b); // to sort non-string non-number falsy or null values, modify as you like.
}
const compareWithIndexes = (...indexes) => {
return (a, b) => {
for (let i in indexes) {
let diff = 0;
while (!diff) {
compareWithType(a[i], b[i]);
}
return diff;
}
}
}
[[1, 2, 3, 4, 5], [0, 2, 3, 4, 6]].sort(compareWithIndexes(2, 3, 4));
// compares (3 - 3) then (4 - 4) then (5 - 6)
Despite a lot of complicated answers here, I still like the basic way to do it
var arr = [
[3, 'pub2', 1, 'ownA'],
[1, 'pub1', 2, 'ownA'],
[2, 'pub1', 3, 'ownC']
];
// sorting priority is bottom to top, in this case owner name then publication name
// sort publication name
arr.sort((a,b) => a[1].localeCompare(b[1]));
// sort owner name
arr.sort((a,b) => a[3].localeCompare(b[3]));
console.log(arr);
I am trying to come up with a single function that returns the object with the longest length array within an array.
Here is the question:
Write a function that lists all the Orcs that have the most the weapons.
Example:
var orcs = [{
name: 'Orgoth',
strength: 9001,
weapons: ['Bone ax', 'Mace of Strength']
}, {
name: 'Blaroguhh',
strength: 500,
weapons: ['Cheeseburger', 'Spear of the Hut']
}, {
name: 'Mark',
strength: 543,
weapons: ['Ax of Defense', 'Dagger', 'Sword']
}]
getMostWeapons(orcs);
// => {name: 'Mark', strength: 543, weapons: ['Ax of Defense', 'Dagger', 'Sword' ]}
And this is what I have so far:
function getMostWeapons(orcs) {
var length = 0;
return orcs.filter(function (obj) {
return obj.filter(function (val) {
if (val.length > length) {
return (length = val.length);
}
});
});
}
.filter is used to return all the array elements that match a criteria. Since you don't know the maximum length until you've gone through all the orcs, you can't use it in one pass to find the orc to return.
Just use an ordinary loop that compares the length of weapons to the longest seen so far. If it's longer, replace the longest with this one.
function getMostWeapons(orcs) {
var longest = 0;
var longestOrcs = [];
orcs.forEach(function(orc) {
if (orc.weapons.length > longest) {
longestOrcs = [orc];
longest = orc.weapons.length;
} else if (orc.weapons.length == longest) {
longestOrcs.push(orc);
}
});
return longestOrcs;
}
.filter() isn't really what you want here because you wouldn't know what you were filtering for without first making a pass to see what the max length was. .reduce() could be used (for side effects), but you aren't really accumulating a single value here like .reduce() is built for. So, it makes sense to just use .forEach() with a couple parent-scoped variables to keep track of our state.
To return all objects that have the max length, you can do this (in a snippet you can run to see the results). This returns an array of all objects that have the maximum length.
var orcs = [{
name: 'Orgoth',
strength: 9001,
weapons: ['Bone ax', 'Mace of Strength']
}, {
name: 'Blaroguhh',
strength: 500,
weapons: ['Cheeseburger', 'Spear of the Hut']
}, {
name: 'Mark',
strength: 543,
weapons: ['Ax of Defense', 'Dagger', 'Sword']
}];
function getMostWeapons(o) {
var max = 0, maxObj = [];
o.forEach(function(item) {
if (item.weapons.length > max) {
max = item.weapons.length;
maxObj = [item];
} else if (item.weapons.length === max) {
maxObj.push(item);
}
});
return maxObj;
}
var max = getMostWeapons(orcs);
log(max);
function log(x) {
document.write(JSON.stringify(x));
}
You can use filter although it's nearly unreadable and cannot be called "good coding" with a clear conscious but when others can use forEach with a function instead of a simple loop without any good reason ... ;-)
JSON.stringify(
orcs.sort(function(a,b){
return b.weapons.length - a.weapons.length;
}).filter(function(value,index,array){
return value.weapons.length == array[0].weapons.length;
})
)
I'd separate into two functions, one to find the max, and one to find all orc with that max:
function getMostWeapons(allOrcs) {
return allOrcs.reduce((max, currentOrc) =>
Math.max(max, currentOrc.weapons.length), 0);
}
function getOrcsWithMostWeapons(allOrcs) {
let maxWeapons = getMostWeapons(allOrcs);
return allOrcs.filter(orc => orc.weapons.length === maxWeapons);
}
Note that this will return an array even if there's only one orc. I find it best to always return the same type.
Try like this
var length=Math.max.apply(Math,orcs.map(function(o){return o.weapons.length;}));
var obj=orcs.find(function(x){ return x.weapons.length == length;}); // single object
var objList=orcs.filter(function(x){ return x.weapons.length == length;}); // multple
console.log(obj);
console.log(objList);
JSFIDDLE
Late answer but good if anyone comes here looking for a similar answer, I took the current answer and converted it to use for in, a little more generic too
function getLargest(o) {
var max = 0, maxObj = [];
for ( var item in o)
{
if (o[item].length > max) {
max = o[item].length;
maxObj = [item];
} else if (o[item].length === max) {
maxObj.push(item);
}
};
return maxObj;
}
A simple loop with a length variable would probably be the best way
function getMostWeapons(orcs) {
var len = 0; //longest
for( x in orcs ){ //simple loop
len = len < orcs[x].weapons.length ? orcs[x].weapons.length : len;
}
}
Well, I have an array objects with random values,
Ex.
var arr = [
{ id:1001, date:"20-02-2014", Name: 'demo1' },
{ id:1004, date:"13-02-2014", Name: 'demo0' },
{ id:1000, date:"10-02-2014", Name: 'demo14' },
{ id:1004, date:"16-02-2014", Name: 'demo10' },
{ id:1006, date:"22-02-2014", Name: 'demo111' },
{ id:1003, date:"28-02-2014", Name: 'demo16' },
{ id:1000, date:"28-01-2014", Name: 'demo12' },
{ id:1004, date:"28-01-2014", Name: 'demo01' },
{ id:1000, date:"08-01-2014", Name: 'demo41' },
{ id:1006, date:"08-01-2014", Name: 'demo91' }
]
I wanted to sort this array firstly by key id & then by key date as,
Output:
sorted_arr = [
{"id":1000,"date":"08-01-2014","Name":"demo41"}, //group1
{"id":1000,"date":"28-01-2014","Name":"demo12"}, //group1
{"id":1000,"date":"10-02-2014","Name":"demo14"}, //group1
{"id":1001,"date":"20-02-2014","Name":"demo1"}, //group2
{"id":1003,"date":"28-02-2014","Name":"demo16"}, //group3
{"id":1004,"date":"28-01-2014","Name":"demo01"}, //group4
{"id":1004,"date":"13-02-2014","Name":"demo0"}, //group4
{"id":1004,"date":"16-02-2014","Name":"demo10"}, //group4
{"id":1006,"date":"08-01-2014","Name":"demo91"} //group5
{"id":1006,"date":"22-02-2014","Name":"demo111"} //group5
]
I tried few generic code to sort,
// generic comparison function
cmp = function(x, y){
return x > y ? 1 : x < y ? -1 : 0;
};
arr.sort(function(a, b){
return cmp(
[cmp(a.id, b.id), cmp(a.date, b.date)],
[cmp(b.id, a.id), cmp(b.date, a.date)]
);
});
I referred few examples SO Example but not getting expected output. Please suggest me best way to get this.
No need to create Date objects, just reorder the date string into a sortable string, example
This example assumes that your dates are in the same format DD-MM-YYYY and creates YYYYMMDD for the date sort.
Javascript
var arr = [
{ id:1001, date:"20-02-2014", Name: 'demo1' },
{ id:1004, date:"13-02-2014", Name: 'demo0' },
{ id:1000, date:"10-02-2014", Name: 'demo14' },
{ id:1004, date:"16-02-2014", Name: 'demo10' },
{ id:1006, date:"22-02-2014", Name: 'demo111' },
{ id:1003, date:"28-02-2014", Name: 'demo16' },
{ id:1000, date:"28-01-2014", Name: 'demo12' },
{ id:1004, date:"28-01-2014", Name: 'demo01' },
{ id:1000, date:"08-01-2014", Name: 'demo41' },
{ id:1006, date:"08-01-2014", Name: 'demo91' }
];
var sorted = arr.sort(function (a, b) {
return a.id - b.id || a.date.split('-').reverse().join('') - b.date.split('-').reverse().join('');
});
sorted.forEach(function (element) {
console.log(JSON.stringify(element));
});
Output
{"id":1000,"date":"08-01-2014","Name":"demo41"}
{"id":1000,"date":"28-01-2014","Name":"demo12"}
{"id":1000,"date":"10-02-2014","Name":"demo14"}
{"id":1001,"date":"20-02-2014","Name":"demo1"}
{"id":1003,"date":"28-02-2014","Name":"demo16"}
{"id":1004,"date":"28-01-2014","Name":"demo01"}
{"id":1004,"date":"13-02-2014","Name":"demo0"}
{"id":1004,"date":"16-02-2014","Name":"demo10"}
{"id":1006,"date":"08-01-2014","Name":"demo91"}
{"id":1006,"date":"22-02-2014","Name":"demo111"}
On jsFiddle
If there is any concern over mixing date formats, as discussed with #xdazz, then you can improve on this by checking the padding yourself. The following creates the format 'YYYYYYMMDD' when sorting by the date. The extra year padding is not necessary in this example as I am taking the numeric difference of the values, but if you choose to compare the strings then it is important.
function pad(s, n) {
var v = '',
i;
for(i = 0; i < n - s.length; i += 1) {
v += '0';
}
return v + s;
}
var sorted = arr.sort(function (a, b) {
var idDiff = a.id - b.id;
if (idDiff) {
return idDiff;
}
var ordA = a.date.split('-').reverse(),
ordB = b.date.split('-').reverse();
ordA[0] = pad(ordA[0], 6);
ordA[1] = pad(ordA[1], 2);
ordA[2] = pad(ordA[2], 2);
ordA = ordA.join('');
ordB[0] = pad(ordB[0], 6);
ordB[1] = pad(ordB[1], 2);
ordB[2] = pad(ordB[2], 2);
ordB = ordB.join('');
return ordA - ordB;
});
On jsFiddle
If you really want to use Date objects the I would suggest the following.
var sorted = arr.sort(function (a, b) {
var idDiff = a.id - b.id;
if (idDiff) {
return idDiff;
}
var ordA = a.date.split('-').reverse(),
ordB = b.date.split('-').reverse();
ordA[1] -= 1;
ordB[1] -= 1;
return new Date(Date.UTC.apply(undefined, ordA)).valueOf() - new Date(Date.UTC.apply(undefined, ordB)).valueOf();
});
sorted.forEach(function (element) {
console.log(JSON.stringify(element));
});
On jsFiddle
Note: These examples do not handle dates with negative years, again you would need to make further modifications.
First compare with id, then compare with date if id equal. But because your date is in invalid date format, extra work has to be done for letting it be recognized by Date.
sorted_arr = arr.sort(function(a, b) {
return a.id - b.id || new Date(a.date.split('-').reverse().join('-')) - new Date(b.date.split('-').reverse().join('-'));
});
Edit:
If you are guaranteed to have zeros in front of the 1-digit months and dates, then you could even not to parse to date:
sorted_arr = arr.sort(function(a, b) {
return a.id - b.id || a.date.split('-').reverse().join('') - b.date.split('-').reverse().join('');
});
It's better to have a date operate lib momentjs to help.
You could check the code on jsBin
function compare(a, b){
var idDiff = a.id - b.id;
var adate = moment(a.date, "DD-MM-YYYY");
var bdate = moment(b.date, "DD-MM-YYYY");
var dateDiff = adate.diff(bdate);
return idDiff || dateDiff;
}
var sortedArr = arr.sort(compare);
console.log(sortedArr);
You can sort array by two properties with Alasql library:
var res = alasql('SELECT *, mid(date,7,4)+mid(date,4,2)+mid(date,1,2) AS ndate \
FROM ? ORDER BY id, ndate',[arr]);
Try this example at jsFiddle.
Here "mid(date,7,4)+mid(date,4,2)+mid(date,1,2)" was used to convert date from '28-11-2014' to the sort key like '20141128'.
Came up with this using underscore.js & it's chain and sortBy:
var sorted_array = _(arr).chain().sortBy(function(o) {
return o.date.split('-').reverse().join();
}).sortBy(function(o) {
return o.id;
}).value();
Sorting on the date first, and then id will give you the list sorted as expected.
jsfiddle
How can I easily obtain the min or max element of a JavaScript array?
Example pseudocode:
let array = [100, 0, 50]
array.min() //=> 0
array.max() //=> 100
How about augmenting the built-in Array object to use Math.max/Math.min instead:
Array.prototype.max = function() {
return Math.max.apply(null, this);
};
Array.prototype.min = function() {
return Math.min.apply(null, this);
};
let p = [35,2,65,7,8,9,12,121,33,99];
console.log(`Max value is: ${p.max()}` +
`\nMin value is: ${p.min()}`);
Here is a JSFiddle.
Augmenting the built-ins can cause collisions with other libraries (some see), so you may be more comfortable with just apply'ing Math.xxx() to your array directly:
var min = Math.min.apply(null, arr),
max = Math.max.apply(null, arr);
Alternately, assuming your browser supports ECMAScript 6, you can use spread syntax which functions similarly to the apply method:
var min = Math.min( ...arr ),
max = Math.max( ...arr );
var max_of_array = Math.max.apply(Math, array);
For a full discussion see:
http://aaroncrane.co.uk/2008/11/javascript_max_api/
Using spread operator (ES6)
Math.max(...array) // The same with "min" => Math.min(...array)
const array = [10, 2, 33, 4, 5];
console.log(
Math.max(...array)
)
For big arrays (~10⁷ elements), Math.min and Math.max both produces the following error in Node.js.
RangeError: Maximum call stack size exceeded
A more robust solution is to not add every element to the call stack, but to instead pass an array:
function arrayMin(arr) {
return arr.reduce(function (p, v) {
return ( p < v ? p : v );
});
}
function arrayMax(arr) {
return arr.reduce(function (p, v) {
return ( p > v ? p : v );
});
}
If you are concerned about speed, the following code is ~3 times faster then Math.max.apply is on my computer. See https://jsben.ch/JPOyL.
function arrayMin(arr) {
var len = arr.length, min = Infinity;
while (len--) {
if (arr[len] < min) {
min = arr[len];
}
}
return min;
};
function arrayMax(arr) {
var len = arr.length, max = -Infinity;
while (len--) {
if (arr[len] > max) {
max = arr[len];
}
}
return max;
};
If your arrays contains strings instead of numbers, you also need to coerce them into numbers. The below code does that, but it slows the code down ~10 times on my machine. See https://jsben.ch/uPipD.
function arrayMin(arr) {
var len = arr.length, min = Infinity;
while (len--) {
if (Number(arr[len]) < min) {
min = Number(arr[len]);
}
}
return min;
};
function arrayMax(arr) {
var len = arr.length, max = -Infinity;
while (len--) {
if (Number(arr[len]) > max) {
max = Number(arr[len]);
}
}
return max;
};
tl;dr
// For regular arrays:
var max = Math.max(...arrayOfNumbers);
// For arrays with tens of thousands of items:
let max = testArray[0];
for (let i = 1; i < testArrayLength; ++i) {
if (testArray[i] > max) {
max = testArray[i];
}
}
MDN solution
The official MDN docs on Math.max() already covers this issue:
The following function uses Function.prototype.apply() to find the maximum element in a numeric array. getMaxOfArray([1, 2, 3]) is equivalent to Math.max(1, 2, 3), but you can use getMaxOfArray() on programmatically constructed arrays of any size.
function getMaxOfArray(numArray) {
return Math.max.apply(null, numArray);
}
Or with the new spread operator, getting the maximum of an array becomes a lot easier.
var arr = [1, 2, 3];
var max = Math.max(...arr);
Maximum size of an array
According to MDN the apply and spread solutions had a limitation of 65536 that came from the limit of the maximum number of arguments:
But beware: in using apply this way, you run the risk of exceeding the JavaScript engine's argument length limit. The consequences of applying a function with too many arguments (think more than tens of thousands of arguments) vary across engines (JavaScriptCore has hard-coded argument limit of 65536), because the limit (indeed even the nature of any excessively-large-stack behavior) is unspecified. Some engines will throw an exception. More perniciously, others will arbitrarily limit the number of arguments actually passed to the applied function. To illustrate this latter case: if such an engine had a limit of four arguments (actual limits are of course significantly higher), it would be as if the arguments 5, 6, 2, 3 had been passed to apply in the examples above, rather than the full array.
They even provide a hybrid solution which doesn't really have good performance compared to other solutions. See performance test below for more.
In 2019 the actual limit is the maximum size of the call stack. For modern Chromium based desktop browsers this means that when it comes to finding min/max with apply or spread, practically the maximum size for numbers only arrays is ~120000. Above this, there will be a stack overflow and the following error will be thrown:
RangeError: Maximum call stack size exceeded
With the script below (based on this blog post), by catching that error you can calculate the limit for your specific environment.
Warning! Running this script takes time and depending on the performance of your system it might slow or crash your browser/system!
let testArray = Array.from({length: 10000}, () => Math.floor(Math.random() * 2000000));
for (i = 10000; i < 1000000; ++i) {
testArray.push(Math.floor(Math.random() * 2000000));
try {
Math.max.apply(null, testArray);
} catch (e) {
console.log(i);
break;
}
}
Performance on large arrays
Based on the test in EscapeNetscape's comment I created some benchmarks that tests 5 different methods on a random number only array with 100000 items.
In 2019, the results show that the standard loop (which BTW doesn't have the size limitation) is the fastest everywhere. apply and spread comes closely after it, then much later MDN's hybrid solution then reduce as the slowest.
Almost all tests gave the same results, except for one where spread somewhy ended up being the slowest.
If you step up your array to have 1 million items, things start to break and you are left with the standard loop as a fast solution and reduce as a slower.
JSPerf benchmark
JSBen benchmark
JSBench.me benchmark
Benchmark source code
var testArrayLength = 100000
var testArray = Array.from({length: testArrayLength}, () => Math.floor(Math.random() * 2000000));
// ES6 spread
Math.min(...testArray);
Math.max(...testArray);
// reduce
testArray.reduce(function(a, b) {
return Math.max(a, b);
});
testArray.reduce(function(a, b) {
return Math.min(a, b);
});
// apply
Math.min.apply(Math, testArray);
Math.max.apply(Math, testArray);
// standard loop
let max = testArray[0];
for (let i = 1; i < testArrayLength; ++i) {
if (testArray[i] > max) {
max = testArray[i];
}
}
let min = testArray[0];
for (let i = 1; i < testArrayLength; ++i) {
if (testArray[i] < min) {
min = testArray[i];
}
}
// MDN hibrid soltuion
// Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/apply#Using_apply_and_built-in_functions
function minOfArray(arr) {
var min = Infinity;
var QUANTUM = 32768;
for (var i = 0, len = arr.length; i < len; i += QUANTUM) {
var submin = Math.min.apply(null, arr.slice(i, Math.min(i + QUANTUM, len)));
min = Math.min(submin, min);
}
return min;
}
minOfArray(testArray);
function maxOfArray(arr) {
var max = -Infinity;
var QUANTUM = 32768;
for (var i = 0, len = arr.length; i < len; i += QUANTUM) {
var submax = Math.max.apply(null, arr.slice(i, Math.max(i + QUANTUM, len)));
max = Math.max(submax, max);
}
return max;
}
maxOfArray(testArray);
If you're paranoid like me about using Math.max.apply (which could cause errors when given large arrays according to MDN), try this:
function arrayMax(array) {
return array.reduce(function(a, b) {
return Math.max(a, b);
});
}
function arrayMin(array) {
return array.reduce(function(a, b) {
return Math.min(a, b);
});
}
Or, in ES6:
function arrayMax(array) {
return array.reduce((a, b) => Math.max(a, b));
}
function arrayMin(array) {
return array.reduce((a, b) => Math.min(a, b));
}
The anonymous functions are unfortunately necessary (instead of using Math.max.bind(Math) because reduce doesn't just pass a and b to its function, but also i and a reference to the array itself, so we have to ensure we don't try to call max on those as well.
Alternative Methods
The Math.min and Math.max are great methods to get the minimum and maximum item out of a collection of items, however it's important to be aware of some cavities that can comes with it.
Using them with an array that contains large number of items (more than ~10⁷ items, depends on the user's browser) most likely will crash and give the following error message:
const arr = Array.from(Array(1000000).keys());
Math.min(arr);
Math.max(arr);
Uncaught RangeError: Maximum call stack size exceeded
UPDATE
Latest browsers might return NaN instead. That might be a better way to handle errors, however it doesn't solve the problem just yet.
Instead, consider using something like so:
function maxValue(arr) {
return arr.reduce((max, val) => max > val ? max : val)
}
Or with better run-time:
function maxValue(arr) {
let max = arr[0];
for (let val of arr) {
if (val > max) {
max = val;
}
}
return max;
}
Or to get both Min and Max:
function getMinMax(arr) {
return arr.reduce(({min, max}, v) => ({
min: min < v ? min : v,
max: max > v ? max : v,
}), { min: arr[0], max: arr[0] });
}
Or with even better run-time*:
function getMinMax(arr) {
let min = arr[0];
let max = arr[0];
let i = arr.length;
while (i--) {
min = arr[i] < min ? arr[i] : min;
max = arr[i] > max ? arr[i] : max;
}
return { min, max };
}
* Tested with 1,000,000 items:
Just for a reference, the 1st function run-time (on my machine) was 15.84ms vs 2nd function with only 4.32ms.
Two ways are shorter and easy:
let arr = [2, 6, 1, 0]
Way 1:
let max = Math.max.apply(null, arr)
Way 2:
let max = arr.reduce(function(a, b) {
return Math.max(a, b);
});
.apply is often used when the intention is to invoke a variadic function with a list of argument values, e.g.
The Math.max([value1[,value2, ...]]) function returns the largest of zero or more numbers.
Math.max(10, 20); // 20
Math.max(-10, -20); // -10
Math.max(-10, 20); // 20
The Math.max() method doesn't allow you to pass in an array. If you have a list of values of which you need to get the largest, you would normally call this function using Function.prototype.apply(), e.g.
Math.max.apply(null, [10, 20]); // 20
Math.max.apply(null, [-10, -20]); // -10
Math.max.apply(null, [-10, 20]); // 20
However, as of the ECMAScript 6 you can use the spread operator:
The spread operator allows an expression to be expanded in places where multiple arguments (for function calls) or multiple elements (for array literals) are expected.
Using the spread operator, the above can be rewritten as such:
Math.max(...[10, 20]); // 20
Math.max(...[-10, -20]); // -10
Math.max(...[-10, 20]); // 20
When calling a function using the variadic operator, you can even add additional values, e.g.
Math.max(...[10, 20], 50); // 50
Math.max(...[-10, -20], 50); // 50
Bonus:
Spread operator enables you to use the array literal syntax to create new arrays in situations where in ES5 you would need to fall back to imperative code, using a combination of push, splice, etc.
let foo = ['b', 'c'];
let bar = ['a', ...foo, 'd', 'e']; // ['a', 'b', 'c', 'd', 'e']
You do it by extending the Array type:
Array.max = function( array ){
return Math.max.apply( Math, array );
};
Array.min = function( array ){
return Math.min.apply( Math, array );
};
Boosted from here (by John Resig)
A simple solution to find the minimum value over an Array of elements is to use the Array prototype function reduce:
A = [4,3,-9,-2,2,1];
A.reduce((min, val) => val < min ? val : min, A[0]); // returns -9
or using JavaScript's built-in Math.Min() function (thanks #Tenflex):
A.reduce((min,val) => Math.min(min,val), A[0]);
This sets min to A[0], and then checks for A[1]...A[n] whether it is strictly less than the current min. If A[i] < min then min is updated to A[i]. When all array elements has been processed, min is returned as the result.
EDIT: Include position of minimum value:
A = [4,3,-9,-2,2,1];
A.reduce((min, val) => val < min._min ? {_min: val, _idx: min._curr, _curr: min._curr + 1} : {_min: min._min, _idx: min._idx, _curr: min._curr + 1}, {_min: A[0], _idx: 0, _curr: 0}); // returns { _min: -9, _idx: 2, _curr: 6 }
For a concise, modern solution, one can perform a reduce operation over the array, keeping track of the current minimum and maximum values, so the array is only iterated over once (which is optimal). Destructuring assignment is used here for succinctness.
let array = [100, 0, 50];
let [min, max] = array.reduce(([prevMin,prevMax], curr)=>
[Math.min(prevMin, curr), Math.max(prevMax, curr)], [Infinity, -Infinity]);
console.log("Min:", min);
console.log("Max:", max);
To only find either the minimum or maximum, we can use perform a reduce operation in much the same way, but we only need to keep track of the previous optimal value. This method is better than using apply as it will not cause errors when the array is too large for the stack.
const arr = [-1, 9, 3, -6, 35];
//Only find minimum
const min = arr.reduce((a,b)=>Math.min(a,b), Infinity);
console.log("Min:", min);//-6
//Only find maximum
const max = arr.reduce((a,b)=>Math.max(a,b), -Infinity);
console.log("Max:", max);//35
Others have already given some solutions in which they augment Array.prototype. All I want in this answer is to clarify whether it should be Math.min.apply( Math, array ) or Math.min.apply( null, array ). So what context should be used, Math or null?
When passing null as a context to apply, then the context will default to the global object (the window object in the case of browsers). Passing the Math object as the context would be the correct solution, but it won't hurt passing null either. Here's an example when null might cause trouble, when decorating the Math.max function:
// decorate Math.max
(function (oldMax) {
Math.max = function () {
this.foo(); // call Math.foo, or at least that's what we want
return oldMax.apply(this, arguments);
};
})(Math.max);
Math.foo = function () {
print("foo");
};
Array.prototype.max = function() {
return Math.max.apply(null, this); // <-- passing null as the context
};
var max = [1, 2, 3].max();
print(max);
The above will throw an exception because this.foo will be evaluated as window.foo, which is undefined. If we replace null with Math, things will work as expected and the string "foo" will be printed to the screen (I tested this using Mozilla Rhino).
You can pretty much assume that nobody has decorated Math.max so, passing null will work without problems.
One more way to do it:
var arrayMax = Function.prototype.apply.bind(Math.max, null);
Usage:
var max = arrayMax([2, 5, 1]);
I am surprised not one mentiond the reduce function.
var arr = [1, 10, 5, 11, 2]
var b = arr.reduce(function(previous,current){
return previous > current ? previous:current
});
b => 11
arr => [1, 10, 5, 11, 2]
https://developer.mozilla.org/ru/docs/Web/JavaScript/Reference/Global_Objects/Math/max
function getMaxOfArray(numArray) {
return Math.max.apply(null, numArray);
}
var arr = [100, 0, 50];
console.log(getMaxOfArray(arr))
this worked for me.
This may suit your purposes.
Array.prototype.min = function(comparer) {
if (this.length === 0) return null;
if (this.length === 1) return this[0];
comparer = (comparer || Math.min);
var v = this[0];
for (var i = 1; i < this.length; i++) {
v = comparer(this[i], v);
}
return v;
}
Array.prototype.max = function(comparer) {
if (this.length === 0) return null;
if (this.length === 1) return this[0];
comparer = (comparer || Math.max);
var v = this[0];
for (var i = 1; i < this.length; i++) {
v = comparer(this[i], v);
}
return v;
}
let array = [267, 306, 108]
let longest = Math.max(...array);
I thought I'd share my simple and easy to understand solution.
For the min:
var arr = [3, 4, 12, 1, 0, 5];
var min = arr[0];
for (var k = 1; k < arr.length; k++) {
if (arr[k] < min) {
min = arr[k];
}
}
console.log("Min is: " + min);
And for the max:
var arr = [3, 4, 12, 1, 0, 5];
var max = arr[0];
for (var k = 1; k < arr.length; k++) {
if (arr[k] > max) {
max = arr[k];
}
}
console.log("Max is: " + max);
For big arrays (~10⁷ elements), Math.min and Math.max procuces a RangeError (Maximum call stack size exceeded) in node.js.
For big arrays, a quick & dirty solution is:
Array.prototype.min = function() {
var r = this[0];
this.forEach(function(v,i,a){if (v<r) r=v;});
return r;
};
For an array containing objects instead of numbers:
arr = [
{ name: 'a', value: 5 },
{ name: 'b', value: 3 },
{ name: 'c', value: 4 }
]
You can use reduce to get the element with the smallest value (min)
arr.reduce((a, b) => a.value < b.value ? a : b)
// { name: 'b', value: 3 }
or the largest value (max)
arr.reduce((a, b) => a.value > b.value ? a : b)
// { name: 'a', value: 5 }
Aside using the math function max and min, another function to use is the built in function of sort(): here we go
const nums = [12, 67, 58, 30].sort((x, y) =>
x - y)
let min_val = nums[0]
let max_val = nums[nums.length -1]
I had the same problem, I needed to obtain the minimum and maximum values of an array and, to my surprise, there were no built-in functions for arrays. After reading a lot, I decided to test the "top 3" solutions myself:
discrete solution: a FOR loop to check every element of the array against the current max and/or min value;
APPLY solution: sending the array to the Math.max and/or Math.min internal functions using apply(null,array);
REDUCE solution: recursing a check against every element of the array using reduce(function).
The test code was this:
function GetMaxDISCRETE(A)
{ var MaxX=A[0];
for (var X=0;X<A.length;X++)
if (MaxX<A[X])
MaxX=A[X];
return MaxX;
}
function GetMaxAPPLY(A)
{ return Math.max.apply(null,A);
}
function GetMaxREDUCE(A)
{ return A.reduce(function(p,c)
{ return p>c?p:c;
});
}
The array A was filled with 100,000 random integer numbers, each function was executed 10,000 times on Mozilla Firefox 28.0 on an intel Pentium 4 2.99GHz desktop with Windows Vista. The times are in seconds, retrieved by performance.now() function. The results were these, with 3 fractional digits and standard deviation:
Discrete solution: mean=0.161s, sd=0.078
APPLY solution: mean=3.571s, sd=0.487
REDUCE solution: mean=0.350s, sd=0.044
The REDUCE solution was 117% slower than the discrete solution. The APPLY solution was the worse, 2,118% slower than the discrete solution. Besides, as Peter observed, it doesn't work for large arrays (about more than 1,000,000 elements).
Also, to complete the tests, I tested this extended discrete code:
var MaxX=A[0],MinX=A[0];
for (var X=0;X<A.length;X++)
{ if (MaxX<A[X])
MaxX=A[X];
if (MinX>A[X])
MinX=A[X];
}
The timing: mean=0.218s, sd=0.094
So, it is 35% slower than the simple discrete solution, but it retrieves both the maximum and the minimum values at once (any other solution would take at least twice that to retrieve them). Once the OP needed both values, the discrete solution would be the best choice (even as two separate functions, one for calculating maximum and another for calculating minimum, they would outperform the second best, the REDUCE solution).
Iterate through, keeping track as you go.
var min = null;
var max = null;
for (var i = 0, len = arr.length; i < len; ++i)
{
var elem = arr[i];
if (min === null || min > elem) min = elem;
if (max === null || max < elem) max = elem;
}
alert( "min = " + min + ", max = " + max );
This will leave min/max null if there are no elements in the array. Will set min and max in one pass if the array has any elements.
You could also extend Array with a range method using the above to allow reuse and improve on readability. See a working fiddle at http://jsfiddle.net/9C9fU/
Array.prototype.range = function() {
var min = null,
max = null,
i, len;
for (i = 0, len = this.length; i < len; ++i)
{
var elem = this[i];
if (min === null || min > elem) min = elem;
if (max === null || max < elem) max = elem;
}
return { min: min, max: max }
};
Used as
var arr = [3, 9, 22, -7, 44, 18, 7, 9, 15];
var range = arr.range();
console.log(range.min);
console.log(range.max);
You can use the following function anywhere in your project:
function getMin(array){
return Math.min.apply(Math,array);
}
function getMax(array){
return Math.max.apply(Math,array);
}
And then you can call the functions passing the array:
var myArray = [1,2,3,4,5,6,7];
var maximo = getMax(myArray); //return the highest number
The following code works for me :
var valueList = [10,4,17,9,3];
var maxValue = valueList.reduce(function(a, b) { return Math.max(a, b); });
var minValue = valueList.reduce(function(a, b) { return Math.min(a, b); });
array.sort((a, b) => b - a)[0];
Gives you the maximum value in an array of numbers.
array.sort((a, b) => a - b)[0];
Gives you the minimum value in an array of numbers.
let array = [0,20,45,85,41,5,7,85,90,111];
let maximum = array.sort((a, b) => b - a)[0];
let minimum = array.sort((a, b) => a - b)[0];
console.log(minimum, maximum)
let arr=[20,8,29,76,7,21,9]
Math.max.apply( Math, arr ); // 76
Simple stuff, really.
var arr = [10,20,30,40];
arr.max = function() { return Math.max.apply(Math, this); }; //attach max funct
arr.min = function() { return Math.min.apply(Math, this); }; //attach min funct
alert("min: " + arr.min() + " max: " + arr.max());
Here's one way to get the max value from an array of objects. Create a copy (with slice), then sort the copy in descending order and grab the first item.
var myArray = [
{"ID": 1, "Cost": 200},
{"ID": 2, "Cost": 1000},
{"ID": 3, "Cost": 50},
{"ID": 4, "Cost": 500}
]
maxsort = myArray.slice(0).sort(function(a, b) { return b.ID - a.ID })[0].ID;