在Haskell中将字符串格式化为三角形

时间:2014-12-20 00:46:42

标签: haskell formatting

我有一个字符串列表列表,我需要使用句点将它们格式化为三角形,这样每个列表都在它自己的行上,并且每个列表的字符串至少相隔一个句点。此外,每个角色必须在上方和下方有点。这可能是最好的例子:

> dots [["test"], ["hello", "world"], ["some", "random", "words"], ["even", "more", "random", "words"]]

应该返回

............test.....................
.......hello.......world.............
...some......random......words.......
not....really......random.....anymore

最后,它应该使用尽可能少的句号,即将每个单词填充到最大单词长度的方法太浪费了;这就是上面的例子不应该返回

.....................test........................
..............hello.........world................
.......some..........random........words.........
not...........really........random........anymore

我可以很容易地编写一个函数来执行将句点放在任意一侧以使其成为三角形的形状,我的问题是在单词之间的句点。

我有一个函数,只要字长为1就可以工作,这对任务来说显然是无用的。不过,我的函数dots

dots :: [[String]] -> [[String]]
dots xss = map dots' xss
    where dots' (x:[]) = [x]
          dots' (x:xs) = [x] ++ ["."] ++ dots' xs

这是一项家庭作业,所以提示是首选,但我一直试图这样做几个小时没有运气。

3 个答案:

答案 0 :(得分:3)

首先,您需要一个函数,将占位符添加到这样的列表中:

addPlaceholders [ ["test"]
                , ["hello", "world"]
                , ["some", "random", "words"]
                , ["not", "really", "random", "anymore"]
                ]

==> [ [""   , ""    , ""      , "test"  , ""      , ""     , ""       ]
    , [""   , ""    , "hello" , ""      , "world" , ""     , ""       ]
    , [""   , "some", ""      , "random", ""      , "words", ""       ]
    , ["not", ""    , "really", ""      , "random", ""     , "anymore"]
    ]

现在您需要用点填充这些""。因此,您可以编写一个辅助函数,为列表添加点:

addDots ["test", "", "random", ""]

==> ["test..","......","random","......"]

然后fill只是

fill = transpose . map addDots . transpose

你的功能只是

triangle = map concat . fill . addPlaceholders

答案 1 :(得分:2)

首先,一些术语:对于给定的行,例如["some", "more", "random", "words"],我们会将行中的某个单词的索引称为"逻辑列"。因此"more"在该行中具有逻辑列1;并且"words"具有逻辑列3。一旦我们为每个单词选择了一个位置,我们也会有一个"物理列"这表示在渲染行时应该在它之前出现多少个字符(点或其他字符)。

让我们做一个简化的假设(问题很难甚至简化):在最终版式中,行r,逻辑列c之间的单词必须位于单词之间在行r+1,逻辑列cc+1

解决这个问题的一个想法是添加第三种列,我们将其称为"棋盘栏",作为中间步骤。从底部开始的偶数行将在棋盘列中包含所有单词,而从底部开始的行数为奇数,将在奇数棋盘列中包含所有单词。然后可以为每个棋盘列选择宽度,并将单词的物理列设置为棋盘列宽度小于它的总和。

然而,这有一个小问题;考虑这个棋盘,我已经明确标出了棋盘栏边界:

 | |  |aa| | |
 | | b|  |c| |
 |d|  |e | |f|
g| |hh|  |i| |j

因为我们为每个棋盘格列选择了一个宽度,所以不同棋盘格列中的单词永远不会重叠。这排除了类似下面的解决方案,它们稍微窄一些:

   aa
  b  c
 d  e f
g hh i j

请注意aahh重叠 - 尽管它们不在相邻的行上,所以这没关系。

另一个解决方案是按顺序列出单词:

   4
  3 7
 2 6 9
1 5 8 10

在布置给定单词时,我们可以通过查看其上方/左侧和下方/左侧的单词的位置和长度,为其选择不违反规则的最小物理列(这已经计算好了)。我有一个这个算法的实现,我将在几天内添加到这个答案(根据关于家庭作业的网站指南),但这个提示应该足以让你自己重现一些非常类似的东西。我实现的有趣算法位是Map (Row, LogicalColumn) String -> Map (Row, PhysicalColumn) String类型的十行函数,我建议您尝试类似的类型函数。应该可以直接巧妙地遍历输入列表(因此消除任何地图索引成本),但我无法完全理解它。我们可以通过归纳(我们所引入的变量是我们布置单词的顺序)来证明这种方法可以产生最小宽度的解。

正如所承诺的,我提出的代码:

import Control.Applicative
import Data.List
import Data.Map hiding (empty, map)
import Data.Ord

type Row = Int
type PhysicalColumn = Int
type LogicalColumn  = Int

layout :: Map (Row, LogicalColumn) [a] -> Map (Row, PhysicalColumn) [a]
layout m = munge answer where
    answer = mapWithKey positionFor m
    positionFor (r, c) as = maximumBy (comparing snd) . concat $
        [ [(as, 0)]
        , addLength as <$> lookup (r+1, c  ) answer
        , addLength as <$> lookup (r-1, c-1) answer
        ]
    addLength as (v, p) = (as, p + length v)
    lookup k m = maybe empty pure (Data.Map.lookup k m)
    munge = fromAscList . map (\((r, _), (w, c)) -> ((r, c), w)) . toAscList

parse :: String -> Map (Row, LogicalColumn) String
parse = fromList
      . enumerate
      . map words
      . lines

enumerate :: [[a]] -> [((Row, LogicalColumn), a)]
enumerate xss = concat . zipWith (\i xs -> [((i, j), x) | (j, x) <- xs]) [0..] . map (zip [0..]) $ xss

groups :: Eq b => (a -> (b, c)) -> [a] -> [(b, [c])]
groups f
    = map (\pairs -> (fst . head $ pairs, map snd pairs))
    . groupBy ((==) `on` fst)
    . map f

flatten :: Map (Int, Int) [a] -> [(Int, [(Int, [a])])]
flatten
    = map (\(r, pairs) -> (r, map (concat <$>) (groups id pairs)))
    . groups (\((r, c), a) -> (r, (c, a)))
    . toAscList

pad :: a -> [(Int, [a])] -> [a]
pad blank = go 0 where
    go n ((target, v):rest) = replicate (target-n) blank ++ v ++ go (target+length v) rest
    go _ [] = []

pprint = unlines . map (pad ' ' . snd) . flatten

allTogetherNow = putStr . pprint . layout . parse

答案 2 :(得分:1)

我会按如下方式解决问题。

首先,忽略每个单词的长度。想象一下n个棋盘,并将每个单词放在一个正方形中,这样单词最终只能以黑色方块结束。现在,行数n是您拥有的单词列表的数量。找出应该是什么。

您可能希望在每行中“分配”分配,以便最后获得一个三角形。

然后,考虑字长。每列m列的宽度(以字符为单位)是多少?对于每列,计算其宽度。用点填充每个正方形,以达到预期的宽度。

我没有声称这是更简单的方法 - 它只是第一个出现在我面前的方法:)