系統參數設定

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"

載入套件

require(dplyr)
require(tidytext)
require(jiebaR)
require(gutenbergr)
library(stringr)
library(wordcloud2)
library(ggplot2)
library(tidyr)
library(scales)

自gutenberg下載文本資料(西遊記,23962),並移除空行

west = gutenberg_download(c(23962)) %>%
  filter(text != "")
# 修正自gutenberg 下載的文本,以印刷排版斷行,而非句號斷行所形成的斷詞異常
doc = paste0(west$text,collapse = "") #將text欄位的全部文字合併
docVector = unlist(strsplit(doc,"[。.]"), use.names=FALSE) #以全形或半形句號斷句
# 下行必須加上stringsAsFactors = F, 否則字串會被轉成Integer型態
# 後續的jiebar 斷詞時會有異常
west = data.frame(gutenberg_id = "23962", text = docVector, stringsAsFactors = F) #gutenberg_id換成自己的書本id

觀察文本資料,發現章節標題的格式為如下 “第一回 靈根育孕源流出 心性修持大道生”, “第六一回 豬八戒助力敗魔王 孫行者三調芭蕉扇” 至https://regex101.com/ ,進行正規表示式測試後 其樣式可表示為: “^( |000)第.回 {1}”

資料前處理

對資料表附加章節欄位

west = west %>% 
  mutate(chapter = cumsum(str_detect(west$text, regex("^( |\u3000)*第.*回 {1}"))))
west

檢查章節欄位是否正常

從文本可以知道共100回(第一回至第一○○回)

# 列印章節欄位的最大值
max(west$chapter)
[1] 100

初始化Jieba斷詞引擎

# 使用西遊記專有名詞字典
jieba_tokenizer <- worker(user="JourneyToTheWest.scel_2019-03-16_12_58_58.traditional.dict")
# 設定斷詞function
west_tokenizer <- function(t) {
  lapply(t, function(x) {
    tokens <- segment(x, jieba_tokenizer)
    return(tokens)
  })
}

進行斷詞

tokens <- west %>% unnest_tokens(word, text, token=west_tokenizer)
str(tokens)
'data.frame':   377057 obs. of  3 variables:
 $ gutenberg_id: chr  "23962" "23962" "23962" "23962" ...
 $ chapter     : int  1 1 1 1 1 1 1 1 1 1 ...
 $ word        : chr  "第一回" "靈根育孕" "源流" "出" ...

計算整本書斷詞後,各詞彙的出現次數

# 計算詞彙的出現次數,如果詞彙只有一個字則不列入計算
tokens_count <- tokens %>% 
  filter(nchar(.$word)>1) %>%
  group_by(word) %>% 
  summarise(sum = n()) %>% 
  filter(sum>10) %>%
  arrange(desc(sum))
# 印出最常見的20個詞彙
head(tokens_count, 20)

畫出文字雲

tokens_count %>% wordcloud2()

從文字雲的結果,可以大略感受到主角群的出現頻率高於其它詞彙

計算孫悟空在整本書中出現的頻率

孫悟空的等同詞有“石猴”、“美猴王”、“弼馬溫”、“齊天大聖”、“行者”、“妖猴”、“心猿”、“大師兄”

# 將悟空的等同詞保留成變數
wukong_alias = c("老孫", "孫悟空","悟空","孫大聖","石猴","美猴王","弼馬溫","齊天大聖","行者","妖猴","心猿","大師兄")
# 計算悟空在整本書中出現的頻率及比例
wukong_count = tokens %>% 
  filter(nchar(.$word)>1) %>%                     # 保留非空白資料
  mutate(total = nrow(tokens)) %>%                # 加入一個total (資料總列數)   
  group_by(word) %>% 
  filter(word %in% wukong_alias) %>%              # 保留悟空的等同詞
  summarise(count = n(), total = mean(total)) %>% # 計算群組內的數量;mean 函式無意義,僅用來讓total 出現在表格
  mutate(proportion = count / total) %>%          # 加入比例欄位
  arrange(desc(count))                            # 根據count欄位,由大至小排列
