En la última sección de lectura de datos viste el concepto de datos “anchos” y “largos”.

Los datos en formato “largo” o “tidy”, son aquellos en los cuales:

En el formato “ancho” es un poco más complejo de definirlo pero la idea general es que:

Una tabla en formato largo va a tener una cierta cantidad de columnas que cumplen el rol de identificadores y cuya combinación identifican una única observación y una única columna con el valor de la observación. En el ejemplo de arriba, pais y anio son las columnas identificadoras y casos es la columna que contiene el valor de las observaciones.

En una tabla ancha, cada observación única se identifica a partir de la intersección de filas y columnas. En el ejemplo, los países están en las filas y los años en las columnas.

En general, el formato ancho es más compacto y legible por humanos mientras que el largo es más fácil de manejar con la computadora. Si te fijás en las tablas de arriba, es más fácil comparar los valores entre países y entre años en la tabla ancha. Pero el nombre de las columnas (“1999”, “2000”) en realidad ¡son datos! Además este formato se empieza a complicar en cuanto hay más de dos identificadores.

Un mismo set de datos puede ser representado de forma completamente “larga”, completamente “ancha” o –lo que es más común– en un formato intermedio pero no existe una forma “correcta” de organizar los datos; cada una tiene sus ventajas y desventajas. Por esto es que es muy normal que durante un análisis los datos vayan y vuelvan entre distintos formatos dependiendo de los métodos estadísticos que se le aplican. Entonces, aprender a transformar datos anchos en largos y viceversa es un habilidad muy útil.

Desafío

En las tablas de ejemplo cada país tiene el un valor observado de “casos” para cada año. ¿Cómo agregarías una nueva variable con información sobre “precios”? Dibujá un esquema en papel y lápiz en formato ancho y uno en formato largo. ¿En qué formato es más “natural” esa extensión?

En esta sección vas a usar el paquete {tidyr} para manipular datos. Si no lo tenés instalado, instalalo con el comando:

install.packages("tidyr")

(como siempre, recordá que esto hay que hacerlo una única vez)

Y luego cargá {tidyr} y {dplyr} (que usaste en una sección anterior) con:

library(tidyr)
library(dplyr)

De ancho a largo con pivot_longer()

En secciones anteriores usaste una versión de los datos de gapminder. Ahora vas a leer los datos en su formato original:

paises_ancho <- readr::read_csv("datos/paises_ancho.csv")
## Parsed with column specification:
## cols(
##   .default = col_double(),
##   continente = col_character(),
##   pais = col_character()
## )
## See spec(...) for full column specifications.
paises_ancho
## # A tibble: 142 x 38
##    continente pais  pib_per_capita_… pib_per_capita_… pib_per_capita_…
##    <chr>      <chr>            <dbl>            <dbl>            <dbl>
##  1 Africa     Alge…            2449.            3014.            2551.
##  2 Africa     Ango…            3521.            3828.            4269.
##  3 Africa     Benin            1063.             960.             949.
##  4 Africa     Bots…             851.             918.             984.
##  5 Africa     Burk…             543.             617.             723.
##  6 Africa     Buru…             339.             380.             355.
##  7 Africa     Came…            1173.            1313.            1400.
##  8 Africa     Cent…            1071.            1191.            1193.
##  9 Africa     Chad             1179.            1308.            1390.
## 10 Africa     Como…            1103.            1211.            1407.
## # … with 132 more rows, and 33 more variables: pib_per_capita_1967 <dbl>,
## #   pib_per_capita_1972 <dbl>, pib_per_capita_1977 <dbl>,
## #   pib_per_capita_1982 <dbl>, pib_per_capita_1987 <dbl>,
## #   pib_per_capita_1992 <dbl>, pib_per_capita_1997 <dbl>,
## #   pib_per_capita_2002 <dbl>, pib_per_capita_2007 <dbl>,
## #   esperanza_de_vida_1952 <dbl>, esperanza_de_vida_1957 <dbl>,
## #   esperanza_de_vida_1962 <dbl>, esperanza_de_vida_1967 <dbl>,
## #   esperanza_de_vida_1972 <dbl>, esperanza_de_vida_1977 <dbl>,
## #   esperanza_de_vida_1982 <dbl>, esperanza_de_vida_1987 <dbl>,
## #   esperanza_de_vida_1992 <dbl>, esperanza_de_vida_1997 <dbl>,
## #   esperanza_de_vida_2002 <dbl>, esperanza_de_vida_2007 <dbl>,
## #   poblacion_1952 <dbl>, poblacion_1957 <dbl>, poblacion_1962 <dbl>,
## #   poblacion_1967 <dbl>, poblacion_1972 <dbl>, poblacion_1977 <dbl>,
## #   poblacion_1982 <dbl>, poblacion_1987 <dbl>, poblacion_1992 <dbl>,
## #   poblacion_1997 <dbl>, poblacion_2002 <dbl>, poblacion_2007 <dbl>

¿Notaste que en el código anterior no usaste library(readr) para cargar el paquete y luego leer? Con la notación paquete::funcion() podés acceder a las funciones de un paquete sin tener que cargarlo. Es una buena forma de no tener que cargar un montón de paquetes innecesarios si vas a correr una única función de un paquete pocas veces.

Esta tabla, increíblemente ancha, es muy difícil de manejar. Por ejemplo, es imposible hacer una serie de tiempo de una variable, o calcular el promedio por variable y país; ni hablar de calcular una regresión lineal.

Para convertirlo en una tabla más larga, se usa pivot_longer() (“longer” es “más largo” en inglés):

paises_largo <- pivot_longer(paises_ancho,
                             cols = c(starts_with('pob'), 
                                      starts_with('esperanza'), 
                                      starts_with('pib_per')),
                             names_to = "variable_anio", 
                             values_to = "valor"
)
paises_largo
## # A tibble: 5,112 x 4
##    continente pais    variable_anio     valor
##    <chr>      <chr>   <chr>             <dbl>
##  1 Africa     Algeria poblacion_1952  9279525
##  2 Africa     Algeria poblacion_1957 10270856
##  3 Africa     Algeria poblacion_1962 11000948
##  4 Africa     Algeria poblacion_1967 12760499
##  5 Africa     Algeria poblacion_1972 14760787
##  6 Africa     Algeria poblacion_1977 17152804
##  7 Africa     Algeria poblacion_1982 20033753
##  8 Africa     Algeria poblacion_1987 23254956
##  9 Africa     Algeria poblacion_1992 26298373
## 10 Africa     Algeria poblacion_1997 29072015
## # … with 5,102 more rows

El primer argumento depivot_longer() es la tabla que va a modificar: paises_ancho. El segundo argumento se llama cols y es un vector con las columnas que tienen los valores a “alargar”. Podría ser un vector escrito a mano (algo como c("pib_per_capita_1952", "pib_per_capita_1957"...)) pero con más de 30 columnas, escribir todo eso sería tedioso y probablemente estaría lleno de errores. Por eso {tidyr} provee funciones de ayuda para seleccionar columnas en base a patrones. El código de arriba usa starts_with() que, como su nombre en inglés lo indica, selecciona las columnas que empiezan con una determinada cadena de caracteres. El vector c(starts_with('pob'), starts_with('esperanza'), starts_with('pib_per')) le dice a pivot_longer() que seleccione las columnas que empieza con “pob”, las que empiezan con “esperanza” y las que empiezan con “pib_per”.

Estas funciones accesorias para seleccionar muchas funciones se llaman “tidyselect”. Si querés leer más detalles de las distintas formas que podés seleccionar variables leé la documentación usando ?tidyselect::language.

El tercer y cuarto argumento son los nombres de las columnas de “nombre” y de “valor” que va a tener la nueva tabla. Como la nueva columna de identificación tiene los datos de la variable y el año a medir, “variable_anio” es un buen nombre. Y la columna de valor va a tener… bueno, el valor.

Tomate un momento para visualizar lo que acaba de pasar. La tabla ancha tenía un montón de columnas con distintos datos. Ahora estos datos están uno arriba de otro en la columna “valor”, pero para identificar el nombre de la columna de la cual vinieron, se agrega la columna “variable_anio”.

Proceso de largo a ancho

La columna variable_anio todavía no es muy útil porque contiene 2 datos, la variable (población, expectativa de vida o PBI per cápita) y el año. Sería mejor separar esta información en dos columnas llamadas “variable” y “anio”. Para eso está la función separate().

separate(paises_largo, 
         col = variable_anio, 
         into = c("variable", "anio"), 
         sep = -4)
## # A tibble: 5,112 x 5
##    continente pais    variable   anio     valor
##    <chr>      <chr>   <chr>      <chr>    <dbl>
##  1 Africa     Algeria poblacion_ 1952   9279525
##  2 Africa     Algeria poblacion_ 1957  10270856
##  3 Africa     Algeria poblacion_ 1962  11000948
##  4 Africa     Algeria poblacion_ 1967  12760499
##  5 Africa     Algeria poblacion_ 1972  14760787
##  6 Africa     Algeria poblacion_ 1977  17152804
##  7 Africa     Algeria poblacion_ 1982  20033753
##  8 Africa     Algeria poblacion_ 1987  23254956
##  9 Africa     Algeria poblacion_ 1992  26298373
## 10 Africa     Algeria poblacion_ 1997  29072015
## # … with 5,102 more rows

