K-Nearest Neighbors Classification

The K-Nearest Neighbors Classification algorithm classifies observations by allowing the K observations in the training set that are nearest to the new observation to “vote” on the class of the new observation. More specifically, the algorithm works as follows:

  1. Calculate the distance between the new observation and EACH obsevation in the training set. Distances are calculated within the feature space.
  2. Select the K observations in the training set that are nearest to the new observation.
  3. Determine the majority class within the K nearest neighbors.
  4. Classify the new observation as this majority class.
  5. If there is a tie in the vote, resolve this according to some pre-determine scheme.

We will demonstrate this classification algorithm through examples.

Load Packages

library(ggplot2)
library(gridExtra)
library(caret)
library(class)

We will use ggplot2 and gridExtra for plotting. The carat packages will be used for splitting our data and for feature scaling. The functionality for performing KNN classification is provided by the class package.

Example 1: Iris Dataset

For this example, we will be working with the Iris Dataset. This data set is a real-world “toy” dataset that is often used to demonstrate concepts in data science. The iris dataset contains information about several flowers selected from three different species of iris: versicolor, setosa, and virginica.

The dataset contains the following five pieces of information for 150 flowers:

Load and Explore the Data

iris_tr <- read.table('data/iris.txt', sep='\t', header=TRUE)
summary(iris_tr)
  sepal_length    sepal_width     petal_length    petal_width          species  
 Min.   :4.300   Min.   :2.000   Min.   :1.000   Min.   :0.100   setosa    :50  
 1st Qu.:5.100   1st Qu.:2.800   1st Qu.:1.600   1st Qu.:0.300   versicolor:50  
 Median :5.800   Median :3.000   Median :4.350   Median :1.300   virginica :50  
 Mean   :5.843   Mean   :3.057   Mean   :3.758   Mean   :1.199                  
 3rd Qu.:6.400   3rd Qu.:3.300   3rd Qu.:5.100   3rd Qu.:1.800                  
 Max.   :7.900   Max.   :4.400   Max.   :6.900   Max.   :2.500                  
p1 <- ggplot(iris_tr, aes(x=sepal_length, y=sepal_width, col=species)) +
  geom_point(alpha=0.8)

p2 <- ggplot(iris_tr, aes(x=petal_length, y=petal_width, col=species)) +
  geom_point(alpha=0.8)

grid.arrange(p1, p2, ncol=2)

Build a 3-Nearest Neighbors Model

We will now use the knn function from the class package to create a 3-Nearest Neighbors model for the iris dataset.

iris_tr_feat <- iris_tr[,1:4]
set.seed(1)
train_pred <- knn(iris_tr_feat, iris_tr_feat, iris_tr$species, k=3)
train_pred[1:10]
 [1] setosa     versicolor virginica  setosa     versicolor versicolor versicolor
 [8] virginica  versicolor setosa    
Levels: setosa versicolor virginica

Let’s calculate the model’s training accuracy.

accuracy <- mean(train_pred == iris_tr$species)
cat("Training Accuracy: ", accuracy, sep='')
Training Accuracy: 0.96

Effects of Changing K

The selection of K=3 in the previous model was somewhat arbitrary. In the figure below, we will plot the KNN model’s training accuracy for several values of K.

train_acc <- c()

for (i in 1:150){
  set.seed(1)
  train_pred <- knn(iris_tr_feat, iris_tr_feat, iris_tr$species, k=i)
  train_acc <- c(train_acc, mean(train_pred == iris_tr$species))
}

plot(1:150, train_acc, pch='.', ylim=c(0.3, 1), col='salmon')
lines(1:150, train_acc, lwd=2, col='salmon')

Validation Set

KNN model’s will (almost) always get 100% accuracy on the training set when K=1. For this reason, it is not helpful to use training performance to select the best value of K for our dataset. To select the value of K for which the model is most likely to generalize well, we need to create a validation set.

iris_va <- read.table('data/iris_valid.txt', sep='\t', header=TRUE)
summary(iris_va)
  sepal_length    sepal_width     petal_length   petal_width          species  
 Min.   :4.600   Min.   :2.100   Min.   :1.30   Min.   :0.200   setosa    :10  
 1st Qu.:5.200   1st Qu.:2.825   1st Qu.:1.55   1st Qu.:0.400   versicolor:11  
 Median :5.900   Median :3.000   Median :4.30   Median :1.400   virginica : 9  
 Mean   :5.883   Mean   :3.033   Mean   :3.73   Mean   :1.193                  
 3rd Qu.:6.375   3rd Qu.:3.200   3rd Qu.:4.80   3rd Qu.:1.800                  
 Max.   :7.800   Max.   :3.900   Max.   :6.70   Max.   :2.300                  

Selecting K

We will now calculate validation accuracy for each of our models, and will use this information to select our final model.

train_acc <- c()
valid_acc <- c()

iris_va_feat <- iris_va[,1:4]


for (i in 1:100){
  set.seed(1)
  train_pred <- knn(iris_tr_feat, iris_tr_feat, iris_tr$species, k=i)
  train_acc <- c(train_acc, mean(train_pred == iris_tr$species))
  
  set.seed(1)
  valid_pred <- knn(iris_tr_feat, iris_va_feat, iris_tr$species, k=i)
  valid_acc <- c(valid_acc, mean(valid_pred == iris_va$species))
}

plot(1:100, train_acc, pch='.', ylim=c(0.8, 1), col='salmon')
lines(1:100, train_acc, lwd=2, col='salmon')
lines(1:100, valid_acc, lwd=2, col='cornflowerblue')
legend(38, 1, legend=c("Training Acc", "Validation Acc"),
       col=c("salmon", "cornflowerblue"), lty=1, lwd=2, cex=0.8)

The largest validation accuracy obtained by any model was 93.33%.

max(valid_acc)
[1] 0.9333333

The maximum validation accuracy was obtained by the K=11 model.

which.max(valid_acc)
[1] 11

Example 2: Pima Diabetes Dataset

For this example, we will be working with the Pima Diabetes Dataset. This dataset is originally from the National Institute of Diabetes and Digestive and Kidney Diseases. The objective is to predict based on diagnostic measurements whether a patient has diabetes. All patients are females at least 21 years old of Pima Indian heritage.

The columns in this dataset are described below.

Load the Data

pima <- read.table("data/diabetes.csv", sep=",", header=TRUE)
pima$Outcome <- factor(pima$Outcome)
summary(pima)
  Pregnancies        Glucose      BloodPressure    SkinThickness  
 Min.   : 0.000   Min.   :  0.0   Min.   :  0.00   Min.   : 0.00  
 1st Qu.: 1.000   1st Qu.: 99.0   1st Qu.: 62.00   1st Qu.: 0.00  
 Median : 3.000   Median :117.0   Median : 72.00   Median :23.00  
 Mean   : 3.845   Mean   :120.9   Mean   : 69.11   Mean   :20.54  
 3rd Qu.: 6.000   3rd Qu.:140.2   3rd Qu.: 80.00   3rd Qu.:32.00  
 Max.   :17.000   Max.   :199.0   Max.   :122.00   Max.   :99.00  
    Insulin           BMI        DiabetesPedigreeFunction      Age       
 Min.   :  0.0   Min.   : 0.00   Min.   :0.0780           Min.   :21.00  
 1st Qu.:  0.0   1st Qu.:27.30   1st Qu.:0.2437           1st Qu.:24.00  
 Median : 30.5   Median :32.00   Median :0.3725           Median :29.00  
 Mean   : 79.8   Mean   :31.99   Mean   :0.4719           Mean   :33.24  
 3rd Qu.:127.2   3rd Qu.:36.60   3rd Qu.:0.6262           3rd Qu.:41.00  
 Max.   :846.0   Max.   :67.10   Max.   :2.4200           Max.   :81.00  
 Outcome
 0:500  
 1:268  
        
        
        
        

Split the Data

We will use createDataPartition to create a stratified partition of our data into training and validation sets.

set.seed(1)
train.index <- createDataPartition(pima$Outcome, p = .7, list=FALSE)
train <- pima[ train.index,]
valid  <- pima[-train.index,]

summary(train$Outcome)
  0   1 
350 188 
summary(valid$Outcome)
  0   1 
150  80 

3-Nearest Neighbors

For practice, we will create and evaluate a 3-Nearest Neighbors model.

train_feat <- train[,1:8] 
valid_feat <- valid[,1:8] 

set.seed(1)
train_pred <- knn(train_feat, train_feat, train$Outcome, k=3)
train_acc <- mean(train_pred == train$Outcome)

set.seed(1)
valid_pred <- knn(train_feat, valid_feat, train$Outcome, k=3)
valid_acc <- mean(valid_pred == valid$Outcome)

cat('Training Accuracy:   ', train_acc, '\n',
    'Validation Accuracy: ', valid_acc, sep='')
Training Accuracy:   0.8513011
Validation Accuracy: 0.7130435

Feature Scaling

For many machine learning algorithms, it is important to make sure that the features are on roughly the same scale before training. This is especially true of distance-based algorithms such as K-Nearest Neighbors.

We will consider two types of scaling in this course: Standardization and Min/Max Scaling.

