HỌC VIỆN KỸ THUẬT QUÂN SỰ
KHOA CÔNG NGHỆ THÔNG TIN
o0o
BÀI TẬP LỚN
Môn: Automata và ngôn ngữ hình thức
Đề tài: Viết chương trình phân tích cú pháp theo phương pháp Earley.
Có trình bày các bước tính toán và dẫn xuất nếu có.
Giáo viên hướng dẫn:
Nhóm SV thực hiện:
1. Vương Thị Tuyến
2. Lưu Thị Thảnh
3. Nguyễn Huy Linh
Lớp: Tin Học 9A
Hà Nội, tháng 01 năm 2013
MỤC LỤC
1. Tóm tắt
Giải thuật Earley là một giải thuật cơ bản, được sử dụng tương đối rộng rãi trong
các hệ thống phân tích cú pháp. Tuy nhiên, giải thuật này vẫn còn hạn chế như
2
sinh ra quá nhiều luật dư thừa trong quá trình phân tích. Trong bài này, chúng tôi
đề xuất ra phương pháp phân tích cú pháp theo giải thuật Earley.
Giải thuật Earley là một trong những giải thuật được sử dụng phổ biến trong việc
xây dựng các hệ thống phân tích cú pháp. Giải thuật này sử dụng chiến lược phân
tích kiểu trên xuống (top-down), bắt đầu với một ký hiệu không kết thúc đại diện
cho câu và sử dụng các luật khai triển cho đến khi thu được câu vào. Hạn chế của
cách tiếp cận này là không chú trọng nhiều đến các từ đầu vào. Vì vậy trong quá
trình phân tích, giải thuật Earley sản sinh ra rất nhiều luật dư thừa.Ngoài ra, giải
thuật Earley được xây dựng cho tiếng Anh nên khi áp dụng cho tiếng Việt sẽ có
hạn chế. Mỗi câu vào tiếng Anh chỉ có một cách tách từ, trong khi với tiếng Việt,
mỗi câu vào có thể có nhiều cách tách từ khác nhau. Với đặc điểm đầu vào của
giải thuật Earley chỉ là một câu với một cách tách, bộ phân tích cú pháp sẽ phải
thực hiện lặp đi lặp lại giải thuật này cho từng trường hợp tách từ đối với tiếng
Việt. Để giải quyết vấn đề này, chúng tôi nhận thấy trong các cách tách từ Việt tồn
tại các cặp cách tách giống nhau ở danh sách các từ loại đầu tiên và chỉ khác nhau
ở phần đuôi của chúng.
Giải thuật Earley cơ bản, giúp người đọc có thể hình dung một cách khái quát về
giải thuật này.
3
2. Giải thuật Earley
Giải thuật Earley cơ bản được phát biểu như sau:
Đầu vào: Văn phạm G = (N, T, S, P), trong đó:
• N: tập kí hiệu không kết thúc.
• T: tập kí hiệu kết thúc.
• S: kí hiệu không kết thúc bắt đầu.
• P: tập luật cú pháp.
Xâu vào w = a1a2 an.
Đầu ra: Phân tích đối với w hoặc "sai".
Kí hiệu:
• α, β, γ biểu diễn xâu chứa các kí hiệu kết thúc, không kết thúc hoặc rỗng.
• X, Y, Z biểu diễn các kí hiệu không kết thúc đơn.
• a biểu diễn kí hiệu kết thúc.
Earley sử dụng cách biểu diễn luật thông qua dấu chấm “• ”
X→ α • β có nghĩa :
• Trong P có một luật sản xuất X→ α β.
• α đã được phân tích.
• β đang được chờ phân tích.
• Khi dấu chấm “ • ” được chuyển ra sau β có nghĩa đây là một luật hoàn thiện.
Thành phần X đã được phân tích đầy đủ, ngược lại nó là một luật chưa hoàn thiện.
Đối với mỗi từ thứ j của xâu đầu vào, bộ phân tích khởi tạo một bộ có thứ tự các
trạng thái S(j).
Mỗi bộ tương ứng với một cột trong bảng phân tích. Mỗi trạng thái có dạng (X →
α • β, i), thành phần sau dấu phẩy xác định rằng luật này được phát sinh từ cột thứ
i.
a.Khởi tạo
• S(0) được khởi tạo chứa ROOT → • S.
• Nếu tại bộ cuối cùng ta có luật (ROOT → S•, 0) thì có nghĩa xâu vào được
phân tích thành công.
4
b. Thuật toán
Thuật toán phân tích thực hiện 3 bước: Dự đoán (Predictor), Duyệt (Scanner), và
Hoàn thiện (Completer) đối với mỗi bộ S(j).
+) Dự đoán
Với mọi trạng thái trong S(j): (X → α • Y β, i), ta thêm trạng thái (Y → • γ, j)
vào S(j) nếu có luật sản xuất Y → γ trong P.
+) Duyệt
Nếu a là kí hiệu kết thúc tiếp theo. Với mọi trạng thái trong S(j): (X → α • a β,
i), ta thêm trạng thái (X → α a • β, i) vào S(j+1).
+) Hoàn thiện
Với mọi trạng thái trong S(j): (X → γ• , i), ta tìm trong S(i) trạng thái (Y → α •
X β, k), sau đó thêm (Y → α X • β, k) vào S(j).
Ở mỗi bộ S(j) phải kiểm tra xem trạng thái đã có chưa trước khi thêm vào để
tránh trùng lặp.
Để minh họa cho thuật toán trên, chúng ta phân tích câu “học sinh học sinh
học” với tập luật cú pháp sau:
S → N VP
S → P VP
S → N AP
S → VP AP
VP → V N
VP → V NP
NP → N N
NP → N A
AP → R A
N → học sinh
N → sinh học
V → học
V → sinh
Trong đó:
S – câu
VP – cụm động từ
NP – cụm danh từ
AP – cụm tính từ
P – đại từ
N – danh từ
V – động từ
A – tính từ
R – phụ từ
5
Do câu trên có nhiều cách tách từ, trong khi đầu vào của giải thuật Earley chỉ là
một câu với một cách tách từ nên chúng tôi minh họa giải thuật Earley với cách
tách từ trong trường hợp câu được phân tích là: học sinh, học, sinh học.
Bảng phân tích cho cách tách này như sau:
Cột 0
1 2 3
ROOT • S, 0 N học sinh•, 0 V học•, 1 N sinh học•, 2
S •N VP, 0 S N •VP, 0 VP V •N, 1 VP V N•, 1
S •P VP, 0 S N •AP, 0 VP V •NP, 1 NP N •N, 2
S •N AP, 0 VP •V N, 1 NP •N N, 2 NP N •A, 2
S •VP AP, 0 VP •V NP, 1 NP •N A, 2 S N VP•, 0
VP •V N, 0 AP •R A, 1 N •học sinh, 2 ROOT S•, 0
VP •V NP, 0
V •học, 1 N •sinh học, 2
N •học sinh, 0
N •sinh học, 0
V •học, 0
Bảng 1. Bảng minh họa giải thuật Earley
6
3. Chương trình phân tích cú pháp câu theo phương pháp Early Parser
#include <iostream>
#include <fstream>
#include <map>
#include <vector>
#include <string>
using namespace std;
#define noun 1
#define verb_i 2
#define verb_t 4
#define adj 8
#define aux 16
#define pro 32
#define det 64
#define prep 128
#define SENT 256
#define NP 257
#define VP 258
#define PP 259
#define NP2 260
#define NP3 261
#define LHS 0
#define NUMRHS 1
#define DOT 2
#define TABLE 3
void nhapDanhTu(map<string, unsigned int> &TL)
{
fstream fin;
fin.open("noun.txt");
int n;
fin >> n;
string str;
for (int i = 0; i < n; i++)
{
fin >> str;
TL[str] = TL[str] | noun;
}
fin.close();
}
void nhapNoiDongTu(map<string, unsigned int> &TL)
{
fstream fin;
fin.open("verb_i.txt");
7
int n;
fin >> n;
string str;
for (int i = 0; i < n; i++)
{
fin >> str;
TL[str] = TL[str] | verb_i;
}
fin.close();
}
void nhapNgoaiDongTu(map<string, unsigned int> &TL)
{
fstream fin;
fin.open("verb_t.txt");
int n;
fin >> n;
string str;
for (int i = 0; i < n; i++)
{
fin >> str;
TL[str] = TL[str] | verb_t;
}
fin.close();
}
void nhapTinhTu(map<string, unsigned int> &TL)
{
fstream fin;
fin.open("adj.txt");
int n;
fin >> n;
string str;
for (int i = 0; i < n; i++)
{
fin >> str;
TL[str] = TL[str] | adj;
}
fin.close();
}
void nhapGioiTu(map<string, unsigned int> &TL)
{
fstream fin;
fin.open("prep.txt");
int n;
fin >> n;
8
string str;
for (int i = 0; i < n; i++)
{
fin >> str;
TL[str] = TL[str] | prep;
}
fin.close();
}
void nhapMaoTu(map<string, unsigned int> &TL)
{
fstream fin;
fin.open("det.txt");
int n;
fin >> n;
string str;
for (int i = 0; i < n; i++)
{
fin >> str;
TL[str] = TL[str] | det;
}
fin.close();
}
void nhapDaiTu(map<string, unsigned int> &TL)
{
fstream fin;
fin.open("pro.txt");
int n;
fin >> n;
string str;
for (int i = 0; i < n; i++)
{
fin >> str;
TL[str] = TL[str] | pro;
}
fin.close();
}
void nhapTroDongTu(map<string, unsigned int> &TL)
{
fstream fin;
fin.open("auxi.txt");
int n;
fin >> n;
string str;
9
for (int i = 0; i < n; i++)
{
fin >> str;
TL[str] = TL[str] | aux;
}
fin.close();
}
void nhapDanhSachLuat(vector<int> Luat[], int &iNumRule)
{
map<string, int> BangTra;
BangTra["noun"] = noun;
BangTra["verb_i"] = verb_i;
BangTra["verb_t"] = verb_t;
BangTra["adj"] = adj;
BangTra["prep"] = prep;
BangTra["pro"] = pro;
BangTra["aux"] = aux;
BangTra["det"] = det;
BangTra["S"] = SENT;
BangTra["NP"] = NP;
BangTra["VP"] = VP;
BangTra["PP"] = PP;
BangTra["NP2"] = NP2;
BangTra["NP3"] = NP3;
fstream fin;
char str[100];
fin.open("Rules.txt");
fin >> iNumRule;
fin.getline(str, 100, '\n');
for (int i = 0; i < iNumRule; i++)
{
fin.getline(str, 100, '\n');
int j = 0;
int k;
char* p = strtok(str, "-> ,+");
while (p != NULL)
{
if (j == 0)
{
j++;
k = BangTra[p];
Luat[i].push_back(k);
Luat[i].push_back(0);
Luat[i].push_back(0);
Luat[i].push_back(0);
}
else
{
Luat[i].push_back(BangTra[p]);
10
Luat[i][1]++;
}
p = strtok(NULL, "-> ,+");
}
}
fin.close();
}
void nhapCauInput(vector<string> &Cau)
{
Cau.clear();
char str[100];
gets(str);
char* p = strtok(str, " ");
while ( p != NULL)
{
Cau.push_back(p);
p = strtok(NULL, " ");
}
}
bool isTerminal(int term)
{
return (term < 128);
}
bool DauChamCuoiCau(vector<int> &Luat)
{
return (Luat[DOT] == Luat[NUMRHS]);
}
// Tu loa 1 va tu loai 2 phai la cac terminal
bool CungTuLoai(int TuLoai1, int TuLoai2)
{
if ( (TuLoai1 & TuLoai2) != 0)
return true;
else
return false;
}
void thuatToan(vector<string> Cau, vector<int> Luat[], map<string,
unsigned int> &TuLoai, int iNumRule, vector<vector<int> > Table[])
{
/*
Trong khi chua ket thuc cau
Lay tu do ra,
kiem tra tu loai cua tu do
Tim trong bang truoc do nhung cau co chi so la n-1
Neu tu loai sau dau cham = tu loai cua tu dang xet
Dich chuyen cac dau cham, nho cap nhat lai chi
so cua cau
Voi moi cau vua cap nhat lai chi so
Neu dau cham truoc non-terminal
thi trien khai
else Neu dau cham truoc terminal
11
thi khong trien khai
else Neu dau cham nam o cuoi cung
Backtracking
*/
int i;
int n = Cau.size();
// Init
i = 0;
while (i < iNumRule)
{
if (Luat[i][LHS] == SENT)
{
//Luat[i][TABLE] = -1;
Table[0].push_back(Luat[i]);
}
i++;
}
int j = 0;
int num = Table[0].size();
vector<int> DSKhaiTrien;
while(j < num)
{
int pos = Table[0][j][DOT];
int iTuLoai = Table[0][j][pos+4];
int flag = 0;
for (int l = 0; l < DSKhaiTrien.size(); l++)
if (DSKhaiTrien[l] == iTuLoai)
flag = 1;
if ( flag == 0 && !isTerminal(iTuLoai))
{
DSKhaiTrien.push_back(iTuLoai);
for(int k = 0; k < iNumRule; k++)
{
if (Luat[k][LHS] == iTuLoai)
{
vector<int> temp (Luat[k]);
temp[TABLE] = 0;
Table[0].push_back(temp);
}
}
}
j++;
}
DSKhaiTrien.clear();
////////////////////////////////////////////////////////////////////////
//////////////////
int flag;
12
// Vong lap thuat toan
i = 1;
while (i <= n)
{
// Lay tu ra
string Word = Cau[i-1];
// Kiem tra trong bang i-1 xem phan tu nao cung tu loai
j = 0;
int num = 0;
// Trong khi ma chua xet het cac bieu thuc trong Table(i-1)
while (j < Table[i-1].size())
{
// Neu bieu thuc thuoc bang i-1
if (Table[i-1][j][TABLE] == i-1)
{
int pos = Table[i-1][j][DOT] + 4;
// Vi tri cua dau cham
//int temp1 = Table[i-1][j][pos];
//int temp2 = TuLoai[Word];
if (CungTuLoai(Table[i-1][j][pos],
TuLoai[Word])) // Neu Tu loai trung nhau
{
// Dich chuyen cac dau cham, nho cap nhat
lai chi so cua cau
Table[i].push_back(Table[i-1][j]);
// Them cau vao bang moi
Table[i][num][DOT]++;
// Dich chuyen vi tri cua dau cham sang ben phai
num++;
}
}
j++;
}
// Voi moi cau vua cap nhat lai chi so
j = 0;
// Neu dau cham truoc non-terminal
while (j < num)
{
int pos = Table[i][j][DOT];
if (pos < Table[i][j][NUMRHS])
{
int iTuLoai = Table[i][j][pos+4];
flag = 0;
for (int l = 0; l < DSKhaiTrien.size(); l++)
if (DSKhaiTrien[l] == iTuLoai)
flag = 1;
if (flag == 0 && ! isTerminal(iTuLoai))
{
DSKhaiTrien.push_back(iTuLoai);
for(int k = 0; k < iNumRule; k++)
{
if (Luat[k][LHS] == iTuLoai)
13
{
vector<int> temp (Luat[k]);
temp[TABLE] = i;
Table[i].push_back(temp);
}
}
}
}
j++;
}
DSKhaiTrien.clear();
// Neu dau cham truoc terminal
j = 0;
while(j < num)
{
int pos = Table[i][j][DOT];
if (pos < Table[i][j][NUMRHS])
{
int iTuLoai = Table[i][j][pos+4];
if ( isTerminal(iTuLoai))
{
vector<int> temp(Table[i][j]);
temp[TABLE] = i;
Table[i].push_back(temp);
}
}
j++;
}
// Neu dau cham nam o vi tri cuoi cung
j = 0;
vector<vector<int> > Stack;
while (j < num)
{
int pos = Table[i][j][DOT];
if (pos == Table[i][j][NUMRHS])
{
vector<int> temp(Table[i][j]);
Stack.push_back(temp);
while(Stack.size() != 0)
{
temp = Stack[Stack.size()-1];
Stack.pop_back();
int k = temp[TABLE];
for (int l = 0; l < Table[k].size() &&
temp[LHS] != SENT; l++)
{
pos = Table[k][l][DOT] + 4;
if (Table[k][l][pos] == temp[LHS] &&
(Table[k][l][TABLE] == k-1 || Table[k][l][LHS] == SENT))
{
vector<int> LuatPhatSinh =
Table[k][l]; ////////
LuatPhatSinh[DOT]++;
Stack.push_back(LuatPhatSinh);
Table[i].push_back(LuatPhatSinh);
14
}
}
}
}
j++;
}
// Trien khai tiep tuc nhung luat moi them vao
for (j = num; j < Table[i].size(); j++)
{
// Neu chi so bang nho hon chi so bang hien tai
if (Table[i][j][TABLE] < i )
{
int pos = Table[i][j][DOT];
// Neu dau cham chua nam o vi tri cuoi cung
if (pos < Table[i][j][NUMRHS])
{
int iTuLoai = Table[i][j][pos+4];
flag = 0;
for (int l = 0; l < DSKhaiTrien.size(); l+
+)
if (DSKhaiTrien[l] == iTuLoai)
flag = 1;
if (flag == 0 &&! isTerminal(iTuLoai))
{
DSKhaiTrien.push_back(iTuLoai);
for(int k = 0; k < iNumRule; k++)
{
if (Luat[k][LHS] == iTuLoai)
{
vector<int> temp
(Luat[k]);
temp[TABLE] = i;
Table[i].push_back(temp);
}
}
}
}
}
}
DSKhaiTrien.clear();
i++;
}
}
void testInput(vector<string> &Cau, map<string, unsigned int> TuLoai,
vector<int> Luat[], int &iNumRule)
{
int n;
// Test Danh sach luat
map<int, string> BangTra;
BangTra[noun] = "noun";
BangTra[verb_i] = "verb_i";
15
BangTra[verb_t] = "verb_t";
BangTra[adj] = "adj";
BangTra[prep] = "prep";
BangTra[pro] = "pro";
BangTra[aux] = "aux";
BangTra[det] = "det";
BangTra[SENT] = "SENT";
BangTra[NP] = "NP";
BangTra[VP] = "VP";
BangTra[PP] = "PP";
BangTra[NP2] = "NP2";
BangTra[NP3] = "NP3";
for (int i = 0; i < iNumRule; i++)
{
vector<int>::iterator j;
j = Luat[i].begin();
cout << BangTra[*j] << " ";
j++;
cout << *j << " ";
j++;
cout << *j << " ";
j++;
cout << *j << " ";
j++;
while (j != Luat[i].end())
{
cout << BangTra[*j] << " ";
j++;
}
cout << endl;
}
// Test Cau nhap vao + Test tu loai
cout << endl << "Test input sentence:";
n = Cau.size();
for(int i = 0; i < n; i++)
{
cout << Cau[i] << " " << TuLoai[Cau[i]] << endl;
}
}
void testTableOutput(vector<vector<int> > Table[], int iNumWord)
{
map<int, string> BangTra;
BangTra[noun] = "noun";
BangTra[verb_i] = "verb_i";
BangTra[verb_t] = "verb_t";
BangTra[adj] = "adj";
BangTra[prep] = "prep";
BangTra[pro] = "pro";
BangTra[aux] = "aux";
BangTra[det] = "det";
BangTra[SENT] = "SENT";
16
BangTra[NP] = "NP";
BangTra[VP] = "VP";
BangTra[PP] = "PP";
BangTra[NP2] = "NP2";
BangTra[NP3] = "NP3";
for (int i = 0; i <= iNumWord; i++)
{
cout << "Table " << i << " :" << endl;
for (int j = 0; j < Table[i].size(); j++)
{
cout << BangTra[Table[i][j][0]] << " ";
cout << Table[i][j][1] << " ";
cout << Table[i][j][2] << " ";
cout << Table[i][j][3] << " ";
for (int k = 0; k < Table[i][j][NUMRHS]; k++)
{
cout << BangTra[Table[i][j][k+4]] << " ";
}
cout << endl;
}
}
}
void main()
{
vector<string> Cau;
map<string, unsigned int> TuLoai;
vector<int> Luat[100];
int iNumRule;
vector<vector<int> > Table[100];
nhapDaiTu(TuLoai);
nhapDanhTu(TuLoai);
nhapGioiTu(TuLoai);
nhapMaoTu(TuLoai);
nhapNgoaiDongTu(TuLoai);
nhapNoiDongTu(TuLoai);
nhapTinhTu(TuLoai);
nhapTroDongTu(TuLoai);
nhapDanhSachLuat(Luat, iNumRule);
nhapCauInput(Cau);
// testInput(Cau, TuLoai, Luat, iNumRule);
thuatToan(Cau, Luat, TuLoai, iNumRule, Table);
testTableOutput(Table, Cau.size());
getch();
}
17
18