使用DFA作文本編輯器的自動提示

以前看龍書的時候,龍書提到能夠在編譯器裏用動態的生成的NFA自動機來動態匹配本身的輸入串,NFA的簡單實現其實寫起來很是簡單,可是我是實際憑感受寫完以後,卻以爲並非很是的好用,在處理本身已經輸入過的串,若是還要處理空串和一個符號對應多種路徑就勢必涉及回溯,因此我就動態生成了一個DFA,應該不是最簡的,可是也能知足需求。java

DFA狀態

package sample;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

/**
 * Dfa 狀態
 *
 * @author liufengkai
 *         Created by liufengkai on 16/7/10.
 */
public class DfaState implements Comparable<DfaState> {

    private static int DFA_ID_COUNT = 0;
    /**
     * state id
     */
    private int stateId;
    /**
     * transition set
     * char / set of dfaState
     */
    private Map<Integer, DfaState> transitionSet;

    private DfaState parentState;

    private Integer parentInput;

    /**
     * 構造方法
     *
     * @param input       輸入串
     * @param parentState 父節點
     */
    public DfaState(Integer input, DfaState parentState) {
        this.parentInput = input;
        this.parentState = parentState;
        this.stateId = DFA_ID_COUNT++;
        this.transitionSet = new HashMap<>();
    }

    /**
     * 添加一條轉移語句
     *
     * @param input 輸入字符
     * @param state 下一個狀態
     * @return 返回添加狀態
     */
    public DfaState addTransition(int input, DfaState state) {

        if (!transitionSet.containsKey(input)) {
            transitionSet.put(input, state);
        }

        return state;
    }

    public DfaState getTransitionInput(int input) {
        return getTransitionSet().get(input);
    }

    public int getStateId() {
        return stateId;
    }

    public static int getTotalNumber() {
        return DFA_ID_COUNT;
    }

    public Map<Integer, DfaState> getTransitionSet() {
        return transitionSet;
    }

    public DfaState getParentState() {
        return parentState;
    }

    @Override
    public int compareTo(DfaState o) {
        return 0;
    }

    public int getParentInput() {
        return parentInput;
    }

    /**
     * 打印狀態
     */
    public void printState() {
        System.out.println("state : " + getStateId());
        for (Integer integer : transitionSet.keySet()) {
            System.out.println("symbol: " +
                    (char) integer.intValue() + " to :" +
                    transitionSet.get(integer).getStateId());
            transitionSet.get(integer).printState();
        }
    }

    /**
     * 返回結束狀態
     *
     * @param list 傳入結束狀態
     */
    public void returnEndList(ArrayList<DfaState> list) {
        for (Integer key : transitionSet.keySet()) {
            DfaState cur = transitionSet.get(key);
            if (cur.getTransitionSet().isEmpty()) {
                list.add(cur);
            } else {
                cur.returnEndList(list);
            }
        }
    }
}

DFAState定義了不少基礎的方法,好比每一個狀態都有惟一的ID值與之對應,雖然是一個DFA可是插入的過程是發現下一個節點不一樣才分叉,由此看來每一個狀態都應該有惟一的父節點與之對應,使用了一個Map來記錄咱們有哪些路徑,還有一個方法來遞歸查找終結節點,使用這個方法來倒序查找預測分析出的字符串,這並非一個效率很高的方法,以後也會被替換。ide

DFABuilder建立DFA

package sample;

import java.util.ArrayList;
import java.util.HashMap;

/**
 * Created by liufengkai on 16/7/10.
 */
public class DfaBuilder {
    /**
     * NFA 狀態機的起始狀態
     */
    public DfaState startState = null;
    /**
     * 狀態機的當前狀態
     */
    public DfaState currentState = null;
    /**
     * 接受狀態
     */
    public HashMap<Integer, DfaState> acceptState;

    private static final int RETURN_ID = 13;

    private static final int CHANGE_LINE_ID = 10;

    private static final int TAB_ID = 9;

    private static final int SPACE_ID = 32;

    private ArrayList<Integer> endIdList;

    private DfaCallBack dfaCallBack = null;

    public DfaBuilder() {
        // parent is null
        this(new DfaState(null, null));
    }

    public DfaBuilder(DfaState startState) {
        this.startState = startState;
        this.currentState = startState;
        initial();
    }

    /**
     * 添加接受狀態
     */
    public void addAcceptState(int input, DfaState accept) {
        if (!acceptState.containsKey(input)) {
            acceptState.put(input, accept);
        }
    }

    private void initial() {
        this.acceptState = new HashMap<>();
        this.endIdList = new ArrayList<>();
        initialEndIdList();
    }


