문제1

Construct a Bayes’ classifier to predict “default” using “balance” and “income” variables. Estimate the test error rate using following methods.

  1. VS approach,
  2. LOOCV, and
  3. 10-fold CV.

데이터 살펴보기

데이터를 살펴보자.
총 4개 변수로 이루어져있고, 만건의 관측치가 있다.
default는 채무불이행, student는 학생여부, balance는 통장평균잔고, income은 수입을 의미한다.

default student balance income
No No 729.5265 44361.625
No Yes 817.1804 12106.135
No No 1073.5492 31767.139
No No 529.2506 35704.494
No No 785.6559 38463.496
No Yes 919.5885 7491.559

'data.frame':   10000 obs. of  4 variables:
 $ default: Factor w/ 2 levels "No","Yes": 1 1 1 1 1 1 1 1 1 1 ...
 $ student: Factor w/ 2 levels "No","Yes": 1 2 1 1 1 2 1 2 1 1 ...
 $ balance: num  730 817 1074 529 786 ...
 $ income : num  44362 12106 31767 35704 38463 ...

정규성 검정

  • Bayes classifier의 경우 정규성을 반드시 만족시켜야 하는 것은 아니다.
  • 하지만, 정규성이 만족될 때 error를 최소로 줄일 수 있고, 그 결과도 안정적이다.
  • 따라서 이왕이면 정규성이 만족되면 더 좋다고 한다.

  • 안타깝게도, 우리의 데이터는 정규성을 만족하지 못했다.
  • balance와 income에 대한 p-value=<0.000이고, Normality가 만족되지 않았다.


  • 여유가 된다면 boxcox transformation 등을 이용해 정규성이 만족되는 형태로 바꾸어준다면 분석 결과가 더 잘 나올 수 있을 것 같다.
  • 지금은, 그냥 이대로 분석을 진행하도록 하자.
p-value will be approximate in the presence of ties

    Two-sample Kolmogorov-Smirnov test

data:  rnorm(10^4) and Default$balance
D = 0.9491, p-value < 2.2e-16
alternative hypothesis: two-sided


    Two-sample Kolmogorov-Smirnov test

data:  rnorm(10^4) and Default$income
D = 1, p-value < 2.2e-16
alternative hypothesis: two-sided


Validation Set Approach.

VS - LDA

  • 먼저LDA로 분석을 해보자.
  • validation set approach는 sampling에 영향을 많이 받으므로, 그 영향을 최소화하기 위해 30번 반복측정했다.
  • LDA에 의해 분석한 결과를 아래 confusion matrix로 정리해보았다.
No Yes
No 4808.6 120.8
Yes 25.3 45.3
  • 실제 채무불이행자인데, 예측결과로는 채무불이행자가 아니라고 나온 사람이 약 121명이나 있다.
  • 적절한 분류가 아닌 것 같다.
mean(error.rate.of.lda.vs)
[1] 0.02732
  • 30번 반복측정한 error rate의 평균은 0.0273로 나온다.
  • error rate은 작게 나왔으나, confusion matrix를 고려하면 그리 좋은 분석은 아닌 것을 알 수 있다.

  • 위 그래프는 새로운 sampling 할때마다 얻은 error rate이다. 대체로 0.026주변을 맴돈다.

VS - QDA

  • 아래는 QDA로 분석한 결과다. LDA와 차이가 많이 나지 않는다.
No Yes
No 4820.9 121.7
Yes 13.0 44.5
  • LDA와 비교하여 좀 더 자세하게 보자면,
    • No -> NO로 분류한 케이스가 많아졌고,
    • Yes -> Yes로 분류한 케이스가 줄었다.
  • 은행입장에선 Yes를 Yes라고 제대로 분류하는 것이 중요한데 QDA는 이를 잘 분류하지 못하는 것 같다.
mean(error.rate.of.qda.vs)
[1] 0.02693333
  • QDA에서 30번 반복측정한 error rate의 평균은 0.0269로 나온다.

  • 위 그래프는 각 sampling에서 얻은 error rate이다.


  • error rate이 0.026~ 0.28 주변을 맴돈다. 오분류 비율이 2~3%내외이므로 꽤 작은 편이다.
  • 하지만 오비율이 작다고 다 좋은 것은 아니다.
  • 잘못 분류한 것에 대한 비용도 생각해야한다.
  • 이를 고려하면 bayes classifier로 수행한 분류분석이 아주 좋은 것은 아님을 생각해볼 수 있다.


  • LDA의 error rate가 더 낮으므로, LDA로 분석하는 것이 더 좋아보인다.
  • 실제, LDA와 QDA의 결과가 크게 차이나지 않으니 어느 것을 사용해도 상관없다. 하지만 이왕이면비교적 단순한 모형으로 classification을 수행하는 것이 좋다.


LOOCV

LOOCV- LDA

No Yes
No 9647 256
Yes 20 77
mean(Y.hat.loocv != Default[,1])
[1] 0.0276
  • LOOCV를 이용해 LDA의 error rate를 구한 결과다.
  • error rate가 0.0276이다.



LOOCV- QDA

  • LOOCV를 이용해 QDA의 error rate를 구한 결과다.
No Yes
No 9637 242
Yes 30 91
mean(Y.hat.loocv.qda != Default[,1])
[1] 0.0272
  • error rate가 0.0272이다.
  • 여기서도 역시나, LDA와 QDA의 결과사이에 큰 차이가 없다. 따라서 상대적으로 단순한 LDA모형을 사용하는 것이 좋겠다.


10-fold CV

10-fold CV - LDA

  • 10 fold 를 이용해 구한 LDA의 결과다.
No Yes
No 9647 255
Yes 20 78
error.rate.10fold
[1] 0.0275
  • error rate는 0.0275이다.

10-fold CV - QDA

No Yes
No 9647 255
Yes 20 78
error.rate.10fold.qda <- mean(predcv.qda!=Default[,1])
error.rate.10fold.qda
[1] 0.0275
  • error rate가 0.0275이다.
  • 10-fold에서 측정한 error rate은 LDA나, QDA나 그 결과가 같다.
  • 그러니 이왕이면 단순한 모형인 LDA를 사용하는 것이 좋겠다.





문제 2

Construct a naive Bayes’s classifier to predict “default” using “student”, “balance”, and “income” variables. Estimate the test error rate using following methods.

  1. VS approach,
  2. LOOCV, and
  3. 10-fold CV.

Validation Set approach

  • VS를 이용해 Naive Bayes의 error rate를 살펴보았다.
  • VS approach가 sampling에 따라 많은 영향을 받으므로, sampling에 의한 영향을 줄이기 위해 sampling을 10번 반복했다.
No Yes
No 4808.6 120.8
Yes 25.3 45.3
  • confustion matrix
mean(error.rate.nb.vs)
[1] 0.04489333
  • error rate은 0.045이다.
  • bayes classifier보다 naive bayes를 사용했을 때 error rate이 더 높다.
  • 이것은 아마, naive bayes에서 가정하는 변수들의 독립성으로 인해 정보가 손실되어 나타난 영향인 것 같다.

  • 30번 반복 측정할 때마다 얻은 error rate를 plot으로 나타내보았다.



LOOCV

  • LOOCV를 이용해 구한 Naive Bayes의 error rate를 살펴보자.
No Yes
No 9615 243
Yes 52 90
mean(error.loocv)
[1] 0.0295
  • error rate는 0.0295로 나타났다.
  • Bayes Classifier의 error rate보다 크다.
  • 이것은 아마, naive bayes에서 가정하는 변수들의 독립성으로 인해 정보가 손실되어 나타난 영향인 것 같다.


  • VS에서 얻은 error rate은 0.045인데, LOOCV에서 구한 error rate은 0.0295이다. 비교적 작다.
  • VS은 데이터의 절반만 사용하는 반면, LOOCV는 데이터를 모두 다 사용하기 때문에 나타난 현상인 것 같다.


10-fold CV

  • 10-fold CV를 이용해 Naive Bayes의 결과를 살펴보았다.
No Yes
No 9617 243
Yes 50 90
error.kfold <- pred.10fold != Default[,1]
mean(error.kfold) 
[1] 0.0293
  • error rate는 0.0293로 나타났다.
  • 이는 LOOCV와 비슷한 결과다. good!



분석결과 종합

나 같으면 LDA를 이용하여 classification을 할 것 같다. error rate이 작고, 모형도 비교적 단순하기 때문이다.


Appendix - Code

