Machine Learning in R by Udemy

step 1 - import dataset

dataset = read.csv('Data.csv')

step 2 - missing data

Often datasets have missing data, and the common practice is to remove missing data rows but it has a negative impact on observations. Best practice is to take the means of each columns with missing data.

dataset = read.csv('./Part 1 Data Preprocessing/R/Data.csv')

dataset$Age = ifelse(is.na(dataset$Age),
                     ave(dataset$Age, 
                         FUN = function(x) mean(x, na.rm = TRUE)),
                     dataset$Age) # return column
                
dataset$Salary = ifelse(is.na(dataset$Salary),
                        ave(dataset$Salary, FUN = function(x) mean(x, na.rm = TRUE)),
                        dataset$Salary)
dataset                       

Categorical data Need to encode categorical data into numerical data type

Step 3 - Encoding categorical data

Need the factor() function which takes categorical variables and stores it in levels (helps reduce redundancy and CPU memory). Requires c() vectors.

Example:

dataset = read.csv('./Part 1 Data Preprocessing/R/Data.csv')

# want to convert countries to number representation 
dataset$Country = factor(dataset$Country,
                         levels = c('France', 'Spain', 'Germany'),
                         labels = c(1, 2, 3))

dataset$Purchased = factor(dataset$Purchased,
                           levels = c('No', 'Yes'),
                           labels = c(0, 1))
dataset                   

step 4 - Train/Test split of data

Need to import a library that makes Train Test split easy.

dataset = read.csv('./Part 1 Data Preprocessing/R/Data.csv')

# install.packages('caTools')

# load library
library(caTools)

set.seed(123)

#------ Train / Test split
#                    dataset$DependentVariable
split = sample.split(dataset$Purchased, 
                     SplitRatio = 0.8) # returns T/F, 

# True => Training set, False => Test set
training_set = subset(dataset, split == TRUE)
test_set = subset(dataset, split == FALSE)

dataset

step 5 - Feature Scaling

Euclidean Distance between coordinates == x and y values, when one variables has more variance it becomes dominant in data when they are squared. This is why variables need to be in the same scale value range.

Two Feature Scaling methods:

Not all R libraries require feature scaling to be done beforehand, some deal with it for you.

dataset = read.csv('./Part 1 Data Preprocessing/R/Data.csv')
#------ Feature Scaling
# watch out for errors from categorical factors, factors are not numerical
# need to exclude categorical columns
# grab just the numerical, use slicing for columns 2 and 3 Age and Salary
# slice notation [, 2:3]

training_set[, 2:3] = scale(training_set[, 2:3])
test_set[, 2:3] = scale(test_set[, 2:3])   

End of Data Preprocessing


Simple Linear Regression

(dependent var) = (constant) + (coefficient, unit changes) * (Indep. var)

The formula: y = \(b_{0}\) + \(b_{1}\) * \(x_{1}\)

Example:

Find the line of best fit is Ordinary Least Squares

Fitting Simple Linear Regression to the Training set

# step 1
dataset = read.csv('./Part 2 - Regression/Section 4 - Simple Linear Regression/R/Salary_Data.csv')

# step 3, 4
split = sample.split(dataset$Salary, SplitRatio = 2/3)
training_set = subset(dataset, split == TRUE)
test_set = subset(dataset, split == FALSE)

# no feature scaling is required with this caTools library

#-------- Linear Regression lm() 
# the '~' means "as a function of"
# ~ separates dependent variable from the indep. var
# (dep. var) ~ (indep. var)
# how it will be plotted: (y-axis var) ~ (x-axis var)
regressor = lm(formula = Salary ~ YearsExperience,
               data = training_set)

#---- Predicting the Test set results
# predict salaries based on Years Experience
y_pred = predict(regressor, newdata = test_set)

summary(regressor)

Call:
lm(formula = Salary ~ YearsExperience, data = training_set)

Residuals:
   Min     1Q Median     3Q    Max 
 -7580  -4472  -1390   3586  12154 

Coefficients:
                Estimate Std. Error t value Pr(>|t|)    
(Intercept)      26547.8     2838.2   9.354 2.47e-08 ***
YearsExperience   9206.4      464.4  19.824 1.12e-13 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 6098 on 18 degrees of freedom
Multiple R-squared:  0.9562,    Adjusted R-squared:  0.9538 
F-statistic:   393 on 1 and 18 DF,  p-value: 1.122e-13

the summary(regressor) shows that in the coefficients section that the YearsExperience has 3 stars, meaning it is highly statistically significant. The lower the p-value means it is more significant, when p < 0.05 == indep. var (YearsExperience) is statistically significant.

Look at what the salary is for first row item and compare with predicted value:

Visualising the Training set results

# shift + Cmd + c   is shortcut for comment line
dataset = read.csv('./Part 2 - Regression/Section 4 - Simple Linear Regression/R/Salary_Data.csv')

library(ggplot2)
ggplot() +
  geom_point(aes(x = training_set$YearsExperience, # make sure you select correct dataset!
                 y = training_set$Salary),
             colour = 'red') + 
  geom_line(aes(x = training_set$YearsExperience, 
                y = predict(regressor, newdata = training_set)),
            colour = 'blue') +
  ggtitle('Salary vs Experience (Training set)') +
  xlab('Years of experience') +
  ylab('Salary')


# Red dots are the Real data
# blue line is the model prediction

Visualising the Test set results

dataset = read.csv('./Part 2 - Regression/Section 4 - Simple Linear Regression/R/Salary_Data.csv')

library(ggplot2)
ggplot() +
  geom_point(aes(x = test_set$YearsExperience, 
                 y = test_set$Salary),
             colour = 'red') +
  geom_line(aes(x = training_set$YearsExperience, 
                y = predict(regressor, newdata = training_set)),
            colour = 'blue') +
  ggtitle('Salary vs Experience (Test set)') +
  xlab('Years of experience') +
  ylab('Salary')           

Multiple Linear Regression Intuition

The formula is:

Linear Regression Assumptions:

Using the dataset 50_Startups

P-value

Is the result statistically significant?

  • \(H_{0}\) = Null hypothesis (default)
  • \(H_{1}\) = Alt hypothesis

Coin flip:

[H or T] # flips Probability
T 1 0.50
T 2 0.25
T 3 0.12
T 4 0.06 p < 0.05 = Significant
T 5 0.03
T 6 0.01

Multiple Linear Regression Model Methods

Multiple Linear Regression

                   
# Importing the dataset
dataset = read.csv('./Part 2 - Regression/Section 5 - Multiple Linear Regression/R/50_Startups.csv')

# Encoding categorical data
dataset$State = factor(dataset$State,
                       levels = c('New York', 'California', 'Florida'),
                       labels = c(1, 2, 3)) # encoding 

# Splitting the dataset into the Training set and Test set
# install.packages('caTools')
library(caTools)

set.seed(123)

split = sample.split(dataset$Profit, SplitRatio = 0.8)

# ---- Train/Test split
training_set = subset(dataset, split == TRUE)
test_set = subset(dataset, split == FALSE)                  
                   
#------ caTools library does not need feature scaling
# Feature Scaling
# training_set = scale(training_set)
# test_set = scale(test_set)
# ------

#---- Fitting Multiple Linear Regression to the Training set

# (profit) ~ (column1 + column2 + column3 + column4)
# << shortcut >> is '.'
# (profit) ~ (all other columns)
# (profit) ~ .
regressor = lm(formula = Profit ~ .,
               data = training_set)


#---- Predicting the Test set results
y_pred = predict(regressor, 
                 newdata = test_set)                   

# see the summary statistics 
summary(regressor)

Call:
lm(formula = Profit ~ ., data = training_set)

Residuals:
   Min     1Q Median     3Q    Max 
-33128  -4865      5   6098  18065 

Coefficients:
                  Estimate Std. Error t value Pr(>|t|)    
(Intercept)      4.965e+04  7.637e+03   6.501 1.94e-07 ***
R.D.Spend        7.986e-01  5.604e-02  14.251 6.70e-16 ***
Administration  -2.942e-02  5.828e-02  -0.505    0.617    
Marketing.Spend  3.268e-02  2.127e-02   1.537    0.134    
State2           1.213e+02  3.751e+03   0.032    0.974    
State3           2.376e+02  4.127e+03   0.058    0.954    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 9908 on 34 degrees of freedom
Multiple R-squared:  0.9499,    Adjusted R-squared:  0.9425 
F-statistic:   129 on 5 and 34 DF,  p-value: < 2.2e-16
# see the predictions
y_pred
        4         5         8        11        16        20        21        24        31 
173981.09 172655.64 160250.02 135513.90 146059.36 114151.03 117081.62 110671.31  98975.29 
       32 
 96867.03 

The summary statistics have state2 and state3 which are dummy variables R created based on the factors encoded above. The p-value show the significance level value of independent variables. Lower the p-value the more significant.

For the data above, R&D Spending has a strong statistical significant (3 stars ***) effect on profits (dependent variable). Takeaway: Look at companies that spend money on R&D as it plays biggest role on profits.

The real dataset profits vs predicted profits: 182901.99 (row 4) vs predicted 173981.09 (row 4)

Backward Elimination model

reuse the code above


dataset = read.csv('./Part 2 - Regression/Section 5 - Multiple Linear Regression/R/50_Startups.csv')


# Encoding categorical data
dataset$State = factor(dataset$State,
                       levels = c('New York', 'California', 'Florida'),
                       labels = c(1, 2, 3)) # encoding 

set.seed(123)

split = sample.split(dataset$Profit, SplitRatio = 0.8)

# ---- Train/Test split
training_set = subset(dataset, split == TRUE)
test_set = subset(dataset, split == FALSE)   


# Goal: need to remove the non-stat signif indep variables, delete the ., 
#       iterate through steps and remove columns (indep variables)
# columns with spaces => use dots 

#  original:
#     Profit ~ R&D.Spend + Administration + Marketing.Spend + State

# step 1  alpha = 0.05
# step 2
regressor = lm(formula = Profit ~ R.D.Spend + Marketing.Spend,
               data = dataset)

# step 3
# find the values with highest p-values, look for the stars under Pr(>|t|)
summary(regressor)

Call:
lm(formula = Profit ~ R.D.Spend + Marketing.Spend, data = dataset)

Residuals:
   Min     1Q Median     3Q    Max 
-33645  -4632   -414   6484  17097 

Coefficients:
                 Estimate Std. Error t value Pr(>|t|)    
(Intercept)     4.698e+04  2.690e+03  17.464   <2e-16 ***
R.D.Spend       7.966e-01  4.135e-02  19.266   <2e-16 ***
Marketing.Spend 2.991e-02  1.552e-02   1.927     0.06 .  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 9161 on 47 degrees of freedom
Multiple R-squared:  0.9505,    Adjusted R-squared:  0.9483 
F-statistic: 450.8 on 2 and 47 DF,  p-value: < 2.2e-16
# step 4 remove the predictor (variables after the ~), step 3
# step 3: 
#       State2 p= 0.990 (99% > 0.05) ==> remove it
#       State3 p= 0.943 (94% > 0.05) ==> remove it
#       Admin  p= 0.602 (60% > 0.05) ==> remove it

# go to step 2, remove State, remove Admin

# Summary stats now show that Marketing Spending variable is 0.06 
# and has a '.' meaning it is (weak) stat significant 

# can remove the Marketing Spending and just have the 1 highly stat significant variable
# Step 5

Automate Backward Elimination in R

# automatic backward elimination
backwardElimination <- function(x, sl) {
    numVars = length(x)
    for (i in c(1:numVars)){
      regressor = lm(formula = Profit ~ ., data = x)
      maxVar = max(coef(summary(regressor))[c(2:numVars), "Pr(>|t|)"])
      if (maxVar > sl){
        j = which(coef(summary(regressor))[c(2:numVars), "Pr(>|t|)"] == maxVar)
        x = x[, -j]
      }
      numVars = numVars - 1
    }
    return(summary(regressor))
  }
  
SL = 0.05
dataset = dataset[, c(1,2,3,4,5)]
backwardElimination(training_set, SL)
                   

Polynomial Regression

The formula is y = \(b_{0}\) + \(b_{1}x_{1}\) + \(b_{2}x^{2}_{1}\).

used in Healthcare/ Epidemiology data

Position Salary dataset

# Importing the dataset
dataset = read.csv('./Part 2 - Regression/Section 6 - Polynomial Regression/R/Position_Salaries.csv')

# use the columns level and salary
dataset = dataset[2:3]                    

# no data splitting due to small dataset
# no feature scaling needed

# for a baseline comparison, use Simple Linear Regression
lin_reg = lm(formula = Salary ~ .,
             data = dataset)

summary(lin_reg)

Call:
lm(formula = Salary ~ ., data = dataset)

Residuals:
    Min      1Q  Median      3Q     Max 
-170818 -129720  -40379   65856  386545 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)   
(Intercept)  -195333     124790  -1.565  0.15615   
Level          80879      20112   4.021  0.00383 **
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 182700 on 8 degrees of freedom
Multiple R-squared:  0.669, Adjusted R-squared:  0.6277 
F-statistic: 16.17 on 1 and 8 DF,  p-value: 0.003833
# Fitting Polynomial Regression to the dataset
# polynomial features of indep variables (to any degree you want)
# 1 indep + dep. vars
# add column using $ and name it

#   dataset$column^2 returns squared column for all level column values
dataset$Level2 = dataset$Level^2
#   dataset$column^3 returns cubed column for all level column values
dataset$Level3 = dataset$Level^3
dataset$Level4 = dataset$Level^4

poly_reg = lm(formula = Salary ~ .,
              data = dataset)

summary(poly_reg)

Call:
lm(formula = Salary ~ ., data = dataset)

Residuals:
     1      2      3      4      5      6      7      8      9     10 
 -8357  18240   1358 -14633 -11725   6725  15997  10006 -28695  11084 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)   
(Intercept)  184166.7    67768.0   2.718  0.04189 * 
Level       -211002.3    76382.2  -2.762  0.03972 * 
Level2        94765.4    26454.2   3.582  0.01584 * 
Level3       -15463.3     3535.0  -4.374  0.00719 **
Level4          890.2      159.8   5.570  0.00257 **
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 20510 on 5 degrees of freedom
Multiple R-squared:  0.9974,    Adjusted R-squared:  0.9953 
F-statistic: 478.1 on 4 and 5 DF,  p-value: 1.213e-06
#---------------- Visualizing the Linear Regression results
# install.packages('ggplot2')
library(ggplot2)

ggplot() + # x= indep  y= dep var
  geom_point(aes(x = dataset$Level, y = dataset$Salary), # real points
             colour = 'red') +       # predict function
  geom_line(aes(x = dataset$Level, y = predict(lin_reg, newdata = dataset)), # predicted
            colour = 'blue') +
  ggtitle('Truth or Bluff (Linear Regression)') +
  xlab('Level') +
  ylab('Salary')


# the linear regression line does not fit the real data points, 
# this is clearly a polynomial problem
# predicted salaries are linear but real data points are polynomial
# real salary level 5= $125,000 vs predicted= $240,000


#-------------- Visualizing the Polynomial Regression results
# 
library(ggplot2)
ggplot() +
  geom_point(aes(x = dataset$Level, y = dataset$Salary), # real
             colour = 'red') +      # predict function, change to poly_reg
  geom_line(aes(x = dataset$Level, y = predict(poly_reg, newdata = dataset)), # predicted
            colour = 'blue') +
  ggtitle('Truth or Bluff (Polynomial Regression)') +
  xlab('Level') +
  ylab('Salary')


# the predicted line fits better with the data points, curved line



#------------ Visualizing the Regression Model results 
# (for higher resolution and smoother curve)
# 
library(ggplot2)

x_grid = seq(min(dataset$Level), max(dataset$Level), 0.1)

ggplot() +
  geom_point(aes(x = dataset$Level, y = dataset$Salary),
             colour = 'red') +
  geom_line(aes(x = x_grid, y = predict(poly_reg,
                                        newdata = data.frame(Level = x_grid,
                                                             Level2 = x_grid^2,
                                                             Level3 = x_grid^3,
                                                             Level4 = x_grid^4))),
            colour = 'blue') +
  ggtitle('Truth or Bluff (Polynomial Regression)') +
  xlab('Level') +
  ylab('Salary')


# Predicting a new result with Linear Regression
# make a prediction on based on level 6.5 
# make a new dataframe row
# y_pred.2 = predict(lin_reg, data.frame(Level = 6.5))

predict(lin_reg, data.frame(Level = 6.5))
       1 
330378.8 
# Predicting a new result with Polynomial Regression
# add polynomial features for each level column
y_pred.3 = predict(poly_reg, data.frame(Level = 6.5,
                             Level2 = 6.5^2,
                             Level3 = 6.5^3,
                             Level4 = 6.5^4))

y_pred.3
       1 
158862.5 

Support Vector Regression

# Importing the dataset
dataset = read.csv('Part 2 - Regression/Section 7 - Support Vector Regression (SVR)/R/Position_Salaries.csv')
dataset = dataset[2:3]

# Fitting Support Vector Regression to the dataset
# install.packages('e1071')
library(e1071)

regressor = svm(formula = Salary ~ .,
                data = dataset,
                type = 'eps-regression', # VERY IMPORTANT, options for type (see docs)
                kernel = 'radial')


# Predicting a new result, this is shown in Data environment right panel
y_pred = predict(regressor, data.frame(Level = 6.5))

#----------- Visualizing the SVR results
# install.packages('ggplot2')
library(ggplot2)
ggplot() +
  geom_point(aes(x = dataset$Level, y = dataset$Salary),
             colour = 'red') + # real data points
  geom_line(aes(x = dataset$Level, y = predict(regressor, newdata = dataset)),
            colour = 'blue') + # predicted points
  ggtitle('Truth or Bluff (SVR 1)') +
  xlab('Level') +
  ylab('Salary')


#----------- Visualizing the SVR results 
# (for higher resolution and smoother curve)
# install.packages('ggplot2')
library(ggplot2)
x_grid = seq(min(dataset$Level), max(dataset$Level), 0.1)
ggplot() +
  geom_point(aes(x = dataset$Level, y = dataset$Salary),
             colour = 'red') +
  geom_line(aes(x = x_grid, y = predict(regressor, newdata = data.frame(Level = x_grid))),
            colour = 'blue') +
  ggtitle('Truth or Bluff (SVR 2)') +
  xlab('Level') +
  ylab('Salary')

Decision Tree Regression

Decision Trees have 2 types (CART): Classification Trees and Regression Trees (more complex)

predicting 3rd variable (y), using x1 and x2

# Importing the dataset
dataset = read.csv('Part 2 - Regression/Section 8 - Decision Tree Regression/R/Position_Salaries.csv')
dataset = dataset[2:3]

# Fitting Support Vector Regression to the dataset
# install.packages('e1071')
library(e1071)

# Fitting Decision Tree Regression to the dataset
# install.packages('rpart')
library(rpart)

# RPART = Recursive Partitioning 
regressor = rpart(formula = Salary ~ .,
                  data = dataset,
                  control = rpart.control(minsplit = 1)) # new part 


# Predicting a new result with Decision Tree Regression
y_pred = predict(regressor, data.frame(Level = 6.5))

#--------------- Visualizing the Decision Tree Regression 
# results (higher resolution)
# install.packages('ggplot2')

library(ggplot2)

x_grid = seq(min(dataset$Level), max(dataset$Level), 0.01)

ggplot() +
  geom_point(aes(x = dataset$Level, y = dataset$Salary),
             colour = 'red') +
  geom_line(aes(x = x_grid, y = predict(regressor, newdata = data.frame(Level = x_grid))),
            colour = 'blue') +
  ggtitle('Truth or Bluff (Decision Tree Regression)') +
  xlab('Level') +
  ylab('Salary')

NA
NA
NA

this shows with the blue line the average of salaries for level 6.5 is $250,000

# Plotting the tree
plot(regressor)
text(regressor)

Random Forest Regression

Ensemble Learning = take multiple algorithms to make powerful algorithm

# Importing the dataset
dataset = read.csv('Part 2 - Regression/Section 9 - Random Forest Regression/R/Position_Salaries.csv')
dataset = dataset[2:3]

#----- Fitting Random Forest Regression to the dataset
# install.packages('randomForest')
library(randomForest)

set.seed(1234) # common random seed in R

# -- build Random Forest Model
regressor = randomForest(x = dataset[-2],
                         y = dataset$Salary,
                         ntree = 500) # change values to find best value, 500

# Predicting a new result
y_pred = predict(regressor, data.frame(Level = 6.5))
# y_pred = 160908 for ntree= 500

#----------- Visualizing the Random Forest Regression Model 
# results (for higher resolution and smoother curve)
# install.packages('ggplot2')
library(ggplot2)

x_grid = seq(min(dataset$Level), max(dataset$Level), 0.01)
ggplot() +
  geom_point(aes(x = dataset$Level, y = dataset$Salary),
             colour = 'red') +
  geom_line(aes(x = x_grid, y = predict(regressor, newdata = data.frame(Level = x_grid))),
            colour = 'blue') +
  ggtitle('Truth or Bluff (Random Forest Regression)') +
  xlab('Level') +
  ylab('Salary')

NA
NA
NA

The blue line is the predicted averages of salaries, with 500 trees, the model predicted salary of $160,908 for level 6.5

R Squared

the total of sum of squares, try to fit a line to minimize the line of least squares residuals, how good is your line compared to the average ? The closer \(R^2\) is to 1 the better!

adjusted R squared is used for multiple regression.

  • p = number of regressors
  • n= sample size
  • adj. \(R^2\) = 1 - (1 - \(R^2\)) (n - 1)/ (n - p - 1)

how well your model has been fitted, closer to 1 the better, but it is biased OLS method, R^2 never decreases, adding more variables R^2 grows. Adjusted R^2 penalizes the growing value from variables, hence it decreases in value. Droppiung unhelpful columns for models helps the adjusted R^2 value get closer to 1, which is all good news and what we want.

So in models above, the 3rd and 4th models R^2 values:

  • lm(formula= Profit ~ R&D.Spend + Marketing.Spend, data = dataset) \(R^2\) = 0.9483
  • lm(formula= Profit ~ R&D.Spend, data = dataset) \(R^2\) = 0.9454

Comparing the last 2 models: 0.9454 - 0.9483 = -0.0029, so last model did worse then 3rd model

coefficients

Understanding the coefficients

lm(formula = Profit ~ R.D.Spend + Marketing.Spend, data = dataset)

Residuals:
   Min     1Q Median     3Q    Max 
-33645  -4632   -414   6484  17097 

Coefficients:
                 Estimate Std. Error t value Pr(>|t|)    
(Intercept)     4.698e+04  2.690e+03  17.464   <2e-16 ***
R.D.Spend       7.966e-01  4.135e-02  19.266   <2e-16 ***
Marketing.Spend 2.991e-02  1.552e-02   1.927     0.06 .  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 9161 on 47 degrees of freedom
Multiple R-squared:  0.9505,    Adjusted R-squared:  0.9483 
F-statistic: 450.8 on 2 and 47 DF,  p-value: < 2.2e-16

positive value is positive correlated, increase in R.D.Spend == increase in Profit, and vice-versa.

Magnitude is column under Estimate, R.D.Spend 7.966e-01 in units of independent variable values. Correct to say: RD Spend has greater impact on profit per unit of RD Spend than marketing spend. For every unit increase of RD Spend ($1), profits will increase by 0.79 cents per unit. Marketing Spend adds value of 3 cents units to profit.

End of Part 2 – Regression

Classification

Logistic Regression

age and email action taken (Y/N | 1/0) correlation, {linear regression is not right model for this but shows a trend between variables}. Sigmoid function forces values 0 to 1, S-shaped curve on plot which makes the best fitting line for variables. Used for predicting probability (\(p^-\)) (p_hat)

Logistic Regression formula \(ln\)(p / 1 - p) = \(b_{0}\) + \(b_{1}*x\)

Example: on x-axis, 4 age column values at random, y-axis is p_hat, the s-curve on the plot. Person of age 20 has a p_hat probability value of 0.7% (p^- = 0.7) of taking action with an email. Person age 40 has p^- = 85% of taking action with an email. Any values below 50% the y_hat value is pushed down towards 0, any value above 50% is pushed up towards 1, so the results in a binary 0/1 outcome.


# Importing the dataset
dataset = read.csv('./Part 3 - Classification/Section 14 - Logistic Regression/R/Social_Network_Ads.csv')

dataset = dataset[3:5]

# Encoding the target feature as factor
dataset$Purchased = factor(dataset$Purchased, levels = c(0, 1))

# Splitting the dataset into the Training set and Test set
# install.packages('caTools')
library(caTools)
set.seed(123)
split = sample.split(dataset$Purchased, SplitRatio = 0.75)
training_set = subset(dataset, split == TRUE)
test_set = subset(dataset, split == FALSE)

# Feature Scaling is best practice for Logistic Regression
#  [-3] is the last column of dataset
training_set[-3] = scale(training_set[-3])
test_set[-3] = scale(test_set[-3])

# Fitting Logistic Regression to the Training set
# glm (general linear model) builds the logistic regression
# predict the dependent variable of Purchased based on indep. variables: age, estimated salary
classifier = glm(formula = Purchased ~ ., 
                 family = binomial,
                 data = training_set)

# Predicting the Test set results
prob_pred = predict(classifier, 
                    type = 'response', 
                    newdata = test_set[-3])

y_pred.logr = ifelse(prob_pred > 0.5, 1, 0)

# Making the Confusion Matrix
cm = table(test_set[, 3], y_pred.logr > 0.5)
cm
   
    FALSE TRUE
  0    57    7
  1    10   26
prob_pred[1]
         2 
0.01623954 
y_pred.logr[1]
2 
0 

the 1st probability predicted = 0.162 and the test_set 1st value is 0, this mean user #2 is unlikely to purchase. Using the y_pred.logr variable the model predicted that user #2 will not purchase an item.

the model correctly predicted a purchase (57+26) 83 times and 17 incorrect predictions

# Visualising the Training set results
library(ElemStatLearn)
set = training_set
X1 = seq(min(set[, 1]) - 1, max(set[, 1]) + 1, by = 0.01)
X2 = seq(min(set[, 2]) - 1, max(set[, 2]) + 1, by = 0.01)
grid_set = expand.grid(X1, X2)
colnames(grid_set) = c('Age', 'EstimatedSalary')
prob_set = predict(classifier, type = 'response', newdata = grid_set)
y_grid = ifelse(prob_set > 0.5, 1, 0)
plot(set[, -3],
     main = 'Logistic Regression (Training set)',
     xlab = 'Age', ylab = 'Estimated Salary',
     xlim = range(X1), ylim = range(X2))
contour(X1, X2, matrix(as.numeric(y_grid), length(X1), length(X2)), add = TRUE)
points(grid_set, pch = '.', col = ifelse(y_grid == 1, 'springgreen3', 'tomato'))
points(set, pch = 21, bg = ifelse(set[, 3] == 1, 'green4', 'red3'))


# Visualising the Test set results
library(ElemStatLearn)
set = test_set
X1 = seq(min(set[, 1]) - 1, max(set[, 1]) + 1, by = 0.01)
X2 = seq(min(set[, 2]) - 1, max(set[, 2]) + 1, by = 0.01)
grid_set = expand.grid(X1, X2)
colnames(grid_set) = c('Age', 'EstimatedSalary')
prob_set = predict(classifier, type = 'response', newdata = grid_set)
y_grid = ifelse(prob_set > 0.5, 1, 0)
plot(set[, -3],
     main = 'Logistic Regression (Test set)',
     xlab = 'Age', ylab = 'Estimated Salary',
     xlim = range(X1), ylim = range(X2))
contour(X1, X2, matrix(as.numeric(y_grid), length(X1), length(X2)), add = TRUE)
points(grid_set, pch = '.', col = ifelse(y_grid == 1, 'springgreen3', 'tomato'))
points(set, pch = 21, bg = ifelse(set[, 3] == 1, 'green4', 'red3'))

NA
NA
NA

Training set observation datapoints, red points are training observation where (dependent variable) Purchased=0 and the green points are training set observation where purchased=1. red zone is prediction region non-purchase. classifier predicted that the higher age the more estimated salary and to purchased item. Classifier is straight line for linear models. Focus on the dot color and the zone they fall.

K-Nearest Neighbor

KNN process:

  1. choose the number k of neighbors
  2. take the KNNs of the new data point, according to Euclidean distance
  3. among these KNNs, count the number of data points in each category
  4. assign the new data point to the category where you counted the most neighbors
  5. model is done

# Importing the dataset
dataset = read.csv('./Part 3 - Classification/Section 15 - K-Nearest Neighbors (K-NN)/R/Social_Network_Ads.csv')

dataset = dataset[3:5]

# Encoding the target feature as factor
dataset$Purchased = factor(dataset$Purchased, levels = c(0, 1))

# Splitting the dataset into the Training set and Test set
# install.packages('caTools')
library(caTools)
set.seed(123)
split = sample.split(dataset$Purchased, SplitRatio = 0.75)
training_set = subset(dataset, split == TRUE)
test_set = subset(dataset, split == FALSE)

# Feature Scaling
training_set[-3] = scale(training_set[-3])
test_set[-3] = scale(test_set[-3])

# Fitting classifier to the Training set
# Create your classifier here

# build a KNN classifier
library(class)
# fit a KNN to Training set and Predict the Test set
# remove last column of training set
y_pred.KNN = knn(train= training_set[,-3],
                 test= test_set[, -3],
                 cl= training_set[, 3],
                 k= 5) 

# y_pred.KNN[1:5]


# Predicting the Test set results
# for KNN comment out 
# y_pred = predict(classifier, newdata = test_set[-3])

# Making the Confusion Matrix
cm = table(test_set[, 3], y_pred.KNN)
cm
   y_pred.KNN
     0  1
  0 59  5
  1  6 30
#============ Visualizing the Training set results
library(ElemStatLearn)
set = training_set

X1 = seq(min(set[, 1]) - 1, max(set[, 1]) + 1, by = 0.01)
X2 = seq(min(set[, 2]) - 1, max(set[, 2]) + 1, by = 0.01)

grid_set = expand.grid(X1, X2)

colnames(grid_set) = c('Age', 'EstimatedSalary')

# y_grid = predict(classifier, newdata = grid_set)

# for KNN replace the predict and its arguments with KNN arguments
y_grid = knn(train= training_set[,-3],
                 test= grid_set, # replace test_set
                 cl= training_set[, 3],
                 k= 5)

plot(set[, -3],
     main = 'KNN Classifier (Training set)',
     xlab = 'Age', ylab = 'Estimated Salary',
     xlim = range(X1), ylim = range(X2))

contour(X1, X2, matrix(as.numeric(y_grid), length(X1), length(X2)), add = TRUE)
points(grid_set, pch = '.', col = ifelse(y_grid == 1, 'springgreen3', 'tomato'))
points(set, pch = 21, bg = ifelse(set[, 3] == 1, 'green4', 'red3'))


