在没有库函数的情况下,查找字符串是否是Sml中另一个字符串的子字符串

时间:2019-03-12 04:21:17

标签: string recursion substring sml ml

我正在尝试编写一个函数subString:string * string-> int 检查第一个字符串是否是第二个字符串的子字符串,并且区分大小写。

如果第一个字符串是子字符串,我想返回从0开始的索引,如果不是则返回-1。如果它出现多次,则返回第一次出现的索引。

例如:

subString("bc","abcabc") ===>1
subString("aaa","aaaa") ===>0
subString("bc","ABC") ===>-1

由于我不太熟悉sml或在sml中使用字符串,并且不应该使用诸如String.sub之类的任何内置函数,因此我在解决问题时遇到了很多麻烦。

我可以使用辅助功能。

我能想到的就是在助手函数中使用explode,然后以某种方式检查列表,然后将其内爆,但是如何获得索引位置呢?

我只有

fun subString(s1,s2) =
     if null s2 then ~1
     else if s1 = s2 then 0
     else 1+subString(s1, tl s2);

我正在考虑使用一个辅助函数,该函数会爆炸字符串,然后将两者进行比较,但是我不知道如何使它起作用。

2 个答案:

答案 0 :(得分:1)

这已经是一个很好的开始,但是存在一些小问题:

在递归情况下,即使递归应用程序未找到子字符串并返回-1,也向递归结果添加1。在添加1之前,应检查结果是否为-1。

在第二行中,检查两个字符串是否相等。如果执行此操作,则只有在字符串以该子字符串结尾的情况下,您才会找到该子字符串。因此,您在第2行中真正想要做的是测试s2是否以s1开头。我建议您编写一个执行该测试的辅助函数。对于此辅助功能,您确实可以使用explode,然后递归检查列表的第一个字符是否相同。 有了此辅助函数后,请在第2行中使用它,而不要使用相等性测试。

答案 1 :(得分:0)

  

我不应该使用任何内置功能,例如String.sub

太可惜了!由于字符串具有抽象接口,而带有列表的字符串可以直接访问其主要构造函数[]::,因此您必须使用库函数来获取 anywhere < / em>与字符串。 explode也是一个库函数。但是好吧,如果您的约束是必须将字符串转换为列表以解决该练习,就这样吧。

给出您当前的代码,

fun subString(s1,s2) =
     if null s2 then ~1
     else if s1 = s2 then 0
     else 1+subString(s1, tl s2);

我在这里遇到一个问题:

   subString ([#"b",#"c"], [#"a",#"b",#"c",#"d"])
~> if null ([#"a",#"b",#"c",#"d"]) then ... else
   if [#"b",#"c"] = [#"a",#"b",#"c",#"d"] then ... else
   1 + subString([#"b",#"c"], [#"b",#"c",#"d"])

~> 1 + subString([#"b",#"c"], [#"b",#"c",#"d"])
~> 1 + if null ([#"b",#"c",#"d"]) then ... else
       if [#"b",#"c"] = [#"b",#"c",#"d"] then ... else
       1 + subString([#"b",#"c"], [#"c",#"d"])

支票s1 = s2似乎还不够:我们应该喜欢说[#"b",#"c"][#"b",#"c",#"d"]的子字符串,因为它是它的前缀,而不是因为当量。使用s1 = s2,您最终将检查某项内容是否是有效的后缀,而不是有效的 substring 。因此,您需要将s1 = s2更改为更智能的内容。

也许您可以构建一个辅助函数来确定一个列表是否是另一个列表的前缀,然后在此处使用它?


关于通过explode将字符串分成列表来解决此问题的方法:这种方法效率极低,以至于标准ML的姐妹语言Ocaml从库中获得了explode entirely removed

  

函数explodeimplode在旧版本的Caml中,但是我们从OCaml中省略了它们,因为它们会鼓励低效率的代码。将字符串视为字符列表通常是一个坏主意,并且将其视为字符数组更适合实际实现。

首先,String.isSubstring already exists,所以这是一个已解决的问题。但是,如果不是这样的话,并且有人想用结构写这个,而String.sub就是在作弊(它访问的是字符串中的字符,相当于通过{{1 }}),然后让我鼓励您编写高效,可组合且功能强大的代码:

x::xs

这里的总体思路是抽象构建算法,您可以在构建使用列表递归而不是字符串索引递归的(* Check that a predicate holds for all (c, i) of s, where * s is a string, c is every character in that string, and * i is the position of c in s. *) fun alli s p = let val stop = String.size s fun go i = i = stop orelse p (String.sub (s, i), i) andalso go (i + 1) in go 0 end (* needle is a prefix of haystack from the start'th index *) fun isPrefixFrom (needle, haystack, start) = String.size needle + start <= String.size haystack andalso alli needle (fn (c, i) => String.sub (haystack, i + start) = c) (* needle is a prefix of haystack if it is from the 0th index *) fun isPrefix (needle, haystack) = isPrefixFrom (needle, haystack, 0) (* needle is a substring of haystack if is a prefix from any index *) fun isSubstring (needle, haystack) = let fun go i = String.size needle + i <= String.size haystack andalso (isPrefixFrom (needle, haystack, i) orelse go (i + 1)) in go 0 end 时重用它,即:isSubstring是{{ 1}}可以用更简单的术语定义,即needlehaystack的前缀,从needle中的任何有效位置开始计数(当然不能超过haystack)。而且,更容易确定列表是否是前缀!

此建议将为您提供模板,

haystack

对于优化字符串索引递归解决方案,您可以通过将haystack设为只能访问{{1}的局部函数,来避免在fun isPrefix ([], _) = ... | isPrefix (_, []) = ... | isPrefix (x::xs, y::ys) = ... fun isSubstring ([], _) = ... | isSubstring (xs, ys) = ... isPrefix ... orelse ... isPrefixFrom中进行双重边界检查。 }和isSubstring;否则会不安全。

对此进行测试

isPrefixFrom