首先,一些术语:对于给定的行,例如["some", "more", "random", "words"]
,我们将在行的“逻辑列”中调用给定单词的索引。因此"more"
在该行中具有逻辑列1
;并且"words"
具有逻辑列3
。一旦我们为每个单词选择了一个位置,我们还会有一个“物理列”,表示在渲染行时应该出现多少个字符(点或其他单词字符)。
让我们做一个简单的假设(的问题是够硬,甚至简化):在最后的布局,在r
行字,逻辑列c
必须在r+1
行,逻辑列c
和c+1
是在字与字之间。
解决此问题的一个想法是添加第三种列,我们称之为“棋盘列”作为中间步骤。从底部开始的偶数步骤将在偶数棋盘列中包含所有单词,而从底部开始奇数个步骤的行将具有奇数棋盘列中的所有单词。然后可以为每个棋盘列选择一个宽度,并将单词的物理列设置为棋盘列的宽度小于它的总和。
但是,这有一个小问题;考虑这个棋盘,在那里我已经明确地标示出棋盘列边界:
| | |aa| | |
| | b| |c| |
|d| |e | |f|
g| |hh| |i| |j
因为我们选择每个棋盘列的宽度,在不同的棋盘格列说出的话不能重叠。这排除了类似下面的一个解决方案,这是略窄:
aa
b c
d e f
g hh i j
注意aa
和hh
重叠 - 尽管他们不在相邻行,所以这是好的。
另一种解决方案是顺序进行布局的话:
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
也许例子稍有不正确的?在世界的下方有一个米,根据规则,这应该是不允许的。另外还有一整列可能被删除的点。 (和+1清除它是作业) – chi
是的,这是错误的,谢谢指出。我编辑过它。 – b3036667
这个问题令人惊讶地微妙!例如,[[“aa”],[“b”,“c”],[“d”,“e”,“ff”]'的一个正确答案是“.... aa \ nbc。 \ nd.e ... \ n .... FF“'。注意'aa'是'c'的*右边*和答案'“..aa ... \ nb.c .. \ nd.e .... \ n ..... ff”'将'aa'放在'b'和'c'之间的点数更多(因此根据您的规格不正确)。这是故意的吗? –