您的问题有点过于宽泛,因为它询问的是用函数式语言实现此算法,但如何最好地做到这一点取决于语言。因此,鉴于您的示例代码,我的答案集中在 Erlang。
首先,请注意,不需要有单独的 patternCount 和 patternCount_ 函数。相反,您可以只拥有多个具有不同元数的 patternCount 函数以及具有相同元数的多个子句。首先,让我们重写您的函数以考虑到这一点,并将对 string:len/1 的调用替换为 length/1 内置函数:
patternCount(Text,Pattern) ->
patternCount(Text,Pattern,length(Pattern),0).
patternCount(Text,Pattern,PatternLength,Count) ->
case length(Text) < PatternLength of
true -> Count;
false ->
case string:equal(string:substr(Text,1,PatternLength),Pattern) of
true ->
patternCount(string:substr(Text,2),Pattern,PatternLength,Count+1);
false ->
patternCount(string:substr(Text,2),Pattern,PatternLength,Count)
end
end.
接下来,patternCount/4 函数中的多级缩进是一种“代码味道”,表明它可以做得更好。让我们将该函数拆分为多个子句:
patternCount(Text,Pattern,PatternLength,Count) when length(Text) < PatternLength ->
Count;
patternCount(Text,Pattern,PatternLength,Count) ->
case string:equal(string:substr(Text,1,PatternLength),Pattern) of
true ->
patternCount(string:substr(Text,2),Pattern,PatternLength,Count+1);
false ->
patternCount(string:substr(Text,2),Pattern,PatternLength,Count)
end.
第一个子句使用一个守卫来检测不可能有更多的匹配,而第二个子句寻找匹配。现在让我们重构第二个子句以使用 Erlang 的内置匹配。就像原始代码一样,我们希望一次通过输入文本一个元素,但我们也希望在这样做时检测匹配项。让我们在函数头中执行匹配,如下所示:
patternCount(_Text,[]) -> 0;
patternCount(Text,Pattern) ->
patternCount(Text,Pattern,Pattern,length(Pattern),0).
patternCount(Text,_Pattern,_Pattern,PatternLength,Count) when length(Text) < PatternLength ->
Count;
patternCount(Text,[],Pattern,PatternLength,Count) ->
patternCount(Text,Pattern,Pattern,PatternLength,Count+1);
patternCount([C|TextTail],[C|PatternTail],Pattern,PatternLength,Count) ->
patternCount(TextTail,PatternTail,Pattern,PatternLength,Count);
patternCount([_|TextTail],_,Pattern,PatternLength,Count) ->
patternCount(TextTail,Pattern,Pattern,PatternLength,Count).
首先,请注意我们在底部的四个子句中添加了一个新参数:我们现在将Pattern 作为第二个和第三个参数传递,以允许我们使用其中一个进行匹配,而其中一个保持原始模式,如下面更全面的解释。另请注意,我们在最顶部添加了一个新子句来检查空的Pattern,在这种情况下只返回 0。
让我们只关注底部的三个patternCount/5 子句。这些子句在运行时按顺序尝试,但让我们先看这三个子句中的第二个,然后是第三个子句,然后是三个子句中的第一个:
1234563列表。我们对两个列表的头部使用相同的变量,这意味着如果两个列表的第一个元素相等,我们就有可能进行匹配,因此我们递归调用patternCount/5,将列表的尾部作为第一个传递两个论点。传递尾部允许我们在输入文本和模式中一次前进一个元素,检查匹配的元素。
在最后一个子句中,前两个参数的头部不匹配;如果他们这样做了,运行时将执行第二个子句,而不是这个。这意味着我们的模式匹配失败了,所以我们不再关心第一个参数的第一个元素,也不再关心第二个参数,我们必须通过输入文本来寻找新的匹配。请注意,我们将输入文本的开头和第二个参数都写为_“不关心”变量,因为它们对我们不再重要。我们递归调用patternCount/5,将输入文本的尾部作为第一个参数,将完整的Pattern 作为第二个参数,让我们开始寻找新的匹配。
在这三个子句中的第一个子句中,第二个参数是空列表,这意味着我们通过成功匹配完整的Pattern,逐个元素地到达这里。所以我们递归调用patternCount/5,传递完整的Pattern作为第二个参数开始寻找新的匹配,我们也增加匹配计数。
试试吧!这是完整的修订模块:
-module(test).
-export([read_text/1,pattern_count/2,main/0]).
read_text(FileName) ->
{ok,File} = file:read_file(FileName),
unicode:characters_to_list(File).
pattern_count(_Text,[]) -> 0;
pattern_count(Text,Pattern) ->
pattern_count(Text,Pattern,Pattern,length(Pattern),0).
pattern_count(Text,_Pattern,_Pattern,PatternLength,Count)
when length(Text) < PatternLength ->
Count;
pattern_count(Text,[],Pattern,PatternLength,Count) ->
pattern_count(Text,Pattern,Pattern,PatternLength,Count+1);
pattern_count([C|TextTail],[C|PatternTail],Pattern,PatternLength,Count) ->
pattern_count(TextTail,PatternTail,Pattern,PatternLength,Count);
pattern_count([_|TextTail],_,Pattern,PatternLength,Count) ->
pattern_count(TextTail,Pattern,Pattern,PatternLength,Count).
main() ->
pattern_count(read_text("file.txt"),"hello").
一些最终建议:
逐个元素地搜索文本比必要的要慢。您应该查看Boyer-Moore algorithm 和其他相关算法,以了解在较大块中推进文本的方法。例如,Boyer-Moore 尝试首先在模式的 end 处进行匹配,因为如果不匹配,它可以在文本中前进与模式的全长一样多。
-
您可能还想研究使用 Erlang 二进制文件而不是列表,因为它们在内存方面更紧凑并且they allow for matching more than just their first elements。例如,如果Text 是二进制输入文本,Pattern 是二进制模式,假设Text 的大小等于或大于Pattern 的大小,则此代码尝试匹配整个模式:
case Text of
<<Pattern:PatternLength/binary, TextTail/binary>> = Text ->
patternCount(TextTail,Pattern,PatternLength,Count+1);
<<_/binary,TextTail/binary>> ->
patternCount(TextTail,Pattern,PatLen,Count)
end.
请注意,此代码 sn-p 恢复为使用 patternCount/4,因为我们不再需要额外的 Pattern 参数来逐个元素地工作。
如完整修改后的模块所示,在同一个模块中调用函数时,不需要模块前缀。请参阅简化的main/0 函数。
如完整修改后的模块所示,传统的 Erlang 风格不使用混合大小写的函数名称,如 patternCount。大多数 Erlang 程序员会改用pattern_count。