KNN is k-nearest neighbor classification algorithm. KNN is a supervised algorithm.This deal with classification problems.

Limitations–

  1. If data is noisy hard to find the clear boundary.
  2. Need nice k
    3)Lasy learner-Do you feel it is learning from training data. No
  3. Missing data need extra effort

Example for KNN algorithm– suppose we have 4 cities in the two dim space. A(2,3), B(3,4), C(1,3) and D(4,6). This are in our training model.let a test city is T(2,2). Given A and B in SF and C and D in San Jose.

Distance of A from T=sqrt((2-2)^2 + (3-2)^2)=1
Distance of B from T=sqrt((3-2)^2 + (4-2)^2)=2.2360
Distance of C from T=sqrt((1-2)^2 + (3-2)^2)=1.414
Distance of D from T=sqrt((4-2)^2 + (6-2)^2)=4.4721

If we will take k=1, A will be nearest to T.T will be in SF If we will take k=2, It will take A and C, so even number is a bad option in KNN. If we will take k=3, It will take A, C, B majority is in SF so T will be in SF.

A larger selection of k is a bad option for noisy data. Mostly k is used as sqrt(n), where n is the number of observations in training dataset.

As we are dealing with the distance. we should use the same scale for all variables otherwise it will mislead our algorithms.

KNN use Euclidean distance for distance calculation.

#Distance calculation
sqrt((4-2)^2+(6-2)^2)
[1] 4.472136
#Reading data
data1=iris
str(data1)
'data.frame':   150 obs. of  5 variables:
 $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
 $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
 $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
 $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
 $ Species     : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...

Iris data has 4 numerical and 1 categorical variables. Something interesting about iris data. link

#Number of species
table(data1$Species)

    setosa versicolor  virginica 
        50         50         50 
#Summary of numerical variables 
summary(data1)
  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                  

As we can see that scale is not same for all three variables, so our first task is to make scale same.

#Lets normalize the data
n=function(y) {return((y-min(y))/(max(y)-min(y))) }
data1_n=as.data.frame(lapply(data1[-5],n))
#Summary of normalized data
summary(data1_n)
  Sepal.Length     Sepal.Width      Petal.Length     Petal.Width     
 Min.   :0.0000   Min.   :0.0000   Min.   :0.0000   Min.   :0.00000  
 1st Qu.:0.2222   1st Qu.:0.3333   1st Qu.:0.1017   1st Qu.:0.08333  
 Median :0.4167   Median :0.4167   Median :0.5678   Median :0.50000  
 Mean   :0.4287   Mean   :0.4406   Mean   :0.4675   Mean   :0.45806  
 3rd Qu.:0.5833   3rd Qu.:0.5417   3rd Qu.:0.6949   3rd Qu.:0.70833  
 Max.   :1.0000   Max.   :1.0000   Max.   :1.0000   Max.   :1.00000  
#Lets try Z score
data1_z=as.data.frame(scale(data1[-5]))

value are in the same scale.

#Lets divide data into training and test
set.seed(12)
sam=sample(150,110,replace = FALSE)
sam
  [1]  11 122 140  40  25   5  26  92   4   2  55 114  52  53  37  60  62  72  88  15  29 102
 [23]  13  91  28  34  63  24  54  82 130 107 105  96  74 109  80 117  44 112  65  58 106  22
 [45]  90  10 147 145  16  79 104  36  42  32 149  93 137 113 135  75  68  39  87  49 144  14
 [67]  12 124  30 134  86  57  48 121 123 133  59 138  84 115  38  61   9  17  78  18 142  56
 [89]  76   7  41 118 116  21  33 126  98  20  67 110 146  95 119 143  97  83 111 108  69 101
train1=data1_n[sam,]
test1=data1_n[-sam,]
train2=data1_z[sam,]
test2=data1_z[-sam,]
#Creating labels 
train_label=data1[sam,5]
test_label=data1[-sam,5]
#KNN model with k=1
library(class)
pred1=knn(train = train1,test=test1, cl=train_label , k=1)
#Model performance 
library(gmodels)
CrossTable(x=test_label,y=pred1,prop.chisq = FALSE)

 
   Cell Contents
|-------------------------|
|                       N |
|           N / Row Total |
|           N / Col Total |
|         N / Table Total |
|-------------------------|

 
Total Observations in Table:  40 

 
             | pred1 
  test_label |     setosa | versicolor |  virginica |  Row Total | 
