Skip to main content

39. [Java] 세 개의 구분자 - 정규식

https://school.programmers.co.kr/learn/courses/30/lessons/181862

  • 임의의 문자열이 주어졌을 때 문자 "a", "b", "c"를 구분자로 사용해 문자열을 나누고자 한다.
  • 예를 들어 주어진 문자열이 "baconlettucetomato"라면 나눠진 문자열 목록은 ["onlettu", "etom", "to"]
  • 문자열 myStr이 주어졌을 때 위 예시와 같이 "a", "b", "c"를 사용해 나눠진 문자열을 순서대로 저장한 배열을 return
    • 단, 두 구분자 사이에 다른 문자가 없을 경우에는 아무것도 저장하지 않으며,
    • return할 배열이 빈 배열이라면 ["EMPTY"]를 return

  • 1 ≤ myStr의 길이 ≤ 1,000,000
    • myStr은 알파벳 소문자로 이루어진 문자열

정답코드

import java.util.*;

class Solution {
    public String[] solution(String myStr) {
        // 정규표현식 a,b,c 기준으로 분리
        String[] splited = myStr.split("[abc]");
        
        // 빈 문자열 제거하기
        List<String> list = new ArrayList<>();
        for (String s : splited) {
            if (!s.isEmpty()) {
                list.add(s);
            }
        }
        
        // 결과 비어있는 경우 return "EMPTY"
        if (list.isEmpty()) {
            return new String[]{"EMPTY"};
        }
        
        return list.toArray(new String[0]);
    }
}
String[] splited = myStr.split("[abc]");
  • split(String regex)는 정규 표현식(Regex)을 기준으로 문자열을 나누어 문자열 배열로 반환한다.
  • "[abc]"는 정규식에서 'a' 또는 'b' 또는 'c' 중 하나를 의미한다.
  • 따라서 이 코드는 split() 메서드로 문자열 myStr'a', 'b', 'c' 중 하나라도 등장하면 그 위치에서 문자열을 분리한다.

split() 이후 빈 문자열을 제거하는 이유

자바의 split() 메서드는 지정한 구분자 기준으로 문자열을 나눌 때, 구분자 자체는 결과 배열에 포함되지 않는다. 이 문제에서 split("[abc]")은 "a", "b", "c"를 기준으로 나누지만, 연속되거나 앞뒤에 아무것도 없을 경우 빈 문자열이 생긴다. 그래서 isEmpty() 체크로 실제 값이 있는 문자열만 리스트에 저장해야 우리가 원하는 결과가 나온다.

예를 들어 다음과 같은 문자열이 있을 때:

String myStr = "baconlettucetomato";
→ split 결과: ["", "onlettu", "", "etom", "", "to", ""]
→ 빈 문자열 제거: ["onlettu", "etom", "to"]
→ 최종 반환
String myStr = "abcabc";
→ split 결과: ["", "", "", "", "", "", ""]
→ 빈 문자열 제거 → []
→ ["EMPTY"] 리턴

이처럼 "a", "b", "c"는 구분자로 잘렸고, a와 b 사이처럼 아무 문자도 없는 경우에는 빈 문자열("")이 결과에 포함된다. 따라서 우리가 원하는 실제 유효한 문자열만 추출하려면, split() 이후 빈 문자열을 필터링해서 제거해주는 작업이 꼭 필요하다.

for (String s : splited) {
    if (!s.isEmpty()) {
        list.add(s);
    }
}

Stream 으로 푼 코드 (프로그래머스 장홍범 님 외 3인의 코드)

import java.util.Arrays;

class Solution {
    public String[] solution(String myStr) {
        String[] arr = Arrays.stream(myStr.split("[abc]+"))
                             .filter(str -> !str.isEmpty())
                             .toArray(String[]::new);
        return arr.length == 0 ? new String[] { "EMPTY" } : arr;
    }
}
  • myStr.split("[abc]+")
    • 'a', 'b', 'c' 중 하나 이상(+)이 등장하는 부분을 기준으로 문자열을 분리한다.
    • 예: "abcbaconcabc"["", "", "on", "", "on"]
    • + 때문에 연속된 구분자에도 대응 가능하다
  • Arrays.stream(...)
    • 배열을 스트림으로 변환
  • .filter(str -> !str.isEmpty())
    • 빈 문자열 제거
  • .toArray(String[]::new)
    • 최종적으로 필터링된 값을 다시 String[] 배열로 변환

정답코드보다 더 간단한 코드 (프로그래머스 김가영님 코드)

import java.util.Arrays;
class Solution {
    public String[] solution(String myStr) {
        myStr = myStr.replaceAll("[a|b|c]+", ",");
        myStr = myStr.charAt(0) == ',' ? myStr.substring(1) : myStr;
        myStr = myStr.equals("") ? "EMPTY" : myStr;

        return myStr.split("[,]");
    }
}
myStr = myStr.replaceAll("[a|b|c]+", ",");
  • "a", "b", "c" 중 하나 이상 연속된 구간을 콤마(,)로 치환한다.
  • 예: "abcbaconcabc"",onlettu,tomato,"
myStr = myStr.charAt(0) == ',' ? myStr.substring(1) : myStr;
  • 문자열이 콤마로 시작하는 경우 첫 글자를 잘라낸다.
myStr = myStr.equals("") ? "EMPTY" : myStr;
  • 문자열 전체가 "a", "b", "c"만으로 구성되어 있었다면 치환 후 빈 문자열이 되는데 이 때 "EMPTY" 문자열을 반환한다.
return myStr.split("[,]");
  • 최종적으로 콤마를 기준으로 분리하여 배열로 반환한다.

참고 자료 : 프로그래머스 좋아요 가장 많이 받은 코드