How to read line by line from a file in Haskell - haskell

im trying to make a programm that should read line by line from a file and check if its a palindrom, if it is, then print.
I'm really new to haskell so the only thing i could do is just print out each line, with this code :
main :: IO()
main = do
filecontent <- readFile "palindrom.txt"
mapM_ putStrLn (lines filecontent)
isPalindrom w = w==reverse w
The thing is, i dont know how to go line by line and check if the line is a palindrom ( note that in my file, each line contains only one word). Thanks for any help.

Here is one suggested approach
main :: IO()
main = do
filecontent <- readFile "palindrom.txt"
putStrLn (unlines $ filter isPalindrome $ lines filecontent)
isPalindrome w = w==reverse w
The part in parens is pure code, it has type String->String. It is generally a good idea to isolate pure code as much as possible, because that code tends to be the easiest to reason about, and often is more easily reusable.
You can think of data as flowing from right to left in that section, broken apart by the ($) operators. First you split the content into separate lines, then filter only the palindromes, finally rebuild the full output as a string. Also, because Haskell is lazy, even though it looks like it is treating the input as a single String in memory, it actually is only pulling the data as needed.
Edited to add extra info....
OK, so the heart of the soln is the pure portion:
unlines $ filter isPalindrome $ lines filecontent
The way that ($) works is by evaluating the function to the right, then using that as the input of the stuff on the left. In this case, filecontent is the full input from the file (a String, including newline chars), and the output is STDOUT (also a full string including newline chars).
Let's follow sample input through this process, "abcba\n1234\nK"
unlines $ filter isPalindrome $ lines "abcba\n1234\nK"
First, lines will break this into an array of lines
unlines $ filter isPalindrome ["abcba", "1234", "K"]
Note that the output of lines is being fed into the input for filter.
So, what does filter do? Notice its type
filter :: (a -> Bool) -> [a] -> [a]
This takes 2 input params, the first is a function (which isPalendrome is), the second a list of items. It will test each item in the list using the function, and its output is the same list input, minus items that the function has chosen to remove (returned False on). In our case, the first and third items are in fact palendromes, the second not. Our expression evaluates as follows
unlines ["abcba", "K"]
Finally, unlines is the opposite of lines.... It will concatinate the items again, inserting newlines in between.
"abcba\nK"
Since STDIO itself is a String, this is ready for outputting.
Note that is it perfectly OK to output a list of Strings using non-pure functions, as follows
forM ["1", "2", "3"] $ \item -> do
putStrLn item
This method however mixes pure and impure code, and is considered slightly less idiomatic Haskell code than the former. You will still see this type of thing a lot though!

Have a look at the filter function. You may not want to put all processing on a single line, but use a let expression. Also, your indentation is off:
main :: IO ()
main = do
filecontent <- readFile "palindrom.txt"
let selected = filter ... filecontent
...

Related

Haskell file I/O return types not matching?

Forward notice, this is my first day of Haskell.
So I have a function parse :: String -> String, I'm attempting to pass a file to it as such, either through the standard,
input <- readFile "input.txt"
or the other standard,
handle <- openFile "input.txt" ReadMode
input <- hGetContents handle
To which I want to perform,
output <- unlines $ map parse $ lines input
(Or however that should be canonically formatted)
Now as I understand it, readFile returns a string, lines takes a string and returns a list of strings, and map should map over a list of strings. If this is true, then why is lines input telling me that got a [String] instead of a String, while omitting lines and just having map parse input tells me it got a String instead of a [String]?
Here is the working example
parse :: String -> String
parse = id -- don't do anything to input
main = do
input <- readFile "input.txt"
let output = unlines $ map parse $ lines input
print output
In your example everything is fine except for output <- which should be replaced with the let output = for a reason you probably don't get yet, judging on your wording: readFile returns a string. As it was pointed in the comments, it is not. It returns IO String which means an IO-action you may fire up and get a string (or not… if, say, a file does not exist). The <- operation lets you inspect positive outcome of this action: it says that when the action is successful, you get a string (which you called input). And then you can play with it.
So why is <- not appropriate in the second line (with lines, map and stuff)? Simply because there is no action in it. You are applying pure functions in there. Binding a result of pure computations is done via let smth =.
Also, please, be aware that <- as well as let X = belong to do-blocks, syntactic constructs designed to write a sequence of steps to process actions. In contrast, inside pure functions you can find let X = … in (note in) or where X = to bind intermediate results.

Wondering about interact usage

