강의영상

- (1/5) 징검다리 문제 III 두번째 풀이 (1)

- (2/5) 징검다리 문제 III 두번째 풀이 (2)

- (3/5) 징검다리 문제 III 세번째 풀이 (1)

- (4/5) 징검다리 문제 III 세번째 풀이 (2)

- (5/5) 징검다리 문제 III 세번째 풀이 (3)

징검다리 문제 III.

강화유리와 유리를 구분할 수 있는 유리 장인이 있다.

이 유리장인은 80퍼센트의 확률로 강화유리를 고른다.

총 10명의 참가자가 있고 이 참가자들은 (유리,강화유리)의 조합으로 이루어진 징검다리를 5번연속으로 건너야 한다.

아래의 경우에 참가자들은 평균적으로 몇명이 살아남겠는가?

(1) 일반인1 - 일반인2 - .... - 일반인9 - 유리장인 || (강화유리, 유리)

(2) 유리장인 - 일반인1 - 일반인2 - ... - 일반인9 || (강화유리, 유리)

1000번 시뮬레이션을 하여 결과를 추정하라.

(단, 일반인은 50%의 확률로 강화유리를 고를수 있다고 하자)


[예시] (1)의 시뮬레이션 결과가 아래와 같다고 하자.

  • 첫번째 징검다리: 유리장인이 강화유리 선택
  • 두번째 징검다리: 유리장인이 강화유리 선택
  • 세번째 징검다리: 유리장인이 일반유리 선택 $\to$ 유리장인 탈락 & 일반인9는 당연히 강화유리를 선택
  • 네번째 징검다리: 일반인9가 일반유리 선택 $\to$ 일반인9 탈락 & 일반인8은 당연히 강화유리 선택
  • 다섯번째 징검다리: 일반인8이 강화유리 선택

이 경우는 일반인8,일반인7, $\dots$, 일반인1이 살아남으므로 8명이 살아남는다.

[예시] (2)의 시뮬레이션 결과가 아래와 같다고 하자.

  • 첫번째 징검다리: 일반인9 일반유리 선택 $\to$ 일반인9 탈락 & 일반인8은 강화유리 선택
  • 두번째 징검다리: 일반인8 일반유리 선택 $\to$ 일반인8 탈락 & 일반인7은 강화유리 선택
  • 세번째 징검다리: 일반인7 일반유리 선택 $\to$ 일반인7 탈락 & 일반인6은 강화유리 선택
  • 네번째 징검다리: 일반인6 일반유리 선택 $\to$ 일반인6 탈락 & 일반인5는 강화유리 선택
  • 다섯번째 징검다리: 일반인5 일반유리 선택 $\to$ 일반인5 탈락 & 일반인4는 강화유리 선택

이 경우는 일반인4,일반인3,일반인2,일반인1,유리장인 이 살아남는다. (따라서 5명)

- 즉 살아남을수 있는 최대인원수는 10명이며 최소인원수는 5명이다.

- 유리장인이 100%의 확률로 강화유리를 구분한다면 (1)의 경우 항상 10명이 살아남는다. (즉 평균도 10명)

풀이2

- 아래와 같은 수식을 세울 수 있다.

총 살아남은 사람의 수 = 5 + 장인의 성공횟수 + (5-장인의성공횟수-1)의 기회중에서 일반인의 성공횟수

  • 단 (5-장인의성공횟수-1)=<0 이면 마지막항은 0으로 계산한다.

- 장인의 성공횟수를 $x$, 일반인의 성공횟수를 $y$라고 하자. 그러면 구하는것은 $5+x+y$이다.

- 장인의 성공횟수

library(tidyverse)
x_ = rbinom(5,size=1,0.8)
x_
[1] 1 1 1 0 1
cumprod(x_)
[1] 1 1 1 0 0
x=sum(cumprod(x_))
print(x)
[1] 3

- 일반인의 성공횟수

xx_ = 5-x-1
print(xx_)
[1] 1
y = rbinom(1, size=xx_, 0.5)
y
[1] 1

