knitr::include_url("https://form.com.mx/")

Definiciones

1. K - means Clustering

Contribuyen a la identificación de clusters, ya que K-means lo que hace es agrupar, quiere decir que esta función o algoritmo divide los datos u observaciones obtenidas en grupos con características similares. De esta manera es que se puede analizar con éxito cada Cluster, obteniendo información importante de cada variable y la similitudes entre ellas, lo que hace que sea más comprensible, ya que hace que los datos o información que no se puede procesar en información procesable. Un claro ejemplo de cómo funciona es teniendo grupos que comparten los mismos temas, imágenes con el mismo objeto o persona, etc.

2. Unsupervised Learning

Cuando se utiliza la función K-means se enfoca en agrupar datos no supervisados, esto quiere decir que K-means es un algoritmo no supervisado. En otras palabras lo que quiere decir Aprendizaje no supervisado, es referente al tipo de algoritmo o técnica que se utiliza y es importante, ya que gracias a que se define de esta manera las técnicas y algoritmos, podemos implementarlo de manera correcta en el análisis de Datos. Esto quiere decir que este termino nos da a entender que esa base de datos se ocupará el algoritmo de agrupamiento no supervisado, ya que la base cuenta con datos de entrada sin respuestas etiquetadas, es por eso la importancia para poder identificarlo.

3. Distancia Euclidiana / Eucliedeance Distance

Principalmente la distancia euclidiana es cuando se encuentra un espacio vectorial, que es igual o inferior a la variable que estamos analizando. Cuando se identifican clusters encontramos a los centroides, En la parte donde se utiliza la distancia euclidiana es cuando los centroides ya no se repiten y encuentran una posición y no cambian, por lo que se hace la relación con puntos al rededor del centroides, cada punto es una variable por lo tanto la distancia que existe entre variables es la distancia euclidiana. La cual nos ayuda a descubrir la similitud entre las variables que estamos analizando.

Cluster 1

Importar Paquetes

Limpieza de datos y transformacion

Importar Base de Datos

file.choose()

Explorar datos

rh1<-rh %>% select(EDAD,DIAS.TRABAJADOS,SALARIO.DIARIO.IMSS)
summary(rh1)
##       EDAD       DIAS.TRABAJADOS  SALARIO.DIARIO.IMSS
##  Min.   : 0.00   Min.   :  0.00   Min.   :151.6      
##  1st Qu.:22.25   1st Qu.:  9.00   1st Qu.:180.7      
##  Median :30.00   Median : 19.00   Median :180.7      
##  Mean   :30.50   Mean   : 45.10   Mean   :174.3      
##  3rd Qu.:36.00   3rd Qu.: 39.75   3rd Qu.:180.7      
##  Max.   :52.00   Max.   :730.00   Max.   :183.7

Normalizar los datos

rh1_norm<-scale(rh1[2:3]) 

Numero Optimo de Clusters

fviz_nbclust(rh1_norm, kmeans, method="wss")+ # wss method considers total within sum of square
  geom_vline(xintercept=4, linetype=2)+           # optimal number of clusters is computed with the default method = "euclidean"
  labs(subtitle = "Elbow method")  
## Registered S3 methods overwritten by 'broom':
##   method            from  
##   tidy.glht         jtools
##   tidy.summary.glht jtools

Visualizar la info de clusters

rh1_cluster<-kmeans(rh1_norm,4)
rh1_cluster
## K-means clustering with 4 clusters of sizes 1, 17, 8, 52
## 
## Cluster means:
##   DIAS.TRABAJADOS SALARIO.DIARIO.IMSS
## 1       7.5323951           0.5246350
## 2       0.2712304          -1.8780410
## 3       0.6353722           0.4428101
## 4      -0.3312748           0.5357612
## 
## Clustering vector:
##  [1] 2 2 2 2 2 2 2 2 2 2 2 2 4 4 4 4 4 4 4 3 2 4 2 2 2 2 4 4 4 4 4 4 4 4 4 4 4 4
## [39] 4 4 4 4 4 4 4 3 4 4 3 3 3 1 4 4 3 4 4 4 4 4 4 4 4 4 4 4 4 3 3 4 4 4 4 4 4 4
## [77] 4 4
## 
## Within cluster sum of squares by cluster:
## [1] 0.000000 6.019937 3.270968 1.055662
##  (between_SS / total_SS =  93.3 %)
## 
## Available components:
## 
## [1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
## [6] "betweenss"    "size"         "iter"         "ifault"

Muestra que utilicemos 4 clusters para analizar y visualizar la información.

Visualizar Resultados

fviz_cluster(rh1_cluster,data=rh1_norm)

El primer cluster analiza la relación entre el salario diario IMSS y los días trabajados de los empleados que ya fueron dados de baja. En este caso, vemos que hay un impacto alto entre aquellos empleados que trabajaron de 2 a 8 días, pero tuvieron un salario diario bajo, mucho menor a 0 en la gráfica. Los otros dos resultados, se muestra que trabajaron menos días, pero al igual que el primer cluster explicado, tuvieron un salario muy bajo. Con estos tres, hay una correlación alta, significativa y negativa. Para el grupo de arriba, vemos que estos trabajaron un promedio de 2-3 días y tuvieron un salario diario muy alto. Podemos inferir que este grupo pertenece al área administrativa, que como vimos en análisis anteriores, son el grupo con un salario mucho más alto al resto.

rh2<-rh1
rh2$Clusters<-rh1_cluster$cluster
summary(rh2)
##       EDAD       DIAS.TRABAJADOS  SALARIO.DIARIO.IMSS    Clusters    
##  Min.   : 0.00   Min.   :  0.00   Min.   :151.6       Min.   :1.000  
##  1st Qu.:22.25   1st Qu.:  9.00   1st Qu.:180.7       1st Qu.:3.000  
##  Median :30.00   Median : 19.00   Median :180.7       Median :4.000  
##  Mean   :30.50   Mean   : 45.10   Mean   :174.3       Mean   :3.423  
##  3rd Qu.:36.00   3rd Qu.: 39.75   3rd Qu.:180.7       3rd Qu.:4.000  
##  Max.   :52.00   Max.   :730.00   Max.   :183.7       Max.   :4.000

Crear un dataset

rh3<-rh2 %>% group_by(Clusters) %>% summarise(SALARIO.DIARIO.IMSS=max(SALARIO.DIARIO.IMSS),DIAS.TRABAJADOS=mean(DIAS.TRABAJADOS)) %>% arrange(desc(SALARIO.DIARIO.IMSS))
summary(rh3)
##     Clusters    SALARIO.DIARIO.IMSS DIAS.TRABAJADOS 
##  Min.   :1.00   Min.   :151.6       Min.   : 14.98  
##  1st Qu.:1.75   1st Qu.:173.4       1st Qu.: 56.07  
##  Median :2.50   Median :180.7       Median : 86.32  
##  Mean   :2.50   Mean   :174.2       Mean   :229.41  
##  3rd Qu.:3.25   3rd Qu.:181.4       3rd Qu.:259.66  
##  Max.   :4.00   Max.   :183.7       Max.   :730.00

Agrupar clusters por nombre

rh2$Cluster_Names<-factor(rh2$Clusters,levels = c(1,2,3,4), 
                              labels=c("Bajo", "Promedio ", "Arriba del prom", "Alto"))
summary(rh2)
##       EDAD       DIAS.TRABAJADOS  SALARIO.DIARIO.IMSS    Clusters    
##  Min.   : 0.00   Min.   :  0.00   Min.   :151.6       Min.   :1.000  
##  1st Qu.:22.25   1st Qu.:  9.00   1st Qu.:180.7       1st Qu.:3.000  
##  Median :30.00   Median : 19.00   Median :180.7       Median :4.000  
##  Mean   :30.50   Mean   : 45.10   Mean   :174.3       Mean   :3.423  
##  3rd Qu.:36.00   3rd Qu.: 39.75   3rd Qu.:180.7       3rd Qu.:4.000  
##  Max.   :52.00   Max.   :730.00   Max.   :183.7       Max.   :4.000  
##          Cluster_Names
##  Bajo           : 1   
##  Promedio       :17   
##  Arriba del prom: 8   
##  Alto           :52   
##                       
## 

Muchos días trabajados y poco salario

Agrupar y dar resumen por nombres

rh4 <- rh2%>% group_by(Cluster_Names) %>% summarize(DIAS.TRABAJADOS=max(DIAS.TRABAJADOS), 
                                                     SALARIO.DIARIO.IMSS =mean(SALARIO.DIARIO.IMSS),
                                                             Count=n())

Dar formato de tabla para enseñar la informacion de los clusters.

clusters<-as.data.frame(rh4)
clusters
##     Cluster_Names DIAS.TRABAJADOS SALARIO.DIARIO.IMSS Count
## 1            Bajo             730            180.6800     1
## 2       Promedio              169            151.6100    17
## 3 Arriba del prom             251            179.6900     8
## 4            Alto              53            180.8146    52

Graficar el no. de datos observados por nombre de clusterslets

ggplot(rh4,aes(x=reorder(Cluster_Names,Count),y=Count,fill=Cluster_Names)) +
  geom_bar(stat="identity")

De acuerdo a la clasificación de clusters, la gráfica anterior nos muestra el impacto y conteo de cada cluster dependiendo del grupo de baja analizado, es decir, vemos la relación entre días trabajados y salario diario, clasificando el conteo acorde a la cantidad de bajas obtenidas. En este caso, en la gráfica se muestra que aquellos que trabajaron más de 600 días y menos de 1900 días, obtuvieron un salario promedio de $500 pesos diario. Entra en la categoría de ‘bajo’, ya que sólo una persona obtuvo esta descripción. El promedio de las personas dadas de baja trabajaron más de 1900 días y obtuvieron un salario diario de $170 pesos. En la barra de ‘alto’ vemos a aquellos ex-empleados que trabajaron 421 días y tuvieron un salario diario de $150 pesos. Por último, vemos a aquellos ‘arriba del promedio’ y son aquellos que tabajaron un promedio de 455 días y tuvieron un salario diario de $180 pesos. Este último grupo es el que tuvo mayor conteo de personas, es decir, la mayoría de las bajas estuvieron laborando más de un año en FORM y tenían un salario diario de $180 pesos.

Ver Rangos

Rango de “Días trabajados” por nombre

ggplot(rh4, aes(x=Cluster_Names,y=DIAS.TRABAJADOS,fill= Cluster_Names,label=round(DIAS.TRABAJADOS,digits=2))) + 
  geom_col() + 
  geom_text()

La gráfica anterior nos presenta la cantidad de días trabajados de acuerdo a la clasificación explicada anteriormente de ‘bajo’, ‘promedio’, ‘arriba del promedio’ y ‘alto’. Con esto en mente, vemos que el promedio de los empleados juntaron un total de 1966 días trabajados, siguiéndole el grupo de ‘bajo’, los que laboraron un promedio de 628 días, luego ‘arriba del promedio’ que laboró 455 días y finalmente ‘alto’, que únicamente laboró 421 días. En todos los grupos, vemos que los empleados trabajaron por más de 1 año en FORM y ganaban un salario diario mayor a $150 pesos.

Ver el rango de Salario Diario

ggplot(rh4,aes(x=Cluster_Names,y=SALARIO.DIARIO.IMSS,fill= Cluster_Names,label=round(SALARIO.DIARIO.IMSS,digits=2))) + 
  geom_col() + 
  geom_text()

La gráfica anterior nos presenta la relación entre el salario diario IMSS y la clasificación explicada anteriormente de ‘bajo’, ‘promedio’, ‘arriba del promedio’ y ‘alto’. Con esto en mente, vemos que el segmento ‘bajo’ es el que tenía un salario más alto que la mayoría. El promedio ganaba un salario diario de $170,79 pesos, ‘arriba del promedio’ ganaba $180,54 pesos y el segmento ‘alto’ gabana un promedio de $151,61 pesos de salario diario. Esto denota una variabilidad alta y podemos suponer que esto se debía de acuerdo a las responsabilidades y puestos analizados.

Dispersion de Dias Trabajados

ggplot(rh2, aes(x=Cluster_Names, y=DIAS.TRABAJADOS, fill=Cluster_Names)) +
  geom_boxplot()+
  ggtitle("Dispersion of 'Días trabajados' by Clusters Names")

La gráfica anterior explica la dispersión de días trabajados de acuerdo a la clasificación explicada anteriormente de ‘bajo’, ‘promedio’, ‘arriba del promedio’ y ‘alto’. Con esto en mente, vemos que el grupo con mayor dispersión es ‘promedio’, pues hay una variabilidad más significativa en los datos de la cantidad de días trabajados, destacando la media de días trabajados, es decir, 1000 días. En el caso de ‘bajo’, hay una dispersión baja y poco variable. Para ‘arriba del promedio’, vemos que los puntos atípicos se salen del boxplot específico, lo cual quitarlos nos puede dar mayor claridad y visibilidad de la información. Para ‘alto’ se muestra una dispersión menor a ‘promedio’ y al igual que ‘arriba del promedio’, tiene aparición de puntos atípicos.

Dispersion de Salario diario

ggplot(rh2, aes(x=Cluster_Names, y=SALARIO.DIARIO.IMSS, fill=Cluster_Names)) +
  geom_boxplot()+
  ggtitle("Dispersion of 'Salario_Diario' by Clusters Names")

