CHAPTER18 _남은정
library(modelr)
library(tidyverse)
options(na.action=na.warn)
sim1
ggplot(sim1,aes(x,y))+ geom_point()
models <- tibble(
a1 = runif(250, -20, 40),
a2 = runif(250, -5, 5)
)
ggplot(sim1, aes(x, y)) + geom_point() + geom_abline(aes(intercept=a1,slope=a2),data=models,alpha=1/4)
model1 <- function(a, data) { a[1] + data$x * a[2] } # input: beta, data / output: predicted y
model1(c(7, 1.5), sim1) # predicted y
measure_distance <- function(mod, data) {
diff <- data$y - model1(mod, data) # y-predicted y : distance
sqrt(mean(diff ^ 2))
}
measure_distance(c(7, 1.5), sim1)
sim1_dist <- function(a1, a2) { # input : beta1, beta2
measure_distance(c(a1, a2), sim1) # output: distance
}
models <- models %>%
mutate(dist = purrr::map2_dbl(a1, a2, sim1_dist)) # dist 변수 생성
models
ggplot(sim1, aes(x,y))+ geom_point(size=2,color="grey30")+
geom_abline(aes(intercept=a1,slope=a2,color=-dist),data=filter(models,rank(dist)<=10))
# dist : dist가 작을수록 색이 어두움
# -dist : dist가 작을수록 색이 밝음
ggplot(models, aes(a1, a2)) +
geom_point(data = filter(models, rank(dist) <= 10), size = 4, colour = "red") +
geom_point(aes(colour = -dist))
grid <- expand.grid( # expand.grid : vector or factor 조합으로 data frame 생성
a1 = seq(-5, 20, length = 25),
a2 = seq(1, 3, length = 25)
) %>%
mutate(dist = purrr::map2_dbl(a1, a2, sim1_dist))
grid %>%
ggplot(aes(a1, a2)) +
geom_point(data = filter(grid, rank(dist) <= 10), size = 4, colour = "red") +
geom_point(aes(colour = -dist))
ggplot(sim1, aes(x, y)) +
geom_point(size = 2, colour = "grey30") +
geom_abline(
aes(intercept = a1, slope = a2, colour = -dist),
data = filter(grid, rank(dist) <= 10)
)
(best <- optim(c(0, 0), measure_distance, data = sim1))
best$par
ggplot(sim1, aes(x, y)) +
geom_point(size = 2, colour = "grey30") +
geom_abline(intercept = best$par[1], slope = best$par[2])
(sim1_mod <- lm(y ~ x, data = sim1))
coef(sim1_mod) # optim 결과와 거의 일치
## Visualizing Models
sim1
(grid <-sim1 %>%
data_grid(x)) # sim1에는 20개의 obs 존재. data_grid(x) : unique한 x를 출력, 10개 출력
sim1_mod
(grid<-grid %>%
add_predictions(sim1_mod)) # lm model의 predicted y 생성
ggplot(sim1, aes(x)) +
geom_point(aes(y = y)) +
geom_line(aes(y = pred), data = grid, colour = "red", size = 1)
(sim1 <- sim1 %>%
add_residuals(sim1_mod))
ggplot(sim1, aes(resid)) +
geom_freqpoly(binwidth = 0.5)
ggplot(sim1, aes(x, resid)) +
geom_ref_line(h = 0) +
geom_point()
df <- tribble(
~y, ~x1, ~x2,
4, 2, 5,
5, 1, 6
)
model_matrix(df, y ~ x1) # Model Matrix 생성 (X)
model_matrix(df, y ~ x1 - 1) # intercept (1) 제외한 Matrix
model_matrix(df, y ~ x1 + x2)
(df <- tribble(
~ sex, ~ response,
"male", 1,
"female", 2,
"male", 1
))
model_matrix(df, response ~ sex) # Categorical variable은 1,0으로 표시
ggplot(sim2) +
geom_point(aes(x, y))
mod2 <- lm(y ~ x, data = sim2)
grid <- sim2 %>%
data_grid(x) %>%
add_predictions(mod2)
grid
ggplot(sim2, aes(x)) +
geom_point(aes(y = y)) +
geom_point(data = grid, aes(y = pred), colour = "red", size = 4)
grid
mod2
tibble(x = "e") %>%
add_predictions(mod2) # obs에 없는 level "e"에 대해서는 predict 불가능
sim3
ggplot(sim3, aes(x1, y)) +
geom_point(aes(colour = x2))
(mod1 <- lm(y ~ x1 + x2, data = sim3))
(mod2 <- lm(y ~ x1 * x2, data = sim3)) # x1 * x2 : interaction까지 포함
grid <- sim3 %>% # 120 rowS
data_grid(x1,x2) %>% # 40 rows (unique한 x1, x2)
gather_predictions(mod1,mod2) # 80 rows (mod1에 대해서 40개, mod2에 대해서 40개의 predictions)
grid
ggplot(sim3, aes(x1,y,color=x2)) + geom_point() + geom_line(data=grid, aes(y=pred))+ facet_wrap(~model)
sim3 <- sim3 %>%
gather_residuals(mod1, mod2) # mod1, mod2 각각에 대해 residual 생성
ggplot(sim3, aes(x1, resid, colour = x2)) +
geom_point() +
facet_grid(model ~ x2)
mod1 <- lm(y ~ x1 + x2, data = sim4)
mod2 <- lm(y ~ x1 * x2, data = sim4)
grid <- sim4 %>%
data_grid(
x1 = seq_range(x1, 5),
x2 = seq_range(x2, 5)
) %>%
gather_predictions(mod1, mod2)
grid
seq_range(c(0.0123, 0.923423), n = 5)
seq_range(c(0.0123, 0.923423), n = 5, pretty = TRUE) # pretty : 반올림해서 보기 좋게 해줌 (table생성시 유용)
x1 <- rcauchy(100)
seq_range(x1, n = 5) # seq_range :min-max 구간을 5등분한 값
seq_range(x1, n = 5, trim = 0.10) # trim: head, tail 각 5%를 제외한 후 시행
seq_range(x1, n = 5, trim = 0.25)
seq_range(x1, n = 5, trim = 0.50)
x2 <- c(0, 1)
seq_range(x2, n = 5)
seq_range(x2, n = 5, expand = 0.10) # expand : 양 끝을 5%씩 확장함
seq_range(x2, n = 5, expand = 0.25)
seq_range(x2, n = 5, expand = 0.50)
ggplot(grid, aes(x1, x2)) +
geom_tile(aes(fill = pred)) +
facet_wrap(~ model)
ggplot(grid, aes(x1, pred, colour = x2, group = x2)) +
geom_line() +
facet_wrap(~ model)
ggplot(grid, aes(x2, pred, colour = x1, group = x1)) +
geom_line() +
facet_wrap(~ model)
## Transformation
df <- tribble(
~y, ~x,
1, 1,
2, 2,
3, 3
)
df
model_matrix(df, y ~ x^2 + x)
model_matrix(df, y ~ I(x^2) + x) # x^2 항 추가
model_matrix(df, y ~ poly(x, 2))
library(splines)
model_matrix(df, y ~ ns(x, 2)) # Generate the B-spline basis matrix for a natural cubic spline
sim5 <- tibble(
x = seq(0, 3.5 * pi, length = 50),
y = 4 * sin(x) + rnorm(length(x))
)
ggplot(sim5, aes(x, y)) +
geom_point()
mod1 <- lm(y ~ ns(x, 1), data = sim5)
mod2 <- lm(y ~ ns(x, 2), data = sim5)
mod3 <- lm(y ~ ns(x, 3), data = sim5)
mod4 <- lm(y ~ ns(x, 4), data = sim5)
mod5 <- lm(y ~ ns(x, 5), data = sim5)
grid <- sim5 %>%
data_grid(x = seq_range(x, n = 50, expand = 0.1)) %>%
gather_predictions(mod1, mod2, mod3, mod4, mod5, .pred = "y")
grid # model 5개 각각에 대해서 x의 seq_range 값에 대해 predictions 생성
ggplot(sim5, aes(x, y)) +
geom_point() +
geom_line(data = grid, colour = "red") +
facet_wrap(~ model)
df <- tribble(
~x, ~y,
1, 2.2,
2, NA,
3, 3.5,
4, 8.3,
NA, 10
)
mod <- lm(y ~ x, data = df)
mod <- lm(y ~ x, data = df, na.action = na.exclude)
nobs(mod) # number of observations (NA 제외하고 3개)
CHAPTER19_문지수
##############19. Model Building############
library(tidyverse)
library(modelr)
options(na.action = na.warn)
library(nycflights13)
library(lubridate)
## Why are low quantity diamonds more expensive?
ggplot(diamonds, aes(cut, price)) + geom_boxplot()
ggplot(diamonds, aes(color, price)) + geom_boxplot() #J색일수록 색이 좋지 않음
ggplot(diamonds, aes(clarity, price)) + geom_boxplot() #l1일수록 선명도가 좋지 않음
## Price and carat
#무게(carat)가 가격을 결정하는 중요한 요인이며, 낮은 품질의 다이아몬드가 무거운 경향이 있음.
install.packages("hexbin")
library(hexbin)
ggplot(diamonds, aes(carat, price)) +
geom_hex(bins = 50) #bins: 수직, 수평방향으로 bins의 개수
# carat의 영향을 분리하는 모델을 적합시켜봄으로써
#다이아몬드의 다른 요소들이 상대적인 가격에 어떻게 영향을 미치는지 살펴볼 수 있음.
##데이터 작업 2가지를 먼저 하고 분석을 하겠다.->diamonds2
#Focus on diamonds smaller than 2.5 carats (99.7% of the data)
#Log-transform the carat and price variables. -> 선형관계로 만들 수 있음.
diamonds2 <- diamonds %>%
filter(carat <= 2.5) %>%
mutate(lprice = log2(price), lcarat = log2(carat))
ggplot(diamonds2, aes(lcarat, lprice)) +
geom_hex(bins = 50)
mod_diamond <- lm(lprice ~ lcarat, data = diamonds2)
diamonds2 %>%
data_grid(carat = seq_range(carat, 20))
diamonds2 %>%
data_grid(carat = seq_range(carat, 20)) %>%
mutate(lcarat = log2(carat))
diamonds2 %>%
data_grid(carat = seq_range(carat, 20)) %>%
mutate(lcarat = log2(carat)) %>%
add_predictions(mod_diamond, "lprice")
grid <- diamonds2 %>%
data_grid(carat = seq_range(carat, 20)) %>%
mutate(lcarat = log2(carat)) %>%
add_predictions(mod_diamond, "lprice") %>%
mutate(price = 2 ^ lprice)
ggplot(diamonds2, aes(carat, price)) +
geom_hex(bins = 50) +
geom_line(data = grid, colour = "red", size = 1)
#적합시킨 모형에 따르면, 무거운 다이아몬드는 예상보다 비싸지 않다는 것을 볼 수 있음.
#(이 데이터셋에는 19000달러보다 비싼 다이아몬드는 없기 때문)
diamonds2 <- diamonds2 %>%
add_residuals(mod_diamond, "lresid")
ggplot(diamonds2, aes(lcarat, lresid)) +
geom_hex(bins = 50)
#잔차가 특별한 패턴을 보이지 않아 적절함을 알 수 있음.
##이제 가격(price)대신에 잔차를 사용하여 플롯을 다시 그려보자
ggplot(diamonds2, aes(cut, lresid)) + geom_boxplot()
ggplot(diamonds2, aes(color, lresid)) + geom_boxplot()
ggplot(diamonds2, aes(clarity, lresid)) + geom_boxplot()
#이를 통해 다이아몬드의 품질이 좋을수록 상대적인 가격도 높아짐을 볼 수 있음.
#잔차가 -1인 경우, lprice가 다이아몬드 무게에 근거한 예측치보다 1단위 낮다는 의미임.
## A more complicated model
mod_diamond2 <- lm(lprice ~ lcarat + color + cut + clarity, data = diamonds2)
diamonds2 %>%
data_grid(cut, .model = mod_diamond2)
grid <- diamonds2 %>%
data_grid(cut, .model = mod_diamond2) %>%
add_predictions(mod_diamond2)
grid
ggplot(grid, aes(cut, pred)) +
geom_point()
diamonds2 <- diamonds2 %>%
add_residuals(mod_diamond2, "lresid2")
ggplot(diamonds2, aes(lcarat, lresid2)) +
geom_hex(bins = 50)
#몇몇의 다이아몬드에서 잔차가 크게 나타나고 있음을 볼 수 있음.
diamonds2 %>%
filter(abs(lresid2) > 1) %>%
add_predictions(mod_diamond2) %>%
mutate(pred = round(2 ^ pred)) %>%
select(price, pred, carat:table, x:z) %>%
arrange(price)
## Exerciese
#4
diamonds2 %>%
add_predictions(mod_diamond2) %>%
add_residuals(mod_diamond2) %>%
summarise(sq_err = sqrt(mean(resid^2)),
abs_err = mean(abs(resid)),
p975_err = quantile(resid, 0.975),
p025_err = quantile(resid, 0.025))
## What affects the number of daily flights?
daily <- flights %>%
mutate(date = make_date(year, month, day)) %>%
group_by(date) %>%
summarise(n = n())
daily ##the number of flights per day
ggplot(daily, aes(date, n)) +
geom_line()
## Day of week
daily <- daily %>%
mutate(wday = wday(date, label = TRUE))
ggplot(daily, aes(wday, n)) +
geom_boxplot()
#주중보다 주말에 비행이 적은 경향을 보임.
mod <- lm(n ~ wday, data = daily)
grid <- daily %>%
data_grid(wday) %>%
add_predictions(mod, "n")
ggplot(daily, aes(wday, n)) +
geom_boxplot() +
geom_point(data = grid, colour = "red", size = 4)
daily <- daily %>%
add_residuals(mod)
daily %>%
ggplot(aes(date, resid)) +
geom_ref_line(h = 0) +
geom_line()
#위의 모형(mod)에 요일 효과를 넣었기 때문에, 이제 위의 플롯(y축이 잔차)을 볼 때 요일과 관련된 영향은 제외된 것임.
#플롯을 보면 6월부터 이상함(잔차가 크게 나타남).
#또한 6월 이후로 규칙적인 패턴이 보임. -> 우리는 이러한 패턴을 모형(mod)에 넣지 못한것임.
ggplot(daily, aes(date, resid, colour = wday)) +
geom_ref_line(h = 0) +
geom_line()
#일요일을 보면 우리 모형이 잘 예측하지 못함을 볼 수 있음.
#여기서 주로 여름에는 예측보다 비행이 많고, 가을에는 예측보다 비행이 적게 나타남.
# Exercises
#3
daily <- daily %>%
mutate(wday2 =
case_when(.$wday == "Sat" & .$term == "summer" ~ "Sat-summer",
.$wday == "Sat" & .$ term == "fall" ~ "Sat-fall",
.$wday == "Sat" & .$term == "spring" ~ "Sat-spring",
TRUE ~ as.character(.$wday)))
mod4 <- lm(n ~ wday2, data = daily)
daily %>%
gather_residuals(sat_term = mod4, all_interact = mod2) %>%
ggplot(aes(date, resid, colour = model)) +
geom_line(alpha = 0.75)
#4
daily <- daily %>%
mutate(wday3 =
case_when(
.$date %in% lubridate::ymd(c(20130101, # new years
20130121, # mlk
20130218, # presidents
20130527, # memorial
20130704, # independence
20130902, # labor
20131028, # columbus
20131111, # veterans
20131128, # thanksgiving
20131225)) ~
"holiday",
.$wday == "Sat" & .$term == "summer" ~ "Sat-summer",
.$wday == "Sat" & .$ term == "fall" ~ "Sat-fall",
.$wday == "Sat" & .$term == "spring" ~ "Sat-spring",
TRUE ~ as.character(.$wday)))
mod5 <- lm(n ~ wday3, data = daily)
daily %>%
spread_residuals(mod5) %>%
arrange(desc(abs(resid))) %>%
slice(1:20) %>% select(date, wday, resid)
#7
flights %>%
mutate(date = make_date(year, month, day),
wday = wday(date, label = TRUE)) %>%
group_by(wday) %>%
summarise(dist_mean = mean(distance),
dist_median = median(distance)) %>%
ggplot(aes(y = dist_mean, x = wday)) +
geom_point()
#8
monday_first <- function(x) {
forcats::fct_relevel(x, levels(x)[-1])
}
aily <- daily %>%
mutate(wday = wday(date, label = TRUE))
ggplot(daily, aes(monday_first(wday), n)) +
geom_boxplot() +
labs(x = "Day of Week", y = "Number of flights")

