Gaston Martinez


1.Preparación de los datos

  1. Leer el archivo titanic_complete_train.csv y mostrar su estructura

library("tidyverse")
-- Attaching packages --------------------------------------- tidyverse 1.2.1 --
v ggplot2 3.2.1     v purrr   0.3.3
v tibble  2.1.3     v dplyr   0.8.3
v tidyr   1.0.0     v stringr 1.4.0
v readr   1.3.1     v forcats 0.4.0
-- Conflicts ------------------------------------------ tidyverse_conflicts() --
x dplyr::filter() masks stats::filter()
x dplyr::lag()    masks stats::lag()
library("ggplot2")
library("GGally")

Attaching package: 㤼㸱GGally㤼㸲

The following object is masked from 㤼㸱package:dplyr㤼㸲:

    nasa
library("dplyr")
library("tidyr")
library("broom")
library("modelr")

Attaching package: 㤼㸱modelr㤼㸲

The following object is masked from 㤼㸱package:broom㤼㸲:

    bootstrap
library("OneR")
library("rlang")

Attaching package: 㤼㸱rlang㤼㸲

The following objects are masked from 㤼㸱package:purrr㤼㸲:

    %@%, as_function, flatten, flatten_chr,
    flatten_dbl, flatten_int, flatten_lgl,
    flatten_raw, invoke, list_along, modify,
    prepend, splice
library("purrr")
library("pROC")
Type 'citation("pROC")' for a citation.

Attaching package: 㤼㸱pROC㤼㸲

The following objects are masked from 㤼㸱package:stats㤼㸲:

    cov, smooth, var
library("caret")
Loading required package: lattice

Attaching package: 㤼㸱caret㤼㸲

The following object is masked from 㤼㸱package:purrr㤼㸲:

    lift
#install.packages('e1071', dependencies=TRUE)
titanic_train = read.csv("titanic_complete_train.csv")

Diccionario de datos

Alt text

Vamos a ver un resumen de los datos y las distribuciones de algunas variables

summary(titanic_train)
  PassengerId       Survived          Pclass     
 Min.   :  1.0   Min.   :0.0000   Min.   :1.000  
 1st Qu.:223.5   1st Qu.:0.0000   1st Qu.:2.000  
 Median :446.0   Median :0.0000   Median :3.000  
 Mean   :446.0   Mean   :0.3838   Mean   :2.309  
 3rd Qu.:668.5   3rd Qu.:1.0000   3rd Qu.:3.000  
 Max.   :891.0   Max.   :1.0000   Max.   :3.000  
                                                 
                                    Name         Sex     
 Abbing, Mr. Anthony                  :  1   female:314  
 Abbott, Mr. Rossmore Edward          :  1   male  :577  
 Abbott, Mrs. Stanton (Rosa Hunt)     :  1               
 Abelson, Mr. Samuel                  :  1               
 Abelson, Mrs. Samuel (Hannah Wizosky):  1               
 Adahl, Mr. Mauritz Nils Martin       :  1               
 (Other)                              :885               
      Age            SibSp           Parch       
 Min.   : 0.42   Min.   :0.000   Min.   :0.0000  
 1st Qu.:21.75   1st Qu.:0.000   1st Qu.:0.0000  
 Median :26.51   Median :0.000   Median :0.0000  
 Mean   :29.32   Mean   :0.523   Mean   :0.3816  
 3rd Qu.:36.00   3rd Qu.:1.000   3rd Qu.:0.0000  
 Max.   :80.00   Max.   :8.000   Max.   :6.0000  
                                                 
      Ticket         Fare                Cabin    
 1601    :  7   Min.   :  0.00   B96 B98    :  4  
 347082  :  7   1st Qu.:  7.91   C23 C25 C27:  4  
 CA. 2343:  7   Median : 14.45   G6         :  4  
 3101295 :  6   Mean   : 32.20   C22 C26    :  3  
 347088  :  6   3rd Qu.: 31.00   D          :  3  
 CA 2144 :  6   Max.   :512.33   (Other)    :186  
 (Other) :852                    NA's       :687  
 Embarked
 C:168   
 Q: 77   
 S:646   
         
         
         
         

Vemos que el dataset tiene 891 registros. Hay mas hombres, mayor personal en la clase 3 y murieron mas personas de las que sobrevivieron. El 50% de las personas se encuentran entre los 21 y 36 años.

  1. Seleccionar las variables PassengerId, Survived, Pclass, Sex, Age, SibSp,Parch, Fare y Embarked

titanic_train_selec = titanic_train %>% select(c(PassengerId, Survived, Pclass, Sex, Age, SibSp,Parch, Fare, Embarked))

  1. Transformar las variables Survived, Pclass y Embarked a factor

titanic_train_selec$Survived = as_factor(titanic_train_selec$Survived)
titanic_train_selec$Pclass = as_factor(titanic_train_selec$Pclass)
titanic_train_selec$Embarked = as_factor(titanic_train_selec$Embarked)

  1. Realizar un gráfico de ggpairs para las variables Survived, Pclass, Sex, Age y Fare e interpretarlo

ggpairs(titanic_train_selec %>%  select(c( Survived, Pclass, Sex, Age , Fare)),  mapping = aes(colour= Survived))

La proporción de muertos para los hombres es mucho mayor que la de las mujeres, lo mismo para la 3° clase.

  1. Mostrar la distribucion de clase (Sobrevivientes vs No Sobrevivientes)

plot(titanic_train_selec$Survived)

  1. Dividir al dataset en conjunto de entrenamiento (70% de los datos) y validacion de los datos). Volver a analizar la distribuci󮠤e clase para chequear que este balanceado

spec = c(train = .7,  validate = .3)
g = sample(cut(
  seq(nrow(titanic_train_selec)), 
  nrow(titanic_train_selec)*cumsum(c(0,spec)),
  labels = names(spec)
))
res = split(titanic_train_selec, g)
plot(res[['train']]$Survived)

plot(res[['validate']]$Survived)

prop = data.frame(
  
"complete.Survived0" = nrow(titanic_train_selec %>% filter(Survived == 0)) / nrow(titanic_train_selec),
"complete.Survived1" = nrow(titanic_train_selec  %>% filter(Survived == 1)) / nrow(titanic_train_selec),
"train_prop.Survived0" = nrow(res[['train']]  %>% filter(Survived == 0)) / nrow(res[['train']]),
"train_prop.Survived1" = nrow(res[['train']]  %>% filter(Survived == 1)) / nrow(res[['train']]),
"valid_prop.Survived0" = nrow(res[['train']]  %>% filter(Survived == 0)) / nrow(res[['train']]),
"valid_prop.Survived1" = nrow(res[['train']]  %>% filter(Survived == 1)) / nrow(res[['train']])
)
  
