Análisis exploratorio e Introducción a la Regresión lineal


  1. Preparación de los datos (I)


library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
-- Attaching packages --------------------------------------- tidyverse 1.3.0 --
v ggplot2 3.3.2     v purrr   0.3.4
v tibble  3.0.3     v dplyr   1.0.1
v tidyr   1.1.1     v stringr 1.4.0
v readr   1.3.1     v forcats 0.5.0
-- Conflicts ------------------------------------------ tidyverse_conflicts() --
x dplyr::filter() masks stats::filter()
x dplyr::lag()    masks stats::lag()
library(dplyr)
library(tidyr)
library(ggplot2)
library(purrr)
library(corrr)
library(GGally)
Registered S3 method overwritten by 'GGally':
  method from   
  +.gg   ggplot2
options(crayon.enabled = FALSE)
  1. Estructura del archivo ar_properties.csv
properties <- read.csv("ar_properties.csv")
glimpse(properties) 
Rows: 388,891
Columns: 24
$ id              <chr> "S0we3z3V2JpHUJreqQ2t/w==", "kMxcmAS8NvrynGBVbMOEaQ==", "Ce3ojF+ZTOkB8d+LI9dpxg==", "AUGpj3...
$ ad_type         <chr> "Propiedad", "Propiedad", "Propiedad", "Propiedad", "Propiedad", "Propiedad", "Propiedad", ...
$ start_date      <chr> "2019-04-14", "2019-04-14", "2019-04-14", "2019-04-14", "2019-04-14", "2019-04-14", "2019-0...
$ end_date        <chr> "2019-06-14", "2019-04-16", "9999-12-31", "9999-12-31", "2019-07-09", "2019-08-08", "2019-0...
$ created_on      <chr> "2019-04-14", "2019-04-14", "2019-04-14", "2019-04-14", "2019-04-14", "2019-04-14", "2019-0...
$ lat             <dbl> -34.94331, -34.63181, NA, -34.65471, -34.65495, -32.93547, -34.65183, -34.91213, -34.60338,...
$ lon             <dbl> -54.92966, -58.42060, NA, -58.79089, -58.78712, -60.68398, -58.65912, -54.84749, -58.43450,...
$ l1              <chr> "Uruguay", "Argentina", "Argentina", "Argentina", "Argentina", "Argentina", "Argentina", "U...
$ l2              <chr> "Maldonado", "Capital Federal", "Bs.As. G.B.A. Zona Norte", "Bs.As. G.B.A. Zona Oeste", "Bs...
$ l3              <chr> "Punta del Este", "Boedo", NA, "Moreno", "Moreno", "Rosario", "Ituzaingó", "José Ignacio"...
$ l4              <chr> NA, NA, NA, "Moreno", "Moreno", NA, "Ituzaingó", NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, N...
$ 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,...
$ 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,...
$ 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,...
$ 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,...
$ 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...
$ surface_total   <int> 45, NA, 200, 460, 660, NA, 70, NA, 1300, 405, 352, 373, 360, 1325, 250, 80142, 101, NA, 54,...
$ 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, ...
$ 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,...
$ currency        <chr> "UYU", NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, ...
$ price_period    <chr> "Mensual", "Mensual", NA, "Mensual", "Mensual", "Mensual", "Mensual", "Mensual", "Mensual",...
$ title           <chr> "Departamento - Roosevelt", "PH - Boedo", "Ituzaingo  1100 - $ 1 - Casa Alquiler", "Dr. Ver...
$ property_type   <chr> "Departamento", "PH", "Casa", "Casa", "Casa", "Casa", "Casa", "Casa", "Lote", "Lote", "Lote...
$ operation_type  <chr> "Alquiler", "Venta", "Alquiler", "Venta", "Venta", "Venta", "Venta", "Alquiler", "Venta", "...
summary(properties)
      id              ad_type           start_date          end_date          created_on             lat        
 Length:388891      Length:388891      Length:388891      Length:388891      Length:388891      Min.   :-54.98  
 Class :character   Class :character   Class :character   Class :character   Class :character   1st Qu.:-34.67  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character   Mode  :character   Median :-34.60  
                                                                                                Mean   :-34.48  
                                                                                                3rd Qu.:-34.43  
                                                                                                Max.   : 44.67  
                                                                                                NA's   :50597   
      lon               l1                 l2                 l3                 l4                 l5           
 Min.   :-105.27   Length:388891      Length:388891      Length:388891      Length:388891      Length:388891     
 1st Qu.: -58.80   Class :character   Class :character   Class :character   Class :character   Class :character  
 Median : -58.48   Mode  :character   Mode  :character   Mode  :character   Mode  :character   Mode  :character  
 Mean   : -59.37                                                                                                 
 3rd Qu.: -58.40                                                                                                 
 Max.   : -41.90                                                                                                 
 NA's   :50597                                                                                                   
    l6              rooms           bedrooms        bathrooms     surface_total      surface_covered  
 Mode:logical   Min.   : 1.0     Min.   : -2.00   Min.   : 1.00   Min.   :    -3.0   Min.   :   -139  
 NA's:388891    1st Qu.: 2.0     1st Qu.:  1.00   1st Qu.: 1.00   1st Qu.:    50.0   1st Qu.:     43  
                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                                                                                                     
  1. Filtrado de registros y selección de variables
Datos <- properties %>% 
  filter(l1 == "Argentina" , l2 == "Capital Federal" , currency == "USD" , operation_type == "Venta" , property_type == "Departamento" | property_type == "Casa" | property_type == "PH") %>%
  select(id, l3, rooms, bedrooms, bathrooms, surface_total, surface_covered, price, property_type)
glimpse(Datos)
Rows: 61,905
Columns: 9
$ id              <chr> "oyj+f764ALCYodIqBvWAww==", "HdjpKrqdwYfH9YU1DKjltg==", "YwWE3rTb2+gmsBwjUHmAPQ==", "6AxnSW...
$ l3              <chr> "Barracas", "Boedo", "Palermo", "Belgrano", "Versalles", "Velez Sarsfield", "Nuñez", "Alma...
$ rooms           <int> NA, 6, NA, 3, NA, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 2, 1, 1, NA, 1, 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,...
$ bathrooms       <int> NA, 2, 2, 4, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, NA,...
$ surface_total   <int> 300, 178, 240, 157, 140, 95, 44, 40, 49, 40, 40, 40, 49, 40, 23, 40, 40, 32, 40, 36, 90, 45...
$ surface_covered <int> 180, 240, 157, NA, 110, 69, 38, 37, 44, 37, 37, 37, 44, 37, 23, 37, 37, 30, 34, 33, 90, 35,...
$ price           <int> 320000, 500000, 350000, 470000, 155000, 199900, 147000, 92294, 115000, 77000, 88900, 88798,...
$ property_type   <chr> "PH", "Casa", "Casa", "Casa", "Casa", "Casa", "Departamento", "Departamento", "Departamento...

  1. Análisis exploratorio (I)


  1. Cantidad de valores únicos para cada variable
