Ch.0 : 資料取得與載入

1. 資料取得及套件載入

載入的資料是由中山大學管理學院文字分析平台取得,在文件集部分選擇下載原始資料。

資料簡介

本資料內容為將PTT八卦板的文章,自 2020/01/01 到 2021/04/07 為止,透過文字分析平台進行關鍵字[水庫、水情、缺水]搜尋,共得到 645 篇文章。

Sys.setlocale(category = "LC_ALL", locale = "zh_TW.UTF-8") # 避免中文亂碼(Windows系統可將這行註解)

安裝需要的packages

packages = c("readr", "dplyr", "stringr", "jiebaR", "tidytext", "NLP", "readr", "tidyr", "ggplot2", "ggraph", "igraph", "scales", "reshape2", "widyr")
existing = as.character(installed.packages()[,1])
for(pkg in packages[!(packages %in% existing)]) install.packages(pkg)

載入需要的packages以及資料

require(readr)
require(dplyr)
require(stringr)
require(jiebaR)
require(tidytext)
require(NLP)
require(tidyr)
require(ggplot2)
require(ggraph)
require(igraph)
require(scales)
require(reshape2)
require(widyr)

載入自平台下載下來的資料

water <- read_csv("./ptt_gos_water_articleMetaData.csv") %>% 
  mutate(sentence=gsub("[\n]{2,}", "。", sentence)) %>% 
  mutate(sentence=gsub("\n", "", sentence)) %>% 
  mutate(sentence=gsub("http(s)?[-:\\/A-Za-z0-9\\.]+", " ", sentence))
Parsed with column specification:
cols(
  artTitle = col_character(),
  artDate = col_date(format = ""),
  artTime = col_time(format = ""),
  artUrl = col_character(),
  artPoster = col_character(),
  artCat = col_character(),
  commentNum = col_double(),
  push = col_double(),
  boo = col_double(),
  sentence = col_character()
)
water

資料欄位

  1. artTitle: 文章之標題,須注意不同文章可能會有完全相同的標題。
  2. artDate: 文章發佈之日期。
  3. artTime: 文章發佈之時間。
  4. artUrl: 文章之網址,每篇文章之網址為獨一無二的,可用來辨識相同標題之不同文章。
  5. artPoster: 發文者ID。
  6. artCat: 版別。
  7. commentNum: 回文數。
  8. push: 推文數。
  9. boo: 噓文數。
  10. sentence: 文章原文。

PTT articles example:
https://www.ptt.cc/bbs/Gossiping/M.1614606334.A.2DD.html https://www.ptt.cc/bbs/Gossiping/M.1614654780.A.7C6.html

台灣水庫即時水情:
https://water.taiwanstat.com/

移除PTT貼新聞時會出現的格式用字

water <- water %>% 
  mutate(sentence=gsub("媒體來源|記者署名|完整新聞標題|完整新聞內文|完整新聞連結|(或短網址)|備註|備註請放最後面|違者新聞文章刪除", "", sentence))

Ch.1 斷句

將文章原文根據規則進行斷句。

以標點符號進行斷句

sample_data <- water %>% head(2)
# 以全形或半形 驚歎號、問號、分號 以及 全形句號 爲依據進行斷句
sample_sentences <- strsplit(sample_data$sentence,"[。!;?!?;]{1,}")
# 回傳結果為list of vectors,每個vector的內容為每篇文章的斷句結果
sample_sentences
[[1]]
[1] "魯蛇家鄉最近停水好幾天引起民怨"                                                               
[2] "就查了最近的水情資料"                                                                         
[3] " 發現最近新竹以南水情不是很樂觀"                                                              
[4] "卻沒有中央官員出來呼籲要怎麼解決缺水問題"                                                     
[5] "反觀鳳梨被中國禁止進口, 中央從總統到行政院長各級長官,都站出來呼籲解決鳳梨問題"              
[6] "所以現在臺灣鳳梨比臺灣缺水誰重要"                                                             
[7] "沒有喝水就喝鳳梨汁是不錯的解決方案吧"                                                         
[8] "是否有專板本板並非萬能問板兩則本看板嚴格禁止政治問卦未滿30繁體中文字水桶3個月,嚴重者以鬧板論"

[[2]]
 [1] "新頭殼"                                                                                                                                                                                                                                                            
 [2] "顏得智"                                                                                                                                                                                                                                                            
 [3] "水情告急"                                                                                                                                                                                                                                                          
 [4] "新竹以南7水庫蓄水率陷10%保衛戰 春季雨量展望暫不樂觀"                                                                                                                                                                                                               
 [5] "水情告急"                                                                                                                                                                                                                                                          
 [6] "嘉義、台南地區於2月25日,水情燈號調整為減量供水的橙燈,截止至3月2日早上8時,新竹以南已有7座主要水庫,蓄水率逼近10%,新竹科學園區多間科技大廠也啟用水車載水確保產能,然而,根據中央氣象局上周公布的春季氣候展望,降雨的部分照目前預測來看為偏少到正常,水情仍不樂觀"
 [7] "截止至3月2日早上8時,新竹寶二水庫蓄水量12.9%、苗栗永和山水庫12.8%、苗栗明德水庫11.0%、鯉魚潭水庫15.8%、台中德基水庫12.3%"                                                                                                                                          
 [8] "新竹、苗栗、台中五座提供民生用水、農溉用水、工業用水的水庫,蓄水率皆逐漸逼近10%,此外,霧社水庫蓄水率10%、曾文水庫也僅有15.1%"                                                                                                                                     
 [9] "除蓄水率陷入10%保衛戰的7座水庫外,包含日月潭水庫、湖山水庫、仁義潭水庫、南化水庫、烏山頭水庫等雲、嘉、南、彰、投地區主要水庫,蓄水率大多也來到50%甚至以下"                                                                                                         
[10] "按照中央氣象局2月23日所發布的春季氣候展望,預估未來一季的氣溫接近正常,針對雨量部分,春雨預估為偏少到正常,缺水狀況短期難以立即改善,民眾仍須節約用水"                                                                                                             
[11] " 中南部肥宅動起來節約用水阿"                                                                                                                                                                                                                                       
[12] "  還看路邊一堆人在洗車"                                                                                                                                                                                                                                            
[13] "#首先R20重複用不要水洗"                                                                                                                                                                                                                                            
# unlist會將list中所有的vector展開成一個一維的vector
sentences <- unlist(sample_sentences)
sentences
 [1] "魯蛇家鄉最近停水好幾天引起民怨"                                                                                                                                                                                                                                    
 [2] "就查了最近的水情資料"                                                                                                                                                                                                                                              
 [3] " 發現最近新竹以南水情不是很樂觀"                                                                                                                                                                                                                                   
 [4] "卻沒有中央官員出來呼籲要怎麼解決缺水問題"                                                                                                                                                                                                                          
 [5] "反觀鳳梨被中國禁止進口, 中央從總統到行政院長各級長官,都站出來呼籲解決鳳梨問題"                                                                                                                                                                                   
 [6] "所以現在臺灣鳳梨比臺灣缺水誰重要"                                                                                                                                                                                                                                  
 [7] "沒有喝水就喝鳳梨汁是不錯的解決方案吧"                                                                                                                                                                                                                              
 [8] "是否有專板本板並非萬能問板兩則本看板嚴格禁止政治問卦未滿30繁體中文字水桶3個月,嚴重者以鬧板論"                                                                                                                                                                     
 [9] "新頭殼"                                                                                                                                                                                                                                                            
