[파이썬 Projects]/<파이썬 기초>

[파이썬] 파이썬 기초- 조 편성 프로그램 만들기

기록자_Recordian 2025. 2. 11. 23:19
728x90
반응형
도움되는 글
 

[파이썬] 파이썬 기초 - 랜덤 모듈

이전 내용 [파이썬] 파이썬 기초: 함수 (보완)파이썬 함수 (다른 글) [파이썬] 파이썬기초: 함수시작에 앞서해당 내용은 ' 박응용 지음. 이지스 퍼블리싱' 을 토대로 작성되었습니다. 보다 자세한

puppy-foot-it.tistory.com


수행 내용

 

학원에서 곧 있을 조별 과제를 앞두고 조 편성을 어떻게 하게 될지 궁금했다.

그런데, 교수님께서 조 편성을 하게 되면 학생들은 이런저런 이유를 대며 교수님의 결정을 받아들이려 하지 않을수도 있다고 생각했다. 그럴때 가장 좋은 게 가위바위보, 엎어라뒤집어라 (지방마다 다르다) 등등 여러 가지 방법들이 많이 있으나, 프로그래밍 언어를 배우는 입장에서 이 역시 프로그램으로 구현해 보면 좋을 거 같다는 생각이 들었다.

 

또한, 학교, 기관을 비롯한 많은 단체들에서 조 편성을 해야 하는 경우들이 많이 있는데, 프로그램이 랜덤으로 짜주게 되면 조 편성 관련해서 큰 스트레스가 사라지지 않을까 하는 마음에 실행 프로그램으로 제작하여 배포하고자 한다.

 

◆ 우선 필요한 내용 (변수)은

  • 전체 인원 수: 조 편성을 위해 전체 몇 명이 필요한 지를 알아야 한다. 그러나 이는 그때그때 달라질 수 있으므로, 입력값을 받도록 한다
  • 편성할 그룹 수: 인원에 따라 편성되는 그룹의 수가 달라질 수 있으므로, 이 역시 입력값을 받도록 한다.
  • 각 그룹에 속하게 되는 인원은 숫자로 표현 된다. 실생활에서는 제비뽑기나 사다리게임 처럼 번호를 미리 정하고나서 프로그램을 돌려서 본인 번호에 맞는 번호를 찾아가면 될 듯 하다.

1차 내용

 

기존에 작성했던 로또 프로그램 관련 글과 랜덤 모듈 관련 글을 참고하여 (파이썬 프로그래밍 배울 때만 해도 코드 다 안 보고 썼는데, 요즘 넘파이, 판다스, 머신러닝 배우면서 다시 원점으로 돌아간 듯 하다.... 코드 기계처럼 코드 주루루룩 쓰던 너 2주 만에 어딜 간거냐..)

from random import sample
import random

people= int(input("인원을 입력해 주세요 (숫자만): "))
groups = int(input("편성할 조 숫자를 입력해 주세요 (숫자만): "))
numbers = range(1, people) # 사람 번호
people_each_group = int(people/groups) # 한 그룹당 인원 수

if (people <= 0)| (groups <= 0):
    print("1 이상의 숫자를 입력해 주세요.")
else:
    for group in range(groups):
        project_group = sample(numbers, people_each_group)
        print(project_group)

 

각설하고, 코드를 대략적으로 보면

  • people 변수: 전체 인원을 나타내는 변수로써, 앞서 언급했듯 input 값을 받도록 했다. (해당 값이 정수형으로 되도록 int 함수 적용)
  • groups 변수: 편성할 그룹의 숫자를 나타내는 변수로써, people 변수와 비슷하다.
  • numbers 변수: 사람을 나타내는 변수로써, 생성되는 그룹 리스트 내에 담기게 된다.
  • people_each_group: 각 그룹 당 인원 수를 나타내는 변수로써, 전체 인원을 그룹 수로 나눈 값.
  • if 조건문: people 변수나 groups 변수가 0보다 작다면, 1 이상의 숫자를 입력해 달라고 하고, 둘 다 1 이상의 숫자가 입력되면 반복문이 실행된다.
  • for 반복문: 편성할 그룹의 수 만큼 반복문이 돌면서 project_group 이라는 변수 안에 numbers를 각 그룹 당 인원 수만큼 랜덤으로 뽑아내고, 각 그룹에 속하는 번호(numbers)를 출력하며 조편성이 마무리 된다.

