Exploratory data analysis
In this section, we will understand the distribution of words and relationship between the words in the corpora, then we will be able to answer these questions.
- Some words are more frequent than others - what are the distributions of word frequencies?
- What are the frequencies of 2-grams and 3-grams in the dataset?
- How many unique words do you need in a frequency sorted dictionary to cover 50% of all word instances in the language? 90%?
- How do you evaluate how many of the words come from foreign languages?
- Can you think of a way to increase the coverage – identifying words that may not be in the corpora or using a smaller number of words in the dictionary to cover the same number of phrases?
Loading the data
The corpora is charged to calculate basic statistics in each type of source: twitter, news and blogs.
f <- file.path(getwd(), "Coursera-SwiftKey.zip")
# Reading the files
de_twitter <- read.table(unz(f,"final/de_DE/de_DE.twitter.txt"), header=F, sep = "\n",stringsAsFactors = F)
de_news <- read.table(unz(f,"final/de_DE/de_DE.news.txt"), header=F, sep = "\n",stringsAsFactors = F)
de_blogs <- read.table(unz(f,"final/de_DE/de_DE.blogs.txt"), header=F, sep = "\n",stringsAsFactors = F)
words_blogs <- stri_count_words(de_blogs$V1)
words_news <- stri_count_words(de_news$V1)
words_twitter <- stri_count_words(de_twitter$V1)
size_blogs <- file.info("final/de_DE/de_DE.blogs.txt")$size/1024^2
size_news <- file.info("final/de_DE/de_DE.news.txt")$size/1024^2
size_twitter <- file.info("final/de_DE/de_DE.twitter.txt")$size/1024^2
basic_stats <- data.frame(filename = c("de_blogs","de_news","de_twitter"),
file_size_MB = c(size_blogs, size_news, size_twitter),
lines = c(length(de_blogs$V1),length(de_news$V1),length(de_twitter$V1)),
num_words = c(sum(words_blogs),sum(words_news),sum(words_twitter)),
mean_num_words = c(mean(words_blogs),mean(words_news),mean(words_twitter)))
basic_stats
Now, we need to take a sample to perform some more complex calculations.
# Taking a sample.
set.seed(85)
de_sample = rbind(de_twitter$V1[sample(length(de_twitter$V1), 1000)],
de_news$V1[sample(length(de_news$V1), 1000)],
de_blogs$V1[sample(length(de_blogs$V1), 1000)])
remove(de_twitter,de_news,de_blogs)
With the sample a Corpus is created, then we will remove profanity (numbers, punctations and multiple whitespace characters). After, the sparse terms are removed, the maximal allowed sparsity is the 0.999. This helps to remove words from other languages or very uncommon ones.
# Creting Corpus.
de_corpus <- VCorpus(VectorSource(de_sample))
# Removing profanity.
de_corpus <- tm_map(de_corpus, function(x) iconv(x, from='UTF-8', to="latin1"))
de_corpus <- tm_map(de_corpus, removeNumbers)
de_corpus <- tm_map(de_corpus, removePunctuation)
de_corpus <- tm_map(de_corpus, stripWhitespace)
de_corpus <- tm_map(de_corpus, PlainTextDocument)
# Creating a document-term matrix.
de_tdm <- TermDocumentMatrix(de_corpus)
nTerms(de_tdm)
[1] 35317
de_tdm <- removeSparseTerms(de_tdm, 0.999)
nTerms(de_tdm)
[1] 4732
Analyzing frequent words
We need to see the frequent words in our corpora. They might appear in our 2-grams, 3-grams and 4-grams. I’m not removing stop words, as the intention of the SwiftKey is to predict the next word to be typed.
# Finding frequent terms
de_freq <- sort(rowSums(as.matrix(de_tdm)),decreasing = T)
de_wc = data.frame(term=names(de_freq),frequency=de_freq)
de_wc[, 'cum_freq'] <- cumsum(de_wc[, 2])
# Number of words with more than 50% of instances
words_50 <- sum(de_wc$cum_freq < tail(de_wc$cum_fre,n=1)*0.5)
words_50
[1] 82
# Number of words with more than 90% of instances
words_90 <- sum(de_wc$cum_freq < tail(de_wc$cum_fre,n=1)*0.9)
words_90
[1] 2177
# Hitogram of frequent terms
p <- ggplot(subset(de_wc, frequency>1000), aes(x=reorder(term, frequency),y=frequency))
p <- p + geom_bar(aes(fill = frequency),stat="identity") + coord_flip() +xlab('words')
p

