Question 5
We have seen that we can fit an SVM with a non-linear kernel in order
to perform classification using a non-linear decision boundary. We will
now see that we can also obtain a non-linear decision boundary by
performing logistic regression using non-linear transformations of the
features.
(a)
Generate a data set with n = 500 and p = 2, such that the
observations belong to two classes with a quadratic decision boundary
between them. For instance, you can do this as follows: x1 <-
runif(500) - 0.5 x2 <- runif(500) - 0.5 y <- 1 * (x1^2 - x2^2 >
0)’
set.seed(1)
x1 <- runif(500) - 0.5
x2 <- runif(500) - 0.5
y <- 1 * (x1^2 - x2^2 > 0)
head(data.frame(x1, x2, y))
table(y)
y
0 1
261 239
(b)
Plot the observations, colored according to their class labels. Your
plot should display X1 on the x-axis, and X2 on the yaxis.
plot(x1, x2, col = (y + 2), pch = 19,
xlab = "X1", ylab = "X2",
main = "Class Labels")
legend("topright", legend = c("Class = 0", "Class = 1"), col = c(2, 3), pch = 19)

(c)
Fit a logistic regression model to the data, using X1 and X2 as
predictors.
dat <- data.frame(x1 = x1, x2 = x2, y = y)
glm.fit <- glm(y ~ x1 + x2, data = dat, family = binomial)
summary(glm.fit)
Call:
glm(formula = y ~ x1 + x2, family = binomial, data = dat)
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -0.087260 0.089579 -0.974 0.330
x1 0.196199 0.316864 0.619 0.536
x2 -0.002854 0.305712 -0.009 0.993
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 692.18 on 499 degrees of freedom
Residual deviance: 691.79 on 497 degrees of freedom
AIC: 697.79
Number of Fisher Scoring iterations: 3
(d)
Apply this model to the training data in order to obtain a predicted
class label for each training observation. Plot the observations,
colored according to the predicted class labels. The decision boundary
should be linear.
glm.probs <- predict(glm.fit, dat, type = "response")
glm.pred <- ifelse(glm.probs > 0.5, 1, 0)
table(predicted = glm.pred, actual = y)
actual
predicted 0 1
0 258 212
1 3 27
plot(x1, x2, col = (glm.pred + 2), pch = 19,
xlab = "X1", ylab = "X2",
main = "Logistic Regression (Linear)")
legend("topright", legend = c("pred = 0", "pred = 1"), col = c(2, 3), pch = 19)

(e)
Now fit a logistic regression model to the data using non-linear
functions of X1 and X2 as predictors (e.g. X2 1 , X1×X2, log(X2),and so
forth).
glm.fit2 <- glm(y ~ poly(x1, 2) + poly(x2, 2) + I(x1 * x2), data = dat, family = binomial)
summary(glm.fit2)
Call:
glm(formula = y ~ poly(x1, 2) + poly(x2, 2) + I(x1 * x2), family = binomial,
data = dat)
Coefficients:
Estimate Std. Error z value Pr(>|z|)
(Intercept) -102.2 4302.0 -0.024 0.981
poly(x1, 2)1 2715.3 141109.5 0.019 0.985
poly(x1, 2)2 27218.5 842987.2 0.032 0.974
poly(x2, 2)1 -279.7 97160.4 -0.003 0.998
poly(x2, 2)2 -28693.0 875451.3 -0.033 0.974
I(x1 * x2) -206.4 41802.8 -0.005 0.996
(Dispersion parameter for binomial family taken to be 1)
Null deviance: 6.9218e+02 on 499 degrees of freedom
Residual deviance: 3.5810e-06 on 494 degrees of freedom
AIC: 12
Number of Fisher Scoring iterations: 25
(f)
Apply this model to the training data in order to obtain a predicted
class label for each training observation. Plot the observations,
colored according to the predicted class labels. The decision boundary
should be obviously non-linear. If it is not, then repeat (a)-(e) until
you come up with an example in which the predicted class labels are
obviously non-linear.
glm.probs2 <- predict(glm.fit2, dat, type = "response")
glm.pred2 <- ifelse(glm.probs2 > 0.5, 1, 0)
table(predicted = glm.pred2, actual = y)
actual
predicted 0 1
0 261 0
1 0 239
plot(x1, x2, col = (glm.pred2 + 2), pch = 19,
xlab = "X1", ylab = "X2",
main = "Logistic Regression (Non-linear)")
legend("topright", legend = c("pred = 0", "pred = 1"), col = c(2, 3), pch = 19)

(g)
Fit a support vector classifier to the data with X1 and X2 as
predictors. Obtain a class prediction for each training observation.
Plot the observations, colored according to the predicted class
labels.
library(caret)
data <- data.frame(x1 = x1, x2 = x2, y = as.factor(y))
Error: object 'x1' not found
(h)
Fit a SVM using a non-linear kernel to the data. Obtain a class
prediction for each training observation. Plot the observations, colored
according to the predicted class labels.
train_control <- trainControl(method = "cv", number = 10)
svm_radial <- train(y~x1+x2, data = data, method = "svmRadial", trControl = train_control,
preProcess = c("center","scale"))
svm_radial_pred <- predict(svm_radial, data)
table(predicted = svm_radial_pred, actual = data$y)
actual
predicted 0 1
0 258 10
1 3 229
plot(x1, x2, col = (as.numeric(svm_radial_pred) + 1), pch = 19,
xlab = "X1", ylab = "X2",
main = "SVM (Radial Kernel)")
legend("topright", legend = c("pred = 0", "pred = 1"), col = c(2, 3), pch = 19)

