您已经找到了一种非常惯用的方法来执行此操作。显式递归本身并不是一个目标,除非是在学习环境中。也就是说,与您当前的解决方案相比,显式递归包含对如何实现结果的机制的描述,而不是结果是什么。 p>
这是一种通过转换为列表来使用显式递归的方法:
fun remCharR (c, s) =
let fun rem [] = []
| rem (c'::cs) =
if c = c'
then rem cs
else c'::rem cs
in implode (rem (explode s)) end
转换为列表(使用explode)效率低下,因为您可以迭代字符串的元素而无需创建相同元素的列表。但是,生成未删除字符的列表不一定是一个坏选择,因为使用不可变字符串,如果没有首先遍历字符串,您不知道最终结果将持续多长时间。 String.translate 函数生成一个字符串列表,然后将其连接起来。你可以做类似的事情。
所以如果用字符串遍历(折叠)替换初始转换为列表,
fun fold_string f e0 s =
let val max = String.size s
fun aux i e =
if i < max
then let val c = String.sub (s, i)
in aux (i+1) (f (c, e))
end
else e
in aux 0 e0 end
然后您可以创建一个基于字符串的 filter 函数(很像您已经找到的 String.translate 函数,但不太通用):
fun string_filter p s =
implode (fold_string (fn (c, res) => if p c then c::res else res) [] s)
fun remCharR (c, s) =
string_filter (fn c' => c <> c') s
除了,你会注意到,它意外地反转了字符串,因为它从左边折叠;您可以从右侧折叠(高效,但语义不同)或反转列表(低效)。我将把它作为练习留给你选择和改进。
如您所见,在避免 String.translate 的过程中,我构建了其他通用辅助函数,以便 remCharR 函数不包含显式递归,而是依赖于更具可读性的高级函数。
更新: String.translate 实际上做了一些非常聪明的事情。内存使用。
这是Moscow ML的String.translate版本:
fun translate f s =
Strbase.translate f (s, 0, size s);
Strbase.translate 看起来像:
fun translate f (s,i,n) =
let val stop = i+n
fun h j res = if j>=stop then res
else h (j+1) (f(sub_ s j) :: res)
in revconcat(h i []) end;
并使用辅助函数revconcat:
fun revconcat strs =
let fun acc [] len = len
| acc (v1::vr) len = acc vr (size v1 + len)
val len = acc strs 0
val newstr = if len > maxlen then raise Size else mkstring_ len
fun copyall to [] = () (* Now: to = 0. *)
| copyall to (v1::vr) =
let val len1 = size v1
val to = to - len1
in blit_ v1 0 newstr to len1; copyall to vr end
in copyall len strs; newstr end;
所以它首先通过将String.translate生成的每个子字符串的长度相加来计算最终字符串的总长度,然后它使用编译器内部的可变函数(mkstring_,blit_)进行复制将翻译后的字符串转化为最终的结果字符串。
当您知道输入字符串中的每个字符都会在输出字符串中产生 0 或 1 个字符时,您可以实现类似的优化。 String.translate 函数不能,因为翻译的结果可以是多个字符。所以另一种实现使用CharArray。例如:
-
求新字符串中的元素个数,
fun countP p s =
fold_string (fn (c, total) => if p c
then total + 1
else total) 0 s
-
构造一个临时的、可变的CharArray,更新它并将其转换为字符串:
fun string_filter p s =
let val newSize = countP p s
val charArr = CharArray.array (newSize, #"x")
fun update (c, (newPos, oldPos)) =
if p c
then ( CharArray.update (charArr, newPos, c) ; (newPos+1, oldPos+1) )
else (newPos, oldPos+1)
in fold_string update (0,0) s
; CharArray.vector charArr
end
fun remCharR (c, s) =
string_filter (fn c' => c <> c') s
您会注意到remCharR 是相同的,只是string_filter 的实现有所不同,这要归功于某种程度的抽象。此实现通过fold_string 使用递归,但在其他方面类似于更新数组索引的for 循环。所以虽然它是递归的,但也不是很抽象。
考虑到您可以获得与使用 String.translate 的优化相当的优化,而没有可变数组的低级复杂性,我认为除非您开始遇到性能问题,否则这是不值得的。