Haskell - read list of persons

Viewed 304

The problem sounds like this: write a program that reads a number n and then n persons, for each persons, read their name and age and then return the oldest persons/persons.

Example input:

3
Ion Ionel Ionescu
70
Gica Petrescu
99
Mustafa ben Muhamad
7

Example output

Oldest is Gica Petrescu (99 years).

My code so far:

readPers :: IO(String, Int)
readPers = do
    name <- getLine
    age <- readLn :: IO Int
    return (name, age)

readPerss :: (Ord t, Num t) => t -> [IO (String, Int)]
readPerss n
    | n > 0 = readPers : readPerss(n-1)
    | otherwise = []

pFunc = do
    print "Numer of persons:"
    n <- readLn :: IO Int
    let persons = readPerss n
    return persons

I first read n, then I try to make a list of persons using readPers and readPerss, but I am stuck, I don't know how to tackle it from that point forward and I guess that my implementation thus far is not quite right.

How should I solve the problem?

2 Answers

You are very close! What you are doing in readPerss :: (Ord t, Num t) => t -> [IO (String, Int)] is returning a list of IO actions; each action returns a pair of String and Int when it is executed. Currently in pFunc you are only building this list of actions, storing it in a variable with let, and returning it from pFunc; you are never executing them with a <- “bind” statement.

There are a few simple ways to do what you want. The smallest change to your code that does what you want is to add sequence, which takes a container of actions and produces an action that returns a container:

sequence :: (Traversable t, Monad m) => t (m a) -> m (t a)

Here t is [], m is IO, and a is (String, Int):

sequence :: [IO (String, Int)] -> IO [(String, Int)]

Another way is to rewrite readPerss so that it executes the actions directly, accumulating the (String, Int) results in a list instead of accumulating the IO actions:

readPerss :: (Ord t, Num t) => t -> IO [(String, Int)]
-- Change [IO …] to IO […]:         ~~~~~~~~~~~~~~~~~~

readPerss n
  | n > 0 = do
    pers <- readPers
    perss <- readPerss (n - 1)
    return (pers : perss)
  | otherwise = return []

I know you may not be supposed to use library functions if this is a homework assignment or exercise, but in typical code “repeat x action n times and accumulate the results” is often represented with replicateM n x:

replicateM :: Applicative m => Int -> m a -> m [a]

This is how I always do this (it is from a code challenge isn’t it). I always seperate IO and logic as soon as possible. Works perfect (unless N is very big).

import Data.List.Split (chunksOf)

type Person = (String, Int)

main = do
    x <- getContents
    putStrLn $ program x

program :: String -> String
program s = “Oldest is “ ++ x ++ “ (“ ++ (show y) ++ “ years old).”
    where
    (x, y)  = solve persons
    persons = [(name, read age :: Int) | [name, age] <- chunksOf 2 . tail . lines $ s]

solve :: [Person] -> Person
solve ls = undefined 

I leave the undefined to you.

Related