2019년 6월 4일 화요일

정규표현식 예제




1. IP 주소 패턴

IP 주소는 0-255까지 숫자 범위가 있기 때문에 각 자리수별로 올 수 있는 숫자를 생각하여 정규표현식으로 표현해야 한다.
 아이피 숫자가 한자리 혹은 두자리 수 일 경우에는 아무런 숫자가 와도 된다. 
 세자리 숫자일 경우에는 생각할 꺼리가 많아지는데, 맨 앞자리가 0,1,2 인 경우에 따라 각각의 케이스들을 | (pipeline) 으로 연결해 주면 된다.
 아이피 숫자는 맨 앞자리가 0이어도 유효하기 때문에 (0|1)일 경우에는 뒤에 두자리가 [0-9] 아무 숫자가 와도 된다. 맨 앞자리가 2일 경우에는 두 가지 분기로 나누어야만 하는데, 
두번째 자리가 [0-4]이면은 일의 자리는 [0-9] 아무숫자나 와도 되지만 앞자리 두개가 25일 경우 일의 자리는 0-5까지만 와야 한다. 
 아래 예시에 IP 매칭에 대한 정규표현식을 작성하였는데 두 개는 동일한 내용이다. 
 두 번째가 [0-9]를 \\d로 표현했기 때문에 더 간결해 보인다.
\\. 이 부분도 점 그 자체로 검색해야 하므로 이스케이프 문자가 붙어있다.



String pattern = "([0-9]{1,2}|(0|1)[0-9][0-9]|2[0-4][0-9]|25[0-5])(\\.([0-9]|[0-9][0-9]|(0|1)[0-9][0-9]|2[0-4][0-9]|25[0-5])){3}";

String pattern = "(\\d{1,2}|(0|1)\\d{2}|2[0-4]\\d|25[0-5])(\\.(\\d{1,2}|(0|1)\\d{2}|2[0-4]\\d|25[0-5])){3}";


2. 중복된 단어 찾기 

아래와 같은 문자열에서 여러번 중복된 단어들을 제거하고 첫번째 문자열만 남긴다. 대소문자의 구분은 없다.


test Matched matched matched matchedtest String string String matched


이걸 풀기 위해서 정규표현식의 grouping 을 이용하면 쉽게 풀린다. group() 관련 메소드는 API문서를 참고하면 된다.


연속으로 중복되는 단어들을 전부 찾아내고, 그 중에 첫번째 단어만 남겨야 한다.
패턴은 아래와 같다.


String pattern = "\\b(\\w+)(?:\\W\\1\\b)+";

하나씩 의미를 파악해보자면,

\\b(\\w+)  
\\b로 단어의 경계를 지정하고 한 글자 이상의 단어를 찾는다. 그 단어 한개가 첫번째 그룹이다. 

(?:\\W\\1\\b)+ 
(?:X) 이 문법은 이 괄호 안에 잡히는 대상은 그룹으로 잡지 않겠다는 의미이다. 또한 마지막에 + 로 되어있는데 이런 그룹이 연속으로 1개 이상이라는 의미이다.

\\W는 word가 아닌 문자열을 의미한다. 중복된 단어 사이에 공백이나 , . 등이 있는 경우도 포함하기 위함이다.
\\1 은 첫번째 그룹에서 가장 최근에 매치된 동일한 텍스트를 의미한다. 
만약 그룹이 여러개라면 \\n 이런 식으로 표현 가능하며, n번째 그룹에서 가장 최근에 매치된 텍스트를 골라낼 수 있다.
위 예제에서는 만약 단어 Matched가 매치되면 그 다음에 이 단어가 \\1자리에 위치하는 것이다.
마지막에 단어의 경계를 나타내는 \\b를 명시하지 않으면 위 예시에서 matchedtest에서 matched까지 찾아내게 된다.

이제 이 정규표현식을 통해 중복을 제거한다.
핵심은 하나의 문장을 입력받아서 하나의 문장을 Matcher가 검사하는데 하나의 문장 내에서도 while문에 Matcher로 find()를 하면서 나란히 중복되는 문자열을 찾는다. 
그리고 그 찾아낸 문자열의 m.group() 을 m.group(1) 로 replaceAll() 을 한다.

m.group()은 위 정규표현식에서 전체에 매칭되는 Full match를 의미한다.

test Matched matched matched matchedtest String string String matched

Matched matched matched 가 첫번째 m.group()이 되고 Matched는 첫번째 그룹의 값인 m.group(1)이 된다. 
String string String은 그 다음 while 루프에서 찾아지며 동일하게 첫번째 String으로 replaceAll이 될 것이다.




public class DuplicateWords {

    public static void main(String[] args) {

        String regex = "\\b(\\w+)(?:\\W\\1\\b)+";
        Pattern p = Pattern.compile(regex, Pattern.CASE_INSENSITIVE); //대소문자 구분없이 찾는다

        Scanner in = new Scanner(System.in);
        int numSentences = Integer.parseInt(in.nextLine());
        
        while (numSentences-- > 0) {
            String input = in.nextLine();
            
            Matcher m = p.matcher(input);
            
            // Check for subsequences of input that match the compiled pattern
            while (m.find()) {
                input = input.replaceAll(m.group(), m.group(1)); 
            }
            // Prints the modified sentence.
            System.out.println(input);
        }
        
        in.close();
    }
}


3. 아이디 제한 걸기

아이디 생성 조건은 다음과 같다.
- 문자열 길이는 8자 이상~ 30자 이하.
- 첫 시작 문자는 반드시 알파벳이어야 함.
- 알파벳 대소문자, 숫자, _ 가능
- 특수문자 불가

위 조건은 아래와 같이 심플하게 정규표현식으로 가능하다.


String pattern = "\\p{Alpha}[\\w]{7,29}";

7,29로 범위를 지정한 이유는 앞에 \\p{Alpha} 가 한자리를 차지하기 때문에 1씩 빼주었다. 



4. Html Tag 안의 텍스트 내용 추출하기

아래는 Tag의 내용을 추출하는 코드이다.
다만 태그의 열고 닫는 문법이 정확해야 그 내용을 추출해야한다. 만약 <tag>내용...<tag>이라면 올바른 태그 형식이 아니다.


public class Solution{
 public static void main(String[] args){
  
 Scanner in = new Scanner(System.in);
 int testCases = Integer.parseInt(in.nextLine());
 while(testCases>0){
     String line = in.nextLine();
   
            String pattern = "<(.+)>([^<>]+)</\\1>";
            Pattern p = Pattern.compile(pattern);
            Matcher m = p.matcher(line);
            boolean flag = false;
   while(m.find()){
                flag = true;
                System.out.println(m.group(2));
            }
            if(!flag)
                System.out.println("None");
   testCases--;
  }
 }
}

String pattern = "<(.+)>([^<>]+)</\\1>";

먼저 여는 태그 부분의 꺽쇠 안의 내용 그룹 한개와, content에 해당하는 부분을 2개의 그룹으로 만들고, 닫는 태그는 \\1을 이용하여 첫번째 그룹의 캡쳐된 문자열을 넣도록 하여 태그 문법이 맞도록 하였다.
content에는 < 혹은 >를 제외한 모든 문자가 들어가도록 되어있다. 
<h1><a>contents</a>invalid</h1> 이런 식의 형태에서 추출 해야 할 때 <a>태그 안에 있는 contents 문구는 유효하지만, invalid 문구는 유효하지 않도록 하는 것이 문제의 규칙이다.


댓글 없음:

댓글 쓰기