-------------|------------|------------|------------|------------|
      setosa |         14 |          0 |          0 |         14 | 
             |      1.000 |      0.000 |      0.000 |      0.350 | 
             |      1.000 |      0.000 |      0.000 |            | 
             |      0.350 |      0.000 |      0.000 |            | 
-------------|------------|------------|------------|------------|
  versicolor |          0 |         12 |          1 |         13 | 
             |      0.000 |      0.923 |      0.077 |      0.325 | 
             |      0.000 |      0.857 |      0.083 |            | 
             |      0.000 |      0.300 |      0.025 |            | 
-------------|------------|------------|------------|------------|
   virginica |          0 |          2 |         11 |         13 | 
             |      0.000 |      0.154 |      0.846 |      0.325 | 
             |      0.000 |      0.143 |      0.917 |            | 
             |      0.000 |      0.050 |      0.275 |            | 
-------------|------------|------------|------------|------------|
Column Total |         14 |         14 |         12 |         40 | 
             |      0.350 |      0.350 |      0.300 |            | 
-------------|------------|------------|------------|------------|

 
#Function for accuracy
accuracy=function(actual,prediction){mean(actual==prediction)}
a1=accuracy(test_label,pred1)
a1
[1] 0.925
#KNN model with z score transformation
pred2=knn(train = train2,test=test2 , cl=train_label ,k=1)
CrossTable(x=pred2,y=test_label,prop.chisq = FALSE)

 
   Cell Contents
|-------------------------|
|                       N |
|           N / Row Total |
|           N / Col Total |
|         N / Table Total |
|-------------------------|

 
Total Observations in Table:  40 

 
             | test_label 
       pred2 |     setosa | versicolor |  virginica |  Row Total | 
-------------|------------|------------|------------|------------|
      setosa |         14 |          0 |          0 |         14 | 
             |      1.000 |      0.000 |      0.000 |      0.350 | 
             |      1.000 |      0.000 |      0.000 |            | 
             |      0.350 |      0.000 |      0.000 |            | 
-------------|------------|------------|------------|------------|
  versicolor |          0 |         12 |          3 |         15 | 
             |      0.000 |      0.800 |      0.200 |      0.375 | 
             |      0.000 |      0.923 |      0.231 |            | 
             |      0.000 |      0.300 |      0.075 |            | 
-------------|------------|------------|------------|------------|
   virginica |          0 |          1 |         10 |         11 | 
             |      0.000 |      0.091 |      0.909 |      0.275 | 
             |      0.000 |      0.077 |      0.769 |            | 
             |      0.000 |      0.025 |      0.250 |            | 
-------------|------------|------------|------------|------------|
Column Total |         14 |         13 |         13 |         40 | 
             |      0.350 |      0.325 |      0.325 |            | 
-------------|------------|------------|------------|------------|

 
#Checking for accuracy
a2=accuracy(test_label,pred2)
a2
[1] 0.9
#KNN model with K=3
pred3=knn(train = train1,test=test1, cl=train_label , k=3)
CrossTable(x=test_label,y=pred3,prop.chisq = FALSE)

 
   Cell Contents
|-------------------------|
|                       N |
|           N / Row Total |
|           N / Col Total |
|         N / Table Total |
|-------------------------|

 
Total Observations in Table:  40 

 
             | pred3 
  test_label |     setosa | versicolor |  virginica |  Row Total | 
-------------|------------|------------|------------|------------|
      setosa |         14 |          0 |          0 |         14 | 
             |      1.000 |      0.000 |      0.000 |      0.350 | 
             |      1.000 |      0.000 |      0.000 |            | 
             |      0.350 |      0.000 |      0.000 |            | 
-------------|------------|------------|------------|------------|
  versicolor |          0 |         12 |          1 |         13 | 
             |      0.000 |      0.923 |      0.077 |      0.325 | 
             |      0.000 |      0.923 |      0.077 |            | 
             |      0.000 |      0.300 |      0.025 |            | 
-------------|------------|------------|------------|------------|
   virginica |          0 |          1 |         12 |         13 | 
             |      0.000 |      0.077 |      0.923 |      0.325 | 
             |      0.000 |      0.077 |      0.923 |            | 
             |      0.000 |      0.025 |      0.300 |            | 
