인공지능과 오토메이션/Automation

회원사 -참여자 자동매칭 프로그램 (Python)

토니치코 2025. 4. 25. 13:50
반응형

1. 회원사 - 참여자 자동매칭 프로그램 (Python) 개요

이 코드는 “참여자가 면접을 희망하는하는 회원사”“회원사가 면접을 원하는 참여자” 정보를 바탕으로,
정해진 시간대와 10분 단위 슬롯(시간칸)에 맞춰 면접 매칭 테이블을 자동으로 생성합니다.

  • 참여자-회사 간 우선순위 점수(예: 1지망=5점, 2지망=4점 등)를 계산
  • 양쪽(참여자, 회사) 모두 원하는 매칭일수록 높은 점수가 되어 먼저 매칭
  • 시간대가 겹치지 않도록(동일 시간대에 한 참여자가 두 회사를 면접 못 하도록) 배정
  • 결과를 엑셀 파일(매칭결과.xlsx)로 저장

이 코드를 실행하면 다음과 같은 단계를 거칩니다.

  1. 엑셀 파일 선택:
    • “참여자→회원사” 희망 순위 엑셀 파일
    • “회원사→참여자” 희망 순위 엑셀 파일
  2. 데이터 로딩 & 전처리
  3. 매칭 알고리즘 수행
  4. 결과 엑셀 생성 & 저장

2. 주요 로직 개요

(A) 참여자-회원사 우선순위 점수

  • 참여자가 1순위(1지망)로 고른 회사면 5점, 2순위면 4점, … 5순위면 1점
  • 회사가 1순위(1지망)로 고른 참여자면 5점, 2순위면 4점, … 5순위면 1점
  • “참여자 우선순위 점수” + “회사 우선순위 점수”의 합으로 매칭 우선도를 정함
    • 예: 어떤 참여자가 그 회사를 1순위(5점), 회사도 그 참여자를 2순위(4점)라 했다면, 합은 9점

(B) 시간대와 서브슬롯

  • 프로그램 안에는 아래처럼 시간표(time_table)가 정의되어 있습니다.
    time_table = {
        "09:00-10:00": ["도우", "디스코", "루시드프로모", "마루프런티어", "미도리얼코"],
        "10:00-11:00": ["건물과사람들", "국진", "더큐브컴퍼니", "더피알커뮤니케이션", "태복플래닝"],
        ...
    }
    • 예를 들어 “09:00-10:00” 에는 5개 회사(도우, 디스코, 루시드프로모, 마루프런티어, 미도리얼코)가 면접을 진행.
  • 각 시간대에 10분 간격으로 0, 10, 20, 30, 40, 50 총 6개의 슬롯을 둡니다.
    • 따라서 한 회사는 1시간 동안 최대 6명의 지원자를 면접 가능.

(C) 매칭 순서

  1. 모든 (참여자, 회원사) 쌍에 대해 우선순위 합계를 계산해 리스트에 담는다.
  2. 그 리스트를 점수가 높은 순(내림차순)으로 정렬.
  3. 높은 점수 쌍부터 차례대로
    • 그 회사가 속한 시간대에서 빈 슬롯(그리고 참여자가 그 시간대에 다른 면접이 없는 슬롯)을 찾는다.
    • 있으면 배정하고, 없으면(슬롯이 다 찼으면) 패스.
  4. 최종 매칭 결과를 엑셀로 저장.

3. 코드 한 줄씩 이해하기

아래는 코드 전체를 구조 별로 나눈 뒤 간단한 주석과 함께 설명합니다.


3-1. 모듈 임포트

import pandas as pd
import tkinter as tk
from tkinter import filedialog
  • pandas: 엑셀 파일을 쉽게 읽고 쓰기 위한 라이브러리
  • tkinter: 파이썬 기본 GUI 라이브러리 (파일 선택 창을 띄우기 위해 사용)

3-2. 엑셀 파일을 선택하는 함수