head(wukong_count, 20)
# 計算悟空在各章節中出現的頻率
wukong_plot = tokens %>% 
  filter(nchar(.$word)>1) %>%
  filter(word %in% wukong_alias) %>%
  group_by(chapter) %>%  
  summarise(count = n()) %>%
  ggplot(aes(x = chapter, y=count)) +
  geom_col() + 
  ggtitle("各章節的悟空出現總數") + 
  xlab("章節") + 
  ylab("悟空數量") +
  theme(text = element_text(family = "Heiti TC Light"))
wukong_plot

由上圖看來,悟空在整本書中出現的頻率亦算平均; 但有數個章節出現數量為0者:9, 10, 11, 13, 29回 其中9, 10, 11 在介紹三藏的出場 12 回有提及「行者」一詞,但並非是指悟空 文中悟空的代稱雖然大部份以「孫行者」,但偶爾也會以「行者」稱呼; 在本例中無法進行區分 在27、28回中,悟空被逐回花果山,因而29 回無描述悟空 上述特別長的部份為82回

探究82 回中,悟空出現較高的原因

# 計算82 回,悟空代稱的分佈
wukong_chapter82 = tokens %>% 
  filter(nchar(.$word)>1) %>%
  filter(word %in% wukong_alias) %>%
  group_by(chapter, word) %>%  
  filter(chapter == 82) %>%  
  summarise(count = n())
wukong_chapter82

第八十二回:姹女求陽,元神護道 內容為豬八戒打探到妖精與唐僧晚間成親,於是請大師兄前來相助,待悟空見到師父,師徒二人定下一條計策。 唐僧用悟空之計,邀妖精入後花園,摘下悟空所變紅桃奉與妖怪。妖怪不知實情,將悟空所變化的桃子吃下, 悟空入肚,迫使妖精送唐僧出洞,唐僧終於是逃脫虎口。因此此回悟空(行者)出現頻率最高。

##各章節長度,以語句數來計算
plot <- 
  bind_rows(
    west %>% 
      group_by(chapter) %>% 
      summarise(count = n(), type="sentences"),
    tokens %>% 
      group_by(chapter) %>% 
      summarise(count = n(), type="words")) %>% 
  group_by(type)%>%
  ggplot(aes(x = chapter, y=count, fill="type", color=factor(type))) +
  geom_line() + 
  ggtitle("各章節的句子總數") + 
  xlab("章節") + 
  ylab("句子數量") + 
  theme(text = element_text(family = "STKaiti"))#mac:STKaiti & window:Heiti TC Light
plot


準備西遊記的詞彙庫

自搜狗下載 西遊記詞彙庫: 安裝處理詞彙庫所需的套件

# 載入library
library(readr)
library(devtools)

# 解碼scel用
install_github("qinwf/cidian")
library(cidian)
# 簡體轉繁體套件
install_github("qinwf/ropencc")
library(ropencc)

上述指令執行時,發生下列異常 Loading required package: stringi Error in value[3L] : Package ‘stringi’ version 1.2.4 cannot be unloaded: Error in unloadNamespace(package) : namespace ‘stringi’ is imported by ‘tokenizers’ so cannot be unloaded

# 排除Package ‘stringi’ version 1.2.4 cannot be unloaded:
unloadNamespace("tidytext")
unloadNamespace("tokenizers")

library(cidian)

將自搜狗下載的詞彙檔(JourneyToTheWest.scel), 置於相同目錄

# 解碼scel檔案
decode_scel(scel = "./JourneyToTheWest.scel",cpp = TRUE)
# 讀取解碼後生成的詞庫檔案
scan(file="./JourneyToTheWest.scel_2019-03-16_12_58_58.dict",
      what=character(),nlines=50,sep='\n',
      encoding='utf-8',fileEncoding='utf-8')
