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 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或許有用的到的時候,先記下來吧。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章