코드의 여백

[프로그래머스] Lv. 2 신규 아이디 추천(Kotlin)

by rowing0328
https://school.programmers.co.kr/learn/courses/30/lessons/72410
 

프로그래머스

SW개발자를 위한 평가, 교육, 채용까지 Total Solution을 제공하는 개발자 성장을 위한 베이스캠프

programmers.co.kr

 

정답 코드 1 : 성능 개선 전

class Solution {

    fun solution(new_id: String): String {
        var answer = new_id.lowercase() // 1단계: 소문자로 변환

        answer = answer.replace(Regex("[^-_.a-z0-9]"), "") // 2단계: 허용되지 않은 문자 제거
        answer = answer.replace(Regex("[.]{2,}"), ".") // 3단계: 연속된 마침표를 하나로 치환
        answer = answer.replace(Regex("^[.]|[.]$"), "") // 4단계: 처음과 끝의 마침표 제거

        if (answer.isEmpty()) { // 5단계: 빈 문자열이면 "a" 추가
            answer = "a"
        }

        if (answer.length >= 16) { // 6단계: 길이가 16자 이상이면, 첫 15자만 남기고 끝의 마침표 제거
            answer = answer.substring(0, 15).replace(Regex("[.]$"), "")
        }

        while (answer.length <= 2) { // 7단계: 길이가 2자 이하라면, 마지막 문자를 반복해서 길이 3까지 확장
            answer += answer.last()
        }

        return answer
    }
    
}

 

실행 결과 : 성능 개선 전

 

설명

정규 표현식
특정한 규칙을 가진 문자열의 집합을 다루는 데 사용하는 형식 언어이다.
다양한 프로그래밍 언어에서 문자열의 검색과 치환을 위해 정규 표현식을 지원한다.

 

Kotlin에서 Regex 객체를 반복적으로 생성하면 성능이 떨어질 수 있다.

특히 데이터가 많거나 정규식을 여러 번 호출하는 경우, 매번 정규식 객체를 생성하고 컴파일하면서 불필요한 리소스 소비가 발생한다.

 

answer = answer.replace(Regex("[^-_.a-z0-9]"), "") // 정규식 객체 매번 생성
answer = answer.replace(Regex("[.]{2,}"), ".") 
answer = answer.replace(Regex("^[.]|[.]$"), "")

이 코드는 내부적으로 java.util.regex.Pattern을 사용하지만, Kotlin에서는 Regex 객체가 캐싱되지 않고 반복 생성되며, 이 과정에서 정규식 컴파일 비용이 누적된다.

 

Java와 Kotlin의 차이

  • Java : replaceAll 메서드는 내부적으로 Pattern.compile()을 호출하며, 정규식을 캐싱한다.
  • Kotlin : Regex 객체는 별도의 캐싱 없이 매번 새로운 객체를 생성한다. 이로 인해 반복 호출 시 성능이 저하된다.

 

이 문제를 해결하기 위해 정규식을 companion object에 선언하고 재사용하도록 수정했다.

 

정답 코드 2 : 성능 개선 후

class Solution {
    
    companion object {
        val regexAllowed = Regex("[^-_.a-z0-9]") // 허용되지 않은 문자 제거
        val regexDots = Regex("[.]{2,}")         // 연속된 마침표 제거
        val regexEdgeDots = Regex("^[.]|[.]$")  // 처음과 끝의 마침표 제거
        val trailingDotRegex = Regex("[.]$")    // 마지막 마침표 제거
    }

    fun solution(new_id: String): String {
        var answer = new_id.lowercase()

        answer = answer.replace(regexAllowed, "") // 2~4단계: 정규식을 캐싱한 객체로 처리
            .replace(regexDots, ".")
            .replace(regexEdgeDots, "")

        if (answer.isEmpty()) { // 5단계: 빈 문자열 처리
            answer = "a"
        }

        if (answer.length >= 16) { // 6단계: 길이 제한 및 끝 마침표 제거
            answer = answer.substring(0, 15).replace(trailingDotRegex, "")
        }

        while (answer.length <= 2) { // 7단계: 최소 길이 확장
            answer += answer.last()
        }

        return answer
    }
    
}

 

실행 결과 : 성능 개선 후

 

마무리

정규식 캐싱을 통해 약 40%의 성능 개선을 확인할 수 있었다.

작은 변환처럼 보이지만, 정규식 캐싱은 실제로 큰 차이를 만들어낼 수 있었다.

 

이번 문제를 통해 캐싱이 얼마나 중요한 성능 최적화 기법인지 다시 한번 확인할 수 있었다.

 

참고 자료 :

취업과 이직을 위한 프로그래머스 코딩 테스트 문제 풀이 전략 : 자바 편 | 김현이

 

프로그래머스 코딩 테스트 문제 풀이 전략: 자바 편 | 김현이 - 교보문고

프로그래머스 코딩 테스트 문제 풀이 전략: 자바 편 | 핵심 개념, 프로그래머스에서 선별한 79개 문제 풀이, PCCP 대비까지! 합격에 한 걸음 더 가까워지는 실전형 코딩 테스트 문제 풀이 가이드개

product.kyobobook.co.kr

블로그의 정보

코드의 여백

rowing0328

활동하기