Visualización de datos con ggplot

En esta primera parte enseñaré las funciones básicas de ggplot2 y a cómo visualizar nuestros datos utilizando este paquete. Para ello utilizaremos los paquetes “ggplot2” y “tidyverse” (éste último es donde se encuentra la base de datos “mpg” que utilizaremos para este ejercicio)

## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.1     ✔ readr     2.1.4
## ✔ forcats   1.0.0     ✔ stringr   1.5.0
## ✔ ggplot2   3.4.2     ✔ tibble    3.2.1
## ✔ lubridate 1.9.2     ✔ tidyr     1.3.0
## ✔ purrr     1.0.1     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
## # A tibble: 234 × 11
##    manufacturer model      displ  year   cyl trans drv     cty   hwy fl    class
##    <chr>        <chr>      <dbl> <int> <int> <chr> <chr> <int> <int> <chr> <chr>
##  1 audi         a4           1.8  1999     4 auto… f        18    29 p     comp…
##  2 audi         a4           1.8  1999     4 manu… f        21    29 p     comp…
##  3 audi         a4           2    2008     4 manu… f        20    31 p     comp…
##  4 audi         a4           2    2008     4 auto… f        21    30 p     comp…
##  5 audi         a4           2.8  1999     6 auto… f        16    26 p     comp…
##  6 audi         a4           2.8  1999     6 manu… f        18    26 p     comp…
##  7 audi         a4           3.1  2008     6 auto… f        18    27 p     comp…
##  8 audi         a4 quattro   1.8  1999     4 manu… 4        18    26 p     comp…
##  9 audi         a4 quattro   1.8  1999     4 auto… 4        16    25 p     comp…
## 10 audi         a4 quattro   2    2008     4 manu… 4        20    28 p     comp…
## # ℹ 224 more rows

Vamos a trabajar con los datos de “mpg”. Las variables “displ” y “hwy” representan el tamaño del motor (en litros) y la eficiencia del uso del combustible (en millas por galón “mpg”)

Cada gráfico se realiza utilizando la función ggplot() cuyo primer argumento (“data”) es el set de datos donde se encuentran las variables a graficar. Sin embargo, la función por sí sola no grafica, sino que necesita funciones de mapeo. Estas funciones especifican el tipo de gráfico que realizará (por ejemplo: geom_point() es la función para realizar un scatterplot). Además, la función de mapeo SIEMPRE debe contener el argumento mapping que SIEMPRE será igual a la función aes() la cual contiene las variables que se plotearán (y todo lo relacionado con ellas).

EJEMPLO 1:

ggplot(data=datos) + geom_point( mapping = aes( x=var1, y=var2))

ggplot(data = mpg)+
  geom_point(mapping = aes(x=displ, y=hwy))

Dentro de la función aes() se realiza todo lo relacionado a las variables (cuáles se utilizarán, la forma en la que se plotearán, el color, transparencia, tamaño, forma, etc). Todas estas características pueden cambiarse de acuerdo a una tercera variable como es la clase a la que pertenecen las variabes ploteadas.

EJEMPLO 2:

ggplot(data=datos) + geom_point( mapping = aes( x=var1, y=var2, color=class)) -> color

ggplot(data=datos) + geom_point( mapping = aes( x=var1, y=var2, size=class)) -> tamaño

ggplot(data=datos) + geom_point( mapping = aes( x=var1, y=var2, shape=class)) -> forma

ggplot(data=datos) + geom_point( mapping = aes( x=var1, y=var2, alpha=class)) -> transparencia

#color
ggplot(data=mpg)+
  geom_point(mapping = aes(x=displ, y=hwy,color=class))

#tamaño
ggplot(data=mpg)+geom_point(mapping = aes(x=displ, y=hwy, size= class))

#La advertencia que se muestra es que estamos asignando "tamaño" a una variable discreta (class) lo que no tiene mucho sentido (tener en cuenta que esto es un ejemplo ilustrativo).

#transparencia
ggplot(data=mpg)+geom_point(mapping = aes(x=displ, y=hwy, alpha=class))

#forma
ggplot(data=mpg)+geom_point(mapping = aes(x=displ, y=hwy, shape=class))

#NOTA IMPORTANTE: Al usar diferentes formas, R solo utiliza 6 a la vez por lo que si hay más de 6 clases solo ploteará las seis primeras

También podemos graficar los datos de acuerdo a condiciones que definamos sobre los datos. Por ejemplo, a continuación graficaré los datos previos, pero con la condición de que los que posean valores menores a 30 en la variable “hwy” tengan una coloración diferente a datos con valores mayores.

ggplot(data=mpg)+
  geom_point(mapping = aes(x=displ, y=hwy,color=hwy<30))

“Facets” es una forma de añadir nuevas variables al gráfico. La función “facet_wrap” se usa para añadir una sola variable, cuyo argumento es la variable en cuestión precedida de ~. Simplemente se debe encadenar al resto de la función ggplot() con el signo +.

ggplot(data=mpg)+ geom_point(mapping=aes(x=displ, y=hwy))+
  facet_wrap(~class, nrow=2)

Para añadir varias variables se utiliza la función “facet_grid”. En este caso el argumento debe contener las variables que se incluyen separadas por ~

ggplot(data=mpg)+geom_point(mapping = aes(x=displ, y=hwy))+
  facet_grid(~drv)

Geometric Objects

geom_ puede ser seguido de diferentes argumentos en dependencia de cómo quieras graficar tus datos (geom_point, geom_smooth, etc). Todos estas funciones se conocen como “Geometric Objects”

Para añadir varios “geom_” simplemente añade diferentes tipos con “+”.

ggplot(data=mpg)+
  geom_point(mapping = aes(x=displ, y=hwy, color=drv))+
  geom_smooth(mapping = aes(x=displ, y=hwy, color=drv))
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'

Otra variante para hacer esto es agrupar el parámetro aes() dentro de ggplot (dado que las variables son las mismas) y solo mantener en “geom_” la variable que nos interese destacar:

ggplot(data=mpg, mapping = aes(x=displ, y=hwy))+
  geom_point(mapping = aes(color=class))+
  geom_smooth()
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'

También se puede especificar si se desea plotear solo una parte de los datos, o determinadas clases.

Con la función “filter” en este ejemplo se seleccionó solamente la clase “2seater” para ser ploteada en “geom_smooth”. El parámetro “se=False” es en el caso de “geom_smooth” para no mostrar la desviación estándar

ggplot(data=mpg, mapping = aes(x=displ, y=hwy))+
  geom_point(mapping = aes(color=class))+
  geom_smooth(data=filter(mpg, class=="2seater"), se = FALSE)
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'

