Reputation: 9695
I am building an abstract table, each column in the table can contain either all numbers or all strings. Each column should be sortable by clicking on the column header.
Currently I am using JS native sort and passing a compareFunction:
const rows = [
{name: 'Adam', age: 27, rank: 3},
{name: 'Zeek', age: 31, rank: 1},
{name: 'Nancy', age: 45, rank: 4},
{name: 'Gramps', age: 102, rank: 2},
]
const compareFn = (x, y) => {
const sortDirValue = this.state.sortDirection === 'DESC' ? 1 : -1
if (x[this.state.sortBy] === y[this.state.sortBy]) return 0
return x[this.state.sortBy] < y[this.state.sortBy] ? sortDirValue : -sortDirValue
}
this.state = {
sortBy: 'name',
sortDirection: 'ASC'
}
rows.sort(compareFn)
console.log('---- Sorted by alphabetical name ----')
console.log(rows)
this.state = {
sortBy: 'age',
sortDirection: 'DESC'
}
rows.sort(compareFn)
console.log('---- Sorted by descending age ----')
console.log(rows)
In all the test cases I've tried so far this appears to work. However, I know JS can be finicky with sorting, like how out of the box sort()
will sort arrays of numbers alphabetically.
Can I rely on consistent correct sorting of both numbers and strings with the above approach? If not, what is an example of data that will not be sorted properly this way.
Upvotes: 13
Views: 2297
Reputation: 9
It will compare the strings, but the specific unicode values may differ from the intended behaviour. For instance, Capital Letters will have a lower value than lowercase ones regardless of alphabetical order. If you were to use this, keep in mind that the order is { / } / | is greater than lowercase, is greater than CAPITAL, is greater than numbers, is greater than most symbols (!",).
Upvotes: 1
Reputation: 9695
No, one cannot rely on alphabetical sorting with the >
/<
operators. The most prominent example of when data will not be sorted properly this way is with a mix of upper and lower case characters.
Other answers are valid in that using localeCompare
is the way to go for comparing strings. However, I have found that numbers and mixes of strings and numbers can also be compared effectively this way as well.
x.localeCompare(y, 'kn', { numeric: true })
By utilizing the numeric option localeCompare provides I was able to achieve much more robust sorting, while also avoiding needing branching conditional logical to handle each the string
and the number
case.
const rows = [
{name: 'Adam', age: 27, rank: 3, thing: 19},
{name: 'Zeek', age: 31, rank: 1, thing: 'wut dis'},
{name: 'Nancy', age: 45, rank: 4, thing: '$dolla'},
{name: 'Gramps', age: 102, rank: 2, thing: 2},
]
const compareFn = (x, y) => {
const xData = x[this.state.sortBy].toString()
const yData = y[this.state.sortBy].toString()
if (this.state.sortDirection !== 'DESC') {
return xData.localeCompare(yData, 'kn', { numeric: true })
} else {
return yData.localeCompare(xData, 'kn', { numeric: true })
}
}
this.state = {
sortBy: 'name',
sortDirection: 'ASC'
}
rows.sort(compareFn)
console.log('---- Sorted by alphabetical name ----')
console.log(rows)
this.state = {
sortBy: 'age',
sortDirection: 'DESC'
}
rows.sort(compareFn)
console.log('---- Sorted by descending age ----')
console.log(rows)
this.state = {
sortBy: 'thing',
sortDirection: 'ASC'
}
rows.sort(compareFn)
console.log('---- Sorted by ascending thing ----')
console.log(rows)
Upvotes: 9
Reputation: 87
To answer your question directly - yes, you can rely on consistent sorting using your approach. Strings are compared based on standard lexicographical ordering, using Unicode values. See:
Upvotes: 0
Reputation: 3967
I would say that the best way to compare strings in JS is localeCompare()
method.
first_string.localeCompare(second_string);
/* Expected results:
0: exact match
-1: first_string< second_string
1: first_string> second_string
*/
If you need other informations you should read the some documentations here and here.
UPDATE
.localeCompare()
allows for the fact that you may want to ignore certain differences in the strings (such as puncutation or diacriticals or case) and still allow them to compare the same or you want to ignore certain differences when deciding which string is before the other. And, it provides lots of options to control what comparison features are or are not used.
If you read the MDN documentation for string.prototype.localeCompare()
, you can see a whole bunch of options you can pass in to specify how the compare works. On a plain ascii string with no special characters in it that are all the same case, you are unlikely to see a difference, but start getting into diacriticals or case issues and localCompare()
has both more features and more options to control the comparison.
Some of the options available for controlling the comparison: - numeric collation - diacritical sensitivity - ability to ignore punctuation - case first - control whether upper or lower case compares first
Morover, localeCompare()
returns a value (negative, 0 or positive) that is perfectly aligned to use with a .sort()
callback.
Upvotes: 1
Reputation: 7605
While you can rely on comparing strings with the >
and <
operators, I'd recommend you to use String#localeCompare
instead.
As mentioned by the ECMAScript specification
, the localeCompare
function will make some checks before comparing the strings.
You can also find more explanations in the original ECMAScript specification
:
This function is intended to rely on whatever language-sensitive comparison functionality is available to the ECMAScript environment from the host environment, and to compare according to the rules of the host environment’s current locale. It is strongly recommended that this function treat strings that are canonically equivalent according to the Unicode standard as identical (in other words, compare the strings as if they had both been converted to Normalised Form C or D first).
The updated code should be like this:
const compareFn = (x, y) => {
if (this.state.sortDirection === 'DESC') {
return x[this.state.sortByKey].localeCompare(y[this.state.sortByKey])
} else {
return y[this.state.sortByKey].localeCompare(x[this.state.sortByKey])
}
}
rows.sort(compareFn)
Upvotes: 2