IMPORTING AND INSPECTING DATA


Importing libraries:

library(data.table)


Importing data:

house <- read.csv("kc_house_data.csv")


View the dimentions of the data:

dim(house)
[1] 21613    21


Inspect data:

data.table(head(house,10))
data.table(tail(house,10))
str(house)
'data.frame':   21613 obs. of  21 variables:
 $ id           : num  7.13e+09 6.41e+09 5.63e+09 2.49e+09 1.95e+09 ...
 $ date         : Factor w/ 372 levels "20140502T000000",..: 165 221 291 221 284 11 57 252 340 306 ...
 $ price        : num  221900 538000 180000 604000 510000 ...
 $ bedrooms     : int  3 3 2 4 3 4 3 3 3 3 ...
 $ bathrooms    : num  1 2.25 1 3 2 4.5 2.25 1.5 1 2.5 ...
 $ sqft_living  : int  1180 2570 770 1960 1680 5420 1715 1060 1780 1890 ...
 $ sqft_lot     : int  5650 7242 10000 5000 8080 101930 6819 9711 7470 6560 ...
 $ floors       : num  1 2 1 1 1 1 2 1 1 2 ...
 $ waterfront   : int  0 0 0 0 0 0 0 0 0 0 ...
 $ view         : int  0 0 0 0 0 0 0 0 0 0 ...
 $ condition    : int  3 3 3 5 3 3 3 3 3 3 ...
 $ grade        : int  7 7 6 7 8 11 7 7 7 7 ...
 $ sqft_above   : int  1180 2170 770 1050 1680 3890 1715 1060 1050 1890 ...
 $ sqft_basement: int  0 400 0 910 0 1530 0 0 730 0 ...
 $ yr_built     : int  1955 1951 1933 1965 1987 2001 1995 1963 1960 2003 ...
 $ yr_renovated : int  0 1991 0 0 0 0 0 0 0 0 ...
 $ zipcode      : int  98178 98125 98028 98136 98074 98053 98003 98198 98146 98038 ...
 $ lat          : num  47.5 47.7 47.7 47.5 47.6 ...
 $ long         : num  -122 -122 -122 -122 -122 ...
 $ sqft_living15: int  1340 1690 2720 1360 1800 4760 2238 1650 1780 2390 ...
 $ sqft_lot15   : int  5650 7639 8062 5000 7503 101930 6819 9711 8113 7570 ...
library(dplyr)
glimpse(house)
Observations: 21,613
Variables: 21
$ id            <dbl> 7129300520, 6414100192, 5631500400, 2487200875, 1954400510, 7237550310, 132...
$ date          <fctr> 20141013T000000, 20141209T000000, 20150225T000000, 20141209T000000, 201502...
$ price         <dbl> 221900, 538000, 180000, 604000, 510000, 1225000, 257500, 291850, 229500, 32...
$ bedrooms      <int> 3, 3, 2, 4, 3, 4, 3, 3, 3, 3, 3, 2, 3, 3, 5, 4, 3, 4, 2, 3, 4, 3, 5, 2, 3, ...
$ bathrooms     <dbl> 1.00, 2.25, 1.00, 3.00, 2.00, 4.50, 2.25, 1.50, 1.00, 2.50, 2.50, 1.00, 1.0...
$ sqft_living   <int> 1180, 2570, 770, 1960, 1680, 5420, 1715, 1060, 1780, 1890, 3560, 1160, 1430...
$ sqft_lot      <int> 5650, 7242, 10000, 5000, 8080, 101930, 6819, 9711, 7470, 6560, 9796, 6000, ...
$ floors        <dbl> 1.0, 2.0, 1.0, 1.0, 1.0, 1.0, 2.0, 1.0, 1.0, 2.0, 1.0, 1.0, 1.5, 1.0, 1.5, ...
$ waterfront    <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...
$ view          <int> 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 4, 0, 0, 0, ...
$ condition     <int> 3, 3, 3, 5, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 3, 3, 3, 4, 4, 4, 4, 3, 3, 3, 4, ...
$ grade         <int> 7, 7, 6, 7, 8, 11, 7, 7, 7, 7, 8, 7, 7, 7, 7, 9, 7, 7, 7, 7, 7, 9, 8, 7, 8,...
$ sqft_above    <int> 1180, 2170, 770, 1050, 1680, 3890, 1715, 1060, 1050, 1890, 1860, 860, 1430,...
$ sqft_basement <int> 0, 400, 0, 910, 0, 1530, 0, 0, 730, 0, 1700, 300, 0, 0, 0, 970, 0, 0, 0, 0,...
$ yr_built      <int> 1955, 1951, 1933, 1965, 1987, 2001, 1995, 1963, 1960, 2003, 1965, 1942, 192...
$ yr_renovated  <int> 0, 1991, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ...
$ zipcode       <int> 98178, 98125, 98028, 98136, 98074, 98053, 98003, 98198, 98146, 98038, 98007...
$ lat           <dbl> 47.5112, 47.7210, 47.7379, 47.5208, 47.6168, 47.6561, 47.3097, 47.4095, 47....
$ long          <dbl> -122.257, -122.319, -122.233, -122.393, -122.045, -122.005, -122.327, -122....
$ sqft_living15 <int> 1340, 1690, 2720, 1360, 1800, 4760, 2238, 1650, 1780, 2390, 2210, 1330, 178...
$ sqft_lot15    <int> 5650, 7639, 8062, 5000, 7503, 101930, 6819, 9711, 8113, 7570, 8925, 6000, 1...


Description of the variables:

  • id: This column indicates a notation for a house.
  • date: This column indicates the date house was sold.
  • price: This column indicates the price of house (Target Variable).
  • bedrooms: This column indicates the number of bedrooms in the house.
  • bathrooms: This column indicates the number of bathrooms in the house.
  • sqft_living: This column indicates the square footage of the house.
  • sqft_lot: This column indicates the square footage of the lot.
  • floors: This column indicates the number of floors (levels) in the house.
  • waterfront: This column indicates the house which has the view to a waterfront.
  • view:: This column indicates the house been viewed.
  • condition: This column indicates how good the condition is (overall).
  • grade: This column indicates the overall grade given to the housing unit, based on King Country grading system.
  • sqft_above: This column indicates the square footage of house apart from basement.
  • sqft_basement: This column indicates the square footage of the basement.
  • yr_built: This column indicates the year in which the house was built.
  • yr_renovated: This column indicates the year in which the house was renovated.
  • zipcode: This column indicates the zipcode.
  • lat: This column indicates the latitude coordinate.
  • long: This column indicates the longitude coordinate.
  • sqft_living15: This column indicates the living room area in 2015 (implies => some renovations) This might or might not have affected the lotsize area.
  • sqft_lot15: This column indicates the lotSize area in 2015 (implies => some renovations).


