packages = c(
  "dplyr","ggplot2","stringr", "dslabs", "readr", "tidyr", "purrr",
  "lubridate", "rvest"
  )
existing = as.character(installed.packages()[,1])
for(pkg in packages[!(packages %in% existing)]) install.packages(pkg)
rm(list=ls(all=T))
Sys.setlocale("LC_ALL","C")
[1] "C"
options(digits=4, scipen=12)
library(rvest)
library(readr)
library(dplyr)
library(ggplot2)
library(stringr)
library(lubridate)
library(tidyr)
library(dslabs)

Tidy Data

Web Scraping
library(rvest)
url = "https://en.wikipedia.org/wiki/Murder_in_the_United_States_by_state"
h = read_html(url)
tab = html_nodes(h, "table")[[2]] %>%
  html_table %>% 
  setNames(c(
    "state","population","total","murders","gun_murders",
    "gun_ownersjip","total_rate","murder_rate","gun_murder_rate"))

A. String Processing Overview


B. String Processing Part 1

B1. String Parsing

Q1: Which of the following is NOT an application of string parsing?

  • Formatting numbers and characters so they can easily be displayed in deliverables like papers and presentations.
B2. Defining Strings: Single and Double Quotes and How to Escape

Q1: Which of the following commands would not give you an error in R?

cat(" LeBron James is 6'8\" ")
 LeBron James is 6'8" 
B3. stringr Package

Q1: Which of the following are advantages of the stringr package over string processing functions in base R? Select all that apply.

  • Functions in stringr all start with “str_”, which makes them easy to look up using autocomplete.
  • Stringr functions work better with pipes.
  • he order of arguments is more consistent in stringr functions than in base R.
B4. Case Study 1: US Murders Data
sapply(tab, str_detect, ",") %>% colSums
          state      population           total         murders 
              0              51               3               2 
    gun_murders   gun_ownersjip      total_rate     murder_rate 
              1               0               0               0 
gun_murder_rate 
              0 
# 向量是同一種資料的集合(欄),可以是字串向量,數值向量,邏輯向量等等...
# sapply對一個collcetion(即tab,tab是一個資料框)做detect,回傳一個布林矩陣,再算成colSums
tab2 = tab %>% mutate_at(2:3, parse_number) 
# mutatae是對某東西做轉換 # parse可以消除逗號並將字串轉回數字
sapply(tab2, str_detect, ",") %>% colSums
          state      population           total         murders 
              0               0               0               2 
    gun_murders   gun_ownersjip      total_rate     murder_rate 
              1               0               0               0 
gun_murder_rate 
              0 

Q1: You have a dataframe of monthly sales and profits in R

dat = read.table("data/sales.txt", header=T, sep="", stringsAsFactors=F)
dat
     Month    Sales  Profit
1  January $128,568 $16,234
2 February $109,523 $12,876
3    March $115,468 $17,920
4    April $122,274 $15,825
5      May $117,921 $15,437

Which of the following commands could convert the sales and profits columns to numeric? Select all that apply.

dat %>% mutate_at(2:3, parse_number)
     Month  Sales Profit
1  January 128568  16234
2 February 109523  12876
3    March 115468  17920
4    April 122274  15825
5      May 117921  15437
dat %>% mutate_at(2:3, funs(str_replace_all(., c("\\$|,"), "")))
     Month  Sales Profit
1  January 128568  16234
2 February 109523  12876
3    March 115468  17920
4    April 122274  15825
5      May 117921  15437
dat %>% mutate_at(2:3, str_replace_all, "\\$|,", "")
     Month  Sales Profit
1  January 128568  16234
2 February 109523  12876
3    March 115468  17920
4    April 122274  15825
5      May 117921  15437
# 對$或,做replace,變成沒有東西("":空集合)
dat %>% mutate_all(2:3, parse_number)
# mutate_all是將整個資料框都做過一次,但因為第一欄是月份沒有逗號,會失敗
dat$Profit <- str_replace_all(dat$Profit, c("\\$|,"), "") 
dat$Sales <- parse_number(dat$Sales) 
dat
     Month  Sales Profit
1  January 128568  16234
2 February 109523  12876
3    March 115468  17920
4    April 122274  15825
5      May 117921  15437

C. String Processing Part 2

C1. Case Study2: Reported Heights
library(dslabs)
data(reported_heights)
reported_heights %>% head
           time_stamp    sex height
1 2014-09-02 13:40:36   Male     75
2 2014-09-02 13:46:59   Male     70
3 2014-09-02 13:59:20   Male     68
4 2014-09-02 14:51:53   Male     74
5 2014-09-02 15:16:15   Male     61
6 2014-09-02 15:16:16 Female     65
reported_heights %>%
  mutate(new_height = as.numeric(height)) %>% 
  filter(is.na(new_height)) %>% 
  getElement("height")
NAs introduced by coercion
 [1] "5' 4\""                 "165cm"                 
 [3] "5'7"                    ">9000"                 
 [5] "5'7\""                  "5'3\""                 
 [7] "5 feet and 8.11 inches" "5'11"                  
 [9] "5'9''"                  "5'10''"                
[11] "5,3"                    "6'"                    
[13] "6,8"                    "5' 10"                 
[15] "Five foot eight inches" "5'5\""                 
[17] "5'2\""                  "5,4"                   
[19] "5'3"                    "5'10''"                
[21] "5'3''"                  "5'7''"                 
[23] "5'12"                   "2'33"                  
[25] "5'11"                   "5'3\""                 
[27] "5,8"                    "5'6''"                 
[29] "5'4"                    "1,70"                  
[31] "5'7.5''"                "5'7.5''"               
[33] "5'2\""                  "5' 7.78\""             
[35] "yyy"                    "5'5"                   
[37] "5'8"                    "5'6"                   
[39] "5 feet 7inches"         "6*12"                  
[41] "5 .11"                  "5 11"                  
[43] "5'4"                    "5'8\""                 
[45] "5'5"                    "5'7"                   
[47] "5'6"                    "5'11\""                
[49] "5'7\""                  "5'7"                   
[51] "5'8"                    "5' 11\""               
[53] "6'1\""                  "69\""                  
[55] "5' 7\""                 "5'10''"                
[57] "5'10"                   "5'10"                  
[59] "5ft 9 inches"           "5 ft 9 inches"         
[61] "5'2"                    "5'11"                  
[63] "5'11''"                 "5'8\""                 
[65] "708,661"                "5 feet 6 inches"       
[67] "5'10''"                 "5'8"                   
[69] "6'3\""                  "649,606"               
[71] "728,346"                "6 04"                  
[73] "5'9"                    "5'5''"                 
[75] "5'7\""                  "6'4\""                 
[77] "5'4"                    "170 cm"                
[79] "7,283,465"              "5'6"                   
[81] "5'6"                   
# mutate是加一個新的變數 # 轉不成功的會變成NA
reported_heights %>%
  filter(is.na(as.numeric(height))) %>% 
  .$height
NAs introduced by coercion
 [1] "5' 4\""                 "165cm"                 
 [3] "5'7"                    ">9000"                 
 [5] "5'7\""                  "5'3\""                 
 [7] "5 feet and 8.11 inches" "5'11"                  
 [9] "5'9''"                  "5'10''"                
[11] "5,3"                    "6'"                    
[13] "6,8"                    "5' 10"                 
[15] "Five foot eight inches" "5'5\""                 
[17] "5'2\""                  "5,4"                   
[19] "5'3"                    "5'10''"                
[21] "5'3''"                  "5'7''"                 
[23] "5'12"                   "2'33"                  
[25] "5'11"                   "5'3\""                 
[27] "5,8"                    "5'6''"                 
[29] "5'4"                    "1,70"                  
[31] "5'7.5''"                "5'7.5''"               
[33] "5'2\""                  "5' 7.78\""             
[35] "yyy"                    "5'5"                   
[37] "5'8"                    "5'6"                   
[39] "5 feet 7inches"         "6*12"                  
[41] "5 .11"                  "5 11"                  
[43] "5'4"                    "5'8\""                 
[45] "5'5"                    "5'7"                   
[47] "5'6"                    "5'11\""                
[49] "5'7\""                  "5'7"                   
[51] "5'8"                    "5' 11\""               
[53] "6'1\""                  "69\""                  
[55] "5' 7\""                 "5'10''"                
[57] "5'10"                   "5'10"                  
[59] "5ft 9 inches"           "5 ft 9 inches"         
[61] "5'2"                    "5'11"                  
[63] "5'11''"                 "5'8\""                 
[65] "708,661"                "5 feet 6 inches"       
[67] "5'10''"                 "5'8"                   
[69] "6'3\""                  "649,606"               
[71] "728,346"                "6 04"                  
[73] "5'9"                    "5'5''"                 
[75] "5'7\""                  "6'4\""                 
[77] "5'4"                    "170 cm"                
[79] "7,283,465"              "5'6"                   
[81] "5'6"                   
reported_heights$height %>% .[is.na(as.numeric(.))]
NAs introduced by coercion
 [1] "5' 4\""                 "165cm"                 
 [3] "5'7"                    ">9000"                 
 [5] "5'7\""                  "5'3\""                 
 [7] "5 feet and 8.11 inches" "5'11"                  
 [9] "5'9''"                  "5'10''"                
[11] "5,3"                    "6'"                    
[13] "6,8"                    "5' 10"                 
[15] "Five foot eight inches" "5'5\""                 
[17] "5'2\""                  "5,4"                   
[19] "5'3"                    "5'10''"                
[21] "5'3''"                  "5'7''"                 
[23] "5'12"                   "2'33"                  
[25] "5'11"                   "5'3\""                 
[27] "5,8"                    "5'6''"                 
[29] "5'4"                    "1,70"                  
[31] "5'7.5''"                "5'7.5''"               
[33] "5'2\""                  "5' 7.78\""             
[35] "yyy"                    "5'5"                   
[37] "5'8"                    "5'6"                   
[39] "5 feet 7inches"         "6*12"                  
[41] "5 .11"                  "5 11"                  
[43] "5'4"                    "5'8\""                 
[45] "5'5"                    "5'7"                   
[47] "5'6"                    "5'11\""                
[49] "5'7\""                  "5'7"                   
[51] "5'8"                    "5' 11\""               
[53] "6'1\""                  "69\""                  
[55] "5' 7\""                 "5'10''"                
[57] "5'10"                   "5'10"                  
[59] "5ft 9 inches"           "5 ft 9 inches"         
[61] "5'2"                    "5'11"                  
[63] "5'11''"                 "5'8\""                 
[65] "708,661"                "5 feet 6 inches"       
[67] "5'10''"                 "5'8"                   
[69] "6'3\""                  "649,606"               
[71] "728,346"                "6 04"                  
[73] "5'9"                    "5'5''"                 
[75] "5'7\""                  "6'4\""                 
[77] "5'4"                    "170 cm"                
[79] "7,283,465"              "5'6"                   
[81] "5'6"                   
not_inches <- function(x, smallest = 50, tallest = 84) {
  inches <- suppressWarnings(as.numeric(x))
  ind <- is.na(inches) | inches < smallest | inches > tallest 
  ind}
# 把英吋的大小控制在50-84的合理範圍
problems = reported_heights$height %>% .[not_inches(.)]
problems
  [1] "6"                      "5' 4\""                
  [3] "5.3"                    "165cm"                 
  [5] "511"                    "6"                     
  [7] "2"                      "5'7"                   
  [9] ">9000"                  "5'7\""                 
 [11] "5'3\""                  "5 feet and 8.11 inches"
 [13] "5.25"                   "5'11"                  
 [15] "5.5"                    "11111"                 
 [17] "5'9''"                  "6"                     
 [19] "6.5"                    "150"                   
 [21] "5'10''"                 "103.2"                 
 [23] "5.8"                    "19"                    
 [25] "5"                      "5.6"                   
 [27] "175"                    "177"                   
 [29] "300"                    "5,3"                   
 [31] "6'"                     "6"                     
 [33] "5.9"                    "6,8"                   
 [35] "5' 10"                  "5.5"                   
 [37] "178"                    "163"                   
 [39] "6.2"                    "175"                   
 [41] "Five foot eight inches" "6.2"                   
 [43] "5.8"                    "5.1"                   
 [45] "178"                    "165"                   
 [47] "5.11"                   "5'5\""                 
 [49] "165"                    "180"                   
 [51] "5'2\""                  "5.75"                  
 [53] "169"                    "5,4"                   
 [55] "7"                      "5.4"                   
 [57] "157"                    "6.1"                   
 [59] "169"                    "5'3"                   
 [61] "5.6"                    "214"                   
 [63] "183"                    "5.6"                   
 [65] "6"                      "162"                   
 [67] "178"                    "180"                   
 [69] "5'10''"                 "170"                   
 [71] "5'3''"                  "178"                   
 [73] "0.7"                    "190"                   
 [75] "5.4"                    "184"                   
 [77] "5'7''"                  "5.9"                   
 [79] "5'12"                   "5.6"                   
 [81] "5.6"                    "184"                   
 [83] "6"                      "167"                   
 [85] "2'33"                   "5'11"                  
 [87] "5'3\""                  "5.5"                   
 [89] "5.2"                    "180"                   
 [91] "5.5"                    "5.5"                   
 [93] "6.5"                    "5,8"                   
 [95] "180"                    "183"                   
 [97] "170"                    "5'6''"                 
 [99] "172"                    "612"                   
