지연과 손실 계산
75분 안팎
학습 목표
제공된 측정 표를 입력받아 손실률과 응답한 표본의 평균 지연을 출력합니다.
개념
느리다는 말을 계산 가능한 자료로 바꿉니다
학교 웹의 지연을 비교할 때 성공한 응답의 시간만 보면 실패가 숨을 수 있습니다. 반대로 실패를 0밀리초로 넣으면 가장 나쁜 측정이 평균을 낮춥니다. 이번 목표는 시도 전체의 손실률과 응답한 표본의 평균 지연을 나란히 출력하는 프로그램을 만드는 것입니다. 입력은 제공된 학습용 표이며 실제 네트워크에서 수집한 수치가 아닙니다. 계산 정답과 회선 상태의 판단을 구분합니다.
표본 하나의 뜻
한 칸은 같은 방법과 대기 제한으로 수행한 시도 한 번입니다. 숫자는 응답을 받은 왕복 밀리초이고 null은 제한 시간 안에 응답이 없었음을 뜻합니다. 0은 매우 작거나 반올림된 유효 응답값이므로 손실로 취급하지 않습니다. null과 0을 같은 조건으로 검사하면 손실 수가 잘못됩니다. 성공 시도만 모은 배열이 아니라 실패 칸까지 포함한 배열을 보존합니다.
입력 형식
표준 입력은 JSON 배열 하나입니다. 예를 들어 [10, null, 20, 0]은 네 번 시도해 세 응답을 받은 자료입니다. 원소는 유한한 0 이상 숫자 또는 null이고 배열 길이는 1 이상입니다. 문자열 숫자, 음수, true, 객체, 빈 배열, 잘못된 JSON은 INVALID 한 줄입니다. 공백과 줄바꿈은 JSON 파서가 처리합니다. NaN과 Infinity도 유한 측정값이 아니므로 거절합니다.
분모를 먼저 정합니다
손실률의 분모는 응답 수가 아닌 전체 시도 수입니다. 손실 두 번과 성공 두 번이면 전체 네 번 중 두 번이므로 50퍼센트입니다. 성공이 늘거나 줄어도 각 칸의 의미는 같습니다. 이미 받은 응답을 또 세지 않고 배열 길이를 시도 수로 둡니다. 손실률을 소수 비율로 계산한 다음 100을 곱하고 표시할 때만 소수 첫째 자리로 반올림합니다.
평균의 모집단을 구분합니다
평균 지연은 응답한 숫자의 합을 응답 수로 나눈 값입니다. 손실을 더하지 않으며 시간 제한을 대신 넣지도 않습니다. [10, null, 20, 0]의 합은 30이고 응답 수는 3이므로 평균은 10.0입니다. 전체 시도 수 4로 나누면 7.5가 되어 질문이 바뀝니다. 그 값은 응답 지연 평균이라고 부를 수 없으므로 분모 이름을 변수와 설명에 함께 적습니다.
전부 손실일 때
[null, null]은 손실률 100.0이고 응답 평균은 NA입니다. 응답이 없는데 0.0으로 출력하면 응답이 매우 빠른 상태와 혼동됩니다. 응답 수가 0인지 나눗셈 전에 검사하면 ZeroDivisionError도 피할 수 있습니다. NA는 이번 출력 계약에서 계산할 수 있는 응답 표본이 없다는 뜻입니다. 손실률 자체는 전체 시도가 존재하므로 정상적으로 계산합니다.
빈 입력과 전부 손실은 다릅니다
[]는 시험이 한 번도 없는 자료라서 INVALID입니다. 이를 손실 0퍼센트로 표시하면 미측정을 정상으로 보고하는 셈입니다. [null]은 실제 시도 하나가 응답 없이 끝난 자료이므로 유효하고 100.0 NA가 됩니다. 운영 대시보드에서도 수집 중단과 서비스 실패를 구분할 필요가 있습니다. 이번 구현은 두 경우를 별도 테스트로 유지하여 의미를 보존합니다.
숫자 타입의 함정
Python의 bool은 정수와 연관된 타입이므로 isinstance(value, int)만 사용하면 true가 1처럼 들어갈 수 있습니다. type(value) in (int, float) 조건으로 불리언을 제외합니다. math.isfinite로 유한성을 검사하고 음수도 검사합니다. 너무 큰 정수나 합산 범위 오류는 INVALID로 다룹니다. JSON 배열이 아닌 객체를 반복하면 키를 측정값처럼 읽을 수 있어 최상위 타입도 먼저 확인합니다.
출력은 두 줄입니다
첫 줄은 loss_pct 뒤에 공백과 소수 첫째 자리 숫자를 씁니다. 둘째 줄은 mean_ms 뒤에 같은 자릿수의 숫자 또는 NA를 씁니다. 예제 배열의 결과는 loss_pct 25.0과 mean_ms 10.0입니다. 퍼센트 기호나 ms 단위를 추가하지 않습니다. 둘 다 값이 0이어도 0.0으로 출력합니다. f-string의 .1f는 표시 형식을 맞추는 방법이며 중간 합을 먼저 반올림하지 않습니다.
구현 순서
sys.stdin.read로 전체 입력을 읽고 json.loads로 배열을 만듭니다. 계약 위반을 검사한 뒤 null 아닌 숫자만 응답 배열에 모읍니다. 전체 길이와 응답 길이의 차이가 손실 수입니다. 계산 결과를 포맷해 출력합니다. 잘못된 입력은 예외 종류를 정해 INVALID로 처리합니다. 디버그 출력은 채점 표준 출력에서 제거하고 실패 사례는 별도의 로컬 재실행으로 확인합니다.
평균은 분포를 숨깁니다
10과 90의 평균은 50이고 50과 50의 평균도 50입니다. 같은 평균이어도 흔들림은 다릅니다. 이 과제는 요구한 두 수치만 출력하지만 현장 보고에서는 표본 수와 개별 시각, 최댓값 또는 분포를 함께 보관합니다. 작은 표본의 손실률을 장기 SLA처럼 설명하지 않습니다. 실패가 많을 때 남은 빠른 응답만으로 지연이 개선됐다는 결론도 내리지 않습니다.
측정 조건을 맞춥니다
ICMP 왕복 지연과 HTTP 전체 시간을 같은 열에 합치지 않습니다. 출발지, 목적지, 패킷 크기, 시간 제한, 실행 간격을 기록합니다. 대기 제한을 줄이면 같은 회선에서도 응답 없음이 늘어날 수 있습니다. 이번 배열에는 메타데이터가 없으므로 모든 칸이 같은 조건이라고 가정합니다. 실제 미션에서 측정 표를 만들 때는 그 가정을 확인할 수 있도록 표 옆에 조건을 붙입니다.
흔한 오답을 테스트로 잡습니다
0을 손실로 센 구현은 [0] 테스트를 통과하지 못합니다. null을 0으로 바꾼 구현은 혼합 배열의 평균이 낮아집니다. 응답 수로 손실률을 나누는 구현은 손실과 성공이 섞인 자료에서 틀립니다. 빈 배열을 정상으로 받은 구현은 미측정 경계 시험에서 실패합니다. 정상 예제 한 개만 맞히는 대신 각 시험이 어떤 계산 오류를 드러내는지 설명합니다.
진단에 수치를 가져갑니다
최종 기록에는 시도 수, 손실 수, 평균의 대상 표본 수와 계산 결과를 함께 적습니다. 100퍼센트 손실이면 다음 조사는 경로·정책·수신 상태 등 후보를 확인하며 평균 숫자를 만들려고 응답을 지어내지 않습니다. 값의 상승만으로 장비 고장을 확정하지 않고 동일 조건의 대조 측정을 계획합니다. 프로그램이 통과하면 제공 표의 계산을 할 수 있게 된 것이며 실망 관측은 다음 진단 절차와 연결합니다.
따라하기
응답만 평균
학습용 배열에서 null을 제외합니다. 이 출력은 실제 Python 계산 결과이며 네트워크 실측이 아닙니다.
a=[10,None,20,0]
r=[x for x in a if x is not None]
print(f'loss_pct {(len(a)-len(r))/len(a)*100:.1f}')
print(f'mean_ms {sum(r)/len(r):.1f}')실행 결과
loss_pct 25.0 mean_ms 10.0
전부 손실 분기
응답 수가 없으면 나눗셈 대신 NA를 씁니다.
a=[None,None]
r=[x for x in a if x is not None]
print(f'loss_pct {(len(a)-len(r))/len(a)*100:.1f}')
print('mean_ms '+(f'{sum(r)/len(r):.1f}' if r else 'NA'))실행 결과
loss_pct 100.0 mean_ms NA
0 응답 유지
0과 null의 판단 차이를 직접 확인하고 browser 코드를 구현합니다.
a=[0,None]
print('responses',sum(x is not None for x in a))
print('lost',sum(x is None for x in a))실행 결과
responses 1 lost 1
확인 문제
실습
JSON 배열을 읽어 loss_pct와 mean_ms 두 줄을 출력합니다. 숫자는 유한한 0 이상 밀리초, null은 응답 없음입니다. 전부 손실의 평균은 NA, 빈 배열·계약 위반은 INVALID입니다. 숫자는 소수 첫째 자리로 출력합니다.
모범 답안
import sys,json,math
try:
a=json.loads(sys.stdin.read())
if type(a) is not list or not a: raise ValueError()
for x in a:
if x is not None and (type(x) not in (int,float) or not math.isfinite(x) or x < 0): raise ValueError()
r=[x for x in a if x is not None]
mean=sum(r)/len(r) if r else None
if mean is not None and not math.isfinite(mean): raise ValueError()
print(f"loss_pct {(len(a)-len(r))/len(a)*100:.1f}")
print('mean_ms '+(f'{mean:.1f}' if mean is not None else 'NA'))
except (ValueError,TypeError,OverflowError):
print('INVALID')
더 읽기
면접 질문
- 응답 없는 표본이 있을 때 손실률과 평균 지연의 분모를 어떻게 정합니까?