7

Trying to figure out how to sort an array of letter grades properly ['A+', 'A', 'A-', 'B+', 'B', 'B-', 'C+', 'C', 'C-', 'D+', 'D', 'D-', 'F']. Standard sorting function doesn't do it. I'm also looking to do this without setting a manual index for each letter grade.

const grades = ['B+', 'F', 'A-', 'A+', 'B', 'B-', 'C+', 'C', 'C-', 'D+', 'D', 'D-', 'A']

grades.sort((a, b) => {
    return a - b;
});

console.log(grades);

Expected output:

['A+', 'A', 'A-', 'B+', 'B', 'B-', 'C+', 'C', 'C-', 'D+', 'D', 'D-', 'F']

jsheffers
  • 1,602
  • 19
  • 41

7 Answers7

8

You could separate the grades and take an object for the postfix symbols.

const grades = ['B+', 'A', 'A-', 'A+', 'B', 'B-', 'C+', 'C', 'C-', 'D+', 'D', 'D-', 'F']

grades.sort((a, b) => {
    const order = { '+': -1, '-': 1, undefined: 0 };
    return a[0].localeCompare(b[0]) || order[a[1]] - order[b[1]];
});

console.log(...grades);
Nina Scholz
  • 376,160
  • 25
  • 347
  • 392
  • 1
    Why split at all? Seems you could do this without the split, strings can already be treated like arrays. Why use `var` instead of `const`? The `order` variable never changes. `grades.sort((a, b) => { const order = { '+': -1, '-': 1, undefined: 0 }; return a[0].localeCompare(b[0]) || order[a[1]] - order[b[1]]; });` – spex Sep 23 '19 at 16:59
  • Ideally you could just ```grades.sort((a, b) => `${a},`.localeCompare(`${b},`));``` but unfortunately with `localeCompare` the `-` comes before the `+` – spex Oct 13 '19 at 01:56
8

Actually simpler than you think:

const grades = ['B+', 'F', 'A-', 'A+', 'B', 'B-', 'C+', 'C', 'C-', 'D+', 'D', 'D-', 'A']


res = grades
    .map(x => x + ',')
    .sort()
    .map(x => x.slice(0, -1))

console.log(res.join())

The "magic" here is that , is right between + and - in the ascii table, so A becomes A, and sorts between A+ and A-.

As Nina suggested, you can also put the +, thing right into the sort callback:

const grades = ['B+', 'F', 'A-', 'A+', 'B', 'B-', 'C+', 'C', 'C-', 'D+', 'D', 'D-', 'A']

let cmp = (x, y) => (x > y) - (x < y);

res = grades.sort((x, y) => cmp(x + ',', y + ','))

console.log(...res)

where cmp is the poor man's replacement for the <=> operator

georg
  • 211,518
  • 52
  • 313
  • 390
  • if hacky, why not sort with postfix comma, like `grades.sort((a, b) => (a + ',').localeCompare(b + ','))`? – Nina Scholz Sep 23 '19 at 16:02
  • @NinaScholz: that's possible too, but I hate `localeCompare` ;) Let's wait until they add `<=>` to the language. – georg Sep 23 '19 at 16:05
  • @NinaScholz unfortunately `localCompare` will sort `-` before `+` – spex Sep 23 '19 at 16:30
  • @georg I would suggest using `const` for your `cmp` which shouldn't change. You are also declaring `res` without a declaration type, so it will default to `var`. But it's not even necessary to declare `res` since `Array.prototype.sort` does an in-place sort. The original `grades` is modified, so you can simply log out `grades` without an intermediate variable declaration. – spex Sep 23 '19 at 16:55
2

If you want to just stick to ASCII values:

const grades = ['B+', 'F', 'A-', 'A+', 'B', 'B-', 'C+', 'C', 'C-', 'D+', 'D', 'D-', 'A']

grades.sort((a, b) =>
  a.charCodeAt(0) === b.charCodeAt(0) // If the letters are the same
  ? (a.charCodeAt(1) || 44) - (b.charCodeAt(1) || 44) // Just compare the postfix
  : a.charCodeAt(0) - b.charCodeAt(0) // Otherwise compare the letters
);

console.log(...grades);

The ACII value of + is 43 and the ASCII value of - is 45, so we can use 44 (which is ,) when there is no postfix.

Alternatively, using template literals:

const grades = ['B+', 'F', 'A-', 'A+', 'B', 'B-', 'C+', 'C', 'C-', 'D+', 'D', 'D-', 'A']

grades.sort((a, b) =>
  a.charCodeAt(0) === b.charCodeAt(0) 
  ? `${a},`.charCodeAt(1) - `${b},`.charCodeAt(1)
  : a.charCodeAt(0) - b.charCodeAt(0)
);

console.log(...grades);
spex
  • 1,110
  • 10
  • 21
1

Few ways of doing it. One way is to split on the characters and compare the parts.

const grades = ['B+', 'A', 'A-', 'A+', 'B', 'B-', 'C+', 'C', 'C-', 'D+', 'D', 'D-', 'F']
const sorted = grades.sort((a, b) => {
  if (a === b) return 0 // if equal nothing changes
  const ap = a.split('') // separate into letter and sign
  const bp = b.split('')
  if (ap[0] === bp[0]) { // if first letter is the same, we have to check second
    return (ap[1] === "+" || b[1] === "-") ? -1 : 1
  }
  return ap[0].localeCompare(b[0]) // sort by letter grade
})
console.log(sorted)
epascarello
  • 204,599
  • 20
  • 195
  • 236
0

Try this.

const grades = ['B+', 'A', 'A-', 'A+', 'B', 'B-', 'C+', 'C', 'C-', 'D+', 'D', 'D-', 'F']
const sortGrades = function (grades) {
    const result = grades.sort(function (a, b) {
        if (a[0] < b[0]) {
            return -1;
        }
        if (b[0] > a[0]) {
            return 1;
        }
        if(a[0] === b[0]) {
            if(a[1] && a[1] === "+") {
                return -1;
            }
        }
        return 0;
    });
        return result;
    };
sortGrades(grades)
zakariah1
  • 362
  • 1
  • 11
0

you can try something like:


const symbols = ['+', undefined, '-']

function sort(a, b) {
  if (a[0] !== b[0]) return a > b ? 1 : -1
  return symbols.indexOf(a[1]) > symbols.indexOf(b[1]) ? 1 : -1
}

it checks first the letter, then the modifier (if any)

grokked
  • 401
  • 2
  • 8
0

Here is my solution using simple math :

const grades = ['B+', 'F', 'A-', 'A+', 'B', 'B-', 'C+', 'C', 'C-', 'D+', 'D', 'D-', 'A']

const getCounter = (letter) => {
  // Making simple math to sort it out
  let letterCounter = letter.charCodeAt(0) * 10;
  if(letter[1] === "+"){
    letterCounter -= 1;
  }
  else if(letter[1] === "-"){
    letterCounter += 1;
  }
  return letterCounter;
}

grades.sort((a, b) => {
  return getCounter(a) - getCounter(b);
});

console.log(grades)
// Print => ['A+','A','A-','B+','B','B-','C+','C','C-','D+','D','D-','F']
JulienRioux
  • 2,853
  • 2
  • 24
  • 37