Axel Mantalian (axel.manta@gmail.com)

1) Preparación de los datos (I)

Cargo las librerías necesarias

library(tidyverse)
library(corrr)
library(ggplot2)
library(ggthemes)
library(GGally)
library(data.table)
library(scales)

Leo el archivo csv

datos <- read_csv("./data/ar_properties.csv",progress = FALSE)

Estructura del dataset, conformado por 24 variables y 388891 observaciones de avisos inmobiliarios.

# Muestro la estructura 
head(datos)
glimpse(datos)
Observations: 388,891
Variables: 24
$ id              <chr> "S0we3z3V2JpHUJreqQ2t/w==", "kMxcmAS8NvrynGBVbMOEaQ==", "Ce3ojF+ZTOkB8d+LI9dpxg=="…
$ ad_type         <chr> "Propiedad", "Propiedad", "Propiedad", "Propiedad", "Propiedad", "Propiedad", "Pro…
$ start_date      <date> 2019-04-14, 2019-04-14, 2019-04-14, 2019-04-14, 2019-04-14, 2019-04-14, 2019-04-1…
$ end_date        <date> 2019-06-14, 2019-04-16, 9999-12-31, 9999-12-31, 2019-07-09, 2019-08-08, 2019-07-1…
$ created_on      <date> 2019-04-14, 2019-04-14, 2019-04-14, 2019-04-14, 2019-04-14, 2019-04-14, 2019-04-1…
$ lat             <dbl> -34.94331, -34.63181, NA, -34.65471, -34.65495, -32.93547, -34.65183, -34.91213, -…
$ lon             <dbl> -54.92966, -58.42060, NA, -58.79089, -58.78712, -60.68398, -58.65912, -54.84749, -…
$ l1              <chr> "Uruguay", "Argentina", "Argentina", "Argentina", "Argentina", "Argentina", "Argen…
$ l2              <chr> "Maldonado", "Capital Federal", "Bs.As. G.B.A. Zona Norte", "Bs.As. G.B.A. Zona Oe…
$ l3              <chr> "Punta del Este", "Boedo", NA, "Moreno", "Moreno", "Rosario", "Ituzaingó", "José I…
$ l4              <chr> NA, NA, NA, "Moreno", "Moreno", NA, "Ituzaingó", 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…
$ l6              <lgl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
$ rooms           <dbl> 2, NA, 2, 2, 2, 4, NA, 6, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, 1, 1, 1,…
$ bedrooms        <dbl> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA…
$ bathrooms       <dbl> 1, NA, 1, 2, 3, 1, 3, 3, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, 1, NA, 1, 1, 1, 1…
$ surface_total   <dbl> 45, NA, 200, 460, 660, NA, 70, NA, 1300, 405, 352, 373, 360, 1325, 250, 80142, 101…
$ surface_covered <dbl> 40, NA, NA, 100, 148, 89, 122, NA, NA, NA, NA, NA, NA, 2, NA, NA, NA, NA, 54, 180,…
$ price           <dbl> 13000, 0, NA, NA, NA, NA, NA, NA, 0, NA, 0, NA, NA, NA, NA, NA, 0, 0, 0, NA, 0, 0,…
$ currency        <chr> "UYU", 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", "…
$ title           <chr> "Departamento - Roosevelt", "PH - Boedo", "Ituzaingo  1100 - $ 1 - Casa Alquiler",…
$ property_type   <chr> "Departamento", "PH", "Casa", "Casa", "Casa", "Casa", "Casa", "Casa", "Lote", "Lot…
$ operation_type  <chr> "Alquiler", "Venta", "Alquiler", "Venta", "Venta", "Venta", "Venta", "Alquiler", "…

Filtrado y selección de variables

Aplicando diversos filtros, reduzco el dataset a un total de 61905 observaciones. También, de las 24 variables originales solamente conservo 9.

Los filtros aplicados son:

Las variables seleccionadas son:

datos <- datos %>% 
  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)
datos

2) Análisis exploratorios (I)

Se comienza el análisis exploratorio verificando la cantidad de valores únicos y de valores faltantes (NAs) para cada una de las variables seleccionadas.

# Valores únicos
distintos <- map_dfc(datos,function(x) n_distinct(x))
# Valores faltantes
faltantes <- map_dfc(datos,function(x) sum(is.na(x)))
# Junto los datos y les doy formato de tabla
t_unicos_faltantes <- rbind(distintos,faltantes) %>% 
  transpose() 
  
rownames(t_unicos_faltantes) <- colnames(distintos)
colnames(t_unicos_faltantes) <- c('Valores únicos','Valores faltantes')
t_unicos_faltantes

Podemos observar de la tabla resultante, que los únicos campos que no presentan valores faltantes son price y property_type. Por otro lado, particularmente la variable bedrooms contiene una altísima proporción de faltantes (más del 40%). El resto de las variables presentan proporciones más aceptables de faltantes como para no considerarlas candidatas a ser eliminadas.

En cuanto a los valores únicos, se puede destacar que la variable categórica property_type posee 3 posibles valores (Casa, Departamento y PH) y variable l3 que corresponder al barrio posee 58 valores distintos. El resto de las variables, que son numéricas, serán analizadas a continuación mediante la matriz de correlación.

Matriz de correlación

# Formato tabla
datos %>% 
  select_if(.,is.numeric) %>% 
  correlate(use="complete.obs") %>% 
  shave() %>% 
  fashion()
# Formato gráfico
datos %>% 
  select_if(.,is.numeric) %>% 
  correlate(use="complete.obs") %>% 
  rplot() + labs(title= 'Gráfico de correlación')

De la matriz resultante se obtiene que existe una importante correlación entre las variables rooms y bedrooms. Esto resulta particularmente útil ya que, como se observó en el paso anterior, la variable bedrooms posee un alto porcentaje de valores faltantes. Así, se puede proceder a eliminar la variable bedrooms del dataset ya que en gran medida es explicada por la variable rooms. Esto es lo que se hará a continuación.

También cabe mencionar que se observa una correlación medianamente alta entre las variables relacionadas a la superficie de la propiedad (surface_total y surface_covered) lo cual tiene sentido dada la lógica y el conocimiento del dominio.

3) Preparación de los datos (II)

A continuación se elimina la variable bedrooms y adicionalmente todas las observaciones que posean datos faltantes.

El dataset resultante quedará con 51210 observaciones y 8 variables.

# Elimino todos los registros con datos faltantes
datos <- datos %>%
  select(.,-bedrooms) %>% 
  drop_na()
datos

4) Análisis exploratorios (II)

Se analiza la variable price utilizando estadísticas descriptivas (cuartiles, promedio, mínimo y máximo) junto con histogramas que muestren la distribución de la misma.

# Estadísticas descriptivas de la variable precio
datos %>% 
  summarise(min=min(price),
            max=max(price),
            q1 = quantile(price,0.25),
            q2 = quantile(price,0.5),
            q3 = quantile(price,0.75),
            promedio = mean(price)
  ) 
# Histograma con escala original
  ggplot(datos,aes(price))+
  geom_histogram(colour = "darkgreen", fill = "lightblue",bins=50) +
  labs(title = 'Distribución de la variable precio')

  
  # Histograma con escala logarítmica
  ggplot(datos,aes(price)) +
  geom_histogram(colour = "darkgreen", fill = "lightblue",bins=50) +
  labs(title = 'Distribución de la variable precio (escala logarítmica)')+
  scale_x_log10()

NA

El histograma de la variable price, en su escala original (valores en USD), muestra claramente una asimetría a derecha. Esto, sumado a los resultados de las estadísticas descriptivas, nos indica que hay algunos valores extremos que podrían interferir en el análisis. Habrá que analizar con algún criterio si dichos valores extremos, sobre todo los mayores, pueden catalogarse como outliers y tratarlos en consecuencia. La media y la mediana no se encuentran muy cercanas, lo cual también es un indicador de posible presencia de outliers.

Adicionalmente al histograma en escala original se agregó uno escalado de manera logarítmica (log10) que permite observar de manera un poco más clara la distribución ya que no muestra valores absolutos sino relativos, en términos de proporciones. Este último histograma queda de una forma más normalizada.

A continuación calculo nuevamente las estadísticas descriptivas sobre la variable price pero agrupando por property_type. También se grafican boxplots con la misma configuración de variables y finalmente un correlograma general utilizando la librería GGAlly.

# Agrupo por property_type
datos %>% 
  group_by(property_type) %>% 
  summarise(min=min(price),
            max=max(price),
            q1 = quantile(price,0.25),
            q2 = quantile(price,0.5),
            q3 = quantile(price,0.75),
            promedio = mean(price)
  )

Boxplots

