Files
hackerthon-vote/assign_teams.py
th-kim0823 3453217e62 refactor: 부서별 ceil-aware 균등 분배 알고리즘
기존 round-robin은 운에 따라 부서 분포 max-min=2 발생 (예: HPC 2,1,0,1,1,2,1).
ceil 슬롯을 ceil_count 적은 팀에 우선 배정하여 모든 부서 max-min ≤ 1 보장.

결과:
- EffTech [1,2,2,2,2,2,1]
- System  [2,1,1,1,1,1,1]
- HPC     [1,1,1,1,1,1,2]
- Data    [1,1,1,1,0,1,1]
- Platform [0,0,0,0,1,0,0] (1명)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-25 19:08:56 +09:00

143 lines
4.4 KiB
Python

"""
참가자 35명 → 7팀 (팀당 5명) 배정.
같은 부서 2명 이하 제약. 랜덤 시드로 재현 가능.
결과: participants.json (이름→팀 매핑) + 콘솔 출력.
"""
import json
import random
from collections import Counter, defaultdict
from pathlib import Path
PEOPLE = [
("한지승", "MLOps Platform"),
("변수민", "MLOps Data"),
("박재호", "MLOps Data"),
("김태현", "MLOps Data"),
("강승형", "MLOps Data"),
("손현준", "MLOps Data"),
("김동국", "MLOps Data"),
("김재현", "MLOps HPC"),
("이준석", "MLOps HPC"),
("오근현", "MLOps HPC"),
("김정명", "MLOps HPC"),
("김영관", "MLOps HPC"),
("유용혁", "MLOps HPC"),
("최호진", "MLOps HPC"),
("전효준", "MLOps HPC"),
("김병훈", "MLOps System"),
("이지환", "MLOps System"),
("서희", "MLOps System"),
("정채윤", "MLOps System"),
("장혁진", "MLOps System"),
("장다현", "MLOps System"),
("박영훈", "MLOps System"),
("길주현", "MLOps System"),
("조민정", "AI Efficiency Tech"),
("김민섭", "AI Efficiency Tech"),
("김호승", "AI Efficiency Tech"),
("서한배", "AI Efficiency Tech"),
("심성환", "AI Efficiency Tech"),
("유준희", "AI Efficiency Tech"),
("이성재", "AI Efficiency Tech"),
("이재광", "AI Efficiency Tech"),
("이정태", "AI Efficiency Tech"),
("이준형", "AI Efficiency Tech"),
("정현준", "AI Efficiency Tech"),
("유지원", "AI Efficiency Tech"),
]
NUM_TEAMS = 7
TEAM_SIZE = 5
SEED = 20260428 # 행사일 시드 (재현 가능)
def assign(seed):
"""
부서별 균등 분배 + 팀 사이즈 5 보장.
각 부서 N명 → q명 모든 팀 + r명 추가 (q, r = divmod(N, NUM_TEAMS)).
추가 r명 받을 팀은 "지금까지 추가 적게 받은 팀" 우선 → 모든 팀 정확히 같은 횟수만큼 ceil 받음.
효과:
- 부서별 분포: max - min ≤ 1 (균등)
- 모든 팀 정확히 5명
- EffTech 12명: 5팀이 2명, 2팀이 1명 (모든 팀 ≥1)
"""
rng = random.Random(seed)
by_dept = defaultdict(list)
for name, dept in PEOPLE:
by_dept[dept].append(name)
for d in by_dept:
rng.shuffle(by_dept[d])
depts_sorted = sorted(by_dept.keys(), key=lambda d: -len(by_dept[d]))
teams = [[] for _ in range(NUM_TEAMS)]
ceil_count = [0] * NUM_TEAMS # 각 팀이 받은 ceil 횟수
for dept in depts_sorted:
members = by_dept[dept]
n = len(members)
q, r = divmod(n, NUM_TEAMS)
if r > 0:
# ceil_count 적은 팀 우선, tie는 random
order = sorted(range(NUM_TEAMS), key=lambda i: (ceil_count[i], rng.random()))
ceil_teams = set(order[:r])
else:
ceil_teams = set()
idx = 0
for ti in range(NUM_TEAMS):
count = q + (1 if ti in ceil_teams else 0)
for _ in range(count):
teams[ti].append((members[idx], dept))
idx += 1
if ti in ceil_teams:
ceil_count[ti] += 1
return teams
def main():
assert len(PEOPLE) == NUM_TEAMS * TEAM_SIZE, (
f"인원수 불일치: {len(PEOPLE)}명, 기대 {NUM_TEAMS * TEAM_SIZE}"
)
teams = assign(SEED)
print(f"# 시드 {SEED} (round-robin 결정적 배정)\n")
# 부서별 분포 검증
dept_dist = defaultdict(list)
for team in teams:
team_counts = Counter(d for _, d in team)
for dept in {d for _, d in PEOPLE}:
dept_dist[dept].append(team_counts.get(dept, 0))
print("# 부서별 팀 분배 (max - min ≤ 1 = 균등)")
for dept, dist in sorted(dept_dist.items(), key=lambda x: -sum(x[1])):
print(f" {dept}: {dist} (총 {sum(dist)}명)")
print()
mapping = {}
for i, team in enumerate(teams, 1):
team_name = f"{i}"
dept_counts = Counter(d for _, d in team)
dept_summary = ", ".join(f"{d.replace('MLOps ', '').replace('AI Efficiency Tech', 'AI Eff')} {c}" for d, c in dept_counts.items())
print(f"## {team_name} ({dept_summary})")
for name, dept in team:
print(f" - {name} ({dept})")
mapping[name] = team_name
print()
out = Path(__file__).parent / "participants.json"
out.write_text(
json.dumps(mapping, ensure_ascii=False, indent=2),
encoding="utf-8",
)
print(f"저장: {out}")
if __name__ == "__main__":
main()