prop

Luego de dividir el set de datos, vemos que las proporciones siguen siendo similares.

  1. Predicciones

  1. Realizar un modelo de regresión logística para predecir la supervivencia en funci󮠤e Pclass, Sex y Age. Usar solo el dataset de entrenamiento

glm.fit = glm(Survived ~ Pclass + Sex + Age, data = res[['train']], family = binomial)

  1. Dar una breve interpretaci󮠤e los coeficientes y su significativiad

summary(glm.fit)

Call:
glm(formula = Survived ~ Pclass + Sex + Age, family = binomial, 
    data = res[["train"]])

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-2.8248  -0.6364  -0.4445   0.6381   2.4815  

Coefficients:
             Estimate Std. Error z value Pr(>|z|)    
(Intercept)  4.061364   0.474382   8.561  < 2e-16 ***
Pclass2     -1.259315   0.314522  -4.004 6.23e-05 ***
Pclass3     -2.392876   0.305697  -7.828 4.97e-15 ***
Sexmale     -2.670144   0.225457 -11.843  < 2e-16 ***
Age         -0.045113   0.009313  -4.844 1.27e-06 ***
---
Signif. codes:  
0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 833.29  on 622  degrees of freedom
Residual deviance: 558.85  on 618  degrees of freedom
AIC: 568.85

Number of Fisher Scoring iterations: 5

Todos los coeficientes son significativos. Ser hombre parece disminuir las probabilidades de sobrevivir respecto al caso basal (mujer). Ser de las clases 2 y 3, también disminuye las probabilidades de sobrevir respecto al caso basal (clase 1). A mayor edad, mayor es la posibilidad de no sobrevivir.

  1. ¿Quien tiene una mayor probabilidad de supervivencia? Rose que es una mujer de 17 a񯳠que viaja en primera clase o Jack que es un hombre de 20 a񯳠viajando en tercera clase

rose = predict(glm.fit,newdata = data.frame(Pclass = '1', Sex = "female", Age = 17),type = "response")
jack = predict(glm.fit,newdata = data.frame(Pclass = '3', Sex = "male", Age = 20),type = "response")
#glm.pred = ifelse(glm.probs > 0.5, "Up", "Down")
rose
        1 
0.9642374 
jack
        1 
0.1296649 

Vemos que claramente, Jack no tenia chances de sobrevivir, a penas un 10%. En cambio, Rose tenia un 95%.

  1. Generacion de modelos

���e modelos

  1. Generar 3 modelos de regresion logistica sobre el dataset de entrenamiento utilizando diferentes combinaciones de variables. Al menos dos modelos deben ser multivariados
  1. Ordenar por la deviance los 3 modelos creados en el punto 3)a) y el creado en el punto 2)a) y seleccionar el mejor modelo en términos de la deviance explicada

f = formulas(.response = ~Survived,
                         Familia= ~SibSp+Parch+Sex+Age,
                         Embarque=~Embarked+Parch+Sex+Age,
                         Todo=~Pclass+Sex+Age+SibSp+Parch+Fare+Embarked 
                         )
modelos = data_frame(f) %>%
  mutate(modelos = names(f),
         expresion = paste(f),
         mod = map(f, ~glm(.,family = 'binomial', data = res[['train']]))) 
`data_frame()` is deprecated, use `tibble()`.
This warning is displayed once per session.
modelos = modelos %>% 
  mutate(glance = map(mod,glance)) %>%
  mutate(pred= map(mod,augment, type.predict = "response"))
  modelos %>% 
    unnest(glance, .drop = TRUE) %>%
    mutate(perc_explained_dev = 1-deviance/null.deviance) %>% 
    select(-c(modelos, df.null, AIC, BIC)) %>% 
    arrange(deviance)
The `.drop` argument of `unnest()` is deprecated as of tidyr 1.0.0.
All list-columns are now preserved.
This warning is displayed once per session.
Call `lifecycle::last_warnings()` to see where this warning was generated.
NA

El mejor modelo segun deviance es el que usa todas las variables con un porcentaje explicado de 0.35. El peor modelo es el que tiene como formula: Survived ~ SibSp + Parch + Sex + Age. Con un porcentaje explicado de 0.24

  1. Evaluación del modelo

  1. Realizar el gráfico de curva ROC y obtener el AUC para el modelo elegido. Interpretar el gráfico

Voy a elegir el modelo completo ya que tiene la mayor explicacion

models <- models %>% 
  mutate(pred= map(mod,augment, newdata=res[["validate"]], type.predict = "response"))
prediction_full <- models %>% 
  filter(models=="Todo") %>% 
  unnest(pred, .drop=TRUE)
roc_full <- roc(response=prediction_full$Survived, predictor=prediction_full$.fitted)
Setting levels: control = 0, case = 1
Setting direction: controls < cases
ggroc(list(full=roc_full), size=1) + 
  geom_abline(slope = 1, intercept = 1, linetype='dashed') + 
  theme_minimal() + labs(title=paste('Curva ROC AUC:',round(roc_full$auc,2)), color='Modelo Completo')

Podemos ver la relación entre la specificity (FPR) y sensitivity (TPR). La curva se encuentra por encima de la recta del azar. Se obtuvo un Area bajo la curva de 0.86.

  1. Realizar un violin plot e interpretar

ggplot(prediction_full, aes(x=Survived, y=.fitted, group=Survived,fill=factor(Survived))) + 
  geom_violin() +
  labs(title='Violin plot', subtitle='Modelo completo', y='Predicted probability')

Vemos una gran concentracion de observaciones de no sobrevivientes por de bajo de probabilidades predichas de 0.25 aunque tambien hay casos con probabilidad superior a 0.75. Mientras que los sobrevivientes, la cantidad de observaciones comienza a aumentar considerablemente por encima de 0.5, parecido a lo anterior, tambien hay casos con probabilidad menor a 0.125 que sobrevivieron. El punto de corte puede ser cualquera entre 0.25 y 0.5.

  1. Elección del punto corte

  1. Sobre el dataset de validación realizar un gráfico de Accuracy, Specificity, Recall y Precision en función del punto de corte.