I'm playing with the interact function from Prelude, wanting to do a simple REPL evaluating my inputs line by line, and I cannot understand what's going on.
If I make it simple like this:
main :: IO ()
main = interact interaction
interaction :: String -> String
interaction (x:xs) = xs
interaction x = x
Then it behaves ok, and removes the first character from my input, or returns the input if it is only one character long.
What puzzles me is if add this line:
interaction :: String -> String
interaction x | length x > 10 = "long word" -- this line causes problem
interaction (x:xs) = xs
interaction x = x
Then, interact seems not to work correctly any longer.
It just awaits for my input, swallows it awaiting another input and so on, but never outputs anything.
It seems so simple however, but I cannot see what is going wrong.
Any idea ?
(On my path I have GHC 7.6.3, I don't know if it has some importance.)
With what you've written, you're trying to calculate the length of the whole input sequence, so your program has to wait for the entire sequence to be available.
You could try a lazy pattern-match like this:
interaction (x1:x2:x3:x4:x5:x6:x7:x8:x9:x10:x11:_) = "long word"
This allows you to ignore the rest of the input once you know you've reached 10 characters.
A cleaner/more general alternative (suggested by #amalloy) that scales for bigger lengths and allows a variable length guard would be something like:
interaction xs | not . null . drop 10 $ xs = "long word"
If what you really want to do is process your input a line at a time, and produce this message for an individual line longer than 10 characters, you can use lines and unlines to make your interaction function line-oriented rather than character-oriented, e.g.:
main :: IO ()
main = interact (unlines . interaction . lines)
interaction :: [String] -> [String]
interaction (x:_) | length x > 10 = "long word" -- this is just looking at the first line
...
or maybe if you want to do that for every line, not just the first:
main :: IO ()
main = interact (unlines . map interaction . lines)
interaction :: String -> String
interaction x | length x > 10 = "long word"
...
interact takes the entirety of standard input at once, as one big string. You call length on all of stdin, and so your function cannot return until stdin is exhausted entirely. You could, for example, hit ctrl-D (assuming Unix) to send EOF, and then your function will finally find out what stdin's length is.

How to read text files into a haskell program?

I have a text file which contains two lists on each line. Each list can contain any number of alphanumeric arguments.
eg [t1,t2,...] [m1,m2,...]
I can read the file into ghc, but how can I read this into another main file and how can the main file recognise each argument separately to then process it?
I think it's best for you to figure out most of this for yourself, but I've got some pointers for you.
Firstly, try not to deal with the file access until you've got the rest of the code working, otherwise you might end up having IO all over the place. Start with some sample data:
sampleData = "[m1,m2,m3][x1,x2,x3,x4]\n[f3,f4,f5][y7,y8,y123]\n[m4,m5,m6][x5,x6,x7,x8]"
You should not mention sampleData anywhere else in your code, but you should use it in ghci for testing.
Once you have a function that does everything you want, eg processLists::String->[(String,String)], you can replcae readFile "data.txt" :: IO String with
readInLists :: FilePath -> IO [(String,String)]
readInLists filename = fmap processLists (readFile filename)
If fmap makes no sense to you, you could read a tutorial I accidentally wrote.
If they really are alphanumeric, you can split them quite easily. Here are some handy functions, with examples.
tail :: [a] -> [a]
tail "(This)" = "This)"
You can use that to throw away something you don't want at the front of your string.
break :: (Char->Bool) -> String -> (String,String)
break (== ' ') "Hello Mum" = ("Hello"," Mum")
So break uses a test to find the first character of the second string, and breaks the string just before it.
Notice that the break character is still there at the front of the next string. span is the same but uses a test for what to have in the first list, so
span :: (Char->Bool) -> String -> (String,String)
span (/= ' ') "Hello Mum" = ("Hello"," Mum")
You can use these functions with things like (==','), or isAlphaNum (you'll have to import Data.Char at the top of your file to use it).
You might want to look at the functions splitWith and splitOn that I have in this answer. They're based on the definitions of split and words from the Prelude.

Finding and replacing words with asterisk, in a text file output

Hello I am new at Haskell and i'm having problems trying to get this script to work. This script reads in arguements from a command line and find them in a seperate text file.
E.G: cat.txt | ./redact house big cat (in compiler)
It redacts certain words in a text file by replacing them with stars (**)asterisks. The number of stars used for each redacted word should equal the number of characters in the word.
module Main where
import System
import Data.Char
import Data.List
lowercase :: String -> String
lowercase = map toLower
main = do
arg1 <- getArgs
txt <- getContents
putStr (redact txt arg1)
redact :: String -> String -> String
redact input xWords = unlines [ work line | line <- lines input ]
where work line = unwords [ foo word | word <- words line ]
foo w | lowercase(w) == lowercase(xWords) = convertWord w 1
| otherwise = w
convertWord Eq a => [a] -> [a]
convertWord = map (const '*')
However, when i try to compile this, GHCi returns the error:
redact.hs:13:38:
Couldn't match expected thye 'Char' with actual type '[Char]'
Expected type: String
Actual type: [String]
In the second argument of 'redact', namely 'arg1'
In the first of 'putStr', namely '<redact txt arg1>'
Failed, module loaded: none.
So the code:
putStr (redact txt arg1)
is causing the problem.
Thank you in advance for any help and if you can improve the code in anyway that would be great.
EDIT:
I want to enter as many args as possible, it doesnt matter how many args you enter, i tried:
(arg1:arg2:arg3:arg4:arg5:_) <- getArgs
but I have to enter EXACT 5 args, It shouldn't matter how many args I enter.
I was thinking of using some kind of loop but I am not sure?
Again thank you for your help.
To get it to work with multiple arguments, use getArgs as you have it. The problem lies with
foo w | lowercase(w) == lowercase(xWords) = convertWord w 1
| otherwise = w
where you compare the lowercase of one word to lowercase of multiple words. The latter is not defined, you'd like to compare it to the lowercase of each of the xWords. So first you need to bring them all to lowercase, that's most efficiently done by calling from main redact txt (map lowercase arg1) rather than just redact txt arg1. Then you need to determine if a read word is in the list xWords, that's what the elem function is there for.
foo w | lowercase w `elem` xWords = convertWord w 1
| otherwise = w
BTW, you should maybe not call this function foo even if it's only a local one.
getArgs :: IO [String], so after arg1 <- getArgs, arg1 has the type [String]: it contains all the arguments passed to your program, as a list. But you're using it as String, thus the error: GHC expected arg1 to be a String, but it's a [String].
You can pattern-match on the result like this:
arg1:_ <- getArgs
This results in arg1 containing the first element of the list, and discards the rest of the list. If you don't pass an argument, it'll result in a runtime error. Of course, if you want more specialised behaviour (say, printing an error when no arguments are given), you could use a more complex method of extracting the first argument, such as a case expression.
As far as improvements to your program go:
You can simplify the definition of work using function composition and map rather than the list comprehension: work = unwords . map foo . words (read: "map foo over all the elements of the words, then unwords them").
redact can be simplified similarly, to redact input xWords = unlines . map work . lines $ input.
lowercase(w) is better written as lowercase w.
But your program looks basically fine to me, apart from some oddities (like the missing :: in convertWord's type signature, the additional 1 you pass to it in foo — but going by the somewhat erratic indentation, I guess you edited the code before posting it). I wouldn't make the first two changes unless you understand how they work and are comfortable writing code like that.

How to combine user input with list of tuples and write the complete list of tuples to the file?

I am trying to take user input and convert it in the form of a list of tuples.
What I want to do is that, I need to take the data from the user and convert it in the form of
[(Code,Name, Price)] and finally combine this user input with the previous list and write the new list to the same file.
The problem I am facing is that as soon as the program completes taking user input, WinHugs is showing an error like this Program error: Prelude.read: no parse.
Here is the code:
type Code=Int
type Price=Int
type Name=String
type ProductDatabase=(Code,Name,Price)
finaliser=do
a<-input_taker
b<-list_returner
let w=a++b
outh <- openFile "testx.txt" WriteMode
Print outh w
Close outh
The problem is that you're using lazy IO to read from a file while you're writing to it at the same time. This causes problems when read sees data that has been partially written.
We need to force the reading of the input data to be complete before you try writing to the file. One way of doing this is to use seq to force the list of products to be read into memory.
list_returner :: IO ([ProductDatabase])
list_returner = do
inh <- openFile "testx.txt" ReadMode
product_text <- hGetContents inh
let product :: [ProductDatabase]
product = read product_text
product `seq` hClose inh
return product
Also, this will fail if the file is empty. The file should contain at least [] before running your code the first time, so that it will parse as the empty list.
The code looks fine to me, except for certain style points. It should work like that. Try to separate concerns more. The exception "no parse" means that the read function was unable to convert its argument string to the desired type. The base library coming with Hugs may be more restrictive on spaces and line feeds. I would recommend using GHC instead of Hugs in general.
In case you're interested: One style point you may want to consider is using withFile instead of an openFile/hClose combination. You may also want to use the writeFile with show:
writeFile "testx.txt" (show w)
Another style point: Your input_taker action should not return a list. There is really no reason to return a list. Return a single tuple instead, so you can use (:) instead of (++). In general the usage of (++) indicates that you may be taking the wrong approach.
Further your ProductDatabase type name is misleading, because I would interpret [ProductDatabase] as a list of databases. Your tuple is a Product.
Final style point: This is really just about code beauty, so it's controversial. This is not C/C++, so you would really want to write f x instead of f(x):
...
return product
-- Since your `Product` is just a type alias, I would use
-- a smart constructor:
product :: Code -> Name -> Price -> Product
product = (,,)
readProduct :: IO Product
readProduct = do
...
code <- fmap read getLine
...
name <- getLine
...
price <- fmap read getLine
return (product code name price)

Resources