이를 실행해보면

▶ 그런데, 문제가 있다. 3번과 9번 학생의 경우 1조에도 속하고, 3조에도 속하게 된다. 한 명이 두 개 이상의 그룹에 속하지 못하도록 수정을 해줘야 할 필요가 있다.


1차 수정
중복되지 않도록 하기

 

앞서 얘기한 한 사람이 두 개 이상의 그룹에 속하는 것을 방지하기 위한 코드로 수정했다.

from random import sample
import random

people= int(input("인원을 입력해 주세요 (숫자만): "))
groups = int(input("편성할 조 숫자를 입력해 주세요 (숫자만): "))
numbers = list(range(1, people + 1)) # 사람 번호 # 중복 제거를 위해 리스트로 생성
people_each_group = int(people/groups) # 한 그룹당 인원 수

if (people <= 0)| (groups <= 0):
    print("1 이상의 숫자를 입력해 주세요.")
else:
    for group in range(groups):
        project_group = sample(numbers, people_each_group)
        for person in project_group:
            numbers.remove(person) # 선택된 사람 번호 삭제
        print(f'{group+1}조: ', project_group)
    print('조 편성이 완료되었습니다.')

 

  • numbers 변수를 리스트 형태로 받도록 하였고, for 반복문을 중첩 반복문으로 바꿔서 numbers에서 선택된 사람은 제외되도록 numbers.remove 부분을 추가해줬다. (이 명령어는 조가 정해질 때마다, 선택된 사람 번호를 numbers 리스트에서 삭제한다.)
  • numbers 변수에서 range는 범위 생성 시 뒤의 숫자를 포함하지 않으므로, 1을 더하여 인원 수 만큼 범위가 생성되도록 했다.
  • 그리고 본인이 몇 조인지 알기 쉽도록 project_group 변수 print 문에 각 조가 표시되도록 했다.

다시 코드를 실행해보면,

 

만약에, 11명이거나 17명처럼 숫자가 소수라서 정확히 떨어지지 않을 경우에는 어떻게 해야 할까?

그 부분도 반영이 되도록 해줘야 할 거 같다. (개인적으로 필자는 이런 디벨롭 과정이 너무 재밌다.)


2차 수정
전체 인원과 그룹 수가 딱 떨어지지 않는 경우 보완하기

 

인원이 맞지 않는 상황을 대비하여 코드를 다시 수정하였다.

from random import sample
import random

people= int(input("인원을 입력해 주세요 (숫자만): "))
groups = int(input("편성할 조 숫자를 입력해 주세요 (숫자만): "))
numbers = list(range(1, people + 1)) # 사람 번호 # 중복 제거를 위해 리스트로 생성


if (people <= 0)| (groups <= 0): # 0 이하의 숫자를 입력했을 경우
    print("1 이상의 숫자를 입력해 주세요.")

else:
    people_each_group = int(people/groups) # 한 그룹당 인원 수
    extra_people = people % groups # 나머지 인원 수
    project_groups = []
    
    for group in range(groups):
        if group < extra_people: # 여분의 인원이 있는 그룹에 추가
            group_size  = people_each_group + 1
        else:
            group_size  = people_each_group
            
        project_group = sample(numbers, group_size)
        
        for person in project_group:
            numbers.remove(person) # 선택된 사람 번호 삭제
        
        project_groups.append(project_group)   
        print(f'{group+1}조: ', project_group)
        
    print('조 편성이 완료되었습니다.')

참고했던 내용

https://puppy-foot-it.tistory.com/645

https://velog.io/@cdspacenoob/python-%EC%A1%B0-%ED%8E%B8%EC%84%B1-%EC%BD%94%EB%93%9C-%EB%A7%8C%EB%93%A4%EA%B8%B0-em0w0smd

  • extra_people 변수는 people 을 groups로 나눈 후 나머지가 대입되도록 하였고,
  • project_groups 는 반복문이 실행되면서 리스트에 값이 추가되기 위해 빈 리스트 형태로 생성하였다.
  • 만약에 group 보다 extra_people이 큰 경우 (그룹 수와 인원이 맞지 않는 경우), 한 그룹 당 인원 수가 한 명 더 들어가도록 수정하고 이를 group_size 라는 변수에 대입되도록 하였다.
  • 수가 맞을 경우에는 group_size 변수에는 전체 인원이 각 그룹에 고루 들어가도록 하였다.
  • project_group 변수는 numbers에서 group_size의 수 만큼 랜덤으로 뽑고, 선택된 사람 번호는 삭제 되도록 remove 를 적용하였다.

