Haskell Display String into 8 columns

不问归期 提交于 2021-01-29 21:37:14

问题


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


回答1:


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.




回答2:


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.



来源:https://stackoverflow.com/questions/61293535/haskell-display-string-into-8-columns

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!