-------------|------------|------------|------------|------------|
Column Total |         14 |         13 |         13 |         40 | 
             |      0.350 |      0.325 |      0.325 |            | 
-------------|------------|------------|------------|------------|

 
#Checking for accuracy
a3=accuracy(test_label,pred3)
a3
[1] 0.95
#KNN model with k=5
pred4=knn(train = train1,test=test1, cl=train_label , k=5)
CrossTable(x=test_label,y=pred4,prop.chisq = FALSE)

 
   Cell Contents
|-------------------------|
|                       N |
|           N / Row Total |
|           N / Col Total |
|         N / Table Total |
|-------------------------|

 
Total Observations in Table:  40 

 
             | pred4 
  test_label |     setosa | versicolor |  virginica |  Row Total | 
-------------|------------|------------|------------|------------|
      setosa |         14 |          0 |          0 |         14 | 
             |      1.000 |      0.000 |      0.000 |      0.350 | 
             |      1.000 |      0.000 |      0.000 |            | 
             |      0.350 |      0.000 |      0.000 |            | 
-------------|------------|------------|------------|------------|
  versicolor |          0 |         13 |          0 |         13 | 
             |      0.000 |      1.000 |      0.000 |      0.325 | 
             |      0.000 |      0.929 |      0.000 |            | 
             |      0.000 |      0.325 |      0.000 |            | 
-------------|------------|------------|------------|------------|
   virginica |          0 |          1 |         12 |         13 | 
             |      0.000 |      0.077 |      0.923 |      0.325 | 
             |      0.000 |      0.071 |      1.000 |            | 
             |      0.000 |      0.025 |      0.300 |            | 
-------------|------------|------------|------------|------------|
Column Total |         14 |         14 |         12 |         40 | 
             |      0.350 |      0.350 |      0.300 |            | 
-------------|------------|------------|------------|------------|

 
#Checking for accuracy
a4=accuracy(test_label,pred4)
a4
[1] 0.975
#KNN model with k=7
pred5=knn(train = train1,test=test1 , cl=train_label ,k=7)
CrossTable(x=test_label , y=pred5, prop.chisq = FALSE)

 
   Cell Contents
|-------------------------|
|                       N |
|           N / Row Total |
|           N / Col Total |
|         N / Table Total |
|-------------------------|

 
Total Observations in Table:  40 

 
             | pred5 
  test_label |     setosa | versicolor |  virginica |  Row Total | 
-------------|------------|------------|------------|------------|
      setosa |         14 |          0 |          0 |         14 | 
             |      1.000 |      0.000 |      0.000 |      0.350 | 
             |      1.000 |      0.000 |      0.000 |            | 
             |      0.350 |      0.000 |      0.000 |            | 
-------------|------------|------------|------------|------------|
  versicolor |          0 |         13 |          0 |         13 | 
             |      0.000 |      1.000 |      0.000 |      0.325 | 
             |      0.000 |      0.929 |      0.000 |            | 
             |      0.000 |      0.325 |      0.000 |            | 
-------------|------------|------------|------------|------------|
   virginica |          0 |          1 |         12 |         13 | 
             |      0.000 |      0.077 |      0.923 |      0.325 | 
             |      0.000 |      0.071 |      1.000 |            | 
             |      0.000 |      0.025 |      0.300 |            | 
-------------|------------|------------|------------|------------|
Column Total |         14 |         14 |         12 |         40 | 
             |      0.350 |      0.350 |      0.300 |            | 
-------------|------------|------------|------------|------------|

 
#Checking for accuracy
a5=accuracy(test_label,pred5)
a5
[1] 0.975
#KNN model with k=9
pred6=knn(train = train1,test=test1 , cl=train_label ,k=9)
CrossTable(x=test_label , y=pred6, prop.chisq = FALSE)

 
   Cell Contents
|-------------------------|
|                       N |
|           N / Row Total |
|           N / Col Total |
|         N / Table Total |
|-------------------------|

 
Total Observations in Table:  40 

 
             | pred6 
  test_label |     setosa | versicolor |  virginica |  Row Total | 
-------------|------------|------------|------------|------------|
      setosa |         14 |          0 |          0 |         14 | 
             |      1.000 |      0.000 |      0.000 |      0.350 | 
             |      1.000 |      0.000 |      0.000 |            | 
             |      0.350 |      0.000 |      0.000 |            | 
