Planeación estratégica basada en analítica prescriptiva

Grupo 503

Profesor Rodolfo Miguel Gameros

Equipo 7:

A00833113 - Avril Lobato

A01771127 - Lesly Darian Romero Vázquez

A00831105 - Jazmín del Carmen Cortez Mendoza

A01284611 - Lisset Hernández

Actividad 04. K-Medias (Distancia - Canberra y Gap_Stat)

Introducción

El Análisis de Clusters mediante el Algoritmo de K-Medias es un algoritmo de agrupamiento no supervisado que divide un conjunto de datos en K grupos o clústeres. Su objetivo es minimizar la varianza intra-clúster (la distancia entre los puntos y su centroide). Se caracteriza porque requiere especificar el número de clústeres (K),asume que los clústeres son esféricos y de similar tamaño; y comúnmente utilizala distancia Euclidiana pero se puede adaptar a otras métricas (como Canberra).

La distancia de Canberra es una medida de disimilitud entre dos vectores numéricos. Es útil cuando el análisis de datos cuenta con muchas dimensiones (features), principalmente cuando los datos contienen valores cercanos a cero, ya que penaliza más las pequeñas diferencias relativas.

Asimismo, el Estadístico de Brecha (Gap Statistic) es una técnica utilizada para determinar el número óptimo de clústeres (k) al aplicar el algoritmo K-Means. A diferencia del método del codo (Elbow Method), que se basa únicamente en la variación interna de los clústeres (Wk), el Gap Statistic compara ese valor con el esperado bajo una distribución de referencia aleatoria, es decir, sin una estructura de clústeres definida. Mientras que el método del codo identifica el k óptimo mediante un punto de inflexión en la curva de Wk, el Gap Statistic selecciona el valor de k en el que la diferencia entre el logaritmo de la dispersión observada y la esperada (log(Wk)) es máxima y presenta estabilidad. Dicho enfoque es especialmente recomendable en escenarios donde los datos contienen ruido o la estructura de los clústeres no es evidente a simple vista.

EDA

A continuación se carga la base de datos a ser analizada: ‘USArrests’ de la librería ‘spData’. El presente conjunto de datos proporciona una visión cuantitativa de la criminalidad registrada en los Estados Unidos en 1973, enfocándose en tres delitos graves: homicidio, asalto y violación. Cada una de estas categorías muestra la tasa de arrestos por cada 100,000 habitantes en los 50 estados del país. Además, se incluye una variable que representa el grado de urbanización de cada estado, expresado como el porcentaje de la población que reside en zonas urbanas. Esta información es útil para analizar la relación entre el entorno urbano y la incidencia delictiva, así como para identificar patrones geográficos y sociales relacionados con la criminalidad en ese periodo histórico.

data("USArrests")
head(USArrests)
##            Murder Assault UrbanPop Rape
## Alabama      13.2     236       58 21.2
## Alaska       10.0     263       48 44.5
## Arizona       8.1     294       80 31.0
## Arkansas      8.8     190       50 19.5
## California    9.0     276       91 40.6
## Colorado      7.9     204       78 38.7
glimpse(USArrests)
## Rows: 50
## Columns: 4
## $ Murder   <dbl> 13.2, 10.0, 8.1, 8.8, 9.0, 7.9, 3.3, 5.9, 15.4, 17.4, 5.3, 2.…
## $ Assault  <int> 236, 263, 294, 190, 276, 204, 110, 238, 335, 211, 46, 120, 24…
## $ UrbanPop <int> 58, 48, 80, 50, 91, 78, 77, 72, 80, 60, 83, 54, 83, 65, 57, 6…
## $ Rape     <dbl> 21.2, 44.5, 31.0, 19.5, 40.6, 38.7, 11.1, 15.8, 31.9, 25.8, 2…

Con base al EDA se determina que existen dos patrones claros de criminalidad entre los estados de EE.UU. representados en los clusters, donde un cluster representa estados con alta criminalidad; y el otro, estados con baja criminalidad. Lo anterior debido a que las variables Murder, Assault y Rape están negativamente correlacionadas con el número de cluster asignado (r ≈ -0.83), lo que sugiere que los clusters capturan niveles distintos de criminalidad.

Correlaciones

  • Murder y Assault: Correlación fuerte y positiva (r ≈ 0.80), indicando que los estados con mayor tasa de asesinatos tienden también a tener mayores tasas de asaltos.

  • Rape se correlaciona de forma moderada con: Assault (r ≈ 0.66) & Murder (r ≈ 0.56)

  • UrbanPop (porcentaje de población urbana) muestra baja o nula correlación con otras variables, lo que sugiere que la urbanización no se asocia directamente con las tasas de crimen reportadas en este conjunto de datos.

Distribución de variables

  • Murder y Assault están sesgadas a la derecha, indicando que la mayoría de los estados tienen tasas bajas, pero algunos tienen valores muy altos.

  • Rape también tiene cierta asimetría. Mientras que, UrbanPop tiene una distribución más uniforme.

  • Los gráficos de dispersión muestran agrupaciones claras, sobre todo entre variables altamente correlacionadas (como Murder vs. Assault).

library(ggplot2)
library(GGally)
# Histogramas 
USArrests_long <- USArrests %>%
  tidyr::pivot_longer(cols = everything(), names_to = "Variable", values_to = "Valor")

ggplot(USArrests_long, aes(x = Valor)) +
  geom_histogram(bins = 20, fill = "steelblue", color = "black") +
  facet_wrap(~ Variable, scales = "free", ncol = 2) +
  labs(title = "Histogramas de variables en USArrests") +
  theme_minimal()

# Boxplots
ggplot(USArrests_long, aes(x = Variable, y = Valor, fill = Variable)) +
  geom_boxplot() +
  labs(title = "Boxplots por variable en USArrests") +
  theme_minimal() +
  theme(legend.position = "none")

# Matriz de correlación
ggpairs(USArrests,
        lower = list(continuous = "smooth"),
        diag = list(continuous = "barDiag"),
        upper = list(continuous = "cor")) +
  ggtitle("Matriz de correlación y relaciones entre variables en USArrests")

Escala de variables

Al momento de realizar el análisis de conglomerados se estandarizan todas las variables, para evitar dar un mayor peso a una variable por tener magnitudes mayores

scaled <- USArrests %>%
  scale() %>% 
  as.data.frame()

#Verificar brevemente que en efecto nuestra nueva base de datos esté centrada en '0' y tenga desviación estándar unitaria ('1')
print("Means")
## [1] "Means"
sapply(scaled, mean) %>%  round(4)
##   Murder  Assault UrbanPop     Rape 
##        0        0        0        0
print("Standard Deviations")
## [1] "Standard Deviations"
sapply(scaled, sd)
##   Murder  Assault UrbanPop     Rape 
##        1        1        1        1
str(scaled)
## 'data.frame':    50 obs. of  4 variables:
##  $ Murder  : num  1.2426 0.5079 0.0716 0.2323 0.2783 ...
##  $ Assault : num  0.783 1.107 1.479 0.231 1.263 ...
##  $ UrbanPop: num  -0.521 -1.212 0.999 -1.074 1.759 ...
##  $ Rape    : num  -0.00342 2.4842 1.04288 -0.18492 2.06782 ...

Análisis de la viabilidad de esta base de datos a contener conglomerados

set.seed(2025)

cluster_td <- 
  get_clust_tendency(
    scaled,
    n = 25,
    gradient = list(low = "white",  
                    high = "steelblue")
    )

cluster_td$plot #Se pueden identificar dos posibles clusters.

Muestra y análisis del Estadístico de Hopkin

Se obtiene un valor de 0.61 que al estar cercano a 1, indica que los datos tienen una tendencia a formar clústeres (estructura no aleatoria).

cluster_td$hopkins_stat 
## [1] 0.6115733
# El estadistico es cercano a 1 (0.61), lo cual indica que el dataset tiene tendencia a ser   clusterizada. 

Matriz de similitudes con la función de distancia Canberra y Gap_Stat

Distancia Canberra:

  • El primer gráfico (Euclídeo) muestra similitudes pero con patrones menos claros.

  • La matriz de distancia Canberra muestra divisiones más marcadas, lo que facilita la detección de grupos.

  • Áreas blancas indican gran similitud; áreas oscuras, gran diferencia.

 fviz_dist(
     get_dist(scaled, method="canberra"),
     order = TRUE,
     show_labels = FALSE,
     lab_size = NULL,
     gradient = list(low = "white", high = "steelblue")
)

#Utilizando la distancia 'Canberra' se ven mucho más claros los dos posibles clusters. 

Número óptimo de Clusters ‘k’

Gap Statistic:

Se obtuvo que el máximo valor del gap para k = 2, lo que sugiere que 2 grupos es lo más adecuado.

#Gap_Stat
fviz_nbclust(scaled, kmeans, method = "gap_stat") # 2 o 3 clusters

Creación de ‘k’ clusters con el algoritmo K-medias

set.seed(2025)
km_cluster <- kmeans(scaled, 2, nstart = 25)

USArrests$km_cluster <- km_cluster$cluster
glimpse(USArrests)
## Rows: 50
## Columns: 5
## $ Murder     <dbl> 13.2, 10.0, 8.1, 8.8, 9.0, 7.9, 3.3, 5.9, 15.4, 17.4, 5.3, …
## $ Assault    <int> 236, 263, 294, 190, 276, 204, 110, 238, 335, 211, 46, 120, …
## $ UrbanPop   <int> 58, 48, 80, 50, 91, 78, 77, 72, 80, 60, 83, 54, 83, 65, 57,…
## $ Rape       <dbl> 21.2, 44.5, 31.0, 19.5, 40.6, 38.7, 11.1, 15.8, 31.9, 25.8,…
## $ km_cluster <int> 1, 1, 1, 2, 1, 1, 2, 2, 1, 1, 2, 2, 1, 2, 2, 2, 2, 1, 2, 1,…

Visualización de ‘k’ clusters

Representación de los clusters formados con k-means

  • Los datos se proyectan en 2 dimensiones (probablemente PCA) para visualizar la separación de grupos.

  • Se identifican dos grupos claramente diferenciados, destacados por colores y elipses que indican la dispersión dentro de cada clúster.

  • Se muestra que Missouri aparece como un punto intermedio entre ambos clústeres, lo que sugiere que comparte características tanto de estados con alta como con baja criminalidad, siendo un caso limítrofe o de transición.

library(factoextra)
library(ggplot2)
library(dplyr)

# PCA
pca_result <- prcomp(scaled)
pca_coords <- as.data.frame(pca_result$x[, 1:2])
pca_coords$state <- rownames(USArrests)
pca_coords$cluster <- USArrests$cluster

# Gráfico base con puntos y elipses
plot_base <- fviz_cluster(object = km_cluster, 
                          data = scaled,
                          ellipse.type = "norm",
                          geom = "point",
                          palette = "jco",
                          main = "",
                          ggtheme = theme_minimal())

# Agregar etiquetas de los estados en sus puntos
plot_base +
  geom_text(data = pca_coords, aes(x = PC1, y = PC2, label = state),
            size = 3, vjust = -0.5)