이렇게 코드를 수정한 뒤 실행해보면

 

원하는 대로, 숫자가 맞지 않을 경우 남은 수만큼 조의 순서대로 대입되도록 하였다. (물론, 임의의 조에 대입되도록 할 수도 있지만, 그거는 추후에 여유가 되면 해보도록.)

 

오늘 학원에서 반장 선출 이벤트가 있었는데, 대부분 반장이 되는 것을 벌칙(!) 처럼 여겨서 내가 교수님께 우리가 프로그래밍을 배우니, 프로그래밍을 이용해서 한 명을 랜덤으로 뽑는 것은 어떠냐는 제의를 했다. (물론, 교수님께 반려 당하긴 했다.)

 

이렇게 만들어보고 나니, 이 프로그램에 조편성 뿐 아니라 이벤트(벌칙이든, 상품이든) 당첨자 발표 프로그램, 그리고 특정 인원 수 뽑기 + 등수 부여하기 (각 등 수에 따라 상품 또는 벌칙 차등화를 위함) 등의 기능도 다 넣어서 추첨 프로그램을 좀 더 빌드업 시켜보면 어떨까 하는 생각이 들었다.

 

앞서 배웠던 클래스 개념을 도입해서 좀 더 디벨롭 시켜보면 좋을 거 같다.


3차 수정
다른 추첨의 경우도 넣어서 추첨 프로그램을 다양화 시켜주기(feat. 클래스)

 

앞서 언급한 대로, 조편성 기능 뿐 아니라, 당첨자 추첨 및 당첨 순위 부여 기능이 추가된 프로그램을 만들었다.

Lot_program 클래스는 프로그램의 기능이 담겨 있으며, 부모 클래스이다.

Start 클래스는 Lot_program 클래스를 상속한 자식 클래스로, 기능을 선택할 수 있고, 앞서 Lot_program에 만들어둔 기능(함수)들이 잘 실행될 수 있도록 하는 역할을 하고 있다.

from random import sample
import random
import os

class Lot_program():
    def __init__(self): # 기본
        pass
        
    def group_formation(self): # 조 편성 프로그램램
        people= int(input("인원을 입력해 주세요 (숫자만): "))
        groups = int(input("편성할 조 숫자를 입력해 주세요 (숫자만): "))
        numbers = list(range(1, people + 1)) # 사람 번호 # 중복 제거를 위해 리스트로 생성


        if (people <= 0)| (groups <= 0): # 0 이하의 숫자를 입력했을 경우
            print("1 이상의 숫자를 입력해 주세요.")

        else:
            people_each_group = int(people/groups) # 한 그룹당 인원 수
            extra_people = people % groups # 나머지 인원 수
            project_groups = []
            
            for group in range(groups):
                if group < extra_people: # 여분의 인원이 있는 그룹에 추가
                    group_size  = people_each_group + 1
                else:
                    group_size  = people_each_group
                    
                project_group = sample(numbers, group_size)
                
                for person in project_group:
                    numbers.remove(person) # 선택된 사람 번호 삭제
                
                project_groups.append(project_group)   
                print(f'{group+1}조: ', project_group)
            
            print('-' * 25)    
            print('조 편성이 완료되었습니다.') 

    
    def lot_program(self): # 당첨 프로그램
        people= int(input("인원을 입력해 주세요 (숫자만): "))
        winners = int(input("당첨 인원을 입력해 주세요 (숫자만): "))
        numbers = list(range(1, people + 1)) # 사람 번호

        if (people <= 0)| (winners <= 0): # 0 이하의 숫자를 입력했을 경우
            print("1 이상의 숫자를 입력해 주세요.")
        else:
            winner_members = sample(numbers, winners)
            print("당첨을 축하합니다.")
            print('-' * 25)
            print("당첨 멤버: ", winner_members)

                
    def lot_with_rank(self): # 순위 당첨
        people= int(input("인원을 입력해 주세요 (숫자만): "))
        winners = int(input("당첨 인원을 입력해 주세요 (숫자만): "))
        numbers = list(range(1, people + 1)) # 사람 번호
        
        if (people <= 0)| (winners <= 0): # 0 이하의 숫자를 입력했을 경우
            print("1 이상의 숫자를 입력해 주세요.")

        else:
            for winner in range(winners):
                winner_member = sample(numbers, 1)
                for person in winner_member:
                    numbers.remove(person) # 중복 제거
                print(f"{winner+1}등: ", winner_member)
            print('-' * 25)
            print("추첨이 완료되었습니다.")
                
        
