简体   繁体   中英

Trying to create list of strings from a list of data types in Haskell

I am trying to take in a list of data types (Polygon), perform an operation on each polygon (calcPerim) which returns a (Double, Double). In which i then convert into a string (lengthsToString). I would like to have a list with all of these said strings.

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

Some sample input:

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

Sample output:

12.000 12.000
25.690 35.302
17.071 27.071

The main issue is in the outList function, as I have tested everything else, now I would just like to get the output into a list so I can print it out in a very certain way. As I need to pass the test cases by doing a "diff" between the output of my program and one given.

First of all, your current approach is not the best way to write Haskell. It's quite easy to mess something up. Let's try backing up a bit. You said you want "perform an operation on each polygon". This is what the map function is for.

Take a look at the type signature:

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

In your case, a will be a Polygon , and b will be a String . So the map function would take a function, and a list of Polygons, and return a list of strings, which is what you want.

However, you'll need to use some function composition, as well as a lambda, to create a single function that takes a Polygon and outputs a String . Here is a possible example of what you could do:

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

In general, you should try to avoid explicit recursion in Haskell, and rather use the Prelude's functions that will do what you need for you. It also gives cleaner, more clear, and shorter code than if you tried to do it manually.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM