Datasets

DNI <- as.integer(params$dni)

library(readxl)

db.accidente <- read_excel(
  './Todas las bases.xlsx',
  sheet = 'accidente',
  col_names = TRUE,
)[,1:5]
New names:
* `` -> ...6
* `` -> ...7
* `` -> ...8
* `` -> ...9
db.seguros <- read_excel(
  './Todas las bases.xlsx',
  sheet = 'seguros',
  col_names = TRUE,
)[,1:8]
New names:
* `` -> ...9
* `` -> ...10

Obtenemos los datos según las restricciones del enunciado:

get_data <- function (datos, ratio) {
  n <- round(ratio * nrow(datos))
  set.seed(DNI)
  cuales <- sample(
    1:nrow(datos),
    size = n,
    replace = FALSE
  )
  return(datos[cuales,])
}

db.accidente.data <- get_data(db.accidente, 0.9)
db.seguros.data <- get_data(db.seguros, 0.75)

Supervisado

# Me fijo si hay datos faltantes
print(db.accidente.data[rowSums(is.na(db.accidente.data)) > 0,])

db.accidente.data$vehiculo <- NULL
db.accidente.cols.vars <- setdiff(colnames(db.accidente.data), c("grave"))

PCA

library(factoextra)
Loading required package: ggplot2
Registered S3 method overwritten by 'dplyr':
  method           from
  print.rowwise_df     
Welcome! Want to learn more? See two factoextra-related books at https://goo.gl/ve3WBa
accidente.pca.cov <- prcomp(db.accidente.data[,db.accidente.cols.vars], center = TRUE, scale. = FALSE)
accidente.pca.cor <- prcomp(db.accidente.data[,db.accidente.cols.vars], center = TRUE, scale. = TRUE)
summary(accidente.pca.cov)
Importance of components:
                           PC1     PC2     PC3
Standard deviation     24.0567 13.3304 3.81237
Proportion of Variance  0.7507  0.2305 0.01885
Cumulative Proportion   0.7507  0.9811 1.00000
summary(accidente.pca.cor)
Importance of components:
                          PC1    PC2    PC3
Standard deviation     1.2432 0.9127 0.7883
Proportion of Variance 0.5152 0.2777 0.2072
Cumulative Proportion  0.5152 0.7929 1.0000
fviz_eig(accidente.pca.cov)
Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     

fviz_eig(accidente.pca.cor)

Perfiles Medios

  • Ver ejemplo 8.4 (287)
accidente.grave <- db.accidente.data[db.accidente.data$grave == 1,][,db.accidente.cols.vars]
accidente.no_grave <- db.accidente.data[db.accidente.data$grave == 0,][,db.accidente.cols.vars]

accidente.mean.conjunta <- apply(db.accidente.data[,db.accidente.cols.vars], 2, mean)
accidente.mean.grave <- apply(accidente.grave, 2, mean)
accidente.mean.grave.matrix <- as.matrix(accidente.mean.grave)
accidente.mean.no_grave <- apply(accidente.no_grave, 2, mean)
accidente.mean.no_grave.matrix <- as.matrix(accidente.mean.no_grave)

accidente.means.data <- cbind(
  rep(1:length(db.accidente.cols.vars), 2),
  rbind(accidente.mean.grave.matrix, accidente.mean.no_grave.matrix),
  c(rep("grave", length(db.accidente.cols.vars)), rep("no grave", length(db.accidente.cols.vars)))
)
colnames(accidente.means.data) <- c('vars', 'means', 'grave')
accidente.means.data <- data.frame(accidente.means.data)
accidente.means.data$vars <- as.numeric(accidente.means.data$vars)
accidente.means.data$means <- as.numeric(accidente.means.data$means)

ggplot(accidente.means.data, aes(x = vars, y = means, colour = grave)) +
  geom_line() +
  scale_x_discrete(
    limit = as.character(1:length(db.accidente.cols.vars)),
    labels = db.accidente.cols.vars
  ) +
  xlab("Variables") +
  ylab("Medias")

  • Ver ejemplo 9.5 (312)
require(reshape2)
Loading required package: reshape2
db.accidente.data.m <- melt(db.accidente.data, id.var = 'grave')
db.accidente.data.m$grave <- as.character(db.accidente.data.m$grave)

require(ggplot2)
ggplot(data = db.accidente.data.m, aes(x=variable, y=value)) + 
  geom_boxplot(aes(fill=grave)) +
  facet_wrap(~variable, scales = "free")

Test Hotelling

Comparar vectores medios

(h0: Dos medias son iguales, p < 0.05 => Rechazo hipotesis de medias iguales)

  • Ver código 9.4 (313)
library(Hotelling)
Loading required package: corpcor
fit <- hotelling.test(.~grave, data=db.accidente.data)
fit
Test stat:  6.9394 
Numerator df:  3 
Denominator df:  32 
P-value:  0.0009969 

Normalidad

Normalidad Univariada

library(mvnormtest)
library(car)
Loading required package: carData
par(mfrow=c(2,2))

for (var in db.accidente.cols.vars) {
  print(var)
  print(mshapiro.test(t(db.accidente.data[[var]])))
  qqPlot(
    db.accidente.data[[var]],
    xlab = 'Cuantiles normales',
    ylab = var,
    col = 'green',
    pch = 20,
    col.lines = 'royalblue',
    lwd = 1,
  )
}
[1] "antigüedad"

    Shapiro-Wilk normality test

data:  Z
W = 0.87457, p-value = 0.0007387

[1] "edad conductor"

    Shapiro-Wilk normality test

data:  Z
W = 0.91839, p-value = 0.0113

[1] "potencia"

    Shapiro-Wilk normality test

data:  Z
W = 0.86769, p-value = 0.0004997

Normalidad multivariada

Test de normalidad multivariada de Shapiro-Wilk

(h0: Distribucion normal, p < 0.05 => Se rechaza hipotesis de normalidad)

  • Ver clase 13
mshapiro.test(t(db.accidente.data[, db.accidente.cols.vars]))

    Shapiro-Wilk normality test

data:  Z
W = 0.91757, p-value = 0.01069
mshapiro.test(t(db.accidente.data[db.accidente.data$grave == 0, db.accidente.cols.vars]))

    Shapiro-Wilk normality test

data:  Z
W = 0.88713, p-value = 0.01657
mshapiro.test(t(db.accidente.data[db.accidente.data$grave == 1, db.accidente.cols.vars]))

    Shapiro-Wilk normality test

data:  Z
W = 0.90161, p-value = 0.119

Igualdad de matrices de varianzas y covarianzas

M Test de Box (h0: Homogeniedad de matrices de var-cov, p < 0.05 => Se rechaza hipotesis de Homogeniedad de matrices de var-cov)

(Homocedasticidad)

Este test es sensible a la falta de normalidad. Una alternativa robusta es el test de Levene para datos multivariados.