# Boxplot de precio, escala original
ggplot(datos,aes(x=property_type,y = price,fill=property_type)) +
  geom_boxplot()+
  labs(title='Boxplot de precio por tipo de propiedad')

# Boxplot de precio, escala logarítmica
ggplot(datos,aes(x=property_type,y = price,fill=property_type)) +
  geom_boxplot()+
  labs(title='Boxplot de precio por tipo de propiedad (escala log10)')+
  scale_y_log10()

Correlograma

# Correlograma
datos %>% 
  select(-id,-l3) %>% 
  ggpairs(., 
        title = "Matriz de correlaciones",
        mapping = aes(colour=property_type))

De estos últimos gráficos podemos observar que las casas son en promedio más caras que los otros tipos de propiedades, y que además presentan una mayor variabilidad en términos de precio. Adicionalmente podemos ver en uno de los scatter plots del correlograma, que hay una cierta linealidad entre las variables rooms y price lo cual es un dato útil para pensar en el posterior modelo lineal que se ajustará.

5) Outliers

Genero estadísticas agrupadas por tipo de propiedad, y agregando también una nueva variable calculada de precio por m2, la cual da una indicación un poco más precisa para detectar datos atípicos.

# Estadísticas descriptivas de la variable precio por m2 (pm2)
datos %>% 
  mutate(pm2 = price/surface_total) %>% 
  arrange(pm2) %>% 
  select(property_type,pm2) %>% 
  group_by(property_type) %>% 
  summarise(min=min(pm2),
            max=max(pm2),
            q1 = quantile(pm2,0.25),
            q2 = quantile(pm2,0.5),
            q3 = quantile(pm2,0.75),
            promedio = mean(pm2),
            iqr = IQR(pm2)
            )
NA

Como criterio para filtrar los outliers se tomarán en cuenta:

  1. Los límites de la variable pm2, definidos por una magnitud mayor al criterio de Fisher. En este caso se consideraron 2.5 veces la distancia IQR, para tener un mayor margen y no eliminar tantos valores de precios altos que no necesariamente corresponden a outliers.
  2. Como el criterio “a” no logra filtrar los valores atípicos muy bajos, se adicionó un filtro que descarta las propiedades cuyo valor pm2 fuera menor a 100 usd.
  3. Por último también se consideró un filtro sobre la variable price que elimina 2 observaciones (una de Casa y otra de Departamento) cuyos valores superaban los 5M USD, monto que daba indicios junto a sus otras variables que se consideraban atípicos.

Con todos estos filtros aplicados, los valores resultantes fueron:

paste('dataset con outliers:',nrow(datos))
[1] "dataset con outliers: 51210"
paste('dataset sin outliers:',nrow(datos_clean))
[1] "dataset sin outliers: 50114"
paste('cantidad de outliers eliminados:',(nrow(datos) - nrow(datos_clean)))
[1] "cantidad de outliers eliminados: 1096"
paste('porcentaje de outliers:',percent((nrow(datos) - nrow(datos_clean)) / nrow(datos)))
[1] "porcentaje de outliers: 2.14%"
# Guardo un nuevo dataframe conservando el campo pm2 (precio por metro cuadrado)
datos_new <- datos %>% 
  mutate(pm2 = price/surface_total)
# Genero el dataframe sin outliers
  
datos_clean <- map_dfr(
  .x = unique(datos_new$property_type),
  .f = function(tprop){
    df <- datos_new %>%
      filter(property_type == tprop)
    q1  <- quantile(df$pm2,0.25)
    q3  <- quantile(df$pm2,0.75)
    iqr <- IQR(df$pm2)
    res <- df %>%
      filter( (pm2 >= q1 - 2.5 * iqr) & (pm2 <= q3 + 2.5 * iqr),
              pm2 > 100,
              price < 5000000
              )
    return(res)
  }
)
# Queda como resultado el nuevo dataframe sin las observaciones atípicas (50114 rows)
datos_clean
# 50114

6) Análisis exploratorios (III)

Repito nuevamente el análisis exploratorio luego de quitar los outliers del dataset, comenzando con estadisticas descriptivas para la variable precio, y continuando con el boxplot y correlograma.

# Estadísticas descriptivas de la variable precio
datos_clean %>% 
  summarise(min=min(price),
            max=max(price),
            q1 = quantile(price,0.25),
            q2 = quantile(price,0.5),
            q3 = quantile(price,0.75),
            promedio = mean(price)
  ) 
# Histograma con escala original
  ggplot(datos_clean,aes(price)) +
  geom_histogram(colour = "darkgreen", fill = "lightblue",bins=50) +
  labs(title = 'Distribución de la variable precio (sin outliers)')

# Agrupo por property_type
datos_clean %>% 
  group_by(property_type) %>% 
  summarise(min=min(price),
            max=max(price),
            q1 = quantile(price,0.25),
            q2 = quantile(price,0.5),
            q3 = quantile(price,0.75),
            promedio = mean(price)
  )

Se puede ver que las medias se acercaron a las medianas al quitar algunos valores muy extremos de precios en el paso anterior. La distribución reflejada en el histograma, si bien sigue presentando cola pesada a derecha, se ha normalizado un poco más.

Vuelvo a graficar el boxplot de la variable precio por tipo de propiedad y el correlograma completo.

Boxplot

# Boxplot de precio, escala original
ggplot(datos_clean,aes(x=property_type,y = price,fill=property_type)) +
  geom_boxplot()+
  labs(title='Boxplot de precio por tipo de propiedad (sin outliers)')

Correlograma
datos_clean %>% 
  select(-id,-l3) %>% 
  ggpairs(., 
        title = "Matriz de correlaciones (sin outliers)",
        mapping = aes(colour=property_type))

Se destaca del correlograma generado a partir del dataset sin outliers que, a diferencia del correlograma previo, ahora se visualiza en el scatter plot una relación lineal mucho más clara entre surface_total y price. Recordando que en el gráfico previo encontramos una relación similar entre rooms y price, tiene sentido que en el próximo paso evaluemos ambos modelos de regresión.

Para finalizar el análisis exploratorio agrego estadísticas descriptivas e histograma de la variable precio por metro cuadrado (pm2), en la cual se pueden ver distribuciones de mayor normalidad. También se ve una clara predominancia de la categoría Departamentos en cuanto a cantidad de observaciones, mayor mediana y media que las Casas y PHs. Esto es particularmente interesante de analizar ya que cuando se computaron las estadísticas descriptivas de la variable price se veía que las casas tienen en promedio un valor absoluto mayor que los Departamentos, pero no ocurre lo mismo en el precio por metro cuadrado.

# Agrego además estadísticas de la variable pm2 (precio por metro cuadrado) y muestro su distribución mediante un histograma agrupado por tipo de propiedad.
datos_clean %>% 
  select(property_type,pm2) %>% 
  group_by(property_type) %>% 
  summarise(min=min(pm2),
            max=max(pm2),
            q1 = quantile(pm2,0.25),
            q2 = quantile(pm2,0.5),
            q3 = quantile(pm2,0.75),
            promedio = mean(pm2),
            iqr = IQR(pm2)
            )
  ggplot(datos_clean,aes(x=pm2,color=property_type)) + 
  geom_histogram()+
  labs(title = 'Distribución de la variable precio/m2')+
  facet_grid(. ~property_type)

NA

7) Modelo lineal

El primer modelo lineal que se muestra a continuación intenta explicar el precio en función de las habitaciones (rooms).

# Modelo lineal 1:
  # target: price
  # predictor: rooms
modelo_rooms <- lm(price ~ rooms, data = datos_clean)
summary(modelo_rooms)

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

Residuals:
     Min       1Q   Median       3Q      Max 
-2198233   -75799   -18967    38033  3224481 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) -15977.4     1639.7  -9.744   <2e-16 ***
rooms        87944.1      527.2 166.818   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 163600 on 50112 degrees of freedom
Multiple R-squared:  0.357, Adjusted R-squared:  0.357 
F-statistic: 2.783e+04 on 1 and 50112 DF,  p-value: < 2.2e-16

Los resultados obtenidos mediante el primer modelo arrojan coeficientes estimados de B0 = -15977.4 y B1 = 87944.1, siendo el primero de ellos el valor correspondiente a la ordenada al origen (en este caso particular sería una propiedad con 0 habitaciones, lo cual no tendría sentido); y el segundo de ellos indica que por cada habitación extra, el valor estimado del inmueble aumenta en 87944.1 USD.

ggplot(datos_clean,aes(x=rooms,y=price))+
  geom_point(aes(color=property_type)) +
  geom_smooth(method = "lm") +
  labs(x="Cantidad de habitaciones",y="Precio (usd)",title="Modelo lineal basado en cantidad de habitaciones")

