系統參數設定

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")
existing = as.character(installed.packages()[,1])
for(pkg in packages[!(packages %in% existing)]) install.packages(pkg)
require(dplyr)
require(tidytext)
require(jiebaR)
require(gutenbergr)
library(stringr)
library(wordcloud2)
library(ggplot2)
library(tidyr)
library(scales)

三國演義:https://www.gutenberg.org/ebooks/23950

# 下載 "三國演義" 書籍,並且將text欄位為空的行給清除,以及將重複的語句清除
three <- gutenberg_download(23950) %>% filter(text!="") %>% distinct(gutenberg_id, text)
doc = paste0(three$text,collapse = "") #將text欄位的全部文字合併
docVector = unlist(strsplit(doc,"[。.?!]"), use.names=FALSE) #以全形或半形句號斷句
three = data.frame(gutenberg_id = "23950" , text = docVector) #gutenberg_id換成自己的書本id
three$text <- as.character(three$text)
three$gutenberg_id <- as.integer(three$gutenberg_id)
View(three)

觀察資料我們可以發現,三國演義中每章皆以“第X回”為標題。
ex.第一回:宴桃園豪傑三結義,斬黃巾英雄首立功

# 根據上方整理出來的規則,我們可以使用正規表示式,將句子區分章節
three <- three %>% 
  mutate(chapter = cumsum(str_detect(three$text, regex("^第.*回.*"))))
str(three)
'data.frame':   29830 obs. of  3 variables:
 $ gutenberg_id: int  1 1 1 1 1 1 1 1 1 1 ...
 $ text        : chr  "第一回:宴桃園豪傑三結義,斬黃巾英雄首立功  詞曰:  滾滾長江東逝水,浪花淘盡英雄" "是非成敗轉頭空:青山依舊在,幾度夕陽紅" "白髮漁樵江渚上,慣看秋月春風" "一壺濁酒喜相逢:古今多少事,都付笑談中" ...
 $ chapter     : int  1 1 1 1 1 1 1 1 1 1 ...

使用搜狗詞彙庫,解碼並將簡體轉為繁體

# 安裝packages
packages = c("readr", "devtools", "stringi", "pbapply", "Rcpp", "RcppProgress")
existing = as.character(installed.packages()[,1])
for(pkg in packages[!(packages %in% existing)]) install.packages(pkg)
# 載入library
library(readr)
library(devtools)
# 解碼scel用
install_github("qinwf/cidian")
Skipping install of 'cidian' from a github remote, the SHA1 (834f0bd0) has not changed since last install.
  Use `force = TRUE` to force installation
library(cidian)
# 簡體轉繁體套件
install_github("qinwf/ropencc")
Skipping install of 'ropencc' from a github remote, the SHA1 (a5deb1fb) has not changed since last install.
  Use `force = TRUE` to force installation
library(ropencc)
# 解碼scel檔案
decode_scel(scel = "three.scel",cpp = TRUE)
output file: three.scel_2019-03-17_13_23_16.dict
# 讀取解碼後生成的詞庫檔案
scan(file="three.scel_2019-03-15_19_50_08.dict",
      what=character(),nlines=50,sep='\n',
      encoding='utf-8',fileEncoding='utf-8')
Read 50 items
 [1] "阿斗当皇帝软弱无能 n" "阿斗的江山白送 n"     "阿会喃 n"             "阿阳 n"              
 [5] "哀牢 n"               "艾县 n"               "安北将军 n"           "安城 n"              
 [9] "安次 n"               "安德 n"               "安定 n"               "安定郡 n"            
[13] "安东将军 n"           "安丰 n"               "安故 n"               "安广 n"              
[17] "安国 n"               "安汉 n"               "安乐 n"               "安乐公 n"            
[21] "安陵 n"               "安陆 n"               "安弥 n"               "安南将军 n"          
[25] "安平 n"               "安平国 n"             "安丘 n"               "安世 n"              
[29] "安市 n"               "安熹 n"               "安西将军 n"           "安阳 n"              
[33] "安夷 n"               "安邑 n"               "安远将军 n"           "安众 n"              
[37] "奥汀多赖把 n"         "鳌头两刃斧 n"         "媪围 n"               "巴郡 n"              
[41] "霸陵 n"               "八路诸侯 n"           "灞水 n"               "八校尉兵 n"          
[45] "拔用 n"               "霸者之威 n"           "白帝城托孤 n"         "白鹤 n"              
[49] "白虹 n"               "白虎银牙 n"          
dict <- read_file("three.scel_2019-03-15_19_50_08.dict")
# 將簡體詞庫轉為繁體
cc <- converter(S2TW)
dict_trad <- cc[dict]
write_file(dict_trad, "three.traditional.dict")
# 讀取轉換成繁體後的詞庫檔案
scan(file="/Users/kunhsiang/Desktop/three/three.traditional.dict",
      what=character(),nlines=50,sep='\n',
      encoding='utf-8',fileEncoding='utf-8')