[101] "5.11"                   "168"                   
[103] "5'4"                    "1,70"                  
[105] "172"                    "87"                    
[107] "5.5"                    "176"                   
[109] "5'7.5''"                "5'7.5''"               
[111] "111"                    "5'2\""                 
[113] "173"                    "174"                   
[115] "176"                    "175"                   
[117] "5' 7.78\""              "6.7"                   
[119] "12"                     "6"                     
[121] "5.1"                    "5.6"                   
[123] "5.5"                    "yyy"                   
[125] "5.2"                    "5'5"                   
[127] "5'8"                    "5'6"                   
[129] "5 feet 7inches"         "89"                    
[131] "5.6"                    "5.7"                   
[133] "183"                    "172"                   
[135] "34"                     "25"                    
[137] "6"                      "5.9"                   
[139] "168"                    "6.5"                   
[141] "170"                    "175"                   
[143] "6"                      "22"                    
[145] "5.11"                   "684"                   
[147] "6"                      "1"                     
[149] "1"                      "6*12"                  
[151] "5 .11"                  "87"                    
[153] "162"                    "165"                   
[155] "184"                    "6"                     
[157] "173"                    "1.6"                   
[159] "172"                    "170"                   
[161] "5.7"                    "5.5"                   
[163] "174"                    "170"                   
[165] "160"                    "120"                   
[167] "120"                    "23"                    
[169] "192"                    "5 11"                  
[171] "167"                    "150"                   
[173] "1.7"                    "174"                   
[175] "5.8"                    "6"                     
[177] "5'4"                    "5'8\""                 
[179] "5'5"                    "5.8"                   
[181] "5.1"                    "5.11"                  
[183] "5.7"                    "5'7"                   
[185] "5'6"                    "5'11\""                
[187] "5'7\""                  "5'7"                   
[189] "172"                    "5'8"                   
[191] "180"                    "5' 11\""               
[193] "5"                      "180"                   
[195] "180"                    "6'1\""                 
[197] "5.9"                    "5.2"                   
[199] "5.5"                    "69\""                  
[201] "5' 7\""                 "5'10''"                
[203] "5.51"                   "5'10"                  
[205] "5'10"                   "5ft 9 inches"          
[207] "5 ft 9 inches"          "5'2"                   
[209] "5'11"                   "5.8"                   
[211] "5.7"                    "167"                   
[213] "168"                    "6"                     
[215] "6.1"                    "5'11''"                
[217] "5.69"                   "178"                   
[219] "182"                    "164"                   
[221] "5'8\""                  "185"                   
[223] "6"                      "86"                    
[225] "5.7"                    "708,661"               
[227] "5.25"                   "5.5"                   
[229] "5 feet 6 inches"        "5'10''"                
[231] "172"                    "6"                     
[233] "5'8"                    "160"                   
[235] "6'3\""                  "649,606"               
[237] "10000"                  "5.1"                   
[239] "152"                    "1"                     
[241] "180"                    "728,346"               
[243] "175"                    "158"                   
[245] "173"                    "164"                   
[247] "6 04"                   "169"                   
[249] "0"                      "185"                   
[251] "168"                    "5'9"                   
[253] "169"                    "5'5''"                 
[255] "174"                    "6.3"                   
[257] "179"                    "5'7\""                 
[259] "5.5"                    "6"                     
[261] "6"                      "170"                   
[263] "6"                      "172"                   
[265] "158"                    "100"                   
[267] "159"                    "190"                   
[269] "5.7"                    "170"                   
[271] "158"                    "6'4\""                 
[273] "180"                    "5.57"                  
[275] "5'4"                    "210"                   
[277] "88"                     "6"                     
[279] "162"                    "170 cm"                
[281] "5.7"                    "170"                   
[283] "157"                    "186"                   
[285] "170"                    "7,283,465"             
[287] "5"                      "5"                     
[289] "34"                     "161"                   
[291] "5'6"                    "5'6"                   
# 對一個vector做篩選可以用.[],"."指向前面的東西(在[]外面再包一個{}比較保險)
# 等於reported_heights$height[ not_inches(reported_heights$height) ]的縮寫
# 這是一個string factor 
str_subset(problems, "cm|inches")
[1] "165cm"                  "5 feet and 8.11 inches"
[3] "Five foot eight inches" "5 feet 7inches"        
[5] "5ft 9 inches"           "5 ft 9 inches"         
[7] "5 feet 6 inches"        "170 cm"                
str_subset(problems, "cm|inches") %>% str_extract("cm|inches")
[1] "cm"     "inches" "inches" "inches" "inches" "inches" "inches"
[8] "cm"    
str_extract(problems, "cm|inches")
  [1] NA       NA       NA       "cm"     NA       NA       NA      
  [8] NA       NA       NA       NA       "inches" NA       NA      
 [15] NA       NA       NA       NA       NA       NA       NA      
 [22] NA       NA       NA       NA       NA       NA       NA      
 [29] NA       NA       NA       NA       NA       NA       NA      
 [36] NA       NA       NA       NA       NA       "inches" NA      
 [43] NA       NA       NA       NA       NA       NA       NA      
 [50] NA       NA       NA       NA       NA       NA       NA      
 [57] NA       NA       NA       NA       NA       NA       NA      
 [64] NA       NA       NA       NA       NA       NA       NA      
 [71] NA       NA       NA       NA       NA       NA       NA      
 [78] NA       NA       NA       NA       NA       NA       NA      
 [85] NA       NA       NA       NA       NA       NA       NA      
 [92] NA       NA       NA       NA       NA       NA       NA      
 [99] NA       NA       NA       NA       NA       NA       NA      
[106] NA       NA       NA       NA       NA       NA       NA      
[113] NA       NA       NA       NA       NA       NA       NA      
[120] NA       NA       NA       NA       NA       NA       NA      
[127] NA       NA       "inches" NA       NA       NA       NA      
[134] NA       NA       NA       NA       NA       NA       NA      
[141] NA       NA       NA       NA       NA       NA       NA      
[148] NA       NA       NA       NA       NA       NA       NA      
[155] NA       NA       NA       NA       NA       NA       NA      
[162] NA       NA       NA       NA       NA       NA       NA      
[169] NA       NA       NA       NA       NA       NA       NA      
[176] NA       NA       NA       NA       NA       NA       NA      
[183] NA       NA       NA       NA       NA       NA       NA      
[190] NA       NA       NA       NA       NA       NA       NA      
[197] NA       NA       NA       NA       NA       NA       NA      
[204] NA       NA       "inches" "inches" NA       NA       NA      
[211] NA       NA       NA       NA       NA       NA       NA      
[218] NA       NA       NA       NA       NA       NA       NA      
[225] NA       NA       NA       NA       "inches" NA       NA      
[232] NA       NA       NA       NA       NA       NA       NA      
[239] NA       NA       NA       NA       NA       NA       NA      
[246] NA       NA       NA       NA       NA       NA       NA      
[253] NA       NA       NA       NA       NA       NA       NA      
[260] NA       NA       NA       NA       NA       NA       NA      
[267] NA       NA       NA       NA       NA       NA       NA      
[274] NA       NA       NA       NA       NA       NA       "cm"    
[281] NA       NA       NA       NA       NA       NA       NA      
[288] NA       NA       NA       NA       NA      

Q1: In the video, we use the function not_inches to identify heights that were incorrectly entered

not_inches <- function(x, smallest = 50, tallest = 84) {
  inches <- suppressWarnings(as.numeric(x))
  ind <- is.na(inches) | inches < smallest | inches > tallest 
  ind
}

In this function, what TWO types of values are identified as not being correctly formatted in inches?

  • Values that result in NA’s when converted to numeric
  • Values less than 50 inches or greater than 84 inches

Q2: Which of the following arguments, when passed to the function not_inches, would return the vector c(FALSE)?

c(70) %>% not_inches
[1] FALSE

Q3: Our function not_inches returns the object ind. Which answer correctly describes ind?

  • ind is a logical vector of TRUE and FALSE, equal in length to the vector x (in the arguments list). TRUE indicates that a height entry is incorrectly formatted.
C2. Regex(定義pattern的語言)

Q1: Given the following code

s = c("70" ,"5 ft", "4'11", "", ".", "Six feet")

What pattern vector yields the following result?

pattern = "\\d|ft"
# 對應阿拉伯數字:\d,但由於在R裡面要在""裡打\,就要打兩次 ex.cat("\\") = "\"
# \' 是 '  \" 是 " \. 是 .
str_subset(s, pattern)
[1] "70"   "5 ft" "4'11"
C3. Character Classes, Anchors, and Qualifiers

Character Classes - []

yes = as.character(4:7)
no = as.character(1:3)
str_detect(c(yes,no), "[4-7]")
[1]  TRUE  TRUE  TRUE  TRUE FALSE FALSE FALSE
# [4-7]是4到7 # [A-Z]是全部大寫字母 # [a-zA-Z]是所有英文字母
# []裡面都是character,所以[1-20]是0,1,2而非1~20
# [0-9] == \\d

Anchors - ^ and $ (^是開始/$是結束的條件)

yes = c("1","5","9")
no = c("12","123"," 1","a4","b")
str_detect(c(yes,no), "^\\d$")
[1]  TRUE  TRUE  TRUE FALSE FALSE FALSE FALSE FALSE
# 選擇開始第一個字是阿拉伯數字就結束
# str_detect(c(yes,no), "\\d$") 會變成只要阿拉伯數字結尾就好

Qualifiers - {} ({}前面的字元有幾個)

yes = c("1","5","9","12")
no = c("123","a4","b")
str_detect(c(yes,no), "^\\d{1,2}$")
[1]  TRUE  TRUE  TRUE  TRUE FALSE FALSE FALSE
# 開始和結尾是阿拉伯數字,可以有1~2個數字

Pattern of Feets & Inches

pattern = "^[4-7]'\\d{1,2}\"$"
yes = c("5'7\"", "6'2\"", "5'12\"")
no = c("6,2\"", "6.2\"", "I am 5'11\"", "3'2\"", "64")
str_detect(c(yes,no), pattern)
[1]  TRUE  TRUE  TRUE FALSE FALSE FALSE FALSE FALSE
# 開頭要是4-7,加個',再來1~2個阿拉伯數字,加個",結束 ex. 5'7"

Q1: You enter the following set of commands into your R console. What is your printed result?

animals <- c("cat", "puppy", "Moose", "MONKEY")
pattern <- "[a-z]"
str_detect(animals, pattern)
[1]  TRUE  TRUE  TRUE FALSE
# 要有小寫字母

Q2: You enter the following set of commands into your R console. What is your printed result?

animals <- c("cat", "puppy", "Moose", "MONKEY")
pattern <- "[A-Z]$"
str_detect(animals, pattern)
[1] FALSE FALSE FALSE  TRUE
# 結尾要是大寫字母

Q3: You enter the following set of commands into your R console. What is your printed result?

animals <- c("cat", "puppy", "Moose", "MONKEY")
pattern <- "[a-z]{4,5}"
str_detect(animals, pattern)
[1] FALSE  TRUE  TRUE FALSE
# 要包含4到5個小寫字母
C4. Search and Replace with Regex

Inital Pattern

pattern = "^[4-7]'\\d{1,2}$"
str_subset(problems, pattern)   # 24
 [1] "5'7"  "5'11" "5'3"  "5'12" "5'11" "5'4"  "5'5"  "5'8"  "5'6" 
[10] "5'4"  "5'5"  "5'7"  "5'6"  "5'7"  "5'8"  "5'10" "5'10" "5'2" 
[19] "5'11" "5'8"  "5'9"  "5'4"  "5'6"  "5'6" 

Replace Feet and Inches

pattern = "^[4-7]'\\d{1,2}$"
problems %>% 
  str_replace("feet|ft|foot","'") %>% 
  str_replace("inches|in|''|\"","") %>%
  str_subset(pattern)
 [1] "5'7"  "5'7"  "5'3"  "5'11" "5'9"  "5'10" "5'5"  "5'2"  "5'3" 
[10] "5'10" "5'3"  "5'7"  "5'12" "5'11" "5'3"  "5'6"  "5'4"  "5'2" 
[19] "5'5"  "5'8"  "5'6"  "5'4"  "5'8"  "5'5"  "5'7"  "5'6"  "5'11"
[28] "5'7"  "5'7"  "5'8"  "6'1"  "5'10" "5'10" "5'10" "5'2"  "5'11"
[37] "5'11" "5'8"  "5'10" "5'8"  "6'3"  "5'9"  "5'5"  "5'7"  "6'4" 
[46] "5'4"  "5'6"  "5'6" 
#  str_detect(pattern) %>%  sum   # 48 

More Qualifiers

  • * : 0 or more
  • + : 1 or more
  • ? : 0 or 1
