1: Preparacion de datos

1.A: Lectura y analisis de estructura del archivo

Cargamos los datos y estudiamos brevemente su composicion

# carga librerias tidyverse y ggplot2
suppressPackageStartupMessages(library(tidyverse))
suppressPackageStartupMessages(library(ggplot2))
suppressPackageStartupMessages(library(GGally))
suppressPackageStartupMessages(library(caret))
suppressPackageStartupMessages(library(modelr))
suppressPackageStartupMessages(library(pROC))
suppressPackageStartupMessages(library(broom))
suppressPackageStartupMessages(library(data.table))

#carga de datos
titanic.full.data <- read.csv('titanic_complete_train.csv', row.names = NULL, stringsAsFactors = TRUE, encoding = "UTF-8")

#verificaion de casos y variables
nrow(titanic.full.data)
[1] 891
str(titanic.full.data)
'data.frame':   891 obs. of  12 variables:
 $ PassengerId: int  1 2 3 4 5 6 7 8 9 10 ...
 $ Survived   : int  0 1 1 1 0 0 0 0 1 1 ...
 $ Pclass     : int  3 1 3 1 3 3 1 3 3 2 ...
 $ Name       : Factor w/ 891 levels "Abbing, Mr. Anthony",..: 109 191 358 277 16 559 520 629 417 581 ...
 $ Sex        : Factor w/ 2 levels "female","male": 2 1 1 1 2 2 2 2 1 1 ...
 $ Age        : num  22 38 26 35 35 ...
 $ SibSp      : int  1 1 0 1 0 0 0 3 0 1 ...
 $ Parch      : int  0 0 0 0 0 0 0 1 2 0 ...
 $ Ticket     : Factor w/ 681 levels "110152","110413",..: 524 597 670 50 473 276 86 396 345 133 ...
 $ Fare       : num  7.25 71.28 7.92 53.1 8.05 ...
 $ Cabin      : Factor w/ 147 levels "A10","A14","A16",..: NA 82 NA 56 NA NA 130 NA NA NA ...
 $ Embarked   : Factor w/ 3 levels "C","Q","S": 3 1 3 3 3 2 3 3 3 1 ...
summary(titanic.full.data)
  PassengerId       Survived          Pclass                                         Name         Sex           Age       
 Min.   :  1.0   Min.   :0.0000   Min.   :1.000   Abbing, Mr. Anthony                  :  1   female:314   Min.   : 0.42  
 1st Qu.:223.5   1st Qu.:0.0000   1st Qu.:2.000   Abbott, Mr. Rossmore Edward          :  1   male  :577   1st Qu.:21.75  
 Median :446.0   Median :0.0000   Median :3.000   Abbott, Mrs. Stanton (Rosa Hunt)     :  1                Median :26.51  
 Mean   :446.0   Mean   :0.3838   Mean   :2.309   Abelson, Mr. Samuel                  :  1                Mean   :29.32  
 3rd Qu.:668.5   3rd Qu.:1.0000   3rd Qu.:3.000   Abelson, Mrs. Samuel (Hannah Wizosky):  1                3rd Qu.:36.00  
 Max.   :891.0   Max.   :1.0000   Max.   :3.000   Adahl, Mr. Mauritz Nils Martin       :  1                Max.   :80.00  
                                                  (Other)                              :885                               
     SibSp           Parch             Ticket         Fare                Cabin     Embarked
 Min.   :0.000   Min.   :0.0000   1601    :  7   Min.   :  0.00   B96 B98    :  4   C:168   
 1st Qu.:0.000   1st Qu.:0.0000   347082  :  7   1st Qu.:  7.91   C23 C25 C27:  4   Q: 77   
 Median :0.000   Median :0.0000   CA. 2343:  7   Median : 14.45   G6         :  4   S:646   
 Mean   :0.523   Mean   :0.3816   3101295 :  6   Mean   : 32.20   C22 C26    :  3           
 3rd Qu.:1.000   3rd Qu.:0.0000   347088  :  6   3rd Qu.: 31.00   D          :  3           
 Max.   :8.000   Max.   :6.0000   CA 2144 :  6   Max.   :512.33   (Other)    :186           
                                  (Other) :852                    NA's       :687           

1.b,c: Preparacion de datos

Ajustamos los datos a factores, y reorganizamos algunas categorias

# Generamos una funcion que ajsuta los datos, para poder reutilizar despues
fix.data <- function(df)
{
  
  # Seleccionamos las columnas de interes
  filtered.df <- df %>% select( PassengerId, Survived, Pclass, Sex, Age, SibSp, Parch, Fare, Embarked ) %>%
  
    # convertimos a factor las columnas indicadas
    mutate_at(vars(Survived, Pclass, Embarked), as_factor)

  # reajustamos los factores para mas facil interpretacion
  levels(filtered.df$Survived) <- c('Died', 'Lived')
  levels(filtered.df$Pclass) <- c('1ra', '2da','3ra' )
  
  return(filtered.df)
}

# Tomamos los datos completos
titanic.data <- titanic.full.data %>% fix.data()

str(titanic.data)
'data.frame':   891 obs. of  9 variables:
 $ PassengerId: int  1 2 3 4 5 6 7 8 9 10 ...
 $ Survived   : Factor w/ 2 levels "Died","Lived": 1 2 2 2 1 1 1 1 2 2 ...
 $ Pclass     : Factor w/ 3 levels "1ra","2da","3ra": 3 1 3 1 3 3 1 3 3 2 ...
 $ Sex        : Factor w/ 2 levels "female","male": 2 1 1 1 2 2 2 2 1 1 ...
 $ Age        : num  22 38 26 35 35 ...
 $ SibSp      : int  1 1 0 1 0 0 0 3 0 1 ...
 $ Parch      : int  0 0 0 0 0 0 0 1 2 0 ...
 $ Fare       : num  7.25 71.28 7.92 53.1 8.05 ...
 $ Embarked   : Factor w/ 3 levels "C","Q","S": 3 1 3 3 3 2 3 3 3 1 ...

1.d: ggpairs

titanic.data %>%
  select(Survived, Pclass, Sex, Age, Fare) %>%
  ggpairs(progress = F, legend=3,aes(colour=Survived) ) +
     theme(legend.position = "bottom")

Se observa como las chances de sobrevivir cambian drasticamente con algunas variables (como sexo, clase o fare), y no tanto para otras (como edad). Esto nos da una pauta de que variables serian las mejores para predecir la probabilidad de supervivencia.

1.e Distribucion sobrevivientes vs no sobrevivientes

# creamos una funcion que nos ayuda a analizar frecuencias relativas y abosultas agrupadas
summary.group <- function( df, ... )
{
    group_var <- enquos(...)
    return ( df %>% 
        group_by( !!!group_var ) %>%
        summarize(n=n()) %>%
        mutate(freq=n/sum(n)) )
}

# y la aplicamos a diversas combinaciones
titanic.data %>% summary.group( Survived )
titanic.data %>% summary.group( Survived, Sex )
titanic.data %>% summary.group( Survived, Pclass )
titanic.data %>% summary.group( Survived, Pclass, Sex )

De la primera tabla se desprende el dato estadistico mas basico: solo un 38% del total sobrevivio la tragedia. Pero agrupando por diferentes categorias, podemos observar como esta proporcion no se mantiene cuando se discrimina por sexo, por clase, o por ambas a la vez.

1.f Particion de dataset

set.seed(456)

# Separamos el dataset en train y test
train.index <- createDataPartition(titanic.data$Survived, p = 0.7, list = FALSE, )
train.data <- titanic.data[train.index,]
validation.data <- titanic.data[-train.index,]

# analizamos la composicion de los mismos para 2 categorias
cbind(
  train.data %>% summary.group( Survived ),
  validation.data %>% summary.group( Survived )  )