# Wordcloud
wordcloud(names(de_freq),de_freq, min.freq=300, colors=brewer.pal(6,"Accent"))

Analyzing frequent n-grams
So far, we have explored the behaviour of individual words in the corpora. Time to see 2-grams and 3-grams.
# Creating tokenizers.
BigramTokenizer <- function(x) unlist(lapply(ngrams(words(x), 2), paste, collapse = " "), use.names = FALSE)
TrigramTokenizer <- function(x) unlist(lapply(ngrams(words(x), 3), paste, collapse = " "), use.names = FALSE)
# 2-grams
de_tdm_2g <- TermDocumentMatrix(de_corpus, control=list(tokenize=BigramTokenizer))
de_tdm_2g <- removeSparseTerms(de_tdm_2g, 0.999)
# Finding frequent terms
de_freq_2g <- sort(rowSums(as.matrix(de_tdm_2g)),decreasing = T)
findFreqTerms(de_tdm_2g,lowfreq=100)
[1] "an der" "auch die" "auf dem" "auf den" "auf der" "auf die" "aus dem"
[8] "bei der" "das ist" "dass die" "für den" "für die" "in den" "in der"
[15] "in die" "mehr als" "mit dem" "mit der" "mit einem" "nicht mehr" "sich die"
[22] "über die" "um die" "und der" "und die" "von der"
de_wc_g = data.frame(term=names(de_freq_2g),occurrences=de_freq_2g)
# Wordcloud
wordcloud(names(de_freq_2g),de_freq_2g, min.freq=75, colors=brewer.pal(6,"Accent"))

# 3-grams
de_tdm_3g <- TermDocumentMatrix(de_corpus, control=list(tokenize=TrigramTokenizer))
de_tdm_3g <- removeSparseTerms(de_tdm_3g, 0.999)
# Finding frequent terms
de_freq_3g <- sort(rowSums(as.matrix(de_tdm_3g)),decreasing = T)
findFreqTerms(de_tdm_3g,lowfreq=12)
[1] "auf jeden fall" "das ist ein" "den vergangenen jahren"
[4] "die zahl der" "im vergangenen jahr" "in den letzten"
[7] "in den nächsten" "in den vergangenen" "in diesem jahr"
[10] "nach wie vor" "sich in den" "sich in der"
de_wc_g = data.frame(term=names(de_freq_3g),occurrences=de_freq_3g)
# Wordcloud
wordcloud(names(de_freq_3g),de_freq_3g, min.freq=50, scale = c(2,.25) , max.words=10,colors=brewer.pal(3,"Accent"))