class Start(Lot_program):
    def display_menu(self):
         print("실행할 프로그램 선택")
         print('-' * 25)
         print("1. 조 편성 프로그램")
         print("2. 당첨 프로그램")
         print("3. 당첨 프로그램 - 순위 있음")
         print("4. 종료")
         menu = int(input("메뉴를 선택해 주세요: "))
         return menu   
    
    def main(self):
        while True:
            try:
                menu = self.display_menu()
                if menu == 1:
                    self.group_formation()
                if menu == 2:
                    self.lot_program()
                if menu == 3:
                    self.lot_with_rank()
                if menu == 4:
                    print("프로그램을 종료합니다.")
                    break
            except ValueError:
                print("잘못된 숫자를 입력하였습니다.")
            finally:
                print('-' * 20)
                print("프로그램을 이용해 주셔서 감사합니다.")
                    
    
# 인스턴스 생성
game_1 = Start()
game_1.main()

[참조한 내용]

https://puppy-foot-it.tistory.com/479

https://puppy-foot-it.tistory.com/659

https://puppy-foot-it.tistory.com/658

https://puppy-foot-it.tistory.com/660

 

이 코드를 실행해보면

잘 실행되는 것을 확인할 수 있다.

이제 이를 실행 프로그램 (exe 파일)로 만들어서 업로드하는 것만 남았다.


4차 수정
.py 확장자 파일 exe 파일로 변환하기

 

py 파일을 exe 파일로 만들기 위해서

  • cmd에 pip install pyinstaller 실행하여 pyinstaller 다운 받기
  • pytinstaller 설치 후, cd 명령어를 사용해서 해당 py 파일이 설치된 경로로 이동
  • py 파일을 exe 파일로 만들어주는 명령어 (하단) 입력하기
pyinstaller --onefile lot_program.py

 

Build complete! 라는 내용이 뜨면, exe 파일로 변환이 완료되었다는 뜻이며,

exe 파일이 설치된 폴더로 들어가보면

 

[build] [dist] 라는 두 개의 폴더가 생성되어있다.

 

◆ 그렇다면 build 폴더와 dist 폴더는 각각 어떤 역할을 할까?

1. build 폴더
- 역할
: build 폴더는 PyInstaller가 .exe 파일을 생성하는 중간 과정에서 필요한 임시 파일들이 저장되는 곳으로, 이 폴더에는 빌드 과정에서 사용된 설정 정보와 .exe 파일을 만드는 데 필요한 임시 파일들이 포함된다.

- 내용: 이 폴더에는 여러 빌드 관련 파일들이 들어있으며, 빌드 로그나 캐시 파일도 여기에 저장된다. 하지만 최종 실행 파일을 실행할 때는 이 폴더는 필요하지 않으며, 배포 시에도 무시해도 된다.

2. dist 폴더
- 역할: dist 폴더는 최종 실행 파일(.exe)을 포함하는 폴더로, 이 폴더 안에 변환된 .exe 파일이 위치하며, 이 파일이 실행 가능한 최종 파일이다.

- 내용: dist 폴더에는 변환된 실행 파일뿐만 아니라 그 파일이 실행되기 위해 필요한 다른 모든 파일들도 함께 포함될 수 있다. 예를 들어, --onefile 옵션을 사용하면 하나의 단일 .exe 파일로 모든 것이 압축되어 들어갑니다

 

이 중, dist 폴더에 들어가면

exe 프로그램이 있고, 이를 실행하면

 

켜지기는 하는데, 잘 실행이 될까?

다행히도, 실행이 잘 된다.

[4번] 종료를 누르면 프로그램이 종료된다.

 

[참고한 글]

https://puppy-foot-it.tistory.com/289


 파일 공유

lot_program.exe
8.14MB

728x90
반응형