【问题标题】:SML program to delete char from string从字符串中删除字符的 SML 程序
【发布时间】:2019-02-18 21:51:11
【问题描述】:

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

【问题讨论】:

  • 把字符串当作字符,递归解应该更明显

标签: sml smlnj


【解决方案1】:

您已经找到了一种非常惯用的方法来执行此操作。显式递归本身并不是一个目标,除非是在学习环境中。也就是说,与您当前的解决方案相比,显式递归包含对如何实现结果的机制的描述,而不是结果是什么。 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 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_blit_)进行复制将翻译后的字符串转化为最终的结果字符串。

当您知道输入字符串中的每个字符都会在输出字符串中产生 0 或 1 个字符时,您可以实现类似的优化。 String.translate 函数不能,因为翻译的结果可以是多个字符。所以另一种实现使用CharArray。例如:

  1. 求新字符串中的元素个数,

    fun countP p s =
        fold_string (fn (c, total) => if p c
                                      then total + 1
                                      else total) 0 s
    
  2. 构造一个临时的、可变的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 的优化相当的优化,而没有可变数组的低级复杂性,我认为除非您开始遇到性能问题,否则这是不值得的。

【讨论】:

  • 感谢西蒙的详细解释
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-07-26
  • 2013-09-18
  • 2011-11-22
相关资源
最近更新 更多