## DATA WRANGLING


#### Is data is missing?

miss <- function(x){
  sum = 0
  for(i in 1:ncol(x))
  {
    cat("In column",colnames(x[i]),"total NA values are:",colSums(is.na(x[i])),"\n")
  }
}
miss(house)
In column id total NA values are: 0 
In column date total NA values are: 0 
In column price total NA values are: 0 
In column bedrooms total NA values are: 0 
In column bathrooms total NA values are: 0 
In column sqft_living total NA values are: 0 
In column sqft_lot total NA values are: 0 
In column floors total NA values are: 0 
In column waterfront total NA values are: 0 
In column view total NA values are: 0 
In column condition total NA values are: 0 
In column grade total NA values are: 0 
In column sqft_above total NA values are: 0 
In column sqft_basement total NA values are: 0 
In column yr_built total NA values are: 0 
In column yr_renovated total NA values are: 0 
In column zipcode total NA values are: 0 
In column lat total NA values are: 0 
In column long total NA values are: 0 
In column sqft_living15 total NA values are: 0 
In column sqft_lot15 total NA values are: 0 


  • There is no missing values in the data!!!


Is data is empty?

blank <- function(x){
  sum = 0
  for(i in 1:ncol(x))
  {
    cat("In column",colnames(x[i]),"total blank values are:",colSums(x[i]==""),"\n")
  }
}
blank(house)
In column id total blank values are: 0 
In column date total blank values are: 0 
In column price total blank values are: 0 
In column bedrooms total blank values are: 0 
In column bathrooms total blank values are: 0 
In column sqft_living total blank values are: 0 
In column sqft_lot total blank values are: 0 
In column floors total blank values are: 0 
In column waterfront total blank values are: 0 
In column view total blank values are: 0 
In column condition total blank values are: 0 
In column grade total blank values are: 0 
In column sqft_above total blank values are: 0 
In column sqft_basement total blank values are: 0 
In column yr_built total blank values are: 0 
In column yr_renovated total blank values are: 0 
In column zipcode total blank values are: 0 
In column lat total blank values are: 0 
In column long total blank values are: 0 
In column sqft_living15 total blank values are: 0 
In column sqft_lot15 total blank values are: 0 


  • There is no empty values in the data!!!


Removing unwanted columns from the data:

  • Remove id column as it is of no use.
  • Remove date column as it is not meaningful.
house1 <- house[,-c(1,2)]


Summary of the whole data:

summary(house1)
     price            bedrooms        bathrooms      sqft_living       sqft_lot           floors     
 Min.   :  75000   Min.   : 0.000   Min.   :0.000   Min.   :  290   Min.   :    520   Min.   :1.000  
 1st Qu.: 321950   1st Qu.: 3.000   1st Qu.:1.750   1st Qu.: 1427   1st Qu.:   5040   1st Qu.:1.000  
 Median : 450000   Median : 3.000   Median :2.250   Median : 1910   Median :   7618   Median :1.500  
 Mean   : 540088   Mean   : 3.371   Mean   :2.115   Mean   : 2080   Mean   :  15107   Mean   :1.494  
 3rd Qu.: 645000   3rd Qu.: 4.000   3rd Qu.:2.500   3rd Qu.: 2550   3rd Qu.:  10688   3rd Qu.:2.000  
 Max.   :7700000   Max.   :33.000   Max.   :8.000   Max.   :13540   Max.   :1651359   Max.   :3.500  
   waterfront            view          condition         grade          sqft_above   sqft_basement   
 Min.   :0.000000   Min.   :0.0000   Min.   :1.000   Min.   : 1.000   Min.   : 290   Min.   :   0.0  
 1st Qu.:0.000000   1st Qu.:0.0000   1st Qu.:3.000   1st Qu.: 7.000   1st Qu.:1190   1st Qu.:   0.0  
 Median :0.000000   Median :0.0000   Median :3.000   Median : 7.000   Median :1560   Median :   0.0  
 Mean   :0.007542   Mean   :0.2343   Mean   :3.409   Mean   : 7.657   Mean   :1788   Mean   : 291.5  
 3rd Qu.:0.000000   3rd Qu.:0.0000   3rd Qu.:4.000   3rd Qu.: 8.000   3rd Qu.:2210   3rd Qu.: 560.0  
 Max.   :1.000000   Max.   :4.0000   Max.   :5.000   Max.   :13.000   Max.   :9410   Max.   :4820.0  
    yr_built     yr_renovated       zipcode           lat             long        sqft_living15 
 Min.   :1900   Min.   :   0.0   Min.   :98001   Min.   :47.16   Min.   :-122.5   Min.   : 399  
 1st Qu.:1951   1st Qu.:   0.0   1st Qu.:98033   1st Qu.:47.47   1st Qu.:-122.3   1st Qu.:1490  
 Median :1975   Median :   0.0   Median :98065   Median :47.57   Median :-122.2   Median :1840  
 Mean   :1971   Mean   :  84.4   Mean   :98078   Mean   :47.56   Mean   :-122.2   Mean   :1987  
 3rd Qu.:1997   3rd Qu.:   0.0   3rd Qu.:98118   3rd Qu.:47.68   3rd Qu.:-122.1   3rd Qu.:2360  
 Max.   :2015   Max.   :2015.0   Max.   :98199   Max.   :47.78   Max.   :-121.3   Max.   :6210  
   sqft_lot15    
 Min.   :   651  
 1st Qu.:  5100  
 Median :  7620  
 Mean   : 12768  
 3rd Qu.: 10083  
 Max.   :871200  


SALES PRIDICTION


First split the data into training and test data:

   library(caTools)
package <U+393C><U+3E31>caTools<U+393C><U+3E32> was built under R version 3.4.4
set.seed(123)
split <- sample.split(house1$price,SplitRatio = 0.75)
training_set <- subset(house1, split == TRUE)
test_set <- subset(house1, split == FALSE)


Check the correlation between the variables:

library(corrplot)
corrplot 0.84 loaded
options(repr.plot.width=10, repr.plot.height=10)
corr<-cor(house1[,c(1:15,18,19)])
corrplot(corr,method = "color", outline = T, addgrid.col = "darkgray", order="hclust", addrect = 4, rect.col = "black", rect.lwd = 5,cl.pos = "b", tl.col = "indianred4", tl.cex = 1.5, cl.cex = 1.5, addCoef.col = "black", number.digits = 2, number.cex = 0.75, col = colorRampPalette(c("green4","white","red"))(100))