- 살아남은 사람수는?

print(5+x+y)
[1] 9

- 정리하면

rslt <- c()
for (i in 1:10000){
    x=sum(cumprod(rbinom(5,size=1,0.8)))
    xx_=5-x-1 
    if (xx_>0) y = rbinom(1, size=xx_, 0.5) else y=0 
    rslt[i]=5+x+y
}
mean(rslt)
[1] 8.4895

풀이3

ARR = c('N1','N2','N3','N4','N5','N6','N7','N8','N9','A')
ARR
 [1] "N1" "N2" "N3" "N4" "N5" "N6" "N7" "N8" "N9" "A" 
SURV = 10
PLAYER = ARR[SURV] 
PLAYER
[1] "A"
STAGE = 0

- 첫시도

toss = function(p) rbinom(n=1,size=1,prob=p) %>% as.logical
PROB = 0.8 
TOSSRSLT = toss(PROB) 
if (TOSSRSLT==TRUE){
    SURV = SURV 
    STAGE = STAGE + 1 
    PLAYER = ARR[SURV]
}else{
    SURV = SURV - 1 
    STAGE = STAGE + 1 
    PLAYER = ARR[SURV]
}
print(TOSSRSLT)
print(SURV)
print(STAGE)
print(PLAYER)
[1] TRUE
[1] 10
[1] 1
[1] "A"

- 코드를 간단히 수정하자.

PROB = 0.8 
TOSSRSLT = toss(PROB) 
if (TOSSRSLT==FALSE) SURV = SURV - 1 
STAGE = STAGE + 1 
PLAYER = ARR[SURV]
print(TOSSRSLT)
print(SURV)
print(STAGE)
print(PLAYER)
[1] TRUE
[1] 10
[1] 2
[1] "A"

- 다시 처음부터 보고싶다면?

SURV = 10 
STAGE = 0 
PLAYER = ARR[SURV]
PROB = 0.8 
TOSSRSLT = toss(PROB) 
if (TOSSRSLT==FALSE) SURV = SURV - 1 
STAGE = STAGE + 1 
PLAYER = ARR[SURV]
print(TOSSRSLT)
print(SURV)
print(STAGE)
print(PLAYER)
[1] TRUE
[1] 10
[1] 1
[1] "A"

- reset을 하는 함수를 만들자.

reset = function(){
    SURV = 10
    STAGE = 0 
    PLAYER = ARR[SURV]
}
  • 제대로 동작하지 않음 $\to$ =<<-로 수정해보자.
reset = function(){
    SURV <<- 10
    STAGE <<- 0 
    PLAYER <<- ARR[SURV]
}
reset()
print(TOSSRSLT)
print(SURV)
print(STAGE)
print(PLAYER)
[1] TRUE
[1] 10
[1] 0
[1] "A"

- 출력함수의 기능을 묶어보자. $\to$ 출력(display)와 저장기능을 통합하여 record 함수 만듬

record = function() list(TOSSRSLT=TOSSRSLT, SURV=SURV, STAGE=STAGE, PLAYER=PLAYER)
record()$SURV %>% print
[1] 10

- reset + record 를 사용하고 결과를 살펴보면

reset()
record()
$TOSSRSLT
[1] TRUE

$SURV
[1] 10

$STAGE
[1] 0

$PLAYER
[1] "A"
  • TOSSRSLT의 의미가 헷갈릴수 있으니 약간 수정하자.
record = function() list(PRE_TOSSRSLT=TOSSRSLT, SURV=SURV, STAGE=STAGE, PLAYER=PLAYER)
reset = function(){
    TOSSRSLT <<- NA
    SURV <<- 10
    STAGE <<- 0 
    PLAYER <<- ARR[SURV]
}
reset()
record()
$PRE_TOSSRSLT
[1] NA

$SURV
[1] 10

$STAGE
[1] 0

$PLAYER
[1] "A"

