Lecture7: Collocation

ライブラリの読み込み

library(httr)
library(rvest)
library(cleanNLP)

文書-単語行列の作成 (Ref. Lec06)

オンライン記事情報を取得

自作関数

getArticleContent <- function(url){
  response <- GET(url, user_agent("Mozilla/5.0 (Macintosh; Intel Mac OS X 14_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.0 Safari/605.1.15"))
  page <- read_html(response)
  article_content <- html_text(html_nodes(page, "p.txt"), trim = TRUE)
  cleaned_content <- trimws(article_content)
  cleaned_content <- paste(cleaned_content, collapse = "")
}

記事データの取得

article_urls <- c()
article_urls <- append(article_urls,"https://mainichi.jp/english/articles/20241107/p2a/00m/0et/015000c")

article_urls <- append(article_urls,"https://mainichi.jp/english/articles/20241110/p2g/00m/0li/028000c")
article_urls <- append(article_urls,"https://mainichi.jp/english/articles/20241111/p2g/00m/0na/048000c")
article_urls <- append(article_urls,"https://mainichi.jp/english/articles/20241112/p2g/00m/0sp/005000c")
length(article_urls)
[1] 4

文書単語行列

#Tokenization (形態素解析)
contents <- lapply(article_urls, getArticleContent)
annotedData<-cnlp_annotate(input = contents)$token
dim(annotedData)
[1] 909  11
### 文書単語行列
docMtx <- as.data.frame.matrix(table(annotedData$lemma, annotedData$doc_id))
View(docMtx)

特定の列情報をリスト型として抽出

res1<-annotedData$token
res1[1:10]
 [1] "TOKYO"    "--"       "An"       "art"      "deco"     "building" "in"       "the"     
 [9] "Japanese" "capital" 
res2<-annotedData[[4]]
res2[1:10]
 [1] "TOKYO"    "--"       "An"       "art"      "deco"     "building" "in"       "the"     
 [9] "Japanese" "capital" 

特定の列情報をdataframe型として抽出

res3<-annotedData[4]
res3[1:10,]

リスト内の要素の位置(index)を特定

colnames(annotedData)
 [1] "doc_id"        "sid"           "tid"           "token"         "token_with_ws" "lemma"        
 [7] "upos"          "xpos"          "feats"         "tid_source"    "relation"     
which(colnames(annotedData)=="token")
[1] 4

練習1:annotedDataからArticle1のtoken列を抽出してください

結果出力

article1[1:10]
 [1] "TOKYO"    "--"       "An"       "art"      "deco"     "building" "in"       "the"     
 [9] "Japanese" "capital" 

出現頻度表

freq_data<-sort(table(article1), decreasing=TRUE)
freq_data[1:20]
article1
     the        .        ,       of        a       in       's        *       as      and      was 
      17       12        9        8        7        7        6        6        6        5        5 
building      The     with        -        (        ) facility       is    Japan 
       4        4        4        3        3        3        3        3        3 

Collocation

中心語(node)の検索

部分一致(検索語を変数に格納)

node <- "article"
grep(node, article1, value=T)
[1] "article"  "articles"

完全一致

(nodeLst <- grep("^article$",article1, value=T))
[1] "article"

完全一致(検索語を変数に格納)

node <- "article"
paste0("^", node,"$")
[1] "^article$"

中心語(node)の出現位置検索

node <- "facility"
search_node <- paste0("^", node,"$")
(nodeIndex <- grep(search_node,article1, ignore.case = T))
[1]  97 164 251

周辺語の抽出

  • span=2 (中心語の左右2語)
Left1 <- article1[nodeIndex-1]
Left2 <- article1[nodeIndex-2]
Right1 <- article1[nodeIndex+1]
Right2 <- article1[nodeIndex+2]

collocationの列結合

cbind(Left2, Left1, node, Right1, Right2)
     Left2    Left1 node       Right1 Right2    
[1,] "."      "The" "facility" "has"  "a"       
[2,] "At"     "the" "facility" "'s"   "entrance"
[3,] "inside" "the" "facility" "."    "("       

