版权声明:本套课程材料开源,使用和分享必须遵守「创作共用许可协议 CC BY-NC-SA」(来源引用-非商业用途使用-以相同方式共享)。


Chap04:字符处理

本章要点目录

## 本章所需R包
library(bruceR)
library(stringr)  # 字符串处理(加载bruceR时已默认加载stringr)
library(rvest)  # 网络爬虫

正则表达式基础

【知识点】正则表达式

正则表达式 / 规律表达式(regular/pattern expression,RegEx)

  • 含义:描述字符串规律(模式)的表达式
  • 作用:根据模式,匹配字符(自动处理文本)
    • 词频统计
    • 数据清洗
    • 文本提取
    • ……

(1)字符匹配

【实践1】字符匹配

text = "笔记本上安装SPSS22或20,授权码复制不上,导致安装失败。已经试了多次了,都安装不上SpSS,请问有遇到这种情况的吗?电脑是64位的,Spss是32位的,是这个原因吗?之前SpsS在其他笔记本和台式电脑上都安装成功了。"
cat(text)
笔记本上安装SPSS22或20,授权码复制不上,导致安装失败。已经试了多次了,都安装不上SpSS,请问有遇到这种情况的吗?电脑是64位的,Spss是32位的,是这个原因吗?之前SpsS在其他笔记本和台式电脑上都安装成功了。
str_extract_all(text, "SPSS")  # 完整字符串
[[1]]
[1] "SPSS"
str_extract_all(text, "SPSS|Spss")  # "|"表示或者
[[1]]
[1] "SPSS" "Spss"
str_extract_all(text, "[SPSS|spss]")  # 思考:为什么?
[[1]]
 [1] "S" "P" "S" "S" "S" "p" "S" "S" "S" "p" "s" "s" "S" "p" "s" "S"
str_extract_all(text, "[SsPp]")
[[1]]
 [1] "S" "P" "S" "S" "S" "p" "S" "S" "S" "p" "s" "s" "S" "p" "s" "S"
str_extract_all(text, "[Ss][Pp][Ss][Ss]")
[[1]]
[1] "SPSS" "SpSS" "Spss" "SpsS"
str_extract_all(text, "\\d")
[[1]]
[1] "2" "2" "2" "0" "6" "4" "3" "2"
str_extract_all(text, "\\d\\d")
[[1]]
[1] "22" "20" "64" "32"
str_extract_all(text, "\\d\\d位")
[[1]]
[1] "64位" "32位"

(2)条件匹配

【实践2】条件匹配

text = "笔记本上安装SPSS22或20,授权码复制不上,导致安装失败。已经试了多次了,都安装不上SpSS,请问有遇到这种情况的吗?电脑是64位的,Spss是32位的,是这个原因吗?之前SpsS在其他笔记本和台式电脑上都安装成功了。"
cat(text)
笔记本上安装SPSS22或20,授权码复制不上,导致安装失败。已经试了多次了,都安装不上SpSS,请问有遇到这种情况的吗?电脑是64位的,Spss是32位的,是这个原因吗?之前SpsS在其他笔记本和台式电脑上都安装成功了。
str_extract_all(text, "\\d{2}位")
[[1]]
[1] "64位" "32位"
str_extract_all(text, "[SsPp]{4}")
[[1]]
[1] "SPSS" "SpSS" "Spss" "SpsS"
str_extract_all(text, "[SP]{4}")
[[1]]
[1] "SPSS"
str_extract_all(text, "[Sps]+")
[[1]]
[1] "S"    "SS"   "SpSS" "Spss" "SpsS"
str_extract_all(text, "(Sps)+")
[[1]]
[1] "Sps" "Sps"
str_extract_all(text, "Sps+")
[[1]]
[1] "Spss" "Sps" 

(3)预查匹配

【实践3】预查匹配

text = "笔记本上安装SPSS22或20,授权码复制不上,导致安装失败。已经试了多次了,都安装不上SpSS,请问有遇到这种情况的吗?电脑是64位的,Spss是32位的,是这个原因吗?之前SpsS在其他笔记本和台式电脑上都安装成功了。"
cat(text)
笔记本上安装SPSS22或20,授权码复制不上,导致安装失败。已经试了多次了,都安装不上SpSS,请问有遇到这种情况的吗?电脑是64位的,Spss是32位的,是这个原因吗?之前SpsS在其他笔记本和台式电脑上都安装成功了。
str_extract_all(text, "\\d{2}位")
[[1]]
[1] "64位" "32位"
str_extract_all(text, "\\d{2}(?=位)")  # 后向肯定预查
[[1]]
[1] "64" "32"
str_extract_all(text, "\\d{2}(?!位)")  # 后向否定预查
[[1]]
[1] "22" "20"
str_extract_all(text, "(?<=SPSS)\\d{2}")  # 前向肯定预查
[[1]]
[1] "22"
str_extract_all(text, "(?<!SPSS)\\d{2}")  # 前向否定预查
[[1]]
[1] "20" "64" "32"

字符串处理实战

stringr

  • 共同参数
    • string:字符串输入
    • pattern:正则表达式
  • 常用函数
    • 判断:str_detect()
    • 计数:str_count()
    • 提取:str_extract()str_extract_all()
    • 替换:str_replace()str_replace_all()
    • 删除:str_remove()str_remove_all()
    • 分割:str_split()
    • 向量取匹配子集:str_subset()

