Step 1-Collecting Data

To develop the Naive Bayes classifier, we will use data adapted from the SMS Spam Collection at http://www.dt.fee.unicamp.br/~tiago/smsspamcollection/.

This dataset includes the text of SMS messages along with a label indicating whether the message is unwanted. Junk messages are labeled spam, while legitimate messages are labeled ham.

Step 2-Exploring and preparing the data

# read the sms data into the sms data frame
sms_raw <- read.csv("sms_spam.csv", stringsAsFactors = FALSE)
# examine the structure of the sms data
str(sms_raw)
'data.frame':   5559 obs. of  2 variables:
 $ type: Factor w/ 2 levels "ham","spam": 1 1 1 2 2 1 1 1 2 1 ...
 $ text: chr  "Hope you are having a good week. Just checking in" "K..give back my thanks." "Am also doing in cbe only. But have to pay." "complimentary 4 STAR Ibiza Holiday or £10,000 cash needs your URGENT collection. 09066364349 NOW from Landline not to lose out!"| __truncated__ ...
# convert spam/ham to factor. Factor converts charactor vector to numerical like 0,1.
sms_raw$type <- factor(sms_raw$type)
# build a corpus using the text mining (tm) package
library(tm)
sms_corpus <- VCorpus(VectorSource(sms_raw$text))
# vectorSource takes apart email,it's a reader function.Vcourpus put into a nice structure.Vcorpus stores in memory as oppose to Pcorpus, stored in Disk
# Corpus is a collection of text documents
# examine the sms corpus
print(sms_corpus)
<<VCorpus>>
Metadata:  corpus specific: 0, document level (indexed): 0
Content:  documents: 5559
inspect(sms_corpus[1:2]) # summary of the 1st and 2nd SMS message in the corpus
<<VCorpus>>
Metadata:  corpus specific: 0, document level (indexed): 0
Content:  documents: 2

[[1]]
<<PlainTextDocument>>
Metadata:  7
Content:  chars: 49

[[2]]
<<PlainTextDocument>>
Metadata:  7
Content:  chars: 23
as.character(sms_corpus[[1]])
[1] "Hope you are having a good week. Just checking in"
lapply(sms_corpus[1:2], as.character) # lapply apply a procedure to each element in the data sturture
$`1`
[1] "Hope you are having a good week. Just checking in"

$`2`
[1] "K..give back my thanks."
# clean up the corpus using tm_map()
sms_corpus_clean <- tm_map(sms_corpus, content_transformer(tolower)) #tolower : transform all letters to lower case 
# show the difference between sms_corpus and corpus_clean
as.character(sms_corpus[[1]]) # as.charactor to view the actual text 
[1] "Hope you are having a good week. Just checking in"
as.character(sms_corpus_clean[[1]])
[1] "hope you are having a good week. just checking in"
sms_corpus_clean <- tm_map(sms_corpus_clean, removeNumbers)
# remove numbers
sms_corpus_clean <- tm_map(sms_corpus_clean, removeWords, stopwords()) # remove stop words like and, or the
sms_corpus_clean <- tm_map(sms_corpus_clean, removePunctuation) # remove punctuation
# tip: create a custom function to replace (rather than remove) punctuation
removePunctuation("hello...world")
[1] "helloworld"
replacePunctuation <- function(x) { gsub("[[:punct:]]+", " ", x) } # replace the punctuations in x with a space rather than remove
replacePunctuation("hello...world")
[1] "hello world"
# illustration of word stemming
library(SnowballC)  # snowwball put words into stems: eg: left to leave
wordStem(c("learn", "learned", "learning", "learns"))
[1] "learn" "learn" "learn" "learn"
sms_corpus_clean <- tm_map(sms_corpus_clean, stemDocument)
sms_corpus_clean <- tm_map(sms_corpus_clean, stripWhitespace) # eliminate unneeded whitespace
# create a document-term sparse matrix: DTM:rows indicates text messgaes and columns indicate words
sms_dtm <- DocumentTermMatrix(sms_corpus_clean) 
# alternative solution: create a document-term sparse matrix directly from the SMS corpus # NICE:do all the data cleaning in one step.
sms_dtm2 <- DocumentTermMatrix(sms_corpus, control = list(
  tolower = TRUE,
  removeNumbers = TRUE,
  stopwords = TRUE,
  removePunctuation = TRUE,
  stemming = TRUE
))
# alternative solution: using custom stop words function ensures identical result
sms_dtm3 <- DocumentTermMatrix(sms_corpus, control = list(
  tolower = TRUE,
  removeNumbers = TRUE,
  stopwords = function(x) { removeWords(x, stopwords()) },
  removePunctuation = TRUE,
  stemming = TRUE
))
# compare the result
sms_dtm
<<DocumentTermMatrix (documents: 5559, terms: 6518)>>
Non-/sparse entries: 42113/36191449
Sparsity           : 100%
Maximal term length: 40
Weighting          : term frequency (tf)
sms_dtm2
<<DocumentTermMatrix (documents: 5559, terms: 6909)>>
Non-/sparse entries: 43192/38363939
Sparsity           : 100%
Maximal term length: 40
Weighting          : term frequency (tf)
sms_dtm3
<<DocumentTermMatrix (documents: 5559, terms: 6518)>>
Non-/sparse entries: 42113/36191449
Sparsity           : 100%
Maximal term length: 40
Weighting          : term frequency (tf)
# creating training and test datasets
# Here I used dtm3 for my clean data as oppose to dtm was used in the lecture
sms_dtm_train <- sms_dtm3[1:4169, ]
sms_dtm_test  <- sms_dtm3[4170:5559, ]
# also save the labels
sms_train_labels <- sms_raw[1:4169, ]$type # label data by their types: ham or spam
sms_test_labels  <- sms_raw[4170:5559, ]$type
# check that the proportion of spam is similar in training vs testing
prop.table(table(sms_train_labels))
sms_train_labels
      ham      spam 
