偶然間遇到了一個需求:漢字形式數字轉換成整數數字。如果不處理意外情況,可以寫的很簡單(比如不會出現三三等),詳情可以看這裏。但是,想着可以寫成一個FA的形式,於是乎,發現並不是想象中的那麼簡單。。因爲寫成FA就發現,要處理非法形式的問題,還是有點麻煩的。
好不容易寫成了FA,發現是個NFA,於是乎寫了個NFA轉換成DFA的代碼,也支持了一套簡單的FA的定義規則。代碼如下:
package ie;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import util.FileEntry;
public class DFA {
public static class RulesLoader {
/*rule文件中,用來縮寫狀態集合的符號*/
public static String SPLITER_STATUS = ",";
/*rule文件中,預定義語句的分隔符*/
public static String SPLITER_DEFINE = "=";
/*rule文件中,定義狀態轉移的分隔符*/
public static String SPLITER_TRANSFORM = "\t";
/*程序中,用來記錄原始狀態和轉移的分隔符*/
public static String SPLITER_MAP_KEY = "--";
/*rule文件中,定義狀態空的符號*/
public static String EMPTY = "$EMPTY";
/**
* 是否是預定義
* @param line rule文件的一行
* @return
*/
public static boolean isDefine(String line) {
return line.indexOf(SPLITER_DEFINE) != -1;
}
/**
* 是否是需要忽略的行
* @param line rule文件的一行
* @return
*/
public static boolean isIgnore(String line) {
return line.length() == 0;
}
/**
* 是否是狀態轉換定義
* @param line rule文件的一行
* @return
*/
public static boolean isTransform(String line) {
return line.split(SPLITER_TRANSFORM).length == 3;
}
/**
* 獲取文件中的預定義
* @param mapDefine 用來存儲所有的預定義
* @param line rule文件的一行
*/
public static void getDefine(Map<String, String> mapDefine, String line) {
String[] parts = line.split(SPLITER_DEFINE);
try {
mapDefine.put(parts[0], parts[1]);
}
catch (ArrayIndexOutOfBoundsException e) {
System.err.println("ERROR:未在當前行中發現定義");
throw e;
}
}
/**
*
* @param mapDefine 記錄文件中所有的預定義
* @param status 原狀態
* @return 先將狀態用預定義替換,然後分隔
*/
public static String[] getStatuses(Map<String, String> mapDefine, String status) {
if (mapDefine.containsKey(status))
status = mapDefine.get(status);
return status.split(SPLITER_STATUS);
}
/**
* 是否是空符號
* @param symbol
* @return
*/
public static boolean isEmpty(String symbol) {
return symbol.equals(EMPTY);
}
public static void display(Set<String> set, String enter) {
for (String s: set) {
System.out.print(s + " ");
}
System.out.print(enter);
}
/**
* 獲取statuses中,所有狀態在空符號下的等價狀態
* @param mapEmpty 記錄rule文件中的所有空轉移信息
* @param statuses
* @return
*/
public static Set<String> getByEmpty(Map<String, Set<String>> mapEmpty, Set<String> statuses) {
Set<String> ret = new HashSet<String>();
Set<String> tmpStatuses = new HashSet<String>(statuses);
while (!tmpStatuses.isEmpty()) {
String status = tmpStatuses.iterator().next();
tmpStatuses.remove(status);
ret.add(status);
for (String dest: mapEmpty.getOrDefault(status, new HashSet<String>()))
ret.add(dest);
}
return ret;
}
/**
* 獲取status在空符號下的等價狀態
* @param mapEmpty 記錄rule文件中的所有空轉移信息
* @param status
* @return
*/
public static Set<String> getByEmpty(Map<String, Set<String>> mapEmpty, String status) {
Set<String> ret = new HashSet<String>();
ret.add(status);
for (String dest: mapEmpty.getOrDefault(status, new HashSet<String>())) {
ret.add(dest);
}
return ret;
}
/**
* 起始狀態經過symbol轉換後的狀態
* @param mapTransform
* @param mapEmpty
* @param startStatuses 起始的狀態,是轉換後的狀態
* @param symbol 需要轉換的符號
* @return
*/
public static Set<String> getDestBySymbol(Map<String, Set<String>> mapTransform,
Map<String, Set<String>> mapEmpty, Set<String> startStatuses, String symbol) {
//將狀態經過空符號轉移後的狀態作爲初始狀態
Set<String> sourceStatuses = getByEmpty(mapEmpty, startStatuses);
Set<String> destStatuses = new HashSet<String>();
//對一個新的狀態的所有原始狀態,都進行轉移,每個原始狀態爲String
while (!sourceStatuses.isEmpty()) {
String status = sourceStatuses.iterator().next();
String key = status + SPLITER_MAP_KEY + symbol;
for (String destStatus: mapTransform.getOrDefault(key, new HashSet<String>())) {
if (!destStatuses.contains(destStatus)) {
Set<String> all = getByEmpty(mapEmpty, destStatus);
sourceStatuses.addAll(all);
destStatuses.addAll(all);
}
}
sourceStatuses.remove(status);
}
return destStatuses;
}
/**
* 從文件中加載信息,構造出DFA
* @param filePath
* @param charset
* @return
*/
public static DFA loadRules(String filePath, String charset) {
ArrayList<String> lines = FileEntry.readFileLines(filePath, charset);
Map<String, String> mapDefine = new HashMap<String, String>();
Map<String, Set<String>> mapTransform = new HashMap<String, Set<String>>();
Map<String, Set<String>> mapEmpty = new HashMap<String, Set<String>>();
Map<String, Integer> statusMap = new HashMap<String, Integer>();
Set<String> symbolSet = new HashSet<String>();
DFA dfa = new DFA();
/**
* 先處理出所有的預定義信息,並記錄下來
*/
for (String line: lines)
if (isDefine(line))
getDefine(mapDefine, line);
/**
* 將所有狀態都使用預定義進行替換處理
* 對rules文件進行處理,生成所有的(a, b, c)三元組:表示a狀態經過b符號到c狀態
* 如果b不是空符號,則記錄在mapTransform中,記錄形式爲 Map[a+SPLITER_MAP_KEY+b]=c
* 否則,記錄在mapEmpty中,記錄形式爲Map[a]=c
*/
for (String line: lines)
if (isTransform(line)) {
String[] parts = line.split("\t");
String[] sources = getStatuses(mapDefine, parts[0]);
String[] symbols = getStatuses(mapDefine, parts[1]);
String[] destinations = getStatuses(mapDefine, parts[2]);
for (String source: sources) {
statusMap.put(source, -1);
for (String symbol: symbols) {
symbolSet.add(symbol);
for (String destination: destinations) {
if (!isEmpty(symbol)) {
statusMap.put(destination, -1);
String key = source + SPLITER_MAP_KEY + symbol;
Set<String> value = mapTransform.getOrDefault(key, new HashSet<String>());
value.add(destination);
mapTransform.put(key, value);
}
else {
Set<String> value = mapEmpty.getOrDefault(source, new HashSet<String>());
value.add(destination);
// System.out.println(source + "\t" + value);
mapEmpty.put(source, value);
}
}
}
}
}
//記錄轉換後的DFA的狀態,每個狀態爲Set<String>
Set<Set<String>> newStatusSet = new HashSet<Set<String>>();
//起點默認爲0
Set<String> startStatus = new HashSet<String>();
startStatus.add("0");
newStatusSet.add(startStatus);
//對所有未處理的狀態,都進行轉移
while (!newStatusSet.isEmpty()) {
Set<String> statuses = newStatusSet.iterator().next();
//對每個狀態,逐一選擇所有的符號,得到新的轉移後狀態
for (String symbol: symbolSet) {
Set<String> destStatuses = getDestBySymbol(mapTransform, mapEmpty, statuses, symbol);
if (destStatuses.size() > 0) {
dfa.addEdge(new HashSet<String>(statuses), symbol, destStatuses);
// display(statuses, "--(");
// display(getByEmpty(mapEmpty, statuses), "--(");
// System.out.print(symbol + ")--> ");
// display(destStatuses, "\n");
newStatusSet.add(destStatuses);
}
}
newStatusSet.remove(statuses);
}
return dfa;
}
}
/**
*
* @author wty
*
*/
class MapNode {
/*自動機的初始狀態*/
public Set<String> status;
/*狀態接受的符號*/
public String symbol;
/*<臨時>,處理漢字轉換成數字用來記錄和*/
public int sum = 0, subSum = 0;
public MapNode() {}
public MapNode(Set<String> status, String symbol) {
this.status = status;
this.symbol = symbol;
}
@Override
public boolean equals(Object obj) {
if (obj == null || getClass() != obj.getClass())
return false;
final MapNode other = (MapNode) obj;
if (this.status != other.status && (this.status == null || !this.status.equals(other.status)))
return false;
if (this.symbol != other.symbol && (this.symbol == null || !this.symbol.equals(other.symbol)))
return false;
return true;
}
@Override
public int hashCode() {
return status.hashCode() ^ symbol.hashCode();
}
}
public static DFA getNumberDFA() {
DFA dfa = RulesLoader.loadRules("src/rules", "UTF-8");
return dfa;
}
public static void display(Set<String> set, String enter) {
for (String s: set) {
System.out.print(s + " ");
}
System.out.print(enter);
}
public final HashMap<String, Integer> addMap = new HashMap<String, Integer>(){
/**
*
*/
private static final long serialVersionUID = 4651868428072469904L;
{
put("零", 0);
put("一", 1);
put("二", 2);
put("兩", 2);
put("三", 3);
put("四", 4);
put("五", 5);
put("六", 6);
put("七", 7);
put("八", 8);
put("九", 9);
}
};
public final HashMap<String, Integer> mulMap = new HashMap<String, Integer>(){
/**
*
*/
private static final long serialVersionUID = 4001356133579818232L;
{
put("十", 10);
put("百", 100);
put("千", 1000);
}
};
private HashMap<MapNode, Set<String>> map = new HashMap<DFA.MapNode, Set<String>>();
/**
* 當前狀態進行轉換時需要進行的操作
*/
public void transform(MapNode mapNode) {
if (addMap.containsKey(mapNode.symbol))
mapNode.subSum = addMap.get(mapNode.symbol);
else if (mulMap.containsKey(mapNode.symbol)) {
mapNode.sum += Math.max(1, mapNode.subSum) * mulMap.get(mapNode.symbol);
mapNode.subSum = 0;
}
else {
if (mapNode.status.size() == 0)
mapNode.sum = -1;
else if (mapNode.status.contains("3"))
mapNode.sum = mapNode.sum + mapNode.subSum * 100;
else if (mapNode.status.contains("7"))
mapNode.sum = mapNode.sum + mapNode.subSum * 10;
else if (mapNode.status.contains("11"))
mapNode.sum = mapNode.sum + mapNode.subSum;
else if (mapNode.status.contains("13"))
mapNode.sum = mapNode.sum + mapNode.subSum;
else if (mapNode.status.contains("14"))
mapNode.sum = mapNode.sum + mapNode.subSum;
else if (mapNode.status.contains("15"))
mapNode.sum = mapNode.sum + mapNode.subSum;
else if (mapNode.status.contains("19"))
mapNode.sum = mapNode.sum + mapNode.subSum;
else
mapNode.sum = mapNode.sum;
}
mapNode.status = getDest(mapNode.status, mapNode.symbol);
mapNode.symbol = "";
}
public void addEdge(Set<String> source, String symbol, Set<String> dest) {
map.put(new MapNode(source, symbol), dest);
}
public Set<String> getDest(Set<String> source, String symbol) {
return map.getOrDefault(new MapNode(source, symbol), new HashSet<String>());
}
public int transform(String inputStream) {
Set<String> status = new HashSet<String>();
status.add("0");
MapNode mapNode = new MapNode(status, "");
for (int i = 0; i < inputStream.length(); i++) {
String symbol = inputStream.substring(i, i + 1);
mapNode.symbol = symbol;
// display(status, " --(" + symbol + ")--> ");
transform(mapNode);
// display(status, "\n");
}
transform(mapNode);
return mapNode.sum;
}
}
這是我寫的,對萬(不包括)以內的數的識別的自動機描述:
1-9=一,二,兩,三,四,五,六,七,八,九
2-9=二,兩,三,四,五,六,七,八,九
0 1-9 1,4,15
0 2-9 8
0 十 9
0 零 16
1 千 2
2 1-9 3
2 零 6,10
3 百 5
4 百 5
5 零 12
5 1-9 7,8
6 1-9 17,10
7 十 9
8 十 9
9 1-9 14
10 1-9 11
12 1-9 13
17 十 18
18 1-9 19
簡單寫一下說明,文件最開始的1-9=str是對【1-9】這個符號的一個簡單替換規則,類似c語言中的#define。下邊的文件,每行分爲三部分:起始狀態 \t 接受的符號 \t 轉換到的狀態,此處爲了編寫的方便,如果有多個狀態or接受的符號,用英文的逗號分隔開即可。
其實這部分工作,對漢字轉數字的作用好像也沒多大。。。畢竟把錯誤的漢字串傳進來也不大可能,而且如果要實現對萬以上的漢字的識別,自動機也比較複雜。倒是這個NFA轉換成DFA或許有用的到的時候,先記下來吧。