powerful ideas
- 많은 수의 간단한 모델은 복잡한 데이터셋을 더 잘 이해하게 해준다.
- list-columns를 이용하여 데이터를 구조화시킨다.
- broom 패키지를 이용해서 tidy data를 만들고, 이로 모델링을 한다.
library(modelr)
library(tidyverse)
library(gapminder)
we will use the “gapminder”data.
gapminder
Q. [“How does life expectancy(lifeExp) change over time(year) for each country(country)?”]
: 3가지 변수에 초점을 맞추어 분석하고자 함.
gapminder %>%
ggplot(aes(year, lifeExp, group = country)) +
geom_line(alpha = 1/3)

1700개의 관측치와 3개의 변수로 plotting해 본 결과, 많은 수가 아님에도 데이터의 흐름을 파악하기가 쉽지 않다. 전반적으로 증가하는 양상을 볼 수는 있으나, 몇 개의 국가들은 이를 따르지 않는 다는 것을 볼 수 있다. 따라서 “strong signal”을 사용해서 트랜드를 분석해보자. 선형 회귀를 적합시킨 것과 남은 residual에 대해 plotting을 해보자.
nz <- filter(gapminder, country == "New Zealand")
nz %>%
ggplot(aes(year, lifeExp)) +
geom_line() +
ggtitle("Full data = ")

nz_mod <- lm(lifeExp ~ year, data = nz)
nz %>%
add_predictions(nz_mod) %>%
ggplot(aes(year, pred)) +
geom_line() +
ggtitle("Linear trend + ")

nz %>%
add_residuals(nz_mod) %>%
ggplot(aes(year, resid)) +
geom_hline(yintercept = 0, colour = "white", size = 3) +
geom_line() +
ggtitle("Remaining pattern")

new zealand 말고도, 모든 나라에 대해 쉽게 fitting 시키려면 어떻게 해야 할까? 방금과 같은 코드를 나라만 바꿔가며 복사 붙여넣기를 하는 방법도 있겠지만, 더 효율적인 방법이 있다.
Nested Data
데이터셋을 나라별로 뽑아서 반복을 하면 되는데, 이를 위해 자료의 구조를 새롭게 정의해야 한다. 이를 nested data frame이라고 한다. 이를 위한 코드는 다음과 같다.
by_country <- gapminder %>%
group_by(country, continent) %>%
nest()
by_country
변형된 자료를 파악하기 위해서, 하나의 원소를 골라서 어떤 정보를 어떻게 담고 있는지를 확인해보자. (Afghanistan을 고른 예시)
by_country$data[[1]]
위에서 처럼, nested data frame에서는 각 열 자체가 그룹임을 확인 할 수 있다. 이제, 이 nested data frame으로 모델을 fitting 시켜 보는 함수를 만들어보자.
country_model <- function(df) {
lm(lifeExp ~ year, data = df)
}
이 함수와 데이터를 이제, purrr패키지의 map 함수를 이용해서 연결시켜보자.
library(purrr)
models <- map(by_country$data, country_model)
그리고, 이 결과를 다른 새로운 object로 만들어서 global environment에 저장하는 것이 아니라, by_country라는 데이터 프레임에 새로운 변수를 만들어서 저장하는 방식을 택하는 것이 더 낫다. 이를 위해 dplyr패키지의 mutate 함수를 이용한다.
by_country <- by_country %>%
mutate(model = map(data, country_model))
by_country
이렇게 작업을 하면, 모든 관련 개체가 함께 저장되므로 필터링하거나 정렬 할 때 수동으로 동기화 할 필요가 없다는 강력한 장점이 있다. 만약 이렇게 하지 않고, 별개의 객체로 저장을 해놓으면, 벡터를 재정의 하거나 벡터를 subset시킬때 하위집합을 계속 동기화해주어야 한다. 이를 잊어버린다면, 코드는 돌아는 가겠지만, 잘못된 답을 출력해주게 된다.
Unnesting
142개의 나라가 있으므로, 우리는 142개의 데이터프레임과 142개의 모델을 갖고 있다. 잔차를 계산하기 위해서, add_residuals()을 이용한다.
by_country <- by_country %>%
mutate(
resids = map2(data, model, add_residuals)
)
by_country
Q. How can you plot a list of data frames?
–> nested된 데이터 프레임을 unnest()를 이용하여 정규 데이터 프레임 형식으로 바꾼 후 plot을 그린다. 그럼 다음과 같이 나라별로 묶여있던 데이터프레임이 풀리게 됨을 확인할 수 있다.
resids <- unnest(by_country, resids)
resids
이제, 잔차도를 그릴 수 있게 되었다.
resids %>%
ggplot(aes(year, resid)) +
geom_line(aes(group = country), alpha = 1 / 3) +
geom_smooth(se = FALSE)

대륙별로 보면, 대륙마다의 특별한 특징이 더 드러나게 된다.
resids %>%
ggplot(aes(year, resid, group = country)) +
geom_line(alpha = 1 / 3) +
facet_wrap(~continent)

잔차그림의 모양을 보니, fitting 시킨 모형이 적절하지 않았다고 판단할 수 있다. 특히 africa에서 큰 잔차들을 볼 수 있다. 다음 섹션에서 이를 다른 방향으로 접근하여 분석해보자.
Model Quality
모델로부터 잔차를 구하여 보는 것보다, model quality에 대한 보편적인 측정치를 보는 방법에 대해 생각해보자. broom 패키지로, 모델을 tidy데이터로 만들 수 있다. 또한, broom 패키지의 glance()함수를 써서 모형 평가 지표를 데이터 프레임 형태로 뽑아낼 수 있다.
broom::glance(nz_mod)
여기서 구한 이 정보를 mutate()와 unnest()를 이용해서 각 나라별의 행으로 다시 넣은 데이터 프레임을 만들 수 있다.
by_country %>%
mutate(glance = map(model, broom::glance)) %>%
unnest(glance)
그러나 이렇게, 모든 list 열을 포함하는 것은 우리가 원하던 결과가 아니므로, unnest에서 .drop=TRUE로 다시 옵션을 설정하여 바꿔준다.
glance <- by_country %>%
mutate(glance = map(model, broom::glance)) %>%
unnest(glance, .drop = TRUE)
glance
이렇게 얻어진 데이터프레임으로 드디어, 잘 맞지 않는 모델이 무엇인지 찾을 수 있게 되었다. 모형 평가 지표중의 하나인 r square값에 대해 오름차순으로 sorting을 해보자.
glance %>%
arrange(r.squared)
그 결과, Africa에서 모델이 제일 안맞음을 확인 할 수 있었다. 이를 다시 재확인해보기 위해 그림을 그려보려 한다. 관측수가 적고, 이산형 변수임으로, 지터그림을 보는 것이 효과적이다.
glance %>%
ggplot(aes(continent, r.squared)) +
geom_jitter(width = 0.5)

우리는 다음과 같이 낮은 R-square값을 갖고 있는 국가들만을 가지고 다시 그림을 그려볼 수 있다.
bad_fit <- filter(glance, r.squared < 0.25)
gapminder %>%
semi_join(bad_fit, by = "country") %>%
ggplot(aes(year, lifeExp, colour = country)) +
geom_line()

우리는 여기서 HIV / AIDS 전염병과 르완다 대학살, 이 두가지 비극의 효과를 그림으로 확인 할 수 있었다. (동영상 내용 참고)

