ysakamoto
ysakamoto

Reputation: 2532

Write a list comprehension to a file

I generated a list of tupules using some function and list comprehension, like this

[(x,y, func (x,y)) | x<-[xmin,(xmin+dx)..xmax], y<-[ymin,(ymin+dy)..ymax]]

So I pretty much created a list of 3D coordinates of many points.

Now my question is, how do I efficiently write this list in a file? I need it to be something like,

x0 y0 z0
x1 y1 z1
x2 y2 z2
....

Upvotes: 1

Views: 426

Answers (1)

bheklilr
bheklilr

Reputation: 54068

To write to a file, you could do something like:

main = do
    h <- openFile "output.txt" WriteMode
    hPutStrLn h "Hello, file system"
    hClose h

A safer way might be to use writeFile :: FilePath -> String -> IO (), which would close the handle if an error occurred, but you have to generate the entire contents first:

main = let contents = "Hello, " ++ "file" ++ " system"
       in writeFile "output.txt" contents

I'll let you decide which you want to use. I would recommend the writeFile method because of its safety.

Next, I would look at Data.List.intercalate. If you're coming from an imperative language like Python, you might be familiar with the string join method. In Python:

with open('output.txt', 'w') as f:
    f.write(' '.join(['Hello,', 'file', 'system']))

this would write the string Hello, file system to the file output.txt. The intercalate function is very similar in Haskell:

main = writeFile "output.txt" $ intercalate " " ["Hello,", "file", "system"]
--         This is the string to join by ----^               ^
--         These are the strings being joined together ------|

Now all you need to do is figure out how to turn your data into strings in order to write it out to a file. I would recommend writing a function

showTriple :: Show a => String -> (a, a, a) -> String
showTriple sep (x, y, z) = ???

which turns a triple into a sep delimited string. That way you could easily swap that space out for a tab, comma, or whatever other symbol you might want to use.

If you get stuck, just edit your question with a progress update, showing your code and comment telling me what's got you stuck.


Now that you've solved the problem yourself, here's how I would do it using these functions:

-- This is just your list of data from above
range :: ((Double, Double) -> Double) -> (Double, Double, Double) -> (Double, Double, Double) -> [(Double, Double, Double)]
range func xs ys = [(x, y, func (x, y)) | x <- interval xs, y <- interval ys]
    where
        interval (tmin, tmax, dt)
            | tmin < tmax = [tmin, tmin+dt .. tmax] 
            | otherwise   = interval (tmax, tmin, dt)

writeDelimitedFile :: FilePath -> String -> [(Double, Double, Double)] -> IO ()
writeDelimitedFile file sep values = writeFile file $ delimitedString sep values

delimitedString :: String -> [(Double, Double, Double)] -> String
delimitedString sep values = intercalate "\n" $ map (showTriple sep) values

showTriple :: Show a => String -> (a, a, a) -> String
showTriple sep (x, y, z) = intercalate sep $ map show [x, y, z]

main :: IO ()
main = writeDelimitedFile "output.txt" " " $ range (uncurry (+)) (0, 10, 0.1) (0, 5, 0.1)

Of course, you could shorten it quite a bit without defining separate functions to do all the work and using unwords and unlines instead of intercalate:

main :: IO ()
main = writeFile "output.txt" $ unlines
        [unwords $ map show [x, y, z] |
            (x, y, z) <- range (uncurry (+)) (0, 10, 0.1) (0, 5, 0.1)]

But this makes it more difficult to change later. And as you can see, if we want to keep it from marching off the side of the screen it has to be broken into multiple lines anyway.

Upvotes: 3

Related Questions