Introducción

La idea de estos apuntes es escribir sobre el análisis exploratorio de datos.

Cargar paquetes utilizados en el documento.

library(ggplot2)
library(dplyr)
library(titanic)
theme_set(theme_bw())

Exploremos la data del Titanic.

# Unir las dos tablas por filas con información de los pasajeros del
# titanic que vienen en el paquete titanic: titanic_train y titanic_test
df <- dplyr::bind_rows(titanic::titanic_train,
                       titanic::titanic_test)
# Ver que contiene la data, un breve resumen con glimpse():
glimpse(df)
Observations: 1,309
Variables: 12
$ PassengerId <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14...
$ Survived    <int> 0, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, ...
$ Pclass      <int> 3, 1, 3, 1, 3, 3, 1, 3, 3, 2, 3, 1, 3, 3, 3, ...
$ Name        <chr> "Braund, Mr. Owen Harris", "Cumings, Mrs. Joh...
$ Sex         <chr> "male", "female", "female", "female", "male",...
$ Age         <dbl> 22, 38, 26, 35, 35, NA, 54, 2, 27, 14, 4, 58,...
$ SibSp       <int> 1, 1, 0, 1, 0, 0, 0, 3, 0, 1, 1, 0, 0, 1, 0, ...
$ Parch       <int> 0, 0, 0, 0, 0, 0, 0, 1, 2, 0, 1, 0, 0, 5, 0, ...
$ Ticket      <chr> "A/5 21171", "PC 17599", "STON/O2. 3101282", ...
$ Fare        <dbl> 7.2500, 71.2833, 7.9250, 53.1000, 8.0500, 8.4...
$ Cabin       <chr> "", "C85", "", "C123", "", "", "E46", "", "",...
$ Embarked    <chr> "S", "C", "S", "S", "S", "Q", "S", "S", "S", ...

Para mayor detalle de que información contienen las variables ver la documentación:

?titanic::titanic_train

Inspeccionar variación en una variable

Hablar de los siguientes puntos:

  1. Repaso de variables categorica y continua
  2. Definición de variación
  3. Introduccir el concepto de distribucción

En variable categórica: geom_bar

Analicemos la distribución de pasajeros por sexo que hay en los datos.

ggplot(data = df) +
  geom_bar(mapping = aes(x = Sex), fill = "steelblue", colour = "black") +
  ggtitle("Para observar la variación de una variable categórica: geom_bar.")

La información del gráfico anterior se puede confirmar con la función dplyr::count. Esta función cuenta el número de observaciones en cada uno de los valores únicos (female y male) que tiene la variable (Sex). Por ejemplo, las observaciones en nuestros datos corresponden a pasajeros del Titanic, sí queremos saber cuantos pasajeros son hombres o mujeres, podemos utilizar count de la siguiente manera:

count(df, Sex)

En variable continua: geom_histogram

Veamos como análizar la distribución de una variable continua como Fare, que indica la tarifa pagada por los pasajeros del Titanic.

df %>% 
  select(Fare)

Gráfiquemos cada observación con un punto cuya coordenada en el eje x será dada por el valor indicado en Fare y en el eje y fijaremos , para todas las observaciones, el valor 0.

ggplot(data = df) +
  geom_point(mapping = aes(x = Fare, y = 0))

El gráfico anterior nos da información del rango que pueden tomar los valores, por ejemplo, los valores mínimos y máximos de la variable Fare son 0 y 512.3292 respectivamente. Además se puede observar que hay una concentración de puntos entre el rango tarifario 0 a 100. Luego, dos subgrupos entre 100 a 160 y 200 a 260 aproximadamente. El valor máximo parece ser una observación puntual y bastante alejado del resto de los puntos. ¿Cómo podemos ser más especificos en visualizar las concentraciones de puntos?

Intentemos capturar la magnitud de cada tarifa, en el sentido de cuantas observaciones hay detrás de estas, tarea especial para la función count. Sí pensamos en el output de count, este nos entrega una tabla totalmente legitima para poder gráficar:

# head nos arroja las primeras 6 observaciones de la tabla que nos entrega count(df, Fare)
head(count(df, Fare))

Utilizaremos la misma técnica del gráfico anterior, pero ahora solo tenemos valores unicos en la variable Fare para el eje x y seguiremos usando la constante 0 para el eje y, pero esta vez, usaremos la variable n que nos arroja la función count. Así tendremos la cantidad de observaciones presentadas por cada unos de los valores únicos de x asociada al atributo size de los puntos en el gráfico.

df %>% 
  count(Fare) %>% 
  ggplot() +
  geom_point(mapping = aes(x = Fare, y = 0, size = n), 
             alpha = 1 / 5,
             shape = 21, 
             fill = "red", 
             colour = "black")

Este gráfico introduce la noción de magnitud en los valores observados en la variable Fare. Ahora se puede distinguir mejor las concentraciones de datos en los valores, sin embargo, si reflexionamos acerca de la naturaleza de esta variable continua puede que no tenga mucho sentido contabilizar una tarifa de 6.01 y 6 como distintas. Por supuesto que esto depende del contexto, pero sí lo pensamos, siempre podemos definir intervalos “pertinentes” para ser contabilizados como observaciones símiles. Esto nos lleva a la idea de discretizar una variable continua.

Es posible definir intervalos de tarifas de igual distancia para la variable Fare y clasificar a cual de estos intervalos pertenece el valor de cada observación. Por ejemplo, la función dplyr::cut_width se puede utilizar para crear estos intervalos y obtener una versión discreta de la variable Fare.

# Crear intervalos y clasificar a cual de estos pertenece los valores de una variable continua. 
df %>% 
  select(Fare) %>% 
  mutate(intervalos = cut_width(Fare, 10))

Existe una representación geométrica en el paquete ggplot2 que realiza la transformación anterior y cuenta el número de observaciones por cada uno de los intervalos generados, esta da como resultado un gráfico que se conoce por histograma. Podemos gráficar de forma rápida un histograma en ggplot con la función geom_histogram, que nos solicita como mínimo el asignar una variable continua al atríbuto estético eje x.

ggplot(data = df) +
  geom_histogram(mapping = aes(x = Fare), binwidth = 10, fill = "orange", colour = "black") +
  ggtitle("Para observar la variación de una variable continua: geom_histogram.")

El argumento binwidth utilizado por geom_histogram sirve para definir el tamaño de los intervalos generados. No hay forma estandar de escoger el tamaño del intervalo, por lo tanto, es importante probar con distintos tamaños para ver sí se encuentran patrones de variación interesantes.

Sí solo especificamos el atríbuto estético x en la función geom_histogram, por defecto se utiliza un tamaño de intervalo de 30 (bin_width = 30). El siguiente histograma se genera a partir de los mismos datos que el de arriba, solo que no se especifica el tamaño del intervalo.

# Por defecto, cuando no especificamos bin_width para fijar el tamaño de los intervalos, se utiliza bin_width = 30.
ggplot(data = df) +
  geom_histogram(mapping = aes(x = Fare), fill = "green", colour = "black")

Inspeccionar covariación entre variables

Entre variable categórica y continua: geom_freqpoly

Comparemos las distribución de tarifas entre grupos definidos por la variable Sex. En nuestra clase de introducción a la visualización, incorporamos a nuestra caja de herramientas la función facet_wrap(~ var_categorica) que nos permite replicar el mismo gráfico para cada uno de los grupos formados en base a una variable categórica. El resultado son una serie de gráficos en la misma escala para facilitar la comparación.