data.frame: コンコーダンス(Concordance)

collo <- data.frame(cbind(Left2, Left1, node, Right1, Right2))
colnames(collo) <- c("2L","1L","node","1R","2R")
rownames(collo) <- seq(dim(collo)[1])
collo

Specify a variable span size

size <- 4

colloLst <- c()
len<-length(article1)-size+1

for(i in nodeIndex) {
  colloLst<-rbind(colloLst,article1[(i-size):(i+size)])
}

colloLst <- data.frame(colloLst)
colnames(colloLst) <- c(paste0(seq(size, 1, -1),"L"),"node",paste0(seq(1,size),"R"))
rownames(colloLst) <- seq(dim(colloLst)[1])
colloLst

Specify a variable span size (code breakdown)

size <- 4
node <- "facility"
search_node <- paste0("^", node,"$")
(nodeIndex <- grep(search_node,article1, ignore.case = T))
[1]  97 164 251
colloLst <- c()
#nodeIndex[1]
(i=nodeIndex[1])
[1] 97
article1[(i-size):(i+size)]
[1] "Shoseki"    "Co"         "."          "The"        "facility"   "has"       
[7] "a"          "large"      "collection"
colloLst<-rbind(colloLst,article1[(i-size):(i+size)])
colloLst
     [,1]      [,2] [,3] [,4]  [,5]       [,6]  [,7] [,8]    [,9]        
[1,] "Shoseki" "Co" "."  "The" "facility" "has" "a"  "large" "collection"
#nodeIndex[2]
(i=nodeIndex[2])
[1] 164
article1[(i-size):(i+size)]
[1] "geometry" "."        "At"       "the"      "facility" "'s"       "entrance"
[8] ","        "two"     
colloLst<-rbind(colloLst,article1[(i-size):(i+size)])
colloLst
     [,1]       [,2] [,3] [,4]  [,5]       [,6]  [,7]       [,8]    [,9]        
[1,] "Shoseki"  "Co" "."  "The" "facility" "has" "a"        "large" "collection"
[2,] "geometry" "."  "At" "the" "facility" "'s"  "entrance" ","     "two"       
#nodeIndex[3]
(i=nodeIndex[3])
[1] 251
article1[(i-size):(i+size)]
[1] "or"       "seeing"   "inside"   "the"      "facility" "."        "("       
[8] "Japanese" "original"
colloLst<-rbind(colloLst,article1[(i-size):(i+size)])
colloLst
     [,1]       [,2]     [,3]     [,4]  [,5]       [,6]  [,7]       [,8]      
[1,] "Shoseki"  "Co"     "."      "The" "facility" "has" "a"        "large"   
[2,] "geometry" "."      "At"     "the" "facility" "'s"  "entrance" ","       
[3,] "or"       "seeing" "inside" "the" "facility" "."   "("        "Japanese"
     [,9]        
[1,] "collection"
[2,] "two"       
[3,] "original"  

課題2(締め切り12月3日)

annotedDataを使用して、次の3つの変数を引数とし、共起情報をコンコーダンス表示で出力する関数を作成してください

引数

  • 記事
  • キーワード(自分で2-3個選んでください)
  • スパンの長さ #### Option: もしキーワードがヒットしなかった場合の処理についても考えてみてください(実行例3を参照)
node_list = c("of", "art", "Japan")
source("getCollo.R")

実行例1

getCollo(articleLst[[2]], current_node=node_list[1])
  3L        2L    1L            node 1R            2R       3R         
1 "--"      "A"   "team"        "of" "researchers" ","      "including"
2 "explore" "the" "possibility" "of" "("           "health" ")"        
3 "after"   "a"   "month"       "of" "treatment"   "with"   "low"      

実行例2

getCollo(articleLst[[4]], current_node=node_list[3], span=5)
  5L                4L         3L           2L          1L       node    1R         2R         