USArrests %>%
  group_by(km_cluster) %>%
  summarise(mean_murder = round(mean(Murder), 2),
            mean_assault = round(mean(Assault), 2),
            mean_urbanpop = round(mean(UrbanPop), 2),
            mean_rape = round(mean(Rape), 2),
            members = n())
## # A tibble: 2 × 6
##   km_cluster mean_murder mean_assault mean_urbanpop mean_rape members
##        <int>       <dbl>        <dbl>         <dbl>     <dbl>   <int>
## 1          1       12.2          255.          68.4      29.2      20
## 2          2        4.87         114.          63.6      15.9      30
library(plotly)

plot_ly(data = USArrests, 
        x = ~Murder, 
        y = ~Assault, 
        z = ~Rape, 
        type = "scatter3d", 
        mode = "markers", 
        color = as.factor(USArrests$km_cluster)) %>%
  layout(title = "Clusters de USArrests en 3D",
         scene = list(
           xaxis = list(title = "Murder"),
           yaxis = list(title = "Assault"),
           zaxis = list(title = "Rape")))

Visualización de distribución de Murder por cluster

Cluster 1: Alta Criminalidad

  • Mediana ≈ 12 homicidios por 100 000 hab.

  • IQR ≈ [10 – 14] (el 50 % de los estados en este cluster está entre esos valores).

  • Valores máximos llegan a ~18, mínimos cerca de 8.

Cluster 2: Baja Criminalidad

  • Mediana ≈ 5 homicidios por 100 000 hab.

  • IQR ≈ [3 – 7].

  • Rango total entre ~1 y ~11, claramente por debajo de los valores de Cluster 1.

library(ggplot2)

ggplot(USArrests, aes(x = as.factor(km_cluster), y = Murder, fill = as.factor(km_cluster))) +
  geom_boxplot() +
  labs(title = "Distribución de 'Murder' por Cluster",
       x = "Cluster",
       y = "Tasa de Homicidios") +
  theme_minimal()

Mapa de EE.UU. coloreado según el cluster asignado a cada estado

Los estados del norte y oeste tienden a agruparse en el Cluster 2 – Menor criminalidad, mientras que los estados del sur y este se concentran en el Cluster 1 – Mayor criminalidad. Esta distribución sugiere una posible relación entre ubicación geográfica, factores socioeconómicos y niveles de criminalidad.

El Cluster 1 - Mayor criminalidad agrupa estados que, según los datos usados en tu análisis, presentan niveles relativamente más altos de criminalidad:

  • Sur de EE.UU.: Alabama, Georgia, Louisiana, Mississippi, North Carolina, South Carolina, Tennessee, Texas. Esta región es consistente en datos históricos de mayor criminalidad en EE.UU., particularmente en crímenes violentos.

  • Costa Oeste y Suroeste: California, Arizona, Nevada, New Mexico, Colorado. Algunos de estos estados tienen grandes ciudades y corredores migratorios.

  • Grandes estados urbanos del norte: Illinois (Chicago), New York (NYC), Michigan (Detroit), Maryland (Baltimore). En estas zonas la criminalidad suele estar concentrada en áreas urbanas específicas.

Los estados agrupados en el “Cluster 2 - Menos criminalidad”, lo que indica que comparten características comunes relacionadas con bajos niveles de criminalidad. Dicho clúster abarca estados de distintas regiones del país:

  • Noreste: Vermont, New Hampshire, Massachusetts, Maine.

  • Medio Oeste: Iowa, Minnesota, Wisconsin, Ohio.

  • Noroeste: Idaho, Montana, Wyoming.

  • Sur: Arkansas, Kentucky, Virginia, West Virginia.

  • Pacífico: Oregon, Washington, Hawaii.

library(dplyr)
library(usmap)
library(ggplot2)

# Añadir columna de estado como variable
USArrests$state <- rownames(USArrests)

# Añadir la variable de clúster como factor
USArrests$cluster <- factor(km_cluster$cluster)

# Crear dataframe con nombres de estados compatibles con usmap
USArrests_map <- USArrests %>%
  mutate(state = state.name[match(state, state.name)]) %>%
  select(state, cluster) %>%
  filter(!is.na(state) & !is.na(cluster))  # Asegura que no haya NAs

# Crear el mapa sin "NA" en la leyenda
plot_usmap(data = USArrests_map, values = "cluster", regions = "states") +
  scale_fill_manual(
    values = c("1" = "steelblue", "2" = "goldenrod"),
    name = "Clúster",
    na.value = "gray90",       # Color de los estados sin datos
    na.translate = FALSE       # <- Evita que aparezca "NA" en la leyenda
  ) +
  labs(
    title = "Clusters de criminalidad en EE.UU. (USArrests)",
    subtitle = "k = 2, basado en tasas de Murder, Assault, Rape y UrbanPop"
  ) +
  theme_minimal()

Medias originales por cluster y listado de estados agrupados

  • El Cluster 1 tiene valores más bajos en crímenes y población urbana.

  • El Cluster 2 agrupa estados con mayores tasas de asalto, asesinato, violación y mayor urbanización.

Esto refuerza la idea de que los estados más urbanizados tienden a tener mayor incidencia de ciertos delitos.

USArrests$cluster <- km_cluster$cluster

aggregate(. ~ cluster, 
          data = USArrests[, c("Murder","Assault","UrbanPop","Rape","cluster")],
          FUN  = mean)
##   cluster Murder  Assault UrbanPop     Rape
## 1       1 12.165 255.2500 68.40000 29.16500
## 2       2  4.870 114.4333 63.63333 15.94333
split(rownames(USArrests), USArrests$cluster)
## $`1`
##  [1] "Alabama"        "Alaska"         "Arizona"        "California"    
##  [5] "Colorado"       "Florida"        "Georgia"        "Illinois"      
##  [9] "Louisiana"      "Maryland"       "Michigan"       "Mississippi"   
## [13] "Missouri"       "Nevada"         "New Mexico"     "New York"      
## [17] "North Carolina" "South Carolina" "Tennessee"      "Texas"         
## 
## $`2`
##  [1] "Arkansas"      "Connecticut"   "Delaware"      "Hawaii"       
##  [5] "Idaho"         "Indiana"       "Iowa"          "Kansas"       
##  [9] "Kentucky"      "Maine"         "Massachusetts" "Minnesota"    
## [13] "Montana"       "Nebraska"      "New Hampshire" "New Jersey"   
## [17] "North Dakota"  "Ohio"          "Oklahoma"      "Oregon"       
## [21] "Pennsylvania"  "Rhode Island"  "South Dakota"  "Utah"         
## [25] "Vermont"       "Virginia"      "Washington"    "West Virginia"
## [29] "Wisconsin"     "Wyoming"

Tabla resumen de tamaño y medias por cluster en unidades originales

Se identifican que los clusters estan compuestos de la siguiente manera:

  • Cluster 1: 19 estados con menor criminalidad.

  • Cluster 2: 31 estados con mayores tasas delictivas.

library(knitr)

tabla_resumen <- USArrests %>%
  group_by(cluster) %>%
  summarise(
    N       = n(),
    Murder  = round(mean(Murder), 2),
    Assault = round(mean(Assault), 2),
    UrbanPop= round(mean(UrbanPop),2),
    Rape    = round(mean(Rape), 2)
  )

kable(tabla_resumen, caption = "Resumen por cluster (unidades originales)")
Resumen por cluster (unidades originales)
cluster N Murder Assault UrbanPop Rape
1 20 12.16 255.25 68.40 29.16
2 30 4.87 114.43 63.63 15.94
centroides_std <- data.frame(
  cluster = rownames(km_cluster$centers),
  km_cluster$centers
)

kable(centroides_std, digits = 3, caption = "Centroides estandarizados")
Centroides estandarizados
cluster Murder Assault UrbanPop Rape
1 1.005 1.014 0.198 0.847
2 -0.670 -0.676 -0.132 -0.565

Tabla de centroides estandarizados resultantes del análisis k-means

tabla_resumen <- USArrests %>%
  group_by(cluster) %>%
  summarise(
    N        = n(),
    Murder   = round(mean(Murder),   2),
    Assault  = round(mean(Assault),  2),
    UrbanPop = round(mean(UrbanPop), 2),
    Rape     = round(mean(Rape),     2)
  )

print(tabla_resumen)
## # A tibble: 2 × 6
##   cluster     N Murder Assault UrbanPop  Rape
##     <int> <int>  <dbl>   <dbl>    <dbl> <dbl>
## 1       1    20  12.2     255.     68.4  29.2
## 2       2    30   4.87    114.     63.6  15.9
centroides_std <- as.data.frame(km_cluster$centers) %>%
  tibble::rownames_to_column(var = "cluster")

print(centroides_std)
##   cluster    Murder    Assault   UrbanPop       Rape
## 1       1  1.004934  1.0138274  0.1975853  0.8469650
## 2       2 -0.669956 -0.6758849 -0.1317235 -0.5646433

Modelo de Random Forest para Clasificar Clusters

El modelo de Random Forest aplicado a la clasificación de clústeres (resultado de kmeans) se comportó de manera óptima tanto en entrenamiento como en prueba. El mejor desempeño se logra con mtry = 1, es decir, considerando solo 1 predictor aleatorio por división.

library(randomForest)
library(caret)
library(randomForest)
library(caret)
library(dplyr)

rf_data <- USArrests %>%
  select(Murder, Assault, UrbanPop, Rape) %>%
  mutate(km_cluster = as.factor(km_cluster$cluster))  # agregar cluster como variable objetivo

set.seed(123)
index <- createDataPartition(rf_data$km_cluster, p = 0.8, list = FALSE)
train_dt <- rf_data[index, ]
test_dt  <- rf_data[-index, ]
trControl <- trainControl(method = "cv", number = 10)
tuneGrid  <- expand.grid(mtry = 1:(ncol(train_dt) - 1))  # -1 porque la última es la variable objetivo

set.seed(123)
modelRF <- train(km_cluster ~ ., 
                 data = train_dt, 
                 method = "rf", 
                 trControl = trControl,
                 tuneGrid = tuneGrid,
                 ntree = 500)

print(modelRF)
## Random Forest 
## 
## 40 samples
##  4 predictor
##  2 classes: '1', '2' 
## 
## No pre-processing
## Resampling: Cross-Validated (10 fold) 
## Summary of sample sizes: 36, 35, 37, 36, 36, 37, ... 
## Resampling results across tuning parameters:
## 
##   mtry  Accuracy  Kappa
##   1     1.000     1.00 
##   2     0.975     0.95 
##   3     0.975     0.95 
##   4     0.975     0.95 
## 
## Accuracy was used to select the optimal model using the largest value.
## The final value used for the model was mtry = 1.

Importancia de variable según Random Forest

  • Murder es el predictor más importante en la clasificación de clústeres.

  • UrbanPop no aporta valor predictivo (importancia = 0).

Por lo tanto, la criminalidad violenta (Murder, Assault, Rape) define los clústeres, no tanto la urbanización.

rf_model <- randomForest(km_cluster ~ ., data = train_dt, mtry = modelRF$bestTune$mtry, importance = TRUE)
varImpPlot(rf_model)

