记住旧数据

时间:2018-06-30 20:13:26

标签: loops haskell state

记住您的第一次(在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)

我的问题是我需要计算第一个和最后一个坐标之间的距离,即记住输入的第一对数字(存储在第1行中)。但是在多次迭代之后,第一对将丢失。我尝试使用全局变量来存储getLine的第一个调用(尽管很少成功,即使它行得通,但我认为这不会有所帮助。)

我觉得我可以尝试一种更实用的方法,但不知道怎么做。

我不是在寻找完整的编码解决方案,而只是将其指向更好的方向。

有什么想法吗?

3 个答案:

答案 0 :(得分:5)

您要求对此进行更实用的思考,因此我将尝试提供这一点。您说您是Haskell的新手,所以如果这涉及您尚未探索的事情,我谨向您道歉。随时要求对其进行任何澄清。

首先,让我们进一步细分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) 函数。我们将其传递两点,因此,我们仅传递两个参数,而不是传递四个参数。

main

现在让我们编写一个读取单个点的函数。我们将从main中调用它,而不是在readPoint :: (Floating a, Read a) => IO (Point a) readPoint = Point <$> readLn <*> readLn 中分别读取两个数值。

readPoint :: (Floating a, Read a) => IO (Point a)
readPoint = do
  x <- readLn
  y <- readLn
  return $ Point x y

我在这里使用了应用语法。如果您对do-notation更为熟悉,则等同于

-- 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)]

现在开始提问。我们要列出事物(您的情况下的要点)并产生相邻的对,并确保从头开始。让我们暂时不再以要点的方式来思考它,而只是编写一个对任何事物列表都适用的函数。

main

现在,我们将编写一个周边函数。最好从IO工作中分离出尽可能多的“纯”非IO逻辑,因此我们希望将此排除在newtype Polygon a = Polygon [Point a] perimeter :: Floating a => Polygon a -> a perimeter (Polygon xs) = sum . map (\(a, b) -> calcLength a b) $ loopedPairs xs 之外。

loopedPairs

我们采用一个多边形,它实际上只是点的列表,使用main配对所有点,然后计算它们之间的长度并求和。

请牢记,main :: IO () main = do n <- readLn :: IO Int points <- replicateM n (readPoint :: IO (Point Double)) let result = perimeter (Polygon points) print result 很短。

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 次执行此操作并将结果累加到列表中)。然后我们计算周长并打印出来。

可运行的解决方案:

ProcessBuilder builder = new ProcessBuilder();
builder.command("sh", "printenv";
Process process = builder.start();

我邀请您进行剖析,如果您有任何疑问,请告诉我。函数式编程是一种棘手的思维方式,因为它与其他编程有很大的不同,但是它是工具带中的一组便捷技术。

答案 1 :(得分:3)

如果您需要区别对待第一个迭代,则应将其分开(如果我正确理解了您的问题)。您可以使用辅助函数来减少重复:

getNumLine :: IO [Int]
getNumLine = do
    line <- getLine
    return (map read (words line))

main = do
    x <- readLn :: IO Int   -- Double seemed wrong, use integer `div` below instead
    firstline <- getNumLine
    forM_ [2..x `div` 2] $ \lc -> do
        ...

是的,您可能对此更具“功能性”,但我认为最好是循序渐进地学习。

答案 2 :(得分:0)

在所有人的帮助之后:我的最终解决方案:

import Data.List
import Data.Foldable
import Data.Traversable
import Data.List.Split

          -- 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)


        -- Calculate the distances between vertex points (except the last)
        getResults list = 
            sum [ calc_length (head f)  (last f) (head s)  (last s)   |  (f,s)  <-  (zip list (tail list)) ]  


       -- Calculate the last vertex distance between points
       headAndTail list =
           calc_length (z!!0) (z!!1) (z!!2) (z!!3)
           where z  = head list ++ last list 

        -- Prompt the user for co-ordinate pairs
        main = do
                x <- readLn :: IO Double
                result <- forM [1,2..x ] ( \lc -> do
                   line1 <- getLine
                   let wds1 = map (\str -> read str::Int) (words $ line1)
                   return wds1)

                print (  (getResults result) + (headAndTail result)   )