Standardization

With standardization, each column in the training set is scaled to have a mean of zero and a standard deviation of 1. If \(x_i\) is a single observation of a particular feature, then its scaled value \(z_i\) is given by:

\[ z_i = \frac{x_i - \bar x}{s_x}\] Note that the values \(\bar x\) and \(s_x\) are calculated from the training set only, but are used to scale any observation, whether it is from the training set, validation set, testing set, or an entirely new observation.

Min/Max Scaling

With min max scaling, each column in the training set is scaled linearly to have a minimum of zero and a maximum of 1. If \(x_i\) is a single observation of a particular feature, then its scaled value \(w_i\) is given by:

\[ w_i = \frac{x_i - \textrm{min}_x}{\textrm{max}_x - \textrm{min}_x}\] Note that the values \(\textrm{min}_x\) and \(\textrm{max}_x\) are calculated from the training set only, but are used to scale any observation, whether it is from the training set, validation set, testing set, or an entirely new observation.

Performing Feature Scaling

We can perform feature scaling using the preProcess function from the carat package.

We will start by performing standard scaling.

standard_scaler <- preProcess(train_feat, method=c('center', 'scale'))
train_s_sc <- predict(standard_scaler, train_feat)
valid_s_sc <- predict(standard_scaler, valid_feat)

summary(train_s_sc)
  Pregnancies         Glucose        BloodPressure     SkinThickness    
 Min.   :-1.1147   Min.   :-3.8189   Min.   :-3.6957   Min.   :-1.2738  
 1st Qu.:-0.8191   1st Qu.:-0.6690   1st Qu.:-0.3764   1st Qu.:-1.2738  
 Median :-0.2280   Median :-0.1020   Median : 0.1590   Median : 0.1330  
 Mean   : 0.0000   Mean   : 0.0000   Mean   : 0.0000   Mean   : 0.0000  
 3rd Qu.: 0.6587   3rd Qu.: 0.6146   3rd Qu.: 0.5873   3rd Qu.: 0.7447  
 Max.   : 3.9100   Max.   : 2.3865   Max.   : 2.4076   Max.   : 4.7818  
    Insulin             BMI           DiabetesPedigreeFunction      Age         
 Min.   :-0.6843   Min.   :-4.06067   Min.   :-1.1888          Min.   :-1.0355  
 1st Qu.:-0.6843   1st Qu.:-0.62397   1st Qu.:-0.6953          1st Qu.:-0.7797  
 Median :-0.5569   Median :-0.01005   Median :-0.2974          Median :-0.3532  
 Mean   : 0.0000   Mean   : 0.00000   Mean   : 0.0000          Mean   : 0.0000  
 3rd Qu.: 0.4141   3rd Qu.: 0.59122   3rd Qu.: 0.4414          3rd Qu.: 0.6703  
 Max.   : 6.7496   Max.   : 3.45830   Max.   : 6.0353          Max.   : 4.0819  
summary(valid_s_sc)
  Pregnancies          Glucose         BloodPressure      SkinThickness     
 Min.   :-1.11470   Min.   :-3.81894   Min.   :-3.69566   Min.   :-1.27384  
 1st Qu.:-0.81913   1st Qu.:-0.70049   1st Qu.:-0.26928   1st Qu.:-1.27384  
 Median :-0.22800   Median :-0.22799   Median : 0.10548   Median : 0.07186  
 Mean   : 0.07272   Mean   :-0.03612   Mean   : 0.01354   Mean   :-0.05899  
 3rd Qu.: 0.65871   3rd Qu.: 0.55162   3rd Qu.: 0.58732   3rd Qu.: 0.68353  
 Max.   : 3.31884   Max.   : 2.44947   Max.   : 2.83588   Max.   : 2.02922  
    Insulin              BMI           DiabetesPedigreeFunction
 Min.   :-0.68430   Min.   :-4.06067   Min.   :-1.15799        
 1st Qu.:-0.68430   1st Qu.:-0.57966   1st Qu.:-0.65443        
 Median :-0.27131   Median : 0.04059   Median :-0.26654        
 Mean   : 0.05645   Mean   :-0.03668   Mean   : 0.08721        
 3rd Qu.: 0.45802   3rd Qu.: 0.52160   3rd Qu.: 0.56245        
 Max.   : 5.85331   Max.   : 4.43298   Max.   : 5.75460        
      Age          
 Min.   :-1.03554  
 1st Qu.:-0.77967  
 Median :-0.26792  
 Mean   : 0.02837  
 3rd Qu.: 0.58499  
 Max.   : 3.31429  

We will now perform min/max scaling.

minmax_scaler <- preProcess(train_feat, method=c('range'))
train_mm_sc <- predict(minmax_scaler, train_feat)
valid_mm_sc <- predict(minmax_scaler, valid_feat)

summary(train_mm_sc)
  Pregnancies         Glucose       BloodPressure    SkinThickness   
 Min.   :0.00000   Min.   :0.0000   Min.   :0.0000   Min.   :0.0000  
 1st Qu.:0.05882   1st Qu.:0.5076   1st Qu.:0.5439   1st Qu.:0.0000  
 Median :0.17647   Median :0.5990   Median :0.6316   Median :0.2323  
 Mean   :0.22185   Mean   :0.6154   Mean   :0.6055   Mean   :0.2104  
 3rd Qu.:0.35294   3rd Qu.:0.7145   3rd Qu.:0.7018   3rd Qu.:0.3333  
 Max.   :1.00000   Max.   :1.0000   Max.   :1.0000   Max.   :1.0000  
    Insulin             BMI         DiabetesPedigreeFunction      Age        
 Min.   :0.00000   Min.   :0.0000   Min.   :0.00000          Min.   :0.0000  
 1st Qu.:0.00000   1st Qu.:0.4571   1st Qu.:0.06832          1st Qu.:0.0500  
 Median :0.01714   Median :0.5387   Median :0.12340          Median :0.1333  
 Mean   :0.09205   Mean   :0.5401   Mean   :0.16456          Mean   :0.2024  
 3rd Qu.:0.14775   3rd Qu.:0.6187   3rd Qu.:0.22566          3rd Qu.:0.3333  
 Max.   :1.00000   Max.   :1.0000   Max.   :1.00000          Max.   :1.0000  
summary(valid_mm_sc)
  Pregnancies         Glucose       BloodPressure    SkinThickness   
 Min.   :0.00000   Min.   :0.0000   Min.   :0.0000   Min.   :0.0000  
 1st Qu.:0.05882   1st Qu.:0.5025   1st Qu.:0.5614   1st Qu.:0.0000  
 Median :0.17647   Median :0.5787   Median :0.6228   Median :0.2222  
 Mean   :0.23632   Mean   :0.6096   Mean   :0.6077   Mean   :0.2006  
 3rd Qu.:0.35294   3rd Qu.:0.7043   3rd Qu.:0.7018   3rd Qu.:0.3232  
 Max.   :0.88235   Max.   :1.0102   Max.   :1.0702   Max.   :0.5455  
    Insulin             BMI         DiabetesPedigreeFunction      Age        
 Min.   :0.00000   Min.   :0.0000   Min.   :0.00427          Min.   :0.0000  
 1st Qu.:0.00000   1st Qu.:0.4630   1st Qu.:0.07398          1st Qu.:0.0500  
 Median :0.05556   Median :0.5455   Median :0.12767          Median :0.1500  
 Mean   :0.09965   Mean   :0.5352   Mean   :0.17664          Mean   :0.2079  
 3rd Qu.:0.15366   3rd Qu.:0.6094   3rd Qu.:0.24242          3rd Qu.:0.3167  
 Max.   :0.87943   Max.   :1.1296   Max.   :0.96114          Max.   :0.8500  

3-Nearest Neighbors on Scaled Data

We will now create our 3-Nearest Neighbors model on the scaled data for the sake of comparison. We will start with the standardized data.

set.seed(1)
train_pred <- knn(train_s_sc, train_s_sc, train$Outcome, k=3)
train_acc <- mean(train_pred == train$Outcome)

set.seed(1)
valid_pred <- knn(train_s_sc, valid_s_sc, train$Outcome, k=3)
valid_acc <- mean(valid_pred == valid$Outcome)

cat('Training Accuracy:   ', train_acc, '\n',
    'Validation Accuracy: ', valid_acc, sep='')
Training Accuracy:   0.8531599
Validation Accuracy: 0.6956522

We will now consider the 3-nearest neighbors algorithm on the min-max scaled data.

set.seed(1)
train_pred <- knn(train_mm_sc, train_mm_sc, train$Outcome, k=3)
train_acc <- mean(train_pred == train$Outcome)

set.seed(1)
valid_pred <- knn(train_mm_sc, valid_mm_sc, train$Outcome, k=3)
valid_acc <- mean(valid_pred == valid$Outcome)

cat('Training Accuracy:   ', train_acc, '\n',
    'Validation Accuracy: ', valid_acc, sep='')
Training Accuracy:   0.8494424
Validation Accuracy: 0.726087

Selecting K

We will now build several KNN models. For each K from 1 to 100, we will calculate training and validation accuracy for the KNN model, using both the scaled and the unscaled data.

