資料取得

資料取得及套件載入

載入的資料是由中山大學管理學院文字分析平台取得,在平台資料選擇下載原始資料所取得之csv檔案。

資料簡介

本資料為2019/01/01 ~ 2019/04/12 PTT八卦版之資料,透過文字分析平台檢索「館長」、「陳之漢」兩個關鍵字,共搜尋到646篇文章。

Sys.setlocale(category = "LC_ALL", locale = "zh_TW.UTF-8") # 避免中文亂碼
[1] "zh_TW.UTF-8/zh_TW.UTF-8/zh_TW.UTF-8/C/zh_TW.UTF-8/zh_TW.UTF-8"

安裝需要的packages

packages = c("dplyr", "tidytext", "jiebaR", "gutenbergr", "stringr", "wordcloud2", "ggplot2", "tidyr", "scales", "widyr", "readr", "reshape2", "NLP", "ggraph", "igraph", "tm", "data.table", "quanteda", "Matrix", "slam", "Rtsne", "randomcoloR", "wordcloud")
existing = as.character(installed.packages()[,1])
for(pkg in packages[!(packages %in% existing)]) install.packages(pkg)
require(dplyr)
require(tidytext)
require(jiebaR)
require(gutenbergr)
require(stringr)
require(wordcloud2)
require(ggplot2)
require(tidyr)
require(scales)
require(widyr)
require(readr)
require(reshape2)
require(NLP)
require(ggraph)
require(igraph)
require(tm)
require(data.table)
require(quanteda)
require(Matrix)
require(slam)
require(Rtsne)
require(randomcoloR)
require(wordcloud)
g_csv <- fread("guan_jang_data.csv", encoding = "UTF-8", header = TRUE)
g_csv <- g_csv %>% 
  filter(artUrl != "https://www.ptt.cc/bbs/Gossiping/M.1547888391.A.836.html")
g_csv$artDate = g_csv$artDate %>% as.Date("%Y/%m/%d")
str(g_csv)
'data.frame':   646 obs. of  10 variables:
 $ artTitle  : chr  "[問卦]館長:服飾Q4營收破6000萬,利潤只有300" "Re:[問卦]館長:服飾Q4營收破6000萬,利潤只有300" "Re:[問卦]館長:服飾Q4營收破6000萬,利潤只有300" "Re:[問卦]館長:服飾Q4營收破6000萬,利潤只有300" ...
 $ artDate   : Date, format: "2018-12-31" "2018-12-31" "2019-01-01" "2019-01-01" ...
 $ artTime   : chr  "23:09:54" "23:23:59" "00:47:39" "01:00:14" ...
 $ artUrl    : chr  "https://www.ptt.cc/bbs/Gossiping/M.1546326956.A.E8A.html" "https://www.ptt.cc/bbs/Gossiping/M.1546327801.A.3DC.html" "https://www.ptt.cc/bbs/Gossiping/M.1546332821.A.7DD.html" "https://www.ptt.cc/bbs/Gossiping/M.1546333577.A.187.html" ...
 $ artPoster : chr  "jack8587" "foreverthink" "MrLuna" "Dannybigma" ...
 $ artCat    : chr  "Gossiping" "Gossiping" "Gossiping" "Gossiping" ...
 $ commentNum: int  133 22 24 7 4 9 2 4 691 7 ...
 $ push      : int  65 9 7 1 1 3 2 2 471 4 ...
 $ boo       : int  13 4 8 3 0 2 0 1 60 0 ...
 $ sentence  : chr  "館長昨天在直播表示,感謝大家支持,衣服事業短短3個月已進帳6000多萬,但會計精算過\n後,扣除人事成本只賺300萬,換"| __truncated__ "算?\n成\n\n三成是行規吧...\n\n第一次或頭幾次跟工廠做生意都是訂金要三成\n\n怎麼會是壓低成本呢\n\n這叫人家怕被詐"| __truncated__ "奇怪 大量訂製衣服  這流程是不是怪怪的\n\n有人跟成衣廠訂貨  還要自己出材料費的嗎??\n\n般流程  指定布料  設計圖  "| __truncated__ "算?\n成\n萬?\n界?\n奇怪 大量訂製衣服  這流程是不是怪怪的\n\n有人跟成衣廠訂貨  還要自己出材料費的嗎??\n\n般流程 "| __truncated__ ...
 - attr(*, ".internal.selfref")=<externalptr> 

預覽資料

head(g_csv)

日期折線圖

這個章節的目的是計算出每一天文章的發表數量,可以看出特定主題討論的熱度。

資料處理

g_date <- g_csv %>% 
  select(artDate, artUrl) %>% 
  distinct()

由於這份資料的每一列是特定文章的每一個詞彙,我們只需要文章以及日期兩個欄位即可,其他重複欄位可以去除。(一篇文章有很多個詞彙,所以會有很多列,但我們只需要保留一個URL即可)。

article_count_by_date <- g_date %>% 
  group_by(artDate) %>% 
  summarise(count = n())
article_count_by_date %>% 
  arrange(desc(count))%>% 
  top_n(10)
Selecting by count

按照日期分群,計算每天共有幾篇討論文章。

article_count_by_date %>% 
ggplot(aes(x = artDate, y = count)) +
geom_line(color = "purple", size = 1.5) + 
geom_vline(xintercept = c(as.numeric(as.Date("2019-01-05")),
                          as.numeric(as.Date("2019-01-16")),
                          as.numeric(as.Date("2019-03-04")),
                          as.numeric(as.Date("2019-03-10")), 
                          as.numeric(as.Date("2019-04-09"))), col='red', size = 1) + 
scale_x_date(labels = date_format("%Y/%m/%d")) +
ggtitle("「館長」討論文章數") + 
xlab("日期") + 
ylab("數量") +
theme(text = element_text(family = "Heiti TC Light"))

從上圖中可以看到關於「館長」的討論在1/05, 1/16, 3/04, 3/10, 4/09 出現高點。 1/05: [問卦]館長嘴斷食,究竟哪邊對? [爆卦]館長:肏你媽有種現在就打過來啦 1/16: [爆卦]館長正在跟柯文哲直播談虐童案 [新聞]館長促阿北2020選總統 柯P:我要再想想 3/04: [新聞]「叫小賀!」孫安佐單挑館長影片曝光眼 [問卦]館長打得贏甄子丹嗎 3/10: [新聞]統促黨嗆館長打一場 「簽生死狀,條件你 [問卦]館長譙統促黨髒話本來就理虧,不是嗎? 4/09: [新聞]館長對決美國智庫?蔡賴今晚熱身賽 [新聞]與館長談統獨賴清德:統一就像斯斯有兩

Ch.2 文字雲

接下來我們來大略觀察討論的內容為何,使用的方式為文字雲。

斷詞、停用詞使用

使用外部檔案作為斷詞、停用詞參數

# 初始化斷詞引擎
jieba_tokenizer <- worker(user="k_dict.txt", stop_word = "stop_words.txt")
# 自定義斷詞函式
g_tokenizer <- function(t) {
  lapply(t, function(x) {
    tokens <- segment(x, jieba_tokenizer)
    tokens <- tokens[nchar(tokens)>1]
    return(tokens)
  })
}
g_tokens <- g_csv %>% 
  unnest_tokens(word, sentence, token=g_tokenizer) %>% 
  select(-artTime, -artUrl)
g_tokens_count <- g_tokens %>% 
  group_by(word) %>% 
  summarise(sum = n()) %>% 
  arrange(desc(sum))

先將資料集中所有文章按照文字進行分群,計算每一個字的總詞頻。

head(g_tokens_count)

結果為總詞頻最多的字。

g_tokens_count %>% 
  filter(word != "館長") %>% 
  filter(sum > 20) %>% 
  wordcloud2()

將整理好的資料直接送入wordcloud2

Ch3. 長條圖

文字雲可以直覺看出較常提到的字,但如果想得到精確的「最常出現詞彙」,我們則可以透過長條圖來查看。

g_tokens_count %>% 
  top_n(10) %>% 
  mutate(word = reorder(word, sum)) %>% 
  ggplot(aes(word, sum)) +
  geom_col(show.legend = FALSE) +
  labs(x = NULL, y="詞頻") +
  coord_flip()+
  theme(text = element_text(family = "Heiti TC Light"))
Selecting by sum

計算tf-idf

以文章區格document

g_tokens_by_art <- g_tokens %>% 
  filter(!str_detect(word, regex("[0-9]"))) %>%
  count(artTitle, word, sort = TRUE)
g_total_words_by_art <- g_tokens_by_art %>% 
  group_by(artTitle) %>% 
  summarize(total = sum(n)) %>% 
  arrange(desc(total))
g_tokens_by_art <- left_join(g_tokens_by_art, g_total_words_by_art)
Joining, by = "artTitle"

過濾掉文章長度少於20個詞的

g_words_tf_idf <- g_tokens_by_art %>%
  bind_tf_idf(word, artTitle, n) 
g_words_tf_idf %>% 
  filter(total > 20) %>% 
  arrange(desc(tf_idf))

文章本文: 背心尊者有強大的背心能力 平常館長也很常穿背心
不過我觀察館長穿的是寬鬆類背心
背心尊者穿的是緊身類背心
雖然是不同背心
但同樣都是背心愛好者
手下的教練 也是有背心軍團的資格
館長根本是真人版的背心尊者吧 大家覺得呢

文章總長度大於100個詞

g_words_tf_idf %>% 
  filter(total > 100) %>% 
  arrange(desc(tf_idf))

用日期來區隔document

g_tokens_by_date <- g_tokens %>% 
  filter(!str_detect(word, regex("[0-9]"))) %>%
  count(artDate, word, sort = TRUE)
g_total_words_by_date <- g_tokens_by_date %>% 
  group_by(artDate) %>% 
  summarize(total = sum(n)) %>% 
  arrange(desc(total))
g_tokens_by_date <- left_join(g_tokens_by_date, g_total_words_by_date)
Joining, by = "artDate"
g_words_tf_idf_date <- g_tokens_by_date %>%
  bind_tf_idf(word, artDate, n) 
g_words_tf_idf_date %>% 
  filter(total > 20) %>% 
  group_by(artDate) %>% 
  top_n(1) %>% 
  arrange(artDate)
Selecting by tf_idf

找出前面五個日期篇數高點

g_words_tf_idf_date %>% 
  filter(total > 20) %>% 
  filter(artDate == as.Date("2019-01-05") | 
         artDate == as.Date("2019-01-16") | 
         artDate == as.Date("2019-03-04") |
         artDate == as.Date("2019-03-10") | 
         artDate == as.Date("2019-04-09")) %>% 
  group_by(artDate) %>%  
  top_n(1) %>% 
  arrange(artDate)
Selecting by tf_idf

前面通過篇數找出的文章篇數高點 1/05, 1/16, 3/04, 3/10, 4/09 。 1/05: [問卦]館長嘴斷食,究竟哪邊對? [爆卦]館長:肏你媽有種現在就打過來啦 1/16: [爆卦]館長正在跟柯文哲直播談虐童案 [新聞]館長促阿北2020選總統 柯P:我要再想想 3/04: [新聞]「叫小賀!」孫安佐單挑館長影片曝光眼 [問卦]館長打得贏甄子丹嗎 3/10: [新聞]統促黨嗆館長打一場 「簽生死狀,條件你 [問卦]館長譙統促黨髒話本來就理虧,不是嗎? 4/09: [新聞]館長對決美國智庫?蔡賴今晚熱身賽 [新聞]與館長談統獨賴清德:統一就像斯斯有兩

前後五個字彙

ngram_11 <- function(t) {
  lapply(t, function(x) {
    tokens <- segment(x, jieba_tokenizer)
    ngram <- ngrams(tokens, 11)
    ngram <- lapply(ngram, paste, collapse = " ")
    unlist(ngram)
  })
}
g_ngram_11 <- g_csv %>%
  select(artUrl, sentence) %>%
  unnest_tokens(ngram, sentence, token = ngram_11) %>%
  filter(!str_detect(ngram, regex("[0-9a-zA-Z]")))
g_ngram_11
g_ngrams_11_separated <- g_ngram_11 %>%
  separate(ngram, paste0("word", c(1:11),sep=""), sep = " ")
g_ngrams_11_separated
g_check_words <- g_ngrams_11_separated %>%
  filter((word6=="統促黨"))
g_check_words
g_check_words_count <- g_check_words %>%
  melt(id.vars = "artUrl", measure.vars = paste0("word", c(1:11),sep="")) %>%
  rename(word=value) %>%
  filter(variable!="word6") %>%
  filter(!(word %in% stop_words), nchar(word)>1) %>%
  count(word, sort = TRUE)
g_check_words_count
g_check_words_count %>%
  arrange(desc(abs(n))) %>%
  head(15) %>%
  mutate(word = reorder(word, n)) %>%
  ggplot(aes(word, n, fill = n > 0)) +
  geom_col(show.legend = FALSE) +
  xlab("Words near by \"館長\"") +
  ylab("Word count") +
  coord_flip()+ 
  theme(text = element_text(family = "Heiti TC Light"))

Word Correlation

g_words_by_art <- g_csv %>%
  unnest_tokens(word, sentence, token=g_tokenizer) %>%
  filter(!str_detect(word, regex("[0-9]"))) %>%
  count(artUrl, word, sort = TRUE)
g_words_by_art
g_word_pairs <- g_words_by_art %>%
  pairwise_count(word, artUrl, sort = TRUE)
g_word_pairs
g_word_cors <- g_words_by_art %>%
  group_by(word) %>%
  filter(n() >= 20) %>%
  pairwise_cor(word, artUrl, sort = TRUE)
g_word_cors
g_word_cors %>%
  filter(item1 == "館長")
set.seed(2019)
g_word_cors %>%
  filter(correlation > .4) %>%
  graph_from_data_frame() %>%
  ggraph(layout = "fr") +
  geom_edge_link(aes(edge_alpha = correlation), show.legend = FALSE) +
  geom_node_point(color = "lightblue", size = 3) +
  geom_node_text(aes(label = name), repel = TRUE, family = "Heiti TC Light") +
  theme_void()

# 設定幾個詞做爲seed words
seed_words <- c("新聞", "綜合", "appledaily")
# 設定threshold爲0.65
threshold <- 0.65
# 跟seed words相關性高於threshold的詞彙會被加入移除列表中
remove_words <- g_word_cors %>%
                filter((item1 %in% seed_words|item2 %in% seed_words), correlation>threshold) %>%
                .$item1 %>%
                unique()
remove_words
 [1] "appledaily" "realtime"   "即時新聞"   "綜合"       "新聞標題"   "新聞"       "完整"       "內文"      
 [9] "網址"       "報導"       "連結"       "來源"       "備註"       "媒體"      
set.seed(2017)
g_word_cors_new <- g_word_cors %>%
                filter(!(item1 %in% remove_words|item2 %in% remove_words))
g_word_cors_new %>%
  filter(correlation > .4) %>%
  graph_from_data_frame() %>%
  ggraph(layout = "fr") +
  geom_edge_link(aes(edge_alpha = correlation), show.legend = FALSE) + 
  geom_node_point(color = "lightblue", size = 3) +
  geom_node_text(aes(label = name), repel = TRUE, family = "Heiti TC Light") +
  theme_void()

g_words_tf_idf
term_avg_tfidf = g_words_tf_idf %>% 
  group_by(word) %>% 
  summarise(tfidf_avg = mean(tf_idf)) 
term_avg_tfidf %>% arrange(desc(tfidf_avg)) 
term_avg_tfidf$tfidf_avg %>% summary
     Min.   1st Qu.    Median      Mean   3rd Qu.      Max. 
0.0001514 0.0325254 0.0521481 0.0988045 0.1138864 2.1033061 
term_remove=term_avg_tfidf %>%  
  filter(tfidf_avg<0.0325254) %>% 
  .$word
term_remove %>% head
[1] "阿嘎"   "阿共嚇" "啊災"   "挨告"   "愛民"   "愛鄉"  
g_dtm = g_words_tf_idf %>%
  filter(!word %in% term_remove) %>%
  cast_dtm(document=artTitle,term=word,value= n)
g_dtm
<<DocumentTermMatrix (documents: 550, terms: 8705)>>
Non-/sparse entries: 22406/4765344
Sparsity           : 100%
Maximal term length: 13
Weighting          : term frequency (tf)
g_dtm_matrix = g_dtm %>% as.data.frame.matrix 
g_dtm_matrix[1:10,1:20]
library(doParallel)
clust = makeCluster(detectCores())
registerDoParallel(clust); getDoParWorkers()
[1] 4
t0 = Sys.time()
d = g_dtm_matrix %>%
  dist(method="euclidean")  #歐式距離,算文章與文章之間的距離
Sys.time() - t0
Time difference of 7.377416 secs
hc = hclust(d, method='ward.D')  
plot(hc, labels = FALSE)
rect.hclust(hc, k = 3, border="red")

kg = cutree(hc, k = 3)
L = split(g_dtm_matrix, kg)
L$`1`[1:10,1:10]
sapply(L, function(x) x%>% colMeans %>% sort %>% tail %>% names)
     1        2          3     
[1,] "賴清德" "韓國瑜"   "影片"
[2,] "統促黨" "體育"     "健身"
[3,] "完整"   "比賽"     "看到"
[4,] "總統"   "高雄市"   "艾瑪"
[5,] "中國"   "成吉思汗" "台灣"
[6,] "台灣"   "艾瑪"     "八卦"
# t0 = Sys.time()
# n = 2000 #n個字
# tsne = g_dtm[, 1:n] %>% as.data.frame.matrix %>%
#   scale %>% t %>% Rtsne(
#     check_duplicates = FALSE, theta=0.0, max_iter=3200)
# Sys.time()-t0
# Y = tsne$Y              # tSNE coordinates
# d_Y = dist(Y)             # distance matrix
# hc_Y = hclust(d_Y )          # hi-clustering
# plot(hc_Y,label=F)
# rect.hclust(hc_Y, k=10, border="red")
# K = 10                            # number of clusters
# g = cutree(hc_Y,K)                # cut into K clusters
# table(g) %>% as.vector %>% sort   # sizes of clusters
# wc = col_sums(g_dtm[,1:n]) #n個字
# colors = distinctColorPalette(K)
# png("./g.png", width=3200, height=1800)#輸出圖片到路徑下
# textplot(
#   Y[,1], Y[,2], colnames(g_dtm)[1:n], show=F,
#   col=colors[g],
#   cex= 0.3 + 1.25 * sqrt(wc/mean(wc)),
#   font=2, family = "Heiti TC Light")
# dev.off()
LS0tCnRpdGxlOiAiUFRU5YWr5Y2m54mI77ya6aSo6ZW355qE6KiO6KuW5YiG5p6QIgphdXRob3I6ICLpmbPnkKjnv5QiCmRhdGU6ICIyMDE5LzA0LzEyIgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdAogIGh0bWxfZG9jdW1lbnQ6IGRlZmF1bHQKICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQKYWJzdHJhY3Q6ICIiCi0tLQojIOizh+aWmeWPluW+lwojIyDos4fmlpnlj5blvpflj4rlpZfku7bovInlhaUKPiDovInlhaXnmoTos4fmlpnmmK/nlLHkuK3lsbHlpKflrbjnrqHnkIblrbjpmaLmloflrZfliIbmnpDlubPlj7Dlj5blvpfvvIzlnKjlubPlj7Dos4fmlpnpgbjmk4fkuIvovInljp/lp4vos4fmlpnmiYDlj5blvpfkuYtjc3bmqpTmoYjjgIIKCiMjIyDos4fmlpnnsKHku4sKPiDmnKzos4fmlpnngroyMDE5LzAxLzAxIH4gMjAxOS8wNC8xMiBQVFTlhavljabniYjkuYvos4fmlpnvvIzpgI/pgY7mloflrZfliIbmnpDlubPlj7DmqqLntKLjgIzppKjplbfjgI3jgIHjgIzpmbPkuYvmvKLjgI3lhanlgIvpl5zpjbXlrZfvvIzlhbHmkJzlsIvliLA2NDbnr4fmlofnq6DjgIIKCmBgYHtyfQpTeXMuc2V0bG9jYWxlKGNhdGVnb3J5ID0gIkxDX0FMTCIsIGxvY2FsZSA9ICJ6aF9UVy5VVEYtOCIpICMg6YG/5YWN5Lit5paH5LqC56K8CmBgYAoKIyMg5a6J6KOd6ZyA6KaB55qEcGFja2FnZXMKYGBge3J9CnBhY2thZ2VzID0gYygiZHBseXIiLCAidGlkeXRleHQiLCAiamllYmFSIiwgImd1dGVuYmVyZ3IiLCAic3RyaW5nciIsICJ3b3JkY2xvdWQyIiwgImdncGxvdDIiLCAidGlkeXIiLCAic2NhbGVzIiwgIndpZHlyIiwgInJlYWRyIiwgInJlc2hhcGUyIiwgIk5MUCIsICJnZ3JhcGgiLCAiaWdyYXBoIiwgInRtIiwgImRhdGEudGFibGUiLCAicXVhbnRlZGEiLCAiTWF0cml4IiwgInNsYW0iLCAiUnRzbmUiLCAicmFuZG9tY29sb1IiLCAid29yZGNsb3VkIikKZXhpc3RpbmcgPSBhcy5jaGFyYWN0ZXIoaW5zdGFsbGVkLnBhY2thZ2VzKClbLDFdKQpmb3IocGtnIGluIHBhY2thZ2VzWyEocGFja2FnZXMgJWluJSBleGlzdGluZyldKSBpbnN0YWxsLnBhY2thZ2VzKHBrZykKYGBgCgpgYGB7cn0KcmVxdWlyZShkcGx5cikKcmVxdWlyZSh0aWR5dGV4dCkKcmVxdWlyZShqaWViYVIpCnJlcXVpcmUoZ3V0ZW5iZXJncikKcmVxdWlyZShzdHJpbmdyKQpyZXF1aXJlKHdvcmRjbG91ZDIpCnJlcXVpcmUoZ2dwbG90MikKcmVxdWlyZSh0aWR5cikKcmVxdWlyZShzY2FsZXMpCnJlcXVpcmUod2lkeXIpCnJlcXVpcmUocmVhZHIpCnJlcXVpcmUocmVzaGFwZTIpCnJlcXVpcmUoTkxQKQpyZXF1aXJlKGdncmFwaCkKcmVxdWlyZShpZ3JhcGgpCnJlcXVpcmUodG0pCnJlcXVpcmUoZGF0YS50YWJsZSkKcmVxdWlyZShxdWFudGVkYSkKcmVxdWlyZShNYXRyaXgpCnJlcXVpcmUoc2xhbSkKcmVxdWlyZShSdHNuZSkKcmVxdWlyZShyYW5kb21jb2xvUikKcmVxdWlyZSh3b3JkY2xvdWQpCmdfY3N2IDwtIGZyZWFkKCJndWFuX2phbmdfZGF0YS5jc3YiLCBlbmNvZGluZyA9ICJVVEYtOCIsIGhlYWRlciA9IFRSVUUpCmdfY3N2IDwtIGdfY3N2ICU+JSAKICBmaWx0ZXIoYXJ0VXJsICE9ICJodHRwczovL3d3dy5wdHQuY2MvYmJzL0dvc3NpcGluZy9NLjE1NDc4ODgzOTEuQS44MzYuaHRtbCIpCmdfY3N2JGFydERhdGUgPSBnX2NzdiRhcnREYXRlICU+JSBhcy5EYXRlKCIlWS8lbS8lZCIpCnN0cihnX2NzdikKYGBgCgojIyDpoJDopr3os4fmlpkKYGBge3J9CmhlYWQoZ19jc3YpCmBgYAoKIyDml6XmnJ/mipjnt5rlnJYKPiDpgJnlgIvnq6Dnr4DnmoTnm67nmoTmmK/oqIjnrpflh7rmr4/kuIDlpKnmlofnq6DnmoTnmbzooajmlbjph4/vvIzlj6/ku6XnnIvlh7rnibnlrprkuLvpoYzoqI7oq5bnmoTnhrHluqbjgIIKCiMjIOizh+aWmeiZleeQhgoKYGBge3J9CmdfZGF0ZSA8LSBnX2NzdiAlPiUgCiAgc2VsZWN0KGFydERhdGUsIGFydFVybCkgJT4lIAogIGRpc3RpbmN0KCkKYGBgCj4g55Sx5pa86YCZ5Lu96LOH5paZ55qE5q+P5LiA5YiX5piv54m55a6a5paH56ug55qE5q+P5LiA5YCL6Kme5b2Z77yM5oiR5YCR5Y+q6ZyA6KaB5paH56ug5Lul5Y+K5pel5pyf5YWp5YCL5qyE5L2N5Y2z5Y+v77yM5YW25LuW6YeN6KSH5qyE5L2N5Y+v5Lul5Y676Zmk44CCKOS4gOevh+aWh+eroOacieW+iOWkmuWAi+ipnuW9me+8jOaJgOS7peacg+acieW+iOWkmuWIl++8jOS9huaIkeWAkeWPqumcgOimgeS/neeVmeS4gOWAi1VSTOWNs+WPrynjgIIKCgpgYGB7cn0KYXJ0aWNsZV9jb3VudF9ieV9kYXRlIDwtIGdfZGF0ZSAlPiUgCiAgZ3JvdXBfYnkoYXJ0RGF0ZSkgJT4lIAogIHN1bW1hcmlzZShjb3VudCA9IG4oKSkKCmFydGljbGVfY291bnRfYnlfZGF0ZSAlPiUgCiAgYXJyYW5nZShkZXNjKGNvdW50KSklPiUgCiAgdG9wX24oMTApCmBgYAo+IOaMieeFp+aXpeacn+WIhue+pO+8jOioiOeul+avj+WkqeWFseacieW5vuevh+iojuirluaWh+eroOOAggoKYGBge3J9CmFydGljbGVfY291bnRfYnlfZGF0ZSAlPiUgCmdncGxvdChhZXMoeCA9IGFydERhdGUsIHkgPSBjb3VudCkpICsKZ2VvbV9saW5lKGNvbG9yID0gInB1cnBsZSIsIHNpemUgPSAxLjUpICsgCmdlb21fdmxpbmUoeGludGVyY2VwdCA9IGMoYXMubnVtZXJpYyhhcy5EYXRlKCIyMDE5LTAxLTA1IikpLAogICAgICAgICAgICAgICAgICAgICAgICAgIGFzLm51bWVyaWMoYXMuRGF0ZSgiMjAxOS0wMS0xNiIpKSwKICAgICAgICAgICAgICAgICAgICAgICAgICBhcy5udW1lcmljKGFzLkRhdGUoIjIwMTktMDMtMDQiKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgYXMubnVtZXJpYyhhcy5EYXRlKCIyMDE5LTAzLTEwIikpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICBhcy5udW1lcmljKGFzLkRhdGUoIjIwMTktMDQtMDkiKSkpLCBjb2w9J3JlZCcsIHNpemUgPSAxKSArIApzY2FsZV94X2RhdGUobGFiZWxzID0gZGF0ZV9mb3JtYXQoIiVZLyVtLyVkIikpICsKZ2d0aXRsZSgi44CM6aSo6ZW344CN6KiO6KuW5paH56ug5pW4IikgKyAKeGxhYigi5pel5pyfIikgKyAKeWxhYigi5pW46YePIikgKwp0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJIZWl0aSBUQyBMaWdodCIpKQpgYGAKCj4g5b6e5LiK5ZyW5Lit5Y+v5Lul55yL5Yiw6Zec5pa844CM6aSo6ZW344CN55qE6KiO6KuW5ZyoMS8wNSwgMS8xNiwgMy8wNCwgMy8xMCwgNC8wOSDlh7rnj77pq5jpu57jgIIKMS8wNTogW+WVj+WNpl3ppKjplbflmLTmlrfpo5/vvIznqbbnq5/lk6rpgorlsI3vvJ8gICAgICAgICAgICAgICBb54iG5Y2mXemkqOmVtzrogo/kvaDlqr3mnInnqK7nj77lnKjlsLHmiZPpgY7kvobllaYKMS8xNjogW+eIhuWNpl3ppKjplbfmraPlnKjot5/mn6/mloflk7Lnm7Tmkq3oq4fomZDnq6XmoYggICAgICAgICAgIFvmlrDogZ5d6aSo6ZW35L+D6Zi/5YyXMjAyMOmBuOe4vee1seOAgOafr1A65oiR6KaB5YaN5oOz5oOzCjMvMDQ6IFvmlrDogZ5d44CM5Y+r5bCP6LOA77yB44CN5a2r5a6J5L2Q5Zau5oyR6aSo6ZW35b2x54mH5pud5YWJ55y8ICAgW+WVj+WNpl3ppKjplbfmiZPlvpfotI/nlITlrZDkuLnll44KMy8xMDogW+aWsOiBnl3ntbHkv4Ppu6jll4bppKjplbfmiZPkuIDloLTjgIDjgIznsL3nlJ/mrbvni4DvvIzmop3ku7bkvaAgW+WVj+WNpl3ppKjplbforZnntbHkv4Ppu6jpq5LoqbHmnKzkvoblsLHnkIbomafvvIzkuI3mmK/ll47vvJ8KNC8wOTogW+aWsOiBnl3ppKjplbflsI3msbrnvo7lnIvmmbrluqvvvJ/olKHos7Tku4rmmZrnhrHouqvos70gICAgICAgW+aWsOiBnl3oiIfppKjplbfoq4fntbHnjajos7TmuIXlvrfvvJrntbHkuIDlsLHlg4/mlq/mlq/mnInlhakKCiMgQ2guMiDmloflrZfpm7IKPiDmjqXkuIvkvobmiJHlgJHkvoblpKfnlaXop4Dlr5/oqI7oq5bnmoTlhaflrrnngrrkvZXvvIzkvb/nlKjnmoTmlrnlvI/ngrrmloflrZfpm7LjgIIKCiMjIOaWt+ipnuOAgeWBnOeUqOipnuS9v+eUqAoKIyMjIOS9v+eUqOWklumDqOaqlOahiOS9nOeCuuaWt+ipnuOAgeWBnOeUqOipnuWPg+aVuAoKYGBge3J9CiMg5Yid5aeL5YyW5pa36Kme5byV5pOOCmppZWJhX3Rva2VuaXplciA8LSB3b3JrZXIodXNlcj0ia19kaWN0LnR4dCIsIHN0b3Bfd29yZCA9ICJzdG9wX3dvcmRzLnR4dCIpCgojIOiHquWumue+qeaWt+ipnuWHveW8jwpnX3Rva2VuaXplciA8LSBmdW5jdGlvbih0KSB7CiAgbGFwcGx5KHQsIGZ1bmN0aW9uKHgpIHsKICAgIHRva2VucyA8LSBzZWdtZW50KHgsIGppZWJhX3Rva2VuaXplcikKICAgIHRva2VucyA8LSB0b2tlbnNbbmNoYXIodG9rZW5zKT4xXQogICAgcmV0dXJuKHRva2VucykKICB9KQp9CmBgYAoKCmBgYHtyfQpnX3Rva2VucyA8LSBnX2NzdiAlPiUgCiAgdW5uZXN0X3Rva2Vucyh3b3JkLCBzZW50ZW5jZSwgdG9rZW49Z190b2tlbml6ZXIpICU+JSAKICBzZWxlY3QoLWFydFRpbWUsIC1hcnRVcmwpCmBgYAoKCmBgYHtyfQpnX3Rva2Vuc19jb3VudCA8LSBnX3Rva2VucyAlPiUgCiAgZ3JvdXBfYnkod29yZCkgJT4lIAogIHN1bW1hcmlzZShzdW0gPSBuKCkpICU+JSAKICBhcnJhbmdlKGRlc2Moc3VtKSkKYGBgCj4g5YWI5bCH6LOH5paZ6ZuG5Lit5omA5pyJ5paH56ug5oyJ54Wn5paH5a2X6YCy6KGM5YiG576k77yM6KiI566X5q+P5LiA5YCL5a2X55qE57i96Kme6aC744CCCgpgYGB7cn0KaGVhZChnX3Rva2Vuc19jb3VudCkKYGBgCj4g57WQ5p6c54K657i96Kme6aC75pyA5aSa55qE5a2X44CCCgpgYGB7cn0KZ190b2tlbnNfY291bnQgJT4lIAogIGZpbHRlcih3b3JkICE9ICLppKjplbciKSAlPiUgCiAgZmlsdGVyKHN1bSA+IDIwKSAlPiUgCiAgd29yZGNsb3VkMigpCmBgYAo+IOWwh+aVtOeQhuWlveeahOizh+aWmeebtOaOpemAgeWFpXdvcmRjbG91ZDIKCiMgQ2gzLiDplbfmop3lnJYKPiDmloflrZfpm7Llj6/ku6Xnm7ToprrnnIvlh7rovIPluLjmj5DliLDnmoTlrZfvvIzkvYblpoLmnpzmg7PlvpfliLDnsr7norrnmoTjgIzmnIDluLjlh7rnj77oqZ7lvZnjgI3vvIzmiJHlgJHliYflj6/ku6XpgI/pgY7plbfmop3lnJbkvobmn6XnnIvjgIIKCmBgYHtyfQpnX3Rva2Vuc19jb3VudCAlPiUgCiAgdG9wX24oMTApICU+JSAKICBtdXRhdGUod29yZCA9IHJlb3JkZXIod29yZCwgc3VtKSkgJT4lIAogIGdncGxvdChhZXMod29yZCwgc3VtKSkgKwogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsKICBsYWJzKHggPSBOVUxMLCB5PSLoqZ7poLsiKSArCiAgY29vcmRfZmxpcCgpKwogIHRoZW1lKHRleHQgPSBlbGVtZW50X3RleHQoZmFtaWx5ID0gIkhlaXRpIFRDIExpZ2h0IikpCmBgYAoKIyMg6KiI566XdGYtaWRmCgojIyMg5Lul5paH56ug5Y2A5qC8ZG9jdW1lbnQKCmBgYHtyfQpnX3Rva2Vuc19ieV9hcnQgPC0gZ190b2tlbnMgJT4lIAogIGZpbHRlcighc3RyX2RldGVjdCh3b3JkLCByZWdleCgiWzAtOV0iKSkpICU+JQogIGNvdW50KGFydFRpdGxlLCB3b3JkLCBzb3J0ID0gVFJVRSkKZ190b3RhbF93b3Jkc19ieV9hcnQgPC0gZ190b2tlbnNfYnlfYXJ0ICU+JSAKICBncm91cF9ieShhcnRUaXRsZSkgJT4lIAogIHN1bW1hcml6ZSh0b3RhbCA9IHN1bShuKSkgJT4lIAogIGFycmFuZ2UoZGVzYyh0b3RhbCkpCmdfdG9rZW5zX2J5X2FydCA8LSBsZWZ0X2pvaW4oZ190b2tlbnNfYnlfYXJ0LCBnX3RvdGFsX3dvcmRzX2J5X2FydCkKYGBgCgojIyMg6YGO5r++5o6J5paH56ug6ZW35bqm5bCR5pa8MjDlgIvoqZ7nmoQKYGBge3J9Cmdfd29yZHNfdGZfaWRmIDwtIGdfdG9rZW5zX2J5X2FydCAlPiUKICBiaW5kX3RmX2lkZih3b3JkLCBhcnRUaXRsZSwgbikgCmdfd29yZHNfdGZfaWRmICU+JSAKICBmaWx0ZXIodG90YWwgPiAyMCkgJT4lIAogIGFycmFuZ2UoZGVzYyh0Zl9pZGYpKQpgYGAKCj4g5paH56ug5pys5paH77yaCuiDjOW/g+WwiuiAheacieW8t+Wkp+eahOiDjOW/g+iDveWKmyAK5bmz5bi46aSo6ZW35Lmf5b6I5bi456m/6IOM5b+DICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCuS4jemBjuaIkeingOWvn+mkqOmVt+epv+eahOaYr+WvrOmshumhnuiDjOW/gyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCuiDjOW/g+WwiuiAheepv+eahOaYr+e3iui6q+mhnuiDjOW/gyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCumblueEtuaYr+S4jeWQjOiDjOW/gyAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIArkvYblkIzmqKPpg73mmK/og4zlv4PmhJvlpb3ogIUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAK5omL5LiL55qE5pWZ57e0IOS5n+aYr+acieiDjOW/g+i7jeWcmOeahOizh+agvCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAK6aSo6ZW35qC55pys5piv55yf5Lq654mI55qE6IOM5b+D5bCK6ICF5ZCnCuWkp+WutuimuuW+l+WRogoKIyMjIOaWh+eroOe4vemVt+W6puWkp+aWvDEwMOWAi+ipngpgYGB7cn0KZ193b3Jkc190Zl9pZGYgJT4lIAogIGZpbHRlcih0b3RhbCA+IDEwMCkgJT4lIAogIGFycmFuZ2UoZGVzYyh0Zl9pZGYpKQpgYGAKCgojIyMg55So5pel5pyf5L6G5Y2A6ZqUZG9jdW1lbnQKYGBge3J9CmdfdG9rZW5zX2J5X2RhdGUgPC0gZ190b2tlbnMgJT4lIAogIGZpbHRlcighc3RyX2RldGVjdCh3b3JkLCByZWdleCgiWzAtOV0iKSkpICU+JQogIGNvdW50KGFydERhdGUsIHdvcmQsIHNvcnQgPSBUUlVFKQpnX3RvdGFsX3dvcmRzX2J5X2RhdGUgPC0gZ190b2tlbnNfYnlfZGF0ZSAlPiUgCiAgZ3JvdXBfYnkoYXJ0RGF0ZSkgJT4lIAogIHN1bW1hcml6ZSh0b3RhbCA9IHN1bShuKSkgJT4lIAogIGFycmFuZ2UoZGVzYyh0b3RhbCkpCmdfdG9rZW5zX2J5X2RhdGUgPC0gbGVmdF9qb2luKGdfdG9rZW5zX2J5X2RhdGUsIGdfdG90YWxfd29yZHNfYnlfZGF0ZSkKZ193b3Jkc190Zl9pZGZfZGF0ZSA8LSBnX3Rva2Vuc19ieV9kYXRlICU+JQogIGJpbmRfdGZfaWRmKHdvcmQsIGFydERhdGUsIG4pIApnX3dvcmRzX3RmX2lkZl9kYXRlICU+JSAKICBmaWx0ZXIodG90YWwgPiAyMCkgJT4lIAogIGdyb3VwX2J5KGFydERhdGUpICU+JSAKICB0b3BfbigxKSAlPiUgCiAgYXJyYW5nZShhcnREYXRlKQpgYGAKCgojIyMg5om+5Ye65YmN6Z2i5LqU5YCL5pel5pyf56+H5pW46auY6bueCmBgYHtyfQpnX3dvcmRzX3RmX2lkZl9kYXRlICU+JSAKICBmaWx0ZXIodG90YWwgPiAyMCkgJT4lIAogIGZpbHRlcihhcnREYXRlID09IGFzLkRhdGUoIjIwMTktMDEtMDUiKSB8IAogICAgICAgICBhcnREYXRlID09IGFzLkRhdGUoIjIwMTktMDEtMTYiKSB8IAogICAgICAgICBhcnREYXRlID09IGFzLkRhdGUoIjIwMTktMDMtMDQiKSB8CiAgICAgICAgIGFydERhdGUgPT0gYXMuRGF0ZSgiMjAxOS0wMy0xMCIpIHwgCiAgICAgICAgIGFydERhdGUgPT0gYXMuRGF0ZSgiMjAxOS0wNC0wOSIpKSAlPiUgCiAgZ3JvdXBfYnkoYXJ0RGF0ZSkgJT4lICAKICB0b3BfbigxKSAlPiUgCiAgYXJyYW5nZShhcnREYXRlKQpgYGAKCj4g5YmN6Z2i6YCa6YGO56+H5pW45om+5Ye655qE5paH56ug56+H5pW46auY6bueIDEvMDUsIDEvMTYsIDMvMDQsIDMvMTAsIDQvMDkg44CCCjEvMDU6IFvllY/ljaZd6aSo6ZW35Zi05pa36aOf77yM56m256uf5ZOq6YKK5bCN77yfICAgICAgICAgICAgICAgW+eIhuWNpl3ppKjplbc66IKP5L2g5aq95pyJ56iu54++5Zyo5bCx5omT6YGO5L6G5ZWmCjEvMTY6IFvniIbljaZd6aSo6ZW35q2j5Zyo6Lef5p+v5paH5ZOy55u05pKt6KuH6JmQ56ul5qGIICAgICAgICAgICBb5paw6IGeXemkqOmVt+S/g+mYv+WMlzIwMjDpgbjnuL3ntbHjgIDmn69QOuaIkeimgeWGjeaDs+aDswozLzA0OiBb5paw6IGeXeOAjOWPq+Wwj+izgO+8geOAjeWtq+WuieS9kOWWruaMkemkqOmVt+W9seeJh+abneWFieecvCAgIFvllY/ljaZd6aSo6ZW35omT5b6X6LSP55SE5a2Q5Li55ZeOCjMvMTA6IFvmlrDogZ5d57Wx5L+D6buo5ZeG6aSo6ZW35omT5LiA5aC044CA44CM57C955Sf5q2754uA77yM5qKd5Lu25L2gIFvllY/ljaZd6aSo6ZW36K2Z57Wx5L+D6buo6auS6Kmx5pys5L6G5bCx55CG6Jmn77yM5LiN5piv5ZeO77yfCjQvMDk6IFvmlrDogZ5d6aSo6ZW35bCN5rG6576O5ZyL5pm65bqr77yf6JSh6LO05LuK5pma54ax6Lqr6LO9ICAgICAgIFvmlrDogZ5d6IiH6aSo6ZW36KuH57Wx542o6LO05riF5b6377ya57Wx5LiA5bCx5YOP5pav5pav5pyJ5YWpCgoKIyMg5YmN5b6M5LqU5YCL5a2X5b2ZCmBgYHtyfQpuZ3JhbV8xMSA8LSBmdW5jdGlvbih0KSB7CiAgbGFwcGx5KHQsIGZ1bmN0aW9uKHgpIHsKICAgIHRva2VucyA8LSBzZWdtZW50KHgsIGppZWJhX3Rva2VuaXplcikKICAgIG5ncmFtIDwtIG5ncmFtcyh0b2tlbnMsIDExKQogICAgbmdyYW0gPC0gbGFwcGx5KG5ncmFtLCBwYXN0ZSwgY29sbGFwc2UgPSAiICIpCiAgICB1bmxpc3QobmdyYW0pCiAgfSkKfQpgYGAKCmBgYHtyfQpnX25ncmFtXzExIDwtIGdfY3N2ICU+JQogIHNlbGVjdChhcnRVcmwsIHNlbnRlbmNlKSAlPiUKICB1bm5lc3RfdG9rZW5zKG5ncmFtLCBzZW50ZW5jZSwgdG9rZW4gPSBuZ3JhbV8xMSkgJT4lCiAgZmlsdGVyKCFzdHJfZGV0ZWN0KG5ncmFtLCByZWdleCgiWzAtOWEtekEtWl0iKSkpCmdfbmdyYW1fMTEKYGBgCgpgYGB7cn0KZ19uZ3JhbXNfMTFfc2VwYXJhdGVkIDwtIGdfbmdyYW1fMTEgJT4lCiAgc2VwYXJhdGUobmdyYW0sIHBhc3RlMCgid29yZCIsIGMoMToxMSksc2VwPSIiKSwgc2VwID0gIiAiKQpnX25ncmFtc18xMV9zZXBhcmF0ZWQKYGBgCgpgYGB7cn0KZ19jaGVja193b3JkcyA8LSBnX25ncmFtc18xMV9zZXBhcmF0ZWQgJT4lCiAgZmlsdGVyKCh3b3JkNj09Iue1seS/g+m7qCIpKQpnX2NoZWNrX3dvcmRzCmBgYAoKYGBge3J9CmdfY2hlY2tfd29yZHNfY291bnQgPC0gZ19jaGVja193b3JkcyAlPiUKICBtZWx0KGlkLnZhcnMgPSAiYXJ0VXJsIiwgbWVhc3VyZS52YXJzID0gcGFzdGUwKCJ3b3JkIiwgYygxOjExKSxzZXA9IiIpKSAlPiUKICByZW5hbWUod29yZD12YWx1ZSkgJT4lCiAgZmlsdGVyKHZhcmlhYmxlIT0id29yZDYiKSAlPiUKICBmaWx0ZXIoISh3b3JkICVpbiUgc3RvcF93b3JkcyksIG5jaGFyKHdvcmQpPjEpICU+JQogIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFKQpnX2NoZWNrX3dvcmRzX2NvdW50CmBgYAoKYGBge3J9CmdfY2hlY2tfd29yZHNfY291bnQgJT4lCiAgYXJyYW5nZShkZXNjKGFicyhuKSkpICU+JQogIGhlYWQoMTUpICU+JQogIG11dGF0ZSh3b3JkID0gcmVvcmRlcih3b3JkLCBuKSkgJT4lCiAgZ2dwbG90KGFlcyh3b3JkLCBuLCBmaWxsID0gbiA+IDApKSArCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIHhsYWIoIldvcmRzIG5lYXIgYnkgXCLppKjplbdcIiIpICsKICB5bGFiKCJXb3JkIGNvdW50IikgKwogIGNvb3JkX2ZsaXAoKSsgCiAgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChmYW1pbHkgPSAiSGVpdGkgVEMgTGlnaHQiKSkKYGBgCgoKIyMgV29yZCBDb3JyZWxhdGlvbgoKYGBge3J9Cmdfd29yZHNfYnlfYXJ0IDwtIGdfY3N2ICU+JQogIHVubmVzdF90b2tlbnMod29yZCwgc2VudGVuY2UsIHRva2VuPWdfdG9rZW5pemVyKSAlPiUKICBmaWx0ZXIoIXN0cl9kZXRlY3Qod29yZCwgcmVnZXgoIlswLTldIikpKSAlPiUKICBjb3VudChhcnRVcmwsIHdvcmQsIHNvcnQgPSBUUlVFKQpnX3dvcmRzX2J5X2FydApgYGAKCmBgYHtyfQpnX3dvcmRfcGFpcnMgPC0gZ193b3Jkc19ieV9hcnQgJT4lCiAgcGFpcndpc2VfY291bnQod29yZCwgYXJ0VXJsLCBzb3J0ID0gVFJVRSkKZ193b3JkX3BhaXJzCmBgYAoKYGBge3J9Cmdfd29yZF9jb3JzIDwtIGdfd29yZHNfYnlfYXJ0ICU+JQogIGdyb3VwX2J5KHdvcmQpICU+JQogIGZpbHRlcihuKCkgPj0gMjApICU+JQogIHBhaXJ3aXNlX2Nvcih3b3JkLCBhcnRVcmwsIHNvcnQgPSBUUlVFKQpnX3dvcmRfY29ycwpgYGAKCmBgYHtyfQpnX3dvcmRfY29ycyAlPiUKICBmaWx0ZXIoaXRlbTEgPT0gIumkqOmVtyIpCmBgYAoKYGBge3J9CnNldC5zZWVkKDIwMTkpCmdfd29yZF9jb3JzICU+JQogIGZpbHRlcihjb3JyZWxhdGlvbiA+IC40KSAlPiUKICBncmFwaF9mcm9tX2RhdGFfZnJhbWUoKSAlPiUKICBnZ3JhcGgobGF5b3V0ID0gImZyIikgKwogIGdlb21fZWRnZV9saW5rKGFlcyhlZGdlX2FscGhhID0gY29ycmVsYXRpb24pLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgZ2VvbV9ub2RlX3BvaW50KGNvbG9yID0gImxpZ2h0Ymx1ZSIsIHNpemUgPSAzKSArCiAgZ2VvbV9ub2RlX3RleHQoYWVzKGxhYmVsID0gbmFtZSksIHJlcGVsID0gVFJVRSwgZmFtaWx5ID0gIkhlaXRpIFRDIExpZ2h0IikgKwogIHRoZW1lX3ZvaWQoKQpgYGAKCmBgYHtyfQojIOioreWumuW5vuWAi+ipnuWBmueIsnNlZWQgd29yZHMKc2VlZF93b3JkcyA8LSBjKCLmlrDogZ4iLCAi57ac5ZCIIiwgImFwcGxlZGFpbHkiKQojIOioreWumnRocmVzaG9sZOeIsjAuNjUKdGhyZXNob2xkIDwtIDAuNjUKIyDot59zZWVkIHdvcmRz55u46Zec5oCn6auY5pa8dGhyZXNob2xk55qE6Kme5b2Z5pyD6KKr5Yqg5YWl56e76Zmk5YiX6KGo5LitCnJlbW92ZV93b3JkcyA8LSBnX3dvcmRfY29ycyAlPiUKICAgICAgICAgICAgICAgIGZpbHRlcigoaXRlbTEgJWluJSBzZWVkX3dvcmRzfGl0ZW0yICVpbiUgc2VlZF93b3JkcyksIGNvcnJlbGF0aW9uPnRocmVzaG9sZCkgJT4lCiAgICAgICAgICAgICAgICAuJGl0ZW0xICU+JQogICAgICAgICAgICAgICAgdW5pcXVlKCkKcmVtb3ZlX3dvcmRzCmBgYAoKYGBge3J9CnNldC5zZWVkKDIwMTcpCmdfd29yZF9jb3JzX25ldyA8LSBnX3dvcmRfY29ycyAlPiUKICAgICAgICAgICAgICAgIGZpbHRlcighKGl0ZW0xICVpbiUgcmVtb3ZlX3dvcmRzfGl0ZW0yICVpbiUgcmVtb3ZlX3dvcmRzKSkKZ193b3JkX2NvcnNfbmV3ICU+JQogIGZpbHRlcihjb3JyZWxhdGlvbiA+IC40KSAlPiUKICBncmFwaF9mcm9tX2RhdGFfZnJhbWUoKSAlPiUKICBnZ3JhcGgobGF5b3V0ID0gImZyIikgKwogIGdlb21fZWRnZV9saW5rKGFlcyhlZGdlX2FscGhhID0gY29ycmVsYXRpb24pLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArIAogIGdlb21fbm9kZV9wb2ludChjb2xvciA9ICJsaWdodGJsdWUiLCBzaXplID0gMykgKwogIGdlb21fbm9kZV90ZXh0KGFlcyhsYWJlbCA9IG5hbWUpLCByZXBlbCA9IFRSVUUsIGZhbWlseSA9ICJIZWl0aSBUQyBMaWdodCIpICsKICB0aGVtZV92b2lkKCkKYGBgCgpgYGB7cn0KZ193b3Jkc190Zl9pZGYKYGBgCgpgYGB7cn0KdGVybV9hdmdfdGZpZGYgPSBnX3dvcmRzX3RmX2lkZiAlPiUgCiAgZ3JvdXBfYnkod29yZCkgJT4lIAogIHN1bW1hcmlzZSh0ZmlkZl9hdmcgPSBtZWFuKHRmX2lkZikpIAp0ZXJtX2F2Z190ZmlkZiAlPiUgYXJyYW5nZShkZXNjKHRmaWRmX2F2ZykpIApgYGAKCmBgYHtyfQp0ZXJtX2F2Z190ZmlkZiR0ZmlkZl9hdmcgJT4lIHN1bW1hcnkKYGBgCgpgYGB7cn0KdGVybV9yZW1vdmU9dGVybV9hdmdfdGZpZGYgJT4lICAKICBmaWx0ZXIodGZpZGZfYXZnPDAuMDMyNTI1NCkgJT4lIAogIC4kd29yZAp0ZXJtX3JlbW92ZSAlPiUgaGVhZApgYGAKCmBgYHtyfQpnX2R0bSA9IGdfd29yZHNfdGZfaWRmICU+JQogIGZpbHRlcighd29yZCAlaW4lIHRlcm1fcmVtb3ZlKSAlPiUKICBjYXN0X2R0bShkb2N1bWVudD1hcnRUaXRsZSx0ZXJtPXdvcmQsdmFsdWU9IG4pCmdfZHRtCmBgYAoKYGBge3J9CmdfZHRtX21hdHJpeCA9IGdfZHRtICU+JSBhcy5kYXRhLmZyYW1lLm1hdHJpeCAKZ19kdG1fbWF0cml4WzE6MTAsMToyMF0KYGBgCgpgYGB7cn0KbGlicmFyeShkb1BhcmFsbGVsKQpjbHVzdCA9IG1ha2VDbHVzdGVyKGRldGVjdENvcmVzKCkpCnJlZ2lzdGVyRG9QYXJhbGxlbChjbHVzdCk7IGdldERvUGFyV29ya2VycygpCmBgYAoKYGBge3J9CnQwID0gU3lzLnRpbWUoKQpkID0gZ19kdG1fbWF0cml4ICU+JQogIGRpc3QobWV0aG9kPSJldWNsaWRlYW4iKSAgI+atkOW8j+i3nembou+8jOeul+aWh+eroOiIh+aWh+eroOS5i+mWk+eahOi3nembogpTeXMudGltZSgpIC0gdDAKYGBgCgpgYGB7cn0KaGMgPSBoY2x1c3QoZCwgbWV0aG9kPSd3YXJkLkQnKSAgCnBsb3QoaGMsIGxhYmVscyA9IEZBTFNFKQpyZWN0LmhjbHVzdChoYywgayA9IDMsIGJvcmRlcj0icmVkIikKYGBgCgpgYGB7cn0Ka2cgPSBjdXRyZWUoaGMsIGsgPSAzKQpMID0gc3BsaXQoZ19kdG1fbWF0cml4LCBrZykKTCRgMWBbMToxMCwxOjEwXQpgYGAKCmBgYHtyfQpzYXBwbHkoTCwgZnVuY3Rpb24oeCkgeCU+JSBjb2xNZWFucyAlPiUgc29ydCAlPiUgdGFpbCAlPiUgbmFtZXMpCmBgYAoKYGBge3J9CiMgdDAgPSBTeXMudGltZSgpCiMgbiA9IDIwMDAgI27lgIvlrZcKIyB0c25lID0gZ19kdG1bLCAxOm5dICU+JSBhcy5kYXRhLmZyYW1lLm1hdHJpeCAlPiUKIyAgIHNjYWxlICU+JSB0ICU+JSBSdHNuZSgKIyAgICAgY2hlY2tfZHVwbGljYXRlcyA9IEZBTFNFLCB0aGV0YT0wLjAsIG1heF9pdGVyPTMyMDApCiMgU3lzLnRpbWUoKS10MApgYGAKCmBgYHtyfQojIFkgPSB0c25lJFkgICAgICAgICAgICAgICMgdFNORSBjb29yZGluYXRlcwojIGRfWSA9IGRpc3QoWSkgICAgICAgICAgICAgIyBkaXN0YW5jZSBtYXRyaXgKIyBoY19ZID0gaGNsdXN0KGRfWSApICAgICAgICAgICMgaGktY2x1c3RlcmluZwojIHBsb3QoaGNfWSxsYWJlbD1GKQojIHJlY3QuaGNsdXN0KGhjX1ksIGs9MTAsIGJvcmRlcj0icmVkIikKYGBgCgpgYGB7cn0KIyBLID0gMTAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBudW1iZXIgb2YgY2x1c3RlcnMKIyBnID0gY3V0cmVlKGhjX1ksSykgICAgICAgICAgICAgICAgIyBjdXQgaW50byBLIGNsdXN0ZXJzCiMgdGFibGUoZykgJT4lIGFzLnZlY3RvciAlPiUgc29ydCAgICMgc2l6ZXMgb2YgY2x1c3RlcnMKYGBgCgpgYGB7cn0KIyB3YyA9IGNvbF9zdW1zKGdfZHRtWywxOm5dKSAjbuWAi+WtlwojIGNvbG9ycyA9IGRpc3RpbmN0Q29sb3JQYWxldHRlKEspCiMgcG5nKCIuL2cucG5nIiwgd2lkdGg9MzIwMCwgaGVpZ2h0PTE4MDApI+i8uOWHuuWclueJh+WIsOi3r+W+keS4iwojIHRleHRwbG90KAojICAgWVssMV0sIFlbLDJdLCBjb2xuYW1lcyhnX2R0bSlbMTpuXSwgc2hvdz1GLAojICAgY29sPWNvbG9yc1tnXSwKIyAgIGNleD0gMC4zICsgMS4yNSAqIHNxcnQod2MvbWVhbih3YykpLAojICAgZm9udD0yLCBmYW1pbHkgPSAiSGVpdGkgVEMgTGlnaHQiKQojIGRldi5vZmYoKQpgYGAKCiFbXShnLnBuZyk=