cbind(
  train.data %>% summary.group( Sex ),
  validation.data %>% summary.group( Sex )
)

Por como generamos la particion del dataset, nos aseguramos de mantener la relacion entre fallecidos y no fallecidos (+/- el error de cuantizar a casos discretos). Sin embargo, no se cumple estrictamente para las otras clases ya que no se forzo esta restriccion para ellas; como por ejemplo, para la variable sexo en este caso.

2. Predicciones

2.a. Regresion logistica

Se genera la regresion logistica y se analizan los resultados

modelo <- glm(Survived ~ Pclass + Sex + Age, family = 'binomial', data=train.data)
summary(modelo)

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

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-2.4610  -0.6159  -0.3879   0.5782   2.5626  

Coefficients:
             Estimate Std. Error z value Pr(>|z|)    
(Intercept)  4.002327   0.472969   8.462  < 2e-16 ***
Pclass2da   -1.274428   0.325464  -3.916 9.01e-05 ***
Pclass3ra   -2.755265   0.317044  -8.690  < 2e-16 ***
Sexmale     -2.649583   0.228995 -11.571  < 2e-16 ***
Age         -0.040950   0.009559  -4.284 1.84e-05 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 832.49  on 624  degrees of freedom
Residual deviance: 535.18  on 620  degrees of freedom
AIC: 545.18

Number of Fisher Scoring iterations: 5

2.b. Interpretacion

A partir del modelo logistico, se observa los iguiente:

  • Todas las variables del modelo aparecen como significativas
  • Tomando como referencia basal la 1era clase, pertenecer a 2da clase disminuye las probabilidad de sobrevivir, y aun mas para 3ra clase
  • De forma semejante, ser hombre disminuye la probabilidad de sobrevivir
  • La edad parece tambien influir en la probabilidad de supervivencia (de forma negativa), pero con un efecto bastante menor al de las clases o sexo

2.c. Rose vs. Jack


# Funcion para crear un caso de estudio
crear_caso <- function( Pclass, Sex, Age )
{
    new=train.data[FALSE,] %>% select( Pclass, Sex, Age )
    new[1,] = list(Pclass, Sex, Age)
    return(new)
}


#Creamos los casos, y los 'etiquetamos'
casos <- do.call(rbind, list(
    crear_caso(Pclass="1ra", Sex="female", Age=17),
    crear_caso(Pclass="3ra", Sex="male", Age=20) 
    )) %>%
    
    # creamos columna temporal para agrupar los casos
    cbind(caso=c('Rose','Jack')) %>%

    #agrupamos los casos
    group_by(caso) %>% nest() %>%

    #hacemos las predicciones para cada caso
    mutate( prediction=map(data,function(data) data.frame(predict(modelo, data, type='response')))) %>%

    unnest(data) %>% unnest(prediction) %>%

    #descartamos la columna temporal de agrupacion
    ungroup() 

head(casos)

Observamos que, fiel a la pelicula, las chances de sobrevivir de Rose eran bastante mayor a las de Jack.

3. Generacion de modelos

  # planeamos los modelos en funcion de las variables
modelos <- tibble( formula=formulas(.response = ~Survived,
                         class.sex = ~Pclass+Sex,
                         fare.sex = ~Fare+Sex,
                         class.sex.fare = ~Pclass+Sex+Fare,
                         useless = ~Embarked+SibSp+Parch,
                         all = ~Pclass+Sex+Age+SibSp+Parch+Fare+Embarked,
                          ) ) %>%
  
  # Agregamos la descripcion de cada modelo
  mutate( desc=names(formula) ) %>%
  
  # Calculamos el modelo
  mutate( modelo=map( formula, ~glm(., family='binomial', data=train.data) ) ) %>%
  
  # extraigo las variables de interes del modelo
  mutate( glance=map(modelo,glance)) %>% unnest(glance)


# analizamos los factores de interes de los modelos
modelos %>% select(desc, deviance, null.deviance ) %>%
  
  # calculo el porcentaje explicado
  mutate(explained = 1-deviance/null.deviance) %>%
  
  # ordeno por deviance
  arrange(deviance)
NA

Observaciones:

  • el modelo que incluye todas las variables es el de menor deviance, como era de esperarse, ya que este posee la mayor cantidad de informacion para explicar lo observado
  • pudimos observar en 1.d que fare podria ayudar a explicar cierta variacion entre quienes sobrevivieron; pero esta variacion pareceria estar aun mejor explicada por la clase del pasajero. Esto se evidencia al ser el modelo de clase + sexo mejor que el de clase + fare.
  • Se observa tambien que hay Fare y clase parecerian estar correlacionados, al no disminuir mucho el deviance al incluir esta variable
  • de forma intencional se incluyo un modelo que agrupaba los parametros que parecian menos relevantes, y efectivamente observamos como este modelo es el de mayor deviance
  • El modelo de clase + sexo parece ser un buen compromiso entre simplicidad y bondad de ajuste, al trabajar con solo 2 variaables y explicar un buen porcentaje en comparacion a las alternativas

Mas alla de lo analizado, se procede a elegir el modelo ‘all’ para los siguientes puntos, ya que este presenta mejores caracteristicas para su analisis (mas alla de su complejidad)

4. Evaluacion de modelos

modelo.elegido <- modelos[modelos$desc=='all',]$modelo[[1]]

prediccion <- augment(modelo.elegido, type.predict='response' )

# Calculamos la curva ROC y la graficamos
roc.curve <- roc(response = prediccion$Survived, predictor = prediccion$.fitted)
Setting levels: control = Died, case = Lived
Setting direction: controls < cases
# Graficamos la curva
ggroc(list(all = roc.curve), size=1) + geom_abline(slope = 1, intercept = 1, linetype='dashed')


# y el violin plot
ggplot(prediccion, aes(x=Survived, y=.fitted, group=Survived,fill=factor(Survived))) + 
  geom_violin()


#observamos el AUC
print( roc.curve$auc )
Area under the curve: 0.8721

Se observa que el modelo predice con una buena precision los casos mas extremos: cuando la probabilidad de sobrevivir es muy alta o muy baja. Esto se aprecia en que la anchura en los graficos de violin en los extremos son muy diferentes en los extremos, y en la curva ROC se aprecia al observar que en ambos extremos de la curva esta se se alinea bastante con el eje de sensibilidad (al inicio) y con el de especificidad (al final). En el medio es donde se presta a mayor probabilidad de error, donde no resulta tan cual seria un punto de corte apropiado.

5. Eleccion del punto de corte

Realizamos nuevamente los mismos graficos, pero con los valores de validacion:

prediccion <- augment(modelo.elegido, newdata=validation.data, type.predict='response' )

# Calculamos la curva ROC y la graficamos
roc.curve <- roc(response = prediccion$Survived, predictor = prediccion$.fitted)
Setting levels: control = Died, case = Lived
Setting direction: controls < cases
# Graficamos la curva
ggroc(list(all = roc.curve), size=1) + geom_abline(slope = 1, intercept = 1, linetype='dashed')


# y el violin plot
ggplot(prediccion, aes(x=Survived, y=.fitted, group=Survived,fill=factor(Survived))) + 
  geom_violin()



help(confusionMatrix)

Vemos que se perdio un poco el poder de prediccion, lo cual es es esperable al validar el modelo con datos distintos a los de entrenamiento.

# Generamos una funcion que calcule los parametros de interes


