본문 바로가기

Coding Test

Java 코딩테스트 연습 13일차 (프로그래머스 스쿨 Lv.0, 1183점)

 

 

옹알이 (1)

 

 

 

 

대망의 정답률 29%짜리 문제다. 원래 계획은 하루종일 이 문제의 풀이를 고민하며 보내는 거였다. 그런데 허무하게도 생각보다 너무 금방 풀어 버렸다. 정답률이 왜 이렇게까지 낮은지 사실 잘 모르겠다.

 

하지만 이 문제를 풀면서, 교재에서 Lv.2 문제를 풀기 전에 Java를 더 공부해 둬야 할 필요성을 느꼈다. 그래서 원래 계획과 달리 정답률 높은 순으로 세 문제 더 풀게 되었다. 내일도 정답률 높은 순 세 문제를 더 풀고 남은 시간에 Java를 더 공부한 뒤, 모레부터 코딩테스트 문제풀이 전략 교재의 문제를 풀 생각이다.

 

처음 떠올린 방법은 contains() 메소드를 이 4가지 문자열들을 포함하는지 검사하고, 길이를 이용해 조건식을 만드는 것이었다. 문자열 종류가 4개이므로 한 문자열만 포함했을 때 4C1 = 4, 두 문자열을 포함했을 때 4C2 = 6, 세 문자열을 포함했을 때 4C3 = 4, 모두 포함했을 때 4C4 = 1 이므로, 일일이 적어 주어도 총 15개케이스로 할 만하다. 이후 예를 들어 ayaye를 검사한 케이스라면 if (babbling[i].length() == 5) { answer += 1} 같은 조건식으로 계산해 주면 될 것이다. 직접 해 본 것이 아니라 확실하지는 않다. 바로 더 간단한 방법이 떠올랐기 때문이다.

 

