30
\$\begingroup\$

Given a string of N, S, E and W, output a bearing (angle clockwise from North in degrees), correct to 5 decimal places.

In traditional compass notation, a string is made up of only 2 of these characters (like NNW or ESE). Here you must also accept strings that contain all 4 (like WNNNSE). Using only 2 symbols allows humans to intuitively understand the meaning. Allowing 4 symbols makes it horrible to read, but allows shorter ways of describing a bearing to a given accuracy.

(As pointed out in the comments by user2357112, it turns out you can prove that for any given bearing, the 4 symbol string will be exactly the same length as the 2 symbol string, so I've based this challenge on a false assumption. Hopefully this lack of a practical purpose doesn't detract from your enjoyment of the challenge...)

The exact method is described below, and is equivalent to the traditional notation (it expands on it rather than changing it).

Input

  • The input is a single string containing only the characters NESW.
  • The input may be a sequence of characters if you prefer, provided this does not include any preprocessing. For example, taking a nested list [N, [E, [S, [W]]]] to help with the order of processing is not permitted.
  • Taking different characters is not permitted. You may not take a string of 1234 instead of NESW.

Output

  • The output must be a decimal number or string representation of one (not a rational/fraction).
  • Trailing zeros do not need to be displayed. If the bearing is 9.00000, then the output 9 also counts as correct to 5 decimal places.
  • The output is in the range [0, 360). That is, including 0 but excluding 360.
  • Correctness is checked by rounding the output to 5 decimal places. If the bearing is 0.000005, this rounds to 0.00001. Outputs 0.00001 and 0.000005 are both correct.
  • Output in scientific notation for some inputs is acceptable. For example, 1e-5 instead of 0.00001.

Conversion

  • The single character compass points N, E, S, and W correspond to 0, 90, 180, and 270 degrees respectively.
  • Prepending one of these to a string results in the bearing that bisects the bearing of the single character and the bearing of the original string.
  • The closest of the two possible bisecting bearings is chosen, so that NE represents 45 degrees, not 225 degrees.
  • This is unambiguous except where the angle to be bisected is 180 degrees. Therefore NS, SN, WE, and EW correspond to undefined bearings, and the input will never end in any of these. They may however appear anywhere else in the input string, as this causes no ambiguity.
  • If the final two characters are identical, the final character will be redundant as the bisection will return the same bearing. Since this adds nothing to the notation, your code does not need to handle this. Therefore NN, EE, SS, and WW correspond to undefined bearings, and the input will never end in any of these. They may however appear anywhere else in the input string.

Examples

N: 0
E: 90
S: 180
SE: halfway between S and E: 135
NSE: halfway between N and SE: 67.5
NNSE: halfway between N and NSE: 33.75
NNNSE: halfway between N and NNSE: 16.875
NNNNSE: halfway between N and NNNSE: 8.4375

Test cases

A submission is only valid if it gives correct output for all of the test cases. Note that the test cases push to the limits of what can be handled with double precision. For languages that default to single precision, you will probably need to spend the bytes to specify double precision in order to get correct outputs.

Test case outputs are shown rounded to 5 decimal places, and also to arbitrary precision. Both are valid outputs.

WNE 337.5 337.5
WEN 337.5 337.5
WEWEWEWEWEWEWEWEWEWEWEN 330.00001 330.000007152557373046875
NESWNESWNESWNESWNESWNESWNESW 90 89.99999932944774627685546875
NNNNNNNNNNNNNNNNNNNNNNNE 0.00001 0.0000107288360595703125
NNNNNNNNNNNNNNNNNNNNNNNW 359.99999 359.9999892711639404296875
SNNNNNNNNNNNNNNNNNNNNNNNE 90.00001 90.00000536441802978515625
SNNNNNNNNNNNNNNNNNNNNNNNW 269.99999 269.99999463558197021484375

Scoring

This is . The score is the length of the source code in bytes, and the shortest wins.


Pedantry

I made the mistake of thinking that "North by North West" was a valid compass direction. A happy mistake, since it led to a challenge idea, but I then discovered from the Wikipedia page:

"The title of the Alfred Hitchcock 1959 movie, North by Northwest, is actually not a direction point on the 32-wind compass, but the film contains a reference to Northwest Airlines."