El primer argumento, como siempre, es la tabla a procesar. El segundo, col, es la columna a separar en dos (o más) columnas nuevas. El tercero, into es el nombre de las nuevas columnas que separate() va a crear. El último argumento es sep que define cómo realizar la separación. Por defecto, sep es una expresión regular que captura cualquier caracter no alfanumérico. En el caso de variable_anio no sirve, porque para valores como "esperanza_de_vida_1952", separaría en "esperanza", "de", "vida" y "1952". Como el año tiene siempre 4 caracteres, una solución simple es usar sep = -4, que significa que la separación es 4 caracteres contando desde el final.

Habrás notado un problema. El texto en la columnas variable todavía tiene un "_" al final. Podrías usar mutate() y un poco de funciones de manipulación de caracteres para quitarlo, pero hay una forma un poco más simple y es separando la columna variable_anio en tres, incluyendo una columna con el guión:

paises_largo <- separate(paises_largo, 
         col = variable_anio, 
         into = c("variable", "guion", "anio"), 
         sep = c(-5, -4))
paises_largo
## # A tibble: 5,112 x 6
##    continente pais    variable  guion anio     valor
##    <chr>      <chr>   <chr>     <chr> <chr>    <dbl>
##  1 Africa     Algeria poblacion _     1952   9279525
##  2 Africa     Algeria poblacion _     1957  10270856
##  3 Africa     Algeria poblacion _     1962  11000948
##  4 Africa     Algeria poblacion _     1967  12760499
##  5 Africa     Algeria poblacion _     1972  14760787
##  6 Africa     Algeria poblacion _     1977  17152804
##  7 Africa     Algeria poblacion _     1982  20033753
##  8 Africa     Algeria poblacion _     1987  23254956
##  9 Africa     Algeria poblacion _     1992  26298373
## 10 Africa     Algeria poblacion _     1997  29072015
## # … with 5,102 more rows

Y ya casi. Hay que eliminar la columna guion, que no sirve para nada. Pero fijate que debajo de la columna anio dice <chr>; eso significa que el tipo de la columna es caracter, pero los años son números. Usando mutate() podés eliminar la columna guion asignándole el valor NULL (nulo) y coercer (recordá esta sección) la columna anio a entero usando as.integer():

paises_largo <- mutate(paises_largo, 
                       guion = NULL,
                       anio = as.integer(anio))
paises_largo
## # A tibble: 5,112 x 5
##    continente pais    variable   anio    valor
##    <chr>      <chr>   <chr>     <int>    <dbl>
##  1 Africa     Algeria poblacion  1952  9279525
##  2 Africa     Algeria poblacion  1957 10270856
##  3 Africa     Algeria poblacion  1962 11000948
##  4 Africa     Algeria poblacion  1967 12760499
##  5 Africa     Algeria poblacion  1972 14760787
##  6 Africa     Algeria poblacion  1977 17152804
##  7 Africa     Algeria poblacion  1982 20033753
##  8 Africa     Algeria poblacion  1987 23254956
##  9 Africa     Algeria poblacion  1992 26298373
## 10 Africa     Algeria poblacion  1997 29072015
## # … with 5,102 more rows

Desafío

Juntá todos los pasos anteriores en una sola cadena de operaciones usando %>%.

De largo a ancho con pivot_wider()

Ahora la variable paises_largo está en el formato más largo posible. Tiene 5 columnas, de las cuales sólo una es la columnas con valores. Pero con los datos así no podrías hacer un gráfico de puntos que muestre la relación entre el PBI per cápita y la expectativa de vida como en la sección de gráficos. Fijate que los valores de la columna valor no tienen todos las mismas unidades, por lo que operar con ese vector podría dar resultados sin sentido. Muchas veces es conveniente y natural tener los datos en un formato intermedio en donde hay múltiples columnas con los valores de distintas variables observadas.

Pasa “ensanchar” una tabla está la función pivot_wider() (“wider” es “más ancha” en inglés) y el código para conseguir este formato intermedio es:

paises_medio <- pivot_wider(paises_largo, names_from = variable, values_from = valor)
paises_medio
## # A tibble: 1,704 x 6
##    continente pais     anio poblacion esperanza_de_vida pib_per_capita
##    <chr>      <chr>   <int>     <dbl>             <dbl>          <dbl>
##  1 Africa     Algeria  1952   9279525              43.1          2449.
##  2 Africa     Algeria  1957  10270856              45.7          3014.
##  3 Africa     Algeria  1962  11000948              48.3          2551.
##  4 Africa     Algeria  1967  12760499              51.4          3247.
##  5 Africa     Algeria  1972  14760787              54.5          4183.
##  6 Africa     Algeria  1977  17152804              58.0          4910.
##  7 Africa     Algeria  1982  20033753              61.4          5745.
##  8 Africa     Algeria  1987  23254956              65.8          5681.
##  9 Africa     Algeria  1992  26298373              67.7          5023.
## 10 Africa     Algeria  1997  29072015              69.2          4797.
## # … with 1,694 more rows

Nuevamente el primer argumento es la tabla original. El segundo, names_from es la columna cuyos valores únicos van a convertirse en nuevas columnas. La columna variable tiene los valores "población", "esperanza_de_vida" y "pib_per_capita" y entonces la tabla nueva tendrá tres columnas con esos nombres. El tercer argumento, values_from, es la columna de la cual sacar los valores.

Para volver al formato más ancho, basta con agregar más columnas en el argumento names_from:

pivot_wider(paises_largo, 
            names_from = c(variable, anio), 
            names_sep = "_",
            values_from = valor)
## # A tibble: 142 x 38
##    continente pais  poblacion_1952 poblacion_1957 poblacion_1962 poblacion_1967
##    <chr>      <chr>          <dbl>          <dbl>          <dbl>          <dbl>
##  1 Africa     Alge…        9279525       10270856       11000948       12760499
##  2 Africa     Ango…        4232095        4561361        4826015        5247469
##  3 Africa     Benin        1738315        1925173        2151895        2427334
##  4 Africa     Bots…         442308         474639         512764         553541
##  5 Africa     Burk…        4469979        4713416        4919632        5127935
##  6 Africa     Buru…        2445618        2667518        2961915        3330989
##  7 Africa     Came…        5009067        5359923        5793633        6335506
##  8 Africa     Cent…        1291695        1392284        1523478        1733638
##  9 Africa     Chad         2682462        2894855        3150417        3495967
## 10 Africa     Como…         153936         170928         191689         217378
## # … with 132 more rows, and 32 more variables: poblacion_1972 <dbl>,
## #   poblacion_1977 <dbl>, poblacion_1982 <dbl>, poblacion_1987 <dbl>,
## #   poblacion_1992 <dbl>, poblacion_1997 <dbl>, poblacion_2002 <dbl>,
## #   poblacion_2007 <dbl>, esperanza_de_vida_1952 <dbl>,
## #   esperanza_de_vida_1957 <dbl>, esperanza_de_vida_1962 <dbl>,
## #   esperanza_de_vida_1967 <dbl>, esperanza_de_vida_1972 <dbl>,
## #   esperanza_de_vida_1977 <dbl>, esperanza_de_vida_1982 <dbl>,
## #   esperanza_de_vida_1987 <dbl>, esperanza_de_vida_1992 <dbl>,
## #   esperanza_de_vida_1997 <dbl>, esperanza_de_vida_2002 <dbl>,
## #   esperanza_de_vida_2007 <dbl>, pib_per_capita_1952 <dbl>,
## #   pib_per_capita_1957 <dbl>, pib_per_capita_1962 <dbl>,
## #   pib_per_capita_1967 <dbl>, pib_per_capita_1972 <dbl>,
## #   pib_per_capita_1977 <dbl>, pib_per_capita_1982 <dbl>,
## #   pib_per_capita_1987 <dbl>, pib_per_capita_1992 <dbl>,
## #   pib_per_capita_1997 <dbl>, pib_per_capita_2002 <dbl>,
## #   pib_per_capita_2007 <dbl>

En esta llamada también está el argumento names_sep, que determina el caracter que se usa para crear el nombre de las nuevas columnas.

Desafío

  • Creá una nueva tabla, llamada paises_superduper_ancho que tenga una columna para cada variable, anio y país. (Consejo: la tabla final tiene que tener 5 filas).

  • ¿Cómo es la tabla más ancha posible que podés generar con estos datos? ¿Cuántas filas y columnas tiene?

Uniendo tablas

Hasta ahora todo lo que usaste de {dplyr} involucra trabajar y modificar con una sola tabla a la vez, pero es muy común tener dos o más tablas con datos relacionados. En ese caso, tenemos que unir estas tablas. a partir de una o más variables en común o keys. En Excel u otro programa de hojas de cálculo, esto se resuelve con la función “VLOOKUP” o “BUSCARV”, en R y en particular dentro del mundo de {dplyr} hay que usar la familia de funciones *_join(). Hay una función cada tipo de unión que queramos hacer.

Asumiendo que querés unir dos data.frames o tablas x e y que tienen en común una variable A:

Ahora vamos a seguir trabajando con las base de datos de paises pero nos vamos a quedar solo con las observaciones del 2007 y de paso unirlo a una nueva base de datos co2 que contiene información de la emisión de dióxido de carbono de cada país para ese mismo año.