0.8647158 0.1352842 
prop.table(table(sms_test_labels))
sms_test_labels
      ham      spam 
0.8683453 0.1316547 
# word cloud visualization
library(wordcloud)
wordcloud(sms_corpus_clean, min.freq = 50, random.order = FALSE)

# random =false means the higher frequence word will appear closer to the center
# min.fre=50 means the word has to accur in the corpus=50/5559=1%.
# subset the training data into spam and ham groups
spam <- subset(sms_raw, type == "spam")
ham  <- subset(sms_raw, type == "ham")
wordcloud(spam$text, max.words = 40, scale = c(3, 0.5))

wordcloud(ham$text, max.words = 40, scale = c(3, 0.5))

sms_dtm_freq_train <- removeSparseTerms(sms_dtm_train, 0.999)
sms_dtm_freq_train
<<DocumentTermMatrix (documents: 4169, terms: 1101)>>
Non-/sparse entries: 24834/4565235
Sparsity           : 99%
Maximal term length: 19
Weighting          : term frequency (tf)
# indicator features for frequent words
sms_freq_words <- findFreqTerms(sms_dtm_train, 5)
str(sms_freq_words)
 chr [1:1136] "abiola" "abl" "abt" ...
# 5 represents at least 5 accurances
#findFreqTerms: takes a DTM and returns a character vector containing
# the words that appear for at least the specified number of times
# create DTMs with only the frequent terms
sms_dtm_freq_train <- sms_dtm_train[ , sms_freq_words]  # remove colums that does not appear at least 5 times
sms_dtm_freq_test <- sms_dtm_test[ , sms_freq_words] 
# convert counts to a factor
convert_counts <- function(x) {
  x <- ifelse(x > 0, "Yes", "No")
}
#The ifelse(x > 0, "Yes", "No") statement transforms the values in x, so that if the value is greater than 0, then it will be replaced by "Yes",otherwise it will be replaced by a "No" string. Lastly, the newly transformed x vector is returned.
# apply() convert_counts() to columns of train/test data
sms_train <- apply(sms_dtm_freq_train, MARGIN = 2, convert_counts)
sms_test  <- apply(sms_dtm_freq_test, MARGIN = 2, convert_counts)
# margin= 2 is to apply to column, margin=1 is to row.

Step 3- Training a model on the data

library(e1071)
sms_classifier <- naiveBayes(sms_train, sms_train_labels)
# what's in the classifier: conditional probability for each

Step 4-Evaluating model performance