1 "("               "Kyodo"    ")"          "--"        "Former" "Japan" "striker"  "Kazuyoshi"
2 "for"             "Atletico" "Suzuka"     "in"        "the"    "Japan" "Football" "League"   
3 "Oliveirense.The" "Shizuoka" "Prefecture" "native"    "left"   "Japan" "at"       "15"       
4 "1986"            "."        "After"      "returning" "to"     "Japan" "in"       "1990"     
  3R      4R       5R      
1 "Miura" "said"   "Monday"
2 "next"  "year"   "in"    
3 "for"   "Brazil" ","     
4 ","     "Miura"  "won"   

実行例3

getCollo(articleLst[[4]], current_node=node_list[2])
[1] "The search term does not appear in this article"
NULL
LS0tCnRpdGxlOiAiTGVjMDc6IENvbGxvY2F0aW9uIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKZWRpdG9yX29wdGlvbnM6IAogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUKLS0tCgojIExlY3R1cmU3OiBDb2xsb2NhdGlvbgoKIyMjIOODqeOCpOODluODqeODquOBruiqreOBv+i+vOOBvwpgYGB7cn0KbGlicmFyeShodHRyKQpsaWJyYXJ5KHJ2ZXN0KQpsaWJyYXJ5KGNsZWFuTkxQKQpgYGAKCiMjIOaWh+abuC3ljZjoqp7ooYzliJfjga7kvZzmiJAgKFJlZi4gTGVjMDYpCiMjIyDjgqrjg7Pjg6njgqTjg7PoqJjkuovmg4XloLHjgpLlj5blvpcKCiMjIyDoh6rkvZzplqLmlbAKYGBge3J9CmdldEFydGljbGVDb250ZW50IDwtIGZ1bmN0aW9uKHVybCl7CiAgcmVzcG9uc2UgPC0gR0VUKHVybCwgdXNlcl9hZ2VudCgiTW96aWxsYS81LjAgKE1hY2ludG9zaDsgSW50ZWwgTWFjIE9TIFggMTRfNykgQXBwbGVXZWJLaXQvNjA1LjEuMTUgKEtIVE1MLCBsaWtlIEdlY2tvKSBWZXJzaW9uLzE4LjAgU2FmYXJpLzYwNS4xLjE1IikpCiAgcGFnZSA8LSByZWFkX2h0bWwocmVzcG9uc2UpCiAgYXJ0aWNsZV9jb250ZW50IDwtIGh0bWxfdGV4dChodG1sX25vZGVzKHBhZ2UsICJwLnR4dCIpLCB0cmltID0gVFJVRSkKICBjbGVhbmVkX2NvbnRlbnQgPC0gdHJpbXdzKGFydGljbGVfY29udGVudCkKICBjbGVhbmVkX2NvbnRlbnQgPC0gcGFzdGUoY2xlYW5lZF9jb250ZW50LCBjb2xsYXBzZSA9ICIiKQp9CmBgYAoKIyMjIOODi+ODpeODvOOCueiomOS6iwoqIDxhIGhyZWY9Imh0dHBzOi8vbWFpbmljaGkuanAvZW5nbGlzaC9hcnRpY2xlcy8yMDI0MTEwNy9wMmEvMDBtLzBldC8wMTUwMDBjIiB0YXJnZXQ9Il9ibGFuayI+UmV0cm8gSmFwYW46IFRva3lvIHRleHRib29rIGxpYnJhcnkgZGVzaWduZWQgaW4gYXJ0IGRlY28gc3R5bGUgc3RvcmVzIGhpc3RvcmljYWwgbWF0ZXJpYWxzPC9hPgoqIDxhIGhyZWY9Imh0dHBzOi8vbWFpbmljaGkuanAvZW5nbGlzaC9hcnRpY2xlcy8yMDI0MTExMC9wMmcvMDBtLzBsaS8wMjgwMDBjIiB0YXJnZXQ9Il9ibGFuayI+SmFwYW4gcmVzZWFyY2hlcnMgdG8gc2VlIGlmIHNraW4gdmlicmF0aW9uIGJvb3N0cyBtZW50YWwgaGVhbHRoPC9hPgoqIDxhIGhyZWY9Imh0dHBzOi8vbWFpbmljaGkuanAvZW5nbGlzaC9hcnRpY2xlcy8yMDI0MTExMS9wMmcvMDBtLzBuYS8wNDgwMDBjIiB0YXJnZXQ9Il9ibGFuayI+SXNoaWJhIHN1c3BlY3RlZCBvZiBmYWxsaW5nIGFzbGVlcCBkdXJpbmcgRGlldCBzZXNzaW9uIHRvIHNlbGVjdCBQTTwvYT4KKiA8YSBocmVmPSJodHRwczovL21haW5pY2hpLmpwL2VuZ2xpc2gvYXJ0aWNsZXMvMjAyNDExMTIvcDJnLzAwbS8wc3AvMDA1MDAwYyIgdGFyZ2V0PSJfYmxhbmsiPkZvb3RiYWxsOiBLYXp1eW9zaGkgTWl1cmEsIDU3LCBzZXQgdG8gcGxheSA0MHRoIHNlYXNvbiBhcyBwcm9mZXNzaW9uYWw8L2E+CgojIyMg6KiY5LqL44OH44O844K/44Gu5Y+W5b6XCmBgYHtyfQphcnRpY2xlX3VybHMgPC0gYygpCmFydGljbGVfdXJscyA8LSBhcHBlbmQoYXJ0aWNsZV91cmxzLCJodHRwczovL21haW5pY2hpLmpwL2VuZ2xpc2gvYXJ0aWNsZXMvMjAyNDExMDcvcDJhLzAwbS8wZXQvMDE1MDAwYyIpCgphcnRpY2xlX3VybHMgPC0gYXBwZW5kKGFydGljbGVfdXJscywiaHR0cHM6Ly9tYWluaWNoaS5qcC9lbmdsaXNoL2FydGljbGVzLzIwMjQxMTEwL3AyZy8wMG0vMGxpLzAyODAwMGMiKQphcnRpY2xlX3VybHMgPC0gYXBwZW5kKGFydGljbGVfdXJscywiaHR0cHM6Ly9tYWluaWNoaS5qcC9lbmdsaXNoL2FydGljbGVzLzIwMjQxMTExL3AyZy8wMG0vMG5hLzA0ODAwMGMiKQphcnRpY2xlX3VybHMgPC0gYXBwZW5kKGFydGljbGVfdXJscywiaHR0cHM6Ly9tYWluaWNoaS5qcC9lbmdsaXNoL2FydGljbGVzLzIwMjQxMTEyL3AyZy8wMG0vMHNwLzAwNTAwMGMiKQpsZW5ndGgoYXJ0aWNsZV91cmxzKQpgYGAKIyMjIOaWh+abuOWNmOiqnuihjOWIlwpgYGB7cn0KI1Rva2VuaXphdGlvbiAo5b2i5oWL57Sg6Kej5p6QKQpjb250ZW50cyA8LSBsYXBwbHkoYXJ0aWNsZV91cmxzLCBnZXRBcnRpY2xlQ29udGVudCkKYW5ub3RlZERhdGE8LWNubHBfYW5ub3RhdGUoaW5wdXQgPSBjb250ZW50cykkdG9rZW4KZGltKGFubm90ZWREYXRhKQoKIyMjIOaWh+abuOWNmOiqnuihjOWIlwpkb2NNdHggPC0gYXMuZGF0YS5mcmFtZS5tYXRyaXgodGFibGUoYW5ub3RlZERhdGEkbGVtbWEsIGFubm90ZWREYXRhJGRvY19pZCkpClZpZXcoZG9jTXR4KQpgYGAKIyMjIOeJueWumuOBruWIl+aDheWgseOCkuODquOCueODiOWei+OBqOOBl+OBpuaKveWHugoqIDxhIGhyZWY9Imh0dHBzOi8vZGF0YWFuYWx5dGljcy5vcmcudWsvci1vYmplY3QtZWxlbWVudHMtYnJhY2tldHMtZG91YmxlLWJyYWNrZXRzLWFuZC8iIHRhcmdldD0iX2JsYW5rIj5Eb3VibGUgYnJhY2tldHM8L2E+CmBgYHtyfQpyZXMxPC1hbm5vdGVkRGF0YSR0b2tlbgpyZXMxWzE6MTBdCnJlczI8LWFubm90ZWREYXRhW1s0XV0KcmVzMlsxOjEwXQpgYGAKCiMjIyDnibnlrprjga7liJfmg4XloLHjgpJkYXRhZnJhbWXlnovjgajjgZfjgabmir3lh7oKYGBge3J9CnJlczM8LWFubm90ZWREYXRhWzRdCnJlczNbMToxMCxdCmBgYAoKIyMjIOODquOCueODiOWGheOBruimgee0oOOBruS9jee9rihpbmRleCnjgpLnibnlrpoKYGBge3J9CmNvbG5hbWVzKGFubm90ZWREYXRhKQp3aGljaChjb2xuYW1lcyhhbm5vdGVkRGF0YSk9PSJ0b2tlbiIpCmBgYAoKIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjogYmx1ZTsgIj7nt7Tnv5IxPC9zcGFuPjphbm5vdGVkRGF0YeOBi+OCiUFydGljbGUx44GudG9rZW7liJfjgpLmir3lh7rjgZfjgabjgY/jgaDjgZXjgYQKYGBge3IsIGVjaG89ZmFsc2V9CmNvbG5hbWU8LSJ0b2tlbiIKYXJ0aWNsZTEgPC0gYW5ub3RlZERhdGFbYW5ub3RlZERhdGEkZG9jX2lkPT0xLF1bW3doaWNoKGNvbG5hbWVzKGFubm90ZWREYXRhKT09Y29sbmFtZSldXQpgYGAKIyMjIyDntZDmnpzlh7rlipsgCmBgYHtyfQphcnRpY2xlMVsxOjEwXQpgYGAKIyMjIOWHuuePvumgu+W6puihqApgYGB7cn0KZnJlcV9kYXRhPC1zb3J0KHRhYmxlKGFydGljbGUxKSwgZGVjcmVhc2luZz1UUlVFKQpmcmVxX2RhdGFbMToyMF0KYGBgCgojIENvbGxvY2F0aW9uCiMjIOS4reW/g+iqnihub2RlKeOBruaknOe0ogoqIGlnbm9yZS5jYXNlOiDlpKfmloflrZfjg7vlsI/mloflrZfjga7ljLrliKUKKiDmloflrZfmpJzntKI6IDxhIGhyZWY9Imh0dHBzOi8vd3d3LnJkb2N1bWVudGF0aW9uLm9yZy9wYWNrYWdlcy9iYXNlL3ZlcnNpb25zLzMuNi4yL3RvcGljcy9ncmVwIiB0YXJnZXQ9Il9ibGFuayI+Z3JlcDwvYT4KKiA8YSBocmVmPSJodHRwczovL3N0YXRzLmJpb3BhcHlydXMuanAvci9kZXZlbC9yZWdleC5odG1sIiB0YXJnZXQ9Il9ibGFuayI+Z3JlcOS9v+eUqOS+izwvYT4KCiMjIyDpg6jliIbkuIDoh7TvvIjmpJzntKLoqp7jgpLlpInmlbDjgavmoLzntI3vvIkKYGBge3J9Cm5vZGUgPC0gImFydGljbGUiCmdyZXAobm9kZSwgYXJ0aWNsZTEsIHZhbHVlPVQpCmBgYAoKIyMjIOWujOWFqOS4gOiHtApgYGB7cn0KKG5vZGVMc3QgPC0gZ3JlcCgiXmFydGljbGUkIixhcnRpY2xlMSwgdmFsdWU9VCkpCmBgYAoKIyMjIOWujOWFqOS4gOiHtO+8iOaknOe0ouiqnuOCkuWkieaVsOOBq+agvOe0je+8iQpgYGB7cn0Kbm9kZSA8LSAiYXJ0aWNsZSIKcGFzdGUwKCJeIiwgbm9kZSwiJCIpCmBgYAoKIyMg5Lit5b+D6KqeKG5vZGUp44Gu5Ye654++5L2N572u5qSc57SiCmBgYHtyfQpub2RlIDwtICJmYWNpbGl0eSIKc2VhcmNoX25vZGUgPC0gcGFzdGUwKCJeIiwgbm9kZSwiJCIpCihub2RlSW5kZXggPC0gZ3JlcChzZWFyY2hfbm9kZSxhcnRpY2xlMSwgaWdub3JlLmNhc2UgPSBUKSkKYGBgCiMjIOWRqOi+uuiqnuOBruaKveWHugoqIHNwYW49MiAo5Lit5b+D6Kqe44Gu5bem5Y+z77yS6KqeKQpgYGB7cn0KTGVmdDEgPC0gYXJ0aWNsZTFbbm9kZUluZGV4LTFdCkxlZnQyIDwtIGFydGljbGUxW25vZGVJbmRleC0yXQpSaWdodDEgPC0gYXJ0aWNsZTFbbm9kZUluZGV4KzFdClJpZ2h0MiA8LSBhcnRpY2xlMVtub2RlSW5kZXgrMl0KYGBgCgojIyMgY29sbG9jYXRpb27jga7liJfntZDlkIgKYGBge3J9CmNiaW5kKExlZnQyLCBMZWZ0MSwgbm9kZSwgUmlnaHQxLCBSaWdodDIpCmBgYAojIyMgZGF0YS5mcmFtZTog44Kz44Oz44Kz44O844OA44Oz44K5KENvbmNvcmRhbmNlKQpgYGB7cn0KY29sbG8gPC0gZGF0YS5mcmFtZShjYmluZChMZWZ0MiwgTGVmdDEsIG5vZGUsIFJpZ2h0MSwgUmlnaHQyKSkKY29sbmFtZXMoY29sbG8pIDwtIGMoIjJMIiwiMUwiLCJub2RlIiwiMVIiLCIyUiIpCnJvd25hbWVzKGNvbGxvKSA8LSBzZXEoZGltKGNvbGxvKVsxXSkKY29sbG8KYGBgCiMjIyBTcGVjaWZ5IGEgdmFyaWFibGUgc3BhbiBzaXplCmBgYHtyfQpzaXplIDwtIDQKCm5vZGUgPC0gImZhY2lsaXR5IgpzZWFyY2hfbm9kZSA8LSBwYXN0ZTAoIl4iLCBub2RlLCIkIikKbm9kZUluZGV4IDwtIGdyZXAoc2VhcmNoX25vZGUsYXJ0aWNsZTEsIGlnbm9yZS5jYXNlID0gVCkKCmNvbGxvTHN0IDwtIGMoKQpsZW48LWxlbmd0aChhcnRpY2xlMSktc2l6ZSsxCgpmb3IoaSBpbiBub2RlSW5kZXgpIHsKICBjb2xsb0xzdDwtcmJpbmQoY29sbG9Mc3QsYXJ0aWNsZTFbKGktc2l6ZSk6KGkrc2l6ZSldKQp9Cgpjb2xsb0xzdCA8LSBkYXRhLmZyYW1lKGNvbGxvTHN0KQpjb2xuYW1lcyhjb2xsb0xzdCkgPC0gYyhwYXN0ZTAoc2VxKHNpemUsIDEsIC0xKSwiTCIpLCJub2RlIixwYXN0ZTAoc2VxKDEsc2l6ZSksIlIiKSkKcm93bmFtZXMoY29sbG9Mc3QpIDwtIHNlcShkaW0oY29sbG9Mc3QpWzFdKQpjb2xsb0xzdApgYGAKIyMjIFNwZWNpZnkgYSB2YXJpYWJsZSBzcGFuIHNpemUgKGNvZGUgYnJlYWtkb3duKQpgYGB7cn0Kc2l6ZSA8LSA0Cm5vZGUgPC0gImZhY2lsaXR5IgpzZWFyY2hfbm9kZSA8LSBwYXN0ZTAoIl4iLCBub2RlLCIkIikKKG5vZGVJbmRleCA8LSBncmVwKHNlYXJjaF9ub2RlLGFydGljbGUxLCBpZ25vcmUuY2FzZSA9IFQpKQoKY29sbG9Mc3QgPC0gYygpCiNub2RlSW5kZXhbMV0KKGk9bm9kZUluZGV4WzFdKQphcnRpY2xlMVsoaS1zaXplKTooaStzaXplKV0KY29sbG9Mc3Q8LXJiaW5kKGNvbGxvTHN0LGFydGljbGUxWyhpLXNpemUpOihpK3NpemUpXSkKY29sbG9Mc3QKI25vZGVJbmRleFsyXQooaT1ub2RlSW5kZXhbMl0pCmFydGljbGUxWyhpLXNpemUpOihpK3NpemUpXQpjb2xsb0xzdDwtcmJpbmQoY29sbG9Mc3QsYXJ0aWNsZTFbKGktc2l6ZSk6KGkrc2l6ZSldKQpjb2xsb0xzdAojbm9kZUluZGV4WzNdCihpPW5vZGVJbmRleFszXSkKYXJ0aWNsZTFbKGktc2l6ZSk6KGkrc2l6ZSldCmNvbGxvTHN0PC1yYmluZChjb2xsb0xzdCxhcnRpY2xlMVsoaS1zaXplKTooaStzaXplKV0pCmNvbGxvTHN0CmBgYAoKIyMg6Kqy6aGM77yS77yI57eg44KB5YiH44KKMTLmnIgz5pel77yJCiMjIyBhbm5vdGVkRGF0YeOCkuS9v+eUqOOBl+OBpuOAgeasoeOBru+8k+OBpOOBruWkieaVsOOCkuW8leaVsOOBqOOBl+OAgeWFsei1t+aDheWgseOCkuOCs+ODs+OCs+ODvOODgOODs+OCueihqOekuuOBp+WHuuWKm+OBmeOCi+mWouaVsOOCkuS9nOaIkOOBl+OBpuOBj+OBoOOBleOBhAojIyMjIOW8leaVsAotIOiomOS6iwotIOOCreODvOODr+ODvOODie+8iOiHquWIhuOBpzItM+WAi+mBuOOCk+OBp+OBj+OBoOOBleOBhO+8iQotIOOCueODkeODs+OBrumVt+OBlQojIyMjIE9wdGlvbjog44KC44GX44Kt44O844Ov44O844OJ44GM44OS44OD44OI44GX44Gq44GL44Gj44Gf5aC05ZCI44Gu5Yem55CG44Gr44Gk44GE44Gm44KC6ICD44GI44Gm44G/44Gm44GP44Gg44GV44GE77yI5a6f6KGM5L6LM+OCkuWPgueFp++8iQpgYGB7cn0Kbm9kZV9saXN0ID0gYygib2YiLCAiYXJ0IiwgIkphcGFuIikKc291cmNlKCJnZXRDb2xsby5SIikKYGBgCgojIyMg5a6f6KGM5L6LMQpgYGB7cn0KZ2V0Q29sbG8oYXJ0aWNsZUxzdFtbMl1dLCBjdXJyZW50X25vZGU9bm9kZV9saXN0WzFdKQpgYGAKIyMjIOWun+ihjOS+izIKYGBge3J9CmdldENvbGxvKGFydGljbGVMc3RbWzRdXSwgY3VycmVudF9ub2RlPW5vZGVfbGlzdFszXSwgc3Bhbj01KQpgYGAKCiMjIyDlrp/ooYzkvoszCmBgYHtyfQpnZXRDb2xsbyhhcnRpY2xlTHN0W1s0XV0sIGN1cnJlbnRfbm9kZT1ub2RlX2xpc3RbMl0pCmBgYAo=