(i)
Comment on your results.
The logistic regression model performs poorly because the true
decision boundary is quadratic instead of linear. Because of this, the
model incorrectly classifies many observations and produces a linear
decision boundary that does not separate the two classes. After adding
quadratic terms, the logistic regression model is able to capture the
non-linear relationship between the predictors.The linear support vector
classifier performs similarly to the linear logistic regression model
because it is also restricted to a linear decision boundary. Using a
non-linear kernel allows the SVM to model the quadratic decision
boundary much more effectively.
Question 7
In this problem, you will use support vector approaches in order to
predict whether a given car gets high or low gas mileage based on the
Auto data set.
(a)
Create a binary variable that takes on a 1 for cars with gas mileage
above the median, and a 0 for cars with gas mileage below the
median.
library(ISLR2)
Auto$mpg01 <- as.factor(ifelse(Auto$mpg > median(Auto$mpg), 1, 0))
(b)
Fit a support vector classifier to the data with various values of
cost, in order to predict whether a car gets high or low gas mileage.
Report the cross-validation errors associated with different values of
this parameter. Comment on your results. Note you will need to fit the
classifier without the gas mileage variable to produce sensible
results.
library(e1071)
set.seed(1)
tune.out <- tune(svm, mpg01 ~ . - mpg - name, data = Auto, kernel = "linear",
ranges = list(cost = c(0.001, 0.01, 0.1, 1, 5, 10, 100)))
summary(tune.out)
Parameter tuning of ‘svm’:
- sampling method: 10-fold cross validation
- best parameters:
- best performance: 0.08435897
- Detailed performance results:
NA
Small values of cost leads to more classification errors while larger
values will classify the training data more accurately. Having a lower
cross-validation error results in a more optimal model.
(c)
Now repeat (b), this time using SVMs with radial and polynomial basis
kernels, with different values of gamma and degree and cost. Comment on
your results.
set.seed(1)
tune.radial <- tune(svm, mpg01 ~ . - mpg - name, data = Auto, kernel = "radial",
ranges = list(cost = c(0.1, 1, 5, 10, 100),
gamma = c(0.01, 0.1, 1, 5, 10)))
summary(tune.radial)
Parameter tuning of ‘svm’:
- sampling method: 10-fold cross validation
- best parameters:
- best performance: 0.06634615
- Detailed performance results:
NA
set.seed(1)
tune.poly <- tune(svm, mpg01 ~ . - mpg - name, data = Auto, kernel = "polynomial",
ranges = list(cost = c(0.1, 1, 5, 10, 100),
degree = c(2, 3, 4)))
summary(tune.poly)
Parameter tuning of ‘svm’:
- sampling method: 10-fold cross validation
- best parameters:
- best performance: 0.08423077
- Detailed performance results:
NA
The radial kernel has a lower cross validation error that he linear
kernel because of the relationship with the predictor variables. The
polynomial kernel also improves performance over the linear
classifier.
(d)
Make some plots to back up your assertions in (b) and (c). Hint: In
the lab, we used the plot() function for svm objects only in cases with
p = 2. When p > 2, you can use the plot() function to create plots
displaying pairs of variables at a time. Essentially, instead of typing
plot(svmfit , dat) where svmfit contains your fitted model and dat is a
data frame containing your data, you can type plot(svmfit , dat , x1 ∼
x4) in order to plot just the first and fourth variables. However, you
must replace x1 and x4 with the correct variable names. To find out
more, type ?plot.svm.
dat <- Auto[, !(names(Auto) %in% c("mpg", "name"))]
svm.linear <- svm(mpg01 ~ ., data = dat, kernel = "linear", cost = 1)
svm.radial <- svm(mpg01 ~ ., data = dat, kernel = "radial", cost = 1, gamma = 1)
svm.poly <- svm(mpg01 ~ ., data = dat, kernel = "polynomial", cost = 100, degree = 3)
slc <- list(cylinders = mean(dat$cylinders), displacement = mean(dat$displacement),
acceleration = mean(dat$acceleration), year = mean(dat$year),
origin = mean(dat$origin))
plot(svm.linear, dat, horsepower ~ weight, slice = slc)

plot(svm.radial, dat, horsepower ~ weight, slice = slc)

plot(svm.poly, dat, horsepower ~ weight, slice = slc)