ggplot(data = df) +
  geom_histogram(mapping = aes(x = Fare, fill = Sex),
                 colour = "black") +
  scale_fill_viridis_d() +
  facet_wrap(~ Sex)

Este gráfico nos revela que las distribuciiones son similares, presentan una cola hacía la derecha, donde la mayor concentración de valores se encuentran entre el intervalo tarifario 0-100. Sin embargo, podemos ver que la magnitud (count) es menor para el grupo de mujeres. ¿Por qué?

Otra forma de comparar distribuciones de distintos grupos es con la función geom_freqpoly. Básicamente es lo mismo que un histograma, solo que en vez de utilizar barras ocupa líneas, esto facilita la sobreposición visual de los distintos grupos para comparar.

Muchas veces hay un desequilibrio con respecto al número de observaciones pertenecientes a cada grupo. Esto no tiene que ver con que las comparaciones de los grupos solo sean entre grupos de igual tamaño. Por ejemplo, en nuestros datos del Titanic es esperable que hayan menos observaciones de pasajeros en primera clase que en tercera porque habían menos cupos para primera clase en el barco.

df %>% 
  count(Pclass)
ggplot(data = df) +
  geom_freqpoly(mapping = aes(x = Fare, y = ..density.., colour = factor(Pclass),),
                binwidth = 10,
                size = 0.8) +
  ggtitle("Para comparar distintas distribuciones: geom_freqpoly\nSe puede utilizar y = ..density.. para hacer más comparables \nlos grupos con cantidad de observaciones muy distintas.")

Entre variable categórica y continua: geom_boxplot

Hasta ahora hemos visto que el proceso de visualización implica muchas veces realizar transformaciones estadisticas para sintetizar la información de nuestros datos en métricas más faciles de comprender. Un simple ejemplo de transformación es el conteo de observaciones, empleada por las funciones: geom_bar, geom_histogram y geom_freqpoly.

Un gráfico que utiliza otras transformaciones estadísticas y sirve para inspeccionar como covaria una variable continua con respecto a una categórica, es el gráfico de cajas y bigotes, o mejor conocido por su nombre en ingles como boxplot.

Comencemos creando una data de juguete para entender los componentes de boxplot.

# Crear un dataframe a partir de una secuencia que comienza en el 1 y términa en el 25.
(data_juguete <- tibble(
                        id = "var_categorica",
                        seq_num = seq(1, 25)
                        )
)
ggplot(data = data_juguete) +
  geom_boxplot(mapping = aes(x = id, y = seq_num)) +
  ggtitle("Gráfico de caja.")

¿Qué significa esta caja?

Sí ordenamos los valores de la variable seq_num de menor a mayor, y nos preguntamos cual es el valor que se encuentra en el 25%, 50% y 75% de los datos. Encontraremos información clave para comprender el boxplot.

Podemos responder la interrogante anterior, calculando facilmente con la función quantile los valores que representan los % solicitados.

quantile(data_juguete$seq_num)
  0%  25%  50%  75% 100% 
   1    7   13   19   25 
ggplot(data = data_juguete) +
  geom_boxplot(mapping = aes(x = id, y = seq_num)) +
  geom_hline(yintercept = 7, colour = "steelblue", size = 2) +  # valor correspondiente al 25% de los datos
  geom_hline(yintercept = 13, colour = "orange", size = 2) +    # valor correspondiente al 50% de los datos
  geom_hline(yintercept = 19, colour = "red", size = 2) +       # valor correspondiente al 75% de los datos
  ggtitle("Gráfico de caja con líneas horizontales destacando\nlos valores del primero, segundo y tercer cuartil.")

El tamaño de la caja se le conoce como rango intercuartil (IQR) y es la diferencia entre el tercer (75%) y primer cuartil (25%) de una distribución. La línea horizontal que divide por la mitad la caja es el estadístico conocico como la mediana (50%). Fuera de la caja tenemos los bigotes que representan los valores que se encuentran en las colas izquierda y derecha de la distribución.

# Podemos calcular el rango intercuartil con la función:
IQR(data_juguete$seq_num)
[1] 12

Lo que es equivalente a:

19 - 7
[1] 12

Los bigotes del gráfico no superan una distancia desde los valores del cuartil 25% y 75% \(-/+ 1.5 * \text{IQR}\)

La ventaja del boxplot es que compacta la distribución que vimos anteriormente representada por un histograma en esta caja con bigotes. Esto nos permite comparar la distribución de una variable continua con respecto a una variable categorica.

ggplot(data = df) +
  geom_boxplot(mapping = aes(x = factor(Sex), y = Fare, fill = factor(Pclass))) +
  facet_wrap(~ Pclass, scales = "free") +
  scale_fill_viridis_d() +
  ggtitle("Distribución tarifa pasajeros por sexo y clase.")

Los puntos que observamos más allá de los bigotes se consiideran outliers. Sin embargo, esta es una definición arbitraria.

Entre variables categóricas

ggplot(data = df) +
  geom_count(mapping = aes(x = factor(Survived), y = Sex),
             shape = 21,
             fill = "orange",
             colour = "black") +
  xlab("Survived") + 
  ggtitle("Para observar la covaración entre dos variables\ncategóricas: geom_count")

df %>% 
  count(Sex, Survived) %>% 
  ggplot(mapping = aes(x = factor(Survived), y = Sex)) +
  geom_tile(mapping = aes(fill = n)) +
  scale_fill_viridis_c() +
  xlab("Survived") +
  ggtitle("Para observar la covariación entre dos variables\ncategóricas: geom_tile")

Gráfico de correlación

La correlación es una medida para ver el grado de relación lineal entre dos variables. El rango que puede tomar esta métrica es de -1 a 1, siendo:

  • 1: Una relación lineal positiva perfecta
  • 0: Ausencia de relación lineal
  • -1: Una relación lineal negativa perfecta
library(purrr)
library(tidyr)
# Solo seleccionar las variables númericas.
var_caracter <- map_lgl(df, is.character)
datos_num <- df[, !var_caracter]
# Crear matriz de correlación y redondear al segundo decimal.
datos_cor <- as_tibble(cor(datos_num, use = "complete.obs"),
                       rownames = NA)
# Crear una variable con los nombres de las variables.
datos_cor$var1 <- names(datos_num)
# Trasponer la tabla, dejando los nombres de las columnas
# como una variable
datos_cor <- tidyr::gather(datos_cor, "var2", "cor", -var1)
ggplot(data = datos_cor) +
  geom_tile(mapping = aes(x = var1, y = var2, fill = cor)) +
  scale_fill_viridis_c() +
  ggtitle("Gráfico de correlación entre las variables númericas") +
  xlab(NULL) +  # Eliminar etiqueta del eje x
  ylab(NULL)    # Eliminar etiqueta del eje y

