Librerias

library(skimr)
library(data.table)
library(tidyverse)
library(purrr)
library(dplyr)
library(ggplot2)
library(GGally)
library(corrr)
library(knitr)
library(kableExtra)

Funciones

#Imprime en consola la cantidad de filas y columnas de un data frame.
printShape<-function(df) {
  print(paste('#Filas: ',dim(df)[1]))
  print(paste('#Columnas: ',dim(df)[2]))
}

#Devuelve un data frame con las estadisticas descriptivas de un vector númerico.
getEstadisticas <- function(vec){
  promedio<-mean(vec)
  maximo<-max(vec)
  minimo<-min(vec)
  cuantiles<-t(quantile(vec))
  data.frame(promedio,maximo,minimo,cuantiles)
}

Variables Globales

kable_options<-c("striped", "hover", "condensed", "responsive")

Preparación de Datos - I

Leer el archivo ar_properties.csv y mostrar su estructura. Quedarse con aquellos registros que:

  • Pertenecen a Argentina y Capital Federal
  • Cuyo precio esta en dolares (USD)
  • El tipo de propiedad sea: Departamento, PH o Casa
  • El tipo de operacion sea Venta

Luego seleccionar las variables id, l3, rooms, bedrooms, bathrooms, surface_total, surface_covered, price y property_type.

El dataset tiene 15 variables de tipo string, 2 variables numéricas, 6 de tipo enteras y una booleana. En realidad la variable l6 la toma como booleana porque no se tiene ningún dato para ningún registro.
Se observa que las features l4, l5 y bedrooms son las que más cantidad de missing tienen. Los nombres de las variables no son muy descriptivas pero de acuerdo a su contenido se puede ver que la variable l1 indica el pais de la propiedad, l2 el estado o provincia, l3 la localidad o municipio, l4 el nombre del barrio o localidad y l5 indica el nombre del barrio.
Se puede destacar que la variable price tiene gran variabilidad en sus valores y eso debe a que depende de los distintos tipos de operacion(venta, Alquiler, Alquiler Temporal), a las diferentes monedas en que se registran los precios y los distintos periodos de precio(Diario, Mensual, Semanal) que se tienen.
propiedades<-fread("ar_properties.csv", header = TRUE, sep = ',')

#Analizar estructura
printShape(propiedades)
[1] "#Filas:  388891"
[1] "#Columnas:  24"
skim_with(numeric = list(median = median, hist = NULL),
          integer = list(median = median, n_unique = n_unique, hist = NULL)
          )

desc <- as.data.table(skim_to_wide(propiedades))
unique(desc$type)
[1] "character" "integer"   "logical"   "numeric"  
desc[type == "character", c("variable", show_skimmers("character")[[1]])] %>%
  kable() %>% 
  kable_styling(bootstrap_options = kable_options)

variable missing complete n min max empty n_unique
ad_type 0 388891 388891 9 9 0 1
created_on 0 388891 388891 10 10 0 180
currency 28994 359897 388891 3 3 0 4
end_date 0 388891 388891 10 10 0 236
id 0 388891 388891 24 24 0 388891
l1 0 388891 388891 6 14 0 4
l2 0 388891 388891 5 29 0 41
l3 12771 376120 388891 4 35 0 989
l4 273380 115511 388891 3 41 0 842
l5 386485 2406 388891 5 28 0 21
operation_type 0 388891 388891 5 17 0 3
price_period 161189 227702 388891 6 7 0 3
property_type 0 388891 388891 2 15 0 10
start_date 0 388891 388891 10 10 0 180
title 0 388891 388891 1 266 0 225414

desc[type == "numeric", c("variable", show_skimmers("numeric")[[1]])] %>%
  kable() %>% 
  kable_styling(bootstrap_options = kable_options)

variable missing complete n mean sd p0 p25 p50 p75 p100 median
lat 50597 338294 388891 -34.48 2.88 -54.98 -34.67 -34.6 -34.43 44.67 NA
lon 50597 338294 388891 -59.37 2.72 -105.27 -58.8 -58.48 -58.4 -41.9 NA

desc[type == "integer", c("variable", show_skimmers("integer")[[1]])] %>%
  kable() %>% 
  kable_styling(bootstrap_options = kable_options)

variable missing complete n mean sd p0 p25 p50 p75 p100 median n_unique
bathrooms 94136 294755 388891 1.67 1.08 1 1 1 2 20 NA 20
bedrooms 230747 158144 388891 2.16 2.73 -2 1 2 3 390 NA 56
price 21222 367669 388891 268955.05 4757708.72 0 20000 94000 225000 2.1e+09 NA 10229
rooms 144668 244223 388891 2.9 1.67 1 2 3 4 40 NA 31
surface_covered 97854 291037 388891 235.01 12501.36 -139 43 70 142 4e+06 NA 1978
surface_total 74063 314828 388891 458.28 3932.54 -3 50 91 266 2e+05 NA 4010

desc[type == "logical", c("variable", show_skimmers("logical")[[1]])] %>%
  kable() %>% 
  kable_styling(bootstrap_options = kable_options)

variable missing complete n mean count
l6 388891 0 388891 NaN 388891


#Filtrar datos de acuerdo a consigna
prop_selected<-propiedades %>% filter( l1=='Argentina' & l2=='Capital Federal')
prop_selected<-prop_selected %>% filter( currency=='USD')
prop_selected<-prop_selected %>% filter( property_type %in%c('Casa','Departamento','PH') )
prop_selected<-prop_selected %>% filter( operation_type =='Venta')
prop_selected<-prop_selected[, c('id', 'l3', 'rooms', 'bedrooms', 'bathrooms', 'surface_total', 'surface_covered', 'price', 'property_type')]
printShape(prop_selected)
[1] "#Filas:  61905"
[1] "#Columnas:  9"

Análisis Exploratorios - I

Se utilizan las funciones de la libreria purr para obtener #de NA y #de valores únicos de cada variable y la libreria corrr para graficar matriz de correlación.
Se observa una fuerte correlacion entre rooms y bedrooms siendo esta última la feature con más missing. Tambien se ve una relacion fuerte entre surface_total y surface_covered dado que una esta inlcuida en la otra.
#Contar NA y Unique
na<-prop_selected %>%
  map_dbl(function(x) sum(is.na(x)))
uni<-prop_selected %>%
  map_dbl(function(x) length(unique(x)))

resultado<-rbind(na, uni)

