偶然间遇到了一个需求:汉字形式数字转换成整数数字。如果不处理意外情况,可以写的很简单(比如不会出现三三等),详情可以看这里。但是,想着可以写成一个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 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);
}
};
/**
*
* @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();
}
}
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;
}
}
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;
}
}
//public class DFA {
// public static DFA getDFA(String[] args) {
// DFA dfa = RulesLoader.loadRules("src/rules", "UTF-8");
// return dfa;
// //
//// for (String s: new String[]{"零", "五", "十", "十五", "五十五", "一百", "一百零五",
//// "一百五十", "一百五十五", "一千零五", "一千零一十", "一千零一十一", "一千一", "一千一百零三",
//// "一千零三十", "一千零三十五"}) {
//// int v = dfa.transform(s);
//// System.out.println( v );
//// }
//// System.out.println("------------------------------------------------------------");
//// for (String s: new String[]{"零零", "五五", "十十", "十五五", "五十五五"}) {
//// int v = dfa.transform(s);
//// System.out.println( v );
//// }
// }
//}
这是我写的,对万(不包括)以内的数的识别的自动机描述:
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或许有用的到的时候,先记下来吧。
时间: 2024-11-25 11:52:37