LS0tDQp0aXRsZTogIk1pbGVzdG9uZSBSZXBvcnQiDQphdXRob3I6IFNhbmRyYSBNZW5lc2VzDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpgYGB7ciB3YXJuaW5nPUZBTFNFLCBlcnJvcj1GQUxTRX0NCmxpYnJhcnkoJ2tuaXRyJykNCnNldHdkKCd+L0RhdGFfU2NpZW5jZS9SL1Rhc2tzL1N3aWZ0S2V5JykNCm9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRSkNCmBgYA0KDQoNCiMgUHJvamVjdCBTd2lmdGtleTogR2VybWFuIENvcnBvcmENCg0KVGhlIG1haW4gZ29hbCBvZiB0aGlzIHByb2plY3QgaXMgdG8gYnVpbGQgYSBwcmVkaWN0aXZlIHRleHQgbW9kZWwuIFRoYXQgbWVhbnMsIEkgd2lsbCB1c2Ugc29tZSBkYXRhIHNjaWVuY2UsIG9yIHRvIGJlIG1vcmUgc3BlY2lmaWMsIE5MUCAoTmF0dXJhbCBMYW5ndWFnZSBQcm9jZXNzaW5nKSB0byBwcmVkaWN0IHRoZSBuZXh0IHdvcmRzIGEgdXNlciBpbnRlbmRzIHRvIHR5cGUuIFRoaXMgcmVkdWNlcyB0aGUgdGltZSBhIHBlcnNvbiBuZWVkcyB0byB3cml0ZSBhIG1lc3NhZ2UuIFRvIHN0YXJ0IHdpdGggdGhpcyBwcm9qZWN0IHRoZSBkYXRhIGV4cGxvcmF0aW9uIHRoYXQgeW91IHdpbGwgc2VlIGluIHRoaXMgZG9jdW1lbnRzIGlzIHBlcmZvcm1lZC4NCg0KKlBhY2thZ2VzKg0KDQpgYGB7ciB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0NCmxpYnJhcnkoJ3RtJykNCmxpYnJhcnkoJ3N0cmluZ2knKQ0KbGlicmFyeSgnd29yZGNsb3VkJykNCmxpYnJhcnkoJ2dncGxvdDInKQ0KDQpgYGANCg0KLSB0bTogQSBmcmFtZXdvcmsgZm9yIHRleHQgbWluaW5nIGFwcGxpY2F0aW9ucyB3aXRoaW4gUi4NCi0gc3RyaW5naTogQWxsb3dzIGZvciBmYXN0LCBjb3JyZWN0LCBjb25zaXN0ZW50LCBwb3J0YWJsZSwgYXMgd2VsbCBhcyBjb252ZW5pZW50IGNoYXJhY3RlciBzdHJpbmcvdGV4dCBwcm9jZXNzaW5nIGluIGV2ZXJ5IGxvY2FsZSBhbmQgYW55IG5hdGl2ZSBlbmNvZGluZy4NCi0gd29yZGNsb3VkOiBQcmV0dHkgd29yZCBjbG91ZHMuDQotIGdncGxvdDI6IEEgc3lzdGVtIGZvciAnZGVjbGFyYXRpdmVseScgY3JlYXRpbmcgZ3JhcGhpY3MsIGJhc2VkIG9uICJUaGUgR3JhbW1hciBvZiBHcmFwaGljcyINCg0KDQojIEV4cGxvcmF0b3J5IGRhdGEgYW5hbHlzaXMNCg0KSW4gdGhpcyBzZWN0aW9uLCB3ZSB3aWxsIHVuZGVyc3RhbmQgdGhlIGRpc3RyaWJ1dGlvbiBvZiB3b3JkcyBhbmQgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIHdvcmRzIGluIHRoZSBjb3Jwb3JhLCB0aGVuIHdlIHdpbGwgYmUgYWJsZSB0byBhbnN3ZXIgdGhlc2UgcXVlc3Rpb25zLg0KDQoxLiBTb21lIHdvcmRzIGFyZSBtb3JlIGZyZXF1ZW50IHRoYW4gb3RoZXJzIC0gd2hhdCBhcmUgdGhlIGRpc3RyaWJ1dGlvbnMgb2Ygd29yZCBmcmVxdWVuY2llcz8NCjIuIFdoYXQgYXJlIHRoZSBmcmVxdWVuY2llcyBvZiAyLWdyYW1zIGFuZCAzLWdyYW1zIGluIHRoZSBkYXRhc2V0Pw0KMy4gSG93IG1hbnkgdW5pcXVlIHdvcmRzIGRvIHlvdSBuZWVkIGluIGEgZnJlcXVlbmN5IHNvcnRlZCBkaWN0aW9uYXJ5IHRvIGNvdmVyIDUwJSBvZiBhbGwgd29yZCBpbnN0YW5jZXMgaW4gdGhlIGxhbmd1YWdlPyA5MCU/DQo0LiBIb3cgZG8geW91IGV2YWx1YXRlIGhvdyBtYW55IG9mIHRoZSB3b3JkcyBjb21lIGZyb20gZm9yZWlnbiBsYW5ndWFnZXM/DQo1LiBDYW4geW91IHRoaW5rIG9mIGEgd2F5IHRvIGluY3JlYXNlIHRoZSBjb3ZlcmFnZSAtLSBpZGVudGlmeWluZyB3b3JkcyB0aGF0IG1heSBub3QgYmUgaW4gdGhlIGNvcnBvcmEgb3IgdXNpbmcgYSBzbWFsbGVyIG51bWJlciBvZiB3b3JkcyBpbiB0aGUgZGljdGlvbmFyeSB0byBjb3ZlciB0aGUgc2FtZSBudW1iZXIgb2YgcGhyYXNlcz8NCg0KIyMgTG9hZGluZyB0aGUgZGF0YQ0KDQpUaGUgY29ycG9yYSBpcyBjaGFyZ2VkIHRvIGNhbGN1bGF0ZSBiYXNpYyBzdGF0aXN0aWNzIGluIGVhY2ggdHlwZSBvZiBzb3VyY2U6IHR3aXR0ZXIsIG5ld3MgYW5kIGJsb2dzLg0KDQpgYGB7ciBjYWNoZT1UUlVFLCB3YXJuaW5nID0gRkFMU0UsIG1lc3NhZ2UgPSBGQUxTRX0NCmYgPC0gZmlsZS5wYXRoKGdldHdkKCksICJDb3Vyc2VyYS1Td2lmdEtleS56aXAiKQ0KDQojIFJlYWRpbmcgdGhlIGZpbGVzDQpkZV90d2l0dGVyIDwtIHJlYWQudGFibGUodW56KGYsImZpbmFsL2RlX0RFL2RlX0RFLnR3aXR0ZXIudHh0IiksIGhlYWRlcj1GLCBzZXAgPSAiXG4iLHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQ0KZGVfbmV3cyA8LSAgcmVhZC50YWJsZSh1bnooZiwiZmluYWwvZGVfREUvZGVfREUubmV3cy50eHQiKSwgaGVhZGVyPUYsIHNlcCA9ICJcbiIsc3RyaW5nc0FzRmFjdG9ycyA9IEYpDQpkZV9ibG9ncyA8LSByZWFkLnRhYmxlKHVueihmLCJmaW5hbC9kZV9ERS9kZV9ERS5ibG9ncy50eHQiKSwgaGVhZGVyPUYsIHNlcCA9ICJcbiIsc3RyaW5nc0FzRmFjdG9ycyA9IEYpDQoNCndvcmRzX2Jsb2dzIDwtIHN0cmlfY291bnRfd29yZHMoZGVfYmxvZ3MkVjEpDQp3b3Jkc19uZXdzIDwtIHN0cmlfY291bnRfd29yZHMoZGVfbmV3cyRWMSkNCndvcmRzX3R3aXR0ZXIgPC0gc3RyaV9jb3VudF93b3JkcyhkZV90d2l0dGVyJFYxKQ0Kc2l6ZV9ibG9ncyA8LSBmaWxlLmluZm8oImZpbmFsL2RlX0RFL2RlX0RFLmJsb2dzLnR4dCIpJHNpemUvMTAyNF4yDQpzaXplX25ld3MgPC0gZmlsZS5pbmZvKCJmaW5hbC9kZV9ERS9kZV9ERS5uZXdzLnR4dCIpJHNpemUvMTAyNF4yDQpzaXplX3R3aXR0ZXIgPC0gZmlsZS5pbmZvKCJmaW5hbC9kZV9ERS9kZV9ERS50d2l0dGVyLnR4dCIpJHNpemUvMTAyNF4yDQpiYXNpY19zdGF0cyA8LSBkYXRhLmZyYW1lKGZpbGVuYW1lID0gYygiZGVfYmxvZ3MiLCJkZV9uZXdzIiwiZGVfdHdpdHRlciIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbGVfc2l6ZV9NQiA9IGMoc2l6ZV9ibG9ncywgc2l6ZV9uZXdzLCBzaXplX3R3aXR0ZXIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpbmVzID0gYyhsZW5ndGgoZGVfYmxvZ3MkVjEpLGxlbmd0aChkZV9uZXdzJFYxKSxsZW5ndGgoZGVfdHdpdHRlciRWMSkpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIG51bV93b3JkcyA9IGMoc3VtKHdvcmRzX2Jsb2dzKSxzdW0od29yZHNfbmV3cyksc3VtKHdvcmRzX3R3aXR0ZXIpKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBtZWFuX251bV93b3JkcyA9IGMobWVhbih3b3Jkc19ibG9ncyksbWVhbih3b3Jkc19uZXdzKSxtZWFuKHdvcmRzX3R3aXR0ZXIpKSkNCmJhc2ljX3N0YXRzDQoNCmBgYA0KDQpOb3csIHdlIG5lZWQgdG8gdGFrZSBhIHNhbXBsZSB0byBwZXJmb3JtIHNvbWUgbW9yZSBjb21wbGV4IGNhbGN1bGF0aW9ucy4gDQoNCmBgYHtyfQ0KIyBUYWtpbmcgYSBzYW1wbGUuDQoNCnNldC5zZWVkKDg1KQ0KZGVfc2FtcGxlID0gcmJpbmQoZGVfdHdpdHRlciRWMVtzYW1wbGUobGVuZ3RoKGRlX3R3aXR0ZXIkVjEpLCAxMDAwKV0sDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRlX25ld3MkVjFbc2FtcGxlKGxlbmd0aChkZV9uZXdzJFYxKSwgMTAwMCldLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVfYmxvZ3MkVjFbc2FtcGxlKGxlbmd0aChkZV9ibG9ncyRWMSksIDEwMDApXSkNCnJlbW92ZShkZV90d2l0dGVyLGRlX25ld3MsZGVfYmxvZ3MpDQoNCmBgYA0KDQpXaXRoIHRoZSBzYW1wbGUgYSBDb3JwdXMgaXMgY3JlYXRlZCwgdGhlbiB3ZSB3aWxsIHJlbW92ZSBwcm9mYW5pdHkgKG51bWJlcnMsIHB1bmN0YXRpb25zIGFuZCBtdWx0aXBsZSB3aGl0ZXNwYWNlIGNoYXJhY3RlcnMpLiBBZnRlciwgdGhlIHNwYXJzZSB0ZXJtcyBhcmUgcmVtb3ZlZCwgdGhlIG1heGltYWwgYWxsb3dlZCBzcGFyc2l0eSBpcyB0aGUgMC45OTkuIFRoaXMgaGVscHMgdG8gcmVtb3ZlIHdvcmRzIGZyb20gb3RoZXIgbGFuZ3VhZ2VzIG9yIHZlcnkgdW5jb21tb24gb25lcy4NCg0KYGBge3J9DQoNCiMgQ3JlYXRpbmcgQ29ycHVzLg0KZGVfY29ycHVzIDwtIFZDb3JwdXMoVmVjdG9yU291cmNlKGRlX3NhbXBsZSkpDQoNCiMgUmVtb3ZpbmcgcHJvZmFuaXR5Lg0KDQpkZV9jb3JwdXMgPC0gdG1fbWFwKGRlX2NvcnB1cywgZnVuY3Rpb24oeCkgaWNvbnYoeCwgZnJvbT0nVVRGLTgnLCB0bz0ibGF0aW4xIikpDQpkZV9jb3JwdXMgPC0gdG1fbWFwKGRlX2NvcnB1cywgcmVtb3ZlTnVtYmVycykNCmRlX2NvcnB1cyA8LSB0bV9tYXAoZGVfY29ycHVzLCByZW1vdmVQdW5jdHVhdGlvbikNCmRlX2NvcnB1cyA8LSB0bV9tYXAoZGVfY29ycHVzLCBzdHJpcFdoaXRlc3BhY2UpDQpkZV9jb3JwdXMgPC0gdG1fbWFwKGRlX2NvcnB1cywgUGxhaW5UZXh0RG9jdW1lbnQpDQoNCiMgQ3JlYXRpbmcgYSBkb2N1bWVudC10ZXJtIG1hdHJpeC4NCg0KZGVfdGRtIDwtIFRlcm1Eb2N1bWVudE1hdHJpeChkZV9jb3JwdXMpDQpuVGVybXMoZGVfdGRtKQ0KZGVfdGRtIDwtIHJlbW92ZVNwYXJzZVRlcm1zKGRlX3RkbSwgMC45OTkpDQpuVGVybXMoZGVfdGRtKQ0KDQpgYGANCg0KDQojIyBBbmFseXppbmcgZnJlcXVlbnQgd29yZHMNCg0KV2UgbmVlZCB0byBzZWUgdGhlIGZyZXF1ZW50IHdvcmRzIGluIG91ciBjb3Jwb3JhLiBUaGV5IG1pZ2h0IGFwcGVhciBpbiBvdXIgMi1ncmFtcywgMy1ncmFtcyBhbmQgNC1ncmFtcy4gSSdtIG5vdCByZW1vdmluZyBzdG9wIHdvcmRzLCBhcyB0aGUgaW50ZW50aW9uIG9mIHRoZSBTd2lmdEtleSBpcyB0byBwcmVkaWN0IHRoZSBuZXh0IHdvcmQgdG8gYmUgdHlwZWQuDQoNCmBgYHtyfQ0KDQojIEZpbmRpbmcgZnJlcXVlbnQgdGVybXMNCmRlX2ZyZXEgPC0gc29ydChyb3dTdW1zKGFzLm1hdHJpeChkZV90ZG0pKSxkZWNyZWFzaW5nID0gVCkNCmRlX3djID0gZGF0YS5mcmFtZSh0ZXJtPW5hbWVzKGRlX2ZyZXEpLGZyZXF1ZW5jeT1kZV9mcmVxKQ0KZGVfd2NbLCAnY3VtX2ZyZXEnXSA8LSBjdW1zdW0oZGVfd2NbLCAyXSkNCg0KIyBOdW1iZXIgb2Ygd29yZHMgd2l0aCBtb3JlIHRoYW4gNTAlIG9mIGluc3RhbmNlcw0Kd29yZHNfNTAgPC0gc3VtKGRlX3djJGN1bV9mcmVxIDwgdGFpbChkZV93YyRjdW1fZnJlLG49MSkqMC41KQ0Kd29yZHNfNTANCg0KIyBOdW1iZXIgb2Ygd29yZHMgd2l0aCBtb3JlIHRoYW4gOTAlIG9mIGluc3RhbmNlcw0Kd29yZHNfOTAgPC0gc3VtKGRlX3djJGN1bV9mcmVxIDwgdGFpbChkZV93YyRjdW1fZnJlLG49MSkqMC45KQ0Kd29yZHNfOTANCg0KIyBIaXRvZ3JhbSBvZiBmcmVxdWVudCB0ZXJtcw0KcCA8LSBnZ3Bsb3Qoc3Vic2V0KGRlX3djLCBmcmVxdWVuY3k+MTAwMCksIGFlcyh4PXJlb3JkZXIodGVybSwgZnJlcXVlbmN5KSx5PWZyZXF1ZW5jeSkpDQpwIDwtIHAgKyBnZW9tX2JhcihhZXMoZmlsbCA9IGZyZXF1ZW5jeSksc3RhdD0iaWRlbnRpdHkiKSArIGNvb3JkX2ZsaXAoKSAreGxhYignd29yZHMnKQ0KcA0KDQojIFdvcmRjbG91ZA0Kd29yZGNsb3VkKG5hbWVzKGRlX2ZyZXEpLGRlX2ZyZXEsIG1pbi5mcmVxPTMwMCwgY29sb3JzPWJyZXdlci5wYWwoNiwiQWNjZW50IikpDQoNCmBgYA0KDQoNCiMjIEFuYWx5emluZyBmcmVxdWVudCBuLWdyYW1zDQoNClNvIGZhciwgd2UgaGF2ZSBleHBsb3JlZCB0aGUgYmVoYXZpb3VyIG9mIGluZGl2aWR1YWwgd29yZHMgaW4gdGhlIGNvcnBvcmEuIFRpbWUgdG8gc2VlIDItZ3JhbXMgYW5kIDMtZ3JhbXMuIA0KDQpgYGB7cn0NCiMgQ3JlYXRpbmcgdG9rZW5pemVycy4NCkJpZ3JhbVRva2VuaXplciA8LSBmdW5jdGlvbih4KSB1bmxpc3QobGFwcGx5KG5ncmFtcyh3b3Jkcyh4KSwgMiksIHBhc3RlLCBjb2xsYXBzZSA9ICIgIiksIHVzZS5uYW1lcyA9IEZBTFNFKQ0KVHJpZ3JhbVRva2VuaXplciA8LSBmdW5jdGlvbih4KSB1bmxpc3QobGFwcGx5KG5ncmFtcyh3b3Jkcyh4KSwgMyksIHBhc3RlLCBjb2xsYXBzZSA9ICIgIiksIHVzZS5uYW1lcyA9IEZBTFNFKQ0KDQojIDItZ3JhbXMNCmRlX3RkbV8yZyA8LSBUZXJtRG9jdW1lbnRNYXRyaXgoZGVfY29ycHVzLCAgY29udHJvbD1saXN0KHRva2VuaXplPUJpZ3JhbVRva2VuaXplcikpDQpkZV90ZG1fMmcgPC0gcmVtb3ZlU3BhcnNlVGVybXMoZGVfdGRtXzJnLCAwLjk5OSkNCg0KIyBGaW5kaW5nIGZyZXF1ZW50IHRlcm1zDQpkZV9mcmVxXzJnIDwtIHNvcnQocm93U3Vtcyhhcy5tYXRyaXgoZGVfdGRtXzJnKSksZGVjcmVhc2luZyA9IFQpDQpmaW5kRnJlcVRlcm1zKGRlX3RkbV8yZyxsb3dmcmVxPTEwMCkNCmRlX3djX2cgPSBkYXRhLmZyYW1lKHRlcm09bmFtZXMoZGVfZnJlcV8yZyksb2NjdXJyZW5jZXM9ZGVfZnJlcV8yZykNCg0KIyBXb3JkY2xvdWQNCndvcmRjbG91ZChuYW1lcyhkZV9mcmVxXzJnKSxkZV9mcmVxXzJnLCBtaW4uZnJlcT03NSwgY29sb3JzPWJyZXdlci5wYWwoNiwiQWNjZW50IikpDQoNCiMgMy1ncmFtcw0KZGVfdGRtXzNnIDwtIFRlcm1Eb2N1bWVudE1hdHJpeChkZV9jb3JwdXMsICBjb250cm9sPWxpc3QodG9rZW5pemU9VHJpZ3JhbVRva2VuaXplcikpDQpkZV90ZG1fM2cgPC0gcmVtb3ZlU3BhcnNlVGVybXMoZGVfdGRtXzNnLCAwLjk5OSkNCg0KIyBGaW5kaW5nIGZyZXF1ZW50IHRlcm1zDQpkZV9mcmVxXzNnIDwtIHNvcnQocm93U3Vtcyhhcy5tYXRyaXgoZGVfdGRtXzNnKSksZGVjcmVhc2luZyA9IFQpDQpmaW5kRnJlcVRlcm1zKGRlX3RkbV8zZyxsb3dmcmVxPTEyKQ0KZGVfd2NfZyA9IGRhdGEuZnJhbWUodGVybT1uYW1lcyhkZV9mcmVxXzNnKSxvY2N1cnJlbmNlcz1kZV9mcmVxXzNnKQ0KDQojIFdvcmRjbG91ZA0Kd29yZGNsb3VkKG5hbWVzKGRlX2ZyZXFfM2cpLGRlX2ZyZXFfM2csIG1pbi5mcmVxPTUwLCBzY2FsZSA9IGMoMiwuMjUpICwgbWF4LndvcmRzPTEwLGNvbG9ycz1icmV3ZXIucGFsKDMsIkFjY2VudCIpKQ0KDQpgYGANCg0KIyBQbGFuIGZvciBmdXJ0aGVyIHN0ZXBzDQoNCkdvYWxzIG9mIHRoZSBwcmVkaWN0aW9uIG1vZGVsOg0KDQogLSBFeHBsb3JlIGRpZmZlcmVudCBtZXRob2RzIHRvIHByZWRpY3QgdGV4dCBpbiBHZXJtYW4gbGFuZ3VhZ2UuDQogLSBWYWxpZGF0ZSBkaWZmZXJlbnQgbW9kZWxzIHVzaW5nIGEgbWV0cmljIG9yIGFjY3VyYWN5IG1lYXN1cmUuDQogLSBCdWlsZCBhIGZpbmFsIHRleHQgcHJlZGljdGlvbiBtb2RlbCB1c2luZyB0aGUgbW9zdCBmcmVxdWVudCBuLWdyYW1zLg0KDQoNClRvIGJ1aWxkIGEgcHJlZGljdGlvbiBtb2RlbCwgSSBoYXZlIHRvIGZpbmQgYW4gYW5zd2VyIHRvIHRoZXNlIHF1ZXN0aW9ucy4NCg0KIC0gSG93IGNhbiB5b3UgZWZmaWNpZW50bHkgc3RvcmUgYW4gbi1ncmFtIG1vZGVsPw0KIC0gSG93IGNhbiB5b3UgdXNlIHRoZSBrbm93bGVkZ2UgYWJvdXQgd29yZCBmcmVxdWVuY2llcyB0byBtYWtlIHlvdXIgbW9kZWwgc21hbGxlciBhbmQgbW9yZSBlZmZpY2llbnQ/DQogLSBIb3cgbWFueSBwYXJhbWV0ZXJzIGRvIHlvdSBuZWVkIChpLmUuIGhvdyBiaWcgaXMgbiBpbiB5b3VyIG4tZ3JhbSBtb2RlbCk/DQogLSBDYW4geW91IHRoaW5rIG9mIHNpbXBsZSB3YXlzIHRvICJzbW9vdGgiIHRoZSBwcm9iYWJpbGl0aWVzPw0KIC0gSG93IGRvIHlvdSBldmFsdWF0ZSB3aGV0aGVyIHlvdXIgbW9kZWwgaXMgYW55IGdvb2Q/DQogLSBIb3cgY2FuIHlvdSB1c2UgYmFja29mZiBtb2RlbHMgdG8gZXN0aW1hdGUgdGhlIHByb2JhYmlsaXR5IG9mIHVub2JzZXJ2ZWQgbi1ncmFtcz8NCg0KUGxhbnMgZm9yIGNyZWF0aW5nIGEgcHJlZGljdGlvbiBhbGdvcml0aG0gYW5kIFNoaW55IGFwcC4NCg0KIDEuIERlZmluZSBhIG1ldHJpYyB0byBldmFsdWF0ZSB0aGUgcGVyZm9ybWFuY2Ugb2YgdGhlIG1vZGVsLg0KIDIuIERlc2lnbiB0aGUgU2hpbnkgYXBwbGljYXRpb24uDQogMy4gQ2xlYW4gYW5kIHRyYW5zZm9ybSB0aGUgZGFhIGZvbGxvd2luZyB0aGUgc3RlcHMgZG9uZSB0aW4gdGhpcyBkYXRhIGV4cGxvcmF0aW9uLg0KIDQuIFNldCBhIHNpbXBsZSBiYXNlbGluZSB0byBidWlsZCBhIG1vZGVsIGJ5IGltcHJvdmluZyBvdmVyIHRoZSBiYXNlbGluZS4NCiA1LiBFeHBsb3JlIGRpZmZlcmVudCBwYXJhbWV0ZXJzIChsaWtlIG4gaW4gdGhlIG4tZ3JhbSBtb2RlbCkgdG8gYmFsYW5jZSBzaW1wbGlzaXR5IHZzLiBhY2N1cmFjeS4NCiA2LiBFeHBsb3JlIGNsdXN0ZXIgYW5hbHlzaXMgb3IgSy1tZWFucyB0byBmaW5kIGNsb3NlIHdvcmRzIGluICd1bm9ic2VydmVyZCcgd29yZHMgb3Igbi1ncmFtcy4NCiA3LiBCdWlsZCBhbmQgdmFsaWRhdGUgZGlmZmVyZW50IG1vZGVscyB0byBmaW5kIGFuIGFkZXF1YXRlIHNvbHV0aW9uLg0KIDguIEV2YWx1YXRlIHRoZSBmaW5hbCBtb2RlbCAod2hpY2ggc2hvdWxkIGJlIGJldHRlciB0aGFuIHRoZSBiYXNlbGluZSBkZWZpbmVkIGluIHN0ZXAgNCkgdXNpbmcgdGhlICBtZXRyaWMgaW4gc3RlcCAxLg0KIDkuIERldmVsb3AgYSBTaGlueSBhcHBsaWNhdGlvbiBhY2NvcmRpbmcgdG8gaXRzIGRlc2lnbi4NCg0K