다시 떠올린 방법은 "aya", "ye", "woo", "ma"를 모두 "0"으로 바꾼 뒤 contains 메소드와 저번에 다른 풀이에서 본 정규 표현식을 이용해 if (!babbling[i].contains("[a-z]") 조건식을 사용하는 것과, 모두 ""로 바꾼 뒤 if (babbling[i].length() == 0) 조건식을 사용하는 것이었다. 후자가 더 심플해 보여서 먼저 시도해 봤다.

 

 

 

 

어김없이 첫 시도는 실패했다. 원인을 고심하다, "yayae" 같은 문자열에서 가운데 aya를 먼저 ""로 바꿔 버리면 "ye"가 남아 다시 ""로 바뀌어 길이0이 된다는 사실을 깨달았다. 그래서 그냥 "0"으로 대체하는 방법을 사용하기로 했다.

 

 

 

 

소문자 제한을 깜빡 잊고 A-Z까지 넣어 버린 모습이다. 그런데 answer는 계속 카운트되는 모양이다. a부터 z까지 알파벳 중 하나라는 뜻이 아니었나? 분명 알파벳이 포함되어 있는데도 왜 카운트가 올라가는지 모르겠다.

 

 

 

 

알파벳을 전부 1로 바꾸고 contains("1")로 시도해 봤다. 여전히 안 된다. 이 와중에 replace의 숫자를 줄였다. 불필요하게 많이 선언했다. 다 풀고 생각해 보니 x 하나만 가지고도 가능한데 쓸데없이 다른 변수들을 선언했다.

 

 

 

 

contains() 메소드가 문제인가 싶어 indexOf 메소드로 바꿔 봤다. 여전히 안 된다. 나는 이 정규표현식을 아예 잘못 생각하고 있었던 것이다. 역시 지나가다 본 것을 직접 활용하는 데에는 한계가 있다. 정규 표현식을 공부한 후에 다시 복습해 봐야겠다.

 

 

 

 

결국 처음에 했던 방법과 적절히 섞어서 "0"을 다시 ""으로 만들어 준 뒤 길이를 이용했다. 통과는 했지만 뭔가 찜찜하다. 이 의문을 해결하지 않고 넘어갈 수는 없다. 바로 구글링을 해 봤다.

 

다음은 구글에 contains("[a-z]") 라고 치면 첫 번째로 나오는 게시글의 질문과 답변이다.

 

 

 

 

출처 : https://stackoverflow.com/questions/24086968/tell-if-string-contains-a-z-chars

 

 

contains 메소드에서 오류가 발생하지 않았기 때문에 문자 여러 개 중 하나찾지 못한다고는 생각을 못 했다. 심지어 이 메소드는 나름의 방식으로 계산을 해서, 조건식에 !를 붙이면 무조건 5가 나오고 !를 붙이지 않으면 무조건 0이 나온다. 결국 정규 표현식이 아니라 contains 메소드가 문제였다.

 

.모든 문자(기본 한 개), * 0개 이상이라는 뜻이다. 즉, 앞 뒤로 무엇이 와도 된다는 뜻이다. matches는 명칭 상 완전히 일치하는 문자열을 찾는 메소드일 것이므로, 목표 문자열 앞 뒤의 불일치하는 문자열은 다른 정규 표현식을 이용해 보완이 필요한 것으로 보인다. 정규 표현식을 처음 접하는 입장에서는 복잡하다.

 

^이외라는 뜻이다. 아래 식의 설명은 메소드 호출 앞에 !를 붙이고 a-z 앞에 ^를 붙이면 모든 문자가 a-z인지 여부를 판독한다는 것이다.

 

정규 표현식은 공식 문서에서 못 찾았었는데, 위의 링크 덕분에 찾을 수 있었다. regex를 몰라서 regular를 쳤는데 안 나왔었다. regular expression의 줄임말일 것이다.

 

 

 

 

이걸 보고 해 봤더니 진짜로 된다. 다만 contains는 ".* , .*" 을 붙여도 안 된다. contains 메소드는 원래부터 내부에 포함된 문자열을 찾는 메소드이기 때문에 matches와 달리 추가 정규 표현식의 영향이 없을 것이다. 위의 답변에서는 정규 표현식을 추가해야 하는 이유만 설명되어 있고, 왜 contains는 안 되고 matches만 되는지는 설명하지 않고 있다. 궁금해서 공식 문서를 찾아봤다.

 

 

 

 

matches는 메소드를 호출한 String 변수가 매개 변수로 주어진 정규 표현식일치하는지 여부를 판독한다. 즉 원래 정규 표현식을 입력하는 용도의 메소드인 것으로 보인다.

 

contains매개 변수 타입이 String이 아니라 CharSequence다. 문자들의 나열을 뜻하는 것 같다. 눌러보니 인터페이스로, String구현 클래스다. 매개변수로 String 타입을 입력했으니 String 타입이 자동 타입 변환되어 대입되었을 것이다. String 클래스에서 CharSequence 인터페이스의 메소드를 재정의한 내용을 찾아보고 싶은데 아직 검색 실력이 미숙한지 못 찾겠다. 호출한 String 변수문자값들의 특정한 순서포함하는지를 판독한다. 즉 문자열 내부의 정규식계산해서 판독할 수 있는 기능없는 것으로 보인다.

 

 

 

CharSequence 인터페이스의 설명을 읽어 보면 문자값들의 나열이고, 다양한 종류의 문자열 중 한 가지 형태읽기 전용 접근을 제공한다고 되어 있다. String, Segment 등의 구현 클래스 중 하나를 연결해 준다는 뜻으로 보인다. String 클래스를 연결한다면 재정의된 메소드 실행내용에 따라 다르겠지만, 정규 표현식문자 그대로를 가져올 뿐 정규 표현식 연산을 처리한 결과를 가져오지는 못할 것으로 보인다. 그래서 정규표현식 기호로 이루어진 문자열 자체를 포함하지 않는 경우 모두 answer에 더했고, 따라서 항상 답이 5가 나온 것 같다. 아직 개념이 제대로 안 잡혀서 정확한 정보를 얻기는 어렵다. Java 공부를 더 해야 확실해질 것으로 보인다.

 

추가) '이것이 자바다'를 공부하다 보니, 문자열을 분리할 때 split 메소드정규 표현식으로, StringTokenizer 클래스의 생성자 문자로 구분한다고 한다. 위의 matches와 contains 메소드의 경우와 비슷하다. 각 메소드마다 내부의 연산 내용이 다를 것이므로, 구별되는 논리적 규칙을 찾기보다는 method별분류하고 기억해 둬야 할 것으로 보인다.

 

 

