본문 바로가기
Tech/Coding

도깨비말(Pig Latin)

by redcubes 2025. 6. 23.

https://www.acmicpc.net/problem/9226

문제 설명

도깨비말은 언어 유희 중 하나로, 글자를 특정 법칙에 따라 재구성하는 것을 말한다. 영어권에서는 피그 라틴어(Pig Latin)라는 것이 있으며, 어린이들이 종종 자신들만의 은어처럼 사용한다.

이 언어에는 다음과 같은 규칙이 있다:

  1. 단어의 앞에서부터 모음이 나올 때까지 자음들을 잘라낸다.
  2. 잘라낸 자음들은 단어 끝으로 옮긴다.
  3. 그 뒤에 ay를 붙인다.
  4. 만약 단어가 모음으로 시작한다면 그대로 ay를 붙인다.
  5. 만약 모음이 전혀 없다면, 단어 그대로 뒤에 ay를 붙인다.

입력

각 줄에 하나의 단어가 주어진다. 모든 단어는 공백 없이 소문자로만 구성되며, 길이는 20자를 넘지 않는다. 입력의 마지막은 # 한 줄이 주어진다.

출력

각 단어에 대해 피그라틴어 변환 결과를 한 줄에 하나씩 출력한다.

예제 입력

frog
apple
pear
#

예제 출력

ogfray
appleay
earpay

 

lines = [line.rstrip() for line in open(0) if line.strip() not in '#\n']
v = set('aeiou')
res = []
for ln in lines:
    for i in range(len(ln)):
        if ln[i] in v:
            res.append(ln[i:] + ln[:i] + 'ay')
            break
    else:res.append(ln + 'ay')
print('\n'.join(res))

코드 설명

open(0)으로 표준 입력 전체를 한 번에 읽고, 바로 리스트 내포로 # 종료 표식을 제외한 모든 행을 수집한다.
② 모음 집합 v를 미리 만들어 membership 테스트를 O(1)로 수행한다.
③ 각 단어 ln에 대해 왼쪽부터 최초로 모음이 발견되는 인덱스 i를 찾는다. 찾은 즉시 break하여 불필요한 순회를 중단한다.
④ 모음이 존재하지 않는 경우 for … else 문법의 else 절이 실행돼 원본 단어에 바로 'ay'를 붙인다.
⑤ 시간 복잡도는 단어 길이를 L이라 할 때 O(L), 전체 입력에 대해서는 모든 글자 수의 합에 선형이다.

import io
inp = io.BufferedReader(io.FileIO(0, 'rb'), 1 << 16).readline
v = [b'a', b'e', b'i', b'o', b'u']
res = []
while True:
    ln = inp()
    if ln == b'#\n':
        break
    ln = ln.rstrip()
    idx = len(ln)
    for i, b in enumerate(ln):
        if bytes([b]) in v:
            idx = i
            break
    res.append(ln[idx:] + ln[:idx] + b'ay')
open(1,'wb').write(b'\n'.join(res))

코드 설명

io.FileIO + BufferedReader로 64 KiB 버퍼를 사용하여 표준 입력을 바이트 단위로 고속 처리한다. 모든 연산이 bytes 객체로 이뤄지므로 UTF-8 디코딩 비용이 없다.
② 반복문 내부에서 #\n이 들어오면 즉시 종료한다.
③ 초기 idxlen(ln)으로 잡아 두면, 모음이 없을 때 자연스럽게 “모음 없음” 경로가 된다.
enumerate를 이용해 바이트 인덱스를 탐색하고, 발견 즉시 break한다.
⑤ 출력을 위해 한줄 한줄 res에 모아 두다가 마지막에 open(1, 'wb')로 한 방에 쓰기(write coalescing)하여 시스템 호출 횟수를 최소화한다.

v=set('aeiou')
res=[]
for ln in open(0):
    if ln=='#\n':break
    ln=ln.rstrip()
    idxs = [ln.find(c) for c in v]
    idxs = [i for i in idxs if i >= 0]
    i = min(idxs) if idxs else 0
    res.append(ln[i:] + ln[:i] + 'ay')
print('\n'.join(res))

코드 설명

str.find를 모음 개수(5 개)만큼 호출하여 각 모음의 위치를 리스트 idxs에 저장한다.
② 모음이 있다면 최소값(min)이 최초 모음 위치이고, 없다면 idxs가 비어 있으므로 i = 0으로 처리해 원본 단어 + 'ay' 경로를 따른다.
find는 내부적으로 C 루프가 돈다. 호출 횟수가 고정이라 평균적으로 빠르지만, 문자열을 다섯 번 순회한다는 점에서 앞선 두 코드보다 이론적인 최악 시간복잡도는 살짝 크다(5L vs L). 실무에서는 입력이 짧아 체감 차이는 거의 없다.

import io
inp = io.BufferedReader(io.FileIO(0, 'rb'), 1 << 16).readline
out = io.BufferedWriter(io.FileIO(1, 'wb'), 1 << 16)
v = b'aeiou'
while True:
    ln = inp()
    if ln == b'#\n':
        break
    ln = ln.rstrip()
    idx = next((i for i, b in enumerate(ln) if b in v), len(ln))
    out.write(ln[idx:] + ln[:idx] + b'ay\n')
    out.flush()

코드 설명

① 입·출력 모두 버퍼링(BufferedReader/Writer)을 적용했다.
next(generator, default) 패턴으로 “처음 모음 위치 탐색”을 단 한 줄로 처리한다. 찾지 못하면 len(ln)으로 대체된다.
③ 매 루프마다 out.flush()를 호출해 즉시 표준 출력으로 비운다. BOJ처럼 라인버퍼링을 해주는 환경에서는 성능 차가 크지 않으니, 실무에서는 플러시를 생략해 호출 횟수를 줄이는 편이 더 효율적이다.

import re
vowel_pattern = re.compile(r'[aeiou]')
while True:
    word = input().strip()
    if word == '#':
        break
    match = vowel_pattern.search(word)
    if not match:
        print(word + 'ay')
    elif match.start() == 0:
        print(word + 'ay')
    else:
        i = match.start()
        print(word[i:] + word[:i] + 'ay')

코드 설명

① 정규식 패턴 [aeiou]를 미리 컴파일하여 런타임 오버헤드를 줄인다.
search는 첫 일치만 찾고 멈추므로 평균적으로 O(L) 시간이다.
③ 직관성을 위해 조건 분기를 세 개로 나눠 “모음 없음→단어 그대로”, “첫 글자가 모음→그대로”, “그 외→분리·재조합”으로 처리했다.
④ 정규식을 쓰면 가독성이 높아지지만, 모음 개수가 적어 in 연산 대비 성능이 확 뛰지는 않는다. 교육용·설명용 코드로 적합하다.

위와 같이 각 코드 스니펫 뒤에 핵심 아이디어, 데이터 흐름, 성능 특징을 덧붙이면 독자가 다양한 구현 스타일을 비교·학습하기 쉽다.

'Tech > Coding' 카테고리의 다른 글

최장 공통 부분수열(LCS) 과 DP 역추적  (0) 2025.07.21
파이썬 조건 매핑 함수 구현 방법  (0) 2025.07.03
색칠 공부  (0) 2025.06.22
17144번: 미세먼지 안녕!(OOP 문제 해결)  (0) 2025.06.17
회문 끝말잇기  (0) 2025.06.02