It also turns out that the method used for this challenge is only consistent with traditional compass points up to and including the 16 point compass. The 32-wind compass described on that page is subtly different and I have conveniently overlooked its existence for this challenge.

Finally, for anyone who thinks I should use "Southeast" instead of "South East", it seems to be a regional difference.

\$\endgroup\$
  • \$\begingroup\$ WNNNSE <= what would be the output for this example entry at start of your post ? it sounds invalid for me, but it's hard to tell. \$\endgroup\$ – Tensibai Nov 15 '16 at 13:39
  • \$\begingroup\$ @Tensibai For input WNNNSE the output would be 323.4375. See the example section for a walkthrough that would apply in the same way to this case. \$\endgroup\$ – trichoplax Nov 15 '16 at 13:40
  • \$\begingroup\$ Is input like f(N,N,N,S,E) okay? \$\endgroup\$ – Karl Napf Nov 15 '16 at 13:40
  • \$\begingroup\$ @KarlNapf I've expanded the input section to clarify. If I understand correctly, your example input with multiple arguments seems equivalent to a sequence of characters, so it would be acceptable. \$\endgroup\$ – trichoplax Nov 15 '16 at 13:59
  • 2
    \$\begingroup\$ "Allowing 4 symbols makes it horrible to read, but allows shorter ways of describing a bearing to a given accuracy." - are you sure about that? It seems like all inputs that describe the same bearing have the same length, since if you assign each bearing a dyadic rational from 0 to 1, a length-N string with N>1 always corresponds to a dyadic rational with denominator 2^(N+1) in lowest terms. Also, allowing more than 2 distinct letters in a bearing adds no expressive power; any bearing expressed with 3 or 4 letters can be expressed with 2. \$\endgroup\$ – user2357112 Nov 15 '16 at 23:35

13 Answers 13

13
\$\begingroup\$

JavaScript (ES6), 84 80 78 74 72 bytes

Saved a byte thanks to @Titus, 1 thanks to @Neil

f=([c,...s],b="NESW".search(c))=>b*90-(s[0]?(b-=f(s)/90)-4*(b*b>4):0)*45

It took a while, but I think I've finally perfected the formula...

Test snippet

f=([c,...s],b="NESW".search(c))=>b*90-(s[0]?(b-=f(s)/90)-4*(b*b>4):0)*45
g=(s,n)=>console.log('f("'+s+'"):',f(s),"expected:",n)

g("N", 0)
g("NE", 45)
g("E", 90)
g("SE", 135)
g("S", 180)
g("SW", 225)
g("W", 270)
g("NW", 315)
g("WNE", 337.5)
g("WEN", 337.5)
g("WEWEWEWEWEWEWEWEWEWEWEN", 330.00001)
g("NESWNESWNESWNESWNESWNESWNESWNESWNESWNESWNESWNESWNESW", 90)
g("NNNNNNNNNNNNNNNNNNNNNNNE", 0.00001)
g("NNNNNNNNNNNNNNNNNNNNNNNW", 359.99999)
g("SNNNNNNNNNNNNNNNNNNNNNNNE", 90.00001)
g("SNNNNNNNNNNNNNNNNNNNNNNNW", 269.99999)

Explanation

Let's start with the simplest case: a single-char string. The result is simply its (0-indexed) position in the string NESW, multiplied by 90.

For a two-char string, the result lies halfway between the result of the first char and the result of the second. However, there's a catch: if the absolute difference between the two is greater than 180 (e.g. NW or WN), we must 180 to the angle so that it's not pointing the opposite direction.

For any longer string, the result lies halfway between the result of the first char and the result of the rest of the string. This can be generalized in the following way:

  • If the input is a single char, return its index in the string NESW times 90.
  • Otherwise, return the index of the first char in the string NESW times 45, plus half the result of the rest of the string; add an extra 180 if the absolute difference between the two is greater than 90.
\$\endgroup\$
  • \$\begingroup\$ Excellent way to cutting the first character off the string! You can save one byte if you calculate with values divided by 45. \$\endgroup\$ – Titus Nov 16 '16 at 9:55
  • \$\begingroup\$ @Titus I can save 2 bytes with that technique, thanks! \$\endgroup\$ – ETHproductions Nov 16 '16 at 13:08
  • 1
    \$\begingroup\$ search instead of indexOf to save you a byte. \$\endgroup\$ – Neil Nov 16 '16 at 13:47
  • \$\begingroup\$ @Neil Thanks again! I managed to golf off three more by completely rearranging the equation. \$\endgroup\$ – ETHproductions Nov 16 '16 at 16:04
