PronunciationSort™

We all know of different fancy sorting algorithms, but none of these give us numbers in a way that's easy to pronounce. To remedy this, I propose using PronunciationSort™, the most natural way to sort lists of numbers.

Pronunciation

The official rules for pronouncing numbers (in this challenge) is that the digits are pronounced one by one, and the resulting string is sorted in lexicographic order. As an example, this means that the number 845 is pronounced "eight four five", and should be sorted accordingly.

Negative numbers

Negative numbers are pronounced by prepending the word "minus". Thus, -23 is pronounced as "minus two three". Note that this causes negative numbers to end up in the middle of the output, right between numbers starting with 4 (four) and 9 (nine).

As a guide, the official order of words for PronunciationSort™ is:

• eight
• five
• four
• minus
• nine
• one
• seven
• six
• three
• two
• zero

That is,

8, 5, 4, -, 9, 1, 7, 6, 3, 2, 0

Input

A list of integers in the range $$\[-999, 999]\$$, containing at most 100 elements. Input as a list of strings is not permitted. If your language does not support input as list, it is permissible to give input as separate integers.

The input will not contain any invalid numbers, or any number starting with a 0 (except the number 0 itself). The input will generally not be sorted, it can be given in any order.

Output

The same integers, in PronunciationSort™ order. Note that the numbers should only be converted to their pronunciations to get the sorting, the output should not contain any strings.

Examples

For the examples, the middle step (wrapped in parentheses) only serves as a guide, and is not a part of the output.

[1, 2, 3] -> (['one', 'two', 'three']) -> [1, 3, 2]
[-1, 0, 1, 2] -> (['minus one', 'zero', 'one', 'two']) -> [-1, 1, 2, 0]
[-100, 45, 96] -> (['minus one zero zero', 'four five', 'nine six']) -> [45, -100, 96]
[11, 12, 13, 134, 135] -> (['one one', 'one two', 'one three', 'one three four', 'one three five']) -> [11, 13, 135, 134, 12]

There's also a script for verifying your results.

• Why doesn't "one" (pronounced "won") come after two and before zero? – Ben Miller Sep 27 '18 at 18:50
• @BenMiller I can't recall if it was you who commented in the sandbox, but that comment gave me deja vu. To answer it here, I contemplated it, but I went with spelling to avoid spelling discussions (e.g. "two" vs "too", "won" or "wan") – maxb Sep 27 '18 at 19:57
• So it is actually more "spelling sort" than "pronounciation sort" :-) – Paŭlo Ebermann Sep 27 '18 at 20:20
• bummer. I was hoping they would be sorted by how difficult they are to pronounce... – NH. Sep 28 '18 at 15:08
• This challenge really should be renamed, since the sorting here has virtually nothing to do with pronunciation. It would be immensely more complex if it were pronunciation-based (e.g., four might come before five if you sort the monophthong ⟨ɔː⟩ before the diphthong ⟨aɪ⟩, but five before four if you sort a-derived ⟨a⟩ before o-derived ⟨ɔ⟩ – there is, to my knowledge, no established sorting order for pronunciation, neither in IPA nor in any other scheme). – Janus Bahs Jacquet Sep 29 '18 at 14:09

Jelly,  15  13 bytes

ṾV€ị“Þ⁽3Z6»µÞ

Try it online!

A monadic link accepting a list of integers which yields a list of integers.

How?

Sorts by the ordinal values of the digits of the integers (where - is a "digit" of -1) converted to strings using the characters at their 1-based & modular index in the magic string "murgeon lix".

The sort is effectively alphabetic where a space is considered less than any letter.

The magic string "murgeon lix" was found by inspecting Jelly's dictionaries used in compression. There are no words of 11 letters which satisfy the requirements (and none of more that would upon de-duplication). since a space sorts before letters the next most obvious choice is a word of length seven followed by a space followed by a word of length three. "murgeon" and "lix" is the only satisfying combination, although without a space others may be possible (e.g. “£Py:ƥ» is "murgeonalix" which works for the same byte-count)