La gráfica anterior explica la dispersión de salario diario de acuerdo a la clasificación explicada anteriormente de ‘bajo’, ‘promedio’, ‘arriba del promedio’ y ‘alto’. ‘Bajo’ muestra un salario diario de $500 pesos, ‘promedio’ muestra un salario de $180 pesos aprox y puntos atípicos que podrían insinuar un salario menor, ‘arriba del promedio’ también muestra puntos atípicos. Sin embargo, todos se mantienen al igual que el dato anterior entre $170 y $180 pesos de salario diario, Finalmente, ‘alto’ muestra un salario diario abajo del promedio de aprox. $140 pesos.

Cluster 2

Importar Base de Datos

file.choose()

rhCluster2<-read.csv("C:\\Users\\danyc\\Downloads\\HR_Bajas 2.csv") 
summary(rhCluster2)
##   NO.DE.BAJAS      APELLIDOS            NOMBRE          FECHA.DE.NACIMIENTO
##  Min.   :  5.00   Length:78          Length:78          Min.   :25585      
##  1st Qu.: 49.75   Class :character   Class :character   1st Qu.:31751      
##  Median :167.50   Mode  :character   Mode  :character   Median :33797      
##  Mean   :139.60                                         Mean   :33611      
##  3rd Qu.:212.75                                         3rd Qu.:36519      
##  Max.   :238.00                                         Max.   :44632      
##       EDAD          GENERO          FECHA.DE.ALTA   MOTIVO.DE.BAJA    
##  Min.   : 0.00   Length:78          Min.   :43961   Length:78         
##  1st Qu.:22.25   Class :character   1st Qu.:44567   Class :character  
##  Median :30.00   Mode  :character   Median :44726   Mode  :character  
##  Mean   :30.50                      Mean   :44664                     
##  3rd Qu.:36.00                      3rd Qu.:44759                     
##  Max.   :52.00                      Max.   :44790                     
##  DIAS.TRABAJADOS       BAJA          PUESTO          DEPARTAMENTO      
##  Min.   :  0.00   Min.   :44569   Length:78          Length:78         
##  1st Qu.:  9.00   1st Qu.:44613   Class :character   Class :character  
##  Median : 19.00   Median :44741   Mode  :character   Mode  :character  
##  Mean   : 45.10   Mean   :44709                                        
##  3rd Qu.: 39.75   3rd Qu.:44784                                        
##  Max.   :730.00   Max.   :44814                                        
##  NO.SEGURO.SOCIAL   SALARIO.DIARIO.IMSS FACTOR.CRED.INFONAVIT
##  Length:78          Min.   :151.6       Min.   :   0.0       
##  Class :character   1st Qu.:180.7       1st Qu.:   0.0       
##  Mode  :character   Median :180.7       Median :   0.0       
##                     Mean   :174.3       Mean   : 130.4       
##                     3rd Qu.:180.7       3rd Qu.:   0.0       
##                     Max.   :183.7       Max.   :2795.3       
##  NO.CREDITO.INFONAVIT LUGAR.DE.NACIMIENTO     CURP              CALLE          
##  Min.   :0.000e+00    Length:78           Length:78          Length:78         
##  1st Qu.:0.000e+00    Class :character    Class :character   Class :character  
##  Median :0.000e+00    Mode  :character    Mode  :character   Mode  :character  
##  Mean   :1.871e+08                                                             
##  3rd Qu.:0.000e+00                                                             
##  Max.   :6.919e+09                                                             
##  NUMERO.INTERNO       COLONIA          CODIGO.POSTAL    MUNICIPIO        
##  Length:78          Length:78          Min.   :25019   Length:78         
##  Class :character   Class :character   1st Qu.:33604   Class :character  
##  Mode  :character   Mode  :character   Median :33604   Mode  :character  
##                                        Mean   :46508                     
##                                        3rd Qu.:66645                     
##                                        Max.   :67450                     
##     ESTADO          ESTADO.CIVIL       TARJETA.CUENTA    
##  Length:78          Length:78          Length:78         
##  Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character  
##                                                          
##                                                          
## 
str(rhCluster2)
## 'data.frame':    78 obs. of  26 variables:
##  $ NO.DE.BAJAS          : int  5 6 7 8 9 11 12 16 19 20 ...
##  $ APELLIDOS            : chr  "BERNAL FLORES" "SAUCEDO GUZMAN" "MEZA LLANAS" "TORRES LARA" ...
##  $ NOMBRE               : chr  "ERIKA ROSALINDA" "GUADALUPE" "YOANA CRISTINA" "CESAR ANTONIO" ...
##  $ FECHA.DE.NACIMIENTO  : int  33997 28106 34174 33491 26422 36970 32443 37872 37512 36915 ...
##  $ EDAD                 : int  29 46 29 31 50 21 34 19 20 21 ...
##  $ GENERO               : chr  "FEMENINO" "FEMENINO" "FEMENINO" "MASCULINO" ...
##  $ FECHA.DE.ALTA        : int  44518 44532 44532 44538 44551 44531 44532 44488 44541 44546 ...
##  $ MOTIVO.DE.BAJA       : chr  "RENUNCIA VOLUNTARIA" "BAJA POR FALTAS" "BAJA POR FALTAS" "BAJA POR FALTAS" ...
##  $ DIAS.TRABAJADOS      : int  51 37 37 31 18 40 39 86 33 28 ...
##  $ BAJA                 : int  44569 44569 44569 44569 44569 44571 44571 44574 44574 44574 ...
##  $ PUESTO               : chr  "AYUDANTE GENERAL" "AYUDANTE GENERAL" "AYUDANTE GENERAL" "AYUDANTE GENERAL" ...
##  $ DEPARTAMENTO         : chr  "VARIOS" "VARIOS" "VARIOS" "VARIOS" ...
##  $ NO.SEGURO.SOCIAL     : chr  "43109363747" "43937683647" "43099330201" "43099151714" ...
##  $ SALARIO.DIARIO.IMSS  : num  152 152 152 152 152 ...
##  $ FACTOR.CRED.INFONAVIT: num  0 1320 0 0 0 ...
##  $ NO.CREDITO.INFONAVIT : num  0.00 1.92e+09 0.00 0.00 0.00 ...
##  $ LUGAR.DE.NACIMIENTO  : chr  "NUEVO LEON" "NUEVO LEON" "NUEVO LEON" "NUEVO LEON" ...
##  $ CURP                 : chr  "BEFE930128MNLRLR05" "SAGG761212MNLCZD08" "MELY930724MNLZLN01" "TOLC910910HNLRRS09" ...
##  $ CALLE                : chr  "JULIAN VILLAGRAN" "PAPAGAYOS" "RIO AMANONAS" "PALMERA" ...
##  $ NUMERO.INTERNO       : chr  "452" "220" "300" "104" ...
##  $ COLONIA              : chr  "REFORMA" "GOLONDRINAS" "PUEBLO NUEVO" "MIRADOR DEL PARQUE" ...
##  $ CODIGO.POSTAL        : int  66640 66649 66646 67254 67114 66645 66646 66646 66645 66646 ...
##  $ MUNICIPIO            : chr  "APODACA" "APODACA" "APODACA" "JUAREZ" ...
##  $ ESTADO               : chr  "NUEVO LEON" "NUEVO LEON" "NUEVO LEON" "NUEVO LEON" ...
##  $ ESTADO.CIVIL         : chr  "SOLTERO/A" "SOLTERO/A" "UNION LIBRE" "UNION LIBRE" ...
##  $ TARJETA.CUENTA       : chr  "BANORTE" "BANORTE" "BANORTE" "BANORTE" ...

Explorar KMeans en edad por años

humanos5<-rhCluster2 %>% select(EDAD,DIAS.TRABAJADOS,SALARIO.DIARIO.IMSS)
summary(humanos5)
##       EDAD       DIAS.TRABAJADOS  SALARIO.DIARIO.IMSS
##  Min.   : 0.00   Min.   :  0.00   Min.   :151.6      
##  1st Qu.:22.25   1st Qu.:  9.00   1st Qu.:180.7      
##  Median :30.00   Median : 19.00   Median :180.7      
##  Mean   :30.50   Mean   : 45.10   Mean   :174.3      
##  3rd Qu.:36.00   3rd Qu.: 39.75   3rd Qu.:180.7      
##  Max.   :52.00   Max.   :730.00   Max.   :183.7

Normalizar los datos

rhCluster_norm<-scale(humanos5[1:2]) 

Sacar numero de clusters

fviz_nbclust() helps to determine and visualize the optimal number of clusters

fviz_nbclust(rhCluster_norm, kmeans, method="wss")+ # wss method considers total within sum of square
  geom_vline(xintercept=4, linetype=2)+           # optimal number of clusters is computed with the default method = "euclidean"
  labs(subtitle = "Elbow method")

Ver datos de los clusters

rh_cluster2<-kmeans(rh1_norm,4)
rh_cluster2
## K-means clustering with 4 clusters of sizes 1, 17, 8, 52
## 
## Cluster means:
##   DIAS.TRABAJADOS SALARIO.DIARIO.IMSS
## 1       7.5323951           0.5246350
## 2       0.2712304          -1.8780410
## 3       0.6353722           0.4428101
## 4      -0.3312748           0.5357612
## 
## Clustering vector:
##  [1] 2 2 2 2 2 2 2 2 2 2 2 2 4 4 4 4 4 4 4 3 2 4 2 2 2 2 4 4 4 4 4 4 4 4 4 4 4 4
## [39] 4 4 4 4 4 4 4 3 4 4 3 3 3 1 4 4 3 4 4 4 4 4 4 4 4 4 4 4 4 3 3 4 4 4 4 4 4 4
## [77] 4 4
## 
## Within cluster sum of squares by cluster:
## [1] 0.000000 6.019937 3.270968 1.055662
##  (between_SS / total_SS =  93.3 %)
## 
## Available components:
## 
## [1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
## [6] "betweenss"    "size"         "iter"         "ifault"

Visualizar clustering

fviz_cluster(rh_cluster2,data=rhCluster_norm)

La gráfica anterior muestra 4 clusters que analizan la relación entre edad y días trabajados. El cluster verde muestra que un alto número de personas tabajaba más días que el promedio y tenía una edad similar al promedio. El punto naranja muestra que pocos usuarios trabajaban más días que el promedio y al igual que el punto anterior, tenían una edad promedio. En el caso del punto azul y el morado, ambos muestran que un mayor grupo de personas laboraba ‘2 días’, pero tenía mayor variabilidad en la edad.

Agregar la info al data set original

humanos6<-humanos5
humanos6$Clusters<-rh_cluster2$cluster
summary(humanos6)
##       EDAD       DIAS.TRABAJADOS  SALARIO.DIARIO.IMSS    Clusters    
##  Min.   : 0.00   Min.   :  0.00   Min.   :151.6       Min.   :1.000  
##  1st Qu.:22.25   1st Qu.:  9.00   1st Qu.:180.7       1st Qu.:3.000  
##  Median :30.00   Median : 19.00   Median :180.7       Median :4.000  
##  Mean   :30.50   Mean   : 45.10   Mean   :174.3       Mean   :3.423  
##  3rd Qu.:36.00   3rd Qu.: 39.75   3rd Qu.:180.7       3rd Qu.:4.000  
##  Max.   :52.00   Max.   :730.00   Max.   :183.7       Max.   :4.000

Crear Dataset

lets create a dataset so we can identify some characteristics of “Edad” by cluster

humanos7<-humanos6 %>% group_by(Clusters) %>% summarise(EDAD=max(EDAD),DIAS.TRABAJADOS=mean(DIAS.TRABAJADOS)) %>% arrange(desc(EDAD))
summary(humanos7)
##     Clusters         EDAD       DIAS.TRABAJADOS 
##  Min.   :1.00   Min.   :32.00   Min.   : 14.98  
##  1st Qu.:1.75   1st Qu.:41.75   1st Qu.: 56.07  
##  Median :2.50   Median :47.50   Median : 86.32  
##  Mean   :2.50   Mean   :44.75   Mean   :229.41  
##  3rd Qu.:3.25   3rd Qu.:50.50   3rd Qu.:259.66  
##  Max.   :4.00   Max.   :52.00   Max.   :730.00

Agrupar clusters por nombre

humanos6$Cluster_Names<-factor(humanos6$Clusters,levels = c(1,2,3,4), 
                          labels=c("Joven", "Avanzado ", " Adulto", "Jubilado"))
summary(humanos6)
##       EDAD       DIAS.TRABAJADOS  SALARIO.DIARIO.IMSS    Clusters    
##  Min.   : 0.00   Min.   :  0.00   Min.   :151.6       Min.   :1.000  
##  1st Qu.:22.25   1st Qu.:  9.00   1st Qu.:180.7       1st Qu.:3.000  
##  Median :30.00   Median : 19.00   Median :180.7       Median :4.000  
##  Mean   :30.50   Mean   : 45.10   Mean   :174.3       Mean   :3.423  
##  3rd Qu.:36.00   3rd Qu.: 39.75   3rd Qu.:180.7       3rd Qu.:4.000  
##  Max.   :52.00   Max.   :730.00   Max.   :183.7       Max.   :4.000  
##    Cluster_Names
##  Joven    : 1   
##  Avanzado :17   
##   Adulto  : 8   
##  Jubilado :52   
##                 
## 

Entre más joven más días trabajados

Agrupar por clusters y resumir columnas

humanos8 <- humanos6%>% group_by(Cluster_Names) %>% summarize(EDAD=max(EDAD), 
                                                       DIAS.TRABAJADOS =mean(DIAS.TRABAJADOS),
                                                         Count=n())

Convertir a tabla

clusters2<-as.data.frame(humanos8)
clusters2
##   Cluster_Names EDAD DIAS.TRABAJADOS Count
## 1         Joven   32       730.00000     1
## 2     Avanzado    50        69.76471    17
## 3        Adulto   45       102.87500     8
## 4      Jubilado   52        14.98077    52