#models_validate = glm(f$Todo, family = 'binomial', data = res[['validate']])
#modelo_todo = modelos %>% select(mod)
#prediction_validategment(x = models_validate, type.predict = 'response')
#prediction_todo(x = modelo_todo, type.predict = 'response')
prediction_metrics <- function(cutoff, predictions=prediction_full){
  table <- predictions %>% 
    mutate(predicted_class=if_else(.fitted>cutoff, 1, 0) %>% as.factor(),
           Survived= factor(Survived))
  
  confusionMatrix(table$predicted_class, table$Survived, positive = "1") %>%
    tidy() %>%
    select(term, estimate) %>%
    filter(term %in% c('accuracy', 'sensitivity', 'specificity', 'precision','recall')) %>%
    mutate(cutoff=cutoff)
  
}
cutoffs = seq(0.01,0.95,0.01)
logit_pred= map_dfr(cutoffs, prediction_metrics)%>% mutate(term=as.factor(term))
ggplot(logit_pred, aes(cutoff,estimate, group=term, color=term)) + geom_line(size=1) +
  theme_minimal() +
  labs(title= 'Accuracy, Sensitivity, Specificity, Recall y Precision', subtitle= 'Modelo completo', color="")

  1. Elegir un punto de corte y explicar su decisión

Me quedaría con un punto de corte cercano a 0.36 (un poco antes del centro [0.25:0.5]), donde se cruzan la mayoria de curvas. La desicion depende de cual metrica se quiera maximizar. Prefiero tener un equilibrio entre todas las metricas.

  1. Obtener la matriz de confusión con el modelo y punto de corte elegidos. Interpretarla

corte = 0.37
table= prediction_full  %>% mutate(predicted_class=if_else(.fitted>corte, 1, 0) %>% as.factor(),Survived= factor(Survived))
confusionMatrix(table(table$predicted_class, table$Survived), positive = "1")
Confusion Matrix and Statistics

   
      0   1
  0 132  22
  1  37  77
                                         
               Accuracy : 0.7799         
                 95% CI : (0.7254, 0.828)
    No Information Rate : 0.6306         
    P-Value [Acc > NIR] : 1.06e-07       
                                         
                  Kappa : 0.5418         
                                         
 Mcnemar's Test P-Value : 0.06836        
                                         
            Sensitivity : 0.7778         
            Specificity : 0.7811         
         Pos Pred Value : 0.6754         
         Neg Pred Value : 0.8571         
             Prevalence : 0.3694         
         Detection Rate : 0.2873         
   Detection Prevalence : 0.4254         
      Balanced Accuracy : 0.7794         
                                         
       'Positive' Class : 1              
                                         

Así obtenemos un acc de 0.7799 El modelo tiene mejor presicion para los casos negativos (no sobrevive) que los positivos, dado los valores de Pos Pred Value y Neg Pred Value.

  1. Dataset de testeo

  1. Leer el archivo titanic_complete_test.csv y transformar las variables Survived, Pclass y Embarked a factor

titanic_test = read.csv("titanic_complete_test.csv",sep = ",")
titanic_test = titanic_test %>%
  select(PassengerId, Survived, Pclass, Sex, Age, SibSp,Parch, Fare, Embarked) %>%
  mutate(Survived = as.factor(Survived)) %>%
  mutate(Pclass = as.factor(Pclass)) %>%
  mutate(Embarked = as.factor(Embarked))
summary(titanic_test)
  PassengerId     Survived Pclass      Sex           Age            SibSp            Parch             Fare        
 Min.   : 892.0   0:261    1:107   female:152   Min.   : 0.17   Min.   :0.0000   Min.   :0.0000   Min.   :  0.000  
 1st Qu.: 996.2   1:157    2: 93   male  :266   1st Qu.:23.00   1st Qu.:0.0000   1st Qu.:0.0000   1st Qu.:  7.896  
 Median :1100.5            3:218                Median :25.00   Median :0.0000   Median :0.0000   Median : 14.454  
 Mean   :1100.5                                 Mean   :29.42   Mean   :0.4474   Mean   :0.3923   Mean   : 35.577  
 3rd Qu.:1204.8                                 3rd Qu.:36.38   3rd Qu.:1.0000   3rd Qu.:0.0000   3rd Qu.: 31.472  
 Max.   :1309.0                                 Max.   :76.00   Max.   :8.0000   Max.   :9.0000   Max.   :512.329  
 Embarked
 C:102   
 Q: 46   
 S:270   
         
         
         

  1. con el modelo y punto de corte elegidos clasificar a las personas del dataset de testing.
  2. Obtener la matriz de confusión y comparar con la obtenida en el punto 5)c)

model <- glm(f$Todo, family = 'binomial', data = res[["train"]])
prediction_test <- augment(x = model, newdata = titanic_test, type.predict = 'response')
table <- prediction_test %>% 
    mutate(predicted_class=if_else(.fitted>corte, 1, 0) %>% as.factor(),
           Survived= factor(Survived))
confusionMatrix(table(table$predicted_class, table$Survived), positive = "1")
Confusion Matrix and Statistics

   
      0   1
  0 192  39
  1  69 118
                                          
               Accuracy : 0.7416          
                 95% CI : (0.6968, 0.7829)
    No Information Rate : 0.6244          
    P-Value [Acc > NIR] : 2.496e-07       
                                          
                  Kappa : 0.4694          
                                          
 Mcnemar's Test P-Value : 0.005262        
                                          
            Sensitivity : 0.7516          
            Specificity : 0.7356          
         Pos Pred Value : 0.6310          
         Neg Pred Value : 0.8312          
             Prevalence : 0.3756          
         Detection Rate : 0.2823          
   Detection Prevalence : 0.4474          
      Balanced Accuracy : 0.7436          
                                          
       'Positive' Class : 1               
                                          

Se redujo el accuracy, algo que se espera si el modelo no esta “overfiteando”. Lo mismo paso con la capacidad de predecir las clases positivas. Se sigue prediciendo mejor la clase negativa.

