您需要实际递归地继续。在您的代码中,如现在所示,makePaths
调用findPaths
,但findPaths
和makePaths
都不会以递归方式调用makePaths
或findPaths
。看到算法的机制有点难,原因有两个:首先,你用很多临时状态注释树,其次,你不必要地处理Set
。
让我们剥去一些东西。
让我们从树开始。最终,我们只需要一个具有节点值的n -ary树。
data Tree a = Empty | Node a [Tree a] deriving (Show, Read, Eq, Ord)
需要明确的是,这Tree
相当于你Tree
type OldTree a h d = Tree (a, h, d)
这就是说,因为最终的目标树是一个唯一的节点处饰有String
小号我们要瞄准对于这样的函数:
makeTree :: String -> [String] -> Tree String
这里,第一个字符串是种子值,字符串列表是可能的连续剩余的字符串,树是我们完全构建的字符串树。该功能也可以直接写入。它进行递归基于断给定一个种子,我们马上就知道我们的树的根的事实:
makeTree seed vals = Node seed children where
children = ...
孩子们通过建立自己的子树递归地进行。这是迄今为止我们运行的算法的精确副本,除了我们使用vals
中的字符串作为新种子。要做到这一点,我们需要一个将列表分割成“选定值”列表的算法。喜欢的东西
selectEach :: [a] -> [(a, [a])]
,从而为每个值(c, extras)
这样elem (c, extras) (selectEach lst)
列表c:extras
拥有所有的lst
如果可能以不同的顺序相同的价值观。我会有点不同写这个功能,但是,由于
selectEach :: [a] -> [([a], a, [a])]
其中,结果可分为三个部分,例如,如果(before, here, after)
是一个值,然后elem (before, here, after) (selectEach lst)
lst == reverse before ++ [here] ++ after
。这将变成更容易
selectEach [] = []
selectEach (a:as) = go ([], a, as) where
go (before, here, []) = [(before, here, [])]
go (before, here, [email protected](a:as)) = (before, here, after) : go (here:before, a, as)
> selectEach "foo"
[("",'f',"oo"),("f",'o',"o"),("of",'o',"")]
这种辅助功能,我们可以很容易地生成我们的树的孩子一点点,但是我们最终将建立太多。
makeTree seed vals = Node seed children where
children = map (\(before, here, after) -> makeTree here (before ++ after))
(selectEach vals)
事实上太多了。如果我们运行
makeTree "sip" ["sour","piss","rune","profit","today","rat"]
我们生产规模1957年,而不是大小8的不错得心应手树,我们想的树。这是因为我们迄今为止已经消除了约束条件,即种子中的最后一个字母必须是所选值继续的第一个字母。我们将通过滤除坏树来解决这个问题。
goodTree :: String -> Tree String -> Bool
特别是,如果它遵循这个约束条件,我们会调用树“好”。给定种子值,如果树的根节点有一个值,其第一个字母与种子的最后一个字母相同,那么它是好的。
goodTree [] _ = False
goodTree seed Empty = False
goodTree seed (Node "" _) = False
goodTree seed (Node (h:_) _) = last seed == h
,我们将只过滤在此基础上绕圈
makeTree seed vals = Node seed children where
children =
filter goodTree
$ map (\(before, here, after) -> makeTree here (before ++ after))
$ selectEach
$ vals
,现在我们所做的孩子!
> makeTree "sip" ["sour","piss","rune","profit","today","rat"]
Node "sip"
[ Node "piss" [ Node "sour" [ Node "rune" []
, Node "rat" [ Node "today" [] ]
]
]
, Node "profit" [ Node "today" [] ]
]
完整的代码是:
selectEach :: [a] -> [([a], a, [a])]
selectEach [] = []
selectEach (a:as) = go ([], a, as) where
go (before, here, []) = [(before, here, [])]
go (before, here, [email protected](a:as)) = (before, here, after) : go (here:before, a, as)
data Tree a = Empty | Node a [Tree a] deriving Show
goodTree :: Eq a => [a] -> Tree [a] -> Bool
goodTree [] _ = False
goodTree seed Empty = False
goodTree seed (Node [] _) = False
goodTree seed (Node (h:_) _) = last seed == h
makeTree :: Eq a => [a] -> [[a]] -> Tree [a]
makeTree seed vals = Node seed children where
children =
filter (goodTree seed)
$ map (\(before, here, after) -> makeTree here (before ++ after))
$ selectEach
$ vals
而且这将会是值得如何selectEach
使用一种名为名单的拉链和makeTree
如何在Reader
单子操作读了。这两个都是巩固我在这里使用的方法的中间主题。
有趣的 - 完全不同的如何,我想接近它......用集关于的,难道不是比过滤列表更有效率? – beoliver
虽然我想我经常过滤集合) – beoliver
集合可能更有效率,但在这种情况下,我不需要这样的效率 - 每次选择种子时,我遍历剩余候选单词的每个列表。同样值得注意的是,由于懒惰,究竟有多少树展开。但是,在所有情况下,尽早优化可能会掩盖正确性所需的要点。 –