rownames(resultado)<-c('#NA','#Unique')

resultado %>% 
  kable() %>% 
  kable_styling(bootstrap_options = kable_options)

id l3 rooms bedrooms bathrooms surface_total surface_covered price property_type
#NA 0 355 5314 25298 3196 3671 2975 0 0
#Unique 61905 58 24 25 15 671 573 4095 3


#Matriz de Correlacion
prop_selected[, 3:8] %>% 
 correlate(use = 'pairwise.complete.obs', method = 'pearson') %>% 
   shave() %>% 
   fashion()

Correlation method: 'pearson'
Missing treated using: 'pairwise.complete.obs'

Preparación de Datos - II

Eliminar variable bedrooms y las filas con NAs.

prop_selected$bedrooms<-NULL
prop_selected<-na.omit(prop_selected)
printShape(prop_selected)
[1] "#Filas:  51210"
[1] "#Columnas:  8"

Análisis Exploratorios - II

Obterner estadísticas descriptivas, histograma, boxplot y correlograma.
La distribución de la variale precio con respecto al tipo de propiedad es asimétrica positiva. Los precios de los Departametnos y Casa presentan valores atípicos muy altos. La varianza de las propiedades de tipo Casa pareciera ser sutilmente más grande que la de los otros dos tipos.
En el correlograma se observa una correlación entre:

  • surface_total y surface_covered
  • rooms y bathrooms
  • price y bathrooms
  • price y rooms

getEstadisticas(prop_selected$price)

prop_selected$property_type<- as.factor(prop_selected$property_type)

pricextype<-prop_selected %>%
  group_split(property_type) %>% 
  map_dfr(function(x) getEstadisticas(x$price))
rownames(pricextype)<-unique(prop_selected$property_type)

pricextype %>%
  kable() %>% 
  kable_styling(bootstrap_options = kable_options)

promedio maximo minimo X0. X25. X50. X75. X100.
Casa 434188.8 5000000 20000 20000 235000 335000 490000 5000000
Departamento 246855.7 6000000 6000 6000 115000 164000 260000 6000000
PH 218747.4 1500000 32000 32000 137000 190000 270000 1500000


ggplot(data = prop_selected, aes(prop_selected$price, color = property_type))+
  geom_histogram() +
  labs(title = "Histograma de Precios por tipo de Propiedad", x="Precio", y="Frecuencia")+
  theme(legend.position = 'none')+
  facet_wrap(~property_type)


ggplot(prop_selected, aes(x = property_type, y = price, group = property_type, fill = property_type )) +
  geom_boxplot()+
  labs(title = "Boxplot Precios vs Tipo de Propiedad", x="Tipo de Propiedad", y="Precio")


ggcorr(prop_selected, layout.exp = 2) + labs(title='Correlograma variables cuantitativas')
data in column(s) 'id', 'l3', 'property_type' are not numeric and were ignored

Outliers

Eliminar outliers de la variable precio. Se crea una nueva variable para obtener el precio por metro cuadrado y se analizan las estadisticas descriptivas de ésta para establecer los valores de corte. Además se utiliza las tasaciones online de los barrios de la capital para fijar una cota inferior de 700 y una cota superior de 10000. Se encontraron 320 outliers.
Se observan registros que tienen mal cargados los metros cuadrados de la superficie, otros que tienen el precio de referente a un alquiler en lugar de una venta.


sin_outliers<-prop_selected %>%
  mutate(pricexsurface=price/surface_total)

getEstadisticas(sin_outliers$pricexsurface)

sin_outliers$in_=sin_outliers$pricexsurface>700 & sin_outliers$pricexsurface<10000

print(paste('Cantidad de Outliers encontrados: ',sum(sin_outliers$in_==FALSE))) 
[1] "Cantidad de Outliers encontrados:  320"
sin_outliers<-sin_outliers %>% filter(in_==TRUE)
sin_outliers[,9]<-NULL
sin_outliers<-as.data.frame(sin_outliers)
printShape(sin_outliers)
[1] "#Filas:  50890"
[1] "#Columnas:  9"

Análisis Exploratorios - III

Repertir análisis sin outliers.
Se siguen observando outliers, los rangos intercuartiles permanecieron sutilmente similares y los graficos del histograma y boxplot tambien se asemejan al análisis anterior. Se presencia un porcentaje menor de valores atípicos y además se ajustó el valor mínimo. El cambio que más se destacó es en el gráfico del correlograma donde pareciera que las variables cuantitativas estan más relaciones entre todas.


getEstadisticas(sin_outliers$price)

pricextype<-sin_outliers %>%
  group_split(property_type) %>% 
  map_dfr(function(x) getEstadisticas(x$price))
rownames(pricextype)<-c('Casa','Depto','PH')

pricextype %>%
  kable() %>% 
  kable_styling(bootstrap_options = kable_options)

promedio maximo minimo X0. X25. X50. X75. X100.
Casa 441583.5 5000000 62000 62000 244900 340000 499000 5000000
Depto 242785.4 5800000 12000 12000 115000 163590 260000 5800000
PH 219273.0 1500000 45000 45000 138000 194500 274000 1500000


ggplot(data = sin_outliers, aes(sin_outliers$price, color = property_type))+
  geom_histogram() +
  labs(title = "Histograma de Precios por tipo de Propiedad", x="Precio", y="Frecuencia")+
  theme(legend.position = 'none')+
  facet_wrap(~property_type)


ggplot(sin_outliers, aes(x = property_type, y = price, group = property_type, fill = property_type )) +
  geom_boxplot()+
  labs(title = "Boxplot Precios vs Tipo de Propiedad", x="Tipo de Propiedad", y="Precio")


ggcorr(sin_outliers, layout.exp = 2) + labs(title='Correlograma variables cuantitativas')
data in column(s) 'id', 'l3', 'property_type', 'in_' are not numeric and were ignored

Modelo Lineal

Se analizan las salidas de ambos modelos luego de aplicar el modelo lineal simple de R.
El p-valor en ambos modelos para ambos coeficientes estimados es pequeño lo que indica que se puede considerar un modelo lineal y que el precio se relaciona con cada una de las variables de cada modelo. Se observa que el desvio estandar de error de los coefcientes son más bajos en el segundo modelo(surface_total) que en el primero.
El 28% de la variabilidad observada en los precios queda explicada por la relacion lineal entre el precio y rooms.
El 53% de la variabilidad observada en los precios queda explicada por la relacion lineal entre el precio y surface_total.