LS0tDQp0aXRsZTogIlRQIDM6IFJlZ3Jlc2lvbiBMb2fDrXN0aWNhIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KPGgzPkdhc3RvbiBNYXJ0aW5lejwvaDM+DQo8YnIvPjxoMz4xLlByZXBhcmFjacOzbiBkZSBsb3MgZGF0b3M8L2gzPg0KPGgzPmEuIExlZXIgZWwgYXJjaGl2byB0aXRhbmljX2NvbXBsZXRlX3RyYWluLmNzdiB5IG1vc3RyYXIgc3UgZXN0cnVjdHVyYTwvaDM+DQpgYGB7cn0NCmxpYnJhcnkoInRpZHl2ZXJzZSIpDQpsaWJyYXJ5KCJnZ3Bsb3QyIikNCmxpYnJhcnkoIkdHYWxseSIpDQpsaWJyYXJ5KCJkcGx5ciIpDQpsaWJyYXJ5KCJ0aWR5ciIpDQpsaWJyYXJ5KCJicm9vbSIpDQpsaWJyYXJ5KCJtb2RlbHIiKQ0KbGlicmFyeSgiT25lUiIpDQpsaWJyYXJ5KCJybGFuZyIpDQpsaWJyYXJ5KCJwdXJyciIpDQpsaWJyYXJ5KCJwUk9DIikNCmxpYnJhcnkoImNhcmV0IikNCiNpbnN0YWxsLnBhY2thZ2VzKCdlMTA3MScsIGRlcGVuZGVuY2llcz1UUlVFKQ0KDQp0aXRhbmljX3RyYWluID0gcmVhZC5jc3YoInRpdGFuaWNfY29tcGxldGVfdHJhaW4uY3N2IikNCmBgYA0KPHA+RGljY2lvbmFyaW8gZGUgZGF0b3M8L3A+DQohW0FsdCB0ZXh0XShkaWNjaW9uYXJpby5qcGcpDQo8cD5WYW1vcyBhIHZlciB1biByZXN1bWVuIGRlIGxvcyBkYXRvcyB5IGxhcyBkaXN0cmlidWNpb25lcyBkZSBhbGd1bmFzIHZhcmlhYmxlczwvcD4NCmBgYHtyfQ0Kc3VtbWFyeSh0aXRhbmljX3RyYWluKQ0KYGBgDQo8cD5WZW1vcyBxdWUgZWwgZGF0YXNldCB0aWVuZSA4OTEgcmVnaXN0cm9zLiBIYXkgbWFzIGhvbWJyZXMsIG1heW9yIHBlcnNvbmFsIGVuIGxhIGNsYXNlIDMgeSBtdXJpZXJvbiBtYXMgcGVyc29uYXMgZGUgbGFzIHF1ZSBzb2JyZXZpdmllcm9uLiBFbCA1MCUgZGUgbGFzIHBlcnNvbmFzIHNlIGVuY3VlbnRyYW4gZW50cmUgbG9zIDIxIHkgMzYgYcOxb3MuPC9wPg0KPGgzPmIuIFNlbGVjY2lvbmFyIGxhcyB2YXJpYWJsZXMgUGFzc2VuZ2VySWQsIFN1cnZpdmVkLCBQY2xhc3MsIFNleCwgQWdlLCBTaWJTcCxQYXJjaCwgRmFyZSB5IEVtYmFya2VkPC9oMz4NCmBgYHtyfQ0KdGl0YW5pY190cmFpbl9zZWxlYyA9IHRpdGFuaWNfdHJhaW4gJT4lIHNlbGVjdChjKFBhc3NlbmdlcklkLCBTdXJ2aXZlZCwgUGNsYXNzLCBTZXgsIEFnZSwgU2liU3AsUGFyY2gsIEZhcmUsIEVtYmFya2VkKSkNCmBgYA0KPGgzPmMuIFRyYW5zZm9ybWFyIGxhcyB2YXJpYWJsZXMgU3Vydml2ZWQsIFBjbGFzcyB5IEVtYmFya2VkIGEgZmFjdG9yPC9oMz4NCmBgYHtyfQ0KdGl0YW5pY190cmFpbl9zZWxlYyRTdXJ2aXZlZCA9IGFzX2ZhY3Rvcih0aXRhbmljX3RyYWluX3NlbGVjJFN1cnZpdmVkKQ0KdGl0YW5pY190cmFpbl9zZWxlYyRQY2xhc3MgPSBhc19mYWN0b3IodGl0YW5pY190cmFpbl9zZWxlYyRQY2xhc3MpDQp0aXRhbmljX3RyYWluX3NlbGVjJEVtYmFya2VkID0gYXNfZmFjdG9yKHRpdGFuaWNfdHJhaW5fc2VsZWMkRW1iYXJrZWQpDQpgYGANCjxoMz5kLiBSZWFsaXphciB1biBncsOhZmljbyBkZSBnZ3BhaXJzIHBhcmEgbGFzIHZhcmlhYmxlcyBTdXJ2aXZlZCwgUGNsYXNzLCBTZXgsIEFnZSB5IEZhcmUgZSBpbnRlcnByZXRhcmxvPC9oMz4NCmBgYHtyfQ0KZ2dwYWlycyh0aXRhbmljX3RyYWluX3NlbGVjICU+JSAgc2VsZWN0KGMoIFN1cnZpdmVkLCBQY2xhc3MsIFNleCwgQWdlICwgRmFyZSkpLCAgbWFwcGluZyA9IGFlcyhjb2xvdXI9IFN1cnZpdmVkKSkNCmBgYA0KPHA+TGEgcHJvcG9yY2nDs24gZGUgbXVlcnRvcyBwYXJhIGxvcyBob21icmVzIGVzIG11Y2hvIG1heW9yIHF1ZSBsYSBkZSBsYXMgbXVqZXJlcywgbG8gbWlzbW8gcGFyYSBsYSAzwrAgY2xhc2UuPC9wPg0KPGgzPmUuIE1vc3RyYXIgbGEgZGlzdHJpYnVjaW9uIGRlIGNsYXNlIChTb2JyZXZpdmllbnRlcyB2cyBObyBTb2JyZXZpdmllbnRlcyk8L2gzPg0KYGBge3J9DQpwbG90KHRpdGFuaWNfdHJhaW5fc2VsZWMkU3Vydml2ZWQpDQpgYGANCjxoMz5mLiBEaXZpZGlyIGFsIGRhdGFzZXQgZW4gY29uanVudG8gZGUgZW50cmVuYW1pZW50byAoNzAlIGRlIGxvcyBkYXRvcykgeSB2YWxpZGFjaW9uIGRlIGxvcyBkYXRvcykuIFZvbHZlciBhIGFuYWxpemFyIGxhIGRpc3RyaWJ1Y2nzrqCkZSBjbGFzZSBwYXJhIGNoZXF1ZWFyIHF1ZSBlc3RlIGJhbGFuY2VhZG88L2gzPg0KYGBge3J9DQpzcGVjID0gYyh0cmFpbiA9IC43LCAgdmFsaWRhdGUgPSAuMykNCg0KZyA9IHNhbXBsZShjdXQoDQogIHNlcShucm93KHRpdGFuaWNfdHJhaW5fc2VsZWMpKSwgDQogIG5yb3codGl0YW5pY190cmFpbl9zZWxlYykqY3Vtc3VtKGMoMCxzcGVjKSksDQogIGxhYmVscyA9IG5hbWVzKHNwZWMpDQopKQ0KDQpyZXMgPSBzcGxpdCh0aXRhbmljX3RyYWluX3NlbGVjLCBnKQ0KDQpwbG90KHJlc1tbJ3RyYWluJ11dJFN1cnZpdmVkKQ0KcGxvdChyZXNbWyd2YWxpZGF0ZSddXSRTdXJ2aXZlZCkNCg0KcHJvcCA9IGRhdGEuZnJhbWUoDQogIA0KImNvbXBsZXRlLlN1cnZpdmVkMCIgPSBucm93KHRpdGFuaWNfdHJhaW5fc2VsZWMgJT4lIGZpbHRlcihTdXJ2aXZlZCA9PSAwKSkgLyBucm93KHRpdGFuaWNfdHJhaW5fc2VsZWMpLA0KDQoiY29tcGxldGUuU3Vydml2ZWQxIiA9IG5yb3codGl0YW5pY190cmFpbl9zZWxlYyAgJT4lIGZpbHRlcihTdXJ2aXZlZCA9PSAxKSkgLyBucm93KHRpdGFuaWNfdHJhaW5fc2VsZWMpLA0KDQoidHJhaW5fcHJvcC5TdXJ2aXZlZDAiID0gbnJvdyhyZXNbWyd0cmFpbiddXSAgJT4lIGZpbHRlcihTdXJ2aXZlZCA9PSAwKSkgLyBucm93KHJlc1tbJ3RyYWluJ11dKSwNCg0KInRyYWluX3Byb3AuU3Vydml2ZWQxIiA9IG5yb3cocmVzW1sndHJhaW4nXV0gICU+JSBmaWx0ZXIoU3Vydml2ZWQgPT0gMSkpIC8gbnJvdyhyZXNbWyd0cmFpbiddXSksDQoNCiJ2YWxpZF9wcm9wLlN1cnZpdmVkMCIgPSBucm93KHJlc1tbJ3RyYWluJ11dICAlPiUgZmlsdGVyKFN1cnZpdmVkID09IDApKSAvIG5yb3cocmVzW1sndHJhaW4nXV0pLA0KDQoidmFsaWRfcHJvcC5TdXJ2aXZlZDEiID0gbnJvdyhyZXNbWyd0cmFpbiddXSAgJT4lIGZpbHRlcihTdXJ2aXZlZCA9PSAxKSkgLyBucm93KHJlc1tbJ3RyYWluJ11dKQ0KDQopDQogIA0KDQoNCnByb3ANCmBgYA0KPHA+THVlZ28gZGUgZGl2aWRpciBlbCBzZXQgZGUgZGF0b3MsIHZlbW9zIHF1ZSBsYXMgcHJvcG9yY2lvbmVzIHNpZ3VlbiBzaWVuZG8gc2ltaWxhcmVzLjwvcD4NCjxoMj4yLiBQcmVkaWNjaW9uZXM8L2gyPg0KPGgzPmEuIFJlYWxpemFyIHVuIG1vZGVsbyBkZSByZWdyZXNpw7NuIGxvZ8Otc3RpY2EgcGFyYSBwcmVkZWNpciBsYSBzdXBlcnZpdmVuY2lhIGVuIGZ1bmNp866gpGUgUGNsYXNzLCBTZXggeSBBZ2UuIFVzYXIgc29sbyBlbCBkYXRhc2V0IGRlIGVudHJlbmFtaWVudG88L2gzPg0KYGBge3J9DQpnbG0uZml0ID0gZ2xtKFN1cnZpdmVkIH4gUGNsYXNzICsgU2V4ICsgQWdlLCBkYXRhID0gcmVzW1sndHJhaW4nXV0sIGZhbWlseSA9IGJpbm9taWFsKQ0KYGBgDQo8aDM+Yi4gRGFyIHVuYSBicmV2ZSBpbnRlcnByZXRhY2nzrqCkZSBsb3MgY29lZmljaWVudGVzIHkgc3Ugc2lnbmlmaWNhdGl2aWFkPC9oMz4NCmBgYHtyfQ0Kc3VtbWFyeShnbG0uZml0KQ0KYGBgDQo8cD5Ub2RvcyBsb3MgY29lZmljaWVudGVzIHNvbiBzaWduaWZpY2F0aXZvcy4gU2VyIGhvbWJyZSBwYXJlY2UgZGlzbWludWlyIGxhcyBwcm9iYWJpbGlkYWRlcyBkZSBzb2JyZXZpdmlyIHJlc3BlY3RvIGFsIGNhc28gYmFzYWwgKG11amVyKS4NClNlciBkZSBsYXMgY2xhc2VzIDIgeSAzLCB0YW1iacOpbiBkaXNtaW51eWUgbGFzIHByb2JhYmlsaWRhZGVzIGRlIHNvYnJldmlyIHJlc3BlY3RvIGFsIGNhc28gYmFzYWwgKGNsYXNlIDEpLiBBIG1heW9yIGVkYWQsIG1heW9yIGVzIGxhIHBvc2liaWxpZGFkIGRlIG5vIHNvYnJldml2aXIuPC9wPg0KPGgzPmMuIMK/UXVpZW4gdGllbmUgdW5hIG1heW9yIHByb2JhYmlsaWRhZCBkZSBzdXBlcnZpdmVuY2lhPyBSb3NlIHF1ZSBlcyB1bmEgbXVqZXIgZGUgMTcgYfGvs6BxdWUgdmlhamEgZW4gcHJpbWVyYSBjbGFzZSBvIEphY2sgcXVlIGVzIHVuIGhvbWJyZSBkZSAyMCBh8a+zoHZpYWphbmRvIGVuIHRlcmNlcmEgY2xhc2U8L2gzPg0KYGBge3J9DQpyb3NlID0gcHJlZGljdChnbG0uZml0LG5ld2RhdGEgPSBkYXRhLmZyYW1lKFBjbGFzcyA9ICcxJywgU2V4ID0gImZlbWFsZSIsIEFnZSA9IDE3KSx0eXBlID0gInJlc3BvbnNlIikNCg0KamFjayA9IHByZWRpY3QoZ2xtLmZpdCxuZXdkYXRhID0gZGF0YS5mcmFtZShQY2xhc3MgPSAnMycsIFNleCA9ICJtYWxlIiwgQWdlID0gMjApLHR5cGUgPSAicmVzcG9uc2UiKQ0KI2dsbS5wcmVkID0gaWZlbHNlKGdsbS5wcm9icyA+IDAuNSwgIlVwIiwgIkRvd24iKQ0Kcm9zZQ0KamFjaw0KYGBgDQo8cD5WZW1vcyBxdWUgY2xhcmFtZW50ZSwgSmFjayBubyB0ZW5pYSBjaGFuY2VzIGRlIHNvYnJldml2aXIsIGEgcGVuYXMgdW4gMTAlLiBFbiBjYW1iaW8sIFJvc2UgdGVuaWEgdW4gOTUlLjwvcD4NCjxoMj4zLiBHZW5lcmFjaW9uIGRlIG1vZGVsb3M8L2gyPu+/ve+/ve+/vWUgbW9kZWxvczwvaDI+DQo8aDM+YS4gR2VuZXJhciAzIG1vZGVsb3MgZGUgcmVncmVzaW9uIGxvZ2lzdGljYSBzb2JyZSBlbCBkYXRhc2V0IGRlIGVudHJlbmFtaWVudG8gdXRpbGl6YW5kbyBkaWZlcmVudGVzIGNvbWJpbmFjaW9uZXMgZGUgdmFyaWFibGVzLiBBbCBtZW5vcyBkb3MgbW9kZWxvcyBkZWJlbiBzZXIgbXVsdGl2YXJpYWRvcw0KYi4gT3JkZW5hciBwb3IgbGEgZGV2aWFuY2UgbG9zIDMgbW9kZWxvcyBjcmVhZG9zIGVuIGVsIHB1bnRvIDMpYSkgeSBlbCBjcmVhZG8gZW4gZWwgcHVudG8gMilhKSB5IHNlbGVjY2lvbmFyIGVsIG1lam9yIG1vZGVsbyBlbiB0w6lybWlub3MgZGUgbGEgZGV2aWFuY2UgZXhwbGljYWRhPC9oMz4NCmBgYHtyfQ0KZiA9IGZvcm11bGFzKC5yZXNwb25zZSA9IH5TdXJ2aXZlZCwNCiAgICAgICAgICAgICAgICAgICAgICAgICBGYW1pbGlhPSB+U2liU3ArUGFyY2grU2V4K0FnZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBFbWJhcnF1ZT1+RW1iYXJrZWQrUGFyY2grU2V4K0FnZSwNCiAgICAgICAgICAgICAgICAgICAgICAgICBUb2RvPX5QY2xhc3MrU2V4K0FnZStTaWJTcCtQYXJjaCtGYXJlK0VtYmFya2VkIA0KICAgICAgICAgICAgICAgICAgICAgICAgICkNCg0KbW9kZWxvcyA9IGRhdGFfZnJhbWUoZikgJT4lDQogIG11dGF0ZShtb2RlbG9zID0gbmFtZXMoZiksDQogICAgICAgICBleHByZXNpb24gPSBwYXN0ZShmKSwNCiAgICAgICAgIG1vZCA9IG1hcChmLCB+Z2xtKC4sZmFtaWx5ID0gJ2Jpbm9taWFsJywgZGF0YSA9IHJlc1tbJ3RyYWluJ11dKSkpIA0KDQptb2RlbG9zID0gbW9kZWxvcyAlPiUgDQogIG11dGF0ZShnbGFuY2UgPSBtYXAobW9kLGdsYW5jZSkpICU+JQ0KICBtdXRhdGUocHJlZD0gbWFwKG1vZCxhdWdtZW50LCB0eXBlLnByZWRpY3QgPSAicmVzcG9uc2UiKSkNCg0KICBtb2RlbG9zICU+JSANCiAgICB1bm5lc3QoZ2xhbmNlLCAuZHJvcCA9IFRSVUUpICU+JQ0KICAgIG11dGF0ZShwZXJjX2V4cGxhaW5lZF9kZXYgPSAxLWRldmlhbmNlL251bGwuZGV2aWFuY2UpICU+JSANCiAgICBzZWxlY3QoLWMobW9kZWxvcywgZGYubnVsbCwgQUlDLCBCSUMpKSAlPiUgDQogICAgYXJyYW5nZShkZXZpYW5jZSkNCiANCmBgYA0KDQo8cD5FbCBtZWpvciBtb2RlbG8gc2VndW4gZGV2aWFuY2UgZXMgZWwgcXVlIHVzYSB0b2RhcyBsYXMgdmFyaWFibGVzIGNvbiB1biBwb3JjZW50YWplIGV4cGxpY2FkbyBkZSAwLjM1LiBFbCBwZW9yIG1vZGVsbyBlcyBlbCBxdWUgdGllbmUgY29tbyBmb3JtdWxhOiBTdXJ2aXZlZCB+IFNpYlNwICsgUGFyY2ggKyBTZXggKyBBZ2UuIENvbiB1biBwb3JjZW50YWplIGV4cGxpY2FkbyBkZSAwLjI0PC9wPg0KPGgyPjQuIEV2YWx1YWNpw7NuIGRlbCBtb2RlbG88L2gyPg0KPGgzPmEuIFJlYWxpemFyIGVsIGdyw6FmaWNvIGRlIGN1cnZhIFJPQyB5IG9idGVuZXIgZWwgQVVDIHBhcmEgZWwgbW9kZWxvIGVsZWdpZG8uIEludGVycHJldGFyIGVsIGdyw6FmaWNvPC9oMz4NCjxwPlZveSBhIGVsZWdpciBlbCBtb2RlbG8gY29tcGxldG8geWEgcXVlIHRpZW5lIGxhIG1heW9yIGV4cGxpY2FjaW9uPC9wPg0KYGBge3J9DQptb2RlbHMgPC0gbW9kZWxzICU+JSANCiAgbXV0YXRlKHByZWQ9IG1hcChtb2QsYXVnbWVudCwgbmV3ZGF0YT1yZXNbWyJ2YWxpZGF0ZSJdXSwgdHlwZS5wcmVkaWN0ID0gInJlc3BvbnNlIikpDQpwcmVkaWN0aW9uX2Z1bGwgPC0gbW9kZWxzICU+JSANCiAgZmlsdGVyKG1vZGVscz09IlRvZG8iKSAlPiUgDQogIHVubmVzdChwcmVkLCAuZHJvcD1UUlVFKQ0KDQpyb2NfZnVsbCA8LSByb2MocmVzcG9uc2U9cHJlZGljdGlvbl9mdWxsJFN1cnZpdmVkLCBwcmVkaWN0b3I9cHJlZGljdGlvbl9mdWxsJC5maXR0ZWQpDQoNCmdncm9jKGxpc3QoZnVsbD1yb2NfZnVsbCksIHNpemU9MSkgKyANCiAgZ2VvbV9hYmxpbmUoc2xvcGUgPSAxLCBpbnRlcmNlcHQgPSAxLCBsaW5ldHlwZT0nZGFzaGVkJykgKyANCiAgdGhlbWVfbWluaW1hbCgpICsgbGFicyh0aXRsZT1wYXN0ZSgnQ3VydmEgUk9DIEFVQzonLHJvdW5kKHJvY19mdWxsJGF1YywyKSksIGNvbG9yPSdNb2RlbG8gQ29tcGxldG8nKQ0KYGBgDQo8cD5Qb2RlbW9zIHZlciBsYSByZWxhY2nDs24gZW50cmUgbGEgc3BlY2lmaWNpdHkgKEZQUikgeSBzZW5zaXRpdml0eSAoVFBSKS4gTGEgY3VydmEgc2UgZW5jdWVudHJhIHBvciBlbmNpbWEgZGUgbGEgcmVjdGEgZGVsIGF6YXIuIFNlIG9idHV2byB1biBBcmVhIGJham8gbGEgY3VydmEgZGUgMC44Ni48L3A+DQo8aDM+Yi4gUmVhbGl6YXIgdW4gdmlvbGluIHBsb3QgZSBpbnRlcnByZXRhcjwvaDM+DQpgYGB7cn0NCmdncGxvdChwcmVkaWN0aW9uX2Z1bGwsIGFlcyh4PVN1cnZpdmVkLCB5PS5maXR0ZWQsIGdyb3VwPVN1cnZpdmVkLGZpbGw9ZmFjdG9yKFN1cnZpdmVkKSkpICsgDQogIGdlb21fdmlvbGluKCkgKw0KICBsYSBicyh0aXRsZT0nVmlvbGluIHBsb3QnLCBzdWJ0aXRsZT0nTW9kZWxvIGNvbXBsZXRvJywgeT0nUHJlZGljdGVkIHByb2JhYmlsaXR5JykNCg0KYGBgDQo8cD5WZW1vcyB1bmEgZ3JhbiBjb25jZW50cmFjaW9uIGRlIG9ic2VydmFjaW9uZXMgZGUgbm8gc29icmV2aXZpZW50ZXMgcG9yIGRlIGJham8gZGUgcHJvYmFiaWxpZGFkZXMgcHJlZGljaGFzIGRlIDAuMjUgYXVucXVlIHRhbWJpZW4gaGF5IGNhc29zIGNvbiBwcm9iYWJpbGlkYWQgc3VwZXJpb3IgYSAwLjc1LiBNaWVudHJhcyBxdWUgbG9zIHNvYnJldml2aWVudGVzLCBsYSBjYW50aWRhZCBkZSBvYnNlcnZhY2lvbmVzIGNvbWllbnphIGEgYXVtZW50YXIgY29uc2lkZXJhYmxlbWVudGUgcG9yIGVuY2ltYSBkZSAwLjUsIHBhcmVjaWRvIGEgbG8gYW50ZXJpb3IsIHRhbWJpZW4gaGF5IGNhc29zIGNvbiBwcm9iYWJpbGlkYWQgbWVub3IgYSAwLjEyNSBxdWUgc29icmV2aXZpZXJvbi4gRWwgcHVudG8gZGUgY29ydGUgcHVlZGUgc2VyIGN1YWxxdWVyYSBlbnRyZSAwLjI1IHkgMC41LjwvcD4NCjxoMj41LiBFbGVjY2nDs24gZGVsIHB1bnRvIGNvcnRlPC9oMj4NCjxoMz5hLiBTb2JyZSBlbCBkYXRhc2V0IGRlIHZhbGlkYWNpw7NuIHJlYWxpemFyIHVuIGdyw6FmaWNvIGRlIEFjY3VyYWN5LCBTcGVjaWZpY2l0eSwgUmVjYWxsIHkgUHJlY2lzaW9uIGVuIGZ1bmNpw7NuIGRlbCBwdW50byBkZSBjb3J0ZS48L2gzPg0KYGBge3J9DQojbW9kZWxzX3ZhbGlkYXRlID0gZ2xtKGYkVG9kbywgZmFtaWx5ID0gJ2Jpbm9taWFsJywgZGF0YSA9IHJlc1tbJ3ZhbGlkYXRlJ11dKQ0KDQojbW9kZWxvX3RvZG8gPSBtb2RlbG9zICU+JSBzZWxlY3QobW9kKQ0KDQojcHJlZGljdGlvbl92YWxpZGF0ZWdtZW50KHggPSBtb2RlbHNfdmFsaWRhdGUsIHR5cGUucHJlZGljdCA9ICdyZXNwb25zZScpDQojcHJlZGljdGlvbl90b2RvKHggPSBtb2RlbG9fdG9kbywgdHlwZS5wcmVkaWN0ID0gJ3Jlc3BvbnNlJykNCg0KDQpwcmVkaWN0aW9uX21ldHJpY3MgPC0gZnVuY3Rpb24oY3V0b2ZmLCBwcmVkaWN0aW9ucz1wcmVkaWN0aW9uX2Z1bGwpew0KICB0YWJsZSA8LSBwcmVkaWN0aW9ucyAlPiUgDQogICAgbXV0YXRlKHByZWRpY3RlZF9jbGFzcz1pZl9lbHNlKC5maXR0ZWQ+Y3V0b2ZmLCAxLCAwKSAlPiUgYXMuZmFjdG9yKCksDQogICAgICAgICAgIFN1cnZpdmVkPSBmYWN0b3IoU3Vydml2ZWQpKQ0KICANCiAgY29uZnVzaW9uTWF0cml4KHRhYmxlJHByZWRpY3RlZF9jbGFzcywgdGFibGUkU3Vydml2ZWQsIHBvc2l0aXZlID0gIjEiKSAlPiUNCiAgICB0aWR5KCkgJT4lDQogICAgc2VsZWN0KHRlcm0sIGVzdGltYXRlKSAlPiUNCiAgICBmaWx0ZXIodGVybSAlaW4lIGMoJ2FjY3VyYWN5JywgJ3NlbnNpdGl2aXR5JywgJ3NwZWNpZmljaXR5JywgJ3ByZWNpc2lvbicsJ3JlY2FsbCcpKSAlPiUNCiAgICBtdXRhdGUoY3V0b2ZmPWN1dG9mZikNCiAgDQp9DQoNCmN1dG9mZnMgPSBzZXEoMC4wMSwwLjk1LDAuMDEpDQpsb2dpdF9wcmVkPSBtYXBfZGZyKGN1dG9mZnMsIHByZWRpY3Rpb25fbWV0cmljcyklPiUgbXV0YXRlKHRlcm09YXMuZmFjdG9yKHRlcm0pKQ0KDQpnZ3Bsb3QobG9naXRfcHJlZCwgYWVzKGN1dG9mZixlc3RpbWF0ZSwgZ3JvdXA9dGVybSwgY29sb3I9dGVybSkpICsgZ2VvbV9saW5lKHNpemU9MSkgKw0KICB0aGVtZV9taW5pbWFsKCkgKw0KICBsYWJzKHRpdGxlPSAnQWNjdXJhY3ksIFNlbnNpdGl2aXR5LCBTcGVjaWZpY2l0eSwgUmVjYWxsIHkgUHJlY2lzaW9uJywgc3VidGl0bGU9ICdNb2RlbG8gY29tcGxldG8nLCBjb2xvcj0iIikNCmBgYA0KPGgzPmIuIEVsZWdpciB1biBwdW50byBkZSBjb3J0ZSB5IGV4cGxpY2FyIHN1IGRlY2lzacOzbjwvaDM+DQo8cD5NZSBxdWVkYXLDrWEgY29uIHVuIHB1bnRvIGRlIGNvcnRlIGNlcmNhbm8gYSAwLjM2ICh1biBwb2NvIGFudGVzIGRlbCBjZW50cm8gWzAuMjU6MC41XSksIGRvbmRlIHNlIGNydXphbiBsYSBtYXlvcmlhIGRlIGN1cnZhcy4gTGEgZGVzaWNpb24gZGVwZW5kZSBkZSBjdWFsIG1ldHJpY2Egc2UgcXVpZXJhIG1heGltaXphci4gUHJlZmllcm8gdGVuZXIgdW4gZXF1aWxpYnJpbyBlbnRyZSB0b2RhcyBsYXMgbWV0cmljYXMuPC9wPg0KPGgzPmMuIE9idGVuZXIgbGEgbWF0cml6IGRlIGNvbmZ1c2nDs24gY29uIGVsIG1vZGVsbyB5IHB1bnRvIGRlIGNvcnRlIGVsZWdpZG9zLiBJbnRlcnByZXRhcmxhPC9oMz4NCmBgYHtyfQ0KY29ydGUgPSAwLjM3DQp0YWJsZT0gcHJlZGljdGlvbl9mdWxsICAlPiUgbXV0YXRlKHByZWRpY3RlZF9jbGFzcz1pZl9lbHNlKC5maXR0ZWQ+Y29ydGUsIDEsIDApICU+JSBhcy5mYWN0b3IoKSxTdXJ2aXZlZD0gZmFjdG9yKFN1cnZpdmVkKSkNCg0KDQpjb25mdXNpb25NYXRyaXgodGFibGUodGFibGUkcHJlZGljdGVkX2NsYXNzLCB0YWJsZSRTdXJ2aXZlZCksIHBvc2l0aXZlID0gIjEiKQ0KYGBgDQo8cD5Bc8OtIG9idGVuZW1vcyB1biBhY2MgZGUgMC43Nzk5IEVsIG1vZGVsbyB0aWVuZSBtZWpvciBwcmVzaWNpb24gcGFyYSBsb3MgY2Fzb3MgbmVnYXRpdm9zIChubyBzb2JyZXZpdmUpIHF1ZSBsb3MgcG9zaXRpdm9zLCBkYWRvIGxvcyB2YWxvcmVzIGRlIFBvcyBQcmVkIFZhbHVlIHkgTmVnIFByZWQgVmFsdWUuPC9wPg0KPGgyPjYuIERhdGFzZXQgZGUgdGVzdGVvPC9oMj4NCjxoMz5hLiBMZWVyIGVsIGFyY2hpdm8gdGl0YW5pY19jb21wbGV0ZV90ZXN0LmNzdiB5IHRyYW5zZm9ybWFyIGxhcyB2YXJpYWJsZXMgU3Vydml2ZWQsIFBjbGFzcyB5IEVtYmFya2VkIGEgZmFjdG9yPC9oMz4NCmBgYHtyfQ0KdGl0YW5pY190ZXN0ID0gcmVhZC5jc3YoInRpdGFuaWNfY29tcGxldGVfdGVzdC5jc3YiLHNlcCA9ICIsIikNCg0KdGl0YW5pY190ZXN0ID0gdGl0YW5pY190ZXN0ICU+JQ0KICBzZWxlY3QoUGFzc2VuZ2VySWQsIFN1cnZpdmVkLCBQY2xhc3MsIFNleCwgQWdlLCBTaWJTcCxQYXJjaCwgRmFyZSwgRW1iYXJrZWQpICU+JQ0KICBtdXRhdGUoU3Vydml2ZWQgPSBhcy5mYWN0b3IoU3Vydml2ZWQpKSAlPiUNCiAgbXV0YXRlKFBjbGFzcyA9IGFzLmZhY3RvcihQY2xhc3MpKSAlPiUNCiAgbXV0YXRlKEVtYmFya2VkID0gYXMuZmFjdG9yKEVtYmFya2VkKSkNCg0Kc3VtbWFyeSh0aXRhbmljX3Rlc3QpDQpgYGANCjxoMz5iLiBjb24gZWwgbW9kZWxvIHkgcHVudG8gZGUgY29ydGUgZWxlZ2lkb3MgY2xhc2lmaWNhciBhIGxhcyBwZXJzb25hcyBkZWwgZGF0YXNldCBkZSB0ZXN0aW5nLg0KYy4gT2J0ZW5lciBsYSBtYXRyaXogZGUgY29uZnVzacOzbiB5IGNvbXBhcmFyIGNvbiBsYSBvYnRlbmlkYSBlbiBlbCBwdW50byA1KWMpPC9oMz4NCmBgYHtyfQ0KbW9kZWwgPC0gZ2xtKGYkVG9kbywgZmFtaWx5ID0gJ2Jpbm9taWFsJywgZGF0YSA9IHJlc1tbInRyYWluIl1dKQ0KcHJlZGljdGlvbl90ZXN0IDwtIGF1Z21lbnQoeCA9IG1vZGVsLCBuZXdkYXRhID0gdGl0YW5pY190ZXN0LCB0eXBlLnByZWRpY3QgPSAncmVzcG9uc2UnKQ0KDQp0YWJsZSA8LSBwcmVkaWN0aW9uX3Rlc3QgJT4lIA0KICAgIG11dGF0ZShwcmVkaWN0ZWRfY2xhc3M9aWZfZWxzZSguZml0dGVkPmNvcnRlLCAxLCAwKSAlPiUgYXMuZmFjdG9yKCksDQogICAgICAgICAgIFN1cnZpdmVkPSBmYWN0b3IoU3Vydml2ZWQpKQ0KDQpjb25mdXNpb25NYXRyaXgodGFibGUodGFibGUkcHJlZGljdGVkX2NsYXNzLCB0YWJsZSRTdXJ2aXZlZCksIHBvc2l0aXZlID0gIjEiKQ0KDQpgYGANCjxwPlNlIHJlZHVqbyBlbCBhY2N1cmFjeSwgYWxnbyBxdWUgc2UgZXNwZXJhIHNpIGVsIG1vZGVsbyBubyBlc3RhICJvdmVyZml0ZWFuZG8iLiBMbyBtaXNtbyBwYXNvIGNvbiBsYSBjYXBhY2lkYWQgZGUgcHJlZGVjaXIgbGFzIGNsYXNlcyBwb3NpdGl2YXMuIFNlIHNpZ3VlIHByZWRpY2llbmRvIG1lam9yIGxhIGNsYXNlIG5lZ2F0aXZhLjwvcD4gDQo=