ṾV€ị“Þ⁽3Z6»µÞ - Link: list of integers
Þ - sort by:
µ  -   the monadic link: -- i.e. do this for each integer, then sort by that
Ṿ             -     unevaluate  (e.g. -803 -> ['-','8','0','3'])
V€           -     evaluate each as Jelly code  (e.g. ['-','8','0','3'] -> [-1,8,0,3])
“Þ⁽3Z6»   -     "murgeon lix" (compression of words in Jelly's dictionary plus a space)
ị          -     index into (1-indexed & modular) (e.g. [-1,8,0,3] -> "i xr")

ṾV€ị“¡Zo⁶’Œ?¤µÞ

Here “¡Zo⁶’Œ?¤ finds the first permutation of natural numbers which would reside at the index 21,340,635 when all permutations of the numbers are sorted lexicographically - which is [6,10,9,3,2,8,7,1,5,4,11]. (“¡Zo⁶’ is a base 250 representation of 21340635, while Œ? does the calculation and ¤ groups these instructions together)

• Even with the explanation, I don't feel very clever. Awesome solution! – maxb Sep 27 '18 at 20:00
• The shorter version is probably also easier to understand! – Jonathan Allan Sep 27 '18 at 21:02

05AB1E (legacy), 15 bytes

Σε•Koéa₃•'-3ǝsk

Try it online!

Explanation

Σ                 # sort by
ε                # apply to each
sk   # index of the element in
•Koéa₃•         # "85409176320"
'-3ǝ     # with "-" inserted at index 3
• There seems to be a bug in the compressed integer •ĆU‘•.. It adds a newline during the mapping/sorting for whatever reason. Σ•ĆU‘•"54-ÿ"sSk could have been a 15-byte alternative I was working on, if it weren't for that weird bug.. If I change •ĆU‘• to the literal 9176320 it works just fine.. – Kevin Cruijssen Sep 27 '18 at 10:53
• @KevinCruijssen: That is weird. Yours would be 14 with …54-ì even – Emigna Sep 27 '18 at 11:35
• @KevinCruijssen: You could do Σ•RT‹•Á…54-ìsSk for 15 – Emigna Sep 27 '18 at 11:41
• •t∍ýJ•'-ìÁÁ would also work – Emigna Sep 27 '18 at 11:45

import Data.List
sortOn$map(elemIndex"54-9176320").show Try it online! • This is so clean and readable! Definitely the winner in terms of ease of understanding. – Stephen Belden Oct 2 '18 at 18:05 Perl 6, 30 bytes *.sort:{TR/0..9-/a5982176043/} Try it online! Port of G B's Ruby solution. Original 35 byte version *.sort: (~*).uninames».&{S/\w*.//} Try it online! Convert each number to a string, get the Unicode name of each character, strip the first word ("DIGIT" or "HYPHEN"), then sort. JavaScript (SpiderMonkey), 69 bytes a=>a.sort((a,b)=>(g=n=>[...n+''].map(c=>':598217604'[c]||3))(a)>g(b)) Try it online! • It looks like you can remove the +'', seeing as you're taking input as an array of strings. – Shaggy Sep 27 '18 at 10:01 • @Shaggy I'm not taking strings anymore, as I'm not sure if that's allowed here. – Arnauld Sep 27 '18 at 10:01 • Ah ... You're right. That's gonna add a couple of bytes to my solution. – Shaggy Sep 27 '18 at 10:03 K (ngn/k), 21 20 bytes {x@<"54-9176320"?$x}

Try it online!

{ } function with argument x

\$ format as strings

"..."? find indices of individual characters in the given string (or $$\-2^{63}\$$ for "not found" - note that the "8" is missing)

< compute sort-ascending permutation

x@ the argument at those indices

Python 3, 68 bytes67 bytes 64 bytes

lambda x:sorted(x,key=lambda y:[*map('54-9176320'.find,str(y))])

Uses built-in sorted function with an anonymous lambda for the key. Hard-code the sort order and compare each digit in each value in the input list to its position in the sort order list.

Edit: Saved 1 byte by removing 8 from sort list to take advantage of str.find returning -1 when parameter is not found. Thanks to maxb.

Edit2: Saved 3 bytes by using starred unpacking syntax in a list literal instead of list constructor

Try it online!

Pyth, 17 16 bytes

oxL"54-9176320"

Try it online here, or verify all the test cases at once here.

oxL"54-9176320"NQ   Implicit: Q=eval(input())
Trailing N, Q inferred
o                Q   Order the elements of Q, as N, using...
N      Convert N to string
xL                    Get the index of each character of that string...
"54-9176320"        ... in the lookup ordering
(if character missing, returns -1, so 8 is still sorted before 5)

Saved 1 byte thanks to @ngn and their K answer, by omitting 8 from the start of the dictionary string

Japt, 19 bytes

ñ_s ®n"54-9176320

Try it

• Nice solution! Input as a list of strings is not allowed unfortunately. – maxb Sep 27 '18 at 10:55
• You can save some by abusing S.n(s): ñ_s ®n"54-9176320 (apparently S.n(s) is exactly the same as s.b(S) for S of length 1, except it returns 0 in place of -1) – ETHproductions Sep 28 '18 at 2:58
• Nice trick, @ETHproductions, thanks :) I'll have to remember that one for the future. – Shaggy Sep 28 '18 at 10:14

Retina 0.8.2, 36 bytes

T-d3:598217604
O
T3:598217604-d

Try it online! Link includes test suite. Explanation:

T-d3:598217604

Translate the minus sign and digits to their position in pronunciation order, using : for 10th position.

O

Sort in pronunciation order.

T3:598217604`-d

Translate the order back to the original minus sign and digits.

Ruby, 50 bytes

->a{a.sort_by{|x|x.to_s.tr('0-9-','a5982176043')}}

Try it online!

R, 58 bytes

function(x)x[order(mapply(chartr,"-0-9","dkfjicbhgae",x))]

Try it online!

Input is a list of numbers which is implicitly converted as string using chartr. order then uses lexigographic order to retrieve the order by which the original list should be sorted.

Java (JDK 10), 123 bytes

l->l.sort(java.util.Comparator.comparing(n->{var r="";for(var c:(""+n).split(""))r+=11+"54-9176320".indexOf(c);return r;}))

Try it online!

This is a naive Java implementation. Should be much golfable.

Credits

• Changing the .chars-IntStream and .reduce to a regular loop saves 2 bytes: n->{var r="";for(var c:(""+n).split(""))r+=10+"854-9176320".indexOf(c);return r;}. In addition, one more byte can be saved by changing 10+"85 to 20+"5, since the .indexOf for digit 8 would then result in -1. Try it online 123 bytes – Kevin Cruijssen Sep 27 '18 at 16:58

JavaScript (SpiderMonkey), 87 73 bytes

a=>a.sort((p,q,F=b=>[...""+b].map(x=>"54-9176320".search(x)))=>F(p)>F(q))

Try it online!

Thanks @Arnauld for telling that the sort in SpiderMonkey is stable, so the ||-(F(q)>F(p)) part can finally be dropped.

Red, 114 bytes

func[n][g: func[a][collect[foreach c form a[keep index? find"854-9176320"c]]]sort/compare n func[x y][(g x)< g y]]

Try it online!

f: func [ n ] [
g: func [ a ] [
collect [
foreach c form a [
keep index? find "854-9176320" c
]
]
]
sort/compare n func [ x y ] [ (g x) < g y ]
]

C++, 353 bytes

This is kind of a comedy entry, but I was wasting time and wrote it, so I can't not post it... Enjoy a chuckle, and let me know if there are any space-savers I missed!

#include<algorithm>
#include<iostream>
#include<iterator>
#include<numeric>
#include<string>
using namespace std;auto f(int i){auto s=to_string(i);for(auto&c:s)c='A'+"854-9176320"s.find(c);return s;}int main(){int a[100];auto b=begin(a);auto e=end(a);iota(b,e,-50);sort(b,e,[](int l,int r){return f(l)<f(r);});copy(b,e,ostream_iterator<int>(cout," "));}

Output:

8 5 4 48 45 44 49 41 47 46 43 42 40 -8 -5 -50 -4 -48 -45 -44 -49 -41 -47 -46 -43 -42 -40 -9 -1 -18 -15 -14 -19 -11 -17 -16 -13 -12 -10 -7 -6 -3 -38 -35 -34 -39 -31 -37 -36 -33 -32 -30 -2 -28 -25 -24 -29 -21 -27 -26 -23 -22 -20 9 1 18 15 14 19 11 17 16 13 12 10 7 6 3 38 35 34 39 31 37 36 33 32 30 2 28 25 24 29 21 27 26 23 22 20 0

• I see you also live by the motto "if I don't press enter there are fewer lines to debug" – maxb Sep 30 '18 at 6:30
• Hey, I can't afford frivolous whitespace in this language! It's funny to have to do this, seeing as the rest of the time, few things make me as angry as opening up others' code, who apparently think writing a horrifying monolithic wall will make them seem smarter, and/or are actively docked pay for every newline they type. – underscore_d Sep 30 '18 at 10:40
• Hello! Squeezed your solution to 195 characters – Max Yekhlakov Oct 2 '18 at 5:53
• @MaxYekhlakov Cool, thanks for pondering it! I realise after reading around a bit more; it seems that I needn't necessarily have provided a full compilable program, but instead only the functions that would process the specified input and output. D'oh! – underscore_d Oct 2 '18 at 19:27

Mathematica, 68 bytes

SortBy[If[# < 0,"m ",""]<>StringRiffle@IntegerName@IntegerDigits@#&]

Function. Takes a list of integers as input and returns the sorted list as output. Just separates the digits of each number with IntegerDigits, converts each digit to "zero", "one", etc., with IntegerName, converts the list into a space-separated string with StringRiffle, prepends an "m " if the number is negative, and sorts based on this string. Indeed, this was the shortest approach I can find, since Mathematica only natively uses lexicographic sorting for lists of the same length; thus, an approach based on 854-9176320 ends up taking more bytes since string functions are so expensive.

• Always trust mathematica to have a combination of builtins. Clever solution! – maxb Sep 30 '18 at 10:55

05AB1E, 15 14 bytes

Σ•ĆU‘•…54-ìsSk

-1 byte thanks to @Emigna.

Explanation:

Σ                 # Sort by:
•ĆU‘•            #  Push the compressed integer 9176320
…54-        #  Push the string "54-"
ì       #  Prepend this string before the integer: "54-9176320"
s      #  Swap so the current number to sort is at the top of the stack
S     #  Convert it to a list of characters
k    #  Check for each its index in the string (resulting in -1 for '8')

See this 05AB1E tip of mine (section How to compress large integers) to understand why •ĆU‘• is 9176320.