- 지금까지 코드의 중간정리

ARR = c('N1','N2','N3','N4','N5','N6','N7','N8','N9','A')
SURV = 10 
PLAYER = ARR[SURV] 
STAGE = 0 
PROB = 0.8 
TOSSRSLT = NA
### 변수들의 모음
toss = function(p) rbinom(n=1,size=1,prob=p) %>% as.logical
reset = function(){
    TOSSRSLT <<- NA
    SURV <<- 10
    STAGE <<- 0 
    PLAYER <<- ARR[SURV]
}
record = function(){
    list(PRE_TOSSRSLT=TOSSRSLT, SURV=SURV, STAGE=STAGE, PLAYER=PLAYER) 
}
### 함수들의 모음
PROB = 0.8 
TOSSRSLT = toss(PROB) 
if (TOSSRSLT==FALSE) SURV = SURV - 1 
STAGE = STAGE + 1 
PLAYER = ARR[SURV]
### body

- PROB 가 항상 0.8인것은 아니며 사실 PLAYER=='A' 일 경우에만 0.8 이고 그외의 경우는 0.5 이다.

ARR = c('N1','N2','N3','N4','N5','N6','N7','N8','N9','A')
SURV = 10 
PLAYER = ARR[SURV] 
STAGE = 0 
PROB = 0.8 
TOSSRSLT = NA
### 변수들의 모음
toss = function(p) rbinom(n=1,size=1,prob=p) %>% as.logical
reset = function(){
    TOSSRSLT <<- NA
    SURV <<- 10
    STAGE <<- 0 
    PLAYER <<- ARR[SURV]
}
record = function(){
    list(PRE_TOSSRSLT=TOSSRSLT, SURV=SURV, STAGE=STAGE, PLAYER=PLAYER) 
}
### 함수들의 모음
PROB = 0.5+ (PLAYER=='A')*0.3 #### 이부분을 수정했음 
TOSSRSLT = toss(PROB) 
if (TOSSRSLT==FALSE) SURV = SURV - 1 
STAGE = STAGE + 1 
PLAYER = ARR[SURV]
### body

- Test 해보자.

reset() 
record()
$PRE_TOSSRSLT
[1] NA

$SURV
[1] 10

$STAGE
[1] 0

$PLAYER
[1] "A"
PROB = 0.5+ (PLAYER=='A')*0.3 #### 이부분을 수정했음 
TOSSRSLT = toss(PROB) 
if (TOSSRSLT==FALSE) SURV = SURV - 1 
STAGE = STAGE + 1 
PLAYER = ARR[SURV]
record()
$PRE_TOSSRSLT
[1] TRUE

$SURV
[1] 10

$STAGE
[1] 1

$PLAYER
[1] "A"

- 스테이지를 진행하는 부분을 묶어서 함수로 처리하자.

ARR = c('N1','N2','N3','N4','N5','N6','N7','N8','N9','A')
SURV = 10 
PLAYER = ARR[SURV] 
STAGE = 0 
PROB = 0.8 
TOSSRSLT = NA
### 변수들의 모음
toss = function(p) rbinom(n=1,size=1,prob=p) %>% as.logical
reset = function(){
    TOSSRSLT <<- NA
    SURV <<- 10
    STAGE <<- 0 
    PLAYER <<- ARR[SURV]
}
record = function(){
    list(PRE_TOSSRSLT=TOSSRSLT, SURV=SURV, STAGE=STAGE, PLAYER=PLAYER) 
}
go = function(){
    PROB <<- 0.5+ (PLAYER=='A')*0.3
    TOSSRSLT <<- toss(PROB) 
    if (TOSSRSLT==FALSE) SURV <<- SURV - 1 
    STAGE <<- STAGE + 1 
    PLAYER <<- ARR[SURV]    
}    
### 함수들의 모음
### body

- test

reset()
record()
$PRE_TOSSRSLT
[1] NA

$SURV
[1] 10

$STAGE
[1] 0