Now creat the multiple linear regression model to fit the training dataset:

regressor <- lm(price ~ bedrooms + bathrooms + sqft_living + sqft_lot + floors + waterfront + view + condition + grade + sqft_above + sqft_basement + yr_built + yr_renovated + zipcode + sqft_living15 + sqft_lot15, data = training_set)


Now check the model:

options(scipen = 999)
summary(regressor)

Call:
lm(formula = price ~ bedrooms + bathrooms + sqft_living + sqft_lot + 
    floors + waterfront + view + condition + grade + sqft_above + 
    sqft_basement + yr_built + yr_renovated + zipcode + sqft_living15 + 
    sqft_lot15, data = training_set)

Residuals:
     Min       1Q   Median       3Q      Max 
-1296939  -111677    -9825    92081  4168237 

Coefficients: (1 not defined because of singularities)
                   Estimate    Std. Error t value             Pr(>|t|)    
(Intercept)   9699903.30218 3634933.32384   2.669              0.00763 ** 
bedrooms       -42737.27095    2337.32266 -18.285 < 0.0000000000000002 ***
bathrooms       45152.95452    4046.07923  11.160 < 0.0000000000000002 ***
sqft_living       181.61504       5.42746  33.462 < 0.0000000000000002 ***
sqft_lot            0.02038       0.05948   0.343              0.73193    
floors          23342.45448    4472.82155   5.219    0.000000182277830 ***
waterfront     622641.20338   21429.25029  29.056 < 0.0000000000000002 ***
view            43166.46999    2622.78965  16.458 < 0.0000000000000002 ***
condition       18590.10956    2950.98054   6.300    0.000000000305699 ***
grade          119691.42897    2629.77930  45.514 < 0.0000000000000002 ***
sqft_above         -5.59422       5.32644  -1.050              0.29361    
sqft_basement            NA            NA      NA                   NA    
yr_built        -3603.01098      86.55945 -41.625 < 0.0000000000000002 ***
yr_renovated        9.96793       4.54884   2.191              0.02844 *  
zipcode           -34.99556      36.48498  -0.959              0.33748    
sqft_living15      17.41835       4.23536   4.113    0.000039307331800 ***
sqft_lot15         -0.65627       0.09155  -7.169    0.000000000000789 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 222900 on 16940 degrees of freedom
Multiple R-squared:  0.6612,    Adjusted R-squared:  0.6609 
F-statistic:  2204 on 15 and 16940 DF,  p-value: < 0.00000000000000022


  • Here we found the variable bedrooms, bathrooms, sqft_living, floors, waterfront, view, condition, grade, yr_built, sqft_living15, sqft_lot15 are highly significant as there P-value is less than 5%.


Predicting the price of houses for test data using our model:

pricePred <- predict(regressor, newdata = test_set)
prediction from a rank-deficient fit may be misleading
head(pricePred,30)
        4        12        15        17        19        48        49        51        54        65 
 474906.6  389701.7  565319.5  344345.8  470651.4  713791.7  349678.9  279401.6  414766.4  343844.2 
       71        72        75        80        84        88        90        94        95       109 
1394889.4  309000.9  414544.9  938408.8  490465.5  759349.3  171798.4  560398.5  560398.5  936329.6 
      123       128       143       145       147       149       154       156       157       166 
 148009.7  525819.0  361023.8  584687.7  822615.7  555430.6 1656548.8  257226.2  943530.5  321495.1 


How much our prediction is correct?

priceDiff <- test_set$price - pricePred
head(priceDiff, 30)
         4         12         15         17         19         48         49         51         54 
 129093.39   78298.28  -35319.50   50654.20 -281651.44   71208.29  100321.06  -51401.62  170233.63 
        65         71         72         75         80         84         88         90         94 
  81155.82 -354889.36   15999.07  -65544.90 -218408.76 -134465.52 -298349.33  163201.58 -130398.52 
        95        109        123        128        143        145        147        149        154 
 139601.48 -256329.55  246990.35 -265818.99 -156023.77  -84687.69   67384.28 -297430.56  593451.23 
       156        157        166 
 -42226.19 -293530.53   48504.86 


library(ggplot2)
library(gridExtra)

Attaching package: <U+393C><U+3E31>gridExtra<U+393C><U+3E32>

The following object is masked from <U+393C><U+3E31>package:dplyr<U+393C><U+3E32>:

    combine
g1 = ggplot(test_set,aes(x = price)) + geom_histogram(aes(y = ..density..), color = "white", fill = "purple") + stat_function(fun=dnorm, args = list(mean = mean(test_set$price), sd= sd(test_set$price)), col = 'black')
g2 = ggplot(test_set, aes(sample=c(scale(price)))) + stat_qq() + geom_abline(intercept = 0, slope = 1)
g3 = data.frame(priceDiff) %>% ggplot(aes(priceDiff)) + geom_histogram(aes(y = ..density..), color = "white", fill = "purple") + stat_function(fun=dnorm, args = list(mean = mean(priceDiff), sd= sd(priceDiff)), col = 'black')
g4 = data.frame(priceDiff) %>% ggplot(aes(sample=c(scale(priceDiff)))) + stat_qq() + geom_abline(intercept = 0, slope = 1)
grid.arrange(g1, g2, g3, g4, nrow = 2)


Checking our model by visualization and also check the assumptions:

par(mfrow = c(2,2))
plot(regressor)


Check the correlation accuracy between actual price and predicted price:

df1 <- data.frame(cbind(actual = test_set$price, predicted = pricePred))
corraccuracy <- cor(df1)
corraccuracy
             actual predicted
actual    1.0000000 0.7869606
predicted 0.7869606 1.0000000


Now optimising the regression model to improve the performance of the model:

  • Remove the columns sqft_lot, sqft_basement, sqft_above, yr_renovated, zipcode since they have no significance level.
regressor1 <- lm(price ~ bedrooms + bathrooms + sqft_living + floors + waterfront + view + condition + grade + yr_built + sqft_living15 + sqft_lot15, data = training_set)
summary(regressor1)

Call:
lm(formula = price ~ bedrooms + bathrooms + sqft_living + floors + 
    waterfront + view + condition + grade + yr_built + sqft_living15 + 
    sqft_lot15, data = training_set)

Residuals:
     Min       1Q   Median       3Q      Max 
-1307416  -112001   -10135    92130  4175456 

