백준에서 브론즈 1 등급의 문제 FizzBuzz를 풀어가는 과정에서 좋은 코드를 발견하여 동작 원리와 핵심 코드를 분석해 보았다.
내가 직접 작성한 코드는 굉장히 1차원적이라 좀 더 나은 방안들을 고려하다가, 상위 랭킹에 bennyws 님의 코드가 매우 간결하고 효율적이라 생각하여 포스팅을 작성했다.
문제
FizzBuzz 문제는 i=1,2,⋯ 에 대해 다음 규칙에 따라 문자열을 한 줄에 하나씩 출력하는 문제입니다.
- 의 배수이면서 의 배수이면 “FizzBuzz”를 출력합니다. 가
- 의 배수이지만 의 배수가 아니면 “Fizz”를 출력합니다. 가
- 의 배수가 아니지만 의 배수이면 “Buzz”를 출력합니다. 가
- 의 배수도 아니고 의 배수도 아닌 경우 를 그대로 출력합니다. 가
FizzBuzz 문제에서 연속으로 출력된 세 개의 문자열이 주어집니다. 이때, 이 세 문자열 다음에 올 문자열은 무엇일까요?
입력
FizzBuzz 문제에서 연속으로 출력된 세 개의 문자열이 한 줄에 하나씩 주어집니다. 각 문자열의 길이는 8 이하입니다. 입력이 항상 FizzBuzz 문제에서 연속으로 출력된 세 개의 문자열에 대응됨이 보장됩니다.
출력
연속으로 출력된 세 개의 문자열 다음에 올 문자열을 출력하세요. 여러 문자열이 올 수 있는 경우, 아무거나 하나 출력하세요.
예제 입력
Fizz
Buzz
11
예제 출력
Fizz
내가 작성한 코드
def is_fizz_buzz(num):
"""
숫자를 받아서 Fizz, Buzz, FizzBuzz를 출력하는 함수
- 3의 배수면 "Fizz"
- 5의 배수면 "Buzz"
- 3과 5의 공배수면 "FizzBuzz"
- 위 조건이 아니면 숫자 그대로 출력
"""
if num % 3 == 0 and num % 5 == 0:
print("FizzBuzz")
elif num % 3 == 0:
print("Fizz")
elif num % 5 == 0:
print("Buzz")
else:
print(num)
def predict_fizz_buzz(a, b, c):
"""
입력된 값 a, b, c를 확인하여 다음 숫자의 FizzBuzz 상태를 예측하는 함수
- a, b, c 중 가장 먼저 숫자로 변환 가능한 값을 찾고
- 해당 숫자에 특정 값을 더한 뒤 FizzBuzz 여부를 확인하여 출력
"""
def convert_to_int(value):
"""문자열이 정수로 변환 가능하면 변환, 아니면 그대로 반환"""
try:
return int(value) # 정수 변환 성공하면 반환
except ValueError:
return value # 변환 불가능하면 원래 값 그대로 반환
# 입력값을 정수로 변환 시도
a, b, c = convert_to_int(a), convert_to_int(b), convert_to_int(c)
# 첫 번째로 숫자인 값을 찾아 다음 수 예측
if isinstance(a, int): # a가 숫자라면
is_fizz_buzz(a + 3) # 3을 더한 후 FizzBuzz 판단
return
elif isinstance(b, int): # b가 숫자라면
is_fizz_buzz(b + 2) # 2를 더한 후 FizzBuzz 판단
return
elif isinstance(c, int): # c가 숫자라면
is_fizz_buzz(c + 1) # 1을 더한 후 FizzBuzz 판단
return
# 사용자 입력 받기
a = input()
b = input()
c = input()
# FizzBuzz 예측 실행
predict_fizz_buzz(a, b, c)
굉장히 직관적이지만, 1차원적이라고 생각했다.
알고리즘 풀이 자체에는 문제가 없지만, 코드가 지나치게 길다고 생각했다.
그래도 제출 자체는 성공했다.
먼저 연속된 세 개의 숫자 (x, x+1, x+2)가 항상 Fizz, Buzz, FizzBuzz로만 구성될 수 없다(불가능하다)는 것을 유추해낼 수 있었다.
그 근거는 다음과 같다:
- 모든 연속된 세 숫자가 Fizz, Buzz, FizzBuzz만으로 구성될 수는 없다
- 어떤 세 숫자든 적어도 하나는 일반 숫자(즉, 아무 조건에도 해당되지 않는 값)가 나온다
- 즉, "Fizz", "Fizz", "Buzz" 또는 "Buzz", "Fizz", "FizzBuzz" 같은 조합이 항상 가능한 것은 아니다
이후 이를 기반으로 주어진 3개의 연속된 문자열들(i.e. Fizz, 4, Buzz) 중 하나는 무조건 정수 int 일 것이라는 전제 하에 로직을 구성했다.
주어진 연속된 세 수 a, b, c를 모두 받아왔고, 이를 기반으로 각 수들이 정수인지, 정수라면 입력받은 순서에 따라 값들(3 또는 2 또는 1)을 더한 후 FizzBuzz 함수의 논리를 적용시켰다.
예제의 입력값과 출력값을 정직하게 따른 방법이지만, 당연하게도 매우 비효율적이라고 생각이 들었다.
하지만 어떻게 해야 세 번의 입력을 받으면서 빠르게 정답을 도출하고 출력받을 수 있는지 생각이 잘 들지 않았고, 앞서 언급한 대로 다른 사람들의 코드 제출 결과를 확인했다.
분석할 코드
bennyws 님의 코드로, 상위 랭킹 2등에 등록되어 있다.
for i in range(3, 0, -1): # i = 3, 2, 1 (3번 반복)
x = input() # 사용자 입력 받기
if x not in ['Fizz', 'Buzz', 'FizzBuzz']: # 입력값이 Fizz, Buzz, FizzBuzz가 아니라면
n = int(x) + i # 숫자로 변환 후 i를 더함
print('Fizz'*(n % 3 == 0) + 'Buzz'*(n % 5 == 0) or n) # FizzBuzz 판별 후 출력
break # 반복문 종료 (첫 번째 숫자 입력에서만 실행)
먼저 코드의 동작 원리와 핵심 동작으로 구분해 보았다.
동작 원리
1. 반복문 ( for i in range (3,0,-1) )
- i 값이 3, 2, 1로 반복 (즉, 총 3번 입력을 받음)
- 만약 x가 "Fizz", "Buzz", "FizzBuzz"라면 입력을 다시 받음
2. 숫자 입력을 받았을 때
- x가 정수라면, int(x) + i 를 수행하여 새로운 값을 계산
- Fizz, Buzz, FizzBuzz 로직에 따라 해당 수를 판별 후 출력
- break로 루프 종료 (첫 번째 숫자를 입력받은 순간 바로 종료)
핵심 동작
n = int(x) + i
- 입력값 x가 정수라면, 현재 루프의 i 값을 더함
- 즉, 가장 먼저 나오는 숫자가 입력값보다 i만큼 증가한 값으로 변환
print('Fizz'*(n % 3 == 0) + 'Buzz'*(n % 5 == 0) or n)
- 기존의 FizzBuzz 연산을 계산
- n % 3 == 0이면 'Fizz' 출력
- n % 5 == 0이면 'Buzz' 출력
- 둘 다 참이면 'FizzBuzz' 출력
- or 연산자를 활용해 Fizz나 Buzz가 없으면 n을 출력
3번의 반복, 정수 여부를 간단하게 파악한 후 FizzBuzz 로직을 별도의 함수 없이 적용하는 것까지 매우 깔끔했다.
특히 a, b, c의 여부에 따른 1~3의 수를 더한 후 다음 수를 예측하는 방식을 반복문에 포함하고 내림차순으로 3~1까지 예측되는 수에 더하는 방식이 아주 효율적이라고 생각했다.
'개발자 일기 > 알고리즘 공부' 카테고리의 다른 글
[프로그래머스 JS] 숫자 짝꿍 (2) | 2023.10.14 |
---|---|
[프로그래머스 JS] 삼총사 (0) | 2023.09.29 |
[프로그래머스 JS] 콜라 문제 (2) | 2023.09.25 |
[프로그래머스 JS] 옹알이 (2) (0) | 2023.08.22 |
[프로그래머스 JS] 햄버거 만들기 (0) | 2023.08.18 |
댓글