> ## ----include=FALSE, echo=FALSE-------------------------------------------
> knitr::opts_chunk$set(comment = "", prompt = TRUE, out.width = 400, fig.height = 4, fig.width = 4)
> library(knitr)
> Sys.setlocale("LC_ALL", "eng")
> setwd("E:/Dropbox/00.2018/01.2018_1_semester/01.DataMining/04.HW/HW3")
> 
> ## ----echo=FALSE----------------------------------------------------------
> library(ISLR)
> data(Default)
> kable(head(Default), caption = "head(Default)")
> str(Default)
> 
> ## ----echo=FALSE----------------------------------------------------------
> library(MVN)
> ks.test(x = rnorm(10 ^ 4), Default$balance, alternative = "two.sided")
> ks.test(x = rnorm(10 ^ 4), Default$income, alternative = "two.sided")
> 
> 
> ## ----echo=FALSE----------------------------------------------------------
> 
> n <- dim(Default)[1]
> 
> table1 <- array(NA, c(2, 2, 30))
> error.rate.of.lda.vs <- rep(NA, 30)
> 
> for (i in 1:30) {
+   set.seed(i)
+   train <- sample(1:n, n / 2)
+   default.train <- Default[train, ]
+   default.test <- Default[-train, ]
+   fit2 <- lda(default~ balance + income, data = Default, subset = train)
+   pred.lda <- predict(fit2, Default[-train, ])$class
+   table1[, , i] <- table(pred = pred.lda, true = Default[-train, 1])
+   error.rate.of.lda.vs[i] <- mean(pred.lda != Default[-train, 1])
+ }
> 
> ## ----echo=FALSE----------------------------------------------------------
> # apply(table1,1:2,mean)
> 
> tmp1 <- apply(table, 1:2, mean)
> dimnames(tmp1) <- list(c("No", "Yes"), c("No", "Yes"))
> kable(tmp1, "html", digits = 1) %>%
+   kable_styling(full_width = F)
> 
> ## ------------------------------------------------------------------------
> mean(error.rate.of.lda.vs)
> 
> 
> ## ----echo=FALSE----------------------------------------------------------
> plot(
+   error.rate.of.lda.vs, type = "l",
+   ylim = c(min(error.rate.of.lda.vs) * 0.9, max(error.rate.of.lda.vs) * 1.1),
+   ylab = "Error Rate",
+   xlab = "K"
+ )
> abline(h = mean(error.rate.of.lda.vs), lty = 2, col = 2)
> 
> ## ----echo=FALSE----------------------------------------------------------
> 
> table2 <- array(NA, c(2, 2, 30))
> error.rate.of.qda.vs <- rep(NA, 30)
> 
> for (i in 1:30) {
+   set.seed(i)
+   train <- sample(1:n, n / 2)
+   fit2 <- qda(default~ balance + income, data = Default, subset = train)
+   pred.qda <- predict(fit2, Default[-train, ])$class
+   table2[, , i] <- table(pred = pred.qda, true = Default[-train, 1])
+   error.rate.of.qda.vs[i] <- mean(pred.qda != Default[-train, 1])
+ }
> 
> ## ----echo=FALSE----------------------------------------------------------
> # apply(table2,1:2, mean)
> 
> tmp2 <- apply(table2, 1:2, mean)
> dimnames(tmp2) <- list(c("No", "Yes"), c("No", "Yes"))
> kable(tmp2, "html", digits = 1) %>%
+   kable_styling(full_width = F)
> 
> ## ------------------------------------------------------------------------
> mean(error.rate.of.qda.vs)
> 
> ## ----echo=FALSE----------------------------------------------------------
> plot(
+   error.rate.of.qda.vs, type = "l",
+   ylim = c(min(error.rate.of.qda.vs) * 0.9, max(error.rate.of.qda.vs) * 1.1),
+   ylab = "Error Rate",
+   xlab = "K"
+ )
> abline(h = mean(error.rate.of.qda.vs), lty = 2, col = 2)
> 
> ## ------------------------------------------------------------------------
> Y.pred.loocv <- rep(NA, n)
> pprob <- tmp.cv.lda$posterior
> 
> for (i in 1:n) Y.pred.loocv[i] <- which.max(pprob[i, ])
> Y.hat.loocv <- character(length(Y.pred.loocv))
> Y.hat.loocv[Y.pred.loocv == 1] <- "No"
> Y.hat.loocv[Y.pred.loocv == 2] <- "Yes"
> Y.hat.loocv <- factor(Y.hat.loocv, levels = c("No", "Yes"))
> 
> kable(table(pred = Y.hat.loocv, true = Default[, 1]), "html") %>%
+   kable_styling(full_width = F)
> 
> ## ------------------------------------------------------------------------
> mean(Y.hat.loocv != Default[, 1])
> 
> ## ----echo=FALSE----------------------------------------------------------
> Y.pred.loocv.qda <- rep(NA, n)
> pprob.qda <- tmp.cv.qda$posterior
> 
> for (i in 1:n) Y.pred.loocv.qda[i] <- which.max(pprob.qda[i, ])
> Y.hat.loocv.qda <- character(length(Y.pred.loocv.qda))
> Y.hat.loocv.qda[Y.pred.loocv.qda == 1] <- "No"
> Y.hat.loocv.qda[Y.pred.loocv.qda == 2] <- "Yes"
> 
> kable(table(pred = Y.hat.loocv.qda, true = Default[, 1]), "html") %>%
+   kable_styling(full_width = F)
> 
> ## ------------------------------------------------------------------------
> mean(Y.hat.loocv.qda != Default[, 1])
> 
> ## ----echo=FALSE----------------------------------------------------------
> # K-fold CV
> K <- 10
> ind <- (1:n) %% K + 1
> set.seed(1 * i)
> folds <- sample(ind, n)
> predcv <- character(n)
> for (k in 1:K) {
+   fit <- lda(default~income + balance, data = Default, subset = which(ind != k))
+   predcv[ind == k] <- as.character(predict(fit, Default[ind == k, ])$class)
+ }
> table.10fold.lda <- table(pred = predcv, true = Default[, 1])
> kable(table.10fold.lda, "html") %>%
+   kable_styling(full_width = F)
> 
> 
> ## ------------------------------------------------------------------------
> error.rate.10fold <- mean(predcv != Default[, 1])
> error.rate.10fold
> 
> ## ----echo=FALSE----------------------------------------------------------
> # K-fold CV
> K <- 10
> ind <- (1:n) %% K + 1
> set.seed(1 * i)
> folds <- sample(ind, n)
> predcv.qda <- character(n)
> for (k in 1:K) {
+   fit <- lda(default~income + balance, data = Default, subset = which(ind != k))
+   predcv.qda[ind == k] <- as.character(predict(fit, Default[ind == k, ])$class)
+ }
> table.10fold.qda <- table(pred = predcv.qda, true = Default[, 1])
> kable(table.10fold.qda, "html") %>%
+   kable_styling(full_width = F)
> 
> 
> ## ------------------------------------------------------------------------
> error.rate.10fold.qda <- mean(predcv.qda != Default[, 1])
> error.rate.10fold.qda
> 
> ## ----echo=FALSE, message=FALSE, warning=FALSE----------------------------
> library(MASS)
> library(naivebayes)
> library(e1071)
> 
> error.rate.nb.vs <- rep(NA, 30)
> n <- dim(Default)[1]
> table <- array(NA, c(2, 2, 30))
> 
> for (i in 1:30) {
+   set.seed(i)
+   train <- sample(1:n, n / 2)
+   fit.nb.vs <- naiveBayes(default ~ ., data = Default[train, ])
+   pred.nb.vs <- predict(fit.nb.vs, newdata = Default[-train, ])
+   table[, , i] <- as.matrix(table(pred = pred.nb.vs, true = Default[-train, 1]))
+   error.rate.nb.vs[i] <- mean(pred1 != Default[-train, ]$default)
+ }
> 
> 
> ## ----echo=FALSE----------------------------------------------------------
> tmp <- apply(table, 1:2, mean)
> dimnames(tmp) <- list(c("No", "Yes"), c("No", "Yes"))
> kable(tmp, "html", digits = 1) %>%
+   kable_styling(full_width = F)
> 
> ## ------------------------------------------------------------------------
> mean(error.rate.nb.vs)
> 
> ## ----echo=FALSE----------------------------------------------------------
> plot(
+   error.rate.nb.vs, type = "l",
+   ylim = c(min(error.rate) * 0.9, max(error.rate) * 1.1),
+   ylab = "Error Rate",
+   xlab = "K"
+ )
> abline(h = mean(error.rate), lty = 2, col = 2)
> 
> ## ----echo=FALSE, message=FALSE, warning=FALSE----------------------------
> 
> n <- nrow(Default)
> error.loocv <- vector(mode = "logical", n)
> pred.nb.loocv <- rep(NA, n)
> 
> for (i in 1:n) {
+   fit.loocv <- naiveBayes(default ~ ., data = Default[-i, ])
+   pred2 <- predict(fit.loocv, newdata = Default[i, ])
+   pred.nb.loocv[i] <- as.character(pred2)
+   error.loocv[i] <- (pred2 != Default[i, ]$default)
+ }
> 
> 
> ## ----echo=FALSE----------------------------------------------------------
> tab.loocv <- table(pred = pred.nb.loocv, true = Default[, 1])
> kable(tab.loocv, "html") %>%
+   kable_styling(full_width = F)
> 
> ## ------------------------------------------------------------------------
> 
> mean(error.loocv)
> 
> ## ----echo=FALSE----------------------------------------------------------
> 
> library(leaps)
> 
> k <- 10
> error.kfold <- rep(NA, k)
> n <- dim(Default)[1]
> set.seed(1)
> 
> pred.10fold <- character(n)
> folds <- sample(1:k, nrow(Default), replace = TRUE, prob = rep(1 / k, k))
> 
> for (i in 1:k) {
+   fit.10fold <- naive_bayes(default ~ ., data = Default[folds != i, ])
+   pred.10fold[folds == i] <- as.character(predict(fit.10fold, newdata = Default[folds == i, ]))
+ }
> 
> 
> ## ----echo=FALSE----------------------------------------------------------
> tab1 <- table(pred = pred.10fold, true = Default[, 1])
> kable(tab1, "html") %>%
+   kable_styling(full_width = F)
> 
> ## ------------------------------------------------------------------------
> error.kfold <- pred.10fold != Default[, 1]
> mean(error.kfold)
LS0tDQp0aXRsZTogIkRhdGEgbWluaW5nIEhXMzogWWVyaW0gTGltIg0KQXV0aG9yOiAiWWVyaW0gTGltIg0KRGF0ZTogIjIwMTjrhYQgNeyblCAxOOydvCINCm91dHB1dDogDQogIGh0bWxfbm90ZWJvb2s6DQogICAgdG9jOiB0cnVlDQogIHByZXR0eWRvYzo6aHRtbF9wcmV0dHk6DQogICAgaGlnaGxpZ2h0OiBnaXRodWINCiAgICB0aGVtZTogYXJjaGl0ZWN0DQogICAgdG9jOiB5ZXMNCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHllcw0KLS0tDQpgYGB7ciBpbmNsdWRlPUZBTFNFLCBlY2hvPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGNvbW1lbnQgPSAiIiAsIHByb21wdCA9IFRSVUUsIG91dC53aWR0aCA9IDMwMCwgZmlnLmhlaWdodCA9IDMsIGZpZy53aWR0aCA9IDMpDQpsaWJyYXJ5KGtuaXRyKQ0KU3lzLnNldGxvY2FsZSgiTENfQUxMIiwgImVuZyIpDQpzZXR3ZCgiRTovRHJvcGJveC8wMC4yMDE4LzAxLjIwMThfMV9zZW1lc3Rlci8wMS5EYXRhTWluaW5nLzA0LkhXL0hXMyIpDQpgYGAgDQoNCiMg66y47KCcMQ0KDQpDb25zdHJ1Y3QgYSBCYXllcycgY2xhc3NpZmllciB0byBwcmVkaWN0ICJkZWZhdWx0IiB1c2luZyAiYmFsYW5jZSIgYW5kICJpbmNvbWUiIHZhcmlhYmxlcy4gRXN0aW1hdGUgdGhlIHRlc3QgZXJyb3IgcmF0ZSB1c2luZyBmb2xsb3dpbmcgbWV0aG9kcy4NCg0KKGkpIFZTIGFwcHJvYWNoLCANCihpaSkgTE9PQ1YsIGFuZCANCihpaWkpIDEwLWZvbGQgQ1YuDQoNCg0KDQojIyDrjbDsnbTthLAg7IK07Y6067O06riwIA0K642w7J207YSw66W8IOyCtO2OtOuztOyekC4gPGJyLz4NCuy0nSA06rCcIOuzgOyImOuhnCDsnbTro6jslrTsoLjsnojqs6AsIOunjOqxtOydmCDqtIDsuKHsuZjqsIAg7J6I64ukLiA8YnIvPg0KZGVmYXVsdOuKlCDssYTrrLTrtojsnbTtloksIHN0dWRlbnTripQg7ZWZ7IOd7Jes67aALCBiYWxhbmNl64qUIO2Gteyepe2Pieq3oOyelOqzoCwgaW5jb21l7J2AIOyImOyeheydhCDsnZjrr7jtlZzri6QuIA0KDQpgYGB7ciBlY2hvPUZBTFNFfQ0KbGlicmFyeShJU0xSKQ0KZGF0YShEZWZhdWx0KQ0Ka2FibGUoaGVhZChEZWZhdWx0KSwgY2FwdGlvbiA9ICJoZWFkKERlZmF1bHQpIikNCnN0cihEZWZhdWx0KQ0KYGBgDQoNCg0KIyMg7KCV6rec7ISxIOqygOyglQ0KDQotIEJheWVzIGNsYXNzaWZpZXLsnZgg6rK97JqwIOygleq3nOyEseydhCDrsJjrk5zsi5wg66eM7KGx7Iuc7Lyc7JW8IO2VmOuKlCDqsoPsnYAg7JWE64uI64ukLiANCi0gKirtlZjsp4Drp4wsIOygleq3nOyEseydtCDrp4zsobHrkKAg65WMIGVycm9y66W8IOy1nOyGjOuhnCDspITsnbwg7IiYIOyeiOqzoCwg6re4IOqysOqzvOuPhCDslYjsoJXsoIHsnbTri6QuKiogPGJyLz4NCi0g65Sw65287IScIOydtOyZleydtOuptCDsoJXqt5zshLHsnbQg66eM7KGx65CY66m0IOuNlCDsoovri6Tqs6Ag7ZWc64ukLiANCjxici8+DQoNCi0g7JWI7YOA6rmd6rKM64+ELCDsmrDrpqzsnZgg642w7J207YSw64qUIOygleq3nOyEseydhCDrp4zsobHtlZjsp4Ag66q77ZaI64ukLiANCi0gYmFsYW5jZeyZgCBpbmNvbWXsl5Ag64yA7ZWcIHAtdmFsdWU9PDAuMDAw7J206rOgLCBOb3JtYWxpdHnqsIAg66eM7KGx65CY7KeAIOyViuyVmOuLpC4gDQoNCjxici8+DQoNCi0g7Jes7Jyg6rCAIOuQnOuLpOuptCBib3hjb3ggdHJhbnNmb3JtYXRpb24g65Ox7J2EIOydtOyaqe2VtCDsoJXqt5zshLHsnbQg66eM7KGx65CY64qUIO2Yle2DnOuhnCDrsJTqvrjslrTspIDri6TrqbQg67aE7ISdIOqysOqzvOqwgCDrjZQg7J6YIOuCmOyYrCDsiJgg7J6I7J2EIOqygyDqsJnri6QuIA0KLSDsp4DquIjsnYAsIOq3uOuDpSDsnbTrjIDroZwg67aE7ISd7J2EIOynhO2Wie2VmOuPhOuhnSDtlZjsnpAuIA0KDQogDQpgYGB7ciBlY2hvPUZBTFNFfQ0KbGlicmFyeShNVk4pDQprcy50ZXN0KHg9cm5vcm0oMTBeNCksRGVmYXVsdCRiYWxhbmNlLGFsdGVybmF0aXZlPSd0d28uc2lkZWQnKQ0Ka3MudGVzdCh4PXJub3JtKDEwXjQpLERlZmF1bHQkaW5jb21lLGFsdGVybmF0aXZlPSd0d28uc2lkZWQnKQ0KDQpgYGANCg0KDQoNCg0KPGJyLz4NCg0KIyMgVmFsaWRhdGlvbiBTZXQgQXBwcm9hY2guDQoNCiMjIyBWUyAtIExEQQ0KLSDrqLzsoIBMREHroZwg67aE7ISd7J2EIO2VtOuztOyekC4gDQotIHZhbGlkYXRpb24gc2V0IGFwcHJvYWNo64qUIHNhbXBsaW5n7JeQIOyYge2WpeydhCDrp47snbQg67Cb7Jy866+A66GcLCDqt7gg7JiB7Zal7J2EIOy1nOyGjO2ZlO2VmOq4sCDsnITtlbQgMzDrsogg67CY67O17Lih7KCV7ZaI64ukLiAgDQotIExEQeyXkCDsnZjtlbQg67aE7ISd7ZWcIOqysOqzvOulvCDslYTrnpggY29uZnVzaW9uIG1hdHJpeOuhnCDsoJXrpqztlbTrs7TslZjri6QuIA0KDQoNCmBgYHtyIGVjaG89RkFMU0V9DQoNCm4gPC0gZGltKERlZmF1bHQpWzFdDQoNCnRhYmxlMSA8LSBhcnJheShOQSxjKDIsMiwzMCkpDQplcnJvci5yYXRlLm9mLmxkYS52cyA8LSByZXAoTkEsMzApDQoNCmZvcihpIGluIDE6MzApew0Kc2V0LnNlZWQoaSkNCnRyYWluIDwtIHNhbXBsZSgxOm4sIG4vMikNCmRlZmF1bHQudHJhaW4gPC0gRGVmYXVsdFt0cmFpbixdDQpkZWZhdWx0LnRlc3QgPC0gRGVmYXVsdFstdHJhaW4sXQ0KZml0MjwtbGRhKGRlZmF1bHR+IGJhbGFuY2UraW5jb21lLGRhdGE9RGVmYXVsdCxzdWJzZXQ9dHJhaW4pDQpwcmVkLmxkYTwtcHJlZGljdChmaXQyLERlZmF1bHRbLXRyYWluLF0pJGNsYXNzDQp0YWJsZTFbLCxpXSA8LSB0YWJsZShwcmVkID0gcHJlZC5sZGEsIHRydWU9IERlZmF1bHRbLXRyYWluLDFdKQ0KZXJyb3IucmF0ZS5vZi5sZGEudnNbaV0gPC0gbWVhbihwcmVkLmxkYSE9RGVmYXVsdFstdHJhaW4sMV0pDQp9DQpgYGANCg0KDQoNCmBgYHtyIGVjaG89RkFMU0V9DQojIGFwcGx5KHRhYmxlMSwxOjIsbWVhbikNCg0KdG1wMSA8LSBhcHBseSh0YWJsZSwgMToyICxtZWFuKQ0KZGltbmFtZXModG1wMSkgPC0gbGlzdChjKCJObyIsIlllcyIpLGMoIk5vIiwiWWVzIikpDQprYWJsZSh0bXAxLCAiaHRtbCIsIGRpZ2l0cyA9IDEpICU+JSANCiAga2FibGVfc3R5bGluZyhmdWxsX3dpZHRoPUYpDQpgYGANCg0KLSDsi6TsoJwg7LGE66y067aI7J207ZaJ7J6Q7J24642wLCDsmIjsuKHqsrDqs7zroZzripQg7LGE66y067aI7J207ZaJ7J6Q6rCAICoq7JWE64uI65286rOgKiog64KY7JioIOyCrOuejOydtCDslb0gMTIx66qF7J2064KYIOyeiOuLpC4gDQotIOyggeygiO2VnCDrtoTrpZjqsIAg7JWE64uMIOqygyDqsJnri6QuIA0KDQpgYGB7cn0NCm1lYW4oZXJyb3IucmF0ZS5vZi5sZGEudnMpDQoNCmBgYA0KLSAzMOuyiCDrsJjrs7XsuKHsoJXtlZwgZXJyb3IgcmF0ZeydmCDtj4nqt6DsnYAgICoqMC4wMjczKirroZwg64KY7Jio64ukLg0KLSBlcnJvciByYXRl7J2AIOyekeqyjCDrgpjsmZTsnLzrgpgsIGNvbmZ1c2lvbiBtYXRyaXjrpbwg6rOg66Ck7ZWY66m0IOq3uOumrCDsoovsnYAg67aE7ISd7J2AIOyVhOuLjCDqsoPsnYQg7JWMIOyImCDsnojri6QuIA0KDQoNCg0KYGBge3IgZWNobz1GQUxTRX0NCnBsb3QoZXJyb3IucmF0ZS5vZi5sZGEudnMgLHR5cGU9ImwiLCANCiAgICAgeWxpbT1jKG1pbihlcnJvci5yYXRlLm9mLmxkYS52cykqMC45LG1heChlcnJvci5yYXRlLm9mLmxkYS52cykqMS4xKSwNCiAgICAgeWxhYj0iRXJyb3IgUmF0ZSIsDQogICAgIHhsYWI9IksiKQ0KYWJsaW5lKGg9bWVhbihlcnJvci5yYXRlLm9mLmxkYS52cyksIGx0eT0yLCBjb2w9MikNCmBgYA0KLSDsnIQg6re4656Y7ZSE64qUIOyDiOuhnOyatCBzYW1wbGluZyDtlaDrlYzrp4jri6Qg7Ja77J2AIGVycm9yIHJhdGXsnbTri6QuIOuMgOyytOuhnCAwLjAyNuyjvOuzgOydhCDrp7Trj4jri6QuIA0KPGJyLz4NCg0KIyMjIFZTIC0gUURBDQotIOyVhOuemOuKlCBRREHroZwg67aE7ISd7ZWcIOqysOqzvOuLpC4gTERB7JmAIOywqOydtOqwgCDrp47snbQg64KY7KeAIOyViuuKlOuLpC4gDQpgYGB7ciBlY2hvPUZBTFNFfQ0KDQp0YWJsZTIgPC0gYXJyYXkoTkEsYygyLDIsMzApKQ0KZXJyb3IucmF0ZS5vZi5xZGEudnMgPC0gcmVwKE5BLDMwKQ0KDQpmb3IoaSBpbiAxOjMwKXsNCiAgc2V0LnNlZWQoaSkNCiAgdHJhaW4gPC0gc2FtcGxlKDE6biwgbi8yKQ0KICBmaXQyPC1xZGEoZGVmYXVsdH4gYmFsYW5jZStpbmNvbWUsZGF0YT1EZWZhdWx0LHN1YnNldD10cmFpbikNCiAgcHJlZC5xZGE8LXByZWRpY3QoZml0MixEZWZhdWx0Wy10cmFpbixdKSRjbGFzcw0KICB0YWJsZTJbLCxpXSA8LSB0YWJsZShwcmVkPSBwcmVkLnFkYSx0cnVlID0gRGVmYXVsdFstdHJhaW4sMV0pDQogIGVycm9yLnJhdGUub2YucWRhLnZzW2ldIDwtIG1lYW4ocHJlZC5xZGEhPURlZmF1bHRbLXRyYWluLDFdKQ0KfQ0KYGBgDQoNCmBgYHtyIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIGFwcGx5KHRhYmxlMiwxOjIsIG1lYW4pDQoNCnRtcDIgPC0gYXBwbHkodGFibGUyLCAxOjIgLG1lYW4pDQpkaW1uYW1lcyh0bXAyKSA8LSBsaXN0KGMoIk5vIiwiWWVzIiksYygiTm8iLCJZZXMiKSkNCmthYmxlKHRtcDIsICJodG1sIiwgZGlnaXRzID0gMSkgJT4lIA0KICBrYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGg9RikNCmBgYA0KLSBMREHsmYAg67mE6rWQ7ZWY7JesIOyigCDrjZQg7J6Q7IS47ZWY6rKMIOuztOyekOuptCwgDQogICAgKyBObyAtPiBOT+uhnCDrtoTrpZjtlZwg7LyA7J207Iqk6rCAIOunjuyVhOyhjOqzoCwgDQogICAgKyBZZXMgLT4gWWVz66GcIOu2hOulmO2VnCDsvIDsnbTsiqTqsIAg7KSE7JeI64ukLiANCi0g7J2A7ZaJ7J6F7J6l7JeQ7ISgIFllc+ulvCBZZXPrnbzqs6Ag7KCc64yA66GcIOu2hOulmO2VmOuKlCDqsoPsnbQg7KSR7JqU7ZWc642wIFFEQeuKlCDsnbTrpbwg7J6YIOu2hOulmO2VmOyngCDrqrvtlZjripQg6rKDIOqwmeuLpC4gDQoNCg0KYGBge3IgfQ0KbWVhbihlcnJvci5yYXRlLm9mLnFkYS52cykNCmBgYA0KLSBRREHsl5DshJwgMzDrsogg67CY67O17Lih7KCV7ZWcIGVycm9yIHJhdGXsnZgg7Y+J6reg7J2AICAqKjAuMDI2OSoq66GcIOuCmOyYqOuLpC4NCg0KYGBge3IgZWNobz1GQUxTRX0NCnBsb3QoZXJyb3IucmF0ZS5vZi5xZGEudnMgLHR5cGU9ImwiLCANCiAgICAgeWxpbT1jKG1pbihlcnJvci5yYXRlLm9mLnFkYS52cykqMC45LG1heChlcnJvci5yYXRlLm9mLnFkYS52cykqMS4xKSwNCiAgICAgeWxhYj0iRXJyb3IgUmF0ZSIsDQogICAgIHhsYWI9IksiKQ0KYWJsaW5lKGg9bWVhbihlcnJvci5yYXRlLm9mLnFkYS52cyksIGx0eT0yLCBjb2w9MikNCmBgYA0KLSDsnIQg6re4656Y7ZSE64qUIOqwgSAgc2FtcGxpbmfsl5DshJwg7Ja77J2AIGVycm9yIHJhdGXsnbTri6QuIA0KDQogPGJyLz4NCg0KLSBlcnJvciByYXRl7J20IDAuMDI2fiAwLjI4IOyjvOuzgOydhCDrp7Trj4jri6QuIOyYpOu2hOulmCDruYTsnKjsnbQgMn4zJeuCtOyZuOydtOuvgOuhnCDqvaQg7J6R7J2AIO2OuOydtOuLpC4gIA0KLSDtlZjsp4Drp4wg7Jik67mE7Jyo7J20IOyekeuLpOqzoCDri6Qg7KKL7J2AIOqyg+ydgCDslYTri4jri6QuIA0KLSDsnpjrqrsg67aE66WY7ZWcIOqyg+yXkCDrjIDtlZwg67mE7Jqp64+EIOyDneqwge2VtOyVvO2VnOuLpC4gDQotIOydtOulvCDqs6DroKTtlZjrqbQgYmF5ZXMgY2xhc3NpZmllcuuhnCDsiJjtlontlZwg67aE66WY67aE7ISd7J20ICoq7JWE7KO8IOyii+ydgCDqsoPsnYAg7JWE64uYKirsnYQg7IOd6rCB7ZW067O8IOyImCDsnojri6QuIA0KDQoNCjxici8+DQoNCi0gTERB7J2YIGVycm9yIHJhdGXqsIAg642UIOuCruycvOuvgOuhnCwgTERB66GcIOu2hOyEne2VmOuKlCDqsoPsnbQg642UIOyii+yVhOuztOyduOuLpC4gDQotICoq7Iuk7KCcLCAgTERB7JmAIFFEQeydmCDqsrDqs7zqsIAg7YGs6rKMIOywqOydtOuCmOyngCDslYrsnLzri4gg7Ja064qQIOqyg+ydhCDsgqzsmqntlbTrj4Qg7IOB6rSA7JeG64ukLiDtlZjsp4Drp4wg7J207JmV7J2066m067mE6rWQ7KCBIOuLqOyInO2VnCDrqqjtmJXsnLzroZwgY2xhc3NpZmljYXRpb27snYQg7IiY7ZaJ7ZWY64qUIOqyg+ydtCDsoovri6QuKiogDQoNCg0KPGJyLz4NCg0KIyMgTE9PQ1YNCiMjIyBMT09DVi0gTERBDQpgYGB7cn0NClkucHJlZC5sb29jdiA8LSByZXAoTkEsbikNCnBwcm9iIDwtIHRtcC5jdi5sZGEkcG9zdGVyaW9yDQoNCmZvcihpIGluIDE6bikgWS5wcmVkLmxvb2N2W2ldPC13aGljaC5tYXgocHByb2JbaSxdKQ0KWS5oYXQubG9vY3Y8LWNoYXJhY3RlcihsZW5ndGgoWS5wcmVkLmxvb2N2KSkNClkuaGF0Lmxvb2N2W1kucHJlZC5sb29jdj09MV08LSJObyINClkuaGF0Lmxvb2N2W1kucHJlZC5sb29jdj09Ml08LSJZZXMiDQpZLmhhdC5sb29jdiA8LSBmYWN0b3IoWS5oYXQubG9vY3YsIGxldmVscz1jKCJObyIsIlllcyIpKQ0KDQprYWJsZSh0YWJsZShwcmVkPVkuaGF0Lmxvb2N2LCB0cnVlPURlZmF1bHRbLDFdKSwgImh0bWwiKSAlPiUgDQogIGthYmxlX3N0eWxpbmcoZnVsbF93aWR0aD1GKQ0KYGBgDQoNCg0KYGBge3J9DQptZWFuKFkuaGF0Lmxvb2N2ICE9IERlZmF1bHRbLDFdKQ0KYGBgDQotIExPT0NW66W8IOydtOyaqe2VtCBMREHsnZggZXJyb3IgcmF0ZeulvCDqtaztlZwg6rKw6rO864ukLiANCi0gZXJyb3IgcmF0ZeqwgCAqKjAuMDI3Nioq7J2064ukLiANCg0KPGJyLz48YnIvPg0KDQojIyMgTE9PQ1YtIFFEQQ0KLSBMT09DVuulvCDsnbTsmqntlbQgUURB7J2YIGVycm9yIHJhdGXrpbwg6rWs7ZWcIOqysOqzvOuLpC4gDQoNCmBgYHtyIGVjaG89RkFMU0V9DQpZLnByZWQubG9vY3YucWRhIDwtIHJlcChOQSxuKQ0KcHByb2IucWRhIDwtIHRtcC5jdi5xZGEkcG9zdGVyaW9yDQoNCmZvcihpIGluIDE6bikgWS5wcmVkLmxvb2N2LnFkYVtpXTwtd2hpY2gubWF4KHBwcm9iLnFkYVtpLF0pDQpZLmhhdC5sb29jdi5xZGE8LWNoYXJhY3RlcihsZW5ndGgoWS5wcmVkLmxvb2N2LnFkYSkpDQpZLmhhdC5sb29jdi5xZGFbWS5wcmVkLmxvb2N2LnFkYT09MV08LSJObyINClkuaGF0Lmxvb2N2LnFkYVtZLnByZWQubG9vY3YucWRhPT0yXTwtIlllcyINCg0Ka2FibGUodGFibGUocHJlZD1ZLmhhdC5sb29jdi5xZGEsdHJ1ZT1EZWZhdWx0WywxXSksICJodG1sIikgJT4lIA0KICBrYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGg9RikNCmBgYA0KDQpgYGB7cn0NCm1lYW4oWS5oYXQubG9vY3YucWRhICE9IERlZmF1bHRbLDFdKQ0KYGBgDQotIGVycm9yIHJhdGXqsIAgKiowLjAyNzIqKuydtOuLpC4gDQotIOyXrOq4sOyEnOuPhCDsl63si5zrgpgsIExEQeyZgCBRREHsnZgg6rKw6rO87IKs7J207JeQIO2BsCDssKjsnbTqsIAg7JeG64ukLiDrlLDrnbzshJwg7IOB64yA7KCB7Jy866GcIOuLqOyInO2VnCBMREHrqqjtmJXsnYQg7IKs7Jqp7ZWY64qUIOqyg+ydtCDsoovqsqDri6QuIA0KDQo8YnIvPg0KDQojIyAxMC1mb2xkIENWIA0KIyMjIDEwLWZvbGQgQ1YgLSBMREENCi0gMTAgZm9sZCDrpbwg7J207Jqp7ZW0IOq1rO2VnCBMREHsnZgg6rKw6rO864ukLiANCmBgYHtyIGVjaG89RkFMU0V9DQojSy1mb2xkIENWDQpLPC0xMA0KaW5kPC0oMTpuKSUlSysxDQpzZXQuc2VlZCgxKmkpDQpmb2xkczwtc2FtcGxlKGluZCxuKQ0KcHJlZGN2PC1jaGFyYWN0ZXIobikNCmZvciAoayBpbiAxOkspew0KCWZpdDwtbGRhKGRlZmF1bHR+aW5jb21lK2JhbGFuY2UsZGF0YT1EZWZhdWx0LHN1YnNldD13aGljaChpbmQhPWspKQ0KCXByZWRjdltpbmQ9PWtdPC1hcy5jaGFyYWN0ZXIocHJlZGljdChmaXQsRGVmYXVsdFtpbmQ9PWssXSkkY2xhc3MpDQp9DQp0YWJsZS4xMGZvbGQubGRhIDwtIHRhYmxlKHByZWQ9IHByZWRjdix0cnVlID0gRGVmYXVsdFssMV0pDQprYWJsZSh0YWJsZS4xMGZvbGQubGRhLCAiaHRtbCIpICU+JSANCiAga2FibGVfc3R5bGluZyhmdWxsX3dpZHRoPUYpDQoNCmBgYA0KDQpgYGB7cn0NCmVycm9yLnJhdGUuMTBmb2xkIDwtIG1lYW4ocHJlZGN2IT1EZWZhdWx0WywxXSkNCmVycm9yLnJhdGUuMTBmb2xkDQpgYGANCi0gZXJyb3IgcmF0ZeuKlCAqKjAuMDI3NSoq7J2064ukLiANCg0KDQoNCiMjIyAxMC1mb2xkIENWIC0gUURBDQpgYGB7ciBlY2hvPUZBTFNFfQ0KI0stZm9sZCBDVg0KSzwtMTANCmluZDwtKDE6biklJUsrMQ0Kc2V0LnNlZWQoMSppKQ0KZm9sZHM8LXNhbXBsZShpbmQsbikNCnByZWRjdi5xZGE8LWNoYXJhY3RlcihuKQ0KZm9yIChrIGluIDE6Syl7DQoJZml0PC1sZGEoZGVmYXVsdH5pbmNvbWUrYmFsYW5jZSxkYXRhPURlZmF1bHQsc3Vic2V0PXdoaWNoKGluZCE9aykpDQoJcHJlZGN2LnFkYVtpbmQ9PWtdPC1hcy5jaGFyYWN0ZXIocHJlZGljdChmaXQsRGVmYXVsdFtpbmQ9PWssXSkkY2xhc3MpDQp9DQp0YWJsZS4xMGZvbGQucWRhIDwtIHRhYmxlKHByZWQ9IHByZWRjdi5xZGEsdHJ1ZSA9IERlZmF1bHRbLDFdKQ0Ka2FibGUodGFibGUuMTBmb2xkLnFkYSwgImh0bWwiKSAlPiUgDQogIGthYmxlX3N0eWxpbmcoZnVsbF93aWR0aD1GKQ0KDQpgYGANCg0KYGBge3J9DQplcnJvci5yYXRlLjEwZm9sZC5xZGEgPC0gbWVhbihwcmVkY3YucWRhIT1EZWZhdWx0WywxXSkNCmVycm9yLnJhdGUuMTBmb2xkLnFkYQ0KYGBgDQotIGVycm9yIHJhdGXqsIAgKiowLjAyNzUqKuydtOuLpC4gDQotIDEwLWZvbGTsl5DshJwg7Lih7KCV7ZWcIGVycm9yIHJhdGXsnYAgTERB64KYLCBRREHrgpgg6re4IOqysOqzvOqwgCDqsJnri6QuIA0KLSDqt7jrn6zri4gg7J207JmV7J2066m0IOuLqOyInO2VnCDrqqjtmJXsnbggTERB66W8IOyCrOyaqe2VmOuKlCDqsoPsnbQg7KKL6rKg64ukLiANCg0KDQo8YnIvPg0KPGJyLz4NCg0KKioqDQoNCjxici8+DQoNCiMg66y47KCcIDINCg0KQ29uc3RydWN0IGEgbmFpdmUgQmF5ZXMncyBjbGFzc2lmaWVyIHRvIHByZWRpY3QgImRlZmF1bHQiIHVzaW5nICJzdHVkZW50IiwgImJhbGFuY2UiLCBhbmQgImluY29tZSIgdmFyaWFibGVzLiBFc3RpbWF0ZSB0aGUgdGVzdCBlcnJvciByYXRlIHVzaW5nIGZvbGxvd2luZyBtZXRob2RzLg0KDQooaSkgVlMgYXBwcm9hY2gsIA0KKGlpKSBMT09DViwgYW5kIA0KKGlpaSkgMTAtZm9sZCBDVi4NCg0KIyMgVmFsaWRhdGlvbiBTZXQgYXBwcm9hY2gNCi0gVlPrpbwg7J207Jqp7ZW0IE5haXZlIEJheWVz7J2YIGVycm9yIHJhdGXrpbwg7IK07Y6067O07JWY64ukLiANCi0gVlMgYXBwcm9hY2jqsIAgc2FtcGxpbmfsl5Ag65Sw6528IOunjuydgCDsmIHtlqXsnYQg67Cb7Jy866+A66GcLCBzYW1wbGluZ+yXkCDsnZjtlZwg7JiB7Zal7J2EIOykhOydtOq4sCDsnITtlbQgc2FtcGxpbmfsnYQgMTDrsogg67CY67O17ZaI64ukLiANCg0KYGBge3IgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmxpYnJhcnkoTUFTUykNCmxpYnJhcnkobmFpdmViYXllcykNCmxpYnJhcnkoZTEwNzEpDQoNCmVycm9yLnJhdGUubmIudnMgPC0gcmVwKE5BLCAzMCkNCm4gPC0gZGltKERlZmF1bHQpWzFdDQp0YWJsZSA8LSBhcnJheShOQSwgYygyLDIsMzApKQ0KDQpmb3IoaSBpbiAxOjMwKXsNCiAgc2V0LnNlZWQoaSkNCiAgdHJhaW4gPC0gc2FtcGxlKDE6biwgbi8yKQ0KICBmaXQubmIudnMgPC0gbmFpdmVCYXllcyhkZWZhdWx0IH4gLiwgZGF0YT1EZWZhdWx0W3RyYWluLF0pDQogIHByZWQubmIudnMgPC0gcHJlZGljdChmaXQubmIudnMsIG5ld2RhdGE9RGVmYXVsdFstdHJhaW4sXSkNCiAgdGFibGVbLCxpXSA8LSBhcy5tYXRyaXgodGFibGUocHJlZCA9IHByZWQubmIudnMsIHRydWUgPSBEZWZhdWx0Wy10cmFpbiwxXSkpDQogIGVycm9yLnJhdGUubmIudnNbaV0gPC0gbWVhbihwcmVkMSAhPSBEZWZhdWx0Wy10cmFpbixdJGRlZmF1bHQpDQp9DQoNCmBgYA0KDQoNCg0KDQpgYGB7ciBlY2hvPUZBTFNFfQ0KdG1wIDwtIGFwcGx5KHRhYmxlLCAxOjIgLG1lYW4pDQpkaW1uYW1lcyh0bXApIDwtIGxpc3QoYygiTm8iLCJZZXMiKSxjKCJObyIsIlllcyIpKQ0Ka2FibGUodG1wLCJodG1sIiwgZGlnaXRzID0gMSkgJT4lIA0KICBrYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGg9RikNCmBgYA0KLSBjb25mdXN0aW9uIG1hdHJpeA0KDQpgYGB7ciB9DQptZWFuKGVycm9yLnJhdGUubmIudnMpDQpgYGANCi0gIGVycm9yIHJhdGXsnYAgKiowLjA0NSoq7J2064ukLiANCi0gYmF5ZXMgY2xhc3NpZmllcuuztOuLpCBuYWl2ZSBiYXllc+ulvCDsgqzsmqntlojsnYQg65WMIGVycm9yIHJhdGXsnbQg642UIOuGkuuLpC4gDQotIOydtOqyg+ydgCDslYTrp4gsIG5haXZlIGJheWVz7JeQ7IScIOqwgOygle2VmOuKlCDrs4DsiJjrk6TsnZgg64+F66a97ISx7Jy866GcIOyduO2VtCDsoJXrs7TqsIAg7IaQ7Iuk65CY7Ja0IOuCmO2DgOuCnCDsmIHtlqXsnbgg6rKDIOqwmeuLpC4NCg0KDQpgYGB7ciBlY2hvPUZBTFNFfQ0KcGxvdChlcnJvci5yYXRlLm5iLnZzICx0eXBlPSJsIiwgDQogICAgIHlsaW09YyhtaW4oZXJyb3IucmF0ZSkqMC45LG1heChlcnJvci5yYXRlKSoxLjEpLA0KICAgICB5bGFiPSJFcnJvciBSYXRlIiwNCiAgICAgeGxhYj0iSyIpDQphYmxpbmUoaD1tZWFuKGVycm9yLnJhdGUpLCBsdHk9MiwgY29sPTIpDQpgYGANCi0gMzDrsogg67CY67O1IOy4oeygle2VoCDrlYzrp4jri6Qg7Ja77J2AIGVycm9yIHJhdGXrpbwgcGxvdOycvOuhnCDrgpjtg4DrgrTrs7TslZjri6QuIA0KDQoNCg0KPGJyLz48YnIvPg0KDQojIyBMT09DVg0KLSBMT09DVuulvCDsnbTsmqntlbQg6rWs7ZWcICBOYWl2ZSBCYXllc+ydmCAgZXJyb3IgcmF0ZeulvCDsgrTtjrTrs7TsnpAuIA0KIA0KYGBge3IgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCg0KbiA8LSBucm93KERlZmF1bHQpDQplcnJvci5sb29jdiA8LSB2ZWN0b3IobW9kZSA9ICJsb2dpY2FsIiwgbikNCnByZWQubmIubG9vY3YgPC0gcmVwKE5BLG4pDQoNCmZvciggaSBpbiAxOm4pew0KICBmaXQubG9vY3YgPC0gbmFpdmVCYXllcyhkZWZhdWx0IH4gLiwgZGF0YT1EZWZhdWx0Wy1pLF0pDQogIHByZWQyIDwtIHByZWRpY3QoZml0Lmxvb2N2LCBuZXdkYXRhPURlZmF1bHRbaSxdKQ0KICBwcmVkLm5iLmxvb2N2W2ldIDwtIGFzLmNoYXJhY3RlcihwcmVkMikNCiAgZXJyb3IubG9vY3ZbaV0gPC0gKHByZWQyICE9IERlZmF1bHRbaSxdJGRlZmF1bHQpDQp9DQoNCmBgYA0KDQoNCmBgYHtyIGVjaG89RkFMU0V9DQp0YWIubG9vY3Y8LSB0YWJsZShwcmVkID0gcHJlZC5uYi5sb29jdiwgdHJ1ZSA9IERlZmF1bHRbLDFdKQ0Ka2FibGUodGFiLmxvb2N2LCJodG1sIikgJT4lIA0KICBrYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGg9RikNCmBgYA0KDQpgYGB7cn0NCg0KbWVhbihlcnJvci5sb29jdikNCmBgYA0KLSBlcnJvciByYXRl64qUICoqMC4wMjk1KirroZwg64KY7YOA64Ks64ukLg0KLSBCYXllcyBDbGFzc2lmaWVy7J2YIGVycm9yIHJhdGXrs7Tri6Qg7YGs64ukLiANCi0g7J206rKD7J2AIOyVhOuniCwgbmFpdmUgYmF5ZXPsl5DshJwg6rCA7KCV7ZWY64qUIOuzgOyImOuTpOydmCDrj4Xrpr3shLHsnLzroZwg7J247ZW0IOygleuztOqwgCDshpDsi6TrkJjslrQg64KY7YOA64KcIOyYge2WpeyduCDqsoMg6rCZ64ukLg0KDQoNCjxici8+DQoNCi0gVlPsl5DshJwg7Ja77J2AIGVycm9yIHJhdGXsnYAgKiowLjA0NSoq7J24642wLCBMT09DVuyXkOyEnCDqtaztlZwgZXJyb3IgcmF0ZeydgCAqKjAuMDI5NSoq7J2064ukLiDruYTqtZDsoIEg7J6R64ukLiANCi0gVlPsnYAg642w7J207YSw7J2YIOygiOuwmOunjCDsgqzsmqntlZjripQg67CY66m0LCBMT09DVuuKlCDrjbDsnbTthLDrpbwg66qo65GQIOuLpCDsgqzsmqntlZjquLAg65WM66y47JeQIOuCmO2DgOuCnCDtmITsg4Hsnbgg6rKDIOqwmeuLpC4gDQoNCg0KPGJyLz4NCg0KIyMgMTAtZm9sZCBDVg0KLSAxMC1mb2xkIENW66W8IOydtOyaqe2VtCBOYWl2ZSBCYXllc+ydmCDqsrDqs7zrpbwg7IK07Y6067O07JWY64ukLiANCg0KYGBge3IgZWNobz1GQUxTRX0NCg0KbGlicmFyeShsZWFwcykNCg0KayA8LSAxMA0KZXJyb3Iua2ZvbGQgPC0gcmVwKE5BLCBrKQ0KbiA8LSBkaW0oRGVmYXVsdClbMV0NCnNldC5zZWVkKDEpDQoNCnByZWQuMTBmb2xkIDwtIGNoYXJhY3RlcihuKQ0KZm9sZHMgPC0gc2FtcGxlKDE6aywgbnJvdyhEZWZhdWx0KSwgcmVwbGFjZSA9IFRSVUUsIHByb2IgPSByZXAoMSAvIGssIGspKQ0KDQpmb3IoaSBpbiAxOmspew0KICBmaXQuMTBmb2xkIDwtIG5haXZlX2JheWVzKGRlZmF1bHQgfiAuLCBkYXRhPURlZmF1bHRbZm9sZHMhPWksXSkNCiAgcHJlZC4xMGZvbGRbZm9sZHM9PWldIDwtIGFzLmNoYXJhY3RlcihwcmVkaWN0KGZpdC4xMGZvbGQsIG5ld2RhdGE9RGVmYXVsdFtmb2xkcz09aSxdKSkNCn0NCg0KYGBgDQoNCg0KDQoNCg0KYGBge3IgZWNobz1GQUxTRX0NCnRhYjEgPC0gdGFibGUocHJlZCA9IHByZWQuMTBmb2xkLCB0cnVlID0gRGVmYXVsdFssMV0pDQprYWJsZSh0YWIxLCJodG1sIikgJT4lIA0KICBrYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGg9RikNCmBgYA0KDQpgYGB7cn0NCmVycm9yLmtmb2xkIDwtIHByZWQuMTBmb2xkICE9IERlZmF1bHRbLDFdDQptZWFuKGVycm9yLmtmb2xkKSANCmBgYA0KLSBlcnJvciByYXRl64qUICoqMC4wMjkzKirroZwg64KY7YOA64Ks64ukLiANCi0g7J2064qUIExPT0NW7JmAIOu5hOyKt+2VnCDqsrDqs7zri6QuIGdvb2QhIA0KDQo8YnIvPg0KPGJyLz4NCg0KDQojIOu2hOyEneqysOqzvCDsooXtlakNCi0gTmFpdmUgQmF5ZXPrs7Tri6QgQmF5ZXMgQ2xhc3NpZmllcuulvCDsgqzsmqntlojsnYQg65WMIGVycm9yIHJhdGXqsIAg7KCB7JeI64ukLiANCg0KPiDrgpgg6rCZ7Jy866m0IExEQeulvCDsnbTsmqntlZjsl6wgY2xhc3NpZmljYXRpb27snYQg7ZWgIOqygyDqsJnri6QuIGVycm9yIHJhdGXsnbQg7J6R6rOgLCDrqqjtmJXrj4Qg67mE6rWQ7KCBIOuLqOyInO2VmOq4sCDrlYzrrLjsnbTri6QuIA0KDQotIEJheWVzIENsYXNzaWZpZXLsnZgg6rK97JqwLCBRREHrpbwg7IKs7Jqp7ZWY64KYLCBMREHrpbwg7IKs7Jqp7ZWY64KYIO2BsCDssKjsnbTqsIAg7JeG7JeI64ukLg0KLSDqs6DroZwgTERB66W8IOydtOyaqe2VnCAgY2xhc3NpZmljYXRpb27snbQg642UIOyggeygiO2VmOqyoOuLpOqzoCDtjJDri6jtlZjsmIDri6QuIA0KDQotIE5haXZlIEJheWVz66W8IOyCrOyaqe2WiOydhCDrlYwsIGVycm9yIHJhdGXqsIAg642UIOuGkuydgCDqsoPsnYAsIOuzgOyImOqwhCDrj4Xrpr3snYQg6rCA7KCV7ZWY64qUIOqzvOygleyXkCDsoJXrs7TqsIAg7IaQ7Iuk65CY7Ja0IOyDneq4tCDqsoPsnbwg7IiYIOyeiOydhOqxsOudvCDstpTsuKHtlbTrs7jri6QuIA0KDQo8YnIvPg0KDQoNCg0KIyBBcHBlbmRpeCAtIENvZGUNCg0KYGBge3IgZWNobz1UUlVFLCBldmFsPUZBTFNFLGluY2x1ZGU9VFJVRX0NCg0KIyMgLS0tLWluY2x1ZGU9RkFMU0UsIGVjaG89RkFMU0UtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQprbml0cjo6b3B0c19jaHVuayRzZXQoY29tbWVudCA9ICIiLCBwcm9tcHQgPSBUUlVFLCBvdXQud2lkdGggPSA0MDAsIGZpZy5oZWlnaHQgPSA0LCBmaWcud2lkdGggPSA0KQ0KbGlicmFyeShrbml0cikNClN5cy5zZXRsb2NhbGUoIkxDX0FMTCIsICJlbmciKQ0Kc2V0d2QoIkU6L0Ryb3Bib3gvMDAuMjAxOC8wMS4yMDE4XzFfc2VtZXN0ZXIvMDEuRGF0YU1pbmluZy8wNC5IVy9IVzMiKQ0KDQojIyAtLS0tZWNobz1GQUxTRS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCmxpYnJhcnkoSVNMUikNCmRhdGEoRGVmYXVsdCkNCmthYmxlKGhlYWQoRGVmYXVsdCksIGNhcHRpb24gPSAiaGVhZChEZWZhdWx0KSIpDQpzdHIoRGVmYXVsdCkNCg0KIyMgLS0tLWVjaG89RkFMU0UtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQpsaWJyYXJ5KE1WTikNCmtzLnRlc3QoeCA9IHJub3JtKDEwIF4gNCksIERlZmF1bHQkYmFsYW5jZSwgYWx0ZXJuYXRpdmUgPSAidHdvLnNpZGVkIikNCmtzLnRlc3QoeCA9IHJub3JtKDEwIF4gNCksIERlZmF1bHQkaW5jb21lLCBhbHRlcm5hdGl2ZSA9ICJ0d28uc2lkZWQiKQ0KDQoNCiMjIC0tLS1lY2hvPUZBTFNFLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQpuIDwtIGRpbShEZWZhdWx0KVsxXQ0KDQp0YWJsZTEgPC0gYXJyYXkoTkEsIGMoMiwgMiwgMzApKQ0KZXJyb3IucmF0ZS5vZi5sZGEudnMgPC0gcmVwKE5BLCAzMCkNCg0KZm9yIChpIGluIDE6MzApIHsNCiAgc2V0LnNlZWQoaSkNCiAgdHJhaW4gPC0gc2FtcGxlKDE6biwgbiAvIDIpDQogIGRlZmF1bHQudHJhaW4gPC0gRGVmYXVsdFt0cmFpbiwgXQ0KICBkZWZhdWx0LnRlc3QgPC0gRGVmYXVsdFstdHJhaW4sIF0NCiAgZml0MiA8LSBsZGEoZGVmYXVsdH4gYmFsYW5jZSArIGluY29tZSwgZGF0YSA9IERlZmF1bHQsIHN1YnNldCA9IHRyYWluKQ0KICBwcmVkLmxkYSA8LSBwcmVkaWN0KGZpdDIsIERlZmF1bHRbLXRyYWluLCBdKSRjbGFzcw0KICB0YWJsZTFbLCAsIGldIDwtIHRhYmxlKHByZWQgPSBwcmVkLmxkYSwgdHJ1ZSA9IERlZmF1bHRbLXRyYWluLCAxXSkNCiAgZXJyb3IucmF0ZS5vZi5sZGEudnNbaV0gPC0gbWVhbihwcmVkLmxkYSAhPSBEZWZhdWx0Wy10cmFpbiwgMV0pDQp9DQoNCiMjIC0tLS1lY2hvPUZBTFNFLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KIyBhcHBseSh0YWJsZTEsMToyLG1lYW4pDQoNCnRtcDEgPC0gYXBwbHkodGFibGUsIDE6MiwgbWVhbikNCmRpbW5hbWVzKHRtcDEpIDwtIGxpc3QoYygiTm8iLCAiWWVzIiksIGMoIk5vIiwgIlllcyIpKQ0Ka2FibGUodG1wMSwgImh0bWwiLCBkaWdpdHMgPSAxKSAlPiUNCiAga2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRikNCg0KIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQptZWFuKGVycm9yLnJhdGUub2YubGRhLnZzKQ0KDQoNCiMjIC0tLS1lY2hvPUZBTFNFLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KcGxvdCgNCiAgZXJyb3IucmF0ZS5vZi5sZGEudnMsIHR5cGUgPSAibCIsDQogIHlsaW0gPSBjKG1pbihlcnJvci5yYXRlLm9mLmxkYS52cykgKiAwLjksIG1heChlcnJvci5yYXRlLm9mLmxkYS52cykgKiAxLjEpLA0KICB5bGFiID0gIkVycm9yIFJhdGUiLA0KICB4bGFiID0gIksiDQopDQphYmxpbmUoaCA9IG1lYW4oZXJyb3IucmF0ZS5vZi5sZGEudnMpLCBsdHkgPSAyLCBjb2wgPSAyKQ0KDQojIyAtLS0tZWNobz1GQUxTRS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KdGFibGUyIDwtIGFycmF5KE5BLCBjKDIsIDIsIDMwKSkNCmVycm9yLnJhdGUub2YucWRhLnZzIDwtIHJlcChOQSwgMzApDQoNCmZvciAoaSBpbiAxOjMwKSB7DQogIHNldC5zZWVkKGkpDQogIHRyYWluIDwtIHNhbXBsZSgxOm4sIG4gLyAyKQ0KICBmaXQyIDwtIHFkYShkZWZhdWx0fiBiYWxhbmNlICsgaW5jb21lLCBkYXRhID0gRGVmYXVsdCwgc3Vic2V0ID0gdHJhaW4pDQogIHByZWQucWRhIDwtIHByZWRpY3QoZml0MiwgRGVmYXVsdFstdHJhaW4sIF0pJGNsYXNzDQogIHRhYmxlMlssICwgaV0gPC0gdGFibGUocHJlZCA9IHByZWQucWRhLCB0cnVlID0gRGVmYXVsdFstdHJhaW4sIDFdKQ0KICBlcnJvci5yYXRlLm9mLnFkYS52c1tpXSA8LSBtZWFuKHByZWQucWRhICE9IERlZmF1bHRbLXRyYWluLCAxXSkNCn0NCg0KIyMgLS0tLWVjaG89RkFMU0UtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQojIGFwcGx5KHRhYmxlMiwxOjIsIG1lYW4pDQoNCnRtcDIgPC0gYXBwbHkodGFibGUyLCAxOjIsIG1lYW4pDQpkaW1uYW1lcyh0bXAyKSA8LSBsaXN0KGMoIk5vIiwgIlllcyIpLCBjKCJObyIsICJZZXMiKSkNCmthYmxlKHRtcDIsICJodG1sIiwgZGlnaXRzID0gMSkgJT4lDQogIGthYmxlX3N0eWxpbmcoZnVsbF93aWR0aCA9IEYpDQoNCiMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KbWVhbihlcnJvci5yYXRlLm9mLnFkYS52cykNCg0KIyMgLS0tLWVjaG89RkFMU0UtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQpwbG90KA0KICBlcnJvci5yYXRlLm9mLnFkYS52cywgdHlwZSA9ICJsIiwNCiAgeWxpbSA9IGMobWluKGVycm9yLnJhdGUub2YucWRhLnZzKSAqIDAuOSwgbWF4KGVycm9yLnJhdGUub2YucWRhLnZzKSAqIDEuMSksDQogIHlsYWIgPSAiRXJyb3IgUmF0ZSIsDQogIHhsYWIgPSAiSyINCikNCmFibGluZShoID0gbWVhbihlcnJvci5yYXRlLm9mLnFkYS52cyksIGx0eSA9IDIsIGNvbCA9IDIpDQoNCiMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KWS5wcmVkLmxvb2N2IDwtIHJlcChOQSwgbikNCnBwcm9iIDwtIHRtcC5jdi5sZGEkcG9zdGVyaW9yDQoNCmZvciAoaSBpbiAxOm4pIFkucHJlZC5sb29jdltpXSA8LSB3aGljaC5tYXgocHByb2JbaSwgXSkNClkuaGF0Lmxvb2N2IDwtIGNoYXJhY3RlcihsZW5ndGgoWS5wcmVkLmxvb2N2KSkNClkuaGF0Lmxvb2N2W1kucHJlZC5sb29jdiA9PSAxXSA8LSAiTm8iDQpZLmhhdC5sb29jdltZLnByZWQubG9vY3YgPT0gMl0gPC0gIlllcyINClkuaGF0Lmxvb2N2IDwtIGZhY3RvcihZLmhhdC5sb29jdiwgbGV2ZWxzID0gYygiTm8iLCAiWWVzIikpDQoNCmthYmxlKHRhYmxlKHByZWQgPSBZLmhhdC5sb29jdiwgdHJ1ZSA9IERlZmF1bHRbLCAxXSksICJodG1sIikgJT4lDQogIGthYmxlX3N0eWxpbmcoZnVsbF93aWR0aCA9IEYpDQoNCiMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KbWVhbihZLmhhdC5sb29jdiAhPSBEZWZhdWx0WywgMV0pDQoNCiMjIC0tLS1lY2hvPUZBTFNFLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KWS5wcmVkLmxvb2N2LnFkYSA8LSByZXAoTkEsIG4pDQpwcHJvYi5xZGEgPC0gdG1wLmN2LnFkYSRwb3N0ZXJpb3INCg0KZm9yIChpIGluIDE6bikgWS5wcmVkLmxvb2N2LnFkYVtpXSA8LSB3aGljaC5tYXgocHByb2IucWRhW2ksIF0pDQpZLmhhdC5sb29jdi5xZGEgPC0gY2hhcmFjdGVyKGxlbmd0aChZLnByZWQubG9vY3YucWRhKSkNClkuaGF0Lmxvb2N2LnFkYVtZLnByZWQubG9vY3YucWRhID09IDFdIDwtICJObyINClkuaGF0Lmxvb2N2LnFkYVtZLnByZWQubG9vY3YucWRhID09IDJdIDwtICJZZXMiDQoNCmthYmxlKHRhYmxlKHByZWQgPSBZLmhhdC5sb29jdi5xZGEsIHRydWUgPSBEZWZhdWx0WywgMV0pLCAiaHRtbCIpICU+JQ0KICBrYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGKQ0KDQojIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCm1lYW4oWS5oYXQubG9vY3YucWRhICE9IERlZmF1bHRbLCAxXSkNCg0KIyMgLS0tLWVjaG89RkFMU0UtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQojIEstZm9sZCBDVg0KSyA8LSAxMA0KaW5kIDwtICgxOm4pICUlIEsgKyAxDQpzZXQuc2VlZCgxICogaSkNCmZvbGRzIDwtIHNhbXBsZShpbmQsIG4pDQpwcmVkY3YgPC0gY2hhcmFjdGVyKG4pDQpmb3IgKGsgaW4gMTpLKSB7DQogIGZpdCA8LSBsZGEoZGVmYXVsdH5pbmNvbWUgKyBiYWxhbmNlLCBkYXRhID0gRGVmYXVsdCwgc3Vic2V0ID0gd2hpY2goaW5kICE9IGspKQ0KICBwcmVkY3ZbaW5kID09IGtdIDwtIGFzLmNoYXJhY3RlcihwcmVkaWN0KGZpdCwgRGVmYXVsdFtpbmQgPT0gaywgXSkkY2xhc3MpDQp9DQp0YWJsZS4xMGZvbGQubGRhIDwtIHRhYmxlKHByZWQgPSBwcmVkY3YsIHRydWUgPSBEZWZhdWx0WywgMV0pDQprYWJsZSh0YWJsZS4xMGZvbGQubGRhLCAiaHRtbCIpICU+JQ0KICBrYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGKQ0KDQoNCiMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KZXJyb3IucmF0ZS4xMGZvbGQgPC0gbWVhbihwcmVkY3YgIT0gRGVmYXVsdFssIDFdKQ0KZXJyb3IucmF0ZS4xMGZvbGQNCg0KIyMgLS0tLWVjaG89RkFMU0UtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQojIEstZm9sZCBDVg0KSyA8LSAxMA0KaW5kIDwtICgxOm4pICUlIEsgKyAxDQpzZXQuc2VlZCgxICogaSkNCmZvbGRzIDwtIHNhbXBsZShpbmQsIG4pDQpwcmVkY3YucWRhIDwtIGNoYXJhY3RlcihuKQ0KZm9yIChrIGluIDE6Sykgew0KICBmaXQgPC0gbGRhKGRlZmF1bHR+aW5jb21lICsgYmFsYW5jZSwgZGF0YSA9IERlZmF1bHQsIHN1YnNldCA9IHdoaWNoKGluZCAhPSBrKSkNCiAgcHJlZGN2LnFkYVtpbmQgPT0ga10gPC0gYXMuY2hhcmFjdGVyKHByZWRpY3QoZml0LCBEZWZhdWx0W2luZCA9PSBrLCBdKSRjbGFzcykNCn0NCnRhYmxlLjEwZm9sZC5xZGEgPC0gdGFibGUocHJlZCA9IHByZWRjdi5xZGEsIHRydWUgPSBEZWZhdWx0WywgMV0pDQprYWJsZSh0YWJsZS4xMGZvbGQucWRhLCAiaHRtbCIpICU+JQ0KICBrYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGKQ0KDQoNCiMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KZXJyb3IucmF0ZS4xMGZvbGQucWRhIDwtIG1lYW4ocHJlZGN2LnFkYSAhPSBEZWZhdWx0WywgMV0pDQplcnJvci5yYXRlLjEwZm9sZC5xZGENCg0KIyMgLS0tLWVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0UtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQpsaWJyYXJ5KE1BU1MpDQpsaWJyYXJ5KG5haXZlYmF5ZXMpDQpsaWJyYXJ5KGUxMDcxKQ0KDQplcnJvci5yYXRlLm5iLnZzIDwtIHJlcChOQSwgMzApDQpuIDwtIGRpbShEZWZhdWx0KVsxXQ0KdGFibGUgPC0gYXJyYXkoTkEsIGMoMiwgMiwgMzApKQ0KDQpmb3IgKGkgaW4gMTozMCkgew0KICBzZXQuc2VlZChpKQ0KICB0cmFpbiA8LSBzYW1wbGUoMTpuLCBuIC8gMikNCiAgZml0Lm5iLnZzIDwtIG5haXZlQmF5ZXMoZGVmYXVsdCB+IC4sIGRhdGEgPSBEZWZhdWx0W3RyYWluLCBdKQ0KICBwcmVkLm5iLnZzIDwtIHByZWRpY3QoZml0Lm5iLnZzLCBuZXdkYXRhID0gRGVmYXVsdFstdHJhaW4sIF0pDQogIHRhYmxlWywgLCBpXSA8LSBhcy5tYXRyaXgodGFibGUocHJlZCA9IHByZWQubmIudnMsIHRydWUgPSBEZWZhdWx0Wy10cmFpbiwgMV0pKQ0KICBlcnJvci5yYXRlLm5iLnZzW2ldIDwtIG1lYW4ocHJlZDEgIT0gRGVmYXVsdFstdHJhaW4sIF0kZGVmYXVsdCkNCn0NCg0KDQojIyAtLS0tZWNobz1GQUxTRS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCnRtcCA8LSBhcHBseSh0YWJsZSwgMToyLCBtZWFuKQ0KZGltbmFtZXModG1wKSA8LSBsaXN0KGMoIk5vIiwgIlllcyIpLCBjKCJObyIsICJZZXMiKSkNCmthYmxlKHRtcCwgImh0bWwiLCBkaWdpdHMgPSAxKSAlPiUNCiAga2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRikNCg0KIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQptZWFuKGVycm9yLnJhdGUubmIudnMpDQoNCiMjIC0tLS1lY2hvPUZBTFNFLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KcGxvdCgNCiAgZXJyb3IucmF0ZS5uYi52cywgdHlwZSA9ICJsIiwNCiAgeWxpbSA9IGMobWluKGVycm9yLnJhdGUpICogMC45LCBtYXgoZXJyb3IucmF0ZSkgKiAxLjEpLA0KICB5bGFiID0gIkVycm9yIFJhdGUiLA0KICB4bGFiID0gIksiDQopDQphYmxpbmUoaCA9IG1lYW4oZXJyb3IucmF0ZSksIGx0eSA9IDIsIGNvbCA9IDIpDQoNCiMjIC0tLS1lY2hvPUZBTFNFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQpuIDwtIG5yb3coRGVmYXVsdCkNCmVycm9yLmxvb2N2IDwtIHZlY3Rvcihtb2RlID0gImxvZ2ljYWwiLCBuKQ0KcHJlZC5uYi5sb29jdiA8LSByZXAoTkEsIG4pDQoNCmZvciAoaSBpbiAxOm4pIHsNCiAgZml0Lmxvb2N2IDwtIG5haXZlQmF5ZXMoZGVmYXVsdCB+IC4sIGRhdGEgPSBEZWZhdWx0Wy1pLCBdKQ0KICBwcmVkMiA8LSBwcmVkaWN0KGZpdC5sb29jdiwgbmV3ZGF0YSA9IERlZmF1bHRbaSwgXSkNCiAgcHJlZC5uYi5sb29jdltpXSA8LSBhcy5jaGFyYWN0ZXIocHJlZDIpDQogIGVycm9yLmxvb2N2W2ldIDwtIChwcmVkMiAhPSBEZWZhdWx0W2ksIF0kZGVmYXVsdCkNCn0NCg0KDQojIyAtLS0tZWNobz1GQUxTRS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCnRhYi5sb29jdiA8LSB0YWJsZShwcmVkID0gcHJlZC5uYi5sb29jdiwgdHJ1ZSA9IERlZmF1bHRbLCAxXSkNCmthYmxlKHRhYi5sb29jdiwgImh0bWwiKSAlPiUNCiAga2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRikNCg0KIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQoNCm1lYW4oZXJyb3IubG9vY3YpDQoNCiMjIC0tLS1lY2hvPUZBTFNFLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KDQpsaWJyYXJ5KGxlYXBzKQ0KDQprIDwtIDEwDQplcnJvci5rZm9sZCA8LSByZXAoTkEsIGspDQpuIDwtIGRpbShEZWZhdWx0KVsxXQ0Kc2V0LnNlZWQoMSkNCg0KcHJlZC4xMGZvbGQgPC0gY2hhcmFjdGVyKG4pDQpmb2xkcyA8LSBzYW1wbGUoMTprLCBucm93KERlZmF1bHQpLCByZXBsYWNlID0gVFJVRSwgcHJvYiA9IHJlcCgxIC8gaywgaykpDQoNCmZvciAoaSBpbiAxOmspIHsNCiAgZml0LjEwZm9sZCA8LSBuYWl2ZV9iYXllcyhkZWZhdWx0IH4gLiwgZGF0YSA9IERlZmF1bHRbZm9sZHMgIT0gaSwgXSkNCiAgcHJlZC4xMGZvbGRbZm9sZHMgPT0gaV0gPC0gYXMuY2hhcmFjdGVyKHByZWRpY3QoZml0LjEwZm9sZCwgbmV3ZGF0YSA9IERlZmF1bHRbZm9sZHMgPT0gaSwgXSkpDQp9DQoNCg0KIyMgLS0tLWVjaG89RkFMU0UtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tDQp0YWIxIDwtIHRhYmxlKHByZWQgPSBwcmVkLjEwZm9sZCwgdHJ1ZSA9IERlZmF1bHRbLCAxXSkNCmthYmxlKHRhYjEsICJodG1sIikgJT4lDQogIGthYmxlX3N0eWxpbmcoZnVsbF93aWR0aCA9IEYpDQoNCiMjIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQ0KZXJyb3Iua2ZvbGQgPC0gcHJlZC4xMGZvbGQgIT0gRGVmYXVsdFssIDFdDQptZWFuKGVycm9yLmtmb2xkKQ0KDQoNCmBgYA==