El código fuente de este trabajo se encuentra disponible aquí


En primer lugar, se importan las librerías con las que se trabajará: tidyverse, data.table, corrr, ggplot2, GGally y hrbtthemes.

library(tidyverse)
library(data.table)
library(corrr)
library(ggplot2)
library(GGally)
library(hrbrthemes)
library(modelr)

1. Preparación de los datos I

1.1. Carga del dataset

Como primer paso en la preparación de los datos, se procede a cargar el dataset con la función fread de la librerría data.table y se lo asigna a una variable.

A continuación se puede observar que este dataset consta de 388891 registros y 24 columnas.

dataset = fread("ar_properties.csv")
|--------------------------------------------------|
|==================================================|
dataset

Con el fin de conocer un poco mejor su estructura, se emplea la función summary de R base para ver qué tipo de información contiene cada columna.

Aquí se puede apreciar que, de los 24 atributos, 15 contienen información de tipo “caracter”; 8, de tipo “numérico” y uno contiene solamente missing values (l6).

De aquellos atributos numéricos, se obtienen también las medidas de posición (media, mediana, primer cuartil, tercer cuartil, mínimo y máximo) y la cantidad de datos faltantes (NAs).

summary(dataset)
      id              ad_type           start_date          end_date          created_on             lat              lon               l1                 l2           
 Length:388891      Length:388891      Length:388891      Length:388891      Length:388891      Min.   :-54.98   Min.   :-105.27   Length:388891      Length:388891     
 Class :character   Class :character   Class :character   Class :character   Class :character   1st Qu.:-34.67   1st Qu.: -58.80   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character   Mode  :character   Median :-34.60   Median : -58.48   Mode  :character   Mode  :character  
                                                                                                Mean   :-34.48   Mean   : -59.37                                        
                                                                                                3rd Qu.:-34.43   3rd Qu.: -58.40                                        
                                                                                                Max.   : 44.67   Max.   : -41.90                                        
                                                                                                NA's   :50597    NA's   :50597                                          
      l3                 l4                 l5               l6              rooms           bedrooms        bathrooms     surface_total      surface_covered  
 Length:388891      Length:388891      Length:388891      Mode:logical   Min.   : 1.0     Min.   : -2.00   Min.   : 1.00   Min.   :    -3.0   Min.   :   -139  
 Class :character   Class :character   Class :character   NA's:388891    1st Qu.: 2.0     1st Qu.:  1.00   1st Qu.: 1.00   1st Qu.:    50.0   1st Qu.:     43  
 Mode  :character   Mode  :character   Mode  :character                  Median : 3.0     Median :  2.00   Median : 1.00   Median :    91.0   Median :     70  
                                                                         Mean   : 2.9     Mean   :  2.16   Mean   : 1.67   Mean   :   458.3   Mean   :    235  
                                                                         3rd Qu.: 4.0     3rd Qu.:  3.00   3rd Qu.: 2.00   3rd Qu.:   266.0   3rd Qu.:    142  
                                                                         Max.   :40.0     Max.   :390.00   Max.   :20.00   Max.   :200000.0   Max.   :4000000  
                                                                         NA's   :144668   NA's   :230747   NA's   :94136   NA's   :74063      NA's   :97854    
     price             currency         price_period          title           property_type      operation_type    
 Min.   :0.000e+00   Length:388891      Length:388891      Length:388891      Length:388891      Length:388891     
 1st Qu.:2.000e+04   Class :character   Class :character   Class :character   Class :character   Class :character  
 Median :9.400e+04   Mode  :character   Mode  :character   Mode  :character   Mode  :character   Mode  :character  
 Mean   :2.690e+05                                                                                                 
 3rd Qu.:2.250e+05                                                                                                 
 Max.   :2.147e+09                                                                                                 
 NA's   :21222                                                                                                     

Dado que se observa una gran cantidad de atributos categóricos, se utiliza la función glimpse de la librería tidyverse, que permite visualizar de modo general qué valores contiene cada atributo

glimpse(dataset)
Observations: 388,891
Variables: 24
$ id              <chr> "S0we3z3V2JpHUJreqQ2t/w==", "kMxcmAS8NvrynGBVbMOEaQ==", "Ce3ojF+ZTOkB8d+LI9dpxg==", "AUGpj3raGmOCiulSMGIBPA==", "m+MwZmJl3OoxmfWcB//sBA==", "pJRPrB5G8Jl…
$ ad_type         <chr> "Propiedad", "Propiedad", "Propiedad", "Propiedad", "Propiedad", "Propiedad", "Propiedad", "Propiedad", "Propiedad", "Propiedad", "Propiedad", "Propieda…
$ start_date      <chr> "2019-04-14", "2019-04-14", "2019-04-14", "2019-04-14", "2019-04-14", "2019-04-14", "2019-04-14", "2019-04-14", "2019-04-14", "2019-04-14", "2019-04-14"…
$ end_date        <chr> "2019-06-14", "2019-04-16", "9999-12-31", "9999-12-31", "2019-07-09", "2019-08-08", "2019-07-10", "2019-06-14", "2019-04-29", "2019-07-10", "2019-04-29"…
$ created_on      <chr> "2019-04-14", "2019-04-14", "2019-04-14", "2019-04-14", "2019-04-14", "2019-04-14", "2019-04-14", "2019-04-14", "2019-04-14", "2019-04-14", "2019-04-14"…
$ lat             <dbl> -34.94331, -34.63181, NA, -34.65471, -34.65495, -32.93547, -34.65183, -34.91213, -34.60338, NA, -34.59005, NA, NA, NA, -39.05412, -39.05859, -34.59413, …
$ lon             <dbl> -54.92966, -58.42060, NA, -58.79089, -58.78712, -60.68398, -58.65912, -54.84749, -58.43450, NA, -58.43672, NA, NA, NA, -67.39418, -67.58741, -58.44373, …
$ l1              <chr> "Uruguay", "Argentina", "Argentina", "Argentina", "Argentina", "Argentina", "Argentina", "Uruguay", "Argentina", "Argentina", "Argentina", "Argentina", …
$ l2              <chr> "Maldonado", "Capital Federal", "Bs.As. G.B.A. Zona Norte", "Bs.As. G.B.A. Zona Oeste", "Bs.As. G.B.A. Zona Oeste", "Santa Fe", "Bs.As. G.B.A. Zona Oest…
$ l3              <chr> "Punta del Este", "Boedo", NA, "Moreno", "Moreno", "Rosario", "Ituzaingó", "José Ignacio", "Almagro", "Miramar", "Palermo", "Roldán", "Roldán", "Rosario…
$ l4              <chr> NA, NA, NA, "Moreno", "Moreno", NA, "Ituzaingó", NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,…
$ l5              <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ l6              <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ rooms           <int> 2, NA, 2, 2, 2, 4, NA, 6, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, NA, NA, NA, NA, NA, NA, NA, NA…
$ bedrooms        <int> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, …
$ bathrooms       <int> 1, NA, 1, 2, 3, 1, 3, 3, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, 1, NA, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 2, 1, NA, 2, 5, 5, 5, 5, 5…
$ surface_total   <int> 45, NA, 200, 460, 660, NA, 70, NA, 1300, 405, 352, 373, 360, 1325, 250, 80142, 101, NA, 54, 180, 36, 36, 40, 43, 40, 40, 36, 36, 36, 36, 43, 43, 36, 37,…
$ surface_covered <int> 40, NA, NA, 100, 148, 89, 122, NA, NA, NA, NA, NA, NA, 2, NA, NA, NA, NA, 54, 180, 33, 33, 34, 34, 34, 34, 33, 33, 33, 33, 34, 34, 33, 32, 97, 50, NA, N…
$ price           <int> 13000, 0, NA, NA, NA, NA, NA, NA, 0, NA, 0, NA, NA, NA, NA, NA, 0, 0, 0, NA, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, NA, NA, NA, NA, NA, 0, 0, 0, 0…
$ currency        <chr> "UYU", NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N…
$ price_period    <chr> "Mensual", "Mensual", NA, "Mensual", "Mensual", "Mensual", "Mensual", "Mensual", "Mensual", "Mensual", "Mensual", "Mensual", "Mensual", "Mensual", NA, "…
$ title           <chr> "Departamento - Roosevelt", "PH - Boedo", "Ituzaingo  1100 - $ 1 - Casa Alquiler", "Dr. Vera   300 - Consulte precio - Casa en Venta", "L. N. Alem   240…
$ property_type   <chr> "Departamento", "PH", "Casa", "Casa", "Casa", "Casa", "Casa", "Casa", "Lote", "Lote", "Lote", "Lote", "Lote", "Lote", "Lote", "Lote", "Lote", "Oficina",…
$ operation_type  <chr> "Alquiler", "Venta", "Alquiler", "Venta", "Venta", "Venta", "Venta", "Alquiler", "Venta", "Venta", "Venta", "Venta", "Venta", "Venta", "Venta", "Venta",…

De lo observado, se puede realizar la siguiente descripción de cada atributo:

  • id: número de identificación de la propiedad. Atributo nominal.
  • ad_type: posiblemente tipo de anuncio. Atributo nominal.
  • start_date: fecha de inicio (del anuncio, posiblemente). Atributo ordinal.
  • ent_date: fecha de finalización (del anuncio, posiblemente). Atributo ordinal.
  • created_on: fecha de creación. Atributo ordinal.
  • lat: latitud donde se encuentra la propiedad.
  • longitud: longitud donde se encuentra la propiedad. Atributo numérico.
  • l1: país donde se encuentra la propiedad. Atributo nominal.
  • l2: provincia (si es CABA, se indida “Capital Federal”) o región (si es GBA) donde se encuentra la propiedad. Atributo nominal.
  • l3: ciudad, partido (si es GBA) o barrio (si es CABA) donde se encuentra la propiedad. Atributo nominal.
  • l4: barrio (si es GBA) donde se encuentra la propiedad. Atributo nominal.
  • l5: mayores especificaciones sobre el barrio. Atributo nominal.
  • l6: atributo con valores faltantes únicamente.
  • rooms: cantidad de habitaciones que posee la propiedad. Atributo numérico.
  • bedrooms: cantidad de dormitorios que posee la propiedad. Atributo numérico.
  • bathrooms: cantidad de baños que posee la propiedad. Atributo numérico.
  • surface_total: superficie total de la propiedad. Atributo numérico.
  • surface_covered: superficie cubierta de la propiedad. Atributo numérico.
  • price: precio de la propiedad. Atributo numérico.
  • currency: moneda en la cual se expresa el precio. Atributo nominal.
  • price_period: período que comprende el precio de la propiedad. Atributo nominal.
  • title: título del anuncio. Atributo nominal.
  • property_type: tipo de propiedad. Atributo nominal.
  • operation_type: tipo de operación. Atributo nominal.

1.2. Filtrado del dataset

Dado que solo se trabajará con las propiedades en venta que correspondan a casas, departamentos o PHs que se encuentren ubicados en Capital Federal (Argentina) y cuyo precio se exprese en dólares (USD), se procede a filtrar del dataset para que muestre los registros deseados.

Para ello se utiliza la función filter del paquete dplyr importado junto a tidyverse.

Asimismo, se emplea la función select del mismo paquete para seleccionar los atributos de interés para este trabajo: id, l3, rooms, bedrooms, bathrooms, surface_total, surface_covered, price y property_type.

Se obtiene una dataset de 61905 registros y 9 atributos y se lo asigna a una nueva varibale.

dataset_filtrado <- dataset %>% 
  filter(l1 == 'Argentina',
         l2 == 'Capital Federal',
         currency == 'USD',
         property_type %in% c('Departamento','PH','Casa'),
         operation_type == 'Venta') %>% 
  select(id, l3, rooms, bedrooms, bathrooms, surface_total, surface_covered, price, property_type)

dataset_filtrado

2. Análisis exploratorio I

2.1. Valores únicos y faltantes

En un primer análisis exploratorio, se verifica la cantidad de valores faltantes y de valores únicos en cada atributo.

Para obtener la cantidad de valores únicos se utiliza la función summarise_all de paquete dplyr que permite aplicar una misma función a todas las columnas del dataset. En este caso, se le aplica la función n_distinct que devuelve la cantidad de valores distintos en cada atributo.

unique_values <- dataset_filtrado %>% 
  summarise_all(n_distinct)

unique_values

En el caso de los valores faltantes, se aplica la función is.na de R base sobre el dataset filtrado para obtener una máscara booleana que indique si en cada celda hay un NA o no. Luego, por columna, se suman las filas que sí tienen un NA (que resultaron TRUE para la función aplicada). Para una mejor visualización, se transpone la información obtenida y se la muestra en un data frame.

na_values <- dataset_filtrado %>% 
  is.na() %>% 
  colSums() %>% 
  t() %>% 
  as.data.frame()

na_values

2.2. Correlación entre variables

Con el propósito de cotejar si existe alguna correlación entre la variables numéricas, se realiza una matriz de correlación de las mismas y se la grafica con la función rplot del paquete ggplot2.

Para calcular la correlación se utiliza el coeficiente de correlación de Pearson y se toman en cuenta solamente los registros completos. Esto último se realiza mediante el parámetro “complete.obs”.

