Sorting a float number against a string? - javascript

I think I finally found the source of an irritating problem. I am sorting an array of objects by the price of a product and sometimes the product price is not available denoted an "N/A". I am expecting all the values of "N/A" to be put last in the total sort order...but its not turning out that way. It seems to work for a bit, all prices listed first and then prices and N/A's are interspersed towards the bottom of the sort. How/What can I do to solve this issue?
var arr = [
{id:0,vendor:'ACompany',price:'10.82'},
{id:1,vendor:'ZCompany',price:'10.00'},
{id:2,vendor:'LCompany',price:'9.82'},
{id:3,vendor:'DCompany',price:'N/A'},
{id:4,vendor:'WCompany',price:'11.82'},
{id:5,vendor:'RCompany',price:'N/A'},
{id:6,vendor:'HCompany',price:'10.83'},
{id:7,vendor:'MCompany',price:'10.72'},
{id:8,vendor:'XCompany',price:'9.92'},
{id:9,vendor:'ICompany',price:'N/A'},
{id:10,vendor:'GCompany',price:'10.82'},
] ;
function sortArr(key) {
arr.sort(function(a,b) {
var x = a[key]; var y = b[key] ;
if (key == "vendor") {
return ((x<y) ? -1 : ((x > y) ? 1: 0)) ;
} else {
return parseFloat(x) - parseFloat(y) ;
}
})
}
sortArr('price') ; // when sorting by 'vendor', it works fine.
for (x=0;x<arr.length;x++) {
console.log(arr[x].vendor+ ": " +arr[x].price) ;
}
The Sort output is:
LCompany: 9.82
XCompany: 9.92
ZCompany: 10.00
MCompany: 10.72
ACompany: 10.82
GCompany: 10.82
DCompany: N/A
ICompany: N/A
HCompany: 10.83
WCompany: 11.82
RCompany: N/A
And I know I can't try to parse the prices as strings, the 'N/A's will be last, but $10 values will be listed before $9 values.

You can first sort it by not NaN values and then by price values.
var arr = [{id:0,vendor:'ACompany',price:'10.82'},{id:1,vendor:'ZCompany',price:'10.00'},{id:2,vendor:'LCompany',price:'9.82'},{id:3,vendor:'DCompany',price:'N/A'},{id:4,vendor:'WCompany',price:'11.82'},{id:5,vendor:'RCompany',price:'N/A'},{id:6,vendor:'HCompany',price:'10.83'},{id:7,vendor:'MCompany',price:'10.72'},{id:8,vendor:'XCompany',price:'9.92'},{id:9,vendor:'ICompany',price:'N/A'},{id:10,vendor:'GCompany',price:'10.82'}];
arr.sort((a, b) => {
let aN = +a.price, bN = +b.price;
return !isNaN(bN) - !isNaN(aN) || aN - bN
})
console.log(arr)

Your solution is close, but needs to handle non-numeric input as a special exception. You can detect if the string is non-numeric as follows: isNaN(parseFloat("N/A")) == true, isNaN(parseFloat("3.0")) == false.
The problem is because NaN has no ordering. NaN < NaN == false, NaN > NaN == false, and NaN == NaN == false.
So before you decide to sort numerically with parseFloat(x) - parseFloat(y) in your code, check for that condition and use a different ordering of your choosing.
var arr = [
{id:0,vendor:'ACompany',price:'10.82'},
{id:1,vendor:'ZCompany',price:'10.00'},
{id:2,vendor:'LCompany',price:'9.82'},
{id:3,vendor:'DCompany',price:'N/A'},
{id:4,vendor:'WCompany',price:'11.82'},
{id:5,vendor:'RCompany',price:'N/A'},
{id:6,vendor:'HCompany',price:'10.83'},
{id:7,vendor:'MCompany',price:'10.72'},
{id:8,vendor:'XCompany',price:'9.92'},
{id:9,vendor:'ICompany',price:'N/A'},
{id:10,vendor:'GCompany',price:'10.82'},
] ;
function sortArr(key) {
arr.sort(function(a,b) {
var x = a[key]; var y = b[key] ;
if (key == "vendor") {
return ((x<y) ? -1 : ((x > y) ? 1: 0)) ;
} else {
return isNaN(+x) ? 1 : isNaN(+y) ? -1 : parseFloat(x) - parseFloat(y) ;
}
})
}
sortArr('price') ; // when sorting by 'vendor', it works fine.
for (x=0;x<arr.length;x++) {
console.log(arr[x].vendor+ ": " +arr[x].price) ;
}
This solution is slightly more robust than just handling 'N/A', since it will also put any non-numeric input at the bottom, though not in a specified order.

Try this custom sorter instead.
In short, numeric prices will be sorted at the top of the array and all N/A price types will be sorted down to the bottom. I'd recommend reading about custom sorters here...
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
var arr = [
{id:0,vendor:'ACompany',price:'10.82'},
{id:1,vendor:'ZCompany',price:'10.00'},
{id:2,vendor:'LCompany',price:'9.82'},
{id:3,vendor:'DCompany',price:'N/A'},
{id:4,vendor:'WCompany',price:'11.82'},
{id:5,vendor:'RCompany',price:'N/A'},
{id:6,vendor:'HCompany',price:'10.83'},
{id:7,vendor:'MCompany',price:'10.72'},
{id:8,vendor:'XCompany',price:'9.92'},
{id:9,vendor:'ICompany',price:'N/A'},
{id:10,vendor:'GCompany',price:'10.82'}
];
arr.sort(function(a, b) {
if (a.price == b.price) return 0;
if (a.price == "N/A") return 1;
if (b.price == "N/A") return -1;
return a.price - b.price;
});
console.log(arr);

You could check if the property has a string and get the delta of the comparison fo a chained retuen value.
var array = [{ id: 0, vendor: 'ACompany', price: '10.82' }, { id: 1, vendor: 'ZCompany', price: '10.00' }, { id: 2, vendor: 'LCompany', price: '9.82' }, { id: 3, vendor: 'DCompany', price: 'N/A' }, { id: 4, vendor: 'WCompany', price: '11.82' }, { id: 5, vendor: 'RCompany', price: 'N/A' }, { id: 6, vendor: 'HCompany', price: '10.83' }, { id: 7, vendor: 'MCompany', price: '10.72' }, { id: 8, vendor: 'XCompany', price: '9.92' }, { id: 9, vendor: 'ICompany', price: 'N/A' }, { id: 10, vendor: 'GCompany', price: '10.82' }];
array.sort(function (a, b) {
return (a.price === 'N/A') - (b.price === 'N/A') || a.price - b.price;
});
console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Do you really need that N/A's in your output? Filter them out _.filter(arr, ({price}) => price !== 'N/A') and then pass the result to a sort function. A sort function shouldn't know about N/A I guess, it should just do the sorting.

You should check isNaN(parseFloat(x)) and isNaN(parseFloat(y)) before returning the result : if one is NaN then put it last

Related

custom sorting v-data-table with null values last

