
안녕하세요, 계략입니다.
지난번
https://steemit.com/kr-dev/@gyeryak/random-number-with-python
게시글에서 번호 추첨기를 만들었던 적이 있습니다.
여기에는 아주 큰 문제가 있습니다.
import random
def generate_random(endNum, numTot):
alreadyGenerated = set([])
numCount = 0
while numCount != numTot and numCount != endNum:
ranNum = random.randint(1,endNum)
if ranNum not in alreadyGenerated:
alreadyGenerated.add(ranNum)
numCount += 1
print("%d번째 생성된 수는 %d입니다." %(numCount, ranNum) )
generate_random(50,3)
이 프로그램이 동작하는 방법을 간단하게 설명해드릴게요

쉽게 얘기해서, 숫자를 생성하고 중복인지 확인합니다.
중복이면 다시 만들고, 아니라면 그대로 출력하는 것이죠.
여기서 문제가 발생합니다.
엄청난 확률로 계속 중복이 발생한다면?
망한 겁니다.
프로그램은 종료되지 않을 것입니다.
그래서, 이런 부분이 개선될 필요가 있었습니다.
그리하여 개선된 프로그램을 들고 왔습니다.
Python 3으로 작성된 프로그램입니다.
import random
lastNum = int(input())
numCount = int(input())
gen = list(range(1,lastNum+1))
while lastNum - len(gen) < numCount:
r = gen[random.randrange(0,len(gen))]
gen.remove(r)
print("생성된 수는 %d입니다." %(r))
이제는 조금 친절하게 설명드리겠습니다.
하나하나 살펴봅시다!

import random 는 간단하게 생각했을 때, 있어야만 하는 것(...)입니다.없으면 랜덤 수를 생성하는 방법이 매우 어려워지니, 간단하게 하자는 거죠.
~~ = int(input()) 는 정수를 입력받는 것입니다.input()는 의미 그대로 입력이고, int는 정수를 뜻하는 integer의 약자로, 받은 입력을 정수로 바꿔준다는 뜻이 되겠죠.
여기까지는 특별한 내용이 아닙니다.
저번의 프로그램이랑 어떤 면이 달라졌는지 봅시다.

gen = list(range(1,lastNum))은 아까 입력받은 마지막 번호까지 하나씩 들어가 있는 리스트를 만드는 것입니다.리스트는 사물함이라고 생각하시면 됩니다.
1번부터 10번까지의 사물함에, 1부터 10까지가 차례대로 들어가 있습니다.
그런 모양이 대충 그려지시죠?

저 부분은 나중에 다시 설명드릴게요.
while 아래 줄부터, 띄워진 부분들은 반복된다는 것입니다.

이렇게 대충 넘어가면 안 되겠죠. 설명 들어갑니다.
배열은 0번부터 시작합니다만, 이해를 돕기 위해 1번부터 들어가는 것을 가정을 합니다.

처음에는 이렇게 사물함이 다 그대로 있습니다.
자기 번호에, 그 숫자가 그대로 들어가 있습니다.

고런데, 여기서 선생님이 학생 한명을 뽑아서 부르듯
사물함 번호 하나가 뽑힙니다.
여기서는 6번이 뽑혔네요.

사물함 6번에 있던 번호는 6입니다.
remove는 없앤다는 뜻을 가지고 있어요.
그래서 6은 사라지게 되었습니다.
6이 사라진 뒤가 중요합니다.

왜인진 잘 몰라도, 사물함 번호가 작은게 좋나 봅니다.
앞에가 빠지면 우르르 몰려들어요.
그래서 7부터 10이, 한 칸씩 앞으로 왔습니다.
그리고 방 빠진 10번 사물함은 고독사합니다.

선생님은 똑똑하십니다.
결석자는 부르지 않아요.
그래서 10번 사물함이 불릴 일은 없습니다.
1번 사물함부터 9번 사물함 중 하나가 불릴 것입니다.
이번에는 7번이 뽑혔네요.
그런데 7번 사물함에는 8이 들어있습니다.