dataset_filtrado %>% 
  select_if(is.numeric) %>% 
  correlate(use="complete.obs") %>% 
  shave() %>% 
  fashion()

Correlation method: 'pearson'
Missing treated using: 'complete.obs'
dataset_filtrado %>% 
  select_if(is.numeric) %>% 
  correlate(use="complete.obs")  %>% 
  rplot(colours = c('#003366','#3399FF','#FF3300')) +
  geom_point(size=5) +
  labs(title = 'Correlograma de variables') +
  theme(axis.text.x = element_text(angle = 45, hjust=1))

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

3. Preparacion de los datos II

El análisis exploratorio realizado en el apartado anterior se halló que las variables dormitorios (bedrooms) y habitaciones (rooms) presentan una alta correlacionadas positiva (con un valor de 0.92). Esto significa que cuando los valores de una aumentan, los de la otra también lo hacen. De este modo, la información que ofrecen es, en cierto sentido, redundante. Esto permite optar por remover una de dichas variables y, debido a que dormitorios demostró tener gran cantidad de valores faltantes, se quita esta variable del dataset.

Para no tener mayores inconvenientes con el resto de los registros, se decide asimismo quitar aquellos que contengan valores faltantes, preservando solamente los registros completos.

Como resultado, se obtiene un dataset con 8 variables y 51210 registros.

dataset_sin_NA <- dataset_filtrado %>% 
  select(-c(bedrooms)) %>% 
  na.omit()

dataset_sin_NA

4.Análisis exploratorio II

Se realiza un nuevo análisis exploratorio, ahora utilizando el dataset sin faltantes, obtenido en la sección 3.

A continuación, se ofrece una exploración más detallada sobre la variable precio (price) y su relación con el atributo tipo de propiedad (property_type).

Asimismo, se realiza un nuevo correlograma con el dataset sin faltantes.

4.1. Medidas de posición de la variable precio

Mediante la función summary se obtienen las medidad de posición de la variable precio.

Aquí, se ve que su valores pueden tomar un máximo de 6000000 y un mínimo de 6000 o, lo que es lo mismo, que su rango es de 5994000.

Los cuartiles son 119000 (primer cuartil) y 270000 (tercer cuartil), lo que indica una distancia intercuartil de 151000.

Y se observa una media de 251577 y una mediana de 170000. Es decir que la media es mayor que la mediana, lo que nos hace suponer una distribución asimétrica a derecha.

dataset_sin_NA %>% 
  select(price) %>% 
  summary()
     price        
 Min.   :   6000  
 1st Qu.: 119000  
 Median : 170000  
 Mean   : 251577  
 3rd Qu.: 270000  
 Max.   :6000000  

Se realiza un histograma que permita visualizar más claramente esta información.

dataset_sin_NA %>% 
  ggplot(aes(x=price)) + 
  geom_histogram(fill="#69b3a2", color="#e9ecef", bins = 227, binwidth = 26429) + 
  #ggtitle("Histograma de precios") + 
  labs(title='Histograma de precios',
       x = 'Precios (USD)',
       y = 'Frecuencia') +
  theme_minimal()

Como se anticipó, la variable presenta una distribución asimétrica a derecha.

Se realiza además un conteo de frecunecia relativa y frecuencia relativa acumulada con el fin de verificar los datos del gráfico. Como resultado, se encuentra que el 50% de los registros se concentra en apenas el 2% de los valores observados, y que la mayoría de estos valores se encuentran por debajo de los 350000.

frec <- count(dataset_sin_NA, price) %>% 
  mutate(frec = n/nrow(dataset_sin_NA)*100) %>% 
  arrange(desc(frec)) %>% 
  mutate(frec_acum = cumsum(frec),
         id = 1:nrow(.)) %>% 
  select(id, everything())

frec

4.2. Relación con la variable tipo de propiedad

En vistas de amplio rango de precios observado, se busca obtener métricas de posición de esta variable agrupándola por tipo de propiedad para verificar si existe algún patrón relacionado.

dataset_sin_NA %>% 
  group_by(property_type) %>% 
  summarise(mín = min(price), 
            Q1 = quantile(x = price, probs = 0.25, names = FALSE), 
            mediana = median(price),
            media = mean(price), 
            Q3 = quantile(x = price, probs = 0.75, names = FALSE), 
            máx = max(price))

De estas medidas se puede extraer que el tipo de propiedad Departamento es el que presenta el mayor rango de valores y el tipo de propiedad PH, el menor.

De hecho, si bien el tipo de propiedad Departamento muestra una media y una mediana similares a las del tipo de propiedad PH, dado que también evidencia un rango considerablemente mayor, es de esperar que las colas de su distribución sean más livianas que las de la distribución del tipo de propiedad PH.

El tipo de propiedad Casa, por su parte, posee una media y una mediana mayor a las de ambos atributos ya mencionados.

A continuación, se grafica un boxplot que facilite la visualización de estos datos. Previamente, se convierten los valores de la variable tipo de propiedad en factores.

ggdata <- dataset_sin_NA %>% 
  select(property_type, price) %>% 
  mutate(property_type = as.factor(property_type))

ggplot(ggdata, aes(x = property_type, y = price, group = property_type, fill = property_type)) +
  geom_boxplot() +
  labs(title='Precio por tipo de propiedad',
       x = 'Tipos de propiedad',
       y = 'Precio (USD)')

Si bien el boxplot confirma las suposiciones anteriores, también muestra que no es posible utilizarlo para encontrar valores atípicos, dado que hay una gran cantidad de datos que caen por fuera de los bigotes y que no se encuentran aislados o separados del resto.

4.3. Relación entre variables

Se realiza un nuevo correlograma de variable con el dataset sin datos faltantes. Esta vez se utiliza la función ggcorr de la librería GGAlly.

dataset_sin_NA %>% 
  select_if(is.numeric) %>%
  ggcorr(method = c("complete.obs","pearson"),
         low = '#e0f3db',
         mid = '#a8ddb5',
         high = '#43a2ca',
         label = TRUE,
         label_color = "black",
         nbreaks = 5,
         hjust = 0.75,
         legend.position = "left") +
  labs(title = "Propiedades en venta en CABA - Correlograma")

Este gráfico muestra una fuerte correlación positiva entre la variable superficie cubierta (surface_covered) y superficie total (surface_total). Desde el sentido común, esto resulta esperable en las propiedades de Capital Federal, dado que muchas veces ambas superficies coinciden y, cuando no lo hacen, es infrecuente que se deba a que la propiedad tiene una superficie descubierta mayor que la cubierta (caso en el que la superficie total aumentaría pero la superficie cubierta no).

También resulta esperable la correlación observada entre habitaciones y baños (bathrooms), pues suele ocurrir que, a medida que la primera aumenta, la segunda también lo hace.

Quizá sí pueda ser curioso el hecho de que, si bien estas útimas dos variables se correlacionan de manera semejante con el precio, la cantidad de baños exhibe, aunque muy leve, una mayor correlación, dando lugar a la sospecha de que la cantidad de baños aumenta en mayor medida la cotizaicón de una propiedad (en comparación con la cantidad de habitaciones).

5. Outliers

En vistas del amplio rango de la variable precio, se propone remover los valores atípicos.

Para ello, se ordenan los datos de manera creciente y se los grafica en un scatterplot con el fin de encontrar si existe algún punto de corte a partir del cual se observen outliers.

sort(dataset_sin_NA$price, decreasing = FALSE) %>% 
  plot(main = "Scatterplot de precios",
       xlab = "Índice",
       ylab = "Precios",
       col = "#43a2ca")

Si bien no parece haber un punto de corte claro, se intenta estableciendo el límite en 3000000, se filtran todos los precios menores a ese valor y se obtienen nuevas medidas de posición para evaluar el impacto de esta decisión.

dataset_sin_NA %>%
  filter(price < 3000000) %>% 
  select(price) %>% 
  summary()
     price        
 Min.   :   6000  
 1st Qu.: 119000  
 Median : 170000  
 Mean   : 245391  
 3rd Qu.: 270000  
 Max.   :2975000  

Aunque el rango de los valores obtenidos es menor, aún se observa una alta variación entre un extremo y otro. Se propone entonces eliminar los valores que se encuentren a 1.5 rangos intercuartiles del primer y tercer cuartil.

Para esto se buscan primero el rango intercuartil y los cuartiles de precios y se los asignado a una variable. Luego, se establece como corte mínimo el punto que se ubica a 1.5 veces el rango intercuartil por debajo del primer cuartil, y se hace lo mismo con el corte máximo, solo que ubicándolo por encima del tercer cuartil. Finalmente, se filtra el dataset y se almacenan solamente los registros cuyo precio esté por encima del corte mínimo y por debajo del corte máximo.

data.riq <- IQR(dataset_sin_NA$price)

cuantiles<-quantile(dataset_sin_NA$price, c(0.25, 0.5, 0.75), type = 7)

outliers_min<-as.numeric(cuantiles[1])-1.5*data.riq
outliers_max<-as.numeric(cuantiles[3])+1.5*data.riq

dataset_sin_outliers <- dataset_sin_NA %>% 
  filter(price > outliers_min,
         price < outliers_max)

dataset_sin_outliers

6. Análisis exploratorios III

Se procede a realizar el mismo análisis desarrollado en el apartado 4 pero utilizando el dataset sin faltantes y sin valores atípicos.

6.1. Medidas de posición de la variable precio

Se calculan nuevamente las medidas de posición.

dataset_sin_outliers %>% 
  select(price) %>% 
  summary()
     price       
 Min.   :  6000  
 1st Qu.:115000  
 Median :160000  
 Mean   :187634  
 3rd Qu.:240000  
 Max.   :496000  

Se observa que el valor mínimo se mantiene, mientras que el máximo ha disminuido considerablemente hasta un valor que presenta la misma cantidad de dígitos que la media.

Asimismo, la media y la mediana se han aproximado. Sin embargo, en tanto la primera constinúa siendo mayor que la segunda, cabe esperar que la distribución de la variable siga siendo asimétrica a derecha. Se confirma esto con un nuevo histograma.

dataset_sin_outliers %>% 
  ggplot(aes(x=price)) + 
  geom_histogram(fill="#69b3a2", color="#e9ecef",bins = 100) + 
  labs(title='Histograma de precios',
       subtitle = 'Valores atípicos eliminados',
       x = 'Precios (USD)',
       y = 'Frecuencia') +
  theme_minimal()

El haber filtrado los valores atípicos ha facilitado la visualización de la información, peritiendo observar más claramente el intervalo en el cual se concentra la mayor cantidad de datos.

De todos modos, se mantiene constante el hecho de que aproximadamente el 2% de los precios observados represente al 50% de las propiedades del dataset.

count(dataset_sin_outliers, price) %>% 
  mutate(frec = n/nrow(dataset_sin_outliers)*100) %>% 
  arrange(desc(frec)) %>% 
  mutate(frec_acum = cumsum(frec),
         id = 1:nrow(.)) %>% 
  select(id, everything())

6.2. Relación con la variable tipo de propiedad

Se replica la obtención de medidas de posición de la variable precio agrupada de acuerdo a la variable tipo de propiedad.

dataset_sin_outliers %>% 
  group_by(property_type) %>% 
  summarise(mín = min(price), 
            Q1 = quantile(x = price, probs = 0.25, names = FALSE), 
            mediana = median(price),
            media = mean(price), 
            Q3 = quantile(x = price, probs = 0.75, names = FALSE), 
            máx = max(price))

Si bien persiste el hecho de que el tipo de propiedad Departamento presente el mayor rango entre sus valores, ahora los tres tipos de propiedad exhiben valores máximos muy similares. Esto era esperable dada la remoción previa de valores atípicos, en la cual se estableció una cota superior e inferior y se filtraron los valores por encima y por debajo respectivamente. En tanto no existían valores menores a la cota inferior, el mínimo se mantuvo. El máximo, en cambio, se vio reducido al límite superior establecido.

La media y la mediana del tipo de propiedad Departamento sigue siendo similar a las del tipo de propiedad PH aunque en ambos casos las distancias entre estas dos medidas se ha acortado.

Esto también ha ocurrido con la media y la mediana del tipo de propiedad Casa, donde ambas medidas se han aproximado de manera más notable. Si se agrega a este dato el hecho de que, como producto de la poda de outliers, este grupo presente menor dispersión, se podría suponer que su distribución ya no debería se asimétrica o, al menos, no de forma tan marcada.

Se cotejan estos supuestos con un nuevo boxplot. Igual que se hizo anteriormente, se convierten los valores de la variable tipo de propiedad en factores para poder realizar el gráfico.

ggdata_sin_out <- dataset_sin_outliers %>% 
  mutate(property_type = as.factor(property_type))

ggplot(ggdata_sin_out, aes(x = property_type, y = price, group = property_type, fill = property_type)) +
  geom_boxplot() +
  labs(title='Precio por tipo de propiedad',
       subtitle = 'Valores atípicos eliminados',
       x = 'Tipos de propiedad',
       y = 'Precio (USD)')

