user13349043
user13349043

Reputation:

Haskell Display String into 8 columns

I have type person = (String, Float, Float, [Int]) ** name, height, weight, miles walked past week**

The [Int] will contain data for how many miles the person walked on each day for the past 7 days e.g. [5,8,12,7,12,6,9]

My testData consists of multiple peoples data and I want to return all names and the 7 daily figures of the miles they walked as a single string, neatly into separate rows for each person and have the miles walked data lined up in columns.

e.g. testData = [(John, 1.76, 63, [5,8,12,7,12,6,9]), (Hannah, 1.64, 56, [6,9,10,9,5,13,13]), (Lewis, 1.80, 73, [4,6,2,6,8,4,6])]

I want this returned like:

Return result

what is the best way to do this in haskell? Thanks

My Code --

personToString :: [Person] -> String   
personToString [] = []   
personToString ((name,height,weight,miles):person)=   
   name ++ take (9 - length name) (cycle " ") ++ intercalate ", " (map show miles) ++ "\n \n" ++ personToString person  

This returns what I want but the miles digits don't line up as there is a mix of single and double digit figures

Upvotes: 0

Views: 501

Answers (2)

jpmarinier
jpmarinier

Reputation: 4733

OK, somebody took the trouble of putting together module Text.Printf.

Seems just a job cut for it here, so let's give it a try:

import  Data.List (intercalate)
import  Text.Printf

type Person = (String, Float, Float, [Int]) -- ** name, height, weight, miles...

testData  :: [Person]
testData = [("John",   1.76, 63, [5,8,12,7,12,6,9]),
            ("Hannah", 1.64, 56, [6,9,10,9,5,13,13]),
            ("Lewis",  1.80, 73, [4,6,2,6,8,4,6])]

showMileList :: [Int] -> String
showMileList mileList = intercalate ","  $
                            map (printf "%3d") mileList

personToString :: Person -> String
personToString (name, h, w, miles) =
    (printf "%-9s" name) ++ " "                    ++
       (printf "%5.2f " h) ++ (printf "%3.0f " w)  ++
       (showMileList miles)

printAsLines :: [String] -> IO ()
printAsLines xs = mapM_ putStrLn  xs  -- a loop in the IO monad


main = do
   let strings = map  personToString  testData
   printAsLines strings

Program output:

John       1.76  63   5,  8, 12,  7, 12,  6,  9
Hannah     1.64  56   6,  9, 10,  9,  5, 13, 13
Lewis      1.80  73   4,  6,  2,  6,  8,  4,  6

Sole hitch in the deal I could find, the module is a bit hard to use under ghci: any looseness in the typing around printf seems to derail the interactive compiler.

Upvotes: 1

MikaelF
MikaelF

Reputation: 3644

Your main issue is properly padding columns with spaces. Going from this other answer, we can define our own left-padding and right-padding functions that take the desired length, the padding character, the string to pad, and return a padded string of the correct length (or more, if the string was already longer than the required length):

lPadWithChar, rPadWithChar :: Int -> Char -> String -> String
lPadWithChar l c s = replicate (l - length s) c ++ s
rPadWithChar l c s = s ++ replicate (l - length s) c

The name of your personToList function is a bit confusing, since it takes a list of Person. It would be more readable as personsToList. To show our padding functions in action, we can look into formatting a single Person without worrying for now about recursion:

personToString :: Person -> String
personToString (name, _, _, miles) = rPadWithChar 10 ' ' name
  ++ intercalate "," (map (lPadWithChar 3 ' ' . show) miles)

Notice you also don't need to bind the second and third elements of the Person, since you're not using them. This is standard practice in Haskell and makes it easier, just by looking at the first few characters of a function, that it only uses the name and miles elements.

Finally, we deal with the recursion and multi-line formatting in a separate function which accepts a [Person].

personsToString :: [Person] -> String
personsToString = concat . intersperse "\n" . map personToString
                  --You only need one \n, right?

Here, I used the higher-order functions map and intersperse to abstract the recursion away, but you can easily turn this into explicit recursion. Here it is in action:

>>> let testData = [("John", 1.76, 63, [5,8,12,7,12,6,9]), ("Hannah", 1.64, 56, [6,9,10,9,5,13,13]), ("Lewis", 1.80, 73, [4,6,2,6,8,4,6])]
>>> putStrLn $ personsToString testData
John        5,  8, 12,  7, 12,  6,  9
Hannah      6,  9, 10,  9,  5, 13, 13
Lewis       4,  6,  2,  6,  8,  4,  6

From the formatting of your test data, I assume that this is from a course. Just note that using tuples to carry around complex groups of real-world information is very error-prone and makes for hard-to-maintain code. For example, consider what would happen if you had a requirement to make the miles the first element of the tuple, you would end up with a completely broken API. The standard way to fix this is by using record syntax.

Upvotes: 0

Related Questions