Datos %>%
    map_df(function(x) length(unique(x))) %>% head

Cantidad de valores faltantes (NAs) para cada variable

Datos %>%
    map_df(function(x) sum(is.na(x))) %>% head


b) Matriz de correlación para las variables numéricas

Datos %>% select(-c(id,l3,property_type)) %>%
  correlate(use = "complete.obs", diagonal = 1) %>% 
  shave() %>% 
  fashion()

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

  1. Preparación de los datos (II)


  1. La variable bedrooms presenta una alta proporción de valores faltantes y una fuerte correlación con la variable rooms. Es por ello que la eliminamos.
  2. Eliminación de registros que presentan valores faltantes.
Datos <- Datos %>% select(-c(bedrooms)) %>% drop_na()
glimpse(Datos)
Rows: 51,210
Columns: 8
$ id              <chr> "HdjpKrqdwYfH9YU1DKjltg==", "AfdcsqUSelai1ofCAq2B0Q==", "ESzybdH7YU2uIU1/kHtRGw==", "r22Ofz...
$ l3              <chr> "Boedo", "Velez Sarsfield", "Nuñez", "Almagro", "Almagro", "Almagro", "Almagro", "Almagro"...
$ rooms           <int> 6, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 2, 1, 1, 1, 1, 1, 4, 1, 2, 1, 1, 2...
$ bathrooms       <int> 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 2, 1, 1, 1...
$ surface_total   <int> 178, 95, 44, 40, 49, 40, 40, 40, 49, 40, 23, 40, 40, 32, 40, 36, 90, 45, 54, 40, 33, 40, 40...
$ surface_covered <int> 240, 69, 38, 37, 44, 37, 37, 37, 44, 37, 23, 37, 37, 30, 34, 33, 90, 35, 48, 40, 32, 34, 36...
$ price           <int> 500000, 199900, 147000, 92294, 115000, 77000, 88900, 88798, 110975, 92943, 69000, 99000, 96...
$ property_type   <chr> "Casa", "Casa", "Departamento", "Departamento", "Departamento", "Departamento", "Departamen...

  1. Análisis exploratorio (II)


  1. Estadísticas descriptivas para la variable precio: cuartiles, promedio, mínimo y máximo. Podemos observar que el mínimo es probablemente un outlier, al igual que el máximo. La media se encuentra a la derecha de la mediana debido a los valores altos de la variable.
summary(Datos$price)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   6000  119000  170000  251577  270000 6000000 

Histograma de la variable precio: podemos observar que la mayoría de las observaciones se encuentran entre los 100 mil y los 200 mil USD. Existen valores altos de esta variable que van hasta 6 millones de USD.

Datos %>% ggplot(aes(price)) + geom_histogram()


b) Estadísticas descriptivas para la variable precio: cuartiles, promedio, mínimo y máximo por cada tipo de propiedad. Se observa que las casas valen alrededor del doble de los departamentos y PHs y que los departamentos y PHs tienen precios relativamente parecidos en promedio.La menor mediana es la de los departamentos, la media en este caso se encuentra bastante alejada de la mediana, por lo que podemos deducir que los outliers superiores están afectando la media.

Datos %>% 
  group_by(property_type) %>%
  summarise(quantile1 = quantile(price, probs = 0.25), median(price), quantile3 = quantile(price, probs = 0.75), mean(price), min(price), max(price))
`summarise()` ungrouping output (override with `.groups` argument)


c) Boxplot de la variable precio por tipo de propiedad: podemos observar numerosos outliers en los valores superiores del precio de las casas y departamentos, que son mayores que los de los PHs.

Datos %>% ggplot(aes(x = property_type, y = price, group = property_type, fill = property_type )) +
  geom_boxplot()


d) Correlograma de las variables numéricas: se muestra la fuerza y el sentido de la correlación del conjunto de variables. Todas las correlaciones son positivas siendo la más fuerte la de surface_total contra surface_covered. En general las correlaciones son bajas, probablemente por la influencia de los outliers.

Datos %>% select(-c(id,l3,property_type)) %>% 
  ggcorr(palette = "RdBu", label = TRUE)


  1. Outliers


  1. Eliminación de los outliers de la variable precio:
Datos %>% 
  group_by(surface_total) %>%
  summarise(quantile1 = quantile(price, probs = 0.25), median(price), quantile3 = quantile(price, probs = 0.75), mean(price), min(price), max(price))
`summarise()` ungrouping output (override with `.groups` argument)

Como se puede observar en la salida de la estadística descriptiva de la variable precio por cada cantidad de metros cuadrados, existen valores muy bajos de superficie, por ejemplo 10 metros cuadrados. Se decide eliminar las observaciones con menos de 16 metros cuadrados porque probablemente pertenezcan a cocheras y algunos tienen precios son muy elevados, como por ejemplo 220 mil USD por una propiedad de 10 metros cuadrados.
También se elimina la observación de 126062 metros cuadrados porque es 10 veces mayor a los otros valores altos observados y el valor de esta propiedad es de la misma magnitud de las otras. Para ello se pone una cota superior de 17 mil metros cuadrados. No se eliminan los otros valores altos porque pueden provenir de terrenos muy grandes.
Estudiamos ahora la variable precio por metro cuadrado:

summary(Datos$price/Datos$surface_total)
     Min.   1st Qu.    Median      Mean   3rd Qu.      Max. 
     1.71   2155.17   2653.85   2802.62   3233.08 124682.93 

Para descartar los valores que no suenan razonables de la variable precio por metro cuadrado, podemos poner una cota inferior de mil USD por metro cuadrado y una cota superior de 6 mil USD por metro cuadrado (que es el precio por metro cuadrado del barrio más caro de Capital Federal, que es Puerto Madero).

quantile(Datos$price/Datos$surface_total, probs=c(.0165, .982))
    1.65%     98.2% 
 999.9778 6000.0000 

Como se puede observar las cotas establecidas descartan el 1,65% de los datos por debajo y el 1,8% de los datos por arriba. De un total de 51210 observaciones, nos quedamos con 49339 observaciones.

Datos <- Datos %>% 
  filter(surface_total > 16 , surface_total < 17000 , price/surface_total > 1000 , price/surface_total < 6000) 
 glimpse(Datos)  