10
\$\begingroup\$

C#6, 226 217 207 185 bytes

using System.Linq;double N(string s){double b=f(s.Last());foreach(var c in s.Reverse()){b=(b+f(c)+(b-f(c)>2?4:f(c)-b>2?-4:0))/2;b=(b+4)%4;}return b*90;}int f(char x)=>"NESW".IndexOf(x);

Edit: -10 bytes by "borrowing" idea from ETHproductions's submission
-22 bytes thanks to @Titus

Ungolfed

// Call this method
double N(string s){
    // Initialize bearing with last direction
    double b=f(s.Last());
    // Do backward. Doing last direction once more doesn't impact result
    foreach(var c in s.Reverse()){
        // Average current bearing with new bearing, adjusted with wrapping
        b=(b+f(c)+(b-f(c)>2?4:f(c)-b>2?-4:0))/2;
        // Make bearing back to range [0,4)
        b=(b+4)%4;
    }
    // Change from "full circle = 4" unit to degree
    return b*90;
}
// helper method to convert direction to bearing. This returns bearing with full circle = 4.
int f(char x)=>"NESW".IndexOf(x);
\$\endgroup\$
  • \$\begingroup\$ I think you can make your range adjustment back to [0,360) shorter by using % \$\endgroup\$ – trichoplax Nov 15 '16 at 15:01
  • \$\begingroup\$ @trichoplax Won´t that cut the decimals? \$\endgroup\$ – Titus Nov 15 '16 at 15:06
  • 1
    \$\begingroup\$ @Titus C# % works for floats as well as integers. \$\endgroup\$ – trichoplax Nov 15 '16 at 15:08
  • 1
    \$\begingroup\$ Save 10 bytes with b=(b+360)%360; instead of b+=b>360?-360:b<0?360:0;. Save another 12 bytes with dividing everything by 90 and return b*90;. \$\endgroup\$ – Titus Nov 16 '16 at 10:07
  • 1
    \$\begingroup\$ Here are 10 more bytes: merge the two assignments and remove braces: b=(b+f(c)+(b-f(c)>2?4:f(c)-b>2?-4:0)+8)/2%4; then distribute +8 to the ternary results b=(b+f(c)+(b-f(c)>2?12:f(c)-b>2?4:8))/2%4; \$\endgroup\$ – Titus Nov 16 '16 at 12:29
8
\$\begingroup\$

PHP, 95 88 86 100 127 104 101 bytes

  • -7 bytes with the null coalescing operator
  • -2 bytes by not replacing N (and more, because that allows to put the translation to the loop head: N is truthy, but evaluates to 0 in the calculation.)
  • +41 bytes for fixing the bisection (cough)
  • -7 bytes directly and -16 indirectly inspired by @ETHproductions´ code
  • -3 bytes by replacing strtr with one of my bit jugglings

for($i=strlen($s=$argv[1]);$i--;$p=($q+$p=$p??$q)/2+2*(abs($q-$p)>2))$q=ord($s[$i])/.8+3&3;echo$p*90;

This is officially the first time ever that I use the null coalescing operator. Run with -r.

PHP 7.1

Negative string offsets in the upcoming PHP version will save 12 bytes:
Replace strlen($s=$argv[1]) with 0 and $s with $argv[1].


Free bytes for (almost) everyone:

  • Calculating with 0,1,2,3 instead of 0,90,180,270 and multiplying the final result with 90 will save two bytes and probably allow further golfing.
  • There are some patterns in the ASCII codes of the characters. Try one of these in your language:
    • (a/2%6+2)%5
    • a<87?a/2&3^3:3 or a/2&3^3*(a<87)
    • a&1?a&2|a/4&1:0
    • a/.8-1&3
\$\endgroup\$
5
\$\begingroup\$

Python 3, 133 113 bytes

Just improving on @L3viathan's answer because I just made this account and therefore can't make comments yet.

d={"N":0,"E":.5,"S":1,"W":1.5}
def B(s):
 b=d[s[-1]]
 for c in s[::-1]:b=(b+d[c])/2+(abs(b-d[c])>1)
 return b*180