I have a v-data-table in vueJS that contains some numeric columns and some string columns.
In each column, some of the values are null.
I am trying to create a custom sorting function that will place null values last.
This is what I tried so far:
<v-data-table
:headers="[
{ text: 'Name', value: 'name' },
{ text: 'Date of Birth', value: 'dateofbirth_fmt' },
{ text: 'Team', value: 'team_name' },
{
text: 'dp1 (string)',
value: 'dp1',
},
{
text: 'dp2 (Numeric),
value: 'dp2',
}
]"
:items="filteredPlayersData"
item-key="_id"
class="elevation-1"
:custom-sort="customSort"
/>
and this function
customSort(items, index, isDesc) {
items.sort((a, b) => {
if (!isDesc[0]) {
return (a[index] != null ? a[index] : Infinity) >
(b[index] != null ? b[index] : Infinity)
? 1
: -1;
} else {
return (b[index] != null ? b[index] : -Infinity) >
(a[index] != null ? a[index] : -Infinity)
? 1
: -1;
}
});
return items;
}
It is working for this numeric column (dp1), but not for the string one (dp2).
Any ideas how to get this work?
Your sorting algorithm is not working correctly for strings.
Imagine that your first string is null, and the second one is 'Jelly bean'.
Instead of null value you are trying to compate Infinity with 'Jelly bean'.
This comparison will be false in both cases:
let a = Infinity;
let b = 'Jelly bean';
console.log(a > b);
console.log(a < b);
It'd be better to use another sorting algorithm.
For example, I've adapted an algorithm from this post:
customSort(items, index, isDesc) {
items.sort((a, b) => {
if (a[index] === b[index]) { // equal items sort equally
return 0;
} else if (a[index] === null) { // nulls sort after anything else
return 1;
} else if (b[index] === null) {
return -1;
} else if (!isDesc[0]) { // otherwise, if we're ascending, lowest sorts first
return a[index] < b[index] ? -1 : 1;
} else { // if descending, highest sorts first
return a[index] < b[index] ? 1 : -1;
}
});
return items;
}
You may test this at CodePen. Works fine for both strings and numbers.

Custom sorting array of objects

I have an array of objects like so for example:
[ {
id: '-1'
},
{
id: '10'
},
{
id: '1234'
},
{
id: '1235'
},
{
id: '-1'
} ]
I would like to sort this array so that it is ordered by the ids ascending (smallest to largest), however I would like objects that have the id of '-1' to be sent to the back of the array. So I tried this:
const arr = [ { id: '-1' }, { id: '10'}, { id: '1234' }, { id: '1235' }, { id: '-1' } ]
arr.sort((a, b) => {
if (parseInt(a.id) === -1 || parseInt(b.id) === -1) return 1;
if (parseInt(a.id) - parseInt(b.id)) return -1;
});
console.log(arr);
As you can see from the snippet above it sorts them successfully ascending however it doesn't successfully move ids with '-1' to the back of the array.
Why does this happen? What am I doing wrong?
You could take the function with a check for the items which should be sorted to bottom.
var array = [{ id: '-1' }, { id: '10' }, { id: '1234' }, { id: '1235' }, { id: '-1' }];
array.sort((a, b) => (a.id === '-1') - (b.id === '-1') || a.id - b.id);
console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }
How sorting works
First, let's take a look at what .sort expects you to feed it.
Given paramaters (a, b) -- we should return
< 0 if a < b
> 0 if a > b
How you did the sorting
Let's take a look at your first line
if (parseInt(a.id) === -1 || parseInt(b.id) === -1) return 1;
-1 means "a is smaller". But that might not be true. It might be that a (not b) is -1, and is bigger, yet we're always telling JavaScript to send a to the back when that's what we should be doing to b.
Instead, we want to return -1 when b is -1, but +1 when a is -1. It doesn't really matter what we return if they're both -1.
In JavaScript, any number except for 0 is truthful. Let's take a look at your next line.
if (parseInt(a.id) - parseInt(b.id)) return -1;
If a - b is 0, we don't return anything in this callback. If it isn't 0, we always say that a < b.
Notice that never do we say that b < a -- so if such an event were to occur, we couldn't handle it and would sort it incorrectly.
How to sort correctly
Fetch IDs
const aID = parseInt(a.id)
const bID = parseInt(b.id)
Is a or b -1?
if(aID === -1) return +1;
if(bID === -1) return -1;
Which is bigger, a or b
If you assume that a and b are not -1, then we can simply subtract a - b. Why? If a is bigger than b, then the subtraction will create a positive number to say b < a. If a is less than b, then the subtraction will create a negative number, to say a < b.
return aID - bID
All together
const arr = [ { id: '-1' }, { id: '10'}, { id: '1234' }, { id: '1235' }, { id: '-1' } ]
arr.sort((a, b) => {
const [aID, bID] = [parseInt(a.id), parseInt(b.id)]
if(aID === -1) return +1
if(bID === -1) return -1
return aID - bID
});
console.log(arr);
Golfing
It might be helpful to make things shorter. Another answer, by #Nina Scholz, helpfully showed a much shorter version. I thought it might be useful to explain why this works.
return (a.id === '-1') - (b.id === '-1') || a.id - b.id
What is x || y
x || y means:
if x is truthful, return x.
if x isn't truthful, return y.
What is (aID === -1)
This means true if aID is -1, and false otherwise
What is (aID === -1) - (bID === -1)
How can you subtract true and false? true will be interpreted as 1, and false as 0.
aID = -1, bID = not
This value will be 1 - 0, or +1
aID = not, bID = -1
This value will be 0 - 1, or -1
aID = -1, bID = -1
Remember, it doesn't matter what we return if the two values are the same
aID = not, bID = not
0 - 0. This is 0. This is not a truthful value. So we go into the || bit. Which, in that answer, has the second bit be aID - bID, as described above. It's very clever and very short, though might not be as readable.

Alphanumeric sort on first first three chars in the JSON object in javascript

I wanted to sort the JSON object specific key values of first three characters.
Below is the sample JSON format
var obj = [{
id :12,
trackingNo: 'APQW123'},
{
id :13,
trackingNo: '123ABXU'},
{
id :12,
trackingNo: '98012JIH'},
{
id :12,
trackingNo: 'JHG567'}
];
In above object, based on first three chars of trackingNo need to sort the object. trackingNo is combination of alphanumeric.
I tried the below method. I thought it is numeric sort.It not correct one for this alphanumeric.
function sortObject(obj) {
var sortedData = obj.sort(function (a, b) {
return a.trackingNo > b.trackingNo;
});
return sortedData;
}
Could anyone please provide the logic to implement the alphanumeric sort based on first three characters in jasscript.
You could slice the first characters for sorting.
For checking if the value is not a number, a check for isNaN is included. Numbers are sorted first and then the other characters.
function sortTrackingNo(array, order) {
return array.sort(order === 'DESC'
? function (b, a) {
a = a.trackingNo.slice(0, 3);
b = b.trackingNo.slice(0, 3);
return isNaN(b) - isNaN(a) || a > b || -(a < b);
}
: function (a, b) {
a = a.trackingNo.slice(0, 3);
b = b.trackingNo.slice(0, 3);
return isNaN(a) - isNaN(b) || a > b || -(a < b);
});
}
var array = [{ id: 12, trackingNo: 'APQW123' }, { id: 13, trackingNo: '123ABXU' }, { id: 12, trackingNo: '98012JIH' }, { id: 12, trackingNo: 'JHG567' }];
sortTrackingNo(array);
console.log(array);
sortTrackingNo(array, 'DESC');
console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }

Sort array first by string and then by date

