무엇을 만드나
급식 줄에 대한 물음을 이제 실제로 실행해 봅니다.
물음
「학년별로 수업을 조금씩 다른 시각에 마치면, 가장 긴 줄이 얼마나 짧아질까?」
핵심 요소는 넷입니다 — 마치는 시각, 걸어오는 시간, 배식대 개수, 한 사람이 받는 시간. 여기에 사람마다 다른 것을 흔들림으로 넣습니다. 코드 맨 위에 대문자 이름으로 모아 두었습니다.
물음에 걸려 있어 바꿔 가며 실행해 볼 값이 열어 둔 값이고, 이번 실험에서
가정으로 깔고 가는 값이 고정한 값입니다. 이번 실험의 열어 둔 값인 간격(gap)은
본문에서 함수의 인자로 넘기고, 나머지 값은 맨 위의 이름으로 적습니다.
GRADES = (1, 2, 3)
STUDENTS_PER_GRADE = 120 # 학년마다 몇 명이 급식을 먹는지
WALK_SECONDS = {1: 60, 2: 90, 3: 120} # 교실에서 급식실까지 걸리는 시간
WALK_SPREAD = 150 # 사람마다 흩어지는 정도 - 뛰는 사람, 걷는 사람, 들렀다 오는 사람
COUNTERS = 4 # 배식대 개수
SERVE_MIN = 6 # 한 사람이 배식받는 데 걸리는 시간 - 가장 짧을 때
SERVE_MAX = 10 # 가장 길 때
TOTAL_SECONDS = 2400 # 40분 동안 지켜본다| 이름 | 무엇인가 | 어디에 드나 |
|---|---|---|
STUDENTS_PER_GRADE |
학년마다 급식을 먹는 학생 수. 셋을 합쳐 360명이다. | 고정한 값 |
WALK_SECONDS |
교실에서 급식실까지 걸리는 시간. 학년마다 다르게 두었다. 층이 다르기 때문이다. | 고정한 값 |
WALK_SPREAD |
사람마다 흩어지는 정도. 뛰는 사람, 걷는 사람, 들렀다 오는 사람을 이 하나로 표현했다. | 흔들림 |
COUNTERS |
배식대 개수. 동시에 몇 명이 받는지를 정한다. | 고정한 값(「해 보기」에서 열어 본다) |
SERVE_MIN · SERVE_MAX |
한 사람이 받는 데 걸리는 시간. 6초에서 10초 사이로 매번 다르게 뽑는다. | 고정한 값(「해 보기」에서 열어 본다) + 흔들림 |
이번 물음에서 바꿔 가며 실행할 값은 학년 사이의 간격(gap) 하나입니다.
표에 있는 배식대 개수와 배식 시간은 이번 실험에서는 고정하고, 「해 보기」에서 바꿔 봅니다.
여러 사람을 하나의 흔들림으로 묶었다
「뛰는 사람 / 걷는 사람 / 친구를 기다리는 사람」을 각각 만들 수도 있었습니다. 그러지 않고 「도착 시각을 150초 안에서 흩는다」 하나로 묶었습니다.
이것이 덜어 내기의 실제 모습입니다. 세 가지를 따로 만들면 코드가 길어지는데, 「가장 긴 줄」이라는 물음에는 결과가 크게 달라지지 않으리라 보고 하나로 묶었습니다. 셋을 구별해야 할 물음이라면 그때 나누면 됩니다.
한 걸음은 1초로 잡았습니다. 배식이 8초 안팎이므로 1초면 충분히 잘게 본 것입니다. 1분으로 잡았다면 그 사이에 서른 명 가까이 받은 것이 한꺼번에 처리되어, 줄이 어떻게 오르내렸는지 볼 수 없었을 것입니다.
WALK_SECONDS를 학년마다 다르게 둔 까닭은 무엇일까요? 셋을 같은
값으로 두면 결과가 어떻게 달라질지 예상해 보세요.
규칙 적기
규칙은 두 덩어리입니다. 언제 누가 오는가와 한 걸음마다 무슨 일이 일어나는가입니다.
언제 누가 오는가
def arrivals(gap):
"""학생마다 급식실에 닿는 시각을 초 단위로 만들어 돌려준다.
gap은 학년별로 수업을 마치는 시각의 간격이다.
0이면 세 학년이 동시에 마친다.
"""
times = []
for order, grade in enumerate(GRADES):
end = order * gap
for _ in range(STUDENTS_PER_GRADE):
walk = WALK_SECONDS[grade] + random.randint(0, WALK_SPREAD)
times.append(end + walk)
return sorted(times)order * gap이 학년마다 다른 시각에 마치게 하는 부분입니다.gap이 0이면 셋 다 0초에 마칩니다.random.randint(0, WALK_SPREAD)가 사람마다 흩어지게 합니다. 이 한 줄이 없으면 같은 학년 120명이 정확히 같은 순간에 도착합니다.sorted(times)로 이른 사람부터 정렬해 둡니다. 그래야 한 걸음마다 앞에서부터 꺼내 쓸 수 있습니다.
한 걸음마다 무슨 일이 일어나는가
def run_once(gap):
"""한 번 돌려 가장 긴 줄과 마지막 사람이 받은 시각을 돌려준다."""
coming = arrivals(gap)
next_to_arrive = 0
queue = 0 # 지금 줄에 선 사람 수
free_at = [0] * COUNTERS # 배식대마다 언제 비는지
longest = 0
last_served = 0
for now in range(TOTAL_SECONDS):
# 이 순간에 닿은 사람들이 줄 뒤에 선다
while next_to_arrive < len(coming) and coming[next_to_arrive] <= now:
queue = queue + 1
next_to_arrive = next_to_arrive + 1
# 비어 있는 배식대가 앞사람을 받는다
for c in range(COUNTERS):
if free_at[c] <= now and queue > 0:
queue = queue - 1
free_at[c] = now + random.randint(SERVE_MIN, SERVE_MAX)
last_served = now
if queue > longest:
longest = queue
return longest, last_served| 차례 | 하는 일 |
|---|---|
| ① 도착 | 이 순간에 닿은 사람들을 줄 뒤에 세운다.
while인 까닭은 한 초에 여러 명이 닿을 수 있기 때문이다.
|
| ② 배식 | 배식대를 하나씩 살펴 비어 있고 줄에 사람이 있으면 한 명을 받는다. 받은 배식대는 6~10초 뒤에 다시 빈다. |
| ③ 기록 | 지금 줄이 이제까지 가장 길면 그 값을 남긴다. |
순서를 바꾸면 결과가 달라진다
①과 ②의 순서를 바꾸면 방금 도착한 사람이 그 초에 바로 받지 못하고 한 걸음을 기다립니다. 줄이 비어 있을 때 온 사람은 모두 1초씩 늦게 받게 됩니다.
어느 쪽이 맞을까요? 현실에서 어느 쪽인지를 생각해 정해야 합니다. 줄이 비어 있으면 오자마자 받으니 지금 순서가 맞습니다. 이런 사소해 보이는 순서도 모형을 만드는 사람이 정하는 것입니다.
규칙이 이만큼 짧다
한 걸음의 규칙은 열 줄 남짓입니다. 줄이 얼마나 길어질지는 아무 데도 적혀 있지 않습니다. 실행해 봐야 압니다.
②에서 if를 쓰지 않고 무조건 한 명씩 받게 하면 어떻게 될까요?
queue가 어떤 값이 될지 검토해 보세요.
실행해 보기
우연이 들어 있으므로 한 번 실행한 값은 그날 하루일 뿐입니다. 그래서 같은 조건으로 다섯 번씩 실행하고, 다섯 값을 모두 적습니다.
def run_many(gap, times=5):
"""같은 조건으로 여러 번 돌려 결과를 모아 돌려준다."""
longests = []
lasts = []
for _ in range(times):
longest, last = run_once(gap)
longests.append(longest)
lasts.append(last)
return longests, lasts그리고 간격을 0 · 2 · 5 · 10분으로 바꿔 가며 실행합니다. 바꾸는 것은 간격 하나뿐이고 나머지는 그대로입니다.
random.seed(20260824)
print("간격(분) 가장 긴 줄(다섯 번) 마지막 사람(분)")
for gap_minutes in (0, 2, 5, 10):
longests, lasts = run_many(gap_minutes * 60)
average = sum(longests) / len(longests)
last_minutes = max(lasts) // 60
print(f"{gap_minutes:>6} {longests} 평균 {average:5.1f} {last_minutes}")나온 결과
| 간격 | 가장 긴 줄(다섯 번) | 평균 | 마지막 사람이 받은 시각 |
|---|---|---|---|
| 0분 | 254 · 255 · 254 · 256 · 256 | 255.0 | 13분 |
| 2분 | 135 · 138 · 134 · 131 · 132 | 134.0 | 12분 |
| 5분 | 46 · 46 · 47 · 44 · 44 | 45.4 | 16분 |
| 10분 | 47 · 46 · 45 · 45 · 49 | 46.4 | 26분 |
다섯 값이 촘촘히 모여 있다
5분 줄을 보면 44부터 47까지입니다. 아주 좁게 모여 있습니다. 2분 줄과의 차이(134와 45)는 다섯 번 값이 퍼진 폭보다 훨씬 큽니다. 그러니 「2분보다 5분이 낫다」는 우연으로 보기 어렵습니다.
반대로 5분(45.4)과 10분(46.4)의 차이는 다섯 번 값이 퍼진 폭 안에 들어 있습니다. 이럴 때는 「둘은 사실상 같다」고 읽어야 합니다. 평균 숫자만 보고 「10분이 더 나쁘다」고 하면 없는 차이를 만들어 내는 셈입니다.
평균과 함께 퍼진 모양까지 보았기 때문에 이렇게 읽을 수 있었습니다. 다섯 값을 다 적어 두지 않았다면 이 판단을 할 수 없었을 것입니다.
만약 5분에서 나온 다섯 값이 20 · 45 · 70 · 30 · 62였다면, 평균은 같아도 어떻게 읽어야 할까요?
읽어 내기
표에서 가장 중요한 칸은 「마지막 사람이 받은 시각」입니다. 줄만 보면 5분과 10분이 같지만, 마지막 사람이 받은 시각은 16분과 26분으로 크게 다릅니다.
맞바꿈이 드러났다
간격을 벌리면 한꺼번에 몰리는 일이 줄어 줄이 짧아집니다. 그런데 너무 벌리면 마지막 학년이 아예 늦게 출발하게 되어, 그 학년 학생들의 점심시간이 짧아집니다.
처음 물음은 「가장 긴 줄」만 물었습니다. 그런데 실행해 보니 묻지 않았던 것이 함께 드러났습니다. 마지막 사람이 받은 시각을 함께 기록해 두었기 때문입니다. 물음에 걸린 값만 기록했다면 이 맞바꿈은 보이지 않았을 것입니다.
그래서 결론은 「간격을 최대한 벌리자」가 아닙니다. 실행해 본 네 간격 안에서 이 모형이 말하는 것은 「5분 언저리에서 줄이 줄어드는 효과는 거의 다 얻어지고, 그 뒤로는 손해만 커진다」입니다.
이 모형이 말할 수 없는 것
- 수업 시간표를 어떻게 맞출지. 학년마다 마치는 시각을 바꾸려면 시간표 전체를 수정해야 한다.
- 늦게 먹는 학년의 불만. 매번 같은 학년이 마지막이면 공평하지 않다.
- 식판 반납과 자리 찾기. 처음부터 담지 않았으므로 이 모형은 모른다.
- 다른 방법. 배식대를 늘리거나 메뉴를 나누는 쪽이 나을 수도 있다.
「다른 방법」 항목이 중요합니다. 우리는 「간격을 바꾼다」는 한 가지 방법만 실행해 봤습니다. 모형이 그 방법을 가장 좋은 방법이라고 말한 것이 아닙니다.
해 보기 — 네 가지 고쳐 보기
- 배식대를 늘려 보세요.
COUNTERS를 5, 6으로 바꿔 실행하면 간격을 벌리는 것과 어느 쪽이 효과가 큰가요? - 흔들림을 없애 보세요.
WALK_SPREAD를 0으로 하면 어떻게 되나요? 줄이 짧아지나요, 길어지나요? - 우리 학교 값으로 바꿔 보세요. 학생 수와 배식대 개수, 걸리는 시간을 실제로 알아보고 넣습니다.
- 학년 순서를 바꿔 보세요. 지금은 1학년이 먼저입니다. 급식실에서 가장 먼 학년을 먼저 내보내면 어떻게 될까요?
학년 순서 바꾸기가 특히 재미있습니다. 간격을 벌리지 않고도 줄을 줄이는 방법이 있을지 모릅니다. 실제로 실행해 보고 예상과 맞는지 확인해 보세요.
「매번 같은 학년이 마지막」이라는 문제를 모형을 고치지 않고 풀 방법이 있을까요? 학교에서 정할 수 있는 것으로 생각해 보세요.
핵심 정리
무엇을 만드나
- 조건은 맨 위에 대문자 이름으로 모아 둔다. 고칠 때 한 곳만 본다.
- 열어 둔 값과 고정한 값은 바꿔 가며 실행해 볼 값인지로 구분한다. 이번 실험의 열어 둔 값
gap은 인자로 넘기고, 나머지는 맨 위 이름으로 적는다. - 여러 사람의 차이를 흔들림 하나로 묶는 것도 덜어 내기다.
- 한 걸음을 1초로 잡았다. 1분이면 줄의 오르내림이 보이지 않는다.
규칙 적기
- 규칙은 언제 오는가와 한 걸음마다 무슨 일이 일어나는가 둘이다.
- 한 걸음 안의 순서도 만드는 사람이 정한다. 바꾸면 결과가 달라진다.
- 규칙은 열 줄 남짓이고, 결과는 아무 데도 적혀 있지 않다.
실행해 보기
- 같은 조건을 다섯 번씩 실행하고 다섯 값을 모두 적는다.
- 차이가 다섯 번 값이 퍼진 폭보다 크면 우연으로 보기 어렵고, 그 폭 안에 있으면 「사실상 같다」.
- 평균만 적었다면 이 판단을 할 수 없었다.
읽어 내기
- 간격을 어느 이상 벌리면 줄은 더 짧아지지 않고 마지막 사람만 늦어진다. 맞바꿈이 드러났다.
- 물음 밖의 값도 함께 기록해 두면 내가 미처 묻지 않은 것이 드러난다.
- 모형은 실행해 본 방법이 최선이라고 말한 것이 아니다. 다른 방법이 있을 수 있다.
확인 퀴즈
배운 내용을 확인해 봅시다.
5분에서 45.4, 10분에서 46.4가 나왔습니다. 어떻게 읽어야 하나요?
간격을 10분으로 하면 무엇이 나빠지나요?
도착을 만드는 부분에서 random.randint(0, WALK_SPREAD)를
빼면 어떻게 되나요?
이 모형의 결과로 말할 수 있는 것은?