Introducción

La idea de esta notebook es poder introducir algunos conceptos básicos del llamado tidyverse en R. Vamos a tratar de hacernos amigos de algunos algunos de los verbos que vimos hace un rato y que nos van a hacer la vida más fácil en la manipulación de datos.

Objetivos

  • Brindar nociones sobre la lógica general del tidyverse para el preprocesamiento de datos
  • Introducir algunas funciones básicas para el filtrado, trasformación y merge de datos
  • Presentar herramientas para la visualización de datos

PASO 1. Cargar las librerías a utilizar

Lo primero que tenemos que hacer, siempre, en una sesión de R es cargar o importar las librerías que vamos a utilizar.

library(tidyverse)
library(sf)
library(gdalUtils)

PASO 2. Importando los datos

El (obvio) siguiente paso es importar los datos que vamos a utilizar. En este caso, vamos a tratar de hacer un análisis exploratorio de un dataset sobre la distribución de delitos en la Ciudad Autónoma de Buenos Aires. Se trata de un dataset no oficial generado por el dueño de este repositorio. Si bien son datos “no oficiales” están extraidos de una fuente oficial: el Mapa del Delito generado por el GCBA.

Primero, importamos el gran archivo en formato .csv que contiene en cada fila un delito reportado y una serie de atributos asociados.

delitos <- read.csv("./data/delitos.csv")
head(delitos)

Hagamos un primer gráfico rápido de este dataset a ver qué encontramos… Lo más fácil de todo sería plotear la latitud contra la longitud.

ggplot(delitos) + 
        geom_point(aes(x=longitud, y=latitud))

Ya vemos que hay algo raro… hay un puntito solitario en (lat=0, long=0). Podemos eliminarlo, entonces.

delitos_limpios <- filter(delitos, latitud!=0 | longitud!=0)
head(delitos_limpios)

Bien… ya hicimos una primera limpieza de los datos.

ggplot(delitos_limpios) + 
        geom_point(aes(x=longitud, y=latitud))


¿Qué pasó acá?

Acabamos de introducir una buena cantidad de código que conviene empezar a revisar para ir fijando conceptos.

Gráficos

Acabamos de hacer una visualización bien rápida. Teníamos una varaiable que medía la posición en el eje \(X\) (o sea la longitud) y otra que lo hacía en el eje \(Y\) (latutud). Hicimos un scatterplot de eso, usando la librería ggplot2. Hay dos pasos básicos para hacer un gráfico en ggplot:

  1. ggplot(delitos) + \(=>\) Creamos el objeto ggplot y lo “llenamos” con un dataset. En este caso, una tibble
  2. geom_point(aes(x=longitud, y=latitud)) \(=>\) agregamos una capa estética, en este caso de puntos.

Vamos a volver, pero en general, todos los gráficos de ggplot2 se construyen acumulando capas estéticas.

Filtros

Encontramos ¿una? inconsistencia en la base de datos. Básicamente, uno o varios registros con coordenadas latitud==0 & longitud==0. Entonces, usamos la instrucción filter() para filtrar los casos que cumplían con esa condicion.


PASO 3. Visualizando los datos

Ya vimos un scatterplot, muy útil para plotear la distribución conjunta de dos variables cuantitativas. Sigamos con nuestro ejemplo y veamos algunos parámetros para modificar la estética del plot (que vale para cualquier geom_XXX).

Tamaño, color y forma (y una proyección)

Pongamos un color más bonito que ese negro. Y aprovechemos para hacer una aclaración. Estrictamente, estamos trabajando con variables de coordenadas geográficas. No tenemos tiempo en el curso para trabajar en detalle este tema pero digamos que son variables cuantitativas “especiales”. Cada mapa está dibujado según un sistema de coordenadas que llevan la forma “geódica” de la tierra (básicamente, algo que está en 3 dimensiones) a un plano (en 2 dimensiones).

Es decir que “deforman” la forma de la tierra para llevarla a la hoja de papel (o a la pantalla, en este caso). Si se fijan, la CABA aparece medio alargada en el mapa anterior. Esto es porque no le especificamos a ggploten qué sistema de coordenadas está.

Pasemos, entonces la capa coord_map('mercator')


Para más detalles sobre sistemas de coordenadas y referencia pueden consultar Ciencia de Datos para Gente Sociable de Antonio Vázquez Brust, material (y autor) que dieron una mano grande a este curso -además de ser colega y amigo-.


ggplot(delitos_limpios) + 
        geom_point(aes(x=longitud, y=latitud), color='blue') +
        coord_map("mercator")

Ahora se parece más al Buenos Aires de Gardel…

Bien, cambiemos el tamaño de los puntitos. El parámetro size está en pixels, por lo cual no es fácil estimarlo sin ver una versión previa del plot, primero.

ggplot(delitos_limpios) + 
        geom_point(aes(x=longitud, y=latitud), color='red', size=0.05) +
        coord_map("mercator")

Y, por último, cambiemos la forma…

ggplot(delitos_limpios) + 
        geom_point(aes(x=longitud, y=latitud), color='red', size=0.05, shape=3) +
        coord_map("mercator")

Facetado

Ahora, si queremos agregar más dimensiones al plot… la cosa se hace un poco más densa. Es por eso que podemos usar una nueva “capa” de ggplot, llamada facetado. La idea es que podemos tener gráficos continguos, condicioados a los valores de una variable (generalmente, categórica). Veamos, entonces, un plot por cada uno de los tipos de delitos…

ggplot(delitos_limpios) + 
        geom_point(aes(x=longitud, y=latitud, color=tipo_delito), size=0.05, alpha=0.25) +
        facet_wrap(~tipo_delito) +
        coord_map("mercator")

¿Qué diferencias hay con los plots anteriores?


Consigna 1.

Generar los siguientes gráficos:

  • El gráfico anterior, variando la capa facet_wrap por facet_grid.
  • El gráfico anterior, sin facetado y con un color por cada tipo de delito.

Otros plots…

Veamos ahora cuáles son los tipos de delitos más comunes en la CABA.

ggplot(delitos_limpios, aes(x=tipo_delito))+
        geom_bar(stat="count")

Se ve bastante bien… aunque tenemos acomodar un poco las etiquetas. Una opción es pedirle a ggplot que las abrevie:

ggplot(delitos_limpios, aes(x=tipo_delito))+
        geom_bar(stat="count") + 
        scale_x_discrete(labels = abbreviate)

Otra es pasarle nosotros un vector de etiquetas:

ggplot(delitos_limpios, aes(x=tipo_delito))+
        geom_bar(stat="count") + 
        scale_x_discrete(labels = c('H.doloso','H.seg.vial', 'Hurto(s/v)', 
                                    'Robo(c/v)', 'Robo auto', 'Hurto auto', 'Lesion.seg.vial'))


Consigna 2.

Generar un gráfico de barras por comuna


Histogramas

Un histograma, como hemos visto, se usa para mostrar la distribución de una variable continua. Por ejemplo, podríamos hacer un histograma de la distribución de edades de la EPH o de los ingresos.

No tenemos una variable cuantitativa, vamos a inventarnos una. Pero previamente vamos a tener que introducir algunas ideas de limpieza de datos…

PASO 4. Limpieza de datos

Recordemos:

Ahora la cosa se pone seria. Una de las primeras cosas que vamos a tener que hacer es poder tener una visión más o menos general de la tabla con la que estamos trabajando. Para eso, llamamos a la función str() que puede usarse para cualquier clase de objeto en R.

str(delitos_limpios)
'data.frame':   237445 obs. of  14 variables:
 $ id                : int  68400 68401 68402 68492 132437 132469 132480 132566 132583 134576 ...
 $ comuna            : Factor w/ 15 levels "Comuna 1","Comuna 10",..: 9 13 10 5 15 3 3 13 14 3 ...
 $ barrio            : Factor w/ 48 levels "AGRONOMIA","ALMAGRO",..: 3 12 20 5 15 46 46 12 40 39 ...
 $ latitud           : num  -34.6 -34.7 -34.6 -34.6 -34.7 ...
 $ longitud          : num  -58.4 -58.5 -58.4 -58.5 -58.5 ...
 $ fecha             : Factor w/ 731 levels "2016-01-01","2016-01-02",..: 305 305 305 305 305 305 305 305 305 305 ...
 $ hora              : Factor w/ 1417 levels "00:00:00","00:01:00",..: 61 147 235 174 1238 458 1213 1088 1148 1358 ...
 $ uso_arma          : Factor w/ 1 level "SIN USO DE ARMA": 1 1 1 1 1 1 1 1 1 1 ...
 $ uso_moto          : Factor w/ 1 level "SIN MOTO": 1 1 1 1 1 1 1 1 1 1 ...
 $ lugar             : logi  NA NA NA NA NA NA ...
 $ origen_dato       : logi  NA NA NA NA NA NA ...
 $ tipo_delito       : Factor w/ 7 levels "Homicidio Doloso",..: 1 1 1 2 3 3 6 3 6 3 ...
 $ cantidad_vehiculos: int  0 0 0 0 0 0 0 0 0 0 ...
 $ cantidad_victimas : int  0 0 0 1 0 0 0 0 0 0 ...

¿Qué se puede observar en esta salida?

Por defecto, R asume que toda columna de texto es un factor. Este último punto puede llegar a traer problemas. Por eso suele ser buena idea setear el argumento stringsAsFactors=FALSE: datos <- read.csv("/ruta/a/misdatos.csv", stringsAsFactors=FALSE).

Otro paso útil es llamar a la función summary()

summary(delitos_limpios)
       id               comuna               barrio      
 Min.   :     1   Comuna 1 : 32898   PALERMO    : 19055  
 1st Qu.: 59552   Comuna 3 : 19894   BALVANERA  : 15816  
 Median :128908   Comuna 4 : 19475   FLORES     : 12939  
 Mean   :125662   Comuna 14: 19056   SAN NICOLAS: 11586  
 3rd Qu.:188423   Comuna 7 : 18034   CABALLITO  : 11573  
 Max.   :248024   Comuna 9 : 14886   RECOLETA   : 10974  
                  (Other)  :113202   (Other)    :155502  
    latitud          longitud             fecha       
 Min.   :-34.70   Min.   :-58.53   2017-12-06:   495  
 1st Qu.:-34.63   1st Qu.:-58.47   2017-12-11:   485  
 Median :-34.61   Median :-58.43   2017-11-22:   479  
 Mean   :-34.61   Mean   :-58.44   2017-04-03:   473  
 3rd Qu.:-34.59   3rd Qu.:-58.40   2017-03-20:   460  
 Max.   :-34.53   Max.   :-58.34   2017-06-26:   456  
                                   (Other)   :234597  
       hora                   uso_arma          uso_moto     
 00:00:00:  7953   SIN USO DE ARMA:237445   SIN MOTO:237445  
 20:00:00:  7892                                             
 21:00:00:  7512                                             
 22:00:00:  7255                                             
 19:00:00:  6777                                             
 18:00:00:  6338                                             
 (Other) :193718                                             
  lugar         origen_dato                   tipo_delito    
 Mode:logical   Mode:logical   Homicidio Doloso     :   272  
 NA's:237445    NA's:237445    Homicidio Seg Vial   :   264  
                               Hurto (Sin violencia): 76002  
                               Hurto Automotor      : 12075  
                               Lesiones Seg Vial    :  9798  
                               Robo (Con violencia) :132047  
                               Robo Automotor       :  6987  
 cantidad_vehiculos cantidad_victimas 
 Min.   :0          Min.   :0.000000  
 1st Qu.:0          1st Qu.:0.000000  
 Median :0          Median :0.000000  
 Mean   :0          Mean   :0.001162  
 3rd Qu.:0          3rd Qu.:0.000000  
 Max.   :0          Max.   :3.000000  
                                      

Recordemos… las categorías de un factor en R se llamana “niveles”

levels(delitos_limpios$barrio)
 [1] "AGRONOMIA"         "ALMAGRO"           "BALVANERA"        
 [4] "BARRACAS"          "BELGRANO"          "BOEDO"            
 [7] "CABALLITO"         "CHACARITA"         "COGHLAND"         
[10] "COLEGIALES"        "CONSTITUCION"      "FLORES"           
[13] "FLORESTA"          "LA BOCA"           "LINIERS"          
[16] "MATADEROS"         "MONTE CASTRO"      "MONTSERRAT"       
[19] "NUÑEZ"            "NUEVA POMPEYA"     "PALERMO"          
[22] "PARQUE AVELLANEDA" "PARQUE CHACABUCO"  "PARQUE CHAS"      
[25] "PARQUE PATRICIOS"  "PATERNAL"          "PUERTO MADERO"    
[28] "RECOLETA"          "RETIRO"            "SAAVEDRA"         
[31] "SAN CRISTOBAL"     "SAN NICOLAS"       "SAN TELMO"        
[34] "VELEZ SARSFIELD"   "VERSALLES"         "VILLA CRESPO"     
[37] "VILLA DEL PARQUE"  "VILLA DEVOTO"      "VILLA GRAL MITRE" 
[40] "VILLA LUGANO"      "VILLA LURO"        "VILLA ORTUZAR"    
[43] "VILLA PUEYRREDON"  "VILLA REAL"        "VILLA RIACHUELO"  
[46] "VILLA SANTA RITA"  "VILLA SOLDATI"     "VILLA URQUIZA"    