Coefficients:
                   Estimate    Std. Error t value             Pr(>|t|)    
(Intercept)   6350684.88941  151067.08913  42.039 < 0.0000000000000002 ***
bedrooms       -42780.75211    2333.01079 -18.337 < 0.0000000000000002 ***
bathrooms       47047.82502    3949.21716  11.913 < 0.0000000000000002 ***
sqft_living       177.98913       4.11935  43.208 < 0.0000000000000002 ***
floors          21280.27611    3996.52367   5.325       0.000000102411 ***
waterfront     624194.65274   21385.63463  29.188 < 0.0000000000000002 ***
view            43514.16897    2563.21234  16.976 < 0.0000000000000002 ***
condition       18004.05223    2872.28743   6.268       0.000000000374 ***
grade          119404.88001    2608.82004  45.770 < 0.0000000000000002 ***
yr_built        -3643.75905      77.76533 -46.856 < 0.0000000000000002 ***
sqft_living15      16.87998       4.08846   4.129       0.000036658928 ***
sqft_lot15         -0.63156       0.06415  -9.846 < 0.0000000000000002 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 223000 on 16944 degrees of freedom
Multiple R-squared:  0.661, Adjusted R-squared:  0.6608 
F-statistic:  3004 on 11 and 16944 DF,  p-value: < 0.00000000000000022


  • Now we get all columns with high significance level.


Now again predict the price for the test data:

pricePred2 <- predict(regressor1, newdata = test_set)
head(pricePred,30)
        4        12        15        17        19        48        49        51        54        65 
 474906.6  389701.7  565319.5  344345.8  470651.4  713791.7  349678.9  279401.6  414766.4  343844.2 
       71        72        75        80        84        88        90        94        95       109 
1394889.4  309000.9  414544.9  938408.8  490465.5  759349.3  171798.4  560398.5  560398.5  936329.6 
      123       128       143       145       147       149       154       156       157       166 
 148009.7  525819.0  361023.8  584687.7  822615.7  555430.6 1656548.8  257226.2  943530.5  321495.1 


How much is our prediction is correct?

priceDiff2 <- test_set$price - pricePred2
head(priceDiff2, 30)
         4         12         15         17         19         48         49         51         54 
 127488.80   77749.79  -41555.36   52360.94 -280902.67   72693.95   96815.40  -54948.45  171557.59 
        65         71         72         75         80         84         88         90         94 
  79479.95 -351316.31   15971.18  -62260.27 -218503.39 -135796.97 -296544.58  164500.02 -133079.43 
        95        109        123        128        143        145        147        149        154 
 136920.57 -258334.63  245241.44 -263026.16 -152786.99  -81930.95   64457.53 -293488.75  597772.82 
       156        157        166 
 -41517.99 -290485.64   50095.64 


  • Here we see again that there is so much difference between the actual price and our predicted price. So this model is not a best model. We can see this in visualization as well.
g1 = ggplot(test_set,aes(x = price)) + geom_histogram(aes(y = ..density..), color = "white", fill = "purple") + stat_function(fun=dnorm, args = list(mean = mean(test_set$price), sd= sd(test_set$price)), col = 'black')
g2 = ggplot(test_set, aes(sample=c(scale(price)))) + stat_qq() + geom_abline(intercept = 0, slope = 1)
g3 = data.frame(priceDiff2) %>% ggplot(aes(priceDiff2)) + geom_histogram(aes(y = ..density..), color = "white", fill = "purple") + stat_function(fun=dnorm, args = list(mean = mean(priceDiff2), sd= sd(priceDiff2)), col = 'black')
g4 = data.frame(priceDiff2) %>% ggplot(aes(sample=c(scale(priceDiff2)))) + stat_qq() + geom_abline(intercept = 0, slope = 1)
grid.arrange(g1, g2, g3, g4, nrow = 2)


Checking our optimized model by visualization and also check the assumptions:

par(mfrow = c(2,2))
plot(regressor1)


Check again the correlation accuracy between actual price and predicted price:

df2 <- data.frame(cbind(actual = test_set$price, predicted = pricePred2))
corraccuracy <- cor(df2)
corraccuracy
             actual predicted
actual    1.0000000 0.7868934
predicted 0.7868934 1.0000000


  • There is no difference between the model1 and optimized model.
gvlma::gvlma(regressor1)

Call:
lm(formula = price ~ bedrooms + bathrooms + sqft_living + floors + 
    waterfront + view + condition + grade + yr_built + sqft_living15 + 
    sqft_lot15, data = training_set)

Coefficients:
  (Intercept)       bedrooms      bathrooms    sqft_living         floors     waterfront  
 6350684.8894    -42780.7521     47047.8250       177.9891     21280.2761    624194.6527  
         view      condition          grade       yr_built  sqft_living15     sqft_lot15  
   43514.1690     18004.0522    119404.8800     -3643.7590        16.8800        -0.6316  


  • Here we see that the only one assuption is true so we can conclude that the regression model is not good prediction model for this data since Adjusted R-squared value is also low ie 66%.
