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).

  1. Sumá una tercera capa para visualizar puntos además de las líneas.
  2. ¿Porqué los puntos ahora no siguen los colores de las distintas temperaturas?
  3. ¿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
  • fillmodifica 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=