Related
I have an array of following strings:
['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0']
...etc.
I need a solution that will give me following ordered result
['4.5.0', '4.21.0', '4.22.0', '5.1.0', '5.5.1', '6.1.0'].
I tried to implement a sort so it first sorts by the numbers in the first position, than in case of equality, sort by the numbers in the second position (after the first dot), and so on...
I tried using sort() and localeCompare(), but if I have elements '4.5.0' and '4.11.0', I get them sorted as ['4.11.0','4.5.0'], but I need to get ['4.5.0','4.11.0'].
How can I achieve this?
You could prepend all parts to fixed size strings, then sort that, and finally remove the padding again.
var arr = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];
arr = arr.map( a => a.split('.').map( n => +n+100000 ).join('.') ).sort()
.map( a => a.split('.').map( n => +n-100000 ).join('.') );
console.log(arr)
Obviously you have to choose the size of the number 100000 wisely: it should have at least one more digit than your largest number part will ever have.
With regular expression
The same manipulation can be achieved without having to split & join, when you use the callback argument to the replace method:
var arr = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];
arr = arr.map( a => a.replace(/\d+/g, n => +n+100000 ) ).sort()
.map( a => a.replace(/\d+/g, n => +n-100000 ) );
console.log(arr)
Defining the padding function once only
As both the padding and its reverse functions are so similar, it seemed a nice exercise to use one function f for both, with an extra argument defining the "direction" (1=padding, -1=unpadding). This resulted in this quite obscure, and extreme code. Consider this just for fun, not for real use:
var arr = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];
arr = (f=>f(f(arr,1).sort(),-1)) ((arr,v)=>arr.map(a=>a.replace(/\d+/g,n=>+n+v*100000)));
console.log(arr);
Use the sort compare callback function
You could use the compare function argument of sort to achieve the same:
arr.sort( (a, b) => a.replace(/\d+/g, n => +n+100000 )
.localeCompare(b.replace(/\d+/g, n => +n+100000 )) );
But for larger arrays this will lead to slower performance. This is because the sorting algorithm will often need to compare a certain value several times, each time with a different value from the array. This means that the padding will have to be executed multiple times for the same number. For this reason, it will be faster for larger arrays to first apply the padding in the whole array, then use the standard sort, and then remove the padding again.
But for shorter arrays, this approach might still be the fastest. In that case, the so-called natural sort option -- that can be achieved with the extra arguments of localeCompare -- will be more efficient than the padding method:
var arr = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];
arr = arr.sort( (a, b) => a.localeCompare(b, undefined, { numeric:true }) );
console.log(arr);
More about the padding and unary plus
To see how the padding works, look at the intermediate result it generates:
[ "100005.100005.100001", "100004.100021.100000", "100004.100022.100000",
"100006.100001.100000", "100005.100001.100000" ]
Concerning the expression +n+100000, note that the first + is the unary plus and is the most efficient way to convert a string-encoded decimal number to its numerical equivalent. The 100000 is added to make the number have a fixed number of digits. Of course, it could just as well be 200000 or 300000. Note that this addition does not change the order the numbers will have when they would be sorted numerically.
The above is just one way to pad a string. See this Q&A for some other alternatives.
If you are looking for a npm package to compare two semver version, https://www.npmjs.com/package/compare-versions is the one.
Then you can sort version like this:
// ES6/TypeScript
import compareVersions from 'compare-versions';
var versions = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];
var sorted = versions.sort(compareVersions);
You could split the strings and compare the parts.
function customSort(data, order) {
function isNumber(v) {
return (+v).toString() === v;
}
var sort = {
asc: function (a, b) {
var i = 0,
l = Math.min(a.value.length, b.value.length);
while (i < l && a.value[i] === b.value[i]) {
i++;
}
if (i === l) {
return a.value.length - b.value.length;
}
if (isNumber(a.value[i]) && isNumber(b.value[i])) {
return a.value[i] - b.value[i];
}
return a.value[i].localeCompare(b.value[i]);
},
desc: function (a, b) {
return sort.asc(b, a);
}
}
var mapped = data.map(function (el, i) {
return {
index: i,
value: el.split('')
};
});
mapped.sort(sort[order] || sort.asc);
return mapped.map(function (el) {
return data[el.index];
});
}
var array = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0'];
console.log('sorted array asc', customSort(array));
console.log('sorted array desc ', customSort(array, 'desc'));
console.log('original array ', array);
.as-console-wrapper { max-height: 100% !important; top: 0; }
You can check in loop if values are different, return difference, else continue
var a=['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];
a.sort(function(a,b){
var a1 = a.split('.');
var b1 = b.split('.');
var len = Math.max(a1.length, b1.length);
for(var i = 0; i< len; i++){
var _a = +a1[i] || 0;
var _b = +b1[i] || 0;
if(_a === _b) continue;
else return _a > _b ? 1 : -1
}
return 0;
})
console.log(a)
Though slightly late this would be my solution;
var arr = ["5.1.1","5.1.12","5.1.2","3.7.6","2.11.4","4.8.5","4.8.4","2.10.4"],
sorted = arr.sort((a,b) => {var aa = a.split("."),
ba = b.split(".");
return +aa[0] < +ba[0] ? -1
: aa[0] === ba[0] ? +aa[1] < +ba[1] ? -1
: aa[1] === ba[1] ? +aa[2] < +ba[2] ? -1
: 1
: 1
: 1;
});
console.log(sorted);
Here's a solution I developed based on #trincot's that will sort by semver even if the strings aren't exactly "1.2.3" - they could be i.e. "v1.2.3" or "2.4"
function sortSemVer(arr, reverse = false) {
let semVerArr = arr.map(i => i.replace(/(\d+)/g, m => +m + 100000)).sort(); // +m is just a short way of converting the match to int
if (reverse)
semVerArr = semVerArr.reverse();
return semVerArr.map(i => i.replace(/(\d+)/g, m => +m - 100000))
}
console.log(sortSemVer(["1.0.1", "1.0.9", "1.0.10"]))
console.log(sortSemVer(["v2.1", "v2.0.9", "v2.0.12", "v2.2"], true))
This seems to work provided there are only digits between the dots:
var a = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0']
a = a.map(function (x) {
return x.split('.').map(function (x) {
return parseInt(x)
})
}).sort(function (a, b) {
var i = 0, m = a.length, n = b.length, o, d
o = m < n ? n : m
for (; i < o; ++i) {
d = (a[i] || 0) - (b[i] || 0)
if (d) return d
}
return 0
}).map(function (x) {
return x.join('.')
})
'use strict';
var arr = ['5.1.2', '5.1.1', '5.1.1', '5.1.0', '5.7.2.2'];
Array.prototype.versionSort = function () {
var arr = this;
function isNexVersionBigger (v1, v2) {
var a1 = v1.split('.');
var b2 = v2.split('.');
var len = a1.length > b2.length ? a1.length : b2.length;
for (var k = 0; k < len; k++) {
var a = a1[k] || 0;
var b = b2[k] || 0;
if (a === b) {
continue;
} else
return b < a;
}
}
for (var i = 0; i < arr.length; i++) {
var min_i = i;
for (var j = i + 1; j < arr.length; j++) {
if (isNexVersionBigger(arr[i], arr[j])) {
min_i = j;
}
}
var temp = arr[i];
arr[i] = arr[min_i];
arr[min_i] = temp;
}
return arr;
}
console.log(arr.versionSort());
This solution accounts for version numbers that might not be in the full, 3-part format (for example, if one of the version numbers is just 2 or 2.0 or 0.1, etc).
The custom sort function I wrote is probably mostly what you're looking for, it just needs an array of objects in the format {"major":X, "minor":X, "revision":X}:
var versionArr = ['5.5.1', '4.21.0', '4.22.0', '6.1.0', '5.1.0', '4.5.0'];
var versionObjectArr = [];
var finalVersionArr = [];
/*
split each version number string by the '.' and separate them in an
object by part (major, minor, & revision). If version number is not
already in full, 3-part format, -1 will represent that part of the
version number that didn't exist. Push the object into an array that
can be sorted.
*/
for(var i = 0; i < versionArr.length; i++){
var splitVersionNum = versionArr[i].split('.');
var versionObj = {};
switch(splitVersionNum.length){
case 1:
versionObj = {
"major":parseInt(splitVersionNum[0]),
"minor":-1,
"revision":-1
};
break;
case 2:
versionObj = {
"major":parseInt(splitVersionNum[0]),
"minor":parseInt(splitVersionNum[1]),
"revision":-1
};
break;
case 3:
versionObj = {
"major":parseInt(splitVersionNum[0]),
"minor":parseInt(splitVersionNum[1]),
"revision":parseInt(splitVersionNum[2])
};
}
versionObjectArr.push(versionObj);
}
//sort objects by parts, going from major to minor to revision number.
versionObjectArr.sort(function(a, b){
if(a.major < b.major) return -1;
else if(a.major > b.major) return 1;
else {
if(a.minor < b.minor) return -1;
else if(a.minor > b.minor) return 1;
else {
if(a.revision < b.revision) return -1;
else if(a.revision > b.revision) return 1;
}
}
});
/*
loops through sorted object array to recombine it's version keys to match the original string's value. If any trailing parts of the version
number are less than 0 (i.e. they didn't exist so we replaced them with
-1) then leave that part of the version number string blank.
*/
for(var i = 0; i < versionObjectArr.length; i++){
var versionStr = "";
for(var key in versionObjectArr[i]){
versionStr = versionObjectArr[i].major;
versionStr += (versionObjectArr[i].minor < 0 ? '' : "." + versionObjectArr[i].minor);
versionStr += (versionObjectArr[i].revision < 0 ? '' : "." + versionObjectArr[i].revision);
}
finalVersionArr.push(versionStr);
}
console.log('Original Array: ',versionArr);
console.log('Expected Output: ',['4.5.0', '4.21.0', '4.22.0', '5.1.0', '5.5.1', '6.1.0']);
console.log('Actual Output: ', finalVersionArr);
Inspired from the accepted answer, but ECMA5-compatible, and with regular string padding (see my comments on the answer):
function sortCallback(a, b) {
function padParts(version) {
return version
.split('.')
.map(function (part) {
return '00000000'.substr(0, 8 - part.length) + part;
})
.join('.');
}
a = padParts(a);
b = padParts(b);
return a.localeCompare(b);
}
Usage:
['1.1', '1.0'].sort(sortCallback);
const arr = ["5.1.1","5.1.12","5.1.2","3.7.6","2.11.4","4.8.5","4.8.4","2.10.4"];
const sorted = arr.sort((a,b) => {
const ba = b.split('.');
const d = a.split('.').map((a1,i)=>a1-ba[i]);
return d[0] ? d[0] : d[1] ? d[1] : d[2]
});
console.log(sorted);
This can be in an easier way using the sort method without hardcoding any numbers and in a more generic way.
enter code here
var arr = ['5.1.2', '5.1.1', '5.1.1', '5.1.0', '5.7.2.2'];
splitArray = arr.map(elements => elements.split('.'))
//now lets sort based on the elements on the corresponding index of each array
//mapped.sort(function(a, b) {
// if (a.value > b.value) {
// return 1;
// }
// if (a.value < b.value) {
// return -1;
// }
// return 0;
//});
//here we compare the first element with the first element of the next version number and that is [5.1.2,5.7.2] 5,5 and 1,7 and 2,2 are compared to identify the smaller version...In the end use the join() to get back the version numbers in the proper format.
sortedArray = splitArray.sort((a, b) => {
for (i in a) {
if (parseInt(a[i]) < parseInt(b[i])) {
return -1;
break
}
if (parseInt(a[i]) > parseInt(b[i])) {
return +1;
break
} else {
continue
}
}
}).map(p => p.join('.'))
sortedArray = ["5.1.0", "5.1.1", "5.1.1", "5.1.2", "5.7.2.2"]
sort 1.0a notation correct
use native localeCompare to sort 1.090 notation
function log(label,val){
document.body.append(label,String(val).replace(/,/g," - "),document.createElement("BR"));
}
const sortVersions = (
x,
v = s => s.match(/[a-z]|\d+/g).map(c => c==~~c ? String.fromCharCode(97 + c) : c)
) => x.sort((a, b) => (a + b).match(/[a-z]/)
? v(b) < v(a) ? 1 : -1
: a.localeCompare(b, 0, {numeric: true}))
let v=["1.90.1","1.090","1.0a","1.0.1","1.0.0a","1.0.0b","1.0.0.1","1.0a"];
log(' input : ',v);
log('sorted: ',sortVersions(v));
log('no dups:',[...new Set(sortVersions(v))]);
In ES6 you can go without regex.
const versions = ["0.4", "0.11", "0.4.1", "0.4", "0.4.2", "2.0.1","2", "0.0.1", "0.2.3"];
const splitted = versions.map(version =>
version
.split('.')
.map(i => +i))
.map(i => {
let items;
if (i.length === 1) {
items = [0, 0]
i.push(...items)
}
if (i.length === 2) {
items = [0]
i.push(...items)
}
return i
})
.sort((a, b) => {
for(i in a) {
if (a[i] < b[i]) {
return -1;
}
if (a[i] > b[i]) {
return +1;
}
}
})
.map(item => item.join('.'))
const sorted = [...new Set(splitted)]
If ES6 I do this:
versions.sort((v1, v2) => {
let [, major1, minor1, revision1 = 0] = v1.match(/([0-9]+)\.([0-9]+)(?:\.([0-9]+))?/);
let [, major2, minor2, revision2 = 0] = v2.match(/([0-9]+)\.([0-9]+)(?:\.([0-9]+))?/);
if (major1 != major2) return parseInt(major1) - parseInt(major2);
if (minor1 != minor2) return parseInt(minor1) - parseInt(major2);
return parseInt(revision1) - parseInt(revision2);
});
**Sorted Array Object by dotted version value**
var sampleData = [
{ name: 'Edward', value: '2.1.2' },
{ name: 'Sharpe', value: '2.1.3' },
{ name: 'And', value: '2.2.1' },
{ name: 'The', value: '2.1' },
{ name: 'Magnetic', value: '2.2' },
{ name: 'Zeros', value: '0' },
{ name: 'Zeros', value: '1' }
];
arr = sampleData.map( a => a.value).sort();
var requireData = [];
arr.forEach(function(record, index){
var findRecord = sampleData.find(arr => arr.value === record);
if(findRecord){
requireData.push(findRecord);
}
});
console.log(requireData);
[check on jsfiddle.net][1]
[1]: https://jsfiddle.net/jx3buswq/2/
It is corrected now!!!
I'm trying to .sort() an array of objects, but my javascript knowledge isn't strong enough to rewrite the comparator function to accept an arrow function to find the key on the objects rather than use a string. Any help refactoring this would be appreciated:
My comparison function:
compareValues = (key, order = "ascending") => {
let result = 0;
return function(lhs, rhs) {
if (!(lhs.hasOwnProperty(key) && rhs.hasOwnProperty(key))) {
return result; // property is missing; comparison is impossible
}
const l = lhs[key].toLowerCase() || lhs[key];
const r = rhs[key].toLowerCase() || rhs[key];
result = (l > r) ? 1 : (l < r) ? -1 : 0;
return result * (order === "ascending") ? 1 : -1;
};
};
which is used in the conventional way:
objects.sort(compareValues("name")); // or
objects.sort(compareValues("name", descending));
The goal is to be able to use it thusly:
objects.sort(compareValues(o => o.name));
... but frankly I haven't used JS much until lately, so I suck at it.
Maybe something like:
const compareValues = (pickProp, order = 'ascending') => {
let result = 0
return (lhs, rhs) => {
const l = pickProp(lhs) // pickProp is passed #first-class
const r = pickProp(rhs)
if (!l && !r) {
return 0 // Quick callout here, comparators only ever return -1, 0, or 1.
}
result = l > r ? 1 : l < r ? -1 : 0
// Leveraging your default values here for cleaner signature
return result * (order === 'ascending') ? 1 : -1
}
}
list.sort(compareValues(o => o.name))
// or even
list.sort(compareValues(o => o.name), 'descending')
I believe thats in-line with your question.
I don't think you need to perform checks on objects to see if they have specific properties because you would know for sure. So this is how I see it.
const comparer= (select, descend = false) => (a, b) => {
let lhs = select(a); let rhs = select(b);
if (lhs < rhs) return descend ? 1 : -1;
if (lhs > rhs) return descend ? -1 : 1;
return 0;
}
let arr = [{ id: 1, name: "Clara" }, { id: 2, name: "Abraham" }, { id: 3, name: "Brian" }]
arr.sort(comparer(o => o.name))
console.log(arr);
arr.sort(comparer(o => o.name, true))
console.log(arr);
I think the closer you can get with what you want is this:
let compareValues = (key, order = "ascending") => {
let result = 0;
return function(lhs, rhs) {
if (!(key(lhs) && key(rhs))) {
return result; // property is invalid; comparison is impossible
}
const l = key(lhs).toLowerCase() || key(lhs);
const r = key(rhs).toLowerCase() || key(rhs);
result = (l > r) ? 1 : (l < r) ? -1 : 0;
return result * (order === "ascending") ? 1 : -1;
};
};
let objects = [{
"name": "Mary"
}, {
"name": "John"
}];
objects.sort(compareValues(o => o.name));
console.log(objects);
I have an array of objects like this:
var booksArray = [
{Id:1,Rate:5,Price:200,Name:"History"},
{Id:2,Rate:5,Price:150,Name:"Geographic"},
{Id:3,Rate:1,Price:75,Name:"Maths"},
{Id:4,Rate:2,Price:50,Name:"Statistics"},
{Id:5,Rate:3,Price:120,Name:"Drawing"},
{Id:6,Rate:2,Price:100,Name:"Arts"},
{Id:7,Rate:3,Price:110,Name:"Chemistry"},
{Id:8,Rate:4,Price:160,Name:"Biology"},
{Id:9,Rate:4,Price:90,Name:"Software Engineering"},
{Id:10,Rate:4,Price:80,Name:"Algorithm"},
{Id:11,Rate:1,Price:85,Name:"Urdu"},
{Id:12,Rate:1,Price:110,Name:"International Relations"},
{Id:13,Rate:1,Price:120,Name:"Physics"},
{Id:14,Rate:3,Price:230,Name:"Electronics"},
{Id:15,Rate:3,Price:250,Name:"Jihad"},
{Id:16,Rate:3,Price:200,Name:"Functional Programming"},
{Id:17,Rate:2,Price:190,Name:"Computer Science"},
{Id:18,Rate:2,Price:50,Name:"Problem solving Techniques"},
{Id:19,Rate:6,Price:150,Name:"C#"},
{Id:20,Rate:7,Price:250,Name:"ASP.Net"}
]
I am sorting it with this function:
function sortBy(key, reverse) {
var moveSmaller = reverse ? 1 : -1;
var moveLarger = reverse ? -1 : 1;
return function(a, b) {
if (a[key] < b[key]) {
return moveSmaller;
}
if (a[key] > b[key]) {
return moveLarger;
}
return 0;
};
}
booksArray.sort(sortBy('Rate', false))
console.log(JSON.stringify(booksArray))
And this is producing this result:
[
{Id:3,Rate:1,Price:75,Name:"Maths"},
{Id:11,Rate:1,Price:85,Name:"Urdu"},
{Id:12,Rate:1,Price:110,Name:"International Relations"},
{Id:13,Rate:1,Price:120,Name:"Physics"},
{Id:4,Rate:2,Price:50,Name:"Statistics"},
{Id:6,Rate:2,Price:100,Name:"Arts"},
{Id:17,Rate:2,Price:190,Name:"Computer Science"},
{Id:18,Rate:2,Price:50,Name:"Problem solving Techniques"},
{Id:5,Rate:3,Price:120,Name:"Drawing"},
{Id:7,Rate:3,Price:110,Name:"Chemistry"},
{Id:14,Rate:3,Price:230,Name:"Electronics"},
{Id:15,Rate:3,Price:250,Name:"Jihad"},
{Id:16,Rate:3,Price:200,Name:"Functional Programming"},
{Id:8,Rate:4,Price:160,Name:"Biology"},
{Id:9,Rate:4,Price:90,Name:"Software Engineering"},
{Id:10,Rate:4,Price:80,Name:"Algorithm"},
{Id:1,Rate:5,Price:200,Name:"History"},
{Id:2,Rate:5,Price:150,Name:"Geographic"},
{Id:19,Rate:6,Price:150,Name:"C#"},
{Id:20,Rate:7,Price:250,Name:"ASP.Net"}
]
You can see it is sorting on the Rate field which is fine. Now I want to resort again the parts of array without disturbing Rate sorting. I need output like this:
[
{Id:12,Rate:1,Price:110,Name:"International Relations"},
{Id:3,Rate:1,Price:75,Name:"Maths"},
{Id:13,Rate:1,Price:120,Name:"Physics"},
{Id:11,Rate:1,Price:85,Name:"Urdu"},
{Id:6,Rate:2,Price:100,Name:"Arts"},
{Id:17,Rate:2,Price:190,Name:"Computer Science"},
{Id:18,Rate:2,Price:50,Name:"Problem solving Techniques"},
{Id:4,Rate:2,Price:50,Name:"Statistics"},
{Id:7,Rate:3,Price:110,Name:"Chemistry"},
{Id:5,Rate:3,Price:120,Name:"Drawing"},
{Id:14,Rate:3,Price:230,Name:"Electronics"},
{Id:16,Rate:3,Price:200,Name:"Functional Programming"},
{Id:15,Rate:3,Price:250,Name:"Jihad"},
{Id:10,Rate:4,Price:80,Name:"Algorithm"},
{Id:8,Rate:4,Price:160,Name:"Biology"},
{Id:9,Rate:4,Price:90,Name:"Software Engineering"},
{Id:2,Rate:5,Price:150,Name:"Geographic"},
{Id:1,Rate:5,Price:200,Name:"History"},
{Id:19,Rate:6,Price:150,Name:"C#"},
{Id:20,Rate:7,Price:250,Name:"ASP.Net"}
]
Here you can see it is sorted on Rate as well as Name.
Why am I not doing sorting with multiple keys once and for all? Because I am creating a function which sorts the array of objects and can be called multiple times.
Example:
myarray.order('Rate') // single sort
myarray.order('Rate').order('Name') // multiple sort
I can use some more parameters in my function to track if array has already been sorted.
Sample Fiddle
Assuming that you already have bookArray sorted by Rate. It can be sorted on second level using following. Can be fine tuned better for easy usability.
var arr = bookArray;
var firstSortKey = 'Rate';
var secondSortKey = 'Name';
var count = 0 ;
var firstSortValue = arr[count][firstSortKey];
for(var i=count+1; i<arr.length; i++){
if(arr[i][firstSortKey]!==firstSortValue){
var data = arr.slice(count, i);
data = data.sort(function(a, b){
return a[secondSortKey]>b[secondSortKey];
});
var argsArray = [count, i-count];
argsArray.push.apply(argsArray, data);
Array.prototype.splice.apply(arr, argsArray);
count = i;
firstSortValue = arr[i][firstSortKey];
}
}
Fiddle Demo
Change your sortBy function to accept an array of keys. Something like:
function sortBy(keys, reverse) {
var moveSmaller = reverse ? 1 : -1;
var moveLarger = reverse ? -1 : 1;
var key = keys.shift();
return function(a, b) {
if (a[key] < b[key]) {
return moveSmaller;
}
if (a[key] > b[key]) {
return moveLarger;
}
return keys.length ? (sortBy(keys, reverse))(a, b) : 0;
};
}
booksArray.sort(sortBy(['Rate', 'Name'], false))
(Untested, jsfiddle is down for me)
Just for fun: here's a generic sorter for multiple levels of field values within an Array of Objects.
Syntax
XSort().create([arrOfObjects])
.orderBy(
{key: [keyname], [descending=0/1] },
{key: [keyname], [descending=0/1]},
... );
See jsFiddle for an example using booksArray.
function XSort() {
const multiSorter = sortKeys => {
if (!sortKeys || sortKeys[0].constructor !== Object) {
throw new TypeError("Provide at least one {key: [keyname]} to sort on");
}
return function (val0, val1) {
for (let sortKey of sortKeys) {
const v0 = sortKey.key instanceof Function
? sortKey.key(val0) : val0[sortKey.key];
const v1 = sortKey.key instanceof Function
? sortKey.key(val1) : val1[sortKey.key];
const isString = v0.constructor === String || v1.constructor === String;
const compare = sortKey.descending ?
isString ? v1.toLowerCase().localeCompare(v0.toLowerCase()) : v1 - v0 :
isString ? v0.toLowerCase().localeCompare(v1.toLowerCase()) : v0 - v1;
if ( compare !== 0 ) { return compare; }
}
};
}
const Sorter = function (array) {
this.array = array;
};
Sorter.prototype = {
orderBy: function(...sortOns) {
return this.array.slice().sort(multiSorter(sortOns));
},
};
return {
create: array => new Sorter(array)
};
}
I have following array from facebook graph API.
I want to sort it by comment_count, like_count, time in javascript.
[
{
"status_id": "1",
"message": "message1",
"comment_info": {
"comment_count": "1"
},
"like_info": {
"like_count": "0"
},
"time": "1380046653"
},
{
"status_id": "2",
"message": "message2",
"comment_info": {
"comment_count": "2"
},
"like_info": {
"like_count": "5"
},
"time": "1368109884"
}
]
I wrote function like below,
function sortResults(prop, asc) {
statusString = statusString.sort(function(a, b) {
if (asc) return (a[prop] > b[prop]) ? 1 : ((a[prop] < b[prop]) ? -1 : 0);
else return (b[prop] > a[prop]) ? 1 : ((b[prop] < a[prop]) ? -1 : 0);
});
console.log(statusString);
}
And on button click
sortResults(['comment_info']['comment_count'], true);
But it sorts weiredly.
Your function didnt' take in consideration multi dimensional sort which needs to access deep property of nested arrays.
Here is a working example JSFIDLE link (click here)
var jSon = [{"status_id":"1","message":"message1","comment_info":{"comment_count":"1"},"like_info":{"like_count":"0"},"time":"1380046653"},{"status_id":"2","message":"message2","comment_info":{"comment_count":"2"},"like_info":{"like_count":"5"},"time":"1368109884"}];
// Function that sorts arr Array
// by prop (handling custom Fb cases)
// in dir direction (asc/desc)
function sortJson(arr, prop, dir) {
return arr.sort(function(a,b) {
var propA,propB;
if (prop == "comment_count") {
propA = a['comment_info']['comment_count'];
propB = b['comment_info']['comment_count'];
} else if (prop == "like_count") {
propA = a['like_info']['like_count'];
propB = b['like_info']['like_count'];
} else {
propA = a[prop];
propB = b[prop];
}
if (dir=='asc') {
return propA - propB;
} else {
return propB - propA;
}
});
}
console.log( sortJson(jSon, 'time', 'asc') );
console.log( sortJson(jSon, 'comment_count', 'asc') );
console.log( sortJson(jSon, 'like_count', 'desc').toString() );
You probably need sort function from native [].
[].sort(compareFunction)
example from here :
function compare(a, b) {
if (a is less than b by some ordering criterion)
return -1;
if (a is greater than b by the ordering criterion)
return 1;
// a must be equal to b
return 0;
}
let's say your order in the sort is as you have mentioned them:
var arr = [{"status_id":"1","message":"message1","comment_info":{"comment_count":"1"},"like_info":{"like_count":"0"},"time":"1380046653"},{"status_id":"2","message":"message2","comment_info":{"comment_count":"2"},"like_info":{"like_count":"5"},"time":"1368109884"}];
arr.sort(function (a, b) {
var countA = parseInt(a["comment_info"]["comment_count"]);
var countB = parseInt(b["comment_info"]["comment_count"]);
var likeCountA = parseInt(a["like_info"]["like_count"]);
var likeCountB = parseInt(b["like_info"]["like_count"]);
var timeA = a["time"];
var timeB = b["time"];
return ((countA - countB) || (likeCountA - likeCountB) || (timeA - timeB));
});
I have this function to sort a JavaScript array of objects based on a property:
// arr is the array of objects, prop is the property to sort by
var sort = function (prop, arr) {
arr.sort(function (a, b) {
if (a[prop] < b[prop]) {
return -1;
} else if (a[prop] > b[prop]) {
return 1;
} else {
return 0;
}
});
};
It works with arrays like this:
sort('property', [
{property:'1'},
{property:'3'},
{property:'2'},
{property:'4'},
]);
But I want to be able to sort also by nested properties, for example something like:
sort('nestedobj.property', [
{nestedobj:{property:'1'}},
{nestedobj:{property:'3'}},
{nestedobj:{property:'2'}},
{nestedobj:{property:'4'}}
]);
However this doesn't work because it is not possible to do something like object['nestedobj.property'], it should be object['nestedobj']['property'].
Do you know how could I solve this problem and make my function work with properties of nested objects?
Thanks in advance
You can split the prop on ., and iterate over the Array updating the a and b with the next nested property during each iteration.
Example: http://jsfiddle.net/x8KD6/1/
var sort = function (prop, arr) {
prop = prop.split('.');
var len = prop.length;
arr.sort(function (a, b) {
var i = 0;
while( i < len ) { a = a[prop[i]]; b = b[prop[i]]; i++; }
if (a < b) {
return -1;
} else if (a > b) {
return 1;
} else {
return 0;
}
});
return arr;
};
Use Array.prototype.sort() with a custom compare function to do the descending sort first:
champions.sort(function(a, b) { return b.level - a.level }).slice(...
Even nicer with ES6:
champions.sort((a, b) => b.level - a.level).slice(...
Instead of passing the property as a string, pass a function that can retrieve the property from the top level object.
var sort = function (propertyRetriever, arr) {
arr.sort(function (a, b) {
var valueA = propertyRetriever(a);
var valueB = propertyRetriever(b);
if (valueA < valueB) {
return -1;
} else if (valueA > valueB) {
return 1;
} else {
return 0;
}
});
};
Invoke as,
var simplePropertyRetriever = function(obj) {
return obj.property;
};
sort(simplePropertyRetriever, { .. });
Or using a nested object,
var nestedPropertyRetriever = function(obj) {
return obj.nestedObj.property;
};
sort(nestedPropertyRetriever, { .. });
if you have array of objects like
const objs = [{
first_nom: 'Lazslo',
last_nom: 'Jamf',
moreDetails: {
age: 20
}
}, {
first_nom: 'Pig',
last_nom: 'Bodine',
moreDetails: {
age: 21
}
}, {
first_nom: 'Pirate',
last_nom: 'Prentice',
moreDetails: {
age: 22
}
}];
you can use simply
nestedSort = (prop1, prop2 = null, direction = 'asc') => (e1, e2) => {
const a = prop2 ? e1[prop1][prop2] : e1[prop1],
b = prop2 ? e2[prop1][prop2] : e2[prop1],
sortOrder = direction === "asc" ? 1 : -1
return (a < b) ? -sortOrder : (a > b) ? sortOrder : 0;
}
and call it
for direct objects
objs.sort(nestedSort("last_nom"));
objs.sort(nestedSort("last_nom", null, "desc"));
for nested objects
objs.sort(nestedSort("moreDetails", "age"));
objs.sort(nestedSort("moreDetails", "age", "desc"));
You can use Agile.js for this kind of things.
Actually you pass an expression instead of callback, it's handle nested properties and javascript expression in a very nice-ish way.
Usage: _.orderBy(array, expression/callback, reverse[optional])
Example:
var orders = [
{ product: { price: 91.12, id: 1 }, date: new Date('01/01/2014') },
{ product: { price: 79.21, id: 2 }, date: new Date('01/01/2014') },
{ product: { price: 99.90, id: 3 }, date: new Date('01/01/2013') },
{ product: { price: 19.99, id: 4 }, date: new Date('01/01/1970') }
];
_.orderBy(orders, 'product.price');
// → [orders[3], orders[1], orders[0], orders[2]]
_.orderBy(orders, '-product.price');
// → [orders[2], orders[0], orders[1], orders[3]]
Would this meet your needs?
// arr is the array of objects, prop is the property to sort by
var sort = function (nestedObj, prop, arr) {
arr.sort(function (a, b) {
if (a[nestedObj][prop] < b[nestedObj][prop]) {
return -1;
} else if (a[nestedObj][prop] > b[nestedObj][prop]) {
return 1;
} else {
return 0;
}
});
};
Try this (used a recursive function to get nested value, you can pass the nested property as nestedobj.property):
You can use this for any level of hierarchy
// arr is the array of objects, prop is the property to sort by
var getProperty = function(obj, propNested){
if(!obj || !propNested){
return null;
}
else if(propNested.length == 1) {
var key = propNested[0];
return obj[key];
}
else {
var newObj = propNested.shift();
return getProperty(obj[newObj], propNested);
}
};
var sort = function (prop, arr) {
arr.sort(function (a, b) {
var aProp = getProperty(a, prop.split("."));
var bProp = getProperty(a, prop.split("."));
if (aProp < bProp) {
return -1;
} else if (aProp > bProp) {
return 1;
} else {
return 0;
}
});
};
This is my modify code.
// arr is the array of objects, prop is the property to sort by
var s = function (prop, arr) {
// add sub function for get value from obj (1/2)
var _getVal = function(o, key){
var v = o;
var k = key.split(".");
for(var i in k){
v = v[k[i]];
}
return v;
}
return arr.sort(function (a, b) {
// get value from obj a, b before sort (2/2)
var aVal = _getVal(a, prop);
var bVal = _getVal(b, prop);
if (aVal < bVal) {
return -1;
} else if (aVal > bVal) {
return 1;
} else {
return 0;
}
});
};
var objectsArr = [
{nestedobj:{property:'1'}},
{nestedobj:{property:'3'}},
{nestedobj:{property:'2'}},
{nestedobj:{property:'4'}}
];
function getFromPath(obj, path) {
let r = obj;
path.forEach(key => { r = r[key]})
return r
}
function sortObjectsArr(objectsArray, ...path) {
objectsArray.sort((a, b) => getFromPath(a, path) - getFromPath(b, path))
}
sortObjectsArr(objectsArr, 'nestedobj', 'property');
console.log(objectsArr);
Unfortunately, I didn't find any nice way to use the arguments in order to access the attributes of the nested object.
Want to mention that there can be some checks if the keys are available in the passed object, but this depends on who and how want to implement this.
Description
My solution is this one. I decide to flat the object first:
function flattenObject(value: any): any {
let toReturn: any = {};
for (const i in value) {
if (!value.hasOwnProperty(i)) {
continue;
}
if (typeof value[i] == 'object') {
const flatObject = flattenObject(value[i]);
for (const x in flatObject) {
if (!flatObject.hasOwnProperty(x)) continue;
toReturn[i + '.' + x] = flatObject[x];
}
} else {
toReturn[i] = value[i];
}
}
return toReturn;
}
And then I'll extract the value from the object:
function nestedFieldValue(
nestedJoinedFieldByDot: string,
obj: any,
): any {
return flattenObject(obj)[nestedJoinedFieldByDot];
}
Ant at the end I just need to do this:
export function fieldSorter(fields: string[]) {
return function (a: any, b: any) {
return fields
.map(function (fieldKey) {
// README: Sort Ascending by default
let dir = 1;
if (fieldKey[0] === '-') {
// README: Sort Descending if `-` was passed at the beginning of the field name
dir = -1;
fieldKey = fieldKey.substring(1);
}
const aValue = nestedFlattenObjectFieldValue(
fieldKey,
a,
);
const bValue = nestedFlattenObjectFieldValue(
fieldKey,
b,
);
if (
typeof aValue === 'number' ||
typeof bValue === 'number'
) {
/**
* README: default value when the field does not exists to prevent unsorted array
* I assume that 0 should be the last element. In other word I sort arrays in a way
* that biggest numbers comes first and then smallest numbers
*/
if (aValue ?? 0 > bValue ?? 0) {
return dir;
}
if (aValue ?? 0 < bValue ?? 0) {
return -dir;
}
} else {
if (aValue ?? 0 > bValue ?? 0) {
return dir;
}
if (aValue ?? 0 < bValue ?? 0) {
return -dir;
}
}
return 0;
})
.reduce(function firstNonZeroValue(p, n) {
return p ? p : n;
}, 0);
};
}
Finally we need to do this:
const unsorted = [
{
city: {
priority: 1,
name: 'Tokyo',
airport: { name: 'Haneda Airport' }
}
}
]
const result = unsorted.sort(
fieldSorter(['city.priority', 'city.airport.name', 'city.name']),
);
I think this way is much much clear and cleaner. It is readable and more functional. I merge multiple answer from stackoverflow to reach this solution :sweat_smile:
This should work and can accept multiple parameters.
https://codepen.io/allenACR/pen/VwQKWZG
function sortArrayOfObjects(items, getter) {
const copy = JSON.parse(JSON.stringify(items));
const sortFn = fn => {
copy.sort((a, b) => {
a = fn(a)
b = fn(b)
return a === b ? 0 : a < b ? -1 : 1;
});
};
getter.forEach(x => {
const fn = typeof x === 'function' ? x : item => item[x];
sortFn(fn);
});
return copy;
}
// example dataset
const data = [
{id: 3, name: "Dave", details: {skill: "leader"} },
{id: 1, name: "Razor", details: {skill: "music"} },
{id: 2, name: "Syd", details: {skill: "animal husbandry"} }
]
// sort via single prop
const sort1 = sortArrayOfObjects(data, ["id"])
// returns [Razor, Syd, Dave]
// sort via nested
const sort2 = sortArrayOfObjects(data, [
(item) => item.details.skill
])
// returns [Syd, Dave, Razor]
console.log({sort1, sort2})
3 levels deep. path can look like this.
'level1' or 'level1.level2' or 'level1.level2.level3'
I also did uppercase for the sort all my items are strings.
Anwser is a modified version from - #Mas
public keysrt(arr: Object[], path: string, reverse: boolean): void {
const nextOrder = reverse ? -1 : 1;
const pathSplit = path.split('.');
if (arr === null || arr === undefined ) {
return;
}
if (arr.length <= 1) {
return;
}
const nestedSort = (prop1, prop2 = null, prop3 = null, direction = 'asc') => (e1, e2) => {
const a = prop3 ? e1[prop1][prop2][prop3] : prop2 ? e1[prop1][prop2] : e1[prop1],
b = prop3 ? e2[prop1][prop2][prop3] : prop2 ? e2[prop1][prop2] : e2[prop1],
sortOrder = direction === 'asc' ? 1 : -1;
return (a.toString().toUpperCase() < b.toString().toUpperCase()) ?
-sortOrder * nextOrder : (a.toString().toUpperCase() > b.toString().toUpperCase()) ?
sortOrder * nextOrder : 0;
};
if (pathSplit.length === 3) {
arr.sort(nestedSort(pathSplit[0], pathSplit[1], pathSplit[2]));
}
if (pathSplit.length === 2) {
arr.sort(nestedSort(pathSplit[0], pathSplit[1]));
}
if (pathSplit.length === 1) {
arr.sort(nestedSort(pathSplit[0], null));
}
}
For those who a researching on how to sort nested properties. I created a small type-safe array sorting method with support for deeply nested properties and Typescript autocompletion.
https://github.com/jvandenaardweg/sort-by-property
https://www.npmjs.com/package/sort-by-property
Example:
blogPosts.sort(sortByProperty('author.name', 'asc'));