LS0tDQp0aXRsZTogIkhPVVNFIFNBTEVTIElOIEtJTkcgQ09VTlRSWSwgVVNBIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KPGJyPg0KDQojIyBJTVBPUlRJTkcgQU5EIElOU1BFQ1RJTkcgREFUQQ0KDQo8YnI+DQoNCiMjIyMgSW1wb3J0aW5nIGxpYnJhcmllczoNCg0KDQpgYGB7cn0NCmxpYnJhcnkoZGF0YS50YWJsZSkNCmBgYA0KDQo8YnI+DQoNCiMjIyMgSW1wb3J0aW5nIGRhdGE6DQoNCg0KYGBge3J9DQpob3VzZSA8LSByZWFkLmNzdigia2NfaG91c2VfZGF0YS5jc3YiKQ0KYGBgDQoNCjxicj4NCg0KIyMjIyBWaWV3IHRoZSBkaW1lbnRpb25zIG9mIHRoZSBkYXRhOg0KDQoNCg0KYGBge3J9DQpkaW0oaG91c2UpDQpgYGANCg0KPGJyPg0KDQojIyMjIEluc3BlY3QgZGF0YToNCg0KYGBge3J9DQpkYXRhLnRhYmxlKGhlYWQoaG91c2UsMTApKQ0KYGBgDQoNCmBgYHtyfQ0KZGF0YS50YWJsZSh0YWlsKGhvdXNlLDEwKSkNCmBgYA0KDQpgYGB7cn0NCnN0cihob3VzZSkNCmBgYA0KDQpgYGB7cn0NCmxpYnJhcnkoZHBseXIpDQpnbGltcHNlKGhvdXNlKQ0KYGBgDQoNCjxicj4NCg0KIyMjIyBEZXNjcmlwdGlvbiBvZiB0aGUgdmFyaWFibGVzOg0KICogPGI+aWQ6PC9iPiBUaGlzIGNvbHVtbiBpbmRpY2F0ZXMgYSBub3RhdGlvbiBmb3IgYSBob3VzZS4NCiAqIDxiPmRhdGU6PC9iPiBUaGlzIGNvbHVtbiBpbmRpY2F0ZXMgdGhlIGRhdGUgaG91c2Ugd2FzIHNvbGQuDQogKiA8Yj5wcmljZTo8L2I+IFRoaXMgY29sdW1uIGluZGljYXRlcyB0aGUgcHJpY2Ugb2YgaG91c2UgKFRhcmdldCBWYXJpYWJsZSkuDQogKiA8Yj5iZWRyb29tczo8L2I+IFRoaXMgY29sdW1uIGluZGljYXRlcyB0aGUgbnVtYmVyIG9mIGJlZHJvb21zIGluIHRoZSBob3VzZS4NCiAqIDxiPmJhdGhyb29tczo8L2I+IFRoaXMgY29sdW1uIGluZGljYXRlcyB0aGUgbnVtYmVyIG9mIGJhdGhyb29tcyBpbiB0aGUgaG91c2UuDQogKiA8Yj5zcWZ0X2xpdmluZzo8L2I+IFRoaXMgY29sdW1uIGluZGljYXRlcyB0aGUgc3F1YXJlIGZvb3RhZ2Ugb2YgdGhlIGhvdXNlLg0KICogPGI+c3FmdF9sb3Q6PC9iPiBUaGlzIGNvbHVtbiBpbmRpY2F0ZXMgdGhlIHNxdWFyZSBmb290YWdlIG9mIHRoZSBsb3QuDQogKiA8Yj5mbG9vcnM6PC9iPiBUaGlzIGNvbHVtbiBpbmRpY2F0ZXMgdGhlIG51bWJlciBvZiBmbG9vcnMgKGxldmVscykgaW4gdGhlIGhvdXNlLiANCiAqIDxiPndhdGVyZnJvbnQ6PC9iPiBUaGlzIGNvbHVtbiBpbmRpY2F0ZXMgdGhlIGhvdXNlIHdoaWNoIGhhcyB0aGUgdmlldyB0byBhIHdhdGVyZnJvbnQuDQogKiA8Yj52aWV3OjwvYj46IFRoaXMgY29sdW1uIGluZGljYXRlcyB0aGUgaG91c2UgYmVlbiB2aWV3ZWQuDQogKiA8Yj5jb25kaXRpb246PC9iPiBUaGlzIGNvbHVtbiBpbmRpY2F0ZXMgaG93IGdvb2QgdGhlIGNvbmRpdGlvbiBpcyAob3ZlcmFsbCkuDQogKiA8Yj5ncmFkZTo8L2I+IFRoaXMgY29sdW1uIGluZGljYXRlcyB0aGUgb3ZlcmFsbCBncmFkZSBnaXZlbiB0byB0aGUgaG91c2luZyB1bml0LCBiYXNlZCBvbiBLaW5nIENvdW50cnkgDQogICAgICAgICAgICAgICAgIGdyYWRpbmcgc3lzdGVtLg0KICogPGI+c3FmdF9hYm92ZTo8L2I+IFRoaXMgY29sdW1uIGluZGljYXRlcyB0aGUgc3F1YXJlIGZvb3RhZ2Ugb2YgaG91c2UgYXBhcnQgZnJvbSBiYXNlbWVudC4NCiAqIDxiPnNxZnRfYmFzZW1lbnQ6PC9iPiBUaGlzIGNvbHVtbiBpbmRpY2F0ZXMgdGhlIHNxdWFyZSBmb290YWdlIG9mIHRoZSBiYXNlbWVudC4NCiAqIDxiPnlyX2J1aWx0OjwvYj4gVGhpcyBjb2x1bW4gaW5kaWNhdGVzIHRoZSB5ZWFyIGluIHdoaWNoIHRoZSBob3VzZSB3YXMgYnVpbHQuDQogKiA8Yj55cl9yZW5vdmF0ZWQ6PC9iPiBUaGlzIGNvbHVtbiBpbmRpY2F0ZXMgdGhlIHllYXIgaW4gd2hpY2ggdGhlIGhvdXNlIHdhcyByZW5vdmF0ZWQuDQogKiA8Yj56aXBjb2RlOjwvYj4gVGhpcyBjb2x1bW4gaW5kaWNhdGVzIHRoZSB6aXBjb2RlLg0KICogPGI+bGF0OjwvYj4gVGhpcyBjb2x1bW4gaW5kaWNhdGVzIHRoZSBsYXRpdHVkZSBjb29yZGluYXRlLg0KICogPGI+bG9uZzo8L2I+IFRoaXMgY29sdW1uIGluZGljYXRlcyB0aGUgbG9uZ2l0dWRlIGNvb3JkaW5hdGUuDQogKiA8Yj5zcWZ0X2xpdmluZzE1OjwvYj4gVGhpcyBjb2x1bW4gaW5kaWNhdGVzIHRoZSBsaXZpbmcgcm9vbSBhcmVhIGluIDIwMTUgKGltcGxpZXMgPT4gc29tZSByZW5vdmF0aW9ucykNCiAgICAgICAgICAgICAgICAgICAgICAgICBUaGlzIG1pZ2h0IG9yIG1pZ2h0IG5vdCBoYXZlIGFmZmVjdGVkIHRoZSBsb3RzaXplIGFyZWEuDQogKiA8Yj5zcWZ0X2xvdDE1OjwvYj4gVGhpcyBjb2x1bW4gaW5kaWNhdGVzIHRoZSBsb3RTaXplIGFyZWEgaW4gMjAxNSAoaW1wbGllcyA9PiBzb21lIHJlbm92YXRpb25zKS4NCiANCiA8YnI+DQogDQogIyMgREFUQSBXUkFOR0xJTkcNCiANCiA8YnI+DQogDQogIyMjIyBJcyBkYXRhIGlzIG1pc3Npbmc/DQogDQogDQpgYGB7ciBmdW5jdGlvbjF9DQptaXNzIDwtIGZ1bmN0aW9uKHgpew0KICBzdW0gPSAwDQogIGZvcihpIGluIDE6bmNvbCh4KSkNCiAgew0KICAgIGNhdCgiSW4gY29sdW1uIixjb2xuYW1lcyh4W2ldKSwidG90YWwgTkEgdmFsdWVzIGFyZToiLGNvbFN1bXMoaXMubmEoeFtpXSkpLCJcbiIpDQogIH0NCn0NCm1pc3MoaG91c2UpDQpgYGAgDQoNCjxicj4NCg0KKiBUaGVyZSBpcyBubyBtaXNzaW5nIHZhbHVlcyBpbiB0aGUgZGF0YSEhIQ0KDQo8YnI+DQoNCiMjIyMgSXMgZGF0YSBpcyBlbXB0eT8NCg0KDQpgYGB7ciBmdW5jdGlvbjJ9DQpibGFuayA8LSBmdW5jdGlvbih4KXsNCiAgc3VtID0gMA0KICBmb3IoaSBpbiAxOm5jb2woeCkpDQogIHsNCiAgICBjYXQoIkluIGNvbHVtbiIsY29sbmFtZXMoeFtpXSksInRvdGFsIGJsYW5rIHZhbHVlcyBhcmU6Iixjb2xTdW1zKHhbaV09PSIiKSwiXG4iKQ0KICB9DQp9DQpibGFuayhob3VzZSkNCmBgYA0KDQo8YnI+DQoNCiogVGhlcmUgaXMgbm8gZW1wdHkgdmFsdWVzIGluIHRoZSBkYXRhISEhDQoNCjxicj4NCg0KIyMjIyBSZW1vdmluZyB1bndhbnRlZCBjb2x1bW5zIGZyb20gdGhlIGRhdGE6DQoNCiogUmVtb3ZlIDxiPiBpZCA8L2I+IGNvbHVtbiBhcyBpdCBpcyBvZiBubyB1c2UuDQoqIFJlbW92ZSA8Yj4gZGF0ZSA8L2I+IGNvbHVtbiBhcyBpdCBpcyBub3QgbWVhbmluZ2Z1bC4gDQoNCg0KYGBge3J9DQpob3VzZTEgPC0gaG91c2VbLC1jKDEsMildDQpgYGANCg0KPGJyPg0KDQojIyMjIFN1bW1hcnkgb2YgdGhlIHdob2xlIGRhdGE6IA0KDQoNCg0KYGBge3J9DQpzdW1tYXJ5KGhvdXNlMSkNCmBgYA0KDQo8YnI+DQoNCiMjIFNBTEVTIFBSSURJQ1RJT04NCg0KPGJyPg0KDQojIyMjIEZpcnN0IHNwbGl0IHRoZSBkYXRhIGludG8gdHJhaW5pbmcgYW5kIHRlc3QgZGF0YToNCg0KDQpgYGB7cn0NCmxpYnJhcnkoY2FUb29scykNCnNldC5zZWVkKDEyMykNCnNwbGl0IDwtIHNhbXBsZS5zcGxpdChob3VzZTEkcHJpY2UsU3BsaXRSYXRpbyA9IDAuNzUpDQp0cmFpbmluZ19zZXQgPC0gc3Vic2V0KGhvdXNlMSwgc3BsaXQgPT0gVFJVRSkNCnRlc3Rfc2V0IDwtIHN1YnNldChob3VzZTEsIHNwbGl0ID09IEZBTFNFKQ0KYGBgDQoNCjxicj4NCg0KIyMjIyBDaGVjayB0aGUgY29ycmVsYXRpb24gYmV0d2VlbiB0aGUgdmFyaWFibGVzOg0KDQoNCmBgYHtyfQ0KbGlicmFyeShjb3JycGxvdCkNCm9wdGlvbnMocmVwci5wbG90LndpZHRoPTEwLCByZXByLnBsb3QuaGVpZ2h0PTEwKQ0KY29ycjwtY29yKGhvdXNlMVssYygxOjE1LDE4LDE5KV0pDQpjb3JycGxvdChjb3JyLG1ldGhvZCA9ICJjb2xvciIsIG91dGxpbmUgPSBULCBhZGRncmlkLmNvbCA9ICJkYXJrZ3JheSIsIG9yZGVyPSJoY2x1c3QiLCBhZGRyZWN0ID0gNCwgcmVjdC5jb2wgPSAiYmxhY2siLCByZWN0Lmx3ZCA9IDUsY2wucG9zID0gImIiLCB0bC5jb2wgPSAiaW5kaWFucmVkNCIsIHRsLmNleCA9IDEuNSwgY2wuY2V4ID0gMS41LCBhZGRDb2VmLmNvbCA9ICJibGFjayIsIG51bWJlci5kaWdpdHMgPSAyLCBudW1iZXIuY2V4ID0gMC43NSwgY29sID0gY29sb3JSYW1wUGFsZXR0ZShjKCJncmVlbjQiLCJ3aGl0ZSIsInJlZCIpKSgxMDApKQ0KYGBgDQoNCjxicj4NCg0KIyMjIyBOb3cgY3JlYXQgdGhlIG11bHRpcGxlIGxpbmVhciByZWdyZXNzaW9uIG1vZGVsIHRvIGZpdCB0aGUgdHJhaW5pbmcgZGF0YXNldDoNCg0KDQpgYGB7cn0NCnJlZ3Jlc3NvciA8LSBsbShwcmljZSB+IGJlZHJvb21zICsgYmF0aHJvb21zICsgc3FmdF9saXZpbmcgKyBzcWZ0X2xvdCArIGZsb29ycyArIHdhdGVyZnJvbnQgKyB2aWV3ICsgY29uZGl0aW9uICsgZ3JhZGUgKyBzcWZ0X2Fib3ZlICsgc3FmdF9iYXNlbWVudCArIHlyX2J1aWx0ICsgeXJfcmVub3ZhdGVkICsgemlwY29kZSArIHNxZnRfbGl2aW5nMTUgKyBzcWZ0X2xvdDE1LCBkYXRhID0gdHJhaW5pbmdfc2V0KQ0KYGBgDQoNCjxicj4NCg0KIyMjIyBOb3cgY2hlY2sgdGhlIG1vZGVsOg0KDQoNCmBgYHtyfQ0Kb3B0aW9ucyhzY2lwZW4gPSA5OTkpDQpzdW1tYXJ5KHJlZ3Jlc3NvcikNCmBgYA0KDQo8YnI+DQoNCiogSGVyZSB3ZSBmb3VuZCB0aGUgdmFyaWFibGUgPGI+IGJlZHJvb21zLCBiYXRocm9vbXMsIHNxZnRfbGl2aW5nLCBmbG9vcnMsIHdhdGVyZnJvbnQsIHZpZXcsIGNvbmRpdGlvbiwgZ3JhZGUsIHlyX2J1aWx0LCBzcWZ0X2xpdmluZzE1LCBzcWZ0X2xvdDE1IDwvYj4gYXJlIGhpZ2hseSBzaWduaWZpY2FudCBhcyB0aGVyZSBQLXZhbHVlIGlzIGxlc3MgdGhhbiA1JS4NCg0KPGJyPg0KDQojIyBQcmVkaWN0aW5nIHRoZSBwcmljZSBvZiBob3VzZXMgZm9yIHRlc3QgZGF0YSB1c2luZyBvdXIgbW9kZWw6DQoNCg0KYGBge3J9DQpwcmljZVByZWQgPC0gcHJlZGljdChyZWdyZXNzb3IsIG5ld2RhdGEgPSB0ZXN0X3NldCkNCmhlYWQocHJpY2VQcmVkLDMwKQ0KYGBgDQoNCjxicj4NCg0KIyMgSG93IG11Y2ggb3VyIHByZWRpY3Rpb24gaXMgY29ycmVjdD8NCg0KDQpgYGB7cn0NCnByaWNlRGlmZiA8LSB0ZXN0X3NldCRwcmljZSAtIHByaWNlUHJlZA0KaGVhZChwcmljZURpZmYsIDMwKQ0KYGBgDQoNCjxicj4NCg0KKiBIZXJlIHdlIHNlZSB0aGF0IHRoZXJlIGlzIHNvIG11Y2ggZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSBhY3R1YWwgcHJpY2UgYW5kIG91ciBwcmVkaWN0ZWQgcHJpY2UuIFNvIHRoaXMgbW9kZWwgaXMgbm90IGEgYmVzdCBtb2RlbC4gV2UgY2FuIHNlZSB0aGlzIGluIHZpc3VhbGl6YXRpb24gYXMgd2VsbC4NCg0KDQpgYGB7cn0NCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoZ3JpZEV4dHJhKQ0KYGBgDQoNCmBgYHtyfQ0KZzEgPSBnZ3Bsb3QodGVzdF9zZXQsYWVzKHggPSBwcmljZSkpICsgZ2VvbV9oaXN0b2dyYW0oYWVzKHkgPSAuLmRlbnNpdHkuLiksIGNvbG9yID0gIndoaXRlIiwgZmlsbCA9ICJwdXJwbGUiKSArIHN0YXRfZnVuY3Rpb24oZnVuPWRub3JtLCBhcmdzID0gbGlzdChtZWFuID0gbWVhbih0ZXN0X3NldCRwcmljZSksIHNkPSBzZCh0ZXN0X3NldCRwcmljZSkpLCBjb2wgPSAnYmxhY2snKQ0KDQpnMiA9IGdncGxvdCh0ZXN0X3NldCwgYWVzKHNhbXBsZT1jKHNjYWxlKHByaWNlKSkpKSArIHN0YXRfcXEoKSArIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSkNCg0KZzMgPSBkYXRhLmZyYW1lKHByaWNlRGlmZikgJT4lIGdncGxvdChhZXMocHJpY2VEaWZmKSkgKyBnZW9tX2hpc3RvZ3JhbShhZXMoeSA9IC4uZGVuc2l0eS4uKSwgY29sb3IgPSAid2hpdGUiLCBmaWxsID0gInB1cnBsZSIpICsgc3RhdF9mdW5jdGlvbihmdW49ZG5vcm0sIGFyZ3MgPSBsaXN0KG1lYW4gPSBtZWFuKHByaWNlRGlmZiksIHNkPSBzZChwcmljZURpZmYpKSwgY29sID0gJ2JsYWNrJykNCg0KZzQgPSBkYXRhLmZyYW1lKHByaWNlRGlmZikgJT4lIGdncGxvdChhZXMoc2FtcGxlPWMoc2NhbGUocHJpY2VEaWZmKSkpKSArIHN0YXRfcXEoKSArIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSkNCg0KZ3JpZC5hcnJhbmdlKGcxLCBnMiwgZzMsIGc0LCBucm93ID0gMikNCmBgYA0KDQo8YnI+DQoNCiMjIENoZWNraW5nIG91ciBtb2RlbCBieSB2aXN1YWxpemF0aW9uIGFuZCBhbHNvIGNoZWNrIHRoZSBhc3N1bXB0aW9uczoNCg0KDQpgYGB7cn0NCnBhcihtZnJvdyA9IGMoMiwyKSkNCnBsb3QocmVncmVzc29yKQ0KYGBgDQoNCjxicj4NCg0KIyMjIyBDaGVjayB0aGUgY29ycmVsYXRpb24gYWNjdXJhY3kgYmV0d2VlbiBhY3R1YWwgcHJpY2UgYW5kIHByZWRpY3RlZCBwcmljZToNCg0KDQpgYGB7cn0NCmRmMSA8LSBkYXRhLmZyYW1lKGNiaW5kKGFjdHVhbCA9IHRlc3Rfc2V0JHByaWNlLCBwcmVkaWN0ZWQgPSBwcmljZVByZWQpKQ0KY29ycmFjY3VyYWN5IDwtIGNvcihkZjEpDQpjb3JyYWNjdXJhY3kNCmBgYA0KDQo8YnI+DQoNCiMjIyMgTm93IG9wdGltaXNpbmcgdGhlIHJlZ3Jlc3Npb24gbW9kZWwgdG8gaW1wcm92ZSB0aGUgcGVyZm9ybWFuY2Ugb2YgdGhlIG1vZGVsOg0KDQoqIFJlbW92ZSB0aGUgY29sdW1ucyA8Yj4gc3FmdF9sb3QsIHNxZnRfYmFzZW1lbnQsIHNxZnRfYWJvdmUsIHlyX3Jlbm92YXRlZCwgemlwY29kZSA8L2I+IHNpbmNlIHRoZXkgaGF2ZSBubyBzaWduaWZpY2FuY2UgbGV2ZWwuDQoNCg0KYGBge3J9DQpyZWdyZXNzb3IxIDwtIGxtKHByaWNlIH4gYmVkcm9vbXMgKyBiYXRocm9vbXMgKyBzcWZ0X2xpdmluZyArIGZsb29ycyArIHdhdGVyZnJvbnQgKyB2aWV3ICsgY29uZGl0aW9uICsgZ3JhZGUgKyB5cl9idWlsdCArIHNxZnRfbGl2aW5nMTUgKyBzcWZ0X2xvdDE1LCBkYXRhID0gdHJhaW5pbmdfc2V0KQ0Kc3VtbWFyeShyZWdyZXNzb3IxKQ0KYGBgDQoNCjxicj4NCg0KKiBOb3cgd2UgZ2V0IGFsbCBjb2x1bW5zIHdpdGggaGlnaCBzaWduaWZpY2FuY2UgbGV2ZWwuDQoNCjxicj4NCg0KIyMgTm93IGFnYWluIHByZWRpY3QgdGhlIHByaWNlIGZvciB0aGUgdGVzdCBkYXRhOg0KDQoNCmBgYHtyfQ0KcHJpY2VQcmVkMiA8LSBwcmVkaWN0KHJlZ3Jlc3NvcjEsIG5ld2RhdGEgPSB0ZXN0X3NldCkNCmhlYWQocHJpY2VQcmVkLDMwKQ0KYGBgDQoNCjxicj4NCg0KIyMjIyBIb3cgbXVjaCBpcyBvdXIgcHJlZGljdGlvbiBpcyBjb3JyZWN0Pw0KDQoNCmBgYHtyfQ0KcHJpY2VEaWZmMiA8LSB0ZXN0X3NldCRwcmljZSAtIHByaWNlUHJlZDINCmhlYWQocHJpY2VEaWZmMiwgMzApDQpgYGANCg0KPGJyPg0KDQoqIEhlcmUgd2Ugc2VlIGFnYWluIHRoYXQgdGhlcmUgaXMgc28gbXVjaCBkaWZmZXJlbmNlIGJldHdlZW4gdGhlIGFjdHVhbCBwcmljZSBhbmQgb3VyIHByZWRpY3RlZCBwcmljZS4gU28gdGhpcyBtb2RlbCBpcyBub3QgYSBiZXN0IG1vZGVsLiBXZSBjYW4gc2VlIHRoaXMgaW4gdmlzdWFsaXphdGlvbiBhcyB3ZWxsLg0KDQoNCmBgYHtyfQ0KZzEgPSBnZ3Bsb3QodGVzdF9zZXQsYWVzKHggPSBwcmljZSkpICsgZ2VvbV9oaXN0b2dyYW0oYWVzKHkgPSAuLmRlbnNpdHkuLiksIGNvbG9yID0gIndoaXRlIiwgZmlsbCA9ICJwdXJwbGUiKSArIHN0YXRfZnVuY3Rpb24oZnVuPWRub3JtLCBhcmdzID0gbGlzdChtZWFuID0gbWVhbih0ZXN0X3NldCRwcmljZSksIHNkPSBzZCh0ZXN0X3NldCRwcmljZSkpLCBjb2wgPSAnYmxhY2snKQ0KDQpnMiA9IGdncGxvdCh0ZXN0X3NldCwgYWVzKHNhbXBsZT1jKHNjYWxlKHByaWNlKSkpKSArIHN0YXRfcXEoKSArIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSkNCg0KZzMgPSBkYXRhLmZyYW1lKHByaWNlRGlmZjIpICU+JSBnZ3Bsb3QoYWVzKHByaWNlRGlmZjIpKSArIGdlb21faGlzdG9ncmFtKGFlcyh5ID0gLi5kZW5zaXR5Li4pLCBjb2xvciA9ICJ3aGl0ZSIsIGZpbGwgPSAicHVycGxlIikgKyBzdGF0X2Z1bmN0aW9uKGZ1bj1kbm9ybSwgYXJncyA9IGxpc3QobWVhbiA9IG1lYW4ocHJpY2VEaWZmMiksIHNkPSBzZChwcmljZURpZmYyKSksIGNvbCA9ICdibGFjaycpDQoNCmc0ID0gZGF0YS5mcmFtZShwcmljZURpZmYyKSAlPiUgZ2dwbG90KGFlcyhzYW1wbGU9YyhzY2FsZShwcmljZURpZmYyKSkpKSArIHN0YXRfcXEoKSArIGdlb21fYWJsaW5lKGludGVyY2VwdCA9IDAsIHNsb3BlID0gMSkNCg0KZ3JpZC5hcnJhbmdlKGcxLCBnMiwgZzMsIGc0LCBucm93ID0gMikNCmBgYA0KDQo8YnI+DQoNCiMjIENoZWNraW5nIG91ciBvcHRpbWl6ZWQgbW9kZWwgYnkgdmlzdWFsaXphdGlvbiBhbmQgYWxzbyBjaGVjayB0aGUgYXNzdW1wdGlvbnM6DQoNCg0KYGBge3J9DQpwYXIobWZyb3cgPSBjKDIsMikpDQpwbG90KHJlZ3Jlc3NvcjEpDQpgYGANCg0KPGJyPg0KDQojIyMjIENoZWNrIGFnYWluIHRoZSBjb3JyZWxhdGlvbiBhY2N1cmFjeSBiZXR3ZWVuIGFjdHVhbCBwcmljZSBhbmQgcHJlZGljdGVkIHByaWNlOg0KDQoNCmBgYHtyfQ0KZGYyIDwtIGRhdGEuZnJhbWUoY2JpbmQoYWN0dWFsID0gdGVzdF9zZXQkcHJpY2UsIHByZWRpY3RlZCA9IHByaWNlUHJlZDIpKQ0KY29ycmFjY3VyYWN5IDwtIGNvcihkZjIpDQpjb3JyYWNjdXJhY3kNCmBgYA0KDQo8YnI+DQoNCiogVGhlcmUgaXMgbm8gZGlmZmVyZW5jZSBiZXR3ZWVuIHRoZSBtb2RlbDEgYW5kIG9wdGltaXplZCBtb2RlbC4NCg0KYGBge3J9DQpndmxtYTo6Z3ZsbWEocmVncmVzc29yMSkNCmBgYA0KDQo8YnI+DQoNCiogSGVyZSB3ZSBzZWUgdGhhdCB0aGUgb25seSBvbmUgYXNzdXB0aW9uIGlzIHRydWUgc28gd2UgY2FuIGNvbmNsdWRlIHRoYXQgdGhlIHJlZ3Jlc3Npb24gbW9kZWwgaXMgbm90IGdvb2QgcHJlZGljdGlvbiBtb2RlbCBmb3IgdGhpcyBkYXRhIHNpbmNlIEFkanVzdGVkIFItc3F1YXJlZCB2YWx1ZSBpcyBhbHNvIGxvdyBpZSA2NiUuDQoNCmBgYHtyfQ0KDQpgYGANCg0K