The objective of this project is to predict heart disease in hospital patients. The processed.data.cleveland data set will be used for testing the KNN algorithm.

First I’ll import the dataset.

Heart <- read.csv(url("https://archive.ics.uci.edu/ml/machine-learning-databases/heart-disease/processed.cleveland.data"), header = FALSE)
head(Heart)

Insert column names into data.

colnames(Heart) <- c("age","sex","cp","trestbps", "chol", "fbs", "restecg", "thalach", "exang", "oldpeak", "slope", "ca", "thal", "num")

head(Heart)

The attribute information is the following:

age: age in years

sex: gender( 1 = male; 0 = female)

cp: chest pain type

– Value 1: typical angina

– Value 2: atypical angina

– Value 3: non-anginal pain

– Value 4: asymptomatic

trestbps: resting blood pressure (in mm Hg on admission to the hospital)

chol: serum cholestoral in mg/dl

fbs: (fasting blood sugar > 120 mg/dl) (1 = true; 0 = false)

restecg: resting electrocardiographic results

– Value 0: normal

– Value 1: having ST-T wave abnormality (T wave inversions and/or ST elevation or depression of > 0.05 mV)

– Value 2: showing probable or definite left ventricular hypertrophy by Estes’ criteria

thalach: maximum heart rate achieved

exang: exercise induced angina (1 = yes; 0 = no)

oldpeak: ST depression induced by exercise relative to rest

slope: the slope of the peak exercise ST segment

– Value 1: upsloping

– Value 2: flat

– Value 3: downsloping

ca: number of major vessels (0-3) colored by flourosopy

thal: 3 = normal; 6 = fixed defect; 7 = reversable defect

num: diagnosis of heart disease (angiographic disease status)

– Value 0: < 50% diameter narrowing

– Value 1: > 50% diameter narrowing

class(Heart$num)

Because num is the outcome we want the algorithm to predict, we must change it to factor. The num variable is coded as

0 - disease free, and then 1, 2, 3, and 4 being different degrees of severity of heart disease. We’re only interested in predicting whether or not a patient has the disease or not, so our “dummy variables” will be:

0 = No Disease

1 = Disease

I’’ll first change the “num” integer variables “2”, “3”, and “4” to “1”, so only “0” and “1” variables remain.

Heart$num[Heart$num=="4"] <- "1"
Heart$num[Heart$num=="3"] <- "1"
Heart$num[Heart$num=="2"] <- "1"

Next I’ll change the “num” variable to a factor

Heart$num <- as.factor(Heart$num)

class(Heart$num)
head(Heart$num)
round(prop.table(table(Heart$num)) * 100, digits = 1)

As shown above, our data set includes 45.9% with heart disease and 54.1% without heart disease.

We also need to convert “thal” and “ca” to number variables, because K-NN requires all variables besides the predictor variable to be numeric.

Heart$thal <- as.character(Heart$thal)
Heart$thal <- as.numeric(Heart$thal)
str(Heart)
'data.frame':   297 obs. of  14 variables:
 $ age     : num  63 67 67 37 41 56 62 57 63 53 ...
 $ sex     : num  1 1 1 1 0 1 0 0 1 1 ...
 $ cp      : num  1 4 4 3 2 2 4 4 4 4 ...
 $ trestbps: num  145 160 120 130 130 120 140 120 130 140 ...
 $ chol    : num  233 286 229 250 204 236 268 354 254 203 ...
 $ fbs     : num  1 0 0 0 0 0 0 0 0 1 ...
 $ restecg : num  2 2 2 0 2 0 2 0 2 2 ...
 $ thalach : num  150 108 129 187 172 178 160 163 147 155 ...
 $ exang   : num  0 1 1 0 0 0 0 1 0 1 ...
 $ oldpeak : num  2.3 1.5 2.6 3.5 1.4 0.8 3.6 0.6 1.4 3.1 ...
 $ slope   : num  3 2 2 3 1 1 3 1 2 3 ...
 $ ca      : Factor w/ 5 levels "?","0.0","1.0",..: 2 5 4 2 2 2 4 2 3 2 ...
 $ thal    : num  6 3 7 3 3 3 3 3 7 7 ...
 $ num     : Factor w/ 2 levels "0","1": 1 2 2 1 1 1 2 1 2 2 ...
 - attr(*, "na.action")=Class 'omit'  Named int [1:6] 88 167 193 267 288 303
  .. ..- attr(*, "names")= chr [1:6] "88" "167" "193" "267" ...
