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.
Related
I have a function which contains a list. I want just to write that list content in a file from main after user input.
putTodo :: (Int, String) -> IO ()
putTodo (n, todo) = putStrLn (show n ++ ": " ++ todo)
prompt :: [String] -> IO ()
prompt todos = do
putStrLn "The list contains:"
mapM_ putTodo (zip [0..] todos)
putStrLn " "
command <- getLine
getCommand command todos
What I tried:
main = do
outh <- openFile "agenda.txt" WriteMode;
hPutStrLn outh prompt[]
-- hPutStrLn outh (show prompt[])
-- hPrint (show prompt[])
hClose outh;
Thank you.
Your code contains a couple of errors / problems:
prompt[] isn't valid (in main) - this should be prompt
hPutStrLn expects a String as its second argument, but you provide IO()
getCommand is not defined
What you need is:
a list of todos (possibly returned by a function)
a function that converts this list of todos to a string
hPutStrLn to print this string to the output file
Here's a simple version with a hard-coded list of todos (my Haskell isn't very advanced, so this could probably be done in a much more elegant way):
import System.IO
type Todo = (Int, String)
todoToString :: Todo -> String
todoToString (idx, name) = (show idx) ++ " : " ++ name
todosToString :: [Todo] -> String
todosToString todos = foldl (\acc t -> acc ++ "\n" ++ (todoToString t)) "" todos
allTodos :: [Todo]
allTodos = [(1, "Buy milk"), (2, "Program Haskell")]
main = do
outh <- openFile "agenda.txt" WriteMode;
hPutStrLn outh (todosToString allTodos);
hClose outh;
At the moment, I have this code in and around main:
import Control.Monad
import Control.Applicative
binSearch :: Ord a => [a] -> a -> Maybe Int
main = do
xs <- lines <$> readFile "Cars1.txt"
x <- getLine <* putStr "Registration: " -- Right?
putStrLn $ case binSearch xs x of
Just n -> "Found at position " ++ show n
Nothing -> "Not found"
My hope is for “Registration: ” to be printed, then for the program to wait for the input to x. Does what I've written imply that that will be the case? Do I need the <*, or will putting the putStr expression on the line above make things work as well?
PS: I know I have to convert binSearch to work with arrays rather than lists (otherwise it's probably not worth doing a binary search), but that's a problem for another day.
The line
x <- getLine <* putStr "Registration: "
orders the IO actions left-to-right: first a line is taken as input, then the message is printed, and finally variable x is bound to the result of getLine.
Do I need the <*, or will putting the putStr expression on the line
above make things work as well?
If you want the message to precede the input, you have to put the putStr on the line above, as follows:
main :: IO ()
main = do
xs <- lines <$> readFile "Cars1.txt"
putStr "Registration: "
x <- getLine
putStrLn $ case binSearch xs x of
Just n -> "Found at position " ++ show n
Nothing -> "Not found"
Alternatively,
x <- putStr "Registration: " *> getLine
or
x <- putStr "Registration: " >> getLine
would work, but they are less readable.
Finally, since you added the lazy-evaluation tag, let me add that your question is actually not about laziness, but about how the operator <* is defined, and in particular about the order in which it sequences the IO actions.
I am a new one for studying Haskell, and I want to write a small phone book program. I put the contact list in a file called phone.txt
which like:
("xie","123")
("bob","234")
And my program is :
import System.IO
main = do
handle <- openFile "phone.txt" ReadMode
contents <- hGetContents handle
name<-getLine
putStrLn $ [findNumber name contents]
findNumber x [] = "not found"
findNumber x ((y,z):ys) = if x==y then z else findNumber x ys
This program is for looking the number by input a name, why can not I compile it?
What's wrong here?
You were nearly there... here's my solution (not optimized for performance):
import System.IO
main = do
handle <- openFile "phone.txt" ReadMode
contents <- hGetContents handle
let phonebook = map readEntry $ lines contents
name<-getLine
putStrLn $ findNumber name phonebook
where readEntry line = read line :: (String,String)
findNumber x [] = "not found"
findNumber x ((y,z):ys) = if x==y then z else findNumber x ys
Have fun further with Haskell :)
findNumber has the following type:
findNumber :: Eq a => a -> [(a, [Char])] -> [Char]
However, your second argument in findNumber name contents has type String, not [(a, [Char]). You need to interpret the String you got from hGetContents correctly:
toDict :: String -> [(String, String)]
toDict = map read . lines
Afterwards you can search in toDict contents:
putStrLn . findNumber name $ toDict contents
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
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