=============================================================================================
List-Columns
지금까지 많은 모델을 다루는 것에 대한 전반적인 흐름을 보았다. 지금부터는 list-column 데이터 구조에 대해 더 자세히 살펴 볼 것이다. 데이터 프레임은 동일한 길이 벡터가 모여 명명되고, list-column은 데이터프레임의 정의안에 내포되어 있는 개념이다. list는 벡터이므로, list를 데이터 프레임의 열로 추가하는 것은 괜찮다. 그러나, data.frame()에서는 list를 열의 list로 다루므로, 기존의 R에서는 이러한 작업이 쉽지 않았다. 예를 통해 살펴보자.
data.frame(x = list(1:3, 3:5))
이렇게 하는 것은 우리가 원했던 결과가 아니므로, 이때는 I()를 사용하여 아래와 같이 나타내야 한다. 그러나 완전히 잘 출력해준다고는 볼 수 없다.
data.frame(
x = I(list(1:3, 3:5)),
y = c("1, 2", "3, 4, 5")
)
tibble(
x = list(1:3, 3:5),
y = c("1, 2", "3, 4, 5")
)
tibble은 입력값을 복사하는 개념이 아니므로, 더 나은 출력 방법을 제공하여 위의 예시들에서 발생한 문제들을 해결한다.
tribble(
~x, ~y,
1:3, "1, 2",
3:5, "3, 4, 5"
)
이렇게 하면 내가 필요로 하는 리스트를 더 쉽게 자동적으로 tibble을 이용하여 만들 수 있다. 이렇게, list-columns는 즉각적인 데이터 구조로 활용되기에 유용하다. 대부분의 R함수들은 atomic 벡터와 데이터 프레임을 이용해서 함수가 짜여져 있으므로, tibble을 이용해서 바로 작업을 수행하기는 어려울 수 있지만, 관련있는 항목들을 연결하여 쓸 수 있다는 장점이 있으므로 그만한 가치가 있다고 본다.
Creating List-Columns
통상적으로, tibble()을 이용해서 list-columns를 바로 만들지는 않는다. 대신에, regular columns를 이용해서 만드는데, 다음 세가지 방법들 중의 하나로 한다. - (1) tidyr::nest() 로 그룹화된 데이터 프레임을 nested 데이터 프레임으로 바꾼다. - (2) mutate() 와 벡터화 시키는 함수를 이용해 list를 반환한다. - (3) summerize() 와 요약해주는 함수를 통해 여러 결과를 출력한다. 대체적으로, tibble::enframe()을 사용하면, 명명된 리스트로 부터 만들어 낼 수도 있다. 일반적으로, list-columns를 만들때는, 그들이 homogeneous한 것을 확실하게 해야한다. 즉, 같은 요소를 담고 있어야 한다는 것이다. purrr에서 type-stable 함수를 배웠으므로, 이를 기억하고 자연스럽게 작업을 할 수 있을 것이다.
(1) With Nesting
nest()는 리스트 열을 갖는 nested 데이터 프레임을 만들어 준다. nest()를 쓰는 방법에는 두 가지가 있는데, 지금까지는 그룹화된 데이터 프레임에서 사용해왔었다. 이 때 nest()는 그룹화된 열을 그 그대로 유지시켜주면서 모든 것을 다발로 그 리스트 열 안에 담아 주었었다. 다음 예시를 확인해보면 이해가 될 것이다.
gapminder %>%
group_by(country, continent) %>%
nest()
우리는 이것을 그룹화되지 않았던 데이터 프레임에도 사용할 수 있고, 중첩 할 열을 특별히 지정할 수도 있다.
gapminder %>%
nest(year:gdpPercap)
(2) From Vectorized Functions
atomic 벡터를 리스트로 바꿔주는 유용한 함수들에 대해서 배웠었다(11장 참고). mutate를 사용하면, list-column을 만들 수 있다. 다음 예시를 확인해보자.
df <- tribble(
~x1,
"a,b,c",
"d,e,f,g"
)
df %>%
mutate(x2 = stringr::str_split(x1, ","))
이를 unnest()를 사용하면, x2에 할당된 리스트를 벡터로 바꿀 수 있게 된다.
df %>%
mutate(x2 = stringr::str_split(x1, ",")) %>%
unnest()
이러한 유형은 purrr 패키지의 map(), map2(), pmap()등을 사용하여 비슷한 작업을 할 수 있다.
(3) From Multivalued Summeries
summerize()의 제약조건은, 한 값에 대해서만 출력을 해주는 함수라는 것이다. 쉽게 말해, 우리는 이것을 임의의 길이를 가진 벡터 즉, quantile()같은 함수에는 사용을 할 수 없다는 것이다. 때문에 다음 코드에서 에러가 나타난다.
mtcars %>%
group_by(cyl) %>%
summarise(q = quantile(mpg))
Error in summarise_impl(.data, dots) : expecting a single value
에러가 나지 않게 하려면 아래처럼 결과를 list로 묶어서 돌리면 돌아가게 된다. 왜냐하면 summary는 길이가 1이 된 리스트에 적용되기 때문이다.
mtcars %>%
group_by(cyl) %>%
summarise(q = list(quantile(mpg)))
이 결과를 unnest()를 이용하여 다시 풀어주면, 우리가 원하던 결과와 확률값을 얻게 된다.
probs <- c(0.01, 0.25, 0.5, 0.75, 0.99)
mtcars %>%
group_by(cyl) %>%
summarise(p = list(probs), q = list(quantile(mpg, probs))) %>%
unnest()
(4) From a Named List
list열을 갖고 있는 데이터 프레임은 “목록의 내용과 요소를 모두 반복하려는 경우 어떻게 해야 하나?”라는 흔한 문제에 대한 해결책을 제시한다. 모든 것을 하나의 객체로 묶는 대신 데이터 프레임을 만드는 것이 더 쉽다. 한 열에는 element가 포함될 수 있고 또 한 열에는 list가 포함될 수 있다. list로부터 그러한 데이터 프레임을 만드는 쉬운 방법은 tibble :: enframe ()를 쓰면 된다. 다음 예시를 보자.
x <- list(
a = 1:5,
b = 3:4,
c = 5:6
)
df <- enframe(x)
df
이러한 구조의 장점은 직설적인 방식으로 일반화된다는 것이다. name은 문자형 변수가 meta data로 있으면 유용하지만, 다른 유형의 데이터나 여러개의 벡터가 이름에 들어가있으면 유용하지는 않은 표현방식이다. 어쨌든, 이제 이름과 값을 함께 반복하려면, map2()를 사용하면 된다.
df %>%
mutate(
smry = map2_chr(name, value, ~ stringr::str_c(.x, ": ", .y[1]))
)
List-Columns를 간단히 하기
- (1). 리스트를 벡터화 시키기 : 단일 값을 원할 경우 map_lgl (), map_int (), map_dbl () 및 map_chr ()에 mutate ()를 사용하여 원자 벡터를 만든다.
df <- tribble(
~x,
letters[1:5],
1:3,
runif(5)
)
df %>% mutate(
type = map_chr(x, typeof),
length = map_int(x, length)
)
- (2). Unnesting : 많은 값을 원하면 unnest ()를 사용하여 목록 열을 일반 열로 다시 변환하고 필요한만큼 행을 반복한다. 예시1
tibble(x = 1:2, y = list(1:4, 1)) %>% unnest(y)
예시2
df1 <- tribble(
~x, ~y, ~z,
1, c("a", "b"), 1:2,
2, "c", 3
)
df1
df1 %>% unnest(y, z)
Broom을 이용해 Tidy data 만들기
- broom::glance(model)
- broom::tidy(model)
- broom::augment(model, data)
Broom은 가장 인기인는 모델링 패키지들을 이용해 다양한 모델들로 작동한다. 최근에 제공해주는 모델들에 대한 정보는 다음의 깃허브를 확인해보자. https://github.com/tidyverse/broom
LS0tDQp0aXRsZTogIlBBUlQgSVYgTW9kZWwiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCiAgaHRtbF9kb2N1bWVudDogZGVmYXVsdA0KICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQNCi0tLQ0KDQojI0NIQVBURVIxOCBf64Ko7J2A7KCVDQpgYGB7cn0NCmxpYnJhcnkobW9kZWxyKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpvcHRpb25zKG5hLmFjdGlvbj1uYS53YXJuKQ0Kc2ltMQ0KZ2dwbG90KHNpbTEsYWVzKHgseSkpKyBnZW9tX3BvaW50KCkNCg0KbW9kZWxzIDwtIHRpYmJsZSgNCiAgYTEgPSBydW5pZigyNTAsIC0yMCwgNDApLA0KICBhMiA9IHJ1bmlmKDI1MCwgLTUsIDUpDQopDQpnZ3Bsb3Qoc2ltMSwgYWVzKHgsIHkpKSArIGdlb21fcG9pbnQoKSArIGdlb21fYWJsaW5lKGFlcyhpbnRlcmNlcHQ9YTEsc2xvcGU9YTIpLGRhdGE9bW9kZWxzLGFscGhhPTEvNCkNCm1vZGVsMSA8LSBmdW5jdGlvbihhLCBkYXRhKSB7IGFbMV0gKyBkYXRhJHggKiBhWzJdIH0gICMgaW5wdXQ6IGJldGEsIGRhdGEgLyBvdXRwdXQ6IHByZWRpY3RlZCB5DQptb2RlbDEoYyg3LCAxLjUpLCBzaW0xKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIHByZWRpY3RlZCB5IA0KbWVhc3VyZV9kaXN0YW5jZSA8LSBmdW5jdGlvbihtb2QsIGRhdGEpIHsNCiAgZGlmZiA8LSBkYXRhJHkgLSBtb2RlbDEobW9kLCBkYXRhKSAgICAgICMgeS1wcmVkaWN0ZWQgeSA6IGRpc3RhbmNlDQogIHNxcnQobWVhbihkaWZmIF4gMikpDQp9DQptZWFzdXJlX2Rpc3RhbmNlKGMoNywgMS41KSwgc2ltMSkNCnNpbTFfZGlzdCA8LSBmdW5jdGlvbihhMSwgYTIpIHsgICAgICAgICAjIGlucHV0IDogYmV0YTEsIGJldGEyDQogIG1lYXN1cmVfZGlzdGFuY2UoYyhhMSwgYTIpLCBzaW0xKSAgICAgIyBvdXRwdXQ6IGRpc3RhbmNlIA0KfQ0KbW9kZWxzIDwtIG1vZGVscyAlPiUgDQogIG11dGF0ZShkaXN0ID0gcHVycnI6Om1hcDJfZGJsKGExLCBhMiwgc2ltMV9kaXN0KSkgICAjIGRpc3Qg67OA7IiYIOyDneyEsSANCm1vZGVscw0KZ2dwbG90KHNpbTEsIGFlcyh4LHkpKSsgZ2VvbV9wb2ludChzaXplPTIsY29sb3I9ImdyZXkzMCIpKyANCiAgZ2VvbV9hYmxpbmUoYWVzKGludGVyY2VwdD1hMSxzbG9wZT1hMixjb2xvcj0tZGlzdCksZGF0YT1maWx0ZXIobW9kZWxzLHJhbmsoZGlzdCk8PTEwKSkNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjICBkaXN0IDogZGlzdOqwgCDsnpHsnYTsiJjroZ0g7IOJ7J20IOyWtOuRkOybgA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgLWRpc3QgOiBkaXN06rCAIOyekeydhOyImOuhnSDsg4nsnbQg67Cd7J2MDQpnZ3Bsb3QobW9kZWxzLCBhZXMoYTEsIGEyKSkgKw0KICBnZW9tX3BvaW50KGRhdGEgPSBmaWx0ZXIobW9kZWxzLCByYW5rKGRpc3QpIDw9IDEwKSwgc2l6ZSA9IDQsIGNvbG91ciA9ICJyZWQiKSArDQogIGdlb21fcG9pbnQoYWVzKGNvbG91ciA9IC1kaXN0KSkNCmdyaWQgPC0gZXhwYW5kLmdyaWQoCQkJCSAjIGV4cGFuZC5ncmlkIDogdmVjdG9yIG9yIGZhY3RvciDsobDtlansnLzroZwgZGF0YSBmcmFtZSDsg53shLENCiAgYTEgPSBzZXEoLTUsIDIwLCBsZW5ndGggPSAyNSksDQogIGEyID0gc2VxKDEsIDMsIGxlbmd0aCA9IDI1KQ0KICApICU+JSANCiAgbXV0YXRlKGRpc3QgPSBwdXJycjo6bWFwMl9kYmwoYTEsIGEyLCBzaW0xX2Rpc3QpKQ0KZ3JpZCAlPiUgDQogIGdncGxvdChhZXMoYTEsIGEyKSkgKw0KICBnZW9tX3BvaW50KGRhdGEgPSBmaWx0ZXIoZ3JpZCwgcmFuayhkaXN0KSA8PSAxMCksIHNpemUgPSA0LCBjb2xvdXIgPSAicmVkIikgKw0KICBnZW9tX3BvaW50KGFlcyhjb2xvdXIgPSAtZGlzdCkpIA0KZ2dwbG90KHNpbTEsIGFlcyh4LCB5KSkgKyANCiAgZ2VvbV9wb2ludChzaXplID0gMiwgY29sb3VyID0gImdyZXkzMCIpICsgDQogIGdlb21fYWJsaW5lKA0KICAgIGFlcyhpbnRlcmNlcHQgPSBhMSwgc2xvcGUgPSBhMiwgY29sb3VyID0gLWRpc3QpLCANCiAgICBkYXRhID0gZmlsdGVyKGdyaWQsIHJhbmsoZGlzdCkgPD0gMTApDQogICkNCihiZXN0IDwtIG9wdGltKGMoMCwgMCksIG1lYXN1cmVfZGlzdGFuY2UsIGRhdGEgPSBzaW0xKSkNCmJlc3QkcGFyDQpnZ3Bsb3Qoc2ltMSwgYWVzKHgsIHkpKSArIA0KICBnZW9tX3BvaW50KHNpemUgPSAyLCBjb2xvdXIgPSAiZ3JleTMwIikgKyANCiAgZ2VvbV9hYmxpbmUoaW50ZXJjZXB0ID0gYmVzdCRwYXJbMV0sIHNsb3BlID0gYmVzdCRwYXJbMl0pDQooc2ltMV9tb2QgPC0gbG0oeSB+IHgsIGRhdGEgPSBzaW0xKSkNCmNvZWYoc2ltMV9tb2QpCQkJCQkjIG9wdGltIOqysOqzvOyZgCDqsbDsnZgg7J287LmYDQojIyBWaXN1YWxpemluZyBNb2RlbHMNCnNpbTEgDQooZ3JpZCA8LXNpbTEgJT4lDQogZGF0YV9ncmlkKHgpKQkJIyBzaW0x7JeQ64qUIDIw6rCc7J2YIG9icyDsobTsnqwuIGRhdGFfZ3JpZCh4KSA6IHVuaXF1Ze2VnCB466W8IOy2nOugpSwgMTDqsJwg7Lac66ClDQpzaW0xX21vZA0KKGdyaWQ8LWdyaWQgJT4lDQogYWRkX3ByZWRpY3Rpb25zKHNpbTFfbW9kKSkJCSMgbG0gbW9kZWzsnZggcHJlZGljdGVkIHkg7IOd7ISxDQpnZ3Bsb3Qoc2ltMSwgYWVzKHgpKSArDQogIGdlb21fcG9pbnQoYWVzKHkgPSB5KSkgKw0KICBnZW9tX2xpbmUoYWVzKHkgPSBwcmVkKSwgZGF0YSA9IGdyaWQsIGNvbG91ciA9ICJyZWQiLCBzaXplID0gMSkNCihzaW0xIDwtIHNpbTEgJT4lIA0KICBhZGRfcmVzaWR1YWxzKHNpbTFfbW9kKSkNCmdncGxvdChzaW0xLCBhZXMocmVzaWQpKSArIA0KICBnZW9tX2ZyZXFwb2x5KGJpbndpZHRoID0gMC41KQ0KZ2dwbG90KHNpbTEsIGFlcyh4LCByZXNpZCkpICsgDQogIGdlb21fcmVmX2xpbmUoaCA9IDApICsNCiAgZ2VvbV9wb2ludCgpIA0KZGYgPC0gdHJpYmJsZSgNCiAgfnksIH54MSwgfngyLA0KICA0LCAyLCA1LA0KICA1LCAxLCA2DQopDQptb2RlbF9tYXRyaXgoZGYsIHkgfiB4MSkJCSMgTW9kZWwgTWF0cml4IOyDneyEsSAoWCkgDQptb2RlbF9tYXRyaXgoZGYsIHkgfiB4MSAtIDEpCQkjIGludGVyY2VwdCAoMSkg7KCc7Jm47ZWcIE1hdHJpeA0KbW9kZWxfbWF0cml4KGRmLCB5IH4geDEgKyB4MikNCihkZiA8LSB0cmliYmxlKA0KICB+IHNleCwgfiByZXNwb25zZSwNCiAgIm1hbGUiLCAxLA0KICAiZmVtYWxlIiwgMiwNCiAgIm1hbGUiLCAxDQopKQ0KbW9kZWxfbWF0cml4KGRmLCByZXNwb25zZSB+IHNleCkJIyBDYXRlZ29yaWNhbCB2YXJpYWJsZeydgCAxLDDsnLzroZwg7ZGc7IucDQpnZ3Bsb3Qoc2ltMikgKyANCiAgZ2VvbV9wb2ludChhZXMoeCwgeSkpDQptb2QyIDwtIGxtKHkgfiB4LCBkYXRhID0gc2ltMikNCmdyaWQgPC0gc2ltMiAlPiUgDQogIGRhdGFfZ3JpZCh4KSAlPiUgDQogIGFkZF9wcmVkaWN0aW9ucyhtb2QyKQ0KZ3JpZA0KZ2dwbG90KHNpbTIsIGFlcyh4KSkgKyANCiAgZ2VvbV9wb2ludChhZXMoeSA9IHkpKSArDQogIGdlb21fcG9pbnQoZGF0YSA9IGdyaWQsIGFlcyh5ID0gcHJlZCksIGNvbG91ciA9ICJyZWQiLCBzaXplID0gNCkNCmdyaWQNCm1vZDINCnRpYmJsZSh4ID0gImUiKSAlPiUgDQogIGFkZF9wcmVkaWN0aW9ucyhtb2QyKQkJCSMgb2Jz7JeQIOyXhuuKlCBsZXZlbCAiZSLsl5Ag64yA7ZW07ISc64qUIHByZWRpY3Qg67aI6rCA64qlDQpzaW0zDQpnZ3Bsb3Qoc2ltMywgYWVzKHgxLCB5KSkgKyANCiAgZ2VvbV9wb2ludChhZXMoY29sb3VyID0geDIpKQ0KKG1vZDEgPC0gbG0oeSB+IHgxICsgeDIsIGRhdGEgPSBzaW0zKSkNCihtb2QyIDwtIGxtKHkgfiB4MSAqIHgyLCBkYXRhID0gc2ltMykpCSMgeDEgKiB4MiA6IGludGVyYWN0aW9u6rmM7KeAIO2PrO2VqA0KZ3JpZCA8LSBzaW0zICU+JQkJCQkJCSMgMTIwIHJvd1MNCiAgICAgICAgICBkYXRhX2dyaWQoeDEseDIpICU+JQkJCSMgNDAgIHJvd3MgICh1bmlxdWXtlZwgeDEsIHgyKQ0KICAJCWdhdGhlcl9wcmVkaWN0aW9ucyhtb2QxLG1vZDIpCQkjIDgwICByb3dzICAobW9kMeyXkCDrjIDtlbTshJwgNDDqsJwsIG1vZDLsl5Ag64yA7ZW07IScIDQw6rCc7J2YIHByZWRpY3Rpb25zKQ0KZ3JpZA0KZ2dwbG90KHNpbTMsIGFlcyh4MSx5LGNvbG9yPXgyKSkgKyBnZW9tX3BvaW50KCkgKyBnZW9tX2xpbmUoZGF0YT1ncmlkLCBhZXMoeT1wcmVkKSkrIGZhY2V0X3dyYXAofm1vZGVsKQ0Kc2ltMyA8LSBzaW0zICU+JSANCiAgZ2F0aGVyX3Jlc2lkdWFscyhtb2QxLCBtb2QyKQkJIyBtb2QxLCBtb2QyIOqwgeqwgeyXkCDrjIDtlbQgcmVzaWR1YWwg7IOd7ISxDQpnZ3Bsb3Qoc2ltMywgYWVzKHgxLCByZXNpZCwgY29sb3VyID0geDIpKSArIA0KICBnZW9tX3BvaW50KCkgKyANCiAgZmFjZXRfZ3JpZChtb2RlbCB+IHgyKQ0KbW9kMSA8LSBsbSh5IH4geDEgKyB4MiwgZGF0YSA9IHNpbTQpDQptb2QyIDwtIGxtKHkgfiB4MSAqIHgyLCBkYXRhID0gc2ltNCkNCmdyaWQgPC0gc2ltNCAlPiUgDQogIGRhdGFfZ3JpZCgNCiAgICB4MSA9IHNlcV9yYW5nZSh4MSwgNSksIA0KICAgIHgyID0gc2VxX3JhbmdlKHgyLCA1KSANCiAgKSAlPiUgDQogIGdhdGhlcl9wcmVkaWN0aW9ucyhtb2QxLCBtb2QyKQ0KZ3JpZA0Kc2VxX3JhbmdlKGMoMC4wMTIzLCAwLjkyMzQyMyksIG4gPSA1KQ0Kc2VxX3JhbmdlKGMoMC4wMTIzLCAwLjkyMzQyMyksIG4gPSA1LCBwcmV0dHkgPSBUUlVFKQkJIyBwcmV0dHkgOiDrsJjsmKzrprztlbTshJwg67O06riwIOyii+qyjCDtlbTspIwgKHRhYmxl7IOd7ISx7IucIOycoOyaqSkNCngxIDwtIHJjYXVjaHkoMTAwKQ0Kc2VxX3JhbmdlKHgxLCBuID0gNSkJCQkJIyBzZXFfcmFuZ2UgIDptaW4tbWF4IOq1rOqwhOydhCA165Ox67aE7ZWcIOqwkg0Kc2VxX3JhbmdlKHgxLCBuID0gNSwgdHJpbSA9IDAuMTApCQkjIHRyaW06IGhlYWQsIHRhaWwg6rCBIDUl66W8IOygnOyZuO2VnCDtm4Qg7Iuc7ZaJDQpzZXFfcmFuZ2UoeDEsIG4gPSA1LCB0cmltID0gMC4yNSkJCQ0Kc2VxX3JhbmdlKHgxLCBuID0gNSwgdHJpbSA9IDAuNTApDQp4MiA8LSBjKDAsIDEpDQpzZXFfcmFuZ2UoeDIsIG4gPSA1KQ0Kc2VxX3JhbmdlKHgyLCBuID0gNSwgZXhwYW5kID0gMC4xMCkJCSMgZXhwYW5kIDog7JaRIOuBneydhCA1JeyUqSDtmZXsnqXtlagNCnNlcV9yYW5nZSh4MiwgbiA9IDUsIGV4cGFuZCA9IDAuMjUpDQpzZXFfcmFuZ2UoeDIsIG4gPSA1LCBleHBhbmQgPSAwLjUwKQ0KZ2dwbG90KGdyaWQsIGFlcyh4MSwgeDIpKSArIA0KICBnZW9tX3RpbGUoYWVzKGZpbGwgPSBwcmVkKSkgKyANCiAgZmFjZXRfd3JhcCh+IG1vZGVsKQ0KZ2dwbG90KGdyaWQsIGFlcyh4MSwgcHJlZCwgY29sb3VyID0geDIsIGdyb3VwID0geDIpKSArIA0KICBnZW9tX2xpbmUoKSArDQogIGZhY2V0X3dyYXAofiBtb2RlbCkNCmdncGxvdChncmlkLCBhZXMoeDIsIHByZWQsIGNvbG91ciA9IHgxLCBncm91cCA9IHgxKSkgKyANCiAgZ2VvbV9saW5lKCkgKw0KICBmYWNldF93cmFwKH4gbW9kZWwpDQoNCiMjIFRyYW5zZm9ybWF0aW9uDQpkZiA8LSB0cmliYmxlKA0KICB+eSwgfngsDQogICAxLCAgMSwNCiAgIDIsICAyLCANCiAgIDMsICAzDQopDQpkZg0KbW9kZWxfbWF0cml4KGRmLCB5IH4geF4yICsgeCkNCm1vZGVsX21hdHJpeChkZiwgeSB+IEkoeF4yKSArIHgpCSMgeF4yIO2VrSDstpTqsIANCm1vZGVsX21hdHJpeChkZiwgeSB+IHBvbHkoeCwgMikpDQpsaWJyYXJ5KHNwbGluZXMpDQptb2RlbF9tYXRyaXgoZGYsIHkgfiBucyh4LCAyKSkJIyBHZW5lcmF0ZSB0aGUgQi1zcGxpbmUgYmFzaXMgbWF0cml4IGZvciBhIG5hdHVyYWwgY3ViaWMgc3BsaW5lDQpzaW01IDwtIHRpYmJsZSgNCiAgeCA9IHNlcSgwLCAzLjUgKiBwaSwgbGVuZ3RoID0gNTApLA0KICB5ID0gNCAqIHNpbih4KSArIHJub3JtKGxlbmd0aCh4KSkNCikNCmdncGxvdChzaW01LCBhZXMoeCwgeSkpICsNCiAgZ2VvbV9wb2ludCgpDQptb2QxIDwtIGxtKHkgfiBucyh4LCAxKSwgZGF0YSA9IHNpbTUpDQptb2QyIDwtIGxtKHkgfiBucyh4LCAyKSwgZGF0YSA9IHNpbTUpDQptb2QzIDwtIGxtKHkgfiBucyh4LCAzKSwgZGF0YSA9IHNpbTUpDQptb2Q0IDwtIGxtKHkgfiBucyh4LCA0KSwgZGF0YSA9IHNpbTUpDQptb2Q1IDwtIGxtKHkgfiBucyh4LCA1KSwgZGF0YSA9IHNpbTUpDQoNCmdyaWQgPC0gc2ltNSAlPiUgDQogIGRhdGFfZ3JpZCh4ID0gc2VxX3JhbmdlKHgsIG4gPSA1MCwgZXhwYW5kID0gMC4xKSkgJT4lIA0KICBnYXRoZXJfcHJlZGljdGlvbnMobW9kMSwgbW9kMiwgbW9kMywgbW9kNCwgbW9kNSwgLnByZWQgPSAieSIpDQpncmlkCQkJCQkJCQkJCQkjIG1vZGVsIDXqsJwg6rCB6rCB7JeQIOuMgO2VtOyEnCB47J2YIHNlcV9yYW5nZSDqsJLsl5Ag64yA7ZW0IHByZWRpY3Rpb25zIOyDneyEsQ0KZ2dwbG90KHNpbTUsIGFlcyh4LCB5KSkgKyANCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9saW5lKGRhdGEgPSBncmlkLCBjb2xvdXIgPSAicmVkIikgKw0KICBmYWNldF93cmFwKH4gbW9kZWwpDQpkZiA8LSB0cmliYmxlKA0KICB+eCwgfnksDQogIDEsIDIuMiwNCiAgMiwgTkEsDQogIDMsIDMuNSwNCiAgNCwgOC4zLA0KICBOQSwgMTANCikNCm1vZCA8LSBsbSh5IH4geCwgZGF0YSA9IGRmKQ0KbW9kIDwtIGxtKHkgfiB4LCBkYXRhID0gZGYsIG5hLmFjdGlvbiA9IG5hLmV4Y2x1ZGUpDQpub2JzKG1vZCkJIyBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zIChOQSDsoJzsmbjtlZjqs6AgM+qwnCkNCmBgYA0KIyNDSEFQVEVSMTlf66y47KeA7IiYDQpgYGB7cn0NCiMjIyMjIyMjIyMjIyMjMTkuIE1vZGVsIEJ1aWxkaW5nIyMjIyMjIyMjIyMjDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkobW9kZWxyKQ0Kb3B0aW9ucyhuYS5hY3Rpb24gPSBuYS53YXJuKQ0KbGlicmFyeShueWNmbGlnaHRzMTMpDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCiMjIFdoeSBhcmUgbG93IHF1YW50aXR5IGRpYW1vbmRzIG1vcmUgZXhwZW5zaXZlPw0KZ2dwbG90KGRpYW1vbmRzLCBhZXMoY3V0LCBwcmljZSkpICsgZ2VvbV9ib3hwbG90KCkNCmdncGxvdChkaWFtb25kcywgYWVzKGNvbG9yLCBwcmljZSkpICsgZ2VvbV9ib3hwbG90KCkgI0rsg4nsnbzsiJjroZ0g7IOJ7J20IOyii+yngCDslYrsnYwNCmdncGxvdChkaWFtb25kcywgYWVzKGNsYXJpdHksIHByaWNlKSkgKyBnZW9tX2JveHBsb3QoKSAjbDHsnbzsiJjroZ0g7ISg66qF64+E6rCAIOyii+yngCDslYrsnYwNCiMjIFByaWNlIGFuZCBjYXJhdA0KI+ustOqyjChjYXJhdCnqsIAg6rCA6rKp7J2EIOqysOygle2VmOuKlCDspJHsmpTtlZwg7JqU7J247J2066mwLCDrgq7snYAg7ZKI7KeI7J2YIOuLpOydtOyVhOuqrOuTnOqwgCDrrLTqsbDsmrQg6rK97Zal7J20IOyeiOydjC4NCmluc3RhbGwucGFja2FnZXMoImhleGJpbiIpDQpsaWJyYXJ5KGhleGJpbikNCmdncGxvdChkaWFtb25kcywgYWVzKGNhcmF0LCBwcmljZSkpICsgDQogIGdlb21faGV4KGJpbnMgPSA1MCkgI2JpbnM6IOyImOyngSwg7IiY7Y+J67Cp7Zal7Jy866GcIGJpbnPsnZgg6rCc7IiYDQojIGNhcmF07J2YIOyYge2WpeydhCDrtoTrpqztlZjripQg66qo64247J2EIOygge2VqeyLnOy8nOu0hOycvOuhnOyNqCANCiPri6TsnbTslYTrqqzrk5zsnZgg64uk66W4IOyalOyGjOuTpOydtCDsg4HrjIDsoIHsnbgg6rCA6rKp7JeQIOyWtOuWu+qyjCDsmIHtlqXsnYQg66+47LmY64qU7KeAIOyCtO2OtOuzvCDsiJgg7J6I7J2MLg0KIyPrjbDsnbTthLAg7J6R7JeFIDLqsIDsp4Drpbwg66i87KCAIO2VmOqzoCDrtoTshJ3snYQg7ZWY6rKg64ukLi0+ZGlhbW9uZHMyDQojRm9jdXMgb24gZGlhbW9uZHMgc21hbGxlciB0aGFuIDIuNSBjYXJhdHMgKDk5LjclIG9mIHRoZSBkYXRhKQ0KI0xvZy10cmFuc2Zvcm0gdGhlIGNhcmF0IGFuZCBwcmljZSB2YXJpYWJsZXMuIC0+IOyEoO2Yleq0gOqzhOuhnCDrp4zrk6Qg7IiYIOyeiOydjC4NCmRpYW1vbmRzMiA8LSBkaWFtb25kcyAlPiUgDQogIGZpbHRlcihjYXJhdCA8PSAyLjUpICU+JSANCiAgbXV0YXRlKGxwcmljZSA9IGxvZzIocHJpY2UpLCBsY2FyYXQgPSBsb2cyKGNhcmF0KSkgIA0KZ2dwbG90KGRpYW1vbmRzMiwgYWVzKGxjYXJhdCwgbHByaWNlKSkgKyANCiAgZ2VvbV9oZXgoYmlucyA9IDUwKSAgDQptb2RfZGlhbW9uZCA8LSBsbShscHJpY2UgfiBsY2FyYXQsIGRhdGEgPSBkaWFtb25kczIpICANCmRpYW1vbmRzMiAlPiUgDQogIGRhdGFfZ3JpZChjYXJhdCA9IHNlcV9yYW5nZShjYXJhdCwgMjApKSAgDQpkaWFtb25kczIgJT4lIA0KICBkYXRhX2dyaWQoY2FyYXQgPSBzZXFfcmFuZ2UoY2FyYXQsIDIwKSkgJT4lIA0KICBtdXRhdGUobGNhcmF0ID0gbG9nMihjYXJhdCkpICANCmRpYW1vbmRzMiAlPiUgDQogIGRhdGFfZ3JpZChjYXJhdCA9IHNlcV9yYW5nZShjYXJhdCwgMjApKSAlPiUgDQogIG11dGF0ZShsY2FyYXQgPSBsb2cyKGNhcmF0KSkgJT4lIA0KICBhZGRfcHJlZGljdGlvbnMobW9kX2RpYW1vbmQsICJscHJpY2UiKQ0KZ3JpZCA8LSBkaWFtb25kczIgJT4lIA0KICBkYXRhX2dyaWQoY2FyYXQgPSBzZXFfcmFuZ2UoY2FyYXQsIDIwKSkgJT4lIA0KICBtdXRhdGUobGNhcmF0ID0gbG9nMihjYXJhdCkpICU+JSANCiAgYWRkX3ByZWRpY3Rpb25zKG1vZF9kaWFtb25kLCAibHByaWNlIikgJT4lIA0KICBtdXRhdGUocHJpY2UgPSAyIF4gbHByaWNlKQ0KZ2dwbG90KGRpYW1vbmRzMiwgYWVzKGNhcmF0LCBwcmljZSkpICsgDQogIGdlb21faGV4KGJpbnMgPSA1MCkgKyANCiAgZ2VvbV9saW5lKGRhdGEgPSBncmlkLCBjb2xvdXIgPSAicmVkIiwgc2l6ZSA9IDEpDQoj7KCB7ZWp7Iuc7YKoIOuqqO2YleyXkCDrlLDrpbTrqbQsIOustOqxsOyatCDri6TsnbTslYTrqqzrk5zripQg7JiI7IOB67O064ukIOu5hOyLuOyngCDslYrri6TripQg6rKD7J2EIOuzvCDsiJgg7J6I7J2MLg0KIyjsnbQg642w7J207YSw7IWL7JeQ64qUIDE5MDAw64us65+s67O064ukIOu5hOyLvCDri6TsnbTslYTrqqzrk5zripQg7JeG6riwIOuVjOusuCkNCmRpYW1vbmRzMiA8LSBkaWFtb25kczIgJT4lIA0KICBhZGRfcmVzaWR1YWxzKG1vZF9kaWFtb25kLCAibHJlc2lkIikNCmdncGxvdChkaWFtb25kczIsIGFlcyhsY2FyYXQsIGxyZXNpZCkpICsgDQogIGdlb21faGV4KGJpbnMgPSA1MCkNCiPsnpTssKjqsIAg7Yq567OE7ZWcIO2MqO2EtOydhCDrs7TsnbTsp4Ag7JWK7JWEIOyggeygiO2VqOydhCDslYwg7IiYIOyeiOydjC4NCiMj7J207KCcIOqwgOqyqShwcmljZSnrjIDsi6Dsl5Ag7J6U7LCo66W8IOyCrOyaqe2VmOyXrCDtlIzroa/snYQg64uk7IucIOq3uOugpOuztOyekA0KZ2dwbG90KGRpYW1vbmRzMiwgYWVzKGN1dCwgbHJlc2lkKSkgKyBnZW9tX2JveHBsb3QoKQ0KZ2dwbG90KGRpYW1vbmRzMiwgYWVzKGNvbG9yLCBscmVzaWQpKSArIGdlb21fYm94cGxvdCgpDQpnZ3Bsb3QoZGlhbW9uZHMyLCBhZXMoY2xhcml0eSwgbHJlc2lkKSkgKyBnZW9tX2JveHBsb3QoKQ0KI+ydtOulvCDthrXtlbQg64uk7J207JWE66qs65Oc7J2YIO2SiOyniOydtCDsoovsnYTsiJjroZ0g7IOB64yA7KCB7J24IOqwgOqyqeuPhCDrhpLslYTsp5DsnYQg67O8IOyImCDsnojsnYwuDQoj7J6U7LCo6rCAIC0x7J24IOqyveyasCwgbHByaWNl6rCAIOuLpOydtOyVhOuqrOuTnCDrrLTqsozsl5Ag6re86rGw7ZWcIOyYiOy4oey5mOuztOuLpCAx64uo7JyEIOuCruuLpOuKlCDsnZjrr7jsnoQuDQojIyBBIG1vcmUgY29tcGxpY2F0ZWQgbW9kZWwNCm1vZF9kaWFtb25kMiA8LSBsbShscHJpY2UgfiBsY2FyYXQgKyBjb2xvciArIGN1dCArIGNsYXJpdHksIGRhdGEgPSBkaWFtb25kczIpDQpkaWFtb25kczIgJT4lIA0KICBkYXRhX2dyaWQoY3V0LCAubW9kZWwgPSBtb2RfZGlhbW9uZDIpIA0KZ3JpZCA8LSBkaWFtb25kczIgJT4lIA0KICBkYXRhX2dyaWQoY3V0LCAubW9kZWwgPSBtb2RfZGlhbW9uZDIpICU+JSANCiAgYWRkX3ByZWRpY3Rpb25zKG1vZF9kaWFtb25kMikNCmdyaWQNCmdncGxvdChncmlkLCBhZXMoY3V0LCBwcmVkKSkgKyANCiAgZ2VvbV9wb2ludCgpDQpkaWFtb25kczIgPC0gZGlhbW9uZHMyICU+JSANCiAgYWRkX3Jlc2lkdWFscyhtb2RfZGlhbW9uZDIsICJscmVzaWQyIikNCmdncGxvdChkaWFtb25kczIsIGFlcyhsY2FyYXQsIGxyZXNpZDIpKSArIA0KICBnZW9tX2hleChiaW5zID0gNTApDQoj66qH66qH7J2YIOuLpOydtOyVhOuqrOuTnOyXkOyEnCDsnpTssKjqsIAg7YGs6rKMIOuCmO2DgOuCmOqzoCDsnojsnYzsnYQg67O8IOyImCDsnojsnYwuDQpkaWFtb25kczIgJT4lIA0KICBmaWx0ZXIoYWJzKGxyZXNpZDIpID4gMSkgJT4lIA0KICBhZGRfcHJlZGljdGlvbnMobW9kX2RpYW1vbmQyKSAlPiUgDQogIG11dGF0ZShwcmVkID0gcm91bmQoMiBeIHByZWQpKSAlPiUgDQogIHNlbGVjdChwcmljZSwgcHJlZCwgY2FyYXQ6dGFibGUsIHg6eikgJT4lIA0KICBhcnJhbmdlKHByaWNlKSAgDQoNCiMjIEV4ZXJjaWVzZQ0KIzQNCmRpYW1vbmRzMiAlPiUgDQogIGFkZF9wcmVkaWN0aW9ucyhtb2RfZGlhbW9uZDIpICU+JQ0KICBhZGRfcmVzaWR1YWxzKG1vZF9kaWFtb25kMikgJT4lDQogIHN1bW1hcmlzZShzcV9lcnIgPSBzcXJ0KG1lYW4ocmVzaWReMikpLA0KICAgICAgICAgICAgYWJzX2VyciA9IG1lYW4oYWJzKHJlc2lkKSksDQogICAgICAgICAgICBwOTc1X2VyciA9IHF1YW50aWxlKHJlc2lkLCAwLjk3NSksDQogICAgICAgICAgICBwMDI1X2VyciA9IHF1YW50aWxlKHJlc2lkLCAwLjAyNSkpDQoNCiMjIFdoYXQgYWZmZWN0cyB0aGUgbnVtYmVyIG9mICBkYWlseSBmbGlnaHRzPw0KZGFpbHkgPC0gZmxpZ2h0cyAlPiUgDQogIG11dGF0ZShkYXRlID0gbWFrZV9kYXRlKHllYXIsIG1vbnRoLCBkYXkpKSAlPiUgDQogIGdyb3VwX2J5KGRhdGUpICU+JSANCiAgc3VtbWFyaXNlKG4gPSBuKCkpDQpkYWlseSAgIyN0aGUgbnVtYmVyIG9mIGZsaWdodHMgcGVyIGRheQ0KZ2dwbG90KGRhaWx5LCBhZXMoZGF0ZSwgbikpICsgDQogIGdlb21fbGluZSgpDQojIyBEYXkgb2Ygd2Vlaw0KZGFpbHkgPC0gZGFpbHkgJT4lIA0KICBtdXRhdGUod2RheSA9IHdkYXkoZGF0ZSwgbGFiZWwgPSBUUlVFKSkNCmdncGxvdChkYWlseSwgYWVzKHdkYXksIG4pKSArIA0KICBnZW9tX2JveHBsb3QoKQ0KI+yjvOykkeuztOuLpCDso7zrp5Dsl5Ag67mE7ZaJ7J20IOyggeydgCDqsr3tlqXsnYQg67O07J6ELg0KbW9kIDwtIGxtKG4gfiB3ZGF5LCBkYXRhID0gZGFpbHkpDQpncmlkIDwtIGRhaWx5ICU+JSANCiAgZGF0YV9ncmlkKHdkYXkpICU+JSANCiAgYWRkX3ByZWRpY3Rpb25zKG1vZCwgIm4iKQ0KZ2dwbG90KGRhaWx5LCBhZXMod2RheSwgbikpICsgDQogIGdlb21fYm94cGxvdCgpICsNCiAgZ2VvbV9wb2ludChkYXRhID0gZ3JpZCwgY29sb3VyID0gInJlZCIsIHNpemUgPSA0KQ0KZGFpbHkgPC0gZGFpbHkgJT4lIA0KICBhZGRfcmVzaWR1YWxzKG1vZCkNCmRhaWx5ICU+JSANCiAgZ2dwbG90KGFlcyhkYXRlLCByZXNpZCkpICsgDQogIGdlb21fcmVmX2xpbmUoaCA9IDApICsgDQogIGdlb21fbGluZSgpDQoj7JyE7J2YIOuqqO2YlShtb2Qp7JeQIOyalOydvCDtmqjqs7zrpbwg64Sj7JeI6riwIOuVjOusuOyXkCwg7J207KCcIOychOydmCDtlIzroa8oeey2leydtCDsnpTssKgp7J2EIOuzvCDrlYwg7JqU7J286rO8IOq0gOugqOuQnCDsmIHtlqXsnYAg7KCc7Jm465CcIOqyg+yehC4NCiPtlIzroa/snYQg67O066m0IDbsm5TrtoDthLAg7J207IOB7ZWoKOyelOywqOqwgCDtgazqsowg64KY7YOA64KoKS4gDQoj65iQ7ZWcIDbsm5Qg7J207ZuE66GcIOq3nOy5meyggeyduCDtjKjthLTsnbQg67O07J6ELiAtPiDsmrDrpqzripQg7J2065+s7ZWcIO2MqO2EtOydhCDrqqjtmJUobW9kKeyXkCDrhKPsp4Ag66q77ZWc6rKD7J6ELg0KZ2dwbG90KGRhaWx5LCBhZXMoZGF0ZSwgcmVzaWQsIGNvbG91ciA9IHdkYXkpKSArIA0KICBnZW9tX3JlZl9saW5lKGggPSAwKSArIA0KICBnZW9tX2xpbmUoKQ0KI+ydvOyalOydvOydhCDrs7TrqbQg7Jqw66asIOuqqO2YleydtCDsnpgg7JiI7Lih7ZWY7KeAIOuqu+2VqOydhCDrs7wg7IiYIOyeiOydjC4NCiPsl6zquLDshJwg7KO866GcIOyXrOumhOyXkOuKlCDsmIjsuKHrs7Tri6Qg67mE7ZaJ7J20IOunjuqzoCwg6rCA7J2E7JeQ64qUIOyYiOy4oeuztOuLpCDruYTtlonsnbQg7KCB6rKMIOuCmO2DgOuCqC4NCiMgRXhlcmNpc2VzDQojMw0KZGFpbHkgPC0gZGFpbHkgJT4lDQogIG11dGF0ZSh3ZGF5MiA9IA0KICAgICAgICAgY2FzZV93aGVuKC4kd2RheSA9PSAiU2F0IiAmIC4kdGVybSA9PSAic3VtbWVyIiB+ICJTYXQtc3VtbWVyIiwNCiAgICAgICAgIC4kd2RheSA9PSAiU2F0IiAmIC4kIHRlcm0gPT0gImZhbGwiIH4gIlNhdC1mYWxsIiwNCiAgICAgICAgIC4kd2RheSA9PSAiU2F0IiAmIC4kdGVybSA9PSAic3ByaW5nIiB+ICJTYXQtc3ByaW5nIiwNCiAgICAgICAgIFRSVUUgfiBhcy5jaGFyYWN0ZXIoLiR3ZGF5KSkpDQptb2Q0IDwtIGxtKG4gfiB3ZGF5MiwgZGF0YSA9IGRhaWx5KQ0KZGFpbHkgJT4lIA0KICBnYXRoZXJfcmVzaWR1YWxzKHNhdF90ZXJtID0gbW9kNCwgYWxsX2ludGVyYWN0ID0gbW9kMikgJT4lIA0KICBnZ3Bsb3QoYWVzKGRhdGUsIHJlc2lkLCBjb2xvdXIgPSBtb2RlbCkpICsNCiAgICBnZW9tX2xpbmUoYWxwaGEgPSAwLjc1KQ0KIzQNCmRhaWx5IDwtIGRhaWx5ICU+JQ0KICBtdXRhdGUod2RheTMgPSANCiAgICAgICAgIGNhc2Vfd2hlbigNCiAgICAgICAgICAgLiRkYXRlICVpbiUgbHVicmlkYXRlOjp5bWQoYygyMDEzMDEwMSwgIyBuZXcgeWVhcnMNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAyMDEzMDEyMSwgIyBtbGsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAyMDEzMDIxOCwgIyBwcmVzaWRlbnRzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMjAxMzA1MjcsICMgbWVtb3JpYWwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAyMDEzMDcwNCwgIyBpbmRlcGVuZGVuY2UNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAyMDEzMDkwMiwgIyBsYWJvcg0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDIwMTMxMDI4LCAjIGNvbHVtYnVzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgMjAxMzExMTEsICMgdmV0ZXJhbnMNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAyMDEzMTEyOCwgIyB0aGFua3NnaXZpbmcNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAyMDEzMTIyNSkpIH4NCiAgICAgICAgICAgICAiaG9saWRheSIsDQogICAgICAgICAgIC4kd2RheSA9PSAiU2F0IiAmIC4kdGVybSA9PSAic3VtbWVyIiB+ICJTYXQtc3VtbWVyIiwNCiAgICAgICAgICAgLiR3ZGF5ID09ICJTYXQiICYgLiQgdGVybSA9PSAiZmFsbCIgfiAiU2F0LWZhbGwiLA0KICAgICAgICAgICAuJHdkYXkgPT0gIlNhdCIgJiAuJHRlcm0gPT0gInNwcmluZyIgfiAiU2F0LXNwcmluZyIsDQogICAgICAgICAgIFRSVUUgfiBhcy5jaGFyYWN0ZXIoLiR3ZGF5KSkpDQoNCm1vZDUgPC0gbG0obiB+IHdkYXkzLCBkYXRhID0gZGFpbHkpDQoNCmRhaWx5ICU+JSANCiAgc3ByZWFkX3Jlc2lkdWFscyhtb2Q1KSAlPiUNCiAgYXJyYW5nZShkZXNjKGFicyhyZXNpZCkpKSAlPiUNCiAgc2xpY2UoMToyMCkgJT4lIHNlbGVjdChkYXRlLCB3ZGF5LCByZXNpZCkNCg0KDQojNw0KZmxpZ2h0cyAlPiUgDQogIG11dGF0ZShkYXRlID0gbWFrZV9kYXRlKHllYXIsIG1vbnRoLCBkYXkpLA0KICAgICAgICAgd2RheSA9IHdkYXkoZGF0ZSwgbGFiZWwgPSBUUlVFKSkgJT4lDQogIGdyb3VwX2J5KHdkYXkpICU+JQ0KICBzdW1tYXJpc2UoZGlzdF9tZWFuID0gIG1lYW4oZGlzdGFuY2UpLA0KICAgICAgICAgICAgZGlzdF9tZWRpYW4gPSBtZWRpYW4oZGlzdGFuY2UpKSAlPiUNCiAgZ2dwbG90KGFlcyh5ID0gZGlzdF9tZWFuLCB4ID0gd2RheSkpICsNCiAgZ2VvbV9wb2ludCgpDQoNCiM4DQptb25kYXlfZmlyc3QgPC0gZnVuY3Rpb24oeCkgew0KICBmb3JjYXRzOjpmY3RfcmVsZXZlbCh4LCBsZXZlbHMoeClbLTFdKSAgDQp9DQoNCmFpbHkgPC0gZGFpbHkgJT4lIA0KICBtdXRhdGUod2RheSA9IHdkYXkoZGF0ZSwgbGFiZWwgPSBUUlVFKSkNCmdncGxvdChkYWlseSwgYWVzKG1vbmRheV9maXJzdCh3ZGF5KSwgbikpICsgDQogIGdlb21fYm94cGxvdCgpICsNCiAgbGFicyh4ID0gIkRheSBvZiBXZWVrIiwgeSA9ICJOdW1iZXIgb2YgZmxpZ2h0cyIpDQoNCmBgYA0KIyNDSEFQVEVSMjBf7KGw7ZiE7ISgDQojIyMiTWFueSBNb2RlbHMgd2l0aCBwdXJyIGFuZCBicm9vbSIiDQojIyMgSGFucyBSb3NsaW5nIOydmCB0ZWQg6rCV7Jew67O06riwICjtlZzquIAg7J6Q66eJKSAgaHR0cHM6Ly95b3V0dS5iZS9OVndCX29mOFpZcw0KIyFbSGFucyByb3NsaW5nXShDOi9Vc2Vycy9FS0xlZS9EZXNrdG9wL2hhbnNfcm9zbGluZy5qcGcpDQojIyMjIHBvd2VyZnVsIGlkZWFzDQogLSDrp47snYAg7IiY7J2YIOqwhOuLqO2VnCDrqqjrjbjsnYAg67O17J6h7ZWcIOuNsOydtO2EsOyFi+ydhCDrjZQg7J6YIOydtO2VtO2VmOqyjCDtlbTspIDri6QuIA0KIC0gbGlzdC1jb2x1bW5z66W8IOydtOyaqe2VmOyXrCDrjbDsnbTthLDrpbwg6rWs7KGw7ZmU7Iuc7YKo64ukLg0KIC0gYnJvb20g7Yyo7YKk7KeA66W8IOydtOyaqe2VtOyEnCB0aWR5IGRhdGHrpbwg66eM65Ok6rOgLCDsnbTroZwg66qo642466eB7J2EIO2VnOuLpC4NCg0KYGBge3J9DQpsaWJyYXJ5KG1vZGVscikNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShnYXBtaW5kZXIpDQpgYGANCndlIHdpbGwgdXNlIHRoZSAiZ2FwbWluZGVyImRhdGEuDQpgYGB7cn0NCmdhcG1pbmRlcg0KYGBgDQoNCiMjIyNRLiBbIkhvdyBkb2VzIGxpZmUgZXhwZWN0YW5jeShsaWZlRXhwKSBjaGFuZ2Ugb3ZlciB0aW1lKHllYXIpIGZvciBlYWNoIGNvdW50cnkoY291bnRyeSk/Il0NCjogM+qwgOyngCDrs4DsiJjsl5Ag7LSI7KCQ7J2EIOunnuy2lOyWtCDrtoTshJ3tlZjqs6DsnpAg7ZWoLg0KDQpgYGB7cn0NCmdhcG1pbmRlciAlPiUgDQogIGdncGxvdChhZXMoeWVhciwgbGlmZUV4cCwgZ3JvdXAgPSBjb3VudHJ5KSkgKw0KICAgIGdlb21fbGluZShhbHBoYSA9IDEvMykNCmBgYA0KMTcwMOqwnOydmCDqtIDsuKHsuZjsmYAgM+qwnOydmCDrs4DsiJjroZwgcGxvdHRpbmftlbQg67O4IOqysOqzvCwg66eO7J2AIOyImOqwgCDslYTri5jsl5Drj4Qg642w7J207YSw7J2YIO2dkOumhOydhCDtjIzslYXtlZjquLDqsIAg7Im97KeAIOyViuuLpC4g7KCE67CY7KCB7Jy866GcIOymneqwgO2VmOuKlCDslpHsg4HsnYQg67O8IOyImOuKlCDsnojsnLzrgpgsIOuqhyDqsJzsnZgg6rWt6rCA65Ok7J2AIOydtOulvCDrlLDrpbTsp4Ag7JWK64qUIOuLpOuKlCDqsoPsnYQg67O8IOyImCDsnojri6QuIOuUsOudvOyEnCAic3Ryb25nIHNpZ25hbCLsnYQg7IKs7Jqp7ZW07IScIO2KuOuenOuTnOulvCDrtoTshJ3tlbTrs7TsnpAuIOyEoO2YlSDtmozqt4Drpbwg7KCB7ZWp7Iuc7YKoIOqyg+qzvCDrgqjsnYAgcmVzaWR1YWzsl5Ag64yA7ZW0IHBsb3R0aW5n7J2EIO2VtOuztOyekC4NCg0KYGBge3J9DQpueiA8LSBmaWx0ZXIoZ2FwbWluZGVyLCBjb3VudHJ5ID09ICJOZXcgWmVhbGFuZCIpDQpueiAlPiUgDQogIGdncGxvdChhZXMoeWVhciwgbGlmZUV4cCkpICsgDQogIGdlb21fbGluZSgpICsgDQogIGdndGl0bGUoIkZ1bGwgZGF0YSA9ICIpDQoNCm56X21vZCA8LSBsbShsaWZlRXhwIH4geWVhciwgZGF0YSA9IG56KQ0KbnogJT4lIA0KICBhZGRfcHJlZGljdGlvbnMobnpfbW9kKSAlPiUNCiAgZ2dwbG90KGFlcyh5ZWFyLCBwcmVkKSkgKyANCiAgZ2VvbV9saW5lKCkgKyANCiAgZ2d0aXRsZSgiTGluZWFyIHRyZW5kICsgIikNCg0KbnogJT4lIA0KICBhZGRfcmVzaWR1YWxzKG56X21vZCkgJT4lIA0KICBnZ3Bsb3QoYWVzKHllYXIsIHJlc2lkKSkgKyANCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3VyID0gIndoaXRlIiwgc2l6ZSA9IDMpICsgDQogIGdlb21fbGluZSgpICsgDQogIGdndGl0bGUoIlJlbWFpbmluZyBwYXR0ZXJuIikNCmBgYA0KbmV3IHplYWxhbmQg66eQ6rOg64+ELCDrqqjrk6Ag64KY65287JeQIOuMgO2VtCDsib3qsowgZml0dGluZyDsi5ztgqTroKTrqbQg7Ja065a76rKMIO2VtOyVvCDtlaDquYw/IOuwqeq4iOqzvCDqsJnsnYAg7L2U65Oc66W8IOuCmOudvOunjCDrsJTqv5TqsIDrqbAg67O17IKsIOu2meyXrOuEo+q4sOulvCDtlZjripQg67Cp67KV64+EIOyeiOqyoOyngOunjCwg642UIO2aqOycqOyggeyduCDrsKnrspXsnbQg7J6I64ukLg0KDQojIyMjIE5lc3RlZCBEYXRhDQog642w7J207YSw7IWL7J2EIOuCmOudvOuzhOuhnCDrvZHslYTshJwg67CY67O17J2EIO2VmOuptCDrkJjripTrjbAsIOydtOulvCDsnITtlbQg7J6Q66OM7J2YIOq1rOyhsOulvCDsg4jroa3qsowg7KCV7J2Y7ZW07JW8IO2VnOuLpC4g7J2066W8IG5lc3RlZCBkYXRhIGZyYW1l7J2065286rOgIO2VnOuLpC4g7J2066W8IOychO2VnCDsvZTrk5zripQg64uk7J2M6rO8IOqwmeuLpC4NCmBgYHtyfQ0KYnlfY291bnRyeSA8LSBnYXBtaW5kZXIgJT4lIA0KICBncm91cF9ieShjb3VudHJ5LCBjb250aW5lbnQpICU+JSANCiAgbmVzdCgpDQoNCmJ5X2NvdW50cnkNCmBgYA0K67OA7ZiV65CcIOyekOujjOulvCDtjIzslYXtlZjquLAg7JyE7ZW07IScLCDtlZjrgpjsnZgg7JuQ7IaM66W8IOqzqOudvOyEnCDslrTrlqQg7KCV67O066W8IOyWtOuWu+qyjCDri7Tqs6Ag7J6I64qU7KeA66W8IO2ZleyduO2VtOuztOyekC4gKEFmZ2hhbmlzdGFu7J2EIOqzoOuluCDsmIjsi5wpDQpgYGB7cn0NCmJ5X2NvdW50cnkkZGF0YVtbMV1dDQpgYGANCuychOyXkOyEnCDsspjrn7wsIG5lc3RlZCBkYXRhIGZyYW1l7JeQ7ISc64qUIOqwgSDsl7Qg7J6Q7LK06rCAIOq3uOujueyehOydhCDtmZXsnbgg7ZWgIOyImCDsnojri6QuIOydtOygnCwg7J20IG5lc3RlZCBkYXRhIGZyYW1l7Jy866GcIOuqqOuNuOydhCBmaXR0aW5nIOyLnOy8nCDrs7TripQg7ZWo7IiY66W8IOunjOuTpOyWtOuztOyekC4NCmBgYHtyfQ0KY291bnRyeV9tb2RlbCA8LSBmdW5jdGlvbihkZikgew0KICBsbShsaWZlRXhwIH4geWVhciwgZGF0YSA9IGRmKQ0KfQ0KYGBgDQrsnbQg7ZWo7IiY7JmAIOuNsOydtO2EsOulvCDsnbTsoJwsIHB1cnJy7Yyo7YKk7KeA7J2YIG1hcCDtlajsiJjrpbwg7J207Jqp7ZW07IScIOyXsOqysOyLnOy8nOuztOyekC4NCmBgYHtyfQ0KbGlicmFyeShwdXJycikNCm1vZGVscyA8LSBtYXAoYnlfY291bnRyeSRkYXRhLCBjb3VudHJ5X21vZGVsKQ0KYGBgDQrqt7jrpqzqs6AsIOydtCDqsrDqs7zrpbwg64uk66W4IOyDiOuhnOyatCBvYmplY3TroZwg66eM65Ok7Ja07IScIGdsb2JhbCBlbnZpcm9ubWVudOyXkCDsoIDsnqXtlZjripQg6rKD7J20IOyVhOuLiOudvCwgYnlfY291bnRyeeudvOuKlCDrjbDsnbTthLAg7ZSE66CI7J6E7JeQIOyDiOuhnOyatCDrs4DsiJjrpbwg66eM65Ok7Ja07IScIOyggOyepe2VmOuKlCDrsKnsi53snYQg7YOd7ZWY64qUIOqyg+ydtCDrjZQg64Kr64ukLiDsnbTrpbwg7JyE7ZW0IGRwbHly7Yyo7YKk7KeA7J2YIG11dGF0ZSDtlajsiJjrpbwg7J207Jqp7ZWc64ukLiANCmBgYHtyfQ0KYnlfY291bnRyeSA8LSBieV9jb3VudHJ5ICU+JSANCiAgbXV0YXRlKG1vZGVsID0gbWFwKGRhdGEsIGNvdW50cnlfbW9kZWwpKQ0KYnlfY291bnRyeQ0KYGBgDQrsnbTroIfqsowg7J6R7JeF7J2EIO2VmOuptCwg66qo65OgIOq0gOugqCDqsJzssrTqsIAg7ZWo6ruYIOyggOyepeuQmOuvgOuhnCDtlYTthLDrp4HtlZjqsbDrgpgg7KCV66CsIO2VoCDrlYwg7IiY64+Z7Jy866GcIOuPmeq4sO2ZlCDtlaAg7ZWE7JqU6rCAIOyXhuuLpOuKlCDqsJXroKXtlZwg7J6l7KCQ7J20IOyeiOuLpC4g66eM7JW9IOydtOugh+qyjCDtlZjsp4Ag7JWK6rOgLCDrs4TqsJzsnZgg6rCd7LK066GcIOyggOyepeydhCDtlbTrhpPsnLzrqbQsIOuyoe2EsOulvCDsnqzsoJXsnZgg7ZWY6rGw64KYIOuyoe2EsOulvCBzdWJzZXTsi5ztgqzrlYwg7ZWY7JyE7KeR7ZWp7J2EIOqzhOyGjSDrj5nquLDtmZTtlbTso7zslrTslbwg7ZWc64ukLiDsnbTrpbwg7J6K7Ja067KE66aw64uk66m0LCDsvZTrk5zripQg64+M7JWE64qUIOqwgOqyoOyngOunjCwg7J6Y66q765CcIOuLteydhCDstpzroKXtlbTso7zqsowg65Cc64ukLg0KDQojIyMjIFVubmVzdGluZw0KMTQy6rCc7J2YIOuCmOudvOqwgCDsnojsnLzrr4DroZwsIOyasOumrOuKlCAxNDLqsJzsnZgg642w7J207YSw7ZSE66CI7J6E6rO8IDE0MuqwnOydmCDrqqjrjbjsnYQg6rCW6rOgIOyeiOuLpC4g7J6U7LCo66W8IOqzhOyCsO2VmOq4sCDsnITtlbTshJwsIGFkZF9yZXNpZHVhbHMoKeydhCDsnbTsmqntlZzri6QuIA0KYGBge3J9DQpieV9jb3VudHJ5IDwtIGJ5X2NvdW50cnkgJT4lIA0KICBtdXRhdGUoDQogICAgcmVzaWRzID0gbWFwMihkYXRhLCBtb2RlbCwgYWRkX3Jlc2lkdWFscykNCiAgKQ0KYnlfY291bnRyeQ0KYGBgDQojIyMjIFEuIEhvdyBjYW4geW91IHBsb3QgYSBsaXN0IG9mIGRhdGEgZnJhbWVzPw0KLS0+IG5lc3RlZOuQnCDrjbDsnbTthLAg7ZSE66CI7J6E7J2EIHVubmVzdCgp66W8IOydtOyaqe2VmOyXrCDsoJXqt5wg642w7J207YSwIO2UhOugiOyehCDtmJXsi53snLzroZwg67CU6r68IO2bhCBwbG907J2EIOq3uOumsOuLpC4g6re465+8IOuLpOydjOqzvCDqsJnsnbQg64KY652867OE66GcIOustuyXrOyeiOuNmCDrjbDsnbTthLDtlITroIjsnoTsnbQg7ZKA66as6rKMIOuQqOydhCDtmZXsnbjtlaAg7IiYIOyeiOuLpC4gDQpgYGB7cn0NCnJlc2lkcyA8LSB1bm5lc3QoYnlfY291bnRyeSwgcmVzaWRzKQ0KcmVzaWRzDQpgYGANCuydtOygnCwg7J6U7LCo64+E66W8IOq3uOumtCDsiJgg7J6I6rKMIOuQmOyXiOuLpC4NCmBgYHtyfQ0KcmVzaWRzICU+JSANCiAgZ2dwbG90KGFlcyh5ZWFyLCByZXNpZCkpICsNCiAgICBnZW9tX2xpbmUoYWVzKGdyb3VwID0gY291bnRyeSksIGFscGhhID0gMSAvIDMpICArDQogICAgZ2VvbV9zbW9vdGgoc2UgPSBGQUxTRSkNCmBgYA0K64yA66WZ67OE66GcIOuztOuptCwg64yA66WZ66eI64uk7J2YIO2KueuzhO2VnCDtirnsp5XsnbQg642UIOuTnOufrOuCmOqyjCDrkJzri6QuIA0KYGBge3J9DQpyZXNpZHMgJT4lIA0KICBnZ3Bsb3QoYWVzKHllYXIsIHJlc2lkLCBncm91cCA9IGNvdW50cnkpKSArDQogICAgZ2VvbV9saW5lKGFscGhhID0gMSAvIDMpICsgDQogICAgZmFjZXRfd3JhcCh+Y29udGluZW50KQ0KYGBgDQrsnpTssKjqt7jrprzsnZgg66qo7JaR7J2EIOuztOuLiCwgZml0dGluZyDsi5ztgqgg66qo7ZiV7J20IOyggeygiO2VmOyngCDslYrslZjri6Tqs6Ag7YyQ64uo7ZWgIOyImCDsnojri6QuIO2Kue2eiCBhZnJpY2Hsl5DshJwg7YGwIOyelOywqOuTpOydhCDrs7wg7IiYIOyeiOuLpC4g64uk7J2MIOyEueyFmOyXkOyEnCDsnbTrpbwg64uk66W4IOuwqe2WpeycvOuhnCDsoJHqt7ztlZjsl6wg67aE7ISd7ZW067O07J6QLg0KDQojIyMjIE1vZGVsIFF1YWxpdHkNCiDrqqjrjbjroZzrtoDthLAg7J6U7LCo66W8IOq1rO2VmOyXrCDrs7TripQg6rKD67O064ukLCBtb2RlbCBxdWFsaXR57JeQIOuMgO2VnCDrs7TtjrjsoIHsnbgg7Lih7KCV7LmY66W8IOuztOuKlCDrsKnrspXsl5Ag64yA7ZW0IOyDneqwge2VtOuztOyekC4gYnJvb20g7Yyo7YKk7KeA66GcLCDrqqjrjbjsnYQgdGlkeeuNsOydtO2EsOuhnCDrp4zrk6Qg7IiYIOyeiOuLpC4g65iQ7ZWcLCBicm9vbSDtjKjtgqTsp4DsnZggZ2xhbmNlKCntlajsiJjrpbwg7I2o7IScIOuqqO2YlSDtj4nqsIAg7KeA7ZGc66W8IOuNsOydtO2EsCDtlITroIjsnoQg7ZiV7YOc66GcIOu9keyVhOuCvCDsiJgg7J6I64ukLiANCmBgYHtyfQ0KYnJvb206OmdsYW5jZShuel9tb2QpDQpgYGANCuyXrOq4sOyEnCDqtaztlZwg7J20IOygleuztOulvCBtdXRhdGUoKeyZgCB1bm5lc3QoKeulvCDsnbTsmqntlbTshJwg6rCBIOuCmOudvOuzhOydmCDtlonsnLzroZwg64uk7IucIOuEo+ydgCDrjbDsnbTthLAg7ZSE66CI7J6E7J2EIOunjOuTpCDsiJgg7J6I64ukLg0KYGBge3J9DQpieV9jb3VudHJ5ICU+JSANCiAgbXV0YXRlKGdsYW5jZSA9IG1hcChtb2RlbCwgYnJvb206OmdsYW5jZSkpICU+JSANCiAgdW5uZXN0KGdsYW5jZSkNCmBgYA0K6re465+s64KYIOydtOugh+qyjCwg66qo65OgIGxpc3Qg7Je07J2EIO2PrO2VqO2VmOuKlCDqsoPsnYAg7Jqw66as6rCAIOybkO2VmOuNmCDqsrDqs7zqsIAg7JWE64uI66+A66GcLCB1bm5lc3Tsl5DshJwgLmRyb3A9VFJVReuhnCDri6Tsi5wg7Ji17IWY7J2EIOyEpOygle2VmOyXrCDrsJTqv5TspIDri6QuIA0KYGBge3J9DQpnbGFuY2UgPC0gYnlfY291bnRyeSAlPiUgDQogIG11dGF0ZShnbGFuY2UgPSBtYXAobW9kZWwsIGJyb29tOjpnbGFuY2UpKSAlPiUgDQogIHVubmVzdChnbGFuY2UsIC5kcm9wID0gVFJVRSkNCmdsYW5jZQ0KYGBgDQrsnbTroIfqsowg7Ja77Ja07KeEIOuNsOydtO2EsO2UhOugiOyehOycvOuhnCDrk5zrlJTslrQsIOyemCDrp57sp4Ag7JWK64qUIOuqqOuNuOydtCDrrLTsl4fsnbjsp4Ag7LC+7J2EIOyImCDsnojqsowg65CY7JeI64ukLiDrqqjtmJUg7Y+J6rCAIOyngO2RnOykkeydmCDtlZjrgpjsnbggciBzcXVhcmXqsJLsl5Ag64yA7ZW0IOyYpOumhOywqOyInOycvOuhnCBzb3J0aW5n7J2EIO2VtOuztOyekC4NCmBgYHtyfQ0KZ2xhbmNlICU+JSANCiAgYXJyYW5nZShyLnNxdWFyZWQpDQpgYGANCiDqt7gg6rKw6rO8LCBBZnJpY2Hsl5DshJwg66qo64247J20IOygnOydvCDslYjrp57snYzsnYQg7ZmV7J24IO2VoCDsiJgg7J6I7JeI64ukLiDsnbTrpbwg64uk7IucIOyerO2ZleyduO2VtOuztOq4sCDsnITtlbQg6re466a87J2EIOq3uOugpOuztOugpCDtlZzri6QuIOq0gOy4oeyImOqwgCDsoIHqs6AsIOydtOyCsO2YlSDrs4DsiJjsnoTsnLzroZwsIOyngO2EsOq3uOumvOydhCDrs7TripQg6rKD7J20IO2aqOqzvOyggeydtOuLpC4NCmBgYHtyfQ0KZ2xhbmNlICU+JSANCiAgZ2dwbG90KGFlcyhjb250aW5lbnQsIHIuc3F1YXJlZCkpICsgDQogICAgZ2VvbV9qaXR0ZXIod2lkdGggPSAwLjUpDQpgYGANCiDsmrDrpqzripQg64uk7J2M6rO8IOqwmeydtCDrgq7snYAgUi1zcXVhcmXqsJLsnYQg6rCW6rOgIOyeiOuKlCDqta3qsIDrk6Trp4zsnYQg6rCA7KeA6rOgIOuLpOyLnCDqt7jrprzsnYQg6re466Ck67O8IOyImCDsnojri6QuDQpgYGB7cn0NCmJhZF9maXQgPC0gZmlsdGVyKGdsYW5jZSwgci5zcXVhcmVkIDwgMC4yNSkNCg0KZ2FwbWluZGVyICU+JSANCiAgc2VtaV9qb2luKGJhZF9maXQsIGJ5ID0gImNvdW50cnkiKSAlPiUgDQogIGdncGxvdChhZXMoeWVhciwgbGlmZUV4cCwgY29sb3VyID0gY291bnRyeSkpICsNCiAgICBnZW9tX2xpbmUoKQ0KYGBgDQrsmrDrpqzripQg7Jes6riw7IScIEhJViAvIEFJRFMg7KCE7Je867OR6rO8IOultOyZhOuLpCDrjIDtlZnsgrQsIOydtCDrkZDqsIDsp4Ag67mE6re57J2YIO2aqOqzvOulvCDqt7jrprzsnLzroZwg7ZmV7J24IO2VoCDsiJgg7J6I7JeI64ukLg0KKOuPmeyYgeyDgSDrgrTsmqkg7LC46rOgKQ0KDQojIVtIYW5zIHJvc2xpbmddKEM6L1VzZXJzL0VLTGVlL0Rlc2t0b3AvaGFuc19yb3NsaW5nX2hpdi5qcGcpDQoNCg0KPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09DQoNCiMjI0xpc3QtQ29sdW1ucyANCuyngOq4iOq5jOyngCDrp47snYAg66qo64247J2EIOuLpOujqOuKlCDqsoPsl5Ag64yA7ZWcIOyghOuwmOyggeyduCDtnZDrpoTsnYQg67O07JWY64ukLiDsp4DquIjrtoDthLDripQgbGlzdC1jb2x1bW4g642w7J207YSwIOq1rOyhsOyXkCDrjIDtlbQg642UIOyekOyEuO2eiCDsgrTtjrQg67O8IOqyg+ydtOuLpC4g642w7J207YSwIO2UhOugiOyehOydgCDrj5nsnbztlZwg6ri47J20IOuyoe2EsOqwgCDrqqjsl6wg66qF66qF65CY6rOgLCBsaXN0LWNvbHVtbuydgCDrjbDsnbTthLDtlITroIjsnoTsnZgg7KCV7J2Y7JWI7JeQIOuCtO2PrOuQmOyWtCDsnojripQg6rCc64WQ7J2064ukLiBsaXN064qUIOuyoe2EsOydtOuvgOuhnCwgbGlzdOulvCDrjbDsnbTthLAg7ZSE66CI7J6E7J2YIOyXtOuhnCDstpTqsIDtlZjripQg6rKD7J2AIOq0nOywruuLpC4g6re465+s64KYLCBkYXRhLmZyYW1lKCnsl5DshJzripQgbGlzdOulvCDsl7TsnZggbGlzdOuhnCDri6Tro6jrr4DroZwsIOq4sOyhtOydmCBS7JeQ7ISc64qUIOydtOufrO2VnCDsnpHsl4XsnbQg7Im97KeAIOyViuyVmOuLpC4g7JiI66W8IO2Gte2VtCDsgrTtjrTrs7TsnpAuDQpgYGB7cn0NCmRhdGEuZnJhbWUoeCA9IGxpc3QoMTozLCAzOjUpKQ0KYGBgDQrsnbTroIfqsowg7ZWY64qUIOqyg+ydgCDsmrDrpqzqsIAg7JuQ7ZaI642YIOqysOqzvOqwgCDslYTri4jrr4DroZwsIOydtOuVjOuKlCBJKCnrpbwg7IKs7Jqp7ZWY7JesIOyVhOuemOyZgCDqsJnsnbQg64KY7YOA64K07JW8IO2VnOuLpC4g6re465+s64KYIOyZhOyghO2eiCDsnpgg7Lac66Cl7ZW07KSA64uk6rOg64qUIOuzvCDsiJgg7JeG64ukLg0KYGBge3J9DQpkYXRhLmZyYW1lKA0KICB4ID0gSShsaXN0KDE6MywgMzo1KSksIA0KICB5ID0gYygiMSwgMiIsICIzLCA0LCA1IikNCikNCmBgYA0KYGBge3J9DQp0aWJibGUoDQogIHggPSBsaXN0KDE6MywgMzo1KSwgDQogIHkgPSBjKCIxLCAyIiwgIjMsIDQsIDUiKQ0KKQ0KYGBgDQp0aWJibGXsnYAg7J6F66Cl6rCS7J2EIOuzteyCrO2VmOuKlCDqsJzrhZDsnbQg7JWE64uI66+A66GcLCDrjZQg64KY7J2AIOy2nOugpSDrsKnrspXsnYQg7KCc6rO17ZWY7JesIOychOydmCDsmIjsi5zrk6Tsl5DshJwg67Cc7IOd7ZWcIOusuOygnOuTpOydhCDtlbTqsrDtlZzri6QuDQpgYGB7cn0NCnRyaWJibGUoDQogICB+eCwgfnksDQogIDE6MywgIjEsIDIiLA0KICAzOjUsICIzLCA0LCA1Ig0KKQ0KYGBgDQrsnbTroIfqsowg7ZWY66m0IOuCtOqwgCDtlYTsmpTroZwg7ZWY64qUIOumrOyKpO2KuOulvCDrjZQg7Im96rKMIOyekOuPmeyggeycvOuhnCB0aWJibGXsnYQg7J207Jqp7ZWY7JesIOunjOuTpCDsiJgg7J6I64ukLiDsnbTroIfqsowsIGxpc3QtY29sdW1uc+uKlCDsponqsIHsoIHsnbgg642w7J207YSwIOq1rOyhsOuhnCDtmZzsmqnrkJjquLDsl5Ag7Jyg7Jqp7ZWY64ukLiDrjIDrtoDrtoTsnZggUu2VqOyImOuTpOydgCBhdG9taWMg67Kh7YSw7JmAIOuNsOydtO2EsCDtlITroIjsnoTsnYQg7J207Jqp7ZW07IScIO2VqOyImOqwgCDsp5zsl6zsoLgg7J6I7Jy866+A66GcLCB0aWJibGXsnYQg7J207Jqp7ZW07IScIOuwlOuhnCDsnpHsl4XsnYQg7IiY7ZaJ7ZWY6riw64qUIOyWtOugpOyauCDsiJgg7J6I7KeA66eMLCDqtIDroKjsnojripQg7ZWt66qp65Ok7J2EIOyXsOqysO2VmOyXrCDsk7gg7IiYIOyeiOuLpOuKlCDsnqXsoJDsnbQg7J6I7Jy866+A66GcIOq3uOunjO2VnCDqsIDsuZjqsIAg7J6I64uk6rOgIOuzuOuLpC4NCg0KIyMjQ3JlYXRpbmcgTGlzdC1Db2x1bW5zIA0K7Ya17IOB7KCB7Jy866GcLCB0aWJibGUoKeydhCDsnbTsmqntlbTshJwgbGlzdC1jb2x1bW5z66W8IOuwlOuhnCDrp4zrk6Tsp4DripQg7JWK64qU64ukLiDrjIDsi6Dsl5AsIHJlZ3VsYXIgY29sdW1uc+ulvCDsnbTsmqntlbTshJwg66eM65Oc64qU642wLCDri6TsnYwg7IS46rCA7KeAIOuwqeuyleuTpCDspJHsnZgg7ZWY64KY66GcIO2VnOuLpC4NCiAtICgxKSB0aWR5cjo6bmVzdCgpIOuhnCDqt7jro7ntmZTrkJwg642w7J207YSwIO2UhOugiOyehOydhCBuZXN0ZWQg642w7J207YSwIO2UhOugiOyehOycvOuhnCDrsJTqvrzri6QuDQogLSAoMikgbXV0YXRlKCkg7JmAIOuyoe2EsO2ZlCDsi5ztgqTripQg7ZWo7IiY66W8IOydtOyaqe2VtCBsaXN066W8IOuwmO2ZmO2VnOuLpC4NCiAtICgzKSBzdW1tZXJpemUoKSDsmYAg7JqU7JW97ZW07KO864qUIO2VqOyImOulvCDthrXtlbQg7Jes65+sIOqysOqzvOulvCDstpzroKXtlZzri6QuDQrrjIDssrTsoIHsnLzroZwsIHRpYmJsZTo6ZW5mcmFtZSgp7J2EIOyCrOyaqe2VmOuptCwg66qF66qF65CcIOumrOyKpO2KuOuhnCDrtoDthLAg66eM65Ok7Ja0IOuCvCDsiJjrj4Qg7J6I64ukLiDsnbzrsJjsoIHsnLzroZwsIGxpc3QtY29sdW1uc+ulvCDrp4zrk6TrlYzripQsIOq3uOuTpOydtCBob21vZ2VuZW91c+2VnCDqsoPsnYQg7ZmV7Iuk7ZWY6rKMIO2VtOyVvO2VnOuLpC4g7KaJLCDqsJnsnYAg7JqU7IaM66W8IOuLtOqzoCDsnojslrTslbwg7ZWc64uk64qUIOqyg+ydtOuLpC4gcHVycnLsl5DshJwgdHlwZS1zdGFibGUg7ZWo7IiY66W8IOuwsOyboOycvOuvgOuhnCwg7J2066W8IOq4sOyWte2VmOqzoCDsnpDsl7DsiqTrn73qsowg7J6R7JeF7J2EIO2VoCDsiJgg7J6I7J2EIOqyg+ydtOuLpC4NCg0KDQojIyMjICgxKSBXaXRoIE5lc3RpbmcNCm5lc3QoKeuKlCDrpqzsiqTtirgg7Je07J2EIOqwluuKlCBuZXN0ZWQg642w7J207YSwIO2UhOugiOyehOydhCDrp4zrk6TslrQg7KSA64ukLiBuZXN0KCnrpbwg7JOw64qUIOuwqeuyleyXkOuKlCDrkZAg6rCA7KeA6rCAIOyeiOuKlOuNsCwg7KeA6riI6rmM7KeA64qUIOq3uOujue2ZlOuQnCDrjbDsnbTthLAg7ZSE66CI7J6E7JeQ7IScIOyCrOyaqe2VtOyZlOyXiOuLpC4g7J20IOuVjCBuZXN0KCnripQg6re466O57ZmU65CcIOyXtOydhCDqt7gg6re464yA66GcIOycoOyngOyLnOy8nOyjvOuptOyEnCDrqqjrk6Ag6rKD7J2EIOuLpOuwnOuhnCDqt7gg66as7Iqk7Yq4IOyXtCDslYjsl5Ag64u07JWEIOyjvOyXiOyXiOuLpC4g64uk7J2MIOyYiOyLnOulvCDtmZXsnbjtlbTrs7TrqbQg7J207ZW06rCAIOuQoCDqsoPsnbTri6QuDQpgYGB7cn0NCmdhcG1pbmRlciAlPiUgDQogIGdyb3VwX2J5KGNvdW50cnksIGNvbnRpbmVudCkgJT4lIA0KICBuZXN0KCkNCmBgYA0K7Jqw66as64qUIOydtOqyg+ydhCDqt7jro7ntmZTrkJjsp4Ag7JWK7JWY642YIOuNsOydtO2EsCDtlITroIjsnoTsl5Drj4Qg7IKs7Jqp7ZWgIOyImCDsnojqs6AsIOykkeyyqSDtlaAg7Je07J2EIO2KueuzhO2eiCDsp4DsoJXtlaAg7IiY64+EIOyeiOuLpC4gDQpgYGB7cn0NCmdhcG1pbmRlciAlPiUgDQogIG5lc3QoeWVhcjpnZHBQZXJjYXApDQpgYGANCg0KIyMjIyAoMikgRnJvbSBWZWN0b3JpemVkIEZ1bmN0aW9ucw0KYXRvbWljIOuyoe2EsOulvCDrpqzsiqTtirjroZwg67CU6r+U7KO864qUIOycoOyaqe2VnCDtlajsiJjrk6Tsl5Ag64yA7ZW07IScIOuwsOyboOyXiOuLpCgxMeyepSDssLjqs6ApLiBtdXRhdGXrpbwg7IKs7Jqp7ZWY66m0LCBsaXN0LWNvbHVtbuydhCDrp4zrk6Qg7IiYIOyeiOuLpC4NCuuLpOydjCDsmIjsi5zrpbwg7ZmV7J247ZW067O07J6QLg0KYGBge3J9DQpkZiA8LSB0cmliYmxlKA0KICB+eDEsDQogICJhLGIsYyIsIA0KICAiZCxlLGYsZyINCikgDQpkZiAlPiUgDQogbXV0YXRlKHgyID0gc3RyaW5ncjo6c3RyX3NwbGl0KHgxLCAiLCIpKQ0KYGBgDQrsnbTrpbwgdW5uZXN0KCnrpbwg7IKs7Jqp7ZWY66m0LCB4MuyXkCDtlaDri7nrkJwg66as7Iqk7Yq466W8IOuyoe2EsOuhnCDrsJTqv4Ag7IiYIOyeiOqyjCDrkJzri6QuIA0KYGBge3J9DQpkZiAlPiUgDQogIG11dGF0ZSh4MiA9IHN0cmluZ3I6OnN0cl9zcGxpdCh4MSwgIiwiKSkgJT4lIA0KICB1bm5lc3QoKQ0KYGBgDQrsnbTrn6ztlZwg7Jyg7ZiV7J2AIHB1cnJyIO2MqO2CpOyngOydmCBtYXAoKSwgbWFwMigpLCBwbWFwKCnrk7HsnYQg7IKs7Jqp7ZWY7JesIOu5hOyKt+2VnCDsnpHsl4XsnYQg7ZWgIOyImCDsnojri6QuIA0KDQoNCiMjIyMgKDMpIEZyb20gTXVsdGl2YWx1ZWQgU3VtbWVyaWVzDQogc3VtbWVyaXplKCnsnZgg7KCc7JW97KGw6rG07J2ALCDtlZwg6rCS7JeQIOuMgO2VtOyEnOunjCDstpzroKXsnYQg7ZW07KO864qUIO2VqOyImOudvOuKlCDqsoPsnbTri6QuIOyJveqyjCDrp5DtlbQsIOyasOumrOuKlCDsnbTqsoPsnYQg7J6E7J2Y7J2YIOq4uOydtOulvCDqsIDsp4Qg67Kh7YSwIOymiSwgcXVhbnRpbGUoKeqwmeydgCDtlajsiJjsl5DripQg7IKs7Jqp7J2EIO2VoCDsiJgg7JeG64uk64qUIOqyg+ydtOuLpC4g65WM66y47JeQIOuLpOydjCDsvZTrk5zsl5DshJwg7JeQ65+s6rCAIOuCmO2DgOuCnOuLpC4NCmBgYHtyfQ0KbXRjYXJzICU+JSANCiAgZ3JvdXBfYnkoY3lsKSAlPiUgDQogIHN1bW1hcmlzZShxID0gcXVhbnRpbGUobXBnKSkNCmBgYA0K7JeQ65+s6rCAIOuCmOyngCDslYrqsowg7ZWY66Ck66m0IOyVhOuemOyymOufvCDqsrDqs7zrpbwgbGlzdOuhnCDrrLbslrTshJwg64+M66as66m0IOuPjOyVhOqwgOqyjCDrkJzri6QuIOyZnOuDkO2VmOuptCBzdW1tYXJ564qUIOq4uOydtOqwgCAx7J20IOuQnCDrpqzsiqTtirjsl5Ag7KCB7Jqp65CY6riwIOuVjOusuOydtOuLpC4NCmBgYHtyfQ0KbXRjYXJzICU+JSANCiAgZ3JvdXBfYnkoY3lsKSAlPiUgDQogIHN1bW1hcmlzZShxID0gbGlzdChxdWFudGlsZShtcGcpKSkNCmBgYA0K7J20IOqysOqzvOulvCB1bm5lc3QoKeulvCDsnbTsmqntlZjsl6wg64uk7IucIO2SgOyWtOyjvOuptCwg7Jqw66as6rCAIOybkO2VmOuNmCDqsrDqs7zsmYAg7ZmV66Wg6rCS7J2EIOyWu+qyjCDrkJzri6QuDQpgYGB7cn0NCnByb2JzIDwtIGMoMC4wMSwgMC4yNSwgMC41LCAwLjc1LCAwLjk5KQ0KbXRjYXJzICU+JSANCiAgZ3JvdXBfYnkoY3lsKSAlPiUgDQogIHN1bW1hcmlzZShwID0gbGlzdChwcm9icyksIHEgPSBsaXN0KHF1YW50aWxlKG1wZywgcHJvYnMpKSkgJT4lIA0KICB1bm5lc3QoKQ0KYGBgDQoNCiMjIyMgKDQpIEZyb20gYSBOYW1lZCBMaXN0DQpsaXN07Je07J2EIOqwluqzoCDsnojripQg642w7J207YSwIO2UhOugiOyehOydgCAi66qp66Gd7J2YIOuCtOyaqeqzvCDsmpTshozrpbwg66qo65GQIOuwmOuzte2VmOugpOuKlCDqsr3smrAg7Ja065a76rKMIO2VtOyVvCDtlZjrgpg/IuudvOuKlCDtnZTtlZwg66y47KCc7JeQIOuMgO2VnCDtlbTqsrDssYXsnYQg7KCc7Iuc7ZWc64ukLiDrqqjrk6Ag6rKD7J2EIO2VmOuCmOydmCDqsJ3ssrTroZwg66y264qUIOuMgOyLoCDrjbDsnbTthLAg7ZSE66CI7J6E7J2EIOunjOuTnOuKlCDqsoPsnbQg642UIOyJveuLpC4g7ZWcIOyXtOyXkOuKlCBlbGVtZW506rCAIO2PrO2VqOuQoCDsiJgg7J6I6rOgIOuYkCDtlZwg7Je07JeQ64qUIGxpc3TqsIAg7Y+s7ZWo65CgIOyImCDsnojri6QuIGxpc3TroZzrtoDthLAg6re465+s7ZWcIOuNsOydtO2EsCDtlITroIjsnoTsnYQg66eM65Oc64qUIOyJrOyatCDrsKnrspXsnYAgdGliYmxlIDo6IGVuZnJhbWUgKCnrpbwg7JOw66m0IOuQnOuLpC4g64uk7J2MIOyYiOyLnOulvCDrs7TsnpAuDQoNCmBgYHtyfQ0KeCA8LSBsaXN0KA0KICBhID0gMTo1LA0KICBiID0gMzo0LCANCiAgYyA9IDU6Ng0KKSANCmRmIDwtIGVuZnJhbWUoeCkNCmRmDQpgYGANCuydtOufrO2VnCDqtazsobDsnZgg7J6l7KCQ7J2AIOyngeyEpOyggeyduCDrsKnsi53snLzroZwg7J2867CY7ZmU65Cc64uk64qUIOqyg+ydtOuLpC4gbmFtZeydgCDrrLjsnpDtmJUg67OA7IiY6rCAIG1ldGEgZGF0YeuhnCDsnojsnLzrqbQg7Jyg7Jqp7ZWY7KeA66eMLCDri6Trpbgg7Jyg7ZiV7J2YIOuNsOydtO2EsOuCmCDsl6zrn6zqsJzsnZgg67Kh7YSw6rCAIOydtOumhOyXkCDrk6TslrTqsIDsnojsnLzrqbQg7Jyg7Jqp7ZWY7KeA64qUIOyViuydgCDtkZztmITrsKnsi53snbTri6QuDQrslrTsqIzrk6AsIOydtOygnCDsnbTrpoTqs7wg6rCS7J2EIO2VqOq7mCDrsJjrs7XtlZjroKTrqbQsIG1hcDIoKeulvCDsgqzsmqntlZjrqbQg65Cc64ukLg0KYGBge3J9DQpkZiAlPiUgDQogIG11dGF0ZSgNCiAgICBzbXJ5ID0gbWFwMl9jaHIobmFtZSwgdmFsdWUsIH4gc3RyaW5ncjo6c3RyX2MoLngsICI6ICIsIC55WzFdKSkNCiAgKQ0KYGBgDQoNCiMjIyBMaXN0LUNvbHVtbnPrpbwg6rCE64uo7Z6IIO2VmOq4sA0KIC0gKDEpLiDrpqzsiqTtirjrpbwg67Kh7YSw7ZmUIOyLnO2CpOq4sCA6IOuLqOydvCDqsJLsnYQg7JuQ7ZWgIOqyveyasCBtYXBfbGdsICgpLCBtYXBfaW50ICgpLCBtYXBfZGJsICgpIOuwjyBtYXBfY2hyICgp7JeQIG11dGF0ZSAoKeulvCDsgqzsmqntlZjsl6wg7JuQ7J6QIOuyoe2EsOulvCDrp4zrk6Dri6QuDQpgYGB7cn0NCmRmIDwtIHRyaWJibGUoDQogIH54LA0KICBsZXR0ZXJzWzE6NV0sDQogIDE6MywNCiAgcnVuaWYoNSkNCikNCiAgDQpkZiAlPiUgbXV0YXRlKA0KICB0eXBlID0gbWFwX2Nocih4LCB0eXBlb2YpLA0KICBsZW5ndGggPSBtYXBfaW50KHgsIGxlbmd0aCkNCikNCmBgYA0KDQogLSAoMikuIFVubmVzdGluZyA6IOunjuydgCDqsJLsnYQg7JuQ7ZWY66m0IHVubmVzdCAoKeulvCDsgqzsmqntlZjsl6wg66qp66GdIOyXtOydhCDsnbzrsJgg7Je066GcIOuLpOyLnCDrs4DtmZjtlZjqs6Ag7ZWE7JqU7ZWc66eM7YG8IO2WieydhCDrsJjrs7XtlZzri6QuDQrsmIjsi5wxDQpgYGB7cn0NCnRpYmJsZSh4ID0gMToyLCB5ID0gbGlzdCgxOjQsIDEpKSAlPiUgdW5uZXN0KHkpDQpgYGANCuyYiOyLnDINCmBgYHtyfQ0KZGYxIDwtIHRyaWJibGUoDQogIH54LCB+eSwgICAgICAgICAgIH56LA0KICAgMSwgYygiYSIsICJiIiksIDE6MiwNCiAgIDIsICJjIiwgICAgICAgICAgIDMNCikNCmRmMQ0KYGBgDQpgYGB7cn0NCmRmMSAlPiUgdW5uZXN0KHksIHopDQpgYGANCiMjIyBCcm9vbeydhCDsnbTsmqntlbQgVGlkeSBkYXRhIOunjOuTpOq4sA0KIC0gYnJvb206OmdsYW5jZShtb2RlbCkgDQogLSBicm9vbTo6dGlkeShtb2RlbCkNCiAtIGJyb29tOjphdWdtZW50KG1vZGVsLCBkYXRhKQ0KDQpCcm9vbeydgCDqsIDsnqUg7J246riw7J2464qUIOuqqOuNuOungSDtjKjtgqTsp4Drk6TsnYQg7J207Jqp7ZW0IOuLpOyWke2VnCDrqqjrjbjrk6TroZwg7J6R64+Z7ZWc64ukLiDstZzqt7zsl5Ag7KCc6rO17ZW07KO864qUIOuqqOuNuOuTpOyXkCDrjIDtlZwg7KCV67O064qUIOuLpOydjOydmCDquYPtl4jruIzrpbwg7ZmV7J247ZW067O07J6QLiBodHRwczovL2dpdGh1Yi5jb20vdGlkeXZlcnNlL2Jyb29tDQoNCg==