Rows: 49,339
Columns: 8
$ id              <chr> "HdjpKrqdwYfH9YU1DKjltg==", "AfdcsqUSelai1ofCAq2B0Q==", "ESzybdH7YU2uIU1/kHtRGw==", "r22Ofz...
$ l3              <chr> "Boedo", "Velez Sarsfield", "Nuñez", "Almagro", "Almagro", "Almagro", "Almagro", "Almagro"...
$ rooms           <int> 6, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3, 1, 1, 2, 1, 1, 1, 1, 1, 1, 2, 1, 1, 2, 2...
$ bathrooms       <int> 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 1, 1, 1, 1...
$ surface_total   <int> 178, 95, 44, 40, 49, 40, 40, 40, 49, 40, 23, 40, 40, 32, 40, 36, 90, 45, 54, 40, 33, 40, 40...
$ surface_covered <int> 240, 69, 38, 37, 44, 37, 37, 37, 44, 37, 23, 37, 37, 30, 34, 33, 90, 35, 48, 40, 32, 34, 36...
$ price           <int> 500000, 199900, 147000, 92294, 115000, 77000, 88900, 88798, 110975, 92943, 69000, 99000, 96...
$ property_type   <chr> "Casa", "Casa", "Departamento", "Departamento", "Departamento", "Departamento", "Departamen...

  1. Análisis exploratorio (III)


  1. Estadísticas descriptivas para la variable precio: cuartiles, promedio, mínimo y máximo. Tanto los cuartiles como el promedio y la mediana se reducen porque ya no se encuentran afectados por el outlier de 6 millones de USD. El mínimo es un orden mayor que el mínimo antes de eliminar los outliers.
summary(Datos$price)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  30000  118000  168000  230827  265000 5700000 

Histograma de la variable precio: no existen cambios notables en el gráfico, sólo una leve baja en la cantidad de observaciones de los valores bajos.

Datos %>% ggplot(aes(price)) + geom_histogram()


b) Estadísticas descriptivas para la variable precio: cuartiles, promedio, mínimo y máximo por cada tipo de propiedad. En general se mantienen las relaciones observadas en el anterior análisis. Los mayores cambios se presentan en el promedio: en las casas el promedio aumenta aproximadamente en 10 mil USD, en los departamentos disminuye en 23 mil USD y en los PHs se mantiene aproximadamente igual.

Datos %>% 
  group_by(property_type) %>%
  summarise(quantile1 = quantile(price, probs = 0.25), median(price), quantile3 = quantile(price, probs = 0.75), mean(price), min(price), max(price))
`summarise()` ungrouping output (override with `.groups` argument)


c) Boxplot de la variable precio por tipo de propiedad: no se observan cambios significativos, más que la disminución de la cantidad de valores altos en los precios de los departamentos.

Datos %>% ggplot(aes(x = property_type, y = price, group = property_type, fill = property_type )) +
  geom_boxplot()


d) Correlograma de las variables numéricas: en este caso, los cambios son significativos. Aumenta la fuerza de la correlación del conjunto de variables en la mayoría de los casos. Sólo se mantiene la correlación entre rooms y bathrooms. Como eliminamos los outliers estableciendo un rango en la variable precio por metro cuadrado, se nota un aumento importante en la correlacion de precio y superfiecie total. También es importante el incremento en la correlación de surface_total con rooms y bathrooms.

Datos %>% select(-c(id,l3,property_type)) %>% 
  ggcorr(palette = "RdBu", label = TRUE)


  1. Modelo lineal


  1. Modelo 1: Modelo lineal simple para explicar el precio en función de las habitaciones (rooms)
ajuste1 <- lm(Datos$price~Datos$rooms)
summary(ajuste1)

Call:
lm(formula = Datos$price ~ Datos$rooms)

Residuals:
     Min       1Q   Median       3Q      Max 
-2436398   -79231   -19294    41224  4774212 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) -33579.6     1738.5  -19.32   <2e-16 ***
Datos$rooms  95936.8      566.4  169.39   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 170000 on 49337 degrees of freedom
Multiple R-squared:  0.3677,    Adjusted R-squared:  0.3677 
F-statistic: 2.869e+04 on 1 and 49337 DF,  p-value: < 2.2e-16


Modelo 2: Modelo lineal simple para explicar el precio en función de la superficie total (surface_total)

ajuste2 <- lm(Datos$price~Datos$surface_total)
summary(ajuste2)

Call:
lm(formula = Datos$price ~ Datos$surface_total)

Residuals:
     Min       1Q   Median       3Q      Max 
-1466055   -36653    -8431    25152  3302659 

Coefficients:
                     Estimate Std. Error t value Pr(>|t|)    
(Intercept)         20323.796    884.675   22.97   <2e-16 ***
Datos$surface_total  2430.488      7.882  308.34   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 125000 on 49337 degrees of freedom
Multiple R-squared:  0.6584,    Adjusted R-squared:  0.6584 
F-statistic: 9.507e+04 on 1 and 49337 DF,  p-value: < 2.2e-16


b) El coeficiente beta1 en el modelo 1 indica que por cada incremento en una unidad de la variable habitaciones (rooms), el precio sube USD 95936.8. En el modelo 2, beta1 indica que por cada incremento en una unidad de la variable superficie total (surface_total), el precio sube USD 2430.488.
En el modelo 1 la estimación de σ que resulta ser 170000, indicada por Residual standard error. Si comparamos la estimación de σ del modelo 1 con la obtenida en el modelo 2, vemos que el desvío estándar se redujo considerableme.
En los dos modelos podemos ver que la distribución de los residuos no parece ser muy simétrica. Eso significa que el modelo predice ciertos puntos que se alejan mucho de los puntos reales observados.
R^2 nos dice qué proporción de la variabilidad total en la variable Y puede ser explicada por la variable regresora, en consecuencia es una medida de la capacidad de predicción del modelo.
R^2 también puede verse como una medida de la fuerza de la asociación lineal entre X e Y. Mientras mayor sea R^2 menor es la SSRes y por lo tanto, más cercanos están los puntos a la recta.
Para los datos de la regresión del precio versus habitaciones, en la salida del modelo lineal 1 vemos que R^2 es 0.3677, por lo tanto la relación lineal es levemente fuerte. Esto significa que el 36,77 % de la variabilidad observada en los valores del precio queda explicada por la relación lineal entre el precio y las habitaciones.
En cambio en modelo lineal 2 de precio en función de la superficie total, vemos que R^2 = 0.6584. Este valor implica una relación lineal moderadamente fuerte entre el precio y la superficie total. En particular, el 65,84 % de la variabilidad observada en los valores del precio queda explicada por la relación lineal entre el precio y la superficie total. El restante 100 % − 65,84 % = 34,16 % de la variabilidad no queda explicada por esta relación.

c) Entre los dos modelos, es mejor utilizar el modelo 2 para predecir el precio ya que explica un porcentaje mayor de la variabilidad de los valores del precio. Esta regresión lineal puede ser observada en el siguiente gráfico.

ggplot(Datos, aes(x=surface_total,y=price))+
  geom_point(aes(colour=property_type),alpha=0.3)+
  geom_smooth(method = 'lm')

