Reputation: 27
I have a file (points.txt) with some Cartesian coordinates defined:
A 1.0 2.2
B 2.1 3.0
C 3.5 4.0
D 4.0 5.0
I have a second file (routes.txt) with routes defined based on the points from points.txt.
route1 ACDB
route2 ABC
I need to find the length of each route. So far I have a calculation of the distance between two points, like this:
type Point = (String, Float, Float)
distance_points :: IO ()
distance_points = do s <- readFile "pontos.txt"
putStr "Fom: "
p1 <- getLine
putStr "To: "
p2 <- getLine
print ( distance (search_point p1 (map words (lines s))) (search_point p2 (map words (lines s))))
search_point :: String -> [[String]] -> Point
search_point pt ([p,c1,c2]:xs) = if pt == p then (p, read(c1)::Float, read(c2)::Float)
else search_point pt xs
distance :: Point -> Point -> Float
distance (s1,x1,y1) (s2,x2,y2) = sqrt ((x1-x2)^2 + (y1-y2)^2)
How can I calculate the full distance of a route?
Also, if I have several routes how can I find the longest one?
Thanks in advance.
Upvotes: 2
Views: 237
Reputation: 32455
The easiest way to do this is to solve your problem in pure code first, then add the file-reading afterwards. Otherwise you'll be tempted to write a lot of IO code.
It'll be easier if you separate the name from the coordinates:
type Coordinates = (Float,Float)
type Name = Char -- You had String, which is also fine
type Point = (Name, Coordinates)
type Points = [Point] -- or Map String Point from Data.Map
Then have some practice data:
sampleData :: Points
sampleData = [('A',(1.0,2.2), .....
First you'll need a function that takes a name and gives you some coordinates.
coordinates :: Points -> Name -> Coordinates
If we're using [Point]
, the easiest way to do this is to use lookup
. (You can find out about functions on hoogle like this or by type like this, although there's no obvious way for you to know that you wanted a Maybe
, and when you just search for [(a,b)] -> b
, lookup is a long way down.)
Comment if you need help with this step.
Using that you'll be able to write
distBetween :: Points -> Name -> Name -> Float
getPath :: String -> [(Name,Name)]
or (cooler) use zipWith
to get to the distances. After that, applying sum
should be easy to finish the problem.
The cool way of making this list of pairs is to use a trick we use for the fibonacci numbers (fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
) of zipping a function with its tail. If you've not met it, zip works like this:
ghci> zip [1..5] "Hello Mum"
[(1,'H'),(2,'e'),(3,'l'),(4,'l'),(5,'o')]
zip "Hello" "ello"
[('H','e'),('e','l'),('l','l'),('l','o')]
*Main> zip "Hello" (tail "Hello")
[('H','e'),('e','l'),('l','l'),('l','o')]
Awesome - that's exactly the trick you need.
You'll need functions like
readPointsFile :: FilePath -> IO Points
readPointsFile fileName = do
....
....
return (map readPoint pointStrings)
and then you can glue it together with, for example:
pathLengthFile :: FilePath -> FilePath -> IO Float
pathLengthFile pointsFilename pathFilename = do
points <- readPointsFile pointsFilename
path <- readPathFile pathFilename
return (getPathLength points path)
Notice how hardly any of the logic is in this bit. You do all the real graft in pure code.
Secretly I'm a massive Applicative
fan, and want to import Control.Applicative
and write this as
pathLengthFile pointsFile pathFile =
getPathLength <$> readPointsFile pointsFile <*> readPathFile pathFile
But that's another lesson for a later day. :)
Upvotes: 8