Otros ejemplos:

ggplot(data = mpg,mapping = aes(x=displ, y=hwy))+
  geom_point(mapping = aes(color=drv))+
  geom_smooth(mapping = aes(linetype=drv, color=drv),se = FALSE)
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'

ggplot(data=mpg, mapping = aes(x=displ, y=hwy))+
  geom_point(mapping = aes(color=drv))+
  geom_smooth(se=FALSE)
## `geom_smooth()` using method = 'loess' and formula = 'y ~ x'

Transformaciones estadísticas

En esta segunda parte me enfocaré en algunas transformaciones estadísticas sencillas, en algunas funciones interesantes que utilizar a la hora de graficar y de otros tipos de gráficos que se pueden realizar con R.

En este caso utilizaremos la base de datos “diamonds”, así como los paquetes utilizados en la parte 1 (“tidyverse” y “ggplot2”)

## # A tibble: 53,940 × 10
##    carat cut       color clarity depth table price     x     y     z
##    <dbl> <ord>     <ord> <ord>   <dbl> <dbl> <int> <dbl> <dbl> <dbl>
##  1  0.23 Ideal     E     SI2      61.5    55   326  3.95  3.98  2.43
##  2  0.21 Premium   E     SI1      59.8    61   326  3.89  3.84  2.31
##  3  0.23 Good      E     VS1      56.9    65   327  4.05  4.07  2.31
##  4  0.29 Premium   I     VS2      62.4    58   334  4.2   4.23  2.63
##  5  0.31 Good      J     SI2      63.3    58   335  4.34  4.35  2.75
##  6  0.24 Very Good J     VVS2     62.8    57   336  3.94  3.96  2.48
##  7  0.24 Very Good I     VVS1     62.3    57   336  3.95  3.98  2.47
##  8  0.26 Very Good H     SI1      61.9    55   337  4.07  4.11  2.53
##  9  0.22 Fair      E     VS2      65.1    61   337  3.87  3.78  2.49
## 10  0.23 Very Good H     VS1      59.4    61   338  4     4.05  2.39
## # ℹ 53,930 more rows

En este caso ploteamos en un gráfico de barras solamente una variable. Por defecto, estos gráficos grafican el conteo de cada categoría (parámetro “..count..”) en ausencia de una segunda variable.

ggplot(data=diamonds)+
  geom_bar(mapping = aes(x=cut))

#En este ejemplo se grafica en el eje y la proporción de cada categoría de la variable x (se utiliza "..prop..")
ggplot(data=diamonds)+
  geom_bar(mapping = aes(x=cut, y= ..prop.., group=1))

También se pueden utilizar otras funciones como “stat_summary” que generaliza los valores de x para cada valor de x. La ventaja de esta función es que puedes introducir funciones para especificar qué graficar.

ggplot(data=diamonds)+
  stat_summary(mapping = aes(x=cut, y=depth),
               fun.ymin = min,
               fun.ymax = max,
               fun.y=median)

IMPORTANTE Si no se incluye group = 1, entonces todas las barras en el gráfico tendrán la misma altura, una altura de 1. La función geom_bar() asume que los grupos son iguales a los valores x ya que la estadística calcula los conteos dentro del grupo .

ggplot(data = diamonds) +
  geom_bar(mapping = aes(x = cut, y = ..prop.., group=1))

Ajustes de “fill”

En este caso cuando se utiliza la función “fill” para colorear cada barra en relación a su altura relativa, la variable “y” debe estar normalizada de forma manual dado que “..prop..” calcula el porcentaje dentro del grupo. Necesita una variable de agrupación; de lo contrario, cada x es su propio grupo y prop = 1 que es 100%, para cada x.

ggplot(data = diamonds) +
  geom_bar(
    mapping = aes(x = cut, fill=color, y = ..count../sum(..count..) ) )

Otra forma de hacer lo mismo es simplemente en el eje y plotear “..count..”.

ggplot(data = diamonds) +
  geom_bar(mapping = aes(x = cut, fill=color, y = ..count..))

Se pueden colorear las barras de los gráficos de acuerdo a las variables. Si se utiliza la función “color” solo se colorea el contorno.

ggplot(data=diamonds)+
  geom_bar(mapping = aes(x=cut, color=cut))

Para colorear cada barra se debe usar “fill”.

ggplot(data=diamonds)+
  geom_bar(mapping = aes(x=cut, fill=cut))

Si se utiliza como argumento de la función “fill” otra variable, esta se representa dentro de cada categoría#de la variable x como un rectángulo.

ggplot(data=diamonds)+
  geom_bar(mapping = aes(x=cut, fill=clarity))

Ajustes de “position”

El parámetro “position” dentro de “geom_bar” se puede modificar a 3 valores en dependencia de lo que se quiera:

1.- “identity” no se suele usar en barplots, es más común en scatterplots. Esto no es muy útil para las barras, porque las superpone. Para ver esa superposición, debemos hacer que las barras sean ligeramente transparentes configurando alfa en un valor pequeño, o completamente transparentes configurando “fill = NA”.

ggplot(data=diamonds, mapping = aes(x=cut, fill=clarity))+
  geom_bar(alpha=1/5, position="identity")

2.- “fill” hace que todas las barras tengan la misma altura, lo que puede ser útil para comparar proporciones

ggplot(data=diamonds)+
  geom_bar(mapping = aes(x=cut, fill=clarity), position="fill")

3.- “dodge” coloca los objetos superpuestos uno al lado del otro, siendo más sencillo comparar los valores individuales

ggplot(data=diamonds)+
  geom_bar(mapping=aes(x=cut, fill= clarity), position="dodge")

Otro ajuste, esta vez para scatterplots, consiste en eliminar la forma ordenada del siguiente gráfico. Por defecto, trata de ordenar los puntos colocándolos en una cuadrícula, provocando que haya mucha superposición y que no se observen todos los puntos existentes.

ggplot(data=mpg)+
  geom_point(mapping = aes(x=displ, y=hwy))

Para ello se añade position=“jitter” que dispersa los puntos y ayuda a visualizarlos mejor. También tiene su proia forma abreviada como “geom_jitter()”. “geom_jitter()” tiene los parámetros “height” y “width” que controlan la dispersión de los puntos en los planos x e y.

NOTA IMPORTANTE: Este parámetro añade ruido al plot y hace que los puntos no aparezcan en su posición exacta ya que evita la superposición. A pequeña escala hace el gráfico más inexacto, pero a gran escala mejora la visualización de los datos

ggplot(data=mpg)+
  geom_point(mapping = aes(x=displ, y=hwy), position="jitter")