En principio, esta variable parece estar bien. Sin embargo, existen al menos otras dos variables que no parece tener demasiado sentido que tengan formato de factor: fecha y hora. En efecto, existe un tipo de dato especial en R para datos de tiempo, fecha, hora, etc. Vamos a hacer uso del paquete lubridate:

library(lubridate)
delitos_limpios <- mutate(delitos_limpios, fecha=ymd(fecha), hora=hms(hora))

Aquí, usamos la instrucción mutate() para transformar las dos columnas junto con las funciones ymd() por “year / month / day” y

Ahora estamos en condiciones de pensar nuestro histograma. Vamos a contar cuántos hechos delictivos hubo por día y luego generaremos sobre esta nueva tabla el histograma.

p <- group_by(delitos_limpios, fecha) 
periodo <- summarise(p, gran_total = n())
head(periodo)

Ahora, solamente, tenemos que crear a nuestro histograma:


Hay unas cuántas instrucciones que no vimos y que retomaremos enseguida. No obstante, el código es lo suficientemente expresivo como para poder intuir qué pasó:

  1. tomamos el dataset original
  2. agrupamos por el campo “fecha” (es decir, por día)
  3. hicimos un conteo de cuántos registros había por cada día ***
ggplot(periodo) + 
        geom_histogram(aes(x = gran_total))

Podemos ver que hay un rango grande en la cantidad de delitos por día:

Podríamos también construir un histograma para cada tipo de delito…

p <- group_by(delitos_limpios, fecha, tipo_delito)
periodo <- summarise(p, gran_total=n())
head(periodo)

Ahora, facetando…

ggplot(periodo) + 
        geom_histogram(aes(x=gran_total)) + 
        facet_wrap(~tipo_delito)

Integrando datos de diferentes fuentes

Ahora bien, en general al utilizar datos “en la vida real” no es habitual tener una sola tabla. Es habitual tener que integrar datos de varias fuentes. Ya trabajaremos con los datos de la Encuesta Permanente de Hogares del INDEC y veremos que tenemos, al menos, dos tablas para trabajar: una correspondiente a los datos de los individuos encuestados y otra correspondiente a los de los hogares en que habitan esos individuos.

Ahora… supongamos que tenemos una nueva fuente de datos: los reclamos del Sistema de Atención Ciudadana correspondientes a los años 2016 (uno de los años que abarca el dataset de delitos):

table(year(delitos_limpios$fecha))

  2016   2017 
117288 120157 
at_ciudadano <- read.csv("../data/sistema-unico-de-atencion-ciudadana-2016.csv", sep=";")
head(at_ciudadano)

Podríamos pensar que en aquellos barrios en los que mayor prevalencia de delitos existe, debería verificarse una mayor cantidad de reclamos por rubros asociados a estos delitos. Observemos previamente la estructura de nuestro dataset e identifiquemos qué variable especifica el rubro de reclamo y veamos los levels correspondientes…

str(at_ciudadano)
'data.frame':   965951 obs. of  13 variables:
 $ PERIODO                  : int  201601 201601 201601 201601 201601 201601 201601 201601 201601 201601 ...
 $ CONCEPTO                 : Factor w/ 373 levels "ABONOS DE DESCUENTO POR VOLUMEN - SUBTE",..: 259 332 320 72 77 332 77 332 77 332 ...
 $ RUBRO                    : Factor w/ 49 levels "","ACTOS DE CORRUPCION",..: 28 45 45 23 23 45 23 45 23 45 ...
 $ TIPO_PRESTACION          : Factor w/ 5 levels "DENUNCIA","QUEJA",..: 3 5 3 1 1 5 1 5 1 5 ...
 $ FECHA_INGRESO            : Factor w/ 363 levels "","01/01/2016",..: 2 2 2 2 2 2 2 2 2 2 ...
 $ HORA_INGRESO             : Factor w/ 72730 levels "","01:00:00 A.M.",..: 65810 7210 12662 14455 15170 15189 15586 15594 16049 16615 ...
 $ DOMICILIO_CGPC           : Factor w/ 16 levels "COMUNA 1","COMUNA 10",..: 14 2 14 5 5 1 5 1 5 1 ...
 $ DOMICILIO_BARRIO         : Factor w/ 49 levels "","AGRONOMIA",..: 48 45 48 6 6 28 6 28 6 28 ...
 $ DOMICILIO_CALLE          : Factor w/ 2190 levels "","11 DE SEPTIEMBRE DE 1888",..: 1702 1388 1702 1695 1695 1538 1695 1538 1695 1538 ...
 $ DOMICILIO_ALTURA         : Factor w/ 8788 levels "","$ 4.216,00",..: 2719 2543 1830 633 560 794 560 794 627 794 ...
 $ DOMICILIO_ESQUINA_PROXIMA: Factor w/ 1756 levels "","11 DE SEPTIEMBRE DE 1888",..: 1 1 1 1 1 1 1 1 1 1 ...
 $ LAT                      : Factor w/ 37042 levels "","-103,611",..: 32721 20698 31819 3886 3824 19813 3824 19813 3886 19813 ...
 $ LONG                     : Factor w/ 44969 levels "","-116,7084",..: 21533 44386 19149 25205 25179 4210 25179 4210 25152 4210 ...

Haciendo una inspección rápida, puede verse que

  • DENUNCIAS SOBRE INCONDUCTAS REFERIDAS A LA ACTUACION POLICIAL
  • EMERGENCIAS
  • SEGURIDAD
  • SEGURIDAD E HIGIENE

son las categorías que podrían estar asociadas a reclamos por inseguridad y hechos delictivos.

Transformando y vinculando datos: los 5 verbos del tidyverse

Ahora bien… ¿cómo vinculamos ambas tablas? El problema es que en ambos casos tenemos registros individuales (delitos en una caso, reclamos en otro). Tenemos, entonces, que agregarlos de alguna forma a una unidad común. En pricipio, hay una columna “BARRIO” en ambas. Eso es una primera posibildiad. Contemos, entonces, la cantidad de delitos (total) por barrio y la cantidad de reclamosen las cuatro categorías anteriores y peguemos ambas tablas juntas. Esto nos va a permitir introducir varias funciones, operadores y comandos sumamente útiles.

Para hacer todo esto, y mucho más, vamos a aprender funciones que representan cinco verbos básicos para la transformación de datos:

  • select(): seleccionar -elegir- columnas por su nombre
  • filter(): filtrar, es decir quedarse sólo con las filas que cumplan cierta condición
  • arrange(): ordenar las filas de acuerdo a su contenido o algún otro índice
  • mutate(): mutar -cambiar- un dataframe, modificando el contenido de sus columnas o creando columnas (es decir, variables) nuevas
  • summarise(): producir sumarios -un valor extraído de muchos, por ejemplo el promedio- con el contenido de las columnas

Estas funciones tienen una sintaxis, una forma de escribirse, uniforme. El primer argumento que toman siempre es un dataframe; los siguientes indican qué hacer con los datos. El resultado siempre es un nuevo dataframe.

Las funciones son parte de dplyr, uno de los componentes de la familia de paquetes Tidyverse. Ya tenemos disponible todo lo necesario, activado cuando invocamos library(tidiverse) al comienzo.

Empecemos por el dataset at_ciudadano.

  1. Lo primero que tenemos que hacer es seleccionar las columnas con las que vamos a trabajar. Para ello, vamos a introducir la función select()
at_barrio <- select(at_ciudadano, DOMICILIO_BARRIO, RUBRO)
head(at_barrio)
  1. Luego, vamos a filtrar -filter()- las categorías que no nos interesan:
at_barrio <- filter(at_barrio, RUBRO == 'DENUNCIAS SOBRE INCONDUCTAS REFERIDAS A LA ACTUACION POLICIAL' | RUBRO == 'EMERGENCIAS' |  RUBRO == 'SEGURIDAD' | RUBRO == 'SEGURIDAD E HIGIENE' | RUBRO == 'VEHICULOS DE FANTASIA')
  1. Ahora, deberíamos sumar la cantidad de reclamos por barrio -group_by() + summarise():
at_barrio_agg <- group_by(at_barrio, DOMICILIO_BARRIO)
at_barrio_agg <- summarize(at_barrio_agg, total=n())
  1. Por último, y como para ser gente prolija, ordenemos en forma descendente los barrios:
at_barrio_agg <- arrange(at_barrio_agg, desc(total))
head(at_barrio_agg)

Entonces, hasta acá vimos tres de los cinco verbos… Nos falta analizar mutate(). Sobre eso volveremos enseguida.

Operador pipe %>%

Antes de terminar, vamos a presentar una herramienta más: el operador pipe (pronúnciese “paip”, es el término en inglés que significa “tubo”).

El pipe es un operador: un símbolo que relaciona dos entidades. Dicho en forma más simple, el pipe de R, cuyo símbolo es %>% está en familia con otros operadores más convencionales, como +, - o /. Y al igual que los otros operadores, entrega un resultado en base a los operandos que recibe. Ahora bien… ¿Para qué sirve? En resumidas cuentas, hace que el código necesario para realizar una serie de operaciones de transformación de datos sea mucho más simple de escribir y de interpretar.

Repasemos la secuencia anterior…

  1. Seleccionamos las columnas a usar
  2. Filtramos registros
  3. Generamos un resumen
  4. Ordenamos en forma descendente

Pegando todo el código junto…

at_barrio <- select(at_ciudadano, DOMICILIO_BARRIO, RUBRO)
at_barrio <- filter(at_barrio, RUBRO == 'DENUNCIAS SOBRE INCONDUCTAS REFERIDAS A LA ACTUACION POLICIAL' | RUBRO == 'EMERGENCIAS' |  RUBRO == 'SEGURIDAD' | RUBRO == 'SEGURIDAD E HIGIENE' | RUBRO == 'VEHICULOS DE FANTASIA')
at_barrio_agg <- group_by(at_barrio, DOMICILIO_BARRIO)
at_barrio_agg <- summarize(at_barrio_agg, total=n())
arrange(at_barrio_agg, desc(total))

Todo bien, pero el problema es que hemos generado unas cuantas variables (“at_barrio”, “at_barrio_agg”) que no volveremos a usar. Además de ser inútiles una vez obtenido el resultado buscado, estas variables intermedias requieren que las nombremos. Decidir el nombre de estas variables que no nos importan toma tiempo (sobre todo cuando producimos muchas), y nos distrae de lo importante, que es el análisis.

El pipe, %>%, permite encadenar operaciones, conectando el resultado de una como el dato de entrada de la siguiente. La misma secuencia que realizamos antes puede resolverse con pipes, quedando así:

at_barrio <- select(at_ciudadano, DOMICILIO_BARRIO, RUBRO) %>%
        filter(RUBRO == 'DENUNCIAS SOBRE INCONDUCTAS REFERIDAS A LA ACTUACION POLICIAL' | 
                       RUBRO == 'EMERGENCIAS' |  RUBRO == 'SEGURIDAD' | 
                       RUBRO == 'SEGURIDAD E HIGIENE' | RUBRO == 'VEHICULOS DE FANTASIA') %>%
        group_by(DOMICILIO_BARRIO) %>% 
        summarize(total=n()) %>% 
        arrange(desc(total))
head(at_barrio)

¿Qué pasó acá?

  • Verbos en tidyverse
  • Operador %>% en magritt


Consigna 3.

Repetir el proceso para generar una tabla de cantidad de delitos por barrio usando el operador pipe…


delitos_barrio <- select(delitos_limpios, barrio, tipo_delito) %>%
        group_by(barrio) %>% 
        summarize(total=n()) %>% 
        arrange(desc(total))
head(delitos_barrio)

El último paso es “unir” las dos tablas. Para eso vamos a usar el operador left_join.

barrios <- left_join(delitos_barrio, at_barrio)
Joining, by = "total"
barrios

left_join usa como clave las columnas que en las dos tablas tengan el mismo nombre. Parece que tenemos un problema: las tablas tienen una sola columna con un nombre en común (“total”) y justamente, no es esa la que queremos para joinear ambas variables. La que queremos es la que contiene el barrio, el problema es que se llaman distinto. Solucionemos este problema:

at_barrio <- at_barrio %>%
                rename(barrio=DOMICILIO_BARRIO)
barrios <- left_join(delitos_barrio, at_barrio, by='barrio')
Column `barrio` joining factors with different levels, coercing to character vector

Funciona mejor, pero vemos todavía que queda unos cuántos barrios que no se han joineado.

filter(barrios, is.na(total.y))

Seguramente, se deba a que se encuentran escritas de forma diferente en ambas tablas. Así que vamos a unificar su escritura… Solo por esta vez, hagámoslo usando solamente el R-base y no el tidyverse…