El segundo modelo lineal intenta explicar el precio (rooms) en función de la superficie total (surface_total) (surface_total)

# Modelo lineal 2:
  # target: price
  # predictor: surface_total
modelo_surface_total <- lm(price ~ surface_total, data = datos_clean)
summary(modelo_surface_total)

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

Residuals:
     Min       1Q   Median       3Q      Max 
-3048394   -44973   -18310    23255  2077479 

Coefficients:
               Estimate Std. Error t value Pr(>|t|)    
(Intercept)   61141.078    938.550   65.14   <2e-16 ***
surface_total  1868.627      7.819  238.97   <2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 139500 on 50112 degrees of freedom
Multiple R-squared:  0.5326,    Adjusted R-squared:  0.5326 
F-statistic: 5.711e+04 on 1 and 50112 DF,  p-value: < 2.2e-16

Los resultados obtenidos mediante el segundo modelo arrojan coeficientes estimados de B0 = 61141.078 y B1 = 1868.627, siendo el primero de ellos el valor correspondiente a la ordenada al origen (en este caso particular sería una propiedad con 0 metros cuadrados de superficie, lo cual no tendría sentido); y el segundo de ellos indica que por cada metro cuadrado extra de superficie, el valor estimado del inmueble aumenta en 1868.627 USD.

ggplot(datos_clean,aes(x=surface_total,y=price))+
  geom_point(aes(color=property_type)) +
  geom_smooth(method = "lm") +
  labs(x="Superficie total (m2)",y="Precio (usd)",title="Modelo lineal basado en superficie total")

Habiendo analizado los resultados de ambos modelos y comparándolos mediante la métrica resultante R2, se concluye que el segundo modelo es más preciso a la hora de estimar el precio del inmueble, alcanzando un valor de variabilidad explicada del 53.26% frente al 35.7% del primer modelo.