varImp(modelRF, scale = TRUE)
## rf variable importance
## 
##          Overall
## Murder    100.00
## Assault    87.37
## Rape       63.83
## UrbanPop    0.00

Precisión de Random Forest

  • El rendimiento en test es perfecto, lo cual puede deberse al tamaño reducido de muestra (n=10).
preds <- predict(modelRF, newdata = test_dt)
confusionMatrix(preds, test_dt$km_cluster)
## Confusion Matrix and Statistics
## 
##           Reference
## Prediction 1 2
##          1 4 0
##          2 0 6
##                                      
##                Accuracy : 1          
##                  95% CI : (0.6915, 1)
##     No Information Rate : 0.6        
##     P-Value [Acc > NIR] : 0.006047   
##                                      
##                   Kappa : 1          
##                                      
##  Mcnemar's Test P-Value : NA         
##                                      
##             Sensitivity : 1.0        
##             Specificity : 1.0        
##          Pos Pred Value : 1.0        
##          Neg Pred Value : 1.0        
##              Prevalence : 0.4        
##          Detection Rate : 0.4        
##    Detection Prevalence : 0.4        
##       Balanced Accuracy : 1.0        
##                                      
##        'Positive' Class : 1          
## 

Conclusión

El análisis del conjunto de datos USArrests revela que existen dos grupos claramente diferenciados entre los estados de EE.UU. en función de los delitos (asesinato, asalto, violación) y la proporción de población urbana. La segmentación mediante K-means clustering muestra una clara separación geográfica y de características criminales. El uso de métricas de distancia como Canberra, junto con el estadístico de Hopkins y el método de Gap Statistic, permite confirmar la validez de la agrupación.

El Clúster 1, compuesto por 20 estados, agrupa principalmente a los estados del norte y oeste, y se caracteriza por niveles más elevados de criminalidad en los indicadores analizados. Estos estados presentan valores por encima del promedio en asesinatos, asaltos y violaciones, así como una mayor proporción de población urbana.

Por otro lado, el Clúster 2, que incluye a 30 estados del sur y este, muestra un perfil opuesto: niveles más bajos de delitos violentos y menor urbanización. Esta división no solo es estadísticamente sólida, sino que también presenta una clara correspondencia geográfica, lo que sugiere que los factores regionales pueden estar influyendo en los patrones delictivos.

Por último, la validación del modelo con un algoritmo de random forest mostró una exactitud del 100% en la clasificación de los clústeres, y la importancia de variables indica que los delitos como asesinato y asalto son los predictores más relevantes en la segmentación de los estados.

Referencias

Ketchen, D. J., & Shook, C. L. (1996). The application of cluster analysis in strategic management research: An analysis and critique. Strategic Management Journal, 17(6), 441–458.

Lance, G. N., & Williams, W. T. (1967). A general theory of classificatory sorting strategies: I. Hierarchical systems. The Computer Journal.

Sneath, P.H.A., & Sokal, R.R. (1973). Numerical Taxonomy. W. H. Freeman and Company.

MacQueen, J. (1967). Some methods for classification and analysis of multivariate observations. Proceedings of the Fifth Berkeley Symposium on Mathematical Statistics and Probability.

Tan, P.-N., Steinbach, M., & Kumar, V. (2018). Introduction to Data Mining. Pearson.