Well, I have an array objects with random values,
Ex.
var value = [
{"date":"06/11/2017","status":"B"},
{"date":"06/11/2017","status":"B"},
{"date":"15/05/2017","status":"R"},
{"date":"15/05/2017","status":"R"},
{"date":"14/05/2018","status":"R"},
{"date":"05/05/2017","status":"R"},
{"date":null,"status":"W"},
{"date":null,"status":"W"},
{"date":null,"status":"W"},
{"date":"05/11/2017","status":"B"},
{"date":"27/07/2017","status":"R"},
{"date":"14/05/2018","status":"R"},
{"date":"27/07/201","status":"R"},
{"date":"14/05/2018","status":"R"},
{"date":"26/02/2018","status":"R"},
{"date":null,"status":"W"}
];
I wanted to sort this array firstly by key status & then by key date as,
Output:
var result = [
{"date":"05/11/2017","status":"B"},
{"date":"06/11/2017","status":"B"},
{"date":"06/11/2017","status":"B"},
{"date":"05/05/2017","status":"R"},
{"date":"15/05/2017","status":"R"},
{"date":"15/05/2017","status":"R"},
{"date":"27/07/2017","status":"R"},
{"date":"14/05/2018","status":"R"},
{"date":"14/05/2018","status":"R"},
{"date":"14/05/2018","status":"R"},
{"date":"26/02/2018","status":"R"},
{"date":"27/07/2018","status":"R"},
{"date":null,"status":"W"},
{"date":null,"status":"W"},
{"date":null,"status":"W"},
{"date":null,"status":"W"}
];
/*I tried few generic code to sort, */
var result = value.sort(function (a, b) {
var aValue = (a.date) ? a.date: 0;
var bValue = (b.date) ? b.date: 0;
return a.status - b.status || aValue - bValue;
});
I referred few examples SO Example but not getting expected output. Please suggest me best way to get this.
Would this work?
value = value.sort(function (a, b) {
if (a === b || (a.status === b.status && a.date === b.date)) return 0;
if (a.status > b.status) return 1;
if (a.status < b.status) return -1;
if (a.date > b.date) return 1;
if (a.date < b.date) return -1;
})
return a.status - b.status || aValue - bValue;
The values you are dealing with are not numbers! You can't subtract one from the other.
Also: Don't use nested ternary operators. It makes it very hard to understand the code.
Use if statements. They are much more readable.
I think this is what you are looking for, but your description of the problem isn't clear. This should, however, show you the principles so you can tweak it as you want.
const a_is_first = -1;
const b_is_first = 1;
function compare(a, b) {
// Sort and group by status first. Sort by date within the statuses.
if (a.status < b.status) {
return a_is_first;
} else if (a.status > b.status) {
return b_is_first;
} else { // They are the same
var a__date = convert_to_a_date_object(a.date);
var b__date = convert_to_a_date_object(b.date);
if (a__date < b__date) {
return a_is_first;
} else if (a__date > b__date) {
return b_is_first;
} else {
return 0;
}
}
}
value.sort(compare);
The implementation of convert_to_a_date_object is left as an exercise for the reader. Remember it has to handle null.
Please see if this helps
value.sort(function(a, b) {
var nameA = a.status.toUpperCase(),
nameB = b.status.toUpperCase();
if (nameA < nameB) {
return -1;
}
if (nameA > nameB) {
return 1;
}
// names must be equal
return 0;
}).sort(function(a, b) {
return new Date(b.date) - new Date(a.date);
});
You could take status for sorting and if equal take a date string in ISO 8601 notation for sorting. ISO 6801 allowes to use a sorting by string without taking a date object.
var array = [{ date: "06/11/2017", status: "B" }, { date: "06/11/2017", status: "B" }, { date: "15/05/2017", status: "R" }, { date: "15/05/2017", status: "R" }, { date: "14/05/2018", status: "R" }, { date: "05/05/2017", status: "R" }, { date: null, status: "W" }, { date: null, status: "W" }, { date: null, status: "W" }, { date: "05/11/2017", status: "B" }, { date: "27/07/2017", status: "R" }, { date: "14/05/2018", status: "R" }, { date: "27/07/2017", status: "R" }, { date: "14/05/2018", status: "R" }, { date: "26/02/2018", status: "R" }, { date: null, status: "W" }];
array.sort(function (a, b) {
function getDate(d) {
return d ? d.replace(/(..)\/(..)\/(....)/, '$3-$2-$1') : '';
}
return a.status.localeCompare(b.status) || getDate(a.date).localeCompare(getDate(b.date));
});
console.log(array);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Your date is under format "dd/mm/yyyy". You can use Date.parse("11/05/2017") (with format "mm/dd/yyyy", see parse for more formats) to return the number of milliseconds and compare. For the status, you can use status.charCodeAt(0) to get ASCII value and compare.
Assuming that you have a good date format, here is the solution:
value.sort(function(a, b) {
var diff = Date.parse(a.date) - Date.parse(b.date);
if (diff === 0) {
return a.status.charCodeAt(0) - b.status.charCodeAt(0);
}
return diff;
});
Hope it helps.

JavaScript: How to sort array of objects by two object properties? [duplicate]