def load_excel_file(title="엑셀 파일을 선택하세요"):
    """엑셀 파일을 불러오는 간단한 함수"""
    root = tk.Tk()
    root.withdraw()
    file_path = filedialog.askopenfilename(
        title=title,
        filetypes=[("Excel files", "*.xlsx *.xls")]
    )
    root.destroy()
    return file_path
  1. tk.Tk()tkinter 윈도우 생성
  2. root.withdraw()보이지 않도록(숨김) 설정
  3. filedialog.askopenfilename(...)파일 선택 대화창 열기
    • title: 창 제목
    • filetypes: 확장자 필터 (여기서는 .xlsx, .xls)
  4. root.destroy() → 사용 끝났으므로 창 닫기
  5. 최종적으로 선택된 파일 경로(file_path)를 반환.

이 함수:

  • “어떤 파일을 열고 싶나요?” 같은 안내 창을 띄우고,
  • 사용자가 직접 엑셀 파일을 선택하면 그 경로를 받아옴.

3-3. main() 함수

이 프로그램의 중심 함수입니다.
맨 아래의

if __name__ == "__main__":
    main()

를 통해 스크립트를 직접 실행할 때 main()가 호출됩니다.

3-3-1) 엑셀 파일 불러오기 (

print("1) 참여자 희망기업 엑셀을 선택하세요. (열 제목 없이, 첫 줄부터 데이터인 파일)")
participant_file = load_excel_file("참여자 -> 회원사 희망 순위 엑셀을 선택")
if not participant_file:
    print("파일이 선택되지 않았습니다. 종료합니다.")
    return
  • 참여자 희망기업 엑셀 파일을 먼저 선택.
  • 만약 취소 등을 해서 participant_file 경로가 비었다면, 프로그램을 종료.
print("2) 회원사 희망참여자 엑셀을 선택하세요. (열 제목 없이, 첫 줄부터 데이터인 파일)")
company_file = load_excel_file("회원사 -> 참여자 희망 순위 엑셀을 선택")
if not company_file:
    print("파일이 선택되지 않았습니다. 종료합니다.")
    return
  • 다음으로 회원사 희망참여자 엑셀 파일을 선택.

3-3-2) 엑셀 읽기

df_participant = pd.read_excel(
    participant_file,
    header=None,
    names=["이름","1지망","2지망","3지망","4지망","5지망"],
    dtype=str
)

df_company = pd.read_excel(
    company_file,
    header=None,
    names=["회사명","1지망","2지망","3지망","4지망","5지망"],
    dtype=str
)
  • pandas.read_excel() 함수를 이용해 엑셀 파일을 DataFrame 형태로 불러옵니다.
  • header=None엑셀에 별도의 컬럼 제목 행이 없다고 가정.
    (맨 윗줄부터 실제 데이터가 들어있다고 보는 것)
  • names=[...] → 우리가 직접 6개의 열 이름을 붙여줌.
    (A열=이름, B열=1지망, … F열=5지망)
  • dtype=str → 모든 값을 문자열로 읽음(숫자, 날짜가 섞여 있어도 에러 방지).
df_participant.dropna(how='all', inplace=True)
df_company.dropna(how='all', inplace=True)
  • 모든 칸이 비어있는 행(결측치만 있는 행) 은 제거.

3-3-3) 시간표 설정

time_table = {
    "09:00-10:00": ["도우", "디스코", "루시드프로모", "마루프런티어", "미도리얼코"],
    "10:00-11:00": ["건물과사람들", "국진", "더큐브컴퍼니", "더피알커뮤니케이션", "태복플래닝"],
    "11:00-12:00": ["씨엘케이", "애드파워", "에이치와이마케팅그룹", "와이낫플래닝", "콘텐츠 민주주의"],
    "12:00-13:00": ["상림디엠텍", "유성", "신림피앤디", "쓰리에스씨앤에프", "씨앤디플래닝"],
    "13:00-14:00": [],  # 점심 시간 (면접 없음)
    "15:00-16:00": ["유당디앤씨", "세보", "데이터노우즈", "한국자산매입", "홈스페이"]
}
sub_slots = [0, 10, 20, 30, 40, 50]
  • time_table:
    • 키(key) = 시간 구간 (예: "09:00-10:00")
    • 값(value) = 그 시간대에 면접을 보는 5개 회사 이름(리스트)
  • sub_slots: 한 시간에 0,10,20,30,40,50 분 시작하는 6개의 면접 슬롯을 의미.