generate.prediction <- function(curr.cutoff, prediccion, target)
{  
 # Hacer la prediccion con probabilidades
  predictions <- data.table( predict.prob=prediccion$.fitted) %>%
  
    # elegir un resultado en base al punto de corte
    mutate( predict.value=if_else( predict.prob>=curr.cutoff, 'Lived', 'Died') ) %>%
    
    # Adjutnar la columna de resultados verderos
    cbind( actual.value=target)  %>%
    
    # convertir a factor para comparacion
    mutate_at( vars(predict.value, actual.value), as_factor )
}


calc.metrics <- function( curr.cutoff, prediccion )
{
  predictions <- generate.prediction(curr.cutoff, prediccion, validation.data$Survived)

  # calculamos los parametros de la matriz de confusion
  confusion <- confusionMatrix(table(predictions$predict.value, predictions$actual.value ), positive='Lived' ) %>%
    
    # desagrupamos los valores de la matriz de confusion
    tidy() %>% select(term, estimate) %>%
    
    # Tomamos los valores que nos interesan
    filter(term %in% c('accuracy', 'specificity', 'recall', 'precision')) %>%
    
    # agregamos la columna de cutoff
    mutate(cutoff=curr.cutoff)
  
  return(confusion)
  
}

# preparo secuencia
seq( 0.1, 0.9, 0.01) %>%

  # calculo las metricas
  map_dfr( function(cutoff) calc.metrics(cutoff, prediccion) ) %>% 
  
  # las grafico
  ggplot( aes(x=cutoff, y=estimate, color=term)) + geom_line() + geom_vline(xintercept=0.54)

Elegir un punto de corte para el modelo sin un fin establecido resulta arbitrario ya que hay varios criterios y estos dependen del uso que se le de al modelo (por ejemplo, cual es el balance entre el costo de un falso positivo vs un falso negativo). Dada esta falta de contexto, se elige maximizar el accuracy para minimizar los errores en general, que podemos observar en el grafico que este punto corresponde aproximadamente al valor 0.54

# Genero la matrix de confusion del modelo
prediction <- generate.prediction(0.54, prediccion)
confusionMatrix(table(prediction$predict.value, prediction$actual.value ), positive='Lived' )
Confusion Matrix and Statistics

       
        Died Lived
  Died   147    35
  Lived   17    67
                                          
               Accuracy : 0.8045          
                 95% CI : (0.7517, 0.8504)
    No Information Rate : 0.6165          
    P-Value [Acc > NIR] : 3.037e-11       
                                          
                  Kappa : 0.5723          
                                          
 Mcnemar's Test P-Value : 0.0184          
                                          
            Sensitivity : 0.6569          
            Specificity : 0.8963          
         Pos Pred Value : 0.7976          
         Neg Pred Value : 0.8077          
             Prevalence : 0.3835          
         Detection Rate : 0.2519          
   Detection Prevalence : 0.3158          
      Balanced Accuracy : 0.7766          
                                          
       'Positive' Class : Lived           
                                          

Podemos apreciar como el valor de corte elegido con el criterio de mayor accuracy intenta maximizar la traza de la matriz de confusion (que corresponde a la suma de verdaderos positivos y verdadeos negativos), minimizando los falsos positivos (35) y los falsos negativos (17)

6. Dataset de testeo

#carga de datos
titanic.test.data <- read.csv('titanic_complete_test.csv', row.names = NULL, stringsAsFactors = TRUE, encoding = "UTF-8") %>% 
  fix.data()


test.prediction <- augment(modelo.elegido, newdata=titanic.test.data, type.predict='response' )
prediction <- generate.prediction(0.54, test.prediction, test.prediction$Survived)
confusionMatrix(table(prediction$predict.value, prediction$actual.value ), positive='Lived' )
Confusion Matrix and Statistics

       
        Died Lived
  Died   219    53
  Lived   42   104
                                         
               Accuracy : 0.7727         
                 95% CI : (0.7295, 0.812)
    No Information Rate : 0.6244         
    P-Value [Acc > NIR] : 6.054e-11      
                                         
                  Kappa : 0.5086         
                                         
 Mcnemar's Test P-Value : 0.3049         
                                         
            Sensitivity : 0.6624         
            Specificity : 0.8391         
         Pos Pred Value : 0.7123         
         Neg Pred Value : 0.8051         
             Prevalence : 0.3756         
         Detection Rate : 0.2488         
   Detection Prevalence : 0.3493         
      Balanced Accuracy : 0.7508         
                                         
       'Positive' Class : Lived          
                                         

Se observa como disminuyo el accuracy, esperado de 80% a 77%, al tratarse de nuevos datos, pero aun asi conserva poder de prediccion apreciable. Vemos como tambien se mantiene la tendencia a tratar de maximizar la traza, pareceria que el punto de corte es acertado (aunque para asegurarnos de esto habria que realizar nuevamente los analisis realizados previamente pero con los nuevos datos)