그러면 이번엔 8이 빠질 것입니다.
그리고 9와 10이 앞으로 올 것이고, 9번 사물함도 고독사하겠죠.
이런식으로 반복되면서,
한번 뽑힌 숫자는 사라집니다!
그리고 사물함 번호를 뽑는 것이기 때문에, 삽질할 우려도 없죠.
이렇게 앞서 만든 코드보다 개선된 코드가 완성되었습니다.
while lastNum-numCount-len(gen)-1이 부분은 상당히 복잡하게 적은 부분입니다.
그래도 간단하게 설명하자면, 고독사한 사물함의 개수가 뽑고자 하는 수의 개수만큼 되었을 때 까지 반복하라는 의미입니다.
어찌 되었든, 이렇게 해서 적절한 랜덤 번호 뽑기 프로그램이 완성되었습니다.
직접 실행해봅시다.
파이썬을 여기서 설치한다는 것은 귀찮으므로,
웹에서 바로 실행해봅시다.
http://ideone.com/cETEQe
이 곳으로 들어가셔서

왼쪽 위의 fork를 누르시고

enter input부분에
[참가자 수] [뽑을 인원] 을 적으시고

오른쪽 밑의 Run을 누르시면

이렇게 추첨이 완료됩니다.
이렇게 해서 코드를 간략하게 알아봤습니다.
아마... 어려우셨을 것 같습니다...
그래도,

위 캘리그라피를 만들어주신 @hellojun 님께 감사의 말씀 올립니다.
뉴비는 언제나 환영!/응원!이에요.
팁! : 고양이는 사랑입니다. 그렇게 생각하지 않으시나요?
2.0833333% 보팅
링크더리스트로 만들면 더 효율적이나요?
파이썬의 List에 대해서는
라고 하네요. 링크드 리스트가 아니라 배열에서 변형된 자료구조인 것 같습니다.
Remove 함수의 시간복잡도가 O(N), N은 리스트의 크기인걸 봐서는 배열이 확실한 것 같습니다.
만약 링크드 리스트로 작성한다고 하면
X번째의 값을 찾는데 O(N)의 시간이 걸릴 것이고
X번째 인덱스를 제거하는데 O(1)의 시간이 걸릴 것입니다.
결국에 시간복잡도는 비슷할 것 같네요.
별차이가 없네요.. 제 기억 중
학교에서 자료구조배울때 쉬운더라도 링크더리스트로 구성하는게 좋다들어서 질문드렸습다!
대단하십니다!!ㅎㅎ
과찬이십니다 ㅎ... 감사합니다!
이게 진짜 로또 방식이죠~ 왜 전 기존방식으로만해야한다고 생각했을까요~ 크레이티브 한수 배우고 갑니당
좋은 내용에 보팅과 팔로를
감사합니다!
while 조건문이 좀 어렵네요...
아래 처럼 하면 조금은 이해가 잘 될 지도 모르겠습니다만,
여전히 조건문은 어렵습니다.
range 에서 첫번째 인자가 1이니까 두번째 인자에 +1 해야 lastNum 갯수만큼의 리스트가 생깁니다.
gen = list(range(1,lastNum + 1)) while lastNum - len(gen) < numCount: r = gen[random.randrange(0,len(gen))] gen.remove(r) print("생성된 수는 %d입니다." %(r))부등식으로 한게 길이가 더 짧네요... 줄여보겠다고 했던 것이었는데...
그리고 range에 대해서는 수정했습니다. 오류가 있었네요. 감사합니다.
조건문이 이해하기 어렵다면,
하는 법이 있습니다만, 추천하고 싶진 않습니다.
코드를 좀 더 수정해서 이해하기 쉬운 조건문을 만들어 낼 수 있지 않을까 생각이 드네요...
으음... 조건문을 이해하기 쉽게 하려면 어떻게 해야 할까요?
잘 떠오르지가 않네요
뭔가 개선하고 싶으나 잘 안되는 경우,
뒤로 물러나서 코드를 지긋이 쳐다 봅니다. ^^
수정된 프로그램은 정확히 numCount 만큼만 루프를 돌게 되어있습니다.
for (int i = 0; i < numCount; i++) 이런 코드로 충분합니다.
파이선에서는
pickCount = 0 while pickCount < numCount: ... pickCount += 1저번 이벤트 글에서 지역변수를 줄이라는 말이 있어서 생각을 못했던 부분이네요.
확실히 그러면 반복문 자체는 이해하기 쉬워지겠군요
lastNum 이 numCount 보다 작은 경우, 에러가 발생하는군요...
딱 numCount 만큼 반복하게끔 하게 하더라도 발생합니다.
numCount보다 작을 때 오류가 나는건 알겠는데
numCount와 같을때는 왜 오류가 나는걸까요... 으음;
좀 더 알아봐야겠습니다...
숏코딩 안하시나요? ㅎㅎ
ㅠ.ㅠ 잘 안되네요... 다른 일도 하는 중이라서...