【实践4】字符串综合处理

## 数据采集
## 已加载rvest包:library(rvest)
url = "https://psy.ecnu.edu.cn/17437/list.htm"  # 学院师资队伍页面
xml = url %>% read_html()  # 读取网页所有信息
xml
{html_document}
<html class="webplus-list">
[1] <head>\n<meta http-equiv="Content-Type" content="text/html; charset=UTF-8 ...
[2] <body class="wp-column-page">\r\n<!--头部开始-->\r\n<header class="wp-wrapper ...
[3] <script type="text/javascript" src="/_upload/tpl/0b/a5/2981/template2981/ ...
[4] <script type="text/javascript">\r\n$(function(){\r\n\t// 初始化SDAPP\r\n\tne ...
## 字符串处理(1):院系名称
menu = xml %>% html_elements(".column-item-link .column-name") %>% html_text2()
menu
 [1] "各级人才工程"               "认知与神经科学系"          
 [3] "毕生发展与学习科学系"       "社会与管理心理学系"        
 [5] "健康与临床心理学系"         "人工智能与人因工程系(筹)"
 [7] "实验员/专任助理研究员"      "党政管理"                  
 [9] "兼职教师"                   "全职博士后"                
[11] "荣休教师"                   "永远怀念教师名录"          
str_subset(menu, "系")
[1] "认知与神经科学系"           "毕生发展与学习科学系"      
[3] "社会与管理心理学系"         "健康与临床心理学系"        
[5] "人工智能与人因工程系(筹)"
str_subset(menu, "系$")
[1] "认知与神经科学系"     "毕生发展与学习科学系" "社会与管理心理学系"  
[4] "健康与临床心理学系"  
str_subset(menu, ".*系$")
[1] "认知与神经科学系"     "毕生发展与学习科学系" "社会与管理心理学系"  
[4] "健康与临床心理学系"  
str_subset(menu, ".*(?=系$)")  # 只是取子集,并不能预查提取
[1] "认知与神经科学系"     "毕生发展与学习科学系" "社会与管理心理学系"  
[4] "健康与临床心理学系"  
str_extract(menu, ".*(?=系$)")  # 真正的预查提取
 [1] NA                   "认知与神经科学"     "毕生发展与学习科学"
 [4] "社会与管理心理学"   "健康与临床心理学"   NA                  
 [7] NA                   NA                   NA                  
[10] NA                   NA                   NA                  
str_extract(menu, ".*(?=系$)") %>% na.omit()  # 移除缺失值NA
[1] "认知与神经科学"     "毕生发展与学习科学" "社会与管理心理学"  
[4] "健康与临床心理学"  
attr(,"na.action")
[1]  1  6  7  8  9 10 11 12
attr(,"class")
[1] "omit"
## 字符串处理(2):人才计划
title = xml %>% html_elements(".subcolumn-name") %>% html_text2()
title
 [1] "华东师范大学特聘教授"     "国家级人才项目"          
 [3] "国家级青年人才项目"       "中国科协托举人才"        
 [5] "教育部新世纪人才"         "上海市曙光学者"          
 [7] "上海市浦江学者"           "上海市青年科技启明星项目"
 [9] "上海市晨光计划"           "上海市扬帆计划"          
str_subset(title, "人才")
[1] "国家级人才项目"     "国家级青年人才项目" "中国科协托举人才"  
[4] "教育部新世纪人才"  
str_subset(title, "人才$")
[1] "中国科协托举人才" "教育部新世纪人才"
str_subset(title, "上海市.*")
[1] "上海市曙光学者"           "上海市浦江学者"          
[3] "上海市青年科技启明星项目" "上海市晨光计划"          
[5] "上海市扬帆计划"          
str_subset(title, "^上海市")
[1] "上海市曙光学者"           "上海市浦江学者"          
[3] "上海市青年科技启明星项目" "上海市晨光计划"          
[5] "上海市扬帆计划"          
str_extract(title, "(?<=^上海市).*(?=学者$|计划$)") %>% na.omit()
[1] "曙光" "浦江" "晨光" "扬帆"
attr(,"na.action")
[1] 1 2 3 4 5 8
attr(,"class")
[1] "omit"
## 字符串处理(3):教师姓名
name = xml %>% html_elements(".column-news-title") %>% html_text2()
name
 [1] "周晓林 博士"   "蒯曙光 博士"   "周晓林 博士"   "蔡清 博士"    
 [5] "郝宁 博士"     "蒯曙光 博士"   "高晓雪 博士"   "罗艺 博士"    
 [9] "崔丽娟 博士"   "刘永芳 博士"   "庞维国 博士"   "郝宁 博士"    
[13] "刘俊升 博士"   "陆静怡 博士"   "蔡清 博士"     "包寒吴霜 博士"
[17] "李先春 博士"   "刘俊升 博士"   "孟慧 博士"     "庞维国 博士"  
[21] "宋永宁博士"    "王弘毅 博士"   "王青 博士"     "席居哲 博士"  
[25] "谢鑫宇博士"    "周宁宁 博士"   "张琪 博士"     "包寒吴霜 博士"
[29] "梁一鸣 博士"   "陆静怡 博士"   "王青 博士"     "杨莹 博士"    
[33] "周晗昱 博士"   "陈 曦 博士"    "梁一鸣 博士"   "李世佳 博士"  
[37] "杨莹 博士"     "周晗昱 博士"  
table(name)
name
包寒吴霜 博士     蔡清 博士    陈 曦 博士   崔丽娟 博士   高晓雪 博士 
            2             2             1             1             1 
    郝宁 博士   蒯曙光 博士   李世佳 博士   李先春 博士   梁一鸣 博士 
            2             2             1             1             2 
  刘俊升 博士   刘永芳 博士   陆静怡 博士     罗艺 博士     孟慧 博士 
            2             1             2             1             1 
  庞维国 博士    宋永宁博士   王弘毅 博士     王青 博士   席居哲 博士 
            2             1             1             2             1 
   谢鑫宇博士     杨莹 博士     张琪 博士   周晗昱 博士   周宁宁 博士 
            1             2             1             2             1 
  周晓林 博士 
            2 
unique(name)
 [1] "周晓林 博士"   "蒯曙光 博士"   "蔡清 博士"     "郝宁 博士"    
 [5] "高晓雪 博士"   "罗艺 博士"     "崔丽娟 博士"   "刘永芳 博士"  
 [9] "庞维国 博士"   "刘俊升 博士"   "陆静怡 博士"   "包寒吴霜 博士"
[13] "李先春 博士"   "孟慧 博士"     "宋永宁博士"    "王弘毅 博士"  
[17] "王青 博士"     "席居哲 博士"   "谢鑫宇博士"    "周宁宁 博士"  
[21] "张琪 博士"     "梁一鸣 博士"   "杨莹 博士"     "周晗昱 博士"  
[25] "陈 曦 博士"    "李世佳 博士"  
name %>% unique() %>% sort()
 [1] "包寒吴霜 博士" "蔡清 博士"     "陈 曦 博士"    "崔丽娟 博士"  
 [5] "高晓雪 博士"   "郝宁 博士"     "蒯曙光 博士"   "李世佳 博士"  
 [9] "李先春 博士"   "梁一鸣 博士"   "刘俊升 博士"   "刘永芳 博士"  
[13] "陆静怡 博士"   "罗艺 博士"     "孟慧 博士"     "庞维国 博士"  
[17] "宋永宁博士"    "王弘毅 博士"   "王青 博士"     "席居哲 博士"  
[21] "谢鑫宇博士"    "杨莹 博士"     "张琪 博士"     "周晗昱 博士"  
[25] "周宁宁 博士"   "周晓林 博士"  
name %>% unique() %>% sort() %>% str_remove_all("博士|\\s")
 [1] "包寒吴霜" "蔡清"     "陈曦"     "崔丽娟"   "高晓雪"   "郝宁"    
 [7] "蒯曙光"   "李世佳"   "李先春"   "梁一鸣"   "刘俊升"   "刘永芳"  
[13] "陆静怡"   "罗艺"     "孟慧"     "庞维国"   "宋永宁"   "王弘毅"  
[19] "王青"     "席居哲"   "谢鑫宇"   "杨莹"     "张琪"     "周晗昱"  
[25] "周宁宁"   "周晓林"  
name %>%
  unique() %>%
  sort() %>%
  str_remove_all("博士|\\s") %>%
  str_subset("宁")
[1] "郝宁"   "宋永宁" "周宁宁"
name %>%
  unique() %>%
  sort() %>%
  str_remove_all("博士|\\s") %>%
  str_subset("^赵|^钱|^孙|^李")  # 赵钱孙李,周吴郑王
[1] "李世佳" "李先春"
name %>%
  unique() %>%
  sort() %>%
  str_remove_all("博士|\\s") %>%
  str_subset("^[周吴郑王]")  # 赵钱孙李,周吴郑王
[1] "王弘毅" "王青"   "周晗昱" "周宁宁" "周晓林"
## 补充:字符向量 vs. 固定位置字符提取
"Psychology"[1]  # 长度为1的字符向量,[1]仍然是字符串,而不是第1个字母
[1] "Psychology"
str_sub("Psychology", 1, 1)  # 提取第1个字母
[1] "P"
str_sub("Psychology", 1, 5)  # 提取第1~5个字母
[1] "Psych"

【探索发现】stringr包的函数功能

  • 检测匹配

  • 子集提取

  • 长度控制

  • 替换修改

  • 拼接分割

【作业5】字符向量处理

作业要求:

  • 围绕stringr包提供的字符串示例数据fruit(80个水果名称),运用str_detect()str_subset()str_extract_all()str_replace_all()stringr包函数中的至少2个,任意完成至少2种字符串处理任务,并对代码和结果进行简单的注释和解读
  • 使用R Markdown完成

平台提交:

  • 运行得到的HTML网页,及其关键部分截图
## 附:作业需要用到的数据集(字符向量)
stringr::fruit
 [1] "apple"             "apricot"           "avocado"          
 [4] "banana"            "bell pepper"       "bilberry"         
 [7] "blackberry"        "blackcurrant"      "blood orange"     
[10] "blueberry"         "boysenberry"       "breadfruit"       
[13] "canary melon"      "cantaloupe"        "cherimoya"        
[16] "cherry"            "chili pepper"      "clementine"       
[19] "cloudberry"        "coconut"           "cranberry"        
[22] "cucumber"          "currant"           "damson"           
[25] "date"              "dragonfruit"       "durian"           
[28] "eggplant"          "elderberry"        "feijoa"           
[31] "fig"               "goji berry"        "gooseberry"       
[34] "grape"             "grapefruit"        "guava"            
[37] "honeydew"          "huckleberry"       "jackfruit"        
[40] "jambul"            "jujube"            "kiwi fruit"       
[43] "kumquat"           "lemon"             "lime"             
[46] "loquat"            "lychee"            "mandarine"        
[49] "mango"             "mulberry"          "nectarine"        
[52] "nut"               "olive"             "orange"           
[55] "pamelo"            "papaya"            "passionfruit"     
[58] "peach"             "pear"              "persimmon"        
[61] "physalis"          "pineapple"         "plum"             
[64] "pomegranate"       "pomelo"            "purple mangosteen"
[67] "quince"            "raisin"            "rambutan"         
[70] "raspberry"         "redcurrant"        "rock melon"       
[73] "salal berry"       "satsuma"           "star fruit"       
[76] "strawberry"        "tamarillo"         "tangerine"        
[79] "ugli fruit"        "watermelon"       
LS0tDQp0aXRsZTogIuOAilLor63oqIDjgIvnrKw056ug77ya5a2X56ym5aSE55CGIg0Kc3VidGl0bGU6IDxhIGhyZWY9Imh0dHBzOi8vcHN5Y2hicnVjZS5naXRodWIuaW8vUkNvdXJzZS8iPuWbnuWIsOivvueoi+S4u+mhtTwvYT4NCmF1dGhvcjogIuaOiOivvuaVmeW4iO+8muWMheWvkuWQtOmcnCINCiMgZGF0ZTogImByIFN5cy5EYXRlKClgIg0Kb3V0cHV0Og0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogdHJ1ZQ0KICAgIHRvY19kZXB0aDogMw0KICAgIHRvY19mbG9hdDoNCiAgICAgIGNvbGxhcHNlZDogZmFsc2UNCiAgICAgIHNtb290aF9zY3JvbGw6IGZhbHNlDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KICAgIGFuY2hvcl9zZWN0aW9uczogdHJ1ZQ0KICAgIGhpZ2hsaWdodDogcHlnbWVudHMNCiAgICBjc3M6IFJtZENTUy5jc3MNCi0tLQ0KDQpgYGB7PWh0bWx9DQo8cCBzdHlsZT0iZm9udC1zaXplOiAxMnB4Ij7niYjmnYPlo7DmmI7vvJrmnKzlpZfor77nqIvmnZDmlpnlvIDmupDvvIzkvb/nlKjlkozliIbkuqvlv4XpobvpgbXlrojjgIzliJvkvZzlhbHnlKjorrjlj6/ljY/orq4gQ0MgQlktTkMtU0HjgI3vvIjmnaXmupDlvJXnlKgt6Z2e5ZWG5Lia55So6YCU5L2/55SoLeS7peebuOWQjOaWueW8j+WFseS6q++8ieOAgjxpbWcgc3JjPSJpbWcvQ0MtQlktTkMtU0EuanBnIiB3aWR0aD0iMTIwcHgiIGhlaWdodD0iNDJweCIgc3R5bGU9ImZsb2F0OiByaWdodCIgLz48L3A+DQpgYGANCg0KYGBge3IgQ29uZmlnLCBpbmNsdWRlPUZBTFNFfQ0Kb3B0aW9ucygNCiAga25pdHIua2FibGUuTkEgPSAiIiwNCiAgZGlnaXRzID0gNA0KKQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KA0KICBjb21tZW50ID0gIiIsDQogIGZpZy53aWR0aCA9IDYsDQogIGZpZy5oZWlnaHQgPSA0LA0KICBkcGkgPSAzMDANCikNCmBgYA0KDQotLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KIyBDaGFwMDTvvJrlrZfnrKblpITnkIYNCg0KIyMjIyDlvoDmnJ/opoHngrnlm57pob4NCg0KLSAgIFtDaGFwMDMgXCMg5pWw5o2u5qGG77yIZGF0YSBmcmFtZe+8iV0oaHR0cHM6Ly9wc3ljaGJydWNlLmdpdGh1Yi5pby9SQ291cnNlL0NoYXAwMyMlRTYlOTUlQjAlRTYlOEQlQUUlRTYlQTElODZkYXRhLWZyYW1lKXsudXJpfQ0KLSAgIFtDaGFwMDMgXCMg4oCc5LiA56uZ5byP4oCd5pWw5o2u5a+85YWl5a+85Ye6XShodHRwczovL3BzeWNoYnJ1Y2UuZ2l0aHViLmlvL1JDb3Vyc2UvQ2hhcDAzIyVFNSVBNCU5NiVFOSU4MyVBOCVFNiU5NSVCMCVFNiU4RCVBRSVFNSVBRiVCQyVFNSU4NSVBNSVFNSVBRiVCQyVFNSU4NyVCQSl7LnVyaX0NCg0KIyMjIyDmnKznq6DopoHngrnnm67lvZUNCg0KLSAgIFvjgJDnn6Xor4bngrnjgJHmraPliJnooajovr7lvI9dKCPnn6Xor4bngrnmraPliJnooajovr7lvI8p77yI6YeN54K577yJDQotICAgW+OAkOWunui3tTHjgJHlrZfnrKbljLnphY1dKCPlrp7ot7Ux5a2X56ym5Yy56YWNKQ0KLSAgIFvjgJDlrp7ot7Uy44CR5p2h5Lu25Yy56YWNXSgj5a6e6Le1MuadoeS7tuWMuemFjSkNCi0gICBb44CQ5a6e6Le1M+OAkemihOafpeWMuemFjV0oI+Wunui3tTPpooTmn6XljLnphY0pDQotICAgW+OAkOWunui3tTTjgJHlrZfnrKbkuLLnu7zlkIjlpITnkIZdKCPlrp7ot7U05a2X56ym5Liy57u85ZCI5aSE55CGKe+8iOmHjeeCue+8iQ0KLSAgIFvjgJDmjqLntKLlj5HnjrDjgJFzdHJpbmdy5YyF55qE5Ye95pWw5Yqf6IO9XSgj5o6i57Si5Y+R546wc3RyaW5ncuWMheeahOWHveaVsOWKn+iDvSkNCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQojIyDmnKznq6DmiYDpnIBS5YyFDQpsaWJyYXJ5KGJydWNlUikNCmxpYnJhcnkoc3RyaW5ncikgICMg5a2X56ym5Liy5aSE55CG77yI5Yqg6L29YnJ1Y2VS5pe25bey6buY6K6k5Yqg6L29c3RyaW5ncu+8iQ0KbGlicmFyeShydmVzdCkgICMg572R57uc54is6JmrDQpgYGANCg0KIyDmraPliJnooajovr7lvI/ln7rnoYANCg0KIyMjIyDjgJDnn6Xor4bngrnjgJHmraPliJnooajovr7lvI8geyPnn6Xor4bngrnmraPliJnooajovr7lvI99DQoNCioq5q2j5YiZ6KGo6L6+5byPIC8g6KeE5b6L6KGo6L6+5byP77yIcmVndWxhci9wYXR0ZXJuIGV4cHJlc3Npb27vvIxSZWdFeO+8iSoqDQoNCi0gICDlkKvkuYnvvJrmj4/ov7DlrZfnrKbkuLLop4TlvovvvIjmqKHlvI/vvInnmoTooajovr7lvI8NCi0gICDkvZznlKjvvJrmoLnmja7mqKHlvI/vvIzljLnphY3lrZfnrKbvvIjoh6rliqjlpITnkIbmlofmnKzvvIkNCiAgICAtICAg6K+N6aKR57uf6K6hDQogICAgLSAgIOaVsOaNrua4hea0lw0KICAgIC0gICDmlofmnKzmj5Dlj5YNCiAgICAtICAg4oCm4oCmDQoNCiMjIO+8iDHvvInlrZfnrKbljLnphY0NCg0KIVtdKGltYWdlcy9jbGlwYm9hcmQtNDA4NjU3NjU4My5wbmcpDQoNCiMjIyMg44CQ5a6e6Le1MeOAkeWtl+espuWMuemFjSB7I+Wunui3tTHlrZfnrKbljLnphY19DQoNCiFbXShpbWFnZXMvY2xpcGJvYXJkLTE1NzYzOTExODIucG5nKQ0KDQpgYGB7cn0NCnRleHQgPSAi56yU6K6w5pys5LiK5a6J6KOFU1BTUzIy5oiWMjDvvIzmjojmnYPnoIHlpI3liLbkuI3kuIrvvIzlr7zoh7Tlronoo4XlpLHotKXjgILlt7Lnu4/or5XkuoblpJrmrKHkuobvvIzpg73lronoo4XkuI3kuIpTcFNT77yM6K+36Zeu5pyJ6YGH5Yiw6L+Z56eN5oOF5Ya155qE5ZCX77yf55S16ISR5pivNjTkvY3nmoTvvIxTcHNz5pivMzLkvY3nmoTvvIzmmK/ov5nkuKrljp/lm6DlkJfvvJ/kuYvliY1TcHNT5Zyo5YW25LuW56yU6K6w5pys5ZKM5Y+w5byP55S16ISR5LiK6YO95a6J6KOF5oiQ5Yqf5LqG44CCIg0KY2F0KHRleHQpDQoNCnN0cl9leHRyYWN0X2FsbCh0ZXh0LCAiU1BTUyIpICAjIOWujOaVtOWtl+espuS4sg0Kc3RyX2V4dHJhY3RfYWxsKHRleHQsICJTUFNTfFNwc3MiKSAgIyAifCLooajnpLrmiJbogIUNCnN0cl9leHRyYWN0X2FsbCh0ZXh0LCAiW1NQU1N8c3Bzc10iKSAgIyDmgJ3ogIPvvJrkuLrku4DkuYjvvJ8NCnN0cl9leHRyYWN0X2FsbCh0ZXh0LCAiW1NzUHBdIikNCnN0cl9leHRyYWN0X2FsbCh0ZXh0LCAiW1NzXVtQcF1bU3NdW1NzXSIpDQoNCnN0cl9leHRyYWN0X2FsbCh0ZXh0LCAiXFxkIikNCnN0cl9leHRyYWN0X2FsbCh0ZXh0LCAiXFxkXFxkIikNCnN0cl9leHRyYWN0X2FsbCh0ZXh0LCAiXFxkXFxk5L2NIikNCmBgYA0KDQojIyDvvIgy77yJ5p2h5Lu25Yy56YWNDQoNCiFbXShpbWFnZXMvY2xpcGJvYXJkLTMwMjEyODcwODAucG5nKQ0KDQojIyMjIOOAkOWunui3tTLjgJHmnaHku7bljLnphY0geyPlrp7ot7Uy5p2h5Lu25Yy56YWNfQ0KDQpgYGB7cn0NCnRleHQgPSAi56yU6K6w5pys5LiK5a6J6KOFU1BTUzIy5oiWMjDvvIzmjojmnYPnoIHlpI3liLbkuI3kuIrvvIzlr7zoh7Tlronoo4XlpLHotKXjgILlt7Lnu4/or5XkuoblpJrmrKHkuobvvIzpg73lronoo4XkuI3kuIpTcFNT77yM6K+36Zeu5pyJ6YGH5Yiw6L+Z56eN5oOF5Ya155qE5ZCX77yf55S16ISR5pivNjTkvY3nmoTvvIxTcHNz5pivMzLkvY3nmoTvvIzmmK/ov5nkuKrljp/lm6DlkJfvvJ/kuYvliY1TcHNT5Zyo5YW25LuW56yU6K6w5pys5ZKM5Y+w5byP55S16ISR5LiK6YO95a6J6KOF5oiQ5Yqf5LqG44CCIg0KY2F0KHRleHQpDQoNCnN0cl9leHRyYWN0X2FsbCh0ZXh0LCAiXFxkezJ95L2NIikNCnN0cl9leHRyYWN0X2FsbCh0ZXh0LCAiW1NzUHBdezR9IikNCnN0cl9leHRyYWN0X2FsbCh0ZXh0LCAiW1NQXXs0fSIpDQpzdHJfZXh0cmFjdF9hbGwodGV4dCwgIltTcHNdKyIpDQpzdHJfZXh0cmFjdF9hbGwodGV4dCwgIihTcHMpKyIpDQpzdHJfZXh0cmFjdF9hbGwodGV4dCwgIlNwcysiKQ0KYGBgDQoNCiMjIO+8iDPvvInpooTmn6XljLnphY0NCg0KIVtdKGltYWdlcy9jbGlwYm9hcmQtNDE4NDQyODU0NS5wbmcpDQoNCiMjIyMg44CQ5a6e6Le1M+OAkemihOafpeWMuemFjSB7I+Wunui3tTPpooTmn6XljLnphY19DQoNCmBgYHtyfQ0KdGV4dCA9ICLnrJTorrDmnKzkuIrlronoo4VTUFNTMjLmiJYyMO+8jOaOiOadg+eggeWkjeWItuS4jeS4iu+8jOWvvOiHtOWuieijheWksei0peOAguW3sue7j+ivleS6huWkmuasoeS6hu+8jOmDveWuieijheS4jeS4ilNwU1PvvIzor7fpl67mnInpgYfliLDov5nnp43mg4XlhrXnmoTlkJfvvJ/nlLXohJHmmK82NOS9jeeahO+8jFNwc3PmmK8zMuS9jeeahO+8jOaYr+i/meS4quWOn+WboOWQl++8n+S5i+WJjVNwc1PlnKjlhbbku5bnrJTorrDmnKzlkozlj7DlvI/nlLXohJHkuIrpg73lronoo4XmiJDlip/kuobjgIIiDQpjYXQodGV4dCkNCg0Kc3RyX2V4dHJhY3RfYWxsKHRleHQsICJcXGR7Mn3kvY0iKQ0Kc3RyX2V4dHJhY3RfYWxsKHRleHQsICJcXGR7Mn0oPz3kvY0pIikgICMg5ZCO5ZCR6IKv5a6a6aKE5p+lDQpzdHJfZXh0cmFjdF9hbGwodGV4dCwgIlxcZHsyfSg/IeS9jSkiKSAgIyDlkI7lkJHlkKblrprpooTmn6UNCnN0cl9leHRyYWN0X2FsbCh0ZXh0LCAiKD88PVNQU1MpXFxkezJ9IikgICMg5YmN5ZCR6IKv5a6a6aKE5p+lDQpzdHJfZXh0cmFjdF9hbGwodGV4dCwgIig/PCFTUFNTKVxcZHsyfSIpICAjIOWJjeWQkeWQpuWumumihOafpQ0KYGBgDQoNCiMg5a2X56ym5Liy5aSE55CG5a6e5oiYDQoNCmBzdHJpbmdyYOWMhQ0KDQotICAg5YWx5ZCM5Y+C5pWwDQogICAgLSAgIGBzdHJpbmdg77ya5a2X56ym5Liy6L6T5YWlDQogICAgLSAgIGBwYXR0ZXJuYO+8muato+WImeihqOi+vuW8jw0KLSAgIOW4uOeUqOWHveaVsA0KICAgIC0gICDliKTmlq3vvJpgc3RyX2RldGVjdCgpYA0KICAgIC0gICDorqHmlbDvvJpgc3RyX2NvdW50KClgDQogICAgLSAgIOaPkOWPlu+8mmBzdHJfZXh0cmFjdCgpYOOAgWBzdHJfZXh0cmFjdF9hbGwoKWANCiAgICAtICAg5pu/5o2i77yaYHN0cl9yZXBsYWNlKClg44CBYHN0cl9yZXBsYWNlX2FsbCgpYA0KICAgIC0gICDliKDpmaTvvJpgc3RyX3JlbW92ZSgpYOOAgWBzdHJfcmVtb3ZlX2FsbCgpYA0KICAgIC0gICDliIblibLvvJpgc3RyX3NwbGl0KClgDQogICAgLSAgIOWQkemHj+WPluWMuemFjeWtkOmbhu+8mmBzdHJfc3Vic2V0KClgDQoNCiMjIyMg44CQ5a6e6Le1NOOAkeWtl+espuS4sue7vOWQiOWkhOeQhiB7I+Wunui3tTTlrZfnrKbkuLLnu7zlkIjlpITnkIZ9DQoNCi0gICDor77nqIvlm57pob7vvJpbQ2hhcDAzIFwjIOmdmeaAgee9kemhteino+aekF0oaHR0cHM6Ly9wc3ljaGJydWNlLmdpdGh1Yi5pby9SQ291cnNlL0NoYXAwMyMlRTUlQUUlOUUlRTglQjclQjU1JUU5JTlEJTk5JUU2JTgwJTgxJUU3JUJEJTkxJUU5JUExJUI1JUU4JUE3JUEzJUU2JTlFJTkwKXsudXJpfQ0KLSAgIOe9kemhtee0oOadkO+8mlvljY7kuJzluIjojIPlpKflrablv4PnkIbkuI7orqTnn6Xnp5HlrablrabpmaLigJzluIjotYTpmJ/kvI3igJ1dKGh0dHBzOi8vcHN5LmVjbnUuZWR1LmNuLzE3NDM3L2xpc3QuaHRtKXsudXJpfQ0KDQpgYGB7cn0NCiMjIOaVsOaNrumHh+mbhg0KIyMg5bey5Yqg6L29cnZlc3TljIXvvJpsaWJyYXJ5KHJ2ZXN0KQ0KdXJsID0gImh0dHBzOi8vcHN5LmVjbnUuZWR1LmNuLzE3NDM3L2xpc3QuaHRtIiAgIyDlrabpmaLluIjotYTpmJ/kvI3pobXpnaINCnhtbCA9IHVybCAlPiUgcmVhZF9odG1sKCkgICMg6K+75Y+W572R6aG15omA5pyJ5L+h5oGvDQp4bWwNCg0KIyMg5a2X56ym5Liy5aSE55CG77yIMe+8ie+8mumZouezu+WQjeensA0KbWVudSA9IHhtbCAlPiUgaHRtbF9lbGVtZW50cygiLmNvbHVtbi1pdGVtLWxpbmsgLmNvbHVtbi1uYW1lIikgJT4lIGh0bWxfdGV4dDIoKQ0KbWVudQ0KDQpzdHJfc3Vic2V0KG1lbnUsICLns7siKQ0Kc3RyX3N1YnNldChtZW51LCAi57O7JCIpDQpzdHJfc3Vic2V0KG1lbnUsICIuKuezuyQiKQ0Kc3RyX3N1YnNldChtZW51LCAiLiooPz3ns7skKSIpICAjIOWPquaYr+WPluWtkOmbhu+8jOW5tuS4jeiDvemihOafpeaPkOWPlg0Kc3RyX2V4dHJhY3QobWVudSwgIi4qKD8957O7JCkiKSAgIyDnnJ/mraPnmoTpooTmn6Xmj5Dlj5YNCnN0cl9leHRyYWN0KG1lbnUsICIuKig/PeezuyQpIikgJT4lIG5hLm9taXQoKSAgIyDnp7vpmaTnvLrlpLHlgLxOQQ0KDQojIyDlrZfnrKbkuLLlpITnkIbvvIgy77yJ77ya5Lq65omN6K6h5YiSDQp0aXRsZSA9IHhtbCAlPiUgaHRtbF9lbGVtZW50cygiLnN1YmNvbHVtbi1uYW1lIikgJT4lIGh0bWxfdGV4dDIoKQ0KdGl0bGUNCnN0cl9zdWJzZXQodGl0bGUsICLkurrmiY0iKQ0Kc3RyX3N1YnNldCh0aXRsZSwgIuS6uuaJjSQiKQ0Kc3RyX3N1YnNldCh0aXRsZSwgIuS4iua1t+W4gi4qIikNCnN0cl9zdWJzZXQodGl0bGUsICJe5LiK5rW35biCIikNCnN0cl9leHRyYWN0KHRpdGxlLCAiKD88PV7kuIrmtbfluIIpLiooPz3lrabogIUkfOiuoeWIkiQpIikgJT4lIG5hLm9taXQoKQ0KDQojIyDlrZfnrKbkuLLlpITnkIbvvIgz77yJ77ya5pWZ5biI5aeT5ZCNDQpuYW1lID0geG1sICU+JSBodG1sX2VsZW1lbnRzKCIuY29sdW1uLW5ld3MtdGl0bGUiKSAlPiUgaHRtbF90ZXh0MigpDQpuYW1lDQoNCnRhYmxlKG5hbWUpDQp1bmlxdWUobmFtZSkNCg0KbmFtZSAlPiUgdW5pcXVlKCkgJT4lIHNvcnQoKQ0KbmFtZSAlPiUgdW5pcXVlKCkgJT4lIHNvcnQoKSAlPiUgc3RyX3JlbW92ZV9hbGwoIuWNmuWjq3xcXHMiKQ0KDQpuYW1lICU+JQ0KICB1bmlxdWUoKSAlPiUNCiAgc29ydCgpICU+JQ0KICBzdHJfcmVtb3ZlX2FsbCgi5Y2a5aOrfFxccyIpICU+JQ0KICBzdHJfc3Vic2V0KCLlroEiKQ0KDQpuYW1lICU+JQ0KICB1bmlxdWUoKSAlPiUNCiAgc29ydCgpICU+JQ0KICBzdHJfcmVtb3ZlX2FsbCgi5Y2a5aOrfFxccyIpICU+JQ0KICBzdHJfc3Vic2V0KCJe6LW1fF7pkrF8XuWtmXxe5p2OIikgICMg6LW16ZKx5a2Z5p2O77yM5ZGo5ZC06YOR546LDQoNCm5hbWUgJT4lDQogIHVuaXF1ZSgpICU+JQ0KICBzb3J0KCkgJT4lDQogIHN0cl9yZW1vdmVfYWxsKCLljZrlo6t8XFxzIikgJT4lDQogIHN0cl9zdWJzZXQoIl5b5ZGo5ZC06YOR546LXSIpICAjIOi1temSseWtmeadju+8jOWRqOWQtOmDkeeOiw0KDQojIyDooaXlhYXvvJrlrZfnrKblkJHph48gdnMuIOWbuuWumuS9jee9ruWtl+espuaPkOWPlg0KIlBzeWNob2xvZ3kiWzFdICAjIOmVv+W6puS4ujHnmoTlrZfnrKblkJHph4/vvIxbMV3ku43nhLbmmK/lrZfnrKbkuLLvvIzogIzkuI3mmK/nrKwx5Liq5a2X5q+NDQpzdHJfc3ViKCJQc3ljaG9sb2d5IiwgMSwgMSkgICMg5o+Q5Y+W56ysMeS4quWtl+avjQ0Kc3RyX3N1YigiUHN5Y2hvbG9neSIsIDEsIDUpICAjIOaPkOWPluesrDF+NeS4quWtl+avjQ0KYGBgDQoNCiMjIyMg44CQ5o6i57Si5Y+R546w44CRc3RyaW5ncuWMheeahOWHveaVsOWKn+iDvSB7I+aOoue0ouWPkeeOsHN0cmluZ3LljIXnmoTlh73mlbDlip/og719DQoNCi0gICDmo4DmtYvljLnphY0NCg0KIVtdKGltYWdlcy9jbGlwYm9hcmQtMTgwODgzNDA5OC5wbmcpDQoNCi0gICDlrZDpm4bmj5Dlj5YNCg0KIVtdKGltYWdlcy9jbGlwYm9hcmQtOTAwODAyNDAyLnBuZykNCg0KLSAgIOmVv+W6puaOp+WItg0KDQohW10oaW1hZ2VzL2NsaXBib2FyZC00MjAwNjYyNDExLnBuZykNCg0KLSAgIOabv+aNouS/ruaUuQ0KDQohW10oaW1hZ2VzL2NsaXBib2FyZC0zMTA0MzExMTM1LnBuZykNCg0KLSAgIOaLvOaOpeWIhuWJsg0KDQohW10oaW1hZ2VzL2NsaXBib2FyZC0zMzMzNDY2OTcyLnBuZykNCg0KIyDjgJDkvZzkuJo144CR5a2X56ym5ZCR6YeP5aSE55CGDQoNCuS9nOS4muimgeaxgu+8mg0KDQotICAg5Zu057uVYHN0cmluZ3Jg5YyF5o+Q5L6b55qE5a2X56ym5Liy56S65L6L5pWw5o2uYGZydWl0YO+8iDgw5Liq5rC05p6c5ZCN56ew77yJ77yM6L+Q55SoYHN0cl9kZXRlY3QoKWDjgIFgc3RyX3N1YnNldCgpYOOAgWBzdHJfZXh0cmFjdF9hbGwoKWDjgIFgc3RyX3JlcGxhY2VfYWxsKClg562JYHN0cmluZ3Jg5YyF5Ye95pWw5Lit55qE6Iez5bCRMuS4qu+8jOS7u+aEj+WujOaIkOiHs+WwkTLnp43lrZfnrKbkuLLlpITnkIbku7vliqHvvIzlubblr7nku6PnoIHlkoznu5Pmnpzov5vooYznroDljZXnmoTms6jph4rlkozop6Por7sNCi0gICDkvb/nlKhSIE1hcmtkb3du5a6M5oiQDQoNCuW5s+WPsOaPkOS6pO+8mg0KDQotICAg6L+Q6KGM5b6X5Yiw55qESFRNTOe9kemhte+8jOWPiuWFtuWFs+mUrumDqOWIhuaIquWbvg0KDQpgYGB7cn0NCiMjIOmZhO+8muS9nOS4mumcgOimgeeUqOWIsOeahOaVsOaNrumbhu+8iOWtl+espuWQkemHj++8iQ0Kc3RyaW5ncjo6ZnJ1aXQNCmBgYA0K