240514_TIL

J Lee·2024년 5월 14일
0

아무리 사소하더라도 배움이 없는 날은 없다.

알고리즘 코드카타 40번

자연수 n이 매개변수로 주어집니다. n을 3진법 상에서 앞뒤로 뒤집은 후, 이를 다시 10진법으로 표현한 수를 return 하도록 solution 함수를 완성해주세요.

예를 들어 n = 45라고 하면
1. 삼진법으로 표현하면 1200이 되고
2. 이걸 앞뒤로 반전시키면 0021이 되고
3. 이걸 다시 10진법 수로 바꾸면ㅋㅋ 2×3^1 + 1×3^0 = 7이 되는 식.

40번부터는 실행시간도 채점에 포함되기 시작했다. 로직이 맞고 실행이 제대로 되어도 실행시간이 느려지면 얼럿이 뜨면서 채점이 아예 안 되는 바람에, 조금 더 효율적인 코드를 짜는 데에도 머리를 굴려야 할 상황이 된 것 같다.

먼저 주어진 n을 3진수로 바꾸는 알고리즘을 만들었다.

n3 = ''                     
    while n>0:                  
        remain = n%3            
        n3 = n3+str(remain)     
        n //= 3
  1. 3진수로 바뀐 n을 저장할 새로운 빈 문자열 n3를 정의하고,
  2. n이 0보다 클 경우에 대해 while문이 작동한다.
  3. n을 3으로 나눈 나머지를 remain이라는 변수에 담고,
  4. n3는 n3+str(remain)으로 대체해 준다.
    3진수로 정확히 표현하려면 str(remain)+n3가 맞겠으나, 어차피 조금 이따가 다시 뒤집어서 써 줘야 하므로 그냥 뒤집힌 채로 내비뒀다. (즉, n3는 3진수가 아니라 3진수를 뒤집은 결과인 것)
  5. 그리고 기존의 n을, n을 3으로 나눈 몫으로 대체한다. n이 0보다 큰 경우라면 여전히 while문이 작동한다.

while문이 끝나고 나면 '자연수n을 3진법으로 바꾼 후 뒤집은 결과'를 얻을 수 있다. 문제의 예시처럼 n이 45라면 n3 = 0021이 되고, 125라면 22111이 되는 식.

이제 n3를 다시 십진수로 바꿔줘야 하므로 for문을 사용해 반복했다.

def solution(n):
    answer = 0
    n3 = ''                     
    while n>0:                  
        remain = n%3            
        n3 = n3+str(remain)     
        n //= 3
    for i in range(1,len(n3)+1):
        x = int(n3[i-1])
        y = 3**(len(n3)-i)
        answer += x*y
    return answer

1부터 n3의 자릿수까지 for문이 반복되는데,
이 때 x는 n3의 index에 해당하는 숫자를, y는 자릿수에 해당하는 3의 거듭제곱이다. 그리고 x와 y를 계속 곱하면서 더해나가는 결과를 answer에 담아서 반환하면 된다. 예컨대 n3가 0021이라면 for문의 결과는

answer
= n[0]×3^3 + n[1]×3^2 + n[2]×3^1 + n[3]×3^0
= 0 + 0 + (2×3) + (1×1)
= 7

이 된다.
3진수를 구한 뒤에 다시 뒤집지 않고 처음부터 뒤집힌 상태의 결과값을 구해서 연산 단계를 하나 줄이는 것이 최선이었는데, 조금 더 간단한 방법으로 구현할 수도 있을 것 같다. 정리하면서 따로 고민해 보자.


SQL 코드카타 116번
SQL로 이동평균을 구하는 문제.

row끼리의 관계를(기준일로부터 1주일 전) 컬럼으로 정의해야 하므로 window함수를 써야 한다는 것까지는 감이 왔는데, 정확한 문법을 몰라서 검색을 병행하면서 해결했다.

먼저 1월 10일의 경우처럼 두 명의 customer가 주문하는 경우도 있을 수 있으므로, window함수부터 쓰려고 달려들기 전에 일자별 amount의 합부터 구해놔야 한다.

