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

本文介绍了一个用于将汉字形式数字转换为整数的自动机实现。该实现考虑了各种特殊情况,并通过构建非确定有限自动机(NFA)并将其转换为确定有限自动机(DFA)来处理复杂的转换逻辑。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值