Visualizar datos es útil para identificar a relación entre distintas
variables pero también para comunicar el análisis de los datos y
resultados. El paquete ggplot2
permite generar gráficos de
gran calidad en pocos pasos. Cualquier gráfico de ggplot tendrá como
mínimo 3 componentes: los datos, un sistema de
coordenadas y una geometría (la representación
visual de los datos) y se irá construyendo por capas.
Primera capa: el área del gráfico
Cómo siempre será necesario cargar los paquetes que vamos a usar y ya
que estamos la base de datos de Bariloche con la que estuvimos
trabajando anteriormente.
library(ggplot2) # El paquete del momento
library(dplyr)
library(readr)
library(tidyr)
bariloche <- readr::read_csv("datos/bariloche_enlimpio.csv") %>%
select(Fecha, starts_with("Temperatura")) %>%
pivot_longer(cols = -Fecha,
names_to = "variable_lugar_altura_tipo",
values_to = "valor") %>%
separate(col = variable_lugar_altura_tipo,
into = c("variable", "lugar", "altura", "tipo"),
sep = "_") %>%
mutate(tipo = if_else(is.na(tipo), "Media", tipo))
Tip: Es muy común tener que manipular y limpiar los
datos para poder utilizarlos tal como hicimos con las observaciones de
Bariloche. En este caso esa manipulación no requiere de mucho tiempo
porque se tratan de pocos datos. Si estuvieras trabajando con muchos
datos o haciendo una manipulación que requiere mucho tiempo de computo,
es recomendable guardar esa nueva versión es un nuevo archivo. Pero, es
muy importante guardar el código que generó esa nueva versión de la base
de datos, nunca se sabe cuando habrá que rehacer todo!
La función principal de ggplot2 es justamente ggplot()
que nos permite iniciar el gráfico y además definir las
características globales. El primer argumento de esta función
serán los datos que queremos visualizar, siempre en un data.frame. En
este caso usamos paises
.
El segundo argumento se llama mapping justamente porque
mapea o dibuja los ejes del gráfico y
siempre va acompañado de la función aes()
.
La función aes()
recibe las propiedades estéticas del
gráfico (o aesthetic en inglés) a partir de las variables (o
columnas) del data.frame estamos usando. En este caso le indicamos que
en el eje x querremos graficar la variable
tipo
(o sea mínima, media y máxima) y en eje
y la variable valor
que en realidad
contiene las observaciones de temperatura.
Pero esta sola función no es suficiente, solo genera la primera capa:
el área del gráfico.
ggplot(data = bariloche, mapping = aes(x = tipo, y = valor))
Segunda capa: geometrías
Necesitamos agregar una nueva capa a nuestro gráfico, los elementos
geométricos o geoms que representaran los datos. Para esto
sumamos una función geom, por ejemplo si queremos representar
los datos con puntos usaremos geom_point()
ggplot(data = bariloche, mapping = aes(x = tipo, y = valor)) +
geom_point()
## Warning: Removed 16105 rows containing missing values (geom_point).
¡Nuestro primer gráfico!
Primer desafío
Ahora es tu turno. Modifica el gráfico anterior para visualizar cómo
cambia la temperatura a lo largo del tiempo
¿Te parece útil este gráfico?
Este gráfico tiene muchísima información porque tiene un punto por
cada observación para cada día y cada variable de temperatura. Pero si
bien podemos adivinar un ciclo anual, los veranos más cálidos y los
inviernos más fríos, hay mucha información que se pierde.
ggplot(data = bariloche, mapping = aes(x = Fecha, y = valor)) +
geom_point()
## Warning: Removed 16105 rows containing missing values (geom_point).
Mapear variables a elementos
Una posible solución sería utilizar otras variables de nuestros
datos, por ejemplo el tipo
y mapear el color de
los puntos de a cuerdo al tipo
de temperatura, máxima,
mínima y media.
ggplot(data = bariloche, mapping = aes(x = Fecha, y = valor)) +
geom_point(aes(color = tipo))
## Warning: Removed 16105 rows containing missing values (geom_point).
Ahora está un poco mejor. Por ejemplo ya podemos ver que en general
las temperaturas máximas (los puntos rosas) tienen en promedio mayor
valor a lo largo de los años que las temperaturas mínimas (los puntos
celestes). Aún no podemos identificar en lugar donde se tomaron los
datos pero tenemos algo más de información.
Algo muy importante a tener en cuenta: los puntos toman un
color de acuerdo a una variable de los datos, y para que
ggplot2 identifique esa variable (en este caso tipo
) es
necesario incluirla dentro de una función aes()
.
Otras geometrías
Este gráfico posiblemente no sea muy adecuado si queremos visualizar
la evolución de una variable a lo largo del tiempo, necesitamos
cambiar la geometría a lineas usando geom_line()
ggplot(data = na.omit(bariloche), mapping = aes(x = Fecha, y = valor)) +
geom_line(aes(color = tipo))
Es posible que te estés preguntando que hace ese
na.omit()
metido adentro del código. Resulta que los
gráficos de líneas en general no se llevan bien con los datos faltantes
y necesitamos sacarlos para que no nos haga problemas. En la vida real
tendríamos que decidir como resolver este problema de una manera menos
drástica!
Por suerte las funciones geom_*()
tienen más o menos
nombres amigables. Pero el gráfico sigue teniendo problemas algo más
sutiles. En este caso está haciendo un gráfico por tipo de temperatura,
pero en el camino mezcló las observaciones que se hicieron en el abrigo,
a la intemperie y en el suelo. Si estuviéramos dibujando este gráfico
con lápiz y papel muy posiblemente hubiéramos identificado los puntos
que corresponden a cada tipo de temperatura y lugar y los hubiéramos
“unido con líneas”, necesitamos que ggplot2 haga esto. ¿Cómo le
indicamos que observaciones corresponde a cada variable de temperatura?
Necesitamos que los agrupe por las variables tipo
y lugar
(¡qué bueno que tenemos toda esa información en
nuestra base de datos!).
ggplot(data = na.omit(bariloche), mapping = aes(x = Fecha, y = valor)) +
geom_line(aes(color = tipo, group = interaction(tipo, lugar)))
Usamos el argumento group =
y de nuevo, lo incluimos
dentro de la función aes()
para indicarle a ggplot2 que
busque la variable tipo
y lugar
dentro del
data.frame que estamos usando. En este caso como estamos agrupando por
dos variables tenemos que incluir la función
interaction()
.
Y ahora si, conseguimos el gráfico que estamos buscando.
Segundo desafío
Cuando mencionamos que ggplot2 construye gráficos por capas, lo
decíamos en serio! Hasta ahora tenemos dos capas: el área del gráfico y
una geometría (las líneas).
- Sumá una tercera capa para visualizar puntos además de las
líneas.
- ¿Porqué los puntos ahora no siguen los colores de las distintas
temperaturas?
- ¿Qué cambio podrías hacer para que los puntos también tengan color
según el tipo de temperatura?
Acá surge una característica importante de las capas: pueden tener
apariencia independiente si solo mapeamos el color en la capa
de las líneas y no en la capa de los puntos. Al mismo tiempo, si
quisiéramos que todas las capas tenga la misma apariencia podemos
incluir el argumento color =
en la función global
ggpplot()
o repetirlo en cada capa.
ggplot(na.omit(bariloche), aes(x = Fecha, y = valor)) +
geom_line(aes(color = tipo, group = interaction(tipo, lugar))) +
geom_point()
Si te preguntabas a donde fueron a parar el data =
, el
mapping =
y los nombres de los argumentos adentro de la
función aes()
, x =
e y =
, resulta
que estamos aprovechando que tanto ggplot2 como nosotros ahora sabemos
en que orden recibe la información cada función. Siempre el primer
elemento que le pasemos o indiquemos a la función
ggplot()
será el data.frame.
Algunos argumentos para cambiar la apariencia de las geometrías
son:
color
o colour
modifica el color de líneas
y puntos
fill
modifica el color del área de un elemento, por
ejemplo el relleno de un punto
linetype
modifica el tipo de línea (punteada, continua,
con guiones, etc.)
pch
modifica el tamaño del punto
size
modifica el tamaño de los elementos (por ejemplo
el tamaño de puntos o el grosor de líneas)
alpha
modifica la transparencia de los elementos (1 =
opaco, 0 = transparente)
shape
modifica el tipo de punto (círculos, cuadrados,
triángulos, etc.)
El mapeo entre una variable y un parámetro de geometría se
hace a través de una escala. La escala de colores es lo
que define, por ejemplo, que los puntos donde la variable
tipo
toma el valor "Maxima"
van a tener el
color rosa (●), donde toma el valor
"Minima"
, celeste (●),
etc…
Modificar elementos utilizando un valor único
Es posible que en algún momento necesites cambiar la apariencia de
los elementos o geometrías independientemente de las variables de tu
data.frame. Por ejemplo podrías querer que todos los puntos sean de un
único color: rojos. En este caso
geom_point(aes(color = "red"))
no va a funcionar -ojo que
los colores van en inglés-. Lo que ese código dice es que mapee el
parámetro geométrico “color” a una variable que contiene el valor
"red"
para todas las filas. El mapeo se hace a través de la
escala, que va a asignarle un valor (rosa ●) a los puntos correspondientes al valor
"red"
.
Ahora que no nos interesa mapear el color a una variable,
podemos mover ese argumento afuera de la función
aes()
: geom_point(color = "red")
.
Relación entre variables
Muchas veces no es suficiente con mirar los datos crudos para
identificar la relación entre las variables; es necesario usar alguna
transformación estadística que resalte esas relaciones, ya sea ajustando
una recta o calculando promedios.
Para alguna transformaciones estadísticas comunes, {ggplot2} tiene
geoms ya programados, pero muchas veces es posible que necesitemos
manipular los datos antes de poder hacer un gráfico. A veces esa
manipulación será compleja y entonces para no repetir el cálculo muchas
veces, guardaremos los datos modificados en una nueva variable. Pero
también podemos encadenar la manipulación de los datos y el
gráfico resultante.
Por ejemplo, calculemos la temperatura media mensual para cada tipo y
lugar usando dplyr
y luego
grafiquemos la t_mensual
a los largo de los
meses
:
bariloche %>%
group_by(tipo, lugar, mes = lubridate::floor_date(Fecha, unit = "month")) %>%
summarise(t_mensual = mean(valor, na.rm = TRUE)) %>%
ggplot(aes(mes, t_mensual)) + # Acá se acaban los %>% y comienzan los "+"
geom_point(aes(color = tipo))
## `summarise()` has grouped output by 'tipo', 'lugar'. You can override using the
## `.groups` argument.
## Warning: Removed 238 rows containing missing values (geom_point).
Tal vez notaste que agregamos una función
(lubridate::floor_date()
) para obtener el mes de la
variable Fecha
. La función es del paquete {lubridate} que
vemos en más detalle acá, y lo que hace
es redondear la fecha en este caso al mes. Esto es necesario porque ya
no nos interesa la información de los días para calcular un promedio
mensual. También podríamos haber calculado un promedio anual y en ese
caso usar floor_date(Fecha, unit = "year)
.
Esto es posible gracias al operador %>%
que le
pasa el resultado de summarise()
a la función
ggplot()
. Y este resultado no es ni más ni menos que el
data.frame que necesitamos para hacer nuestro gráfico. Es importante
notar que una vez que comenzamos el gráfico ya no se
puede usar el operador %>%
y las capas del gráfico se
suman como siempre con +
.
Este gráfico entonces parece mostrar la evolución de la temperatura a
lo largo del tiempo de una manera más limpia. Pero sería interesante ver
esa evolución o relación en el tiempo más explícitamente agregando una
nueva capa con geom_smooth()
.
bariloche %>%
group_by(tipo, lugar, mes = lubridate::floor_date(Fecha, unit = "month")) %>%
summarise(t_mensual = mean(valor, na.rm = TRUE)) %>%
ggplot(aes(mes, t_mensual)) +
geom_point(aes(color = tipo)) +
geom_smooth()
## `summarise()` has grouped output by 'tipo', 'lugar'. You can override using the
## `.groups` argument.
## `geom_smooth()` using method = 'loess' and formula 'y ~ x'
## Warning: Removed 238 rows containing non-finite values (stat_smooth).
## Warning: Removed 238 rows containing missing values (geom_point).
Como dice en el mensaje, por defecto geom_smooth()
suaviza los datos usando el método loess (regresión lineal
local) cuando hay menos de 1000 datos. Seguramente va a ser muy común
que quieras ajustar una regresión lineal global. En ese caso, hay que
poner method = "lm"
:
bariloche %>%
group_by(tipo, lugar, mes = lubridate::floor_date(Fecha, unit = "month")) %>%
summarise(t_mensual = mean(valor, na.rm = TRUE)) %>%
ggplot(aes(mes, t_mensual)) +
geom_point(aes(color = tipo)) +
geom_smooth(method = "lm")
## `summarise()` has grouped output by 'tipo', 'lugar'. You can override using the
## `.groups` argument.
## `geom_smooth()` using formula 'y ~ x'
## Warning: Removed 238 rows containing non-finite values (stat_smooth).
## Warning: Removed 238 rows containing missing values (geom_point).
En gris nos muestra el intervalo de confianza al rededor de este
suavizado.
Cómo cualquier geom, podemos modificar el color, el grosor de la
línea y casi cualquier cosa que se te ocurra.
Tercer desafío
Modificá el siguiente código para obtener el gráfico que se muestra
más abajo.
bariloche %>%
group_by(______, lugar, mes = lubridate::floor_date(Fecha, unit = "month")) %>%
summarise(t_mensual = mean(valor, na.rm = TRUE)) %>%
ggplot(aes(mes, _______)) +
geom_point(aes(color = tipo), shape = ____, size = 2) +
geom_smooth(color = tipo, method = "lm")
Graficando en paneles
Vimos que es posible graficar más de dos variables en un gráfico
mapeando una variable al color
o por ejemplo el tipo de
línea o linetype
para observar la relación entre las 3
variables. También podríamos haber intentando resolver el problema
generando un gráfico por cada color filtrando las observaciones
correspondientes.
bariloche %>%
group_by(tipo, lugar, mes = lubridate::floor_date(Fecha, unit = "month")) %>%
summarise(t_mensual = mean(valor, na.rm = TRUE)) %>%
filter(tipo == "Maxima") %>%
ggplot(aes(mes, t_mensual)) +
geom_point(aes(color = tipo))
## `summarise()` has grouped output by 'tipo', 'lugar'. You can override using the
## `.groups` argument.
## Warning: Removed 8 rows containing missing values (geom_point).
Pero sería muchísimo trabajo si tenemos que hacer esto para cada una
de las posibles categorías de. La buena noticia es que {ggplot2} tiene
un par de funciones justo para resolver este problema:
bariloche %>%
group_by(tipo, lugar, mes = lubridate::floor_date(Fecha, unit = "month")) %>%
summarise(t_mensual = mean(valor, na.rm = TRUE)) %>%
ggplot(aes(mes, t_mensual)) +
geom_point(aes(color = tipo)) +
facet_wrap(~tipo)
## `summarise()` has grouped output by 'tipo', 'lugar'. You can override using the
## `.groups` argument.
## Warning: Removed 238 rows containing missing values (geom_point).
Esta nueva capa con facet_wrap()
divide al gráfico
inicial en 3 paneles o facets, uno por cada color (y cada tipo de
temperatura). Esta función requiere saber que variable será la
responsable de separar los paneles y para eso se usa la notación de
funciones de R: ~tipo
. Esto se lee como generar paneles “en
función del tipo (de temperatura)”.
¿Y si quisiéramos generar paneles a partir de 2 variables? Para eso
existe facet_grid()
. En este gráfico generamos paneles
viendo la “relación entre el tipo y el lugar” donde se midió la
temperatura y por ejemplo en el primer panel arriba a la izquierda
podremos observar la temperatura máxima en el abrigo. En este caso
mapear el tipo de temperatura al color delos puntos no parece ser
necesario ya que cada columna ya nos permite identificar eso, sin
embargo en algunos casos ayuda a leer el gráfico más rápido.
En este caso también notamos que esta base de datos está poblada de
datos faltantes ya que varios paneles quedan vacíos.
bariloche %>%
group_by(tipo, lugar, mes = lubridate::floor_date(Fecha, unit = "month")) %>%
summarise(t_mensual = mean(valor, na.rm = TRUE)) %>%
ggplot(aes(mes, t_mensual)) +
geom_point(aes(color = tipo)) +
facet_grid(lugar ~ tipo)
## `summarise()` has grouped output by 'tipo', 'lugar'. You can override using the
## `.groups` argument.
## Warning: Removed 238 rows containing missing values (geom_point).
Cuarto desafío
Ahora es tu turno, intentá reproducir el siguiente gráfico con todo
lo visto arriba.
(Psss! Probá cambiar el orden en facet_grid()
)
Quedan muchas otras geometrías que no describimos, si te interesa por
ejemplo aprender a hacer un gráfico de barras o un boxplot podés revisar
este episodio.
LS0tCnRpdGxlOiAiVmlzdWFsaXphY2nDs24gZGUgZGF0b3MgY29uIHtnZ3Bsb3QyfSBJIgpvdXRwdXQ6IAogIGh0bWxfZG9jdW1lbnQ6CiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgaGlnaGxpZ2h0OiB0YW5nbyAgICAKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpgYGAKClZpc3VhbGl6YXIgZGF0b3MgZXMgw7p0aWwgcGFyYSBpZGVudGlmaWNhciBhIHJlbGFjacOzbiBlbnRyZSBkaXN0aW50YXMgdmFyaWFibGVzIHBlcm8gdGFtYmnDqW4gcGFyYSBjb211bmljYXIgZWwgYW7DoWxpc2lzIGRlIGxvcyBkYXRvcyB5IHJlc3VsdGFkb3MuIEVsIHBhcXVldGUgYGdncGxvdDJgIHBlcm1pdGUgZ2VuZXJhciBncsOhZmljb3MgZGUgZ3JhbiBjYWxpZGFkIGVuIHBvY29zIHBhc29zLiBDdWFscXVpZXIgZ3LDoWZpY28gZGUgZ2dwbG90IHRlbmRyw6EgY29tbyBtw61uaW1vIDMgY29tcG9uZW50ZXM6IGxvcyAqKmRhdG9zKiosIHVuICoqc2lzdGVtYSBkZSBjb29yZGVuYWRhcyoqIHkgdW5hICoqZ2VvbWV0csOtYSoqIChsYSByZXByZXNlbnRhY2nDs24gdmlzdWFsIGRlIGxvcyBkYXRvcykgeSBzZSBpcsOhIGNvbnN0cnV5ZW5kbyBwb3IgY2FwYXMuIAoKIyMgUHJpbWVyYSBjYXBhOiBlbCDDoXJlYSBkZWwgZ3LDoWZpY28KCkPDs21vIHNpZW1wcmUgc2Vyw6EgbmVjZXNhcmlvIGNhcmdhciBsb3MgcGFxdWV0ZXMgcXVlIHZhbW9zIGEgdXNhciB5IHlhIHF1ZSBlc3RhbW9zIGxhIGJhc2UgZGUgZGF0b3MgZGUgQmFyaWxvY2hlIGNvbiBsYSBxdWUgZXN0dXZpbW9zIHRyYWJhamFuZG8gW2FudGVyaW9ybWVudGVdKDA2LWRwbHlyLXRpZHlyLUlJLmh0bWwpLgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeShnZ3Bsb3QyKSAjIEVsIHBhcXVldGUgZGVsIG1vbWVudG8KbGlicmFyeShkcGx5cikKbGlicmFyeShyZWFkcikKbGlicmFyeSh0aWR5cikKCgpiYXJpbG9jaGUgPC0gcmVhZHI6OnJlYWRfY3N2KCJkYXRvcy9iYXJpbG9jaGVfZW5saW1waW8uY3N2IikgJT4lIAogIHNlbGVjdChGZWNoYSwgc3RhcnRzX3dpdGgoIlRlbXBlcmF0dXJhIikpICU+JSAKICBwaXZvdF9sb25nZXIoY29scyA9IC1GZWNoYSwKICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAidmFyaWFibGVfbHVnYXJfYWx0dXJhX3RpcG8iLAogICAgICAgICAgICAgICB2YWx1ZXNfdG8gPSAidmFsb3IiKSAlPiUgCiAgc2VwYXJhdGUoY29sID0gdmFyaWFibGVfbHVnYXJfYWx0dXJhX3RpcG8sIAogICAgICAgICAgIGludG8gPSBjKCJ2YXJpYWJsZSIsICJsdWdhciIsICJhbHR1cmEiLCAidGlwbyIpLCAKICAgICAgICAgICBzZXAgPSAiXyIpICU+JSAKICBtdXRhdGUodGlwbyA9IGlmX2Vsc2UoaXMubmEodGlwbyksICJNZWRpYSIsIHRpcG8pKQpgYGAKCjo6OiB7LmFsZXJ0IC5hbGVydC1zdWNjZXNzfQoqKlRpcDoqKiBFcyBtdXkgY29tw7puIHRlbmVyIHF1ZSBtYW5pcHVsYXIgeSBsaW1waWFyIGxvcyBkYXRvcyBwYXJhIHBvZGVyIHV0aWxpemFybG9zIHRhbCBjb21vIGhpY2ltb3MgY29uIGxhcyBvYnNlcnZhY2lvbmVzIGRlIEJhcmlsb2NoZS4gRW4gZXN0ZSBjYXNvIGVzYSBtYW5pcHVsYWNpw7NuIG5vIHJlcXVpZXJlIGRlIG11Y2hvIHRpZW1wbyBwb3JxdWUgc2UgdHJhdGFuIGRlIHBvY29zIGRhdG9zLiBTaSBlc3R1dmllcmFzIHRyYWJhamFuZG8gY29uIG11Y2hvcyBkYXRvcyBvIGhhY2llbmRvIHVuYSBtYW5pcHVsYWNpw7NuIHF1ZSByZXF1aWVyZSBtdWNobyB0aWVtcG8gZGUgY29tcHV0bywgZXMgcmVjb21lbmRhYmxlIGd1YXJkYXIgZXNhIG51ZXZhIHZlcnNpw7NuIGVzIHVuIG51ZXZvIGFyY2hpdm8uIFBlcm8sIGVzIG11eSBpbXBvcnRhbnRlIGd1YXJkYXIgZWwgY8OzZGlnbyBxdWUgZ2VuZXLDsyBlc2EgbnVldmEgdmVyc2nDs24gZGUgbGEgYmFzZSBkZSBkYXRvcywgbnVuY2Egc2Ugc2FiZSBjdWFuZG8gaGFicsOhIHF1ZSByZWhhY2VyIHRvZG8hCjo6OgoKTGEgZnVuY2nDs24gcHJpbmNpcGFsIGRlIGdncGxvdDIgZXMganVzdGFtZW50ZSBgZ2dwbG90KClgIHF1ZSBub3MgcGVybWl0ZSAqaW5pY2lhciogZWwgZ3LDoWZpY28geSBhZGVtw6FzIGRlZmluaXIgbGFzIGNhcmFjdGVyw61zdGljYXMgKmdsb2JhbGVzKi4gRWwgcHJpbWVyIGFyZ3VtZW50byBkZSBlc3RhIGZ1bmNpw7NuIHNlcsOhbiBsb3MgZGF0b3MgcXVlIHF1ZXJlbW9zIHZpc3VhbGl6YXIsIHNpZW1wcmUgZW4gdW4gZGF0YS5mcmFtZS4gRW4gZXN0ZSBjYXNvIHVzYW1vcyBgcGFpc2VzYC4gCgpFbCBzZWd1bmRvIGFyZ3VtZW50byBzZSBsbGFtYSBtYXBwaW5nIGp1c3RhbWVudGUgcG9ycXVlICptYXBlYSogbyAqZGlidWphKiBsb3MgZWplcyBkZWwgZ3LDoWZpY28geSAqKnNpZW1wcmUqKiB2YSBhY29tcGHDsWFkbyBkZSBsYSBmdW5jacOzbiBgYWVzKClgLiBMYSBmdW5jacOzbiBgYWVzKClgIHJlY2liZSBsYXMgcHJvcGllZGFkZXMgZXN0w6l0aWNhcyBkZWwgZ3LDoWZpY28gKG8gKmFlc3RoZXRpYyogZW4gaW5nbMOpcykgYSBwYXJ0aXIgZGUgbGFzIHZhcmlhYmxlcyAobyBjb2x1bW5hcykgZGVsIGRhdGEuZnJhbWUgZXN0YW1vcyB1c2FuZG8uIEVuIGVzdGUgY2FzbyBsZSBpbmRpY2Ftb3MgcXVlIGVuIGVsIGVqZSAqKngqKiBxdWVycmVtb3MgZ3JhZmljYXIgbGEgdmFyaWFibGUgYHRpcG9gIChvIHNlYSBtw61uaW1hLCBtZWRpYSB5IG3DoXhpbWEpIHkgZW4gZWplICoqeSoqIGxhIHZhcmlhYmxlIGB2YWxvcmAgcXVlIGVuIHJlYWxpZGFkIGNvbnRpZW5lIGxhcyBvYnNlcnZhY2lvbmVzIGRlIHRlbXBlcmF0dXJhLgoKUGVybyBlc3RhIHNvbGEgZnVuY2nDs24gbm8gZXMgc3VmaWNpZW50ZSwgc29sbyBnZW5lcmEgbGEgcHJpbWVyYSBjYXBhOiBlbCDDoXJlYSBkZWwgZ3LDoWZpY28uCgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBiYXJpbG9jaGUsIG1hcHBpbmcgPSBhZXMoeCA9IHRpcG8sIHkgPSB2YWxvcikpIApgYGAKCiMjIFNlZ3VuZGEgY2FwYTogZ2VvbWV0csOtYXMKTmVjZXNpdGFtb3MgYWdyZWdhciB1bmEgbnVldmEgY2FwYSBhIG51ZXN0cm8gZ3LDoWZpY28sIGxvcyBlbGVtZW50b3MgZ2VvbcOpdHJpY29zIG8gKmdlb21zKiBxdWUgcmVwcmVzZW50YXJhbiBsb3MgZGF0b3MuIFBhcmEgZXN0byAqc3VtYW1vcyogdW5hIGZ1bmNpw7NuIGdlb20sIHBvciBlamVtcGxvIHNpIHF1ZXJlbW9zIHJlcHJlc2VudGFyIGxvcyBkYXRvcyBjb24gcHVudG9zIHVzYXJlbW9zIGBnZW9tX3BvaW50KClgCgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBiYXJpbG9jaGUsIG1hcHBpbmcgPSBhZXMoeCA9IHRpcG8sIHkgPSB2YWxvcikpICsKICBnZW9tX3BvaW50KCkKYGBgCgrCoU51ZXN0cm8gcHJpbWVyIGdyw6FmaWNvISAKCjo6OiB7LmFsZXJ0IC5hbGVydC1pbmZvfQoqKlByaW1lciBkZXNhZsOtbyoqCgpBaG9yYSBlcyB0dSB0dXJuby4gTW9kaWZpY2EgZWwgZ3LDoWZpY28gYW50ZXJpb3IgcGFyYSB2aXN1YWxpemFyIGPDs21vIGNhbWJpYSBsYSB0ZW1wZXJhdHVyYSBhIGxvIGxhcmdvIGRlbCB0aWVtcG8gCgrCv1RlIHBhcmVjZSDDunRpbCBlc3RlIGdyw6FmaWNvPwo6OjoKCkVzdGUgZ3LDoWZpY28gdGllbmUgbXVjaMOtc2ltYSBpbmZvcm1hY2nDs24gcG9ycXVlIHRpZW5lIHVuIHB1bnRvIHBvciBjYWRhIG9ic2VydmFjacOzbiBwYXJhIGNhZGEgZMOtYSB5IGNhZGEgdmFyaWFibGUgZGUgdGVtcGVyYXR1cmEuIFBlcm8gc2kgYmllbiBwb2RlbW9zIGFkaXZpbmFyIHVuIGNpY2xvIGFudWFsLCBsb3MgdmVyYW5vcyBtw6FzIGPDoWxpZG9zIHkgbG9zIGludmllcm5vcyBtw6FzIGZyw61vcywgaGF5IG11Y2hhIGluZm9ybWFjacOzbiBxdWUgc2UgcGllcmRlLiAKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IGJhcmlsb2NoZSwgbWFwcGluZyA9IGFlcyh4ID0gRmVjaGEsIHkgPSB2YWxvcikpICsKICBnZW9tX3BvaW50KCkKYGBgCgojIyMgTWFwZWFyIHZhcmlhYmxlcyBhIGVsZW1lbnRvcwoKVW5hIHBvc2libGUgc29sdWNpw7NuIHNlcsOtYSB1dGlsaXphciBvdHJhcyB2YXJpYWJsZXMgZGUgbnVlc3Ryb3MgZGF0b3MsIHBvciBlamVtcGxvIGVsIGB0aXBvYCB5ICptYXBlYXIqIGVsIGNvbG9yIGRlIGxvcyBwdW50b3MgZGUgYSBjdWVyZG8gYWwgYHRpcG9gIGRlIHRlbXBlcmF0dXJhLCBtw6F4aW1hLCBtw61uaW1hIHkgbWVkaWEuCgpgYGB7cn0KZ2dwbG90KGRhdGEgPSBiYXJpbG9jaGUsIG1hcHBpbmcgPSBhZXMoeCA9IEZlY2hhLCB5ID0gdmFsb3IpKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSB0aXBvKSkKYGBgCgpBaG9yYSBlc3TDoSB1biBwb2NvIG1lam9yLiBQb3IgZWplbXBsbyB5YSBwb2RlbW9zIHZlciBxdWUgZW4gZ2VuZXJhbCBsYXMgdGVtcGVyYXR1cmFzIG3DoXhpbWFzIChsb3MgcHVudG9zIHJvc2FzKSB0aWVuZW4gZW4gcHJvbWVkaW8gbWF5b3IgdmFsb3IgYSBsbyBsYXJnbyBkZSBsb3MgYcOxb3MgcXVlIGxhcyB0ZW1wZXJhdHVyYXMgbcOtbmltYXMgKGxvcyBwdW50b3MgY2VsZXN0ZXMpLiBBw7puIG5vIHBvZGVtb3MgaWRlbnRpZmljYXIgZW4gbHVnYXIgZG9uZGUgc2UgdG9tYXJvbiBsb3MgZGF0b3MgcGVybyB0ZW5lbW9zIGFsZ28gbcOhcyBkZSBpbmZvcm1hY2nDs24uIAoKCkFsZ28gbXV5IGltcG9ydGFudGUgYSB0ZW5lciBlbiBjdWVudGE6ICoqbG9zIHB1bnRvcyB0b21hbiB1biBjb2xvciBkZSBhY3VlcmRvIGEgdW5hIHZhcmlhYmxlIGRlIGxvcyBkYXRvcyoqLCB5IHBhcmEgcXVlIGdncGxvdDIgaWRlbnRpZmlxdWUgZXNhIHZhcmlhYmxlIChlbiBlc3RlIGNhc28gYHRpcG9gKSBlcyBuZWNlc2FyaW8gaW5jbHVpcmxhIGRlbnRybyBkZSB1bmEgZnVuY2nDs24gYGFlcygpYC4KCiMjIE90cmFzIGdlb21ldHLDrWFzCgpFc3RlIGdyw6FmaWNvIHBvc2libGVtZW50ZSBubyBzZWEgbXV5IGFkZWN1YWRvIHNpIHF1ZXJlbW9zIHZpc3VhbGl6YXIgbGEgKmV2b2x1Y2nDs24qIGRlIHVuYSB2YXJpYWJsZSBhIGxvIGxhcmdvIGRlbCB0aWVtcG8sIG5lY2VzaXRhbW9zIGNhbWJpYXIgbGEgZ2VvbWV0csOtYSBhIGxpbmVhcyB1c2FuZG8gYGdlb21fbGluZSgpYAoKYGBge3J9CmdncGxvdChkYXRhID0gbmEub21pdChiYXJpbG9jaGUpLCBtYXBwaW5nID0gYWVzKHggPSBGZWNoYSwgeSA9IHZhbG9yKSkgKwogIGdlb21fbGluZShhZXMoY29sb3IgPSB0aXBvKSkKYGBgCgo6Ojogey5hbGVydCAuYWxlcnQtc3VjY2Vzc30KRXMgcG9zaWJsZSBxdWUgdGUgZXN0w6lzIHByZWd1bnRhbmRvIHF1ZSBoYWNlIGVzZSBgbmEub21pdCgpYCBtZXRpZG8gYWRlbnRybyBkZWwgY8OzZGlnby4gUmVzdWx0YSBxdWUgbG9zIGdyw6FmaWNvcyBkZSBsw61uZWFzIGVuIGdlbmVyYWwgbm8gc2UgbGxldmFuIGJpZW4gY29uIGxvcyBkYXRvcyBmYWx0YW50ZXMgeSBuZWNlc2l0YW1vcyBzYWNhcmxvcyBwYXJhIHF1ZSBubyBub3MgaGFnYSBwcm9ibGVtYXMuIEVuIGxhIHZpZGEgcmVhbCB0ZW5kcsOtYW1vcyBxdWUgZGVjaWRpciBjb21vIHJlc29sdmVyIGVzdGUgcHJvYmxlbWEgZGUgdW5hIG1hbmVyYSBtZW5vcyBkcsOhc3RpY2EhCjo6OgoKUG9yIHN1ZXJ0ZSBsYXMgZnVuY2lvbmVzIGBnZW9tXyooKWAgdGllbmVuIG3DoXMgbyBtZW5vcyBub21icmVzIGFtaWdhYmxlcy4gUGVybyBlbCBncsOhZmljbyBzaWd1ZSB0ZW5pZW5kbyBwcm9ibGVtYXMgYWxnbyBtw6FzIHN1dGlsZXMuIEVuIGVzdGUgY2FzbyBlc3TDoSBoYWNpZW5kbyB1biBncsOhZmljbyBwb3IgdGlwbyBkZSB0ZW1wZXJhdHVyYSwgcGVybyBlbiBlbCBjYW1pbm8gbWV6Y2zDsyBsYXMgb2JzZXJ2YWNpb25lcyBxdWUgc2UgaGljaWVyb24gZW4gZWwgYWJyaWdvLCBhIGxhIGludGVtcGVyaWUgeSBlbiBlbCBzdWVsby4gU2kgZXN0dXZpw6lyYW1vcyBkaWJ1amFuZG8gZXN0ZSBncsOhZmljbyBjb24gbMOhcGl6IHkgcGFwZWwgbXV5IHBvc2libGVtZW50ZSBodWJpw6lyYW1vcyBpZGVudGlmaWNhZG8gbG9zIHB1bnRvcyBxdWUgY29ycmVzcG9uZGVuIGEgY2FkYSB0aXBvIGRlIHRlbXBlcmF0dXJhIHkgbHVnYXIgeSBsb3MgaHViacOpcmFtb3MgInVuaWRvIGNvbiBsw61uZWFzIiwgbmVjZXNpdGFtb3MgcXVlIGdncGxvdDIgaGFnYSBlc3RvLiDCv0PDs21vIGxlIGluZGljYW1vcyBxdWUgb2JzZXJ2YWNpb25lcyBjb3JyZXNwb25kZSBhIGNhZGEgdmFyaWFibGUgZGUgdGVtcGVyYXR1cmE/IE5lY2VzaXRhbW9zIHF1ZSBsb3MgKmFncnVwZSogcG9yIGxhcyB2YXJpYWJsZXMgYHRpcG9gIHkgYGx1Z2FyYCAowqFxdcOpIGJ1ZW5vIHF1ZSB0ZW5lbW9zIHRvZGEgZXNhIGluZm9ybWFjacOzbiBlbiBudWVzdHJhIGJhc2UgZGUgZGF0b3MhKS4KCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IG5hLm9taXQoYmFyaWxvY2hlKSwgbWFwcGluZyA9IGFlcyh4ID0gRmVjaGEsIHkgPSB2YWxvcikpICsKICBnZW9tX2xpbmUoYWVzKGNvbG9yID0gdGlwbywgZ3JvdXAgPSBpbnRlcmFjdGlvbih0aXBvLCBsdWdhcikpKQpgYGAKClVzYW1vcyBlbCBhcmd1bWVudG8gYGdyb3VwID1gIHkgZGUgbnVldm8sIGxvIGluY2x1aW1vcyBkZW50cm8gZGUgbGEgZnVuY2nDs24gYGFlcygpYCBwYXJhIGluZGljYXJsZSBhIGdncGxvdDIgcXVlIGJ1c3F1ZSBsYSB2YXJpYWJsZSBgdGlwb2AgeSBgbHVnYXJgIGRlbnRybyBkZWwgZGF0YS5mcmFtZSBxdWUgZXN0YW1vcyB1c2FuZG8uIEVuIGVzdGUgY2FzbyBjb21vIGVzdGFtb3MgYWdydXBhbmRvIHBvciBkb3MgdmFyaWFibGVzIHRlbmVtb3MgcXVlIGluY2x1aXIgbGEgZnVuY2nDs24gYGludGVyYWN0aW9uKClgLgoKWSBhaG9yYSBzaSwgY29uc2VndWltb3MgZWwgZ3LDoWZpY28gcXVlIGVzdGFtb3MgYnVzY2FuZG8uIAoKOjo6IHsuYWxlcnQgLmFsZXJ0LWluZm99CioqU2VndW5kbyBkZXNhZsOtbyoqCgpDdWFuZG8gbWVuY2lvbmFtb3MgcXVlIGdncGxvdDIgY29uc3RydXllIGdyw6FmaWNvcyBwb3IgY2FwYXMsIGxvIGRlY8OtYW1vcyBlbiBzZXJpbyEgSGFzdGEgYWhvcmEgdGVuZW1vcyBkb3MgY2FwYXM6IGVsIMOhcmVhIGRlbCBncsOhZmljbyB5IHVuYSBnZW9tZXRyw61hIChsYXMgbMOtbmVhcykuIAoKMS4gU3Vtw6EgdW5hIHRlcmNlcmEgY2FwYSBwYXJhIHZpc3VhbGl6YXIgcHVudG9zIGFkZW3DoXMgZGUgbGFzIGzDrW5lYXMuCjIuIMK/UG9ycXXDqSBsb3MgcHVudG9zIGFob3JhIG5vIHNpZ3VlbiBsb3MgY29sb3JlcyBkZSBsYXMgZGlzdGludGFzIHRlbXBlcmF0dXJhcz8KMy4gwr9RdcOpIGNhbWJpbyBwb2Ryw61hcyBoYWNlciBwYXJhIHF1ZSBsb3MgcHVudG9zIHRhbWJpw6luIHRlbmdhbiBjb2xvciBzZWfDum4gZWwgdGlwbyBkZSB0ZW1wZXJhdHVyYT8KOjo6CgpBY8OhIHN1cmdlIHVuYSBjYXJhY3RlcsOtc3RpY2EgaW1wb3J0YW50ZSBkZSBsYXMgY2FwYXM6IHB1ZWRlbiB0ZW5lciBhcGFyaWVuY2lhIGluZGVwZW5kaWVudGUgc2kgc29sbyAqbWFwZWFtb3MqIGVsIGNvbG9yIGVuIGxhIGNhcGEgZGUgbGFzIGzDrW5lYXMgeSBubyBlbiBsYSBjYXBhIGRlIGxvcyBwdW50b3MuIEFsIG1pc21vIHRpZW1wbywgc2kgcXVpc2nDqXJhbW9zIHF1ZSB0b2RhcyBsYXMgY2FwYXMgdGVuZ2EgbGEgbWlzbWEgYXBhcmllbmNpYSBwb2RlbW9zIGluY2x1aXIgZWwgYXJndW1lbnRvIGBjb2xvciA9IGBlbiBsYSBmdW5jacOzbiBnbG9iYWwgYGdncHBsb3QoKWAgbyByZXBldGlybG8gZW4gY2FkYSBjYXBhLgoKYGBge3J9CmdncGxvdChuYS5vbWl0KGJhcmlsb2NoZSksIGFlcyh4ID0gRmVjaGEsIHkgPSB2YWxvcikpICsKICBnZW9tX2xpbmUoYWVzKGNvbG9yID0gdGlwbywgZ3JvdXAgPSBpbnRlcmFjdGlvbih0aXBvLCBsdWdhcikpKSArCiAgZ2VvbV9wb2ludCgpCmBgYAoKOjo6IHsuYWxlcnQgLmFsZXJ0LXN1Y2Nlc3N9CgpTaSB0ZSBwcmVndW50YWJhcyBhIGRvbmRlIGZ1ZXJvbiBhIHBhcmFyIGVsIGBkYXRhID0gYCwgZWwgYG1hcHBpbmcgPSBgIHkgbG9zIG5vbWJyZXMgZGUgbG9zIGFyZ3VtZW50b3MgYWRlbnRybyBkZSBsYSBmdW5jacOzbiBgYWVzKClgLCBgeCA9IGAgZSBgeSA9IGAsIHJlc3VsdGEgcXVlIGVzdGFtb3MgYXByb3ZlY2hhbmRvIHF1ZSB0YW50byBnZ3Bsb3QyIGNvbW8gbm9zb3Ryb3MgYWhvcmEgc2FiZW1vcyBlbiBxdWUgb3JkZW4gcmVjaWJlIGxhIGluZm9ybWFjacOzbiBjYWRhIGZ1bmNpw7NuLiBTaWVtcHJlIGVsIHByaW1lciBlbGVtZW50byBxdWUgbGUgKnBhc2Vtb3MqIG8gaW5kaXF1ZW1vcyBhIGxhIGZ1bmNpw7NuIGBnZ3Bsb3QoKWAgc2Vyw6EgZWwgZGF0YS5mcmFtZS4KOjo6CgpBbGd1bm9zIGFyZ3VtZW50b3MgcGFyYSBjYW1iaWFyIGxhIGFwYXJpZW5jaWEgZGUgbGFzIGdlb21ldHLDrWFzIHNvbjoKCiogYGNvbG9yYCBvIGBjb2xvdXJgIG1vZGlmaWNhIGVsIGNvbG9yIGRlIGzDrW5lYXMgeSBwdW50b3MKKiBgZmlsbGBtb2RpZmljYSBlbCBjb2xvciBkZWwgw6FyZWEgZGUgdW4gZWxlbWVudG8sIHBvciBlamVtcGxvIGVsIHJlbGxlbm8gZGUgdW4gcHVudG8KKiBgbGluZXR5cGVgIG1vZGlmaWNhIGVsIHRpcG8gZGUgbMOtbmVhIChwdW50ZWFkYSwgY29udGludWEsIGNvbiBndWlvbmVzLCBldGMuKQoqIGBwY2hgIG1vZGlmaWNhIGVsIHRhbWHDsW8gZGVsIHB1bnRvCiogYHNpemVgIG1vZGlmaWNhIGVsIHRhbWHDsW8gZGUgbG9zIGVsZW1lbnRvcyAocG9yIGVqZW1wbG8gZWwgdGFtYcOxbyBkZSBwdW50b3MgbyBlbCBncm9zb3IgZGUgbMOtbmVhcykKKiBgYWxwaGFgIG1vZGlmaWNhIGxhIHRyYW5zcGFyZW5jaWEgZGUgbG9zIGVsZW1lbnRvcyAoMSA9IG9wYWNvLCAwID0gdHJhbnNwYXJlbnRlKQoqIGBzaGFwZWAgbW9kaWZpY2EgZWwgdGlwbyBkZSBwdW50byAoY8OtcmN1bG9zLCBjdWFkcmFkb3MsIHRyacOhbmd1bG9zLCBldGMuKQoKRWwgKm1hcGVvKiBlbnRyZSB1bmEgdmFyaWFibGUgeSB1biBwYXLDoW1ldHJvIGRlIGdlb21ldHLDrWEgc2UgaGFjZSBhIHRyYXbDqXMgZGUgdW5hICoqZXNjYWxhKiouIExhIGVzY2FsYSBkZSBjb2xvcmVzIGVzIGxvIHF1ZSBkZWZpbmUsIHBvciBlamVtcGxvLCBxdWUgbG9zIHB1bnRvcyBkb25kZSBsYSB2YXJpYWJsZSBgdGlwb2AgdG9tYSBlbCB2YWxvciBgIk1heGltYSJgIHZhbiBhIHRlbmVyIGVsIGNvbG9yIHJvc2EgKDxzcGFuIHN0eWxlPSJjb2xvcjojRjc3RDc1Ij4mIzk2Nzk7PC9zcGFuPiksIGRvbmRlIHRvbWEgZWwgdmFsb3IgYCJNaW5pbWEiYCwgY2VsZXN0ZSAoPHNwYW4gc3R5bGU9ImNvbG9yOiM2MTljZmYiPiYjOTY3OTs8L3NwYW4+KSwgZXRjLi4uCgo6Ojogey5hbGVydCAuYWxlcnQtc3VjY2Vzc30KKipNb2RpZmljYXIgZWxlbWVudG9zIHV0aWxpemFuZG8gdW4gdmFsb3Igw7puaWNvKioKCkVzIHBvc2libGUgcXVlIGVuIGFsZ8O6biBtb21lbnRvIG5lY2VzaXRlcyBjYW1iaWFyIGxhIGFwYXJpZW5jaWEgZGUgbG9zIGVsZW1lbnRvcyBvIGdlb21ldHLDrWFzIGluZGVwZW5kaWVudGVtZW50ZSBkZSBsYXMgdmFyaWFibGVzIGRlIHR1IGRhdGEuZnJhbWUuIFBvciBlamVtcGxvIHBvZHLDrWFzIHF1ZXJlciBxdWUgdG9kb3MgbG9zIHB1bnRvcyBzZWFuIGRlIHVuIMO6bmljbyBjb2xvcjogcm9qb3MuIEVuIGVzdGUgY2FzbyBgZ2VvbV9wb2ludChhZXMoY29sb3IgPSAicmVkIikpYCBubyB2YSBhIGZ1bmNpb25hciAtb2pvIHF1ZSBsb3MgY29sb3JlcyB2YW4gZW4gaW5nbMOpcy0uIExvIHF1ZSBlc2UgY8OzZGlnbyBkaWNlIGVzIHF1ZSBtYXBlZSBlbCBwYXLDoW1ldHJvIGdlb23DqXRyaWNvICJjb2xvciIgYSB1bmEgdmFyaWFibGUgcXVlIGNvbnRpZW5lIGVsIHZhbG9yIGAicmVkImAgcGFyYSB0b2RhcyBsYXMgZmlsYXMuIEVsIG1hcGVvIHNlIGhhY2UgYSB0cmF2w6lzIGRlIGxhIGVzY2FsYSwgcXVlIHZhIGEgYXNpZ25hcmxlIHVuIHZhbG9yIChyb3NhIDxzcGFuIHN0eWxlPSJjb2xvcjojRjc3RDc1Ij4mIzk2Nzk7PC9zcGFuPikgYSBsb3MgcHVudG9zIGNvcnJlc3BvbmRpZW50ZXMgYWwgdmFsb3IgYCJyZWQiYC4KCkFob3JhIHF1ZSBubyBub3MgaW50ZXJlc2EgKm1hcGVhciogZWwgY29sb3IgYSB1bmEgdmFyaWFibGUsIHBvZGVtb3MgbW92ZXIgZXNlIGFyZ3VtZW50byAqKmFmdWVyYSoqIGRlIGxhIGZ1bmNpw7NuIGBhZXMoKWA6IGBnZW9tX3BvaW50KGNvbG9yID0gInJlZCIpYC4gCjo6OiAKCiMjIFJlbGFjacOzbiBlbnRyZSB2YXJpYWJsZXMKCk11Y2hhcyB2ZWNlcyBubyBlcyBzdWZpY2llbnRlIGNvbiBtaXJhciBsb3MgZGF0b3MgY3J1ZG9zIHBhcmEgaWRlbnRpZmljYXIgbGEgcmVsYWNpw7NuIGVudHJlIGxhcyB2YXJpYWJsZXM7IGVzIG5lY2VzYXJpbyB1c2FyIGFsZ3VuYSB0cmFuc2Zvcm1hY2nDs24gZXN0YWTDrXN0aWNhIHF1ZSByZXNhbHRlIGVzYXMgcmVsYWNpb25lcywgeWEgc2VhIGFqdXN0YW5kbyB1bmEgcmVjdGEgbyBjYWxjdWxhbmRvIHByb21lZGlvcy4gCgpQYXJhIGFsZ3VuYSB0cmFuc2Zvcm1hY2lvbmVzIGVzdGFkw61zdGljYXMgY29tdW5lcywge2dncGxvdDJ9IHRpZW5lIGdlb21zIHlhIHByb2dyYW1hZG9zLCBwZXJvIG11Y2hhcyB2ZWNlcyBlcyBwb3NpYmxlIHF1ZSBuZWNlc2l0ZW1vcyBtYW5pcHVsYXIgbG9zIGRhdG9zIGFudGVzIGRlIHBvZGVyIGhhY2VyIHVuIGdyw6FmaWNvLiBBIHZlY2VzIGVzYSBtYW5pcHVsYWNpw7NuIHNlcsOhIGNvbXBsZWphIHkgZW50b25jZXMgcGFyYSBubyByZXBldGlyIGVsIGPDoWxjdWxvIG11Y2hhcyB2ZWNlcywgZ3VhcmRhcmVtb3MgbG9zIGRhdG9zIG1vZGlmaWNhZG9zIGVuIHVuYSBudWV2YSB2YXJpYWJsZS4gUGVybyB0YW1iacOpbiBwb2RlbW9zICplbmNhZGVuYXIqIGxhIG1hbmlwdWxhY2nDs24gZGUgbG9zIGRhdG9zIHkgZWwgZ3LDoWZpY28gcmVzdWx0YW50ZS4KClBvciBlamVtcGxvLCBjYWxjdWxlbW9zIGxhIHRlbXBlcmF0dXJhIG1lZGlhIG1lbnN1YWwgcGFyYSBjYWRhIHRpcG8geSBsdWdhciB1c2FuZG8gW2BkcGx5cmBdKDA1LWRwbHlyLUkuaHRtbCkgeSBsdWVnbyBncmFmaXF1ZW1vcyBsYSBgdF9tZW5zdWFsYCBhIGxvcyBsYXJnbyBkZSBsb3MgYG1lc2VzYDoKCmBgYHtyfQpiYXJpbG9jaGUgJT4lIAogIGdyb3VwX2J5KHRpcG8sIGx1Z2FyLCBtZXMgPSBsdWJyaWRhdGU6OmZsb29yX2RhdGUoRmVjaGEsIHVuaXQgPSAibW9udGgiKSkgJT4lIAogIHN1bW1hcmlzZSh0X21lbnN1YWwgPSBtZWFuKHZhbG9yLCBuYS5ybSA9IFRSVUUpKSAlPiUgCiAgZ2dwbG90KGFlcyhtZXMsIHRfbWVuc3VhbCkpICsgICAgIyBBY8OhIHNlIGFjYWJhbiBsb3MgJT4lIHkgY29taWVuemFuIGxvcyAiKyIKICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IHRpcG8pKQpgYGAKCjo6OiB7LmFsZXJ0IC5hbGVydC1zdWNjZXNzfQoKVGFsIHZleiBub3Rhc3RlIHF1ZSBhZ3JlZ2Ftb3MgdW5hIGZ1bmNpw7NuIChgbHVicmlkYXRlOjpmbG9vcl9kYXRlKClgKSBwYXJhIG9idGVuZXIgZWwgbWVzIGRlIGxhIHZhcmlhYmxlIGBGZWNoYWAuIExhIGZ1bmNpw7NuIGVzIGRlbCBwYXF1ZXRlIHtsdWJyaWRhdGV9IHF1ZSB2ZW1vcyBlbiBtw6FzIGRldGFsbGUgW2Fjw6FdKDA5LWx1YnJpZGF0ZS5odG1sKSwgeSBsbyBxdWUgaGFjZSBlcyByZWRvbmRlYXIgbGEgZmVjaGEgZW4gZXN0ZSBjYXNvIGFsIG1lcy4gRXN0byBlcyBuZWNlc2FyaW8gcG9ycXVlIHlhIG5vIG5vcyBpbnRlcmVzYSBsYSBpbmZvcm1hY2nDs24gZGUgbG9zIGTDrWFzIHBhcmEgY2FsY3VsYXIgdW4gcHJvbWVkaW8gbWVuc3VhbC4gVGFtYmnDqW4gcG9kcsOtYW1vcyBoYWJlciBjYWxjdWxhZG8gdW4gcHJvbWVkaW8gYW51YWwgeSBlbiBlc2UgY2FzbyB1c2FyIGBmbG9vcl9kYXRlKEZlY2hhLCB1bml0ID0gInllYXIpYC4KCjo6OgoKRXN0byBlcyBwb3NpYmxlIGdyYWNpYXMgYWwgb3BlcmFkb3IgYCU+JWAgcXVlIGxlICpwYXNhKiBlbCByZXN1bHRhZG8gZGUgYHN1bW1hcmlzZSgpYCBhIGxhIGZ1bmNpw7NuIGBnZ3Bsb3QoKWAuIFkgZXN0ZSByZXN1bHRhZG8gbm8gZXMgbmkgbcOhcyBuaSBtZW5vcyBxdWUgZWwgZGF0YS5mcmFtZSBxdWUgbmVjZXNpdGFtb3MgcGFyYSBoYWNlciBudWVzdHJvIGdyw6FmaWNvLiBFcyBpbXBvcnRhbnRlIG5vdGFyIHF1ZSB1bmEgdmV6IHF1ZSBjb21lbnphbW9zIGVsIGdyw6FmaWNvIHlhICoqbm8qKiBzZSBwdWVkZSB1c2FyIGVsIG9wZXJhZG9yIGAlPiVgIHkgbGFzIGNhcGFzIGRlbCBncsOhZmljbyBzZSAqc3VtYW4qIGNvbW8gc2llbXByZSBjb24gYCtgLgoKRXN0ZSBncsOhZmljbyBlbnRvbmNlcyBwYXJlY2UgbW9zdHJhciBsYSBldm9sdWNpw7NuIGRlIGxhIHRlbXBlcmF0dXJhIGEgbG8gbGFyZ28gZGVsIHRpZW1wbyBkZSB1bmEgbWFuZXJhIG3DoXMgbGltcGlhLiBQZXJvIHNlcsOtYSBpbnRlcmVzYW50ZSB2ZXIgZXNhIGV2b2x1Y2nDs24gbyByZWxhY2nDs24gZW4gZWwgdGllbXBvIG3DoXMgZXhwbMOtY2l0YW1lbnRlIGFncmVnYW5kbyB1bmEgbnVldmEgY2FwYSBjb24gYGdlb21fc21vb3RoKClgLgoKYGBge3J9CmJhcmlsb2NoZSAlPiUgCiAgZ3JvdXBfYnkodGlwbywgbHVnYXIsIG1lcyA9IGx1YnJpZGF0ZTo6Zmxvb3JfZGF0ZShGZWNoYSwgdW5pdCA9ICJtb250aCIpKSAlPiUgCiAgc3VtbWFyaXNlKHRfbWVuc3VhbCA9IG1lYW4odmFsb3IsIG5hLnJtID0gVFJVRSkpICU+JSAKICBnZ3Bsb3QoYWVzKG1lcywgdF9tZW5zdWFsKSkgKyAgIAogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gdGlwbykpICsKICBnZW9tX3Ntb290aCgpCmBgYAoKQ29tbyBkaWNlIGVuIGVsIG1lbnNhamUsIHBvciBkZWZlY3RvIGBnZW9tX3Ntb290aCgpYCBzdWF2aXphIGxvcyBkYXRvcyB1c2FuZG8gZWwgbcOpdG9kbyAqbG9lc3MqIChyZWdyZXNpw7NuIGxpbmVhbCBsb2NhbCkgY3VhbmRvIGhheSBtZW5vcyBkZSAxMDAwIGRhdG9zLiBTZWd1cmFtZW50ZSB2YSBhIHNlciBtdXkgY29tw7puIHF1ZSBxdWllcmFzIGFqdXN0YXIgdW5hIHJlZ3Jlc2nDs24gbGluZWFsIGdsb2JhbC4gRW4gZXNlIGNhc28sIGhheSBxdWUgcG9uZXIgYG1ldGhvZCA9ICJsbSJgOgoKYGBge3J9CmJhcmlsb2NoZSAlPiUgCiAgZ3JvdXBfYnkodGlwbywgbHVnYXIsIG1lcyA9IGx1YnJpZGF0ZTo6Zmxvb3JfZGF0ZShGZWNoYSwgdW5pdCA9ICJtb250aCIpKSAlPiUgCiAgc3VtbWFyaXNlKHRfbWVuc3VhbCA9IG1lYW4odmFsb3IsIG5hLnJtID0gVFJVRSkpICU+JSAKICBnZ3Bsb3QoYWVzKG1lcywgdF9tZW5zdWFsKSkgKyAgIAogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gdGlwbykpICsKICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iKQpgYGAKCkVuIGdyaXMgbm9zIG11ZXN0cmEgZWwgaW50ZXJ2YWxvIGRlIGNvbmZpYW56YSBhbCByZWRlZG9yIGRlIGVzdGUgc3Vhdml6YWRvLgoKQ8OzbW8gY3VhbHF1aWVyIGdlb20sIHBvZGVtb3MgbW9kaWZpY2FyIGVsIGNvbG9yLCBlbCBncm9zb3IgZGUgbGEgbMOtbmVhIHkgY2FzaSBjdWFscXVpZXIgY29zYSBxdWUgc2UgdGUgb2N1cnJhLgoKOjo6IHsuYWxlcnQgLmFsZXJ0LWluZm99CioqVGVyY2VyIGRlc2Fmw61vKioKCk1vZGlmaWPDoSBlbCBzaWd1aWVudGUgY8OzZGlnbyBwYXJhIG9idGVuZXIgZWwgZ3LDoWZpY28gcXVlIHNlIG11ZXN0cmEgbcOhcyBhYmFqby4KCmBgYHtyIGV2YWw9RkFMU0V9CmJhcmlsb2NoZSAlPiUgCiAgZ3JvdXBfYnkoX19fX19fLCBsdWdhciwgbWVzID0gbHVicmlkYXRlOjpmbG9vcl9kYXRlKEZlY2hhLCB1bml0ID0gIm1vbnRoIikpICU+JSAKICBzdW1tYXJpc2UodF9tZW5zdWFsID0gbWVhbih2YWxvciwgbmEucm0gPSBUUlVFKSkgJT4lIAogIGdncGxvdChhZXMobWVzLCBfX19fX19fKSkgKyAgIAogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gdGlwbyksIHNoYXBlID0gX19fXywgc2l6ZSA9IDIpICsKICBnZW9tX3Ntb290aChjb2xvciA9IHRpcG8sIG1ldGhvZCA9ICJsbSIpCmBgYAoKYGBge3IgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KYmFyaWxvY2hlICU+JSAKICBncm91cF9ieSh0aXBvLCBsdWdhciwgbWVzID0gbHVicmlkYXRlOjpmbG9vcl9kYXRlKEZlY2hhLCB1bml0ID0gIm1vbnRoIikpICU+JSAKICBzdW1tYXJpc2UodF9tZW5zdWFsID0gbWVhbih2YWxvciwgbmEucm0gPSBUUlVFKSkgJT4lIAogIGdncGxvdChhZXMobWVzLCB0X21lbnN1YWwpKSArICAgCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSB0aXBvKSwgc2hhcGUgPSAxNywgc2l6ZSA9IDIpICsKICBnZW9tX3Ntb290aChhZXMoY29sb3IgPSB0aXBvKSwgbWV0aG9kID0gImxtIikKYGBgCjo6OiAKCiMjIEdyYWZpY2FuZG8gZW4gcGFuZWxlcwoKVmltb3MgcXVlIGVzIHBvc2libGUgZ3JhZmljYXIgbcOhcyBkZSBkb3MgdmFyaWFibGVzIGVuIHVuIGdyw6FmaWNvIG1hcGVhbmRvIHVuYSB2YXJpYWJsZSBhbCBgY29sb3JgIG8gcG9yIGVqZW1wbG8gZWwgdGlwbyBkZSBsw61uZWEgbyBgbGluZXR5cGVgIHBhcmEgb2JzZXJ2YXIgbGEgcmVsYWNpw7NuIGVudHJlIGxhcyAzIHZhcmlhYmxlcy4gVGFtYmnDqW4gcG9kcsOtYW1vcyBoYWJlciBpbnRlbnRhbmRvIHJlc29sdmVyIGVsIHByb2JsZW1hIGdlbmVyYW5kbyB1biBncsOhZmljbyBwb3IgY2FkYSBjb2xvciBmaWx0cmFuZG8gbGFzIG9ic2VydmFjaW9uZXMgY29ycmVzcG9uZGllbnRlcy4KCgpgYGB7cn0KYmFyaWxvY2hlICU+JSAKICBncm91cF9ieSh0aXBvLCBsdWdhciwgbWVzID0gbHVicmlkYXRlOjpmbG9vcl9kYXRlKEZlY2hhLCB1bml0ID0gIm1vbnRoIikpICU+JSAKICBzdW1tYXJpc2UodF9tZW5zdWFsID0gbWVhbih2YWxvciwgbmEucm0gPSBUUlVFKSkgJT4lIAogIGZpbHRlcih0aXBvID09ICJNYXhpbWEiKSAlPiUgCiAgZ2dwbG90KGFlcyhtZXMsIHRfbWVuc3VhbCkpICsgICAKICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IHRpcG8pKQpgYGAKClBlcm8gc2Vyw61hIG11Y2jDrXNpbW8gdHJhYmFqbyBzaSB0ZW5lbW9zIHF1ZSBoYWNlciBlc3RvIHBhcmEgY2FkYSB1bmEgZGUgbGFzIHBvc2libGVzIGNhdGVnb3LDrWFzIGRlLiBMYSBidWVuYSBub3RpY2lhIGVzIHF1ZSB7Z2dwbG90Mn0gdGllbmUgdW4gcGFyIGRlIGZ1bmNpb25lcyBqdXN0byBwYXJhIHJlc29sdmVyIGVzdGUgcHJvYmxlbWE6CgpgYGB7cn0KYmFyaWxvY2hlICU+JSAKICBncm91cF9ieSh0aXBvLCBsdWdhciwgbWVzID0gbHVicmlkYXRlOjpmbG9vcl9kYXRlKEZlY2hhLCB1bml0ID0gIm1vbnRoIikpICU+JSAKICBzdW1tYXJpc2UodF9tZW5zdWFsID0gbWVhbih2YWxvciwgbmEucm0gPSBUUlVFKSkgJT4lIAogIGdncGxvdChhZXMobWVzLCB0X21lbnN1YWwpKSArICAgCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSB0aXBvKSkgKwogIGZhY2V0X3dyYXAofnRpcG8pCmBgYAoKRXN0YSBudWV2YSBjYXBhIGNvbiBgZmFjZXRfd3JhcCgpYCBkaXZpZGUgYWwgZ3LDoWZpY28gaW5pY2lhbCBlbiAzIHBhbmVsZXMgbyBmYWNldHMsIHVubyBwb3IgY2FkYSBjb2xvciAoeSBjYWRhIHRpcG8gZGUgdGVtcGVyYXR1cmEpLiBFc3RhIGZ1bmNpw7NuIHJlcXVpZXJlIHNhYmVyIHF1ZSB2YXJpYWJsZSBzZXLDoSBsYSByZXNwb25zYWJsZSBkZSBzZXBhcmFyIGxvcyBwYW5lbGVzIHkgcGFyYSBlc28gc2UgdXNhIGxhIG5vdGFjacOzbiBkZSBmdW5jaW9uZXMgZGUgUjogYH50aXBvYC4gRXN0byBzZSBsZWUgY29tbyBnZW5lcmFyIHBhbmVsZXMg4oCcZW4gZnVuY2nDs24gZGVsIHRpcG8gKGRlIHRlbXBlcmF0dXJhKeKAnS4KCsK/WSBzaSBxdWlzacOpcmFtb3MgZ2VuZXJhciBwYW5lbGVzIGEgcGFydGlyIGRlIDIgdmFyaWFibGVzPyBQYXJhIGVzbyBleGlzdGUgYGZhY2V0X2dyaWQoKWAuIEVuIGVzdGUgZ3LDoWZpY28gZ2VuZXJhbW9zIHBhbmVsZXMgdmllbmRvIGxhIOKAnHJlbGFjacOzbiBlbnRyZSBlbCB0aXBvIHkgZWwgbHVnYXLigJ0gZG9uZGUgc2UgbWlkacOzIGxhIHRlbXBlcmF0dXJhIHkgcG9yIGVqZW1wbG8gZW4gZWwgcHJpbWVyIHBhbmVsIGFycmliYSBhIGxhIGl6cXVpZXJkYSBwb2RyZW1vcyBvYnNlcnZhciBsYSB0ZW1wZXJhdHVyYSBtw6F4aW1hIGVuIGVsIGFicmlnby4gRW4gZXN0ZSBjYXNvIG1hcGVhciBlbCB0aXBvIGRlIHRlbXBlcmF0dXJhIGFsIGNvbG9yIGRlbG9zIHB1bnRvcyBubyBwYXJlY2Ugc2VyIG5lY2VzYXJpbyB5YSBxdWUgY2FkYSBjb2x1bW5hIHlhIG5vcyBwZXJtaXRlIGlkZW50aWZpY2FyIGVzbywgc2luIGVtYmFyZ28gZW4gYWxndW5vcyBjYXNvcyBheXVkYSBhIGxlZXIgZWwgZ3LDoWZpY28gbcOhcyByw6FwaWRvLgoKRW4gZXN0ZSBjYXNvIHRhbWJpw6luIG5vdGFtb3MgcXVlIGVzdGEgYmFzZSBkZSBkYXRvcyBlc3TDoSBwb2JsYWRhIGRlIGRhdG9zIGZhbHRhbnRlcyB5YSBxdWUgdmFyaW9zIHBhbmVsZXMgcXVlZGFuIHZhY8Otb3MuIAoKYGBge3J9CmJhcmlsb2NoZSAlPiUgCiAgZ3JvdXBfYnkodGlwbywgbHVnYXIsIG1lcyA9IGx1YnJpZGF0ZTo6Zmxvb3JfZGF0ZShGZWNoYSwgdW5pdCA9ICJtb250aCIpKSAlPiUgCiAgc3VtbWFyaXNlKHRfbWVuc3VhbCA9IG1lYW4odmFsb3IsIG5hLnJtID0gVFJVRSkpICU+JSAKICBnZ3Bsb3QoYWVzKG1lcywgdF9tZW5zdWFsKSkgKyAgIAogIGdlb21fcG9pbnQoYWVzKGNvbG9yID0gdGlwbykpICsKICBmYWNldF9ncmlkKGx1Z2FyIH4gdGlwbykKYGBgCgo6Ojogey5hbGVydCAuYWxlcnQtaW5mb30KKipDdWFydG8gZGVzYWbDrW8qKgoKQWhvcmEgZXMgdHUgdHVybm8sIGludGVudMOhIHJlcHJvZHVjaXIgZWwgc2lndWllbnRlIGdyw6FmaWNvIGNvbiB0b2RvIGxvIHZpc3RvIGFycmliYS4KCmBgYHtyIGVjaG89RkFMU0UsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmJhcmlsb2NoZSAlPiUgCiAgbmEub21pdCgpICU+JSAKICBncm91cF9ieSh0aXBvLCBsdWdhciwgbWVzID0gbHVicmlkYXRlOjpmbG9vcl9kYXRlKEZlY2hhLCB1bml0ID0gIm1vbnRoIikpICU+JSAKICBzdW1tYXJpc2UodF9tZW5zdWFsID0gbWVhbih2YWxvciwgbmEucm0gPSBUUlVFKSkgJT4lIAogIGdncGxvdChhZXMobWVzLCB0X21lbnN1YWwpKSArICAgCiAgZ2VvbV9saW5lKGFlcyhjb2xvciA9IHRpcG8pKSArCiAgZ2VvbV9wb2ludChhZXMoY29sb3IgPSB0aXBvKSkgKwogIGZhY2V0X2dyaWQodGlwbyB+IGx1Z2FyKQpgYGAKCihQc3NzISBQcm9iw6EgY2FtYmlhciBlbCBvcmRlbiBlbiBgZmFjZXRfZ3JpZCgpYCkKCjo6OgoKUXVlZGFuIG11Y2hhcyBvdHJhcyBnZW9tZXRyw61hcyBxdWUgbm8gZGVzY3JpYmltb3MsIHNpIHRlIGludGVyZXNhIHBvciBlamVtcGxvIGFwcmVuZGVyIGEgaGFjZXIgdW4gZ3LDoWZpY28gZGUgYmFycmFzIG8gdW4gYm94cGxvdCBwb2TDqXMgcmV2aXNhciBlc3RlIFtlcGlzb2Rpb10oaHR0cHM6Ly9wYW9jb3JyYWxlcy5naXRodWIuaW8vZGVFeGNlbGFSLzA3LWdyYWZpY29zLUlJLmh0bWwpLgoKPGRpdiBjbGFzcz0iYnRuLWdyb3VwIiByb2xlPSJncm91cCIgYXJpYS1sYWJlbD0iTmF2ZWdhY2nDs24iPgogIDxhIGhyZWY9ICIwNi1kcGx5ci10aWR5ci1JSS5odG1sIiBjbGFzcyA9ICJidG4gYnRuLXByaW1hcnkiPkFudGVyaW9yPC9hPgogIDxhIGhyZWY9ICIwOC1sdWJyaWRhdGUuaHRtbCIgY2xhc3MgPSAiYnRuIGJ0bi1wcmltYXJ5Ij5TaWd1aWVudGU8L2E+CjwvZGl2Pgo=