$PLAYER
[1] "A"
go()
record()
$PRE_TOSSRSLT
[1] FALSE

$SURV
[1] 9

$STAGE
[1] 1

$PLAYER
[1] "N9"

- 어차피 5번 시행을 하므로 go이외에 gogo함수를 따로 만들자.

ARR = c('N1','N2','N3','N4','N5','N6','N7','N8','N9','A')
SURV = 10 
PLAYER = ARR[SURV] 
STAGE = 0 
PROB = 0.8 
TOSSRSLT = NA
### 변수들의 모음
toss = function(p) rbinom(n=1,size=1,prob=p) %>% as.logical
reset = function(){
    TOSSRSLT <<- NA
    SURV <<- 10
    STAGE <<- 0 
    PLAYER <<- ARR[SURV]
}
record = function(){
    list(PRE_TOSSRSLT=TOSSRSLT, SURV=SURV, STAGE=STAGE, PLAYER=PLAYER) 
}
go = function(){
    PROB <<- 0.5+ (PLAYER=='A')*0.3
    TOSSRSLT <<- toss(PROB) 
    if (TOSSRSLT==FALSE) SURV <<- SURV - 1 
    STAGE <<- STAGE + 1 
    PLAYER <<- ARR[SURV]    
}
gogo = function() for(i in 1:5) go()
### 함수들의 모음

- test

reset()
record()
$PRE_TOSSRSLT
[1] NA

$SURV
[1] 10

$STAGE
[1] 0

$PLAYER
[1] "A"
gogo()
record()
$PRE_TOSSRSLT
[1] TRUE

$SURV
[1] 9

$STAGE
[1] 5

$PLAYER
[1] "N9"

- 히스토리를 볼 수 있는 gogo함수를 만들어보자.

gogo_history = function(){
    rslt_ = as_tibble(record()) ## go를 시작하기전의 결과가 티블로 만들어짐 
    for(i in 1:5){
        go()
        rslt_ = rbind(rslt_, as_tibble(record())) 
    }
    print(rslt_)
}
reset()
record()
$PRE_TOSSRSLT
[1] NA

$SURV
[1] 10

$STAGE
[1] 0

$PLAYER
[1] "A"
gogo_history()
# A tibble: 6 × 4
  PRE_TOSSRSLT  SURV STAGE PLAYER
  <lgl>        <dbl> <dbl> <chr> 
1 NA              10     0 A     
2 FALSE            9     1 N9    
3 TRUE             9     2 N9    
4 TRUE             9     3 N9    
5 FALSE            8     4 N8    
6 TRUE             8     5 N8    

- 이제 simulate_once 라는 함수를 만들어서 시뮬레이션 결과를 저장하자.

simulate_once = function(){
    reset()
    gogo()
    return(record()$SURV )
}
simulate_once() %>% print
[1] 10

- 따라서 답은

simrslt = c() 
for (i in 1:100000) simrslt[i] = simulate_once() 
mean(simrslt) %>% print
[1] 8.51736

- 코드를 최종적으로 정리하면

ARR = c('N1','N2','N3','N4','N5','N6','N7','N8','N9','A')
SURV = 10 
PLAYER = ARR[SURV] 
STAGE = 0 
PROB = 0.8 
TOSSRSLT = NA
### 변수들의 모음
toss = function(p) rbinom(n=1,size=1,prob=p) %>% as.logical
reset = function(){
    TOSSRSLT <<- NA
    SURV <<- 10
    STAGE <<- 0 
    PLAYER <<- ARR[SURV]
}
record = function(){
    list(PRE_TOSSRSLT=TOSSRSLT, SURV=SURV, STAGE=STAGE, PLAYER=PLAYER) 
}
go = function(){
    PROB <<- 0.5+ (PLAYER=='A')*0.3
    TOSSRSLT <<- toss(PROB) 
    if (TOSSRSLT==FALSE) SURV <<- SURV - 1 
    STAGE <<- STAGE + 1 
    PLAYER <<- ARR[SURV]    
}
gogo = function() for(i in 1:5) go()