-------------|------------|------------|------------|------------|
  versicolor |          0 |         13 |          0 |         13 | 
             |      0.000 |      1.000 |      0.000 |      0.325 | 
             |      0.000 |      1.000 |      0.000 |            | 
             |      0.000 |      0.325 |      0.000 |            | 
-------------|------------|------------|------------|------------|
   virginica |          0 |          0 |         13 |         13 | 
             |      0.000 |      0.000 |      1.000 |      0.325 | 
             |      0.000 |      0.000 |      1.000 |            | 
             |      0.000 |      0.000 |      0.325 |            | 
-------------|------------|------------|------------|------------|
Column Total |         14 |         13 |         13 |         40 | 
             |      0.350 |      0.325 |      0.325 |            | 
-------------|------------|------------|------------|------------|

 
#Checking for accuracy
a6=accuracy(test_label,pred6)
a6
[1] 1
accuracy=c(a1,a3,a4,a5,a6)
k_values=c(1,3,5,7,9)
library(ggplot2)
data2=cbind.data.frame(accuracy,k_values)
ggplot(data2,aes(y=accuracy,x=k_values))+geom_smooth(col="red")

KKN is non-parametric algorithm. It does not keep any assumption for data distribution.and it does not effected by outliers.

library(alr4)
data3=alr4::UN11
str(data3)
'data.frame':   199 obs. of  6 variables:
 $ region   : Factor w/ 8 levels "Africa","Asia",..: 2 4 1 1 3 5 2 3 8 4 ...
 $ group    : Factor w/ 3 levels "oecd","other",..: 2 2 3 3 2 2 2 2 1 1 ...
 $ fertility: num  5.97 1.52 2.14 5.13 2 ...
 $ ppgdp    : num  499 3677 4473 4322 13750 ...
 $ lifeExpF : num  49.5 80.4 75 53.2 81.1 ...
 $ pctUrban : num  23 53 67 59 100 93 64 47 89 68 ...
 - attr(*, "na.action")=Class 'omit'  Named int [1:34] 4 5 8 28 41 67 68 72 79 83 ...
  .. ..- attr(*, "names")= chr [1:34] "Am Samoa" "Andorra" "Antigua and Barbuda" "Br Virigin Is" ...

We have 2 factor variable and 4 numerical variables. our preditor variable is life expectancy.

#Converting factors to numeric variables 
data3$region=as.integer(as.factor(data3$region))
data3$group=as.integer(as.factor(data3$group))
str(data3)
'data.frame':   199 obs. of  6 variables:
 $ region   : int  2 4 1 1 3 5 2 3 8 4 ...
 $ group    : int  2 2 3 3 2 2 2 2 1 1 ...
 $ fertility: num  5.97 1.52 2.14 5.13 2 ...
 $ ppgdp    : num  499 3677 4473 4322 13750 ...
 $ lifeExpF : num  49.5 80.4 75 53.2 81.1 ...
 $ pctUrban : num  23 53 67 59 100 93 64 47 89 68 ...
 - attr(*, "na.action")=Class 'omit'  Named int [1:34] 4 5 8 28 41 67 68 72 79 83 ...
  .. ..- attr(*, "names")= chr [1:34] "Am Samoa" "Andorra" "Antigua and Barbuda" "Br Virigin Is" ...
dim(data3_test_labels)
[1] 79  1
#Creating Dataframe with test and prediction
data_pred=cbind.data.frame(pred_55,data3_test_labels)
library(dplyr)
rename(data=data_pred,t.pred_44.=predicted , `data3_n[-sample1, 5]`=actual)
Error in captureDots(strict = `__quosured`) : 
  the argument has already been evaluated
#Actual and predicted
ggplot(data=data_pred,aes(x=t.pred_44.,y=`data3_n[-sample1, 5]`))+geom_smooth(col="red")+xlab("predicted")+ylab("Actual")

LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpLTk4gaXMgay1uZWFyZXN0IG5laWdoYm9yIGNsYXNzaWZpY2F0aW9uIGFsZ29yaXRobS4gS05OIGlzIGEgc3VwZXJ2aXNlZCBhbGdvcml0aG0uVGhpcyBkZWFsIHdpdGggY2xhc3NpZmljYXRpb24gcHJvYmxlbXMuDQoNCiNMaW1pdGF0aW9ucy0tICANCjEpIElmIGRhdGEgaXMgbm9pc3kgaGFyZCB0byBmaW5kIHRoZSBjbGVhciBib3VuZGFyeS4gICANCjIpIE5lZWQgbmljZSBrICANCjMpTGFzeSBsZWFybmVyLURvIHlvdSBmZWVsIGl0IGlzIGxlYXJuaW5nIGZyb20gdHJhaW5pbmcgZGF0YS4gTm8gIA0KNCkgTWlzc2luZyBkYXRhIG5lZWQgZXh0cmEgZWZmb3J0ICANCg0KRXhhbXBsZSBmb3IgS05OIGFsZ29yaXRobS0tIHN1cHBvc2Ugd2UgaGF2ZSA0IGNpdGllcyBpbiB0aGUgdHdvIGRpbSBzcGFjZS4gQSgyLDMpLCBCKDMsNCksIEMoMSwzKSBhbmQgRCg0LDYpLiBUaGlzIGFyZSBpbiBvdXIgdHJhaW5pbmcgbW9kZWwubGV0IGEgdGVzdCBjaXR5IGlzIFQoMiwyKS4gR2l2ZW4gQSBhbmQgQiBpbiBTRiBhbmQgQyBhbmQgRCBpbiBTYW4gSm9zZS4NCg0KRGlzdGFuY2Ugb2YgQSBmcm9tIFQ9c3FydCgoMi0yKV4yICsgKDMtMileMik9MSAgDQpEaXN0YW5jZSBvZiBCIGZyb20gVD1zcXJ0KCgzLTIpXjIgKyAoNC0yKV4yKT0yLjIzNjAgIA0KRGlzdGFuY2Ugb2YgQyBmcm9tIFQ9c3FydCgoMS0yKV4yICsgKDMtMileMik9MS40MTQgIA0KRGlzdGFuY2Ugb2YgRCBmcm9tIFQ9c3FydCgoNC0yKV4yICsgKDYtMileMik9NC40NzIxICANCg0KSWYgd2Ugd2lsbCB0YWtlIGs9MSwgQSB3aWxsIGJlIG5lYXJlc3QgdG8gVC5UIHdpbGwgYmUgaW4gU0YNCklmIHdlIHdpbGwgdGFrZSBrPTIsIEl0IHdpbGwgdGFrZSBBIGFuZCBDLCBzbyBldmVuIG51bWJlciBpcyBhIGJhZCBvcHRpb24gaW4gS05OLg0KSWYgd2Ugd2lsbCB0YWtlIGs9MywgSXQgd2lsbCB0YWtlIEEsIEMsIEIgbWFqb3JpdHkgaXMgaW4gU0Ygc28gVCB3aWxsIGJlIGluIFNGLg0KDQpBIGxhcmdlciBzZWxlY3Rpb24gb2YgayBpcyBhIGJhZCBvcHRpb24gZm9yIG5vaXN5IGRhdGEuIE1vc3RseSBrIGlzIHVzZWQgYXMgc3FydChuKSwgd2hlcmUgbiBpcyB0aGUgbnVtYmVyIG9mIG9ic2VydmF0aW9ucyBpbiB0cmFpbmluZyBkYXRhc2V0Lg0KDQpBcyB3ZSBhcmUgZGVhbGluZyB3aXRoIHRoZSBkaXN0YW5jZS4gd2Ugc2hvdWxkIHVzZSB0aGUgc2FtZSBzY2FsZSBmb3IgYWxsIHZhcmlhYmxlcyBvdGhlcndpc2UgaXQgd2lsbCBtaXNsZWFkIG91ciBhbGdvcml0aG1zLg0KDQpLTk4gdXNlIEV1Y2xpZGVhbiBkaXN0YW5jZSBmb3IgZGlzdGFuY2UgY2FsY3VsYXRpb24uDQoNCmBgYHtyfQ0KI0Rpc3RhbmNlIGNhbGN1bGF0aW9uDQpzcXJ0KCg0LTIpXjIrKDYtMileMikNCmBgYA0KYGBge3J9DQojUmVhZGluZyBkYXRhDQpkYXRhMT1pcmlzDQpzdHIoZGF0YTEpDQpgYGANCg0KSXJpcyBkYXRhIGhhcyA0IG51bWVyaWNhbCBhbmQgMSBjYXRlZ29yaWNhbCB2YXJpYWJsZXMuDQpTb21ldGhpbmcgaW50ZXJlc3RpbmcgYWJvdXQgaXJpcyBkYXRhLg0KW2xpbmtdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0lyaXNfKHBsYW50KSkNCg0KYGBge3J9DQojTnVtYmVyIG9mIHNwZWNpZXMNCnRhYmxlKGRhdGExJFNwZWNpZXMpDQpgYGANCg0KYGBge3J9DQojU3VtbWFyeSBvZiBudW1lcmljYWwgdmFyaWFibGVzIA0Kc3VtbWFyeShkYXRhMSkNCmBgYA0KQXMgd2UgY2FuIHNlZSB0aGF0IHNjYWxlIGlzIG5vdCBzYW1lIGZvciBhbGwgdGhyZWUgdmFyaWFibGVzLCBzbyBvdXIgZmlyc3QgdGFzayBpcyB0byBtYWtlIHNjYWxlIHNhbWUuDQpgYGB7cn0NCiNMZXRzIG5vcm1hbGl6ZSB0aGUgZGF0YQ0Kbj1mdW5jdGlvbih5KSB7cmV0dXJuKCh5LW1pbih5KSkvKG1heCh5KS1taW4oeSkpKSB9DQpkYXRhMV9uPWFzLmRhdGEuZnJhbWUobGFwcGx5KGRhdGExWy01XSxuKSkNCg0KI1N1bW1hcnkgb2Ygbm9ybWFsaXplZCBkYXRhDQpzdW1tYXJ5KGRhdGExX24pDQoNCiNMZXRzIHRyeSBaIHNjb3JlDQpkYXRhMV96PWFzLmRhdGEuZnJhbWUoc2NhbGUoZGF0YTFbLTVdKSkNCmBgYA0KdmFsdWUgYXJlIGluIHRoZSBzYW1lIHNjYWxlLg0KYGBge3J9DQojTGV0cyBkaXZpZGUgZGF0YSBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0DQpzZXQuc2VlZCgxMikNCnNhbT1zYW1wbGUoMTUwLDExMCxyZXBsYWNlID0gRkFMU0UpDQpzYW0NCnRyYWluMT1kYXRhMV9uW3NhbSxdDQp0ZXN0MT1kYXRhMV9uWy1zYW0sXQ0KDQp0cmFpbjI9ZGF0YTFfeltzYW0sXQ0KdGVzdDI9ZGF0YTFfelstc2FtLF0NCg0KI0NyZWF0aW5nIGxhYmVscyANCnRyYWluX2xhYmVsPWRhdGExW3NhbSw1XQ0KdGVzdF9sYWJlbD1kYXRhMVstc2FtLDVdDQpgYGANCg0KYGBge3J9DQojS05OIG1vZGVsIHdpdGggaz0xDQpsaWJyYXJ5KGNsYXNzKQ0KcHJlZDE9a25uKHRyYWluID0gdHJhaW4xLHRlc3Q9dGVzdDEsIGNsPXRyYWluX2xhYmVsICwgaz0xKQ0KDQojTW9kZWwgcGVyZm9ybWFuY2UgDQpsaWJyYXJ5KGdtb2RlbHMpDQpDcm9zc1RhYmxlKHg9dGVzdF9sYWJlbCx5PXByZWQxLHByb3AuY2hpc3EgPSBGQUxTRSkNCmBgYA0KYGBge3J9DQojRnVuY3Rpb24gZm9yIGFjY3VyYWN5DQphY2N1cmFjeT1mdW5jdGlvbihhY3R1YWwscHJlZGljdGlvbil7bWVhbihhY3R1YWw9PXByZWRpY3Rpb24pfQ0KYTE9YWNjdXJhY3kodGVzdF9sYWJlbCxwcmVkMSkNCmExDQpgYGANCg0KYGBge3J9DQojS05OIG1vZGVsIHdpdGggeiBzY29yZSB0cmFuc2Zvcm1hdGlvbg0KcHJlZDI9a25uKHRyYWluID0gdHJhaW4yLHRlc3Q9dGVzdDIgLCBjbD10cmFpbl9sYWJlbCAsaz0xKQ0KQ3Jvc3NUYWJsZSh4PXByZWQyLHk9dGVzdF9sYWJlbCxwcm9wLmNoaXNxID0gRkFMU0UpDQpgYGANCg0KYGBge3J9DQojQ2hlY2tpbmcgZm9yIGFjY3VyYWN5DQphMj1hY2N1cmFjeSh0ZXN0X2xhYmVsLHByZWQyKQ0KYTINCmBgYA0KDQoNCmBgYHtyfQ0KI0tOTiBtb2RlbCB3aXRoIEs9Mw0KcHJlZDM9a25uKHRyYWluID0gdHJhaW4xLHRlc3Q9dGVzdDEsIGNsPXRyYWluX2xhYmVsICwgaz0zKQ0KQ3Jvc3NUYWJsZSh4PXRlc3RfbGFiZWwseT1wcmVkMyxwcm9wLmNoaXNxID0gRkFMU0UpDQpgYGANCmBgYHtyfQ0KI0NoZWNraW5nIGZvciBhY2N1cmFjeQ0KYTM9YWNjdXJhY3kodGVzdF9sYWJlbCxwcmVkMykNCmEzDQpgYGANCg0KDQpgYGB7cn0NCiNLTk4gbW9kZWwgd2l0aCBrPTUNCnByZWQ0PWtubih0cmFpbiA9IHRyYWluMSx0ZXN0PXRlc3QxLCBjbD10cmFpbl9sYWJlbCAsIGs9NSkNCkNyb3NzVGFibGUoeD10ZXN0X2xhYmVsLHk9cHJlZDQscHJvcC5jaGlzcSA9IEZBTFNFKQ0KYGBgDQoNCmBgYHtyfQ0KI0NoZWNraW5nIGZvciBhY2N1cmFjeQ0KYTQ9YWNjdXJhY3kodGVzdF9sYWJlbCxwcmVkNCkNCmE0DQpgYGANCg0KYGBge3J9DQojS05OIG1vZGVsIHdpdGggaz03DQpwcmVkNT1rbm4odHJhaW4gPSB0cmFpbjEsdGVzdD10ZXN0MSAsIGNsPXRyYWluX2xhYmVsICxrPTcpDQpDcm9zc1RhYmxlKHg9dGVzdF9sYWJlbCAsIHk9cHJlZDUsIHByb3AuY2hpc3EgPSBGQUxTRSkNCg0KYGBgDQpgYGB7cn0NCiNDaGVja2luZyBmb3IgYWNjdXJhY3kNCmE1PWFjY3VyYWN5KHRlc3RfbGFiZWwscHJlZDUpDQphNQ0KYGBgDQoNCg0KYGBge3J9DQojS05OIG1vZGVsIHdpdGggaz05DQpwcmVkNj1rbm4odHJhaW4gPSB0cmFpbjEsdGVzdD10ZXN0MSAsIGNsPXRyYWluX2xhYmVsICxrPTkpDQpDcm9zc1RhYmxlKHg9dGVzdF9sYWJlbCAsIHk9cHJlZDYsIHByb3AuY2hpc3EgPSBGQUxTRSkNCmBgYA0KYGBge3J9DQojQ2hlY2tpbmcgZm9yIGFjY3VyYWN5DQphNj1hY2N1cmFjeSh0ZXN0X2xhYmVsLHByZWQ2KQ0KYTYNCmBgYA0KDQoNCg0KYGBge3J9DQphY2N1cmFjeT1jKGExLGEzLGE0LGE1LGE2KQ0Ka192YWx1ZXM9YygxLDMsNSw3LDkpDQpsaWJyYXJ5KGdncGxvdDIpDQpkYXRhMj1jYmluZC5kYXRhLmZyYW1lKGFjY3VyYWN5LGtfdmFsdWVzKQ0KZ2dwbG90KGRhdGEyLGFlcyh5PWFjY3VyYWN5LHg9a192YWx1ZXMpKStnZW9tX3Ntb290aChjb2w9InJlZCIpDQpgYGANCktLTiBpcyBub24tcGFyYW1ldHJpYyBhbGdvcml0aG0uIEl0IGRvZXMgbm90IGtlZXAgYW55IGFzc3VtcHRpb24gZm9yIGRhdGEgZGlzdHJpYnV0aW9uLmFuZCBpdCBkb2VzIG5vdCBlZmZlY3RlZCBieSBvdXRsaWVycy4NCg0KYGBge3J9DQpsaWJyYXJ5KGFscjQpDQpkYXRhMz1hbHI0OjpVTjExDQpzdHIoZGF0YTMpDQpgYGANCg0KV2UgaGF2ZSAyIGZhY3RvciB2YXJpYWJsZSBhbmQgNCBudW1lcmljYWwgdmFyaWFibGVzLiBvdXIgcHJlZGl0b3IgdmFyaWFibGUgaXMgbGlmZSBleHBlY3RhbmN5Lg0KYGBge3J9DQojQ29udmVydGluZyBmYWN0b3JzIHRvIG51bWVyaWMgdmFyaWFibGVzIA0KZGF0YTMkcmVnaW9uPWFzLmludGVnZXIoYXMuZmFjdG9yKGRhdGEzJHJlZ2lvbikpDQpkYXRhMyRncm91cD1hcy5pbnRlZ2VyKGFzLmZhY3RvcihkYXRhMyRncm91cCkpDQpzdHIoZGF0YTMpDQoNCmBgYA0KDQpgYGB7cn0NCmRhdGEzX249YXMuZGF0YS5mcmFtZShsYXBwbHkoZGF0YTMsbikpDQoNCiNUcmFpbmluZyBhbmQgdGVzIGRhdGENCnNhbXBsZTE9c2FtcGxlKDE5OSwxMjAscmVwbGFjZSA9IEZBTFNFKQ0KZGF0YTNfdHJhaW49ZGF0YTNfbltzYW1wbGUxLC01XQ0KZGF0YTNfdGVzdD1kYXRhM19uWy1zYW1wbGUxLC01XQ0KZGltKGRhdGEzX3RyYWluKQ0KZGltKGRhdGEzX3Rlc3QpDQoNCiNDcmVhdGUgbGFiZWxzDQpkYXRhM190cmFpbl9sYWJlbHM9YXMuZGF0YS5mcmFtZShkYXRhM19uW3NhbXBsZTEsNV0pDQpkYXRhM190ZXN0X2xhYmVscz1hcy5kYXRhLmZyYW1lKGRhdGEzX25bLXNhbXBsZTEsNV0pDQoNCiNBcHBseSBLTk4tUmVncmVzc2lvbg0KbGlicmFyeShjbGFzcykNCmxpYnJhcnkoRk5OKQ0KcHJlZDIxPWtubi5yZWcodHJhaW49ZGF0YTNfdHJhaW4sdGVzdCA9ZGF0YTNfdGVzdCx5PWRhdGEzX3RyYWluX2xhYmVscyxrPTEsYWxnb3JpdGhtPWMoImtkX3RyZWUiKSkNCnR5cGVvZihwcmVkMjEpDQojQ29udmVydGluZyBsaXN0IG9mIHByZWQgdG8gZGF0YWZyYW1lIA0KcHJlZDIxX2Q9IGNiaW5kLmRhdGEuZnJhbWUobWF0cml4KHVubGlzdChwcmVkMjEpKSkNCg0KI0RlbGV0aW5nIHVzdXNlZCB2YWx1ZXMgDQpwcmVkXzIyPWRhdGEuZnJhbWUocHJlZDIxX2QpDQpwcmVkXzQ0PWRhdGEuZnJhbWUocHJlZF8yMlstYygxOjMpLF0pDQpwcmVkXzU1PWRhdGEuZnJhbWUodChwcmVkXzQ0KSkNCg0KZGltKHByZWRfNTUpDQpkaW0oZGF0YTNfdGVzdF9sYWJlbHMpDQoNCmBgYA0KYGBge3J9DQojQ3JlYXRpbmcgRGF0YWZyYW1lIHdpdGggdGVzdCBhbmQgcHJlZGljdGlvbg0KZGF0YV9wcmVkPWNiaW5kLmRhdGEuZnJhbWUocHJlZF81NSxkYXRhM190ZXN0X2xhYmVscykNCg0KYGBgDQoNCmBgYHtyfQ0KI0FjdHVhbCBhbmQgcHJlZGljdGVkDQpnZ3Bsb3QoZGF0YT1kYXRhX3ByZWQsYWVzKHg9dC5wcmVkXzQ0Lix5PWBkYXRhM19uWy1zYW1wbGUxLCA1XWApKStnZW9tX3Ntb290aChjb2w9InJlZCIpK3hsYWIoInByZWRpY3RlZCIpK3lsYWIoIkFjdHVhbCIpDQpgYGANCg0KDQoNCg0KDQo=