Heart$ca <- as.character(Heart$ca)
Heart$ca <- as.numeric(Heart$ca)
str(Heart)
'data.frame':   297 obs. of  14 variables:
 $ age     : num  63 67 67 37 41 56 62 57 63 53 ...
 $ sex     : num  1 1 1 1 0 1 0 0 1 1 ...
 $ cp      : num  1 4 4 3 2 2 4 4 4 4 ...
 $ trestbps: num  145 160 120 130 130 120 140 120 130 140 ...
 $ chol    : num  233 286 229 250 204 236 268 354 254 203 ...
 $ fbs     : num  1 0 0 0 0 0 0 0 0 1 ...
 $ restecg : num  2 2 2 0 2 0 2 0 2 2 ...
 $ thalach : num  150 108 129 187 172 178 160 163 147 155 ...
 $ exang   : num  0 1 1 0 0 0 0 1 0 1 ...
 $ oldpeak : num  2.3 1.5 2.6 3.5 1.4 0.8 3.6 0.6 1.4 3.1 ...
 $ slope   : num  3 2 2 3 1 1 3 1 2 3 ...
 $ ca      : num  0 3 2 0 0 0 2 0 1 0 ...
 $ thal    : num  6 3 7 3 3 3 3 3 7 7 ...
 $ num     : Factor w/ 2 levels "0","1": 1 2 2 1 1 1 2 1 2 2 ...
 - attr(*, "na.action")=Class 'omit'  Named int [1:6] 88 167 193 267 288 303
  .. ..- attr(*, "names")= chr [1:6] "88" "167" "193" "267" ...

We now need to normalize the data so the KNN vector isn’t improperly influenced by differing measurments and lengths. I’ll first create a normalize function, test it, and then apply it to the data.

normalize <- function(x) {
  return ((x - min(x)) / (max(x) - min(x)))
}

normalize(c(1,2,3,4,5))

normalize(c(10, 20, 30, 40, 50))

As shown above, the function appears to be working correctly - the second set is exactly 10x greater than the first, but after normalization they both output the same values.

I’ll now normalize all the numeric features in the data frame.

Heart[1:13] <- as.data.frame(lapply(Heart[1:13], normalize))


summary(Heart_n$chol)

As seen above, cholestoral which used to have a min of 126 and a max of 564, now has values ranging between 0 and 1.

Split data into training and testing sets.

ind <- sample(2, nrow(Heart), replace=TRUE, prob=c(0.7, 0.3))
train <- Heart[ind==1,]
test <- Heart[ind==2,]

We’ve created “train”, which is a random selection of 197 observations for training, and “test” which is a random selection of 100 observations for testing.

Now to test the model

install.packages("class")
library("class")
pred <- knn(train = train[1:13], test = test[1:13], cl = train$num, k = 1)

We now have an output of predicted disease for the test set of data. To evaluate the model we’ll compare the predictions to the observations we have inthe test data.

library("gmodels")
CrossTable(x = test$num, y = pred, prop.chisq = FALSE)

As seen in the table, 78/100 cases were accurately predicted. This is a good start, but the model must improve if it is to be really used to diagnose patients. Mistakes in this domain are extremely consequential.

I’m now going to try to improve the model by making the k value 5.

pred_2 <- knn(train = train[1:13], test = test[1:13], cl = train$num, k = 5)

CrossTable(x = test$num, y = pred_2, prop.chisq = FALSE)

This is a slight improvement. This model predicted 85/100 observations correctly. In the next model, I’m going to make k = 14. It’s common to make the k value equal to the square root of the sum of the observations in the training set - the square root of 197 is approx. 14.

pred_3 <- knn(train = train[1:13], test = test[1:13], cl = train$num, k = 14)

CrossTable(x = test$num, y = pred_3, prop.chisq = FALSE)

This model is slightly worse - 84/100 correctly chosen. I’m now going to try one more vlaue: k = 10.

pred_4 <- knn(train = train[1:13], test = test[1:13], cl = train$num, k = 10)

CrossTable(x = test$num, y = pred_4, prop.chisq = FALSE)