LS0tDQp0aXRsZTogIkNsdXN0ZXJpemFjacOzbiB1dGlsaXphbmRvIEstTWVkaWFzIg0Kc3VidGl0bGU6ICJBY3RpdmlkYWQgMDQiDQphdXRob3I6ICJFcXVpcG8gNyINCmRhdGU6ICIyMDI1LTAzLTI4Ig0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgICBjb2RlX2Rvd25sb2FkOiB5ZXMNCiAgICB0aGVtZTogY2VydWxlYW4NCiAgICBoaWdobGlnaHQ6IHB5Z21lbnRzDQotLS0NCg0KIVtdKGh0dHBzOi8vY2l0cmlzLXVjLm9yZy93cC1jb250ZW50L3VwbG9hZHMvMjAxOS8xMC9UZWMtZGUtTW9udGVycmV5LWxvZ28taG9yaXpvbnRhbC1ibHVlLnBuZykNCg0KPGRpdiBzdHlsZT0idGV4dC1hbGlnbjogY2VudGVyIj4NCiAgPHA+PHN0cm9uZz5QbGFuZWFjacOzbiBlc3RyYXTDqWdpY2EgYmFzYWRhIGVuIGFuYWzDrXRpY2EgcHJlc2NyaXB0aXZhPC9zdHJvbmc+PC9wPg0KICA8cD48c3Ryb25nPkdydXBvIDUwMzwvc3Ryb25nPjwvcD4NCiAgPHA+PHN0cm9uZz5Qcm9mZXNvciBSb2RvbGZvIE1pZ3VlbCBHYW1lcm9zPC9zdHJvbmc+PC9wPg0KPC9kaXY+DQoNCjxkaXYgc3R5bGU9InRleHQtYWxpZ246IGNlbnRlciI+DQogIDxwPjxzdHJvbmc+RXF1aXBvIDc6PC9zdHJvbmc+PC9wPg0KICA8cD5BMDA4MzMxMTMgLSBBdnJpbCBMb2JhdG88L3A+DQogIDxwPkEwMTc3MTEyNyAtIExlc2x5IERhcmlhbiBSb21lcm8gVsOhenF1ZXo8L3A+DQogIDxwPkEwMDgzMTEwNSAtIEphem3DrW4gZGVsIENhcm1lbiBDb3J0ZXogTWVuZG96YTwvcD4NCiAgPHA+QTAxMjg0NjExIC0gTGlzc2V0IEhlcm7DoW5kZXo8L3A+DQo8L2Rpdj4NCg0KYGBge3IsIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQoDQoJZWNobyA9IFRSVUUsDQoJbWVzc2FnZSA9IEZBTFNFLA0KCXdhcm5pbmcgPSBGQUxTRQ0KKQ0KI2luc3RhbGwucGFja2FnZXMoInJuYXR1cmFsZWFydGhoaXJlcyIsIHJlcG9zID0gImh0dHBzOi8vcGFja2FnZXMucm9wZW5zY2kub3JnIikNCiNpbnN0YWxsLnBhY2thZ2VzKCJkZXZ0b29scyIpDQpwYWNtYW46OnBfbG9hZChkcGx5ciwgZ2dwbG90MiwgY2FyLCB0c2VyaWVzLCBjb3JycGxvdCwgcGF0Y2h3b3JrLCByZWFkeGwsIHBzeWNoLCBtYXBzLCBNQVNTLCBBRVIsIHF1YW50bW9kLCBzdGF0cywgc2NhbGVzLCBkbG9va3IsIERhdGFFeHBsb3JlciwgdGlkeXIsIGtuaXRyLCBSQ29sb3JCcmV3ZXIsIHBsb3RseSwgZm9yZWNhc3QsIHRzZXJpZXMsIG9wZW54bHN4LCB2YXJzLCB0aWR5dmVyc2UsIGxtdGVzdCwgdXJjYSwgYVRTQSwgZmFjdG9leHRyYSwgZ2dwbG90MiwgbmFuaWFyLCB0c2VyaWVzLCBjb3JycGxvdCwgcGF0Y2h3b3JrLCByZWFkeGwsIHBzeWNoLCBtYXBzLCBzZiwgcm5hdHVyYWxlYXJ0aCwgcm5hdHVyYWxlYXJ0aGRhdGEsIHN0cmluZ2ksIHNjYWxlcywgZGxvb2tyLCBEYXRhRXhwbG9yZXIsIHRpZHlyLCBrbml0ciwgUkNvbG9yQnJld2VyLCBwbG90bHksIGZvcmVjYXN0LCB0c2VyaWVzLCBvcGVueGxzeCwgdmFycywgbG10ZXN0LCB0aWJibGUsIFNNQ1JNLCBjYXJldCwgcmFuZG9tRm9yZXN0LCB0cmVlKQ0KYGBgDQoNCiMgKipBY3RpdmlkYWQgMDQuIEstTWVkaWFzIChEaXN0YW5jaWEgLSBDYW5iZXJyYSB5IEdhcF9TdGF0KSoqDQoNCiMjICoqSW50cm9kdWNjacOzbioqDQoNCkVsIEFuw6FsaXNpcyBkZSBDbHVzdGVycyBtZWRpYW50ZSBlbCBBbGdvcml0bW8gZGUgSy1NZWRpYXMgZXMgdW4gYWxnb3JpdG1vIGRlIGFncnVwYW1pZW50byBubyBzdXBlcnZpc2FkbyBxdWUgZGl2aWRlIHVuIGNvbmp1bnRvIGRlIGRhdG9zIGVuIEsgZ3J1cG9zIG8gY2zDunN0ZXJlcy4gU3Ugb2JqZXRpdm8gZXMgbWluaW1pemFyIGxhIHZhcmlhbnphIGludHJhLWNsw7pzdGVyIChsYSBkaXN0YW5jaWEgZW50cmUgbG9zIHB1bnRvcyB5IHN1IGNlbnRyb2lkZSkuIFNlIGNhcmFjdGVyaXphIHBvcnF1ZSByZXF1aWVyZSBlc3BlY2lmaWNhciBlbCBuw7ptZXJvIGRlIGNsw7pzdGVyZXMgKEspLGFzdW1lIHF1ZSBsb3MgY2zDunN0ZXJlcyBzb24gZXNmw6lyaWNvcyB5IGRlIHNpbWlsYXIgdGFtYcOxbzsgeSBjb23Dum5tZW50ZSB1dGlsaXphbGEgZGlzdGFuY2lhIEV1Y2xpZGlhbmEgcGVybyBzZSBwdWVkZSBhZGFwdGFyIGEgb3RyYXMgbcOpdHJpY2FzIChjb21vIENhbmJlcnJhKS4NCg0KTGEgZGlzdGFuY2lhIGRlIENhbmJlcnJhIGVzIHVuYSBtZWRpZGEgZGUgZGlzaW1pbGl0dWQgZW50cmUgZG9zIHZlY3RvcmVzIG51bcOpcmljb3MuIEVzIMO6dGlsIGN1YW5kbyBlbCBhbsOhbGlzaXMgZGUgZGF0b3MgY3VlbnRhIGNvbiBtdWNoYXMgZGltZW5zaW9uZXMgKGZlYXR1cmVzKSwgcHJpbmNpcGFsbWVudGUgY3VhbmRvIGxvcyBkYXRvcyBjb250aWVuZW4gdmFsb3JlcyBjZXJjYW5vcyBhIGNlcm8sIHlhIHF1ZSBwZW5hbGl6YSBtw6FzIGxhcyBwZXF1ZcOxYXMgZGlmZXJlbmNpYXMgcmVsYXRpdmFzLg0KDQpBc2ltaXNtbywgZWwgRXN0YWTDrXN0aWNvIGRlIEJyZWNoYSAoR2FwIFN0YXRpc3RpYykgZXMgdW5hIHTDqWNuaWNhIHV0aWxpemFkYSBwYXJhIGRldGVybWluYXIgZWwgbsO6bWVybyDDs3B0aW1vIGRlIGNsw7pzdGVyZXMgKGspIGFsIGFwbGljYXIgZWwgYWxnb3JpdG1vIEstTWVhbnMuIEEgZGlmZXJlbmNpYSBkZWwgbcOpdG9kbyBkZWwgY29kbyAoRWxib3cgTWV0aG9kKSwgcXVlIHNlIGJhc2Egw7puaWNhbWVudGUgZW4gbGEgdmFyaWFjacOzbiBpbnRlcm5hIGRlIGxvcyBjbMO6c3RlcmVzIChXayksIGVsIEdhcCBTdGF0aXN0aWMgY29tcGFyYSBlc2UgdmFsb3IgY29uIGVsIGVzcGVyYWRvIGJham8gdW5hIGRpc3RyaWJ1Y2nDs24gZGUgcmVmZXJlbmNpYSBhbGVhdG9yaWEsIGVzIGRlY2lyLCBzaW4gdW5hIGVzdHJ1Y3R1cmEgZGUgY2zDunN0ZXJlcyBkZWZpbmlkYS4gTWllbnRyYXMgcXVlIGVsIG3DqXRvZG8gZGVsIGNvZG8gaWRlbnRpZmljYSBlbCBrIMOzcHRpbW8gbWVkaWFudGUgdW4gcHVudG8gZGUgaW5mbGV4acOzbiBlbiBsYSBjdXJ2YSBkZSBXaywgZWwgR2FwIFN0YXRpc3RpYyBzZWxlY2Npb25hIGVsIHZhbG9yIGRlIGsgZW4gZWwgcXVlIGxhIGRpZmVyZW5jaWEgZW50cmUgZWwgbG9nYXJpdG1vIGRlIGxhIGRpc3BlcnNpw7NuIG9ic2VydmFkYSB5IGxhIGVzcGVyYWRhIChsb2coV2spKSBlcyBtw6F4aW1hIHkgcHJlc2VudGEgZXN0YWJpbGlkYWQuIERpY2hvIGVuZm9xdWUgZXMgZXNwZWNpYWxtZW50ZSByZWNvbWVuZGFibGUgZW4gZXNjZW5hcmlvcyBkb25kZSBsb3MgZGF0b3MgY29udGllbmVuIHJ1aWRvIG8gbGEgZXN0cnVjdHVyYSBkZSBsb3MgY2zDunN0ZXJlcyBubyBlcyBldmlkZW50ZSBhIHNpbXBsZSB2aXN0YS4NCg0KDQojIyAqKkVEQSoqDQoNCkEgY29udGludWFjacOzbiBzZSBjYXJnYSBsYSBiYXNlIGRlIGRhdG9zIGEgc2VyIGFuYWxpemFkYTogJ1VTQXJyZXN0cycgZGUgbGEgbGlicmVyw61hICdzcERhdGEnLiBFbCBwcmVzZW50ZSBjb25qdW50byBkZSBkYXRvcyBwcm9wb3JjaW9uYSB1bmEgdmlzacOzbiBjdWFudGl0YXRpdmEgZGUgbGEgY3JpbWluYWxpZGFkIHJlZ2lzdHJhZGEgZW4gbG9zIEVzdGFkb3MgVW5pZG9zIGVuIDE5NzMsIGVuZm9jw6FuZG9zZSBlbiB0cmVzIGRlbGl0b3MgZ3JhdmVzOiBob21pY2lkaW8sIGFzYWx0byB5IHZpb2xhY2nDs24uIENhZGEgdW5hIGRlIGVzdGFzIGNhdGVnb3LDrWFzIG11ZXN0cmEgbGEgdGFzYSBkZSBhcnJlc3RvcyBwb3IgY2FkYSAxMDAsMDAwIGhhYml0YW50ZXMgZW4gbG9zIDUwIGVzdGFkb3MgZGVsIHBhw61zLiBBZGVtw6FzLCBzZSBpbmNsdXllIHVuYSB2YXJpYWJsZSBxdWUgcmVwcmVzZW50YSBlbCBncmFkbyBkZSB1cmJhbml6YWNpw7NuIGRlIGNhZGEgZXN0YWRvLCBleHByZXNhZG8gY29tbyBlbCBwb3JjZW50YWplIGRlIGxhIHBvYmxhY2nDs24gcXVlIHJlc2lkZSBlbiB6b25hcyB1cmJhbmFzLiBFc3RhIGluZm9ybWFjacOzbiBlcyDDunRpbCBwYXJhIGFuYWxpemFyIGxhIHJlbGFjacOzbiBlbnRyZSBlbCBlbnRvcm5vIHVyYmFubyB5IGxhIGluY2lkZW5jaWEgZGVsaWN0aXZhLCBhc8OtIGNvbW8gcGFyYSBpZGVudGlmaWNhciBwYXRyb25lcyBnZW9ncsOhZmljb3MgeSBzb2NpYWxlcyByZWxhY2lvbmFkb3MgY29uIGxhIGNyaW1pbmFsaWRhZCBlbiBlc2UgcGVyaW9kbyBoaXN0w7NyaWNvLg0KDQpgYGB7cn0NCmRhdGEoIlVTQXJyZXN0cyIpDQpoZWFkKFVTQXJyZXN0cykNCmdsaW1wc2UoVVNBcnJlc3RzKQ0KYGBgDQoNCkNvbiBiYXNlIGFsIEVEQSBzZSBkZXRlcm1pbmEgcXVlIGV4aXN0ZW4gZG9zIHBhdHJvbmVzIGNsYXJvcyBkZSBjcmltaW5hbGlkYWQgZW50cmUgbG9zIGVzdGFkb3MgZGUgRUUuVVUuIHJlcHJlc2VudGFkb3MgZW4gbG9zIGNsdXN0ZXJzLCBkb25kZSB1biBjbHVzdGVyIHJlcHJlc2VudGEgZXN0YWRvcyBjb24gYWx0YSBjcmltaW5hbGlkYWQ7IHkgZWwgb3RybywgZXN0YWRvcyBjb24gYmFqYSBjcmltaW5hbGlkYWQuIExvIGFudGVyaW9yIGRlYmlkbyBhIHF1ZSBsYXMgdmFyaWFibGVzIE11cmRlciwgQXNzYXVsdCB5IFJhcGUgZXN0w6FuIG5lZ2F0aXZhbWVudGUgY29ycmVsYWNpb25hZGFzIGNvbiBlbCBuw7ptZXJvIGRlIGNsdXN0ZXIgYXNpZ25hZG8gKHIg4omIIC0wLjgzKSwgbG8gcXVlIHN1Z2llcmUgcXVlIGxvcyBjbHVzdGVycyBjYXB0dXJhbiBuaXZlbGVzIGRpc3RpbnRvcyBkZSBjcmltaW5hbGlkYWQuDQoNCioqQ29ycmVsYWNpb25lcyoqDQoNCiogTXVyZGVyIHkgQXNzYXVsdDogQ29ycmVsYWNpw7NuIGZ1ZXJ0ZSB5IHBvc2l0aXZhIChyIOKJiCAwLjgwKSwgaW5kaWNhbmRvIHF1ZSBsb3MgZXN0YWRvcyBjb24gbWF5b3IgdGFzYSBkZSBhc2VzaW5hdG9zIHRpZW5kZW4gdGFtYmnDqW4gYSB0ZW5lciBtYXlvcmVzIHRhc2FzIGRlIGFzYWx0b3MuDQoNCiogUmFwZSBzZSBjb3JyZWxhY2lvbmEgZGUgZm9ybWEgbW9kZXJhZGEgY29uOiBBc3NhdWx0IChyIOKJiCAwLjY2KSAmIE11cmRlciAociDiiYggMC41NikNCg0KKiBVcmJhblBvcCAocG9yY2VudGFqZSBkZSBwb2JsYWNpw7NuIHVyYmFuYSkgbXVlc3RyYSBiYWphIG8gbnVsYSBjb3JyZWxhY2nDs24gY29uIG90cmFzIHZhcmlhYmxlcywgbG8gcXVlIHN1Z2llcmUgcXVlIGxhIHVyYmFuaXphY2nDs24gbm8gc2UgYXNvY2lhIGRpcmVjdGFtZW50ZSBjb24gbGFzIHRhc2FzIGRlIGNyaW1lbiByZXBvcnRhZGFzIGVuIGVzdGUgY29uanVudG8gZGUgZGF0b3MuDQoNCioqRGlzdHJpYnVjacOzbiBkZSB2YXJpYWJsZXMqKg0KDQoqIE11cmRlciB5IEFzc2F1bHQgZXN0w6FuIHNlc2dhZGFzIGEgbGEgZGVyZWNoYSwgaW5kaWNhbmRvIHF1ZSBsYSBtYXlvcsOtYSBkZSBsb3MgZXN0YWRvcyB0aWVuZW4gdGFzYXMgYmFqYXMsIHBlcm8gYWxndW5vcyB0aWVuZW4gdmFsb3JlcyBtdXkgYWx0b3MuDQoNCiogUmFwZSB0YW1iacOpbiB0aWVuZSBjaWVydGEgYXNpbWV0csOtYS4gTWllbnRyYXMgcXVlLCBVcmJhblBvcCB0aWVuZSB1bmEgZGlzdHJpYnVjacOzbiBtw6FzIHVuaWZvcm1lLg0KDQoqIExvcyBncsOhZmljb3MgZGUgZGlzcGVyc2nDs24gbXVlc3RyYW4gYWdydXBhY2lvbmVzIGNsYXJhcywgc29icmUgdG9kbyBlbnRyZSB2YXJpYWJsZXMgYWx0YW1lbnRlIGNvcnJlbGFjaW9uYWRhcyAoY29tbyBNdXJkZXIgdnMuIEFzc2F1bHQpLg0KDQpgYGB7cn0NCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoR0dhbGx5KQ0KIyBIaXN0b2dyYW1hcyANClVTQXJyZXN0c19sb25nIDwtIFVTQXJyZXN0cyAlPiUNCiAgdGlkeXI6OnBpdm90X2xvbmdlcihjb2xzID0gZXZlcnl0aGluZygpLCBuYW1lc190byA9ICJWYXJpYWJsZSIsIHZhbHVlc190byA9ICJWYWxvciIpDQoNCmdncGxvdChVU0FycmVzdHNfbG9uZywgYWVzKHggPSBWYWxvcikpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlucyA9IDIwLCBmaWxsID0gInN0ZWVsYmx1ZSIsIGNvbG9yID0gImJsYWNrIikgKw0KICBmYWNldF93cmFwKH4gVmFyaWFibGUsIHNjYWxlcyA9ICJmcmVlIiwgbmNvbCA9IDIpICsNCiAgbGFicyh0aXRsZSA9ICJIaXN0b2dyYW1hcyBkZSB2YXJpYWJsZXMgZW4gVVNBcnJlc3RzIikgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KIyBCb3hwbG90cw0KZ2dwbG90KFVTQXJyZXN0c19sb25nLCBhZXMoeCA9IFZhcmlhYmxlLCB5ID0gVmFsb3IsIGZpbGwgPSBWYXJpYWJsZSkpICsNCiAgZ2VvbV9ib3hwbG90KCkgKw0KICBsYWJzKHRpdGxlID0gIkJveHBsb3RzIHBvciB2YXJpYWJsZSBlbiBVU0FycmVzdHMiKSArDQogIHRoZW1lX21pbmltYWwoKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikNCg0KIyBNYXRyaXogZGUgY29ycmVsYWNpw7NuDQpnZ3BhaXJzKFVTQXJyZXN0cywNCiAgICAgICAgbG93ZXIgPSBsaXN0KGNvbnRpbnVvdXMgPSAic21vb3RoIiksDQogICAgICAgIGRpYWcgPSBsaXN0KGNvbnRpbnVvdXMgPSAiYmFyRGlhZyIpLA0KICAgICAgICB1cHBlciA9IGxpc3QoY29udGludW91cyA9ICJjb3IiKSkgKw0KICBnZ3RpdGxlKCJNYXRyaXogZGUgY29ycmVsYWNpw7NuIHkgcmVsYWNpb25lcyBlbnRyZSB2YXJpYWJsZXMgZW4gVVNBcnJlc3RzIikNCmBgYA0KDQoNCiMjICoqRXNjYWxhIGRlIHZhcmlhYmxlcyoqDQoNCkFsIG1vbWVudG8gZGUgcmVhbGl6YXIgZWwgYW7DoWxpc2lzIGRlIGNvbmdsb21lcmFkb3Mgc2UgZXN0YW5kYXJpemFuIHRvZGFzIGxhcyB2YXJpYWJsZXMsIHBhcmEgZXZpdGFyIGRhciB1biBtYXlvciBwZXNvIGEgdW5hIHZhcmlhYmxlIHBvciB0ZW5lciBtYWduaXR1ZGVzIG1heW9yZXMNCg0KYGBge3J9DQpzY2FsZWQgPC0gVVNBcnJlc3RzICU+JQ0KICBzY2FsZSgpICU+JSANCiAgYXMuZGF0YS5mcmFtZSgpDQoNCiNWZXJpZmljYXIgYnJldmVtZW50ZSBxdWUgZW4gZWZlY3RvIG51ZXN0cmEgbnVldmEgYmFzZSBkZSBkYXRvcyBlc3TDqSBjZW50cmFkYSBlbiAnMCcgeSB0ZW5nYSBkZXN2aWFjacOzbiBlc3TDoW5kYXIgdW5pdGFyaWEgKCcxJykNCnByaW50KCJNZWFucyIpDQpzYXBwbHkoc2NhbGVkLCBtZWFuKSAlPiUgIHJvdW5kKDQpDQpwcmludCgiU3RhbmRhcmQgRGV2aWF0aW9ucyIpDQpzYXBwbHkoc2NhbGVkLCBzZCkNCg0Kc3RyKHNjYWxlZCkNCmBgYA0KDQojIyAqKkFuw6FsaXNpcyBkZSBsYSB2aWFiaWxpZGFkIGRlIGVzdGEgYmFzZSBkZSBkYXRvcyBhIGNvbnRlbmVyIGNvbmdsb21lcmFkb3MqKg0KYGBge3J9DQpzZXQuc2VlZCgyMDI1KQ0KDQpjbHVzdGVyX3RkIDwtIA0KICBnZXRfY2x1c3RfdGVuZGVuY3koDQogICAgc2NhbGVkLA0KICAgIG4gPSAyNSwNCiAgICBncmFkaWVudCA9IGxpc3QobG93ID0gIndoaXRlIiwgIA0KICAgICAgICAgICAgICAgICAgICBoaWdoID0gInN0ZWVsYmx1ZSIpDQogICAgKQ0KDQpjbHVzdGVyX3RkJHBsb3QgI1NlIHB1ZWRlbiBpZGVudGlmaWNhciBkb3MgcG9zaWJsZXMgY2x1c3RlcnMuDQpgYGANCg0KIyMgKipNdWVzdHJhIHkgYW7DoWxpc2lzIGRlbCBFc3RhZMOtc3RpY28gZGUgSG9wa2luKioNCg0KU2Ugb2J0aWVuZSB1biB2YWxvciBkZSAwLjYxIHF1ZSBhbCBlc3RhciBjZXJjYW5vIGEgMSwgaW5kaWNhIHF1ZSBsb3MgZGF0b3MgdGllbmVuIHVuYSB0ZW5kZW5jaWEgYSBmb3JtYXIgY2zDunN0ZXJlcyAoZXN0cnVjdHVyYSBubyBhbGVhdG9yaWEpLg0KDQpgYGB7cn0NCmNsdXN0ZXJfdGQkaG9wa2luc19zdGF0IA0KDQojIEVsIGVzdGFkaXN0aWNvIGVzIGNlcmNhbm8gYSAxICgwLjYxKSwgbG8gY3VhbCBpbmRpY2EgcXVlIGVsIGRhdGFzZXQgdGllbmUgdGVuZGVuY2lhIGEgc2VyICAgY2x1c3Rlcml6YWRhLiANCmBgYA0KDQojIyAqKk1hdHJpeiBkZSBzaW1pbGl0dWRlcyBjb24gbGEgZnVuY2nDs24gZGUgZGlzdGFuY2lhIENhbmJlcnJhIHkgR2FwX1N0YXQqKg0KDQoqKkRpc3RhbmNpYSBDYW5iZXJyYToqKg0KDQoqIEVsIHByaW1lciBncsOhZmljbyAoRXVjbMOtZGVvKSBtdWVzdHJhIHNpbWlsaXR1ZGVzIHBlcm8gY29uIHBhdHJvbmVzIG1lbm9zIGNsYXJvcy4NCg0KKiBMYSBtYXRyaXogZGUgZGlzdGFuY2lhIENhbmJlcnJhIG11ZXN0cmEgZGl2aXNpb25lcyBtw6FzIG1hcmNhZGFzLCBsbyBxdWUgZmFjaWxpdGEgbGEgZGV0ZWNjacOzbiBkZSBncnVwb3MuDQoNCiogw4FyZWFzIGJsYW5jYXMgaW5kaWNhbiBncmFuIHNpbWlsaXR1ZDsgw6FyZWFzIG9zY3VyYXMsIGdyYW4gZGlmZXJlbmNpYS4NCg0KYGBge3J9DQogZnZpel9kaXN0KA0KICAgICBnZXRfZGlzdChzY2FsZWQsIG1ldGhvZD0iY2FuYmVycmEiKSwNCiAgICAgb3JkZXIgPSBUUlVFLA0KICAgICBzaG93X2xhYmVscyA9IEZBTFNFLA0KICAgICBsYWJfc2l6ZSA9IE5VTEwsDQogICAgIGdyYWRpZW50ID0gbGlzdChsb3cgPSAid2hpdGUiLCBoaWdoID0gInN0ZWVsYmx1ZSIpDQopDQoNCiNVdGlsaXphbmRvIGxhIGRpc3RhbmNpYSAnQ2FuYmVycmEnIHNlIHZlbiBtdWNobyBtw6FzIGNsYXJvcyBsb3MgZG9zIHBvc2libGVzIGNsdXN0ZXJzLiANCmBgYA0KDQojIyAqKk7Dum1lcm8gw7NwdGltbyBkZSBDbHVzdGVycyAnaycqKg0KDQoqKkdhcCBTdGF0aXN0aWM6KioNCg0KU2Ugb2J0dXZvIHF1ZSBlbCBtw6F4aW1vIHZhbG9yIGRlbCBnYXAgcGFyYSBrID0gMiwgbG8gcXVlIHN1Z2llcmUgcXVlIDIgZ3J1cG9zIGVzIGxvIG3DoXMgYWRlY3VhZG8uDQoNCmBgYHtyfQ0KI0dhcF9TdGF0DQpmdml6X25iY2x1c3Qoc2NhbGVkLCBrbWVhbnMsIG1ldGhvZCA9ICJnYXBfc3RhdCIpICMgMiBvIDMgY2x1c3RlcnMNCmBgYA0KDQojIyAqKkNyZWFjacOzbiBkZSAnaycgY2x1c3RlcnMgY29uIGVsIGFsZ29yaXRtbyBLLW1lZGlhcyoqDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMjAyNSkNCmttX2NsdXN0ZXIgPC0ga21lYW5zKHNjYWxlZCwgMiwgbnN0YXJ0ID0gMjUpDQoNClVTQXJyZXN0cyRrbV9jbHVzdGVyIDwtIGttX2NsdXN0ZXIkY2x1c3Rlcg0KZ2xpbXBzZShVU0FycmVzdHMpDQpgYGANCg0KIyMgKipWaXN1YWxpemFjacOzbiBkZSAnaycgY2x1c3RlcnMqKg0KDQojIyMgUmVwcmVzZW50YWNpw7NuIGRlIGxvcyBjbHVzdGVycyBmb3JtYWRvcyBjb24gay1tZWFucw0KDQoqIExvcyBkYXRvcyBzZSBwcm95ZWN0YW4gZW4gMiBkaW1lbnNpb25lcyAocHJvYmFibGVtZW50ZSBQQ0EpIHBhcmEgdmlzdWFsaXphciBsYSBzZXBhcmFjacOzbiBkZSBncnVwb3MuDQoNCiogU2UgaWRlbnRpZmljYW4gZG9zIGdydXBvcyBjbGFyYW1lbnRlIGRpZmVyZW5jaWFkb3MsIGRlc3RhY2Fkb3MgcG9yIGNvbG9yZXMgeSBlbGlwc2VzIHF1ZSBpbmRpY2FuIGxhIGRpc3BlcnNpw7NuIGRlbnRybyBkZSBjYWRhIGNsw7pzdGVyLg0KDQoqIFNlIG11ZXN0cmEgcXVlIE1pc3NvdXJpIGFwYXJlY2UgY29tbyB1biBwdW50byBpbnRlcm1lZGlvIGVudHJlIGFtYm9zIGNsw7pzdGVyZXMsIGxvIHF1ZSBzdWdpZXJlIHF1ZSBjb21wYXJ0ZSBjYXJhY3RlcsOtc3RpY2FzIHRhbnRvIGRlIGVzdGFkb3MgY29uIGFsdGEgY29tbyBjb24gYmFqYSBjcmltaW5hbGlkYWQsIHNpZW5kbyB1biBjYXNvIGxpbcOtdHJvZmUgbyBkZSB0cmFuc2ljacOzbi4NCg0KYGBge3J9DQpsaWJyYXJ5KGZhY3RvZXh0cmEpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGRwbHlyKQ0KDQojIFBDQQ0KcGNhX3Jlc3VsdCA8LSBwcmNvbXAoc2NhbGVkKQ0KcGNhX2Nvb3JkcyA8LSBhcy5kYXRhLmZyYW1lKHBjYV9yZXN1bHQkeFssIDE6Ml0pDQpwY2FfY29vcmRzJHN0YXRlIDwtIHJvd25hbWVzKFVTQXJyZXN0cykNCnBjYV9jb29yZHMkY2x1c3RlciA8LSBVU0FycmVzdHMkY2x1c3Rlcg0KDQojIEdyw6FmaWNvIGJhc2UgY29uIHB1bnRvcyB5IGVsaXBzZXMNCnBsb3RfYmFzZSA8LSBmdml6X2NsdXN0ZXIob2JqZWN0ID0ga21fY2x1c3RlciwgDQogICAgICAgICAgICAgICAgICAgICAgICAgIGRhdGEgPSBzY2FsZWQsDQogICAgICAgICAgICAgICAgICAgICAgICAgIGVsbGlwc2UudHlwZSA9ICJub3JtIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgZ2VvbSA9ICJwb2ludCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgIHBhbGV0dGUgPSAiamNvIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgbWFpbiA9ICIiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBnZ3RoZW1lID0gdGhlbWVfbWluaW1hbCgpKQ0KDQojIEFncmVnYXIgZXRpcXVldGFzIGRlIGxvcyBlc3RhZG9zIGVuIHN1cyBwdW50b3MNCnBsb3RfYmFzZSArDQogIGdlb21fdGV4dChkYXRhID0gcGNhX2Nvb3JkcywgYWVzKHggPSBQQzEsIHkgPSBQQzIsIGxhYmVsID0gc3RhdGUpLA0KICAgICAgICAgICAgc2l6ZSA9IDMsIHZqdXN0ID0gLTAuNSkNCg0KYGBgDQoNCmBgYHtyfQ0KVVNBcnJlc3RzICU+JQ0KICBncm91cF9ieShrbV9jbHVzdGVyKSAlPiUNCiAgc3VtbWFyaXNlKG1lYW5fbXVyZGVyID0gcm91bmQobWVhbihNdXJkZXIpLCAyKSwNCiAgICAgICAgICAgIG1lYW5fYXNzYXVsdCA9IHJvdW5kKG1lYW4oQXNzYXVsdCksIDIpLA0KICAgICAgICAgICAgbWVhbl91cmJhbnBvcCA9IHJvdW5kKG1lYW4oVXJiYW5Qb3ApLCAyKSwNCiAgICAgICAgICAgIG1lYW5fcmFwZSA9IHJvdW5kKG1lYW4oUmFwZSksIDIpLA0KICAgICAgICAgICAgbWVtYmVycyA9IG4oKSkNCmBgYA0KDQpgYGB7cn0NCmxpYnJhcnkocGxvdGx5KQ0KDQpwbG90X2x5KGRhdGEgPSBVU0FycmVzdHMsIA0KICAgICAgICB4ID0gfk11cmRlciwgDQogICAgICAgIHkgPSB+QXNzYXVsdCwgDQogICAgICAgIHogPSB+UmFwZSwgDQogICAgICAgIHR5cGUgPSAic2NhdHRlcjNkIiwgDQogICAgICAgIG1vZGUgPSAibWFya2VycyIsIA0KICAgICAgICBjb2xvciA9IGFzLmZhY3RvcihVU0FycmVzdHMka21fY2x1c3RlcikpICU+JQ0KICBsYXlvdXQodGl0bGUgPSAiQ2x1c3RlcnMgZGUgVVNBcnJlc3RzIGVuIDNEIiwNCiAgICAgICAgIHNjZW5lID0gbGlzdCgNCiAgICAgICAgICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gIk11cmRlciIpLA0KICAgICAgICAgICB5YXhpcyA9IGxpc3QodGl0bGUgPSAiQXNzYXVsdCIpLA0KICAgICAgICAgICB6YXhpcyA9IGxpc3QodGl0bGUgPSAiUmFwZSIpKSkNCmBgYA0KDQojIyMgVmlzdWFsaXphY2nDs24gZGUgZGlzdHJpYnVjacOzbiBkZSBNdXJkZXIgcG9yIGNsdXN0ZXINCg0KKipDbHVzdGVyIDE6IEFsdGEgQ3JpbWluYWxpZGFkKioNCg0KKiBNZWRpYW5hIOKJiCAxMiBob21pY2lkaW9zIHBvciAxMDAgMDAwIGhhYi4NCg0KKiBJUVIg4omIIFsxMCDigJMgMTRdIChlbCA1MCAlIGRlIGxvcyBlc3RhZG9zIGVuIGVzdGUgY2x1c3RlciBlc3TDoSBlbnRyZSBlc29zIHZhbG9yZXMpLg0KDQoqIFZhbG9yZXMgbcOheGltb3MgbGxlZ2FuIGEgfjE4LCBtw61uaW1vcyBjZXJjYSBkZSA4Lg0KDQoqKkNsdXN0ZXIgMjogQmFqYSBDcmltaW5hbGlkYWQqKg0KDQoqIE1lZGlhbmEg4omIIDUgaG9taWNpZGlvcyBwb3IgMTAwIDAwMCBoYWIuDQoNCiogSVFSIOKJiCBbMyDigJMgN10uDQoNCiogUmFuZ28gdG90YWwgZW50cmUgfjEgeSB+MTEsIGNsYXJhbWVudGUgcG9yIGRlYmFqbyBkZSBsb3MgdmFsb3JlcyBkZSBDbHVzdGVyIDEuDQoNCmBgYHtyfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KDQpnZ3Bsb3QoVVNBcnJlc3RzLCBhZXMoeCA9IGFzLmZhY3RvcihrbV9jbHVzdGVyKSwgeSA9IE11cmRlciwgZmlsbCA9IGFzLmZhY3RvcihrbV9jbHVzdGVyKSkpICsNCiAgZ2VvbV9ib3hwbG90KCkgKw0KICBsYWJzKHRpdGxlID0gIkRpc3RyaWJ1Y2nDs24gZGUgJ011cmRlcicgcG9yIENsdXN0ZXIiLA0KICAgICAgIHggPSAiQ2x1c3RlciIsDQogICAgICAgeSA9ICJUYXNhIGRlIEhvbWljaWRpb3MiKSArDQogIHRoZW1lX21pbmltYWwoKQ0KYGBgDQoNCg0KIyMjIE1hcGEgZGUgRUUuVVUuIGNvbG9yZWFkbyBzZWfDum4gZWwgY2x1c3RlciBhc2lnbmFkbyBhIGNhZGEgZXN0YWRvDQoNCkxvcyBlc3RhZG9zIGRlbCBub3J0ZSB5IG9lc3RlIHRpZW5kZW4gYSBhZ3J1cGFyc2UgZW4gZWwgQ2x1c3RlciAyIOKAkyBNZW5vciBjcmltaW5hbGlkYWQsIG1pZW50cmFzIHF1ZSBsb3MgZXN0YWRvcyBkZWwgc3VyIHkgZXN0ZSBzZSBjb25jZW50cmFuIGVuIGVsIENsdXN0ZXIgMSDigJMgTWF5b3IgY3JpbWluYWxpZGFkLiBFc3RhIGRpc3RyaWJ1Y2nDs24gc3VnaWVyZSB1bmEgcG9zaWJsZSByZWxhY2nDs24gZW50cmUgdWJpY2FjacOzbiBnZW9ncsOhZmljYSwgZmFjdG9yZXMgc29jaW9lY29uw7NtaWNvcyB5IG5pdmVsZXMgZGUgY3JpbWluYWxpZGFkLg0KDQpFbCAqKkNsdXN0ZXIgMSAtIE1heW9yIGNyaW1pbmFsaWRhZCoqIGFncnVwYSBlc3RhZG9zIHF1ZSwgc2Vnw7puIGxvcyBkYXRvcyB1c2Fkb3MgZW4gdHUgYW7DoWxpc2lzLCBwcmVzZW50YW4gbml2ZWxlcyByZWxhdGl2YW1lbnRlIG3DoXMgYWx0b3MgZGUgY3JpbWluYWxpZGFkOg0KDQoqIFN1ciBkZSBFRS5VVS46IEFsYWJhbWEsIEdlb3JnaWEsIExvdWlzaWFuYSwgTWlzc2lzc2lwcGksIE5vcnRoIENhcm9saW5hLCBTb3V0aCBDYXJvbGluYSwgVGVubmVzc2VlLCBUZXhhcy4gRXN0YSByZWdpw7NuIGVzIGNvbnNpc3RlbnRlIGVuIGRhdG9zIGhpc3TDs3JpY29zIGRlIG1heW9yIGNyaW1pbmFsaWRhZCBlbiBFRS5VVS4sIHBhcnRpY3VsYXJtZW50ZSBlbiBjcsOtbWVuZXMgdmlvbGVudG9zLg0KDQoqIENvc3RhIE9lc3RlIHkgU3Vyb2VzdGU6IENhbGlmb3JuaWEsIEFyaXpvbmEsIE5ldmFkYSwgTmV3IE1leGljbywgQ29sb3JhZG8uIEFsZ3Vub3MgZGUgZXN0b3MgZXN0YWRvcyB0aWVuZW4gZ3JhbmRlcyBjaXVkYWRlcyB5IGNvcnJlZG9yZXMgbWlncmF0b3Jpb3MuDQoNCiogR3JhbmRlcyBlc3RhZG9zIHVyYmFub3MgZGVsIG5vcnRlOiBJbGxpbm9pcyAoQ2hpY2FnbyksIE5ldyBZb3JrIChOWUMpLCBNaWNoaWdhbiAoRGV0cm9pdCksIE1hcnlsYW5kIChCYWx0aW1vcmUpLiBFbiBlc3RhcyB6b25hcyBsYSBjcmltaW5hbGlkYWQgc3VlbGUgZXN0YXIgY29uY2VudHJhZGEgZW4gw6FyZWFzIHVyYmFuYXMgZXNwZWPDrWZpY2FzLg0KDQoNCkxvcyBlc3RhZG9zIGFncnVwYWRvcyBlbiBlbCAqKiJDbHVzdGVyIDIgLSBNZW5vcyBjcmltaW5hbGlkYWQiKiosIGxvIHF1ZSBpbmRpY2EgcXVlIGNvbXBhcnRlbiBjYXJhY3RlcsOtc3RpY2FzIGNvbXVuZXMgcmVsYWNpb25hZGFzIGNvbiBiYWpvcyBuaXZlbGVzIGRlIGNyaW1pbmFsaWRhZC4gRGljaG8gY2zDunN0ZXIgYWJhcmNhIGVzdGFkb3MgZGUgZGlzdGludGFzIHJlZ2lvbmVzIGRlbCBwYcOtczoNCg0KKiBOb3Jlc3RlOiBWZXJtb250LCBOZXcgSGFtcHNoaXJlLCBNYXNzYWNodXNldHRzLCBNYWluZS4NCg0KKiBNZWRpbyBPZXN0ZTogSW93YSwgTWlubmVzb3RhLCBXaXNjb25zaW4sIE9oaW8uDQoNCiogTm9yb2VzdGU6IElkYWhvLCBNb250YW5hLCBXeW9taW5nLg0KDQoqIFN1cjogQXJrYW5zYXMsIEtlbnR1Y2t5LCBWaXJnaW5pYSwgV2VzdCBWaXJnaW5pYS4NCg0KKiBQYWPDrWZpY286IE9yZWdvbiwgV2FzaGluZ3RvbiwgSGF3YWlpLg0KDQpgYGB7cn0NCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHVzbWFwKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KDQojIEHDsWFkaXIgY29sdW1uYSBkZSBlc3RhZG8gY29tbyB2YXJpYWJsZQ0KVVNBcnJlc3RzJHN0YXRlIDwtIHJvd25hbWVzKFVTQXJyZXN0cykNCg0KIyBBw7FhZGlyIGxhIHZhcmlhYmxlIGRlIGNsw7pzdGVyIGNvbW8gZmFjdG9yDQpVU0FycmVzdHMkY2x1c3RlciA8LSBmYWN0b3Ioa21fY2x1c3RlciRjbHVzdGVyKQ0KDQojIENyZWFyIGRhdGFmcmFtZSBjb24gbm9tYnJlcyBkZSBlc3RhZG9zIGNvbXBhdGlibGVzIGNvbiB1c21hcA0KVVNBcnJlc3RzX21hcCA8LSBVU0FycmVzdHMgJT4lDQogIG11dGF0ZShzdGF0ZSA9IHN0YXRlLm5hbWVbbWF0Y2goc3RhdGUsIHN0YXRlLm5hbWUpXSkgJT4lDQogIHNlbGVjdChzdGF0ZSwgY2x1c3RlcikgJT4lDQogIGZpbHRlcighaXMubmEoc3RhdGUpICYgIWlzLm5hKGNsdXN0ZXIpKSAgIyBBc2VndXJhIHF1ZSBubyBoYXlhIE5Bcw0KDQojIENyZWFyIGVsIG1hcGEgc2luICJOQSIgZW4gbGEgbGV5ZW5kYQ0KcGxvdF91c21hcChkYXRhID0gVVNBcnJlc3RzX21hcCwgdmFsdWVzID0gImNsdXN0ZXIiLCByZWdpb25zID0gInN0YXRlcyIpICsNCiAgc2NhbGVfZmlsbF9tYW51YWwoDQogICAgdmFsdWVzID0gYygiMSIgPSAic3RlZWxibHVlIiwgIjIiID0gImdvbGRlbnJvZCIpLA0KICAgIG5hbWUgPSAiQ2zDunN0ZXIiLA0KICAgIG5hLnZhbHVlID0gImdyYXk5MCIsICAgICAgICMgQ29sb3IgZGUgbG9zIGVzdGFkb3Mgc2luIGRhdG9zDQogICAgbmEudHJhbnNsYXRlID0gRkFMU0UgICAgICAgIyA8LSBFdml0YSBxdWUgYXBhcmV6Y2EgIk5BIiBlbiBsYSBsZXllbmRhDQogICkgKw0KICBsYWJzKA0KICAgIHRpdGxlID0gIkNsdXN0ZXJzIGRlIGNyaW1pbmFsaWRhZCBlbiBFRS5VVS4gKFVTQXJyZXN0cykiLA0KICAgIHN1YnRpdGxlID0gImsgPSAyLCBiYXNhZG8gZW4gdGFzYXMgZGUgTXVyZGVyLCBBc3NhdWx0LCBSYXBlIHkgVXJiYW5Qb3AiDQogICkgKw0KICB0aGVtZV9taW5pbWFsKCkNCg0KYGBgDQoNCiMjIyBNZWRpYXMgb3JpZ2luYWxlcyBwb3IgY2x1c3RlciB5IGxpc3RhZG8gZGUgZXN0YWRvcyBhZ3J1cGFkb3MNCg0KKiBFbCBDbHVzdGVyIDEgdGllbmUgdmFsb3JlcyBtw6FzIGJham9zIGVuIGNyw61tZW5lcyB5IHBvYmxhY2nDs24gdXJiYW5hLg0KDQoqIEVsIENsdXN0ZXIgMiBhZ3J1cGEgZXN0YWRvcyBjb24gbWF5b3JlcyB0YXNhcyBkZSBhc2FsdG8sIGFzZXNpbmF0bywgdmlvbGFjacOzbiB5IG1heW9yIHVyYmFuaXphY2nDs24uDQoNCkVzdG8gcmVmdWVyemEgbGEgaWRlYSBkZSBxdWUgbG9zIGVzdGFkb3MgbcOhcyB1cmJhbml6YWRvcyB0aWVuZGVuIGEgdGVuZXIgbWF5b3IgaW5jaWRlbmNpYSBkZSBjaWVydG9zIGRlbGl0b3MuDQoNCmBgYHtyfQ0KVVNBcnJlc3RzJGNsdXN0ZXIgPC0ga21fY2x1c3RlciRjbHVzdGVyDQoNCmFnZ3JlZ2F0ZSguIH4gY2x1c3RlciwgDQogICAgICAgICAgZGF0YSA9IFVTQXJyZXN0c1ssIGMoIk11cmRlciIsIkFzc2F1bHQiLCJVcmJhblBvcCIsIlJhcGUiLCJjbHVzdGVyIildLA0KICAgICAgICAgIEZVTiAgPSBtZWFuKQ0KDQpzcGxpdChyb3duYW1lcyhVU0FycmVzdHMpLCBVU0FycmVzdHMkY2x1c3RlcikNCmBgYA0KDQojIyMgVGFibGEgcmVzdW1lbiBkZSB0YW1hw7FvIHkgbWVkaWFzIHBvciBjbHVzdGVyIGVuIHVuaWRhZGVzIG9yaWdpbmFsZXMNCg0KU2UgaWRlbnRpZmljYW4gcXVlIGxvcyBjbHVzdGVycyBlc3RhbiBjb21wdWVzdG9zIGRlIGxhIHNpZ3VpZW50ZSBtYW5lcmE6DQoNCiogQ2x1c3RlciAxOiAxOSBlc3RhZG9zIGNvbiBtZW5vciBjcmltaW5hbGlkYWQuDQoNCiogQ2x1c3RlciAyOiAzMSBlc3RhZG9zIGNvbiBtYXlvcmVzIHRhc2FzIGRlbGljdGl2YXMuDQoNCmBgYHtyfQ0KbGlicmFyeShrbml0cikNCg0KdGFibGFfcmVzdW1lbiA8LSBVU0FycmVzdHMgJT4lDQogIGdyb3VwX2J5KGNsdXN0ZXIpICU+JQ0KICBzdW1tYXJpc2UoDQogICAgTiAgICAgICA9IG4oKSwNCiAgICBNdXJkZXIgID0gcm91bmQobWVhbihNdXJkZXIpLCAyKSwNCiAgICBBc3NhdWx0ID0gcm91bmQobWVhbihBc3NhdWx0KSwgMiksDQogICAgVXJiYW5Qb3A9IHJvdW5kKG1lYW4oVXJiYW5Qb3ApLDIpLA0KICAgIFJhcGUgICAgPSByb3VuZChtZWFuKFJhcGUpLCAyKQ0KICApDQoNCmthYmxlKHRhYmxhX3Jlc3VtZW4sIGNhcHRpb24gPSAiUmVzdW1lbiBwb3IgY2x1c3RlciAodW5pZGFkZXMgb3JpZ2luYWxlcykiKQ0KDQpjZW50cm9pZGVzX3N0ZCA8LSBkYXRhLmZyYW1lKA0KICBjbHVzdGVyID0gcm93bmFtZXMoa21fY2x1c3RlciRjZW50ZXJzKSwNCiAga21fY2x1c3RlciRjZW50ZXJzDQopDQoNCmthYmxlKGNlbnRyb2lkZXNfc3RkLCBkaWdpdHMgPSAzLCBjYXB0aW9uID0gIkNlbnRyb2lkZXMgZXN0YW5kYXJpemFkb3MiKQ0KYGBgDQoNCiMjIyBUYWJsYSBkZSBjZW50cm9pZGVzIGVzdGFuZGFyaXphZG9zIHJlc3VsdGFudGVzIGRlbCBhbsOhbGlzaXMgay1tZWFucw0KYGBge3J9DQp0YWJsYV9yZXN1bWVuIDwtIFVTQXJyZXN0cyAlPiUNCiAgZ3JvdXBfYnkoY2x1c3RlcikgJT4lDQogIHN1bW1hcmlzZSgNCiAgICBOICAgICAgICA9IG4oKSwNCiAgICBNdXJkZXIgICA9IHJvdW5kKG1lYW4oTXVyZGVyKSwgICAyKSwNCiAgICBBc3NhdWx0ICA9IHJvdW5kKG1lYW4oQXNzYXVsdCksICAyKSwNCiAgICBVcmJhblBvcCA9IHJvdW5kKG1lYW4oVXJiYW5Qb3ApLCAyKSwNCiAgICBSYXBlICAgICA9IHJvdW5kKG1lYW4oUmFwZSksICAgICAyKQ0KICApDQoNCnByaW50KHRhYmxhX3Jlc3VtZW4pDQoNCmNlbnRyb2lkZXNfc3RkIDwtIGFzLmRhdGEuZnJhbWUoa21fY2x1c3RlciRjZW50ZXJzKSAlPiUNCiAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4odmFyID0gImNsdXN0ZXIiKQ0KDQpwcmludChjZW50cm9pZGVzX3N0ZCkNCmBgYA0KDQojIyAqKk1vZGVsbyBkZSBSYW5kb20gRm9yZXN0IHBhcmEgQ2xhc2lmaWNhciBDbHVzdGVycyoqDQoNCkVsIG1vZGVsbyBkZSBSYW5kb20gRm9yZXN0IGFwbGljYWRvIGEgbGEgY2xhc2lmaWNhY2nDs24gZGUgY2zDunN0ZXJlcyAocmVzdWx0YWRvIGRlIGttZWFucykgc2UgY29tcG9ydMOzIGRlIG1hbmVyYSDDs3B0aW1hIHRhbnRvIGVuIGVudHJlbmFtaWVudG8gY29tbyBlbiBwcnVlYmEuIEVsIG1lam9yIGRlc2VtcGXDsW8gc2UgbG9ncmEgY29uIG10cnkgPSAxLCBlcyBkZWNpciwgY29uc2lkZXJhbmRvIHNvbG8gMSBwcmVkaWN0b3IgYWxlYXRvcmlvIHBvciBkaXZpc2nDs24uDQoNCmBgYHtyfQ0KbGlicmFyeShyYW5kb21Gb3Jlc3QpDQpsaWJyYXJ5KGNhcmV0KQ0KbGlicmFyeShyYW5kb21Gb3Jlc3QpDQpsaWJyYXJ5KGNhcmV0KQ0KbGlicmFyeShkcGx5cikNCg0KcmZfZGF0YSA8LSBVU0FycmVzdHMgJT4lDQogIHNlbGVjdChNdXJkZXIsIEFzc2F1bHQsIFVyYmFuUG9wLCBSYXBlKSAlPiUNCiAgbXV0YXRlKGttX2NsdXN0ZXIgPSBhcy5mYWN0b3Ioa21fY2x1c3RlciRjbHVzdGVyKSkgICMgYWdyZWdhciBjbHVzdGVyIGNvbW8gdmFyaWFibGUgb2JqZXRpdm8NCg0Kc2V0LnNlZWQoMTIzKQ0KaW5kZXggPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihyZl9kYXRhJGttX2NsdXN0ZXIsIHAgPSAwLjgsIGxpc3QgPSBGQUxTRSkNCnRyYWluX2R0IDwtIHJmX2RhdGFbaW5kZXgsIF0NCnRlc3RfZHQgIDwtIHJmX2RhdGFbLWluZGV4LCBdDQpgYGANCg0KYGBge3J9DQp0ckNvbnRyb2wgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDEwKQ0KdHVuZUdyaWQgIDwtIGV4cGFuZC5ncmlkKG10cnkgPSAxOihuY29sKHRyYWluX2R0KSAtIDEpKSAgIyAtMSBwb3JxdWUgbGEgw7psdGltYSBlcyBsYSB2YXJpYWJsZSBvYmpldGl2bw0KDQpzZXQuc2VlZCgxMjMpDQptb2RlbFJGIDwtIHRyYWluKGttX2NsdXN0ZXIgfiAuLCANCiAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluX2R0LCANCiAgICAgICAgICAgICAgICAgbWV0aG9kID0gInJmIiwgDQogICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IHRyQ29udHJvbCwNCiAgICAgICAgICAgICAgICAgdHVuZUdyaWQgPSB0dW5lR3JpZCwNCiAgICAgICAgICAgICAgICAgbnRyZWUgPSA1MDApDQoNCnByaW50KG1vZGVsUkYpDQpgYGANCg0KIyMjIEltcG9ydGFuY2lhIGRlIHZhcmlhYmxlIHNlZ8O6biBSYW5kb20gRm9yZXN0DQoNCiogTXVyZGVyIGVzIGVsIHByZWRpY3RvciBtw6FzIGltcG9ydGFudGUgZW4gbGEgY2xhc2lmaWNhY2nDs24gZGUgY2zDunN0ZXJlcy4NCg0KKiBVcmJhblBvcCBubyBhcG9ydGEgdmFsb3IgcHJlZGljdGl2byAoaW1wb3J0YW5jaWEgPSAwKS4NCg0KUG9yIGxvIHRhbnRvLCBsYSBjcmltaW5hbGlkYWQgdmlvbGVudGEgKE11cmRlciwgQXNzYXVsdCwgUmFwZSkgZGVmaW5lIGxvcyBjbMO6c3RlcmVzLCBubyB0YW50byBsYSB1cmJhbml6YWNpw7NuLg0KDQpgYGB7cn0NCnJmX21vZGVsIDwtIHJhbmRvbUZvcmVzdChrbV9jbHVzdGVyIH4gLiwgZGF0YSA9IHRyYWluX2R0LCBtdHJ5ID0gbW9kZWxSRiRiZXN0VHVuZSRtdHJ5LCBpbXBvcnRhbmNlID0gVFJVRSkNCnZhckltcFBsb3QocmZfbW9kZWwpDQp2YXJJbXAobW9kZWxSRiwgc2NhbGUgPSBUUlVFKQ0KYGBgDQoNCiMjIyBQcmVjaXNpw7NuIGRlIFJhbmRvbSBGb3Jlc3QNCg0KKiBFbCByZW5kaW1pZW50byBlbiB0ZXN0IGVzIHBlcmZlY3RvLCBsbyBjdWFsIHB1ZWRlIGRlYmVyc2UgYWwgdGFtYcOxbyByZWR1Y2lkbyBkZSBtdWVzdHJhIChuPTEwKS4NCg0KYGBge3J9DQpwcmVkcyA8LSBwcmVkaWN0KG1vZGVsUkYsIG5ld2RhdGEgPSB0ZXN0X2R0KQ0KY29uZnVzaW9uTWF0cml4KHByZWRzLCB0ZXN0X2R0JGttX2NsdXN0ZXIpDQpgYGANCg0KIyMgKipDb25jbHVzacOzbioqDQoNCkVsIGFuw6FsaXNpcyBkZWwgY29uanVudG8gZGUgZGF0b3MgVVNBcnJlc3RzIHJldmVsYSBxdWUgZXhpc3RlbiBkb3MgZ3J1cG9zIGNsYXJhbWVudGUgZGlmZXJlbmNpYWRvcyBlbnRyZSBsb3MgZXN0YWRvcyBkZSBFRS5VVS4gZW4gZnVuY2nDs24gZGUgbG9zIGRlbGl0b3MgKGFzZXNpbmF0bywgYXNhbHRvLCB2aW9sYWNpw7NuKSB5IGxhIHByb3BvcmNpw7NuIGRlIHBvYmxhY2nDs24gdXJiYW5hLiBMYSBzZWdtZW50YWNpw7NuIG1lZGlhbnRlIEstbWVhbnMgY2x1c3RlcmluZyBtdWVzdHJhIHVuYSBjbGFyYSBzZXBhcmFjacOzbiBnZW9ncsOhZmljYSB5IGRlIGNhcmFjdGVyw61zdGljYXMgY3JpbWluYWxlcy4gRWwgdXNvIGRlIG3DqXRyaWNhcyBkZSBkaXN0YW5jaWEgY29tbyBDYW5iZXJyYSwganVudG8gY29uIGVsIGVzdGFkw61zdGljbyBkZSBIb3BraW5zIHkgZWwgbcOpdG9kbyBkZSBHYXAgU3RhdGlzdGljLCBwZXJtaXRlIGNvbmZpcm1hciBsYSB2YWxpZGV6IGRlIGxhIGFncnVwYWNpw7NuLg0KDQpFbCBDbMO6c3RlciAxLCBjb21wdWVzdG8gcG9yIDIwIGVzdGFkb3MsIGFncnVwYSBwcmluY2lwYWxtZW50ZSBhIGxvcyBlc3RhZG9zIGRlbCBub3J0ZSB5IG9lc3RlLCB5IHNlIGNhcmFjdGVyaXphIHBvciBuaXZlbGVzIG3DoXMgZWxldmFkb3MgZGUgY3JpbWluYWxpZGFkIGVuIGxvcyBpbmRpY2Fkb3JlcyBhbmFsaXphZG9zLiBFc3RvcyBlc3RhZG9zIHByZXNlbnRhbiB2YWxvcmVzIHBvciBlbmNpbWEgZGVsIHByb21lZGlvIGVuIGFzZXNpbmF0b3MsIGFzYWx0b3MgeSB2aW9sYWNpb25lcywgYXPDrSBjb21vIHVuYSBtYXlvciBwcm9wb3JjacOzbiBkZSBwb2JsYWNpw7NuIHVyYmFuYS4NCg0KUG9yIG90cm8gbGFkbywgZWwgQ2zDunN0ZXIgMiwgcXVlIGluY2x1eWUgYSAzMCBlc3RhZG9zIGRlbCBzdXIgeSBlc3RlLCBtdWVzdHJhIHVuIHBlcmZpbCBvcHVlc3RvOiBuaXZlbGVzIG3DoXMgYmFqb3MgZGUgZGVsaXRvcyB2aW9sZW50b3MgeSBtZW5vciB1cmJhbml6YWNpw7NuLiBFc3RhIGRpdmlzacOzbiBubyBzb2xvIGVzIGVzdGFkw61zdGljYW1lbnRlIHPDs2xpZGEsIHNpbm8gcXVlIHRhbWJpw6luIHByZXNlbnRhIHVuYSBjbGFyYSBjb3JyZXNwb25kZW5jaWEgZ2VvZ3LDoWZpY2EsIGxvIHF1ZSBzdWdpZXJlIHF1ZSBsb3MgZmFjdG9yZXMgcmVnaW9uYWxlcyBwdWVkZW4gZXN0YXIgaW5mbHV5ZW5kbyBlbiBsb3MgcGF0cm9uZXMgZGVsaWN0aXZvcy4NCg0KUG9yIMO6bHRpbW8sIGxhIHZhbGlkYWNpw7NuIGRlbCBtb2RlbG8gY29uIHVuIGFsZ29yaXRtbyBkZSByYW5kb20gZm9yZXN0IG1vc3Ryw7MgdW5hIGV4YWN0aXR1ZCBkZWwgMTAwJSBlbiBsYSBjbGFzaWZpY2FjacOzbiBkZSBsb3MgY2zDunN0ZXJlcywgeSBsYSBpbXBvcnRhbmNpYSBkZSB2YXJpYWJsZXMgaW5kaWNhIHF1ZSBsb3MgZGVsaXRvcyBjb21vIGFzZXNpbmF0byB5IGFzYWx0byBzb24gbG9zIHByZWRpY3RvcmVzIG3DoXMgcmVsZXZhbnRlcyBlbiBsYSBzZWdtZW50YWNpw7NuIGRlIGxvcyBlc3RhZG9zLg0KDQoNCiMjICoqUmVmZXJlbmNpYXMqKg0KDQpLZXRjaGVuLCBELiBKLiwgJiBTaG9vaywgQy4gTC4gKDE5OTYpLiBUaGUgYXBwbGljYXRpb24gb2YgY2x1c3RlciBhbmFseXNpcyBpbiBzdHJhdGVnaWMgbWFuYWdlbWVudCByZXNlYXJjaDogQW4gYW5hbHlzaXMgYW5kIGNyaXRpcXVlLiBTdHJhdGVnaWMgTWFuYWdlbWVudCBKb3VybmFsLCAxNyg2KSwgNDQx4oCTNDU4Lg0KDQpMYW5jZSwgRy4gTi4sICYgV2lsbGlhbXMsIFcuIFQuICgxOTY3KS4gQSBnZW5lcmFsIHRoZW9yeSBvZiBjbGFzc2lmaWNhdG9yeSBzb3J0aW5nIHN0cmF0ZWdpZXM6IEkuIEhpZXJhcmNoaWNhbCBzeXN0ZW1zLiBUaGUgQ29tcHV0ZXIgSm91cm5hbC4NCg0KU25lYXRoLCBQLkguQS4sICYgU29rYWwsIFIuUi4gKDE5NzMpLiBOdW1lcmljYWwgVGF4b25vbXkuIFcuIEguIEZyZWVtYW4gYW5kIENvbXBhbnkuDQoNCk1hY1F1ZWVuLCBKLiAoMTk2NykuIFNvbWUgbWV0aG9kcyBmb3IgY2xhc3NpZmljYXRpb24gYW5kIGFuYWx5c2lzIG9mIG11bHRpdmFyaWF0ZSBvYnNlcnZhdGlvbnMuIFByb2NlZWRpbmdzIG9mIHRoZSBGaWZ0aCBCZXJrZWxleSBTeW1wb3NpdW0gb24gTWF0aGVtYXRpY2FsIFN0YXRpc3RpY3MgYW5kIFByb2JhYmlsaXR5Lg0KDQpUYW4sIFAuLU4uLCBTdGVpbmJhY2gsIE0uLCAmIEt1bWFyLCBWLiAoMjAxOCkuIEludHJvZHVjdGlvbiB0byBEYXRhIE1pbmluZy4gUGVhcnNvbi4=