Erlang中CYK算法实现的代码回顾

Erlang中CYK算法实现的代码回顾,erlang,cyk,Erlang,Cyk,我开始学习Erlang,作为一种练习,我尝试实现 主代码(cyk.erl): %%%尝试在Erlang中使用CYK解析器 -模块(cyk)。 -出口([ 初始化存储/0, 导入语法文件/1, 添加语法规则/1, 分析/1, 测试/分析/0 ]). %%为语法初始化ets存储器 初始化存储()-> ets:新的(?模块,[bag,命名为_表])。 %%------------------------------------------ %% %%文法 %% %%-----------------

我开始学习Erlang,作为一种练习,我尝试实现

主代码(cyk.erl):

%%%尝试在Erlang中使用CYK解析器
-模块(cyk)。
-出口([
初始化存储/0,
导入语法文件/1,
添加语法规则/1,
分析/1,
测试/分析/0
]).
%%为语法初始化ets存储器
初始化存储()->
ets:新的(?模块,[bag,命名为_表])。
%%------------------------------------------
%% 
%%文法
%%
%%------------------------------------------
%%导入语法文件
导入语法文件(文件)->
{ok,Device}=文件:打开(文件,读取),
导入文件规则(设备)。
%%导入文件中的所有规则
导入文件规则(设备)->
案例io:get_行(设备“”)的
eof->
io:格式(“导入的语法文件~n”),
文件:关闭(设备);
行->
添加语法规则(行),
导入文件规则(设备)
结束。
%%添加语法规则
添加语法规则(规则)->
案例re:run(规则“^([^\s]+)\s?->\s?([^\n]+)$”,[{capture,除第一个外的所有字符,二进制代码}])
{匹配,[A,B]}->
ets:插入(?模,{A,B}),
io:format(“解析~p->~p~n”,[A,B]);
nomatch->
io:格式(“无法解析~p~n”,[Rule])
结束。
%%------------------------------------------
%% 
%%主要逻辑
%%
%%------------------------------------------
%%分析句子
分析(句子)->
io:格式(“分析:~p~n,[句子]),
单词列表=re:split(句子“”),
io:格式(“单词列表:~p~n,[wordlist]),
表示=列表:映射(乐趣(单词)->关联(单词)结束,单词列表),
io:格式(“表示:~p~n,[representation]),
结果=过程([表示]),
io:格式(“结果:~p~n,[result])。
%把句子词和语法词联系起来
联想(单词)->
case-ets:match(cyk,{'$1',Word})of
[H | T]->列表:展平([H | T]);
[] -> []
结束。
%过程句表征
过程(表示)->
限制=长度(列表:最后一个(表示)),
过程(表示、限制)。
当限制>1->时处理(表示、限制)
下一步=过程(表示,1,极限-1,[]),
过程([NextStep |表示],极限-1);
过程(表示、限制)->
代表性。
当索引=
子树=提取子树(列表:反向(表示)、索引),
结果=过程_子树(子树),
过程(表示、索引+1、限制、[结果| Acc]);
流程(表示、索引、限制、Acc)->
列表:反向(Acc)。
%%------------------------------------------
%% 
%%子树
%%
%%------------------------------------------
进程_子树(子树)->
进程_子树(子树,子树,[],1)。
进程_子树([],_子树,Acc,_索引)->
行政协调会;
进程|子树([H | T],子树,Acc,索引)->
A=列表:第n(1,H),
绑定=长度(子树)-索引+1,
B=列表:最后一个(列表:第n个(绑定,子树)),
%生成语法的可能性
Pos=[list_to_binary(binary:bin_to_list(X)+++++binary:bin_to_list(Y))|X
段=列表:子列表(H、位置、大小),
提取|u子树(T,大小-1,位置,[段| Acc])。
%%------------------------------------------
%% 
%%试验
%%使用与相同的示例
%% http://en.wikipedia.org/wiki/CYK_algorithm
%%
%%------------------------------------------
test_analyze()->
初始化存储(),
导入语法文件(“grammar.txt”),
分析(“她用叉子吃鱼”)。
语法文件(grammar.txt)

可以从erlangshell测试代码

> c(cyk).
> cyk:test_analyze().
parsing <<"S">> -> <<"NP VP">>
parsing <<"VP">> -> <<"VP PP">>
parsing <<"VP">> -> <<"V NP">>
parsing <<"VP">> -> <<"eats">>
parsing <<"PP">> -> <<"P NP">>
parsing <<"NP">> -> <<"Det N">>
parsing <<"NP">> -> <<"she">>
parsing <<"V">> -> <<"eats">>
parsing <<"P">> -> <<"with">>
parsing <<"N">> -> <<"fish">>
parsing <<"N">> -> <<"fork">>
parsing <<"Det">> -> <<"a">>
Grammar file imported
analysing: "she eats a fish with a fork"
wordlist: [<<"she">>,<<"eats">>,<<"a">>,<<"fish">>,<<"with">>,<<"a">>,
           <<"fork">>]
representation: [[<<"NP">>],
                 [<<"VP">>,<<"V">>],
                 [<<"Det">>],
                 [<<"N">>],
                 [<<"P">>],
                 [<<"Det">>],
                 [<<"N">>]]
result: [[[<<"S">>]],
         [[],[<<"VP">>]],
         [[],[],[]],
         [[<<"S">>],[],[],[]],
         [[],[<<"VP">>],[],[],[<<"PP">>]],
         [[<<"S">>],[],[<<"NP">>],[],[],[<<"NP">>]],
         [[<<"NP">>],
          [<<"VP">>,<<"V">>],
          [<<"Det">>],
          [<<"N">>],
          [<<"P">>],
          [<<"Det">>],
          [<<"N">>]]]
>c(cyk)。
>cyk:test_analyze()。
解析->
解析->
解析->
解析->
解析->
解析->
解析->
解析->
解析->
解析->
解析->
解析->
已导入语法文件
分析:“她用叉子吃鱼”
词表:[,,,,,,
]
代表:[[],
[,],
[],
[],
[],
[],
[]]
结果:[[]],
[[],[]],
[[],[],[]],
[[],[],[],[]],
[[],[],[],[],[]],
[[],[],[],[],[],[]],
[[],
[,],
[],
[],
[],
[],
[]]]
对于这个示例,代码似乎工作得很好,但我一直在寻找改进它的方法(使它更像erlang ish),特别是使处理分布在多个进程/节点上