This model predicted 81/100 observations correctly. Of the models I attempted, “pred_2” was most effective with k = 5. It’s still not accurate enough to use to diagnose heart disease. Some ideas for further improving the model might be to use a larger training set - more data would certainly help. KNN might not be the optimal machine learning method for this problem.

LS0tCnRpdGxlOiAiS05OIE1hY2hpbmUgTGVhcm5pbmcgZm9yIFByZWRpY3RpbmcgSGVhcnQgRGlzZWFzZSIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKIyMjIyBUaGUgb2JqZWN0aXZlIG9mIHRoaXMgcHJvamVjdCBpcyB0byBwcmVkaWN0IGhlYXJ0IGRpc2Vhc2UgaW4gaG9zcGl0YWwgcGF0aWVudHMuIFRoZSBwcm9jZXNzZWQuZGF0YS5jbGV2ZWxhbmQgZGF0YSBzZXQgd2lsbCBiZSB1c2VkIGZvciB0ZXN0aW5nIHRoZSBLTk4gYWxnb3JpdGhtLiAKCiMjIyMgRmlyc3QgSSdsbCBpbXBvcnQgdGhlIGRhdGFzZXQuCgoKYGBge3J9CkhlYXJ0IDwtIHJlYWQuY3N2KHVybCgiaHR0cHM6Ly9hcmNoaXZlLmljcy51Y2kuZWR1L21sL21hY2hpbmUtbGVhcm5pbmctZGF0YWJhc2VzL2hlYXJ0LWRpc2Vhc2UvcHJvY2Vzc2VkLmNsZXZlbGFuZC5kYXRhIiksIGhlYWRlciA9IEZBTFNFKQpoZWFkKEhlYXJ0KQpgYGAKCiMjIyMgIEluc2VydCBjb2x1bW4gbmFtZXMgaW50byBkYXRhLgoKYGBge3J9CmNvbG5hbWVzKEhlYXJ0KSA8LSBjKCJhZ2UiLCJzZXgiLCJjcCIsInRyZXN0YnBzIiwgImNob2wiLCAiZmJzIiwgInJlc3RlY2ciLCAidGhhbGFjaCIsICJleGFuZyIsICJvbGRwZWFrIiwgInNsb3BlIiwgImNhIiwgInRoYWwiLCAibnVtIikKCmhlYWQoSGVhcnQpCmBgYAoKIyMjIyBUaGUgYXR0cmlidXRlIGluZm9ybWF0aW9uIGlzIHRoZSBmb2xsb3dpbmc6CgojIyMjIGFnZTogYWdlIGluIHllYXJzCiMjIyMgc2V4OiBnZW5kZXIoIDEgPSBtYWxlOyAwID0gZmVtYWxlKQojIyMjIGNwOiBjaGVzdCBwYWluIHR5cGUgCiMjIyMgLS0gVmFsdWUgMTogdHlwaWNhbCBhbmdpbmEgCiMjIyMgLS0gVmFsdWUgMjogYXR5cGljYWwgYW5naW5hIAojIyMjIC0tIFZhbHVlIDM6IG5vbi1hbmdpbmFsIHBhaW4gCiMjIyMgLS0gVmFsdWUgNDogYXN5bXB0b21hdGljIAojIyMjIHRyZXN0YnBzOiByZXN0aW5nIGJsb29kIHByZXNzdXJlIChpbiBtbSBIZyBvbiBhZG1pc3Npb24gdG8gdGhlIGhvc3BpdGFsKSAKIyMjIyBjaG9sOiBzZXJ1bSBjaG9sZXN0b3JhbCBpbiBtZy9kbCAKIyMjIyBmYnM6IChmYXN0aW5nIGJsb29kIHN1Z2FyID4gMTIwIG1nL2RsKSAoMSA9IHRydWU7IDAgPSBmYWxzZSkgCiMjIyMgcmVzdGVjZzogcmVzdGluZyBlbGVjdHJvY2FyZGlvZ3JhcGhpYyByZXN1bHRzIAojIyMjIC0tIFZhbHVlIDA6IG5vcm1hbCAKIyMjIyAtLSBWYWx1ZSAxOiBoYXZpbmcgU1QtVCB3YXZlIGFibm9ybWFsaXR5IChUIHdhdmUgaW52ZXJzaW9ucyBhbmQvb3IgU1QgZWxldmF0aW9uIG9yIGRlcHJlc3Npb24gb2YgPiAwLjA1IG1WKSAKIyMjIyAtLSBWYWx1ZSAyOiBzaG93aW5nIHByb2JhYmxlIG9yIGRlZmluaXRlIGxlZnQgdmVudHJpY3VsYXIgaHlwZXJ0cm9waHkgYnkgRXN0ZXMnIGNyaXRlcmlhIAojIyMjIHRoYWxhY2g6IG1heGltdW0gaGVhcnQgcmF0ZSBhY2hpZXZlZAojIyMjIGV4YW5nOiBleGVyY2lzZSBpbmR1Y2VkIGFuZ2luYSAoMSA9IHllczsgMCA9IG5vKSAKIyMjIyBvbGRwZWFrOiBTVCBkZXByZXNzaW9uIGluZHVjZWQgYnkgZXhlcmNpc2UgcmVsYXRpdmUgdG8gcmVzdCAKIyMjIyBzbG9wZTogdGhlIHNsb3BlIG9mIHRoZSBwZWFrIGV4ZXJjaXNlIFNUIHNlZ21lbnQgCiMjIyMgLS0gVmFsdWUgMTogdXBzbG9waW5nIAojIyMjIC0tIFZhbHVlIDI6IGZsYXQgCiMjIyMgLS0gVmFsdWUgMzogZG93bnNsb3BpbmcgCiMjIyMgY2E6IG51bWJlciBvZiBtYWpvciB2ZXNzZWxzICgwLTMpIGNvbG9yZWQgYnkgZmxvdXJvc29weSAKIyMjIyB0aGFsOiAzID0gbm9ybWFsOyA2ID0gZml4ZWQgZGVmZWN0OyA3ID0gcmV2ZXJzYWJsZSBkZWZlY3QgCiMjIyMgbnVtOiBkaWFnbm9zaXMgb2YgaGVhcnQgZGlzZWFzZSAoYW5naW9ncmFwaGljIGRpc2Vhc2Ugc3RhdHVzKSAKIyMjIyAtLSBWYWx1ZSAwOiA8IDUwJSBkaWFtZXRlciBuYXJyb3dpbmcgCiMjIyMgLS0gVmFsdWUgMTogPiA1MCUgZGlhbWV0ZXIgbmFycm93aW5nCgoKYGBge3J9CmNsYXNzKEhlYXJ0JG51bSkKYGBgCgojIyMjIFRoZXJlIGFyZSBzb21lIGVtcHR5IGZlYXR1cmVkIGluIGJvdGggdGhlICJ0aGFsIiBhbmQiY3AiIGNvbHVtbnMuIFRoZXkncmUgbGlzdGVkIGFzICI/Ii4gSSdtIGdvaW5nIHRvIHJlbW92ZSB0aG9zZSBwYXRpZW50IHJlY29yZHMgZnJvbSB0aGUgZGF0YSBzbyB0aGUgSy1OTiBhbGdvcml0aG0gY2FuIHJ1bi4KCmBgYHtyfQpIZWFydFtIZWFydCA9PSAiPyJdIDwtIE5BCkhlYXJ0IDwtIG5hLm9taXQoSGVhcnQpCnN1bShpcy5uYShIZWFydCkpCmBgYAoKCiMjIyMgQmVjYXVzZSBudW0gaXMgdGhlIG91dGNvbWUgd2Ugd2FudCB0aGUgYWxnb3JpdGhtIHRvIHByZWRpY3QsIHdlIG11c3QgY2hhbmdlIGl0IHRvIGZhY3Rvci4gVGhlIG51bSB2YXJpYWJsZSBpcyBjb2RlZCBhcwoKIyMjIyAwIC0gZGlzZWFzZSBmcmVlLCBhbmQgdGhlbiAxLCAyLCAzLCBhbmQgNCBiZWluZyBkaWZmZXJlbnQgZGVncmVlcyBvZiBzZXZlcml0eSBvZiBoZWFydCBkaXNlYXNlLiBXZSdyZSBvbmx5IGludGVyZXN0ZWQgaW4gcHJlZGljdGluZyB3aGV0aGVyIG9yIG5vdCBhIHBhdGllbnQgaGFzIHRoZSBkaXNlYXNlIG9yIG5vdCwgc28gb3VyICJkdW1teSB2YXJpYWJsZXMiIHdpbGwgYmU6CiMjIyMgMCA9IE5vIERpc2Vhc2UKIyMjIyAxID0gRGlzZWFzZQoKIyMjIyBJJydsbCBmaXJzdCBjaGFuZ2UgdGhlICJudW0iIGludGVnZXIgdmFyaWFibGVzICIyIiwgIjMiLCBhbmQgIjQiIHRvICIxIiwgc28gb25seSAiMCIgYW5kICIxIiB2YXJpYWJsZXMgcmVtYWluLgoKCmBgYHtyfQpIZWFydCRudW1bSGVhcnQkbnVtPT0iNCJdIDwtICIxIgpIZWFydCRudW1bSGVhcnQkbnVtPT0iMyJdIDwtICIxIgpIZWFydCRudW1bSGVhcnQkbnVtPT0iMiJdIDwtICIxIgpgYGAKCgojIyMjIE5leHQgSSdsbCBjaGFuZ2UgdGhlICJudW0iIHZhcmlhYmxlIHRvIGEgZmFjdG9yCgoKYGBge3J9CkhlYXJ0JG51bSA8LSBhcy5mYWN0b3IoSGVhcnQkbnVtKQoKY2xhc3MoSGVhcnQkbnVtKQpoZWFkKEhlYXJ0JG51bSkKYGBgCiAKCgoKYGBge3J9CnJvdW5kKHByb3AudGFibGUodGFibGUoSGVhcnQkbnVtKSkgKiAxMDAsIGRpZ2l0cyA9IDEpCmBgYAoKIyMjIyBBcyBzaG93biBhYm92ZSwgb3VyIGRhdGEgc2V0IGluY2x1ZGVzIDQ1LjklIHdpdGggaGVhcnQgZGlzZWFzZSBhbmQgNTQuMSUgd2l0aG91dCBoZWFydCBkaXNlYXNlLgoKIyMjIyBXZSBhbHNvIG5lZWQgdG8gY29udmVydCAidGhhbCIgYW5kICJjYSIgdG8gbnVtYmVyIHZhcmlhYmxlcywgYmVjYXVzZSBLLU5OIHJlcXVpcmVzIGFsbCB2YXJpYWJsZXMgYmVzaWRlcyB0aGUgcHJlZGljdG9yIHZhcmlhYmxlIHRvIGJlIG51bWVyaWMuCgpgYGB7cn0KSGVhcnQkdGhhbCA8LSBhcy5jaGFyYWN0ZXIoSGVhcnQkdGhhbCkKSGVhcnQkdGhhbCA8LSBhcy5udW1lcmljKEhlYXJ0JHRoYWwpCnN0cihIZWFydCkKYGBgCgoKYGBge3J9CkhlYXJ0JGNhIDwtIGFzLmNoYXJhY3RlcihIZWFydCRjYSkKSGVhcnQkY2EgPC0gYXMubnVtZXJpYyhIZWFydCRjYSkKc3RyKEhlYXJ0KQpgYGAKCiMjIyMgV2Ugbm93IG5lZWQgdG8gbm9ybWFsaXplIHRoZSBkYXRhIHNvIHRoZSBLTk4gdmVjdG9yIGlzbid0IGltcHJvcGVybHkgaW5mbHVlbmNlZCBieSBkaWZmZXJpbmcgbWVhc3VybWVudHMgYW5kIGxlbmd0aHMuIEknbGwgZmlyc3QgY3JlYXRlIGEgbm9ybWFsaXplIGZ1bmN0aW9uLCB0ZXN0IGl0LCBhbmQgdGhlbiBhcHBseSBpdCB0byB0aGUgZGF0YS4KCmBgYHtyfQpub3JtYWxpemUgPC0gZnVuY3Rpb24oeCkgewogIHJldHVybiAoKHggLSBtaW4oeCkpIC8gKG1heCh4KSAtIG1pbih4KSkpCn0KCm5vcm1hbGl6ZShjKDEsMiwzLDQsNSkpCgpub3JtYWxpemUoYygxMCwgMjAsIDMwLCA0MCwgNTApKQpgYGAKCiMjIyMgQXMgc2hvd24gYWJvdmUsIHRoZSBmdW5jdGlvbiBhcHBlYXJzIHRvIGJlIHdvcmtpbmcgY29ycmVjdGx5IC0gdGhlIHNlY29uZCBzZXQgaXMgZXhhY3RseSAxMHggZ3JlYXRlciB0aGFuIHRoZSBmaXJzdCwgYnV0IGFmdGVyIG5vcm1hbGl6YXRpb24gdGhleSBib3RoIG91dHB1dCB0aGUgc2FtZSB2YWx1ZXMuCgojIyMjIEknbGwgbm93IG5vcm1hbGl6ZSBhbGwgdGhlIG51bWVyaWMgZmVhdHVyZXMgaW4gdGhlIGRhdGEgZnJhbWUuCgoKYGBge3J9CkhlYXJ0WzE6MTNdIDwtIGFzLmRhdGEuZnJhbWUobGFwcGx5KEhlYXJ0WzE6MTNdLCBub3JtYWxpemUpKQoKCnN1bW1hcnkoSGVhcnRfbiRjaG9sKQpgYGAKCgojIyMjIEFzIHNlZW4gYWJvdmUsIGNob2xlc3RvcmFsIHdoaWNoIHVzZWQgdG8gaGF2ZSBhIG1pbiBvZiAxMjYgYW5kIGEgbWF4IG9mIDU2NCwgbm93IGhhcyB2YWx1ZXMgcmFuZ2luZyBiZXR3ZWVuIDAgYW5kIDEuCgoKCiMjIyMgU3BsaXQgZGF0YSBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0aW5nIHNldHMuCgpgYGB7cn0KaW5kIDwtIHNhbXBsZSgyLCBucm93KEhlYXJ0KSwgcmVwbGFjZT1UUlVFLCBwcm9iPWMoMC43LCAwLjMpKQp0cmFpbiA8LSBIZWFydFtpbmQ9PTEsXQp0ZXN0IDwtIEhlYXJ0W2luZD09MixdCmBgYAoKCgoKIyMjIyBXZSd2ZSBjcmVhdGVkICJ0cmFpbiIsIHdoaWNoIGlzIGEgcmFuZG9tIHNlbGVjdGlvbiBvZiAxOTcgb2JzZXJ2YXRpb25zIGZvciB0cmFpbmluZywgYW5kICJ0ZXN0IiB3aGljaCBpcyBhIHJhbmRvbSBzZWxlY3Rpb24gb2YgMTAwIG9ic2VydmF0aW9ucyBmb3IgdGVzdGluZy4KIyMjIE5vdyB0byB0ZXN0IHRoZSBtb2RlbAoKYGBge3J9Cmluc3RhbGwucGFja2FnZXMoImNsYXNzIikKbGlicmFyeSgiY2xhc3MiKQpgYGAKCgpgYGB7cn0KcHJlZCA8LSBrbm4odHJhaW4gPSB0cmFpblsxOjEzXSwgdGVzdCA9IHRlc3RbMToxM10sIGNsID0gdHJhaW4kbnVtLCBrID0gMSkKYGBgCgojIyMjIFdlIG5vdyBoYXZlIGFuIG91dHB1dCBvZiBwcmVkaWN0ZWQgZGlzZWFzZSBmb3IgdGhlIHRlc3Qgc2V0IG9mIGRhdGEuIFRvIGV2YWx1YXRlIHRoZSBtb2RlbCB3ZSdsbCBjb21wYXJlIHRoZSBwcmVkaWN0aW9ucyB0byB0aGUgb2JzZXJ2YXRpb25zIHdlIGhhdmUgaW50aGUgdGVzdCBkYXRhLgoKYGBge3J9CmxpYnJhcnkoImdtb2RlbHMiKQpDcm9zc1RhYmxlKHggPSB0ZXN0JG51bSwgeSA9IHByZWQsIHByb3AuY2hpc3EgPSBGQUxTRSkKYGBgCgoKCgoKIyMjIyBBcyBzZWVuIGluIHRoZSB0YWJsZSwgNzgvMTAwIGNhc2VzIHdlcmUgYWNjdXJhdGVseSBwcmVkaWN0ZWQuIFRoaXMgaXMgYSBnb29kIHN0YXJ0LCBidXQgdGhlIG1vZGVsIG11c3QgaW1wcm92ZSBpZiBpdCBpcyB0byBiZSByZWFsbHkgdXNlZCB0byBkaWFnbm9zZSBwYXRpZW50cy4gTWlzdGFrZXMgaW4gdGhpcyBkb21haW4gYXJlIGV4dHJlbWVseSBjb25zZXF1ZW50aWFsLiAKCiMjIyMgSSdtIG5vdyBnb2luZyB0byB0cnkgdG8gaW1wcm92ZSB0aGUgbW9kZWwgYnkgbWFraW5nIHRoZSBrIHZhbHVlIDUuCgpgYGB7cn0KcHJlZF8yIDwtIGtubih0cmFpbiA9IHRyYWluWzE6MTNdLCB0ZXN0ID0gdGVzdFsxOjEzXSwgY2wgPSB0cmFpbiRudW0sIGsgPSA1KQoKQ3Jvc3NUYWJsZSh4ID0gdGVzdCRudW0sIHkgPSBwcmVkXzIsIHByb3AuY2hpc3EgPSBGQUxTRSkKYGBgCgoKIyMjIyBUaGlzIGlzIGEgc2xpZ2h0IGltcHJvdmVtZW50LiBUaGlzIG1vZGVsIHByZWRpY3RlZCA4NS8xMDAgb2JzZXJ2YXRpb25zIGNvcnJlY3RseS4gSW4gdGhlIG5leHQgbW9kZWwsIEknbSBnb2luZyB0byBtYWtlIGsgPSAxNC4gSXQncyBjb21tb24gdG8gbWFrZSB0aGUgayB2YWx1ZSBlcXVhbCB0byB0aGUgc3F1YXJlIHJvb3Qgb2YgdGhlIHN1bSBvZiB0aGUgb2JzZXJ2YXRpb25zIGluIHRoZSB0cmFpbmluZyBzZXQgLSB0aGUgc3F1YXJlIHJvb3Qgb2YgMTk3IGlzIGFwcHJveC4gMTQuCgoKYGBge3J9CnByZWRfMyA8LSBrbm4odHJhaW4gPSB0cmFpblsxOjEzXSwgdGVzdCA9IHRlc3RbMToxM10sIGNsID0gdHJhaW4kbnVtLCBrID0gMTQpCgpDcm9zc1RhYmxlKHggPSB0ZXN0JG51bSwgeSA9IHByZWRfMywgcHJvcC5jaGlzcSA9IEZBTFNFKQpgYGAKCiMjIyMgVGhpcyBtb2RlbCBpcyBzbGlnaHRseSB3b3JzZSAtIDg0LzEwMCBjb3JyZWN0bHkgY2hvc2VuLiBJJ20gbm93IGdvaW5nIHRvIHRyeSBvbmUgbW9yZSB2bGF1ZTogayA9IDEwLgoKYGBge3J9CnByZWRfNCA8LSBrbm4odHJhaW4gPSB0cmFpblsxOjEzXSwgdGVzdCA9IHRlc3RbMToxM10sIGNsID0gdHJhaW4kbnVtLCBrID0gMTApCgpDcm9zc1RhYmxlKHggPSB0ZXN0JG51bSwgeSA9IHByZWRfNCwgcHJvcC5jaGlzcSA9IEZBTFNFKQpgYGAKCgojIyMjIFRoaXMgbW9kZWwgcHJlZGljdGVkIDgxLzEwMCBvYnNlcnZhdGlvbnMgY29ycmVjdGx5LiBPZiB0aGUgbW9kZWxzIEkgYXR0ZW1wdGVkLCAicHJlZF8yIiB3YXMgbW9zdCBlZmZlY3RpdmUgd2l0aCBrID0gNS4gSXQncyBzdGlsbCBub3QgYWNjdXJhdGUgZW5vdWdoIHRvIHVzZSB0byBkaWFnbm9zZSBoZWFydCBkaXNlYXNlLiBTb21lIGlkZWFzIGZvciBmdXJ0aGVyIGltcHJvdmluZyB0aGUgbW9kZWwgbWlnaHQgYmUgdG8gdXNlIGEgbGFyZ2VyIHRyYWluaW5nIHNldCAtIG1vcmUgZGF0YSB3b3VsZCBjZXJ0YWlubHkgaGVscC4gS05OIG1pZ2h0IG5vdCBiZSB0aGUgb3B0aW1hbCBtYWNoaW5lIGxlYXJuaW5nIG1ldGhvZCBmb3IgdGhpcyBwcm9ibGVtLgo=