Background
I've tried to solve /r/DailyProgrammer Challenge #303 [Easy]: Ricochet (summarised below) in Haskell. The code below is now the second Haskell script I've ever written, so I'd consider myself quite the beginner. It works (to a bare minimum), but I'm not particularly happy with it.
My knowledge in Haskell at this point spans as far as the first two chapters of the fantastic Haskell Wikibook. I've not reached functors, monads, or friends, so if the solutions to my queries lie there then it might be wisest for me to settle until I learn more.
My Queries
Whilst I'm sure there are many problems, my main concerns in the code below regard the manner in which I'm dealing with the numerical arguments to ricochet
, and the fragile nature of IO.
More particularly, regarding ricochet
:
- I wanted the 5 arguments (namely
h
,w
,m
,n
andv
) all to be any kind of integer (not mindingInt
orInteger
, etc., so long as it's a whole number, mathematically speaking), barringv
which can be any number at all (i.e. 1.05). - Whilst need for integers is important (as modulo (and thus the algorithm) wouldn't make sense without them), I also want to be able to perform arbitrary arithmetic, including any kind of division.
- I took me much time and frustration to come up with anything that'd even compile, for what's considerably basic arithmetic, and at the end I still find that function signature and spam
fromIntegral
nasty. Any advice on dealing with this kind of situation in future?
And regarding IO:
- What's there now is the bare minimum to work with the challenge. I'm not happy with it because it's so fragile. If anything at all is wrong with the input, the whole program explodes.
- I'd rather just output a blank line if the input is invalid, but I've no idea how to get that done.
However, I'd of course appreciate any advice at all, even around spacing/indentation!
#!/usr/bin/env stack
-- stack --resolver=lts-8.2 --install-ghc runghc
{-
Ricochet
========
- https://redd.it/5vb1wf
-}
module Main
( Corner(..)
, ricochet
, main
) where
data Corner = UL | UR | LR | LL deriving (Show, Eq)
ricochet :: (Integral a, Fractional b) => a -> a -> a -> a -> b -> (Corner, a, b)
ricochet h w m n v
= (c,b,d / v)
where
-- What's a more idiomatic way of doing this?
h', w', d :: Num a => a
h' = fromIntegral (h - m)
w' = fromIntegral (w - n)
d = fromIntegral $ lcm w' h'
b = (d * (h' + w')) `div` (h' * w') - 2
c =
case (
((d `div` h' - 1) `mod` 2),
((d `div` w' - 1) `mod` 2)
) of
(0,0) -> LR
(0,1) -> LL
(1,0) -> UR
(1,1) -> UL
main :: IO ()
main = interact ( unlines
. map (output . input . words)
. lines
)
where
-- How to make these less fragile and more idiomatic?
input (h:w:m:n:v:[]) = ricochet (read h) (read w) (read m) (read n) (read v)
output (c,b,t) = show c ++ " " ++ show b ++ " " ++ show t
Challenge Summary
Each line input describes a bounding box with dimensions h
and w
, and an inner box with dimensions m
and n
(for height and width respectively).
The inner box starts in the top-right corner (0,0) and is travelling diagonally, towards (1,1). You're told the inner box is travelling at a velocity of v
units per second.
I've interpreted all of the parameters as whole numbers, other than v
, which is a real number.
An example line of input: 2 4 1 2 0.5
.
The inner box can ricochet off of the bounding walls as it travels.
For each line of input, calculate the first corner that the inner box reaches (UR (upper right), LR (lower right) or LL (lower left)), along with the number of times the box ricocheted off of a wall before it reached that corner, and the time taken to get there.