【问题标题】:Format list output in Haskell?在 Haskell 中格式化列表输出?
【发布时间】:2011-08-21 05:23:51
【问题描述】:

我在尝试在 Haskell 中格式化我自己类型的列表的输出 a 时遇到问题。

我想要这样的东西:

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 String [Int] 类型加载的数据。

我将如何在 Haskell 中执行此操作?

【问题讨论】:

    标签: list haskell types pretty-print


    【解决方案1】:

    一般来说,我们使用“漂亮打印”库来进行漂亮的格式化输出。您应该知道的标准是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
    

    快速编写漂亮打印机的能力是一项非常有用的技能。

    【讨论】:

    • 如果你想让列宽适应最宽的项目,你需要更多的代码。
    • id 不喜欢使用这些库,我喜欢下面的方法,但我不确定如何使用一组测试数据?
    • 您会从文件或数据库中读取测试数据。编辑:啊,我明白了,你也在研究如何做 IO。在本网站上查找有关 io + haskell 的其他问题。
    • 它在一个函数中:testData = [("type1","type2",["item1","item12"], "type2","type3",["item2","item22 "])] 是的,这就是我自己的格式/类型中的数据...
    【解决方案2】:

    这是一个通用的表格生成器。它计算列宽以适合最宽的行。 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
    

    【讨论】:

    • 这是我现在最喜欢的用于表格的漂亮打印机。我喜欢它简单且可组合。
    【解决方案3】:

    这样的?

    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"
    

    【讨论】:

    • 啊,我今天看到了类似的东西 - 它是如何工作的?
    • 我将如何为此使用测试数据?所以你实际上并没有给用户提供任何东西,它只是从名为 testData 的 antoehr 函数中提供的??
    • @Ash:我添加了一个简单的示例。请注意,这假设您的字符串长度相同,否则列将不会对齐。请参阅 Don Stewart 的回答,了解如何处理对齐不同宽度的列。
    • 那么我将如何使这个调用另一个函数用于测试数据?这是另一个函数: testData = [("type1","type2",["item1","item12"], "type2","type3",["item2","item22"])] 我怎么能把它放在那里,这样就不需要用户输入了?
    • 您可以执行mapM_ (putStrLn . fooToLine) testData 之类的操作,但您必须将Foos 更改为元组,反之亦然。
    猜你喜欢
    • 1970-01-01
    • 2018-07-03
    • 2011-12-21
    • 2018-06-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-13
    相关资源
    最近更新 更多