Reputation: 318
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.
Upvotes: 0
Views: 663
Reputation: 377
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.
Upvotes: 1