Reputation: 21
I have just recently started learning Haskell,and what I am trying to do is this:
I have a txt file like this
2000
booka 500
bookb 1000
bookc 250
bookd 250
and I want my output like this
booka 25%
bookb 50%
bookc 12.5%
bookd 12.5%
I can read the conten from the file with readFile but I dont know how to treat the content after
this is what I have
-- Imports needed
import System.IO
import Data.List
main = do
putStrLn "Please insert the name of the file you want to process:"
file <- getLine
read_File file
read_File file = do
h <- openFile file ReadMode
content <- readFile file
Upvotes: 2
Views: 1130
Reputation: 8050
One approach is to break down the problem into three obvious parts:
Indeed, almost all data-processing problems can be broken down like this.
Parsing the input materialises into reading the file and then breaking the contents up into a list of lines. The first of these lines contains a total value, the remaining lines form a table of rows consisting of a label and a value:
parseFile :: FilePath -> IO (Float, [(String, Float)])
parseFile fp = do
contents <- readFile fp
let (s : ss) = lines contents
return (read s, parseTable ss)
The table is parsed by means of an auxiliary function that breaks each line up into a list of two words: one for the label, one for the value.
parseTable :: [String] -> [(String, Float)]
parseTable [] = []
parseTable ("" : ss) = parseTable ss
parseTable (s : ss) = let [s1, s2] = words s
in (s1, read s2) : parseTable ss
Processing the input is straightforward:
processTable :: Float -> [(String, Float)] -> [(String, Float)]
processTable sum table = [(label, (part / sum) * 100) | (label, part) <- table]
The second component of each row in the table is divided by the total value and then multiplied by hundred to yield a percentage.
A textual rendering of single row consisting, now, of a label and a percentage is easily obtained:
showRow :: (String, Float) -> String
showRow (label, perc) = label ++ " " ++ show perc ++ "%"
Putting it all together, a whole file is then processed by (1) parsing it to obtain a total value and a table, (2) processing the table to obtain a processed table, and (3) printing the processed table row by row:
processFile :: FilePath -> IO ()
processFile fp = do
(sum, table) <- parseFile fp
mapM_ (putStrLn . showRow) (processTable sum table)
For your example data, this generates almost the output as you specified it:
booka 25.0%
bookb 50.0%
bookc 12.5%
bookd 12.5%
That is, getting rid of trailing ".0"
s in printed floating-point numbers is left as an exercise. ;)
Upvotes: 1
Reputation: 76240
To "store" the lines of the file you can just call:
fileContent <- readFile file
let fileLines = lines fileContent
from there you can use words
for every line and it will return a list of [label, amount]. You can read the amount
with read
to place it back into some integral or fractional form.
Upvotes: 0
Reputation: 54058
First of all, you should be opening the file with openFile
, then reading the file with readFile
. You only need the latter, it is a handy function that opens the file, reads it, returns the contents, then closes the file safely for you.
What you should be aiming to write is a pure function
processContents :: String -> Maybe (Float, [(String, Float)])
that can get the first number from the file and then each successive line. Since we want to ensure our code is robust, we should also handle failures in some way. For this problem, I think using Maybe
is sufficient to handle errors in parsing. For this, we'll add a couple imports:
import Text.Read (readMaybe)
import Data.Maybe
processContents :: String -> Maybe (Float, [(String, Float)])
processContents c = do
let ls = lines c
-- If we can't get a single line, the whole operation fails
firstLine <- listToMaybe ls
-- If we can't parse the total, the whole operation fails
total <- readMaybe firstLine
let rest = drop 1 ls
return (total, catMaybes $ map parseLine $ rest)
The listToMaybe
function acts as a "safe head", it is defined as
listToMaybe (x:xs) = Just x
listToMaybe [] = Nothing
The readMaybe
function has the type Read a => String -> Maybe a
, and returns Nothing
if it couldn't parse the value, making it easy to use in this case. The catMaybes
function takes a list of Maybe a
s, extracts all the ones that aren't Nothing
, and returns the rest as a list with the type catMaybes :: [Maybe a] -> [a]
.
Now we just need to implement parseLine
:
parseLine :: String -> Maybe (String, Float)
parseLine line = case words line of
(book:costStr:_) -> do
cost <- readMaybe costStr
return (book, cost)
_ -> Nothing
This breaks the line up by spaces, grabs the first two elements, and parses them into a name and a number, if possible.
Now you have a function that could parse your example file into the value
Just (2000.0, [("booka", 500.0), ("bookb", 1000.0), ("bookc", 250.0), ("bookd", 250.0)])
Now it's just up to you to figure out how calculate the percentages and print it to the screen in your desired format.
Upvotes: 1