Visualizar graficos

Ver los dias trabajados en general junto con la edad

ggplot(humanos8,aes(x=reorder(Cluster_Names,Count),y=Count,fill=Cluster_Names)) +
  geom_bar(stat="identity")

La gráfica anterior nos muestra la cantidad de días trabajados de acuerdo a la edad de los empleados. En este caso, vemos que el grupo de edad ‘joven’ tenían aprox. 31 años y representa un bajo porcentaje de los empleados. Para ‘avanzado’ son los empleados que tienen arriba de 32 años y laboraron la mayor cantidad de días (1067). Para ‘jubilado’, son las personas con un aprox. de 50 años y que laboraron durante 102 días. Finalmente, para el grupo de ‘adulto’ (el más elevado), son aquellos que laboraron un promedio de 44 días y tienen la edad de 52 años.

Dias trabajados por nombre de clusters

ggplot(humanos8, aes(x=Cluster_Names,y=DIAS.TRABAJADOS,fill= Cluster_Names,label=round(DIAS.TRABAJADOS,digits=2))) + 
  geom_col() + 
  geom_text()

La gráfica anterior nos muestra la cantidad de días trabajados de acuerdo a los días trabajados. En este caso, esto nos muestra que el grupo con mayor bajas fueron del segmento ‘adulto’ y es el que laboró en promedio un menor número de días contra ‘avanzado’ que es el segundo grupo más bajo y laboró el mayor número de días en promedio.

Edad por nombre de clusters

ggplot(humanos8,aes(x=Cluster_Names,y=EDAD,fill= Cluster_Names,label=round(EDAD,digits=2))) + 
  geom_col() + 
  geom_text()

La gráfica anterior nos muestra la edad promedio de cada uno de los grupos. Como explicamos anteriormente, se muestra que ‘adulto’ es el de empleados con mayor edad y ‘joven’ es el de personas de menor edad.

Dias trabajados por nombre de clusters

ggplot(humanos6, aes(x=Cluster_Names, y=EDAD, fill=Cluster_Names)) +
  geom_boxplot()+
  ggtitle("Dispersion of 'Edad' by Clusters Names")

La gráfica anterior nos muestra la dispersión de acuerdo a la edad de los empleados. En el caso de ‘adulto’, tiene una gran cantidad de puntos atípicos lo cual indica que hay datos fuera de serie. En el caso de ‘jubilado’, vemos que es el grupo con mayor dispersión, mostrando datos de edad entre 25 y 32 años.

Salario diario por nombre de clusters

ggplot(humanos6, aes(x=Cluster_Names, y=DIAS.TRABAJADOS, fill=Cluster_Names)) +
  geom_boxplot()+
  ggtitle("Dispersion of 'Dias_Trabajados' by Clusters Names")

La gráfica anterior muestra la dispersión de acuerdo a días trabajados y los grupos anterior explicados. Vemos que ‘avanzado’ es el gurpo con mayor dispersión, mostrando que el promedio de los empleados laboraron entre 550 y 1300 días. Es el grupo con mayor número de empleados que trabajaron en este periodo de tiempo. En el caso de ‘adulto’ y ‘jubilado’, son aquellos grupos que muestran una dispersión baja y una variedad de puntos atípicos, mientras que ‘joven’ destaca una baja dispersión y una media de más de 500 días trabajados.

Cluster 3

Importar la base de datos

cluster3<-read.csv("C:\\Users\\danyc\\Downloads\\HR_Bajas 2.csv") 
summary(cluster3)
##   NO.DE.BAJAS      APELLIDOS            NOMBRE          FECHA.DE.NACIMIENTO
##  Min.   :  5.00   Length:78          Length:78          Min.   :25585      
##  1st Qu.: 49.75   Class :character   Class :character   1st Qu.:31751      
##  Median :167.50   Mode  :character   Mode  :character   Median :33797      
##  Mean   :139.60                                         Mean   :33611      
##  3rd Qu.:212.75                                         3rd Qu.:36519      
##  Max.   :238.00                                         Max.   :44632      
##       EDAD          GENERO          FECHA.DE.ALTA   MOTIVO.DE.BAJA    
##  Min.   : 0.00   Length:78          Min.   :43961   Length:78         
##  1st Qu.:22.25   Class :character   1st Qu.:44567   Class :character  
##  Median :30.00   Mode  :character   Median :44726   Mode  :character  
##  Mean   :30.50                      Mean   :44664                     
##  3rd Qu.:36.00                      3rd Qu.:44759                     
##  Max.   :52.00                      Max.   :44790                     
##  DIAS.TRABAJADOS       BAJA          PUESTO          DEPARTAMENTO      
##  Min.   :  0.00   Min.   :44569   Length:78          Length:78         
##  1st Qu.:  9.00   1st Qu.:44613   Class :character   Class :character  
##  Median : 19.00   Median :44741   Mode  :character   Mode  :character  
##  Mean   : 45.10   Mean   :44709                                        
##  3rd Qu.: 39.75   3rd Qu.:44784                                        
##  Max.   :730.00   Max.   :44814                                        
##  NO.SEGURO.SOCIAL   SALARIO.DIARIO.IMSS FACTOR.CRED.INFONAVIT
##  Length:78          Min.   :151.6       Min.   :   0.0       
##  Class :character   1st Qu.:180.7       1st Qu.:   0.0       
##  Mode  :character   Median :180.7       Median :   0.0       
##                     Mean   :174.3       Mean   : 130.4       
##                     3rd Qu.:180.7       3rd Qu.:   0.0       
##                     Max.   :183.7       Max.   :2795.3       
##  NO.CREDITO.INFONAVIT LUGAR.DE.NACIMIENTO     CURP              CALLE          
##  Min.   :0.000e+00    Length:78           Length:78          Length:78         
##  1st Qu.:0.000e+00    Class :character    Class :character   Class :character  
##  Median :0.000e+00    Mode  :character    Mode  :character   Mode  :character  
##  Mean   :1.871e+08                                                             
##  3rd Qu.:0.000e+00                                                             
##  Max.   :6.919e+09                                                             
##  NUMERO.INTERNO       COLONIA          CODIGO.POSTAL    MUNICIPIO        
##  Length:78          Length:78          Min.   :25019   Length:78         
##  Class :character   Class :character   1st Qu.:33604   Class :character  
##  Mode  :character   Mode  :character   Median :33604   Mode  :character  
##                                        Mean   :46508                     
##                                        3rd Qu.:66645                     
##                                        Max.   :67450                     
##     ESTADO          ESTADO.CIVIL       TARJETA.CUENTA    
##  Length:78          Length:78          Length:78         
##  Class :character   Class :character   Class :character  
##  Mode  :character   Mode  :character   Mode  :character  
##                                                          
##                                                          
## 
str(cluster3)
## 'data.frame':    78 obs. of  26 variables:
##  $ NO.DE.BAJAS          : int  5 6 7 8 9 11 12 16 19 20 ...
##  $ APELLIDOS            : chr  "BERNAL FLORES" "SAUCEDO GUZMAN" "MEZA LLANAS" "TORRES LARA" ...
##  $ NOMBRE               : chr  "ERIKA ROSALINDA" "GUADALUPE" "YOANA CRISTINA" "CESAR ANTONIO" ...
##  $ FECHA.DE.NACIMIENTO  : int  33997 28106 34174 33491 26422 36970 32443 37872 37512 36915 ...
##  $ EDAD                 : int  29 46 29 31 50 21 34 19 20 21 ...
##  $ GENERO               : chr  "FEMENINO" "FEMENINO" "FEMENINO" "MASCULINO" ...
##  $ FECHA.DE.ALTA        : int  44518 44532 44532 44538 44551 44531 44532 44488 44541 44546 ...
##  $ MOTIVO.DE.BAJA       : chr  "RENUNCIA VOLUNTARIA" "BAJA POR FALTAS" "BAJA POR FALTAS" "BAJA POR FALTAS" ...
##  $ DIAS.TRABAJADOS      : int  51 37 37 31 18 40 39 86 33 28 ...
##  $ BAJA                 : int  44569 44569 44569 44569 44569 44571 44571 44574 44574 44574 ...
##  $ PUESTO               : chr  "AYUDANTE GENERAL" "AYUDANTE GENERAL" "AYUDANTE GENERAL" "AYUDANTE GENERAL" ...
##  $ DEPARTAMENTO         : chr  "VARIOS" "VARIOS" "VARIOS" "VARIOS" ...
##  $ NO.SEGURO.SOCIAL     : chr  "43109363747" "43937683647" "43099330201" "43099151714" ...
##  $ SALARIO.DIARIO.IMSS  : num  152 152 152 152 152 ...
##  $ FACTOR.CRED.INFONAVIT: num  0 1320 0 0 0 ...
##  $ NO.CREDITO.INFONAVIT : num  0.00 1.92e+09 0.00 0.00 0.00 ...
##  $ LUGAR.DE.NACIMIENTO  : chr  "NUEVO LEON" "NUEVO LEON" "NUEVO LEON" "NUEVO LEON" ...
##  $ CURP                 : chr  "BEFE930128MNLRLR05" "SAGG761212MNLCZD08" "MELY930724MNLZLN01" "TOLC910910HNLRRS09" ...
##  $ CALLE                : chr  "JULIAN VILLAGRAN" "PAPAGAYOS" "RIO AMANONAS" "PALMERA" ...
##  $ NUMERO.INTERNO       : chr  "452" "220" "300" "104" ...
##  $ COLONIA              : chr  "REFORMA" "GOLONDRINAS" "PUEBLO NUEVO" "MIRADOR DEL PARQUE" ...
##  $ CODIGO.POSTAL        : int  66640 66649 66646 67254 67114 66645 66646 66646 66645 66646 ...
##  $ MUNICIPIO            : chr  "APODACA" "APODACA" "APODACA" "JUAREZ" ...
##  $ ESTADO               : chr  "NUEVO LEON" "NUEVO LEON" "NUEVO LEON" "NUEVO LEON" ...
##  $ ESTADO.CIVIL         : chr  "SOLTERO/A" "SOLTERO/A" "UNION LIBRE" "UNION LIBRE" ...
##  $ TARJETA.CUENTA       : chr  "BANORTE" "BANORTE" "BANORTE" "BANORTE" ...

Numero de clusters

Juntar los datos relacionados a edad en años

newbd<-cluster3 %>% select(EDAD,SALARIO.DIARIO.IMSS)
summary(newbd)
##       EDAD       SALARIO.DIARIO.IMSS
##  Min.   : 0.00   Min.   :151.6      
##  1st Qu.:22.25   1st Qu.:180.7      
##  Median :30.00   Median :180.7      
##  Mean   :30.50   Mean   :174.3      
##  3rd Qu.:36.00   3rd Qu.:180.7      
##  Max.   :52.00   Max.   :183.7

Normalizar datos

newbdnorm<-scale(newbd[2:1])

Grafica para ver numero optimo de clusters

fviz_nbclust(newbdnorm, kmeans, method="wss")+ # wss method considers total within sum of square
  geom_vline(xintercept=4, linetype=2)+           # optimal number of clusters is computed with the default method = "euclidean"
  labs(subtitle = "Elbow method")

Visualizar informacion

newbd2clus<-kmeans(newbdnorm,4)
newbd2clus
## K-means clustering with 4 clusters of sizes 12, 17, 26, 23
## 
## Cluster means:
##   SALARIO.DIARIO.IMSS       EDAD
## 1           0.5317981  1.5586093
## 2          -1.8780410 -0.1112219
## 3           0.5184044 -0.8844567
## 4           0.5246350  0.2688407
## 
## Clustering vector:
##  [1] 2 2 2 2 2 2 2 2 2 2 2 2 1 3 3 4 3 3 3 3 2 1 2 2 2 2 4 4 1 1 1 3 3 4 4 3 3 4
## [39] 1 3 3 4 1 3 1 4 4 3 4 4 4 4 4 4 4 3 3 1 3 3 3 4 4 3 4 4 4 1 3 4 1 4 1 3 3 3
## [77] 3 3
## 
## Within cluster sum of squares by cluster:
## [1]  1.534968 15.627899  7.104697  1.698551
##  (between_SS / total_SS =  83.1 %)
## 
## Available components:
## 
## [1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
## [6] "betweenss"    "size"         "iter"         "ifault"

Ver resultados de clusters

fviz_cluster(newbd2clus,data=newbdnorm)

Agregar los resultados al dataset original para interpretacion

newbd3<-newbd 
newbd3$Clusters<-newbd2clus$cluster
summary(newbd3)
##       EDAD       SALARIO.DIARIO.IMSS    Clusters    
##  Min.   : 0.00   Min.   :151.6       Min.   :1.000  
##  1st Qu.:22.25   1st Qu.:180.7       1st Qu.:2.000  
##  Median :30.00   Median :180.7       Median :3.000  
##  Mean   :30.50   Mean   :174.3       Mean   :2.769  
##  3rd Qu.:36.00   3rd Qu.:180.7       3rd Qu.:4.000  
##  Max.   :52.00   Max.   :183.7       Max.   :4.000

Crear datasets para ver la info de caracteristicas por edad por clusters