paises_2007 <- readr::read_csv("datos/paises.csv") %>% 
  filter(anio == 2007) 

co2_2007 <- readr::read_csv("datos/co2_2007.csv")
co2_2007
## # A tibble: 218 x 3
##    codigo_iso emision_co2 pais                  
##    <chr>            <dbl> <chr>                 
##  1 ABW            27.9    Aruba                 
##  2 AFG             0.0854 Afghanistán           
##  3 AGO             1.20   Angola                
##  4 ALB             1.32   Albania               
##  5 AND             6.52   Andorra               
##  6 ARB             4.10   Mundo Árabe           
##  7 ARE            22.4    Emiratos Árabes Unidos
##  8 ARG             4.38   Argentina             
##  9 ARM             1.73   Armenia               
## 10 ATG             5.14   Antigua y Barbuda     
## # … with 208 more rows

Esta nueva tabla tiene 3 columnas: codigo_iso tiene el código ISO de 3 letras de (abreviaturas que se usan internacionalmente), emision_co2 tiene las emisiones anuales per cápita de CO2 en toneladas, pais tiene el nombre del país. Esta última columna también está presente en la tabla paises_2007 y es la que va a servir como variable llave para unir las dos tablas.

Para unir las dos tablas, cualquier función join requiere cierta información:

Unamos paises_2007 y co2_2007 primero con full_join():

paises_co2_2007 <- full_join(paises_2007, co2_2007, by = "pais")
paises_co2_2007
## # A tibble: 241 x 8
##    pais  continente  anio esperanza_de_vi… poblacion pib_per_capita codigo_iso
##    <chr> <chr>      <dbl>            <dbl>     <dbl>          <dbl> <chr>     
##  1 Afga… Asia        2007             43.8  31889923           975. <NA>      
##  2 Alba… Europa      2007             76.4   3600523          5937. ALB       
##  3 Arge… África      2007             72.3  33333216          6223. <NA>      
##  4 Ango… África      2007             42.7  12420476          4797. AGO       
##  5 Arge… Américas    2007             75.3  40301927         12779. ARG       
##  6 Aust… Oceanía     2007             81.2  20434176         34435. AUS       
##  7 Aust… Europa      2007             79.8   8199783         36126. AUT       
##  8 Baré… Asia        2007             75.6    708573         29796. <NA>      
##  9 Bang… Asia        2007             64.1 150448339          1391. BGD       
## 10 Bélg… Europa      2007             79.4  10392226         33693. BEL       
## # … with 231 more rows, and 1 more variable: emision_co2 <dbl>

Si miramos de cerca la tabla unida veremos un par de cosas:

Esta es la opción más segura si no sabemos si todas las observaciones de una tabla están presente en a otra.

Si solo nos interesa conservar las filas de la tabla de la izquierda, en este caso paises_2007 entonces:

paises_co2_2007 <- left_join(paises_2007, co2_2007, by = "pais")
paises_co2_2007
## # A tibble: 142 x 8
##    pais  continente  anio esperanza_de_vi… poblacion pib_per_capita codigo_iso
##    <chr> <chr>      <dbl>            <dbl>     <dbl>          <dbl> <chr>     
##  1 Afga… Asia        2007             43.8  31889923           975. <NA>      
##  2 Alba… Europa      2007             76.4   3600523          5937. ALB       
##  3 Arge… África      2007             72.3  33333216          6223. <NA>      
##  4 Ango… África      2007             42.7  12420476          4797. AGO       
##  5 Arge… Américas    2007             75.3  40301927         12779. ARG       
##  6 Aust… Oceanía     2007             81.2  20434176         34435. AUS       
##  7 Aust… Europa      2007             79.8   8199783         36126. AUT       
##  8 Baré… Asia        2007             75.6    708573         29796. <NA>      
##  9 Bang… Asia        2007             64.1 150448339          1391. BGD       
## 10 Bélg… Europa      2007             79.4  10392226         33693. BEL       
## # … with 132 more rows, and 1 more variable: emision_co2 <dbl>

Ahora esperamos que la tabla resultante tenga la misma cantidad de filas que paises_2007 y efectivamente eso ocurre. Pero al mismo tiempo varios países en esa tabla no encontraron coincidencia en co2_2007 y por esa razón, la columna nueva columna emisiones_co2 tiene NA.

Finalmente, si quisiéramos quedarnos solo con las observaciones que están presentes en ambas tablas usamos inner_join().

paises_co2_2007 <- inner_join(paises_2007, co2_2007, by = "pais")
paises_co2_2007
## # A tibble: 119 x 8
##    pais  continente  anio esperanza_de_vi… poblacion pib_per_capita codigo_iso
##    <chr> <chr>      <dbl>            <dbl>     <dbl>          <dbl> <chr>     
##  1 Alba… Europa      2007             76.4   3600523          5937. ALB       
##  2 Ango… África      2007             42.7  12420476          4797. AGO       
##  3 Arge… Américas    2007             75.3  40301927         12779. ARG       
##  4 Aust… Oceanía     2007             81.2  20434176         34435. AUS       
##  5 Aust… Europa      2007             79.8   8199783         36126. AUT       
##  6 Bang… Asia        2007             64.1 150448339          1391. BGD       
##  7 Bélg… Europa      2007             79.4  10392226         33693. BEL       
##  8 Boli… Américas    2007             65.6   9119152          3822. BOL       
##  9 Bosn… Europa      2007             74.9   4552198          7446. BIH       
## 10 Bots… África      2007             50.7   1639131         12570. BWA       
## # … with 109 more rows, and 1 more variable: emision_co2 <dbl>

En este caso, perdemos las filas de co2_2007 que no encontraron coincidencia en paises_2007 y viceversa y la tabla resultante tiene aún menos filas (119).

Desafío

Estuvimos trabajando con una parte de la base de datos de emisiones. Pero también está disponible co2_completo.csv que contiene las emisiones para distintos años. El objetivo es que unas paises y co2 teniendo en cuenta tanto el país como el año. Para eso:

  1. Lee la base de datos co2_completo.csv en una nueva variable que se llame co2.
  2. Revisá el nombre de las variables en esta base de datos, ¿se llaman igual que las variables en paises?
  3. Uní las dos tablas usando full_join(), tené en cuenta que ahora usamos dos variables llave pais y anio. Buscá en la documentación cómo indicarle eso a la función full_join().
