📌 문제
프로그래머스
코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.
programmers.co.kr
✅ Pre-Refactor Code
import java.util.*;
class Solution {
public String solution(String new_id) {
//1
char[] C = new_id.toLowerCase().toCharArray();
ArrayList<Character> cc = new ArrayList<>();
//2
for (char c : C) {
if (Character.isLetter(c) || Character.isDigit(c) || c == '-' || c == '_' || c == '.') {
cc.add(c);
}
}
//3
for (int i = 1; i < cc.size(); i++) {
if (cc.get(i) == '.' && cc.get(i - 1) == '.') {
cc.remove(i);
i--; // 현재 인덱스가 줄어드므로 다시 검사
}
}
//4
if (cc.size() > 0 && cc.get(0) == '.') {
cc.remove(0); // 시작의 마침표 제거
}
if (cc.size() > 0 && cc.get(cc.size() - 1) == '.') {
cc.remove(cc.size() - 1); // 끝의 마침표 제거
}
//5
if (cc.isEmpty()) {
cc.add('a');
}
//6
if (cc.size() >= 16) {
cc = new ArrayList<>(cc.subList(0, 15)); // 첫 15개 문자만 남김
if (cc.get(cc.size() - 1) == '.') {
cc.remove(cc.size() - 1); // 끝의 마침표 제거
}
}
//7
while (cc.size() < 3) {
cc.add(cc.get(cc.size() - 1)); // 마지막 문자를 반복하여 추가
}
StringBuilder result = new StringBuilder();
for (char c : cc) {
result.append(c);
}
return result.toString();
}
}
보시다시피 코드가 너무 복잡하고 효율적이지 않아서 이보다 좋은 방법이 있지 않을까 생각하며 코드를 작성했는데요..!
코드를 작성하는 당시에는 다른 방법이 생각나지 않았지만, 이후 개선해야 될 부분을 다시 생각해보니, 여러 가지 통찰이 있었습니다.
사실 처음에는 new_id라는 문자열 그대로 수정하는 방법으로 진행하고 싶었지만, replace하는 메서드가 기억이 나지 않아 어쩔 수 없이 char 배열로 고쳐서 문제를 풀어냈습니다..
그러다보니 인덱스 삭제가 문제여서 동적 배열로 고쳤고요..ㅎ
그리고 최종 결과를 생성하는 과정에서도, StringBuilder를 사용한 것이 불필요한 메모리 사용으로 이어졌습니다. 처음부터 문자 배열이 아닌 문자열 자체를 조작하는 것이 더 깔끔하고 효율적이었을 텐데, 메서드를 알지 못해 문자열로 풀어내지 못한 점이 아쉬웠습니다.
그래도 최대한 문자형 동적 배열을 쓰는 방식으로 문제를 맞췄구요!
맞춘 후에 이를 개선하고 싶어서 문자열로 풀어내는 방식을 알아보고 고쳐봤습니다!
🔄️ Refactored Code
import java.util.*;
class Solution {
public String solution(String new_id) {
//1
new_id = new_id.toLowerCase();
//2
new_id = new_id.replaceAll("[^a-z0-9._-]", "");
//3
new_id = new_id.replaceAll("\\.{2,}", ".");
//4
new_id = new_id.replaceAll("^\\.|\\.$", "");
//5
if (new_id.isEmpty()) {
new_id = "a";
}
//6
if (new_id.length() >= 16) {
new_id = new_id.substring(0, 15);
new_id = new_id.replaceAll("\\.$", "");
}
//7
while (new_id.length() < 3) {
new_id += new_id.charAt(new_id.length() - 1);
}
return new_id;
}
}
정규 표현식 설명
- \\.
- 정규 표현식에서 .(마침표)는 모든 문자와 매칭되는 특수 문자입니다. 따라서 실제 마침표 문자 자체를 찾기 위해서는 이스케이프(escape) 처리가 필요합니다.
- Java의 문자열 안에서 이스케이프 문자를 사용하려면 백슬래시(\)를 두 번 써야 하므로 \\.이 됩니다.
- 이렇게 하면 실제로 "마침표(.)"를 찾겠다는 의미가 됩니다.
- $
- $는 정규 표현식에서 "문자열의 끝"을 나타냅니다. 즉, 이 기호가 있는 경우 문자열의 마지막 위치와 매칭됩니다.
- ^
- 문자셋에서의 ^:
- [^a-z0-9._-]에서 ^는 "not"의 의미입니다. 즉, 대괄호 [] 안에 있을 때는 특정 문자들을 제외한 모든 문자를 의미합니다. 여기서는 알파벳 소문자, 숫자, 마침표, 밑줄, 빼기를 제외한 모든 문자를 찾는 것입니다.
- 문자열의 시작에서의 ^:
- ^\\.는 문자열의 시작을 나타냅니다. 이 경우, 문자열의 시작에서 마침표(.)가 있는지를 검사합니다. 여기서는 문자열이 마침표로 시작하는지를 확인하기 위한 것입니다.
- 문자셋에서의 ^:
✅ new_id = new_id.replaceAll("[^a-z0-9._-]", "");
기능: new_id에서 알파벳 소문자, 숫자, 마침표(.), 밑줄(_), 빼기(-)를 제외한 모든 문자를 제거합니다.
정규 표현식: ^는 "not"을 의미합니다. 즉, [^a-z0-9._-]는 알파벳 소문자, 숫자, 마침표, 밑줄, 빼기가 아닌 모든 문자를 찾습니다.
결과: new_id는 허용되지 않는 모든 문자가 제거된 문자열로 업데이트됩니다.
✅ new_id = new_id.replaceAll("\\.{2,}", ".");
기능: 연속된 마침표(..)를 하나의 마침표(.)로 대체합니다.
정규 표현식:
\\.는 마침표를 나타내며, {2,}는 "2개 이상"을 의미합니다.
- {n}: 정확히 n개의 반복
- {n,}: n개 이상 반복
- {n,m}: n개에서 m개까지 반복
예를 들어, \\d{2,}는 2자리 이상의 숫자를 의미하고, \\d{2,5}는 2자리에서 5자리 사이의 숫자를 의미합니다. 이런 형식을 사용하면 다양한 조건을 쉽게 표현할 수 있습니다.
결과: new_id의 연속된 마침표가 하나의 마침표로 줄어듭니다.
✅ new_id = new_id.replaceAll("^\\.|\\.$", "");
기능: new_id의 시작(^)과 끝($)에 위치한 마침표(.)를 제거합니다.
정규 표현식:
^\\.는 문자열의 시작에서 마침표를 찾습니다.
\\.$는 문자열의 끝에서 마침표를 찾습니다.
|는 "또는"을 의미하여, 두 가지 조건 중 하나를 만족하는 경우를 찾습니다.
결과: new_id의 시작과 끝에 있는 마침표가 제거됩니다.
✅ new_id = new_id.replaceAll("\\.$", "");
기능: new_id의 끝에 위치한 마침표를 제거합니다. (이 부분은 이전 코드와 중복된 기능)
정규 표현식: \\.$는 문자열의 끝에서 마침표를 찾습니다.
결과: new_id의 끝에 있는 마침표가 제거됩니다.
🔥 개선된 점
- 간결함: 첫 번째 코드에서는 정규 표현식을 사용하여 문자열을 처리하므로 코드가 더 간결하고 이해하기 쉬워졌습니다. 반면 두 번째 코드는 ArrayList를 사용하여 직접 문자를 관리하므로 더 복잡합니다.
- 성능: 정규 표현식을 사용하는 첫 번째 코드가 성능 면에서도 이점이 있습니다. 두 번째 코드에서는 리스트의 크기를 줄이기 위해 반복적으로 제거 작업을 수행해야 하므로 성능 저하가 발생할 수 있습니다.
- 메모리 관리: 첫 번째 코드는 불필요한 메모리 사용을 줄이고 문자열을 직접 다루는 방식으로 메모리 관리를 효율적으로 수행합니다.
🤔 느낀 점
이런 코드 개선을 바탕으로 첫 번째 코드에서는 정규 표현식을 적극 활용하여 문제를 훨씬 간결하고 명확하게 해결할 수 있었습니다.
아직 정규 표현식을 사용하는 부분이 매우 약하다는 것을 알아서 더 배워야겠다고 생각했습니다,,
그래도 이 과정을 통해 스스로 개선할 수 있는 방향성을 발견하게 되었고, 다음에는 더 나은 접근 방식을 고민해보아야겠다는 다짐을 하게 되었습니다!
코드를 작성하며 문제를 발견하고 해결해 나가는 과정이 이렇게 값진 것임을 다시 한번 느꼈습니다.
앞으로도 화이팅!!
'코딩 테스트 일지 📒' 카테고리의 다른 글
[프로그래머스] 프로세스 | 자료 구조, 큐 | Level.2 | JAVA 💟 반례 (2) | 2024.10.02 |
---|---|
[백준] 9093 단어 뒤집기 | 구현, 문자열 | 브론즈 Ⅰ | JAVA 💡시간복잡도 아주 간단하게 줄이는 방법 (2) | 2024.09.28 |
[백준] 3460 이진수 | 구현, 수학 | 브론즈 Ⅲ | JAVA 💡25%에 틀렸습니다. 완벽 해결법 + 1등하는 법 (0) | 2024.09.24 |
[코테/JAVA] 효율적인 출력 방법: 언제 BufferedWriter, 언제 System.out.print, 언제 StringBuilder? (0) | 2024.09.23 |
[백준] 2576 홀수 | 구현, 수학 | 브론즈 Ⅲ | JAVA 💡내가 백준 자바 부문 1등?!?! (2) | 2024.09.23 |