gogo_history = function(){
    rslt_ = as_tibble(record()) ## go를 시작하기전의 결과가 티블로 만들어짐 
    for(i in 1:5){
        go()
        rslt_ = rbind(rslt_, as_tibble(record())) 
    }
    print(rslt_)
}

simulate_once = function(){
    reset()
    gogo()
    return(record()$SURV )
}
### 함수들의 모음
simrslt = c() 
for (i in 1:100000) simrslt[i] = simulate_once() 
mean(simrslt) 
### body
[1] 8.50571

이러한 방식의 코딩스타일

- 장점: 함수의 입력/출력을 상당부분 생략가능, 함수의 출력을 어딘가에 저장하는 번거로운 과정도 스킵가능, 구현이 쉬움

- 단점: 지정한 변수들과 함수의 목록을 한번에 파악하기 힘듬. 하나의 노트북에서 다른 풀이도 함께하다보면 변수이름들이 매우 헷갈릴수 있음.

- 단점을 극복하기 위한 대안 (1) 하나의 노트북을 사용한다. (2) 변수나 함수들을 모두 초기화한다. (커널초기화) (3) 이 모든것을 하나의 함수로 묶는다.

- (2) 변수나 함수들을 모두 초기화하는 방법

ls() ## 현재 R에 저장된 변수목록을 출력
 [1] "ARR"           "go"            "gogo"          "gogo_history" 
 [5] "i"             "PLAYER"        "PROB"          "record"       
 [9] "reset"         "rslt"          "simrslt"       "simulate_once"
[13] "STAGE"         "SURV"          "toss"          "TOSSRSLT"     
[17] "x"             "x_"            "xx_"           "y"            
rm(list=ls()) # 현재 R환경에 있는 함수+변수를 삭제
ls() ## 현재 R에 저장된 변수목록을 출력
character(0)
  • 이제 R에는 더이상 함수나 변수가 없다.

- (3) 이 모든것을 하나의 함수로 묶는방법

rm(list=ls())
ls()
character(0)
  • 일단 R환경은 깨끗한 상태임
SIMULATE = function(){
### 변수들의 모음
ARR = c('N1','N2','N3','N4','N5','N6','N7','N8','N9','A')
SURV = 10 
PLAYER = ARR[SURV] 
STAGE = 0 
PROB = 0.8 
TOSSRSLT = NA

### Subfunction들의 모음 
toss = function(p) rbinom(n=1,size=1,prob=p) %>% as.logical
reset = function(){
    TOSSRSLT <<- NA
    SURV <<- 10
    STAGE <<- 0 
    PLAYER <<- ARR[SURV]
}
record = function(){
    list(PRE_TOSSRSLT=TOSSRSLT, SURV=SURV, STAGE=STAGE, PLAYER=PLAYER) 
}
go = function(){
    PROB <<- 0.5+ (PLAYER=='A')*0.3
    TOSSRSLT <<- toss(PROB) 
    if (TOSSRSLT==FALSE) SURV <<- SURV - 1 
    STAGE <<- STAGE + 1 
    PLAYER <<- ARR[SURV]    
}
gogo = function() for(i in 1:5) go()

gogo_history = function(){
    rslt_ = as_tibble(record()) ## go를 시작하기전의 결과가 티블로 만들어짐 
    for(i in 1:5){
        go()
        rslt_ = rbind(rslt_, as_tibble(record())) 
    }
    print(rslt_)
}

simulate_once = function(){
    reset()
    gogo()
    return(record()$SURV )
}

### BODY
simrslt = c() 
for (i in 1:100000) simrslt[i] = simulate_once() 
mean(simrslt)
}
ls()
[1] "SIMULATE"
  • 현재는 SIMULATE 하나만 저장되어있음
SIMULATE()
[1] 8.5075
ls()
[1] "SIMULATE"
  • 실행한이후에도 SIMULATE 하나만 저장되어 있음