세 명의 총잡이

강현구·2021년 12월 26일

잡담

목록 보기
1/1

휴일을 지낸 후라 그런지 공부도 안 잡히던 와중에 같이 공부하고 있던 친구가 재미있는 소재를 던져줘서 가지고 놀아봤다.
세 명의 총잡이라는 확률론적으로 접근하는 이론이다.
(통계학과 출신인지 이런 테마에 대해 )

세 명의 총잡이?

나무위키 개요
게임 이론 예시 중 하나며 총잡이 이론이라고도 불린다. 아래에 서술된 문제들 이외에도 다양하게 조건을 바꿔서 생각해 볼 수 있다. 활을 쓰는 판본도 있다. 하술할 문제 1의 충격적인 결론 때문에 죄수의 딜레마와 더불어 가장 대중적이고 널리 알려진 이론이기도 하다.

문제 :
세 명의 총잡이가 서로 결투를 벌인다.

미스터 블랙은 백발백중, 명중률 100%의 사격 실력을 가지고 있다.
미스터 그레이는 명중률 70%의 사격 실력을 갖고 있다.
미스터 화이트는 명중률 30%다.

세 명의 총잡이들은 서로의 사격 실력을 잘 알고 있다. 그렇기에 이들은 서로의 실력차를 감안해서 '화이트→ 그레이→ 블랙' 순서대로 발포하기로 하며 (죽지 않으면 또 쏠 수 있겠지만) 한 번에 한 발만 쏠 수 있다.

이 때 화이트는 어떻게 쏴야 가장 생존율이 높은가

정리하면, 서로 다른 명중률을 가진 세사람이 순서대로 총을 쐈을 때의 생존율을 구하는 문제이다.
직관적으로 봤을 때는 100%의 명중률을 가진 총잡이가 제일 잘 살아남을 것 같다! 라고 생각이 들지만 나무위키에 있는 해설처럼 조금 다른 결과를 얻을 수 있다.
실제 문제는 위와 같지만, 코드로 편하게 구현하기 위해 조금 문제를 변형해서 다음과 같이 해보았다.

python으로 시뮬레이션 구현

내가 푼 방법
서로 다른 명중률을 가진 세 사람 (A:30%, B:70%, C:100%) 이 있다.
이 이들의 실력을 고려하여, 명중률이 제일 낮은 사람이 먼저 총을 쏘고 제일 높은 사람이 마지막에 쏜다.
각 사람은 일부러 안 쏘거나 빗맞추는 일 없이 오로지 확률에 의해서만 명중여부가 갈린다.
사격은 한사람이 남을 때까지 반복해서 진행한다고 했을 때, 각 사람의 생존 확율을 구하라.

이렇게 구하면 구해지는 확률값은 원래 문제와는 살짝 다른 값이 나오겠지만, 방향성은 비슷하게 나온다.

code

한번에 전체 flow를 한번에 담은 코드를 작성할 수도 있지만, 기능적인 부분을 분리하여 함수로 구분,연결 해보았다.
1. 각 사람마다 주어진 명중률에 따른 적중여부 함수

from random import choice

def get_probable(probability):
    hit_probability = [True]*probability+[False]*(100-probability)
    hit = choice(hit_probability)
    return hit

python의 내장모듈인 random 모듈의 choice 메서드로 무작위 값을 추출해 내는 것으로 확률에 맞는 결과값을 출력해보았다.(100분율 값 기준)
문제를 제시했던 친구는 주어진 명중률을 기준으로 random 메서드로 0~1 사이의 값을 출력하고 이것이 명중률보다 큰지 작은지에 대한 판별로 적중여부를 출력해냈다.(1 이하의 소수값 출력)

  1. 순서대로 발사하며 최종 생존자를 산출
def shot_arrow(archers):
    while len(archers) > 1:
        for archer in archers:
            if get_probable(archer):
                if len(archers) == 3:
                    if archer == max(archers):
                        archers.pop(1)
                    else:
                        archers.pop()
                else:
                    if archer == min(archers):
                        archers.pop(1)
                    else:
                        archers.pop(0)
    return archers[0]

음... 제일 마음에 안드는 부분이다.
참가자들을 리스트에 넣고 명중률로 sorting한 뒤 낮은 값을 가진 사람부터 순서대로 쏘도록 만들었다.
1.에서 만든 적중여부 함수로 참가자중 한명을 쏘게 되고 적중하면 맞은사람은 리스트에서 제외된다.
if문 안의 if문을 쓰고 싶지 않았는데, 구현할만한 방법이 떠오르지 않아 직접적으로 조건분기를 걸어주었다.
(가독성, 확장성이 좋지 않다고 생각한다.)
코드를 좀 더 간결하고 확장성 있게 구현하는 방법을 더 생각해 봐야한다.

  1. input값을 주고 최종적으로 생존자를 출력하는 함수
def get_surviving_person(a,b,c):
    archers = [a,b,c]
    archers.sort()
    survivor = shot_arrow(archers)
    return survivor

아까는 잘 만들었다고 생각했는데,,,
지금보니 이 함수가 굳이 필요했나 싶다.
이미 2.함수에서 생존자를 출력해주고 있기 때문에 중복해서 돌리는 꼴이 되었다.
물론 여기서 다음의 n번 반복하는 구문을 이 함수 안으로 넣어서 구현하는 방법이 있겠다.(아까는 생각 못 했던 내용...)
4. 위 사이클의 반복 진행을 통한 통계적인 확률 산출

a = 100
b = 70
c = 30
member = {a: 0, b: 0, c: 0}
for i in range(10000):
    winner = get_surviving_person(a,b,c)
    member[winner] += 1

total = sum(member.values())
for i in member.keys():
    print(f"archer of {i} : {round(member[i]/total*100,1)}%")

그래서 지금까지 구현했던 함수들을 n번 반복하여 각각의 참가자들이 얼만큼의 확률로 살아남을 수 있는지에 대한 계산 부분이다.
3., 4.를 묶어서 하나도 표현해주는 것이 더 합리적이라고 생각한다.
어차피 3.은 크게 의미없어진 함수일 뿐만아니라 결국 4.에서 이어져서 계산하는데 이 부분이 우리가 필요했던 부분이다.

어쩃든,,, 이렇게 해서 n회 만큼 진행해서 각 참가자의 생존확률을 구하면,

  • a(100% 명중) : 14.7%
  • b(70% 명중) : 57.0%
  • c(30% 명중) : 28.3%

위와 같이 나온다. 물론 계산치와는 살~~짝 다를 수 있지만 표본을 10000이상 했기 때문에 계산치에 근사한 값을 얻을 수 있다.
결과를 보면 알 수 있듯이 오히려 100%의 명중률이 생존율이 제일 낮게 나온다!!
왜냐면, 100%명중률을 가진 사람은 자기가 총을 쏘기도 전에 30%, 70%인 사람들의 표적이 되버리기 때문.
자기보다 명중률이 낮은 사람의 명중률이 얼마나 되냐에 따라 자신의 생존율에 종속성이 생긴다.
위 코드에서 a,b,c를 조절해서 다양한 조건을 만들 수 있었다.

중간중간 투박한 코드가 많아 수정이 많이 필요해보인다.
또한 원래의 문제에 가깝게 구현하는 방법도 생각해볼 필요가 있을 것으로 보인다.

참고
세명의 총잡이_나무위키
블로그 포스팅
관련 자료_나무위키

profile
한걸음씩

0개의 댓글