The objective of this project is to classify SMS messages as spam or ham (not spam). A Naive Bayes classifier approach will be used. This example is taken from chapter 4 of Machine Learning with R, Second Addition"
An example of the conditional probability that will be computed is as follows P(Spam|Hospital) = P(Hospital|Spam)P(Spam)/P(Hospital), which is the formula for determining the probability that a message is spam given that it contains the word “Hospital” in the message.
1. Loading Data
Load the data into R.
setwd("/Users/sauce/desktop/R")
sms_raw <- read.csv("SMSSpamCollection", header = FALSE, sep = "\t", quote = "", col.names = c("type","text"), stringsAsFactors = FALSE)
The data frame has 5574 observations with 2 observations of either spam or ham.
str(sms_raw)
As seen above, the “type”" element is a character.Change it to a factor for the analysis.
sms_raw$type <- factor(sms_raw$type)
str(sms_raw$type)
table(sms_raw$type)
Now the “type” variable is a factor with 2 levels. Of the 5574 messages, 747 are spam, which is about 13.4%.
2. Text Mining
For Naive Bayes to run effectively, the test data needs to be transformed. This begins with using the “tm” package in R to create a volitile coprus that contains the “text” vector from our data frame.
install.packages("tm")
library(tm)
sms_corpus <- VCorpus(VectorSource(sms_raw$text))
print(sms_corpus)
Check out the first few messages in the new corpus, which is basically a list that can be manipulated with list operations.
inspect(sms_corpus[1:3])
Use “as.character” function to see what a message looks like.
as.character(sms_corpus[[3]])
In order to standardize the messages, the data set must be tranformed to all lower case letters. The words “Free”, “free”, and “FREE” should all be treated as the same word. Use the “tm_map”" funtion in R, and use the “content_transformer” function to transform the text.
sms_corpus_clean <- tm_map(sms_corpus, content_transformer(tolower))
Remome words that appear often but don’t contribute to our objective. These words include “to”, “and”, “but” and “or”.
sms_corpus_clean <- tm_map(sms_corpus_clean, removeWords, stopwords())
Remove punctuation as well using the “removePunctuation” function.
sms_corpus_clean <- tm_map(sms_corpus_clean, removePunctuation)
as.character((sms_corpus_clean[[3]]))
Perform “stemming” to the text data to strip the suffix from words like “jumping”, so the words “jumping” “jumps” and “jumped” are all transformed into “jump”. Stemming can be perfromed using the “tm” package with help from the “SnowballC” package.
install.packages("SnowballC")
library(SnowballC)
sms_corpus_clean <- tm_map(sms_corpus_clean, stemDocument)
And now the final step in text mining is to remove white space from the document.
sms_corpus_clean <- tm_map(sms_corpus_clean, stripWhitespace)
as.character(sms_corpus_clean[[3]])
Perform tokenization using the “DocumentTermMatrix” function. This creates a matrix in which the rows indicat documents (SMS messages in this case) and the columns indicate words. It should be noted that the “DocumentTermMaxtrix” function has the power to do all of the text mining above in one command.
sms_dtm <- DocumentTermMatrix(sms_corpus_clean)
3. Data Preparation
Split our data into training and testing sets, so that after Naive Bayes spam filter algorithm is built it can be applied to unseen data. Divide our data set into 75% training and 25% testing.
.75 * 5574
[1] 4180.5
.25 * 5574
[1] 1393.5
Because the dataset is random, the first 4180 messages can be used for the training set - there’s no need to randomize the data first.
sms_dtm_train <- sms_dtm[1:4180, ]
sms_dtm_test <- sms_dtm[4181:5559, ]
Save vectors labeling rows in the training and testing vectors
sms_train_labels <- sms_raw[1:4180, ]$type
sms_test_labels <- sms_raw[4181:5559,]$type
Make sure that the proportion of spam is similar in the training and testing data set.
prop.table(table(sms_train_labels))
sms_train_labels
ham spam
0.8648325 0.1351675
prop.table(table(sms_test_labels))
sms_test_labels
ham spam
0.8694706 0.1305294
Each have approx. 13% spam.
4. Visualization
Create a wordcloud of the frequency of the words in the dataset using the package “wordcloud”.
install.packages("wordcloud")
trying URL 'https://cran.rstudio.com/bin/macosx/mavericks/contrib/3.3/wordcloud_2.5.tgz'
Content type 'application/x-gzip' length 127386 bytes (124 KB)
==================================================
downloaded 124 KB
The downloaded binary packages are in
/var/folders/6b/yb20hcv16nz__qyd0fcg7b9h0000gn/T//Rtmp1ALMfi/downloaded_packages
install.packages("RColorBrewer")
trying URL 'https://cran.rstudio.com/bin/macosx/mavericks/contrib/3.3/RColorBrewer_1.1-2.tgz'
Content type 'application/x-gzip' length 24183 bytes (23 KB)
==================================================
downloaded 23 KB
The downloaded binary packages are in
/var/folders/6b/yb20hcv16nz__qyd0fcg7b9h0000gn/T//RtmpBiLPJT/downloaded_packages
library(wordcloud)
wordcloud(sms_corpus_clean, max.words = 50, random.order = FALSE)