我想每个步骤的所有进程_子树执行都可以并发完成,但我真的不知道如何执行


任何建议都将不胜感激!

我已经编写了这个使用并发执行的解决方案

与Eric解决方案相比,多进程的使用需要进行一些更改,另一些更改是因为我认为它更有效(我在规则集中还原了键和值,并且选择了一组),另一些更改是因为我认为它更干净(我在打开它的函数中关闭语法文件)还有一些是因为我更熟悉这些模块(字符串:令牌…)

[编辑]

我用更快的递归调用替换了无用的spawn,并通过添加消息来同步进程来抑制wait函数

我是在看了一个很好的动画之后才想到这个实现的

@Eric,可以查看分析的所有步骤打开ets analyze with observer,这就是我不删除它的原因

-module(cyk).

-export([
         import_grammar_file/1,
         add_grammar_rule/2,
         analyze/1,
         test_analyze/1,
         test_analyze/0
        ]).

%%------------------------------------------
%% 
%% Grammar
%%
%%------------------------------------------

%% Import a grammar file
import_grammar_file(File) ->
  reset_ets(rules, ets:info(rules)),
  {ok, Device} = file:open(File, read),
  ok = add_grammar_rule(Device,file:read_line(Device)),
  file:close(Device),
  io:format("Grammar file imported~n").

%% Add a grammar rule
add_grammar_rule(_,eof) -> ok;
add_grammar_rule(Device,{ok,Rule}) ->
  [T,"->",H|Q] = string:tokens(Rule," \n"),
  Key = key(H,Q),
  insert(Key,T,ets:lookup(rules, Key)),  
  add_grammar_rule(Device,file:read_line(Device)).

key(H,[]) -> H;
key(H,[Q]) -> {H,Q}.

insert(Key,T,[]) -> ets:insert(rules, {Key,[T]});
insert(Key,T,[{Key,L}]) -> ets:insert(rules, {Key,[T|L]}).


%%------------------------------------------
%% 
%% Main logic
%%
%%------------------------------------------

%% Analyze a sentence
analyze(Sentence) ->
  reset_ets(analyze, ets:info(analyze)),
  io:format("analysing: ~p~n", [Sentence]),
  WordList = string:tokens(Sentence, " "),
  Len = length(WordList),
  Me = self(),
  lists:foldl(fun(X,{J,Pid}) -> ets:insert(analyze,{{0,J},ets:lookup_element(rules,X,2)}),
                          (NewPid = spawn(fun() -> whatis(1,J,Len,Pid,Me) end)) ! {done,0},
                          {J+1,NewPid} end,
                        {1,none}, WordList),
  receive
    M -> M
  end.

reset_ets(Name, undefined) -> ets:new(Name,[set, named_table,public]);
reset_ets(Name, _) -> ets:delete_all_objects(Name).

whatis(Len,1,Len,_,PidRet) -> PidRet ! ets:lookup_element(analyze,{Len-1,1},2); % finished
whatis(I,J,Len,_,_) when I + J == Len +1 -> ok; % ends useless processes
whatis(I,J,Len,Pid,PidRet) ->
  receive {done,V} when V == I-1 -> ok end,
  Cases = lists:map(fun({X,Y}) -> [{A,B} || A <- ets:lookup_element(analyze,X,2), 
                                            B <- ets:lookup_element(analyze,Y,2)] end,
                         [{{X-1,J},{I-X,J+X}} || X <- lists:seq(1,I)]),
  Val = lists:foldl(fun(X,Acc) -> case ets:lookup(rules,X) of
                                      [] -> Acc;
                                      [{_,[R]}] -> [R|Acc]
                                      end end,
                                      [],lists:flatten(Cases)),
  ets:insert(analyze,{{I,J},Val}),
  send(Pid,I),
  whatis(I+1,J,Len,Pid,PidRet).

