숫자 문자열과 영단어

나의 기록·2026년 7월 20일

코딩테스트

목록 보기
30/35

문제

프로그래머스 - 숫자 문자열과 영단어

숫자의 일부 자릿수가 영단어(zero~nine)로 바뀐 문자열 s가 주어질 때, 원래 숫자를 구하는 문제.

  • s는 "zero" 또는 "0"으로 시작하지 않음
  • 결과값은 1 ~ 2,000,000,000 사이의 정수

처음 작성한 코드

class Solution {
    public int solution(String s) {
        String[] word = new String[]{"zero","one","two","three","four","five","six","seven","eight","nine","ten"};

        for(int i=0; i<word.length; i++){
            if(s.contains(word[i])){
                s=s.replaceAll(word[i],String.valueOf(i));
            }
        }

        return Integer.valueOf(s);
    }
}

주어진 예시 4개는 모두 통과했지만, 코드 리뷰를 받으며 몇 가지를 다시 짚어봐야 했다.

1. 없는 단어를 넣었다 — "ten"

문제의 대응표는 0~9(zero~nine)까지만 있는데, 배열에 "ten"을 실수로 추가했다. 입력값에 "ten"이라는 문자열이 등장할 일이 없어서 결과에 영향은 없었지만, 문제 요구사항에 없는 값을 넣은 건 명백한 실수였다.

→ 배열에서 "ten" 제거.

2. contains로 먼저 확인할 필요가 있을까?

if(s.contains(word[i])){
    s=s.replaceAll(word[i],String.valueOf(i));
}

이렇게 짜면 문자열을 contains로 한 번, replaceAll로 또 한 번 — 두 번 훑게 된다. 그런데 애초에 word[i]s에 없으면 replace/replaceAll을 호출해도 아무 일도 일어나지 않는다 (매칭되는 게 없으니 원본 그대로 반환). 즉 contains 체크는 로직상 불필요한 중복 작업이었다.

contains 체크 제거, 바로 치환.

3. replace vs replaceAll

처음엔 "같은 숫자가 여러 개 들어있을 수 있어서 replaceAll을 써야 한다"고 생각했다. 하지만 이건 틀린 이유였다 — Java의 String.replace(CharSequence, CharSequence)도 매칭되는 모든 부분을 치환한다. "All"이 이름에 붙어야만 전부 바뀌는 게 아니다.

진짜 차이는 이거다:

  • replace(a, b) : a를 리터럴 문자열 그대로 매칭해서 치환
  • replaceAll(regex, b) : 첫 번째 인자를 정규식으로 해석해서 매칭

지금 사용하는 단어들(zero, one, two...)에는 정규식 특수문자(., *, + 등)가 없어서 두 메서드의 결과는 같지만, 의도가 "정확히 이 문자열을 찾아 바꾸는 것"이라면 replace가 더 정확한 선택이다. 불필요하게 정규식 엔진을 거칠 이유가 없다.

replaceAllreplace로 변경.

4. 단어들끼리 서로 겹치는 부분 문자열이 있을까?

10개 단어를 순서대로 치환하는데, 만약 어떤 단어가 다른 단어의 부분 문자열이라면(예: A가 B 안에 포함) 치환 순서에 따라 결과가 달라질 수 있다. 이걸 눈으로 일일이 대조하려니 비효율적이고 실수하기 쉬웠다.

그래서 직접 검증 코드를 짜서 확인했다:

for(int i=0; i<word.length; i++){
    for(int j=0; j<word.length; j++){
        if(word[i] != word[j]){
            System.out.println(word[i].contains(word[j]));
        }
    }
}

여기서 문자열 비교에 !=를 썼는데, Java에서 문자열은 객체이기 때문에 ==/!=는 내용이 아니라 참조(주소)를 비교한다. 지금은 배열이 문자열 리터럴로 초기화돼서 상수 풀(string pool)에 의해 우연히 같은 참조를 가리켜 결과가 맞게 나왔지만, 만약 new String("one")처럼 만들어졌다면 내용이 같아도 !=가 의도와 다르게 동작했을 것이다.

→ 문자열 내용 비교는 항상 .equals()를 사용해야 한다. (또는 애초에 인덱스 i != j로 비교했으면 더 간단했다.)

검증 결과 겹치는 단어는 없었다. 따라서 치환 순서는 결과에 영향을 주지 않는다는 게 확인됐고, 이 검증 코드는 확인용이었으므로 최종 제출 코드에서는 제거했다.

최종 코드

class Solution {
    public int solution(String s) {
        String[] word = new String[]{"zero","one","two","three","four","five","six","seven","eight","nine"};

        for(int i=0; i<word.length; i++){
            s = s.replace(word[i], String.valueOf(i));
        }

        return Integer.valueOf(s);
    }
}

배운 것

  • 문제 요구사항에 없는 값을 임의로 추가하지 말 것 (ten)
  • 이미 치환 함수 자체가 "없으면 그대로 반환"하는 동작을 하므로, 사전 존재 여부 체크는 대부분 불필요한 중복
  • replace/replaceAll의 차이는 "전체 치환 여부"가 아니라 "리터럴이냐 정규식이냐"
  • Java에서 문자열 내용 비교는 .equals(), ==/!=는 참조 비교라는 걸 다시 확인
  • 눈으로 조합을 다 따지기 어려우면 직접 검증 코드를 짜서 확인하는 게 안전하다
profile
뭐든 남겨본다

0개의 댓글