Sort these bond ratings

23

The task

The credit rating agencies assign ratings to bonds according to the credit-worthiness of the issuer, and the "Big Three" credit rating agencies use a similar (though not identical) tiered rating system. These have a clear, logical order - using the S&P tiers, AAA > AA+ > AA > AA- > A+ > ... > BBB > B > ... > C. Moody's uses a similar system, but names their tiers differently (Aaa > Aa1 > Aa2 > ... > Baa1 > ... > C).

Your task is to design a program which takes as input a list of strings representing bond rating tiers and output the same list, sorted in descending order from the highest tier (AAA/Aaa) to the lowest tier (C).

Input / Output

You may choose the format of the input (list, one per argument, CSV file). You may assume that every item in the input list is a valid rating string and that all rating strings in a list came from the same rating agency. Additionally, you may assume that none of the funky NA-style ratings such as "NR" or "WR" will be included - this is strictly from "Aaa/AAA" to "C". There may be duplicate ratings in the input list, and if found they should not be removed.

You may also choose the format of the output as is appropriate for your language, with the only restriction being that it should be outputting some standard text encoding like UTF-8 or ASCII.

Rules and scoring

This is code golf, so lowest byte count wins, standard loopholes disallowed. Please specify what the input and output format is.

Example program and test cases

The example Python program below can be used as a standard example of the correct sort order. The lists Moody and SP are the orderings to use.

Moody = ['Aaa', 'Aa1', 'Aa2', 'Aa3', 'A1', 'A2', 'A3',
         'Baa1', 'Baa2', 'Baa3', 'Ba1', 'Ba2', 'Ba3',
         'B1', 'B2', 'B3', 'Caa', 'Ca', 'C']

SP = ['AAA', 'AA+', 'AA', 'AA-', 'A+', 'A', 'A-',
      'BBB+', 'BBB', 'BBB-', 'BB+', 'BB', 'BB-',
      'B+', 'B', 'B-', 'CCC', 'CC', 'C']

test_cases = [
    (['Aa2', 'Aaa', 'Aa1'], ['Aaa', 'Aa1', 'Aa2']),
    (['AA', 'AA-', 'AA+'], ['AA+', 'AA', 'AA-']),
    (['Baa1', 'Ba1', 'A1', 'B1', 'Aaa', 'C', 'Caa', 'Aa1'],
     ['Aaa', 'Aa1', 'A1', 'Baa1', 'Ba1', 'B1', 'Caa', 'C']),
    (['BBB+', 'BB+', 'A+', 'B+', 'AAA', 'C', 'CCC', 'AA+'],
     ['AAA', 'AA+', 'A+', 'BBB+', 'BB+', 'B+', 'CCC', 'C']),
    (['B3', 'B1', 'B2'], ['B1', 'B2', 'B3']),
    (['B-', 'B+', 'B'], ['B+', 'B', 'B-']),
    (['B3', 'Caa', 'Aa1', 'Caa', 'Ca', 'B3'],
     ['Aa1', 'B3', 'B3', 'Caa', 'Caa', 'Ca']),
    (['B-', 'CCC', 'AA+', 'CCC', 'CC', 'B-'],
     ['AA+', 'B-', 'B-', 'CCC', 'CCC', 'CC'])
]

mdy_sort = lambda x: Moody.index(x)
sp_sort = lambda x: SP.index(x)

for l_in, l_out in test_cases:
    sort_key = mdy_sort if set(l_in).issubset(set(Moody)) else sp_sort
    assert sorted(l_in, key=sort_key) == l_out

Test cases

In case the python-style test case formatting is inconvenient, I've output it as space-delimited input strings (grouped in two-line pairs input followed by output):

Aa2 Aaa Aa1
Aaa Aa1 Aa2

AA AA- AA+
AA+ AA AA-

Baa1 Ba1 A1 B1 Aaa C Caa Aa1
Aaa Aa1 A1 Baa1 Ba1 B1 Caa C

BBB+ BB+ A+ B+ AAA C CCC AA+
AAA AA+ A+ BBB+ BB+ B+ CCC C