sms_test_pred <- predict(sms_classifier, sms_test)
sms_test_pred
   [1] ham  ham  ham  ham  spam ham  ham  ham  ham 
  [10] spam ham  ham  ham  spam spam ham  ham  ham 
  [19] ham  ham  ham  ham  ham  ham  ham  ham  ham 
  [28] ham  ham  ham  ham  ham  ham  ham  spam ham 
  [37] ham  ham  ham  spam ham  ham  ham  ham  ham 
  [46] ham  ham  ham  ham  spam ham  ham  ham  ham 
  [55] ham  ham  ham  ham  ham  ham  ham  ham  ham 
  [64] ham  ham  ham  ham  ham  ham  ham  ham  ham 
  [73] ham  ham  ham  ham  ham  ham  ham  ham  spam
  [82] ham  ham  ham  ham  ham  ham  ham  ham  ham 
  [91] ham  ham  ham  ham  ham  ham  ham  spam ham 
 [100] ham  ham  ham  ham  ham  ham  ham  ham  ham 
 [109] ham  spam spam ham  ham  ham  ham  ham  ham 
 [118] ham  ham  ham  ham  ham  ham  ham  ham  spam
 [127] ham  ham  ham  ham  ham  ham  spam ham  ham 
 [136] ham  ham  ham  ham  ham  ham  ham  ham  ham 
 [145] ham  ham  spam ham  ham  ham  ham  ham  spam
 [154] ham  spam ham  ham  ham  spam ham  ham  ham 
 [163] ham  ham  spam ham  ham  ham  spam ham  ham 
 [172] ham  ham  ham  ham  ham  ham  ham  spam ham 
 [181] ham  ham  ham  ham  ham  ham  spam ham  ham 
 [190] ham  spam ham  ham  ham  ham  ham  ham  ham 
 [199] ham  spam spam ham  ham  ham  ham  ham  ham 
 [208] ham  ham  spam ham  ham  ham  ham  ham  ham 
 [217] spam ham  ham  ham  ham  ham  ham  ham  ham 
 [226] ham  ham  ham  ham  ham  ham  ham  ham  spam
 [235] ham  ham  ham  ham  spam ham  ham  ham  ham 
 [244] ham  ham  ham  ham  ham  ham  spam ham  ham 
 [253] ham  ham  ham  ham  ham  ham  ham  ham  ham 
 [262] ham  ham  ham  ham  ham  ham  ham  ham  spam
 [271] ham  ham  ham  ham  ham  ham  ham  ham  ham 
 [280] ham  ham  ham  ham  ham  ham  ham  ham  ham 
 [289] ham  ham  ham  spam ham  ham  ham  ham  ham 
 [298] spam ham  ham  ham  ham  ham  ham  ham  ham 
 [307] ham  spam ham  ham  ham  spam ham  ham  ham 
 [316] ham  ham  ham  ham  ham  ham  ham  ham  ham 
 [325] ham  ham  ham  spam ham  ham  ham  ham  ham 
 [334] ham  ham  ham  spam ham  ham  ham  ham  ham 
 [343] ham  ham  ham  ham  spam spam ham  ham  ham 
 [352] ham  ham  ham  ham  ham  ham  ham  ham  ham 
 [361] ham  ham  ham  ham  ham  ham  ham  spam ham 
 [370] ham  ham  ham  ham  ham  ham  ham  ham  ham 
 [379] spam ham  ham  ham  ham  spam ham  spam ham 
 [388] ham  ham  ham  ham  ham  ham  ham  ham  ham 
 [397] ham  ham  ham  ham  ham  ham  ham  ham  ham 
 [406] spam ham  ham  spam ham  ham  ham  spam spam
 [415] ham  ham  ham  ham  ham  ham  spam ham  ham 
 [424] ham  ham  ham  ham  ham  ham  ham  ham  ham 
 [433] ham  ham  spam ham  spam ham  ham  ham  ham 
 [442] ham  ham  ham  ham  ham  ham  spam ham  ham 
 [451] ham  spam ham  ham  ham  ham  ham  ham  ham 
 [460] ham  ham  ham  ham  spam ham  ham  ham  ham 
 [469] ham  ham  ham  ham  ham  ham  ham  ham  ham 
 [478] ham  spam ham  ham  ham  spam ham  ham  ham 
 [487] ham  ham  ham  spam ham  ham  ham  ham  ham 
 [496] ham  spam ham  ham  spam spam spam ham  ham 
 [505] ham  ham  ham  ham  spam ham  ham  ham  ham 
 [514] spam ham  ham  ham  ham  ham  ham  ham  ham 
 [523] ham  spam ham  ham  ham  ham  ham  ham  ham 
 [532] ham  ham  ham  ham  spam ham  spam ham  ham 
 [541] ham  ham  ham  ham  spam ham  ham  ham  ham 
 [550] ham  ham  ham  ham  ham  ham  ham  ham  ham 
 [559] ham  ham  ham  ham  ham  spam ham  ham  ham 
 [568] ham  ham  ham  ham  ham  ham  ham  ham  ham 
 [577] spam ham  ham  ham  ham  ham  ham  ham  ham 
 [586] ham  ham  ham  ham  spam ham  ham  ham  ham 
 [595] ham  ham  ham  ham  ham  ham  ham  ham  ham 
 [604] ham  ham  ham  ham  spam ham  spam ham  ham 
 [613] ham  ham  ham  ham  ham  ham  ham  ham  ham 
 [622] ham  ham  ham  spam ham  ham  ham  spam ham 
 [631] ham  ham  ham  ham  ham  ham  ham  ham  ham 
 [640] spam ham  ham  ham  ham  ham  ham  ham  ham 
 [649] ham  spam ham  ham  ham  ham  ham  ham  ham 
 [658] ham  ham  ham  ham  ham  ham  ham  ham  ham 
 [667] ham  ham  ham  ham  ham  ham  ham  ham  ham 
 [676] ham  spam ham  ham  ham  ham  ham  ham  ham 
 [685] ham  spam spam ham  ham  spam ham  ham  spam
 [694] ham  ham  ham  ham  ham  ham  ham  ham  ham 
 [703] ham  ham  ham  ham  ham  spam ham  ham  ham 
 [712] ham  ham  ham  ham  ham  spam ham  ham  ham 
 [721] ham  ham  ham  ham  ham  ham  ham  ham  ham 
 [730] ham  spam ham  spam ham  ham  spam ham  ham 
 [739] ham  ham  spam ham  spam ham  ham  ham  ham 
 [748] ham  spam ham  spam ham  ham  ham  ham  ham 
 [757] ham  ham  ham  ham  ham  ham  ham  ham  ham 
 [766] ham  ham  ham  ham  spam ham  ham  ham  ham 
 [775] ham  ham  ham  ham  ham  ham  spam ham  ham 
 [784] ham  ham  ham  ham  ham  ham  ham  ham  ham 
 [793] ham  ham  ham  ham  ham  ham  ham  ham  ham 
 [802] spam ham  ham  ham  ham  ham  ham  ham  ham 
 [811] ham  ham  ham  spam ham  ham  spam ham  spam
 [820] ham  ham  ham  ham  spam ham  spam ham  spam
 [829] ham  ham  ham  spam ham  ham  ham  ham  spam
 [838] ham  ham  ham  ham  ham  ham  ham  ham  ham 
 [847] ham  ham  ham  ham  ham  ham  ham  ham  ham 
 [856] ham  ham  spam ham  ham  spam ham  ham  ham 
 [865] ham  ham  ham  ham  ham  ham  ham  ham  ham 
 [874] ham  ham  ham  ham  ham  ham  ham  ham  ham 
 [883] ham  ham  ham  ham  ham  ham  ham  ham  ham 
 [892] ham  ham  ham  ham  spam spam ham  ham  spam
 [901] ham  ham  ham  ham  ham  ham  ham  ham  spam
 [910] ham  ham  ham  ham  spam ham  ham  ham  ham 
 [919] ham  ham  ham  ham  ham  ham  ham  ham  ham 
 [928] ham  ham  ham  spam ham  ham  ham  ham  ham 
 [937] ham  ham  ham  ham  ham  ham  ham  ham  ham 
 [946] ham  ham  ham  ham  ham  ham  ham  ham  ham 
 [955] ham  spam ham  ham  ham  ham  ham  ham  ham 
 [964] ham  ham  ham  spam ham  ham  ham  ham  ham 
 [973] ham  ham  ham  ham  ham  ham  ham  spam ham 
 [982] ham  ham  ham  ham  ham  ham  ham  ham  ham 
 [991] ham  ham  ham  spam ham  ham  ham  ham  ham 