ggplot(data=mpg)+
  geom_jitter(mapping = aes(x=displ, y=hwy))

ggplot(data=mpg)+
  geom_jitter(mapping = aes(x=displ, y=hwy), height = 5, width = 3)

Sistema de coordenadas

Aquí mostraré algunas funciones útiles referidas a los sistemas de coordenadas.

    • coord_flip() intercambia los ejex x e y. Útil, por ejemplo, para boxplots horizontales .
ggplot(data=mpg, mapping = aes(x=class, y=hwy))+
  geom_boxplot()

ggplot(data=mpg, mapping = aes(x=class, y=hwy))+
  geom_boxplot()+
  coord_flip()

    • coord_quickmap() te muestra las dimensiones correctas de mapas. Útil para datos espaciales.
nz <- map_data("nz")
ggplot(nz, aes(long, lat, group = group)) +
  geom_polygon(fill = "white", color = "black")

ggplot(nz, aes(long, lat, group = group)) +
  geom_polygon(fill = "white", color = "black") +
  coord_quickmap()

    • coord_polar() usa coordenadas polares. Otro tipo de gráfico.
bar<- ggplot(data=diamonds)+
  geom_bar(mapping = aes(x=cut, fill=cut), show.legend = FALSE, width = 1)+
  theme(aspect.ratio = 1)+
  labs(x=NULL, y=NULL)

bar+coord_flip()

bar+coord_polar()

