我是SML的新手,试图编写递归程序从字符串中删除字符:
remCharR: char * string -> string
到目前为止,已编写了此非递归编。需要帮助来编写递归代码。
- fun stripchars(string,chars) = let
= fun aux c =
= if String.isSubstring(str c) chars then
= ""
= else
= str c
= in
= String.translate aux string
= end
= ;
答案 0 :(得分:2)
您已经找到了一种非常惯用的方法。明确的递归本身并不是目标,除非是在学习环境中。就是说,与您当前的解决方案相比,显式递归在描述如何实现结果的机制的描述上受累,而对结果却不起作用。 >
这是通过转换为列表使用显式递归的一种方法:
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
函数产生一个字符串列表,然后将其连接。您可以做类似的事情。
因此,如果您使用字符串遍历( fold )替换要转换为list的初始转换,
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
函数不包含显式递归,而是依赖于可读性更高的高级函数。 / p>
更新: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_
,{{ 1}}),将翻译后的字符串复制到最终结果字符串中。
当您知道输入字符串中的每个字符将在输出字符串中导致0或1个字符时,您可以实现类似的优化。 blit_
函数不能,因为翻译的结果可以是多个字符。因此,替代实现使用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
使用递归,但是在其他方面与 for循环类似,后者可以更新数组的索引。因此,尽管它是递归的,但也不是很抽象。
考虑到您可以获得与使用fold_string
进行的优化相当的优化,而没有可变数组的底层复杂性,除非您开始遇到性能问题,否则我认为这样做不值得。