java 求First集和Follow集

package cn.spy.action;
import java.util.ArrayList;
import java.util.Scanner;
import java.util.StringTokenizer;
/**
 *運行要求:
 * 1、一行只能輸入一個句子,且不能拖延到第二行,當然可以加多個或語句
 * 2、推導符號,只能是→和->中一種,每行語句中必須有推導符
 * 3、如果某非終結符的是次大寫字母表的某個字符,那麼在後面只能用’或者'來表示
 * 4、最後用end結束
 *實現功能:
 * 1、標準文法輸入
 * 2、求First集和First求解算法
 * 3、求Follow集合Follow集求解算法
 * 4、隨便多個語句輸入
 * 5、忽略空格的影響
 * 6、兼容兩種推導符的輸入和中英文輸入一撇符和或連接符
 * 7、簡單輸入錯誤判斷功能
 *某一輸入實例:
 * E->TE'
 * E'->+E|ε
 * T->FT'
 * T'->T|ε
 * F->PF'
 * F'->*F'|ε
 * P->(E)|a|b|^
 * end
 */
public class FirstFollow3 {
public ArrayList<String[]> in=new ArrayList<String[]>();//這數據結構真是逼人絕路纔去想到絕處逢生,哈哈,關鍵實現了可變長度文法接收,在這存放的是拆分後最簡單的文法,也是由用戶輸入
public ArrayList<String[]> first = new ArrayList<String[]>();//包括左推導符和其First集
public ArrayList<String[]> follow = new ArrayList<String[]>();
public ArrayList<String[]> track = new ArrayList<String[]>();//track有一條一條的非終結符串組成的路徑數組
public FirstFollow3(){
Scanner sc = new Scanner(System.in);
System.out.println("請分行輸入一個完整文法:(end結束)");
String sline="";
sline=sc.nextLine();
while(!sline.startsWith("end")){
StringBuffer buffer=new StringBuffer(sline);
int l=buffer.indexOf(" ");
while(l>=0){//去空格
buffer.delete(l,l+1);
l=buffer.indexOf(" ");
}
sline=buffer.toString();
String s[]=sline.split("->");//左推導符
if(s.length==1)
s=sline.split("→");//考慮到輸入習慣和形式問題
if(s.length==1)
s=sline.split("=>");
if(s.length==1){
System.out.println("文法有誤");
System.exit(0);
}
StringTokenizer fx = new StringTokenizer(s[1],"|︱");//按英文隔符拆開產生式或按中文隔符拆開
while(fx.hasMoreTokens()){
String[] one = new String[2];//對於一個語句只需保存兩個數據就可以了,語句左部和語句右部的一個簡單導出式,假如有或符,就按多條存放
one[0]=s[0];//頭不變,0位置放非終結符
one[1]=fx.nextToken();//1位置放導出的產生式,就是產生式右部的一個最簡單導出式
in.add(one);
}
sline=sc.nextLine();
}
//求First集過程
this.process("First");
/*
* 打印First集算法和First集
*/
System.out.println("\nFirst集算法:");
this.print(track);//打印First集算法
System.out.println("\nFirst集:");
for(int i=0;i<first.size();i++){
String[] r=first.get(i);
System.out.print("First("+r[0]+")={");
for(int j=1;j<r.length;j++){
System.out.print(r[j]);
if(j<r.length-1)
System.out.print(",");
}
System.out.println("}");
}
track.clear();//因爲下面還要用,這裏就先清空了
//求Follow集過程
this.process("Follow");
System.out.println("\nFollow集算法:");
for(int i=0;i<track.size();i++){
String[] one = track.get(i);
System.out.print("Follow("+follow.get(i)[0]+"):\t");
for(int j=0;j<one.length;j++)
System.out.print(one[j]+"\t");
System.out.println();
}

System.out.println("\nFollow集:");
for(int i=0;i<follow.size();i++){
String[] r=follow.get(i);
System.out.print("Follow("+r[0]+")={");
for(int j=1;j<r.length;j++){
System.out.print(r[j]);
if(j<r.length-1)
System.out.print(",");
}
System.out.println("}");
}
}
public void process(String firstORfollow){
for(int i=0;i<in.size();i++){
boolean bool=true;
for(int j=0;j<i;j++)
if(in.get(j)[0].equals(in.get(i)[0]))
bool=false;
if(bool){
ArrayList<String> a=null;
if(firstORfollow.equals("First"))
   a=this.getFirst(in.get(i)[0],"First("+in.get(i)[0]+")/");
else if(firstORfollow.equals("Follow"))
a=this.getFollow(in.get(i)[0],in.get(i)[0],"");
String[] sf=new String[a.size()/2+1];
String[] st=new String[a.size()/2];
sf[0]=in.get(i)[0];
for(int j=0;j<a.size();j++)
{
if(j%2==0)
   sf[j/2+1]=a.get(j);
else
st[j/2]=a.get(j);
}
if(firstORfollow.equals("First"))
   first.add(sf);//first集
else if(firstORfollow.equals("Follow"))
follow.add(sf);
track.add(st);//對應上面求得集的路徑,在開始保存該非終結符了,因爲已保存了該字符的First或Follow表示法
}
}
}
public ArrayList<String> getFirst(String s,String track1){//s表示左推導,track表示尋找路徑,避免循環查找
ArrayList<String> result = new ArrayList<String>();
ArrayList<String> result1 = new ArrayList<String>();
if(Character.isUpperCase(s.charAt(0))){//如果是非終結符,大寫
for(int i=0;i<in.size();i++){
String[] one = in.get(i);
if(s.equals(one[0])){
if(track1.substring(0,track1.length()-9).indexOf("First("+s+")")>=0)//假如在查找過程嵌套了這步,證明進入了無限循環,不需再找,此路徑無結果
;//有點要注意一下,本來一開始就把第一個開始推導符的First路徑放進去了的,所以要避開這一次,不然已開始就結束了
else if(one[1].length()==1||one[1].charAt(1)!='\''&&one[1].charAt(1)!='’')
result1=getFirst(one[1].charAt(0)+"",track1+"First("+one[1].charAt(0)+")/");
else if(one[1].length()>1&&one[1].charAt(1)=='\''||one[1].charAt(1)=='’')//假如接下來一個要求First的非終結符帶了一撇,那一撇包括英文表示和中文表示
result1=this.getFirst(one[1].substring(0,2),track1+"First("+one[1].substring(0,2)+")/");
result=addArrayString(result,result1);
result1.clear();
}
}
}
else{//如果產生式首字符是終結字符
if(s.equals("ε"))//注意:表示空的字符只能是這種了,其他形式在這個編譯器中不能通過,還請原諒
result1.add("#");
else
result1.add(s);
result1.add(track1);//爲了方便,把路徑也加入了結果集,不然可能路徑不匹配,沒辦法,因爲中間有刪去重複項
result=result1;
}
return result;
}
public ArrayList<String> getFollow(String s,String element,String track1){//從右至左反推,不是求Follow的等價Follow,因爲推到後面的反而範圍大
ArrayList<String> result = new ArrayList<String>();
ArrayList<String> result1 = new ArrayList<String>();
if(Character.isUpperCase(s.charAt(0))){
   for(int i=0;i<in.size();i++){
String[] one = in.get(i);
int slen=s.length();
int olen=one[1].length();
if(element.equals(in.get(0)[0])){//如果是開始符號,或是可以反推到開始符號,證明也可以順推導開始符號
result1.add("#");
result1.add(in.get(0)[0]+"→"+in.get(0)[0]+"\t");
result = addArrayString(result,result1);
result1.clear();
}
if(one[1].indexOf(s)>=0&&track1.indexOf((char)('a'+i)+"")>=0)//假如之前走過某一步,就不必再走了,那是死循環,之前在這語句前面加了個else,結果又部分內容顯示不出來,總算髮現了,就算反推到開始符號,也不一定就到結果了的,開始符號也可以反推,所以要繼續
;
else if(one[1].indexOf(s)>=0&&(olen-slen==one[1].indexOf(s)||slen==2||one[1].charAt(one[1].indexOf(s)+1)!='’'&&one[1].charAt(one[1].indexOf(s)+1)!='\''))
{//如果在右產生式中真正存在需要求反推的字符,後面的條件控制它是真正存在,因爲裏面包含這個字符也不一定是真,就像E’中包含E,但這不是真正的包含
int index=-1;
index = one[1].indexOf(s,0);
while(index>=0){//之前這沒有用到循環,結果可能少點東西,仔細一想,必須要,就算是一個推導語句,也可能推出多個相同非終結符的組合,其實這也是一種特殊情況了,不考慮也可能正確了,也可能之前在其他地方把這樣的結果求出來了,不求也沒事,但就像假如要求T的Follow集,假如可以產生出T+a*T*b,這時還是有用的,萬一吧
if(olen-slen==index){//如果該非終結符在末尾,那麼求導出該產生式的非終結符的倒推
result1=getFollow(one[0], element,track1+(char)('a'+i));
result=addArrayString(result,result1);
result1.clear();
}else{//如果後繼非終結符在產生式中不是最後
int t=index+slen;//指向在產生式非終結符s的後一個字符位置
result1=returnFirstofFollow(s, element, track1, one[0], one[1], index, t);
result=addArrayString(result,result1);//之前也沒寫這句話,結果把之前的內容覆蓋了,就是之前的數據丟失
result1.clear();
}
index = one[1].indexOf(s,index+1);
}//endwhile
}
if(one[1].endsWith(element)){//如果最開始要求的Follow集非終結符在末尾
result1.add("#");
result1.add(in.get(0)[0]+"→"+one[1]+"\t");
result=addArrayString(result,result1);//之前也沒寫這句話,結果把之前的內容覆蓋了,就是之前的數據丟失
result1.clear();
}
   }//endfor
}
return result;
}
public ArrayList<String> returnFirstofFollow(String s,String element,String track1,String one0,String one1,int index,int t){//返回求Follow集中要求的First集部分
ArrayList<String> result = new ArrayList<String>();
ArrayList<String> result1 = new ArrayList<String>();
ArrayList<String > beckFirst;
String lsh;//記錄下一個字符
if(t+1<one1.length()&&(one1.charAt(t+1)=='’'||one1.charAt(t+1)=='\''))//如果隨後的非終結符還帶了一撇符
lsh=one1.substring(t,t+2);
else//如果沒帶一撇,就只要截取一個字母就可以了
lsh=one1.substring(t,t+1);
String[] ls = null;
int beflen=2;
if(track1.length()>0){//這些都是爲了算法輸出容易理解點用的,其實要不輸出這算法,要省下好多東西
ls=in.get((int)(track1.charAt(track1.length()-1)-'a'));//得到上一步調用的語句
if(Character.isUpperCase(ls[1].charAt(ls[1].length()-1)))
beflen=1;
}
beckFirst=this.getFirst(lsh,"First("+lsh+")/");//相當於得到後繼字符的First集
for(int j=0;j<beckFirst.size()/2;j++){//調用求First集,返回的不一定只一個結果
String lh="";
if(beckFirst.get(j*2).equals("#")){
result1.add(beckFirst.get(j*2));//這個加了是數據,下面一步就是把地址加上,就是一個結果,要兩份數據
if(ls==null)
lh=in.get(0)[0]+"→"+one1+"→"+one1.substring(0,index)+element+"ε"+one1.substring(t+lsh.length(),one1.length());
else
lh=in.get(0)[0]+"→"+one1+"→"+one1.substring(0,index)+ls[1]+one1.substring(index+s.length(),one1.length())+"→."+element+"ε"+one1.substring(t+lsh.length(),one1.length());
result1.add(lh);
result=addArrayString(result,result1);
result1.clear();
if(1+index+lsh.length()<one1.length())//證明後面還有字符,爲什麼要這一步,打個比方把,假如要求F的Follow集,而存在產生式FPQ,而P的有個First集爲空,那麼得還接着求Q的First,類推,假如最後一個字符Q還是返回空,那麼求要求產生式左邊的推導非終結符的Follow集了,必須把這些結果都算到F的Follow集中去
result1=returnFirstofFollow(s, element, track1, one0,one1, index, t+lsh.length());
else//到最後,那麼求要求產生式左邊的推導非終結符的Follow集了,其實這和上面一種情況都很特殊了,一般用不上了
result1=getFollow(one0, element, track1);
}
else{//其實下面這一大坨都是爲了易懂一點,Follow集算法清晰一點,好苦啊
if(Character.isUpperCase(one1.charAt(t))){//如果是有隨後的一個非終結符的First集求出的結果
if(ls==null)
lh=in.get(0)[0]+"→"+one1+"→"+one1.substring(0,index)+element+beckFirst.get(j*2)+one1.substring(t+lsh.length(),one1.length());
else
lh=in.get(0)[0]+"→"+one1+"→"+one1.substring(0,index)+ls[1]+one1.substring(index+s.length(),one1.length())+"→."+element+beckFirst.get(j*2)+one1.substring(t+lsh.length(),one1.length());
}
else{//如果不是大寫,就是終結符了,那麼用First集求出來的結果連接起來還是一樣的,所以不要重複打印兩次了
if(ls==null){
if(element==in.get(0)[0]||s.equals(element))
lh=in.get(0)[0]+"→"+one1.substring(0,index)+element+one1.substring(t,one1.length())+"\t";
else
lh=in.get(0)[0]+"→"+one1+"→"+one1.substring(0,index)+element+one1.substring(t,one1.length())+"\t";
}
else{
if(ls[1].length()==1||ls[1].length()==2&&!ls[1].endsWith("’")&&!ls[1].endsWith("\'"))
lh=in.get(0)[0]+"→"+one1+"→"+one1.substring(0,index)+element+one1.substring(t,one1.length());
else
lh=in.get(0)[0]+"→"+one1+"→"+one1.substring(0,index)+ls[1]+one1.substring(index+s.length(),one1.length())+"→."+element+one1.substring(t,one1.length())+"!";
}
}
result1.add(beckFirst.get(j*2));//這個加了是數據,下面一步就是把地址加上,就是一個結果,要兩份數據
result1.add(lh);
}
}
result=addArrayString(result,result1);//之前也沒寫這句話,結果把之前的內容覆蓋了,就是之前的數據丟失
result1.clear();
return result;
}
public ArrayList<String> addArrayString(ArrayList<String> a,ArrayList<String> b){//兩個字符串數組相加
ArrayList<String> result = new ArrayList<String>();
for(int i=0;i<a.size();i+=2){//因爲這每一個結果,都保存了兩個數據,第一個是結果,第二個位置保存的是得到這結果的路徑
String s = a.get(i);
if(result.contains(s)||s.equals("")){//如果結果集包含了這個字符串,就不加入結果集了,就是爲了去掉重複項
int index=result.indexOf(s);
if(result.get(index+1).length()>a.get(i+1).length()){//如果新來的路徑比現有的短
result.set(index, s);
result.set(index+1,a.get(i+1));
}
continue;
}
result.add(s);
result.add(a.get(i+1));//還是要把路徑繼續保存在新的結果集中
}
for(int i=0;i<b.size();i+=2){
String s = b.get(i);
if(result.contains(s)||s.equals("")){
int index=result.indexOf(s);
if(result.get(index+1).length()>b.get(i+1).length()){//如果新來的路徑比現有的短
result.set(index, s);
result.set(index+1,b.get(i+1));
}
continue;
}
result.add(s);//偶數地址存放的是數據
result.add(b.get(i+1));//奇數地址存放的是該數據獲得的路徑
}
return result;
}
public void print(ArrayList<String[]> list){
for(int i=0;i<list.size();i++){//循環非終結符個數次數
String[] one = list.get(i);//得到某一個非終結符運行的所有路徑
String[][] strings= new String[one.length][];
String[] finals = new String[one.length];//路徑最終站點
int number=0;//記錄某一步最終有效站點個數,本來有幾條路徑,就因該有幾個有效站點,但可能有些站點有重複的,即從同一站點發出
int max=0;
for(int j=0;j<one.length;j++){
strings[j]=one[j].split("/");
if(strings[j].length>max)
max=strings[j].length;//求得某一非終結符路徑最長一條
}
for(int j=0;j<max;j++){//循環最長站點次數
number=0;
for(int k=0;k<strings.length;k++){//有多少條路徑就循環多少次
String lsh="";
if(j>=strings[k].length){
lsh=strings[k][strings[k].length-1];
}else {
lsh=strings[k][j];
}
int m=0;
for(m=0;m<number;m++){//記錄有效站點
if(lsh.equals(finals[m]))
break;
}
if(m==number){
finals[number]=lsh;
number++;
}
}
for(int k=0;k<number;k++){//打印每一條路徑的某個站點
System.out.print(finals[k]);
if(k!=number-1)
System.out.print(" + ");
}
if(j<max-1)
System.out.print(" = ");
}
System.out.println();
}
}
public static void main(String[] args){
new FirstFollow3();
}
}

發佈了28 篇原創文章 · 獲贊 2 · 訪問量 3萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章