Compare wordclouds between spam and ham.
spam <- subset(sms_raw, type == "spam")
ham <- subset(sms_raw, type == "ham")
wordcloud(spam$text, max.words = 50, scale = c(3, 0.5))

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

5. Preparation for Naive Bayes
Remove words from the matrix that appear less than 5 times.
sms_freq_words <- findFreqTerms(sms_dtm_train, 5)
str(sms_freq_words)
chr [1:1161] "abiola" "abl" "abt" "accept" ...
Limit our Document Term Matrix to only include words in the sms_freq_vector. We want all the rows, but we want to limit the column to these words in the frequency vector.
sms_dtm_freq_train <- sms_dtm_train[ , sms_freq_words]
sms_dtm_freq_test <- sms_dtm_test[ , sms_freq_words]
The naive bayes classifier works with categorical reatures, so we need to convert the matrix to “yes” and “no” categorical variables. To do this we’ll build a convert_counts function and apply it to our data.
convert_counts <- function(x) {
x <- ifelse(x > 0, "Yes", "No")
}
This replaces values greater than 0 with yes, and values not greater than 0 with no. Let’s apply it to our data.
sms_train <- apply(sms_dtm_freq_train, MARGIN = 2, convert_counts)
sms_test <- apply(sms_dtm_freq_test, MARGIN = 2, convert_counts)
The resulting matrixes will be character type, with cells indicating “yes” or “no” if the word represented by the column appears in the message represented by the row.
6. Train Model on the Data.
Use the e1071 package to impliment the Naive Bayes algorithm on the data, and predict whether a message is likely to be spam or ham.
install.packages("e1071")
trying URL 'https://cran.rstudio.com/bin/macosx/mavericks/contrib/3.3/e1071_1.6-8.tgz'
Content type 'application/x-gzip' length 742779 bytes (725 KB)
==================================================
downloaded 725 KB
The downloaded binary packages are in
/var/folders/6b/yb20hcv16nz__qyd0fcg7b9h0000gn/T//RtmpBiLPJT/downloaded_packages
library(e1071)
package ‘e1071’ was built under R version 3.3.2
sms_classifier <- naiveBayes(sms_train, sms_train_labels)
7. Predict and Evaluate the Model.
sms_test_pred <- predict(sms_classifier, sms_test)
Evaluate the predition with the actual data using a crosstable from the gmodels package.
library(gmodels)
CrossTable(sms_test_pred, sms_test_labels, prop.chisq = FALSE, prop.t = FALSE, dnn = c('predicted', 'actual'))
Cell Contents
|-------------------------|
| N |
| N / Row Total |
| N / Col Total |
|-------------------------|
Total Observations in Table: 1379
| actual
predicted | ham | spam | Row Total |
-------------|-----------|-----------|-----------|
ham | 1190 | 20 | 1210 |
| 0.983 | 0.017 | 0.877 |
| 0.992 | 0.111 | |
-------------|-----------|-----------|-----------|
spam | 9 | 160 | 169 |
| 0.053 | 0.947 | 0.123 |
| 0.008 | 0.889 | |
-------------|-----------|-----------|-----------|
Column Total | 1199 | 180 | 1379 |
| 0.869 | 0.131 | |
-------------|-----------|-----------|-----------|
29/1379
[1] 0.02102973
As shown in the table only 29/1379 messages were classified incorrectly. This means that the algorithm classifed the testing set as spam or ham with approx. 98% accuracy. That’s impressive. To improve the model, one might tamper with the Laplace value, colect more sms data, or try splitting the dataset randomly into training and testing.
I suspect the accuracy would increase as the dataset gets bigger. The more data there is to train the algorith, the more effective it would be in predicting Spam or Ham.
Show the 5 most frequent words in the sms data:
sack <- TermDocumentMatrix(sms_corpus_clean)
pack <- as.matrix(sack)
snack <- sort(rowSums(pack), decreasing = TRUE)
hack <- data.frame(word = names(snack), freq=snack)
head(hack, 5)
And the 5 most frequent words from each class:
wordcloud(spam$text, max.words = 5, scale = c(3, 0.5))

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

