记住您的第一次(在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的第一个调用(尽管很少成功,即使它行得通,但我认为这不会有所帮助。)
我觉得我可以尝试一种更实用的方法,但不知道怎么做。
我不是在寻找完整的编码解决方案,而只是将其指向更好的方向。
有什么想法吗?
答案 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) )