SML程序从字符串中删除char

时间:2019-02-16 23:17:24

标签: sml smlnj

我是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
= ;

1 个答案:

答案 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 MLString.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。例如:

  1. 查找新字符串中的元素数,

    CharArray
  2. 构造一个临时的可变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进行的优化相当的优化,而没有可变数组的底层复杂性,除非您开始遇到性能问题,否则我认为这样做不值得。