码迷,mamicode.com
首页 > 其他好文 > 详细

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

时间:2016-05-16 13:01:02      阅读:230      评论:0      收藏:0      [点我收藏+]

标签:

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

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

标签:

原文地址:http://blog.csdn.net/wty__/article/details/51423537

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!