There be many a land lubber that knows naught of the pirate ways and gives direction by degree!
They know not how to box the compass!



Task description

 Create a function that takes a heading in degrees and returns the correct 32-point compass heading.
 Use the function to print and display a table of Index, Compass point, and Degree; rather like the corresponding columns from, the first table of the wikipedia article, but use only the following 33 headings as input:
[0.0, 16.87, 16.88, 33.75, 50.62, 50.63, 67.5, 84.37, 84.38, 101.25, 118.12, 118.13, 135.0, 151.87, 151.88, 168.75, 185.62, 185.63, 202.5, 219.37, 219.38, 236.25, 253.12, 253.13, 270.0, 286.87, 286.88, 303.75, 320.62, 320.63, 337.5, 354.37, 354.38]. (They should give the same order of points but are spread throughout the ranges of acceptance).


Notes;
 The headings and indices can be calculated from this pseudocode:
for i in 0..32 inclusive:
    heading = i * 11.25
    case i %3:
      if 1: heading += 5.62; break
      if 2: heading -= 5.62; break
    end
    index = ( i mod 32) + 1
 The column of indices can be thought of as an enumeration of the thirty two cardinal points (see talk page)..


import Data.Char (toUpper)
 
import Data.Maybe (fromMaybe)
 
import Text.Printf (PrintfType, printf)
 
dirs :: [String]
dirs =
  [ "N"
  , "NbE"
  , "N-NE"
  , "NEbN"
  , "NE"
  , "NEbE"
  , "E-NE"
  , "EbN"
  , "E"
  , "EbS"
  , "E-SE"
  , "SEbE"
  , "SE"
  , "SEbS"
  , "S-SE"
  , "SbE"
  , "S"
  , "SbW"
  , "S-SW"
  , "SWbS"
  , "SW"
  , "SWbW"
  , "W-SW"
  , "WbS"
  , "W"
  , "WbN"
  , "W-NW"
  , "NWbW"
  , "NW"
  , "NWbN"
  , "N-NW"
  , "NbW"
  ]
 
-- Index between 0 and 31 ->  the corresponding compass point name.
pointName :: Int -> String
pointName = capitalize . concatMap (fromMaybe "?" . fromChar) . (dirs !!)
  where
    fromChar c =
      lookup
        c
        [ ('N', "north")
        , ('S', "south")
        , ('E', "east")
        , ('W', "west")
        , ('b', " by ")
        , ('-', "-")
        ]
    capitalize (c:cs) = toUpper c : cs
 
-- Degrees -> compass point index between 0 and 31.
pointIndex :: Double -> Int
pointIndex d = (round (d * 1000) + 5625) `mod` 360000 `div` 11250
 
printPointName :: PrintfType t => String -> t
printPointName d =
  let deg = read d :: Double
      idx = pointIndex deg
  in printf "%2d  %-18s  %6.2f°\n" (idx + 1) (pointName idx) deg
 
main :: IO ()
main = mapM_ (printPointName . show) [0 .. 31]