at_barrio$barrio <- as.character(at_barrio$barrio)
at_barrio$barrio[at_barrio$barrio=='MONSERRAT'] <- 'MONTSERRAT'
at_barrio$barrio[at_barrio$barrio=='BOCA'] <- 'LA BOCA'
at_barrio$barrio[at_barrio$barrio=='VILLA GRAL. MITRE'] <- 'VILLA GRAL MITRE'
at_barrio$barrio[at_barrio$barrio=='COGHLAN'] <- 'COGHLAND'
at_barrio$barrio <- as.factor(at_barrio$barrio)

Hacemos el left_join(). Aprovechamos para darles un nombre coherente a las variables. Y vemos que quedan aún algunos datos con NA: se trata de barrios que no tienen datos en la tabla de at_barrio.

barrios <- left_join(delitos_barrio, at_barrio, by='barrio') %>%
                        rename(n_delitos = total.x, n_reclamos=total.y)
Column `barrio` joining factors with different levels, coercing to character vector

Podríamos filtrarlos fácilmente:

barrios %>%
        filter(!is.na(n_reclamos))

Pero mejor vamos a asigarnles un valor de igual a 0:

barrios <- barrios %>% 
        mutate(n_reclamos=replace_na(n_reclamos, 0))

Consigna 4.

  1. Realizar un scatterplot de la cantidad de delitos contra la cantidad de reclamos por seguridad.

 ggplot(barrios, aes(x=n_delitos, y=n_reclamos), color='red') + 
        geom_point() + 
        geom_smooth(method = 'lm') +
         labs(title = "Delitos registrados según contactos al SIUAC vinculados a seguridad",
         subtitle = "Barrios de la CABA, 2016 - 2017",
         caption = "Fuente: portal de datos abiertos de la Ciudad - http://data.buenosaires.gob.ar",
         x = "Cantidad de delitos",
         y = "Cantidad de contactos")

Para ir cerrando…

Vamos a ver dos últimas cuestiones vinculadas a la construcción de gráficos en ggplot2.

Generación de títulos en el gráfico: siempre es importate poder transmitir de forma clara qué información se encuentra en el mismo. En general, hay varias reglas y formas de hacerlo pero al menos cuatro elemenos tienen que existir:

Todo puede ser manjeando con la capa labs

Curva de ajuste: podemos agregar una a los datos del scatter mediante geom_smooth. Aquí hay varios parámetros. Veremos dos que son los más importantes.

 ggplot(barrios, aes(x=n_delitos, y=n_reclamos), color='red') + 
        geom_point() + 
        geom_smooth(method = 'loess', span=0.8, se=FALSE) +
         labs(title = "Delitos registrados según contactos al SIUAC vinculados a seguridad",
         subtitle = "Barrios de la CABA, 2016 - 2017",
         caption = "Fuente: portal de datos abiertos de la Ciudad - http://data.buenosaires.gob.ar",
         x = "Cantidad de delitos",
         y = "Cantidad de contactos")

 ggplot(barrios, aes(x=n_delitos, y=n_reclamos), color='red') + 
        geom_point() + 
        geom_smooth(method = 'loess', span=0.3) +
         labs(title = "Delitos registrados según contactos al SIUAC vinculados a seguridad",
         subtitle = "Barrios de la CABA, 2016 - 2017",
         caption = "Fuente: portal de datos abiertos de la Ciudad - http://data.buenosaires.gob.ar",
         x = "Cantidad de delitos",
         y = "Cantidad de contactos")