newbd4<-newbd3 %>% group_by(Clusters) %>% summarise(EDAD=max(EDAD),SALARIO.DIARIO.IMSS=mean(SALARIO.DIARIO.IMSS)) %>% arrange(desc(EDAD))
summary(newbd4)
##     Clusters         EDAD       SALARIO.DIARIO.IMSS
##  Min.   :1.00   Min.   :27.00   Min.   :151.6      
##  1st Qu.:1.75   1st Qu.:35.25   1st Qu.:173.4      
##  Median :2.50   Median :44.00   Median :180.6      
##  Mean   :2.50   Mean   :41.75   Mean   :173.4      
##  3rd Qu.:3.25   3rd Qu.:50.50   3rd Qu.:180.7      
##  Max.   :4.00   Max.   :52.00   Max.   :180.8

Agrupar clusters por nombre

newbd3$Cluster_Names<-factor(newbd3$Clusters,levels = c(1,2,3,4), 
                          labels=c("Joven", "Avanzado ", " Adulto", "Jubilado"))
summary(newbd3)
##       EDAD       SALARIO.DIARIO.IMSS    Clusters       Cluster_Names
##  Min.   : 0.00   Min.   :151.6       Min.   :1.000   Joven    :12   
##  1st Qu.:22.25   1st Qu.:180.7       1st Qu.:2.000   Avanzado :17   
##  Median :30.00   Median :180.7       Median :3.000    Adulto  :26   
##  Mean   :30.50   Mean   :174.3       Mean   :2.769   Jubilado :23   
##  3rd Qu.:36.00   3rd Qu.:180.7       3rd Qu.:4.000                  
##  Max.   :52.00   Max.   :183.7       Max.   :4.000

Agrupar por nombre y por columna

newbd5 <- newbd3%>% group_by(Cluster_Names) %>% summarize(EDAD=max(EDAD), 
                                                       SALARIO.DIARIO.IMSS =mean(SALARIO.DIARIO.IMSS),
                                                         Count=n())

Visualizar graficas

Poner como tabla los datos

clusterssalario<-as.data.frame(newbd5)
clusterssalario
##   Cluster_Names EDAD SALARIO.DIARIO.IMSS Count
## 1         Joven   52            180.7667    12
## 2     Avanzado    50            151.6100    17
## 3        Adulto   27            180.6046    26
## 4      Jubilado   38            180.6800    23

Graficar por nombre

ggplot(newbd5,aes(x=reorder(Cluster_Names,Count),y=Count,fill=Cluster_Names)) +
  geom_bar(stat="identity")

Salario diario por numero de clusters

ggplot(newbd5, aes(x=Cluster_Names,y=SALARIO.DIARIO.IMSS,fill= Cluster_Names,label=round(SALARIO.DIARIO.IMSS,digits=2))) + 
  geom_col() + 
  geom_text()

Aqui nos podemos dar cuenta acerca de que los sueldos no varian mucho entre edades pero es importante decir que se denota por centavos una mayoria en el sueldo de los jovenes especificamente más que en otros rangos de edades.

Hallazgos

[ 1 ] El salario diario promedio de FORM es de $180 pesos mexicanos, es decir, $5,400 pesos mensuales. De acuerdo a datos compartidos por la INEGI, se prevé que para 2022 el salario mínimo en México sea de $5,255 pesos mensuales. Esto refleja que el salario mensual de FORM es 2% mayor al salario mínimo del país. [ 2 ] Se destacan tres razones principales de bajas de empleados: bajas por faltas, renuncia voluntaria o término de contrato. En su mayoría, hubo una baja por faltas del puesto de ayudante general y renuncia voluntaria para el mismo puesto. [ 3 ] Hubo un alto número de bajas de empleados que trabajaron más de 200 días, es decir, la mayoría de los ex-empleados estuvieron en FORM más de 1 año y tenían un salario promedio de $180 pesos. [ 4 ] Los sueldos de los empleados no varían mucho dependiendo de la edad. Sin embargo, vemos que hay mayor rotación para el grupo ‘adulto’

Referencias

Zhu, A. (2022, 12 agosto). K-Means Clustering Explained Simply - Towards Data Science. Medium. Recuperado 10 de octubre de 2022, de https://towardsdatascience.com/explain-ml-in-a-simple-way-k-means-clustering-e925d019743b

Gulzar, M. (2022, 6 enero). K-Means Clustering: Concepts and Implementation in R for Data Science. Medium. Recuperado 10 de octubre de 2022, de https://towardsdatascience.com/k-means-clustering-concepts-and-implementation-in-r-for-data-science-32cae6a3ceba

Gulzar, M. (2022b, enero 6). K-Means Clustering: Concepts and Implementation in R for Data Science. Medium. Recuperado 10 de octubre de 2022, de https://towardsdatascience.com/k-means-clustering-concepts-and-implementation-in-r-for-data-science-32cae6a3ceba

