{r setup, include=FALSE} knitr::opts_chunk$set(echo = TRUE)

Teoría

El Bosque Aleatorio es un algoritmo de aprendizaje automatico que combina el resultado de multiples arboles de decision para llegar a un resultado optimo.

Ejemplo 1. Melbourne

En esta base de datos tnemos los precios de más de 13,000 casas de la ciudad de Melbourne

Instalar paquetes y llamar librerías

# Instalar (solo si no lo tienes en tu compu)
# install.packages(c("tidyverse","rpart","rpart.plot","randomForest","caret","modelr","tibble"))

# Cargar librerías
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr     1.1.4     ✔ readr     2.1.5
## ✔ forcats   1.0.0     ✔ stringr   1.5.1
## ✔ ggplot2   3.5.2     ✔ tibble    3.3.0
## ✔ lubridate 1.9.4     ✔ tidyr     1.3.1
## ✔ purrr     1.1.0     
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag()    masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
library(rpart)
library(rpart.plot)
library(randomForest)
## randomForest 4.7-1.2
## Type rfNews() to see new features/changes/bug fixes.
## 
## Attaching package: 'randomForest'
## 
## The following object is masked from 'package:dplyr':
## 
##     combine
## 
## The following object is masked from 'package:ggplot2':
## 
##     margin
library(caret)
## Loading required package: lattice
## 
## Attaching package: 'caret'
## 
## The following object is masked from 'package:purrr':
## 
##     lift
library(modelr)
library(tibble)

Importar la base de datos

df1 <- read.csv("/Users/antoniodiaz/Desktop/MODULO2/melbourne.csv")

Entender la base de datos