[10] "顏得智"                                                                                                                                                                                                                                                            
[11] "水情告急"                                                                                                                                                                                                                                                          
[12] "新竹以南7水庫蓄水率陷10%保衛戰 春季雨量展望暫不樂觀"                                                                                                                                                                                                               
[13] "水情告急"                                                                                                                                                                                                                                                          
[14] "嘉義、台南地區於2月25日,水情燈號調整為減量供水的橙燈,截止至3月2日早上8時,新竹以南已有7座主要水庫,蓄水率逼近10%,新竹科學園區多間科技大廠也啟用水車載水確保產能,然而,根據中央氣象局上周公布的春季氣候展望,降雨的部分照目前預測來看為偏少到正常,水情仍不樂觀"
[15] "截止至3月2日早上8時,新竹寶二水庫蓄水量12.9%、苗栗永和山水庫12.8%、苗栗明德水庫11.0%、鯉魚潭水庫15.8%、台中德基水庫12.3%"                                                                                                                                          
[16] "新竹、苗栗、台中五座提供民生用水、農溉用水、工業用水的水庫,蓄水率皆逐漸逼近10%,此外,霧社水庫蓄水率10%、曾文水庫也僅有15.1%"                                                                                                                                     
[17] "除蓄水率陷入10%保衛戰的7座水庫外,包含日月潭水庫、湖山水庫、仁義潭水庫、南化水庫、烏山頭水庫等雲、嘉、南、彰、投地區主要水庫,蓄水率大多也來到50%甚至以下"                                                                                                         
[18] "按照中央氣象局2月23日所發布的春季氣候展望,預估未來一季的氣溫接近正常,針對雨量部分,春雨預估為偏少到正常,缺水狀況短期難以立即改善,民眾仍須節約用水"                                                                                                             
[19] " 中南部肥宅動起來節約用水阿"                                                                                                                                                                                                                                       
[20] "  還看路邊一堆人在洗車"                                                                                                                                                                                                                                            
[21] "#首先R20重複用不要水洗"                                                                                                                                                                                                                                            

但在unlist後我們就沒辦法判別每個句子是出自於哪篇文章了,
因此需要一個方法在unlist的同時仍能夠保留句子是屬於哪篇文章

rep function

# rep(x, times = 1, length.out = NA, each = 1)
# 當給定兩個vector長度相同時,rep function會自動對齊兩個vector的值。
# 前面的vector決定要重複的值
# 後面的vector則決定要重複的次數
# ex.
rep(c("Social", "Media"), c(2,5))
[1] "Social" "Social" "Media"  "Media"  "Media"  "Media"  "Media" 
# rep function會自動對齊兩個vector,
# "Social" 會重複2次,"Media"會重複5次
# 原本資料的artUrl
sample_data$artUrl
[1] "https://www.ptt.cc/bbs/Gossiping/M.1614606334.A.2DD.html" "https://www.ptt.cc/bbs/Gossiping/M.1614654780.A.7C6.html"
# 回傳每篇文章的斷句後,包含了幾個句(vector的長度)
sapply(sample_sentences, length)
[1]  8 13
# 使用rep去配對原本資料的artUrl以及sapply的回傳(每篇文章包含了幾個句)
# 產生的長度會與 unlist(sample_sentences) 的長度一樣,
# 兩邊join起來就可以新增一個欄位代表每個句子來自哪篇文章
rep(sample_data$artUrl, sapply(sample_sentences, length))
 [1] "https://www.ptt.cc/bbs/Gossiping/M.1614606334.A.2DD.html" "https://www.ptt.cc/bbs/Gossiping/M.1614606334.A.2DD.html"
 [3] "https://www.ptt.cc/bbs/Gossiping/M.1614606334.A.2DD.html" "https://www.ptt.cc/bbs/Gossiping/M.1614606334.A.2DD.html"
 [5] "https://www.ptt.cc/bbs/Gossiping/M.1614606334.A.2DD.html" "https://www.ptt.cc/bbs/Gossiping/M.1614606334.A.2DD.html"
 [7] "https://www.ptt.cc/bbs/Gossiping/M.1614606334.A.2DD.html" "https://www.ptt.cc/bbs/Gossiping/M.1614606334.A.2DD.html"
 [9] "https://www.ptt.cc/bbs/Gossiping/M.1614654780.A.7C6.html" "https://www.ptt.cc/bbs/Gossiping/M.1614654780.A.7C6.html"
[11] "https://www.ptt.cc/bbs/Gossiping/M.1614654780.A.7C6.html" "https://www.ptt.cc/bbs/Gossiping/M.1614654780.A.7C6.html"
[13] "https://www.ptt.cc/bbs/Gossiping/M.1614654780.A.7C6.html" "https://www.ptt.cc/bbs/Gossiping/M.1614654780.A.7C6.html"
[15] "https://www.ptt.cc/bbs/Gossiping/M.1614654780.A.7C6.html" "https://www.ptt.cc/bbs/Gossiping/M.1614654780.A.7C6.html"
[17] "https://www.ptt.cc/bbs/Gossiping/M.1614654780.A.7C6.html" "https://www.ptt.cc/bbs/Gossiping/M.1614654780.A.7C6.html"
[19] "https://www.ptt.cc/bbs/Gossiping/M.1614654780.A.7C6.html" "https://www.ptt.cc/bbs/Gossiping/M.1614654780.A.7C6.html"
[21] "https://www.ptt.cc/bbs/Gossiping/M.1614654780.A.7C6.html"
data.frame(artUrl=rep(sample_data$artUrl, sapply(sample_sentences, length)),
           sentences = unlist(sample_sentences))

對全部的文章進行斷句,並儲存結果

# 以全形或半形 驚歎號、問號、分號 以及 全形句號 爲依據進行斷句
water_sentences <- strsplit(water$sentence,"[。!;?!?;]")
# 將每句句子,與他所屬的文章連結配對起來,整理成一個dataframe
water_sentences <- data.frame(
                        artUrl = rep(water$artUrl, sapply(water_sentences, length)), 
                        sentence = unlist(water_sentences)
                      ) %>%
                      filter(!str_detect(sentence, regex("^(\t|\n| )*$")))

water_sentences$sentence <- as.character(water_sentences$sentence)

water_sentences

Ch2. 斷詞

1.初始化斷詞器

# 使用默認參數初始化一個斷詞引擎
# 先不使用任何的字典和停用詞
jieba_tokenizer = worker()

chi_tokenizer <- function(t) {
  lapply(t, function(x) {
    if(nchar(x)>1){
      tokens <- segment(x, jieba_tokenizer)
      # 去掉字串長度爲1的詞彙
      tokens <- tokens[nchar(tokens)>1]
      return(tokens)
    }
  })
}

2.斷詞與整理斷詞結果

# 進行斷詞,並計算各詞彙在各文章中出現的次數
water_words <- water_sentences %>%
  unnest_tokens(word, sentence, token=chi_tokenizer) %>%
  filter(!str_detect(word, regex("[0-9a-zA-Z]"))) %>%
  count(artUrl, word, sort = TRUE)
water_words

Ch3. TF-IDF

1. 計算每篇文章的詞數

total_words <- water_words %>% 
  group_by(artUrl) %>% 
  summarize(total = sum(n))
`summarise()` ungrouping output (override with `.groups` argument)
total_words

2. 合併需要的資料欄位

# 合併 mask_words(每個詞彙在每個文章中出現的次數)
# 與 total_words(每篇文章的詞數)
# 新增各個詞彙在所有詞彙中的總數欄位
water_words <- left_join(water_words, total_words)
Joining, by = "artUrl"
water_words

3. 計算 tf-idf 值

# 以每篇文章爲單位,計算每個詞彙的 tf-idf 值
water_words_tf_idf <- water_words %>%
  bind_tf_idf(word, artUrl, n)
water_words_tf_idf

檢視結果

# 選出每篇文章,tf-idf值最大的五個詞
water_words_tf_idf %>% 
  group_by(artUrl) %>%
  slice_max(tf_idf, n=5) %>%
  arrange(desc(artUrl))

計算整個文集中較常 tf-idf 值高的字

# 從每篇文章挑選出tf-idf最大的十個詞,
# 並計算每個詞被選中的次數
water_words_tf_idf %>% 
  group_by(artUrl) %>%
  slice_max(tf_idf, n=10) %>%
  ungroup() %>%
  count(word, sort=TRUE)

Ch4. 透過結巴斷詞與N-gram幫助建立字典

jiebar and ngrams

# 使用結巴斷詞,並搭配NLP packages中的 ngrams function
# e.g.
tokens <- segment("中山資管全國第一", jieba_tokenizer)
tokens
[1] "中山" "資管" "全國" "第一"
bigram <- ngrams(tokens, 2)
bigram
[[1]]
[1] "中山" "資管"

[[2]]
[1] "資管" "全國"

[[3]]
[1] "全國" "第一"
# Combine each bigrams into a single string, with the " " as the seperater.
bigram <- lapply(bigram, paste, collapse = " ")
unlist(bigram)
[1] "中山 資管" "資管 全國" "全國 第一"

Bigram

bigram function

jieba_tokenizer = worker()