El valor intercept(\(\beta_0\)) -43616,80 en el primero modelo es el valor promedio de una propiedad sin ninguna habitación. Este valor no tiene sentido ya que no se encuentra dentro de los rangos validos de nuestro modelo. El \(\beta_1\) indica en promedio que una propiedad aumenta USD 104425,2 por cada habitación que se agrega.
En el caso del segundo modelo, el valor intercept(\(\beta_0\)) 18810,54 es el valor promedio de una propiedad con superficie 0. Este valor tampoco tiene sentido. El \(\beta_1\) nos indica que una propiedad aumenta en promedio USD 2537,67 por cada metro cuadrado.
En base a lo expuesto anteriormente el segundo modelo seria el mejor de ambos.

# Se quiere explicar el precio de las propiedades en base a la cant de habitaciones de las mismas.
# Y: los precios de las propiedades
# X: la cantidad de habitaciones de la propiedad
modelo_rooms=lm(price~rooms, data=sin_outliers)

summary(modelo_rooms)

Call:
lm(formula = price ~ rooms, data = sin_outliers)

Residuals:
     Min       1Q   Median       3Q      Max 
-2697989   -94659   -30234    39341  5008215 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) -43616.8     2272.8  -19.19   <2e-16 ***
rooms       104425.2      729.3  143.18   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 227300 on 50888 degrees of freedom
Multiple R-squared:  0.2872,    Adjusted R-squared:  0.2872 
F-statistic: 2.05e+04 on 1 and 50888 DF,  p-value: < 2.2e-16
ggplot(sin_outliers, aes(rooms, price)) +
  geom_abline(aes(intercept = modelo_rooms$coefficients[1], slope = modelo_rooms$coefficients[2]), data = modelo_rooms, colour = "blue") +
  labs(title = "Precio versus Rooms con Recta ajustada por mínimos cuadrados.")+
  geom_point()


# Y: los precios de las propiedades
# X: la superficie total de la propiedad
modelo_surface=lm(price~surface_total, data=sin_outliers)

summary(modelo_surface)

Call:
lm(formula = price ~ surface_total, data = sin_outliers)

Residuals:
     Min       1Q   Median       3Q      Max 
-2698908   -43382   -12578    21844  4063187 

Coefficients:
              Estimate Std. Error t value Pr(>|t|)    
(Intercept)   18810.54    1256.67   14.97   <2e-16 ***
surface_total  2537.67      10.57  240.12   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 184300 on 50888 degrees of freedom
Multiple R-squared:  0.5312,    Adjusted R-squared:  0.5312 
F-statistic: 5.766e+04 on 1 and 50888 DF,  p-value: < 2.2e-16
ggplot(sin_outliers, aes(surface_total, price)) +
  geom_abline(aes(intercept = modelo_surface$coefficients[1], slope = modelo_surface$coefficients[2]), data = modelo_surface, colour = "blue") +
  labs(title = "Precio versus Surface_total con Recta ajustada por mínimos cuadrados.")+
  geom_point()

