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:
ggplot(delitos) +
\(=>\) Creamos el objeto ggplot
y lo “llenamos” con un dataset. En este caso, una tibble
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 ggplot
en 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:
- cargamos librerías con
library()
- cargamos datos con
read_csv()
- filtramos datos con
filter()
- hicimos algunas visuzalizaciones básicas con
ggplot2
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?
- Es un dataframe con 237.445 observaciones y 14 variables
- Tenemos nombre, tipo y algunos valores de cada una de las variables
- Hay varias columnas que son
factors
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”
- ¿Cuáles de las variabes en este dataset son factors? Veamos los levels de una de ellas…
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ó:
- tomamos el dataset original
- agrupamos por el campo “fecha” (es decir, por día)
- 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:
- hubo días con menos de 100 delitos y días con 500
- no obstante, la mayor parte de los días parecen conentrarse entre 300 y 400 delitos
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
.
- 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)
- 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')
- 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())
- 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…
- Seleccionamos las columnas a usar
- Filtramos registros
- Generamos un resumen
- 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.
- 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:
- Título en el que deben estar expresadas de forma clara las variables que se grafican, el período temporal y el área o región al que corresponde el gráfico.
- Etiquetas en los ejes
- Leyendas
- Fuente de los datos
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.
method
: puede asumir varios valores, correspondientes a diferentes métodos para generar la recta de ajuste. El que usamos previamente es method='lm'
que ajusta una recta mediante un modelo lineal. Existen otros, como method=loess
.
- En el caso de métodos como loess o lowess que usan una ventansd
sd
a de valores locales, el parámetro span
controla el “ancho” de la ventana
se
: TRUE
, calcula y despliega el error estándar para cada punto de la curva ajustada.
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==