As shown in the word clouds, the most frequent words from the spam messages are “call”, “free”, “now”, “mobile”, and “text”. And in the Ham messages, the 5 most frequent words are “can”, “get”, “just”, “will”, and “now”.
LS0tCnRpdGxlOiAiTmFpdmUgQmF5ZXMgTWFjaGluZSBMZWFybmluZyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKCiMjIyBUaGUgb2JqZWN0aXZlIG9mIHRoaXMgcHJvamVjdCBpcyB0byBjbGFzc2lmeSBTTVMgbWVzc2FnZXMgYXMgc3BhbSBvciBoYW0gKG5vdCBzcGFtKS4gQSBOYWl2ZSBCYXllcyBjbGFzc2lmaWVyIGFwcHJvYWNoIHdpbGwgYmUgdXNlZC4gVGhpcyBleGFtcGxlIGlzIHRha2VuIGZyb20gY2hhcHRlciA0IG9mIE1hY2hpbmUgTGVhcm5pbmcgd2l0aCBSLCBTZWNvbmQgQWRkaXRpb24iCgoKIyMjIEFuIGV4YW1wbGUgb2YgdGhlIGNvbmRpdGlvbmFsIHByb2JhYmlsaXR5IHRoYXQgd2lsbCBiZSBjb21wdXRlZCBpcyBhcyBmb2xsb3dzIFAoU3BhbXxIb3NwaXRhbCkgPSBQKEhvc3BpdGFsfFNwYW0pUChTcGFtKS9QKEhvc3BpdGFsKSwgd2hpY2ggaXMgdGhlIGZvcm11bGEgZm9yIGRldGVybWluaW5nIHRoZSBwcm9iYWJpbGl0eSB0aGF0IGEgbWVzc2FnZSBpcyBzcGFtIGdpdmVuIHRoYXQgaXQgY29udGFpbnMgdGhlIHdvcmQgIkhvc3BpdGFsIiBpbiB0aGUgbWVzc2FnZS4KCiMgMS4gTG9hZGluZyBEYXRhCgoKIyMjIExvYWQgdGhlIGRhdGEgaW50byBSLgoKYGBge3J9CnNldHdkKCIvVXNlcnMvc2F1Y2UvZGVza3RvcC9SIikKc21zX3JhdyA8LSByZWFkLmNzdigiU01TU3BhbUNvbGxlY3Rpb24iLCBoZWFkZXIgPSBGQUxTRSwgc2VwID0gIlx0IiwgcXVvdGUgPSAiIiwgY29sLm5hbWVzID0gYygidHlwZSIsInRleHQiKSwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQpgYGAKCgojIyMgVGhlIGRhdGEgZnJhbWUgaGFzIDU1NzQgb2JzZXJ2YXRpb25zIHdpdGggMiBvYnNlcnZhdGlvbnMgb2YgZWl0aGVyIHNwYW0gb3IgaGFtLgoKYGBge3J9CnN0cihzbXNfcmF3KQpgYGAKCgojIyMgQXMgc2VlbiBhYm92ZSwgdGhlICJ0eXBlIiIgZWxlbWVudCBpcyBhIGNoYXJhY3Rlci5DaGFuZ2UgaXQgdG8gYSBmYWN0b3IgZm9yIHRoZSBhbmFseXNpcy4KCmBgYHtyfQpzbXNfcmF3JHR5cGUgPC0gZmFjdG9yKHNtc19yYXckdHlwZSkKc3RyKHNtc19yYXckdHlwZSkKdGFibGUoc21zX3JhdyR0eXBlKQpgYGAKCgojIyMgTm93IHRoZSAidHlwZSIgdmFyaWFibGUgaXMgYSBmYWN0b3Igd2l0aCAyIGxldmVscy4gT2YgdGhlIDU1NzQgbWVzc2FnZXMsIDc0NyBhcmUgc3BhbSwgd2hpY2ggaXMgYWJvdXQgMTMuNCUuCgojIDIuIFRleHQgTWluaW5nCgojIyMgRm9yIE5haXZlIEJheWVzIHRvIHJ1biBlZmZlY3RpdmVseSwgdGhlIHRlc3QgZGF0YSBuZWVkcyB0byBiZSB0cmFuc2Zvcm1lZC4gVGhpcyBiZWdpbnMgd2l0aCB1c2luZyB0aGUgInRtIiBwYWNrYWdlIGluIFIgdG8gY3JlYXRlIGEgdm9saXRpbGUgY29wcnVzIHRoYXQgY29udGFpbnMgdGhlICJ0ZXh0IiB2ZWN0b3IgZnJvbSBvdXIgZGF0YSBmcmFtZS4KCmBgYHtyfQppbnN0YWxsLnBhY2thZ2VzKCJ0bSIpCmxpYnJhcnkodG0pCmBgYAoKYGBge3J9CnNtc19jb3JwdXMgPC0gVkNvcnB1cyhWZWN0b3JTb3VyY2Uoc21zX3JhdyR0ZXh0KSkKcHJpbnQoc21zX2NvcnB1cykKYGBgCgojIyMgQ2hlY2sgb3V0IHRoZSBmaXJzdCBmZXcgbWVzc2FnZXMgaW4gdGhlIG5ldyBjb3JwdXMsIHdoaWNoIGlzIGJhc2ljYWxseSBhIGxpc3QgdGhhdCBjYW4gYmUgbWFuaXB1bGF0ZWQgd2l0aCBsaXN0IG9wZXJhdGlvbnMuCgpgYGB7cn0KaW5zcGVjdChzbXNfY29ycHVzWzE6M10pCmBgYAoKIyMjIFVzZSAiYXMuY2hhcmFjdGVyIiBmdW5jdGlvbiB0byBzZWUgd2hhdCBhIG1lc3NhZ2UgbG9va3MgbGlrZS4KCmBgYHtyfQphcy5jaGFyYWN0ZXIoc21zX2NvcnB1c1tbM11dKQpgYGAKCiMjIyBJbiBvcmRlciB0byBzdGFuZGFyZGl6ZSB0aGUgbWVzc2FnZXMsIHRoZSBkYXRhIHNldCBtdXN0IGJlIHRyYW5mb3JtZWQgdG8gYWxsIGxvd2VyIGNhc2UgbGV0dGVycy4gVGhlIHdvcmRzICJGcmVlIiwgImZyZWUiLCBhbmQgIkZSRUUiIHNob3VsZCBhbGwgYmUgdHJlYXRlZCBhcyB0aGUgc2FtZSB3b3JkLiBVc2UgdGhlICJ0bV9tYXAiIiBmdW50aW9uIGluIFIsIGFuZCB1c2UgdGhlICJjb250ZW50X3RyYW5zZm9ybWVyIiBmdW5jdGlvbiB0byB0cmFuc2Zvcm0gdGhlIHRleHQuCgoKYGBge3J9CnNtc19jb3JwdXNfY2xlYW4gPC0gdG1fbWFwKHNtc19jb3JwdXMsIGNvbnRlbnRfdHJhbnNmb3JtZXIodG9sb3dlcikpCmBgYAoKCiMjIyBMb29rIGF0IHRoaXJkIG1lc3NhZ2UgYWdhaW4gdG8gc2VlIGlmIG91ciBkYXRhIHdhcyB0cmFuc2Zvcm1lZC4KCmBgYHtyfQphcy5jaGFyYWN0ZXIoc21zX2NvcnB1c1tbM11dKQoKYXMuY2hhcmFjdGVyKChzbXNfY29ycHVzX2NsZWFuW1szXV0pKQpgYGAKCiMjIyBUaGUgbWVzc2FnZSB3YXMgaW5kZWVkIHRyYW5mb3JtZWQgdG8gYWxsIGxvd2VyY2FzZSBsZXR0ZXJzLiBOb3cgdG8gcmVtb3ZlIHRoZSBudW1iZXJzIHVzaW5nIHRoZSAicmVtb3ZlTnVtYmVycyIgZnVuY3Rpb24uCgoKYGBge3J9CnNtc19jb3JwdXNfY2xlYW4gPC0gdG1fbWFwKHNtc19jb3JwdXNfY2xlYW4sIHJlbW92ZU51bWJlcnMpCmBgYAoKCiMjIyBSZW1vbWUgd29yZHMgdGhhdCBhcHBlYXIgb2Z0ZW4gYnV0IGRvbid0IGNvbnRyaWJ1dGUgdG8gb3VyIG9iamVjdGl2ZS4gVGhlc2Ugd29yZHMgaW5jbHVkZSAidG8iLCAiYW5kIiwgImJ1dCIgYW5kICJvciIuCgoKYGBge3J9CnNtc19jb3JwdXNfY2xlYW4gPC0gdG1fbWFwKHNtc19jb3JwdXNfY2xlYW4sIHJlbW92ZVdvcmRzLCBzdG9wd29yZHMoKSkKYGBgCgojIyMgUmVtb3ZlIHB1bmN0dWF0aW9uIGFzIHdlbGwgdXNpbmcgdGhlICJyZW1vdmVQdW5jdHVhdGlvbiIgZnVuY3Rpb24uCgpgYGB7cn0Kc21zX2NvcnB1c19jbGVhbiA8LSB0bV9tYXAoc21zX2NvcnB1c19jbGVhbiwgcmVtb3ZlUHVuY3R1YXRpb24pCgphcy5jaGFyYWN0ZXIoKHNtc19jb3JwdXNfY2xlYW5bWzNdXSkpCmBgYAoKIyMjIFBlcmZvcm0gInN0ZW1taW5nIiB0byB0aGUgdGV4dCBkYXRhIHRvIHN0cmlwIHRoZSBzdWZmaXggZnJvbSB3b3JkcyBsaWtlICJqdW1waW5nIiwgc28gdGhlIHdvcmRzICJqdW1waW5nIiAianVtcHMiIGFuZCAianVtcGVkIiBhcmUgYWxsIHRyYW5zZm9ybWVkIGludG8gImp1bXAiLiBTdGVtbWluZyBjYW4gYmUgcGVyZnJvbWVkIHVzaW5nIHRoZSAidG0iIHBhY2thZ2Ugd2l0aCBoZWxwIGZyb20gdGhlICJTbm93YmFsbEMiIHBhY2thZ2UuCgpgYGB7cn0KaW5zdGFsbC5wYWNrYWdlcygiU25vd2JhbGxDIikKYGBgCgpgYGB7cn0KbGlicmFyeShTbm93YmFsbEMpCgpzbXNfY29ycHVzX2NsZWFuIDwtIHRtX21hcChzbXNfY29ycHVzX2NsZWFuLCBzdGVtRG9jdW1lbnQpCmBgYAoKQW5kIG5vdyB0aGUgZmluYWwgc3RlcCBpbiB0ZXh0IG1pbmluZyBpcyB0byByZW1vdmUgd2hpdGUgc3BhY2UgZnJvbSB0aGUgZG9jdW1lbnQuIAoKYGBge3J9CnNtc19jb3JwdXNfY2xlYW4gPC0gdG1fbWFwKHNtc19jb3JwdXNfY2xlYW4sIHN0cmlwV2hpdGVzcGFjZSkKYGBgCgpgYGB7cn0KYXMuY2hhcmFjdGVyKHNtc19jb3JwdXNfY2xlYW5bWzNdXSkKYGBgCgoKIyMjIFBlcmZvcm0gdG9rZW5pemF0aW9uIHVzaW5nIHRoZSAiRG9jdW1lbnRUZXJtTWF0cml4IiBmdW5jdGlvbi4gVGhpcyBjcmVhdGVzIGEgbWF0cml4ICBpbiB3aGljaCB0aGUgcm93cyBpbmRpY2F0IGRvY3VtZW50cyAoU01TIG1lc3NhZ2VzIGluIHRoaXMgY2FzZSkgYW5kIHRoZSBjb2x1bW5zIGluZGljYXRlIHdvcmRzLiBJdCBzaG91bGQgYmUgbm90ZWQgdGhhdCB0aGUgIkRvY3VtZW50VGVybU1heHRyaXgiIGZ1bmN0aW9uIGhhcyB0aGUgcG93ZXIgdG8gZG8gYWxsIG9mIHRoZSB0ZXh0IG1pbmluZyBhYm92ZSBpbiBvbmUgY29tbWFuZC4KCgpgYGB7cn0Kc21zX2R0bSA8LSBEb2N1bWVudFRlcm1NYXRyaXgoc21zX2NvcnB1c19jbGVhbikKYGBgCgoKIyAzLiBEYXRhIFByZXBhcmF0aW9uCgojIyMgU3BsaXQgb3VyIGRhdGEgaW50byB0cmFpbmluZyBhbmQgdGVzdGluZyBzZXRzLCBzbyB0aGF0IGFmdGVyIE5haXZlIEJheWVzIHNwYW0gZmlsdGVyIGFsZ29yaXRobSBpcyBidWlsdCBpdCBjYW4gYmUgYXBwbGllZCB0byB1bnNlZW4gZGF0YS4gRGl2aWRlIG91ciBkYXRhIHNldCBpbnRvIDc1JSB0cmFpbmluZyBhbmQgMjUlIHRlc3RpbmcuCgpgYGB7cn0KLjc1ICogNTU3NAouMjUgKiA1NTc0CmBgYAoKIyMjIEJlY2F1c2UgdGhlIGRhdGFzZXQgaXMgcmFuZG9tLCB0aGUgZmlyc3QgNDE4MCBtZXNzYWdlcyBjYW4gYmUgdXNlZCBmb3IgdGhlIHRyYWluaW5nIHNldCAtIHRoZXJlJ3Mgbm8gbmVlZCB0byByYW5kb21pemUgdGhlIGRhdGEgZmlyc3QuCgpgYGB7cn0Kc21zX2R0bV90cmFpbiA8LSBzbXNfZHRtWzE6NDE4MCwgXQpzbXNfZHRtX3Rlc3QgPC0gc21zX2R0bVs0MTgxOjU1NTksIF0KYGBgCgojIyMgU2F2ZSB2ZWN0b3JzIGxhYmVsaW5nIHJvd3MgaW4gdGhlIHRyYWluaW5nIGFuZCB0ZXN0aW5nIHZlY3RvcnMKCmBgYHtyfQpzbXNfdHJhaW5fbGFiZWxzIDwtIHNtc19yYXdbMTo0MTgwLCBdJHR5cGUKc21zX3Rlc3RfbGFiZWxzIDwtIHNtc19yYXdbNDE4MTo1NTU5LF0kdHlwZQpgYGAKCiMjIyBNYWtlIHN1cmUgdGhhdCB0aGUgcHJvcG9ydGlvbiBvZiBzcGFtIGlzIHNpbWlsYXIgaW4gdGhlIHRyYWluaW5nIGFuZCB0ZXN0aW5nIGRhdGEgc2V0LgoKYGBge3J9CnByb3AudGFibGUodGFibGUoc21zX3RyYWluX2xhYmVscykpCgpwcm9wLnRhYmxlKHRhYmxlKHNtc190ZXN0X2xhYmVscykpCmBgYAoKIyMjIEVhY2ggaGF2ZSBhcHByb3guIDEzJSBzcGFtLgoKIyA0LiBWaXN1YWxpemF0aW9uCgojIyMgQ3JlYXRlIGEgd29yZGNsb3VkIG9mIHRoZSBmcmVxdWVuY3kgb2YgdGhlIHdvcmRzIGluIHRoZSBkYXRhc2V0IHVzaW5nIHRoZSBwYWNrYWdlICJ3b3JkY2xvdWQiLgoKYGBge3J9Cmluc3RhbGwucGFja2FnZXMoIndvcmRjbG91ZCIpCmBgYApgYGB7cn0KaW5zdGFsbC5wYWNrYWdlcygiUkNvbG9yQnJld2VyIikKYGBgCgoKYGBge3J9CmxpYnJhcnkod29yZGNsb3VkKQp3b3JkY2xvdWQoc21zX2NvcnB1c19jbGVhbiwgbWF4LndvcmRzID0gNTAsIHJhbmRvbS5vcmRlciA9IEZBTFNFKQpgYGAKCgojIyMgQ29tcGFyZSB3b3JkY2xvdWRzIGJldHdlZW4gc3BhbSBhbmQgaGFtLgoKYGBge3J9CnNwYW0gPC0gc3Vic2V0KHNtc19yYXcsIHR5cGUgPT0gInNwYW0iKQpoYW0gPC0gc3Vic2V0KHNtc19yYXcsIHR5cGUgPT0gImhhbSIpCgp3b3JkY2xvdWQoc3BhbSR0ZXh0LCBtYXgud29yZHMgPSA1MCwgc2NhbGUgPSBjKDMsIDAuNSkpCndvcmRjbG91ZChoYW0kdGV4dCwgbWF4LndvcmRzID0gNTAsIHNjYWxlID0gYygzLCAwLjUpKQpgYGAKCgojIDUuIFByZXBhcmF0aW9uIGZvciBOYWl2ZSBCYXllcwoKIyMjIFJlbW92ZSB3b3JkcyBmcm9tIHRoZSBtYXRyaXggdGhhdCBhcHBlYXIgbGVzcyB0aGFuIDUgdGltZXMuCgpgYGB7cn0Kc21zX2ZyZXFfd29yZHMgPC0gZmluZEZyZXFUZXJtcyhzbXNfZHRtX3RyYWluLCA1KQpzdHIoc21zX2ZyZXFfd29yZHMpCmBgYAoKCiMjIyBMaW1pdCBvdXIgRG9jdW1lbnQgVGVybSBNYXRyaXggdG8gb25seSBpbmNsdWRlIHdvcmRzIGluIHRoZSBzbXNfZnJlcV92ZWN0b3IuIFdlIHdhbnQgYWxsIHRoZSByb3dzLCBidXQgd2Ugd2FudCB0byBsaW1pdCB0aGUgY29sdW1uIHRvIHRoZXNlIHdvcmRzIGluIHRoZSBmcmVxdWVuY3kgdmVjdG9yLgoKYGBge3J9CnNtc19kdG1fZnJlcV90cmFpbiA8LSBzbXNfZHRtX3RyYWluWyAsIHNtc19mcmVxX3dvcmRzXQpzbXNfZHRtX2ZyZXFfdGVzdCA8LSBzbXNfZHRtX3Rlc3RbICwgc21zX2ZyZXFfd29yZHNdCmBgYAoKCiMjIyBUaGUgbmFpdmUgYmF5ZXMgY2xhc3NpZmllciB3b3JrcyB3aXRoIGNhdGVnb3JpY2FsIHJlYXR1cmVzLCBzbyB3ZSBuZWVkIHRvIGNvbnZlcnQgdGhlIG1hdHJpeCB0byAieWVzIiBhbmQgIm5vIiBjYXRlZ29yaWNhbCB2YXJpYWJsZXMuIFRvIGRvIHRoaXMgd2UnbGwgYnVpbGQgYSBjb252ZXJ0X2NvdW50cyBmdW5jdGlvbiBhbmQgYXBwbHkgaXQgdG8gb3VyIGRhdGEuCgoKYGBge3J9CmNvbnZlcnRfY291bnRzIDwtIGZ1bmN0aW9uKHgpIHsKICB4IDwtIGlmZWxzZSh4ID4gMCwgIlllcyIsICJObyIpCn0KYGBgCgoKIyMjIFRoaXMgcmVwbGFjZXMgdmFsdWVzIGdyZWF0ZXIgdGhhbiAwIHdpdGggeWVzLCBhbmQgdmFsdWVzIG5vdCBncmVhdGVyIHRoYW4gMCB3aXRoIG5vLiBMZXQncyBhcHBseSBpdCB0byBvdXIgZGF0YS4KCmBgYHtyfQpzbXNfdHJhaW4gPC0gYXBwbHkoc21zX2R0bV9mcmVxX3RyYWluLCBNQVJHSU4gPSAyLCBjb252ZXJ0X2NvdW50cykKCnNtc190ZXN0IDwtIGFwcGx5KHNtc19kdG1fZnJlcV90ZXN0LCBNQVJHSU4gPSAyLCBjb252ZXJ0X2NvdW50cykKYGBgCgoKIyMjIFRoZSByZXN1bHRpbmcgbWF0cml4ZXMgd2lsbCBiZSBjaGFyYWN0ZXIgdHlwZSwgd2l0aCBjZWxscyBpbmRpY2F0aW5nICJ5ZXMiIG9yICJubyIgaWYgdGhlIHdvcmQgcmVwcmVzZW50ZWQgYnkgdGhlIGNvbHVtbiBhcHBlYXJzIGluIHRoZSBtZXNzYWdlIHJlcHJlc2VudGVkIGJ5IHRoZSByb3cuCgoKCiMgNi4gVHJhaW4gTW9kZWwgb24gdGhlIERhdGEuCgoKIyMjIFVzZSB0aGUgZTEwNzEgcGFja2FnZSB0byBpbXBsaW1lbnQgdGhlIE5haXZlIEJheWVzIGFsZ29yaXRobSBvbiB0aGUgZGF0YSwgYW5kIHByZWRpY3Qgd2hldGhlciBhIG1lc3NhZ2UgaXMgbGlrZWx5IHRvIGJlIHNwYW0gb3IgaGFtLgoKYGBge3J9Cmluc3RhbGwucGFja2FnZXMoImUxMDcxIikKbGlicmFyeShlMTA3MSkKYGBgCgoKCmBgYHtyfQpzbXNfY2xhc3NpZmllciA8LSBuYWl2ZUJheWVzKHNtc190cmFpbiwgc21zX3RyYWluX2xhYmVscykKCmBgYAoKIyA3LiBQcmVkaWN0IGFuZCBFdmFsdWF0ZSB0aGUgTW9kZWwuCgpgYGB7cn0Kc21zX3Rlc3RfcHJlZCA8LSBwcmVkaWN0KHNtc19jbGFzc2lmaWVyLCBzbXNfdGVzdCkKYGBgCgoKIyMjIEV2YWx1YXRlIHRoZSBwcmVkaXRpb24gd2l0aCB0aGUgYWN0dWFsIGRhdGEgdXNpbmcgYSBjcm9zc3RhYmxlIGZyb20gdGhlIGdtb2RlbHMgcGFja2FnZS4KCmBgYHtyfQpsaWJyYXJ5KGdtb2RlbHMpCkNyb3NzVGFibGUoc21zX3Rlc3RfcHJlZCwgc21zX3Rlc3RfbGFiZWxzLCBwcm9wLmNoaXNxID0gRkFMU0UsIHByb3AudCA9IEZBTFNFLCBkbm4gPSBjKCdwcmVkaWN0ZWQnLCAnYWN0dWFsJykpCmBgYAoKCgpgYGB7cn0KMjkvMTM3OQpgYGAKCiMjIyBBcyBzaG93biBpbiB0aGUgdGFibGUgb25seSAyOS8xMzc5IG1lc3NhZ2VzIHdlcmUgY2xhc3NpZmllZCBpbmNvcnJlY3RseS4gVGhpcyBtZWFucyB0aGF0IHRoZSBhbGdvcml0aG0gY2xhc3NpZmVkIHRoZSB0ZXN0aW5nIHNldCBhcyBzcGFtIG9yIGhhbSB3aXRoIGFwcHJveC4gOTglIGFjY3VyYWN5LiBUaGF0J3MgaW1wcmVzc2l2ZS4gVG8gaW1wcm92ZSB0aGUgbW9kZWwsIG9uZSBtaWdodCB0YW1wZXIgd2l0aCB0aGUgTGFwbGFjZSB2YWx1ZSwgY29sZWN0IG1vcmUgc21zIGRhdGEsIG9yIHRyeSBzcGxpdHRpbmcgdGhlIGRhdGFzZXQgcmFuZG9tbHkgaW50byB0cmFpbmluZyBhbmQgdGVzdGluZy4KCiMjIyBJIHN1c3BlY3QgdGhlIGFjY3VyYWN5IHdvdWxkIGluY3JlYXNlIGFzIHRoZSBkYXRhc2V0IGdldHMgYmlnZ2VyLiBUaGUgbW9yZSBkYXRhIHRoZXJlIGlzIHRvIHRyYWluIHRoZSBhbGdvcml0aCwgdGhlIG1vcmUgZWZmZWN0aXZlIGl0IHdvdWxkIGJlIGluIHByZWRpY3RpbmcgU3BhbSBvciBIYW0uCgoKCiMjIyBTaG93IHRoZSA1IG1vc3QgZnJlcXVlbnQgd29yZHMgaW4gdGhlIHNtcyBkYXRhOgoKYGBge3J9CnNhY2sgPC0gVGVybURvY3VtZW50TWF0cml4KHNtc19jb3JwdXNfY2xlYW4pCnBhY2sgPC0gYXMubWF0cml4KHNhY2spCnNuYWNrIDwtIHNvcnQocm93U3VtcyhwYWNrKSwgZGVjcmVhc2luZyA9IFRSVUUpCmhhY2sgPC0gZGF0YS5mcmFtZSh3b3JkID0gbmFtZXMoc25hY2spLCBmcmVxPXNuYWNrKQpoZWFkKGhhY2ssIDUpCmBgYAoKIyMjIEFuZCB0aGUgNSBtb3N0IGZyZXF1ZW50IHdvcmRzIGZyb20gZWFjaCBjbGFzczoKCmBgYHtyfQp3b3JkY2xvdWQoc3BhbSR0ZXh0LCBtYXgud29yZHMgPSA1LCBzY2FsZSA9IGMoMywgMC41KSkKd29yZGNsb3VkKGhhbSR0ZXh0LCBtYXgud29yZHMgPSA1LCBzY2FsZSA9IGMoMywgMC41KSkKYGBgCgojIyMgQXMgc2hvd24gaW4gdGhlIHdvcmQgY2xvdWRzLCB0aGUgbW9zdCBmcmVxdWVudCB3b3JkcyBmcm9tIHRoZSBzcGFtIG1lc3NhZ2VzIGFyZSAiY2FsbCIsICJmcmVlIiwgIm5vdyIsICJtb2JpbGUiLCBhbmQgInRleHQiLiBBbmQgaW4gdGhlIEhhbSBtZXNzYWdlcywgdGhlIDUgbW9zdCBmcmVxdWVudCB3b3JkcyBhcmUgImNhbiIsICJnZXQiLCAianVzdCIsICJ3aWxsIiwgYW5kICJub3ciLgoKCg==