LS0tDQp0aXRsZTogIlZpc3VhbGl6YWNpw7NuIGRlIGRhdG9zIGNvbiBnZ3Bsb3QyIg0KYXV0aG9yOiAiQWxlamFuZHJvIEpvc8OpIEfDs21leiBHYXJjw61hIg0KZGF0ZTogIjE1LzUvMjAyMiINCmVtYWlsOiBhbGVqYW5kcm9yZXg5NUBnbWFpbC5jb20NCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IFRSVUUNCiAgICB0b2NfZmxvYXQ6IFRSVUUNCiAgICBjb2RlX2Rvd25sb2FkOiBUUlVFDQogICAgdGhlbWU6IHVuaXRlZA0KICBjc2w6IGFwYS5jc2wNCi0tLQ0KDQojIyBWaXN1YWxpemFjacOzbiBkZSBkYXRvcyBjb24gZ2dwbG90DQoNCkVuIGVzdGEgcHJpbWVyYSBwYXJ0ZSBlbnNlw7FhcsOpIGxhcyBmdW5jaW9uZXMgYsOhc2ljYXMgZGUgZ2dwbG90MiB5IGEgY8OzbW8gdmlzdWFsaXphciBudWVzdHJvcyBkYXRvcyB1dGlsaXphbmRvIGVzdGUgcGFxdWV0ZS4NClBhcmEgZWxsbyB1dGlsaXphcmVtb3MgbG9zIHBhcXVldGVzICJnZ3Bsb3QyIiB5ICJ0aWR5dmVyc2UiICjDqXN0ZSDDumx0aW1vIGVzIGRvbmRlIHNlIGVuY3VlbnRyYSBsYSBiYXNlIGRlIGRhdG9zICJtcGciIHF1ZSB1dGlsaXphcmVtb3MgcGFyYSBlc3RlIGVqZXJjaWNpbykNCg0KYGBge3IsIGVjaG89RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoZ2dwbG90MikNCg0KbXBnICAjQmFzZSBkZSBkYXRvcw0KYGBgDQoNClZhbW9zIGEgdHJhYmFqYXIgY29uIGxvcyBkYXRvcyBkZSAibXBnIi4NCkxhcyB2YXJpYWJsZXMgImRpc3BsIiB5ICJod3kiIHJlcHJlc2VudGFuIGVsIHRhbWHDsW8gZGVsIG1vdG9yIChlbiBsaXRyb3MpIHkgbGEgZWZpY2llbmNpYSBkZWwgdXNvIGRlbCBjb21idXN0aWJsZSAoZW4gbWlsbGFzIHBvciBnYWzDs24gIm1wZyIpDQoNCkNhZGEgZ3LDoWZpY28gc2UgcmVhbGl6YSB1dGlsaXphbmRvIGxhIGZ1bmNpw7NuIGdncGxvdCgpIGN1eW8gcHJpbWVyIGFyZ3VtZW50byAoImRhdGEiKSBlcyBlbCBzZXQgZGUgZGF0b3MgZG9uZGUgc2UgZW5jdWVudHJhbiBsYXMgdmFyaWFibGVzIGEgZ3JhZmljYXIuIFNpbiBlbWJhcmdvLCBsYSBmdW5jacOzbiBwb3Igc8OtIHNvbGEgbm8gZ3JhZmljYSwgc2lubyBxdWUgbmVjZXNpdGEgZnVuY2lvbmVzIGRlIG1hcGVvLiBFc3RhcyBmdW5jaW9uZXMgZXNwZWNpZmljYW4gZWwgdGlwbyBkZSBncsOhZmljbyBxdWUgcmVhbGl6YXLDoSAocG9yIGVqZW1wbG86IGdlb21fcG9pbnQoKSBlcyBsYSBmdW5jacOzbiBwYXJhIHJlYWxpemFyIHVuIHNjYXR0ZXJwbG90KS4gQWRlbcOhcywgbGEgZnVuY2nDs24gZGUgbWFwZW8gU0lFTVBSRSBkZWJlIGNvbnRlbmVyIGVsIGFyZ3VtZW50byBtYXBwaW5nIHF1ZSBTSUVNUFJFIHNlcsOhIGlndWFsIGEgbGEgZnVuY2nDs24gYWVzKCkgbGEgY3VhbCBjb250aWVuZSBsYXMgdmFyaWFibGVzIHF1ZSBzZSBwbG90ZWFyw6FuICh5IHRvZG8gbG8gcmVsYWNpb25hZG8gY29uIGVsbGFzKS4NCg0KRUpFTVBMTyAxOg0KDQpnZ3Bsb3QoZGF0YT1kYXRvcykgKyBnZW9tX3BvaW50KCBtYXBwaW5nID0gYWVzKCB4PXZhcjEsIHk9dmFyMikpDQoNCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQ0KZ2dwbG90KGRhdGEgPSBtcGcpKw0KICBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoeD1kaXNwbCwgeT1od3kpKQ0KYGBgDQoNCkRlbnRybyBkZSBsYSBmdW5jacOzbiBhZXMoKSBzZSByZWFsaXphIHRvZG8gbG8gcmVsYWNpb25hZG8gYSBsYXMgdmFyaWFibGVzIChjdcOhbGVzIHNlIHV0aWxpemFyw6FuLCBsYSBmb3JtYSBlbiBsYSBxdWUgc2UgcGxvdGVhcsOhbiwgZWwgY29sb3IsIHRyYW5zcGFyZW5jaWEsIHRhbWHDsW8sIGZvcm1hLCBldGMpLiBUb2RhcyBlc3RhcyBjYXJhY3RlcsOtc3RpY2FzIHB1ZWRlbiBjYW1iaWFyc2UgZGUgYWN1ZXJkbyBhIHVuYSB0ZXJjZXJhIHZhcmlhYmxlIGNvbW8gZXMgbGEgY2xhc2UgYSBsYSBxdWUgcGVydGVuZWNlbiBsYXMgdmFyaWFiZXMgcGxvdGVhZGFzLg0KDQpFSkVNUExPIDI6DQoNCmdncGxvdChkYXRhPWRhdG9zKSArIGdlb21fcG9pbnQoIG1hcHBpbmcgPSBhZXMoIHg9dmFyMSwgeT12YXIyLCBjb2xvcj1jbGFzcykpIC0+IGNvbG9yDQoNCmdncGxvdChkYXRhPWRhdG9zKSArIGdlb21fcG9pbnQoIG1hcHBpbmcgPSBhZXMoIHg9dmFyMSwgeT12YXIyLCBzaXplPWNsYXNzKSkgLT4gdGFtYcOxbw0KDQpnZ3Bsb3QoZGF0YT1kYXRvcykgKyBnZW9tX3BvaW50KCBtYXBwaW5nID0gYWVzKCB4PXZhcjEsIHk9dmFyMiwgc2hhcGU9Y2xhc3MpKSAtPiBmb3JtYQ0KDQpnZ3Bsb3QoZGF0YT1kYXRvcykgKyBnZW9tX3BvaW50KCBtYXBwaW5nID0gYWVzKCB4PXZhcjEsIHk9dmFyMiwgYWxwaGE9Y2xhc3MpKSAtPiB0cmFuc3BhcmVuY2lhDQoNCg0KYGBge3IsIHdhcm5pbmc9RkFMU0V9DQojY29sb3INCmdncGxvdChkYXRhPW1wZykrDQogIGdlb21fcG9pbnQobWFwcGluZyA9IGFlcyh4PWRpc3BsLCB5PWh3eSxjb2xvcj1jbGFzcykpDQoNCiN0YW1hw7FvDQpnZ3Bsb3QoZGF0YT1tcGcpK2dlb21fcG9pbnQobWFwcGluZyA9IGFlcyh4PWRpc3BsLCB5PWh3eSwgc2l6ZT0gY2xhc3MpKQ0KI0xhIGFkdmVydGVuY2lhIHF1ZSBzZSBtdWVzdHJhIGVzIHF1ZSBlc3RhbW9zIGFzaWduYW5kbyAidGFtYcOxbyIgYSB1bmEgdmFyaWFibGUgZGlzY3JldGEgKGNsYXNzKSBsbyBxdWUgbm8gdGllbmUgbXVjaG8gc2VudGlkbyAodGVuZXIgZW4gY3VlbnRhIHF1ZSBlc3RvIGVzIHVuIGVqZW1wbG8gaWx1c3RyYXRpdm8pLg0KDQojdHJhbnNwYXJlbmNpYQ0KZ2dwbG90KGRhdGE9bXBnKStnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoeD1kaXNwbCwgeT1od3ksIGFscGhhPWNsYXNzKSkNCg0KI2Zvcm1hDQpnZ3Bsb3QoZGF0YT1tcGcpK2dlb21fcG9pbnQobWFwcGluZyA9IGFlcyh4PWRpc3BsLCB5PWh3eSwgc2hhcGU9Y2xhc3MpKQ0KI05PVEEgSU1QT1JUQU5URTogQWwgdXNhciBkaWZlcmVudGVzIGZvcm1hcywgUiBzb2xvIHV0aWxpemEgNiBhIGxhIHZleiBwb3IgbG8gcXVlIHNpIGhheSBtw6FzIGRlIDYgY2xhc2VzIHNvbG8gcGxvdGVhcsOhIGxhcyBzZWlzIHByaW1lcmFzDQpgYGANCg0KDQpUYW1iacOpbiBwb2RlbW9zIGdyYWZpY2FyIGxvcyBkYXRvcyBkZSBhY3VlcmRvIGEgY29uZGljaW9uZXMgcXVlIGRlZmluYW1vcyBzb2JyZSBsb3MgZGF0b3MuIFBvciBlamVtcGxvLCBhIGNvbnRpbnVhY2nDs24gZ3JhZmljYXLDqSBsb3MgZGF0b3MgcHJldmlvcywgcGVybyBjb24gbGEgY29uZGljacOzbiBkZSBxdWUgbG9zIHF1ZSBwb3NlYW4gdmFsb3JlcyBtZW5vcmVzIGEgMzAgZW4gbGEgdmFyaWFibGUgImh3eSIgdGVuZ2FuIHVuYSBjb2xvcmFjacOzbiBkaWZlcmVudGUgYSBkYXRvcyBjb24gdmFsb3JlcyBtYXlvcmVzLg0KDQpgYGB7ciwgd2FybmluZz1GQUxTRX0NCmdncGxvdChkYXRhPW1wZykrDQogIGdlb21fcG9pbnQobWFwcGluZyA9IGFlcyh4PWRpc3BsLCB5PWh3eSxjb2xvcj1od3k8MzApKQ0KYGBgDQoNCiJGYWNldHMiIGVzIHVuYSBmb3JtYSBkZSBhw7FhZGlyIG51ZXZhcyB2YXJpYWJsZXMgYWwgZ3LDoWZpY28uIExhIGZ1bmNpw7NuICJmYWNldF93cmFwIiBzZSB1c2EgcGFyYSBhw7FhZGlyIHVuYSBzb2xhIHZhcmlhYmxlLCBjdXlvIGFyZ3VtZW50byBlcyBsYSB2YXJpYWJsZSBlbiBjdWVzdGnDs24gcHJlY2VkaWRhIGRlIH4uIFNpbXBsZW1lbnRlIHNlIGRlYmUgZW5jYWRlbmFyIGFsIHJlc3RvIGRlIGxhIGZ1bmNpw7NuIGdncGxvdCgpIGNvbiBlbCBzaWdubyArLg0KDQpgYGB7ciwgd2FybmluZz1GQUxTRX0NCmdncGxvdChkYXRhPW1wZykrIGdlb21fcG9pbnQobWFwcGluZz1hZXMoeD1kaXNwbCwgeT1od3kpKSsNCiAgZmFjZXRfd3JhcCh+Y2xhc3MsIG5yb3c9MikNCmBgYA0KDQpQYXJhIGHDsWFkaXIgdmFyaWFzIHZhcmlhYmxlcyBzZSB1dGlsaXphIGxhIGZ1bmNpw7NuICJmYWNldF9ncmlkIi4gRW4gZXN0ZSBjYXNvIGVsIGFyZ3VtZW50byBkZWJlIGNvbnRlbmVyIGxhcyB2YXJpYWJsZXMgcXVlIHNlIGluY2x1eWVuIHNlcGFyYWRhcyBwb3Igfg0KDQpgYGB7ciwgd2FybmluZz1GQUxTRX0NCmdncGxvdChkYXRhPW1wZykrZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHg9ZGlzcGwsIHk9aHd5KSkrDQogIGZhY2V0X2dyaWQofmRydikNCmBgYA0KDQoNCiMjIEdlb21ldHJpYyBPYmplY3RzDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgDQpnZW9tXyBwdWVkZSBzZXIgc2VndWlkbyBkZSBkaWZlcmVudGVzIGFyZ3VtZW50b3MgZW4gZGVwZW5kZW5jaWEgZGUgY8OzbW8gcXVpZXJhcyBncmFmaWNhciB0dXMgZGF0b3MgKGdlb21fcG9pbnQsIGdlb21fc21vb3RoLCBldGMpLiBUb2RvcyBlc3RhcyBmdW5jaW9uZXMgc2UgY29ub2NlbiBjb21vICJHZW9tZXRyaWMgT2JqZWN0cyINCg0KUGFyYSBhw7FhZGlyIHZhcmlvcyAiZ2VvbV8iIHNpbXBsZW1lbnRlIGHDsWFkZSBkaWZlcmVudGVzIHRpcG9zIGNvbiAiKyIuDQoNCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQ0KZ2dwbG90KGRhdGE9bXBnKSsNCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHg9ZGlzcGwsIHk9aHd5LCBjb2xvcj1kcnYpKSsNCiAgZ2VvbV9zbW9vdGgobWFwcGluZyA9IGFlcyh4PWRpc3BsLCB5PWh3eSwgY29sb3I9ZHJ2KSkNCmBgYA0KDQpPdHJhIHZhcmlhbnRlIHBhcmEgaGFjZXIgZXN0byBlcyBhZ3J1cGFyIGVsIHBhcsOhbWV0cm8gYWVzKCkgZGVudHJvIGRlIGdncGxvdCAoZGFkbyBxdWUgbGFzIHZhcmlhYmxlcyBzb24gbGFzIG1pc21hcykgeSBzb2xvIG1hbnRlbmVyIGVuICJnZW9tXyIgbGEgdmFyaWFibGUgcXVlIG5vcyBpbnRlcmVzZSBkZXN0YWNhcjoNCg0KYGBge3IsIHdhcm5pbmc9RkFMU0V9DQpnZ3Bsb3QoZGF0YT1tcGcsIG1hcHBpbmcgPSBhZXMoeD1kaXNwbCwgeT1od3kpKSsNCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKGNvbG9yPWNsYXNzKSkrDQogIGdlb21fc21vb3RoKCkNCmBgYA0KDQpUYW1iacOpbiBzZSBwdWVkZSBlc3BlY2lmaWNhciBzaSBzZSBkZXNlYSBwbG90ZWFyIHNvbG8gdW5hIHBhcnRlIGRlIGxvcyBkYXRvcywgbyBkZXRlcm1pbmFkYXMgY2xhc2VzLg0KDQpDb24gbGEgZnVuY2nDs24gImZpbHRlciIgZW4gZXN0ZSBlamVtcGxvIHNlIHNlbGVjY2lvbsOzIHNvbGFtZW50ZSBsYSBjbGFzZSAiMnNlYXRlciIgcGFyYSBzZXIgcGxvdGVhZGEgZW4gImdlb21fc21vb3RoIi4gRWwgcGFyw6FtZXRybyAic2U9RmFsc2UiIGVzIGVuIGVsIGNhc28gZGUgImdlb21fc21vb3RoIiBwYXJhIG5vIG1vc3RyYXIgbGEgZGVzdmlhY2nDs24gZXN0w6FuZGFyDQoNCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQ0KZ2dwbG90KGRhdGE9bXBnLCBtYXBwaW5nID0gYWVzKHg9ZGlzcGwsIHk9aHd5KSkrDQogIGdlb21fcG9pbnQobWFwcGluZyA9IGFlcyhjb2xvcj1jbGFzcykpKw0KICBnZW9tX3Ntb290aChkYXRhPWZpbHRlcihtcGcsIGNsYXNzPT0iMnNlYXRlciIpLCBzZSA9IEZBTFNFKQ0KYGBgDQoNCk90cm9zIGVqZW1wbG9zOg0KDQpgYGB7ciwgd2FybmluZz1GQUxTRX0NCmdncGxvdChkYXRhID0gbXBnLG1hcHBpbmcgPSBhZXMoeD1kaXNwbCwgeT1od3kpKSsNCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKGNvbG9yPWRydikpKw0KICBnZW9tX3Ntb290aChtYXBwaW5nID0gYWVzKGxpbmV0eXBlPWRydiwgY29sb3I9ZHJ2KSxzZSA9IEZBTFNFKQ0KDQpnZ3Bsb3QoZGF0YT1tcGcsIG1hcHBpbmcgPSBhZXMoeD1kaXNwbCwgeT1od3kpKSsNCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKGNvbG9yPWRydikpKw0KICBnZW9tX3Ntb290aChzZT1GQUxTRSkNCg0KYGBgDQoNCiMjIFRyYW5zZm9ybWFjaW9uZXMgZXN0YWTDrXN0aWNhcw0KDQpFbiBlc3RhIHNlZ3VuZGEgcGFydGUgbWUgZW5mb2NhcsOpIGVuIGFsZ3VuYXMgdHJhbnNmb3JtYWNpb25lcyBlc3RhZMOtc3RpY2FzIHNlbmNpbGxhcywgZW4gYWxndW5hcyBmdW5jaW9uZXMgaW50ZXJlc2FudGVzIHF1ZSB1dGlsaXphciBhIGxhIGhvcmEgZGUgZ3JhZmljYXIgeSBkZSBvdHJvcyB0aXBvcyBkZSBncsOhZmljb3MgcXVlIHNlIHB1ZWRlbiByZWFsaXphciBjb24gUi4NCg0KRW4gZXN0ZSBjYXNvIHV0aWxpemFyZW1vcyBsYSBiYXNlIGRlIGRhdG9zICJkaWFtb25kcyIsIGFzw60gY29tbyBsb3MgcGFxdWV0ZXMgdXRpbGl6YWRvcyBlbiBsYSBwYXJ0ZSAxICgidGlkeXZlcnNlIiB5ICJnZ3Bsb3QyIikNCg0KYGBge3IsIGVjaG89RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoZ2dwbG90MikNCg0KZGlhbW9uZHMgICNCYXNlIGRlIGRhdG9zDQpgYGANCg0KRW4gZXN0ZSBjYXNvIHBsb3RlYW1vcyBlbiB1biBncsOhZmljbyBkZSBiYXJyYXMgc29sYW1lbnRlIHVuYSB2YXJpYWJsZS4gUG9yIGRlZmVjdG8sIGVzdG9zIGdyw6FmaWNvcyBncmFmaWNhbiBlbCBjb250ZW8gZGUgY2FkYSBjYXRlZ29yw61hIChwYXLDoW1ldHJvICIuLmNvdW50Li4iKSBlbiBhdXNlbmNpYSBkZSB1bmEgc2VndW5kYSB2YXJpYWJsZS4NCg0KYGBge3IgICwgd2FybmluZz1GQUxTRX0gDQoNCmdncGxvdChkYXRhPWRpYW1vbmRzKSsNCiAgZ2VvbV9iYXIobWFwcGluZyA9IGFlcyh4PWN1dCkpDQoNCiNFbiBlc3RlIGVqZW1wbG8gc2UgZ3JhZmljYSBlbiBlbCBlamUgeSBsYSBwcm9wb3JjacOzbiBkZSBjYWRhIGNhdGVnb3LDrWEgZGUgbGEgdmFyaWFibGUgeCAoc2UgdXRpbGl6YSAiLi5wcm9wLi4iKQ0KZ2dwbG90KGRhdGE9ZGlhbW9uZHMpKw0KICBnZW9tX2JhcihtYXBwaW5nID0gYWVzKHg9Y3V0LCB5PSAuLnByb3AuLiwgZ3JvdXA9MSkpDQpgYGANCg0KVGFtYmnDqW4gc2UgcHVlZGVuIHV0aWxpemFyIG90cmFzIGZ1bmNpb25lcyBjb21vICJzdGF0X3N1bW1hcnkiIHF1ZSBnZW5lcmFsaXphIGxvcyB2YWxvcmVzIGRlIHggcGFyYSBjYWRhIHZhbG9yIGRlIHguIExhIHZlbnRhamEgZGUgZXN0YSBmdW5jacOzbiBlcyBxdWUgcHVlZGVzIGludHJvZHVjaXIgZnVuY2lvbmVzIHBhcmEgZXNwZWNpZmljYXIgcXXDqSBncmFmaWNhci4NCg0KYGBge3IgICwgd2FybmluZz1GQUxTRX0gDQpnZ3Bsb3QoZGF0YT1kaWFtb25kcykrDQogIHN0YXRfc3VtbWFyeShtYXBwaW5nID0gYWVzKHg9Y3V0LCB5PWRlcHRoKSwNCiAgICAgICAgICAgICAgIGZ1bi55bWluID0gbWluLA0KICAgICAgICAgICAgICAgZnVuLnltYXggPSBtYXgsDQogICAgICAgICAgICAgICBmdW4ueT1tZWRpYW4pDQpgYGANCg0KSU1QT1JUQU5URQ0KU2kgbm8gc2UgaW5jbHV5ZSBncm91cCA9IDEsIGVudG9uY2VzIHRvZGFzIGxhcyBiYXJyYXMgZW4gZWwgZ3LDoWZpY28gdGVuZHLDoW4gbGEgbWlzbWEgYWx0dXJhLCB1bmEgYWx0dXJhIGRlIDEuIExhIGZ1bmNpw7NuIGdlb21fYmFyKCkgYXN1bWUgcXVlIGxvcyBncnVwb3Mgc29uIGlndWFsZXMgYSBsb3MgdmFsb3JlcyB4IHlhIHF1ZSBsYSBlc3RhZMOtc3RpY2EgY2FsY3VsYSBsb3MgY29udGVvcyBkZW50cm8gZGVsIGdydXBvIC4gDQoNCmBgYHtyICAsIHdhcm5pbmc9RkFMU0V9IA0KZ2dwbG90KGRhdGEgPSBkaWFtb25kcykgKw0KICBnZW9tX2JhcihtYXBwaW5nID0gYWVzKHggPSBjdXQsIHkgPSAuLnByb3AuLiwgZ3JvdXA9MSkpDQpgYGANCg0KIyMgQWp1c3RlcyBkZSAiZmlsbCINCg0KRW4gZXN0ZSBjYXNvIGN1YW5kbyBzZSB1dGlsaXphIGxhIGZ1bmNpw7NuICJmaWxsIiBwYXJhIGNvbG9yZWFyIGNhZGEgYmFycmEgZW4gcmVsYWNpw7NuIGEgc3UgYWx0dXJhIHJlbGF0aXZhLCBsYSB2YXJpYWJsZSAieSIgZGViZSBlc3RhciBub3JtYWxpemFkYSBkZSBmb3JtYSBtYW51YWwgZGFkbyBxdWUgIi4ucHJvcC4uIiBjYWxjdWxhIGVsIHBvcmNlbnRhamUgZGVudHJvIGRlbCBncnVwby4gTmVjZXNpdGEgdW5hIHZhcmlhYmxlIGRlIGFncnVwYWNpw7NuOyBkZSBsbyBjb250cmFyaW8sIGNhZGEgeCBlcyBzdSBwcm9waW8gZ3J1cG8geSBwcm9wID0gMSBxdWUgZXMgMTAwJSwgcGFyYSBjYWRhIHguDQoNCmBgYHtyICAsIHdhcm5pbmc9RkFMU0V9IA0KZ2dwbG90KGRhdGEgPSBkaWFtb25kcykgKw0KICBnZW9tX2JhcigNCiAgICBtYXBwaW5nID0gYWVzKHggPSBjdXQsIGZpbGw9Y29sb3IsIHkgPSAuLmNvdW50Li4vc3VtKC4uY291bnQuLikgKSApDQpgYGANCg0KT3RyYSBmb3JtYSBkZSBoYWNlciBsbyBtaXNtbyBlcyBzaW1wbGVtZW50ZSBlbiBlbCBlamUgeSBwbG90ZWFyICIuLmNvdW50Li4iLg0KDQpgYGB7ciAgLCB3YXJuaW5nPUZBTFNFfSANCmdncGxvdChkYXRhID0gZGlhbW9uZHMpICsNCiAgZ2VvbV9iYXIobWFwcGluZyA9IGFlcyh4ID0gY3V0LCBmaWxsPWNvbG9yLCB5ID0gLi5jb3VudC4uKSkNCmBgYA0KDQpTZSBwdWVkZW4gY29sb3JlYXIgbGFzIGJhcnJhcyBkZSBsb3MgZ3LDoWZpY29zIGRlIGFjdWVyZG8gYSBsYXMgdmFyaWFibGVzLiBTaSBzZSB1dGlsaXphIGxhIGZ1bmNpw7NuICJjb2xvciIgc29sbyBzZSBjb2xvcmVhIGVsIGNvbnRvcm5vLg0KDQpgYGB7ciAgLCB3YXJuaW5nPUZBTFNFfSANCmdncGxvdChkYXRhPWRpYW1vbmRzKSsNCiAgZ2VvbV9iYXIobWFwcGluZyA9IGFlcyh4PWN1dCwgY29sb3I9Y3V0KSkNCmBgYA0KDQpQYXJhIGNvbG9yZWFyIGNhZGEgYmFycmEgc2UgZGViZSB1c2FyICJmaWxsIi4NCg0KYGBge3IgICwgd2FybmluZz1GQUxTRX0gDQpnZ3Bsb3QoZGF0YT1kaWFtb25kcykrDQogIGdlb21fYmFyKG1hcHBpbmcgPSBhZXMoeD1jdXQsIGZpbGw9Y3V0KSkNCg0KYGBgDQoNClNpIHNlIHV0aWxpemEgY29tbyBhcmd1bWVudG8gZGUgbGEgZnVuY2nDs24gImZpbGwiIG90cmEgdmFyaWFibGUsIGVzdGEgc2UgcmVwcmVzZW50YSBkZW50cm8gZGUgY2FkYSBjYXRlZ29yw61hI2RlIGxhIHZhcmlhYmxlIHggY29tbyB1biByZWN0w6FuZ3Vsby4NCg0KYGBge3IgICwgd2FybmluZz1GQUxTRX0gDQpnZ3Bsb3QoZGF0YT1kaWFtb25kcykrDQogIGdlb21fYmFyKG1hcHBpbmcgPSBhZXMoeD1jdXQsIGZpbGw9Y2xhcml0eSkpDQpgYGANCg0KIyMgQWp1c3RlcyBkZSAicG9zaXRpb24iDQoNCkVsIHBhcsOhbWV0cm8gInBvc2l0aW9uIiBkZW50cm8gZGUgImdlb21fYmFyIiBzZSBwdWVkZSBtb2RpZmljYXIgYSAzIHZhbG9yZXMgZW4gZGVwZW5kZW5jaWEgZGUgbG8gcXVlIHNlIHF1aWVyYToNCg0KICAxLi0gImlkZW50aXR5IiBubyBzZSBzdWVsZSB1c2FyIGVuIGJhcnBsb3RzLCBlcyBtw6FzIGNvbcO6biBlbiBzY2F0dGVycGxvdHMuIEVzdG8gbm8gZXMgbXV5IMO6dGlsIHBhcmEgbGFzIGJhcnJhcywgcG9ycXVlIGxhcyBzdXBlcnBvbmUuIFBhcmEgdmVyIGVzYSBzdXBlcnBvc2ljacOzbiwgZGViZW1vcyBoYWNlciBxdWUgbGFzIGJhcnJhcyBzZWFuIGxpZ2VyYW1lbnRlIHRyYW5zcGFyZW50ZXMgY29uZmlndXJhbmRvIGFsZmEgZW4gdW4gdmFsb3IgcGVxdWXDsW8sIG8gY29tcGxldGFtZW50ZSB0cmFuc3BhcmVudGVzIGNvbmZpZ3VyYW5kbyAiZmlsbCA9IE5BIi4NCg0KYGBge3IgICwgd2FybmluZz1GQUxTRX0gDQpnZ3Bsb3QoZGF0YT1kaWFtb25kcywgbWFwcGluZyA9IGFlcyh4PWN1dCwgZmlsbD1jbGFyaXR5KSkrDQogIGdlb21fYmFyKGFscGhhPTEvNSwgcG9zaXRpb249ImlkZW50aXR5IikNCmBgYA0KDQogIDIuLSAiZmlsbCIgaGFjZSBxdWUgdG9kYXMgbGFzIGJhcnJhcyB0ZW5nYW4gbGEgbWlzbWEgYWx0dXJhLCBsbyBxdWUgcHVlZGUgc2VyIMO6dGlsIHBhcmEgY29tcGFyYXIgcHJvcG9yY2lvbmVzDQoNCmBgYHtyICAsIHdhcm5pbmc9RkFMU0V9IA0KZ2dwbG90KGRhdGE9ZGlhbW9uZHMpKw0KICBnZW9tX2JhcihtYXBwaW5nID0gYWVzKHg9Y3V0LCBmaWxsPWNsYXJpdHkpLCBwb3NpdGlvbj0iZmlsbCIpDQpgYGANCg0KICAzLi0gImRvZGdlIiBjb2xvY2EgbG9zIG9iamV0b3Mgc3VwZXJwdWVzdG9zIHVubyBhbCBsYWRvIGRlbCBvdHJvLCBzaWVuZG8gbcOhcyBzZW5jaWxsbyBjb21wYXJhciBsb3MgdmFsb3JlcyBpbmRpdmlkdWFsZXMNCg0KDQpgYGB7ciAgLCB3YXJuaW5nPUZBTFNFfSANCmdncGxvdChkYXRhPWRpYW1vbmRzKSsNCiAgZ2VvbV9iYXIobWFwcGluZz1hZXMoeD1jdXQsIGZpbGw9IGNsYXJpdHkpLCBwb3NpdGlvbj0iZG9kZ2UiKQ0KYGBgDQoNCg0KT3RybyBhanVzdGUsIGVzdGEgdmV6IHBhcmEgc2NhdHRlcnBsb3RzLCBjb25zaXN0ZSBlbiBlbGltaW5hciBsYSBmb3JtYSBvcmRlbmFkYSBkZWwgc2lndWllbnRlIGdyw6FmaWNvLiBQb3IgZGVmZWN0bywgdHJhdGEgZGUgb3JkZW5hciBsb3MgcHVudG9zIGNvbG9jw6FuZG9sb3MgZW4gdW5hIGN1YWRyw61jdWxhLCBwcm92b2NhbmRvIHF1ZSBoYXlhIG11Y2hhIHN1cGVycG9zaWNpw7NuIHkgcXVlIG5vIHNlIG9ic2VydmVuIHRvZG9zIGxvcyBwdW50b3MgZXhpc3RlbnRlcy4gDQoNCmBgYHtyICAsIHdhcm5pbmc9RkFMU0V9IA0KZ2dwbG90KGRhdGE9bXBnKSsNCiAgZ2VvbV9wb2ludChtYXBwaW5nID0gYWVzKHg9ZGlzcGwsIHk9aHd5KSkNCmBgYA0KDQpQYXJhIGVsbG8gc2UgYcOxYWRlIHBvc2l0aW9uPSJqaXR0ZXIiIHF1ZSBkaXNwZXJzYSBsb3MgcHVudG9zIHkgYXl1ZGEgYSB2aXN1YWxpemFybG9zIG1lam9yLiBUYW1iacOpbiB0aWVuZSBzdSBwcm9pYSBmb3JtYSBhYnJldmlhZGEgY29tbyAiZ2VvbV9qaXR0ZXIoKSIuICJnZW9tX2ppdHRlcigpIiB0aWVuZSBsb3MgcGFyw6FtZXRyb3MgImhlaWdodCIgeSAid2lkdGgiIHF1ZSBjb250cm9sYW4gbGEgZGlzcGVyc2nDs24gZGUgbG9zIHB1bnRvcyBlbiBsb3MgcGxhbm9zIHggZSB5Lg0KDQoNCk5PVEEgSU1QT1JUQU5URTogRXN0ZSBwYXLDoW1ldHJvIGHDsWFkZSBydWlkbyBhbCBwbG90IHkgaGFjZSBxdWUgbG9zIHB1bnRvcyBubyBhcGFyZXpjYW4gZW4gc3UgcG9zaWNpw7NuIGV4YWN0YSB5YSBxdWUgZXZpdGEgbGEgc3VwZXJwb3NpY2nDs24uIEEgcGVxdWXDsWEgZXNjYWxhIGhhY2UgZWwgZ3LDoWZpY28gbcOhcyBpbmV4YWN0bywgcGVybyBhIGdyYW4gZXNjYWxhIG1lam9yYSBsYSB2aXN1YWxpemFjacOzbiBkZSBsb3MgZGF0b3MNCg0KYGBge3IgICwgd2FybmluZz1GQUxTRX0gDQpnZ3Bsb3QoZGF0YT1tcGcpKw0KICBnZW9tX3BvaW50KG1hcHBpbmcgPSBhZXMoeD1kaXNwbCwgeT1od3kpLCBwb3NpdGlvbj0iaml0dGVyIikNCg0KZ2dwbG90KGRhdGE9bXBnKSsNCiAgZ2VvbV9qaXR0ZXIobWFwcGluZyA9IGFlcyh4PWRpc3BsLCB5PWh3eSkpDQoNCmdncGxvdChkYXRhPW1wZykrDQogIGdlb21faml0dGVyKG1hcHBpbmcgPSBhZXMoeD1kaXNwbCwgeT1od3kpLCBoZWlnaHQgPSA1LCB3aWR0aCA9IDMpDQpgYGANCg0KDQojIyBTaXN0ZW1hIGRlIGNvb3JkZW5hZGFzDQpBcXXDrSBtb3N0cmFyw6kgYWxndW5hcyBmdW5jaW9uZXMgw7p0aWxlcyByZWZlcmlkYXMgYSBsb3Mgc2lzdGVtYXMgZGUgY29vcmRlbmFkYXMuIA0KDQogIDEuIC0gY29vcmRfZmxpcCgpIGludGVyY2FtYmlhIGxvcyBlamV4IHggZSB5LiDDmnRpbCwgcG9yIGVqZW1wbG8sIHBhcmEgYm94cGxvdHMgaG9yaXpvbnRhbGVzIC4NCg0KYGBge3IgICwgd2FybmluZz1GQUxTRX0gDQpnZ3Bsb3QoZGF0YT1tcGcsIG1hcHBpbmcgPSBhZXMoeD1jbGFzcywgeT1od3kpKSsNCiAgZ2VvbV9ib3hwbG90KCkNCg0KZ2dwbG90KGRhdGE9bXBnLCBtYXBwaW5nID0gYWVzKHg9Y2xhc3MsIHk9aHd5KSkrDQogIGdlb21fYm94cGxvdCgpKw0KICBjb29yZF9mbGlwKCkNCmBgYA0KDQogIDIuIC0gY29vcmRfcXVpY2ttYXAoKSB0ZSBtdWVzdHJhIGxhcyBkaW1lbnNpb25lcyBjb3JyZWN0YXMgZGUgbWFwYXMuIMOadGlsIHBhcmEgZGF0b3MgZXNwYWNpYWxlcy4NCg0KYGBge3IgICwgd2FybmluZz1GQUxTRX0gDQpueiA8LSBtYXBfZGF0YSgibnoiKQ0KZ2dwbG90KG56LCBhZXMobG9uZywgbGF0LCBncm91cCA9IGdyb3VwKSkgKw0KICBnZW9tX3BvbHlnb24oZmlsbCA9ICJ3aGl0ZSIsIGNvbG9yID0gImJsYWNrIikNCg0KZ2dwbG90KG56LCBhZXMobG9uZywgbGF0LCBncm91cCA9IGdyb3VwKSkgKw0KICBnZW9tX3BvbHlnb24oZmlsbCA9ICJ3aGl0ZSIsIGNvbG9yID0gImJsYWNrIikgKw0KICBjb29yZF9xdWlja21hcCgpDQpgYGANCg0KICAzLiAtIGNvb3JkX3BvbGFyKCkgdXNhIGNvb3JkZW5hZGFzIHBvbGFyZXMuIE90cm8gdGlwbyBkZSBncsOhZmljby4NCg0KYGBge3IgICwgd2FybmluZz1GQUxTRX0gDQpiYXI8LSBnZ3Bsb3QoZGF0YT1kaWFtb25kcykrDQogIGdlb21fYmFyKG1hcHBpbmcgPSBhZXMoeD1jdXQsIGZpbGw9Y3V0KSwgc2hvdy5sZWdlbmQgPSBGQUxTRSwgd2lkdGggPSAxKSsNCiAgdGhlbWUoYXNwZWN0LnJhdGlvID0gMSkrDQogIGxhYnMoeD1OVUxMLCB5PU5VTEwpDQoNCmJhcitjb29yZF9mbGlwKCkNCmJhcitjb29yZF9wb2xhcigpDQpgYGANCg==