제목 : 크로아티아 알파벳
- 문제 : 예전에는 운영체제에서 크로아티아 알파벳을 입력할 수가 없었다. 따라서, 다음과 같이 크로아티아 알파벳을 변경해서 입력했다.
크로아티아 알파벳 | 변경 |
---|---|
č | c= |
ć | c- |
dž | dz= |
đ | d- |
lj | lj |
nj | nj |
š | s= |
ž | z= |
예를 들어, ljes=njak은 크로아티아 알파벳 6개(lj, e, š, nj, a, k)로 이루어져 있다. 단어가 주어졌을 때, 몇 개의 크로아티아 알파벳으로 이루어져 있는지 출력한다.
dž는 무조건 하나의 알파벳으로 쓰이고, d와 ž가 분리된 것으로 보지 않는다. lj와 nj도 마찬가지이다. 위 목록에 없는 알파벳은 한 글자씩 센다.
- 입력 : 첫째 줄에 최대 100글자의 단어가 주어진다. 알파벳 소문자와 ‘-‘, ‘=’로만 이루어져 있다. 단어는 크로아티아 알파벳으로 이루어져 있다. 문제 설명의 표에 나와있는 알파벳은 변경된 형태로 입력된다.
- 출력 : 입력으로 주어진 단어가 몇 개의 크로아티아 알파벳으로 이루어져 있는지 출력한다.
몇 가지 유의사항만 주의한다면 크게 어렵지 않다.
- 1) 언급된 바와 같이 ‘dz=’는 존재할 경우 ‘z=’와 혼동없이 무조건 ‘dz=’로 인식해야한다.
- 2) 글자 갯수를 세는 방법은 입력받은 문자열(이후 w)을 한 번에(통째로) 읽어서 크로아티아 알파벳을 찾으면 안된다(이유는 후술)
1은 2만 잘 유념하면 크게 문제 없다. 2는 무슨 문제인고 하니… 정답처리 받고 나서 제출된 답안들을 쭉 훑어보니, 크로아티아 알파벳과 일치하는 항목이 있을 경우 w에서 그 부분만 빼내는 방식의 알고리즘을 만든 분도 있었다. 문제는 그런식으로 접근할 경우 ‘ldz=j’에서 ‘dz=’만 인식해서 제거하면 ‘l (dz=) j’가 되어버려 있지도 않은 ‘lj’를 다시 카운트하게 되는 상황이 발생한다. 직관적으로 문자열을 순서대로 읽어나가면서 확인하는게 가장 안전하고 빠르다. 어차피 w는 무조건 전체를 한 번 훑어야 되기 때문이다.
조건문으로 떡칠해서 문제를 푸는 방법이 내가 생각하기엔 가장 직관적이고 빠른 방법일 것 같다.
- 1) w[i]에 대해 크로아티아 알파벳의 첫글자들(c, d, l, n, s, z)과 일치하는가?
- 2) w[i]뒤에 충분한 문자열이 존재하는가?(1번 조건을 만족한다 할지라도 w의 마지막에 위치한다면 크로아티아 알파벳이 아니다)
- 3) w[i]를 포함한 특정 길이의 문자열(2개 혹은 3개)이 크로아티아 알파벳과 일치하는가? 이 조건만 통과하면 된다.
예시로 ‘dz=ak’를 보자
- i=0일 때 w[i]=d는 1번 조건에 부합한다. 또한 그 뒤로도 충분한 문자열(해당 문자열을 포함해서 5개)이 존재하므로 2번도 만족한다. 마지막으로 w[i]뒤에 ‘z=’까지 포함한 ‘dz=’는 크로아티아 알파벳 목록의 ‘dz=’와 일치하기 때문에 크로아티아 알파벳이다.
- 그러므로 입력받은 문자열 w[0:3]=’dz=’는 크로아티아 알파벳이다.
- 그렇다면 w를 ‘dz=’까지 읽었을 때, 알파벳의 갯수는 1이고 현재 위치는 i=0에서 i=3으로 이동해야 할 것이다.
- w[3]은 a로 1번 조건에서 걸러진다. 알파벳의 갯수는 1+1=2가 되고 i=4로 이동한다.
- w[4]은 k로 1번 조건에서 걸러진다. 알파벳 갯수는 2+1=3이 되고 i=5가 되어 전차 문자열을 훑었기 때문에 알고리즘을 종료한다.
코드는 다음과 같다.
import sys
w = str(sys.stdin.readline()).strip()
croa = ['c=', 'c-', 'dz=', 'd-', 'lj', 'nj', 's=', 'z=']#크로아티아 알파벳 목록
char0 = [i[0] for i in croa]#크로아티아 알파벳 식별을 위한 첫글자
nchar = [len(i) for i in croa]#크로아티아 알파벳 표시를 위한 글자의 갯수. dz=를 제외하곤 모두 2
i = 0#받은 단어를 0번부터 쭉 훑는다
l = 0#알파벳의 갯수
while i<len(w):#string의 0번 위치부터 하나씩 확인
check = False#크로아티아 알파벳이 아닐경우 그대로 False
for j, char in enumerate(char0):#현재 알파벳이 char0, 즉 크로아티아 알파벳의 첫글자와 같은지 확인
if char==w[i] and len(w)-i>=nchar[j] and w[i:i+nchar[j]] == croa[j]:
#첫글자가 크로아티아 알파벳의 첫글자와 같고 / 현재 글자 뒤에 크로아티아 알파벳표시를 할 만큼의 글자가 여유있으며 / 크로아티아 알파벳과 같으면
l+=1#알파벳의 갯수를 1개증가시키고
i+=nchar[j]#현재 위치를 크로아티아 알파벳의 길이만큼 이동시키며
check = True#크로아티아 알파벳을 찾았음을 표시
break#for문 탈출
else:
pass
if not check:#크로아티아 알파벳을 모두 체크했으나 해당사항이 없을 경우
l+=1#알파벳의 갯수를 1개 증가
i+=1#위치도 1개 이동
print(l)#알파벳 갯수 출력
팁으로 조건문에서 조건 A, B, C를 다음과 같이 건다고 가정해보자
if A and B and C:
내 코드의 경우
- A :
char==w[i]
, 첫글자가 크로아티아 알파벳의 첫글자와 같고 - B :
len(w)-i>=[j]
, 현재 글자 뒤에 크로아티아 알파벳 표시를 할 만큼 글자가 여유있으며 - C :
w[i:i+nchar[j]] == croa[j]
, 크로아티아 알파벳과 같다면
이 될 것이다.
이 때 코드는 주어진 조건문을 순차적으로 확인한다. 이게 무슨 말인고하니,
우리가 가장 주의해야할 부분은 사실 조건 B이다. 왜냐하면 조건 B를 확인하지 않으면 조건 C에서 문자열의 길이를 벗어나는 범위를 호출하게 되고 이는 IndexError을 발생시켜 프로그램을 중단시키기 때문이다.
ex) w=’abcde’일 때 w[3]=’d’인데, 여기서 ‘dz=’나 ‘d-‘ 확인을 위해서 w[3:6]나 w[3:5]을 언급할 경우
그런데 위 코드처럼 A and B and C 순서로 if에 조건을 걸면,
A조건을 먼저 확인해서 True 일 때, B 조건을 확인하고, B 또한 True일 때, 비로소 C조건을 확인한다.
즉 A=B=True이기 때문에, B=True에 의거하여 크로아티아 알파벳을 표시할 만큼 글자가 여유있기 때문에
C조건을 확인할 때 인덱스 에러가 나지 않는다.
반대로 충분한 글자가 존재하지 않는다면, 즉 B=False일 경우, if가 아니라 else로 바로 빠지기 때문에 C조건을 확인하지 않고 넘어간다. 덕분에 w=’abcde’에서 w[3:6]과 같은 상황을 언급하지 않기 때문에 자동적으로 IndexError
을 넘어갈 수 있다.
조건문을 어떻게 읽는지 알고 있다면 조건문의 순서를 잘 활용하여 코드를 좀 더 간결하게 짤 수 있으니 잘 활용해 보도록하자.