    private void initialEndIdList() {
        endIdList.add(RETURN_ID);
        endIdList.add(CHANGE_LINE_ID);
        endIdList.add(TAB_ID);
        endIdList.add(SPACE_ID);
    }

    public DfaState input(int input) {
        // parser 了全部特殊狀況 對於單詞的提示
        // 一個單詞內是不會出現空格製表符和換行的
//        System.out.println(input + "sss");
        if (endIdList.contains(input)) {
            this.currentState = startState;
            return null;
        }


        // 處理了當輸入串還在起始狀態的狀況
        if (currentState.getStateId() == startState.getStateId()) {
            return startInput(input);
        }

        // 說明狀態不在起始狀態
        DfaState tempCurrent = currentState.getTransitionInput(input);
        if (tempCurrent == null) {
            tempCurrent = new DfaState(input, currentState);
            currentState.addTransition(input, tempCurrent);
        } else {
            if (dfaCallBack != null) dfaCallBack.onMultipleSetBack(tempCurrent, tempCurrent.getTransitionSet());
        }
        currentState = tempCurrent;
        return currentState;
    }

    /**
     * 處理還在輸入串起始狀態的狀況
     *
     * @param input 輸入
     * @return current狀態
     */
    public DfaState startInput(int input) {
        DfaState current;
        // 轉入第一個起始狀態
        if (!acceptState.containsKey(input)) {
            current = new DfaState(input, currentState);
            this.addAcceptState(input, current);
        } else {
            current = acceptState.get(input);
            if (dfaCallBack != null) dfaCallBack.onMultipleSetBack(current, current.getTransitionSet());
        }
        this.currentState = current;
        return current;
    }

    public void setDfaCallBack(DfaCallBack dfaCallBack) {
        this.dfaCallBack = dfaCallBack;
    }

    public void printDfa() {
        for (Integer integer : acceptState.keySet()) {
            System.out.println("接受狀態 " + acceptState.get(integer).getStateId());
            acceptState.get(integer).printState();
        }
    }

    /**
     * 重設startState
     */
    public void resetStartState() {
        this.currentState = startState;
    }
}

DFABuilder定義了DFA的開始狀態和如今的匹配的狀態還定義了一些如何繼續處理節點的方法。ui

重點應該看以下的這個方法:this

public DfaState input(int input) {
        // parser 了全部特殊狀況 對於單詞的提示
        // 一個單詞內是不會出現空格製表符和換行的
//        System.out.println(input + "sss");
        if (endIdList.contains(input)) {
            this.currentState = startState;
            return null;
        }


        // 處理了當輸入串還在起始狀態的狀況
        if (currentState.getStateId() == startState.getStateId()) {
            return startInput(input);
        }

        // 說明狀態不在起始狀態
        DfaState tempCurrent = currentState.getTransitionInput(input);
        if (tempCurrent == null) {
            tempCurrent = new DfaState(input, currentState);
            currentState.addTransition(input, tempCurrent);
        } else {
            if (dfaCallBack != null) dfaCallBack.onMultipleSetBack(tempCurrent, tempCurrent.getTransitionSet());
        }
        currentState = tempCurrent;
        return currentState;
    }

endIdList裏面包含一些提示匹配方法結束的標誌,好比空格,換行,回車,製表符,每次匹配到這個的時候就把狀態切換回初始狀態。若是輸入串還在初始狀態即第一次輸入,就添加一個接受狀態,其他的就找到對應的DFAState步進或是插入就能夠了。另外還定義了一個接口用於返回數據進行處理。code

package sample;

import java.util.Map;

/**
 * Created by liufengkai on 16/7/10.
 */
public interface DfaCallBack {
    void onMultipleSetBack(DfaState current, Map<Integer, DfaState> states);
}

返回當前節點和接續的狀態集。blog

DfaBuilder builder = new DfaBuilder();

        builder.setDfaCallBack((current, states) -> {

            System.out.println("current list " + getCurrentString(current));
            ArrayList<DfaState> list = new ArrayList<>();
            for (Integer key : states.keySet()) {
                states.get(key).returnEndList(list);
            }

            for (DfaState state : list) {
                System.out.println("prediction list " + getCurrentString(state));
            }
        });

    public static String getCurrentString(DfaState currentState) {
        String tempString = "";
        DfaState tempState = currentState;
        while (tempState.getParentState() != null) {
            tempString = (char) tempState.getParentInput() + tempString;
            tempState = tempState.getParentState();
        }
        return tempString;
    }

使用的時候使用相似這樣的方式就能實現簡單的預測提示了。遞歸

效果圖

我打算看看javaFx,寫一個帶gui的,不過如今有點懶,還沒寫完。接口

pic

相關文章
相關標籤/搜索