Convert a Point of the Compass to Degrees

I came up with this challenge independently, but it turns out to be the inverse to this challenge by Doorknob. As I really like his spec, I decided to steal large parts of it instead of cooking up my own explanations.

The Challenge

Given the abbreviation of one of the 32 points on the compass, print the corresponding degrees. Feel free to skip ahead to the table below if you're not interested in an explanation of the 32 points.

Here is the full compass:

By Denelson83 (Own work) [GFDL or CC-BY-SA-3.0], via Wikimedia Commons

Each direction is 11.25 (360 / 32) degrees farther than the previous. For example, N (north) is 0 degrees, NbE (north by east) is 11.25 degrees, NNE (north-northeast) is 22.5 degrees, etc.

In detail, the names are assigned as follows:

• 0 degrees is N, 90 degrees is E, 180 degrees is S, and 270 degrees is W. These are called cardinal directions.
• The halfway points between the cardinal directions are simply the cardinal directions they're between concatenated. N or S always go first, and W or E always are second. These are called ordinal directions. The ordinal and cardinal directions together form the principal winds.
• The halfway points between the principal winds are the directions they're between concatenated. Cardinal directions go first, ordinal second. These are called half winds.
• The halfway points between principal and half winds are the adjacent principal wind "by" the closest cardinal direction away from the principal wind. This is denoted with a b. These are called quarter winds.

This results in the following chart:

#   Degrees  Abbrv.  Name
1   0        N       North
2   11.25    NbE     North by east
3   22.5     NNE     North-northeast
4   33.75    NEbN    Northeast by north
5   45       NE      Northeast
6   56.25    NEbE    Northeast by east
7   67.5     ENE     East-northeast
8   78.75    EbN     East by north
9   90       E       East
10  101.25   EbS     East by south
11  112.5    ESE     East-southeast
12  123.75   SEbE    Southeast by east
13  135      SE      Southeast
14  146.25   SEbS    Southeast by south
15  157.5    SSE     South-southeast
16  168.75   SbE     South by east
17  180      S       South
18  191.25   SbW     South by west
19  202.5    SSW     South-southwest
20  213.75   SWbS    Southwest by south
21  225      SW      Southwest
22  236.25   SWbW    Southwest by west
23  247.5    WSW     West-southwest
24  258.75   WbS     West by south
25  270      W       West
26  281.25   WbN     West by north
27  292.5    WNW     West-northwest
28  303.75   NWbW    Northwest by west
29  315      NW      Northwest
30  326.25   NWbN    Northwest by north
31  337.5    NNW     North-northwest
32  348.75   NbW     North by west

Here is a more detailed chart and possibly better explanation of the points of the compass.

Your task is to take as input one of the 32 abbreviations from the third column and output the corresponding degrees in the second column.

You may assume that input will always be exactly one of those 32 strings (and you may optionally but consistently expect a single trailing newline). Output should also be given exactly as listed above, although trailing zeroes are allowed. You may optionally output a single trailing newline.

You may write a program or function, taking input via STDIN (or closest alternative), command-line argument or function argument and outputting the result via STDOUT (or closest alternative), function return value or function (out) parameter.

This is code golf, so the shortest answer (in bytes) wins.

Pyth, 47 bytes

