So i've got two files with the following content:
File 1:
Tom 965432145
Bill 932121234
File 2:
Steve 923432323
Tom 933232323
and i want to merge them and write the resulting output to a file named 'out.txt'.
i wrote this function to deal with duplicates (when the same name appears more than once, it choses what number goes into the final file).
the function is called choosing:
choosing :: [String] −> Int −> Int −> Int
choosing ("Name_of_person":_) num1 _ = num1
choosing _ num1 num2
| num2 ‘div‘ 100000000 == 2 = num2
| otherwise = num1
Here's my attempt at this:
import System.IO
import Data.Char
choosing :: [String] −> Int −> Int −> Int
choosing name num1 _ = num1
choosing _ num1 num2
| num2 `div` 100000000 == 2 = num2
| otherwise = num1
main :: IO ()
main = do
in1 <- openFile "in1.txt" ReadMode
in2 <- openFile "in2.txt" ReadMode
out <- openFile "out.txt" WriteMode
processData in1 in2 out
hClose in1
hClose in2
hClose out
processData :: Handle -> Handle -> Handle -> IO ()
processData in1 in2 out =
do ineof <- hIsEOF in1
ineof2 <- h2IsEOF in2
if ineof && ineof2
then return ()
else do inpStr <- hGetLine in1
inp2Str <- h2GetLine in2
num1Int <- num1GetNumber in1
num2Int <- num2GetNumber in2
if inpStr = inp2Str
then PutStrLn out (impStr choosing inpStr num1Int num2Int )
else PutStrLn out (inpStr num1Int)
PutStrLn out (inp2Str num2Int)
processData in1 in2 out
However this kinda of makes sense to me, it doesn't compile and after a while trying to debug this i'm now starting to think there's some serious mistakes here, so i would really appreciate your help on this.
Here's my attempt at something simpler first:
import System.IO
import Data.Char
choosing name num1 _ = num1
choosing _ num1 num2
| num2 `div` 100000000 == 2 = num2
| otherwise = num1
main :: IO ()
main = do
in1 <- openFile "in1.rtf" ReadMode
in2 <- openFile "in2.rtf" ReadMode
out <- openFile "out.rtf" WriteMode
mainloop in1 out
mainloop in2 out
hClose in1
hClose in2
hClose out
mainloop :: Handle -> Handle -> IO ()
mainloop _ out =
do ineof <- hIsEOF in
if ineof
then return ()
else do inpStr <- hGetLine in
hPutStrLn out (inpStr)
mainloop in out
but it's not working either...
UPDATED:
So basically i've been trying to solve my problem, with all the tips i got, i managed to do this:
import System.IO
import Data.Char
- Main function to run the program
main = do
entries1 <- fmap parseEntries $ readFile "in1.txt"
entries2 <- fmap parseEntries $ readFile "in2.txt"
writeFile "out.txt" $ serializeEntries $ mergeEntries entries1 entries2
- Function to deal with duplicates
choosing name num1 _ = num1
choosing _ num1 num2
| num2 `div` 100000000 == 2 = num2
| otherwise = num1
- Function to read a line from a file into a tuple
Now i need help making this function 'cover' the whole file, and not just one line of it.
parseLine :: String -> (String, Int)
parseLine xs = (\(n:i:_) -> (n, read i)) (words xs)
- A function that receives entries, merges them into a single string so that it can be writen to a file.
import Data.Char
tupleToString :: (Int, Char) -> [Char]
tupleToString x = (intToDigit.fst) x:(snd x):[]
tuplesToStrings [] = []
tuplesToStrings (x:xs) = tupleToString x : tuplesToStrings xs
tuplesToString xs = (concat . tuplesToStrings) xs
I think the problem is that your thinking too imperative. In Haskell you usually split your solution in small blocks, and each block does only one thing. It's much easier to reason about one small block, and it's also easier to reuse that block in other parts. For example, here's how I would breakdown the code for this problem:
parseEntries :: String -> [(String, Int)]
A function that receives the content of a file and parses the entries. It the case of the content of in1.txt it would return [("Tom", 965432145), ("Bill", 932121234)]
mergeEntries :: [(String, Int)] -> [(String, Int)] -> [(String, Int)]
A function that receives entries from two files and merges them.
serializeEntries :: [(String, Int)] -> String
A function that receives entries, merges them into a single string so that it can be writen to a file.
Having defined these functions, main becomes as simple as this:
main = do
entries1 <- fmap parseEntries $ readFile "in1.txt"
entries2 <- fmap parseEntries $ readFile "in2.txt"
writeFile "out.txt" $ serializeEntries $ mergeEntries entries1 entries2
Answering to your updated code:
Now that you have a function to parse a line, parseEntries is easy. Use the lines function to split the content by lines, then map parseLine to each line.
tuplesToStrings could be writen much simpler as tuplesToStrings = map tupleToString
I don't see how tuplesToString will help you. Its type doesn't match the type returned by parseLine (parseLine returns a list of (String, Int) while tuplesToString expects a list of (Int, Char)). And it doesn't even insert spaces between words or between lines.
Here's a possible implementation for serializeEntries (using Text.Printf module):
serializeEntries entries = concatMap (uncurry $ printf "%s %d\n") entries
Related
I am fairly new to Haskell and am writing some simple text/file manipulation functions. I am currently attempting to modify a string before adding it to a file.
I have a function 'insertChar' which adds a character at any given position in a string (using the solution of problem 21 from 99 problems in Haskell https://wiki.haskell.org/99_questions/21_to_28).
import System.Environment
import System.Directory
import System.IO
import Data.List
main :: IO ()
main = do
putStrLn "Insert a string"
word <- getLine
putStrLn "Insert a char"
char <- getChar
putStrLn "Insert a position"
pos <- getLine *line which is skipped*
let x = (read pos :: Int) *converts string into int*
putStrLn "Adding char to list..."
let newS = [(insertChar char word x)] *result of insertChar is set to
newS*
putStrLn "Printed list: "
print (newS) *print new string*
putStrLn "Insert file name"
file <- getLine
putStrLn "Adding new string to file..."
add file newS
insertChar :: a -> [a] -> Int -> [a]
insertChar x ys 1 = x:ys
insertChar x (y:ys) n = y:insertChar x ys (n-1)
add :: String -> [String] -> IO ()
add fileName [item] = appendFile fileName item
The user is asked to enter a string, then a character they wish to add to this string and finally the position in the string where they wish to add the character. I can input the string fine but when I press enter after inputting the character, the 'getLine' for inputting the position is skipped and the following error is produced;
GHCI>main
Insert a string
hello world
Insert a char
s
Insert a position
Adding char to list...
Printed list:
["*** Exception: Prelude.read: no parse
I have seen this stack overflow post; haskell -skipping getLine
and have attempted to follow that answer changing the code to;
import System.Environment
import System.Directory
import System.IO (hSetBuffering, stdin, BufferMode(NoBuffering)) *New line*
import Data.List
main :: IO ()
main = do
hSetBuffering stdin NoBuffering *New line*
putStrLn "Insert a string"
word <- getLine
putStrLn "Insert a char"
char <- getChar
putStrLn "Insert a position"
pos <- getLine
let x = (read pos :: Int)
putStrLn "Adding char to list..."
let newS = [(insertChar char word x)]
putStrLn "Printed list: "
print (newS)
putStrLn "Insert file name"
file <- getLine
putStrLn "Adding new string to file..."
add file newS
insertChar :: a -> [a] -> Int -> [a]
insertChar x ys 1 = x:ys
insertChar x (y:ys) n = y:insertChar x ys (n-1)
add :: String -> [String] -> IO ()
add fileName [item] = appendFile fileName item
However, it still produces the same error. Any clue what I am doing wrong?
Thanks to Willem Van Onsem's comment on my original question I have been able to find a solution. I added a "getLine" after the line 'putStrLn "Insert a position"' so the code now looks like;
import System.Environment
import System.Directory
import System.IO
import Data.List
main :: IO ()
main = do
putStrLn "Insert a string"
word <- getLine
putStrLn "Insert a char"
char <- getChar
putStrLn "Insert a position"
temp <- getLine *line that has been added*
pos <- getLine
let x = (read pos :: Int)
putStrLn "Adding char to list..."
let newS = [(insertChar char word x)]
putStrLn "Printed list: "
print (newS)
putStrLn "Insert file name"
file <- getLine
putStrLn "Adding new string to file..."
add file newS
insertChar :: a -> [a] -> Int -> [a]
insertChar x ys 1 = x:ys
insertChar x (y:ys) n = y:insertChar x ys (n-1)
add :: String -> [String] -> IO ()
add fileName [item] = appendFile fileName item
You imported the right stuff, but never actually called it! Try this:
main = do
hSetBuffering stdin NoBuffering
-- ...all the rest of your original main, exactly as it used to be
This way, when it gets to the getChar line, it will return from the getChar as soon as you press a key -- not wait until you press enter. The UI will make more sense, and the code will, too, because you don't need to swallow a phantom newline that the user didn't actually want to enter anyway.
I'll get right to it (I removed a bunch of extraneous code, in case this looks a little funny -- trying to make this a MCVE):
import System.IO
import Control.Monad
import Data.IORef
main :: IO ()
main = do
hSetBuffering stdout NoBuffering
input_line <- getLine
let input = words input_line
let x0 = read (input!!0) :: Int
x <- newIORef x0
loop
loop :: IO ()
loop = do
input_line <- getLine
let the_dir = input_line :: String
putStrLn x
loop
Just to test it I tried outputting x and it's saying it's out of scope. Why? How do I fix it so it is in scope? I need to access the variables but I also need to have them initialized before I enter the loop for the first time.
You can make x a parameter.
import System.IO
import Control.Monad
import Data.IORef
main :: IO ()
main = do
hSetBuffering stdout NoBuffering
input_line <- getLine
let input = words input_line
let x0 = read (input!!0) :: Int
x <- newIORef x0
loop x
loop :: Int -> IO ()
loop x = do
input_line <- getLine
let the_dir = input_line :: String
putStrLn $ show x
loop
Or simply use forever.
import Control.Monad
...
x <- newIORef x0
forever $ do
input_line <- getLine
let the_dir = input_line :: String
putStrLn $ show x
I have a file in the form:
3
1 2
3 4
5 7
Where the first line is the number of lines
I know that:
getInt :: IO Int
getInt = readLn
main = do num <- getInt
print (num)
Reads the first line.
Next, I tried:
readInts :: IO [Int]
readInts = fmap (map read.words) getLine
For read a line and get a list: [a,b].
And I tried to use the above in a recursive loop
loop :: Int -> IO()
loop n = if 1 == n then do num <- readInts
print(num)
else loop (n-1)
I'm getting the first line only:
[5,3]
But I need to read the rest of lines, given N
The format of the input file looks a lot like the ones used in programming contests. Here is my standard setup for programming contests like that:
import Control.Monad
import Text.Printf
main :: IO ()
main = do
n <- readLn
forM_ [1 .. n] $ \i -> do
printf "Case %d: " (i :: Int)
solve
An example of solve might be:
solve :: IO ()
solve = do
nums <- map read . words <$> getLine
print (sum nums)
Adding to this helpful answer and comment, for some challenges you'll need to collect n lines and then emit a single result at the end based on an aggregation of the data. An approach for creating a list might use replicateM as follows:
import Control.Monad
toInt :: String -> Int
toInt x = read x :: Int
lineToInts :: String -> [Int]
lineToInts x = map toInt $ words x
main :: IO ()
main = do
n <- readLn
remainingLines <- replicateM n getLine
let list = map lineToInts remainingLines
print list
Sample run:
3
0 1
3 4
6 8
[[0,1],[3,4],[6,8]]
See also Read n lines input with Haskell
Have you looked into the function lines? It takes a string and returns the same string as a list separated by \n. Using this function you don't even have to have the number of lines.
I am a Haskell newbie. I want to read only N characters of a text file into memory. So I wrote this code:
main :: IO()
main = do
inh <- openFile "input.txt" ReadMode
transformedList <- Control.Monad.liftM (take 4) $ transformFileToList inh
putStrLn "transformedList became available"
putStrLn transformedList
hClose inh
transformFileToList :: Handle -> IO [Char]
transformFileToList h = transformFileToListAcc h []
transformFileToListAcc :: Handle -> [Char] -> IO [Char]
transformFileToListAcc h acc = do
readResult <- tryIOError (hGetChar h)
case readResult of
Left e -> if isEOFError e then return acc else ioError e
Right c -> do let acc' = acc ++ [transformChar c]
putStrLn "got char"
unsafeInterleaveIO $ transformFileToListAcc h acc'
My input file several lines, with the first one being "hello world", and when I run this program, I get this output:
got char
transformedList became available
got char
["got char" a bunch of times]
hell
My expectation is that "got char" happens only 4 times. Instead, the entire file is read, one character at a time, and only THEN the first 4 characters are taken.
What am I doing wrong?
I acknowledge I don't understand how unsafeInterLeaveIO works but I suspect the problem here is somehow related to it. Maybe with this example you are trying to understand unsafeInterLeaveIO, but if I were you I'd try to avoid its direct use. Here is how I'd do it in your particular case.
main :: IO ()
main = do
inh <- openFile "input.txt" ReadMode
charList <- replicateM 4 $ hGetChar inh
let transformedList = map transformChar charList
putStrLn "transformedList became available"
putStrLn transformedList
hClose inh
This should just read the first 4 characters of the file.
If you are looking for a truly effectful streaming solution, I'd look into pipes or conduit instead of unsafeInterLeaveIO.
This code reads the number of lines to process from the first line of stdin, then it loops number_of_lines_to_process times doing some calculations and prints the result.
I want it to print the line number in "Line #" after "#" but I don't know how to obtain it
import IO
import Control.Monad (replicateM)
main :: IO ()
main = do
hSetBuffering stdin LineBuffering
s <- getLine
let number_of_lines_to_process = read s :: Integer
lines <- replicateM (fromIntegral(number_of_lines_to_process)) $ do
line <- getLine
let number = read line :: Integer
result = number*2 --example
putStrLn ("Line #"++": "++(show result)) --I want to print the number of the iteration and the result
return ()
I guess that the solution to this problem is really easy, but I'm not familiar with Haskell (coding in it for the first time) and I didn't find any way of doing this. Can anyone help?
You could use forM_ instead of replicateM:
import IO
import Control.Monad
main :: IO ()
main = do
hSetBuffering stdin LineBuffering
s <- getLine
let number_of_lines_to_process = read s :: Integer
forM_ [1..number_of_lines_to_process] (\i -> do
line <- getLine
let number = read line :: Integer
result = number * 2
putStrLn $ "Line #" ++ show i ++ ": " ++ show result)
Note that because you use forM_ (which discards the results of each iteration) you don't need the additional return () at the end - the do block returns the value of the last statement, which in this case is the () which is returned by forM_.
The trick is to first create a list of all the line numbers you want to print, and to then loop through that list, printing each number in turn. So, like this:
import Control.Monad
import System.IO
main :: IO ()
main = do
hSetBuffering stdin LineBuffering
s <- getLine
let lineCount = read s :: Int
-- Create a list of the line numbers
lineNumbers = [1..lineCount]
-- `forM_` is like a "for-loop"; it takes each element in a list and performs
-- an action function that takes the element as a parameter
forM_ lineNumbers $ \ lineNumber -> do
line <- getLine
let number = read line :: Integer
result = number*2 --example
putStrLn $ "Line #" ++ show lineNumber ++ ": " ++ show result
return ()
Read the definition of forM_.
By the way, I wouldn't recommend using the old Haskell98 IO library. Use System.IO instead.
You could calculate the results, enumerate them, and then print them:
import IO
import Control.Monad (replicateM)
-- I'm assuming you start counting from zero
enumerate xs = zip [0..] xs
main :: IO ()
main = do
hSetBuffering stdin LineBuffering
s <- getLine
let number_of_lines_to_process = read s :: Integer
lines <- replicateM (fromIntegral(number_of_lines_to_process)) $ do
line <- getLine
let number = read line :: Integer
result = number*2 --example
return result
mapM_ putStrLn [ "Line "++show i++": "++show l | (i,l) <- enumerate lines ]
I'm still new at Haskell, so there could be problems with the program below (it does work). This program is a tail recursive implementation. The doLine helper function carries around the line number. The processing step is factored into process, which you can change according to the problem you are presented.
import System.IO
import Text.Printf
main = do
hSetBuffering stdin LineBuffering
s <- getLine
let number_of_lines_to_process = read s :: Integer
processLines number_of_lines_to_process
return ()
-- This reads "max" lines from stdin, processing each line and
-- printing the result.
processLines :: Integer -> IO ()
processLines max = doLine 0
where doLine i
| i == max = return ()
| otherwise =
do
line <- getLine
let result = process line
Text.Printf.printf "Line #%d: %d\n" (i + 1) result
doLine (i + 1)
-- Just an example. (This doubles the input.)
process :: [Char] -> Integer
process line = let number = read line :: Integer
in
number * 2
I'm a haskell rookie, so any critiques of the above are welcome.
Just as an alternative, I thought that you might enjoy an answer with minimal monad mucking and no do notation. We zip a lazy list of the user's data with an infinite list of the line number using the enumerate function to give us our desired output.
import System.IO
import Control.Monad (liftM)
--Here's the function that does what you really want with the data
example = (* 2)
--Enumerate takes a function, a line number, and a line of input and returns
--an ennumerated line number of the function performed on the data
enumerate :: (Show a, Show b, Read a) => (a->b) -> Integer -> String -> String
enumerate f i x = "Line #" ++
show i ++
": " ++
(show . f . read $ x) -- show . f . read handles our string conversion
-- Runover takes a list of lines and runs
-- an enumerated version of the sample over those lines.
-- The first line is the number of lines to process.
runOver :: [String] -> [String]
runOver (line:lines) = take (read line) $ --We only want to process the number of lines given in the first line
zipWith (enumerate example) [1..] lines -- run the enumerated example
-- over the list of numbers and the list of lines
-- In our main, we'll use liftM to lift our functions into the IO Monad
main = liftM (runOver . lines) getContents