summary(df1)
##     Suburb            Address              Rooms            Type          
##  Length:13580       Length:13580       Min.   : 1.000   Length:13580      
##  Class :character   Class :character   1st Qu.: 2.000   Class :character  
##  Mode  :character   Mode  :character   Median : 3.000   Mode  :character  
##                                        Mean   : 2.938                     
##                                        3rd Qu.: 3.000                     
##                                        Max.   :10.000                     
##                                                                           
##      Price            Method            SellerG              Date          
##  Min.   :  85000   Length:13580       Length:13580       Length:13580      
##  1st Qu.: 650000   Class :character   Class :character   Class :character  
##  Median : 903000   Mode  :character   Mode  :character   Mode  :character  
##  Mean   :1075684                                                           
##  3rd Qu.:1330000                                                           
##  Max.   :9000000                                                           
##                                                                            
##     Distance        Postcode       Bedroom2         Bathroom    
##  Min.   : 0.00   Min.   :3000   Min.   : 0.000   Min.   :0.000  
##  1st Qu.: 6.10   1st Qu.:3044   1st Qu.: 2.000   1st Qu.:1.000  
##  Median : 9.20   Median :3084   Median : 3.000   Median :1.000  
##  Mean   :10.14   Mean   :3105   Mean   : 2.915   Mean   :1.534  
##  3rd Qu.:13.00   3rd Qu.:3148   3rd Qu.: 3.000   3rd Qu.:2.000  
##  Max.   :48.10   Max.   :3977   Max.   :20.000   Max.   :8.000  
##                                                                 
##       Car           Landsize         BuildingArea     YearBuilt   
##  Min.   : 0.00   Min.   :     0.0   Min.   :    0   Min.   :1196  
##  1st Qu.: 1.00   1st Qu.:   177.0   1st Qu.:   93   1st Qu.:1940  
##  Median : 2.00   Median :   440.0   Median :  126   Median :1970  
##  Mean   : 1.61   Mean   :   558.4   Mean   :  152   Mean   :1965  
##  3rd Qu.: 2.00   3rd Qu.:   651.0   3rd Qu.:  174   3rd Qu.:1999  
##  Max.   :10.00   Max.   :433014.0   Max.   :44515   Max.   :2018  
##  NA's   :62                         NA's   :6450    NA's   :5375  
##  CouncilArea          Lattitude        Longtitude     Regionname       
##  Length:13580       Min.   :-38.18   Min.   :144.4   Length:13580      
##  Class :character   1st Qu.:-37.86   1st Qu.:144.9   Class :character  
##  Mode  :character   Median :-37.80   Median :145.0   Mode  :character  
##                     Mean   :-37.81   Mean   :145.0                     
##                     3rd Qu.:-37.76   3rd Qu.:145.1                     
##                     Max.   :-37.41   Max.   :145.5                     
##                                                                        
##  Propertycount  
##  Min.   :  249  
##  1st Qu.: 4380  
##  Median : 6555  
##  Mean   : 7454  
##  3rd Qu.:10331  
##  Max.   :21650  
## 
str(df1)
## 'data.frame':    13580 obs. of  21 variables:
##  $ Suburb       : chr  "Abbotsford" "Abbotsford" "Abbotsford" "Abbotsford" ...
##  $ Address      : chr  "85 Turner St" "25 Bloomburg St" "5 Charles St" "40 Federation La" ...
##  $ Rooms        : int  2 2 3 3 4 2 3 2 1 2 ...
##  $ Type         : chr  "h" "h" "h" "h" ...
##  $ Price        : num  1480000 1035000 1465000 850000 1600000 ...
##  $ Method       : chr  "S" "S" "SP" "PI" ...
##  $ SellerG      : chr  "Biggin" "Biggin" "Biggin" "Biggin" ...
##  $ Date         : chr  "3/12/2016" "4/02/2016" "4/03/2017" "4/03/2017" ...
##  $ Distance     : num  2.5 2.5 2.5 2.5 2.5 2.5 2.5 2.5 2.5 2.5 ...
##  $ Postcode     : num  3067 3067 3067 3067 3067 ...
##  $ Bedroom2     : num  2 2 3 3 3 2 4 2 1 3 ...
##  $ Bathroom     : num  1 1 2 2 1 1 2 1 1 1 ...
##  $ Car          : num  1 0 0 1 2 0 0 2 1 2 ...
##  $ Landsize     : num  202 156 134 94 120 181 245 256 0 220 ...
##  $ BuildingArea : num  NA 79 150 NA 142 NA 210 107 NA 75 ...
##  $ YearBuilt    : num  NA 1900 1900 NA 2014 ...
##  $ CouncilArea  : chr  "Yarra" "Yarra" "Yarra" "Yarra" ...
##  $ Lattitude    : num  -37.8 -37.8 -37.8 -37.8 -37.8 ...
##  $ Longtitude   : num  145 145 145 145 145 ...
##  $ Regionname   : chr  "Northern Metropolitan" "Northern Metropolitan" "Northern Metropolitan" "Northern Metropolitan" ...
##  $ Propertycount: num  4019 4019 4019 4019 4019 ...
df1 <- na.omit(df1)

Arbol de decisión

library(rpart)

arbol <- rpart(Price ~ Rooms + Distance + Bedroom2 + Bathroom + Car +
               Landsize + BuildingArea + Propertycount, data = df1)

plot(arbol, uniform = TRUE)
text(arbol, cex = 0.5)

# Predicciones con los primeros registros
pred_arbol <- predict(arbol, head(df1))
head(df1$Price)
## [1] 1035000 1465000 1600000 1876000 1636000 1097000
prueba_arbol <- head(df1)

# MAE manual
mae_arbol <- mean(abs(pred_arbol - prueba_arbol$Price))
mae_arbol
## [1] 516955

Bosque Aleatorio

set.seed(123)

# Eliminar NAs solo en las variables usadas
df1_clean <- na.omit(df1[, c("Price","Rooms","Distance","Bedroom2","Bathroom",
                             "Car","Landsize","BuildingArea","Propertycount")])

# Partición de entrenamiento (80%) y prueba (20%)
renglones_entrenamiento <- createDataPartition(df1_clean$Price, p = 0.8, list = FALSE)

entrenamiento <- df1_clean[renglones_entrenamiento, ]
prueba <- df1_clean[-renglones_entrenamiento, ]

# Modelo de Random Forest
rf <- randomForest(Price ~ Rooms + Distance + Bedroom2 + Bathroom + Car +
                   Landsize + BuildingArea + Propertycount,
                   data = entrenamiento, ntree = 500, mtry = 3, importance = TRUE)