LS0tCnRpdGxlOiAiTWFuaXB1bGFjacOzbiBkZSBkYXRvcyBvcmRlbmFkb3MgdXNhbmRvIHtkcGx5cn0geSB7dGlkeXJ9IElJIgpvdXRwdXQ6IAogIGh0bWxfZG9jdW1lbnQ6CiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogZmFsc2UKICAgIGhpZ2hsaWdodDogdGFuZ28gIAotLS0KCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCmBgYAoKCkVuIFtsYSDDumx0aW1hIHNlY2Npw7NuIGRlIGxlY3R1cmEgZGUgZGF0b3NdKDA0LWxlY3R1cmEtZGF0b3MuaHRtbCNGb3JtYXRvc19kZV90YWJsYXMpIHZpc3RlIGVsIGNvbmNlcHRvIGRlIGRhdG9zICJhbmNob3MiIHkgImxhcmdvcyIuIAoKTG9zIGRhdG9zIGVuIGZvcm1hdG8gImxhcmdvIiBvICJ0aWR5Iiwgc29uIGFxdWVsbG9zIGVuIGxvcyBjdWFsZXM6CgoqIGNhZGEgZmlsYSBlcyB1bmEgb2JzZXJ2YWNpw7NuCiogY2FkYSBjb2x1bW5hIGVzIHVuYSB2YXJpYWJsZQoKRW4gZWwgZm9ybWF0byAiYW5jaG8iIGVzIHVuIHBvY28gbcOhcyBjb21wbGVqbyBkZSBkZWZpbmlybG8gcGVybyBsYSBpZGVhIGdlbmVyYWwgZXMgcXVlOgoKKiBjYWRhIGZpbGEgZXMgdW4gIml0ZW0iCiogY2FkYSBjb2x1bW5hIGVzIHVuYSB2YXJpYWJsZQoKIVtdKGltZy9sYXJnby1hbmNoby5wbmcpCgpVbmEgdGFibGEgZW4gZm9ybWF0byBsYXJnbyB2YSBhIHRlbmVyIHVuYSBjaWVydGEgY2FudGlkYWQgZGUgY29sdW1uYXMgcXVlIGN1bXBsZW4gZWwgcm9sIGRlICppZGVudGlmaWNhZG9yZXMgKiB5IGN1eWEgY29tYmluYWNpw7NuIGlkZW50aWZpY2FuIHVuYSDDum5pY2Egb2JzZXJ2YWNpw7NuIHkgdW5hIMO6bmljYSBjb2x1bW5hIGNvbiBlbCB2YWxvciBkZSBsYSBvYnNlcnZhY2nDs24uIEVuIGVsIGVqZW1wbG8gZGUgYXJyaWJhLCBgcGFpc2AgeSBgYW5pb2Agc29uIGxhcyBjb2x1bW5hcyBpZGVudGlmaWNhZG9yYXMgeSBgY2Fzb3NgIGVzIGxhIGNvbHVtbmEgcXVlIGNvbnRpZW5lIGVsIHZhbG9yIGRlIGxhcyBvYnNlcnZhY2lvbmVzLiAKCkVuIHVuYSB0YWJsYSBhbmNoYSwgY2FkYSBvYnNlcnZhY2nDs24gw7puaWNhIHNlIGlkZW50aWZpY2EgYSBwYXJ0aXIgZGUgbGEgaW50ZXJzZWNjacOzbiBkZSBmaWxhcyB5IGNvbHVtbmFzLiBFbiBlbCBlamVtcGxvLCBsb3MgcGHDrXNlcyBlc3TDoW4gZW4gbGFzIGZpbGFzIHkgbG9zIGHDsW9zIGVuIGxhcyBjb2x1bW5hcy4gCgpFbiBnZW5lcmFsLCBlbCBmb3JtYXRvIGFuY2hvIGVzIG3DoXMgY29tcGFjdG8geSBsZWdpYmxlIHBvciBodW1hbm9zIG1pZW50cmFzIHF1ZSBlbCBsYXJnbyBlcyBtw6FzIGbDoWNpbCBkZSBtYW5lamFyIGNvbiBsYSBjb21wdXRhZG9yYS4gU2kgdGUgZmlqw6FzIGVuIGxhcyB0YWJsYXMgZGUgYXJyaWJhLCBlcyBtw6FzIGbDoWNpbCBjb21wYXJhciBsb3MgdmFsb3JlcyBlbnRyZSBwYcOtc2VzIHkgZW50cmUgYcOxb3MgZW4gbGEgdGFibGEgYW5jaGEuIFBlcm8gZWwgbm9tYnJlIGRlIGxhcyBjb2x1bW5hcyAoIjE5OTkiLCAiMjAwMCIpIGVuIHJlYWxpZGFkIMKhc29uIGRhdG9zISBBZGVtw6FzIGVzdGUgZm9ybWF0byBzZSBlbXBpZXphIGEgY29tcGxpY2FyIGVuIGN1YW50byBoYXkgbcOhcyBkZSBkb3MgaWRlbnRpZmljYWRvcmVzLiAKClVuIG1pc21vIHNldCBkZSBkYXRvcyBwdWVkZSBzZXIgcmVwcmVzZW50YWRvIGRlIGZvcm1hIGNvbXBsZXRhbWVudGUgImxhcmdhIiwgY29tcGxldGFtZW50ZSAiYW5jaGEiIG8gLS1sbyBxdWUgZXMgbcOhcyBjb23Dum4tLSBlbiB1biBmb3JtYXRvIGludGVybWVkaW8gcGVybyBubyBleGlzdGUgdW5hIGZvcm1hICJjb3JyZWN0YSIgZGUgb3JnYW5pemFyIGxvcyBkYXRvczsgY2FkYSB1bmEgdGllbmUgc3VzIHZlbnRhamFzIHkgZGVzdmVudGFqYXMuIFBvciBlc3RvIGVzIHF1ZSBlcyBtdXkgbm9ybWFsIHF1ZSBkdXJhbnRlIHVuIGFuw6FsaXNpcyBsb3MgZGF0b3MgdmF5YW4geSB2dWVsdmFuIGVudHJlIGRpc3RpbnRvcyBmb3JtYXRvcyBkZXBlbmRpZW5kbyBkZSBsb3MgbcOpdG9kb3MgZXN0YWTDrXN0aWNvcyBxdWUgc2UgbGUgYXBsaWNhbi4gRW50b25jZXMsIGFwcmVuZGVyIGEgdHJhbnNmb3JtYXIgZGF0b3MgYW5jaG9zIGVuIGxhcmdvcyB5IHZpY2V2ZXJzYSBlcyB1biBoYWJpbGlkYWQgbXV5IMO6dGlsLiAKCgo6Ojogey5hbGVydCAuYWxlcnQtaW5mb30KKipEZXNhZsOtbyoqCgpFbiBsYXMgdGFibGFzIGRlIGVqZW1wbG8gY2FkYSBwYcOtcyB0aWVuZSBlbCB1biB2YWxvciBvYnNlcnZhZG8gZGUgImNhc29zIiBwYXJhIGNhZGEgYcOxby4gwr9Dw7NtbyBhZ3JlZ2Fyw61hcyB1bmEgbnVldmEgdmFyaWFibGUgY29uIGluZm9ybWFjacOzbiBzb2JyZSAicHJlY2lvcyI/IERpYnVqw6EgdW4gZXNxdWVtYSBlbiBwYXBlbCB5IGzDoXBpeiBlbiBmb3JtYXRvIGFuY2hvIHkgdW5vIGVuIGZvcm1hdG8gbGFyZ28uIMK/RW4gcXXDqSBmb3JtYXRvIGVzIG3DoXMgIm5hdHVyYWwiIGVzYSBleHRlbnNpw7NuPwoKOjo6CgoKRW4gZXN0YSBzZWNjacOzbiB2YXMgYSB1c2FyIGVsIHBhcXVldGUge3RpZHlyfSBwYXJhIG1hbmlwdWxhciBkYXRvcy4gU2kgbm8gbG8gdGVuw6lzIGluc3RhbGFkbywgaW5zdGFsYWxvIGNvbiBlbCBjb21hbmRvOgoKYGBge3IgZXZhbCA9IEZBTFNFfQppbnN0YWxsLnBhY2thZ2VzKCJ0aWR5ciIpCmBgYAoKKGNvbW8gc2llbXByZSwgcmVjb3Jkw6EgcXVlIGVzdG8gaGF5IHF1ZSBoYWNlcmxvIHVuYSDDum5pY2EgdmV6KQoKWSBsdWVnbyBjYXJnw6Ege3RpZHlyfSB5IHtkcGx5cn0gKHF1ZSB1c2FzdGUgZW4gW3VuYSBzZWNjacOzbiBhbnRlcmlvcl0oMDUtZHBseXItSS5odG1sKSkgY29uOiAKCmBgYHtyfQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KGRwbHlyKQpgYGAKCiMjIERlIGFuY2hvIGEgbGFyZ28gY29uIGBwaXZvdF9sb25nZXIoKWAKCkVuIHNlY2Npb25lcyBhbnRlcmlvcmVzIHVzYXN0ZSB1bmEgdmVyc2nDs24gZGUgbG9zIGRhdG9zIGRlIFtnYXBtaW5kZXJdKGh0dHBzOi8vd3d3LmdhcG1pbmRlci5vcmcvKS4gQWhvcmEgdmFzIGEgbGVlciBsb3MgZGF0b3MgZW4gc3UgZm9ybWF0byBvcmlnaW5hbDoKCmBgYHtyfQpwYWlzZXNfYW5jaG8gPC0gcmVhZHI6OnJlYWRfY3N2KCJkYXRvcy9wYWlzZXNfYW5jaG8uY3N2IikKcGFpc2VzX2FuY2hvCmBgYAoKOjo6IHsuYWxlcnQgLmFsZXJ0LXN1Y2Nlc3N9CgrCv05vdGFzdGUgcXVlIGVuIGVsIGPDs2RpZ28gYW50ZXJpb3Igbm8gdXNhc3RlIGBsaWJyYXJ5KHJlYWRyKWAgcGFyYSBjYXJnYXIgZWwgcGFxdWV0ZSB5IGx1ZWdvIGxlZXI/IENvbiBsYSBub3RhY2nDs24gYHBhcXVldGU6OmZ1bmNpb24oKWAgcG9kw6lzIGFjY2VkZXIgYSBsYXMgZnVuY2lvbmVzIGRlIHVuIHBhcXVldGUgc2luIHRlbmVyIHF1ZSBjYXJnYXJsby4gRXMgdW5hIGJ1ZW5hIGZvcm1hIGRlIG5vIHRlbmVyIHF1ZSBjYXJnYXIgdW4gbW9udMOzbiBkZSBwYXF1ZXRlcyBpbm5lY2VzYXJpb3Mgc2kgdmFzIGEgY29ycmVyIHVuYSDDum5pY2EgZnVuY2nDs24gZGUgdW4gcGFxdWV0ZSBwb2NhcyB2ZWNlcy4gCgo6OjoKCkVzdGEgdGFibGEsIGluY3Jlw61ibGVtZW50ZSBhbmNoYSwgZXMgbXV5IGRpZsOtY2lsIGRlIG1hbmVqYXIuIFBvciBlamVtcGxvLCBlcyBpbXBvc2libGUgaGFjZXIgdW5hIHNlcmllIGRlIHRpZW1wbyBkZSB1bmEgdmFyaWFibGUsIG8gY2FsY3VsYXIgZWwgcHJvbWVkaW8gcG9yIHZhcmlhYmxlIHkgcGHDrXM7IG5pIGhhYmxhciBkZSBjYWxjdWxhciB1bmEgcmVncmVzacOzbiBsaW5lYWwuIAoKUGFyYSBjb252ZXJ0aXJsbyBlbiB1bmEgdGFibGEgbcOhcyBsYXJnYSwgc2UgdXNhIGBwaXZvdF9sb25nZXIoKWAgKCJsb25nZXIiIGVzICJtw6FzIGxhcmdvIiBlbiBpbmdsw6lzKToKCmBgYHtyfQpwYWlzZXNfbGFyZ28gPC0gcGl2b3RfbG9uZ2VyKHBhaXNlc19hbmNobywKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xzID0gYyhzdGFydHNfd2l0aCgncG9iJyksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0YXJ0c193aXRoKCdlc3BlcmFuemEnKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RhcnRzX3dpdGgoJ3BpYl9wZXInKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmFtZXNfdG8gPSAidmFyaWFibGVfYW5pbyIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJ2YWxvciIKKQpwYWlzZXNfbGFyZ28KYGBgCgpFbCBwcmltZXIgYXJndW1lbnRvIGRlYHBpdm90X2xvbmdlcigpYCBlcyBsYSB0YWJsYSBxdWUgdmEgYSBtb2RpZmljYXI6IGBwYWlzZXNfYW5jaG9gLiBFbCBzZWd1bmRvIGFyZ3VtZW50byBzZSBsbGFtYSBgY29sc2AgeSBlcyB1biB2ZWN0b3IgY29uIGxhcyBjb2x1bW5hcyBxdWUgdGllbmVuIGxvcyB2YWxvcmVzIGEgImFsYXJnYXIiLiBQb2Ryw61hIHNlciB1biB2ZWN0b3IgZXNjcml0byBhIG1hbm8gKGFsZ28gY29tbyBgYygicGliX3Blcl9jYXBpdGFfMTk1MiIsICJwaWJfcGVyX2NhcGl0YV8xOTU3Ii4uLilgKSBwZXJvIGNvbiBtw6FzIGRlIDMwIGNvbHVtbmFzLCBlc2NyaWJpciB0b2RvIGVzbyBzZXLDrWEgdGVkaW9zbyB5IHByb2JhYmxlbWVudGUgZXN0YXLDrWEgbGxlbm8gZGUgZXJyb3Jlcy4gUG9yIGVzbyB7dGlkeXJ9IHByb3ZlZSBmdW5jaW9uZXMgZGUgYXl1ZGEgcGFyYSBzZWxlY2Npb25hciBjb2x1bW5hcyBlbiBiYXNlIGEgcGF0cm9uZXMuIEVsIGPDs2RpZ28gZGUgYXJyaWJhIHVzYSBgc3RhcnRzX3dpdGgoKWAgcXVlLCBjb21vIHN1IG5vbWJyZSBlbiBpbmdsw6lzIGxvIGluZGljYSwgc2VsZWNjaW9uYSBsYXMgY29sdW1uYXMgcXVlICplbXBpZXphbiBjb24qIHVuYSBkZXRlcm1pbmFkYSBjYWRlbmEgZGUgY2FyYWN0ZXJlcy4gRWwgdmVjdG9yIGBjKHN0YXJ0c193aXRoKCdwb2InKSwgc3RhcnRzX3dpdGgoJ2VzcGVyYW56YScpLCBzdGFydHNfd2l0aCgncGliX3BlcicpKWAgbGUgZGljZSBhIGBwaXZvdF9sb25nZXIoKWAgcXVlIHNlbGVjY2lvbmUgbGFzIGNvbHVtbmFzIHF1ZSBlbXBpZXphIGNvbiAicG9iIiwgbGFzIHF1ZSBlbXBpZXphbiBjb24gImVzcGVyYW56YSIgeSBsYXMgcXVlIGVtcGllemFuIGNvbiAicGliX3BlciIuIAoKOjo6IHsuYWxlcnQgLmFsZXJ0LXN1Y2Nlc3N9CgpFc3RhcyBmdW5jaW9uZXMgYWNjZXNvcmlhcyBwYXJhIHNlbGVjY2lvbmFyIG11Y2hhcyBmdW5jaW9uZXMgc2UgbGxhbWFuICJ0aWR5c2VsZWN0Ii4gU2kgcXVlcsOpcyBsZWVyIG3DoXMgZGV0YWxsZXMgZGUgbGFzIGRpc3RpbnRhcyBmb3JtYXMgcXVlIHBvZMOpcyBzZWxlY2Npb25hciB2YXJpYWJsZXMgbGXDqSBsYSBkb2N1bWVudGFjacOzbiB1c2FuZG8gYD90aWR5c2VsZWN0OjpsYW5ndWFnZWAuCgo6OjoKCkVsIHRlcmNlciB5IGN1YXJ0byBhcmd1bWVudG8gc29uIGxvcyBub21icmVzIGRlIGxhcyBjb2x1bW5hcyBkZSAibm9tYnJlIiB5IGRlICJ2YWxvciIgcXVlIHZhIGEgdGVuZXIgbGEgbnVldmEgdGFibGEuIENvbW8gbGEgbnVldmEgY29sdW1uYSBkZSBpZGVudGlmaWNhY2nDs24gdGllbmUgbG9zIGRhdG9zIGRlIGxhIHZhcmlhYmxlIHkgZWwgYcOxbyBhIG1lZGlyLCAidmFyaWFibGVfYW5pbyIgZXMgdW4gYnVlbiBub21icmUuIFkgbGEgY29sdW1uYSBkZSB2YWxvciB2YSBhIHRlbmVyLi4uIGJ1ZW5vLCBlbCB2YWxvci4gCgpUb21hdGUgdW4gbW9tZW50byBwYXJhIHZpc3VhbGl6YXIgbG8gcXVlIGFjYWJhIGRlIHBhc2FyLiBMYSB0YWJsYSBhbmNoYSB0ZW7DrWEgdW4gbW9udMOzbiBkZSBjb2x1bW5hcyBjb24gZGlzdGludG9zIGRhdG9zLiBBaG9yYSBlc3RvcyBkYXRvcyBlc3TDoW4gdW5vIGFycmliYSBkZSBvdHJvIGVuIGxhIGNvbHVtbmEgInZhbG9yIiwgcGVybyBwYXJhIGlkZW50aWZpY2FyIGVsIG5vbWJyZSBkZSBsYSBjb2x1bW5hIGRlIGxhIGN1YWwgdmluaWVyb24sIHNlIGFncmVnYSBsYSBjb2x1bW5hICJ2YXJpYWJsZV9hbmlvIi4gCgoKIVtQcm9jZXNvIGRlIGxhcmdvIGEgYW5jaG9dKGltZy9hbmNoby1hLWxhcmdvLnBuZykKCkxhIGNvbHVtbmEgYHZhcmlhYmxlX2FuaW9gIHRvZGF2w61hIG5vIGVzIG11eSDDunRpbCBwb3JxdWUgY29udGllbmUgMiBkYXRvcywgbGEgdmFyaWFibGUgKHBvYmxhY2nDs24sIGV4cGVjdGF0aXZhIGRlIHZpZGEgbyBQQkkgcGVyIGPDoXBpdGEpIHkgZWwgYcOxby4gU2Vyw61hIG1lam9yIHNlcGFyYXIgZXN0YSBpbmZvcm1hY2nDs24gZW4gZG9zIGNvbHVtbmFzIGxsYW1hZGFzICJ2YXJpYWJsZSIgeSAiYW5pbyIuIFBhcmEgZXNvIGVzdMOhIGxhIGZ1bmNpw7NuIGBzZXBhcmF0ZSgpYC4KCmBgYHtyfQpzZXBhcmF0ZShwYWlzZXNfbGFyZ28sIAogICAgICAgICBjb2wgPSB2YXJpYWJsZV9hbmlvLCAKICAgICAgICAgaW50byA9IGMoInZhcmlhYmxlIiwgImFuaW8iKSwgCiAgICAgICAgIHNlcCA9IC00KQpgYGAKCkVsIHByaW1lciBhcmd1bWVudG8sIGNvbW8gc2llbXByZSwgZXMgbGEgdGFibGEgYSBwcm9jZXNhci4gRWwgc2VndW5kbywgYGNvbGAsIGVzIGxhIGNvbHVtbmEgYSBzZXBhcmFyIGVuIGRvcyAobyBtw6FzKSBjb2x1bW5hcyBudWV2YXMuIEVsIHRlcmNlcm8sIGBpbnRvYCBlcyBlbCBub21icmUgZGUgbGFzIG51ZXZhcyBjb2x1bW5hcyBxdWUgYHNlcGFyYXRlKClgIHZhIGEgY3JlYXIuIEVsIMO6bHRpbW8gYXJndW1lbnRvIGVzIGBzZXBgIHF1ZSBkZWZpbmUgY8OzbW8gcmVhbGl6YXIgbGEgc2VwYXJhY2nDs24uIFBvciBkZWZlY3RvLCBgc2VwYCBlcyB1bmEgW2V4cHJlc2nDs24gcmVndWxhcl0oaHR0cHM6Ly9lcy53aWtpcGVkaWEub3JnL3dpa2kvRXhwcmVzaSVDMyVCM25fcmVndWxhcikgcXVlIGNhcHR1cmEgY3VhbHF1aWVyIGNhcmFjdGVyIG5vIGFsZmFudW3DqXJpY28uIEVuIGVsIGNhc28gZGUgYHZhcmlhYmxlX2FuaW9gIG5vIHNpcnZlLCBwb3JxdWUgcGFyYSB2YWxvcmVzIGNvbW8gYCJlc3BlcmFuemFfZGVfdmlkYV8xOTUyImAsIHNlcGFyYXLDrWEgZW4gYCJlc3BlcmFuemEiYCwgYCJkZSJgLCBgInZpZGEiYCB5IGAiMTk1MiJgLiBDb21vIGVsIGHDsW8gdGllbmUgc2llbXByZSA0IGNhcmFjdGVyZXMsIHVuYSBzb2x1Y2nDs24gc2ltcGxlIGVzIHVzYXIgYHNlcCA9IC00YCwgcXVlIHNpZ25pZmljYSBxdWUgbGEgc2VwYXJhY2nDs24gZXMgNCBjYXJhY3RlcmVzIGNvbnRhbmRvIGRlc2RlIGVsIGZpbmFsLiAKCkhhYnLDoXMgbm90YWRvIHVuIHByb2JsZW1hLiBFbCB0ZXh0byBlbiBsYSBjb2x1bW5hcyBgdmFyaWFibGVgIHRvZGF2w61hIHRpZW5lIHVuICJfIiBhbCBmaW5hbC4gUG9kcsOtYXMgdXNhciBgbXV0YXRlKClgIHkgdW4gcG9jbyBkZSBmdW5jaW9uZXMgZGUgbWFuaXB1bGFjacOzbiBkZSBjYXJhY3RlcmVzIHBhcmEgcXVpdGFybG8sIHBlcm8gaGF5IHVuYSBmb3JtYSB1biBwb2NvIG3DoXMgc2ltcGxlIHkgZXMgc2VwYXJhbmRvIGxhIGNvbHVtbmEgYHZhcmlhYmxlX2FuaW9gIGVuIHRyZXMsIGluY2x1eWVuZG8gdW5hIGNvbHVtbmEgY29uIGVsIGd1acOzbjoKCmBgYHtyfQpwYWlzZXNfbGFyZ28gPC0gc2VwYXJhdGUocGFpc2VzX2xhcmdvLCAKICAgICAgICAgY29sID0gdmFyaWFibGVfYW5pbywgCiAgICAgICAgIGludG8gPSBjKCJ2YXJpYWJsZSIsICJndWlvbiIsICJhbmlvIiksIAogICAgICAgICBzZXAgPSBjKC01LCAtNCkpCnBhaXNlc19sYXJnbwpgYGAKClkgeWEgY2FzaS4gSGF5IHF1ZSBlbGltaW5hciBsYSBjb2x1bW5hIGBndWlvbmAsIHF1ZSBubyBzaXJ2ZSBwYXJhIG5hZGEuIFBlcm8gZmlqYXRlIHF1ZSBkZWJham8gZGUgbGEgY29sdW1uYSBgYW5pb2AgZGljZSBgPGNocj5gOyBlc28gc2lnbmlmaWNhIHF1ZSBlbCB0aXBvIGRlIGxhIGNvbHVtbmEgZXMgY2FyYWN0ZXIsIHBlcm8gbG9zIGHDsW9zIHNvbiBuw7ptZXJvcy4gVXNhbmRvIGBtdXRhdGUoKWAgcG9kw6lzIGVsaW1pbmFyIGxhIGNvbHVtbmEgYGd1aW9uYCBhc2lnbsOhbmRvbGUgZWwgdmFsb3IgYE5VTExgIChudWxvKSB5IGNvZXJjZXIgKHJlY29yZMOhIFtlc3RhIHNlY2Npw7NuXSgwNC1sZWN0dXJhLWRhdG9zLmh0bWwjVmVjdG9yZXMpKSBsYSBjb2x1bW5hIGBhbmlvYCBhIGVudGVybyB1c2FuZG8gYGFzLmludGVnZXIoKWA6CgpgYGB7cn0KcGFpc2VzX2xhcmdvIDwtIG11dGF0ZShwYWlzZXNfbGFyZ28sIAogICAgICAgICAgICAgICAgICAgICAgIGd1aW9uID0gTlVMTCwKICAgICAgICAgICAgICAgICAgICAgICBhbmlvID0gYXMuaW50ZWdlcihhbmlvKSkKcGFpc2VzX2xhcmdvCmBgYAoKOjo6IHsuYWxlcnQgLmFsZXJ0LWluZm99CioqRGVzYWbDrW8qKgoKSnVudMOhIHRvZG9zIGxvcyBwYXNvcyBhbnRlcmlvcmVzIGVuIHVuYSBzb2xhIGNhZGVuYSBkZSBvcGVyYWNpb25lcyB1c2FuZG8gYCU+JWAuCgo6OjoKCgoKIyMgRGUgbGFyZ28gYSBhbmNobyBjb24gYHBpdm90X3dpZGVyKClgCgpBaG9yYSBsYSB2YXJpYWJsZSBgcGFpc2VzX2xhcmdvYCBlc3TDoSBlbiBlbCBmb3JtYXRvIG3DoXMgbGFyZ28gcG9zaWJsZS4gVGllbmUgNSBjb2x1bW5hcywgZGUgbGFzIGN1YWxlcyBzw7NsbyB1bmEgZXMgbGEgY29sdW1uYXMgY29uIHZhbG9yZXMuIFBlcm8gY29uIGxvcyBkYXRvcyBhc8OtIG5vIHBvZHLDrWFzIGhhY2VyIHVuIGdyw6FmaWNvIGRlIHB1bnRvcyBxdWUgbXVlc3RyZSBsYSByZWxhY2nDs24gZW50cmUgZWwgUEJJIHBlciBjw6FwaXRhIHkgbGEgZXhwZWN0YXRpdmEgZGUgdmlkYSBjb21vIGVuIGxhIFtzZWNjacOzbiBkZSBncsOhZmljb3NdKDA2LWdyYWZpY29zLUkuaHRtbCNTZWd1bmRhX2NhcGE6X2dlb21ldHLDrWFzKS4gRmlqYXRlIHF1ZSBsb3MgdmFsb3JlcyBkZSBsYSBjb2x1bW5hIGB2YWxvcmAgbm8gdGllbmVuIHRvZG9zIGxhcyBtaXNtYXMgdW5pZGFkZXMsIHBvciBsbyBxdWUgb3BlcmFyIGNvbiBlc2UgdmVjdG9yIHBvZHLDrWEgZGFyIHJlc3VsdGFkb3Mgc2luIHNlbnRpZG8uIE11Y2hhcyB2ZWNlcyBlcyBjb252ZW5pZW50ZSB5IG5hdHVyYWwgdGVuZXIgbG9zIGRhdG9zIGVuIHVuIGZvcm1hdG8gaW50ZXJtZWRpbyBlbiBkb25kZSBoYXkgbcO6bHRpcGxlcyBjb2x1bW5hcyBjb24gbG9zIHZhbG9yZXMgZGUgZGlzdGludGFzIHZhcmlhYmxlcyBvYnNlcnZhZGFzLiAKClBhc2EgImVuc2FuY2hhciIgdW5hIHRhYmxhIGVzdMOhIGxhIGZ1bmNpw7NuIGBwaXZvdF93aWRlcigpYCAoIndpZGVyIiBlcyAibcOhcyBhbmNoYSIgZW4gaW5nbMOpcykgeSBlbCBjw7NkaWdvIHBhcmEgY29uc2VndWlyIGVzdGUgZm9ybWF0byBpbnRlcm1lZGlvIGVzOgoKYGBge3J9CnBhaXNlc19tZWRpbyA8LSBwaXZvdF93aWRlcihwYWlzZXNfbGFyZ28sIG5hbWVzX2Zyb20gPSB2YXJpYWJsZSwgdmFsdWVzX2Zyb20gPSB2YWxvcikKcGFpc2VzX21lZGlvCmBgYAoKTnVldmFtZW50ZSBlbCBwcmltZXIgYXJndW1lbnRvIGVzIGxhIHRhYmxhIG9yaWdpbmFsLiBFbCBzZWd1bmRvLCBgbmFtZXNfZnJvbWAgZXMgbGEgY29sdW1uYSBjdXlvcyB2YWxvcmVzIMO6bmljb3MgdmFuIGEgY29udmVydGlyc2UgZW4gbnVldmFzIGNvbHVtbmFzLiBMYSBjb2x1bW5hIGB2YXJpYWJsZWAgdGllbmUgbG9zIHZhbG9yZXMgYCJwb2JsYWNpw7NuImAsIGAiZXNwZXJhbnphX2RlX3ZpZGEiYCB5IGAicGliX3Blcl9jYXBpdGEiYCB5IGVudG9uY2VzIGxhIHRhYmxhIG51ZXZhIHRlbmRyw6EgdHJlcyBjb2x1bW5hcyBjb24gZXNvcyBub21icmVzLiBFbCB0ZXJjZXIgYXJndW1lbnRvLCBgdmFsdWVzX2Zyb21gLCBlcyBsYSBjb2x1bW5hIGRlIGxhIGN1YWwgc2FjYXIgbG9zIHZhbG9yZXMuIAoKUGFyYSB2b2x2ZXIgYWwgZm9ybWF0byBtw6FzIGFuY2hvLCBiYXN0YSBjb24gYWdyZWdhciBtw6FzIGNvbHVtbmFzIGVuIGVsIGFyZ3VtZW50byBgbmFtZXNfZnJvbWA6CgpgYGB7cn0KcGl2b3Rfd2lkZXIocGFpc2VzX2xhcmdvLCAKICAgICAgICAgICAgbmFtZXNfZnJvbSA9IGModmFyaWFibGUsIGFuaW8pLCAKICAgICAgICAgICAgbmFtZXNfc2VwID0gIl8iLAogICAgICAgICAgICB2YWx1ZXNfZnJvbSA9IHZhbG9yKQpgYGAKCkVuIGVzdGEgbGxhbWFkYSB0YW1iacOpbiBlc3TDoSBlbCBhcmd1bWVudG8gYG5hbWVzX3NlcGAsIHF1ZSBkZXRlcm1pbmEgZWwgY2FyYWN0ZXIgcXVlIHNlIHVzYSBwYXJhIGNyZWFyIGVsIG5vbWJyZSBkZSBsYXMgbnVldmFzIGNvbHVtbmFzLiAKCgo6Ojogey5hbGVydCAgLmFsZXJ0LWluZm99CioqRGVzYWbDrW8qKgoKKiBDcmXDoSB1bmEgbnVldmEgdGFibGEsIGxsYW1hZGEgYHBhaXNlc19zdXBlcmR1cGVyX2FuY2hvYCBxdWUgdGVuZ2EgdW5hIGNvbHVtbmEgcGFyYSBjYWRhIHZhcmlhYmxlLCBhbmlvIHkgcGHDrXMuIChDb25zZWpvOiBsYSB0YWJsYSBmaW5hbCB0aWVuZSBxdWUgdGVuZXIgNSBmaWxhcykuCgoqIMK/Q8OzbW8gZXMgbGEgdGFibGEgbcOhcyBhbmNoYSBwb3NpYmxlIHF1ZSBwb2TDqXMgZ2VuZXJhciBjb24gZXN0b3MgZGF0b3M/IMK/Q3XDoW50YXMgZmlsYXMgeSBjb2x1bW5hcyB0aWVuZT8KCjo6OgoKCiMjIFVuaWVuZG8gdGFibGFzCgpIYXN0YSBhaG9yYSB0b2RvIGxvIHF1ZSB1c2FzdGUgZGUge2RwbHlyfSBpbnZvbHVjcmEgdHJhYmFqYXIgeSBtb2RpZmljYXIgY29uIHVuYSBzb2xhIHRhYmxhIGEgbGEgdmV6LCBwZXJvIGVzIG11eSBjb23Dum4gdGVuZXIgZG9zIG8gbcOhcyB0YWJsYXMgY29uIGRhdG9zIHJlbGFjaW9uYWRvcy4gRW4gZXNlIGNhc28sIHRlbmVtb3MgcXVlICp1bmlyKiBlc3RhcyB0YWJsYXMuIGEgcGFydGlyIGRlIHVuYSBvIG3DoXMgdmFyaWFibGVzIGVuIGNvbcO6biBvICprZXlzKi4gRW4gRXhjZWwgdSBvdHJvIHByb2dyYW1hIGRlIGhvamFzIGRlIGPDoWxjdWxvLCBlc3RvIHNlIHJlc3VlbHZlIGNvbiBsYSBmdW5jacOzbiAiVkxPT0tVUCIgbyAgIkJVU0NBUlYiLCBlbiBSIHkgZW4gcGFydGljdWxhciBkZW50cm8gZGVsIG11bmRvIGRlIHtkcGx5cn0gaGF5IHF1ZSB1c2FyIGxhIGZhbWlsaWEgZGUgZnVuY2lvbmVzIGAqX2pvaW4oKWAuIEhheSB1bmEgZnVuY2nDs24gY2FkYSB0aXBvIGRlIHVuacOzbiBxdWUgcXVlcmFtb3MgaGFjZXIuIAoKQXN1bWllbmRvIHF1ZSBxdWVyw6lzIHVuaXIgZG9zIGRhdGEuZnJhbWVzIG8gdGFibGFzIGB4YCBlIGB5YCBxdWUgdGllbmVuIGVuIGNvbcO6biB1bmEgdmFyaWFibGUgYEFgOgoKIVtdKGltZy9qb2luLnBuZykKCiogYGZ1bGxfam9pbigpYDogZGV2dWVsdmUgdG9kYXMgbGFzIGZpbGFzIHkgdG9kYXMgbGFzIGNvbHVtbmFzIGRlIGFtYmFzIHRhYmxhcyBgeGAgZSBgeWAuIEN1YW5kbyBubyBjb2luY2lkZW4gbG9zIGVsZW1lbnRvcyBlbiBgeDFgLCBkZXZ1ZWx2ZSBgTkFgIChkYXRvIGZhbHRhbnRlKS4gRXN0byBzaWduaWZpY2EgcXVlIG5vIHNlIHBpZXJkZW4gZmlsYXMgZGUgbmluZ3VuYSBkZSBsYXMgZG9zIHRhYmxhcyBhw7puIGN1YW5kbyBubyBoYXkgY29pbmNpZGVuY2lhLiBFc3TDoSBlcyBsYSBtYW5lcmEgbcOhcyBzZWd1cmEgZGUgdW5pciB0YWJsYXMuCgoqIGBsZWZ0X2pvaW4oKWA6IGRldnVlbHZlIHRvZGFzIGxhcyBmaWxhcyBkZSBgeGAgeSB0b2RhcyBsYXMgY29sdW1uYXMgZGUgYHhgIGUgYHlgLiBMYXMgZmlsYXMgZW4gYHhgIHF1ZSBubyB0ZW5nYW4gY29pbmNpZGVuY2lhIGNvbiBgeWAgdGVuZHLDoW4gYE5BYCBlbiBsYXMgbnVldmFzIGNvbHVtbmFzLiBTaSBoYXkgbcO6bHRpcGxlcyBjb2luY2lkZW5jaWFzIGVudHJlIGB4YGUgYHlgLCBkZXZ1ZWx2ZSB0b2RhcyBsYXMgY29pbmNpZGVuY2lhcyBwb3NpYmxlcy4gCgoqIGByaWdodF9qb2luKClgOiBlcyBpZ3VhbCBxdWUgYGxlZnRfam9pbigpYCBwZXJvIGludGVyY2FtYmlhbmRvIGVsIG9yZGVuIGRlIGB4YCBlIGB5YC4gRW4gb3RyYXMgcGFsYWJyYXMsIGByaWdodF9qb2luKHgsIHkpYCBlcyBpZMOpbnRpY28gYSBgbGVmdF9qb2luKHksIHgpYC4KCiogYGlubmVyX2pvaW4oKWA6IGRldnVlbHZlIHRvZGFzIGxhcyBmaWxhcyBkZSBgeGAgZG9uZGUgaGF5IGNvaW5jaWRlbmNpYXMgY29uIGB5YCB5IHRvZGFzIGxhcyBjb2x1bW5hcyBkZSBgeGAgZSBgeWAuIFNpIGhheSBtw7psdGlwbGVzIGNvaW5jaWRlbmNpYXMgZW50cmUgYHhgIGUgYHlgLCBlbnRvbmNlcyBkZXZ1ZWx2ZSB0b2RhcyBsYXMgY29pbmNpZGVuY2lhcy4gRXN0byBzaWduaWZpY2EgcXVlIGVsaW1pbmFyw6EgbGFzIGZpbGFzIChvYnNlcnZhY2lvbmVzKSBxdWUgbm8gY29pbmNpZGFuIGVuIGFtYmFzIHRhYmxhcywgbG8gcXVlIHB1ZWRlIHNlciBwZWxpZ3Jvc28uCgohW10oaW1nL2pvaW5fZmFtaWx5LnBuZykKCkFob3JhIHZhbW9zIGEgc2VndWlyIHRyYWJhamFuZG8gY29uIGxhcyBiYXNlIGRlIGRhdG9zIGRlIGBwYWlzZXNgIHBlcm8gbm9zIHZhbW9zIGEgcXVlZGFyIHNvbG8gY29uIGxhcyBvYnNlcnZhY2lvbmVzIGRlbCAyMDA3IHkgZGUgcGFzbyB1bmlybG8gYSB1bmEgbnVldmEgYmFzZSBkZSBkYXRvcyBgY28yYCBxdWUgY29udGllbmUgaW5mb3JtYWNpw7NuIGRlIGxhIGVtaXNpw7NuIGRlIGRpw7N4aWRvIGRlIGNhcmJvbm8gZGUgY2FkYSBwYcOtcyBwYXJhIGVzZSBtaXNtbyBhw7FvLiAKCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQpwYWlzZXNfMjAwNyA8LSByZWFkcjo6cmVhZF9jc3YoImRhdG9zL3BhaXNlcy5jc3YiKSAlPiUgCiAgZmlsdGVyKGFuaW8gPT0gMjAwNykgCgpjbzJfMjAwNyA8LSByZWFkcjo6cmVhZF9jc3YoImRhdG9zL2NvMl8yMDA3LmNzdiIpCmNvMl8yMDA3CmBgYAoKRXN0YSBudWV2YSB0YWJsYSB0aWVuZSAzIGNvbHVtbmFzOiBgY29kaWdvX2lzb2AgdGllbmUgZWwgY8OzZGlnbyBJU08gZGUgMyBsZXRyYXMgZGUgKGFicmV2aWF0dXJhcyBxdWUgc2UgdXNhbiBpbnRlcm5hY2lvbmFsbWVudGUpLCBgZW1pc2lvbl9jbzJgIHRpZW5lIGxhcyBlbWlzaW9uZXMgYW51YWxlcyBwZXIgY8OhcGl0YSBkZSBDTzIgZW4gdG9uZWxhZGFzLCBgcGFpc2AgdGllbmUgZWwgbm9tYnJlIGRlbCBwYcOtcy4gRXN0YSDDumx0aW1hIGNvbHVtbmEgdGFtYmnDqW4gZXN0w6EgcHJlc2VudGUgZW4gbGEgdGFibGEgYHBhaXNlc18yMDA3YCB5IGVzIGxhIHF1ZSB2YSBhIHNlcnZpciBjb21vIHZhcmlhYmxlIGxsYXZlIHBhcmEgdW5pciBsYXMgZG9zIHRhYmxhcy4gCgpQYXJhIHVuaXIgbGFzIGRvcyB0YWJsYXMsIGN1YWxxdWllciBmdW5jacOzbiAqam9pbiogcmVxdWllcmUgY2llcnRhIGluZm9ybWFjacOzbjoKCiogbGFzIHRhYmxhcyBhIHVuaXI6IHNvbiBsb3MgZG9zIHByaW1lcm9zIGFyZ3VtZW50b3MuCiogcXXDqSB2YXJpYWJsZSBvIHZhcmlhYmxlcyAoc2UgcHVlZGUgdXNhciBtw6FzIGRlIHVuYSEpIHVzYXIgcGFyYSBpZGVudGlmaWNhciBjb2luY2lkZW5jaWFzOiBlbCBhcmd1bWVudG8gYGJ5YC4KClVuYW1vcyBgcGFpc2VzXzIwMDdgIHkgYGNvMl8yMDA3YCBwcmltZXJvIGNvbiBgZnVsbF9qb2luKClgOgoKYGBge3J9CnBhaXNlc19jbzJfMjAwNyA8LSBmdWxsX2pvaW4ocGFpc2VzXzIwMDcsIGNvMl8yMDA3LCBieSA9ICJwYWlzIikKcGFpc2VzX2NvMl8yMDA3CmBgYAoKU2kgbWlyYW1vcyBkZSBjZXJjYSBsYSB0YWJsYSB1bmlkYSB2ZXJlbW9zIHVuIHBhciBkZSBjb3NhczoKCiogVG9kYXMgbGFzIGNvbHVtbmFzIGRlIGBwYWlzZXNfMjAwN2AgeSBkZSBgY28yXzIwMDdgIGVzdMOhbiBwcmVzZW50ZXMuCiogVG9kYXMgbGFzIG9ic2VydmFjaW9uZXMgZXN0w6FuIHByZXNlbnRlcywgYcO6biBsb3MgcGHDrXNlcyBxdWUgZXN0w6FuIHByZXNlbnRlcyBlbiBgY28yXzIwMDdgIHBlcm8gbm8gZW4gYHBhaXNlc18yMDA3YCB5IHZpY2V2ZXJzYS4gRW4gZXNvcyBjYXNvcyBhaG9yYSB0ZW5lbW9zIGBOQWAuIEVzdG8gZ2VuZXJhIHVuYSB0YWJsYSBjb24gMjQxIGZpbGFzLgoKRXN0YSBlcyBsYSBvcGNpw7NuIG3DoXMgc2VndXJhIHNpIG5vIHNhYmVtb3Mgc2kgdG9kYXMgbGFzIG9ic2VydmFjaW9uZXMgZGUgdW5hIHRhYmxhIGVzdMOhbiBwcmVzZW50ZSBlbiBhIG90cmEuIAoKU2kgc29sbyBub3MgaW50ZXJlc2EgY29uc2VydmFyIGxhcyBmaWxhcyBkZSBsYSB0YWJsYSAqZGUgbGEgaXpxdWllcmRhKiwgZW4gZXN0ZSBjYXNvIGBwYWlzZXNfMjAwN2AgZW50b25jZXM6CgpgYGB7cn0KcGFpc2VzX2NvMl8yMDA3IDwtIGxlZnRfam9pbihwYWlzZXNfMjAwNywgY28yXzIwMDcsIGJ5ID0gInBhaXMiKQpwYWlzZXNfY28yXzIwMDcKYGBgCgpBaG9yYSBlc3BlcmFtb3MgcXVlIGxhIHRhYmxhIHJlc3VsdGFudGUgdGVuZ2EgbGEgbWlzbWEgY2FudGlkYWQgZGUgZmlsYXMgcXVlIGBwYWlzZXNfMjAwN2AgeSBlZmVjdGl2YW1lbnRlIGVzbyBvY3VycmUuIFBlcm8gYWwgbWlzbW8gdGllbXBvIHZhcmlvcyBwYcOtc2VzIGVuIGVzYSB0YWJsYSBubyBlbmNvbnRyYXJvbiBjb2luY2lkZW5jaWEgZW4gYGNvMl8yMDA3YCB5IHBvciBlc2EgcmF6w7NuLCBsYSBjb2x1bW5hIG51ZXZhIGNvbHVtbmEgYGVtaXNpb25lc19jbzJgIHRpZW5lIGBOQWAuIAoKCkZpbmFsbWVudGUsIHNpIHF1aXNpw6lyYW1vcyBxdWVkYXJub3Mgc29sbyBjb24gbGFzIG9ic2VydmFjaW9uZXMgcXVlIGVzdMOhbiBwcmVzZW50ZXMgZW4gYW1iYXMgdGFibGFzIHVzYW1vcyBgaW5uZXJfam9pbigpYC4KCmBgYHtyfQpwYWlzZXNfY28yXzIwMDcgPC0gaW5uZXJfam9pbihwYWlzZXNfMjAwNywgY28yXzIwMDcsIGJ5ID0gInBhaXMiKQpwYWlzZXNfY28yXzIwMDcKYGBgCgpFbiBlc3RlIGNhc28sIHBlcmRlbW9zIGxhcyBmaWxhcyBkZSBgY28yXzIwMDdgIHF1ZSBubyBlbmNvbnRyYXJvbiBjb2luY2lkZW5jaWEgZW4gYHBhaXNlc18yMDA3YCB5IHZpY2V2ZXJzYSB5IGxhIHRhYmxhIHJlc3VsdGFudGUgdGllbmUgYcO6biBtZW5vcyBmaWxhcyAoMTE5KS4KCjo6OiB7LmFsZXJ0ICAuYWxlcnQtaW5mb30KKipEZXNhZsOtbyoqCgpFc3R1dmltb3MgdHJhYmFqYW5kbyBjb24gdW5hIHBhcnRlIGRlIGxhIGJhc2UgZGUgZGF0b3MgZGUgZW1pc2lvbmVzLiBQZXJvIHRhbWJpw6luIGVzdMOhIGRpc3BvbmlibGUgYGNvMl9jb21wbGV0by5jc3ZgIHF1ZSBjb250aWVuZSBsYXMgZW1pc2lvbmVzIHBhcmEgZGlzdGludG9zIGHDsW9zLiBFbCBvYmpldGl2byBlcyBxdWUgdW5hcyBgcGFpc2VzYCB5IGBjbzJgIHRlbmllbmRvIGVuIGN1ZW50YSB0YW50byBlbCBwYcOtcyBjb21vIGVsIGHDsW8uIFBhcmEgZXNvOgoKMS4gTGVlIGxhIGJhc2UgZGUgZGF0b3MgYGNvMl9jb21wbGV0by5jc3ZgIGVuIHVuYSBudWV2YSB2YXJpYWJsZSBxdWUgc2UgbGxhbWUgYGNvMmAuCjIuIFJldmlzw6EgZWwgbm9tYnJlIGRlIGxhcyB2YXJpYWJsZXMgZW4gZXN0YSBiYXNlIGRlIGRhdG9zLCDCv3NlIGxsYW1hbiBpZ3VhbCBxdWUgbGFzIHZhcmlhYmxlcyBlbiBgcGFpc2VzYD8KMy4gVW7DrSBsYXMgZG9zIHRhYmxhcyB1c2FuZG8gYGZ1bGxfam9pbigpYCwgdGVuw6kgZW4gY3VlbnRhIHF1ZSBhaG9yYSB1c2Ftb3MgZG9zIHZhcmlhYmxlcyBsbGF2ZSBgcGFpc2AgeSBgYW5pb2AuIEJ1c2PDoSBlbiBsYSBkb2N1bWVudGFjacOzbiBjw7NtbyBpbmRpY2FybGUgZXNvIGEgbGEgZnVuY2nDs24gYGZ1bGxfam9pbigpYC4gIAo6OjoKCjxkaXYgY2xhc3M9ImJ0bi1ncm91cCIgcm9sZT0iZ3JvdXAiIGFyaWEtbGFiZWw9Ik5hdmVnYWNpw7NuIj4KICA8YSBocmVmPSAiMDctZ3JhZmljb3MtSUkuaHRtbCIgY2xhc3MgPSAiYnRuIGJ0bi1wcmltYXJ5Ij5BbnRlcmlvcjwvYT4KICA8YSBocmVmPSAiMDktZnVuY2lvbmVzLmh0bWwiIGNsYXNzID0gImJ0biBidG4tcHJpbWFyeSI+U2lndWllbnRlPC9hPgo8L2Rpdj4=