# unnest_tokens 使用的bigram分詞函數
# Input: a character vector
# Output: a list of character vectors of the same length
jieba_bigram <- function(t) {
  lapply(t, function(x) {
    if(nchar(x)>1){
      tokens <- segment(x, jieba_tokenizer)
      bigram<- ngrams(tokens, 2)
      bigram <- lapply(bigram, paste, collapse = " ")
      unlist(bigram)
    }
  })
}

jieba_bigram(c("中山資管全國第一", "我今天晚餐吃水餃"))
[[1]]
[1] "中山 資管" "資管 全國" "全國 第一"

[[2]]
[1] "我 今天"   "今天 晚餐" "晚餐 吃"   "吃 水餃"  

執行bigram分詞


water_bigram <- water %>%
  unnest_tokens(bigram, sentence, token = jieba_bigram)
water_bigram

統計最常出現的bigram組合

# 清除包含英文或數字的bigram組合
# 計算每個組合出現的次數
water_bigram %>%
  filter(!str_detect(bigram, regex("[0-9a-zA-Z]"))) %>%
  count(bigram, sort = TRUE)

Trigram

trigram function

jieba_trigram <- function(t) {
  lapply(t, function(x) {
    if(nchar(x)>1){
      tokens <- segment(x, jieba_tokenizer)
      ngram<- ngrams(unlist(tokens), 3)
      ngram <- lapply(ngram, paste, collapse = " ")
      unlist(ngram)
    }
  })
}

jieba_trigram(c("中山資管全國第一", "我今天晚餐吃水餃"))
[[1]]
[1] "中山 資管 全國" "資管 全國 第一"

[[2]]
[1] "我 今天 晚餐" "今天 晚餐 吃" "晚餐 吃 水餃"

執行trigram分詞

water_trigram <- water %>%
  unnest_tokens(ngrams, sentence, token = jieba_trigram)
water_trigram %>%
  filter(!str_detect(ngrams, regex("[0-9a-zA-Z]"))) %>%
  count(ngrams, sort = TRUE)

bigram和trigram的結果可以發現有很多包含stopwords的組合,所以我們接著將stopwords清除再看看有什麼新組合

Remove stop words

載入stop words字典

# load stop words
stop_words <- scan(file = "./dict/stop_words.txt", what=character(),sep='\n', 
                   encoding='utf-8',fileEncoding='utf-8')
Read 1211 items

Remove the stopwords in bigram

water_bigram %>%
  filter(!str_detect(bigram, regex("[0-9a-zA-Z]"))) %>%
  separate(bigram, c("word1", "word2"), sep = " ") %>% 
  filter(!(word1 %in% stop_words), !(word2 %in% stop_words)) %>%
  count(word1, word2, sort = TRUE) %>%
  unite_("bigram", c("word1","word2"), sep=" ")

Remove the stopwords in trigram

water_trigram %>%
  filter(!str_detect(ngrams, regex("[0-9a-zA-Z]"))) %>%
  separate(ngrams, c("word1", "word2", "word3"), sep = " ") %>% 
  filter(!(word1 %in% stop_words), !(word2 %in% stop_words), !(word3 %in% stop_words)) %>%
  count(word1, word2, word3, sort = TRUE) %>%
  unite_("ngrams", c("word1", "word2", "word3"), sep=" ")

從上面的 bigram 和 trigram 的結果中,我們可以看到有些字應該被組合在一起,我們以此來建立更好的斷詞字典。
我們將詞彙整理好存在 dict 文件夾中的 mask_lexicon.txt 中。

Ch5. 使用自建字典

載入自建字典

# load mask_lexicon
water_lexicon <- scan(file = "./dict/water_lexicon.txt", what=character(),sep='\n', 
                   encoding='utf-8',fileEncoding='utf-8',quiet = T)
# 自建水情相關字典
water_lexicon
 [1] "水利署"       "蓄水率"       "減壓供水"     "海水淡化廠"   "海水淡化"     "台中"         "柯文哲"       "超前部署"    
 [9] "民生用水"     "農業用水"     "北水南送"     "美濃水庫"     "南化水庫"     "阿公店水庫"   "烏山頭水庫"   "永和山水庫"  
[17] "鯉魚潭水庫"   "南區水資源局" "生態教育園區" "人工增雨"     "東北季風"     "自由時報"    

使用新字典建立斷詞器

jieba_tokenizer = worker()

# 使用疫情相關字典重新斷詞
new_user_word(jieba_tokenizer, c(water_lexicon))
[1] TRUE
chi_tokenizer <- function(t) {
  lapply(t, function(x) {
    if(nchar(x)>1){
      tokens <- segment(x, jieba_tokenizer)
      tokens <- tokens[!tokens %in% stop_words]
      # 去掉字串長度爲1的詞彙
      tokens <- tokens[nchar(tokens)>1]
      return(tokens)
    }
  })
}

Ch6. Word Correlation

使用自建辭典進行斷詞與計算

# 剛才的斷詞結果沒有使用新增的辭典,因此我們重新進行斷詞,再計算各詞彙在各文章中出現的次數
water_words <- water_sentences %>%
  unnest_tokens(word, sentence, token=chi_tokenizer) %>%
  filter(!str_detect(word, regex("[0-9a-zA-Z]"))) %>%
  count(artUrl, word, sort = TRUE)
water_words

計算兩個詞彙同時出現的總次數

# 過濾掉三個關鍵字"缺水", "水庫", "水情"
word_pairs <- water_words %>%
  pairwise_count(word, artUrl, sort = TRUE) %>% 
  filter(!item1 %in% c("缺水", "水庫", "水情") & !item2 %in% c("缺水", "水庫", "水情"))

word_pairs

計算兩個詞彙間的相關性

word_cors <- water_words %>%
  group_by(word) %>%
  filter(n() >= 10) %>%
  pairwise_cor(word, artUrl, sort = TRUE)

word_cors

視覺化

找出與 “石門水庫” “曾文水庫” 這兩個水庫相關性最高的 15 個詞彙

word_cors %>%
  filter(item1 %in% c("石門水庫", "曾文水庫")) %>%
  group_by(item1) %>%
  top_n(15) %>%
  ungroup() %>%
  mutate(item2 = reorder(item2, correlation)) %>%
  ggplot(aes(item2, correlation)) +
  geom_bar(stat = "identity") +
  facet_wrap(~ item1, scales = "free") +
  coord_flip()+ 
  theme(text = element_text(family = "Heiti TC Light")) #加入中文字型設定,避免中文字顯示錯誤。
Selecting by correlation

使用詞彙關係圖畫出相關性大於0.5的組合

set.seed(2020)

word_cors %>%
  filter(correlation > 0.5) %>%
  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()

使用詞彙關係圖畫出相關性大於0.6的組合

set.seed(2020)

word_cors %>%
  filter(correlation > 0.6) %>%
  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("看板", "中央社", "時報")
# 設定threshold爲0.6
threshold <- 0.6
# 跟seed words相關性高於threshold的詞彙會被加入移除列表中
remove_words <- word_cors %>%
                filter((item1 %in% seed_words|item2 %in% seed_words), correlation>threshold) %>%
                .$item1 %>%
                unique()
remove_words
[1] "日電"   "中央社" "編輯"   "問卦"   "嚴格"   "看板"   "禁止"   "水桶"  

使用詞彙關係圖畫出相關性大於0.6的組合

# 清除存在這些詞彙的組合
word_cors_new <- word_cors %>%
                filter(!(item1 %in% remove_words|item2 %in% remove_words))

word_cors_new %>%
  filter(correlation > 0.6) %>%
  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()