# Predicciones
resultado_entrenamiento <- predict(rf, entrenamiento)
resultado_prueba <- predict(rf, prueba)

# MAE corregido (predicho vs real)
mae_rf <- mean(abs(resultado_prueba - prueba$Price))

# Comparación de modelos
resultados <- tibble(Modelo = c("Arbol de decisión", "Bosque Aleatorio"),
                     MAE = c(mae_arbol, mae_rf))

resultados
## # A tibble: 2 × 2
##   Modelo                MAE
##   <chr>               <dbl>
## 1 Arbol de decisión 516955.
## 2 Bosque Aleatorio  221075.

Ejercicio 1. Redimiento Automotriz

Importar base de datos

df2 <- mtcars

Entender Base de datos

summary(df2)
##       mpg             cyl             disp             hp       
##  Min.   :10.40   Min.   :4.000   Min.   : 71.1   Min.   : 52.0  
##  1st Qu.:15.43   1st Qu.:4.000   1st Qu.:120.8   1st Qu.: 96.5  
##  Median :19.20   Median :6.000   Median :196.3   Median :123.0  
##  Mean   :20.09   Mean   :6.188   Mean   :230.7   Mean   :146.7  
##  3rd Qu.:22.80   3rd Qu.:8.000   3rd Qu.:326.0   3rd Qu.:180.0  
##  Max.   :33.90   Max.   :8.000   Max.   :472.0   Max.   :335.0  
##       drat             wt             qsec             vs        
##  Min.   :2.760   Min.   :1.513   Min.   :14.50   Min.   :0.0000  
##  1st Qu.:3.080   1st Qu.:2.581   1st Qu.:16.89   1st Qu.:0.0000  
##  Median :3.695   Median :3.325   Median :17.71   Median :0.0000  
##  Mean   :3.597   Mean   :3.217   Mean   :17.85   Mean   :0.4375  
##  3rd Qu.:3.920   3rd Qu.:3.610   3rd Qu.:18.90   3rd Qu.:1.0000  
##  Max.   :4.930   Max.   :5.424   Max.   :22.90   Max.   :1.0000  
##        am              gear            carb      
##  Min.   :0.0000   Min.   :3.000   Min.   :1.000  
##  1st Qu.:0.0000   1st Qu.:3.000   1st Qu.:2.000  
##  Median :0.0000   Median :4.000   Median :2.000  
##  Mean   :0.4062   Mean   :3.688   Mean   :2.812  
##  3rd Qu.:1.0000   3rd Qu.:4.000   3rd Qu.:4.000  
##  Max.   :1.0000   Max.   :5.000   Max.   :8.000
str(df2)
## 'data.frame':    32 obs. of  11 variables:
##  $ mpg : num  21 21 22.8 21.4 18.7 18.1 14.3 24.4 22.8 19.2 ...
##  $ cyl : num  6 6 4 6 8 6 8 4 4 6 ...
##  $ disp: num  160 160 108 258 360 ...
##  $ hp  : num  110 110 93 110 175 105 245 62 95 123 ...
##  $ drat: num  3.9 3.9 3.85 3.08 3.15 2.76 3.21 3.69 3.92 3.92 ...
##  $ wt  : num  2.62 2.88 2.32 3.21 3.44 ...
##  $ qsec: num  16.5 17 18.6 19.4 17 ...
##  $ vs  : num  0 0 1 1 0 1 0 1 1 1 ...
##  $ am  : num  1 1 1 0 0 0 0 0 0 0 ...
##  $ gear: num  4 4 4 3 3 3 3 4 4 4 ...
##  $ carb: num  4 4 1 1 2 1 4 2 2 4 ...
df2 <- na.omit(df2)

Arbol de decisión

# Árbol de decisión SIN las variables vs y am (objetivo: mpg)
library(rpart)

arbol2 <- rpart(mpg ~ . - vs - am, data = df2)

plot(arbol2, uniform = TRUE)
text(arbol2, cex = 0.5)