B3 B1 B2
B1 B2 B3

B- B+ B
B+ B B-

B3 Caa Aa1 Caa Ca B3
Aa1 B3 B3 Caa Caa Ca

B- CCC AA+ CCC CC B-
AA+ B- B- CCC CCC CC

Note: I mention the "Big Three" but only specify Moody's and S&P here - the reason is that the third, Fitch, uses the same system as S&P when you don't take into account the NA-style ratings, so including Fitch would be redundant.

Paul

Posted 2016-02-10T23:06:57.843

Reputation: 333

3Dude, if this is your first post, I am damn well impressed. +1 – Addison Crump – 2016-02-10T23:10:26.733

9@VoteToClose Long time listener, first time caller. ;) – Paul – 2016-02-10T23:11:46.903

1Should the output be case sensitive? – andlrc – 2016-02-10T23:27:52.347

@dev-null Yes, the output should be the same input strings, reordered. – Paul – 2016-02-10T23:29:00.223

Can you post some test cases? – Luis Mendo – 2016-02-10T23:51:46.533

@LuisMendo The test cases are in the example program, test_cases. The program runs the tests. – Paul – 2016-02-10T23:59:56.453

@Paul I know... but what if some of us don't have Python? – Luis Mendo – 2016-02-11T00:02:48.150

I assumed that anyone using the test cases would just copy-paste them and apply some text transformation to them to get them into their preferred input format. If there's a preferred format for test cases, I can add that to the main post. – Paul – 2016-02-11T00:06:55.493

1Can the input lists contain any equal ratings? If so should all equals be output, or remove duplicates? – Digital Trauma – 2016-02-11T00:09:54.977

@DigitalTrauma Yes, they can contain duplicates, and duplicates should not be removed. I will add a test case to this effect. – Paul – 2016-02-11T00:16:56.570

Answers

1

Pyth, 16 bytes

o+Xs}RGrN0N\z\,Q

We sort lexicographically by a key using @Neil's approach. Input and output are as lists; this does not mutate the list.

o+Xs}RGrN0N\z\,Q       Implicit: Q = input list
                        lambda N  (N is a bond rating)
       rN0               Convert N to lowercase
    }RG                  Map is-in G, the lowercase alphabet.
   s                     Sum the list of bools; the number of letters in N.
  X       N\z            Insert "z" at that position in N.
 +           \,          Append "," to the end.
                         This works because "," is between "+" and "-" lexicographically.
o              Q       Sort Q, using that lambda as a key.

Try it here. Test cases are all bond ratings of each rating scheme, with a duplicate thrown in.

lirtosiast

Posted 2016-02-10T23:06:57.843

Reputation: 20 331

7

ES6, 71 65 bytes

a=>a.sort((b,c)=>r(b)>r(c)||-1,r=s=>s.replace(/[^A-z]*$/,"z$&,"))

By inserting a z after the letters and suffixing a , we just have to sort the strings lexically.

Edit: Saved 6 bytes thanks to @user81655.

Neil

Posted 2016-02-10T23:06:57.843

Reputation: 95 035

Nice idea. It could also be a bit shorter by defining a separate function for the replace and using $& inside it: a=>a.sort((b,c)=>(r=t=>t.replace(/[^A-z]*$/,"z$&,"))(b)>r(c)||-1) – user81655 – 2016-02-11T10:17:18.703

@user81655 Bah, I originally had s="$1z$2," and although I realised that I could golf away the $1 it hadn't occurred to me that I could now golf away the $2 too... – Neil – 2016-02-11T19:37:13.207

2

Bash + GNU utilities, 45

Credit is due to @Neil for the approach.

sed s/$/e/|tr +-3 d-l|sort|tr -d e|tr d-l +-3

In my locale sort order, numbers sort before letters and - sorts before +. So these characters are transliterated into the alphabet range so they sort in the right order.

Try it online.

Digital Trauma

Posted 2016-02-10T23:06:57.843

Reputation: 64 644