주소표를 위한 Python 읽기
100분 안팎
학습 목표
제공된 학교 단말 목록을 입력받아 역할별 개수를 출력합니다.
개념
장비 목록이 늘어도 같은 방법으로 집계합니다
학교 주소 설계에서 “학생 단말이 두 대”라는 숫자는 목록에서 계산한 값이어야 합니다. 장비를 추가하고도 수량을 손으로 고치지 않으면 주소가 부족한 계획을 만들 수 있습니다. 이번 레슨은 단말 목록을 읽어 역할별 개수를 출력하는 작은 Python 프로그램을 만듭니다. 아직 IP 주소를 계산하거나 실제 통신을 하지 않습니다. 같은 입력이 들어오면 같은 순서와 같은 결과를 내는 자료 처리부터 익힙니다.
레슨 입력은 쉼표로 구분한 role,id 형식이며 헤더는 없습니다. 한 줄이 단말 한 대입니다. 역할은 student, staff, server, guest 중 하나이고 ID는 소문자로 시작하는 영숫자 1~10글자입니다. 예를 들어 student,s1은 학생 단말 s1 한 대를 뜻합니다. 줄 앞뒤와 각 필드의 공백은 제거합니다. 빈 줄은 건너뜁니다. 이 형식은 장비 6대가 들어 있는 미션 JSON과 다릅니다. 미션의 router는 전달 장비라 이 레슨의 사용자 단말 집계에 넣지 않습니다.
출력은 student, staff, server, guest 순서의 네 줄입니다. 해당 역할의 단말이 없어도 0을 출력합니다. 이어서 total과 전체 단말 수를 한 줄로 출력합니다. 입력 순서가 바뀌어도 출력 순서가 고정되므로 검사기와 사람이 쉽게 비교할 수 있습니다. 정렬되지 않은 집합의 순서나 입력에서 먼저 만난 역할 순서에 의존하면 값은 맞더라도 제출 계약을 지키지 못할 수 있습니다.
문자열에서 필드를 꺼내는 흐름을 읽습니다
sys.stdin.read()는 표준 입력 전체를 문자열로 읽습니다. splitlines()는 그 문자열을 줄별 목록으로 나눕니다. for raw in ...는 각 줄을 차례로 처리하고, raw.strip()은 앞뒤 공백을 제거한 새 문자열을 만듭니다. if not raw.strip(): continue는 비어 있는 줄의 처리를 건너뛰는 뜻입니다. 다음 줄로 이동하는 것과 전체 프로그램을 끝내는 것은 다르므로 빈 줄 때문에 나머지 단말을 놓치지 않도록 합니다.
줄을 split(",")로 나누면 각 칸을 얻습니다. parts가 두 칸인지 먼저 확인한 뒤 role, device = parts처럼 두 변수에 나누어 담습니다. split은 공백을 자동으로 정리하지 않으므로 각 칸에 strip을 적용합니다. student , s1처럼 들어와도 두 필드가 각각 student와 s1이 되어야 합니다. 이 레슨에서는 필드 안의 쉼표나 따옴표가 허용되지 않아 단순 분리가 충분합니다. 임의의 CSV 문서를 처리하려면 csv 모듈의 규칙이 필요합니다.
counts는 역할 이름을 키로, 지금까지 센 수를 값으로 가진 딕셔너리입니다. 모든 역할을 0으로 준비하고 counts[role] += 1을 하면 해당 역할의 값만 하나 늘어납니다. role이 허용 목록에 없으면 counts[role]에서 KeyError가 날 수 있어 접근 전에 검사합니다. 네트워크 장비 종류가 잘못 들어온 상황을 Python 내부 오류처럼 숨기지 않고 계약 위반으로 알려 주는 것이 이 집계기의 역할입니다.
seen은 이미 읽은 ID를 보관하는 집합입니다. 같은 역할의 두 장비는 ID가 다르면 정상적으로 두 대입니다. 같은 ID가 두 번 나타나면 역할이 같든 다르든 잘못된 목록입니다. 중복을 조용히 제거해 한 대라고 보고하면 두 입력 행의 불일치가 사라져 버립니다. 주소표 작성 전에 원본 작성자가 바로잡을 수 있도록 중복을 오류로 처리합니다. 집합은 수량을 대신 세는 용도가 아니라 중복 여부를 판단하는 용도로 씁니다.
오류가 있으면 부분 집계를 발표하지 않습니다
잘못된 줄이 하나라도 있으면 결과 전체 대신 ERROR 한 줄만 출력합니다. 이 브라우저 문제에서 ERROR는 채점 계약에 정한 정상적인 결과 문자열이며 프로그램은 일반적으로 종료합니다. 앞 레슨의 실제 셸 진단에서 비영 종료 상태와 표준 오류로 실패를 알리는 방식과 목적이 다릅니다. 브라우저는 입력에 대한 결정적인 표준 출력을 비교하므로 오류 사례도 출력 값으로 확인합니다.
잘못된 역할, 필드 수 오류, ID 형식 오류, 중복 ID가 거부 대상입니다. ID 조건은 re.fullmatch(r"[a-z][a-z0-9]{0,9}", device)로 문자열 전체가 맞는지 확인할 수 있습니다. {0,9}는 첫 글자 뒤에 영숫자가 0~9개 더 올 수 있다는 뜻입니다. 빈 ID, 숫자로 시작한 ID, 밑줄이 있는 ID, 11글자 ID는 이 계약에 맞지 않습니다. 실제 장비 이름 규칙이 모두 이와 같다는 주장은 아닙니다.
출력은 입력 검사가 모두 끝난 뒤 수행합니다. 반복문 안에서 student 개수를 바로 출력하면 뒤에서 중복 ID를 발견했을 때 이미 숫자가 나간 상태가 됩니다. 검사할 동안 counts와 seen만 갱신하고, 성공한 경우에만 마지막 출력 루프로 넘어갑니다. 이렇게 하면 ERROR와 일부 정상 결과가 섞이는 일을 막을 수 있습니다. 조건을 발견하면 즉시 ERROR를 출력하고 함수에서 return하는 구조가 읽기 쉽습니다.
빈 입력은 입력 오류가 아니라 단말이 없는 목록으로 정의합니다. 네 역할 모두 0, total 0을 출력합니다. 이는 빈 목록과 잘못된 행을 구별하려는 선택입니다. 실제 운영 주소표에는 최소 장비 수를 별도로 요구할 수 있으며 첫 미션이 그 조건을 검사합니다. 이 작은 함수에서 임의로 최소 6대를 강제하면 한 장비만 넣는 브라우저 경계 시험의 의미가 바뀝니다. 각 도구가 맡은 계약을 명확히 구분합니다.
작은 입력으로 진행 중인 상태를 추적합니다
student,s1과 student,s2를 처리하면 counts의 student만 2가 되고 seen에는 s1과 s2가 들어갑니다. 이어 guest,g1을 읽으면 guest가 1이 됩니다. 아직 staff와 server는 0이므로 출력에서 생략하지 않습니다. total은 counts 값의 합입니다. 입력 줄 수를 그대로 total로 쓰면 빈 줄까지 세거나 오류 행을 포함하게 됩니다. 유효한 단말만 집계한 counts에서 합계를 구합니다.
따라하기의 예제는 각각 독립적인 코드입니다. 앞 단계에서 변수가 만들어졌다고 기대하지 않고 해당 코드 안에 필요한 값을 모두 넣었습니다. 첫 예제에서 필드가 두 개로 잘 분리되는지, 다음 예제에서 역할별 초기값이 보존되는지, 마지막 예제에서 중복 여부가 드러나는지 확인합니다. 그 뒤 브라우저 starter의 TODO를 구현하면서 세 흐름을 하나로 합칩니다. 단순히 출력 숫자를 고정하는 방식은 입력이 달라지는 시험을 통과하지 못합니다.
테스트가 실패할 때 첫 차이를 찾습니다
예상 total 0인데 total 1이면 빈 입력을 한 줄로 센 것은 아닌지 확인합니다. guest 행이 빠졌다면 “입력에 없는 역할은 출력하지 않기”로 구현한 부분을 찾습니다. ERROR가 아닌 Python 예외가 보이면 role과 필드 길이 확인 전에 딕셔너리 접근이나 두 변수 대입을 수행했을 수 있습니다. ValueError: too many values to unpack는 필드가 둘보다 많은 줄을 두 변수에 넣으려 했다는 단서입니다.
출력에 디버깅 문장이 섞이면 채점 계약과 달라집니다. 제출 전에는 print(counts)처럼 내부 상태를 보여 주는 줄을 제거하고 결과 다섯 줄 또는 ERROR만 남깁니다. 테스트는 빈 목록, 전 역할, 공백, 중복, 잘못된 역할, 필드 수와 ID 길이 경계를 포함합니다. 하나가 실패했을 때 전체 코드를 바꾸기보다 해당 입력에서 어느 검사가 필요했는지 따라가며 고칩니다. 딕셔너리·집합의 더 넓은 비교는 서재 장에서 이어 읽습니다.
따라하기
공백을 제거해 필드 읽기
각 필드의 공백을 처리합니다. 출력은 정리된 역할과 ID입니다.
raw = ' student , s1 '
parts = [part.strip() for part in raw.split(',')]
print(len(parts))
role, device = parts
print(role, device)실행 결과
2 student s1
역할 초기값과 합계 확인
없는 역할도 0을 보존합니다. 여기서는 유효한 줄만 있다고 가정한 집계 핵심을 확인합니다.
roles = ('student', 'staff', 'server', 'guest')
counts = dict.fromkeys(roles, 0)
for role in ['student', 'student', 'guest']:
counts[role] += 1
for role in roles:
print(role, counts[role])
print('total', sum(counts.values()))실행 결과
student 2 staff 0 server 0 guest 1 total 3
중복 ID 판단
같은 ID가 두 번째로 등장하는 시점의 검사 결과를 봅니다.
seen = set()
for device in ['s1', 's2', 's1']:
print(device, 'duplicate' if device in seen else 'new')
seen.add(device)실행 결과
s1 new s2 new s1 duplicate
입력부터 출력까지 실행
main 함수가 검사 완료 후에만 결과를 출력합니다. 표준 입력 예시는 student,s1, staff,t1, guest,g1의 세 줄입니다.
import re
import sys
def main():
roles = ('student', 'staff', 'server', 'guest')
counts = dict.fromkeys(roles, 0)
seen = set()
for raw in sys.stdin.read().splitlines():
if not raw.strip():
continue
parts = [part.strip() for part in raw.split(',')]
if len(parts) != 2:
print('ERROR')
return
role, device = parts
if role not in counts or not re.fullmatch(r'[a-z][a-z0-9]{0,9}', device) or device in seen:
print('ERROR')
return
seen.add(device)
counts[role] += 1
for role in roles:
print(role, counts[role])
print('total', sum(counts.values()))
main()
실행 결과
student 1 staff 1 server 0 guest 1 total 3
확인 문제
실습
표준 입력의 role,id 행을 집계합니다. 헤더는 없고 필드 앞뒤 공백과 빈 줄은 허용합니다. 역할은 student/staff/server/guest, ID는 소문자로 시작하는 영숫자 1~10글자입니다. 중복 ID·잘못된 역할·필드 수·ID 형식 오류가 하나라도 있으면 ERROR 한 줄만 출력합니다. 정상 입력은 student, staff, server, guest 순서로 “역할 개수” 네 줄과 “total 합계” 한 줄을 출력합니다. 빈 입력은 모두 0입니다. 실제 패킷 송수신은 하지 않습니다.
모범 답안
import re
import sys
def main():
roles = ('student', 'staff', 'server', 'guest')
counts = dict.fromkeys(roles, 0)
seen = set()
for raw in sys.stdin.read().splitlines():
if not raw.strip():
continue
parts = [part.strip() for part in raw.split(',')]
if len(parts) != 2:
print('ERROR')
return
role, device = parts
if role not in counts or not re.fullmatch(r'[a-z][a-z0-9]{0,9}', device) or device in seen:
print('ERROR')
return
seen.add(device)
counts[role] += 1
for role in roles:
print(role, counts[role])
print('total', sum(counts.values()))
main()
더 읽기
면접 질문
- 장비 목록에서 중복 ID와 역할별 수량을 어떻게 검사하나요?
- 주소 할당표의 수량을 원본 목록과 일치시키는 방법을 설명해 보세요.