I have an array of objects and I want to compare those objects on a specific object property. Here's my array:
var myArray = [
{"ID": 1, "Cost": 200},
{"ID": 2, "Cost": 1000},
{"ID": 3, "Cost": 50},
{"ID": 4, "Cost": 500}
]
I'd like to zero in on the "cost" specifically and a get a min and maximum value. I realize I can just grab the cost values and push them off into a javascript array and then run the Fast JavaScript Max/Min.
However is there an easier way to do this by bypassing the array step in the middle and going off the objects properties (in this case "Cost") directly?
The reduce is good for stuff like this: to perform aggregate operations (like min, max, avg, etc.) on an array of objects, and return a single result:
myArray.reduce(function(prev, curr) {
return prev.Cost < curr.Cost ? prev : curr;
});
...or you can define that inner function with ES6 function syntax:
(prev, curr) => prev.Cost < curr.Cost ? prev : curr
If you want to be cute you can attach this to array:
Array.prototype.hasMin = function(attrib) {
return (this.length && this.reduce(function(prev, curr){
return prev[attrib] < curr[attrib] ? prev : curr;
})) || null;
}
Now you can just say:
myArray.hasMin('ID') // result: {"ID": 1, "Cost": 200}
myArray.hasMin('Cost') // result: {"ID": 3, "Cost": 50}
myEmptyArray.hasMin('ID') // result: null
Please note that if you intend to use this, it doesn't have full checks for every situation. If you pass in an array of primitive types, it will fail. If you check for a property that doesn't exist, or if not all the objects contain that property, you will get the last element. This version is a little more bulky, but has those checks:
Array.prototype.hasMin = function(attrib) {
const checker = (o, i) => typeof(o) === 'object' && o[i]
return (this.length && this.reduce(function(prev, curr){
const prevOk = checker(prev, attrib);
const currOk = checker(curr, attrib);
if (!prevOk && !currOk) return {};
if (!prevOk) return curr;
if (!currOk) return prev;
return prev[attrib] < curr[attrib] ? prev : curr;
})) || null;
}
One way is to loop through all elements and compare it to the highest/lowest value.
(Creating an array, invoking array methods is overkill for this simple operation).
// There's no real number bigger than plus Infinity
var lowest = Number.POSITIVE_INFINITY;
var highest = Number.NEGATIVE_INFINITY;
var tmp;
for (var i=myArray.length-1; i>=0; i--) {
tmp = myArray[i].Cost;
if (tmp < lowest) lowest = tmp;
if (tmp > highest) highest = tmp;
}
console.log(highest, lowest);
Using Math.min and Math.max:
var myArray = [
{ id: 1, cost: 200},
{ id: 2, cost: 1000},
{ id: 3, cost: 50},
{ id: 4, cost: 500}
]
var min = Math.min(...myArray.map(item => item.cost));
var max = Math.max(...myArray.map(item => item.cost));
console.log("min: " + min);
console.log("max: " + max);
Use sort, if you don't care about the array being modified.
myArray.sort(function (a, b) {
return a.Cost - b.Cost
})
var min = myArray[0],
max = myArray[myArray.length - 1]
Use Math functions and pluck out the values you want with map.
Here is the jsbin:
https://jsbin.com/necosu/1/edit?js,console
var myArray = [{
"ID": 1,
"Cost": 200
}, {
"ID": 2,
"Cost": 1000
}, {
"ID": 3,
"Cost": 50
}, {
"ID": 4,
"Cost": 500
}],
min = Math.min.apply(null, myArray.map(function(item) {
return item.Cost;
})),
max = Math.max.apply(null, myArray.map(function(item) {
return item.Cost;
}));
console.log('min', min);//50
console.log('max', max);//1000
UPDATE:
If you want to use ES6:
var min = Math.min.apply(null, myArray.map(item => item.Cost)),
max = Math.max.apply(null, myArray.map(item => item.Cost));
I think Rob W's answer is really the right one (+1), but just for fun: if you wanted to be "clever", you could do something like this:
var myArray =
[
{"ID": 1, "Cost": 200},
{"ID": 2, "Cost": 1000},
{"ID": 3, "Cost": 50},
{"ID": 4, "Cost": 500}
]
function finder(cmp, arr, attr) {
var val = arr[0][attr];
for(var i=1;i<arr.length;i++) {
val = cmp(val, arr[i][attr])
}
return val;
}
alert(finder(Math.max, myArray, "Cost"));
alert(finder(Math.min, myArray, "Cost"));
or if you had a deeply nested structure, you could get a little more functional and do the following:
var myArray =
[
{"ID": 1, "Cost": { "Wholesale":200, Retail: 250 }},
{"ID": 2, "Cost": { "Wholesale":1000, Retail: 1010 }},
{"ID": 3, "Cost": { "Wholesale":50, Retail: 300 }},
{"ID": 4, "Cost": { "Wholesale":500, Retail: 1050 }}
]
function finder(cmp, arr, getter) {
var val = getter(arr[0]);
for(var i=1;i<arr.length;i++) {
val = cmp(val, getter(arr[i]))
}
return val;
}
alert(finder(Math.max, myArray, function(x) { return x.Cost.Wholesale; }));
alert(finder(Math.min, myArray, function(x) { return x.Cost.Retail; }));
These could easily be curried into more useful/specific forms.
Try (a is array, f is field to compare)
let max= (a,f)=> a.reduce((m,x)=> m[f]>x[f] ? m:x);
let min= (a,f)=> a.reduce((m,x)=> m[f]<x[f] ? m:x);
let max= (a,f)=> a.reduce((m,x)=> m[f]>x[f] ? m:x);
let min= (a,f)=> a.reduce((m,x)=> m[f]<x[f] ? m:x);
// TEST
var myArray = [
{"ID": 1, "Cost": 200},
{"ID": 2, "Cost": 1000},
{"ID": 3, "Cost": 50},
{"ID": 4, "Cost": 500}
]
console.log('Max Cost', max(myArray, 'Cost'));
console.log('Min Cost', min(myArray, 'Cost'));
console.log('Max ID', max(myArray, 'ID'));
console.log('Min ID', min(myArray, 'ID'));
for Max
Math.max.apply(Math, myArray.map(a => a.Cost));
for min
Math.min.apply(Math, myArray.map(a => a.Cost));
This can be achieved with lodash's minBy and maxBy functions.
Lodash's minBy and maxBy documentation
_.minBy(array, [iteratee=_.identity])
_.maxBy(array, [iteratee=_.identity])
These methods accept an iteratee which is invoked for each element in
array to generate the criterion by which the value is ranked. The
iteratee is invoked with one argument: (value).
Solution
var myArray = [
{"ID": 1, "Cost": 200},
{"ID": 2, "Cost": 1000},
{"ID": 3, "Cost": 50},
{"ID": 4, "Cost": 500}
]
const minimumCostItem = _.minBy(myArray, "Cost");
console.log("Minimum cost item: ", minimumCostItem);
// Getting the maximum using a functional iteratee
const maximumCostItem = _.maxBy(myArray, function(entry) {
return entry["Cost"];
});
console.log("Maximum cost item: ", maximumCostItem);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>
Using Array.prototype.reduce(), you can plug in comparator functions to determine the min, max, etc. item in an array.
var items = [
{ name : 'Apple', count : 3 },
{ name : 'Banana', count : 10 },
{ name : 'Orange', count : 2 },
{ name : 'Mango', count : 8 }
];
function findBy(arr, key, comparatorFn) {
return arr.reduce(function(prev, curr, index, arr) {
return comparatorFn.call(arr, prev[key], curr[key]) ? prev : curr;
});
}
function minComp(prev, curr) {
return prev < curr;
}
function maxComp(prev, curr) {
return prev > curr;
}
document.body.innerHTML = 'Min: ' + findBy(items, 'count', minComp).name + '<br />';
document.body.innerHTML += 'Max: ' + findBy(items, 'count', maxComp).name;
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).
let [min, max] = myArray.reduce(([prevMin,prevMax], {Cost})=>
[Math.min(prevMin, Cost), Math.max(prevMax, Cost)], [Infinity, -Infinity]);
Demo:
var myArray = [
{"ID": 1, "Cost": 200},
{"ID": 2, "Cost": 1000},
{"ID": 3, "Cost": 50},
{"ID": 4, "Cost": 500}
]
let [min, max] = myArray.reduce(([prevMin,prevMax], {Cost})=>
[Math.min(prevMin, Cost), Math.max(prevMax, Cost)], [Infinity, -Infinity]);
console.log("Min cost:", min);
console.log("Max cost:", max);
This is more better solution
var myArray = [
{"ID": 1, "Cost": 200},
{"ID": 2, "Cost": 1000},
{"ID": 3, "Cost": 50},
{"ID": 4, "Cost": 500}
]
var lowestNumber = myArray[0].Cost;
var highestNumber = myArray[0].Cost;
myArray.forEach(function (keyValue, index, myArray) {
if(index > 0) {
if(keyValue.Cost < lowestNumber){
lowestNumber = keyValue.Cost;
}
if(keyValue.Cost > highestNumber) {
highestNumber = keyValue.Cost;
}
}
});
console.log('lowest number' , lowestNumber);
console.log('highest Number' , highestNumber);
Adding onto Tristan Reid's answer (+ using es6), you could create a function that accepts a callback, which will contain the operator you want to be applied to the prev and curr:
const compare = (arr, key, callback) => arr.reduce((prev, curr) =>
(callback(prev[key], curr[key]) ? prev : curr), {})[key];
// remove `[key]` to return the whole object
Then you could simply call it using:
const costMin = compare(myArray, 'Cost', (a, b) => a < b);
const costMax = compare(myArray, 'Cost', (a, b) => a > b);
we can solve problem by two approach
both method is already explained above but the performance test was missing so completing that one
1, native java-script way
2, first sort object then it easy to get min
max from sorted obj
i also test performance of both tow approach
you can also run and test performance... Happy coding (:
//first approach
var myArray = [
{"ID": 1, "Cost": 200},
{"ID": 2, "Cost": 1000},
{"ID": 3, "Cost": 50},
{"ID": 4, "Cost": 500}
]
var t1 = performance.now();;
let max=Math.max.apply(Math, myArray.map(i=>i.Cost))
let min=Math.min.apply(Math, myArray.map(i=>i.Cost))
var t2 = performance.now();;
console.log("native fuction took " + (t2 - t1) + " milliseconds.");
console.log("max Val:"+max)
console.log("min Val:"+min)
// Second approach:
function sortFunc (a, b) {
return a.Cost - b.Cost
}
var s1 = performance.now();;
sortedArray=myArray.sort(sortFunc)
var minBySortArray = sortedArray[0],
maxBySortArray = sortedArray[myArray.length - 1]
var s2 = performance.now();;
console.log("sort funciton took " + (s2 - s1) + " milliseconds.");
console.log("max ValBySortArray :"+max)
console.log("min Val BySortArray:"+min)
max = totalAVG.reduce(function (a, b) { return Math.max(a, b)}, -Infinity);
min = totalAVG.reduce(function (a, b) {return Math.min(a, b)}, Infinity);
Another one, similar to Kennebec's answer, but all in one line:
maxsort = myArray.slice(0).sort(function (a, b) { return b.ID - a.ID })[0].ID;
You can use built-in Array object to use Math.max/Math.min instead:
var arr = [1,4,2,6,88,22,344];
var max = Math.max.apply(Math, arr);// return 344
var min = Math.min.apply(Math, arr);// return 1
Related
I have an array of objects and I want to compare those objects on a specific object property. Here's my array:
var myArray = [
{"ID": 1, "Cost": 200},
{"ID": 2, "Cost": 1000},
{"ID": 3, "Cost": 50},
{"ID": 4, "Cost": 500}
]
I'd like to zero in on the "cost" specifically and a get a min and maximum value. I realize I can just grab the cost values and push them off into a javascript array and then run the Fast JavaScript Max/Min.
However is there an easier way to do this by bypassing the array step in the middle and going off the objects properties (in this case "Cost") directly?
The reduce is good for stuff like this: to perform aggregate operations (like min, max, avg, etc.) on an array of objects, and return a single result:
myArray.reduce(function(prev, curr) {
return prev.Cost < curr.Cost ? prev : curr;
});
...or you can define that inner function with ES6 function syntax:
(prev, curr) => prev.Cost < curr.Cost ? prev : curr
If you want to be cute you can attach this to array:
Array.prototype.hasMin = function(attrib) {
return (this.length && this.reduce(function(prev, curr){
return prev[attrib] < curr[attrib] ? prev : curr;
})) || null;
}
Now you can just say:
myArray.hasMin('ID') // result: {"ID": 1, "Cost": 200}
myArray.hasMin('Cost') // result: {"ID": 3, "Cost": 50}
myEmptyArray.hasMin('ID') // result: null
Please note that if you intend to use this, it doesn't have full checks for every situation. If you pass in an array of primitive types, it will fail. If you check for a property that doesn't exist, or if not all the objects contain that property, you will get the last element. This version is a little more bulky, but has those checks:
Array.prototype.hasMin = function(attrib) {
const checker = (o, i) => typeof(o) === 'object' && o[i]
return (this.length && this.reduce(function(prev, curr){
const prevOk = checker(prev, attrib);
const currOk = checker(curr, attrib);
if (!prevOk && !currOk) return {};
if (!prevOk) return curr;
if (!currOk) return prev;
return prev[attrib] < curr[attrib] ? prev : curr;
})) || null;
}
One way is to loop through all elements and compare it to the highest/lowest value.
(Creating an array, invoking array methods is overkill for this simple operation).
// There's no real number bigger than plus Infinity
var lowest = Number.POSITIVE_INFINITY;
var highest = Number.NEGATIVE_INFINITY;
var tmp;
for (var i=myArray.length-1; i>=0; i--) {
tmp = myArray[i].Cost;
if (tmp < lowest) lowest = tmp;
if (tmp > highest) highest = tmp;
}
console.log(highest, lowest);
Using Math.min and Math.max:
var myArray = [
{ id: 1, cost: 200},
{ id: 2, cost: 1000},
{ id: 3, cost: 50},
{ id: 4, cost: 500}
]
var min = Math.min(...myArray.map(item => item.cost));
var max = Math.max(...myArray.map(item => item.cost));
console.log("min: " + min);
console.log("max: " + max);
Use sort, if you don't care about the array being modified.
myArray.sort(function (a, b) {
return a.Cost - b.Cost
})
var min = myArray[0],
max = myArray[myArray.length - 1]
Use Math functions and pluck out the values you want with map.
Here is the jsbin:
https://jsbin.com/necosu/1/edit?js,console
var myArray = [{
"ID": 1,
"Cost": 200
}, {
"ID": 2,
"Cost": 1000
}, {
"ID": 3,
"Cost": 50
}, {
"ID": 4,
"Cost": 500
}],
min = Math.min.apply(null, myArray.map(function(item) {
return item.Cost;
})),
max = Math.max.apply(null, myArray.map(function(item) {
return item.Cost;
}));
console.log('min', min);//50
console.log('max', max);//1000
UPDATE:
If you want to use ES6:
var min = Math.min.apply(null, myArray.map(item => item.Cost)),
max = Math.max.apply(null, myArray.map(item => item.Cost));
I think Rob W's answer is really the right one (+1), but just for fun: if you wanted to be "clever", you could do something like this:
var myArray =
[
{"ID": 1, "Cost": 200},
{"ID": 2, "Cost": 1000},
{"ID": 3, "Cost": 50},
{"ID": 4, "Cost": 500}
]
function finder(cmp, arr, attr) {
var val = arr[0][attr];
for(var i=1;i<arr.length;i++) {
val = cmp(val, arr[i][attr])
}
return val;
}
alert(finder(Math.max, myArray, "Cost"));
alert(finder(Math.min, myArray, "Cost"));
or if you had a deeply nested structure, you could get a little more functional and do the following:
var myArray =
[
{"ID": 1, "Cost": { "Wholesale":200, Retail: 250 }},
{"ID": 2, "Cost": { "Wholesale":1000, Retail: 1010 }},
{"ID": 3, "Cost": { "Wholesale":50, Retail: 300 }},
{"ID": 4, "Cost": { "Wholesale":500, Retail: 1050 }}
]
function finder(cmp, arr, getter) {
var val = getter(arr[0]);
for(var i=1;i<arr.length;i++) {
val = cmp(val, getter(arr[i]))
}
return val;
}
alert(finder(Math.max, myArray, function(x) { return x.Cost.Wholesale; }));
alert(finder(Math.min, myArray, function(x) { return x.Cost.Retail; }));
These could easily be curried into more useful/specific forms.
Try (a is array, f is field to compare)
let max= (a,f)=> a.reduce((m,x)=> m[f]>x[f] ? m:x);
let min= (a,f)=> a.reduce((m,x)=> m[f]<x[f] ? m:x);
let max= (a,f)=> a.reduce((m,x)=> m[f]>x[f] ? m:x);
let min= (a,f)=> a.reduce((m,x)=> m[f]<x[f] ? m:x);
// TEST
var myArray = [
{"ID": 1, "Cost": 200},
{"ID": 2, "Cost": 1000},
{"ID": 3, "Cost": 50},
{"ID": 4, "Cost": 500}
]
console.log('Max Cost', max(myArray, 'Cost'));
console.log('Min Cost', min(myArray, 'Cost'));
console.log('Max ID', max(myArray, 'ID'));
console.log('Min ID', min(myArray, 'ID'));
for Max
Math.max.apply(Math, myArray.map(a => a.Cost));
for min
Math.min.apply(Math, myArray.map(a => a.Cost));
This can be achieved with lodash's minBy and maxBy functions.
Lodash's minBy and maxBy documentation
_.minBy(array, [iteratee=_.identity])
_.maxBy(array, [iteratee=_.identity])
These methods accept an iteratee which is invoked for each element in
array to generate the criterion by which the value is ranked. The
iteratee is invoked with one argument: (value).
Solution
var myArray = [
{"ID": 1, "Cost": 200},
{"ID": 2, "Cost": 1000},
{"ID": 3, "Cost": 50},
{"ID": 4, "Cost": 500}
]
const minimumCostItem = _.minBy(myArray, "Cost");
console.log("Minimum cost item: ", minimumCostItem);
// Getting the maximum using a functional iteratee
const maximumCostItem = _.maxBy(myArray, function(entry) {
return entry["Cost"];
});
console.log("Maximum cost item: ", maximumCostItem);
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.js"></script>
Using Array.prototype.reduce(), you can plug in comparator functions to determine the min, max, etc. item in an array.
var items = [
{ name : 'Apple', count : 3 },
{ name : 'Banana', count : 10 },
{ name : 'Orange', count : 2 },
{ name : 'Mango', count : 8 }
];
function findBy(arr, key, comparatorFn) {
return arr.reduce(function(prev, curr, index, arr) {
return comparatorFn.call(arr, prev[key], curr[key]) ? prev : curr;
});
}
function minComp(prev, curr) {
return prev < curr;
}
function maxComp(prev, curr) {
return prev > curr;
}
document.body.innerHTML = 'Min: ' + findBy(items, 'count', minComp).name + '<br />';
document.body.innerHTML += 'Max: ' + findBy(items, 'count', maxComp).name;
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).
let [min, max] = myArray.reduce(([prevMin,prevMax], {Cost})=>
[Math.min(prevMin, Cost), Math.max(prevMax, Cost)], [Infinity, -Infinity]);
Demo:
var myArray = [
{"ID": 1, "Cost": 200},
{"ID": 2, "Cost": 1000},
{"ID": 3, "Cost": 50},
{"ID": 4, "Cost": 500}
]
let [min, max] = myArray.reduce(([prevMin,prevMax], {Cost})=>
[Math.min(prevMin, Cost), Math.max(prevMax, Cost)], [Infinity, -Infinity]);
console.log("Min cost:", min);
console.log("Max cost:", max);
This is more better solution
var myArray = [
{"ID": 1, "Cost": 200},
{"ID": 2, "Cost": 1000},
{"ID": 3, "Cost": 50},
{"ID": 4, "Cost": 500}
]
var lowestNumber = myArray[0].Cost;
var highestNumber = myArray[0].Cost;
myArray.forEach(function (keyValue, index, myArray) {
if(index > 0) {
if(keyValue.Cost < lowestNumber){
lowestNumber = keyValue.Cost;
}
if(keyValue.Cost > highestNumber) {
highestNumber = keyValue.Cost;
}
}
});
console.log('lowest number' , lowestNumber);
console.log('highest Number' , highestNumber);
Adding onto Tristan Reid's answer (+ using es6), you could create a function that accepts a callback, which will contain the operator you want to be applied to the prev and curr:
const compare = (arr, key, callback) => arr.reduce((prev, curr) =>
(callback(prev[key], curr[key]) ? prev : curr), {})[key];
// remove `[key]` to return the whole object
Then you could simply call it using:
const costMin = compare(myArray, 'Cost', (a, b) => a < b);
const costMax = compare(myArray, 'Cost', (a, b) => a > b);
we can solve problem by two approach
both method is already explained above but the performance test was missing so completing that one
1, native java-script way
2, first sort object then it easy to get min
max from sorted obj
i also test performance of both tow approach
you can also run and test performance... Happy coding (:
//first approach
var myArray = [
{"ID": 1, "Cost": 200},
{"ID": 2, "Cost": 1000},
{"ID": 3, "Cost": 50},
{"ID": 4, "Cost": 500}
]
var t1 = performance.now();;
let max=Math.max.apply(Math, myArray.map(i=>i.Cost))
let min=Math.min.apply(Math, myArray.map(i=>i.Cost))
var t2 = performance.now();;
console.log("native fuction took " + (t2 - t1) + " milliseconds.");
console.log("max Val:"+max)
console.log("min Val:"+min)
// Second approach:
function sortFunc (a, b) {
return a.Cost - b.Cost
}
var s1 = performance.now();;
sortedArray=myArray.sort(sortFunc)
var minBySortArray = sortedArray[0],
maxBySortArray = sortedArray[myArray.length - 1]
var s2 = performance.now();;
console.log("sort funciton took " + (s2 - s1) + " milliseconds.");
console.log("max ValBySortArray :"+max)
console.log("min Val BySortArray:"+min)
max = totalAVG.reduce(function (a, b) { return Math.max(a, b)}, -Infinity);
min = totalAVG.reduce(function (a, b) {return Math.min(a, b)}, Infinity);
Another one, similar to Kennebec's answer, but all in one line:
maxsort = myArray.slice(0).sort(function (a, b) { return b.ID - a.ID })[0].ID;
You can use built-in Array object to use Math.max/Math.min instead:
var arr = [1,4,2,6,88,22,344];
var max = Math.max.apply(Math, arr);// return 344
var min = Math.min.apply(Math, arr);// return 1
grouperArray.sort(function (a, b) {
var aSize = a.gsize;
var bSize = b.gsize;
var aLow = a.glow;
var bLow = b.glow;
console.log(aLow + " | " + bLow);
return (aSize < bSize) ? -1 : (aSize > bSize) ? 1 : 0;
});
This code sorts the array by gsize, smallest to largest.
How would I change it to sort first by gsize and then by glow?
grouperArray.sort(function (a, b) {
return a.gsize - b.gsize || a.glow - b.glow;
});
shorter version
grouperArray.sort(function (a, b) {
var aSize = a.gsize;
var bSize = b.gsize;
var aLow = a.glow;
var bLow = b.glow;
console.log(aLow + " | " + bLow);
if(aSize == bSize)
{
return (aLow < bLow) ? -1 : (aLow > bLow) ? 1 : 0;
}
else
{
return (aSize < bSize) ? -1 : 1;
}
});
grouperArray.sort((a, b) => a.gsize - b.gsize || a.glow - b.glow);
Even shorter version using arrow syntax!
I realize this was asked some time ago, but I thought I would add my solution.
This function generates sort methods dynamically. simply supply each sortable child property name, prepended with +/- to indicate ascending or descending order. Super re-usable, and it doesn't need to know anything about the data structure you've put together. Could be made idiot proof - but doesn't seem necessary.
function getSortMethod(){
var _args = Array.prototype.slice.call(arguments);
return function(a, b){
for(var x in _args){
var ax = a[_args[x].substring(1)];
var bx = b[_args[x].substring(1)];
var cx;
ax = typeof ax == "string" ? ax.toLowerCase() : ax / 1;
bx = typeof bx == "string" ? bx.toLowerCase() : bx / 1;
if(_args[x].substring(0,1) == "-"){cx = ax; ax = bx; bx = cx;}
if(ax != bx){return ax < bx ? -1 : 1;}
}
}
}
example usage:
items.sort(getSortMethod('-price', '+priority', '+name'));
this would sort items with lowest price first, with ties going to the item with the highest priority. further ties are broken by the item name
where items is an array like:
var items = [
{ name: "z - test item", price: "99.99", priority: 0, reviews: 309, rating: 2 },
{ name: "z - test item", price: "1.99", priority: 0, reviews: 11, rating: 0.5 },
{ name: "y - test item", price: "99.99", priority: 1, reviews: 99, rating: 1 },
{ name: "y - test item", price: "0", priority: 1, reviews: 394, rating: 3.5 },
{ name: "x - test item", price: "0", priority: 2, reviews: 249, rating: 0.5 } ...
];
live demo: http://gregtaff.com/misc/multi_field_sort/
EDIT: Fixed issue with Chrome.
I expect the ternary operator ((aSize < bSize) ? -1 : (aSize > bSize) ? 1 : 0;) has you confused. You should check out the link to understand it better.
Until then, here's your code blown out into full if/else.
grouperArray.sort(function (a, b) {
if (a.gsize < b.gsize)
{
return -1;
}
else if (a.gsize > b.gsize)
{
return 1;
}
else
{
if (a.glow < b.glow)
{
return -1;
}
else if (a.glow > b.glow)
{
return 1;
}
return 0;
}
});
Here's an implementation for those who may want something more generic that would work with any number of fields.
Array.prototype.sortBy = function (propertyName, sortDirection) {
var sortArguments = arguments;
this.sort(function (objA, objB) {
var result = 0;
for (var argIndex = 0; argIndex < sortArguments.length && result === 0; argIndex += 2) {
var propertyName = sortArguments[argIndex];
result = (objA[propertyName] < objB[propertyName]) ? -1 : (objA[propertyName] > objB[propertyName]) ? 1 : 0;
//Reverse if sort order is false (DESC)
result *= !sortArguments[argIndex + 1] ? 1 : -1;
}
return result;
});
}
Basically, you may specify any number of property name / sort direction:
var arr = [{
LastName: "Doe",
FirstName: "John",
Age: 28
}, {
LastName: "Doe",
FirstName: "Jane",
Age: 28
}, {
LastName: "Foo",
FirstName: "John",
Age: 30
}];
arr.sortBy("LastName", true, "FirstName", true, "Age", false);
//Will return Jane Doe / John Doe / John Foo
arr.sortBy("Age", false, "LastName", true, "FirstName", false);
//Will return John Foo / John Doe / Jane Doe
grouperArray.sort(function (a, b) {
var aSize = a.gsize;
var bSize = b.gsize;
var aLow = a.glow;
var bLow = b.glow;
console.log(aLow + " | " + bLow);
return (aSize < bSize) ? -1 : (aSize > bSize) ? 1 : ( (aLow < bLow ) ? -1 : (aLow > bLow ) ? 1 : 0 );
});
grouperArray.sort(function (a, b) {
var aSize = a.gsize;
var bSize = b.gsize;
var aLow = a.glow;
var bLow = b.glow;
console.log(aLow + " | " + bLow);
return (aSize < bSize) ? -1 : (aSize > bSize) ? 1 : (aLow < bLow) ? -1 : (aLow > bLow) ? 1 : 0); });
Here is an implementation that uses recursion to sort by any number of sort fields from 1 to infinite. You pass it a results array which is an array of result objects to sort, and a sorts array which is an array of sort objects defining the sort. Each sort object must have a "select" key for the key name that it sorts by and an "order" key which is a string indicating "ascending" or "descending".
sortMultiCompare = (a, b, sorts) => {
let select = sorts[0].select
let order = sorts[0].order
if (a[select] < b[select]) {
return order == 'ascending' ? -1 : 1
}
if (a[select] > b[select]) {
return order == 'ascending' ? 1 : -1
}
if(sorts.length > 1) {
let remainingSorts = sorts.slice(1)
return this.sortMultiCompare(a, b, remainingSorts)
}
return 0
}
sortResults = (results, sorts) => {
return results.sort((a, b) => {
return this.sortMultiCompare(a, b, sorts)
})
}
// example inputs
const results = [
{
"LastName": "Doe",
"FirstName": "John",
"MiddleName": "Bill"
},
{
"LastName": "Doe",
"FirstName": "Jane",
"MiddleName": "Bill"
},
{
"LastName": "Johnson",
"FirstName": "Kevin",
"MiddleName": "Bill"
}
]
const sorts = [
{
"select": "LastName",
"order": "ascending"
},
{
"select": "FirstName",
"order": "ascending"
},
{
"select": "MiddleName",
"order": "ascending"
}
]
// call the function like this:
let sortedResults = sortResults(results, sorts)
A dynamic way to do that with MULTIPLE keys:
filter unique values from each col/key of sort
put in order or reverse it
add weights width zeropad for each object based on indexOf(value) keys values
sort using caclutated weights
Object.defineProperty(Array.prototype, 'orderBy', {
value: function(sorts) {
sorts.map(sort => {
sort.uniques = Array.from(
new Set(this.map(obj => obj[sort.key]))
);
sort.uniques = sort.uniques.sort((a, b) => {
if (typeof a == 'string') {
return sort.inverse ? b.localeCompare(a) : a.localeCompare(b);
}
else if (typeof a == 'number') {
return sort.inverse ? (a < b) : (a > b ? 1 : 0);
}
else if (typeof a == 'boolean') {
let x = sort.inverse ? (a === b) ? 0 : a? -1 : 1 : (a === b) ? 0 : a? 1 : -1;
return x;
}
return 0;
});
});
const weightOfObject = (obj) => {
let weight = "";
sorts.map(sort => {
let zeropad = `${sort.uniques.length}`.length;
weight += sort.uniques.indexOf(obj[sort.key]).toString().padStart(zeropad, '0');
});
//obj.weight = weight; // if you need to see weights
return weight;
}
this.sort((a, b) => {
return weightOfObject(a).localeCompare( weightOfObject(b) );
});
return this;
}
});
Use:
// works with string, number and boolean
let sortered = your_array.orderBy([
{key: "type", inverse: false},
{key: "title", inverse: false},
{key: "spot", inverse: false},
{key: "internal", inverse: true}
]);
This is what I use
function sort(a, b) {
var _a = "".concat(a.size, a.glow);
var _b = "".concat(b.size, b.glow);
return _a < _b;
}
concat the two items as a string and they will be sorted by a string value. If you want you could wrap _a and _b with parseInt to compare them as numbers if you know they will be numerical.
Here is the solution for the case, when you have a priority sort key, which might not exist in some particular items, so you have to sort by fallback keys.
An input data example (id2 is priority sort key):
const arr = [
{id: 1},
{id: 2, id2: 3},
{id: 4},
{id: 3},
{id: 10, id2: 2},
{id: 7},
{id: 6, id2: 1},
{id: 5},
{id: 9, id2: 2},
{id: 8},
];
And the output should be:
[ { id: 6, id2: 1 },
{ id: 9, id2: 2 },
{ id: 10, id2: 2 },
{ id: 2, id2: 3 },
{ id: 1 },
{ id: 3 },
{ id: 4 },
{ id: 5 },
{ id: 7 },
{ id: 8 } ]
The comparator function will be like:
arr.sort((a,b) => {
if(a.id2 || b.id2) {
if(a.id2 && b.id2) {
if(a.id2 === b.id2) {
return a.id - b.id;
}
return a.id2 - b.id2;
}
return a.id2 ? -1 : 1;
}
return a.id - b.id
});
P.S. In case if .id of .id2 can be zeros, consider to use typeof.
Let's simplify.
Say you have an array of arrays:
let tmp = [
[0, 1],
[2, 1],
[1, 1],
[0, 0],
[2, 0],
[1, 0],
[0, 2],
[2, 2],
[1, 2],
]
Executing:
tmp.sort((a, b) => {
if (a[1] != b[1])
return a[1] - b[1];
else
return a[0] - b[0];
})
Will yield:
[
[0, 0],
[1, 0],
[2, 0],
[0, 1],
[1, 1],
[2, 1],
[0, 2],
[1, 2],
[2, 2]
]
var items = [
{ name: "z - test item", price: "99.99", priority: 0, reviews: 309, rating: 2 },
{ name: "z - test item", price: "1.99", priority: 0, reviews: 11, rating: 0.5 },
{ name: "y - test item", price: "99.99", priority: 1, reviews: 99, rating: 1 },
{ name: "y - test item", price: "0", priority: 1, reviews: 394, rating: 3.5 },
{ name: "x - test item", price: "0", priority: 2, reviews: 249, rating: 0.5 }];
items.sort(function (a, b) {
var nameA = a.name.toUpperCase();
var nameB = b.name.toUpperCase();
var nameC = a.price.toUpperCase();
var nameD = b.price.toUpperCase();
if (nameA < nameB) {
return -1;
}
if (nameA > nameB || nameC > nameD) {
return 1;
}
// names must be equal
return 0;
});`
grouperArray.sort(
function(a,b){return a.gsize == b.gsize ? a.glow - b.glow : a.gsize - b.gsize}
);
grouperArray.sort(function (a, b) {
var aSize = a.gsize;
var bSize = b.gsize;
if (aSize !== aSize)
return aSize - bSize;
return a.glow - b.glow;
});
not tested, but I think that should work.
In my case, i sort notification list by param 'important' and by 'date'
step 1: i filter notifications by 'important' and unImportant
let importantNotifications = notifications.filter(
(notification) => notification.isImportant);
let unImportantNotifications = notifications.filter(
(notification) => !notification.isImportant);
step 2: i sort them by date
sortByDate = (notifications) => {
return notifications.sort((notificationOne, notificationTwo) => {
return notificationOne.date - notificationTwo.date;
});
};
step 3: merge them
[
...this.sortByDate(importantNotifications),
...this.sortByDate(unImportantNotifications),
];
If you're happy to use the new tidy.js package you can achieve this with
tidy(input_array,
arrange(['var1', desc('var2')])
);
Besides the other answers here I got inconsistent data on my arrays where 1 wanted a primary ASC sort on field x and a secondary DESC sort on field y.
The solution is in giving the primary sort more importance by multiplying the number with lets say 1000000000
arrayOfObjects.sort((a, b) => {
return (
// Multiply by a high number to the most important sort, that makes them heavier than the second sort
// First sort ASC (notice the - minus in the end instead of the || in other answers !)
(a.paramX * 1000000000) -
(b.paramX * 1000000000) -
// Second sort DESC (switch them if you want ASC too)
(a.paramY - b.paramY)
)
})
for sorting on multiple dates on the object it is this:
// param date1 ASC and param date2 DESC
arrayOfObjects.sort((a, b) => {
return (
(a.date1.getTime() * 1000000000) -
(b.date1.getTime() * 1000000000) -
(a.date2.getTime() - b.date2.getTime())
)
})
I'm making a simple RPG and trying to calculate which attribute should be increased when a character levels up. They have a potential limit for each attribute and I want to increment the attribute that is furthest from its potential.
I can loop through each attribute and subtract its current value from its potential value to get the difference. I can then push the difference to an array. The result looks like:
[
{Strength: 5},
{Dexterity: 6},
{Constitution: 3},
{Wisdom: 4},
{Charisma: 8}
]
Charisma is the key with the highest difference, so how can I evaluate this and return the name of the key (not the value itself)?
EDIT: Here is the logic which is used to get the array:
let difference = [];
let key;
for (key in currentAttributes) {
difference.push({[key]: potentialAttributes[key] - currentAttributes[key]});
};
Simple reduce with Object.entries
const items = [
{ Strength: 5 },
{ Dexterity: 6 },
{ Constitution: 3 },
{ Wisdom: 4 },
{ Charisma: 8 }
]
const biggest = items.reduce((biggest, current, ind) => {
const parts = Object.entries(current)[0] //RETURNS [KEY, VALUE]
return (!ind || parts[1] > biggest[1]) ? parts : biggest // IF FIRST OR BIGGER
}, null)
console.log(biggest[0]) // 0 = KEY, 1 = BIGGEST VALUE
Your data model is a bit weird with the array with objects, a better model would just be an object.
const items = {
Strength: 5,
Dexterity: 6,
Constitution: 3,
Wisdom: 4,
Charisma: 8
}
const biggest = Object.entries(items)
.reduce((biggest, current, ind) => {
const parts = current
return (!ind || parts[1] > biggest[1]) ? parts : biggest
}, null)
console.log(biggest[0])
You could create an object, take the entries and reduce the entries by taking the entry with the greatest value. At the end take the key from the entry.
var data = [{ Strength: 5 }, { Dexterity: 6 }, { Constitution: 3 }, { Wisdom: 4 }, { Charisma: 8 }],
greatest = Object
.entries(Object.assign({}, ...data))
.reduce((a, b) => a[1] > b[1] ? a : b)
[0];
console.log(greatest);
Sort in descending order and grab the first item:
let attributes = [
{Strength: 5},
{Dexterity: 6},
{Constitution: 3},
{Wisdom: 4},
{Charisma: 8}
];
//for convenience
const getValue = obj => Object.values(obj)[0];
//sort descending
attributes.sort((a, b) => getValue(b) - getValue(a));
let highest = attributes[0];
console.log(Object.keys(highest)[0]);
Alternatively, go though the array and find the highest score:
let attributes = [
{Strength: 5},
{Dexterity: 6},
{Constitution: 3},
{Wisdom: 4},
{Charisma: 8}
];
//for convenience
const getValue = obj => Object.values(obj)[0];
//find the highest score
let highest = attributes.reduce((currentHighest, nextItem) => getValue(currentHighest) > getValue(nextItem) ? currentHighest : nextItem);
console.log(Object.keys(highest)[0]);
I have an array like this: arr = {lst1: 300, lst2: 381, lst3: 4, lst4: 4, lst5: 49, …}
And I'm trying get the lowest value with key using Javascript.
What I've tried:
alert(Math.min.apply(Math, arr)); returns Infinity I don't know why
I got this on Google, just for try:
var keys = Object.keys(arr).map(Number).filter(function(a){
return arr[a];
}); alert(Math.min.apply(Math, keys));
returns Infinity too
I want something more complete, like this output: "The lowest value is 2 from lst9".
I really tried fix it myself before asking here, but without success!
Can you help me fix this "Infinity" issue? Thank you.
You can get the key and value using Object.entries:
var arr = {
lst1: 300,
lst2: 381,
lst3: 4,
lst4: 4,
lst5: 49
};
function lowestValueAndKey(obj) {
var [lowestItems] = Object.entries(obj).sort(([ ,v1], [ ,v2]) => v1 - v2);
return `Lowest value is ${lowestItems[1]}, with a key of ${lowestItems[0]}`;
}
var lowest = lowestValueAndKey(arr);
console.log(lowest);
There are many ways to approach your problem, but here is one simple alternative way to do it. Basically, it is a brute force way of doing things, whereby you iterate through every property to check for the value.
const obj = {lst1: 300, lst2: 381, lst3: 4, lst4: 4, lst5: 49};
const getSmallest = function (object) {
let smallestValue = Number.MAX_VALUE;
let selectedKey = '';
for (let key in object) {
if (object[key] < smallestValue) {
smallestValue = object[key];
selectedKey = key;
}
};
console.log(selectedKey, smallestValue)
return `The lowest value is ${smallestValue} from ${selectedKey}`;
};
getSmallest(obj);
There are several ways to arrive at what you want. Please see the following methods:
const obj = { lst1: 300, lst2: 381, lst3: 4, lst4: 4, lst5: 49 }
const values = Object.values(obj)
const lowest = Math.min.apply(null, values)
// Using Object.keys(), Object.values() and findIndex()
const keys = Object.keys(obj)
const indexOfLowest = values.findIndex(function (x) { return x === lowest })
console.log(`The lowest value is ${lowest} from ${keys[indexOfLowest]}`)
// OR Using Object.keys() and filter()
const key = Object.keys(obj).filter(function (x) { return obj[x] === lowest })[0]
console.log(`The lowest value is ${lowest} from ${key}`)
// OR Using Object.entries() and sort()
const [[lowestKey, lowestVal]] = Object.entries(obj).sort(function ([,valA], [,valB]) { return valA - valB });
console.log(`The lowest value is ${lowestVal} from ${lowestKey}`)
To get only the minimum number you can use Math.min(...Object.entries(arr).map(o => o[1])):
const arr = {lst1: 300, lst2: 381, lst3: 4, lst4: 4, lst5: 49};
const lowestValue = Math.min(...Object.entries(arr).map(o => o[1]));
console.log(lowestValue);
And to get the object with the minimum value you can use Object.entries(arr).reduce((a, [k, v]) => a[Object.keys(a)[0]] < v ? a : {[k]: v}, {}):
const arr = {lst1: 300, lst2: 381, lst3: 4, lst4: 4, lst5: 49};
const lowestObj = Object.entries(arr).reduce((a, [k, v]) => a[Object.keys(a)[0]] < v ? a : {[k]: v}, {});
console.log(lowestObj);
Here we first assume that the min, key and value as first property and the value respectively. Then we iterate through all the elements and compare with minValue to find any value less than that in the object. If it is, then we update both minValue and minKey. Anyway, the arr type is an Object. it is not an array in javascript.
var arr = {lst1: 300, lst2: 381, lst3: 4, lst4: 4, lst5: 49};
var minkey = Object.keys(arr)[0];
var minValue = arr[Object.keys(arr)[0]];
for (var key in arr) {
if (arr.hasOwnProperty(key)) {
if(arr[key] < minValue){
minValue = arr[key];
minKey = key;
}
}
}
You can try this:
obj = {lst1: 300, lst2: 381, lst3: 4, lst4: 4, lst5: 49, lst9: 2}; // This is an object, That;s ehy you getting infinite
var arr = Object.keys( obj ).map(function ( key ) { return obj[key]; });
alert(Math.min.apply( null, arr ));
Fiddle: https://jsfiddle.net/ozvubmdr/
Easy, though not optimal, as it traverses the array twice: find the smallest value, then find the key belonging to it.
arr = {lst1: 300, lst2: 381, lst3: 4, lst4: 4, lst5: 49, lst9: 2}
min = Math.min(...Object.values(arr));
minkey = Object.keys(arr).find(key => arr[key] = min);
console.log(`Lowest is ${min} at ${minkey}`);
You may try this:
const arr = {lst1: 300, lst2: 381, lst3: 4, lst4: 4, lst5: 49, lst9: 2}
const minVal = Math.min(...Object.values(arr))
const fromKey = Object.keys(arr).find(key => arr[key] === minVal)
console.log(`The lowest value is ${minVal} from ${fromKey}`)
You can use Object.entries and use Array.reduce to reduce the resulting [key-value] pair to an object which will have lowest entry:
const data = {lst1: 300, lst2: -381, lst3: 4, lst4: 4, lst5: 49, lst6: -49, lst7: 0, lst7: 78, lst7: -90};
const obj = Object.entries(data).reduce((acc, [key, value]) =>{
acc["lowest"] = acc["lowest"] ? Object.values(acc["lowest"])[0] < value ? acc["lowest"] : {[key] : value} : {[key] : value };
return acc;
}, {});
console.log(`Lowest value is found in ${JSON.stringify(obj.lowest)}`);
You can use Object.entries and Array.reduce to keep track of the minimal pair as you go:
const findKeyForMin = obj => Object.entries(obj).reduce(([accKey, accVal], [curKey, curVal]) =>
/* Change `<` to `<=` to find the last minimum instead of the first minimum. */
curVal < accVal ? [curKey, curVal] : [accKey, accVal]
)[0];
grouperArray.sort(function (a, b) {
var aSize = a.gsize;
var bSize = b.gsize;
var aLow = a.glow;
var bLow = b.glow;
console.log(aLow + " | " + bLow);
return (aSize < bSize) ? -1 : (aSize > bSize) ? 1 : 0;
});
This code sorts the array by gsize, smallest to largest.
How would I change it to sort first by gsize and then by glow?
grouperArray.sort(function (a, b) {
return a.gsize - b.gsize || a.glow - b.glow;
});
shorter version
grouperArray.sort(function (a, b) {
var aSize = a.gsize;
var bSize = b.gsize;
var aLow = a.glow;
var bLow = b.glow;
console.log(aLow + " | " + bLow);
if(aSize == bSize)
{
return (aLow < bLow) ? -1 : (aLow > bLow) ? 1 : 0;
}
else
{
return (aSize < bSize) ? -1 : 1;
}
});
grouperArray.sort((a, b) => a.gsize - b.gsize || a.glow - b.glow);
Even shorter version using arrow syntax!
I realize this was asked some time ago, but I thought I would add my solution.
This function generates sort methods dynamically. simply supply each sortable child property name, prepended with +/- to indicate ascending or descending order. Super re-usable, and it doesn't need to know anything about the data structure you've put together. Could be made idiot proof - but doesn't seem necessary.
function getSortMethod(){
var _args = Array.prototype.slice.call(arguments);
return function(a, b){
for(var x in _args){
var ax = a[_args[x].substring(1)];
var bx = b[_args[x].substring(1)];
var cx;
ax = typeof ax == "string" ? ax.toLowerCase() : ax / 1;
bx = typeof bx == "string" ? bx.toLowerCase() : bx / 1;
if(_args[x].substring(0,1) == "-"){cx = ax; ax = bx; bx = cx;}
if(ax != bx){return ax < bx ? -1 : 1;}
}
}
}
example usage:
items.sort(getSortMethod('-price', '+priority', '+name'));
this would sort items with lowest price first, with ties going to the item with the highest priority. further ties are broken by the item name
where items is an array like:
var items = [
{ name: "z - test item", price: "99.99", priority: 0, reviews: 309, rating: 2 },
{ name: "z - test item", price: "1.99", priority: 0, reviews: 11, rating: 0.5 },
{ name: "y - test item", price: "99.99", priority: 1, reviews: 99, rating: 1 },
{ name: "y - test item", price: "0", priority: 1, reviews: 394, rating: 3.5 },
{ name: "x - test item", price: "0", priority: 2, reviews: 249, rating: 0.5 } ...
];
live demo: http://gregtaff.com/misc/multi_field_sort/
EDIT: Fixed issue with Chrome.
I expect the ternary operator ((aSize < bSize) ? -1 : (aSize > bSize) ? 1 : 0;) has you confused. You should check out the link to understand it better.
Until then, here's your code blown out into full if/else.
grouperArray.sort(function (a, b) {
if (a.gsize < b.gsize)
{
return -1;
}
else if (a.gsize > b.gsize)
{
return 1;
}
else
{
if (a.glow < b.glow)
{
return -1;
}
else if (a.glow > b.glow)
{
return 1;
}
return 0;
}
});
Here's an implementation for those who may want something more generic that would work with any number of fields.
Array.prototype.sortBy = function (propertyName, sortDirection) {
var sortArguments = arguments;
this.sort(function (objA, objB) {
var result = 0;
for (var argIndex = 0; argIndex < sortArguments.length && result === 0; argIndex += 2) {
var propertyName = sortArguments[argIndex];
result = (objA[propertyName] < objB[propertyName]) ? -1 : (objA[propertyName] > objB[propertyName]) ? 1 : 0;
//Reverse if sort order is false (DESC)
result *= !sortArguments[argIndex + 1] ? 1 : -1;
}
return result;
});
}
Basically, you may specify any number of property name / sort direction:
var arr = [{
LastName: "Doe",
FirstName: "John",
Age: 28
}, {
LastName: "Doe",
FirstName: "Jane",
Age: 28
}, {
LastName: "Foo",
FirstName: "John",
Age: 30
}];
arr.sortBy("LastName", true, "FirstName", true, "Age", false);
//Will return Jane Doe / John Doe / John Foo
arr.sortBy("Age", false, "LastName", true, "FirstName", false);
//Will return John Foo / John Doe / Jane Doe
grouperArray.sort(function (a, b) {
var aSize = a.gsize;
var bSize = b.gsize;
var aLow = a.glow;
var bLow = b.glow;
console.log(aLow + " | " + bLow);
return (aSize < bSize) ? -1 : (aSize > bSize) ? 1 : ( (aLow < bLow ) ? -1 : (aLow > bLow ) ? 1 : 0 );
});
grouperArray.sort(function (a, b) {
var aSize = a.gsize;
var bSize = b.gsize;
var aLow = a.glow;
var bLow = b.glow;
console.log(aLow + " | " + bLow);
return (aSize < bSize) ? -1 : (aSize > bSize) ? 1 : (aLow < bLow) ? -1 : (aLow > bLow) ? 1 : 0); });
Here is an implementation that uses recursion to sort by any number of sort fields from 1 to infinite. You pass it a results array which is an array of result objects to sort, and a sorts array which is an array of sort objects defining the sort. Each sort object must have a "select" key for the key name that it sorts by and an "order" key which is a string indicating "ascending" or "descending".
sortMultiCompare = (a, b, sorts) => {
let select = sorts[0].select
let order = sorts[0].order
if (a[select] < b[select]) {
return order == 'ascending' ? -1 : 1
}
if (a[select] > b[select]) {
return order == 'ascending' ? 1 : -1
}
if(sorts.length > 1) {
let remainingSorts = sorts.slice(1)
return this.sortMultiCompare(a, b, remainingSorts)
}
return 0
}
sortResults = (results, sorts) => {
return results.sort((a, b) => {
return this.sortMultiCompare(a, b, sorts)
})
}
// example inputs
const results = [
{
"LastName": "Doe",
"FirstName": "John",
"MiddleName": "Bill"
},
{
"LastName": "Doe",
"FirstName": "Jane",
"MiddleName": "Bill"
},
{
"LastName": "Johnson",
"FirstName": "Kevin",
"MiddleName": "Bill"
}
]
const sorts = [
{
"select": "LastName",
"order": "ascending"
},
{
"select": "FirstName",
"order": "ascending"
},
{
"select": "MiddleName",
"order": "ascending"
}
]
// call the function like this:
let sortedResults = sortResults(results, sorts)
A dynamic way to do that with MULTIPLE keys:
filter unique values from each col/key of sort
put in order or reverse it
add weights width zeropad for each object based on indexOf(value) keys values
sort using caclutated weights
Object.defineProperty(Array.prototype, 'orderBy', {
value: function(sorts) {
sorts.map(sort => {
sort.uniques = Array.from(
new Set(this.map(obj => obj[sort.key]))
);
sort.uniques = sort.uniques.sort((a, b) => {
if (typeof a == 'string') {
return sort.inverse ? b.localeCompare(a) : a.localeCompare(b);
}
else if (typeof a == 'number') {
return sort.inverse ? (a < b) : (a > b ? 1 : 0);
}
else if (typeof a == 'boolean') {
let x = sort.inverse ? (a === b) ? 0 : a? -1 : 1 : (a === b) ? 0 : a? 1 : -1;
return x;
}
return 0;
});
});
const weightOfObject = (obj) => {
let weight = "";
sorts.map(sort => {
let zeropad = `${sort.uniques.length}`.length;
weight += sort.uniques.indexOf(obj[sort.key]).toString().padStart(zeropad, '0');
});
//obj.weight = weight; // if you need to see weights
return weight;
}
this.sort((a, b) => {
return weightOfObject(a).localeCompare( weightOfObject(b) );
});
return this;
}
});
Use:
// works with string, number and boolean
let sortered = your_array.orderBy([
{key: "type", inverse: false},
{key: "title", inverse: false},
{key: "spot", inverse: false},
{key: "internal", inverse: true}
]);
This is what I use
function sort(a, b) {
var _a = "".concat(a.size, a.glow);
var _b = "".concat(b.size, b.glow);
return _a < _b;
}
concat the two items as a string and they will be sorted by a string value. If you want you could wrap _a and _b with parseInt to compare them as numbers if you know they will be numerical.
Here is the solution for the case, when you have a priority sort key, which might not exist in some particular items, so you have to sort by fallback keys.
An input data example (id2 is priority sort key):
const arr = [
{id: 1},
{id: 2, id2: 3},
{id: 4},
{id: 3},
{id: 10, id2: 2},
{id: 7},
{id: 6, id2: 1},
{id: 5},
{id: 9, id2: 2},
{id: 8},
];
And the output should be:
[ { id: 6, id2: 1 },
{ id: 9, id2: 2 },
{ id: 10, id2: 2 },
{ id: 2, id2: 3 },
{ id: 1 },
{ id: 3 },
{ id: 4 },
{ id: 5 },
{ id: 7 },
{ id: 8 } ]
The comparator function will be like:
arr.sort((a,b) => {
if(a.id2 || b.id2) {
if(a.id2 && b.id2) {
if(a.id2 === b.id2) {
return a.id - b.id;
}
return a.id2 - b.id2;
}
return a.id2 ? -1 : 1;
}
return a.id - b.id
});
P.S. In case if .id of .id2 can be zeros, consider to use typeof.
Let's simplify.
Say you have an array of arrays:
let tmp = [
[0, 1],
[2, 1],
[1, 1],
[0, 0],
[2, 0],
[1, 0],
[0, 2],
[2, 2],
[1, 2],
]
Executing:
tmp.sort((a, b) => {
if (a[1] != b[1])
return a[1] - b[1];
else
return a[0] - b[0];
})
Will yield:
[
[0, 0],
[1, 0],
[2, 0],
[0, 1],
[1, 1],
[2, 1],
[0, 2],
[1, 2],
[2, 2]
]
var items = [
{ name: "z - test item", price: "99.99", priority: 0, reviews: 309, rating: 2 },
{ name: "z - test item", price: "1.99", priority: 0, reviews: 11, rating: 0.5 },
{ name: "y - test item", price: "99.99", priority: 1, reviews: 99, rating: 1 },
{ name: "y - test item", price: "0", priority: 1, reviews: 394, rating: 3.5 },
{ name: "x - test item", price: "0", priority: 2, reviews: 249, rating: 0.5 }];
items.sort(function (a, b) {
var nameA = a.name.toUpperCase();
var nameB = b.name.toUpperCase();
var nameC = a.price.toUpperCase();
var nameD = b.price.toUpperCase();
if (nameA < nameB) {
return -1;
}
if (nameA > nameB || nameC > nameD) {
return 1;
}
// names must be equal
return 0;
});`
grouperArray.sort(
function(a,b){return a.gsize == b.gsize ? a.glow - b.glow : a.gsize - b.gsize}
);
grouperArray.sort(function (a, b) {
var aSize = a.gsize;
var bSize = b.gsize;
if (aSize !== aSize)
return aSize - bSize;
return a.glow - b.glow;
});
not tested, but I think that should work.
In my case, i sort notification list by param 'important' and by 'date'
step 1: i filter notifications by 'important' and unImportant
let importantNotifications = notifications.filter(
(notification) => notification.isImportant);
let unImportantNotifications = notifications.filter(
(notification) => !notification.isImportant);
step 2: i sort them by date
sortByDate = (notifications) => {
return notifications.sort((notificationOne, notificationTwo) => {
return notificationOne.date - notificationTwo.date;
});
};
step 3: merge them
[
...this.sortByDate(importantNotifications),
...this.sortByDate(unImportantNotifications),
];
If you're happy to use the new tidy.js package you can achieve this with
tidy(input_array,
arrange(['var1', desc('var2')])
);
Besides the other answers here I got inconsistent data on my arrays where 1 wanted a primary ASC sort on field x and a secondary DESC sort on field y.
The solution is in giving the primary sort more importance by multiplying the number with lets say 1000000000
arrayOfObjects.sort((a, b) => {
return (
// Multiply by a high number to the most important sort, that makes them heavier than the second sort
// First sort ASC (notice the - minus in the end instead of the || in other answers !)
(a.paramX * 1000000000) -
(b.paramX * 1000000000) -
// Second sort DESC (switch them if you want ASC too)
(a.paramY - b.paramY)
)
})
for sorting on multiple dates on the object it is this:
// param date1 ASC and param date2 DESC
arrayOfObjects.sort((a, b) => {
return (
(a.date1.getTime() * 1000000000) -
(b.date1.getTime() * 1000000000) -
(a.date2.getTime() - b.date2.getTime())
)
})