一、背景
這個學期開設的Java程序設計課程,需要用課堂派來簽到和平常練習,老師苦於課堂派后台的課堂測試需要人工填入題目,好在課堂派運行符合格式的題目直接從word文檔中導入,於是讓我們來寫一個小程序來實現這個格式轉換。老師用的源題庫是有特殊格式的,這樣我們就可以通過一些特殊的標記來實現對源題庫的讀取,我最開始使用C++來實現的,后來補充的Java的版本。該程序主要是對string的操作,二者之中有許多相同的函數方法,但具體的寫法卻有所不同,同時Java的限制數組越界、不能更改String的一些特點,也讓有些頭疼,Java寫的還是太少了!
二、格式要求
源題庫樣例:
目標題庫格式:
具體要求:
1.第一行為題干,中間不要換號(課堂派后台只能識別一行題干),可以加題號,也可以不加題號,編寫程序時可以通過一個選項開關讓使用者決定是否需要保留題號。
2.答案要寫在題目開頭或者結尾,用括號括起來,大小寫括號均可。在題干末尾添加本題的類型和分數,用大括號括起來。題目的類型需要通過答案的個數自己判斷,題目的分數需要程序的使用者來輸入。
3.題干之后是選項行,選項中不要空行,一個選項是單獨的一行,選項字母需要英文大寫。
4.每一道題與下一道題目之間需要空三行作為標志。
5.源題庫中的有些題目可能會有解析,這里解析不需要提取處理。
6.源題庫中出現的Chapter和Section的內容可以輸出到控制台,目標文件中不要出現。
三、C++源代碼
#include<fstream> //ifstream #include<iostream> #include<string> #include<map> using namespace std; struct subject { int id;//題目序號 int type;//題目類型 string stem;//題干內容 map<string,string>options;//選項與答案的map鍵值對 int num;//選項個數 string score;//題目分數 string key;//答案 string analysis;//有些題目需要解析 }; int main() { string s,temp; string path; ofstream outf; ifstream inf; int cnt=0;//記錄題目的個數 int num1=0;//單選題個數 int num2=0;//多選題個數 int flag=0;//默認保留原題號 char select;//是否保留題號的選項 string score; struct subject sbj[100]; string::size_type pos(0); cout << "請輸入需要轉換的文本文件路徑:(例:C://in.txt)" <<endl; cin >> path; if(inf) { cout << "打開文本文件成功!" <<endl; } else { cout << "打開文本文件失敗!" <<endl; } inf.open(path);//打開文本文件I://chapter1.txt cout << "請輸入轉換輸出的文本文件路徑:(例:C://out.txt)" <<endl; cin >> path; outf.open(path);//需要寫入的文本文件 //目標文本的讀取 cout << "轉換完成后的題目是否保留原題號?(y/n)" <<endl; while(1) { cin >> select; if(select=='y') { flag=1; break; } else if(select=='n') { flag=0; break; } else { cout << "只能輸入y或n,請重新輸入!" <<endl; } } while (getline(inf, s)) { if(s.substr(0,7)=="Chapter") { cout<<s<<endl; } else if(s.substr(0,7)=="Section") { cout<<s<<endl; } //提取選項信息 else if(s.substr(0,1)>="0"&&s.substr(0,1)<="9") //提取題干信息 { cnt++; if(flag==1) { pos =s.find("."); temp=s.substr(pos+1,s.length()-pos); sbj[cnt].stem = temp;//保存題干內容 } else if(flag==0) { sbj[cnt].stem = s; } sbj[cnt].id = cnt; sbj[cnt].num = 0; while (getline(inf, s)) { if(s.substr(0,4)=="Key:")//提取答案信息 { temp=s.substr(4,s.length()-4); pos =temp.find(" "); sbj[cnt].key=temp.substr(0,pos); cout<<sbj[cnt].key<<endl; for(int i=0; i<sbj[cnt].key.length(); i++)//答案替換為大寫字母 { if(sbj[cnt].key[i]>='a'&&sbj[cnt].key[i]<='z') { sbj[cnt].key[i]=sbj[cnt].key[i]-32; } } pos=sbj[cnt].key.length()-1; if(temp.length()>sbj[cnt].key.length()) { sbj[cnt].analysis=temp.substr(pos,temp.length()-pos); } } else if((s[0]>='a'&&s[0]<='z')||(s[0]>='A'&&s[0]<='Z'))//提取選項信息 { if(s[0]>='a'&&s[0]<='z')//選項是小寫字母,轉為大寫字母 { s[0]=s[0]-32; } sbj[cnt].options.insert(pair<string,string>(s.substr(0,1),s.substr(2,s.length()-2)));//保存選項 sbj[cnt].num++; } else if(s.substr(0,1)=="#")//分隔符,下一道題 { break; } } cout<<"第"<<cnt<<"道題識別成功!!"<<endl; cout<<sbj[cnt].stem<<endl; cout<<"答案:"<<sbj[cnt].key<<endl<<endl; } } cout<<"讀取文本文件成功!"<<endl; //目標文本的處理 for(int i=1; i<=cnt; i++) { pos=0; sbj[i].stem.insert(pos,"( "+sbj[i].key+" )");//答案的插入 if(sbj[i].key.length()==1) { sbj[i].stem=sbj[i].stem+"[單選題]"; num1++; } else if(sbj[i].key.length()>1) { sbj[i].stem=sbj[i].stem+"[多選題]"; num2++; } } cout<<"檢測到單選題"<<num1<<"道,多選題"<<num2<<"道"<<endl; cout<<"請輸入題目的分數:[1~100]"<<endl; cin>>score; for(int i=1; i<=cnt; i++) { sbj[i].score=score; sbj[i].stem=sbj[i].stem+"["+score+"分]"; } for(int i=1; i<=cnt; i++)//文本寫入 { outf<<sbj[i].stem<< '\n';//寫入題干 map<string,string>::iterator iter; for(iter = sbj[i].options.begin(); iter != sbj[i].options.end(); iter++) { outf<<iter->first<<"."<<iter->second<< '\n';//寫入選項 } outf<< '\n'<< '\n'<< '\n'; } inf.close(); outf.close(); cout<<"文本轉換完成!"<<endl; return 0; }
四、Java源代碼
import java.io.*; import java.util.*; //import java.util.Map; //import java.util.HashMap; class subject { int id;//題目序號 int type;//題目類型 int num;//選項個數 String stem;//題干內容 int score;//題目分數 String key;//答案 String analysis;//有些題目需要解析 Map<String,String>options=new LinkedHashMap<String,String>();//map映射,LinkedHashSet正序輸出 public subject()//構造函數 { id = 0; type = 1; num = 0; score =1; stem = null; key = null; analysis = null; } } public class Main { public static void main(String args[]) { int cnt = 0; int num1=0;//單選題個數 int num2=0;//多選題個數 int flag=0;//默認保留原題號 char select;//是否保留題號的選項 int score; String pathname_in; String pathname_out; Scanner in=new Scanner(System.in); subject [] sbj; sbj = new subject[1000]; for(int i=0;i<sbj.length;i++){ sbj[i]= new subject(); } //String pathname = "I://chapter1.txt"; System.out.println("請輸入需要轉換的文本文件路徑:(例:C://in.txt)"); pathname_in =in.nextLine(); System.out.println("請輸入轉換輸出的文本文件路徑:(例:C://out.txt)"); pathname_out =in.nextLine(); System.out.println("轉換完成后的題目是否保留原題號?(y/n)"); while(true){ select =in.nextLine().charAt(0); if(select=='y'){ flag=1; break; } else if(select=='n'){ flag=0; break; } else { System.out.println("只能輸入y或n,請重新輸入!"); } } try (FileReader reader = new FileReader(pathname_in); BufferedReader br = new BufferedReader(reader) // 建立一個對象,它把文件內容轉成計算機能讀懂的語言 ) { String line; //網友推薦更加簡潔的寫法 while ((line = br.readLine()) != null) { if(line.length()==0){ continue; } else if(line.substring(0,7).equals("Chapter")){ System.out.println(line); } else if(line.substring(0,7).equals("Section")){ System.out.println(line); } else if((line.charAt(0)>='0')&&(line.charAt(0)<='9')){ cnt++; String temp; int pos; if(flag==1){ pos =line.indexOf('.'); temp=line.substring(pos+1,line.length()); sbj[cnt].stem = temp;//保存題干內容 } else if(flag==0){ sbj[cnt].stem = line; } sbj[cnt].id=cnt; sbj[cnt].num=0; while ((line = br.readLine()) != null) { System.out.println(line); if(line.length()==0){ continue; } else if(line.charAt(0)=='#'){ break;//分隔符 } else if(line.length()<=4) { continue; } else if(line.substring(0,4).equals("Key:")){ temp = line.substring(4,line.length());//答案和解析的內容 pos = temp.indexOf(' '); if(pos==-1){//沒有解析說明 sbj[cnt].key= temp.toUpperCase();//寫回 System.out.println(sbj[cnt].key); } else { sbj[cnt].key=temp.substring(0,pos);//答案選項 sbj[cnt].key=sbj[cnt].key.toUpperCase();//寫回 pos=sbj[cnt].key.length()-1; if(temp.length()>sbj[cnt].key.length()){ sbj[cnt].analysis=temp.substring(pos,temp.length()-pos); } } } else if(((line.charAt(0)>='a'&&line.charAt(0)<='z')||(line.charAt(0)>='A'&&line.charAt(0)<='Z'))&&(line.charAt(1)=='.')){ String opt = line.substring(0,2);//選項 String detail = line.substring(2,line.length());//選項描述 sbj[cnt].options.put(opt,detail); sbj[cnt].num++; } else { continue; } } System.out.println("第"+cnt+"道題識別成功!!"); System.out.println(sbj[cnt].stem); System.out.println("答案:"+sbj[cnt].key+"\n"); } else { continue; } } } catch (IOException e) { e.printStackTrace(); } System.out.println("讀取文本文件成功!"); for(int i=1;i<=cnt;i++){ sbj[i].stem="( "+sbj[i].key+" )"+sbj[i].stem; if(sbj[i].key.length()==1){ sbj[i].stem=sbj[i].stem+"[單選題]"; num1++; } else if(sbj[i].key.length()>1){ sbj[i].stem=sbj[i].stem+"[多選題]"; num2++; } } System.out.println("檢測到單選題"+num1+"道,多選題"+num2+"道"); System.out.println("請輸入題目的分數:[1~100]"); score =in.nextInt(); for(int i=1; i<=cnt; i++){ sbj[i].score=score; sbj[i].stem=sbj[i].stem+"["+score+"分]"; } try { File writeName = new File(pathname_out); // 相對路徑,如果沒有則要建立一個新的output.txt文件 writeName.createNewFile(); // 創建新文件,有同名的文件的話直接覆蓋 try (FileWriter writer = new FileWriter(writeName); BufferedWriter out = new BufferedWriter(writer) ) { for(int i=1; i<=cnt; i++){//文本寫入 out.write(sbj[i].stem+"\r\n"); Iterator<Map.Entry<String, String>> entries = sbj[i].options.entrySet().iterator(); while(entries.hasNext()){ Map.Entry<String, String> entry = entries.next(); out.write(entry.getKey()+entry.getValue()+"\r\n"); } out.write("\r\n"+"\r\n"+"\r\n"); } out.flush(); // 把緩存區內容壓入文件 } } catch (IOException e){ e.printStackTrace(); } System.out.println("文本轉換完成!"); } }
五、遇到的一些問題
1.Java中關於HashMap的元素遍歷的順序問題
摘自https://www.cnblogs.com/xdp-gacl/p/3558625.html
在使用如下的方式遍歷HashMap里面的元素時
for (Entry<String, String> entry : hashMap.entrySet()) { MessageFormat.format("{0}={1}",entry.getKey(),entry.getValue()); }
發現得到的元素不是按照之前加入HashMap的順序輸出的,這個問題我之前倒是沒有注意過,后來上網查了一下原因,發現是:HashMap散列圖、Hashtable散列表是按“有利於隨機查找的散列(hash)的順序”。並非按輸入順序。遍歷時只能全部輸出,而沒有順序。甚至可以rehash()重新散列,來獲得更利於隨機存取的內部順序。
總之,遍歷HashMap或Hashtable時不要求順序輸出,即與順序無關。
Map<String, String> paramMap = new HashMap<String, String>();
可以用java.util.LinkedHashMap 就是按加入時的順序遍歷了。
Map<String, String> paramMap = new LinkedHashMap <String, String>();
類似的還有 java.util.LinkedHashSet
2.C++中對字符串中所有指定的子串進行替換的函數
/* * string& str 源字符串 * const string& old_value 被替換子串 * const string& new_value 替換子串 */ string& replace_all(string& str,const string& old_value,const string& new_value)//替換函數 { while (true) { string::size_type pos(0); if ((pos = str.find(old_value)) != string::npos) { str.replace(pos, old_value.length(), new_value); } else { break; } } return str; }