set.seed(1)

train_acc <- c()
valid_acc <- c()
train_acc_s_sc <- c()
valid_acc_s_sc <- c()
train_acc_mm_sc <- c()
valid_acc_mm_sc <- c()

k_range <- 1:100

for (i in k_range){
  # Unscaled
  set.seed(1)
  train_pred <- knn(train_feat, train_feat, train$Outcome, k=i)
  train_acc <- c(train_acc, mean(train_pred == train$Outcome))
  
  set.seed(1)
  valid_pred <- knn(train_feat, valid_feat, train$Outcome, k=i)
  valid_acc <- c(valid_acc, mean(valid_pred == valid$Outcome))
  
  # Standard Scaling
  set.seed(1)
  train_pred <- knn(train_s_sc, train_s_sc, train$Outcome, k=i)
  train_acc_s_sc <- c(train_acc_s_sc, mean(train_pred == train$Outcome))
  
  set.seed(1)
  valid_pred <- knn(train_s_sc, valid_s_sc, train$Outcome, k=i)
  valid_acc_s_sc <- c(valid_acc_s_sc, mean(valid_pred == valid$Outcome))
  
  # MinMax Scaling
  set.seed(1)
  train_pred <- knn(train_mm_sc, train_mm_sc, train$Outcome, k=i)
  train_acc_mm_sc <- c(train_acc_mm_sc, mean(train_pred == train$Outcome))
  
  set.seed(1)
  valid_pred <- knn(train_mm_sc, valid_mm_sc, train$Outcome, k=i)
  valid_acc_mm_sc <- c(valid_acc_mm_sc, mean(valid_pred == valid$Outcome))
  
}

max(valid_acc)
[1] 0.7869565
max(valid_acc_s_sc)
[1] 0.773913
max(valid_acc_mm_sc)
[1] 0.7608696

Training and Validation Curves for Unscaled Data

plot(k_range, train_acc, pch='.', ylim=c(0.65, 1), col='salmon')
lines(k_range, train_acc, lwd=2, col='salmon')
lines(k_range, valid_acc, lwd=2, col='cornflowerblue')
legend(75, 1, legend=c("Training Acc", "Validation Acc"),
       col=c("salmon", "cornflowerblue"), lty=1, lwd=2, cex=0.8)

Training and Validation Curves for Standard Scaled Data

plot(k_range, train_acc_s_sc, pch='.', ylim=c(0.65, 1), col='salmon')
lines(k_range, train_acc_s_sc, lwd=2, col='salmon')
lines(k_range, valid_acc_s_sc, lwd=2, col='cornflowerblue')
legend(75, 1, legend=c("Training Acc", "Validation Acc"),
       col=c("salmon", "cornflowerblue"), lty=1, lwd=2, cex=0.8)

Training and Validation Curves for Min-Max Scaled Data

plot(k_range, train_acc_mm_sc, pch='.', ylim=c(0.65, 1), col='salmon')
lines(k_range, train_acc_mm_sc, lwd=2, col='salmon')
lines(k_range, valid_acc_mm_sc, lwd=2, col='cornflowerblue')
legend(75, 1, legend=c("Training Acc", "Validation Acc"),
       col=c("salmon", "cornflowerblue"), lty=1, lwd=2, cex=0.8)

Selecting the Final Model

Our best validation performance was obtained using unscaled data. Let’s determine the value of K used to create this particular model.

which.max(valid_acc)
[1] 11

We will now recalculate the training and validation accuracies for this model.

set.seed(1)
train_pred <- knn(train, train, train$Outcome, k=11)
train_acc <- mean(train_pred == train$Outcome)
  
set.seed(1)
valid_pred <- knn(train, valid, train$Outcome, k=11)
valid_acc <- mean(valid_pred == valid$Outcome)

cat('Training Accuracy:   ', train_acc, '\n',
    'Validation Accuracy: ', valid_acc, sep='')
Training Accuracy:   0.7788104
Validation Accuracy: 0.7869565

We can use the table function to create a quick confusion matrix.

table(valid$Outcome, valid_pred)
   valid_pred
      0   1
  0 134  16
  1  33  47

Evaluating the Final Model

We will use the confusion matrix to view several classification metrics for our final model.

confusionMatrix(valid_pred, valid$Outcome)
Confusion Matrix and Statistics

          Reference
Prediction   0   1
         0 134  33
         1  16  47
                                         
               Accuracy : 0.787          
                 95% CI : (0.7283, 0.838)
    No Information Rate : 0.6522         
    P-Value [Acc > NIR] : 5.84e-06       
                                         
                  Kappa : 0.5059         
                                         
 Mcnemar's Test P-Value : 0.02227        
                                         
            Sensitivity : 0.8933         
            Specificity : 0.5875         
         Pos Pred Value : 0.8024         
         Neg Pred Value : 0.7460         
             Prevalence : 0.6522         
         Detection Rate : 0.5826         
   Detection Prevalence : 0.7261         
      Balanced Accuracy : 0.7404         
                                         
       'Positive' Class : 0              
                                         

Weighting Predictors

If we believe that a certain feature will be more or less important than other features in our model, then we can apply a weight to its scaled values in order to have it emphasized more highly when calculating distances.

set.seed(1)

train_acc_w <- c()
valid_acc_w <- c()

w <- c(1, 1, 0, 0, 0, 1, 1, 2)

train_w <- t(t(train_s_sc)*w)
valid_w <- t(t(valid_s_sc)*w)

k_range <- 1:100

for (i in k_range){
  # Scaled
  set.seed(1)
  train_pred <- knn(train_w, train_w, train$Outcome, k=i)
  train_acc_w <- c(train_acc_w, mean(train_pred == train$Outcome))
  
  set.seed(1)
  valid_pred <- knn(train_w, valid_w, train$Outcome, k=i)
  valid_acc_w <- c(valid_acc_w, mean(valid_pred == valid$Outcome))
}

max(valid_acc_w)
[1] 0.7913043
plot(k_range, train_acc_w, pch='.', ylim=c(0.65, 1), col='salmon')
lines(k_range, train_acc_w, lwd=2, col='salmon')
lines(k_range, valid_acc_w, lwd=2, col='cornflowerblue')
legend(75, 1, legend=c("Training Acc", "Validation Acc"),
       col=c("salmon", "cornflowerblue"), lty=1, lwd=2, cex=0.8)

max(valid_acc_w)
[1] 0.7913043
which.max(valid_acc_w)
[1] 24

Pros and Cons of KNN

Pros

Cons

