如何在Haskell中格式化列表输出?

7

我正在尝试在Haskell中格式化自己类型的列表输出,但遇到了困难。

我想要的输出结果如下:

Make  | Model | Years(this is a list)    <- this would be the headers if you like
-------------------
Item1 | Item1 | Item1s,Item1s           
Item2 | Item2 | Item2s,Items2,Items2

这是我从字符串String [Int]类型中加载的数据。

在Haskell中,我该如何做到这一点?

3个回答

14

通常,我们使用“漂亮打印”库来进行格式化输出。您应该知道的标准库是Text.PrettyPrint。给定一个数据类型,您可以遍历该类型,并构建出格式良好的文档。

以下是一个例子:

import Text.PrettyPrint
import Data.List

-- a type for records
data T = T { make  :: String
           , model :: String
           , years :: [Int] }
    deriving Show

-- test data
test =
    [ T "foo" "avenger" [1990, 1992]
    , T "bar" "eagle"   [1980, 1982]
    ]

-- print lists of records: a header, then each row
draw :: [T] -> Doc
draw xs =
    text "Make\t|\tModel\t|\tYear"
   $+$
    vcat (map row xs)
 where
    -- print a row
    row t = foldl1 (<|>) [ text (make t)
                         , text (model t)
                         , foldl1 (<^>) (map int (years t))
                         ]

-- helpers
x <|> y = x <> text "\t|\t" <> y
x <^> y = x <> text "," <+> y

测试:

main = putStrLn (render (draw test))

结果为:

Make    |   Model   |   Year
foo     |   avenger |   1990, 1992
bar     |   eagle   |   1980, 1982

快速编写漂亮的打印输出程序是一项非常有用的技能。

如果您希望列宽度适应最宽的项目,则需要编写更多代码。 - augustss
我更喜欢不使用这些库,但我喜欢下面的方法,但我不确定如何使用一组测试数据? - Ash
你需要从文件或数据库中读取测试数据。编辑:啊,我明白了,你也在寻找如何进行IO操作。在这个网站上寻找关于“io”和“haskell”的其他问题。 - Don Stewart
这是一个函数中的内容:testData = [("type1","type2",["item1","item12"], "type2","type3",["item2","item22"])]是的,这就是我的自定义格式/类型中传入数据的方式... - Ash

6
这是一个通用的表格生成器。它计算列宽以适应最宽的行。`ColDesc` 类型允许您为每个列指定标题对齐方式、标题字符串、数据对齐方式和格式化数据的函数。
import Data.List (transpose, intercalate)

-- a type for records
data T = T { make  :: String
           , model :: String
           , years :: [Int] }
    deriving Show

-- a type for fill functions
type Filler = Int -> String -> String

-- a type for describing table columns
data ColDesc t = ColDesc { colTitleFill :: Filler
                         , colTitle     :: String
                         , colValueFill :: Filler
                         , colValue     :: t -> String
                         }

-- test data
test =
    [ T "foo" "avenger" [1990, 1992]
    , T "bar" "eagle"   [1980, 1982, 1983]
    ]

-- functions that fill a string (s) to a given width (n) by adding pad
-- character (c) to align left, right, or center
fillLeft c n s = s ++ replicate (n - length s) c
fillRight c n s = replicate (n - length s) c ++ s
fillCenter c n s = replicate l c ++ s ++ replicate r c
    where x = n - length s
          l = x `div` 2
          r = x - l

-- functions that fill with spaces
left = fillLeft ' '
right = fillRight ' '
center = fillCenter ' '

-- converts a list of items into a table according to a list
-- of column descriptors
showTable :: [ColDesc t] -> [t] -> String
showTable cs ts =
    let header = map colTitle cs
        rows = [[colValue c t | c <- cs] | t <- ts]
        widths = [maximum $ map length col | col <- transpose $ header : rows]
        separator = intercalate "-+-" [replicate width '-' | width <- widths]
        fillCols fill cols = intercalate " | " [fill c width col | (c, width, col) <- zip3 cs widths cols]
    in
        unlines $ fillCols colTitleFill header : separator : map (fillCols colValueFill) rows

跑步:
putStrLn $ showTable [ ColDesc center "Make"  left  make
                     , ColDesc center "Model" left  model
                     , ColDesc center "Year"  right (intercalate ", " . map show . years)
                     ] test

结果为:
Make |  Model  |       Year      
-----+---------+-----------------
foo  | avenger |       1990, 1992
bar  | eagle   | 1980, 1982, 1983

这是我现在最喜欢的表格美化工具。我喜欢它的简单和可组合性。 - CoolCodeBro

4
像这样的吗?
import Data.List (intercalate)
data Foo = Foo String String [Int]

fooToLine :: Foo -> String
fooToLine (Foo a b cs) = a ++ " | " ++ b ++ " | " ++ intercalate ", " (map show cs)

现在,您可以实现以下操作:

>>> fooToLine (Foo "Hello" "World" [1, 2, 3])
"Hello | World | 1, 2, 3"

我今天看到过类似的东西,它是如何工作的? - Ash
我该如何使用测试数据呢?这样你就不需要从用户那里输入任何内容,而是从另一个名为testData的函数中获取数据。 - Ash
@Ash:我已经添加了一个简单的例子。请注意,这假定您的字符串长度相同,否则列将不对齐。有关如何处理不同宽度列的对齐,请参见Don Stewart的答案。 - hammar
那么我该如何调用另一个函数来测试数据呢?这是另一个函数:testData = [("type1","type2",["item1","item12"], "type2","type3",["item2","item22"])]我该怎么把它放进去,使其不需要用户输入? - Ash
你可以像这样做:mapM_ (putStrLn . fooToLine) testData,不过你需要将 Foo 转换成元组或者反过来。 - hammar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接