\$\endgroup\$
  • \$\begingroup\$ Welcome to Programming Puzzles & Code Golf, and nice improvement... \$\endgroup\$ – trichoplax Nov 15 '16 at 21:52
  • \$\begingroup\$ I didn't see your answer, but Titus had a similar idea, plus I had another, I'm down to 98 now :) \$\endgroup\$ – L3viathan Nov 16 '16 at 12:55
5
\$\begingroup\$

05AB1E, 48 42 37 32 bytes

Saved 6 bytes thanks to Emigna. Saved 5 bytes thanks to Titus' idea to work on range [0,4[ and multiply by 90 at the end. Saved 5 bytes thanks to Adnan's mastery of ancient xor/modulo metamorphosis.

So every angle is reduced from range [0,360[ to range [0,4[ throughout the execution. Result is then multiplied by 90 and displayed.

Ç30^5%R¬U¦vXy+;DX-Ä0›2*+4%U}X90*

It can be divided into two sequentially called subprograms.
First program: convert input string into an array of the corresponding angles in range [0,4[
Ç      Take the ascii value of all input characters
 30^5% Dark ascii manipulation that yields [0,1,2,3] for [N,E,S,W]

Now we have an array of integers in range [0,4[.

Second program: actually compute the final angle
R                          Reverse the array
 ¬                         Take the first value (the last of the non-reversed array)
  U                        Pop it from the stack and set X to the same value
   ¦                       Strip the first element
    v                      For each remaining element
     Xy+;                  Compute the average value between the leftmost value and X
         DX-Ä0›            Push 1 if angular distance cast to integer is > 0 (i.e. if it is >= 1), 0 otherwise. It's equivalent to checking >= 90 degrees
               2*+         Multiply by 2 (=2 if angular distance is >= 1 and 0 otherwise) and add it to the formerly computed average value. It's equivalent to multiplying by 180
                  4%       Perform mod 4. It's equivalent to performing mod 360
                    U      Store the result back to X
                     }     End for, mandatory if input has only one character
                      X90* Push X*90 and implicitly display it

Try it online!

Potential axes of golfing:

  • Not sure if that mod 4 is required (it would save 2 bytes). All test cases work without it, but maybe there exists a tricky case. A mathematical proof to either validate it or nullify it would be top-notch.
  • There is no implicit stuff besides displaying the result (closing quote marks, closing brackets).
\$\endgroup\$
  • 1
    \$\begingroup\$ Doesn't seem to give the requested result on the NNNNNNNNNNNNNNNNNNNNNNNE and SNNNNNNNNNNNNNNNNNNNNNNNE test cases. \$\endgroup\$ – Emigna Nov 16 '16 at 10:27
  • 2
    \$\begingroup\$ Weird. Now I do as well. I must have pasted wrong or something, sorry. You can shorten the code down to v"NESW"yk90*})R¬U¦vXy+;DX-Ä89›180*+360%U}X. \$\endgroup\$ – Emigna Nov 16 '16 at 10:47
  • 1
    \$\begingroup\$ Great explanation! It might be worth including a note that 89› actually means the integer part is greater than 89, which is equivalent to saying the complete number is greater than or equal to 90 (which still works fine because exactly 90 should never occur). Currently the comment in the explained code makes it sound like it's checking for greater than 89, whereas your code passes the test cases so is clearly correctly checking for greater than 90. \$\endgroup\$ – trichoplax Nov 16 '16 at 12:50
  • 1
    \$\begingroup\$ I edited the explanation so, however I wrote "cast to integer" since I'm not sure how the operator should behave towards negative floating point values. There is no problem here since it works on the absolute value, but I prefer not making too strong assumptions on the operator. \$\endgroup\$ – Osable Nov 16 '16 at 22:28
  • 1
    \$\begingroup\$ You can replace v"NESW"yk}) with Ç30^5% :) \$\endgroup\$ – Adnan Nov 17 '16 at 9:03
5
\$\begingroup\$

Python 3, 146 145 117 107 97 94 93 92 bytes

f(s):u='NESW'.find(s[0])*90;return(u+f(s[1:]))/2+180*(abs(u-‌​f(s[1:]))>180)if s[1:]else u

Call f with the string.