# Visualising the Test set results
library(ElemStatLearn)
set = test_set
X1 = seq(min(set[, 1]) - 1, max(set[, 1]) + 1, by = 0.01)
X2 = seq(min(set[, 2]) - 1, max(set[, 2]) + 1, by = 0.01)
grid_set = expand.grid(X1, X2)
colnames(grid_set) = c('Age', 'EstimatedSalary')

# for KNN replace the predict and its arguments with KNN arguments
y_grid = knn(train= training_set[,-3],
                 test= grid_set, # replace test_set
                 cl= training_set[, 3],
                 k= 5)
# y_grid = predict(classifier, newdata = grid_set)

plot(set[, -3], main = 'KNN Classifier (Test set)',
     xlab = 'Age', ylab = 'Estimated Salary',
     xlim = range(X1), ylim = range(X2))
contour(X1, X2, matrix(as.numeric(y_grid), length(X1), length(X2)), add = TRUE)
points(grid_set, pch = '.', col = ifelse(y_grid == 1, 'springgreen3', 'tomato'))
points(set, pch = 21, bg = ifelse(set[, 3] == 1, 'green4', 'red3'))

NA
NA
NA

Support Vector Machines

Started in 1960s and 1990s. Separate datapoints on a plot and classify them. Goal: find best decision boundary using a max margin hyperplane line that has a max margin (distance away from line and distance between max margin lines) and datapoints outside the max margin lines are positive or negative hyperplane.

classify apples and oranges, training on best examples of each fruit


# Importing the dataset
dataset = read.csv('./Part 3 - Classification/Section 16 - Support Vector Machine (SVM)/R/Social_Network_Ads.csv')

dataset = dataset[3:5]

# Encoding the target feature as factor
dataset$Purchased = factor(dataset$Purchased, levels = c(0, 1))

# Splitting the dataset into the Training set and Test set
# install.packages('caTools')
library(caTools)

set.seed(123)

split = sample.split(dataset$Purchased, SplitRatio = 0.75)
training_set = subset(dataset, split == TRUE)
test_set = subset(dataset, split == FALSE)

# Feature Scaling
training_set[-3] = scale(training_set[-3])
test_set[-3] = scale(test_set[-3])

# Fitting classifier to the Training set
# Create your classifier here

# library e1071 for SVM
library(e1071)

# read the documentation
classifier.SVM = svm(formula= Purchased ~ .,
                     data= training_set,
                     type= 'C-classification', # classification
                     kernel= 'linear'
                     )


# Predicting the Test set results
y_pred.SVM = predict(classifier.SVM, newdata = test_set[-3])

# Making the Confusion Matrix
cm = table(test_set[, 3], y_pred.SVM)
cm
   y_pred.SVM
     0  1
  0 57  7
  1 13 23
#============= Visualizing the Training set results
library(ElemStatLearn)
set = training_set
X1 = seq(min(set[, 1]) - 1, max(set[, 1]) + 1, by = 0.01)
X2 = seq(min(set[, 2]) - 1, max(set[, 2]) + 1, by = 0.01)

grid_set = expand.grid(X1, X2)

colnames(grid_set) = c('Age', 'EstimatedSalary')

y_grid = predict(classifier.SVM, newdata = grid_set)
plot(set[, -3],
     main = 'SVM Classifier (Training set)',
     xlab = 'Age', 
     ylab = 'Estimated Salary',
     xlim = range(X1), ylim = range(X2))

contour(X1, X2, matrix(as.numeric(y_grid), length(X1), length(X2)), add = TRUE)
points(grid_set, pch = '.', col = ifelse(y_grid == 1, 'springgreen3', 'tomato'))
points(set, pch = 21, bg = ifelse(set[, 3] == 1, 'green4', 'red3'))



#============ Visualizing the Test set results

library(ElemStatLearn)

set = test_set

X1 = seq(min(set[, 1]) - 1, max(set[, 1]) + 1, by = 0.01)
X2 = seq(min(set[, 2]) - 1, max(set[, 2]) + 1, by = 0.01)

grid_set = expand.grid(X1, X2)

colnames(grid_set) = c('Age', 'EstimatedSalary')

y_grid = predict(classifier.SVM, 
                 newdata = grid_set)
plot(set[, -3], main = 'SVM Classifier (Test set)',
     xlab = 'Age', ylab = 'Estimated Salary',
     xlim = range(X1), ylim = range(X2))

contour(X1, X2, matrix(as.numeric(y_grid), length(X1), length(X2)), add = TRUE)
points(grid_set, pch = '.', col = ifelse(y_grid == 1, 'springgreen3', 'tomato'))
points(set, pch = 21, bg = ifelse(set[, 3] == 1, 'green4', 'red3'))

NA
NA

mapping datapoints to a Higher dimensional shape separates points by mapping the points to a algebraic function then to have a hyperplane a separator, but this is very computer intensive and not practical.

Kernel SVM

Decision boundaries for when data points are clustered in circles and is not linear. Gaussian RBF Kernel.

              
 # Importing the dataset
dataset = read.csv('./Part 3 - Classification/Section 17 - Kernel SVM/R/Social_Network_Ads.csv')
dataset = dataset[3:5]

# Encoding the target feature as factor
dataset$Purchased = factor(dataset$Purchased, levels = c(0, 1))

# Splitting the dataset into the Training set and Test set
# install.packages('caTools')
library(caTools)
set.seed(123)
split = sample.split(dataset$Purchased, SplitRatio = 0.75)
training_set = subset(dataset, split == TRUE)
test_set = subset(dataset, split == FALSE)

# Feature Scaling
training_set[-3] = scale(training_set[-3])
test_set[-3] = scale(test_set[-3])


# Fitting classifier to the Training set
# Create your classifier here

#=========== Kernel SVM
library(e1071)

# Gaussian classifier, radial
classifier.SVM = svm(formula= Purchased ~ .,
                 data = training_set,
                 type = "C-classification",
                 kernel= 'radial')


# Predicting the Test set results
y_pred = predict(classifier.SVM, newdata = test_set[-3])
y_pred[1:5]
 2  4  5  9 12 
 0  0  0  0  0 
Levels: 0 1
# Making the Confusion Matrix
cm = table(test_set[, 3], y_pred)
cm
   y_pred
     0  1
  0 58  6
  1  4 32
#============== Visualizing the Training set results
library(ElemStatLearn)
set = training_set
X1 = seq(min(set[, 1]) - 1, max(set[, 1]) + 1, by = 0.01)
X2 = seq(min(set[, 2]) - 1, max(set[, 2]) + 1, by = 0.01)
grid_set = expand.grid(X1, X2)
colnames(grid_set) = c('Age', 'EstimatedSalary')
y_grid = predict(classifier.SVM, newdata = grid_set)
plot(set[, -3],
     main = 'Kernel SVM Classifier (Training set)',
     xlab = 'Age', ylab = 'Estimated Salary',
     xlim = range(X1), ylim = range(X2))
contour(X1, X2, matrix(as.numeric(y_grid), length(X1), length(X2)), add = TRUE)
points(grid_set, pch = '.', col = ifelse(y_grid == 1, 'springgreen3', 'tomato'))
points(set, pch = 21, bg = ifelse(set[, 3] == 1, 'green4', 'red3'))


#================== Visualizing the Test set results
library(ElemStatLearn)
set = test_set
X1 = seq(min(set[, 1]) - 1, max(set[, 1]) + 1, by = 0.01)
X2 = seq(min(set[, 2]) - 1, max(set[, 2]) + 1, by = 0.01)
grid_set = expand.grid(X1, X2)
colnames(grid_set) = c('Age', 'EstimatedSalary')
y_grid = predict(classifier.SVM, newdata = grid_set)
plot(set[, -3], main = 'Kernel SVM Classifier (Test set)',
     xlab = 'Age', ylab = 'Estimated Salary',
     xlim = range(X1), ylim = range(X2))
contour(X1, X2, matrix(as.numeric(y_grid), length(X1), length(X2)), add = TRUE)
points(grid_set, pch = '.', col = ifelse(y_grid == 1, 'springgreen3', 'tomato'))
points(set, pch = 21, bg = ifelse(set[, 3] == 1, 'green4', 'red3'))

Confusion matrix shows 10 incorrect results and 90 correct results

The SVM mapped data to a 3D plane, green zone is users who purchased item and red zone is no purchased item.

Naive Bayes Theorem Classification

Bayes Theorem of probability formula \(P(A|B)\) = \(P(B|A) * P(A) \div P(B)\)

Example: machine.1 makes 30 items/hr and machine.2 makes 20 items/hr. Out of all items made, 1 % is defective, and out of all defective items 50% came from machine.1 and 50% from machine.2. What is the probability that a item made by machine.2 is defective?

  • P(Machine.2) = 20/50
  • P(Defect) = 1%
  • P(Machine.2 | Defect)= 50%
  • P(Defect | Machine.2) = ?

P(Defect | Machine.2) = P(Machine.2 | Defect) * P(Defect) / P(Machine.2) P(Defect | Machine.2) = 0.5 * 0.01 / 0.4 == 0.0125 (1.25%)