# Predicción y comparación rápida
predict(arbol2, head(df2))
##         Mazda RX4     Mazda RX4 Wag        Datsun 710    Hornet 4 Drive 
##          18.26429          18.26429          26.66364          18.26429 
## Hornet Sportabout           Valiant 
##          18.26429          18.26429
head(df2$mpg)
## [1] 21.0 21.0 22.8 21.4 18.7 18.1
prueba_arbol2 <- head(df2)

# MAE: error absoluto medio (mismas unidades que mpg)
mae_arbol2 <- mean(abs(prueba_arbol2$mpg - predict(arbol2, prueba_arbol2)))
mae_arbol2
## [1] 2.178463

Bosque Aleatorio

# Bosque Aleatorio con mtcars
library(randomForest)
library(caret)   # <-- necesario para createDataPartition
library(tibble)

set.seed(123)

# Partición de entrenamiento (80%) y prueba (20%)
renglones_entrenamiento <- createDataPartition(df2$mpg, p = 0.8, list = FALSE)

entrenamiento <- df2[renglones_entrenamiento, ]
prueba <- df2[-renglones_entrenamiento, ]

# Modelo de Random Forest
rf2 <- randomForest(mpg ~ . - vs - am,
                    data = entrenamiento,
                    ntree = 500, mtry = 3, importance = TRUE)

# Predicciones
pred_train2 <- predict(rf2, entrenamiento)
pred_test2  <- predict(rf2, prueba)

# Calcular MAE manual
mae_rf2 <- mean(abs(prueba$mpg - pred_test2))

# Comparación de modelos
resultados <- tibble(Modelo = c("Árbol de decisión", "Bosque Aleatorio"),
                     MAE = c(mae_arbol2, mae_rf2))