[1000] spam
 [ reached getOption("max.print") -- omitted 390 entries ]
Levels: ham spam
library(gmodels)
CrossTable(sms_test_pred, sms_test_labels,
           prop.chisq = FALSE, prop.t = FALSE, prop.r = FALSE,
           dnn = c('predicted', 'actual'))

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

 
Total Observations in Table:  1390 

 
             | actual 
   predicted |       ham |      spam | Row Total | 
-------------|-----------|-----------|-----------|
         ham |      1201 |        30 |      1231 | 
             |     0.995 |     0.164 |           | 
-------------|-----------|-----------|-----------|
        spam |         6 |       153 |       159 | 
             |     0.005 |     0.836 |           | 
-------------|-----------|-----------|-----------|
Column Total |      1207 |       183 |      1390 | 
             |     0.868 |     0.132 |           | 
-------------|-----------|-----------|-----------|

 

Step 5: Improving model performancestep

sms_classifier2 <- naiveBayes(sms_train, sms_train_labels, laplace = 1)
sms_test_pred2 <- predict(sms_classifier2, sms_test)
CrossTable(sms_test_pred2, sms_test_labels,
           prop.chisq = FALSE, prop.t = FALSE, prop.r = FALSE,
           dnn = c('predicted', 'actual'))

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

 
Total Observations in Table:  1390 

 
             | actual 
   predicted |       ham |      spam | Row Total | 
-------------|-----------|-----------|-----------|
         ham |      1202 |        28 |      1230 | 
             |     0.996 |     0.153 |           | 
-------------|-----------|-----------|-----------|
        spam |         5 |       155 |       160 | 
             |     0.004 |     0.847 |           | 
-------------|-----------|-----------|-----------|
Column Total |      1207 |       183 |      1390 | 
             |     0.868 |     0.132 |           | 
-------------|-----------|-----------|-----------|

 
## change lplace =1 makes the model a lil bit beter (cuz FN and FP decrease)
## tuning parameter is to add 1.5 to each value is that to move them a lil away from 0
## Naive Bayes is just as slow as k-NN

Accuracy when laplace=0: Accuracy=(1201+153)/1390= 97%

Accuracy when laplace=1: Accuracy=(1202+155)/1390= 98%