6.3. Relación entre variables

Nuevamete, se grafica un correlograma que evidencie la relación entre las variables del dataset sin faltantes ni datos atípicos.

dataset_sin_outliers %>% 
  select_if(is.numeric) %>%
  ggcorr(method = c("complete.obs","pearson"),
         low = '#e0f3db',
         mid = '#a8ddb5',
         high = '#43a2ca',
         label = TRUE,
         label_color = "black",
         nbreaks = 5,
         hjust = 0.75,
         legend.position = "left") +
  labs(title = "Propiedades en venta en CABA - Correlograma",
       subtitle = "Valores atípicos eliminados")

Al igual que se observó anteriormente, la variable superficie total y superficie cubierta son las que se encuentran más correlacionadas en un sentido positivo.

Lo que ha camiado es que ya no son las variables habitaciones y baños las que le siguen en orden de correlación, sino que ahora son las correlaciones de baño y precio y de habitaciones y precio las que ocupan el segundo lugar, ambas con una correlación apenas superior a 0.6.

Esto demuestra que, si bien la remoción de datos faltantes no tuvo mayores consecunecias en el dataset utilizado, tampoco fue inocuo. Previamente a realizar este procesamiento, las variables más correlacionadas luego de superficie total y superficie cubierta eran habitaciones y baños, por un lado, y precio y baño, por otro. La correlación entre dormitorios y precio se encontraba recién en un tercer lugar.

Ahora, además de haber modificado sus órdenes, esta últimas tres correlaciones se han aproximado entre sí.

7. Modelo lineal

7.1. Modelos lineales

Se realizan dos modelos lineales con el fin de poder predecir el precio en función de otra variable.

7.1.1. Modelo 1: el precio en función de las habitaciones

En este primer modelo, se busca explicar el precio en función de la variable habitaciones (rooms). Primero se seleccionan los atributos a utilizar y luego se ajusta el modelo.

Mediante la función summary se extraen los coficientes estimados para la recta generada por el modelo:

  • 57218.9: es la ordenada al origen. Este valor indica el precio esperado cuando se toma en consideración el promedio de la variable habitaciones de todas las propiedades del dataset.
  • 49561.7: el la pendiente de la recta. Indica cuánto aumenta la varible target (en este caso, el precio) por cada unidad que aumenta la variable independiente (es decir, la cantidad de habitaciones).
data.model1 <- dataset_sin_outliers %>% 
  select(price, rooms)

mod1 <- lm(price ~ rooms, data = data.model1)

summary(mod1)

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

Residuals:
    Min      1Q  Median      3Q     Max 
-710329  -46904  -11781   33219  370504 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept)  57218.9      821.3   69.67   <2e-16 ***
rooms        49561.7      282.7  175.29   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 75360 on 46950 degrees of freedom
Multiple R-squared:  0.3956,    Adjusted R-squared:  0.3956 
F-statistic: 3.073e+04 on 1 and 46950 DF,  p-value: < 2.2e-16

7.1.2. Modelo 2: el precio en función de la superficie total

En un segundo modelo, se busca explicar el precio en función de la variable superficie total (surface_total). Nuevamente, se seleccionan los atributos a utilizar y luego se ajusta el modelo.

Del mismo modo que se hizo anteriormente, se extraen los coficientes estimados:

  • 1.872e+05: es la ordenada al origen de la recta generada. Expresa el precio de una propiedad con una superficie total promedio.
  • 5.058e+00: es la pendiente del modelo e indica cuánto aumenta el precio confome aumenta la superficie total de la propiedad.
data.model2 <- dataset_sin_outliers %>% 
  select(price, surface_total)

mod2 <- lm(price ~ surface_total, data = data.model2)

summary(mod2)

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

Residuals:
    Min      1Q  Median      3Q     Max 
-609854  -72458  -27592   52385  308340 

Coefficients:
               Estimate Std. Error t value Pr(>|t|)    
(Intercept)   1.872e+05  4.494e+02 416.568   <2e-16 ***
surface_total 5.058e+00  5.270e-01   9.599   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 96840 on 46950 degrees of freedom
Multiple R-squared:  0.001959,  Adjusted R-squared:  0.001938 
F-statistic: 92.15 on 1 and 46950 DF,  p-value: < 2.2e-16

7.2. Selección del modelo

De los modelos anteriormente ajustado, el modelo 1, que intenta explicar el precio de una propiedad en función de la cantidad de habitaciones que posee, parece ser el más adecuado.

Si bien ninguno de los modelos exhibe un residuo con una mediana 0 o cercana a 0, lo que sería deseable puesto que eso significa que la diferencia entre los datos observados y los predichos presenta una distribución simétrica y da cuenta de cuánto se ajusta el modelo a los datos, la mediana de los residuos del modelo 1 presenta una menor distancia al 0 que la mediana del modelo 2.

Además, aunque los desvíos estándares de los coeficientes del primer modelo son mayores en términos absolutos, cuando se los relaciona con los coeficientes estimados, resultan valores proporcionalmente menores a los exhibidos por el modelo 2.

Por último, el t valor observado entre la variable habitaciones y precio es mayor que el que existe entre superficie total y precio, sugiriendo que la primera relación es más estrecha.