LS0tDQp0aXRsZTogIkludHJvZHVjY2lvbiBhbCBgdGlkeXZlcnNlYCBjb24gZGF0b3MgZ2VvZ3JhZmljb3MgLSBJIg0KYXV0aG9yOiAiRHIuIEdlcm1hbiBSb3NhdGkiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQoNCmBgYHtyIGluY2x1ZGU9RkFMU0V9DQprbml0cjo6b3B0c19jaHVuayRzZXQobWVzc2FnZT1GQUxTRSwgd2FybmluZz1UUlVFLCBoaWdobGlnaHQ9VFJVRSkNCmBgYA0KDQojIyBJbnRyb2R1Y2Npw7NuDQpMYSBpZGVhIGRlIGVzdGEgbm90ZWJvb2sgZXMgcG9kZXIgaW50cm9kdWNpciBhbGd1bm9zIGNvbmNlcHRvcyBiw6FzaWNvcyBkZWwgbGxhbWFkbyBgdGlkeXZlcnNlYCBlbiBSLiBWYW1vcyBhIHRyYXRhciBkZSBoYWNlcm5vcyBhbWlnb3MgZGUgYWxndW5vcyBhbGd1bm9zIGRlIGxvcyB2ZXJib3MgcXVlIHZpbW9zIGhhY2UgdW4gcmF0byB5IHF1ZSBub3MgdmFuIGEgaGFjZXIgbGEgdmlkYSBtw6FzIGbDoWNpbCBlbiBsYSBtYW5pcHVsYWNpw7NuIGRlIGRhdG9zLg0KDQojIyMgT2JqZXRpdm9zDQoNCiogQnJpbmRhciBub2Npb25lcyBzb2JyZSBsYSBsw7NnaWNhIGdlbmVyYWwgZGVsIGB0aWR5dmVyc2VgIHBhcmEgZWwgcHJlcHJvY2VzYW1pZW50byBkZSBkYXRvcw0KKiBJbnRyb2R1Y2lyIGFsZ3VuYXMgZnVuY2lvbmVzIGLDoXNpY2FzIHBhcmEgZWwgZmlsdHJhZG8sIHRyYXNmb3JtYWNpw7NuIHkgbWVyZ2UgZGUgZGF0b3MNCiogUHJlc2VudGFyIGhlcnJhbWllbnRhcyBwYXJhIGxhIHZpc3VhbGl6YWNpw7NuIGRlIGRhdG9zDQoNCg0KIyMgUEFTTyAxLiBDYXJnYXIgbGFzIGxpYnJlcsOtYXMgYSB1dGlsaXphcg0KDQpMbyBwcmltZXJvIHF1ZSB0ZW5lbW9zIHF1ZSBoYWNlciwgc2llbXByZSwgZW4gdW5hIHNlc2nDs24gZGUgUiBlcyBjYXJnYXIgbyBpbXBvcnRhciBsYXMgbGlicmVyw61hcyBxdWUgdmFtb3MgYSB1dGlsaXphci4NCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoc2YpDQpsaWJyYXJ5KGdkYWxVdGlscykNCmBgYA0KDQoNCg0KIyMgUEFTTyAyLiBJbXBvcnRhbmRvIGxvcyBkYXRvcw0KDQpFbCAob2J2aW8pIHNpZ3VpZW50ZSBwYXNvIGVzIGltcG9ydGFyIGxvcyBkYXRvcyBxdWUgdmFtb3MgYSB1dGlsaXphci4gRW4gZXN0ZSBjYXNvLCB2YW1vcyBhIHRyYXRhciBkZSBoYWNlciB1biBhbsOhbGlzaXMgZXhwbG9yYXRvcmlvIGRlIHVuIGRhdGFzZXQgc29icmUgbGEgZGlzdHJpYnVjacOzbiBkZSBkZWxpdG9zIGVuIGxhIENpdWRhZCBBdXTDs25vbWEgZGUgQnVlbm9zIEFpcmVzLiBTZSB0cmF0YSBkZSB1biBkYXRhc2V0IG5vIG9maWNpYWwgZ2VuZXJhZG8gcG9yIGVsIGR1ZcOxbyBkZSBlc3RlICBbcmVwb3NpdG9yaW9dKGh0dHBzOi8vZ2l0aHViLmNvbS9yYW1hZGlzL2RlbGl0b3MtY2FiYSkuIFNpIGJpZW4gc29uIGRhdG9zICJubyBvZmljaWFsZXMiIGVzdMOhbiBleHRyYWlkb3MgZGUgdW5hIGZ1ZW50ZSBvZmljaWFsOiBlbCBbTWFwYSBkZWwgRGVsaXRvXShodHRwczovL21hcGEuc2VndXJpZGFkY2l1ZGFkLmdvYi5hci8pIGdlbmVyYWRvIHBvciBlbCBHQ0JBLg0KDQpQcmltZXJvLCBpbXBvcnRhbW9zIGVsIGdyYW4gYXJjaGl2byBlbiBmb3JtYXRvIC5jc3YgcXVlIGNvbnRpZW5lIGVuIGNhZGEgZmlsYSB1biBkZWxpdG8gcmVwb3J0YWRvIHkgdW5hIHNlcmllIGRlIGF0cmlidXRvcyBhc29jaWFkb3MuDQoNCmBgYHtyfQ0KZGVsaXRvcyA8LSByZWFkLmNzdigiLi4vZGF0YS9kZWxpdG9zLmNzdiIpDQpoZWFkKGRlbGl0b3MpDQpgYGANCg0KDQpIYWdhbW9zIHVuIHByaW1lciBncsOhZmljbyByw6FwaWRvIGRlIGVzdGUgZGF0YXNldCBhIHZlciBxdcOpIGVuY29udHJhbW9zLi4uIExvIG3DoXMgZsOhY2lsIGRlIHRvZG8gc2Vyw61hIHBsb3RlYXIgbGEgbGF0aXR1ZCBjb250cmEgbGEgbG9uZ2l0dWQuDQoNCmBgYHtyfQ0KZ2dwbG90KGRlbGl0b3MpICsgDQogICAgICAgIGdlb21fcG9pbnQoYWVzKHg9bG9uZ2l0dWQsIHk9bGF0aXR1ZCkpDQpgYGANCg0KDQpZYSB2ZW1vcyBxdWUgaGF5IGFsZ28gcmFyby4uLiBoYXkgdW4gcHVudGl0byBzb2xpdGFyaW8gZW4gYChsYXQ9MCwgbG9uZz0wKWAuIFBvZGVtb3MgZWxpbWluYXJsbywgZW50b25jZXMuDQoNCmBgYHtyfQ0KZGVsaXRvc19saW1waW9zIDwtIGZpbHRlcihkZWxpdG9zLCBsYXRpdHVkIT0wIHwgbG9uZ2l0dWQhPTApDQpoZWFkKGRlbGl0b3NfbGltcGlvcykNCmBgYA0KDQoNCkJpZW4uLi4geWEgaGljaW1vcyB1bmEgcHJpbWVyYSBsaW1waWV6YSBkZSBsb3MgZGF0b3MuDQoNCmBgYHtyfQ0KZ2dwbG90KGRlbGl0b3NfbGltcGlvcykgKyANCiAgICAgICAgZ2VvbV9wb2ludChhZXMoeD1sb25naXR1ZCwgeT1sYXRpdHVkKSkNCmBgYA0KDQoNCg0KKioqIA0KIyMjIyDCv1F1w6kgcGFzw7MgYWPDoT8NCkFjYWJhbW9zIGRlIGludHJvZHVjaXIgdW5hIGJ1ZW5hIGNhbnRpZGFkIGRlIGPDs2RpZ28gcXVlIGNvbnZpZW5lIGVtcGV6YXIgYSByZXZpc2FyIHBhcmEgaXIgZmlqYW5kbyBjb25jZXB0b3MuDQoNCiMjIyMjIEdyw6FmaWNvcw0KDQpBY2FiYW1vcyBkZSBoYWNlciB1bmEgdmlzdWFsaXphY2nDs24gYmllbiByw6FwaWRhLiBUZW7DrWFtb3MgdW5hIHZhcmFpYWJsZSBxdWUgbWVkw61hIGxhIHBvc2ljacOzbiBlbiBlbCBlamUgJFgkIChvIHNlYSBsYSBgbG9uZ2l0dWRgKSB5IG90cmEgcXVlIGxvIGhhY8OtYSBlbiBlbCBlamUgJFkkIChgbGF0dXR1ZGApLiBIaWNpbW9zIHVuIHNjYXR0ZXJwbG90IGRlIGVzbywgdXNhbmRvIGxhIGxpYnJlcsOtYSBgZ2dwbG90MmAuIEhheSBkb3MgcGFzb3MgYsOhc2ljb3MgcGFyYSBoYWNlciB1biBncsOhZmljbyBlbiBnZ3Bsb3Q6DQoNCjEuIGBnZ3Bsb3QoZGVsaXRvcykgKyBgICQ9PiQgQ3JlYW1vcyBlbCBvYmpldG8gYGdncGxvdGAgeSBsbyAibGxlbmFtb3MiIGNvbiB1biBkYXRhc2V0LiBFbiBlc3RlIGNhc28sIHVuYSBgdGliYmxlYA0KMi4gYGdlb21fcG9pbnQoYWVzKHg9bG9uZ2l0dWQsIHk9bGF0aXR1ZCkpYCAkPT4kIGFncmVnYW1vcyB1bmEgY2FwYSBlc3TDqXRpY2EsIGVuIGVzdGUgY2FzbyBkZSBwdW50b3MuDQoNClZhbW9zIGEgdm9sdmVyLCBwZXJvIGVuIGdlbmVyYWwsIHRvZG9zIGxvcyBncsOhZmljb3MgZGUgYGdncGxvdDJgIHNlIGNvbnN0cnV5ZW4gYWN1bXVsYW5kbyBjYXBhcyBlc3TDqXRpY2FzLg0KDQojIyMjIyBGaWx0cm9zDQoNCkVuY29udHJhbW9zIMK/dW5hPyBpbmNvbnNpc3RlbmNpYSBlbiBsYSBiYXNlIGRlIGRhdG9zLiBCw6FzaWNhbWVudGUsIHVubyBvIHZhcmlvcyByZWdpc3Ryb3MgY29uIGNvb3JkZW5hZGFzIGBsYXRpdHVkPT0wICYgbG9uZ2l0dWQ9PTBgLiBFbnRvbmNlcywgdXNhbW9zIGxhIGluc3RydWNjacOzbiBgZmlsdGVyKClgIHBhcmEgZmlsdHJhciBsb3MgY2Fzb3MgcXVlIGN1bXBsw61hbiBjb24gZXNhIGNvbmRpY2lvbi4NCg0KKioqIA0KDQoNCg0KIyMgUEFTTyAzLiBWaXN1YWxpemFuZG8gbG9zIGRhdG9zIA0KDQpZYSB2aW1vcyB1biBzY2F0dGVycGxvdCwgbXV5IMO6dGlsIHBhcmEgcGxvdGVhciBsYSBkaXN0cmlidWNpw7NuIGNvbmp1bnRhIGRlIGRvcyB2YXJpYWJsZXMgY3VhbnRpdGF0aXZhcy4gU2lnYW1vcyBjb24gbnVlc3RybyBlamVtcGxvIHkgdmVhbW9zIGFsZ3Vub3MgcGFyw6FtZXRyb3MgcGFyYSBtb2RpZmljYXIgbGEgZXN0w6l0aWNhIGRlbCBwbG90IChxdWUgdmFsZSBwYXJhIGN1YWxxdWllciBgZ2VvbV9YWFhgKS4NCg0KDQojIyMgVGFtYcOxbywgY29sb3IgeSBmb3JtYSAoeSB1bmEgcHJveWVjY2nDs24pDQoNClBvbmdhbW9zIHVuIGNvbG9yIG3DoXMgYm9uaXRvIHF1ZSBlc2UgbmVncm8uIFkgYXByb3ZlY2hlbW9zIHBhcmEgaGFjZXIgdW5hIGFjbGFyYWNpw7NuLiBFc3RyaWN0YW1lbnRlLCBlc3RhbW9zIHRyYWJhamFuZG8gY29uIHZhcmlhYmxlcyBkZSBjb29yZGVuYWRhcyBnZW9ncsOhZmljYXMuIE5vIHRlbmVtb3MgdGllbXBvIGVuIGVsIGN1cnNvIHBhcmEgdHJhYmFqYXIgZW4gZGV0YWxsZSBlc3RlIHRlbWEgcGVybyBkaWdhbW9zIHF1ZSBzb24gdmFyaWFibGVzIGN1YW50aXRhdGl2YXMgImVzcGVjaWFsZXMiLiBDYWRhIG1hcGEgZXN0w6EgZGlidWphZG8gc2Vnw7puIHVuIHNpc3RlbWEgZGUgY29vcmRlbmFkYXMgcXVlIGxsZXZhbiBsYSBmb3JtYSAiZ2XDs2RpY2EiIGRlIGxhIHRpZXJyYSAoYsOhc2ljYW1lbnRlLCBhbGdvIHF1ZSBlc3TDoSBlbiAzIGRpbWVuc2lvbmVzKSBhIHVuIHBsYW5vIChlbiAyIGRpbWVuc2lvbmVzKS4NCg0KRXMgZGVjaXIgcXVlICJkZWZvcm1hbiIgbGEgZm9ybWEgZGUgbGEgdGllcnJhIHBhcmEgbGxldmFybGEgYSBsYSBob2phIGRlIHBhcGVsIChvIGEgbGEgcGFudGFsbGEsIGVuIGVzdGUgY2FzbykuIFNpIHNlIGZpamFuLCBsYSBDQUJBIGFwYXJlY2UgbWVkaW8gYWxhcmdhZGEgZW4gZWwgbWFwYSBhbnRlcmlvci4gRXN0byBlcyBwb3JxdWUgbm8gbGUgZXNwZWNpZmljYW1vcyBhIGBnZ3Bsb3RgZW4gcXXDqSBzaXN0ZW1hIGRlIGNvb3JkZW5hZGFzIGVzdMOhLiANCg0KUGFzZW1vcywgZW50b25jZXMgbGEgY2FwYSBgY29vcmRfbWFwKCdtZXJjYXRvcicpYA0KDQoNCioqKiANClBhcmEgbcOhcyBkZXRhbGxlcyBzb2JyZSBzaXN0ZW1hcyBkZSBjb29yZGVuYWRhcyB5IHJlZmVyZW5jaWEgcHVlZGVuIGNvbnN1bHRhciBbQ2llbmNpYSBkZSBEYXRvcyBwYXJhIEdlbnRlIFNvY2lhYmxlXShodHRwczovL2JpdHNhbmRicmlja3MuZ2l0aHViLmlvL2NpZW5jaWFfZGVfZGF0b3NfZ2VudGVfc29jaWFibGUvKSBkZSBBbnRvbmlvIFbDoXpxdWV6IEJydXN0LCBtYXRlcmlhbCAoeSBhdXRvcikgcXVlIGRpZXJvbiB1bmEgbWFubyBncmFuZGUgYSBlc3RlIGN1cnNvIC1hZGVtw6FzIGRlIHNlciBjb2xlZ2EgeSBhbWlnby0uDQoNCioqKiANCg0KDQpgYGB7cn0NCmdncGxvdChkZWxpdG9zX2xpbXBpb3MpICsgDQogICAgICAgIGdlb21fcG9pbnQoYWVzKHg9bG9uZ2l0dWQsIHk9bGF0aXR1ZCksIGNvbG9yPSdibHVlJykgKw0KICAgICAgICBjb29yZF9tYXAoIm1lcmNhdG9yIikNCmBgYA0KDQoNCkFob3JhIHNlIHBhcmVjZSBtw6FzIGFsIEJ1ZW5vcyBBaXJlcyBkZSBHYXJkZWwuLi4NCg0KQmllbiwgY2FtYmllbW9zIGVsIHRhbWHDsW8gZGUgbG9zIHB1bnRpdG9zLiBFbCBwYXLDoW1ldHJvIGBzaXplYCBlc3TDoSBlbiBwaXhlbHMsIHBvciBsbyBjdWFsIG5vIGVzIGbDoWNpbCBlc3RpbWFybG8gc2luIHZlciB1bmEgdmVyc2nDs24gcHJldmlhIGRlbCBwbG90LCBwcmltZXJvLg0KDQpgYGB7cn0NCmdncGxvdChkZWxpdG9zX2xpbXBpb3MpICsgDQogICAgICAgIGdlb21fcG9pbnQoYWVzKHg9bG9uZ2l0dWQsIHk9bGF0aXR1ZCksIGNvbG9yPSdyZWQnLCBzaXplPTAuMDUpICsNCiAgICAgICAgY29vcmRfbWFwKCJtZXJjYXRvciIpDQpgYGANCg0KDQpZLCBwb3Igw7psdGltbywgY2FtYmllbW9zIGxhIGZvcm1hLi4uDQoNCmBgYHtyfQ0KZ2dwbG90KGRlbGl0b3NfbGltcGlvcykgKyANCiAgICAgICAgZ2VvbV9wb2ludChhZXMoeD1sb25naXR1ZCwgeT1sYXRpdHVkKSwgY29sb3I9J3JlZCcsIHNpemU9MC4wNSwgc2hhcGU9MykgKw0KICAgICAgICBjb29yZF9tYXAoIm1lcmNhdG9yIikNCmBgYA0KDQoNCg0KIyMjIEZhY2V0YWRvDQoNCkFob3JhLCBzaSBxdWVyZW1vcyBhZ3JlZ2FyIG3DoXMgZGltZW5zaW9uZXMgYWwgcGxvdC4uLiBsYSBjb3NhIHNlIGhhY2UgdW4gcG9jbyBtw6FzIGRlbnNhLiBFcyBwb3IgZXNvIHF1ZSBwb2RlbW9zIHVzYXIgdW5hIG51ZXZhICJjYXBhIiBkZSBgZ2dwbG90YCwgbGxhbWFkYSAqZmFjZXRhZG8qLiBMYSBpZGVhIGVzIHF1ZSBwb2RlbW9zIHRlbmVyIGdyw6FmaWNvcyBjb250aW5ndW9zLCBjb25kaWNpb2Fkb3MgYSBsb3MgdmFsb3JlcyBkZSB1bmEgdmFyaWFibGUgKGdlbmVyYWxtZW50ZSwgY2F0ZWfDs3JpY2EpLiBWZWFtb3MsIGVudG9uY2VzLCB1biBwbG90IHBvciBjYWRhIHVubyBkZSBsb3MgdGlwb3MgZGUgZGVsaXRvcy4uLg0KDQpgYGB7ciwgZmlnLmhlaWdodCA9IDUsIGZpZy53aWR0aCA9IDh9DQpnZ3Bsb3QoZGVsaXRvc19saW1waW9zKSArIA0KICAgICAgICBnZW9tX3BvaW50KGFlcyh4PWxvbmdpdHVkLCB5PWxhdGl0dWQsIGNvbG9yPXRpcG9fZGVsaXRvKSwgc2l6ZT0wLjA1LCBhbHBoYT0wLjI1KSArDQogICAgICAgIGZhY2V0X3dyYXAofnRpcG9fZGVsaXRvKSArDQogICAgICAgIGNvb3JkX21hcCgibWVyY2F0b3IiKQ0KYGBgDQoNCg0KKsK/UXXDqSBkaWZlcmVuY2lhcyBoYXkgY29uIGxvcyBwbG90cyBhbnRlcmlvcmVzPyoNCg0KDQoqKiogDQojIyMjIENvbnNpZ25hIDEuDQoNCiBHZW5lcmFyIGxvcyBzaWd1aWVudGVzIGdyw6FmaWNvczoNCg0KKiBFbCBncsOhZmljbyBhbnRlcmlvciwgdmFyaWFuZG8gbGEgY2FwYSBgZmFjZXRfd3JhcGAgcG9yIGBmYWNldF9ncmlkYC4NCiogRWwgZ3LDoWZpY28gYW50ZXJpb3IsIHNpbiBmYWNldGFkbyB5IGNvbiB1biBjb2xvciBwb3IgY2FkYSB0aXBvIGRlIGRlbGl0by4NCg0KKioqIA0KDQpgYGB7ciBldmFsPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KZ2dwbG90KGRlbGl0b3NfbGltcGlvcykgKyANCiAgICAgICAgZ2VvbV9wb2ludChhZXMoeD1sb25naXR1ZCwgeT1sYXRpdHVkLCBjb2xvcj10aXBvX2RlbGl0byksIHNpemU9MC4wNSwgYWxwaGE9MC4yNSkgKw0KICAgICAgICBmYWNldF9ncmlkKH50aXBvX2RlbGl0bykgKw0KICAgICAgICBjb29yZF9tYXAoIm1lcmNhdG9yIikNCg0KZ2dwbG90KGRlbGl0b3NfbGltcGlvcykgKyANCiAgICAgICAgZ2VvbV9wb2ludChhZXMoeD1sb25naXR1ZCwgeT1sYXRpdHVkLCBjb2xvcj10aXBvX2RlbGl0byksIHNpemU9MC4wNSwgYWxwaGE9MC4yNSkgKw0KICAgICAgICBjb29yZF9tYXAoIm1lcmNhdG9yIikNCmBgYA0KDQoNCg0KDQoNCiMjIyBPdHJvcyBwbG90cy4uLg0KDQpWZWFtb3MgYWhvcmEgY3XDoWxlcyBzb24gbG9zIHRpcG9zIGRlIGRlbGl0b3MgbcOhcyBjb211bmVzIGVuIGxhIENBQkEuDQoNCg0KYGBge3J9DQpnZ3Bsb3QoZGVsaXRvc19saW1waW9zLCBhZXMoeD10aXBvX2RlbGl0bykpKw0KICAgICAgICBnZW9tX2JhcihzdGF0PSJjb3VudCIpDQpgYGANCg0KDQpTZSB2ZSBiYXN0YW50ZSBiaWVuLi4uIGF1bnF1ZSB0ZW5lbW9zIGFjb21vZGFyIHVuIHBvY28gbGFzIGV0aXF1ZXRhcy4gVW5hIG9wY2nDs24gZXMgcGVkaXJsZSBhIGBnZ3Bsb3RgIHF1ZSBsYXMgYWJyZXZpZToNCg0KDQpgYGB7cn0NCmdncGxvdChkZWxpdG9zX2xpbXBpb3MsIGFlcyh4PXRpcG9fZGVsaXRvKSkrDQogICAgICAgIGdlb21fYmFyKHN0YXQ9ImNvdW50IikgKyANCiAgICAgICAgc2NhbGVfeF9kaXNjcmV0ZShsYWJlbHMgPSBhYmJyZXZpYXRlKQ0KYGBgDQoNCg0KT3RyYSBlcyBwYXNhcmxlIG5vc290cm9zIHVuIHZlY3RvciBkZSBldGlxdWV0YXM6DQoNCmBgYHtyfQ0KZ2dwbG90KGRlbGl0b3NfbGltcGlvcywgYWVzKHg9dGlwb19kZWxpdG8pKSsNCiAgICAgICAgZ2VvbV9iYXIoc3RhdD0iY291bnQiKSArIA0KICAgICAgICBzY2FsZV94X2Rpc2NyZXRlKGxhYmVscyA9IGMoJ0guZG9sb3NvJywnSC5zZWcudmlhbCcsICdIdXJ0byhzL3YpJywgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnUm9ibyhjL3YpJywgJ1JvYm8gYXV0bycsICdIdXJ0byBhdXRvJywgJ0xlc2lvbi5zZWcudmlhbCcpKQ0KYGBgDQoNCg0KKioqIA0KIyMjIyBDb25zaWduYSAyLg0KDQpHZW5lcmFyIHVuIGdyw6FmaWNvIGRlIGJhcnJhcyBwb3IgY29tdW5hDQoNCioqKiANCg0KYGBge3IgZXZhbD1GQUxTRSwgaW5jbHVkZT1GQUxTRX0NCmdncGxvdChkZWxpdG9zX2xpbXBpb3MsIGFlcyh4PWNvbXVuYSkpKw0KICAgICAgICBnZW9tX2JhcihzdGF0PSJjb3VudCIpDQpgYGANCg0KDQojIyMgSGlzdG9ncmFtYXMgDQoNClVuIGhpc3RvZ3JhbWEsIGNvbW8gaGVtb3MgdmlzdG8sIHNlIHVzYSBwYXJhIG1vc3RyYXIgbGEgZGlzdHJpYnVjacOzbiBkZSB1bmEgdmFyaWFibGUgY29udGludWEuIFBvciBlamVtcGxvLCBwb2Ryw61hbW9zIGhhY2VyIHVuIGhpc3RvZ3JhbWEgZGUgbGEgZGlzdHJpYnVjacOzbiBkZSBlZGFkZXMgZGUgbGEgRVBIIG8gZGUgbG9zIGluZ3Jlc29zLiANCg0KTm8gdGVuZW1vcyB1bmEgdmFyaWFibGUgY3VhbnRpdGF0aXZhLCB2YW1vcyBhIGludmVudGFybm9zIHVuYS4gUGVybyBwcmV2aWFtZW50ZSB2YW1vcyBhIHRlbmVyIHF1ZSBpbnRyb2R1Y2lyIGFsZ3VuYXMgaWRlYXMgZGUgbGltcGllemEgZGUgZGF0b3MuLi4NCg0KDQoNCiMjIFBBU08gNC4gTGltcGllemEgZGUgZGF0b3MNCg0KUmVjb3JkZW1vczoNCg0KKiBjYXJnYW1vcyBsaWJyZXLDrWFzIGNvbiBgbGlicmFyeSgpYA0KKiBjYXJnYW1vcyBkYXRvcyBjb24gYHJlYWRfY3N2KClgDQoqIGZpbHRyYW1vcyBkYXRvcyBjb24gYGZpbHRlcigpYA0KKiBoaWNpbW9zIGFsZ3VuYXMgdmlzdXphbGl6YWNpb25lcyBiw6FzaWNhcyBjb24gYGdncGxvdDJgDQoNCkFob3JhIGxhIGNvc2Egc2UgcG9uZSBzZXJpYS4gVW5hIGRlIGxhcyBwcmltZXJhcyBjb3NhcyBxdWUgdmFtb3MgYSB0ZW5lciBxdWUgaGFjZXIgZXMgcG9kZXIgdGVuZXIgdW5hIHZpc2nDs24gbcOhcyBvIG1lbm9zIGdlbmVyYWwgZGUgbGEgdGFibGEgY29uIGxhIHF1ZSBlc3RhbW9zIHRyYWJhamFuZG8uIFBhcmEgZXNvLCBsbGFtYW1vcyBhIGxhIGZ1bmNpw7NuIGBzdHIoKWAgcXVlIHB1ZWRlIHVzYXJzZSBwYXJhIGN1YWxxdWllciBjbGFzZSBkZSBvYmpldG8gZW4gUi4NCg0KYGBge3J9DQpzdHIoZGVsaXRvc19saW1waW9zKQ0KYGBgDQoNCg0Kwr9RdcOpIHNlIHB1ZWRlIG9ic2VydmFyIGVuIGVzdGEgc2FsaWRhPw0KDQoqIEVzIHVuIGRhdGFmcmFtZSBjb24gMjM3LjQ0NSBvYnNlcnZhY2lvbmVzIHkgMTQgdmFyaWFibGVzDQoqIFRlbmVtb3Mgbm9tYnJlLCB0aXBvIHkgYWxndW5vcyB2YWxvcmVzIGRlIGNhZGEgdW5hIGRlIGxhcyB2YXJpYWJsZXMNCiogSGF5IHZhcmlhcyBjb2x1bW5hcyBxdWUgc29uIGBmYWN0b3JzYA0KDQpQb3IgZGVmZWN0bywgUiBhc3VtZSBxdWUgdG9kYSBjb2x1bW5hIGRlIHRleHRvIGVzIHVuIGZhY3Rvci4gRXN0ZSDDumx0aW1vIHB1bnRvIHB1ZWRlIGxsZWdhciBhIHRyYWVyIHByb2JsZW1hcy4gUG9yIGVzbyBzdWVsZSBzZXIgYnVlbmEgaWRlYSBzZXRlYXIgZWwgYXJndW1lbnRvIGBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFYDogYGRhdG9zIDwtIHJlYWQuY3N2KCIvcnV0YS9hL21pc2RhdG9zLmNzdiIsIHN0cmluZ3NBc0ZhY3RvcnM9RkFMU0UpYC4NCg0KT3RybyBwYXNvIMO6dGlsIGVzIGxsYW1hciBhIGxhIGZ1bmNpw7NuIGBzdW1tYXJ5KClgDQoNCg0KYGBge3J9DQpzdW1tYXJ5KGRlbGl0b3NfbGltcGlvcykNCmBgYA0KDQoNClJlY29yZGVtb3MuLi4gbGFzIGNhdGVnb3LDrWFzIGRlIHVuIGZhY3RvciBlbiBSIHNlIGxsYW1hbmEgIm5pdmVsZXMiDQoNCiogwr9DdcOhbGVzIGRlIGxhcyB2YXJpYWJlcyBlbiBlc3RlIGRhdGFzZXQgc29uIGZhY3RvcnM/IFZlYW1vcyBsb3MgbGV2ZWxzIGRlIHVuYSBkZSBlbGxhcy4uLg0KDQpgYGB7cn0NCmxldmVscyhkZWxpdG9zX2xpbXBpb3MkYmFycmlvKQ0KYGBgDQoNCg0KRW4gcHJpbmNpcGlvLCBlc3RhIHZhcmlhYmxlIHBhcmVjZSBlc3RhciBiaWVuLiBTaW4gZW1iYXJnbywgZXhpc3RlbiBhbCBtZW5vcyBvdHJhcyBkb3MgdmFyaWFibGVzIHF1ZSBubyBwYXJlY2UgdGVuZXIgZGVtYXNpYWRvIHNlbnRpZG8gcXVlIHRlbmdhbiBmb3JtYXRvIGRlIGZhY3RvcjogZmVjaGEgeSBob3JhLiBFbiBlZmVjdG8sIGV4aXN0ZSB1biB0aXBvIGRlIGRhdG8gZXNwZWNpYWwgZW4gUiBwYXJhIGRhdG9zIGRlIHRpZW1wbywgZmVjaGEsIGhvcmEsIGV0Yy4gVmFtb3MgYSBoYWNlciB1c28gZGVsIHBhcXVldGUgYGx1YnJpZGF0ZWA6DQoNCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCg0KZGVsaXRvc19saW1waW9zIDwtIG11dGF0ZShkZWxpdG9zX2xpbXBpb3MsIGZlY2hhPXltZChmZWNoYSksIGhvcmE9aG1zKGhvcmEpKQ0KYGBgDQoNCkFxdcOtLCB1c2Ftb3MgbGEgaW5zdHJ1Y2Npw7NuIGBtdXRhdGUoKWAgcGFyYSB0cmFuc2Zvcm1hciBsYXMgZG9zIGNvbHVtbmFzIGp1bnRvIGNvbiBsYXMgZnVuY2lvbmVzIGB5bWQoKWAgcG9yICJ5ZWFyIC8gbW9udGggLyBkYXkiIHkgDQoNCg0KQWhvcmEgZXN0YW1vcyBlbiBjb25kaWNpb25lcyBkZSBwZW5zYXIgbnVlc3RybyBoaXN0b2dyYW1hLiBWYW1vcyBhIGNvbnRhciBjdcOhbnRvcyBoZWNob3MgZGVsaWN0aXZvcyBodWJvIHBvciBkw61hIHkgbHVlZ28gZ2VuZXJhcmVtb3Mgc29icmUgZXN0YSBudWV2YSB0YWJsYSBlbCBoaXN0b2dyYW1hLg0KDQpgYGB7cn0NCnAgPC0gZ3JvdXBfYnkoZGVsaXRvc19saW1waW9zLCBmZWNoYSkgDQpwZXJpb2RvIDwtIHN1bW1hcmlzZShwLCBncmFuX3RvdGFsID0gbigpKQ0KDQpoZWFkKHBlcmlvZG8pDQpgYGANCg0KDQpBaG9yYSwgc29sYW1lbnRlLCB0ZW5lbW9zIHF1ZSBjcmVhciBhIG51ZXN0cm8gaGlzdG9ncmFtYTogDQoNCioqKg0KSGF5IHVuYXMgY3XDoW50YXMgaW5zdHJ1Y2Npb25lcyBxdWUgbm8gdmltb3MgeSBxdWUgcmV0b21hcmVtb3MgZW5zZWd1aWRhLiBObyBvYnN0YW50ZSwgZWwgY8OzZGlnbyBlcyBsbyBzdWZpY2llbnRlbWVudGUgZXhwcmVzaXZvIGNvbW8gcGFyYSBwb2RlciBpbnR1aXIgcXXDqSBwYXPDszogDQoNCjEuIHRvbWFtb3MgZWwgZGF0YXNldCBvcmlnaW5hbA0KMi4gYWdydXBhbW9zIHBvciBlbCBjYW1wbyAiZmVjaGEiIChlcyBkZWNpciwgcG9yIGTDrWEpDQozLiBoaWNpbW9zIHVuIGNvbnRlbyBkZSBjdcOhbnRvcyByZWdpc3Ryb3MgaGFiw61hIHBvciBjYWRhIGTDrWENCioqKg0KDQoNCg0KYGBge3J9DQpnZ3Bsb3QocGVyaW9kbykgKyANCiAgICAgICAgZ2VvbV9oaXN0b2dyYW0oYWVzKHggPSBncmFuX3RvdGFsKSkNCmBgYA0KDQpQb2RlbW9zIHZlciBxdWUgaGF5IHVuIHJhbmdvIGdyYW5kZSBlbiBsYSBjYW50aWRhZCBkZSBkZWxpdG9zIHBvciBkw61hOiANCg0KKiBodWJvIGTDrWFzIGNvbiBtZW5vcyBkZSAxMDAgZGVsaXRvcyB5IGTDrWFzIGNvbiA1MDANCiogbm8gb2JzdGFudGUsIGxhIG1heW9yIHBhcnRlIGRlIGxvcyBkw61hcyBwYXJlY2VuIGNvbmVudHJhcnNlIGVudHJlIDMwMCB5IDQwMCBkZWxpdG9zDQoNCg0KUG9kcsOtYW1vcyB0YW1iacOpbiBjb25zdHJ1aXIgdW4gaGlzdG9ncmFtYSBwYXJhIGNhZGEgdGlwbyBkZSBkZWxpdG8uLi4NCg0KDQpgYGB7cn0NCnAgPC0gZ3JvdXBfYnkoZGVsaXRvc19saW1waW9zLCBmZWNoYSwgdGlwb19kZWxpdG8pDQpwZXJpb2RvIDwtIHN1bW1hcmlzZShwLCBncmFuX3RvdGFsPW4oKSkNCg0KaGVhZChwZXJpb2RvKQ0KYGBgDQoNCg0KQWhvcmEsIGZhY2V0YW5kby4uLg0KDQpgYGB7cn0NCmdncGxvdChwZXJpb2RvKSArIA0KICAgICAgICBnZW9tX2hpc3RvZ3JhbShhZXMoeD1ncmFuX3RvdGFsKSkgKyANCiAgICAgICAgZmFjZXRfd3JhcCh+dGlwb19kZWxpdG8pDQpgYGANCg0KDQojIyMjIEludGVncmFuZG8gZGF0b3MgZGUgZGlmZXJlbnRlcyBmdWVudGVzDQoNCkFob3JhIGJpZW4sIGVuIGdlbmVyYWwgYWwgdXRpbGl6YXIgZGF0b3MgImVuIGxhIHZpZGEgcmVhbCIgbm8gZXMgaGFiaXR1YWwgdGVuZXIgdW5hIHNvbGEgdGFibGEuIEVzIGhhYml0dWFsIHRlbmVyIHF1ZSBpbnRlZ3JhciBkYXRvcyBkZSB2YXJpYXMgZnVlbnRlcy4gWWEgdHJhYmFqYXJlbW9zIGNvbiBsb3MgZGF0b3MgZGUgbGEgW0VuY3Vlc3RhIFBlcm1hbmVudGUgZGUgSG9nYXJlcyBkZWwgSU5ERUNdKGh0dHBzOi8vd3d3LmluZGVjLmdvYi5hci9iYXNlcy1kZS1kYXRvcy1lcGgtYnVjLmFzcCkgeSB2ZXJlbW9zIHF1ZSB0ZW5lbW9zLCBhbCBtZW5vcywgZG9zIHRhYmxhcyBwYXJhIHRyYWJhamFyOiB1bmEgY29ycmVzcG9uZGllbnRlIGEgbG9zIGRhdG9zIGRlIGxvcyBpbmRpdmlkdW9zIGVuY3Vlc3RhZG9zIHkgb3RyYSBjb3JyZXNwb25kaWVudGUgYSBsb3MgZGUgbG9zIGhvZ2FyZXMgZW4gcXVlIGhhYml0YW4gZXNvcyBpbmRpdmlkdW9zLg0KDQpBaG9yYS4uLiBzdXBvbmdhbW9zIHF1ZSB0ZW5lbW9zIHVuYSBudWV2YSBmdWVudGUgZGUgZGF0b3M6IGxvcyByZWNsYW1vcyBkZWwgU2lzdGVtYSBkZSBBdGVuY2nDs24gQ2l1ZGFkYW5hIGNvcnJlc3BvbmRpZW50ZXMgYSBsb3MgYcOxb3MgMjAxNiAodW5vIGRlIGxvcyBhw7FvcyBxdWUgYWJhcmNhIGVsIGRhdGFzZXQgZGUgZGVsaXRvcyk6DQoNCmBgYHtyfQ0KdGFibGUoeWVhcihkZWxpdG9zX2xpbXBpb3MkZmVjaGEpKQ0KYGBgDQoNCg0KYGBge3J9DQphdF9jaXVkYWRhbm8gPC0gcmVhZC5jc3YoIi4uL2RhdGEvc2lzdGVtYS11bmljby1kZS1hdGVuY2lvbi1jaXVkYWRhbmEtMjAxNi5jc3YiLCBzZXA9IjsiKQ0KaGVhZChhdF9jaXVkYWRhbm8pDQpgYGANCg0KDQpQb2Ryw61hbW9zIHBlbnNhciBxdWUgZW4gYXF1ZWxsb3MgYmFycmlvcyBlbiBsb3MgcXVlIG1heW9yIHByZXZhbGVuY2lhIGRlIGRlbGl0b3MgZXhpc3RlLCBkZWJlcsOtYSB2ZXJpZmljYXJzZSB1bmEgbWF5b3IgY2FudGlkYWQgZGUgcmVjbGFtb3MgcG9yIHJ1YnJvcyBhc29jaWFkb3MgYSBlc3RvcyBkZWxpdG9zLiBPYnNlcnZlbW9zIHByZXZpYW1lbnRlIGxhIGVzdHJ1Y3R1cmEgZGUgbnVlc3RybyBkYXRhc2V0IGUgaWRlbnRpZmlxdWVtb3MgcXXDqSB2YXJpYWJsZSBlc3BlY2lmaWNhIGVsIHJ1YnJvIGRlIHJlY2xhbW8geSB2ZWFtb3MgbG9zIGxldmVscyBjb3JyZXNwb25kaWVudGVzLi4uDQoNCmBgYHtyfQ0Kc3RyKGF0X2NpdWRhZGFubykNCmBgYA0KDQoNCkhhY2llbmRvIHVuYSBpbnNwZWNjacOzbiByw6FwaWRhLCBwdWVkZSB2ZXJzZSBxdWUgDQoNCg0KKiBERU5VTkNJQVMgU09CUkUgSU5DT05EVUNUQVMgUkVGRVJJREFTIEEgTEEgQUNUVUFDSU9OIFBPTElDSUFMDQoqIEVNRVJHRU5DSUFTDQoqIFNFR1VSSURBRA0KKiBTRUdVUklEQUQgRSBISUdJRU5FDQoNCg0Kc29uIGxhcyBjYXRlZ29yw61hcyBxdWUgcG9kcsOtYW4gZXN0YXIgYXNvY2lhZGFzIGEgcmVjbGFtb3MgcG9yIGluc2VndXJpZGFkIHkgaGVjaG9zIGRlbGljdGl2b3MuDQoNCg0KIyMjIyBUcmFuc2Zvcm1hbmRvIHkgdmluY3VsYW5kbyBkYXRvczogbG9zIDUgdmVyYm9zIGRlbCBgdGlkeXZlcnNlYA0KDQpBaG9yYSBiaWVuLi4uIMK/Y8OzbW8gdmluY3VsYW1vcyBhbWJhcyB0YWJsYXM/IEVsIHByb2JsZW1hIGVzIHF1ZSBlbiBhbWJvcyBjYXNvcyB0ZW5lbW9zIHJlZ2lzdHJvcyBpbmRpdmlkdWFsZXMgKGRlbGl0b3MgZW4gdW5hIGNhc28sIHJlY2xhbW9zIGVuIG90cm8pLiBUZW5lbW9zLCBlbnRvbmNlcywgcXVlIGFncmVnYXJsb3MgZGUgYWxndW5hIGZvcm1hIGEgdW5hIHVuaWRhZCBjb23Dum4uIEVuIHByaWNpcGlvLCBoYXkgdW5hIGNvbHVtbmEgIkJBUlJJTyIgZW4gYW1iYXMuIEVzbyBlcyB1bmEgcHJpbWVyYSBwb3NpYmlsZGlhZC4gQ29udGVtb3MsIGVudG9uY2VzLCBsYSBjYW50aWRhZCBkZSBkZWxpdG9zICh0b3RhbCkgcG9yIGJhcnJpbyB5IGxhIGNhbnRpZGFkIGRlIHJlY2xhbW9zZW4gbGFzIGN1YXRybyBjYXRlZ29yw61hcyBhbnRlcmlvcmVzIHkgcGVndWVtb3MgYW1iYXMgdGFibGFzIGp1bnRhcy4gRXN0byBub3MgdmEgYSBwZXJtaXRpciBpbnRyb2R1Y2lyIHZhcmlhcyBmdW5jaW9uZXMsIG9wZXJhZG9yZXMgeSBjb21hbmRvcyBzdW1hbWVudGUgw7p0aWxlcy4NCg0KUGFyYSBoYWNlciB0b2RvIGVzdG8sIHkgbXVjaG8gbcOhcywgdmFtb3MgYSBhcHJlbmRlciBmdW5jaW9uZXMgcXVlIHJlcHJlc2VudGFuIGNpbmNvIHZlcmJvcyBiw6FzaWNvcyBwYXJhIGxhIHRyYW5zZm9ybWFjacOzbiBkZSBkYXRvczoNCg0KKiBgc2VsZWN0KClgOiBzZWxlY2Npb25hciAtZWxlZ2lyLSBjb2x1bW5hcyBwb3Igc3Ugbm9tYnJlDQoqIGBmaWx0ZXIoKWA6IGZpbHRyYXIsIGVzIGRlY2lyIHF1ZWRhcnNlIHPDs2xvIGNvbiBsYXMgZmlsYXMgcXVlIGN1bXBsYW4gY2llcnRhIGNvbmRpY2nDs24NCiogYGFycmFuZ2UoKWA6IG9yZGVuYXIgbGFzIGZpbGFzIGRlIGFjdWVyZG8gYSBzdSBjb250ZW5pZG8gbyBhbGfDum4gb3RybyDDrW5kaWNlDQoqIGBtdXRhdGUoKWA6IG11dGFyIC1jYW1iaWFyLSB1biBkYXRhZnJhbWUsIG1vZGlmaWNhbmRvIGVsIGNvbnRlbmlkbyBkZSBzdXMgY29sdW1uYXMgbyBjcmVhbmRvIGNvbHVtbmFzIChlcyBkZWNpciwgdmFyaWFibGVzKSBudWV2YXMNCiogYHN1bW1hcmlzZSgpYDogcHJvZHVjaXIgc3VtYXJpb3MgLXVuIHZhbG9yIGV4dHJhw61kbyBkZSBtdWNob3MsIHBvciBlamVtcGxvIGVsIHByb21lZGlvLSBjb24gZWwgY29udGVuaWRvIGRlIGxhcyBjb2x1bW5hcw0KDQpFc3RhcyBmdW5jaW9uZXMgdGllbmVuIHVuYSBzaW50YXhpcywgdW5hIGZvcm1hIGRlIGVzY3JpYmlyc2UsIHVuaWZvcm1lLiBFbCBwcmltZXIgYXJndW1lbnRvIHF1ZSB0b21hbiBzaWVtcHJlIGVzIHVuIGRhdGFmcmFtZTsgbG9zIHNpZ3VpZW50ZXMgaW5kaWNhbiBxdcOpIGhhY2VyIGNvbiBsb3MgZGF0b3MuIEVsIHJlc3VsdGFkbyBzaWVtcHJlIGVzIHVuIG51ZXZvIGRhdGFmcmFtZS4NCg0KTGFzIGZ1bmNpb25lcyBzb24gcGFydGUgZGUgYGRwbHlyYCwgdW5vIGRlIGxvcyBjb21wb25lbnRlcyBkZSBsYSBmYW1pbGlhIGRlIHBhcXVldGVzIFRpZHl2ZXJzZS4gWWEgdGVuZW1vcyBkaXNwb25pYmxlIHRvZG8gbG8gbmVjZXNhcmlvLCBhY3RpdmFkbyBjdWFuZG8gaW52b2NhbW9zIGBsaWJyYXJ5KHRpZGl2ZXJzZSlgIGFsIGNvbWllbnpvLg0KDQpFbXBlY2Vtb3MgcG9yIGVsIGRhdGFzZXQgYGF0X2NpdWRhZGFub2AuIA0KDQoxLiBMbyBwcmltZXJvIHF1ZSB0ZW5lbW9zIHF1ZSBoYWNlciBlcyBzZWxlY2Npb25hciBsYXMgY29sdW1uYXMgY29uIGxhcyBxdWUgdmFtb3MgYSB0cmFiYWphci4gUGFyYSBlbGxvLCB2YW1vcyBhIGludHJvZHVjaXIgbGEgZnVuY2nDs24gYHNlbGVjdCgpYA0KDQpgYGB7cixtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPVRSVUV9DQphdF9iYXJyaW8gPC0gc2VsZWN0KGF0X2NpdWRhZGFubywgRE9NSUNJTElPX0JBUlJJTywgUlVCUk8pDQpoZWFkKGF0X2JhcnJpbykNCmBgYA0KDQoNCjIuIEx1ZWdvLCB2YW1vcyBhIGZpbHRyYXIgLWBmaWx0ZXIoKWAtIGxhcyBjYXRlZ29yw61hcyBxdWUgbm8gbm9zIGludGVyZXNhbjoNCg0KYGBge3J9DQphdF9iYXJyaW8gPC0gZmlsdGVyKGF0X2JhcnJpbywgUlVCUk8gPT0gJ0RFTlVOQ0lBUyBTT0JSRSBJTkNPTkRVQ1RBUyBSRUZFUklEQVMgQSBMQSBBQ1RVQUNJT04gUE9MSUNJQUwnIHwgUlVCUk8gPT0gJ0VNRVJHRU5DSUFTJyB8ICBSVUJSTyA9PSAnU0VHVVJJREFEJyB8IFJVQlJPID09ICdTRUdVUklEQUQgRSBISUdJRU5FJyB8IFJVQlJPID09ICdWRUhJQ1VMT1MgREUgRkFOVEFTSUEnKQ0KYGBgDQoNCg0KMy4gQWhvcmEsIGRlYmVyw61hbW9zIHN1bWFyIGxhIGNhbnRpZGFkIGRlIHJlY2xhbW9zIHBvciBiYXJyaW8gLWBncm91cF9ieSgpICsgc3VtbWFyaXNlKClgOg0KDQpgYGB7cn0NCmF0X2JhcnJpb19hZ2cgPC0gZ3JvdXBfYnkoYXRfYmFycmlvLCBET01JQ0lMSU9fQkFSUklPKQ0KYXRfYmFycmlvX2FnZyA8LSBzdW1tYXJpemUoYXRfYmFycmlvX2FnZywgdG90YWw9bigpKQ0KYGBgDQoNCg0KNC4gUG9yIMO6bHRpbW8sIHkgY29tbyBwYXJhIHNlciBnZW50ZSBwcm9saWphLCBvcmRlbmVtb3MgZW4gZm9ybWEgZGVzY2VuZGVudGUgbG9zIGJhcnJpb3M6DQoNCmBgYHtyfQ0KYXRfYmFycmlvX2FnZyA8LSBhcnJhbmdlKGF0X2JhcnJpb19hZ2csIGRlc2ModG90YWwpKQ0KaGVhZChhdF9iYXJyaW9fYWdnKQ0KYGBgDQoNCg0KRW50b25jZXMsIGhhc3RhIGFjw6Egdmltb3MgdHJlcyBkZSBsb3MgY2luY28gdmVyYm9zLi4uIE5vcyBmYWx0YSBhbmFsaXphciBgbXV0YXRlKClgLiBTb2JyZSBlc28gdm9sdmVyZW1vcyBlbnNlZ3VpZGEuDQoNCg0KIyMjIE9wZXJhZG9yIHBpcGUgYCU+JWAgDQoNCkFudGVzIGRlIHRlcm1pbmFyLCB2YW1vcyBhIHByZXNlbnRhciB1bmEgaGVycmFtaWVudGEgbcOhczogZWwgb3BlcmFkb3IgcGlwZSAocHJvbsO6bmNpZXNlIOKAnHBhaXDigJ0sIGVzIGVsIHTDqXJtaW5vIGVuIGluZ2zDqXMgcXVlIHNpZ25pZmljYSDigJx0dWJv4oCdKS4NCg0KRWwgcGlwZSBlcyB1biBvcGVyYWRvcjogdW4gc8OtbWJvbG8gcXVlIHJlbGFjaW9uYSBkb3MgZW50aWRhZGVzLiBEaWNobyBlbiBmb3JtYSBtw6FzIHNpbXBsZSwgZWwgcGlwZSBkZSBSLCBjdXlvIHPDrW1ib2xvIGVzICU+JSBlc3TDoSBlbiBmYW1pbGlhIGNvbiBvdHJvcyBvcGVyYWRvcmVzIG3DoXMgY29udmVuY2lvbmFsZXMsIGNvbW8gKywgLSBvIC8uIFkgYWwgaWd1YWwgcXVlIGxvcyBvdHJvcyBvcGVyYWRvcmVzLCBlbnRyZWdhIHVuIHJlc3VsdGFkbyBlbiBiYXNlIGEgbG9zIG9wZXJhbmRvcyBxdWUgcmVjaWJlLiBBaG9yYSBiaWVu4oCmIMK/UGFyYSBxdcOpIHNpcnZlPyBFbiByZXN1bWlkYXMgY3VlbnRhcywgaGFjZSBxdWUgZWwgY8OzZGlnbyBuZWNlc2FyaW8gcGFyYSByZWFsaXphciB1bmEgc2VyaWUgZGUgb3BlcmFjaW9uZXMgZGUgdHJhbnNmb3JtYWNpw7NuIGRlIGRhdG9zIHNlYSBtdWNobyBtw6FzIHNpbXBsZSBkZSBlc2NyaWJpciB5IGRlIGludGVycHJldGFyLg0KDQpSZXBhc2Vtb3MgbGEgc2VjdWVuY2lhIGFudGVyaW9yLi4uDQoNCjEuIFNlbGVjY2lvbmFtb3MgbGFzIGNvbHVtbmFzIGEgdXNhcg0KMi4gRmlsdHJhbW9zIHJlZ2lzdHJvcw0KMy4gR2VuZXJhbW9zIHVuIHJlc3VtZW4NCjQuIE9yZGVuYW1vcyBlbiBmb3JtYSBkZXNjZW5kZW50ZQ0KDQpQZWdhbmRvIHRvZG8gZWwgY8OzZGlnbyBqdW50by4uLiANCg0KYGBge3J9DQphdF9iYXJyaW8gPC0gc2VsZWN0KGF0X2NpdWRhZGFubywgRE9NSUNJTElPX0JBUlJJTywgUlVCUk8pDQoNCmF0X2JhcnJpbyA8LSBmaWx0ZXIoYXRfYmFycmlvLCBSVUJSTyA9PSAnREVOVU5DSUFTIFNPQlJFIElOQ09ORFVDVEFTIFJFRkVSSURBUyBBIExBIEFDVFVBQ0lPTiBQT0xJQ0lBTCcgfCBSVUJSTyA9PSAnRU1FUkdFTkNJQVMnIHwgIFJVQlJPID09ICdTRUdVUklEQUQnIHwgUlVCUk8gPT0gJ1NFR1VSSURBRCBFIEhJR0lFTkUnIHwgUlVCUk8gPT0gJ1ZFSElDVUxPUyBERSBGQU5UQVNJQScpDQoNCmF0X2JhcnJpb19hZ2cgPC0gZ3JvdXBfYnkoYXRfYmFycmlvLCBET01JQ0lMSU9fQkFSUklPKQ0KDQphdF9iYXJyaW9fYWdnIDwtIHN1bW1hcml6ZShhdF9iYXJyaW9fYWdnLCB0b3RhbD1uKCkpDQoNCmFycmFuZ2UoYXRfYmFycmlvX2FnZywgZGVzYyh0b3RhbCkpDQpgYGANCg0KDQpUb2RvIGJpZW4sIHBlcm8gZWwgcHJvYmxlbWEgZXMgcXVlIGhlbW9zIGdlbmVyYWRvIHVuYXMgY3VhbnRhcyB2YXJpYWJsZXMgKOKAnGF0X2JhcnJpb+KAnSwg4oCcYXRfYmFycmlvX2FnZ+KAnSkgcXVlIG5vIHZvbHZlcmVtb3MgYSB1c2FyLiBBZGVtw6FzIGRlIHNlciBpbsO6dGlsZXMgdW5hIHZleiBvYnRlbmlkbyBlbCByZXN1bHRhZG8gYnVzY2FkbywgZXN0YXMgdmFyaWFibGVzIGludGVybWVkaWFzIHJlcXVpZXJlbiBxdWUgbGFzIG5vbWJyZW1vcy4gRGVjaWRpciBlbCBub21icmUgZGUgZXN0YXMgdmFyaWFibGVzIHF1ZSBubyBub3MgaW1wb3J0YW4gdG9tYSB0aWVtcG8gKHNvYnJlIHRvZG8gY3VhbmRvIHByb2R1Y2ltb3MgbXVjaGFzKSwgeSBub3MgZGlzdHJhZSBkZSBsbyBpbXBvcnRhbnRlLCBxdWUgZXMgZWwgYW7DoWxpc2lzLg0KDQpFbCBwaXBlLCAlPiUsIHBlcm1pdGUgZW5jYWRlbmFyIG9wZXJhY2lvbmVzLCBjb25lY3RhbmRvIGVsIHJlc3VsdGFkbyBkZSB1bmEgY29tbyBlbCBkYXRvIGRlIGVudHJhZGEgZGUgbGEgc2lndWllbnRlLiBMYSBtaXNtYSBzZWN1ZW5jaWEgcXVlIHJlYWxpemFtb3MgYW50ZXMgcHVlZGUgcmVzb2x2ZXJzZSBjb24gcGlwZXMsIHF1ZWRhbmRvIGFzw606DQoNCg0KYGBge3J9DQphdF9iYXJyaW8gPC0gc2VsZWN0KGF0X2NpdWRhZGFubywgRE9NSUNJTElPX0JBUlJJTywgUlVCUk8pICU+JQ0KICAgICAgICBmaWx0ZXIoUlVCUk8gPT0gJ0RFTlVOQ0lBUyBTT0JSRSBJTkNPTkRVQ1RBUyBSRUZFUklEQVMgQSBMQSBBQ1RVQUNJT04gUE9MSUNJQUwnIHwgDQogICAgICAgICAgICAgICAgICAgICAgIFJVQlJPID09ICdFTUVSR0VOQ0lBUycgfCAgUlVCUk8gPT0gJ1NFR1VSSURBRCcgfCANCiAgICAgICAgICAgICAgICAgICAgICAgUlVCUk8gPT0gJ1NFR1VSSURBRCBFIEhJR0lFTkUnIHwgUlVCUk8gPT0gJ1ZFSElDVUxPUyBERSBGQU5UQVNJQScpICU+JQ0KICAgICAgICBncm91cF9ieShET01JQ0lMSU9fQkFSUklPKSAlPiUgDQogICAgICAgIHN1bW1hcml6ZSh0b3RhbD1uKCkpICU+JSANCiAgICAgICAgYXJyYW5nZShkZXNjKHRvdGFsKSkNCg0KaGVhZChhdF9iYXJyaW8pDQpgYGANCg0KDQoqKiogDQojIyMjIMK/UXXDqSBwYXPDsyBhY8OhPw0KDQoqIFZlcmJvcyBlbiBgdGlkeXZlcnNlYC4uLg0KKiBPcGVyYWRvciBgJT4lYCBlbiBgbWFncml0dGANCg0KKioqIA0KDQoNCioqKiANCiMjIyMgQ29uc2lnbmEgMy4gDQoNClJlcGV0aXIgZWwgcHJvY2VzbyBwYXJhIGdlbmVyYXIgdW5hIHRhYmxhIGRlIGNhbnRpZGFkIGRlIGRlbGl0b3MgcG9yIGJhcnJpbyB1c2FuZG8gZWwgb3BlcmFkb3IgcGlwZS4uLg0KDQoqKiogDQoNCmBgYHtyfQ0KZGVsaXRvc19iYXJyaW8gPC0gc2VsZWN0KGRlbGl0b3NfbGltcGlvcywgYmFycmlvLCB0aXBvX2RlbGl0bykgJT4lDQogICAgICAgIGdyb3VwX2J5KGJhcnJpbykgJT4lIA0KICAgICAgICBzdW1tYXJpemUodG90YWw9bigpKSAlPiUgDQogICAgICAgIGFycmFuZ2UoZGVzYyh0b3RhbCkpDQoNCmhlYWQoZGVsaXRvc19iYXJyaW8pDQpgYGANCg0KDQpFbCDDumx0aW1vIHBhc28gZXMgInVuaXIiIGxhcyBkb3MgdGFibGFzLiBQYXJhIGVzbyB2YW1vcyBhIHVzYXIgZWwgb3BlcmFkb3IgYGxlZnRfam9pbmAuDQoNCmBgYHtyfQ0KYmFycmlvcyA8LSBsZWZ0X2pvaW4oZGVsaXRvc19iYXJyaW8sIGF0X2JhcnJpbykNCmJhcnJpb3MNCmBgYA0KDQoNCmBsZWZ0X2pvaW5gIHVzYSBjb21vIGNsYXZlIGxhcyBjb2x1bW5hcyBxdWUgZW4gbGFzIGRvcyB0YWJsYXMgdGVuZ2FuIGVsIG1pc21vIG5vbWJyZS4gUGFyZWNlIHF1ZSB0ZW5lbW9zIHVuIHByb2JsZW1hOiBsYXMgdGFibGFzIHRpZW5lbiB1bmEgc29sYSBjb2x1bW5hIGNvbiB1biBub21icmUgZW4gY29tw7puICgidG90YWwiKSB5IGp1c3RhbWVudGUsIG5vIGVzIGVzYSBsYSBxdWUgcXVlcmVtb3MgcGFyYSBqb2luZWFyIGFtYmFzIHZhcmlhYmxlcy4gTGEgcXVlIHF1ZXJlbW9zIGVzIGxhIHF1ZSBjb250aWVuZSBlbCBiYXJyaW8sIGVsIHByb2JsZW1hIGVzIHF1ZSBzZSBsbGFtYW4gZGlzdGludG8uIFNvbHVjaW9uZW1vcyBlc3RlIHByb2JsZW1hOg0KIA0KDQpgYGB7cn0NCmF0X2JhcnJpbyA8LSBhdF9iYXJyaW8gJT4lDQogICAgICAgICAgICAgICAgcmVuYW1lKGJhcnJpbz1ET01JQ0lMSU9fQkFSUklPKQ0KDQpiYXJyaW9zIDwtIGxlZnRfam9pbihkZWxpdG9zX2JhcnJpbywgYXRfYmFycmlvLCBieT0nYmFycmlvJykNCmBgYA0KDQoNCkZ1bmNpb25hIG1lam9yLCBwZXJvIHZlbW9zIHRvZGF2w61hIHF1ZSBxdWVkYSB1bm9zIGN1w6FudG9zIGJhcnJpb3MgcXVlIG5vIHNlIGhhbiBqb2luZWFkby4NCg0KYGBge3J9DQpmaWx0ZXIoYmFycmlvcywgaXMubmEodG90YWwueSkpDQpgYGANCg0KU2VndXJhbWVudGUsIHNlIGRlYmEgYSBxdWUgc2UgZW5jdWVudHJhbiBlc2NyaXRhcyBkZSBmb3JtYSBkaWZlcmVudGUgZW4gYW1iYXMgdGFibGFzLiBBc8OtIHF1ZSB2YW1vcyBhIHVuaWZpY2FyIHN1IGVzY3JpdHVyYS4uLiBTb2xvIHBvciBlc3RhIHZleiwgaGFnw6Ftb3NsbyB1c2FuZG8gc29sYW1lbnRlIGVsIFItYmFzZSB5IG5vIGVsIHRpZHl2ZXJzZS4uLg0KDQpgYGB7cn0NCmF0X2JhcnJpbyRiYXJyaW8gPC0gYXMuY2hhcmFjdGVyKGF0X2JhcnJpbyRiYXJyaW8pDQphdF9iYXJyaW8kYmFycmlvW2F0X2JhcnJpbyRiYXJyaW89PSdNT05TRVJSQVQnXSA8LSAnTU9OVFNFUlJBVCcNCmF0X2JhcnJpbyRiYXJyaW9bYXRfYmFycmlvJGJhcnJpbz09J0JPQ0EnXSA8LSAnTEEgQk9DQScNCmF0X2JhcnJpbyRiYXJyaW9bYXRfYmFycmlvJGJhcnJpbz09J1ZJTExBIEdSQUwuIE1JVFJFJ10gPC0gJ1ZJTExBIEdSQUwgTUlUUkUnDQphdF9iYXJyaW8kYmFycmlvW2F0X2JhcnJpbyRiYXJyaW89PSdDT0dITEFOJ10gPC0gJ0NPR0hMQU5EJw0KYXRfYmFycmlvJGJhcnJpbyA8LSBhcy5mYWN0b3IoYXRfYmFycmlvJGJhcnJpbykNCmBgYA0KDQpIYWNlbW9zIGVsIGBsZWZ0X2pvaW4oKWAuIEFwcm92ZWNoYW1vcyBwYXJhIGRhcmxlcyB1biBub21icmUgY29oZXJlbnRlIGEgbGFzIHZhcmlhYmxlcy4gWSB2ZW1vcyBxdWUgcXVlZGFuIGHDum4gYWxndW5vcyBkYXRvcyBjb24gTkE6IHNlIHRyYXRhIGRlIGJhcnJpb3MgcXVlIG5vIHRpZW5lbiBkYXRvcyBlbiBsYSB0YWJsYSBkZSBhdF9iYXJyaW8uIA0KDQpgYGB7cn0NCmJhcnJpb3MgPC0gbGVmdF9qb2luKGRlbGl0b3NfYmFycmlvLCBhdF9iYXJyaW8sIGJ5PSdiYXJyaW8nKSAlPiUNCiAgICAgICAgICAgICAgICAgICAgICAgIHJlbmFtZShuX2RlbGl0b3MgPSB0b3RhbC54LCBuX3JlY2xhbW9zPXRvdGFsLnkpDQpgYGANCg0KUG9kcsOtYW1vcyBmaWx0cmFybG9zIGbDoWNpbG1lbnRlOg0KDQpgYGB7cn0NCmJhcnJpb3MgJT4lDQogICAgICAgIGZpbHRlcighaXMubmEobl9yZWNsYW1vcykpDQpgYGANCg0KDQpQZXJvIG1lam9yIHZhbW9zIGEgYXNpZ2FybmxlcyB1biB2YWxvciBkZSBpZ3VhbCBhIDA6DQoNCmBgYHtyfQ0KYmFycmlvcyA8LSBiYXJyaW9zICU+JSANCiAgICAgICAgbXV0YXRlKG5fcmVjbGFtb3M9cmVwbGFjZV9uYShuX3JlY2xhbW9zLCAwKSkNCmBgYA0KDQoNCioqKiANCiMjIyMgQ29uc2lnbmEgNC4gDQoxLiBSZWFsaXphciB1biBzY2F0dGVycGxvdCBkZSBsYSBjYW50aWRhZCBkZSBkZWxpdG9zIGNvbnRyYSBsYSBjYW50aWRhZCBkZSByZWNsYW1vcyBwb3Igc2VndXJpZGFkLg0KDQoqKiogDQoNCg0KYGBge3J9DQogZ2dwbG90KGJhcnJpb3MsIGFlcyh4PW5fZGVsaXRvcywgeT1uX3JlY2xhbW9zKSwgY29sb3I9J3JlZCcpICsgDQogICAgICAgIGdlb21fcG9pbnQoKSArIA0KICAgICAgICBnZW9tX3Ntb290aChtZXRob2QgPSAnbG0nKSArDQogICAgICAgICBsYWJzKHRpdGxlID0gIkRlbGl0b3MgcmVnaXN0cmFkb3Mgc2Vnw7puIGNvbnRhY3RvcyBhbCBTSVVBQyB2aW5jdWxhZG9zIGEgc2VndXJpZGFkIiwNCiAgICAgICAgIHN1YnRpdGxlID0gIkJhcnJpb3MgZGUgbGEgQ0FCQSwgMjAxNiAtIDIwMTciLA0KICAgICAgICAgY2FwdGlvbiA9ICJGdWVudGU6IHBvcnRhbCBkZSBkYXRvcyBhYmllcnRvcyBkZSBsYSBDaXVkYWQgLSBodHRwOi8vZGF0YS5idWVub3NhaXJlcy5nb2IuYXIiLA0KICAgICAgICAgeCA9ICJDYW50aWRhZCBkZSBkZWxpdG9zIiwNCiAgICAgICAgIHkgPSAiQ2FudGlkYWQgZGUgY29udGFjdG9zIikNCmBgYA0KDQoNCiMjIFBhcmEgaXIgY2VycmFuZG8uLi4NCg0KDQpWYW1vcyBhIHZlciBkb3Mgw7psdGltYXMgY3Vlc3Rpb25lcyB2aW5jdWxhZGFzIGEgbGEgY29uc3RydWNjacOzbiBkZSBncsOhZmljb3MgZW4gYGdncGxvdDJgLg0KDQoqR2VuZXJhY2nDs24gZGUgdMOtdHVsb3MgZW4gZWwgZ3LDoWZpY286KiBzaWVtcHJlIGVzIGltcG9ydGF0ZSBwb2RlciB0cmFuc21pdGlyIGRlIGZvcm1hIGNsYXJhIHF1w6kgaW5mb3JtYWNpw7NuIHNlIGVuY3VlbnRyYSBlbiBlbCBtaXNtby4gRW4gZ2VuZXJhbCwgaGF5IHZhcmlhcyByZWdsYXMgeSBmb3JtYXMgZGUgaGFjZXJsbyBwZXJvIGFsIG1lbm9zIGN1YXRybyBlbGVtZW5vcyB0aWVuZW4gcXVlIGV4aXN0aXI6DQoNCiogVMOtdHVsbyBlbiBlbCBxdWUgZGViZW4gZXN0YXIgZXhwcmVzYWRhcyBkZSBmb3JtYSBjbGFyYSBsYXMgdmFyaWFibGVzIHF1ZSBzZSBncmFmaWNhbiwgZWwgcGVyw61vZG8gdGVtcG9yYWwgeSBlbCDDoXJlYSBvIHJlZ2nDs24gYWwgcXVlIGNvcnJlc3BvbmRlIGVsIGdyw6FmaWNvLg0KKiBFdGlxdWV0YXMgZW4gbG9zIGVqZXMNCiogTGV5ZW5kYXMNCiogRnVlbnRlIGRlIGxvcyBkYXRvcw0KICAgICAgICANClRvZG8gcHVlZGUgc2VyIG1hbmplYW5kbyBjb24gbGEgY2FwYSBgbGFic2ANCg0KKkN1cnZhIGRlIGFqdXN0ZToqIHBvZGVtb3MgYWdyZWdhciB1bmEgYSBsb3MgZGF0b3MgZGVsIHNjYXR0ZXIgbWVkaWFudGUgYGdlb21fc21vb3RoYC4gQXF1w60gaGF5IHZhcmlvcyBwYXLDoW1ldHJvcy4gVmVyZW1vcyBkb3MgcXVlIHNvbiBsb3MgbcOhcyBpbXBvcnRhbnRlcy4NCg0KKiBgbWV0aG9kYDogcHVlZGUgYXN1bWlyIHZhcmlvcyB2YWxvcmVzLCBjb3JyZXNwb25kaWVudGVzIGEgZGlmZXJlbnRlcyBtw6l0b2RvcyBwYXJhIGdlbmVyYXIgbGEgcmVjdGEgZGUgYWp1c3RlLiBFbCBxdWUgdXNhbW9zIHByZXZpYW1lbnRlIGVzIGBtZXRob2Q9J2xtJ2AgcXVlIGFqdXN0YSB1bmEgcmVjdGEgbWVkaWFudGUgdW4gbW9kZWxvIGxpbmVhbC4gRXhpc3RlbiBvdHJvcywgY29tbyBgbWV0aG9kPWxvZXNzYC4gDQoqIEVuIGVsIGNhc28gZGUgbcOpdG9kb3MgY29tbyBsb2VzcyBvIGxvd2VzcyBxdWUgdXNhbiB1bmEgdmVudGFuc2Rgc2RgYSBkZSB2YWxvcmVzIGxvY2FsZXMsIGVsIHBhcsOhbWV0cm8gYHNwYW5gIGNvbnRyb2xhIGVsICJhbmNobyIgZGUgbGEgdmVudGFuYQ0KKiBgc2VgOiBgVFJVRWAsIGNhbGN1bGEgeSBkZXNwbGllZ2EgZWwgZXJyb3IgZXN0w6FuZGFyIHBhcmEgY2FkYSBwdW50byBkZSBsYSBjdXJ2YSBhanVzdGFkYS4NCg0KDQpgYGB7cn0NCiBnZ3Bsb3QoYmFycmlvcywgYWVzKHg9bl9kZWxpdG9zLCB5PW5fcmVjbGFtb3MpLCBjb2xvcj0ncmVkJykgKyANCiAgICAgICAgZ2VvbV9wb2ludCgpICsgDQogICAgICAgIGdlb21fc21vb3RoKG1ldGhvZCA9ICdsb2VzcycsIHNwYW49MC44LCBzZT1GQUxTRSkgKw0KICAgICAgICAgbGFicyh0aXRsZSA9ICJEZWxpdG9zIHJlZ2lzdHJhZG9zIHNlZ8O6biBjb250YWN0b3MgYWwgU0lVQUMgdmluY3VsYWRvcyBhIHNlZ3VyaWRhZCIsDQogICAgICAgICBzdWJ0aXRsZSA9ICJCYXJyaW9zIGRlIGxhIENBQkEsIDIwMTYgLSAyMDE3IiwNCiAgICAgICAgIGNhcHRpb24gPSAiRnVlbnRlOiBwb3J0YWwgZGUgZGF0b3MgYWJpZXJ0b3MgZGUgbGEgQ2l1ZGFkIC0gaHR0cDovL2RhdGEuYnVlbm9zYWlyZXMuZ29iLmFyIiwNCiAgICAgICAgIHggPSAiQ2FudGlkYWQgZGUgZGVsaXRvcyIsDQogICAgICAgICB5ID0gIkNhbnRpZGFkIGRlIGNvbnRhY3RvcyIpDQpgYGANCg0KDQpgYGB7cn0NCiBnZ3Bsb3QoYmFycmlvcywgYWVzKHg9bl9kZWxpdG9zLCB5PW5fcmVjbGFtb3MpLCBjb2xvcj0ncmVkJykgKyANCiAgICAgICAgZ2VvbV9wb2ludCgpICsgDQogICAgICAgIGdlb21fc21vb3RoKG1ldGhvZCA9ICdsb2VzcycsIHNwYW49MC4zKSArDQogICAgICAgICBsYWJzKHRpdGxlID0gIkRlbGl0b3MgcmVnaXN0cmFkb3Mgc2Vnw7puIGNvbnRhY3RvcyBhbCBTSVVBQyB2aW5jdWxhZG9zIGEgc2VndXJpZGFkIiwNCiAgICAgICAgIHN1YnRpdGxlID0gIkJhcnJpb3MgZGUgbGEgQ0FCQSwgMjAxNiAtIDIwMTciLA0KICAgICAgICAgY2FwdGlvbiA9ICJGdWVudGU6IHBvcnRhbCBkZSBkYXRvcyBhYmllcnRvcyBkZSBsYSBDaXVkYWQgLSBodHRwOi8vZGF0YS5idWVub3NhaXJlcy5nb2IuYXIiLA0KICAgICAgICAgeCA9ICJDYW50aWRhZCBkZSBkZWxpdG9zIiwNCiAgICAgICAgIHkgPSAiQ2FudGlkYWQgZGUgY29udGFjdG9zIikNCmBgYA==