LS0tCnRpdGxlOiAi5YiG5p6QUFRU5YWr5Y2m54mI5rC05bqr55u46Zec5paH56ug5LmL6Kme5b2Z6Zec5L+CIgphdXRob3I6ICJLdW4tSHNpYW5nIENoZW4iCmRhdGU6ICIyMDIxLzA0LzA4IgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwogICAgaGlnaGxpZ2h0OiBweWdtZW50cwogICAgdGhlbWU6IGZsYXRseQogICAgY3NzOiBzdHlsZS5jc3MKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIGRmX3ByaW50OiBwYWdlZAotLS0KCiMgQ2guMCA6IOizh+aWmeWPluW+l+iIh+i8ieWFpQojIyAxLiDos4fmlpnlj5blvpflj4rlpZfku7bovInlhaUKPiDovInlhaXnmoTos4fmlpnmmK/nlLHkuK3lsbHlpKflrbjnrqHnkIblrbjpmaLmloflrZfliIbmnpDlubPlj7Dlj5blvpfvvIzlnKjmlofku7bpm4bpg6jliIbpgbjmk4fkuIvovInljp/lp4vos4fmlpnjgIIKCiMjIyDos4fmlpnnsKHku4sKPiDmnKzos4fmlpnlhaflrrnngrrlsIdQVFTlhavljabmnb/nmoTmlofnq6DvvIzoh6ogMjAyMC8wMS8wMSDliLAgMjAyMS8wNC8wNyDngrrmraLvvIzpgI/pgY7mloflrZfliIbmnpDlubPlj7DpgLLooYzpl5zpjbXlrZdb5rC05bqr44CB5rC05oOF44CB57y65rC0XeaQnOWwi++8jOWFseW+l+WIsCA2NDUg56+H5paH56ug44CCCgpgYGB7ciBlY2hvID0gVCwgcmVzdWx0cyA9ICdoaWRlJ30KU3lzLnNldGxvY2FsZShjYXRlZ29yeSA9ICJMQ19BTEwiLCBsb2NhbGUgPSAiemhfVFcuVVRGLTgiKSAjIOmBv+WFjeS4reaWh+S6gueivChXaW5kb3dz57O757Wx5Y+v5bCH6YCZ6KGM6Ki76KejKQpgYGAKCiMjIyDlronoo53pnIDopoHnmoRwYWNrYWdlcwpgYGB7cn0KcGFja2FnZXMgPSBjKCJyZWFkciIsICJkcGx5ciIsICJzdHJpbmdyIiwgImppZWJhUiIsICJ0aWR5dGV4dCIsICJOTFAiLCAicmVhZHIiLCAidGlkeXIiLCAiZ2dwbG90MiIsICJnZ3JhcGgiLCAiaWdyYXBoIiwgInNjYWxlcyIsICJyZXNoYXBlMiIsICJ3aWR5ciIpCmV4aXN0aW5nID0gYXMuY2hhcmFjdGVyKGluc3RhbGxlZC5wYWNrYWdlcygpWywxXSkKZm9yKHBrZyBpbiBwYWNrYWdlc1shKHBhY2thZ2VzICVpbiUgZXhpc3RpbmcpXSkgaW5zdGFsbC5wYWNrYWdlcyhwa2cpCmBgYAoKIyMjIOi8ieWFpemcgOimgeeahHBhY2thZ2Vz5Lul5Y+K6LOH5paZCmBgYHtyIGVjaG8gPSBULCByZXN1bHRzID0gJ2hpZGUnfQpyZXF1aXJlKHJlYWRyKQpyZXF1aXJlKGRwbHlyKQpyZXF1aXJlKHN0cmluZ3IpCnJlcXVpcmUoamllYmFSKQpyZXF1aXJlKHRpZHl0ZXh0KQpyZXF1aXJlKE5MUCkKcmVxdWlyZSh0aWR5cikKcmVxdWlyZShnZ3Bsb3QyKQpyZXF1aXJlKGdncmFwaCkKcmVxdWlyZShpZ3JhcGgpCnJlcXVpcmUoc2NhbGVzKQpyZXF1aXJlKHJlc2hhcGUyKQpyZXF1aXJlKHdpZHlyKQpgYGAKCiMjIyDovInlhaXoh6rlubPlj7DkuIvovInkuIvkvobnmoTos4fmlpkKYGBge3J9CndhdGVyIDwtIHJlYWRfY3N2KCIuL3B0dF9nb3Nfd2F0ZXJfYXJ0aWNsZU1ldGFEYXRhLmNzdiIpICU+JSAKICBtdXRhdGUoc2VudGVuY2U9Z3N1YigiW1xuXXsyLH0iLCAi44CCIiwgc2VudGVuY2UpKSAlPiUgCiAgbXV0YXRlKHNlbnRlbmNlPWdzdWIoIlxuIiwgIiIsIHNlbnRlbmNlKSkgJT4lIAogIG11dGF0ZShzZW50ZW5jZT1nc3ViKCJodHRwKHMpP1stOlxcL0EtWmEtejAtOVxcLl0rIiwgIiAiLCBzZW50ZW5jZSkpCgp3YXRlcgpgYGAKIyMjIOizh+aWmeashOS9jQo+IDEuIGFydFRpdGxlOiDmlofnq6DkuYvmqJnpoYzvvIzpoIjms6jmhI/kuI3lkIzmlofnq6Dlj6/og73mnIPmnInlrozlhajnm7jlkIznmoTmqJnpoYzjgIIKMi4gYXJ0RGF0ZTog5paH56ug55m85L2I5LmL5pel5pyf44CCCjMuIGFydFRpbWU6IOaWh+eroOeZvOS9iOS5i+aZgumWk+OAggo0LiBhcnRVcmw6IOaWh+eroOS5i+e2suWdgO+8jOavj+evh+aWh+eroOS5i+e2suWdgOeCuueNqOS4gOeEoeS6jOeahO+8jOWPr+eUqOS+hui+qOitmOebuOWQjOaomemhjOS5i+S4jeWQjOaWh+eroOOAggo1LiBhcnRQb3N0ZXI6IOeZvOaWh+iAhUlE44CCCjYuIGFydENhdDog54mI5Yil44CCCjcuIGNvbW1lbnROdW06IOWbnuaWh+aVuOOAggo4LiBwdXNoOiDmjqjmlofmlbjjgIIKOS4gYm9vOiDlmZPmlofmlbjjgIIKMTAuIHNlbnRlbmNlOiDmlofnq6Dljp/mlofjgIIKCj4gUFRUIGFydGljbGVzIGV4YW1wbGU6IDxicj4KIGh0dHBzOi8vd3d3LnB0dC5jYy9iYnMvR29zc2lwaW5nL00uMTYxNDYwNjMzNC5BLjJERC5odG1sCiBodHRwczovL3d3dy5wdHQuY2MvYmJzL0dvc3NpcGluZy9NLjE2MTQ2NTQ3ODAuQS43QzYuaHRtbAogCj4g5Y+w54Gj5rC05bqr5Y2z5pmC5rC05oOFOiA8YnI+CiBodHRwczovL3dhdGVyLnRhaXdhbnN0YXQuY29tLwoKIyMjIOenu+mZpFBUVOiyvOaWsOiBnuaZguacg+WHuuePvueahOagvOW8j+eUqOWtlwpgYGB7cn0Kd2F0ZXIgPC0gd2F0ZXIgJT4lIAogIG11dGF0ZShzZW50ZW5jZT1nc3ViKCLlqpLpq5TkvobmupB86KiY6ICF572y5ZCNfOWujOaVtOaWsOiBnuaomemhjHzlrozmlbTmlrDogZ7lhafmlod85a6M5pW05paw6IGe6YCj57WQfCjmiJbnn63ntrLlnYApfOWCmeiou3zlgpnoqLvoq4vmlL7mnIDlvozpnaJ86YGV6ICF5paw6IGe5paH56ug5Yiq6ZmkIiwgIiIsIHNlbnRlbmNlKSkKYGBgCgoKIyBDaC4xIOaWt+WPpQo+IOWwh+aWh+eroOWOn+aWh+agueaTmuimj+WJh+mAsuihjOaWt+WPpeOAggoKIyMg5Lul5qiZ6bue56ym6Jmf6YCy6KGM5pa35Y+lCmBgYHtyfQpzYW1wbGVfZGF0YSA8LSB3YXRlciAlPiUgaGVhZCgyKQojIOS7peWFqOW9ouaIluWNiuW9oiDpqZrmrY7omZ/jgIHllY/omZ/jgIHliIbomZ8g5Lul5Y+KIOWFqOW9ouWPpeiZnyDniLLkvp3mk5rpgLLooYzmlrflj6UKc2FtcGxlX3NlbnRlbmNlcyA8LSBzdHJzcGxpdChzYW1wbGVfZGF0YSRzZW50ZW5jZSwiW+OAgu+8ge+8m++8nyE/O117MSx9IikKIyDlm57lgrPntZDmnpzngrpsaXN0IG9mIHZlY3RvcnPvvIzmr4/lgIt2ZWN0b3LnmoTlhaflrrnngrrmr4/nr4fmlofnq6DnmoTmlrflj6XntZDmnpwKc2FtcGxlX3NlbnRlbmNlcwpgYGAKCmBgYHtyfQojIHVubGlzdOacg+Wwh2xpc3TkuK3miYDmnInnmoR2ZWN0b3LlsZXplovmiJDkuIDlgIvkuIDntq3nmoR2ZWN0b3IKc2VudGVuY2VzIDwtIHVubGlzdChzYW1wbGVfc2VudGVuY2VzKQpzZW50ZW5jZXMKYGBgCj4g5L2G5ZyodW5saXN05b6M5oiR5YCR5bCx5rKS6L6m5rOV5Yik5Yil5q+P5YCL5Y+l5a2Q5piv5Ye66Ieq5pa85ZOq56+H5paH56ug5LqG77yMPGJyPgogIOWboOatpOmcgOimgeS4gOWAi+aWueazleWcqHVubGlzdOeahOWQjOaZguS7jeiDveWkoOS/neeVmeWPpeWtkOaYr+WxrOaWvOWTquevh+aWh+eroAoKIyMgcmVwIGZ1bmN0aW9uCmBgYHtyfQojIHJlcCh4LCB0aW1lcyA9IDEsIGxlbmd0aC5vdXQgPSBOQSwgZWFjaCA9IDEpCiMg55W257Wm5a6a5YWp5YCLdmVjdG9y6ZW35bqm55u45ZCM5pmC77yMcmVwIGZ1bmN0aW9u5pyD6Ieq5YuV5bCN6b2K5YWp5YCLdmVjdG9y55qE5YC844CCCiMg5YmN6Z2i55qEdmVjdG9y5rG65a6a6KaB6YeN6KSH55qE5YC8CiMg5b6M6Z2i55qEdmVjdG9y5YmH5rG65a6a6KaB6YeN6KSH55qE5qyh5pW4CiMgZXguCnJlcChjKCJTb2NpYWwiLCAiTWVkaWEiKSwgYygyLDUpKQojIHJlcCBmdW5jdGlvbuacg+iHquWLleWwjem9iuWFqeWAi3ZlY3Rvcu+8jAojICJTb2NpYWwiIOacg+mHjeikhzLmrKHvvIwiTWVkaWEi5pyD6YeN6KSHNeasoQpgYGAKCmBgYHtyfQojIOWOn+acrOizh+aWmeeahGFydFVybApzYW1wbGVfZGF0YSRhcnRVcmwKYGBgCgpgYGB7cn0KIyDlm57lgrPmr4/nr4fmlofnq6DnmoTmlrflj6XlvozvvIzljIXlkKvkuoblub7lgIvlj6XvvIh2ZWN0b3LnmoTplbfluqbvvIkKc2FwcGx5KHNhbXBsZV9zZW50ZW5jZXMsIGxlbmd0aCkKYGBgCgpgYGB7cn0KIyDkvb/nlKhyZXDljrvphY3lsI3ljp/mnKzos4fmlpnnmoRhcnRVcmzku6Xlj4pzYXBwbHnnmoTlm57lgrPvvIjmr4/nr4fmlofnq6DljIXlkKvkuoblub7lgIvlj6XvvIkKIyDnlKLnlJ/nmoTplbfluqbmnIPoiIcgdW5saXN0KHNhbXBsZV9zZW50ZW5jZXMpIOeahOmVt+W6puS4gOaoo++8jAojIOWFqemCimpvaW7otbfkvoblsLHlj6/ku6XmlrDlop7kuIDlgIvmrITkvY3ku6Pooajmr4/lgIvlj6XlrZDkvoboh6rlk6rnr4fmlofnq6AKcmVwKHNhbXBsZV9kYXRhJGFydFVybCwgc2FwcGx5KHNhbXBsZV9zZW50ZW5jZXMsIGxlbmd0aCkpCmBgYAoKYGBge3J9CmRhdGEuZnJhbWUoYXJ0VXJsPXJlcChzYW1wbGVfZGF0YSRhcnRVcmwsIHNhcHBseShzYW1wbGVfc2VudGVuY2VzLCBsZW5ndGgpKSwKICAgICAgICAgICBzZW50ZW5jZXMgPSB1bmxpc3Qoc2FtcGxlX3NlbnRlbmNlcykpCmBgYAoKIyMg5bCN5YWo6YOo55qE5paH56ug6YCy6KGM5pa35Y+l77yM5Lim5YSy5a2Y57WQ5p6cCmBgYHtyfQojIOS7peWFqOW9ouaIluWNiuW9oiDpqZrmrY7omZ/jgIHllY/omZ/jgIHliIbomZ8g5Lul5Y+KIOWFqOW9ouWPpeiZnyDniLLkvp3mk5rpgLLooYzmlrflj6UKd2F0ZXJfc2VudGVuY2VzIDwtIHN0cnNwbGl0KHdhdGVyJHNlbnRlbmNlLCJb44CC77yB77yb77yfIT87XSIpCmBgYAoKYGBge3J9CiMg5bCH5q+P5Y+l5Y+l5a2Q77yM6IiH5LuW5omA5bGs55qE5paH56ug6YCj57WQ6YWN5bCN6LW35L6G77yM5pW055CG5oiQ5LiA5YCLZGF0YWZyYW1lCndhdGVyX3NlbnRlbmNlcyA8LSBkYXRhLmZyYW1lKAogICAgICAgICAgICAgICAgICAgICAgICBhcnRVcmwgPSByZXAod2F0ZXIkYXJ0VXJsLCBzYXBwbHkod2F0ZXJfc2VudGVuY2VzLCBsZW5ndGgpKSwgCiAgICAgICAgICAgICAgICAgICAgICAgIHNlbnRlbmNlID0gdW5saXN0KHdhdGVyX3NlbnRlbmNlcykKICAgICAgICAgICAgICAgICAgICAgICkgJT4lCiAgICAgICAgICAgICAgICAgICAgICBmaWx0ZXIoIXN0cl9kZXRlY3Qoc2VudGVuY2UsIHJlZ2V4KCJeKFx0fFxufCApKiQiKSkpCgp3YXRlcl9zZW50ZW5jZXMkc2VudGVuY2UgPC0gYXMuY2hhcmFjdGVyKHdhdGVyX3NlbnRlbmNlcyRzZW50ZW5jZSkKCndhdGVyX3NlbnRlbmNlcwpgYGAKCgojIENoMi4g5pa36KmeCiMjIDEu5Yid5aeL5YyW5pa36Kme5ZmoCmBgYHtyfQojIOS9v+eUqOm7mOiqjeWPg+aVuOWIneWni+WMluS4gOWAi+aWt+ipnuW8leaTjgojIOWFiOS4jeS9v+eUqOS7u+S9leeahOWtl+WFuOWSjOWBnOeUqOipngpqaWViYV90b2tlbml6ZXIgPSB3b3JrZXIoKQoKY2hpX3Rva2VuaXplciA8LSBmdW5jdGlvbih0KSB7CiAgbGFwcGx5KHQsIGZ1bmN0aW9uKHgpIHsKICAgIGlmKG5jaGFyKHgpPjEpewogICAgICB0b2tlbnMgPC0gc2VnbWVudCh4LCBqaWViYV90b2tlbml6ZXIpCiAgICAgICMg5Y675o6J5a2X5Liy6ZW35bqm54iyMeeahOipnuW9mQogICAgICB0b2tlbnMgPC0gdG9rZW5zW25jaGFyKHRva2Vucyk+MV0KICAgICAgcmV0dXJuKHRva2VucykKICAgIH0KICB9KQp9CmBgYAoKIyMgMi7mlrfoqZ7oiIfmlbTnkIbmlrfoqZ7ntZDmnpwKYGBge3J9CiMg6YCy6KGM5pa36Kme77yM5Lim6KiI566X5ZCE6Kme5b2Z5Zyo5ZCE5paH56ug5Lit5Ye654++55qE5qyh5pW4CndhdGVyX3dvcmRzIDwtIHdhdGVyX3NlbnRlbmNlcyAlPiUKICB1bm5lc3RfdG9rZW5zKHdvcmQsIHNlbnRlbmNlLCB0b2tlbj1jaGlfdG9rZW5pemVyKSAlPiUKICBmaWx0ZXIoIXN0cl9kZXRlY3Qod29yZCwgcmVnZXgoIlswLTlhLXpBLVpdIikpKSAlPiUKICBjb3VudChhcnRVcmwsIHdvcmQsIHNvcnQgPSBUUlVFKQp3YXRlcl93b3JkcwpgYGAKCiMgQ2gzLiBURi1JREYKIyMgMS4g6KiI566X5q+P56+H5paH56ug55qE6Kme5pW4CmBgYHtyfQp0b3RhbF93b3JkcyA8LSB3YXRlcl93b3JkcyAlPiUgCiAgZ3JvdXBfYnkoYXJ0VXJsKSAlPiUgCiAgc3VtbWFyaXplKHRvdGFsID0gc3VtKG4pKQp0b3RhbF93b3JkcwpgYGAKIyMgMi4g5ZCI5L216ZyA6KaB55qE6LOH5paZ5qyE5L2NCmBgYHtyfQojIOWQiOS9tSBtYXNrX3dvcmRz77yI5q+P5YCL6Kme5b2Z5Zyo5q+P5YCL5paH56ug5Lit5Ye654++55qE5qyh5pW477yJCiMg6IiHIHRvdGFsX3dvcmRz77yI5q+P56+H5paH56ug55qE6Kme5pW477yJCiMg5paw5aKe5ZCE5YCL6Kme5b2Z5Zyo5omA5pyJ6Kme5b2Z5Lit55qE57i95pW45qyE5L2NCndhdGVyX3dvcmRzIDwtIGxlZnRfam9pbih3YXRlcl93b3JkcywgdG90YWxfd29yZHMpCndhdGVyX3dvcmRzCmBgYAoKIyMgMy4g6KiI566XIHRmLWlkZiDlgLwKYGBge3J9CiMg5Lul5q+P56+H5paH56ug54iy5Zau5L2N77yM6KiI566X5q+P5YCL6Kme5b2Z55qEIHRmLWlkZiDlgLwKd2F0ZXJfd29yZHNfdGZfaWRmIDwtIHdhdGVyX3dvcmRzICU+JQogIGJpbmRfdGZfaWRmKHdvcmQsIGFydFVybCwgbikKd2F0ZXJfd29yZHNfdGZfaWRmCmBgYAoKIyMjIOaqouimlue1kOaenApgYGB7cn0KIyDpgbjlh7rmr4/nr4fmlofnq6DvvIx0Zi1pZGblgLzmnIDlpKfnmoTkupTlgIvoqZ4Kd2F0ZXJfd29yZHNfdGZfaWRmICU+JSAKICBncm91cF9ieShhcnRVcmwpICU+JQogIHNsaWNlX21heCh0Zl9pZGYsIG49NSkgJT4lCiAgYXJyYW5nZShkZXNjKGFydFVybCkpCmBgYAoKIyMjIOioiOeul+aVtOWAi+aWh+mbhuS4rei8g+W4uCB0Zi1pZGYg5YC86auY55qE5a2XCmBgYHtyfQojIOW+nuavj+evh+aWh+eroOaMkemBuOWHunRmLWlkZuacgOWkp+eahOWNgeWAi+ipnu+8jAojIOS4puioiOeul+avj+WAi+ipnuiiq+mBuOS4reeahOasoeaVuAp3YXRlcl93b3Jkc190Zl9pZGYgJT4lIAogIGdyb3VwX2J5KGFydFVybCkgJT4lCiAgc2xpY2VfbWF4KHRmX2lkZiwgbj0xMCkgJT4lCiAgdW5ncm91cCgpICU+JQogIGNvdW50KHdvcmQsIHNvcnQ9VFJVRSkKYGBgCgoKIyBDaDQuIOmAj+mBjue1kOW3tOaWt+ipnuiIh04tZ3JhbeW5q+WKqeW7uueri+Wtl+WFuAojIyBqaWViYXIgYW5kIG5ncmFtcwpgYGB7cn0KIyDkvb/nlKjntZDlt7TmlrfoqZ7vvIzkuKbmkK3phY1OTFAgcGFja2FnZXPkuK3nmoQgbmdyYW1zIGZ1bmN0aW9uCiMgZS5nLgp0b2tlbnMgPC0gc2VnbWVudCgi5Lit5bGx6LOH566h5YWo5ZyL56ys5LiAIiwgamllYmFfdG9rZW5pemVyKQp0b2tlbnMKCmJpZ3JhbSA8LSBuZ3JhbXModG9rZW5zLCAyKQpiaWdyYW0KYGBgCmBgYHtyfQojIENvbWJpbmUgZWFjaCBiaWdyYW1zIGludG8gYSBzaW5nbGUgc3RyaW5nLCB3aXRoIHRoZSAiICIgYXMgdGhlIHNlcGVyYXRlci4KYmlncmFtIDwtIGxhcHBseShiaWdyYW0sIHBhc3RlLCBjb2xsYXBzZSA9ICIgIikKdW5saXN0KGJpZ3JhbSkKYGBgCiMjIEJpZ3JhbQojIyMgYmlncmFtIGZ1bmN0aW9uCmBgYHtyfQpqaWViYV90b2tlbml6ZXIgPSB3b3JrZXIoKQoKIyB1bm5lc3RfdG9rZW5zIOS9v+eUqOeahGJpZ3JhbeWIhuipnuWHveaVuAojIElucHV0OiBhIGNoYXJhY3RlciB2ZWN0b3IKIyBPdXRwdXQ6IGEgbGlzdCBvZiBjaGFyYWN0ZXIgdmVjdG9ycyBvZiB0aGUgc2FtZSBsZW5ndGgKamllYmFfYmlncmFtIDwtIGZ1bmN0aW9uKHQpIHsKICBsYXBwbHkodCwgZnVuY3Rpb24oeCkgewogICAgaWYobmNoYXIoeCk+MSl7CiAgICAgIHRva2VucyA8LSBzZWdtZW50KHgsIGppZWJhX3Rva2VuaXplcikKICAgICAgYmlncmFtPC0gbmdyYW1zKHRva2VucywgMikKICAgICAgYmlncmFtIDwtIGxhcHBseShiaWdyYW0sIHBhc3RlLCBjb2xsYXBzZSA9ICIgIikKICAgICAgdW5saXN0KGJpZ3JhbSkKICAgIH0KICB9KQp9CgpqaWViYV9iaWdyYW0oYygi5Lit5bGx6LOH566h5YWo5ZyL56ys5LiAIiwgIuaIkeS7iuWkqeaZmumkkOWQg+awtOmkgyIpKQpgYGAKIyMjIOWft+ihjGJpZ3JhbeWIhuipngpgYGB7cn0KCndhdGVyX2JpZ3JhbSA8LSB3YXRlciAlPiUKICB1bm5lc3RfdG9rZW5zKGJpZ3JhbSwgc2VudGVuY2UsIHRva2VuID0gamllYmFfYmlncmFtKQp3YXRlcl9iaWdyYW0KYGBgCgojIyMg57Wx6KiI5pyA5bi45Ye654++55qEYmlncmFt57WE5ZCICmBgYHtyfQojIOa4hemZpOWMheWQq+iLseaWh+aIluaVuOWtl+eahGJpZ3Jhbee1hOWQiAojIOioiOeul+avj+WAi+e1hOWQiOWHuuePvueahOasoeaVuAp3YXRlcl9iaWdyYW0gJT4lCiAgZmlsdGVyKCFzdHJfZGV0ZWN0KGJpZ3JhbSwgcmVnZXgoIlswLTlhLXpBLVpdIikpKSAlPiUKICBjb3VudChiaWdyYW0sIHNvcnQgPSBUUlVFKQpgYGAKCiMjIFRyaWdyYW0KIyMjIHRyaWdyYW0gZnVuY3Rpb24KYGBge3J9CmppZWJhX3RyaWdyYW0gPC0gZnVuY3Rpb24odCkgewogIGxhcHBseSh0LCBmdW5jdGlvbih4KSB7CiAgICBpZihuY2hhcih4KT4xKXsKICAgICAgdG9rZW5zIDwtIHNlZ21lbnQoeCwgamllYmFfdG9rZW5pemVyKQogICAgICBuZ3JhbTwtIG5ncmFtcyh1bmxpc3QodG9rZW5zKSwgMykKICAgICAgbmdyYW0gPC0gbGFwcGx5KG5ncmFtLCBwYXN0ZSwgY29sbGFwc2UgPSAiICIpCiAgICAgIHVubGlzdChuZ3JhbSkKICAgIH0KICB9KQp9CgpqaWViYV90cmlncmFtKGMoIuS4reWxseizh+euoeWFqOWci+esrOS4gCIsICLmiJHku4rlpKnmmZrppJDlkIPmsLTppIMiKSkKYGBgCiMjIyDln7fooYx0cmlncmFt5YiG6KmeCmBgYHtyfQp3YXRlcl90cmlncmFtIDwtIHdhdGVyICU+JQogIHVubmVzdF90b2tlbnMobmdyYW1zLCBzZW50ZW5jZSwgdG9rZW4gPSBqaWViYV90cmlncmFtKQp3YXRlcl90cmlncmFtICU+JQogIGZpbHRlcighc3RyX2RldGVjdChuZ3JhbXMsIHJlZ2V4KCJbMC05YS16QS1aXSIpKSkgJT4lCiAgY291bnQobmdyYW1zLCBzb3J0ID0gVFJVRSkKYGBgCgo+IGJpZ3JhbeWSjHRyaWdyYW3nmoTntZDmnpzlj6/ku6Xnmbznj77mnInlvojlpJrljIXlkKtzdG9wd29yZHPnmoTntYTlkIjvvIzmiYDku6XmiJHlgJHmjqXokZflsIdzdG9wd29yZHPmuIXpmaTlho3nnIvnnIvmnInku4DpurzmlrDntYTlkIgKCiMjIFJlbW92ZSBzdG9wIHdvcmRzCiMjIyDovInlhaVzdG9wIHdvcmRz5a2X5YW4CmBgYHtyfQojIGxvYWQgc3RvcCB3b3JkcwpzdG9wX3dvcmRzIDwtIHNjYW4oZmlsZSA9ICIuL2RpY3Qvc3RvcF93b3Jkcy50eHQiLCB3aGF0PWNoYXJhY3RlcigpLHNlcD0nXG4nLCAKICAgICAgICAgICAgICAgICAgIGVuY29kaW5nPSd1dGYtOCcsZmlsZUVuY29kaW5nPSd1dGYtOCcpCmBgYAoKIyMjIFJlbW92ZSB0aGUgc3RvcHdvcmRzIGluIGJpZ3JhbQpgYGB7cn0Kd2F0ZXJfYmlncmFtICU+JQogIGZpbHRlcighc3RyX2RldGVjdChiaWdyYW0sIHJlZ2V4KCJbMC05YS16QS1aXSIpKSkgJT4lCiAgc2VwYXJhdGUoYmlncmFtLCBjKCJ3b3JkMSIsICJ3b3JkMiIpLCBzZXAgPSAiICIpICU+JSAKICBmaWx0ZXIoISh3b3JkMSAlaW4lIHN0b3Bfd29yZHMpLCAhKHdvcmQyICVpbiUgc3RvcF93b3JkcykpICU+JQogIGNvdW50KHdvcmQxLCB3b3JkMiwgc29ydCA9IFRSVUUpICU+JQogIHVuaXRlXygiYmlncmFtIiwgYygid29yZDEiLCJ3b3JkMiIpLCBzZXA9IiAiKQpgYGAKCiMjIyBSZW1vdmUgdGhlIHN0b3B3b3JkcyBpbiB0cmlncmFtCmBgYHtyfQp3YXRlcl90cmlncmFtICU+JQogIGZpbHRlcighc3RyX2RldGVjdChuZ3JhbXMsIHJlZ2V4KCJbMC05YS16QS1aXSIpKSkgJT4lCiAgc2VwYXJhdGUobmdyYW1zLCBjKCJ3b3JkMSIsICJ3b3JkMiIsICJ3b3JkMyIpLCBzZXAgPSAiICIpICU+JSAKICBmaWx0ZXIoISh3b3JkMSAlaW4lIHN0b3Bfd29yZHMpLCAhKHdvcmQyICVpbiUgc3RvcF93b3JkcyksICEod29yZDMgJWluJSBzdG9wX3dvcmRzKSkgJT4lCiAgY291bnQod29yZDEsIHdvcmQyLCB3b3JkMywgc29ydCA9IFRSVUUpICU+JQogIHVuaXRlXygibmdyYW1zIiwgYygid29yZDEiLCAid29yZDIiLCAid29yZDMiKSwgc2VwPSIgIikKYGBgCgo+IOW+nuS4iumdoueahCBiaWdyYW0g5ZKMIHRyaWdyYW0g55qE57WQ5p6c5Lit77yM5oiR5YCR5Y+v5Lul55yL5Yiw5pyJ5Lqb5a2X5oeJ6Kmy6KKr57WE5ZCI5Zyo5LiA6LW377yM5oiR5YCR5Lul5q2k5L6G5bu656uL5pu05aW955qE5pa36Kme5a2X5YW444CCPGJyPgogIOaIkeWAkeWwh+ipnuW9meaVtOeQhuWlveWtmOWcqCBkaWN0IOaWh+S7tuWkvuS4reeahCBtYXNrX2xleGljb24udHh0IOS4reOAggoKCiMgQ2g1LiDkvb/nlKjoh6rlu7rlrZflhbgKIyMjIOi8ieWFpeiHquW7uuWtl+WFuApgYGB7cn0KIyBsb2FkIG1hc2tfbGV4aWNvbgp3YXRlcl9sZXhpY29uIDwtIHNjYW4oZmlsZSA9ICIuL2RpY3Qvd2F0ZXJfbGV4aWNvbi50eHQiLCB3aGF0PWNoYXJhY3RlcigpLHNlcD0nXG4nLCAKICAgICAgICAgICAgICAgICAgIGVuY29kaW5nPSd1dGYtOCcsZmlsZUVuY29kaW5nPSd1dGYtOCcscXVpZXQgPSBUKQojIOiHquW7uuawtOaDheebuOmXnOWtl+WFuAp3YXRlcl9sZXhpY29uCmBgYAojIyDkvb/nlKjmlrDlrZflhbjlu7rnq4vmlrfoqZ7lmagKYGBge3J9CmppZWJhX3Rva2VuaXplciA9IHdvcmtlcigpCgojIOS9v+eUqOeWq+aDheebuOmXnOWtl+WFuOmHjeaWsOaWt+ipngpuZXdfdXNlcl93b3JkKGppZWJhX3Rva2VuaXplciwgYyh3YXRlcl9sZXhpY29uKSkKCmNoaV90b2tlbml6ZXIgPC0gZnVuY3Rpb24odCkgewogIGxhcHBseSh0LCBmdW5jdGlvbih4KSB7CiAgICBpZihuY2hhcih4KT4xKXsKICAgICAgdG9rZW5zIDwtIHNlZ21lbnQoeCwgamllYmFfdG9rZW5pemVyKQogICAgICB0b2tlbnMgPC0gdG9rZW5zWyF0b2tlbnMgJWluJSBzdG9wX3dvcmRzXQogICAgICAjIOWOu+aOieWtl+S4sumVt+W6pueIsjHnmoToqZ7lvZkKICAgICAgdG9rZW5zIDwtIHRva2Vuc1tuY2hhcih0b2tlbnMpPjFdCiAgICAgIHJldHVybih0b2tlbnMpCiAgICB9CiAgfSkKfQpgYGAKCgojIENoNi4gV29yZCBDb3JyZWxhdGlvbgojIyDkvb/nlKjoh6rlu7rovq3lhbjpgLLooYzmlrfoqZ7oiIfoqIjnrpcKYGBge3J9CiMg5Ymb5omN55qE5pa36Kme57WQ5p6c5rKS5pyJ5L2/55So5paw5aKe55qE6L6t5YW477yM5Zug5q2k5oiR5YCR6YeN5paw6YCy6KGM5pa36Kme77yM5YaN6KiI566X5ZCE6Kme5b2Z5Zyo5ZCE5paH56ug5Lit5Ye654++55qE5qyh5pW4CndhdGVyX3dvcmRzIDwtIHdhdGVyX3NlbnRlbmNlcyAlPiUKICB1bm5lc3RfdG9rZW5zKHdvcmQsIHNlbnRlbmNlLCB0b2tlbj1jaGlfdG9rZW5pemVyKSAlPiUKICBmaWx0ZXIoIXN0cl9kZXRlY3Qod29yZCwgcmVnZXgoIlswLTlhLXpBLVpdIikpKSAlPiUKICBjb3VudChhcnRVcmwsIHdvcmQsIHNvcnQgPSBUUlVFKQp3YXRlcl93b3JkcwpgYGAKCiMjIOioiOeul+WFqeWAi+ipnuW9meWQjOaZguWHuuePvueahOe4veasoeaVuApgYGB7cn0KIyDpgY7mv77mjonkuInlgIvpl5zpjbXlrZci57y65rC0IiwgIuawtOW6qyIsICLmsLTmg4UiCndvcmRfcGFpcnMgPC0gd2F0ZXJfd29yZHMgJT4lCiAgcGFpcndpc2VfY291bnQod29yZCwgYXJ0VXJsLCBzb3J0ID0gVFJVRSkgJT4lIAogIGZpbHRlcighaXRlbTEgJWluJSBjKCLnvLrmsLQiLCAi5rC05bqrIiwgIuawtOaDhSIpICYgIWl0ZW0yICVpbiUgYygi57y65rC0IiwgIuawtOW6qyIsICLmsLTmg4UiKSkKCndvcmRfcGFpcnMKYGBgCgojIyDoqIjnrpflhanlgIvoqZ7lvZnplpPnmoTnm7jpl5zmgKcKYGBge3J9CndvcmRfY29ycyA8LSB3YXRlcl93b3JkcyAlPiUKICBncm91cF9ieSh3b3JkKSAlPiUKICBmaWx0ZXIobigpID49IDEwKSAlPiUKICBwYWlyd2lzZV9jb3Iod29yZCwgYXJ0VXJsLCBzb3J0ID0gVFJVRSkKCndvcmRfY29ycwpgYGAKCiMjIOimluimuuWMlgojIyMg5om+5Ye66IiHICLnn7PploDmsLTluqsiICLmm77mlofmsLTluqsiIOmAmeWFqeWAi+awtOW6q+ebuOmXnOaAp+acgOmrmOeahCAxNSDlgIvoqZ7lvZkKYGBge3J9CndvcmRfY29ycyAlPiUKICBmaWx0ZXIoaXRlbTEgJWluJSBjKCLnn7PploDmsLTluqsiLCAi5pu+5paH5rC05bqrIikpICU+JQogIGdyb3VwX2J5KGl0ZW0xKSAlPiUKICB0b3BfbigxNSkgJT4lCiAgdW5ncm91cCgpICU+JQogIG11dGF0ZShpdGVtMiA9IHJlb3JkZXIoaXRlbTIsIGNvcnJlbGF0aW9uKSkgJT4lCiAgZ2dwbG90KGFlcyhpdGVtMiwgY29ycmVsYXRpb24pKSArCiAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpICsKICBmYWNldF93cmFwKH4gaXRlbTEsIHNjYWxlcyA9ICJmcmVlIikgKwogIGNvb3JkX2ZsaXAoKSsgCiAgdGhlbWUodGV4dCA9IGVsZW1lbnRfdGV4dChmYW1pbHkgPSAiSGVpdGkgVEMgTGlnaHQiKSkgI+WKoOWFpeS4reaWh+Wtl+Wei+ioreWumu+8jOmBv+WFjeS4reaWh+Wtl+mhr+ekuumMr+iqpOOAggpgYGAKIyMjIOS9v+eUqOipnuW9memXnOS/guWclueVq+WHuuebuOmXnOaAp+Wkp+aWvDAuNeeahOe1hOWQiApgYGB7cn0Kc2V0LnNlZWQoMjAyMCkKCndvcmRfY29ycyAlPiUKICBmaWx0ZXIoY29ycmVsYXRpb24gPiAwLjUpICU+JQogIGdyYXBoX2Zyb21fZGF0YV9mcmFtZSgpICU+JQogIGdncmFwaChsYXlvdXQgPSAiZnIiKSArCiAgZ2VvbV9lZGdlX2xpbmsoYWVzKGVkZ2VfYWxwaGEgPSBjb3JyZWxhdGlvbiksIHNob3cubGVnZW5kID0gRkFMU0UpICsKICBnZW9tX25vZGVfcG9pbnQoY29sb3IgPSAibGlnaHRibHVlIiwgc2l6ZSA9IDMpICsKICBnZW9tX25vZGVfdGV4dChhZXMobGFiZWwgPSBuYW1lKSwgcmVwZWwgPSBUUlVFLCBmYW1pbHkgPSAiSGVpdGkgVEMgTGlnaHQiKSArICPliqDlhaXkuK3mloflrZflnovoqK3lrprvvIzpgb/lhY3kuK3mloflrZfpoa/npLrpjK/oqqTjgIIKICB0aGVtZV92b2lkKCkKYGBgCiMjIyDkvb/nlKjoqZ7lvZnpl5zkv4LlnJbnlavlh7rnm7jpl5zmgKflpKfmlrwwLjbnmoTntYTlkIgKYGBge3J9CnNldC5zZWVkKDIwMjApCgp3b3JkX2NvcnMgJT4lCiAgZmlsdGVyKGNvcnJlbGF0aW9uID4gMC42KSAlPiUKICBncmFwaF9mcm9tX2RhdGFfZnJhbWUoKSAlPiUKICBnZ3JhcGgobGF5b3V0ID0gImZyIikgKwogIGdlb21fZWRnZV9saW5rKGFlcyhlZGdlX2FscGhhID0gY29ycmVsYXRpb24pLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgZ2VvbV9ub2RlX3BvaW50KGNvbG9yID0gImxpZ2h0Ymx1ZSIsIHNpemUgPSAzKSArCiAgZ2VvbV9ub2RlX3RleHQoYWVzKGxhYmVsID0gbmFtZSksIHJlcGVsID0gVFJVRSwgZmFtaWx5ID0gIkhlaXRpIFRDIExpZ2h0IikgKwogIHRoZW1lX3ZvaWQoKQpgYGAKCiMjIyDnp7vpmaTkuI3pnIDopoHnmoTlrZcKYGBge3J9CiMg6Kit5a6a5bm+5YCL6Kme5YGa54iyc2VlZCB3b3JkcwpzZWVkX3dvcmRzIDwtIGMoIueci+advyIsICLkuK3lpK7npL4iLCAi5pmC5aCxIikKIyDoqK3lrpp0aHJlc2hvbGTniLIwLjYKdGhyZXNob2xkIDwtIDAuNgojIOi3n3NlZWQgd29yZHPnm7jpl5zmgKfpq5jmlrx0aHJlc2hvbGTnmoToqZ7lvZnmnIPooqvliqDlhaXnp7vpmaTliJfooajkuK0KcmVtb3ZlX3dvcmRzIDwtIHdvcmRfY29ycyAlPiUKICAgICAgICAgICAgICAgIGZpbHRlcigoaXRlbTEgJWluJSBzZWVkX3dvcmRzfGl0ZW0yICVpbiUgc2VlZF93b3JkcyksIGNvcnJlbGF0aW9uPnRocmVzaG9sZCkgJT4lCiAgICAgICAgICAgICAgICAuJGl0ZW0xICU+JQogICAgICAgICAgICAgICAgdW5pcXVlKCkKcmVtb3ZlX3dvcmRzCmBgYAojIyMg5L2/55So6Kme5b2Z6Zec5L+C5ZyW55Wr5Ye655u46Zec5oCn5aSn5pa8MC4255qE57WE5ZCICmBgYHtyfQojIOa4hemZpOWtmOWcqOmAmeS6m+ipnuW9meeahOe1hOWQiAp3b3JkX2NvcnNfbmV3IDwtIHdvcmRfY29ycyAlPiUKICAgICAgICAgICAgICAgIGZpbHRlcighKGl0ZW0xICVpbiUgcmVtb3ZlX3dvcmRzfGl0ZW0yICVpbiUgcmVtb3ZlX3dvcmRzKSkKCndvcmRfY29yc19uZXcgJT4lCiAgZmlsdGVyKGNvcnJlbGF0aW9uID4gMC42KSAlPiUKICBncmFwaF9mcm9tX2RhdGFfZnJhbWUoKSAlPiUKICBnZ3JhcGgobGF5b3V0ID0gImZyIikgKwogIGdlb21fZWRnZV9saW5rKGFlcyhlZGdlX2FscGhhID0gY29ycmVsYXRpb24pLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArIAogIGdlb21fbm9kZV9wb2ludChjb2xvciA9ICJsaWdodGJsdWUiLCBzaXplID0gMykgKwogIGdlb21fbm9kZV90ZXh0KGFlcyhsYWJlbCA9IG5hbWUpLCByZXBlbCA9IFRSVUUsIGZhbWlseSA9ICJIZWl0aSBUQyBMaWdodCIpICsKICB0aGVtZV92b2lkKCkKYGBgCg==