LS0tCnRpdGxlOiAiVHJhYmFqbyBQcsOhY3RpY28gTsKwMSIKYXV0aG9yOiAiTWFjYXJlbmEgRmVybmFuZGV6IFVycXVpemEiCm91dHB1dDogCiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICBkZXB0aDogMwotLS0KCjxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+CmRpdi5tYWluLWNvbnRhaW5lciB7CiAgbWF4LXdpZHRoOiAxNjAwcHg7CiAgbWFyZ2luLWxlZnQ6IGF1dG87CiAgbWFyZ2luLXJpZ2h0OiBhdXRvOwp9CmJvZHkgewp0ZXh0LWFsaWduOiBqdXN0aWZ5fQpoMXsKICBmb250LXNpemU6IDE5cHQKfQpoMnsKICBmb250LXNpemU6IDE3cHQKfQpoM3sKICBmb250LXNpemU6IDE1cHQKfQpoNXsKICBmb250LXNpemU6IDEwcHQ7Cn0KPC9zdHlsZT4KCiMjIyMjIEVsIGPDs2RpZ28gZnVlbnRlIGRlIGVzdGUgdHJhYmFqbyBzZSBlbmN1ZW50cmEgZGlzcG9uaWJsZSBbYXF1w61dKGh0dHBzOi8vZ2l0aHViLmNvbS9hbGFkYXNwYWxhYnJhcy9FRUEyMDE5L3RyZWUvbWFzdGVyL0VFQS1UUHMvVFAwMSkKCjxici8+CgpFbiBwcmltZXIgbHVnYXIsIHNlIGltcG9ydGFuIGxhcyBsaWJyZXLDrWFzIGNvbiBsYXMgcXVlIHNlIHRyYWJhamFyw6E6IF90aWR5dmVyc2UsIGRhdGEudGFibGUsIGNvcnJyLCBnZ3Bsb3QyLCBHR2FsbHkgeSBocmJ0dGhlbWVzXy4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShkYXRhLnRhYmxlKQpsaWJyYXJ5KGNvcnJyKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoR0dhbGx5KQpsaWJyYXJ5KGhyYnJ0aGVtZXMpCmxpYnJhcnkobW9kZWxyKQpgYGAKCiMgMS4gUHJlcGFyYWNpw7NuIGRlIGxvcyBkYXRvcyBJCgojIyAxLjEuIENhcmdhIGRlbCBkYXRhc2V0CgpDb21vIHByaW1lciBwYXNvIGVuIGxhIHByZXBhcmFjacOzbiBkZSBsb3MgZGF0b3MsIHNlIHByb2NlZGUgYSBjYXJnYXIgZWwgZGF0YXNldCBjb24gbGEgZnVuY2nDs24gX2ZyZWFkXyBkZSBsYSBsaWJyZXJyw61hIF9kYXRhLnRhYmxlXyB5IHNlIGxvIGFzaWduYSBhIHVuYSB2YXJpYWJsZS4KCkEgY29udGludWFjacOzbiBzZSBwdWVkZSBvYnNlcnZhciBxdWUgZXN0ZSBkYXRhc2V0IGNvbnN0YSBkZSAzODg4OTEgcmVnaXN0cm9zIHkgMjQgY29sdW1uYXMuCgpgYGB7cn0KZGF0YXNldCA9IGZyZWFkKCJhcl9wcm9wZXJ0aWVzLmNzdiIpCgpkYXRhc2V0CmBgYAoKQ29uIGVsIGZpbiBkZSBjb25vY2VyIHVuIHBvY28gbWVqb3Igc3UgZXN0cnVjdHVyYSwgc2UgZW1wbGVhIGxhIGZ1bmNpw7NuIF9zdW1tYXJ5XyBkZSBSIGJhc2UgcGFyYSB2ZXIgcXXDqSB0aXBvIGRlIGluZm9ybWFjacOzbiBjb250aWVuZSBjYWRhIGNvbHVtbmEuCgpBcXXDrSBzZSBwdWVkZSBhcHJlY2lhciBxdWUsIGRlIGxvcyAyNCBhdHJpYnV0b3MsIDE1IGNvbnRpZW5lbiBpbmZvcm1hY2nDs24gZGUgdGlwbyAiY2FyYWN0ZXIiOyA4LCBkZSB0aXBvICJudW3DqXJpY28iIHkgdW5vIGNvbnRpZW5lIHNvbGFtZW50ZSBfbWlzc2luZyB2YWx1ZXNfIChsNikuIAoKRGUgYXF1ZWxsb3MgYXRyaWJ1dG9zIG51bcOpcmljb3MsIHNlIG9idGllbmVuIHRhbWJpw6luIGxhcyBtZWRpZGFzIGRlIHBvc2ljacOzbiAobWVkaWEsIG1lZGlhbmEsIHByaW1lciBjdWFydGlsLCB0ZXJjZXIgY3VhcnRpbCwgbcOtbmltbyB5IG3DoXhpbW8pIHkgbGEgY2FudGlkYWQgZGUgZGF0b3MgZmFsdGFudGVzIChOQXMpLgoKYGBge3J9CnN1bW1hcnkoZGF0YXNldCkKYGBgCgpEYWRvIHF1ZSBzZSBvYnNlcnZhIHVuYSBncmFuIGNhbnRpZGFkIGRlIGF0cmlidXRvcyBjYXRlZ8Ozcmljb3MsIHNlIHV0aWxpemEgbGEgZnVuY2nDs24gX2dsaW1wc2VfIGRlIGxhIGxpYnJlcsOtYSBfdGlkeXZlcnNlXywgcXVlIHBlcm1pdGUgdmlzdWFsaXphciBkZSBtb2RvIGdlbmVyYWwgcXXDqSB2YWxvcmVzIGNvbnRpZW5lIGNhZGEgYXRyaWJ1dG8KCmBgYHtyfQpnbGltcHNlKGRhdGFzZXQpCmBgYAoKRGUgbG8gb2JzZXJ2YWRvLCBzZSBwdWVkZSByZWFsaXphciBsYSBzaWd1aWVudGUgZGVzY3JpcGNpw7NuIGRlIGNhZGEgYXRyaWJ1dG86CgogLSBpZDogbsO6bWVybyBkZSBpZGVudGlmaWNhY2nDs24gZGUgbGEgcHJvcGllZGFkLiBBdHJpYnV0byBub21pbmFsLgogLSBhZF90eXBlOiBwb3NpYmxlbWVudGUgdGlwbyBkZSBhbnVuY2lvLiBBdHJpYnV0byBub21pbmFsLgogLSBzdGFydF9kYXRlOiBmZWNoYSBkZSBpbmljaW8gKGRlbCBhbnVuY2lvLCBwb3NpYmxlbWVudGUpLiBBdHJpYnV0byBvcmRpbmFsLgogLSBlbnRfZGF0ZTogZmVjaGEgZGUgZmluYWxpemFjacOzbiAoZGVsIGFudW5jaW8sIHBvc2libGVtZW50ZSkuIEF0cmlidXRvIG9yZGluYWwuCiAtIGNyZWF0ZWRfb246IGZlY2hhIGRlIGNyZWFjacOzbi4gQXRyaWJ1dG8gb3JkaW5hbC4gCiAtIGxhdDogbGF0aXR1ZCBkb25kZSBzZSBlbmN1ZW50cmEgbGEgcHJvcGllZGFkLiAKIC0gbG9uZ2l0dWQ6IGxvbmdpdHVkIGRvbmRlIHNlIGVuY3VlbnRyYSBsYSBwcm9waWVkYWQuIEF0cmlidXRvIG51bcOpcmljby4KIC0gbDE6IHBhw61zIGRvbmRlIHNlIGVuY3VlbnRyYSBsYSBwcm9waWVkYWQuIEF0cmlidXRvIG5vbWluYWwuCiAtIGwyOiBwcm92aW5jaWEgKHNpIGVzIENBQkEsIHNlIGluZGlkYSAiQ2FwaXRhbCBGZWRlcmFsIikgbyByZWdpw7NuIChzaSBlcyBHQkEpIGRvbmRlIHNlIGVuY3VlbnRyYSBsYSBwcm9waWVkYWQuIEF0cmlidXRvIG5vbWluYWwuIAogLSBsMzogY2l1ZGFkLCBwYXJ0aWRvIChzaSBlcyBHQkEpIG8gYmFycmlvIChzaSBlcyBDQUJBKSBkb25kZSBzZSBlbmN1ZW50cmEgbGEgcHJvcGllZGFkLiBBdHJpYnV0byBub21pbmFsLgogLSBsNDogYmFycmlvIChzaSBlcyBHQkEpIGRvbmRlIHNlIGVuY3VlbnRyYSBsYSBwcm9waWVkYWQuIEF0cmlidXRvIG5vbWluYWwuCiAtIGw1OiBtYXlvcmVzIGVzcGVjaWZpY2FjaW9uZXMgc29icmUgZWwgYmFycmlvLiBBdHJpYnV0byBub21pbmFsLgogLSBsNjogYXRyaWJ1dG8gY29uIHZhbG9yZXMgZmFsdGFudGVzIMO6bmljYW1lbnRlLgogLSByb29tczogY2FudGlkYWQgZGUgaGFiaXRhY2lvbmVzIHF1ZSBwb3NlZSBsYSBwcm9waWVkYWQuIEF0cmlidXRvIG51bcOpcmljby4KIC0gYmVkcm9vbXM6IGNhbnRpZGFkIGRlIGRvcm1pdG9yaW9zIHF1ZSBwb3NlZSBsYSBwcm9waWVkYWQuIEF0cmlidXRvIG51bcOpcmljby4KIC0gYmF0aHJvb21zOiBjYW50aWRhZCBkZSBiYcOxb3MgcXVlIHBvc2VlIGxhIHByb3BpZWRhZC4gQXRyaWJ1dG8gbnVtw6lyaWNvLgogLSBzdXJmYWNlX3RvdGFsOiBzdXBlcmZpY2llIHRvdGFsIGRlIGxhIHByb3BpZWRhZC4gQXRyaWJ1dG8gbnVtw6lyaWNvLgogLSBzdXJmYWNlX2NvdmVyZWQ6IHN1cGVyZmljaWUgY3ViaWVydGEgZGUgbGEgcHJvcGllZGFkLiBBdHJpYnV0byBudW3DqXJpY28uCiAtIHByaWNlOiBwcmVjaW8gZGUgbGEgcHJvcGllZGFkLiBBdHJpYnV0byBudW3DqXJpY28uCiAtIGN1cnJlbmN5OiBtb25lZGEgZW4gbGEgY3VhbCBzZSBleHByZXNhIGVsIHByZWNpby4gQXRyaWJ1dG8gbm9taW5hbC4KIC0gcHJpY2VfcGVyaW9kOiBwZXLDrW9kbyBxdWUgY29tcHJlbmRlIGVsIHByZWNpbyBkZSBsYSBwcm9waWVkYWQuIEF0cmlidXRvIG5vbWluYWwuCiAtIHRpdGxlOiB0w610dWxvIGRlbCBhbnVuY2lvLiBBdHJpYnV0byBub21pbmFsLgogLSBwcm9wZXJ0eV90eXBlOiB0aXBvIGRlIHByb3BpZWRhZC4gQXRyaWJ1dG8gbm9taW5hbC4KIC0gb3BlcmF0aW9uX3R5cGU6IHRpcG8gZGUgb3BlcmFjacOzbi4gQXRyaWJ1dG8gbm9taW5hbC4KCiMjIDEuMi4gRmlsdHJhZG8gZGVsIGRhdGFzZXQKCkRhZG8gcXVlIHNvbG8gc2UgdHJhYmFqYXLDoSBjb24gbGFzIHByb3BpZWRhZGVzIGVuIHZlbnRhIHF1ZSBjb3JyZXNwb25kYW4gYSBjYXNhcywgZGVwYXJ0YW1lbnRvcyBvIFBIcyBxdWUgc2UgZW5jdWVudHJlbiB1YmljYWRvcyBlbiBDYXBpdGFsIEZlZGVyYWwgKEFyZ2VudGluYSkgeSBjdXlvIHByZWNpbyBzZSBleHByZXNlIGVuIGTDs2xhcmVzIChVU0QpLCBzZSBwcm9jZWRlIGEgZmlsdHJhciBkZWwgZGF0YXNldCBwYXJhIHF1ZSBtdWVzdHJlIGxvcyByZWdpc3Ryb3MgZGVzZWFkb3MuCgpQYXJhIGVsbG8gc2UgdXRpbGl6YSBsYSBmdW5jacOzbiBfZmlsdGVyXyBkZWwgcGFxdWV0ZSBfZHBseXJfIGltcG9ydGFkbyBqdW50byBhIF90aWR5dmVyc2VfLgoKQXNpbWlzbW8sIHNlIGVtcGxlYSBsYSBmdW5jacOzbiBfc2VsZWN0XyBkZWwgbWlzbW8gcGFxdWV0ZSBwYXJhIHNlbGVjY2lvbmFyIGxvcyBhdHJpYnV0b3MgZGUgaW50ZXLDqXMgcGFyYSBlc3RlIHRyYWJham86IGlkLCBsMywgcm9vbXMsIGJlZHJvb21zLCBiYXRocm9vbXMsIHN1cmZhY2VfdG90YWwsIHN1cmZhY2VfY292ZXJlZCwgcHJpY2UgeSBwcm9wZXJ0eV90eXBlLgoKU2Ugb2J0aWVuZSB1bmEgZGF0YXNldCBkZSA2MTkwNSByZWdpc3Ryb3MgeSA5IGF0cmlidXRvcyB5IHNlIGxvIGFzaWduYSBhIHVuYSBudWV2YSB2YXJpYmFsZS4KCmBgYHtyfQpkYXRhc2V0X2ZpbHRyYWRvIDwtIGRhdGFzZXQgJT4lIAogIGZpbHRlcihsMSA9PSAnQXJnZW50aW5hJywKICAgICAgICAgbDIgPT0gJ0NhcGl0YWwgRmVkZXJhbCcsCiAgICAgICAgIGN1cnJlbmN5ID09ICdVU0QnLAogICAgICAgICBwcm9wZXJ0eV90eXBlICVpbiUgYygnRGVwYXJ0YW1lbnRvJywnUEgnLCdDYXNhJyksCiAgICAgICAgIG9wZXJhdGlvbl90eXBlID09ICdWZW50YScpICU+JSAKICBzZWxlY3QoaWQsIGwzLCByb29tcywgYmVkcm9vbXMsIGJhdGhyb29tcywgc3VyZmFjZV90b3RhbCwgc3VyZmFjZV9jb3ZlcmVkLCBwcmljZSwgcHJvcGVydHlfdHlwZSkKCmRhdGFzZXRfZmlsdHJhZG8KYGBgCgojIDIuIEFuw6FsaXNpcyBleHBsb3JhdG9yaW8gSSB7I2FuYWxpc2lzaX0KCiMjIDIuMS4gVmFsb3JlcyDDum5pY29zIHkgZmFsdGFudGVzCgpFbiB1biBwcmltZXIgYW7DoWxpc2lzIGV4cGxvcmF0b3Jpbywgc2UgdmVyaWZpY2EgbGEgY2FudGlkYWQgZGUgdmFsb3JlcyBmYWx0YW50ZXMgeSBkZSB2YWxvcmVzIMO6bmljb3MgZW4gY2FkYSBhdHJpYnV0by4KClBhcmEgb2J0ZW5lciBsYSBjYW50aWRhZCBkZSB2YWxvcmVzIMO6bmljb3Mgc2UgdXRpbGl6YSBsYSBmdW5jacOzbiAqc3VtbWFyaXNlX2FsbCogZGUgcGFxdWV0ZSBfZHBseXJfIHF1ZSBwZXJtaXRlIGFwbGljYXIgdW5hIG1pc21hIGZ1bmNpw7NuIGEgdG9kYXMgbGFzIGNvbHVtbmFzIGRlbCBkYXRhc2V0LiBFbiBlc3RlIGNhc28sIHNlIGxlIGFwbGljYSBsYSBmdW5jacOzbiAqbl9kaXN0aW5jdCogcXVlIGRldnVlbHZlIGxhIGNhbnRpZGFkIGRlIHZhbG9yZXMgZGlzdGludG9zIGVuIGNhZGEgYXRyaWJ1dG8uCgpgYGB7cn0KdW5pcXVlX3ZhbHVlcyA8LSBkYXRhc2V0X2ZpbHRyYWRvICU+JSAKICBzdW1tYXJpc2VfYWxsKG5fZGlzdGluY3QpCgp1bmlxdWVfdmFsdWVzCmBgYAoKRW4gZWwgY2FzbyBkZSBsb3MgdmFsb3JlcyBmYWx0YW50ZXMsIHNlIGFwbGljYSBsYSBmdW5jacOzbiBfaXMubmFfIGRlIFIgYmFzZSBzb2JyZSBlbCBkYXRhc2V0IGZpbHRyYWRvIHBhcmEgb2J0ZW5lciB1bmEgbcOhc2NhcmEgYm9vbGVhbmEgcXVlIGluZGlxdWUgc2kgZW4gY2FkYSBjZWxkYSBoYXkgdW4gTkEgbyBuby4gTHVlZ28sIHBvciBjb2x1bW5hLCBzZSBzdW1hbiBsYXMgZmlsYXMgcXVlIHPDrSB0aWVuZW4gdW4gTkEgKHF1ZSByZXN1bHRhcm9uIFRSVUUgcGFyYSBsYSBmdW5jacOzbiBhcGxpY2FkYSkuIFBhcmEgdW5hIG1lam9yIHZpc3VhbGl6YWNpw7NuLCBzZSB0cmFuc3BvbmUgbGEgaW5mb3JtYWNpw7NuIG9idGVuaWRhIHkgc2UgbGEgbXVlc3RyYSBlbiB1biBkYXRhIGZyYW1lLgoKYGBge3J9Cm5hX3ZhbHVlcyA8LSBkYXRhc2V0X2ZpbHRyYWRvICU+JSAKICBpcy5uYSgpICU+JSAKICBjb2xTdW1zKCkgJT4lIAogIHQoKSAlPiUgCiAgYXMuZGF0YS5mcmFtZSgpCgpuYV92YWx1ZXMKYGBgCgojIyAyLjIuIENvcnJlbGFjacOzbiBlbnRyZSB2YXJpYWJsZXMKCkNvbiBlbCBwcm9ww7NzaXRvIGRlIGNvdGVqYXIgc2kgZXhpc3RlIGFsZ3VuYSBjb3JyZWxhY2nDs24gZW50cmUgbGEgdmFyaWFibGVzIG51bcOpcmljYXMsIHNlIHJlYWxpemEgdW5hIG1hdHJpeiBkZSBjb3JyZWxhY2nDs24gZGUgbGFzIG1pc21hcyB5IHNlIGxhIGdyYWZpY2EgY29uIGxhIGZ1bmNpw7NuIF9ycGxvdF8gZGVsIHBhcXVldGUgX2dncGxvdDJfLgoKUGFyYSBjYWxjdWxhciBsYSBjb3JyZWxhY2nDs24gc2UgdXRpbGl6YSBlbCBjb2VmaWNpZW50ZSBkZSBjb3JyZWxhY2nDs24gZGUgUGVhcnNvbiB5IHNlIHRvbWFuIGVuIGN1ZW50YSBzb2xhbWVudGUgbG9zIHJlZ2lzdHJvcyBjb21wbGV0b3MuIEVzdG8gw7psdGltbyBzZSByZWFsaXphIG1lZGlhbnRlIGVsIHBhcsOhbWV0cm8gImNvbXBsZXRlLm9icyIuCgpgYGB7cn0KZGF0YXNldF9maWx0cmFkbyAlPiUgCiAgc2VsZWN0X2lmKGlzLm51bWVyaWMpICU+JSAKICBjb3JyZWxhdGUodXNlPSJjb21wbGV0ZS5vYnMiKSAlPiUgCiAgc2hhdmUoKSAlPiUgCiAgZmFzaGlvbigpCmBgYAoKYGBge3J9CmRhdGFzZXRfZmlsdHJhZG8gJT4lIAogIHNlbGVjdF9pZihpcy5udW1lcmljKSAlPiUgCiAgY29ycmVsYXRlKHVzZT0iY29tcGxldGUub2JzIikgICU+JSAKICBycGxvdChjb2xvdXJzID0gYygnIzAwMzM2NicsJyMzMzk5RkYnLCcjRkYzMzAwJykpICsKICBnZW9tX3BvaW50KHNpemU9NSkgKwogIGxhYnModGl0bGUgPSAnQ29ycmVsb2dyYW1hIGRlIHZhcmlhYmxlcycpICsKICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDQ1LCBoanVzdD0xKSkKYGBgCgojIDMuIFByZXBhcmFjaW9uIGRlIGxvcyBkYXRvcyBJSSB7I3ByZXBhcmFjaW9uaWl9CgpFbCBhbsOhbGlzaXMgZXhwbG9yYXRvcmlvIHJlYWxpemFkbyBlbiBbZWwgYXBhcnRhZG8gYW50ZXJpb3JdKCNhbmFsaXNpc2kpIHNlIGhhbGzDsyBxdWUgbGFzIHZhcmlhYmxlcyBfZG9ybWl0b3Jpb3NfIChiZWRyb29tcykgeSBfaGFiaXRhY2lvbmVzXyAocm9vbXMpIHByZXNlbnRhbiB1bmEgYWx0YSBjb3JyZWxhY2lvbmFkYXMgcG9zaXRpdmEgKGNvbiB1biB2YWxvciBkZSAwLjkyKS4gRXN0byBzaWduaWZpY2EgcXVlIGN1YW5kbyBsb3MgdmFsb3JlcyBkZSB1bmEgYXVtZW50YW4sIGxvcyBkZSBsYSBvdHJhIHRhbWJpw6luIGxvIGhhY2VuLiBEZSBlc3RlIG1vZG8sIGxhIGluZm9ybWFjacOzbiBxdWUgb2ZyZWNlbiBlcywgZW4gY2llcnRvIHNlbnRpZG8sIHJlZHVuZGFudGUuIEVzdG8gcGVybWl0ZSBvcHRhciBwb3IgcmVtb3ZlciB1bmEgZGUgZGljaGFzIHZhcmlhYmxlcyB5LCBkZWJpZG8gYSBxdWUgX2Rvcm1pdG9yaW9zXyBkZW1vc3Ryw7MgdGVuZXIgZ3JhbiBjYW50aWRhZCBkZSB2YWxvcmVzIGZhbHRhbnRlcywgc2UgcXVpdGEgZXN0YSB2YXJpYWJsZSBkZWwgZGF0YXNldC4KClBhcmEgbm8gdGVuZXIgbWF5b3JlcyBpbmNvbnZlbmllbnRlcyBjb24gZWwgcmVzdG8gZGUgbG9zIHJlZ2lzdHJvcywgc2UgZGVjaWRlIGFzaW1pc21vIHF1aXRhciBhcXVlbGxvcyBxdWUgY29udGVuZ2FuIHZhbG9yZXMgZmFsdGFudGVzLCBwcmVzZXJ2YW5kbyBzb2xhbWVudGUgbG9zIHJlZ2lzdHJvcyBjb21wbGV0b3MuCgpDb21vIHJlc3VsdGFkbywgc2Ugb2J0aWVuZSB1biBkYXRhc2V0IGNvbiA4IHZhcmlhYmxlcyB5IDUxMjEwIHJlZ2lzdHJvcy4KCmBgYHtyfQpkYXRhc2V0X3Npbl9OQSA8LSBkYXRhc2V0X2ZpbHRyYWRvICU+JSAKICBzZWxlY3QoLWMoYmVkcm9vbXMpKSAlPiUgCiAgbmEub21pdCgpCgpkYXRhc2V0X3Npbl9OQQpgYGAKCiMgNC5BbsOhbGlzaXMgZXhwbG9yYXRvcmlvIElJIHsjYW5hbGlzaXNpaX0KClNlIHJlYWxpemEgdW4gbnVldm8gYW7DoWxpc2lzIGV4cGxvcmF0b3JpbywgYWhvcmEgdXRpbGl6YW5kbyBlbCBkYXRhc2V0IHNpbiBmYWx0YW50ZXMsIG9idGVuaWRvIGVuIFtsYSBzZWNjacOzbiAzXSgjcHJlcGFyYWNpb25paSkuIAoKQSBjb250aW51YWNpw7NuLCBzZSBvZnJlY2UgdW5hIGV4cGxvcmFjacOzbiBtw6FzIGRldGFsbGFkYSBzb2JyZSBsYSB2YXJpYWJsZSBfcHJlY2lvXyAocHJpY2UpIHkgc3UgcmVsYWNpw7NuIGNvbiBlbCBhdHJpYnV0byBfdGlwbyBkZSBwcm9waWVkYWRfIChwcm9wZXJ0eV90eXBlKS4KCkFzaW1pc21vLCBzZSByZWFsaXphIHVuIG51ZXZvIGNvcnJlbG9ncmFtYSBjb24gZWwgZGF0YXNldCBzaW4gZmFsdGFudGVzLgoKIyMgNC4xLiBNZWRpZGFzIGRlIHBvc2ljacOzbiBkZSBsYSB2YXJpYWJsZSBfcHJlY2lvXwoKTWVkaWFudGUgbGEgZnVuY2nDs24gX3N1bW1hcnlfIHNlIG9idGllbmVuIGxhcyBtZWRpZGFkIGRlIHBvc2ljacOzbiBkZSBsYSB2YXJpYWJsZSBfcHJlY2lvXy4gCgpBcXXDrSwgc2UgdmUgcXVlIHN1IHZhbG9yZXMgcHVlZGVuIHRvbWFyIHVuIG3DoXhpbW8gZGUgNjAwMDAwMCB5IHVuIG3DrW5pbW8gZGUgNjAwMCBvLCBsbyBxdWUgZXMgbG8gbWlzbW8sIHF1ZSBzdSByYW5nbyBlcyBkZSA1OTk0MDAwLiAKCkxvcyBjdWFydGlsZXMgc29uIDExOTAwMCAocHJpbWVyIGN1YXJ0aWwpIHkgMjcwMDAwICh0ZXJjZXIgY3VhcnRpbCksIGxvIHF1ZSBpbmRpY2EgdW5hIGRpc3RhbmNpYSBpbnRlcmN1YXJ0aWwgZGUgMTUxMDAwLgoKWSBzZSBvYnNlcnZhIHVuYSBtZWRpYSBkZSAyNTE1NzcgeSB1bmEgbWVkaWFuYSBkZSAxNzAwMDAuIEVzIGRlY2lyIHF1ZSBsYSBtZWRpYSBlcyBtYXlvciBxdWUgbGEgbWVkaWFuYSwgbG8gcXVlIG5vcyBoYWNlIHN1cG9uZXIgdW5hIGRpc3RyaWJ1Y2nDs24gYXNpbcOpdHJpY2EgYSBkZXJlY2hhLgoKYGBge3J9CmRhdGFzZXRfc2luX05BICU+JSAKICBzZWxlY3QocHJpY2UpICU+JSAKICBzdW1tYXJ5KCkKYGBgCgpTZSByZWFsaXphIHVuIGhpc3RvZ3JhbWEgcXVlIHBlcm1pdGEgdmlzdWFsaXphciBtw6FzIGNsYXJhbWVudGUgZXN0YSBpbmZvcm1hY2nDs24uCgpgYGB7cn0KZGF0YXNldF9zaW5fTkEgJT4lIAogIGdncGxvdChhZXMoeD1wcmljZSkpICsgCiAgZ2VvbV9oaXN0b2dyYW0oZmlsbD0iIzY5YjNhMiIsIGNvbG9yPSIjZTllY2VmIiwgYmlucyA9IDIyNywgYmlud2lkdGggPSAyNjQyOSkgKyAKICAjZ2d0aXRsZSgiSGlzdG9ncmFtYSBkZSBwcmVjaW9zIikgKyAKICBsYWJzKHRpdGxlPSdIaXN0b2dyYW1hIGRlIHByZWNpb3MnLAogICAgICAgeCA9ICdQcmVjaW9zIChVU0QpJywKICAgICAgIHkgPSAnRnJlY3VlbmNpYScpICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgpDb21vIHNlIGFudGljaXDDsywgbGEgdmFyaWFibGUgcHJlc2VudGEgdW5hIGRpc3RyaWJ1Y2nDs24gYXNpbcOpdHJpY2EgYSBkZXJlY2hhLiAKClNlIHJlYWxpemEgYWRlbcOhcyB1biBjb250ZW8gZGUgZnJlY3VuZWNpYSByZWxhdGl2YSB5IGZyZWN1ZW5jaWEgcmVsYXRpdmEgYWN1bXVsYWRhIGNvbiBlbCBmaW4gZGUgdmVyaWZpY2FyIGxvcyBkYXRvcyBkZWwgZ3LDoWZpY28uIENvbW8gcmVzdWx0YWRvLCBzZSBlbmN1ZW50cmEgcXVlIGVsIDUwJSBkZSBsb3MgcmVnaXN0cm9zIHNlIGNvbmNlbnRyYSBlbiBhcGVuYXMgZWwgMiUgZGUgbG9zIHZhbG9yZXMgb2JzZXJ2YWRvcywgeSBxdWUgbGEgbWF5b3LDrWEgZGUgZXN0b3MgdmFsb3JlcyBzZSBlbmN1ZW50cmFuIHBvciBkZWJham8gZGUgbG9zIDM1MDAwMC4KCmBgYHtyfQpmcmVjIDwtIGNvdW50KGRhdGFzZXRfc2luX05BLCBwcmljZSkgJT4lIAogIG11dGF0ZShmcmVjID0gbi9ucm93KGRhdGFzZXRfc2luX05BKSoxMDApICU+JSAKICBhcnJhbmdlKGRlc2MoZnJlYykpICU+JSAKICBtdXRhdGUoZnJlY19hY3VtID0gY3Vtc3VtKGZyZWMpLAogICAgICAgICBpZCA9IDE6bnJvdyguKSkgJT4lIAogIHNlbGVjdChpZCwgZXZlcnl0aGluZygpKQoKZnJlYwpgYGAKCiMjIDQuMi4gUmVsYWNpw7NuIGNvbiBsYSB2YXJpYWJsZSBfdGlwbyBkZSBwcm9waWVkYWRfCgpFbiB2aXN0YXMgZGUgYW1wbGlvIHJhbmdvIGRlIHByZWNpb3Mgb2JzZXJ2YWRvLCBzZSBidXNjYSBvYnRlbmVyIG3DqXRyaWNhcyBkZSBwb3NpY2nDs24gZGUgZXN0YSB2YXJpYWJsZSBhZ3J1cMOhbmRvbGEgcG9yIHRpcG8gZGUgcHJvcGllZGFkIHBhcmEgdmVyaWZpY2FyIHNpIGV4aXN0ZSBhbGfDum4gcGF0csOzbiByZWxhY2lvbmFkby4KIApgYGB7cn0KZGF0YXNldF9zaW5fTkEgJT4lIAogIGdyb3VwX2J5KHByb3BlcnR5X3R5cGUpICU+JSAKICBzdW1tYXJpc2UobcOtbiA9IG1pbihwcmljZSksIAogICAgICAgICAgICBRMSA9IHF1YW50aWxlKHggPSBwcmljZSwgcHJvYnMgPSAwLjI1LCBuYW1lcyA9IEZBTFNFKSwgCiAgICAgICAgICAgIG1lZGlhbmEgPSBtZWRpYW4ocHJpY2UpLAogICAgICAgICAgICBtZWRpYSA9IG1lYW4ocHJpY2UpLCAKICAgICAgICAgICAgUTMgPSBxdWFudGlsZSh4ID0gcHJpY2UsIHByb2JzID0gMC43NSwgbmFtZXMgPSBGQUxTRSksIAogICAgICAgICAgICBtw6F4ID0gbWF4KHByaWNlKSkKYGBgCgpEZSBlc3RhcyBtZWRpZGFzIHNlIHB1ZWRlIGV4dHJhZXIgcXVlIGVsIHRpcG8gZGUgcHJvcGllZGFkIERlcGFydGFtZW50byBlcyBlbCBxdWUgcHJlc2VudGEgZWwgbWF5b3IgcmFuZ28gZGUgdmFsb3JlcyB5IGVsIHRpcG8gZGUgcHJvcGllZGFkIFBILCBlbCBtZW5vci4KCkRlIGhlY2hvLCBzaSBiaWVuIGVsIHRpcG8gZGUgcHJvcGllZGFkIERlcGFydGFtZW50byBtdWVzdHJhIHVuYSBtZWRpYSB5IHVuYSBtZWRpYW5hIHNpbWlsYXJlcyBhIGxhcyBkZWwgdGlwbyBkZSBwcm9waWVkYWQgUEgsIGRhZG8gcXVlIHRhbWJpw6luIGV2aWRlbmNpYSB1biByYW5nbyBjb25zaWRlcmFibGVtZW50ZSBtYXlvciwgZXMgZGUgZXNwZXJhciBxdWUgbGFzIGNvbGFzIGRlIHN1IGRpc3RyaWJ1Y2nDs24gc2VhbiBtw6FzIGxpdmlhbmFzIHF1ZSBsYXMgZGUgbGEgZGlzdHJpYnVjacOzbiBkZWwgdGlwbyBkZSBwcm9waWVkYWQgUEguCgpFbCB0aXBvIGRlIHByb3BpZWRhZCBDYXNhLCBwb3Igc3UgcGFydGUsIHBvc2VlIHVuYSBtZWRpYSB5IHVuYSBtZWRpYW5hIG1heW9yIGEgbGFzIGRlIGFtYm9zIGF0cmlidXRvcyB5YSBtZW5jaW9uYWRvcy4KCkEgY29udGludWFjacOzbiwgc2UgZ3JhZmljYSB1biBib3hwbG90IHF1ZSBmYWNpbGl0ZSBsYSB2aXN1YWxpemFjacOzbiBkZSBlc3RvcyBkYXRvcy4gUHJldmlhbWVudGUsIHNlIGNvbnZpZXJ0ZW4gbG9zIHZhbG9yZXMgZGUgbGEgdmFyaWFibGUgX3RpcG8gZGUgcHJvcGllZGFkXyBlbiBmYWN0b3Jlcy4KCmBgYHtyfQpnZ2RhdGEgPC0gZGF0YXNldF9zaW5fTkEgJT4lIAogIHNlbGVjdChwcm9wZXJ0eV90eXBlLCBwcmljZSkgJT4lIAogIG11dGF0ZShwcm9wZXJ0eV90eXBlID0gYXMuZmFjdG9yKHByb3BlcnR5X3R5cGUpKQoKZ2dwbG90KGdnZGF0YSwgYWVzKHggPSBwcm9wZXJ0eV90eXBlLCB5ID0gcHJpY2UsIGdyb3VwID0gcHJvcGVydHlfdHlwZSwgZmlsbCA9IHByb3BlcnR5X3R5cGUpKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIGxhYnModGl0bGU9J1ByZWNpbyBwb3IgdGlwbyBkZSBwcm9waWVkYWQnLAogICAgICAgeCA9ICdUaXBvcyBkZSBwcm9waWVkYWQnLAogICAgICAgeSA9ICdQcmVjaW8gKFVTRCknKQpgYGAKClNpIGJpZW4gZWwgYm94cGxvdCBjb25maXJtYSBsYXMgc3Vwb3NpY2lvbmVzIGFudGVyaW9yZXMsIHRhbWJpw6luIG11ZXN0cmEgcXVlIG5vIGVzIHBvc2libGUgdXRpbGl6YXJsbyBwYXJhIGVuY29udHJhciB2YWxvcmVzIGF0w61waWNvcywgZGFkbyBxdWUgaGF5IHVuYSBncmFuIGNhbnRpZGFkIGRlIGRhdG9zIHF1ZSBjYWVuIHBvciBmdWVyYSBkZSBsb3MgYmlnb3RlcyB5IHF1ZSBubyBzZSBlbmN1ZW50cmFuIGFpc2xhZG9zIG8gc2VwYXJhZG9zIGRlbCByZXN0by4KCiMjIDQuMy4gUmVsYWNpw7NuIGVudHJlIHZhcmlhYmxlcwoKU2UgcmVhbGl6YSB1biBudWV2byBjb3JyZWxvZ3JhbWEgZGUgdmFyaWFibGUgY29uIGVsIGRhdGFzZXQgc2luIGRhdG9zIGZhbHRhbnRlcy4gRXN0YSB2ZXogc2UgdXRpbGl6YSBsYSBmdW5jacOzbiBfZ2djb3JyXyBkZSBsYSBsaWJyZXLDrWEgX0dHQWxseV8uCgpgYGB7cn0KZGF0YXNldF9zaW5fTkEgJT4lIAogIHNlbGVjdF9pZihpcy5udW1lcmljKSAlPiUKICBnZ2NvcnIobWV0aG9kID0gYygiY29tcGxldGUub2JzIiwicGVhcnNvbiIpLAogICAgICAgICBsb3cgPSAnI2UwZjNkYicsCiAgICAgICAgIG1pZCA9ICcjYThkZGI1JywKICAgICAgICAgaGlnaCA9ICcjNDNhMmNhJywKICAgICAgICAgbGFiZWwgPSBUUlVFLAogICAgICAgICBsYWJlbF9jb2xvciA9ICJibGFjayIsCiAgICAgICAgIG5icmVha3MgPSA1LAogICAgICAgICBoanVzdCA9IDAuNzUsCiAgICAgICAgIGxlZ2VuZC5wb3NpdGlvbiA9ICJsZWZ0IikgKwogIGxhYnModGl0bGUgPSAiUHJvcGllZGFkZXMgZW4gdmVudGEgZW4gQ0FCQSAtIENvcnJlbG9ncmFtYSIpCmBgYAoKRXN0ZSBncsOhZmljbyBtdWVzdHJhIHVuYSBmdWVydGUgY29ycmVsYWNpw7NuIHBvc2l0aXZhIGVudHJlIGxhIHZhcmlhYmxlIF9zdXBlcmZpY2llIGN1YmllcnRhXyAoc3VyZmFjZV9jb3ZlcmVkKSB5IF9zdXBlcmZpY2llIHRvdGFsXyAoc3VyZmFjZV90b3RhbCkuIERlc2RlIGVsIHNlbnRpZG8gY29tw7puLCBlc3RvIHJlc3VsdGEgZXNwZXJhYmxlIGVuIGxhcyBwcm9waWVkYWRlcyBkZSBDYXBpdGFsIEZlZGVyYWwsIGRhZG8gcXVlIG11Y2hhcyB2ZWNlcyBhbWJhcyBzdXBlcmZpY2llcyBjb2luY2lkZW4geSwgY3VhbmRvIG5vIGxvIGhhY2VuLCBlcyBpbmZyZWN1ZW50ZSBxdWUgc2UgZGViYSBhIHF1ZSBsYSBwcm9waWVkYWQgdGllbmUgdW5hIHN1cGVyZmljaWUgZGVzY3ViaWVydGEgbWF5b3IgcXVlIGxhIGN1YmllcnRhIChjYXNvIGVuIGVsIHF1ZSBsYSBzdXBlcmZpY2llIHRvdGFsIGF1bWVudGFyw61hIHBlcm8gbGEgc3VwZXJmaWNpZSBjdWJpZXJ0YSBubykuCgpUYW1iacOpbiByZXN1bHRhIGVzcGVyYWJsZSBsYSBjb3JyZWxhY2nDs24gb2JzZXJ2YWRhIGVudHJlIF9oYWJpdGFjaW9uZXNfIHkgX2Jhw7Fvc18gKGJhdGhyb29tcyksIHB1ZXMgc3VlbGUgb2N1cnJpciBxdWUsIGEgbWVkaWRhIHF1ZSBsYSBwcmltZXJhIGF1bWVudGEsIGxhIHNlZ3VuZGEgdGFtYmnDqW4gbG8gaGFjZS4KClF1aXrDoSBzw60gcHVlZGEgc2VyIGN1cmlvc28gZWwgaGVjaG8gZGUgcXVlLCBzaSBiaWVuIGVzdGFzIMO6dGltYXMgZG9zIHZhcmlhYmxlcyBzZSBjb3JyZWxhY2lvbmFuIGRlIG1hbmVyYSBzZW1lamFudGUgY29uIGVsIHByZWNpbywgbGEgY2FudGlkYWQgZGUgYmHDsW9zIGV4aGliZSwgYXVucXVlIG11eSBsZXZlLCB1bmEgbWF5b3IgY29ycmVsYWNpw7NuLCBkYW5kbyBsdWdhciBhIGxhIHNvc3BlY2hhIGRlIHF1ZSBsYSBjYW50aWRhZCBkZSBiYcOxb3MgYXVtZW50YSBlbiBtYXlvciBtZWRpZGEgbGEgY290aXphaWPDs24gZGUgdW5hIHByb3BpZWRhZCAoZW4gY29tcGFyYWNpw7NuIGNvbiBsYSBjYW50aWRhZCBkZSBoYWJpdGFjaW9uZXMpLgoKIyA1LiBPdXRsaWVycwoKRW4gdmlzdGFzIGRlbCBhbXBsaW8gcmFuZ28gZGUgbGEgdmFyaWFibGUgX3ByZWNpb18sIHNlIHByb3BvbmUgcmVtb3ZlciBsb3MgdmFsb3JlcyBhdMOtcGljb3MuCgpQYXJhIGVsbG8sIHNlIG9yZGVuYW4gbG9zIGRhdG9zIGRlIG1hbmVyYSBjcmVjaWVudGUgeSBzZSBsb3MgZ3JhZmljYSBlbiB1biBzY2F0dGVycGxvdCBjb24gZWwgZmluIGRlIGVuY29udHJhciBzaSBleGlzdGUgYWxnw7puIHB1bnRvIGRlIGNvcnRlIGEgcGFydGlyIGRlbCBjdWFsIHNlIG9ic2VydmVuIG91dGxpZXJzLgogCmBgYHtyfQpzb3J0KGRhdGFzZXRfc2luX05BJHByaWNlLCBkZWNyZWFzaW5nID0gRkFMU0UpICU+JSAKICBwbG90KG1haW4gPSAiU2NhdHRlcnBsb3QgZGUgcHJlY2lvcyIsCiAgICAgICB4bGFiID0gIsONbmRpY2UiLAogICAgICAgeWxhYiA9ICJQcmVjaW9zIiwKICAgICAgIGNvbCA9ICIjNDNhMmNhIikKYGBgCgpTaSBiaWVuIG5vIHBhcmVjZSBoYWJlciB1biBwdW50byBkZSBjb3J0ZSBjbGFybywgc2UgaW50ZW50YSBlc3RhYmxlY2llbmRvIGVsIGzDrW1pdGUgZW4gMzAwMDAwMCwgc2UgZmlsdHJhbiB0b2RvcyBsb3MgcHJlY2lvcyBtZW5vcmVzIGEgZXNlIHZhbG9yIHkgc2Ugb2J0aWVuZW4gbnVldmFzIG1lZGlkYXMgZGUgcG9zaWNpw7NuIHBhcmEgZXZhbHVhciBlbCBpbXBhY3RvIGRlIGVzdGEgZGVjaXNpw7NuLgoKYGBge3J9CmRhdGFzZXRfc2luX05BICU+JQogIGZpbHRlcihwcmljZSA8IDMwMDAwMDApICU+JSAKICBzZWxlY3QocHJpY2UpICU+JSAKICBzdW1tYXJ5KCkKYGBgCgpBdW5xdWUgZWwgcmFuZ28gZGUgbG9zIHZhbG9yZXMgb2J0ZW5pZG9zIGVzIG1lbm9yLCBhw7puIHNlIG9ic2VydmEgdW5hIGFsdGEgdmFyaWFjacOzbiBlbnRyZSB1biBleHRyZW1vIHkgb3Ryby4gU2UgcHJvcG9uZSBlbnRvbmNlcyBlbGltaW5hciBsb3MgdmFsb3JlcyBxdWUgc2UgZW5jdWVudHJlbiBhIDEuNSByYW5nb3MgaW50ZXJjdWFydGlsZXMgZGVsIHByaW1lciB5IHRlcmNlciBjdWFydGlsLgoKUGFyYSBlc3RvIHNlIGJ1c2NhbiBwcmltZXJvIGVsIHJhbmdvIGludGVyY3VhcnRpbCB5IGxvcyBjdWFydGlsZXMgZGUgX3ByZWNpb3NfIHkgc2UgbG9zIGFzaWduYWRvIGEgdW5hIHZhcmlhYmxlLiBMdWVnbywgc2UgZXN0YWJsZWNlIGNvbW8gY29ydGUgbcOtbmltbyBlbCBwdW50byBxdWUgc2UgdWJpY2EgYSAxLjUgdmVjZXMgZWwgcmFuZ28gaW50ZXJjdWFydGlsIHBvciBkZWJham8gZGVsIHByaW1lciBjdWFydGlsLCB5IHNlIGhhY2UgbG8gbWlzbW8gY29uIGVsIGNvcnRlIG3DoXhpbW8sIHNvbG8gcXVlIHViaWPDoW5kb2xvIHBvciBlbmNpbWEgZGVsIHRlcmNlciBjdWFydGlsLiBGaW5hbG1lbnRlLCBzZSBmaWx0cmEgZWwgZGF0YXNldCB5IHNlIGFsbWFjZW5hbiBzb2xhbWVudGUgbG9zIHJlZ2lzdHJvcyBjdXlvIHByZWNpbyBlc3TDqSBwb3IgZW5jaW1hIGRlbCBjb3J0ZSBtw61uaW1vIHkgcG9yIGRlYmFqbyBkZWwgY29ydGUgbcOheGltby4KCmBgYHtyfQpkYXRhLnJpcSA8LSBJUVIoZGF0YXNldF9zaW5fTkEkcHJpY2UpCgpjdWFudGlsZXM8LXF1YW50aWxlKGRhdGFzZXRfc2luX05BJHByaWNlLCBjKDAuMjUsIDAuNSwgMC43NSksIHR5cGUgPSA3KQoKb3V0bGllcnNfbWluPC1hcy5udW1lcmljKGN1YW50aWxlc1sxXSktMS41KmRhdGEucmlxCm91dGxpZXJzX21heDwtYXMubnVtZXJpYyhjdWFudGlsZXNbM10pKzEuNSpkYXRhLnJpcQoKZGF0YXNldF9zaW5fb3V0bGllcnMgPC0gZGF0YXNldF9zaW5fTkEgJT4lIAogIGZpbHRlcihwcmljZSA+IG91dGxpZXJzX21pbiwKICAgICAgICAgcHJpY2UgPCBvdXRsaWVyc19tYXgpCgpkYXRhc2V0X3Npbl9vdXRsaWVycwpgYGAKCiMgNi4gQW7DoWxpc2lzIGV4cGxvcmF0b3Jpb3MgSUlJCgpTZSBwcm9jZWRlIGEgcmVhbGl6YXIgZWwgbWlzbW8gYW7DoWxpc2lzIGRlc2Fycm9sbGFkbyBlbiBlbCBbYXBhcnRhZG8gNF0oI2FuYWxpc2lzaWkpIHBlcm8gdXRpbGl6YW5kbyBlbCBkYXRhc2V0IHNpbiBmYWx0YW50ZXMgeSBzaW4gdmFsb3JlcyBhdMOtcGljb3MuCgojIyA2LjEuIE1lZGlkYXMgZGUgcG9zaWNpw7NuIGRlIGxhIHZhcmlhYmxlIF9wcmVjaW9fCgpTZSBjYWxjdWxhbiBudWV2YW1lbnRlIGxhcyBtZWRpZGFzIGRlIHBvc2ljacOzbi4gCgpgYGB7cn0KZGF0YXNldF9zaW5fb3V0bGllcnMgJT4lIAogIHNlbGVjdChwcmljZSkgJT4lIAogIHN1bW1hcnkoKQpgYGAKClNlIG9ic2VydmEgcXVlIGVsIHZhbG9yIG3DrW5pbW8gc2UgbWFudGllbmUsIG1pZW50cmFzIHF1ZSBlbCBtw6F4aW1vIGhhIGRpc21pbnVpZG8gY29uc2lkZXJhYmxlbWVudGUgaGFzdGEgdW4gdmFsb3IgcXVlIHByZXNlbnRhIGxhIG1pc21hIGNhbnRpZGFkIGRlIGTDrWdpdG9zIHF1ZSBsYSBtZWRpYS4KCkFzaW1pc21vLCBsYSBtZWRpYSB5IGxhIG1lZGlhbmEgc2UgaGFuIGFwcm94aW1hZG8uIFNpbiBlbWJhcmdvLCBlbiB0YW50byBsYSBwcmltZXJhIGNvbnN0aW7DumEgc2llbmRvIG1heW9yIHF1ZSBsYSBzZWd1bmRhLCBjYWJlIGVzcGVyYXIgcXVlIGxhIGRpc3RyaWJ1Y2nDs24gZGUgbGEgdmFyaWFibGUgc2lnYSBzaWVuZG8gYXNpbcOpdHJpY2EgYSBkZXJlY2hhLiBTZSBjb25maXJtYSBlc3RvIGNvbiB1biBudWV2byBoaXN0b2dyYW1hLgoKYGBge3J9IApkYXRhc2V0X3Npbl9vdXRsaWVycyAlPiUgCiAgZ2dwbG90KGFlcyh4PXByaWNlKSkgKyAKICBnZW9tX2hpc3RvZ3JhbShmaWxsPSIjNjliM2EyIiwgY29sb3I9IiNlOWVjZWYiLGJpbnMgPSAxMDApICsgCiAgbGFicyh0aXRsZT0nSGlzdG9ncmFtYSBkZSBwcmVjaW9zJywKICAgICAgIHN1YnRpdGxlID0gJ1ZhbG9yZXMgYXTDrXBpY29zIGVsaW1pbmFkb3MnLAogICAgICAgeCA9ICdQcmVjaW9zIChVU0QpJywKICAgICAgIHkgPSAnRnJlY3VlbmNpYScpICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgpFbCBoYWJlciBmaWx0cmFkbyBsb3MgdmFsb3JlcyBhdMOtcGljb3MgaGEgZmFjaWxpdGFkbyBsYSB2aXN1YWxpemFjacOzbiBkZSBsYSBpbmZvcm1hY2nDs24sIHBlcml0aWVuZG8gb2JzZXJ2YXIgbcOhcyBjbGFyYW1lbnRlIGVsIGludGVydmFsbyBlbiBlbCBjdWFsIHNlIGNvbmNlbnRyYSBsYSBtYXlvciBjYW50aWRhZCBkZSBkYXRvcy4KCkRlIHRvZG9zIG1vZG9zLCBzZSBtYW50aWVuZSBjb25zdGFudGUgZWwgaGVjaG8gZGUgcXVlIGFwcm94aW1hZGFtZW50ZSBlbCAyJSBkZSBsb3MgcHJlY2lvcyBvYnNlcnZhZG9zIHJlcHJlc2VudGUgYWwgNTAlIGRlIGxhcyBwcm9waWVkYWRlcyBkZWwgZGF0YXNldC4KCmBgYHtyfQpjb3VudChkYXRhc2V0X3Npbl9vdXRsaWVycywgcHJpY2UpICU+JSAKICBtdXRhdGUoZnJlYyA9IG4vbnJvdyhkYXRhc2V0X3Npbl9vdXRsaWVycykqMTAwKSAlPiUgCiAgYXJyYW5nZShkZXNjKGZyZWMpKSAlPiUgCiAgbXV0YXRlKGZyZWNfYWN1bSA9IGN1bXN1bShmcmVjKSwKICAgICAgICAgaWQgPSAxOm5yb3coLikpICU+JSAKICBzZWxlY3QoaWQsIGV2ZXJ5dGhpbmcoKSkKYGBgCgojIyA2LjIuIFJlbGFjacOzbiBjb24gbGEgdmFyaWFibGUgX3RpcG8gZGUgcHJvcGllZGFkXwoKU2UgcmVwbGljYSBsYSBvYnRlbmNpw7NuIGRlIG1lZGlkYXMgZGUgcG9zaWNpw7NuIGRlIGxhIHZhcmlhYmxlIF9wcmVjaW9fIGFncnVwYWRhIGRlIGFjdWVyZG8gYSBsYSB2YXJpYWJsZSBfdGlwbyBkZSBwcm9waWVkYWRfLgoKYGBge3J9CmRhdGFzZXRfc2luX291dGxpZXJzICU+JSAKICBncm91cF9ieShwcm9wZXJ0eV90eXBlKSAlPiUgCiAgc3VtbWFyaXNlKG3DrW4gPSBtaW4ocHJpY2UpLCAKICAgICAgICAgICAgUTEgPSBxdWFudGlsZSh4ID0gcHJpY2UsIHByb2JzID0gMC4yNSwgbmFtZXMgPSBGQUxTRSksIAogICAgICAgICAgICBtZWRpYW5hID0gbWVkaWFuKHByaWNlKSwKICAgICAgICAgICAgbWVkaWEgPSBtZWFuKHByaWNlKSwgCiAgICAgICAgICAgIFEzID0gcXVhbnRpbGUoeCA9IHByaWNlLCBwcm9icyA9IDAuNzUsIG5hbWVzID0gRkFMU0UpLCAKICAgICAgICAgICAgbcOheCA9IG1heChwcmljZSkpCmBgYAoKU2kgYmllbiBwZXJzaXN0ZSBlbCBoZWNobyBkZSBxdWUgZWwgdGlwbyBkZSBwcm9waWVkYWQgRGVwYXJ0YW1lbnRvIHByZXNlbnRlIGVsIG1heW9yIHJhbmdvIGVudHJlIHN1cyB2YWxvcmVzLCBhaG9yYSBsb3MgdHJlcyB0aXBvcyBkZSBwcm9waWVkYWQgZXhoaWJlbiB2YWxvcmVzIG3DoXhpbW9zIG11eSBzaW1pbGFyZXMuIEVzdG8gZXJhIGVzcGVyYWJsZSBkYWRhIGxhIHJlbW9jacOzbiBwcmV2aWEgZGUgdmFsb3JlcyBhdMOtcGljb3MsIGVuIGxhIGN1YWwgc2UgZXN0YWJsZWNpw7MgdW5hIGNvdGEgc3VwZXJpb3IgZSBpbmZlcmlvciB5IHNlIGZpbHRyYXJvbiBsb3MgdmFsb3JlcyBwb3IgZW5jaW1hIHkgcG9yIGRlYmFqbyByZXNwZWN0aXZhbWVudGUuIEVuIHRhbnRvIG5vIGV4aXN0w61hbiB2YWxvcmVzIG1lbm9yZXMgYSBsYSBjb3RhIGluZmVyaW9yLCBlbCBtw61uaW1vIHNlIG1hbnR1dm8uIEVsIG3DoXhpbW8sIGVuIGNhbWJpbywgc2UgdmlvIHJlZHVjaWRvIGFsIGzDrW1pdGUgc3VwZXJpb3IgZXN0YWJsZWNpZG8uCgpMYSBtZWRpYSB5ICBsYSBtZWRpYW5hIGRlbCB0aXBvIGRlIHByb3BpZWRhZCBEZXBhcnRhbWVudG8gc2lndWUgc2llbmRvIHNpbWlsYXIgYSBsYXMgZGVsIHRpcG8gZGUgcHJvcGllZGFkIFBIIGF1bnF1ZSBlbiBhbWJvcyBjYXNvcyBsYXMgZGlzdGFuY2lhcyBlbnRyZSBlc3RhcyBkb3MgbWVkaWRhcyBzZSBoYSBhY29ydGFkby4KCkVzdG8gdGFtYmnDqW4gaGEgb2N1cnJpZG8gY29uIGxhIG1lZGlhIHkgbGEgbWVkaWFuYSBkZWwgdGlwbyBkZSBwcm9waWVkYWQgQ2FzYSwgZG9uZGUgYW1iYXMgbWVkaWRhcyBzZSBoYW4gYXByb3hpbWFkbyBkZSBtYW5lcmEgbcOhcyBub3RhYmxlLiBTaSBzZSBhZ3JlZ2EgYSBlc3RlIGRhdG8gZWwgaGVjaG8gZGUgcXVlLCBjb21vIHByb2R1Y3RvIGRlIGxhIHBvZGEgZGUgb3V0bGllcnMsIGVzdGUgZ3J1cG8gcHJlc2VudGUgbWVub3IgZGlzcGVyc2nDs24sIHNlIHBvZHLDrWEgc3Vwb25lciBxdWUgc3UgZGlzdHJpYnVjacOzbiB5YSBubyBkZWJlcsOtYSBzZSBhc2ltw6l0cmljYSBvLCBhbCBtZW5vcywgbm8gZGUgZm9ybWEgdGFuIG1hcmNhZGEuCgpTZSBjb3RlamFuIGVzdG9zIHN1cHVlc3RvcyBjb24gdW4gbnVldm8gYm94cGxvdC4gSWd1YWwgcXVlIHNlIGhpem8gYW50ZXJpb3JtZW50ZSwgc2UgY29udmllcnRlbiBsb3MgdmFsb3JlcyBkZSBsYSB2YXJpYWJsZSBfdGlwbyBkZSBwcm9waWVkYWRfIGVuIGZhY3RvcmVzIHBhcmEgcG9kZXIgcmVhbGl6YXIgZWwgZ3LDoWZpY28uCgpgYGB7cn0KZ2dkYXRhX3Npbl9vdXQgPC0gZGF0YXNldF9zaW5fb3V0bGllcnMgJT4lIAogIG11dGF0ZShwcm9wZXJ0eV90eXBlID0gYXMuZmFjdG9yKHByb3BlcnR5X3R5cGUpKQoKZ2dwbG90KGdnZGF0YV9zaW5fb3V0LCBhZXMoeCA9IHByb3BlcnR5X3R5cGUsIHkgPSBwcmljZSwgZ3JvdXAgPSBwcm9wZXJ0eV90eXBlLCBmaWxsID0gcHJvcGVydHlfdHlwZSkpICsKICBnZW9tX2JveHBsb3QoKSArCiAgbGFicyh0aXRsZT0nUHJlY2lvIHBvciB0aXBvIGRlIHByb3BpZWRhZCcsCiAgICAgICBzdWJ0aXRsZSA9ICdWYWxvcmVzIGF0w61waWNvcyBlbGltaW5hZG9zJywKICAgICAgIHggPSAnVGlwb3MgZGUgcHJvcGllZGFkJywKICAgICAgIHkgPSAnUHJlY2lvIChVU0QpJykKYGBgCgojIyA2LjMuIFJlbGFjacOzbiBlbnRyZSB2YXJpYWJsZXMKCk51ZXZhbWV0ZSwgc2UgZ3JhZmljYSB1biBjb3JyZWxvZ3JhbWEgcXVlIGV2aWRlbmNpZSBsYSByZWxhY2nDs24gZW50cmUgbGFzIHZhcmlhYmxlcyBkZWwgZGF0YXNldCBzaW4gZmFsdGFudGVzIG5pIGRhdG9zIGF0w61waWNvcy4KCmBgYHtyfQpkYXRhc2V0X3Npbl9vdXRsaWVycyAlPiUgCiAgc2VsZWN0X2lmKGlzLm51bWVyaWMpICU+JQogIGdnY29ycihtZXRob2QgPSBjKCJjb21wbGV0ZS5vYnMiLCJwZWFyc29uIiksCiAgICAgICAgIGxvdyA9ICcjZTBmM2RiJywKICAgICAgICAgbWlkID0gJyNhOGRkYjUnLAogICAgICAgICBoaWdoID0gJyM0M2EyY2EnLAogICAgICAgICBsYWJlbCA9IFRSVUUsCiAgICAgICAgIGxhYmVsX2NvbG9yID0gImJsYWNrIiwKICAgICAgICAgbmJyZWFrcyA9IDUsCiAgICAgICAgIGhqdXN0ID0gMC43NSwKICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gImxlZnQiKSArCiAgbGFicyh0aXRsZSA9ICJQcm9waWVkYWRlcyBlbiB2ZW50YSBlbiBDQUJBIC0gQ29ycmVsb2dyYW1hIiwKICAgICAgIHN1YnRpdGxlID0gIlZhbG9yZXMgYXTDrXBpY29zIGVsaW1pbmFkb3MiKQpgYGAKCkFsIGlndWFsIHF1ZSBzZSBvYnNlcnbDsyBhbnRlcmlvcm1lbnRlLCBsYSB2YXJpYWJsZSBfc3VwZXJmaWNpZSB0b3RhbF8geSBfc3VwZXJmaWNpZSBjdWJpZXJ0YV8gc29uIGxhcyBxdWUgc2UgZW5jdWVudHJhbiBtw6FzIGNvcnJlbGFjaW9uYWRhcyBlbiB1biBzZW50aWRvIHBvc2l0aXZvLiAKCkxvIHF1ZSBoYSBjYW1pYWRvIGVzIHF1ZSB5YSBubyBzb24gbGFzIHZhcmlhYmxlcyBfaGFiaXRhY2lvbmVzXyB5IF9iYcOxb3NfIGxhcyBxdWUgbGUgc2lndWVuIGVuIG9yZGVuIGRlIGNvcnJlbGFjacOzbiwgc2lubyBxdWUgYWhvcmEgc29uIGxhcyBjb3JyZWxhY2lvbmVzIGRlIF9iYcOxb18geSBfcHJlY2lvXyB5IGRlIF9oYWJpdGFjaW9uZXNfIHkgX3ByZWNpb18gbGFzIHF1ZSBvY3VwYW4gZWwgc2VndW5kbyBsdWdhciwgYW1iYXMgY29uIHVuYSBjb3JyZWxhY2nDs24gYXBlbmFzIHN1cGVyaW9yIGEgMC42LgoKRXN0byBkZW11ZXN0cmEgcXVlLCBzaSBiaWVuIGxhIHJlbW9jacOzbiBkZSBkYXRvcyBmYWx0YW50ZXMgbm8gdHV2byBtYXlvcmVzIGNvbnNlY3VuZWNpYXMgZW4gZWwgZGF0YXNldCB1dGlsaXphZG8sIHRhbXBvY28gZnVlIGlub2N1by4gUHJldmlhbWVudGUgYSByZWFsaXphciBlc3RlIHByb2Nlc2FtaWVudG8sIGxhcyB2YXJpYWJsZXMgbcOhcyBjb3JyZWxhY2lvbmFkYXMgbHVlZ28gZGUgX3N1cGVyZmljaWUgdG90YWxfIHkgX3N1cGVyZmljaWUgY3ViaWVydGFfIGVyYW4gX2hhYml0YWNpb25lc18geSBfYmHDsW9zXywgcG9yIHVuIGxhZG8sIHkgX3ByZWNpb18geSBfYmHDsW9fLCBwb3Igb3Ryby4gTGEgY29ycmVsYWNpw7NuIGVudHJlIF9kb3JtaXRvcmlvc18geSBfcHJlY2lvXyBzZSBlbmNvbnRyYWJhIHJlY2nDqW4gZW4gdW4gdGVyY2VyIGx1Z2FyLiAKCkFob3JhLCBhZGVtw6FzIGRlIGhhYmVyIG1vZGlmaWNhZG8gc3VzIMOzcmRlbmVzLCBlc3RhIMO6bHRpbWFzIHRyZXMgY29ycmVsYWNpb25lcyBzZSBoYW4gYXByb3hpbWFkbyBlbnRyZSBzw60uCgojIDcuIE1vZGVsbyBsaW5lYWwKCiMjIDcuMS4gTW9kZWxvcyBsaW5lYWxlcwoKU2UgcmVhbGl6YW4gZG9zIG1vZGVsb3MgbGluZWFsZXMgY29uIGVsIGZpbiBkZSBwb2RlciBwcmVkZWNpciBlbCBwcmVjaW8gZW4gZnVuY2nDs24gZGUgb3RyYSB2YXJpYWJsZS4KCiMjIyA3LjEuMS4gTW9kZWxvIDE6IGVsIHByZWNpbyBlbiBmdW5jacOzbiBkZSBsYXMgaGFiaXRhY2lvbmVzCgpFbiBlc3RlIHByaW1lciBtb2RlbG8sIHNlIGJ1c2NhIGV4cGxpY2FyIGVsIHByZWNpbyBlbiBmdW5jacOzbiBkZSBsYSB2YXJpYWJsZSBfaGFiaXRhY2lvbmVzXyAocm9vbXMpLiAKUHJpbWVybyBzZSBzZWxlY2Npb25hbiBsb3MgYXRyaWJ1dG9zIGEgdXRpbGl6YXIgeSBsdWVnbyBzZSBhanVzdGEgZWwgbW9kZWxvLiAKCk1lZGlhbnRlIGxhIGZ1bmNpw7NuIF9zdW1tYXJ5XyBzZSBleHRyYWVuIGxvcyBjb2ZpY2llbnRlcyBlc3RpbWFkb3MgcGFyYSBsYSByZWN0YSBnZW5lcmFkYSBwb3IgZWwgbW9kZWxvOgogCiAtIDU3MjE4Ljk6IGVzIGxhIG9yZGVuYWRhIGFsIG9yaWdlbi4gRXN0ZSB2YWxvciBpbmRpY2EgZWwgcHJlY2lvIGVzcGVyYWRvIGN1YW5kbyBzZSB0b21hIGVuIGNvbnNpZGVyYWNpw7NuIGVsIHByb21lZGlvIGRlIGxhIHZhcmlhYmxlIF9oYWJpdGFjaW9uZXNfIGRlIHRvZGFzIGxhcyBwcm9waWVkYWRlcyBkZWwgZGF0YXNldC4KIC0gNDk1NjEuNzogZWwgbGEgcGVuZGllbnRlIGRlIGxhIHJlY3RhLiBJbmRpY2EgY3XDoW50byBhdW1lbnRhIGxhIHZhcmlibGUgdGFyZ2V0IChlbiBlc3RlIGNhc28sIGVsIHByZWNpbykgcG9yIGNhZGEgdW5pZGFkIHF1ZSBhdW1lbnRhIGxhIHZhcmlhYmxlIGluZGVwZW5kaWVudGUgKGVzIGRlY2lyLCBsYSBjYW50aWRhZCBkZSBoYWJpdGFjaW9uZXMpLgoKCmBgYHtyfQpkYXRhLm1vZGVsMSA8LSBkYXRhc2V0X3Npbl9vdXRsaWVycyAlPiUgCiAgc2VsZWN0KHByaWNlLCByb29tcykKCm1vZDEgPC0gbG0ocHJpY2UgfiByb29tcywgZGF0YSA9IGRhdGEubW9kZWwxKQoKc3VtbWFyeShtb2QxKQpgYGAKCiMjIyA3LjEuMi4gTW9kZWxvIDI6IGVsIHByZWNpbyBlbiBmdW5jacOzbiBkZSBsYSBzdXBlcmZpY2llIHRvdGFsCgpFbiB1biBzZWd1bmRvIG1vZGVsbywgc2UgYnVzY2EgZXhwbGljYXIgZWwgcHJlY2lvIGVuIGZ1bmNpw7NuIGRlIGxhIHZhcmlhYmxlIF9zdXBlcmZpY2llIHRvdGFsXyAoc3VyZmFjZV90b3RhbCkuIApOdWV2YW1lbnRlLCBzZSBzZWxlY2Npb25hbiBsb3MgYXRyaWJ1dG9zIGEgdXRpbGl6YXIgeSBsdWVnbyBzZSBhanVzdGEgZWwgbW9kZWxvLiAKCkRlbCBtaXNtbyBtb2RvIHF1ZSBzZSBoaXpvIGFudGVyaW9ybWVudGUsIHNlIGV4dHJhZW4gbG9zIGNvZmljaWVudGVzIGVzdGltYWRvczoKCiAtIDEuODcyZSswNTogZXMgbGEgb3JkZW5hZGEgYWwgb3JpZ2VuIGRlIGxhIHJlY3RhIGdlbmVyYWRhLiBFeHByZXNhIGVsIHByZWNpbyBkZSB1bmEgcHJvcGllZGFkIGNvbiB1bmEgc3VwZXJmaWNpZSB0b3RhbCBwcm9tZWRpby4KIC0gNS4wNThlKzAwOiBlcyBsYSBwZW5kaWVudGUgZGVsIG1vZGVsbyBlIGluZGljYSBjdcOhbnRvIGF1bWVudGEgZWwgcHJlY2lvIGNvbmZvbWUgYXVtZW50YSBsYSBzdXBlcmZpY2llIHRvdGFsIGRlIGxhIHByb3BpZWRhZC4KIApgYGB7cn0KZGF0YS5tb2RlbDIgPC0gZGF0YXNldF9zaW5fb3V0bGllcnMgJT4lIAogIHNlbGVjdChwcmljZSwgc3VyZmFjZV90b3RhbCkKCm1vZDIgPC0gbG0ocHJpY2UgfiBzdXJmYWNlX3RvdGFsLCBkYXRhID0gZGF0YS5tb2RlbDIpCgpzdW1tYXJ5KG1vZDIpCmBgYAoKIyMgNy4yLiBTZWxlY2Npw7NuIGRlbCBtb2RlbG8KCkRlIGxvcyBtb2RlbG9zIGFudGVyaW9ybWVudGUgYWp1c3RhZG8sIGVsIG1vZGVsbyAxLCBxdWUgaW50ZW50YSBleHBsaWNhciBlbCBwcmVjaW8gZGUgdW5hIHByb3BpZWRhZCBlbiBmdW5jacOzbiBkZSBsYSBjYW50aWRhZCBkZSBoYWJpdGFjaW9uZXMgcXVlIHBvc2VlLCBwYXJlY2Ugc2VyIGVsIG3DoXMgYWRlY3VhZG8uIAoKU2kgYmllbiBuaW5ndW5vIGRlIGxvcyBtb2RlbG9zIGV4aGliZSB1biByZXNpZHVvIGNvbiB1bmEgbWVkaWFuYSAwIG8gY2VyY2FuYSBhIDAsIGxvIHF1ZSBzZXLDrWEgZGVzZWFibGUgcHVlc3RvIHF1ZSBlc28gc2lnbmlmaWNhIHF1ZSBsYSBkaWZlcmVuY2lhIGVudHJlIGxvcyBkYXRvcyBvYnNlcnZhZG9zIHkgbG9zIHByZWRpY2hvcyBwcmVzZW50YSB1bmEgZGlzdHJpYnVjacOzbiBzaW3DqXRyaWNhIHkgZGEgY3VlbnRhIGRlIGN1w6FudG8gc2UgYWp1c3RhIGVsIG1vZGVsbyBhIGxvcyBkYXRvcywgbGEgbWVkaWFuYSBkZSBsb3MgcmVzaWR1b3MgZGVsIG1vZGVsbyAxIHByZXNlbnRhIHVuYSBtZW5vciBkaXN0YW5jaWEgYWwgMCBxdWUgbGEgbWVkaWFuYSBkZWwgbW9kZWxvIDIuCgpBZGVtw6FzLCBhdW5xdWUgbG9zIGRlc3bDrW9zIGVzdMOhbmRhcmVzIGRlIGxvcyBjb2VmaWNpZW50ZXMgZGVsIHByaW1lciBtb2RlbG8gc29uIG1heW9yZXMgZW4gdMOpcm1pbm9zIGFic29sdXRvcywgY3VhbmRvIHNlIGxvcyByZWxhY2lvbmEgY29uIGxvcyBjb2VmaWNpZW50ZXMgZXN0aW1hZG9zLCByZXN1bHRhbiB2YWxvcmVzIHByb3BvcmNpb25hbG1lbnRlIG1lbm9yZXMgYSBsb3MgZXhoaWJpZG9zIHBvciBlbCBtb2RlbG8gMi4KClBvciDDumx0aW1vLCBlbCB0IHZhbG9yIG9ic2VydmFkbyBlbnRyZSBsYSB2YXJpYWJsZSBfaGFiaXRhY2lvbmVzXyB5IF9wcmVjaW9fIGVzIG1heW9yIHF1ZSBlbCBxdWUgZXhpc3RlIGVudHJlIF9zdXBlcmZpY2llIHRvdGFsXyB5IF9wcmVjaW9fLCBzdWdpcmllbmRvIHF1ZSBsYSBwcmltZXJhIHJlbGFjacOzbiBlcyBtw6FzIGVzdHJlY2hhLg==