resultados
## # A tibble: 2 × 2
##   Modelo              MAE
##   <chr>             <dbl>
## 1 Árbol de decisión  2.18
## 2 Bosque Aleatorio   1.75
LS0tCnRpdGxlOiAiQm9zcXVlIEFsZWF0b3JpbyIKYXV0aG9yOiAiQW50b25pbyBEw61heiBBMDA4MzcyNTkiCmRhdGU6ICIyMDI1LTA4LTI3IgpvdXRwdXQ6IAogIGh0bWxfZG9jdW1lbnQ6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgY29kZV9kb3dubG9hZDogVFJVRQogICAgdGhlbWU6IGpvdXJuYWwKLS0tCntyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQprbml0cjo6b3B0c19jaHVuayRzZXQoZWNobyA9IFRSVUUpCgohW10oL1VzZXJzL2FudG9uaW9kaWF6L0Rlc2t0b3AvTU9EVUxPMi90dW1ibHJfaW5saW5lX3BrMG5lYVlmOEsxcWE3bGFuXzEyODAuZ2lmKQoKIyA8c3BhbiBzdHlsZT0iY29sb3I6bGlnaHQgYnJvd247Ij4gVGVvcsOtYSA8L3NwYW4+IApFbCAqQm9zcXVlIEFsZWF0b3JpbyogZXMgdW4gYWxnb3JpdG1vIGRlIGFwcmVuZGl6YWplIGF1dG9tYXRpY28gcXVlIGNvbWJpbmEgZWwgcmVzdWx0YWRvIGRlIG11bHRpcGxlcyBhcmJvbGVzIGRlIGRlY2lzaW9uIHBhcmEgbGxlZ2FyIGEgdW4gcmVzdWx0YWRvIG9wdGltby4KCgojIDxzcGFuIHN0eWxlPSJjb2xvcjpsaWdodCBicm93bjsiPiBFamVtcGxvIDEuIE1lbGJvdXJuZSA8L3NwYW4+IApFbiBlc3RhIGJhc2UgZGUgZGF0b3MgdG5lbW9zIGxvcyBwcmVjaW9zIGRlIG3DoXMgZGUgMTMsMDAwIGNhc2FzIGRlIGxhIGNpdWRhZCBkZSBNZWxib3VybmUKCgoKIyA8c3BhbiBzdHlsZT0iY29sb3I6bGlnaHQgYnJvd247Ij4gSW5zdGFsYXIgcGFxdWV0ZXMgeSBsbGFtYXIgbGlicmVyw61hcyA8L3NwYW4+IApgYGB7cn0KIyBJbnN0YWxhciAoc29sbyBzaSBubyBsbyB0aWVuZXMgZW4gdHUgY29tcHUpCiMgaW5zdGFsbC5wYWNrYWdlcyhjKCJ0aWR5dmVyc2UiLCJycGFydCIsInJwYXJ0LnBsb3QiLCJyYW5kb21Gb3Jlc3QiLCJjYXJldCIsIm1vZGVsciIsInRpYmJsZSIpKQoKIyBDYXJnYXIgbGlicmVyw61hcwpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShycGFydCkKbGlicmFyeShycGFydC5wbG90KQpsaWJyYXJ5KHJhbmRvbUZvcmVzdCkKbGlicmFyeShjYXJldCkKbGlicmFyeShtb2RlbHIpCmxpYnJhcnkodGliYmxlKQpgYGAKCgojIDxzcGFuIHN0eWxlPSJjb2xvcjpsaWdodCBicm93bjsiPiBJbXBvcnRhciBsYSBiYXNlIGRlIGRhdG9zIDwvc3Bhbj4gCmBgYHtyfQpkZjEgPC0gcmVhZC5jc3YoIi9Vc2Vycy9hbnRvbmlvZGlhei9EZXNrdG9wL01PRFVMTzIvbWVsYm91cm5lLmNzdiIpCmBgYAoKCiMgPHNwYW4gc3R5bGU9ImNvbG9yOmxpZ2h0IGJyb3duOyI+IEVudGVuZGVyIGxhIGJhc2UgZGUgZGF0b3MgPC9zcGFuPiAKYGBge3J9CnN1bW1hcnkoZGYxKQpzdHIoZGYxKQpkZjEgPC0gbmEub21pdChkZjEpCmBgYAoKCiMgPHNwYW4gc3R5bGU9ImNvbG9yOmxpZ2h0IGJyb3duOyI+IEFyYm9sIGRlIGRlY2lzacOzbiAgPC9zcGFuPiAKYGBge3J9CmxpYnJhcnkocnBhcnQpCgphcmJvbCA8LSBycGFydChQcmljZSB+IFJvb21zICsgRGlzdGFuY2UgKyBCZWRyb29tMiArIEJhdGhyb29tICsgQ2FyICsKICAgICAgICAgICAgICAgTGFuZHNpemUgKyBCdWlsZGluZ0FyZWEgKyBQcm9wZXJ0eWNvdW50LCBkYXRhID0gZGYxKQoKcGxvdChhcmJvbCwgdW5pZm9ybSA9IFRSVUUpCnRleHQoYXJib2wsIGNleCA9IDAuNSkKCiMgUHJlZGljY2lvbmVzIGNvbiBsb3MgcHJpbWVyb3MgcmVnaXN0cm9zCnByZWRfYXJib2wgPC0gcHJlZGljdChhcmJvbCwgaGVhZChkZjEpKQpoZWFkKGRmMSRQcmljZSkKcHJ1ZWJhX2FyYm9sIDwtIGhlYWQoZGYxKQoKIyBNQUUgbWFudWFsCm1hZV9hcmJvbCA8LSBtZWFuKGFicyhwcmVkX2FyYm9sIC0gcHJ1ZWJhX2FyYm9sJFByaWNlKSkKbWFlX2FyYm9sCgoKCmBgYAoKCiMgPHNwYW4gc3R5bGU9ImNvbG9yOmJyb3duOyI+IEJvc3F1ZSBBbGVhdG9yaW8gPC9zcGFuPiAKYGBge3J9CnNldC5zZWVkKDEyMykKCiMgRWxpbWluYXIgTkFzIHNvbG8gZW4gbGFzIHZhcmlhYmxlcyB1c2FkYXMKZGYxX2NsZWFuIDwtIG5hLm9taXQoZGYxWywgYygiUHJpY2UiLCJSb29tcyIsIkRpc3RhbmNlIiwiQmVkcm9vbTIiLCJCYXRocm9vbSIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkNhciIsIkxhbmRzaXplIiwiQnVpbGRpbmdBcmVhIiwiUHJvcGVydHljb3VudCIpXSkKCiMgUGFydGljacOzbiBkZSBlbnRyZW5hbWllbnRvICg4MCUpIHkgcHJ1ZWJhICgyMCUpCnJlbmdsb25lc19lbnRyZW5hbWllbnRvIDwtIGNyZWF0ZURhdGFQYXJ0aXRpb24oZGYxX2NsZWFuJFByaWNlLCBwID0gMC44LCBsaXN0ID0gRkFMU0UpCgplbnRyZW5hbWllbnRvIDwtIGRmMV9jbGVhbltyZW5nbG9uZXNfZW50cmVuYW1pZW50bywgXQpwcnVlYmEgPC0gZGYxX2NsZWFuWy1yZW5nbG9uZXNfZW50cmVuYW1pZW50bywgXQoKIyBNb2RlbG8gZGUgUmFuZG9tIEZvcmVzdApyZiA8LSByYW5kb21Gb3Jlc3QoUHJpY2UgfiBSb29tcyArIERpc3RhbmNlICsgQmVkcm9vbTIgKyBCYXRocm9vbSArIENhciArCiAgICAgICAgICAgICAgICAgICBMYW5kc2l6ZSArIEJ1aWxkaW5nQXJlYSArIFByb3BlcnR5Y291bnQsCiAgICAgICAgICAgICAgICAgICBkYXRhID0gZW50cmVuYW1pZW50bywgbnRyZWUgPSA1MDAsIG10cnkgPSAzLCBpbXBvcnRhbmNlID0gVFJVRSkKCiMgUHJlZGljY2lvbmVzCnJlc3VsdGFkb19lbnRyZW5hbWllbnRvIDwtIHByZWRpY3QocmYsIGVudHJlbmFtaWVudG8pCnJlc3VsdGFkb19wcnVlYmEgPC0gcHJlZGljdChyZiwgcHJ1ZWJhKQoKIyBNQUUgY29ycmVnaWRvIChwcmVkaWNobyB2cyByZWFsKQptYWVfcmYgPC0gbWVhbihhYnMocmVzdWx0YWRvX3BydWViYSAtIHBydWViYSRQcmljZSkpCgojIENvbXBhcmFjacOzbiBkZSBtb2RlbG9zCnJlc3VsdGFkb3MgPC0gdGliYmxlKE1vZGVsbyA9IGMoIkFyYm9sIGRlIGRlY2lzacOzbiIsICJCb3NxdWUgQWxlYXRvcmlvIiksCiAgICAgICAgICAgICAgICAgICAgIE1BRSA9IGMobWFlX2FyYm9sLCBtYWVfcmYpKQoKcmVzdWx0YWRvcwpgYGAKCgojIDxzcGFuIHN0eWxlPSJjb2xvcjpicm93bjsiPiBFamVyY2ljaW8gMS4gUmVkaW1pZW50byBBdXRvbW90cml6IDwvc3Bhbj4gCgoKCiMgPHNwYW4gc3R5bGU9ImNvbG9yOmJyb3duOyI+IEltcG9ydGFyIGJhc2UgZGUgZGF0b3MgPC9zcGFuPiAKYGBge3J9CmRmMiA8LSBtdGNhcnMKYGBgCgojIDxzcGFuIHN0eWxlPSJjb2xvcjpicm93bjsiPiBFbnRlbmRlciBCYXNlIGRlIGRhdG9zIDwvc3Bhbj4KYGBge3J9CnN1bW1hcnkoZGYyKQpzdHIoZGYyKQpkZjIgPC0gbmEub21pdChkZjIpCmBgYAojIDxzcGFuIHN0eWxlPSJjb2xvcjpicm93bjsiPiBBcmJvbCBkZSBkZWNpc2nDs24gPC9zcGFuPiAKYGBge3J9CiMgw4FyYm9sIGRlIGRlY2lzacOzbiBTSU4gbGFzIHZhcmlhYmxlcyB2cyB5IGFtIChvYmpldGl2bzogbXBnKQpsaWJyYXJ5KHJwYXJ0KQoKYXJib2wyIDwtIHJwYXJ0KG1wZyB+IC4gLSB2cyAtIGFtLCBkYXRhID0gZGYyKQoKcGxvdChhcmJvbDIsIHVuaWZvcm0gPSBUUlVFKQp0ZXh0KGFyYm9sMiwgY2V4ID0gMC41KQoKIyBQcmVkaWNjacOzbiB5IGNvbXBhcmFjacOzbiByw6FwaWRhCnByZWRpY3QoYXJib2wyLCBoZWFkKGRmMikpCmhlYWQoZGYyJG1wZykKcHJ1ZWJhX2FyYm9sMiA8LSBoZWFkKGRmMikKCiMgTUFFOiBlcnJvciBhYnNvbHV0byBtZWRpbyAobWlzbWFzIHVuaWRhZGVzIHF1ZSBtcGcpCm1hZV9hcmJvbDIgPC0gbWVhbihhYnMocHJ1ZWJhX2FyYm9sMiRtcGcgLSBwcmVkaWN0KGFyYm9sMiwgcHJ1ZWJhX2FyYm9sMikpKQptYWVfYXJib2wyCgpgYGAKCiMgPHNwYW4gc3R5bGU9ImNvbG9yOmJyb3duOyI+IEJvc3F1ZSBBbGVhdG9yaW8gPC9zcGFuPiAKCmBgYHtyfQojIEJvc3F1ZSBBbGVhdG9yaW8gY29uIG10Y2FycwpsaWJyYXJ5KHJhbmRvbUZvcmVzdCkKbGlicmFyeShjYXJldCkgICAjIDwtLSBuZWNlc2FyaW8gcGFyYSBjcmVhdGVEYXRhUGFydGl0aW9uCmxpYnJhcnkodGliYmxlKQoKc2V0LnNlZWQoMTIzKQoKIyBQYXJ0aWNpw7NuIGRlIGVudHJlbmFtaWVudG8gKDgwJSkgeSBwcnVlYmEgKDIwJSkKcmVuZ2xvbmVzX2VudHJlbmFtaWVudG8gPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihkZjIkbXBnLCBwID0gMC44LCBsaXN0ID0gRkFMU0UpCgplbnRyZW5hbWllbnRvIDwtIGRmMltyZW5nbG9uZXNfZW50cmVuYW1pZW50bywgXQpwcnVlYmEgPC0gZGYyWy1yZW5nbG9uZXNfZW50cmVuYW1pZW50bywgXQoKIyBNb2RlbG8gZGUgUmFuZG9tIEZvcmVzdApyZjIgPC0gcmFuZG9tRm9yZXN0KG1wZyB+IC4gLSB2cyAtIGFtLAogICAgICAgICAgICAgICAgICAgIGRhdGEgPSBlbnRyZW5hbWllbnRvLAogICAgICAgICAgICAgICAgICAgIG50cmVlID0gNTAwLCBtdHJ5ID0gMywgaW1wb3J0YW5jZSA9IFRSVUUpCgojIFByZWRpY2Npb25lcwpwcmVkX3RyYWluMiA8LSBwcmVkaWN0KHJmMiwgZW50cmVuYW1pZW50bykKcHJlZF90ZXN0MiAgPC0gcHJlZGljdChyZjIsIHBydWViYSkKCiMgQ2FsY3VsYXIgTUFFIG1hbnVhbAptYWVfcmYyIDwtIG1lYW4oYWJzKHBydWViYSRtcGcgLSBwcmVkX3Rlc3QyKSkKCiMgQ29tcGFyYWNpw7NuIGRlIG1vZGVsb3MKcmVzdWx0YWRvcyA8LSB0aWJibGUoTW9kZWxvID0gYygiw4FyYm9sIGRlIGRlY2lzacOzbiIsICJCb3NxdWUgQWxlYXRvcmlvIiksCiAgICAgICAgICAgICAgICAgICAgIE1BRSA9IGMobWFlX2FyYm9sMiwgbWFlX3JmMikpCgpyZXN1bHRhZG9zCmBgYAoKCgo=