숫자의 일부 자릿수가 영단어(zero~nine)로 바뀐 문자열 s가 주어질 때, 원래 숫자를 구하는 문제.
s는 "zero" 또는 "0"으로 시작하지 않음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개는 모두 통과했지만, 코드 리뷰를 받으며 몇 가지를 다시 짚어봐야 했다.
문제의 대응표는 0~9(zero~nine)까지만 있는데, 배열에 "ten"을 실수로 추가했다. 입력값에 "ten"이라는 문자열이 등장할 일이 없어서 결과에 영향은 없었지만, 문제 요구사항에 없는 값을 넣은 건 명백한 실수였다.
→ 배열에서 "ten" 제거.
contains로 먼저 확인할 필요가 있을까?if(s.contains(word[i])){
s=s.replaceAll(word[i],String.valueOf(i));
}
이렇게 짜면 문자열을 contains로 한 번, replaceAll로 또 한 번 — 두 번 훑게 된다. 그런데 애초에 word[i]가 s에 없으면 replace/replaceAll을 호출해도 아무 일도 일어나지 않는다 (매칭되는 게 없으니 원본 그대로 반환). 즉 contains 체크는 로직상 불필요한 중복 작업이었다.
→ contains 체크 제거, 바로 치환.
replace vs replaceAll처음엔 "같은 숫자가 여러 개 들어있을 수 있어서 replaceAll을 써야 한다"고 생각했다. 하지만 이건 틀린 이유였다 — Java의 String.replace(CharSequence, CharSequence)도 매칭되는 모든 부분을 치환한다. "All"이 이름에 붙어야만 전부 바뀌는 게 아니다.
진짜 차이는 이거다:
replace(a, b) : a를 리터럴 문자열 그대로 매칭해서 치환replaceAll(regex, b) : 첫 번째 인자를 정규식으로 해석해서 매칭지금 사용하는 단어들(zero, one, two...)에는 정규식 특수문자(., *, + 등)가 없어서 두 메서드의 결과는 같지만, 의도가 "정확히 이 문자열을 찾아 바꾸는 것"이라면 replace가 더 정확한 선택이다. 불필요하게 정규식 엔진을 거칠 이유가 없다.
→ replaceAll → replace로 변경.
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);
}
}
replace/replaceAll의 차이는 "전체 치환 여부"가 아니라 "리터럴이냐 정규식이냐".equals(), ==/!=는 참조 비교라는 걸 다시 확인