c*45x"NuMD¢¼Ew
XSj.{§/gWbZ¹°"C%CzC\½4

Hexdump, due to unprintable characters:

0000000: 632a 3435 7822 4e86 754d 0344 a2bc 4504  c*45x"N.uM.D..E.
0000010: 770a 9518 1c58 536a 2e7b a77f 2f67 5762  w....XSj.{../gWb
0000020: 5ab9 15b0 8798 2243 2543 7a43 5cbd 34    Z....."C%CzC\.4

Test harness

Due to a bug in the official command line compiler, this code only works via the online compiler, linked above, or the -c flag of the offline compiler. (The bug was fixed after the question was asked.)

This solution is very similar to @Dennis's CJam answer, using the process of hashing the input, looking up the result in a 32 byte lookup string, and then multiplying by 11.25.

The hash function I use is converting the input to a string as if it was a base 256 integer with C, taking the result modulo C of ½, which is 189, but saves a byte due to superior parsing, and converting that back to a string with C again.

Multiplying by 11.25 is accomplished by multiplying by 45, then dividing by 4, which saves a byte.

Ruby, 118 106

Thanks to Martin Büttner for 12 bytes saved.

This is currently the same length regardless of whether it's a function or a program.

lambda function

->s{n=4
d=0,0
s.chars{|e|c="SWNE".index e
c ?d[c%2]+=c/2*2*n-n :n=1}
(Complex(*d).arg*5.1).round%32*11.25}

program

n=4
d=0,0
gets.chars{|e|c="SWNE".index e
c ?d[c%2]+=c/2*2*n-n :n=1}
p (Complex(*d).arg*5.1).round%32*11.25

This is a cartesian walk through the points. The characters NSEW add or subtract 4 from the x and y coordinates stored in d[]. After a b (or any symbol other than NSEW) is encountered, this is reduced to 1.

The x and y data is then treated as a complex number to extract the angular argument. This is multiplied by 16/PI=5.1 . Although there are some geometrical errors in the approach, rounding this angle is sufficient to give the correct number -15..+16. Modulo is used to correct this to 0..31 (in Ruby % always returns positive.) Finally the result is multiplied by 11.25.

• What a clever idea with rounding! You get the arctan of the angle instead of the angle, taken relative to the nearest orthogonal direction, but that turns out close enough. – xnor Aug 15 '15 at 20:15
• @xnor without the rounding I get NbE 14.05 (+2.8), NNE 26.60 (+4.1), NEbE 51.41 (-4.84) so I had some leeway with the values of n but I did have to choose them carefully. – Level River St Aug 15 '15 at 20:47

Javascript (ES6), 153 bytes

Just wanted to get the ball rolling with a simple one.

x=>'N NbE NNE NEbN NE NEbE ENE EbN E EbS ESE SEbE SE SEbS SSE SbE S SbW SSW SWbS SW SWbW WSW WbS W WbN WNW NWbW NW NWbN NNW NbW'.split .indexOf(x)*45/4

Not particularly innovative, but it works, and perhaps there's some tips that could be derived from it. Don't worry, I'll think of another (hopefully better) technique.

• Perhaps you can compress the string? – mbomb007 Aug 15 '15 at 20:13
• Amazingly, it's still shorter than my (not submitted) solution in Python, which uses algorithmical approach. – pawel.boczarski Aug 16 '15 at 20:18

CJam, 49 bytes

0000000: 72 34 62 32 35 33 25 63 22 4e bf 6f f1 80 e8 dc 38  r4b253%c"N.o....8
0000011: 45 3d f0 2e 94 3c d3 12 53 24 e5 5f a6 63 28 60 57  E=...<..S$._.c(`W 0000022: 5b 14 20 92 17 81 d1 22 23 31 31 2e 32 35 2a [. ...."#11.25* The above is a hexdump, which can be reversed with xxd -r -c 17 -g 1. Try it online in the CJam interpreter. How it works r e# Read a token from STDIN. 4b e# Convert the string (array of code points) from base 4 to integer. 253% e# Take the result modulo 253. c e# Cast to character. "…" e# Push a 32 byte lookup table. # e# Find the index of the character. 11.25* e# Multiply the index by 11.25. Java, 653 (characters) I know Java can't win but I like to make the effort anyway. class C{float c=1/8f;int n=0;float a;public C(String s){if(s.contains("W"))n=4;switch(s.length()){case 1:p(d(s));case 2:p(e(s));case 3:if(s.contains("b"))f(s,1);g(s);}f(s,2);}int v(char x){switch(x){case 'N':return n;case 'E':return 1;case 'S':return 2;}return 3;}int d(String s){return v(s.charAt(0));}float e(String s){return (v(s.charAt(0))+v(s.charAt(1)))/2f;}void f(String s,int i){if(i<2)a=v(s.charAt(0));else a=e(s.substring(0,i));if(v(s.charAt(1+i))<a)c=-c;p(a+c);}void g(String s){p((d(s.substring(0,1))+e(s.substring(1)))/2f);}void p(float x){System.out.printf("%.2f",x*90);System.exit(0);}public static void main(String[]r){C c=new C(r[0]);}} It takes input from the commandline and outputs to the console. Ungolfed version: class Compass { float c = 1/8f; int n = 0; float a; public Compass( String s ) { if( s.contains( "W" ) ) { n = 4; } switch( s.length() ) { case 1: print( parse1( s ) ); case 2: print( parse2( s ) ); case 3: if( s.contains( "b" ) ) { parse3b4( s , 1 ); } parse3( s ); } parse3b4( s , 2 ); } int getValue( char x ) { switch( x ) { case 'N': return n; case 'E': return 1; case 'S': return 2; } return 3; } int parse1( String s ) { return getValue( s.charAt( 0 ) ); } float parse2( String s ) { return ( getValue( s.charAt( 0 ) ) + getValue( s.charAt( 1 ) ) ) / 2f; } void parse3b4( String s , int i ) { if( i < 2 ) a = getValue( s.charAt( 0 ) ); else a = parse2( s.substring( 0 , i ) ); if( getValue( s.charAt( 1 + i ) ) < a ) { c = -c; } print( a + c ); } void parse3( String s ) { print( ( parse1( s.substring( 0 , 1 ) ) + parse2( s.substring( 1 ) ) ) / 2f ); } void print( float x ) { System.out.printf( "%.2f" , x * 90 ); System.exit( 0 ); } public static void main( String[] args ) { Compass compass = new Compass( args[ 0 ] ); } } It works by assigning 0-3 to N-W (or 4 for N if W is involved). It recognizes 4 different situations: • parse1 is for single letter points, it just returns the value. • parse2 is for double letter points, it averages the values of the 2 points. • parse3 is for triple letter points, it takes the average of the average of the double and the single points. • parse3b4 is for all the ones with a 'b' in them, it calculates the value of the point before the 'b' and adds or subtracts 1/8 based on the 'direction' of the point after the 'b'. In print() the value is multiplied by 90 to get the actual angle. • Is it necessary to write C c=new C(r[0]);? Maybe new C(r[0]); is sufficient? – pawel.boczarski Aug 20 '15 at 17:58 Python 3, 149 bytes I tried a recursive algorithmic approach. The quarter winds were harder to handle than I thought at first, so this solution grew relatively long. def f(s): if'W'in s:s=s.replace(*'Nn') a=(len(s)-2)/8;return'b'in s and(1-a)*f(s[:-2])+a*f(s[-1])or a>=0and(f(s[0])+f(s[1:]))/2or'NESWn'.find(s)*90 Ungolfed: def f(s): if 'W'in s: s = s.replace('N','n') a=(len(s)-2)/8 if 'b' in s: a = 1/8 if len(s)==3 else 1/4 return (1-a)*f(s[:-2])+a*f(s[-1]) else: if len(s)==1: return 'NESWn'.find(s)*90 else: return (f(s[0])+f(s[1:]))/2 • Golfed version returns a decimal that needs to be multiplied by 10 (f("NbW") returns 34.875 instead of 348.75) – 智障的人 Aug 21 '15 at 4:38 • @viktorahlström, are you sure? It returns the correct value for me. Maybe you missed the last zero when copying and pasting the code? – Emil Aug 21 '15 at 7:45 • Oh, sorry, apparently it was that - my mistake, sorry! – 智障的人 Aug 21 '15 at 8:56 Haskell, 206 bytes c l=11.25*(fromIntegral$b$l) b l|(p y l)<0=a l+16|0<1=mod(a l)32 a l=round$(16/pi*)$atan$d$l d l=p x l/p y l p z[]=0.0 p z('b':[r])=z r/4 p z(a:r)=z a+p z r x 'E'=4 x 'W'=(-4) x c=0 y 'N'=4 y 'S'=(-4) y c=0 Convenient test: *Main> map c ["N","NbE","NNE","NEbN","NE","NEbE","ENE","EbN","E","EbS","ESE","SEbE","SE","SEbS","SSE","SbE","S","SbW","SSW","SWbS","SW","SWbW","WSW","WbS","W","WbN","WNW","NWbW","NW","NWbN","NNW","NbW"] [0.0,11.25,22.5,33.75,45.0,56.25,67.5,78.75,90.0,101.25,112.5,123.75,135.0,146.25,157.5,168.75,180.0,191.25,202.5,213.75,225.0,236.25,247.5,258.75,270.0,281.25,292.5,303.75,315.0,326.25,337.5,348.75] PowerShell - 350 Add-Type -AssemblyName *sys*forms*$f=new-object windows.forms.form
$c=new-object windows.forms.combobox$c.DataSource=(-split"N NbE NNE NEbN NE NEbE ENE EbN E EbS ESE SEbE SE SEbS SSE SbE S SbW SSW SWbS SW SWbW WSW WbS W WbN WNW NWbW NW NWbN NNW NbW")
$c.Parent=$f
$c.Add_SelectedValueChanged({$f.text=$c.SelectedIndex*11.25})$f.ShowDialog()

Julia, 151147 142 bytes

t=strchr
p=s->if ""==s 0;else i=t(s,'b')
(32/2^i)^sign(i)*p(i>0?s[1:i-1]:s[2:])+im^t("ESWN",s[i+1])end
f=s->90int(16mod(angle(p(s)),2pi)/pi)/8

A bit ungolfed:

# return approx. direction in term of complex number of absolute value 1,
# whose argument is the direction:
# N -> 0, E -> 0+1j, S -> -1, W -> 0-1j
function p(s)
if ""==s 0;
else
i=strchr(s,'b');
if i!=0
# if 'b' is 2nd in the word, following direction weight is 1/8,
# if 'b' is 3rd in the word, following direction weight is 1/4.
weight=2^(5-i)
# the first term to count avg is all before 'b'
first_term=s[1:i-1]
else
# weights are equal for the counted and the new (eg. 'NNW <= avg(N, NW)')
weight=1
# the first term to count avg is all after the first character
first_term=s[2:]
end
# the return value - average of two vectors
# s[i+1] evaluates to FIRST character if 'b' didn't occur
# or to the LAST CHARACTER (after 'b') if it did.
e^(im*angle(weight*p(first_term)+im^t("ESWN",s[i+1])));
end
end

# ... And the proper function for returning angle
# there are errors (sic!) in the counted direction, but dividing by 11.25,
# rounding and remultiplying by 11.25 filters them out
f=s->int32(mod(angle(p(s)),2pi)/pi*16)*11.25

In the ungolfed code, I count the average of two vectors as in order to have the vector still normalized. However, the errors due to elongation of the first vector are not yet accumulating with so little additions in our recursion, so while golfing the normalization step was removed and the computation goes simply. Errors less than 1/64 of full circle are filtered out by rounding.