近来,想做一些算法练习,无意中想到了我们非常熟悉的成语接龙游戏(本次以四字成语为例),就想着如何用java实现成龙接龙的关键逻辑。说干就干,于是很快梳理了一下思路:
1、首先在数据库构建一个表,导入大量的成语,作为成语储备。(这是基础)
2、思考接龙逻辑,实现成语接龙。(这是核心)
首先,我们来构建成语库,根据成语接龙的游戏规则,思考了下,表至少需要以下字段:成语内容、成语首字、成语末尾字。如果成语接龙的规则再放宽,比如接龙的时候只考虑读音一致也行,那么再补充成语首字拼音、成语末尾字拼音两个字段。于是,表结构基本确定下来了,以Spring Data JPA为例,成语的Entity如下:
@Entity
@Table(name = "t_idiom")
public class IdiomEntity {
@Id
private String id = Util.getUUIDStr();
private String value;//四字成语,如:龙飞凤舞
private String wordS;//成语的首字
private String wordE;//成语的末尾字
private String pinyinS;//成语的首字拼音
private String pinyinE;//成语的末尾字拼音
private String paraphrase;//成语解释
//为节约篇幅,此处省略get、set方法。
}
表建好之后就需要导入数据了,我从网上找了个txt版的成语大全,非常全,大概接近三万条成语。这么多数据,当然需要通过程序导入表里了,可以通过java读取txt文件的内容,加以逻辑,将数据存到表里。java读取txt文件的代码可自行研究学习,不在详细解释,因为这不是本文的重点。txt文件内容的格式如下:
读取txt文件并保存数据代码如下:
@Override
public void readTxt(String filePath) {
File file = new File(filePath);
StringBuilder result = new StringBuilder();
try{
BufferedReader br = new BufferedReader(new FileReader(file));//构造一个BufferedReader类来读取文件
String s = null;
while((s = br.readLine())!=null){//使用readLine方法,一次读一行
if (s.indexOf("拼音") > 0) {
String value = s.substring(0, s.indexOf("拼音")).trim();
if (value.length() == 4) {
IdiomEntity idiomEntityOld = idiomRepo.findFirstByValue(value);
if (idiomEntityOld == null) {
try {
String pinyin = s.substring(s.indexOf("拼音:") + 3, s.indexOf("释义"));
String paraphrase = s.substring(s.indexOf("释义:") + 3);
String wordS = value.substring(0,1);
String wordE = value.substring(3,4);
String pinyinS = pinyin.substring(0, pinyin.indexOf(" "));
String pinyinE = pinyin.substring(pinyin.lastIndexOf(" ") + 1);
IdiomEntity idiomEntity = new IdiomEntity();
idiomEntity.setValue(value);
idiomEntity.setWordS(wordS);
idiomEntity.setPinyinS(pinyinS);
idiomEntity.setWordE(wordE);
idiomEntity.setPinyinE(pinyinE);
idiomEntity.setParaphrase(paraphrase);
idiomRepo.save(idiomEntity);
} catch (Throwable e) {
continue;
}
}
}
}
}
br.close();
}catch(Exception e){
e.printStackTrace();
}
}
成语大全里已经给出了成语的汉语拼音了,非常方便,但是如果没有带拼音的话,我们也可以通过java代码获取汉字的汉语拼音,代码如下:
/**
* 汉字转为拼音
* @param chinese
* @return
*/
public static String ToPinyin(String chinese){
String pinyinStr = "";
char[] newChar = chinese.toCharArray();
HanyuPinyinOutputFormat defaultFormat = new HanyuPinyinOutputFormat();
defaultFormat.setCaseType(HanyuPinyinCaseType.LOWERCASE);
defaultFormat.setToneType(HanyuPinyinToneType.WITHOUT_TONE);
for (int i = 0; i < newChar.length; i++) {
if (newChar[i] > 128) {
try {
pinyinStr += PinyinHelper.toHanyuPinyinStringArray(newChar[i], defaultFormat)[0];
} catch (BadHanyuPinyinOutputFormatCombination e) {
e.printStackTrace();
}
}else{
pinyinStr += newChar[i];
}
}
return pinyinStr;
}
读入成语数据,算是完成了第一步,如图:
一共28603条成语,成语库很丰富了。
接下来,最关键的一步,实现成语接龙的逻辑,就以最常见的游戏规则(接龙的时候前后两个成语的首尾字必须一致)为例。思路如下:根据输入的第一个成语(也就是龙头句),根据它的末尾字(也就是关节字)来接龙,我们可以去数据库里检索第二个成语(首字等于龙头句的末尾字)。检索到的第二个成语肯定不止一个,按照同样逻辑,检索下一个成语,每次检索都会有多个答案。就像一棵树一样,龙头句相当于树的根部,每次接龙多会分N个树杈,直到无法接上(到达叶子)。也就是说每条从树根到达某个叶子节点的路径都是成语接龙的答案。如何得到这些答案,并输出出来,核心就是利用递归和遍历。实现代码如下:
@Override
public void idiomPlay(String sIdiom) { //sIdiom:输入的成语(龙头句)
String wordE = sIdiom.substring(3, 4); //关节字
doPlay(sIdiom, wordE);
}
//递归方法
public void doPlay(String idiomStr, String wordE){
List<IdiomEntity> idiomEntities = idiomRepo.findAllByWordS(wordE); //根据关节字检索下一条成语
if (idiomEntities.size() > 0) {
for (IdiomEntity entity : idiomEntities) {
if (idiomStr.indexOf(entity.getValue()) > -1) { //如果检索到的成语已经在之前的成语串里,则停止递归
System.out.println(idiomStr);
} else {
doPlay(idiomStr + "->" + entity.getValue(), entity.getWordE());
}
}
} else { //检索不到下一条成语,该路径完成
System.out.println(idiomStr);
}
}
输入“龙飞凤舞”,执行结果片段如下:
由于成语库非常大,接龙的答案会非常多,一时半会是执行不完的。在测试的过程中,发现有时会输出重复的答案,分析发现,在成语串已经非常长的情况下,检索下一条成语的时候如果检索到很多个,则可能会出现其中多个是已经在之前成语串中出现过,根据程序逻辑,这种情况会输出几条重复的答案,但是不会出现死循环。可以利用set集合去重,本文不再详述。
这样,一个简单的成语接龙就实现了,其他接龙玩法可依本例扩展,不再详述。