전에 풀이에서 보고 참고하는 계기가 되었던 replaceAll 역시 regex를 매개값으로 받지만, replaceCharSequence 인터페이스를 매개값으로 받는 것을 볼 수 있다. replaceAll의 all 주어진 문자열뿐만 아니라 정규 표현식의 연산 결과에 해당하는 값을 모두 대체하라는 의미인 것으로 생각된다.

 

 

다른 풀이 1

 

정규 표현식을 잘 쓰면 이렇게 몇 줄로도 끝난다.....

 

 

다른 풀이 2

 

 

나와 거의 유사한 방식으로 풀었다. isEmpty() 메소드는 이름만 봐도, 안이 비어 있는지 확인해 주는 작업을 수행 후 boolean 타입을 return하는 메소드임을 짐작할 수 있다. 이런 기초적인 메소드도 아직 못 배워 length() 메소드만 주구장창 쓰고 있으니 슬프다.

 

 

제곱수 판별하기

 

 

 

 

n이 제곱수가 맞는지 여부를 확인하는 쉬운 문제다.

 

 

 

 

저번에 다른 풀이에서 봐 뒀던 Math 클래스 pow 메소드를 사용해 봤다.

 

 

 

 

그냥 i 끼리 곱해 주는 방식으로도 고쳐 봤다. 지수의 크기가 작을 때는 pow 메소드를 이용하는 것보다 간단하다.

 

 

다른 풀이

 

Math 클래스 sqrt 메소드를 이용했다. 찾아보니 제곱근을 해 주는 메소드라고 한다. square root의 약자인 모양이다. 이것도 외워 둬야겠다. 제곱근은 제곱의 반대다. 그런데 3제곱근 이상은 없는 모양이다(공식문서를 찾아보니 3제곱근을 구하려면 Math.cbrt()를 사용해야 한다. 메소드가 따로 있어 불편하다.). 사용처가 한정적이고 pow 메소드의 반대 연산을 통해 제곱근을 해결할 수 있으므로, pow 메소드에 비해 유용해 보이지는 않는다.

 

 

개미 군단

 

 

 

 

쉬운 문제다. 나눗셈의  나머지를 이용하면 간단하게 풀 수 있다.

 

 

 

 

general, soldier, worker 순서로 몫과 나머지를 이용해 필요한 최소 숫자를 구했다.

 

 

암호 해독

 

 

 

 

for 반복문을 이용해 code i배만큼 곱한 자리에 위치한 문자 추출하고, answer에 차례대로 더해 주는 식으로 풀면 될 것 같다.

 

 

 

 

간단한 문제였는데도 한 번에 푸는 데 실패했다...

 

StringIndexOutOfBoundsException이 발생했다. index가 범위를 벗어났다는 것이다. cipher.charAt 메소드의 매개변수인 i * code는 실제 index보다 1씩 많다. index 0부터 시작한다는 기초적인 사실을 고려하지 못했다ㅠㅠ

 

 

 

 

charAt(i * code - 1) 을 해 줘야 index와 일치한다.

 

 

다른 풀이

 

 

substring을 이용했다는 점이 흥미롭다. 저렇게 써 볼 생각은 안 했었다. 아무래도 charAt의 경우 추출 결과가 아스키 코드 값을 가지는 char 타입이 되어 버려 추출 후 다른 타입 변수들과 함께 이것저것 조작하기에 번거로운 편이다. 다른 타입 변수들과 추가 연산을 해야 하는 상황에서 substring을 주로 이용해야겠다.