sort objects by a property values - javascript

I have some objects in an array and want to have them sorted. The objects should be sorted by a metric value. So I have the following function:
objectz.sort(function(a,b){
return b.metric - a.metric;
}
The problem is that some objects have the same property values and the results of the sorting are always different.I want to additionally sort the objects with the same metric value by their name property so I get the same order of objects every time I sort them.
Thx in advance!

objectz.sort(function(a,b){
var result = b.metric - a.metric;
if (!result) return a.name > b.name ? 1 : -1;
return result;
});

Similar to zerkms:
objectz.sort(function(a,b) {
var x = b.metric - a.metric;
return x || b.name - a.name;
});
Seems to be a reverse sort (higher values occur first), is that what you want?
Edit
Note that the - operator is only suitable if the value of 'name' can be converted to a number. Otherwise, use < or >. The sort function should deal with a.name == b.name, which the > opertator on its own won't do, so you need something like:
objectz.sort(function(a,b) {
var x = b.metric - a.metric;
// If b.metric == a.metric
if (!x) {
if (b.name == a.name) {
x = 0;
else if (b.name < a.name) {
x = 1;
else {
x = -1;
}
}
return x;
});
which can be made more concise:
objectz.sort(function(a,b) {
var x = b.metric - a.metric;
if (!x) {
x = (b.name == a.name)? 0 : (b.name < a.name)? 1 : -1;
}
return x;
});
Given that the metric comparison seems to be largest to smallest order, then the ternary exrpession should be:
x = (b.name == a.name)? 0 : (b.name < a.name)? -1 : 1;
if it is required that say Zelda comes before Ann. Also, the value of name should be reduced to all lower case (or all upper case), otherwise 'zelda' and 'Ann' will be sorted in the opposite order to 'Zelda' and 'ann'

Related

Making compare() function more elegant

I have this code:
function compare (a, b) {
let comparison = 0;
if (a.essentialsPercentage < b.essentialsPercentage) {
comparison = 1;
} else if (a.essentialsPercentage > b.essentialsPercentage) {
comparison = -1;
} else {
if (a.skillsNicePercentage < b.skillsNicePercentage) {
comparison = 1;
} else if (a.skillsNicePercentage > b.skillsNicePercentage) {
comparison = -1;
} else {
if (a.startDate > b.startDate) {
comparison = 1
} else if (a.startDate < b.startDate) {
comparison = -1
}
}
}
return comparison;
}
What would be the most elegant way of writing it? It doesn't seems nice at the moment.
Assuming this is being used as the comparison function for Array.prototype.sort(), only the sign of the result matters, it doesn't have to be specifically -1 or 1. So instead of if and else, you can simply subtract the numbers.
compare(a, b) {
let comparison = b.essentialPercentage - a.essentialPercentage;
if (comparison == 0) {
comparison = b.skillsNicePercentage - a.skillsNicePercentage;
if (comparison == 0) {
comparison = a.startDate - b.startDate;
}
}
return comparison;
}
If any of the properties are strings rather than numbers, you can use localCompare instead of subtraction.
This tiny function (or the equivalent <=> operator) is perhaps the most obvious lack in the js standard library:
// returns 1 if a > b, -1 if a < b, 0 if a == b
let cmp = (a, b) => (a > b) - (a < b)
Once you have defined it, chained comparisons are very easy:
compare = (a, b) =>
cmp(a.essentialsPercentage, b.essentialsPercentage)
|| cmp(a.skillsNicePercentage, b.skillsNicePercentage)
|| cmp(a.startDate, b.startDate)
If you wanted to you could use a switch statement to keep each one of your cases nice and organized. This would be a "cleaner" method but not necessarily the most correct method. See this thread --> https://stackoverflow.com/a/2312837/11263228
Alternatively, you could create separate functions that will check each of your cases. For example, having a function that takes in a.skillsNicePercentage and b.skillsNicePercentage as parameters and returns true/false. This would be cleaner and also reusable!
You could generalize this in a simpler function that takes an array of field names and sorts the overall array based on those fields, in order. The code below takes some inspiration from the Mongoose database library and allows the - prefix on a field name to sort descending instead of the default ascending order. It also only works for numbers and strings; if you want to support other types, you'll have to extend the code.
function multiSort(fields) {
return (a,b) => {
let result = 0;
for (let i = 0; result === 0 && i < fields.length; ++i) {
let fieldName = fields[i];
if (fieldName.charAt(0) === '-') {
fieldName = fieldName.substring(1);
if (typeof a[fieldName] === 'string') {
result = b[fieldName].localeCompare(a[fieldName]);
}
else {
result = b[fieldName] - a[fieldName];
}
} else {
if (typeof a[fieldName] === 'string') {
result = a[fieldName].localeCompare(b[fieldName]);
}
else {
result = a[fieldName] - b[fieldName];
}
}
}
return result;
};
}
This higher-order function will take an array of fields and return a function that sorts by those fields, in order, for strings and numbers, optionally with a field name prepended by - to sort that field in descending order. You'd use it like this:
someArrayValue.sort(multisort(['essentialsPercentage', 'skillsNicePercentage', '-startDate']));
Based on the logic in your comparison, this should work
function compare (a, b) {
let comparison = a.skillsNicePercentage == b.skillsNicePercentage ? (a.startDate - b.startDate) : b.skillsNicePercentage - a.skillsNicePercentage
let comparison1 = a.essentialsPercentage == b.essentialsPercentage ? b.skillsNicePercentage - a.skillsNicePercentage : comparison
return comparison1;
}
Try
function compare(a, b) {
let c= b.essentialsPercentage - a.essentialsPercentage;
let d= b.skillsNicePercentage - a.skillsNicePercentage;
let e= a.startDate - b.startDate
return Math.sign(c||d||e);
}
function compareNew(a, b) {
let c= b.essentialsPercentage - a.essentialsPercentage;
let d= b.skillsNicePercentage - a.skillsNicePercentage;
let e= a.startDate - b.startDate
return Math.sign(c||d||e);
}
function compareOld(a, b) {
let comparison = 0;
if (a.essentialsPercentage < b.essentialsPercentage) {
comparison = 1;
} else if (a.essentialsPercentage > b.essentialsPercentage) {
comparison = -1;
} else {
if (a.skillsNicePercentage < b.skillsNicePercentage) {
comparison = 1;
} else if (a.skillsNicePercentage > b.skillsNicePercentage) {
comparison = -1;
} else {
if (a.startDate > b.startDate) {
comparison = 1
} else if (a.startDate < b.startDate) {
comparison = -1
}
}
}
return comparison;
}
// TESTS
a={essentialsPercentage:2,skillsNicePercentage:2,startDate:new Date(0)};
tests=[
{essentialsPercentage:2,skillsNicePercentage:2,startDate:new Date(0)},
{essentialsPercentage:2,skillsNicePercentage:2,startDate:new Date(+10000)},
{essentialsPercentage:2,skillsNicePercentage:2,startDate:new Date(-10000)},
{essentialsPercentage:2,skillsNicePercentage:2,startDate:new Date()},
{essentialsPercentage:2,skillsNicePercentage:3,startDate:new Date()},
{essentialsPercentage:2,skillsNicePercentage:1,startDate:new Date()},
{essentialsPercentage:3,skillsNicePercentage:1,startDate:new Date()},
{essentialsPercentage:1,skillsNicePercentage:1,startDate:new Date()},
]
tests.map(b=> console.log(`old: ${compareNew(a,b)} new:${ compareOld(a,b)}`));

Sort the data having combination of letters and digits has value in array of json in javascript

How to sort the array of json data having letters and digits?
JS:
function sortOn(property) {
return function (a, b) {
if (a[property] < b[property]) {
return -1;
} else if (a[property] > b[property]) {
return 1;
} else {
return 0;
}
}
}
var data = [{"id":1,name:"text10"},
{"id":4,"name":"text1"}, {"id":4,"name":"text19"}, {"id":4,"name":"text2"}, {"id":4,"name":"text20"},
{"id":5,"name":"book"}];
data.sort(sortOn('name'));
console.log(data);// when I print the JSON getting book,text1,text10..
//But I have to want to show the json as book,text1,text2...
Any one can help me how to sort the name thing having both letters and digits
Please find the jsfiddle for reference.
Very simplistic implementation. Sort on either the number in the name and if it doesn't exist, use the full name. Could be sped up by caching the parseInt || name comparison.
var result = data.sort(function ( a, b ) {
var first = parseInt(/\d+/g.exec(a.name), 10) || a.name,
second = parseInt(/\d+/g.exec(b.name), 10) || b.name;
if (first > second) return 1;
else if (first === second) return 0;
else return -1;
return -1
});

How to .sort based on multiple conditions

I have an array items like so:
items.sort(function(a,b) {
});
a & b have the following properties:
a.suggested (true, false, undefined)
a.meta.member_count (int .. 1 or more, undefined.
I would like to sort items as follows:
all items that are not suggested. Either false or undefined
member_count, highest # first,
Then show any suggested === true
Is sort the right way to go about this? Any suggestions on how I can make this happen in the sort function?
Thank you
This will sort by the 'suggested' value, putting false/undefined values above true values. After that, it will sort by meta.member_count (highest first).
items.sort(function(a,b) {
if (a.meta.member_count === undefined) { x = 0; } else { x = a.meta.member_count;}
if (b.meta.member_count === undefined) { y = 0; } else { y = b.meta.member_count;}
if (a.suggested !== true) {
if (b.suggested !== true) {
n = x - y;
return n < 0 ? 1 : n > 0 ? -1 : 0;
}
return -1;
} else if (b.suggested === true) {
n = x - y;
return n < 0 ? 1 : n > 0 ? -1 : 0;
} else {
return 1;
}
});
The key in this case lies in converting the undefined member_count values to 0 so we can do math with them.
Also, when sorting numbers using the JavaScript sort() function, make sure to do math on the actual numbers and then use an if statement to return 1, -1, or 0. If you return the difference between the values directly, it will compare the values as strings instead of numbers.
Fiddle

How can one compare string and numeric values (respecting negative values, with null always last)?

I'm trying to sort an array of values that can be a mixture of numeric or string values (e.g. [10,"20",null,"1","bar","-2",-3,null,5,"foo"]). How can I sort this array such that
null values are always placed last (regardless of sorting order, see jsFiddle)
negative numbers are sorted correctly (i.e. they are less than positive numbers and sort correctly amongst themselves)
? I made a jsFiddle with detailed numeric and string examples (using localeCompare and the numeric option), but will paste the numeric version of my sorting algorithm below as a starting point.
// Sorting order
var order = "asc"; // Try switching between "asc" and "dsc"
// Dummy arrays
var numericArr = [10,20,null,1,-2,-3,null,5];
// Sort arrays
$(".output1").append(numericArr.toString());
numericArr.sort(sortByDataNumeric);
$(".output2").append(numericArr.toString());
// Numeric sorting function
function sortByDataNumeric(a, b, _order) {
// Replace internal parameters if not used
if (_order == null) _order = order;
// If values are null, place them at the end
var dflt = (_order == "asc" ? Number.MAX_VALUE : -Number.MAX_VALUE);
// Numeric values
var aVal = (a == null ? dflt : a);
var bVal = (b == null ? dflt : b);
return _order == "asc" ? (aVal - bVal) : (bVal - aVal);
}
The problem with my string sorting algorithm (see jsFiddle) is that I can't find a way to always place null values last and negative values aren't correctly sorted within themselves (e.g. -3 should be less than -2)
Edit
To answer the comments, I expect [10,"20",null,"1","bar","-2",-3,null,5,"foo"] to sort to [-3,"-2","1",5,10,"20","bar","foo",null,null]
You should first check to see if either value is null and return the opposite value.
On a side note:
For your default _order value, you should check if the parameter is undefined instead of comparing its value to null. If you try to compare something that is undefined directly you will get a reference error:
(undefinedVar == null) // ReferenceError: undefinedVar is not defined
Instead, you should check if the variable is undefined:
(typeof undefinedVar == "undefined") // true
Also, it's probably a better idea to wrap your compare function in a closure instead of relying on a global order variable.
Sometime like:
[].sort(function(a, b){ return sort(a, b, order)})
This way you can sort at a per-instance level.
http://jsfiddle.net/gxFGN/10/
JavaScript
function sort(a, b, asc) {
var result;
/* Default ascending order */
if (typeof asc == "undefined") asc = true;
if (a === null) return 1;
if (b === null) return -1;
if (a === null && b === null) return 0;
result = a - b;
if (isNaN(result)) {
return (asc) ? a.toString().localeCompare(b) : b.toString().localeCompare(a);
}
else {
return (asc) ? result : -result;
}
}
function sortByDataString(a, b) {
if (a === null) {
return 1;
}
if (b === null) {
return -1;
}
if (isNumber(a) && isNumber(b)) {
if (parseInt(a,10) === parseInt(b,10)) {
return 0;
}
return parseInt(a,10) > parseInt(b,10) ? 1 : -1;
}
if (isNumber(a)) {
return -1;
}
if (isNumber(b)) {
return 1;
}
if (a === b) {
return 0;
}
return a > b ? 1 : -1;
}
fiddle here: http://jsfiddle.net/gxFGN/6/
I left out the order parameter, but you could always reverse the array at the end if needed.
Use this:
function typeOrder(x) {
if (x == null)
return 2;
if (isNaN(+x))
return 1;
return 0;
}
function sortNumber(a, b) {
a = parseInt(a, 10); b = parseInt(b, 10);
if (isNaN(a) || isNaN(b))
return 0;
return a - b;
}
function sortString(a, b) {
if (typeof a != "string" || typeof b != "string")
return 0;
return +(a > b) || -(b > a);
}
order = order == "dsc" ? -1 : 1;
numericArr.sort(function(a, b) {
return order * ( typeOrder(a)-typeOrder(b)
|| sortNumber(a, b)
|| sortString(a, b)
);
});
(updated fiddle)
I'm pretty sure that your problem is a red herring... the abstract function that you past into sort doesn't get a third parameter (in your case _order). So in your situation that's always going to be undefined.
Please reconsider your code with that in mind and see what you get.
The array you specify is entirely Numeric so your sort should work correctly, though as other commenters have suggested, if your array ever winds up with string values (i.e. "10", "-7" etc) you'll want to parseInt and test for isNaN before doing your comparison.

javascript sort array

My array isn't being sorted properly. Can someone let me know what I am doing wrong?
...
sortArray = new Array ("hello", "Link to Google", "zFile", "aFile");
//sort array
if (dir == "asc") {
sortArray.sort(function(a,b){return a - b});
} else {
sortArray.sort(function(a,b){return b - a});
}
for(var i=0; i<sortArray.length; i++) {
console.log(sortArray[i]);
}
the log is showing them in the same order as they were entered.
You want to make a comparison in your sort, not a subtraction:
if (dir == "asc") {
sortArray.sort(function(a, b) {
a = a.toLowerCase();
b = b.toLowerCase();
return a === b ? 0 : a > b : 1 : -1;
});
} else {
sortArray.sort(function(a, b) {
a = a.toLowerCase();
b = b.toLowerCase();
return b === a ? 0 : b > a : 1 : -1;
});
}
I also used toLowerCase() so that 'Link to Google' is placed appropriately.
EDIT: Updated to fix comparison issue according to comment.
See example →
You're trying to sort by subtracting strings, to which you'll get NaN.
The trouble is that "a - b" is treating the strings like numbers, which returns NaN. You will get the behavior you are looking for (assuming you are looking for case-sensitive sorts) if you replace your sorts with:
if (dir == "asc") {
sortArray.sort(function(a,b){return a < b ? -1 : 1});
} else {
sortArray.sort(function(a,b){return b < a ? -1 : 1});
}
Your comparator functions returns NaN, since it receives two strings, and performs subtraction, an operation that isn't well-defined on strings.
What you should have is something more like:
function(a,b){
return a>b? 1 : (a<b ? -1 : 0);
}
or you can use localeCompare:
function(a,b){
return a.localeCompare(b);
}
Remember to treat case appropriately, e.g. "L" < "a" whilst "l" > "a"

Categories

Resources