LS0tDQp0aXRsZTogIkFuw6FsaXNpcyBleHBsb3JhdG9yaW8gZGUgZGF0b3MiDQpvdXRwdXQ6IA0KICBodG1sX25vdGVib29rOiANCiAgICBoaWdobGlnaHQ6IGhhZGRvY2sNCiAgICB0aGVtZTogc3BhY2VsYWINCiAgICB0b2M6IHllcw0KLS0tDQoNCiMjIEludHJvZHVjY2nDs24NCg0KTGEgaWRlYSBkZSBlc3RvcyBhcHVudGVzIGVzIGVzY3JpYmlyIHNvYnJlIGVsIGFuw6FsaXNpcyBleHBsb3JhdG9yaW8gZGUgZGF0b3MuDQoNCkNhcmdhciBwYXF1ZXRlcyB1dGlsaXphZG9zIGVuIGVsIGRvY3VtZW50by4NCg0KYGBge3J9DQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeSh0aXRhbmljKQ0KDQp0aGVtZV9zZXQodGhlbWVfYncoKSkNCmBgYA0KDQpFeHBsb3JlbW9zIGxhIGRhdGEgZGVsIFRpdGFuaWMuDQoNCmBgYHtyfQ0KIyBVbmlyIGxhcyBkb3MgdGFibGFzIHBvciBmaWxhcyBjb24gaW5mb3JtYWNpw7NuIGRlIGxvcyBwYXNhamVyb3MgZGVsDQojIHRpdGFuaWMgcXVlIHZpZW5lbiBlbiBlbCBwYXF1ZXRlIHRpdGFuaWM6IHRpdGFuaWNfdHJhaW4geSB0aXRhbmljX3Rlc3QNCmRmIDwtIGRwbHlyOjpiaW5kX3Jvd3ModGl0YW5pYzo6dGl0YW5pY190cmFpbiwNCiAgICAgICAgICAgICAgICAgICAgICAgdGl0YW5pYzo6dGl0YW5pY190ZXN0KQ0KDQojIFZlciBxdWUgY29udGllbmUgbGEgZGF0YSwgdW4gYnJldmUgcmVzdW1lbiBjb24gZ2xpbXBzZSgpOg0KZ2xpbXBzZShkZikNCmBgYA0KDQpQYXJhIG1heW9yIGRldGFsbGUgZGUgcXVlIGluZm9ybWFjacOzbiBjb250aWVuZW4gbGFzIHZhcmlhYmxlcyB2ZXIgbGEgZG9jdW1lbnRhY2nDs246DQoNCmBgYHtyLCBldmFsID0gRkFMU0V9DQo/dGl0YW5pYzo6dGl0YW5pY190cmFpbg0KYGBgDQoNCg0KIyMgSW5zcGVjY2lvbmFyIHZhcmlhY2nDs24gZW4gdW5hIHZhcmlhYmxlDQoNCkhhYmxhciBkZSBsb3Mgc2lndWllbnRlcyBwdW50b3M6DQoNCjEuIFJlcGFzbyBkZSB2YXJpYWJsZXMgY2F0ZWdvcmljYSB5IGNvbnRpbnVhDQoyLiBEZWZpbmljacOzbiBkZSB2YXJpYWNpw7NuDQozLiBJbnRyb2R1Y2NpciBlbCBjb25jZXB0byBkZSBkaXN0cmlidWNjacOzbg0KDQojIyMgRW4gdmFyaWFibGUgY2F0ZWfDs3JpY2E6IGdlb21fYmFyDQoNCkFuYWxpY2Vtb3MgbGEgZGlzdHJpYnVjacOzbiBkZSBwYXNhamVyb3MgcG9yIHNleG8gcXVlIGhheSBlbiBsb3MgZGF0b3MuDQoNCmBgYHtyIGVjaG89VFJVRX0NCmdncGxvdChkYXRhID0gZGYpICsNCiAgZ2VvbV9iYXIobWFwcGluZyA9IGFlcyh4ID0gU2V4KSwgZmlsbCA9ICJzdGVlbGJsdWUiLCBjb2xvdXIgPSAiYmxhY2siKSArDQogIGdndGl0bGUoIlBhcmEgb2JzZXJ2YXIgbGEgdmFyaWFjacOzbiBkZSB1bmEgdmFyaWFibGUgY2F0ZWfDs3JpY2E6IGdlb21fYmFyLiIpDQpgYGANCg0KTGEgaW5mb3JtYWNpw7NuIGRlbCBncsOhZmljbyBhbnRlcmlvciBzZSBwdWVkZSBjb25maXJtYXIgY29uIGxhIGZ1bmNpw7NuIGBkcGx5cjo6Y291bnRgLiBFc3RhIGZ1bmNpw7NuIGN1ZW50YSBlbCBuw7ptZXJvIGRlIG9ic2VydmFjaW9uZXMgZW4gY2FkYSB1bm8gZGUgbG9zIHZhbG9yZXMgw7puaWNvcyAoYGZlbWFsZWAgeSBgbWFsZWApIHF1ZSB0aWVuZSBsYSB2YXJpYWJsZSAoYFNleGApLiBQb3IgZWplbXBsbywgbGFzIG9ic2VydmFjaW9uZXMgZW4gbnVlc3Ryb3MgZGF0b3MgY29ycmVzcG9uZGVuIGEgcGFzYWplcm9zIGRlbCBUaXRhbmljLCBzw60gcXVlcmVtb3Mgc2FiZXIgY3VhbnRvcyBwYXNhamVyb3Mgc29uIGhvbWJyZXMgbyBtdWplcmVzLCBwb2RlbW9zIHV0aWxpemFyIGBjb3VudGAgZGUgbGEgc2lndWllbnRlIG1hbmVyYToNCg0KYGBge3J9DQpjb3VudChkZiwgU2V4KQ0KYGBgDQoNCg0KIyMjIEVuIHZhcmlhYmxlIGNvbnRpbnVhOiBnZW9tX2hpc3RvZ3JhbQ0KDQpWZWFtb3MgY29tbyBhbsOhbGl6YXIgbGEgZGlzdHJpYnVjacOzbiBkZSB1bmEgdmFyaWFibGUgY29udGludWEgY29tbyBgRmFyZWAsIHF1ZSBpbmRpY2EgbGEgdGFyaWZhIHBhZ2FkYSBwb3IgbG9zIHBhc2FqZXJvcyBkZWwgVGl0YW5pYy4NCg0KYGBge3J9DQpkZiAlPiUgDQogIHNlbGVjdChGYXJlKQ0KYGBgDQoNCkdyw6FmaXF1ZW1vcyBjYWRhIG9ic2VydmFjacOzbiBjb24gdW4gcHVudG8gY3V5YSBjb29yZGVuYWRhIGVuIGVsIGVqZSB4IHNlcsOhIGRhZGEgcG9yIGVsIHZhbG9yIGluZGljYWRvIGVuIGBGYXJlYCB5IGVuIGVsIGVqZSB5IGZpamFyZW1vcyAsIHBhcmEgdG9kYXMgbGFzIG9ic2VydmFjaW9uZXMsIGVsIHZhbG9yIDAuDQoNCmBgYHtyIGVjaG89VFJVRX0NCmdncGxvdChkYXRhID0gZGYpICsNCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHggPSBGYXJlLCB5ID0gMCkpDQpgYGANCg0KRWwgZ3LDoWZpY28gYW50ZXJpb3Igbm9zIGRhIGluZm9ybWFjacOzbiBkZWwgcmFuZ28gcXVlIHB1ZWRlbiB0b21hciBsb3MgdmFsb3JlcywgcG9yIGVqZW1wbG8sIGxvcyB2YWxvcmVzIG3DrW5pbW9zIHkgbcOheGltb3MgZGUgbGEgdmFyaWFibGUgYEZhcmVgIHNvbiBgciBtaW4oZGYkRmFyZSwgbmEucm0gPSBUUlVFKWAgeSBgciBtYXgoZGYkRmFyZSwgbmEucm0gPSBUUlVFKWAgcmVzcGVjdGl2YW1lbnRlLiBBZGVtw6FzIHNlIHB1ZWRlIG9ic2VydmFyIHF1ZSBoYXkgdW5hIGNvbmNlbnRyYWNpw7NuIGRlIHB1bnRvcyBlbnRyZSBlbCByYW5nbyB0YXJpZmFyaW8gMCBhIDEwMC4gTHVlZ28sIGRvcyBzdWJncnVwb3MgZW50cmUgMTAwIGEgMTYwIHkgMjAwIGEgMjYwIGFwcm94aW1hZGFtZW50ZS4gRWwgdmFsb3IgbcOheGltbyBwYXJlY2Ugc2VyIHVuYSBvYnNlcnZhY2nDs24gcHVudHVhbCB5IGJhc3RhbnRlIGFsZWphZG8gZGVsIHJlc3RvIGRlIGxvcyBwdW50b3MuIMK/Q8OzbW8gcG9kZW1vcyBzZXIgbcOhcyBlc3BlY2lmaWNvcyBlbiB2aXN1YWxpemFyIGxhcyBjb25jZW50cmFjaW9uZXMgZGUgcHVudG9zPw0KDQpJbnRlbnRlbW9zIGNhcHR1cmFyIGxhIG1hZ25pdHVkIGRlIGNhZGEgdGFyaWZhLCBlbiBlbCBzZW50aWRvIGRlIGN1YW50YXMgb2JzZXJ2YWNpb25lcyBoYXkgZGV0csOhcyBkZSBlc3RhcywgdGFyZWEgZXNwZWNpYWwgcGFyYSBsYSBmdW5jacOzbiBgY291bnRgLiBTw60gcGVuc2Ftb3MgZW4gZWwgb3V0cHV0IGRlIGBjb3VudGAsIGVzdGUgbm9zIGVudHJlZ2EgdW5hIHRhYmxhIHRvdGFsbWVudGUgbGVnaXRpbWEgcGFyYSBwb2RlciBncsOhZmljYXI6DQoNCmBgYHtyLCBlY2hvID0gVFJVRX0NCiMgaGVhZCBub3MgYXJyb2phIGxhcyBwcmltZXJhcyA2IG9ic2VydmFjaW9uZXMgZGUgbGEgdGFibGEgcXVlIG5vcyBlbnRyZWdhIGNvdW50KGRmLCBGYXJlKS4NCmhlYWQoY291bnQoZGYsIEZhcmUpKQ0KYGBgDQoNClV0aWxpemFyZW1vcyBsYSBtaXNtYSB0w6ljbmljYSBkZWwgZ3LDoWZpY28gYW50ZXJpb3IsIHBlcm8gYWhvcmEgc29sbyB0ZW5lbW9zIHZhbG9yZXMgdW5pY29zIGVuIGxhIHZhcmlhYmxlIGBGYXJlYCBwYXJhIGVsIGVqZSBgeGAgeSBzZWd1aXJlbW9zIHVzYW5kbyBsYSBjb25zdGFudGUgMCBwYXJhIGVsIGVqZSBgeWAsIHBlcm8gZXN0YSB2ZXosIHVzYXJlbW9zIGxhIHZhcmlhYmxlIGBuYCBxdWUgbm9zIGFycm9qYSBsYSBmdW5jacOzbiBgY291bnRgLiBBc8OtIHRlbmRyZW1vcyBsYSBjYW50aWRhZCBkZSBvYnNlcnZhY2lvbmVzIHByZXNlbnRhZGFzIHBvciBjYWRhIHVub3MgZGUgbG9zIHZhbG9yZXMgw7puaWNvcyBkZSBgeGAgYXNvY2lhZGEgYWwgYXRyaWJ1dG8gYHNpemVgIGRlIGxvcyBwdW50b3MgZW4gZWwgZ3LDoWZpY28uDQoNCmBgYHtyIGVjaG89VFJVRX0NCmRmICU+JSANCiAgY291bnQoRmFyZSkgJT4lIA0KICBnZ3Bsb3QoKSArDQogIGdlb21fcG9pbnQobWFwcGluZyA9IGFlcyh4ID0gRmFyZSwgeSA9IDAsIHNpemUgPSBuKSwgDQogICAgICAgICAgICAgYWxwaGEgPSAxIC8gNSwNCiAgICAgICAgICAgICBzaGFwZSA9IDIxLCANCiAgICAgICAgICAgICBmaWxsID0gInJlZCIsIA0KICAgICAgICAgICAgIGNvbG91ciA9ICJibGFjayIpDQpgYGANCg0KRXN0ZSBncsOhZmljbyBpbnRyb2R1Y2UgbGEgbm9jacOzbiBkZSBtYWduaXR1ZCBlbiBsb3MgdmFsb3Jlcw0Kb2JzZXJ2YWRvcyBlbiBsYSB2YXJpYWJsZSBgRmFyZWAuIEFob3JhIHNlIHB1ZWRlIGRpc3Rpbmd1aXIgbWVqb3INCmxhcyBjb25jZW50cmFjaW9uZXMgZGUgZGF0b3MgZW4gbG9zIHZhbG9yZXMsIHNpbiBlbWJhcmdvLCBzaQ0KcmVmbGV4aW9uYW1vcyBhY2VyY2EgZGUgbGEgbmF0dXJhbGV6YSBkZSBlc3RhIHZhcmlhYmxlIGNvbnRpbnVhDQpwdWVkZSBxdWUgbm8gdGVuZ2EgbXVjaG8gc2VudGlkbyBjb250YWJpbGl6YXIgdW5hIHRhcmlmYSBkZSBgNi4wMWAgeQ0KYDZgIGNvbW8gZGlzdGludGFzLiBQb3Igc3VwdWVzdG8gcXVlIGVzdG8gZGVwZW5kZSBkZWwgY29udGV4dG8sIHBlcm8NCnPDrSBsbyBwZW5zYW1vcywgc2llbXByZSBwb2RlbW9zIGRlZmluaXIgaW50ZXJ2YWxvcyAicGVydGluZW50ZXMiIHBhcmEgc2VyIGNvbnRhYmlsaXphZG9zIGNvbW8gb2JzZXJ2YWNpb25lcyBzw61taWxlcy4gRXN0byBub3MgbGxldmEgYSBsYSBpZGVhIGRlICoqZGlzY3JldGl6YXIqKiB1bmEgdmFyaWFibGUgY29udGludWEuDQoNCkVzIHBvc2libGUgZGVmaW5pciBpbnRlcnZhbG9zIGRlIHRhcmlmYXMgZGUgaWd1YWwgZGlzdGFuY2lhIHBhcmEgbGEgdmFyaWFibGUgYEZhcmVgIHkgY2xhc2lmaWNhciBhIGN1YWwgZGUgZXN0b3MgaW50ZXJ2YWxvcyBwZXJ0ZW5lY2UgZWwgdmFsb3IgZGUgY2FkYSBvYnNlcnZhY2nDs24uIFBvciBlamVtcGxvLCBsYSBmdW5jacOzbiBgZHBseXI6OmN1dF93aWR0aGAgc2UgcHVlZGUgdXRpbGl6YXIgcGFyYSBjcmVhciBlc3RvcyBpbnRlcnZhbG9zIHkgb2J0ZW5lciB1bmEgdmVyc2nDs24gZGlzY3JldGEgZGUgbGEgdmFyaWFibGUgYEZhcmVgLg0KDQpgYGB7cn0NCiMgQ3JlYXIgaW50ZXJ2YWxvcyB5IGNsYXNpZmljYXIgYSBjdWFsIGRlIGVzdG9zIHBlcnRlbmVjZSBsb3MgdmFsb3JlcyBkZSB1bmEgdmFyaWFibGUgY29udGludWEuIA0KZGYgJT4lIA0KICBzZWxlY3QoRmFyZSkgJT4lIA0KICBtdXRhdGUoaW50ZXJ2YWxvcyA9IGN1dF93aWR0aChGYXJlLCAxMCkpDQpgYGANCg0KRXhpc3RlIHVuYSByZXByZXNlbnRhY2nDs24gZ2VvbcOpdHJpY2EgZW4gZWwgcGFxdWV0ZSAqKmdncGxvdDIqKiBxdWUgcmVhbGl6YSBsYSB0cmFuc2Zvcm1hY2nDs24gYW50ZXJpb3IgeSBjdWVudGEgZWwgbsO6bWVybyBkZSBvYnNlcnZhY2lvbmVzIHBvciBjYWRhIHVubyBkZSBsb3MgaW50ZXJ2YWxvcyBnZW5lcmFkb3MsIGVzdGEgZGEgY29tbyByZXN1bHRhZG8gdW4gZ3LDoWZpY28gcXVlIHNlIGNvbm9jZSBwb3IgKipoaXN0b2dyYW1hKiouIFBvZGVtb3MgZ3LDoWZpY2FyIGRlIGZvcm1hIHLDoXBpZGEgdW4gaGlzdG9ncmFtYSBlbiBnZ3Bsb3QgY29uIGxhIGZ1bmNpw7NuIGBnZW9tX2hpc3RvZ3JhbWAsIHF1ZSBub3Mgc29saWNpdGEgY29tbyBtw61uaW1vIGVsIGFzaWduYXIgdW5hIHZhcmlhYmxlIGNvbnRpbnVhIGFsIGF0csOtYnV0byBlc3TDqXRpY28gZWplIGB4YC4NCg0KYGBge3IgZWNobz1UUlVFfQ0KZ2dwbG90KGRhdGEgPSBkZikgKw0KICBnZW9tX2hpc3RvZ3JhbShtYXBwaW5nID0gYWVzKHggPSBGYXJlKSwgYmlud2lkdGggPSAxMCwgZmlsbCA9ICJvcmFuZ2UiLCBjb2xvdXIgPSAiYmxhY2siKSArDQogIGdndGl0bGUoIlBhcmEgb2JzZXJ2YXIgbGEgdmFyaWFjacOzbiBkZSB1bmEgdmFyaWFibGUgY29udGludWE6IGdlb21faGlzdG9ncmFtLiIpDQpgYGANCg0KRWwgYXJndW1lbnRvIGBiaW53aWR0aGAgdXRpbGl6YWRvIHBvciBgZ2VvbV9oaXN0b2dyYW1gIHNpcnZlIHBhcmEgZGVmaW5pciBlbCB0YW1hw7FvIGRlIGxvcyBpbnRlcnZhbG9zIGdlbmVyYWRvcy4gTm8gaGF5IGZvcm1hIGVzdGFuZGFyIGRlIGVzY29nZXIgZWwgdGFtYcOxbyBkZWwgaW50ZXJ2YWxvLCBwb3IgbG8gdGFudG8sIGVzIGltcG9ydGFudGUgcHJvYmFyIGNvbiBkaXN0aW50b3MgdGFtYcOxb3MgcGFyYSB2ZXIgc8OtIHNlIGVuY3VlbnRyYW4gcGF0cm9uZXMgZGUgdmFyaWFjacOzbiBpbnRlcmVzYW50ZXMuDQoNClPDrSBzb2xvIGVzcGVjaWZpY2Ftb3MgZWwgYXRyw61idXRvIGVzdMOpdGljbyBgeGAgZW4gbGEgZnVuY2nDs24gYGdlb21faGlzdG9ncmFtYCwgcG9yIGRlZmVjdG8gc2UgdXRpbGl6YSB1biB0YW1hw7FvIGRlIGludGVydmFsbyBkZSAzMCAoYGJpbl93aWR0aCA9IDMwYCkuIEVsIHNpZ3VpZW50ZSBoaXN0b2dyYW1hIHNlIGdlbmVyYSBhIHBhcnRpciBkZSBsb3MgbWlzbW9zIGRhdG9zIHF1ZSBlbCBkZSBhcnJpYmEsIHNvbG8gcXVlIG5vIHNlIGVzcGVjaWZpY2EgZWwgdGFtYcOxbyBkZWwgaW50ZXJ2YWxvLg0KDQoNCg0KYGBge3J9DQojIFBvciBkZWZlY3RvLCBjdWFuZG8gbm8gZXNwZWNpZmljYW1vcyBiaW5fd2lkdGggcGFyYSBmaWphciBlbCB0YW1hw7FvIGRlIGxvcyBpbnRlcnZhbG9zLCBzZSB1dGlsaXphIGJpbl93aWR0aCA9IDMwLg0KZ2dwbG90KGRhdGEgPSBkZikgKw0KICBnZW9tX2hpc3RvZ3JhbShtYXBwaW5nID0gYWVzKHggPSBGYXJlKSwgZmlsbCA9ICJncmVlbiIsIGNvbG91ciA9ICJibGFjayIpDQpgYGANCg0KIyMgSW5zcGVjY2lvbmFyIGNvdmFyaWFjacOzbiBlbnRyZSB2YXJpYWJsZXMNCg0KIyMjIEVudHJlIHZhcmlhYmxlIGNhdGVnw7NyaWNhIHkgY29udGludWE6IGdlb21fZnJlcXBvbHkNCg0KQ29tcGFyZW1vcyBsYXMgZGlzdHJpYnVjacOzbiBkZSB0YXJpZmFzIGVudHJlIGdydXBvcyBkZWZpbmlkb3MgcG9yIGxhIHZhcmlhYmxlIGBTZXhgLiBFbiBudWVzdHJhIGNsYXNlIGRlIGludHJvZHVjY2nDs24gYSBsYSB2aXN1YWxpemFjacOzbiwgaW5jb3Jwb3JhbW9zIGEgbnVlc3RyYSBjYWphIGRlIGhlcnJhbWllbnRhcyBsYSBmdW5jacOzbiBgZmFjZXRfd3JhcCh+IHZhcl9jYXRlZ29yaWNhKWAgcXVlIG5vcyBwZXJtaXRlIHJlcGxpY2FyIGVsIG1pc21vIGdyw6FmaWNvIHBhcmEgY2FkYSB1bm8gZGUgbG9zIGdydXBvcyBmb3JtYWRvcyBlbiBiYXNlIGEgdW5hIHZhcmlhYmxlIGNhdGVnw7NyaWNhLiBFbCByZXN1bHRhZG8gc29uIHVuYSBzZXJpZSBkZSBncsOhZmljb3MgZW4gbGEgbWlzbWEgZXNjYWxhIHBhcmEgZmFjaWxpdGFyIGxhIGNvbXBhcmFjacOzbi4NCg0KYGBge3IgZWNobz1UUlVFfQ0KZ2dwbG90KGRhdGEgPSBkZikgKw0KICBnZW9tX2hpc3RvZ3JhbShtYXBwaW5nID0gYWVzKHggPSBGYXJlLCBmaWxsID0gU2V4KSwNCiAgICAgICAgICAgICAgICAgY29sb3VyID0gImJsYWNrIikgKw0KICBzY2FsZV9maWxsX3ZpcmlkaXNfZCgpICsNCiAgZmFjZXRfd3JhcCh+IFNleCkNCmBgYA0KDQpFc3RlIGdyw6FmaWNvIG5vcyByZXZlbGEgcXVlIGxhcyBkaXN0cmlidWNpaW9uZXMgc29uIHNpbWlsYXJlcywgcHJlc2VudGFuIHVuYSBjb2xhIGhhY8OtYSBsYSBkZXJlY2hhLCBkb25kZSBsYSBtYXlvciBjb25jZW50cmFjacOzbiBkZSB2YWxvcmVzIHNlIGVuY3VlbnRyYW4gZW50cmUgZWwgaW50ZXJ2YWxvIHRhcmlmYXJpbyAwLTEwMC4gU2luIGVtYmFyZ28sIHBvZGVtb3MgdmVyIHF1ZSBsYSBtYWduaXR1ZCAoYGNvdW50YCkgZXMgbWVub3IgcGFyYSBlbCBncnVwbyBkZSBtdWplcmVzLiDCv1BvciBxdcOpPw0KDQpPdHJhIGZvcm1hIGRlIGNvbXBhcmFyIGRpc3RyaWJ1Y2lvbmVzIGRlIGRpc3RpbnRvcyBncnVwb3MgZXMgY29uIGxhIGZ1bmNpw7NuIGBnZW9tX2ZyZXFwb2x5YC4gQsOhc2ljYW1lbnRlIGVzIGxvIG1pc21vIHF1ZSB1biAqKmhpc3RvZ3JhbWEqKiwgc29sbyBxdWUgZW4gdmV6IGRlIHV0aWxpemFyIGJhcnJhcyBvY3VwYSBsw61uZWFzLCBlc3RvIGZhY2lsaXRhIGxhIHNvYnJlcG9zaWNpw7NuIHZpc3VhbCBkZSBsb3MgZGlzdGludG9zIGdydXBvcyBwYXJhIGNvbXBhcmFyLg0KDQpgYGB7ciBlY2hvPVRSVUV9DQpnZ3Bsb3QoZGF0YSA9IGRmKSArDQogIGdlb21fZnJlcXBvbHkobWFwcGluZyA9IGFlcyh4ID0gRmFyZSwgY29sb3VyID0gZmFjdG9yKFBjbGFzcykpLA0KICAgICAgICAgICAgICAgIGJpbndpZHRoID0gMTAsDQogICAgICAgICAgICAgICAgc2l6ZSA9IDAuOCkgKw0KICBnZ3RpdGxlKCJQYXJhIGNvbXBhcmFyIGRpc3RpbnRhcyBkaXN0cmlidWNpb25lczogZ2VvbV9mcmVxcG9seS4iKSANCmBgYA0KDQpNdWNoYXMgdmVjZXMgaGF5IHVuIGRlc2VxdWlsaWJyaW8gY29uIHJlc3BlY3RvIGFsIG7Dum1lcm8gZGUgb2JzZXJ2YWNpb25lcyBwZXJ0ZW5lY2llbnRlcyBhIGNhZGEgZ3J1cG8uIEVzdG8gbm8gdGllbmUgcXVlIHZlciBjb24gcXVlIGxhcyBjb21wYXJhY2lvbmVzIGRlIGxvcyBncnVwb3Mgc29sbyBzZWFuIGVudHJlIGdydXBvcyBkZSBpZ3VhbCB0YW1hw7FvLiBQb3IgZWplbXBsbywgZW4gbnVlc3Ryb3MgZGF0b3MgZGVsIFRpdGFuaWMgZXMgZXNwZXJhYmxlIHF1ZSBoYXlhbiBtZW5vcyBvYnNlcnZhY2lvbmVzIGRlIHBhc2FqZXJvcyBlbiBwcmltZXJhIGNsYXNlIHF1ZSBlbiB0ZXJjZXJhIHBvcnF1ZSBoYWLDrWFuIG1lbm9zIGN1cG9zIHBhcmEgcHJpbWVyYSBjbGFzZSBlbiBlbCBiYXJjby4NCg0KYGBge3IgZWNobyA9IFRSVUV9DQpkZiAlPiUgDQogIGNvdW50KFBjbGFzcykNCmBgYA0KDQoNCmBgYHtyIGVjaG8gPSBUUlVFfQ0KZ2dwbG90KGRhdGEgPSBkZikgKw0KICBnZW9tX2ZyZXFwb2x5KG1hcHBpbmcgPSBhZXMoeCA9IEZhcmUsIHkgPSAuLmRlbnNpdHkuLiwgY29sb3VyID0gZmFjdG9yKFBjbGFzcyksKSwNCiAgICAgICAgICAgICAgICBiaW53aWR0aCA9IDEwLA0KICAgICAgICAgICAgICAgIHNpemUgPSAwLjgpICsNCiAgZ2d0aXRsZSgiUGFyYSBjb21wYXJhciBkaXN0aW50YXMgZGlzdHJpYnVjaW9uZXM6IGdlb21fZnJlcXBvbHlcblNlIHB1ZWRlIHV0aWxpemFyIHkgPSAuLmRlbnNpdHkuLiBwYXJhIGhhY2VyIG3DoXMgY29tcGFyYWJsZXMgXG5sb3MgZ3J1cG9zIGNvbiBjYW50aWRhZCBkZSBvYnNlcnZhY2lvbmVzIG11eSBkaXN0aW50YXMuIikNCmBgYA0KDQojIyMgRW50cmUgdmFyaWFibGUgY2F0ZWfDs3JpY2EgeSBjb250aW51YTogZ2VvbV9ib3hwbG90DQoNCkhhc3RhIGFob3JhIGhlbW9zIHZpc3RvIHF1ZSBlbCBwcm9jZXNvIGRlIHZpc3VhbGl6YWNpw7NuIGltcGxpY2EgbXVjaGFzIHZlY2VzIHJlYWxpemFyIHRyYW5zZm9ybWFjaW9uZXMgZXN0YWRpc3RpY2FzIHBhcmEgc2ludGV0aXphciBsYSBpbmZvcm1hY2nDs24gZGUgbnVlc3Ryb3MgZGF0b3MgZW4gbcOpdHJpY2FzIG3DoXMgZmFjaWxlcyBkZSBjb21wcmVuZGVyLiBVbiBzaW1wbGUgZWplbXBsbyBkZSB0cmFuc2Zvcm1hY2nDs24gZXMgZWwgY29udGVvIGRlIG9ic2VydmFjaW9uZXMsIGVtcGxlYWRhIHBvciBsYXMgZnVuY2lvbmVzOiBgZ2VvbV9iYXJgLCBgZ2VvbV9oaXN0b2dyYW1gIHkgYGdlb21fZnJlcXBvbHlgLg0KDQpVbiBncsOhZmljbyBxdWUgdXRpbGl6YSBvdHJhcyB0cmFuc2Zvcm1hY2lvbmVzIGVzdGFkw61zdGljYXMgeSBzaXJ2ZSBwYXJhIGluc3BlY2Npb25hciBjb21vIGNvdmFyaWEgdW5hIHZhcmlhYmxlIGNvbnRpbnVhIGNvbiByZXNwZWN0byBhIHVuYSBjYXRlZ8OzcmljYSwgZXMgZWwgZ3LDoWZpY28gZGUgY2FqYXMgeSBiaWdvdGVzLCBvIG1lam9yIGNvbm9jaWRvIHBvciBzdSBub21icmUgZW4gaW5nbGVzIGNvbW8gKipib3hwbG90KiouDQoNCkNvbWVuY2Vtb3MgY3JlYW5kbyB1bmEgZGF0YSBkZSBqdWd1ZXRlIHBhcmEgZW50ZW5kZXIgbG9zIGNvbXBvbmVudGVzIGRlICoqYm94cGxvdCoqLg0KDQpgYGB7cn0NCiMgQ3JlYXIgdW4gZGF0YWZyYW1lIGEgcGFydGlyIGRlIHVuYSBzZWN1ZW5jaWEgcXVlIGNvbWllbnphIGVuIGVsIDEgeSB0w6lybWluYSBlbiBlbCAyNS4NCihkYXRhX2p1Z3VldGUgPC0gdGliYmxlKA0KICAgICAgICAgICAgICAgICAgICAgICAgaWQgPSAidmFyX2NhdGVnb3JpY2EiLA0KICAgICAgICAgICAgICAgICAgICAgICAgc2VxX251bSA9IHNlcSgxLCAyNSkNCiAgICAgICAgICAgICAgICAgICAgICAgICkNCikNCmBgYA0KDQoNCmBgYHtyIGVjaG8gPSBUUlVFfQ0KZ2dwbG90KGRhdGEgPSBkYXRhX2p1Z3VldGUpICsNCiAgZ2VvbV9ib3hwbG90KG1hcHBpbmcgPSBhZXMoeCA9IGlkLCB5ID0gc2VxX251bSkpICsNCiAgZ2d0aXRsZSgiR3LDoWZpY28gZGUgY2FqYS4iKQ0KYGBgDQoNCsK/UXXDqSBzaWduaWZpY2EgZXN0YSBjYWphPw0KDQpTw60gb3JkZW5hbW9zIGxvcyB2YWxvcmVzIGRlIGxhIHZhcmlhYmxlIGBzZXFfbnVtYCBkZSBtZW5vciBhIG1heW9yLCB5IG5vcyBwcmVndW50YW1vcyBjdWFsIGVzIGVsIHZhbG9yIHF1ZSBzZSBlbmN1ZW50cmEgZW4gZWwgMjUlLCA1MCUgeSA3NSUgZGUgbG9zIGRhdG9zLiBFbmNvbnRyYXJlbW9zIGluZm9ybWFjacOzbiBjbGF2ZSBwYXJhIGNvbXByZW5kZXIgZWwgKipib3hwbG90KiouDQoNClBvZGVtb3MgcmVzcG9uZGVyIGxhIGludGVycm9nYW50ZSBhbnRlcmlvciwgY2FsY3VsYW5kbyBmYWNpbG1lbnRlIGNvbiBsYSBmdW5jacOzbiBgcXVhbnRpbGVgIGxvcyB2YWxvcmVzIHF1ZSByZXByZXNlbnRhbiBsb3MgJSBzb2xpY2l0YWRvcy4NCg0KYGBge3J9DQpxdWFudGlsZShkYXRhX2p1Z3VldGUkc2VxX251bSkNCmBgYA0KDQpgYGB7ciBlY2hvID0gVFJVRX0NCmdncGxvdChkYXRhID0gZGF0YV9qdWd1ZXRlKSArDQogIGdlb21fYm94cGxvdChtYXBwaW5nID0gYWVzKHggPSBpZCwgeSA9IHNlcV9udW0pKSArDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDcsIGNvbG91ciA9ICJzdGVlbGJsdWUiLCBzaXplID0gMikgKyAgIyB2YWxvciBjb3JyZXNwb25kaWVudGUgYWwgMjUlIGRlIGxvcyBkYXRvcw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAxMywgY29sb3VyID0gIm9yYW5nZSIsIHNpemUgPSAyKSArICAgICMgdmFsb3IgY29ycmVzcG9uZGllbnRlIGFsIDUwJSBkZSBsb3MgZGF0b3MNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMTksIGNvbG91ciA9ICJyZWQiLCBzaXplID0gMikgKyAgICAgICAjIHZhbG9yIGNvcnJlc3BvbmRpZW50ZSBhbCA3NSUgZGUgbG9zIGRhdG9zDQogIGdndGl0bGUoIkdyw6FmaWNvIGRlIGNhamEgY29uIGzDrW5lYXMgaG9yaXpvbnRhbGVzIGRlc3RhY2FuZG9cbmxvcyB2YWxvcmVzIGRlbCBwcmltZXJvLCBzZWd1bmRvIHkgdGVyY2VyIGN1YXJ0aWwuIikNCmBgYA0KDQpFbCB0YW1hw7FvIGRlIGxhIGNhamEgc2UgbGUgY29ub2NlIGNvbW8gcmFuZ28gaW50ZXJjdWFydGlsIChJUVIpIHkgZXMgbGEgZGlmZXJlbmNpYSBlbnRyZSBlbCB0ZXJjZXIgKDc1JSkgeSBwcmltZXIgY3VhcnRpbCAoMjUlKSBkZSB1bmEgZGlzdHJpYnVjacOzbi4gTGEgbMOtbmVhIGhvcml6b250YWwgcXVlIGRpdmlkZSBwb3IgbGEgbWl0YWQgbGEgY2FqYSBlcyBlbCBlc3RhZMOtc3RpY28gY29ub2NpY28gY29tbyBsYSBtZWRpYW5hICg1MCUpLiBGdWVyYSBkZSBsYSBjYWphIHRlbmVtb3MgbG9zIGJpZ290ZXMgcXVlIHJlcHJlc2VudGFuIGxvcyB2YWxvcmVzIHF1ZSBzZSBlbmN1ZW50cmFuIGVuIGxhcyBjb2xhcyBpenF1aWVyZGEgeSBkZXJlY2hhIGRlIGxhIGRpc3RyaWJ1Y2nDs24uDQoNCmBgYHtyfQ0KIyBQb2RlbW9zIGNhbGN1bGFyIGVsIHJhbmdvIGludGVyY3VhcnRpbCBjb24gbGEgZnVuY2nDs246DQpJUVIoZGF0YV9qdWd1ZXRlJHNlcV9udW0pDQpgYGANCg0KTG8gcXVlIGVzIGVxdWl2YWxlbnRlIGE6DQpgYGB7cn0NCjE5IC0gNw0KYGBgDQoNCkxvcyBiaWdvdGVzIGRlbCBncsOhZmljbyBubyBzdXBlcmFuIHVuYSBkaXN0YW5jaWEgZGVzZGUgbG9zIHZhbG9yZXMgZGVsIGN1YXJ0aWwgMjUlIHkgNzUlICAkLS8rIDEuNSAqIFx0ZXh0e0lRUn0kDQoNCkxhIHZlbnRhamEgZGVsICoqYm94cGxvdCoqIGVzIHF1ZSBjb21wYWN0YSBsYSBkaXN0cmlidWNpw7NuIHF1ZSB2aW1vcyBhbnRlcmlvcm1lbnRlIHJlcHJlc2VudGFkYSBwb3IgdW4gKipoaXN0b2dyYW1hKiogZW4gZXN0YSBjYWphIGNvbiBiaWdvdGVzLiBFc3RvIG5vcyBwZXJtaXRlIGNvbXBhcmFyIGxhIGRpc3RyaWJ1Y2nDs24gZGUgdW5hIHZhcmlhYmxlIGNvbnRpbnVhIGNvbiByZXNwZWN0byBhIHVuYSB2YXJpYWJsZSBjYXRlZ29yaWNhLg0KDQpgYGB7ciBlY2hvPVRSVUV9DQpnZ3Bsb3QoZGF0YSA9IGRmKSArDQogIGdlb21fYm94cGxvdChtYXBwaW5nID0gYWVzKHggPSBmYWN0b3IoU2V4KSwgeSA9IEZhcmUsIGZpbGwgPSBmYWN0b3IoUGNsYXNzKSkpICsNCiAgZmFjZXRfd3JhcCh+IFBjbGFzcywgc2NhbGVzID0gImZyZWUiKSArDQogIHNjYWxlX2ZpbGxfdmlyaWRpc19kKCkgKw0KICBnZ3RpdGxlKCJEaXN0cmlidWNpw7NuIHRhcmlmYSBwYXNhamVyb3MgcG9yIHNleG8geSBjbGFzZS4iKQ0KYGBgDQoNCg0KTG9zIHB1bnRvcyBxdWUgb2JzZXJ2YW1vcyBtw6FzIGFsbMOhIGRlIGxvcyBiaWdvdGVzIHNlIGNvbnNpaWRlcmFuICpvdXRsaWVycyouIFNpbiBlbWJhcmdvLCBlc3RhIGVzIHVuYSBkZWZpbmljacOzbiBhcmJpdHJhcmlhLg0KDQojIyMgRW50cmUgdmFyaWFibGVzIGNhdGVnw7NyaWNhcw0KDQpgYGB7ciBlY2hvPVRSVUV9DQpnZ3Bsb3QoZGF0YSA9IGRmKSArDQogIGdlb21fY291bnQobWFwcGluZyA9IGFlcyh4ID0gZmFjdG9yKFN1cnZpdmVkKSwgeSA9IFNleCksDQogICAgICAgICAgICAgc2hhcGUgPSAyMSwNCiAgICAgICAgICAgICBmaWxsID0gIm9yYW5nZSIsDQogICAgICAgICAgICAgY29sb3VyID0gImJsYWNrIikgKw0KICB4bGFiKCJTdXJ2aXZlZCIpICsgDQogIGdndGl0bGUoIlBhcmEgb2JzZXJ2YXIgbGEgY292YXJhY2nDs24gZW50cmUgZG9zIHZhcmlhYmxlc1xuY2F0ZWfDs3JpY2FzOiBnZW9tX2NvdW50IikNCmBgYA0KDQpgYGB7ciBlY2hvPVRSVUV9DQpkZiAlPiUgDQogIGNvdW50KFNleCwgU3Vydml2ZWQpICU+JSANCiAgZ2dwbG90KG1hcHBpbmcgPSBhZXMoeCA9IGZhY3RvcihTdXJ2aXZlZCksIHkgPSBTZXgpKSArDQogIGdlb21fdGlsZShtYXBwaW5nID0gYWVzKGZpbGwgPSBuKSkgKw0KICBzY2FsZV9maWxsX3ZpcmlkaXNfYygpICsNCiAgeGxhYigiU3Vydml2ZWQiKSArDQogIGdndGl0bGUoIlBhcmEgb2JzZXJ2YXIgbGEgY292YXJpYWNpw7NuIGVudHJlIGRvcyB2YXJpYWJsZXNcbmNhdGVnw7NyaWNhczogZ2VvbV90aWxlIikNCmBgYA0KDQoNCg0KIyMjIEdyw6FmaWNvIGRlIGNvcnJlbGFjacOzbg0KDQpMYSBjb3JyZWxhY2nDs24gZXMgdW5hIG1lZGlkYSBwYXJhIHZlciBlbCBncmFkbyBkZSByZWxhY2nDs24gbGluZWFsIGVudHJlIGRvcyB2YXJpYWJsZXMuIEVsIHJhbmdvIHF1ZSBwdWVkZSB0b21hciBlc3RhIG3DqXRyaWNhIGVzIGRlIC0xIGEgMSwgc2llbmRvOg0KDQotIDE6IFVuYSByZWxhY2nDs24gbGluZWFsIHBvc2l0aXZhIHBlcmZlY3RhDQotIDA6IEF1c2VuY2lhIGRlIHJlbGFjacOzbiBsaW5lYWwNCi0gLTE6IFVuYSByZWxhY2nDs24gbGluZWFsIG5lZ2F0aXZhIHBlcmZlY3RhDQoNCg0KDQpgYGB7cn0NCmxpYnJhcnkocHVycnIpDQpsaWJyYXJ5KHRpZHlyKQ0KIyBTb2xvIHNlbGVjY2lvbmFyIGxhcyB2YXJpYWJsZXMgbsO6bWVyaWNhcy4NCnZhcl9jYXJhY3RlciA8LSBtYXBfbGdsKGRmLCBpcy5jaGFyYWN0ZXIpDQpkYXRvc19udW0gPC0gZGZbLCAhdmFyX2NhcmFjdGVyXQ0KIyBDcmVhciBtYXRyaXogZGUgY29ycmVsYWNpw7NuIHkgcmVkb25kZWFyIGFsIHNlZ3VuZG8gZGVjaW1hbC4NCmRhdG9zX2NvciA8LSBhc190aWJibGUoY29yKGRhdG9zX251bSwgdXNlID0gImNvbXBsZXRlLm9icyIpLA0KICAgICAgICAgICAgICAgICAgICAgICByb3duYW1lcyA9IE5BKQ0KDQojIENyZWFyIHVuYSB2YXJpYWJsZSBjb24gbG9zIG5vbWJyZXMgZGUgbGFzIHZhcmlhYmxlcy4NCmRhdG9zX2NvciR2YXIxIDwtIG5hbWVzKGRhdG9zX251bSkNCg0KIyBUcmFzcG9uZXIgbGEgdGFibGEsIGRlamFuZG8gbG9zIG5vbWJyZXMgZGUgbGFzIGNvbHVtbmFzDQojIGNvbW8gdW5hIHZhcmlhYmxlDQpkYXRvc19jb3IgPC0gdGlkeXI6OmdhdGhlcihkYXRvc19jb3IsICJ2YXIyIiwgImNvciIsIC12YXIxKQ0KZ2dwbG90KGRhdGEgPSBkYXRvc19jb3IpICsNCiAgZ2VvbV90aWxlKG1hcHBpbmcgPSBhZXMoeCA9IHZhcjEsIHkgPSB2YXIyLCBmaWxsID0gY29yKSkgKw0KICBzY2FsZV9maWxsX3ZpcmlkaXNfYygpICsNCiAgZ2d0aXRsZSgiR3LDoWZpY28gZGUgY29ycmVsYWNpw7NuIGVudHJlIGxhcyB2YXJpYWJsZXMgbsO6bWVyaWNhcyIpICsNCiAgeGxhYihOVUxMKSArICAjIEVsaW1pbmFyIGV0aXF1ZXRhIGRlbCBlamUgeA0KICB5bGFiKE5VTEwpICAgICMgRWxpbWluYXIgZXRpcXVldGEgZGVsIGVqZSB5DQpgYGANCg0KDQoNCg0K