格子内输出
【题目】从控制台输入若干行。每行若干项,由逗号分开。输出时,要求把这些内容放到表格中,并且要居中对齐。
比如:从键盘上输入了:
a,bb,ccc
aaaa,ccccc
a,b,c,ddd
aa,ccccccccccccccccc,ee
(注意,连续的逗号表示中间的项是空的)
(注意,连续输入了两个回车就表示输入线束了)
这时,程序应该输出:
显然,程序需要计算出每个列的宽度。并把每个格子中的内容安排在合适的位置(通过加入空格)。
通用 haskell 解决这个问题时,要注意把信息本身,和它的表达形式区分开,这样各个击破不仅易于编写,也易于将来的维护。
上代码:
import Data.List (delete)
---- 按字符c把串分开为若干词
split :: Char -> String -> [String]
split _ [] = []
split c s = takeWhile (/=c) s : (split c . delete c . dropWhile (/=c)) s
----若干行词 --> 求出每列的最大宽度
getWidths :: [[String]] -> [Int]
getWidths [] = []
getWidths (ss:sss) = f (map length ss) (getWidths sss)
where
f [] [] = []
f [] y = y
f x [] = x
f (x:xs) (y:ys) = max x y : f xs ys
----生成在格子中显示效果的串列表
gridShow :: [Int] -> [[String]] -> [String]
gridShow l列宽 l多行
| null l多行 = [f l列宽]
| otherwise = f l列宽 : g l列宽 (head l多行) : gridShow l列宽 (tail l多行)
where
---- f 列宽 ---> 生成边线
f [] = "+"
f (x:xs) = "+" ++ replicate x '-' ++ f xs
---- g 列宽 内空 ---> 内容行(无内容的列要补空格)
g [] _ = "|"
g ws [] = g ws [""]
g (w:ws) (x:xs) = "|" ++ fill w x ++ g ws xs
fill w x =
let n = (w - length x) `div` 2
in replicate n ' ' ++ x ++ replicate (w-n-length x) ' '
ok :: [String] -> [String]
ok ss = let da = map (split ',') ss
w = getWidths da
in gridShow w da
----------------------------------------
---- 项间逗号分割,空行则结束输入
getSomeLines :: [String] -> IO [String]
getSomeLines xs = do
s <- getLine
if null s then
return $ reverse xs
else
getSomeLines (s:xs)
main :: IO ()
main = do
ss <- getSomeLines []
putStr $ unlines (ok ss)
程序是由虚线分开的两个部分。虚线以下是实现 IO 交互的部分。它的主要工作是识别用户连续两个回车而结束程序,然后把读入的每个行变成 [String] 列表的一部分,放入到 ss 中,等待一个纯函数
去处理它。
split 实现按给定的字符对串进行分割。
getWidths 扫描所有的行,得出每个项应该具有的宽度(该列所有元素的宽度的最大值,只是内容,不含格子)
gridShow 显示每一行。每一行的显示,先输出它的上边线
,再输出多个项的内容。
当遇到空时,只输出一行边线,这就是整个表格的下边线。
内部的函数 g 是输出内容的。需要注意的是,当内容列表为空,而列宽列表不空时,意味着这行的后面的格子没有内容。这相当于内容为空串(因为空格和格子线还是要画出来的)。