Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

In JS, can you rely on comparing strings with the ">" and "<" operators?

Tags:

javascript

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.

like image 267
Cumulo Nimbus Avatar asked Aug 18 '17 15:08

Cumulo Nimbus


People also ask

Can you compare strings in JavaScript?

Overview. Two strings in Javascript can be compared to check whether they are the same or not using different methods like toUpperCase(), localeCompare(), etc. We can also compare two strings in javascript using RegEx. Operators like greater than, or less than or equality operators to compare two strings.

What happens when you compare strings in JavaScript?

Comparison operators return a boolean value. Strings are compared letter-by-letter in the “dictionary” order. When values of different types are compared, they get converted to numbers (with the exclusion of a strict equality check). The values null and undefined equal == each other and do not equal any other value.

Can we compare string and number in JavaScript?

When comparing a string with a number, JavaScript will convert the string to a number when doing the comparison. An empty string converts to 0. A non-numeric string converts to NaN which is always false . When comparing two strings, "2" will be greater than "12", because (alphabetically) 1 is less than 2.

How do you compare strings correctly?

The right way of comparing String in Java is to either use equals(), equalsIgnoreCase(), or compareTo() method. You should use equals() method to check if two String contains exactly same characters in same order. It returns true if two String are equal or false if unequal.


2 Answers

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)
like image 200
Cumulo Nimbus Avatar answered Oct 18 '22 20:10

Cumulo Nimbus


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)
like image 22
Erazihel Avatar answered Oct 18 '22 18:10

Erazihel