dict <- read_file("./JourneyToTheWest.scel_2019-03-16_12_58_58.dict")
# 將簡體詞庫轉為繁體
cc <- converter(S2TW)
dict_trad <- cc[dict]
write_file(dict_trad, "./JourneyToTheWest.scel_2019-03-16_12_58_58.traditional.dict")
# 讀取轉換成繁體後的詞庫檔案
scan(file="./JourneyToTheWest.scel_2019-03-16_12_58_58.traditional.dict",
      what=character(),nlines=50,sep='\n',
      encoding='utf-8',fileEncoding='utf-8')
LS0tCnRpdGxlOiAiZ3V0ZW5iZXJnIOS4iueahOilv+mBiuiomOaWh+acrOWIhuaekCIKYXV0aG9yOiAiTjA0NDAyMDAxMiDmnZznkYvojLksIE4wNDQwMjAwMjYg5by16ZuF5am3LCBOMDY0MDIwMDE1IOmQmOaYjuW/lyxOMDY0MjIwMDA3IOmZs+aFp+WAqSwgTjA2NDIyMDAwOSDorJ3lh7HlqIEsIE4wNjQyMjAwMjYg5YqJ5b+X5pS/IgpkYXRlOiAiMjAxOS8wMy8xMiIKb3V0cHV0OiBodG1sX25vdGVib29rCmFic3RyYWN0OiDntZDlkIhqaWViYXLoiIdUaWR5IHRleHTlpZfku7bvvIzomZXnkIZHdXRlbmJlcmfkuIrnmoTkuK3mloflsI/oqqoKLS0tCgojIOezu+e1seWPg+aVuOioreWumgpgYGB7cn0KU3lzLnNldGxvY2FsZShjYXRlZ29yeSA9ICJMQ19BTEwiLCBsb2NhbGUgPSAiemhfVFcuVVRGLTgiKSAjIOmBv+WFjeS4reaWh+S6gueivApgYGAKCiMg6LyJ5YWl5aWX5Lu2CmBgYHtyfQpyZXF1aXJlKGRwbHlyKQpyZXF1aXJlKHRpZHl0ZXh0KQpyZXF1aXJlKGppZWJhUikKcmVxdWlyZShndXRlbmJlcmdyKQpsaWJyYXJ5KHN0cmluZ3IpCmxpYnJhcnkod29yZGNsb3VkMikKbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KHNjYWxlcykKYGBgCgojIOiHqmd1dGVuYmVyZ+S4i+i8ieaWh+acrOizh+aWmSjopb/pgYroqJjvvIwyMzk2MinvvIzkuKbnp7vpmaTnqbrooYwKYGBge3J9Cndlc3QgPSBndXRlbmJlcmdfZG93bmxvYWQoYygyMzk2MikpICU+JQogIGZpbHRlcih0ZXh0ICE9ICIiKQoKIyDkv67mraPoh6pndXRlbmJlcmcg5LiL6LyJ55qE5paH5pys77yM5Lul5Y2w5Yi35o6S54mI5pa36KGM77yM6ICM6Z2e5Y+l6Jmf5pa36KGM5omA5b2i5oiQ55qE5pa36Kme55Ww5bi4CmRvYyA9IHBhc3RlMCh3ZXN0JHRleHQsY29sbGFwc2UgPSAiIikgI+Wwh3RleHTmrITkvY3nmoTlhajpg6jmloflrZflkIjkvbUKZG9jVmVjdG9yID0gdW5saXN0KHN0cnNwbGl0KGRvYywiW+OAgi5dIiksIHVzZS5uYW1lcz1GQUxTRSkgI+S7peWFqOW9ouaIluWNiuW9ouWPpeiZn+aWt+WPpQojIOS4i+ihjOW/hemgiOWKoOS4inN0cmluZ3NBc0ZhY3RvcnMgPSBGLCDlkKbliYflrZfkuLLmnIPooqvovYnmiJBJbnRlZ2Vy5Z6L5oWLCiMg5b6M57qM55qEamllYmFyIOaWt+ipnuaZguacg+acieeVsOW4uAp3ZXN0ID0gZGF0YS5mcmFtZShndXRlbmJlcmdfaWQgPSAiMjM5NjIiLCB0ZXh0ID0gZG9jVmVjdG9yLCBzdHJpbmdzQXNGYWN0b3JzID0gRikgI2d1dGVuYmVyZ19pZOaPm+aIkOiHquW3seeahOabuOacrGlkCmBgYAo+IOingOWvn+aWh+acrOizh+aWme+8jOeZvOePvueroOevgOaomemhjOeahOagvOW8j+eCuuWmguS4iwo+ICLnrKzkuIDlm54g6Z2I5qC56IKy5a2V5rqQ5rWB5Ye644CA5b+D5oCn5L+u5oyB5aSn6YGT55SfIiwgIuesrOWFreS4gOWbniDosazlhavmiJLliqnlipvmlZfprZTnjovjgIDlravooYzogIXkuInoqr/oiq3olYnmiYciCj4g6IezaHR0cHM6Ly9yZWdleDEwMS5jb20vIO+8jOmAsuihjOato+imj+ihqOekuuW8j+a4rOippuW+jAo+IOWFtuaoo+W8j+WPr+ihqOekuueCujogIl4oIHxcdTMwMDApKuesrC4q5ZueIHsxfSIKCiMg6LOH5paZ5YmN6JmV55CGCiAg5bCN6LOH5paZ6KGo6ZmE5Yqg56ug56+A5qyE5L2NCmBgYHtyfQojIFx1MzAwMOeCuuepuuW9ouepuuagvAp3ZXN0ID0gd2VzdCAlPiUgCiAgbXV0YXRlKGNoYXB0ZXIgPSBjdW1zdW0oc3RyX2RldGVjdCh3ZXN0JHRleHQsIHJlZ2V4KCJeKCB8XHUzMDAwKSrnrKwuKuWbniB7MX0iKSkpKQoKd2VzdApgYGAKCiMg5qqi5p+l56ug56+A5qyE5L2N5piv5ZCm5q2j5bi4CiAg5b6e5paH5pys5Y+v5Lul55+l6YGT5YWxMTAw5Zue77yI56ys5LiA5Zue6Iez56ys5LiA4peL4peL5Zue77yJCmBgYHtyfQojIOWIl+WNsOeroOevgOashOS9jeeahOacgOWkp+WAvAptYXgod2VzdCRjaGFwdGVyKQpgYGAKCiMg5Yid5aeL5YyWSmllYmHmlrfoqZ7lvJXmk44KYGBge3J9CiMg5L2/55So6KW/6YGK6KiY5bCI5pyJ5ZCN6Kme5a2X5YW4CmppZWJhX3Rva2VuaXplciA8LSB3b3JrZXIodXNlcj0iSm91cm5leVRvVGhlV2VzdC5zY2VsXzIwMTktMDMtMTZfMTJfNThfNTgudHJhZGl0aW9uYWwuZGljdCIpCiMg6Kit5a6a5pa36KmeZnVuY3Rpb24Kd2VzdF90b2tlbml6ZXIgPC0gZnVuY3Rpb24odCkgewogIGxhcHBseSh0LCBmdW5jdGlvbih4KSB7CiAgICB0b2tlbnMgPC0gc2VnbWVudCh4LCBqaWViYV90b2tlbml6ZXIpCiAgICByZXR1cm4odG9rZW5zKQogIH0pCn0KYGBgCgojIOmAsuihjOaWt+ipngpgYGB7cn0KdG9rZW5zIDwtIHdlc3QgJT4lIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCwgdG9rZW49d2VzdF90b2tlbml6ZXIpCnN0cih0b2tlbnMpCmBgYAoKIyDoqIjnrpfmlbTmnKzmm7jmlrfoqZ7lvozvvIzlkIToqZ7lvZnnmoTlh7rnj77mrKHmlbgKYGBge3J9CiMg6KiI566X6Kme5b2Z55qE5Ye654++5qyh5pW477yM5aaC5p6c6Kme5b2Z5Y+q5pyJ5LiA5YCL5a2X5YmH5LiN5YiX5YWl6KiI566XCnRva2Vuc19jb3VudCA8LSB0b2tlbnMgJT4lIAogIGZpbHRlcihuY2hhciguJHdvcmQpPjEpICU+JQogIGdyb3VwX2J5KHdvcmQpICU+JSAKICBzdW1tYXJpc2Uoc3VtID0gbigpKSAlPiUgCiAgZmlsdGVyKHN1bT4xMCkgJT4lCiAgYXJyYW5nZShkZXNjKHN1bSkpCgojIOWNsOWHuuacgOW4uOimi+eahDIw5YCL6Kme5b2ZCmhlYWQodG9rZW5zX2NvdW50LCAyMCkKYGBgCgojIOeVq+WHuuaWh+Wtl+mbsgpgYGB7cn0KdG9rZW5zX2NvdW50ICU+JSB3b3JkY2xvdWQyKCkKYGBgCj4g5b6e5paH5a2X6Zuy55qE57WQ5p6c77yM5Y+v5Lul5aSn55Wl5oSf5Y+X5Yiw5Li76KeS576k55qE5Ye654++6aC7546H6auY5pa85YW25a6D6Kme5b2ZCgojIOioiOeul+Wtq+aCn+epuuWcqOaVtOacrOabuOS4reWHuuePvueahOmgu+eOhwogIOWtq+aCn+epuueahOetieWQjOipnuaciSLnn7PnjLQi44CBIue+jueMtOeOiyLjgIEi5by86aas5rqrIuOAgSLpvYrlpKnlpKfogZYi44CBIuihjOiAhSLjgIEi5aaW54y0IuOAgSLlv4PnjL8i44CBIuWkp+W4q+WFhCIKYGBge3J9CiMg5bCH5oKf56m655qE562J5ZCM6Kme5L+d55WZ5oiQ6K6K5pW4Cnd1a29uZ19hbGlhcyA9IGMoIuiAgeWtqyIsICLlravmgp/nqboiLCLmgp/nqboiLCLlravlpKfogZYiLCLnn7PnjLQiLCLnvo7njLTnjosiLCLlvLzppqzmuqsiLCLpvYrlpKnlpKfogZYiLCLooYzogIUiLCLlppbnjLQiLCLlv4PnjL8iLCLlpKfluKvlhYQiKQojIOioiOeul+aCn+epuuWcqOaVtOacrOabuOS4reWHuuePvueahOmgu+eOh+WPiuavlOS+iwp3dWtvbmdfY291bnQgPSB0b2tlbnMgJT4lIAogIGZpbHRlcihuY2hhciguJHdvcmQpPjEpICU+JSAgICAgICAgICAgICAgICAgICAgICMg5L+d55WZ6Z2e56m655m96LOH5paZCiAgbXV0YXRlKHRvdGFsID0gbnJvdyh0b2tlbnMpKSAlPiUgICAgICAgICAgICAgICAgIyDliqDlhaXkuIDlgIt0b3RhbCAo6LOH5paZ57i95YiX5pW4KSAgIAogIGdyb3VwX2J5KHdvcmQpICU+JSAKICBmaWx0ZXIod29yZCAlaW4lIHd1a29uZ19hbGlhcykgJT4lICAgICAgICAgICAgICAjIOS/neeVmeaCn+epuueahOetieWQjOipngogIHN1bW1hcmlzZShjb3VudCA9IG4oKSwgdG90YWwgPSBtZWFuKHRvdGFsKSkgJT4lICMg6KiI566X576k57WE5YWn55qE5pW46YeP77ybbWVhbiDlh73lvI/nhKHmhI/nvqnvvIzlg4XnlKjkvoborpN0b3RhbCDlh7rnj77lnKjooajmoLwKICBtdXRhdGUocHJvcG9ydGlvbiA9IGNvdW50IC8gdG90YWwpICU+JSAgICAgICAgICAjIOWKoOWFpeavlOS+i+ashOS9jQogIGFycmFuZ2UoZGVzYyhjb3VudCkpICAgICAgICAgICAgICAgICAgICAgICAgICAgICMg5qC55pOaY291bnTmrITkvY3vvIznlLHlpKfoh7PlsI/mjpLliJcKCmhlYWQod3Vrb25nX2NvdW50LCAyMCkKYGBgCgpgYGB7cn0KIyDoqIjnrpfmgp/nqbrlnKjlkITnq6Dnr4DkuK3lh7rnj77nmoTpoLvnjocKd3Vrb25nX3Bsb3QgPSB0b2tlbnMgJT4lIAogIGZpbHRlcihuY2hhciguJHdvcmQpPjEpICU+JQogIGZpbHRlcih3b3JkICVpbiUgd3Vrb25nX2FsaWFzKSAlPiUKICBncm91cF9ieShjaGFwdGVyKSAlPiUgIAogIHN1bW1hcmlzZShjb3VudCA9IG4oKSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gY2hhcHRlciwgeT1jb3VudCkpICsKICBnZW9tX2NvbCgpICsgCiAgZ2d0aXRsZSgi5ZCE56ug56+A55qE5oKf56m65Ye654++57i95pW4IikgKyAKICB4bGFiKCLnq6Dnr4AiKSArIAogIHlsYWIoIuaCn+epuuaVuOmHjyIpICsKICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJIZWl0aSBUQyBMaWdodCIpKQoKd3Vrb25nX3Bsb3QKYGBgCj4g55Sx5LiK5ZyW55yL5L6G77yM5oKf56m65Zyo5pW05pys5pu45Lit5Ye654++55qE6aC7546H5Lqm566X5bmz5Z2H77ybCj4g5L2G5pyJ5pW45YCL56ug56+A5Ye654++5pW46YeP54K6MOiAhe+8mjksIDEwLCAxMSwgMTMsIDI55ZueCj4g5YW25LitOSwgMTAsIDExIOWcqOS7i+e0ueS4ieiXj+eahOWHuuWgtAo+IDEyIOWbnuacieaPkOWPiuOAjOihjOiAheOAjeS4gOipnu+8jOS9huS4pumdnuaYr+aMh+aCn+epugo+IOaWh+S4reaCn+epuueahOS7o+eosemblueEtuWkp+mDqOS7veS7peOAjOWtq+ihjOiAheOAje+8jOS9huWBtueIvuS5n+acg+S7peOAjOihjOiAheOAjeeoseWRvO+8mwo+IOWcqOacrOS+i+S4reeEoeazlemAsuihjOWNgOWIhgo+IOWcqDI344CBMjjlm57kuK3vvIzmgp/nqbrooqvpgJDlm57oirHmnpzlsbHvvIzlm6DogIwyOSDlm57nhKHmj4/ov7Dmgp/nqboKPiDkuIrov7DnibnliKXplbfnmoTpg6jku73ngro4MuWbngoKIyDmjqLnqbY4MiDlm57kuK3vvIzmgp/nqbrlh7rnj77ovIPpq5jnmoTljp/lm6AKYGBge3J9CiMg6KiI566XODIg5Zue77yM5oKf56m65Luj56ix55qE5YiG5L2ICnd1a29uZ19jaGFwdGVyODIgPSB0b2tlbnMgJT4lIAogIGZpbHRlcihuY2hhciguJHdvcmQpPjEpICU+JQogIGZpbHRlcih3b3JkICVpbiUgd3Vrb25nX2FsaWFzKSAlPiUKICBncm91cF9ieShjaGFwdGVyLCB3b3JkKSAlPiUgIAogIGZpbHRlcihjaGFwdGVyID09IDgyKSAlPiUgIAogIHN1bW1hcmlzZShjb3VudCA9IG4oKSkKCnd1a29uZ19jaGFwdGVyODIKYGBgCj4g56ys5YWr5Y2B5LqM5Zue77ya5ae55aWz5rGC6Zm977yM5YWD56We6K236YGTCj4g5YWn5a6554K66LGs5YWr5oiS5omT5o6i5Yiw5aaW57K+6IiH5ZSQ5YOn5pma6ZaT5oiQ6Kaq77yM5pa85piv6KuL5aSn5bir5YWE5YmN5L6G55u45Yqp77yM5b6F5oKf56m66KaL5Yiw5bir54i277yM5bir5b6S5LqM5Lq65a6a5LiL5LiA5qKd6KiI562W44CCCj4g5ZSQ5YOn55So5oKf56m65LmL6KiI77yM6YKA5aaW57K+5YWl5b6M6Iqx5ZyS77yM5pGY5LiL5oKf56m65omA6K6K57SF5qGD5aWJ6IiH5aaW5oCq44CC5aaW5oCq5LiN55+l5a+m5oOF77yM5bCH5oKf56m65omA6K6K5YyW55qE5qGD5a2Q5ZCD5LiL77yMCj4g5oKf56m65YWl6IKa77yM6L+r5L2/5aaW57K+6YCB5ZSQ5YOn5Ye65rSe77yM5ZSQ5YOn57WC5pa85piv6YCD6ISr6JmO5Y+j44CC5Zug5q2k5q2k5Zue5oKf56m6KOihjOiAhSnlh7rnj77poLvnjofmnIDpq5jjgIIKCgpgYGB7cn0KIyPlkITnq6Dnr4DplbfluqbvvIzku6Xoqp7lj6XmlbjkvoboqIjnrpcKcGxvdCA8LSAKICBiaW5kX3Jvd3MoCiAgICB3ZXN0ICU+JSAKICAgICAgZ3JvdXBfYnkoY2hhcHRlcikgJT4lIAogICAgICBzdW1tYXJpc2UoY291bnQgPSBuKCksIHR5cGU9InNlbnRlbmNlcyIpLAogICAgdG9rZW5zICU+JSAKICAgICAgZ3JvdXBfYnkoY2hhcHRlcikgJT4lIAogICAgICBzdW1tYXJpc2UoY291bnQgPSBuKCksIHR5cGU9IndvcmRzIikpICU+JSAKICBncm91cF9ieSh0eXBlKSU+JQogIGdncGxvdChhZXMoeCA9IGNoYXB0ZXIsIHk9Y291bnQsIGZpbGw9InR5cGUiLCBjb2xvcj1mYWN0b3IodHlwZSkpKSArCiAgZ2VvbV9saW5lKCkgKyAKICBnZ3RpdGxlKCLlkITnq6Dnr4DnmoTlj6XlrZDnuL3mlbgiKSArIAogIHhsYWIoIueroOevgCIpICsgCiAgeWxhYigi5Y+l5a2Q5pW46YePIikgKyAKICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJTVEthaXRpIikpI21hYzpTVEthaXRpICYgd2luZG93OkhlaXRpIFRDIExpZ2h0CnBsb3QKYGBgCgoKLS0tLQojIOa6luWCmeilv+mBiuiomOeahOipnuW9meW6qwogIOiHquaQnOeLl+S4i+i8iSBb6KW/6YGK6KiY6Kme5b2Z5bqrXShodHRwOi8vd3ViaS5zb2dvdS5jb20vZGljdC9jZWxsLnBocD9pZD03MzAwNCk6CiAg5a6J6KOd6JmV55CG6Kme5b2Z5bqr5omA6ZyA55qE5aWX5Lu2CmBgYHtyfQojIOi8ieWFpWxpYnJhcnkKbGlicmFyeShyZWFkcikKbGlicmFyeShkZXZ0b29scykKCiMg6Kej56K8c2NlbOeUqAppbnN0YWxsX2dpdGh1YigicWlud2YvY2lkaWFuIikKbGlicmFyeShjaWRpYW4pCiMg57Ch6auU6L2J57mB6auU5aWX5Lu2Cmluc3RhbGxfZ2l0aHViKCJxaW53Zi9yb3BlbmNjIikKbGlicmFyeShyb3BlbmNjKQpgYGAKPiDkuIrov7DmjIfku6Tln7fooYzmmYLvvIznmbznlJ/kuIvliJfnlbDluLgKTG9hZGluZyByZXF1aXJlZCBwYWNrYWdlOiBzdHJpbmdpCkVycm9yIGluIHZhbHVlW1szTF1dKGNvbmQpIDogCiAgUGFja2FnZSDigJhzdHJpbmdp4oCZIHZlcnNpb24gMS4yLjQgY2Fubm90IGJlIHVubG9hZGVkOgogRXJyb3IgaW4gdW5sb2FkTmFtZXNwYWNlKHBhY2thZ2UpIDogbmFtZXNwYWNlIOKAmHN0cmluZ2nigJkgaXMgaW1wb3J0ZWQgYnkg4oCYdG9rZW5pemVyc+KAmSBzbyBjYW5ub3QgYmUgdW5sb2FkZWQKCmBgYHtyfQojIOaOkumZpFBhY2thZ2Ug4oCYc3RyaW5naeKAmSB2ZXJzaW9uIDEuMi40IGNhbm5vdCBiZSB1bmxvYWRlZDoKdW5sb2FkTmFtZXNwYWNlKCJ0aWR5dGV4dCIpCnVubG9hZE5hbWVzcGFjZSgidG9rZW5pemVycyIpCgpsaWJyYXJ5KGNpZGlhbikKYGBgCgojIOWwh+iHquaQnOeLl+S4i+i8ieeahOipnuW9meaqlChKb3VybmV5VG9UaGVXZXN0LnNjZWwpLCDnva7mlrznm7jlkIznm67pjIQKYGBge3J9CiMg6Kej56K8c2NlbOaqlOahiApkZWNvZGVfc2NlbChzY2VsID0gIi4vSm91cm5leVRvVGhlV2VzdC5zY2VsIixjcHAgPSBUUlVFKQpgYGAKCmBgYHtyfQojIOiugOWPluino+eivOW+jOeUn+aIkOeahOipnuW6q+aqlOahiApzY2FuKGZpbGU9Ii4vSm91cm5leVRvVGhlV2VzdC5zY2VsXzIwMTktMDMtMTZfMTJfNThfNTguZGljdCIsCiAgICAgIHdoYXQ9Y2hhcmFjdGVyKCksbmxpbmVzPTUwLHNlcD0nXG4nLAogICAgICBlbmNvZGluZz0ndXRmLTgnLGZpbGVFbmNvZGluZz0ndXRmLTgnKQpgYGAKCmBgYHtyfQpkaWN0IDwtIHJlYWRfZmlsZSgiLi9Kb3VybmV5VG9UaGVXZXN0LnNjZWxfMjAxOS0wMy0xNl8xMl81OF81OC5kaWN0IikKIyDlsIfnsKHpq5ToqZ7luqvovYnngrrnuYHpq5QKY2MgPC0gY29udmVydGVyKFMyVFcpCmRpY3RfdHJhZCA8LSBjY1tkaWN0XQp3cml0ZV9maWxlKGRpY3RfdHJhZCwgIi4vSm91cm5leVRvVGhlV2VzdC5zY2VsXzIwMTktMDMtMTZfMTJfNThfNTgudHJhZGl0aW9uYWwuZGljdCIpCmBgYAoKYGBge3J9CiMg6K6A5Y+W6L2J5o+b5oiQ57mB6auU5b6M55qE6Kme5bqr5qqU5qGICnNjYW4oZmlsZT0iLi9Kb3VybmV5VG9UaGVXZXN0LnNjZWxfMjAxOS0wMy0xNl8xMl81OF81OC50cmFkaXRpb25hbC5kaWN0IiwKICAgICAgd2hhdD1jaGFyYWN0ZXIoKSxubGluZXM9NTAsc2VwPSdcbicsCiAgICAgIGVuY29kaW5nPSd1dGYtOCcsZmlsZUVuY29kaW5nPSd1dGYtOCcpCmBgYAoKCg==