I have a multidimensional array. The primary array is an array of
[publicationID][publication_name][ownderID][owner_name]
What I am trying to do is sort the array by owner_name and then by publication_name. I know in JavaScript you have Array.sort(), into which you can put a custom function, in my case i have:
function mysortfunction(a, b) {
var x = a[3].toLowerCase();
var y = b[3].toLowerCase();
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
}
This is fine for just sorting on the one column, namely owner_name, but how do I modify it to sort on owner_name, then publication_name?
If owner names differ, sort by them. Otherwise, use publication name for tiebreaker.
function mysortfunction(a, b) {
var o1 = a[3].toLowerCase();
var o2 = b[3].toLowerCase();
var p1 = a[1].toLowerCase();
var p2 = b[1].toLowerCase();
if (o1 < o2) return -1;
if (o1 > o2) return 1;
if (p1 < p2) return -1;
if (p1 > p2) return 1;
return 0;
}
I think what you're looking for is thenBy.js: https://github.com/Teun/thenBy.js
It allows you to use the standard Array.sort, but with firstBy().thenBy().thenBy() style.
An example can be seen here.
A good way to sort on many fields that are strings is to use toLocaleCompare and the boolean operator ||.
Something like:
// Sorting record releases by name and then by title.
releases.sort((oldRelease, newRelease) => {
const compareName = oldRelease.name.localeCompare(newRelease.name);
const compareTitle = oldRelease.title.localeCompare(newRelease.title);
return compareName || compareTitle;
})
If you wanted to sort on more fields, you could simply chain them off the return statement with more boolean operators.
Came across a need to do SQL-style mixed asc and desc object array sorts by keys.
kennebec's solution above helped me get to this:
Array.prototype.keySort = function(keys) {
keys = keys || {};
// via
// https://stackoverflow.com/questions/5223/length-of-javascript-object-ie-associative-array
var obLen = function(obj) {
var size = 0, key;
for (key in obj) {
if (obj.hasOwnProperty(key))
size++;
}
return size;
};
// avoiding using Object.keys because I guess did it have IE8 issues?
// else var obIx = function(obj, ix){ return Object.keys(obj)[ix]; } or
// whatever
var obIx = function(obj, ix) {
var size = 0, key;
for (key in obj) {
if (obj.hasOwnProperty(key)) {
if (size == ix)
return key;
size++;
}
}
return false;
};
var keySort = function(a, b, d) {
d = d !== null ? d : 1;
// a = a.toLowerCase(); // this breaks numbers
// b = b.toLowerCase();
if (a == b)
return 0;
return a > b ? 1 * d : -1 * d;
};
var KL = obLen(keys);
if (!KL)
return this.sort(keySort);
for ( var k in keys) {
// asc unless desc or skip
keys[k] =
keys[k] == 'desc' || keys[k] == -1 ? -1
: (keys[k] == 'skip' || keys[k] === 0 ? 0
: 1);
}
this.sort(function(a, b) {
var sorted = 0, ix = 0;
while (sorted === 0 && ix < KL) {
var k = obIx(keys, ix);
if (k) {
var dir = keys[k];
sorted = keySort(a[k], b[k], dir);
ix++;
}
}
return sorted;
});
return this;
};
sample usage:
var obja = [
{USER:"bob", SCORE:2000, TIME:32, AGE:16, COUNTRY:"US"},
{USER:"jane", SCORE:4000, TIME:35, AGE:16, COUNTRY:"DE"},
{USER:"tim", SCORE:1000, TIME:30, AGE:17, COUNTRY:"UK"},
{USER:"mary", SCORE:1500, TIME:31, AGE:19, COUNTRY:"PL"},
{USER:"joe", SCORE:2500, TIME:33, AGE:18, COUNTRY:"US"},
{USER:"sally", SCORE:2000, TIME:30, AGE:16, COUNTRY:"CA"},
{USER:"yuri", SCORE:3000, TIME:34, AGE:19, COUNTRY:"RU"},
{USER:"anita", SCORE:2500, TIME:32, AGE:17, COUNTRY:"LV"},
{USER:"mark", SCORE:2000, TIME:30, AGE:18, COUNTRY:"DE"},
{USER:"amy", SCORE:1500, TIME:29, AGE:19, COUNTRY:"UK"}
];
var sorto = {
SCORE:"desc",TIME:"asc", AGE:"asc"
};
obja.keySort(sorto);
yields the following:
0: { USER: jane; SCORE: 4000; TIME: 35; AGE: 16; COUNTRY: DE; }
1: { USER: yuri; SCORE: 3000; TIME: 34; AGE: 19; COUNTRY: RU; }
2: { USER: anita; SCORE: 2500; TIME: 32; AGE: 17; COUNTRY: LV; }
3: { USER: joe; SCORE: 2500; TIME: 33; AGE: 18; COUNTRY: US; }
4: { USER: sally; SCORE: 2000; TIME: 30; AGE: 16; COUNTRY: CA; }
5: { USER: mark; SCORE: 2000; TIME: 30; AGE: 18; COUNTRY: DE; }
6: { USER: bob; SCORE: 2000; TIME: 32; AGE: 16; COUNTRY: US; }
7: { USER: amy; SCORE: 1500; TIME: 29; AGE: 19; COUNTRY: UK; }
8: { USER: mary; SCORE: 1500; TIME: 31; AGE: 19; COUNTRY: PL; }
9: { USER: tim; SCORE: 1000; TIME: 30; AGE: 17; COUNTRY: UK; }
keySort: { }
(using a print function from here)
here is a jsbin example.
edit: cleaned up and posted as mksort.js on github.
This is handy for alpha sorts of all sizes.
Pass it the indexes you want to sort by, in order, as arguments.
Array.prototype.deepSortAlpha= function(){
var itm, L=arguments.length, order=arguments;
var alphaSort= function(a, b){
a= a.toLowerCase();
b= b.toLowerCase();
if(a== b) return 0;
return a> b? 1:-1;
}
if(!L) return this.sort(alphaSort);
this.sort(function(a, b){
var tem= 0, indx=0;
while(tem==0 && indx<L){
itm=order[indx];
tem= alphaSort(a[itm], b[itm]);
indx+=1;
}
return tem;
});
return this;
}
var arr= [[ "Nilesh","Karmshil"], ["Pranjal","Deka"], ["Susants","Ghosh"],
["Shiv","Shankar"], ["Javid","Ghosh"], ["Shaher","Banu"], ["Javid","Rashid"]];
arr.deepSortAlpha(1,0);
I suggest to use a built in comparer and chain the wanted sort order with logical or ||.
function customSort(a, b) {
return a[3].localeCompare(b[3]) || a[1].localeCompare(b[1]);
}
Working example:
var array = [
[0, 'Aluminium', 0, 'Francis'],
[1, 'Argon', 1, 'Ada'],
[2, 'Brom', 2, 'John'],
[3, 'Cadmium', 3, 'Marie'],
[4, 'Fluor', 3, 'Marie'],
[5, 'Gold', 1, 'Ada'],
[6, 'Kupfer', 4, 'Ines'],
[7, 'Krypton', 4, 'Joe'],
[8, 'Sauerstoff', 3, 'Marie'],
[9, 'Zink', 5, 'Max']
];
array.sort(function (a, b) {
return a[3].localeCompare(b[3]) || a[1].localeCompare(b[1]);
});
document.write('<pre>');
array.forEach(function (a) {
document.write(JSON.stringify(a) + '<br>');
});
You could concat the 2 variables together into a sortkey and use that for your comparison.
list.sort(function(a,b){
var aCat = a.var1 + a.var2;
var bCat = b.var1 + b.var2;
return (aCat > bCat ? 1 : aCat < bCat ? -1 : 0);
});
I found multisotr. This is simple, powerfull and small library for multiple sorting. I was need to sort an array of objects with dynamics sorting criteria:
const criteria = ['name', 'speciality']
const data = [
{ name: 'Mike', speciality: 'JS', age: 22 },
{ name: 'Tom', speciality: 'Java', age: 30 },
{ name: 'Mike', speciality: 'PHP', age: 40 },
{ name: 'Abby', speciality: 'Design', age: 20 },
]
const sorted = multisort(data, criteria)
console.log(sorted)
<script src="https://cdn.rawgit.com/peterkhayes/multisort/master/multisort.js"></script>
This library more mutch powerful, that was my case. Try it.
String Appending Method
You can sort by multiple values simply by appending the values into a string and comparing the strings. It is helpful to add a split key character to prevent runoff from one key to the next.
Example
const arr = [
{ a: 1, b: 'a', c: 3 },
{ a: 2, b: 'a', c: 5 },
{ a: 1, b: 'b', c: 4 },
{ a: 2, b: 'a', c: 4 }
]
function sortBy (arr, keys, splitKeyChar='~') {
return arr.sort((i1,i2) => {
const sortStr1 = keys.reduce((str, key) => str + splitKeyChar+i1[key], '')
const sortStr2 = keys.reduce((str, key) => str + splitKeyChar+i2[key], '')
return sortStr1.localeCompare(sortStr2)
})
}
console.log(sortBy(arr, ['a', 'b', 'c']))
Recursion Method
You can also use Recursion to do this. It is a bit more complex than the String Appending Method but it allows you to do ASC and DESC on the key level. I'm commenting on each section as it is a bit more complex.
There are a few commented out tests to show and verify the sorting works with a mixture of order and default order.
Example
const arr = [
{ a: 1, b: 'a', c: 3 },
{ a: 2, b: 'a', c: 5 },
{ a: 1, b: 'b', c: 4 },
{ a: 2, b: 'a', c: 4 }
]
function sortBy (arr, keys) {
return arr.sort(function sort (i1,i2, sKeys=keys) {
// Get order and key based on structure
const compareKey = (sKeys[0].key) ? sKeys[0].key : sKeys[0];
const order = sKeys[0].order || 'ASC'; // ASC || DESC
// Calculate compare value and modify based on order
let compareValue = i1[compareKey].toString().localeCompare(i2[compareKey].toString())
compareValue = (order.toUpperCase() === 'DESC') ? compareValue * -1 : compareValue
// See if the next key needs to be considered
const checkNextKey = compareValue === 0 && sKeys.length !== 1
// Return compare value
return (checkNextKey) ? sort(i1, i2, sKeys.slice(1)): compareValue;
})
}
// console.log(sortBy(arr, ['a', 'b', 'c']))
console.log(sortBy(arr, [{key:'a',order:'desc'}, 'b', 'c']))
// console.log(sortBy(arr, ['a', 'b', {key:'c',order:'desc'}]))
// console.log(sortBy(arr, ['a', {key:'b',order:'desc'}, 'c']))
// console.log(sortBy(arr, [{key:'a',order:'asc'}, {key:'b',order:'desc'}, {key:'c',order:'desc'}]))
Try this:
t.sort( (a,b)=> a[3].localeCompare(b[3]) || a[1].localeCompare(b[1]) );
let t = [
//[publicationID, publication_name, ownderID, owner_name ]
[1, 'ZBC', 3, 'John Smith'],
[2, 'FBC', 5, 'Mike Tyson'],
[3, 'ABC', 7, 'Donald Duck'],
[4, 'DBC', 1, 'Michael Jackson'],
[5, 'XYZ', 2, 'Michael Jackson'],
[6, 'BBC', 4, 'Michael Jackson'],
];
// owner_name subarrray index = 3
// publication_name subarrray index = 1
t.sort( (a,b)=> a[3].localeCompare(b[3]) || a[1].localeCompare(b[1]) );
console.log(t.join('\n'));
I assume that your data in array let t = [ [publicationID, publication_name, ownderID, owner_name ], ... ] where index of owner_name = 3 and publication_name =1.
I was working with ng-grid and needed to to multiple column sorting on an array of records returned from an API, so I came up with this nifty, dynamic multi-sort function.
First of all, ng-grid fires an "event" for "ngGridSorted" and passes this structure back, describing the sort:
sortData = {
columns: DOM Element,
directions: [], //Array of string values desc or asc. Each index relating to the same index of fields
fields: [], //Array of string values
};
So I built a function that will dynamically generate a sort function based on the sortData as shown above (Don't be scared by the scroll bar! It's only about 50 lines long! Also, I'm sorry about the slop. It prevented a horizontal scrollbar!):
function SortingFunction(sortData)
{
this.sortData = sortData;
this.sort = function(a, b)
{
var retval = 0;
if(this.sortData.fields.length)
{
var i = 0;
/*
Determine if there is a column that both entities (a and b)
have that are not exactly equal. The first one that we find
will be the column we sort on. If a valid column is not
located, then we will return 0 (equal).
*/
while( ( !a.hasOwnProperty(this.sortData.fields[i])
|| !b.hasOwnProperty(this.sortData.fields[i])
|| (a.hasOwnProperty(this.sortData.fields[i])
&& b.hasOwnProperty(this.sortData.fields[i])
&& a[this.sortData.fields[i]] === b[this.sortData.fields[i]])
) && i < this.sortData.fields.length){
i++;
}
if(i < this.sortData.fields.length)
{
/*
A valid column was located for both entities
in the SortData. Now perform the sort.
*/
if(this.sortData.directions
&& i < this.sortData.directions.length
&& this.sortData.directions[i] === 'desc')
{
if(a[this.sortData.fields[i]] > b[this.sortData.fields[i]])
retval = -1;
else if(a[this.sortData.fields[i]] < b[this.sortData.fields[i]])
retval = 1;
}
else
{
if(a[this.sortData.fields[i]] < b[this.sortData.fields[i]])
retval = -1;
else if(a[this.sortData.fields[i]] > b[this.sortData.fields[i]])
retval = 1;
}
}
}
return retval;
}.bind(this);
}
I then sort the results of my API (results) like so:
results.sort(new SortingFunction(sortData).sort);
I hope somebody else enjoys this solution as much as I do! Thanks!
I had a similar problem while displaying memory pool blocks from the output of some virtual DOM h-functions composition. Basically I faced to the same problem as sorting multi-criteria data like scoring results from players around the world.
I have noticed that multi-criteria sorting is:
- sort by the first column
- if equal, sort by the second
- if equal, sort by the third
- etc... nesting and nesting if-else
And if you don't care, you could fail quickly in a if-else nesting hell... like callback hell of promises...
What about if we write a "predicate" function to decide if which part of alternative using ? The predicate is simply :
// useful for chaining test
const decide = (test, other) => test === 0 ? other : test
Now after having written your classifying tests (byCountrySize, byAge, byGameType, byScore, byLevel...) whatever who need, you can weight your tests (1 = asc, -1 = desc, 0 = disable), put them in an array, and apply a reducing 'decide' function like this:
const multisort = (s1, s2) => {
const bcs = -1 * byCountrySize(s1, s2) // -1 = desc
const ba = 1 *byAge(s1, s2)
const bgt = 0 * byGameType(s1, s2) // 0 = doesn't matter
const bs = 1 * byScore(s1, s2)
const bl = -1 * byLevel(s1, s2) // -1 = desc
// ... other weights and criterias
// array order matters !
return [bcs, ba, bgt, bs, bl].reduce((acc, val) => decide(val, acc), 0)
}
// invoke [].sort with custom sort...
scores.sort(multisort)
And voila ! It's up to you to define your own criterias / weights / orders... but you get the idea. Hope this helps !
EDIT:
* ensure that there is a total sorting order on each column
* be aware of not having dependencies between columns orders, and no circular dependencies
if, not, sorting can be unstable !
function multiSort() {
var args =$.makeArray( arguments ),
sortOrder=1, prop='', aa='', b='';
return function (a, b) {
for (var i=0; i<args.length; i++){
if(args[i][0]==='-'){
prop=args[i].substr(1)
sortOrder=-1
}
else{sortOrder=1; prop=args[i]}
aa = a[prop].toLowerCase()
bb = b[prop].toLowerCase()
if (aa < bb) return -1 * sortOrder;
if (aa > bb) return 1 * sortOrder;
}
return 0
}
}
empArray.sort(multiSort( 'lastname','firstname')) Reverse with '-lastname'
My own library for working with ES6 iterables (blinq) allows (among other things) easy multi-level sorting
const blinq = window.blinq.blinq
// or import { blinq } from 'blinq'
// or const { blinq } = require('blinq')
const dates = [{
day: 1, month: 10, year: 2000
},
{
day: 1, month: 1, year: 2000
},
{
day: 2, month: 1, year: 2000
},
{
day: 1, month: 1, year: 1999
},
{
day: 1, month: 1, year: 2000
}
]
const sortedDates = blinq(dates)
.orderBy(x => x.year)
.thenBy(x => x.month)
.thenBy(x => x.day);
console.log(sortedDates.toArray())
// or console.log([...sortedDates])
<script src="https://cdn.jsdelivr.net/npm/blinq#2.0.2"></script>
I have just published to npm a micro-library called sort-helper (source on github). The idea is to import the helper by to create the comparison function for sort array method through the syntax items.sort(by(column, ...otherColumns)), with several way to express the columns to sort by:
By key: persons.sort(by('lastName', 'firstName')),
By selector: dates.sort(by(x => x.toISOString())),
In descending order: [3, 2, 4, 1].sort(by(desc(n => n))) → [3, 2, 1, 0],
Ignoring case: ['B', 'D', 'c', 'a'].sort(by(ignoreCase(x => x))).join('') → 'aBcD'.
It's similar to the nice thenBy mentioned in this answer but with the following differences that may be more to the taste of some:
An approach more functional than object-oriented (see thenBy fluent API),
A syntax a bit terser and still as much readable, natural almost like SQL.
Fully implemented in TypeScript, to benefit from type safety and type expressivity.
Sourced from GitHub
function sortMethodAsc(a, b) {
return a == b ? 0 : a > b ? 1 : -1;
}
function sortMethodWithDirection(direction) {
if (direction === undefined || direction == "asc") {
return sortMethodAsc;
} else {
return function(a, b) {
return -sortMethodAsc(a, b);
}
}
}
function sortMethodWithDirectionByColumn(columnName, direction){
const sortMethod = sortMethodWithDirection(direction)
return function(a, b){
return sortMethod(a[columnName], b[columnName]);
}
}
function sortMethodWithDirectionMultiColumn(sortArray) {
//sample of sortArray
// sortArray = [
// { column: "column5", direction: "asc" },
// { column: "column3", direction: "desc" }
// ]
const sortMethodsForColumn = (sortArray || []).map( item => sortMethodWithDirectionByColumn(item.column, item.direction) );
return function(a,b) {
let sorted = 0;
let index = 0;
while (sorted === 0 && index < sortMethodsForColumn.length) {
sorted = sortMethodsForColumn[index++](a,b);
}
return sorted;
}
}
//=============================================
//=============================================
//=============================================
//test
var data = [
{"CountryName":"Aruba","CountryCode":"ABW","GNI":280},{
"CountryName":"Afghanistan","CountryCode":"ABW","GNI":280},{"CountryName":"Angola","CountryCode":"AGO","GNI":280},{"CountryName":"Albania","CountryCode":"ALB","GNI":4320},
{"CountryName":"Arab World","CountryCode":"ARB","GNI":280},{"CountryName":"United Arab Emirates","CountryCode":"ARE","GNI":39130},
{"CountryName":"Argentina","CountryCode":"ARG","GNI":13030},{"CountryName":"Armenia","CountryCode":"ARM","GNI":3990},{"CountryName":"American Samoa","CountryCode":"ASM","GNI":280},
{"CountryName":"Antigua and Barbuda","CountryCode":"ATG","GNI":13810},{"CountryName":"Australia","CountryCode":"AUS","GNI":51360},
{"CountryName":"Austria","CountryCode":"AUT","GNI":45440},{"CountryName":"Azerbaijan","CountryCode":"AZE","GNI":4080},{"CountryName":"Burundi","CountryCode":"BDI","GNI":280},
{"CountryName":"Belgium","CountryCode":"BEL","GNI":41790},{"CountryName":"Benin","CountryCode":"BEN","GNI":800},{"CountryName":"Burkina Faso","CountryCode":"BFA","GNI":590},
{"CountryName":"Bangladesh","CountryCode":"BGD","GNI":1470},{"CountryName":"Bulgaria","CountryCode":"BGR","GNI":7860},{"CountryName":"Bahrain","CountryCode":"BHR","GNI":21150},
{"CountryName":"Bosnia and Herzegovina","CountryCode":"BIH","GNI":4910},{"CountryName":"Belarus","CountryCode":"BLR","GNI":5280},
{"CountryName":"Belize","CountryCode":"BLZ","GNI":4390},{"CountryName":"Bolivia","CountryCode":"BOL","GNI":3130},{"CountryName":"Brazil","CountryCode":"BRA","GNI":8600},
{"CountryName":"Barbados","CountryCode":"BRB","GNI":15270},{"CountryName":"Brunei Darussalam","CountryCode":"BRN","GNI":29600},
{"CountryName":"Bhutan","CountryCode":"BTN","GNI":2660},{"CountryName":"Botswana","CountryCode":"BWA","GNI":6730},
{"CountryName":"Central African Republic","CountryCode":"CAF","GNI":390},{"CountryName":"Canada","CountryCode":"CAN","GNI":42870},
{"CountryName":"Central Europe and the Baltics","CountryCode":"CEB","GNI":13009},{"CountryName":"Switzerland","CountryCode":"CHE","GNI":80560},
{"CountryName":"Chile","CountryCode":"CHL","GNI":13610},{"CountryName":"China","CountryCode":"CHN","GNI":8690},{"CountryName":"Cote d'Ivoire","CountryCode":"CIV","GNI":1580},
{"CountryName":"Cameroon","CountryCode":"CMR","GNI":1370},{"CountryName":"Colombia","CountryCode":"COL","GNI":5890},{"CountryName":"Comoros","CountryCode":"COM","GNI":1280},
{"CountryName":"Cabo Verde","CountryCode":"CPV","GNI":3030},{"CountryName":"Costa Rica","CountryCode":"CRI","GNI":11120},
{"CountryName":"Caribbean small states","CountryCode":"CSS","GNI":8909},{"CountryName":"Cyprus","CountryCode":"CYP","GNI":23720},
{"CountryName":"Czech Republic","CountryCode":"CZE","GNI":18160},{"CountryName":"Germany","CountryCode":"DEU","GNI":43490},
{"CountryName":"Djibouti","CountryCode":"DJI","GNI":1880},{"CountryName":"Dominica","CountryCode":"DMA","GNI":6590},{"CountryName":"Denmark","CountryCode":"DNK","GNI":55220},
{"CountryName":"Dominican Republic","CountryCode":"DOM","GNI":6630},{"CountryName":"Algeria","CountryCode":"DZA","GNI":3940},
{"CountryName":"East Asia & Pacific (excluding high income)","CountryCode":"EAP","GNI":6987},{"CountryName":"Early-demographic dividend","CountryCode":"EAR","GNI":3352},
{"CountryName":"East Asia & Pacific","CountryCode":"EAS","GNI":10171},{"CountryName":"Europe & Central Asia (excluding high income)","CountryCode":"ECA","GNI":7375},
{"CountryName":"Europe & Central Asia","CountryCode":"ECS","GNI":22656},{"CountryName":"Ecuador","CountryCode":"ECU","GNI":5920},
{"CountryName":"Euro area","CountryCode":"EMU","GNI":35645},{"CountryName":"Spain","CountryCode":"ESP","GNI":27180},{"CountryName":"Estonia","CountryCode":"EST","GNI":18190},
{"CountryName":"Ethiopia","CountryCode":"ETH","GNI":740},{"CountryName":"European Union","CountryCode":"EUU","GNI":32784},
{"CountryName":"Fragile and conflict affected situations","CountryCode":"FCS","GNI":1510},{"CountryName":"Finland","CountryCode":"FIN","GNI":44580},
{"CountryName":"Fiji","CountryCode":"FJI","GNI":4970},{"CountryName":"France","CountryCode":"FRA","GNI":37970},{"CountryName":"Gabon","CountryCode":"GAB","GNI":6650},
{"CountryName":"United Kingdom","CountryCode":"GBR","GNI":40530},{"CountryName":"Georgia","CountryCode":"GEO","GNI":3780},{"CountryName":"Ghana","CountryCode":"GHA","GNI":1880},
{"CountryName":"Guinea","CountryCode":"GIN","GNI":790},{"CountryName":"Guinea-Bissau","CountryCode":"GNB","GNI":660},
{"CountryName":"Equatorial Guinea","CountryCode":"GNQ","GNI":7050},{"CountryName":"Greece","CountryCode":"GRC","GNI":18090},
{"CountryName":"Grenada","CountryCode":"GRD","GNI":9180},{"CountryName":"Guatemala","CountryCode":"GTM","GNI":4060},{"CountryName":"Guyana","CountryCode":"GUY","GNI":4500},
{"CountryName":"High income","CountryCode":"HIC","GNI":40142},{"CountryName":"Honduras","CountryCode":"HND","GNI":2250},{"CountryName":"Heavily indebted poor countries (HIPC)","CountryCode":"HPC","GNI":904},{"CountryName":"Croatia","CountryCode":"HRV","GNI":12570},{"CountryName":"Haiti","CountryCode":"HTI","GNI":760},{"CountryName":"Hungary","CountryCode":"HUN","GNI":12870},{"CountryName":"IBRD only","CountryCode":"IBD","GNI":5745},{"CountryName":"IDA & IBRD total","CountryCode":"IBT","GNI":4620},{"CountryName":"IDA total","CountryCode":"IDA","GNI":1313},{"CountryName":"IDA blend","CountryCode":"IDB","GNI":1791},
{"CountryName":"Indonesia","CountryCode":"IDN","GNI":3540},{"CountryName":"IDA only","CountryCode":"IDX","GNI":1074},{"CountryName":"India","CountryCode":"IND","GNI":1800},{"CountryName":"Ireland","CountryCode":"IRL","GNI":55290},{"CountryName":"Iraq","CountryCode":"IRQ","GNI":4630},{"CountryName":"Iceland","CountryCode":"ISL","GNI":60830},{"CountryName":"Israel","CountryCode":"ISR","GNI":37270},{"CountryName":"Italy","CountryCode":"ITA","GNI":31020},{"CountryName":"Jamaica","CountryCode":"JAM","GNI":4760},{"CountryName":"Jordan","CountryCode":"JOR","GNI":3980},{"CountryName":"Japan","CountryCode":"JPN","GNI":38550},{"CountryName":"Kazakhstan","CountryCode":"KAZ","GNI":7970},{"CountryName":"Kenya","CountryCode":"KEN","GNI":1460},{"CountryName":"Kyrgyz Republic","CountryCode":"KGZ","GNI":1130},
{"CountryName":"Cambodia","CountryCode":"KHM","GNI":1230},{"CountryName":"Kiribati","CountryCode":"KIR","GNI":3010},{"CountryName":"St. Kitts and Nevis","CountryCode":"KNA","GNI":16240},{"CountryName":"Kuwait","CountryCode":"KWT","GNI":31430},{"CountryName":"Latin America & Caribbean (excluding high income)","CountryCode":"LAC","GNI":7470},{"CountryName":"Lao PDR","CountryCode":"LAO","GNI":2270},{"CountryName":"Lebanon","CountryCode":"LBN","GNI":8400},{"CountryName":"Liberia","CountryCode":"LBR","GNI":620},{"CountryName":"Libya","CountryCode":"LBY","GNI":5500},{"CountryName":"St. Lucia","CountryCode":"LCA","GNI":8830},{"CountryName":"Latin America & Caribbean","CountryCode":"LCN","GNI":8251},{"CountryName":"Least developed countries: UN classification","CountryCode":"LDC","GNI":1011},{"CountryName":"Low income","CountryCode":"LIC","GNI":774},{"CountryName":"Sri Lanka","CountryCode":"LKA","GNI":3850},{"CountryName":"Lower middle income","CountryCode":"LMC","GNI":2118},{"CountryName":"Low & middle income","CountryCode":"LMY","GNI":4455},{"CountryName":"Lesotho","CountryCode":"LSO","GNI":1210},{"CountryName":"Late-demographic dividend","CountryCode":"LTE","GNI":8518},{"CountryName":"Lithuania","CountryCode":"LTU","GNI":15200},{"CountryName":"Luxembourg","CountryCode":"LUX","GNI":70260},{"CountryName":"Latvia","CountryCode":"LVA","GNI":14740},{"CountryName":"Morocco","CountryCode":"MAR","GNI":2860},{"CountryName":"Moldova","CountryCode":"MDA","GNI":2200},{"CountryName":"Madagascar","CountryCode":"MDG","GNI":400},{"CountryName":"Maldives","CountryCode":"MDV","GNI":9760},
{"CountryName":"Middle East & North Africa","CountryCode":"MEA","GNI":7236},{"CountryName":"Mexico","CountryCode":"MEX","GNI":8610},{"CountryName":"Marshall Islands","CountryCode":"MHL","GNI":4840},{"CountryName":"Middle income","CountryCode":"MIC","GNI":4942},{"CountryName":"Mali","CountryCode":"MLI","GNI":770},
{"CountryName":"Malta","CountryCode":"MLT","GNI":24080},{"CountryName":"Myanmar","CountryCode":"MMR","GNI":1210},{"CountryName":"Middle East & North Africa (excluding high income)","CountryCode":"MNA","GNI":3832},{"CountryName":"Montenegro","CountryCode":"MNE","GNI":7400},{"CountryName":"Mongolia","CountryCode":"MNG","GNI":3270},{"CountryName":"Mozambique","CountryCode":"MOZ","GNI":420},{"CountryName":"Mauritania","CountryCode":"MRT","GNI":1100},{"CountryName":"Mauritius","CountryCode":"MUS","GNI":10130},{"CountryName":"Malawi","CountryCode":"MWI","GNI":320},{"CountryName":"Malaysia","CountryCode":"MYS","GNI":9650},{"CountryName":"North America","CountryCode":"NAC","GNI":56721},{"CountryName":"Namibia","CountryCode":"NAM","GNI":4570},{"CountryName":"Niger","CountryCode":"NER","GNI":360},{"CountryName":"Nigeria","CountryCode":"NGA","GNI":2100},
{"CountryName":"Nicaragua","CountryCode":"NIC","GNI":2130},{"CountryName":"Netherlands","CountryCode":"NLD","GNI":46180},{"CountryName":"Norway","CountryCode":"NOR","GNI":75990},{"CountryName":"Nepal","CountryCode":"NPL","GNI":800},{"CountryName":"Nauru","CountryCode":"NRU","GNI":10220},{"CountryName":"New Zealand","CountryCode":"NZL","GNI":38970},{"CountryName":"OECD members","CountryCode":"OED","GNI":37273},{"CountryName":"Oman","CountryCode":"OMN","GNI":14440},{"CountryName":"Other small states","CountryCode":"OSS","GNI":12199},{"CountryName":"Pakistan","CountryCode":"PAK","GNI":1580},{"CountryName":"Panama","CountryCode":"PAN","GNI":13280},{"CountryName":"Peru","CountryCode":"PER","GNI":5960},{"CountryName":"Philippines","CountryCode":"PHL","GNI":3660},{"CountryName":"Palau","CountryCode":"PLW","GNI":12700},{"CountryName":"Papua New Guinea","CountryCode":"PNG","GNI":2340},{"CountryName":"Poland","CountryCode":"POL","GNI":12730},{"CountryName":"Pre-demographic dividend","CountryCode":"PRE","GNI":1379},{"CountryName":"Portugal","CountryCode":"PRT","GNI":19820},{"CountryName":"Paraguay","CountryCode":"PRY","GNI":5470},{"CountryName":"West Bank and Gaza","CountryCode":"PSE","GNI":3180},{"CountryName":"Pacific island small states","CountryCode":"PSS","GNI":3793},{"CountryName":"Post-demographic dividend","CountryCode":"PST","GNI":41609},{"CountryName":"Qatar","CountryCode":"QAT","GNI":60510},{"CountryName":"Romania","CountryCode":"ROU","GNI":10000},{"CountryName":"Russian Federation","CountryCode":"RUS","GNI":9230},{"CountryName":"Rwanda","CountryCode":"RWA","GNI":720},{"CountryName":"South Asia","CountryCode":"SAS","GNI":1729},{"CountryName":"Saudi Arabia","CountryCode":"SAU","GNI":20090},{"CountryName":"Sudan","CountryCode":"SDN","GNI":2380},{"CountryName":"Senegal","CountryCode":"SEN","GNI":1240},{"CountryName":"Singapore","CountryCode":"SGP","GNI":54530},{"CountryName":"Solomon Islands","CountryCode":"SLB","GNI":1920},{"CountryName":"Sierra Leone","CountryCode":"SLE","GNI":510},{"CountryName":"El Salvador","CountryCode":"SLV","GNI":3560},{"CountryName":"Serbia","CountryCode":"SRB","GNI":5180},{"CountryName":"Sub-Saharan Africa (excluding high income)","CountryCode":"SSA","GNI":1485},{"CountryName":"Sub-Saharan Africa","CountryCode":"SSF","GNI":1486},{"CountryName":"Small states","CountryCode":"SST","GNI":11099},{"CountryName":"Sao Tome and Principe","CountryCode":"STP","GNI":1770},{"CountryName":"Suriname","CountryCode":"SUR","GNI":5150},{"CountryName":"Slovak Republic","CountryCode":"SVK","GNI":16610},{"CountryName":"Slovenia","CountryCode":"SVN","GNI":22000},{"CountryName":"Sweden","CountryCode":"SWE","GNI":52590},{"CountryName":"Eswatini","CountryCode":"SWZ","GNI":2950},{"CountryName":"Seychelles","CountryCode":"SYC","GNI":14170},{"CountryName":"Chad","CountryCode":"TCD","GNI":640},{"CountryName":"East Asia & Pacific (IDA & IBRD countries)","CountryCode":"TEA","GNI":7061},
{"CountryName":"Europe & Central Asia (IDA & IBRD countries)","CountryCode":"TEC","GNI":7866},{"CountryName":"Togo","CountryCode":"TGO","GNI":610},{"CountryName":"Thailand","CountryCode":"THA","GNI":5950},{"CountryName":"Tajikistan","CountryCode":"TJK","GNI":990},{"CountryName":"Turkmenistan","CountryCode":"TKM","GNI":6380},{"CountryName":"Latin America & the Caribbean (IDA & IBRD countries)","CountryCode":"TLA","GNI":8179},{"CountryName":"Timor-Leste","CountryCode":"TLS","GNI":1790},{"CountryName":"Middle East & North Africa (IDA & IBRD countries)","CountryCode":"TMN","GNI":3839},{"CountryName":"Tonga","CountryCode":"TON","GNI":4010},{"CountryName":"South Asia (IDA & IBRD)","CountryCode":"TSA","GNI":1729},
{"CountryName":"Sub-Saharan Africa (IDA & IBRD countries)","CountryCode":"TSS","GNI":1486},{"CountryName":"Trinidad and Tobago","CountryCode":"TTO","GNI":15340},{"CountryName":"Tunisia","CountryCode":"TUN","GNI":3490},{"CountryName":"Turkey","CountryCode":"TUR","GNI":10940},{"CountryName":"Tuvalu","CountryCode":"TUV","GNI":4970},{"CountryName":"Tanzania","CountryCode":"TZA","GNI":910},{"CountryName":"Uganda","CountryCode":"UGA","GNI":600},{"CountryName":"Ukraine","CountryCode":"UKR","GNI":2390},{"CountryName":"Upper middle income","CountryCode":"UMC","GNI":8197},{"CountryName":"Uruguay","CountryCode":"URY","GNI":15250},{"CountryName":"United States","CountryCode":"USA","GNI":58270},{"CountryName":"Uzbekistan","CountryCode":"UZB","GNI":2000},{"CountryName":"St. Vincent and the Grenadines","CountryCode":"VCT","GNI":7390},{"CountryName":"Vietnam","CountryCode":"VNM","GNI":2160},{"CountryName":"Vanuatu","CountryCode":"VUT","GNI":2920},{"CountryName":"World","CountryCode":"WLD","GNI":10371},{"CountryName":"Samoa","CountryCode":"WSM","GNI":4090},{"CountryName":"Kosovo","CountryCode":"XKX","GNI":3900},
{"CountryName":"South Africa","CountryCode":"ZAF","GNI":5430},{"CountryName":"Zambia","CountryCode":"ZMB","GNI":1290},{"CountryName":"Zimbabwe","CountryCode":"ZWE","GNI":1170},
{"CountryName":"Zimbabwe","CountryCode":"ZWE","GNI":1171}];
const sortMethod = sortMethodWithDirectionMultiColumn(
[
{ column: "GNI", direction: "asc" },
{ column: "CountryCode", direction: "desc" }
]
);
let sortedData = data.sort(sortMethod);
console.log("sorted by: 1)column:GNI-asc, 2)column:CountryCode-desc")
console.table(sortedData);
console.log(sortedData);
I need this for a small project I'm working on, so performance is not a priority.
I have two arrays, main array I want to be sorted, and array of sorting rules. I loop that rules array inside sorting callback function, and try to exit that loop as soon as possible.
I use multiplier in order to convert -1 to 1 depending on weather I'm sorting a property in ascending or descending order.
let array = [
{fullName: 'Michael Schumacher', sport: 'Formula 1'},
{fullName: 'Michael Jordan', sport: 'Basketball'},
{fullName: 'Damon Hill', sport: 'Formula 1'},
{fullName: 'Kobe Bryant', sport: 'Basketball'},
{fullName: 'Lebron James', sport: 'Basketball'},
{fullName: 'Lewis Hamilton', sport: 'Formula 1'},
];
const sortArray = (array, options) => {
if (!Array.isArray(options)) {
options = [{ key: options, order: 'asc' }];
}
options.forEach(item => {
item.multiplier = item.order != 'desc' ? -1 : 1;
});
return array.sort((firstItem, secondItem) => {
for (item of options) {
const { key, multiplier } = item;
const firstValue = firstItem[key];
const secondValue = secondItem[key];
if (firstValue != secondValue) {
return multiplier * (firstValue < secondValue ? 1 : -1);
}
}
return 0;
});
}
console.log('Original array');
console.log([...array]);
sortArray(array, 'sport');
console.log('Sorted by sport only (ascending, implicit, keeping the same order of athletes)');
console.log([...array]);
sortArray(array, [{key: 'sport'}, {key: 'fullName', order: 'desc'}]);
console.log('Sorted by sport (ascending, implicit), and by fullName (descending)');
console.log(array);
To simplify the understanding
The sort method compares numbers, if below 0, it sorts it to the let, if above zero it sorts it to the right.
So to add multi level sorting, check if the match === 0, then further sort it.
See example below
['a/b/c', 'a long piece of text/b', 'apple/b'].sort((a, b) => {
const asc = a.split('/').length - b.split('/').length
return asc
})
// outputs ['a long piece of text/b', 'apple/b', 'a/b/c']
['a/b/c', 'a long piece of text/b', 'apple/b'].sort((a, b) => {
const asc = a.split('/').length - b.split('/').length
return asc === 0 ? a.length - b.length : asc
})
// outputs: 'apple/b', 'a long piece of text/b', 'a/b/c'
I see a lot of complicated solutions, so I'll paste here what I'm using:
assignedIssues.sort((a, b) => {
let order = sortByText(a.assignee?.username, b.assignee?.username)
if (order === 0) order = sort(a.labels, b.labels, statusLabels)
if (order === 0) order = sort(a.labels, b.labels, priorityLabels)
if (order === 0) order = sortByText(a.web_url, b.web_url)
return order
})
I think that this is much more readable, let you implement any custom sorting function for each level, without calling all unnecessarily.
Assuming you want to sort by multiple indexes, and assuming that you don't know the type of each field (string, number, or null).
You can create a function to sort with as many indexes as you like.
const compareWithType = (a, b) => {
if (typeof a === 'string') return a.localeCompare(b);
if (typeof a === 'number') return a - b;
return (!!a) - (!!b); // to sort non-string non-number falsy or null values, modify as you like.
}
const compareWithIndexes = (...indexes) => {
return (a, b) => {
for (let i in indexes) {
let diff = 0;
while (!diff) {
compareWithType(a[i], b[i]);
}
return diff;
}
}
}
[[1, 2, 3, 4, 5], [0, 2, 3, 4, 6]].sort(compareWithIndexes(2, 3, 4));
// compares (3 - 3) then (4 - 4) then (5 - 6)
Despite a lot of complicated answers here, I still like the basic way to do it
var arr = [
[3, 'pub2', 1, 'ownA'],
[1, 'pub1', 2, 'ownA'],
[2, 'pub1', 3, 'ownC']
];
// sorting priority is bottom to top, in this case owner name then publication name
// sort publication name
arr.sort((a,b) => a[1].localeCompare(b[1]));
// sort owner name
arr.sort((a,b) => a[3].localeCompare(b[3]));
console.log(arr);

Categories