Read 50 items
 [1] "阿斗當皇帝軟弱無能 n" "阿斗的江山白送 n"     "阿會喃 n"             "阿陽 n"              
 [5] "哀牢 n"               "艾縣 n"               "安北將軍 n"           "安城 n"              
 [9] "安次 n"               "安德 n"               "安定 n"               "安定郡 n"            
[13] "安東將軍 n"           "安豐 n"               "安故 n"               "安廣 n"              
[17] "安國 n"               "安漢 n"               "安樂 n"               "安樂公 n"            
[21] "安陵 n"               "安陸 n"               "安彌 n"               "安南將軍 n"          
[25] "安平 n"               "安平國 n"             "安丘 n"               "安世 n"              
[29] "安市 n"               "安熹 n"               "安西將軍 n"           "安陽 n"              
[33] "安夷 n"               "安邑 n"               "安遠將軍 n"           "安眾 n"              
[37] "奧汀多賴把 n"         "鰲頭兩刃斧 n"         "媼圍 n"               "巴郡 n"              
[41] "霸陵 n"               "八路諸侯 n"           "灞水 n"               "八校尉兵 n"          
[45] "拔用 n"               "霸者之威 n"           "白帝城託孤 n"         "白鶴 n"              
[49] "白虹 n"               "白虎銀牙 n"          

執行斷詞

# 使用三國演義專有名詞字典
jieba_tokenizer <- worker(user="three.traditional.dict", stop_word = "stop_words.txt")
# 設定斷詞function
three_tokenizer <- function(t) {
  lapply(t, function(x) {
    tokens <- segment(x, jieba_tokenizer)
    return(tokens)
  })
}
three_tokens <- three %>% unnest_tokens(word, text, token=three_tokenizer)
str(three_tokens)
'data.frame':   236150 obs. of  3 variables:
 $ gutenberg_id: int  1 1 1 1 1 1 1 1 1 1 ...
 $ chapter     : int  1 1 1 1 1 1 1 1 1 1 ...
 $ word        : chr  "第一回" "宴桃園豪傑三結義" "斬黃巾英雄首立功" "詞曰" ...
head(three_tokens, 20)

文字雲

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

比較主要人物各章節出現次數

因本文中對同一人有多種稱呼,ex.玄德、劉備,因此為了該名人物的次數是正確的, 此處將不同稱呼但為同一人,皆計算為同一人的出場次數。

tokens_shiuan_de <- three_tokens %>% 
  filter(.$word == "玄德" | .$word == "劉備") %>% 
  group_by(chapter) %>% 
  summarise(count = n()) %>% 
  mutate(word = "玄德")
tokens_kung_ming <- three_tokens %>% 
  filter(.$word == "孔明" | .$word == "諸葛亮") %>% 
  group_by(chapter) %>% 
  summarise(count = n()) %>% 
  mutate(word = "孔明")
tokens_tsau_tsau<- three_tokens %>% 
  filter(.$word == "曹操" | .$word == "孟德") %>% 
  group_by(chapter) %>% 
  summarise(count = n()) %>% 
  mutate(word = "曹操")
tokens_guan_gung<- three_tokens %>% 
  filter(.$word == "關公" | .$word == "雲長") %>% 
  group_by(chapter) %>% 
  summarise(count = n()) %>% 
  mutate(word = "關公")
tokens_jang_fei<- three_tokens %>% 
  filter(.$word == "張飛" | .$word == "翼德") %>% 
  group_by(chapter) %>% 
  summarise(count = n()) %>% 
  mutate(word = "張飛")

主要三人物

出現率最高的三人。

major_name_compare_plot <- 
  bind_rows(tokens_shiuan_de, tokens_kung_ming, tokens_tsau_tsau) %>%
  ggplot(aes(x = chapter, y=count, fill=word)) +
  geom_col(show.legend = F) +
  facet_wrap(~word, ncol = 1) + 
  ggtitle("「曹操」v.s.「孔明」v.s.「玄德」") + 
  xlab("章節") + 
  ylab("出現次數") + 
  theme(text = element_text(family = "Heiti TC Light"))
