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==