1. Introducción


En el tutorial anterior aprendimos a cargar datos en R. Sin embargo, es difícil que en una aplicación real tengamos los datos tal y como los necesitamos para hacer nuestro análisis. Habitualmente tendremos que trabajar los datos para arreglarlos. Este proceso, que en castellano podría llamarse “limpieza” o procesado de datos, se conoce en inglés como data munging or data wrangling.

En el curso vamos a trabajar/manejar los datos usando un conjunto de paquetes asociados asociados con el enfoque conocido como tidyverse. Como puedes ver en la imagen, ya hemos importado los datos y, antes de empezar a hacer el verdadero análisis, tenemos que pasar por 2 etapas más:

  • hacer nuestros datos tidy
  • arreglarlos para que sean útiles para nuestros propósitos



Se suele decir que el procesado/limpieza de los datos suele ocupar un 80% del tiempo de un análisis de datos. Quizás sea una cifra un poco exagerada, pero, en cualquier caso, es una tarea que ocupa tiempo y que puede llegar a ser tediosa y frustrante si no se dispone de las herramientas adecuadas. Incluso datos que parecen que ya están trabajados es bastante fácil que tengamos que trabajarlos para adaptarlos a nuestras necesidades.

Una secuencia "real" de tweets:

0930: How lucky I am to work on clean datasets curated by true professionals.
1330: Huh. Some inconsistencies here. No bigs. Ill just write up some quick and dirty regex to clean this up.

1720: I WILL BURN THIS HERETICAL DATA CENTER AND SCATTER ITS ASHES (traducción: me cago en todo lo que se menea)

En clase utilizamos datos reales, pero la verdad es que suelen ya estar casi limpios del todo. Estamos aprendiendo.

Classroom data are like teddy bears; real data are like a grizzly with salmon blood dripping out its mouth. —- [@JennyBryan]

Como dice Albert Y. Kim en estas transparencias los datos utilizados para aprender a manejar datos tienen que ser realistas pero sin llegar a ser intimidantes.


En este tutorial aprenderemos a limpiar y transformar datos en R. Priorizaremos la nueva forma de hacer las cosas en R (o workflow) conocido como tidyverse. En los últimos años se ha convertido, por varias razones, en el enfoque estándar; a pesar de ello, cada cierto tiempo vuelve a reabrirse el debate sobre cómo enseñar/aprender R y si es apropiado priorizar el tidyverse sobre R-base. Aquí tienes un hilo de twitter donde se debate sobre este tema.


Aquí tenéis un post sobre las diferencias entre las funciones de R-base y las del tidyverse para el procesado de datos, y aquí otro post de un nuevo convencido de las bondades de esta nueva forma de manipular datos en R. Como ejemplo:

Up until last year my R workflow was not dramatically different from when I started using R more than 10 years ago. Thanks to several R package authors, most notably Hadley Wickham, my workflow has changed for the better using dplyr, magrittr, tidyr and ggplot2. Given how much I’ve enjoyed the speed and clarity of the new workflow



Tidyverse

¿Qué es esto del tidyverse?


Con la palabra tidyverse se hace referencia a una “nueva” forma de afrontar el análisis de datos en R en la que se hace uso de un grupo de paquetes que trabajan en armonía porque comparten ciertos principios, como por ejemplo, la forma de estructurar los datos.

La mayoría de estos paquetes han sido desarrollados por (o al menos con la colaboración de) Hadley Wickham. Esta es la página web del tidyverse

No es necesario, pero si quieres conocer un poco mejor qué es el tidyverse, puedes hacerlo leyendo The tidy tools manifesto. Está cita es un buen referente de la filosofía o enfoque del tidyverse

Programs must be written for people to read, and only incidentally for machines to execute – Hal Abelson


Para continuar entendiendo qué es esto del tidyverse, citaré 2 de sus principios:

  • Los scripts deben ser “fácilmente” legibles por las personas

  • Resolver problemas complejos encadenando funciones simples con el operador %>%



The pipe (%>%)

Este operador ocupa un lugar fundamental en el tidyverse. Permite resolver un problema complejo no de una sola vez, sino encadenando llamadas a funciones que se van encadenando con el operador %>%. Este operador facilita mucho la lectura e interpretación del código, ya que se van encadenando operaciones sencillas para, poco a poco, conseguir transformaciones de datos complejas. El operador pipe se lo debemos a Stefan Bache en su pkg magrittr.


En palabras, lo que hace este operador es pasar el elemento que está a su izquierda como un argumento de la función que tiene a la derecha. Así al principio parece complicado.

Con expresiones el operador pipe hace:

f(object, argumentos de la función) ES EQUIVALENTE a object %>% f(argumentos de la función)


Se entiende mejor con ejemplos sencillos. Las siguientes dos instrucciones de R hacen exactamente lo mismo: permiten ver las 4 primeras filas del penguins dataset.

library(palmerpenguins)

head(penguins, n = 4)         #- forma habitual de llamar/usar la función head()

penguins %>% head(. , n = 4)  #- usando el operador pipe

La primera expresión es la manera habitual de usar/llamar a la función head(). La segunda expresión es la sintaxis, la forma que hay que usar, si trabajamos con el operador pipe.

Así, a primera vista, parece que el operador %>% no supone ninguna ventaja, sólo es una forma distinta de ejecutar o llamar a una función, y a primera vista parece complicar las cosas. Sí, eso es cierto, si solo usas una función no tendría mucho sentido usar %>%, pero cuando tienes que hacer una sucesión de cálculos, una sucesión de llamadas a funciones, facilita mucho la lectura del código y por tanto el análisis. Lo vemos enseguida.

Para entender un poco más el funcionamiento de %>%, has de ver que estas tres instrucciones son equivalentes.

head(penguins, n = 4)         #- forma habitual de llamar/usar la función head()

penguins %>% head(. , n = 4)  #- usando el operador pipe (con el punto actuando como placeholder)

penguins %>% head(n = 4)      #- usando el operador pipe (SIN el punto)

El punto de la segunda expresión señala, le dice a the pipe donde debe situarse el argumento de la izquierda dentro de la función; en nuestro ejemplo le dice a %>% que penguins debe situarse en el primer slot de head(). El punto . le está diciendo a %>% donde debe situarse penguins; es decir, el punto actúa, lo estamos usando, como un “placeholder”.

La tercera expresión también funciona porque si no usamos el punto (.), entonces, por defecto, el operador pipe situará penguins en el primer slot de la función, en nuestro caso situará a penguins en el primer slot de head().

La forma más habitual es no poner el .; es decir, la tercera expresión. La razón es simplemente que se ahorra tiempo al escribir, aunque la segunda expresión es mucho más explicita, más descriptiva, de lo que hace el operador pipe.


Para casi terminar de entender la sintaxis del operador pipe. Intentad ver si entendéis la siguiente instrucción:

4 %>% head(penguins, .)

Si no sabéis lo que hace, siempre podéis ejecutar la instrucción en la consola de RStudio.

Recuerda: Cuando usamos el operador pipe, tenemos obligatoriamente que usar el punto si queremos que el argumento de la izquierda se sitúe en un slot diferente del primer slot


Para acabar nuestro repaso a %>% mirad por qué no funciona la siguiente instrucción:

4 %>% head(penguins)

El operador pipe quiere llevar el 4 al primer slot de head() ya que si no ponemos el punto, ese es su comportamiento por defecto. Sin embargo, al ejecutar la expresión, el interprete de R nos devuelve un mensaje de error. ¿Por qué? Tendrás que mirar la ayuda de la función con help(head).


Aún no sabemos muy bien cuál es su utilidad, pero ya conocemos la sintaxis de %>%. Lo que hace que este operador sea tan útil es que las pipes se pueden encadenar.


El operador pipe podemos leerlo como entonces y permite encadenar sucesivas llamadas a funciones. Por ejemplo:

penguins %>% filter(sex == "female") %>% 
             group_by(species) %>% 
             summarise(peso_medio = mean(body_mass_g))


La anterior linea de código R hace:

  1. coge los datos de pingüinos y selecciona (o filtra) las filas/pinguinos cuyo valor de la variable sex es female; es decir, seleccionamos los pingüinos hembras, entonces (o después)
  2. agrupa los datos/pingüinos por la variable species, entonces
  3. calcula la media de body_mass_g

En conjunto, encadenando las 3 funciones hemos seleccionado las filas que pertenecen a pingüinos hembras, hemos agrupado las pingüino hembras en función de su especie (hay 3 especies de pingüinos) y calculado el peso medio de cada uno de las 3 especies de pingüinos; es decir, hemos calculado el peso medio de las pingüinos hembra en cada uno de las tres especies de pingüinos.


Con esta nueva sintaxis (que permite el operador pipe) ya no necesitamos anidar funciones, sino que las instrucciones van una después de otra. Es mucho más fácil de leer y de escribir. Esta idea de que es mucho más fácil escribir à la tidyverse no se llega a apreciar con los ejemplos que hemos hecho en esta sección, pero se hará evidente cuando empecemos a encadenar operaciones con dplyr. Como ejemplo este tweet.


No te va a resultar sencillo porque no sabes que es letters, ni paste0(), ni toupper(), pero intenta entender por ti mismo que hace la siguiente linea de código. ya sabes que siempre puedes ejecutarla y ver que hace, y mucho mejor si la ejecutas por trozos para ir viendo poco a poco qué hace:

letters %>% paste0( "-----" ,  .  ,  "!!!" ) %>% toupper


Un poco más acerca de the pipe (%>%) [OPCIONAL]

De forma más técnica. Aquí podéis ver el funcionamiento del operador pipe:

library("magrittr")

#-------- Rule 1
f(xx)     es equivalente a     xx %>% f

#-------- Rule 2
g(xx, n = 5)
xx %>% g(n = 5)

#-------- Rule 3
g(f(xx), n = 5)
xx %>% f %>% g(n = 5)

Se lee como "Take xx then do f then do g with n = 5".

#-------- Rule 4
f(y, x)
x %>% f(y, .)

#-------- Rule 5
f(y, z = x)
x %>% f(y, z = .)

#(!!!!)------------- BONUS: The input to the pipeline can itself be a placeholder!!
num_unique <- . %>% unique %>% length       

num_unique(iris$Species)

iris$Species %>% num_unique

-----

num_unique es equivalente a : f <- function(.) length(unique(.)) 


Un buen recurso para aprender el uso de %>% son estas transparencias. Una exposición más detallada de la sintaxis y posibilidades del operador %>%, así como la de otros operadores como %T>% y %<>% puedes encontrarla aquí.


The “tee pipe” (%T>%) permite hacer cosas como esta:

#- !!!!!!
rnorm(200) %>% matrix(ncol = 2) %T>%
plot %>% # plot usually does not return anything. 
colSums

The “tee pipe”, como el pipe original, pasa el argumento de la izquierda a la función de la derecha, PERO devuelve el propio valor original, no devuelve el resultado de la evaluación de la función. Como se señala aquí, este comportamiento es útil cuando se usa la función por sus side-effects; es decir, para imprimir o graficar. Yo la utilidad que le veo es hacer chequeos dentro de una secuencia de pipes, como por ejemplo hacen en este tweet


The “exposition pipe” (%$%) también del pkg magrittr. En este post nos explican su utilidad. Pero solo leedlo cuando ya seáis usuarios intermedios de R. Sirve para hacer accesibles las columnas de un dataframe a funciones que no admiten dataframes como la función cor(). De esta forma podemos integrar en nuestro pipeline funciones que no están preparadas para el tidyverse.

#- !!!
library(magrittr)
iris %>% mean(Sepal.Length)   #- no funciona
iris %$% mean(Sepal.Length)   #- con the exposition pipe sí funciona

iris %>% cor(Sepal.Length, Sepal.Width)  #- no funciona
iris %$% cor(Sepal.Length, Sepal.Width)

Si no usásemos este nueva pipe, tendríamos que hacer lo siguiente:

cor(iris$Sepal.Length, iris$Sepal.Width)


Muchas funciones de R-base no están preparadas para trabajar con el operador pipe. Son funciones que se escribieron antes de que se creara %>%. Se puede tratar de reescribir código en “R-base” usando %>% pero no tiene mucho sentido y no es muy agradable; sin embargo si se trabaja con el tidyverse, utilizar the pipe hace la sintaxis muy fluida. Como ejemplo de esto el siguiente chunk:

library(tidyverse)    

x1 <- c(-5:5, NA)  #- es un vector

#- escribiendo à la R-base
mean(x1[x1>0], na.rm = TRUE)   #-  calcula la media de los valores positivos de x1
sum(x1[!is.na(x1)])            #-  calcula la suma de los valores de x1 que no son NA

#- ahora haremos lo mismo, seguimos usando R-base, pero con el operador pipe (!!!!)
x1 %>% .[.>0] %>% mean(., na.rm = TRUE)
x1 %>% .[!is.na(.)]  %>% sum

#- podríamos trabajar con data.frames usando the exposition pipe
df <- as.data.frame(x1)   #- tidyverse usa data.frames
df %$% x1 %>% .[.>0] %>% mean(., na.rm = TRUE)
df %$% x1 %>% .[!is.na(.)]  %>% sum

#- con tydiverse
df <- as.data.frame(x1)   #- tidyverse usa data.frames
df %>% filter(x1 > 0) %>% summarise(mean_x1 = mean(x1, na.rm = TRUE))
df %>% filter(!is.na(x1)) %>% summarise(suma_x1 = sum(x1))


Bien, ya sabemos como funciona “the pipe”. Volvamos al tidyverse y a aprender a manipular datos en R.


Principales pkgs del tidyverse

Como puede verse en su página web, los principales packages del tidyverse son:

  • readr: para importar datos

  • tidyr: para convertir los datos a tidy data

  • dplyr: para manipular datos

  • ggplot2: para hacer gráficos

  • tibble: data frames actualizados

  • forcast: para manipular factores

  • stringr: para manipular strings

  • purrr: para functional programming

  • y algunos más

Nos centraremos en los cuatro primeros paquetes, principalmente en dplyr y ggplot2.


Los principales paquetes del tidyverse se han “agrupado” en un metapaquete llamado tidyverse, así que cuando ejecutas library(tidyverse) en realidad estás cargando varios paquetes del tidyverse



2. Tidy data (tidyr)


If I had one thing to tell biologists learning bioinformatics, it would be write code for humans, write data for computers. —-— Vince Buffalo (@vsbuffalo)

Y si vamos a manejar datos con R y a la manera del tidyverse, como Jenny Bryan señala en su excelente tutorial sobre tidy data:

An important aspect of “writing data for computers” is to make your data TIDY. —- Jenny Bryan


Antes de comenzar a manipular los datos à la tidyverse, es conviene saber que se entiende por tidy data. La razón es que los paquetes del tidyverse trabajan mejor si los datos están en formato tidy. Es fácil!!


¿Qué son los tidy data?

Ahora lo veremos, pero enfatizar que si los datos son tidy (si siguen ese formato) será más fácil trabajar con ellos con el tidyverse, ya sea para manipularlos o para hacer gráficos.

De forma sencilla, tidy data son simplemente datos organizados de una determinada manera. Además es justo de la manera a la estamos familiarizados. De forma más precisa se puede leer aquí, o de forma más elaborada [aquí] :

Tidy datasets provide a standardized way to link the structure of a dataset (its physical layout) with its semantics (its meaning). —– Hadley Wickham


La mayoría de datos en Ciencias Sociales se ajustan a la categoría de datos tabulares; es decir, están organizados en filas y columnas. En R este tipo de datos se almacenan en dataframes (o tibbles). En esencia, un dataframe será tidy si cada columna es una variable y cada fila es una unidad de análisis (persona, país, región etc…); es decir, cada celda contiene el valor de una variable para una unidad de análisis.

A dataset is a collection of values. Every value belongs to a variable and an observation. A variable contains all values that measure the same underlying attribute (like height, temperature, duration) across units. An observation contains all values measured on the same unit (like a person, or a day, or a race) across attributes


No parece muy alejado de lo que estamos acostumbrados. Pero …. desarrollemos la idea un poco más.


Un ejemplo de datos (no tidy)

Supongamos que la variable (o atributo) a medir es el salario y la unidad de análisis las personas. Hemos recogido datos para 3 personas. Veámoslos:

data_1 <- data.frame(
            year  = c("2014", "2015", "2016"),  
            Pedro = c(100, 500, 200), 
            Carla = c(400, 600, 250), 
            María = c(200, 700, 900)  )
data_1
Tabla 1: Salario de 3 personas:
year Pedro Carla María
2014 100 400 200
2015 500 600 700
2016 200 250 900

Entendemos perfectamente estos datos, visualmente son cómodos, pero ¿son tidy data? NO* porque los individuos (o unidades de análisis) están en columnas.


Un ejemplo de datos (tidy* pero wide)

Exactamente los mismos datos podrían estructurarse así:

data_2 <- data.frame(names = c("Pedro", "Carla", "María"), 
                      W_2014 = c(100, 400, 200), 
                      W_2015 = c(500, 600, 700),
                      W_2016 = c(200, 250, 900)   )

data_2
Tabla 2: Salario de 3 personas (wide format)
names W_2014 W_2015 W_2016
Pedro 100 500 200
Carla 400 600 250
María 200 700 900

También es un formato fácil de entender por nosotros, pero ¿son tidy? SI*, pero …

  • Es el formato al que estamos más acostumbrados (individuos o registros en filas y “variables” en columnas). ¿Realmente el W de 2014 es una variable?

  • En jerga del tidyverse este formato de datos es “wide” (o ancho)


Podemos trabajar tranquilamente con el anterior formato, PERO, si queremos sacar todo el provecho al tidyverse es mejor tener los datos en long format.


Un ejemplo de datos (tidy-tidy y long)

data_3 <- data.frame(
            names =rep(c("Pedro", "Carla", "María"), times = 3),  
            year = rep(c("2014", "2015", "2016"), each = 3),
            salario = c(100, 400, 200, 500, 600, 700, 200, 250,900) )
data_3
Tabla 3: Salario de 3 personas (long format)
names year salario
Pedro 2014 100
Carla 2014 400
María 2014 200
Pedro 2015 500
Carla 2015 600
María 2015 700
Pedro 2016 200
Carla 2016 250
María 2016 900

Este formato, formato long, es más difícil de leer para nosotros, pero es más eficiente para los ordenadores. Y los datos los procesan los ordenadores!!

Generalmente, cuando estemos trabajando con los datos con el tidyverse convendrá que los datos estén en formato long, pero habrá veces, por ejemplo para mostrar tablas, tendremos que pasarlos a formato ancho. ¿Cómo podemos pasar un df de formato long a wide y al contrario? Lo más habitual es usar dos funciones del paquete tidyr. Veámoslo.


pivot_longer() y pivot_wider()

funciones para pasar de wide a long (& viceversa)


Ya hemos dicho que los packages del tidyverse trabajan mejor con tidy data en formato “long”. ¿Qué hacemos si tenemos un dataframe en formato wide? Pues pasarlo a long. Afortunadamente tenemos un pkg que hace muy sencillo pasar los datos de wide a long (y viceversa): tidyr. Concretamente usaremos las funciones pivot_longer() y pivot_wider()1

Aquí tienes el post oficial donde se anunciaba la llegada a CRAN de tidyr 1.0.0 y aquí y aquí un post detallado sobre ellas. La conclusión del autor del último de ellos es:

The new tidyr functions have intuitive syntax, are easy to use, and are more flexibile than the prior functions. Several of the new arguments and features are extremely useful, and will save lots of time on common tasks.

En este otro post tienes también una explicación detallada, de pivot_*() pero además incluye una serie de gifs que ejemplifican el paso de wide a long.


De wide a long format con pivot_longer()

La función pivot_longer() convierte dataframes de wide a long format

Hagámoslo:

library(tidyr)
data_wide <- data_2   #- data_2 está en formato ancho (wide)

#- la función pivot_longer() transforma los datos de formato ancho(wide) a formato largo(long)
data_long <- data_wide %>% pivot_longer(cols = 2:4, names_to = "periodo")

Si quisiéramos arreglar los valores de los periodos:

#(!!) stringr::str_replace encuentra el texto "W_" en la columna "periodo" y lo sustituye por ""
data_long <- data_long %>% mutate(periodo = str_replace(periodo, "W_", "" ))


De long a wide format con pivot_wider()

Pasar pasar de long a wide, tidyr tiene la función pivot_longer()

Hagámoslo:

#- `pivot_longer()` convierte un df de long a wide
data_wide2 <- data_long %>% pivot_wider(names_from = periodo, values_from = value)


separate() y unite()

funciones para separar y unir columnas


El pkg tidyr contiene otras 2 funciones: separate() y unite() que facilitan el separar y unir columnas. Veamos un ejemplo:

df <- data.frame( names = c("Pedro_Navaja", "Bob_Dylan", "Cid_Campeador"), 
                  year  = c(1978, 1941, 1048) )
df
names year
Pedro_Navaja 1978
Bob_Dylan 1941
Cid_Campeador 1048

Separamos la primera columna:

df_a <- df %>% separate(names, c("Nombre", "Apellido"), sep = "_")
df_a
Nombre Apellido year
Pedro Navaja 1978
Bob Dylan 1941
Cid Campeador 1048

Si queremos volver a unirlos, tendríamos que:

df_b <- df_a %>% unite(Nombre_y_Apellido, Nombre:Apellido, sep = "&")
df_b
Nombre_y_Apellido year
Pedro&Navaja 1978
Bob&Dylan 1941
Cid&Campeador 1048


mas funciones de tidyr

Además, recuerda que el paquete tidyr tiene muchas más funciones que nos facilitan conseguir que nuestros datos sean tidy.




3. DPLYR

En R hay varios enfoques para manipular datos en R, pero el más habitual, de hecho se ha convertido en el estándar, es utilizar el tidyverse, concretamente el paquete dplyr.

dplyr es un paquete que permite manipular datos de forma intuitiva. Tiene 6-7 funciones o verbos principales. Cada uno de ellos hace “una sola cosa”, así que para realizar transformaciones complejas hay que ir concatenando instrucciones sencillas. Esto se hace con el operador pipe (%>%)



dplyr basics

Tras mucho pensar como estructurábamos este apartado del tutorial, al final me decanté por utilizar los materiales del curso STAT 545. ¿que quien ha se encarga del curso? Pues Jenny Bryan (always rocks!!). Puedes encontrarlos aquí.

dplyr tiene muchas funciones, pero las principales son 6-7, luego las veremos. Con ellas se pueden resolver la mayoría de problemas asociados a la manipulación de datos.

Cada función (o verbo) hace una sola cosa, pero concatenándolas con %>% permiten resolver cuestiones complejas.

Todas las funciones tienen una estructura o comportamiento similar:

  • el primer argumento siempre es un df. Esto es importante
  • los siguientes argumentos describen que hacer con los datos
  • el resultado es siempre un nuevo df. Esto es importante

Por ejemplo, filter(df, X1 >= 10) devuelve un df con las filas del df original que cumplen la condición de que la variable X1 es mayor o igual a 10

Podemos escribir la anterior instrucción de 3 formas. La más utilizada es la última:

df_new <- filter(df, X1 >= 10)

df_new <- df %>% filter(. , X1 >= 10)

df_new <- df %>% filter(X1 >= 10)



4. Principales funciones de dplyr

Hay 6-7 principales.

  • filter() : permite seleccionar filas (que cumplen una o varias condiciones)
  • arrange(): reordena las filas (arrange()).
  • rename() : cambia los nombres de las columnas (variables)
  • select() : selecciona columnas (variables)
  • mutate() : crea nuevas variables
  • summarise() : resume (colapsa) unos cuantos valores a uno sólo. Por ejemplo, calcula la media, moda, etc… de un conjunto de valores

Hay una séptima:

  • group_by() : permite agrupar filas en función de una o varias condiciones

Y después de dplyr 1.0.0, en mayo de 2020, añado 2 más:

  • across() y where(). Estas funciones son un poco diferentes, solo se usan en combinación de otro función/verbo. Son 2 funciones que en la jerga del tidyverse no son verbos sino adverbios. Lo vemos


Veámoslas una a una. Veremos sólo algunos ejemplos. Ya iremos practicando



filter()

Esta función (o verbo) se utiliza para seleccionar filas de un dataframe (df). Se seleccionan las filas que cumplen una determinada condición o criterio lógico. Por ejemplo:

#- vamos a trabajar con los datos del [pkg gapminder](https://github.com/jennybc/gapminder)
gapminder <- gapminder::gapminder


Seleccionamos las filas que cumplen determinados criterios:

#- Observaciones de España (country == "Spain")
aa <- gapminder %>% filter(country == "Spain") 

#- filas con valores de "lifeExp" < 29
aa <- gapminder %>% filter(lifeExp < 29)       

#- filas con valores de "lifeExp" entre [29, 32]
aa <- gapminder %>% filter(lifeExp >=  29 , lifeExp <= 32)   
aa <- gapminder %>% filter(lifeExp >=  29 &  lifeExp <= 32)  
aa <- gapminder %>% filter(between(lifeExp, 29, 32))       

#- observaciones de paises de África con lifeExp > 32
aa <- gapminder %>% filter(lifeExp > 72 &  continent == "Africa") 

#- observaciones de países de África o Asia con lifeExp > 32
aa <- gapminder %>% filter(lifeExp > 72 &  continent %in% c("Africa", "Asia") )  
aa <- gapminder %>% filter(lifeExp > 72 & (continent == "Africa" | continent == "Asia") )  


La función filter() tiene muchas más posibilidades. Ya las iremos viendo. PERO si quieres ver un resumen de las posibilidades del paquete dplyr mira su CHEAT SHEET. 2. La versión antigua de la Cheat sheet contiene también las funciones de tidyr.


slice() también es muy útil para seleccionar filas

  • slice(): filtra filas por su posición (física en el df)


Como dijimos, slice() sirve para seleccionar filas por posición:

#- selecciona las observaciones de la décima a la quinceava
aa <- gapminder %>% slice(c(10:15)) 

#- selecciona las observaciones de la 12 a 13 Y de la 44 a 46, Y las 4 últimas
aa <- gapminder %>% slice( c(12:14, 44:46, n()-4:n()) ) #- AQUI hay un error, tenéis que arreglarlo. 

#- Pista: igual os ayuda crear una columna con el índice de rows y repetir el cálculo
aa <- gapminder %>% mutate(index = 1:n())
aa <- gapminder %>% slice( c(12:14, 44:46, n()-4:n()) )


variantes de slice()

Hay varias variantes de slice(). Concretamente slice_max() slice_min(), slice_smpl(), slice_head() slice_tail(). Veremos algún ejemplo con las 3 primeras.

Si queremos seleccionar las filas que tienen el valor máximo (o mínimo) de una determinada variable, podemos usar slice_max() y slice_min()

#- selecciona las 3 filas con mayor valor de lifeExp
aa <- gapminder %>% slice_max(lifeExp, n = 3)
#- selecciona las 4 filas con MENOR valor de pop
aa <- gapminder %>% slice_min(pop, n = 4)

Para ver la potencialidad de una función tienes que ver su ayuda interna (presionando F1 o con help()). Por ejemplo slice_min() tiene otro argumento (prop) que nos permite calcular, por ejemplo, el 10% de observaciones/países con menor esperanza de vida.

#- observaciones en el primer decil en cuanto a esperanza de vida, 10% con menor esperanza de vida
aa <- gapminder %>% slice_min(lifeExp, prop = 0.1)
#- 1% de observaciones con mayor población. Imagino que estarán China e India
aa <- gapminder %>% slice_max(pop, prop = 0.01)

A veces se necesita obtener una muestra aleatoria de los datos. La función slice_sample() está diseñada para ayudarnos en esta tarea:

#- selecciona (aleatoriamente) 100 filas de los datos
aa <- gapminder %>% slice_sample(n = 100)
#- selecciona (aleatoriamente) un 5% de los datos
aa <- gapminder %>% slice_sample(prop = 0.05)



arrange()

Esta función (o verbo) se utiliza para reordenar las filas de un dataframe (df).

#- ordena las filas de MENOR a mayor según los valores de la v. lifeExp 
aa <- gapminder %>% arrange(lifeExp)

#- ordena las filas de MAYOR a menor según los valores de la v. lifeExp
aa <- gapminder %>% arrange(desc(lifeExp))  

#- ordenada las filas de MENOR a mayor según los valores de la v. lifeExp. 
#- Si hay empates se resuelve con la variable "pop"
aa <- gapminder %>% arrange(lifeExp, pop) 



rename()

Esta función permite cambiar los nombres de las columnas

#- cambia los nombres de lifeExp y gdpPercap a life_exp y gdp_percap 
gapminder %>% rename(life_exp = lifeExp,  gdp_percap = gdpPercap)

#-(!!) la función names() de R-base es muy útil. Tb setNames() y set_names()
aa <- gapminder
names(aa) <- names(aa) %>% toupper
names(aa) <- names(aa) %>% tolower
names(aa) <- c("var_01", "var_02", "var_03", "var_04", "var_05" , "var_06")
names(aa) <- paste0("Var_", 1:6)
names(aa) <- paste0("Lag_", formatC(1:6, width = 2, flag = "0")) 


rename_with() , una variante de rename()

Si tienes que hacer transformaciones más complejas, que requieran el uso de funciones o pautas, de los nombres de las variables puedes usar rename_with()

aa <- gapminder
rename_with(aa, toupper)
rename_with(aa, toupper, starts_with("Life") | contains("countr"))
rename_with(aa, ~ str_replace(.x, "e", "Ö"))  #- (!!!!)


La función rename() es útil pero, enseguida veremos que la siguiente función, select(), también permite renombrar las columnas, e incluso reordenar la posición de estas.



select()

Esta función (o verbo) sirve para seleccionar columnas de un df.

seleccionar variables por nombre

Seleccionamos las variables “year” y “lifeExp”:

#- Se lee como: “Take el df gapminder, then select the variables year and lifeExp”
aa <- gapminder %>% select(year, lifeExp) 
aa <- gapminder %>% select(c(year, lifeExp))


quitar variables

Para eliminar una variable hay varias formas:

aa <- gapminder %>% select(-year)   #- la forma mas habitual

#- estas dos formas son mucho menos habituales
aa <- gapminder %>% select(!year)   
aa <- gapminder %>% mutate(year = NULL)   #- aún no hemos visto mutate()

Para eliminar varias variables:

#- quitamos las variables: year y lifeExp
aa <- gapminder %>% select(-c(year, lifeExp))


seleccionar por posición

Seleccionamos las variables del df gapminder siguientes: de la primera a la tercera y también la quinta (mejor seleccionarlas por nombre!!)

#- seleccionamos las variables {1, 2, 3 y 5}
aa <- gapminder %>% select(1:3, 5)


quitar variables por posición

Seleccionamos todas las variables del df gapminder excepto las siguientes: de la primera a la tercer y la quinta (mejor seleccionarlas por nombre)

#- quitamos las variables {1, 2, 3 y 5}
aa <- gapminder %>% select(- c(1:3, 5))


select() con la función auxiliar where()

En el data.frame gapminder las 2 primeras variables (country y continent) son factores y las 4 siguientes son variable numéricas.

print(gapminder, n = 3)
#> # A tibble: 1,704 × 6
#>   country     continent  year lifeExp      pop gdpPercap
#>   <fct>       <fct>     <int>   <dbl>    <int>     <dbl>
#> 1 Afghanistan Asia       1952    28.8  8425333      779.
#> 2 Afghanistan Asia       1957    30.3  9240934      821.
#> 3 Afghanistan Asia       1962    32.0 10267083      853.
#> # … with 1,701 more rows

Imagina que queremos seleccionar sólo las variables que son numéricas. Podemos hacerlo por nombre o por posición pero mejor con select() y la función auxiliar where()3.

aa <- gapminder %>% select(is.numeric)        #- funciona, pero ...
aa <- gapminder %>% select(where(is.numeric)) #- es "preferible" esta segunda expresión

select() y where() son dos funciones, sí, pero en la jerga del tidyverse, select() es un verbo y where() es un adverbio, cualifica/cambia lo que hace select().

Si quisiéramos seleccionar las variables que no son numéricas haríamos:

aa <- gapminder %>% select(!is.numeric)
aa <- gapminder %>% select(!where(is.numeric))  #- es preferible esta segunda expresión


La función select() tiene muchas más posibilidades. ya las iremos viendo. PERO si quieres ver un resumen de las posibilidades del pkg dplyr mira su CHEAT SHEET.


renombrando y reordenando columnas con select()

Lo que sí vamos a ver son 2 posibilidades de select() que son muy útiles. Con select() podemos: renombrar y reordenar las columnas:

#- dejamos en aa solamente a las columnas "year" y "pop"; ADEMÁS, ahora, "pop" irá antes que "year"
aa <- gapminder %>% select(pop, year)


#- dejamos en aa solamente a las columnas "year" y "pop" y les cambiamos el nombre
aa <- gapminder %>% select(poblacion = pop, año = year)


Imagina que quieres que la última columna pase a ser la primera (manías!!). Podemos hacerlo con select y everything(). everything es una función auxiliar:

#- "gdpPercap" que es la última columna pasa a ser la primera
aa <- gapminder %>% select(gdpPercap, everything())

#(!!) otras 3 formas de hacer lo mismo: que la última columna pase a ser la primera
aa <- gapminder %>% select(ncol(df), everything())
aa <- gapminder %>% select(length(df), everything())
aa <- gapminder %>% select(last_col(), everything())  #- usamos the selection helper last_col()

En la última instrucción hemos usado dos funciones auxiliares de select(), dos “helper functions”. La lista completa de funciones auxiliares para select() puedes verla aquí.


relocate()

Desde dplyr 1.0.0, tenemos otra función para reordenar las variables de un data.frame: relocate(). Veámosla en acción:

aa <- gapminder %>% dplyr::relocate(country, .after = lifeExp)
aa <- gapminder %>% dplyr::relocate(country, .before = lifeExp)

Las opciones .after y .before también están disponibles en mutate(), la siguiente función que presentaremos.




mutate()

Esta función (o verbo) sirve para crear nuevas variables (columnas). Lógicamente, es muy útil en análisis de datos.

Creamos la variable: GDP = pop*gdpperCap

#- Creamos la variable: GDP = pop*gdpperCap
aa <- gapminder %>% mutate(GDP = pop*gdpPercap)

Por defecto, la nueva variable creada se situará al final del data frame, a no ser que usemos los argumentos .after y .before

aa <- gapminder %>% mutate(GDP = pop*gdpPercap, .after = country)
aa <- gapminder %>% mutate(GDP = pop*gdpPercap, .before = country)

mutate() también tiene un argumento(.keep) para controlar que variables permanecen en el data frame. Por ejemplo, en el chunk de abajo dejaremos solo las variables usadas.

aa <- gapminder %>% mutate(GDP = pop*gdpPercap, .keep = "used")



summarise()

Esta función (o verbo) sirve para RESUMIR (o “colapsar filas”). Coge una variable o grupo de valores como input y devuelve un solo valor; por ejemplo, haya la media aritmética (o el mínimo, o el máximo …) de una columna/variable.


Obtengamos determinados estadísticos de una variable. Para esto no nos hace falta dplyr pero conviene ir habituándose a su sintaxis.

#- retornará un único valor: la media global de la v. "lifeExp"
aa <- gapminder %>% summarise(media = mean(lifeExp))  

#- retornará un único valor: el número de filas
aa <- gapminder %>% summarise(NN = n())  
aa <- gapminder %>% count()                #- más adelante veremos la utilidad de count()


#- retornará un único valor: la desviación típica de la v. "lifeExp"
aa <- gapminder %>% summarise(desviacion_tipica = sd(lifeExp))  

#- retornará un único valor: el máximo de la variable "pop"
aa <- gapminder %>% summarise(max(pop))  

#- retornará 2 valores: la media y sd de la v. "lifeExp"
aa <- gapminder %>% summarise(mean(lifeExp), sd(lifeExp))  

#- retornará 2 valores: las medias de "lifeExp" y "gdpPercap"
aa <- gapminder %>% summarise(mean(lifeExp), mean(gdpPercap))  


across() y where()

Antes de pasar a ver group_by(), vamos a utilizar las 2 nuevas funciones across() y where().

Muchas veces en un trabajo se han de calcular estadísticos de todas las variables del df. Esto se hace con summarise(), PERO utilizando también una función de ayuda (helper function): across()4. A veces también hay que usar otra helper function: where().

La sintaxis de across() es:

across(.cols = everything(), .fns = NULL, ..., .names = NULL); es decir,

across("columnas seleccionadas", "funciones o cálculos a realizar", "si quieres controlar los nombres")).

Si al seleccionar las columnas utilizas algún criterio lógico, como por ejemplo is.numeric(), entonces la sintaxis de across() es un poco diferente; concretamente será:

across( where("columnas seleccionadas"), "funciones o cálculos a realizar", "si quieres controlar los nombres"))

Los ejemplos ayudan a entenderlo:

#- media de cada una de las 6 variables. Devuelve 2 warnings porque las 2 primeras son textuales. No se puede calcular la media de continent y country
gapminder %>% summarise(across(everything(), mean) ) 

#- calculamos la media de tercera a la sexta variable
gapminder %>% summarise(across(3:6, mean) ) 

Lo que os decía de seleccionar columnas con un criterio lógico y usar where()

gapminder %>% summarise(across(where(is.numeric), mean)) 

#- con los nombres de los argumentos (más largo pero conviene verlo de vez en cuando)
gapminder %>% summarise(across(.cols = where(is.numeric), .fns = mean)) 

Vamos a calcular cosas un poco más complejas. Imagina que no sólo quieres calcular la media sino que también quieres calcular la desviación típica. Seguiremos usando summarise() y across(). Lo único nuevo es que como vamos a aplicar dos funciones (mean() y sd()) las tenemos que poner dentro de list(). Tiene sentido, es una lista de funciones a aplicar a las columnas que seleccionemos con across()

#- calculamos la media y desviación típica de las columnas 3 a 6.
gapminder %>% summarise(across(3:6, list(media = mean, desv = sd)))

#- lo mismo, pero explicitando los nombres de los argumentos
gapminder %>% summarise(across(.cols = 3:6, .fns = list(media = mean, desv = sd) ))

#- lo mismo otra vez, pero eligiendo el nombre de las variables que se van aa crear con .names
gapminder %>% summarise(across(3:6, list(media = mean, desv = sd), .names = "{fn}_{col}"))

(!!!) Imagina que quisiéramos presentar en una tabla los anteriores resultados; tendríamos que usar tidyr::pivot_longer(). Lo voy a hacer por trozos. Nos va a costar un poco:

aa <- gapminder %>% summarise(across(3:6, list(media = mean, desv = sd), .names = "{fn}_{col}")) 

aa1 <- aa %>% pivot_longer(1:8, names_to = "names", values_to = "values")
aa2 <- aa1 %>% separate(names, into = c("operacion", "variable"), sep =  "_") %>% 
               select(variable, everything())
aa3 <- aa2 %>% pivot_wider(names_from = operacion, values_from = values)

Lo practicaremos, y veremos métodos más sencillos para hacer tablas con estadísticos descriptivos, pero eso será en el tutorial dedicado a tablas; pero no os olvidéis de across() que luego tenemos que volver a ella.


group_by()

Con esta función ya empezaremos a ver la potencia de dplyr. En análisis de datos muchas operaciones (media etc..) queremos calcularlas para distintos grupos (hombre, mujer …). group_by() permite hacerlo.

group_by()coge un df y lo convierte en un “df agrupado”. En ese nuevo “df agrupado”, las operaciones que hagamos con summarise() se harán por separado para cada uno de los grupos que hayamos definido. Ahora lo vemos.

Si, por ejemplo, agrupamos un df por países, al ejecutar summarise(), nos retornará una fila con el resultado para cada país. En realidad, podemos pensar que group_by() no hace “nada”, que en realidad solo cambia lo que hacen las otras funciones: ahora los cálculos se harán para cada uno de los grupos que define group_by()



Como dice Jenny: Let’s start with simple counting. ¿Cuantas observaciones(rows) tenemos por continente?

#- cogemos df y lo (des)agrupamos por grupos definidos por la variable "continent"; osea, habrá 5 grupos
#- después con summarise() calcularemos el nº de observaciones en cada continente o grupo; es decir, nos retornará un df con una fila por cada continente
aa <- gapminder %>% group_by(continent) %>% summarise(NN = n()) 
aa
continent NN
Africa 624
Americas 300
Asia 396
Europe 360
Oceania 24

Esto tan sencillo también se puede hacer con count()

aa <- gapminder %>% group_by(continent) %>% count() 
aa <- gapminder %>% group_by(continent) %>% count(name = "NN") 
aa


¿Y cuantos países hay en la base de datos? Para este tipo de cosas, se pueden usar funciones de R-base, pero dplyr tiene muchas funciones auxiliares.

#- cogemos df y lo agrupamos por "continent", 
#- después calculamos 2 cosas: el número de observaciones o rows
#- y el número de países en cada continente (NN_countries)
aa <- gapminder %>% group_by(continent) %>%  
          summarize(NN = n(), 
                    NN_countries = n_distinct(country)) 
aa
continent NN NN_countries
Africa 624 52
Americas 300 25
Asia 396 33
Europe 360 30
Oceania 24 2


Calculemos la esperanza de vida media por continente

#- cogemos df y lo agrupamos por "continent", después calculamos la media de "lifeExp"
aa <- gapminder %>% group_by(continent) %>%  
                    summarize(mean(lifeExp)) 
aa
continent mean(lifeExp)
Africa 48.86533
Americas 64.65874
Asia 60.06490
Europe 71.90369
Oceania 74.32621

Guau! Hay que irse a vivir a Oceanía!!


Calculemos la esperanza de vida media por continente en el primer periodo (1952)

#- cogemos df y filtramos para quedarnos con las observaciones de 1952
#- después lo agrupamos por "continent", 
#- después calculamos la media de "lifeExp"
gapminder %>% filter(year == "1952") %>%  
              group_by(continent) %>%  
              summarize(mean(lifeExp)) 

Habría sido mejor en lugar de poner filter(year == "1952") haber puesto filter(year == min(year))

Guau! Habría que haber vivido en Oceanía (en 1952)!!


Se pueden calcular varios estadísticos a la vez

#- cogemos df y filtramos (cogemos) las observaciones de 1952 y 2007
#- agrupamos por "continent", 
#- después calculamos la media de "lifeExp" y de "gdpPercap"
gapminder %>% filter(year %in% c(1952, 2007)) %>%  
             group_by(continent, year) %>%  
             summarize(mean(lifeExp), mean(gdpPercap)) 

Vamos a hacer cálculos cada vez más complejos:

#- cogemos df y lo agrupamos por "continent" y "year", 
#- después calculamos la media de "lifeExp" y de "gdpPercap"
gapminder %>% filter(year %in% c(1952, 2007)) %>%
              group_by(continent, year) %>% 
              #- después calculamos la media de "lifeExp" 
              summarise(media = mean(lifeExp))
#- Voy a crear un nuevo df: "gapminder_gr" o "gapminder agrupado"
gapminder_gr <- gapminder %>% filter(year %in% c(1952, 2007)) %>%
                 group_by(continent, year) 
#- y sobre "gapminder_gr" iremos haciendo cálculos
  
#- si queremos calcular la media de varias variables tenemos que usar across()
gapminder_gr %>% summarise(across(c(lifeExp, gdpPercap), mean))

#- si queremos calcular la media de todas las variables numéricas tenemos que usar across() y where()
gapminder_gr %>% summarise(across(where(is.numeric), mean))

#- si queremos calcular la media y la mediana, hay que usar list()
gapminder_gr %>% summarise(across(c(lifeExp, gdpPercap), 
                            list (media = mean, mediana = median) ))

#- si ponemos los nombres de los argumentos quedaría como
gapminder_gr %>% summarise(across(.cols = c(lifeExp, gdpPercap), 
                                  .fns = list (media = mean, mediana = median)))

#- además, podemos controlar el nombre de las variables creadas con el argumento .names
gapminder_gr %>% summarise(across(c(lifeExp, gdpPercap), 
                        list (media = mean, mediana = median), 
                        .names = "{fn}_{col}"))


Preguntas de verdad

Bueno, pues ya conocéis lo principal, lo básico y más importante de dplyr, solo queda ir cogiendo práctica y confianza, así que para ello toca hacer una serie de preguntas de verdad!!. Por ejemplo:

  1. ¿en que continente ha aumentado más la esperanza de vida en el periodo 1952-2007?
#- cogemos df y lo agrupamos por "continent", después calculamos la media de "lifeExp"
gapminder %>% 
  filter(year %in% c(1952, 2007)) %>%  
  group_by(continent, year) %>% 
  summarize(media = mean(lifeExp)) %>% ungroup()
continent year media
Africa 1952 39.13550
Africa 2007 54.80604
Americas 1952 53.27984
Americas 2007 73.60812
Asia 1952 46.31439
Asia 2007 70.72848
Europe 1952 64.40850
Europe 2007 77.64860
Oceania 1952 69.25500
Oceania 2007 80.71950

Casi, pero no!! Sólo hemos conseguido ver la esperanza de vida por continente en 1952 y 2007.En realidad esto ya lo hicimos antes. Falta restar.

Quizás podríamos calcular el máximo, el mínimo y restarlos. No, porque supondríamos que “lifeExp” siempre aumenta. Vamos que el tiempo apremia:

#- primer intento: se puede hacer de una vez, pero vamos a partir el código en 2 trozos
aa <- gapminder %>% filter(year %in% c(1952, 2007)) %>%  
  group_by(continent, year) %>% 
  summarize(media = mean(lifeExp)) %>% ungroup() 

aa1 <- aa %>% group_by(continent) %>% 
  summarise(min_l = min(media), max_l = max(media)) %>% 
  mutate(dif = max_l-min_l) %>% 
  arrange(desc(dif))

aa1
continent min_l max_l dif
Asia 46.31439 70.72848 24.41409
Americas 53.27984 73.60812 20.32828
Africa 39.13550 54.80604 15.67054
Europe 64.40850 77.64860 13.24010
Oceania 69.25500 80.71950 11.46450

Asia son los ganadores. En promedio, en Asía se mejoró la esperanza de vida en 24.4 años entre 1952 y 2007.

Lo de restar el máximo y el mínimo ha funcionado, PERO podría no haberlo hecho si hubiese habido algún continente en el que en la esperanza de vida, en lugar de haber aumentado, hubiese bajado. Entonces, ¿cómo lo hacemos?

#- segundo intento: se puede hacer de una vez, pero vamos a partir el código en 2 trozos
aa <- gapminder %>% filter(year %in% c(1952, 2007)) %>%  
         group_by(continent, year) %>% 
         summarize(media = mean(lifeExp)) %>% ungroup() 

#- usamos lag()
aa1 <- aa %>% group_by(continent) %>% 
              arrange(year) %>%
              mutate(variac_l = media - lag(media))

#- mostramos los resultados
aa1 %>% filter(year == 2007) %>% arrange(desc(variac_l))
continent year media variac_l
Asia 2007 70.72848 24.41409
Americas 2007 73.60812 20.32828
Africa 2007 54.80604 15.67054
Europe 2007 77.64860 13.24010
Oceania 2007 80.71950 11.46450

Sí, Asia es la ganadora. En promedio, en Asía se mejoró la esperanza de vida en 24 años entre 1952 y 2007.

Otra forma de obtener el mismo resultado:

#- esta parte es común
aa <- gapminder %>% 
  filter(year %in% c(1952, 2007)) %>%  
  group_by(continent, year) %>% 
  summarize(media = mean(lifeExp)) %>% ungroup()

#- pero ahora usamos pivot_wider()
aa %>% pivot_wider(names_from = year, values_from = media) %>% 
     mutate(dif_l = `2007` - `1952`) %>% 
     arrange(desc(dif_l))
continent 1952 2007 dif_l
Asia 46.31439 70.72848 24.41409
Americas 53.27984 73.60812 20.32828
Africa 39.13550 54.80604 15.67054
Europe 64.40850 77.64860 13.24010
Oceania 69.25500 80.71950 11.46450


El chunk de abajo, ¿qué hace? ¿qué se está calculando?:

aa <- gapminder %>% 
  group_by(continent, year) %>% 
  select(continent, year, lifeExp) %>% 
  summarise(mean_life = mean(lifeExp)) %>% 
  arrange(year) %>% 
  mutate(incre_mean_life_0 = mean_life - first(mean_life)) %>% 
  mutate(incre_mean_life_t = mean_life - lag(mean_life)) %>% 
  arrange(continent)

#- por ejemplo veamos el resultado para Europe
aa %>% filter(continent == "Europe")
continent year mean_life incre_mean_life_0 incre_mean_life_t
Europe 1952 64.40850 0.000000 NA
Europe 1957 66.70307 2.294567 2.2945667
Europe 1962 68.53923 4.130733 1.8361667
Europe 1967 69.73760 5.329100 1.1983667
Europe 1972 70.77503 6.366533 1.0374333
Europe 1977 71.93777 7.529267 1.1627333
Europe 1982 72.80640 8.397900 0.8686333
Europe 1987 73.64217 9.233667 0.8357667
Europe 1992 74.44010 10.031600 0.7979333
Europe 1997 75.50517 11.096667 1.0650667
Europe 2002 76.70060 12.292100 1.1954333
Europe 2007 77.64860 13.240100 0.9480000

Sed conscientes de que la soluciones a una pregunta no sale a la primera, a veces hay que calentarse el cap:

Break the code into pieces, starting at the top, and inspect the intermediate results. That’s certainly how I was able to write such a thing. These commands do not leap fully formed out of anyone’s forehead – they are built up gradually, with lots of errors and refinements along the way. Is the statement above really hard for you to read? If yes, then by all means break it into pieces and make some intermediate objects. Your code should be easy to write and read when you’re done. —- Jenny Bryan


Otras cuestiones que podemos resolver con dplyr sobre la esperanza de vida:

  • ¿Cómo ha evolucionado la esperanza de vida en Spain lustro a lustro?
#- variación de lifeExp en Spain año a año (bueno lustro a lustro)
aa <- gapminder %>% 
  group_by(country) %>% 
  select(country, year, lifeExp) %>% 
  mutate(lifeExp_gain_cada_lustro = lifeExp - lag(lifeExp)) %>% 
  filter(country == "Spain" )
aa
country year lifeExp lifeExp_gain_cada_lustro
Spain 1952 64.940 NA
Spain 1957 66.660 1.720
Spain 1962 69.690 3.030
Spain 1967 71.440 1.750
Spain 1972 73.060 1.620
Spain 1977 74.390 1.330
Spain 1982 76.300 1.910
Spain 1987 76.900 0.600
Spain 1992 77.570 0.670
Spain 1997 78.770 1.200
Spain 2002 79.780 1.010
Spain 2007 80.941 1.161


  • ¿Y la variación acumulada? Fácil!! Sólo tendríamos que sumar o acumular la variable “lifeExp_gain_cada_lustro” que hemos generado anteriormente, así que sólo habría que añadir una linea a nuestro código:
#- ganancia acumulada
aa <- gapminder %>% 
  group_by(country) %>% 
  select(country, year, lifeExp) %>% 
  mutate(lifeExp_gain_cada_lustro = lifeExp - lag(lifeExp)) %>% 
  #--- 2 filas nuevas: ifelse()  y cumsum()
  mutate(lifeExp_gain_cada_lustro2 = ifelse(is.na(lifeExp_gain_cada_lustro), 0, lifeExp_gain_cada_lustro)) %>% 
  mutate(lifeExp_gain_acumulado = cumsum(lifeExp_gain_cada_lustro2)) %>%   
  filter(country == "Spain")
aa
country year lifeExp lifeExp_gain_cada_lustro lifeExp_gain_cada_lustro2 lifeExp_gain_acumulado
Spain 1952 64.940 NA 0.000 0.000
Spain 1957 66.660 1.720 1.720 1.720
Spain 1962 69.690 3.030 3.030 4.750
Spain 1967 71.440 1.750 1.750 6.500
Spain 1972 73.060 1.620 1.620 8.120
Spain 1977 74.390 1.330 1.330 9.450
Spain 1982 76.300 1.910 1.910 11.360
Spain 1987 76.900 0.600 0.600 11.960
Spain 1992 77.570 0.670 0.670 12.630
Spain 1997 78.770 1.200 1.200 13.830
Spain 2002 79.780 1.010 1.010 14.840
Spain 2007 80.941 1.161 1.161 16.001

Al final para hacerlo (como había pensado) me han hecho falta 2 lineas, porque la primera observación de “lifeExp_gain_cada_lustro” es un NA y eso hacía que la función cumsum() no funcionase.


  • Otra forma de hacer lo mismo sería (se me ha ocurrido después). Además es más fácil
#- ganancia acumulada (otra forma de hacer lo mismo)
aa <- gapminder %>% 
  group_by(country) %>% 
  select(country, year, lifeExp) %>% 
  mutate(lifeExp_gain_acumulada = lifeExp - lifeExp[1])  %>% 
  filter(country == "Spain")


  • Obtener, para cada periodo, los (3) países de Asia con MAYOR lifeExp. Usaremos una variante de slice(), concrétamente slice_max()
aa <- gapminder %>%
  filter(continent == "Asia") %>%
  select(year, country, lifeExp) %>%
  group_by(year) %>%
  slice_max(n = 3, lifeExp) %>% 
  arrange(year) 

Para obtener los 4 países con MENOR “lifeExp” sólo tendríamos que sustituir la quinta linea por slice_min(n = 4, lifeExp)


  • Obtener, para cada periodo, los países de Asia con mayor y menor lifeExp.
#- Obtener, para cada periodo, los países de Asia con mayor y menor lifeExp.
aa <- gapminder %>%
  filter(continent == "Asia") %>%
  select(year, continent, country, lifeExp) %>%
  group_by(year) %>%
  filter(min_rank(desc(lifeExp)) < 2 | min_rank(lifeExp) < 2) %>% 
  arrange(year) 

Las 2 últimas funciones que hemos usado: slice_min() y min_rank() son funciones de dplyr pero no son son funciones principales, en cierta forma son auxiliares.


Podéis ver las funciones auxiliares que tiene dplyr en la segunda página de CHEAT SHEET. también se pueden usar las funciones de R-base o de otros packages. Aquí tenéis algunas posibilidades sacadas de un tutorial de Hadley

Types of summary functions:
• min(x), median(x), max(x), quantile(x, p) 
• n(), n_distinct(), sum(x), mean(x) 
• sum(x > 10), mean(x > 10) 
• sd(x), var(x), iqr(x), mad(x)

Types of window functions:
• Ranking and ordering 
• Offsets: lead & lag 
• Cumulative aggregates
• Rolling aggregates

Ejemplos:
• Was there a change?  x != lag(x)
• Percent change? (x - lag(x)) / x
• Fold-change? x / lag(x)
• Previously false, now true? !lag(x) & x

• If one of the specialised verbs doesn’t do what you need, you can use do()


A ver si entendeis este ejemplo

Una función auxiliar que es muy útil al utilizarla junto a mutate: case_when().

aa <- gapminder %>%
  group_by(continent, year)  %>%
  mutate(media_lifeExp = mean(lifeExp)) %>% 
  mutate(media_gdpPercap = mean(gdpPercap)) %>% 
  mutate(GOOD_or_BAD = case_when( 
    lifeExp > mean(lifeExp) & gdpPercap > mean(gdpPercap)  ~ "good",
    lifeExp < mean(lifeExp) & gdpPercap < mean(gdpPercap)  ~ "bad" ,
    lifeExp < mean(lifeExp) | gdpPercap < mean(gdpPercap)  ~ "medium"
    )) %>%
  filter(country == "Spain")


Más funciones auxiliares

Hay algunas que no quiero olvidar:

dplyr::ntile(x, n) : categorizes a vector of values into "ntiles" such as quartiles if n = 4

dplyr::n_distinct(x):  counts unique values in a vector; similar a  length(unique(x))

dplyr::between(x, left, right) : is a shortcut for x >= left & x <= right, 

tibble::add_row()  : añade rows a un df

tibble::rownames_to_column()


Más detalles sobre dplyr

Los verbos mutate() y summarise() ya sabemos que pueden hacer uso de funciones como mean(), sd() etc … Por ejemplo podemos transformar las variables numéricas a logaritmos:

gapminder %>% mutate(across(where(is.numeric), log)) %>% head(n = 3)

Se pueden usar tidy_helpers para seleccionar las columnas y aplicar más de una función:

gapminder %>% mutate(across(c(starts_with("life"), contains("po")), .fns =  mean)) %>% head

o calcular la media de las variable numéricas y controlar el nombre de las variables creadas:

gapminder %>% group_by(continent) %>%
  summarize(across(where(is.numeric), mean, .names = "mean_{col}")) %>% head(n = 3)

Si quieres seleccionar todas las variables usa everything()

gapminder %>% group_by(continent) %>%
  summarize(across(everything(), as.character, .names = "CHAR_{col}")) %>% head(n = 3)

Pero vamos a ver otras posibilidades:


más de una condición para seleccionar las columnas:

gapminder %>% mutate(across(where(is.double) & ends_with("cap"), as.integer)) %>% head(n = 3)
country continent year lifeExp pop gdpPercap
Afghanistan Asia 1952 28.801 8425333 779
Afghanistan Asia 1957 30.332 9240934 820
Afghanistan Asia 1962 31.997 10267083 853


uso de funciones propias (!!!!)

Tenemos varias posibilidades:

    1. definiendo primero la función:
dividir_100 <- function(x) {x / 100}  #- defino una función

gapminder %>% mutate(across(where(is.numeric), .fns = dividir_100)) %>% head
country continent year lifeExp pop gdpPercap
Afghanistan Asia 19.52 0.28801 84253.33 7.794453
Afghanistan Asia 19.57 0.30332 92409.34 8.208530
Afghanistan Asia 19.62 0.31997 102670.83 8.531007
Afghanistan Asia 19.67 0.34020 115379.66 8.361971
Afghanistan Asia 19.72 0.36088 130794.60 7.399811
Afghanistan Asia 19.77 0.38438 148803.72 7.861134
    1. usando formulas anónimas
gapminder %>% mutate(across(where(is.numeric), .fns = function(x) {x / 100})) %>% head
country continent year lifeExp pop gdpPercap
Afghanistan Asia 19.52 0.28801 84253.33 7.794453
Afghanistan Asia 19.57 0.30332 92409.34 8.208530
Afghanistan Asia 19.62 0.31997 102670.83 8.531007
Afghanistan Asia 19.67 0.34020 115379.66 8.361971
Afghanistan Asia 19.72 0.36088 130794.60 7.399811
Afghanistan Asia 19.77 0.38438 148803.72 7.861134
    1. usando fórmulas
#- con formulas
gapminder %>% mutate(across(where(is.numeric), .fns = ~ .x/100)) %>% head
country continent year lifeExp pop gdpPercap
Afghanistan Asia 19.52 0.28801 84253.33 7.794453
Afghanistan Asia 19.57 0.30332 92409.34 8.208530
Afghanistan Asia 19.62 0.31997 102670.83 8.531007
Afghanistan Asia 19.67 0.34020 115379.66 8.361971
Afghanistan Asia 19.72 0.36088 130794.60 7.399811
Afghanistan Asia 19.77 0.38438 148803.72 7.861134

gapminder %>% mutate(across(where(is.numeric), .fns = ~ {1/sqrt(.)})) %>% head
country continent year lifeExp pop gdpPercap
Afghanistan Asia 0.0226339 0.1863358 0.0003445 0.0358185
Afghanistan Asia 0.0226050 0.1815723 0.0003290 0.0349034
Afghanistan Asia 0.0225762 0.1767850 0.0003121 0.0342373
Afghanistan Asia 0.0225475 0.1714482 0.0002944 0.0345816
Afghanistan Asia 0.0225189 0.1664633 0.0002765 0.0367612
Afghanistan Asia 0.0224904 0.1612945 0.0002592 0.0356662

usar formulas facilita el uso de argumentos dentro de la función:

gapminder %>% 
  group_by(continent, year) %>% 
  summarise(across(c("lifeExp", "gdpPercap"), 
                   list(mean = ~ mean(.x, na.rm = TRUE, trim = 0.1))))

crear indices de grupo

A veces cuando trabajas con df agrupados es útil saber a que grupo pertenece cada observación. Puedes hacerlo fácilmente con:

gapminder %>% group_by(year)  %>% mutate(id_grupo = cur_group_id()) 




5. Combinando (joining) df’s


OK, ya sabemos manejar/filtrar etc… un conjunto de datos, PERO muchas veces lo que hay que hacer es unir o combinar varias tablas o conjuntos de datos (Joinings en inglés).

Vamos a aprender como hacerlo usando dplyr. [Aquí] tenéis la vignette de dplyr para “two table verbs”, también podéis ver un vídeo muy ilustrativo [aquí]. También podéis usar el tutorial de Jenny. La CHEAT SHEET es muy-muy buena.

Los ejemplos e imágenes usados en esta sección se basan en los creados por Mara Averick (@dataandme) en este repo, que a su vez se basaron en la idea de Garrick Aden-Buie @grrrck


(dos) casos sencillos

Dos casos ideales (sencillos de unir): bind_cols() y bind_rows()

  1. Si los 2 dfs tienen exactamente las mismas filas o unidades de análisis ( y ademas en el mismo orden). En este caso, solo habría que juntar en una misma tabla las columnas de df1 y de df2. Esto lo podemos hacer con bind_cols() (o con cbind() de R-base).
df_1 <- iris[ , 1:2]  ; df_2 <- iris[ , 3:5]

df_1 <- iris %>% select(1:2)  ; df_2 <- iris %>% select(3:5) 

df_3 <- `bind_cols`(df_1, df_2)

identical(iris, df_3)
  1. Si los 2 dfs tienen exactamente las mismas columnas ( y ademas en el mismo orden). En este caso, se trataría simplemente de juntar todas las observaciones o filas de los 2 df’s. Esto lo podemos hacer con bind_rows() (o con rbind() de R-base)
df_1 <- iris[1:75, ]  ; df_2 <- iris[76:150, ]

df_1 <- iris %>% slice(1:75)  ; df_2 <- iris %>% slice(76:150) 

df_3 <- `bind_rows`(df_1, df_2)

identical(iris, df_3)


Olvidando ya los 2 casos ideales y sencillos


En dplyr hay 3 tipos de funciones(verbos) que se ocupan de diferentes operaciones para unir datasets:

  • Mutating joins, añade nuevas variables (o columnas) a un dataframe (df1). Estas nuevas columnas vienen de un segundo df2 (hay varias mutating joins, dependiendo del criterio para seleccionar las filas)

  • Filtering joins, filtra las filas (observaciones) de un dataframe (df1) basándose en sí las filas de df1 coinciden (match) o no con una observación del segundo df2

  • Set operations, combina las observaciones de los dos datasets (df1 y df2) as if they were set elements.


Todas estas funciones tienen una estructura similar: sus dos primeros argumentos son 2 df’s (en realidad tablas de datos): df1 y df2. El output de la función es siempre una nueva tabla (del mismo tipo que df1).



Mutating joins

Hay 4 tipos de mutating joins. Su sintaxis es idéntica, sólo se diferencian en que las filas que se seleccionan dependen del criterio para hacer el match:

  • inner_join(df1,df2): Retorna todas las columnas de df1 y también las de df2, PERO solo retorna las filas de df1 que tienen una equivalencia en df2. (la equivalencia se define en función del valor de una variable o variables comunes en df1 y df2)

  • left_join(df1,df2): Retorna todas las columnas de df1 y también las de df2; en cuanto a las filas, retorna TODAS las filas de df1. (Si hubiesen varios matches entre df1 e df2 se retornan todas las combinaciones!!!!)

  • rigth_join(df1,df2): Retorna todas las columnas de df1 y también las de df2; en cuanto a las filas, retorna TODAS las filas de df2. De df2!! (Si hubiesen varios matches entre df1 y df2 se retornan todas las combinaciones!!!!)

  • full_join(df1,df2): Retorna todas las columnas de df1 y también las de df2; en cuanto a las filas, retorna TODAS las filas de df1 y de df2. Osea, retorna TODAS las filas y TODAS las columnas de las 2 tablas. (Donde no hay matches retorna NA’s)


Ejemplos de mutating joins

Sean los siguientes 2 dataframes (tibbles):

x <- tibble(id = 1:3, x = paste0("x", 1:3))

y <- tibble(id = (1:4)[-3], y = paste0("y", (1:4)[-3]))


Inner Join
#- only includes observations that match in both x and y
df_inner <- inner_join(x, y)


Left Join
#- includes all observations in x, regardless of whether they match or not. 
#- This is the most commonly used join because it ensures that you don’t lose observations from your primary table.
df_left_join <- left_join(x, y)


Right Joint
#- includes all observations in y. 
#- It’s equivalent to left_join(y, x), but the columns will be ordered differently.
df_right_join <- right_join(x, y)


Full Joint
#- full_join() includes all observations from x and y
df_full_join <- full_join(x, y)


2 precisiones sobre las mutating joins

Las (left, right and full) joins se llaman colectivamente como “outer joins”. Cuando una fila no tiene match en una outer join, las nuevas variables que se crean se llenan con NA’s.

Las mutating joins se usan principalmente para añadir columnas, PERO en el proceso pueden generarse nuevas filas: si un match no es único, se añadirán todas las combinaciones posibles (el producto cartesiano) de las matching observations. Veamos un ejemplo con una left_join:

x <- tibble(id = 1:3, x = paste0("x", 1:3))

y <- tibble(id = c(1:4,2)[-3], y = paste0("y", c(1:5)[-3]))


left_join() en la que se crean nuevas filas
df_left_join <- left_join(x, y)


Importante ¿Cómo decir a las funciones la columnas (o columnas) que se usarán para hacer los matching?

Podemos(DEBEMOS) elegir las columnas (o variables) que nos servirán para unir los 2 df’s. Estas columnas que se usan para para hallar los matchings y que por tanto nos permiten fusionar los 2 df’s se llaman “keys”.

La opción de las funciones para seleccionar estas columnas “keys” es by =.

  • si ponemos left_join(df1, df2, by = "X1") se hará una left_join siendo la variable “X1” la que hará de key. Si las variables key no se llamasen igual en los 2 df’s siempre podemos renombrarlas o hacer lo siguiente: left_join(df1, df2, by = c("X1" = "D4")

  • También se pueden fusionar tablas usando dos keys; por ejemplo: left_join(df1, df2, by = c("X1", "X2")) . Si no se llamasen igual las variables en df1 y df2 haríamos left_join(df1, df2, by = c("X1" = "D4", "X2" = "D7"))




Filtering joins

Filtering joins son similares a los anteriores (Mutating joins); o sea, hacen machting con las filas de la misma manera, PERO afectan a las filas, NO a las columnas. Hay filtering joins de 2 tipos:

  • semi_join(df1,df2): retorna las observaciones de df1 que tienen un match en df2. En cuanto a las columnas sólo retorna las columnas de df1
  • anti_join(df1,df2): retorna las observaciones de df1 que NO tienen un match en df2; osea, quita las observaciones con match. En cuanto a las columnas sólo retorna las columnas de df1

La semi_join se diferencia de la inner_join en que la inner_join solo retorna una fila de df1 por cada matching, mientras que la semi_join NUNCA duplica filas de df1

Las filtering joins son útiles para diagnosticar mismatches. Si quieres saber sobre los matches, haz una semi_join() or anti_join(). semi_join() and anti_join() NUNCA duplican filas; solo pueden quitar filas.


semi_join
df_semi_join <-  semi_join(x, y, by = "id")


anti_join
df_anti_join <-  anti_join(x, y, by = "id")


comparemos la semi_join con la inner_join
df_inner <-  inner_join(x, y, by = "id")
df_semi_join <-  semi_join(x, y, by = "id")

La inner_join

    1. añade variables del data.frame y al data.frame x (en este caso y$y)
    1. sólo retiene las rows del data.frame x que tienen un match en y (en este caso las 2 primeras filas de x)
    1. PERO en este ejemplo ADEMÁS duplica rows. la razón es que hay matching duplicados, hay 2 unos en x y otros 2 unos en el data.frame y (con distintos valores de y$y) (así que salen 4 rows)


Importante ¿Cómo decir a las funciones la columna, o columnas, que se usarán para hacer los matching?

Al igual que con las mutating joins, en las filtering joins también podemos(DEBEMOS) elegir las columnas (o variables) que nos servirán para unir los 2 df’s. Estas columnas que se usan para para hallar los matchings y que por tanto que permiten fusionar los 2 df’s se llaman “keys”.

La opción de las funciones para seleccionar estas columnas “keys” es by =.

  • si ponemos semi_join(df1, df2, by = "X1") se hará una semi_join siendo la variable “X1” la que hará de key. Si las variables key no se llamasen igual en los 2 df’s siempre podemos renombrarlas o hacer semi_join(df1, df2, by = c("X1" = "D4")

  • si ponemos semi_join(df1, df2,by = c("X1", "X2") hará falta que una row de df1 tenga los valores tanto de X1 como de X2 iguales a los de esas mismas variables en df2. Si no se llamasen igual las variables en df1 y df2 haríamos by = c("X1" = "D4", "X2" = "D7")



Set operations

Este tipo de joins es más estricta: hace falta que los 2 df’s tengan las mismas variables (o columnas). Los 2 df’s pueden tener observaciones(filas) diferentes, PERO es necesario que tengan las mismas variables (o columnas).

Como los 2 df’s tienen las mismas columnas, entonces es como si se tratasen los dfs como conjuntos:

  • intersect(df1, df2): devuelve un df con las observaciones comunes en df1 y df2
  • union(df1, df2): devuelve la unión; o sea, las observaciones de df1 y de df2 (quitando las posibles filas duplicadas)
  • union_all(df1, df2): devuelve la unión (sin quitar los duplicados)
  • setdiff(df1, df2): devuelve las filas en df1 que no están en df2


  • setequal(df1,df2: retorna TRUE si df y df2 tienen exactamente las mismas filas (da igual el orden en el que estén las filas)


x <- tibble::tibble(v1 = c(1, 1, 2), v2 = c("a" , "b", "a"))
y <- tibble::tibble(v1 = c(1, 2),    v2 = c("a" , "b"))


intersección
intersect(x, y)


unión
union(x, y)


setdiff
setdiff(x, y)

Puedes probar tú mismo a cambiar el orden del los df’s en setdiff():

setdiff(y, x)


setqual

Sirve para determinar si 2 df’s son iguales (sin importar el orden en que estén las filas)

setequal(x, y)
#> [1] FALSE
setequal(union(x, y), union(y, x))
#> [1] TRUE




Esperando a GODOT/ggplot2


Bueno, pues hemos visto “TODO” sobre manipulación de datos. El próximo tutorial va de visualización: hacia ggplot2

Aquí un pequeño avance:

library("ggplot2")
my_plot <- ggplot(gapminder, aes(x = continent, y = lifeExp)) +
  geom_boxplot(outlier.colour = "hotpink") +
  geom_jitter(position = position_jitter(width = 0.1, height = 0), alpha = 1/4) +
  labs(title = "Experanza de vida (por continente)",
       subtitle = "Datos de gapminder. 1952-2007(observaciones cada 5 años)",
       caption = "Source: Gapminder. Jenny Bryan rocks in gapminder vignette!!", 
       x = "Continente", y = "Esperanza de Vida (lifeExp)") 




gapminder2 <- gapminder %>% mutate(year = as.factor(year))

library("ggplot2")
my_plot <- ggplot(gapminder2, aes(x = year, y = lifeExp)) +
  geom_boxplot(outlier.colour = "hotpink") +
  geom_jitter(position = position_jitter(width = 0.1, height = 0), alpha = 1/4) +
  labs(title = "Experanza de vida (por año)",
       subtitle = "Datos de gapminder. 1952-2007(observaciones cada 5 años)",
       caption = "Source: Gapminder. Jenny Bryan rocks in gapminder vignette!!", 
       x = "Periodo", y = "Esperanza de Vida (lifeExp)") 



library("ggplot2")
my_plot <- ggplot(gapminder, aes(x = gdpPercap, y = lifeExp, colour = continent)) +
  geom_jitter(position = position_jitter(width = 0.1, height = 0), alpha = 1/4) +
  labs(title = "Experanza de vida vs. GDP (per cápita)",
       subtitle = "Datos de gapminder. 1952-2007(observaciones cada 5 años)",
       caption = "Source: Gapminder. Jenny Bryan rocks in gapminder vignette!!", 
       x = "GDP (per cápita)", y = "Esperanza de Vida (lifeExp)") 




Tidylog package

Una herramienta que os puede ser de utilidad para aprender el uso de dplyr es el paquete tidylog. Este paquete nos da feedback instantáneo sobre qué hacemos cuando usamos las principales funciones de dplyr y tidyr. Veámoslo en acción:

library("dplyr")
library("tidyr")
library("tidylog", warn.conflicts = FALSE)
filtered <- filter(mtcars, cyl == 4)
#> filter: removed 21 rows (66%), 11 rows remaining
mutated <- mutate(mtcars, new_var = wt ** 2)
#> mutate: new variable 'new_var' (double) with 29 unique values and 0% NA

Si os fijáis, cada vez que ejecutas una función de dplyr nos devuelve un mensaje explicándonos que se ha hecho. Por ejemplo la linea de código filtered <- filter(mtcars, cyl == 4) ha creado un nuevo data.frame donde se han eliminado 21 filas del df original: #> filter: removed 21 rows (66%), 11 rows remaining.

La segunda linea de código mutated <- mutate(mtcars, new_var = wt ** 2) ha creado una nueva variable con mutate(): #> mutate: new variable 'new_var' with 29 unique values and 0% NA.




Tidyverse vs. Base R

Todo lo que se puede hacer con dplyr, tidyr etc. ,también se puede hacer con Base-R pero de una manera mucho menos intuitiva.

El siguiente ejemplo esta sacado de este post. Son dos trozos de código que hacen exactamente lo mismo:

Con el tidyverse:

library(dplyr)
mtcars %>% 
  group_by(cyl, am) %>%
  select(mpg, cyl, wt, am) %>%
  summarise(avgmpg = mean(mpg), avgwt = mean(wt)) %>%
  filter(avgmpg > 20)

Con la sintaxis de base R:

filter(
  summarise(
    select(
      group_by(mtcars, cyl, am),
      mpg, cyl, wt, am),
    avgmpg = mean(mpg), avgwt = mean(wt)),
  avgmpg > 20)

O puesto en horizontal

filter(summarise(select(group_by(mtcars, cyl, am),  mpg, cyl, wt, am),avgmpg = mean(mpg), avgwt = mean(wt)), avgmpg > 20)


Otros 2 ejemplos de comparación tidyverse versus Base-R:
df %>% filter(country == "Spain") %>%  select(year, lifeExp)

df[df$country == "Spain", c("year", "lifeExp")] 

Este último ejemplo lo introduzco porque quiero recordar estas trasparencias que explican que el paquete dbplyr traduce expresiones de dplyr a SQL. El código de las transparencias está aquí, y aquí puedes aprender como hacer queries SQL a un database utilizando la sintaxis de dplyr.

#- con tidyverse
df_cars %>% 
  select(longname, cyl, hp) %>%
  mutate( shortname = word(longname, 1) ) %>%
  select( - longname)  ->
df_cars_limited
head( df_cars_limited )

#- con dplyr PERO sin %>% 
head(
  select(
    mutate( 
      select(df_cars, longname, cyl, hp) , 
      shortname = word(longname, 1) 
      ), 
    -longname 
    )
)





Bibliografía



  1. Hasta la aparición de tidyr 1.0.0, las funciones que se usaban eran gather() y spread(). En esta conferencia, Hadley Wickham nos contó que uno de sus grandes errores durante el desarrollo del tidyverse fue la elección de los nombres de las funciones gather() y spread(). Finalmente podemos decir: bye bye gather() y spread(), wellcome tidyr 1.0.0 and pivot_longer() and pivot_wider().↩︎

  2. La última vez que miré la cheatsheet aún no estaba actualizada para recoger los cambios que aparecieron en dplyr 1.0.0, pero aún así os resultará de mucha utilidad↩︎

  3. Hasta mayo de 2020, esto es, hasta dplyr 1.0.0, esto se hacia con la función select_if()↩︎

  4. Hasta la aparición de dplyr 1.0.0 en mayo de 2020, se utilizaba la función sumarise_all()↩︎

LS0tCnRpdGxlOiAnRGF0YSBtdW5naW5nOiBtYW5lam8gZGUgZGF0b3MgY29uIFIsIHRoZSB0aWR5dmVyc2Ugd2F5JwphdXRob3I6ICJQZWRybyBKLiBQw6lyZXogKHBlZHJvLmoucGVyZXpAdXYuZXMpLiBVbml2ZXJzaXRhdCBkZSBWYWzDqG5jaWEgPGJyPiA8YnI+IFdlYiBkZWwgY3Vyc286IDxodHRwczovL3BlcmV6cDQ0LmdpdGh1Yi5pby9pbnRyby1kcy0yMi0yMy13ZWIvPiIKZGF0ZTogIk5vdmllbWJyZSBkZSAyMDE3IChhY3R1YWxpemFkbyBlbCBgciBmb3JtYXQoU3lzLnRpbWUoKSwgJyVkLSVtLSVZJylgKSIKb3V0cHV0OgogIGh0bWxfZG9jdW1lbnQ6CiAgICBjc3M6ICFleHByIGhlcmU6OmhlcmUoImFzc2V0cyIsICJzdHlsZXNfcGpwLmNzcyIpCiAgICB0aGVtZTogcGFwZXIKICAgIGhpZ2hsaWdodDogdGV4dG1hdGUKICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiAzCiAgICB0b2NfZmxvYXQ6CiAgICAgIGNvbGxhcHNlZDogdHJ1ZQogICAgICBzbW9vdGhfc2Nyb2xsOiB0cnVlCiAgICBzZWxmX2NvbnRhaW5lZDogdHJ1ZQogICAgbnVtYmVyX3NlY3Rpb25zOiBmYWxzZQogICAgaW5jbHVkZXM6CiAgICAgIGFmdGVyX2JvZHk6ICFleHByIGhlcmU6OmhlcmUoImFzc2V0cyIsICJmb290ZXIuaHRtbCIpIAogICAgICBpbl9oZWFkZXI6IAogICAgICAgIC0gIWV4cHIgaGVyZTo6aGVyZSgiYXNzZXRzIiwgImdvb2dsZS1hbmFseXRpY3MuaHRtbCIpIAogICAgICAgIC0gIWV4cHIgaGVyZTo6aGVyZSgiYXNzZXRzIiwgImZhdmljb24tc29sLmh0bWwiKQogICAgZGZfcHJpbnQ6IGthYmxlCiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCmVkaXRvcl9vcHRpb25zOgogIGNodW5rX291dHB1dF90eXBlOiBjb25zb2xlCi0tLQoKYGBge3IsIGluY2x1ZGUgPSBGQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpCmBgYAoKYGBge3IgY2h1bmstc2V0dXAsIGluY2x1ZGUgPSBGQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBldmFsID0gVFJVRSwgbWVzc2FnZSA9IEZBTFNFLCB3YXJuaW5nID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAjcmVzdWx0cyA9ICJob2xkIiwKICAgICAgICAgICAgICAgICAgICAgIGNhY2hlID0gRkFMU0UsIGNhY2hlLnBhdGggPSAiL2NhY2hlcy8iLCBjb21tZW50ID0gIiM+IiwKICAgICAgICAgICAgICAgICAgICAgICNmaWcud2lkdGggPSA3LCAjZmlnLmhlaWdodD0gNywKICAgICAgICAgICAgICAgICAgICAgICNvdXQud2lkdGggPSA3LCBvdXQuaGVpZ2h0ID0gNywKICAgICAgICAgICAgICAgICAgICAgIGNvbGxhcHNlID0gVFJVRSwgIGZpZy5zaG93ID0gImhvbGQiLAogICAgICAgICAgICAgICAgICAgICAgZmlnLmFzcCA9IDcvOSwgb3V0LndpZHRoID0gIjYwJSIsIGZpZy5hbGlnbiA9ICJjZW50ZXIiKQoKIy0gcGFyYSBtZWpvcmFyIGxvcyBncsOhZmljb3MsIGJ1ZW5vIGVuIHJlYWxpZGFkIHBhcmEgcXVlIHNlIHZlYW4gaWd1YWwgZW4gZGlzdGludG9zIFNPCiMtIGh0dHBzOi8vd3d3Lmp1bXBpbmdyaXZlcnMuY29tL2Jsb2cvci1rbml0ci1tYXJrZG93bi1wbmctcGRmLWdyYXBoaWNzLwprbml0cjo6b3B0c19jaHVuayRzZXQoZGV2ID0gInBuZyIsIGRldi5hcmdzID0gbGlzdCh0eXBlID0gImNhaXJvLXBuZyIpKQpgYGAKCmBgYHtyIG9wdGlvbnMtc2V0dXAsIGluY2x1ZGUgPSBGQUxTRX0Kb3B0aW9ucyhzY2lwZW4gPSA5OTkpICMtIHBhcmEgcXVpdGFyIGxhIG5vdGFjacOzbiBjaWVudMOtZmljYQpvcHRpb25zKCJ5YW1sLmV2YWwuZXhwciIgPSBUUlVFKSAjLSBodHRwczovL2dpdGh1Yi5jb20vdmlraW5nL3IteWFtbC9pc3N1ZXMvNDcgIChsbyBwdXNlIHggZWwgcGIgY29uIGVsIHdhcm5pbmcpIEVuIHJlYWxpZGFkIGNyZW8gcXVlIG1lam9yIHNlcsOtYSBwb25lcmxvIGVuIFJQcm9maWxlCmBgYAoKCmBgYHtyIGtsaXBweSwgZWNobyA9IEZBTFNFfQprbGlwcHk6OmtsaXBweShwb3NpdGlvbiA9IGMoInRvcCIsICJyaWdodCIpKSAjLSByZW1vdGVzOjppbnN0YWxsX2dpdGh1Yigicmxlc3VyL2tsaXBweSIpCmBgYAoKYGBge3IgY2FyZ2FyX3BrZ3MsIGVjaG8gPSBGfQpsaWJyYXJ5KCJoYXZlbiIpCiNsaWJyYXJ5KCJ4bHN4IikKbGlicmFyeSgiZm9yZWlnbiIpCmxpYnJhcnkoInJlYWR4bCIpCmxpYnJhcnkoImdhcG1pbmRlciIpCmxpYnJhcnkoInRpZHl2ZXJzZSIpCmBgYAoKCi0tLS0tLS0tLS0tLS0tLS0tCgo8YnI+CgojIDEuIEludHJvZHVjY2nDs24KCjxicj4KCkVuIGVsIHR1dG9yaWFsIGFudGVyaW9yIGFwcmVuZGltb3MgYSBjYXJnYXIgZGF0b3MgZW4gUi4gU2luIGVtYmFyZ28sIGVzIGRpZsOtY2lsIHF1ZSBlbiB1bmEgYXBsaWNhY2nDs24gcmVhbCB0ZW5nYW1vcyBsb3MgZGF0b3MgdGFsIHkgY29tbyBsb3MgbmVjZXNpdGFtb3MgcGFyYSBoYWNlciBudWVzdHJvIGFuw6FsaXNpcy4gSGFiaXR1YWxtZW50ZSB0ZW5kcmVtb3MgcXVlIHRyYWJhamFyIGxvcyBkYXRvcyBwYXJhIGFycmVnbGFybG9zLiBFc3RlIHByb2Nlc28sIHF1ZSBlbiBjYXN0ZWxsYW5vIHBvZHLDrWEgbGxhbWFyc2UgImxpbXBpZXphIiBvICoqcHJvY2VzYWRvIGRlIGRhdG9zKiosIHNlIGNvbm9jZSBlbiBpbmdsw6lzIGNvbW8gKipkYXRhIG11bmdpbmcgb3IgZGF0YSB3cmFuZ2xpbmcqKi4KCkVuIGVsIGN1cnNvIHZhbW9zIGEgdHJhYmFqYXIvbWFuZWphciBsb3MgZGF0b3MgdXNhbmRvIHVuIGNvbmp1bnRvIGRlIHBhcXVldGVzIGFzb2NpYWRvcyBhc29jaWFkb3MgY29uIGVsIGVuZm9xdWUgY29ub2NpZG8gY29tbyAqKnRpZHl2ZXJzZSoqLiBDb21vIHB1ZWRlcyB2ZXIgZW4gbGEgaW1hZ2VuLCB5YSBoZW1vcyBpbXBvcnRhZG8gbG9zIGRhdG9zIHksIGFudGVzIGRlIGVtcGV6YXIgYSBoYWNlciBlbCB2ZXJkYWRlcm8gYW7DoWxpc2lzLCB0ZW5lbW9zIHF1ZSBwYXNhciBwb3IgMiBldGFwYXMgbcOhczogCgogIC0gaGFjZXIgbnVlc3Ryb3MgZGF0b3MgdGlkeQogIC0gYXJyZWdsYXJsb3MgcGFyYSBxdWUgc2VhbiDDunRpbGVzIHBhcmEgbnVlc3Ryb3MgcHJvcMOzc2l0b3MKCjxicj4KCgpgYGB7ciAsIGVjaG89RkFMU0UsIGZpZy5jYXA9IioqRGF0YSB3cmFuZ2xpbmcqKiBmcm9tIGh0dHA6Ly9yNGRzLmhhZC5jby5uei93cmFuZ2xlLWludHJvLmh0bWwiLCBldmFsID0gVFJVRSwgZmlnLmFzcCA9IDQvMiwgb3V0LndpZHRoID0gIjgwJSIsIGZpZy5hbGlnbiA9ICJjZW50ZXIifQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhoZXJlOjpoZXJlKCJpbWFnZW5lcyIsICJ0dF8wNV9pbWdfMDFfZGF0YS13cmFuZ2xlLnBuZyIpKQpgYGAKCjxicj4KCgpTZSBzdWVsZSBkZWNpciBxdWUgZWwgcHJvY2VzYWRvL2xpbXBpZXphIGRlIGxvcyBkYXRvcyBzdWVsZSBvY3VwYXIgdW4gODAlIGRlbCB0aWVtcG8gZGUgdW4gYW7DoWxpc2lzIGRlIGRhdG9zLiBRdWl6w6FzIHNlYSB1bmEgY2lmcmEgdW4gcG9jbyBleGFnZXJhZGEsIHBlcm8sIGVuIGN1YWxxdWllciBjYXNvLCBlcyB1bmEgdGFyZWEgcXVlIG9jdXBhIHRpZW1wbyB5IHF1ZSBwdWVkZSBsbGVnYXIgYSBzZXIgdGVkaW9zYSB5IGZydXN0cmFudGUgc2kgbm8gc2UgZGlzcG9uZSBkZSBsYXMgKipoZXJyYW1pZW50YXMgYWRlY3VhZGFzKiouIEluY2x1c28gZGF0b3MgcXVlIHBhcmVjZW4gcXVlIHlhIGVzdMOhbiB0cmFiYWphZG9zIGVzIGJhc3RhbnRlIGbDoWNpbCBxdWUgdGVuZ2Ftb3MgcXVlIHRyYWJhamFybG9zIHBhcmEgYWRhcHRhcmxvcyBhIG51ZXN0cmFzIG5lY2VzaWRhZGVzLgoKCmBgYHIKVW5hIHNlY3VlbmNpYSAicmVhbCIgZGUgdHdlZXRzOgoKMDkzMDogSG93IGx1Y2t5IEkgYW0gdG8gd29yayBvbiBjbGVhbiBkYXRhc2V0cyBjdXJhdGVkIGJ5IHRydWUgcHJvZmVzc2lvbmFscy4KMTMzMDogSHVoLiBTb21lIGluY29uc2lzdGVuY2llcyBoZXJlLiBObyBiaWdzLiBJbGwganVzdCB3cml0ZSB1cCBzb21lIHF1aWNrIGFuZCBkaXJ0eSByZWdleCB0byBjbGVhbiB0aGlzIHVwLgoKMTcyMDogSSBXSUxMIEJVUk4gVEhJUyBIRVJFVElDQUwgREFUQSBDRU5URVIgQU5EIFNDQVRURVIgSVRTIEFTSEVTICh0cmFkdWNjacOzbjogbWUgY2FnbyBlbiB0b2RvIGxvIHF1ZSBzZSBtZW5lYSkKYGBgCgpFbiBjbGFzZSB1dGlsaXphbW9zIGRhdG9zIHJlYWxlcywgcGVybyBsYSB2ZXJkYWQgZXMgcXVlIHN1ZWxlbiB5YSBlc3RhciBjYXNpIGxpbXBpb3MgZGVsIHRvZG8uIEVzdGFtb3MgYXByZW5kaWVuZG8uCgo+IENsYXNzcm9vbSBkYXRhIGFyZSBsaWtlIHRlZGR5IGJlYXJzOyByZWFsIGRhdGEgYXJlIGxpa2UgYSBncml6emx5IHdpdGggc2FsbW9uIGJsb29kIGRyaXBwaW5nIG91dCBpdHMgbW91dGguIC0tLS0gIFtcQEplbm55QnJ5YW5dCgoKQ29tbyBkaWNlIEFsYmVydCBZLiBLaW0gZW4gW2VzdGFzIHRyYW5zcGFyZW5jaWFzXShodHRwOi8vcnB1YnMuY29tL3J1ZGVib3liZXJ0L2VDT1RTXzIwMTgpIGxvcyBkYXRvcyB1dGlsaXphZG9zIHBhcmEgYXByZW5kZXIgYSBtYW5lamFyIGRhdG9zIHRpZW5lbiBxdWUgc2VyIHJlYWxpc3RhcyBwZXJvIHNpbiBsbGVnYXIgYSBzZXIgaW50aW1pZGFudGVzLgoKYGBge3IgZWNobyA9IEZBTFNFLCBvdXQud2lkdGggPSAiMTIwJSIsIGV2YWwgPSBUUlVFfQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhoZXJlOjpoZXJlKCIuL2ltYWdlbmVzL3R0XzA1X2ltZ18wMl9ncml6bGx5LXZzLXRlZGR5LmpwZyIpKQpgYGAKCgo8YnI+CgoKRW4gZXN0ZSB0dXRvcmlhbCBhcHJlbmRlcmVtb3MgYSBsaW1waWFyIHkgdHJhbnNmb3JtYXIgZGF0b3MgZW4gUi4gUHJpb3JpemFyZW1vcyBsYSBudWV2YSBmb3JtYSBkZSBoYWNlciBsYXMgY29zYXMgZW4gUiAobyB3b3JrZmxvdykgY29ub2NpZG8gY29tbyAqKnRpZHl2ZXJzZSoqLiBFbiBsb3Mgw7psdGltb3MgYcOxb3Mgc2UgaGEgY29udmVydGlkbywgcG9yIHZhcmlhcyByYXpvbmVzLCBlbiBlbCBlbmZvcXVlIGVzdMOhbmRhcjsgYSBwZXNhciBkZSBlbGxvLCBjYWRhIGNpZXJ0byB0aWVtcG8gdnVlbHZlIGEgcmVhYnJpcnNlIGVsIGRlYmF0ZSBzb2JyZSBjw7NtbyBlbnNlw7Fhci9hcHJlbmRlciBSIHkgc2kgZXMgYXByb3BpYWRvIHByaW9yaXphciBlbCB0aWR5dmVyc2Ugc29icmUgUi1iYXNlLiBbQXF1w61dKGh0dHBzOi8vdHdpdHRlci5jb20va2FpamFfYmVhbi9zdGF0dXMvMTIxNzI5MzM5NjcwNjA1NDE0NSkgdGllbmVzIHVuIGhpbG8gZGUgdHdpdHRlciBkb25kZSBzZSBkZWJhdGUgc29icmUgZXN0ZSB0ZW1hLgoKPGJyPgoKCltBcXXDrV0oaHR0cDovL3d3dy5vbnRoZWxhbWJkYS5jb20vMjAxNC8wMi8xMC9ob3ctZHBseXItcmVwbGFjZWQtbXktbW9zdC1jb21tb24tci1pZGlvbXMvKSB0ZW7DqWlzIHVuIHBvc3Qgc29icmUgbGFzIGRpZmVyZW5jaWFzIGVudHJlIGxhcyBmdW5jaW9uZXMgZGUgUi1iYXNlIHkgbGFzIGRlbCB0aWR5dmVyc2UgcGFyYSBlbCBwcm9jZXNhZG8gZGUgZGF0b3MsIHkgW2FxdcOtXShodHRwOi8vemV2cm9zcy5jb20vYmxvZy8yMDE1LzAxLzEzL2EtbmV3LWRhdGEtcHJvY2Vzc2luZy13b3JrZmxvdy1mb3Itci1kcGx5ci1tYWdyaXR0ci10aWR5ci1nZ3Bsb3QyLykgb3RybyBwb3N0IGRlIHVuIG51ZXZvIGNvbnZlbmNpZG8gZGUgbGFzIGJvbmRhZGVzIGRlIGVzdGEgbnVldmEgZm9ybWEgZGUgbWFuaXB1bGFyIGRhdG9zIGVuIFIuIENvbW8gZWplbXBsbzoKCgo+IFVwIHVudGlsIGxhc3QgeWVhciBteSBSIHdvcmtmbG93IHdhcyBub3QgZHJhbWF0aWNhbGx5IGRpZmZlcmVudCBmcm9tIHdoZW4gSSBzdGFydGVkIHVzaW5nIFIgbW9yZSB0aGFuIDEwIHllYXJzIGFnby4gVGhhbmtzIHRvIHNldmVyYWwgUiBwYWNrYWdlIGF1dGhvcnMsIG1vc3Qgbm90YWJseSBIYWRsZXkgV2lja2hhbSwgbXkgd29ya2Zsb3cgaGFzIGNoYW5nZWQgZm9yIHRoZSBiZXR0ZXIgdXNpbmcgZHBseXIsIG1hZ3JpdHRyLCB0aWR5ciBhbmQgZ2dwbG90Mi4gR2l2ZW4gaG93IG11Y2ggSSd2ZSBlbmpveWVkIHRoZSBzcGVlZCBhbmQgY2xhcml0eSBvZiB0aGUgbmV3IHdvcmtmbG93Cgo8YnI+CgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyMgIFRpZHl2ZXJzZQoKIyMjIyDCv1F1w6kgZXMgZXN0byBkZWwgdGlkeXZlcnNlPwoKPGJyPgoKQ29uIGxhIHBhbGFicmEgdGlkeXZlcnNlIHNlIGhhY2UgcmVmZXJlbmNpYSBhIHVuYSAibnVldmEiIGZvcm1hIGRlIGFmcm9udGFyIGVsIGFuw6FsaXNpcyBkZSBkYXRvcyBlbiBSIGVuIGxhIHF1ZSBzZSAgaGFjZSB1c28gZGUgKip1biBncnVwbyBkZSBwYXF1ZXRlcyBxdWUgdHJhYmFqYW4gZW4gYXJtb27DrWEqKiBwb3JxdWUgY29tcGFydGVuIGNpZXJ0b3MgcHJpbmNpcGlvcywgY29tbyBwb3IgZWplbXBsbywgbGEgZm9ybWEgZGUgZXN0cnVjdHVyYXIgbG9zIGRhdG9zLiAKCkxhIG1heW9yw61hIGRlIGVzdG9zIHBhcXVldGVzIGhhbiBzaWRvIGRlc2Fycm9sbGFkb3MgcG9yIChvIGFsIG1lbm9zIGNvbiBsYSBjb2xhYm9yYWNpw7NuIGRlKSBbSGFkbGV5IFdpY2toYW1dKGh0dHA6Ly9oYWRsZXkubnovKS4gRXN0YSBlcyBsYSBbcMOhZ2luYSB3ZWIgZGVsIHRpZHl2ZXJzZV0oaHR0cHM6Ly93d3cudGlkeXZlcnNlLm9yZy8pCgoKTm8gZXMgbmVjZXNhcmlvLCBwZXJvIHNpIHF1aWVyZXMgY29ub2NlciB1biBwb2NvIG1lam9yIHF1w6kgZXMgZWwgdGlkeXZlcnNlLCBwdWVkZXMgaGFjZXJsbyBsZXllbmRvIFtUaGUgdGlkeSB0b29scyBtYW5pZmVzdG9dKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy90aWR5dmVyc2UvdmlnbmV0dGVzL21hbmlmZXN0by5odG1sKS4gRXN0w6EgY2l0YSBlcyB1biBidWVuIHJlZmVyZW50ZSBkZSBsYSBmaWxvc29mw61hIG8gZW5mb3F1ZSBkZWwgdGlkeXZlcnNlCgo+IFByb2dyYW1zIG11c3QgYmUgd3JpdHRlbiBmb3IgcGVvcGxlIHRvIHJlYWQsIGFuZCBvbmx5IGluY2lkZW50YWxseSBmb3IgbWFjaGluZXMgdG8gZXhlY3V0ZSAgLS0gSGFsIEFiZWxzb24KCgo8YnI+CgpQYXJhIGNvbnRpbnVhciBlbnRlbmRpZW5kbyBxdcOpIGVzIGVzdG8gZGVsIHRpZHl2ZXJzZSwgY2l0YXLDqSAyIGRlIHN1cyBwcmluY2lwaW9zOgoKICAtIExvcyBzY3JpcHRzIGRlYmVuIHNlciAqKiJmw6FjaWxtZW50ZSIgbGVnaWJsZXMgcG9yIGxhcyBwZXJzb25hcyoqICAKICAKICAtICoqUmVzb2x2ZXIgcHJvYmxlbWFzIGNvbXBsZWpvcyoqIGVuY2FkZW5hbmRvIGZ1bmNpb25lcyBzaW1wbGVzIGNvbiBlbCAqKm9wZXJhZG9yIGAlPiVgKioKCgo8YnI+CgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgoKIyMgVGhlIHBpcGUgKGAgJT4lIGApCgpFc3RlIG9wZXJhZG9yIG9jdXBhIHVuIGx1Z2FyIGZ1bmRhbWVudGFsIGVuIGVsIHRpZHl2ZXJzZS4gUGVybWl0ZSByZXNvbHZlciB1biBwcm9ibGVtYSBjb21wbGVqbyBubyBkZSB1bmEgc29sYSB2ZXosIHNpbm8gZW5jYWRlbmFuZG8gbGxhbWFkYXMgYSBmdW5jaW9uZXMgcXVlIHNlIHZhbiBlbmNhZGVuYW5kbyBjb24gZWwgb3BlcmFkb3IgYCU+JWAuIEVzdGUgb3BlcmFkb3IgZmFjaWxpdGEgbXVjaG8gbGEgbGVjdHVyYSBlIGludGVycHJldGFjacOzbiBkZWwgY8OzZGlnbywgeWEgcXVlIHNlIHZhbiBlbmNhZGVuYW5kbyBvcGVyYWNpb25lcyBzZW5jaWxsYXMgcGFyYSwgcG9jbyBhIHBvY28sIGNvbnNlZ3VpciB0cmFuc2Zvcm1hY2lvbmVzIGRlIGRhdG9zIGNvbXBsZWphcy4gRWwgKipvcGVyYWRvciBwaXBlKiogc2UgbG8gZGViZW1vcyBhIFN0ZWZhbiBCYWNoZSBlbiBzdSBwa2cgW21hZ3JpdHRyXShodHRwczovL2dpdGh1Yi5jb20vdGlkeXZlcnNlL21hZ3JpdHRyKS4KCgoKYGBge3IgLCBlY2hvPUZBTFNFLCBldmFsID0gVFJVRSwgZmlnLmFzcCA9IDQvMiwgb3V0LndpZHRoID0gIjIwJSIsIGZpZy5hbGlnbiA9ICJjZW50ZXIifQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhoZXJlOjpoZXJlKCJpbWFnZW5lcyIsICJ0dF8wNV9pbWdfMDNfb3BlcmFkb3ItcGlwZS5wbmciKSkKYGBgCgo8YnI+CgpFbiBwYWxhYnJhcywgbG8gcXVlIGhhY2UgZXN0ZSBvcGVyYWRvciBlcyAqKnBhc2FyIGVsIGVsZW1lbnRvIHF1ZSBlc3TDoSBhIHN1IGl6cXVpZXJkYSBjb21vIHVuIGFyZ3VtZW50byBkZSBsYSBmdW5jacOzbiBxdWUgdGllbmUgYSBsYSBkZXJlY2hhKiouIEFzw60gYWwgcHJpbmNpcGlvIHBhcmVjZSBjb21wbGljYWRvLiAKCgoqKkNvbiBleHByZXNpb25lcyoqIGVsIG9wZXJhZG9yIHBpcGUgaGFjZTogCmBgYHtyLCBlY2hvID0gRkFMU0UsIGV2YWwgPSBUUlVFfQphYSA8LSBkYXRhLmZyYW1lKHggPSBjKCJmKG9iamVjdCwgYXJndW1lbnRvcyBkZSBsYSBmdW5jacOzbikiLCAiIiksICB5ID0gYygiRVMgRVFVSVZBTEVOVEUgYSIsICItLSIpLCB6ID0gYygib2JqZWN0ICAlPiUgIGYoYXJndW1lbnRvcyBkZSBsYSBmdW5jacOzbikiLCAiIikgKQprbml0cjo6a2FibGUoYWEsY29sLm5hbWVzID0gYygiICIsICItLSIsICIgIiksIGFsaWduID0gImMiKQpgYGAKCgoKPGJyPgoKKipTZSBlbnRpZW5kZSBtZWpvciBjb24gZWplbXBsb3Mgc2VuY2lsbG9zKiouIExhcyBzaWd1aWVudGVzIGRvcyBpbnN0cnVjY2lvbmVzIGRlIFIgKipoYWNlbiBleGFjdGFtZW50ZSBsbyBtaXNtbyoqOiBwZXJtaXRlbiB2ZXIgbGFzIDQgcHJpbWVyYXMgZmlsYXMgZGVsIGBwZW5ndWluc2AgZGF0YXNldC4KCmBgYHtyLCBlY2hvID0gVFJVRSwgZXZhbCA9IEZBTFNFfQpsaWJyYXJ5KHBhbG1lcnBlbmd1aW5zKQoKaGVhZChwZW5ndWlucywgbiA9IDQpICAgICAgICAgIy0gZm9ybWEgaGFiaXR1YWwgZGUgbGxhbWFyL3VzYXIgbGEgZnVuY2nDs24gaGVhZCgpCgpwZW5ndWlucyAlPiUgaGVhZCguICwgbiA9IDQpICAjLSB1c2FuZG8gZWwgb3BlcmFkb3IgcGlwZQpgYGAKCgpMYSBwcmltZXJhIGV4cHJlc2nDs24gZXMgbGEgbWFuZXJhIGhhYml0dWFsIGRlIHVzYXIvbGxhbWFyIGEgbGEgZnVuY2nDs24gYGhlYWQoKWAuIExhIHNlZ3VuZGEgZXhwcmVzacOzbiBlcyBsYSBzaW50YXhpcywgbGEgZm9ybWEgcXVlIGhheSBxdWUgdXNhciwgc2kgdHJhYmFqYW1vcyBjb24gZWwgb3BlcmFkb3IgcGlwZS4KCgpBc8OtLCBhIHByaW1lcmEgdmlzdGEsIHBhcmVjZSBxdWUgZWwgb3BlcmFkb3IgYCU+JWAgbm8gc3Vwb25lIG5pbmd1bmEgdmVudGFqYSwgc8OzbG8gZXMgdW5hIGZvcm1hIGRpc3RpbnRhIGRlIGVqZWN1dGFyIG8gbGxhbWFyIGEgdW5hIGZ1bmNpw7NuLCB5IGEgcHJpbWVyYSB2aXN0YSBwYXJlY2UgY29tcGxpY2FyIGxhcyBjb3Nhcy4gU8OtLCBlc28gZXMgY2llcnRvLCBzaSBzb2xvIHVzYXMgdW5hIGZ1bmNpw7NuIG5vIHRlbmRyw61hIG11Y2hvIHNlbnRpZG8gdXNhciBgJT4lYCwgcGVybyBjdWFuZG8gdGllbmVzIHF1ZSBoYWNlciB1bmEgc3VjZXNpw7NuIGRlIGPDoWxjdWxvcywgdW5hIHN1Y2VzacOzbiBkZSBsbGFtYWRhcyBhIGZ1bmNpb25lcywgZmFjaWxpdGEgbXVjaG8gbGEgbGVjdHVyYSBkZWwgY8OzZGlnbyB5IHBvciB0YW50byBlbCBhbsOhbGlzaXMuIExvIHZlbW9zIGVuc2VndWlkYS4KClBhcmEgZW50ZW5kZXIgdW4gcG9jbyBtw6FzIGVsIGZ1bmNpb25hbWllbnRvIGRlIGAlPiVgLCBoYXMgZGUgdmVyIHF1ZSBlc3RhcyB0cmVzIGluc3RydWNjaW9uZXMgc29uIGVxdWl2YWxlbnRlcy4KCmBgYHtyLCBlY2hvID0gVFJVRSwgZXZhbCA9IEZBTFNFfQpoZWFkKHBlbmd1aW5zLCBuID0gNCkgICAgICAgICAjLSBmb3JtYSBoYWJpdHVhbCBkZSBsbGFtYXIvdXNhciBsYSBmdW5jacOzbiBoZWFkKCkKCnBlbmd1aW5zICU+JSBoZWFkKC4gLCBuID0gNCkgICMtIHVzYW5kbyBlbCBvcGVyYWRvciBwaXBlIChjb24gZWwgcHVudG8gYWN0dWFuZG8gY29tbyBwbGFjZWhvbGRlcikKCnBlbmd1aW5zICU+JSBoZWFkKG4gPSA0KSAgICAgICMtIHVzYW5kbyBlbCBvcGVyYWRvciBwaXBlIChTSU4gZWwgcHVudG8pCmBgYAoKRWwgcHVudG8gZGUgbGEgc2VndW5kYSBleHByZXNpw7NuIHNlw7FhbGEsIGxlIGRpY2UgYSB0aGUgcGlwZSBkb25kZSBkZWJlIHNpdHVhcnNlIGVsIGFyZ3VtZW50byBkZSBsYSBpenF1aWVyZGEgZGVudHJvIGRlIGxhIGZ1bmNpw7NuOyBlbiBudWVzdHJvIGVqZW1wbG8gbGUgZGljZSBhIGAlPiVgIHF1ZSBgcGVuZ3VpbnNgIGRlYmUgc2l0dWFyc2UgZW4gZWwgcHJpbWVyIHNsb3QgZGUgYGhlYWQoKWAuIEVsIHB1bnRvIGAuYCBsZSBlc3TDoSBkaWNpZW5kbyBhIGAlPiVgIGRvbmRlIGRlYmUgc2l0dWFyc2UgYHBlbmd1aW5zYDsgZXMgZGVjaXIsIGVsIHB1bnRvIGFjdMO6YSwgbG8gZXN0YW1vcyB1c2FuZG8sIGNvbW8gdW4gInBsYWNlaG9sZGVyIi4KCkxhIHRlcmNlcmEgZXhwcmVzacOzbiB0YW1iacOpbiBmdW5jaW9uYSBwb3JxdWUgc2kgbm8gdXNhbW9zIGVsIHB1bnRvIChgLmApLCBlbnRvbmNlcywgcG9yIGRlZmVjdG8sIGVsIG9wZXJhZG9yIHBpcGUgc2l0dWFyw6EgYHBlbmd1aW5zYCBlbiBlbCBwcmltZXIgc2xvdCBkZSBsYSBmdW5jacOzbiwgZW4gbnVlc3RybyBjYXNvIHNpdHVhcsOhIGEgYHBlbmd1aW5zYCBlbiBlbCBwcmltZXIgc2xvdCBkZSBgaGVhZCgpYC4KCkxhIGZvcm1hIG3DoXMgaGFiaXR1YWwgZXMgbm8gcG9uZXIgZWwgYC5gOyBlcyBkZWNpciwgbGEgdGVyY2VyYSBleHByZXNpw7NuLiBMYSByYXrDs24gZXMgc2ltcGxlbWVudGUgcXVlIHNlIGFob3JyYSB0aWVtcG8gYWwgZXNjcmliaXIsIGF1bnF1ZSBsYSBzZWd1bmRhIGV4cHJlc2nDs24gZXMgbXVjaG8gbcOhcyBleHBsaWNpdGEsIG3DoXMgZGVzY3JpcHRpdmEsIGRlIGxvIHF1ZSBoYWNlIGVsIG9wZXJhZG9yIHBpcGUuCgo8YnI+CgpQYXJhIGNhc2kgdGVybWluYXIgZGUgZW50ZW5kZXIgbGEgc2ludGF4aXMgZGVsIG9wZXJhZG9yIHBpcGUuIEludGVudGFkIHZlciBzaSBlbnRlbmTDqWlzIGxhIHNpZ3VpZW50ZSBpbnN0cnVjY2nDs246CgpgYGB7ciwgZWNobyA9IFRSVUUsIGV2YWwgPSBGQUxTRX0KNCAlPiUgaGVhZChwZW5ndWlucywgLikKYGBgCgpTaSBubyBzYWLDqWlzIGxvIHF1ZSBoYWNlLCBzaWVtcHJlIHBvZMOpaXMgZWplY3V0YXIgbGEgaW5zdHJ1Y2Npw7NuIGVuIGxhIGNvbnNvbGEgZGUgUlN0dWRpby4KClJlY3VlcmRhOiBDdWFuZG8gdXNhbW9zIGVsIG9wZXJhZG9yIHBpcGUsIHRlbmVtb3Mgb2JsaWdhdG9yaWFtZW50ZSBxdWUgdXNhciBlbCBwdW50byBzaSBxdWVyZW1vcyBxdWUgZWwgYXJndW1lbnRvIGRlIGxhIGl6cXVpZXJkYSBzZSBzaXTDumUgZW4gdW4gc2xvdCBkaWZlcmVudGUgZGVsIHByaW1lciBzbG90Cgo8YnI+CgpQYXJhIGFjYWJhciBudWVzdHJvIHJlcGFzbyBhIGAlPiVgIG1pcmFkIHBvciBxdcOpIG5vIGZ1bmNpb25hIGxhIHNpZ3VpZW50ZSBpbnN0cnVjY2nDs246CgpgYGB7ciwgZWNobyA9IFRSVUUsIGV2YWwgPSBGQUxTRX0KNCAlPiUgaGVhZChwZW5ndWlucykKYGBgCgpFbCBvcGVyYWRvciBwaXBlIHF1aWVyZSBsbGV2YXIgZWwgYDRgIGFsIHByaW1lciBzbG90IGRlIGBoZWFkKClgIHlhIHF1ZSBzaSBubyBwb25lbW9zIGVsIHB1bnRvLCBlc2UgZXMgc3UgY29tcG9ydGFtaWVudG8gcG9yIGRlZmVjdG8uIFNpbiBlbWJhcmdvLCBhbCBlamVjdXRhciBsYSBleHByZXNpw7NuLCBlbCBpbnRlcnByZXRlIGRlIFIgbm9zIGRldnVlbHZlIHVuIG1lbnNhamUgZGUgZXJyb3IuIMK/UG9yIHF1w6k/IFRlbmRyw6FzIHF1ZSBtaXJhciBsYSBheXVkYSBkZSBsYSBmdW5jacOzbiBjb24gYGhlbHAoaGVhZClgLgoKPGJyPgoKQcO6biBubyBzYWJlbW9zIG11eSBiaWVuIGN1w6FsIGVzIHN1IHV0aWxpZGFkLCBwZXJvIHlhIGNvbm9jZW1vcyBsYSBzaW50YXhpcyBkZSBgJT4lYC4gTG8gcXVlIGhhY2UgcXVlIGVzdGUgb3BlcmFkb3Igc2VhIHRhbiDDunRpbCBlcyBxdWUgKipsYXMgcGlwZXMgc2UgcHVlZGVuIGVuY2FkZW5hcioqLiAKCjxicj4KCkVsIG9wZXJhZG9yIHBpcGUgcG9kZW1vcyBsZWVybG8gY29tbyAqKiIqZW50b25jZXMqIioqIHkgcGVybWl0ZSBlbmNhZGVuYXIgc3VjZXNpdmFzIGxsYW1hZGFzIGEgZnVuY2lvbmVzLiBQb3IgZWplbXBsbzoKCmBgYHtyLCBlY2hvID0gVFJVRSwgZXZhbCA9IEZBTFNFfQpwZW5ndWlucyAlPiUgZmlsdGVyKHNleCA9PSAiZmVtYWxlIikgJT4lIAogICAgICAgICAgICAgZ3JvdXBfYnkoc3BlY2llcykgJT4lIAogICAgICAgICAgICAgc3VtbWFyaXNlKHBlc29fbWVkaW8gPSBtZWFuKGJvZHlfbWFzc19nKSkKYGBgCjxicj4KCkxhIGFudGVyaW9yIGxpbmVhIGRlIGPDs2RpZ28gUiBoYWNlOgoKICAxKSBjb2dlIGxvcyBkYXRvcyBkZSBwaW5nw7xpbm9zIHkgc2VsZWNjaW9uYSAobyBmaWx0cmEpIGxhcyBmaWxhcy9waW5ndWlub3MgY3V5byB2YWxvciBkZSBsYSB2YXJpYWJsZSBgc2V4YCBlcyBmZW1hbGU7IGVzIGRlY2lyLCBzZWxlY2Npb25hbW9zIGxvcyBwaW5nw7xpbm9zIGhlbWJyYXMsICplbnRvbmNlcyogKG8gZGVzcHXDqXMpICAKICAyKSBhZ3J1cGEgbG9zIGRhdG9zL3BpbmfDvGlub3MgcG9yIGxhIHZhcmlhYmxlIGBzcGVjaWVzYCwgKmVudG9uY2VzKiAgCiAgMykgY2FsY3VsYSBsYSBtZWRpYSBkZSBgYm9keV9tYXNzX2dgCgpFbiBjb25qdW50bywgZW5jYWRlbmFuZG8gbGFzIDMgZnVuY2lvbmVzIGhlbW9zIHNlbGVjY2lvbmFkbyBsYXMgZmlsYXMgcXVlIHBlcnRlbmVjZW4gYSBwaW5nw7xpbm9zIGhlbWJyYXMsIGhlbW9zIGFncnVwYWRvIGxhcyBwaW5nw7xpbm8gaGVtYnJhcyBlbiBmdW5jacOzbiBkZSBzdSBlc3BlY2llIChoYXkgMyBlc3BlY2llcyBkZSBwaW5nw7xpbm9zKSB5IGNhbGN1bGFkbyBlbCBwZXNvIG1lZGlvIGRlIGNhZGEgdW5vIGRlIGxhcyAzIGVzcGVjaWVzIGRlIHBpbmfDvGlub3M7IGVzIGRlY2lyLCBoZW1vcyBjYWxjdWxhZG8gZWwgcGVzbyBtZWRpbyBkZSBsYXMgcGluZ8O8aW5vcyBoZW1icmEgZW4gY2FkYSB1bm8gZGUgbGFzIHRyZXMgZXNwZWNpZXMgZGUgcGluZ8O8aW5vcy4KCjxicj4KCkNvbiBlc3RhIG51ZXZhIHNpbnRheGlzIChxdWUgcGVybWl0ZSBlbCBvcGVyYWRvciBwaXBlKSB5YSBubyBuZWNlc2l0YW1vcyBhbmlkYXIgZnVuY2lvbmVzLCBzaW5vIHF1ZSBsYXMgaW5zdHJ1Y2Npb25lcyB2YW4gdW5hIGRlc3B1w6lzIGRlIG90cmEuIEVzICoqbXVjaG8gbcOhcyBmw6FjaWwgZGUgbGVlciB5IGRlIGVzY3JpYmlyKiouIEVzdGEgaWRlYSBkZSBxdWUgZXMgbXVjaG8gbcOhcyBmw6FjaWwgZXNjcmliaXIgw6AgbGEgdGlkeXZlcnNlIG5vIHNlIGxsZWdhIGEgYXByZWNpYXIgY29uIGxvcyBlamVtcGxvcyBxdWUgaGVtb3MgaGVjaG8gZW4gZXN0YSBzZWNjacOzbiwgcGVybyBzZSBoYXLDoSBldmlkZW50ZSBjdWFuZG8gZW1wZWNlbW9zIGEgZW5jYWRlbmFyIG9wZXJhY2lvbmVzIGNvbiBkcGx5ci4gQ29tbyBlamVtcGxvIGVzdGUgdHdlZXQuCgoKCmBgYHtyLCBlY2hvID0gRkFMU0V9CnR3ZWV0cm1kOjp0d2VldF9lbWJlZCgiaHR0cHM6Ly90d2l0dGVyLmNvbS9hbmRyZXdoZWlzcy9zdGF0dXMvMTE3Mzc0MzQ0NzE3MTM1NDYyNCIsIHRoZW1lID0gImxpZ2h0IiwgYWxpZ24gPSAiY2VudGVyIiwgbWF4d2lkdGggPSA2NTApCmBgYAoKPGJyPgoKTm8gdGUgdmEgYSByZXN1bHRhciBzZW5jaWxsbyBwb3JxdWUgbm8gc2FiZXMgcXVlIGVzIGBsZXR0ZXJzYCwgbmkgYHBhc3RlMCgpYCwgbmkgYHRvdXBwZXIoKWAsIHBlcm8gaW50ZW50YSBlbnRlbmRlciBwb3IgdGkgbWlzbW8gcXVlIGhhY2UgbGEgc2lndWllbnRlIGxpbmVhIGRlIGPDs2RpZ28uIHlhIHNhYmVzIHF1ZSBzaWVtcHJlIHB1ZWRlcyBlamVjdXRhcmxhIHkgdmVyIHF1ZSBoYWNlLCB5IG11Y2hvIG1lam9yIHNpIGxhIGVqZWN1dGFzIHBvciB0cm96b3MgcGFyYSBpciB2aWVuZG8gcG9jbyBhIHBvY28gcXXDqSBoYWNlOgoKYGBge3IsIGVjaG8gPSBUUlVFLCBldmFsID0gRkFMU0V9CmxldHRlcnMgJT4lIHBhc3RlMCggIi0tLS0tIiAsICAuICAsICAiISEhIiApICU+JSB0b3VwcGVyCmBgYAoKPGJyPgoKIyMjIyBVbiBwb2NvIG3DoXMgYWNlcmNhIGRlIHRoZSBwaXBlIChgICU+JSBgKSBbT1BDSU9OQUxdCgoKRGUgZm9ybWEgbcOhcyB0w6ljbmljYS4gQXF1w60gcG9kw6lpcyB2ZXIgZWwgZnVuY2lvbmFtaWVudG8gZGVsIG9wZXJhZG9yIHBpcGU6CgpgYGByCmxpYnJhcnkoIm1hZ3JpdHRyIikKCiMtLS0tLS0tLSBSdWxlIDEKZih4eCkgICAgIGVzIGVxdWl2YWxlbnRlIGEgICAgIHh4ICU+JSBmCgojLS0tLS0tLS0gUnVsZSAyCmcoeHgsIG4gPSA1KQp4eCAlPiUgZyhuID0gNSkKCiMtLS0tLS0tLSBSdWxlIDMKZyhmKHh4KSwgbiA9IDUpCnh4ICU+JSBmICU+JSBnKG4gPSA1KQoKU2UgbGVlIGNvbW8gIlRha2UgeHggdGhlbiBkbyBmIHRoZW4gZG8gZyB3aXRoIG4gPSA1Ii4KCiMtLS0tLS0tLSBSdWxlIDQKZih5LCB4KQp4ICU+JSBmKHksIC4pCgojLS0tLS0tLS0gUnVsZSA1CmYoeSwgeiA9IHgpCnggJT4lIGYoeSwgeiA9IC4pCgojKCEhISEpLS0tLS0tLS0tLS0tLSBCT05VUzogVGhlIGlucHV0IHRvIHRoZSBwaXBlbGluZSBjYW4gaXRzZWxmIGJlIGEgcGxhY2Vob2xkZXIhIQpudW1fdW5pcXVlIDwtIC4gJT4lIHVuaXF1ZSAlPiUgbGVuZ3RoICAgICAgIAoKbnVtX3VuaXF1ZShpcmlzJFNwZWNpZXMpCgppcmlzJFNwZWNpZXMgJT4lIG51bV91bmlxdWUKCi0tLS0tCgpudW1fdW5pcXVlIGVzIGVxdWl2YWxlbnRlIGEgOiBmIDwtIGZ1bmN0aW9uKC4pIGxlbmd0aCh1bmlxdWUoLikpIAoKYGBgCgo8YnI+CgpVbiBidWVuIHJlY3Vyc28gcGFyYSBhcHJlbmRlciBlbCB1c28gZGUgYCU+JWAgc29uIFtlc3RhcyB0cmFuc3BhcmVuY2lhc10oaHR0cHM6Ly90ZWFjaHRoYXQubmV0bGlmeS5hcHAvcGlwZS8jMSkuIFVuYSBleHBvc2ljacOzbiBtw6FzIGRldGFsbGFkYSBkZSBsYSBzaW50YXhpcyB5IHBvc2liaWxpZGFkZXMgZGVsIG9wZXJhZG9yIGAlPiVgLCBhc8OtIGNvbW8gbGEgZGUgb3Ryb3Mgb3BlcmFkb3JlcyBjb21vIGAlVD4lYCB5IGAlPD4lYCBwdWVkZXMgZW5jb250cmFybGEgW2FxdcOtXShodHRwczovL3d3dy5yZG9jdW1lbnRhdGlvbi5vcmcvcGFja2FnZXMvbWFncml0dHIvdmVyc2lvbnMvMS41KS4KCjxicj4KClRoZSAqKiJ0ZWUgcGlwZSIqKiAoYCVUPiVgKSBwZXJtaXRlIGhhY2VyIGNvc2FzIGNvbW8gZXN0YToKCmBgYHtyLCBldmFsID0gRkFMU0V9CiMtICEhISEhIQpybm9ybSgyMDApICU+JSBtYXRyaXgobmNvbCA9IDIpICVUPiUKcGxvdCAlPiUgIyBwbG90IHVzdWFsbHkgZG9lcyBub3QgcmV0dXJuIGFueXRoaW5nLiAKY29sU3VtcwpgYGAKCgpUaGUgKioidGVlIHBpcGUiKiosIGNvbW8gZWwgcGlwZSBvcmlnaW5hbCwgcGFzYSBlbCBhcmd1bWVudG8gZGUgbGEgaXpxdWllcmRhIGEgbGEgZnVuY2nDs24gZGUgbGEgZGVyZWNoYSwgUEVSTyBkZXZ1ZWx2ZSBlbCBwcm9waW8gdmFsb3Igb3JpZ2luYWwsIG5vIGRldnVlbHZlIGVsIHJlc3VsdGFkbyBkZSBsYSBldmFsdWFjacOzbiBkZSBsYSBmdW5jacOzbi4gQ29tbyBzZSBzZcOxYWxhIFthcXXDrV0oaHR0cHM6Ly9tYWdyaXR0ci50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS90ZWUuaHRtbCksIGVzdGUgY29tcG9ydGFtaWVudG8gZXMgw7p0aWwgY3VhbmRvIHNlIHVzYSBsYSBmdW5jacOzbiBwb3Igc3VzIHNpZGUtZWZmZWN0czsgZXMgZGVjaXIsIHBhcmEgaW1wcmltaXIgbyBncmFmaWNhci4gWW8gbGEgdXRpbGlkYWQgcXVlIGxlIHZlbyBlcyBoYWNlciBjaGVxdWVvcyBkZW50cm8gZGUgdW5hIHNlY3VlbmNpYSBkZSBwaXBlcywgY29tbyBwb3IgZWplbXBsbyBoYWNlbiBlbiBbZXN0ZSB0d2VldF0oaHR0cHM6Ly90d2l0dGVyLmNvbS9sZXBvdmFzL3N0YXR1cy8xMjgzNDExNzc0MTM0NTM4MjUxKQoKPGJyPgpUaGUgKioiZXhwb3NpdGlvbiBwaXBlIioqIChgJSQlYCkgdGFtYmnDqW4gZGVsIHBrZyBgbWFncml0dHJgLiBFbiBbZXN0ZSBwb3N0XShodHRwczovL3RoZXdvb2RwZWNrci53b3JkcHJlc3MuY29tLzIwMjAvMDIvMTAvdXBwaW5nLXlvdXItcGlwZS1nYW1lLykgbm9zIGV4cGxpY2FuIHN1IHV0aWxpZGFkLiBQZXJvIHNvbG8gbGVlZGxvIGN1YW5kbyB5YSBzZcOhaXMgdXN1YXJpb3MgaW50ZXJtZWRpb3MgZGUgUi4gU2lydmUgcGFyYSBoYWNlciBhY2Nlc2libGVzIGxhcyBjb2x1bW5hcyBkZSB1biBkYXRhZnJhbWUgYSBmdW5jaW9uZXMgcXVlIG5vIGFkbWl0ZW4gZGF0YWZyYW1lcyBjb21vIGxhIGZ1bmNpw7NuIGBjb3IoKWAuIERlIGVzdGEgZm9ybWEgcG9kZW1vcyBpbnRlZ3JhciBlbiBudWVzdHJvIHBpcGVsaW5lIGZ1bmNpb25lcyBxdWUgbm8gZXN0w6FuIHByZXBhcmFkYXMgcGFyYSBlbCB0aWR5dmVyc2UuCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQojLSAhISEKbGlicmFyeShtYWdyaXR0cikKaXJpcyAlPiUgbWVhbihTZXBhbC5MZW5ndGgpICAgIy0gbm8gZnVuY2lvbmEKaXJpcyAlJCUgbWVhbihTZXBhbC5MZW5ndGgpICAgIy0gY29uIHRoZSBleHBvc2l0aW9uIHBpcGUgc8OtIGZ1bmNpb25hCgppcmlzICU+JSBjb3IoU2VwYWwuTGVuZ3RoLCBTZXBhbC5XaWR0aCkgICMtIG5vIGZ1bmNpb25hCmlyaXMgJSQlIGNvcihTZXBhbC5MZW5ndGgsIFNlcGFsLldpZHRoKQpgYGAKClNpIG5vIHVzw6FzZW1vcyBlc3RlIG51ZXZhIHBpcGUsIHRlbmRyw61hbW9zIHF1ZSBoYWNlciBsbyBzaWd1aWVudGU6CgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpjb3IoaXJpcyRTZXBhbC5MZW5ndGgsIGlyaXMkU2VwYWwuV2lkdGgpCmBgYAoKPGJyPgoKTXVjaGFzIGZ1bmNpb25lcyBkZSBSLWJhc2Ugbm8gZXN0w6FuIHByZXBhcmFkYXMgcGFyYSB0cmFiYWphciBjb24gZWwgb3BlcmFkb3IgcGlwZS4gU29uIGZ1bmNpb25lcyBxdWUgc2UgZXNjcmliaWVyb24gYW50ZXMgZGUgcXVlIHNlIGNyZWFyYSBgJT4lYC4gU2UgcHVlZGUgdHJhdGFyIGRlIHJlZXNjcmliaXIgY8OzZGlnbyBlbiAiUi1iYXNlIiB1c2FuZG8gYCAlPiUgYCBwZXJvIG5vIHRpZW5lIG11Y2hvIHNlbnRpZG8geSBubyBlcyBtdXkgYWdyYWRhYmxlOyBzaW4gZW1iYXJnbyBzaSBzZSB0cmFiYWphIGNvbiBlbCB0aWR5dmVyc2UsIHV0aWxpemFyIHRoZSBwaXBlIGhhY2UgbGEgc2ludGF4aXMgbXV5IGZsdWlkYS4gQ29tbyBlamVtcGxvIGRlIGVzdG8gZWwgc2lndWllbnRlIGNodW5rOgoKYGBge3IsIGVjaG8gPSBUUlVFLCBldmFsID0gRkFMU0V9CmxpYnJhcnkodGlkeXZlcnNlKSAgICAKCngxIDwtIGMoLTU6NSwgTkEpICAjLSBlcyB1biB2ZWN0b3IKCiMtIGVzY3JpYmllbmRvIMOgIGxhIFItYmFzZQptZWFuKHgxW3gxPjBdLCBuYS5ybSA9IFRSVUUpICAgIy0gIGNhbGN1bGEgbGEgbWVkaWEgZGUgbG9zIHZhbG9yZXMgcG9zaXRpdm9zIGRlIHgxCnN1bSh4MVshaXMubmEoeDEpXSkgICAgICAgICAgICAjLSAgY2FsY3VsYSBsYSBzdW1hIGRlIGxvcyB2YWxvcmVzIGRlIHgxIHF1ZSBubyBzb24gTkEKCiMtIGFob3JhIGhhcmVtb3MgbG8gbWlzbW8sIHNlZ3VpbW9zIHVzYW5kbyBSLWJhc2UsIHBlcm8gY29uIGVsIG9wZXJhZG9yIHBpcGUgKCEhISEpCngxICU+JSAuWy4+MF0gJT4lIG1lYW4oLiwgbmEucm0gPSBUUlVFKQp4MSAlPiUgLlshaXMubmEoLildICAlPiUgc3VtCgojLSBwb2Ryw61hbW9zIHRyYWJhamFyIGNvbiBkYXRhLmZyYW1lcyB1c2FuZG8gdGhlIGV4cG9zaXRpb24gcGlwZQpkZiA8LSBhcy5kYXRhLmZyYW1lKHgxKSAgICMtIHRpZHl2ZXJzZSB1c2EgZGF0YS5mcmFtZXMKZGYgJSQlIHgxICU+JSAuWy4+MF0gJT4lIG1lYW4oLiwgbmEucm0gPSBUUlVFKQpkZiAlJCUgeDEgJT4lIC5bIWlzLm5hKC4pXSAgJT4lIHN1bQoKIy0gY29uIHR5ZGl2ZXJzZQpkZiA8LSBhcy5kYXRhLmZyYW1lKHgxKSAgICMtIHRpZHl2ZXJzZSB1c2EgZGF0YS5mcmFtZXMKZGYgJT4lIGZpbHRlcih4MSA+IDApICU+JSBzdW1tYXJpc2UobWVhbl94MSA9IG1lYW4oeDEsIG5hLnJtID0gVFJVRSkpCmRmICU+JSBmaWx0ZXIoIWlzLm5hKHgxKSkgJT4lIHN1bW1hcmlzZShzdW1hX3gxID0gc3VtKHgxKSkKYGBgCgoKPGJyPgoKQmllbiwgeWEgc2FiZW1vcyBjb21vIGZ1bmNpb25hICJ0aGUgcGlwZSIuIFZvbHZhbW9zIGFsIHRpZHl2ZXJzZSB5IGEgYXByZW5kZXIgYSBtYW5pcHVsYXIgZGF0b3MgZW4gUi4KCgo8YnI+CgojIyBQcmluY2lwYWxlcyBwa2dzIGRlbCB0aWR5dmVyc2UKCgpDb21vIHB1ZWRlIHZlcnNlIGVuIHN1IFtww6FnaW5hIHdlYl0oaHR0cHM6Ly93d3cudGlkeXZlcnNlLm9yZy8pLCBsb3MgcHJpbmNpcGFsZXMgcGFja2FnZXMgZGVsIHRpZHl2ZXJzZSBzb246IAoKYGBge3IgLCBlY2hvPUZBTFNFLCBldmFsID0gVFJVRSwgZmlnLmFzcCA9IDQvMiwgb3V0LndpZHRoID0gIjgwJSIsIGZpZy5hbGlnbiA9ICJjZW50ZXIifQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhoZXJlOjpoZXJlKCJpbWFnZW5lcyIsICJ0dF8wNV9pbWdfMDNiX3BrZ3MtdGlkeXZlcnNlLnBuZyIpKQpgYGAKCgoKICAtIGByZWFkcmA6IHBhcmEgaW1wb3J0YXIgZGF0b3MgIAogIC0gYHRpZHlyYDogcGFyYSBjb252ZXJ0aXIgbG9zIGRhdG9zIGEgdGlkeSBkYXRhICAKICAtIGBkcGx5cmA6IHBhcmEgbWFuaXB1bGFyIGRhdG9zICAKICAtIGBnZ3Bsb3QyYDogcGFyYSBoYWNlciBncsOhZmljb3MgIAogIAogIC0gYHRpYmJsZWA6IGRhdGEgZnJhbWVzIGFjdHVhbGl6YWRvcwogICAgCiAgLSBgZm9yY2FzdGA6IHBhcmEgbWFuaXB1bGFyIGZhY3RvcmVzICAKICAtIGBzdHJpbmdyYDogcGFyYSBtYW5pcHVsYXIgc3RyaW5ncyAgCiAgCiAgLSBgcHVycnJgOiBwYXJhIGZ1bmN0aW9uYWwgcHJvZ3JhbW1pbmcKCiAgICAgIAogIC0geSBhbGd1bm9zIG3DoXMgIAoKTm9zIGNlbnRyYXJlbW9zIGVuIGxvcyAqKmN1YXRybyBwcmltZXJvcyBwYXF1ZXRlcyoqLCBwcmluY2lwYWxtZW50ZSBlbiBgZHBseXJgIHkgYGdncGxvdDJgLgoKPGJyPgoKCkxvcyBwcmluY2lwYWxlcyBwYXF1ZXRlcyBkZWwgdGlkeXZlcnNlIHNlIGhhbiAiYWdydXBhZG8iIGVuIHVuIG1ldGFwYXF1ZXRlIGxsYW1hZG8gYHRpZHl2ZXJzZWAsIGFzw60gcXVlIGN1YW5kbyBlamVjdXRhcyBgbGlicmFyeSh0aWR5dmVyc2UpYCBlbiByZWFsaWRhZCBlc3TDoXMgY2FyZ2FuZG8gdmFyaW9zIHBhcXVldGVzIGRlbCB0aWR5dmVyc2UKCgo8YnI+CgotLS0tLS0tLS0tLS0tLS0tLS0tCgoKIyAyLiBUaWR5IGRhdGEgKHRpZHlyKQoKPGJyPgoKPiBJZiBJIGhhZCBvbmUgdGhpbmcgdG8gdGVsbCBiaW9sb2dpc3RzIGxlYXJuaW5nIGJpb2luZm9ybWF0aWNzLCBpdCB3b3VsZCBiZSB3cml0ZSBjb2RlIGZvciBodW1hbnMsICoqd3JpdGUgZGF0YSBmb3IgY29tcHV0ZXJzKiouICAtLS0t4oCUIFZpbmNlIEJ1ZmZhbG8gKFxAdnNidWZmYWxvKQoKClkgc2kgdmFtb3MgYSBtYW5lamFyIGRhdG9zIGNvbiBSIHkgYSBsYSBtYW5lcmEgZGVsIHRpZHl2ZXJzZSwgY29tbyBKZW5ueSBCcnlhbiBzZcOxYWxhIGVuIHN1IGV4Y2VsZW50ZSBbdHV0b3JpYWwgc29icmUgdGlkeSBkYXRhXShodHRwczovL2dpdGh1Yi5jb20vamVubnliYy9sb3RyLXRpZHkvYmxvYi9tYXN0ZXIvMDEtaW50cm8ubWQpOiAKCj4gQW4gaW1wb3J0YW50IGFzcGVjdCBvZiAid3JpdGluZyBkYXRhIGZvciBjb21wdXRlcnMiIGlzIHRvICoqbWFrZSB5b3VyIGRhdGEgVElEWSoqLiAtLS0tIEplbm55IEJyeWFuCgoKPGJyPgoKQW50ZXMgZGUgY29tZW56YXIgYSBtYW5pcHVsYXIgbG9zIGRhdG9zIMOgIGxhIHRpZHl2ZXJzZSwgZXMgY29udmllbmUgc2FiZXIgcXVlIHNlIGVudGllbmRlIHBvciAqKnRpZHkgZGF0YSoqLiBMYSByYXrDs24gZXMgcXVlIGxvcyBwYXF1ZXRlcyBkZWwgdGlkeXZlcnNlIHRyYWJhamFuIG1lam9yIHNpIGxvcyBkYXRvcyBlc3TDoW4gZW4gZm9ybWF0byB0aWR5LiBFcyBmw6FjaWwhIQoKCjxicj4KCiMjIyMgwr9RdcOpIHNvbiBsb3MgKip0aWR5IGRhdGEqKj8KCgpBaG9yYSBsbyB2ZXJlbW9zLCBwZXJvIGVuZmF0aXphciBxdWUgc2kgbG9zIGRhdG9zIHNvbiB0aWR5IChzaSBzaWd1ZW4gZXNlIGZvcm1hdG8pICoqc2Vyw6EgbcOhcyBmw6FjaWwgdHJhYmFqYXIgY29uIGVsbG9zIGNvbiBlbCB0aWR5dmVyc2UqKiwgeWEgc2VhIHBhcmEgbWFuaXB1bGFybG9zIG8gcGFyYSBoYWNlciBncsOhZmljb3MuCgpEZSBmb3JtYSBzZW5jaWxsYSwgdGlkeSBkYXRhIHNvbiBzaW1wbGVtZW50ZSBkYXRvcyBvcmdhbml6YWRvcyBkZSB1bmEgZGV0ZXJtaW5hZGEgbWFuZXJhLiBBZGVtw6FzIGVzIGp1c3RvIGRlIGxhIG1hbmVyYSBhIGxhIGVzdGFtb3MgZmFtaWxpYXJpemFkb3MuIERlIGZvcm1hIG3DoXMgcHJlY2lzYSBzZSBwdWVkZSBsZWVyIFthcXXDrV0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvVGlkeV9kYXRhKSwgbyBkZSBmb3JtYSBtw6FzIGVsYWJvcmFkYSBbW2FxdcOtXShmdHA6Ly9jcmFuLnItcHJvamVjdC5vcmcvcHViL1Ivd2ViL3BhY2thZ2VzL3RpZHlyL3ZpZ25ldHRlcy90aWR5LWRhdGEuaHRtbCldICA6Cgo+IFRpZHkgZGF0YXNldHMgcHJvdmlkZSBhIHN0YW5kYXJkaXplZCB3YXkgdG8gbGluayB0aGUgc3RydWN0dXJlIG9mIGEgZGF0YXNldCAoaXRzIHBoeXNpY2FsIGxheW91dCkgd2l0aCBpdHMgc2VtYW50aWNzIChpdHMgbWVhbmluZykuICAgLS0tLS0gSGFkbGV5IFdpY2toYW0KCjxicj4KCkxhIG1heW9yw61hIGRlIGRhdG9zIGVuIENpZW5jaWFzIFNvY2lhbGVzIHNlIGFqdXN0YW4gYSBsYSBjYXRlZ29yw61hIGRlIGRhdG9zIHRhYnVsYXJlczsgZXMgIGRlY2lyLCAqKmVzdMOhbiBvcmdhbml6YWRvcyBlbiBmaWxhcyB5IGNvbHVtbmFzKiouIEVuIFIgZXN0ZSB0aXBvIGRlIGRhdG9zIHNlIGFsbWFjZW5hbiBlbiBkYXRhZnJhbWVzIChvIHRpYmJsZXMpLiBFbiBlc2VuY2lhLCB1biBkYXRhZnJhbWUgc2Vyw6EgdGlkeSBzaSBjYWRhIGNvbHVtbmEgZXMgdW5hIHZhcmlhYmxlIHkgY2FkYSBmaWxhIGVzIHVuYSB1bmlkYWQgZGUgYW7DoWxpc2lzIChwZXJzb25hLCBwYcOtcywgcmVnacOzbiBldGMuLi4pOyBlcyBkZWNpciwgY2FkYSBjZWxkYSBjb250aWVuZSBlbCB2YWxvciBkZSB1bmEgdmFyaWFibGUgcGFyYSB1bmEgdW5pZGFkIGRlIGFuw6FsaXNpcy4KCj4gQSBkYXRhc2V0IGlzIGEgY29sbGVjdGlvbiBvZiB2YWx1ZXMuIEV2ZXJ5IHZhbHVlIGJlbG9uZ3MgdG8gYSB2YXJpYWJsZSBhbmQgYW4gb2JzZXJ2YXRpb24uIEEgdmFyaWFibGUgY29udGFpbnMgYWxsIHZhbHVlcyB0aGF0IG1lYXN1cmUgdGhlIHNhbWUgdW5kZXJseWluZyBhdHRyaWJ1dGUgKGxpa2UgaGVpZ2h0LCB0ZW1wZXJhdHVyZSwgZHVyYXRpb24pIGFjcm9zcyB1bml0cy4gQW4gb2JzZXJ2YXRpb24gY29udGFpbnMgYWxsIHZhbHVlcyBtZWFzdXJlZCBvbiB0aGUgc2FtZSB1bml0IChsaWtlIGEgcGVyc29uLCBvciBhIGRheSwgb3IgYSByYWNlKSBhY3Jvc3MgYXR0cmlidXRlcwoKCmBgYHtyICwgZWNobz1GQUxTRSwgZmlnLmNhcD0iVGlkeSBkYXRhIGZyb20gaHR0cDovL3I0ZHMuaGFkLmNvLm56L3RpZHktZGF0YS5odG1sIiwgZXZhbCA9IFRSVUUsIGZpZy5hc3AgPSA0LzIsIG91dC53aWR0aCA9ICI4MCUiLCBmaWcuYWxpZ24gPSAiY2VudGVyIn0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoaGVyZTo6aGVyZSgiaW1hZ2VuZXMiLCAidHRfMDVfaW1nXzA0X3RpZHktZGF0YS5wbmciKSkKYGBgCgo8YnI+CgpObyBwYXJlY2UgbXV5IGFsZWphZG8gZGUgbG8gcXVlIGVzdGFtb3MgYWNvc3R1bWJyYWRvcy4gUGVybyAuLi4uIGRlc2Fycm9sbGVtb3MgbGEgaWRlYSB1biBwb2NvIG3DoXMuCgo8YnI+CgojIyMjIFVuIGVqZW1wbG8gZGUgZGF0b3MgKG5vIHRpZHkpCgpTdXBvbmdhbW9zIHF1ZSBsYSB2YXJpYWJsZSAobyBhdHJpYnV0bykgYSBtZWRpciBlcyBlbCBzYWxhcmlvIHkgbGEgdW5pZGFkIGRlIGFuw6FsaXNpcyBsYXMgcGVyc29uYXMuIEhlbW9zIHJlY29naWRvIGRhdG9zIHBhcmEgMyBwZXJzb25hcy4gVmXDoW1vc2xvczoKCgpgYGB7ciwgZWNobyA9IFRSVUUsIGV2YWwgPSBUUlVFLCByZXN1bHRzID0gJ2hpZGUnfQpkYXRhXzEgPC0gZGF0YS5mcmFtZSgKICAgICAgICAgICAgeWVhciAgPSBjKCIyMDE0IiwgIjIwMTUiLCAiMjAxNiIpLCAgCiAgICAgICAgICAgIFBlZHJvID0gYygxMDAsIDUwMCwgMjAwKSwgCiAgICAgICAgICAgIENhcmxhID0gYyg0MDAsIDYwMCwgMjUwKSwgCiAgICAgICAgICAgIE1hcsOtYSA9IGMoMjAwLCA3MDAsIDkwMCkgICkKZGF0YV8xCmBgYAoKCmBgYHtyLCBlY2hvID0gRkFMU0UsIGV2YWwgPSBUUlVFLCByZXN1bHRzID0gJ21hcmt1cCd9CmtuaXRyOjprYWJsZShkYXRhXzEsIGFsaWduID0gJ2MnLCBjYXB0aW9uID0gIlRhYmxhIDE6IFNhbGFyaW8gZGUgMyBwZXJzb25hczoiKQpgYGAKCgoKRW50ZW5kZW1vcyBwZXJmZWN0YW1lbnRlIGVzdG9zIGRhdG9zLCB2aXN1YWxtZW50ZSBzb24gY8OzbW9kb3MsIHBlcm8gwr9zb24gdGlkeSBkYXRhPyBOTyogcG9ycXVlIGxvcyBpbmRpdmlkdW9zIChvIHVuaWRhZGVzIGRlIGFuw6FsaXNpcykgZXN0w6FuIGVuIGNvbHVtbmFzLgoKPGJyPgoKIyMjIyBVbiBlamVtcGxvIGRlIGRhdG9zICh0aWR5KiBwZXJvIHdpZGUpCgpFeGFjdGFtZW50ZSBsb3MgbWlzbW9zIGRhdG9zIHBvZHLDrWFuIGVzdHJ1Y3R1cmFyc2UgYXPDrToKCmBgYHtyLCBlY2hvID0gVFJVRSwgZXZhbCA9IFRSVUUsIHJlc3VsdHMgPSAnaGlkZSd9CmRhdGFfMiA8LSBkYXRhLmZyYW1lKG5hbWVzID0gYygiUGVkcm8iLCAiQ2FybGEiLCAiTWFyw61hIiksIAogICAgICAgICAgICAgICAgICAgICAgV18yMDE0ID0gYygxMDAsIDQwMCwgMjAwKSwgCiAgICAgICAgICAgICAgICAgICAgICBXXzIwMTUgPSBjKDUwMCwgNjAwLCA3MDApLAogICAgICAgICAgICAgICAgICAgICAgV18yMDE2ID0gYygyMDAsIDI1MCwgOTAwKSAgICkKCmRhdGFfMgpgYGAKCgoKYGBge3IsIGVjaG8gPSBGQUxTRSwgZXZhbCA9IFRSVUUsIHJlc3VsdHMgPSAnbWFya3VwJ30Ka25pdHI6OmthYmxlKGRhdGFfMiwgYWxpZ24gPSAnYycsIGNhcHRpb24gPSAiVGFibGEgMjogU2FsYXJpbyBkZSAzIHBlcnNvbmFzICh3aWRlIGZvcm1hdCkiKQpgYGAKCgoKVGFtYmnDqW4gZXMgdW4gZm9ybWF0byBmw6FjaWwgZGUgZW50ZW5kZXIgcG9yIG5vc290cm9zLCBwZXJvIMK/c29uIHRpZHk/IFNJKiwgcGVybyAuLi4KCiAgLSBFcyBlbCBmb3JtYXRvIGFsIHF1ZSBlc3RhbW9zIG3DoXMgYWNvc3R1bWJyYWRvcyAoaW5kaXZpZHVvcyBvIHJlZ2lzdHJvcyBlbiBmaWxhcyB5ICJ2YXJpYWJsZXMiIGVuIGNvbHVtbmFzKS4gwr9SZWFsbWVudGUgZWwgVyBkZSAyMDE0IGVzIHVuYSB2YXJpYWJsZT8KCiAgLSBFbiBqZXJnYSBkZWwgdGlkeXZlcnNlIGVzdGUgZm9ybWF0byBkZSBkYXRvcyBlcyAqKiJ3aWRlIioqIChvIGFuY2hvKQoKPGJyPgoKClBvZGVtb3MgdHJhYmFqYXIgdHJhbnF1aWxhbWVudGUgY29uIGVsIGFudGVyaW9yIGZvcm1hdG8sIFBFUk8sIHNpIHF1ZXJlbW9zIHNhY2FyIHRvZG8gZWwgcHJvdmVjaG8gYWwgdGlkeXZlcnNlIGVzIG1lam9yIHRlbmVyIGxvcyBkYXRvcyBlbiAqKmxvbmcgZm9ybWF0KiouCgo8YnI+CgojIyMjIFVuIGVqZW1wbG8gZGUgZGF0b3MgKHRpZHktdGlkeSB5IGxvbmcpCgpgYGB7ciwgZWNobyA9IFRSVUUsIGV2YWwgPSBUUlVFLCByZXN1bHRzID0gJ2hpZGUnfQpkYXRhXzMgPC0gZGF0YS5mcmFtZSgKICAgICAgICAgICAgbmFtZXMgPXJlcChjKCJQZWRybyIsICJDYXJsYSIsICJNYXLDrWEiKSwgdGltZXMgPSAzKSwgIAogICAgICAgICAgICB5ZWFyID0gcmVwKGMoIjIwMTQiLCAiMjAxNSIsICIyMDE2IiksIGVhY2ggPSAzKSwKICAgICAgICAgICAgc2FsYXJpbyA9IGMoMTAwLCA0MDAsIDIwMCwgNTAwLCA2MDAsIDcwMCwgMjAwLCAyNTAsOTAwKSApCmRhdGFfMwpgYGAKCgpgYGB7ciwgZWNobyA9IEZBTFNFLCBldmFsID0gVFJVRSwgcmVzdWx0cyA9ICdtYXJrdXAnfQprbml0cjo6a2FibGUoZGF0YV8zLCBhbGlnbiA9ICdjJywgY2FwdGlvbiA9ICJUYWJsYSAzOiBTYWxhcmlvIGRlIDMgcGVyc29uYXMgKGxvbmcgZm9ybWF0KSIpCmBgYAoKRXN0ZSBmb3JtYXRvLCBmb3JtYXRvIGxvbmcsIGVzIG3DoXMgZGlmw61jaWwgZGUgbGVlciBwYXJhIG5vc290cm9zLCBwZXJvIGVzIG3DoXMgZWZpY2llbnRlIHBhcmEgbG9zIG9yZGVuYWRvcmVzLiBZIGxvcyBkYXRvcyBsb3MgcHJvY2VzYW4gbG9zIG9yZGVuYWRvcmVzISEKCkdlbmVyYWxtZW50ZSwgY3VhbmRvIGVzdGVtb3MgdHJhYmFqYW5kbyBjb24gbG9zIGRhdG9zIGNvbiBlbCB0aWR5dmVyc2UgY29udmVuZHLDoSBxdWUgbG9zIGRhdG9zIGVzdMOpbiBlbiBmb3JtYXRvIGxvbmcsIHBlcm8gaGFicsOhIHZlY2VzLCBwb3IgZWplbXBsbyBwYXJhIG1vc3RyYXIgdGFibGFzLCB0ZW5kcmVtb3MgcXVlIHBhc2FybG9zIGEgZm9ybWF0byBhbmNoby4gwr9Dw7NtbyBwb2RlbW9zIHBhc2FyIHVuIGRmIGRlIGZvcm1hdG8gbG9uZyBhIHdpZGUgeSBhbCBjb250cmFyaW8/IExvIG3DoXMgaGFiaXR1YWwgZXMgdXNhciBkb3MgZnVuY2lvbmVzIGRlbCBwYXF1ZXRlIGB0aWR5cmAuIFZlw6Ftb3Nsby4KCgoKPGJyPgoKCgojIyMgcGl2b3RfbG9uZ2VyKCkgeSBwaXZvdF93aWRlcigpCgojIyMjIGZ1bmNpb25lcyBwYXJhIHBhc2FyIGRlIHdpZGUgYSBsb25nICgmIHZpY2V2ZXJzYSkKCjxicj4KCllhIGhlbW9zIGRpY2hvIHF1ZSBsb3MgcGFja2FnZXMgZGVsIHRpZHl2ZXJzZSB0cmFiYWphbiBtZWpvciBjb24gdGlkeSBkYXRhIGVuICoqZm9ybWF0byAibG9uZyIqKi4gwr9RdcOpIGhhY2Vtb3Mgc2kgdGVuZW1vcyB1biBkYXRhZnJhbWUgZW4gZm9ybWF0byB3aWRlPyBQdWVzIHBhc2FybG8gYSBsb25nLiBBZm9ydHVuYWRhbWVudGUgdGVuZW1vcyB1biBwa2cgcXVlIGhhY2UgbXV5IHNlbmNpbGxvIHBhc2FyIGxvcyBkYXRvcyBkZSB3aWRlIGEgbG9uZyAoeSB2aWNldmVyc2EpOiBgdGlkeXJgLiBDb25jcmV0YW1lbnRlIHVzYXJlbW9zIGxhcyBmdW5jaW9uZXMgYHBpdm90X2xvbmdlcigpYCB5IGBwaXZvdF93aWRlcigpYF5bSGFzdGEgbGEgYXBhcmljacOzbiBkZSB0aWR5ciAxLjAuMCwgbGFzIGZ1bmNpb25lcyBxdWUgc2UgdXNhYmFuIGVyYW4gYGdhdGhlcigpYCB5IGBzcHJlYWQoKWAuIEVuIFtlc3RhIGNvbmZlcmVuY2lhXShodHRwczovL3d3dy55b3V0dWJlLmNvbS93YXRjaD92PXZZd1hNbkMwM0k0Jmxpc3Q9TExMbTZrN1BnRGpWY0ZYSkRiV2dfT2FRKSwgIEhhZGxleSBXaWNraGFtIG5vcyBjb250w7MgcXVlIHVubyBkZSBzdXMgZ3JhbmRlcyBlcnJvcmVzIGR1cmFudGUgZWwgZGVzYXJyb2xsbyBkZWwgdGlkeXZlcnNlIGZ1ZSBsYSBlbGVjY2nDs24gZGUgbG9zIG5vbWJyZXMgZGUgbGFzIGZ1bmNpb25lcyBgZ2F0aGVyKClgIHkgYHNwcmVhZCgpYC4gRmluYWxtZW50ZSBwb2RlbW9zIGRlY2lyOiBieWUgYnllIGBnYXRoZXIoKWAgeSBgc3ByZWFkKClgLCB3ZWxsY29tZSBgdGlkeXIgMS4wLjBgIGFuZCBgcGl2b3RfbG9uZ2VyKClgIGFuZCBgcGl2b3Rfd2lkZXIoKWAuXQoKCgpbQXF1w61dKGh0dHBzOi8vd3d3LnRpZHl2ZXJzZS5vcmcvYmxvZy8yMDIwLzA1L3RpZHlyLTEuMS4wLykgdGllbmVzIGVsIHBvc3Qgb2ZpY2lhbCBkb25kZSBzZSBhbnVuY2lhYmEgbGEgbGxlZ2FkYSBhIENSQU4gZGUgYHRpZHlyIDEuMC4wYCB5IFthcXXDrV0oaHR0cHM6Ly90aWR5ci50aWR5dmVyc2Uub3JnL2FydGljbGVzL3Bpdm90Lmh0bWwpIHkgW2FxdcOtXShodHRwczovL2Jsb2cubWV0aG9kc2NvbnN1bHRhbnRzLmNvbS9wb3N0cy9kYXRhLXBpdm90aW5nLXdpdGgtdGlkeXIvKSB1biBwb3N0IGRldGFsbGFkbyBzb2JyZSBlbGxhcy4gTGEgY29uY2x1c2nDs24gZGVsIGF1dG9yIGRlbCDDumx0aW1vIGRlIGVsbG9zIGVzOgoKCj4gVGhlIG5ldyB0aWR5ciBmdW5jdGlvbnMgaGF2ZSBpbnR1aXRpdmUgc3ludGF4LCBhcmUgZWFzeSB0byB1c2UsIGFuZCBhcmUgbW9yZSBmbGV4aWJpbGUgdGhhbiB0aGUgcHJpb3IgZnVuY3Rpb25zLiBTZXZlcmFsIG9mIHRoZSBuZXcgYXJndW1lbnRzIGFuZCBmZWF0dXJlcyBhcmUgZXh0cmVtZWx5IHVzZWZ1bCwgYW5kIHdpbGwgc2F2ZSBsb3RzIG9mIHRpbWUgb24gY29tbW9uIHRhc2tzLgoKCgpFbiBlc3RlIFtvdHJvIHBvc3RdKGh0dHBzOi8vZnJvbXRoZWJvdHRvbW9mdGhlaGVhcC5uZXQvMjAxOS8xMC8yNS9waXZvdGluZy10aWRpbHkvKSB0aWVuZXMgdGFtYmnDqW4gdW5hIGV4cGxpY2FjacOzbiBkZXRhbGxhZGEsIGRlIGBwaXZvdF8qKClgIHBlcm8gYWRlbcOhcyBpbmNsdXllIHVuYSBzZXJpZSBkZSBnaWZzIHF1ZSBlamVtcGxpZmljYW4gZWwgcGFzbyBkZSB3aWRlIGEgbG9uZy4KCjxicj4KCiMjIyMgRGUgd2lkZSBhIGxvbmcgZm9ybWF0IGNvbiBgcGl2b3RfbG9uZ2VyKClgCgoKTGEgZnVuY2nDs24gYHBpdm90X2xvbmdlcigpYCBjb252aWVydGUgZGF0YWZyYW1lcyBkZSB3aWRlIGEgbG9uZyBmb3JtYXQKCgpgYGB7ciAsIGVjaG89RkFMU0UsIGV2YWwgPSBUUlVFLCBmaWcuYXNwID0gNC8yLCBvdXQud2lkdGggPSAiOTUlIiwgZmlnLmFsaWduID0gImNlbnRlciJ9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGhlcmU6OmhlcmUoImltYWdlbmVzIiwgInR0XzA1X2ltZ18wNWFfd2lkZS1sb25nLnBuZyIpKQpgYGAKCkhhZ8OhbW9zbG86CgpgYGB7ciwgZWNobyA9IFRSVUUsIGV2YWwgPSBUUlVFfQpsaWJyYXJ5KHRpZHlyKQpkYXRhX3dpZGUgPC0gZGF0YV8yICAgIy0gZGF0YV8yIGVzdMOhIGVuIGZvcm1hdG8gYW5jaG8gKHdpZGUpCgojLSBsYSBmdW5jacOzbiBwaXZvdF9sb25nZXIoKSB0cmFuc2Zvcm1hIGxvcyBkYXRvcyBkZSBmb3JtYXRvIGFuY2hvKHdpZGUpIGEgZm9ybWF0byBsYXJnbyhsb25nKQpkYXRhX2xvbmcgPC0gZGF0YV93aWRlICU+JSBwaXZvdF9sb25nZXIoY29scyA9IDI6NCwgbmFtZXNfdG8gPSAicGVyaW9kbyIpCmBgYAoKU2kgcXVpc2nDqXJhbW9zIGFycmVnbGFyIGxvcyB2YWxvcmVzIGRlIGxvcyBwZXJpb2RvczoKCgpgYGB7ciwgZWNobyA9IFRSVUUsIGV2YWwgPSBUUlVFfQojKCEhKSBzdHJpbmdyOjpzdHJfcmVwbGFjZSBlbmN1ZW50cmEgZWwgdGV4dG8gIldfIiBlbiBsYSBjb2x1bW5hICJwZXJpb2RvIiB5IGxvIHN1c3RpdHV5ZSBwb3IgIiIKZGF0YV9sb25nIDwtIGRhdGFfbG9uZyAlPiUgbXV0YXRlKHBlcmlvZG8gPSBzdHJfcmVwbGFjZShwZXJpb2RvLCAiV18iLCAiIiApKQpgYGAKCjxicj4KCiMjIyMgRGUgbG9uZyBhIHdpZGUgZm9ybWF0IGNvbiBgcGl2b3Rfd2lkZXIoKWAKClBhc2FyIHBhc2FyIGRlIGxvbmcgYSB3aWRlLCB0aWR5ciB0aWVuZSBsYSBmdW5jacOzbiBgcGl2b3RfbG9uZ2VyKClgCgoKYGBge3IgLCBlY2hvPUZBTFNFLCBldmFsID0gVFJVRSwgZmlnLmFzcCA9IDQvMiwgb3V0LndpZHRoID0gIjk1JSIsIGZpZy5hbGlnbiA9ICJjZW50ZXIifQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhoZXJlOjpoZXJlKCJpbWFnZW5lcyIsICJ0dF8wNV9pbWdfMDViX3dpZGUtbG9uZy5wbmciKSkKYGBgCgpIYWfDoW1vc2xvOgoKCmBgYHtyLCBlY2hvID0gVFJVRSwgZXZhbCA9IFRSVUV9CiMtIGBwaXZvdF9sb25nZXIoKWAgY29udmllcnRlIHVuIGRmIGRlIGxvbmcgYSB3aWRlCmRhdGFfd2lkZTIgPC0gZGF0YV9sb25nICU+JSBwaXZvdF93aWRlcihuYW1lc19mcm9tID0gcGVyaW9kbywgdmFsdWVzX2Zyb20gPSB2YWx1ZSkKYGBgCgoKCjxicj4KCiMjIyBzZXBhcmF0ZSgpIHkgdW5pdGUoKQoKIyMjIyBmdW5jaW9uZXMgcGFyYSBzZXBhcmFyIHkgdW5pciBjb2x1bW5hcwoKPGJyPgoKRWwgcGtnIHRpZHlyIGNvbnRpZW5lIG90cmFzIDIgZnVuY2lvbmVzOiBgc2VwYXJhdGUoKWAgeSBgdW5pdGUoKWAgcXVlIGZhY2lsaXRhbiBlbCBzZXBhcmFyIHkgdW5pciBjb2x1bW5hcy4gVmVhbW9zIHVuIGVqZW1wbG86CgoKYGBge3IsIGVjaG8gPSBUUlVFLCBldmFsID0gVFJVRSwgcmVzdWx0cyA9ICdtYXJrdXAnfQpkZiA8LSBkYXRhLmZyYW1lKCBuYW1lcyA9IGMoIlBlZHJvX05hdmFqYSIsICJCb2JfRHlsYW4iLCAiQ2lkX0NhbXBlYWRvciIpLCAKICAgICAgICAgICAgICAgICAgeWVhciAgPSBjKDE5NzgsIDE5NDEsIDEwNDgpICkKZGYKYGBgCgoKU2VwYXJhbW9zIGxhIHByaW1lcmEgY29sdW1uYToKCmBgYHtyLCBlY2hvID0gVFJVRSwgZXZhbCA9IFRSVUUsIHJlc3VsdHMgPSAnbWFya3VwJ30KZGZfYSA8LSBkZiAlPiUgc2VwYXJhdGUobmFtZXMsIGMoIk5vbWJyZSIsICJBcGVsbGlkbyIpLCBzZXAgPSAiXyIpCmRmX2EKYGBgCgoKU2kgcXVlcmVtb3Mgdm9sdmVyIGEgdW5pcmxvcywgdGVuZHLDrWFtb3MgcXVlOgoKCmBgYHtyLCBlY2hvID0gVFJVRSwgZXZhbCA9IFRSVUUsIHJlc3VsdHMgPSAnbWFya3VwJ30KZGZfYiA8LSBkZl9hICU+JSB1bml0ZShOb21icmVfeV9BcGVsbGlkbywgTm9tYnJlOkFwZWxsaWRvLCBzZXAgPSAiJiIpCmRmX2IKYGBgCgo8YnI+CgojIyMjIG1hcyBmdW5jaW9uZXMgZGUgdGlkeXIKCgpBZGVtw6FzLCByZWN1ZXJkYSBxdWUgZWwgcGFxdWV0ZSBgdGlkeXJgIHRpZW5lIG11Y2hhcyBbbcOhcyBmdW5jaW9uZXNdKGh0dHBzOi8vdGlkeXIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2UvaW5kZXguaHRtbCkgcXVlIG5vcyBmYWNpbGl0YW4gY29uc2VndWlyIHF1ZSBudWVzdHJvcyBkYXRvcyBzZWFuICoqdGlkeSoqLgoKCjxicj4KCjxicj4KCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIDMuICBEUExZUgoKRW4gUiBoYXkgdmFyaW9zIGVuZm9xdWVzIHBhcmEgbWFuaXB1bGFyIGRhdG9zIGVuIFIsIHBlcm8gZWwgbcOhcyBoYWJpdHVhbCwgZGUgaGVjaG8gc2UgaGEgY29udmVydGlkbyBlbiBlbCBlc3TDoW5kYXIsIGVzIHV0aWxpemFyIGVsIHRpZHl2ZXJzZSwgY29uY3JldGFtZW50ZSBlbCBwYXF1ZXRlIFtgZHBseXJgXShodHRwczovL2RwbHlyLnRpZHl2ZXJzZS5vcmcvKS4KCmBkcGx5cmAgZXMgdW4gcGFxdWV0ZSBxdWUgcGVybWl0ZSBtYW5pcHVsYXIgZGF0b3MgZGUgZm9ybWEgaW50dWl0aXZhLiBUaWVuZSA2LTcgZnVuY2lvbmVzIG8gdmVyYm9zIHByaW5jaXBhbGVzLiBDYWRhIHVubyBkZSBlbGxvcyBoYWNlICJ1bmEgc29sYSBjb3NhIiwgYXPDrSBxdWUgcGFyYSByZWFsaXphciB0cmFuc2Zvcm1hY2lvbmVzIGNvbXBsZWphcyBoYXkgcXVlIGlyIGNvbmNhdGVuYW5kbyBpbnN0cnVjY2lvbmVzIHNlbmNpbGxhcy4gRXN0byBzZSBoYWNlIGNvbiBlbCAqKm9wZXJhZG9yIHBpcGUqKiAoYCAlPiUgYCkKCgo8YnI+CgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIyBkcGx5ciBiYXNpY3MKClRyYXMgbXVjaG8gcGVuc2FyIGNvbW8gZXN0cnVjdHVyw6FiYW1vcyBlc3RlIGFwYXJ0YWRvIGRlbCB0dXRvcmlhbCwgYWwgZmluYWwgbWUgZGVjYW50w6kgcG9yIHV0aWxpemFyIGxvcyBtYXRlcmlhbGVzIGRlbCBjdXJzbyBTVEFUIDU0NS4gwr9xdWUgcXVpZW4gaGEgc2UgZW5jYXJnYSBkZWwgY3Vyc28/IFB1ZXMgSmVubnkgQnJ5YW4gKGFsd2F5cyByb2NrcyEhKS4gUHVlZGVzIGVuY29udHJhcmxvcyBbYXF1w61dKGh0dHA6Ly9zdGF0NTQ1LmNvbS9ibG9jazAwOV9kcGx5ci1pbnRyby5odG1sKS4KCmBkcGx5cmAgdGllbmUgbXVjaGFzIGZ1bmNpb25lcywgcGVybyBsYXMgcHJpbmNpcGFsZXMgc29uIDYtNywgbHVlZ28gbGFzIHZlcmVtb3MuIENvbiBlbGxhcyBzZSBwdWVkZW4gcmVzb2x2ZXIgbGEgbWF5b3LDrWEgZGUgcHJvYmxlbWFzIGFzb2NpYWRvcyBhIGxhIG1hbmlwdWxhY2nDs24gZGUgZGF0b3MuCgpDYWRhIGZ1bmNpw7NuIChvIHZlcmJvKSBoYWNlIHVuYSBzb2xhIGNvc2EsIHBlcm8gY29uY2F0ZW7DoW5kb2xhcyBjb24gYCU+JSBgIHBlcm1pdGVuIHJlc29sdmVyIGN1ZXN0aW9uZXMgY29tcGxlamFzLgoKVG9kYXMgbGFzIGZ1bmNpb25lcyB0aWVuZW4gdW5hIGVzdHJ1Y3R1cmEgbyBjb21wb3J0YW1pZW50byBzaW1pbGFyOgoKICAtIGVsIHByaW1lciBhcmd1bWVudG8gc2llbXByZSBlcyB1biBkZi4gRXN0byBlcyBpbXBvcnRhbnRlICAgIAogIC0gbG9zIHNpZ3VpZW50ZXMgYXJndW1lbnRvcyBkZXNjcmliZW4gcXVlIGhhY2VyIGNvbiBsb3MgZGF0b3MgICAKICAtIGVsIHJlc3VsdGFkbyBlcyBzaWVtcHJlIHVuIG51ZXZvIGRmLiBFc3RvIGVzIGltcG9ydGFudGUgICAKICAKUG9yIGVqZW1wbG8sIGBmaWx0ZXIoZGYsIFgxID49IDEwKWAgZGV2dWVsdmUgdW4gZGYgY29uIGxhcyBmaWxhcyBkZWwgZGYgb3JpZ2luYWwgcXVlIGN1bXBsZW4gbGEgY29uZGljacOzbiBkZSBxdWUgbGEgdmFyaWFibGUgWDEgZXMgbWF5b3IgbyBpZ3VhbCBhIDEwCgpQb2RlbW9zIGVzY3JpYmlyIGxhIGFudGVyaW9yIGluc3RydWNjacOzbiBkZSAzIGZvcm1hcy4gTGEgbcOhcyB1dGlsaXphZGEgZXMgbGEgw7psdGltYToKCmBgYHtyLCBlY2hvID0gVFJVRSwgZXZhbCA9IEZBTFNFfQpkZl9uZXcgPC0gZmlsdGVyKGRmLCBYMSA+PSAxMCkKCmRmX25ldyA8LSBkZiAlPiUgZmlsdGVyKC4gLCBYMSA+PSAxMCkKCmRmX25ldyA8LSBkZiAlPiUgZmlsdGVyKFgxID49IDEwKQpgYGAKCgo8YnI+CgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMgNC4gUHJpbmNpcGFsZXMgZnVuY2lvbmVzIGRlIGRwbHlyCgpIYXkgNi03IHByaW5jaXBhbGVzLiAKCgogIC0gYGZpbHRlcigpYCA6IHBlcm1pdGUgc2VsZWNjaW9uYXIgZmlsYXMgKHF1ZSBjdW1wbGVuIHVuYSBvIHZhcmlhcyBjb25kaWNpb25lcykKICAtIGBhcnJhbmdlKClgOiByZW9yZGVuYSBsYXMgZmlsYXMgKGBhcnJhbmdlKClgKS4KICAtIGByZW5hbWUoKWAgOiBjYW1iaWEgbG9zIG5vbWJyZXMgZGUgbGFzIGNvbHVtbmFzICh2YXJpYWJsZXMpCiAgLSBgc2VsZWN0KClgIDogc2VsZWNjaW9uYSBjb2x1bW5hcyAodmFyaWFibGVzKQogIC0gYG11dGF0ZSgpYCA6IGNyZWEgbnVldmFzIHZhcmlhYmxlcwogIC0gYHN1bW1hcmlzZSgpYCA6IHJlc3VtZSAoY29sYXBzYSkgdW5vcyBjdWFudG9zIHZhbG9yZXMgYSB1bm8gc8OzbG8uIFBvciBlamVtcGxvLCBjYWxjdWxhIGxhIG1lZGlhLCBtb2RhLCBldGMuLi4gZGUgdW4gY29uanVudG8gZGUgdmFsb3JlcwogIApIYXkgdW5hIHPDqXB0aW1hOgoKICAtIGBncm91cF9ieSgpYCA6IHBlcm1pdGUgYWdydXBhciBmaWxhcyBlbiBmdW5jacOzbiBkZSB1bmEgbyB2YXJpYXMgY29uZGljaW9uZXMKICAKWSBkZXNwdcOpcyBkZSBgZHBseXIgMS4wLjBgLCBlbiBtYXlvIGRlIDIwMjAsIGHDsWFkbyAyIG3DoXM6CgogIC0gYGFjcm9zcygpYCAgIHkgYHdoZXJlKClgLiBFc3RhcyBmdW5jaW9uZXMgc29uIHVuIHBvY28gZGlmZXJlbnRlcywgc29sbyBzZSB1c2FuIGVuIGNvbWJpbmFjacOzbiBkZSBvdHJvIGZ1bmNpw7NuL3ZlcmJvLiBTb24gMiBmdW5jaW9uZXMgcXVlIGVuIGxhIGplcmdhIGRlbCB0aWR5dmVyc2Ugbm8gc29uIHZlcmJvcyBzaW5vIGFkdmVyYmlvcy4gTG8gdmVtb3MKCjxicj4KClZlw6Ftb3NsYXMgdW5hIGEgdW5hLiBWZXJlbW9zIHPDs2xvIGFsZ3Vub3MgZWplbXBsb3MuIFlhIGlyZW1vcyBwcmFjdGljYW5kbwoKPGJyPgoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIyBgZmlsdGVyKClgCgpFc3RhIGZ1bmNpw7NuIChvIHZlcmJvKSBzZSB1dGlsaXphIHBhcmEgKipzZWxlY2Npb25hciBmaWxhcyoqIGRlIHVuIGRhdGFmcmFtZSAoZGYpLiBTZSBzZWxlY2Npb25hbiBsYXMgZmlsYXMgcXVlIGN1bXBsZW4gdW5hIGRldGVybWluYWRhIGNvbmRpY2nDs24gbyBjcml0ZXJpbyBsw7NnaWNvLiBQb3IgZWplbXBsbzoKCmBgYHtyLCBlY2hvID0gVFJVRSwgZXZhbCA9IFRSVUV9CiMtIHZhbW9zIGEgdHJhYmFqYXIgY29uIGxvcyBkYXRvcyBkZWwgW3BrZyBnYXBtaW5kZXJdKGh0dHBzOi8vZ2l0aHViLmNvbS9qZW5ueWJjL2dhcG1pbmRlcikKZ2FwbWluZGVyIDwtIGdhcG1pbmRlcjo6Z2FwbWluZGVyCmBgYAoKPGJyPgoKU2VsZWNjaW9uYW1vcyBsYXMgZmlsYXMgcXVlIGN1bXBsZW4gZGV0ZXJtaW5hZG9zIGNyaXRlcmlvczoKCgpgYGB7ciwgZWNobyA9IFRSVUUsIGV2YWwgPSBGQUxTRX0KIy0gT2JzZXJ2YWNpb25lcyBkZSBFc3Bhw7FhIChjb3VudHJ5ID09ICJTcGFpbiIpCmFhIDwtIGdhcG1pbmRlciAlPiUgZmlsdGVyKGNvdW50cnkgPT0gIlNwYWluIikgCgojLSBmaWxhcyBjb24gdmFsb3JlcyBkZSAibGlmZUV4cCIgPCAyOQphYSA8LSBnYXBtaW5kZXIgJT4lIGZpbHRlcihsaWZlRXhwIDwgMjkpICAgICAgIAoKIy0gZmlsYXMgY29uIHZhbG9yZXMgZGUgImxpZmVFeHAiIGVudHJlIFsyOSwgMzJdCmFhIDwtIGdhcG1pbmRlciAlPiUgZmlsdGVyKGxpZmVFeHAgPj0gIDI5ICwgbGlmZUV4cCA8PSAzMikgICAKYWEgPC0gZ2FwbWluZGVyICU+JSBmaWx0ZXIobGlmZUV4cCA+PSAgMjkgJiAgbGlmZUV4cCA8PSAzMikgIAphYSA8LSBnYXBtaW5kZXIgJT4lIGZpbHRlcihiZXR3ZWVuKGxpZmVFeHAsIDI5LCAzMikpICAgICAgIAoKIy0gb2JzZXJ2YWNpb25lcyBkZSBwYWlzZXMgZGUgw4FmcmljYSBjb24gbGlmZUV4cCA+IDMyCmFhIDwtIGdhcG1pbmRlciAlPiUgZmlsdGVyKGxpZmVFeHAgPiA3MiAmICBjb250aW5lbnQgPT0gIkFmcmljYSIpIAoKIy0gb2JzZXJ2YWNpb25lcyBkZSBwYcOtc2VzIGRlIMOBZnJpY2EgbyBBc2lhIGNvbiBsaWZlRXhwID4gMzIKYWEgPC0gZ2FwbWluZGVyICU+JSBmaWx0ZXIobGlmZUV4cCA+IDcyICYgIGNvbnRpbmVudCAlaW4lIGMoIkFmcmljYSIsICJBc2lhIikgKSAgCmFhIDwtIGdhcG1pbmRlciAlPiUgZmlsdGVyKGxpZmVFeHAgPiA3MiAmIChjb250aW5lbnQgPT0gIkFmcmljYSIgfCBjb250aW5lbnQgPT0gIkFzaWEiKSApICAKYGBgCgo8YnI+CgpMYSBmdW5jacOzbiBgZmlsdGVyKClgIHRpZW5lIG11Y2hhcyBtw6FzIHBvc2liaWxpZGFkZXMuIFlhIGxhcyBpcmVtb3MgdmllbmRvLiBQRVJPIHNpIHF1aWVyZXMgdmVyIHVuIHJlc3VtZW4gZGUgbGFzIHBvc2liaWxpZGFkZXMgZGVsIHBhcXVldGUgZHBseXIgbWlyYSBzdSBbQ0hFQVQgU0hFRVRdKGh0dHBzOi8vd3d3LnJzdHVkaW8uY29tL3Jlc291cmNlcy9jaGVhdHNoZWV0cy8pLiBeW0xhIMO6bHRpbWEgdmV6IHF1ZSBtaXLDqSBsYSBjaGVhdHNoZWV0IGHDum4gbm8gZXN0YWJhIGFjdHVhbGl6YWRhIHBhcmEgcmVjb2dlciBsb3MgY2FtYmlvcyBxdWUgYXBhcmVjaWVyb24gZW4gZHBseXIgMS4wLjAsIHBlcm8gYcO6biBhc8OtIG9zIHJlc3VsdGFyw6EgZGUgbXVjaGEgdXRpbGlkYWRdLiBMYSBbdmVyc2nDs24gYW50aWd1YV0oaHR0cHM6Ly93d3cucnN0dWRpby5jb20vd3AtY29udGVudC91cGxvYWRzLzIwMTUvMDIvZGF0YS13cmFuZ2xpbmctY2hlYXRzaGVldC5wZGYpIGRlIGxhIENoZWF0IHNoZWV0IGNvbnRpZW5lIHRhbWJpw6luIGxhcyBmdW5jaW9uZXMgZGUgYHRpZHlyYC4KCjxicj4KCiMjIyMgYHNsaWNlKClgIHRhbWJpw6luIGVzIG11eSDDunRpbCBwYXJhIHNlbGVjY2lvbmFyIGZpbGFzCgogIC0gYHNsaWNlKClgOiBmaWx0cmEgZmlsYXMgcG9yIHN1IHBvc2ljacOzbiAoZsOtc2ljYSBlbiBlbCBkZikKCjxicj4KCkNvbW8gZGlqaW1vcywgYHNsaWNlKClgIHNpcnZlIHBhcmEgc2VsZWNjaW9uYXIgZmlsYXMgcG9yIHBvc2ljacOzbjoKCmBgYHtyLCBlY2hvID0gVFJVRSwgZXZhbCA9IEZBTFNFfQojLSBzZWxlY2Npb25hIGxhcyBvYnNlcnZhY2lvbmVzIGRlIGxhIGTDqWNpbWEgYSBsYSBxdWluY2VhdmEKYWEgPC0gZ2FwbWluZGVyICU+JSBzbGljZShjKDEwOjE1KSkgCgojLSBzZWxlY2Npb25hIGxhcyBvYnNlcnZhY2lvbmVzIGRlIGxhIDEyIGEgMTMgWSBkZSBsYSA0NCBhIDQ2LCBZIGxhcyA0IMO6bHRpbWFzCmFhIDwtIGdhcG1pbmRlciAlPiUgc2xpY2UoIGMoMTI6MTQsIDQ0OjQ2LCBuKCktNDpuKCkpICkgIy0gQVFVSSBoYXkgdW4gZXJyb3IsIHRlbsOpaXMgcXVlIGFycmVnbGFybG8uIAoKIy0gUGlzdGE6IGlndWFsIG9zIGF5dWRhIGNyZWFyIHVuYSBjb2x1bW5hIGNvbiBlbCDDrW5kaWNlIGRlIHJvd3MgeSByZXBldGlyIGVsIGPDoWxjdWxvCmFhIDwtIGdhcG1pbmRlciAlPiUgbXV0YXRlKGluZGV4ID0gMTpuKCkpCmFhIDwtIGdhcG1pbmRlciAlPiUgc2xpY2UoIGMoMTI6MTQsIDQ0OjQ2LCBuKCktNDpuKCkpICkKYGBgYAogIAo8YnI+CgojIyMjIHZhcmlhbnRlcyBkZSBgc2xpY2UoKWAKCkhheSB2YXJpYXMgdmFyaWFudGVzIGRlIGBzbGljZSgpYC4gQ29uY3JldGFtZW50ZSBgc2xpY2VfbWF4KClgICBgc2xpY2VfbWluKClgLCBgc2xpY2Vfc21wbCgpYCwgYHNsaWNlX2hlYWQoKWAgYHNsaWNlX3RhaWwoKWAuIFZlcmVtb3MgYWxnw7puIGVqZW1wbG8gY29uIGxhcyAzIHByaW1lcmFzLgoKClNpIHF1ZXJlbW9zIHNlbGVjY2lvbmFyIGxhcyBmaWxhcyBxdWUgdGllbmVuIGVsIHZhbG9yIG3DoXhpbW8gKG8gbcOtbmltbykgZGUgdW5hIGRldGVybWluYWRhIHZhcmlhYmxlLCBwb2RlbW9zIHVzYXIgYHNsaWNlX21heCgpYCB5IGBzbGljZV9taW4oKWAKCmBgYHtyLCBlY2hvID0gVFJVRSwgZXZhbCA9IEZBTFNFfQojLSBzZWxlY2Npb25hIGxhcyAzIGZpbGFzIGNvbiBtYXlvciB2YWxvciBkZSBsaWZlRXhwCmFhIDwtIGdhcG1pbmRlciAlPiUgc2xpY2VfbWF4KGxpZmVFeHAsIG4gPSAzKQojLSBzZWxlY2Npb25hIGxhcyA0IGZpbGFzIGNvbiBNRU5PUiB2YWxvciBkZSBwb3AKYWEgPC0gZ2FwbWluZGVyICU+JSBzbGljZV9taW4ocG9wLCBuID0gNCkKYGBgYAoKUGFyYSB2ZXIgbGEgcG90ZW5jaWFsaWRhZCBkZSB1bmEgZnVuY2nDs24gdGllbmVzIHF1ZSB2ZXIgc3UgYXl1ZGEgaW50ZXJuYSAocHJlc2lvbmFuZG8gRjEgbyBjb24gYGhlbHAoKWApLiBQb3IgZWplbXBsbyBgc2xpY2VfbWluKClgIHRpZW5lIG90cm8gYXJndW1lbnRvIChgcHJvcGApIHF1ZSBub3MgcGVybWl0ZSBjYWxjdWxhciwgcG9yIGVqZW1wbG8sIGVsIDEwJSBkZSBvYnNlcnZhY2lvbmVzL3Bhw61zZXMgY29uIG1lbm9yIGVzcGVyYW56YSBkZSB2aWRhLgoKCmBgYHtyLCBlY2hvID0gVFJVRSwgZXZhbCA9IEZBTFNFfQojLSBvYnNlcnZhY2lvbmVzIGVuIGVsIHByaW1lciBkZWNpbCBlbiBjdWFudG8gYSBlc3BlcmFuemEgZGUgdmlkYSwgMTAlIGNvbiBtZW5vciBlc3BlcmFuemEgZGUgdmlkYQphYSA8LSBnYXBtaW5kZXIgJT4lIHNsaWNlX21pbihsaWZlRXhwLCBwcm9wID0gMC4xKQojLSAxJSBkZSBvYnNlcnZhY2lvbmVzIGNvbiBtYXlvciBwb2JsYWNpw7NuLiBJbWFnaW5vIHF1ZSBlc3RhcsOhbiBDaGluYSBlIEluZGlhCmFhIDwtIGdhcG1pbmRlciAlPiUgc2xpY2VfbWF4KHBvcCwgcHJvcCA9IDAuMDEpCmBgYGAKCkEgdmVjZXMgc2UgbmVjZXNpdGEgb2J0ZW5lciB1bmEgbXVlc3RyYSBhbGVhdG9yaWEgZGUgbG9zIGRhdG9zLiBMYSBmdW5jacOzbiBgc2xpY2Vfc2FtcGxlKClgIGVzdMOhIGRpc2XDsWFkYSBwYXJhIGF5dWRhcm5vcyBlbiBlc3RhIHRhcmVhOgoKCmBgYHtyfQojLSBzZWxlY2Npb25hIChhbGVhdG9yaWFtZW50ZSkgMTAwIGZpbGFzIGRlIGxvcyBkYXRvcwphYSA8LSBnYXBtaW5kZXIgJT4lIHNsaWNlX3NhbXBsZShuID0gMTAwKQojLSBzZWxlY2Npb25hIChhbGVhdG9yaWFtZW50ZSkgdW4gNSUgZGUgbG9zIGRhdG9zCmFhIDwtIGdhcG1pbmRlciAlPiUgc2xpY2Vfc2FtcGxlKHByb3AgPSAwLjA1KQpgYGAKCjxicj4KCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyMgYGFycmFuZ2UoKWAKCkVzdGEgZnVuY2nDs24gKG8gdmVyYm8pIHNlIHV0aWxpemEgcGFyYSAqKnJlb3JkZW5hciBsYXMgZmlsYXMqKiBkZSB1biBkYXRhZnJhbWUgKGRmKS4KCmBgYHtyLCBlY2hvID0gVFJVRSwgZXZhbCA9IEZBTFNFfQojLSBvcmRlbmEgbGFzIGZpbGFzIGRlIE1FTk9SIGEgbWF5b3Igc2Vnw7puIGxvcyB2YWxvcmVzIGRlIGxhIHYuIGxpZmVFeHAgCmFhIDwtIGdhcG1pbmRlciAlPiUgYXJyYW5nZShsaWZlRXhwKQoKIy0gb3JkZW5hIGxhcyBmaWxhcyBkZSBNQVlPUiBhIG1lbm9yIHNlZ8O6biBsb3MgdmFsb3JlcyBkZSBsYSB2LiBsaWZlRXhwCmFhIDwtIGdhcG1pbmRlciAlPiUgYXJyYW5nZShkZXNjKGxpZmVFeHApKSAgCgojLSBvcmRlbmFkYSBsYXMgZmlsYXMgZGUgTUVOT1IgYSBtYXlvciBzZWfDum4gbG9zIHZhbG9yZXMgZGUgbGEgdi4gbGlmZUV4cC4gCiMtIFNpIGhheSBlbXBhdGVzIHNlIHJlc3VlbHZlIGNvbiBsYSB2YXJpYWJsZSAicG9wIgphYSA8LSBnYXBtaW5kZXIgJT4lIGFycmFuZ2UobGlmZUV4cCwgcG9wKSAKYGBgCgo8YnI+CgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIyBgcmVuYW1lKClgCgpFc3RhIGZ1bmNpw7NuIHBlcm1pdGUgY2FtYmlhciBsb3Mgbm9tYnJlcyBkZSBsYXMgY29sdW1uYXMgCgpgYGB7ciwgZWNobyA9IFRSVUUsIGV2YWwgPSBGQUxTRX0KIy0gY2FtYmlhIGxvcyBub21icmVzIGRlIGxpZmVFeHAgeSBnZHBQZXJjYXAgYSBsaWZlX2V4cCB5IGdkcF9wZXJjYXAgCmdhcG1pbmRlciAlPiUgcmVuYW1lKGxpZmVfZXhwID0gbGlmZUV4cCwgIGdkcF9wZXJjYXAgPSBnZHBQZXJjYXApCgojLSghISkgbGEgZnVuY2nDs24gbmFtZXMoKSBkZSBSLWJhc2UgZXMgbXV5IMO6dGlsLiBUYiBzZXROYW1lcygpIHkgc2V0X25hbWVzKCkKYWEgPC0gZ2FwbWluZGVyCm5hbWVzKGFhKSA8LSBuYW1lcyhhYSkgJT4lIHRvdXBwZXIKbmFtZXMoYWEpIDwtIG5hbWVzKGFhKSAlPiUgdG9sb3dlcgpuYW1lcyhhYSkgPC0gYygidmFyXzAxIiwgInZhcl8wMiIsICJ2YXJfMDMiLCAidmFyXzA0IiwgInZhcl8wNSIgLCAidmFyXzA2IikKbmFtZXMoYWEpIDwtIHBhc3RlMCgiVmFyXyIsIDE6NikKbmFtZXMoYWEpIDwtIHBhc3RlMCgiTGFnXyIsIGZvcm1hdEMoMTo2LCB3aWR0aCA9IDIsIGZsYWcgPSAiMCIpKSAKYGBgCgo8YnI+CgojIyMjIHJlbmFtZV93aXRoKCkgLCB1bmEgdmFyaWFudGUgZGUgcmVuYW1lKCkKClNpIHRpZW5lcyBxdWUgaGFjZXIgdHJhbnNmb3JtYWNpb25lcyBtw6FzIGNvbXBsZWphcywgcXVlIHJlcXVpZXJhbiBlbCB1c28gZGUgZnVuY2lvbmVzIG8gcGF1dGFzLCBkZSBsb3Mgbm9tYnJlcyBkZSBsYXMgdmFyaWFibGVzIHB1ZWRlcyB1c2FyIGByZW5hbWVfd2l0aCgpYAoKCmBgYHtyLCBldmFsID0gRkFMU0V9CmFhIDwtIGdhcG1pbmRlcgpyZW5hbWVfd2l0aChhYSwgdG91cHBlcikKcmVuYW1lX3dpdGgoYWEsIHRvdXBwZXIsIHN0YXJ0c193aXRoKCJMaWZlIikgfCBjb250YWlucygiY291bnRyIikpCnJlbmFtZV93aXRoKGFhLCB+IHN0cl9yZXBsYWNlKC54LCAiZSIsICLDliIpKSAgIy0gKCEhISEpCmBgYAoKPGJyPgoKTGEgZnVuY2nDs24gYHJlbmFtZSgpYCBlcyDDunRpbCBwZXJvLCBlbnNlZ3VpZGEgdmVyZW1vcyBxdWUgbGEgc2lndWllbnRlIGZ1bmNpw7NuLCBgc2VsZWN0KClgLCB0YW1iacOpbiBwZXJtaXRlIHJlbm9tYnJhciBsYXMgY29sdW1uYXMsIGUgaW5jbHVzbyByZW9yZGVuYXIgbGEgcG9zaWNpw7NuIGRlIGVzdGFzLgoKCgo8YnI+CgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIyBgc2VsZWN0KClgCgpFc3RhIGZ1bmNpw7NuIChvIHZlcmJvKSBzaXJ2ZSBwYXJhICoqc2VsZWNjaW9uYXIgY29sdW1uYXMqKiBkZSB1biBkZi4KCiMjIyMgc2VsZWNjaW9uYXIgdmFyaWFibGVzIHBvciBub21icmUKClNlbGVjY2lvbmFtb3MgbGFzIHZhcmlhYmxlcyAieWVhciIgeSAibGlmZUV4cCI6CgpgYGB7ciwgZWNobyA9IFRSVUUsIGV2YWwgPSBGQUxTRX0KIy0gU2UgbGVlIGNvbW86IOKAnFRha2UgZWwgZGYgZ2FwbWluZGVyLCB0aGVuIHNlbGVjdCB0aGUgdmFyaWFibGVzIHllYXIgYW5kIGxpZmVFeHDigJ0KYWEgPC0gZ2FwbWluZGVyICU+JSBzZWxlY3QoeWVhciwgbGlmZUV4cCkgCmFhIDwtIGdhcG1pbmRlciAlPiUgc2VsZWN0KGMoeWVhciwgbGlmZUV4cCkpCmBgYAoKPGJyPgoKIyMjIyBxdWl0YXIgdmFyaWFibGVzCgpQYXJhIGVsaW1pbmFyIHVuYSB2YXJpYWJsZSBoYXkgdmFyaWFzIGZvcm1hczoKCmBgYHtyfQphYSA8LSBnYXBtaW5kZXIgJT4lIHNlbGVjdCgteWVhcikgICAjLSBsYSBmb3JtYSBtYXMgaGFiaXR1YWwKCiMtIGVzdGFzIGRvcyBmb3JtYXMgc29uIG11Y2hvIG1lbm9zIGhhYml0dWFsZXMKYWEgPC0gZ2FwbWluZGVyICU+JSBzZWxlY3QoIXllYXIpICAgCmFhIDwtIGdhcG1pbmRlciAlPiUgbXV0YXRlKHllYXIgPSBOVUxMKSAgICMtIGHDum4gbm8gaGVtb3MgdmlzdG8gbXV0YXRlKCkKYGBgCgpQYXJhIGVsaW1pbmFyIHZhcmlhcyB2YXJpYWJsZXM6CgpgYGB7cn0KIy0gcXVpdGFtb3MgbGFzIHZhcmlhYmxlczogeWVhciB5IGxpZmVFeHAKYWEgPC0gZ2FwbWluZGVyICU+JSBzZWxlY3QoLWMoeWVhciwgbGlmZUV4cCkpCmBgYAoKPGJyPgoKIyMjIyBzZWxlY2Npb25hciBwb3IgcG9zaWNpw7NuCgpTZWxlY2Npb25hbW9zIGxhcyB2YXJpYWJsZXMgZGVsIGRmIGdhcG1pbmRlciBzaWd1aWVudGVzOiBkZSBsYSBwcmltZXJhIGEgbGEgdGVyY2VyYSB5IHRhbWJpw6luIGxhIHF1aW50YSAobWVqb3Igc2VsZWNjaW9uYXJsYXMgcG9yIG5vbWJyZSEhKQoKYGBge3IsIGVjaG8gPSBUUlVFLCBldmFsID0gRkFMU0V9CiMtIHNlbGVjY2lvbmFtb3MgbGFzIHZhcmlhYmxlcyB7MSwgMiwgMyB5IDV9CmFhIDwtIGdhcG1pbmRlciAlPiUgc2VsZWN0KDE6MywgNSkKYGBgCgo8YnI+CgojIyMjIHF1aXRhciB2YXJpYWJsZXMgcG9yIHBvc2ljacOzbgoKU2VsZWNjaW9uYW1vcyB0b2RhcyBsYXMgdmFyaWFibGVzIGRlbCBkZiBnYXBtaW5kZXIgKipleGNlcHRvKiogbGFzIHNpZ3VpZW50ZXM6IGRlIGxhIHByaW1lcmEgYSBsYSB0ZXJjZXIgeSBsYSBxdWludGEgKG1lam9yIHNlbGVjY2lvbmFybGFzIHBvciBub21icmUpCgpgYGB7ciwgZWNobyA9IFRSVUUsIGV2YWwgPSBGQUxTRX0KIy0gcXVpdGFtb3MgbGFzIHZhcmlhYmxlcyB7MSwgMiwgMyB5IDV9CmFhIDwtIGdhcG1pbmRlciAlPiUgc2VsZWN0KC0gYygxOjMsIDUpKQpgYGAKCjxicj4KCiMjIyMgYHNlbGVjdCgpYCBjb24gbGEgZnVuY2nDs24gYXV4aWxpYXIgYHdoZXJlKClgCgpFbiBlbCBkYXRhLmZyYW1lIGBnYXBtaW5kZXJgIGxhcyAyIHByaW1lcmFzIHZhcmlhYmxlcyAoY291bnRyeSB5IGNvbnRpbmVudCkgc29uIGZhY3RvcmVzIHkgbGFzIDQgc2lndWllbnRlcyBzb24gdmFyaWFibGUgbnVtw6lyaWNhcy4gCgpgYGB7cn0KcHJpbnQoZ2FwbWluZGVyLCBuID0gMykKYGBgCgoKSW1hZ2luYSBxdWUgcXVlcmVtb3Mgc2VsZWNjaW9uYXIgc8OzbG8gbGFzIHZhcmlhYmxlcyBxdWUgc29uIG51bcOpcmljYXMuIFBvZGVtb3MgaGFjZXJsbyBwb3Igbm9tYnJlIG8gcG9yIHBvc2ljacOzbiBwZXJvIG1lam9yIGNvbiBgc2VsZWN0KClgIHkgbGEgZnVuY2nDs24gYXV4aWxpYXIgYHdoZXJlKClgXltIYXN0YSBtYXlvIGRlIDIwMjAsIGVzdG8gZXMsIGhhc3RhIGRwbHlyIDEuMC4wLCBlc3RvIHNlIGhhY2lhIGNvbiBsYSBmdW5jacOzbiBgc2VsZWN0X2lmKClgXS4gCgoKCmBgYHtyLCBlY2hvID0gVFJVRSwgZXZhbCA9IEZBTFNFfQphYSA8LSBnYXBtaW5kZXIgJT4lIHNlbGVjdChpcy5udW1lcmljKSAgICAgICAgIy0gZnVuY2lvbmEsIHBlcm8gLi4uCmFhIDwtIGdhcG1pbmRlciAlPiUgc2VsZWN0KHdoZXJlKGlzLm51bWVyaWMpKSAjLSBlcyAicHJlZmVyaWJsZSIgZXN0YSBzZWd1bmRhIGV4cHJlc2nDs24KYGBgCgoKYHNlbGVjdCgpYCB5IGB3aGVyZSgpYCBzb24gZG9zIGZ1bmNpb25lcywgc8OtLCBwZXJvIGVuIGxhIGplcmdhIGRlbCB0aWR5dmVyc2UsIGBzZWxlY3QoKWAgZXMgdW4gdmVyYm8geSBgd2hlcmUoKWAgZXMgdW4gYWR2ZXJiaW8sIGN1YWxpZmljYS9jYW1iaWEgbG8gcXVlIGhhY2UgYHNlbGVjdCgpYC4KCgpTaSBxdWlzacOpcmFtb3Mgc2VsZWNjaW9uYXIgbGFzIHZhcmlhYmxlcyBxdWUgbm8gc29uIG51bcOpcmljYXMgaGFyw61hbW9zOgoKCmBgYHtyLCBlY2hvID0gVFJVRSwgZXZhbCA9IEZBTFNFfQphYSA8LSBnYXBtaW5kZXIgJT4lIHNlbGVjdCghaXMubnVtZXJpYykKYWEgPC0gZ2FwbWluZGVyICU+JSBzZWxlY3QoIXdoZXJlKGlzLm51bWVyaWMpKSAgIy0gZXMgcHJlZmVyaWJsZSBlc3RhIHNlZ3VuZGEgZXhwcmVzacOzbgpgYGAKCjxicj4KCkxhIGZ1bmNpw7NuIGBzZWxlY3QoKWAgdGllbmUgbXVjaGFzIG3DoXMgcG9zaWJpbGlkYWRlcy4geWEgbGFzIGlyZW1vcyB2aWVuZG8uIFBFUk8gc2kgcXVpZXJlcyB2ZXIgdW4gcmVzdW1lbiBkZSBsYXMgcG9zaWJpbGlkYWRlcyBkZWwgcGtnIGRwbHlyIG1pcmEgc3UgW0NIRUFUIFNIRUVUXShodHRwczovL3d3dy5yc3R1ZGlvLmNvbS9yZXNvdXJjZXMvY2hlYXRzaGVldHMvKS4KCjxicj4KCiMjIyMgcmVub21icmFuZG8geSByZW9yZGVuYW5kbyBjb2x1bW5hcyBjb24gYHNlbGVjdCgpYAoKTG8gcXVlIHPDrSB2YW1vcyBhIHZlciBzb24gMiBwb3NpYmlsaWRhZGVzIGRlIGBzZWxlY3QoKWAgcXVlIHNvbiBtdXkgw7p0aWxlcy4gQ29uIGBzZWxlY3QoKWAgcG9kZW1vczogKipyZW5vbWJyYXIqKiB5ICoqcmVvcmRlbmFyKiogbGFzIGNvbHVtbmFzOgoKYGBge3IsIGVjaG8gPSBUUlVFLCBldmFsID0gRkFMU0V9CiMtIGRlamFtb3MgZW4gYWEgc29sYW1lbnRlIGEgbGFzIGNvbHVtbmFzICJ5ZWFyIiB5ICJwb3AiOyBBREVNw4FTLCBhaG9yYSwgInBvcCIgaXLDoSBhbnRlcyBxdWUgInllYXIiCmFhIDwtIGdhcG1pbmRlciAlPiUgc2VsZWN0KHBvcCwgeWVhcikKYGBgCgo8YnI+CgpgYGB7ciwgZWNobyA9IFRSVUUsIGV2YWwgPSBGQUxTRX0KIy0gZGVqYW1vcyBlbiBhYSBzb2xhbWVudGUgYSBsYXMgY29sdW1uYXMgInllYXIiIHkgInBvcCIgeSBsZXMgY2FtYmlhbW9zIGVsIG5vbWJyZQphYSA8LSBnYXBtaW5kZXIgJT4lIHNlbGVjdChwb2JsYWNpb24gPSBwb3AsIGHDsW8gPSB5ZWFyKQpgYGAKCjxicj4KCkltYWdpbmEgcXVlIHF1aWVyZXMgcXVlIGxhIMO6bHRpbWEgY29sdW1uYSBwYXNlIGEgc2VyIGxhIHByaW1lcmEgKG1hbsOtYXMhISkuIFBvZGVtb3MgaGFjZXJsbyBjb24gc2VsZWN0IHkgYGV2ZXJ5dGhpbmcoKWAuIGV2ZXJ5dGhpbmcgZXMgdW5hICoqZnVuY2nDs24gYXV4aWxpYXIqKjoKCmBgYHtyLCBlY2hvID0gVFJVRSwgZXZhbCA9IEZBTFNFfQojLSAiZ2RwUGVyY2FwIiBxdWUgZXMgbGEgw7psdGltYSBjb2x1bW5hIHBhc2EgYSBzZXIgbGEgcHJpbWVyYQphYSA8LSBnYXBtaW5kZXIgJT4lIHNlbGVjdChnZHBQZXJjYXAsIGV2ZXJ5dGhpbmcoKSkKCiMoISEpIG90cmFzIDMgZm9ybWFzIGRlIGhhY2VyIGxvIG1pc21vOiBxdWUgbGEgw7psdGltYSBjb2x1bW5hIHBhc2UgYSBzZXIgbGEgcHJpbWVyYQphYSA8LSBnYXBtaW5kZXIgJT4lIHNlbGVjdChuY29sKGRmKSwgZXZlcnl0aGluZygpKQphYSA8LSBnYXBtaW5kZXIgJT4lIHNlbGVjdChsZW5ndGgoZGYpLCBldmVyeXRoaW5nKCkpCmFhIDwtIGdhcG1pbmRlciAlPiUgc2VsZWN0KGxhc3RfY29sKCksIGV2ZXJ5dGhpbmcoKSkgICMtIHVzYW1vcyB0aGUgc2VsZWN0aW9uIGhlbHBlciBsYXN0X2NvbCgpCmBgYAoKRW4gbGEgw7psdGltYSBpbnN0cnVjY2nDs24gaGVtb3MgdXNhZG8gZG9zIGZ1bmNpb25lcyBhdXhpbGlhcmVzIGRlIHNlbGVjdCgpLCBkb3MgImhlbHBlciBmdW5jdGlvbnMiLiBMYSBsaXN0YSBjb21wbGV0YSBkZSBmdW5jaW9uZXMgYXV4aWxpYXJlcyBwYXJhIHNlbGVjdCgpIHB1ZWRlcyB2ZXJsYSBbYXF1w61dKGh0dHBzOi8vdGlkeXNlbGVjdC5yLWxpYi5vcmcvcmVmZXJlbmNlL3NlbGVjdF9oZWxwZXJzLmh0bWwpLgoKPGJyPgoKCiMjIyMgYHJlbG9jYXRlKCkgYAoKRGVzZGUgZHBseXIgMS4wLjAsIHRlbmVtb3Mgb3RyYSBmdW5jacOzbiBwYXJhIHJlb3JkZW5hciBsYXMgdmFyaWFibGVzIGRlIHVuIGRhdGEuZnJhbWU6IGByZWxvY2F0ZSgpYC4gVmXDoW1vc2xhIGVuIGFjY2nDs246CgpgYGB7ciwgZWNobyA9IFRSVUUsIGV2YWwgPSBGQUxTRX0KYWEgPC0gZ2FwbWluZGVyICU+JSBkcGx5cjo6cmVsb2NhdGUoY291bnRyeSwgLmFmdGVyID0gbGlmZUV4cCkKYWEgPC0gZ2FwbWluZGVyICU+JSBkcGx5cjo6cmVsb2NhdGUoY291bnRyeSwgLmJlZm9yZSA9IGxpZmVFeHApCmBgYAoKTGFzIG9wY2lvbmVzIGAuYWZ0ZXJgIHkgYC5iZWZvcmVgIHRhbWJpw6luIGVzdMOhbiBkaXNwb25pYmxlcyBlbiBgbXV0YXRlKClgLCBsYSBzaWd1aWVudGUgZnVuY2nDs24gcXVlIHByZXNlbnRhcmVtb3MuCgo8YnI+Cjxicj4KCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMjIGBtdXRhdGUoKWAKCkVzdGEgZnVuY2nDs24gKG8gdmVyYm8pIHNpcnZlIHBhcmEgKipjcmVhciBudWV2YXMgdmFyaWFibGVzKiogKGNvbHVtbmFzKS4gTMOzZ2ljYW1lbnRlLCBlcyBtdXkgw7p0aWwgZW4gYW7DoWxpc2lzIGRlIGRhdG9zLgoKCkNyZWFtb3MgbGEgdmFyaWFibGU6IGBHRFAgPSBwb3AqZ2RwcGVyQ2FwYAoKYGBge3IsIGVjaG8gPSBUUlVFLCBldmFsID0gRkFMU0V9CiMtIENyZWFtb3MgbGEgdmFyaWFibGU6IEdEUCA9IHBvcCpnZHBwZXJDYXAKYWEgPC0gZ2FwbWluZGVyICU+JSBtdXRhdGUoR0RQID0gcG9wKmdkcFBlcmNhcCkKYGBgCgpQb3IgZGVmZWN0bywgbGEgbnVldmEgdmFyaWFibGUgY3JlYWRhIHNlIHNpdHVhcsOhIGFsIGZpbmFsIGRlbCBkYXRhIGZyYW1lLCBhIG5vIHNlciBxdWUgdXNlbW9zIGxvcyBhcmd1bWVudG9zIGAuYWZ0ZXJgIHkgYC5iZWZvcmVgCgoKYGBge3IsIGVjaG8gPSBUUlVFLCBldmFsID0gRkFMU0V9CmFhIDwtIGdhcG1pbmRlciAlPiUgbXV0YXRlKEdEUCA9IHBvcCpnZHBQZXJjYXAsIC5hZnRlciA9IGNvdW50cnkpCmFhIDwtIGdhcG1pbmRlciAlPiUgbXV0YXRlKEdEUCA9IHBvcCpnZHBQZXJjYXAsIC5iZWZvcmUgPSBjb3VudHJ5KQpgYGAKCmBtdXRhdGUoKWAgdGFtYmnDqW4gdGllbmUgdW4gYXJndW1lbnRvKGAua2VlcGApIHBhcmEgY29udHJvbGFyIHF1ZSB2YXJpYWJsZXMgcGVybWFuZWNlbiBlbiBlbCBkYXRhIGZyYW1lLiBQb3IgZWplbXBsbywgZW4gZWwgY2h1bmsgZGUgYWJham8gZGVqYXJlbW9zIHNvbG8gbGFzIHZhcmlhYmxlcyB1c2FkYXMuCgpgYGB7cn0KYWEgPC0gZ2FwbWluZGVyICU+JSBtdXRhdGUoR0RQID0gcG9wKmdkcFBlcmNhcCwgLmtlZXAgPSAidXNlZCIpCmBgYAoKPGJyPgoKLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyMgYHN1bW1hcmlzZSgpYAoKRXN0YSBmdW5jacOzbiAobyB2ZXJibykgc2lydmUgcGFyYSAqKlJFU1VNSVIqKiAobyAiY29sYXBzYXIgZmlsYXMiKS4gQ29nZSB1bmEgdmFyaWFibGUgbyBncnVwbyBkZSB2YWxvcmVzIGNvbW8gaW5wdXQgeSBkZXZ1ZWx2ZSB1biBzb2xvIHZhbG9yOyBwb3IgZWplbXBsbywgaGF5YSBsYSBtZWRpYSBhcml0bcOpdGljYSAobyBlbCBtw61uaW1vLCBvIGVsIG3DoXhpbW8gLi4uKSBkZSB1bmEgY29sdW1uYS92YXJpYWJsZS4KCgo8YnI+CgpPYnRlbmdhbW9zIGRldGVybWluYWRvcyBlc3RhZMOtc3RpY29zIGRlICoqdW5hIHZhcmlhYmxlKiouIFBhcmEgZXN0byBubyBub3MgaGFjZSBmYWx0YSBgZHBseXJgIHBlcm8gY29udmllbmUgaXIgaGFiaXR1w6FuZG9zZSBhIHN1IHNpbnRheGlzLgoKYGBge3IsIGVjaG8gPSBUUlVFLCBldmFsID0gRkFMU0V9CiMtIHJldG9ybmFyw6EgdW4gw7puaWNvIHZhbG9yOiBsYSBtZWRpYSBnbG9iYWwgZGUgbGEgdi4gImxpZmVFeHAiCmFhIDwtIGdhcG1pbmRlciAlPiUgc3VtbWFyaXNlKG1lZGlhID0gbWVhbihsaWZlRXhwKSkgIAoKIy0gcmV0b3JuYXLDoSB1biDDum5pY28gdmFsb3I6IGVsIG7Dum1lcm8gZGUgZmlsYXMKYWEgPC0gZ2FwbWluZGVyICU+JSBzdW1tYXJpc2UoTk4gPSBuKCkpICAKYWEgPC0gZ2FwbWluZGVyICU+JSBjb3VudCgpICAgICAgICAgICAgICAgICMtIG3DoXMgYWRlbGFudGUgdmVyZW1vcyBsYSB1dGlsaWRhZCBkZSBjb3VudCgpCgoKIy0gcmV0b3JuYXLDoSB1biDDum5pY28gdmFsb3I6IGxhIGRlc3ZpYWNpw7NuIHTDrXBpY2EgZGUgbGEgdi4gImxpZmVFeHAiCmFhIDwtIGdhcG1pbmRlciAlPiUgc3VtbWFyaXNlKGRlc3ZpYWNpb25fdGlwaWNhID0gc2QobGlmZUV4cCkpICAKCiMtIHJldG9ybmFyw6EgdW4gw7puaWNvIHZhbG9yOiBlbCBtw6F4aW1vIGRlIGxhIHZhcmlhYmxlICJwb3AiCmFhIDwtIGdhcG1pbmRlciAlPiUgc3VtbWFyaXNlKG1heChwb3ApKSAgCgojLSByZXRvcm5hcsOhIDIgdmFsb3JlczogbGEgbWVkaWEgeSBzZCBkZSBsYSB2LiAibGlmZUV4cCIKYWEgPC0gZ2FwbWluZGVyICU+JSBzdW1tYXJpc2UobWVhbihsaWZlRXhwKSwgc2QobGlmZUV4cCkpICAKCiMtIHJldG9ybmFyw6EgMiB2YWxvcmVzOiBsYXMgbWVkaWFzIGRlICJsaWZlRXhwIiB5ICJnZHBQZXJjYXAiCmFhIDwtIGdhcG1pbmRlciAlPiUgc3VtbWFyaXNlKG1lYW4obGlmZUV4cCksIG1lYW4oZ2RwUGVyY2FwKSkgIApgYGAKCjxicj4KCgojIyMjIGBhY3Jvc3MoKWAgeSBgd2hlcmUoKWAKCkFudGVzIGRlIHBhc2FyIGEgdmVyIGBncm91cF9ieSgpYCwgdmFtb3MgYSB1dGlsaXphciBsYXMgMiBudWV2YXMgZnVuY2lvbmVzIGBhY3Jvc3MoKWAgeSBgd2hlcmUoKWAuCgpNdWNoYXMgdmVjZXMgZW4gdW4gdHJhYmFqbyBzZSBoYW4gZGUgY2FsY3VsYXIgZXN0YWTDrXN0aWNvcyBkZSAqKnRvZGFzIGxhcyB2YXJpYWJsZXMqKiBkZWwgZGYuIEVzdG8gc2UgaGFjZSBjb24gYHN1bW1hcmlzZSgpYCwgUEVSTyB1dGlsaXphbmRvIHRhbWJpw6luIHVuYSBmdW5jacOzbiBkZSBheXVkYSAoaGVscGVyIGZ1bmN0aW9uKTogYGFjcm9zcygpYF5bSGFzdGEgbGEgYXBhcmljacOzbiBkZSBkcGx5ciAxLjAuMCBlbiBtYXlvIGRlIDIwMjAsIHNlIHV0aWxpemFiYSBsYSBmdW5jacOzbiBgc3VtYXJpc2VfYWxsKClgXS4gQSB2ZWNlcyB0YW1iacOpbiBoYXkgcXVlIHVzYXIgb3RyYSBoZWxwZXIgZnVuY3Rpb246IGB3aGVyZSgpYC4KCkxhIHNpbnRheGlzIGRlIGBhY3Jvc3MoKWAgZXM6ICAKCmBhY3Jvc3MoLmNvbHMgPSBldmVyeXRoaW5nKCksIC5mbnMgPSBOVUxMLCAuLi4sIC5uYW1lcyA9IE5VTEwpYDsgZXMgZGVjaXIsICAgCgpgYWNyb3NzKCJjb2x1bW5hcyBzZWxlY2Npb25hZGFzIiwgImZ1bmNpb25lcyBvIGPDoWxjdWxvcyBhIHJlYWxpemFyIiwgInNpIHF1aWVyZXMgY29udHJvbGFyIGxvcyBub21icmVzIikpYC4gCgpTaSBhbCBzZWxlY2Npb25hciBsYXMgY29sdW1uYXMgdXRpbGl6YXMgYWxnw7puIGNyaXRlcmlvIGzDs2dpY28sIGNvbW8gcG9yIGVqZW1wbG8gYGlzLm51bWVyaWMoKWAsIGVudG9uY2VzIGxhIHNpbnRheGlzIGRlIGBhY3Jvc3MoKWAgZXMgdW4gcG9jbyBkaWZlcmVudGU7IGNvbmNyZXRhbWVudGUgc2Vyw6E6IAoKYGFjcm9zcyggd2hlcmUoImNvbHVtbmFzIHNlbGVjY2lvbmFkYXMiKSwgImZ1bmNpb25lcyBvIGPDoWxjdWxvcyBhIHJlYWxpemFyIiwgInNpIHF1aWVyZXMgY29udHJvbGFyIGxvcyBub21icmVzIikpYAoKTG9zIGVqZW1wbG9zIGF5dWRhbiBhIGVudGVuZGVybG86CgpgYGB7ciwgZWNobyA9IFRSVUUsIGV2YWwgPSBGQUxTRX0KIy0gbWVkaWEgZGUgY2FkYSB1bmEgZGUgbGFzIDYgdmFyaWFibGVzLiBEZXZ1ZWx2ZSAyIHdhcm5pbmdzIHBvcnF1ZSBsYXMgMiBwcmltZXJhcyBzb24gdGV4dHVhbGVzLiBObyBzZSBwdWVkZSBjYWxjdWxhciBsYSBtZWRpYSBkZSBjb250aW5lbnQgeSBjb3VudHJ5CmdhcG1pbmRlciAlPiUgc3VtbWFyaXNlKGFjcm9zcyhldmVyeXRoaW5nKCksIG1lYW4pICkgCgojLSBjYWxjdWxhbW9zIGxhIG1lZGlhIGRlIHRlcmNlcmEgYSBsYSBzZXh0YSB2YXJpYWJsZQpnYXBtaW5kZXIgJT4lIHN1bW1hcmlzZShhY3Jvc3MoMzo2LCBtZWFuKSApIApgYGAKCkxvIHF1ZSBvcyBkZWPDrWEgZGUgc2VsZWNjaW9uYXIgY29sdW1uYXMgY29uIHVuIGNyaXRlcmlvIGzDs2dpY28geSB1c2FyIGB3aGVyZSgpYAoKYGBge3IsIGVjaG8gPSBUUlVFLCBldmFsID0gRkFMU0V9CmdhcG1pbmRlciAlPiUgc3VtbWFyaXNlKGFjcm9zcyh3aGVyZShpcy5udW1lcmljKSwgbWVhbikpIAoKIy0gY29uIGxvcyBub21icmVzIGRlIGxvcyBhcmd1bWVudG9zIChtw6FzIGxhcmdvIHBlcm8gY29udmllbmUgdmVybG8gZGUgdmV6IGVuIGN1YW5kbykKZ2FwbWluZGVyICU+JSBzdW1tYXJpc2UoYWNyb3NzKC5jb2xzID0gd2hlcmUoaXMubnVtZXJpYyksIC5mbnMgPSBtZWFuKSkgCmBgYAoKVmFtb3MgYSBjYWxjdWxhciBjb3NhcyB1biBwb2NvIG3DoXMgY29tcGxlamFzLiBJbWFnaW5hIHF1ZSBubyBzw7NsbyBxdWllcmVzIGNhbGN1bGFyIGxhIG1lZGlhIHNpbm8gcXVlIHRhbWJpw6luIHF1aWVyZXMgY2FsY3VsYXIgbGEgZGVzdmlhY2nDs24gdMOtcGljYS4gU2VndWlyZW1vcyB1c2FuZG8gYHN1bW1hcmlzZSgpYCB5IGBhY3Jvc3MoKWAuIExvIMO6bmljbyBudWV2byBlcyBxdWUgY29tbyB2YW1vcyBhIGFwbGljYXIgZG9zIGZ1bmNpb25lcyAoYG1lYW4oKWAgeSBgc2QoKWApIGxhcyB0ZW5lbW9zIHF1ZSBwb25lciBkZW50cm8gZGUgYGxpc3QoKWAuIFRpZW5lIHNlbnRpZG8sIGVzIHVuYSBsaXN0YSBkZSBmdW5jaW9uZXMgYSBhcGxpY2FyIGEgbGFzIGNvbHVtbmFzIHF1ZSBzZWxlY2Npb25lbW9zIGNvbiBgYWNyb3NzKClgCgpgYGB7ciwgZWNobyA9IFRSVUUsIGV2YWwgPSBGQUxTRX0KIy0gY2FsY3VsYW1vcyBsYSBtZWRpYSB5IGRlc3ZpYWNpw7NuIHTDrXBpY2EgZGUgbGFzIGNvbHVtbmFzIDMgYSA2LgpnYXBtaW5kZXIgJT4lIHN1bW1hcmlzZShhY3Jvc3MoMzo2LCBsaXN0KG1lZGlhID0gbWVhbiwgZGVzdiA9IHNkKSkpCgojLSBsbyBtaXNtbywgcGVybyBleHBsaWNpdGFuZG8gbG9zIG5vbWJyZXMgZGUgbG9zIGFyZ3VtZW50b3MKZ2FwbWluZGVyICU+JSBzdW1tYXJpc2UoYWNyb3NzKC5jb2xzID0gMzo2LCAuZm5zID0gbGlzdChtZWRpYSA9IG1lYW4sIGRlc3YgPSBzZCkgKSkKCiMtIGxvIG1pc21vIG90cmEgdmV6LCBwZXJvIGVsaWdpZW5kbyBlbCBub21icmUgZGUgbGFzIHZhcmlhYmxlcyBxdWUgc2UgdmFuIGFhIGNyZWFyIGNvbiAubmFtZXMKZ2FwbWluZGVyICU+JSBzdW1tYXJpc2UoYWNyb3NzKDM6NiwgbGlzdChtZWRpYSA9IG1lYW4sIGRlc3YgPSBzZCksIC5uYW1lcyA9ICJ7Zm59X3tjb2x9IikpCmBgYAoKKCEhISkgSW1hZ2luYSBxdWUgcXVpc2nDqXJhbW9zIHByZXNlbnRhciBlbiB1bmEgdGFibGEgbG9zIGFudGVyaW9yZXMgcmVzdWx0YWRvczsgdGVuZHLDrWFtb3MgcXVlIHVzYXIgYHRpZHlyOjpwaXZvdF9sb25nZXIoKWAuIExvIHZveSBhIGhhY2VyIHBvciB0cm96b3MuIE5vcyB2YSBhIGNvc3RhciB1biBwb2NvOgoKYGBge3J9CmFhIDwtIGdhcG1pbmRlciAlPiUgc3VtbWFyaXNlKGFjcm9zcygzOjYsIGxpc3QobWVkaWEgPSBtZWFuLCBkZXN2ID0gc2QpLCAubmFtZXMgPSAie2ZufV97Y29sfSIpKSAKCmFhMSA8LSBhYSAlPiUgcGl2b3RfbG9uZ2VyKDE6OCwgbmFtZXNfdG8gPSAibmFtZXMiLCB2YWx1ZXNfdG8gPSAidmFsdWVzIikKYWEyIDwtIGFhMSAlPiUgc2VwYXJhdGUobmFtZXMsIGludG8gPSBjKCJvcGVyYWNpb24iLCAidmFyaWFibGUiKSwgc2VwID0gICJfIikgJT4lIAogICAgICAgICAgICAgICBzZWxlY3QodmFyaWFibGUsIGV2ZXJ5dGhpbmcoKSkKYWEzIDwtIGFhMiAlPiUgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IG9wZXJhY2lvbiwgdmFsdWVzX2Zyb20gPSB2YWx1ZXMpCmBgYAoKTG8gcHJhY3RpY2FyZW1vcywgeSB2ZXJlbW9zIG3DqXRvZG9zIG3DoXMgc2VuY2lsbG9zIHBhcmEgaGFjZXIgdGFibGFzIGNvbiBlc3RhZMOtc3RpY29zIGRlc2NyaXB0aXZvcywgcGVybyBlc28gc2Vyw6EgZW4gZWwgdHV0b3JpYWwgZGVkaWNhZG8gYSB0YWJsYXM7IHBlcm8gbm8gb3Mgb2x2aWTDqWlzIGRlIGBhY3Jvc3MoKWAgcXVlIGx1ZWdvIHRlbmVtb3MgcXVlIHZvbHZlciBhIGVsbGEuCjxicj4KCi0tLS0tLS0tLS0tLS0tLS0tLS0KCiMjIGBncm91cF9ieSgpYAoKQ29uIGVzdGEgZnVuY2nDs24geWEgZW1wZXphcmVtb3MgYSB2ZXIgbGEgcG90ZW5jaWEgZGUgZHBseXIuIEVuIGFuw6FsaXNpcyBkZSBkYXRvcyBtdWNoYXMgb3BlcmFjaW9uZXMgKG1lZGlhIGV0Yy4uKSBxdWVyZW1vcyBjYWxjdWxhcmxhcyBwYXJhIGRpc3RpbnRvcyBncnVwb3MgKGhvbWJyZSwgbXVqZXIgLi4uKS4gYGdyb3VwX2J5KClgIHBlcm1pdGUgaGFjZXJsby4KCmBncm91cF9ieSgpYGNvZ2UgdW4gZGYgeSBsbyBjb252aWVydGUgZW4gdW4gKioiZGYgYWdydXBhZG8iKiouIEVuIGVzZSBudWV2byAiZGYgYWdydXBhZG8iLCBsYXMgb3BlcmFjaW9uZXMgcXVlIGhhZ2Ftb3MgY29uIGBzdW1tYXJpc2UoKWAgc2UgaGFyw6FuIHBvciBzZXBhcmFkbyBwYXJhIGNhZGEgdW5vIGRlIGxvcyBncnVwb3MgcXVlIGhheWFtb3MgZGVmaW5pZG8uIEFob3JhIGxvIHZlbW9zLgoKU2ksIHBvciBlamVtcGxvLCBhZ3J1cGFtb3MgdW4gZGYgcG9yIHBhw61zZXMsIGFsIGVqZWN1dGFyIGBzdW1tYXJpc2UoKWAsIG5vcyByZXRvcm5hcsOhIHVuYSBmaWxhIGNvbiBlbCByZXN1bHRhZG8gcGFyYSBjYWRhIHBhw61zLiBFbiByZWFsaWRhZCwgcG9kZW1vcyBwZW5zYXIgcXVlIGBncm91cF9ieSgpYCBubyBoYWNlICJuYWRhIiwgcXVlIGVuIHJlYWxpZGFkIHNvbG8gY2FtYmlhIGxvIHF1ZSBoYWNlbiBsYXMgb3RyYXMgZnVuY2lvbmVzOiBhaG9yYSBsb3MgY8OhbGN1bG9zIHNlIGhhcsOhbiBwYXJhIGNhZGEgdW5vIGRlIGxvcyBncnVwb3MgcXVlIGRlZmluZSBgZ3JvdXBfYnkoKWAKCjxicj4KCi0tLS0tLS0tLS0tLS0tLS0KCkNvbW8gZGljZSBKZW5ueTogTGV04oCZcyBzdGFydCB3aXRoIHNpbXBsZSBjb3VudGluZy4gwr9DdWFudGFzIG9ic2VydmFjaW9uZXMocm93cykgdGVuZW1vcyBwb3IgY29udGluZW50ZT8KCgpgYGB7ciwgZWNobyA9IFRSVUUsIGV2YWwgPSBUUlVFfQojLSBjb2dlbW9zIGRmIHkgbG8gKGRlcylhZ3J1cGFtb3MgcG9yIGdydXBvcyBkZWZpbmlkb3MgcG9yIGxhIHZhcmlhYmxlICJjb250aW5lbnQiOyBvc2VhLCBoYWJyw6EgNSBncnVwb3MKIy0gZGVzcHXDqXMgY29uIHN1bW1hcmlzZSgpIGNhbGN1bGFyZW1vcyBlbCBuwrogZGUgb2JzZXJ2YWNpb25lcyBlbiBjYWRhIGNvbnRpbmVudGUgbyBncnVwbzsgZXMgZGVjaXIsIG5vcyByZXRvcm5hcsOhIHVuIGRmIGNvbiB1bmEgZmlsYSBwb3IgY2FkYSBjb250aW5lbnRlCmFhIDwtIGdhcG1pbmRlciAlPiUgZ3JvdXBfYnkoY29udGluZW50KSAlPiUgc3VtbWFyaXNlKE5OID0gbigpKSAKYWEKYGBgCgpFc3RvIHRhbiBzZW5jaWxsbyB0YW1iacOpbiBzZSBwdWVkZSBoYWNlciBjb24gYGNvdW50KClgCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQphYSA8LSBnYXBtaW5kZXIgJT4lIGdyb3VwX2J5KGNvbnRpbmVudCkgJT4lIGNvdW50KCkgCmFhIDwtIGdhcG1pbmRlciAlPiUgZ3JvdXBfYnkoY29udGluZW50KSAlPiUgY291bnQobmFtZSA9ICJOTiIpIAphYQpgYGAKCgo8YnI+CgrCv1kgY3VhbnRvcyBwYcOtc2VzIGhheSBlbiBsYSBiYXNlIGRlIGRhdG9zPyBQYXJhIGVzdGUgdGlwbyBkZSBjb3Nhcywgc2UgcHVlZGVuIHVzYXIgZnVuY2lvbmVzIGRlIFItYmFzZSwgcGVybyBkcGx5ciB0aWVuZSBtdWNoYXMgZnVuY2lvbmVzIGF1eGlsaWFyZXMuCgoKYGBge3IsIGVjaG8gPSBUUlVFLCBldmFsID0gVFJVRX0KIy0gY29nZW1vcyBkZiB5IGxvIGFncnVwYW1vcyBwb3IgImNvbnRpbmVudCIsIAojLSBkZXNwdcOpcyBjYWxjdWxhbW9zIDIgY29zYXM6IGVsIG7Dum1lcm8gZGUgb2JzZXJ2YWNpb25lcyBvIHJvd3MKIy0geSBlbCBuw7ptZXJvIGRlIHBhw61zZXMgZW4gY2FkYSBjb250aW5lbnRlIChOTl9jb3VudHJpZXMpCmFhIDwtIGdhcG1pbmRlciAlPiUgZ3JvdXBfYnkoY29udGluZW50KSAlPiUgIAogICAgICAgICAgc3VtbWFyaXplKE5OID0gbigpLCAKICAgICAgICAgICAgICAgICAgICBOTl9jb3VudHJpZXMgPSBuX2Rpc3RpbmN0KGNvdW50cnkpKSAKYWEKYGBgCgoKPGJyPgoKQ2FsY3VsZW1vcyBsYSBlc3BlcmFuemEgZGUgdmlkYSBtZWRpYSBwb3IgY29udGluZW50ZQoKYGBge3IsIGVjaG8gPSBUUlVFLCBldmFsID0gVFJVRX0KIy0gY29nZW1vcyBkZiB5IGxvIGFncnVwYW1vcyBwb3IgImNvbnRpbmVudCIsIGRlc3B1w6lzIGNhbGN1bGFtb3MgbGEgbWVkaWEgZGUgImxpZmVFeHAiCmFhIDwtIGdhcG1pbmRlciAlPiUgZ3JvdXBfYnkoY29udGluZW50KSAlPiUgIAogICAgICAgICAgICAgICAgICAgIHN1bW1hcml6ZShtZWFuKGxpZmVFeHApKSAKYWEKYGBgCgpHdWF1ISBIYXkgcXVlIGlyc2UgYSB2aXZpciBhIE9jZWFuw61hISEKCgo8YnI+CgpDYWxjdWxlbW9zIGxhIGVzcGVyYW56YSBkZSB2aWRhIG1lZGlhIHBvciBjb250aW5lbnRlIGVuIGVsIHByaW1lciBwZXJpb2RvICgxOTUyKQoKYGBge3IsIGVjaG8gPSBUUlVFLCBldmFsID0gRkFMU0V9CiMtIGNvZ2Vtb3MgZGYgeSBmaWx0cmFtb3MgcGFyYSBxdWVkYXJub3MgY29uIGxhcyBvYnNlcnZhY2lvbmVzIGRlIDE5NTIKIy0gZGVzcHXDqXMgbG8gYWdydXBhbW9zIHBvciAiY29udGluZW50IiwgCiMtIGRlc3B1w6lzIGNhbGN1bGFtb3MgbGEgbWVkaWEgZGUgImxpZmVFeHAiCmdhcG1pbmRlciAlPiUgZmlsdGVyKHllYXIgPT0gIjE5NTIiKSAlPiUgIAogICAgICAgICAgICAgIGdyb3VwX2J5KGNvbnRpbmVudCkgJT4lICAKICAgICAgICAgICAgICBzdW1tYXJpemUobWVhbihsaWZlRXhwKSkgCgpgYGAKCkhhYnLDrWEgc2lkbyBtZWpvciBlbiBsdWdhciBkZSBwb25lciBgZmlsdGVyKHllYXIgPT0gIjE5NTIiKWAgaGFiZXIgcHVlc3RvIGBmaWx0ZXIoeWVhciA9PSBtaW4oeWVhcikpYAoKR3VhdSEgSGFicsOtYSBxdWUgaGFiZXIgdml2aWRvIGVuIE9jZWFuw61hIChlbiAxOTUyKSEhCgo8YnI+CgpTZSBwdWVkZW4gY2FsY3VsYXIgdmFyaW9zIGVzdGFkw61zdGljb3MgYSBsYSB2ZXoKCmBgYHtyLCBlY2hvID0gVFJVRSwgZXZhbCA9IEZBTFNFfQojLSBjb2dlbW9zIGRmIHkgZmlsdHJhbW9zIChjb2dlbW9zKSBsYXMgb2JzZXJ2YWNpb25lcyBkZSAxOTUyIHkgMjAwNwojLSBhZ3J1cGFtb3MgcG9yICJjb250aW5lbnQiLCAKIy0gZGVzcHXDqXMgY2FsY3VsYW1vcyBsYSBtZWRpYSBkZSAibGlmZUV4cCIgeSBkZSAiZ2RwUGVyY2FwIgpnYXBtaW5kZXIgJT4lIGZpbHRlcih5ZWFyICVpbiUgYygxOTUyLCAyMDA3KSkgJT4lICAKICAgICAgICAgICAgIGdyb3VwX2J5KGNvbnRpbmVudCwgeWVhcikgJT4lICAKICAgICAgICAgICAgIHN1bW1hcml6ZShtZWFuKGxpZmVFeHApLCBtZWFuKGdkcFBlcmNhcCkpIApgYGAKClZhbW9zIGEgaGFjZXIgY8OhbGN1bG9zIGNhZGEgdmV6IG3DoXMgY29tcGxlam9zOgoKYGBge3IsIGVjaG8gPSBUUlVFLCBldmFsID0gRkFMU0V9CiMtIGNvZ2Vtb3MgZGYgeSBsbyBhZ3J1cGFtb3MgcG9yICJjb250aW5lbnQiIHkgInllYXIiLCAKIy0gZGVzcHXDqXMgY2FsY3VsYW1vcyBsYSBtZWRpYSBkZSAibGlmZUV4cCIgeSBkZSAiZ2RwUGVyY2FwIgpnYXBtaW5kZXIgJT4lIGZpbHRlcih5ZWFyICVpbiUgYygxOTUyLCAyMDA3KSkgJT4lCiAgICAgICAgICAgICAgZ3JvdXBfYnkoY29udGluZW50LCB5ZWFyKSAlPiUgCiAgICAgICAgICAgICAgIy0gZGVzcHXDqXMgY2FsY3VsYW1vcyBsYSBtZWRpYSBkZSAibGlmZUV4cCIgCiAgICAgICAgICAgICAgc3VtbWFyaXNlKG1lZGlhID0gbWVhbihsaWZlRXhwKSkKYGBgCgoKYGBge3IsIGVjaG8gPSBUUlVFLCBldmFsID0gRkFMU0V9CiMtIFZveSBhIGNyZWFyIHVuIG51ZXZvIGRmOiAiZ2FwbWluZGVyX2dyIiBvICJnYXBtaW5kZXIgYWdydXBhZG8iCmdhcG1pbmRlcl9nciA8LSBnYXBtaW5kZXIgJT4lIGZpbHRlcih5ZWFyICVpbiUgYygxOTUyLCAyMDA3KSkgJT4lCiAgICAgICAgICAgICAgICAgZ3JvdXBfYnkoY29udGluZW50LCB5ZWFyKSAKIy0geSBzb2JyZSAiZ2FwbWluZGVyX2dyIiBpcmVtb3MgaGFjaWVuZG8gY8OhbGN1bG9zCiAgCiMtIHNpIHF1ZXJlbW9zIGNhbGN1bGFyIGxhIG1lZGlhIGRlIHZhcmlhcyB2YXJpYWJsZXMgdGVuZW1vcyBxdWUgdXNhciBhY3Jvc3MoKQpnYXBtaW5kZXJfZ3IgJT4lIHN1bW1hcmlzZShhY3Jvc3MoYyhsaWZlRXhwLCBnZHBQZXJjYXApLCBtZWFuKSkKCiMtIHNpIHF1ZXJlbW9zIGNhbGN1bGFyIGxhIG1lZGlhIGRlIHRvZGFzIGxhcyB2YXJpYWJsZXMgbnVtw6lyaWNhcyB0ZW5lbW9zIHF1ZSB1c2FyIGFjcm9zcygpIHkgd2hlcmUoKQpnYXBtaW5kZXJfZ3IgJT4lIHN1bW1hcmlzZShhY3Jvc3Mod2hlcmUoaXMubnVtZXJpYyksIG1lYW4pKQoKIy0gc2kgcXVlcmVtb3MgY2FsY3VsYXIgbGEgbWVkaWEgeSBsYSBtZWRpYW5hLCBoYXkgcXVlIHVzYXIgbGlzdCgpCmdhcG1pbmRlcl9nciAlPiUgc3VtbWFyaXNlKGFjcm9zcyhjKGxpZmVFeHAsIGdkcFBlcmNhcCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdCAobWVkaWEgPSBtZWFuLCBtZWRpYW5hID0gbWVkaWFuKSApKQoKIy0gc2kgcG9uZW1vcyBsb3Mgbm9tYnJlcyBkZSBsb3MgYXJndW1lbnRvcyBxdWVkYXLDrWEgY29tbwpnYXBtaW5kZXJfZ3IgJT4lIHN1bW1hcmlzZShhY3Jvc3MoLmNvbHMgPSBjKGxpZmVFeHAsIGdkcFBlcmNhcCksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLmZucyA9IGxpc3QgKG1lZGlhID0gbWVhbiwgbWVkaWFuYSA9IG1lZGlhbikpKQoKIy0gYWRlbcOhcywgcG9kZW1vcyBjb250cm9sYXIgZWwgbm9tYnJlIGRlIGxhcyB2YXJpYWJsZXMgY3JlYWRhcyBjb24gZWwgYXJndW1lbnRvIC5uYW1lcwpnYXBtaW5kZXJfZ3IgJT4lIHN1bW1hcmlzZShhY3Jvc3MoYyhsaWZlRXhwLCBnZHBQZXJjYXApLCAKICAgICAgICAgICAgICAgICAgICAgICAgbGlzdCAobWVkaWEgPSBtZWFuLCBtZWRpYW5hID0gbWVkaWFuKSwgCiAgICAgICAgICAgICAgICAgICAgICAgIC5uYW1lcyA9ICJ7Zm59X3tjb2x9IikpCmBgYAoKPGJyPgoKIyMjIyBQcmVndW50YXMgZGUgdmVyZGFkCgpCdWVubywgcHVlcyB5YSBjb25vY8OpaXMgbG8gcHJpbmNpcGFsLCBsbyBiw6FzaWNvIHkgbcOhcyBpbXBvcnRhbnRlIGRlIGBkcGx5cmAsIHNvbG8gcXVlZGEgaXIgY29naWVuZG8gcHLDoWN0aWNhIHkgY29uZmlhbnphLCBhc8OtIHF1ZSBwYXJhIGVsbG8gdG9jYSBoYWNlciB1bmEgc2VyaWUgZGUgKipwcmVndW50YXMgZGUgdmVyZGFkISEqKi4gUG9yIGVqZW1wbG86IAoKMS4gIMK/ZW4gcXVlIGNvbnRpbmVudGUgaGEgYXVtZW50YWRvIG3DoXMgbGEgZXNwZXJhbnphIGRlIHZpZGEgZW4gZWwgcGVyaW9kbyAxOTUyLTIwMDc/CgoKYGBge3IsIGVjaG8gPSBUUlVFLCBldmFsID0gVFJVRX0KIy0gY29nZW1vcyBkZiB5IGxvIGFncnVwYW1vcyBwb3IgImNvbnRpbmVudCIsIGRlc3B1w6lzIGNhbGN1bGFtb3MgbGEgbWVkaWEgZGUgImxpZmVFeHAiCmdhcG1pbmRlciAlPiUgCiAgZmlsdGVyKHllYXIgJWluJSBjKDE5NTIsIDIwMDcpKSAlPiUgIAogIGdyb3VwX2J5KGNvbnRpbmVudCwgeWVhcikgJT4lIAogIHN1bW1hcml6ZShtZWRpYSA9IG1lYW4obGlmZUV4cCkpICU+JSB1bmdyb3VwKCkKYGBgCgoKQ2FzaSwgcGVybyBubyEhIFPDs2xvIGhlbW9zIGNvbnNlZ3VpZG8gdmVyIGxhIGVzcGVyYW56YSBkZSB2aWRhIHBvciBjb250aW5lbnRlIGVuIDE5NTIgeSAyMDA3LkVuIHJlYWxpZGFkIGVzdG8geWEgbG8gaGljaW1vcyBhbnRlcy4gRmFsdGEgcmVzdGFyLiAKClF1aXrDoXMgcG9kcsOtYW1vcyBjYWxjdWxhciBlbCBtw6F4aW1vLCBlbCBtw61uaW1vIHkgcmVzdGFybG9zLiBObywgcG9ycXVlIHN1cG9uZHLDrWFtb3MgcXVlICJsaWZlRXhwIiBzaWVtcHJlIGF1bWVudGEuIFZhbW9zIHF1ZSBlbCB0aWVtcG8gYXByZW1pYToKCmBgYHtyLCBlY2hvID0gVFJVRSwgZXZhbCA9IFRSVUV9CiMtIHByaW1lciBpbnRlbnRvOiBzZSBwdWVkZSBoYWNlciBkZSB1bmEgdmV6LCBwZXJvIHZhbW9zIGEgcGFydGlyIGVsIGPDs2RpZ28gZW4gMiB0cm96b3MKYWEgPC0gZ2FwbWluZGVyICU+JSBmaWx0ZXIoeWVhciAlaW4lIGMoMTk1MiwgMjAwNykpICU+JSAgCiAgZ3JvdXBfYnkoY29udGluZW50LCB5ZWFyKSAlPiUgCiAgc3VtbWFyaXplKG1lZGlhID0gbWVhbihsaWZlRXhwKSkgJT4lIHVuZ3JvdXAoKSAKCmFhMSA8LSBhYSAlPiUgZ3JvdXBfYnkoY29udGluZW50KSAlPiUgCiAgc3VtbWFyaXNlKG1pbl9sID0gbWluKG1lZGlhKSwgbWF4X2wgPSBtYXgobWVkaWEpKSAlPiUgCiAgbXV0YXRlKGRpZiA9IG1heF9sLW1pbl9sKSAlPiUgCiAgYXJyYW5nZShkZXNjKGRpZikpCgphYTEKYGBgCgpBc2lhIHNvbiBsb3MgZ2FuYWRvcmVzLiBFbiBwcm9tZWRpbywgZW4gQXPDrWEgc2UgbWVqb3LDsyBsYSBlc3BlcmFuemEgZGUgdmlkYSBlbiAyNC40IGHDsW9zIGVudHJlIDE5NTIgeSAyMDA3LgoKTG8gZGUgcmVzdGFyIGVsIG3DoXhpbW8geSBlbCBtw61uaW1vIGhhIGZ1bmNpb25hZG8sIFBFUk8gcG9kcsOtYSBubyBoYWJlcmxvIGhlY2hvIHNpIGh1Ymllc2UgaGFiaWRvIGFsZ8O6biBjb250aW5lbnRlIGVuIGVsIHF1ZSBlbiBsYSBlc3BlcmFuemEgZGUgdmlkYSwgZW4gbHVnYXIgZGUgaGFiZXIgYXVtZW50YWRvLCBodWJpZXNlIGJhamFkby4gRW50b25jZXMsIMK/Y8OzbW8gbG8gaGFjZW1vcz8KCmBgYHtyLCBlY2hvID0gVFJVRSwgZXZhbCA9IFRSVUV9CiMtIHNlZ3VuZG8gaW50ZW50bzogc2UgcHVlZGUgaGFjZXIgZGUgdW5hIHZleiwgcGVybyB2YW1vcyBhIHBhcnRpciBlbCBjw7NkaWdvIGVuIDIgdHJvem9zCmFhIDwtIGdhcG1pbmRlciAlPiUgZmlsdGVyKHllYXIgJWluJSBjKDE5NTIsIDIwMDcpKSAlPiUgIAogICAgICAgICBncm91cF9ieShjb250aW5lbnQsIHllYXIpICU+JSAKICAgICAgICAgc3VtbWFyaXplKG1lZGlhID0gbWVhbihsaWZlRXhwKSkgJT4lIHVuZ3JvdXAoKSAKCiMtIHVzYW1vcyBsYWcoKQphYTEgPC0gYWEgJT4lIGdyb3VwX2J5KGNvbnRpbmVudCkgJT4lIAogICAgICAgICAgICAgIGFycmFuZ2UoeWVhcikgJT4lCiAgICAgICAgICAgICAgbXV0YXRlKHZhcmlhY19sID0gbWVkaWEgLSBsYWcobWVkaWEpKQoKIy0gbW9zdHJhbW9zIGxvcyByZXN1bHRhZG9zCmFhMSAlPiUgZmlsdGVyKHllYXIgPT0gMjAwNykgJT4lIGFycmFuZ2UoZGVzYyh2YXJpYWNfbCkpCmBgYAoKClPDrSwgQXNpYSBlcyBsYSBnYW5hZG9yYS4gRW4gcHJvbWVkaW8sIGVuIEFzw61hIHNlIG1lam9yw7MgbGEgZXNwZXJhbnphIGRlIHZpZGEgZW4gMjQgYcOxb3MgZW50cmUgMTk1MiB5IDIwMDcuIAoKT3RyYSBmb3JtYSBkZSBvYnRlbmVyIGVsIG1pc21vIHJlc3VsdGFkbzoKCmBgYHtyfQojLSBlc3RhIHBhcnRlIGVzIGNvbcO6bgphYSA8LSBnYXBtaW5kZXIgJT4lIAogIGZpbHRlcih5ZWFyICVpbiUgYygxOTUyLCAyMDA3KSkgJT4lICAKICBncm91cF9ieShjb250aW5lbnQsIHllYXIpICU+JSAKICBzdW1tYXJpemUobWVkaWEgPSBtZWFuKGxpZmVFeHApKSAlPiUgdW5ncm91cCgpCgojLSBwZXJvIGFob3JhIHVzYW1vcyBwaXZvdF93aWRlcigpCmFhICU+JSBwaXZvdF93aWRlcihuYW1lc19mcm9tID0geWVhciwgdmFsdWVzX2Zyb20gPSBtZWRpYSkgJT4lIAogICAgIG11dGF0ZShkaWZfbCA9IGAyMDA3YCAtIGAxOTUyYCkgJT4lIAogICAgIGFycmFuZ2UoZGVzYyhkaWZfbCkpCmBgYAoKPGJyPgoKCkVsIGNodW5rIGRlIGFiYWpvLCDCv3F1w6kgaGFjZT8gwr9xdcOpIHNlIGVzdMOhIGNhbGN1bGFuZG8/OgoKCmBgYHtyLCBlY2hvID0gVFJVRSwgZXZhbCA9IFRSVUV9CmFhIDwtIGdhcG1pbmRlciAlPiUgCiAgZ3JvdXBfYnkoY29udGluZW50LCB5ZWFyKSAlPiUgCiAgc2VsZWN0KGNvbnRpbmVudCwgeWVhciwgbGlmZUV4cCkgJT4lIAogIHN1bW1hcmlzZShtZWFuX2xpZmUgPSBtZWFuKGxpZmVFeHApKSAlPiUgCiAgYXJyYW5nZSh5ZWFyKSAlPiUgCiAgbXV0YXRlKGluY3JlX21lYW5fbGlmZV8wID0gbWVhbl9saWZlIC0gZmlyc3QobWVhbl9saWZlKSkgJT4lIAogIG11dGF0ZShpbmNyZV9tZWFuX2xpZmVfdCA9IG1lYW5fbGlmZSAtIGxhZyhtZWFuX2xpZmUpKSAlPiUgCiAgYXJyYW5nZShjb250aW5lbnQpCgojLSBwb3IgZWplbXBsbyB2ZWFtb3MgZWwgcmVzdWx0YWRvIHBhcmEgRXVyb3BlCmFhICU+JSBmaWx0ZXIoY29udGluZW50ID09ICJFdXJvcGUiKQpgYGAKCgpTZWQgY29uc2NpZW50ZXMgZGUgcXVlIGxhIHNvbHVjaW9uZXMgYSB1bmEgcHJlZ3VudGEgbm8gc2FsZSBhIGxhIHByaW1lcmEsIGEgdmVjZXMgaGF5IHF1ZSBjYWxlbnRhcnNlIGVsIGNhcDoKCj4gQnJlYWsgdGhlIGNvZGUgaW50byBwaWVjZXMsIHN0YXJ0aW5nIGF0IHRoZSB0b3AsIGFuZCBpbnNwZWN0IHRoZSBpbnRlcm1lZGlhdGUgcmVzdWx0cy4gVGhhdOKAmXMgY2VydGFpbmx5IGhvdyBJIHdhcyBhYmxlIHRvIHdyaXRlIHN1Y2ggYSB0aGluZy4gVGhlc2UgY29tbWFuZHMgZG8gbm90IGxlYXAgZnVsbHkgZm9ybWVkIG91dCBvZiBhbnlvbmXigJlzIGZvcmVoZWFkIOKAkyB0aGV5IGFyZSBidWlsdCB1cCBncmFkdWFsbHksIHdpdGggbG90cyBvZiBlcnJvcnMgYW5kIHJlZmluZW1lbnRzIGFsb25nIHRoZSB3YXkuIElzIHRoZSBzdGF0ZW1lbnQgYWJvdmUgcmVhbGx5IGhhcmQgZm9yIHlvdSB0byByZWFkPyBJZiB5ZXMsIHRoZW4gYnkgYWxsIG1lYW5zIGJyZWFrIGl0IGludG8gcGllY2VzIGFuZCBtYWtlIHNvbWUgaW50ZXJtZWRpYXRlIG9iamVjdHMuIFlvdXIgY29kZSBzaG91bGQgYmUgZWFzeSB0byB3cml0ZSBhbmQgcmVhZCB3aGVuIHlvdeKAmXJlIGRvbmUuICAgIC0tLS0gSmVubnkgQnJ5YW4KCjxicj4KCk90cmFzIGN1ZXN0aW9uZXMgcXVlIHBvZGVtb3MgcmVzb2x2ZXIgY29uIGRwbHlyIHNvYnJlIGxhIGVzcGVyYW56YSBkZSB2aWRhOgoKLSDCv0PDs21vIGhhIGV2b2x1Y2lvbmFkbyBsYSBlc3BlcmFuemEgZGUgdmlkYSBlbiBTcGFpbiBsdXN0cm8gYSBsdXN0cm8/CgpgYGB7ciwgZWNobyA9IFRSVUUsIGV2YWwgPSBUUlVFfQojLSB2YXJpYWNpw7NuIGRlIGxpZmVFeHAgZW4gU3BhaW4gYcOxbyBhIGHDsW8gKGJ1ZW5vIGx1c3RybyBhIGx1c3RybykKYWEgPC0gZ2FwbWluZGVyICU+JSAKICBncm91cF9ieShjb3VudHJ5KSAlPiUgCiAgc2VsZWN0KGNvdW50cnksIHllYXIsIGxpZmVFeHApICU+JSAKICBtdXRhdGUobGlmZUV4cF9nYWluX2NhZGFfbHVzdHJvID0gbGlmZUV4cCAtIGxhZyhsaWZlRXhwKSkgJT4lIAogIGZpbHRlcihjb3VudHJ5ID09ICJTcGFpbiIgKQphYQpgYGAKCjxicj4KCi0gwr9ZIGxhIHZhcmlhY2nDs24gYWN1bXVsYWRhPyBGw6FjaWwhISBTw7NsbyB0ZW5kcsOtYW1vcyBxdWUgc3VtYXIgbyBhY3VtdWxhciBsYSB2YXJpYWJsZSAibGlmZUV4cF9nYWluX2NhZGFfbHVzdHJvIiBxdWUgaGVtb3MgZ2VuZXJhZG8gYW50ZXJpb3JtZW50ZSwgYXPDrSBxdWUgc8OzbG8gaGFicsOtYSBxdWUgYcOxYWRpciB1bmEgbGluZWEgYSBudWVzdHJvIGPDs2RpZ286CgpgYGB7ciwgZWNobyA9IFRSVUUsIGV2YWwgPSBUUlVFfQojLSBnYW5hbmNpYSBhY3VtdWxhZGEKYWEgPC0gZ2FwbWluZGVyICU+JSAKICBncm91cF9ieShjb3VudHJ5KSAlPiUgCiAgc2VsZWN0KGNvdW50cnksIHllYXIsIGxpZmVFeHApICU+JSAKICBtdXRhdGUobGlmZUV4cF9nYWluX2NhZGFfbHVzdHJvID0gbGlmZUV4cCAtIGxhZyhsaWZlRXhwKSkgJT4lIAogICMtLS0gMiBmaWxhcyBudWV2YXM6IGlmZWxzZSgpICB5IGN1bXN1bSgpCiAgbXV0YXRlKGxpZmVFeHBfZ2Fpbl9jYWRhX2x1c3RybzIgPSBpZmVsc2UoaXMubmEobGlmZUV4cF9nYWluX2NhZGFfbHVzdHJvKSwgMCwgbGlmZUV4cF9nYWluX2NhZGFfbHVzdHJvKSkgJT4lIAogIG11dGF0ZShsaWZlRXhwX2dhaW5fYWN1bXVsYWRvID0gY3Vtc3VtKGxpZmVFeHBfZ2Fpbl9jYWRhX2x1c3RybzIpKSAlPiUgICAKICBmaWx0ZXIoY291bnRyeSA9PSAiU3BhaW4iKQphYQpgYGAKCkFsIGZpbmFsIHBhcmEgaGFjZXJsbyAoY29tbyBoYWLDrWEgcGVuc2FkbykgbWUgaGFuIGhlY2hvIGZhbHRhIDIgbGluZWFzLCBwb3JxdWUgbGEgcHJpbWVyYSBvYnNlcnZhY2nDs24gZGUgImxpZmVFeHBfZ2Fpbl9jYWRhX2x1c3RybyIgZXMgdW4gTkEgeSBlc28gaGFjw61hIHF1ZSBsYSBmdW5jacOzbiBgY3Vtc3VtKClgIG5vIGZ1bmNpb25hc2UuCgo8YnI+CgotIE90cmEgZm9ybWEgZGUgaGFjZXIgbG8gbWlzbW8gc2Vyw61hIChzZSBtZSBoYSBvY3VycmlkbyBkZXNwdcOpcykuIEFkZW3DoXMgZXMgbcOhcyBmw6FjaWwKCmBgYHtyLCBlY2hvID0gVFJVRSwgZXZhbCA9IFRSVUV9CiMtIGdhbmFuY2lhIGFjdW11bGFkYSAob3RyYSBmb3JtYSBkZSBoYWNlciBsbyBtaXNtbykKYWEgPC0gZ2FwbWluZGVyICU+JSAKICBncm91cF9ieShjb3VudHJ5KSAlPiUgCiAgc2VsZWN0KGNvdW50cnksIHllYXIsIGxpZmVFeHApICU+JSAKICBtdXRhdGUobGlmZUV4cF9nYWluX2FjdW11bGFkYSA9IGxpZmVFeHAgLSBsaWZlRXhwWzFdKSAgJT4lIAogIGZpbHRlcihjb3VudHJ5ID09ICJTcGFpbiIpCmBgYAoKPGJyPgoKLSBPYnRlbmVyLCBwYXJhIGNhZGEgcGVyaW9kbywgbG9zICgzKSBwYcOtc2VzIGRlIEFzaWEgY29uIE1BWU9SIGxpZmVFeHAuIFVzYXJlbW9zIHVuYSB2YXJpYW50ZSBkZSBgc2xpY2UoKWAsIGNvbmNyw6l0YW1lbnRlIGBzbGljZV9tYXgoKWAKCgpgYGB7ciwgZWNobyA9IFRSVUUsIGV2YWwgPSBUUlVFfQphYSA8LSBnYXBtaW5kZXIgJT4lCiAgZmlsdGVyKGNvbnRpbmVudCA9PSAiQXNpYSIpICU+JQogIHNlbGVjdCh5ZWFyLCBjb3VudHJ5LCBsaWZlRXhwKSAlPiUKICBncm91cF9ieSh5ZWFyKSAlPiUKICBzbGljZV9tYXgobiA9IDMsIGxpZmVFeHApICU+JSAKICBhcnJhbmdlKHllYXIpIApgYGAKCgpQYXJhIG9idGVuZXIgbG9zIDQgcGHDrXNlcyBjb24gKipNRU5PUioqICJsaWZlRXhwIiBzw7NsbyB0ZW5kcsOtYW1vcyBxdWUgc3VzdGl0dWlyIGxhIHF1aW50YSBsaW5lYSBwb3IgYHNsaWNlX21pbihuID0gNCwgbGlmZUV4cClgCgo8YnI+CgotIE9idGVuZXIsIHBhcmEgY2FkYSBwZXJpb2RvLCBsb3MgcGHDrXNlcyBkZSBBc2lhIGNvbiBtYXlvciB5IG1lbm9yIGxpZmVFeHAuCgpgYGB7ciwgZWNobyA9IFRSVUUsIGV2YWwgPSBUUlVFfQojLSBPYnRlbmVyLCBwYXJhIGNhZGEgcGVyaW9kbywgbG9zIHBhw61zZXMgZGUgQXNpYSBjb24gbWF5b3IgeSBtZW5vciBsaWZlRXhwLgphYSA8LSBnYXBtaW5kZXIgJT4lCiAgZmlsdGVyKGNvbnRpbmVudCA9PSAiQXNpYSIpICU+JQogIHNlbGVjdCh5ZWFyLCBjb250aW5lbnQsIGNvdW50cnksIGxpZmVFeHApICU+JQogIGdyb3VwX2J5KHllYXIpICU+JQogIGZpbHRlcihtaW5fcmFuayhkZXNjKGxpZmVFeHApKSA8IDIgfCBtaW5fcmFuayhsaWZlRXhwKSA8IDIpICU+JSAKICBhcnJhbmdlKHllYXIpIApgYGAKCgpMYXMgMiDDumx0aW1hcyBmdW5jaW9uZXMgcXVlIGhlbW9zIHVzYWRvOiBgc2xpY2VfbWluKClgICB5IGBtaW5fcmFuaygpYCBzb24gZnVuY2lvbmVzIGRlIGRwbHlyIHBlcm8gbm8gc29uIHNvbiBmdW5jaW9uZXMgcHJpbmNpcGFsZXMsIGVuIGNpZXJ0YSBmb3JtYSBzb24gYXV4aWxpYXJlcy4KCjxicj4KCgpQb2TDqWlzIHZlciBsYXMgZnVuY2lvbmVzIGF1eGlsaWFyZXMgcXVlIHRpZW5lIGRwbHlyIGVuIGxhIHNlZ3VuZGEgcMOhZ2luYSBkZSBbQ0hFQVQgU0hFRVRdKGh0dHBzOi8vd3d3LnJzdHVkaW8uY29tL3Jlc291cmNlcy9jaGVhdHNoZWV0cy8pLiB0YW1iacOpbiBzZSBwdWVkZW4gdXNhciBsYXMgZnVuY2lvbmVzIGRlIFItYmFzZSBvIGRlIG90cm9zIHBhY2thZ2VzLiBBcXXDrSB0ZW7DqWlzIGFsZ3VuYXMgcG9zaWJpbGlkYWRlcyBzYWNhZGFzIGRlIHVuIFt0dXRvcmlhbCBkZSBIYWRsZXldKGh0dHBzOi8vd3d3LmRyb3Bib3guY29tL3NoL2k4cW5sdXdtdWllaWN4Yy9BQUFndDl0SUtvSW03V1pLSXlLMjVsaDZhP3ByZXZpZXc9ZHBseXItdHV0b3JpYWwucGRmKQoKCmBgYHIKVHlwZXMgb2Ygc3VtbWFyeSBmdW5jdGlvbnM6CuKAoiBtaW4oeCksIG1lZGlhbih4KSwgbWF4KHgpLCBxdWFudGlsZSh4LCBwKSAK4oCiIG4oKSwgbl9kaXN0aW5jdCgpLCBzdW0oeCksIG1lYW4oeCkgCuKAoiBzdW0oeCA+IDEwKSwgbWVhbih4ID4gMTApIArigKIgc2QoeCksIHZhcih4KSwgaXFyKHgpLCBtYWQoeCkKClR5cGVzIG9mIHdpbmRvdyBmdW5jdGlvbnM6CuKAoiBSYW5raW5nIGFuZCBvcmRlcmluZyAK4oCiIE9mZnNldHM6IGxlYWQgJiBsYWcgCuKAoiBDdW11bGF0aXZlIGFnZ3JlZ2F0ZXMK4oCiIFJvbGxpbmcgYWdncmVnYXRlcwoKRWplbXBsb3M6CuKAoiBXYXMgdGhlcmUgYSBjaGFuZ2U/ICB4ICE9IGxhZyh4KQrigKIgUGVyY2VudCBjaGFuZ2U/ICh4IC0gbGFnKHgpKSAvIHgK4oCiIEZvbGQtY2hhbmdlPyB4IC8gbGFnKHgpCuKAoiBQcmV2aW91c2x5IGZhbHNlLCBub3cgdHJ1ZT8gIWxhZyh4KSAmIHgKCuKAoiBJZiBvbmUgb2YgdGhlIHNwZWNpYWxpc2VkIHZlcmJzIGRvZXNu4oCZdCBkbyB3aGF0IHlvdSBuZWVkLCB5b3UgY2FuIHVzZSBkbygpCmBgYAoKPGJyPgoKIyMjIyMgQSB2ZXIgc2kgZW50ZW5kZWlzIGVzdGUgZWplbXBsbwoKVW5hICoqZnVuY2nDs24gYXV4aWxpYXIqKiBxdWUgZXMgKiptdXkgw7p0aWwqKiBhbCB1dGlsaXphcmxhIGp1bnRvIGEgbXV0YXRlOiBgY2FzZV93aGVuKClgLiAKCmBgYHtyLCBlY2hvID0gVFJVRSwgZXZhbCA9IEZBTFNFfQphYSA8LSBnYXBtaW5kZXIgJT4lCiAgZ3JvdXBfYnkoY29udGluZW50LCB5ZWFyKSAgJT4lCiAgbXV0YXRlKG1lZGlhX2xpZmVFeHAgPSBtZWFuKGxpZmVFeHApKSAlPiUgCiAgbXV0YXRlKG1lZGlhX2dkcFBlcmNhcCA9IG1lYW4oZ2RwUGVyY2FwKSkgJT4lIAogIG11dGF0ZShHT09EX29yX0JBRCA9IGNhc2Vfd2hlbiggCiAgICBsaWZlRXhwID4gbWVhbihsaWZlRXhwKSAmIGdkcFBlcmNhcCA+IG1lYW4oZ2RwUGVyY2FwKSAgfiAiZ29vZCIsCiAgICBsaWZlRXhwIDwgbWVhbihsaWZlRXhwKSAmIGdkcFBlcmNhcCA8IG1lYW4oZ2RwUGVyY2FwKSAgfiAiYmFkIiAsCiAgICBsaWZlRXhwIDwgbWVhbihsaWZlRXhwKSB8IGdkcFBlcmNhcCA8IG1lYW4oZ2RwUGVyY2FwKSAgfiAibWVkaXVtIgogICAgKSkgJT4lCiAgZmlsdGVyKGNvdW50cnkgPT0gIlNwYWluIikKYGBgCgo8YnI+CgojIyMjIyBNw6FzIGZ1bmNpb25lcyBhdXhpbGlhcmVzCgpIYXkgYWxndW5hcyBxdWUgbm8gcXVpZXJvIG9sdmlkYXI6CgpgYGB7ciwgZWNobyA9IFRSVUUsIGV2YWwgPSBGQUxTRX0KZHBseXI6Om50aWxlKHgsIG4pIDogY2F0ZWdvcml6ZXMgYSB2ZWN0b3Igb2YgdmFsdWVzIGludG8gIm50aWxlcyIgc3VjaCBhcyBxdWFydGlsZXMgaWYgbiA9IDQKCmRwbHlyOjpuX2Rpc3RpbmN0KHgpOiAgY291bnRzIHVuaXF1ZSB2YWx1ZXMgaW4gYSB2ZWN0b3I7IHNpbWlsYXIgYSAgbGVuZ3RoKHVuaXF1ZSh4KSkKCmRwbHlyOjpiZXR3ZWVuKHgsIGxlZnQsIHJpZ2h0KSA6IGlzIGEgc2hvcnRjdXQgZm9yIHggPj0gbGVmdCAmIHggPD0gcmlnaHQsIAoKdGliYmxlOjphZGRfcm93KCkgIDogYcOxYWRlIHJvd3MgYSB1biBkZgoKdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4oKQpgYGAKCjxicj4KCgojIyBNw6FzIGRldGFsbGVzIHNvYnJlIGRwbHlyCgpMb3MgdmVyYm9zIGBtdXRhdGUoKWAgeSBgc3VtbWFyaXNlKClgIHlhIHNhYmVtb3MgcXVlIHB1ZWRlbiBoYWNlciB1c28gZGUgZnVuY2lvbmVzIGNvbW8gYG1lYW4oKWAsIGBzZCgpYCBldGMgLi4uIFBvciBlamVtcGxvIHBvZGVtb3MgdHJhbnNmb3JtYXIgbGFzIHZhcmlhYmxlcyBudW3DqXJpY2FzIGEgbG9nYXJpdG1vczoKCmBgYHtyLCBldmFsID0gRkFMU0V9CmdhcG1pbmRlciAlPiUgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5udW1lcmljKSwgbG9nKSkgJT4lIGhlYWQobiA9IDMpCmBgYAoKU2UgcHVlZGVuIHVzYXIgdGlkeV9oZWxwZXJzIHBhcmEgc2VsZWNjaW9uYXIgbGFzIGNvbHVtbmFzIHkgYXBsaWNhciBtw6FzIGRlIHVuYSBmdW5jacOzbjoKCmBgYHtyLCBldmFsID0gRkFMU0V9CmdhcG1pbmRlciAlPiUgbXV0YXRlKGFjcm9zcyhjKHN0YXJ0c193aXRoKCJsaWZlIiksIGNvbnRhaW5zKCJwbyIpKSwgLmZucyA9ICBtZWFuKSkgJT4lIGhlYWQKYGBgCgpvIGNhbGN1bGFyIGxhIG1lZGlhIGRlIGxhcyB2YXJpYWJsZSBudW3DqXJpY2FzIHkgY29udHJvbGFyIGVsIG5vbWJyZSBkZSBsYXMgdmFyaWFibGVzIGNyZWFkYXM6CgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpnYXBtaW5kZXIgJT4lIGdyb3VwX2J5KGNvbnRpbmVudCkgJT4lCiAgc3VtbWFyaXplKGFjcm9zcyh3aGVyZShpcy5udW1lcmljKSwgbWVhbiwgLm5hbWVzID0gIm1lYW5fe2NvbH0iKSkgJT4lIGhlYWQobiA9IDMpCmBgYAoKU2kgcXVpZXJlcyBzZWxlY2Npb25hciB0b2RhcyBsYXMgdmFyaWFibGVzIHVzYSBgZXZlcnl0aGluZygpYAoKCmBgYHtyLCBldmFsID0gRkFMU0V9CmdhcG1pbmRlciAlPiUgZ3JvdXBfYnkoY29udGluZW50KSAlPiUKICBzdW1tYXJpemUoYWNyb3NzKGV2ZXJ5dGhpbmcoKSwgYXMuY2hhcmFjdGVyLCAubmFtZXMgPSAiQ0hBUl97Y29sfSIpKSAlPiUgaGVhZChuID0gMykKYGBgCgpQZXJvIHZhbW9zIGEgdmVyIG90cmFzIHBvc2liaWxpZGFkZXM6Cgo8YnI+CgojIyMgbcOhcyBkZSB1bmEgY29uZGljacOzbiBwYXJhIHNlbGVjY2lvbmFyIGxhcyBjb2x1bW5hczoKCmBgYHtyfQpnYXBtaW5kZXIgJT4lIG11dGF0ZShhY3Jvc3Mod2hlcmUoaXMuZG91YmxlKSAmIGVuZHNfd2l0aCgiY2FwIiksIGFzLmludGVnZXIpKSAlPiUgaGVhZChuID0gMykKYGBgCgo8YnI+CgojIyMgdXNvIGRlIGZ1bmNpb25lcyBwcm9waWFzICghISEhKQoKVGVuZW1vcyB2YXJpYXMgcG9zaWJpbGlkYWRlczoKCi0gMS4gZGVmaW5pZW5kbyBwcmltZXJvIGxhIGZ1bmNpw7NuOgoKCmBgYHtyfQpkaXZpZGlyXzEwMCA8LSBmdW5jdGlvbih4KSB7eCAvIDEwMH0gICMtIGRlZmlubyB1bmEgZnVuY2nDs24KCmdhcG1pbmRlciAlPiUgbXV0YXRlKGFjcm9zcyh3aGVyZShpcy5udW1lcmljKSwgLmZucyA9IGRpdmlkaXJfMTAwKSkgJT4lIGhlYWQKYGBgCgotIDIuIHVzYW5kbyBmb3JtdWxhcyBhbsOzbmltYXMKCmBgYHtyfQpnYXBtaW5kZXIgJT4lIG11dGF0ZShhY3Jvc3Mod2hlcmUoaXMubnVtZXJpYyksIC5mbnMgPSBmdW5jdGlvbih4KSB7eCAvIDEwMH0pKSAlPiUgaGVhZApgYGAKCi0gMy4gdXNhbmRvIGbDs3JtdWxhcwoKYGBge3J9CiMtIGNvbiBmb3JtdWxhcwpnYXBtaW5kZXIgJT4lIG11dGF0ZShhY3Jvc3Mod2hlcmUoaXMubnVtZXJpYyksIC5mbnMgPSB+IC54LzEwMCkpICU+JSBoZWFkCgpnYXBtaW5kZXIgJT4lIG11dGF0ZShhY3Jvc3Mod2hlcmUoaXMubnVtZXJpYyksIC5mbnMgPSB+IHsxL3NxcnQoLil9KSkgJT4lIGhlYWQKYGBgCgp1c2FyIGZvcm11bGFzIGZhY2lsaXRhIGVsIHVzbyBkZSBhcmd1bWVudG9zIGRlbnRybyBkZSBsYSBmdW5jacOzbjoKCmBgYHtyLCBldmFsID0gRkFMU0V9CmdhcG1pbmRlciAlPiUgCiAgZ3JvdXBfYnkoY29udGluZW50LCB5ZWFyKSAlPiUgCiAgc3VtbWFyaXNlKGFjcm9zcyhjKCJsaWZlRXhwIiwgImdkcFBlcmNhcCIpLCAKCQkJCSAgIGxpc3QobWVhbiA9IH4gbWVhbigueCwgbmEucm0gPSBUUlVFLCB0cmltID0gMC4xKSkpKQpgYGAKCgojIyMgY3JlYXIgaW5kaWNlcyBkZSBncnVwbwoKQSB2ZWNlcyBjdWFuZG8gdHJhYmFqYXMgY29uIGRmIGFncnVwYWRvcyBlcyDDunRpbCBzYWJlciBhIHF1ZSBncnVwbyBwZXJ0ZW5lY2UgY2FkYSBvYnNlcnZhY2nDs24uIFB1ZWRlcyBoYWNlcmxvIGbDoWNpbG1lbnRlIGNvbjogCgoKYGBge3IsIGV2YWwgPSBGQUxTRX0KZ2FwbWluZGVyICU+JSBncm91cF9ieSh5ZWFyKSAgJT4lIG11dGF0ZShpZF9ncnVwbyA9IGN1cl9ncm91cF9pZCgpKSAKYGBgCgoKCgo8YnI+PGJyPgoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIDUuIENvbWJpbmFuZG8gKGpvaW5pbmcpIGRmJ3MKCjxicj4KCk9LLCB5YSBzYWJlbW9zIG1hbmVqYXIvZmlsdHJhciBldGMuLi4gdW4gY29uanVudG8gZGUgZGF0b3MsIFBFUk8gbXVjaGFzIHZlY2VzIGxvIHF1ZSBoYXkgcXVlIGhhY2VyIGVzIHVuaXIgbyAqKmNvbWJpbmFyIHZhcmlhcyB0YWJsYXMqKiBvIGNvbmp1bnRvcyBkZSBkYXRvcyAoSm9pbmluZ3MgZW4gaW5nbMOpcykuIAoKClZhbW9zIGEgYXByZW5kZXIgY29tbyBoYWNlcmxvIHVzYW5kbyBkcGx5ci4gW1tBcXXDrV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2RwbHlyL3ZpZ25ldHRlcy90d28tdGFibGUuaHRtbCldIHRlbsOpaXMgbGEgdmlnbmV0dGUgZGUgZHBseXIgcGFyYSAidHdvIHRhYmxlIHZlcmJzIiwgdGFtYmnDqW4gcG9kw6lpcyB2ZXIgdW4gdsOtZGVvIG11eSBpbHVzdHJhdGl2byBbW2FxdcOtXShodHRwczovL3d3dy55b3V0dWJlLmNvbS93YXRjaD92PVFIWHBzTVNEc3pnICldLiBUYW1iacOpbiBwb2TDqWlzIHVzYXIgZWwgW3R1dG9yaWFsIGRlIEplbm55XShodHRwOi8vc3RhdDU0NS5jb20vYml0MDAxX2RwbHlyLWNoZWF0c2hlZXQuaHRtbCkuIExhIFtDSEVBVCBTSEVFVF0oaHR0cHM6Ly93d3cucnN0dWRpby5jb20vcmVzb3VyY2VzL2NoZWF0c2hlZXRzLykgZXMgbXV5LW11eSBidWVuYS4KCgpMb3MgZWplbXBsb3MgZSBpbcOhZ2VuZXMgdXNhZG9zIGVuIGVzdGEgc2VjY2nDs24gc2UgYmFzYW4gZW4gbG9zIGNyZWFkb3MgcG9yIFtNYXJhIEF2ZXJpY2sgKFxAZGF0YWFuZG1lKV0oaHR0cHM6Ly90d2l0dGVyLmNvbS9kYXRhYW5kbWU/bGFuZz1lcykgZW4gW2VzdGUgcmVwb10oaHR0cHM6Ly9naXRodWIuY29tL2JhdHBpZ2FuZG1lL3RpZHlleHBsYWluL3RyZWUvcGl2b3QpLCBxdWUgYSBzdSB2ZXogc2UgYmFzYXJvbiBlbiBsYSBpZGVhIGRlIFtHYXJyaWNrIEFkZW4tQnVpZSAgXEBncnJyY2tdKGh0dHBzOi8vdHdpdHRlci5jb20vZ3JycmNrKQoKCjxicj4KCiMjIChkb3MpIGNhc29zIHNlbmNpbGxvcwoKIyMjIyBEb3MgY2Fzb3MgaWRlYWxlcyAoc2VuY2lsbG9zIGRlIHVuaXIpOiBgYmluZF9jb2xzKClgIHkgYGJpbmRfcm93cygpYAoKMS4gU2kgbG9zIDIgZGZzIHRpZW5lbiAqKmV4YWN0YW1lbnRlIGxhcyBtaXNtYXMgZmlsYXMqKiBvIHVuaWRhZGVzIGRlIGFuw6FsaXNpcyAoIHkgYWRlbWFzIGVuIGVsIG1pc21vIG9yZGVuKS4gRW4gZXN0ZSBjYXNvLCBzb2xvIGhhYnLDrWEgcXVlIGp1bnRhciBlbiB1bmEgbWlzbWEgdGFibGEgbGFzIGNvbHVtbmFzIGRlIGRmMSB5IGRlIGRmMi4gRXN0byBsbyBwb2RlbW9zIGhhY2VyIGNvbiBgYmluZF9jb2xzKClgIChvIGNvbiAqKmMqKmJpbmQoKSBkZSBSLWJhc2UpLgoKCmBgYHtyLCBldmFsID0gRkFMU0V9CmRmXzEgPC0gaXJpc1sgLCAxOjJdICA7IGRmXzIgPC0gaXJpc1sgLCAzOjVdCgpkZl8xIDwtIGlyaXMgJT4lIHNlbGVjdCgxOjIpICA7IGRmXzIgPC0gaXJpcyAlPiUgc2VsZWN0KDM6NSkgCgpkZl8zIDwtIGBiaW5kX2NvbHNgKGRmXzEsIGRmXzIpCgppZGVudGljYWwoaXJpcywgZGZfMykKYGBgCgoKMi4gU2kgbG9zIDIgZGZzIHRpZW5lbiAqKmV4YWN0YW1lbnRlIGxhcyBtaXNtYXMgY29sdW1uYXMqKiAoIHkgYWRlbWFzIGVuIGVsIG1pc21vIG9yZGVuKS4gRW4gZXN0ZSBjYXNvLCBzZSB0cmF0YXLDrWEgc2ltcGxlbWVudGUgZGUganVudGFyIHRvZGFzIGxhcyBvYnNlcnZhY2lvbmVzIG8gZmlsYXMgZGUgbG9zIDIgZGYncy4gRXN0byBsbyBwb2RlbW9zIGhhY2VyIGNvbiBgYmluZF9yb3dzKClgIChvIGNvbiAqKnIqKmJpbmQoKSBkZSBSLWJhc2UpCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpkZl8xIDwtIGlyaXNbMTo3NSwgXSAgOyBkZl8yIDwtIGlyaXNbNzY6MTUwLCBdCgpkZl8xIDwtIGlyaXMgJT4lIHNsaWNlKDE6NzUpICA7IGRmXzIgPC0gaXJpcyAlPiUgc2xpY2UoNzY6MTUwKSAKCmRmXzMgPC0gYGJpbmRfcm93c2AoZGZfMSwgZGZfMikKCmlkZW50aWNhbChpcmlzLCBkZl8zKQpgYGAKCgo8YnI+CgojIyMjIE9sdmlkYW5kbyB5YSBsb3MgMiBjYXNvcyBpZGVhbGVzIHkgc2VuY2lsbG9zCgo8YnI+CgpFbiBkcGx5ciBoYXkgMyB0aXBvcyBkZSBmdW5jaW9uZXModmVyYm9zKSBxdWUgc2Ugb2N1cGFuIGRlIGRpZmVyZW50ZXMgb3BlcmFjaW9uZXMgcGFyYSB1bmlyIGRhdGFzZXRzOgoKICAtICoqTXV0YXRpbmcgam9pbnMqKiwgYcOxYWRlIG51ZXZhcyB2YXJpYWJsZXMgKG8gY29sdW1uYXMpIGEgdW4gZGF0YWZyYW1lIChkZjEpLiBFc3RhcyBudWV2YXMgY29sdW1uYXMgdmllbmVuIGRlIHVuIHNlZ3VuZG8gZGYyIChoYXkgdmFyaWFzIG11dGF0aW5nIGpvaW5zLCBkZXBlbmRpZW5kbyBkZWwgY3JpdGVyaW8gcGFyYSBzZWxlY2Npb25hciBsYXMgZmlsYXMpCgogIC0gKipGaWx0ZXJpbmcgam9pbnMqKiwgZmlsdHJhIGxhcyBmaWxhcyAob2JzZXJ2YWNpb25lcykgZGUgdW4gZGF0YWZyYW1lIChkZjEpIGJhc8OhbmRvc2UgZW4gc8OtIGxhcyBmaWxhcyBkZSBkZjEgY29pbmNpZGVuIChtYXRjaCkgbyBubyBjb24gdW5hIG9ic2VydmFjacOzbiBkZWwgc2VndW5kbyBkZjIKCiAgLSAqKlNldCBvcGVyYXRpb25zKiosIGNvbWJpbmEgbGFzIG9ic2VydmFjaW9uZXMgZGUgbG9zIGRvcyBkYXRhc2V0cyAoZGYxIHkgZGYyKSBhcyBpZiB0aGV5IHdlcmUgc2V0IGVsZW1lbnRzLgoKPGJyPgoKVG9kYXMgZXN0YXMgZnVuY2lvbmVzIHRpZW5lbiB1bmEgKiplc3RydWN0dXJhIHNpbWlsYXIqKjogc3VzIGRvcyBwcmltZXJvcyBhcmd1bWVudG9zIHNvbiAyIGRmJ3MgKGVuIHJlYWxpZGFkIHRhYmxhcyBkZSBkYXRvcyk6IGRmMSB5IGRmMi4gRWwgb3V0cHV0IGRlIGxhIGZ1bmNpw7NuIGVzIHNpZW1wcmUgdW5hIG51ZXZhIHRhYmxhIChkZWwgbWlzbW8gdGlwbyBxdWUgZGYxKS4KCgo8YnI+CgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgojIyBNdXRhdGluZyBqb2lucwoKSGF5ICoqNCB0aXBvcyBkZSBtdXRhdGluZyBqb2lucyoqLiBTdSBzaW50YXhpcyBlcyBpZMOpbnRpY2EsIHPDs2xvIHNlIGRpZmVyZW5jaWFuIGVuIHF1ZSBsYXMgZmlsYXMgcXVlIHNlIHNlbGVjY2lvbmFuIGRlcGVuZGVuIGRlbCBjcml0ZXJpbyBwYXJhIGhhY2VyIGVsIG1hdGNoOgoKICAtIGBpbm5lcl9qb2luKGRmMSxkZjIpYDogUmV0b3JuYSB0b2RhcyBsYXMgY29sdW1uYXMgZGUgZGYxIHkgdGFtYmnDqW4gbGFzIGRlIGRmMiwgUEVSTyAqKnNvbG8gcmV0b3JuYSBsYXMgZmlsYXMgZGUgZGYxIHF1ZSB0aWVuZW4gdW5hIGVxdWl2YWxlbmNpYSBlbiBkZjIqKi4gKGxhIGVxdWl2YWxlbmNpYSBzZSBkZWZpbmUgZW4gZnVuY2nDs24gZGVsIHZhbG9yIGRlIHVuYSB2YXJpYWJsZSBvIHZhcmlhYmxlcyBjb211bmVzIGVuIGRmMSB5IGRmMikKICAKICAtIGBsZWZ0X2pvaW4oZGYxLGRmMilgOiBSZXRvcm5hIHRvZGFzIGxhcyBjb2x1bW5hcyBkZSBkZjEgeSB0YW1iacOpbiBsYXMgZGUgZGYyOyBlbiBjdWFudG8gYSBsYXMgZmlsYXMsICoqcmV0b3JuYSBUT0RBUyBsYXMgZmlsYXMgZGUgZGYxKiouIChTaSBodWJpZXNlbiB2YXJpb3MgbWF0Y2hlcyBlbnRyZSBkZjEgZSBkZjIgc2UgcmV0b3JuYW4gdG9kYXMgbGFzIGNvbWJpbmFjaW9uZXMhISEhKQogIAogLSBgcmlndGhfam9pbihkZjEsZGYyKWA6IFJldG9ybmEgdG9kYXMgbGFzIGNvbHVtbmFzIGRlIGRmMSB5IHRhbWJpw6luIGxhcyBkZSBkZjI7IGVuIGN1YW50byBhIGxhcyBmaWxhcywgKipyZXRvcm5hIFRPREFTIGxhcyBmaWxhcyBkZSBkZjIqKi4gRGUgKipkZjIqKiEhIChTaSBodWJpZXNlbiB2YXJpb3MgbWF0Y2hlcyBlbnRyZSBkZjEgeSBkZjIgc2UgcmV0b3JuYW4gdG9kYXMgbGFzIGNvbWJpbmFjaW9uZXMhISEhKSAgCiAgCgogLSBgZnVsbF9qb2luKGRmMSxkZjIpYDogUmV0b3JuYSB0b2RhcyBsYXMgY29sdW1uYXMgZGUgZGYxIHkgdGFtYmnDqW4gbGFzIGRlIGRmMjsgZW4gY3VhbnRvIGEgbGFzIGZpbGFzLCAqKnJldG9ybmEgVE9EQVMgbGFzIGZpbGFzIGRlIGRmMSB5IGRlIGRmMioqLiBPc2VhLCByZXRvcm5hIFRPREFTIGxhcyBmaWxhcyB5IFRPREFTIGxhcyBjb2x1bW5hcyBkZSBsYXMgMiB0YWJsYXMuIChEb25kZSBubyBoYXkgbWF0Y2hlcyByZXRvcm5hIE5BJ3MpCiAgCiAgICAKPGJyPgoKIyMjIyBFamVtcGxvcyBkZSBtdXRhdGluZyBqb2lucyAKClNlYW4gbG9zIHNpZ3VpZW50ZXMgMiBkYXRhZnJhbWVzICh0aWJibGVzKToKCmBgYHtyfQp4IDwtIHRpYmJsZShpZCA9IDE6MywgeCA9IHBhc3RlMCgieCIsIDE6MykpCgp5IDwtIHRpYmJsZShpZCA9ICgxOjQpWy0zXSwgeSA9IHBhc3RlMCgieSIsICgxOjQpWy0zXSkpCmBgYAoKCjxicj4KCiMjIyMjIElubmVyIEpvaW4KCmBgYHtyLCBlY2hvID0gVFJVRSwgZXZhbCA9IFRSVUV9CiMtIG9ubHkgaW5jbHVkZXMgb2JzZXJ2YXRpb25zIHRoYXQgbWF0Y2ggaW4gYm90aCB4IGFuZCB5CmRmX2lubmVyIDwtIGlubmVyX2pvaW4oeCwgeSkKYGBgCgoKYGBge3IgLCBlY2hvPUZBTFNFLCBldmFsID0gVFJVRSwgZmlnLmFzcCA9IDQvMiwgb3V0LndpZHRoID0gIjc1JSIsIGZpZy5hbGlnbiA9ICJjZW50ZXIifQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhoZXJlOjpoZXJlKCJpbWFnZW5lcyIsICJ0dF8wNV9pbWdfMDZfaW5uZXItam9pbi5wbmciKSkKYGBgCgoKPGJyPgoKIyMjIyMgTGVmdCBKb2luCgoKYGBge3IsIGVjaG8gPSBUUlVFLCBldmFsID0gVFJVRX0KIy0gaW5jbHVkZXMgYWxsIG9ic2VydmF0aW9ucyBpbiB4LCByZWdhcmRsZXNzIG9mIHdoZXRoZXIgdGhleSBtYXRjaCBvciBub3QuIAojLSBUaGlzIGlzIHRoZSBtb3N0IGNvbW1vbmx5IHVzZWQgam9pbiBiZWNhdXNlIGl0IGVuc3VyZXMgdGhhdCB5b3UgZG9u4oCZdCBsb3NlIG9ic2VydmF0aW9ucyBmcm9tIHlvdXIgcHJpbWFyeSB0YWJsZS4KZGZfbGVmdF9qb2luIDwtIGxlZnRfam9pbih4LCB5KQpgYGAKCgpgYGB7ciAsIGVjaG89RkFMU0UsIGV2YWwgPSBUUlVFLCBmaWcuYXNwID0gNC8yLCBvdXQud2lkdGggPSAiNzUlIiwgZmlnLmFsaWduID0gImNlbnRlciJ9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGhlcmU6OmhlcmUoImltYWdlbmVzIiwgInR0XzA1X2ltZ18wN19sZWZ0LWpvaW4ucG5nIikpCmBgYAoKCgo8YnI+CgojIyMjIyBSaWdodCBKb2ludAoKYGBge3IsIGVjaG8gPSBUUlVFLCBldmFsID0gVFJVRX0KIy0gaW5jbHVkZXMgYWxsIG9ic2VydmF0aW9ucyBpbiB5LiAKIy0gSXTigJlzIGVxdWl2YWxlbnQgdG8gbGVmdF9qb2luKHksIHgpLCBidXQgdGhlIGNvbHVtbnMgd2lsbCBiZSBvcmRlcmVkIGRpZmZlcmVudGx5LgpkZl9yaWdodF9qb2luIDwtIHJpZ2h0X2pvaW4oeCwgeSkKYGBgCgoKYGBge3IgLCBlY2hvPUZBTFNFLCBldmFsID0gVFJVRSwgZmlnLmFzcCA9IDQvMiwgb3V0LndpZHRoID0gIjc1JSIsIGZpZy5hbGlnbiA9ICJjZW50ZXIifQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhoZXJlOjpoZXJlKCJpbWFnZW5lcyIsICJ0dF8wNV9pbWdfMDhfcmlndGgtam9pbi5wbmciKSkKYGBgCgo8YnI+CgojIyMjIyBGdWxsIEpvaW50CgoKYGBge3IsIGVjaG8gPSBUUlVFLCBldmFsID0gVFJVRX0KIy0gZnVsbF9qb2luKCkgaW5jbHVkZXMgYWxsIG9ic2VydmF0aW9ucyBmcm9tIHggYW5kIHkKZGZfZnVsbF9qb2luIDwtIGZ1bGxfam9pbih4LCB5KQpgYGAKCjxicj4KCgpgYGB7ciAsIGVjaG89RkFMU0UsIGV2YWwgPSBUUlVFLCBmaWcuYXNwID0gNC8yLCBvdXQud2lkdGggPSAiNzUlIiwgZmlnLmFsaWduID0gImNlbnRlciJ9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGhlcmU6OmhlcmUoImltYWdlbmVzIiwgInR0XzA1X2ltZ18wOV9mdWxsLWpvaW4ucG5nIikpCmBgYAoKCiMjIyMjIDIgcHJlY2lzaW9uZXMgc29icmUgbGFzIG11dGF0aW5nIGpvaW5zCgpMYXMgKGxlZnQsIHJpZ2h0IGFuZCBmdWxsKSBqb2lucyBzZSBsbGFtYW4gY29sZWN0aXZhbWVudGUgY29tbyAib3V0ZXIgam9pbnMiLiBDdWFuZG8gdW5hIGZpbGEgbm8gdGllbmUgbWF0Y2ggZW4gdW5hIG91dGVyIGpvaW4sIGxhcyBudWV2YXMgdmFyaWFibGVzIHF1ZSBzZSBjcmVhbiBzZSBsbGVuYW4gY29uIE5BJ3MuCgpMYXMgbXV0YXRpbmcgam9pbnMgc2UgdXNhbiBwcmluY2lwYWxtZW50ZSBwYXJhIGHDsWFkaXIgY29sdW1uYXMsICoqUEVSTyoqIGVuIGVsIHByb2Nlc28gcHVlZGVuIGdlbmVyYXJzZSBudWV2YXMgZmlsYXM6IHNpIHVuIG1hdGNoIG5vIGVzIMO6bmljbywgc2UgYcOxYWRpcsOhbiB0b2RhcyBsYXMgY29tYmluYWNpb25lcyBwb3NpYmxlcyAoZWwgcHJvZHVjdG8gY2FydGVzaWFubykgZGUgbGFzIG1hdGNoaW5nIG9ic2VydmF0aW9ucy4gVmVhbW9zIHVuIGVqZW1wbG8gY29uIHVuYSBsZWZ0X2pvaW46CgpgYGB7cn0KeCA8LSB0aWJibGUoaWQgPSAxOjMsIHggPSBwYXN0ZTAoIngiLCAxOjMpKQoKeSA8LSB0aWJibGUoaWQgPSBjKDE6NCwyKVstM10sIHkgPSBwYXN0ZTAoInkiLCBjKDE6NSlbLTNdKSkKYGBgCjxicj4KCiMjIyMjIGxlZnRfam9pbigpIGVuIGxhIHF1ZSBzZSBjcmVhbiBudWV2YXMgZmlsYXMKCmBgYHtyLCBlY2hvID0gVFJVRSwgZXZhbCA9IFRSVUV9CmRmX2xlZnRfam9pbiA8LSBsZWZ0X2pvaW4oeCwgeSkKYGBgCgoKYGBge3IgLCBlY2hvPUZBTFNFLCBldmFsID0gVFJVRSwgZmlnLmFzcCA9IDQvMiwgb3V0LndpZHRoID0gIjc1JSIsIGZpZy5hbGlnbiA9ICJjZW50ZXIifQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhoZXJlOjpoZXJlKCJpbWFnZW5lcyIsICJ0dF8wNV9pbWdfMTBfbGVmdC1qb2luLWV4dHJhLnBuZyIpKQpgYGAKCgo8YnI+CgojIyMjIEltcG9ydGFudGUgwr9Dw7NtbyBkZWNpciBhIGxhcyBmdW5jaW9uZXMgbGEgY29sdW1uYXMgKG8gY29sdW1uYXMpIHF1ZSBzZSB1c2Fyw6FuIHBhcmEgaGFjZXIgbG9zIG1hdGNoaW5nPwoKUG9kZW1vcyhERUJFTU9TKSBlbGVnaXIgbGFzIGNvbHVtbmFzIChvIHZhcmlhYmxlcykgcXVlIG5vcyBzZXJ2aXLDoW4gcGFyYSB1bmlyIGxvcyAyIGRmJ3MuIEVzdGFzIGNvbHVtbmFzIHF1ZSBzZSB1c2FuIHBhcmEgcGFyYSBoYWxsYXIgbG9zIG1hdGNoaW5ncyB5IHF1ZSBwb3IgdGFudG8gbm9zIHBlcm1pdGVuIGZ1c2lvbmFyIGxvcyAyIGRmJ3Mgc2UgbGxhbWFuICJrZXlzIi4gCgpMYSBvcGNpw7NuIGRlIGxhcyBmdW5jaW9uZXMgcGFyYSBzZWxlY2Npb25hciBlc3RhcyBjb2x1bW5hcyAia2V5cyIgZXMgYGJ5ID1gLiAKCiAgLSBzaSBwb25lbW9zIGBsZWZ0X2pvaW4oZGYxLCBkZjIsIGJ5ID0gIlgxIilgIHNlIGhhcsOhIHVuYSBsZWZ0X2pvaW4gc2llbmRvIGxhIHZhcmlhYmxlICJYMSIgbGEgcXVlIGhhcsOhIGRlIGtleS4gU2kgbGFzIHZhcmlhYmxlcyBrZXkgbm8gc2UgbGxhbWFzZW4gaWd1YWwgZW4gbG9zIDIgZGYncyBzaWVtcHJlIHBvZGVtb3MgcmVub21icmFybGFzIG8gaGFjZXIgbG8gc2lndWllbnRlOiBgbGVmdF9qb2luKGRmMSwgZGYyLCBieSA9IGMoIlgxIiA9ICJENCIpYAogIAogIC0gIFRhbWJpw6luIHNlIHB1ZWRlbiBmdXNpb25hciB0YWJsYXMgdXNhbmRvIGRvcyBrZXlzOyBwb3IgZWplbXBsbzogIGBsZWZ0X2pvaW4oZGYxLCBkZjIsIGJ5ID0gYygiWDEiLCAiWDIiKSlgIC4gU2kgbm8gc2UgbGxhbWFzZW4gaWd1YWwgbGFzIHZhcmlhYmxlcyBlbiBkZjEgeSBkZjIgaGFyw61hbW9zIGBsZWZ0X2pvaW4oZGYxLCBkZjIsIGJ5ID0gYygiWDEiID0gIkQ0IiwgIlgyIiA9ICJENyIpKWAKCjxicj4KPGJyPgoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKIyMgRmlsdGVyaW5nIGpvaW5zCgoKRmlsdGVyaW5nIGpvaW5zIHNvbiBzaW1pbGFyZXMgYSBsb3MgYW50ZXJpb3JlcyAoTXV0YXRpbmcgam9pbnMpOyBvIHNlYSwgaGFjZW4gbWFjaHRpbmcgY29uIGxhcyBmaWxhcyBkZSBsYSBtaXNtYSBtYW5lcmEsICoqUEVSTyBhZmVjdGFuIGEgbGFzIGZpbGFzKiosIE5PIGEgbGFzIGNvbHVtbmFzLiBIYXkgZmlsdGVyaW5nICBqb2lucyBkZSAyIHRpcG9zOgoKICAtIGBzZW1pX2pvaW4oZGYxLGRmMilgOiByZXRvcm5hIGxhcyBvYnNlcnZhY2lvbmVzIGRlIGRmMSBxdWUgdGllbmVuIHVuIG1hdGNoIGVuIGRmMi4gRW4gY3VhbnRvIGEgbGFzIGNvbHVtbmFzIHPDs2xvIHJldG9ybmEgbGFzIGNvbHVtbmFzIGRlIGRmMQogIC0gYGFudGlfam9pbihkZjEsZGYyKWA6IHJldG9ybmEgbGFzIG9ic2VydmFjaW9uZXMgZGUgZGYxIHF1ZSBOTyB0aWVuZW4gdW4gbWF0Y2ggZW4gZGYyOyBvc2VhLCBxdWl0YSBsYXMgb2JzZXJ2YWNpb25lcyBjb24gbWF0Y2guIEVuIGN1YW50byBhIGxhcyBjb2x1bW5hcyBzw7NsbyByZXRvcm5hIGxhcyBjb2x1bW5hcyBkZSBkZjEKCgpMYSBzZW1pX2pvaW4gc2UgZGlmZXJlbmNpYSBkZSBsYSBpbm5lcl9qb2luIGVuIHF1ZSBsYSBpbm5lcl9qb2luIHNvbG8gcmV0b3JuYSB1bmEgZmlsYSBkZSBkZjEgcG9yIGNhZGEgbWF0Y2hpbmcsIG1pZW50cmFzIHF1ZSAqKmxhIHNlbWlfam9pbiBOVU5DQSBkdXBsaWNhIGZpbGFzIGRlIGRmMSoqCgoKTGFzIGZpbHRlcmluZyBqb2lucyBzb24gw7p0aWxlcyBwYXJhIGRpYWdub3N0aWNhciBtaXNtYXRjaGVzLgpTaSBxdWllcmVzIHNhYmVyIHNvYnJlIGxvcyBtYXRjaGVzLCBoYXogdW5hIHNlbWlfam9pbigpIG9yIGFudGlfam9pbigpLiBzZW1pX2pvaW4oKSBhbmQgYW50aV9qb2luKCkgTlVOQ0EgZHVwbGljYW4gZmlsYXM7IHNvbG8gcHVlZGVuIHF1aXRhciBmaWxhcy4KICAgIAogICAKCjxicj4KCiMjIyMjIHNlbWlfam9pbgoKYGBge3IsIGVjaG8gPSBUUlVFLCBldmFsID0gVFJVRX0KZGZfc2VtaV9qb2luIDwtICBzZW1pX2pvaW4oeCwgeSwgYnkgPSAiaWQiKQpgYGAKCgpgYGB7ciAsIGVjaG89RkFMU0UsIGV2YWwgPSBUUlVFLCBmaWcuYXNwID0gNC8yLCBvdXQud2lkdGggPSAiNzUlIiwgZmlnLmFsaWduID0gImNlbnRlciJ9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGhlcmU6OmhlcmUoImltYWdlbmVzIiwgInR0XzA1X2ltZ18xMV9zZW1pLWpvaW4ucG5nIikpCmBgYAoKCjxicj4KCiMjIyMjIGFudGlfam9pbgoKYGBge3IsIGVjaG8gPSBUUlVFLCBldmFsID0gVFJVRX0KZGZfYW50aV9qb2luIDwtICBhbnRpX2pvaW4oeCwgeSwgYnkgPSAiaWQiKQpgYGAKCgpgYGB7ciAsIGVjaG89RkFMU0UsIGV2YWwgPSBUUlVFLCBmaWcuYXNwID0gNC8yLCBvdXQud2lkdGggPSAiNzUlIiwgZmlnLmFsaWduID0gImNlbnRlciJ9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGhlcmU6OmhlcmUoImltYWdlbmVzIiwgInR0XzA1X2ltZ18xMl9hbnRpLWpvaW4ucG5nIikpCmBgYAoKCjxicj4KCgojIyMjIyBjb21wYXJlbW9zIGxhIHNlbWlfam9pbiBjb24gbGEgaW5uZXJfam9pbgoKYGBge3IsIGVjaG8gPSBUUlVFLCBldmFsID0gVFJVRX0KZGZfaW5uZXIgPC0gIGlubmVyX2pvaW4oeCwgeSwgYnkgPSAiaWQiKQpkZl9zZW1pX2pvaW4gPC0gIHNlbWlfam9pbih4LCB5LCBieSA9ICJpZCIpCmBgYAoKCmBgYHtyICwgZWNobz1GQUxTRSwgZXZhbCA9IFRSVUUsIGZpZy5hc3AgPSA0LzIsIG91dC53aWR0aCA9ICI3NSUiLCBmaWcuYWxpZ24gPSAiY2VudGVyIn0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoICBjKGhlcmU6OmhlcmUoImltYWdlbmVzIiwgInR0XzA1X2ltZ18wNl9pbm5lci1qb2luLnBuZyIpLCBoZXJlOjpoZXJlKCJpbWFnZW5lcyIsICJ0dF8wNV9pbWdfMTFfc2VtaS1qb2luLnBuZyIpKSAgKQpgYGAKCgpMYSBpbm5lcl9qb2luIAoKICAtIChpKSBhw7FhZGUgdmFyaWFibGVzIGRlbCBkYXRhLmZyYW1lIHkgYWwgZGF0YS5mcmFtZSB4IChlbiBlc3RlIGNhc28geSR5KQogIC0gKGlpKSBzw7NsbyByZXRpZW5lIGxhcyByb3dzIGRlbCBkYXRhLmZyYW1lIHggKipxdWUgdGllbmVuIHVuIG1hdGNoIGVuIHkqKiAoZW4gZXN0ZSBjYXNvIGxhcyAyIHByaW1lcmFzIGZpbGFzIGRlIHgpCiAgCiAgLSAoaWlpKSBQRVJPICoqZW4gZXN0ZSBlamVtcGxvIEFERU3DgVMqKiBkdXBsaWNhIHJvd3MuIGxhIHJhesOzbiBlcyBxdWUgaGF5IG1hdGNoaW5nIGR1cGxpY2Fkb3MsIGhheSAyIHVub3MgZW4geCB5IG90cm9zIDIgdW5vcyBlbiBlbCBkYXRhLmZyYW1lIHkgKGNvbiBkaXN0aW50b3MgdmFsb3JlcyBkZSB5JHkpIChhc8OtIHF1ZSBzYWxlbiA0IHJvd3MpCgoKCjxicj4KCiMjIyMgSW1wb3J0YW50ZSDCv0PDs21vIGRlY2lyIGEgbGFzIGZ1bmNpb25lcyBsYSBjb2x1bW5hLCBvIGNvbHVtbmFzLCBxdWUgc2UgdXNhcsOhbiBwYXJhIGhhY2VyIGxvcyBtYXRjaGluZz8KCgpBbCBpZ3VhbCBxdWUgY29uIGxhcyBtdXRhdGluZyBqb2lucywgZW4gbGFzIGZpbHRlcmluZyBqb2lucyB0YW1iacOpbiBwb2RlbW9zKERFQkVNT1MpIGVsZWdpciBsYXMgY29sdW1uYXMgKG8gdmFyaWFibGVzKSBxdWUgbm9zIHNlcnZpcsOhbiBwYXJhIHVuaXIgbG9zIDIgZGYncy4gRXN0YXMgY29sdW1uYXMgcXVlIHNlIHVzYW4gcGFyYSBwYXJhIGhhbGxhciBsb3MgbWF0Y2hpbmdzIHkgcXVlIHBvciB0YW50byBxdWUgcGVybWl0ZW4gZnVzaW9uYXIgbG9zIDIgZGYncyBzZSBsbGFtYW4gImtleXMiLiAKCkxhIG9wY2nDs24gZGUgbGFzIGZ1bmNpb25lcyBwYXJhIHNlbGVjY2lvbmFyIGVzdGFzIGNvbHVtbmFzICJrZXlzIiBlcyBgYnkgPWAuIAoKICAtIHNpIHBvbmVtb3MgYHNlbWlfam9pbihkZjEsIGRmMiwgYnkgPSAiWDEiKWAgc2UgaGFyw6EgdW5hIHNlbWlfam9pbiBzaWVuZG8gbGEgdmFyaWFibGUgIlgxIiBsYSBxdWUgaGFyw6EgZGUga2V5LiBTaSBsYXMgdmFyaWFibGVzIGtleSBubyBzZSBsbGFtYXNlbiBpZ3VhbCBlbiBsb3MgMiBkZidzIHNpZW1wcmUgcG9kZW1vcyByZW5vbWJyYXJsYXMgbyBoYWNlciBgc2VtaV9qb2luKGRmMSwgZGYyLCBieSA9IGMoIlgxIiA9ICJENCIpYAogIAogIC0gIHNpIHBvbmVtb3MgYHNlbWlfam9pbihkZjEsIGRmMixieSA9IGMoIlgxIiwgIlgyIilgIGhhcsOhIGZhbHRhIHF1ZSB1bmEgcm93IGRlIGRmMSB0ZW5nYSBsb3MgdmFsb3JlcyB0YW50byBkZSBYMSBjb21vIGRlIFgyIGlndWFsZXMgIGEgbG9zIGRlIGVzYXMgbWlzbWFzIHZhcmlhYmxlcyBlbiBkZjIuIFNpIG5vIHNlIGxsYW1hc2VuIGlndWFsIGxhcyB2YXJpYWJsZXMgZW4gZGYxIHkgZGYyIGhhcsOtYW1vcyBgYnkgPSBjKCJYMSIgPSAiRDQiLCAiWDIiID0gIkQ3IilgCgoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQoKPGJyPgoKIyMgU2V0IG9wZXJhdGlvbnMKCgpFc3RlIHRpcG8gZGUgam9pbnMgZXMgbcOhcyBlc3RyaWN0YTogKipoYWNlIGZhbHRhIHF1ZSBsb3MgMiBkZidzIHRlbmdhbiBsYXMgbWlzbWFzIHZhcmlhYmxlcyAobyBjb2x1bW5hcykqKi4gTG9zIDIgZGYncyBwdWVkZW4gdGVuZXIgb2JzZXJ2YWNpb25lcyhmaWxhcykgZGlmZXJlbnRlcywgUEVSTyBlcyBuZWNlc2FyaW8gcXVlIHRlbmdhbiBsYXMgbWlzbWFzIHZhcmlhYmxlcyAobyBjb2x1bW5hcykuCgpDb21vIGxvcyAyIGRmJ3MgdGllbmVuIGxhcyBtaXNtYXMgY29sdW1uYXMsIGVudG9uY2VzIGVzIGNvbW8gc2kgc2UgdHJhdGFzZW4gbG9zIGRmcyBjb21vIGNvbmp1bnRvczoKCiAgLSBgaW50ZXJzZWN0KGRmMSwgZGYyKWA6IGRldnVlbHZlIHVuIGRmIGNvbiBsYXMgb2JzZXJ2YWNpb25lcyBjb211bmVzIGVuIGRmMSB5IGRmMgogIC0gYHVuaW9uKGRmMSwgZGYyKWA6IGRldnVlbHZlIGxhIHVuacOzbjsgbyBzZWEsIGxhcyBvYnNlcnZhY2lvbmVzIGRlIGRmMSB5IGRlIGRmMiAocXVpdGFuZG8gbGFzIHBvc2libGVzIGZpbGFzIGR1cGxpY2FkYXMpCiAgLSBgdW5pb25fYWxsKGRmMSwgZGYyKWA6IGRldnVlbHZlIGxhIHVuacOzbiAoc2luIHF1aXRhciBsb3MgZHVwbGljYWRvcykKICAtIGBzZXRkaWZmKGRmMSwgZGYyKWA6IGRldnVlbHZlIGxhcyBmaWxhcyBlbiBkZjEgcXVlIG5vIGVzdMOhbiBlbiBkZjIKICAKICA8YnI+CiAgCiAgLSBgc2V0ZXF1YWwoZGYxLGRmMmA6IHJldG9ybmEgVFJVRSBzaSBkZiB5IGRmMiB0aWVuZW4gZXhhY3RhbWVudGUgbGFzIG1pc21hcyBmaWxhcyAoZGEgaWd1YWwgZWwgb3JkZW4gZW4gZWwgcXVlIGVzdMOpbiBsYXMgZmlsYXMpCgo8YnI+CgpgYGB7ciwgZWNobyA9IFRSVUUsIGV2YWwgPSBUUlVFfQp4IDwtIHRpYmJsZTo6dGliYmxlKHYxID0gYygxLCAxLCAyKSwgdjIgPSBjKCJhIiAsICJiIiwgImEiKSkKeSA8LSB0aWJibGU6OnRpYmJsZSh2MSA9IGMoMSwgMiksICAgIHYyID0gYygiYSIgLCAiYiIpKQpgYGAKCjxicj4KCiMjIyMjIGludGVyc2VjY2nDs24KCmBgYHtyLCBlY2hvID0gVFJVRSwgZXZhbCA9IEZBTFNFfQppbnRlcnNlY3QoeCwgeSkKYGBgCgoKYGBge3IgLCBlY2hvPUZBTFNFLCBldmFsID0gVFJVRSwgZmlnLmFzcCA9IDQvMiwgb3V0LndpZHRoID0gIjc1JSIsIGZpZy5hbGlnbiA9ICJjZW50ZXIifQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhoZXJlOjpoZXJlKCJpbWFnZW5lcyIsICJ0dF8wNV9pbWdfMTNfaW50ZXJzZWN0LnBuZyIpKQpgYGAKCgo8YnI+CgojIyMjIyB1bmnDs24KCmBgYHtyLCBlY2hvID0gVFJVRSwgZXZhbCA9IEZBTFNFfQp1bmlvbih4LCB5KQpgYGAKCgpgYGB7ciAsIGVjaG89RkFMU0UsIGV2YWwgPSBUUlVFLCBmaWcuYXNwID0gNC8yLCBvdXQud2lkdGggPSAiNzUlIiwgZmlnLmFsaWduID0gImNlbnRlciJ9CmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKGhlcmU6OmhlcmUoImltYWdlbmVzIiwgInR0XzA1X2ltZ18xNF91bmlvbi5wbmciKSkKYGBgCgoKPGJyPgoKIyMjIyMgc2V0ZGlmZgoKYGBge3IsIGVjaG8gPSBUUlVFLCBldmFsID0gRkFMU0V9CnNldGRpZmYoeCwgeSkKYGBgCgoKYGBge3IgLCBlY2hvPUZBTFNFLCBldmFsID0gVFJVRSwgZmlnLmFzcCA9IDQvMiwgb3V0LndpZHRoID0gIjc1JSIsIGZpZy5hbGlnbiA9ICJjZW50ZXIifQprbml0cjo6aW5jbHVkZV9ncmFwaGljcyhoZXJlOjpoZXJlKCJpbWFnZW5lcyIsICJ0dF8wNV9pbWdfMTVfc2V0ZGlmZi5wbmciKSkKYGBgCgpQdWVkZXMgcHJvYmFyIHTDuiBtaXNtbyBhIGNhbWJpYXIgZWwgb3JkZW4gZGVsIGxvcyBkZidzIGVuIGBzZXRkaWZmKClgOgoKYGBge3IsIGVjaG8gPSBUUlVFLCBldmFsID0gRkFMU0V9CnNldGRpZmYoeSwgeCkKYGBgCgoKCgo8YnI+CgojIyMjIyBzZXRxdWFsCgpTaXJ2ZSBwYXJhIGRldGVybWluYXIgc2kgMiBkZidzIHNvbiBpZ3VhbGVzIChzaW4gaW1wb3J0YXIgZWwgb3JkZW4gZW4gcXVlIGVzdMOpbiBsYXMgZmlsYXMpCgpgYGB7ciwgZWNobyA9IFRSVUUsIGV2YWwgPSBUUlVFfQpzZXRlcXVhbCh4LCB5KQpgYGAKCgpgYGB7ciwgZWNobyA9IFRSVUUsIGV2YWwgPSBUUlVFfQpzZXRlcXVhbCh1bmlvbih4LCB5KSwgdW5pb24oeSwgeCkpCmBgYAoKPGJyPgoKLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCjxicj4KCiMgRXNwZXJhbmRvIGEgR09ET1QvZ2dwbG90MgoKPGJyPgoKQnVlbm8sIHB1ZXMgaGVtb3MgdmlzdG8gIlRPRE8iIHNvYnJlIG1hbmlwdWxhY2nDs24gZGUgZGF0b3MuIEVsIHByw7N4aW1vIHR1dG9yaWFsIHZhIGRlIHZpc3VhbGl6YWNpw7NuOiBoYWNpYSBnZ3Bsb3QyCgpBcXXDrSB1biBwZXF1ZcOxbyBhdmFuY2U6CgoKYGBge3IsIGVjaG8gPSBUUlVFLCBldmFsID0gVFJVRX0KbGlicmFyeSgiZ2dwbG90MiIpCm15X3Bsb3QgPC0gZ2dwbG90KGdhcG1pbmRlciwgYWVzKHggPSBjb250aW5lbnQsIHkgPSBsaWZlRXhwKSkgKwogIGdlb21fYm94cGxvdChvdXRsaWVyLmNvbG91ciA9ICJob3RwaW5rIikgKwogIGdlb21faml0dGVyKHBvc2l0aW9uID0gcG9zaXRpb25faml0dGVyKHdpZHRoID0gMC4xLCBoZWlnaHQgPSAwKSwgYWxwaGEgPSAxLzQpICsKICBsYWJzKHRpdGxlID0gIkV4cGVyYW56YSBkZSB2aWRhIChwb3IgY29udGluZW50ZSkiLAogICAgICAgc3VidGl0bGUgPSAiRGF0b3MgZGUgZ2FwbWluZGVyLiAxOTUyLTIwMDcob2JzZXJ2YWNpb25lcyBjYWRhIDUgYcOxb3MpIiwKICAgICAgIGNhcHRpb24gPSAiU291cmNlOiBHYXBtaW5kZXIuIEplbm55IEJyeWFuIHJvY2tzIGluIGdhcG1pbmRlciB2aWduZXR0ZSEhIiwgCiAgICAgICB4ID0gIkNvbnRpbmVudGUiLCB5ID0gIkVzcGVyYW56YSBkZSBWaWRhIChsaWZlRXhwKSIpIApgYGAKCjxicj4KCmBgYHtyLCBlY2hvID0gRkFMU0UsIGV2YWwgPSBUUlVFfQpteV9wbG90CmBgYAoKPGJyPgo8YnI+CgoKYGBge3IsIGVjaG8gPSBUUlVFLCBldmFsID0gVFJVRX0KZ2FwbWluZGVyMiA8LSBnYXBtaW5kZXIgJT4lIG11dGF0ZSh5ZWFyID0gYXMuZmFjdG9yKHllYXIpKQoKbGlicmFyeSgiZ2dwbG90MiIpCm15X3Bsb3QgPC0gZ2dwbG90KGdhcG1pbmRlcjIsIGFlcyh4ID0geWVhciwgeSA9IGxpZmVFeHApKSArCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuY29sb3VyID0gImhvdHBpbmsiKSArCiAgZ2VvbV9qaXR0ZXIocG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIod2lkdGggPSAwLjEsIGhlaWdodCA9IDApLCBhbHBoYSA9IDEvNCkgKwogIGxhYnModGl0bGUgPSAiRXhwZXJhbnphIGRlIHZpZGEgKHBvciBhw7FvKSIsCiAgICAgICBzdWJ0aXRsZSA9ICJEYXRvcyBkZSBnYXBtaW5kZXIuIDE5NTItMjAwNyhvYnNlcnZhY2lvbmVzIGNhZGEgNSBhw7FvcykiLAogICAgICAgY2FwdGlvbiA9ICJTb3VyY2U6IEdhcG1pbmRlci4gSmVubnkgQnJ5YW4gcm9ja3MgaW4gZ2FwbWluZGVyIHZpZ25ldHRlISEiLCAKICAgICAgIHggPSAiUGVyaW9kbyIsIHkgPSAiRXNwZXJhbnphIGRlIFZpZGEgKGxpZmVFeHApIikgCmBgYAoKPGJyPgoKCmBgYHtyLCBlY2hvID0gRkFMU0UsIGV2YWwgPSBUUlVFfQpteV9wbG90CmBgYAoKPGJyPgoKYGBge3IsIGVjaG8gPSBUUlVFLCBldmFsID0gVFJVRX0KbGlicmFyeSgiZ2dwbG90MiIpCm15X3Bsb3QgPC0gZ2dwbG90KGdhcG1pbmRlciwgYWVzKHggPSBnZHBQZXJjYXAsIHkgPSBsaWZlRXhwLCBjb2xvdXIgPSBjb250aW5lbnQpKSArCiAgZ2VvbV9qaXR0ZXIocG9zaXRpb24gPSBwb3NpdGlvbl9qaXR0ZXIod2lkdGggPSAwLjEsIGhlaWdodCA9IDApLCBhbHBoYSA9IDEvNCkgKwogIGxhYnModGl0bGUgPSAiRXhwZXJhbnphIGRlIHZpZGEgdnMuIEdEUCAocGVyIGPDoXBpdGEpIiwKICAgICAgIHN1YnRpdGxlID0gIkRhdG9zIGRlIGdhcG1pbmRlci4gMTk1Mi0yMDA3KG9ic2VydmFjaW9uZXMgY2FkYSA1IGHDsW9zKSIsCiAgICAgICBjYXB0aW9uID0gIlNvdXJjZTogR2FwbWluZGVyLiBKZW5ueSBCcnlhbiByb2NrcyBpbiBnYXBtaW5kZXIgdmlnbmV0dGUhISIsIAogICAgICAgeCA9ICJHRFAgKHBlciBjw6FwaXRhKSIsIHkgPSAiRXNwZXJhbnphIGRlIFZpZGEgKGxpZmVFeHApIikgCmBgYAoKPGJyPgoKYGBge3IsIGVjaG8gPSBGQUxTRSwgZXZhbCA9IFRSVUV9Cm15X3Bsb3QKYGBgCgo8YnI+CgotLS0tLS0tLS0tLS0tLS0KCiMgVGlkeWxvZyBwYWNrYWdlCgpVbmEgaGVycmFtaWVudGEgcXVlIG9zIHB1ZWRlIHNlciBkZSB1dGlsaWRhZCBwYXJhIGFwcmVuZGVyIGVsIHVzbyBkZSBgZHBseXJgIGVzIGVsIHBhcXVldGUgW2B0aWR5bG9nYF0oaHR0cHM6Ly9naXRodWIuY29tL2VsYmVyc2IvdGlkeWxvZykuIEVzdGUgcGFxdWV0ZSBub3MgZGEgZmVlZGJhY2sgaW5zdGFudMOhbmVvIHNvYnJlIHF1w6kgaGFjZW1vcyBjdWFuZG8gdXNhbW9zIGxhcyBwcmluY2lwYWxlcyBmdW5jaW9uZXMgZGUgYGRwbHlyYCB5IGB0aWR5cmAuIFZlw6Ftb3NsbyBlbiBhY2Npw7NuOgoKCmBgYHtyLCBlY2hvID0gVFJVRSwgZXZhbCA9IFRSVUUsIG1lc3NhZ2UgPSBUUlVFfQpsaWJyYXJ5KCJkcGx5ciIpCmxpYnJhcnkoInRpZHlyIikKbGlicmFyeSgidGlkeWxvZyIsIHdhcm4uY29uZmxpY3RzID0gRkFMU0UpCmZpbHRlcmVkIDwtIGZpbHRlcihtdGNhcnMsIGN5bCA9PSA0KQptdXRhdGVkIDwtIG11dGF0ZShtdGNhcnMsIG5ld192YXIgPSB3dCAqKiAyKQpgYGAKClNpIG9zIGZpasOhaXMsIGNhZGEgdmV6IHF1ZSBlamVjdXRhcyB1bmEgZnVuY2nDs24gZGUgYGRwbHlyYCBub3MgZGV2dWVsdmUgdW4gbWVuc2FqZSBleHBsaWPDoW5kb25vcyBxdWUgc2UgaGEgaGVjaG8uIFBvciBlamVtcGxvIGxhIGxpbmVhIGRlIGPDs2RpZ28gYGZpbHRlcmVkIDwtIGZpbHRlcihtdGNhcnMsIGN5bCA9PSA0KWAgaGEgY3JlYWRvIHVuIG51ZXZvIGRhdGEuZnJhbWUgZG9uZGUgc2UgaGFuIGVsaW1pbmFkbyAyMSBmaWxhcyBkZWwgZGYgb3JpZ2luYWw6IGAjPiBmaWx0ZXI6IHJlbW92ZWQgMjEgcm93cyAoNjYlKSwgMTEgcm93cyByZW1haW5pbmdgLgoKTGEgc2VndW5kYSBsaW5lYSBkZSBjw7NkaWdvIGBtdXRhdGVkIDwtIG11dGF0ZShtdGNhcnMsIG5ld192YXIgPSB3dCAqKiAyKWAgaGEgY3JlYWRvIHVuYSBudWV2YSB2YXJpYWJsZSBjb24gYG11dGF0ZSgpYDogYCM+IG11dGF0ZTogbmV3IHZhcmlhYmxlICduZXdfdmFyJyB3aXRoIDI5IHVuaXF1ZSB2YWx1ZXMgYW5kIDAlIE5BYC4KCgoKPGJyPgoKLS0tLS0tLS0tLS0tLS0tCgo8YnI+CgojIFRpZHl2ZXJzZSB2cy4gQmFzZSBSCgoKVG9kbyBsbyBxdWUgc2UgcHVlZGUgaGFjZXIgY29uIGRwbHlyLCB0aWR5ciBldGMuICx0YW1iacOpbiBzZSBwdWVkZSBoYWNlciBjb24gQmFzZS1SIHBlcm8gZGUgdW5hIG1hbmVyYSBtdWNobyBtZW5vcyBpbnR1aXRpdmEuIAoKCkVsIHNpZ3VpZW50ZSBlamVtcGxvIGVzdGEgc2FjYWRvIGRlIFtlc3RlIHBvc3RdKGh0dHBzOi8vd3d3LnItYmxvZ2dlcnMuY29tL3doZW4taS11c2UtcGx5cmRwbHlyLykuIFNvbiBkb3MgdHJvem9zIGRlIGPDs2RpZ28gcXVlIGhhY2VuIGV4YWN0YW1lbnRlIGxvIG1pc21vOgoKQ29uIGVsIHRpZHl2ZXJzZToKCmBgYHtyLCBlY2hvID0gVFJVRSwgZXZhbCA9IEZBTFNFfQpsaWJyYXJ5KGRwbHlyKQptdGNhcnMgJT4lIAogIGdyb3VwX2J5KGN5bCwgYW0pICU+JQogIHNlbGVjdChtcGcsIGN5bCwgd3QsIGFtKSAlPiUKICBzdW1tYXJpc2UoYXZnbXBnID0gbWVhbihtcGcpLCBhdmd3dCA9IG1lYW4od3QpKSAlPiUKICBmaWx0ZXIoYXZnbXBnID4gMjApCmBgYAoKQ29uIGxhIHNpbnRheGlzIGRlIGJhc2UgUjoKCgpgYGB7ciwgZWNobyA9IFRSVUUsIGV2YWwgPSBGQUxTRX0KZmlsdGVyKAogIHN1bW1hcmlzZSgKICAgIHNlbGVjdCgKICAgICAgZ3JvdXBfYnkobXRjYXJzLCBjeWwsIGFtKSwKICAgICAgbXBnLCBjeWwsIHd0LCBhbSksCiAgICBhdmdtcGcgPSBtZWFuKG1wZyksIGF2Z3d0ID0gbWVhbih3dCkpLAogIGF2Z21wZyA+IDIwKQpgYGAKCk8gcHVlc3RvIGVuIGhvcml6b250YWwKCmBgYHtyLCBlY2hvID0gVFJVRSwgZXZhbCA9IEZBTFNFfQpmaWx0ZXIoc3VtbWFyaXNlKHNlbGVjdChncm91cF9ieShtdGNhcnMsIGN5bCwgYW0pLCAgbXBnLCBjeWwsIHd0LCBhbSksYXZnbXBnID0gbWVhbihtcGcpLCBhdmd3dCA9IG1lYW4od3QpKSwgYXZnbXBnID4gMjApCmBgYAoKPGJyPgoKCiMjIyMjIE90cm9zIDIgZWplbXBsb3MgZGUgY29tcGFyYWNpw7NuIHRpZHl2ZXJzZSB2ZXJzdXMgQmFzZS1SOgoKYGBge3IsIGVjaG8gPSBUUlVFLCBldmFsID0gRkFMU0V9CmRmICU+JSBmaWx0ZXIoY291bnRyeSA9PSAiU3BhaW4iKSAlPiUgIHNlbGVjdCh5ZWFyLCBsaWZlRXhwKQoKZGZbZGYkY291bnRyeSA9PSAiU3BhaW4iLCBjKCJ5ZWFyIiwgImxpZmVFeHAiKV0gCmBgYAoKRXN0ZSDDumx0aW1vIGVqZW1wbG8gbG8gaW50cm9kdXpjbyBwb3JxdWUgcXVpZXJvIHJlY29yZGFyIFtlc3RhcyB0cmFzcGFyZW5jaWFzXShodHRwczovL2NlcmVicmFsbWFzdGljYXRpb24uZ2l0aHViLmlvL2Rvd25fd2l0aF9vcHBfZHBseXIuaHRtbCMvKSBxdWUgZXhwbGljYW4gcXVlIGVsIHBhcXVldGUgYGRicGx5cmAgdHJhZHVjZSBleHByZXNpb25lcyBkZSBkcGx5ciBhIFNRTC4gRWwgY8OzZGlnbyBkZSBsYXMgdHJhbnNwYXJlbmNpYXMgZXN0w6EgW2FxdcOtXShodHRwczovL2dpdGh1Yi5jb20vQ2VyZWJyYWxNYXN0aWNhdGlvbi9QcmVzZW50YXRpb25zKSwgeSBbYXF1w61dKGh0dHBzOi8vZGIucnN0dWRpby5jb20vZHBseXIvKSBwdWVkZXMgYXByZW5kZXIgY29tbyBoYWNlciBxdWVyaWVzIFNRTCBhIHVuIGRhdGFiYXNlIHV0aWxpemFuZG8gbGEgc2ludGF4aXMgZGUgZHBseXIuCgpgYGB7ciwgZWNobyA9IFRSVUUsIGV2YWwgPSBGQUxTRX0KIy0gY29uIHRpZHl2ZXJzZQpkZl9jYXJzICU+JSAKICBzZWxlY3QobG9uZ25hbWUsIGN5bCwgaHApICU+JQogIG11dGF0ZSggc2hvcnRuYW1lID0gd29yZChsb25nbmFtZSwgMSkgKSAlPiUKICBzZWxlY3QoIC0gbG9uZ25hbWUpICAtPgpkZl9jYXJzX2xpbWl0ZWQKaGVhZCggZGZfY2Fyc19saW1pdGVkICkKCiMtIGNvbiBkcGx5ciBQRVJPIHNpbiAlPiUgCmhlYWQoCiAgc2VsZWN0KAogICAgbXV0YXRlKCAKICAgICAgc2VsZWN0KGRmX2NhcnMsIGxvbmduYW1lLCBjeWwsIGhwKSAsIAogICAgICBzaG9ydG5hbWUgPSB3b3JkKGxvbmduYW1lLCAxKSAKICAgICAgKSwgCiAgICAtbG9uZ25hbWUgCiAgICApCikKYGBgCgoKPGJyPgo8YnI+CgotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCjxicj4KCiMgQmlibGlvZ3JhZsOtYQoKLSBbSW50cm9kdWNjacOzbiBhIGRwbHlyXShodHRwOi8vc3RhdDU0NS5jb20vYmxvY2swMDlfZHBseXItaW50cm8uaHRtbCkuIEplbm55IEJyeWFuIG5vcyBjdWVudGEgKGVuIHN1cyBGQU5UQVNUSUNPUyBtYXRlcmlhbGVzIHBhcmEgZWwgY3Vyc28gU1RBVDU0NSkgbG9zIHJ1ZGltZW50b3MgZGUgZHBseXIuIEJ1ZW5hIHBhcnRlIGRlIGVzdGUgdHV0b3JpYWwgc2UgYmFzYSBlbiBlbCBzdXlvLgoKLSBbZHBseXIgZnVuY3Rpb25zIGZvciBhIHNpbmdsZSBkYXRhc2V0XShodHRwOi8vc3RhdDU0NS5jb20vYmxvY2swMTBfZHBseXItZW5kLXNpbmdsZS10YWJsZS5odG1sKS4gT3RybyBtYXRlcmlhbCBkZSBKZW5ueSBkZWwgY3Vyc28gU1RBVDU0NSBzb2JyZSBkcGx5cgoKLSBbZHBseXIgQ0hFQVQgU0hFRVRdKGh0dHBzOi8vd3d3LnJzdHVkaW8uY29tL3Jlc291cmNlcy9jaGVhdHNoZWV0cy8pLiBGQU5UQVNUSVFVw4lSUklNQSEhISBJbXByZXNpb25hbnRlISEKCi0gW0PDoXBpdHVsbyBkZSBSNERTIHNvYnJlICJkcGx5ciJdKGh0dHA6Ly9yNGRzLmhhZC5jby5uei90cmFuc2Zvcm0uaHRtbCkuIERlIEhhZGxleS4gRmFudMOhc3RpY28gbGlicm8geSBmYW50w6FzdGljbyBjYXDDrXR1bG8gc29icmUgbWFuZWpvIGRlIGRhdG9zIChkcGx5cikuCgotIFtBIG5ldyBkYXRhIHByb2Nlc3Npbmcgd29ya2Zsb3cgZm9yIFI6IGRwbHlyLCBtYWdyaXR0ciwgdGlkeXIsIGdncGxvdDJdKGh0dHA6Ly96ZXZyb3NzLmNvbS9ibG9nLzIwMTUvMDEvMTMvYS1uZXctZGF0YS1wcm9jZXNzaW5nLXdvcmtmbG93LWZvci1yLWRwbHlyLW1hZ3JpdHRyLXRpZHlyLWdncGxvdDIvKS4gUG9zdCBkZSB1biBudWV2byBjb252ZW5jaWRvIGRlIGxhcyBib25kYWRlcyBkZWwgdGlkeXZlcnNlLiBVbiBlamVtcGxvIHNlbmNpbGxvIHBlcm8gaWx1c3RyYXRpdm8uCgotIFtXaWRlICYgTG9uZyBEYXRhXShodHRwczovL3N0YW5mb3JkLmVkdS9+ZWpkZW15ci9yLXR1dG9yaWFscy93aWRlLWFuZC1sb25nLykgUG9zdCBxdWUgZXhwbGljYSBsb3MgYmVuZWZpY2lvcyBkZSB0cmFiYWphciBjb24gZGF0b3MgZW4gZm9ybWF0byBsb25nICh0aWR5KS4KCi0gW0xhIGJpYmxpYSBkZWwgdGlkeSBkYXRhXShmdHA6Ly9jcmFuLnItcHJvamVjdC5vcmcvcHViL1Ivd2ViL3BhY2thZ2VzL3RpZHlyL3ZpZ25ldHRlcy90aWR5LWRhdGEuaHRtbCkuIFZpZ25ldHRlIGRlIHRpZHlyIHBhY2thZ2UgZXNjcml0YSBwb3IgSGFkbGV5IFdpY2toYW0gcXVlIGV4cGxpY2EgY29uIE1VQ0hPIGRldGFsbGUgcXVlIHNvbiBsb3MgdGlkeSBkYXRhLgoKLSBbTGVzc2VyIGtub3duIGRwbHlyIGZ1bmN0aW9uc10oaHR0cHM6Ly9zdGF0aXN0aWNhbG9kZHNhbmRlbmRzLndvcmRwcmVzcy5jb20vMjAxOS8wOC8zMC9sZXNzZXIta25vd24tZHBseXItZnVuY3Rpb25zLykuIFVuIHBvc3QgZmFudMOhc3RpY28gc29icmUgYWxndW5hcyBmdW5jaW9uZXMgbm8gdGFuIGNvbm9jaWRhcyBkZSBgZHBseXJgLgoKLSBbVmnDsWV0YXMgb2ZpY2lhbGVzIGRlIGRwbHlyIDEuMC4wXShodHRwczovL2RwbHlyLnRpZHl2ZXJzZS5vcmcvaW5kZXguaHRtbCkuIEVzdMOhbiBlbiBsYSBzZWNjacOzbiAiQXJ0w61jdWxvcyIuIEV4cGxpY2FuIG11eSBiaWVuIGVsIGZ1bmNpb25hbWllbnRvIGRlIGxhcyBudWV2YXMgZnVuY2lvbmVzIGRlIGRwbHlyIDEuMC4wCgotIER1cmFudGUgZWwgcHJvY2VzbyBkZSBhbnVuY2lvIG9maWNpYWwgZGUgZHBseXIgMS4wLjAgc2UgcHVibGljYXJvbiB1bmEgc2VyaWUgZGUgcG9zdHMgb2ZpY2lhbGVzIGRlbCB0aWR5dmVyc2UsIGNvbmNyZXRhbWVudGUgW2FxdcOtXShodHRwczovL3d3dy50aWR5dmVyc2Uub3JnL2Jsb2cvMjAyMC8wNi9kcGx5ci0xLTAtMC8pLCBbYXF1w61dKGh0dHBzOi8vd3d3LnRpZHl2ZXJzZS5vcmcvYmxvZy8yMDIwLzA0L2RwbHlyLTEtMC0wLWFuZC12Y3Rycy8pLCBbYXF1w61dKGh0dHBzOi8vd3d3LnRpZHl2ZXJzZS5vcmcvYmxvZy8yMDIwLzAzL2RwbHlyLTEtMC0wLXNlbGVjdC1yZW5hbWUtcmVsb2NhdGUvKSwgW2FxdcOtXShodHRwczovL3d3dy50aWR5dmVyc2Uub3JnL2Jsb2cvMjAyMC8wNS9kcGx5ci0xLTAtMC1sYXN0LW1pbnV0ZS1hZGRpdGlvbnMvKSB5IFthcXXDrV0oaHR0cHM6Ly93d3cudGlkeXZlcnNlLm9yZy9ibG9nLzIwMjAvMDMvZHBseXItMS0wLTAtc3VtbWFyaXNlLykuRXN0w6FuIG11eSBiaWVuLgoKLSBLZWl0aCBNY051bHR5IHRhbWJpw6luIG1lIGF5dWRvIGEgZW50ZW5kZXIgZHBseXIgMS4wMDogW2FxdcOtXShodHRwczovL3Rvd2FyZHNkYXRhc2NpZW5jZS5jb20vd2hhdC15b3UtbmVlZC10by1rbm93LWFib3V0LXRoZS1uZXctZHBseXItMS0wLTAtN2VhYWFmNmQ3OGFjKSB5IFthcXXDrV0oaHR0cHM6Ly90b3dhcmRzZGF0YXNjaWVuY2UuY29tL2ZpdmUtdGlkeXZlcnNlLXRyaWNrcy15b3UtbWF5LW5vdC1rbm93LWFib3V0LWM1MDI2ZDVhMTlkYSkKCi0gUmViZWNjYSBCYXJ0ZXIgdGFtYmnDqW4gaGl6byB1biBbbXV5IGJ1ZW4gcG9zdF0oaHR0cDovL3d3dy5yZWJlY2NhYmFydGVyLmNvbS9ibG9nLzIwMjAtMDctMDktYWNyb3NzLykuIAoKLSBRdWl6w6FzIGVsIHR1dG9yaWFsIG3DoXMgY29tcGxldG8gcXVlIGhlIHZpc3RvIGxvIGhpem8gZWwgZXF1aXBvIGRlIFRoaW5rUjogW2FxdcOtXShodHRwczovL3RoaW5rci5mci9oZXktcXVvaS1kZS1uZXVmLWRwbHlyLWxlLXBvaW50LXN1ci1sYS12MS8pLiBFc3TDoSBlbiBmcmFuY8Opcy4KCgoKCi0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCgoK