Ch0 About data
** 資料是從文字分析平台下載而來,使用2/1~2/3華航罷工之PTT文章,關鍵字為:華航、罷工、機師,共有2200多篇文章**
Sys.setlocale(category = "LC_ALL", locale = "zh_TW.UTF-8") # 避免中文亂碼
OS reports request to set locale to "zh_TW.UTF-8" cannot be honored
[1] ""
Warning message:
In strsplit(code, "\n", fixed = TRUE) :
input string 1 is invalid in this locale
require(data.table)
require(readr)
require(dplyr)
require(stringr)
require(jsonlite)
require(tidyr)
require(tidytext)
require(wordcloud)
require(reshape2)
require(ggraph) #extends ggplot2 to construct network graph.
require(widyr) #calculate pairwise correlation.
require(igraph)
require(topicmodels)
2.1 ~ 2.3 直接透過平台的產出(文章+情緒)
csv <- fread("../data/social_media_artSen.csv",encoding = "UTF-8")
data <- csv %>%
mutate(sentiment = positive_emotion_grade - negative_emotion_grade) %>%
mutate(doc_id = row_number()) %>%
select(doc_id, sentiment)
data[1:150,] %>%
ggplot(aes(doc_id, sentiment)) +
geom_col(show.legend = FALSE)

2.4 最常出現的正面與負面情緒
csv <- fread("../data/social_media_artWordFreq.csv", encoding = "UTF-8")
中文情緒辭典
positive_file <- "../data/positive.txt"
positive_line <- read_lines(positive_file, n_max = 200000, progress = FALSE)
positive <- str_split(positive_line, ",")
negative_file <- "../data/negative.txt"
negative_line <- read_lines(negative_file, n_max = 200000, progress = FALSE)
negative <- str_split(negative_line, ",")
positive <- data.frame(word = positive, sentiments = "positive")
colnames(positive) = c("word","sentiment")
negative <- data.frame(word = negative, sentiemtns = "negative")
colnames(negative) = c("word","sentiment")
LIWC_ch <- rbind(positive, negative)
對應中文情緒辭典
data <- csv
word_counts <- data %>%
select(word,count) %>%
group_by(word) %>%
summarise(count = sum(count)) %>%
inner_join(LIWC_ch)
Joining, by = "word"
Column `word` joining character vector and factor, coercing into character vector
word_counts
word_counts %>%
group_by(sentiment) %>%
top_n(10,wt = count) %>%
ungroup() %>%
mutate(word = reorder(word, count)) %>%
ggplot(aes(word, count, fill = sentiment)) +
geom_col(show.legend = FALSE) +
facet_wrap(~sentiment, scales = "free_y") +
labs(y = "Contribution to sentiment",
x = NULL) +
theme(text=element_text(family="黑體-繁 中黑", size=14))+
coord_flip()
Warning message:
In strsplit(code, "\n", fixed = TRUE) :
input string 1 is invalid in this locale

par(family=("Heiti TC Light")) #避免字型發生錯誤
Warning message:
In strsplit(code, "\n", fixed = TRUE) :
input string 1 is invalid in this locale
word_counts %>%
with(wordcloud(word, count, max.words = 100))

par(family=("Heiti TC Light")) #避免字型發生錯誤
Warning message:
In strsplit(code, "\n", fixed = TRUE) :
input string 1 is invalid in this locale
word_counts %>%
acast(word ~ sentiment, value.var = "count", fill = 0) %>%
comparison.cloud(colors = c("gray20", "gray80"),
max.words = 100)

2.7 計算負面情緒佔該篇評論的比率 (原本是佔該章節)
all_word <- data %>%
mutate(id = group_indices(data,artUrl)) %>%
select(id, word, count) %>%
group_by(id) %>%
summarise(all = sum(count))
negative_word <- data %>%
mutate(id = group_indices(data,artUrl)) %>%
select(id, word, count) %>%
left_join(negative,) %>%
na.omit() %>%
group_by(id) %>%
summarise(negative_sum = sum(count))
Joining, by = "word"
Column `word` joining character vector and factor, coercing into character vector
merge <- left_join(all_word, negative_word)
Joining, by = "id"
merge %>%
na.omit() %>%
mutate(ratio = negative_sum / all)
Ch3
3.1 Calculate Term Frequency
zipf_ch <- data %>%
mutate(id = group_indices(data,artUrl)) %>%
group_by(id) %>%
mutate(total = sum(count)) %>%
select(id, word, count, total)
ggplot(zipf_ch, aes(count/total))+
geom_histogram(show.legend = FALSE) +
xlim(NA, 0.1)

3.2 Zipf’s Law
freq_by_rank <- data %>%
mutate(id = group_indices(data,artUrl)) %>%
group_by(word) %>%
summarise(count = sum(count)) %>%
arrange(desc(count)) %>%
mutate(total = sum(count)) %>%
mutate(rank = row_number()) %>%
mutate("term frequency" = count / total)
freq_by_rank %>%
ggplot(aes(rank, `term frequency`)) +
geom_line(size = 1.1, alpha = 0.8, show.legend = FALSE) +
scale_x_log10() +
scale_y_log10()

儘管我們用中文的資料集,跑出來的結果也跟教科書的很接近 => 符合Zipf’s Law 
rank_subset <- freq_by_rank %>%
filter(rank < 500,
rank > 10)
lm(log10(`term frequency`) ~ log10(rank), data = rank_subset)
Call:
lm(formula = log10(`term frequency`) ~ log10(rank), data = rank_subset)
Coefficients:
(Intercept) log10(rank)
-1.4822 -0.7466
係數不太接近-1 => 可能是因為資料量不夠大(?)
3.3 The bind_tf_idf function
data_tfidf <- data %>%
bind_tf_idf(word, artUrl, count)
data_tfidf %>%
group_by(artUrl) %>%
mutate(total = sum(count)) %>%
select(-total) %>%
arrange(desc(tf_idf)) %>%
ungroup()%>%
select(artTitle, word, tf_idf)
文章的重要詞彙其實蠻合理的(但是artTitle要考慮一下)
隨機取一篇評論來看看他擁有高tf-idf的字
data_tfidf %>%
filter(artUrl == "https://www.ptt.cc/bbs/Gossiping/M.1549710934.A.821.html") %>%
top_n(10,wt = tf_idf) %>%
ggplot(aes(word, tf_idf), ) +
ggtitle("[問卦]華航有可能跟日航一樣破產嗎?")+
geom_col(show.legend = FALSE) +
labs(x = NULL, y = "tf-idf") +
theme(text=element_text(family="黑體-繁 中黑", size=14))+
coord_flip()
Warning message:
In strsplit(code, "\n", fixed = TRUE) :
input string 1 is invalid in this locale

3.4是分析不同書本,省略不做
CH4
CH5
#install.packages('tmcn')
library(dplyr)
library(data.table)
library(ggplot2)
library(tmcn)
library(tm)
library(tidytext)
social_media=fread('../data/social_media_artWord.csv',encoding='UTF-8',stringsAsFactors = T)
social_media_artWordFreq=fread('../data/social_media_artWordFreq.csv',encoding='UTF-8',stringsAsFactors = T)
5.1 Tidying a document-term matrix
dtm_data=social_media_artWordFreq %>%
cast_dtm(artTitle, word, count)
dfm_data=social_media_artWordFreq %>%
cast_dfm(.,artTitle,word,count)
5.1.1 Tidying DocumentTermMatrix objects
library(tm)
dfm_data= tidy(dfm_data)
dtm_data_tfidf=dfm_data %>% bind_tf_idf(term, document, count)
dtm_data_tfidf
library(ggplot2)
dtm_data_tfidf %>%
mutate(term = reorder(term, count)) %>%
filter(count > 25) %>%
ggplot(aes(term, count,fill=term)) +
geom_col(show.legend = FALSE) +
xlab(NULL) +
coord_flip()

5.2 Casting tidy text data into a matrix
dtm_data=social_media_artWordFreq %>%
cast_dtm(artTitle, word, count)
dfm_data=social_media_artWordFreq %>%
cast_dfm(.,artTitle,word,count)
library(Matrix)
# cast into a Matrix object
m <- social_media_artWordFreq %>%
cast_sparse(artTitle,word,count)
class(m)
[1] "dgCMatrix"
attr(,"package")
[1] "Matrix"
dim(m)
[1] 1653 22437
CH6
#install.packages('topicmodels')
library(dplyr)
library(data.table)
library(ggplot2)
library(topicmodels)
social_media=fread('../data/social_media_artWord.csv',encoding='UTF-8',stringsAsFactors = T)
social_media_artWordFreq=fread('../data/social_media_artWordFreq.csv',encoding='UTF-8',stringsAsFactors = T)
6.1 Latent Dirichlet allocation
dtm_data=social_media_artWordFreq %>%
cast_dtm(artTitle, word, count)
dtm_data
<<DocumentTermMatrix (documents: 1653, terms: 22437)>>
Non-/sparse entries: 117527/36970834
Sparsity : 100%
Maximal term length: 73
Weighting : term frequency (tf)
social_lda <- LDA(dtm_data, k = 4, control = list(seed = 1234))
social_lda
A LDA_VEM topic model with 4 topics.
6.1.1 Word-topic probabilities
social_topics <- tidy(social_lda, matrix = "beta")
social_topics
top_terms <- social_topics %>%
group_by(topic) %>%
top_n(5, beta) %>%
ungroup() %>%
arrange(topic, -beta)
top_terms
library(ggplot2)
top_terms %>%
mutate(term = reorder(term, beta)) %>%
ggplot(aes(term, beta, fill = factor(topic))) +
geom_col(show.legend = FALSE) +
facet_wrap(~ topic, scales = "free") +
coord_flip()

library(tidyr)
beta_spread <- social_topics %>%
mutate(topic = paste0("topic", topic)) %>%
spread(topic, beta) %>%
filter(topic1 > .001 | topic2 > .001) %>%
mutate(log_ratio = log2(topic2 / topic1))
beta_spread
6.1.2 Document-topic probabilities
ap_documents <- tidy(social_lda, matrix = "gamma")
ap_documents[,2:3]
LS0tDQp0aXRsZTogIkNoaW5lc2VfdHV0b3JpYWxfY2gyMyINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KYXV0aG9yOiAiTWluZ0x1biBXdSwgWXVtaW5nIExpdSINCi0tLQ0KDQojIENoMCBBYm91dCBkYXRhDQoqKiDos4fmlpnmmK/lvp7mloflrZfliIbmnpDlubPlj7DkuIvovInogIzkvobvvIzkvb/nlKgyLzF+Mi8z6I+v6Iiq57235bel5LmLUFRU5paH56ug77yM6Zec6Y215a2X54K6OuiPr+iIquOAgee9t+W3peOAgeapn+W4q++8jOWFseaciTIyMDDlpJrnr4fmlofnq6AqKg0KYGBge3J9DQpTeXMuc2V0bG9jYWxlKGNhdGVnb3J5ID0gIkxDX0FMTCIsIGxvY2FsZSA9ICJ6aF9UVy5VVEYtOCIpICMg6YG/5YWN5Lit5paH5LqC56K8DQpyZXF1aXJlKGRhdGEudGFibGUpDQpyZXF1aXJlKHJlYWRyKQ0KcmVxdWlyZShkcGx5cikNCnJlcXVpcmUoc3RyaW5ncikNCnJlcXVpcmUoanNvbmxpdGUpDQpyZXF1aXJlKHRpZHlyKQ0KDQpyZXF1aXJlKHRpZHl0ZXh0KQ0KDQpyZXF1aXJlKHdvcmRjbG91ZCkNCnJlcXVpcmUocmVzaGFwZTIpDQoNCnJlcXVpcmUoZ2dyYXBoKSAjZXh0ZW5kcyBnZ3Bsb3QyIHRvIGNvbnN0cnVjdCBuZXR3b3JrIGdyYXBoLg0KcmVxdWlyZSh3aWR5cikgI2NhbGN1bGF0ZSBwYWlyd2lzZSBjb3JyZWxhdGlvbi4NCg0KcmVxdWlyZShpZ3JhcGgpDQpyZXF1aXJlKHRvcGljbW9kZWxzKQ0KYGBgDQoNCg0KDQojIDIuMSB+IDIuMyDnm7TmjqXpgI/pgY7lubPlj7DnmoTnlKLlh7oo5paH56ugK+aDhee3kikNCmBgYHtyfQ0KY3N2IDwtIGZyZWFkKCIuLi9kYXRhL3NvY2lhbF9tZWRpYV9hcnRTZW4uY3N2IixlbmNvZGluZyA9ICJVVEYtOCIpDQpgYGANCmBgYHtyfQ0KZGF0YSA8LSBjc3YgJT4lIA0KICBtdXRhdGUoc2VudGltZW50ID0gcG9zaXRpdmVfZW1vdGlvbl9ncmFkZSAtIG5lZ2F0aXZlX2Vtb3Rpb25fZ3JhZGUpICU+JQ0KICBtdXRhdGUoZG9jX2lkID0gcm93X251bWJlcigpKSAlPiUNCiAgc2VsZWN0KGRvY19pZCwgc2VudGltZW50KQ0KDQpkYXRhWzE6MTUwLF0gJT4lIA0KZ2dwbG90KGFlcyhkb2NfaWQsIHNlbnRpbWVudCkpICsNCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkNCmBgYA0KIyMjIDIuNCDmnIDluLjlh7rnj77nmoTmraPpnaLoiIfosqDpnaLmg4Xnt5INCmBgYHtyfQ0KY3N2IDwtIGZyZWFkKCIuLi9kYXRhL3NvY2lhbF9tZWRpYV9hcnRXb3JkRnJlcS5jc3YiLCBlbmNvZGluZyA9ICJVVEYtOCIpDQpgYGANCg0KIyMjIyMg5Lit5paH5oOF57eS6L6t5YW4DQpgYGB7cn0NCnBvc2l0aXZlX2ZpbGUgPC0gIi4uL2RhdGEvcG9zaXRpdmUudHh0Ig0KcG9zaXRpdmVfbGluZSA8LSByZWFkX2xpbmVzKHBvc2l0aXZlX2ZpbGUsIG5fbWF4ID0gMjAwMDAwLCBwcm9ncmVzcyA9IEZBTFNFKQ0KDQpwb3NpdGl2ZSA8LSBzdHJfc3BsaXQocG9zaXRpdmVfbGluZSwgIiwiKQ0KDQpuZWdhdGl2ZV9maWxlIDwtICIuLi9kYXRhL25lZ2F0aXZlLnR4dCINCm5lZ2F0aXZlX2xpbmUgPC0gcmVhZF9saW5lcyhuZWdhdGl2ZV9maWxlLCBuX21heCA9IDIwMDAwMCwgcHJvZ3Jlc3MgPSBGQUxTRSkNCg0KbmVnYXRpdmUgPC0gc3RyX3NwbGl0KG5lZ2F0aXZlX2xpbmUsICIsIikNCg0KcG9zaXRpdmUgPC0gZGF0YS5mcmFtZSh3b3JkID0gcG9zaXRpdmUsIHNlbnRpbWVudHMgPSAicG9zaXRpdmUiKQ0KY29sbmFtZXMocG9zaXRpdmUpID0gYygid29yZCIsInNlbnRpbWVudCIpDQoNCm5lZ2F0aXZlIDwtIGRhdGEuZnJhbWUod29yZCA9IG5lZ2F0aXZlLCBzZW50aWVtdG5zID0gIm5lZ2F0aXZlIikNCmNvbG5hbWVzKG5lZ2F0aXZlKSA9IGMoIndvcmQiLCJzZW50aW1lbnQiKQ0KDQpMSVdDX2NoIDwtIHJiaW5kKHBvc2l0aXZlLCBuZWdhdGl2ZSkNCmBgYA0KDQojIyMg5bCN5oeJ5Lit5paH5oOF57eS6L6t5YW4DQpgYGB7cn0NCmRhdGEgPC0gY3N2DQp3b3JkX2NvdW50cyA8LSBkYXRhICU+JQ0KICBzZWxlY3Qod29yZCxjb3VudCkgJT4lIA0KICBncm91cF9ieSh3b3JkKSAlPiUNCiAgc3VtbWFyaXNlKGNvdW50ID0gc3VtKGNvdW50KSkgJT4lDQogIGlubmVyX2pvaW4oTElXQ19jaCkNCndvcmRfY291bnRzDQpgYGANCg0KYGBge3J9DQp3b3JkX2NvdW50cyAlPiUNCiAgZ3JvdXBfYnkoc2VudGltZW50KSAlPiUNCiAgdG9wX24oMTAsd3QgPSBjb3VudCkgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgbXV0YXRlKHdvcmQgPSByZW9yZGVyKHdvcmQsIGNvdW50KSkgJT4lDQogIGdncGxvdChhZXMod29yZCwgY291bnQsIGZpbGwgPSBzZW50aW1lbnQpKSArDQogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsNCiAgZmFjZXRfd3JhcCh+c2VudGltZW50LCBzY2FsZXMgPSAiZnJlZV95IikgKw0KICBsYWJzKHkgPSAiQ29udHJpYnV0aW9uIHRvIHNlbnRpbWVudCIsDQogICAgICAgeCA9IE5VTEwpICsNCiAgdGhlbWUodGV4dD1lbGVtZW50X3RleHQoZmFtaWx5PSLpu5Hpq5Qt57mBIOS4rem7kSIsIHNpemU9MTQpKSsNCiAgY29vcmRfZmxpcCgpDQpgYGANCg0KYGBge3J9DQpwYXIoZmFtaWx5PSgiSGVpdGkgVEMgTGlnaHQiKSkgI+mBv+WFjeWtl+Wei+eZvOeUn+mMr+iqpA0Kd29yZF9jb3VudHMgJT4lDQogIHdpdGgod29yZGNsb3VkKHdvcmQsIGNvdW50LCBtYXgud29yZHMgPSAxMDApKQ0KYGBgDQoNCmBgYHtyfQ0KcGFyKGZhbWlseT0oIkhlaXRpIFRDIExpZ2h0IikpICPpgb/lhY3lrZflnovnmbznlJ/pjK/oqqQNCndvcmRfY291bnRzICU+JQ0KICBhY2FzdCh3b3JkIH4gc2VudGltZW50LCB2YWx1ZS52YXIgPSAiY291bnQiLCBmaWxsID0gMCkgJT4lDQogIGNvbXBhcmlzb24uY2xvdWQoY29sb3JzID0gYygiZ3JheTIwIiwgImdyYXk4MCIpLA0KICAgICAgICAgICAgICAgICAgIG1heC53b3JkcyA9IDEwMCkNCmBgYA0KIyMjIDIuNyDoqIjnrpfosqDpnaLmg4Xnt5LkvZToqbLnr4foqZXoq5bnmoTmr5TnjocgKOWOn+acrOaYr+S9lOipsueroOevgCkNCmBgYHtyfQ0KYWxsX3dvcmQgPC0gZGF0YSAlPiUNCiAgbXV0YXRlKGlkID0gZ3JvdXBfaW5kaWNlcyhkYXRhLGFydFVybCkpICU+JQ0KICBzZWxlY3QoaWQsIHdvcmQsIGNvdW50KSAlPiUNCiAgZ3JvdXBfYnkoaWQpICU+JQ0KICBzdW1tYXJpc2UoYWxsID0gc3VtKGNvdW50KSkNCg0KbmVnYXRpdmVfd29yZCA8LSBkYXRhICU+JQ0KICBtdXRhdGUoaWQgPSBncm91cF9pbmRpY2VzKGRhdGEsYXJ0VXJsKSkgJT4lDQogIHNlbGVjdChpZCwgd29yZCwgY291bnQpICU+JQ0KICBsZWZ0X2pvaW4obmVnYXRpdmUsKSAlPiUNCiAgbmEub21pdCgpICU+JQ0KICBncm91cF9ieShpZCkgJT4lDQogIHN1bW1hcmlzZShuZWdhdGl2ZV9zdW0gPSBzdW0oY291bnQpKQ0KDQptZXJnZSA8LSBsZWZ0X2pvaW4oYWxsX3dvcmQsIG5lZ2F0aXZlX3dvcmQpIA0KbWVyZ2UgJT4lDQogIG5hLm9taXQoKSAlPiUNCiAgbXV0YXRlKHJhdGlvID0gbmVnYXRpdmVfc3VtIC8gYWxsKQ0KYGBgDQoNCiMjIyMgQ2gzIA0KIyMjIDMuMSBDYWxjdWxhdGUgVGVybSBGcmVxdWVuY3kNCmBgYHtyfQ0KemlwZl9jaCA8LSBkYXRhICU+JSANCiAgbXV0YXRlKGlkID0gZ3JvdXBfaW5kaWNlcyhkYXRhLGFydFVybCkpICU+JQ0KICBncm91cF9ieShpZCkgJT4lDQogIG11dGF0ZSh0b3RhbCA9IHN1bShjb3VudCkpICU+JQ0KICBzZWxlY3QoaWQsIHdvcmQsIGNvdW50LCB0b3RhbCkgDQoNCmdncGxvdCh6aXBmX2NoLCBhZXMoY291bnQvdG90YWwpKSsNCmdlb21faGlzdG9ncmFtKHNob3cubGVnZW5kID0gRkFMU0UpICsNCnhsaW0oTkEsIDAuMSkNCmBgYA0KDQojIyMgMy4yIFppcGYncyBMYXcNCmBgYHtyfQ0KZnJlcV9ieV9yYW5rIDwtIGRhdGEgJT4lIA0KICBtdXRhdGUoaWQgPSBncm91cF9pbmRpY2VzKGRhdGEsYXJ0VXJsKSkgJT4lIA0KICBncm91cF9ieSh3b3JkKSAlPiUNCiAgc3VtbWFyaXNlKGNvdW50ID0gc3VtKGNvdW50KSkgJT4lDQogIGFycmFuZ2UoZGVzYyhjb3VudCkpICU+JQ0KICBtdXRhdGUodG90YWwgPSBzdW0oY291bnQpKSAlPiUNCiAgbXV0YXRlKHJhbmsgPSByb3dfbnVtYmVyKCkpICU+JQ0KICBtdXRhdGUoInRlcm0gZnJlcXVlbmN5IiA9IGNvdW50IC8gdG90YWwpDQogIA0KZnJlcV9ieV9yYW5rICU+JSANCiAgZ2dwbG90KGFlcyhyYW5rLCBgdGVybSBmcmVxdWVuY3lgKSkgKyANCiAgZ2VvbV9saW5lKHNpemUgPSAxLjEsIGFscGhhID0gMC44LCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArIA0KICBzY2FsZV94X2xvZzEwKCkgKw0KICBzY2FsZV95X2xvZzEwKCkNCmBgYA0KDQoqKuWEmOeuoeaIkeWAkeeUqOS4reaWh+eahOizh+aWmembhu+8jOi3keWHuuS+hueahOe1kOaenOS5n+i3n+aVmeenkeabuOeahOW+iOaOpei/kSA9PiDnrKblkIhaaXBmJ3MgTGF3KioNCjxpbWcgc3JjPSJodHRwczovL3d3dy50aWR5dGV4dG1pbmluZy5jb20vMDMtdGYtaWRmX2ZpbGVzL2ZpZ3VyZS1odG1sL3ppcGYtMS5wbmciPg0KDQpgYGB7cn0NCnJhbmtfc3Vic2V0IDwtIGZyZXFfYnlfcmFuayAlPiUgDQogIGZpbHRlcihyYW5rIDwgNTAwLA0KICAgICAgICAgcmFuayA+IDEwKQ0KDQpsbShsb2cxMChgdGVybSBmcmVxdWVuY3lgKSB+IGxvZzEwKHJhbmspLCBkYXRhID0gcmFua19zdWJzZXQpDQpgYGANCioq5L+C5pW45LiN5aSq5o6l6L+RLTEgPT4g5Y+v6IO95piv5Zug54K66LOH5paZ6YeP5LiN5aSg5aSnKD8pKioNCg0KIyMjIDMuMyBUaGUgYmluZF90Zl9pZGYgZnVuY3Rpb24NCmBgYHtyfQ0KZGF0YV90ZmlkZiA8LSBkYXRhICU+JQ0KICBiaW5kX3RmX2lkZih3b3JkLCBhcnRVcmwsIGNvdW50KQ0KYGBgDQoNCmBgYHtyfQ0KZGF0YV90ZmlkZiAlPiUgDQogIGdyb3VwX2J5KGFydFVybCkgJT4lDQogIG11dGF0ZSh0b3RhbCA9IHN1bShjb3VudCkpICU+JQ0KICBzZWxlY3QoLXRvdGFsKSAlPiUgDQogIGFycmFuZ2UoZGVzYyh0Zl9pZGYpKSAlPiUNCiAgdW5ncm91cCgpJT4lDQogIHNlbGVjdChhcnRUaXRsZSwgd29yZCwgdGZfaWRmKQ0KYGBgDQoNCioq5paH56ug55qE6YeN6KaB6Kme5b2Z5YW25a+m6KC75ZCI55CG55qEKOS9huaYr2FydFRpdGxl6KaB6ICD5oWu5LiA5LiLKSoqDQoNCiMjIyDpmqjmqZ/lj5bkuIDnr4foqZXoq5bkvobnnIvnnIvku5bmk4HmnInpq5h0Zi1pZGbnmoTlrZcNCmBgYHtyfQ0KZGF0YV90ZmlkZiAlPiUgDQogIGZpbHRlcihhcnRVcmwgPT0gImh0dHBzOi8vd3d3LnB0dC5jYy9iYnMvR29zc2lwaW5nL00uMTU0OTcxMDkzNC5BLjgyMS5odG1sIikgJT4lDQogIHRvcF9uKDEwLHd0ID0gdGZfaWRmKSAlPiUNCiAgZ2dwbG90KGFlcyh3b3JkLCB0Zl9pZGYpLCApICsNCiAgZ2d0aXRsZSgiW+WVj+WNpl3oj6/oiKrmnInlj6/og73ot5/ml6XoiKrkuIDmqKPnoLTnlKLll44/IikrDQogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsNCiAgbGFicyh4ID0gTlVMTCwgeSA9ICJ0Zi1pZGYiKSArDQogIHRoZW1lKHRleHQ9ZWxlbWVudF90ZXh0KGZhbWlseT0i6buR6auULee5gSDkuK3pu5EiLCBzaXplPTE0KSkrDQogIGNvb3JkX2ZsaXAoKQ0KYGBgDQoNCiMjIyAzLjTmmK/liIbmnpDkuI3lkIzmm7jmnKzvvIznnIHnlaXkuI3lgZoNCg0KDQoNCiMjIyNDSDQNCg0KDQoNCg0KDQoNCg0KDQoNCg0KIyMjI0NINQ0KDQoNCg0KDQoNCg0KDQpgYGB7cn0NCiNpbnN0YWxsLnBhY2thZ2VzKCd0bWNuJykNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGRhdGEudGFibGUpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KHRtY24pDQpsaWJyYXJ5KHRtKQ0KbGlicmFyeSh0aWR5dGV4dCkNCmBgYA0KDQoNCmBgYHtyfQ0KDQpzb2NpYWxfbWVkaWE9ZnJlYWQoJy4uL2RhdGEvc29jaWFsX21lZGlhX2FydFdvcmQuY3N2JyxlbmNvZGluZz0nVVRGLTgnLHN0cmluZ3NBc0ZhY3RvcnMgPSBUKQ0Kc29jaWFsX21lZGlhX2FydFdvcmRGcmVxPWZyZWFkKCcuLi9kYXRhL3NvY2lhbF9tZWRpYV9hcnRXb3JkRnJlcS5jc3YnLGVuY29kaW5nPSdVVEYtOCcsc3RyaW5nc0FzRmFjdG9ycyA9IFQpDQoNCmBgYA0KDQoNCg0KDQoNCiMjIzUuMSBUaWR5aW5nIGEgZG9jdW1lbnQtdGVybSBtYXRyaXgNCg0KDQoNCg0KDQpgYGB7cn0NCmR0bV9kYXRhPXNvY2lhbF9tZWRpYV9hcnRXb3JkRnJlcSAlPiUNCiAgY2FzdF9kdG0oYXJ0VGl0bGUsIHdvcmQsIGNvdW50KQ0KYGBgDQoNCg0KDQpgYGB7cn0NCmRmbV9kYXRhPXNvY2lhbF9tZWRpYV9hcnRXb3JkRnJlcSAlPiUNCiAgY2FzdF9kZm0oLixhcnRUaXRsZSx3b3JkLGNvdW50KQ0KYGBgDQoNCg0KDQojIyMjNS4xLjEgVGlkeWluZyBEb2N1bWVudFRlcm1NYXRyaXggb2JqZWN0cw0KDQpgYGB7cn0NCmxpYnJhcnkodG0pDQpkZm1fZGF0YT0gdGlkeShkZm1fZGF0YSkNCmR0bV9kYXRhX3RmaWRmPWRmbV9kYXRhICU+JSBiaW5kX3RmX2lkZih0ZXJtLCBkb2N1bWVudCwgY291bnQpIA0KZHRtX2RhdGFfdGZpZGYgDQpgYGANCg0KDQpgYGB7cn0NCmxpYnJhcnkoZ2dwbG90MikNCg0KZHRtX2RhdGFfdGZpZGYgJT4lDQoNCm11dGF0ZSh0ZXJtID0gcmVvcmRlcih0ZXJtLCBjb3VudCkpICU+JQ0KZmlsdGVyKGNvdW50ID4gMjUpICU+JQ0KZ2dwbG90KGFlcyh0ZXJtLCBjb3VudCxmaWxsPXRlcm0pKSArDQpnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArDQp4bGFiKE5VTEwpICsNCmNvb3JkX2ZsaXAoKSANCg0KYGBgDQoNCg0KDQojIyM1LjIgQ2FzdGluZyB0aWR5IHRleHQgZGF0YSBpbnRvIGEgbWF0cml4DQoNCg0KDQoNCmBgYHtyfQ0KZHRtX2RhdGE9c29jaWFsX21lZGlhX2FydFdvcmRGcmVxICU+JQ0KICBjYXN0X2R0bShhcnRUaXRsZSwgd29yZCwgY291bnQpDQpgYGANCg0KDQoNCmBgYHtyfQ0KZGZtX2RhdGE9c29jaWFsX21lZGlhX2FydFdvcmRGcmVxICU+JQ0KICBjYXN0X2RmbSguLGFydFRpdGxlLHdvcmQsY291bnQpDQpgYGANCg0KDQpgYGB7cn0NCmxpYnJhcnkoTWF0cml4KQ0KDQojIGNhc3QgaW50byBhIE1hdHJpeCBvYmplY3QNCm0gPC0gc29jaWFsX21lZGlhX2FydFdvcmRGcmVxICU+JQ0KICBjYXN0X3NwYXJzZShhcnRUaXRsZSx3b3JkLGNvdW50KQ0KDQpjbGFzcyhtKQ0KYGBgDQoNCmBgYHtyfQ0KZGltKG0pDQpgYGANCg0KDQoNCg0KIyMjI0NINg0KDQoNCmBgYHtyfQ0KI2luc3RhbGwucGFja2FnZXMoJ3RvcGljbW9kZWxzJykNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGRhdGEudGFibGUpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KHRvcGljbW9kZWxzKQ0KYGBgDQoNCg0KDQoNCmBgYHtyfQ0KDQpzb2NpYWxfbWVkaWE9ZnJlYWQoJy4uL2RhdGEvc29jaWFsX21lZGlhX2FydFdvcmQuY3N2JyxlbmNvZGluZz0nVVRGLTgnLHN0cmluZ3NBc0ZhY3RvcnMgPSBUKQ0Kc29jaWFsX21lZGlhX2FydFdvcmRGcmVxPWZyZWFkKCcuLi9kYXRhL3NvY2lhbF9tZWRpYV9hcnRXb3JkRnJlcS5jc3YnLGVuY29kaW5nPSdVVEYtOCcsc3RyaW5nc0FzRmFjdG9ycyA9IFQpDQoNCmBgYA0KDQoNCg0KIyMjNi4xICBMYXRlbnQgRGlyaWNobGV0IGFsbG9jYXRpb24NCg0KDQoNCg0KDQoNCg0KYGBge3J9DQpkdG1fZGF0YT1zb2NpYWxfbWVkaWFfYXJ0V29yZEZyZXEgJT4lDQogIGNhc3RfZHRtKGFydFRpdGxlLCB3b3JkLCBjb3VudCkNCmR0bV9kYXRhDQpgYGANCg0KDQoNCg0KDQpgYGB7cn0NCnNvY2lhbF9sZGEgPC0gTERBKGR0bV9kYXRhLCBrID0gNCwgY29udHJvbCA9IGxpc3Qoc2VlZCA9IDEyMzQpKQ0Kc29jaWFsX2xkYQ0KDQpgYGANCg0KIyMjIzYuMS4xIFdvcmQtdG9waWMgcHJvYmFiaWxpdGllcw0KDQpgYGB7cn0NCnNvY2lhbF90b3BpY3MgPC0gdGlkeShzb2NpYWxfbGRhLCBtYXRyaXggPSAiYmV0YSIpDQpzb2NpYWxfdG9waWNzIA0KYGBgDQoNCg0KDQoNCmBgYHtyfQ0KDQp0b3BfdGVybXMgPC0gc29jaWFsX3RvcGljcyAlPiUNCiAgZ3JvdXBfYnkodG9waWMpICU+JQ0KICB0b3Bfbig1LCBiZXRhKSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBhcnJhbmdlKHRvcGljLCAtYmV0YSkNCg0KdG9wX3Rlcm1zDQpgYGANCg0KDQpgYGB7cn0NCmxpYnJhcnkoZ2dwbG90MikNCg0KdG9wX3Rlcm1zICU+JQ0KICBtdXRhdGUodGVybSA9IHJlb3JkZXIodGVybSwgYmV0YSkpICU+JQ0KICBnZ3Bsb3QoYWVzKHRlcm0sIGJldGEsIGZpbGwgPSBmYWN0b3IodG9waWMpKSkgKw0KICBnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArDQogIGZhY2V0X3dyYXAofiB0b3BpYywgc2NhbGVzID0gImZyZWUiKSArDQogIGNvb3JkX2ZsaXAoKQ0KYGBgDQoNCg0KDQoNCmBgYHtyfQ0KbGlicmFyeSh0aWR5cikNCg0KYmV0YV9zcHJlYWQgPC0gc29jaWFsX3RvcGljcyAlPiUNCiAgbXV0YXRlKHRvcGljID0gcGFzdGUwKCJ0b3BpYyIsIHRvcGljKSkgJT4lDQogIHNwcmVhZCh0b3BpYywgYmV0YSkgJT4lDQogIGZpbHRlcih0b3BpYzEgPiAuMDAxIHwgdG9waWMyID4gLjAwMSkgJT4lDQogIG11dGF0ZShsb2dfcmF0aW8gPSBsb2cyKHRvcGljMiAvIHRvcGljMSkpDQoNCmJldGFfc3ByZWFkDQpgYGANCg0KDQoNCiMjIzYuMS4yIERvY3VtZW50LXRvcGljIHByb2JhYmlsaXRpZXMNCg0KDQpgYGB7cn0NCmFwX2RvY3VtZW50cyA8LSB0aWR5KHNvY2lhbF9sZGEsIG1hdHJpeCA9ICJnYW1tYSIpDQphcF9kb2N1bWVudHNbLDI6M10NCmBgYA0KDQoNCg0KDQoNCg0KDQoNCjxzdHlsZT4NCg0KZW0gew0KICAgIGNvbG9yOiAjRkZFQTZDOw0KICAgIGJhY2tncm91bmQ6ICM3RDdEN0Q7DQp9DQoNCi5jYXB0aW9uIHsNCiAgY29sb3I6ICM3Nzc7DQogIG1hcmdpbi10b3A6IDEwcHg7DQp9DQpwIGNvZGUgew0KICB3aGl0ZS1zcGFjZTogaW5oZXJpdDsNCn0NCnByZSB7DQogIHdvcmQtYnJlYWs6IG5vcm1hbDsNCiAgd29yZC13cmFwOiBub3JtYWw7DQogIGxpbmUtaGVpZ2h0OiAxOw0KfQ0KcHJlIGNvZGUgew0KICB3aGl0ZS1zcGFjZTogaW5oZXJpdDsNCn0NCnAsbGkgew0KICBmb250LWZhbWlseTogIlRyZWJ1Y2hldCBNUyIsICLlvq7ou5/mraPpu5Hpq5QiLCAiTWljcm9zb2Z0IEpoZW5nSGVpIjsNCn0NCg0KLnJ7DQogIGxpbmUtaGVpZ2h0OiAxLjI7DQp9DQoNCi5xaXogew0KICBsaW5lLWhlaWdodDogMS43NTsNCiAgYmFja2dyb3VuZDogI2YwZjBmMDsNCiAgYm9yZGVyLWxlZnQ6IDEycHggc29saWQgI2NjZmZjYzsNCiAgcGFkZGluZzogNHB4Ow0KICBwYWRkaW5nLWxlZnQ6IDEwcHg7DQogIGNvbG9yOiAjMDA5OTAwOw0KfQ0KDQp0aXRsZXsNCiAgY29sb3I6ICNjYzAwMDA7DQogIGZvbnQtZmFtaWx5OiAiVHJlYnVjaGV0IE1TIiwgIuW+rui7n+ato+m7kemrlCIsICJNaWNyb3NvZnQgSmhlbmdIZWkiOw0KfQ0KDQpib2R5ew0KICBmb250LWZhbWlseTogIlRyZWJ1Y2hldCBNUyIsICLlvq7ou5/mraPpu5Hpq5QiLCAiTWljcm9zb2Z0IEpoZW5nSGVpIjsNCn0NCg0KaDEsaDIsaDMsaDQsaDV7DQogIGNvbG9yOiAjMDA2NmZmOw0KICBmb250LWZhbWlseTogIlRyZWJ1Y2hldCBNUyIsICLlvq7ou5/mraPpu5Hpq5QiLCAiTWljcm9zb2Z0IEpoZW5nSGVpIjsNCn0NCg0KDQpoM3sNCiAgY29sb3I6ICNiMzZiMDA7DQogIGJhY2tncm91bmQ6ICNmZmUwYjM7DQogIGxpbmUtaGVpZ2h0OiAyOw0KICBmb250LXdlaWdodDogYm9sZDsNCn0NCg0KaDV7DQogIGNvbG9yOiAjMDA2MDAwOw0KICBiYWNrZ3JvdW5kOiAjZjhmOGY4Ow0KICBsaW5lLWhlaWdodDogMS41Ow0KICBmb250LXdlaWdodDogYm9sZDsNCn0NCg0KaDYgew0KICAgIGNvbG9yOiAjMDA2MDAwOw0KICAgIGJhY2tncm91bmQ6ICMwMGZmZmY7DQogICAgbGluZS1oZWlnaHQ6IDI7DQogICAgZm9udC13ZWlnaHQ6IGJvbGQ7DQp9DQoNCjwvc3R5bGU+DQoNCg0KDQo=