2

Given a list of device models, let's say iPhone models and names, I want to create a javascript sorting function that will return them in order. Currently the list could look something like this:

list = ['iPhone 8 Plus', 'iPhone 8', 'iPhone 7 Plus', 'iPhone 7', 'iPhone 6S Plus', 'iPhone 6 16GB', 'iPhone 6', 'iPhone 13', 'iPhone 12 Pro Max', 'iPhone 12 Pro', 'iPhone 11']

Trying to do a normal sort function returns the order where 11, 12 and 13 are not correctly placed. I am guessing this is because the "1" in 11,12 and 13 is lesser than the "7,8,9" and thus during comparison of strings is misplaced.

list.sort((a: string, b: string) => {
      if (a < b) {
        return 1;
      }
      if (a > b) {
        return -1;
      }
      return 0;
    })

How could I write a function that takes the number 11 instead of just the first "1" in it as the comparision?

ZarifS
  • 467
  • 1
  • 11
  • 20
  • I assume you're using typescript? Consider adding the tag – evolutionxbox Mar 13 '23 at 19:52
  • You're probably looking for natural sort algos. Do [either of](https://stackoverflow.com/a/4373037/7978627) [these answer](https://stackoverflow.com/questions/2802341/natural-sort-of-alphanumerical-strings-in-javascript) your question? – async await Mar 13 '23 at 19:57

2 Answers2

3

See if you can use String.prototype.localeCompare.

list.sort((a, b) => a.localeCompare(b, undefined, {
  numeric: true,
  sensitivity: 'base',
}))

Alternatively, you could write a custom sort function. Here is one strategy:

const list = ['iPhone 8 Plus', 'iPhone 8', 'iPhone 7 Plus', 'iPhone 7', 'iPhone 6S Plus', 'iPhone 6 16GB', 'iPhone 6', 'iPhone 13', 'iPhone 12 Pro Max', 'iPhone 12 Pro', 'iPhone 11']
console.log(sort(list))

function sort(list) {
  const result = list.map(s => s.replace(/([0-9]+)/g, ss => pad(ss, 10)))
  result.sort((a, b) => {
    return a < b ? -1 : 1
  })
  return result.map(unpad)
}

function pad(digits, size) {
  while (digits.length < size) {
    digits = '0' + digits
  }
  return digits
}

function unpad(input) {
  return input.replace(/0+([1-9][0-9]*)/g, '$1')
}

Note: This won't accommodate decimals like 'iPhone 5.5' or versions above 10 digits like 'iPhone 12345678912'.

twharmon
  • 4,153
  • 5
  • 22
  • 48
1

Assuming all models follow the naming convention provided and submodel (e.g. 6 vs 6s) does not matter, the simplest solution is:

list.sort((a, b) => {
    return parseInt(a.split(' ')[1]) - parseInt(b.split(' ')[1]);
})

This will split the names of the model by the spaces and sort by the second word (which is always the model number). Note that the integer must be parsed to allow for models including letters.

Your list does not include the iPhone X, but if it did and you could not modify the original list you could always use a conditional to check for X and treat as a 10.

For secondary handling (if the model number is the same) you can always run a second function if parseInt(a.split(' ')[1]) - parseInt(b.split(' ')[1]) === 0

Blair
  • 91
  • 6