3-3-4) 참여자 우선순위, 회원사 우선순위 정리

participant_pref = {}
for idx, row in df_participant.iterrows():
    name = row["이름"]
    if pd.isna(name):
        continue
    name = name.strip()
    if not name:
        continue

    participant_pref[name] = {}
    for i in range(1,6):
        comp = row[f"{i}지망"]
        if pd.isna(comp):
            continue
        comp = comp.strip()
        if not comp:
            continue
        score = 6 - i   # (i=1 → 5점, i=2 → 4점 ...)
        participant_pref[name][comp] = score
  • df_participant.iterrows() → 참여자 엑셀의 각 행(row)을 순회.
  • row["이름"] → 이 행의 “이름” 열 값 (참여자 이름).
  • row["1지망"] ~ row["5지망"] → 1순위 ~ 5순위 회사명.
    • 순위 i에 대해 점수는 6 - i. (1 → 5점, 2 → 4점, 5 → 1점)
  • 이렇게 해서 participant_pref 딕셔너리에
    • participant_pref["참여자이름"] = { "회사A": 점수, "회사B": 점수, ... } 형태로 저장.
company_pref = {}
for idx, row in df_company.iterrows():
    comp_name = row["회사명"]
    ...
    company_pref[comp_name] = {}
    ...
    company_pref[comp_name][p_name] = score
  • 마찬가지로 회원사 엑셀을 돌며,
    회사가 1지망, 2지망으로 원하는 참여자 목록을 점수화해 저장.
    • company_pref["회사명"] = { "참여자이름": 점수, ... }

3-3-5) 회원사가 어느 시간대에 속하는지 매핑

company_to_time = {}
for tslot, clist in time_table.items():
    for c in clist:
        company_to_time[c] = tslot
  • 예:
    • company_to_time["도우"] = "09:00-10:00"
    • company_to_time["디스코"] = "09:00-10:00"
    • 등등.

3-3-6) (참여자, 회원사) 쌍별 점수 계산 & 리스트화

all_participants = list(participant_pref.keys())
all_companies = list(company_pref.keys())

pair_list = []
for p in all_participants:
    for c in all_companies:
        p_score = participant_pref[p].get(c, 0)
        c_score = company_pref[c].get(p, 0) if c in company_pref else 0
        total_score = p_score + c_score
        if total_score > 0 and (c in company_to_time):
            pair_list.append((total_score, p, c))
  • 모든 참여자 p 와 모든 회사 c 에 대해 “참여자가 c를 원하는 점수” + “회사 c가 p를 원하는 점수” = total_score.
  • 이 점수가 0보다 크고, 해당 회사가 time_table 에 존재한다면(즉 면접이 있는 회사라면), 리스트에 추가.
pair_list.sort(key=lambda x: x[0], reverse=True)
  • (점수, 참여자, 회사) 형태의 튜플을 점수 기준으로 내림차순 정렬.

3-3-7) 매칭 구조 준비

matches = {}
participant_busy = {}
for tslot in time_table.keys():
    matches[tslot] = {}
    for c in time_table[tslot]:
        matches[tslot][c] = []
for p in all_participants:
    participant_busy[p] = {}
    for tslot in time_table.keys():
        participant_busy[p][tslot] = set()
  • matches[시간대][회사] = [(슬롯번호, 참여자이름), ...] 형태.
  • participant_busy[참여자][시간대] = {이미 배정된 슬롯번호들}
    • 한 시간대에 한 참여자는 두 면접을 동시에 볼 수 없으므로, 슬롯이 겹치지 않도록 추적.

3-3-8) 매칭 로직