LS0tDQp0aXRsZTogIlRQMDEgLSBFc3RlZmFuaWEgRGUgTWFyemlvIg0KYXV0aG9yOiAiQ29taXNpb24gRGllZ28gS296bG93c2tpIg0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazogDQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgZGVwdGg6IDINCi0tLQ0KDQojIExpYnJlcmlhcw0KYGBge3IgbGlicmVhcmlhcywgbWVzc2FnZSA9IEZBTFNFfQ0KbGlicmFyeShza2ltcikNCmxpYnJhcnkoZGF0YS50YWJsZSkNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShwdXJycikNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KEdHYWxseSkNCmxpYnJhcnkoY29ycnIpDQpsaWJyYXJ5KGtuaXRyKQ0KbGlicmFyeShrYWJsZUV4dHJhKQ0KYGBgDQojIEZ1bmNpb25lcw0KYGBge3IgZnVuY2lvbmVzfQ0KI0ltcHJpbWUgZW4gY29uc29sYSBsYSBjYW50aWRhZCBkZSBmaWxhcyB5IGNvbHVtbmFzIGRlIHVuIGRhdGEgZnJhbWUuDQpwcmludFNoYXBlPC1mdW5jdGlvbihkZikgew0KICBwcmludChwYXN0ZSgnI0ZpbGFzOiAnLGRpbShkZilbMV0pKQ0KICBwcmludChwYXN0ZSgnI0NvbHVtbmFzOiAnLGRpbShkZilbMl0pKQ0KfQ0KDQojRGV2dWVsdmUgdW4gZGF0YSBmcmFtZSBjb24gbGFzIGVzdGFkaXN0aWNhcyBkZXNjcmlwdGl2YXMgZGUgdW4gdmVjdG9yIG7Dum1lcmljby4NCmdldEVzdGFkaXN0aWNhcyA8LSBmdW5jdGlvbih2ZWMpew0KICBwcm9tZWRpbzwtbWVhbih2ZWMpDQogIG1heGltbzwtbWF4KHZlYykNCiAgbWluaW1vPC1taW4odmVjKQ0KICBjdWFudGlsZXM8LXQocXVhbnRpbGUodmVjKSkNCiAgZGF0YS5mcmFtZShwcm9tZWRpbyxtYXhpbW8sbWluaW1vLGN1YW50aWxlcykNCn0NCmBgYA0KIyBWYXJpYWJsZXMgR2xvYmFsZXMNCmBgYHtyIHZhcmlhYmxlc0dsb2JhbGVzfQ0Ka2FibGVfb3B0aW9uczwtYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiLCAicmVzcG9uc2l2ZSIpDQpgYGANCg0KDQojIFByZXBhcmFjacOzbiBkZSBEYXRvcyAtIEkNCjxkaXYgc3R5bGU9InRleHQtYWxpZ246IGp1c3RpZnkiPkxlZXIgZWwgYXJjaGl2byBhcl9wcm9wZXJ0aWVzLmNzdiB5IG1vc3RyYXIgc3UgZXN0cnVjdHVyYS4gUXVlZGFyc2UgY29uIGFxdWVsbG9zIHJlZ2lzdHJvcyBxdWU6DQoNCiogUGVydGVuZWNlbiBhIEFyZ2VudGluYSB5IENhcGl0YWwgRmVkZXJhbA0KKiBDdXlvIHByZWNpbyBlc3RhIGVuIGRvbGFyZXMgKFVTRCkNCiogRWwgdGlwbyBkZSBwcm9waWVkYWQgc2VhOiBEZXBhcnRhbWVudG8sIFBIIG8gQ2FzYQ0KKiBFbCB0aXBvIGRlIG9wZXJhY2lvbiBzZWEgVmVudGEgDQoNCkx1ZWdvIHNlbGVjY2lvbmFyIGxhcyB2YXJpYWJsZXMgaWQsIGwzLCByb29tcywgYmVkcm9vbXMsIGJhdGhyb29tcywgc3VyZmFjZV90b3RhbCwgc3VyZmFjZV9jb3ZlcmVkLCBwcmljZSB5IHByb3BlcnR5X3R5cGUuDQoNCkVsIGRhdGFzZXQgdGllbmUgMTUgdmFyaWFibGVzIGRlIHRpcG8gc3RyaW5nLCAyIHZhcmlhYmxlcyBudW3DqXJpY2FzLCA2IGRlIHRpcG8gZW50ZXJhcyB5IHVuYSBib29sZWFuYS4gRW4gcmVhbGlkYWQgbGEgdmFyaWFibGUgKmw2KiBsYSB0b21hIGNvbW8gYm9vbGVhbmEgcG9ycXVlIG5vIHNlIHRpZW5lIG5pbmfDum4gZGF0byBwYXJhIG5pbmfDum4gcmVnaXN0cm8uIDxicj4NClNlIG9ic2VydmEgcXVlIGxhcyBmZWF0dXJlcyAqbDQqLCAqbDUqIHkgKmJlZHJvb21zKiBzb24gbGFzIHF1ZSBtw6FzIGNhbnRpZGFkIGRlIG1pc3NpbmcgdGllbmVuLiBMb3Mgbm9tYnJlcyBkZSBsYXMgdmFyaWFibGVzIG5vIHNvbiBtdXkgZGVzY3JpcHRpdmFzIHBlcm8gZGUgYWN1ZXJkbyBhIHN1IGNvbnRlbmlkbyBzZSBwdWVkZSB2ZXIgcXVlIGxhIHZhcmlhYmxlICpsMSogaW5kaWNhIGVsIHBhaXMgZGUgbGEgcHJvcGllZGFkLCAqbDIqIGVsIGVzdGFkbyBvIHByb3ZpbmNpYSwgKmwzKiBsYSBsb2NhbGlkYWQgbyBtdW5pY2lwaW8sICpsNCogZWwgbm9tYnJlIGRlbCBiYXJyaW8gbyBsb2NhbGlkYWQgeSAqbDUqIGluZGljYSBlbCBub21icmUgZGVsIGJhcnJpby4gPC9icj4NClNlIHB1ZWRlIGRlc3RhY2FyIHF1ZSBsYSB2YXJpYWJsZSAqcHJpY2UqIHRpZW5lIGdyYW4gdmFyaWFiaWxpZGFkIGVuIHN1cyB2YWxvcmVzIHkgZXNvIGRlYmUgYSBxdWUgZGVwZW5kZSBkZSBsb3MgZGlzdGludG9zIHRpcG9zIGRlIG9wZXJhY2lvbih2ZW50YSwgQWxxdWlsZXIsIEFscXVpbGVyIFRlbXBvcmFsKSwgYSBsYXMgZGlmZXJlbnRlcyBtb25lZGFzIGVuIHF1ZSBzZSByZWdpc3RyYW4gbG9zIHByZWNpb3MgeSBsb3MgZGlzdGludG9zIHBlcmlvZG9zIGRlIHByZWNpbyhEaWFyaW8sIE1lbnN1YWwsIFNlbWFuYWwpIHF1ZSBzZSB0aWVuZW4uIDwvZGl2Pg0KDQpgYGB7ciBkYXRvc0l9DQpwcm9waWVkYWRlczwtZnJlYWQoImFyX3Byb3BlcnRpZXMuY3N2IiwgaGVhZGVyID0gVFJVRSwgc2VwID0gJywnKQ0KDQojQW5hbGl6YXIgZXN0cnVjdHVyYQ0KcHJpbnRTaGFwZShwcm9waWVkYWRlcykNCg0Kc2tpbV93aXRoKG51bWVyaWMgPSBsaXN0KG1lZGlhbiA9IG1lZGlhbiwgaGlzdCA9IE5VTEwpLA0KICAgICAgICAgIGludGVnZXIgPSBsaXN0KG1lZGlhbiA9IG1lZGlhbiwgbl91bmlxdWUgPSBuX3VuaXF1ZSwgaGlzdCA9IE5VTEwpDQogICAgICAgICAgKQ0KDQpkZXNjIDwtIGFzLmRhdGEudGFibGUoc2tpbV90b193aWRlKHByb3BpZWRhZGVzKSkNCnVuaXF1ZShkZXNjJHR5cGUpDQpkZXNjW3R5cGUgPT0gImNoYXJhY3RlciIsIGMoInZhcmlhYmxlIiwgc2hvd19za2ltbWVycygiY2hhcmFjdGVyIilbWzFdXSldICU+JQ0KICBrYWJsZSgpICU+JSANCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGthYmxlX29wdGlvbnMpDQpkZXNjW3R5cGUgPT0gIm51bWVyaWMiLCBjKCJ2YXJpYWJsZSIsIHNob3dfc2tpbW1lcnMoIm51bWVyaWMiKVtbMV1dKV0gJT4lDQogIGthYmxlKCkgJT4lIA0KICBrYWJsZV9zdHlsaW5nKGJvb3RzdHJhcF9vcHRpb25zID0ga2FibGVfb3B0aW9ucykNCmRlc2NbdHlwZSA9PSAiaW50ZWdlciIsIGMoInZhcmlhYmxlIiwgc2hvd19za2ltbWVycygiaW50ZWdlciIpW1sxXV0pXSAlPiUNCiAga2FibGUoKSAlPiUgDQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBrYWJsZV9vcHRpb25zKQ0KZGVzY1t0eXBlID09ICJsb2dpY2FsIiwgYygidmFyaWFibGUiLCBzaG93X3NraW1tZXJzKCJsb2dpY2FsIilbWzFdXSldICU+JQ0KICBrYWJsZSgpICU+JSANCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGthYmxlX29wdGlvbnMpDQoNCiNGaWx0cmFyIGRhdG9zIGRlIGFjdWVyZG8gYSBjb25zaWduYQ0KcHJvcF9zZWxlY3RlZDwtcHJvcGllZGFkZXMgJT4lIGZpbHRlciggbDE9PSdBcmdlbnRpbmEnICYgbDI9PSdDYXBpdGFsIEZlZGVyYWwnKQ0KcHJvcF9zZWxlY3RlZDwtcHJvcF9zZWxlY3RlZCAlPiUgZmlsdGVyKCBjdXJyZW5jeT09J1VTRCcpDQpwcm9wX3NlbGVjdGVkPC1wcm9wX3NlbGVjdGVkICU+JSBmaWx0ZXIoIHByb3BlcnR5X3R5cGUgJWluJWMoJ0Nhc2EnLCdEZXBhcnRhbWVudG8nLCdQSCcpICkNCnByb3Bfc2VsZWN0ZWQ8LXByb3Bfc2VsZWN0ZWQgJT4lIGZpbHRlciggb3BlcmF0aW9uX3R5cGUgPT0nVmVudGEnKQ0KcHJvcF9zZWxlY3RlZDwtcHJvcF9zZWxlY3RlZFssIGMoJ2lkJywgJ2wzJywgJ3Jvb21zJywgJ2JlZHJvb21zJywgJ2JhdGhyb29tcycsICdzdXJmYWNlX3RvdGFsJywgJ3N1cmZhY2VfY292ZXJlZCcsICdwcmljZScsICdwcm9wZXJ0eV90eXBlJyldDQpwcmludFNoYXBlKHByb3Bfc2VsZWN0ZWQpDQpgYGANCiMgQW7DoWxpc2lzIEV4cGxvcmF0b3Jpb3MgLSBJDQo8ZGl2IHN0eWxlPSJ0ZXh0LWFsaWduOiBqdXN0aWZ5Ij5TZSB1dGlsaXphbiBsYXMgZnVuY2lvbmVzIGRlIGxhIGxpYnJlcmlhIHB1cnIgcGFyYSBvYnRlbmVyICNkZSBOQSB5ICNkZSB2YWxvcmVzIMO6bmljb3MgZGUgY2FkYSB2YXJpYWJsZSB5IGxhIGxpYnJlcmlhIGNvcnJyIHBhcmEgZ3JhZmljYXIgbWF0cml6IGRlIGNvcnJlbGFjacOzbi4gPGJyPg0KU2Ugb2JzZXJ2YSB1bmEgZnVlcnRlIGNvcnJlbGFjaW9uIGVudHJlICpyb29tcyogeSAqYmVkcm9vbXMqIHNpZW5kbyBlc3RhIMO6bHRpbWEgbGEgZmVhdHVyZSBjb24gbcOhcyBtaXNzaW5nLiBUYW1iaWVuIHNlIHZlIHVuYSByZWxhY2lvbiBmdWVydGUgZW50cmUgICpzdXJmYWNlX3RvdGFsKiB5ICAqc3VyZmFjZV9jb3ZlcmVkKiBkYWRvIHF1ZSB1bmEgZXN0YSBpbmxjdWlkYSBlbiBsYSBvdHJhLjwvZGl2Pg0KYGBge3IgYW5hbGlzaXNJfQ0KI0NvbnRhciBOQSB5IFVuaXF1ZQ0KbmE8LXByb3Bfc2VsZWN0ZWQgJT4lDQogIG1hcF9kYmwoZnVuY3Rpb24oeCkgc3VtKGlzLm5hKHgpKSkNCnVuaTwtcHJvcF9zZWxlY3RlZCAlPiUNCiAgbWFwX2RibChmdW5jdGlvbih4KSBsZW5ndGgodW5pcXVlKHgpKSkNCg0KcmVzdWx0YWRvPC1yYmluZChuYSwgdW5pKQ0KDQpyb3duYW1lcyhyZXN1bHRhZG8pPC1jKCcjTkEnLCcjVW5pcXVlJykNCg0KcmVzdWx0YWRvICU+JSANCiAga2FibGUoKSAlPiUgDQogIGthYmxlX3N0eWxpbmcoYm9vdHN0cmFwX29wdGlvbnMgPSBrYWJsZV9vcHRpb25zKQ0KDQojTWF0cml6IGRlIENvcnJlbGFjaW9uDQpwcm9wX3NlbGVjdGVkWywgMzo4XSAlPiUgDQogY29ycmVsYXRlKHVzZSA9ICdwYWlyd2lzZS5jb21wbGV0ZS5vYnMnLCBtZXRob2QgPSAncGVhcnNvbicpICU+JSANCiAgIHNoYXZlKCkgJT4lIA0KICAgZmFzaGlvbigpDQpgYGANCg0KIyBQcmVwYXJhY2nDs24gZGUgRGF0b3MgLSBJSQ0KRWxpbWluYXIgdmFyaWFibGUgYmVkcm9vbXMgeSBsYXMgZmlsYXMgY29uIE5Bcy4NCmBgYHtyIGRhdG9zSUl9DQpwcm9wX3NlbGVjdGVkJGJlZHJvb21zPC1OVUxMDQpwcm9wX3NlbGVjdGVkPC1uYS5vbWl0KHByb3Bfc2VsZWN0ZWQpDQpwcmludFNoYXBlKHByb3Bfc2VsZWN0ZWQpDQpgYGANCg0KIyBBbsOhbGlzaXMgRXhwbG9yYXRvcmlvcyAtIElJDQpPYnRlcm5lciBlc3RhZMOtc3RpY2FzIGRlc2NyaXB0aXZhcywgaGlzdG9ncmFtYSwgYm94cGxvdCB5IGNvcnJlbG9ncmFtYS48YnI+DQpMYSBkaXN0cmlidWNpw7NuIGRlIGxhIHZhcmlhbGUgcHJlY2lvIGNvbiByZXNwZWN0byBhbCB0aXBvIGRlIHByb3BpZWRhZCBlcyBhc2ltw6l0cmljYSBwb3NpdGl2YS4gTG9zIHByZWNpb3MgZGUgbG9zICpEZXBhcnRhbWV0bm9zKiB5ICpDYXNhKiBwcmVzZW50YW4gdmFsb3JlcyBhdMOtcGljb3MgbXV5IGFsdG9zLiBMYSB2YXJpYW56YSBkZSBsYXMgcHJvcGllZGFkZXMgZGUgdGlwbyAqQ2FzYSogcGFyZWNpZXJhIHNlciBzdXRpbG1lbnRlIG3DoXMgZ3JhbmRlIHF1ZSBsYSBkZSBsb3Mgb3Ryb3MgZG9zIHRpcG9zLiA8YnI+DQpFbiBlbCBjb3JyZWxvZ3JhbWEgc2Ugb2JzZXJ2YSB1bmEgY29ycmVsYWNpw7NuIGVudHJlOiANCg0KKiAqc3VyZmFjZV90b3RhbCogeSAqc3VyZmFjZV9jb3ZlcmVkKiANCiogKnJvb21zKiB5ICpiYXRocm9vbXMqDQoqICpwcmljZSogeSAqYmF0aHJvb21zKg0KKiAqcHJpY2UqIHkgKnJvb21zKg0KDQpgYGB7ciBhbmFsaXNpc0lJfQ0KDQpnZXRFc3RhZGlzdGljYXMocHJvcF9zZWxlY3RlZCRwcmljZSkNCg0KcHJvcF9zZWxlY3RlZCRwcm9wZXJ0eV90eXBlPC0gYXMuZmFjdG9yKHByb3Bfc2VsZWN0ZWQkcHJvcGVydHlfdHlwZSkNCg0KcHJpY2V4dHlwZTwtcHJvcF9zZWxlY3RlZCAlPiUNCiAgZ3JvdXBfc3BsaXQocHJvcGVydHlfdHlwZSkgJT4lIA0KICBtYXBfZGZyKGZ1bmN0aW9uKHgpIGdldEVzdGFkaXN0aWNhcyh4JHByaWNlKSkNCnJvd25hbWVzKHByaWNleHR5cGUpPC11bmlxdWUocHJvcF9zZWxlY3RlZCRwcm9wZXJ0eV90eXBlKQ0KDQpwcmljZXh0eXBlICU+JQ0KICBrYWJsZSgpICU+JSANCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGthYmxlX29wdGlvbnMpDQoNCmdncGxvdChkYXRhID0gcHJvcF9zZWxlY3RlZCwgYWVzKHByb3Bfc2VsZWN0ZWQkcHJpY2UsIGNvbG9yID0gcHJvcGVydHlfdHlwZSkpKw0KICBnZW9tX2hpc3RvZ3JhbSgpICsNCiAgbGFicyh0aXRsZSA9ICJIaXN0b2dyYW1hIGRlIFByZWNpb3MgcG9yIHRpcG8gZGUgUHJvcGllZGFkIiwgeD0iUHJlY2lvIiwgeT0iRnJlY3VlbmNpYSIpKw0KICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAnbm9uZScpKw0KICBmYWNldF93cmFwKH5wcm9wZXJ0eV90eXBlKQ0KDQpnZ3Bsb3QocHJvcF9zZWxlY3RlZCwgYWVzKHggPSBwcm9wZXJ0eV90eXBlLCB5ID0gcHJpY2UsIGdyb3VwID0gcHJvcGVydHlfdHlwZSwgZmlsbCA9IHByb3BlcnR5X3R5cGUgKSkgKw0KICBnZW9tX2JveHBsb3QoKSsNCiAgbGFicyh0aXRsZSA9ICJCb3hwbG90IFByZWNpb3MgdnMgVGlwbyBkZSBQcm9waWVkYWQiLCB4PSJUaXBvIGRlIFByb3BpZWRhZCIsIHk9IlByZWNpbyIpDQoNCmdnY29ycihwcm9wX3NlbGVjdGVkLCBsYXlvdXQuZXhwID0gMikgKyBsYWJzKHRpdGxlPSdDb3JyZWxvZ3JhbWEgdmFyaWFibGVzIGN1YW50aXRhdGl2YXMnKQ0KDQpgYGANCg0KIyBPdXRsaWVycw0KRWxpbWluYXIgb3V0bGllcnMgZGUgbGEgdmFyaWFibGUgcHJlY2lvLiBTZSBjcmVhIHVuYSBudWV2YSB2YXJpYWJsZSBwYXJhIG9idGVuZXIgZWwgcHJlY2lvIHBvciBtZXRybyBjdWFkcmFkbyB5IHNlIGFuYWxpemFuIGxhcyBlc3RhZGlzdGljYXMgZGVzY3JpcHRpdmFzIGRlIMOpc3RhIHBhcmEgZXN0YWJsZWNlciBsb3MgdmFsb3JlcyBkZSBjb3J0ZS4gQWRlbcOhcyBzZSB1dGlsaXphIGxhcyBbdGFzYWNpb25lcyBvbmxpbmVdKGh0dHA6Ly93d3cudGFzYWNpb255YS5jb20vaW5kZXgucGhwI3ByZWNpb3NfcG9yX3pvbmEpIGRlIGxvcyBiYXJyaW9zIGRlIGxhIGNhcGl0YWwgcGFyYSBmaWphciB1bmEgY290YSBpbmZlcmlvciBkZSA3MDAgeSB1bmEgY290YSBzdXBlcmlvciBkZSAxMDAwMC4gU2UgZW5jb250cmFyb24gMzIwIG91dGxpZXJzLiA8YnI+DQpTZSBvYnNlcnZhbiByZWdpc3Ryb3MgcXVlIHRpZW5lbiBtYWwgY2FyZ2Fkb3MgbG9zIG1ldHJvcyBjdWFkcmFkb3MgZGUgbGEgc3VwZXJmaWNpZSwgb3Ryb3MgcXVlIHRpZW5lbiBlbCBwcmVjaW8gZGUgcmVmZXJlbnRlIGEgdW4gYWxxdWlsZXIgZW4gbHVnYXIgZGUgdW5hIHZlbnRhLg0KYGBge3Igb3V0bGllcnN9DQoNCnNpbl9vdXRsaWVyczwtcHJvcF9zZWxlY3RlZCAlPiUNCiAgbXV0YXRlKHByaWNleHN1cmZhY2U9cHJpY2Uvc3VyZmFjZV90b3RhbCkNCg0KZ2V0RXN0YWRpc3RpY2FzKHNpbl9vdXRsaWVycyRwcmljZXhzdXJmYWNlKQ0KDQpzaW5fb3V0bGllcnMkaW5fPXNpbl9vdXRsaWVycyRwcmljZXhzdXJmYWNlPjcwMCAmIHNpbl9vdXRsaWVycyRwcmljZXhzdXJmYWNlPDEwMDAwDQoNCnByaW50KHBhc3RlKCdDYW50aWRhZCBkZSBPdXRsaWVycyBlbmNvbnRyYWRvczogJyxzdW0oc2luX291dGxpZXJzJGluXz09RkFMU0UpKSkgDQoNCnNpbl9vdXRsaWVyczwtc2luX291dGxpZXJzICU+JSBmaWx0ZXIoaW5fPT1UUlVFKQ0Kc2luX291dGxpZXJzWyw5XTwtTlVMTA0Kc2luX291dGxpZXJzPC1hcy5kYXRhLmZyYW1lKHNpbl9vdXRsaWVycykNCnByaW50U2hhcGUoc2luX291dGxpZXJzKQ0KYGBgDQoNCiMgQW7DoWxpc2lzIEV4cGxvcmF0b3Jpb3MgLSBJSUkNClJlcGVydGlyIGFuw6FsaXNpcyBzaW4gb3V0bGllcnMuPGJyPg0KU2Ugc2lndWVuIG9ic2VydmFuZG8gb3V0bGllcnMsIGxvcyByYW5nb3MgaW50ZXJjdWFydGlsZXMgcGVybWFuZWNpZXJvbiBzdXRpbG1lbnRlIHNpbWlsYXJlcyB5IGxvcyBncmFmaWNvcyBkZWwgaGlzdG9ncmFtYSB5IGJveHBsb3QgdGFtYmllbiBzZSBhc2VtZWphbiBhbCBhbsOhbGlzaXMgYW50ZXJpb3IuIFNlIHByZXNlbmNpYSB1biBwb3JjZW50YWplIG1lbm9yIGRlIHZhbG9yZXMgYXTDrXBpY29zIHkgYWRlbcOhcyBzZSBhanVzdMOzIGVsIHZhbG9yIG3DrW5pbW8uIEVsIGNhbWJpbyBxdWUgbcOhcyBzZSBkZXN0YWPDsyBlcyBlbiBlbCBncsOhZmljbyBkZWwgY29ycmVsb2dyYW1hIGRvbmRlIHBhcmVjaWVyYSBxdWUgbGFzIHZhcmlhYmxlcyBjdWFudGl0YXRpdmFzIGVzdGFuIG3DoXMgcmVsYWNpb25lcyBlbnRyZSB0b2Rhcy4NCmBgYHtyIGFuYWxpc2lzSUlJfQ0KDQpnZXRFc3RhZGlzdGljYXMoc2luX291dGxpZXJzJHByaWNlKQ0KDQpwcmljZXh0eXBlPC1zaW5fb3V0bGllcnMgJT4lDQogIGdyb3VwX3NwbGl0KHByb3BlcnR5X3R5cGUpICU+JSANCiAgbWFwX2RmcihmdW5jdGlvbih4KSBnZXRFc3RhZGlzdGljYXMoeCRwcmljZSkpDQpyb3duYW1lcyhwcmljZXh0eXBlKTwtYygnQ2FzYScsJ0RlcHRvJywnUEgnKQ0KDQpwcmljZXh0eXBlICU+JQ0KICBrYWJsZSgpICU+JSANCiAga2FibGVfc3R5bGluZyhib290c3RyYXBfb3B0aW9ucyA9IGthYmxlX29wdGlvbnMpDQoNCmdncGxvdChkYXRhID0gc2luX291dGxpZXJzLCBhZXMoc2luX291dGxpZXJzJHByaWNlLCBjb2xvciA9IHByb3BlcnR5X3R5cGUpKSsNCiAgZ2VvbV9oaXN0b2dyYW0oKSArDQogIGxhYnModGl0bGUgPSAiSGlzdG9ncmFtYSBkZSBQcmVjaW9zIHBvciB0aXBvIGRlIFByb3BpZWRhZCIsIHg9IlByZWNpbyIsIHk9IkZyZWN1ZW5jaWEiKSsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gJ25vbmUnKSsNCiAgZmFjZXRfd3JhcCh+cHJvcGVydHlfdHlwZSkNCg0KZ2dwbG90KHNpbl9vdXRsaWVycywgYWVzKHggPSBwcm9wZXJ0eV90eXBlLCB5ID0gcHJpY2UsIGdyb3VwID0gcHJvcGVydHlfdHlwZSwgZmlsbCA9IHByb3BlcnR5X3R5cGUgKSkgKw0KICBnZW9tX2JveHBsb3QoKSsNCiAgbGFicyh0aXRsZSA9ICJCb3hwbG90IFByZWNpb3MgdnMgVGlwbyBkZSBQcm9waWVkYWQiLCB4PSJUaXBvIGRlIFByb3BpZWRhZCIsIHk9IlByZWNpbyIpDQoNCmdnY29ycihzaW5fb3V0bGllcnMsIGxheW91dC5leHAgPSAyKSArIGxhYnModGl0bGU9J0NvcnJlbG9ncmFtYSB2YXJpYWJsZXMgY3VhbnRpdGF0aXZhcycpDQoNCmBgYA0KDQojIE1vZGVsbyBMaW5lYWwNClNlIGFuYWxpemFuIGxhcyBzYWxpZGFzIGRlIGFtYm9zIG1vZGVsb3MgbHVlZ28gZGUgYXBsaWNhciBlbCBtb2RlbG8gbGluZWFsIHNpbXBsZSBkZSBSLiA8YnI+DQpFbCBwLXZhbG9yIGVuIGFtYm9zIG1vZGVsb3MgcGFyYSBhbWJvcyBjb2VmaWNpZW50ZXMgZXN0aW1hZG9zIGVzIHBlcXVlw7FvIGxvIHF1ZSBpbmRpY2EgcXVlIHNlIHB1ZWRlIGNvbnNpZGVyYXIgdW4gbW9kZWxvIGxpbmVhbCB5IHF1ZSBlbCBwcmVjaW8gc2UgcmVsYWNpb25hIGNvbiBjYWRhIHVuYSBkZSBsYXMgdmFyaWFibGVzIGRlIGNhZGEgbW9kZWxvLiBTZSBvYnNlcnZhIHF1ZSBlbCBkZXN2aW8gZXN0YW5kYXIgZGUgZXJyb3IgZGUgbG9zIGNvZWZjaWVudGVzIHNvbiBtw6FzIGJham9zIGVuIGVsIHNlZ3VuZG8gbW9kZWxvKHN1cmZhY2VfdG90YWwpIHF1ZSBlbiBlbCBwcmltZXJvLjxicj4NCkVsIDI4JSBkZSBsYSB2YXJpYWJpbGlkYWQgb2JzZXJ2YWRhIGVuIGxvcyBwcmVjaW9zIHF1ZWRhIGV4cGxpY2FkYSBwb3IgbGEgcmVsYWNpb24gbGluZWFsIGVudHJlIGVsIHByZWNpbyB5IHJvb21zLjxicj4NCkVsIDUzJSBkZSBsYSB2YXJpYWJpbGlkYWQgb2JzZXJ2YWRhIGVuIGxvcyBwcmVjaW9zIHF1ZWRhIGV4cGxpY2FkYSBwb3IgbGEgcmVsYWNpb24gbGluZWFsIGVudHJlIGVsIHByZWNpbyB5IHN1cmZhY2VfdG90YWwuPGJyPg0KCQ0KRWwgdmFsb3IgaW50ZXJjZXB0KCRcYmV0YV8wJCkgLTQzNjE2LDgwIGVuIGVsIHByaW1lcm8gbW9kZWxvIGVzIGVsIHZhbG9yIHByb21lZGlvIGRlIHVuYSBwcm9waWVkYWQgc2luIG5pbmd1bmEgaGFiaXRhY2nDs24uIEVzdGUgdmFsb3Igbm8gdGllbmUgc2VudGlkbyB5YSBxdWUgbm8gc2UgZW5jdWVudHJhIGRlbnRybyBkZSBsb3MgcmFuZ29zIHZhbGlkb3MgZGUgbnVlc3RybyBtb2RlbG8uIEVsICRcYmV0YV8xJCBpbmRpY2EgZW4gcHJvbWVkaW8gcXVlIHVuYSBwcm9waWVkYWQgYXVtZW50YSAgVVNEIDEwNDQyNSwyIHBvciBjYWRhIGhhYml0YWNpw7NuIHF1ZSBzZSBhZ3JlZ2EuIDxicj4NCkVuIGVsIGNhc28gZGVsIHNlZ3VuZG8gbW9kZWxvLCBlbCB2YWxvciBpbnRlcmNlcHQoJFxiZXRhXzAkKSAxODgxMCw1NCBlcyBlbCB2YWxvciBwcm9tZWRpbyBkZSB1bmEgcHJvcGllZGFkIGNvbiBzdXBlcmZpY2llIDAuIEVzdGUgdmFsb3IgdGFtcG9jbyB0aWVuZSBzZW50aWRvLiBFbCAkXGJldGFfMSQgIG5vcyBpbmRpY2EgcXVlIHVuYSBwcm9waWVkYWQgYXVtZW50YSBlbiBwcm9tZWRpbyBVU0QgMjUzNyw2NyBwb3IgY2FkYSBtZXRybyBjdWFkcmFkby4gIA0KRW4gYmFzZSBhIGxvIGV4cHVlc3RvIGFudGVyaW9ybWVudGUgZWwgc2VndW5kbyBtb2RlbG8gc2VyaWEgZWwgbWVqb3IgZGUgYW1ib3MuDQpgYGB7ciBtb2RlbG99DQojIFNlIHF1aWVyZSBleHBsaWNhciBlbCBwcmVjaW8gZGUgbGFzIHByb3BpZWRhZGVzIGVuIGJhc2UgYSBsYSBjYW50IGRlIGhhYml0YWNpb25lcyBkZSBsYXMgbWlzbWFzLg0KIyBZOiBsb3MgcHJlY2lvcyBkZSBsYXMgcHJvcGllZGFkZXMNCiMgWDogbGEgY2FudGlkYWQgZGUgaGFiaXRhY2lvbmVzIGRlIGxhIHByb3BpZWRhZA0KbW9kZWxvX3Jvb21zPWxtKHByaWNlfnJvb21zLCBkYXRhPXNpbl9vdXRsaWVycykNCg0Kc3VtbWFyeShtb2RlbG9fcm9vbXMpDQoNCmdncGxvdChzaW5fb3V0bGllcnMsIGFlcyhyb29tcywgcHJpY2UpKSArDQogIGdlb21fYWJsaW5lKGFlcyhpbnRlcmNlcHQgPSBtb2RlbG9fcm9vbXMkY29lZmZpY2llbnRzWzFdLCBzbG9wZSA9IG1vZGVsb19yb29tcyRjb2VmZmljaWVudHNbMl0pLCBkYXRhID0gbW9kZWxvX3Jvb21zLCBjb2xvdXIgPSAiYmx1ZSIpICsNCiAgbGFicyh0aXRsZSA9ICJQcmVjaW8gdmVyc3VzIFJvb21zIGNvbiBSZWN0YSBhanVzdGFkYSBwb3IgbcOtbmltb3MgY3VhZHJhZG9zLiIpKw0KICBnZW9tX3BvaW50KCkNCg0KIyBZOiBsb3MgcHJlY2lvcyBkZSBsYXMgcHJvcGllZGFkZXMNCiMgWDogbGEgc3VwZXJmaWNpZSB0b3RhbCBkZSBsYSBwcm9waWVkYWQNCm1vZGVsb19zdXJmYWNlPWxtKHByaWNlfnN1cmZhY2VfdG90YWwsIGRhdGE9c2luX291dGxpZXJzKQ0KDQpzdW1tYXJ5KG1vZGVsb19zdXJmYWNlKQ0KDQpnZ3Bsb3Qoc2luX291dGxpZXJzLCBhZXMoc3VyZmFjZV90b3RhbCwgcHJpY2UpKSArDQogIGdlb21fYWJsaW5lKGFlcyhpbnRlcmNlcHQgPSBtb2RlbG9fc3VyZmFjZSRjb2VmZmljaWVudHNbMV0sIHNsb3BlID0gbW9kZWxvX3N1cmZhY2UkY29lZmZpY2llbnRzWzJdKSwgZGF0YSA9IG1vZGVsb19zdXJmYWNlLCBjb2xvdXIgPSAiYmx1ZSIpICsNCiAgbGFicyh0aXRsZSA9ICJQcmVjaW8gdmVyc3VzIFN1cmZhY2VfdG90YWwgY29uIFJlY3RhIGFqdXN0YWRhIHBvciBtw61uaW1vcyBjdWFkcmFkb3MuIikrDQogIGdlb21fcG9pbnQoKQ0KDQpgYGANCg0K