major_name_compare_plot

桃園三結義

著名的桃園三結義。

lgj_name_compare_plot <- 
  bind_rows(tokens_shiuan_de, tokens_guan_gung, tokens_jang_fei) %>%
  ggplot(aes(x = chapter, y=count, fill=word)) +
  geom_col(show.legend = F) +
  facet_wrap(~word, ncol = 1) + 
  ggtitle("「關公」v.s.「玄德」v.s.「張飛」") + 
  xlab("章節") + 
  ylab("出現次數") + 
  theme(text = element_text(family = "Heiti TC Light"))
lgj_name_compare_plot

各章節長度,以語句數來計算

ch_sentences_three <- three %>% 
  group_by(chapter) %>% 
  summarise(count = n(), type="sentences")
ch_word_three <- three_tokens %>% 
  group_by(chapter) %>% 
  summarise(count = n(), type="words")
three_length_plot <- 
  bind_rows(ch_sentences_three, ch_word_three) %>% 
  group_by(type)%>%
  ggplot(aes(x = chapter, y=count, fill="type", color=factor(type))) +
  geom_line() + 
  geom_vline(xintercept = 71, col='red', size = 0.2) + 
  ggtitle("各章節的句子和詞彙總數") + 
  xlab("章節") + 
  ylab("數量") + 
  theme(text = element_text(family = "Heiti TC Light"))
three_length_plot

比較

三國時期(西元184年~西元280年)共97年,三國演義前一百零三回述說了前51年,
而後的46年只短短的用十七回收場,比較前後的用詞有何差異。
計算 前一百零三回 和 後十七回 的詞彙在全文中出現比率的差異。

frequency <- three_tokens %>% mutate(part = ifelse(chapter<=103, "First 103", "Last 17")) %>%
  filter(nchar(.$word)>1) %>%
  mutate(word = str_extract(word, "[^0-9a-z']+")) %>%
  mutate(word = str_extract(word, "^[^一二三四五六七八九十]+")) %>%
  count(part, word) %>%
  group_by(part) %>%
  mutate(proportion = n / sum(n)) %>% 
  select(-n) %>% 
  spread(part, proportion) %>% 
  gather(part, proportion, `Last 17`)

匯出圖表

ggplot(frequency, aes(x = proportion, y = `First 103`, color = abs(`First 103` - proportion))) +
  geom_abline(color = "gray40", lty = 2) +
  geom_jitter(alpha = 0.1, size = 2, width = 0.3, height = 0.3, na.rm = T) +
  geom_text(aes(label = word), check_overlap = T, family = "Heiti TC Light", na.rm = T) +
  scale_x_log10(labels = percent_format()) +
  scale_y_log10(labels = percent_format()) +
  scale_color_gradient(limits = c(0, 0.001), low = "darkslategray4", high = "gray75") +
  theme(legend.position="none") +
  labs(y = "First 103", x = "Last 17")

