NFA转换成DFA——汉字形式数字转换成整数数字

偶然间遇到了一个需求:汉字形式数字转换成整数数字。如果不处理意外情况,可以写的很简单(比如不会出现三三等),详情可以看这里。但是,想着可以写成一个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

NFA转换成DFA——汉字形式数字转换成整数数字的相关文章

爪哇国新游记之二十----将数字转换成汉子大写形式

/** * 辅助类 * 用于记载字符和位置 * */ class CharPos{ char c; int pos; public CharPos(char c,int pos){ this.c=c; this.pos=pos; } } /** * 将数字转化成汉字大写形式 * */ public class CnNumMaker { private String integerPart;// 整数部分 private String decimalPart;// 小数部分 private Sta

中文数字转换成阿拉伯数字(一千二百三十四万五千六百七十八--&gt;12345678)

昨天老大问我又没有写过中文数字转换成阿拉伯数字,我说没有,我说那应该简单啊,就是将中文对应的数字换成阿拉伯数字就好了啊,比如一就换成1,二就换成2…十换成10.可是那么问题来了…… 一十二呢…不能是1102吧…这不就坑爹了吗?一百万呢………所有我苦苦思索,花费了我差不多半天的时间,终于写出了下面的程序. 1 public static void main(String[] args){ 2 3 Map<Character, String> numberMap = new HashMap<

数字转换成中文大小写、金额大小写

将数字转换成中文大小写.金额大小写  /// <summary> /// 将数字转换成中文大写 /// </summary> /// <param name="Value">数字</param> /// <param name="bToUpper">是否转换成汉字大写 true表示大写 false表示小写</param> /// <param name="bMoney"

java 数字转换成字符串

一.各种数字类型转换成字符串型:  public static void main(String[] args) { double value = 123456.123; String str = String.valueOf(value); // 其中 value 为任意一种数字类型. System.out.println("字符串str 的值: " + str); //字符串str 的值: 123456.123 } 二.字符串型转换成各种数字类型: public static vo

将json形式的时间字符串转换成正常的形式

//重写time的getter方法 //判断addtime和当期的时间差 // < 60分钟  返回 n分钟前 // > 60分钟  返回 n小时前 //超过24小时  返回 -月-日 - (NSString *)time{ // 1 先把json中的数字转换成日期对象 //把拿到的json中的时间的字符串转换成我们熟悉的时间格式 NSDate *date = [NSDate dateWithTimeIntervalSince1970:[self.addtime intValue]]; //

在C#中将数字转换成中文

上篇我们讲了在MSSQL中将数字转换成中文,这篇我们讲讲在C#中将数字转换成中文 下篇将讲一下如何将金额转换成中文金额,废话不多说,具体代码如下: /// <summary> /// 数字转中文 /// </summary> /// <param name="number">eg: 22</param> /// <returns></returns> public string NumberToChinese(in

C#字母转换成数字/数字转换成字母 - ASCII码转换

字母转换成数字 byte[] array = new byte[1];   //定义一组数组arrayarray = System.Text.Encoding.ASCII.GetBytes(string); //string转换的字母int asciicode = (short)(array[0]); ASCII码 = Convert.ToString(asciicode); //将转换一的ASCII码转换成string型 数字转换成字母byte[] array = new byte[1];ar

java 数字转换成字符串与数字转换成字符串

各种数字类型转换成字符串型: String s = String.valueOf( value); // 其中 value 为任意一种数字类型. 字符串型转换成各种数字类型: String s = "169"; byte b = Byte.parseByte( s ); short t = Short.parseShort( s ); int i = Integer.parseInt( s ); long l = Long.parseLong( s ); Float f = Float

js 将数字转换成人民币大写的方法

//将数字转换成人民币大写的方法 var digitUppercase = function (n) { var fraction = ['角', '分']; var digit = [ '零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖' ]; var unit = [ ['元', '万', '亿'], ['', '拾', '佰', '仟'] ]; var IsNum = Number(n); if (!isNaN(IsNum)) { var hea