[Java] 패턴 매칭을 위한 유한 상태 머신(Finite state machine) 사용

이 글의 목적

  • FSM(Finite state machine)이 무엇인지, Pattern이 FSM이 되는 이유에 대해 알 수 있다.
  • 번역이 매끄럽지 않을 수 있습니다. 문맥상 이해 부탁드립니다.

유한 상태머신은 Pattern matching에 굉장히 유용한 툴이다. Pattern mathcing을 위해 어떻게 FSM을 사용하는지와 JAVA로 쓰여진 예시를 보겠다.

Finite State Machine (유한상태머신)

  • 유한상태머신은 수학 방향 그래프
  • State는 여기서 정점을 뜻함
  • 전이는 그래프 전이

Sample Problem

String이 특정 패턴으로 끝나는지 확인하고 싶다고 가정해 보자. 주어진 String이 @로 시작해서 숫자, 마지막으로 #로 끝나는지 여부를 알고 싶다. 아래는 3가지 예시이다.

  • 예시
  • @3#
  • @12344#
  • @0112#

여기서 우리는 String이 이런 패턴으로 끝나는지 알고 싶다. 우리는 이 패턴을 모델링하는 유한 상태 머신(FSM)을 만들 수 있다.

Non-Finite State Machine

만약 FSM 솔루션을 쓰지 않는다면

  • @와 #를 찾고
  • 그 사이에 문자를 넣고 변환하여 성공적으로 변환되면 패턴을 가질 수 있는 코드를 작성할 수 있다.

성공적으로 변환되지 않으면 문자가 없을 수도 있다고 가정하면서 말이다. 하지만 일련의 숫자가 정말 큰 정수가 될 수 있다. 그러니 이건 쓰기 쉬운 알고리즘은 아니다.

FSM

FSM 접근 방식을 사용하여 패턴의 각 State에서 시작한다. State 0에서 시작하고 만약 다음 문자가 @이면 State 1로 이동한다. 따라서 전이선을 볼 때마다 전이선에 있는 문자가 전이 방법을 보여준다. 다음 문자를 읽은 후에 @이면 State 1로 전환한다.

State 1에서는 0부터 9까지의 숫자 중 하나를 얻으면 State 2로 전환된다. State 2에서 0부터 9까지의 숫자 중 하나를 얻으면 State 2로 유지되므로 두 자리 숫자 또는 세 자리 숫자 또는 100이 될 수 있다. State 2에서 다음 자리가 @이면, @가 패턴의 시작이므로 State 1로 돌아간다. 왜냐하면 2개의 @ 뒤에 숫자와 숫자 기호가 올 수 있기 때문이다.

State 2에서 #가 보이면 State 3으로 이동한다. State 3에서 @ 기호를 보면 패턴을 시작하는 State 1로 돌아간다. 다음 문자가 전이선에 없는 문자인지 확인되면 자동으로 0 상태로 되돌아간다. 그래서 우리가 State 2에 있고 다음 문자가 문자 B이면, 문자 B에 대해 State 2를 떠나는 전이선이 없기 때문에 그것은 State 0으로 돌아간다는 것을 암시한다. 그럼 어떻게 위 로직이 구현되었는지 자바코드를 확인해보자.

FSM 구현 코드

  • Variables
  • a: A1@312# 값을 가진 String이 있고, 이 String은 패턴을 가지고 있는지 우리가 확인할 대상이다. 이 string은 @ 기호 뒤에 숫자, 그리고 끝에 #가 있다.
  • digits: 간단하게 숫자를 저장하는 변수
  • state: 초기값 0

왼쪽부터 오른쪽까지 문자열의 각 문자를 반복하는 for 루프가 있다. 그래프에서와 같이 state 0에 있고 다음 문자가 @ 기호이면 state 1로 이동한다. 그런 다음 for 내부 이후 소스들은 if/else 구문 이므로 건너뛴다.

지금 state 1에 있으면 다음 문자를 얻는다. 다음 문자가 숫자라면 state 2로 이동한다. @ 기호인 경우 state 1로 유지되고 다른 값인 경우 state 0으로 돌아간다. state 2인 경우 다음 문자가 #이면 state 3로 이동, 숫자면 state 2에 그대로, @이면 state 1으로 이동하고, 다른 값인 경우 모두 state 0로 돌아간다. 마지막으로 state 3인 경우 다음 문자가 @이면 state 1로 이동, 다른 값인 경우 모두 state 0로 돌아간다.

그리고 for문이 끝나고 state가 3에 있는 경우 스트링이 패턴과 매치된다는 걸 알 수 있다. state가 3이 아닌 경우 스트링이 패턴과 맞지 않음을 의미한다.

public class FSMPatternExample {

    public static void main(String[] args) {
        String s = new String("A1@312#");
        String digits = new String("0123456789");
        int state = 0;

        for(int idx = 0; idx < s.length(); idx++){
            if(state == 0){
                if(s.charAt(idx) == '@')
                    state = 1;
            }else if(state == 1){
                if(digits.indexOf(s.charAt(idx)) != -1){
                    state = 2;
                }else if(s.charAt(idx) == '@'){
                    state = 1;
                }else{
                    state = 0;
                }
            }else if(state == 2){
                if(s.charAt(idx) == '#'){
                    state = 3;
                }else if(digits.indexOf(s.charAt(idx)) != -1){
                    state = 2;
                }else if(s.charAt(idx) == '@'){
                    state = 1;
                }else{
                    state = 0;
                }
            }else if(state == 3){
                if(s.charAt(idx) == '@'){
                    state = 1;
                }else{
                    state = 0;
                }
            }
        }

        if(state == 3){
            System.out.println("It matches");
        }else{
            System.out.println("It does not match");
        }
    }
}

위 소스코드를 수행하면 It matches가 출력된다. String을 바꿔가며 수행해보면 각 String 별 수행결과가 달라진다.

  • A1@3555555555512# → It matches
  • A13555555555512 → It does not match
  • A135555@r55555512# → It does not match
  • A135555@@@@@55555512# → It matches

위 케이스 외에도 다양한 문자를 통해 테스트 수행이 가능하다. 이것이 String 패턴 일치 여부 검사에 FSM을 사용하는 방법이며 코드를 작성할 때 이러한 유형의 문제에 대해 매우 간단하고 매우 멋지다.

참고