Question 8
This problem involves the OJ data set which is part of the ISLR2
package.
(a)
Create a training set containing a random sample of 800 observations,
and a test set containing the remaining observations.
set.seed(1)
train <- sample(1:nrow(OJ), 800)
OJ.train <- OJ[train, ]
OJ.test <- OJ[-train, ]
(b)
Fit a support vector classifier to the training data using cost =
0.01, with Purchase as the response and the other variables as
predictors. Use the summary() function to produce summary statistics,
and describe the results obtained.
svm.linear <- svm(Purchase ~ ., data = OJ.train, kernel = "linear", cost = 0.01)
summary(svm.linear)
Call:
svm(formula = Purchase ~ ., data = OJ.train, kernel = "linear", cost = 0.01)
Parameters:
SVM-Type: C-classification
SVM-Kernel: linear
cost: 0.01
Number of Support Vectors: 435
( 219 216 )
Number of Classes: 2
Levels:
CH MM
The support vector classifier was fit using a linear kernel with a
cost of 0.01. The classifier allows more observations to fall within the
margin which results in a smoother decision boundary.
(c)
What are the training and test error rates?
train.pred <- predict(svm.linear, OJ.train)
test.pred <- predict(svm.linear, OJ.test)
test.error <- mean(test.pred != OJ.test$Purchase)
train.error
[1] 0.175
test.error
[1] 0.1777778
(d)
Use the tune() function to select an optimal cost. Consider values in
the range 0.01 to 10.
set.seed(1)
tune.linear <- tune(svm, Purchase ~ ., data = OJ.train, kernel = "linear", ranges = list(cost = c(0.01, 0.1, 1, 5, 10)))
summary(tune.linear)
Parameter tuning of ‘svm’:
- sampling method: 10-fold cross validation
- best parameters:
- best performance: 0.1725
- Detailed performance results:
best.linear <- tune.linear$best.model
(e)
Compute the training and test error rates using this new value for
cost.
train.pred.best <- predict(best.linear, OJ.train)
train.error.best <- mean(train.pred.best != OJ.train$Purchase)
test.pred.best <- predict(best.linear, OJ.test)
test.error.best <- mean(test.pred.best != OJ.test$Purchase)
train.error.best
[1] 0.165
test.error.best
[1] 0.162963
(f)
Repeat parts (b) through (e) using a support vector machine with a
radial kernel. Use the default value for gamma.
svm.radial <- svm(Purchase ~ ., data = OJ.train, kernel = "radial")
summary(svm.radial)
Call:
svm(formula = Purchase ~ ., data = OJ.train, kernel = "radial")
Parameters:
SVM-Type: C-classification
SVM-Kernel: radial
cost: 1
Number of Support Vectors: 373
( 188 185 )
Number of Classes: 2
Levels:
CH MM
train.pred <- predict(svm.radial, OJ.train)
test.pred <- predict(svm.radial, OJ.test)
mean(train.pred != OJ.train$Purchase)
[1] 0.15125
mean(test.pred != OJ.test$Purchase)
[1] 0.1851852
set.seed(1)
tune.radial <- tune(svm, Purchase ~ ., data = OJ.train, kernel = "radial", ranges = list(cost = c(0.01, 0.1, 1, 5, 10)))
summary(tune.radial)
Parameter tuning of ‘svm’:
- sampling method: 10-fold cross validation
- best parameters:
- best performance: 0.17125
- Detailed performance results:
best.radial <- tune.radial$best.model
mean(predict(best.radial, OJ.train) != OJ.train$Purchase)
[1] 0.15125
mean(predict(best.radial, OJ.test) != OJ.test$Purchase)
[1] 0.1851852
(g)
Repeat parts (b) through (e) using a support vector machine with a
polynomial kernel. Set degree = 2.
svm.poly <- svm(Purchase ~ ., data = OJ.train, kernel = "polynomial", degree = 2)
summary(svm.poly)
mean(predict(svm.poly, OJ.train) != OJ.train$Purchase)
mean(predict(svm.poly, OJ.test) != OJ.test$Purchase)
set.seed(1)
tune.poly <- tune(svm, Purchase ~ ., data = OJ.train, kernel = "polynomial", degree = 2, ranges = list(cost = c(0.01, 0.1, 1, 5, 10)))
summary(tune.poly)
Parameter tuning of ‘svm’:
- sampling method: 10-fold cross validation
- best parameters:
- best performance: 0.18125
- Detailed performance results:
best.poly <- tune.poly$best.model
mean(predict(best.poly, OJ.train) != OJ.train$Purchase)
[1] 0.15
mean(predict(best.poly, OJ.test) != OJ.test$Purchase)
[1] 0.1888889
(h)
Overall, which approach seems to give the best results on this
data?
The radial kernel has the best results as it can capture non-linear
relationships without over fitting as much as the polynomial kernel. The
linear classifier performs well but does not capture as much
relationships as the radial kernel.
LS0tDQp0aXRsZTogJ0Fzc2lnbm1lbnQgIzgnDQphdXRob3I6IENocnlzdGEgU2NodWVzc2xlcg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOg0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19mbG9hdDogdHJ1ZQ0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogdHJ1ZQ0KICAgIGRmX3ByaW50OiBwYWdlZA0KZWRpdG9yX29wdGlvbnM6IA0KICBtYXJrZG93bjogDQogICAgd3JhcDogNzINCi0tLQ0KDQojIFF1ZXN0aW9uIDUgDQo+V2UgaGF2ZSBzZWVuIHRoYXQgd2UgY2FuIGZpdCBhbiBTVk0gd2l0aCBhIG5vbi1saW5lYXIga2VybmVsIGluIG9yZGVyDQp0byBwZXJmb3JtIGNsYXNzaWZpY2F0aW9uIHVzaW5nIGEgbm9uLWxpbmVhciBkZWNpc2lvbiBib3VuZGFyeS4gV2Ugd2lsbA0Kbm93IHNlZSB0aGF0IHdlIGNhbiBhbHNvIG9idGFpbiBhIG5vbi1saW5lYXIgZGVjaXNpb24gYm91bmRhcnkgYnkNCnBlcmZvcm1pbmcgbG9naXN0aWMgcmVncmVzc2lvbiB1c2luZyBub24tbGluZWFyIHRyYW5zZm9ybWF0aW9ucyBvZiB0aGUNCmZlYXR1cmVzLg0KDQojIyAoYSkNCj5HZW5lcmF0ZSBhIGRhdGEgc2V0IHdpdGggbiA9IDUwMCBhbmQgcCA9IDIsIHN1Y2ggdGhhdCB0aGUgb2JzZXJ2YXRpb25zDQpiZWxvbmcgdG8gdHdvIGNsYXNzZXMgd2l0aCBhIHF1YWRyYXRpYyBkZWNpc2lvbiBib3VuZGFyeQ0KYmV0d2VlbiB0aGVtLiBGb3IgaW5zdGFuY2UsIHlvdSBjYW4gZG8gdGhpcyBhcyBmb2xsb3dzOg0KPiB4MSA8LSBydW5pZig1MDApIC0gMC41DQo+IHgyIDwtIHJ1bmlmKDUwMCkgLSAwLjUNCj4geSA8LSAxICogKHgxXjIgLSB4Ml4yID4gMCknDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMSkNCngxIDwtIHJ1bmlmKDUwMCkgLSAwLjUNCngyIDwtIHJ1bmlmKDUwMCkgLSAwLjUNCnkgPC0gMSAqICh4MV4yIC0geDJeMiA+IDApDQoNCmhlYWQoZGF0YS5mcmFtZSh4MSwgeDIsIHkpKQ0KDQp0YWJsZSh5KQ0KYGBgDQoNCg0KIyMgKGIpIA0KPlBsb3QgdGhlIG9ic2VydmF0aW9ucywgY29sb3JlZCBhY2NvcmRpbmcgdG8gdGhlaXIgY2xhc3MgbGFiZWxzLg0KWW91ciBwbG90IHNob3VsZCBkaXNwbGF5IFgxIG9uIHRoZSB4LWF4aXMsIGFuZCBYMiBvbiB0aGUgeWF4aXMuDQoNCmBgYHtyfQ0KcGxvdCh4MSwgeDIsIGNvbCA9ICh5ICsgMiksIHBjaCA9IDE5LA0KICAgICB4bGFiID0gIlgxIiwgeWxhYiA9ICJYMiIsbWFpbiA9ICJDbGFzcyBMYWJlbHMiKQ0KbGVnZW5kKCJ0b3ByaWdodCIsIGxlZ2VuZCA9IGMoIkNsYXNzID0gMCIsICJDbGFzcyA9IDEiKSwgY29sID0gYygyLCAzKSwgcGNoID0gMTkpDQpgYGANCg0KDQoNCiMjIChjKSANCj5GaXQgYSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIHRvIHRoZSBkYXRhLCB1c2luZyBYMSBhbmQgWDIgYXMNCnByZWRpY3RvcnMuDQoNCmBgYHtyfQ0KZGF0IDwtIGRhdGEuZnJhbWUoeDEgPSB4MSwgeDIgPSB4MiwgeSA9IHkpDQpnbG0uZml0IDwtIGdsbSh5IH4geDEgKyB4MiwgZGF0YSA9IGRhdCwgZmFtaWx5ID0gYmlub21pYWwpDQpzdW1tYXJ5KGdsbS5maXQpDQpgYGANCg0KDQojIyAoZCkgDQo+QXBwbHkgdGhpcyBtb2RlbCB0byB0aGUgdHJhaW5pbmcgZGF0YSBpbiBvcmRlciB0byBvYnRhaW4gYSBwcmVkaWN0ZWQNCmNsYXNzIGxhYmVsIGZvciBlYWNoIHRyYWluaW5nIG9ic2VydmF0aW9uLiBQbG90IHRoZSBvYnNlcnZhdGlvbnMsDQpjb2xvcmVkIGFjY29yZGluZyB0byB0aGUgcHJlZGljdGVkIGNsYXNzIGxhYmVscy4gVGhlDQpkZWNpc2lvbiBib3VuZGFyeSBzaG91bGQgYmUgbGluZWFyLg0KDQpgYGB7cn0NCmdsbS5wcm9icyA8LSBwcmVkaWN0KGdsbS5maXQsIGRhdCwgdHlwZSA9ICJyZXNwb25zZSIpDQpnbG0ucHJlZCA8LSBpZmVsc2UoZ2xtLnByb2JzID4gMC41LCAxLCAwKQ0KdGFibGUocHJlZGljdGVkID0gZ2xtLnByZWQsIGFjdHVhbCA9IHkpDQoNCnBsb3QoeDEsIHgyLCBjb2wgPSAoZ2xtLnByZWQgKyAyKSwgcGNoID0gMTksDQogICAgIHhsYWIgPSAiWDEiLCB5bGFiID0gIlgyIixtYWluID0gIkxvZ2lzdGljIFJlZ3Jlc3Npb24gKExpbmVhcikiKQ0KbGVnZW5kKCJ0b3ByaWdodCIsIGxlZ2VuZCA9IGMoInByZWQgPSAwIiwgInByZWQgPSAxIiksIGNvbCA9IGMoMiwgMyksIHBjaCA9IDE5KQ0KYGBgDQoNCg0KIyMgKGUpIA0KPk5vdyBmaXQgYSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIHRvIHRoZSBkYXRhIHVzaW5nIG5vbi1saW5lYXINCmZ1bmN0aW9ucyBvZiBYMSBhbmQgWDIgYXMgcHJlZGljdG9ycyAoZS5nLiBYMg0KMSAsIFgxw5dYMiwgbG9nKFgyKSxhbmQgc28gZm9ydGgpLg0KDQpgYGB7cn0NCmdsbS5maXQyIDwtIGdsbSh5IH4gcG9seSh4MSwgMikgKyBwb2x5KHgyLCAyKSArIEkoeDEgKiB4MiksIGRhdGEgPSBkYXQsIGZhbWlseSA9IGJpbm9taWFsKQ0Kc3VtbWFyeShnbG0uZml0MikNCmBgYA0KDQojIyAoZikNCj5BcHBseSB0aGlzIG1vZGVsIHRvIHRoZSB0cmFpbmluZyBkYXRhIGluIG9yZGVyIHRvIG9idGFpbiBhIHByZWRpY3RlZA0KY2xhc3MgbGFiZWwgZm9yIGVhY2ggdHJhaW5pbmcgb2JzZXJ2YXRpb24uIFBsb3QgdGhlIG9ic2VydmF0aW9ucywNCmNvbG9yZWQgYWNjb3JkaW5nIHRvIHRoZSBwcmVkaWN0ZWQgY2xhc3MgbGFiZWxzLiBUaGUNCmRlY2lzaW9uIGJvdW5kYXJ5IHNob3VsZCBiZSBvYnZpb3VzbHkgbm9uLWxpbmVhci4gSWYgaXQgaXMgbm90LA0KdGhlbiByZXBlYXQgKGEpLShlKSB1bnRpbCB5b3UgY29tZSB1cCB3aXRoIGFuIGV4YW1wbGUgaW4gd2hpY2gNCnRoZSBwcmVkaWN0ZWQgY2xhc3MgbGFiZWxzIGFyZSBvYnZpb3VzbHkgbm9uLWxpbmVhci4NCg0KYGBge3J9DQpnbG0ucHJvYnMyIDwtIHByZWRpY3QoZ2xtLmZpdDIsIGRhdCwgdHlwZSA9ICJyZXNwb25zZSIpDQpnbG0ucHJlZDIgPC0gaWZlbHNlKGdsbS5wcm9iczIgPiAwLjUsIDEsIDApDQp0YWJsZShwcmVkaWN0ZWQgPSBnbG0ucHJlZDIsIGFjdHVhbCA9IHkpDQoNCnBsb3QoeDEsIHgyLCBjb2wgPSAoZ2xtLnByZWQyICsgMiksIHBjaCA9IDE5LA0KICAgICB4bGFiID0gIlgxIiwgeWxhYiA9ICJYMiIsIG1haW4gPSAiTG9naXN0aWMgUmVncmVzc2lvbiAoTm9uLWxpbmVhcikiKQ0KbGVnZW5kKCJ0b3ByaWdodCIsIGxlZ2VuZCA9IGMoInByZWQgPSAwIiwgInByZWQgPSAxIiksIGNvbCA9IGMoMiwgMyksIHBjaCA9IDE5KQ0KYGBgDQoNCg0KIyMgKGcpIA0KPkZpdCBhIHN1cHBvcnQgdmVjdG9yIGNsYXNzaWZpZXIgdG8gdGhlIGRhdGEgd2l0aCBYMSBhbmQgWDIgYXMNCnByZWRpY3RvcnMuIE9idGFpbiBhIGNsYXNzIHByZWRpY3Rpb24gZm9yIGVhY2ggdHJhaW5pbmcgb2JzZXJ2YXRpb24uDQpQbG90IHRoZSBvYnNlcnZhdGlvbnMsIGNvbG9yZWQgYWNjb3JkaW5nIHRvIHRoZSBwcmVkaWN0ZWQNCmNsYXNzIGxhYmVscy4NCg0KYGBge3J9DQpsaWJyYXJ5KGNhcmV0KQ0KZGF0YSA8LSBkYXRhLmZyYW1lKHgxID0geDEsIHgyID0geDIsIHkgPSBhcy5mYWN0b3IoeSkpDQoNCnRyYWluX2NvbnRyb2wgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDEwKQ0Kc3ZtX2xpbmVhciA8LSB0cmFpbih5fngxK3gyLCBkYXRhID0gZGF0YSwgbWV0aG9kID0gInN2bUxpbmVhciIsIHRyQ29udHJvbCA9IHRyYWluX2NvbnRyb2wsIA0KICAgICAgICAgICAgICAgICAgICBwcmVQcm9jZXNzID0gYygiY2VudGVyIiwic2NhbGUiKSkNCnN2bV9saW5lYXJfcHJlZCA8LSBwcmVkaWN0KHN2bV9saW5lYXIsIGRhdGEpDQoNCnRhYmxlKHByZWRpY3RlZCA9IHN2bV9saW5lYXJfcHJlZCwgYWN0dWFsID0gZGF0YSR5KQ0KDQpwbG90KHgxLCB4MiwgY29sID0gKGFzLm51bWVyaWMoc3ZtX2xpbmVhcl9wcmVkKSArIDEpLCBwY2ggPSAxOSwNCiAgICAgeGxhYiA9ICJYMSIsIHlsYWIgPSAiWDIiLCBtYWluID0gIlNWTSBDbGFzc2lmaWVyIChMaW5lYXIpIikNCmxlZ2VuZCgidG9wcmlnaHQiLCBsZWdlbmQgPSBjKCJwcmVkID0gMCIsICJwcmVkID0gMSIpLCBjb2wgPSBjKDIsIDMpLCBwY2ggPSAxOSkNCmBgYA0KDQoNCiMjIChoKSANCj5GaXQgYSBTVk0gdXNpbmcgYSBub24tbGluZWFyIGtlcm5lbCB0byB0aGUgZGF0YS4gT2J0YWluIGEgY2xhc3MNCnByZWRpY3Rpb24gZm9yIGVhY2ggdHJhaW5pbmcgb2JzZXJ2YXRpb24uIFBsb3QgdGhlIG9ic2VydmF0aW9ucywNCmNvbG9yZWQgYWNjb3JkaW5nIHRvIHRoZSBwcmVkaWN0ZWQgY2xhc3MgbGFiZWxzLg0KDQpgYGB7cn0NCnRyYWluX2NvbnRyb2wgPC0gdHJhaW5Db250cm9sKG1ldGhvZCA9ICJjdiIsIG51bWJlciA9IDEwKQ0Kc3ZtX3JhZGlhbCA8LSB0cmFpbih5fngxK3gyLCBkYXRhID0gZGF0YSwgbWV0aG9kID0gInN2bVJhZGlhbCIsIHRyQ29udHJvbCA9IHRyYWluX2NvbnRyb2wsDQogICAgICAgICAgICAgICAgICAgIHByZVByb2Nlc3MgPSBjKCJjZW50ZXIiLCJzY2FsZSIpKQ0Kc3ZtX3JhZGlhbF9wcmVkIDwtIHByZWRpY3Qoc3ZtX3JhZGlhbCwgZGF0YSkNCg0KdGFibGUocHJlZGljdGVkID0gc3ZtX3JhZGlhbF9wcmVkLCBhY3R1YWwgPSBkYXRhJHkpDQoNCnBsb3QoeDEsIHgyLCBjb2wgPSAoYXMubnVtZXJpYyhzdm1fcmFkaWFsX3ByZWQpICsgMSksIHBjaCA9IDE5LA0KICAgICB4bGFiID0gIlgxIiwgeWxhYiA9ICJYMiIsIG1haW4gPSAiU1ZNIChSYWRpYWwgS2VybmVsKSIpDQpsZWdlbmQoInRvcHJpZ2h0IiwgbGVnZW5kID0gYygicHJlZCA9IDAiLCAicHJlZCA9IDEiKSwgY29sID0gYygyLCAzKSwgcGNoID0gMTkpDQpgYGANCg0KDQoNCiMjIChpKSANCj5Db21tZW50IG9uIHlvdXIgcmVzdWx0cy4NCg0KVGhlIGxvZ2lzdGljIHJlZ3Jlc3Npb24gbW9kZWwgcGVyZm9ybXMgcG9vcmx5IGJlY2F1c2UgdGhlIHRydWUgZGVjaXNpb24gYm91bmRhcnkgaXMgcXVhZHJhdGljIGluc3RlYWQgb2YgIGxpbmVhci4gQmVjYXVzZSBvZiB0aGlzLCB0aGUgbW9kZWwgaW5jb3JyZWN0bHkgY2xhc3NpZmllcyBtYW55IG9ic2VydmF0aW9ucyBhbmQgcHJvZHVjZXMgYSBsaW5lYXIgZGVjaXNpb24gYm91bmRhcnkgdGhhdCBkb2VzIG5vdCBzZXBhcmF0ZSB0aGUgdHdvIGNsYXNzZXMuIEFmdGVyIGFkZGluZyBxdWFkcmF0aWMgdGVybXMsIHRoZSBsb2dpc3RpYyByZWdyZXNzaW9uIG1vZGVsIGlzIGFibGUgdG8gY2FwdHVyZSB0aGUgbm9uLWxpbmVhciByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgcHJlZGljdG9ycy5UaGUgbGluZWFyIHN1cHBvcnQgdmVjdG9yIGNsYXNzaWZpZXIgcGVyZm9ybXMgc2ltaWxhcmx5IHRvIHRoZSBsaW5lYXIgbG9naXN0aWMgcmVncmVzc2lvbiBtb2RlbCBiZWNhdXNlIGl0IGlzIGFsc28gcmVzdHJpY3RlZCB0byBhIGxpbmVhciBkZWNpc2lvbiBib3VuZGFyeS4gVXNpbmcgYSBub24tbGluZWFyIGtlcm5lbCBhbGxvd3MgdGhlIFNWTSB0byBtb2RlbCB0aGUgcXVhZHJhdGljIGRlY2lzaW9uIGJvdW5kYXJ5IG11Y2ggbW9yZSBlZmZlY3RpdmVseS4NCg0KDQoNCiMgUXVlc3Rpb24gNw0KPkluIHRoaXMgcHJvYmxlbSwgeW91IHdpbGwgdXNlIHN1cHBvcnQgdmVjdG9yIGFwcHJvYWNoZXMgaW4gb3JkZXIgdG8NCnByZWRpY3Qgd2hldGhlciBhIGdpdmVuIGNhciBnZXRzIGhpZ2ggb3IgbG93IGdhcyBtaWxlYWdlIGJhc2VkIG9uIHRoZQ0KQXV0byBkYXRhIHNldC4NCg0KIyMgKGEpIA0KPkNyZWF0ZSBhIGJpbmFyeSB2YXJpYWJsZSB0aGF0IHRha2VzIG9uIGEgMSBmb3IgY2FycyB3aXRoIGdhcw0KbWlsZWFnZSBhYm92ZSB0aGUgbWVkaWFuLCBhbmQgYSAwIGZvciBjYXJzIHdpdGggZ2FzIG1pbGVhZ2UNCmJlbG93IHRoZSBtZWRpYW4uDQoNCmBgYHtyfQ0KbGlicmFyeShJU0xSMikNCkF1dG8kbXBnMDEgPC0gYXMuZmFjdG9yKGlmZWxzZShBdXRvJG1wZyA+IG1lZGlhbihBdXRvJG1wZyksIDEsIDApKQ0KYGBgDQoNCg0KIyMgKGIpIA0KPkZpdCBhIHN1cHBvcnQgdmVjdG9yIGNsYXNzaWZpZXIgdG8gdGhlIGRhdGEgd2l0aCB2YXJpb3VzIHZhbHVlcw0Kb2YgY29zdCwgaW4gb3JkZXIgdG8gcHJlZGljdCB3aGV0aGVyIGEgY2FyIGdldHMgaGlnaCBvciBsb3cgZ2FzDQptaWxlYWdlLiBSZXBvcnQgdGhlIGNyb3NzLXZhbGlkYXRpb24gZXJyb3JzIGFzc29jaWF0ZWQgd2l0aCBkaWZmZXJlbnQNCnZhbHVlcyBvZiB0aGlzIHBhcmFtZXRlci4gQ29tbWVudCBvbiB5b3VyIHJlc3VsdHMuIE5vdGUNCnlvdSB3aWxsIG5lZWQgdG8gZml0IHRoZSBjbGFzc2lmaWVyIHdpdGhvdXQgdGhlIGdhcyBtaWxlYWdlIHZhcmlhYmxlDQp0byBwcm9kdWNlIHNlbnNpYmxlIHJlc3VsdHMuDQoNCmBgYHtyfQ0KbGlicmFyeShlMTA3MSkNCnNldC5zZWVkKDEpDQp0dW5lLm91dCA8LSB0dW5lKHN2bSwgbXBnMDEgfiAuIC0gbXBnIC0gbmFtZSwgZGF0YSA9IEF1dG8sIGtlcm5lbCA9ICJsaW5lYXIiLA0KICAgICAgICAgICAgICAgICAgcmFuZ2VzID0gbGlzdChjb3N0ID0gYygwLjAwMSwgMC4wMSwgMC4xLCAxLCA1LCAxMCwgMTAwKSkpDQpzdW1tYXJ5KHR1bmUub3V0KQ0KYGBgDQpTbWFsbCB2YWx1ZXMgb2YgY29zdCBsZWFkcyB0byBtb3JlIGNsYXNzaWZpY2F0aW9uIGVycm9ycyB3aGlsZSBsYXJnZXIgdmFsdWVzIHdpbGwgY2xhc3NpZnkgdGhlIHRyYWluaW5nIGRhdGEgbW9yZSBhY2N1cmF0ZWx5LiBIYXZpbmcgYSBsb3dlciBjcm9zcy12YWxpZGF0aW9uIGVycm9yIHJlc3VsdHMgaW4gYSBtb3JlIG9wdGltYWwgbW9kZWwuDQoNCiMjIChjKSANCj5Ob3cgcmVwZWF0IChiKSwgdGhpcyB0aW1lIHVzaW5nIFNWTXMgd2l0aCByYWRpYWwgYW5kIHBvbHlub21pYWwNCmJhc2lzIGtlcm5lbHMsIHdpdGggZGlmZmVyZW50IHZhbHVlcyBvZiBnYW1tYSBhbmQgZGVncmVlIGFuZA0KY29zdC4gQ29tbWVudCBvbiB5b3VyIHJlc3VsdHMuDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMSkNCnR1bmUucmFkaWFsIDwtIHR1bmUoc3ZtLCBtcGcwMSB+IC4gLSBtcGcgLSBuYW1lLCBkYXRhID0gQXV0bywga2VybmVsID0gInJhZGlhbCIsIA0KICAgICAgICAgICAgICAgICAgICByYW5nZXMgPSBsaXN0KGNvc3QgPSBjKDAuMSwgMSwgNSwgMTAsIDEwMCksDQogICAgICAgICAgICAgICAgICAgIGdhbW1hID0gYygwLjAxLCAwLjEsIDEsIDUsIDEwKSkpDQpzdW1tYXJ5KHR1bmUucmFkaWFsKQ0KYGBgDQpgYGB7cn0NCnNldC5zZWVkKDEpDQp0dW5lLnBvbHkgPC0gdHVuZShzdm0sIG1wZzAxIH4gLiAtIG1wZyAtIG5hbWUsIGRhdGEgPSBBdXRvLCBrZXJuZWwgPSAicG9seW5vbWlhbCIsDQogICAgICAgICAgICAgICAgICByYW5nZXMgPSBsaXN0KGNvc3QgPSBjKDAuMSwgMSwgNSwgMTAsIDEwMCksDQogICAgICAgICAgICAgICAgICBkZWdyZWUgPSBjKDIsIDMsIDQpKSkNCnN1bW1hcnkodHVuZS5wb2x5KQ0KYGBgDQpUaGUgcmFkaWFsIGtlcm5lbCBoYXMgYSBsb3dlciBjcm9zcyB2YWxpZGF0aW9uIGVycm9yIHRoYXQgaGUgbGluZWFyIGtlcm5lbCBiZWNhdXNlIG9mIHRoZSByZWxhdGlvbnNoaXAgd2l0aCB0aGUgcHJlZGljdG9yIHZhcmlhYmxlcy4gVGhlIHBvbHlub21pYWwga2VybmVsIGFsc28gaW1wcm92ZXMgcGVyZm9ybWFuY2Ugb3ZlciB0aGUgbGluZWFyIGNsYXNzaWZpZXIuIA0KDQojIyAoZCkgDQo+TWFrZSBzb21lIHBsb3RzIHRvIGJhY2sgdXAgeW91ciBhc3NlcnRpb25zIGluIChiKSBhbmQgKGMpLg0KSGludDogSW4gdGhlIGxhYiwgd2UgdXNlZCB0aGUgcGxvdCgpIGZ1bmN0aW9uIGZvciBzdm0gb2JqZWN0cw0Kb25seSBpbiBjYXNlcyB3aXRoIHAgPSAyLiBXaGVuIHAgPiAyLCB5b3UgY2FuIHVzZSB0aGUgcGxvdCgpDQpmdW5jdGlvbiB0byBjcmVhdGUgcGxvdHMgZGlzcGxheWluZyBwYWlycyBvZiB2YXJpYWJsZXMgYXQgYSB0aW1lLg0KRXNzZW50aWFsbHksIGluc3RlYWQgb2YgdHlwaW5nDQo+IHBsb3Qoc3ZtZml0ICwgZGF0KQ0Kd2hlcmUgc3ZtZml0IGNvbnRhaW5zIHlvdXIgZml0dGVkIG1vZGVsIGFuZCBkYXQgaXMgYSBkYXRhIGZyYW1lDQpjb250YWluaW5nIHlvdXIgZGF0YSwgeW91IGNhbiB0eXBlDQo+IHBsb3Qoc3ZtZml0ICwgZGF0ICwgeDEg4oi8IHg0KQ0KaW4gb3JkZXIgdG8gcGxvdCBqdXN0IHRoZSBmaXJzdCBhbmQgZm91cnRoIHZhcmlhYmxlcy4gSG93ZXZlciwgeW91DQptdXN0IHJlcGxhY2UgeDEgYW5kIHg0IHdpdGggdGhlIGNvcnJlY3QgdmFyaWFibGUgbmFtZXMuIFRvIGZpbmQNCm91dCBtb3JlLCB0eXBlID9wbG90LnN2bS4NCg0KYGBge3J9DQpkYXQgPC0gQXV0b1ssICEobmFtZXMoQXV0bykgJWluJSBjKCJtcGciLCAibmFtZSIpKV0NCg0Kc3ZtLmxpbmVhciA8LSBzdm0obXBnMDEgfiAuLCBkYXRhID0gZGF0LCBrZXJuZWwgPSAibGluZWFyIiwgY29zdCA9IDEpDQpzdm0ucmFkaWFsIDwtIHN2bShtcGcwMSB+IC4sIGRhdGEgPSBkYXQsIGtlcm5lbCA9ICJyYWRpYWwiLCBjb3N0ID0gMSwgZ2FtbWEgPSAxKQ0Kc3ZtLnBvbHkgICA8LSBzdm0obXBnMDEgfiAuLCBkYXRhID0gZGF0LCBrZXJuZWwgPSAicG9seW5vbWlhbCIsIGNvc3QgPSAxMDAsIGRlZ3JlZSA9IDMpDQoNCnNsYyA8LSBsaXN0KGN5bGluZGVycyA9IG1lYW4oZGF0JGN5bGluZGVycyksIGRpc3BsYWNlbWVudCA9IG1lYW4oZGF0JGRpc3BsYWNlbWVudCksDQogICAgICAgICAgICBhY2NlbGVyYXRpb24gPSBtZWFuKGRhdCRhY2NlbGVyYXRpb24pLCB5ZWFyID0gbWVhbihkYXQkeWVhciksDQogICAgICAgICAgICBvcmlnaW4gPSBtZWFuKGRhdCRvcmlnaW4pKQ0KDQpwbG90KHN2bS5saW5lYXIsIGRhdCwgaG9yc2Vwb3dlciB+IHdlaWdodCwgc2xpY2UgPSBzbGMpDQpwbG90KHN2bS5yYWRpYWwsIGRhdCwgaG9yc2Vwb3dlciB+IHdlaWdodCwgc2xpY2UgPSBzbGMpDQpwbG90KHN2bS5wb2x5LCAgIGRhdCwgaG9yc2Vwb3dlciB+IHdlaWdodCwgc2xpY2UgPSBzbGMpDQpgYGANCg0KDQoNCiMgUXVlc3Rpb24gOA0KPlRoaXMgcHJvYmxlbSBpbnZvbHZlcyB0aGUgT0ogZGF0YSBzZXQgd2hpY2ggaXMgcGFydCBvZiB0aGUgSVNMUjINCnBhY2thZ2UuDQoNCiMjIChhKQ0KPkNyZWF0ZSBhIHRyYWluaW5nIHNldCBjb250YWluaW5nIGEgcmFuZG9tIHNhbXBsZSBvZiA4MDANCm9ic2VydmF0aW9ucywgYW5kIGEgdGVzdCBzZXQgY29udGFpbmluZyB0aGUgcmVtYWluaW5nDQpvYnNlcnZhdGlvbnMuDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMSkNCnRyYWluIDwtIHNhbXBsZSgxOm5yb3coT0opLCA4MDApDQpPSi50cmFpbiA8LSBPSlt0cmFpbiwgXQ0KT0oudGVzdCA8LSBPSlstdHJhaW4sIF0NCmBgYA0KDQoNCiMjIChiKSANCj5GaXQgYSBzdXBwb3J0IHZlY3RvciBjbGFzc2lmaWVyIHRvIHRoZSB0cmFpbmluZyBkYXRhIHVzaW5nDQpjb3N0ID0gMC4wMSwgd2l0aCBQdXJjaGFzZSBhcyB0aGUgcmVzcG9uc2UgYW5kIHRoZSBvdGhlciB2YXJpYWJsZXMNCmFzIHByZWRpY3RvcnMuIFVzZSB0aGUgc3VtbWFyeSgpIGZ1bmN0aW9uIHRvIHByb2R1Y2Ugc3VtbWFyeQ0Kc3RhdGlzdGljcywgYW5kIGRlc2NyaWJlIHRoZSByZXN1bHRzIG9idGFpbmVkLg0KDQpgYGB7cn0NCnN2bS5saW5lYXIgPC0gc3ZtKFB1cmNoYXNlIH4gLiwgZGF0YSA9IE9KLnRyYWluLCBrZXJuZWwgPSAibGluZWFyIiwgY29zdCA9IDAuMDEpDQoNCnN1bW1hcnkoc3ZtLmxpbmVhcikNCmBgYA0KVGhlIHN1cHBvcnQgdmVjdG9yIGNsYXNzaWZpZXIgd2FzIGZpdCB1c2luZyBhIGxpbmVhciBrZXJuZWwgd2l0aCBhIGNvc3Qgb2YgMC4wMS4gVGhlIGNsYXNzaWZpZXIgYWxsb3dzIG1vcmUgb2JzZXJ2YXRpb25zIHRvIGZhbGwgd2l0aGluIHRoZSBtYXJnaW4gd2hpY2ggcmVzdWx0cyBpbiBhIHNtb290aGVyIGRlY2lzaW9uIGJvdW5kYXJ5Lg0KDQojIyAoYykgDQo+V2hhdCBhcmUgdGhlIHRyYWluaW5nIGFuZCB0ZXN0IGVycm9yIHJhdGVzPw0KDQpgYGB7cn0NCnRyYWluLnByZWQgPC0gcHJlZGljdChzdm0ubGluZWFyLCBPSi50cmFpbikNCg0KdGVzdC5wcmVkIDwtIHByZWRpY3Qoc3ZtLmxpbmVhciwgT0oudGVzdCkNCg0KdGVzdC5lcnJvciA8LSBtZWFuKHRlc3QucHJlZCAhPSBPSi50ZXN0JFB1cmNoYXNlKQ0KDQp0cmFpbi5lcnJvcg0KdGVzdC5lcnJvcg0KYGBgDQoNCg0KIyMgKGQpIA0KPlVzZSB0aGUgdHVuZSgpIGZ1bmN0aW9uIHRvIHNlbGVjdCBhbiBvcHRpbWFsIGNvc3QuIENvbnNpZGVyIHZhbHVlcw0KaW4gdGhlIHJhbmdlIDAuMDEgdG8gMTAuDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMSkNCnR1bmUubGluZWFyIDwtIHR1bmUoc3ZtLCBQdXJjaGFzZSB+IC4sIGRhdGEgPSBPSi50cmFpbiwga2VybmVsID0gImxpbmVhciIsIHJhbmdlcyA9IGxpc3QoY29zdCA9IGMoMC4wMSwgMC4xLCAxLCA1LCAxMCkpKQ0KDQpzdW1tYXJ5KHR1bmUubGluZWFyKQ0KYmVzdC5saW5lYXIgPC0gdHVuZS5saW5lYXIkYmVzdC5tb2RlbA0KYGBgDQoNCiMjIChlKSANCj5Db21wdXRlIHRoZSB0cmFpbmluZyBhbmQgdGVzdCBlcnJvciByYXRlcyB1c2luZyB0aGlzIG5ldyB2YWx1ZQ0KZm9yIGNvc3QuDQoNCmBgYHtyfQ0KdHJhaW4ucHJlZC5iZXN0IDwtIHByZWRpY3QoYmVzdC5saW5lYXIsIE9KLnRyYWluKQ0KdHJhaW4uZXJyb3IuYmVzdCA8LSBtZWFuKHRyYWluLnByZWQuYmVzdCAhPSBPSi50cmFpbiRQdXJjaGFzZSkNCg0KdGVzdC5wcmVkLmJlc3QgPC0gcHJlZGljdChiZXN0LmxpbmVhciwgT0oudGVzdCkNCnRlc3QuZXJyb3IuYmVzdCA8LSBtZWFuKHRlc3QucHJlZC5iZXN0ICE9IE9KLnRlc3QkUHVyY2hhc2UpDQoNCnRyYWluLmVycm9yLmJlc3QNCnRlc3QuZXJyb3IuYmVzdA0KYGBgDQoNCg0KIyMgKGYpIA0KPlJlcGVhdCBwYXJ0cyAoYikgdGhyb3VnaCAoZSkgdXNpbmcgYSBzdXBwb3J0IHZlY3RvciBtYWNoaW5lDQp3aXRoIGEgcmFkaWFsIGtlcm5lbC4gVXNlIHRoZSBkZWZhdWx0IHZhbHVlIGZvciBnYW1tYS4NCg0KYGBge3J9DQpzdm0ucmFkaWFsIDwtIHN2bShQdXJjaGFzZSB+IC4sIGRhdGEgPSBPSi50cmFpbiwga2VybmVsID0gInJhZGlhbCIpDQpzdW1tYXJ5KHN2bS5yYWRpYWwpDQoNCnRyYWluLnByZWQgPC0gcHJlZGljdChzdm0ucmFkaWFsLCBPSi50cmFpbikNCnRlc3QucHJlZCA8LSBwcmVkaWN0KHN2bS5yYWRpYWwsIE9KLnRlc3QpDQoNCm1lYW4odHJhaW4ucHJlZCAhPSBPSi50cmFpbiRQdXJjaGFzZSkNCm1lYW4odGVzdC5wcmVkICE9IE9KLnRlc3QkUHVyY2hhc2UpDQpgYGANCg0KDQpgYGB7cn0NCnNldC5zZWVkKDEpDQp0dW5lLnJhZGlhbCA8LSB0dW5lKHN2bSwgUHVyY2hhc2UgfiAuLCBkYXRhID0gT0oudHJhaW4sIGtlcm5lbCA9ICJyYWRpYWwiLCByYW5nZXMgPSBsaXN0KGNvc3QgPSBjKDAuMDEsIDAuMSwgMSwgNSwgMTApKSkNCnN1bW1hcnkodHVuZS5yYWRpYWwpDQpiZXN0LnJhZGlhbCA8LSB0dW5lLnJhZGlhbCRiZXN0Lm1vZGVsDQoNCm1lYW4ocHJlZGljdChiZXN0LnJhZGlhbCwgT0oudHJhaW4pICE9IE9KLnRyYWluJFB1cmNoYXNlKQ0KbWVhbihwcmVkaWN0KGJlc3QucmFkaWFsLCBPSi50ZXN0KSAhPSBPSi50ZXN0JFB1cmNoYXNlKQ0KYGBgDQoNCg0KIyMgKGcpIA0KPlJlcGVhdCBwYXJ0cyAoYikgdGhyb3VnaCAoZSkgdXNpbmcgYSBzdXBwb3J0IHZlY3RvciBtYWNoaW5lDQp3aXRoIGEgcG9seW5vbWlhbCBrZXJuZWwuIFNldCBkZWdyZWUgPSAyLg0KDQpgYGB7cn0NCnN2bS5wb2x5IDwtIHN2bShQdXJjaGFzZSB+IC4sIGRhdGEgPSBPSi50cmFpbiwga2VybmVsID0gInBvbHlub21pYWwiLCBkZWdyZWUgPSAyKQ0Kc3VtbWFyeShzdm0ucG9seSkNCg0KbWVhbihwcmVkaWN0KHN2bS5wb2x5LCBPSi50cmFpbikgIT0gT0oudHJhaW4kUHVyY2hhc2UpDQptZWFuKHByZWRpY3Qoc3ZtLnBvbHksIE9KLnRlc3QpICE9IE9KLnRlc3QkUHVyY2hhc2UpDQpgYGANCg0KYGBge3J9DQpzZXQuc2VlZCgxKQ0KdHVuZS5wb2x5IDwtIHR1bmUoc3ZtLCBQdXJjaGFzZSB+IC4sIGRhdGEgPSBPSi50cmFpbiwga2VybmVsID0gInBvbHlub21pYWwiLCBkZWdyZWUgPSAyLCByYW5nZXMgPSBsaXN0KGNvc3QgPSBjKDAuMDEsIDAuMSwgMSwgNSwgMTApKSkNCnN1bW1hcnkodHVuZS5wb2x5KQ0KYmVzdC5wb2x5IDwtIHR1bmUucG9seSRiZXN0Lm1vZGVsDQoNCm1lYW4ocHJlZGljdChiZXN0LnBvbHksIE9KLnRyYWluKSAhPSBPSi50cmFpbiRQdXJjaGFzZSkNCm1lYW4ocHJlZGljdChiZXN0LnBvbHksIE9KLnRlc3QpICE9IE9KLnRlc3QkUHVyY2hhc2UpDQpgYGANCg0KDQojIyAoaCkNCj5PdmVyYWxsLCB3aGljaCBhcHByb2FjaCBzZWVtcyB0byBnaXZlIHRoZSBiZXN0IHJlc3VsdHMgb24gdGhpcw0KZGF0YT8NCg0KVGhlIHJhZGlhbCBrZXJuZWwgaGFzIHRoZSBiZXN0IHJlc3VsdHMgYXMgaXQgY2FuIGNhcHR1cmUgbm9uLWxpbmVhciByZWxhdGlvbnNoaXBzIHdpdGhvdXQgb3ZlciBmaXR0aW5nIGFzIG11Y2ggYXMgdGhlIHBvbHlub21pYWwga2VybmVsLiBUaGUgbGluZWFyIGNsYXNzaWZpZXIgcGVyZm9ybXMgd2VsbCBidXQgZG9lcyBub3QgY2FwdHVyZSBhcyBtdWNoIHJlbGF0aW9uc2hpcHMgYXMgdGhlIHJhZGlhbCBrZXJuZWwuDQoNCg0K