LS0tCnRpdGxlOiAi57WQ5ZCIamllYmFy6IiHVGlkeSB0ZXh05aWX5Lu277yM5a+m5L2c5paH5a2X5YiG5p6QIgphdXRob3I6ICLpmbPnkKjnv5QiCmRhdGU6ICIyMDE5LzAzLzE1IgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdAogIGh0bWxfZG9jdW1lbnQ6IGRlZmF1bHQKICBwZGZfZG9jdW1lbnQ6IGRlZmF1bHQKYWJzdHJhY3Q6IOe1kOWQiGppZWJhcuiIh1RpZHkgdGV4dOWll+S7tu+8jOiZleeQhkd1dGVuYmVyZ+S4iueahOS4reaWh+Wwj+iqqu+8muS4ieWci+a8lOe+qQotLS0KIyDns7vntbHlj4PmlbjoqK3lrpoKYGBge3J9ClN5cy5zZXRsb2NhbGUoY2F0ZWdvcnkgPSAiTENfQUxMIiwgbG9jYWxlID0gInpoX1RXLlVURi04IikgIyDpgb/lhY3kuK3mlofkuoLnorwKYGBgCgojIyDlronoo53pnIDopoHnmoRwYWNrYWdlcwoKYGBge3J9CnBhY2thZ2VzID0gYygiZHBseXIiLCAidGlkeXRleHQiLCAiamllYmFSIiwgImd1dGVuYmVyZ3IiLCAic3RyaW5nciIsICJ3b3JkY2xvdWQyIiwgImdncGxvdDIiLCAidGlkeXIiLCAic2NhbGVzIikKZXhpc3RpbmcgPSBhcy5jaGFyYWN0ZXIoaW5zdGFsbGVkLnBhY2thZ2VzKClbLDFdKQpmb3IocGtnIGluIHBhY2thZ2VzWyEocGFja2FnZXMgJWluJSBleGlzdGluZyldKSBpbnN0YWxsLnBhY2thZ2VzKHBrZykKYGBgCgpgYGB7cn0KcmVxdWlyZShkcGx5cikKcmVxdWlyZSh0aWR5dGV4dCkKcmVxdWlyZShqaWViYVIpCnJlcXVpcmUoZ3V0ZW5iZXJncikKbGlicmFyeShzdHJpbmdyKQpsaWJyYXJ5KHdvcmRjbG91ZDIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeSh0aWR5cikKbGlicmFyeShzY2FsZXMpCmBgYAoKIyMg5LiJ5ZyL5ryU576p77yaaHR0cHM6Ly93d3cuZ3V0ZW5iZXJnLm9yZy9lYm9va3MvMjM5NTAKCmBgYHtyfQojIOS4i+i8iSAi5LiJ5ZyL5ryU576pIiDmm7jnsY3vvIzkuKbkuJTlsId0ZXh05qyE5L2N54K656m655qE6KGM57Wm5riF6Zmk77yM5Lul5Y+K5bCH6YeN6KSH55qE6Kqe5Y+l5riF6ZmkCnRocmVlIDwtIGd1dGVuYmVyZ19kb3dubG9hZCgyMzk1MCkgJT4lIGZpbHRlcih0ZXh0IT0iIikgJT4lIGRpc3RpbmN0KGd1dGVuYmVyZ19pZCwgdGV4dCkKZG9jID0gcGFzdGUwKHRocmVlJHRleHQsY29sbGFwc2UgPSAiIikgI+Wwh3RleHTmrITkvY3nmoTlhajpg6jmloflrZflkIjkvbUKZG9jVmVjdG9yID0gdW5saXN0KHN0cnNwbGl0KGRvYywiW+OAgi7vvJ/vvIFdIiksIHVzZS5uYW1lcz1GQUxTRSkgI+S7peWFqOW9ouaIluWNiuW9ouWPpeiZn+aWt+WPpQp0aHJlZSA9IGRhdGEuZnJhbWUoZ3V0ZW5iZXJnX2lkID0gIjIzOTUwIiAsIHRleHQgPSBkb2NWZWN0b3IpICNndXRlbmJlcmdfaWTmj5vmiJDoh6rlt7HnmoTmm7jmnKxpZAp0aHJlZSR0ZXh0IDwtIGFzLmNoYXJhY3Rlcih0aHJlZSR0ZXh0KQp0aHJlZSRndXRlbmJlcmdfaWQgPC0gYXMuaW50ZWdlcih0aHJlZSRndXRlbmJlcmdfaWQpClZpZXcodGhyZWUpCmBgYAoKPiDop4Dlr5/os4fmlpnmiJHlgJHlj6/ku6Xnmbznj77vvIzkuInlnIvmvJTnvqnkuK3mr4/nq6Dnmobku6Ui56ysWOWbniLngrrmqJnpoYzjgII8YnI+CmV4LuesrOS4gOWbnu+8muWutOahg+WckuixquWCkeS4iee1kOe+qe+8jOaWrOm7g+W3vuiLsembhOmmlueri+WKnwoKYGBge3J9CiMg5qC55pOa5LiK5pa55pW055CG5Ye65L6G55qE6KaP5YmH77yM5oiR5YCR5Y+v5Lul5L2/55So5q2j6KaP6KGo56S65byP77yM5bCH5Y+l5a2Q5Y2A5YiG56ug56+ACnRocmVlIDwtIHRocmVlICU+JSAKICBtdXRhdGUoY2hhcHRlciA9IGN1bXN1bShzdHJfZGV0ZWN0KHRocmVlJHRleHQsIHJlZ2V4KCJe56ysLirlm54uKiIpKSkpCmBgYAoKYGBge3J9CnN0cih0aHJlZSkKYGBgCgojIyDkvb/nlKjmkJzni5foqZ7lvZnluqvvvIzop6PnorzkuKblsIfnsKHpq5TovYnngrrnuYHpq5QKCmBgYHtyfQojIOWuieijnXBhY2thZ2VzCnBhY2thZ2VzID0gYygicmVhZHIiLCAiZGV2dG9vbHMiLCAic3RyaW5naSIsICJwYmFwcGx5IiwgIlJjcHAiLCAiUmNwcFByb2dyZXNzIikKZXhpc3RpbmcgPSBhcy5jaGFyYWN0ZXIoaW5zdGFsbGVkLnBhY2thZ2VzKClbLDFdKQpmb3IocGtnIGluIHBhY2thZ2VzWyEocGFja2FnZXMgJWluJSBleGlzdGluZyldKSBpbnN0YWxsLnBhY2thZ2VzKHBrZykKYGBgCgoKYGBge3J9CiMg6LyJ5YWlbGlicmFyeQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KGRldnRvb2xzKQoKIyDop6PnorxzY2Vs55SoCmluc3RhbGxfZ2l0aHViKCJxaW53Zi9jaWRpYW4iKQpsaWJyYXJ5KGNpZGlhbikKIyDnsKHpq5TovYnnuYHpq5TlpZfku7YKaW5zdGFsbF9naXRodWIoInFpbndmL3JvcGVuY2MiKQpsaWJyYXJ5KHJvcGVuY2MpCmBgYAoKYGBge3J9CiMg6Kej56K8c2NlbOaqlOahiApkZWNvZGVfc2NlbChzY2VsID0gInRocmVlLnNjZWwiLGNwcCA9IFRSVUUpCmBgYAoKYGBge3J9CiMg6K6A5Y+W6Kej56K85b6M55Sf5oiQ55qE6Kme5bqr5qqU5qGICnNjYW4oZmlsZT0idGhyZWUuc2NlbF8yMDE5LTAzLTE1XzE5XzUwXzA4LmRpY3QiLAogICAgICB3aGF0PWNoYXJhY3RlcigpLG5saW5lcz01MCxzZXA9J1xuJywKICAgICAgZW5jb2Rpbmc9J3V0Zi04JyxmaWxlRW5jb2Rpbmc9J3V0Zi04JykKYGBgCgpgYGB7cn0KZGljdCA8LSByZWFkX2ZpbGUoInRocmVlLnNjZWxfMjAxOS0wMy0xNV8xOV81MF8wOC5kaWN0IikKIyDlsIfnsKHpq5ToqZ7luqvovYnngrrnuYHpq5QKY2MgPC0gY29udmVydGVyKFMyVFcpCmRpY3RfdHJhZCA8LSBjY1tkaWN0XQp3cml0ZV9maWxlKGRpY3RfdHJhZCwgInRocmVlLnRyYWRpdGlvbmFsLmRpY3QiKQojIOiugOWPlui9ieaPm+aIkOe5gemrlOW+jOeahOipnuW6q+aqlOahiApzY2FuKGZpbGU9Ii9Vc2Vycy9rdW5oc2lhbmcvRGVza3RvcC90aHJlZS90aHJlZS50cmFkaXRpb25hbC5kaWN0IiwKICAgICAgd2hhdD1jaGFyYWN0ZXIoKSxubGluZXM9NTAsc2VwPSdcbicsCiAgICAgIGVuY29kaW5nPSd1dGYtOCcsZmlsZUVuY29kaW5nPSd1dGYtOCcpCmBgYAoKIyMjIOWft+ihjOaWt+ipngoKYGBge3J9CiMg5L2/55So5LiJ5ZyL5ryU576p5bCI5pyJ5ZCN6Kme5a2X5YW4CmppZWJhX3Rva2VuaXplciA8LSB3b3JrZXIodXNlcj0idGhyZWUudHJhZGl0aW9uYWwuZGljdCIsIHN0b3Bfd29yZCA9ICJzdG9wX3dvcmRzLnR4dCIpCiMg6Kit5a6a5pa36KmeZnVuY3Rpb24KdGhyZWVfdG9rZW5pemVyIDwtIGZ1bmN0aW9uKHQpIHsKICBsYXBwbHkodCwgZnVuY3Rpb24oeCkgewogICAgdG9rZW5zIDwtIHNlZ21lbnQoeCwgamllYmFfdG9rZW5pemVyKQogICAgcmV0dXJuKHRva2VucykKICB9KQp9CmBgYAoKYGBge3J9CnRocmVlX3Rva2VucyA8LSB0aHJlZSAlPiUgdW5uZXN0X3Rva2Vucyh3b3JkLCB0ZXh0LCB0b2tlbj10aHJlZV90b2tlbml6ZXIpCnN0cih0aHJlZV90b2tlbnMpCmBgYAoKYGBge3J9CmhlYWQodGhyZWVfdG9rZW5zLCAyMCkKYGBgCgojIyDmloflrZfpm7IKCmBgYHtyfQojIOioiOeul+ipnuW9meeahOWHuuePvuasoeaVuO+8jOWmguaenOipnuW9meWPquacieS4gOWAi+Wtl+WJh+S4jeWIl+WFpeioiOeulwp0aHJlZV90b2tlbnNfY291bnQgPC0gdGhyZWVfdG9rZW5zICU+JSAKICBmaWx0ZXIobmNoYXIoLiR3b3JkKT4xKSAlPiUKICBncm91cF9ieSh3b3JkKSAlPiUgCiAgc3VtbWFyaXNlKHN1bSA9IG4oKSkgJT4lIAogIGZpbHRlcihzdW0+MTApICU+JQogIGFycmFuZ2UoZGVzYyhzdW0pKQoKIyDljbDlh7rmnIDluLjopovnmoQyMOWAi+ipnuW9mQpoZWFkKHRocmVlX3Rva2Vuc19jb3VudCwgMjApCmBgYAoKYGBge3J9CnRocmVlX3Rva2Vuc19jb3VudCAlPiUgd29yZGNsb3VkMigpCmBgYAoKIyMg5q+U6LyD5Li76KaB5Lq654mp5ZCE56ug56+A5Ye654++5qyh5pW4Cj4g5Zug5pys5paH5Lit5bCN5ZCM5LiA5Lq65pyJ5aSa56iu56ix5ZG877yMZXgu546E5b6344CB5YqJ5YKZ77yM5Zug5q2k54K65LqG6Kmy5ZCN5Lq654mp55qE5qyh5pW45piv5q2j56K655qE77yMCuatpOiZleWwh+S4jeWQjOeoseWRvOS9hueCuuWQjOS4gOS6uu+8jOeahuioiOeul+eCuuWQjOS4gOS6uueahOWHuuWgtOasoeaVuOOAggoKYGBge3J9CnRva2Vuc19zaGl1YW5fZGUgPC0gdGhyZWVfdG9rZW5zICU+JSAKICBmaWx0ZXIoLiR3b3JkID09ICLnjoTlvrciIHwgLiR3b3JkID09ICLlionlgpkiKSAlPiUgCiAgZ3JvdXBfYnkoY2hhcHRlcikgJT4lIAogIHN1bW1hcmlzZShjb3VudCA9IG4oKSkgJT4lIAogIG11dGF0ZSh3b3JkID0gIueOhOW+tyIpCgp0b2tlbnNfa3VuZ19taW5nIDwtIHRocmVlX3Rva2VucyAlPiUgCiAgZmlsdGVyKC4kd29yZCA9PSAi5a2U5piOIiB8IC4kd29yZCA9PSAi6Ku46JGb5LquIikgJT4lIAogIGdyb3VwX2J5KGNoYXB0ZXIpICU+JSAKICBzdW1tYXJpc2UoY291bnQgPSBuKCkpICU+JSAKICBtdXRhdGUod29yZCA9ICLlrZTmmI4iKQoKdG9rZW5zX3RzYXVfdHNhdTwtIHRocmVlX3Rva2VucyAlPiUgCiAgZmlsdGVyKC4kd29yZCA9PSAi5pu55pONIiB8IC4kd29yZCA9PSAi5a2f5b63IikgJT4lIAogIGdyb3VwX2J5KGNoYXB0ZXIpICU+JSAKICBzdW1tYXJpc2UoY291bnQgPSBuKCkpICU+JSAKICBtdXRhdGUod29yZCA9ICLmm7nmk40iKQoKdG9rZW5zX2d1YW5fZ3VuZzwtIHRocmVlX3Rva2VucyAlPiUgCiAgZmlsdGVyKC4kd29yZCA9PSAi6Zec5YWsIiB8IC4kd29yZCA9PSAi6Zuy6ZW3IikgJT4lIAogIGdyb3VwX2J5KGNoYXB0ZXIpICU+JSAKICBzdW1tYXJpc2UoY291bnQgPSBuKCkpICU+JSAKICBtdXRhdGUod29yZCA9ICLpl5zlhawiKQoKdG9rZW5zX2phbmdfZmVpPC0gdGhyZWVfdG9rZW5zICU+JSAKICBmaWx0ZXIoLiR3b3JkID09ICLlvLXpo5siIHwgLiR3b3JkID09ICLnv7zlvrciKSAlPiUgCiAgZ3JvdXBfYnkoY2hhcHRlcikgJT4lIAogIHN1bW1hcmlzZShjb3VudCA9IG4oKSkgJT4lIAogIG11dGF0ZSh3b3JkID0gIuW8temjmyIpCmBgYAoKIyMjIyDkuLvopoHkuInkurrniakKPiDlh7rnj77njofmnIDpq5jnmoTkuInkurrjgIIKCmBgYHtyfQptYWpvcl9uYW1lX2NvbXBhcmVfcGxvdCA8LSAKICBiaW5kX3Jvd3ModG9rZW5zX3NoaXVhbl9kZSwgdG9rZW5zX2t1bmdfbWluZywgdG9rZW5zX3RzYXVfdHNhdSkgJT4lCiAgZ2dwbG90KGFlcyh4ID0gY2hhcHRlciwgeT1jb3VudCwgZmlsbD13b3JkKSkgKwogIGdlb21fY29sKHNob3cubGVnZW5kID0gRikgKwogIGZhY2V0X3dyYXAofndvcmQsIG5jb2wgPSAxKSArIAogIGdndGl0bGUoIuOAjOabueaTjeOAjXYucy7jgIzlrZTmmI7jgI12LnMu44CM546E5b6344CNIikgKyAKICB4bGFiKCLnq6Dnr4AiKSArIAogIHlsYWIoIuWHuuePvuasoeaVuCIpICsgCiAgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChmYW1pbHkgPSAiSGVpdGkgVEMgTGlnaHQiKSkKbWFqb3JfbmFtZV9jb21wYXJlX3Bsb3QKYGBgCgojIyMjIOahg+WckuS4iee1kOe+qQo+IOiRl+WQjeeahOahg+WckuS4iee1kOe+qeOAggoKYGBge3J9Cmxnal9uYW1lX2NvbXBhcmVfcGxvdCA8LSAKICBiaW5kX3Jvd3ModG9rZW5zX3NoaXVhbl9kZSwgdG9rZW5zX2d1YW5fZ3VuZywgdG9rZW5zX2phbmdfZmVpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBjaGFwdGVyLCB5PWNvdW50LCBmaWxsPXdvcmQpKSArCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGKSArCiAgZmFjZXRfd3JhcCh+d29yZCwgbmNvbCA9IDEpICsgCiAgZ2d0aXRsZSgi44CM6Zec5YWs44CNdi5zLuOAjOeOhOW+t+OAjXYucy7jgIzlvLXpo5vjgI0iKSArIAogIHhsYWIoIueroOevgCIpICsgCiAgeWxhYigi5Ye654++5qyh5pW4IikgKyAKICB0aGVtZSh0ZXh0ID0gZWxlbWVudF90ZXh0KGZhbWlseSA9ICJIZWl0aSBUQyBMaWdodCIpKQpsZ2pfbmFtZV9jb21wYXJlX3Bsb3QKYGBgCgojIyDlkITnq6Dnr4DplbfluqbvvIzku6Xoqp7lj6XmlbjkvoboqIjnrpcKCmBgYHtyfQpjaF9zZW50ZW5jZXNfdGhyZWUgPC0gdGhyZWUgJT4lIAogIGdyb3VwX2J5KGNoYXB0ZXIpICU+JSAKICBzdW1tYXJpc2UoY291bnQgPSBuKCksIHR5cGU9InNlbnRlbmNlcyIpCgpjaF93b3JkX3RocmVlIDwtIHRocmVlX3Rva2VucyAlPiUgCiAgZ3JvdXBfYnkoY2hhcHRlcikgJT4lIAogIHN1bW1hcmlzZShjb3VudCA9IG4oKSwgdHlwZT0id29yZHMiKQoKdGhyZWVfbGVuZ3RoX3Bsb3QgPC0gCiAgYmluZF9yb3dzKGNoX3NlbnRlbmNlc190aHJlZSwgY2hfd29yZF90aHJlZSkgJT4lIAogIGdyb3VwX2J5KHR5cGUpJT4lCiAgZ2dwbG90KGFlcyh4ID0gY2hhcHRlciwgeT1jb3VudCwgZmlsbD0idHlwZSIsIGNvbG9yPWZhY3Rvcih0eXBlKSkpICsKICBnZW9tX2xpbmUoKSArIAogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IDcxLCBjb2w9J3JlZCcsIHNpemUgPSAwLjIpICsgCiAgZ2d0aXRsZSgi5ZCE56ug56+A55qE5Y+l5a2Q5ZKM6Kme5b2Z57i95pW4IikgKyAKICB4bGFiKCLnq6Dnr4AiKSArIAogIHlsYWIoIuaVuOmHjyIpICsgCiAgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChmYW1pbHkgPSAiSGVpdGkgVEMgTGlnaHQiKSkKdGhyZWVfbGVuZ3RoX3Bsb3QKYGBgCgojIyDmr5TovIMKPiDkuInlnIvmmYLmnJ8o6KW/5YWDMTg05bm0772e6KW/5YWDMjgw5bm0KeWFsTk35bm077yM5LiJ5ZyL5ryU576p5YmN5LiA55m+6Zu25LiJ5Zue6L+w6Kqq5LqG5YmNNTHlubTvvIw8YnI+CuiAjOW+jOeahDQ25bm05Y+q55+t55+t55qE55So5Y2B5LiD5Zue5pS25aC077yM5q+U6LyD5YmN5b6M55qE55So6Kme5pyJ5L2V5beu55Ww44CCPGJyPgo+IOioiOeulyDliY3kuIDnmb7pm7bkuInlm54g5ZKMIOW+jOWNgeS4g+WbniDnmoToqZ7lvZnlnKjlhajmlofkuK3lh7rnj77mr5TnjofnmoTlt67nlbDjgIIKCmBgYHtyfQpmcmVxdWVuY3kgPC0gdGhyZWVfdG9rZW5zICU+JSBtdXRhdGUocGFydCA9IGlmZWxzZShjaGFwdGVyPD0xMDMsICJGaXJzdCAxMDMiLCAiTGFzdCAxNyIpKSAlPiUKICBmaWx0ZXIobmNoYXIoLiR3b3JkKT4xKSAlPiUKICBtdXRhdGUod29yZCA9IHN0cl9leHRyYWN0KHdvcmQsICJbXjAtOWEteiddKyIpKSAlPiUKICBtdXRhdGUod29yZCA9IHN0cl9leHRyYWN0KHdvcmQsICJeW17kuIDkuozkuInlm5vkupTlha3kuIPlhavkuZ3ljYFdKyIpKSAlPiUKICBjb3VudChwYXJ0LCB3b3JkKSAlPiUKICBncm91cF9ieShwYXJ0KSAlPiUKICBtdXRhdGUocHJvcG9ydGlvbiA9IG4gLyBzdW0obikpICU+JSAKICBzZWxlY3QoLW4pICU+JSAKICBzcHJlYWQocGFydCwgcHJvcG9ydGlvbikgJT4lIAogIGdhdGhlcihwYXJ0LCBwcm9wb3J0aW9uLCBgTGFzdCAxN2ApCmBgYAoKPiDljK/lh7rlnJbooagKCmBgYHtyfQpnZ3Bsb3QoZnJlcXVlbmN5LCBhZXMoeCA9IHByb3BvcnRpb24sIHkgPSBgRmlyc3QgMTAzYCwgY29sb3IgPSBhYnMoYEZpcnN0IDEwM2AgLSBwcm9wb3J0aW9uKSkpICsKICBnZW9tX2FibGluZShjb2xvciA9ICJncmF5NDAiLCBsdHkgPSAyKSArCiAgZ2VvbV9qaXR0ZXIoYWxwaGEgPSAwLjEsIHNpemUgPSAyLCB3aWR0aCA9IDAuMywgaGVpZ2h0ID0gMC4zLCBuYS5ybSA9IFQpICsKICBnZW9tX3RleHQoYWVzKGxhYmVsID0gd29yZCksIGNoZWNrX292ZXJsYXAgPSBULCBmYW1pbHkgPSAiSGVpdGkgVEMgTGlnaHQiLCBuYS5ybSA9IFQpICsKICBzY2FsZV94X2xvZzEwKGxhYmVscyA9IHBlcmNlbnRfZm9ybWF0KCkpICsKICBzY2FsZV95X2xvZzEwKGxhYmVscyA9IHBlcmNlbnRfZm9ybWF0KCkpICsKICBzY2FsZV9jb2xvcl9ncmFkaWVudChsaW1pdHMgPSBjKDAsIDAuMDAxKSwgbG93ID0gImRhcmtzbGF0ZWdyYXk0IiwgaGlnaCA9ICJncmF5NzUiKSArCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIikgKwogIGxhYnMoeSA9ICJGaXJzdCAxMDMiLCB4ID0gIkxhc3QgMTciKQpgYGAK