\$\endgroup\$
  • \$\begingroup\$ You can't have the two ...0else they throw SyntaxErrors. \$\endgroup\$ – Jonathan Allan Nov 15 '16 at 18:56
  • \$\begingroup\$ @JonathanAllan What version of Python are you using? I'm on 3.5.2 and it works. \$\endgroup\$ – L3viathan Nov 15 '16 at 18:58
  • \$\begingroup\$ I ran it on 3.3.3 - can you remove the space between else and - too? (can in 3.3.3) \$\endgroup\$ – Jonathan Allan Nov 15 '16 at 19:17
  • \$\begingroup\$ @JonathanAllan Yes I can! Thanks, that saves me another byte. \$\endgroup\$ – L3viathan Nov 15 '16 at 19:18
  • 2
    \$\begingroup\$ @Titus d.find can, I had the exact idea a minute ago; see updated answer. \$\endgroup\$ – L3viathan Nov 16 '16 at 12:53
5
\$\begingroup\$

C, 184 bytes

double h(char c){return ((c=='E')+(c=='S')*2+(c=='W')*3);}double d(char*s){double f=h(*s);if(s[1]){double t=f;f=(f+d(s+1)/90)/2;if(((t-f)>1)||((f-t)>1))f+=2;if(f>=4)f-=4;}return f*90;}

Ungolfed

// a helper function
double direction_(char ch)
{
    if (ch=='N')
        return 0.;
    else if (ch=='E')
        return 90.;
    else if (ch=='S')
        return 180.;
    else
        return 270.;
}

// this is the main function to call
double direction(char* str)
{
    double fAngle = direction_(str[0]);
    if (str[1])
    {
        double tmp = fAngle + direction(str+1);
        if (tmp>=360.)
            tmp-=360.;
        tmp/=2;

        if (((tmp-fAngle)>90.) || ((tmp-fAngle)<-90.))
        { //  check if we need to take the "other side"; if the resulting angle is more than 90 degrees away, we took the wrong on
            if (tmp>=180.)
                tmp-=180.;
            else
                tmp+=180.;
        }
        fAngle = tmp;
    }
    return fAngle;
}
\$\endgroup\$
  • \$\begingroup\$ seems that using float, doesn't give you the precision needed. \$\endgroup\$ – Eyal Lev Nov 15 '16 at 16:23
  • 4
    \$\begingroup\$ Welcome to PPCG! :D \$\endgroup\$ – mbomb007 Nov 15 '16 at 16:51
  • \$\begingroup\$ Won't the names of the functions conflict with each other (since the names are both d)? \$\endgroup\$ – Qwerp-Derp Nov 16 '16 at 6:18
  • \$\begingroup\$ @qwerp, different signature (one takes char*, other takes only char) \$\endgroup\$ – Eyal Lev Nov 16 '16 at 8:41
  • 2
    \$\begingroup\$ Function names are not name mangled in C, as they are in C++, so you need to rename one of them if you want it to be C. \$\endgroup\$ – Klas Lindbäck Nov 17 '16 at 14:41
3
\$\begingroup\$

R, 172 146 bytes

z=rev((0:3*90)[match(scan(,""),c("N","E","S","W"))]);p=z[1];l=length(z);for(i in 2:l)p=(p+z[i])/2+(abs(p-z[i])>180)*180;if(l<2)p=z;sprintf("%f",p)

Ungolfed

z=rev((0:3*90)[match(scan,""),c("N","E","S","W"))]); #1
p=z[1];                                              #2
l=length(z)                                          #3
for(i in 2:l)p=(p+z[i])/2+(abs(p-z[i])>180)*180;     #4
if(l<2)p=z                                           #5
sprintf("%f",p)                                      #6

Explained

  1. Read input from stdin
    • Match input by index to c("N","E","S","W")
    • From matched indices: match to the vector of degrees 0:3*90 (instead of c(0,90,180,270))
    • Reverse and store as z
  2. Initialize p to the degree equivalent to last char in input
  3. Store length of input as l
  4. Iteratively, calculate the closest of the two possible bisecting bearings.
  5. If only one input is given, set p to z
  6. Format and print

Try the test cases on R-fiddle (note that this is a function due to scan not working on R-fiddle)

