尝试从Haskell中的数据类型列表创建字符串列表

时间:2018-03-17 00:55:29

标签: list haskell

我正在尝试接收数据类型列表(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;来传递测试用例。在我的程序输出和给定的程序之间。

1 个答案:

答案 0 :(得分:1)

首先,您当前的方法不是编写Haskell的最佳方法。搞砸了很容易。让我们尝试备份一下。 你说你想要“对每个多边形执行操作”。这就是map函数的用途。

看一下类型签名:

map :: (a -> b) -> [a] -> [b]

在您的情况下,a将是Polygonb将是String。因此map函数将获取一个函数和一个多边形列表,并返回一个字符串列表,这就是你想要的。

但是,您需要使用一些函数组合以及一个lambda来创建一个带Polygon并输出String的函数。 以下是您可以做的事情的一个可能示例:

outList :: [Polygon] -> [String]
outList polygons = map (\poly -> lengthsToString . fromJust $
                                 calcPerim (yline poly) (points poly)) polygons

通常,您应该尽量避免在Haskell中进行显式递归,而是使用Prelude的函数来完成您所需要的操作。它还提供了比您手动尝试更清晰,更清晰,更短的代码。