我正在尝试接收数据类型列表(Polygon),对每个返回(Double,Double)的多边形(calcPerim)执行操作。然后我将其转换为字符串(lengthsToString)。我想有一个包含所有这些字符串的列表。
import System.IO
import Data.List
import Text.Printf
type Point = (Double, Double)
type Segment = (Point, Point)
type Length = Double
data Polygon = Polygon { vertices :: Int
, yline :: Double
, points :: [Point]
} deriving (Show)
main = do
let list = []
handle <- openFile "polycake.in" ReadMode
contents <- hGetContents handle
let singlewords = words contents
list = fileToList singlewords
n = list!!0
list' = drop 1 list
polygons = polyList n list'
output = outList polygons n
print (output)
hClose handle
outList :: [Polygon] -> Int -> [String]
outList polygons i =
if i < length polygons
then
let polygon = polygons!!i
perim = calcPerim (yline polygon) (points polygon)
perim' = fromJust perim
lenString = lengthsToString perim'
nextString = outList polygons (i+1)
in (lenString:nextString)
else []
show3Decimals :: Double -> String
show3Decimals x = printf "%.3f" x
lengthsToString :: (Double, Double) -> String
lengthsToString (min, max) = show3Decimals min ++ " " ++ show3Decimals max
maxLength :: (Length, Length) -> Length
maxLength (a, b) =
if a > b
then a
else b
minLength :: (Length, Length) -> Length
minLength (a, b) =
if a < b
then a
else b
--unwraps value from Just
fromJust :: Maybe a -> a
fromJust Nothing = error "Maybe.fromJust: Nothing"
fromJust (Just x) = x
--function to convert file to a list of Ints
--In: list of strings
--Out: list of Ints
fileToList :: [String] -> [Int]
fileToList = map read
--function to create a list of Polygon data types
--In: # of test cases, list containing test case data
--Out: list of Polygons
polyList :: Int -> [Int] -> [Polygon]
polyList n [] = []
polyList _ [x] = error "Too few points remaining"
polyList n (v:y:list') =
let pointList = take (2*v) list' -- Note: list' may not *have* 2*v points
points = getPoints pointList
list'' = drop (2*v) list'
poly = Polygon { vertices = v, yline = fromIntegral y, points = points}
nextPoly = polyList (n-1) list''
in (poly:nextPoly)
--function to convert a list of ints, into a list of tuples containing 2 Doubles
--In: list of ints
--Out: list of Points
getPoints :: [Int] -> [Point]
getPoints [] = []
getPoints (k:v:t) = (fromIntegral k, fromIntegral v) : getPoints t
-- Check whether a segment is over, under or on the line given by y
segmentCompare :: Double -> Segment -> Ordering
segmentCompare y (p,q) =
case () of
_ | all (`isUnder` y) [p,q] -> LT
_ | all (`isOver` y) [p,q] -> GT
_ -> EQ
-- Partition a list into (lt, eq, gt) based on f
partition3 :: (Segment -> Ordering) -> [Segment] -> ([Segment], [Segment], [Segment])
partition3 f = p' ([], [], [])
where
p' (lt, eq, gt) (x:xs) =
case f x of
LT -> p' (x:lt, eq, gt) xs
EQ -> p' (lt, x:eq, gt) xs
GT -> p' (lt, eq, x:gt) xs
p' result [] = result
-- Split a crossing segment into an under part and over part, and return middle
divvy :: Double -> Segment -> (Segment, Segment, Point)
divvy y (start, end) =
if start `isUnder` y
then ((start, middle), (middle, end), middle)
else ((middle, end), (start, middle), middle)
where
middle = intersectPoint y (start, end)
-- Split a polygon in two, or Nothing if it's not convex enough
splitPolygon :: Double -> [Point] -> Maybe ([Segment], [Segment])
splitPolygon y list = do
let (under, crossing, over) = partition3 (segmentCompare y) pairs
case crossing of
-- No lines cross. Simple.
[] -> return (under, over)
-- Two segments cross. Divide them up.
[(p1,p2),(q1,q2)] ->
let (u1, o1, mid1) = divvy y (p1,p2)
(u2, o2, mid2) = divvy y (q1, q2)
split = (mid1, mid2) :: Segment
in return (split:u1:u2:under, split:o1:o2:over)
-- More segments cross. Algorithm doesn't work.
rest -> fail "Can't split polygons concave at y"
where
pairs = zip list (drop 1 $ cycle list) :: [Segment]
--
calcPerim :: Double -> [Point] -> Maybe (Length, Length)
calcPerim y list = do
(under, over) <- (splitPolygon y list :: Maybe ([Segment], [Segment]))
return (sumSegments under, sumSegments over)
-- Self explanatory helpers
distance :: Segment -> Length
distance ((ax, ay), (bx, by)) = sqrt $ (bx-ax)^2 + (by-ay)^2
intersectPoint :: Double -> Segment -> Point
intersectPoint y ((px, py), (qx, qy)) =
let t = (y-py)/(qy-py)
x = px + t * (qx - px)
in
(x,y)
sumSegments :: [Segment] -> Length
sumSegments = sum . map distance
isUnder :: Point -> Double -> Bool
isUnder (_, py) y = py < y
isOver (_, py) y = py > y
一些示例输入:
3
4 2
0 0
4 0
4 4
0 4
6 10
3 15
10 1
12 5
11 19
9 23
6 20
3 5
0 0
10 0
0 10
示例输出:
12.000 12.000
25.690 35.302
17.071 27.071
主要问题在于outList函数,因为我已经测试了其他所有内容,现在我只想将输出放到列表中,这样我就可以以非常确定的方式将其打印出来。因为我需要通过做一个&#34; diff&#34;来传递测试用例。在我的程序输出和给定的程序之间。
答案 0 :(得分:1)
首先,您当前的方法不是编写Haskell的最佳方法。搞砸了很容易。让我们尝试备份一下。
你说你想要“对每个多边形执行操作”。这就是map
函数的用途。
看一下类型签名:
map :: (a -> b) -> [a] -> [b]
在您的情况下,a
将是Polygon
,b
将是String
。因此map函数将获取一个函数和一个多边形列表,并返回一个字符串列表,这就是你想要的。
但是,您需要使用一些函数组合以及一个lambda来创建一个带Polygon
并输出String
的函数。
以下是您可以做的事情的一个可能示例:
outList :: [Polygon] -> [String]
outList polygons = map (\poly -> lengthsToString . fromJust $
calcPerim (yline poly) (points poly)) polygons
通常,您应该尽量避免在Haskell中进行显式递归,而是使用Prelude的函数来完成您所需要的操作。它还提供了比您手动尝试更清晰,更清晰,更短的代码。