Kev*_*vin 2 state haskell loops
记住你的第一次(在Haskell循环中)
我试图通过一些Hackerrank问题来教自己一点Haskell.
我正在研究的问题涉及读取坐标集(x1,y1)和(x2,y2)以及确定由这些坐标绘制的多边形的周长.
到目前为止,这是我的代码:
-- Calculate length of line given points x1,y2, x2,y2
calc_length:: Int -> Int -> Int -> Int -> Float
calc_length x1 y1 x2 y2 =
sqrt ( fromIntegral (height ^2 + width ^2) )
where height = abs( y2 - y1)
width = abs( x2 - x1)
main = do
x <- readLn :: IO Double
forM_ [1,2..(x / 2)] $ \lc -> do
line1 <- getLine
let wds1 = map (\str -> read str::Int) (words $ line1)
line2 <- getLine
let wds2 = map (\str -> read str::Int) (words $ line2)
print ( wds1, wds2)
Run Code Online (Sandbox Code Playgroud)
我遇到的问题是我需要计算第一个和最后一个坐标之间的距离,即记住输入的第一对数字(存储在第1行中).但经过多次迭代后,第一对将会丢失.我已经尝试使用全局变量来存储第一次调用getLine(但收效甚微,即使它有效,我也不认为它会有所帮助.)
我觉得有一种更实用的方法我可以尝试,但只是不知道如何.
我不是在寻找一个完整的编码解决方案,只是一种指向更好方向的方法.
有什么想法吗?
你想要一个更实用的思考方式,所以我会尝试提供它.你说你是Haskell的新手,所以如果这涉及到你还没有探索过的东西,我会事先道歉.随意请求澄清任何部分.
首先,让我们再分段你的calcLength功能.我们传递了两点,所以不要传递四个参数,让我们只传两个.
data Point a = Point a a
calcLength :: Floating a => Point a -> Point a -> a
calcLength (Point x1 y1) (Point x2 y2) = sqrt (height ^ 2 + width ^ 2)
where height = abs (y2 - y1)
width = abs (x2 - x1)
Run Code Online (Sandbox Code Playgroud)
现在让我们编写一个读取单个点的函数.我们称之为main而不是分别读取两个数值main.
readPoint :: (Floating a, Read a) => IO (Point a)
readPoint = Point <$> readLn <*> readLn
Run Code Online (Sandbox Code Playgroud)
我在这里使用了应用语法.如果你对do-notation比较熟悉,那就相当于
readPoint :: (Floating a, Read a) => IO (Point a)
readPoint = do
x <- readLn
y <- readLn
return $ Point x y
Run Code Online (Sandbox Code Playgroud)
现在问你的问题.我们想要列出一些事物(在你的情况下为点)并产生相邻的对,确保循环到开头.让我们暂时不再考虑点,只需编写一个适用于任何事物列表的函数.
-- We're going to take a list of things and produce a list of pairs of those things
loopedPairs :: [a] -> [(a, a)]
-- If the original list is empty, return the empty list
loopedPairs [] = []
-- Otherwise, start recursing
loopedPairs (x:xs) = go x xs
-- Here, we're pairing off all the elements
where go x' (y:ys) = (x', y) : go y ys
-- Because we defined this as an inner function, we can still access
-- the original first element, effectively "remembering" it like you
-- were asking about. Note that we never use any "global" storage or
-- mutable state to do this, just a bit of variable scope.
go x' [] = [(x', x)]
Run Code Online (Sandbox Code Playgroud)
现在我们将编写一个周边函数.将尽可能多的"纯"非IO逻辑与IO工作分开是很好的,因此我们希望将其考虑在内main.
newtype Polygon a = Polygon [Point a]
perimeter :: Floating a => Polygon a -> a
perimeter (Polygon xs) = sum . map (\(a, b) -> calcLength a b) $ loopedPairs xs
Run Code Online (Sandbox Code Playgroud)
我们采用一个多边形,它实际上只是一个点列表,使用所有点对loopedPairs,然后计算每个点之间的长度并对结果求和.
考虑到这一点,main相当短暂.
main :: IO ()
main = do
n <- readLn :: IO Int
points <- replicateM n (readPoint :: IO (Point Double))
let result = perimeter (Polygon points)
print result
Run Code Online (Sandbox Code Playgroud)
我们读取点数,然后我们读取每个点(replicateM基本上意味着"做这个事情n并将结果累积到一个列表中."然后我们计算周长并将其打印出来.
可运行的解决方案:
import Control.Monad
data Point a = Point a a
newtype Polygon a = Polygon [Point a]
calcLength :: Floating a => Point a -> Point a -> a
calcLength (Point x1 y1) (Point x2 y2) = sqrt (height ^ 2 + width ^ 2)
where height = abs (y2 - y1)
width = abs (x2 - x1)
readPoint :: (Floating a, Read a) => IO (Point a)
readPoint = Point <$> readLn <*> readLn
loopedPairs :: [a] -> [(a, a)]
loopedPairs [] = []
loopedPairs (x:xs) = go x xs
where go x' (y:ys) = (x', y) : go y ys
go x' [] = [(x', x)]
perimeter :: Floating a => Polygon a -> a
perimeter (Polygon xs) = sum . map (\(a, b) -> calcLength a b) $ loopedPairs xs
main :: IO ()
main = do
n <- readLn :: IO Int
points <- replicateM n (readPoint :: IO (Point Double))
let result = perimeter (Polygon points)
print result
Run Code Online (Sandbox Code Playgroud)
我邀请您解剖一下,如果您有任何问题,请告诉我.函数式编程是一个棘手的思维方式,因为它与其他编程非常不同,但它是一个方便的技术集合在你的工具带中.