Example 2 Naive Bayes (assumed independence of variables: x= age, y= salary, datapoints grouped: walks to work or drives to work. X= features P(Walks | X) = P(X | Walks) * P(Walks) / P(X)
P(Drives | X) = P(X | Drives) * P(Drives) / P(X)

repeat steps for both Walks and Drives class:

  1. prior probability: P(Walks)
  2. marginal likelihood: P(X)
  3. likelihood: P(X | Walks)
  4. posterior probability: P(Walks | X)

step 1: we have no data, so calculate the points in the walks group from a plot. - P(Walks) = number of walkers / total datapoints

step 2: select a radius on a plot, a circle to contain datapoints, look at features (age and salary) and these points will be similar. the probability of a new datapoint would fall into this radius. Count the number of points inside circle. - P(X) = number of observations / total observations

step 3: use the radius circle for similar features of datapoints, what is the likelihood of features of walkers given that person who walks (ignore the drivers). Count the number of datapoints for walkers inside the circle. - P(X | Walkers) = number of similar points for walkers / total number of walkers

step 4: P(Walks | X) = (3/10) * (10/30) / (4/30) == 0.75 (75% likelihood of datapoint being a Walker)

repeat for driver
step 4: P(Drives | X)= (1/20) * (20/30) / (4/30) = 0.25 (25%)

              
# Importing the dataset
dataset = read.csv('./Part 3 - Classification/Section 18 - Naive Bayes/R/Social_Network_Ads.csv')
dataset = dataset[3:5]

# Encoding the target feature as factor
dataset$Purchased = factor(dataset$Purchased, levels = c(0, 1))

# Splitting the dataset into the Training set and Test set
# install.packages('caTools')
library(caTools)
set.seed(123)
split = sample.split(dataset$Purchased, SplitRatio = 0.75)
training_set = subset(dataset, split == TRUE)
test_set = subset(dataset, split == FALSE)

# Feature Scaling
training_set[-3] = scale(training_set[-3])
test_set[-3] = scale(test_set[-3])

# Fitting classifier to the Training set
# Create your classifier here

# === Bayes Classifier
library(e1071)

# press F1 for documentation when mouse is on function name
classifier.Bayes = naiveBayes(x= training_set[-3],
                              y= training_set$Purchased)



# Predicting the Test set results
y_pred = predict(classifier.Bayes, newdata = test_set[-3])

# Making the Confusion Matrix
cm = table(test_set[, 3], y_pred)

#============== Visualizing the Training set results
library(ElemStatLearn)
set = training_set
X1 = seq(min(set[, 1]) - 1, max(set[, 1]) + 1, by = 0.01)
X2 = seq(min(set[, 2]) - 1, max(set[, 2]) + 1, by = 0.01)
grid_set = expand.grid(X1, X2)
colnames(grid_set) = c('Age', 'EstimatedSalary')
y_grid = predict(classifier.Bayes, newdata = grid_set)
plot(set[, -3],
     main = 'Naive Bayes Classifier (Training set)',
     xlab = 'Age', ylab = 'Estimated Salary',
     xlim = range(X1), ylim = range(X2))
contour(X1, X2, matrix(as.numeric(y_grid), length(X1), length(X2)), add = TRUE)
points(grid_set, pch = '.', col = ifelse(y_grid == 1, 'springgreen3', 'tomato'))
points(set, pch = 21, bg = ifelse(set[, 3] == 1, 'green4', 'red3'))


#========== Visualizing the Test set results
library(ElemStatLearn)
set = test_set
X1 = seq(min(set[, 1]) - 1, max(set[, 1]) + 1, by = 0.01)
X2 = seq(min(set[, 2]) - 1, max(set[, 2]) + 1, by = 0.01)
grid_set = expand.grid(X1, X2)
colnames(grid_set) = c('Age', 'EstimatedSalary')
y_grid = predict(classifier.Bayes, newdata = grid_set)
plot(set[, -3], main = 'Naive Bayes Classifier (Test set)',
     xlab = 'Age', ylab = 'Estimated Salary',
     xlim = range(X1), ylim = range(X2))
contour(X1, X2, matrix(as.numeric(y_grid), length(X1), length(X2)), add = TRUE)
points(grid_set, pch = '.', col = ifelse(y_grid == 1, 'springgreen3', 'tomato'))
points(set, pch = 21, bg = ifelse(set[, 3] == 1, 'green4', 'red3'))            

NA
NA

Decision Tree Classification

The Decision Tree Classifier first condition is age < 44.5 and salary < $90,000, our model classifies person as will not buy the item, if salary is > $90,000 person will buy the item . This was made by not running the code of feature scaling and visualizations, but the classifier and plotting function.

Random Forest Classification

Ensemble Learning = take multiple algorithms to make powerful algorithm

step 1: pick at random k data points from training set

step 2: build decision tree associated to these k data points

step 3: choose the number Ntree of trees you want to build and repeat steps 1 & 2

step 4: for a new data point, make each one of your Ntree trees predict the value of Y for the data point in question, and assign the new data point the average across all of the predicted Y values

This model is used for remote controller free gaming consoles (Microsoft connect)

# Importing the dataset
dataset = read.csv('./Part 3 - Classification/Section 20 - Random Forest Classification/R/Social_Network_Ads.csv')

dataset = dataset[3:5]

# Encoding the target feature as factor
dataset$Purchased = factor(dataset$Purchased, levels = c(0, 1))

# Splitting the dataset into the Training set and Test set
# install.packages('caTools')
library(caTools)
set.seed(123)
split = sample.split(dataset$Purchased, SplitRatio = 0.75)
training_set = subset(dataset, split == TRUE)
test_set = subset(dataset, split == FALSE)

# Feature Scaling
training_set[-3] = scale(training_set[-3])
test_set[-3] = scale(test_set[-3])

# Fitting Random Forest Classification to the Training set
# install.packages('randomForest')
library(randomForest)
randomForest 4.6-14
Type rfNews() to see new features/changes/bug fixes.
set.seed(123)
classifier = randomForest(x = training_set[-3],
                          y = training_set$Purchased,
                          ntree = 500)

# Predicting the Test set results
y_pred = predict(classifier, newdata = test_set[-3])

# Making the Confusion Matrix
cm = table(test_set[, 3], y_pred)

#========== Visualizing the Training set results
library(ElemStatLearn)
set = training_set
X1 = seq(min(set[, 1]) - 1, max(set[, 1]) + 1, by = 0.01)
X2 = seq(min(set[, 2]) - 1, max(set[, 2]) + 1, by = 0.01)
grid_set = expand.grid(X1, X2)
colnames(grid_set) = c('Age', 'EstimatedSalary')
y_grid = predict(classifier, grid_set)

plot(set[, -3],
     main = 'Random Forest Classification (Training set)',
     xlab = 'Age', ylab = 'Estimated Salary',
     xlim = range(X1), ylim = range(X2))
contour(X1, X2, matrix(as.numeric(y_grid), length(X1), length(X2)), add = TRUE)
points(grid_set, pch = '.', col = ifelse(y_grid == 1, 'springgreen3', 'tomato'))
points(set, pch = 21, bg = ifelse(set[, 3] == 1, 'green4', 'red3'))


#======== Visualizing the Test set results
library(ElemStatLearn)
set = test_set
X1 = seq(min(set[, 1]) - 1, max(set[, 1]) + 1, by = 0.01)
X2 = seq(min(set[, 2]) - 1, max(set[, 2]) + 1, by = 0.01)
grid_set = expand.grid(X1, X2)
colnames(grid_set) = c('Age', 'EstimatedSalary')
y_grid = predict(classifier, grid_set)
plot(set[, -3], main = 'Random Forest Classification (Test set)',
     xlab = 'Age', ylab = 'Estimated Salary',
     xlim = range(X1), ylim = range(X2))
contour(X1, X2, matrix(as.numeric(y_grid), length(X1), length(X2)), add = TRUE)
points(grid_set, pch = '.', col = ifelse(y_grid == 1, 'springgreen3', 'tomato'))
points(set, pch = 21, bg = ifelse(set[, 3] == 1, 'green4', 'red3'))




# Choosing the number of trees
plot(classifier)

Classification Model Evaluations

False Positives & False Negatives

False Positive (Type 1 error) {warning}, False Negative (type 2 error) {nothing to see but disaster happens}

Confusion Matrix

y_pred

|     Pred. |  0  | 1  |
|----------------------|
| actual  0 |  56 |  8 |
| actual  1 |  7  | 29 |

Accuracy Rate = correct / total . 56+29= 85 85/100== 85%
Error Rate = wrong/total . 7+8=15 15/100= 15%

Cumulative Accuracy Profile (CAP)

The curved line above the linear line on a plot, AKA Gain Chart.
CAP Analysis: area under perfect model line and under red line.

ROC is Receiver Operating Characteristic (not the same)

End of Part 3 - Classification


Clustering

In Clustering you don’t know what you are looking for, and you are trying to identify some segments or clusters in your data. When you use clustering algorithms on your dataset, unexpected things can suddenly pop up like structures, clusters and groupings you would have never thought of otherwise

K-Means Clustering

This finds the clusters for you.

Process:

  • step 1. choose the number of K of clusters
  • step 2. select at random k points, the centroids
  • step 3. assign each data point to the closest centroid => forms clusters
  • step 4. compute and place the new centroid of each cluster
  • step 5. reassign each datapoint to the new closest centroid, step 4 and back, then finished

selecting k using the “elbow method” k=3 or 4 based on a plot line


# Importing the dataset
dataset = read.csv('./Part 4 - Clustering/Section 24 - K-Means Clustering/R/Mall_Customers.csv')
dataset = dataset[4:5]

# Splitting the dataset into the Training set and Test set
# install.packages('caTools')
# library(caTools)
# set.seed(123)
# split = sample.split(dataset$DependentVariable, SplitRatio = 0.8)
# training_set = subset(dataset, split == TRUE)
# test_set = subset(dataset, split == FALSE)

# Feature Scaling
# training_set = scale(training_set)
# test_set = scale(test_set)

# Using the elbow method to find the optimal number of clusters
set.seed(6)
wcss = vector()
for (i in 1:10) wcss[i] = sum(kmeans(dataset, i)$withinss)
plot(1:10,
     wcss,
     type = 'b',
     main = paste('The Elbow Method'),
     xlab = 'Number of clusters',
     ylab = 'WCSS')


# Fitting K-Means to the dataset
set.seed(29)
kmeans = kmeans(x = dataset, centers = 5)
y_kmeans = kmeans$cluster

# Visualising the clusters
library(cluster)
clusplot(dataset,
         y_kmeans,
         lines = 0,
         shade = TRUE,
         color = TRUE,
         labels = 2,
         plotchar = FALSE,
         span = TRUE,
         main = paste('Clusters of customers'),
         xlab = 'Annual Income',
         ylab = 'Spending Score')

Hierarchical Clustering

Agglomerative (bottom-up) and Divisive (top-down)

Dendrograms (section 27: video 178, 179)


# Importing the dataset
dataset = read.csv('./Part 4 - Clustering/Section 25 - Hierarchical Clustering/R/Mall_Customers.csv')
dataset = dataset[4:5]

# Splitting the dataset into the Training set and Test set
# install.packages('caTools')
# library(caTools)
# set.seed(123)
# split = sample.split(dataset$DependentVariable, SplitRatio = 0.8)
# training_set = subset(dataset, split == TRUE)
# test_set = subset(dataset, split == FALSE)

# Feature Scaling
# training_set = scale(training_set)
# test_set = scale(test_set)

# Using the dendrogram to find the optimal number of clusters
dendrogram = hclust(d = dist(dataset, method = 'euclidean'), method = 'ward.D')
plot(dendrogram,
     main = paste('Dendrogram'),
     xlab = 'Customers',
     ylab = 'Euclidean distances')


# Fitting Hierarchical Clustering to the dataset
hc = hclust(d = dist(dataset, method = 'euclidean'), method = 'ward.D')
y_hc = cutree(hc, 5)

# Visualising the clusters
library(cluster)
clusplot(dataset,
         y_hc,
         lines = 0,
         shade = TRUE,
         color = TRUE,
         labels= 2,
         plotchar = FALSE,
         span = TRUE,
         main = paste('Clusters of customers'),
         xlab = 'Annual Income',
         ylab = 'Spending Score')

NA
NA

Association Rule

“people who bought X also bought Y”

Apriori algorithm


movie recommendation: 
  support(M) = # user watchlist containing M / # user watchlists

movie recommendation: 
  confidence(M1 -> M2) = # user watchlist containing M1, M2 / # user watchlists containing M1

movie recommendation: 
  lift(M1 -> M2) = confidence M1, M2 / support M2
  
  
market basket optimization: 
  support(J) = # transactions containing J / # transactions
  
market basket optimization: 
  confidence(J1 -> J2) = # transactions containing J1, J2 / # transactions containing J1
  
market basket optimization: 
  {what is the random likelihood that person likes this item? Lift is the improvement recommendation}
  lift(J1 -> J2) = confidence J1, J2 / support J2

Process:

  1. set a min support and confidence
  2. take all the subsets in transactions having higher support than min support
  3. take all the rules of these subsets having higher confidence than min confidence
  4. sort the rules by decreasing lift, highest lift is the value you want
# Apriori

# Data Preprocessing
# install.packages('arules')
library(arules)

dataset = read.csv('./Part 5 - Association Rule Learning/Section 28 - Apriori/R/Market_Basket_Optimisation.csv', header = FALSE)

# sparse matrix
dataset = read.transactions('./Part 5 - Association Rule Learning/Section 28 - Apriori/R/Market_Basket_Optimisation.csv', sep = ',', rm.duplicates = TRUE)
distribution of transactions with duplicates:
1 
5 
summary(dataset)
transactions as itemMatrix in sparse format with
 7501 rows (elements/itemsets/transactions) and
 119 columns (items) and a density of 0.03288973 

most frequent items:
mineral water          eggs     spaghetti  french fries     chocolate       (Other) 
         1788          1348          1306          1282          1229         22405 

element (itemset/transaction) length distribution:
sizes
   1    2    3    4    5    6    7    8    9   10   11   12   13   14   15   16   18   19   20 
1754 1358 1044  816  667  493  391  324  259  139  102   67   40   22   17    4    1    2    1 

   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  1.000   2.000   3.000   3.914   5.000  20.000 

includes extended item information - examples:
itemFrequencyPlot(dataset, topN = 30)


# Training Apriori on the dataset

# items bought 3x's a day divided by total products = {3*7/7500} = 0.028 => 0.003
# items bought 4x's a day divided by total products = {4*7/7500} = 0.0037 => 0.004
# confidence value is arbitrary choice 
# 
rules = apriori(data = dataset, 
                parameter = list(support = 0.004,  
                                 confidence = 0.2)) # use small values for more rules
Apriori

Parameter specification:

Algorithmic control:

Absolute minimum support count: 30 

set item appearances ...[0 item(s)] done [0.00s].
set transactions ...[119 item(s), 7501 transaction(s)] done [0.00s].
sorting and recoding items ... [114 item(s)] done [0.00s].
creating transaction tree ... done [0.00s].
checking subsets of size 1 2 3 4 done [0.00s].
writing ... [811 rule(s)] done [0.00s].
creating S4 object  ... done [0.00s].
# Visualizing the results
# get the highest rules by lift
inspect(sort(rules, by = 'lift')[1:10])
NA

the rules show that people who bought {light cream} will buy {chicken} 29% [Confidence] of the cases with a lift value of 4.84

Eclat

this algorithm is similar as above, but it only has the support variable using sets

simple results of items commonly purchased together using the support parameter


# Data Preprocessing
# install.packages('arules')
library(arules)
dataset = read.csv('./Part 5 - Association Rule Learning/Section 29 - Eclat/R/Market_Basket_Optimisation.csv')
dataset = read.transactions('./Part 5 - Association Rule Learning/Section 29 - Eclat/R/Market_Basket_Optimisation.csv', sep = ',', rm.duplicates = TRUE)
distribution of transactions with duplicates:
1 
5 
summary(dataset)
transactions as itemMatrix in sparse format with
 7501 rows (elements/itemsets/transactions) and
 119 columns (items) and a density of 0.03288973 

most frequent items:
mineral water          eggs     spaghetti  french fries     chocolate       (Other) 
         1788          1348          1306          1282          1229         22405 

element (itemset/transaction) length distribution:
sizes
   1    2    3    4    5    6    7    8    9   10   11   12   13   14   15   16   18   19   20 
1754 1358 1044  816  667  493  391  324  259  139  102   67   40   22   17    4    1    2    1 

   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  1.000   2.000   3.000   3.914   5.000  20.000 

includes extended item information - examples:
itemFrequencyPlot(dataset, topN = 10)


# Training Eclat on the dataset
rules = eclat(data = dataset, parameter = list(support = 0.003, minlen = 2))
Eclat

parameter specification:

algorithmic control:

Absolute minimum support count: 22 

create itemset ... 
set transactions ...[119 item(s), 7501 transaction(s)] done [0.01s].
sorting and recoding items ... [115 item(s)] done [0.00s].
creating sparse bit matrix ... [115 row(s), 7501 column(s)] done [0.00s].
writing  ... [1328 set(s)] done [0.01s].
Creating S4 object  ... done [0.00s].
# Visualising the results
inspect(sort(rules, by = 'support')[1:10])
NA
NA
End of section

Principal Component Analysis (PCA)

Unsupervised algorithm for : noise filtering, visualization, feature extraction, time series predictions, gene data analysis.

Goal: identify patterns in data, detect the correlation between variables by reducing the dimensions

# Importing the dataset
dataset = read.csv('./Part 9 - Dimensionality Reduction/Section 43 - Principal Component Analysis (PCA)/R/Wine.csv')

# Splitting the dataset into the Training set and Test set
# install.packages('caTools')
library(caTools)
set.seed(123)
split = sample.split(dataset$Customer_Segment, SplitRatio = 0.8)
training_set = subset(dataset, split == TRUE)
test_set = subset(dataset, split == FALSE)

# Feature Scaling
training_set[-14] = scale(training_set[-14])
test_set[-14] = scale(test_set[-14])

# Applying PCA
# install.packages('caret')
library(caret)
Loading required package: lattice
Loading required package: ggplot2

Attaching package: ‘ggplot2’

The following object is masked from ‘package:randomForest’:

    margin

Registered S3 method overwritten by 'data.table':
  method           from
  print.data.table     
# install.packages('e1071')
library(e1071)
pca = preProcess(x = training_set[-14], method = 'pca', pcaComp = 2)
training_set = predict(pca, training_set)
training_set = training_set[c(2, 3, 1)]
test_set = predict(pca, test_set)
test_set = test_set[c(2, 3, 1)]

# Fitting SVM to the Training set
# install.packages('e1071')
library(e1071)
classifier = svm(formula = Customer_Segment ~ .,
                 data = training_set,
                 type = 'C-classification',
                 kernel = 'linear')

# Predicting the Test set results
y_pred = predict(classifier, newdata = test_set[-3])

# Making the Confusion Matrix
cm = table(test_set[, 3], y_pred)

# Visualising the Training set results
library(ElemStatLearn)
set = training_set
X1 = seq(min(set[, 1]) - 1, max(set[, 1]) + 1, by = 0.01)
X2 = seq(min(set[, 2]) - 1, max(set[, 2]) + 1, by = 0.01)
grid_set = expand.grid(X1, X2)
colnames(grid_set) = c('PC1', 'PC2')
y_grid = predict(classifier, newdata = grid_set)
plot(set[, -3],
     main = 'SVM (Training set)',
     xlab = 'PC1', ylab = 'PC2',
     xlim = range(X1), ylim = range(X2))
contour(X1, X2, matrix(as.numeric(y_grid), length(X1), length(X2)), add = TRUE)
points(grid_set, pch = '.', col = ifelse(y_grid == 2, 'deepskyblue', ifelse(y_grid == 1, 'springgreen3', 'tomato')))
points(set, pch = 21, bg = ifelse(set[, 3] == 2, 'blue3', ifelse(set[, 3] == 1, 'green4', 'red3')))


# Visualising the Test set results
library(ElemStatLearn)
set = test_set
X1 = seq(min(set[, 1]) - 1, max(set[, 1]) + 1, by = 0.01)
X2 = seq(min(set[, 2]) - 1, max(set[, 2]) + 1, by = 0.01)
grid_set = expand.grid(X1, X2)
colnames(grid_set) = c('PC1', 'PC2')
y_grid = predict(classifier, newdata = grid_set)
plot(set[, -3], main = 'SVM (Test set)',
     xlab = 'PC1', ylab = 'PC2',
     xlim = range(X1), ylim = range(X2))
contour(X1, X2, matrix(as.numeric(y_grid), length(X1), length(X2)), add = TRUE)
points(grid_set, pch = '.', col = ifelse(y_grid == 2, 'deepskyblue', ifelse(y_grid == 1, 'springgreen3', 'tomato')))
points(set, pch = 21, bg = ifelse(set[, 3] == 2, 'blue3', ifelse(set[, 3] == 1, 'green4', 'red3')))     

NA
NA
LS0tCnRpdGxlOiAiKipMZWFybmluZyBSIE5vdGVib29rKioiCmF1dGhvcjogIlphbmUgRGF4IgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6IAogICAgdG9jOiB5ZXMKLS0tCjxzdHlsZT4KYm9keXtmb250LXNpemU6IDE2cHg7fQpoMSB7Y29sb3I6ICM5OTAwY2M7fQo8L3N0eWxlPgoKIyMjIE1hY2hpbmUgTGVhcm5pbmcgaW4gUiBieSAqKlVkZW15KioKCiMgc3RlcCAxIC0gaW1wb3J0IGRhdGFzZXQgCmBgZGF0YXNldCA9IHJlYWQuY3N2KCdEYXRhLmNzdicpYGAKCiMgc3RlcCAyIC0gbWlzc2luZyBkYXRhCk9mdGVuIGRhdGFzZXRzIGhhdmUgbWlzc2luZyBkYXRhLCBhbmQgdGhlIGNvbW1vbiBwcmFjdGljZSBpcyB0byByZW1vdmUgbWlzc2luZyBkYXRhIHJvd3MgYnV0IGl0IGhhcyBhIG5lZ2F0aXZlIGltcGFjdCBvbiBvYnNlcnZhdGlvbnMuIApCZXN0IHByYWN0aWNlIGlzIHRvIHRha2UgdGhlIG1lYW5zIG9mIGVhY2ggY29sdW1ucyB3aXRoIG1pc3NpbmcgZGF0YS4gCgotIHRvIHNlbGVjdCB0aGUgZGF0YWZyYW1lIGNvbHVtbnMsIHVzZSBgYCRgYCwgaWYtZWxzZSBmdW5jdGlvbgotIGBgaXMubmFgYCBpcyBhIGZ1bmN0aW9uIHRoYXQgY2hlY2tzIGZvciBOYU5zIGluIGNvbHVtbnMuCi0gdG8gZ2V0IHRoZSBhdmVyYWdlIHVzZSBgYGF2ZSgpYGAKLSBjcmVhdGUgYSBmdW5jdGlvbiwgaGVyZSBpdCdzIG5hbWVkIEZVTiwgdG8gZ2V0IHRoZSBtZWFuIG9mIGNvbHVtbiBBZ2UgYW5kIHJldHVybiBUcnVlCi0gZ2V0IHRoZSBhdmVyYWdlIG9mIGNvbHVtbiBTYWxhcnkgYW5kIGdldCB0aGUgZnVuY3Rpb24gdG8gY2hlY2sgZm9yIG1pc3NpbmcgdmFsdWVzIGFuZCByZXR1cm4gVHJ1ZQoKYGBge3J9CmRhdGFzZXQgPSByZWFkLmNzdignLi9QYXJ0IDEgRGF0YSBQcmVwcm9jZXNzaW5nL1IvRGF0YS5jc3YnKQoKZGF0YXNldCRBZ2UgPSBpZmVsc2UoaXMubmEoZGF0YXNldCRBZ2UpLAogICAgICAgICAgICAgICAgICAgICBhdmUoZGF0YXNldCRBZ2UsIAogICAgICAgICAgICAgICAgICAgICAgICAgRlVOID0gZnVuY3Rpb24oeCkgbWVhbih4LCBuYS5ybSA9IFRSVUUpKSwKICAgICAgICAgICAgICAgICAgICAgZGF0YXNldCRBZ2UpICMgcmV0dXJuIGNvbHVtbgogICAgICAgICAgICAgICAgCmRhdGFzZXQkU2FsYXJ5ID0gaWZlbHNlKGlzLm5hKGRhdGFzZXQkU2FsYXJ5KSwKICAgICAgICAgICAgICAgICAgICAgICAgYXZlKGRhdGFzZXQkU2FsYXJ5LCBGVU4gPSBmdW5jdGlvbih4KSBtZWFuKHgsIG5hLnJtID0gVFJVRSkpLAogICAgICAgICAgICAgICAgICAgICAgICBkYXRhc2V0JFNhbGFyeSkKZGF0YXNldCAgICAgICAgICAgICAgICAgICAgICAgCmBgYCAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgCkNhdGVnb3JpY2FsIGRhdGEKTmVlZCB0byBlbmNvZGUgY2F0ZWdvcmljYWwgZGF0YSBpbnRvIG51bWVyaWNhbCBkYXRhIHR5cGUKCi0gZm9yIHRoaXMgZGF0YXNldCBDb3VudHJ5IGlzIGNhdGVnb3JpY2FsLCBuZWVkIHRvIGVuY29kZQoKIyBTdGVwIDMgLSBFbmNvZGluZyBjYXRlZ29yaWNhbCBkYXRhCk5lZWQgdGhlIGBgZmFjdG9yKClgYCBmdW5jdGlvbiB3aGljaCB0YWtlcyBjYXRlZ29yaWNhbCB2YXJpYWJsZXMgYW5kIHN0b3JlcyBpdCBpbiBsZXZlbHMgKGhlbHBzIHJlZHVjZSByZWR1bmRhbmN5IGFuZCBDUFUgbWVtb3J5KS4gUmVxdWlyZXMgYGBjKClgYCB2ZWN0b3JzLiAKCkV4YW1wbGU6CgotIGBgY29mZmVlLnNpemUgPSBjKCJTbWFsbCIsIk1lZGl1bSIsIkxhcmdlIiwiWExhcmdlIilgYAotIGBgY29mZmVlLnNpemUuZmFjdG9yID0gZmFjdG9yKGNvZmZlZS5zaXplKWBgCi0gYGBjb2ZmZWUuc2l6ZS5mYWN0b3IgPSBmYWN0b3IobGV2ZWxzPSBjb2ZmZWUuc2l6ZSwgb3JkZXJlZCA9IFQsIGxhYmVscyA9IGMoMSwyLDMsNCkpYGAKLSBgYGNvZmZlZS5zaXplLmZhY3RvcmBgCgoKYGBge3IgZmFjdG9yIGNhdGVnb3JpY2FsIGRhdGF9CmRhdGFzZXQgPSByZWFkLmNzdignLi9QYXJ0IDEgRGF0YSBQcmVwcm9jZXNzaW5nL1IvRGF0YS5jc3YnKQoKIyB3YW50IHRvIGNvbnZlcnQgY291bnRyaWVzIHRvIG51bWJlciByZXByZXNlbnRhdGlvbiAKZGF0YXNldCRDb3VudHJ5ID0gZmFjdG9yKGRhdGFzZXQkQ291bnRyeSwKICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGMoJ0ZyYW5jZScsICdTcGFpbicsICdHZXJtYW55JyksCiAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKDEsIDIsIDMpKQoKZGF0YXNldCRQdXJjaGFzZWQgPSBmYWN0b3IoZGF0YXNldCRQdXJjaGFzZWQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGMoJ05vJywgJ1llcycpLAogICAgICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKDAsIDEpKQpkYXRhc2V0ICAgICAgICAgICAgICAgICAgIApgYGAKCiMgc3RlcCA0IC0gVHJhaW4vVGVzdCBzcGxpdCBvZiBkYXRhCk5lZWQgdG8gaW1wb3J0IGEgbGlicmFyeSB0aGF0IG1ha2VzIFRyYWluIFRlc3Qgc3BsaXQgZWFzeS4KCmBgYHtyIHRyYWluLXRlc3Qtc3BsaXR9CmRhdGFzZXQgPSByZWFkLmNzdignLi9QYXJ0IDEgRGF0YSBQcmVwcm9jZXNzaW5nL1IvRGF0YS5jc3YnKQoKIyBpbnN0YWxsLnBhY2thZ2VzKCdjYVRvb2xzJykKCiMgbG9hZCBsaWJyYXJ5CmxpYnJhcnkoY2FUb29scykKCnNldC5zZWVkKDEyMykKCiMtLS0tLS0gVHJhaW4gLyBUZXN0IHNwbGl0CiMgICAgICAgICAgICAgICAgICAgIGRhdGFzZXQkRGVwZW5kZW50VmFyaWFibGUKc3BsaXQgPSBzYW1wbGUuc3BsaXQoZGF0YXNldCRQdXJjaGFzZWQsIAogICAgICAgICAgICAgICAgICAgICBTcGxpdFJhdGlvID0gMC44KSAjIHJldHVybnMgVC9GLCAKCiMgVHJ1ZSA9PiBUcmFpbmluZyBzZXQsIEZhbHNlID0+IFRlc3Qgc2V0CnRyYWluaW5nX3NldCA9IHN1YnNldChkYXRhc2V0LCBzcGxpdCA9PSBUUlVFKQp0ZXN0X3NldCA9IHN1YnNldChkYXRhc2V0LCBzcGxpdCA9PSBGQUxTRSkKCmRhdGFzZXQKYGBgICAgICAgICAgICAgICAgICAgICAgICAKCgojIHN0ZXAgNSAtIEZlYXR1cmUgU2NhbGluZwpFdWNsaWRlYW4gRGlzdGFuY2UgYmV0d2VlbiBjb29yZGluYXRlcyA9PSB4IGFuZCB5IHZhbHVlcywgd2hlbiBvbmUgdmFyaWFibGVzIGhhcyBtb3JlIHZhcmlhbmNlIGl0IGJlY29tZXMgZG9taW5hbnQgaW4gZGF0YSB3aGVuIHRoZXkgYXJlIHNxdWFyZWQuIFRoaXMgaXMgd2h5IHZhcmlhYmxlcyBuZWVkIHRvIGJlIGluIHRoZSBzYW1lIHNjYWxlIHZhbHVlIHJhbmdlLgoKVHdvIEZlYXR1cmUgU2NhbGluZyBtZXRob2RzOgoKLSAqKlN0YW5kYXJkaXphdGlvbioqIDogJFhfe3N0YW5kfSQgPSAkeCQgLSAgbWVhbigkeCQpICRcZGl2JCBzdGQoJHgkKQotICoqTm9ybWFsaXphdGlvbioqIDogJFhfe25vcm19JCA9ICR4JCAtICBtaW4oJHgkKSAkXGRpdiQgbWF4KCR4JCkgLSBtaW4oJHgkKSAgICAgICAgICAgICAgICAgICAKCk5vdCBhbGwgUiBsaWJyYXJpZXMgcmVxdWlyZSBmZWF0dXJlIHNjYWxpbmcgdG8gYmUgZG9uZSBiZWZvcmVoYW5kLCBzb21lIGRlYWwgd2l0aCBpdCBmb3IgeW91LgoKYGBge3IgRmVhdHVyZSBTY2FsaW5nfSAgICAKZGF0YXNldCA9IHJlYWQuY3N2KCcuL1BhcnQgMSBEYXRhIFByZXByb2Nlc3NpbmcvUi9EYXRhLmNzdicpCiMtLS0tLS0gRmVhdHVyZSBTY2FsaW5nCiMgd2F0Y2ggb3V0IGZvciBlcnJvcnMgZnJvbSBjYXRlZ29yaWNhbCBmYWN0b3JzLCBmYWN0b3JzIGFyZSBub3QgbnVtZXJpY2FsCiMgbmVlZCB0byBleGNsdWRlIGNhdGVnb3JpY2FsIGNvbHVtbnMKIyBncmFiIGp1c3QgdGhlIG51bWVyaWNhbCwgdXNlIHNsaWNpbmcgZm9yIGNvbHVtbnMgMiBhbmQgMyBBZ2UgYW5kIFNhbGFyeQojIHNsaWNlIG5vdGF0aW9uIFssIDI6M10KCnRyYWluaW5nX3NldFssIDI6M10gPSBzY2FsZSh0cmFpbmluZ19zZXRbLCAyOjNdKQp0ZXN0X3NldFssIDI6M10gPSBzY2FsZSh0ZXN0X3NldFssIDI6M10pICAgCgoKCmBgYCAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgCkVuZCBvZiBEYXRhIFByZXByb2Nlc3NpbmcKCjxocj4KCiMgU2ltcGxlICoqTGluZWFyIFJlZ3Jlc3Npb24qKgooZGVwZW5kZW50IHZhcikgPSAoY29uc3RhbnQpICsgKGNvZWZmaWNpZW50LCB1bml0IGNoYW5nZXMpICogKEluZGVwLiB2YXIpCgpUaGUgZm9ybXVsYTogKip5KiogPSAkYl97MH0kICsgJGJfezF9JCAgKiAkeF97MX0kIAoKRXhhbXBsZToKCi0gRXhwZXJpZW5jZSAoeCkKLSBTYWxhcnkgKHkpCi0gUXVlcnk6IGRvZXMgc2FsYXJ5IGZhY3RvciBpbiB3aXRoIGV4cGVyaWVuY2UgPwotIFNhbGFyeSA9IChjb25zdGFudCkgKyAoY29lZmZpY2llbnQpICogRXhwZXJpZW5jZSAgICAKLSBwbG90IGl0LCB0aGUgY29uc3RhbnQgb2YgMCBzYXlzIHRoYXQgcGVyc29uIHdpdGggMCBleHBlcmllbmNlIHdpbGwgaGF2ZSBzYWxhcnkgb2YgJDMwaywgdGhlIGNvZWZmaWNpZW50IHNob3dzIHRoYXQgMSB5ZWFyIG9mIGV4cGVyaWVuY2UgaW5jcmVhc2VzIHNhbGFyeSBieSAkMTBrCgpGaW5kIHRoZSBsaW5lIG9mIGJlc3QgZml0IGlzICoqT3JkaW5hcnkgTGVhc3QgU3F1YXJlcyoqCgotIG9uIHRoZSBwbG90LCB0aGUgZG90IGFib3ZlIHRoZSBsaW5lYXIgcmVncmVzc2lvbiBsaW5lIGlzIHdoZXJlIHBlcnNvbiBpcyB3aXRoIHNhbGFyeSBhbmQgZXhwZXJpZW5jZSAoJHlfe2l9JCkgYW5kIHRoZSBsaW5lIGlzIHdoZXJlIHRoZSBtb2RlbCBzYXlzIHdoZXJlIHRoZSBwZXJzb24gc2hvdWxkIGJlIGluIHJlZ2FyZHMgdG8gc2FsYXJ5ICgkeV57LX1fe2l9JCkgLSB0aGUgbW9kZWwgZXZhbHVhdGlvbiB2YWx1ZQotIHN1bSggJHlfe2l9JCAtICR5XnstfV97aX0kKV4yLCB0aGVuIGZpbmQgdGhlIG1pbmltdW0gdmFsdWUgCgpGaXR0aW5nIFNpbXBsZSBMaW5lYXIgUmVncmVzc2lvbiB0byB0aGUgVHJhaW5pbmcgc2V0CgotIG11c3QgdW5kZXJzdGFuZCB3aGljaCBpcyB0aGUgZGVwZW5kZW50IHZhciBhbmQgd2hpY2ggaXMgdGhlIGluZGVwLiB2YXJpYWJsZQotIGluZGVwLiB2YXIgPSBleHBlcmllbmNlIGluIHllYXJzCi0gZGVwLiB2YXIgPSBzYWxhcnkgaW4gJAoKYGBge3IgTGluZWFyIFJlZ3Jlc3Npb259CiMgc3RlcCAxCmRhdGFzZXQgPSByZWFkLmNzdignLi9QYXJ0IDIgLSBSZWdyZXNzaW9uL1NlY3Rpb24gNCAtIFNpbXBsZSBMaW5lYXIgUmVncmVzc2lvbi9SL1NhbGFyeV9EYXRhLmNzdicpCgojIHN0ZXAgMywgNApzcGxpdCA9IHNhbXBsZS5zcGxpdChkYXRhc2V0JFNhbGFyeSwgU3BsaXRSYXRpbyA9IDIvMykKdHJhaW5pbmdfc2V0ID0gc3Vic2V0KGRhdGFzZXQsIHNwbGl0ID09IFRSVUUpCnRlc3Rfc2V0ID0gc3Vic2V0KGRhdGFzZXQsIHNwbGl0ID09IEZBTFNFKQoKIyBubyBmZWF0dXJlIHNjYWxpbmcgaXMgcmVxdWlyZWQgd2l0aCB0aGlzIGNhVG9vbHMgbGlicmFyeQoKIy0tLS0tLS0tIExpbmVhciBSZWdyZXNzaW9uIGxtKCkgCiMgdGhlICd+JyBtZWFucyAiYXMgYSBmdW5jdGlvbiBvZiIKIyB+IHNlcGFyYXRlcyBkZXBlbmRlbnQgdmFyaWFibGUgZnJvbSB0aGUgaW5kZXAuIHZhcgojIChkZXAuIHZhcikgfiAoaW5kZXAuIHZhcikKIyBob3cgaXQgd2lsbCBiZSBwbG90dGVkOiAoeS1heGlzIHZhcikgfiAoeC1heGlzIHZhcikKcmVncmVzc29yID0gbG0oZm9ybXVsYSA9IFNhbGFyeSB+IFllYXJzRXhwZXJpZW5jZSwKICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluaW5nX3NldCkKCiMtLS0tIFByZWRpY3RpbmcgdGhlIFRlc3Qgc2V0IHJlc3VsdHMKIyBwcmVkaWN0IHNhbGFyaWVzIGJhc2VkIG9uIFllYXJzIEV4cGVyaWVuY2UKeV9wcmVkID0gcHJlZGljdChyZWdyZXNzb3IsIG5ld2RhdGEgPSB0ZXN0X3NldCkKCnN1bW1hcnkocmVncmVzc29yKQpgYGAKICAgICAgICAgICAgICAgICAgICAgICAgCnRoZSBgYHN1bW1hcnkocmVncmVzc29yKWBgIHNob3dzIHRoYXQgaW4gdGhlIGNvZWZmaWNpZW50cyBzZWN0aW9uIHRoYXQgdGhlIGBgWWVhcnNFeHBlcmllbmNlYGAgaGFzIDMgc3RhcnMsIG1lYW5pbmcgaXQgaXMgaGlnaGx5IHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQuIFRoZSBsb3dlciB0aGUgcC12YWx1ZSBtZWFucyBpdCBpcyBtb3JlIHNpZ25pZmljYW50LCB3aGVuIHAgPCAwLjA1ID09IGluZGVwLiB2YXIgKGBgWWVhcnNFeHBlcmllbmNlYGApIGlzIHN0YXRpc3RpY2FsbHkgc2lnbmlmaWNhbnQuCgpMb29rIGF0IHdoYXQgdGhlIHNhbGFyeSBpcyBmb3IgZmlyc3Qgcm93IGl0ZW0gYW5kIGNvbXBhcmUgd2l0aCBwcmVkaWN0ZWQgdmFsdWU6IAoKLSBTYWxhcnlfRGF0YSA6IDEuMyBZZWFyc0V4cGVyaWVuY2UsIFNhbGFyeSA0NjIwNQotIExpbmVhciBNb2RlbCBwcmVkaWN0aW9uOiBTYWxhcnkgMzc3NjYKCgoKIyMgVmlzdWFsaXNpbmcgdGhlIFRyYWluaW5nIHNldCByZXN1bHRzCgpgYGB7cn0KIyBzaGlmdCArIENtZCArIGMgICBpcyBzaG9ydGN1dCBmb3IgY29tbWVudCBsaW5lCmRhdGFzZXQgPSByZWFkLmNzdignLi9QYXJ0IDIgLSBSZWdyZXNzaW9uL1NlY3Rpb24gNCAtIFNpbXBsZSBMaW5lYXIgUmVncmVzc2lvbi9SL1NhbGFyeV9EYXRhLmNzdicpCgpsaWJyYXJ5KGdncGxvdDIpCmdncGxvdCgpICsKICBnZW9tX3BvaW50KGFlcyh4ID0gdHJhaW5pbmdfc2V0JFllYXJzRXhwZXJpZW5jZSwgIyBtYWtlIHN1cmUgeW91IHNlbGVjdCBjb3JyZWN0IGRhdGFzZXQhCiAgICAgICAgICAgICAgICAgeSA9IHRyYWluaW5nX3NldCRTYWxhcnkpLAogICAgICAgICAgICAgY29sb3VyID0gJ3JlZCcpICsgCiAgZ2VvbV9saW5lKGFlcyh4ID0gdHJhaW5pbmdfc2V0JFllYXJzRXhwZXJpZW5jZSwgCiAgICAgICAgICAgICAgICB5ID0gcHJlZGljdChyZWdyZXNzb3IsIG5ld2RhdGEgPSB0cmFpbmluZ19zZXQpKSwKICAgICAgICAgICAgY29sb3VyID0gJ2JsdWUnKSArCiAgZ2d0aXRsZSgnU2FsYXJ5IHZzIEV4cGVyaWVuY2UgKFRyYWluaW5nIHNldCknKSArCiAgeGxhYignWWVhcnMgb2YgZXhwZXJpZW5jZScpICsKICB5bGFiKCdTYWxhcnknKQoKIyBSZWQgZG90cyBhcmUgdGhlIFJlYWwgZGF0YQojIGJsdWUgbGluZSBpcyB0aGUgbW9kZWwgcHJlZGljdGlvbgpgYGAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAKIyMgVmlzdWFsaXNpbmcgdGhlIFRlc3Qgc2V0IHJlc3VsdHMKYGBge3J9CmRhdGFzZXQgPSByZWFkLmNzdignLi9QYXJ0IDIgLSBSZWdyZXNzaW9uL1NlY3Rpb24gNCAtIFNpbXBsZSBMaW5lYXIgUmVncmVzc2lvbi9SL1NhbGFyeV9EYXRhLmNzdicpCgpsaWJyYXJ5KGdncGxvdDIpCmdncGxvdCgpICsKICBnZW9tX3BvaW50KGFlcyh4ID0gdGVzdF9zZXQkWWVhcnNFeHBlcmllbmNlLCAKICAgICAgICAgICAgICAgICB5ID0gdGVzdF9zZXQkU2FsYXJ5KSwKICAgICAgICAgICAgIGNvbG91ciA9ICdyZWQnKSArCiAgZ2VvbV9saW5lKGFlcyh4ID0gdHJhaW5pbmdfc2V0JFllYXJzRXhwZXJpZW5jZSwgCiAgICAgICAgICAgICAgICB5ID0gcHJlZGljdChyZWdyZXNzb3IsIG5ld2RhdGEgPSB0cmFpbmluZ19zZXQpKSwKICAgICAgICAgICAgY29sb3VyID0gJ2JsdWUnKSArCiAgZ2d0aXRsZSgnU2FsYXJ5IHZzIEV4cGVyaWVuY2UgKFRlc3Qgc2V0KScpICsKICB4bGFiKCdZZWFycyBvZiBleHBlcmllbmNlJykgKwogIHlsYWIoJ1NhbGFyeScpICAgICAgICAgICAKCmBgYCAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAKCiMgTXVsdGlwbGUgTGluZWFyIFJlZ3Jlc3Npb24gSW50dWl0aW9uClRoZSBmb3JtdWxhIGlzOgoKLSBkZXBlbmRlbnQgdmFyICh5KSwgY29uc3RhbnQgKGIpLCBpbmRlcC4gdmFycyAoYjEsIGIyLCAuLi4pCi0gKip5KiogPSAkYl97MH0kICsgJGJfezF9ICogeF97MX0kICsgJGJfezJ9ICogeF97Mn0kICsgLi4uCgpMaW5lYXIgUmVncmVzc2lvbiBBc3N1bXB0aW9uczoKCi0gbGluZWFyaXR5Ci0gaG9tb3NjZWRhc3RpY2l0eQotIG11bHRpdmFyaWF0ZSBub3JtYWxpdHkKLSBpbmRlcGVuZGVuY2Ugb2YgZXJyb3JzCi0gbGFjayBvZiBtdWx0aWNvbGxpbmVhcml0eQoKVXNpbmcgdGhlIGRhdGFzZXQgYGA1MF9TdGFydHVwc2BgCgotIGZvciB0aGUgY29sdW1uIFN0YXRlLCBpdCBoYXMgY2F0ZWdvcmljYWwgZGF0YSwgc28gbmVlZCB0byBnZXQgZHVtbXkgdmFyaWFibGVzIHRvIHJlcGxhY2Ugc3RyaW5ncyBmb3IgaW50ZWdlciB2YWx1ZXMuIAotIE1ha2UgbmV3IGNvbHVtbnMgZm9yIGVhY2ggY2F0ZWdvcnksIGZvciBOZXcgWW9yayBjb2x1bW4sIGV2ZXJ5IE5ldyBZb3JrIHZhbHVlIGdldHMgMS4gCi0gKHByb2ZpdCkgPSBjb25zdGFudCArIChSJkQgU3BlbmQpICogKFImRCBTcGVuZCB2YWx1ZXMpICsgKEFkbWluKSAqIChBZG1pbiB2YWx1ZXMpICsgKE1hcmtldGluZykgKiAoTWFya2V0aW5nIHZhbHVlcykgKyAoZHVtbXlfdmFyKQotICoqV0FSTklORyoqOiBhbHdheXMgb21pdCAxIGR1bW15IHZhcmlhYmxlIHJlZ2FyZGxlc3MgbnVtYmVyIG9mIGR1bW15IHZhcmlhYmxlcy4gRG8gbm90IGluY2x1ZGUgbW9yZSB0aGFuIDEgZHVtbXkgdmFyaWFibGUKCiMjIFAtdmFsdWUKSXMgdGhlIHJlc3VsdCBzdGF0aXN0aWNhbGx5IHNpZ25pZmljYW50PyAKCi0gJEhfezB9JCA9IE51bGwgaHlwb3RoZXNpcyAoZGVmYXVsdCkKLSAkSF97MX0kID0gQWx0IGh5cG90aGVzaXMKCkNvaW4gZmxpcDogCgp8W0ggb3IgVF0gfCAjIGZsaXBzIHwgUHJvYmFiaWxpdHkgfAp8LS0tLS0tLS0tfC0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tfAp8IFQgICAgICAgfCAxICAgICAgIHwgMC41MCB8CnwgVCAgICAgICB8IDIgICAgICAgfCAwLjI1IHwKfCBUICAgICAgIHwgMyAgICAgICB8IDAuMTIgfAp8IFQgICAgICAgfCA0ICAgICAgIHwgMC4wNiAgICoqcCA8IDAuMDUgPSBTaWduaWZpY2FudCoqfAp8IFQgICAgICAgfCA1ICAgICAgIHwgMC4wMyB8CnwgVCAgICAgICB8IDYgICAgICAgfCAwLjAxIHwKCiAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAKIyBNdWx0aXBsZSBMaW5lYXIgUmVncmVzc2lvbiBNb2RlbCBNZXRob2RzCgotIHN0ZXAgMDogc2VsZWN0IHdoYXQgY29sdW1ucyB0byBpbmNsdWRlIHtnYXJiYWdlIGluID0gZ2FyYmFnZSBvdXR9CiAgYW5kIHlvdSB3aWxsIG5lZWQgdG8gYmUgYWJsZSB0byBleHBsYWluIHlvdXIgbW9kZWwsIHNvIGtlZXAgd2hhdCBpcyBlc3NlbnRpYWwKLSAqKjUgTWV0aG9kcyBvZiBidWlsZGluZyBtb2RlbHMqKiA6CgogIDEuIGFsbC1pbiA9IHB1dHRpbmcgaW4gYWxsIHZhcmlhYmxlcyB7ZG9uJ3QgZG8gdW5sZXNzIHlvdSBuZWVkIHRvfQogIDIuICoqYmFja3dhcmQgZWxpbWluYXRpb24qKiA6CiAgCiAgICAtIDIuMS4gc2VsZWN0IGEgc2lnbmlmaWNhbmNlIGxldmVsICgkXGFscGhhJCA9IDAuMDUpCiAgICAtIDIuMi4gZml0IHRoZSBmdWxsIG1vZGVsIHdpdGggYWxsIHBvc3NpYmxlIHByZWRpY3RvcnMKICAgIC0gMi4zLiBjb25zaWRlciB0aGUgcHJlZGljdG9yIHdpdGggaGlnaGVzdCBwLXZhbHVlLCBpZiBwID4gJFxhbHBoYSQgdGhlbiBnbyB0byBzdGVwIDIuNCwgZWxzZTogYnJlYWssIG1vZGVsIGlzIHJlYWR5CiAgICAtIDIuNCByZW1vdmUgdGhlIHByZWRpY3RvcgogICAgLSAyLjUgZml0IHRoZSBtb2RlbCB3aXRob3V0IHRoaXMgdmFyaWFibGUsIGdvIHRvIHN0ZXAgMi4zCiAgICAKICAzLiAqKmZvcndhcmQgc2VsZWN0aW9uKiogOgogIAogICAgLSAzLjEgc2VsZWN0IGEgc2lnbmlmaWNhbmNlIGxldmVsICgkXGFscGhhJCA9IDAuMDUpCiAgICAtIDMuMiBmaXQgYWxsIHNpbXBsZSByZWdyZXNzaW9uIG1vZGVscyB5IH4gJHhfe259JCwgc2VsZWN0IHRoZSBvbmUgd2l0aCBsb3dlc3QgcC12YWx1ZQogICAgLSAzLjMga2VlcCB0aGlzIHZhcmlhYmxlIGFuZCBmaXQgYWxsIHBvc3NpYmxlIG1vZGVscyB3aXRoIG9uZSBleHRyYSBwcmVkaWN0b3IgYXNzZWQgdG8gdGhlIG9uZShzKSB5b3UgYWxyZWFkeSBoYXZlCiAgICAtIDMuNCBjb25zaWRlciB0aGUgcHJlZGljdG9yIHdpdGggbG93ZXN0IHAtdmFsdWUsIGlmIHAgPCAkXGFscGhhJCBnbyB0byBzdGVwIDMuMywgZWxzZTogYnJlYWssIG1vZGVsIGlzIHJlYWR5IChrZWVwIHByZXZpb3VzIG1vZGVsKQogICAgCiAgNC4gKipiaWRpcmVjdGlvbmFsIGVsaW1pbmF0aW9uIChzdGVwd2lzZSByZWdyZXNzaW9uKSoqOgogIAogICAgLSA0LjEgc2VsZWN0IHNpZ25pZmljYW5jZSBsZXZlbCB0byBlbnRlciBhbmQgc3RheSBpbiB0aGUgbW9kZWwgJFxhbHBoYSQgPSAwLjA1CiAgICAtIDQuMiAgZG8gZm9yd2FyZCBzZWxlY3Rpb24sIG5ldyB2YXJpYWJsZXMgbXVzdCBiZSBwIDwgJFxhbHBoYSQgdG8gZW50ZXIKICAgIC0gNC4zIGRvIGFsbCBzdGVwcyBvZiBiYWNrd2FyZCBlbGltaW5hdGlvbiwgb2xkIHZhcmlhYmxlcyBtdXN0IGJlIHAgPCAkXGFscGhhJCB0byBzdGF5CiAgICAtIDQuNCBubyBuZXcgdmFyaWFibGVzIGNhbiBlbnRlciBhbmQgbm8gb2xkIHZhcmlhYmxlcyBjYW4gZXhpdCwgbW9kZWwgaXMgcmVhZHkKICAKICA1LiAqKnNjb3JlIGNvbXBhcmlzb24qKiA6CiAgCiAgICAtIDUuMSBzZWxlY3QgYSBjcml0ZXJpb24gb2YgZ29vZG5lc3Mgb2YgZml0IChBa2Fpa2UgY3JpdGVyaW9uKQogICAgLSA1LjIgY29uc3RydWN0IGFsbCBwb3NzaWJsZSByZWdyZXNzaW9uIG1vZGVscyAKICAgIC0gNS4zIHNlbGVjdCBvbmUgb2YgdGhlIGJlc3QgY3JpdGVyaW9uLCB5b3VyIG1vZGVsIGlzIHJlYWR5IAogICAgCiAgICAqRXhhbXBsZTogMTAgY29sdW1ucyBtZWFucyA9PSAxMDIzIG1vZGVscyoKCiAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAKIyAqKk11bHRpcGxlKiogTGluZWFyIFJlZ3Jlc3Npb24gICAgICAgICAgICAgICAgICAgICAKCmBgYHtyIE11bHRpcGxlIExpbmVhciBSZWdyZXNzaW9ufSAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgCiMgSW1wb3J0aW5nIHRoZSBkYXRhc2V0CmRhdGFzZXQgPSByZWFkLmNzdignLi9QYXJ0IDIgLSBSZWdyZXNzaW9uL1NlY3Rpb24gNSAtIE11bHRpcGxlIExpbmVhciBSZWdyZXNzaW9uL1IvNTBfU3RhcnR1cHMuY3N2JykKCiMgRW5jb2RpbmcgY2F0ZWdvcmljYWwgZGF0YQpkYXRhc2V0JFN0YXRlID0gZmFjdG9yKGRhdGFzZXQkU3RhdGUsCiAgICAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYygnTmV3IFlvcmsnLCAnQ2FsaWZvcm5pYScsICdGbG9yaWRhJyksCiAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygxLCAyLCAzKSkgIyBlbmNvZGluZyAKCiMgU3BsaXR0aW5nIHRoZSBkYXRhc2V0IGludG8gdGhlIFRyYWluaW5nIHNldCBhbmQgVGVzdCBzZXQKIyBpbnN0YWxsLnBhY2thZ2VzKCdjYVRvb2xzJykKbGlicmFyeShjYVRvb2xzKQoKc2V0LnNlZWQoMTIzKQoKc3BsaXQgPSBzYW1wbGUuc3BsaXQoZGF0YXNldCRQcm9maXQsIFNwbGl0UmF0aW8gPSAwLjgpCgojIC0tLS0gVHJhaW4vVGVzdCBzcGxpdAp0cmFpbmluZ19zZXQgPSBzdWJzZXQoZGF0YXNldCwgc3BsaXQgPT0gVFJVRSkKdGVzdF9zZXQgPSBzdWJzZXQoZGF0YXNldCwgc3BsaXQgPT0gRkFMU0UpICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAKIy0tLS0tLSBjYVRvb2xzIGxpYnJhcnkgZG9lcyBub3QgbmVlZCBmZWF0dXJlIHNjYWxpbmcKIyBGZWF0dXJlIFNjYWxpbmcKIyB0cmFpbmluZ19zZXQgPSBzY2FsZSh0cmFpbmluZ19zZXQpCiMgdGVzdF9zZXQgPSBzY2FsZSh0ZXN0X3NldCkKIyAtLS0tLS0KCiMtLS0tIEZpdHRpbmcgTXVsdGlwbGUgTGluZWFyIFJlZ3Jlc3Npb24gdG8gdGhlIFRyYWluaW5nIHNldAoKIyAocHJvZml0KSB+IChjb2x1bW4xICsgY29sdW1uMiArIGNvbHVtbjMgKyBjb2x1bW40KQojIDw8IHNob3J0Y3V0ID4+IGlzICcuJwojIChwcm9maXQpIH4gKGFsbCBvdGhlciBjb2x1bW5zKQojIChwcm9maXQpIH4gLgpyZWdyZXNzb3IgPSBsbShmb3JtdWxhID0gUHJvZml0IH4gLiwKICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluaW5nX3NldCkKCgojLS0tLSBQcmVkaWN0aW5nIHRoZSBUZXN0IHNldCByZXN1bHRzCnlfcHJlZCA9IHByZWRpY3QocmVncmVzc29yLCAKICAgICAgICAgICAgICAgICBuZXdkYXRhID0gdGVzdF9zZXQpICAgICAgICAgICAgICAgICAgIAoKIyBzZWUgdGhlIHN1bW1hcnkgc3RhdGlzdGljcyAKc3VtbWFyeShyZWdyZXNzb3IpCgojIHNlZSB0aGUgcHJlZGljdGlvbnMKeV9wcmVkCmBgYCAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgIApUaGUgc3VtbWFyeSBzdGF0aXN0aWNzIGhhdmUgc3RhdGUyIGFuZCBzdGF0ZTMgd2hpY2ggYXJlIGR1bW15IHZhcmlhYmxlcyBSIGNyZWF0ZWQgYmFzZWQgb24gdGhlIGZhY3RvcnMgZW5jb2RlZCBhYm92ZS4gVGhlIHAtdmFsdWUgc2hvdyB0aGUgc2lnbmlmaWNhbmNlIGxldmVsIHZhbHVlIG9mIGluZGVwZW5kZW50IHZhcmlhYmxlcy4KTG93ZXIgdGhlIHAtdmFsdWUgdGhlIG1vcmUgc2lnbmlmaWNhbnQuIAoKRm9yIHRoZSBkYXRhIGFib3ZlLCBSJkQgU3BlbmRpbmcgaGFzIGEgc3Ryb25nIHN0YXRpc3RpY2FsIHNpZ25pZmljYW50ICgzIHN0YXJzICoqKikgZWZmZWN0IG9uIHByb2ZpdHMgKGRlcGVuZGVudCB2YXJpYWJsZSkuIFRha2Vhd2F5OiBMb29rIGF0IGNvbXBhbmllcyB0aGF0IHNwZW5kIG1vbmV5IG9uIFImRCBhcyBpdCBwbGF5cyBiaWdnZXN0IHJvbGUgb24gcHJvZml0cy4gCgpUaGUgcmVhbCBkYXRhc2V0IHByb2ZpdHMgdnMgcHJlZGljdGVkIHByb2ZpdHM6IDE4MjkwMS45OSAocm93IDQpIHZzIHByZWRpY3RlZCAxNzM5ODEuMDkgKHJvdyA0KQogICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAKIyMgQmFja3dhcmQgRWxpbWluYXRpb24gbW9kZWwKcmV1c2UgdGhlIGNvZGUgYWJvdmUKCmBgYHtyIEJhY2t3YXJkIEVsaW19CgpkYXRhc2V0ID0gcmVhZC5jc3YoJy4vUGFydCAyIC0gUmVncmVzc2lvbi9TZWN0aW9uIDUgLSBNdWx0aXBsZSBMaW5lYXIgUmVncmVzc2lvbi9SLzUwX1N0YXJ0dXBzLmNzdicpCgoKIyBFbmNvZGluZyBjYXRlZ29yaWNhbCBkYXRhCmRhdGFzZXQkU3RhdGUgPSBmYWN0b3IoZGF0YXNldCRTdGF0ZSwKICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSBjKCdOZXcgWW9yaycsICdDYWxpZm9ybmlhJywgJ0Zsb3JpZGEnKSwKICAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKDEsIDIsIDMpKSAjIGVuY29kaW5nIAoKc2V0LnNlZWQoMTIzKQoKc3BsaXQgPSBzYW1wbGUuc3BsaXQoZGF0YXNldCRQcm9maXQsIFNwbGl0UmF0aW8gPSAwLjgpCgojIC0tLS0gVHJhaW4vVGVzdCBzcGxpdAp0cmFpbmluZ19zZXQgPSBzdWJzZXQoZGF0YXNldCwgc3BsaXQgPT0gVFJVRSkKdGVzdF9zZXQgPSBzdWJzZXQoZGF0YXNldCwgc3BsaXQgPT0gRkFMU0UpICAgCgoKIyBHb2FsOiBuZWVkIHRvIHJlbW92ZSB0aGUgbm9uLXN0YXQgc2lnbmlmIGluZGVwIHZhcmlhYmxlcywgZGVsZXRlIHRoZSAuLCAKIyAgICAgICBpdGVyYXRlIHRocm91Z2ggc3RlcHMgYW5kIHJlbW92ZSBjb2x1bW5zIChpbmRlcCB2YXJpYWJsZXMpCiMgY29sdW1ucyB3aXRoIHNwYWNlcyA9PiB1c2UgZG90cyAKCiMgIG9yaWdpbmFsOgojICAgICBQcm9maXQgfiBSJkQuU3BlbmQgKyBBZG1pbmlzdHJhdGlvbiArIE1hcmtldGluZy5TcGVuZCArIFN0YXRlCgojIHN0ZXAgMSAgYWxwaGEgPSAwLjA1CiMgc3RlcCAyCnJlZ3Jlc3NvciA9IGxtKGZvcm11bGEgPSBQcm9maXQgfiBSLkQuU3BlbmQgKyBNYXJrZXRpbmcuU3BlbmQsCiAgICAgICAgICAgICAgIGRhdGEgPSBkYXRhc2V0KQoKIyBzdGVwIDMKIyBmaW5kIHRoZSB2YWx1ZXMgd2l0aCBoaWdoZXN0IHAtdmFsdWVzLCBsb29rIGZvciB0aGUgc3RhcnMgdW5kZXIgUHIoPnx0fCkKc3VtbWFyeShyZWdyZXNzb3IpCgojIHN0ZXAgNCByZW1vdmUgdGhlIHByZWRpY3RvciAodmFyaWFibGVzIGFmdGVyIHRoZSB+KSwgc3RlcCAzCiMgc3RlcCAzOiAKIyAgICAgICBTdGF0ZTIgcD0gMC45OTAgKDk5JSA+IDAuMDUpID09PiByZW1vdmUgaXQKIyAgICAgICBTdGF0ZTMgcD0gMC45NDMgKDk0JSA+IDAuMDUpID09PiByZW1vdmUgaXQKIyAgICAgICBBZG1pbiAgcD0gMC42MDIgKDYwJSA+IDAuMDUpID09PiByZW1vdmUgaXQKCiMgZ28gdG8gc3RlcCAyLCByZW1vdmUgU3RhdGUsIHJlbW92ZSBBZG1pbgoKIyBTdW1tYXJ5IHN0YXRzIG5vdyBzaG93IHRoYXQgTWFya2V0aW5nIFNwZW5kaW5nIHZhcmlhYmxlIGlzIDAuMDYgCiMgYW5kIGhhcyBhICcuJyBtZWFuaW5nIGl0IGlzICh3ZWFrKSBzdGF0IHNpZ25pZmljYW50IAoKIyBjYW4gcmVtb3ZlIHRoZSBNYXJrZXRpbmcgU3BlbmRpbmcgYW5kIGp1c3QgaGF2ZSB0aGUgMSBoaWdobHkgc3RhdCBzaWduaWZpY2FudCB2YXJpYWJsZQojIFN0ZXAgNQpgYGAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAKIyMgQXV0b21hdGUgQmFja3dhcmQgRWxpbWluYXRpb24gaW4gUgpgYGB7ciBCYWNrd2FyZCBFbGltIEF1dG99CiMgYXV0b21hdGljIGJhY2t3YXJkIGVsaW1pbmF0aW9uCmJhY2t3YXJkRWxpbWluYXRpb24gPC0gZnVuY3Rpb24oeCwgc2wpIHsKICAgIG51bVZhcnMgPSBsZW5ndGgoeCkKICAgIGZvciAoaSBpbiBjKDE6bnVtVmFycykpewogICAgICByZWdyZXNzb3IgPSBsbShmb3JtdWxhID0gUHJvZml0IH4gLiwgZGF0YSA9IHgpCiAgICAgIG1heFZhciA9IG1heChjb2VmKHN1bW1hcnkocmVncmVzc29yKSlbYygyOm51bVZhcnMpLCAiUHIoPnx0fCkiXSkKICAgICAgaWYgKG1heFZhciA+IHNsKXsKICAgICAgICBqID0gd2hpY2goY29lZihzdW1tYXJ5KHJlZ3Jlc3NvcikpW2MoMjpudW1WYXJzKSwgIlByKD58dHwpIl0gPT0gbWF4VmFyKQogICAgICAgIHggPSB4WywgLWpdCiAgICAgIH0KICAgICAgbnVtVmFycyA9IG51bVZhcnMgLSAxCiAgICB9CiAgICByZXR1cm4oc3VtbWFyeShyZWdyZXNzb3IpKQogIH0KICAKU0wgPSAwLjA1CmRhdGFzZXQgPSBkYXRhc2V0WywgYygxLDIsMyw0LDUpXQpiYWNrd2FyZEVsaW1pbmF0aW9uKHRyYWluaW5nX3NldCwgU0wpCiAgICAgICAgICAgICAgICAgICAKYGBgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgCiMgUG9seW5vbWlhbCBSZWdyZXNzaW9uICAgICAgICAgICAgICAgICAgICAKVGhlIGZvcm11bGEgaXMgKip5KiogPSAkYl97MH0kICsgJGJfezF9eF97MX0kICsgJGJfezJ9eF57Mn1fezF9JC4KCgp1c2VkIGluIEhlYWx0aGNhcmUvIEVwaWRlbWlvbG9neSBkYXRhICAgIAoKUG9zaXRpb24gU2FsYXJ5IGRhdGFzZXQKICAgICAgICAgICAgICAgICAgIApgYGB7cn0gICAgICAgICAgICAgICAgICAgICAgCiMgSW1wb3J0aW5nIHRoZSBkYXRhc2V0CmRhdGFzZXQgPSByZWFkLmNzdignLi9QYXJ0IDIgLSBSZWdyZXNzaW9uL1NlY3Rpb24gNiAtIFBvbHlub21pYWwgUmVncmVzc2lvbi9SL1Bvc2l0aW9uX1NhbGFyaWVzLmNzdicpCgojIHVzZSB0aGUgY29sdW1ucyBsZXZlbCBhbmQgc2FsYXJ5CmRhdGFzZXQgPSBkYXRhc2V0WzI6M10gICAgICAgICAgICAgICAgICAgIAoKIyBubyBkYXRhIHNwbGl0dGluZyBkdWUgdG8gc21hbGwgZGF0YXNldAojIG5vIGZlYXR1cmUgc2NhbGluZyBuZWVkZWQKCiMgZm9yIGEgYmFzZWxpbmUgY29tcGFyaXNvbiwgdXNlIFNpbXBsZSBMaW5lYXIgUmVncmVzc2lvbgpsaW5fcmVnID0gbG0oZm9ybXVsYSA9IFNhbGFyeSB+IC4sCiAgICAgICAgICAgICBkYXRhID0gZGF0YXNldCkKCnN1bW1hcnkobGluX3JlZykKCgojIEZpdHRpbmcgUG9seW5vbWlhbCBSZWdyZXNzaW9uIHRvIHRoZSBkYXRhc2V0CiMgcG9seW5vbWlhbCBmZWF0dXJlcyBvZiBpbmRlcCB2YXJpYWJsZXMgKHRvIGFueSBkZWdyZWUgeW91IHdhbnQpCiMgMSBpbmRlcCArIGRlcC4gdmFycwojIGFkZCBjb2x1bW4gdXNpbmcgJCBhbmQgbmFtZSBpdAoKIyAgIGRhdGFzZXQkY29sdW1uXjIgcmV0dXJucyBzcXVhcmVkIGNvbHVtbiBmb3IgYWxsIGxldmVsIGNvbHVtbiB2YWx1ZXMKZGF0YXNldCRMZXZlbDIgPSBkYXRhc2V0JExldmVsXjIKIyAgIGRhdGFzZXQkY29sdW1uXjMgcmV0dXJucyBjdWJlZCBjb2x1bW4gZm9yIGFsbCBsZXZlbCBjb2x1bW4gdmFsdWVzCmRhdGFzZXQkTGV2ZWwzID0gZGF0YXNldCRMZXZlbF4zCmRhdGFzZXQkTGV2ZWw0ID0gZGF0YXNldCRMZXZlbF40Cgpwb2x5X3JlZyA9IGxtKGZvcm11bGEgPSBTYWxhcnkgfiAuLAogICAgICAgICAgICAgIGRhdGEgPSBkYXRhc2V0KQoKc3VtbWFyeShwb2x5X3JlZykKCgoKIy0tLS0tLS0tLS0tLS0tLS0gVmlzdWFsaXppbmcgdGhlIExpbmVhciBSZWdyZXNzaW9uIHJlc3VsdHMKIyBpbnN0YWxsLnBhY2thZ2VzKCdnZ3Bsb3QyJykKbGlicmFyeShnZ3Bsb3QyKQoKZ2dwbG90KCkgKyAjIHg9IGluZGVwICB5PSBkZXAgdmFyCiAgZ2VvbV9wb2ludChhZXMoeCA9IGRhdGFzZXQkTGV2ZWwsIHkgPSBkYXRhc2V0JFNhbGFyeSksICMgcmVhbCBwb2ludHMKICAgICAgICAgICAgIGNvbG91ciA9ICdyZWQnKSArICAgICAgICMgcHJlZGljdCBmdW5jdGlvbgogIGdlb21fbGluZShhZXMoeCA9IGRhdGFzZXQkTGV2ZWwsIHkgPSBwcmVkaWN0KGxpbl9yZWcsIG5ld2RhdGEgPSBkYXRhc2V0KSksICMgcHJlZGljdGVkCiAgICAgICAgICAgIGNvbG91ciA9ICdibHVlJykgKwogIGdndGl0bGUoJ1RydXRoIG9yIEJsdWZmIChMaW5lYXIgUmVncmVzc2lvbiknKSArCiAgeGxhYignTGV2ZWwnKSArCiAgeWxhYignU2FsYXJ5JykKCiMgdGhlIGxpbmVhciByZWdyZXNzaW9uIGxpbmUgZG9lcyBub3QgZml0IHRoZSByZWFsIGRhdGEgcG9pbnRzLCAKIyB0aGlzIGlzIGNsZWFybHkgYSBwb2x5bm9taWFsIHByb2JsZW0KIyBwcmVkaWN0ZWQgc2FsYXJpZXMgYXJlIGxpbmVhciBidXQgcmVhbCBkYXRhIHBvaW50cyBhcmUgcG9seW5vbWlhbAojIHJlYWwgc2FsYXJ5IGxldmVsIDU9ICQxMjUsMDAwIHZzIHByZWRpY3RlZD0gJDI0MCwwMDAKCgojLS0tLS0tLS0tLS0tLS0gVmlzdWFsaXppbmcgdGhlIFBvbHlub21pYWwgUmVncmVzc2lvbiByZXN1bHRzCiMgCmxpYnJhcnkoZ2dwbG90MikKZ2dwbG90KCkgKwogIGdlb21fcG9pbnQoYWVzKHggPSBkYXRhc2V0JExldmVsLCB5ID0gZGF0YXNldCRTYWxhcnkpLCAjIHJlYWwKICAgICAgICAgICAgIGNvbG91ciA9ICdyZWQnKSArICAgICAgIyBwcmVkaWN0IGZ1bmN0aW9uLCBjaGFuZ2UgdG8gcG9seV9yZWcKICBnZW9tX2xpbmUoYWVzKHggPSBkYXRhc2V0JExldmVsLCB5ID0gcHJlZGljdChwb2x5X3JlZywgbmV3ZGF0YSA9IGRhdGFzZXQpKSwgIyBwcmVkaWN0ZWQKICAgICAgICAgICAgY29sb3VyID0gJ2JsdWUnKSArCiAgZ2d0aXRsZSgnVHJ1dGggb3IgQmx1ZmYgKFBvbHlub21pYWwgUmVncmVzc2lvbiknKSArCiAgeGxhYignTGV2ZWwnKSArCiAgeWxhYignU2FsYXJ5JykKCiMgdGhlIHByZWRpY3RlZCBsaW5lIGZpdHMgYmV0dGVyIHdpdGggdGhlIGRhdGEgcG9pbnRzLCBjdXJ2ZWQgbGluZQoKCgojLS0tLS0tLS0tLS0tIFZpc3VhbGl6aW5nIHRoZSBSZWdyZXNzaW9uIE1vZGVsIHJlc3VsdHMgCiMgKGZvciBoaWdoZXIgcmVzb2x1dGlvbiBhbmQgc21vb3RoZXIgY3VydmUpCiMgCmxpYnJhcnkoZ2dwbG90MikKCnhfZ3JpZCA9IHNlcShtaW4oZGF0YXNldCRMZXZlbCksIG1heChkYXRhc2V0JExldmVsKSwgMC4xKQoKZ2dwbG90KCkgKwogIGdlb21fcG9pbnQoYWVzKHggPSBkYXRhc2V0JExldmVsLCB5ID0gZGF0YXNldCRTYWxhcnkpLAogICAgICAgICAgICAgY29sb3VyID0gJ3JlZCcpICsKICBnZW9tX2xpbmUoYWVzKHggPSB4X2dyaWQsIHkgPSBwcmVkaWN0KHBvbHlfcmVnLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmV3ZGF0YSA9IGRhdGEuZnJhbWUoTGV2ZWwgPSB4X2dyaWQsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBMZXZlbDIgPSB4X2dyaWReMiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIExldmVsMyA9IHhfZ3JpZF4zLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgTGV2ZWw0ID0geF9ncmlkXjQpKSksCiAgICAgICAgICAgIGNvbG91ciA9ICdibHVlJykgKwogIGdndGl0bGUoJ1RydXRoIG9yIEJsdWZmIChQb2x5bm9taWFsIFJlZ3Jlc3Npb24pJykgKwogIHhsYWIoJ0xldmVsJykgKwogIHlsYWIoJ1NhbGFyeScpCgojIFByZWRpY3RpbmcgYSBuZXcgcmVzdWx0IHdpdGggTGluZWFyIFJlZ3Jlc3Npb24KIyBtYWtlIGEgcHJlZGljdGlvbiBvbiBiYXNlZCBvbiBsZXZlbCA2LjUgCiMgbWFrZSBhIG5ldyBkYXRhZnJhbWUgcm93CiMgeV9wcmVkLjIgPSBwcmVkaWN0KGxpbl9yZWcsIGRhdGEuZnJhbWUoTGV2ZWwgPSA2LjUpKQoKcHJlZGljdChsaW5fcmVnLCBkYXRhLmZyYW1lKExldmVsID0gNi41KSkKCiMgUHJlZGljdGluZyBhIG5ldyByZXN1bHQgd2l0aCBQb2x5bm9taWFsIFJlZ3Jlc3Npb24KIyBhZGQgcG9seW5vbWlhbCBmZWF0dXJlcyBmb3IgZWFjaCBsZXZlbCBjb2x1bW4KeV9wcmVkLjMgPSBwcmVkaWN0KHBvbHlfcmVnLCBkYXRhLmZyYW1lKExldmVsID0gNi41LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIExldmVsMiA9IDYuNV4yLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIExldmVsMyA9IDYuNV4zLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIExldmVsNCA9IDYuNV40KSkKCnlfcHJlZC4zCmBgYAoKCgojIFN1cHBvcnQgVmVjdG9yIFJlZ3Jlc3Npb24KCmBgYHtyIFN1cHBvcnQgVmVjdG9yIE1hY2hpbmV9CiMgSW1wb3J0aW5nIHRoZSBkYXRhc2V0CmRhdGFzZXQgPSByZWFkLmNzdignUGFydCAyIC0gUmVncmVzc2lvbi9TZWN0aW9uIDcgLSBTdXBwb3J0IFZlY3RvciBSZWdyZXNzaW9uIChTVlIpL1IvUG9zaXRpb25fU2FsYXJpZXMuY3N2JykKZGF0YXNldCA9IGRhdGFzZXRbMjozXQoKIyBGaXR0aW5nIFN1cHBvcnQgVmVjdG9yIFJlZ3Jlc3Npb24gdG8gdGhlIGRhdGFzZXQKIyBpbnN0YWxsLnBhY2thZ2VzKCdlMTA3MScpCmxpYnJhcnkoZTEwNzEpCgpyZWdyZXNzb3IgPSBzdm0oZm9ybXVsYSA9IFNhbGFyeSB+IC4sCiAgICAgICAgICAgICAgICBkYXRhID0gZGF0YXNldCwKICAgICAgICAgICAgICAgIHR5cGUgPSAnZXBzLXJlZ3Jlc3Npb24nLCAjIFZFUlkgSU1QT1JUQU5ULCBvcHRpb25zIGZvciB0eXBlIChzZWUgZG9jcykKICAgICAgICAgICAgICAgIGtlcm5lbCA9ICdyYWRpYWwnKQoKCiMgUHJlZGljdGluZyBhIG5ldyByZXN1bHQsIHRoaXMgaXMgc2hvd24gaW4gRGF0YSBlbnZpcm9ubWVudCByaWdodCBwYW5lbAp5X3ByZWQgPSBwcmVkaWN0KHJlZ3Jlc3NvciwgZGF0YS5mcmFtZShMZXZlbCA9IDYuNSkpCgojLS0tLS0tLS0tLS0gVmlzdWFsaXppbmcgdGhlIFNWUiByZXN1bHRzCiMgaW5zdGFsbC5wYWNrYWdlcygnZ2dwbG90MicpCmxpYnJhcnkoZ2dwbG90MikKZ2dwbG90KCkgKwogIGdlb21fcG9pbnQoYWVzKHggPSBkYXRhc2V0JExldmVsLCB5ID0gZGF0YXNldCRTYWxhcnkpLAogICAgICAgICAgICAgY29sb3VyID0gJ3JlZCcpICsgIyByZWFsIGRhdGEgcG9pbnRzCiAgZ2VvbV9saW5lKGFlcyh4ID0gZGF0YXNldCRMZXZlbCwgeSA9IHByZWRpY3QocmVncmVzc29yLCBuZXdkYXRhID0gZGF0YXNldCkpLAogICAgICAgICAgICBjb2xvdXIgPSAnYmx1ZScpICsgIyBwcmVkaWN0ZWQgcG9pbnRzCiAgZ2d0aXRsZSgnVHJ1dGggb3IgQmx1ZmYgKFNWUiAxKScpICsKICB4bGFiKCdMZXZlbCcpICsKICB5bGFiKCdTYWxhcnknKQoKIy0tLS0tLS0tLS0tIFZpc3VhbGl6aW5nIHRoZSBTVlIgcmVzdWx0cyAKIyAoZm9yIGhpZ2hlciByZXNvbHV0aW9uIGFuZCBzbW9vdGhlciBjdXJ2ZSkKIyBpbnN0YWxsLnBhY2thZ2VzKCdnZ3Bsb3QyJykKbGlicmFyeShnZ3Bsb3QyKQp4X2dyaWQgPSBzZXEobWluKGRhdGFzZXQkTGV2ZWwpLCBtYXgoZGF0YXNldCRMZXZlbCksIDAuMSkKZ2dwbG90KCkgKwogIGdlb21fcG9pbnQoYWVzKHggPSBkYXRhc2V0JExldmVsLCB5ID0gZGF0YXNldCRTYWxhcnkpLAogICAgICAgICAgICAgY29sb3VyID0gJ3JlZCcpICsKICBnZW9tX2xpbmUoYWVzKHggPSB4X2dyaWQsIHkgPSBwcmVkaWN0KHJlZ3Jlc3NvciwgbmV3ZGF0YSA9IGRhdGEuZnJhbWUoTGV2ZWwgPSB4X2dyaWQpKSksCiAgICAgICAgICAgIGNvbG91ciA9ICdibHVlJykgKwogIGdndGl0bGUoJ1RydXRoIG9yIEJsdWZmIChTVlIgMiknKSArCiAgeGxhYignTGV2ZWwnKSArCiAgeWxhYignU2FsYXJ5JykKCmBgYAoKCgoKIyBEZWNpc2lvbiBUcmVlIFJlZ3Jlc3Npb24KRGVjaXNpb24gVHJlZXMgaGF2ZSAyIHR5cGVzIChDQVJUKTogKipDbGFzc2lmaWNhdGlvbiBUcmVlcyoqIGFuZCAqKlJlZ3Jlc3Npb24gVHJlZXMqKiAobW9yZSBjb21wbGV4KQoKcHJlZGljdGluZyAzcmQgdmFyaWFibGUgKHkpLCB1c2luZyB4MSBhbmQgeDIKCmBgYHtyIERlY2lzaW9uIFRyZWUgUmVncmVzc2lvbn0KIyBJbXBvcnRpbmcgdGhlIGRhdGFzZXQKZGF0YXNldCA9IHJlYWQuY3N2KCdQYXJ0IDIgLSBSZWdyZXNzaW9uL1NlY3Rpb24gOCAtIERlY2lzaW9uIFRyZWUgUmVncmVzc2lvbi9SL1Bvc2l0aW9uX1NhbGFyaWVzLmNzdicpCmRhdGFzZXQgPSBkYXRhc2V0WzI6M10KCiMgRml0dGluZyBTdXBwb3J0IFZlY3RvciBSZWdyZXNzaW9uIHRvIHRoZSBkYXRhc2V0CiMgaW5zdGFsbC5wYWNrYWdlcygnZTEwNzEnKQpsaWJyYXJ5KGUxMDcxKQoKIyBGaXR0aW5nIERlY2lzaW9uIFRyZWUgUmVncmVzc2lvbiB0byB0aGUgZGF0YXNldAojIGluc3RhbGwucGFja2FnZXMoJ3JwYXJ0JykKbGlicmFyeShycGFydCkKCiMgUlBBUlQgPSBSZWN1cnNpdmUgUGFydGl0aW9uaW5nIApyZWdyZXNzb3IgPSBycGFydChmb3JtdWxhID0gU2FsYXJ5IH4gLiwKICAgICAgICAgICAgICAgICAgZGF0YSA9IGRhdGFzZXQsCiAgICAgICAgICAgICAgICAgIGNvbnRyb2wgPSBycGFydC5jb250cm9sKG1pbnNwbGl0ID0gMSkpICMgbmV3IHBhcnQgCgoKIyBQcmVkaWN0aW5nIGEgbmV3IHJlc3VsdCB3aXRoIERlY2lzaW9uIFRyZWUgUmVncmVzc2lvbgp5X3ByZWQgPSBwcmVkaWN0KHJlZ3Jlc3NvciwgZGF0YS5mcmFtZShMZXZlbCA9IDYuNSkpCgojLS0tLS0tLS0tLS0tLS0tIFZpc3VhbGl6aW5nIHRoZSBEZWNpc2lvbiBUcmVlIFJlZ3Jlc3Npb24gCiMgcmVzdWx0cyAoaGlnaGVyIHJlc29sdXRpb24pCiMgaW5zdGFsbC5wYWNrYWdlcygnZ2dwbG90MicpCgpsaWJyYXJ5KGdncGxvdDIpCgp4X2dyaWQgPSBzZXEobWluKGRhdGFzZXQkTGV2ZWwpLCBtYXgoZGF0YXNldCRMZXZlbCksIDAuMDEpCgpnZ3Bsb3QoKSArCiAgZ2VvbV9wb2ludChhZXMoeCA9IGRhdGFzZXQkTGV2ZWwsIHkgPSBkYXRhc2V0JFNhbGFyeSksCiAgICAgICAgICAgICBjb2xvdXIgPSAncmVkJykgKwogIGdlb21fbGluZShhZXMoeCA9IHhfZ3JpZCwgeSA9IHByZWRpY3QocmVncmVzc29yLCBuZXdkYXRhID0gZGF0YS5mcmFtZShMZXZlbCA9IHhfZ3JpZCkpKSwKICAgICAgICAgICAgY29sb3VyID0gJ2JsdWUnKSArCiAgZ2d0aXRsZSgnVHJ1dGggb3IgQmx1ZmYgKERlY2lzaW9uIFRyZWUgUmVncmVzc2lvbiknKSArCiAgeGxhYignTGV2ZWwnKSArCiAgeWxhYignU2FsYXJ5JykKCgoKYGBgCgp0aGlzIHNob3dzIHdpdGggdGhlIGJsdWUgbGluZSB0aGUgYXZlcmFnZSBvZiBzYWxhcmllcyBmb3IgbGV2ZWwgNi41IGlzICQyNTAsMDAwCgpgYGB7cn0KIyBQbG90dGluZyB0aGUgdHJlZQpwbG90KHJlZ3Jlc3NvcikKdGV4dChyZWdyZXNzb3IpCmBgYAoKCiMgUmFuZG9tIEZvcmVzdCBSZWdyZXNzaW9uIApFbnNlbWJsZSBMZWFybmluZyA9IHRha2UgbXVsdGlwbGUgYWxnb3JpdGhtcyB0byBtYWtlIHBvd2VyZnVsIGFsZ29yaXRobQoKICAtIHN0ZXAgMTogcGljayBhdCByYW5kb20gayBkYXRhIHBvaW50cyBmcm9tIHRyYWluaW5nIHNldAogIC0gc3RlcCAyOiBidWlsZCBkZWNpc2lvbiB0cmVlIGFzc29jaWF0ZWQgdG8gdGhlc2UgayBkYXRhIHBvaW50cwogIC0gc3RlcCAzOiBjaG9vc2UgdGhlIG51bWJlciAqTip0cmVlIG9mIHRyZWVzIHlvdSB3YW50IHRvIGJ1aWxkIGFuZCByZXBlYXQgc3RlcHMgMSAmIDIKICAtIHN0ZXAgNDogZm9yIGEgbmV3IGRhdGEgcG9pbnQsIG1ha2UgZWFjaCBvbmUgb2YgeW91ciAqTip0cmVlIHRyZWVzIHByZWRpY3QgdGhlIHZhbHVlIG9mIFkgZm9yIHRoZSBkYXRhIHBvaW50IGluIHF1ZXN0aW9uLCBhbmQgYXNzaWduIHRoZSBuZXcgZGF0YSBwb2ludCB0aGUgYXZlcmFnZSBhY3Jvc3MgYWxsIG9mIHRoZSBwcmVkaWN0ZWQgWSB2YWx1ZXMKCmBgYHtyIFJhbmRvbSBGb3Jlc3QgUmVncmVzc2lvbn0KIyBJbXBvcnRpbmcgdGhlIGRhdGFzZXQKZGF0YXNldCA9IHJlYWQuY3N2KCdQYXJ0IDIgLSBSZWdyZXNzaW9uL1NlY3Rpb24gOSAtIFJhbmRvbSBGb3Jlc3QgUmVncmVzc2lvbi9SL1Bvc2l0aW9uX1NhbGFyaWVzLmNzdicpCmRhdGFzZXQgPSBkYXRhc2V0WzI6M10KCiMtLS0tLSBGaXR0aW5nIFJhbmRvbSBGb3Jlc3QgUmVncmVzc2lvbiB0byB0aGUgZGF0YXNldAojIGluc3RhbGwucGFja2FnZXMoJ3JhbmRvbUZvcmVzdCcpCmxpYnJhcnkocmFuZG9tRm9yZXN0KQoKc2V0LnNlZWQoMTIzNCkgIyBjb21tb24gcmFuZG9tIHNlZWQgaW4gUgoKIyAtLSBidWlsZCBSYW5kb20gRm9yZXN0IE1vZGVsCnJlZ3Jlc3NvciA9IHJhbmRvbUZvcmVzdCh4ID0gZGF0YXNldFstMl0sCiAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gZGF0YXNldCRTYWxhcnksCiAgICAgICAgICAgICAgICAgICAgICAgICBudHJlZSA9IDUwMCkgIyBjaGFuZ2UgdmFsdWVzIHRvIGZpbmQgYmVzdCB2YWx1ZSwgNTAwCgojIFByZWRpY3RpbmcgYSBuZXcgcmVzdWx0CnlfcHJlZCA9IHByZWRpY3QocmVncmVzc29yLCBkYXRhLmZyYW1lKExldmVsID0gNi41KSkKIyB5X3ByZWQgPSAxNjA5MDggZm9yIG50cmVlPSA1MDAKCiMtLS0tLS0tLS0tLSBWaXN1YWxpemluZyB0aGUgUmFuZG9tIEZvcmVzdCBSZWdyZXNzaW9uIE1vZGVsIAojIHJlc3VsdHMgKGZvciBoaWdoZXIgcmVzb2x1dGlvbiBhbmQgc21vb3RoZXIgY3VydmUpCiMgaW5zdGFsbC5wYWNrYWdlcygnZ2dwbG90MicpCmxpYnJhcnkoZ2dwbG90MikKCnhfZ3JpZCA9IHNlcShtaW4oZGF0YXNldCRMZXZlbCksIG1heChkYXRhc2V0JExldmVsKSwgMC4wMSkKZ2dwbG90KCkgKwogIGdlb21fcG9pbnQoYWVzKHggPSBkYXRhc2V0JExldmVsLCB5ID0gZGF0YXNldCRTYWxhcnkpLAogICAgICAgICAgICAgY29sb3VyID0gJ3JlZCcpICsKICBnZW9tX2xpbmUoYWVzKHggPSB4X2dyaWQsIHkgPSBwcmVkaWN0KHJlZ3Jlc3NvciwgbmV3ZGF0YSA9IGRhdGEuZnJhbWUoTGV2ZWwgPSB4X2dyaWQpKSksCiAgICAgICAgICAgIGNvbG91ciA9ICdibHVlJykgKwogIGdndGl0bGUoJ1RydXRoIG9yIEJsdWZmIChSYW5kb20gRm9yZXN0IFJlZ3Jlc3Npb24pJykgKwogIHhsYWIoJ0xldmVsJykgKwogIHlsYWIoJ1NhbGFyeScpCgoKCmBgYApUaGUgYmx1ZSBsaW5lIGlzIHRoZSBwcmVkaWN0ZWQgYXZlcmFnZXMgb2Ygc2FsYXJpZXMsIHdpdGggNTAwIHRyZWVzLCB0aGUgbW9kZWwgcHJlZGljdGVkIHNhbGFyeSBvZiAkMTYwLDkwOCBmb3IgbGV2ZWwgNi41CgoKCiMgUiBTcXVhcmVkCgotICoqc3VtIG9mIHNxdWFyZXMgcmVzaWR1YWxzKiogKFNTcmVzKSA9IHN1bSgkeV97aX0kIC0gJHlfe2leaX0kKV4yCi0gYXZlcmFnZSBsaW5lIG9uIHBsb3QgZm9yICoqc3VtIG9mIHNxdWFyZXMgdG90YWwqKiA9IHN1bSgkeV97aX0kIC0gJHlfe2F2Z30kKV4yCi0gJFJeezJ9JCA9IDEgLSAkU1Nfe3Jlc30kICRcZGl2JCAkU1Nfe3RvdH0kIAoKdGhlIHRvdGFsIG9mIHN1bSBvZiBzcXVhcmVzLCB0cnkgdG8gZml0IGEgbGluZSB0byBtaW5pbWl6ZSB0aGUgbGluZSBvZiBsZWFzdCBzcXVhcmVzIHJlc2lkdWFscywgKmhvdyBnb29kIGlzIHlvdXIgbGluZSBjb21wYXJlZCB0byB0aGUgYXZlcmFnZSA/KiBUaGUgY2xvc2VyICRSXjIkIGlzIHRvIDEgdGhlIGJldHRlciEgCgoKIyMgKiphZGp1c3RlZCBSIHNxdWFyZWQqKiBpcyB1c2VkIGZvciBtdWx0aXBsZSByZWdyZXNzaW9uLiAKCi0gcCA9IG51bWJlciBvZiByZWdyZXNzb3JzCi0gbj0gc2FtcGxlIHNpemUKLSBhZGouICRSXjIkID0gMSAtICgxIC0gJFJeMiQpIChuIC0gMSkvIChuIC0gcCAtIDEpCgoKaG93IHdlbGwgeW91ciBtb2RlbCBoYXMgYmVlbiBmaXR0ZWQsIGNsb3NlciB0byAxIHRoZSBiZXR0ZXIsIGJ1dCBpdCBpcyBiaWFzZWQgT0xTIG1ldGhvZCwgUl4yIG5ldmVyIGRlY3JlYXNlcywgYWRkaW5nIG1vcmUgdmFyaWFibGVzIFJeMiBncm93cy4gQWRqdXN0ZWQgUl4yIHBlbmFsaXplcyB0aGUgZ3Jvd2luZyB2YWx1ZSBmcm9tIHZhcmlhYmxlcywgaGVuY2UgaXQgZGVjcmVhc2VzIGluIHZhbHVlLiBEcm9wcGl1bmcgdW5oZWxwZnVsIGNvbHVtbnMgZm9yIG1vZGVscyBoZWxwcyB0aGUgYWRqdXN0ZWQgUl4yIHZhbHVlIGdldCBjbG9zZXIgdG8gMSwgd2hpY2ggaXMgYWxsIGdvb2QgbmV3cyBhbmQgd2hhdCB3ZSB3YW50LiAKClNvIGluIG1vZGVscyBhYm92ZSwgdGhlIDNyZCBhbmQgNHRoIG1vZGVscyBSXjIgdmFsdWVzOgoKLSBgYGxtKGZvcm11bGE9IFByb2ZpdCB+IFImRC5TcGVuZCArIE1hcmtldGluZy5TcGVuZCwgZGF0YSA9IGRhdGFzZXQpYGAgJFJeMiQgPSAwLjk0ODMKLSBgYGxtKGZvcm11bGE9IFByb2ZpdCB+IFImRC5TcGVuZCwgZGF0YSA9IGRhdGFzZXQpYGAgJFJeMiQgPSAwLjk0NTQKCkNvbXBhcmluZyB0aGUgbGFzdCAyIG1vZGVsczogIGBgMC45NDU0IC0gMC45NDgzID0gLTAuMDAyOWBgLCBzbyBsYXN0IG1vZGVsIGRpZCB3b3JzZSB0aGVuIDNyZCBtb2RlbAoKCgojIyBjb2VmZmljaWVudHMgClVuZGVyc3RhbmRpbmcgdGhlIGNvZWZmaWNpZW50cwpgYGAKbG0oZm9ybXVsYSA9IFByb2ZpdCB+IFIuRC5TcGVuZCArIE1hcmtldGluZy5TcGVuZCwgZGF0YSA9IGRhdGFzZXQpCgpSZXNpZHVhbHM6CiAgIE1pbiAgICAgMVEgTWVkaWFuICAgICAzUSAgICBNYXggCi0zMzY0NSAgLTQ2MzIgICAtNDE0ICAgNjQ4NCAgMTcwOTcgCgpDb2VmZmljaWVudHM6CiAgICAgICAgICAgICAgICAgRXN0aW1hdGUgU3RkLiBFcnJvciB0IHZhbHVlIFByKD58dHwpICAgIAooSW50ZXJjZXB0KSAgICAgNC42OThlKzA0ICAyLjY5MGUrMDMgIDE3LjQ2NCAgIDwyZS0xNiAqKioKUi5ELlNwZW5kICAgICAgIDcuOTY2ZS0wMSAgNC4xMzVlLTAyICAxOS4yNjYgICA8MmUtMTYgKioqCk1hcmtldGluZy5TcGVuZCAyLjk5MWUtMDIgIDEuNTUyZS0wMiAgIDEuOTI3ICAgICAwLjA2IC4gIAotLS0KU2lnbmlmLiBjb2RlczogIDAg4oCYKioq4oCZIDAuMDAxIOKAmCoq4oCZIDAuMDEg4oCYKuKAmSAwLjA1IOKAmC7igJkgMC4xIOKAmCDigJkgMQoKUmVzaWR1YWwgc3RhbmRhcmQgZXJyb3I6IDkxNjEgb24gNDcgZGVncmVlcyBvZiBmcmVlZG9tCk11bHRpcGxlIFItc3F1YXJlZDogIDAuOTUwNSwJQWRqdXN0ZWQgUi1zcXVhcmVkOiAgMC45NDgzIApGLXN0YXRpc3RpYzogNDUwLjggb24gMiBhbmQgNDcgREYsICBwLXZhbHVlOiA8IDIuMmUtMTYKYGBgCgpwb3NpdGl2ZSB2YWx1ZSBpcyBwb3NpdGl2ZSBjb3JyZWxhdGVkLCBpbmNyZWFzZSBpbiBSLkQuU3BlbmQgPT0gaW5jcmVhc2UgaW4gUHJvZml0LCBhbmQgdmljZS12ZXJzYS4KCgpNYWduaXR1ZGUgaXMgY29sdW1uIHVuZGVyIEVzdGltYXRlLCBSLkQuU3BlbmQgYGA3Ljk2NmUtMDFgYCBpbiB1bml0cyBvZiBpbmRlcGVuZGVudCB2YXJpYWJsZSB2YWx1ZXMuIENvcnJlY3QgdG8gc2F5OiBSRCBTcGVuZCBoYXMgZ3JlYXRlciBpbXBhY3Qgb24gcHJvZml0IHBlciB1bml0IG9mIFJEIFNwZW5kIHRoYW4gCm1hcmtldGluZyBzcGVuZC4gKkZvciBldmVyeSB1bml0IGluY3JlYXNlIG9mIFJEIFNwZW5kICgkMSksIHByb2ZpdHMgd2lsbCBpbmNyZWFzZSBieSAwLjc5IGNlbnRzIHBlciB1bml0Ki4gTWFya2V0aW5nIFNwZW5kIGFkZHMgdmFsdWUgb2YgMyBjZW50cyB1bml0cyB0byBwcm9maXQuCgoKRW5kIG9mIFBhcnQgMiAtLSBSZWdyZXNzaW9uCjxocj4KCiMgKipDbGFzc2lmaWNhdGlvbioqCgojIyBMb2dpc3RpYyBSZWdyZXNzaW9uCmFnZSBhbmQgZW1haWwgYWN0aW9uIHRha2VuIChZL04gfCAxLzApIGNvcnJlbGF0aW9uLCB7bGluZWFyIHJlZ3Jlc3Npb24gaXMgbm90IHJpZ2h0IG1vZGVsIGZvciB0aGlzIGJ1dCBzaG93cyBhIHRyZW5kIGJldHdlZW4gdmFyaWFibGVzfS4gU2lnbW9pZCBmdW5jdGlvbiBmb3JjZXMgdmFsdWVzIDAgdG8gMSwgUy1zaGFwZWQgY3VydmUgb24gcGxvdCB3aGljaCBtYWtlcyB0aGUgYmVzdCBmaXR0aW5nIGxpbmUgZm9yIHZhcmlhYmxlcy4gKipVc2VkIGZvciBwcmVkaWN0aW5nIHByb2JhYmlsaXR5ICgkcF4tJCkgKHBfaGF0KSoqCgpMb2dpc3RpYyBSZWdyZXNzaW9uIGZvcm11bGEgICRsbiQocCAvIDEgLSBwKSA9ICRiX3swfSQgKyAkYl97MX0qeCQgCgpFeGFtcGxlOiBvbiB4LWF4aXMsIDQgYWdlIGNvbHVtbiB2YWx1ZXMgYXQgcmFuZG9tLCB5LWF4aXMgaXMgcF9oYXQsIHRoZSBzLWN1cnZlIG9uIHRoZSBwbG90LiBQZXJzb24gb2YgYWdlIDIwIGhhcyBhIHBfaGF0IHByb2JhYmlsaXR5IHZhbHVlIG9mIDAuNyUgKHBeLSA9IDAuNykgb2YgdGFraW5nIGFjdGlvbiB3aXRoIGFuIGVtYWlsLiBQZXJzb24gYWdlIDQwIGhhcyBwXi0gPSA4NSUgb2YgdGFraW5nIGFjdGlvbiB3aXRoIGFuIGVtYWlsLiBBbnkgdmFsdWVzIGJlbG93IDUwJSB0aGUgeV9oYXQgdmFsdWUgaXMgcHVzaGVkIGRvd24gdG93YXJkcyAwLCBhbnkgdmFsdWUgYWJvdmUgNTAlIGlzIHB1c2hlZCB1cCB0b3dhcmRzIDEsIHNvIHRoZSByZXN1bHRzIGluIGEgYmluYXJ5IDAvMSBvdXRjb21lLiAKCmBgYHtyIExvZ2lzdGljIFJlZ3Jlc3Npb259CgojIEltcG9ydGluZyB0aGUgZGF0YXNldApkYXRhc2V0ID0gcmVhZC5jc3YoJy4vUGFydCAzIC0gQ2xhc3NpZmljYXRpb24vU2VjdGlvbiAxNCAtIExvZ2lzdGljIFJlZ3Jlc3Npb24vUi9Tb2NpYWxfTmV0d29ya19BZHMuY3N2JykKCmRhdGFzZXQgPSBkYXRhc2V0WzM6NV0KCiMgRW5jb2RpbmcgdGhlIHRhcmdldCBmZWF0dXJlIGFzIGZhY3RvcgpkYXRhc2V0JFB1cmNoYXNlZCA9IGZhY3RvcihkYXRhc2V0JFB1cmNoYXNlZCwgbGV2ZWxzID0gYygwLCAxKSkKCiMgU3BsaXR0aW5nIHRoZSBkYXRhc2V0IGludG8gdGhlIFRyYWluaW5nIHNldCBhbmQgVGVzdCBzZXQKIyBpbnN0YWxsLnBhY2thZ2VzKCdjYVRvb2xzJykKbGlicmFyeShjYVRvb2xzKQpzZXQuc2VlZCgxMjMpCnNwbGl0ID0gc2FtcGxlLnNwbGl0KGRhdGFzZXQkUHVyY2hhc2VkLCBTcGxpdFJhdGlvID0gMC43NSkKdHJhaW5pbmdfc2V0ID0gc3Vic2V0KGRhdGFzZXQsIHNwbGl0ID09IFRSVUUpCnRlc3Rfc2V0ID0gc3Vic2V0KGRhdGFzZXQsIHNwbGl0ID09IEZBTFNFKQoKIyBGZWF0dXJlIFNjYWxpbmcgaXMgYmVzdCBwcmFjdGljZSBmb3IgTG9naXN0aWMgUmVncmVzc2lvbgojICBbLTNdIGlzIHRoZSBsYXN0IGNvbHVtbiBvZiBkYXRhc2V0CnRyYWluaW5nX3NldFstM10gPSBzY2FsZSh0cmFpbmluZ19zZXRbLTNdKQp0ZXN0X3NldFstM10gPSBzY2FsZSh0ZXN0X3NldFstM10pCgojIEZpdHRpbmcgTG9naXN0aWMgUmVncmVzc2lvbiB0byB0aGUgVHJhaW5pbmcgc2V0CiMgZ2xtIChnZW5lcmFsIGxpbmVhciBtb2RlbCkgYnVpbGRzIHRoZSBsb2dpc3RpYyByZWdyZXNzaW9uCiMgcHJlZGljdCB0aGUgZGVwZW5kZW50IHZhcmlhYmxlIG9mIFB1cmNoYXNlZCBiYXNlZCBvbiBpbmRlcC4gdmFyaWFibGVzOiBhZ2UsIGVzdGltYXRlZCBzYWxhcnkKY2xhc3NpZmllciA9IGdsbShmb3JtdWxhID0gUHVyY2hhc2VkIH4gLiwgCiAgICAgICAgICAgICAgICAgZmFtaWx5ID0gYmlub21pYWwsCiAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluaW5nX3NldCkKCiMgUHJlZGljdGluZyB0aGUgVGVzdCBzZXQgcmVzdWx0cwpwcm9iX3ByZWQgPSBwcmVkaWN0KGNsYXNzaWZpZXIsIAogICAgICAgICAgICAgICAgICAgIHR5cGUgPSAncmVzcG9uc2UnLCAKICAgICAgICAgICAgICAgICAgICBuZXdkYXRhID0gdGVzdF9zZXRbLTNdKQoKeV9wcmVkLmxvZ3IgPSBpZmVsc2UocHJvYl9wcmVkID4gMC41LCAxLCAwKQoKIyBNYWtpbmcgdGhlIENvbmZ1c2lvbiBNYXRyaXgKY20gPSB0YWJsZSh0ZXN0X3NldFssIDNdLCB5X3ByZWQubG9nciA+IDAuNSkKY20KCnByb2JfcHJlZFsxXQp5X3ByZWQubG9nclsxXQpgYGAKdGhlIDFzdCBwcm9iYWJpbGl0eSBwcmVkaWN0ZWQgPSAwLjE2MiBhbmQgdGhlIHRlc3Rfc2V0IDFzdCB2YWx1ZSBpcyAwLCB0aGlzIG1lYW4gdXNlciAjMiBpcyB1bmxpa2VseSB0byBwdXJjaGFzZS4gVXNpbmcgdGhlIHlfcHJlZC5sb2dyIHZhcmlhYmxlIHRoZSBtb2RlbCBwcmVkaWN0ZWQgdGhhdCB1c2VyICMyIHdpbGwgbm90IHB1cmNoYXNlIGFuIGl0ZW0uCgoKdGhlIG1vZGVsIGNvcnJlY3RseSBwcmVkaWN0ZWQgYSBwdXJjaGFzZSAoNTcrMjYpIDgzIHRpbWVzIGFuZCAxNyBpbmNvcnJlY3QgcHJlZGljdGlvbnMKCgpgYGB7cn0KIyBWaXN1YWxpemluZyB0aGUgVHJhaW5pbmcgc2V0IHJlc3VsdHMKbGlicmFyeShFbGVtU3RhdExlYXJuKQoKc2V0ID0gdHJhaW5pbmdfc2V0CgojIHRoZSByYW5nZXMgb2Ygb2JzZXJ2YXRpb25zICwgLTEgdG8gYXZvaWQgc3F1ZWV6aW5nClgxID0gc2VxKG1pbihzZXRbLCAxXSkgLSAxLCBtYXgoc2V0WywgMV0pICsgMSwgYnkgPSAwLjAxKSAjIGFnZQpYMiA9IHNlcShtaW4oc2V0WywgMl0pIC0gMSwgbWF4KHNldFssIDJdKSArIDEsIGJ5ID0gMC4wMSkgIyBzYWxhcnkKCiMgbWFrZSBncmlkIG1hdHJpeCB1c2luZyB0aGUgdmFyaWFibGVzIGFib3ZlCmdyaWRfc2V0ID0gZXhwYW5kLmdyaWQoWDEsIFgyKQoKY29sbmFtZXMoZ3JpZF9zZXQpID0gYygnQWdlJywgJ0VzdGltYXRlZFNhbGFyeScpCgpwcm9iX3NldCA9IHByZWRpY3QoY2xhc3NpZmllciwgCiAgICAgICAgICAgICAgICAgICB0eXBlID0gJ3Jlc3BvbnNlJywgCiAgICAgICAgICAgICAgICAgICBuZXdkYXRhID0gZ3JpZF9zZXQpCgp5X2dyaWQgPSBpZmVsc2UocHJvYl9zZXQgPiAwLjUsIDEsIDApCgoKCiM9PT09PT09PT09PT09PSBWaXN1YWxpemluZyB0aGUgVHJhaW5pbmcgc2V0CnBsb3Qoc2V0WywgLTNdLAogICAgIG1haW4gPSAnTG9naXN0aWMgUmVncmVzc2lvbiBDbGFzc2lmaWVyIChUcmFpbmluZyBzZXQpJywKICAgICB4bGFiID0gJ0FnZScsIAogICAgIHlsYWIgPSAnRXN0aW1hdGVkIFNhbGFyeScsCiAgICAgeGxpbSA9IHJhbmdlKFgxKSwgCiAgICAgeWxpbSA9IHJhbmdlKFgyKSkKCmNvbnRvdXIoWDEsIFgyLCBtYXRyaXgoYXMubnVtZXJpYyh5X2dyaWQpLCBsZW5ndGgoWDEpLCBsZW5ndGgoWDIpKSwgYWRkID0gVFJVRSkKcG9pbnRzKGdyaWRfc2V0LCBwY2ggPSAnLicsIGNvbCA9IGlmZWxzZSh5X2dyaWQgPT0gMSwgJ3NwcmluZ2dyZWVuMycsICd0b21hdG8nKSkKcG9pbnRzKHNldCwgcGNoID0gMjEsIGJnID0gaWZlbHNlKHNldFssIDNdID09IDEsICdncmVlbjQnLCAncmVkMycpKQoKIz09PT09PT09PT09PT09IFZpc3VhbGl6aW5nIHRoZSBUZXN0IHNldCByZXN1bHRzCmxpYnJhcnkoRWxlbVN0YXRMZWFybikKCnNldCA9IHRlc3Rfc2V0CgpYMSA9IHNlcShtaW4oc2V0WywgMV0pIC0gMSwgbWF4KHNldFssIDFdKSArIDEsIGJ5ID0gMC4wMSkKWDIgPSBzZXEobWluKHNldFssIDJdKSAtIDEsIG1heChzZXRbLCAyXSkgKyAxLCBieSA9IDAuMDEpCgpncmlkX3NldCA9IGV4cGFuZC5ncmlkKFgxLCBYMikKCmNvbG5hbWVzKGdyaWRfc2V0KSA9IGMoJ0FnZScsICdFc3RpbWF0ZWRTYWxhcnknKQoKcHJvYl9zZXQgPSBwcmVkaWN0KGNsYXNzaWZpZXIsIHR5cGUgPSAncmVzcG9uc2UnLCBuZXdkYXRhID0gZ3JpZF9zZXQpCgp5X2dyaWQgPSBpZmVsc2UocHJvYl9zZXQgPiAwLjUsIDEsIDApCgpwbG90KHNldFssIC0zXSwKICAgICBtYWluID0gJ0xvZ2lzdGljIFJlZ3Jlc3Npb24gQ2xhc3NpZmllciAoVGVzdCBzZXQpJywKICAgICB4bGFiID0gJ0FnZScsIAogICAgIHlsYWIgPSAnRXN0aW1hdGVkIFNhbGFyeScsCiAgICAgeGxpbSA9IHJhbmdlKFgxKSwgCiAgICAgeWxpbSA9IHJhbmdlKFgyKSkKCmNvbnRvdXIoWDEsIFgyLCBtYXRyaXgoYXMubnVtZXJpYyh5X2dyaWQpLCBsZW5ndGgoWDEpLCBsZW5ndGgoWDIpKSwgYWRkID0gVFJVRSkKcG9pbnRzKGdyaWRfc2V0LCBwY2ggPSAnLicsIGNvbCA9IGlmZWxzZSh5X2dyaWQgPT0gMSwgJ3NwcmluZ2dyZWVuMycsICd0b21hdG8nKSkKcG9pbnRzKHNldCwgcGNoID0gMjEsIGJnID0gaWZlbHNlKHNldFssIDNdID09IDEsICdncmVlbjQnLCAncmVkMycpKQoKCgpgYGAKClRyYWluaW5nIHNldCBvYnNlcnZhdGlvbiBkYXRhcG9pbnRzLCByZWQgcG9pbnRzIGFyZSB0cmFpbmluZyBvYnNlcnZhdGlvbiB3aGVyZSAoZGVwZW5kZW50IHZhcmlhYmxlKSBQdXJjaGFzZWQ9MCBhbmQgdGhlIGdyZWVuIHBvaW50cyBhcmUgdHJhaW5pbmcgc2V0IG9ic2VydmF0aW9uIHdoZXJlIHB1cmNoYXNlZD0xLiByZWQgem9uZSBpcyBwcmVkaWN0aW9uIHJlZ2lvbiBub24tcHVyY2hhc2UuIGNsYXNzaWZpZXIgcHJlZGljdGVkIHRoYXQgdGhlIGhpZ2hlciBhZ2UgdGhlIG1vcmUgZXN0aW1hdGVkIHNhbGFyeSBhbmQgdG8gIHB1cmNoYXNlZCBpdGVtLiBDbGFzc2lmaWVyIGlzIHN0cmFpZ2h0IGxpbmUgZm9yIGxpbmVhciBtb2RlbHMuIEZvY3VzIG9uIHRoZSBkb3QgY29sb3IgYW5kIHRoZSB6b25lIHRoZXkgZmFsbC4gCgoKCiMjIEstTmVhcmVzdCBOZWlnaGJvciAKCktOTiBwcm9jZXNzOgoKMS4gY2hvb3NlIHRoZSBudW1iZXIgayBvZiBuZWlnaGJvcnMKMi4gdGFrZSB0aGUgS05OcyBvZiB0aGUgbmV3IGRhdGEgcG9pbnQsIGFjY29yZGluZyB0byBFdWNsaWRlYW4gZGlzdGFuY2UKMy4gYW1vbmcgdGhlc2UgS05OcywgY291bnQgdGhlIG51bWJlciBvZiBkYXRhIHBvaW50cyBpbiBlYWNoIGNhdGVnb3J5CjQuIGFzc2lnbiB0aGUgbmV3IGRhdGEgcG9pbnQgdG8gdGhlIGNhdGVnb3J5IHdoZXJlIHlvdSBjb3VudGVkIHRoZSBtb3N0IG5laWdoYm9ycwo1LiBtb2RlbCBpcyBkb25lCgpgYGB7ciBLTk4gQ2xhc3NpZmllcn0KCiMgSW1wb3J0aW5nIHRoZSBkYXRhc2V0CmRhdGFzZXQgPSByZWFkLmNzdignLi9QYXJ0IDMgLSBDbGFzc2lmaWNhdGlvbi9TZWN0aW9uIDE1IC0gSy1OZWFyZXN0IE5laWdoYm9ycyAoSy1OTikvUi9Tb2NpYWxfTmV0d29ya19BZHMuY3N2JykKCmRhdGFzZXQgPSBkYXRhc2V0WzM6NV0KCiMgRW5jb2RpbmcgdGhlIHRhcmdldCBmZWF0dXJlIGFzIGZhY3RvcgpkYXRhc2V0JFB1cmNoYXNlZCA9IGZhY3RvcihkYXRhc2V0JFB1cmNoYXNlZCwgbGV2ZWxzID0gYygwLCAxKSkKCiMgU3BsaXR0aW5nIHRoZSBkYXRhc2V0IGludG8gdGhlIFRyYWluaW5nIHNldCBhbmQgVGVzdCBzZXQKIyBpbnN0YWxsLnBhY2thZ2VzKCdjYVRvb2xzJykKbGlicmFyeShjYVRvb2xzKQpzZXQuc2VlZCgxMjMpCnNwbGl0ID0gc2FtcGxlLnNwbGl0KGRhdGFzZXQkUHVyY2hhc2VkLCBTcGxpdFJhdGlvID0gMC43NSkKdHJhaW5pbmdfc2V0ID0gc3Vic2V0KGRhdGFzZXQsIHNwbGl0ID09IFRSVUUpCnRlc3Rfc2V0ID0gc3Vic2V0KGRhdGFzZXQsIHNwbGl0ID09IEZBTFNFKQoKIyBGZWF0dXJlIFNjYWxpbmcKdHJhaW5pbmdfc2V0Wy0zXSA9IHNjYWxlKHRyYWluaW5nX3NldFstM10pCnRlc3Rfc2V0Wy0zXSA9IHNjYWxlKHRlc3Rfc2V0Wy0zXSkKCiMgRml0dGluZyBjbGFzc2lmaWVyIHRvIHRoZSBUcmFpbmluZyBzZXQKIyBDcmVhdGUgeW91ciBjbGFzc2lmaWVyIGhlcmUKCiMgYnVpbGQgYSBLTk4gY2xhc3NpZmllcgpsaWJyYXJ5KGNsYXNzKQojIGZpdCBhIEtOTiB0byBUcmFpbmluZyBzZXQgYW5kIFByZWRpY3QgdGhlIFRlc3Qgc2V0CiMgcmVtb3ZlIGxhc3QgY29sdW1uIG9mIHRyYWluaW5nIHNldAp5X3ByZWQuS05OID0ga25uKHRyYWluPSB0cmFpbmluZ19zZXRbLC0zXSwKICAgICAgICAgICAgICAgICB0ZXN0PSB0ZXN0X3NldFssIC0zXSwKICAgICAgICAgICAgICAgICBjbD0gdHJhaW5pbmdfc2V0WywgM10sCiAgICAgICAgICAgICAgICAgaz0gNSkgCgojIHlfcHJlZC5LTk5bMTo1XQoKCiMgUHJlZGljdGluZyB0aGUgVGVzdCBzZXQgcmVzdWx0cwojIGZvciBLTk4gY29tbWVudCBvdXQgCiMgeV9wcmVkID0gcHJlZGljdChjbGFzc2lmaWVyLCBuZXdkYXRhID0gdGVzdF9zZXRbLTNdKQoKIyBNYWtpbmcgdGhlIENvbmZ1c2lvbiBNYXRyaXgKY20gPSB0YWJsZSh0ZXN0X3NldFssIDNdLCB5X3ByZWQuS05OKQpjbQoKIz09PT09PT09PT09PSBWaXN1YWxpemluZyB0aGUgVHJhaW5pbmcgc2V0IHJlc3VsdHMKbGlicmFyeShFbGVtU3RhdExlYXJuKQpzZXQgPSB0cmFpbmluZ19zZXQKClgxID0gc2VxKG1pbihzZXRbLCAxXSkgLSAxLCBtYXgoc2V0WywgMV0pICsgMSwgYnkgPSAwLjAxKQpYMiA9IHNlcShtaW4oc2V0WywgMl0pIC0gMSwgbWF4KHNldFssIDJdKSArIDEsIGJ5ID0gMC4wMSkKCmdyaWRfc2V0ID0gZXhwYW5kLmdyaWQoWDEsIFgyKQoKY29sbmFtZXMoZ3JpZF9zZXQpID0gYygnQWdlJywgJ0VzdGltYXRlZFNhbGFyeScpCgojIHlfZ3JpZCA9IHByZWRpY3QoY2xhc3NpZmllciwgbmV3ZGF0YSA9IGdyaWRfc2V0KQoKIyBmb3IgS05OIHJlcGxhY2UgdGhlIHByZWRpY3QgYW5kIGl0cyBhcmd1bWVudHMgd2l0aCBLTk4gYXJndW1lbnRzCnlfZ3JpZCA9IGtubih0cmFpbj0gdHJhaW5pbmdfc2V0WywtM10sCiAgICAgICAgICAgICAgICAgdGVzdD0gZ3JpZF9zZXQsICMgcmVwbGFjZSB0ZXN0X3NldAogICAgICAgICAgICAgICAgIGNsPSB0cmFpbmluZ19zZXRbLCAzXSwKICAgICAgICAgICAgICAgICBrPSA1KQoKcGxvdChzZXRbLCAtM10sCiAgICAgbWFpbiA9ICdLTk4gQ2xhc3NpZmllciAoVHJhaW5pbmcgc2V0KScsCiAgICAgeGxhYiA9ICdBZ2UnLCB5bGFiID0gJ0VzdGltYXRlZCBTYWxhcnknLAogICAgIHhsaW0gPSByYW5nZShYMSksIHlsaW0gPSByYW5nZShYMikpCgpjb250b3VyKFgxLCBYMiwgbWF0cml4KGFzLm51bWVyaWMoeV9ncmlkKSwgbGVuZ3RoKFgxKSwgbGVuZ3RoKFgyKSksIGFkZCA9IFRSVUUpCnBvaW50cyhncmlkX3NldCwgcGNoID0gJy4nLCBjb2wgPSBpZmVsc2UoeV9ncmlkID09IDEsICdzcHJpbmdncmVlbjMnLCAndG9tYXRvJykpCnBvaW50cyhzZXQsIHBjaCA9IDIxLCBiZyA9IGlmZWxzZShzZXRbLCAzXSA9PSAxLCAnZ3JlZW40JywgJ3JlZDMnKSkKCiMgVmlzdWFsaXNpbmcgdGhlIFRlc3Qgc2V0IHJlc3VsdHMKbGlicmFyeShFbGVtU3RhdExlYXJuKQpzZXQgPSB0ZXN0X3NldApYMSA9IHNlcShtaW4oc2V0WywgMV0pIC0gMSwgbWF4KHNldFssIDFdKSArIDEsIGJ5ID0gMC4wMSkKWDIgPSBzZXEobWluKHNldFssIDJdKSAtIDEsIG1heChzZXRbLCAyXSkgKyAxLCBieSA9IDAuMDEpCmdyaWRfc2V0ID0gZXhwYW5kLmdyaWQoWDEsIFgyKQpjb2xuYW1lcyhncmlkX3NldCkgPSBjKCdBZ2UnLCAnRXN0aW1hdGVkU2FsYXJ5JykKCiMgZm9yIEtOTiByZXBsYWNlIHRoZSBwcmVkaWN0IGFuZCBpdHMgYXJndW1lbnRzIHdpdGggS05OIGFyZ3VtZW50cwp5X2dyaWQgPSBrbm4odHJhaW49IHRyYWluaW5nX3NldFssLTNdLAogICAgICAgICAgICAgICAgIHRlc3Q9IGdyaWRfc2V0LCAjIHJlcGxhY2UgdGVzdF9zZXQKICAgICAgICAgICAgICAgICBjbD0gdHJhaW5pbmdfc2V0WywgM10sCiAgICAgICAgICAgICAgICAgaz0gNSkKIyB5X2dyaWQgPSBwcmVkaWN0KGNsYXNzaWZpZXIsIG5ld2RhdGEgPSBncmlkX3NldCkKCnBsb3Qoc2V0WywgLTNdLCBtYWluID0gJ0tOTiBDbGFzc2lmaWVyIChUZXN0IHNldCknLAogICAgIHhsYWIgPSAnQWdlJywgeWxhYiA9ICdFc3RpbWF0ZWQgU2FsYXJ5JywKICAgICB4bGltID0gcmFuZ2UoWDEpLCB5bGltID0gcmFuZ2UoWDIpKQpjb250b3VyKFgxLCBYMiwgbWF0cml4KGFzLm51bWVyaWMoeV9ncmlkKSwgbGVuZ3RoKFgxKSwgbGVuZ3RoKFgyKSksIGFkZCA9IFRSVUUpCnBvaW50cyhncmlkX3NldCwgcGNoID0gJy4nLCBjb2wgPSBpZmVsc2UoeV9ncmlkID09IDEsICdzcHJpbmdncmVlbjMnLCAndG9tYXRvJykpCnBvaW50cyhzZXQsIHBjaCA9IDIxLCBiZyA9IGlmZWxzZShzZXRbLCAzXSA9PSAxLCAnZ3JlZW40JywgJ3JlZDMnKSkKCgoKYGBgCgoKIyMgU3VwcG9ydCBWZWN0b3IgTWFjaGluZXMKU3RhcnRlZCBpbiAxOTYwcyBhbmQgMTk5MHMuIFNlcGFyYXRlIGRhdGFwb2ludHMgb24gYSBwbG90IGFuZCBjbGFzc2lmeSB0aGVtLiBHb2FsOiBmaW5kIGJlc3QgZGVjaXNpb24gYm91bmRhcnkgdXNpbmcgYSBtYXggbWFyZ2luIGh5cGVycGxhbmUgbGluZSB0aGF0IGhhcyBhIG1heCBtYXJnaW4gKGRpc3RhbmNlIGF3YXkgZnJvbSBsaW5lIGFuZCBkaXN0YW5jZSBiZXR3ZWVuIG1heCBtYXJnaW4gbGluZXMpIGFuZCBkYXRhcG9pbnRzIG91dHNpZGUgdGhlIG1heCBtYXJnaW4gbGluZXMgYXJlIHBvc2l0aXZlIG9yIG5lZ2F0aXZlIGh5cGVycGxhbmUuIAoKY2xhc3NpZnkgYXBwbGVzIGFuZCBvcmFuZ2VzLCB0cmFpbmluZyBvbiBiZXN0IGV4YW1wbGVzIG9mIGVhY2ggZnJ1aXQKCmBgYHtyIFNWTSBDbGFzc2lmaWNhdGlvbn0KCiMgSW1wb3J0aW5nIHRoZSBkYXRhc2V0CmRhdGFzZXQgPSByZWFkLmNzdignLi9QYXJ0IDMgLSBDbGFzc2lmaWNhdGlvbi9TZWN0aW9uIDE2IC0gU3VwcG9ydCBWZWN0b3IgTWFjaGluZSAoU1ZNKS9SL1NvY2lhbF9OZXR3b3JrX0Fkcy5jc3YnKQoKZGF0YXNldCA9IGRhdGFzZXRbMzo1XQoKIyBFbmNvZGluZyB0aGUgdGFyZ2V0IGZlYXR1cmUgYXMgZmFjdG9yCmRhdGFzZXQkUHVyY2hhc2VkID0gZmFjdG9yKGRhdGFzZXQkUHVyY2hhc2VkLCBsZXZlbHMgPSBjKDAsIDEpKQoKIyBTcGxpdHRpbmcgdGhlIGRhdGFzZXQgaW50byB0aGUgVHJhaW5pbmcgc2V0IGFuZCBUZXN0IHNldAojIGluc3RhbGwucGFja2FnZXMoJ2NhVG9vbHMnKQpsaWJyYXJ5KGNhVG9vbHMpCgpzZXQuc2VlZCgxMjMpCgpzcGxpdCA9IHNhbXBsZS5zcGxpdChkYXRhc2V0JFB1cmNoYXNlZCwgU3BsaXRSYXRpbyA9IDAuNzUpCnRyYWluaW5nX3NldCA9IHN1YnNldChkYXRhc2V0LCBzcGxpdCA9PSBUUlVFKQp0ZXN0X3NldCA9IHN1YnNldChkYXRhc2V0LCBzcGxpdCA9PSBGQUxTRSkKCiMgRmVhdHVyZSBTY2FsaW5nCnRyYWluaW5nX3NldFstM10gPSBzY2FsZSh0cmFpbmluZ19zZXRbLTNdKQp0ZXN0X3NldFstM10gPSBzY2FsZSh0ZXN0X3NldFstM10pCgojIEZpdHRpbmcgY2xhc3NpZmllciB0byB0aGUgVHJhaW5pbmcgc2V0CiMgQ3JlYXRlIHlvdXIgY2xhc3NpZmllciBoZXJlCgojIGxpYnJhcnkgZTEwNzEgZm9yIFNWTQpsaWJyYXJ5KGUxMDcxKQoKIyByZWFkIHRoZSBkb2N1bWVudGF0aW9uCmNsYXNzaWZpZXIuU1ZNID0gc3ZtKGZvcm11bGE9IFB1cmNoYXNlZCB+IC4sCiAgICAgICAgICAgICAgICAgICAgIGRhdGE9IHRyYWluaW5nX3NldCwKICAgICAgICAgICAgICAgICAgICAgdHlwZT0gJ0MtY2xhc3NpZmljYXRpb24nLCAjIGNsYXNzaWZpY2F0aW9uCiAgICAgICAgICAgICAgICAgICAgIGtlcm5lbD0gJ2xpbmVhcicKICAgICAgICAgICAgICAgICAgICAgKQoKCiMgUHJlZGljdGluZyB0aGUgVGVzdCBzZXQgcmVzdWx0cwp5X3ByZWQuU1ZNID0gcHJlZGljdChjbGFzc2lmaWVyLlNWTSwgbmV3ZGF0YSA9IHRlc3Rfc2V0Wy0zXSkKCiMgTWFraW5nIHRoZSBDb25mdXNpb24gTWF0cml4CmNtID0gdGFibGUodGVzdF9zZXRbLCAzXSwgeV9wcmVkLlNWTSkKY20KCgoKIz09PT09PT09PT09PT0gVmlzdWFsaXppbmcgdGhlIFRyYWluaW5nIHNldCByZXN1bHRzCmxpYnJhcnkoRWxlbVN0YXRMZWFybikKc2V0ID0gdHJhaW5pbmdfc2V0ClgxID0gc2VxKG1pbihzZXRbLCAxXSkgLSAxLCBtYXgoc2V0WywgMV0pICsgMSwgYnkgPSAwLjAxKQpYMiA9IHNlcShtaW4oc2V0WywgMl0pIC0gMSwgbWF4KHNldFssIDJdKSArIDEsIGJ5ID0gMC4wMSkKCmdyaWRfc2V0ID0gZXhwYW5kLmdyaWQoWDEsIFgyKQoKY29sbmFtZXMoZ3JpZF9zZXQpID0gYygnQWdlJywgJ0VzdGltYXRlZFNhbGFyeScpCgp5X2dyaWQgPSBwcmVkaWN0KGNsYXNzaWZpZXIuU1ZNLCBuZXdkYXRhID0gZ3JpZF9zZXQpCnBsb3Qoc2V0WywgLTNdLAogICAgIG1haW4gPSAnU1ZNIENsYXNzaWZpZXIgKFRyYWluaW5nIHNldCknLAogICAgIHhsYWIgPSAnQWdlJywgCiAgICAgeWxhYiA9ICdFc3RpbWF0ZWQgU2FsYXJ5JywKICAgICB4bGltID0gcmFuZ2UoWDEpLCB5bGltID0gcmFuZ2UoWDIpKQoKY29udG91cihYMSwgWDIsIG1hdHJpeChhcy5udW1lcmljKHlfZ3JpZCksIGxlbmd0aChYMSksIGxlbmd0aChYMikpLCBhZGQgPSBUUlVFKQpwb2ludHMoZ3JpZF9zZXQsIHBjaCA9ICcuJywgY29sID0gaWZlbHNlKHlfZ3JpZCA9PSAxLCAnc3ByaW5nZ3JlZW4zJywgJ3RvbWF0bycpKQpwb2ludHMoc2V0LCBwY2ggPSAyMSwgYmcgPSBpZmVsc2Uoc2V0WywgM10gPT0gMSwgJ2dyZWVuNCcsICdyZWQzJykpCgoKIz09PT09PT09PT09PSBWaXN1YWxpemluZyB0aGUgVGVzdCBzZXQgcmVzdWx0cwoKbGlicmFyeShFbGVtU3RhdExlYXJuKQoKc2V0ID0gdGVzdF9zZXQKClgxID0gc2VxKG1pbihzZXRbLCAxXSkgLSAxLCBtYXgoc2V0WywgMV0pICsgMSwgYnkgPSAwLjAxKQpYMiA9IHNlcShtaW4oc2V0WywgMl0pIC0gMSwgbWF4KHNldFssIDJdKSArIDEsIGJ5ID0gMC4wMSkKCmdyaWRfc2V0ID0gZXhwYW5kLmdyaWQoWDEsIFgyKQoKY29sbmFtZXMoZ3JpZF9zZXQpID0gYygnQWdlJywgJ0VzdGltYXRlZFNhbGFyeScpCgp5X2dyaWQgPSBwcmVkaWN0KGNsYXNzaWZpZXIuU1ZNLCAKICAgICAgICAgICAgICAgICBuZXdkYXRhID0gZ3JpZF9zZXQpCnBsb3Qoc2V0WywgLTNdLCBtYWluID0gJ1NWTSBDbGFzc2lmaWVyIChUZXN0IHNldCknLAogICAgIHhsYWIgPSAnQWdlJywgeWxhYiA9ICdFc3RpbWF0ZWQgU2FsYXJ5JywKICAgICB4bGltID0gcmFuZ2UoWDEpLCB5bGltID0gcmFuZ2UoWDIpKQoKY29udG91cihYMSwgWDIsIG1hdHJpeChhcy5udW1lcmljKHlfZ3JpZCksIGxlbmd0aChYMSksIGxlbmd0aChYMikpLCBhZGQgPSBUUlVFKQpwb2ludHMoZ3JpZF9zZXQsIHBjaCA9ICcuJywgY29sID0gaWZlbHNlKHlfZ3JpZCA9PSAxLCAnc3ByaW5nZ3JlZW4zJywgJ3RvbWF0bycpKQpwb2ludHMoc2V0LCBwY2ggPSAyMSwgYmcgPSBpZmVsc2Uoc2V0WywgM10gPT0gMSwgJ2dyZWVuNCcsICdyZWQzJykpCgoKYGBgICAgICAgICAgICAgIAogICAgICAgICAgICAgIAptYXBwaW5nIGRhdGFwb2ludHMgdG8gYSBIaWdoZXIgZGltZW5zaW9uYWwgc2hhcGUgc2VwYXJhdGVzIHBvaW50cyBieSBtYXBwaW5nIHRoZSBwb2ludHMgdG8gYSBhbGdlYnJhaWMgZnVuY3Rpb24gdGhlbiB0byBoYXZlIGEgaHlwZXJwbGFuZSBhIHNlcGFyYXRvciwgYnV0IHRoaXMgaXMgdmVyeSBjb21wdXRlciBpbnRlbnNpdmUgYW5kIG5vdCBwcmFjdGljYWwuCiAgICAgICAgICAgICAKICAgICAgICAgICAgCiMjICBLZXJuZWwgU1ZNCkRlY2lzaW9uIGJvdW5kYXJpZXMgZm9yIHdoZW4gZGF0YSBwb2ludHMgYXJlIGNsdXN0ZXJlZCBpbiBjaXJjbGVzIGFuZCBpcyBub3QgbGluZWFyLiAKR2F1c3NpYW4gUkJGIEtlcm5lbC4gCgpgYGB7ciBLZXJuZWwgU1ZNfQogICAgICAgICAgICAgIAogIyBJbXBvcnRpbmcgdGhlIGRhdGFzZXQKZGF0YXNldCA9IHJlYWQuY3N2KCcuL1BhcnQgMyAtIENsYXNzaWZpY2F0aW9uL1NlY3Rpb24gMTcgLSBLZXJuZWwgU1ZNL1IvU29jaWFsX05ldHdvcmtfQWRzLmNzdicpCmRhdGFzZXQgPSBkYXRhc2V0WzM6NV0KCiMgRW5jb2RpbmcgdGhlIHRhcmdldCBmZWF0dXJlIGFzIGZhY3RvcgpkYXRhc2V0JFB1cmNoYXNlZCA9IGZhY3RvcihkYXRhc2V0JFB1cmNoYXNlZCwgbGV2ZWxzID0gYygwLCAxKSkKCiMgU3BsaXR0aW5nIHRoZSBkYXRhc2V0IGludG8gdGhlIFRyYWluaW5nIHNldCBhbmQgVGVzdCBzZXQKIyBpbnN0YWxsLnBhY2thZ2VzKCdjYVRvb2xzJykKbGlicmFyeShjYVRvb2xzKQpzZXQuc2VlZCgxMjMpCnNwbGl0ID0gc2FtcGxlLnNwbGl0KGRhdGFzZXQkUHVyY2hhc2VkLCBTcGxpdFJhdGlvID0gMC43NSkKdHJhaW5pbmdfc2V0ID0gc3Vic2V0KGRhdGFzZXQsIHNwbGl0ID09IFRSVUUpCnRlc3Rfc2V0ID0gc3Vic2V0KGRhdGFzZXQsIHNwbGl0ID09IEZBTFNFKQoKIyBGZWF0dXJlIFNjYWxpbmcKdHJhaW5pbmdfc2V0Wy0zXSA9IHNjYWxlKHRyYWluaW5nX3NldFstM10pCnRlc3Rfc2V0Wy0zXSA9IHNjYWxlKHRlc3Rfc2V0Wy0zXSkKCgojIEZpdHRpbmcgY2xhc3NpZmllciB0byB0aGUgVHJhaW5pbmcgc2V0CiMgQ3JlYXRlIHlvdXIgY2xhc3NpZmllciBoZXJlCgojPT09PT09PT09PT0gS2VybmVsIFNWTQpsaWJyYXJ5KGUxMDcxKQoKIyBHYXVzc2lhbiBjbGFzc2lmaWVyLCByYWRpYWwKY2xhc3NpZmllci5TVk0gPSBzdm0oZm9ybXVsYT0gUHVyY2hhc2VkIH4gLiwKICAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW5pbmdfc2V0LAogICAgICAgICAgICAgICAgIHR5cGUgPSAiQy1jbGFzc2lmaWNhdGlvbiIsCiAgICAgICAgICAgICAgICAga2VybmVsPSAncmFkaWFsJykKCgojIFByZWRpY3RpbmcgdGhlIFRlc3Qgc2V0IHJlc3VsdHMKeV9wcmVkID0gcHJlZGljdChjbGFzc2lmaWVyLlNWTSwgbmV3ZGF0YSA9IHRlc3Rfc2V0Wy0zXSkKeV9wcmVkWzE6NV0KCiMgTWFraW5nIHRoZSBDb25mdXNpb24gTWF0cml4CmNtID0gdGFibGUodGVzdF9zZXRbLCAzXSwgeV9wcmVkKQpjbQoKCiM9PT09PT09PT09PT09PSBWaXN1YWxpemluZyB0aGUgVHJhaW5pbmcgc2V0IHJlc3VsdHMKbGlicmFyeShFbGVtU3RhdExlYXJuKQpzZXQgPSB0cmFpbmluZ19zZXQKWDEgPSBzZXEobWluKHNldFssIDFdKSAtIDEsIG1heChzZXRbLCAxXSkgKyAxLCBieSA9IDAuMDEpClgyID0gc2VxKG1pbihzZXRbLCAyXSkgLSAxLCBtYXgoc2V0WywgMl0pICsgMSwgYnkgPSAwLjAxKQpncmlkX3NldCA9IGV4cGFuZC5ncmlkKFgxLCBYMikKY29sbmFtZXMoZ3JpZF9zZXQpID0gYygnQWdlJywgJ0VzdGltYXRlZFNhbGFyeScpCnlfZ3JpZCA9IHByZWRpY3QoY2xhc3NpZmllci5TVk0sIG5ld2RhdGEgPSBncmlkX3NldCkKcGxvdChzZXRbLCAtM10sCiAgICAgbWFpbiA9ICdLZXJuZWwgU1ZNIENsYXNzaWZpZXIgKFRyYWluaW5nIHNldCknLAogICAgIHhsYWIgPSAnQWdlJywgeWxhYiA9ICdFc3RpbWF0ZWQgU2FsYXJ5JywKICAgICB4bGltID0gcmFuZ2UoWDEpLCB5bGltID0gcmFuZ2UoWDIpKQpjb250b3VyKFgxLCBYMiwgbWF0cml4KGFzLm51bWVyaWMoeV9ncmlkKSwgbGVuZ3RoKFgxKSwgbGVuZ3RoKFgyKSksIGFkZCA9IFRSVUUpCnBvaW50cyhncmlkX3NldCwgcGNoID0gJy4nLCBjb2wgPSBpZmVsc2UoeV9ncmlkID09IDEsICdzcHJpbmdncmVlbjMnLCAndG9tYXRvJykpCnBvaW50cyhzZXQsIHBjaCA9IDIxLCBiZyA9IGlmZWxzZShzZXRbLCAzXSA9PSAxLCAnZ3JlZW40JywgJ3JlZDMnKSkKCiM9PT09PT09PT09PT09PT09PT0gVmlzdWFsaXppbmcgdGhlIFRlc3Qgc2V0IHJlc3VsdHMKbGlicmFyeShFbGVtU3RhdExlYXJuKQpzZXQgPSB0ZXN0X3NldApYMSA9IHNlcShtaW4oc2V0WywgMV0pIC0gMSwgbWF4KHNldFssIDFdKSArIDEsIGJ5ID0gMC4wMSkKWDIgPSBzZXEobWluKHNldFssIDJdKSAtIDEsIG1heChzZXRbLCAyXSkgKyAxLCBieSA9IDAuMDEpCmdyaWRfc2V0ID0gZXhwYW5kLmdyaWQoWDEsIFgyKQpjb2xuYW1lcyhncmlkX3NldCkgPSBjKCdBZ2UnLCAnRXN0aW1hdGVkU2FsYXJ5JykKeV9ncmlkID0gcHJlZGljdChjbGFzc2lmaWVyLlNWTSwgbmV3ZGF0YSA9IGdyaWRfc2V0KQpwbG90KHNldFssIC0zXSwgbWFpbiA9ICdLZXJuZWwgU1ZNIENsYXNzaWZpZXIgKFRlc3Qgc2V0KScsCiAgICAgeGxhYiA9ICdBZ2UnLCB5bGFiID0gJ0VzdGltYXRlZCBTYWxhcnknLAogICAgIHhsaW0gPSByYW5nZShYMSksIHlsaW0gPSByYW5nZShYMikpCmNvbnRvdXIoWDEsIFgyLCBtYXRyaXgoYXMubnVtZXJpYyh5X2dyaWQpLCBsZW5ndGgoWDEpLCBsZW5ndGgoWDIpKSwgYWRkID0gVFJVRSkKcG9pbnRzKGdyaWRfc2V0LCBwY2ggPSAnLicsIGNvbCA9IGlmZWxzZSh5X2dyaWQgPT0gMSwgJ3NwcmluZ2dyZWVuMycsICd0b21hdG8nKSkKcG9pbnRzKHNldCwgcGNoID0gMjEsIGJnID0gaWZlbHNlKHNldFssIDNdID09IDEsICdncmVlbjQnLCAncmVkMycpKQoKYGBgCgpDb25mdXNpb24gbWF0cml4IHNob3dzIDEwIGluY29ycmVjdCByZXN1bHRzIGFuZCA5MCBjb3JyZWN0IHJlc3VsdHMgICAgICAgICAKICAgICAgICAgICAgICAKIFRoZSBTVk0gbWFwcGVkIGRhdGEgdG8gYSAzRCBwbGFuZSwgZ3JlZW4gem9uZSBpcyB1c2VycyB3aG8gcHVyY2hhc2VkIGl0ZW0gYW5kIHJlZCB6b25lIGlzIG5vIHB1cmNoYXNlZCBpdGVtLiAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgCiMjIE5haXZlIEJheWVzIFRoZW9yZW0gQ2xhc3NpZmljYXRpb24KQmF5ZXMgVGhlb3JlbSBvZiBwcm9iYWJpbGl0eSBmb3JtdWxhICRQKEF8QikkID0gJFAoQnxBKSAqIFAoQSkgXGRpdiBQKEIpJCAKICAgICAgICAgICAKRXhhbXBsZTogbWFjaGluZS4xIG1ha2VzIDMwIGl0ZW1zL2hyIGFuZCBtYWNoaW5lLjIgbWFrZXMgMjAgaXRlbXMvaHIuIE91dCBvZiBhbGwgaXRlbXMgbWFkZSwgMSAlIGlzIGRlZmVjdGl2ZSwgYW5kIG91dCBvZiBhbGwgZGVmZWN0aXZlIGl0ZW1zIDUwJSBjYW1lIGZyb20gbWFjaGluZS4xIGFuZCA1MCUgZnJvbSBtYWNoaW5lLjIuICpXaGF0IGlzIHRoZSBwcm9iYWJpbGl0eSB0aGF0IGEgaXRlbSBtYWRlIGJ5IG1hY2hpbmUuMiBpcyBkZWZlY3RpdmU/KiAgICAgICAgIAoKLSBQKE1hY2hpbmUuMikgPSAyMC81MAotIFAoRGVmZWN0KSA9IDElCi0gUChNYWNoaW5lLjIgfCBEZWZlY3QpPSA1MCUKLSBQKERlZmVjdCB8IE1hY2hpbmUuMikgPSA/CgpQKERlZmVjdCB8IE1hY2hpbmUuMikgPSBQKE1hY2hpbmUuMiB8IERlZmVjdCkgKiBQKERlZmVjdCkgIC8gUChNYWNoaW5lLjIpClAoRGVmZWN0IHwgTWFjaGluZS4yKSA9IDAuNSAqIDAuMDEgLyAwLjQgPT0gMC4wMTI1ICgxLjI1JSkgICAgICAgICAgICAKCkV4YW1wbGUgMiBOYWl2ZSBCYXllcyAoYXNzdW1lZCBpbmRlcGVuZGVuY2Ugb2YgdmFyaWFibGVzOgp4PSBhZ2UsIHk9IHNhbGFyeSwgZGF0YXBvaW50cyBncm91cGVkOiB3YWxrcyB0byB3b3JrIG9yIGRyaXZlcyB0byB3b3JrLiBYPSBmZWF0dXJlcwpQKFdhbGtzIHwgWCkgPSBQKFggfCBXYWxrcykgKiBQKFdhbGtzKSAvIFAoWCkgICAgICAgICAKUChEcml2ZXMgfCBYKSA9IFAoWCB8IERyaXZlcykgKiBQKERyaXZlcykgLyBQKFgpICAgICAKCnJlcGVhdCBzdGVwcyBmb3IgYm90aCBXYWxrcyBhbmQgRHJpdmVzIGNsYXNzOgoKMS4gcHJpb3IgcHJvYmFiaWxpdHk6IFAoV2Fsa3MpCjIuIG1hcmdpbmFsIGxpa2VsaWhvb2Q6IFAoWCkKMy4gbGlrZWxpaG9vZDogUChYIHwgV2Fsa3MpCjQuIHBvc3RlcmlvciBwcm9iYWJpbGl0eTogUChXYWxrcyB8IFgpCiAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgCnN0ZXAgMTogd2UgaGF2ZSBubyBkYXRhLCBzbyBjYWxjdWxhdGUgdGhlIHBvaW50cyBpbiB0aGUgd2Fsa3MgZ3JvdXAgZnJvbSBhIHBsb3QuIAotIFAoV2Fsa3MpICA9IG51bWJlciBvZiB3YWxrZXJzIC8gdG90YWwgZGF0YXBvaW50cwogIApzdGVwIDI6IHNlbGVjdCBhIHJhZGl1cyBvbiBhIHBsb3QsIGEgY2lyY2xlIHRvIGNvbnRhaW4gZGF0YXBvaW50cywgbG9vayBhdCBmZWF0dXJlcyAoYWdlIGFuZCBzYWxhcnkpIGFuZCB0aGVzZSBwb2ludHMgd2lsbCBiZSBzaW1pbGFyLiB0aGUgcHJvYmFiaWxpdHkgb2YgYSBuZXcgZGF0YXBvaW50IHdvdWxkIGZhbGwgaW50byB0aGlzIHJhZGl1cy4gQ291bnQgdGhlIG51bWJlciBvZiBwb2ludHMgaW5zaWRlIGNpcmNsZS4KLSBQKFgpID0gbnVtYmVyIG9mIG9ic2VydmF0aW9ucyAvIHRvdGFsIG9ic2VydmF0aW9ucyAKICAKc3RlcCAzOiB1c2UgdGhlIHJhZGl1cyBjaXJjbGUgZm9yIHNpbWlsYXIgZmVhdHVyZXMgb2YgZGF0YXBvaW50cywgd2hhdCBpcyB0aGUgbGlrZWxpaG9vZCBvZiBmZWF0dXJlcyBvZiB3YWxrZXJzIGdpdmVuIHRoYXQgcGVyc29uIHdobyB3YWxrcyAoaWdub3JlIHRoZSBkcml2ZXJzKS4gQ291bnQgdGhlIG51bWJlciBvZiBkYXRhcG9pbnRzIGZvciB3YWxrZXJzIGluc2lkZSB0aGUgY2lyY2xlLiAKLSBQKFggfCBXYWxrZXJzKSA9IG51bWJlciBvZiBzaW1pbGFyIHBvaW50cyBmb3Igd2Fsa2VycyAvIHRvdGFsIG51bWJlciBvZiB3YWxrZXJzCiAgCnN0ZXAgNDogUChXYWxrcyB8IFgpID0gKDMvMTApICogKDEwLzMwKSAvICg0LzMwKSA9PSAwLjc1ICAgICg3NSUgbGlrZWxpaG9vZCBvZiBkYXRhcG9pbnQgYmVpbmcgYSBXYWxrZXIpICAgICAgIAogICAgICAgICAgICAgIApyZXBlYXQgZm9yIGRyaXZlciAgICAgICAgICAgCnN0ZXAgNDogUChEcml2ZXMgfCBYKT0gKDEvMjApICogKDIwLzMwKSAvICg0LzMwKSA9IDAuMjUgICgyNSUpICAgICAgICAgICAgIAogICAgICAgICAgICAgIApgYGB7ciBCYXllcyBUaGVvcmVtfSAgICAgICAgICAgIAogICAgICAgICAgICAgIAojIEltcG9ydGluZyB0aGUgZGF0YXNldApkYXRhc2V0ID0gcmVhZC5jc3YoJy4vUGFydCAzIC0gQ2xhc3NpZmljYXRpb24vU2VjdGlvbiAxOCAtIE5haXZlIEJheWVzL1IvU29jaWFsX05ldHdvcmtfQWRzLmNzdicpCmRhdGFzZXQgPSBkYXRhc2V0WzM6NV0KCiMgRW5jb2RpbmcgdGhlIHRhcmdldCBmZWF0dXJlIGFzIGZhY3RvcgpkYXRhc2V0JFB1cmNoYXNlZCA9IGZhY3RvcihkYXRhc2V0JFB1cmNoYXNlZCwgbGV2ZWxzID0gYygwLCAxKSkKCiMgU3BsaXR0aW5nIHRoZSBkYXRhc2V0IGludG8gdGhlIFRyYWluaW5nIHNldCBhbmQgVGVzdCBzZXQKIyBpbnN0YWxsLnBhY2thZ2VzKCdjYVRvb2xzJykKbGlicmFyeShjYVRvb2xzKQpzZXQuc2VlZCgxMjMpCnNwbGl0ID0gc2FtcGxlLnNwbGl0KGRhdGFzZXQkUHVyY2hhc2VkLCBTcGxpdFJhdGlvID0gMC43NSkKdHJhaW5pbmdfc2V0ID0gc3Vic2V0KGRhdGFzZXQsIHNwbGl0ID09IFRSVUUpCnRlc3Rfc2V0ID0gc3Vic2V0KGRhdGFzZXQsIHNwbGl0ID09IEZBTFNFKQoKIyBGZWF0dXJlIFNjYWxpbmcKdHJhaW5pbmdfc2V0Wy0zXSA9IHNjYWxlKHRyYWluaW5nX3NldFstM10pCnRlc3Rfc2V0Wy0zXSA9IHNjYWxlKHRlc3Rfc2V0Wy0zXSkKCiMgRml0dGluZyBjbGFzc2lmaWVyIHRvIHRoZSBUcmFpbmluZyBzZXQKIyBDcmVhdGUgeW91ciBjbGFzc2lmaWVyIGhlcmUKCiMgPT09IEJheWVzIENsYXNzaWZpZXIKbGlicmFyeShlMTA3MSkKCiMgcHJlc3MgRjEgZm9yIGRvY3VtZW50YXRpb24gd2hlbiBtb3VzZSBpcyBvbiBmdW5jdGlvbiBuYW1lCmNsYXNzaWZpZXIuQmF5ZXMgPSBuYWl2ZUJheWVzKHg9IHRyYWluaW5nX3NldFstM10sCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHk9IHRyYWluaW5nX3NldCRQdXJjaGFzZWQpCgoKCiMgUHJlZGljdGluZyB0aGUgVGVzdCBzZXQgcmVzdWx0cwp5X3ByZWQgPSBwcmVkaWN0KGNsYXNzaWZpZXIuQmF5ZXMsIG5ld2RhdGEgPSB0ZXN0X3NldFstM10pCgojIE1ha2luZyB0aGUgQ29uZnVzaW9uIE1hdHJpeApjbSA9IHRhYmxlKHRlc3Rfc2V0WywgM10sIHlfcHJlZCkKCiM9PT09PT09PT09PT09PSBWaXN1YWxpemluZyB0aGUgVHJhaW5pbmcgc2V0IHJlc3VsdHMKbGlicmFyeShFbGVtU3RhdExlYXJuKQpzZXQgPSB0cmFpbmluZ19zZXQKWDEgPSBzZXEobWluKHNldFssIDFdKSAtIDEsIG1heChzZXRbLCAxXSkgKyAxLCBieSA9IDAuMDEpClgyID0gc2VxKG1pbihzZXRbLCAyXSkgLSAxLCBtYXgoc2V0WywgMl0pICsgMSwgYnkgPSAwLjAxKQpncmlkX3NldCA9IGV4cGFuZC5ncmlkKFgxLCBYMikKY29sbmFtZXMoZ3JpZF9zZXQpID0gYygnQWdlJywgJ0VzdGltYXRlZFNhbGFyeScpCnlfZ3JpZCA9IHByZWRpY3QoY2xhc3NpZmllci5CYXllcywgbmV3ZGF0YSA9IGdyaWRfc2V0KQpwbG90KHNldFssIC0zXSwKICAgICBtYWluID0gJ05haXZlIEJheWVzIENsYXNzaWZpZXIgKFRyYWluaW5nIHNldCknLAogICAgIHhsYWIgPSAnQWdlJywgeWxhYiA9ICdFc3RpbWF0ZWQgU2FsYXJ5JywKICAgICB4bGltID0gcmFuZ2UoWDEpLCB5bGltID0gcmFuZ2UoWDIpKQpjb250b3VyKFgxLCBYMiwgbWF0cml4KGFzLm51bWVyaWMoeV9ncmlkKSwgbGVuZ3RoKFgxKSwgbGVuZ3RoKFgyKSksIGFkZCA9IFRSVUUpCnBvaW50cyhncmlkX3NldCwgcGNoID0gJy4nLCBjb2wgPSBpZmVsc2UoeV9ncmlkID09IDEsICdzcHJpbmdncmVlbjMnLCAndG9tYXRvJykpCnBvaW50cyhzZXQsIHBjaCA9IDIxLCBiZyA9IGlmZWxzZShzZXRbLCAzXSA9PSAxLCAnZ3JlZW40JywgJ3JlZDMnKSkKCiM9PT09PT09PT09IFZpc3VhbGl6aW5nIHRoZSBUZXN0IHNldCByZXN1bHRzCmxpYnJhcnkoRWxlbVN0YXRMZWFybikKc2V0ID0gdGVzdF9zZXQKWDEgPSBzZXEobWluKHNldFssIDFdKSAtIDEsIG1heChzZXRbLCAxXSkgKyAxLCBieSA9IDAuMDEpClgyID0gc2VxKG1pbihzZXRbLCAyXSkgLSAxLCBtYXgoc2V0WywgMl0pICsgMSwgYnkgPSAwLjAxKQpncmlkX3NldCA9IGV4cGFuZC5ncmlkKFgxLCBYMikKY29sbmFtZXMoZ3JpZF9zZXQpID0gYygnQWdlJywgJ0VzdGltYXRlZFNhbGFyeScpCnlfZ3JpZCA9IHByZWRpY3QoY2xhc3NpZmllci5CYXllcywgbmV3ZGF0YSA9IGdyaWRfc2V0KQpwbG90KHNldFssIC0zXSwgbWFpbiA9ICdOYWl2ZSBCYXllcyBDbGFzc2lmaWVyIChUZXN0IHNldCknLAogICAgIHhsYWIgPSAnQWdlJywgeWxhYiA9ICdFc3RpbWF0ZWQgU2FsYXJ5JywKICAgICB4bGltID0gcmFuZ2UoWDEpLCB5bGltID0gcmFuZ2UoWDIpKQpjb250b3VyKFgxLCBYMiwgbWF0cml4KGFzLm51bWVyaWMoeV9ncmlkKSwgbGVuZ3RoKFgxKSwgbGVuZ3RoKFgyKSksIGFkZCA9IFRSVUUpCnBvaW50cyhncmlkX3NldCwgcGNoID0gJy4nLCBjb2wgPSBpZmVsc2UoeV9ncmlkID09IDEsICdzcHJpbmdncmVlbjMnLCAndG9tYXRvJykpCnBvaW50cyhzZXQsIHBjaCA9IDIxLCBiZyA9IGlmZWxzZShzZXRbLCAzXSA9PSAxLCAnZ3JlZW40JywgJ3JlZDMnKSkgICAgICAgICAgICAKICAgICAgICAgICAgICAKICAgICAgICAgICAgICAKYGBgICAgICAgICAgCiAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgCiMjIERlY2lzaW9uIFRyZWUgQ2xhc3NpZmljYXRpb24gICAgICAgICAgICAgIApgYGB7ciBEZWNpc2lvbiBUcmVlfSAgICAgICAgICAKCiMgSW1wb3J0aW5nIHRoZSBkYXRhc2V0CmRhdGFzZXQgPSByZWFkLmNzdignLi9QYXJ0IDMgLSBDbGFzc2lmaWNhdGlvbi9TZWN0aW9uIDE5IC0gRGVjaXNpb24gVHJlZSBDbGFzc2lmaWNhdGlvbi9SL1NvY2lhbF9OZXR3b3JrX0Fkcy5jc3YnKQoKZGF0YXNldCA9IGRhdGFzZXRbMzo1XQoKIyBFbmNvZGluZyB0aGUgdGFyZ2V0IGZlYXR1cmUgYXMgZmFjdG9yCmRhdGFzZXQkUHVyY2hhc2VkID0gZmFjdG9yKGRhdGFzZXQkUHVyY2hhc2VkLCBsZXZlbHMgPSBjKDAsIDEpKQoKIyBTcGxpdHRpbmcgdGhlIGRhdGFzZXQgaW50byB0aGUgVHJhaW5pbmcgc2V0IGFuZCBUZXN0IHNldAojIGluc3RhbGwucGFja2FnZXMoJ2NhVG9vbHMnKQpsaWJyYXJ5KGNhVG9vbHMpCnNldC5zZWVkKDEyMykKc3BsaXQgPSBzYW1wbGUuc3BsaXQoZGF0YXNldCRQdXJjaGFzZWQsIFNwbGl0UmF0aW8gPSAwLjc1KQp0cmFpbmluZ19zZXQgPSBzdWJzZXQoZGF0YXNldCwgc3BsaXQgPT0gVFJVRSkKdGVzdF9zZXQgPSBzdWJzZXQoZGF0YXNldCwgc3BsaXQgPT0gRkFMU0UpCgojIEZlYXR1cmUgU2NhbGluZwp0cmFpbmluZ19zZXRbLTNdID0gc2NhbGUodHJhaW5pbmdfc2V0Wy0zXSkKdGVzdF9zZXRbLTNdID0gc2NhbGUodGVzdF9zZXRbLTNdKQoKIz09PT09PT09PT09IEZpdHRpbmcgRGVjaXNpb24gVHJlZSBDbGFzc2lmaWNhdGlvbiB0byB0aGUgVHJhaW5pbmcgc2V0CiMgaW5zdGFsbC5wYWNrYWdlcygncnBhcnQnKQpsaWJyYXJ5KHJwYXJ0KQpjbGFzc2lmaWVyID0gcnBhcnQoZm9ybXVsYSA9IFB1cmNoYXNlZCB+IC4sCiAgICAgICAgICAgICAgICAgICBkYXRhID0gdHJhaW5pbmdfc2V0KQoKIyBQcmVkaWN0aW5nIHRoZSBUZXN0IHNldCByZXN1bHRzCnlfcHJlZCA9IHByZWRpY3QoY2xhc3NpZmllciwgbmV3ZGF0YSA9IHRlc3Rfc2V0Wy0zXSwgdHlwZSA9ICdjbGFzcycpCgojIE1ha2luZyB0aGUgQ29uZnVzaW9uIE1hdHJpeApjbSA9IHRhYmxlKHRlc3Rfc2V0WywgM10sIHlfcHJlZCkKCiMgPT09PT09PT09PSBWaXN1YWxpemluZyB0aGUgVHJhaW5pbmcgc2V0IHJlc3VsdHMKbGlicmFyeShFbGVtU3RhdExlYXJuKQpzZXQgPSB0cmFpbmluZ19zZXQKWDEgPSBzZXEobWluKHNldFssIDFdKSAtIDEsIG1heChzZXRbLCAxXSkgKyAxLCBieSA9IDAuMDEpClgyID0gc2VxKG1pbihzZXRbLCAyXSkgLSAxLCBtYXgoc2V0WywgMl0pICsgMSwgYnkgPSAwLjAxKQpncmlkX3NldCA9IGV4cGFuZC5ncmlkKFgxLCBYMikKY29sbmFtZXMoZ3JpZF9zZXQpID0gYygnQWdlJywgJ0VzdGltYXRlZFNhbGFyeScpCnlfZ3JpZCA9IHByZWRpY3QoY2xhc3NpZmllciwgbmV3ZGF0YSA9IGdyaWRfc2V0LCB0eXBlID0gJ2NsYXNzJykKcGxvdChzZXRbLCAtM10sCiAgICAgbWFpbiA9ICdEZWNpc2lvbiBUcmVlIENsYXNzaWZpY2F0aW9uIChUcmFpbmluZyBzZXQpJywKICAgICB4bGFiID0gJ0FnZScsIHlsYWIgPSAnRXN0aW1hdGVkIFNhbGFyeScsCiAgICAgeGxpbSA9IHJhbmdlKFgxKSwgeWxpbSA9IHJhbmdlKFgyKSkKY29udG91cihYMSwgWDIsIG1hdHJpeChhcy5udW1lcmljKHlfZ3JpZCksIGxlbmd0aChYMSksIGxlbmd0aChYMikpLCBhZGQgPSBUUlVFKQpwb2ludHMoZ3JpZF9zZXQsIHBjaCA9ICcuJywgY29sID0gaWZlbHNlKHlfZ3JpZCA9PSAxLCAnc3ByaW5nZ3JlZW4zJywgJ3RvbWF0bycpKQpwb2ludHMoc2V0LCBwY2ggPSAyMSwgYmcgPSBpZmVsc2Uoc2V0WywgM10gPT0gMSwgJ2dyZWVuNCcsICdyZWQzJykpCgojPT09PT09PT09PT09IFZpc3VhbGl6aW5nIHRoZSBUZXN0IHNldCByZXN1bHRzCmxpYnJhcnkoRWxlbVN0YXRMZWFybikKc2V0ID0gdGVzdF9zZXQKWDEgPSBzZXEobWluKHNldFssIDFdKSAtIDEsIG1heChzZXRbLCAxXSkgKyAxLCBieSA9IDAuMDEpClgyID0gc2VxKG1pbihzZXRbLCAyXSkgLSAxLCBtYXgoc2V0WywgMl0pICsgMSwgYnkgPSAwLjAxKQpncmlkX3NldCA9IGV4cGFuZC5ncmlkKFgxLCBYMikKY29sbmFtZXMoZ3JpZF9zZXQpID0gYygnQWdlJywgJ0VzdGltYXRlZFNhbGFyeScpCnlfZ3JpZCA9IHByZWRpY3QoY2xhc3NpZmllciwgbmV3ZGF0YSA9IGdyaWRfc2V0LCB0eXBlID0gJ2NsYXNzJykKcGxvdChzZXRbLCAtM10sIG1haW4gPSAnRGVjaXNpb24gVHJlZSBDbGFzc2lmaWNhdGlvbiAoVGVzdCBzZXQpJywKICAgICB4bGFiID0gJ0FnZScsIHlsYWIgPSAnRXN0aW1hdGVkIFNhbGFyeScsCiAgICAgeGxpbSA9IHJhbmdlKFgxKSwgeWxpbSA9IHJhbmdlKFgyKSkKY29udG91cihYMSwgWDIsIG1hdHJpeChhcy5udW1lcmljKHlfZ3JpZCksIGxlbmd0aChYMSksIGxlbmd0aChYMikpLCBhZGQgPSBUUlVFKQpwb2ludHMoZ3JpZF9zZXQsIHBjaCA9ICcuJywgY29sID0gaWZlbHNlKHlfZ3JpZCA9PSAxLCAnc3ByaW5nZ3JlZW4zJywgJ3RvbWF0bycpKQpwb2ludHMoc2V0LCBwY2ggPSAyMSwgYmcgPSBpZmVsc2Uoc2V0WywgM10gPT0gMSwgJ2dyZWVuNCcsICdyZWQzJykpCgojIFBsb3R0aW5nIHRoZSB0cmVlCnBsb3QoY2xhc3NpZmllcikKdGV4dChjbGFzc2lmaWVyKQoKYGBgCiAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgClRoZSBEZWNpc2lvbiBUcmVlIENsYXNzaWZpZXIgZmlyc3QgY29uZGl0aW9uIGlzIGFnZSA8IDQ0LjUgYW5kIHNhbGFyeSA8ICQ5MCwwMDAsIG91ciBtb2RlbCBjbGFzc2lmaWVzIHBlcnNvbiBhcyB3aWxsIG5vdCBidXkgdGhlIGl0ZW0sIGlmIHNhbGFyeSBpcyA+ICQ5MCwwMDAgcGVyc29uIHdpbGwgYnV5IHRoZSBpdGVtIC4gKlRoaXMgd2FzIG1hZGUgYnkgbm90IHJ1bm5pbmcgdGhlIGNvZGUgb2YgZmVhdHVyZSBzY2FsaW5nIGFuZCB2aXN1YWxpemF0aW9ucywgYnV0IHRoZSBjbGFzc2lmaWVyIGFuZCBwbG90dGluZyBmdW5jdGlvbi4qICAgICAgICAgIAogICAgICAgICAgICAgIAojIyBSYW5kb20gRm9yZXN0IENsYXNzaWZpY2F0aW9uCgpFbnNlbWJsZSBMZWFybmluZyA9IHRha2UgbXVsdGlwbGUgYWxnb3JpdGhtcyB0byBtYWtlIHBvd2VyZnVsIGFsZ29yaXRobQoKc3RlcCAxOiBwaWNrIGF0IHJhbmRvbSBrIGRhdGEgcG9pbnRzIGZyb20gdHJhaW5pbmcgc2V0CgpzdGVwIDI6IGJ1aWxkIGRlY2lzaW9uIHRyZWUgYXNzb2NpYXRlZCB0byB0aGVzZSBrIGRhdGEgcG9pbnRzCgpzdGVwIDM6IGNob29zZSB0aGUgbnVtYmVyIE50cmVlIG9mIHRyZWVzIHlvdSB3YW50IHRvIGJ1aWxkIGFuZCByZXBlYXQgc3RlcHMgMSAmIDIKCnN0ZXAgNDogZm9yIGEgbmV3IGRhdGEgcG9pbnQsIG1ha2UgZWFjaCBvbmUgb2YgeW91ciBOdHJlZSB0cmVlcyBwcmVkaWN0IHRoZSB2YWx1ZSBvZiBZIGZvciB0aGUgZGF0YSBwb2ludCBpbiBxdWVzdGlvbiwgYW5kIGFzc2lnbiB0aGUgbmV3IGRhdGEgcG9pbnQgdGhlIGF2ZXJhZ2UgYWNyb3NzIGFsbCBvZiB0aGUgcHJlZGljdGVkIFkgdmFsdWVzCiAgICAgICAgICAgICAgClRoaXMgbW9kZWwgaXMgdXNlZCBmb3IgcmVtb3RlIGNvbnRyb2xsZXIgZnJlZSBnYW1pbmcgY29uc29sZXMgKE1pY3Jvc29mdCBjb25uZWN0KSAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgCmBgYHtyIFJhbmRvbSBGb3Jlc3R9ICAgICAgICAgICAgICAKIyBJbXBvcnRpbmcgdGhlIGRhdGFzZXQKZGF0YXNldCA9IHJlYWQuY3N2KCcuL1BhcnQgMyAtIENsYXNzaWZpY2F0aW9uL1NlY3Rpb24gMjAgLSBSYW5kb20gRm9yZXN0IENsYXNzaWZpY2F0aW9uL1IvU29jaWFsX05ldHdvcmtfQWRzLmNzdicpCgpkYXRhc2V0ID0gZGF0YXNldFszOjVdCgojIEVuY29kaW5nIHRoZSB0YXJnZXQgZmVhdHVyZSBhcyBmYWN0b3IKZGF0YXNldCRQdXJjaGFzZWQgPSBmYWN0b3IoZGF0YXNldCRQdXJjaGFzZWQsIGxldmVscyA9IGMoMCwgMSkpCgojIFNwbGl0dGluZyB0aGUgZGF0YXNldCBpbnRvIHRoZSBUcmFpbmluZyBzZXQgYW5kIFRlc3Qgc2V0CiMgaW5zdGFsbC5wYWNrYWdlcygnY2FUb29scycpCmxpYnJhcnkoY2FUb29scykKc2V0LnNlZWQoMTIzKQpzcGxpdCA9IHNhbXBsZS5zcGxpdChkYXRhc2V0JFB1cmNoYXNlZCwgU3BsaXRSYXRpbyA9IDAuNzUpCnRyYWluaW5nX3NldCA9IHN1YnNldChkYXRhc2V0LCBzcGxpdCA9PSBUUlVFKQp0ZXN0X3NldCA9IHN1YnNldChkYXRhc2V0LCBzcGxpdCA9PSBGQUxTRSkKCiMgRmVhdHVyZSBTY2FsaW5nCnRyYWluaW5nX3NldFstM10gPSBzY2FsZSh0cmFpbmluZ19zZXRbLTNdKQp0ZXN0X3NldFstM10gPSBzY2FsZSh0ZXN0X3NldFstM10pCgojIEZpdHRpbmcgUmFuZG9tIEZvcmVzdCBDbGFzc2lmaWNhdGlvbiB0byB0aGUgVHJhaW5pbmcgc2V0CiMgaW5zdGFsbC5wYWNrYWdlcygncmFuZG9tRm9yZXN0JykKbGlicmFyeShyYW5kb21Gb3Jlc3QpCnNldC5zZWVkKDEyMykKY2xhc3NpZmllciA9IHJhbmRvbUZvcmVzdCh4ID0gdHJhaW5pbmdfc2V0Wy0zXSwKICAgICAgICAgICAgICAgICAgICAgICAgICB5ID0gdHJhaW5pbmdfc2V0JFB1cmNoYXNlZCwKICAgICAgICAgICAgICAgICAgICAgICAgICBudHJlZSA9IDUwMCkKCiMgUHJlZGljdGluZyB0aGUgVGVzdCBzZXQgcmVzdWx0cwp5X3ByZWQgPSBwcmVkaWN0KGNsYXNzaWZpZXIsIG5ld2RhdGEgPSB0ZXN0X3NldFstM10pCgojIE1ha2luZyB0aGUgQ29uZnVzaW9uIE1hdHJpeApjbSA9IHRhYmxlKHRlc3Rfc2V0WywgM10sIHlfcHJlZCkKCgoKIz09PT09PT09PT0gVmlzdWFsaXppbmcgdGhlIFRyYWluaW5nIHNldCByZXN1bHRzCmxpYnJhcnkoRWxlbVN0YXRMZWFybikKc2V0ID0gdHJhaW5pbmdfc2V0ClgxID0gc2VxKG1pbihzZXRbLCAxXSkgLSAxLCBtYXgoc2V0WywgMV0pICsgMSwgYnkgPSAwLjAxKQpYMiA9IHNlcShtaW4oc2V0WywgMl0pIC0gMSwgbWF4KHNldFssIDJdKSArIDEsIGJ5ID0gMC4wMSkKZ3JpZF9zZXQgPSBleHBhbmQuZ3JpZChYMSwgWDIpCmNvbG5hbWVzKGdyaWRfc2V0KSA9IGMoJ0FnZScsICdFc3RpbWF0ZWRTYWxhcnknKQp5X2dyaWQgPSBwcmVkaWN0KGNsYXNzaWZpZXIsIGdyaWRfc2V0KQoKcGxvdChzZXRbLCAtM10sCiAgICAgbWFpbiA9ICdSYW5kb20gRm9yZXN0IENsYXNzaWZpY2F0aW9uIChUcmFpbmluZyBzZXQpJywKICAgICB4bGFiID0gJ0FnZScsIHlsYWIgPSAnRXN0aW1hdGVkIFNhbGFyeScsCiAgICAgeGxpbSA9IHJhbmdlKFgxKSwgeWxpbSA9IHJhbmdlKFgyKSkKY29udG91cihYMSwgWDIsIG1hdHJpeChhcy5udW1lcmljKHlfZ3JpZCksIGxlbmd0aChYMSksIGxlbmd0aChYMikpLCBhZGQgPSBUUlVFKQpwb2ludHMoZ3JpZF9zZXQsIHBjaCA9ICcuJywgY29sID0gaWZlbHNlKHlfZ3JpZCA9PSAxLCAnc3ByaW5nZ3JlZW4zJywgJ3RvbWF0bycpKQpwb2ludHMoc2V0LCBwY2ggPSAyMSwgYmcgPSBpZmVsc2Uoc2V0WywgM10gPT0gMSwgJ2dyZWVuNCcsICdyZWQzJykpCgojPT09PT09PT0gVmlzdWFsaXppbmcgdGhlIFRlc3Qgc2V0IHJlc3VsdHMKbGlicmFyeShFbGVtU3RhdExlYXJuKQpzZXQgPSB0ZXN0X3NldApYMSA9IHNlcShtaW4oc2V0WywgMV0pIC0gMSwgbWF4KHNldFssIDFdKSArIDEsIGJ5ID0gMC4wMSkKWDIgPSBzZXEobWluKHNldFssIDJdKSAtIDEsIG1heChzZXRbLCAyXSkgKyAxLCBieSA9IDAuMDEpCmdyaWRfc2V0ID0gZXhwYW5kLmdyaWQoWDEsIFgyKQpjb2xuYW1lcyhncmlkX3NldCkgPSBjKCdBZ2UnLCAnRXN0aW1hdGVkU2FsYXJ5JykKeV9ncmlkID0gcHJlZGljdChjbGFzc2lmaWVyLCBncmlkX3NldCkKcGxvdChzZXRbLCAtM10sIG1haW4gPSAnUmFuZG9tIEZvcmVzdCBDbGFzc2lmaWNhdGlvbiAoVGVzdCBzZXQpJywKICAgICB4bGFiID0gJ0FnZScsIHlsYWIgPSAnRXN0aW1hdGVkIFNhbGFyeScsCiAgICAgeGxpbSA9IHJhbmdlKFgxKSwgeWxpbSA9IHJhbmdlKFgyKSkKY29udG91cihYMSwgWDIsIG1hdHJpeChhcy5udW1lcmljKHlfZ3JpZCksIGxlbmd0aChYMSksIGxlbmd0aChYMikpLCBhZGQgPSBUUlVFKQpwb2ludHMoZ3JpZF9zZXQsIHBjaCA9ICcuJywgY29sID0gaWZlbHNlKHlfZ3JpZCA9PSAxLCAnc3ByaW5nZ3JlZW4zJywgJ3RvbWF0bycpKQpwb2ludHMoc2V0LCBwY2ggPSAyMSwgYmcgPSBpZmVsc2Uoc2V0WywgM10gPT0gMSwgJ2dyZWVuNCcsICdyZWQzJykpCgoKCiMgQ2hvb3NpbmcgdGhlIG51bWJlciBvZiB0cmVlcwpwbG90KGNsYXNzaWZpZXIpCgpgYGAKICAgICAgICAgICAgICAKICAgICAgICAgICAgICAKICAgICAgICAgICAgICAKIyBDbGFzc2lmaWNhdGlvbiBNb2RlbCBFdmFsdWF0aW9ucyAgICAgICAgICAgIAoKIyMgRmFsc2UgUG9zaXRpdmVzICYgRmFsc2UgTmVnYXRpdmVzCkZhbHNlIFBvc2l0aXZlIChUeXBlIDEgZXJyb3IpIHt3YXJuaW5nfSwgRmFsc2UgTmVnYXRpdmUgKHR5cGUgMiBlcnJvcikge25vdGhpbmcgdG8gc2VlIGJ1dCBkaXNhc3RlciBoYXBwZW5zfQogICAgICAgICAgICAgIAojIyBDb25mdXNpb24gTWF0cml4Cgp5X3ByZWQKYGBge30KfCAgICAgUHJlZC4gfCAgMCAgfCAxICB8CnwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tfAp8IGFjdHVhbCAgMCB8ICA1NiB8ICA4IHwKfCBhY3R1YWwgIDEgfCAgNyAgfCAyOSB8CmBgYCAgICAgICAgICAgIApBY2N1cmFjeSBSYXRlID0gY29ycmVjdCAvIHRvdGFsIC4gNTYrMjk9IDg1ICA4NS8xMDA9PSA4NSUgICAgICAgICAKRXJyb3IgUmF0ZSA9IHdyb25nL3RvdGFsIC4gNys4PTE1ICAxNS8xMDA9IDE1JSAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgCiMjIEN1bXVsYXRpdmUgQWNjdXJhY3kgUHJvZmlsZSAoQ0FQKQpUaGUgY3VydmVkIGxpbmUgYWJvdmUgdGhlIGxpbmVhciBsaW5lIG9uIGEgcGxvdCwgQUtBIEdhaW4gQ2hhcnQuICAgICAgICAgIApDQVAgQW5hbHlzaXM6IGFyZWEgdW5kZXIgcGVyZmVjdCBtb2RlbCBsaW5lIGFuZCB1bmRlciByZWQgbGluZS4gCiAgICAgICAgICAgICAgClJPQyBpcyBSZWNlaXZlciBPcGVyYXRpbmcgQ2hhcmFjdGVyaXN0aWMgKG5vdCB0aGUgc2FtZSkgICAgICAgICAgICAgCiAgICAgICAgICAgICAgCkVuZCBvZiBQYXJ0IDMgLSBDbGFzc2lmaWNhdGlvbgoKPGhyPgoKIyAqKkNsdXN0ZXJpbmcqKgpJbiBDbHVzdGVyaW5nIHlvdSBkb27igJl0IGtub3cgd2hhdCB5b3UgYXJlIGxvb2tpbmcgZm9yLCBhbmQgeW91IGFyZSB0cnlpbmcgdG8gaWRlbnRpZnkgc29tZSBzZWdtZW50cyBvciBjbHVzdGVycyBpbiB5b3VyIGRhdGEuIFdoZW4geW91IHVzZSBjbHVzdGVyaW5nIGFsZ29yaXRobXMgb24geW91ciBkYXRhc2V0LCB1bmV4cGVjdGVkIHRoaW5ncyBjYW4gc3VkZGVubHkgcG9wIHVwIGxpa2Ugc3RydWN0dXJlcywgY2x1c3RlcnMgYW5kIGdyb3VwaW5ncyB5b3Ugd291bGQgaGF2ZSBuZXZlciB0aG91Z2h0IG9mIG90aGVyd2lzZQoKICAgICAgICAgICAgICAKIyMgSy1NZWFucyBDbHVzdGVyaW5nClRoaXMgZmluZHMgdGhlIGNsdXN0ZXJzIGZvciB5b3UuCgpQcm9jZXNzOgoKLSBzdGVwIDEuIGNob29zZSB0aGUgbnVtYmVyIG9mIEsgb2YgY2x1c3RlcnMKLSBzdGVwIDIuIHNlbGVjdCBhdCByYW5kb20gayBwb2ludHMsIHRoZSBjZW50cm9pZHMKLSBzdGVwIDMuIGFzc2lnbiBlYWNoIGRhdGEgcG9pbnQgdG8gdGhlIGNsb3Nlc3QgY2VudHJvaWQgPT4gZm9ybXMgY2x1c3RlcnMKLSBzdGVwIDQuIGNvbXB1dGUgYW5kIHBsYWNlIHRoZSBuZXcgY2VudHJvaWQgb2YgZWFjaCBjbHVzdGVyCi0gc3RlcCA1LiByZWFzc2lnbiBlYWNoIGRhdGFwb2ludCB0byB0aGUgbmV3IGNsb3Nlc3QgY2VudHJvaWQsIHN0ZXAgNCBhbmQgYmFjaywgdGhlbiBmaW5pc2hlZAoKCnNlbGVjdGluZyBrIHVzaW5nIHRoZSAiZWxib3cgbWV0aG9kIiBrPTMgb3IgNCBiYXNlZCBvbiBhIHBsb3QgbGluZQoKYGBge3IgSyBNZWFuc30KCiMgSW1wb3J0aW5nIHRoZSBkYXRhc2V0CmRhdGFzZXQgPSByZWFkLmNzdignLi9QYXJ0IDQgLSBDbHVzdGVyaW5nL1NlY3Rpb24gMjQgLSBLLU1lYW5zIENsdXN0ZXJpbmcvUi9NYWxsX0N1c3RvbWVycy5jc3YnKQpkYXRhc2V0ID0gZGF0YXNldFs0OjVdCgojIFNwbGl0dGluZyB0aGUgZGF0YXNldCBpbnRvIHRoZSBUcmFpbmluZyBzZXQgYW5kIFRlc3Qgc2V0CiMgaW5zdGFsbC5wYWNrYWdlcygnY2FUb29scycpCiMgbGlicmFyeShjYVRvb2xzKQojIHNldC5zZWVkKDEyMykKIyBzcGxpdCA9IHNhbXBsZS5zcGxpdChkYXRhc2V0JERlcGVuZGVudFZhcmlhYmxlLCBTcGxpdFJhdGlvID0gMC44KQojIHRyYWluaW5nX3NldCA9IHN1YnNldChkYXRhc2V0LCBzcGxpdCA9PSBUUlVFKQojIHRlc3Rfc2V0ID0gc3Vic2V0KGRhdGFzZXQsIHNwbGl0ID09IEZBTFNFKQoKIyBGZWF0dXJlIFNjYWxpbmcKIyB0cmFpbmluZ19zZXQgPSBzY2FsZSh0cmFpbmluZ19zZXQpCiMgdGVzdF9zZXQgPSBzY2FsZSh0ZXN0X3NldCkKCiMgVXNpbmcgdGhlIGVsYm93IG1ldGhvZCB0byBmaW5kIHRoZSBvcHRpbWFsIG51bWJlciBvZiBjbHVzdGVycwpzZXQuc2VlZCg2KQp3Y3NzID0gdmVjdG9yKCkKZm9yIChpIGluIDE6MTApIHdjc3NbaV0gPSBzdW0oa21lYW5zKGRhdGFzZXQsIGkpJHdpdGhpbnNzKQpwbG90KDE6MTAsCiAgICAgd2NzcywKICAgICB0eXBlID0gJ2InLAogICAgIG1haW4gPSBwYXN0ZSgnVGhlIEVsYm93IE1ldGhvZCcpLAogICAgIHhsYWIgPSAnTnVtYmVyIG9mIGNsdXN0ZXJzJywKICAgICB5bGFiID0gJ1dDU1MnKQoKIyBGaXR0aW5nIEstTWVhbnMgdG8gdGhlIGRhdGFzZXQKc2V0LnNlZWQoMjkpCmttZWFucyA9IGttZWFucyh4ID0gZGF0YXNldCwgY2VudGVycyA9IDUpICMgY2x1c3RlciA9PSBjZW50ZXJzCnlfa21lYW5zID0ga21lYW5zJGNsdXN0ZXIKCiMgVmlzdWFsaXNpbmcgdGhlIGNsdXN0ZXJzCmxpYnJhcnkoY2x1c3RlcikKY2x1c3Bsb3QoZGF0YXNldCwgICAKICAgICAgICAgeV9rbWVhbnMsCiAgICAgICAgIGxpbmVzID0gMCwKICAgICAgICAgc2hhZGUgPSBUUlVFLAogICAgICAgICBjb2xvciA9IFRSVUUsCiAgICAgICAgIGxhYmVscyA9IDIsCiAgICAgICAgIHBsb3RjaGFyID0gRkFMU0UsCiAgICAgICAgIHNwYW4gPSBUUlVFLAogICAgICAgICBtYWluID0gcGFzdGUoJ0NsdXN0ZXJzIG9mIGN1c3RvbWVycycpLAogICAgICAgICB4bGFiID0gJ0FubnVhbCBJbmNvbWUnLAogICAgICAgICB5bGFiID0gJ1NwZW5kaW5nIFNjb3JlJykKCmBgYAoKCiMjIEhpZXJhcmNoaWNhbCBDbHVzdGVyaW5nIApBZ2dsb21lcmF0aXZlIChib3R0b20tdXApIGFuZCBEaXZpc2l2ZSAodG9wLWRvd24pCgpEZW5kcm9ncmFtcyAoc2VjdGlvbiAyNzogdmlkZW8gMTc4LCAxNzkpCgpgYGB7ciBIaWVyYXJjaGljYWwgQ2x1c3RlcmluZ30KCiMgSW1wb3J0aW5nIHRoZSBkYXRhc2V0CmRhdGFzZXQgPSByZWFkLmNzdignLi9QYXJ0IDQgLSBDbHVzdGVyaW5nL1NlY3Rpb24gMjUgLSBIaWVyYXJjaGljYWwgQ2x1c3RlcmluZy9SL01hbGxfQ3VzdG9tZXJzLmNzdicpCmRhdGFzZXQgPSBkYXRhc2V0WzQ6NV0KCiMgU3BsaXR0aW5nIHRoZSBkYXRhc2V0IGludG8gdGhlIFRyYWluaW5nIHNldCBhbmQgVGVzdCBzZXQKIyBpbnN0YWxsLnBhY2thZ2VzKCdjYVRvb2xzJykKIyBsaWJyYXJ5KGNhVG9vbHMpCiMgc2V0LnNlZWQoMTIzKQojIHNwbGl0ID0gc2FtcGxlLnNwbGl0KGRhdGFzZXQkRGVwZW5kZW50VmFyaWFibGUsIFNwbGl0UmF0aW8gPSAwLjgpCiMgdHJhaW5pbmdfc2V0ID0gc3Vic2V0KGRhdGFzZXQsIHNwbGl0ID09IFRSVUUpCiMgdGVzdF9zZXQgPSBzdWJzZXQoZGF0YXNldCwgc3BsaXQgPT0gRkFMU0UpCgojIEZlYXR1cmUgU2NhbGluZwojIHRyYWluaW5nX3NldCA9IHNjYWxlKHRyYWluaW5nX3NldCkKIyB0ZXN0X3NldCA9IHNjYWxlKHRlc3Rfc2V0KQoKIyBVc2luZyB0aGUgZGVuZHJvZ3JhbSB0byBmaW5kIHRoZSBvcHRpbWFsIG51bWJlciBvZiBjbHVzdGVycwpkZW5kcm9ncmFtID0gaGNsdXN0KGQgPSBkaXN0KGRhdGFzZXQsIG1ldGhvZCA9ICdldWNsaWRlYW4nKSwgbWV0aG9kID0gJ3dhcmQuRCcpCnBsb3QoZGVuZHJvZ3JhbSwKICAgICBtYWluID0gcGFzdGUoJ0RlbmRyb2dyYW0nKSwKICAgICB4bGFiID0gJ0N1c3RvbWVycycsCiAgICAgeWxhYiA9ICdFdWNsaWRlYW4gZGlzdGFuY2VzJykKCiMgRml0dGluZyBIaWVyYXJjaGljYWwgQ2x1c3RlcmluZyB0byB0aGUgZGF0YXNldApoYyA9IGhjbHVzdChkID0gZGlzdChkYXRhc2V0LCBtZXRob2QgPSAnZXVjbGlkZWFuJyksIG1ldGhvZCA9ICd3YXJkLkQnKQp5X2hjID0gY3V0cmVlKGhjLCA1KQoKIyBWaXN1YWxpc2luZyB0aGUgY2x1c3RlcnMKbGlicmFyeShjbHVzdGVyKQpjbHVzcGxvdChkYXRhc2V0LAogICAgICAgICB5X2hjLAogICAgICAgICBsaW5lcyA9IDAsCiAgICAgICAgIHNoYWRlID0gVFJVRSwKICAgICAgICAgY29sb3IgPSBUUlVFLAogICAgICAgICBsYWJlbHM9IDIsCiAgICAgICAgIHBsb3RjaGFyID0gRkFMU0UsCiAgICAgICAgIHNwYW4gPSBUUlVFLAogICAgICAgICBtYWluID0gcGFzdGUoJ0NsdXN0ZXJzIG9mIGN1c3RvbWVycycpLAogICAgICAgICB4bGFiID0gJ0FubnVhbCBJbmNvbWUnLAogICAgICAgICB5bGFiID0gJ1NwZW5kaW5nIFNjb3JlJykKCgpgYGAKCgoKCgoKCgoKIyBBc3NvY2lhdGlvbiBSdWxlCiJwZW9wbGUgd2hvIGJvdWdodCBYIGFsc28gYm91Z2h0IFkiCgpBcHJpb3JpIGFsZ29yaXRobQpgYGB7fQoKbW92aWUgcmVjb21tZW5kYXRpb246IAogIHN1cHBvcnQoTSkgPSAjIHVzZXIgd2F0Y2hsaXN0IGNvbnRhaW5pbmcgTSAvICMgdXNlciB3YXRjaGxpc3RzCgptb3ZpZSByZWNvbW1lbmRhdGlvbjogCiAgY29uZmlkZW5jZShNMSAtPiBNMikgPSAjIHVzZXIgd2F0Y2hsaXN0IGNvbnRhaW5pbmcgTTEsIE0yIC8gIyB1c2VyIHdhdGNobGlzdHMgY29udGFpbmluZyBNMQoKbW92aWUgcmVjb21tZW5kYXRpb246IAogIGxpZnQoTTEgLT4gTTIpID0gY29uZmlkZW5jZSBNMSwgTTIgLyBzdXBwb3J0IE0yCiAgCiAgCm1hcmtldCBiYXNrZXQgb3B0aW1pemF0aW9uOiAKICBzdXBwb3J0KEopID0gIyB0cmFuc2FjdGlvbnMgY29udGFpbmluZyBKIC8gIyB0cmFuc2FjdGlvbnMKICAKbWFya2V0IGJhc2tldCBvcHRpbWl6YXRpb246IAogIGNvbmZpZGVuY2UoSjEgLT4gSjIpID0gIyB0cmFuc2FjdGlvbnMgY29udGFpbmluZyBKMSwgSjIgLyAjIHRyYW5zYWN0aW9ucyBjb250YWluaW5nIEoxCiAgCm1hcmtldCBiYXNrZXQgb3B0aW1pemF0aW9uOiAKICB7d2hhdCBpcyB0aGUgcmFuZG9tIGxpa2VsaWhvb2QgdGhhdCBwZXJzb24gbGlrZXMgdGhpcyBpdGVtPyBMaWZ0IGlzIHRoZSBpbXByb3ZlbWVudCByZWNvbW1lbmRhdGlvbn0KICBsaWZ0KEoxIC0+IEoyKSA9IGNvbmZpZGVuY2UgSjEsIEoyIC8gc3VwcG9ydCBKMgoKYGBgCgpQcm9jZXNzOgoKMS4gc2V0IGEgbWluIHN1cHBvcnQgYW5kIGNvbmZpZGVuY2UKMi4gdGFrZSBhbGwgdGhlIHN1YnNldHMgaW4gdHJhbnNhY3Rpb25zIGhhdmluZyBoaWdoZXIgc3VwcG9ydCB0aGFuIG1pbiBzdXBwb3J0CjMuIHRha2UgYWxsIHRoZSBydWxlcyBvZiB0aGVzZSBzdWJzZXRzIGhhdmluZyBoaWdoZXIgY29uZmlkZW5jZSB0aGFuIG1pbiBjb25maWRlbmNlCjQuIHNvcnQgdGhlIHJ1bGVzIGJ5IGRlY3JlYXNpbmcgbGlmdCwgaGlnaGVzdCBsaWZ0IGlzIHRoZSB2YWx1ZSB5b3Ugd2FudCAKCmBgYHtyIEFzc29jaWF0aW9uIFJ1bGV9CiMgQXByaW9yaQoKIyBEYXRhIFByZXByb2Nlc3NpbmcKIyBpbnN0YWxsLnBhY2thZ2VzKCdhcnVsZXMnKQpsaWJyYXJ5KGFydWxlcykKCmRhdGFzZXQgPSByZWFkLmNzdignLi9QYXJ0IDUgLSBBc3NvY2lhdGlvbiBSdWxlIExlYXJuaW5nL1NlY3Rpb24gMjggLSBBcHJpb3JpL1IvTWFya2V0X0Jhc2tldF9PcHRpbWlzYXRpb24uY3N2JywgaGVhZGVyID0gRkFMU0UpCgojIHNwYXJzZSBtYXRyaXgKZGF0YXNldCA9IHJlYWQudHJhbnNhY3Rpb25zKCcuL1BhcnQgNSAtIEFzc29jaWF0aW9uIFJ1bGUgTGVhcm5pbmcvU2VjdGlvbiAyOCAtIEFwcmlvcmkvUi9NYXJrZXRfQmFza2V0X09wdGltaXNhdGlvbi5jc3YnLCBzZXAgPSAnLCcsIHJtLmR1cGxpY2F0ZXMgPSBUUlVFKQoKc3VtbWFyeShkYXRhc2V0KQppdGVtRnJlcXVlbmN5UGxvdChkYXRhc2V0LCB0b3BOID0gMzApCgojIFRyYWluaW5nIEFwcmlvcmkgb24gdGhlIGRhdGFzZXQKCiMgaXRlbXMgYm91Z2h0IDN4J3MgYSBkYXkgZGl2aWRlZCBieSB0b3RhbCBwcm9kdWN0cyA9IHszKjcvNzUwMH0gPSAwLjAyOCA9PiAwLjAwMwojIGl0ZW1zIGJvdWdodCA0eCdzIGEgZGF5IGRpdmlkZWQgYnkgdG90YWwgcHJvZHVjdHMgPSB7NCo3Lzc1MDB9ID0gMC4wMDM3ID0+IDAuMDA0CiMgY29uZmlkZW5jZSB2YWx1ZSBpcyBhcmJpdHJhcnkgY2hvaWNlIAojIApydWxlcyA9IGFwcmlvcmkoZGF0YSA9IGRhdGFzZXQsIAogICAgICAgICAgICAgICAgcGFyYW1ldGVyID0gbGlzdChzdXBwb3J0ID0gMC4wMDQsICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29uZmlkZW5jZSA9IDAuMikpICMgdXNlIHNtYWxsIHZhbHVlcyBmb3IgbW9yZSBydWxlcwoKIyBWaXN1YWxpemluZyB0aGUgcmVzdWx0cwojIGdldCB0aGUgaGlnaGVzdCBydWxlcyBieSBsaWZ0Cmluc3BlY3Qoc29ydChydWxlcywgYnkgPSAnbGlmdCcpWzE6MTBdKQoKYGBgCgp0aGUgcnVsZXMgc2hvdyB0aGF0IHBlb3BsZSB3aG8gYm91Z2h0IHtsaWdodCBjcmVhbX0gd2lsbCBidXkge2NoaWNrZW59IDI5JSBbQ29uZmlkZW5jZV0gb2YgdGhlIGNhc2VzIHdpdGggYSBsaWZ0IHZhbHVlIG9mIDQuODQKCgojIyBFY2xhdAp0aGlzIGFsZ29yaXRobSBpcyBzaW1pbGFyIGFzIGFib3ZlLCBidXQgaXQgb25seSBoYXMgdGhlIHN1cHBvcnQgdmFyaWFibGUgdXNpbmcgc2V0cwoKc2ltcGxlIHJlc3VsdHMgb2YgaXRlbXMgY29tbW9ubHkgcHVyY2hhc2VkIHRvZ2V0aGVyIHVzaW5nIHRoZSBzdXBwb3J0IHBhcmFtZXRlcgpgYGB7ciBFY2xhdH0KCiMgRGF0YSBQcmVwcm9jZXNzaW5nCiMgaW5zdGFsbC5wYWNrYWdlcygnYXJ1bGVzJykKbGlicmFyeShhcnVsZXMpCmRhdGFzZXQgPSByZWFkLmNzdignLi9QYXJ0IDUgLSBBc3NvY2lhdGlvbiBSdWxlIExlYXJuaW5nL1NlY3Rpb24gMjkgLSBFY2xhdC9SL01hcmtldF9CYXNrZXRfT3B0aW1pc2F0aW9uLmNzdicpCgojIHNwYXJzZSBtYXRyaXgKZGF0YXNldCA9IHJlYWQudHJhbnNhY3Rpb25zKCcuL1BhcnQgNSAtIEFzc29jaWF0aW9uIFJ1bGUgTGVhcm5pbmcvU2VjdGlvbiAyOSAtIEVjbGF0L1IvTWFya2V0X0Jhc2tldF9PcHRpbWlzYXRpb24uY3N2Jywgc2VwID0gJywnLCBybS5kdXBsaWNhdGVzID0gVFJVRSkKCnN1bW1hcnkoZGF0YXNldCkKCml0ZW1GcmVxdWVuY3lQbG90KGRhdGFzZXQsIHRvcE4gPSAxMCkKCiMgVHJhaW5pbmcgRWNsYXQgb24gdGhlIGRhdGFzZXQKcnVsZXMgPSBlY2xhdChkYXRhID0gZGF0YXNldCwgcGFyYW1ldGVyID0gbGlzdChzdXBwb3J0ID0gMC4wMDMsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pbmxlbiA9IDIpKSAjIG1pbiBudW1iZXIgb2YgcHVyY2hhc2VkIGl0ZW1zIGJvdWdodCB0b2dldGhlcgoKIyBWaXN1YWxpc2luZyB0aGUgcmVzdWx0cwppbnNwZWN0KHNvcnQocnVsZXMsIGJ5ID0gJ3N1cHBvcnQnKVsxOjEwXSkKCgpgYGAKICAgICAgICAgICAgICAKICAgICAgCkVuZCBvZiBzZWN0aW9uCjxocj4KICAgICAgCgojICoqUHJpbmNpcGFsIENvbXBvbmVudCBBbmFseXNpcyAoUENBKSoqClVuc3VwZXJ2aXNlZCBhbGdvcml0aG0gZm9yIDogbm9pc2UgZmlsdGVyaW5nLCB2aXN1YWxpemF0aW9uLCBmZWF0dXJlIGV4dHJhY3Rpb24sIHRpbWUgc2VyaWVzIHByZWRpY3Rpb25zLCBnZW5lIGRhdGEgYW5hbHlzaXMuCiAgCkdvYWw6IGlkZW50aWZ5IHBhdHRlcm5zIGluIGRhdGEsIGRldGVjdCB0aGUgY29ycmVsYXRpb24gYmV0d2VlbiB2YXJpYWJsZXMgIGJ5IHJlZHVjaW5nIHRoZSBkaW1lbnNpb25zCgpgYGB7ciBQQ0F9CiMgSW1wb3J0aW5nIHRoZSBkYXRhc2V0CmRhdGFzZXQgPSByZWFkLmNzdignLi9QYXJ0IDkgLSBEaW1lbnNpb25hbGl0eSBSZWR1Y3Rpb24vU2VjdGlvbiA0MyAtIFByaW5jaXBhbCBDb21wb25lbnQgQW5hbHlzaXMgKFBDQSkvUi9XaW5lLmNzdicpCgojIFNwbGl0dGluZyB0aGUgZGF0YXNldCBpbnRvIHRoZSBUcmFpbmluZyBzZXQgYW5kIFRlc3Qgc2V0CiMgaW5zdGFsbC5wYWNrYWdlcygnY2FUb29scycpCmxpYnJhcnkoY2FUb29scykKc2V0LnNlZWQoMTIzKQpzcGxpdCA9IHNhbXBsZS5zcGxpdChkYXRhc2V0JEN1c3RvbWVyX1NlZ21lbnQsIFNwbGl0UmF0aW8gPSAwLjgpCnRyYWluaW5nX3NldCA9IHN1YnNldChkYXRhc2V0LCBzcGxpdCA9PSBUUlVFKQp0ZXN0X3NldCA9IHN1YnNldChkYXRhc2V0LCBzcGxpdCA9PSBGQUxTRSkKCiMgRmVhdHVyZSBTY2FsaW5nCnRyYWluaW5nX3NldFstMTRdID0gc2NhbGUodHJhaW5pbmdfc2V0Wy0xNF0pCnRlc3Rfc2V0Wy0xNF0gPSBzY2FsZSh0ZXN0X3NldFstMTRdKQoKIyBBcHBseWluZyBQQ0EKIyBpbnN0YWxsLnBhY2thZ2VzKCdjYXJldCcpCmxpYnJhcnkoY2FyZXQpCiMgaW5zdGFsbC5wYWNrYWdlcygnZTEwNzEnKQpsaWJyYXJ5KGUxMDcxKQpwY2EgPSBwcmVQcm9jZXNzKHggPSB0cmFpbmluZ19zZXRbLTE0XSwgbWV0aG9kID0gJ3BjYScsIHBjYUNvbXAgPSAyKQp0cmFpbmluZ19zZXQgPSBwcmVkaWN0KHBjYSwgdHJhaW5pbmdfc2V0KQp0cmFpbmluZ19zZXQgPSB0cmFpbmluZ19zZXRbYygyLCAzLCAxKV0KdGVzdF9zZXQgPSBwcmVkaWN0KHBjYSwgdGVzdF9zZXQpCnRlc3Rfc2V0ID0gdGVzdF9zZXRbYygyLCAzLCAxKV0KCiMgRml0dGluZyBTVk0gdG8gdGhlIFRyYWluaW5nIHNldAojIGluc3RhbGwucGFja2FnZXMoJ2UxMDcxJykKbGlicmFyeShlMTA3MSkKY2xhc3NpZmllciA9IHN2bShmb3JtdWxhID0gQ3VzdG9tZXJfU2VnbWVudCB+IC4sCiAgICAgICAgICAgICAgICAgZGF0YSA9IHRyYWluaW5nX3NldCwKICAgICAgICAgICAgICAgICB0eXBlID0gJ0MtY2xhc3NpZmljYXRpb24nLAogICAgICAgICAgICAgICAgIGtlcm5lbCA9ICdsaW5lYXInKQoKIyBQcmVkaWN0aW5nIHRoZSBUZXN0IHNldCByZXN1bHRzCnlfcHJlZCA9IHByZWRpY3QoY2xhc3NpZmllciwgbmV3ZGF0YSA9IHRlc3Rfc2V0Wy0zXSkKCiMgTWFraW5nIHRoZSBDb25mdXNpb24gTWF0cml4CmNtID0gdGFibGUodGVzdF9zZXRbLCAzXSwgeV9wcmVkKQoKIyBWaXN1YWxpc2luZyB0aGUgVHJhaW5pbmcgc2V0IHJlc3VsdHMKbGlicmFyeShFbGVtU3RhdExlYXJuKQpzZXQgPSB0cmFpbmluZ19zZXQKWDEgPSBzZXEobWluKHNldFssIDFdKSAtIDEsIG1heChzZXRbLCAxXSkgKyAxLCBieSA9IDAuMDEpClgyID0gc2VxKG1pbihzZXRbLCAyXSkgLSAxLCBtYXgoc2V0WywgMl0pICsgMSwgYnkgPSAwLjAxKQpncmlkX3NldCA9IGV4cGFuZC5ncmlkKFgxLCBYMikKY29sbmFtZXMoZ3JpZF9zZXQpID0gYygnUEMxJywgJ1BDMicpCnlfZ3JpZCA9IHByZWRpY3QoY2xhc3NpZmllciwgbmV3ZGF0YSA9IGdyaWRfc2V0KQpwbG90KHNldFssIC0zXSwKICAgICBtYWluID0gJ1NWTSAoVHJhaW5pbmcgc2V0KScsCiAgICAgeGxhYiA9ICdQQzEnLCB5bGFiID0gJ1BDMicsCiAgICAgeGxpbSA9IHJhbmdlKFgxKSwgeWxpbSA9IHJhbmdlKFgyKSkKY29udG91cihYMSwgWDIsIG1hdHJpeChhcy5udW1lcmljKHlfZ3JpZCksIGxlbmd0aChYMSksIGxlbmd0aChYMikpLCBhZGQgPSBUUlVFKQpwb2ludHMoZ3JpZF9zZXQsIHBjaCA9ICcuJywgY29sID0gaWZlbHNlKHlfZ3JpZCA9PSAyLCAnZGVlcHNreWJsdWUnLCBpZmVsc2UoeV9ncmlkID09IDEsICdzcHJpbmdncmVlbjMnLCAndG9tYXRvJykpKQpwb2ludHMoc2V0LCBwY2ggPSAyMSwgYmcgPSBpZmVsc2Uoc2V0WywgM10gPT0gMiwgJ2JsdWUzJywgaWZlbHNlKHNldFssIDNdID09IDEsICdncmVlbjQnLCAncmVkMycpKSkKCiMgVmlzdWFsaXNpbmcgdGhlIFRlc3Qgc2V0IHJlc3VsdHMKbGlicmFyeShFbGVtU3RhdExlYXJuKQpzZXQgPSB0ZXN0X3NldApYMSA9IHNlcShtaW4oc2V0WywgMV0pIC0gMSwgbWF4KHNldFssIDFdKSArIDEsIGJ5ID0gMC4wMSkKWDIgPSBzZXEobWluKHNldFssIDJdKSAtIDEsIG1heChzZXRbLCAyXSkgKyAxLCBieSA9IDAuMDEpCmdyaWRfc2V0ID0gZXhwYW5kLmdyaWQoWDEsIFgyKQpjb2xuYW1lcyhncmlkX3NldCkgPSBjKCdQQzEnLCAnUEMyJykKeV9ncmlkID0gcHJlZGljdChjbGFzc2lmaWVyLCBuZXdkYXRhID0gZ3JpZF9zZXQpCnBsb3Qoc2V0WywgLTNdLCBtYWluID0gJ1NWTSAoVGVzdCBzZXQpJywKICAgICB4bGFiID0gJ1BDMScsIHlsYWIgPSAnUEMyJywKICAgICB4bGltID0gcmFuZ2UoWDEpLCB5bGltID0gcmFuZ2UoWDIpKQpjb250b3VyKFgxLCBYMiwgbWF0cml4KGFzLm51bWVyaWMoeV9ncmlkKSwgbGVuZ3RoKFgxKSwgbGVuZ3RoKFgyKSksIGFkZCA9IFRSVUUpCnBvaW50cyhncmlkX3NldCwgcGNoID0gJy4nLCBjb2wgPSBpZmVsc2UoeV9ncmlkID09IDIsICdkZWVwc2t5Ymx1ZScsIGlmZWxzZSh5X2dyaWQgPT0gMSwgJ3NwcmluZ2dyZWVuMycsICd0b21hdG8nKSkpCnBvaW50cyhzZXQsIHBjaCA9IDIxLCBiZyA9IGlmZWxzZShzZXRbLCAzXSA9PSAyLCAnYmx1ZTMnLCBpZmVsc2Uoc2V0WywgM10gPT0gMSwgJ2dyZWVuNCcsICdyZWQzJykpKSAgICAgCiAgICAgIAogICAgICAKYGBgICAKICAgICAgCiAgICAgIAogICAgICAKICAgICAgCgogICAgICAKICAgICAgCiAgICAgIAogICAgICAKICAgICAgCiAgICAgIAogICAgICAKICAgICAgCiAgICAgIAogICAgICAKICAgICAgCiAgICAgIAogICAgICAKICAgICAgCiAgICAgIAogICAgICAKIAogCiAKIAogCiAKIAogCiAKIAogCiAKIAogCiAKIAogCiAKIAogCiAKIAogCiAKIAogCiAKIAogCiAKIAogCiAKIAogCiAKICAgICAgCiAgICAgICAgICAgICAg