for total_score, p, c in pair_list:
    tslot = company_to_time[c]
    # 1) 회사 c가 이미 6명 배정됐다면 넘어감
    if len(matches[tslot][c]) >= len(sub_slots):
        continue

    used_by_p = participant_busy[p][tslot]
    used_by_c = [slot for (slot, _) in matches[tslot][c]]
    ...
    for s in sub_slots:
        if (s not in used_by_p) and (s not in used_by_c):
            available_slot = s
            break
    if available_slot is not None:
        matches[tslot][c].append((available_slot, p))
        participant_busy[p][tslot].add(available_slot)
  • 점수가 높은 쌍부터 순회
  • 그 회사가 속한 시간대(tslot)에서,
    • 회사가 이미 6명 배정됐는지 체크(슬롯=6개)
    • 참여자가 이미 다른 회사와 같은 슬롯을 쓰는지 체크
  • 가능한 첫 슬롯(available_slot)을 찾으면, 거기에 배정
  • 배정 후엔 participant_busy[p][tslot]에 그 슬롯을 기록.

이 과정을 거치면, 우선순위가 높은 (참여자, 회사) 조합부터 차곡차곡 매칭이 이뤄집니다.


3-3-9) 결과 만들기 & 엑셀 저장

result_rows = []
for tslot, comp_dict in matches.items():
    for c, assigned_list in comp_dict.items():
        for (s, p) in assigned_list:
            result_rows.append({
                "시간대": tslot,
                "회사명": c,
                "서브슬롯(분)": s,
                "참여자": p
            })

result_df = pd.DataFrame(result_rows)
result_df = result_df.sort_values(by=["시간대","서브슬롯(분)"])

output_file = "매칭결과.xlsx"
result_df.to_excel(output_file, index=False)
print(f"매칭 결과가 '{output_file}'로 저장되었습니다.")
  1. matches 딕셔너리에 쌓인 정보를 순회하며,
    “시간대, 회사명, 서브슬롯, 참여자” 형태로 리스트 생성.
  2. pandas.DataFrame(...) 로 묶어주고, “시간대→서브슬롯(분)” 순으로 정렬.
  3. to_excel(...) 으로 저장.

이로써 최종 “시간대/회사/슬롯별 누가 면접 보나”매칭결과.xlsx 파일에 나타납니다.


4. 사용된 파이썬 함수

아래는 위 코드에서 사용된 함수들을 간단히 정리한 표입니다.

함수(메서드) 역할 및 설명
load_excel_file(title) tkinter로 “파일 열기” 대화창을 띄워서 사용자가 엑셀 파일을 선택하도록 함.
선택된 파일 경로를 리턴
pd.read_excel(...) pandas의 엑셀 파일 읽기 함수. DataFrame 형태로 만들어줌
DataFrame.dropna(...) 결측치(빈 값) 제거. how='all'이면 “해당 행의 모든 값이 NaN일 때” 제거
DataFrame.iterrows() DataFrame의 각 행(row)을 순회할 때 사용
string.strip() 문자열 양쪽 공백 제거
sort_values(...) pandas DataFrame을 특정 컬럼 기준으로 정렬
to_excel(...) pandas DataFrame을 엑셀 파일로 저장
tkinter.Tk(), destroy() tkinter 윈도우를 열고 닫는 메서드
filedialog.askopenfilename 파일 열기 대화창을 띄우는 함수

5. 실행

  1. 파이썬 환경에서(예: Anaconda Prompt, 혹은 Visual Studio Code 등), 이 스크립트(매칭데이.py 예시)를 실행합니다.
  2. 1) 참여자 희망기업 엑셀 파일을 선택 → 열어서 로딩
  3. 2) 회원사 희망참여자 엑셀 파일을 선택 → 열어서 로딩
  4. 자동으로 매칭 로직이 실행되어, 최종적으로 “매칭결과.xlsx” 파일이 생성됩니다.
    • 같은 폴더(또는 실행한 경로)에 저장됨
  5. 매칭결과.xlsx를 열어보면,
    시간대별로(예: 09:00-10:00), 회사명, 서브슬롯(분), 참여자 정보가 담겨 있음.

6. 결론 및 요약

  • 이 프로그램은 “참여자→회사, 회사→참여자” 각각 최대 5순위까지의 희망 정보를 읽어,
    서로 원하는 정도에 따라 점수를 매기고,
    시간과 슬롯(10분 단위)을 겹치지 않게 매칭시키는 자동화 툴입니다.
  • 핵심 구조:
    1) 우선순위 점수 합 계산
    2) 높은 점수부터 빈 슬롯을 배정
    3) 결과를 엑셀로 저장
반응형