2022 KAKAO BLIND RECRUITMENT
[LV.2] 주차 요금 계산
문제 링크: https://programmers.co.kr/learn/courses/30/lessons/92341
물론, 짧은코드가 좋은코드는 아닙니다.
하지만 숏코딩은 내가 알고 있는 문법을 점검하기 좋은 기회이니 둘러보고 가세요!😊
백준은 숏코딩 순위가 나오지만, 프로그래머스에는 그런 기능이 없어서 올리게 되었다.
나만 보기 아까워서..
코드
def solution(f,r):
d={}
for e in r:d[e[6:10]]=d.get(e[6:10],0)+(-1 if e[11:]=='IN' else 1)*(int(e[:2])*60+int(e[3:5]))
return [(f[1]-(-max(0,d[n]+1439*(d[n]<1)-f[0])//f[2])*f[3])for n in sorted(d)]
아래와 같이 하면 한줄을 더 줄일 수 있다. (하지만 실행속도가 조금 더 느려진다.)
전역변수가 지역변수보다 느린것과 같은 원리이지 않을까 추측해본다.
지역변수가 레지스터에 올라갈 확률이 더 높아서 더 빠른 것으로 알고있다.
def solution(f,r,d={}):
for e in r:d[e[6:10]]=d.get(e[6:10],0)+(-1 if e[11:]=='IN' else 1)*(int(e[:2])*60+int(e[3:5]))
return [(f[1]-(-max(0,d[n]+1439*(d[n]<1)-f[0])//f[2])*f[3])for n in sorted(d)]
빠른 설명
Key point
- max()함수를 사용함
- .split()메서드 대신 문자열 슬라이싱만 사용
- IN이면 빼주고 OUT이면 더해준다. 다 계산한 값이 음수면 출차기록이 없다는 의미이다
#{차량번호:누적주차시간}형식이 들어갈 딕셔너리 d선언
#d[차량번호]+=(In이면 -1 OUT이면 1)*(시간*60+분)
#return [기본요금+(max(0,누적주차시간-기본시간)/단위시간*단위요금 for n in 차량번호로 정렬된 누적주차시간들]
'''
"05:34 5961 IN" 형식이므로
시간=[:2]
분=[3:5]
차량번호=[6:10]
출차인지 입차인지=[11:]
로 문자열 슬라이싱 가능함
IN이면 -시간 OUT이면 +시간을 해준다.
다 계산하고 나서 시간이 -이면 출차기록이 없다는 의미이다.
이때는 1439를 더해준다(1439는 23:59를 분으로 환산한 값임) 그럼 누적 주차시간이 완성된다.
max(0,누적주차시간-기본시간)를 해서 식에 넣는다.
(기본시간보다 적게 주차했으면 0이 선택될것이고 더 오래 주차했으면 기본시간을 뺀 시간이 선택된다)
(0을 곱하면 뒤의 식이 모두 0이 되므로 기본요금만 반환된다.)
'''
상세한 설명
- 첫번째 줄 부터 살펴보자.
def solution(f,r,d={}):
원래 solution 함수의 파라미터는 2개이다.
하지만 딕셔너리 d를 선언하는 줄을 아끼기 위해서 디폴트 매개 변수 형식으로 d={}를 추가로 넣어줬다.
2.for e in r:d[e[6:10]]=d.get(e[6:10],0)+(-1 if e[11:]=='IN' else 1)*(int(e[:2])*60+int(e[3:5]))
이 문장은 사실 어떻게 보면 2줄의 문장이다.
- for문이나 if문 안에 한줄의 코드만 있다면 줄바꿈과 들여쓰기를 하지 않고 한줄에 바로 써줄 수 있다.
이건 C언어에서도 비슷하죠! C언어도 한줄이라면 {}안에 써줄 필요가 없으니까요!
for e in r:
d[e[6:10]]=d.get(e[6:10],0)+(-1 if e[11:]=='IN' else 1)*(int(e[:2])*60+int(e[3:5]))
조금 더 친숙하게 보이나요?
records는 다음과 같은 형식으로 주어진다.
["16:00 3961 IN","16:00 0202 IN","18:00 3961 OUT","18:00 0202 OUT","23:58 3961 IN"]
IN OUT 전까지는 항상 같은 길이이다.
따라서 굳이 .split() 메서드를 쓰지 않고 슬라이싱만으로 분리할 수 있다.
for e in r여기에서 e의 예시로 "16:00 3961 IN" 를 보자.
시간(H)=e[:2] (처음부터 2번인덱스 전까지)
분(M)=e[3:5]
차량 번호(carNumber)=e[6:10]
내역(inOut)=e[11:] (11번 인덱스부터 끝까지)
이제 코드를 조금 더 알아보기 쉽게 바꿔보자.
d[차량번호]=d.get(차량번호,0)+(-1 if 내역=='IN' else 1)*(int(시간)*60+int(분))
딕셔너리 d에는 { 차량번호 : 누적주차시간(분) } 형태로 저장 할 것이다.
- 딕셔너리에서 .get() 메서드를 사용하면,
해당하는 키가 딕셔너리 안에 존재하지 않아도 오류가 나지 않게 만들 수 있다.
해당하는 키가 없다면 원래는 None값을 가져온다.
여기서는 None대신 0을 가져오기 위해서 d.get(차랑번호,0) 라고 썼다. - 딕셔너리의 value에는 누적 주차시간을 담아야 하기 때문에 원래 있던 값에 계속 더해줘야한다.(원래 값이 없다면 0에 더해야한다.)
- 파이썬 삼항 연산자 를 이용해 (-1 if 내역=='IN' else 1) 라고 썼다.
파이썬은 참과 거짓을 True 와 False 로 나타낸다. # Boolean 자료형
숫자나 boolean끼리 사칙연산을 하면 알아서 True는 1로 False는 0으로 형변환된다.시간값은 양수여야한다. 따라서, 큰 시간에서 작은 시간을 빼야한다.
입차시간이 출차시간보다 항상 작다. (입차 시간이 출차 시간보다 항상 먼저라서)
따라서 IN이라면 시간을 분으로 환산한 값에 - 를 붙여준다.
OUT이라면 양수다.결국 두번째 줄은 아래와 같은 내용이였다.3. 드디어 세번째 줄이다.변수 f는 원래 fees이다. 문제에 따르면 f안에는 다음과 같은 내용이 들어간다.
그래서 d[n]+1439*(d[n]<1)가 누적 주차시간이다.앞서 다 계산한 값+ (그런데 그 값이 0이하라면 +1439 아니면 0)라서..기본요금 뒤의 식은 옵션이라고 생각할 수 있다. 그리고 나눗셈과 곱셈으로 연결되어있다.
그렇다면,
(누적주차시간-기본시간)부분이 0이면 기본요금만 남게될것이다. IN->OUT->IN 혹은 IN-> 형식으로 출차기록이 없다고 가정하면 -a+(a+1)-(a+2)=-a-2 라서 (a는 음이 아닌 정수) 일때 무조건 음수 or 0일수밖에 없다.
- 기본요금+ (누적주차시간-기본시간)/단위시간*단위요금 기본요금+ (0)/단위시간*단위요금 = 기본요금+0 = 기본요금
- 다음으로,
기본시간보다 적게 주차했다면, 기본요금만 청구되고
기본시간보다 많이 주차했다면, 기본요금+ (누적주차시간-기본시간)/단위시간*단위요금이 청구된다. - d[n]<1이 False면 *연산시 0으로 형변환 d[n]<1이 True면 *연산시 1로 형변환
- -> 이게 이해가 안된다면...아래를 참고하자. (이해가 안된 분만 읽으세요)
- 출차기록이 없다면? 문제에서 요구한대로 23:59가 출차기록인것으로 간주하고 계산해야한다.
앞서 말했듯이 다 계산한 값이 음수이거나 0이면 출차기록이 없는것이다.
(0인경우를 예를들면, "00:00"에 입차한 기록만 있을때)
시간은 앞으로만 가기 때문에 갈수록 시간값은 커진다. - return [(f[1]-(-max(0,d[n]+1439*(d[n]<1)-f[0])//f[2])*f[3])for n in sorted(d)] #return [기본요금+(max(0,누적주차시간-기본시간)/단위시간*단위요금 for n in 차량번호로 정렬된 누적주차시간들]
- if IN: d[차량번호]-=(시간*60+분) elif OUT: d[차량번호]+=(시간*60+분) #d[차량번호]+=(IN이면 -1 OUT이면 1)*(시간*60+분)
- 그렇다면 부호는 해결 됐고...
(int(시간)*60+int(분)) 이 부분은 다 아실 것 같아서 #자세한 설명은 생략한다. - >>>1.5+True 2.5 >>>True*False 0 >>>+True 1 >>>False-True -1
기본시간보다 적게 주차한 경우 누적주차시간-기본시간이 음수가 된다.
음수면 0으로, 양수면 그 양수 그대로 값을 가져가게 만들고 싶다.
- 그래서 max() 함수를 사용했다.
-> max(0,누적주차시간-기본시간) 형태이다.
즉, 기본시간보다 적게 주차했다면 max함수에서 0이 선택되고 기본요금 뒤의 식이 0이되어 기본요금만 청구된다.
기본시간보다 많이 주차했다면, 기본요금+(누적주차시간-기본시간)/단위시간*단위요금이 청구된다.
- import math 없이 올림 연산 하기 #음수 나눗셈에서의 몫
-(-max(0,d[n]+1439*(d[n]<1)-f[0])//f[2])에서 -가 들어가서 의아할수도 있다.
a/b의 값을 올림 하고 싶을 때 파이썬에서는 -(-a/b)라고 쓰면 된다. - 리스트 컴프리헨션 (지능형 리스트)
이제 [~for n in sorted(d)] 부분만 남았다.
리스트 컴프리헨션을 사용해 리스트를 만들었다. - 딕셔너리에 sorted()함수 쓰기
딕셔너리는 순서가 없는 자료형이다.
하지만 sorted()함수는 순서가 없는 자료형에도 사용할 수 있다. (.sort()메서드는 안됨)
단, 결과값은 리스트로 반환된다.
sorted(d)를 하니 알아서 키값을 기준으로 정렬해줬다.
결과
- 모든 테스트 케이스에서 2ms 미만으로 통과했다. (최장 1.41ms, 4줄 버전)
3줄버전은 2ms를 넘길 때도 있고 넘기지 않을때도 있었다. (맨 위에 관련된 설명이 있다.)
의외로 다른 분들의 코드보다 속도도 빠르고 짧다.