记住旧数据

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(但收效甚微,即使它有效,我也不认为它会有所帮助.)

我觉得有一种更实用的方法我可以尝试,但只是不知道如何.

我不是在寻找一个完整的编码解决方案,只是一种指向更好方向的方法.

有什么想法吗?

Sil*_*olo 5

你想要一个更实用的思考方式,所以我会尝试提供它.你说你是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)

我邀请您解剖一下,如果您有任何问题,请告诉我.函数式编程是一个棘手的思维方式,因为它与其他编程非常不同,但它是一个方便的技术集合在你的工具带中.