LS0tDQp0aXRsZTogIlByb3BlcmF0aSAtIFJlZ3Jlc2nDs24gbGluZWFsIg0KYXV0aG9yOiAiS2FyZW4gUm9iZXJ0cyINCmRhdGU6ICIyOSBkZSBtYXlvIGRlIDIwMjEiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCmBgYA0KPGJyPg0KPGJyPg0KDQo8Y2VudGVyPiA8aDI+QW7DoWxpc2lzIGV4cGxvcmF0b3JpbyBlIEludHJvZHVjY2nDs24gYSBsYSBSZWdyZXNpw7NuIGxpbmVhbDwvaDI+IDwvY2VudGVyPg0KDQo8YnI+DQo8aDQ+MS4gUHJlcGFyYWNpw7NuIGRlIGxvcyBkYXRvcyAoSSk8L2g0Pg0KPGJyPg0KDQoNCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeSh0aWR5cikNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkocHVycnIpDQpsaWJyYXJ5KGNvcnJyKQ0KbGlicmFyeShHR2FsbHkpDQpvcHRpb25zKGNyYXlvbi5lbmFibGVkID0gRkFMU0UpDQpgYGANCmEpIEVzdHJ1Y3R1cmEgZGVsIGFyY2hpdm8gYXJfcHJvcGVydGllcy5jc3YNCmBgYHtyfQ0KcHJvcGVydGllcyA8LSByZWFkLmNzdigiYXJfcHJvcGVydGllcy5jc3YiKQ0KZ2xpbXBzZShwcm9wZXJ0aWVzKSANCmBgYA0KYGBge3J9DQpzdW1tYXJ5KHByb3BlcnRpZXMpDQpgYGANCg0KYikgRmlsdHJhZG8gZGUgcmVnaXN0cm9zIHkgc2VsZWNjacOzbiBkZSB2YXJpYWJsZXMNCmBgYHtyfQ0KRGF0b3MgPC0gcHJvcGVydGllcyAlPiUgDQogIGZpbHRlcihsMSA9PSAiQXJnZW50aW5hIiAsIGwyID09ICJDYXBpdGFsIEZlZGVyYWwiICwgY3VycmVuY3kgPT0gIlVTRCIgLCBvcGVyYXRpb25fdHlwZSA9PSAiVmVudGEiICwgcHJvcGVydHlfdHlwZSA9PSAiRGVwYXJ0YW1lbnRvIiB8IHByb3BlcnR5X3R5cGUgPT0gIkNhc2EiIHwgcHJvcGVydHlfdHlwZSA9PSAiUEgiKSAlPiUNCiAgc2VsZWN0KGlkLCBsMywgcm9vbXMsIGJlZHJvb21zLCBiYXRocm9vbXMsIHN1cmZhY2VfdG90YWwsIHN1cmZhY2VfY292ZXJlZCwgcHJpY2UsIHByb3BlcnR5X3R5cGUpDQpnbGltcHNlKERhdG9zKQ0KYGBgDQoNCjxicj4NCjxoND4yLiBBbsOhbGlzaXMgZXhwbG9yYXRvcmlvIChJKTwvaDQ+DQo8YnI+DQoNCmEpIENhbnRpZGFkIGRlIHZhbG9yZXMgw7puaWNvcyBwYXJhIGNhZGEgdmFyaWFibGUNCmBgYHtyfQ0KRGF0b3MgJT4lDQogICAgbWFwX2RmKGZ1bmN0aW9uKHgpIGxlbmd0aCh1bmlxdWUoeCkpKSAlPiUgaGVhZA0KYGBgDQoNCkNhbnRpZGFkIGRlIHZhbG9yZXMgZmFsdGFudGVzIChOQXMpIHBhcmEgY2FkYSB2YXJpYWJsZQ0KYGBge3J9DQpEYXRvcyAlPiUNCiAgICBtYXBfZGYoZnVuY3Rpb24oeCkgc3VtKGlzLm5hKHgpKSkgJT4lIGhlYWQNCmBgYA0KPGJyPg0KYikgTWF0cml6IGRlIGNvcnJlbGFjacOzbiBwYXJhIGxhcyB2YXJpYWJsZXMgbnVtw6lyaWNhcw0KYGBge3J9DQpEYXRvcyAlPiUgc2VsZWN0KC1jKGlkLGwzLHByb3BlcnR5X3R5cGUpKSAlPiUNCiAgY29ycmVsYXRlKHVzZSA9ICJjb21wbGV0ZS5vYnMiLCBkaWFnb25hbCA9IDEpICU+JSANCiAgc2hhdmUoKSAlPiUgDQogIGZhc2hpb24oKQ0KYGBgDQo8YnI+DQo8aDQ+My4gUHJlcGFyYWNpw7NuIGRlIGxvcyBkYXRvcyAoSUkpPC9oND4NCjxicj4NCmEpIExhIHZhcmlhYmxlIGJlZHJvb21zIHByZXNlbnRhIHVuYSBhbHRhIHByb3BvcmNpw7NuIGRlIHZhbG9yZXMgZmFsdGFudGVzIHkgdW5hIGZ1ZXJ0ZSBjb3JyZWxhY2nDs24gY29uIGxhIHZhcmlhYmxlIHJvb21zLiBFcyBwb3IgZWxsbyBxdWUgbGEgZWxpbWluYW1vcy4NCmIpIEVsaW1pbmFjacOzbiBkZSByZWdpc3Ryb3MgcXVlIHByZXNlbnRhbiB2YWxvcmVzIGZhbHRhbnRlcy4NCmBgYHtyfQ0KRGF0b3MgPC0gRGF0b3MgJT4lIHNlbGVjdCgtYyhiZWRyb29tcykpICU+JSBkcm9wX25hKCkNCmdsaW1wc2UoRGF0b3MpDQpgYGANCjxicj4NCjxoND40LiBBbsOhbGlzaXMgZXhwbG9yYXRvcmlvIChJSSk8L2g0Pg0KPGJyPg0KYSkgRXN0YWTDrXN0aWNhcyBkZXNjcmlwdGl2YXMgcGFyYSBsYSB2YXJpYWJsZSBwcmVjaW86IGN1YXJ0aWxlcywgcHJvbWVkaW8sIG3DrW5pbW8geSBtw6F4aW1vLiBQb2RlbW9zIG9ic2VydmFyIHF1ZSBlbCBtw61uaW1vIGVzIHByb2JhYmxlbWVudGUgdW4gb3V0bGllciwgYWwgaWd1YWwgcXVlIGVsIG3DoXhpbW8uIExhIG1lZGlhIHNlIGVuY3VlbnRyYSBhIGxhIGRlcmVjaGEgZGUgbGEgbWVkaWFuYSBkZWJpZG8gYSBsb3MgdmFsb3JlcyBhbHRvcyBkZSBsYSB2YXJpYWJsZS4NCmBgYHtyfQ0Kc3VtbWFyeShEYXRvcyRwcmljZSkNCmBgYA0KDQpIaXN0b2dyYW1hIGRlIGxhIHZhcmlhYmxlIHByZWNpbzogcG9kZW1vcyBvYnNlcnZhciBxdWUgbGEgbWF5b3LDrWEgZGUgbGFzIG9ic2VydmFjaW9uZXMgc2UgZW5jdWVudHJhbiBlbnRyZSBsb3MgMTAwIG1pbCB5IGxvcyAyMDAgbWlsIFVTRC4gRXhpc3RlbiB2YWxvcmVzIGFsdG9zIGRlIGVzdGEgdmFyaWFibGUgcXVlIHZhbiBoYXN0YSA2IG1pbGxvbmVzIGRlIFVTRC4NCmBgYHtyfQ0KRGF0b3MgJT4lIGdncGxvdChhZXMocHJpY2UpKSArIGdlb21faGlzdG9ncmFtKCkNCmBgYA0KDQo8YnI+DQpiKSBFc3RhZMOtc3RpY2FzIGRlc2NyaXB0aXZhcyBwYXJhIGxhIHZhcmlhYmxlIHByZWNpbzogY3VhcnRpbGVzLCBwcm9tZWRpbywgbcOtbmltbyB5IG3DoXhpbW8gcG9yIGNhZGEgdGlwbyBkZSBwcm9waWVkYWQuIFNlIG9ic2VydmEgcXVlIGxhcyBjYXNhcyB2YWxlbiBhbHJlZGVkb3IgZGVsIGRvYmxlIGRlIGxvcyBkZXBhcnRhbWVudG9zIHkgUEhzIHkgcXVlIGxvcyBkZXBhcnRhbWVudG9zIHkgUEhzIHRpZW5lbiBwcmVjaW9zIHJlbGF0aXZhbWVudGUgcGFyZWNpZG9zIGVuIHByb21lZGlvLkxhIG1lbm9yIG1lZGlhbmEgZXMgbGEgZGUgbG9zIGRlcGFydGFtZW50b3MsIGxhIG1lZGlhIGVuIGVzdGUgY2FzbyBzZSBlbmN1ZW50cmEgYmFzdGFudGUgYWxlamFkYSBkZSBsYSBtZWRpYW5hLCBwb3IgbG8gcXVlIHBvZGVtb3MgZGVkdWNpciBxdWUgbG9zIG91dGxpZXJzIHN1cGVyaW9yZXMgZXN0w6FuIGFmZWN0YW5kbyBsYSBtZWRpYS4NCmBgYHtyfQ0KRGF0b3MgJT4lIA0KICBncm91cF9ieShwcm9wZXJ0eV90eXBlKSAlPiUNCiAgc3VtbWFyaXNlKHF1YW50aWxlMSA9IHF1YW50aWxlKHByaWNlLCBwcm9icyA9IDAuMjUpLCBtZWRpYW4ocHJpY2UpLCBxdWFudGlsZTMgPSBxdWFudGlsZShwcmljZSwgcHJvYnMgPSAwLjc1KSwgbWVhbihwcmljZSksIG1pbihwcmljZSksIG1heChwcmljZSkpDQpgYGANCjxicj4NCmMpIEJveHBsb3QgZGUgbGEgdmFyaWFibGUgcHJlY2lvIHBvciB0aXBvIGRlIHByb3BpZWRhZDogcG9kZW1vcyBvYnNlcnZhciBudW1lcm9zb3Mgb3V0bGllcnMgZW4gbG9zIHZhbG9yZXMgc3VwZXJpb3JlcyBkZWwgcHJlY2lvIGRlIGxhcyBjYXNhcyB5IGRlcGFydGFtZW50b3MsIHF1ZSBzb24gbWF5b3JlcyBxdWUgbG9zIGRlIGxvcyBQSHMuDQpgYGB7cn0NCkRhdG9zICU+JSBnZ3Bsb3QoYWVzKHggPSBwcm9wZXJ0eV90eXBlLCB5ID0gcHJpY2UsIGdyb3VwID0gcHJvcGVydHlfdHlwZSwgZmlsbCA9IHByb3BlcnR5X3R5cGUgKSkgKw0KICBnZW9tX2JveHBsb3QoKQ0KYGBgDQo8YnI+DQpkKSBDb3JyZWxvZ3JhbWEgZGUgbGFzIHZhcmlhYmxlcyBudW3DqXJpY2FzOiBzZSBtdWVzdHJhIGxhIGZ1ZXJ6YSB5IGVsIHNlbnRpZG8gZGUgbGEgY29ycmVsYWNpw7NuIGRlbCBjb25qdW50byBkZSB2YXJpYWJsZXMuIFRvZGFzIGxhcyBjb3JyZWxhY2lvbmVzIHNvbiBwb3NpdGl2YXMgc2llbmRvIGxhIG3DoXMgZnVlcnRlIGxhIGRlIHN1cmZhY2VfdG90YWwgY29udHJhIHN1cmZhY2VfY292ZXJlZC4gRW4gZ2VuZXJhbCBsYXMgY29ycmVsYWNpb25lcyBzb24gYmFqYXMsIHByb2JhYmxlbWVudGUgcG9yIGxhIGluZmx1ZW5jaWEgZGUgbG9zIG91dGxpZXJzLg0KYGBge3J9DQpEYXRvcyAlPiUgc2VsZWN0KC1jKGlkLGwzLHByb3BlcnR5X3R5cGUpKSAlPiUgDQogIGdnY29ycihwYWxldHRlID0gIlJkQnUiLCBsYWJlbCA9IFRSVUUpDQpgYGANCjxicj4NCjxoND41LiBPdXRsaWVyczwvaDQ+DQo8YnI+DQphKSBFbGltaW5hY2nDs24gZGUgbG9zIG91dGxpZXJzIGRlIGxhIHZhcmlhYmxlIHByZWNpbzoNCmBgYHtyfQ0KRGF0b3MgJT4lIA0KICBncm91cF9ieShzdXJmYWNlX3RvdGFsKSAlPiUNCiAgc3VtbWFyaXNlKHF1YW50aWxlMSA9IHF1YW50aWxlKHByaWNlLCBwcm9icyA9IDAuMjUpLCBtZWRpYW4ocHJpY2UpLCBxdWFudGlsZTMgPSBxdWFudGlsZShwcmljZSwgcHJvYnMgPSAwLjc1KSwgbWVhbihwcmljZSksIG1pbihwcmljZSksIG1heChwcmljZSkpDQpgYGANCkNvbW8gc2UgcHVlZGUgb2JzZXJ2YXIgZW4gbGEgc2FsaWRhIGRlIGxhIGVzdGFkw61zdGljYSBkZXNjcmlwdGl2YSBkZSBsYSB2YXJpYWJsZSBwcmVjaW8gcG9yIGNhZGEgY2FudGlkYWQgZGUgbWV0cm9zIGN1YWRyYWRvcywgZXhpc3RlbiB2YWxvcmVzIG11eSBiYWpvcyBkZSBzdXBlcmZpY2llLCBwb3IgZWplbXBsbyAxMCBtZXRyb3MgY3VhZHJhZG9zLiBTZSBkZWNpZGUgZWxpbWluYXIgbGFzIG9ic2VydmFjaW9uZXMgY29uIG1lbm9zIGRlIDE2IG1ldHJvcyBjdWFkcmFkb3MgcG9ycXVlIHByb2JhYmxlbWVudGUgcGVydGVuZXpjYW4gYSBjb2NoZXJhcyB5IGFsZ3Vub3MgdGllbmVuIHByZWNpb3Mgc29uIG11eSBlbGV2YWRvcywgY29tbyBwb3IgZWplbXBsbyAyMjAgbWlsIFVTRCBwb3IgdW5hIHByb3BpZWRhZCBkZSAxMCBtZXRyb3MgY3VhZHJhZG9zLiANCjxicj4NClRhbWJpw6luIHNlIGVsaW1pbmEgbGEgb2JzZXJ2YWNpw7NuIGRlIDEyNjA2MiBtZXRyb3MgY3VhZHJhZG9zIHBvcnF1ZSBlcyAxMCB2ZWNlcyBtYXlvciBhIGxvcyBvdHJvcyB2YWxvcmVzIGFsdG9zIG9ic2VydmFkb3MgeSBlbCB2YWxvciBkZSBlc3RhIHByb3BpZWRhZCBlcyBkZSBsYSBtaXNtYSBtYWduaXR1ZCBkZSBsYXMgb3RyYXMuIFBhcmEgZWxsbyBzZSBwb25lIHVuYSBjb3RhIHN1cGVyaW9yIGRlIDE3IG1pbCBtZXRyb3MgY3VhZHJhZG9zLiBObyBzZSBlbGltaW5hbiBsb3Mgb3Ryb3MgdmFsb3JlcyBhbHRvcyBwb3JxdWUgcHVlZGVuIHByb3ZlbmlyIGRlIHRlcnJlbm9zIG11eSBncmFuZGVzLg0KPGJyPg0KRXN0dWRpYW1vcyBhaG9yYSBsYSB2YXJpYWJsZSBwcmVjaW8gcG9yIG1ldHJvIGN1YWRyYWRvOg0KYGBge3J9DQpzdW1tYXJ5KERhdG9zJHByaWNlL0RhdG9zJHN1cmZhY2VfdG90YWwpDQpgYGANClBhcmEgZGVzY2FydGFyIGxvcyB2YWxvcmVzIHF1ZSBubyBzdWVuYW4gcmF6b25hYmxlcyBkZSBsYSB2YXJpYWJsZSBwcmVjaW8gcG9yIG1ldHJvIGN1YWRyYWRvLCBwb2RlbW9zIHBvbmVyIHVuYSBjb3RhIGluZmVyaW9yIGRlIG1pbCBVU0QgcG9yIG1ldHJvIGN1YWRyYWRvIHkgdW5hIGNvdGEgc3VwZXJpb3IgZGUgNiBtaWwgVVNEIHBvciBtZXRybyBjdWFkcmFkbyAocXVlIGVzIGVsIHByZWNpbyBwb3IgbWV0cm8gY3VhZHJhZG8gZGVsIGJhcnJpbyBtw6FzIGNhcm8gZGUgQ2FwaXRhbCBGZWRlcmFsLCBxdWUgZXMgUHVlcnRvIE1hZGVybykuDQpgYGB7cn0NCnF1YW50aWxlKERhdG9zJHByaWNlL0RhdG9zJHN1cmZhY2VfdG90YWwsIHByb2JzPWMoLjAxNjUsIC45ODIpKQ0KYGBgDQpDb21vIHNlIHB1ZWRlIG9ic2VydmFyIGxhcyBjb3RhcyBlc3RhYmxlY2lkYXMgZGVzY2FydGFuIGVsIDEsNjUlIGRlIGxvcyBkYXRvcyBwb3IgZGViYWpvIHkgZWwgMSw4JSBkZSBsb3MgZGF0b3MgcG9yIGFycmliYS4gRGUgdW4gdG90YWwgZGUgNTEyMTAgb2JzZXJ2YWNpb25lcywgbm9zIHF1ZWRhbW9zIGNvbiA0OTMzOSBvYnNlcnZhY2lvbmVzLg0KYGBge3J9DQpEYXRvcyA8LSBEYXRvcyAlPiUgDQogIGZpbHRlcihzdXJmYWNlX3RvdGFsID4gMTYgLCBzdXJmYWNlX3RvdGFsIDwgMTcwMDAgLCBwcmljZS9zdXJmYWNlX3RvdGFsID4gMTAwMCAsIHByaWNlL3N1cmZhY2VfdG90YWwgPCA2MDAwKSANCiBnbGltcHNlKERhdG9zKSAgDQpgYGANCjxicj4NCjxoND42LiBBbsOhbGlzaXMgZXhwbG9yYXRvcmlvIChJSUkpPC9oND4NCjxicj4NCmEpIEVzdGFkw61zdGljYXMgZGVzY3JpcHRpdmFzIHBhcmEgbGEgdmFyaWFibGUgcHJlY2lvOiBjdWFydGlsZXMsIHByb21lZGlvLCBtw61uaW1vIHkgbcOheGltby4gVGFudG8gbG9zIGN1YXJ0aWxlcyBjb21vIGVsIHByb21lZGlvIHkgbGEgbWVkaWFuYSBzZSByZWR1Y2VuIHBvcnF1ZSB5YSBubyBzZSBlbmN1ZW50cmFuIGFmZWN0YWRvcyBwb3IgZWwgb3V0bGllciBkZSA2IG1pbGxvbmVzIGRlIFVTRC4gRWwgbcOtbmltbyBlcyB1biBvcmRlbiBtYXlvciBxdWUgZWwgbcOtbmltbyBhbnRlcyBkZSBlbGltaW5hciBsb3Mgb3V0bGllcnMuDQpgYGB7cn0NCnN1bW1hcnkoRGF0b3MkcHJpY2UpDQpgYGANCg0KSGlzdG9ncmFtYSBkZSBsYSB2YXJpYWJsZSBwcmVjaW86IG5vIGV4aXN0ZW4gY2FtYmlvcyBub3RhYmxlcyBlbiBlbCBncsOhZmljbywgc8OzbG8gdW5hIGxldmUgYmFqYSBlbiBsYSBjYW50aWRhZCBkZSBvYnNlcnZhY2lvbmVzIGRlIGxvcyB2YWxvcmVzIGJham9zLiANCmBgYHtyfQ0KRGF0b3MgJT4lIGdncGxvdChhZXMocHJpY2UpKSArIGdlb21faGlzdG9ncmFtKCkNCmBgYA0KDQo8YnI+DQpiKSBFc3RhZMOtc3RpY2FzIGRlc2NyaXB0aXZhcyBwYXJhIGxhIHZhcmlhYmxlIHByZWNpbzogY3VhcnRpbGVzLCBwcm9tZWRpbywgbcOtbmltbyB5IG3DoXhpbW8gcG9yIGNhZGEgdGlwbyBkZSBwcm9waWVkYWQuIEVuIGdlbmVyYWwgc2UgbWFudGllbmVuIGxhcyByZWxhY2lvbmVzIG9ic2VydmFkYXMgZW4gZWwgYW50ZXJpb3IgYW7DoWxpc2lzLiBMb3MgbWF5b3JlcyBjYW1iaW9zIHNlIHByZXNlbnRhbiBlbiBlbCBwcm9tZWRpbzogZW4gbGFzIGNhc2FzIGVsIHByb21lZGlvIGF1bWVudGEgYXByb3hpbWFkYW1lbnRlIGVuIDEwIG1pbCBVU0QsIGVuIGxvcyBkZXBhcnRhbWVudG9zIGRpc21pbnV5ZSBlbiAyMyBtaWwgVVNEIHkgZW4gbG9zIFBIcyBzZSBtYW50aWVuZSBhcHJveGltYWRhbWVudGUgaWd1YWwuDQpgYGB7cn0NCkRhdG9zICU+JSANCiAgZ3JvdXBfYnkocHJvcGVydHlfdHlwZSkgJT4lDQogIHN1bW1hcmlzZShxdWFudGlsZTEgPSBxdWFudGlsZShwcmljZSwgcHJvYnMgPSAwLjI1KSwgbWVkaWFuKHByaWNlKSwgcXVhbnRpbGUzID0gcXVhbnRpbGUocHJpY2UsIHByb2JzID0gMC43NSksIG1lYW4ocHJpY2UpLCBtaW4ocHJpY2UpLCBtYXgocHJpY2UpKQ0KYGBgDQo8YnI+DQpjKSBCb3hwbG90IGRlIGxhIHZhcmlhYmxlIHByZWNpbyBwb3IgdGlwbyBkZSBwcm9waWVkYWQ6IG5vIHNlIG9ic2VydmFuIGNhbWJpb3Mgc2lnbmlmaWNhdGl2b3MsIG3DoXMgcXVlIGxhIGRpc21pbnVjacOzbiBkZSBsYSBjYW50aWRhZCBkZSB2YWxvcmVzIGFsdG9zIGVuIGxvcyBwcmVjaW9zIGRlIGxvcyBkZXBhcnRhbWVudG9zLg0KYGBge3J9DQpEYXRvcyAlPiUgZ2dwbG90KGFlcyh4ID0gcHJvcGVydHlfdHlwZSwgeSA9IHByaWNlLCBncm91cCA9IHByb3BlcnR5X3R5cGUsIGZpbGwgPSBwcm9wZXJ0eV90eXBlICkpICsNCiAgZ2VvbV9ib3hwbG90KCkNCmBgYA0KPGJyPg0KZCkgQ29ycmVsb2dyYW1hIGRlIGxhcyB2YXJpYWJsZXMgbnVtw6lyaWNhczogZW4gZXN0ZSBjYXNvLCBsb3MgY2FtYmlvcyBzb24gc2lnbmlmaWNhdGl2b3MuIEF1bWVudGEgbGEgZnVlcnphIGRlIGxhIGNvcnJlbGFjacOzbiBkZWwgY29uanVudG8gZGUgdmFyaWFibGVzIGVuIGxhIG1heW9yw61hIGRlIGxvcyBjYXNvcy4gU8OzbG8gc2UgbWFudGllbmUgbGEgY29ycmVsYWNpw7NuIGVudHJlIHJvb21zIHkgYmF0aHJvb21zLiBDb21vIGVsaW1pbmFtb3MgbG9zIG91dGxpZXJzIGVzdGFibGVjaWVuZG8gdW4gcmFuZ28gZW4gbGEgdmFyaWFibGUgcHJlY2lvIHBvciBtZXRybyBjdWFkcmFkbywgc2Ugbm90YSB1biBhdW1lbnRvIGltcG9ydGFudGUgZW4gbGEgY29ycmVsYWNpb24gZGUgcHJlY2lvIHkgc3VwZXJmaWVjaWUgdG90YWwuIFRhbWJpw6luIGVzIGltcG9ydGFudGUgZWwgaW5jcmVtZW50byBlbiBsYSBjb3JyZWxhY2nDs24gZGUgc3VyZmFjZV90b3RhbCBjb24gcm9vbXMgeSBiYXRocm9vbXMuDQpgYGB7cn0NCkRhdG9zICU+JSBzZWxlY3QoLWMoaWQsbDMscHJvcGVydHlfdHlwZSkpICU+JSANCiAgZ2djb3JyKHBhbGV0dGUgPSAiUmRCdSIsIGxhYmVsID0gVFJVRSkNCmBgYA0KPGJyPg0KPGg0PjcuIE1vZGVsbyBsaW5lYWw8L2g0Pg0KPGJyPg0KYSkgTW9kZWxvIDE6IE1vZGVsbyBsaW5lYWwgc2ltcGxlIHBhcmEgZXhwbGljYXIgZWwgcHJlY2lvIGVuIGZ1bmNpw7NuIGRlIGxhcyBoYWJpdGFjaW9uZXMgKHJvb21zKQ0KDQpgYGB7cn0NCmFqdXN0ZTEgPC0gbG0oRGF0b3MkcHJpY2V+RGF0b3Mkcm9vbXMpDQpzdW1tYXJ5KGFqdXN0ZTEpDQpgYGANCjxicj4NCk1vZGVsbyAyOiBNb2RlbG8gbGluZWFsIHNpbXBsZSBwYXJhIGV4cGxpY2FyIGVsIHByZWNpbyBlbiBmdW5jacOzbiBkZSBsYSBzdXBlcmZpY2llIHRvdGFsIChzdXJmYWNlX3RvdGFsKQ0KYGBge3J9DQphanVzdGUyIDwtIGxtKERhdG9zJHByaWNlfkRhdG9zJHN1cmZhY2VfdG90YWwpDQpzdW1tYXJ5KGFqdXN0ZTIpDQpgYGANCjxicj4NCmIpIEVsIGNvZWZpY2llbnRlIGJldGExIGVuIGVsIG1vZGVsbyAxIGluZGljYSBxdWUgcG9yIGNhZGEgaW5jcmVtZW50byBlbiB1bmEgdW5pZGFkIGRlIGxhIHZhcmlhYmxlIGhhYml0YWNpb25lcyAocm9vbXMpLCBlbCBwcmVjaW8gc3ViZSBVU0QgOTU5MzYuOC4gRW4gZWwgbW9kZWxvIDIsIGJldGExIGluZGljYSBxdWUgcG9yIGNhZGEgaW5jcmVtZW50byBlbiB1bmEgdW5pZGFkIGRlIGxhIHZhcmlhYmxlIHN1cGVyZmljaWUgdG90YWwgKHN1cmZhY2VfdG90YWwpLCBlbCBwcmVjaW8gc3ViZSBVU0QgMjQzMC40ODguDQo8YnI+DQpFbiBlbCBtb2RlbG8gMSBsYSBlc3RpbWFjacOzbiBkZSDPgyBxdWUgcmVzdWx0YSBzZXIgMTcwMDAwLCBpbmRpY2FkYSBwb3IgUmVzaWR1YWwgc3RhbmRhcmQgZXJyb3IuIFNpIGNvbXBhcmFtb3MgbGEgZXN0aW1hY2nDs24gZGUgz4MgIGRlbCBtb2RlbG8gMSBjb24gbGEgb2J0ZW5pZGEgZW4gZWwgbW9kZWxvIDIsIHZlbW9zIHF1ZSBlbCBkZXN2w61vIGVzdMOhbmRhciBzZSByZWR1am8gY29uc2lkZXJhYmxlbWUuDQo8YnI+DQpFbiBsb3MgZG9zIG1vZGVsb3MgcG9kZW1vcyB2ZXIgcXVlIGxhIGRpc3RyaWJ1Y2nDs24gZGUgbG9zIHJlc2lkdW9zIG5vIHBhcmVjZSBzZXIgbXV5IHNpbcOpdHJpY2EuIEVzbyBzaWduaWZpY2EgcXVlIGVsIG1vZGVsbyBwcmVkaWNlIGNpZXJ0b3MgcHVudG9zIHF1ZSBzZSBhbGVqYW4gbXVjaG8gZGUgbG9zIHB1bnRvcyByZWFsZXMgb2JzZXJ2YWRvcy4NCjxicj4NClJeMiBub3MgZGljZSBxdcOpIHByb3BvcmNpw7NuIGRlIGxhIHZhcmlhYmlsaWRhZCB0b3RhbCBlbiBsYSB2YXJpYWJsZSBZIHB1ZWRlIHNlciBleHBsaWNhZGEgcG9yIGxhIHZhcmlhYmxlIHJlZ3Jlc29yYSwgZW4gY29uc2VjdWVuY2lhIGVzIHVuYSBtZWRpZGEgZGUgbGEgY2FwYWNpZGFkIGRlIHByZWRpY2Npw7NuIGRlbCBtb2RlbG8uDQo8YnI+DQpSXjIgdGFtYmnDqW4gcHVlZGUgdmVyc2UgY29tbyB1bmEgbWVkaWRhIGRlIGxhIGZ1ZXJ6YSBkZSBsYSBhc29jaWFjacOzbiBsaW5lYWwgZW50cmUgWCBlIFkuIE1pZW50cmFzIG1heW9yIHNlYSBSXjIgbWVub3IgZXMgbGEgU1NSZXMgeSBwb3IgbG8gdGFudG8sIG3DoXMgY2VyY2Fub3MgZXN0w6FuIGxvcyBwdW50b3MgYSBsYSByZWN0YS4NCjxicj4NClBhcmEgbG9zIGRhdG9zIGRlIGxhIHJlZ3Jlc2nDs24gZGVsIHByZWNpbyB2ZXJzdXMgaGFiaXRhY2lvbmVzLCBlbiBsYSBzYWxpZGEgZGVsIG1vZGVsbyBsaW5lYWwgMSB2ZW1vcyBxdWUgUl4yIGVzIDAuMzY3NywgcG9yIGxvIHRhbnRvIGxhIHJlbGFjacOzbiBsaW5lYWwgZXMgbGV2ZW1lbnRlIGZ1ZXJ0ZS4gRXN0byBzaWduaWZpY2EgcXVlIGVsIDM2LDc3ICUgZGUgbGEgdmFyaWFiaWxpZGFkIG9ic2VydmFkYSBlbiBsb3MgdmFsb3JlcyBkZWwgcHJlY2lvIHF1ZWRhIGV4cGxpY2FkYSBwb3IgbGEgcmVsYWNpw7NuIGxpbmVhbCBlbnRyZSBlbCBwcmVjaW8geSBsYXMgaGFiaXRhY2lvbmVzLg0KPGJyPg0KRW4gY2FtYmlvIGVuIG1vZGVsbyBsaW5lYWwgMiBkZSBwcmVjaW8gZW4gZnVuY2nDs24gZGUgbGEgc3VwZXJmaWNpZSB0b3RhbCwgdmVtb3MgcXVlIFJeMiA9IDAuNjU4NC4gRXN0ZSB2YWxvciBpbXBsaWNhIHVuYSByZWxhY2nDs24gbGluZWFsIG1vZGVyYWRhbWVudGUgZnVlcnRlIGVudHJlIGVsIHByZWNpbyB5IGxhIHN1cGVyZmljaWUgdG90YWwuIEVuIHBhcnRpY3VsYXIsIGVsIDY1LDg0ICUgZGUgbGEgdmFyaWFiaWxpZGFkIG9ic2VydmFkYSBlbiBsb3MgdmFsb3JlcyBkZWwgcHJlY2lvIHF1ZWRhIGV4cGxpY2FkYSBwb3IgbGEgcmVsYWNpw7NuIGxpbmVhbCBlbnRyZSBlbCBwcmVjaW8geSBsYSBzdXBlcmZpY2llIHRvdGFsLiBFbCByZXN0YW50ZSAxMDAgJSDiiJIgNjUsODQgJSA9IDM0LDE2ICUgZGUgbGEgdmFyaWFiaWxpZGFkIG5vIHF1ZWRhIGV4cGxpY2FkYSBwb3IgZXN0YSByZWxhY2nDs24uDQo8YnI+DQo8YnI+DQpjKSBFbnRyZSBsb3MgZG9zIG1vZGVsb3MsIGVzIG1lam9yIHV0aWxpemFyIGVsIG1vZGVsbyAyIHBhcmEgcHJlZGVjaXIgZWwgcHJlY2lvIHlhIHF1ZSBleHBsaWNhIHVuIHBvcmNlbnRhamUgbWF5b3IgZGUgbGEgdmFyaWFiaWxpZGFkIGRlIGxvcyB2YWxvcmVzIGRlbCBwcmVjaW8uIEVzdGEgcmVncmVzacOzbiBsaW5lYWwgcHVlZGUgc2VyIG9ic2VydmFkYSBlbiBlbCBzaWd1aWVudGUgZ3LDoWZpY28uIA0KYGBge3J9DQpnZ3Bsb3QoRGF0b3MsIGFlcyh4PXN1cmZhY2VfdG90YWwseT1wcmljZSkpKw0KICBnZW9tX3BvaW50KGFlcyhjb2xvdXI9cHJvcGVydHlfdHlwZSksYWxwaGE9MC4zKSsNCiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gJ2xtJykNCmBgYA0KDQo=