yes = c("AB","A1B","A11B","A111B","A1111B")
no = c("A2B","A21B")
str_detect(c(yes,no), "A1*B")
[1]  TRUE  TRUE  TRUE  TRUE  TRUE FALSE FALSE
# 在A後面可以有0或更多的1

Space - \\s

pattern = "^[4-7]\\s*'\\s*\\d{1,2}$"
problems %>% 
  str_replace("feet|ft|foot","'") %>% 
  str_replace("inches|in|''|\"","") %>%
  str_detect(pattern) %>% 
  sum                           # 53 
[1] 53
# 空白鍵: \s , 在R就要: \\s
# 用星號代表不限空白鍵的多寡

Q1: Given the following code, which TWO pattern vectors would yield the following result?

animals <- c("moose", "monkey", "meerkat", "mountain lion")
pattern = c("mo*","mo?","mo+","moo*")
sapply(pattern, function(p) str_detect(animals, p)) %>% t
     [,1] [,2]  [,3] [,4]
mo*  TRUE TRUE  TRUE TRUE
mo?  TRUE TRUE  TRUE TRUE
mo+  TRUE TRUE FALSE TRUE
moo* TRUE TRUE FALSE TRUE
# moo*是在mo後面有0或更多的o

Q2: You are working on some data from different universities. You have the following vector

schools = c(
  "U. Kentucky","Univ New Hampshire","Univ. of Massachusetts",
  "University Georgia","U California","California State University"
  )

You want to clean this data to match the full names of each university. What of the following commands could accomplish this?

schools %>% 
  str_replace("^Univ\\.?\\s|^U\\.?\\s", "University ") %>% 
  str_replace("^University of |^University ", "University of ")
[1] "University of Kentucky"      "University of New Hampshire"
[3] "University of Massachusetts" "University of Georgia"      
[5] "University of California"    "California State University"
C5. Groups with Regex

Define Groups ()

pattern_no_group = "^[4-7],\\d*$"
pattern_group = "^([4-7]),(\\d*)$"
yes = c("5,9","5,11","6,","6,1")
no = c("5'9",",","2,8","6.1.1")
s = c(yes, no)
# 括號把他們分group

Groups do not affect pattern detection

str_detect(s, pattern_no_group)
[1]  TRUE  TRUE  TRUE  TRUE FALSE FALSE FALSE FALSE
str_detect(s, pattern_group)
[1]  TRUE  TRUE  TRUE  TRUE FALSE FALSE FALSE FALSE

The difference between

  • str_match()
  • str_extarct()
  • str_subset()
  • str_detect()
str_match(s, pattern_group)
     [,1]   [,2] [,3]
[1,] "5,9"  "5"  "9" 
[2,] "5,11" "5"  "11"
[3,] "6,"   "6"  ""  
[4,] "6,1"  "6"  "1" 
[5,] NA     NA   NA  
[6,] NA     NA   NA  
[7,] NA     NA   NA  
[8,] NA     NA   NA  
str_extract(s, pattern_group)
[1] "5,9"  "5,11" "6,"   "6,1"  NA     NA     NA     NA    
str_subset(s, pattern_group)
[1] "5,9"  "5,11" "6,"   "6,1" 
str_detect(s, pattern_group)
[1]  TRUE  TRUE  TRUE  TRUE FALSE FALSE FALSE FALSE

Replace with Group

pattern = "^([4-7])\\s*[,\\.\\s+]\\s*(\\d*)$"
str_subset(problems, pattern) %>% str_match(pattern)
      [,1]    [,2] [,3]
 [1,] "5.3"   "5"  "3" 
 [2,] "5.25"  "5"  "25"
 [3,] "5.5"   "5"  "5" 
 [4,] "6.5"   "6"  "5" 
 [5,] "5.8"   "5"  "8" 
 [6,] "5.6"   "5"  "6" 
 [7,] "5,3"   "5"  "3" 
 [8,] "5.9"   "5"  "9" 
 [9,] "6,8"   "6"  "8" 
[10,] "5.5"   "5"  "5" 
[11,] "6.2"   "6"  "2" 
[12,] "6.2"   "6"  "2" 
[13,] "5.8"   "5"  "8" 
[14,] "5.1"   "5"  "1" 
[15,] "5.11"  "5"  "11"
[16,] "5.75"  "5"  "75"
[17,] "5,4"   "5"  "4" 
[18,] "5.4"   "5"  "4" 
[19,] "6.1"   "6"  "1" 
[20,] "5.6"   "5"  "6" 
[21,] "5.6"   "5"  "6" 
[22,] "5.4"   "5"  "4" 
[23,] "5.9"   "5"  "9" 
[24,] "5.6"   "5"  "6" 
[25,] "5.6"   "5"  "6" 
[26,] "5.5"   "5"  "5" 
[27,] "5.2"   "5"  "2" 
[28,] "5.5"   "5"  "5" 
[29,] "5.5"   "5"  "5" 
[30,] "6.5"   "6"  "5" 
[31,] "5,8"   "5"  "8" 
[32,] "5.11"  "5"  "11"
[33,] "5.5"   "5"  "5" 
[34,] "6.7"   "6"  "7" 
[35,] "5.1"   "5"  "1" 
[36,] "5.6"   "5"  "6" 
[37,] "5.5"   "5"  "5" 
[38,] "5.2"   "5"  "2" 
[39,] "5.6"   "5"  "6" 
[40,] "5.7"   "5"  "7" 
[41,] "5.9"   "5"  "9" 
[42,] "6.5"   "6"  "5" 
[43,] "5.11"  "5"  "11"
[44,] "5 .11" "5"  "11"
[45,] "5.7"   "5"  "7" 
[46,] "5.5"   "5"  "5" 
[47,] "5 11"  "5"  "11"
[48,] "5.8"   "5"  "8" 
[49,] "5.8"   "5"  "8" 
[50,] "5.1"   "5"  "1" 
[51,] "5.11"  "5"  "11"
[52,] "5.7"   "5"  "7" 
[53,] "5.9"   "5"  "9" 
[54,] "5.2"   "5"  "2" 
[55,] "5.5"   "5"  "5" 
[56,] "5.51"  "5"  "51"
[57,] "5.8"   "5"  "8" 
[58,] "5.7"   "5"  "7" 
[59,] "6.1"   "6"  "1" 
[60,] "5.69"  "5"  "69"
[61,] "5.7"   "5"  "7" 
[62,] "5.25"  "5"  "25"
[63,] "5.5"   "5"  "5" 
[64,] "5.1"   "5"  "1" 
[65,] "6 04"  "6"  "04"
[66,] "6.3"   "6"  "3" 
[67,] "5.5"   "5"  "5" 
[68,] "5.7"   "5"  "7" 
[69,] "5.57"  "5"  "57"
[70,] "5.7"   "5"  "7" 
str_subset(problems, pattern) %>% 
  str_replace(pattern, "\\1'\\2")
 [1] "5'3"  "5'25" "5'5"  "6'5"  "5'8"  "5'6"  "5'3"  "5'9"  "6'8" 
[10] "5'5"  "6'2"  "6'2"  "5'8"  "5'1"  "5'11" "5'75" "5'4"  "5'4" 
[19] "6'1"  "5'6"  "5'6"  "5'4"  "5'9"  "5'6"  "5'6"  "5'5"  "5'2" 
[28] "5'5"  "5'5"  "6'5"  "5'8"  "5'11" "5'5"  "6'7"  "5'1"  "5'6" 
[37] "5'5"  "5'2"  "5'6"  "5'7"  "5'9"  "6'5"  "5'11" "5'11" "5'7" 
[46] "5'5"  "5'11" "5'8"  "5'8"  "5'1"  "5'11" "5'7"  "5'9"  "5'2" 
[55] "5'5"  "5'51" "5'8"  "5'7"  "6'1"  "5'69" "5'7"  "5'25" "5'5" 
[64] "5'1"  "6'04" "6'3"  "5'5"  "5'7"  "5'57" "5'7" 
# \\1代表group1;\\2代表group2
# str_replace(pattern, "\\1 feets and \\2 inches")
# 挖出來再重新定義

Q1: Rather than using the pattern_with_groups vector from the video, you accidentally write in the following code. What is your result?

pattern_w_groups = "^([4-7])\\s*[,\\.\\s+]\\s*(\\d*)$"
problems1 <- c("5.3", "5,5", "6 1", "5 .11", "5, 12")
pattern_with_groups <- "^([4-7])[,\\.](\\d*)$"
str_replace(problems1, pattern_with_groups, "\\1'\\2")
[1] "5'3"   "5'5"   "6 1"   "5 .11" "5, 12"

Q2: You notice your mistake and correct your pattern regex to the following What is your result?

problems1 <- c("5.3", "5,5", "6 1", "5 .11", "5, 12")
pattern_with_groups <- "^([4-7])[,\\.\\s](\\d*)$"
str_replace(problems1, pattern_with_groups, "\\1'\\2")
[1] "5'3"   "5'5"   "6'1"   "5 .11" "5, 12"

I think what it intends to do is …

problems1 <- c("5.3", "5,5", "6 1", "5 .11", "5, 12")
pattern_with_groups <- "^([4-7])\\s*[,\\.\\s]\\s*(\\d*)$"
str_replace(problems1, pattern_with_groups, "\\1'\\2")
[1] "5'3"  "5'5"  "6'1"  "5'11" "5'12"
C6. Testing and Improving
converted <- problems %>% 
  str_replace("feet|foot|ft", "'") %>% 
  str_replace("inches|in|''|\"", "") %>% 
  str_replace("^([4-7])\\s*[,\\.\\s+]\\s*(\\d*)$", "\\1'\\2")
pattern <- "^[4-7]\\s*'\\s*\\d{1,2}$"
index <- str_detect(converted, pattern)
mean(index)          # 0.42123
[1] 0.4212
converted[!index]    
  [1] "6"             "165cm"         "511"           "6"            
  [5] "2"             ">9000"         "5 ' and 8.11 " "11111"        
  [9] "6"             "150"           "103.2"         "19"           
 [13] "5"             "175"           "177"           "300"          
 [17] "6'"            "6"             "178"           "163"          
 [21] "175"           "Five ' eight " "178"           "165"          
 [25] "165"           "180"           "169"           "7"            
 [29] "157"           "169"           "214"           "183"          
 [33] "6"             "162"           "178"           "180"          
 [37] "170"           "178"           "0.7"           "190"          
 [41] "184"           "184"           "6"             "167"          
 [45] "2'33"          "180"           "180"           "183"          
 [49] "170"           "172"           "612"           "168"          
 [53] "1,70"          "172"           "87"            "176"          
 [57] "5'7.5"         "5'7.5"         "111"           "173"          
 [61] "174"           "176"           "175"           "5' 7.78"      
 [65] "12"            "6"             "yyy"           "89"           
 [69] "183"           "172"           "34"            "25"           
 [73] "6"             "168"           "170"           "175"          
 [77] "6"             "22"            "684"           "6"            
 [81] "1"             "1"             "6*12"          "87"           
 [85] "162"           "165"           "184"           "6"            
 [89] "173"           "1.6"           "172"           "170"          
 [93] "174"           "170"           "160"           "120"          
 [97] "120"           "23"            "192"           "167"          
[101] "150"           "1.7"           "174"           "6"            
[105] "172"           "180"           "5"             "180"          
[109] "180"           "69"            "5' 9 "         "5 ' 9 "       
[113] "167"           "168"           "6"             "178"          
[117] "182"           "164"           "185"           "6"            
[121] "86"            "708,661"       "5 ' 6 "        "172"          
[125] "6"             "160"           "649,606"       "10000"        
[129] "152"           "1"             "180"           "728,346"      
[133] "175"           "158"           "173"           "164"          
[137] "169"           "0"             "185"           "168"          
[141] "169"           "174"           "179"           "6"            
[145] "6"             "170"           "6"             "172"          
[149] "158"           "100"           "159"           "190"          
[153] "170"           "158"           "180"           "210"          
[157] "88"            "6"             "162"           "170 cm"       
[161] "170"           "157"           "186"           "170"          
[165] "7,283,465"     "5"             "5"             "34"           
[169] "161"          

Q1: In our example, we use the following code to detect height entries that do not match our pattern of x’y”.

problems1 <- c("5.3", "5,5", "6 1", "5 .11", "5, 12")
converted1 <- problems1 %>% 
  str_replace("feet|foot|ft", "'") %>% 
  str_replace("inches|in|''|\"", "") %>% 
  str_replace("^([4-7])\\s*[,\\.\\s+]\\s*(\\d*)$", "\\1'\\2")

pattern <- "^[4-7]\\s*'\\s*\\d{1,2}$"
index <- str_detect(converted1, pattern)
converted1[!index]

Which answer best describes the differences between the regex string we use as an argument in
str_replace("^([4-7])\\s*[,\\.\\s+]\\s*(\\d*)$", "\\1'\\2")
And the regex string in
pattern <- "^[4-7]\\s*'\\s*\\d{1,2}$"?

  • The regex used in str_replace looks for either a comma, period or space between the feet and inches digits, while the pattern regex just looks for an apostrophe; the regex in str_replace allows for none or more digits to be entered as inches, while the pattern regex only allows for one or two digits.