library(biotools) 
Loading required package: rpanel
Loading required package: tcltk
Package `rpanel', version 1.1-4: type help(rpanel) for summary information
Loading required package: tkrplot
Loading required package: MASS
Loading required package: lattice
Loading required package: SpatialEpi
Loading required package: sp
---
biotools version 3.1
boxM(data = db.accidente.data[, db.accidente.cols.vars], grouping = db.accidente.data$grave)

    Box's M-test for Homogeneity of Covariance Matrices

data:  db.accidente.data[, db.accidente.cols.vars]
Chi-Sq (approx.) = 18.163, df = 6, p-value = 0.005837

Matrices de correlación

  • Ver código 9.5 (320)
library(corpcor)
library(corrplot)
corrplot 0.84 loaded
accidente.cor <- cor(db.accidente.data[,db.accidente.cols.vars])
corrplot(accidente.cor, tl.cex = 0.7, cl.cex = 0.7, tl.col = "royalblue")

Por clase

accidente.cor.no_grave <- cor(db.accidente.data[db.accidente.data$grave == 0, db.accidente.cols.vars])
accidente.cor.grave <- cor(db.accidente.data[db.accidente.data$grave == 1, db.accidente.cols.vars])

par(mfrow = c(1, 2))
corrplot(accidente.cor.no_grave, tl.cex = 0.7, cl.cex = 0.7, tl.col = "royalblue")
corrplot(accidente.cor.grave, tl.cex = 0.7, cl.cex = 0.7, tl.col = "royalblue")

División de dataset

split_sets <- function (datos, ratio) {
  if (missing(ratio)) {
    ratio <- 2 /3
  }
  
  n <- round(ratio * nrow(datos))
  set.seed(DNI)
  cuales <- sample(
    1:nrow(datos),
    size = n,
    replace = FALSE
  )
  return(list(
    'training' = datos[cuales,],
    'trainingIndexes' = cuales,
    'validation' = datos[-cuales,],
    'validationIndexes' = -cuales
  ))
}

db.accidente.data.splits <- split_sets(db.accidente.data)
training <- db.accidente.data.splits$training

Aplicamos LDA

ADL es solo válido:

  • Normalidad multivariado (Shapiro-wilk multivariado)
  • Homocedasticidad (test M Box)
  • Independencia
models.lda <- lda(
  formula = grave ~ antigüedad + `edad conductor` + potencia,
  data = training
)

models.lda
Call:
lda(grave ~ antigüedad + `edad conductor` + potencia, data = training)

Prior probabilities of groups:
        0         1 
0.5833333 0.4166667 

Group means:
  antigüedad `edad conductor` potencia
0        6.0         48.35714 79.28571
1        5.6         30.90000 78.20000

Coefficients of linear discriminants:
                         LD1
antigüedad        0.10362744
`edad conductor` -0.09868078
potencia          0.01844371

Error

validation <- db.accidente.data.splits$validation

models.lda.predict <- predict(
  object = models.lda,
  newdata = validation[, db.accidente.cols.vars],
  method = 'predictive'
)

table(
  validation$grave,
  models.lda.predict$class,
  dnn = c("Real", "Predicha")
)
    Predicha
Real 0 1
   0 6 2
   1 0 4
models.lda.predict.error <- mean(validation$grave != models.lda.predict$class) * 100
models.lda.predict.error
[1] 16.66667
library(caret)
models.lda.predict.confusionMatrix <- confusionMatrix(as.factor(validation$grave), models.lda.predict$class)
models.lda.predict.confusionMatrix
Confusion Matrix and Statistics

          Reference
Prediction 0 1
         0 6 2
         1 0 4
                                          
               Accuracy : 0.8333          
                 95% CI : (0.5159, 0.9791)
    No Information Rate : 0.5             
    P-Value [Acc > NIR] : 0.01929         
                                          
                  Kappa : 0.6667          
                                          
 Mcnemar's Test P-Value : 0.47950         
                                          
            Sensitivity : 1.0000          
            Specificity : 0.6667          
         Pos Pred Value : 0.7500          
         Neg Pred Value : 1.0000          
             Prevalence : 0.5000          
         Detection Rate : 0.5000          
   Detection Prevalence : 0.6667          
      Balanced Accuracy : 0.8333          
                                          
       'Positive' Class : 0               
                                          

Aplicamos QDA

models.qda <- qda(
  formula = grave ~ antigüedad + `edad conductor` + potencia,
  data = training
)

models.qda
Call:
qda(grave ~ antigüedad + `edad conductor` + potencia, data = training)

Prior probabilities of groups:
        0         1 
0.5833333 0.4166667 

Group means:
  antigüedad `edad conductor` potencia
0        6.0         48.35714 79.28571
1        5.6         30.90000 78.20000
models.qda.predict <- predict(
  object = models.qda,
  newdata = validation[, db.accidente.cols.vars],
)

table(
  validation$grave,
  models.qda.predict$class,
  dnn = c("Real", "Predicha")
)
    Predicha
Real 0 1
   0 5 3
   1 1 3
models.qda.predict.error <- mean(validation$grave != models.qda.predict$class) * 100
models.qda.predict.error
[1] 33.33333
models.qda.predict.confusionMatrix <- confusionMatrix(as.factor(validation$grave), models.qda.predict$class)
models.qda.predict.confusionMatrix
Confusion Matrix and Statistics

          Reference
Prediction 0 1
         0 5 3
         1 1 3
                                          
               Accuracy : 0.6667          
                 95% CI : (0.3489, 0.9008)
    No Information Rate : 0.5             
    P-Value [Acc > NIR] : 0.1938          
                                          
                  Kappa : 0.3333          
                                          
 Mcnemar's Test P-Value : 0.6171          
                                          
            Sensitivity : 0.8333          
            Specificity : 0.5000          
         Pos Pred Value : 0.6250          
         Neg Pred Value : 0.7500          
             Prevalence : 0.5000          
         Detection Rate : 0.4167          
   Detection Prevalence : 0.6667          
      Balanced Accuracy : 0.6667          
                                          
       'Positive' Class : 0               
                                          
# **Particiones por clase**
# library(klaR)
# 
db.accidente.data.frame <- data.frame(
  "Antiguedad" = db.accidente.data$antigüedad,
  "EdadConductor" = db.accidente.data$antigüedad,
  "Potencia" = db.accidente.data$potencia,
  "Grave" = factor(db.accidente.data$grave)
)
# 
# partimat(
#   Grave ~ Vehiculo + Antiguedad + EdadConductor + Potencia,
#   data = db.accidente.data.frame,
#   method = 'qda',
#   image.colors = c('cadetblue1', 'plum2'),
#   col.mean = 'royalblue',
#   subset = db.accidente.data.splits$trainingIndexes
# )

Alternativa robusta

library(MASS)

models.robust.cov.noGrave <- cov.rob(
  training[training$grave == 0, db.accidente.cols.vars],
  method = 'mcd',
  nsamp = 'best'
)

models.robust.cov.grave <- cov.rob(
  training[training$grave == 1, db.accidente.cols.vars],
  method = 'mcd',
  nsamp = 'best'
)

models.robust.prom.noGrave <- rep(
  models.robust.cov.noGrave$center,
  nrow(validation[validation$grave == 0, ])
)

models.robust.prom.grave <- rep(
  models.robust.cov.grave$center,
  nrow(validation[validation$grave == 1, ])
)

models.robust.var.noGrave <- as.matrix(models.robust.cov.noGrave$cov)
models.robust.var.grave <- as.matrix(models.robust.cov.grave$cov)

models.robust.DR.noGrave <-
  as.matrix(
    validation[, db.accidente.cols.vars] -
      models.robust.prom.noGrave
  ) %*% solve(models.robust.var.noGrave) %*% t(as.matrix(
    validation[, db.accidente.cols.vars] -
      models.robust.prom.noGrave
  ))

models.robust.DR.grave <-
  as.matrix(
    validation[, db.accidente.cols.vars] -
      models.robust.prom.grave
  ) %*% solve(models.robust.var.grave) %*% t(as.matrix(
    validation[, db.accidente.cols.vars] -
      models.robust.prom.grave
  ))

models.robust.predict <- rep(-1, nrow(validation))

for (i in 1:nrow(validation)) {
  ifelse(
    models.robust.DR.noGrave[i] < models.robust.DR.grave[i],
    models.robust.predict[i] <- 0,
    models.robust.predict[i] <- 1
  )
}

table(
  validation$grave,
  models.robust.predict,
  dnn = c("Real", "Predicha")
)
    Predicha
Real 0 1
   0 5 3
   1 3 1
models.robust.predict.error <- mean(validation$grave != models.robust.predict) * 100
models.robust.predict.error
[1] 50
models.robust.predict.confusionMatrix <- confusionMatrix(as.factor(validation$grave), as.factor(models.robust.predict))
models.robust.predict.confusionMatrix
Confusion Matrix and Statistics

          Reference
Prediction 0 1
         0 5 3
         1 3 1
                                          
               Accuracy : 0.5             
                 95% CI : (0.2109, 0.7891)
    No Information Rate : 0.6667          
    P-Value [Acc > NIR] : 0.9336          
                                          
                  Kappa : -0.125          
                                          
 Mcnemar's Test P-Value : 1.0000          
                                          
            Sensitivity : 0.6250          
            Specificity : 0.2500          
         Pos Pred Value : 0.6250          
         Neg Pred Value : 0.2500          
             Prevalence : 0.6667          
         Detection Rate : 0.4167          
   Detection Prevalence : 0.6667          
      Balanced Accuracy : 0.4375          
                                          
       'Positive' Class : 0               
                                          

SVM

library(e1071)

db.accidente.data.frame.std <- data.frame(scale(subset(db.accidente.data.frame, select = -Grave)))
db.accidente.data.frame.std$Grave <- db.accidente.data.frame$Grave

models.svm <- svm(
  Grave ~ Antiguedad + EdadConductor + Potencia,
  data = db.accidente.data.frame.std[db.accidente.data.splits$trainingIndexes,],
  method = 'C-classification',
  kernel = 'radial',
  cost = 10,
  gamma = .1
)

models.svm.predict <- predict(models.svm, db.accidente.data.frame.std[db.accidente.data.splits$validationIndexes,])
models.svm.predictClass <- as.factor(as.integer(models.svm.predict) - 1)

table(
  validation$grave,
  models.svm.predict,
  dnn = c("Real", "Predicha")
)
    Predicha
Real 0 1
   0 8 0
   1 3 1
models.svm.predict.error <- mean(validation$grave != models.svm.predictClass) * 100
models.svm.predict.error
[1] 25
models.svm.predict.confusionMatrix <- confusionMatrix(as.factor(validation$grave), models.svm.predictClass)
models.svm.predict.confusionMatrix
Confusion Matrix and Statistics

          Reference
Prediction 0 1
         0 8 0
         1 3 1
                                          
               Accuracy : 0.75            
                 95% CI : (0.4281, 0.9451)
    No Information Rate : 0.9167          
    P-Value [Acc > NIR] : 0.9862          
                                          
                  Kappa : 0.3077          
                                          
 Mcnemar's Test P-Value : 0.2482          
                                          
            Sensitivity : 0.7273          
            Specificity : 1.0000          
         Pos Pred Value : 1.0000          
         Neg Pred Value : 0.2500          
             Prevalence : 0.9167          
         Detection Rate : 0.6667          
   Detection Prevalence : 0.6667          
      Balanced Accuracy : 0.8636          
                                          
       'Positive' Class : 0               
                                          
library(gridExtra)

grid.arrange(
  ggplot(
    db.accidente.data.frame.std,
    aes(x = EdadConductor, y = Antiguedad)
  ) +
    geom_point(aes(color = Grave)),
  ggplot(
    db.accidente.data.frame.std,
    aes(x = EdadConductor, y = Potencia)
  ) +
    geom_point(aes(color = Grave)),
  ggplot(
    db.accidente.data.frame.std,
    aes(x = Antiguedad, y = Potencia)
  ) +
    geom_point(aes(color = Grave)),
  nrow = 2
)

library(ggbiplot)
Loading required package: plyr
Loading required package: scales
Loading required package: grid
ggbiplot(
  accidente.pca.cor,
  choices = 1:2,
  obs.scale = 1,
  var.scale = 1,
  alpha = 0.5,
  groups = as.factor(db.accidente.data$grave)
) +
  theme(legend.direction = "horizontal", legend.position = "top")

library(plotly)
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio

Attaching package: ‘plotly’

The following objects are masked from ‘package:plyr’:

    arrange, mutate, rename, summarise

The following object is masked from ‘package:MASS’:

    select

The following object is masked from ‘package:ggplot2’:

    last_plot

The following object is masked from ‘package:stats’:

    filter

The following object is masked from ‘package:graphics’:

    layout
plot_ly(
  x = db.accidente.data.frame.std$EdadConductor,
  y = db.accidente.data.frame.std$Antiguedad,
  z = db.accidente.data.frame.std$Potencia,
  type = "scatter3d",
  mode = "markers",
  color = db.accidente.data.frame.std$Grave
)
minimal value for n is 3, returning requested palette with 3 different levels
minimal value for n is 3, returning requested palette with 3 different levels
minimal value for n is 3, returning requested palette with 3 different levels
minimal value for n is 3, returning requested palette with 3 different levels

Regresion logistica

Ver Código 9.9 (341)

par(mfcol = c(2,2))
for (var in db.accidente.cols.vars) {
  print(var)
  modelo_logistico <- glm(
    grave ~ var,
    data = data.frame(
      var = training[[var]],
      grave = training$grave
    ),
    family = 'binomial'
  )
  print(summary(modelo_logistico))
  
  plot(
    training[[var]],
    training$grave,
    col = 'royalblue',
    xlab = var,
    ylab = 'P(grave)'
  )
  curve(
    predict(
      modelo_logistico,
      data.frame(var = x),
      type = 'response'
    ),
    add = TRUE,
    col = 'violet',
    lwd = 2.5
  )
  
  predicciones <- ifelse(
    test = modelo_logistico$fitted.values > 0.5,
    yes = 1,
    no = 0
  )
  
  print(predicciones)
  
  print(
    table(
      training$grave,
      predicciones,
      dnn = c('Observaciones', 'Predicciones')
    )
  )
  
  print(
    mean(training$grave != predicciones) * 100
  )
}
[1] "antigüedad"

Call:
glm(formula = grave ~ var, family = "binomial", data = data.frame(var = training[[var]], 
    grave = training$grave))

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-1.0815  -1.0454  -0.9708   1.2935   1.3645  

Coefficients:
            Estimate Std. Error z value Pr(>|z|)
(Intercept) -0.20764    0.70056  -0.296    0.767
var         -0.02222    0.09805  -0.227    0.821

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 32.601  on 23  degrees of freedom
Residual deviance: 32.550  on 22  degrees of freedom
AIC: 36.55

Number of Fisher Scoring iterations: 4

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 
 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0 
             Predicciones
Observaciones  0
            0 14
            1 10
[1] 41.66667
[1] "edad conductor"

Call:
glm(formula = grave ~ var, family = "binomial", data = data.frame(var = training[[var]], 
    grave = training$grave))

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-1.9986  -0.7359  -0.2701   0.7339   1.7965  

Coefficients:
            Estimate Std. Error z value Pr(>|z|)  
(Intercept)  3.97611    1.78493   2.228   0.0259 *
var         -0.11183    0.04719  -2.370   0.0178 *
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 32.601  on 23  degrees of freedom
Residual deviance: 23.182  on 22  degrees of freedom
AIC: 27.182

Number of Fisher Scoring iterations: 5

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 
 0  1  0  1  0  0  1  0  0  0  0  0  1  1  0  0  0  1  1  0  1  0  1  0 
             Predicciones
Observaciones  0  1
            0 11  3
            1  4  6
[1] 29.16667
[1] "potencia"

Call:
glm(formula = grave ~ var, family = "binomial", data = data.frame(var = training[[var]], 
    grave = training$grave))

Deviance Residuals: 
   Min      1Q  Median      3Q     Max  
-1.052  -1.044  -1.016   1.320   1.363  

Coefficients:
             Estimate Std. Error z value Pr(>|z|)
(Intercept) -0.196686   1.380563  -0.142    0.887
var         -0.001775   0.016744  -0.106    0.916

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 32.601  on 23  degrees of freedom
Residual deviance: 32.590  on 22  degrees of freedom
AIC: 36.59

Number of Fisher Scoring iterations: 4

 1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 
 0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0  0 
             Predicciones
Observaciones  0
            0 14
            1 10
[1] 41.66667

models.lg <- glm(
  as.factor(grave) ~ antigüedad + `edad conductor` + potencia,
  data = training,
  family = 'binomial'
)

summary(models.lg)

Call:
glm(formula = as.factor(grave) ~ antigüedad + `edad conductor` + 
    potencia, family = "binomial", data = training)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-2.1820  -0.5342  -0.2028   0.7442   1.5620  

Coefficients:
                 Estimate Std. Error z value Pr(>|z|)  
(Intercept)       3.10967    2.16626   1.436   0.1511  
antigüedad        0.17703    0.16130   1.098   0.2724  
`edad conductor` -0.16321    0.06457  -2.527   0.0115 *
potencia          0.02491    0.02385   1.044   0.2964  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 32.601  on 23  degrees of freedom
Residual deviance: 20.210  on 20  degrees of freedom
AIC: 28.21

Number of Fisher Scoring iterations: 5
models.lg.predict <- predict(
  object = models.lg,
  newdata = validation[, db.accidente.cols.vars],
  method = 'response'
)

models.lg.predictClass <- ifelse(models.lg.predict > 0.5, 1, 0)

table(
  validation$grave,
  models.lg.predictClass,
  dnn = c("Real", "Predicha")
)
    Predicha
Real 0 1
   0 7 1
   1 1 3
models.lg.predict.confusionMatrix <- confusionMatrix(as.factor(validation$grave), as.factor(models.lg.predictClass))
models.lg.predict.confusionMatrix
Confusion Matrix and Statistics

          Reference
Prediction 0 1
         0 7 1
         1 1 3
                                          
               Accuracy : 0.8333          
                 95% CI : (0.5159, 0.9791)
    No Information Rate : 0.6667          
    P-Value [Acc > NIR] : 0.1811          
                                          
                  Kappa : 0.625           
                                          
 Mcnemar's Test P-Value : 1.0000          
                                          
            Sensitivity : 0.8750          
            Specificity : 0.7500          
         Pos Pred Value : 0.8750          
         Neg Pred Value : 0.7500          
             Prevalence : 0.6667          
         Detection Rate : 0.5833          
   Detection Prevalence : 0.6667          
      Balanced Accuracy : 0.8125          
                                          
       'Positive' Class : 0               
                                          

Probamos solo edad conductor

models.lg2 <- glm(
  as.factor(grave) ~ `edad conductor`,
  data = training,
  family = 'binomial'
)

summary(models.lg2)

Call:
glm(formula = as.factor(grave) ~ `edad conductor`, family = "binomial", 
    data = training)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-1.9986  -0.7359  -0.2701   0.7339   1.7965  

Coefficients:
                 Estimate Std. Error z value Pr(>|z|)  
(Intercept)       3.97611    1.78493   2.228   0.0259 *
`edad conductor` -0.11183    0.04719  -2.370   0.0178 *
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 32.601  on 23  degrees of freedom
Residual deviance: 23.182  on 22  degrees of freedom
AIC: 27.182

Number of Fisher Scoring iterations: 5
models.lg2.predict <- predict(
  object = models.lg2,
  newdata = validation[, db.accidente.cols.vars],
  method = 'response'
)

models.lg2.predictClass <- ifelse(models.lg2.predict > 0.5, 1, 0)

table(
  validation$grave,
  models.lg2.predictClass,
  dnn = c("Real", "Predicha")
)
    Predicha
Real 0 1
   0 6 2
   1 1 3
models.lg2.predict.confusionMatrix <- confusionMatrix(as.factor(validation$grave), factor(models.lg2.predictClass, c(0, 1)))
models.lg2.predict.confusionMatrix
Confusion Matrix and Statistics

          Reference
Prediction 0 1
         0 6 2
         1 1 3
                                          
               Accuracy : 0.75            
                 95% CI : (0.4281, 0.9451)
    No Information Rate : 0.5833          
    P-Value [Acc > NIR] : 0.1916          
                                          
                  Kappa : 0.4706          
                                          
 Mcnemar's Test P-Value : 1.0000          
                                          
            Sensitivity : 0.8571          
            Specificity : 0.6000          
         Pos Pred Value : 0.7500          
         Neg Pred Value : 0.7500          
             Prevalence : 0.5833          
         Detection Rate : 0.5000          
   Detection Prevalence : 0.6667          
      Balanced Accuracy : 0.7286          
                                          
       'Positive' Class : 0               
                                          

Resultados

resultados.accidente <- data.frame(
  modelo = c(
    'lda',
    'qda',
    'rda',
    'svm',
    'log(edad conductor)',
    'log'
  ),
  exactitud = c(
    0.8333,
    0.6667,
    0.5,
    0.75,
    0.75,
    0.8333
  ),
  sensibilidad = c(
    1,
    0.8333,
    0.6250,
    0.7273,
    0.8571,
    0.8750
  ),
  especificidad = c(
    0.6667,
    0.5000,
    0.2500,
    1,
    0.6,
    0.7500
  )
)

ggplot(resultados.accidente, aes(x = modelo)) +
  geom_line(aes(y = exactitud, group = 1, color = 'exactitud')) +
  geom_point(aes(y = exactitud, group = 1, color = 'exactitud')) +
  geom_line(aes(y = sensibilidad, group = 2, color = 'sensibilidad')) +
  geom_point(aes(y = sensibilidad, group = 2, color = 'sensibilidad')) +
  geom_line(aes(y = especificidad, group = 3, color = 'especificidad')) +
  geom_point(aes(y = especificidad, group = 3, color = 'especificidad')) +
  labs(color = 'métrica') +
  ylab('valor')

Clusters

# Me fijo si hay datos faltantes
print(db.seguros.data[rowSums(is.na(db.seguros.data)) > 0,])

Analisis exploratorio

db.seguros.data.m <- melt(db.seguros.data)
No id variables; using all as measure variables
ggplot(data = db.seguros.data.m, aes(x=variable, y=value)) + 
  geom_boxplot(aes()) +
  facet_wrap(~variable, scales = "free")

par(mfcol = c(3,3))

for (k in colnames(db.seguros.data)){
  hist(
    db.seguros.data[[k]],
    proba = T,
    main = names(db.seguros.data[, k]),
    10
  )
  x0 <- seq(
    min(db.seguros.data[, k]),
    max(db.seguros.data[, k]),
    le = 50
  ) 
  lines(
    x0,
    dnorm(
      x0,
      mean(db.seguros.data[[k]]),
      sd(db.seguros.data[[k]])
    ),
    col = "red",
    lwd = 2
  ) 
  grid()
}

Normalidad

par(mfcol = c(2,4))

for (var in colnames(db.seguros.data)) {
  print(var)
  print(mshapiro.test(t(db.seguros.data[[var]])))
  qqPlot(
    db.seguros.data[[var]],
    xlab = 'Cuantiles normales',
    ylab = var,
    col = 'green',
    pch = 20,
    col.lines = 'royalblue',
    lwd = 1,
  )
  grid()
}
[1] "edad"

    Shapiro-Wilk normality test

data:  Z
W = 0.94301, p-value < 2.2e-16

[1] "sexo"

    Shapiro-Wilk normality test

data:  Z
W = 0.63648, p-value < 2.2e-16

[1] "BMI"

    Shapiro-Wilk normality test

data:  Z
W = 0.99278, p-value = 0.0001045

[1] "hijos"

    Shapiro-Wilk normality test

data:  Z
W = 0.6201, p-value < 2.2e-16

[1] "fuma"

    Shapiro-Wilk normality test

data:  Z
W = 0.81612, p-value < 2.2e-16

[1] "region"

    Shapiro-Wilk normality test

data:  Z
W = 0.49732, p-value < 2.2e-16

[1] "cargos"

    Shapiro-Wilk normality test

data:  Z
W = 0.8606, p-value < 2.2e-16

[1] "primadelseguro"

    Shapiro-Wilk normality test

data:  Z
W = 0.8143, p-value < 2.2e-16

Estandarizo datos

db.seguros.data.std <- data.frame(scale(db.seguros.data))
db.seguros.data.std.m <- melt(db.seguros.data.std)
No id variables; using all as measure variables
ggplot(data = db.seguros.data.std.m, aes(x=variable, y=value)) + 
  geom_boxplot(aes()) +
  facet_wrap(~variable, scales = "free")

Correlacion

seguros.cor <- cor(db.seguros.data)
corrplot(seguros.cor, tl.cex = 0.7, cl.cex = 0.7, tl.col = "royalblue")

PCA

seguros.pca.cov <- prcomp(db.seguros.data, center = TRUE, scale. = FALSE)
seguros.pca.cor <- prcomp(db.seguros.data, center = TRUE, scale. = TRUE)
summary(seguros.pca.cov)
Importance of components:
                             PC1       PC2   PC3   PC4   PC5    PC6    PC7    PC8
Standard deviation     1.224e+04 260.75618 13.49 5.845 1.201 0.9754 0.4986 0.2117
Proportion of Variance 9.995e-01   0.00045  0.00 0.000 0.000 0.0000 0.0000 0.0000
Cumulative Proportion  9.995e-01   1.00000  1.00 1.000 1.000 1.0000 1.0000 1.0000
summary(seguros.pca.cor)
Importance of components:
                          PC1    PC2    PC3    PC4    PC5    PC6     PC7     PC8
Standard deviation     1.3816 1.2309 1.0474 1.0005 0.9848 0.9123 0.74030 0.35758
Proportion of Variance 0.2386 0.1894 0.1371 0.1251 0.1212 0.1040 0.06851 0.01598
Cumulative Proportion  0.2386 0.4280 0.5651 0.6902 0.8115 0.9155 0.98402 1.00000
fviz_eig(seguros.pca.cov)

fviz_eig(seguros.pca.cor)

K Means

Ver: https://uc-r.github.io/kmeans_clustering

library(cluster)
library(pracma)

Attaching package: ‘pracma’

The following object is masked from ‘package:e1071’:

    sigmoid

The following object is masked from ‘package:car’:

    logit
esc01 <- function(x) {
  (x - min(x)) / (max(x) - min(x))
} 

metrica = function(datA_esc, kmax, nstart, f) {
  sil = array()
  # within-cluster sum of square
  wss = array()
  
  datA_dist <- dist(
    datA_esc,
    method = "euclidean",
    diag = FALSE,
    upper = FALSE,
    p = 2
  )
  
  for (i in 2:kmax) {
    if (strcmp(f, "kmeans") == TRUE) { # centroide: tipico kmeans
      CL <- kmeans(
        datA_esc,
        centers = i,
        nstart = nstart,
        iter.max = kmax
      )
      wss[i] <- CL$tot.withinss 
      CL_sil <- silhouette(CL$cluster, datA_dist)
      sil[i] <- summary(CL_sil)$avg.width
    }
    if (strcmp(f, "pam") == TRUE) { # medoide: ojo porque este metodo tarda muchisimo 
      CL <- pam(
        x = datA_esc,
        k = i,
        diss = F,
        metric = "euclidean"
      )
      wss[i] <- CL$objective[1] 
      sil[i] <- CL$silinfo$avg.width
    }
  }
  
  return(data.frame(wss, sil))
}
kmax <- 7
nstart <- 25
# 2 opciones de escalamiento
# m1 <- metrica(apply(db.seguros.data, 2, esc01), kmax, "kmeans") # definida en la funcion esc01
m1 <- metrica(db.seguros.data.std, kmax, nstart, "kmeans") # tipica de la normal
did not converge in 7 iterations

Gráficos de los indicadores de clustering

par(mfrow = c(2, 1))

plot(
  2:kmax,
  m1$sil[2:kmax],
  col = 1,
  type = "b",
  pch = 19,
  frame = FALSE, 
  xlab = "Number of clusters K",
  ylab = "sil"
) 

plot(
  2:kmax,
  m1$wss[2:kmax],
  type = "b",
  pch = 19,
  frame = FALSE, 
  xlab="Number of clusters K",
  ylab="Total within-clusters sum of squares"
) 


fviz_nbclust(db.seguros.data.std, kmeans, method = 'silhouette')

fviz_nbclust(db.seguros.data.std, kmeans, method = 'wss')


gap_stat <- clusGap(
  db.seguros.data.std,
  FUN = kmeans,
  nstart = nstart,
  K.max = kmax,
  B = 50
)
Clustering k = 1,2,..., K.max (= 7): .. done
Bootstrapping, b = 1,2,..., B (= 50)  [one "." per sample]:
did not converge in 10 iterations
.
did not converge in 10 iterations
....
did not converge in 10 iterations
.
did not converge in 10 iterations
.......
did not converge in 10 iterations
.......
did not converge in 10 iterations
...
did not converge in 10 iterations
......
did not converge in 10 iterations
........
did not converge in 10 iterationsdid not converge in 10 iterations
......
did not converge in 10 iterations
....... 50 
fviz_gap_stat(gap_stat)

library(NbClust)

res.nbclust <- NbClust(
  data = db.seguros.data.std,
  distance = "euclidean",
  min.nc = 2,
  max.nc = kmax,
  method = 'kmeans'
)
*** : The Hubert index is a graphical method of determining the number of clusters.
                In the plot of Hubert index, we seek a significant knee that corresponds to a 
                significant increase of the value of the measure i.e the significant peak in Hubert
                index second differences plot. 
 

*** : The D index is a graphical method of determining the number of clusters. 
                In the plot of D index, we seek a significant knee (the significant peak in Dindex
                second differences plot) that corresponds to a significant increase of the value of
                the measure. 
 
******************************************************************* 
* Among all indices:                                                
* 11 proposed 2 as the best number of clusters 
* 2 proposed 3 as the best number of clusters 
* 3 proposed 4 as the best number of clusters 
* 3 proposed 5 as the best number of clusters 
* 3 proposed 6 as the best number of clusters 
* 2 proposed 7 as the best number of clusters 

                   ***** Conclusion *****                            
 
* According to the majority rule, the best number of clusters is  2 
 
 
******************************************************************* 

factoextra::fviz_nbclust(res.nbclust) +
  theme_minimal() +
  ggtitle("NbClust's optimal number of clusters")
la condición tiene longitud > 1 y sólo el primer elemento será usadola condición tiene longitud > 1 y sólo el primer elemento será usadola condición tiene longitud > 1 y sólo el primer elemento será usadola condición tiene longitud > 1 y sólo el primer elemento será usado
Among all indices: 
===================
* 2 proposed  0 as the best number of clusters
* 11 proposed  2 as the best number of clusters
* 2 proposed  3 as the best number of clusters
* 3 proposed  4 as the best number of clusters
* 3 proposed  5 as the best number of clusters
* 3 proposed  6 as the best number of clusters
* 2 proposed  7 as the best number of clusters

Conclusion
=========================
* According to the majority rule, the best number of clusters is  2 .

library(ggbiplot)

plot_ggbiplot <- function (clusters) {
  for (i in 1:4) {
    print(ggbiplot(
      seguros.pca.cor,
      choices = i:(i+1),
      obs.scale = 1,
      var.scale = 1,
      alpha = 0.5,
      groups = as.factor(clusters)
    ) +
      theme(legend.direction = "horizontal", legend.position = "top")
    )
  }
}

generate_clusters <- function (ncluster) {
  CL <- kmeans(
    db.seguros.data.std,
    # apply(db.seguros.data, 2, esc01),
    ncluster,
    nstart = nstart,
    iter.max = kmax
  )
  
  plot_ggbiplot(CL$cluster)
  
  return(CL)
}
models.kmeans6 <- generate_clusters(6)

models.kmeans5 <- generate_clusters(5)

models.kmeans4 <- generate_clusters(4)

models.kmeans3 <- generate_clusters(3)

models.kmeans2 <- generate_clusters(2)

library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
── Attaching packages ──────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse 1.3.0 ──
✓ tibble  3.0.1     ✓ dplyr   0.8.5
✓ tidyr   1.1.0     ✓ stringr 1.4.0
✓ readr   1.3.1     ✓ forcats 0.5.0
✓ purrr   0.3.4     
── Conflicts ─────────────────────────────────────────────────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
x dplyr::arrange()    masks plotly::arrange(), plyr::arrange()
x readr::col_factor() masks scales::col_factor()
x dplyr::combine()    masks gridExtra::combine()
x purrr::compact()    masks plyr::compact()
x dplyr::count()      masks plyr::count()
x purrr::cross()      masks pracma::cross()
x purrr::discard()    masks scales::discard()
x dplyr::failwith()   masks plyr::failwith()
x dplyr::filter()     masks plotly::filter(), stats::filter()
x dplyr::id()         masks plyr::id()
x dplyr::lag()        masks stats::lag()
x purrr::lift()       masks caret::lift()
x dplyr::mutate()     masks plotly::mutate(), plyr::mutate()
x dplyr::recode()     masks car::recode()
x dplyr::rename()     masks plotly::rename(), plyr::rename()
x dplyr::select()     masks plotly::select(), MASS::select()
x purrr::some()       masks car::some()
x dplyr::summarise()  masks plotly::summarise(), plyr::summarise()
x dplyr::summarize()  masks plyr::summarize()
library(ggiraphExtra)

plot_radar <- function (clusters) {
  df <- as.data.frame(db.seguros.data.std) %>% rownames_to_column()
  cluster_pos <- as.data.frame(clusters) %>% rownames_to_column()
  colnames(cluster_pos) <- c("rowname", "cluster")
  final <- inner_join(cluster_pos, df)
  
  ggRadar(
    final[-1],
    aes(group = cluster),
    rescale = FALSE,
    legend.position = 'none',
    size = 1,
    interactive = FALSE,
    use.label = TRUE
  ) +
    facet_wrap(~cluster) +
    scale_y_discrete(breaks = NULL) +
    theme(axis.text.x = element_text(size = 10)) +
    scale_fill_manual(values = rep('#1c6193', nrow(final))) +
    scale_color_manual(values = rep('#1c6193', nrow(final)))
}
plot_radar(models.kmeans6$cluster)
Joining, by = "rowname"

plot_radar(models.kmeans2$cluster)
Joining, by = "rowname"

Jerarquico

mat_dist <- dist(
  x = db.seguros.data.std,
  method = 'euclidean'
)

# Dendrogramas (según el tipo de segmentación jerárquica aplicada)  
hc_complete <- hclust(d = mat_dist, method = "complete") 
hc_average <- hclust(d = mat_dist, method = "average")
hc_single <- hclust(d = mat_dist, method = "single")
hc_ward <- hclust(d = mat_dist, method = "ward.D2")

#calculo del coeficiente de correlacion cofenetico
cor(x = mat_dist, cophenetic(hc_complete))
[1] 0.6185278
cor(x = mat_dist, cophenetic(hc_average))
[1] 0.7228582
cor(x = mat_dist, cophenetic(hc_single))
[1] 0.6075149
cor(x = mat_dist, cophenetic(hc_ward))
[1] 0.6638337
# construccion de un dendograma usando los resultados de la técnica de Ward
plot(hc_ward) # no se ve bien si hay muchos datos
rect.hclust(hc_ward, k = 2, border = "red") # con 2 grupos
rect.hclust(hc_ward, k = 6, border = "blue") # con 6 grupos

clusters6 <- cutree(hc_ward, k = 6)
plot_ggbiplot(clusters6)

plot_radar(clusters6)
Joining, by = "rowname"

clusters4 <- cutree(hc_ward, k = 4)
plot_ggbiplot(clusters4)

plot_radar(clusters4)
Joining, by = "rowname"

clusters2 <- cutree(hc_ward, k = 2)
plot_ggbiplot(clusters2)

plot_radar(clusters2)
Joining, by = "rowname"

LS0tCnRpdGxlOiAiVFAgQWlkIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKcGFyYW1zOgogIGRuaTogMzcxNzUyODcKLS0tCgojIERhdGFzZXRzCgogLSAqKlN1cGVydmlzYWRhKio6IF9hY2NpZGVudGVfCiAtICoqTm8gc3VwZXJ2aXNhZGEqKjogX3NlZ3Vyb3NfCgpgYGB7cn0KRE5JIDwtIGFzLmludGVnZXIocGFyYW1zJGRuaSkKCmxpYnJhcnkocmVhZHhsKQoKZGIuYWNjaWRlbnRlIDwtIHJlYWRfZXhjZWwoCiAgJy4vVG9kYXMgbGFzIGJhc2VzLnhsc3gnLAogIHNoZWV0ID0gJ2FjY2lkZW50ZScsCiAgY29sX25hbWVzID0gVFJVRSwKKVssMTo1XQoKZGIuc2VndXJvcyA8LSByZWFkX2V4Y2VsKAogICcuL1RvZGFzIGxhcyBiYXNlcy54bHN4JywKICBzaGVldCA9ICdzZWd1cm9zJywKICBjb2xfbmFtZXMgPSBUUlVFLAopWywxOjhdCmBgYAoKT2J0ZW5lbW9zIGxvcyBkYXRvcyBzZWfDum4gbGFzIHJlc3RyaWNjaW9uZXMgZGVsIGVudW5jaWFkbzoKCiAtIF9hY2NpZGVudGVfIDogIjkwJSBkZSBsb3MgZGF0b3Mgc2Vnw7puIGxhIHNlbWlsbGEgc2V0ZWFkYSIKIC0gX3NlZ3Vyb3NfIDogIjc1JSBkZSBsb3MgZGF0b3Mgc2Vnw7puIGxhIHNlbWlsbGEgc2V0ZWFkYSIKCmBgYHtyfQpnZXRfZGF0YSA8LSBmdW5jdGlvbiAoZGF0b3MsIHJhdGlvKSB7CiAgbiA8LSByb3VuZChyYXRpbyAqIG5yb3coZGF0b3MpKQogIHNldC5zZWVkKEROSSkKICBjdWFsZXMgPC0gc2FtcGxlKAogICAgMTpucm93KGRhdG9zKSwKICAgIHNpemUgPSBuLAogICAgcmVwbGFjZSA9IEZBTFNFCiAgKQogIHJldHVybihkYXRvc1tjdWFsZXMsXSkKfQoKZGIuYWNjaWRlbnRlLmRhdGEgPC0gZ2V0X2RhdGEoZGIuYWNjaWRlbnRlLCAwLjkpCmRiLnNlZ3Vyb3MuZGF0YSA8LSBnZXRfZGF0YShkYi5zZWd1cm9zLCAwLjc1KQpgYGAKCiMgU3VwZXJ2aXNhZG8KCmBgYHtyfQojIE1lIGZpam8gc2kgaGF5IGRhdG9zIGZhbHRhbnRlcwpwcmludChkYi5hY2NpZGVudGUuZGF0YVtyb3dTdW1zKGlzLm5hKGRiLmFjY2lkZW50ZS5kYXRhKSkgPiAwLF0pCgpkYi5hY2NpZGVudGUuZGF0YSR2ZWhpY3VsbyA8LSBOVUxMCmRiLmFjY2lkZW50ZS5jb2xzLnZhcnMgPC0gc2V0ZGlmZihjb2xuYW1lcyhkYi5hY2NpZGVudGUuZGF0YSksIGMoImdyYXZlIikpCmBgYAoKIyMgUENBCgotIFZlciBFamVtcGxvIDMuMTEgKDEwMCkKLSBWZXIgaHR0cDovL3d3dy5zdGhkYS5jb20vZW5nbGlzaC9hcnRpY2xlcy8zMS1wcmluY2lwYWwtY29tcG9uZW50LW1ldGhvZHMtaW4tci1wcmFjdGljYWwtZ3VpZGUvMTE4LXByaW5jaXBhbC1jb21wb25lbnQtYW5hbHlzaXMtaW4tci1wcmNvbXAtdnMtcHJpbmNvbXAvCgpgYGB7cn0KbGlicmFyeShmYWN0b2V4dHJhKQoKYWNjaWRlbnRlLnBjYS5jb3YgPC0gcHJjb21wKGRiLmFjY2lkZW50ZS5kYXRhWyxkYi5hY2NpZGVudGUuY29scy52YXJzXSwgY2VudGVyID0gVFJVRSwgc2NhbGUuID0gRkFMU0UpCmFjY2lkZW50ZS5wY2EuY29yIDwtIHByY29tcChkYi5hY2NpZGVudGUuZGF0YVssZGIuYWNjaWRlbnRlLmNvbHMudmFyc10sIGNlbnRlciA9IFRSVUUsIHNjYWxlLiA9IFRSVUUpCnN1bW1hcnkoYWNjaWRlbnRlLnBjYS5jb3YpCnN1bW1hcnkoYWNjaWRlbnRlLnBjYS5jb3IpCgpmdml6X2VpZyhhY2NpZGVudGUucGNhLmNvdikKZnZpel9laWcoYWNjaWRlbnRlLnBjYS5jb3IpCmBgYAoKCiMjIFBlcmZpbGVzIE1lZGlvcwoKLSBWZXIgZWplbXBsbyA4LjQgKDI4NykKCmBgYHtyfQphY2NpZGVudGUuZ3JhdmUgPC0gZGIuYWNjaWRlbnRlLmRhdGFbZGIuYWNjaWRlbnRlLmRhdGEkZ3JhdmUgPT0gMSxdWyxkYi5hY2NpZGVudGUuY29scy52YXJzXQphY2NpZGVudGUubm9fZ3JhdmUgPC0gZGIuYWNjaWRlbnRlLmRhdGFbZGIuYWNjaWRlbnRlLmRhdGEkZ3JhdmUgPT0gMCxdWyxkYi5hY2NpZGVudGUuY29scy52YXJzXQoKYWNjaWRlbnRlLm1lYW4uY29uanVudGEgPC0gYXBwbHkoZGIuYWNjaWRlbnRlLmRhdGFbLGRiLmFjY2lkZW50ZS5jb2xzLnZhcnNdLCAyLCBtZWFuKQphY2NpZGVudGUubWVhbi5ncmF2ZSA8LSBhcHBseShhY2NpZGVudGUuZ3JhdmUsIDIsIG1lYW4pCmFjY2lkZW50ZS5tZWFuLmdyYXZlLm1hdHJpeCA8LSBhcy5tYXRyaXgoYWNjaWRlbnRlLm1lYW4uZ3JhdmUpCmFjY2lkZW50ZS5tZWFuLm5vX2dyYXZlIDwtIGFwcGx5KGFjY2lkZW50ZS5ub19ncmF2ZSwgMiwgbWVhbikKYWNjaWRlbnRlLm1lYW4ubm9fZ3JhdmUubWF0cml4IDwtIGFzLm1hdHJpeChhY2NpZGVudGUubWVhbi5ub19ncmF2ZSkKCmFjY2lkZW50ZS5tZWFucy5kYXRhIDwtIGNiaW5kKAogIHJlcCgxOmxlbmd0aChkYi5hY2NpZGVudGUuY29scy52YXJzKSwgMiksCiAgcmJpbmQoYWNjaWRlbnRlLm1lYW4uZ3JhdmUubWF0cml4LCBhY2NpZGVudGUubWVhbi5ub19ncmF2ZS5tYXRyaXgpLAogIGMocmVwKCJncmF2ZSIsIGxlbmd0aChkYi5hY2NpZGVudGUuY29scy52YXJzKSksIHJlcCgibm8gZ3JhdmUiLCBsZW5ndGgoZGIuYWNjaWRlbnRlLmNvbHMudmFycykpKQopCmNvbG5hbWVzKGFjY2lkZW50ZS5tZWFucy5kYXRhKSA8LSBjKCd2YXJzJywgJ21lYW5zJywgJ2dyYXZlJykKYWNjaWRlbnRlLm1lYW5zLmRhdGEgPC0gZGF0YS5mcmFtZShhY2NpZGVudGUubWVhbnMuZGF0YSkKYWNjaWRlbnRlLm1lYW5zLmRhdGEkdmFycyA8LSBhcy5udW1lcmljKGFjY2lkZW50ZS5tZWFucy5kYXRhJHZhcnMpCmFjY2lkZW50ZS5tZWFucy5kYXRhJG1lYW5zIDwtIGFzLm51bWVyaWMoYWNjaWRlbnRlLm1lYW5zLmRhdGEkbWVhbnMpCgpnZ3Bsb3QoYWNjaWRlbnRlLm1lYW5zLmRhdGEsIGFlcyh4ID0gdmFycywgeSA9IG1lYW5zLCBjb2xvdXIgPSBncmF2ZSkpICsKICBnZW9tX2xpbmUoKSArCiAgc2NhbGVfeF9kaXNjcmV0ZSgKICAgIGxpbWl0ID0gYXMuY2hhcmFjdGVyKDE6bGVuZ3RoKGRiLmFjY2lkZW50ZS5jb2xzLnZhcnMpKSwKICAgIGxhYmVscyA9IGRiLmFjY2lkZW50ZS5jb2xzLnZhcnMKICApICsKICB4bGFiKCJWYXJpYWJsZXMiKSArCiAgeWxhYigiTWVkaWFzIikKYGBgCgotIFZlciBlamVtcGxvIDkuNSAoMzEyKQoKYGBge3J9CnJlcXVpcmUocmVzaGFwZTIpCmRiLmFjY2lkZW50ZS5kYXRhLm0gPC0gbWVsdChkYi5hY2NpZGVudGUuZGF0YSwgaWQudmFyID0gJ2dyYXZlJykKZGIuYWNjaWRlbnRlLmRhdGEubSRncmF2ZSA8LSBhcy5jaGFyYWN0ZXIoZGIuYWNjaWRlbnRlLmRhdGEubSRncmF2ZSkKCnJlcXVpcmUoZ2dwbG90MikKZ2dwbG90KGRhdGEgPSBkYi5hY2NpZGVudGUuZGF0YS5tLCBhZXMoeD12YXJpYWJsZSwgeT12YWx1ZSkpICsgCiAgZ2VvbV9ib3hwbG90KGFlcyhmaWxsPWdyYXZlKSkgKwogIGZhY2V0X3dyYXAofnZhcmlhYmxlLCBzY2FsZXMgPSAiZnJlZSIpCmBgYAoKIyMjIFRlc3QgSG90ZWxsaW5nCgpDb21wYXJhciB2ZWN0b3JlcyBtZWRpb3MKCihoMDogRG9zIG1lZGlhcyBzb24gaWd1YWxlcywgcCA8IDAuMDUgPT4gUmVjaGF6byBoaXBvdGVzaXMgZGUgbWVkaWFzIGlndWFsZXMpCgotIFZlciBjw7NkaWdvIDkuNCAoMzEzKQoKYGBge3J9CmxpYnJhcnkoSG90ZWxsaW5nKQpmaXQgPC0gaG90ZWxsaW5nLnRlc3QoLn5ncmF2ZSwgZGF0YT1kYi5hY2NpZGVudGUuZGF0YSkKZml0CmBgYAoKIyMgTm9ybWFsaWRhZAoKIyMjIE5vcm1hbGlkYWQgVW5pdmFyaWFkYQoKYGBge3J9CmxpYnJhcnkobXZub3JtdGVzdCkKbGlicmFyeShjYXIpCgpwYXIobWZyb3c9YygyLDIpKQoKZm9yICh2YXIgaW4gZGIuYWNjaWRlbnRlLmNvbHMudmFycykgewogIHByaW50KHZhcikKICBwcmludChtc2hhcGlyby50ZXN0KHQoZGIuYWNjaWRlbnRlLmRhdGFbW3Zhcl1dKSkpCiAgcXFQbG90KAogICAgZGIuYWNjaWRlbnRlLmRhdGFbW3Zhcl1dLAogICAgeGxhYiA9ICdDdWFudGlsZXMgbm9ybWFsZXMnLAogICAgeWxhYiA9IHZhciwKICAgIGNvbCA9ICdncmVlbicsCiAgICBwY2ggPSAyMCwKICAgIGNvbC5saW5lcyA9ICdyb3lhbGJsdWUnLAogICAgbHdkID0gMSwKICApCn0KYGBgCgoKCgojIyMgTm9ybWFsaWRhZCBtdWx0aXZhcmlhZGEKClRlc3QgZGUgbm9ybWFsaWRhZCBtdWx0aXZhcmlhZGEgZGUgU2hhcGlyby1XaWxrCgooaDA6IERpc3RyaWJ1Y2lvbiBub3JtYWwsIHAgPCAwLjA1ID0+IFNlIHJlY2hhemEgaGlwb3Rlc2lzIGRlIG5vcm1hbGlkYWQpCgotIFZlciBjbGFzZSAxMwoKYGBge3J9Cm1zaGFwaXJvLnRlc3QodChkYi5hY2NpZGVudGUuZGF0YVssIGRiLmFjY2lkZW50ZS5jb2xzLnZhcnNdKSkKYGBgCgpgYGB7cn0KbXNoYXBpcm8udGVzdCh0KGRiLmFjY2lkZW50ZS5kYXRhW2RiLmFjY2lkZW50ZS5kYXRhJGdyYXZlID09IDAsIGRiLmFjY2lkZW50ZS5jb2xzLnZhcnNdKSkKYGBgCmBgYHtyfQptc2hhcGlyby50ZXN0KHQoZGIuYWNjaWRlbnRlLmRhdGFbZGIuYWNjaWRlbnRlLmRhdGEkZ3JhdmUgPT0gMSwgZGIuYWNjaWRlbnRlLmNvbHMudmFyc10pKQpgYGAKCiMjIElndWFsZGFkIGRlIG1hdHJpY2VzIGRlIHZhcmlhbnphcyB5IGNvdmFyaWFuemFzCgpNIFRlc3QgZGUgQm94IChoMDogSG9tb2dlbmllZGFkIGRlIG1hdHJpY2VzIGRlIHZhci1jb3YsIHAgPCAwLjA1ID0+IFNlIHJlY2hhemEgaGlwb3Rlc2lzIGRlIEhvbW9nZW5pZWRhZCBkZSBtYXRyaWNlcyBkZSB2YXItY292KQoKKEhvbW9jZWRhc3RpY2lkYWQpCgpFc3RlIHRlc3QgZXMgc2Vuc2libGUgYSBsYSBmYWx0YSBkZSBub3JtYWxpZGFkLiBVbmEgYWx0ZXJuYXRpdmEgcm9idXN0YSBlcyBlbCBfdGVzdCBkZSBMZXZlbmUgcGFyYSBkYXRvcyBtdWx0aXZhcmlhZG9zXy4KCgpgYGB7cn0KbGlicmFyeShiaW90b29scykgCmJveE0oZGF0YSA9IGRiLmFjY2lkZW50ZS5kYXRhWywgZGIuYWNjaWRlbnRlLmNvbHMudmFyc10sIGdyb3VwaW5nID0gZGIuYWNjaWRlbnRlLmRhdGEkZ3JhdmUpCmBgYAoKIyMgTWF0cmljZXMgZGUgY29ycmVsYWNpw7NuCgotIFZlciBjw7NkaWdvIDkuNSAoMzIwKQoKYGBge3J9CmxpYnJhcnkoY29ycGNvcikKbGlicmFyeShjb3JycGxvdCkKCmFjY2lkZW50ZS5jb3IgPC0gY29yKGRiLmFjY2lkZW50ZS5kYXRhWyxkYi5hY2NpZGVudGUuY29scy52YXJzXSkKY29ycnBsb3QoYWNjaWRlbnRlLmNvciwgdGwuY2V4ID0gMC43LCBjbC5jZXggPSAwLjcsIHRsLmNvbCA9ICJyb3lhbGJsdWUiKQpgYGAKCiMjIyBQb3IgY2xhc2UKCmBgYHtyfQphY2NpZGVudGUuY29yLm5vX2dyYXZlIDwtIGNvcihkYi5hY2NpZGVudGUuZGF0YVtkYi5hY2NpZGVudGUuZGF0YSRncmF2ZSA9PSAwLCBkYi5hY2NpZGVudGUuY29scy52YXJzXSkKYWNjaWRlbnRlLmNvci5ncmF2ZSA8LSBjb3IoZGIuYWNjaWRlbnRlLmRhdGFbZGIuYWNjaWRlbnRlLmRhdGEkZ3JhdmUgPT0gMSwgZGIuYWNjaWRlbnRlLmNvbHMudmFyc10pCgpwYXIobWZyb3cgPSBjKDEsIDIpKQpjb3JycGxvdChhY2NpZGVudGUuY29yLm5vX2dyYXZlLCB0bC5jZXggPSAwLjcsIGNsLmNleCA9IDAuNywgdGwuY29sID0gInJveWFsYmx1ZSIpCmNvcnJwbG90KGFjY2lkZW50ZS5jb3IuZ3JhdmUsIHRsLmNleCA9IDAuNywgY2wuY2V4ID0gMC43LCB0bC5jb2wgPSAicm95YWxibHVlIikKYGBgCgojIyBEaXZpc2nDs24gZGUgZGF0YXNldAoKYGBge3J9CnNwbGl0X3NldHMgPC0gZnVuY3Rpb24gKGRhdG9zLCByYXRpbykgewogIGlmIChtaXNzaW5nKHJhdGlvKSkgewogICAgcmF0aW8gPC0gMiAvMwogIH0KICAKICBuIDwtIHJvdW5kKHJhdGlvICogbnJvdyhkYXRvcykpCiAgc2V0LnNlZWQoRE5JKQogIGN1YWxlcyA8LSBzYW1wbGUoCiAgICAxOm5yb3coZGF0b3MpLAogICAgc2l6ZSA9IG4sCiAgICByZXBsYWNlID0gRkFMU0UKICApCiAgcmV0dXJuKGxpc3QoCiAgICAndHJhaW5pbmcnID0gZGF0b3NbY3VhbGVzLF0sCiAgICAndHJhaW5pbmdJbmRleGVzJyA9IGN1YWxlcywKICAgICd2YWxpZGF0aW9uJyA9IGRhdG9zWy1jdWFsZXMsXSwKICAgICd2YWxpZGF0aW9uSW5kZXhlcycgPSAtY3VhbGVzCiAgKSkKfQoKZGIuYWNjaWRlbnRlLmRhdGEuc3BsaXRzIDwtIHNwbGl0X3NldHMoZGIuYWNjaWRlbnRlLmRhdGEpCnRyYWluaW5nIDwtIGRiLmFjY2lkZW50ZS5kYXRhLnNwbGl0cyR0cmFpbmluZwpgYGAKCiMjIEFwbGljYW1vcyBMREEKCkFETCBlcyBzb2xvIHbDoWxpZG86CgotIE5vcm1hbGlkYWQgbXVsdGl2YXJpYWRvIChTaGFwaXJvLXdpbGsgbXVsdGl2YXJpYWRvKQotIEhvbW9jZWRhc3RpY2lkYWQgKHRlc3QgTSBCb3gpCi0gSW5kZXBlbmRlbmNpYQoKYGBge3J9Cm1vZGVscy5sZGEgPC0gbGRhKAogIGZvcm11bGEgPSBncmF2ZSB+IGFudGlnw7xlZGFkICsgYGVkYWQgY29uZHVjdG9yYCArIHBvdGVuY2lhLAogIGRhdGEgPSB0cmFpbmluZwopCgptb2RlbHMubGRhCmBgYAoKKipFcnJvcioqCgpgYGB7cn0KdmFsaWRhdGlvbiA8LSBkYi5hY2NpZGVudGUuZGF0YS5zcGxpdHMkdmFsaWRhdGlvbgoKbW9kZWxzLmxkYS5wcmVkaWN0IDwtIHByZWRpY3QoCiAgb2JqZWN0ID0gbW9kZWxzLmxkYSwKICBuZXdkYXRhID0gdmFsaWRhdGlvblssIGRiLmFjY2lkZW50ZS5jb2xzLnZhcnNdLAogIG1ldGhvZCA9ICdwcmVkaWN0aXZlJwopCgp0YWJsZSgKICB2YWxpZGF0aW9uJGdyYXZlLAogIG1vZGVscy5sZGEucHJlZGljdCRjbGFzcywKICBkbm4gPSBjKCJSZWFsIiwgIlByZWRpY2hhIikKKQpgYGAKCgpgYGB7cn0KbW9kZWxzLmxkYS5wcmVkaWN0LmVycm9yIDwtIG1lYW4odmFsaWRhdGlvbiRncmF2ZSAhPSBtb2RlbHMubGRhLnByZWRpY3QkY2xhc3MpICogMTAwCm1vZGVscy5sZGEucHJlZGljdC5lcnJvcgpgYGAKCmBgYHtyfQpsaWJyYXJ5KGNhcmV0KQptb2RlbHMubGRhLnByZWRpY3QuY29uZnVzaW9uTWF0cml4IDwtIGNvbmZ1c2lvbk1hdHJpeChhcy5mYWN0b3IodmFsaWRhdGlvbiRncmF2ZSksIG1vZGVscy5sZGEucHJlZGljdCRjbGFzcykKbW9kZWxzLmxkYS5wcmVkaWN0LmNvbmZ1c2lvbk1hdHJpeApgYGAKCgojIyBBcGxpY2Ftb3MgUURBCgpgYGB7cn0KbW9kZWxzLnFkYSA8LSBxZGEoCiAgZm9ybXVsYSA9IGdyYXZlIH4gYW50aWfDvGVkYWQgKyBgZWRhZCBjb25kdWN0b3JgICsgcG90ZW5jaWEsCiAgZGF0YSA9IHRyYWluaW5nCikKCm1vZGVscy5xZGEKYGBgCgpgYGB7cn0KbW9kZWxzLnFkYS5wcmVkaWN0IDwtIHByZWRpY3QoCiAgb2JqZWN0ID0gbW9kZWxzLnFkYSwKICBuZXdkYXRhID0gdmFsaWRhdGlvblssIGRiLmFjY2lkZW50ZS5jb2xzLnZhcnNdLAopCgp0YWJsZSgKICB2YWxpZGF0aW9uJGdyYXZlLAogIG1vZGVscy5xZGEucHJlZGljdCRjbGFzcywKICBkbm4gPSBjKCJSZWFsIiwgIlByZWRpY2hhIikKKQpgYGAKCgpgYGB7cn0KbW9kZWxzLnFkYS5wcmVkaWN0LmVycm9yIDwtIG1lYW4odmFsaWRhdGlvbiRncmF2ZSAhPSBtb2RlbHMucWRhLnByZWRpY3QkY2xhc3MpICogMTAwCm1vZGVscy5xZGEucHJlZGljdC5lcnJvcgpgYGAKCmBgYHtyfQptb2RlbHMucWRhLnByZWRpY3QuY29uZnVzaW9uTWF0cml4IDwtIGNvbmZ1c2lvbk1hdHJpeChhcy5mYWN0b3IodmFsaWRhdGlvbiRncmF2ZSksIG1vZGVscy5xZGEucHJlZGljdCRjbGFzcykKbW9kZWxzLnFkYS5wcmVkaWN0LmNvbmZ1c2lvbk1hdHJpeApgYGAKCmBgYHtyfQojICoqUGFydGljaW9uZXMgcG9yIGNsYXNlKioKIyBsaWJyYXJ5KGtsYVIpCiMgCmRiLmFjY2lkZW50ZS5kYXRhLmZyYW1lIDwtIGRhdGEuZnJhbWUoCiAgIkFudGlndWVkYWQiID0gZGIuYWNjaWRlbnRlLmRhdGEkYW50aWfDvGVkYWQsCiAgIkVkYWRDb25kdWN0b3IiID0gZGIuYWNjaWRlbnRlLmRhdGEkYW50aWfDvGVkYWQsCiAgIlBvdGVuY2lhIiA9IGRiLmFjY2lkZW50ZS5kYXRhJHBvdGVuY2lhLAogICJHcmF2ZSIgPSBmYWN0b3IoZGIuYWNjaWRlbnRlLmRhdGEkZ3JhdmUpCikKIyAKIyBwYXJ0aW1hdCgKIyAgIEdyYXZlIH4gVmVoaWN1bG8gKyBBbnRpZ3VlZGFkICsgRWRhZENvbmR1Y3RvciArIFBvdGVuY2lhLAojICAgZGF0YSA9IGRiLmFjY2lkZW50ZS5kYXRhLmZyYW1lLAojICAgbWV0aG9kID0gJ3FkYScsCiMgICBpbWFnZS5jb2xvcnMgPSBjKCdjYWRldGJsdWUxJywgJ3BsdW0yJyksCiMgICBjb2wubWVhbiA9ICdyb3lhbGJsdWUnLAojICAgc3Vic2V0ID0gZGIuYWNjaWRlbnRlLmRhdGEuc3BsaXRzJHRyYWluaW5nSW5kZXhlcwojICkKYGBgCgojIyBBbHRlcm5hdGl2YSByb2J1c3RhCgpgYGB7cn0KbGlicmFyeShNQVNTKQoKbW9kZWxzLnJvYnVzdC5jb3Yubm9HcmF2ZSA8LSBjb3Yucm9iKAogIHRyYWluaW5nW3RyYWluaW5nJGdyYXZlID09IDAsIGRiLmFjY2lkZW50ZS5jb2xzLnZhcnNdLAogIG1ldGhvZCA9ICdtY2QnLAogIG5zYW1wID0gJ2Jlc3QnCikKCm1vZGVscy5yb2J1c3QuY292LmdyYXZlIDwtIGNvdi5yb2IoCiAgdHJhaW5pbmdbdHJhaW5pbmckZ3JhdmUgPT0gMSwgZGIuYWNjaWRlbnRlLmNvbHMudmFyc10sCiAgbWV0aG9kID0gJ21jZCcsCiAgbnNhbXAgPSAnYmVzdCcKKQoKbW9kZWxzLnJvYnVzdC5wcm9tLm5vR3JhdmUgPC0gcmVwKAogIG1vZGVscy5yb2J1c3QuY292Lm5vR3JhdmUkY2VudGVyLAogIG5yb3codmFsaWRhdGlvblt2YWxpZGF0aW9uJGdyYXZlID09IDAsIF0pCikKCm1vZGVscy5yb2J1c3QucHJvbS5ncmF2ZSA8LSByZXAoCiAgbW9kZWxzLnJvYnVzdC5jb3YuZ3JhdmUkY2VudGVyLAogIG5yb3codmFsaWRhdGlvblt2YWxpZGF0aW9uJGdyYXZlID09IDEsIF0pCikKCm1vZGVscy5yb2J1c3QudmFyLm5vR3JhdmUgPC0gYXMubWF0cml4KG1vZGVscy5yb2J1c3QuY292Lm5vR3JhdmUkY292KQptb2RlbHMucm9idXN0LnZhci5ncmF2ZSA8LSBhcy5tYXRyaXgobW9kZWxzLnJvYnVzdC5jb3YuZ3JhdmUkY292KQoKbW9kZWxzLnJvYnVzdC5EUi5ub0dyYXZlIDwtCiAgYXMubWF0cml4KAogICAgdmFsaWRhdGlvblssIGRiLmFjY2lkZW50ZS5jb2xzLnZhcnNdIC0KICAgICAgbW9kZWxzLnJvYnVzdC5wcm9tLm5vR3JhdmUKICApICUqJSBzb2x2ZShtb2RlbHMucm9idXN0LnZhci5ub0dyYXZlKSAlKiUgdChhcy5tYXRyaXgoCiAgICB2YWxpZGF0aW9uWywgZGIuYWNjaWRlbnRlLmNvbHMudmFyc10gLQogICAgICBtb2RlbHMucm9idXN0LnByb20ubm9HcmF2ZQogICkpCgptb2RlbHMucm9idXN0LkRSLmdyYXZlIDwtCiAgYXMubWF0cml4KAogICAgdmFsaWRhdGlvblssIGRiLmFjY2lkZW50ZS5jb2xzLnZhcnNdIC0KICAgICAgbW9kZWxzLnJvYnVzdC5wcm9tLmdyYXZlCiAgKSAlKiUgc29sdmUobW9kZWxzLnJvYnVzdC52YXIuZ3JhdmUpICUqJSB0KGFzLm1hdHJpeCgKICAgIHZhbGlkYXRpb25bLCBkYi5hY2NpZGVudGUuY29scy52YXJzXSAtCiAgICAgIG1vZGVscy5yb2J1c3QucHJvbS5ncmF2ZQogICkpCgptb2RlbHMucm9idXN0LnByZWRpY3QgPC0gcmVwKC0xLCBucm93KHZhbGlkYXRpb24pKQoKZm9yIChpIGluIDE6bnJvdyh2YWxpZGF0aW9uKSkgewogIGlmZWxzZSgKICAgIG1vZGVscy5yb2J1c3QuRFIubm9HcmF2ZVtpXSA8IG1vZGVscy5yb2J1c3QuRFIuZ3JhdmVbaV0sCiAgICBtb2RlbHMucm9idXN0LnByZWRpY3RbaV0gPC0gMCwKICAgIG1vZGVscy5yb2J1c3QucHJlZGljdFtpXSA8LSAxCiAgKQp9Cgp0YWJsZSgKICB2YWxpZGF0aW9uJGdyYXZlLAogIG1vZGVscy5yb2J1c3QucHJlZGljdCwKICBkbm4gPSBjKCJSZWFsIiwgIlByZWRpY2hhIikKKQpgYGAKCmBgYHtyfQptb2RlbHMucm9idXN0LnByZWRpY3QuZXJyb3IgPC0gbWVhbih2YWxpZGF0aW9uJGdyYXZlICE9IG1vZGVscy5yb2J1c3QucHJlZGljdCkgKiAxMDAKbW9kZWxzLnJvYnVzdC5wcmVkaWN0LmVycm9yCmBgYAoKYGBge3J9Cm1vZGVscy5yb2J1c3QucHJlZGljdC5jb25mdXNpb25NYXRyaXggPC0gY29uZnVzaW9uTWF0cml4KGFzLmZhY3Rvcih2YWxpZGF0aW9uJGdyYXZlKSwgYXMuZmFjdG9yKG1vZGVscy5yb2J1c3QucHJlZGljdCkpCm1vZGVscy5yb2J1c3QucHJlZGljdC5jb25mdXNpb25NYXRyaXgKYGBgCgojIyBTVk0KCmBgYHtyfQpsaWJyYXJ5KGUxMDcxKQoKZGIuYWNjaWRlbnRlLmRhdGEuZnJhbWUuc3RkIDwtIGRhdGEuZnJhbWUoc2NhbGUoc3Vic2V0KGRiLmFjY2lkZW50ZS5kYXRhLmZyYW1lLCBzZWxlY3QgPSAtR3JhdmUpKSkKZGIuYWNjaWRlbnRlLmRhdGEuZnJhbWUuc3RkJEdyYXZlIDwtIGRiLmFjY2lkZW50ZS5kYXRhLmZyYW1lJEdyYXZlCgptb2RlbHMuc3ZtIDwtIHN2bSgKICBHcmF2ZSB+IEFudGlndWVkYWQgKyBFZGFkQ29uZHVjdG9yICsgUG90ZW5jaWEsCiAgZGF0YSA9IGRiLmFjY2lkZW50ZS5kYXRhLmZyYW1lLnN0ZFtkYi5hY2NpZGVudGUuZGF0YS5zcGxpdHMkdHJhaW5pbmdJbmRleGVzLF0sCiAgbWV0aG9kID0gJ0MtY2xhc3NpZmljYXRpb24nLAogIGtlcm5lbCA9ICdyYWRpYWwnLAogIGNvc3QgPSAxMCwKICBnYW1tYSA9IC4xCikKCm1vZGVscy5zdm0ucHJlZGljdCA8LSBwcmVkaWN0KG1vZGVscy5zdm0sIGRiLmFjY2lkZW50ZS5kYXRhLmZyYW1lLnN0ZFtkYi5hY2NpZGVudGUuZGF0YS5zcGxpdHMkdmFsaWRhdGlvbkluZGV4ZXMsXSkKbW9kZWxzLnN2bS5wcmVkaWN0Q2xhc3MgPC0gYXMuZmFjdG9yKGFzLmludGVnZXIobW9kZWxzLnN2bS5wcmVkaWN0KSAtIDEpCgp0YWJsZSgKICB2YWxpZGF0aW9uJGdyYXZlLAogIG1vZGVscy5zdm0ucHJlZGljdCwKICBkbm4gPSBjKCJSZWFsIiwgIlByZWRpY2hhIikKKQpgYGAKCmBgYHtyfQptb2RlbHMuc3ZtLnByZWRpY3QuZXJyb3IgPC0gbWVhbih2YWxpZGF0aW9uJGdyYXZlICE9IG1vZGVscy5zdm0ucHJlZGljdENsYXNzKSAqIDEwMAptb2RlbHMuc3ZtLnByZWRpY3QuZXJyb3IKYGBgCgpgYGB7cn0KbW9kZWxzLnN2bS5wcmVkaWN0LmNvbmZ1c2lvbk1hdHJpeCA8LSBjb25mdXNpb25NYXRyaXgoYXMuZmFjdG9yKHZhbGlkYXRpb24kZ3JhdmUpLCBtb2RlbHMuc3ZtLnByZWRpY3RDbGFzcykKbW9kZWxzLnN2bS5wcmVkaWN0LmNvbmZ1c2lvbk1hdHJpeApgYGAKCmBgYHtyfQpsaWJyYXJ5KGdyaWRFeHRyYSkKCmdyaWQuYXJyYW5nZSgKICBnZ3Bsb3QoCiAgICBkYi5hY2NpZGVudGUuZGF0YS5mcmFtZS5zdGQsCiAgICBhZXMoeCA9IEVkYWRDb25kdWN0b3IsIHkgPSBBbnRpZ3VlZGFkKQogICkgKwogICAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBHcmF2ZSkpLAogIGdncGxvdCgKICAgIGRiLmFjY2lkZW50ZS5kYXRhLmZyYW1lLnN0ZCwKICAgIGFlcyh4ID0gRWRhZENvbmR1Y3RvciwgeSA9IFBvdGVuY2lhKQogICkgKwogICAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBHcmF2ZSkpLAogIGdncGxvdCgKICAgIGRiLmFjY2lkZW50ZS5kYXRhLmZyYW1lLnN0ZCwKICAgIGFlcyh4ID0gQW50aWd1ZWRhZCwgeSA9IFBvdGVuY2lhKQogICkgKwogICAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSBHcmF2ZSkpLAogIG5yb3cgPSAyCikKYGBgCmBgYHtyfQpsaWJyYXJ5KGdnYmlwbG90KQoKZ2diaXBsb3QoCiAgYWNjaWRlbnRlLnBjYS5jb3IsCiAgY2hvaWNlcyA9IDE6MiwKICBvYnMuc2NhbGUgPSAxLAogIHZhci5zY2FsZSA9IDEsCiAgYWxwaGEgPSAwLjUsCiAgZ3JvdXBzID0gYXMuZmFjdG9yKGRiLmFjY2lkZW50ZS5kYXRhJGdyYXZlKQopICsKICB0aGVtZShsZWdlbmQuZGlyZWN0aW9uID0gImhvcml6b250YWwiLCBsZWdlbmQucG9zaXRpb24gPSAidG9wIikKYGBgCgpgYGB7cn0KbGlicmFyeShwbG90bHkpCnBsb3RfbHkoCiAgeCA9IGRiLmFjY2lkZW50ZS5kYXRhLmZyYW1lLnN0ZCRFZGFkQ29uZHVjdG9yLAogIHkgPSBkYi5hY2NpZGVudGUuZGF0YS5mcmFtZS5zdGQkQW50aWd1ZWRhZCwKICB6ID0gZGIuYWNjaWRlbnRlLmRhdGEuZnJhbWUuc3RkJFBvdGVuY2lhLAogIHR5cGUgPSAic2NhdHRlcjNkIiwKICBtb2RlID0gIm1hcmtlcnMiLAogIGNvbG9yID0gZGIuYWNjaWRlbnRlLmRhdGEuZnJhbWUuc3RkJEdyYXZlCikKYGBgCgojIyBSZWdyZXNpb24gbG9naXN0aWNhCgpWZXIgQ8OzZGlnbyA5LjkgKDM0MSkKCmBgYHtyfQpwYXIobWZjb2wgPSBjKDIsMikpCmZvciAodmFyIGluIGRiLmFjY2lkZW50ZS5jb2xzLnZhcnMpIHsKICBwcmludCh2YXIpCiAgbW9kZWxvX2xvZ2lzdGljbyA8LSBnbG0oCiAgICBncmF2ZSB+IHZhciwKICAgIGRhdGEgPSBkYXRhLmZyYW1lKAogICAgICB2YXIgPSB0cmFpbmluZ1tbdmFyXV0sCiAgICAgIGdyYXZlID0gdHJhaW5pbmckZ3JhdmUKICAgICksCiAgICBmYW1pbHkgPSAnYmlub21pYWwnCiAgKQogIHByaW50KHN1bW1hcnkobW9kZWxvX2xvZ2lzdGljbykpCiAgCiAgcGxvdCgKICAgIHRyYWluaW5nW1t2YXJdXSwKICAgIHRyYWluaW5nJGdyYXZlLAogICAgY29sID0gJ3JveWFsYmx1ZScsCiAgICB4bGFiID0gdmFyLAogICAgeWxhYiA9ICdQKGdyYXZlKScKICApCiAgY3VydmUoCiAgICBwcmVkaWN0KAogICAgICBtb2RlbG9fbG9naXN0aWNvLAogICAgICBkYXRhLmZyYW1lKHZhciA9IHgpLAogICAgICB0eXBlID0gJ3Jlc3BvbnNlJwogICAgKSwKICAgIGFkZCA9IFRSVUUsCiAgICBjb2wgPSAndmlvbGV0JywKICAgIGx3ZCA9IDIuNQogICkKICAKICBwcmVkaWNjaW9uZXMgPC0gaWZlbHNlKAogICAgdGVzdCA9IG1vZGVsb19sb2dpc3RpY28kZml0dGVkLnZhbHVlcyA+IDAuNSwKICAgIHllcyA9IDEsCiAgICBubyA9IDAKICApCiAgCiAgcHJpbnQocHJlZGljY2lvbmVzKQogIAogIHByaW50KAogICAgdGFibGUoCiAgICAgIHRyYWluaW5nJGdyYXZlLAogICAgICBwcmVkaWNjaW9uZXMsCiAgICAgIGRubiA9IGMoJ09ic2VydmFjaW9uZXMnLCAnUHJlZGljY2lvbmVzJykKICAgICkKICApCiAgCiAgcHJpbnQoCiAgICBtZWFuKHRyYWluaW5nJGdyYXZlICE9IHByZWRpY2Npb25lcykgKiAxMDAKICApCn0KYGBgCgpgYGB7cn0KbW9kZWxzLmxnIDwtIGdsbSgKICBhcy5mYWN0b3IoZ3JhdmUpIH4gYW50aWfDvGVkYWQgKyBgZWRhZCBjb25kdWN0b3JgICsgcG90ZW5jaWEsCiAgZGF0YSA9IHRyYWluaW5nLAogIGZhbWlseSA9ICdiaW5vbWlhbCcKKQoKc3VtbWFyeShtb2RlbHMubGcpCmBgYAoKYGBge3J9Cm1vZGVscy5sZy5wcmVkaWN0IDwtIHByZWRpY3QoCiAgb2JqZWN0ID0gbW9kZWxzLmxnLAogIG5ld2RhdGEgPSB2YWxpZGF0aW9uWywgZGIuYWNjaWRlbnRlLmNvbHMudmFyc10sCiAgbWV0aG9kID0gJ3Jlc3BvbnNlJwopCgptb2RlbHMubGcucHJlZGljdENsYXNzIDwtIGlmZWxzZShtb2RlbHMubGcucHJlZGljdCA+IDAuNSwgMSwgMCkKCnRhYmxlKAogIHZhbGlkYXRpb24kZ3JhdmUsCiAgbW9kZWxzLmxnLnByZWRpY3RDbGFzcywKICBkbm4gPSBjKCJSZWFsIiwgIlByZWRpY2hhIikKKQpgYGAKCmBgYHtyfQptb2RlbHMubGcucHJlZGljdC5jb25mdXNpb25NYXRyaXggPC0gY29uZnVzaW9uTWF0cml4KGFzLmZhY3Rvcih2YWxpZGF0aW9uJGdyYXZlKSwgYXMuZmFjdG9yKG1vZGVscy5sZy5wcmVkaWN0Q2xhc3MpKQptb2RlbHMubGcucHJlZGljdC5jb25mdXNpb25NYXRyaXgKYGBgCgpQcm9iYW1vcyBzb2xvIGVkYWQgY29uZHVjdG9yCgpgYGB7cn0KbW9kZWxzLmxnMiA8LSBnbG0oCiAgYXMuZmFjdG9yKGdyYXZlKSB+IGBlZGFkIGNvbmR1Y3RvcmAsCiAgZGF0YSA9IHRyYWluaW5nLAogIGZhbWlseSA9ICdiaW5vbWlhbCcKKQoKc3VtbWFyeShtb2RlbHMubGcyKQpgYGAKCmBgYHtyfQptb2RlbHMubGcyLnByZWRpY3QgPC0gcHJlZGljdCgKICBvYmplY3QgPSBtb2RlbHMubGcyLAogIG5ld2RhdGEgPSB2YWxpZGF0aW9uWywgZGIuYWNjaWRlbnRlLmNvbHMudmFyc10sCiAgbWV0aG9kID0gJ3Jlc3BvbnNlJwopCgptb2RlbHMubGcyLnByZWRpY3RDbGFzcyA8LSBpZmVsc2UobW9kZWxzLmxnMi5wcmVkaWN0ID4gMC41LCAxLCAwKQoKdGFibGUoCiAgdmFsaWRhdGlvbiRncmF2ZSwKICBtb2RlbHMubGcyLnByZWRpY3RDbGFzcywKICBkbm4gPSBjKCJSZWFsIiwgIlByZWRpY2hhIikKKQpgYGAKCmBgYHtyfQptb2RlbHMubGcyLnByZWRpY3QuY29uZnVzaW9uTWF0cml4IDwtIGNvbmZ1c2lvbk1hdHJpeChhcy5mYWN0b3IodmFsaWRhdGlvbiRncmF2ZSksIGZhY3Rvcihtb2RlbHMubGcyLnByZWRpY3RDbGFzcywgYygwLCAxKSkpCm1vZGVscy5sZzIucHJlZGljdC5jb25mdXNpb25NYXRyaXgKYGBgCiMjIFJlc3VsdGFkb3MKCmBgYHtyfQpyZXN1bHRhZG9zLmFjY2lkZW50ZSA8LSBkYXRhLmZyYW1lKAogIG1vZGVsbyA9IGMoCiAgICAnbGRhJywKICAgICdxZGEnLAogICAgJ3JkYScsCiAgICAnc3ZtJywKICAgICdsb2coZWRhZCBjb25kdWN0b3IpJywKICAgICdsb2cnCiAgKSwKICBleGFjdGl0dWQgPSBjKAogICAgMC44MzMzLAogICAgMC42NjY3LAogICAgMC41LAogICAgMC43NSwKICAgIDAuNzUsCiAgICAwLjgzMzMKICApLAogIHNlbnNpYmlsaWRhZCA9IGMoCiAgICAxLAogICAgMC44MzMzLAogICAgMC42MjUwLAogICAgMC43MjczLAogICAgMC44NTcxLAogICAgMC44NzUwCiAgKSwKICBlc3BlY2lmaWNpZGFkID0gYygKICAgIDAuNjY2NywKICAgIDAuNTAwMCwKICAgIDAuMjUwMCwKICAgIDEsCiAgICAwLjYsCiAgICAwLjc1MDAKICApCikKCmdncGxvdChyZXN1bHRhZG9zLmFjY2lkZW50ZSwgYWVzKHggPSBtb2RlbG8pKSArCiAgZ2VvbV9saW5lKGFlcyh5ID0gZXhhY3RpdHVkLCBncm91cCA9IDEsIGNvbG9yID0gJ2V4YWN0aXR1ZCcpKSArCiAgZ2VvbV9wb2ludChhZXMoeSA9IGV4YWN0aXR1ZCwgZ3JvdXAgPSAxLCBjb2xvciA9ICdleGFjdGl0dWQnKSkgKwogIGdlb21fbGluZShhZXMoeSA9IHNlbnNpYmlsaWRhZCwgZ3JvdXAgPSAyLCBjb2xvciA9ICdzZW5zaWJpbGlkYWQnKSkgKwogIGdlb21fcG9pbnQoYWVzKHkgPSBzZW5zaWJpbGlkYWQsIGdyb3VwID0gMiwgY29sb3IgPSAnc2Vuc2liaWxpZGFkJykpICsKICBnZW9tX2xpbmUoYWVzKHkgPSBlc3BlY2lmaWNpZGFkLCBncm91cCA9IDMsIGNvbG9yID0gJ2VzcGVjaWZpY2lkYWQnKSkgKwogIGdlb21fcG9pbnQoYWVzKHkgPSBlc3BlY2lmaWNpZGFkLCBncm91cCA9IDMsIGNvbG9yID0gJ2VzcGVjaWZpY2lkYWQnKSkgKwogIGxhYnMoY29sb3IgPSAnbcOpdHJpY2EnKSArCiAgeWxhYigndmFsb3InKQpgYGAKCiMgQ2x1c3RlcnMKCmBgYHtyfQojIE1lIGZpam8gc2kgaGF5IGRhdG9zIGZhbHRhbnRlcwpwcmludChkYi5zZWd1cm9zLmRhdGFbcm93U3Vtcyhpcy5uYShkYi5zZWd1cm9zLmRhdGEpKSA+IDAsXSkKYGBgCgojIyBBbmFsaXNpcyBleHBsb3JhdG9yaW8KCmBgYHtyfQpkYi5zZWd1cm9zLmRhdGEubSA8LSBtZWx0KGRiLnNlZ3Vyb3MuZGF0YSkKCmdncGxvdChkYXRhID0gZGIuc2VndXJvcy5kYXRhLm0sIGFlcyh4PXZhcmlhYmxlLCB5PXZhbHVlKSkgKyAKICBnZW9tX2JveHBsb3QoYWVzKCkpICsKICBmYWNldF93cmFwKH52YXJpYWJsZSwgc2NhbGVzID0gImZyZWUiKQpgYGAKCmBgYHtyfQpwYXIobWZjb2wgPSBjKDMsMykpCgpmb3IgKGsgaW4gY29sbmFtZXMoZGIuc2VndXJvcy5kYXRhKSl7CiAgaGlzdCgKICAgIGRiLnNlZ3Vyb3MuZGF0YVtba11dLAogICAgcHJvYmEgPSBULAogICAgbWFpbiA9IG5hbWVzKGRiLnNlZ3Vyb3MuZGF0YVssIGtdKSwKICAgIDEwCiAgKQogIHgwIDwtIHNlcSgKICAgIG1pbihkYi5zZWd1cm9zLmRhdGFbLCBrXSksCiAgICBtYXgoZGIuc2VndXJvcy5kYXRhWywga10pLAogICAgbGUgPSA1MAogICkgCiAgbGluZXMoCiAgICB4MCwKICAgIGRub3JtKAogICAgICB4MCwKICAgICAgbWVhbihkYi5zZWd1cm9zLmRhdGFbW2tdXSksCiAgICAgIHNkKGRiLnNlZ3Vyb3MuZGF0YVtba11dKQogICAgKSwKICAgIGNvbCA9ICJyZWQiLAogICAgbHdkID0gMgogICkgCiAgZ3JpZCgpCn0KYGBgCgoqKk5vcm1hbGlkYWQqKgoKYGBge3J9CnBhcihtZmNvbCA9IGMoMiw0KSkKCmZvciAodmFyIGluIGNvbG5hbWVzKGRiLnNlZ3Vyb3MuZGF0YSkpIHsKICBwcmludCh2YXIpCiAgcHJpbnQobXNoYXBpcm8udGVzdCh0KGRiLnNlZ3Vyb3MuZGF0YVtbdmFyXV0pKSkKICBxcVBsb3QoCiAgICBkYi5zZWd1cm9zLmRhdGFbW3Zhcl1dLAogICAgeGxhYiA9ICdDdWFudGlsZXMgbm9ybWFsZXMnLAogICAgeWxhYiA9IHZhciwKICAgIGNvbCA9ICdncmVlbicsCiAgICBwY2ggPSAyMCwKICAgIGNvbC5saW5lcyA9ICdyb3lhbGJsdWUnLAogICAgbHdkID0gMSwKICApCiAgZ3JpZCgpCn0KYGBgCgoqKkVzdGFuZGFyaXpvIGRhdG9zKioKCmBgYHtyfQpkYi5zZWd1cm9zLmRhdGEuc3RkIDwtIGRhdGEuZnJhbWUoc2NhbGUoZGIuc2VndXJvcy5kYXRhKSkKYGBgCgpgYGB7cn0KZGIuc2VndXJvcy5kYXRhLnN0ZC5tIDwtIG1lbHQoZGIuc2VndXJvcy5kYXRhLnN0ZCkKCmdncGxvdChkYXRhID0gZGIuc2VndXJvcy5kYXRhLnN0ZC5tLCBhZXMoeD12YXJpYWJsZSwgeT12YWx1ZSkpICsgCiAgZ2VvbV9ib3hwbG90KGFlcygpKSArCiAgZmFjZXRfd3JhcCh+dmFyaWFibGUsIHNjYWxlcyA9ICJmcmVlIikKYGBgCgojIyMgQ29ycmVsYWNpb24KCmBgYHtyfQpzZWd1cm9zLmNvciA8LSBjb3IoZGIuc2VndXJvcy5kYXRhKQpjb3JycGxvdChzZWd1cm9zLmNvciwgdGwuY2V4ID0gMC43LCBjbC5jZXggPSAwLjcsIHRsLmNvbCA9ICJyb3lhbGJsdWUiKQpgYGAKCiMjIFBDQQoKYGBge3J9CnNlZ3Vyb3MucGNhLmNvdiA8LSBwcmNvbXAoZGIuc2VndXJvcy5kYXRhLCBjZW50ZXIgPSBUUlVFLCBzY2FsZS4gPSBGQUxTRSkKc2VndXJvcy5wY2EuY29yIDwtIHByY29tcChkYi5zZWd1cm9zLmRhdGEsIGNlbnRlciA9IFRSVUUsIHNjYWxlLiA9IFRSVUUpCnN1bW1hcnkoc2VndXJvcy5wY2EuY292KQpzdW1tYXJ5KHNlZ3Vyb3MucGNhLmNvcikKCmZ2aXpfZWlnKHNlZ3Vyb3MucGNhLmNvdikKZnZpel9laWcoc2VndXJvcy5wY2EuY29yKQpgYGAKCiMjIEsgTWVhbnMKClZlcjogaHR0cHM6Ly91Yy1yLmdpdGh1Yi5pby9rbWVhbnNfY2x1c3RlcmluZwoKYGBge3J9CmxpYnJhcnkoY2x1c3RlcikKbGlicmFyeShwcmFjbWEpCgplc2MwMSA8LSBmdW5jdGlvbih4KSB7CiAgKHggLSBtaW4oeCkpIC8gKG1heCh4KSAtIG1pbih4KSkKfSAKCm1ldHJpY2EgPSBmdW5jdGlvbihkYXRBX2VzYywga21heCwgbnN0YXJ0LCBmKSB7CiAgc2lsID0gYXJyYXkoKQogICMgd2l0aGluLWNsdXN0ZXIgc3VtIG9mIHNxdWFyZQogIHdzcyA9IGFycmF5KCkKICAKICBkYXRBX2Rpc3QgPC0gZGlzdCgKICAgIGRhdEFfZXNjLAogICAgbWV0aG9kID0gImV1Y2xpZGVhbiIsCiAgICBkaWFnID0gRkFMU0UsCiAgICB1cHBlciA9IEZBTFNFLAogICAgcCA9IDIKICApCiAgCiAgZm9yIChpIGluIDI6a21heCkgewogICAgaWYgKHN0cmNtcChmLCAia21lYW5zIikgPT0gVFJVRSkgeyAjIGNlbnRyb2lkZTogdGlwaWNvIGttZWFucwogICAgICBDTCA8LSBrbWVhbnMoCiAgICAgICAgZGF0QV9lc2MsCiAgICAgICAgY2VudGVycyA9IGksCiAgICAgICAgbnN0YXJ0ID0gbnN0YXJ0LAogICAgICAgIGl0ZXIubWF4ID0ga21heAogICAgICApCiAgICAgIHdzc1tpXSA8LSBDTCR0b3Qud2l0aGluc3MgCiAgICAgIENMX3NpbCA8LSBzaWxob3VldHRlKENMJGNsdXN0ZXIsIGRhdEFfZGlzdCkKICAgICAgc2lsW2ldIDwtIHN1bW1hcnkoQ0xfc2lsKSRhdmcud2lkdGgKICAgIH0KICAgIGlmIChzdHJjbXAoZiwgInBhbSIpID09IFRSVUUpIHsgIyBtZWRvaWRlOiBvam8gcG9ycXVlIGVzdGUgbWV0b2RvIHRhcmRhIG11Y2hpc2ltbyAKICAgICAgQ0wgPC0gcGFtKAogICAgICAgIHggPSBkYXRBX2VzYywKICAgICAgICBrID0gaSwKICAgICAgICBkaXNzID0gRiwKICAgICAgICBtZXRyaWMgPSAiZXVjbGlkZWFuIgogICAgICApCiAgICAgIHdzc1tpXSA8LSBDTCRvYmplY3RpdmVbMV0gCiAgICAgIHNpbFtpXSA8LSBDTCRzaWxpbmZvJGF2Zy53aWR0aAogICAgfQogIH0KICAKICByZXR1cm4oZGF0YS5mcmFtZSh3c3MsIHNpbCkpCn0KYGBgCgpgYGB7cn0Ka21heCA8LSA3Cm5zdGFydCA8LSAyNQojIDIgb3BjaW9uZXMgZGUgZXNjYWxhbWllbnRvCiMgbTEgPC0gbWV0cmljYShhcHBseShkYi5zZWd1cm9zLmRhdGEsIDIsIGVzYzAxKSwga21heCwgImttZWFucyIpICMgZGVmaW5pZGEgZW4gbGEgZnVuY2lvbiBlc2MwMQptMSA8LSBtZXRyaWNhKGRiLnNlZ3Vyb3MuZGF0YS5zdGQsIGttYXgsIG5zdGFydCwgImttZWFucyIpICMgdGlwaWNhIGRlIGxhIG5vcm1hbApgYGAKCioqR3LDoWZpY29zIGRlIGxvcyBpbmRpY2Fkb3JlcyBkZSBjbHVzdGVyaW5nKioKCmBgYHtyfQpwYXIobWZyb3cgPSBjKDIsIDEpKQoKcGxvdCgKICAyOmttYXgsCiAgbTEkc2lsWzI6a21heF0sCiAgY29sID0gMSwKICB0eXBlID0gImIiLAogIHBjaCA9IDE5LAogIGZyYW1lID0gRkFMU0UsIAogIHhsYWIgPSAiTnVtYmVyIG9mIGNsdXN0ZXJzIEsiLAogIHlsYWIgPSAic2lsIgopIAoKcGxvdCgKICAyOmttYXgsCiAgbTEkd3NzWzI6a21heF0sCiAgdHlwZSA9ICJiIiwKICBwY2ggPSAxOSwKICBmcmFtZSA9IEZBTFNFLCAKICB4bGFiPSJOdW1iZXIgb2YgY2x1c3RlcnMgSyIsCiAgeWxhYj0iVG90YWwgd2l0aGluLWNsdXN0ZXJzIHN1bSBvZiBzcXVhcmVzIgopIAoKZnZpel9uYmNsdXN0KGRiLnNlZ3Vyb3MuZGF0YS5zdGQsIGttZWFucywgbWV0aG9kID0gJ3NpbGhvdWV0dGUnKQpmdml6X25iY2x1c3QoZGIuc2VndXJvcy5kYXRhLnN0ZCwga21lYW5zLCBtZXRob2QgPSAnd3NzJykKCmdhcF9zdGF0IDwtIGNsdXNHYXAoCiAgZGIuc2VndXJvcy5kYXRhLnN0ZCwKICBGVU4gPSBrbWVhbnMsCiAgbnN0YXJ0ID0gbnN0YXJ0LAogIEsubWF4ID0ga21heCwKICBCID0gNTAKKQpmdml6X2dhcF9zdGF0KGdhcF9zdGF0KQpgYGAKYGBge3J9CmxpYnJhcnkoTmJDbHVzdCkKCnJlcy5uYmNsdXN0IDwtIE5iQ2x1c3QoCiAgZGF0YSA9IGRiLnNlZ3Vyb3MuZGF0YS5zdGQsCiAgZGlzdGFuY2UgPSAiZXVjbGlkZWFuIiwKICBtaW4ubmMgPSAyLAogIG1heC5uYyA9IGttYXgsCiAgbWV0aG9kID0gJ2ttZWFucycKKQoKZmFjdG9leHRyYTo6ZnZpel9uYmNsdXN0KHJlcy5uYmNsdXN0KSArCiAgdGhlbWVfbWluaW1hbCgpICsKICBnZ3RpdGxlKCJOYkNsdXN0J3Mgb3B0aW1hbCBudW1iZXIgb2YgY2x1c3RlcnMiKQpgYGAKCmBgYHtyfQpsaWJyYXJ5KGdnYmlwbG90KQoKcGxvdF9nZ2JpcGxvdCA8LSBmdW5jdGlvbiAoY2x1c3RlcnMpIHsKICBmb3IgKGkgaW4gMTo0KSB7CiAgICBwcmludChnZ2JpcGxvdCgKICAgICAgc2VndXJvcy5wY2EuY29yLAogICAgICBjaG9pY2VzID0gaTooaSsxKSwKICAgICAgb2JzLnNjYWxlID0gMSwKICAgICAgdmFyLnNjYWxlID0gMSwKICAgICAgYWxwaGEgPSAwLjUsCiAgICAgIGdyb3VwcyA9IGFzLmZhY3RvcihjbHVzdGVycykKICAgICkgKwogICAgICB0aGVtZShsZWdlbmQuZGlyZWN0aW9uID0gImhvcml6b250YWwiLCBsZWdlbmQucG9zaXRpb24gPSAidG9wIikKICAgICkKICB9Cn0KCmdlbmVyYXRlX2NsdXN0ZXJzIDwtIGZ1bmN0aW9uIChuY2x1c3RlcikgewogIENMIDwtIGttZWFucygKICAgIGRiLnNlZ3Vyb3MuZGF0YS5zdGQsCiAgICAjIGFwcGx5KGRiLnNlZ3Vyb3MuZGF0YSwgMiwgZXNjMDEpLAogICAgbmNsdXN0ZXIsCiAgICBuc3RhcnQgPSBuc3RhcnQsCiAgICBpdGVyLm1heCA9IGttYXgKICApCiAgCiAgcGxvdF9nZ2JpcGxvdChDTCRjbHVzdGVyKQogIAogIHJldHVybihDTCkKfQpgYGAKCmBgYHtyfQptb2RlbHMua21lYW5zNiA8LSBnZW5lcmF0ZV9jbHVzdGVycyg2KQpgYGAKCmBgYHtyfQptb2RlbHMua21lYW5zNSA8LSBnZW5lcmF0ZV9jbHVzdGVycyg1KQpgYGAKCmBgYHtyfQptb2RlbHMua21lYW5zNCA8LSBnZW5lcmF0ZV9jbHVzdGVycyg0KQpgYGAKCmBgYHtyfQptb2RlbHMua21lYW5zMyA8LSBnZW5lcmF0ZV9jbHVzdGVycygzKQpgYGAKCmBgYHtyfQptb2RlbHMua21lYW5zMiA8LSBnZW5lcmF0ZV9jbHVzdGVycygyKQpgYGAKCmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShnZ2lyYXBoRXh0cmEpCgpwbG90X3JhZGFyIDwtIGZ1bmN0aW9uIChjbHVzdGVycykgewogIGRmIDwtIGFzLmRhdGEuZnJhbWUoZGIuc2VndXJvcy5kYXRhLnN0ZCkgJT4lIHJvd25hbWVzX3RvX2NvbHVtbigpCiAgY2x1c3Rlcl9wb3MgPC0gYXMuZGF0YS5mcmFtZShjbHVzdGVycykgJT4lIHJvd25hbWVzX3RvX2NvbHVtbigpCiAgY29sbmFtZXMoY2x1c3Rlcl9wb3MpIDwtIGMoInJvd25hbWUiLCAiY2x1c3RlciIpCiAgZmluYWwgPC0gaW5uZXJfam9pbihjbHVzdGVyX3BvcywgZGYpCiAgCiAgZ2dSYWRhcigKICAgIGZpbmFsWy0xXSwKICAgIGFlcyhncm91cCA9IGNsdXN0ZXIpLAogICAgcmVzY2FsZSA9IEZBTFNFLAogICAgbGVnZW5kLnBvc2l0aW9uID0gJ25vbmUnLAogICAgc2l6ZSA9IDEsCiAgICBpbnRlcmFjdGl2ZSA9IEZBTFNFLAogICAgdXNlLmxhYmVsID0gVFJVRQogICkgKwogICAgZmFjZXRfd3JhcCh+Y2x1c3RlcikgKwogICAgc2NhbGVfeV9kaXNjcmV0ZShicmVha3MgPSBOVUxMKSArCiAgICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTApKSArCiAgICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSByZXAoJyMxYzYxOTMnLCBucm93KGZpbmFsKSkpICsKICAgIHNjYWxlX2NvbG9yX21hbnVhbCh2YWx1ZXMgPSByZXAoJyMxYzYxOTMnLCBucm93KGZpbmFsKSkpCn0KYGBgCgpgYGB7cn0KcGxvdF9yYWRhcihtb2RlbHMua21lYW5zNiRjbHVzdGVyKQpgYGAKCmBgYHtyfQpwbG90X3JhZGFyKG1vZGVscy5rbWVhbnMyJGNsdXN0ZXIpCmBgYAoKIyMgSmVyYXJxdWljbwoKYGBge3J9Cm1hdF9kaXN0IDwtIGRpc3QoCiAgeCA9IGRiLnNlZ3Vyb3MuZGF0YS5zdGQsCiAgbWV0aG9kID0gJ2V1Y2xpZGVhbicKKQoKIyBEZW5kcm9ncmFtYXMgKHNlZ8O6biBlbCB0aXBvIGRlIHNlZ21lbnRhY2nDs24gamVyw6FycXVpY2EgYXBsaWNhZGEpICAKaGNfY29tcGxldGUgPC0gaGNsdXN0KGQgPSBtYXRfZGlzdCwgbWV0aG9kID0gImNvbXBsZXRlIikgCmhjX2F2ZXJhZ2UgPC0gaGNsdXN0KGQgPSBtYXRfZGlzdCwgbWV0aG9kID0gImF2ZXJhZ2UiKQpoY19zaW5nbGUgPC0gaGNsdXN0KGQgPSBtYXRfZGlzdCwgbWV0aG9kID0gInNpbmdsZSIpCmhjX3dhcmQgPC0gaGNsdXN0KGQgPSBtYXRfZGlzdCwgbWV0aG9kID0gIndhcmQuRDIiKQoKI2NhbGN1bG8gZGVsIGNvZWZpY2llbnRlIGRlIGNvcnJlbGFjaW9uIGNvZmVuZXRpY28KY29yKHggPSBtYXRfZGlzdCwgY29waGVuZXRpYyhoY19jb21wbGV0ZSkpCmBgYAoKYGBge3J9CmNvcih4ID0gbWF0X2Rpc3QsIGNvcGhlbmV0aWMoaGNfYXZlcmFnZSkpCmBgYAoKYGBge3J9CmNvcih4ID0gbWF0X2Rpc3QsIGNvcGhlbmV0aWMoaGNfc2luZ2xlKSkKYGBgCgpgYGB7cn0KY29yKHggPSBtYXRfZGlzdCwgY29waGVuZXRpYyhoY193YXJkKSkKYGBgCgpgYGB7cn0KIyBjb25zdHJ1Y2Npb24gZGUgdW4gZGVuZG9ncmFtYSB1c2FuZG8gbG9zIHJlc3VsdGFkb3MgZGUgbGEgdMOpY25pY2EgZGUgV2FyZApwbG90KGhjX3dhcmQpICMgbm8gc2UgdmUgYmllbiBzaSBoYXkgbXVjaG9zIGRhdG9zCnJlY3QuaGNsdXN0KGhjX3dhcmQsIGsgPSAyLCBib3JkZXIgPSAicmVkIikgIyBjb24gMiBncnVwb3MKcmVjdC5oY2x1c3QoaGNfd2FyZCwgayA9IDYsIGJvcmRlciA9ICJibHVlIikgIyBjb24gNiBncnVwb3MKYGBgCgpgYGB7cn0KY2x1c3RlcnM2IDwtIGN1dHJlZShoY193YXJkLCBrID0gNikKcGxvdF9nZ2JpcGxvdChjbHVzdGVyczYpCnBsb3RfcmFkYXIoY2x1c3RlcnM2KQpgYGAKCmBgYHtyfQpjbHVzdGVyczQgPC0gY3V0cmVlKGhjX3dhcmQsIGsgPSA0KQpwbG90X2dnYmlwbG90KGNsdXN0ZXJzNCkKcGxvdF9yYWRhcihjbHVzdGVyczQpCmBgYAoKYGBge3J9CmNsdXN0ZXJzMiA8LSBjdXRyZWUoaGNfd2FyZCwgayA9IDIpCnBsb3RfZ2diaXBsb3QoY2x1c3RlcnMyKQpwbG90X3JhZGFyKGNsdXN0ZXJzMikKYGBg