I'm trying to write a program that allows the user to build up a list of strings by entering them in one at a time, and displays the list after every step.
Here is my code so far:
buildList :: [String] -> IO ()
buildList arr = do
putStr "Enter a line:"
str <- getLine
if str == "" then
return ()
else do
let newarr = arr : str
putStrLn ("List is now: " ++ newarr)
buildList newarr
listBuilder :: IO ()
listBuilder = do
buildList []
listBuilder is starting the list by passing in the empty list, and I'm trying to use recursion so that the code keeps running until the user enters the empty string.
Its not working, any ideas welcome
Here is a desired input:
Enter a line: hello
List is now ["hello"]
Enter a line: world
List is now ["hello","world"]
Enter a line:
Error:
Couldn't match type `Char' with `[String]'
Expected type: [[String]]
Actual type: String
In the second argument of `(:)', namely `str'
In the expression: arr : str
In an equation for `newarr': newarr = arr : str
EDIT:
This fixed it, thanks to the clues and use of show
buildList :: [String] -> IO ()
buildList arr = do
putStr "Enter a line:"
str <- getLine
if str == "" then
return ()
else do
let newarr = arr++[str]
putStrLn ("List is now: " ++ show newarr)
buildList newarr
listBuilder :: IO ()
listBuilder = do
buildList []
You can get this working by
(a) putting the new string at the end of the list with arr++[str] instead of arr:str since : can only be used like singleThing:list,
(b) splitting the run-round into a separate function, and
(c) passing the result on with return so you can use it elsewhere in your program
buildList arr = do
putStrLn "Enter a line:"
str <- getLine
if str == "" then
return arr
else do
tell (arr++[str])
tell arr = do
putStrLn ("List is now: " ++ show arr) -- show arr to make it a String
buildList arr
giving
Enter a line:
Hello
List is now: ["Hello"]
Enter a line:
world
List is now: ["Hello","world"]
Enter a line:
done
You can solve this problem more declaratively using the pipes and foldl libraries:
import Control.Foldl (purely, list)
import Pipes
import qualified Pipes.Prelude as P
main = runEffect $ P.stdinLn >-> purely P.scan list >-> P.print
You can read this as a pipeline:
P.stdinLn is a source of lines input by the user
P.scan behaves like Data.List.scanl, except for pipelines instead of lists. purely P.scan list says to continuously output the values seen so far.
P.print prints these output lists to the console
Here's an example of this pipeline in action:
$ ./example
[]
Test<Enter>
["Test"]
ABC<Enter>
["Test","ABC"]
42<Enter>
["Test","ABC","42"]
<Ctrl-D>
$
You can also easily switch out other ways to fold the lines just by changing the argument to purely scan. For example, if you switch out list with Control.Foldl.vector then it will output vectors of lines instead of lists.
To learn more, you can read the documentation for the pipes and foldl libraries.
The problem is that the : data constructor can only be used to append an element to the beginning of the list. When you write let arr=arr:str, you are using it to put an element at the end of the list. Instead, you can either construct your list backwards like this let arr=str:arr or use the ++ operator to append it to the end of the list like this let arr=arr++[str].
Related
How do I print each line in a text file only once but in a random order?
I have a text file that containts six individual lines and I am trying to print them to the screen randomly
Here is the code I have so far
open Scanf
open Printf
let id x = x
let const x = fun _ -> x
let read_line file = fscanf file "%s#\n" id
let is_eof file = try fscanf file "%0c" (const false) with End_of_file -> true
let _ =
let file = open_in "text.txt" in
while not (is_eof file) do
let s = read_line file in
printf "%s\n" s
done;
close_in file
I could append elements "s" into a list. Printing elements in a list can be as simple as following however, I am not sure how to print elements in the list randomly.
let rec print_list = function
[] -> ()
| e::l -> print_int e ; print_string " " ; print_list l
Sort your list with random comparator. For example by the following function.
let mix =
let random _ _ =
if Random.bool() then 1 else -1 in
List.sort random
Edit 1 (15.11.20)
List.sort implements Merge Sort algorithm. Merge Sort has stable O(n log n). Also steps count of this algorithm is not dependent on results of items comparison. It means our random function that is nondeterministic doesn't effect the time of List.sort work. (The following image is from wikipedia)
If our input data is list and we can't use mutable data structures - I think it is impossible to implement solution with better Big O than O(n log n) because of immutable list and necessity to have random access to items.
let's define a function that retrieve one element identified by its position in a list, and return a tuple (this_element, the_list_wo_this_element).
Ex : pick [0;2;4;6;8] 3
returns (6, [0;2;4;8)).
Then, by iterating on the resulted list (the rhs of the tuple above), you pick a random element from that list , until that list is empty.
In Haskell I have a list that looks like this:
[[["PersonA"],["AddressA"]],[["PersonB"],["AddressB"]],[["PersonC"]]]
and I need the lists within my list that have length=2, i.e. the people that I know the address of. In this case, I would want:
[["PersonA"],["Address"]]
and
[["PersonB"],["Address"]]
and I would not want PersonC because I don't have his address.
I was thinking about something like:
myList = [[["PersonA"],["123456789"]],[["PersonC"],["987654321"]],[["PersonE"]]]
main :: IO ()
main = do
map (\x -> if length x == 2 print x else print "") myList
(print is just an example, I will need to work with them later)
But this returns a
Couldn't match expected type ‘IO ()’ with actual type ‘[IO ()]’
error on line 5.
Any idea how to do that?
Thanks
Your problem is that print is an IO action, and to sequence these you'd need to use mapM_ instead of map to also get back the IO () that main expects. Alternatively, wrap the list of IO actions that map produced in a sequence_ call.
But I don't think this is the right approach anyway. To select from a list, you should use filter not map:
myList = [[["PersonA"],["123456789"]],[["PersonC"],["987654321"]],[["PersonE"]]]
myLen2List = filter (\x -> length x == 2) myList
main :: IO ()
main = print myLen2List
That said, #Daniel Wagner is totally right in the comments. Don't use lists as your custom data type.
data Person = Person { name :: String, address :: Maybe String } deriving (Eq, Show)
myList = [ Person "PersonA" (Just "123456789"),
Person "PersonC" (Just "987654321"),
Person "PersonE" Nothing ]
myAddressList = filter (isJust . address) myList
main = print myAddressList
I am trying to print list with comma.
I have list like ["1","2","3"] and I want to print 1,2,3
How can I do that?
I tried:
printList xs = mapM_ (\(a) -> do
putStr a
putStr (",")) xs
But I dont know how to remove the last comma.
You can use intercalate. It'll insert the comma between each element of the list and concatenate the resulting list of strings to turn it into a single string.
import Data.List
toCommaSeparatedString :: [String] -> String
toCommaSeparatedString = intercalate ","
ghci> toCommaSeparatedString ["1","2","3"]
"1,2,3"
This is a bit of an XY problem: as Benjamin Hodgson shows, you’re better off turning your list into a string, and then printing that – you want as much of your logic outside of the IO monad as possible.
But of course, even if your question is somewhat in the wrong direction from the start, it has an answer! Which is that, for example, you could write this:
printList :: [String] -> IO ()
printList [] = return ()
printList [x] = putStr x
printList (x:xs) = do
putStr x
putStr ","
printList xs
Benjamin’s answer is better. But this one might elucidate IO monad code and do-notation a bit more.
getLines = liftM lines . readFile
main = do
argv <- getArgs
name <- getProgName
if not (null argv)
then do
let file = head argv
list <- getLines file
let olist = mergesort (<=) list
let splitter = map (split ",") olist
loop splitter
else hPutStr stderr $ "usage: " ++ name ++ " filename"
loop a = do
line <- getLine
case line of
"help" -> putStrLn "print - prints list in alphabetical order\n\
\quit - exits program"
"print" -> do putStrLn "[print]"
mapM_ putStrLn a
putStr "\n"
"quit" -> do putStrLn "[quit]"
exitSuccess
_ -> putStrLn "invalid command"
loop a
I'm trying to split a list of lines that each have a comma, except I get an error at, loop splitter. I think its because mapM_ putStrLn within my loop function can't understand the new list. Before i used the splitter function my program would print out the entire list taken from a text file with no problems. How can i print out a new list of words that were split at the comma?
I would like to build a string list by prompting the user for input. My end goal is to be able to parse a string list against a simple hash table using a simple routine.
`let list_find tbl ls =
List.iter (fun x ->
let mbr = if Hashtbl.mem tbl x then "aok" else "not found"
in
Printf.printf "%s %s\n" x mbr) ls ;;`
Building a string list is accomplished with the cons operator ::, but somehow I am not able to get the prompt to generate a string list. A simpe list function returns anything that is put into it as a list:
`let build_strlist x =
let rec aux x = match x with
| [] -> []
| hd :: tl -> hd :: aux tl
in
aux x ;;`
Thus far, I have been able to set the prompt, but building the string list did not go so well. I am inclined to think I should be using Buffer or Scanning.in_channel. This is what I have thus far:
`#load "unix.cma" ;;
let prompt () = Unix.isatty Unix.stdin && Unix.isatty Unix.stdout ;;
let build_strlist () =
let rec loop () =
let eof = ref false in
try
while not !eof do
if prompt () then print_endline "enter input ";
let line = read_line () in
if line = "-1" then eof := true
else
let rec build x = match x with
| [] -> []
| hd :: tl -> hd :: build tl
in
Printf.printf "you've entered %s\n" (List.iter (build line));
done
with End_of_file -> ()
in
loop () ;;`
I am getting an error the keyword "line" has the type string, but an expression was expected of type 'a list. Should I be building the string list using Buffer.create buf and then Buffer.add_string buf prepending [ followed by quotes " another " and a semicolon? This seems to be an overkill. Maybe I should just return a string list and ignore any attempts to "peek at what we have"? Printing will be done after checking the hash table.
I would like to have a prompt routine so that I can use ocaml for scripting and user interaction. I found some ideas on-line which allowed me to write the skeleton above.
I would probably break down the problem in several steps:
get the list of strings
process it (in your example, simply print it back)
1st step can be achieved with a recursive function as follow:
let build_strlist' () =
let rec loop l =
if prompt () then (
print_string "enter input: ";
match read_line () with
"-1" -> l
| s -> loop (s::l)
) else l
in loop [];;
See how that function loops on itself and build up the list l as it goes. As you mentioned in your comment, I dropped the imperative part of your code to keep the functional recursion only. You could have achieved the same by keeping instead the imperative part and leaving out the recursion, but recursion feels more natural to me, and if written correctly, leads to mostly the same machine code.
Once you have the list, simply apply a List.iter to it with the ad hoc printing function as you did in your original function.