我试图实现一个滑动窗口算法来匹配文本文件中的单词。我来自程序背景,我在Erlang这样的函数式语言中首次尝试这样做似乎需要时间O(n ^ 2)(甚至更多)。如何用函数式语言做到这一点?
-module(test).
-export([readText/1,patternCount/2,main/0]).
readText(FileName) ->
{ok,File} = file:read_file(FileName),
unicode:characters_to_list(File).
patternCount(Text,Pattern) ->
patternCount_(Text,Pattern,string:len(Pattern),0).
patternCount_(Text,Pattern,PatternLength,Count) ->
case string:len(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.
main() ->
test:patternCount(test:readText("file.txt"),"hello").
答案 0 :(得分:5)
您的问题有点过于宽泛,因为它要求在函数式语言中实现此算法,但最好的方法是依赖于语言。因此,我的答案主要针对Erlang,给出了您的示例代码。
首先,请注意,不需要单独的patternCount
和patternCount_
函数。相反,您可以只使用具有不同arities的多个patternCount
函数以及同一arity的多个子句。首先,让我们重写您的函数以将其考虑在内,并使用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.
第一个子句使用guard来检测不可能有更多匹配,而第二个子句查找匹配。现在让我们重构第二个子句以使用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
子句。这些子句在运行时按顺序尝试,但让我们首先看看这三个子句中的第二个,然后是第三个子句,然后是三个子句中的第一个:
在这三个子句的第二个中,我们用[Head|Tail
]列表符号写出第一个和第二个参数,这意味着Head
是列表的第一个元素,{{1}是列表的其余部分。我们对两个列表的头部使用相同的变量,这意味着如果两个列表的第一个元素相等,我们正在进行潜在匹配,因此我们递归调用Tail
将列表的尾部传递为前两个论点。传递尾部允许我们一次通过输入文本和模式元素,检查匹配元素。
在最后一个子句中,前两个参数的头部不匹配;如果他们这样做,运行时将执行第二个子句,而不是这个子句。这意味着我们的模式匹配失败了,因此我们不再关心第一个参数的第一个元素,也不关心第二个参数,我们必须通过输入文本来寻找新的匹配。请注意,我们将输入文本的头部和第二个参数都写为patternCount/5
“不关心”变量,因为它们对我们来说不再重要。我们递归调用_
,将输入文本的尾部作为第一个参数传递,将完整的patternCount/5
作为第二个参数传递,允许我们开始寻找新的匹配。
在这三个子句的第一个中,第二个参数是空列表,这意味着我们已经通过逐个元素成功匹配完整的Pattern
来实现。所以我们递归调用Pattern
传递完整的patternCount/5
作为第二个参数开始寻找新的匹配,我们也增加了匹配次数。
试试吧!这是完整的修订模块:
Pattern
一些最终建议:
逐个元素地搜索文本比必要的慢。您应该查看Boyer-Moore algorithm和其他相关算法,以了解在更大的块中推进文本的方法。例如,Boyer-Moore首先尝试匹配模式的 end ,因为如果不匹配,它可以通过文本前进到模式的整个长度。
您可能还希望使用Erlang二进制文件而不是列表,因为它们在内存方面更加紧凑且they allow for matching more than just their first elements。例如,如果-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").
是输入文本作为二进制文件而Text
是模式作为二进制文件,并假设Pattern
的大小等于或大于{{Text
的大小1}},此代码尝试匹配整个模式:
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.
,因为我们不再需要额外的patternCount/4
参数来逐个元素地工作。
如完整修订模块所示,在同一模块中调用函数时,不需要模块前缀。请参阅简化的Pattern
函数。
如完整修订模块所示,传统的Erlang样式不使用main/0
等混合大小写函数名称。大多数Erlang程序员会使用patternCount
代替。