LS0tCnRpdGxlOiAiRmlsdGVyaW5nIHNwYW0gU01TIG1lc3NhZ2VzIHVzaW5nIE5haXZlIEJheWVzIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tClN0ZXAgMS1Db2xsZWN0aW5nIERhdGEKClRvIGRldmVsb3AgdGhlIE5haXZlIEJheWVzIGNsYXNzaWZpZXIsIHdlIHdpbGwgdXNlIGRhdGEgYWRhcHRlZCBmcm9tIHRoZSBTTVMgU3BhbQpDb2xsZWN0aW9uIGF0IGh0dHA6Ly93d3cuZHQuZmVlLnVuaWNhbXAuYnIvfnRpYWdvL3Ntc3NwYW1jb2xsZWN0aW9uLy4KClRoaXMgZGF0YXNldCBpbmNsdWRlcyB0aGUgdGV4dCBvZiBTTVMgbWVzc2FnZXMgYWxvbmcgd2l0aCBhIGxhYmVsIGluZGljYXRpbmcKd2hldGhlciB0aGUgbWVzc2FnZSBpcyB1bndhbnRlZC4gSnVuayBtZXNzYWdlcyBhcmUgbGFiZWxlZCBzcGFtLCB3aGlsZQpsZWdpdGltYXRlIG1lc3NhZ2VzIGFyZSBsYWJlbGVkIGhhbS4KClN0ZXAgMi1FeHBsb3JpbmcgYW5kIHByZXBhcmluZyB0aGUgZGF0YQpgYGB7cn0KIyByZWFkIHRoZSBzbXMgZGF0YSBpbnRvIHRoZSBzbXMgZGF0YSBmcmFtZQpzbXNfcmF3IDwtIHJlYWQuY3N2KCJzbXNfc3BhbS5jc3YiLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCmBgYApgYGB7cn0KIyBleGFtaW5lIHRoZSBzdHJ1Y3R1cmUgb2YgdGhlIHNtcyBkYXRhCnN0cihzbXNfcmF3KQpgYGAKYGBge3J9CiMgY29udmVydCBzcGFtL2hhbSB0byBmYWN0b3IuIEZhY3RvciBjb252ZXJ0cyBjaGFyYWN0b3IgdmVjdG9yIHRvIG51bWVyaWNhbCBsaWtlIDAsMS4Kc21zX3JhdyR0eXBlIDwtIGZhY3RvcihzbXNfcmF3JHR5cGUpCmBgYApgYGB7cn0KIyBidWlsZCBhIGNvcnB1cyB1c2luZyB0aGUgdGV4dCBtaW5pbmcgKHRtKSBwYWNrYWdlCmxpYnJhcnkodG0pCnNtc19jb3JwdXMgPC0gVkNvcnB1cyhWZWN0b3JTb3VyY2Uoc21zX3JhdyR0ZXh0KSkKIyB2ZWN0b3JTb3VyY2UgdGFrZXMgYXBhcnQgZW1haWwsaXQncyBhIHJlYWRlciBmdW5jdGlvbi5WY291cnB1cyBwdXQgaW50byBhIG5pY2Ugc3RydWN0dXJlLlZjb3JwdXMgc3RvcmVzIGluIG1lbW9yeSBhcyBvcHBvc2UgdG8gUGNvcnB1cywgc3RvcmVkIGluIERpc2sKIyBDb3JwdXMgaXMgYSBjb2xsZWN0aW9uIG9mIHRleHQgZG9jdW1lbnRzCmBgYAoKYGBge3J9CiMgZXhhbWluZSB0aGUgc21zIGNvcnB1cwpwcmludChzbXNfY29ycHVzKQppbnNwZWN0KHNtc19jb3JwdXNbMToyXSkgIyBzdW1tYXJ5IG9mIHRoZSAxc3QgYW5kIDJuZCBTTVMgbWVzc2FnZSBpbiB0aGUgY29ycHVzCgphcy5jaGFyYWN0ZXIoc21zX2NvcnB1c1tbMV1dKQpsYXBwbHkoc21zX2NvcnB1c1sxOjJdLCBhcy5jaGFyYWN0ZXIpICMgbGFwcGx5IGFwcGx5IGEgcHJvY2VkdXJlIHRvIGVhY2ggZWxlbWVudCBpbiB0aGUgZGF0YSBzdHVydHVyZQpgYGAKCmBgYHtyfQojIGNsZWFuIHVwIHRoZSBjb3JwdXMgdXNpbmcgdG1fbWFwKCkKc21zX2NvcnB1c19jbGVhbiA8LSB0bV9tYXAoc21zX2NvcnB1cywgY29udGVudF90cmFuc2Zvcm1lcih0b2xvd2VyKSkgI3RvbG93ZXIgOiB0cmFuc2Zvcm0gYWxsIGxldHRlcnMgdG8gbG93ZXIgY2FzZSAKYGBgCgpgYGB7cn0KIyBzaG93IHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gc21zX2NvcnB1cyBhbmQgY29ycHVzX2NsZWFuCmFzLmNoYXJhY3RlcihzbXNfY29ycHVzW1sxXV0pICMgYXMuY2hhcmFjdG9yIHRvIHZpZXcgdGhlIGFjdHVhbCB0ZXh0IAphcy5jaGFyYWN0ZXIoc21zX2NvcnB1c19jbGVhbltbMV1dKQpzbXNfY29ycHVzX2NsZWFuIDwtIHRtX21hcChzbXNfY29ycHVzX2NsZWFuLCByZW1vdmVOdW1iZXJzKQojIHJlbW92ZSBudW1iZXJzCnNtc19jb3JwdXNfY2xlYW4gPC0gdG1fbWFwKHNtc19jb3JwdXNfY2xlYW4sIHJlbW92ZVdvcmRzLCBzdG9wd29yZHMoKSkgIyByZW1vdmUgc3RvcCB3b3JkcyBsaWtlIGFuZCwgb3IgdGhlCnNtc19jb3JwdXNfY2xlYW4gPC0gdG1fbWFwKHNtc19jb3JwdXNfY2xlYW4sIHJlbW92ZVB1bmN0dWF0aW9uKSAjIHJlbW92ZSBwdW5jdHVhdGlvbgpgYGAKCmBgYHtyfQojIHRpcDogY3JlYXRlIGEgY3VzdG9tIGZ1bmN0aW9uIHRvIHJlcGxhY2UgKHJhdGhlciB0aGFuIHJlbW92ZSkgcHVuY3R1YXRpb24KcmVtb3ZlUHVuY3R1YXRpb24oImhlbGxvLi4ud29ybGQiKQpyZXBsYWNlUHVuY3R1YXRpb24gPC0gZnVuY3Rpb24oeCkgeyBnc3ViKCJbWzpwdW5jdDpdXSsiLCAiICIsIHgpIH0gIyByZXBsYWNlIHRoZSBwdW5jdHVhdGlvbnMgaW4geCB3aXRoIGEgc3BhY2UgcmF0aGVyIHRoYW4gcmVtb3ZlCnJlcGxhY2VQdW5jdHVhdGlvbigiaGVsbG8uLi53b3JsZCIpCmBgYAoKYGBge3J9CiMgaWxsdXN0cmF0aW9uIG9mIHdvcmQgc3RlbW1pbmcKbGlicmFyeShTbm93YmFsbEMpICAjIHNub3d3YmFsbCBwdXQgd29yZHMgaW50byBzdGVtczogZWc6IGxlZnQgdG8gbGVhdmUKd29yZFN0ZW0oYygibGVhcm4iLCAibGVhcm5lZCIsICJsZWFybmluZyIsICJsZWFybnMiKSkKCnNtc19jb3JwdXNfY2xlYW4gPC0gdG1fbWFwKHNtc19jb3JwdXNfY2xlYW4sIHN0ZW1Eb2N1bWVudCkKCnNtc19jb3JwdXNfY2xlYW4gPC0gdG1fbWFwKHNtc19jb3JwdXNfY2xlYW4sIHN0cmlwV2hpdGVzcGFjZSkgIyBlbGltaW5hdGUgdW5uZWVkZWQgd2hpdGVzcGFjZQpgYGAKYGBge3J9CiMgY3JlYXRlIGEgZG9jdW1lbnQtdGVybSBzcGFyc2UgbWF0cml4OiBEVE06cm93cyBpbmRpY2F0ZXMgdGV4dCBtZXNzZ2FlcyBhbmQgY29sdW1ucyBpbmRpY2F0ZSB3b3JkcwpzbXNfZHRtIDwtIERvY3VtZW50VGVybU1hdHJpeChzbXNfY29ycHVzX2NsZWFuKSAKYGBgCgpgYGB7cn0KIyBhbHRlcm5hdGl2ZSBzb2x1dGlvbjogY3JlYXRlIGEgZG9jdW1lbnQtdGVybSBzcGFyc2UgbWF0cml4IGRpcmVjdGx5IGZyb20gdGhlIFNNUyBjb3JwdXMgIyBOSUNFOmRvIGFsbCB0aGUgZGF0YSBjbGVhbmluZyBpbiBvbmUgc3RlcC4Kc21zX2R0bTIgPC0gRG9jdW1lbnRUZXJtTWF0cml4KHNtc19jb3JwdXMsIGNvbnRyb2wgPSBsaXN0KAogIHRvbG93ZXIgPSBUUlVFLAogIHJlbW92ZU51bWJlcnMgPSBUUlVFLAogIHN0b3B3b3JkcyA9IFRSVUUsCiAgcmVtb3ZlUHVuY3R1YXRpb24gPSBUUlVFLAogIHN0ZW1taW5nID0gVFJVRQopKQpgYGAKCmBgYHtyfQojIGFsdGVybmF0aXZlIHNvbHV0aW9uOiB1c2luZyBjdXN0b20gc3RvcCB3b3JkcyBmdW5jdGlvbiBlbnN1cmVzIGlkZW50aWNhbCByZXN1bHQKc21zX2R0bTMgPC0gRG9jdW1lbnRUZXJtTWF0cml4KHNtc19jb3JwdXMsIGNvbnRyb2wgPSBsaXN0KAogIHRvbG93ZXIgPSBUUlVFLAogIHJlbW92ZU51bWJlcnMgPSBUUlVFLAogIHN0b3B3b3JkcyA9IGZ1bmN0aW9uKHgpIHsgcmVtb3ZlV29yZHMoeCwgc3RvcHdvcmRzKCkpIH0sCiAgcmVtb3ZlUHVuY3R1YXRpb24gPSBUUlVFLAogIHN0ZW1taW5nID0gVFJVRQopKQoKYGBgCgpgYGB7cn0KIyBjb21wYXJlIHRoZSByZXN1bHQKc21zX2R0bQpzbXNfZHRtMgpzbXNfZHRtMwpgYGAKYGBge3J9CiMgY3JlYXRpbmcgdHJhaW5pbmcgYW5kIHRlc3QgZGF0YXNldHMKIyBIZXJlIEkgdXNlZCBkdG0zIGZvciBteSBjbGVhbiBkYXRhIGFzIG9wcG9zZSB0byBkdG0gd2FzIHVzZWQgaW4gdGhlIGxlY3R1cmUKc21zX2R0bV90cmFpbiA8LSBzbXNfZHRtM1sxOjQxNjksIF0Kc21zX2R0bV90ZXN0ICA8LSBzbXNfZHRtM1s0MTcwOjU1NTksIF0KYGBgCgpgYGB7cn0KIyBhbHNvIHNhdmUgdGhlIGxhYmVscwpzbXNfdHJhaW5fbGFiZWxzIDwtIHNtc19yYXdbMTo0MTY5LCBdJHR5cGUgIyBsYWJlbCBkYXRhIGJ5IHRoZWlyIHR5cGVzOiBoYW0gb3Igc3BhbQpzbXNfdGVzdF9sYWJlbHMgIDwtIHNtc19yYXdbNDE3MDo1NTU5LCBdJHR5cGUKYGBgCgpgYGB7cn0KIyBjaGVjayB0aGF0IHRoZSBwcm9wb3J0aW9uIG9mIHNwYW0gaXMgc2ltaWxhciBpbiB0cmFpbmluZyB2cyB0ZXN0aW5nCnByb3AudGFibGUodGFibGUoc21zX3RyYWluX2xhYmVscykpCnByb3AudGFibGUodGFibGUoc21zX3Rlc3RfbGFiZWxzKSkKYGBgCmBgYHtyfQojIHdvcmQgY2xvdWQgdmlzdWFsaXphdGlvbgpsaWJyYXJ5KHdvcmRjbG91ZCkKd29yZGNsb3VkKHNtc19jb3JwdXNfY2xlYW4sIG1pbi5mcmVxID0gNTAsIHJhbmRvbS5vcmRlciA9IEZBTFNFKQojIHJhbmRvbSA9ZmFsc2UgbWVhbnMgdGhlIGhpZ2hlciBmcmVxdWVuY2Ugd29yZCB3aWxsIGFwcGVhciBjbG9zZXIgdG8gdGhlIGNlbnRlcgojIG1pbi5mcmU9NTAgbWVhbnMgdGhlIHdvcmQgaGFzIHRvIGFjY3VyIGluIHRoZSBjb3JwdXM9NTAvNTU1OT0xJS4KCiMgc3Vic2V0IHRoZSB0cmFpbmluZyBkYXRhIGludG8gc3BhbSBhbmQgaGFtIGdyb3VwcwpzcGFtIDwtIHN1YnNldChzbXNfcmF3LCB0eXBlID09ICJzcGFtIikKaGFtICA8LSBzdWJzZXQoc21zX3JhdywgdHlwZSA9PSAiaGFtIikKCndvcmRjbG91ZChzcGFtJHRleHQsIG1heC53b3JkcyA9IDQwLCBzY2FsZSA9IGMoMywgMC41KSkKd29yZGNsb3VkKGhhbSR0ZXh0LCBtYXgud29yZHMgPSA0MCwgc2NhbGUgPSBjKDMsIDAuNSkpCgpzbXNfZHRtX2ZyZXFfdHJhaW4gPC0gcmVtb3ZlU3BhcnNlVGVybXMoc21zX2R0bV90cmFpbiwgMC45OTkpCnNtc19kdG1fZnJlcV90cmFpbgpgYGAKYGBge3J9CiMgaW5kaWNhdG9yIGZlYXR1cmVzIGZvciBmcmVxdWVudCB3b3JkcwpzbXNfZnJlcV93b3JkcyA8LSBmaW5kRnJlcVRlcm1zKHNtc19kdG1fdHJhaW4sIDUpCnN0cihzbXNfZnJlcV93b3JkcykKIyA1IHJlcHJlc2VudHMgYXQgbGVhc3QgNSBhY2N1cmFuY2VzCiNmaW5kRnJlcVRlcm1zOiB0YWtlcyBhIERUTSBhbmQgcmV0dXJucyBhIGNoYXJhY3RlciB2ZWN0b3IgY29udGFpbmluZwojIHRoZSB3b3JkcyB0aGF0IGFwcGVhciBmb3IgYXQgbGVhc3QgdGhlIHNwZWNpZmllZCBudW1iZXIgb2YgdGltZXMKYGBgCgpgYGB7cn0KIyBjcmVhdGUgRFRNcyB3aXRoIG9ubHkgdGhlIGZyZXF1ZW50IHRlcm1zCnNtc19kdG1fZnJlcV90cmFpbiA8LSBzbXNfZHRtX3RyYWluWyAsIHNtc19mcmVxX3dvcmRzXSAgIyByZW1vdmUgY29sdW1zIHRoYXQgZG9lcyBub3QgYXBwZWFyIGF0IGxlYXN0IDUgdGltZXMKc21zX2R0bV9mcmVxX3Rlc3QgPC0gc21zX2R0bV90ZXN0WyAsIHNtc19mcmVxX3dvcmRzXSAKCmBgYAoKYGBge3J9CiMgY29udmVydCBjb3VudHMgdG8gYSBmYWN0b3IKY29udmVydF9jb3VudHMgPC0gZnVuY3Rpb24oeCkgewogIHggPC0gaWZlbHNlKHggPiAwLCAiWWVzIiwgIk5vIikKfQojVGhlIGlmZWxzZSh4ID4gMCwgIlllcyIsICJObyIpIHN0YXRlbWVudCB0cmFuc2Zvcm1zIHRoZSB2YWx1ZXMgaW4geCwgc28gdGhhdCBpZiB0aGUgdmFsdWUgaXMgZ3JlYXRlciB0aGFuIDAsIHRoZW4gaXQgd2lsbCBiZSByZXBsYWNlZCBieSAiWWVzIixvdGhlcndpc2UgaXQgd2lsbCBiZSByZXBsYWNlZCBieSBhICJObyIgc3RyaW5nLiBMYXN0bHksIHRoZSBuZXdseSB0cmFuc2Zvcm1lZCB4IHZlY3RvciBpcyByZXR1cm5lZC4KYGBgCgpgYGB7cn0KIyBhcHBseSgpIGNvbnZlcnRfY291bnRzKCkgdG8gY29sdW1ucyBvZiB0cmFpbi90ZXN0IGRhdGEKc21zX3RyYWluIDwtIGFwcGx5KHNtc19kdG1fZnJlcV90cmFpbiwgTUFSR0lOID0gMiwgY29udmVydF9jb3VudHMpCnNtc190ZXN0ICA8LSBhcHBseShzbXNfZHRtX2ZyZXFfdGVzdCwgTUFSR0lOID0gMiwgY29udmVydF9jb3VudHMpCiMgbWFyZ2luPSAyIGlzIHRvIGFwcGx5IHRvIGNvbHVtbiwgbWFyZ2luPTEgaXMgdG8gcm93LgpgYGAKClN0ZXAgMy0gVHJhaW5pbmcgYSBtb2RlbCBvbiB0aGUgZGF0YQpgYGB7cn0KbGlicmFyeShlMTA3MSkKc21zX2NsYXNzaWZpZXIgPC0gbmFpdmVCYXllcyhzbXNfdHJhaW4sIHNtc190cmFpbl9sYWJlbHMpCiMgd2hhdCdzIGluIHRoZSBjbGFzc2lmaWVyOiBjb25kaXRpb25hbCBwcm9iYWJpbGl0eSBmb3IgZWFjaApgYGAKClN0ZXAgNC1FdmFsdWF0aW5nIG1vZGVsIHBlcmZvcm1hbmNlCmBgYHtyfQpzbXNfdGVzdF9wcmVkIDwtIHByZWRpY3Qoc21zX2NsYXNzaWZpZXIsIHNtc190ZXN0KQoKc21zX3Rlc3RfcHJlZAoKbGlicmFyeShnbW9kZWxzKQpDcm9zc1RhYmxlKHNtc190ZXN0X3ByZWQsIHNtc190ZXN0X2xhYmVscywKICAgICAgICAgICBwcm9wLmNoaXNxID0gRkFMU0UsIHByb3AudCA9IEZBTFNFLCBwcm9wLnIgPSBGQUxTRSwKICAgICAgICAgICBkbm4gPSBjKCdwcmVkaWN0ZWQnLCAnYWN0dWFsJykpCgpgYGAKU3RlcCA1OiBJbXByb3ZpbmcgbW9kZWwgcGVyZm9ybWFuY2VzdGVwCmBgYHtyfQpzbXNfY2xhc3NpZmllcjIgPC0gbmFpdmVCYXllcyhzbXNfdHJhaW4sIHNtc190cmFpbl9sYWJlbHMsIGxhcGxhY2UgPSAxKQpzbXNfdGVzdF9wcmVkMiA8LSBwcmVkaWN0KHNtc19jbGFzc2lmaWVyMiwgc21zX3Rlc3QpCkNyb3NzVGFibGUoc21zX3Rlc3RfcHJlZDIsIHNtc190ZXN0X2xhYmVscywKICAgICAgICAgICBwcm9wLmNoaXNxID0gRkFMU0UsIHByb3AudCA9IEZBTFNFLCBwcm9wLnIgPSBGQUxTRSwKICAgICAgICAgICBkbm4gPSBjKCdwcmVkaWN0ZWQnLCAnYWN0dWFsJykpCiMjIGNoYW5nZSBscGxhY2UgPTEgbWFrZXMgdGhlIG1vZGVsIGEgbGlsIGJpdCBiZXRlciAoY3V6IEZOIGFuZCBGUCBkZWNyZWFzZSkKIyMgdHVuaW5nIHBhcmFtZXRlciBpcyB0byBhZGQgMS41IHRvIGVhY2ggdmFsdWUgaXMgdGhhdCB0byBtb3ZlIHRoZW0gYSBsaWwgYXdheSBmcm9tIDAKIyMgTmFpdmUgQmF5ZXMgaXMganVzdCBhcyBzbG93IGFzIGstTk4KYGBgCkFjY3VyYWN5IHdoZW4gbGFwbGFjZT0wOgpBY2N1cmFjeT0oMTIwMSsxNTMpLzEzOTA9IDk3JQoKQWNjdXJhY3kgd2hlbiBsYXBsYWNlPTE6CkFjY3VyYWN5PSgxMjAyKzE1NSkvMTM5MD0gOTglCgoK