Q2: You notice a few entries that are not being properly converted using your str_replace and str_detect code

yes <- c("5 feet 7inches")
no <- c("5ft 9 inches", "5 ft 9 inches")
s <- c(yes, no)
converted <- s %>% 
  str_replace("feet|foot|ft", "'") %>% 
  str_replace("inches|in|''|\"", "") %>% 
  str_replace("^([4-7])\\s*[,\\.\\s+]\\s*(\\d*)$", "\\1'\\2")
converted
[1] "5 ' 7"  "5' 9 "  "5 ' 9 "
pattern <- "^[4-7]\\s*'\\s*\\d{1,2}$"
str_detect(converted, pattern)
[1]  TRUE FALSE FALSE

It seems like the problem may be due to spaces around the words feet|foot|ft and inches|in. What is another way you could fix this problem?

converted <- s %>% 
  str_replace("\\s*feet|foot|ft\\s*", "'") %>% 
  str_replace("\\s*inches|in|''|\"\\s*", "") %>% 
  str_replace("^([4-7])\\s*[,\\.\\s+]\\s*(\\d*)$", "\\1'\\2")
converted
[1] "5' 7" "5'9"  "5 '9"
pattern <- "^[4-7]\\s*'\\s*\\d{1,2}$"
str_detect(converted, pattern)
[1] TRUE TRUE TRUE



D. String Processing Part 3

D2. Separate with Regex
s = c("5'10", "6'1")
tab = data.frame(x = s)
separate(tab, x, c("feet", "inches"), sep="'")
  feet inches
1    5     10
2    6      1
extract(tab, x, c("feet", "inches"), regex="(\\d)'(\\d{1,2})")
  feet inches
1    5     10
2    6      1
s = c("5'10", "6'1\"","5'8inches")
tab = data.frame(x = s)
separate(tab, x, c("feet", "inches"), sep="'")
  feet  inches
1    5      10
2    6      1"
3    5 8inches
extract(tab, x, c("feet", "inches"), regex="(\\d)'(\\d{1,2})")
  feet inches
1    5     10
2    6      1
3    5      8
D1. Using Groups and Quantifiers

** Q1:** If you use the extract code from our video, the decimal point is dropped. What modification of the code would allow you to put the decimals in a third column called “decimal”?

library(tidyr)
s <- c("5'10", "6'1\"", "5'8inches", "5'7.5")
tab <- data.frame(x = s)
rx = c("(\\d)'(\\d{1,2})(\\.)?", 
       "(\\d)'(\\d{1,2})(\\.\\d+)",
       "(\\d)'(\\d{1,2})\\.\\d+?",
       "(\\d)'(\\d{1,2})(\\.\\d+)?")
extract(tab, x, into=c("feet", "inches", "decimal"), regex=rx[4])
  feet inches decimal
1    5     10    <NA>
2    6      1    <NA>
3    5      8    <NA>
4    5      7      .5
D4. String Splitting
filename =  system.file("extdata/murders.csv", package="dslabs")
lines = readLines(filename)
head(lines)
[1] "state,abb,region,population,total"
[2] "Alabama,AL,South,4779736,135"     
[3] "Alaska,AK,West,710231,19"         
[4] "Arizona,AZ,West,6392017,232"      
[5] "Arkansas,AR,South,2915918,93"     
[6] "California,CA,West,37253956,1257" 
x = str_split(lines, ",", simplify=T)
head(x)
     [,1]         [,2]  [,3]     [,4]         [,5]   
[1,] "state"      "abb" "region" "population" "total"
[2,] "Alabama"    "AL"  "South"  "4779736"    "135"  
[3,] "Alaska"     "AK"  "West"   "710231"     "19"   
[4,] "Arizona"    "AZ"  "West"   "6392017"    "232"  
[5,] "Arkansas"   "AR"  "South"  "2915918"    "93"   
[6,] "California" "CA"  "West"   "37253956"   "1257" 
as.data.frame(x[-1,]) %>% 
  setNames(x[1,]) %>% 
  mutate_all(parse_guess) %>% 
  head(10)
                  state abb    region population total
1               Alabama  AL     South    4779736   135
2                Alaska  AK      West     710231    19
3               Arizona  AZ      West    6392017   232
4              Arkansas  AR     South    2915918    93
5            California  CA      West   37253956  1257
6              Colorado  CO      West    5029196    65
7           Connecticut  CT Northeast    3574097    97
8              Delaware  DE     South     897934    38
9  District of Columbia  DC     South     601723    99
10              Florida  FL     South   19687653   669

Q1: You have the following table

schedule = data.frame(
  day = c("Monday", "Tuesday"),
  staff = c("Mandy, Chris and Laura", "Steve, Ruth and Frank"))
schedule
      day                  staff
1  Monday Mandy, Chris and Laura
2 Tuesday  Steve, Ruth and Frank

Which two commands would properly split the text in the “Staff” column into each individual name? Check all that apply.

lapply(c(",|and", ", | and ", ",\\s|\\sand\\s", "\\s?(,|and)\\s?"),
       function(r) str_split(schedule$staff, r, simplify=T))
[[1]]
     [,1]    [,2]     [,3]      [,4]    
[1,] "M"     "y"      " Chris " " Laura"
[2,] "Steve" " Ruth " " Frank"  ""      

[[2]]
     [,1]    [,2]    [,3]   
[1,] "Mandy" "Chris" "Laura"
[2,] "Steve" "Ruth"  "Frank"

[[3]]
     [,1]    [,2]    [,3]   
[1,] "Mandy" "Chris" "Laura"
[2,] "Steve" "Ruth"  "Frank"

[[4]]
     [,1]    [,2]   [,3]    [,4]   
[1,] "M"     "y"    "Chris" "Laura"
[2,] "Steve" "Ruth" "Frank" ""     

Q2: What code would successfully turn your “Schedule” table into the following tidy table

schedule %>% 
  mutate(staff = str_split(staff, ", | and ")) %>% 
  unnest()
      day staff
1  Monday Mandy
2  Monday Chris
3  Monday Laura
4 Tuesday Steve
5 Tuesday  Ruth
6 Tuesday Frank
# split出來變成一個list,再用unnest
D.6 Recoding
library(ggplot2)
data("gapminder")
gapminder %>% filter(region == "Caribbean") %>% 
  ggplot(aes(year, life_expectancy, color=country)) +
  geom_line()

gapminder %>% filter(region == "Caribbean") %>% 
  filter(str_length(country) >= 12) %>% 
  distinct(country)
                         country
1            Antigua and Barbuda
2             Dominican Republic
3 St. Vincent and the Grenadines
4            Trinidad and Tobago
gapminder %>% filter(region == "Caribbean") %>% 
  mutate(country = recode(
    country, 
    `Antigua and Barbuda` = "Barbuda",
    `Dominican Republic` = "DR",
    `St. Vincent and the Grenadines` = "St. Vincent",
    `Trinidad and Tobago` = "Trinidad"
  )) %>% 
  ggplot(aes(year, life_expectancy, color=country)) +
  geom_line()

Q1: Using the gapminder data, you want to recode countries longer than 12 letters in the region Middle Africa to their abbreviations in a new column, country_short. Which code would accomplish this?

library(dslabs)
data(gapminder)
gapminder %>% filter(region == "Middle Africa") %>% 
  filter(nchar(as.character(country)) >= 12) %>% 
  select(region, country) %>% distinct() %>% 
  mutate(country_short = recode(country, 
    "Central African Republic" = "CAR", 
    "Congo, Dem. Rep." = "DRC",
    "Equatorial Guinea" = "Eq. Guinea"
    ) )
         region                  country country_short
1 Middle Africa Central African Republic           CAR
2 Middle Africa         Congo, Dem. Rep.           DRC
3 Middle Africa        Equatorial Guinea    Eq. Guinea



E. Date, Times and Text Mining

E1. Dates and Times

Q1: Which of the following is the standard ISO 8601 format for dates?

  • YYYY-MM-DD

Q2: Which of the following commands could convert this string into the correct date format?

library(lubridate)
dates <- c("09-01-02", "01-12-07", "02-03-04")
ymd(dates)
[1] "2009-01-02" "2001-12-07" "2002-03-04"
mdy(dates)
[1] "2002-09-01" "2007-01-12" "2004-02-03"
dmy(dates)
[1] "2002-01-09" "2007-12-01" "2004-03-02"
  • It is impossible to know which format is correct without additional information.