select visited_on,
       sum(amount) as amount
from Customer
group by 1

그리고 위의 amount를 컬럼으로 쓰기 위해 인라인뷰 서브쿼리로 사용하고, window함수를 사용해 본 쿼리에서 반환해야 할 amount와 average_amount를 정의한다.

SELECT   visited_on,
         Sum(amount) over(ORDER BY visited_on range BETWEEN INTERVAL 6 day preceding AND current row )            AS 'amount',
         round(sum(amount) over(ORDER BY visited_on range BETWEEN INTERVAL 6 day preceding AND current row )/7,2) AS 'average_amount'
FROM     (
          SELECT   visited_on,
                   sum(amount) AS amount
          FROM     customer
          GROUP BY 1
          ) a

sum(amount) over(order by visited_on ~ ) 까지는 익숙하게 썼던 구문인데, 이번에는 뒤에 range가 붙어서 visited_on의 범위를 설정해 준다.
7주일치 데이터를 반영하려면 오늘로부터 6일 전부터 오늘까지의 결과를 그루핑하면 되는데,

SUM(amount) OVER(ORDER BY visited_on RANGE BETWEEN INTERVAL 6 day preceding AND current row)

로 표현한다. INTERVAL을 여기에서도 쓸 수 있는 건 처음 알았다. 6일 '전'이라서 preceding을 쓴 것 같고, 현재 행은 그냥 current row라고 쓰면 된다고 한다. 이 결과를 실행하면 기준 행으로부터 6일 전부터 기준행까지의 visited_on에 해당하는 amount의 sum을 구해서 컬럼으로 쓰게 된다.

같은 원리로 7일간의 이동평균은 위의 결과를 7로 나누고 문제에서 요구한 대로 반올림 처리하면 된다. 이 때, count(visited_on)으로 쓰는 게 아니라 그냥 7로 나눠야 한다는 것에 주의. (안 그러면 visited_on의 갯수에 따라 결과가 달라질 수 있음)

여기까지 실행하면 쿼리의 결과가 아래와 같이 된다.

문제에서 요구한 output에 거의 근접했는데, 1월 1일부터 1월 6일까지의 데이터는 들어갈 필요가 없다. 따라서 where절에서 조건을 걸어서 가장 이른 날짜로부터 6일 이후의 데이터만 조회하는 조건을 걸면 되는데, 위 쿼리에서 where절을 바로 쓰면 1월 1일~ 1월 6일의 데이터가 아예 제외되므로 amount와 이동평균 값도 달라져버리게 된다. 따라서, 위의 결과를 한 번 더 인라인뷰 서브쿼리로 처리한 후에 where절을 걸어줘야 한다. 이걸 몰라서 또 한참 헤맴.

SELECT
	visited_on,
	amount,
	average_amount
FROM
	(
	SELECT
		visited_on,
		Sum(amount) OVER(
		ORDER BY visited_on RANGE BETWEEN INTERVAL 6 DAY PRECEDING AND CURRENT ROW ) AS 'amount',
		round(sum(amount) OVER(ORDER BY visited_on RANGE BETWEEN INTERVAL 6 DAY PRECEDING AND CURRENT ROW )/ 7, 2) AS 'average_amount'
	FROM
		(
		SELECT
			visited_on,
			sum(amount) AS amount
		FROM
			customer
		GROUP BY
			1) a) b
WHERE
	visited_on >=
         (
	SELECT
		min(visited_on)
	FROM
		customer)+ 6
ORDER BY
	1

완성된 정답 쿼리는 위와 같다.

지금까지 코드카타에서 사용했던 window함수는 window_function(컬럼) over(partition by ~ order by ~) 정도였는데, 오늘은 그 안에서 이동평균을 구하기 위해 날짜에 조건을 거는 법까지 알게 됐다. 공식 문서에도 이 정도로 자세한 내용까지는 나오지 않은 것 같으니 잘 정리해 두자.

profile
기본기를 소홀히 하지 말자

0개의 댓글