send(none,_) -> ok;
send(Pid,I) -> Pid ! {done,I}.

%%------------------------------------------
%% 
%% Test
%% using the same example as 
%% http://en.wikipedia.org/wiki/CYK_algorithm
%%
%%------------------------------------------
test_analyze(S) ->
  import_grammar_file("grammar.txt"),
  analyze(S).

test_analyze() ->
  test_analyze("she eats a fish with a fork").
-模块(cyk)。
-出口([
导入语法文件/1,
添加语法规则/2,
分析/1,
测试分析/1,
测试/分析/0
]).
%%------------------------------------------
%% 
%%文法
%%
%%------------------------------------------
%%导入语法文件
导入语法文件(文件)->
重置ets(规则,ets:info(规则)),
{ok,Device}=文件:打开(文件,读取),
确定=添加语法规则(设备,文件:读取行(设备)),
文件:关闭(设备),
io:格式(“导入的语法文件~n”)。
%%加一个g
-module(cyk).

-export([
         import_grammar_file/1,
         add_grammar_rule/2,
         analyze/1,
         test_analyze/1,
         test_analyze/0
        ]).

%%------------------------------------------
%% 
%% Grammar
%%
%%------------------------------------------

%% Import a grammar file
import_grammar_file(File) ->
  reset_ets(rules, ets:info(rules)),
  {ok, Device} = file:open(File, read),
  ok = add_grammar_rule(Device,file:read_line(Device)),
  file:close(Device),
  io:format("Grammar file imported~n").

%% Add a grammar rule
add_grammar_rule(_,eof) -> ok;
add_grammar_rule(Device,{ok,Rule}) ->
  [T,"->",H|Q] = string:tokens(Rule," \n"),
  Key = key(H,Q),
  insert(Key,T,ets:lookup(rules, Key)),  
  add_grammar_rule(Device,file:read_line(Device)).

key(H,[]) -> H;
key(H,[Q]) -> {H,Q}.

insert(Key,T,[]) -> ets:insert(rules, {Key,[T]});
insert(Key,T,[{Key,L}]) -> ets:insert(rules, {Key,[T|L]}).


%%------------------------------------------
%% 
%% Main logic
%%
%%------------------------------------------

%% Analyze a sentence
analyze(Sentence) ->
  reset_ets(analyze, ets:info(analyze)),
  io:format("analysing: ~p~n", [Sentence]),
  WordList = string:tokens(Sentence, " "),
  Len = length(WordList),
  Me = self(),
  lists:foldl(fun(X,{J,Pid}) -> ets:insert(analyze,{{0,J},ets:lookup_element(rules,X,2)}),
                          (NewPid = spawn(fun() -> whatis(1,J,Len,Pid,Me) end)) ! {done,0},
                          {J+1,NewPid} end,
                        {1,none}, WordList),
  receive
    M -> M
  end.

reset_ets(Name, undefined) -> ets:new(Name,[set, named_table,public]);
reset_ets(Name, _) -> ets:delete_all_objects(Name).

whatis(Len,1,Len,_,PidRet) -> PidRet ! ets:lookup_element(analyze,{Len-1,1},2); % finished
whatis(I,J,Len,_,_) when I + J == Len +1 -> ok; % ends useless processes
whatis(I,J,Len,Pid,PidRet) ->
  receive {done,V} when V == I-1 -> ok end,
  Cases = lists:map(fun({X,Y}) -> [{A,B} || A <- ets:lookup_element(analyze,X,2), 
                                            B <- ets:lookup_element(analyze,Y,2)] end,
                         [{{X-1,J},{I-X,J+X}} || X <- lists:seq(1,I)]),
  Val = lists:foldl(fun(X,Acc) -> case ets:lookup(rules,X) of
                                      [] -> Acc;
                                      [{_,[R]}] -> [R|Acc]
                                      end end,
                                      [],lists:flatten(Cases)),
  ets:insert(analyze,{{I,J},Val}),
  send(Pid,I),
  whatis(I+1,J,Len,Pid,PidRet).

send(none,_) -> ok;
send(Pid,I) -> Pid ! {done,I}.

%%------------------------------------------
%% 
%% Test
%% using the same example as 
%% http://en.wikipedia.org/wiki/CYK_algorithm
%%
%%------------------------------------------
test_analyze(S) ->
  import_grammar_file("grammar.txt"),
  analyze(S).

test_analyze() ->
  test_analyze("she eats a fish with a fork").