LS0tDQp0aXRsZTogIldyYW5nbGluZywgU3RyaW5nIFByb2Nlc3NpbmcgJiBEYXRlL1RpbWUiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQo8YnI+DQoNCmBgYHtyfQ0KcGFja2FnZXMgPSBjKA0KICAiZHBseXIiLCJnZ3Bsb3QyIiwic3RyaW5nciIsICJkc2xhYnMiLCAicmVhZHIiLCAidGlkeXIiLCAicHVycnIiLA0KICAibHVicmlkYXRlIiwgInJ2ZXN0Ig0KICApDQpleGlzdGluZyA9IGFzLmNoYXJhY3RlcihpbnN0YWxsZWQucGFja2FnZXMoKVssMV0pDQpmb3IocGtnIGluIHBhY2thZ2VzWyEocGFja2FnZXMgJWluJSBleGlzdGluZyldKSBpbnN0YWxsLnBhY2thZ2VzKHBrZykNCmBgYA0KDQpgYGB7ciBlY2hvPVQsIG1lc3NhZ2U9RiwgY2FjaGU9Riwgd2FybmluZz1GfQ0Kcm0obGlzdD1scyhhbGw9VCkpDQpTeXMuc2V0bG9jYWxlKCJMQ19BTEwiLCJDIikNCm9wdGlvbnMoZGlnaXRzPTQsIHNjaXBlbj0xMikNCmxpYnJhcnkocnZlc3QpDQpsaWJyYXJ5KHJlYWRyKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoc3RyaW5ncikNCmxpYnJhcnkobHVicmlkYXRlKQ0KbGlicmFyeSh0aWR5cikNCmxpYnJhcnkoZHNsYWJzKQ0KYGBgDQoNCi0gLSAtDQoNCiMjIyBUaWR5IERhdGENCg0KIyMjIyMgV2ViIFNjcmFwaW5nDQpgYGB7cn0NCmxpYnJhcnkocnZlc3QpDQp1cmwgPSAiaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvTXVyZGVyX2luX3RoZV9Vbml0ZWRfU3RhdGVzX2J5X3N0YXRlIg0KaCA9IHJlYWRfaHRtbCh1cmwpDQoNCnRhYiA9IGh0bWxfbm9kZXMoaCwgInRhYmxlIilbWzJdXSAlPiUNCiAgaHRtbF90YWJsZSAlPiUgDQogIHNldE5hbWVzKGMoDQogICAgInN0YXRlIiwicG9wdWxhdGlvbiIsInRvdGFsIiwibXVyZGVycyIsImd1bl9tdXJkZXJzIiwNCiAgICAiZ3VuX293bmVyc2ppcCIsInRvdGFsX3JhdGUiLCJtdXJkZXJfcmF0ZSIsImd1bl9tdXJkZXJfcmF0ZSIpKQ0KYGBgDQoNCi0gLSAtDQoNCiMjIyBBLiBTdHJpbmcgUHJvY2Vzc2luZyBPdmVydmlldw0KDQotIC0gLQ0KDQojIyMgQi4gU3RyaW5nIFByb2Nlc3NpbmcgUGFydCAxIA0KDQojIyMjIyBCMS4gU3RyaW5nIFBhcnNpbmcNCg0KKipRMToqKiBfV2hpY2ggb2YgdGhlIGZvbGxvd2luZyBpcyBOT1QgYW4gYXBwbGljYXRpb24gb2Ygc3RyaW5nIHBhcnNpbmc/Xw0KDQorIEZvcm1hdHRpbmcgbnVtYmVycyBhbmQgY2hhcmFjdGVycyBzbyB0aGV5IGNhbiBlYXNpbHkgYmUgZGlzcGxheWVkIGluIGRlbGl2ZXJhYmxlcyBsaWtlIHBhcGVycyBhbmQgcHJlc2VudGF0aW9ucy4NCg0KIyMjIyMgQjIuIERlZmluaW5nIFN0cmluZ3M6IFNpbmdsZSBhbmQgRG91YmxlIFF1b3RlcyBhbmQgSG93IHRvIEVzY2FwZQ0KDQoqKlExOioqIF9XaGljaCBvZiB0aGUgZm9sbG93aW5nIGNvbW1hbmRzIHdvdWxkIG5vdCBnaXZlIHlvdSBhbiBlcnJvciBpbiBSP18NCmBgYHtyfQ0KY2F0KCIgTGVCcm9uIEphbWVzIGlzIDYnOFwiICIpDQpgYGANCg0KIyMjIyMgQjMuIGBzdHJpbmdyYCBQYWNrYWdlDQoNCioqUTE6KiogX1doaWNoIG9mIHRoZSBmb2xsb3dpbmcgYXJlIGFkdmFudGFnZXMgb2YgdGhlIHN0cmluZ3IgcGFja2FnZSBvdmVyIHN0cmluZyBwcm9jZXNzaW5nIGZ1bmN0aW9ucyBpbiBiYXNlIFI/IFNlbGVjdCBhbGwgdGhhdCBhcHBseS5fDQoNCisgRnVuY3Rpb25zIGluIHN0cmluZ3IgYWxsIHN0YXJ0IHdpdGgg4oCcc3RyX+KAnSwgd2hpY2ggbWFrZXMgdGhlbSBlYXN5IHRvIGxvb2sgdXAgdXNpbmcgYXV0b2NvbXBsZXRlLg0KKyBTdHJpbmdyIGZ1bmN0aW9ucyB3b3JrIGJldHRlciB3aXRoIHBpcGVzLg0KKyBoZSBvcmRlciBvZiBhcmd1bWVudHMgaXMgbW9yZSBjb25zaXN0ZW50IGluIHN0cmluZ3IgZnVuY3Rpb25zIHRoYW4gaW4gYmFzZSBSLg0KDQojIyMjIyBCNC4gQ2FzZSBTdHVkeSAxOiBVUyBNdXJkZXJzIERhdGENCg0KYGBge3J9DQpzYXBwbHkodGFiLCBzdHJfZGV0ZWN0LCAiLCIpICU+JSBjb2xTdW1zDQojIOWQkemHj+aYr+WQjOS4gOeoruizh+aWmeeahOmbhuWQiCjmrIQpLOWPr+S7peaYr+Wtl+S4suWQkemHjyzmlbjlgLzlkJHph48s6YKP6Lyv5ZCR6YeP562J562JLi4uDQojIHNhcHBseeWwjeS4gOWAi2NvbGxjZXRpb24o5Y2zdGFiLHRhYuaYr+S4gOWAi+izh+aWmeahhinlgZpkZXRlY3Qs5Zue5YKz5LiA5YCL5biD5p6X55+p6ZmjLOWGjeeul+aIkGNvbFN1bXMNCmBgYA0KDQpgYGB7cn0NCnRhYjIgPSB0YWIgJT4lIG11dGF0ZV9hdCgyOjMsIHBhcnNlX251bWJlcikgDQojIG11dGF0YWXmmK/lsI3mn5DmnbHopb/lgZrovYnmj5sgIyBwYXJzZeWPr+S7pea2iOmZpOmAl+iZn+S4puWwh+Wtl+S4sui9ieWbnuaVuOWtlw0Kc2FwcGx5KHRhYjIsIHN0cl9kZXRlY3QsICIsIikgJT4lIGNvbFN1bXMNCmBgYA0KDQoqKlExOioqIFlvdSBoYXZlIGEgZGF0YWZyYW1lIG9mIG1vbnRobHkgc2FsZXMgYW5kIHByb2ZpdHMgaW4gUg0KYGBge3J9DQpkYXQgPSByZWFkLnRhYmxlKCJkYXRhL3NhbGVzLnR4dCIsIGhlYWRlcj1ULCBzZXA9IiIsIHN0cmluZ3NBc0ZhY3RvcnM9RikNCmRhdA0KYGBgDQoNCl9XaGljaCBvZiB0aGUgZm9sbG93aW5nIGNvbW1hbmRzIGNvdWxkIGNvbnZlcnQgdGhlIHNhbGVzIGFuZCBwcm9maXRzIGNvbHVtbnMgdG8gbnVtZXJpYz8gU2VsZWN0IGFsbCB0aGF0IGFwcGx5Ll8NCmBgYHtyfQ0KZGF0ICU+JSBtdXRhdGVfYXQoMjozLCBwYXJzZV9udW1iZXIpDQpgYGANCg0KYGBge3J9DQpkYXQgJT4lIG11dGF0ZV9hdCgyOjMsIGZ1bnMoc3RyX3JlcGxhY2VfYWxsKC4sIGMoIlxcJHwsIiksICIiKSkpDQpgYGANCg0KYGBge3J9DQpkYXQgJT4lIG11dGF0ZV9hdCgyOjMsIHN0cl9yZXBsYWNlX2FsbCwgIlxcJHwsIiwgIiIpDQojIOWwjSTmiJYs5YGacmVwbGFjZSzorormiJDmspLmnInmnbHopb8oIiI656m66ZuG5ZCIKQ0KYGBgDQoNCmBgYHtyIGV2YWw9Rn0NCmRhdCAlPiUgbXV0YXRlX2FsbCgyOjMsIHBhcnNlX251bWJlcikNCiMgbXV0YXRlX2FsbOaYr+Wwh+aVtOWAi+izh+aWmeahhumDveWBmumBjuS4gOasoSzkvYblm6DngrrnrKzkuIDmrITmmK/mnIjku73mspLmnInpgJfomZ8s5pyD5aSx5pWXDQpgYGANCg0KYGBge3J9DQpkYXQkUHJvZml0IDwtIHN0cl9yZXBsYWNlX2FsbChkYXQkUHJvZml0LCBjKCJcXCR8LCIpLCAiIikgDQpkYXQkU2FsZXMgPC0gcGFyc2VfbnVtYmVyKGRhdCRTYWxlcykgDQpkYXQNCmBgYA0KDQotIC0gLQ0KDQojIyMgQy4gU3RyaW5nIFByb2Nlc3NpbmcgUGFydCAyDQoNCiMjIyMjIEMxLiBDYXNlIFN0dWR5MjogUmVwb3J0ZWQgSGVpZ2h0cw0KDQpgYGB7cn0NCmxpYnJhcnkoZHNsYWJzKQ0KZGF0YShyZXBvcnRlZF9oZWlnaHRzKQ0KcmVwb3J0ZWRfaGVpZ2h0cyAlPiUgaGVhZA0KYGBgDQoNCmBgYHtyfQ0KcmVwb3J0ZWRfaGVpZ2h0cyAlPiUNCiAgbXV0YXRlKG5ld19oZWlnaHQgPSBhcy5udW1lcmljKGhlaWdodCkpICU+JSANCiAgZmlsdGVyKGlzLm5hKG5ld19oZWlnaHQpKSAlPiUgDQogIGdldEVsZW1lbnQoImhlaWdodCIpDQojIG11dGF0ZeaYr+WKoOS4gOWAi+aWsOeahOiuiuaVuCAjIOi9ieS4jeaIkOWKn+eahOacg+iuiuaIkE5BDQpgYGANCg0KYGBge3J9DQpyZXBvcnRlZF9oZWlnaHRzICU+JQ0KICBmaWx0ZXIoaXMubmEoYXMubnVtZXJpYyhoZWlnaHQpKSkgJT4lIA0KICAuJGhlaWdodA0KYGBgDQoNCmBgYHtyfQ0KcmVwb3J0ZWRfaGVpZ2h0cyRoZWlnaHQgJT4lIC5baXMubmEoYXMubnVtZXJpYyguKSldDQpgYGANCg0KYGBge3J9DQpub3RfaW5jaGVzIDwtIGZ1bmN0aW9uKHgsIHNtYWxsZXN0ID0gNTAsIHRhbGxlc3QgPSA4NCkgew0KICBpbmNoZXMgPC0gc3VwcHJlc3NXYXJuaW5ncyhhcy5udW1lcmljKHgpKQ0KICBpbmQgPC0gaXMubmEoaW5jaGVzKSB8IGluY2hlcyA8IHNtYWxsZXN0IHwgaW5jaGVzID4gdGFsbGVzdCANCiAgaW5kfQ0KIyDmioroi7HlkIvnmoTlpKflsI/mjqfliLblnKg1MC04NOeahOWQiOeQhuevhOWcjQ0KYGBgDQoNCmBgYHtyfQ0KcHJvYmxlbXMgPSByZXBvcnRlZF9oZWlnaHRzJGhlaWdodCAlPiUgLltub3RfaW5jaGVzKC4pXQ0KcHJvYmxlbXMNCiMg5bCN5LiA5YCLdmVjdG9y5YGa56+p6YG45Y+v5Lul55SoLltdLCIuIuaMh+WQkeWJjemdoueahOadseilvyjlnKhbXeWklumdouWGjeWMheS4gOWAi3t95q+U6LyD5L+d6ZqqKQ0KIyDnrYnmlrxyZXBvcnRlZF9oZWlnaHRzJGhlaWdodFsgbm90X2luY2hlcyhyZXBvcnRlZF9oZWlnaHRzJGhlaWdodCkgXeeahOe4ruWvqw0KIyDpgJnmmK/kuIDlgItzdHJpbmcgZmFjdG9yIA0KYGBgDQoNCmBgYHtyfQ0Kc3RyX3N1YnNldChwcm9ibGVtcywgImNtfGluY2hlcyIpDQpgYGANCg0KYGBge3J9DQpzdHJfc3Vic2V0KHByb2JsZW1zLCAiY218aW5jaGVzIikgJT4lIHN0cl9leHRyYWN0KCJjbXxpbmNoZXMiKQ0KYGBgDQoNCmBgYHtyfQ0Kc3RyX2V4dHJhY3QocHJvYmxlbXMsICJjbXxpbmNoZXMiKQ0KYGBgDQoNCioqUTE6KiogSW4gdGhlIHZpZGVvLCB3ZSB1c2UgdGhlIGZ1bmN0aW9uIGBub3RfaW5jaGVzYCB0byBpZGVudGlmeSBoZWlnaHRzIHRoYXQgd2VyZSBpbmNvcnJlY3RseSBlbnRlcmVkDQpgYGB7cn0NCm5vdF9pbmNoZXMgPC0gZnVuY3Rpb24oeCwgc21hbGxlc3QgPSA1MCwgdGFsbGVzdCA9IDg0KSB7DQogIGluY2hlcyA8LSBzdXBwcmVzc1dhcm5pbmdzKGFzLm51bWVyaWMoeCkpDQogIGluZCA8LSBpcy5uYShpbmNoZXMpIHwgaW5jaGVzIDwgc21hbGxlc3QgfCBpbmNoZXMgPiB0YWxsZXN0IA0KICBpbmQNCn0NCmBgYA0KSW4gdGhpcyBmdW5jdGlvbiwgX3doYXQgVFdPIHR5cGVzIG9mIHZhbHVlcyBhcmUgaWRlbnRpZmllZCBhcyBub3QgYmVpbmcgY29ycmVjdGx5IGZvcm1hdHRlZCBpbiBpbmNoZXM/Xw0KDQorIFZhbHVlcyB0aGF0IHJlc3VsdCBpbiBOQeKAmXMgd2hlbiBjb252ZXJ0ZWQgdG8gbnVtZXJpYw0KKyBWYWx1ZXMgbGVzcyB0aGFuIDUwIGluY2hlcyBvciBncmVhdGVyIHRoYW4gODQgaW5jaGVzDQoNCioqUTI6KiogV2hpY2ggb2YgdGhlIGZvbGxvd2luZyBhcmd1bWVudHMsIHdoZW4gcGFzc2VkIHRvIHRoZSBmdW5jdGlvbiBub3RfaW5jaGVzLCB3b3VsZCByZXR1cm4gdGhlIHZlY3RvciBgYyhGQUxTRSlgPw0KYGBge3J9DQpjKDcwKSAlPiUgbm90X2luY2hlcw0KYGBgDQoNCioqUTM6KiogT3VyIGZ1bmN0aW9uIGBub3RfaW5jaGVzYCByZXR1cm5zIHRoZSBvYmplY3QgYGluZGAuIFdoaWNoIGFuc3dlciBjb3JyZWN0bHkgZGVzY3JpYmVzIGBpbmRgPw0KDQorIGBpbmRgIGlzIGEgbG9naWNhbCB2ZWN0b3Igb2YgYFRSVUVgIGFuZCBgRkFMU0VgLCBlcXVhbCBpbiBsZW5ndGggdG8gdGhlIHZlY3RvciBgeGAgKGluIHRoZSBhcmd1bWVudHMgbGlzdCkuIGBUUlVFYCBpbmRpY2F0ZXMgdGhhdCBhIGhlaWdodCBlbnRyeSBpcyBpbmNvcnJlY3RseSBmb3JtYXR0ZWQuDQoNCg0KIyMjIyMgQzIuIFJlZ2V4KOWumue+qXBhdHRlcm7nmoToqp7oqIApDQoNCioqUTE6KiogR2l2ZW4gdGhlIGZvbGxvd2luZyBjb2RlDQpgYGB7cn0NCnMgPSBjKCI3MCIgLCI1IGZ0IiwgIjQnMTEiLCAiIiwgIi4iLCAiU2l4IGZlZXQiKQ0KYGBgDQoNCl9XaGF0IHBhdHRlcm4gdmVjdG9yIHlpZWxkcyB0aGUgZm9sbG93aW5nIHJlc3VsdD9fDQpgYGB7cn0NCnBhdHRlcm4gPSAiXFxkfGZ0Ig0KIyDlsI3mh4npmL/mi4nkvK/mlbjlrZc6XGQs5L2G55Sx5pa85ZyoUuijoemdouimgeWcqCIi6KOh5omTXCzlsLHopoHmiZPlhanmrKEgZXguY2F0KCJcXCIpID0gIlwiDQojIFwnIOaYryAnICBcIiDmmK8gIiBcLiDmmK8gLg0Kc3RyX3N1YnNldChzLCBwYXR0ZXJuKQ0KYGBgDQoNCiMjIyMjIEMzLiBDaGFyYWN0ZXIgQ2xhc3NlcywgQW5jaG9ycywgYW5kIFF1YWxpZmllcnMNCg0KQ2hhcmFjdGVyIENsYXNzZXMgLSBgW11gDQpgYGB7cn0NCnllcyA9IGFzLmNoYXJhY3Rlcig0OjcpDQpubyA9IGFzLmNoYXJhY3RlcigxOjMpDQpzdHJfZGV0ZWN0KGMoeWVzLG5vKSwgIls0LTddIikNCiMgWzQtN13mmK805YiwNyAjIFtBLVpd5piv5YWo6YOo5aSn5a+r5a2X5q+NICMgW2EtekEtWl3mmK/miYDmnInoi7HmloflrZfmr40NCiMgW13oo6HpnaLpg73mmK9jaGFyYWN0ZXIs5omA5LulWzEtMjBd5pivMCwxLDLogIzpnZ4xfjIwDQojIFswLTldID09IFxcZA0KYGBgDQoNCkFuY2hvcnMgLSBgXmAgYW5kIGAkYCAoXuaYr+mWi+Wniy8k5piv57WQ5p2f55qE5qKd5Lu2KQ0KYGBge3J9DQp5ZXMgPSBjKCIxIiwiNSIsIjkiKQ0Kbm8gPSBjKCIxMiIsIjEyMyIsIiAxIiwiYTQiLCJiIikNCnN0cl9kZXRlY3QoYyh5ZXMsbm8pLCAiXlxcZCQiKQ0KIyDpgbjmk4fplovlp4vnrKzkuIDlgIvlrZfmmK/pmL/mi4nkvK/mlbjlrZflsLHntZDmnZ8NCiMgc3RyX2RldGVjdChjKHllcyxubyksICJcXGQkIikg5pyD6K6K5oiQ5Y+q6KaB6Zi/5ouJ5Lyv5pW45a2X57WQ5bC+5bCx5aW9DQpgYGANCg0KUXVhbGlmaWVycyAtIGB7fWAgKHt95YmN6Z2i55qE5a2X5YWD5pyJ5bm+5YCLKQ0KYGBge3J9DQp5ZXMgPSBjKCIxIiwiNSIsIjkiLCIxMiIpDQpubyA9IGMoIjEyMyIsImE0IiwiYiIpDQpzdHJfZGV0ZWN0KGMoeWVzLG5vKSwgIl5cXGR7MSwyfSQiKQ0KIyDplovlp4vlkozntZDlsL7mmK/pmL/mi4nkvK/mlbjlrZcs5Y+v5Lul5pyJMX4y5YCL5pW45a2XDQpgYGANCg0KUGF0dGVybiBvZiBGZWV0cyAmIEluY2hlcw0KYGBge3J9DQpwYXR0ZXJuID0gIl5bNC03XSdcXGR7MSwyfVwiJCINCnllcyA9IGMoIjUnN1wiIiwgIjYnMlwiIiwgIjUnMTJcIiIpDQpubyA9IGMoIjYsMlwiIiwgIjYuMlwiIiwgIkkgYW0gNScxMVwiIiwgIjMnMlwiIiwgIjY0IikNCnN0cl9kZXRlY3QoYyh5ZXMsbm8pLCBwYXR0ZXJuKQ0KIyDplovpoK3opoHmmK80LTcs5Yqg5YCLJyzlho3kvoYxfjLlgIvpmL/mi4nkvK/mlbjlrZcs5Yqg5YCLIizntZDmnZ8gZXguIDUnNyINCmBgYA0KDQoqKlExOioqIFlvdSBlbnRlciB0aGUgZm9sbG93aW5nIHNldCBvZiBjb21tYW5kcyBpbnRvIHlvdXIgUiBjb25zb2xlLiBfV2hhdCBpcyB5b3VyIHByaW50ZWQgcmVzdWx0P18NCmBgYHtyfQ0KYW5pbWFscyA8LSBjKCJjYXQiLCAicHVwcHkiLCAiTW9vc2UiLCAiTU9OS0VZIikNCnBhdHRlcm4gPC0gIlthLXpdIg0Kc3RyX2RldGVjdChhbmltYWxzLCBwYXR0ZXJuKQ0KIyDopoHmnInlsI/lr6vlrZfmr40NCmBgYA0KDQoqKlEyOioqIFlvdSBlbnRlciB0aGUgZm9sbG93aW5nIHNldCBvZiBjb21tYW5kcyBpbnRvIHlvdXIgUiBjb25zb2xlLiBfV2hhdCBpcyB5b3VyIHByaW50ZWQgcmVzdWx0P18gDQpgYGB7cn0NCmFuaW1hbHMgPC0gYygiY2F0IiwgInB1cHB5IiwgIk1vb3NlIiwgIk1PTktFWSIpDQpwYXR0ZXJuIDwtICJbQS1aXSQiDQpzdHJfZGV0ZWN0KGFuaW1hbHMsIHBhdHRlcm4pDQojIOe1kOWwvuimgeaYr+Wkp+Wvq+Wtl+avjQ0KYGBgDQoNCioqUTM6KiogWW91IGVudGVyIHRoZSBmb2xsb3dpbmcgc2V0IG9mIGNvbW1hbmRzIGludG8geW91ciBSIGNvbnNvbGUuIF9XaGF0IGlzIHlvdXIgcHJpbnRlZCByZXN1bHQ/Xw0KYGBge3J9DQphbmltYWxzIDwtIGMoImNhdCIsICJwdXBweSIsICJNb29zZSIsICJNT05LRVkiKQ0KcGF0dGVybiA8LSAiW2Etel17NCw1fSINCnN0cl9kZXRlY3QoYW5pbWFscywgcGF0dGVybikNCiMg6KaB5YyF5ZCrNOWIsDXlgIvlsI/lr6vlrZfmr40NCmBgYA0KDQojIyMjIyBDNC4gU2VhcmNoIGFuZCBSZXBsYWNlIHdpdGggUmVnZXgNCg0KSW5pdGFsIFBhdHRlcm4NCmBgYHtyfQ0KcGF0dGVybiA9ICJeWzQtN10nXFxkezEsMn0kIg0Kc3RyX3N1YnNldChwcm9ibGVtcywgcGF0dGVybikgICAjIDI0DQpgYGANCg0KUmVwbGFjZSBGZWV0IGFuZCBJbmNoZXMNCmBgYHtyfQ0KcGF0dGVybiA9ICJeWzQtN10nXFxkezEsMn0kIg0KcHJvYmxlbXMgJT4lIA0KICBzdHJfcmVwbGFjZSgiZmVldHxmdHxmb290IiwiJyIpICU+JSANCiAgc3RyX3JlcGxhY2UoImluY2hlc3xpbnwnJ3xcIiIsIiIpICU+JQ0KICBzdHJfc3Vic2V0KHBhdHRlcm4pDQojICBzdHJfZGV0ZWN0KHBhdHRlcm4pICU+JSAgc3VtICAgIyA0OCANCmBgYA0KDQpNb3JlIFF1YWxpZmllcnMNCg0KKyBgKmAgOiAwIG9yIG1vcmUNCisgYCtgIDogMSBvciBtb3JlDQorIGA/YCA6IDAgb3IgMQ0KDQpgYGB7cn0NCnllcyA9IGMoIkFCIiwiQTFCIiwiQTExQiIsIkExMTFCIiwiQTExMTFCIikNCm5vID0gYygiQTJCIiwiQTIxQiIpDQpzdHJfZGV0ZWN0KGMoeWVzLG5vKSwgIkExKkIiKQ0KIyDlnKhB5b6M6Z2i5Y+v5Lul5pyJMOaIluabtOWkmueahDENCmBgYA0KDQpTcGFjZSAtIGBcXHNgDQpgYGB7cn0NCnBhdHRlcm4gPSAiXls0LTddXFxzKidcXHMqXFxkezEsMn0kIg0KcHJvYmxlbXMgJT4lIA0KICBzdHJfcmVwbGFjZSgiZmVldHxmdHxmb290IiwiJyIpICU+JSANCiAgc3RyX3JlcGxhY2UoImluY2hlc3xpbnwnJ3xcIiIsIiIpICU+JQ0KICBzdHJfZGV0ZWN0KHBhdHRlcm4pICU+JSANCiAgc3VtICAgICAgICAgICAgICAgICAgICAgICAgICAgIyA1MyANCiMg56m655m96Y21OiBccyAsIOWcqFLlsLHopoE6IFxccw0KIyDnlKjmmJ/omZ/ku6PooajkuI3pmZDnqbrnmb3pjbXnmoTlpJrlr6ENCmBgYA0KDQoqKlExOioqIEdpdmVuIHRoZSBmb2xsb3dpbmcgY29kZSwgX3doaWNoIFRXTyBgcGF0dGVybmAgdmVjdG9ycyB3b3VsZCB5aWVsZCB0aGUgZm9sbG93aW5nIHJlc3VsdD9fDQpgYGB7cn0NCmFuaW1hbHMgPC0gYygibW9vc2UiLCAibW9ua2V5IiwgIm1lZXJrYXQiLCAibW91bnRhaW4gbGlvbiIpDQpwYXR0ZXJuID0gYygibW8qIiwibW8/IiwibW8rIiwibW9vKiIpDQpzYXBwbHkocGF0dGVybiwgZnVuY3Rpb24ocCkgc3RyX2RldGVjdChhbmltYWxzLCBwKSkgJT4lIHQNCiMgbW9vKuaYr+WcqG1v5b6M6Z2i5pyJMOaIluabtOWkmueahG8NCmBgYA0KDQoNCioqUTI6KiogWW91IGFyZSB3b3JraW5nIG9uIHNvbWUgZGF0YSBmcm9tIGRpZmZlcmVudCB1bml2ZXJzaXRpZXMuIFlvdSBoYXZlIHRoZSBmb2xsb3dpbmcgdmVjdG9yDQpgYGB7cn0NCnNjaG9vbHMgPSBjKA0KICAiVS4gS2VudHVja3kiLCJVbml2IE5ldyBIYW1wc2hpcmUiLCJVbml2LiBvZiBNYXNzYWNodXNldHRzIiwNCiAgIlVuaXZlcnNpdHkgR2VvcmdpYSIsIlUgQ2FsaWZvcm5pYSIsIkNhbGlmb3JuaWEgU3RhdGUgVW5pdmVyc2l0eSINCiAgKQ0KYGBgDQoNCllvdSB3YW50IHRvIGNsZWFuIHRoaXMgZGF0YSB0byBtYXRjaCB0aGUgZnVsbCBuYW1lcyBvZiBlYWNoIHVuaXZlcnNpdHkuIF9XaGF0IG9mIHRoZSBmb2xsb3dpbmcgY29tbWFuZHMgY291bGQgYWNjb21wbGlzaCB0aGlzP18NCmBgYHtyfQ0Kc2Nob29scyAlPiUgDQogIHN0cl9yZXBsYWNlKCJeVW5pdlxcLj9cXHN8XlVcXC4/XFxzIiwgIlVuaXZlcnNpdHkgIikgJT4lIA0KICBzdHJfcmVwbGFjZSgiXlVuaXZlcnNpdHkgb2YgfF5Vbml2ZXJzaXR5ICIsICJVbml2ZXJzaXR5IG9mICIpDQpgYGANCg0KIyMjIyMgQzUuIEdyb3VwcyB3aXRoIFJlZ2V4DQoNCkRlZmluZSBHcm91cHMgYCgpYCANCmBgYHtyfQ0KcGF0dGVybl9ub19ncm91cCA9ICJeWzQtN10sXFxkKiQiDQpwYXR0ZXJuX2dyb3VwID0gIl4oWzQtN10pLChcXGQqKSQiDQp5ZXMgPSBjKCI1LDkiLCI1LDExIiwiNiwiLCI2LDEiKQ0Kbm8gPSBjKCI1JzkiLCIsIiwiMiw4IiwiNi4xLjEiKQ0KcyA9IGMoeWVzLCBubykNCiMg5ous6Jmf5oqK5LuW5YCR5YiGZ3JvdXANCmBgYA0KDQpHcm91cHMgZG8gbm90IGFmZmVjdCBwYXR0ZXJuIGRldGVjdGlvbg0KYGBge3J9DQpzdHJfZGV0ZWN0KHMsIHBhdHRlcm5fbm9fZ3JvdXApDQpzdHJfZGV0ZWN0KHMsIHBhdHRlcm5fZ3JvdXApDQpgYGANCg0KVGhlIGRpZmZlcmVuY2UgYmV0d2VlbiANCg0KKyBgc3RyX21hdGNoKClgDQorIGBzdHJfZXh0YXJjdCgpYCANCisgYHN0cl9zdWJzZXQoKWAgDQorIGBzdHJfZGV0ZWN0KClgIA0KDQpgYGB7cn0NCnN0cl9tYXRjaChzLCBwYXR0ZXJuX2dyb3VwKQ0KYGBgDQoNCmBgYHtyfQ0Kc3RyX2V4dHJhY3QocywgcGF0dGVybl9ncm91cCkNCmBgYA0KDQpgYGB7cn0NCnN0cl9zdWJzZXQocywgcGF0dGVybl9ncm91cCkNCmBgYA0KDQpgYGB7cn0NCnN0cl9kZXRlY3QocywgcGF0dGVybl9ncm91cCkNCmBgYA0KDQpSZXBsYWNlIHdpdGggR3JvdXANCmBgYHtyfQ0KcGF0dGVybiA9ICJeKFs0LTddKVxccypbLFxcLlxccytdXFxzKihcXGQqKSQiDQpzdHJfc3Vic2V0KHByb2JsZW1zLCBwYXR0ZXJuKSAlPiUgc3RyX21hdGNoKHBhdHRlcm4pDQpgYGANCg0KYGBge3J9DQpzdHJfc3Vic2V0KHByb2JsZW1zLCBwYXR0ZXJuKSAlPiUgDQogIHN0cl9yZXBsYWNlKHBhdHRlcm4sICJcXDEnXFwyIikNCiMgXFwx5Luj6KGoZ3JvdXAxO1xcMuS7o+ihqGdyb3VwMg0KIyBzdHJfcmVwbGFjZShwYXR0ZXJuLCAiXFwxIGZlZXRzIGFuZCBcXDIgaW5jaGVzIikNCiMg5oyW5Ye65L6G5YaN6YeN5paw5a6a576pDQpgYGANCg0KKipRMToqKiBSYXRoZXIgdGhhbiB1c2luZyB0aGUgcGF0dGVybl93aXRoX2dyb3VwcyB2ZWN0b3IgZnJvbSB0aGUgdmlkZW8sIHlvdSBhY2NpZGVudGFsbHkgd3JpdGUgaW4gdGhlIGZvbGxvd2luZyBjb2RlLiBfV2hhdCBpcyB5b3VyIHJlc3VsdD9fDQpgYGB7cn0NCnBhdHRlcm5fd19ncm91cHMgPSAiXihbNC03XSlcXHMqWyxcXC5cXHMrXVxccyooXFxkKikkIg0KcHJvYmxlbXMxIDwtIGMoIjUuMyIsICI1LDUiLCAiNiAxIiwgIjUgLjExIiwgIjUsIDEyIikNCnBhdHRlcm5fd2l0aF9ncm91cHMgPC0gIl4oWzQtN10pWyxcXC5dKFxcZCopJCINCnN0cl9yZXBsYWNlKHByb2JsZW1zMSwgcGF0dGVybl93aXRoX2dyb3VwcywgIlxcMSdcXDIiKQ0KYGBgDQoNCioqUTI6KiogWW91IG5vdGljZSB5b3VyIG1pc3Rha2UgYW5kIGNvcnJlY3QgeW91ciBwYXR0ZXJuIHJlZ2V4IHRvIHRoZSBmb2xsb3dpbmcNCl9XaGF0IGlzIHlvdXIgcmVzdWx0P18NCmBgYHtyfQ0KcHJvYmxlbXMxIDwtIGMoIjUuMyIsICI1LDUiLCAiNiAxIiwgIjUgLjExIiwgIjUsIDEyIikNCnBhdHRlcm5fd2l0aF9ncm91cHMgPC0gIl4oWzQtN10pWyxcXC5cXHNdKFxcZCopJCINCnN0cl9yZXBsYWNlKHByb2JsZW1zMSwgcGF0dGVybl93aXRoX2dyb3VwcywgIlxcMSdcXDIiKQ0KYGBgDQoNCjxwIHN0eWxlPSJjb2xvcjpyZWQiPkkgdGhpbmsgd2hhdCBpdCBpbnRlbmRzIHRvIGRvIGlzIC4uLjwvcD4NCmBgYHtyfQ0KcHJvYmxlbXMxIDwtIGMoIjUuMyIsICI1LDUiLCAiNiAxIiwgIjUgLjExIiwgIjUsIDEyIikNCnBhdHRlcm5fd2l0aF9ncm91cHMgPC0gIl4oWzQtN10pXFxzKlssXFwuXFxzXVxccyooXFxkKikkIg0Kc3RyX3JlcGxhY2UocHJvYmxlbXMxLCBwYXR0ZXJuX3dpdGhfZ3JvdXBzLCAiXFwxJ1xcMiIpDQpgYGANCg0KIyMjIyMgQzYuIFRlc3RpbmcgYW5kIEltcHJvdmluZw0KDQpgYGB7cn0NCmNvbnZlcnRlZCA8LSBwcm9ibGVtcyAlPiUgDQogIHN0cl9yZXBsYWNlKCJmZWV0fGZvb3R8ZnQiLCAiJyIpICU+JSANCiAgc3RyX3JlcGxhY2UoImluY2hlc3xpbnwnJ3xcIiIsICIiKSAlPiUgDQogIHN0cl9yZXBsYWNlKCJeKFs0LTddKVxccypbLFxcLlxccytdXFxzKihcXGQqKSQiLCAiXFwxJ1xcMiIpDQpgYGANCg0KYGBge3J9DQpwYXR0ZXJuIDwtICJeWzQtN11cXHMqJ1xccypcXGR7MSwyfSQiDQppbmRleCA8LSBzdHJfZGV0ZWN0KGNvbnZlcnRlZCwgcGF0dGVybikNCm1lYW4oaW5kZXgpICAgICAgICAgICMgMC40MjEyMw0KYGBgDQoNCmBgYHtyfQ0KY29udmVydGVkWyFpbmRleF0gICAgDQpgYGANCg0KKipRMToqKiBJbiBvdXIgZXhhbXBsZSwgd2UgdXNlIHRoZSBmb2xsb3dpbmcgY29kZSB0byBkZXRlY3QgaGVpZ2h0IGVudHJpZXMgdGhhdCBkbyBub3QgbWF0Y2ggb3VyIHBhdHRlcm4gb2YgeOKAmXnigJ0uDQpgYGB7ciBldmFsPUZ9DQpwcm9ibGVtczEgPC0gYygiNS4zIiwgIjUsNSIsICI2IDEiLCAiNSAuMTEiLCAiNSwgMTIiKQ0KY29udmVydGVkMSA8LSBwcm9ibGVtczEgJT4lIA0KICBzdHJfcmVwbGFjZSgiZmVldHxmb290fGZ0IiwgIiciKSAlPiUgDQogIHN0cl9yZXBsYWNlKCJpbmNoZXN8aW58Jyd8XCIiLCAiIikgJT4lIA0KICBzdHJfcmVwbGFjZSgiXihbNC03XSlcXHMqWyxcXC5cXHMrXVxccyooXFxkKikkIiwgIlxcMSdcXDIiKQ0KDQpwYXR0ZXJuIDwtICJeWzQtN11cXHMqJ1xccypcXGR7MSwyfSQiDQppbmRleCA8LSBzdHJfZGV0ZWN0KGNvbnZlcnRlZDEsIHBhdHRlcm4pDQpjb252ZXJ0ZWQxWyFpbmRleF0NCmBgYA0KDQpfV2hpY2ggYW5zd2VyIGJlc3QgZGVzY3JpYmVzIHRoZSBkaWZmZXJlbmNlc18gYmV0d2VlbiB0aGUgcmVnZXggc3RyaW5nIHdlIHVzZSBhcyBhbiBhcmd1bWVudCBpbiA8YnI+DQpgc3RyX3JlcGxhY2UoIl4oWzQtN10pXFxzKlssXFwuXFxzK11cXHMqKFxcZCopJCIsICJcXDEnXFwyIilgIDxicj4NCkFuZCB0aGUgcmVnZXggc3RyaW5nIGluIDxicj4NCmBwYXR0ZXJuIDwtICJeWzQtN11cXHMqJ1xccypcXGR7MSwyfSQiP2ANCg0KKyBUaGUgcmVnZXggdXNlZCBpbiBzdHJfcmVwbGFjZSBsb29rcyBmb3IgZWl0aGVyIGEgY29tbWEsIHBlcmlvZCBvciBzcGFjZSBiZXR3ZWVuIHRoZSBmZWV0IGFuZCBpbmNoZXMgZGlnaXRzLCB3aGlsZSB0aGUgcGF0dGVybiByZWdleCBqdXN0IGxvb2tzIGZvciBhbiBhcG9zdHJvcGhlOyB0aGUgcmVnZXggaW4gc3RyX3JlcGxhY2UgYWxsb3dzIGZvciBub25lIG9yIG1vcmUgZGlnaXRzIHRvIGJlIGVudGVyZWQgYXMgaW5jaGVzLCB3aGlsZSB0aGUgcGF0dGVybiByZWdleCBvbmx5IGFsbG93cyBmb3Igb25lIG9yIHR3byBkaWdpdHMuDQoNCioqUTI6KiogWW91IG5vdGljZSBhIGZldyBlbnRyaWVzIHRoYXQgYXJlIG5vdCBiZWluZyBwcm9wZXJseSBjb252ZXJ0ZWQgdXNpbmcgeW91ciBzdHJfcmVwbGFjZSBhbmQgc3RyX2RldGVjdCBjb2RlDQpgYGB7cn0NCnllcyA8LSBjKCI1IGZlZXQgN2luY2hlcyIpDQpubyA8LSBjKCI1ZnQgOSBpbmNoZXMiLCAiNSBmdCA5IGluY2hlcyIpDQpzIDwtIGMoeWVzLCBubykNCg0KY29udmVydGVkIDwtIHMgJT4lIA0KICBzdHJfcmVwbGFjZSgiZmVldHxmb290fGZ0IiwgIiciKSAlPiUgDQogIHN0cl9yZXBsYWNlKCJpbmNoZXN8aW58Jyd8XCIiLCAiIikgJT4lIA0KICBzdHJfcmVwbGFjZSgiXihbNC03XSlcXHMqWyxcXC5cXHMrXVxccyooXFxkKikkIiwgIlxcMSdcXDIiKQ0KY29udmVydGVkDQoNCnBhdHRlcm4gPC0gIl5bNC03XVxccyonXFxzKlxcZHsxLDJ9JCINCnN0cl9kZXRlY3QoY29udmVydGVkLCBwYXR0ZXJuKQ0KYGBgDQoNCkl0IHNlZW1zIGxpa2UgdGhlIHByb2JsZW0gbWF5IGJlIGR1ZSB0byBzcGFjZXMgYXJvdW5kIHRoZSB3b3JkcyBmZWV0fGZvb3R8ZnQgYW5kIGluY2hlc3xpbi4gX1doYXQgaXMgYW5vdGhlciB3YXkgeW91IGNvdWxkIGZpeCB0aGlzIHByb2JsZW0/Xw0KYGBge3J9DQpjb252ZXJ0ZWQgPC0gcyAlPiUgDQogIHN0cl9yZXBsYWNlKCJcXHMqZmVldHxmb290fGZ0XFxzKiIsICInIikgJT4lIA0KICBzdHJfcmVwbGFjZSgiXFxzKmluY2hlc3xpbnwnJ3xcIlxccyoiLCAiIikgJT4lIA0KICBzdHJfcmVwbGFjZSgiXihbNC03XSlcXHMqWyxcXC5cXHMrXVxccyooXFxkKikkIiwgIlxcMSdcXDIiKQ0KDQpjb252ZXJ0ZWQNCnBhdHRlcm4gPC0gIl5bNC03XVxccyonXFxzKlxcZHsxLDJ9JCINCnN0cl9kZXRlY3QoY29udmVydGVkLCBwYXR0ZXJuKQ0KYGBgDQoNCjxicj4NCg0KLSAtIC0NCg0KIyMjIEQuIFN0cmluZyBQcm9jZXNzaW5nIFBhcnQgMw0KDQojIyMjIyBEMi4gU2VwYXJhdGUgd2l0aCBSZWdleA0KYGBge3J9DQpzID0gYygiNScxMCIsICI2JzEiKQ0KdGFiID0gZGF0YS5mcmFtZSh4ID0gcykNCnNlcGFyYXRlKHRhYiwgeCwgYygiZmVldCIsICJpbmNoZXMiKSwgc2VwPSInIikNCmV4dHJhY3QodGFiLCB4LCBjKCJmZWV0IiwgImluY2hlcyIpLCByZWdleD0iKFxcZCknKFxcZHsxLDJ9KSIpDQpgYGANCg0KYGBge3J9DQpzID0gYygiNScxMCIsICI2JzFcIiIsIjUnOGluY2hlcyIpDQp0YWIgPSBkYXRhLmZyYW1lKHggPSBzKQ0Kc2VwYXJhdGUodGFiLCB4LCBjKCJmZWV0IiwgImluY2hlcyIpLCBzZXA9IiciKQ0KZXh0cmFjdCh0YWIsIHgsIGMoImZlZXQiLCAiaW5jaGVzIiksIHJlZ2V4PSIoXFxkKScoXFxkezEsMn0pIikNCmBgYA0KDQojIyMjIyBEMS4gVXNpbmcgR3JvdXBzIGFuZCBRdWFudGlmaWVycw0KDQoqKiBRMToqKiBJZiB5b3UgdXNlIHRoZSBleHRyYWN0IGNvZGUgZnJvbSBvdXIgdmlkZW8sIHRoZSBkZWNpbWFsIHBvaW50IGlzIGRyb3BwZWQuIFdoYXQgbW9kaWZpY2F0aW9uIG9mIHRoZSBjb2RlIHdvdWxkIGFsbG93IHlvdSB0byBwdXQgdGhlIGRlY2ltYWxzIGluIGEgdGhpcmQgY29sdW1uIGNhbGxlZCDigJxkZWNpbWFs4oCdPw0KYGBge3J9DQpsaWJyYXJ5KHRpZHlyKQ0KcyA8LSBjKCI1JzEwIiwgIjYnMVwiIiwgIjUnOGluY2hlcyIsICI1JzcuNSIpDQp0YWIgPC0gZGF0YS5mcmFtZSh4ID0gcykNCnJ4ID0gYygiKFxcZCknKFxcZHsxLDJ9KShcXC4pPyIsIA0KICAgICAgICIoXFxkKScoXFxkezEsMn0pKFxcLlxcZCspIiwNCiAgICAgICAiKFxcZCknKFxcZHsxLDJ9KVxcLlxcZCs/IiwNCiAgICAgICAiKFxcZCknKFxcZHsxLDJ9KShcXC5cXGQrKT8iKQ0KZXh0cmFjdCh0YWIsIHgsIGludG89YygiZmVldCIsICJpbmNoZXMiLCAiZGVjaW1hbCIpLCByZWdleD1yeFs0XSkNCmBgYA0KDQojIyMjIyBENC4gU3RyaW5nIFNwbGl0dGluZw0KDQpgYGB7cn0NCmZpbGVuYW1lID0gIHN5c3RlbS5maWxlKCJleHRkYXRhL211cmRlcnMuY3N2IiwgcGFja2FnZT0iZHNsYWJzIikNCmxpbmVzID0gcmVhZExpbmVzKGZpbGVuYW1lKQ0KaGVhZChsaW5lcykNCmBgYA0KDQpgYGB7cn0NCnggPSBzdHJfc3BsaXQobGluZXMsICIsIiwgc2ltcGxpZnk9VCkNCmhlYWQoeCkNCmBgYA0KDQpgYGB7cn0NCmFzLmRhdGEuZnJhbWUoeFstMSxdKSAlPiUgDQogIHNldE5hbWVzKHhbMSxdKSAlPiUgDQogIG11dGF0ZV9hbGwocGFyc2VfZ3Vlc3MpICU+JSANCiAgaGVhZCgxMCkNCmBgYA0KDQoqKlExOioqIFlvdSBoYXZlIHRoZSBmb2xsb3dpbmcgdGFibGUNCmBgYHtyfQ0Kc2NoZWR1bGUgPSBkYXRhLmZyYW1lKA0KICBkYXkgPSBjKCJNb25kYXkiLCAiVHVlc2RheSIpLA0KICBzdGFmZiA9IGMoIk1hbmR5LCBDaHJpcyBhbmQgTGF1cmEiLCAiU3RldmUsIFJ1dGggYW5kIEZyYW5rIikpDQoNCnNjaGVkdWxlDQpgYGANCg0KX1doaWNoIHR3byBjb21tYW5kcyB3b3VsZCBwcm9wZXJseSBzcGxpdCB0aGUgdGV4dCBpbiB0aGUg4oCcU3RhZmbigJ0gY29sdW1uIGludG8gZWFjaCBpbmRpdmlkdWFsIG5hbWU/IENoZWNrIGFsbCB0aGF0IGFwcGx5Ll8NCmBgYHtyfQ0KbGFwcGx5KGMoIix8YW5kIiwgIiwgfCBhbmQgIiwgIixcXHN8XFxzYW5kXFxzIiwgIlxccz8oLHxhbmQpXFxzPyIpLA0KICAgICAgIGZ1bmN0aW9uKHIpIHN0cl9zcGxpdChzY2hlZHVsZSRzdGFmZiwgciwgc2ltcGxpZnk9VCkpDQpgYGANCg0KKipRMjoqKiBfV2hhdCBjb2RlIHdvdWxkIHN1Y2Nlc3NmdWxseSB0dXJuIHlvdXIg4oCcU2NoZWR1bGXigJ0gdGFibGUgaW50byB0aGUgZm9sbG93aW5nIHRpZHkgdGFibGVfDQpgYGB7cn0NCnNjaGVkdWxlICU+JSANCiAgbXV0YXRlKHN0YWZmID0gc3RyX3NwbGl0KHN0YWZmLCAiLCB8IGFuZCAiKSkgJT4lIA0KICB1bm5lc3QoKQ0KIyBzcGxpdOWHuuS+huiuiuaIkOS4gOWAi2xpc3Qs5YaN55SodW5uZXN0DQpgYGANCg0KIyMjIyMgRC42IFJlY29kaW5nDQoNCmBgYHtyfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KZGF0YSgiZ2FwbWluZGVyIikNCmdhcG1pbmRlciAlPiUgZmlsdGVyKHJlZ2lvbiA9PSAiQ2FyaWJiZWFuIikgJT4lIA0KICBnZ3Bsb3QoYWVzKHllYXIsIGxpZmVfZXhwZWN0YW5jeSwgY29sb3I9Y291bnRyeSkpICsNCiAgZ2VvbV9saW5lKCkNCmBgYA0KDQpgYGB7cn0NCmdhcG1pbmRlciAlPiUgZmlsdGVyKHJlZ2lvbiA9PSAiQ2FyaWJiZWFuIikgJT4lIA0KICBmaWx0ZXIoc3RyX2xlbmd0aChjb3VudHJ5KSA+PSAxMikgJT4lIA0KICBkaXN0aW5jdChjb3VudHJ5KQ0KYGBgDQoNCmBgYHtyfQ0KZ2FwbWluZGVyICU+JSBmaWx0ZXIocmVnaW9uID09ICJDYXJpYmJlYW4iKSAlPiUgDQogIG11dGF0ZShjb3VudHJ5ID0gcmVjb2RlKA0KICAgIGNvdW50cnksIA0KICAgIGBBbnRpZ3VhIGFuZCBCYXJidWRhYCA9ICJCYXJidWRhIiwNCiAgICBgRG9taW5pY2FuIFJlcHVibGljYCA9ICJEUiIsDQogICAgYFN0LiBWaW5jZW50IGFuZCB0aGUgR3JlbmFkaW5lc2AgPSAiU3QuIFZpbmNlbnQiLA0KICAgIGBUcmluaWRhZCBhbmQgVG9iYWdvYCA9ICJUcmluaWRhZCINCiAgKSkgJT4lIA0KICBnZ3Bsb3QoYWVzKHllYXIsIGxpZmVfZXhwZWN0YW5jeSwgY29sb3I9Y291bnRyeSkpICsNCiAgZ2VvbV9saW5lKCkNCmBgYA0KDQoNCioqUTE6KiogDQpVc2luZyB0aGUgZ2FwbWluZGVyIGRhdGEsIHlvdSB3YW50IHRvIHJlY29kZSBjb3VudHJpZXMgbG9uZ2VyIHRoYW4gMTIgbGV0dGVycyBpbiB0aGUgcmVnaW9uIGBNaWRkbGUgQWZyaWNhYCB0byB0aGVpciBhYmJyZXZpYXRpb25zIGluIGEgbmV3IGNvbHVtbiwgYGNvdW50cnlfc2hvcnRgLiBfV2hpY2ggY29kZSB3b3VsZCBhY2NvbXBsaXNoIHRoaXM/Xw0KYGBge3J9DQpsaWJyYXJ5KGRzbGFicykNCmRhdGEoZ2FwbWluZGVyKQ0KDQpnYXBtaW5kZXIgJT4lIGZpbHRlcihyZWdpb24gPT0gIk1pZGRsZSBBZnJpY2EiKSAlPiUgDQogIGZpbHRlcihuY2hhcihhcy5jaGFyYWN0ZXIoY291bnRyeSkpID49IDEyKSAlPiUgDQogIHNlbGVjdChyZWdpb24sIGNvdW50cnkpICU+JSBkaXN0aW5jdCgpICU+JSANCiAgbXV0YXRlKGNvdW50cnlfc2hvcnQgPSByZWNvZGUoY291bnRyeSwgDQogICAgIkNlbnRyYWwgQWZyaWNhbiBSZXB1YmxpYyIgPSAiQ0FSIiwgDQogICAgIkNvbmdvLCBEZW0uIFJlcC4iID0gIkRSQyIsDQogICAgIkVxdWF0b3JpYWwgR3VpbmVhIiA9ICJFcS4gR3VpbmVhIg0KICAgICkgKQ0KYGBgDQoNCjxicj4NCg0KLSAtIC0NCg0KIyMjIEUuIERhdGUsIFRpbWVzIGFuZCBUZXh0IE1pbmluZw0KDQojIyMjIyBFMS4gRGF0ZXMgYW5kIFRpbWVzDQoNCioqUTE6KiogX1doaWNoIG9mIHRoZSBmb2xsb3dpbmcgaXMgdGhlIHN0YW5kYXJkIElTTyA4NjAxIGZvcm1hdCBmb3IgZGF0ZXM/Xw0KDQorIFlZWVktTU0tREQNCg0KKipRMjoqKiBfV2hpY2ggb2YgdGhlIGZvbGxvd2luZyBjb21tYW5kcyBjb3VsZCBjb252ZXJ0IHRoaXMgc3RyaW5nIGludG8gdGhlIGNvcnJlY3QgZGF0ZSBmb3JtYXQ/Xw0KDQpgYGB7cn0NCmxpYnJhcnkobHVicmlkYXRlKQ0KZGF0ZXMgPC0gYygiMDktMDEtMDIiLCAiMDEtMTItMDciLCAiMDItMDMtMDQiKQ0KeW1kKGRhdGVzKQ0KbWR5KGRhdGVzKQ0KZG15KGRhdGVzKQ0KYGBgDQoNCisgSXQgaXMgaW1wb3NzaWJsZSB0byBrbm93IHdoaWNoIGZvcm1hdCBpcyBjb3JyZWN0IHdpdGhvdXQgYWRkaXRpb25hbCBpbmZvcm1hdGlvbi4NCg0KDQotIC0gLQ0KDQo8YnI+PGJyPjxicj48YnI+PGJyPg0KDQo8c3R5bGU+DQouY2FwdGlvbiB7DQogIGNvbG9yOiAjNzc3Ow0KICBtYXJnaW4tdG9wOiAxMHB4Ow0KfQ0KcCBjb2RlIHsNCiAgd2hpdGUtc3BhY2U6IGluaGVyaXQ7DQp9DQpwcmUgew0KICB3b3JkLWJyZWFrOiBub3JtYWw7DQogIHdvcmQtd3JhcDogbm9ybWFsOw0KICBsaW5lLWhlaWdodDogMTsNCn0NCnByZSBjb2RlIHsNCiAgd2hpdGUtc3BhY2U6IGluaGVyaXQ7DQp9DQpwLGxpIHsNCiAgZm9udC1mYW1pbHk6ICJUcmVidWNoZXQgTVMiLCAi5b6u6Luf5q2j6buR6auUIiwgIk1pY3Jvc29mdCBKaGVuZ0hlaSI7DQp9DQoNCi5yew0KICBsaW5lLWhlaWdodDogMS4yOw0KfQ0KDQp0aXRsZXsNCiAgY29sb3I6ICNjYzAwMDA7DQogIGZvbnQtZmFtaWx5OiAiVHJlYnVjaGV0IE1TIiwgIuW+rui7n+ato+m7kemrlCIsICJNaWNyb3NvZnQgSmhlbmdIZWkiOw0KfQ0KDQpib2R5ew0KICBmb250LWZhbWlseTogIlRyZWJ1Y2hldCBNUyIsICLlvq7ou5/mraPpu5Hpq5QiLCAiTWljcm9zb2Z0IEpoZW5nSGVpIjsNCn0NCg0KaDEsaDIsaDMsaDQsaDV7DQogIGNvbG9yOiAjMDA4ODAwOw0KICBmb250LWZhbWlseTogIlRyZWJ1Y2hldCBNUyIsICLlvq7ou5/mraPpu5Hpq5QiLCAiTWljcm9zb2Z0IEpoZW5nSGVpIjsNCn0NCg0KaDN7DQogIGNvbG9yOiAjYjM2YjAwOw0KICBiYWNrZ3JvdW5kOiAjZmZlMGIzOw0KICBsaW5lLWhlaWdodDogMjsNCiAgZm9udC13ZWlnaHQ6IGJvbGQ7DQp9DQoNCmg1ew0KICBjb2xvcjogIzAwNjAwMDsNCiAgYmFja2dyb3VuZDogI2ZmZmZlMDsNCiAgbGluZS1oZWlnaHQ6IDI7DQogIGZvbnQtd2VpZ2h0OiBib2xkOw0KfQ0KDQplbXsNCiAgY29sb3I6ICMwMDAwYzA7DQogIGJhY2tncm91bmQ6ICNmMGYwZjA7DQogIH0NCjwvc3R5bGU+DQo=