\$\endgroup\$
  • \$\begingroup\$ Provided that the output is correct to 5 decimal places, you don't need to perform the rounding. From the challenge: Outputs 0.00001 and 0.000005 are both correct. So you should be able to save some bytes by not rounding \$\endgroup\$ – trichoplax Nov 15 '16 at 17:14
  • \$\begingroup\$ @trichoplax I understand. Can the input also be a vector of string characters like. c("N","N","E") instead of "NNE"? This equivalent to a non-nested python list ["N","N","E"]. \$\endgroup\$ – Billywob Nov 15 '16 at 17:19
  • \$\begingroup\$ Yes. I intended "sequence" to be a general term to include things like arrays, vectors, lists, tuples. \$\endgroup\$ – trichoplax Nov 15 '16 at 17:24
  • 1
    \$\begingroup\$ I think you can save 4 bytes if you divide everything by 90 and printf(p*90). \$\endgroup\$ – Titus Nov 16 '16 at 9:58
3
\$\begingroup\$

Haskell, 109 105 103 bytes

h=180
a#b|abs(a-b)<h=n|n>h=n-h|1>0=n+h where n=(a+b)/2 -- calculates the new "mean" on the cirlce
f 'N'=0                                          -- translates characters to angles
f 'E'=90
f 'S'=h
f _=270
foldr1(#).map f                                  -- traverses the whole string

Thanks for -2 byte @xnor!

\$\endgroup\$
  • \$\begingroup\$ The exhaustive list for f looks long, but I'm having trouble finding anything shorter. Closest I got was f c=90*until(\i->"NESW"!!i==c)(+1)0 (35). I think you can replace 'W' with _. \$\endgroup\$ – xnor Nov 16 '16 at 3:18
  • \$\begingroup\$ Yeah I also expected there to be something shorter, but didn't find anything. Thanks for the _! \$\endgroup\$ – flawr Nov 16 '16 at 21:42
3
\$\begingroup\$

Dyalog APL, 55 45 38 bytes

Solution

Requires ⎕IO←0, which is default on many systems. Asks for direction.

360|÷○÷180×12○(+÷(|+))/¯12○○2÷⍨'NES'⍳⍞

Explanation

Goes around the problem by converting each letter to a complex number 1∠θa + b · i, then doing a sum reduction from right-to-left (APL's forte) while normalizing at each step. The final θ is then converted to degrees and normalized to be within [0, 360):

'NES'⍳⍞ the indices of each input letter in "NES"; N→0, E→1, S→2, anything else→3

○2÷⍨ convert to angles in radians; θ = π · x2

¯12○ convert to complex numbers on the unit circle; ei · θ

(...)/ reduce the list with... (i.e. insert the function between the elements of...)

+÷(|+) ... the normalized sum; xn - 1 + xn|xn - 1 + xn|

12○ convert to angle; θ

÷○÷180× convert to degrees; 1π · 1180 · x

360| division remainder when divided by 360

TryAPL online!

Anecdote

If input and output was as orthogonal complex units, the entire solution would be just:

(+÷(|+))/

The rest of the code is parsing input and formatting output.

\$\endgroup\$
  • \$\begingroup\$ I notice the test outputs do not match those in the challenge to 5 decimal places, making this invalid. Does Dyalog APL have the option of using double precision? \$\endgroup\$ – trichoplax Nov 15 '16 at 15:26
  • \$\begingroup\$ @trichoplax Yes, ⎕FR←1287 makes use of 128 bit floats, but TryAPL doesn't allow it. \$\endgroup\$ – Adám Nov 15 '16 at 15:38
  • \$\begingroup\$ I think anything greater than or equal to 64 bit floats should work (I've only tested in Python though). Does this mean you can make the code valid but it will then only work for people who have installed the language? Maybe you could show the full valid code for the score, and include the online version that doesn't quite have the required accuracy so people can see that the algorithm is correct. \$\endgroup\$ – trichoplax Nov 15 '16 at 15:42
  • \$\begingroup\$ @trichoplax Actually, TryAPL uses double precision, but your test cases accumulate errors beyond 53 bits. \$\endgroup\$ – Adám Nov 15 '16 at 16:09
  • \$\begingroup\$ If it can be shown that the difference is due to differences in interpretation of the IEEE 754 standard, that are still standard compliant, then I'll adjust the test cases to make sure both interpretations give the same result to 5 decimal places. I chose the test cases such that they gave the same result to 5 decimal places in Python for both float (double precision) and arbitrary precision decimals. I'll have a look into it. \$\endgroup\$ – trichoplax Nov 15 '16 at 16:29
2
\$\begingroup\$

Common Lisp, 347 327 bytes

Thanks to @Titus for taking off a few

This can probably be golfed more, but at least it works (I think):

(defun d(c)(if(eql c #\N)0(if(eql c #\E)1(if(eql c #\S)2(if(eql c #\W)3)))))(defun m(a b)(if(> a b)(rotatef a b))(if(<(+(- 4 b)a)(- b a))(+(/(+(- 4 b)a)2)b)(+(/(- b a)2)a)))(defun f(s)(let((c))(setf c(d(char s(1-(length s)))))(do((a)(p(-(length s)2)(1- p)))((< p 0))(setf a(char s p))(setf c(m(d a)c)))(format t"~5$"(* c 90))))

Usage:

* (f "WNE")
337.50000
NIL

Function d takes a character N, E, W, or S and returns the appropriate degree. Function m gets the approprate combined degree of two given directions. Function f iterates through the provided string, computes the appropriate degree, and prints it as a floating point.

\$\endgroup\$
  • \$\begingroup\$ My LISP is rusty, but can dividing everything by 90 save 6 bytes? \$\endgroup\$ – Titus Nov 16 '16 at 10:01
  • \$\begingroup\$ @Titus I think it would. I've realized some other improvements so I'll add this when I'm at my computer \$\endgroup\$ – artificialnull Nov 16 '16 at 13:08
2
\$\begingroup\$

Befunge, 183 181 175 bytes

>~#+:#25#%6*#/`#2_$>5%4*:00p"Z}"4*:***20g#v_+2/00g10g-:8`\0\-8`+!v
v5:+*:*:"d"/+55+5$_^#!:\p01/**:*4"}Z":p020<%**:*"(2Z"+**5*:*"0}"!<
>5>+#<%#56#58#:*#/+\#5:#5_$$$,,,".">:#,_@

Try it online!

Explanation

This follows a similar algorithm to many of the other answers, only it's using fixed point calculations emulated with integers since Befunge doesn't support floating point.

Thanks to @Titus for the ASCII-to-int routine.

 ~ : 5 6* ` _$        while ((c = getchar()) > 30)  // ends with any ctrl char or EOF
> + 2 %6 / 2            push(c / 2 % 6 + 2)         // partial conversion to int

                      do {
  5%                    dir = pop() % 5             // completes the conversion to int   
  4*:00p                dir *= 4; lowres_dir = dir  // used by the 180-flip calculation
  "Z}"4*:***            dir *= 22500000             // this is 90000000 / 4 
  20g_                  if (!first_pass) {
    +2/                   dir = (dir+last_dir)/2    // last_dir is second item on stack
    00g10g-               diff = lowres_dir - last_lowres_dir
    :8`\0\-8`+!!          flip = diff>8 || -diff>8
    "}0"*:*5**+           dir += flip * 180000000   // add 180 degrees if we need to flip
    "Z2("*:**%            dir %= 360000000          // keep within the 360 degree range
                        }
  020p                  first_pass = false
  :"Z}"4*:**/10p        last_lowres_dir = dir / 22500000
  \                     last_dir = dir              // saved as second item on stack
  :!_                 } while (!stack.empty())

$                     pop()                         // this leaves the final dir on top
5+55+/                dir = (dir + 5)/10            // round down to 5 decimal places
"d":*:*+              dir += 100000000              // add a terminating digit
                      while (true) {                // convert into chars on stack
:55 + % 6 8 * +\ : _    push(dir%10+'0'); if (!dir) break
   > < 5 5 : /+ 5 5     dir /= 10
                      }

$$$                   pop() x 3                     // drop the chars we don't need
,,,                   putchar(pop()) x 3            // output first three chars
"."                   push('.')                     // add a decimal point
>:#,_@                while(c=pop()) putchar(c)     // output the remaining chars
\$\endgroup\$
  • \$\begingroup\$ Does that just mean you need to emulate a larger fixed point type (more decimal places)? The test cases are designed to require double precision, which is no more than 17 significant figures (a maximum of 16 decimal places), and 14 decimal places might possibly be enough. \$\endgroup\$ – trichoplax Nov 16 '16 at 22:50
1
\$\begingroup\$

APL (Dyalog Classic), 30 27 bytes

90×(4|2÷⍨+-4×2<∘|-)/'NES'⍳⎕

Try it online!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.