LS0tDQp0aXRsZTogIlRyYWJham8gUHJhY3RpY28gMyINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0aGVtZTogc3BhY2VsYWINCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQogICAgZGZfcHJpbnQ6IHBhZ2VkDQotLS0NCg0KDQojIyMgMTogUHJlcGFyYWNpb24gZGUgZGF0b3MNCg0KDQojIyMjIDEuQTogTGVjdHVyYSB5IGFuYWxpc2lzIGRlIGVzdHJ1Y3R1cmEgZGVsIGFyY2hpdm8NCg0KQ2FyZ2Ftb3MgbG9zIGRhdG9zIHkgZXN0dWRpYW1vcyBicmV2ZW1lbnRlIHN1IGNvbXBvc2ljaW9uDQoNCmBgYHtyfQ0KIyBjYXJnYSBsaWJyZXJpYXMgdGlkeXZlcnNlIHkgZ2dwbG90Mg0Kc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKGxpYnJhcnkodGlkeXZlcnNlKSkNCnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyhsaWJyYXJ5KGdncGxvdDIpKQ0Kc3VwcHJlc3NQYWNrYWdlU3RhcnR1cE1lc3NhZ2VzKGxpYnJhcnkoR0dhbGx5KSkNCnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyhsaWJyYXJ5KGNhcmV0KSkNCnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyhsaWJyYXJ5KG1vZGVscikpDQpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMobGlicmFyeShwUk9DKSkNCnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyhsaWJyYXJ5KGJyb29tKSkNCnN1cHByZXNzUGFja2FnZVN0YXJ0dXBNZXNzYWdlcyhsaWJyYXJ5KGRhdGEudGFibGUpKQ0KDQojY2FyZ2EgZGUgZGF0b3MNCnRpdGFuaWMuZnVsbC5kYXRhIDwtIHJlYWQuY3N2KCd0aXRhbmljX2NvbXBsZXRlX3RyYWluLmNzdicsIHJvdy5uYW1lcyA9IE5VTEwsIHN0cmluZ3NBc0ZhY3RvcnMgPSBUUlVFLCBlbmNvZGluZyA9ICJVVEYtOCIpDQoNCiN2ZXJpZmljYWlvbiBkZSBjYXNvcyB5IHZhcmlhYmxlcw0KbnJvdyh0aXRhbmljLmZ1bGwuZGF0YSkNCnN0cih0aXRhbmljLmZ1bGwuZGF0YSkNCnN1bW1hcnkodGl0YW5pYy5mdWxsLmRhdGEpDQpgYGANCg0KDQojIyMjIDEuYixjOiBQcmVwYXJhY2lvbiBkZSBkYXRvcw0KDQpBanVzdGFtb3MgbG9zIGRhdG9zIGEgZmFjdG9yZXMsIHkgcmVvcmdhbml6YW1vcyBhbGd1bmFzIGNhdGVnb3JpYXMNCg0KYGBge3J9DQojIEdlbmVyYW1vcyB1bmEgZnVuY2lvbiBxdWUgYWpzdXRhIGxvcyBkYXRvcywgcGFyYSBwb2RlciByZXV0aWxpemFyIGRlc3B1ZXMNCmZpeC5kYXRhIDwtIGZ1bmN0aW9uKGRmKQ0Kew0KICANCiAgIyBTZWxlY2Npb25hbW9zIGxhcyBjb2x1bW5hcyBkZSBpbnRlcmVzDQogIGZpbHRlcmVkLmRmIDwtIGRmICU+JSBzZWxlY3QoIFBhc3NlbmdlcklkLCBTdXJ2aXZlZCwgUGNsYXNzLCBTZXgsIEFnZSwgU2liU3AsIFBhcmNoLCBGYXJlLCBFbWJhcmtlZCApICU+JQ0KICANCiAgICAjIGNvbnZlcnRpbW9zIGEgZmFjdG9yIGxhcyBjb2x1bW5hcyBpbmRpY2FkYXMNCiAgICBtdXRhdGVfYXQodmFycyhTdXJ2aXZlZCwgUGNsYXNzLCBFbWJhcmtlZCksIGFzX2ZhY3RvcikNCg0KICAjIHJlYWp1c3RhbW9zIGxvcyBmYWN0b3JlcyBwYXJhIG1hcyBmYWNpbCBpbnRlcnByZXRhY2lvbg0KICBsZXZlbHMoZmlsdGVyZWQuZGYkU3Vydml2ZWQpIDwtIGMoJ0RpZWQnLCAnTGl2ZWQnKQ0KICBsZXZlbHMoZmlsdGVyZWQuZGYkUGNsYXNzKSA8LSBjKCcxcmEnLCAnMmRhJywnM3JhJyApDQogIA0KICByZXR1cm4oZmlsdGVyZWQuZGYpDQp9DQoNCiMgVG9tYW1vcyBsb3MgZGF0b3MgY29tcGxldG9zDQp0aXRhbmljLmRhdGEgPC0gdGl0YW5pYy5mdWxsLmRhdGEgJT4lIGZpeC5kYXRhKCkNCg0Kc3RyKHRpdGFuaWMuZGF0YSkNCmBgYA0KDQojIyMjIDEuZDogZ2dwYWlycw0KDQpgYGB7cn0NCnRpdGFuaWMuZGF0YSAlPiUNCiAgc2VsZWN0KFN1cnZpdmVkLCBQY2xhc3MsIFNleCwgQWdlLCBGYXJlKSAlPiUNCiAgZ2dwYWlycyhwcm9ncmVzcyA9IEYsIGxlZ2VuZD0zLGFlcyhjb2xvdXI9U3Vydml2ZWQpICkgKw0KICAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAiYm90dG9tIikNCmBgYA0KDQpTZSBvYnNlcnZhIGNvbW8gbGFzIGNoYW5jZXMgZGUgc29icmV2aXZpciBjYW1iaWFuIGRyYXN0aWNhbWVudGUgY29uIGFsZ3VuYXMgdmFyaWFibGVzIChjb21vIHNleG8sIGNsYXNlIG8gZmFyZSksIHkgbm8gdGFudG8gcGFyYSBvdHJhcyAoY29tbyBlZGFkKS4gRXN0byBub3MgZGEgdW5hIHBhdXRhIGRlIHF1ZSB2YXJpYWJsZXMgc2VyaWFuIGxhcyBtZWpvcmVzIHBhcmEgcHJlZGVjaXIgbGEgcHJvYmFiaWxpZGFkIGRlIHN1cGVydml2ZW5jaWEuDQoNCg0KIyMjIyAxLmUgRGlzdHJpYnVjaW9uIHNvYnJldml2aWVudGVzIHZzIG5vIHNvYnJldml2aWVudGVzDQoNCmBgYHtyfQ0KIyBjcmVhbW9zIHVuYSBmdW5jaW9uIHF1ZSBub3MgYXl1ZGEgYSBhbmFsaXphciBmcmVjdWVuY2lhcyByZWxhdGl2YXMgeSBhYm9zdWx0YXMgYWdydXBhZGFzDQpzdW1tYXJ5Lmdyb3VwIDwtIGZ1bmN0aW9uKCBkZiwgLi4uICkNCnsNCiAgICBncm91cF92YXIgPC0gZW5xdW9zKC4uLikNCiAgICByZXR1cm4gKCBkZiAlPiUgDQogICAgICAgIGdyb3VwX2J5KCAhISFncm91cF92YXIgKSAlPiUNCiAgICAgICAgc3VtbWFyaXplKG49bigpKSAlPiUNCiAgICAgICAgbXV0YXRlKGZyZXE9bi9zdW0obikpICkNCn0NCg0KIyB5IGxhIGFwbGljYW1vcyBhIGRpdmVyc2FzIGNvbWJpbmFjaW9uZXMNCnRpdGFuaWMuZGF0YSAlPiUgc3VtbWFyeS5ncm91cCggU3Vydml2ZWQgKQ0KdGl0YW5pYy5kYXRhICU+JSBzdW1tYXJ5Lmdyb3VwKCBTdXJ2aXZlZCwgU2V4ICkNCnRpdGFuaWMuZGF0YSAlPiUgc3VtbWFyeS5ncm91cCggU3Vydml2ZWQsIFBjbGFzcyApDQp0aXRhbmljLmRhdGEgJT4lIHN1bW1hcnkuZ3JvdXAoIFN1cnZpdmVkLCBQY2xhc3MsIFNleCApDQpgYGANCg0KRGUgbGEgcHJpbWVyYSB0YWJsYSBzZSBkZXNwcmVuZGUgZWwgZGF0byBlc3RhZGlzdGljbyBtYXMgYmFzaWNvOiBzb2xvIHVuIDM4JSBkZWwgdG90YWwgc29icmV2aXZpbyBsYSB0cmFnZWRpYS4gUGVybyBhZ3J1cGFuZG8gcG9yIGRpZmVyZW50ZXMgY2F0ZWdvcmlhcywgcG9kZW1vcyBvYnNlcnZhciBjb21vIGVzdGEgcHJvcG9yY2lvbiBubyBzZSBtYW50aWVuZSBjdWFuZG8gc2UgZGlzY3JpbWluYSBwb3Igc2V4bywgcG9yIGNsYXNlLCBvIHBvciBhbWJhcyBhIGxhIHZlei4NCg0KDQojIyMjIDEuZiBQYXJ0aWNpb24gZGUgZGF0YXNldA0KDQpgYGB7cn0NCnNldC5zZWVkKDQ1NikNCg0KIyBTZXBhcmFtb3MgZWwgZGF0YXNldCBlbiB0cmFpbiB5IHRlc3QNCnRyYWluLmluZGV4IDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24odGl0YW5pYy5kYXRhJFN1cnZpdmVkLCBwID0gMC43LCBsaXN0ID0gRkFMU0UsICkNCnRyYWluLmRhdGEgPC0gdGl0YW5pYy5kYXRhW3RyYWluLmluZGV4LF0NCnZhbGlkYXRpb24uZGF0YSA8LSB0aXRhbmljLmRhdGFbLXRyYWluLmluZGV4LF0NCg0KIyBhbmFsaXphbW9zIGxhIGNvbXBvc2ljaW9uIGRlIGxvcyBtaXNtb3MgcGFyYSAyIGNhdGVnb3JpYXMNCmNiaW5kKA0KICB0cmFpbi5kYXRhICU+JSBzdW1tYXJ5Lmdyb3VwKCBTdXJ2aXZlZCApLA0KICB2YWxpZGF0aW9uLmRhdGEgJT4lIHN1bW1hcnkuZ3JvdXAoIFN1cnZpdmVkICkgICkNCg0KY2JpbmQoDQogIHRyYWluLmRhdGEgJT4lIHN1bW1hcnkuZ3JvdXAoIFNleCApLA0KICB2YWxpZGF0aW9uLmRhdGEgJT4lIHN1bW1hcnkuZ3JvdXAoIFNleCApDQopDQpgYGANCg0KUG9yIGNvbW8gZ2VuZXJhbW9zIGxhIHBhcnRpY2lvbiBkZWwgZGF0YXNldCwgbm9zIGFzZWd1cmFtb3MgZGUgbWFudGVuZXIgbGEgcmVsYWNpb24gZW50cmUgZmFsbGVjaWRvcyB5IG5vIGZhbGxlY2lkb3MgKCsvLSBlbCBlcnJvciBkZSBjdWFudGl6YXIgYSBjYXNvcyBkaXNjcmV0b3MpLiBTaW4gZW1iYXJnbywgbm8gc2UgY3VtcGxlIGVzdHJpY3RhbWVudGUgcGFyYSBsYXMgb3RyYXMgY2xhc2VzIHlhIHF1ZSBubyBzZSBmb3J6byBlc3RhIHJlc3RyaWNjaW9uIHBhcmEgZWxsYXM7IGNvbW8gcG9yIGVqZW1wbG8sIHBhcmEgbGEgdmFyaWFibGUgYHNleG9gIGVuIGVzdGUgY2Fzby4NCg0KDQojIyMgMi4gUHJlZGljY2lvbmVzDQoNCiMjIyMgMi5hLiBSZWdyZXNpb24gbG9naXN0aWNhDQoNClNlIGdlbmVyYSBsYSByZWdyZXNpb24gbG9naXN0aWNhIHkgc2UgYW5hbGl6YW4gbG9zIHJlc3VsdGFkb3MNCg0KYGBge3J9DQptb2RlbG8gPC0gZ2xtKFN1cnZpdmVkIH4gUGNsYXNzICsgU2V4ICsgQWdlLCBmYW1pbHkgPSAnYmlub21pYWwnLCBkYXRhPXRyYWluLmRhdGEpDQpzdW1tYXJ5KG1vZGVsbykNCmBgYA0KDQojIyMjIDIuYi4gSW50ZXJwcmV0YWNpb24NCg0KQSBwYXJ0aXIgZGVsIG1vZGVsbyBsb2dpc3RpY28sIHNlIG9ic2VydmEgbG9zIGlndWllbnRlOg0KDQogKiBUb2RhcyBsYXMgdmFyaWFibGVzIGRlbCBtb2RlbG8gYXBhcmVjZW4gY29tbyBzaWduaWZpY2F0aXZhcw0KICogVG9tYW5kbyBjb21vIHJlZmVyZW5jaWEgYmFzYWwgbGEgMWVyYSBjbGFzZSwgcGVydGVuZWNlciBhIDJkYSBjbGFzZSBkaXNtaW51eWUgbGFzIHByb2JhYmlsaWRhZCBkZSBzb2JyZXZpdmlyLCB5IGF1biBtYXMgcGFyYSAzcmEgY2xhc2UNCiAqIERlIGZvcm1hIHNlbWVqYW50ZSwgc2VyIGhvbWJyZSBkaXNtaW51eWUgbGEgcHJvYmFiaWxpZGFkIGRlIHNvYnJldml2aXINCiAqIExhIGVkYWQgcGFyZWNlIHRhbWJpZW4gaW5mbHVpciBlbiBsYSBwcm9iYWJpbGlkYWQgZGUgc3VwZXJ2aXZlbmNpYSAoZGUgZm9ybWEgbmVnYXRpdmEpLCBwZXJvIGNvbiB1biBlZmVjdG8gYmFzdGFudGUgbWVub3IgYWwgZGUgbGFzIGNsYXNlcyBvIHNleG8NCiANCiANCiMjIyMgMi5jLiBSb3NlIHZzLiBKYWNrDQoNCmBgYHtyfQ0KDQojIEZ1bmNpb24gcGFyYSBjcmVhciB1biBjYXNvIGRlIGVzdHVkaW8NCmNyZWFyX2Nhc28gPC0gZnVuY3Rpb24oIFBjbGFzcywgU2V4LCBBZ2UgKQ0Kew0KICAgIG5ldz10cmFpbi5kYXRhW0ZBTFNFLF0gJT4lIHNlbGVjdCggUGNsYXNzLCBTZXgsIEFnZSApDQogICAgbmV3WzEsXSA9IGxpc3QoUGNsYXNzLCBTZXgsIEFnZSkNCiAgICByZXR1cm4obmV3KQ0KfQ0KDQoNCiNDcmVhbW9zIGxvcyBjYXNvcywgeSBsb3MgJ2V0aXF1ZXRhbW9zJw0KY2Fzb3MgPC0gZG8uY2FsbChyYmluZCwgbGlzdCgNCiAgICBjcmVhcl9jYXNvKFBjbGFzcz0iMXJhIiwgU2V4PSJmZW1hbGUiLCBBZ2U9MTcpLA0KICAgIGNyZWFyX2Nhc28oUGNsYXNzPSIzcmEiLCBTZXg9Im1hbGUiLCBBZ2U9MjApIA0KICAgICkpICU+JQ0KICAgIA0KICAgICMgY3JlYW1vcyBjb2x1bW5hIHRlbXBvcmFsIHBhcmEgYWdydXBhciBsb3MgY2Fzb3MNCiAgICBjYmluZChjYXNvPWMoJ1Jvc2UnLCdKYWNrJykpICU+JQ0KDQogICAgI2FncnVwYW1vcyBsb3MgY2Fzb3MNCiAgICBncm91cF9ieShjYXNvKSAlPiUgbmVzdCgpICU+JQ0KDQogICAgI2hhY2Vtb3MgbGFzIHByZWRpY2Npb25lcyBwYXJhIGNhZGEgY2Fzbw0KICAgIG11dGF0ZSggcHJlZGljdGlvbj1tYXAoZGF0YSxmdW5jdGlvbihkYXRhKSBkYXRhLmZyYW1lKHByZWRpY3QobW9kZWxvLCBkYXRhLCB0eXBlPSdyZXNwb25zZScpKSkpICU+JQ0KDQogICAgdW5uZXN0KGRhdGEpICU+JSB1bm5lc3QocHJlZGljdGlvbikgJT4lDQoNCiAgICAjZGVzY2FydGFtb3MgbGEgY29sdW1uYSB0ZW1wb3JhbCBkZSBhZ3J1cGFjaW9uDQogICAgdW5ncm91cCgpIA0KDQpoZWFkKGNhc29zKQ0KYGBgDQoNCk9ic2VydmFtb3MgcXVlLCBmaWVsIGEgbGEgcGVsaWN1bGEsIGxhcyBjaGFuY2VzIGRlIHNvYnJldml2aXIgZGUgUm9zZSBlcmFuIGJhc3RhbnRlIG1heW9yIGEgbGFzIGRlIEphY2suDQoNCg0KIyMjIDMuIEdlbmVyYWNpb24gZGUgbW9kZWxvcw0KDQoNCmBgYHtyfQ0KICAjIHBsYW5lYW1vcyBsb3MgbW9kZWxvcyBlbiBmdW5jaW9uIGRlIGxhcyB2YXJpYWJsZXMNCm1vZGVsb3MgPC0gdGliYmxlKCBmb3JtdWxhPWZvcm11bGFzKC5yZXNwb25zZSA9IH5TdXJ2aXZlZCwNCiAgICAgICAgICAgICAgICAgICAgICAgICBjbGFzcy5zZXggPSB+UGNsYXNzK1NleCwNCiAgICAgICAgICAgICAgICAgICAgICAgICBmYXJlLnNleCA9IH5GYXJlK1NleCwNCiAgICAgICAgICAgICAgICAgICAgICAgICBjbGFzcy5zZXguZmFyZSA9IH5QY2xhc3MrU2V4K0ZhcmUsDQogICAgICAgICAgICAgICAgICAgICAgICAgdXNlbGVzcyA9IH5FbWJhcmtlZCtTaWJTcCtQYXJjaCwNCiAgICAgICAgICAgICAgICAgICAgICAgICBhbGwgPSB+UGNsYXNzK1NleCtBZ2UrU2liU3ArUGFyY2grRmFyZStFbWJhcmtlZCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgKSApICU+JQ0KICANCiAgIyBBZ3JlZ2Ftb3MgbGEgZGVzY3JpcGNpb24gZGUgY2FkYSBtb2RlbG8NCiAgbXV0YXRlKCBkZXNjPW5hbWVzKGZvcm11bGEpICkgJT4lDQogIA0KICAjIENhbGN1bGFtb3MgZWwgbW9kZWxvDQogIG11dGF0ZSggbW9kZWxvPW1hcCggZm9ybXVsYSwgfmdsbSguLCBmYW1pbHk9J2Jpbm9taWFsJywgZGF0YT10cmFpbi5kYXRhKSApICkgJT4lDQogIA0KICAjIGV4dHJhaWdvIGxhcyB2YXJpYWJsZXMgZGUgaW50ZXJlcyBkZWwgbW9kZWxvDQogIG11dGF0ZSggZ2xhbmNlPW1hcChtb2RlbG8sZ2xhbmNlKSkgJT4lIHVubmVzdChnbGFuY2UpDQoNCg0KIyBhbmFsaXphbW9zIGxvcyBmYWN0b3JlcyBkZSBpbnRlcmVzIGRlIGxvcyBtb2RlbG9zDQptb2RlbG9zICU+JSBzZWxlY3QoZGVzYywgZGV2aWFuY2UsIG51bGwuZGV2aWFuY2UgKSAlPiUNCiAgDQogICMgY2FsY3VsbyBlbCBwb3JjZW50YWplIGV4cGxpY2Fkbw0KICBtdXRhdGUoZXhwbGFpbmVkID0gMS1kZXZpYW5jZS9udWxsLmRldmlhbmNlKSAlPiUNCiAgDQogICMgb3JkZW5vIHBvciBkZXZpYW5jZQ0KICBhcnJhbmdlKGRldmlhbmNlKQ0KDQpgYGANCg0KT2JzZXJ2YWNpb25lczoNCg0KICogZWwgbW9kZWxvIHF1ZSBpbmNsdXllIHRvZGFzIGxhcyB2YXJpYWJsZXMgZXMgZWwgZGUgbWVub3IgKmRldmlhbmNlKiwgY29tbyBlcmEgZGUgZXNwZXJhcnNlLCB5YSBxdWUgZXN0ZSBwb3NlZSBsYSBtYXlvciBjYW50aWRhZCBkZSBpbmZvcm1hY2lvbiBwYXJhICpleHBsaWNhciogbG8gb2JzZXJ2YWRvDQogKiBwdWRpbW9zIG9ic2VydmFyIGVuIDEuZCBxdWUgZmFyZSBwb2RyaWEgYXl1ZGFyIGEgZXhwbGljYXIgY2llcnRhIHZhcmlhY2lvbiBlbnRyZSBxdWllbmVzIHNvYnJldml2aWVyb247IHBlcm8gZXN0YSB2YXJpYWNpb24gcGFyZWNlcmlhIGVzdGFyIGF1biBtZWpvciBleHBsaWNhZGEgcG9yIGxhIGNsYXNlIGRlbCBwYXNhamVyby4gRXN0byBzZSBldmlkZW5jaWEgYWwgc2VyIGVsIG1vZGVsbyBkZSBjbGFzZSArIHNleG8gbWVqb3IgcXVlIGVsIGRlIGNsYXNlICsgZmFyZS4NCiAqIFNlIG9ic2VydmEgdGFtYmllbiBxdWUgaGF5IEZhcmUgeSBjbGFzZSBwYXJlY2VyaWFuIGVzdGFyIGNvcnJlbGFjaW9uYWRvcywgYWwgbm8gZGlzbWludWlyIG11Y2hvIGVsIGBkZXZpYW5jZWAgYWwgaW5jbHVpciBlc3RhIHZhcmlhYmxlDQogKiBkZSBmb3JtYSBpbnRlbmNpb25hbCBzZSBpbmNsdXlvIHVuIG1vZGVsbyBxdWUgYWdydXBhYmEgbG9zIHBhcmFtZXRyb3MgcXVlIHBhcmVjaWFuIG1lbm9zIHJlbGV2YW50ZXMsIHkgZWZlY3RpdmFtZW50ZSBvYnNlcnZhbW9zIGNvbW8gZXN0ZSBtb2RlbG8gZXMgZWwgZGUgbWF5b3IgKmRldmlhbmNlKg0KICogRWwgbW9kZWxvIGRlIGNsYXNlICsgc2V4byBwYXJlY2Ugc2VyIHVuIGJ1ZW4gY29tcHJvbWlzbyBlbnRyZSBzaW1wbGljaWRhZCB5IGJvbmRhZCBkZSBhanVzdGUsIGFsIHRyYWJhamFyIGNvbiBzb2xvIDIgdmFyaWFhYmxlcyB5IGV4cGxpY2FyIHVuIGJ1ZW4gcG9yY2VudGFqZSBlbiBjb21wYXJhY2lvbiBhIGxhcyBhbHRlcm5hdGl2YXMNCiANCk1hcyBhbGxhIGRlIGxvIGFuYWxpemFkbywgc2UgcHJvY2VkZSBhIGVsZWdpciBlbCBtb2RlbG8gJ2FsbCcgcGFyYSBsb3Mgc2lndWllbnRlcyBwdW50b3MsIHlhIHF1ZSBlc3RlIHByZXNlbnRhIG1lam9yZXMgY2FyYWN0ZXJpc3RpY2FzIHBhcmEgc3UgYW5hbGlzaXMgKG1hcyBhbGxhIGRlIHN1IGNvbXBsZWppZGFkKQ0KDQoNCiANCiMjIyA0LiBFdmFsdWFjaW9uIGRlIG1vZGVsb3MNCiANCmBgYHtyfQ0KbW9kZWxvLmVsZWdpZG8gPC0gbW9kZWxvc1ttb2RlbG9zJGRlc2M9PSdhbGwnLF0kbW9kZWxvW1sxXV0NCg0KcHJlZGljY2lvbiA8LSBhdWdtZW50KG1vZGVsby5lbGVnaWRvLCB0eXBlLnByZWRpY3Q9J3Jlc3BvbnNlJyApDQoNCiMgQ2FsY3VsYW1vcyBsYSBjdXJ2YSBST0MgeSBsYSBncmFmaWNhbW9zDQpyb2MuY3VydmUgPC0gcm9jKHJlc3BvbnNlID0gcHJlZGljY2lvbiRTdXJ2aXZlZCwgcHJlZGljdG9yID0gcHJlZGljY2lvbiQuZml0dGVkKQ0KDQojIEdyYWZpY2Ftb3MgbGEgY3VydmENCmdncm9jKGxpc3QoYWxsID0gcm9jLmN1cnZlKSwgc2l6ZT0xKSArIGdlb21fYWJsaW5lKHNsb3BlID0gMSwgaW50ZXJjZXB0ID0gMSwgbGluZXR5cGU9J2Rhc2hlZCcpDQoNCiMgeSBlbCB2aW9saW4gcGxvdA0KZ2dwbG90KHByZWRpY2Npb24sIGFlcyh4PVN1cnZpdmVkLCB5PS5maXR0ZWQsIGdyb3VwPVN1cnZpdmVkLGZpbGw9ZmFjdG9yKFN1cnZpdmVkKSkpICsgDQogIGdlb21fdmlvbGluKCkNCg0KI29ic2VydmFtb3MgZWwgQVVDDQpwcmludCggcm9jLmN1cnZlJGF1YyApDQpgYGANCg0KU2Ugb2JzZXJ2YSBxdWUgZWwgbW9kZWxvIHByZWRpY2UgY29uIHVuYSBidWVuYSBwcmVjaXNpb24gbG9zIGNhc29zIG1hcyAqZXh0cmVtb3MqOiBjdWFuZG8gbGEgcHJvYmFiaWxpZGFkIGRlIHNvYnJldml2aXIgZXMgbXV5IGFsdGEgbyBtdXkgYmFqYS4gRXN0byBzZSBhcHJlY2lhIGVuIHF1ZSBsYSAqYW5jaHVyYSogZW4gbG9zIGdyYWZpY29zIGRlIHZpb2xpbiBlbiBsb3MgZXh0cmVtb3Mgc29uIG11eSBkaWZlcmVudGVzIGVuIGxvcyBleHRyZW1vcywgeSBlbiBsYSBjdXJ2YSBST0Mgc2UgYXByZWNpYSBhbCBvYnNlcnZhciBxdWUgZW4gYW1ib3MgZXh0cmVtb3MgZGUgbGEgY3VydmEgZXN0YSBzZSBzZSAqYWxpbmVhKiBiYXN0YW50ZSBjb24gZWwgZWplIGRlIHNlbnNpYmlsaWRhZCAoYWwgaW5pY2lvKSB5IGNvbiBlbCBkZSBlc3BlY2lmaWNpZGFkIChhbCBmaW5hbCkuIEVuIGVsIG1lZGlvIGVzIGRvbmRlIHNlIHByZXN0YSBhIG1heW9yIHByb2JhYmlsaWRhZCBkZSBlcnJvciwgZG9uZGUgbm8gcmVzdWx0YSB0YW4gY3VhbCBzZXJpYSB1biBwdW50byBkZSBjb3J0ZSBhcHJvcGlhZG8uDQoNCg0KIyMjIDUuIEVsZWNjaW9uIGRlbCBwdW50byBkZSBjb3J0ZQ0KIA0KUmVhbGl6YW1vcyBudWV2YW1lbnRlIGxvcyBtaXNtb3MgZ3JhZmljb3MsIHBlcm8gY29uIGxvcyB2YWxvcmVzIGRlIHZhbGlkYWNpb246DQoNCmBgYHtyfQ0KcHJlZGljY2lvbiA8LSBhdWdtZW50KG1vZGVsby5lbGVnaWRvLCBuZXdkYXRhPXZhbGlkYXRpb24uZGF0YSwgdHlwZS5wcmVkaWN0PSdyZXNwb25zZScgKQ0KDQojIENhbGN1bGFtb3MgbGEgY3VydmEgUk9DIHkgbGEgZ3JhZmljYW1vcw0Kcm9jLmN1cnZlIDwtIHJvYyhyZXNwb25zZSA9IHByZWRpY2Npb24kU3Vydml2ZWQsIHByZWRpY3RvciA9IHByZWRpY2Npb24kLmZpdHRlZCkNCg0KIyBHcmFmaWNhbW9zIGxhIGN1cnZhDQpnZ3JvYyhsaXN0KGFsbCA9IHJvYy5jdXJ2ZSksIHNpemU9MSkgKyBnZW9tX2FibGluZShzbG9wZSA9IDEsIGludGVyY2VwdCA9IDEsIGxpbmV0eXBlPSdkYXNoZWQnKQ0KDQojIHkgZWwgdmlvbGluIHBsb3QNCmdncGxvdChwcmVkaWNjaW9uLCBhZXMoeD1TdXJ2aXZlZCwgeT0uZml0dGVkLCBncm91cD1TdXJ2aXZlZCxmaWxsPWZhY3RvcihTdXJ2aXZlZCkpKSArIA0KICBnZW9tX3Zpb2xpbigpDQoNCg0KaGVscChjb25mdXNpb25NYXRyaXgpDQpgYGANCg0KVmVtb3MgcXVlIHNlIHBlcmRpbyB1biBwb2NvIGVsIHBvZGVyIGRlIHByZWRpY2Npb24sIGxvIGN1YWwgZXMgZXMgZXNwZXJhYmxlIGFsICB2YWxpZGFyIGVsIG1vZGVsbyBjb24gZGF0b3MgZGlzdGludG9zIGEgbG9zIGRlIGVudHJlbmFtaWVudG8uDQoNCg0KYGBge3J9DQojIEdlbmVyYW1vcyB1bmEgZnVuY2lvbiBxdWUgY2FsY3VsZSBsb3MgcGFyYW1ldHJvcyBkZSBpbnRlcmVzDQoNCg0KZ2VuZXJhdGUucHJlZGljdGlvbiA8LSBmdW5jdGlvbihjdXJyLmN1dG9mZiwgcHJlZGljY2lvbiwgdGFyZ2V0KQ0KeyAgDQogIyBIYWNlciBsYSBwcmVkaWNjaW9uIGNvbiBwcm9iYWJpbGlkYWRlcw0KICBwcmVkaWN0aW9ucyA8LSBkYXRhLnRhYmxlKCBwcmVkaWN0LnByb2I9cHJlZGljY2lvbiQuZml0dGVkKSAlPiUNCiAgDQogICAgIyBlbGVnaXIgdW4gcmVzdWx0YWRvIGVuIGJhc2UgYWwgcHVudG8gZGUgY29ydGUNCiAgICBtdXRhdGUoIHByZWRpY3QudmFsdWU9aWZfZWxzZSggcHJlZGljdC5wcm9iPj1jdXJyLmN1dG9mZiwgJ0xpdmVkJywgJ0RpZWQnKSApICU+JQ0KICAgIA0KICAgICMgQWRqdXRuYXIgbGEgY29sdW1uYSBkZSByZXN1bHRhZG9zIHZlcmRlcm9zDQogICAgY2JpbmQoIGFjdHVhbC52YWx1ZT10YXJnZXQpICAlPiUNCiAgICANCiAgICAjIGNvbnZlcnRpciBhIGZhY3RvciBwYXJhIGNvbXBhcmFjaW9uDQogICAgbXV0YXRlX2F0KCB2YXJzKHByZWRpY3QudmFsdWUsIGFjdHVhbC52YWx1ZSksIGFzX2ZhY3RvciApDQp9DQoNCg0KY2FsYy5tZXRyaWNzIDwtIGZ1bmN0aW9uKCBjdXJyLmN1dG9mZiwgcHJlZGljY2lvbiApDQp7DQogIHByZWRpY3Rpb25zIDwtIGdlbmVyYXRlLnByZWRpY3Rpb24oY3Vyci5jdXRvZmYsIHByZWRpY2Npb24sIHZhbGlkYXRpb24uZGF0YSRTdXJ2aXZlZCkNCg0KICAjIGNhbGN1bGFtb3MgbG9zIHBhcmFtZXRyb3MgZGUgbGEgbWF0cml6IGRlIGNvbmZ1c2lvbg0KICBjb25mdXNpb24gPC0gY29uZnVzaW9uTWF0cml4KHRhYmxlKHByZWRpY3Rpb25zJHByZWRpY3QudmFsdWUsIHByZWRpY3Rpb25zJGFjdHVhbC52YWx1ZSApLCBwb3NpdGl2ZT0nTGl2ZWQnICkgJT4lDQogICAgDQogICAgIyBkZXNhZ3J1cGFtb3MgbG9zIHZhbG9yZXMgZGUgbGEgbWF0cml6IGRlIGNvbmZ1c2lvbg0KICAgIHRpZHkoKSAlPiUgc2VsZWN0KHRlcm0sIGVzdGltYXRlKSAlPiUNCiAgICANCiAgICAjIFRvbWFtb3MgbG9zIHZhbG9yZXMgcXVlIG5vcyBpbnRlcmVzYW4NCiAgICBmaWx0ZXIodGVybSAlaW4lIGMoJ2FjY3VyYWN5JywgJ3NwZWNpZmljaXR5JywgJ3JlY2FsbCcsICdwcmVjaXNpb24nKSkgJT4lDQogICAgDQogICAgIyBhZ3JlZ2Ftb3MgbGEgY29sdW1uYSBkZSBjdXRvZmYNCiAgICBtdXRhdGUoY3V0b2ZmPWN1cnIuY3V0b2ZmKQ0KICANCiAgcmV0dXJuKGNvbmZ1c2lvbikNCiAgDQp9DQoNCiMgcHJlcGFybyBzZWN1ZW5jaWENCnNlcSggMC4xLCAwLjksIDAuMDEpICU+JQ0KDQogICMgY2FsY3VsbyBsYXMgbWV0cmljYXMNCiAgbWFwX2RmciggZnVuY3Rpb24oY3V0b2ZmKSBjYWxjLm1ldHJpY3MoY3V0b2ZmLCBwcmVkaWNjaW9uKSApICU+JSANCiAgDQogICMgbGFzIGdyYWZpY28NCiAgZ2dwbG90KCBhZXMoeD1jdXRvZmYsIHk9ZXN0aW1hdGUsIGNvbG9yPXRlcm0pKSArIGdlb21fbGluZSgpICsgZ2VvbV92bGluZSh4aW50ZXJjZXB0PTAuNTQpDQpgYGANCg0KRWxlZ2lyIHVuIHB1bnRvIGRlIGNvcnRlIHBhcmEgZWwgbW9kZWxvIHNpbiB1biBmaW4gZXN0YWJsZWNpZG8gcmVzdWx0YSBhcmJpdHJhcmlvIHlhIHF1ZSBoYXkgdmFyaW9zIGNyaXRlcmlvcyB5IGVzdG9zIGRlcGVuZGVuIGRlbCB1c28gcXVlIHNlIGxlIGRlIGFsIG1vZGVsbyAocG9yIGVqZW1wbG8sIGN1YWwgZXMgZWwgYmFsYW5jZSBlbnRyZSBlbCBjb3N0byBkZSB1biBmYWxzbyBwb3NpdGl2byB2cyB1biBmYWxzbyBuZWdhdGl2bykuIERhZGEgZXN0YSBmYWx0YSBkZSBjb250ZXh0bywgc2UgZWxpZ2UgbWF4aW1pemFyIGVsICphY2N1cmFjeSogcGFyYSBtaW5pbWl6YXIgbG9zIGVycm9yZXMgZW4gZ2VuZXJhbCwgcXVlIHBvZGVtb3Mgb2JzZXJ2YXIgZW4gZWwgZ3JhZmljbyBxdWUgZXN0ZSBwdW50byBjb3JyZXNwb25kZSBhcHJveGltYWRhbWVudGUgYWwgdmFsb3IgMC41NA0KDQpgYGB7cn0NCiMgR2VuZXJvIGxhIG1hdHJpeCBkZSBjb25mdXNpb24gZGVsIG1vZGVsbw0KcHJlZGljdGlvbiA8LSBnZW5lcmF0ZS5wcmVkaWN0aW9uKDAuNTQsIHByZWRpY2Npb24pDQpjb25mdXNpb25NYXRyaXgodGFibGUocHJlZGljdGlvbiRwcmVkaWN0LnZhbHVlLCBwcmVkaWN0aW9uJGFjdHVhbC52YWx1ZSApLCBwb3NpdGl2ZT0nTGl2ZWQnICkNCmBgYA0KDQpQb2RlbW9zIGFwcmVjaWFyIGNvbW8gZWwgdmFsb3IgZGUgY29ydGUgZWxlZ2lkbyBjb24gZWwgY3JpdGVyaW8gZGUgbWF5b3IgKmFjY3VyYWN5KiBpbnRlbnRhIG1heGltaXphciBsYSB0cmF6YSBkZSBsYSBtYXRyaXogZGUgY29uZnVzaW9uIChxdWUgY29ycmVzcG9uZGUgYSBsYSBzdW1hIGRlIHZlcmRhZGVyb3MgcG9zaXRpdm9zIHkgdmVyZGFkZW9zIG5lZ2F0aXZvcyksIG1pbmltaXphbmRvIGxvcyBmYWxzb3MgcG9zaXRpdm9zICgzNSkgeSBsb3MgZmFsc29zIG5lZ2F0aXZvcyAoMTcpDQoNCg0KIyMjIDYuIERhdGFzZXQgZGUgdGVzdGVvDQoNCmBgYHtyfQ0KI2NhcmdhIGRlIGRhdG9zDQp0aXRhbmljLnRlc3QuZGF0YSA8LSByZWFkLmNzdigndGl0YW5pY19jb21wbGV0ZV90ZXN0LmNzdicsIHJvdy5uYW1lcyA9IE5VTEwsIHN0cmluZ3NBc0ZhY3RvcnMgPSBUUlVFLCBlbmNvZGluZyA9ICJVVEYtOCIpICU+JSANCiAgZml4LmRhdGEoKQ0KDQoNCnRlc3QucHJlZGljdGlvbiA8LSBhdWdtZW50KG1vZGVsby5lbGVnaWRvLCBuZXdkYXRhPXRpdGFuaWMudGVzdC5kYXRhLCB0eXBlLnByZWRpY3Q9J3Jlc3BvbnNlJyApDQpwcmVkaWN0aW9uIDwtIGdlbmVyYXRlLnByZWRpY3Rpb24oMC41NCwgdGVzdC5wcmVkaWN0aW9uLCB0ZXN0LnByZWRpY3Rpb24kU3Vydml2ZWQpDQpjb25mdXNpb25NYXRyaXgodGFibGUocHJlZGljdGlvbiRwcmVkaWN0LnZhbHVlLCBwcmVkaWN0aW9uJGFjdHVhbC52YWx1ZSApLCBwb3NpdGl2ZT0nTGl2ZWQnICkNCmBgYA0KDQpTZSBvYnNlcnZhIGNvbW8gZGlzbWludXlvIGVsICphY2N1cmFjeSosIGVzcGVyYWRvIGRlIDgwJSBhIDc3JSwgYWwgdHJhdGFyc2UgZGUgbnVldm9zIGRhdG9zLCBwZXJvIGF1biBhc2kgY29uc2VydmEgcG9kZXIgZGUgcHJlZGljY2lvbiBhcHJlY2lhYmxlLiBWZW1vcyBjb21vIHRhbWJpZW4gc2UgbWFudGllbmUgbGEgdGVuZGVuY2lhIGEgdHJhdGFyIGRlIG1heGltaXphciBsYSB0cmF6YSwgcGFyZWNlcmlhIHF1ZSBlbCBwdW50byBkZSBjb3J0ZSBlcyBhY2VydGFkbyAoYXVucXVlIHBhcmEgYXNlZ3VyYXJub3MgZGUgZXN0byBoYWJyaWEgcXVlIHJlYWxpemFyIG51ZXZhbWVudGUgbG9zIGFuYWxpc2lzIHJlYWxpemFkb3MgcHJldmlhbWVudGUgcGVybyBjb24gbG9zIG51ZXZvcyBkYXRvcykNCg0KDQo=