那么究竟什么是地址?当我考虑到树中特定节点的地址时,我会想到某种关于到达该节点的描述。由于您的树是 n-ary,因此地址可以是索引列表,例如
val someTree =
T [ (* address: [] *)
T [], (* address: [0] *)
T [ (* address: [1] *)
T [], (* address: [1,0] *)
T [] (* address: [1,1] *)
],
T [] (* address: [2] *)
]
因此,如果您的 datatype tree 类型被扩展为在某个节点中保存值,那么知道节点的地址将是导航到正确值的可靠方法。在那之前,这些地址在概念上仍然有意义,尽管它们可能不太有用。
[...] 父节点的所有地址列表,并通过附加所有子节点的地址,最终提供所有可能地址的列表。
假设我的解释是正确的,那么是的,你描述的策略很有道理。
您可以从解决树中的单个层开始,例如
fun range n = List.tabulate (n, fn i => i)
val zip = ListPair.zip
fun addresses (T ts) =
let val ts' = zip (ts, range (length ts))
in ts'
end
试试这个,
- addresses someTree;
> val it = [(T [], 0), (T [T [], T []], 1), (T [], 2)] : (tree * int) list
应该这么说
-
T [],第一个子树,所有子地址都应该以0 : ...为前缀。
-
T [T [], T []],第二个子树,所有子地址都应该以1 : ...为前缀。
-
T [],第三个子树,所有子地址都应该以2 : ...为前缀。
接下来我可能不得不想办法让addresses 为每个子树递归地调用自己。如果我们忽略上面的索引添加函数,递归可能看起来像:
fun addresses (T ts) =
List.concat (List.map (fn t => addresses t) ts)
其中List.map (fn t => ...) ts 寻址ts 中的每个子树T ...。因为addresses t 本身会产生一个列表,所以List.map (fn t => addresses t) ts 会产生一个列表。为了避免无限循环类型的列表列表类型...,List.concat 在每个递归步骤中将“...列表列表”折叠为“...列表”。
但是,运行这个,
- addresses someTree;
! Warning: Value polymorphism:
! Free type variable(s) at top level in value identifier it
> val it = [] : 'a list
非常没用,因为它实际上 不做任何事情,超出了每个节点的地址。
List.concat (List.map f xs) 模式很常见,所以让我们创建一个辅助函数来实现这两种功能,因为它会使代码更整洁。另外,让我们尝试结合使用当前节点的索引为每个子结果添加前缀的两种策略和对每个节点进行全递归遍历:
fun range n = List.tabulate (n, fn i => i)
fun concatMap f xs = List.concat (List.map f xs)
val zip = ListPair.zip
fun addresses (T ts) =
let val ts' = zip (ts, range (length ts))
in concatMap (fn (t, i) => ...) ts'
end
现在,... 部分应该同时执行 addresses t 以获取子结果列表,每个子结果都是索引列表,并且应该将 i 添加到每个列表的前面(例如map (fn addr => i :: addr))。
假设你解决了这部分,你可能仍然遇到生成零结果:
- addresses someTree;
> val it = [] : int list list
这是因为addresses (T [])实际上应该给[[]]:空树的所有地址列表是根节点的地址列表,根节点的地址被定义为(由我) []。所以[[]] 将是一个指向根节点的具有单个地址的列表。
解决这个问题,
fun addresses (T []) = [[]]
| addresses (T ts) = ...
您可以获得所有节点的完整地址列表:
- addresses someTree;
> val it = [[0], [1, 0], [1, 1], [2]] : int list list
我发现[] 不在此列表中,因此您可能需要添加它。
以上只是一个建议。有很多替代方法可以构建此解决方案。您可以使用显式递归辅助函数构建addresses,该函数将第二个参数作为索引:
fun addresses (T ts) = ...
and addresses_helper [] _ = []
| addresses_helper (t:ts) i =
map (fn addr => ...) (addresses t) @ addresses_helper ts (i+1)
或者您可以将其概括为foldl / foldr,就像您最初所做的那样。或者你可以定义一个zipWith 而不是concatMap (... map ...) (zip ...) 你可以有concat (zipWith ...):
fun range n = List.tabulate (n, fn i => i)
fun curry f x y = f (x, y)
fun zipWith f (x::xs) (y::ys) = f x y :: zipWith f xs ys
| zipWith _ _ _ = []
val concat = List.concat
fun addresses (T []) = [[]]
| addresses (T ts) =
let fun prefix t i = map (curry op:: i) (addresses t)
in concat (zipWith prefix ...)
end
我建议使用基于concatMap 或zipWith 的解决方案,而不是使用foldl / foldr 的解决方案,因为我认为它们更易于阅读。它们可能会导致一些额外的遍历,因为使用foldl / foldr 从技术上讲,您可以在递归而不是调用range 时构建i,但这是以可读性为代价的。