LS0tDQp0aXRsZTogIkxlc3NvbiA0LjMgLSBLLU5lYXJlc3QgTmVpZ2hib3JzIENsYXNzaWZpY2F0aW9uIg0KYXV0aG9yOiAiUm9iYmllIEJlYW5lIg0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOg0KICAgIHRoZW1lOiBmbGF0bHkNCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogNA0KLS0tDQoNCiMjIyAqKkstTmVhcmVzdCBOZWlnaGJvcnMgQ2xhc3NpZmljYXRpb24qKg0KDQpUaGUgSy1OZWFyZXN0IE5laWdoYm9ycyBDbGFzc2lmaWNhdGlvbiBhbGdvcml0aG0gY2xhc3NpZmllcyBvYnNlcnZhdGlvbnMgYnkgYWxsb3dpbmcgdGhlIGBLYCBvYnNlcnZhdGlvbnMgaW4gdGhlIHRyYWluaW5nIHNldCB0aGF0IGFyZSBuZWFyZXN0IHRvIHRoZSBuZXcgb2JzZXJ2YXRpb24gdG8gInZvdGUiIG9uIHRoZSBjbGFzcyBvZiB0aGUgbmV3IG9ic2VydmF0aW9uLiBNb3JlIHNwZWNpZmljYWxseSwgdGhlIGFsZ29yaXRobSB3b3JrcyBhcyBmb2xsb3dzOg0KDQoxLiBDYWxjdWxhdGUgdGhlIGRpc3RhbmNlIGJldHdlZW4gdGhlIG5ldyBvYnNlcnZhdGlvbiBhbmQgRUFDSCBvYnNldmF0aW9uIGluIHRoZSB0cmFpbmluZyBzZXQuIERpc3RhbmNlcyBhcmUgY2FsY3VsYXRlZCB3aXRoaW4gdGhlIGZlYXR1cmUgc3BhY2UuDQoyLiBTZWxlY3QgdGhlIGBLYCBvYnNlcnZhdGlvbnMgaW4gdGhlIHRyYWluaW5nIHNldCB0aGF0IGFyZSBuZWFyZXN0IHRvIHRoZSBuZXcgb2JzZXJ2YXRpb24uDQozLiBEZXRlcm1pbmUgdGhlIG1ham9yaXR5IGNsYXNzIHdpdGhpbiB0aGUgYEtgIG5lYXJlc3QgbmVpZ2hib3JzLg0KNC4gQ2xhc3NpZnkgdGhlIG5ldyBvYnNlcnZhdGlvbiBhcyB0aGlzIG1ham9yaXR5IGNsYXNzLg0KNS4gSWYgdGhlcmUgaXMgYSB0aWUgaW4gdGhlIHZvdGUsIHJlc29sdmUgdGhpcyBhY2NvcmRpbmcgdG8gc29tZSBwcmUtZGV0ZXJtaW5lIHNjaGVtZS4NCg0KV2Ugd2lsbCBkZW1vbnN0cmF0ZSB0aGlzIGNsYXNzaWZpY2F0aW9uIGFsZ29yaXRobSB0aHJvdWdoIGV4YW1wbGVzLiANCg0KIyMjICoqTG9hZCBQYWNrYWdlcyoqDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShncmlkRXh0cmEpDQpsaWJyYXJ5KGNhcmV0KQ0KbGlicmFyeShjbGFzcykNCmBgYA0KDQpXZSB3aWxsIHVzZSBgZ2dwbG90MmAgYW5kIGBncmlkRXh0cmFgIGZvciBwbG90dGluZy4gVGhlIGBjYXJhdGAgcGFja2FnZXMgd2lsbCBiZSB1c2VkIGZvciBzcGxpdHRpbmcgb3VyIGRhdGEgYW5kIGZvciBmZWF0dXJlIHNjYWxpbmcuIFRoZSBmdW5jdGlvbmFsaXR5IGZvciBwZXJmb3JtaW5nIEtOTiBjbGFzc2lmaWNhdGlvbiBpcyBwcm92aWRlZCBieSB0aGUgYGNsYXNzYCBwYWNrYWdlLg0KDQojIyMgKipFeGFtcGxlIDE6IElyaXMgRGF0YXNldCoqDQoNCkZvciB0aGlzIGV4YW1wbGUsIHdlIHdpbGwgYmUgd29ya2luZyB3aXRoIHRoZSBJcmlzIERhdGFzZXQuIFRoaXMgZGF0YSBzZXQgaXMgYSByZWFsLXdvcmxkICJ0b3kiIGRhdGFzZXQgdGhhdCBpcyBvZnRlbiB1c2VkIHRvIGRlbW9uc3RyYXRlIGNvbmNlcHRzIGluIGRhdGEgc2NpZW5jZS4gVGhlIGlyaXMgZGF0YXNldCBjb250YWlucyBpbmZvcm1hdGlvbiBhYm91dCBzZXZlcmFsIGZsb3dlcnMgc2VsZWN0ZWQgZnJvbSB0aHJlZSBkaWZmZXJlbnQgc3BlY2llcyBvZiBpcmlzOiB2ZXJzaWNvbG9yLCBzZXRvc2EsIGFuZCB2aXJnaW5pY2EuDQoNClRoZSBkYXRhc2V0IGNvbnRhaW5zIHRoZSBmb2xsb3dpbmcgZml2ZSBwaWVjZXMgb2YgaW5mb3JtYXRpb24gZm9yIDE1MCBmbG93ZXJzOg0KDQoqIFRoZSBzZXBhbCBsZW5ndGggb2YgdGhlIGZsb3dlci4NCiogVGhlIHNlcGFsIHdpZHRoIG9mIHRoZSBmbG93ZXIuDQoqIFRoZSBwZXRhbCBsZW5ndGggb2YgdGhlIGZsb3dlci4NCiogVGhlIHBldGFsIHdpZHRoIG9mIHRoZSBmbG93ZXIuDQoqIFRoZSBzcGVjaWVzIG9mIHRoZSBmbG93ZXIuDQoNCjxjZW50ZXI+DQohW10oaW1hZ2VzL2lyaXMucG5nKQ0KPC9jZW50ZXI+DQoNCiMjIyMgKipMb2FkIGFuZCBFeHBsb3JlIHRoZSBEYXRhKioNCg0KYGBge3J9DQppcmlzX3RyIDwtIHJlYWQudGFibGUoJ2RhdGEvaXJpcy50eHQnLCBzZXA9J1x0JywgaGVhZGVyPVRSVUUpDQpzdW1tYXJ5KGlyaXNfdHIpDQpgYGANCg0KDQpgYGB7ciwgZmlnLndpZHRoPTgsIGZpZy5oZWlnaHQ9NH0NCnAxIDwtIGdncGxvdChpcmlzX3RyLCBhZXMoeD1zZXBhbF9sZW5ndGgsIHk9c2VwYWxfd2lkdGgsIGNvbD1zcGVjaWVzKSkgKw0KICBnZW9tX3BvaW50KGFscGhhPTAuOCkNCg0KcDIgPC0gZ2dwbG90KGlyaXNfdHIsIGFlcyh4PXBldGFsX2xlbmd0aCwgeT1wZXRhbF93aWR0aCwgY29sPXNwZWNpZXMpKSArDQogIGdlb21fcG9pbnQoYWxwaGE9MC44KQ0KDQpncmlkLmFycmFuZ2UocDEsIHAyLCBuY29sPTIpDQpgYGANCg0KIyMjIyAqKkJ1aWxkIGEgMy1OZWFyZXN0IE5laWdoYm9ycyBNb2RlbCoqDQoNCldlIHdpbGwgbm93IHVzZSB0aGUgYGtubmAgZnVuY3Rpb24gZnJvbSB0aGUgYGNsYXNzYCBwYWNrYWdlIHRvIGNyZWF0ZSBhIDMtTmVhcmVzdCBOZWlnaGJvcnMgbW9kZWwgZm9yIHRoZSBpcmlzIGRhdGFzZXQuIA0KDQpgYGB7cn0NCmlyaXNfdHJfZmVhdCA8LSBpcmlzX3RyWywxOjRdDQpzZXQuc2VlZCgxKQ0KdHJhaW5fcHJlZCA8LSBrbm4oaXJpc190cl9mZWF0LCBpcmlzX3RyX2ZlYXQsIGlyaXNfdHIkc3BlY2llcywgaz0zKQ0KdHJhaW5fcHJlZFsxOjEwXQ0KYGBgDQoNCkxldCdzIGNhbGN1bGF0ZSB0aGUgbW9kZWwncyB0cmFpbmluZyBhY2N1cmFjeS4gDQoNCmBgYHtyfQ0KYWNjdXJhY3kgPC0gbWVhbih0cmFpbl9wcmVkID09IGlyaXNfdHIkc3BlY2llcykNCmNhdCgiVHJhaW5pbmcgQWNjdXJhY3k6ICIsIGFjY3VyYWN5LCBzZXA9JycpDQpgYGANCg0KIyMjIyAqKkVmZmVjdHMgb2YgQ2hhbmdpbmcgSyoqDQoNClRoZSBzZWxlY3Rpb24gb2YgYEs9M2AgaW4gdGhlIHByZXZpb3VzIG1vZGVsIHdhcyBzb21ld2hhdCBhcmJpdHJhcnkuIEluIHRoZSBmaWd1cmUgYmVsb3csIHdlIHdpbGwgcGxvdCB0aGUgS05OIG1vZGVsJ3MgdHJhaW5pbmcgYWNjdXJhY3kgZm9yIHNldmVyYWwgdmFsdWVzIG9mIGBLYC4gDQoNCmBgYHtyfQ0KdHJhaW5fYWNjIDwtIGMoKQ0KDQpmb3IgKGkgaW4gMToxNTApew0KICBzZXQuc2VlZCgxKQ0KICB0cmFpbl9wcmVkIDwtIGtubihpcmlzX3RyX2ZlYXQsIGlyaXNfdHJfZmVhdCwgaXJpc190ciRzcGVjaWVzLCBrPWkpDQogIHRyYWluX2FjYyA8LSBjKHRyYWluX2FjYywgbWVhbih0cmFpbl9wcmVkID09IGlyaXNfdHIkc3BlY2llcykpDQp9DQoNCnBsb3QoMToxNTAsIHRyYWluX2FjYywgcGNoPScuJywgeWxpbT1jKDAuMywgMSksIGNvbD0nc2FsbW9uJykNCmxpbmVzKDE6MTUwLCB0cmFpbl9hY2MsIGx3ZD0yLCBjb2w9J3NhbG1vbicpDQpgYGANCg0KIyMjIyAqKlZhbGlkYXRpb24gU2V0KioNCg0KS05OIG1vZGVsJ3Mgd2lsbCAoYWxtb3N0KSBhbHdheXMgZ2V0IDEwMCUgYWNjdXJhY3kgb24gdGhlIHRyYWluaW5nIHNldCB3aGVuIGBLPTFgLiBGb3IgdGhpcyByZWFzb24sIGl0IGlzIG5vdCBoZWxwZnVsIHRvIHVzZSB0cmFpbmluZyBwZXJmb3JtYW5jZSB0byBzZWxlY3QgdGhlIGJlc3QgdmFsdWUgb2YgYEtgIGZvciBvdXIgZGF0YXNldC4gVG8gc2VsZWN0IHRoZSB2YWx1ZSBvZiBgS2AgZm9yIHdoaWNoIHRoZSBtb2RlbCBpcyBtb3N0IGxpa2VseSB0byBnZW5lcmFsaXplIHdlbGwsIHdlIG5lZWQgdG8gY3JlYXRlIGEgdmFsaWRhdGlvbiBzZXQuIA0KDQpgYGB7cn0NCmlyaXNfdmEgPC0gcmVhZC50YWJsZSgnZGF0YS9pcmlzX3ZhbGlkLnR4dCcsIHNlcD0nXHQnLCBoZWFkZXI9VFJVRSkNCnN1bW1hcnkoaXJpc192YSkNCmBgYA0KDQojIyMjICoqU2VsZWN0aW5nIEsqKg0KDQpXZSB3aWxsIG5vdyBjYWxjdWxhdGUgdmFsaWRhdGlvbiBhY2N1cmFjeSBmb3IgZWFjaCBvZiBvdXIgbW9kZWxzLCBhbmQgd2lsbCB1c2UgdGhpcyBpbmZvcm1hdGlvbiB0byBzZWxlY3Qgb3VyIGZpbmFsIG1vZGVsLg0KDQpgYGB7cn0NCnRyYWluX2FjYyA8LSBjKCkNCnZhbGlkX2FjYyA8LSBjKCkNCg0KaXJpc192YV9mZWF0IDwtIGlyaXNfdmFbLDE6NF0NCg0KDQpmb3IgKGkgaW4gMToxMDApew0KICBzZXQuc2VlZCgxKQ0KICB0cmFpbl9wcmVkIDwtIGtubihpcmlzX3RyX2ZlYXQsIGlyaXNfdHJfZmVhdCwgaXJpc190ciRzcGVjaWVzLCBrPWkpDQogIHRyYWluX2FjYyA8LSBjKHRyYWluX2FjYywgbWVhbih0cmFpbl9wcmVkID09IGlyaXNfdHIkc3BlY2llcykpDQogIA0KICBzZXQuc2VlZCgxKQ0KICB2YWxpZF9wcmVkIDwtIGtubihpcmlzX3RyX2ZlYXQsIGlyaXNfdmFfZmVhdCwgaXJpc190ciRzcGVjaWVzLCBrPWkpDQogIHZhbGlkX2FjYyA8LSBjKHZhbGlkX2FjYywgbWVhbih2YWxpZF9wcmVkID09IGlyaXNfdmEkc3BlY2llcykpDQp9DQoNCnBsb3QoMToxMDAsIHRyYWluX2FjYywgcGNoPScuJywgeWxpbT1jKDAuOCwgMSksIGNvbD0nc2FsbW9uJykNCmxpbmVzKDE6MTAwLCB0cmFpbl9hY2MsIGx3ZD0yLCBjb2w9J3NhbG1vbicpDQpsaW5lcygxOjEwMCwgdmFsaWRfYWNjLCBsd2Q9MiwgY29sPSdjb3JuZmxvd2VyYmx1ZScpDQpsZWdlbmQoMzgsIDEsIGxlZ2VuZD1jKCJUcmFpbmluZyBBY2MiLCAiVmFsaWRhdGlvbiBBY2MiKSwNCiAgICAgICBjb2w9Yygic2FsbW9uIiwgImNvcm5mbG93ZXJibHVlIiksIGx0eT0xLCBsd2Q9MiwgY2V4PTAuOCkNCmBgYA0KDQpUaGUgbGFyZ2VzdCB2YWxpZGF0aW9uIGFjY3VyYWN5IG9idGFpbmVkIGJ5IGFueSBtb2RlbCB3YXMgOTMuMzMlLiANCg0KYGBge3J9DQptYXgodmFsaWRfYWNjKQ0KYGBgDQoNClRoZSBtYXhpbXVtIHZhbGlkYXRpb24gYWNjdXJhY3kgd2FzIG9idGFpbmVkIGJ5IHRoZSBgSz0xMWAgbW9kZWwuIA0KDQpgYGB7cn0NCndoaWNoLm1heCh2YWxpZF9hY2MpDQpgYGANCg0KDQoNCiMjIyAqKkV4YW1wbGUgMjogUGltYSBEaWFiZXRlcyBEYXRhc2V0KioNCg0KRm9yIHRoaXMgZXhhbXBsZSwgd2Ugd2lsbCBiZSB3b3JraW5nIHdpdGggdGhlIFBpbWEgRGlhYmV0ZXMgRGF0YXNldC4gVGhpcyBkYXRhc2V0IGlzIG9yaWdpbmFsbHkgZnJvbSB0aGUgTmF0aW9uYWwgSW5zdGl0dXRlIG9mIERpYWJldGVzIGFuZCBEaWdlc3RpdmUgYW5kIEtpZG5leSBEaXNlYXNlcy4gVGhlIG9iamVjdGl2ZSBpcyB0byBwcmVkaWN0IGJhc2VkIG9uIGRpYWdub3N0aWMgbWVhc3VyZW1lbnRzIHdoZXRoZXIgYSBwYXRpZW50IGhhcyBkaWFiZXRlcy4gQWxsIHBhdGllbnRzIGFyZSBmZW1hbGVzIGF0IGxlYXN0IDIxIHllYXJzIG9sZCBvZiBQaW1hIEluZGlhbiBoZXJpdGFnZS4gDQoNClRoZSBjb2x1bW5zIGluIHRoaXMgZGF0YXNldCBhcmUgZGVzY3JpYmVkIGJlbG93LiANCg0KKiAqKlByZWduYW5jaWVzKio6IE51bWJlciBvZiB0aW1lcyBwcmVnbmFudA0KKiAqKkdsdWNvc2UqKjogUGxhc21hIGdsdWNvc2UgY29uY2VudHJhdGlvbiBhIDIgaG91cnMgaW4gYW4gb3JhbCBnbHVjb3NlIHRvbGVyYW5jZSB0ZXN0DQoqICoqQmxvb2RQcmVzc3VyZSoqOiBEaWFzdG9saWMgYmxvb2QgcHJlc3N1cmUgKG1tIEhnKQ0KKiAqKlNraW5UaGlja25lc3MqKjogVHJpY2VwcyBza2luIGZvbGQgdGhpY2tuZXNzIChtbSkNCiogKipJbnN1bGluKio6IDItSG91ciBzZXJ1bSBpbnN1bGluIChtdSBVL21sKQ0KKiAqKkJNSSoqOiBCb2R5IG1hc3MgaW5kZXggKHdlaWdodCBpbiBrZy8oaGVpZ2h0IGluIG0pXjIpDQoqICoqRGlhYmV0ZXNQZWRpZ3JlZUZ1bmN0aW9uKio6IERpYWJldGVzIHBlZGlncmVlIGZ1bmN0aW9uDQoqICoqQWdlKio6IEFnZSAoeWVhcnMpDQoqICoqT3V0Y29tZSoqOiBDbGFzcyB2YXJpYWJsZSAoMCBvciAxKQ0KDQoNCiMjIyMgKipMb2FkIHRoZSBEYXRhKioNCg0KYGBge3J9DQpwaW1hIDwtIHJlYWQudGFibGUoImRhdGEvZGlhYmV0ZXMuY3N2Iiwgc2VwPSIsIiwgaGVhZGVyPVRSVUUpDQpwaW1hJE91dGNvbWUgPC0gZmFjdG9yKHBpbWEkT3V0Y29tZSkNCnN1bW1hcnkocGltYSkNCmBgYA0KDQojIyMjICoqU3BsaXQgdGhlIERhdGEqKg0KDQpXZSB3aWxsIHVzZSBgY3JlYXRlRGF0YVBhcnRpdGlvbmAgdG8gY3JlYXRlIGEgc3RyYXRpZmllZCBwYXJ0aXRpb24gb2Ygb3VyIGRhdGEgaW50byB0cmFpbmluZyBhbmQgdmFsaWRhdGlvbiBzZXRzLiANCg0KYGBge3J9DQpzZXQuc2VlZCgxKQ0KdHJhaW4uaW5kZXggPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihwaW1hJE91dGNvbWUsIHAgPSAuNywgbGlzdD1GQUxTRSkNCnRyYWluIDwtIHBpbWFbIHRyYWluLmluZGV4LF0NCnZhbGlkICA8LSBwaW1hWy10cmFpbi5pbmRleCxdDQoNCnN1bW1hcnkodHJhaW4kT3V0Y29tZSkNCnN1bW1hcnkodmFsaWQkT3V0Y29tZSkNCmBgYA0KDQojIyMjICoqMy1OZWFyZXN0IE5laWdoYm9ycyoqDQoNCkZvciBwcmFjdGljZSwgd2Ugd2lsbCBjcmVhdGUgYW5kIGV2YWx1YXRlIGEgMy1OZWFyZXN0IE5laWdoYm9ycyBtb2RlbC4gDQoNCmBgYHtyfQ0KdHJhaW5fZmVhdCA8LSB0cmFpblssMTo4XSANCnZhbGlkX2ZlYXQgPC0gdmFsaWRbLDE6OF0gDQoNCnNldC5zZWVkKDEpDQp0cmFpbl9wcmVkIDwtIGtubih0cmFpbl9mZWF0LCB0cmFpbl9mZWF0LCB0cmFpbiRPdXRjb21lLCBrPTMpDQp0cmFpbl9hY2MgPC0gbWVhbih0cmFpbl9wcmVkID09IHRyYWluJE91dGNvbWUpDQoNCnNldC5zZWVkKDEpDQp2YWxpZF9wcmVkIDwtIGtubih0cmFpbl9mZWF0LCB2YWxpZF9mZWF0LCB0cmFpbiRPdXRjb21lLCBrPTMpDQp2YWxpZF9hY2MgPC0gbWVhbih2YWxpZF9wcmVkID09IHZhbGlkJE91dGNvbWUpDQoNCmNhdCgnVHJhaW5pbmcgQWNjdXJhY3k6ICAgJywgdHJhaW5fYWNjLCAnXG4nLA0KICAgICdWYWxpZGF0aW9uIEFjY3VyYWN5OiAnLCB2YWxpZF9hY2MsIHNlcD0nJykNCmBgYA0KDQojIyMjICoqRmVhdHVyZSBTY2FsaW5nKioNCg0KRm9yIG1hbnkgbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG1zLCBpdCBpcyBpbXBvcnRhbnQgdG8gbWFrZSBzdXJlIHRoYXQgdGhlIGZlYXR1cmVzIGFyZSBvbiByb3VnaGx5IHRoZSBzYW1lIHNjYWxlIGJlZm9yZSB0cmFpbmluZy4gVGhpcyBpcyBlc3BlY2lhbGx5IHRydWUgb2YgZGlzdGFuY2UtYmFzZWQgYWxnb3JpdGhtcyBzdWNoIGFzIEstTmVhcmVzdCBOZWlnaGJvcnMuIA0KDQpXZSB3aWxsIGNvbnNpZGVyIHR3byB0eXBlcyBvZiBzY2FsaW5nIGluIHRoaXMgY291cnNlOiBTdGFuZGFyZGl6YXRpb24gYW5kIE1pbi9NYXggU2NhbGluZy4gDQoNCiMjIyMgKipTdGFuZGFyZGl6YXRpb24qKg0KDQpXaXRoICoqc3RhbmRhcmRpemF0aW9uKiosIGVhY2ggY29sdW1uIGluIHRoZSB0cmFpbmluZyBzZXQgaXMgc2NhbGVkIHRvIGhhdmUgYSBtZWFuIG9mIHplcm8gYW5kIGEgc3RhbmRhcmQgZGV2aWF0aW9uIG9mIDEuIElmICR4X2kkIGlzIGEgc2luZ2xlIG9ic2VydmF0aW9uIG9mIGEgcGFydGljdWxhciBmZWF0dXJlLCB0aGVuIGl0cyBzY2FsZWQgdmFsdWUgJHpfaSQgaXMgZ2l2ZW4gYnk6DQoNCiQkIHpfaSA9IFxmcmFje3hfaSAtIFxiYXIgeH17c194fSQkDQpOb3RlIHRoYXQgdGhlIHZhbHVlcyAkXGJhciB4JCBhbmQgJHNfeCQgYXJlIGNhbGN1bGF0ZWQgZnJvbSB0aGUgdHJhaW5pbmcgc2V0IG9ubHksIGJ1dCBhcmUgdXNlZCB0byBzY2FsZSBhbnkgb2JzZXJ2YXRpb24sIHdoZXRoZXIgaXQgaXMgZnJvbSB0aGUgdHJhaW5pbmcgc2V0LCB2YWxpZGF0aW9uIHNldCwgdGVzdGluZyBzZXQsIG9yIGFuIGVudGlyZWx5IG5ldyBvYnNlcnZhdGlvbi4gDQoNCiMjIyMgKipNaW4vTWF4IFNjYWxpbmcqKg0KDQpXaXRoICoqbWluIG1heCBzY2FsaW5nKiosIGVhY2ggY29sdW1uIGluIHRoZSB0cmFpbmluZyBzZXQgaXMgc2NhbGVkIGxpbmVhcmx5IHRvIGhhdmUgYSBtaW5pbXVtIG9mIHplcm8gYW5kIGEgbWF4aW11bSBvZiAxLiBJZiAkeF9pJCBpcyBhIHNpbmdsZSBvYnNlcnZhdGlvbiBvZiBhIHBhcnRpY3VsYXIgZmVhdHVyZSwgdGhlbiBpdHMgc2NhbGVkIHZhbHVlICR3X2kkIGlzIGdpdmVuIGJ5Og0KDQokJCB3X2kgPSBcZnJhY3t4X2kgLSBcdGV4dHJte21pbn1feH17XHRleHRybXttYXh9X3ggLSBcdGV4dHJte21pbn1feH0kJA0KTm90ZSB0aGF0IHRoZSB2YWx1ZXMgJFx0ZXh0cm17bWlufV94JCBhbmQgJFx0ZXh0cm17bWF4fV94JCBhcmUgY2FsY3VsYXRlZCBmcm9tIHRoZSB0cmFpbmluZyBzZXQgb25seSwgYnV0IGFyZSB1c2VkIHRvIHNjYWxlIGFueSBvYnNlcnZhdGlvbiwgd2hldGhlciBpdCBpcyBmcm9tIHRoZSB0cmFpbmluZyBzZXQsIHZhbGlkYXRpb24gc2V0LCB0ZXN0aW5nIHNldCwgb3IgYW4gZW50aXJlbHkgbmV3IG9ic2VydmF0aW9uLiANCg0KIyMjIyAqKlBlcmZvcm1pbmcgRmVhdHVyZSBTY2FsaW5nKioNCg0KV2UgY2FuIHBlcmZvcm0gZmVhdHVyZSBzY2FsaW5nIHVzaW5nIHRoZSBgcHJlUHJvY2Vzc2AgZnVuY3Rpb24gZnJvbSB0aGUgYGNhcmF0YCBwYWNrYWdlLiANCg0KV2Ugd2lsbCBzdGFydCBieSBwZXJmb3JtaW5nIHN0YW5kYXJkIHNjYWxpbmcuIA0KDQpgYGB7cn0NCnN0YW5kYXJkX3NjYWxlciA8LSBwcmVQcm9jZXNzKHRyYWluX2ZlYXQsIG1ldGhvZD1jKCdjZW50ZXInLCAnc2NhbGUnKSkNCnRyYWluX3Nfc2MgPC0gcHJlZGljdChzdGFuZGFyZF9zY2FsZXIsIHRyYWluX2ZlYXQpDQp2YWxpZF9zX3NjIDwtIHByZWRpY3Qoc3RhbmRhcmRfc2NhbGVyLCB2YWxpZF9mZWF0KQ0KDQpzdW1tYXJ5KHRyYWluX3Nfc2MpDQpgYGANCg0KYGBge3J9DQpzdW1tYXJ5KHZhbGlkX3Nfc2MpDQpgYGANCg0KV2Ugd2lsbCBub3cgcGVyZm9ybSBtaW4vbWF4IHNjYWxpbmcuIA0KDQpgYGB7cn0NCm1pbm1heF9zY2FsZXIgPC0gcHJlUHJvY2Vzcyh0cmFpbl9mZWF0LCBtZXRob2Q9YygncmFuZ2UnKSkNCnRyYWluX21tX3NjIDwtIHByZWRpY3QobWlubWF4X3NjYWxlciwgdHJhaW5fZmVhdCkNCnZhbGlkX21tX3NjIDwtIHByZWRpY3QobWlubWF4X3NjYWxlciwgdmFsaWRfZmVhdCkNCg0Kc3VtbWFyeSh0cmFpbl9tbV9zYykNCmBgYA0KDQpgYGB7cn0NCnN1bW1hcnkodmFsaWRfbW1fc2MpDQpgYGANCg0KDQojIyMjICoqMy1OZWFyZXN0IE5laWdoYm9ycyBvbiBTY2FsZWQgRGF0YSoqDQoNCldlIHdpbGwgbm93IGNyZWF0ZSBvdXIgMy1OZWFyZXN0IE5laWdoYm9ycyBtb2RlbCBvbiB0aGUgc2NhbGVkIGRhdGEgZm9yIHRoZSBzYWtlIG9mIGNvbXBhcmlzb24uIFdlIHdpbGwgc3RhcnQgd2l0aCB0aGUgc3RhbmRhcmRpemVkIGRhdGEuIA0KDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMSkNCnRyYWluX3ByZWQgPC0ga25uKHRyYWluX3Nfc2MsIHRyYWluX3Nfc2MsIHRyYWluJE91dGNvbWUsIGs9MykNCnRyYWluX2FjYyA8LSBtZWFuKHRyYWluX3ByZWQgPT0gdHJhaW4kT3V0Y29tZSkNCg0Kc2V0LnNlZWQoMSkNCnZhbGlkX3ByZWQgPC0ga25uKHRyYWluX3Nfc2MsIHZhbGlkX3Nfc2MsIHRyYWluJE91dGNvbWUsIGs9MykNCnZhbGlkX2FjYyA8LSBtZWFuKHZhbGlkX3ByZWQgPT0gdmFsaWQkT3V0Y29tZSkNCg0KY2F0KCdUcmFpbmluZyBBY2N1cmFjeTogICAnLCB0cmFpbl9hY2MsICdcbicsDQogICAgJ1ZhbGlkYXRpb24gQWNjdXJhY3k6ICcsIHZhbGlkX2FjYywgc2VwPScnKQ0KYGBgDQoNCldlIHdpbGwgbm93IGNvbnNpZGVyIHRoZSAzLW5lYXJlc3QgbmVpZ2hib3JzIGFsZ29yaXRobSBvbiB0aGUgbWluLW1heCBzY2FsZWQgZGF0YS4gDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMSkNCnRyYWluX3ByZWQgPC0ga25uKHRyYWluX21tX3NjLCB0cmFpbl9tbV9zYywgdHJhaW4kT3V0Y29tZSwgaz0zKQ0KdHJhaW5fYWNjIDwtIG1lYW4odHJhaW5fcHJlZCA9PSB0cmFpbiRPdXRjb21lKQ0KDQpzZXQuc2VlZCgxKQ0KdmFsaWRfcHJlZCA8LSBrbm4odHJhaW5fbW1fc2MsIHZhbGlkX21tX3NjLCB0cmFpbiRPdXRjb21lLCBrPTMpDQp2YWxpZF9hY2MgPC0gbWVhbih2YWxpZF9wcmVkID09IHZhbGlkJE91dGNvbWUpDQoNCmNhdCgnVHJhaW5pbmcgQWNjdXJhY3k6ICAgJywgdHJhaW5fYWNjLCAnXG4nLA0KICAgICdWYWxpZGF0aW9uIEFjY3VyYWN5OiAnLCB2YWxpZF9hY2MsIHNlcD0nJykNCmBgYA0KDQojIyMjICoqU2VsZWN0aW5nIEsqKg0KDQpXZSB3aWxsIG5vdyBidWlsZCBzZXZlcmFsIEtOTiBtb2RlbHMuIEZvciBlYWNoIGBLYCBmcm9tIDEgdG8gMTAwLCB3ZSB3aWxsIGNhbGN1bGF0ZSB0cmFpbmluZyBhbmQgdmFsaWRhdGlvbiBhY2N1cmFjeSBmb3IgdGhlIEtOTiBtb2RlbCwgdXNpbmcgYm90aCB0aGUgc2NhbGVkIGFuZCB0aGUgdW5zY2FsZWQgZGF0YS4gDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMSkNCg0KdHJhaW5fYWNjIDwtIGMoKQ0KdmFsaWRfYWNjIDwtIGMoKQ0KdHJhaW5fYWNjX3Nfc2MgPC0gYygpDQp2YWxpZF9hY2Nfc19zYyA8LSBjKCkNCnRyYWluX2FjY19tbV9zYyA8LSBjKCkNCnZhbGlkX2FjY19tbV9zYyA8LSBjKCkNCg0Ka19yYW5nZSA8LSAxOjEwMA0KDQpmb3IgKGkgaW4ga19yYW5nZSl7DQogICMgVW5zY2FsZWQNCiAgc2V0LnNlZWQoMSkNCiAgdHJhaW5fcHJlZCA8LSBrbm4odHJhaW5fZmVhdCwgdHJhaW5fZmVhdCwgdHJhaW4kT3V0Y29tZSwgaz1pKQ0KICB0cmFpbl9hY2MgPC0gYyh0cmFpbl9hY2MsIG1lYW4odHJhaW5fcHJlZCA9PSB0cmFpbiRPdXRjb21lKSkNCiAgDQogIHNldC5zZWVkKDEpDQogIHZhbGlkX3ByZWQgPC0ga25uKHRyYWluX2ZlYXQsIHZhbGlkX2ZlYXQsIHRyYWluJE91dGNvbWUsIGs9aSkNCiAgdmFsaWRfYWNjIDwtIGModmFsaWRfYWNjLCBtZWFuKHZhbGlkX3ByZWQgPT0gdmFsaWQkT3V0Y29tZSkpDQogIA0KICAjIFN0YW5kYXJkIFNjYWxpbmcNCiAgc2V0LnNlZWQoMSkNCiAgdHJhaW5fcHJlZCA8LSBrbm4odHJhaW5fc19zYywgdHJhaW5fc19zYywgdHJhaW4kT3V0Y29tZSwgaz1pKQ0KICB0cmFpbl9hY2Nfc19zYyA8LSBjKHRyYWluX2FjY19zX3NjLCBtZWFuKHRyYWluX3ByZWQgPT0gdHJhaW4kT3V0Y29tZSkpDQogIA0KICBzZXQuc2VlZCgxKQ0KICB2YWxpZF9wcmVkIDwtIGtubih0cmFpbl9zX3NjLCB2YWxpZF9zX3NjLCB0cmFpbiRPdXRjb21lLCBrPWkpDQogIHZhbGlkX2FjY19zX3NjIDwtIGModmFsaWRfYWNjX3Nfc2MsIG1lYW4odmFsaWRfcHJlZCA9PSB2YWxpZCRPdXRjb21lKSkNCiAgDQogICMgTWluTWF4IFNjYWxpbmcNCiAgc2V0LnNlZWQoMSkNCiAgdHJhaW5fcHJlZCA8LSBrbm4odHJhaW5fbW1fc2MsIHRyYWluX21tX3NjLCB0cmFpbiRPdXRjb21lLCBrPWkpDQogIHRyYWluX2FjY19tbV9zYyA8LSBjKHRyYWluX2FjY19tbV9zYywgbWVhbih0cmFpbl9wcmVkID09IHRyYWluJE91dGNvbWUpKQ0KICANCiAgc2V0LnNlZWQoMSkNCiAgdmFsaWRfcHJlZCA8LSBrbm4odHJhaW5fbW1fc2MsIHZhbGlkX21tX3NjLCB0cmFpbiRPdXRjb21lLCBrPWkpDQogIHZhbGlkX2FjY19tbV9zYyA8LSBjKHZhbGlkX2FjY19tbV9zYywgbWVhbih2YWxpZF9wcmVkID09IHZhbGlkJE91dGNvbWUpKQ0KICANCn0NCg0KbWF4KHZhbGlkX2FjYykNCm1heCh2YWxpZF9hY2Nfc19zYykNCm1heCh2YWxpZF9hY2NfbW1fc2MpDQpgYGANCg0KIyMjIyAqKlRyYWluaW5nIGFuZCBWYWxpZGF0aW9uIEN1cnZlcyBmb3IgVW5zY2FsZWQgRGF0YSoqDQoNCmBgYHtyfQ0KcGxvdChrX3JhbmdlLCB0cmFpbl9hY2MsIHBjaD0nLicsIHlsaW09YygwLjY1LCAxKSwgY29sPSdzYWxtb24nKQ0KbGluZXMoa19yYW5nZSwgdHJhaW5fYWNjLCBsd2Q9MiwgY29sPSdzYWxtb24nKQ0KbGluZXMoa19yYW5nZSwgdmFsaWRfYWNjLCBsd2Q9MiwgY29sPSdjb3JuZmxvd2VyYmx1ZScpDQpsZWdlbmQoNzUsIDEsIGxlZ2VuZD1jKCJUcmFpbmluZyBBY2MiLCAiVmFsaWRhdGlvbiBBY2MiKSwNCiAgICAgICBjb2w9Yygic2FsbW9uIiwgImNvcm5mbG93ZXJibHVlIiksIGx0eT0xLCBsd2Q9MiwgY2V4PTAuOCkNCmBgYA0KDQojIyMjICoqVHJhaW5pbmcgYW5kIFZhbGlkYXRpb24gQ3VydmVzIGZvciBTdGFuZGFyZCBTY2FsZWQgRGF0YSoqDQoNCmBgYHtyfQ0KcGxvdChrX3JhbmdlLCB0cmFpbl9hY2Nfc19zYywgcGNoPScuJywgeWxpbT1jKDAuNjUsIDEpLCBjb2w9J3NhbG1vbicpDQpsaW5lcyhrX3JhbmdlLCB0cmFpbl9hY2Nfc19zYywgbHdkPTIsIGNvbD0nc2FsbW9uJykNCmxpbmVzKGtfcmFuZ2UsIHZhbGlkX2FjY19zX3NjLCBsd2Q9MiwgY29sPSdjb3JuZmxvd2VyYmx1ZScpDQpsZWdlbmQoNzUsIDEsIGxlZ2VuZD1jKCJUcmFpbmluZyBBY2MiLCAiVmFsaWRhdGlvbiBBY2MiKSwNCiAgICAgICBjb2w9Yygic2FsbW9uIiwgImNvcm5mbG93ZXJibHVlIiksIGx0eT0xLCBsd2Q9MiwgY2V4PTAuOCkNCmBgYA0KDQojIyMjICoqVHJhaW5pbmcgYW5kIFZhbGlkYXRpb24gQ3VydmVzIGZvciBNaW4tTWF4IFNjYWxlZCBEYXRhKioNCg0KYGBge3J9DQpwbG90KGtfcmFuZ2UsIHRyYWluX2FjY19tbV9zYywgcGNoPScuJywgeWxpbT1jKDAuNjUsIDEpLCBjb2w9J3NhbG1vbicpDQpsaW5lcyhrX3JhbmdlLCB0cmFpbl9hY2NfbW1fc2MsIGx3ZD0yLCBjb2w9J3NhbG1vbicpDQpsaW5lcyhrX3JhbmdlLCB2YWxpZF9hY2NfbW1fc2MsIGx3ZD0yLCBjb2w9J2Nvcm5mbG93ZXJibHVlJykNCmxlZ2VuZCg3NSwgMSwgbGVnZW5kPWMoIlRyYWluaW5nIEFjYyIsICJWYWxpZGF0aW9uIEFjYyIpLA0KICAgICAgIGNvbD1jKCJzYWxtb24iLCAiY29ybmZsb3dlcmJsdWUiKSwgbHR5PTEsIGx3ZD0yLCBjZXg9MC44KQ0KYGBgDQoNCiMjIyMgKipTZWxlY3RpbmcgdGhlIEZpbmFsIE1vZGVsKioNCg0KT3VyIGJlc3QgdmFsaWRhdGlvbiBwZXJmb3JtYW5jZSB3YXMgb2J0YWluZWQgdXNpbmcgdW5zY2FsZWQgZGF0YS4gTGV0J3MgZGV0ZXJtaW5lIHRoZSB2YWx1ZSBvZiBgS2AgdXNlZCB0byBjcmVhdGUgdGhpcyBwYXJ0aWN1bGFyIG1vZGVsLiANCg0KYGBge3J9DQp3aGljaC5tYXgodmFsaWRfYWNjKQ0KYGBgDQoNCldlIHdpbGwgbm93IHJlY2FsY3VsYXRlIHRoZSB0cmFpbmluZyBhbmQgdmFsaWRhdGlvbiBhY2N1cmFjaWVzIGZvciB0aGlzIG1vZGVsLiANCg0KYGBge3J9DQpzZXQuc2VlZCgxKQ0KdHJhaW5fcHJlZCA8LSBrbm4odHJhaW4sIHRyYWluLCB0cmFpbiRPdXRjb21lLCBrPTExKQ0KdHJhaW5fYWNjIDwtIG1lYW4odHJhaW5fcHJlZCA9PSB0cmFpbiRPdXRjb21lKQ0KICANCnNldC5zZWVkKDEpDQp2YWxpZF9wcmVkIDwtIGtubih0cmFpbiwgdmFsaWQsIHRyYWluJE91dGNvbWUsIGs9MTEpDQp2YWxpZF9hY2MgPC0gbWVhbih2YWxpZF9wcmVkID09IHZhbGlkJE91dGNvbWUpDQoNCmNhdCgnVHJhaW5pbmcgQWNjdXJhY3k6ICAgJywgdHJhaW5fYWNjLCAnXG4nLA0KICAgICdWYWxpZGF0aW9uIEFjY3VyYWN5OiAnLCB2YWxpZF9hY2MsIHNlcD0nJykNCmBgYA0KDQpXZSBjYW4gdXNlIHRoZSBgdGFibGVgIGZ1bmN0aW9uIHRvIGNyZWF0ZSBhIHF1aWNrIGNvbmZ1c2lvbiBtYXRyaXguIA0KDQpgYGB7cn0NCnRhYmxlKHZhbGlkJE91dGNvbWUsIHZhbGlkX3ByZWQpDQpgYGANCg0KIyMjIyAqKkV2YWx1YXRpbmcgdGhlIEZpbmFsIE1vZGVsKioNCg0KV2Ugd2lsbCB1c2UgdGhlIGNvbmZ1c2lvbiBtYXRyaXggdG8gdmlldyBzZXZlcmFsIGNsYXNzaWZpY2F0aW9uIG1ldHJpY3MgZm9yIG91ciBmaW5hbCBtb2RlbC4gDQoNCmBgYHtyfQ0KY29uZnVzaW9uTWF0cml4KHZhbGlkX3ByZWQsIHZhbGlkJE91dGNvbWUpDQpgYGANCg0KIyMjIyAqKldlaWdodGluZyBQcmVkaWN0b3JzKioNCg0KSWYgd2UgYmVsaWV2ZSB0aGF0IGEgY2VydGFpbiBmZWF0dXJlIHdpbGwgYmUgbW9yZSBvciBsZXNzIGltcG9ydGFudCB0aGFuIG90aGVyIGZlYXR1cmVzIGluIG91ciBtb2RlbCwgdGhlbiB3ZSBjYW4gYXBwbHkgYSB3ZWlnaHQgdG8gaXRzIHNjYWxlZCB2YWx1ZXMgaW4gb3JkZXIgdG8gaGF2ZSBpdCBlbXBoYXNpemVkIG1vcmUgaGlnaGx5IHdoZW4gY2FsY3VsYXRpbmcgZGlzdGFuY2VzLiANCg0KYGBge3J9DQpzZXQuc2VlZCgxKQ0KDQp0cmFpbl9hY2NfdyA8LSBjKCkNCnZhbGlkX2FjY193IDwtIGMoKQ0KDQp3IDwtIGMoMSwgMSwgMCwgMCwgMCwgMSwgMSwgMikNCg0KdHJhaW5fdyA8LSB0KHQodHJhaW5fc19zYykqdykNCnZhbGlkX3cgPC0gdCh0KHZhbGlkX3Nfc2MpKncpDQoNCmtfcmFuZ2UgPC0gMToxMDANCg0KZm9yIChpIGluIGtfcmFuZ2Upew0KICAjIFNjYWxlZA0KICBzZXQuc2VlZCgxKQ0KICB0cmFpbl9wcmVkIDwtIGtubih0cmFpbl93LCB0cmFpbl93LCB0cmFpbiRPdXRjb21lLCBrPWkpDQogIHRyYWluX2FjY193IDwtIGModHJhaW5fYWNjX3csIG1lYW4odHJhaW5fcHJlZCA9PSB0cmFpbiRPdXRjb21lKSkNCiAgDQogIHNldC5zZWVkKDEpDQogIHZhbGlkX3ByZWQgPC0ga25uKHRyYWluX3csIHZhbGlkX3csIHRyYWluJE91dGNvbWUsIGs9aSkNCiAgdmFsaWRfYWNjX3cgPC0gYyh2YWxpZF9hY2NfdywgbWVhbih2YWxpZF9wcmVkID09IHZhbGlkJE91dGNvbWUpKQ0KfQ0KDQptYXgodmFsaWRfYWNjX3cpDQpgYGANCg0KDQpgYGB7cn0NCnBsb3Qoa19yYW5nZSwgdHJhaW5fYWNjX3csIHBjaD0nLicsIHlsaW09YygwLjY1LCAxKSwgY29sPSdzYWxtb24nKQ0KbGluZXMoa19yYW5nZSwgdHJhaW5fYWNjX3csIGx3ZD0yLCBjb2w9J3NhbG1vbicpDQpsaW5lcyhrX3JhbmdlLCB2YWxpZF9hY2NfdywgbHdkPTIsIGNvbD0nY29ybmZsb3dlcmJsdWUnKQ0KbGVnZW5kKDc1LCAxLCBsZWdlbmQ9YygiVHJhaW5pbmcgQWNjIiwgIlZhbGlkYXRpb24gQWNjIiksDQogICAgICAgY29sPWMoInNhbG1vbiIsICJjb3JuZmxvd2VyYmx1ZSIpLCBsdHk9MSwgbHdkPTIsIGNleD0wLjgpDQpgYGANCg0KDQpgYGB7cn0NCm1heCh2YWxpZF9hY2NfdykNCmBgYA0KDQoNCmBgYHtyfQ0Kd2hpY2gubWF4KHZhbGlkX2FjY193KQ0KYGBgDQoNCg0KIyMjICoqUHJvcyBhbmQgQ29ucyBvZiBLTk4qKg0KDQoqKlByb3MqKg0KDQoqIEVhc3kgdG8gdW5kZXJzdGFuZC4NCiogRmxleGlibGUgKEFsdGhvdWdoLCBvbmUgbXVzdCBiZSBjYXJlZnVsIGFib3V0IG92ZXJmaXR0aW5nLikNCiogTm8gdGltZSByZXF1aXJlZCB0byB0cmFpbi4NCiogTmF0dXJhbGx5IGFkYXB0cyB0byBtdWx0aS1jbGFzcyBjbGFzc2lmaWNhdGlvbi4NCg0KKipDb25zKioNCg0KKiBNYWtpbmcgcHJlZGljdGlvbnMgaXMgY29tcHV0YXRpb25hbGx5IGludGVuc2l2ZSwgYW5kIGNhbiBiZSBzbG93IG9uIGEgbGFyZ2UgZGF0YXNldC4NCiogUmVxdWlyZXMgYSBtZWFuaW5nZnVsIG5vdGlvbiBvZiBkaXN0YW5jZSBpbiB0aGUgZmVhdHVyZSBzcGFjZS4NCiogR2VuZXJhbGx5IHJlcXVpcmVzIGZlYXR1cmVzIHRvIGJlIHNjYWxlZC9ub3JtYWxpemVkLg0KKiBNb2RlbCBwZXJmb3JtYW5jZSBjYW4gYmUgZGltaW5pc2hlZCB3aGVuIHRoZXJlIGFyZSBtYW55IGRpbWVuc2lvbnMuIChDdXJzZSBvZiBkaW1lbnNpb25hbGl0eSkNCg0K