LS0tDQp0aXRsZTogPHNwYW4gc3R5bGU9IkNvbG9yOk9yYW5nZSI+IkFjdGl2aWRhZCAyLjYgSyBNRUFOUyINCmF1dGhvcjogIkVxdWlwbyAyIg0KZGF0ZTogIjIwMjItMTAtMTAiDQpvdXRwdXQ6IA0KICAgICAgaHRtbF9kb2N1bWVudDoNCiAgICAgICAgdG9jOiB0cnVlDQogICAgICAgIHRvY19mbG9hdDogdHJ1ZQ0KICAgICAgICBjb2RlX2ZvbGRpbmc6ICJoaWRlIg0KICAgICAgICBjb2RlX2Rvd25sb2FkOiB0cnVlDQotLS0NCioqKg0KIyA8aW1nIHNyYz0gIkM6XFxVc2Vyc1xcZGFueWNcXERvd25sb2Fkc1xcbG9nby5wbmciIC8+DQoNCg0KYGBge3J9IA0KDQprbml0cjo6aW5jbHVkZV91cmwoImh0dHBzOi8vZm9ybS5jb20ubXgvIikNCg0KYGBgDQoNCiMgRGVmaW5pY2lvbmVzIA0KDQojIyAqMS4gSyAtIG1lYW5zIENsdXN0ZXJpbmcqDQoNCkNvbnRyaWJ1eWVuIGEgbGEgaWRlbnRpZmljYWNpw7NuIGRlIGNsdXN0ZXJzLCB5YSBxdWUgSy1tZWFucyBsbyBxdWUgaGFjZSBlcyBhZ3J1cGFyLCBxdWllcmUgZGVjaXIgcXVlIGVzdGEgZnVuY2nDs24gbyBhbGdvcml0bW8gZGl2aWRlIGxvcyBkYXRvcyB1IG9ic2VydmFjaW9uZXMgb2J0ZW5pZGFzIGVuIGdydXBvcyBjb24gY2FyYWN0ZXLDrXN0aWNhcyBzaW1pbGFyZXMuIERlIGVzdGEgbWFuZXJhIGVzIHF1ZSBzZSBwdWVkZSBhbmFsaXphciBjb24gw6l4aXRvIGNhZGEgQ2x1c3Rlciwgb2J0ZW5pZW5kbyBpbmZvcm1hY2nDs24gaW1wb3J0YW50ZSBkZSBjYWRhIHZhcmlhYmxlIHkgbGEgc2ltaWxpdHVkZXMgZW50cmUgZWxsYXMsIGxvIHF1ZSBoYWNlIHF1ZSBzZWEgbcOhcyBjb21wcmVuc2libGUsIHlhIHF1ZSBoYWNlIHF1ZSBsb3MgZGF0b3MgbyBpbmZvcm1hY2nDs24gcXVlIG5vIHNlIHB1ZWRlIHByb2Nlc2FyIGVuIGluZm9ybWFjacOzbiBwcm9jZXNhYmxlLiBVbiBjbGFybyBlamVtcGxvIGRlIGPDs21vIGZ1bmNpb25hIGVzIHRlbmllbmRvIGdydXBvcyBxdWUgY29tcGFydGVuIGxvcyBtaXNtb3MgdGVtYXMsIGltw6FnZW5lcyBjb24gZWwgbWlzbW8gb2JqZXRvIG8gcGVyc29uYSwgZXRjLiANCg0KIyMgKjIuIFVuc3VwZXJ2aXNlZCBMZWFybmluZyoNCg0KQ3VhbmRvIHNlIHV0aWxpemEgbGEgZnVuY2nDs24gSy1tZWFucyBzZSBlbmZvY2EgZW4gYWdydXBhciBkYXRvcyBubyBzdXBlcnZpc2Fkb3MsIGVzdG8gcXVpZXJlIGRlY2lyIHF1ZSBLLW1lYW5zIGVzIHVuIGFsZ29yaXRtbyBubyBzdXBlcnZpc2Fkby4gRW4gb3RyYXMgcGFsYWJyYXMgbG8gcXVlIHF1aWVyZSBkZWNpciBBcHJlbmRpemFqZSBubyBzdXBlcnZpc2FkbywgZXMgcmVmZXJlbnRlIGFsIHRpcG8gZGUgYWxnb3JpdG1vIG8gdMOpY25pY2EgcXVlIHNlIHV0aWxpemEgeSBlcyBpbXBvcnRhbnRlLCB5YSBxdWUgZ3JhY2lhcyBhIHF1ZSBzZSBkZWZpbmUgZGUgZXN0YSBtYW5lcmEgbGFzIHTDqWNuaWNhcyB5IGFsZ29yaXRtb3MsIHBvZGVtb3MgaW1wbGVtZW50YXJsbyBkZSBtYW5lcmEgY29ycmVjdGEgZW4gZWwgYW7DoWxpc2lzIGRlIERhdG9zLiBFc3RvIHF1aWVyZSBkZWNpciBxdWUgZXN0ZSB0ZXJtaW5vIG5vcyBkYSBhIGVudGVuZGVyIHF1ZSBlc2EgYmFzZSBkZSBkYXRvcyBzZSBvY3VwYXLDoSBlbCBhbGdvcml0bW8gZGUgYWdydXBhbWllbnRvIG5vIHN1cGVydmlzYWRvLCB5YSBxdWUgbGEgYmFzZSBjdWVudGEgY29uIGRhdG9zIGRlIGVudHJhZGEgc2luIHJlc3B1ZXN0YXMgZXRpcXVldGFkYXMsIGVzIHBvciBlc28gbGEgaW1wb3J0YW5jaWEgcGFyYSBwb2RlciBpZGVudGlmaWNhcmxvLiANCg0KIyMgKjMuIERpc3RhbmNpYSBFdWNsaWRpYW5hIC8gRXVjbGllZGVhbmNlIERpc3RhbmNlKiANCg0KUHJpbmNpcGFsbWVudGUgbGEgZGlzdGFuY2lhIGV1Y2xpZGlhbmEgZXMgY3VhbmRvIHNlIGVuY3VlbnRyYSB1biBlc3BhY2lvIHZlY3RvcmlhbCwgcXVlIGVzIGlndWFsIG8gaW5mZXJpb3IgYSBsYSB2YXJpYWJsZSBxdWUgZXN0YW1vcyBhbmFsaXphbmRvLiBDdWFuZG8gc2UgaWRlbnRpZmljYW4gY2x1c3RlcnMgZW5jb250cmFtb3MgYSBsb3MgY2VudHJvaWRlcywgRW4gbGEgcGFydGUgZG9uZGUgc2UgdXRpbGl6YSBsYSBkaXN0YW5jaWEgZXVjbGlkaWFuYSBlcyBjdWFuZG8gbG9zIGNlbnRyb2lkZXMgeWEgbm8gc2UgcmVwaXRlbiB5IGVuY3VlbnRyYW4gdW5hIHBvc2ljacOzbiB5ICBubyBjYW1iaWFuLCBwb3IgbG8gcXVlIHNlIGhhY2UgbGEgcmVsYWNpw7NuIGNvbiBwdW50b3MgYWwgcmVkZWRvciBkZWwgY2VudHJvaWRlcywgY2FkYSBwdW50byBlcyB1bmEgdmFyaWFibGUgcG9yIGxvIHRhbnRvIGxhIGRpc3RhbmNpYSBxdWUgZXhpc3RlIGVudHJlIHZhcmlhYmxlcyBlcyBsYSBkaXN0YW5jaWEgZXVjbGlkaWFuYS4gTGEgY3VhbCBub3MgYXl1ZGEgYSBkZXNjdWJyaXIgbGEgc2ltaWxpdHVkIGVudHJlIGxhcyB2YXJpYWJsZXMgcXVlIGVzdGFtb3MgYW5hbGl6YW5kby4NCg0KDQojIENsdXN0ZXIgMQ0KDQojIyMgSW1wb3J0YXIgUGFxdWV0ZXMNCg0KYGBge3IsIGluY2x1ZGU9RkFMU0V9DQpsaWJyYXJ5KGZvcmVpZ24pDQpsaWJyYXJ5KGRwbHlyKSAgICAgICAgIyBkYXRhIG1hbmlwdWxhdGlvbiANCmxpYnJhcnkoZ2dwbG90MikgICAgICAjIGRhdGEgdmlzdWFsaXphdGlvbiANCmxpYnJhcnkocHN5Y2gpICAgICAgICAjIGZ1bmN0aW9ucyBmb3IgbXVsdGl2YXJpYXRlIGFuYWx5c2lzIA0KbGlicmFyeShjb3JycGxvdCkgICAgICMgY29ycmVsYXRpb24gcGxvdHMNCmxpYnJhcnkoanRvb2xzKSAgICAgICAjIHByZXNlbnRhdGlvbiBvZiByZWdyZXNzaW9uIGFuYWx5c2lzIA0KbGlicmFyeShsbXRlc3QpICAgICAgICMgZGlhZ25vc3RpYyBjaGVja3MgLSBsaW5lYXIgcmVncmVzc2lvbiBhbmFseXNpcyANCmxpYnJhcnkoY2FyKSAgICAgICAgICAjIGRpYWdub3N0aWMgY2hlY2tzIC0gbGluZWFyIHJlZ3Jlc3Npb24gYW5hbHlzaXMNCmxpYnJhcnkoZmFjdG9leHRyYSkgICAjIHByb3ZpZGVzIGZ1bmN0aW9ucyB0byBleHRyYWN0IGFuZCB2aXN1YWxpemUgdGhlIG91dHB1dCBvZiBleHBsb3JhdG9yeSBtdWx0aXZhcmlhdGUgZGF0YSBhbmFseXNlcw0KbGlicmFyeShnZ2ZvcnRpZnkpICAgICMgZGF0YSB2aXN1YWxpemF0aW9uIHRvb2xzIGZvciBzdGF0aXN0aWNhbCBhbmFseXNpcyByZXN1bHRzDQpgYGANCg0KDQojIyMgTGltcGllemEgZGUgZGF0b3MgeSB0cmFuc2Zvcm1hY2lvbg0KDQojIyBJbXBvcnRhciBCYXNlIGRlIERhdG9zDQpmaWxlLmNob29zZSgpDQpgYGB7ciwgaW5jbHVkZT1GQUxTRX0NCnJoPC1yZWFkLmNzdigiQzpcXFVzZXJzXFxkYW55Y1xcRG93bmxvYWRzXFxIUl9CYWphcyAyLmNzdiIpIA0Kc3VtbWFyeShyaCkNCnN0cihyaCkNCmBgYA0KDQojIyBFeHBsb3JhciBkYXRvcw0KDQpgYGB7cn0NCnJoMTwtcmggJT4lIHNlbGVjdChFREFELERJQVMuVFJBQkFKQURPUyxTQUxBUklPLkRJQVJJTy5JTVNTKQ0Kc3VtbWFyeShyaDEpDQpgYGANCg0KDQojIyBOb3JtYWxpemFyIGxvcyBkYXRvcw0KYGBge3J9DQpyaDFfbm9ybTwtc2NhbGUocmgxWzI6M10pIA0KYGBgDQoNCg0KIyMgTnVtZXJvIE9wdGltbyBkZSBDbHVzdGVycw0KDQpgYGB7cn0NCmZ2aXpfbmJjbHVzdChyaDFfbm9ybSwga21lYW5zLCBtZXRob2Q9IndzcyIpKyAjIHdzcyBtZXRob2QgY29uc2lkZXJzIHRvdGFsIHdpdGhpbiBzdW0gb2Ygc3F1YXJlDQogIGdlb21fdmxpbmUoeGludGVyY2VwdD00LCBsaW5ldHlwZT0yKSsgICAgICAgICAgICMgb3B0aW1hbCBudW1iZXIgb2YgY2x1c3RlcnMgaXMgY29tcHV0ZWQgd2l0aCB0aGUgZGVmYXVsdCBtZXRob2QgPSAiZXVjbGlkZWFuIg0KICBsYWJzKHN1YnRpdGxlID0gIkVsYm93IG1ldGhvZCIpICANCmBgYA0KICAgICAgICAgICAgICAgDQoNCg0KIyMgVmlzdWFsaXphciBsYSBpbmZvIGRlIGNsdXN0ZXJzIA0KYGBge3J9DQpyaDFfY2x1c3Rlcjwta21lYW5zKHJoMV9ub3JtLDQpDQpyaDFfY2x1c3Rlcg0KYGBgDQoNCg0KTXVlc3RyYSBxdWUgdXRpbGljZW1vcyA0IGNsdXN0ZXJzIHBhcmEgYW5hbGl6YXIgeSB2aXN1YWxpemFyIGxhIGluZm9ybWFjacOzbi4gDQoNCiMjIFZpc3VhbGl6YXIgUmVzdWx0YWRvcw0KYGBge3J9DQpmdml6X2NsdXN0ZXIocmgxX2NsdXN0ZXIsZGF0YT1yaDFfbm9ybSkNCmBgYA0KDQoNCkVsIHByaW1lciBjbHVzdGVyIGFuYWxpemEgbGEgcmVsYWNpw7NuIGVudHJlIGVsIHNhbGFyaW8gZGlhcmlvIElNU1MgeSBsb3MgZMOtYXMgdHJhYmFqYWRvcyBkZSBsb3MgZW1wbGVhZG9zIHF1ZSB5YSBmdWVyb24gZGFkb3MgZGUgYmFqYS4gRW4gZXN0ZSBjYXNvLCB2ZW1vcyBxdWUgaGF5IHVuIGltcGFjdG8gYWx0byBlbnRyZSBhcXVlbGxvcyBlbXBsZWFkb3MgcXVlIHRyYWJhamFyb24gZGUgMiBhIDggZMOtYXMsIHBlcm8gdHV2aWVyb24gdW4gc2FsYXJpbyBkaWFyaW8gYmFqbywgbXVjaG8gbWVub3IgYSAwIGVuIGxhIGdyw6FmaWNhLiBMb3Mgb3Ryb3MgZG9zIHJlc3VsdGFkb3MsIHNlIG11ZXN0cmEgcXVlIHRyYWJhamFyb24gbWVub3MgZMOtYXMsIHBlcm8gYWwgaWd1YWwgcXVlIGVsIHByaW1lciBjbHVzdGVyIGV4cGxpY2FkbywgdHV2aWVyb24gdW4gc2FsYXJpbyBtdXkgYmFqby4gQ29uIGVzdG9zIHRyZXMsIGhheSB1bmEgY29ycmVsYWNpw7NuIGFsdGEsIHNpZ25pZmljYXRpdmEgeSBuZWdhdGl2YS4gUGFyYSBlbCBncnVwbyBkZSBhcnJpYmEsIHZlbW9zIHF1ZSBlc3RvcyB0cmFiYWphcm9uIHVuIHByb21lZGlvIGRlIDItMyBkw61hcyB5IHR1dmllcm9uIHVuIHNhbGFyaW8gZGlhcmlvIG11eSBhbHRvLiBQb2RlbW9zIGluZmVyaXIgcXVlIGVzdGUgZ3J1cG8gcGVydGVuZWNlIGFsIMOhcmVhIGFkbWluaXN0cmF0aXZhLCBxdWUgY29tbyB2aW1vcyBlbiBhbsOhbGlzaXMgYW50ZXJpb3Jlcywgc29uIGVsIGdydXBvIGNvbiB1biBzYWxhcmlvIG11Y2hvIG3DoXMgYWx0byBhbCByZXN0by4NCg0KIA0KYGBge3J9DQpyaDI8LXJoMQ0KcmgyJENsdXN0ZXJzPC1yaDFfY2x1c3RlciRjbHVzdGVyDQpzdW1tYXJ5KHJoMikNCmBgYA0KDQoNCiMjIyBDcmVhciB1biBkYXRhc2V0DQpgYGB7cn0NCnJoMzwtcmgyICU+JSBncm91cF9ieShDbHVzdGVycykgJT4lIHN1bW1hcmlzZShTQUxBUklPLkRJQVJJTy5JTVNTPW1heChTQUxBUklPLkRJQVJJTy5JTVNTKSxESUFTLlRSQUJBSkFET1M9bWVhbihESUFTLlRSQUJBSkFET1MpKSAlPiUgYXJyYW5nZShkZXNjKFNBTEFSSU8uRElBUklPLklNU1MpKQ0Kc3VtbWFyeShyaDMpDQpgYGANCg0KDQojIyMgQWdydXBhciBjbHVzdGVycyBwb3Igbm9tYnJlDQpgYGB7cn0NCnJoMiRDbHVzdGVyX05hbWVzPC1mYWN0b3IocmgyJENsdXN0ZXJzLGxldmVscyA9IGMoMSwyLDMsNCksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoIkJham8iLCAiUHJvbWVkaW8gIiwgIkFycmliYSBkZWwgcHJvbSIsICJBbHRvIikpDQpzdW1tYXJ5KHJoMikNCmBgYA0KDQoNCk11Y2hvcyBkw61hcyB0cmFiYWphZG9zIHkgcG9jbyBzYWxhcmlvDQoNCiMjIyBBZ3J1cGFyIHkgZGFyIHJlc3VtZW4gcG9yIG5vbWJyZXMNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIA0KYGBge3J9DQpyaDQgPC0gcmgyJT4lIGdyb3VwX2J5KENsdXN0ZXJfTmFtZXMpICU+JSBzdW1tYXJpemUoRElBUy5UUkFCQUpBRE9TPW1heChESUFTLlRSQUJBSkFET1MpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgU0FMQVJJTy5ESUFSSU8uSU1TUyA9bWVhbihTQUxBUklPLkRJQVJJTy5JTVNTKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBDb3VudD1uKCkpDQoNCg0KYGBgDQoNCiMjIyBEYXIgZm9ybWF0byBkZSB0YWJsYSBwYXJhIGVuc2XDsWFyIGxhIGluZm9ybWFjaW9uIGRlIGxvcyBjbHVzdGVycy4NCmBgYHtyfQ0KY2x1c3RlcnM8LWFzLmRhdGEuZnJhbWUocmg0KQ0KY2x1c3RlcnMNCmBgYA0KDQoNCiMjIyBHcmFmaWNhciBlbCBuby4gZGUgZGF0b3Mgb2JzZXJ2YWRvcyBwb3Igbm9tYnJlIGRlIGNsdXN0ZXJzbGV0cyANCmBgYHtyfQ0KZ2dwbG90KHJoNCxhZXMoeD1yZW9yZGVyKENsdXN0ZXJfTmFtZXMsQ291bnQpLHk9Q291bnQsZmlsbD1DbHVzdGVyX05hbWVzKSkgKw0KICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpDQpgYGANCg0KDQogRGUgYWN1ZXJkbyBhIGxhIGNsYXNpZmljYWNpw7NuIGRlIGNsdXN0ZXJzLCBsYSBncsOhZmljYSBhbnRlcmlvciBub3MgbXVlc3RyYSBlbCBpbXBhY3RvIHkgY29udGVvIGRlIGNhZGEgY2x1c3RlciBkZXBlbmRpZW5kbyBkZWwgZ3J1cG8gZGUgYmFqYSBhbmFsaXphZG8sIGVzIGRlY2lyLCB2ZW1vcyBsYSByZWxhY2nDs24gZW50cmUgZMOtYXMgdHJhYmFqYWRvcyB5IHNhbGFyaW8gZGlhcmlvLCBjbGFzaWZpY2FuZG8gZWwgY29udGVvIGFjb3JkZSBhIGxhIGNhbnRpZGFkIGRlIGJhamFzIG9idGVuaWRhcy4gRW4gZXN0ZSBjYXNvLCBlbiBsYSBncsOhZmljYSBzZSBtdWVzdHJhIHF1ZSBhcXVlbGxvcyBxdWUgdHJhYmFqYXJvbiBtw6FzIGRlIDYwMCBkw61hcyB5IG1lbm9zIGRlIDE5MDAgZMOtYXMsIG9idHV2aWVyb24gdW4gc2FsYXJpbyBwcm9tZWRpbyBkZSAkNTAwIHBlc29zIGRpYXJpby4gRW50cmEgZW4gbGEgY2F0ZWdvcsOtYSBkZSAnYmFqbycsIHlhIHF1ZSBzw7NsbyB1bmEgcGVyc29uYSBvYnR1dm8gZXN0YSBkZXNjcmlwY2nDs24uIEVsIHByb21lZGlvIGRlIGxhcyBwZXJzb25hcyBkYWRhcyBkZSBiYWphIHRyYWJhamFyb24gbcOhcyBkZSAxOTAwIGTDrWFzIHkgb2J0dXZpZXJvbiB1biBzYWxhcmlvIGRpYXJpbyBkZSAkMTcwIHBlc29zLiBFbiBsYSBiYXJyYSBkZSAnYWx0bycgdmVtb3MgYSBhcXVlbGxvcyBleC1lbXBsZWFkb3MgcXVlIHRyYWJhamFyb24gNDIxIGTDrWFzIHkgdHV2aWVyb24gdW4gc2FsYXJpbyBkaWFyaW8gZGUgJDE1MCBwZXNvcy4gUG9yIMO6bHRpbW8sIHZlbW9zIGEgYXF1ZWxsb3MgJ2FycmliYSBkZWwgcHJvbWVkaW8nIHkgc29uIGFxdWVsbG9zIHF1ZSB0YWJhamFyb24gdW4gcHJvbWVkaW8gZGUgNDU1IGTDrWFzIHkgdHV2aWVyb24gdW4gc2FsYXJpbyBkaWFyaW8gZGUgJDE4MCBwZXNvcy4gRXN0ZSDDumx0aW1vIGdydXBvIGVzIGVsIHF1ZSB0dXZvIG1heW9yIGNvbnRlbyBkZSBwZXJzb25hcywgZXMgZGVjaXIsIGxhIG1heW9yw61hIGRlIGxhcyBiYWphcyBlc3R1dmllcm9uIGxhYm9yYW5kbyBtw6FzIGRlIHVuIGHDsW8gZW4gRk9STSB5IHRlbsOtYW4gdW4gc2FsYXJpbyBkaWFyaW8gZGUgJDE4MCBwZXNvcy4NCg0KIyMgVmVyIFJhbmdvcyANCiMjIyBSYW5nbyBkZSAiRMOtYXMgdHJhYmFqYWRvcyIgcG9yIG5vbWJyZSANCmBgYHtyfQ0KZ2dwbG90KHJoNCwgYWVzKHg9Q2x1c3Rlcl9OYW1lcyx5PURJQVMuVFJBQkFKQURPUyxmaWxsPSBDbHVzdGVyX05hbWVzLGxhYmVsPXJvdW5kKERJQVMuVFJBQkFKQURPUyxkaWdpdHM9MikpKSArIA0KICBnZW9tX2NvbCgpICsgDQogIGdlb21fdGV4dCgpDQoNCmBgYA0KDQoNCiBMYSBncsOhZmljYSBhbnRlcmlvciBub3MgcHJlc2VudGEgbGEgY2FudGlkYWQgZGUgZMOtYXMgdHJhYmFqYWRvcyBkZSBhY3VlcmRvIGEgbGEgY2xhc2lmaWNhY2nDs24gZXhwbGljYWRhIGFudGVyaW9ybWVudGUgZGUgJ2Jham8nLCAncHJvbWVkaW8nLCAnYXJyaWJhIGRlbCBwcm9tZWRpbycgeSAnYWx0bycuIENvbiBlc3RvIGVuIG1lbnRlLCB2ZW1vcyBxdWUgZWwgcHJvbWVkaW8gZGUgbG9zIGVtcGxlYWRvcyBqdW50YXJvbiB1biB0b3RhbCBkZSAxOTY2IGTDrWFzIHRyYWJhamFkb3MsIHNpZ3Vpw6luZG9sZSBlbCBncnVwbyBkZSAnYmFqbycsIGxvcyBxdWUgbGFib3Jhcm9uIHVuIHByb21lZGlvIGRlIDYyOCBkw61hcywgbHVlZ28gJ2FycmliYSBkZWwgcHJvbWVkaW8nIHF1ZSBsYWJvcsOzIDQ1NSBkw61hcyB5IGZpbmFsbWVudGUgJ2FsdG8nLCBxdWUgw7puaWNhbWVudGUgbGFib3LDsyA0MjEgZMOtYXMuIEVuIHRvZG9zIGxvcyBncnVwb3MsIHZlbW9zIHF1ZSBsb3MgZW1wbGVhZG9zIHRyYWJhamFyb24gcG9yIG3DoXMgZGUgMSBhw7FvIGVuIEZPUk0geSBnYW5hYmFuIHVuIHNhbGFyaW8gZGlhcmlvIG1heW9yIGEgJDE1MCBwZXNvcy4NCiANCg0KIyMjIFZlciBlbCByYW5nbyBkZSBTYWxhcmlvIERpYXJpbyAgDQpgYGB7cn0NCmdncGxvdChyaDQsYWVzKHg9Q2x1c3Rlcl9OYW1lcyx5PVNBTEFSSU8uRElBUklPLklNU1MsZmlsbD0gQ2x1c3Rlcl9OYW1lcyxsYWJlbD1yb3VuZChTQUxBUklPLkRJQVJJTy5JTVNTLGRpZ2l0cz0yKSkpICsgDQogIGdlb21fY29sKCkgKyANCiAgZ2VvbV90ZXh0KCkNCmBgYA0KDQoNCg0KTGEgZ3LDoWZpY2EgYW50ZXJpb3Igbm9zIHByZXNlbnRhIGxhIHJlbGFjacOzbiBlbnRyZSBlbCBzYWxhcmlvIGRpYXJpbyBJTVNTIHkgbGEgY2xhc2lmaWNhY2nDs24gZXhwbGljYWRhIGFudGVyaW9ybWVudGUgZGUgJ2Jham8nLCAncHJvbWVkaW8nLCAnYXJyaWJhIGRlbCBwcm9tZWRpbycgeSAnYWx0bycuIENvbiBlc3RvIGVuIG1lbnRlLCB2ZW1vcyBxdWUgZWwgc2VnbWVudG8gJ2Jham8nIGVzIGVsIHF1ZSB0ZW7DrWEgdW4gc2FsYXJpbyBtw6FzIGFsdG8gcXVlIGxhIG1heW9yw61hLiBFbCBwcm9tZWRpbyBnYW5hYmEgdW4gc2FsYXJpbyBkaWFyaW8gZGUgJDE3MCw3OSBwZXNvcywgJ2FycmliYSBkZWwgcHJvbWVkaW8nIGdhbmFiYSAkMTgwLDU0IHBlc29zIHkgZWwgc2VnbWVudG8gJ2FsdG8nIGdhYmFuYSB1biBwcm9tZWRpbyBkZSAkMTUxLDYxIHBlc29zIGRlIHNhbGFyaW8gZGlhcmlvLiBFc3RvIGRlbm90YSB1bmEgdmFyaWFiaWxpZGFkIGFsdGEgeSBwb2RlbW9zIHN1cG9uZXIgcXVlIGVzdG8gc2UgZGViw61hIGRlIGFjdWVyZG8gYSBsYXMgcmVzcG9uc2FiaWxpZGFkZXMgeSBwdWVzdG9zIGFuYWxpemFkb3MuDQoNCg0KIyMgRGlzcGVyc2lvbiBkZSBEaWFzIFRyYWJhamFkb3MNCmBgYHtyfQ0KZ2dwbG90KHJoMiwgYWVzKHg9Q2x1c3Rlcl9OYW1lcywgeT1ESUFTLlRSQUJBSkFET1MsIGZpbGw9Q2x1c3Rlcl9OYW1lcykpICsNCiAgZ2VvbV9ib3hwbG90KCkrDQogIGdndGl0bGUoIkRpc3BlcnNpb24gb2YgJ0TDrWFzIHRyYWJhamFkb3MnIGJ5IENsdXN0ZXJzIE5hbWVzIikNCmBgYA0KDQpMYSBncsOhZmljYSBhbnRlcmlvciBleHBsaWNhIGxhIGRpc3BlcnNpw7NuIGRlIGTDrWFzIHRyYWJhamFkb3MgZGUgYWN1ZXJkbyBhIGxhIGNsYXNpZmljYWNpw7NuIGV4cGxpY2FkYSBhbnRlcmlvcm1lbnRlIGRlICdiYWpvJywgJ3Byb21lZGlvJywgJ2FycmliYSBkZWwgcHJvbWVkaW8nIHkgJ2FsdG8nLiBDb24gZXN0byBlbiBtZW50ZSwgdmVtb3MgcXVlIGVsIGdydXBvIGNvbiBtYXlvciBkaXNwZXJzacOzbiBlcyAncHJvbWVkaW8nLCBwdWVzIGhheSB1bmEgdmFyaWFiaWxpZGFkIG3DoXMgc2lnbmlmaWNhdGl2YSBlbiBsb3MgZGF0b3MgZGUgbGEgY2FudGlkYWQgZGUgZMOtYXMgdHJhYmFqYWRvcywgZGVzdGFjYW5kbyBsYSBtZWRpYSBkZSBkw61hcyB0cmFiYWphZG9zLCBlcyBkZWNpciwgMTAwMCBkw61hcy4gRW4gZWwgY2FzbyBkZSAnYmFqbycsIGhheSB1bmEgZGlzcGVyc2nDs24gYmFqYSB5IHBvY28gdmFyaWFibGUuIFBhcmEgJ2FycmliYSBkZWwgcHJvbWVkaW8nLCB2ZW1vcyBxdWUgbG9zIHB1bnRvcyBhdMOtcGljb3Mgc2Ugc2FsZW4gZGVsIGJveHBsb3QgZXNwZWPDrWZpY28sIGxvIGN1YWwgcXVpdGFybG9zIG5vcyBwdWVkZSBkYXIgbWF5b3IgY2xhcmlkYWQgeSB2aXNpYmlsaWRhZCBkZSBsYSBpbmZvcm1hY2nDs24uIFBhcmEgJ2FsdG8nIHNlIG11ZXN0cmEgdW5hIGRpc3BlcnNpw7NuIG1lbm9yIGEgJ3Byb21lZGlvJyB5IGFsIGlndWFsIHF1ZSAnYXJyaWJhIGRlbCBwcm9tZWRpbycsIHRpZW5lIGFwYXJpY2nDs24gZGUgcHVudG9zIGF0w61waWNvcy4NCg0KDQojIyBEaXNwZXJzaW9uIGRlIFNhbGFyaW8gZGlhcmlvDQpgYGB7cn0NCmdncGxvdChyaDIsIGFlcyh4PUNsdXN0ZXJfTmFtZXMsIHk9U0FMQVJJTy5ESUFSSU8uSU1TUywgZmlsbD1DbHVzdGVyX05hbWVzKSkgKw0KICBnZW9tX2JveHBsb3QoKSsNCiAgZ2d0aXRsZSgiRGlzcGVyc2lvbiBvZiAnU2FsYXJpb19EaWFyaW8nIGJ5IENsdXN0ZXJzIE5hbWVzIikNCmBgYA0KDQoNCkxhIGdyw6FmaWNhIGFudGVyaW9yIGV4cGxpY2EgbGEgZGlzcGVyc2nDs24gZGUgc2FsYXJpbyBkaWFyaW8gZGUgYWN1ZXJkbyBhIGxhIGNsYXNpZmljYWNpw7NuIGV4cGxpY2FkYSBhbnRlcmlvcm1lbnRlIGRlICdiYWpvJywgJ3Byb21lZGlvJywgJ2FycmliYSBkZWwgcHJvbWVkaW8nIHkgJ2FsdG8nLiAnQmFqbycgbXVlc3RyYSB1biBzYWxhcmlvIGRpYXJpbyBkZSAkNTAwIHBlc29zLCAncHJvbWVkaW8nIG11ZXN0cmEgdW4gc2FsYXJpbyBkZSAkMTgwIHBlc29zIGFwcm94IHkgcHVudG9zIGF0w61waWNvcyBxdWUgcG9kcsOtYW4gaW5zaW51YXIgdW4gc2FsYXJpbyBtZW5vciwgJ2FycmliYSBkZWwgcHJvbWVkaW8nIHRhbWJpw6luIG11ZXN0cmEgcHVudG9zIGF0w61waWNvcy4gU2luIGVtYmFyZ28sIHRvZG9zIHNlIG1hbnRpZW5lbiBhbCBpZ3VhbCBxdWUgZWwgZGF0byBhbnRlcmlvciBlbnRyZSAkMTcwIHkgJDE4MCBwZXNvcyBkZSBzYWxhcmlvIGRpYXJpbywgRmluYWxtZW50ZSwgJ2FsdG8nIG11ZXN0cmEgdW4gc2FsYXJpbyBkaWFyaW8gYWJham8gZGVsIHByb21lZGlvIGRlIGFwcm94LiAkMTQwIHBlc29zLg0KDQoNCiMgQ2x1c3RlciAyDQojIyBJbXBvcnRhciBCYXNlIGRlIERhdG9zDQpmaWxlLmNob29zZSgpDQpgYGB7cn0NCnJoQ2x1c3RlcjI8LXJlYWQuY3N2KCJDOlxcVXNlcnNcXGRhbnljXFxEb3dubG9hZHNcXEhSX0JhamFzIDIuY3N2IikgDQpzdW1tYXJ5KHJoQ2x1c3RlcjIpDQpzdHIocmhDbHVzdGVyMikNCmBgYA0KDQoNCiMjIEV4cGxvcmFyIEtNZWFucyBlbiBlZGFkIHBvciBhw7Fvcw0KYGBge3J9DQpodW1hbm9zNTwtcmhDbHVzdGVyMiAlPiUgc2VsZWN0KEVEQUQsRElBUy5UUkFCQUpBRE9TLFNBTEFSSU8uRElBUklPLklNU1MpDQpzdW1tYXJ5KGh1bWFub3M1KQ0KYGBgDQogDQoNCg0KIyMgTm9ybWFsaXphciBsb3MgZGF0b3MNCmBgYHtyfQ0KcmhDbHVzdGVyX25vcm08LXNjYWxlKGh1bWFub3M1WzE6Ml0pIA0KYGBgDQoNCg0KIyMgU2FjYXIgbnVtZXJvIGRlIGNsdXN0ZXJzDQogZnZpel9uYmNsdXN0KCkgaGVscHMgdG8gZGV0ZXJtaW5lIGFuZCB2aXN1YWxpemUgdGhlIG9wdGltYWwgbnVtYmVyIG9mIGNsdXN0ZXJzDQpgYGB7cn0NCmZ2aXpfbmJjbHVzdChyaENsdXN0ZXJfbm9ybSwga21lYW5zLCBtZXRob2Q9IndzcyIpKyAjIHdzcyBtZXRob2QgY29uc2lkZXJzIHRvdGFsIHdpdGhpbiBzdW0gb2Ygc3F1YXJlDQogIGdlb21fdmxpbmUoeGludGVyY2VwdD00LCBsaW5ldHlwZT0yKSsgICAgICAgICAgICMgb3B0aW1hbCBudW1iZXIgb2YgY2x1c3RlcnMgaXMgY29tcHV0ZWQgd2l0aCB0aGUgZGVmYXVsdCBtZXRob2QgPSAiZXVjbGlkZWFuIg0KICBsYWJzKHN1YnRpdGxlID0gIkVsYm93IG1ldGhvZCIpDQoNCmBgYA0KDQogICAgICAgICAgICAgIA0KDQojIyMgVmVyIGRhdG9zIGRlIGxvcyBjbHVzdGVycyANCg0KYGBge3J9DQpyaF9jbHVzdGVyMjwta21lYW5zKHJoMV9ub3JtLDQpDQpyaF9jbHVzdGVyMg0KYGBgDQoNCiMjIyBWaXN1YWxpemFyIGNsdXN0ZXJpbmcgDQpgYGB7cn0NCmZ2aXpfY2x1c3RlcihyaF9jbHVzdGVyMixkYXRhPXJoQ2x1c3Rlcl9ub3JtKQ0KYGBgDQoNCg0KDQpMYSBncsOhZmljYSBhbnRlcmlvciBtdWVzdHJhIDQgY2x1c3RlcnMgcXVlIGFuYWxpemFuIGxhIHJlbGFjacOzbiBlbnRyZSBlZGFkIHkgZMOtYXMgdHJhYmFqYWRvcy4gRWwgY2x1c3RlciB2ZXJkZSBtdWVzdHJhIHF1ZSB1biBhbHRvIG7Dum1lcm8gZGUgcGVyc29uYXMgdGFiYWphYmEgbcOhcyBkw61hcyBxdWUgZWwgcHJvbWVkaW8geSB0ZW7DrWEgdW5hIGVkYWQgc2ltaWxhciBhbCBwcm9tZWRpby4gRWwgcHVudG8gbmFyYW5qYSBtdWVzdHJhIHF1ZSBwb2NvcyB1c3VhcmlvcyB0cmFiYWphYmFuIG3DoXMgZMOtYXMgcXVlIGVsIHByb21lZGlvIHkgYWwgaWd1YWwgcXVlIGVsIHB1bnRvIGFudGVyaW9yLCB0ZW7DrWFuIHVuYSBlZGFkIHByb21lZGlvLiBFbiBlbCBjYXNvIGRlbCBwdW50byBhenVsIHkgZWwgbW9yYWRvLCBhbWJvcyBtdWVzdHJhbiBxdWUgdW4gbWF5b3IgZ3J1cG8gZGUgcGVyc29uYXMgbGFib3JhYmEgJzIgZMOtYXMnLCBwZXJvIHRlbsOtYSBtYXlvciB2YXJpYWJpbGlkYWQgZW4gbGEgZWRhZC4NCg0KIyMjIEFncmVnYXIgbGEgaW5mbyBhbCBkYXRhIHNldCBvcmlnaW5hbA0KYGBge3J9DQpodW1hbm9zNjwtaHVtYW5vczUNCmh1bWFub3M2JENsdXN0ZXJzPC1yaF9jbHVzdGVyMiRjbHVzdGVyDQpzdW1tYXJ5KGh1bWFub3M2KQ0KYGBgDQoNCg0KIyMjIENyZWFyIERhdGFzZXQNCmxldHMgY3JlYXRlIGEgZGF0YXNldCBzbyB3ZSBjYW4gaWRlbnRpZnkgc29tZSBjaGFyYWN0ZXJpc3RpY3Mgb2YgIkVkYWQiIGJ5IGNsdXN0ZXIgDQpgYGB7cn0NCmh1bWFub3M3PC1odW1hbm9zNiAlPiUgZ3JvdXBfYnkoQ2x1c3RlcnMpICU+JSBzdW1tYXJpc2UoRURBRD1tYXgoRURBRCksRElBUy5UUkFCQUpBRE9TPW1lYW4oRElBUy5UUkFCQUpBRE9TKSkgJT4lIGFycmFuZ2UoZGVzYyhFREFEKSkNCnN1bW1hcnkoaHVtYW5vczcpDQpgYGANCg0KDQojIyMgQWdydXBhciBjbHVzdGVycyBwb3Igbm9tYnJlDQpgYGB7cn0NCmh1bWFub3M2JENsdXN0ZXJfTmFtZXM8LWZhY3RvcihodW1hbm9zNiRDbHVzdGVycyxsZXZlbHMgPSBjKDEsMiwzLDQpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzPWMoIkpvdmVuIiwgIkF2YW56YWRvICIsICIgQWR1bHRvIiwgIkp1YmlsYWRvIikpDQpzdW1tYXJ5KGh1bWFub3M2KQ0KYGBgDQoNCg0KRW50cmUgbcOhcyBqb3ZlbiBtw6FzIGTDrWFzIHRyYWJhamFkb3MNCg0KDQojIyMgQWdydXBhciBwb3IgY2x1c3RlcnMgeSByZXN1bWlyIGNvbHVtbmFzDQpgYGB7cn0NCmh1bWFub3M4IDwtIGh1bWFub3M2JT4lIGdyb3VwX2J5KENsdXN0ZXJfTmFtZXMpICU+JSBzdW1tYXJpemUoRURBRD1tYXgoRURBRCksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIERJQVMuVFJBQkFKQURPUyA9bWVhbihESUFTLlRSQUJBSkFET1MpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQ291bnQ9bigpKQ0KDQpgYGANCg0KIyMjIENvbnZlcnRpciBhIHRhYmxhDQpgYGB7cn0NCmNsdXN0ZXJzMjwtYXMuZGF0YS5mcmFtZShodW1hbm9zOCkNCmNsdXN0ZXJzMg0KYGBgDQoNCiMjIFZpc3VhbGl6YXIgZ3JhZmljb3MgDQoNCiMjIyAgVmVyIGxvcyBkaWFzIHRyYWJhamFkb3MgZW4gZ2VuZXJhbCBqdW50byBjb24gbGEgZWRhZA0KYGBge3J9DQpnZ3Bsb3QoaHVtYW5vczgsYWVzKHg9cmVvcmRlcihDbHVzdGVyX05hbWVzLENvdW50KSx5PUNvdW50LGZpbGw9Q2x1c3Rlcl9OYW1lcykpICsNCiAgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiKQ0KYGBgDQoNCg0KTGEgZ3LDoWZpY2EgYW50ZXJpb3Igbm9zIG11ZXN0cmEgbGEgY2FudGlkYWQgZGUgZMOtYXMgdHJhYmFqYWRvcyBkZSBhY3VlcmRvIGEgbGEgZWRhZCBkZSBsb3MgZW1wbGVhZG9zLiBFbiBlc3RlIGNhc28sIHZlbW9zIHF1ZSBlbCBncnVwbyBkZSBlZGFkICdqb3ZlbicgdGVuw61hbiBhcHJveC4gMzEgYcOxb3MgeSByZXByZXNlbnRhIHVuIGJham8gcG9yY2VudGFqZSBkZSBsb3MgZW1wbGVhZG9zLiBQYXJhICdhdmFuemFkbycgc29uIGxvcyBlbXBsZWFkb3MgcXVlIHRpZW5lbiBhcnJpYmEgZGUgMzIgYcOxb3MgeSBsYWJvcmFyb24gbGEgbWF5b3IgY2FudGlkYWQgZGUgZMOtYXMgKDEwNjcpLiBQYXJhICdqdWJpbGFkbycsIHNvbiBsYXMgcGVyc29uYXMgY29uIHVuIGFwcm94LiBkZSA1MCBhw7FvcyB5IHF1ZSBsYWJvcmFyb24gZHVyYW50ZSAxMDIgZMOtYXMuIEZpbmFsbWVudGUsIHBhcmEgZWwgZ3J1cG8gZGUgJ2FkdWx0bycgKGVsIG3DoXMgZWxldmFkbyksIHNvbiBhcXVlbGxvcyBxdWUgbGFib3Jhcm9uIHVuIHByb21lZGlvIGRlIDQ0IGTDrWFzIHkgdGllbmVuIGxhIGVkYWQgZGUgNTIgYcOxb3MuDQoNCiMjIyBEaWFzIHRyYWJhamFkb3MgcG9yIG5vbWJyZSBkZSBjbHVzdGVycw0KYGBge3J9DQpnZ3Bsb3QoaHVtYW5vczgsIGFlcyh4PUNsdXN0ZXJfTmFtZXMseT1ESUFTLlRSQUJBSkFET1MsZmlsbD0gQ2x1c3Rlcl9OYW1lcyxsYWJlbD1yb3VuZChESUFTLlRSQUJBSkFET1MsZGlnaXRzPTIpKSkgKyANCiAgZ2VvbV9jb2woKSArIA0KICBnZW9tX3RleHQoKQ0KYGBgDQoNCg0KTGEgZ3LDoWZpY2EgYW50ZXJpb3Igbm9zIG11ZXN0cmEgbGEgY2FudGlkYWQgZGUgZMOtYXMgdHJhYmFqYWRvcyBkZSBhY3VlcmRvIGEgbG9zIGTDrWFzIHRyYWJhamFkb3MuIEVuIGVzdGUgY2FzbywgZXN0byBub3MgbXVlc3RyYSBxdWUgZWwgZ3J1cG8gY29uIG1heW9yIGJhamFzIGZ1ZXJvbiBkZWwgc2VnbWVudG8gJ2FkdWx0bycgeSBlcyBlbCBxdWUgbGFib3LDsyBlbiBwcm9tZWRpbyB1biBtZW5vciBuw7ptZXJvIGRlIGTDrWFzIGNvbnRyYSAnYXZhbnphZG8nIHF1ZSBlcyBlbCBzZWd1bmRvIGdydXBvIG3DoXMgYmFqbyB5IGxhYm9yw7MgZWwgbWF5b3IgbsO6bWVybyBkZSBkw61hcyBlbiBwcm9tZWRpby4NCg0KIyMjIEVkYWQgcG9yIG5vbWJyZSBkZSBjbHVzdGVycyANCmBgYHtyfQ0KZ2dwbG90KGh1bWFub3M4LGFlcyh4PUNsdXN0ZXJfTmFtZXMseT1FREFELGZpbGw9IENsdXN0ZXJfTmFtZXMsbGFiZWw9cm91bmQoRURBRCxkaWdpdHM9MikpKSArIA0KICBnZW9tX2NvbCgpICsgDQogIGdlb21fdGV4dCgpDQpgYGANCg0KDQogTGEgZ3LDoWZpY2EgYW50ZXJpb3Igbm9zIG11ZXN0cmEgbGEgZWRhZCBwcm9tZWRpbyBkZSBjYWRhIHVubyBkZSBsb3MgZ3J1cG9zLiBDb21vIGV4cGxpY2Ftb3MgYW50ZXJpb3JtZW50ZSwgc2UgbXVlc3RyYSBxdWUgJ2FkdWx0bycgZXMgZWwgZGUgZW1wbGVhZG9zIGNvbiBtYXlvciBlZGFkIHkgJ2pvdmVuJyBlcyBlbCBkZSBwZXJzb25hcyBkZSBtZW5vciBlZGFkLg0KDQojIyMgRGlhcyB0cmFiYWphZG9zIHBvciBub21icmUgZGUgY2x1c3RlcnMNCmBgYHtyfQ0KZ2dwbG90KGh1bWFub3M2LCBhZXMoeD1DbHVzdGVyX05hbWVzLCB5PUVEQUQsIGZpbGw9Q2x1c3Rlcl9OYW1lcykpICsNCiAgZ2VvbV9ib3hwbG90KCkrDQogIGdndGl0bGUoIkRpc3BlcnNpb24gb2YgJ0VkYWQnIGJ5IENsdXN0ZXJzIE5hbWVzIikNCmBgYA0KDQogTGEgZ3LDoWZpY2EgYW50ZXJpb3Igbm9zIG11ZXN0cmEgbGEgZGlzcGVyc2nDs24gZGUgYWN1ZXJkbyBhIGxhIGVkYWQgZGUgbG9zIGVtcGxlYWRvcy4gRW4gZWwgY2FzbyBkZSAnYWR1bHRvJywgdGllbmUgdW5hIGdyYW4gY2FudGlkYWQgZGUgcHVudG9zIGF0w61waWNvcyBsbyBjdWFsIGluZGljYSBxdWUgaGF5IGRhdG9zIGZ1ZXJhIGRlIHNlcmllLiBFbiBlbCBjYXNvIGRlICdqdWJpbGFkbycsIHZlbW9zIHF1ZSBlcyBlbCBncnVwbyBjb24gbWF5b3IgZGlzcGVyc2nDs24sIG1vc3RyYW5kbyBkYXRvcyBkZSBlZGFkIGVudHJlIDI1IHkgMzIgYcOxb3MuDQogDQogDQoNCiMjIyBTYWxhcmlvIGRpYXJpbyBwb3Igbm9tYnJlIGRlIGNsdXN0ZXJzDQpgYGB7cn0NCmdncGxvdChodW1hbm9zNiwgYWVzKHg9Q2x1c3Rlcl9OYW1lcywgeT1ESUFTLlRSQUJBSkFET1MsIGZpbGw9Q2x1c3Rlcl9OYW1lcykpICsNCiAgZ2VvbV9ib3hwbG90KCkrDQogIGdndGl0bGUoIkRpc3BlcnNpb24gb2YgJ0RpYXNfVHJhYmFqYWRvcycgYnkgQ2x1c3RlcnMgTmFtZXMiKQ0KYGBgDQoNCg0KDQogTGEgZ3LDoWZpY2EgYW50ZXJpb3IgbXVlc3RyYSBsYSBkaXNwZXJzacOzbiBkZSBhY3VlcmRvIGEgZMOtYXMgdHJhYmFqYWRvcyB5IGxvcyBncnVwb3MgYW50ZXJpb3IgZXhwbGljYWRvcy4gVmVtb3MgcXVlICdhdmFuemFkbycgZXMgZWwgZ3VycG8gY29uIG1heW9yIGRpc3BlcnNpw7NuLCBtb3N0cmFuZG8gcXVlIGVsIHByb21lZGlvIGRlIGxvcyBlbXBsZWFkb3MgbGFib3Jhcm9uIGVudHJlIDU1MCB5IDEzMDAgZMOtYXMuIEVzIGVsIGdydXBvIGNvbiBtYXlvciBuw7ptZXJvIGRlIGVtcGxlYWRvcyBxdWUgdHJhYmFqYXJvbiBlbiBlc3RlIHBlcmlvZG8gZGUgdGllbXBvLiBFbiBlbCBjYXNvIGRlICdhZHVsdG8nIHkgJ2p1YmlsYWRvJywgc29uIGFxdWVsbG9zIGdydXBvcyBxdWUgbXVlc3RyYW4gdW5hIGRpc3BlcnNpw7NuIGJhamEgeSB1bmEgdmFyaWVkYWQgZGUgcHVudG9zIGF0w61waWNvcywgbWllbnRyYXMgcXVlICdqb3ZlbicgZGVzdGFjYSB1bmEgYmFqYSBkaXNwZXJzacOzbiB5IHVuYSBtZWRpYSBkZSBtw6FzIGRlIDUwMCBkw61hcyB0cmFiYWphZG9zLg0KIA0KIyBDbHVzdGVyIDMNCg0KIyMgSW1wb3J0YXIgbGEgYmFzZSBkZSBkYXRvcw0KYGBge3J9DQpjbHVzdGVyMzwtcmVhZC5jc3YoIkM6XFxVc2Vyc1xcZGFueWNcXERvd25sb2Fkc1xcSFJfQmFqYXMgMi5jc3YiKSANCnN1bW1hcnkoY2x1c3RlcjMpDQpzdHIoY2x1c3RlcjMpDQpgYGANCg0KIyMgTnVtZXJvIGRlIGNsdXN0ZXJzDQoNCiMjIyBKdW50YXIgbG9zIGRhdG9zIHJlbGFjaW9uYWRvcyBhIGVkYWQgZW4gYcOxb3MNCmBgYHtyfQ0KbmV3YmQ8LWNsdXN0ZXIzICU+JSBzZWxlY3QoRURBRCxTQUxBUklPLkRJQVJJTy5JTVNTKQ0Kc3VtbWFyeShuZXdiZCkNCmBgYA0KDQojIyMgTm9ybWFsaXphciBkYXRvcw0KYGBge3J9DQpuZXdiZG5vcm08LXNjYWxlKG5ld2JkWzI6MV0pDQpgYGANCg0KIyMjIEdyYWZpY2EgcGFyYSB2ZXIgbnVtZXJvIG9wdGltbyBkZSBjbHVzdGVycw0KYGBge3J9DQpmdml6X25iY2x1c3QobmV3YmRub3JtLCBrbWVhbnMsIG1ldGhvZD0id3NzIikrICMgd3NzIG1ldGhvZCBjb25zaWRlcnMgdG90YWwgd2l0aGluIHN1bSBvZiBzcXVhcmUNCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PTQsIGxpbmV0eXBlPTIpKyAgICAgICAgICAgIyBvcHRpbWFsIG51bWJlciBvZiBjbHVzdGVycyBpcyBjb21wdXRlZCB3aXRoIHRoZSBkZWZhdWx0IG1ldGhvZCA9ICJldWNsaWRlYW4iDQogIGxhYnMoc3VidGl0bGUgPSAiRWxib3cgbWV0aG9kIikNCmBgYA0KDQojIyMgVmlzdWFsaXphciBpbmZvcm1hY2lvbiANCmBgYHtyfQ0KbmV3YmQyY2x1czwta21lYW5zKG5ld2Jkbm9ybSw0KQ0KbmV3YmQyY2x1cw0KYGBgDQoNCiMjIFZlciByZXN1bHRhZG9zIGRlIGNsdXN0ZXJzDQpgYGB7cn0NCmZ2aXpfY2x1c3RlcihuZXdiZDJjbHVzLGRhdGE9bmV3YmRub3JtKQ0KYGBgDQoNCiMjIyBBZ3JlZ2FyIGxvcyByZXN1bHRhZG9zIGFsIGRhdGFzZXQgb3JpZ2luYWwgcGFyYSBpbnRlcnByZXRhY2lvbg0KYGBge3J9DQpuZXdiZDM8LW5ld2JkIA0KbmV3YmQzJENsdXN0ZXJzPC1uZXdiZDJjbHVzJGNsdXN0ZXINCnN1bW1hcnkobmV3YmQzKQ0KYGBgDQoNCiMjIyBDcmVhciBkYXRhc2V0cyBwYXJhIHZlciBsYSBpbmZvIGRlIGNhcmFjdGVyaXN0aWNhcyBwb3IgZWRhZCBwb3IgY2x1c3RlcnMNCmBgYHtyfQ0KbmV3YmQ0PC1uZXdiZDMgJT4lIGdyb3VwX2J5KENsdXN0ZXJzKSAlPiUgc3VtbWFyaXNlKEVEQUQ9bWF4KEVEQUQpLFNBTEFSSU8uRElBUklPLklNU1M9bWVhbihTQUxBUklPLkRJQVJJTy5JTVNTKSkgJT4lIGFycmFuZ2UoZGVzYyhFREFEKSkNCnN1bW1hcnkobmV3YmQ0KQ0KYGBgDQoNCiMjIyBBZ3J1cGFyIGNsdXN0ZXJzIHBvciBub21icmUNCmBgYHtyfQ0KbmV3YmQzJENsdXN0ZXJfTmFtZXM8LWZhY3RvcihuZXdiZDMkQ2x1c3RlcnMsbGV2ZWxzID0gYygxLDIsMyw0KSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVscz1jKCJKb3ZlbiIsICJBdmFuemFkbyAiLCAiIEFkdWx0byIsICJKdWJpbGFkbyIpKQ0Kc3VtbWFyeShuZXdiZDMpDQpgYGANCg0KIyMjIEFncnVwYXIgcG9yIG5vbWJyZSB5IHBvciBjb2x1bW5hDQpgYGB7cn0NCm5ld2JkNSA8LSBuZXdiZDMlPiUgZ3JvdXBfYnkoQ2x1c3Rlcl9OYW1lcykgJT4lIHN1bW1hcml6ZShFREFEPW1heChFREFEKSwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgU0FMQVJJTy5ESUFSSU8uSU1TUyA9bWVhbihTQUxBUklPLkRJQVJJTy5JTVNTKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIENvdW50PW4oKSkNCmBgYA0KDQojIyBWaXN1YWxpemFyIGdyYWZpY2FzDQoNCiMjIyBQb25lciBjb21vIHRhYmxhIGxvcyBkYXRvcw0KYGBge3J9DQpjbHVzdGVyc3NhbGFyaW88LWFzLmRhdGEuZnJhbWUobmV3YmQ1KQ0KY2x1c3RlcnNzYWxhcmlvDQpgYGANCg0KIyMjIEdyYWZpY2FyIHBvciBub21icmUNCmBgYHtyfQ0KZ2dwbG90KG5ld2JkNSxhZXMoeD1yZW9yZGVyKENsdXN0ZXJfTmFtZXMsQ291bnQpLHk9Q291bnQsZmlsbD1DbHVzdGVyX05hbWVzKSkgKw0KICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIpDQpgYGANCg0KIyMjIFNhbGFyaW8gZGlhcmlvIHBvciBudW1lcm8gZGUgY2x1c3RlcnMNCmBgYHtyfQ0KZ2dwbG90KG5ld2JkNSwgYWVzKHg9Q2x1c3Rlcl9OYW1lcyx5PVNBTEFSSU8uRElBUklPLklNU1MsZmlsbD0gQ2x1c3Rlcl9OYW1lcyxsYWJlbD1yb3VuZChTQUxBUklPLkRJQVJJTy5JTVNTLGRpZ2l0cz0yKSkpICsgDQogIGdlb21fY29sKCkgKyANCiAgZ2VvbV90ZXh0KCkNCmBgYA0KDQpBcXVpIG5vcyBwb2RlbW9zIGRhciBjdWVudGEgYWNlcmNhIGRlIHF1ZSBsb3Mgc3VlbGRvcyBubyB2YXJpYW4gbXVjaG8gZW50cmUgZWRhZGVzIHBlcm8gZXMgaW1wb3J0YW50ZSBkZWNpciBxdWUgc2UgZGVub3RhIHBvciBjZW50YXZvcyB1bmEgbWF5b3JpYSBlbiBlbCBzdWVsZG8gZGUgbG9zIGpvdmVuZXMgZXNwZWNpZmljYW1lbnRlIG3DoXMgcXVlIGVuIG90cm9zIHJhbmdvcyBkZSBlZGFkZXMuDQoNCiMgSGFsbGF6Z29zDQpbIDEgXSBFbCBzYWxhcmlvIGRpYXJpbyBwcm9tZWRpbyBkZSBGT1JNIGVzIGRlICQxODAgcGVzb3MgbWV4aWNhbm9zLCBlcyBkZWNpciwgJDUsNDAwIHBlc29zIG1lbnN1YWxlcy4gRGUgYWN1ZXJkbyBhIGRhdG9zIGNvbXBhcnRpZG9zIHBvciBsYSBJTkVHSSwgc2UgcHJldsOpIHF1ZSBwYXJhIDIwMjIgZWwgc2FsYXJpbyBtw61uaW1vIGVuIE3DqXhpY28gc2VhIGRlICQ1LDI1NSBwZXNvcyBtZW5zdWFsZXMuIEVzdG8gcmVmbGVqYSBxdWUgZWwgc2FsYXJpbyBtZW5zdWFsIGRlIEZPUk0gZXMgMiUgbWF5b3IgYWwgc2FsYXJpbyBtw61uaW1vIGRlbCBwYcOtcy4NClsgMiBdIFNlIGRlc3RhY2FuIHRyZXMgcmF6b25lcyBwcmluY2lwYWxlcyBkZSBiYWphcyBkZSBlbXBsZWFkb3M6IGJhamFzIHBvciBmYWx0YXMsIHJlbnVuY2lhIHZvbHVudGFyaWEgbyB0w6lybWlubyBkZSBjb250cmF0by4gRW4gc3UgbWF5b3LDrWEsIGh1Ym8gdW5hIGJhamEgcG9yIGZhbHRhcyBkZWwgcHVlc3RvIGRlIGF5dWRhbnRlIGdlbmVyYWwgeSByZW51bmNpYSB2b2x1bnRhcmlhIHBhcmEgZWwgbWlzbW8gcHVlc3RvLg0KWyAzIF0gSHVibyB1biBhbHRvIG7Dum1lcm8gZGUgYmFqYXMgZGUgZW1wbGVhZG9zIHF1ZSB0cmFiYWphcm9uIG3DoXMgZGUgMjAwIGTDrWFzLCBlcyBkZWNpciwgbGEgbWF5b3LDrWEgZGUgbG9zIGV4LWVtcGxlYWRvcyBlc3R1dmllcm9uIGVuIEZPUk0gbcOhcyBkZSAxIGHDsW8geSB0ZW7DrWFuIHVuIHNhbGFyaW8gcHJvbWVkaW8gZGUgJDE4MCBwZXNvcy4NClsgNCBdIExvcyBzdWVsZG9zIGRlIGxvcyBlbXBsZWFkb3Mgbm8gdmFyw61hbiBtdWNobyBkZXBlbmRpZW5kbyBkZSBsYSBlZGFkLiBTaW4gZW1iYXJnbywgdmVtb3MgcXVlIGhheSBtYXlvciByb3RhY2nDs24gcGFyYSBlbCBncnVwbyAnYWR1bHRvJw0KDQojIyBSZWZlcmVuY2lhcyANClpodSwgQS4gKDIwMjIsIDEyIGFnb3N0bykuIEstTWVhbnMgQ2x1c3RlcmluZyBFeHBsYWluZWQgU2ltcGx5IC0gVG93YXJkcyBEYXRhIFNjaWVuY2UuIE1lZGl1bS4gUmVjdXBlcmFkbyAxMCBkZSBvY3R1YnJlIGRlIDIwMjIsIGRlIGh0dHBzOi8vdG93YXJkc2RhdGFzY2llbmNlLmNvbS9leHBsYWluLW1sLWluLWEtc2ltcGxlLXdheS1rLW1lYW5zLWNsdXN0ZXJpbmctZTkyNWQwMTk3NDNiDQoNCg0KR3VsemFyLCBNLiAoMjAyMiwgNiBlbmVybykuIEstTWVhbnMgQ2x1c3RlcmluZzogQ29uY2VwdHMgYW5kIEltcGxlbWVudGF0aW9uIGluIFIgZm9yIERhdGEgU2NpZW5jZS4gTWVkaXVtLiBSZWN1cGVyYWRvIDEwIGRlIG9jdHVicmUgZGUgMjAyMiwgZGUgaHR0cHM6Ly90b3dhcmRzZGF0YXNjaWVuY2UuY29tL2stbWVhbnMtY2x1c3RlcmluZy1jb25jZXB0cy1hbmQtaW1wbGVtZW50YXRpb24taW4tci1mb3ItZGF0YS1zY2llbmNlLTMyY2FlNmEzY2ViYQ0KDQpHdWx6YXIsIE0uICgyMDIyYiwgZW5lcm8gNikuIEstTWVhbnMgQ2x1c3RlcmluZzogQ29uY2VwdHMgYW5kIEltcGxlbWVudGF0aW9uIGluIFIgZm9yIERhdGEgU2NpZW5jZS4gTWVkaXVtLiBSZWN1cGVyYWRvIDEwIGRlIG9jdHVicmUgZGUgMjAyMiwgZGUgaHR0cHM6Ly90b3dhcmRzZGF0YXNjaWVuY2UuY29tL2stbWVhbnMtY2x1c3RlcmluZy1jb25jZXB0cy1hbmQtaW1wbGVtZW50YXRpb24taW4tci1mb3ItZGF0YS1zY2llbmNlLTMyY2FlNmEzY2ViYQ0K