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