LS0tCnRpdGxlOiAnVFAgMTogQW7DoWxpc2lzIGV4cGxvcmF0b3JpbyBlIEludHJvZHVjY2nDs24gYSBSZWdyZXNpw7NuIGxpbmVhbCcKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6CiAgICB0aGVtZTogZmxhdGx5Ci0tLQoKCiMjIyMgQXhlbCBNYW50YWxpYW4gKGF4ZWwubWFudGFAZ21haWwuY29tKQoKCiMjIyMxKSBQcmVwYXJhY2nDs24gZGUgbG9zIGRhdG9zIChJKQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9Cgprbml0cjo6b3B0c19jaHVuayRzZXQod2FybmluZyA9IEZBTFNFKQprbml0cjo6b3B0c19jaHVuayRzZXQobWVzc2FnZSA9IEZBTFNFKQoKYGBgCgpDYXJnbyBsYXMgbGlicmVyw61hcyBuZWNlc2FyaWFzCgpgYGB7cn0KbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoY29ycnIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShnZ3RoZW1lcykKbGlicmFyeShHR2FsbHkpCmxpYnJhcnkoZGF0YS50YWJsZSkKbGlicmFyeShzY2FsZXMpCmBgYAoKTGVvIGVsIGFyY2hpdm8gY3N2CgpgYGB7cn0KZGF0b3MgPC0gcmVhZF9jc3YoIi4vZGF0YS9hcl9wcm9wZXJ0aWVzLmNzdiIscHJvZ3Jlc3MgPSBGQUxTRSkKYGBgCgpFc3RydWN0dXJhIGRlbCBkYXRhc2V0LCBjb25mb3JtYWRvIHBvciAyNCB2YXJpYWJsZXMgeSAzODg4OTEgb2JzZXJ2YWNpb25lcyBkZSBhdmlzb3MgaW5tb2JpbGlhcmlvcy4KCmBgYHtyfQojIE11ZXN0cm8gbGEgZXN0cnVjdHVyYSAKCmhlYWQoZGF0b3MpCgpnbGltcHNlKGRhdG9zKQoKYGBgCgojIyMjRmlsdHJhZG8geSBzZWxlY2Npw7NuIGRlIHZhcmlhYmxlcwoKQXBsaWNhbmRvIGRpdmVyc29zIGZpbHRyb3MsIHJlZHV6Y28gZWwgZGF0YXNldCBhIHVuIHRvdGFsIGRlIDYxOTA1IG9ic2VydmFjaW9uZXMuIFRhbWJpw6luLCBkZSBsYXMgMjQgdmFyaWFibGVzIG9yaWdpbmFsZXMgc29sYW1lbnRlIGNvbnNlcnZvIDkuCgpMb3MgZmlsdHJvcyBhcGxpY2Fkb3Mgc29uOgoKICAtIFBlcnRlbmVjZW4gYSBBcmdlbnRpbmEgeSBDYXBpdGFsIEZlZGVyYWwKICAtIEN1eW8gcHJlY2lvIGVzdMOhIGVuIGTDs2xhcmVzIChVU0QpCiAgLSBFbCB0aXBvIGRlIHByb3BpZWRhZCBlczogRGVwYXJ0YW1lbnRvLCBQSCBvIENhc2EKICAtIEVsIHRpcG8gZGUgb3BlcmFjaW9uIGVzOiBWZW50YQogIApMYXMgdmFyaWFibGVzIHNlbGVjY2lvbmFkYXMgc29uOgoKICAtIGlkCiAgLSBsMwogIC0gcm9vbXMKICAtIGJlZHJvb21zCiAgLSBiYXRocm9vbXMKICAtIHN1cmZhY2VfdG90YWwKICAtIHN1cmZhY2VfY292ZXJlZAogIC0gcHJpY2UKICAtIHByb3BlcnR5X3R5cGUKICAKICAKYGBge3J9CgpkYXRvcyA8LSBkYXRvcyAlPiUgCiAgZmlsdGVyKGwxID09ICJBcmdlbnRpbmEiIAogICAgICAgICAmIGwyID09ICJDYXBpdGFsIEZlZGVyYWwiCiAgICAgICAgICYgY3VycmVuY3kgPT0gIlVTRCIKICAgICAgICAgJiBwcm9wZXJ0eV90eXBlICVpbiUgYygiRGVwYXJ0YW1lbnRvIiwiUEgiLCJDYXNhIikKICAgICAgICAgJiBvcGVyYXRpb25fdHlwZSA9PSAiVmVudGEiKSAlPiUgCiAgc2VsZWN0KGlkLGwzLHJvb21zLGJlZHJvb21zLGJhdGhyb29tcyxzdXJmYWNlX3RvdGFsLHN1cmZhY2VfY292ZXJlZCxwcmljZSxwcm9wZXJ0eV90eXBlKQoKCmRhdG9zCgpgYGAKCgojIyMjMikgQW7DoWxpc2lzIGV4cGxvcmF0b3Jpb3MgKEkpCgpTZSBjb21pZW56YSBlbCBhbsOhbGlzaXMgZXhwbG9yYXRvcmlvIHZlcmlmaWNhbmRvIGxhIGNhbnRpZGFkIGRlIHZhbG9yZXMgw7puaWNvcyB5IGRlIHZhbG9yZXMgZmFsdGFudGVzIChOQXMpIHBhcmEgY2FkYSB1bmEgZGUgbGFzIHZhcmlhYmxlcyBzZWxlY2Npb25hZGFzLgoKYGBge3J9CgojIFZhbG9yZXMgw7puaWNvcwoKZGlzdGludG9zIDwtIG1hcF9kZmMoZGF0b3MsZnVuY3Rpb24oeCkgbl9kaXN0aW5jdCh4KSkKCiMgVmFsb3JlcyBmYWx0YW50ZXMKCmZhbHRhbnRlcyA8LSBtYXBfZGZjKGRhdG9zLGZ1bmN0aW9uKHgpIHN1bShpcy5uYSh4KSkpCgojIEp1bnRvIGxvcyBkYXRvcyB5IGxlcyBkb3kgZm9ybWF0byBkZSB0YWJsYQoKdF91bmljb3NfZmFsdGFudGVzIDwtIHJiaW5kKGRpc3RpbnRvcyxmYWx0YW50ZXMpICU+JSAKICB0cmFuc3Bvc2UoKSAKICAKcm93bmFtZXModF91bmljb3NfZmFsdGFudGVzKSA8LSBjb2xuYW1lcyhkaXN0aW50b3MpCgpjb2xuYW1lcyh0X3VuaWNvc19mYWx0YW50ZXMpIDwtIGMoJ1ZhbG9yZXMgw7puaWNvcycsJ1ZhbG9yZXMgZmFsdGFudGVzJykKCgp0X3VuaWNvc19mYWx0YW50ZXMKCgpgYGAKClBvZGVtb3Mgb2JzZXJ2YXIgZGUgbGEgdGFibGEgcmVzdWx0YW50ZSwgcXVlIGxvcyDDum5pY29zIGNhbXBvcyBxdWUgbm8gcHJlc2VudGFuIHZhbG9yZXMgZmFsdGFudGVzIHNvbiBgcHJpY2VgIHkgYHByb3BlcnR5X3R5cGVgLiBQb3Igb3RybyBsYWRvLCBwYXJ0aWN1bGFybWVudGUgbGEgdmFyaWFibGUgYGJlZHJvb21zYCBjb250aWVuZSB1bmEgYWx0w61zaW1hIHByb3BvcmNpw7NuIGRlIGZhbHRhbnRlcyAobcOhcyBkZWwgNDAlKS4gRWwgcmVzdG8gZGUgbGFzIHZhcmlhYmxlcyBwcmVzZW50YW4gcHJvcG9yY2lvbmVzIG3DoXMgYWNlcHRhYmxlcyBkZSBmYWx0YW50ZXMgY29tbyBwYXJhIG5vIGNvbnNpZGVyYXJsYXMgY2FuZGlkYXRhcyBhIHNlciBlbGltaW5hZGFzLgoKRW4gY3VhbnRvIGEgbG9zIHZhbG9yZXMgw7puaWNvcywgc2UgcHVlZGUgZGVzdGFjYXIgcXVlIGxhIHZhcmlhYmxlIGNhdGVnw7NyaWNhIGBwcm9wZXJ0eV90eXBlYCBwb3NlZSAzIHBvc2libGVzIHZhbG9yZXMgKENhc2EsIERlcGFydGFtZW50byB5IFBIKSB5IHZhcmlhYmxlIGBsM2AgcXVlIGNvcnJlc3BvbmRlciBhbCBiYXJyaW8gcG9zZWUgNTggdmFsb3JlcyBkaXN0aW50b3MuIEVsIHJlc3RvIGRlIGxhcyB2YXJpYWJsZXMsIHF1ZSBzb24gbnVtw6lyaWNhcywgc2Vyw6FuIGFuYWxpemFkYXMgYSBjb250aW51YWNpw7NuIG1lZGlhbnRlIGxhIG1hdHJpeiBkZSBjb3JyZWxhY2nDs24uCgoKIyMjI01hdHJpeiBkZSBjb3JyZWxhY2nDs24KCmBgYHtyfQoKIyBGb3JtYXRvIHRhYmxhCgpkYXRvcyAlPiUgCiAgc2VsZWN0X2lmKC4saXMubnVtZXJpYykgJT4lIAogIGNvcnJlbGF0ZSh1c2U9ImNvbXBsZXRlLm9icyIpICU+JSAKICBzaGF2ZSgpICU+JSAKICBmYXNoaW9uKCkKCiMgRm9ybWF0byBncsOhZmljbwoKZGF0b3MgJT4lIAogIHNlbGVjdF9pZiguLGlzLm51bWVyaWMpICU+JSAKICBjb3JyZWxhdGUodXNlPSJjb21wbGV0ZS5vYnMiKSAlPiUgCiAgcnBsb3QoKSArIGxhYnModGl0bGU9ICdHcsOhZmljbyBkZSBjb3JyZWxhY2nDs24nKQoKYGBgCgpEZSBsYSBtYXRyaXogcmVzdWx0YW50ZSBzZSBvYnRpZW5lIHF1ZSBleGlzdGUgdW5hIGltcG9ydGFudGUgY29ycmVsYWNpw7NuIGVudHJlIGxhcyB2YXJpYWJsZXMgYHJvb21zYCB5IGBiZWRyb29tc2AuIEVzdG8gcmVzdWx0YSBwYXJ0aWN1bGFybWVudGUgw7p0aWwgeWEgcXVlLCBjb21vIHNlIG9ic2VydsOzIGVuIGVsIHBhc28gYW50ZXJpb3IsIGxhIHZhcmlhYmxlIGBiZWRyb29tc2AgcG9zZWUgdW4gYWx0byBwb3JjZW50YWplIGRlIHZhbG9yZXMgZmFsdGFudGVzLiBBc8OtLCBzZSBwdWVkZSBwcm9jZWRlciBhIGVsaW1pbmFyIGxhIHZhcmlhYmxlIGBiZWRyb29tc2AgZGVsIGRhdGFzZXQgeWEgcXVlIGVuIGdyYW4gbWVkaWRhIGVzIGV4cGxpY2FkYSBwb3IgbGEgdmFyaWFibGUgYHJvb21zYC4gRXN0byBlcyBsbyBxdWUgc2UgaGFyw6EgYSBjb250aW51YWNpw7NuLgoKVGFtYmnDqW4gY2FiZSBtZW5jaW9uYXIgcXVlIHNlIG9ic2VydmEgdW5hIGNvcnJlbGFjacOzbiBtZWRpYW5hbWVudGUgYWx0YSBlbnRyZSBsYXMgdmFyaWFibGVzIHJlbGFjaW9uYWRhcyBhIGxhIHN1cGVyZmljaWUgZGUgbGEgcHJvcGllZGFkIChgc3VyZmFjZV90b3RhbGAgeSBgc3VyZmFjZV9jb3ZlcmVkYCkgbG8gY3VhbCB0aWVuZSBzZW50aWRvIGRhZGEgbGEgbMOzZ2ljYSB5IGVsIGNvbm9jaW1pZW50byBkZWwgZG9taW5pby4KCgojIyMjMykgUHJlcGFyYWNpw7NuIGRlIGxvcyBkYXRvcyAoSUkpCgoKQSBjb250aW51YWNpw7NuIHNlIGVsaW1pbmEgbGEgdmFyaWFibGUgYGJlZHJvb21zYCB5IGFkaWNpb25hbG1lbnRlIHRvZGFzIGxhcyBvYnNlcnZhY2lvbmVzIHF1ZSBwb3NlYW4gZGF0b3MgZmFsdGFudGVzLgoKRWwgZGF0YXNldCByZXN1bHRhbnRlIHF1ZWRhcsOhIGNvbiA1MTIxMCBvYnNlcnZhY2lvbmVzIHkgOCB2YXJpYWJsZXMuCiAgICAKYGBge3J9CgojIEVsaW1pbm8gdG9kb3MgbG9zIHJlZ2lzdHJvcyBjb24gZGF0b3MgZmFsdGFudGVzCgpkYXRvcyA8LSBkYXRvcyAlPiUKICBzZWxlY3QoLiwtYmVkcm9vbXMpICU+JSAKICBkcm9wX25hKCkKCgpkYXRvcwoKCmBgYAoKCiMjIyM0KSBBbsOhbGlzaXMgZXhwbG9yYXRvcmlvcyAoSUkpCgpTZSBhbmFsaXphIGxhIHZhcmlhYmxlIGBwcmljZWAgdXRpbGl6YW5kbyBlc3RhZMOtc3RpY2FzIGRlc2NyaXB0aXZhcyAoY3VhcnRpbGVzLCBwcm9tZWRpbywgbcOtbmltbyB5IG3DoXhpbW8pIGp1bnRvIGNvbiBoaXN0b2dyYW1hcyBxdWUgbXVlc3RyZW4gbGEgZGlzdHJpYnVjacOzbiBkZSBsYSBtaXNtYS4KCgpgYGB7cn0KCiMgRXN0YWTDrXN0aWNhcyBkZXNjcmlwdGl2YXMgZGUgbGEgdmFyaWFibGUgcHJlY2lvCgpkYXRvcyAlPiUgCiAgc3VtbWFyaXNlKG1pbj1taW4ocHJpY2UpLAogICAgICAgICAgICBtYXg9bWF4KHByaWNlKSwKICAgICAgICAgICAgcTEgPSBxdWFudGlsZShwcmljZSwwLjI1KSwKICAgICAgICAgICAgcTIgPSBxdWFudGlsZShwcmljZSwwLjUpLAogICAgICAgICAgICBxMyA9IHF1YW50aWxlKHByaWNlLDAuNzUpLAogICAgICAgICAgICBwcm9tZWRpbyA9IG1lYW4ocHJpY2UpCiAgKSAKCiMgSGlzdG9ncmFtYSBjb24gZXNjYWxhIG9yaWdpbmFsCgogIGdncGxvdChkYXRvcyxhZXMocHJpY2UpKSsKICBnZW9tX2hpc3RvZ3JhbShjb2xvdXIgPSAiZGFya2dyZWVuIiwgZmlsbCA9ICJsaWdodGJsdWUiLGJpbnM9NTApICsKICBsYWJzKHRpdGxlID0gJ0Rpc3RyaWJ1Y2nDs24gZGUgbGEgdmFyaWFibGUgcHJlY2lvJykKICAKICAjIEhpc3RvZ3JhbWEgY29uIGVzY2FsYSBsb2dhcsOtdG1pY2EKCiAgZ2dwbG90KGRhdG9zLGFlcyhwcmljZSkpICsKICBnZW9tX2hpc3RvZ3JhbShjb2xvdXIgPSAiZGFya2dyZWVuIiwgZmlsbCA9ICJsaWdodGJsdWUiLGJpbnM9NTApICsKICBsYWJzKHRpdGxlID0gJ0Rpc3RyaWJ1Y2nDs24gZGUgbGEgdmFyaWFibGUgcHJlY2lvIChlc2NhbGEgbG9nYXLDrXRtaWNhKScpKwogIHNjYWxlX3hfbG9nMTAoKQogIAoKYGBgCgpFbCBoaXN0b2dyYW1hIGRlIGxhIHZhcmlhYmxlIGBwcmljZWAsIGVuIHN1IGVzY2FsYSBvcmlnaW5hbCAodmFsb3JlcyBlbiBVU0QpLCBtdWVzdHJhIGNsYXJhbWVudGUgdW5hIGFzaW1ldHLDrWEgYSBkZXJlY2hhLiBFc3RvLCBzdW1hZG8gYSBsb3MgcmVzdWx0YWRvcyBkZSBsYXMgZXN0YWTDrXN0aWNhcyBkZXNjcmlwdGl2YXMsIG5vcyBpbmRpY2EgcXVlIGhheSBhbGd1bm9zIHZhbG9yZXMgZXh0cmVtb3MgcXVlIHBvZHLDrWFuIGludGVyZmVyaXIgZW4gZWwgYW7DoWxpc2lzLiBIYWJyw6EgcXVlIGFuYWxpemFyIGNvbiBhbGfDum4gY3JpdGVyaW8gc2kgZGljaG9zIHZhbG9yZXMgZXh0cmVtb3MsIHNvYnJlIHRvZG8gbG9zIG1heW9yZXMsIHB1ZWRlbiBjYXRhbG9nYXJzZSBjb21vIG91dGxpZXJzIHkgdHJhdGFybG9zIGVuIGNvbnNlY3VlbmNpYS4gTGEgbWVkaWEgeSBsYSBtZWRpYW5hIG5vIHNlIGVuY3VlbnRyYW4gbXV5IGNlcmNhbmFzLCBsbyBjdWFsIHRhbWJpw6luIGVzIHVuIGluZGljYWRvciBkZSBwb3NpYmxlIHByZXNlbmNpYSBkZSBvdXRsaWVycy4KCkFkaWNpb25hbG1lbnRlIGFsIGhpc3RvZ3JhbWEgZW4gZXNjYWxhIG9yaWdpbmFsIHNlIGFncmVnw7MgdW5vIGVzY2FsYWRvIGRlIG1hbmVyYSBsb2dhcsOtdG1pY2EgKGxvZzEwKSBxdWUgcGVybWl0ZSBvYnNlcnZhciBkZSBtYW5lcmEgdW4gcG9jbyBtw6FzIGNsYXJhIGxhIGRpc3RyaWJ1Y2nDs24geWEgcXVlIG5vIG11ZXN0cmEgdmFsb3JlcyBhYnNvbHV0b3Mgc2lubyByZWxhdGl2b3MsIGVuIHTDqXJtaW5vcyBkZSBwcm9wb3JjaW9uZXMuIEVzdGUgw7psdGltbyBoaXN0b2dyYW1hIHF1ZWRhIGRlIHVuYSBmb3JtYSBtw6FzIG5vcm1hbGl6YWRhLgoKCkEgY29udGludWFjacOzbiBjYWxjdWxvIG51ZXZhbWVudGUgbGFzIGVzdGFkw61zdGljYXMgZGVzY3JpcHRpdmFzIHNvYnJlIGxhIHZhcmlhYmxlIGBwcmljZWAgcGVybyBhZ3J1cGFuZG8gcG9yIGBwcm9wZXJ0eV90eXBlYC4gVGFtYmnDqW4gc2UgZ3JhZmljYW4gYm94cGxvdHMgY29uIGxhIG1pc21hIGNvbmZpZ3VyYWNpw7NuIGRlIHZhcmlhYmxlcyB5IGZpbmFsbWVudGUgdW4gY29ycmVsb2dyYW1hIGdlbmVyYWwgdXRpbGl6YW5kbyBsYSBsaWJyZXLDrWEgKipHR0FsbHkqKi4KCgpgYGB7cn0KCiMgQWdydXBvIHBvciBwcm9wZXJ0eV90eXBlCgpkYXRvcyAlPiUgCiAgZ3JvdXBfYnkocHJvcGVydHlfdHlwZSkgJT4lIAogIHN1bW1hcmlzZShtaW49bWluKHByaWNlKSwKICAgICAgICAgICAgbWF4PW1heChwcmljZSksCiAgICAgICAgICAgIHExID0gcXVhbnRpbGUocHJpY2UsMC4yNSksCiAgICAgICAgICAgIHEyID0gcXVhbnRpbGUocHJpY2UsMC41KSwKICAgICAgICAgICAgcTMgPSBxdWFudGlsZShwcmljZSwwLjc1KSwKICAgICAgICAgICAgcHJvbWVkaW8gPSBtZWFuKHByaWNlKQogICkKCmBgYAoKCiMjIyNCb3hwbG90cwoKYGBge3J9CgojIEJveHBsb3QgZGUgcHJlY2lvLCBlc2NhbGEgb3JpZ2luYWwKCmdncGxvdChkYXRvcyxhZXMoeD1wcm9wZXJ0eV90eXBlLHkgPSBwcmljZSxmaWxsPXByb3BlcnR5X3R5cGUpKSArCiAgZ2VvbV9ib3hwbG90KCkrCiAgbGFicyh0aXRsZT0nQm94cGxvdCBkZSBwcmVjaW8gcG9yIHRpcG8gZGUgcHJvcGllZGFkJykKCiMgQm94cGxvdCBkZSBwcmVjaW8sIGVzY2FsYSBsb2dhcsOtdG1pY2EKCmdncGxvdChkYXRvcyxhZXMoeD1wcm9wZXJ0eV90eXBlLHkgPSBwcmljZSxmaWxsPXByb3BlcnR5X3R5cGUpKSArCiAgZ2VvbV9ib3hwbG90KCkrCiAgbGFicyh0aXRsZT0nQm94cGxvdCBkZSBwcmVjaW8gcG9yIHRpcG8gZGUgcHJvcGllZGFkIChlc2NhbGEgbG9nMTApJykrCiAgc2NhbGVfeV9sb2cxMCgpCgoKYGBgCgojIyMjQ29ycmVsb2dyYW1hCgpgYGB7ciwgZmlnLndpZHRoPTksIGZpZy5oZWlnaHQ9OX0gCgojIENvcnJlbG9ncmFtYQoKZGF0b3MgJT4lIAogIHNlbGVjdCgtaWQsLWwzKSAlPiUgCiAgZ2dwYWlycyguLCAKICAgICAgICB0aXRsZSA9ICJNYXRyaXogZGUgY29ycmVsYWNpb25lcyIsCiAgICAgICAgbWFwcGluZyA9IGFlcyhjb2xvdXI9cHJvcGVydHlfdHlwZSkpCgoKYGBgCgpEZSBlc3RvcyDDumx0aW1vcyBncsOhZmljb3MgcG9kZW1vcyBvYnNlcnZhciBxdWUgbGFzIGNhc2FzIHNvbiBlbiBwcm9tZWRpbyBtw6FzIGNhcmFzIHF1ZSBsb3Mgb3Ryb3MgdGlwb3MgZGUgcHJvcGllZGFkZXMsIHkgcXVlIGFkZW3DoXMgcHJlc2VudGFuIHVuYSBtYXlvciB2YXJpYWJpbGlkYWQgZW4gdMOpcm1pbm9zIGRlIHByZWNpby4gQWRpY2lvbmFsbWVudGUgcG9kZW1vcyB2ZXIgZW4gdW5vIGRlIGxvcyBzY2F0dGVyIHBsb3RzIGRlbCBjb3JyZWxvZ3JhbWEsIHF1ZSBoYXkgdW5hIGNpZXJ0YSBsaW5lYWxpZGFkIGVudHJlIGxhcyB2YXJpYWJsZXMgYHJvb21zYCB5IGBwcmljZWAgbG8gY3VhbCBlcyB1biBkYXRvIMO6dGlsIHBhcmEgcGVuc2FyIGVuIGVsIHBvc3RlcmlvciBtb2RlbG8gbGluZWFsIHF1ZSBzZSBhanVzdGFyw6EuCgoKCiMjIyM1KSBPdXRsaWVycwoKR2VuZXJvIGVzdGFkw61zdGljYXMgYWdydXBhZGFzIHBvciB0aXBvIGRlIHByb3BpZWRhZCwgeSBhZ3JlZ2FuZG8gdGFtYmnDqW4gdW5hIG51ZXZhIHZhcmlhYmxlIGNhbGN1bGFkYSBkZSBwcmVjaW8gcG9yIG0yLCBsYSBjdWFsIGRhIHVuYSBpbmRpY2FjacOzbiB1biBwb2NvIG3DoXMgcHJlY2lzYSBwYXJhIGRldGVjdGFyIGRhdG9zIGF0w61waWNvcy4KCgpgYGB7cn0KCiMgRXN0YWTDrXN0aWNhcyBkZXNjcmlwdGl2YXMgZGUgbGEgdmFyaWFibGUgcHJlY2lvIHBvciBtMiAocG0yKQoKZGF0b3MgJT4lIAogIG11dGF0ZShwbTIgPSBwcmljZS9zdXJmYWNlX3RvdGFsKSAlPiUgCiAgYXJyYW5nZShwbTIpICU+JSAKICBzZWxlY3QocHJvcGVydHlfdHlwZSxwbTIpICU+JSAKICBncm91cF9ieShwcm9wZXJ0eV90eXBlKSAlPiUgCiAgc3VtbWFyaXNlKG1pbj1taW4ocG0yKSwKICAgICAgICAgICAgbWF4PW1heChwbTIpLAogICAgICAgICAgICBxMSA9IHF1YW50aWxlKHBtMiwwLjI1KSwKICAgICAgICAgICAgcTIgPSBxdWFudGlsZShwbTIsMC41KSwKICAgICAgICAgICAgcTMgPSBxdWFudGlsZShwbTIsMC43NSksCiAgICAgICAgICAgIHByb21lZGlvID0gbWVhbihwbTIpLAogICAgICAgICAgICBpcXIgPSBJUVIocG0yKQogICAgICAgICAgICApCiAgCgpgYGAKCkNvbW8gY3JpdGVyaW8gcGFyYSBmaWx0cmFyIGxvcyBvdXRsaWVycyBzZSB0b21hcsOhbiBlbiBjdWVudGE6CgogIGEpIExvcyBsw61taXRlcyBkZSBsYSB2YXJpYWJsZSBgcG0yYCwgZGVmaW5pZG9zIHBvciB1bmEgbWFnbml0dWQgbWF5b3IgYWwgY3JpdGVyaW8gZGUgRmlzaGVyLiBFbiBlc3RlIGNhc28gc2UgY29uc2lkZXJhcm9uIDIuNSB2ZWNlcyBsYSBkaXN0YW5jaWEgSVFSLCBwYXJhIHRlbmVyIHVuIG1heW9yIG1hcmdlbiB5IG5vIGVsaW1pbmFyIHRhbnRvcyB2YWxvcmVzIGRlIHByZWNpb3MgYWx0b3MgcXVlIG5vIG5lY2VzYXJpYW1lbnRlIGNvcnJlc3BvbmRlbiBhIG91dGxpZXJzLgogIGIpIENvbW8gZWwgY3JpdGVyaW8gImEiIG5vIGxvZ3JhIGZpbHRyYXIgbG9zIHZhbG9yZXMgYXTDrXBpY29zIG11eSBiYWpvcywgc2UgYWRpY2lvbsOzIHVuIGZpbHRybyBxdWUgZGVzY2FydGEgbGFzIHByb3BpZWRhZGVzIGN1eW8gdmFsb3IgcG0yIGZ1ZXJhIG1lbm9yIGEgMTAwIHVzZC4KICBjKSBQb3Igw7psdGltbyB0YW1iacOpbiBzZSBjb25zaWRlcsOzIHVuIGZpbHRybyBzb2JyZSBsYSB2YXJpYWJsZSBgcHJpY2VgIHF1ZSBlbGltaW5hIDIgb2JzZXJ2YWNpb25lcyAodW5hIGRlIENhc2EgeSBvdHJhIGRlIERlcGFydGFtZW50bykgY3V5b3MgdmFsb3JlcyBzdXBlcmFiYW4gbG9zIDVNIFVTRCwgbW9udG8gcXVlIGRhYmEgaW5kaWNpb3MganVudG8gYSBzdXMgb3RyYXMgdmFyaWFibGVzIHF1ZSBzZSBjb25zaWRlcmFiYW4gYXTDrXBpY29zLiAKICAKCkNvbiB0b2RvcyBlc3RvcyBmaWx0cm9zIGFwbGljYWRvcywgbG9zIHZhbG9yZXMgcmVzdWx0YW50ZXMgZnVlcm9uOiAgCgpgYGB7cn0KCnBhc3RlKCdkYXRhc2V0IGNvbiBvdXRsaWVyczonLG5yb3coZGF0b3MpKQoKcGFzdGUoJ2RhdGFzZXQgc2luIG91dGxpZXJzOicsbnJvdyhkYXRvc19jbGVhbikpCgpwYXN0ZSgnY2FudGlkYWQgZGUgb3V0bGllcnMgZWxpbWluYWRvczonLChucm93KGRhdG9zKSAtIG5yb3coZGF0b3NfY2xlYW4pKSkKCnBhc3RlKCdwb3JjZW50YWplIGRlIG91dGxpZXJzOicscGVyY2VudCgobnJvdyhkYXRvcykgLSBucm93KGRhdG9zX2NsZWFuKSkgLyBucm93KGRhdG9zKSkpCgpgYGAKICAKCmBgYHtyfQoKIyBHdWFyZG8gdW4gbnVldm8gZGF0YWZyYW1lIGNvbnNlcnZhbmRvIGVsIGNhbXBvIHBtMiAocHJlY2lvIHBvciBtZXRybyBjdWFkcmFkbykKCmRhdG9zX25ldyA8LSBkYXRvcyAlPiUgCiAgbXV0YXRlKHBtMiA9IHByaWNlL3N1cmZhY2VfdG90YWwpCgoKIyBHZW5lcm8gZWwgZGF0YWZyYW1lIHNpbiBvdXRsaWVycwogIApkYXRvc19jbGVhbiA8LSBtYXBfZGZyKAoKICAueCA9IHVuaXF1ZShkYXRvc19uZXckcHJvcGVydHlfdHlwZSksCgogIC5mID0gZnVuY3Rpb24odHByb3ApewoKICAgIGRmIDwtIGRhdG9zX25ldyAlPiUKICAgICAgZmlsdGVyKHByb3BlcnR5X3R5cGUgPT0gdHByb3ApCgogICAgcTEgIDwtIHF1YW50aWxlKGRmJHBtMiwwLjI1KQogICAgcTMgIDwtIHF1YW50aWxlKGRmJHBtMiwwLjc1KQogICAgaXFyIDwtIElRUihkZiRwbTIpCgogICAgcmVzIDwtIGRmICU+JQogICAgICBmaWx0ZXIoIChwbTIgPj0gcTEgLSAyLjUgKiBpcXIpICYgKHBtMiA8PSBxMyArIDIuNSAqIGlxciksCiAgICAgICAgICAgICAgcG0yID4gMTAwLAogICAgICAgICAgICAgIHByaWNlIDwgNTAwMDAwMAogICAgICAgICAgICAgICkKCiAgICByZXR1cm4ocmVzKQoKICB9CgopCgojIFF1ZWRhIGNvbW8gcmVzdWx0YWRvIGVsIG51ZXZvIGRhdGFmcmFtZSBzaW4gbGFzIG9ic2VydmFjaW9uZXMgYXTDrXBpY2FzICg1MDExNCByb3dzKQoKZGF0b3NfY2xlYW4KCgojIDUwMTE0CgpgYGAKCgojIyMjNikgQW7DoWxpc2lzIGV4cGxvcmF0b3Jpb3MgKElJSSkKICAgIApSZXBpdG8gbnVldmFtZW50ZSBlbCBhbsOhbGlzaXMgZXhwbG9yYXRvcmlvIGx1ZWdvIGRlIHF1aXRhciBsb3Mgb3V0bGllcnMgZGVsIGRhdGFzZXQsIGNvbWVuemFuZG8gY29uIGVzdGFkaXN0aWNhcyBkZXNjcmlwdGl2YXMgcGFyYSBsYSB2YXJpYWJsZSBwcmVjaW8sIHkgY29udGludWFuZG8gY29uIGVsIGJveHBsb3QgeSBjb3JyZWxvZ3JhbWEuCgpgYGB7cn0KCiMgRXN0YWTDrXN0aWNhcyBkZXNjcmlwdGl2YXMgZGUgbGEgdmFyaWFibGUgcHJlY2lvCgpkYXRvc19jbGVhbiAlPiUgCiAgc3VtbWFyaXNlKG1pbj1taW4ocHJpY2UpLAogICAgICAgICAgICBtYXg9bWF4KHByaWNlKSwKICAgICAgICAgICAgcTEgPSBxdWFudGlsZShwcmljZSwwLjI1KSwKICAgICAgICAgICAgcTIgPSBxdWFudGlsZShwcmljZSwwLjUpLAogICAgICAgICAgICBxMyA9IHF1YW50aWxlKHByaWNlLDAuNzUpLAogICAgICAgICAgICBwcm9tZWRpbyA9IG1lYW4ocHJpY2UpCiAgKSAKCgojIEhpc3RvZ3JhbWEgY29uIGVzY2FsYSBvcmlnaW5hbAoKICBnZ3Bsb3QoZGF0b3NfY2xlYW4sYWVzKHByaWNlKSkgKwogIGdlb21faGlzdG9ncmFtKGNvbG91ciA9ICJkYXJrZ3JlZW4iLCBmaWxsID0gImxpZ2h0Ymx1ZSIsYmlucz01MCkgKwogIGxhYnModGl0bGUgPSAnRGlzdHJpYnVjacOzbiBkZSBsYSB2YXJpYWJsZSBwcmVjaW8gKHNpbiBvdXRsaWVycyknKQoKYGBgCgoKYGBge3J9CgojIEFncnVwbyBwb3IgcHJvcGVydHlfdHlwZQoKZGF0b3NfY2xlYW4gJT4lIAogIGdyb3VwX2J5KHByb3BlcnR5X3R5cGUpICU+JSAKICBzdW1tYXJpc2UobWluPW1pbihwcmljZSksCiAgICAgICAgICAgIG1heD1tYXgocHJpY2UpLAogICAgICAgICAgICBxMSA9IHF1YW50aWxlKHByaWNlLDAuMjUpLAogICAgICAgICAgICBxMiA9IHF1YW50aWxlKHByaWNlLDAuNSksCiAgICAgICAgICAgIHEzID0gcXVhbnRpbGUocHJpY2UsMC43NSksCiAgICAgICAgICAgIHByb21lZGlvID0gbWVhbihwcmljZSkKICApCgpgYGAKClNlIHB1ZWRlIHZlciBxdWUgbGFzIG1lZGlhcyBzZSBhY2VyY2Fyb24gYSBsYXMgbWVkaWFuYXMgYWwgcXVpdGFyIGFsZ3Vub3MgdmFsb3JlcyBtdXkgZXh0cmVtb3MgZGUgcHJlY2lvcyBlbiBlbCBwYXNvIGFudGVyaW9yLiBMYSBkaXN0cmlidWNpw7NuIHJlZmxlamFkYSBlbiBlbCBoaXN0b2dyYW1hLCBzaSBiaWVuIHNpZ3VlIHByZXNlbnRhbmRvIGNvbGEgcGVzYWRhIGEgZGVyZWNoYSwgc2UgaGEgbm9ybWFsaXphZG8gdW4gcG9jbyBtw6FzLgoKVnVlbHZvIGEgZ3JhZmljYXIgZWwgYm94cGxvdCBkZSBsYSB2YXJpYWJsZSBwcmVjaW8gcG9yIHRpcG8gZGUgcHJvcGllZGFkIHkgZWwgY29ycmVsb2dyYW1hIGNvbXBsZXRvLgoKCiMjIyNCb3hwbG90CgpgYGB7cn0KCiMgQm94cGxvdCBkZSBwcmVjaW8sIGVzY2FsYSBvcmlnaW5hbAoKZ2dwbG90KGRhdG9zX2NsZWFuLGFlcyh4PXByb3BlcnR5X3R5cGUseSA9IHByaWNlLGZpbGw9cHJvcGVydHlfdHlwZSkpICsKICBnZW9tX2JveHBsb3QoKSsKICBsYWJzKHRpdGxlPSdCb3hwbG90IGRlIHByZWNpbyBwb3IgdGlwbyBkZSBwcm9waWVkYWQgKHNpbiBvdXRsaWVycyknKQoKYGBgCgojIyMjIyBDb3JyZWxvZ3JhbWEKCmBgYHtyLCBmaWcud2lkdGg9OSwgZmlnLmhlaWdodD05fSAKCgpkYXRvc19jbGVhbiAlPiUgCiAgc2VsZWN0KC1pZCwtbDMpICU+JSAKICBnZ3BhaXJzKC4sIAogICAgICAgIHRpdGxlID0gIk1hdHJpeiBkZSBjb3JyZWxhY2lvbmVzIChzaW4gb3V0bGllcnMpIiwKICAgICAgICBtYXBwaW5nID0gYWVzKGNvbG91cj1wcm9wZXJ0eV90eXBlKSkKCgpgYGAKCgpTZSBkZXN0YWNhIGRlbCBjb3JyZWxvZ3JhbWEgZ2VuZXJhZG8gYSBwYXJ0aXIgZGVsIGRhdGFzZXQgc2luIG91dGxpZXJzIHF1ZSwgYSBkaWZlcmVuY2lhIGRlbCBjb3JyZWxvZ3JhbWEgcHJldmlvLCBhaG9yYSBzZSB2aXN1YWxpemEgZW4gZWwgc2NhdHRlciBwbG90IHVuYSByZWxhY2nDs24gbGluZWFsIG11Y2hvIG3DoXMgY2xhcmEgZW50cmUgYHN1cmZhY2VfdG90YWxgIHkgYHByaWNlYC4gUmVjb3JkYW5kbyBxdWUgZW4gZWwgZ3LDoWZpY28gcHJldmlvIGVuY29udHJhbW9zIHVuYSByZWxhY2nDs24gc2ltaWxhciBlbnRyZSBgcm9vbXNgIHkgYHByaWNlYCwgdGllbmUgc2VudGlkbyBxdWUgZW4gZWwgcHLDs3hpbW8gcGFzbyBldmFsdWVtb3MgYW1ib3MgbW9kZWxvcyBkZSByZWdyZXNpw7NuLgoKClBhcmEgZmluYWxpemFyIGVsIGFuw6FsaXNpcyBleHBsb3JhdG9yaW8gYWdyZWdvIGVzdGFkw61zdGljYXMgZGVzY3JpcHRpdmFzIGUgaGlzdG9ncmFtYSBkZSBsYSB2YXJpYWJsZSBwcmVjaW8gcG9yIG1ldHJvIGN1YWRyYWRvIChwbTIpLCBlbiBsYSBjdWFsIHNlIHB1ZWRlbiB2ZXIgZGlzdHJpYnVjaW9uZXMgZGUgbWF5b3Igbm9ybWFsaWRhZC4gClRhbWJpw6luIHNlIHZlIHVuYSBjbGFyYSBwcmVkb21pbmFuY2lhIGRlIGxhIGNhdGVnb3LDrWEgRGVwYXJ0YW1lbnRvcyBlbiBjdWFudG8gYSBjYW50aWRhZCBkZSBvYnNlcnZhY2lvbmVzLCBtYXlvciBtZWRpYW5hIHkgbWVkaWEgcXVlIGxhcyBDYXNhcyB5IFBIcy4gRXN0byBlcyBwYXJ0aWN1bGFybWVudGUgaW50ZXJlc2FudGUgZGUgYW5hbGl6YXIgeWEgcXVlIGN1YW5kbyBzZSBjb21wdXRhcm9uIGxhcyBlc3RhZMOtc3RpY2FzIGRlc2NyaXB0aXZhcyBkZSBsYSB2YXJpYWJsZSBgcHJpY2VgIHNlIHZlw61hIHF1ZSBsYXMgY2FzYXMgdGllbmVuIGVuIHByb21lZGlvIHVuIHZhbG9yIGFic29sdXRvIG1heW9yIHF1ZSBsb3MgRGVwYXJ0YW1lbnRvcywgcGVybyBubyBvY3VycmUgbG8gbWlzbW8gZW4gZWwgcHJlY2lvIHBvciBtZXRybyBjdWFkcmFkby4KCgpgYGB7cn0KCiMgQWdyZWdvIGFkZW3DoXMgZXN0YWTDrXN0aWNhcyBkZSBsYSB2YXJpYWJsZSBwbTIgKHByZWNpbyBwb3IgbWV0cm8gY3VhZHJhZG8pIHkgbXVlc3RybyBzdSBkaXN0cmlidWNpw7NuIG1lZGlhbnRlIHVuIGhpc3RvZ3JhbWEgYWdydXBhZG8gcG9yIHRpcG8gZGUgcHJvcGllZGFkLgoKZGF0b3NfY2xlYW4gJT4lIAogIHNlbGVjdChwcm9wZXJ0eV90eXBlLHBtMikgJT4lIAogIGdyb3VwX2J5KHByb3BlcnR5X3R5cGUpICU+JSAKICBzdW1tYXJpc2UobWluPW1pbihwbTIpLAogICAgICAgICAgICBtYXg9bWF4KHBtMiksCiAgICAgICAgICAgIHExID0gcXVhbnRpbGUocG0yLDAuMjUpLAogICAgICAgICAgICBxMiA9IHF1YW50aWxlKHBtMiwwLjUpLAogICAgICAgICAgICBxMyA9IHF1YW50aWxlKHBtMiwwLjc1KSwKICAgICAgICAgICAgcHJvbWVkaW8gPSBtZWFuKHBtMiksCiAgICAgICAgICAgIGlxciA9IElRUihwbTIpCiAgICAgICAgICAgICkKCiAgZ2dwbG90KGRhdG9zX2NsZWFuLGFlcyh4PXBtMixjb2xvcj1wcm9wZXJ0eV90eXBlKSkgKyAKICBnZW9tX2hpc3RvZ3JhbSgpKwogIGxhYnModGl0bGUgPSAnRGlzdHJpYnVjacOzbiBkZSBsYSB2YXJpYWJsZSBwcmVjaW8vbTInKSsKICBmYWNldF9ncmlkKC4gfnByb3BlcnR5X3R5cGUpCiAgCgpgYGAKCiMjIyM3KSBNb2RlbG8gbGluZWFsCgpFbCBwcmltZXIgbW9kZWxvIGxpbmVhbCBxdWUgc2UgbXVlc3RyYSBhIGNvbnRpbnVhY2nDs24gaW50ZW50YSBleHBsaWNhciBlbCBwcmVjaW8gZW4gZnVuY2nDs24gZGUgbGFzIGhhYml0YWNpb25lcyAoYHJvb21zYCkuIAoKCmBgYHtyfQoKIyBNb2RlbG8gbGluZWFsIDE6CgogICMgdGFyZ2V0OiBwcmljZQogICMgcHJlZGljdG9yOiByb29tcwoKbW9kZWxvX3Jvb21zIDwtIGxtKHByaWNlIH4gcm9vbXMsIGRhdGEgPSBkYXRvc19jbGVhbikKCnN1bW1hcnkobW9kZWxvX3Jvb21zKQoKYGBgCgpMb3MgcmVzdWx0YWRvcyBvYnRlbmlkb3MgbWVkaWFudGUgZWwgcHJpbWVyIG1vZGVsbyBhcnJvamFuIGNvZWZpY2llbnRlcyBlc3RpbWFkb3MgZGUgQjAgPSAtMTU5NzcuNCB5IEIxID0gODc5NDQuMSwgc2llbmRvIGVsIHByaW1lcm8gZGUgZWxsb3MgZWwgdmFsb3IgY29ycmVzcG9uZGllbnRlIGEgbGEgb3JkZW5hZGEgYWwgb3JpZ2VuIChlbiBlc3RlIGNhc28gcGFydGljdWxhciBzZXLDrWEgdW5hIHByb3BpZWRhZCBjb24gMCBoYWJpdGFjaW9uZXMsIGxvIGN1YWwgbm8gdGVuZHLDrWEgc2VudGlkbyk7IHkgZWwgc2VndW5kbyBkZSBlbGxvcyBpbmRpY2EgcXVlIHBvciBjYWRhIGhhYml0YWNpw7NuIGV4dHJhLCBlbCB2YWxvciAqKmVzdGltYWRvKiogZGVsIGlubXVlYmxlIGF1bWVudGEgZW4gODc5NDQuMSBVU0QuCgoKYGBge3J9CmdncGxvdChkYXRvc19jbGVhbixhZXMoeD1yb29tcyx5PXByaWNlKSkrCiAgZ2VvbV9wb2ludChhZXMoY29sb3I9cHJvcGVydHlfdHlwZSkpICsKICBnZW9tX3Ntb290aChtZXRob2QgPSAibG0iKSArCiAgbGFicyh4PSJDYW50aWRhZCBkZSBoYWJpdGFjaW9uZXMiLHk9IlByZWNpbyAodXNkKSIsdGl0bGU9Ik1vZGVsbyBsaW5lYWwgYmFzYWRvIGVuIGNhbnRpZGFkIGRlIGhhYml0YWNpb25lcyIpCgpgYGAKCgpFbCBzZWd1bmRvIG1vZGVsbyBsaW5lYWwgaW50ZW50YSBleHBsaWNhciBlbCBwcmVjaW8gKGByb29tc2ApIGVuIGZ1bmNpw7NuIGRlIGxhIHN1cGVyZmljaWUgdG90YWwgKGBzdXJmYWNlX3RvdGFsYCkgKHN1cmZhY2VfdG90YWwpCgoKYGBge3J9CgojIE1vZGVsbyBsaW5lYWwgMjoKCiAgIyB0YXJnZXQ6IHByaWNlCiAgIyBwcmVkaWN0b3I6IHN1cmZhY2VfdG90YWwKCm1vZGVsb19zdXJmYWNlX3RvdGFsIDwtIGxtKHByaWNlIH4gc3VyZmFjZV90b3RhbCwgZGF0YSA9IGRhdG9zX2NsZWFuKQoKc3VtbWFyeShtb2RlbG9fc3VyZmFjZV90b3RhbCkKCmBgYAoKCkxvcyByZXN1bHRhZG9zIG9idGVuaWRvcyBtZWRpYW50ZSBlbCBzZWd1bmRvIG1vZGVsbyBhcnJvamFuIGNvZWZpY2llbnRlcyBlc3RpbWFkb3MgZGUgQjAgPSA2MTE0MS4wNzggeSBCMSA9IDE4NjguNjI3LCBzaWVuZG8gZWwgcHJpbWVybyBkZSBlbGxvcyBlbCB2YWxvciBjb3JyZXNwb25kaWVudGUgYSBsYSBvcmRlbmFkYSBhbCBvcmlnZW4gKGVuIGVzdGUgY2FzbyBwYXJ0aWN1bGFyIHNlcsOtYSB1bmEgcHJvcGllZGFkIGNvbiAwIG1ldHJvcyBjdWFkcmFkb3MgZGUgc3VwZXJmaWNpZSwgbG8gY3VhbCBubyB0ZW5kcsOtYSBzZW50aWRvKTsgeSBlbCBzZWd1bmRvIGRlIGVsbG9zIGluZGljYSBxdWUgcG9yIGNhZGEgbWV0cm8gY3VhZHJhZG8gZXh0cmEgZGUgc3VwZXJmaWNpZSwgZWwgdmFsb3IgKiplc3RpbWFkbyoqIGRlbCBpbm11ZWJsZSBhdW1lbnRhIGVuIDE4NjguNjI3IFVTRC4KCgpgYGB7cn0KCmdncGxvdChkYXRvc19jbGVhbixhZXMoeD1zdXJmYWNlX3RvdGFsLHk9cHJpY2UpKSsKICBnZW9tX3BvaW50KGFlcyhjb2xvcj1wcm9wZXJ0eV90eXBlKSkgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJsbSIpICsKICBsYWJzKHg9IlN1cGVyZmljaWUgdG90YWwgKG0yKSIseT0iUHJlY2lvICh1c2QpIix0aXRsZT0iTW9kZWxvIGxpbmVhbCBiYXNhZG8gZW4gc3VwZXJmaWNpZSB0b3RhbCIpCgpgYGAKCgpIYWJpZW5kbyBhbmFsaXphZG8gbG9zIHJlc3VsdGFkb3MgZGUgYW1ib3MgbW9kZWxvcyB5IGNvbXBhcsOhbmRvbG9zIG1lZGlhbnRlIGxhIG3DqXRyaWNhIHJlc3VsdGFudGUgUjIsIHNlIGNvbmNsdXllIHF1ZSBlbCBzZWd1bmRvIG1vZGVsbyBlcyBtw6FzIHByZWNpc28gYSBsYSBob3JhIGRlIGVzdGltYXIgZWwgcHJlY2lvIGRlbCBpbm11ZWJsZSwgYWxjYW56YW5kbyB1biB2YWxvciBkZSB2YXJpYWJpbGlkYWQgZXhwbGljYWRhIGRlbCA1My4yNiUgZnJlbnRlIGFsIDM1LjclIGRlbCBwcmltZXIgbW9kZWxvLgo=