Install Packages

install.packages("rvest","stringr","dplyr","plyr","ggplot2","tidyr","caret","scales","gridExtra")

Load Libraries

library(rvest)
library(stringr)
library(dplyr)
library(plyr)
library(ggplot2)
library(tidyr)
library(caret)
library(scales)
library(gridExtra)

A website with holiday data is: timeanddate.com

url <- "https://www.timeanddate.com/holidays/"

Great, so how do we get the holidays for each country?

Two options: Either we use the countries names to make the URLs or we know all the URLs for each countries

links <- url %>%
 read_html %>% 
 html_nodes(".main-content-div .row a") %>% 
 html_attr("href")

Let’s inspect what we have gotten

head(links)
[1] "/"                             
[2] "/calendar/"                    
[3] "/holidays/anguilla/"           
[4] "/holidays/antigua-and-barbuda/"
[5] "/holidays/aruba/"              
[6] "/holidays/barbados/"           
paste("")
[1] ""
tail(links)
[1] "/holidays/tanzania/" "/holidays/togo/"    
[3] "/holidays/tunisia/"  "/holidays/uganda/"  
[5] "/holidays/zambia/"   "/holidays/zimbabwe/"

Let’s append the base URL to each path

links <- paste0("https://www.timeanddate.com",links)
links[1:10]
 [1] "https://www.timeanddate.com/holidays/anguilla/"              
 [2] "https://www.timeanddate.com/holidays/antigua-and-barbuda/"   
 [3] "https://www.timeanddate.com/holidays/aruba/"                 
 [4] "https://www.timeanddate.com/holidays/barbados/"              
 [5] "https://www.timeanddate.com/holidays/belize/"                
 [6] "https://www.timeanddate.com/holidays/british-virgin-islands/"
 [7] "https://www.timeanddate.com/holidays/canada/"                
 [8] "https://www.timeanddate.com/holidays/cayman-islands/"        
 [9] "https://www.timeanddate.com/holidays/costa-rica/"            
[10] "https://www.timeanddate.com/holidays/cuba/"                  

Change from lower case to Title Case

country =stringr::str_to_title(country)
country[1:10]
 [1] "Anguilla"               "Antigua And Barbuda"   
 [3] "Aruba"                  "Barbados"              
 [5] "Belize"                 "British Virgin Islands"
 [7] "Canada"                 "Cayman Islands"        
 [9] "Costa Rica"             "Cuba"                  

We are going to write our own function to extract the number of holidays for each country using the holiday_per_country function below

holiday_per_country = function(index){
  require(rvest)
  require(dplyr)
holidays=try(links[index] %>% 
                      read_html %>% 
                      html_node(".zebra") %>% 
                      html_table %>% 
                       dplyr::filter((grepl(pattern = "Holiday",`Holiday Type`,ignore.case= T)==T))
                     ,silent = T)
  if(is.list(holidays)){
    return(c(country[index],nrow(holidays)-1))
  }
  else{
    return(c(country[index],0))
  }
}

We would use ggplot2 to inspect our data. For more info on how to use ggplot2, check out the ggplot2 Cheatseet

holiday_df$no_of_holidays = as.numeric(holiday_df$no_of_holidays)
ggplot(holiday_df,aes(no_of_holidays))+
  geom_histogram(fill="seagreen3",color="black")+
  theme_classic()

Let’s find out the exact countries that are the top outliers

holiday_df %>%
  arrange(-no_of_holidays)

Interesting, let’s take a look at The US

You would see that a lot of their holidays are either State or County-observed. Do these types of holidays count if you are looking at the nation as a whole?

 local_holidays = function(index) {  
   column_names=names(links[index] %>%
                                        read_html %>%
                                        html_node(".zebra") %>%
                                        html_table) %>% str_to_lower()
   if("where it is observed" %in%column_names  == T){ 
     country[index]
   }
 
 }
#We have two options of using this function
#Option 1: A for loop. 
#Pros: Ability to save intermediary result & Ability to create custom progress trackers
#Cons: Significantly slower & Code is less concise
# local_countries=NULL
# for (i in 1:length(links)){
#   ind=local_holidays(i)
#   local_countries=append(local_countries,ind)
#   print(i/length(links))
# }
#Option 2: Vectorization using the lapply function
#Pros: Fast & Concise code
#Cons: Intermediary results are not saved & No custom progress function although some progressbar functionalities can be used with it
local_countries=lapply(1:length(links),local_holidays) %>% unlist
closing unused connection 3 (https://www.timeanddate.com/holidays/anguilla/)
################DO NOT RUN#######################
local_countries=NULL
for (i in 1:length(links)){
  ind=local_holidays(i)
  local_countries=append(local_countries,ind)
  print(i/length(links))
}

#Option 2: Vectorization using the lapply function
#Pros: Fast & Concise code
#Cons: Intermediary results are not saved & No custom progress function although some progressbar functionalities can be used with it
local_countries=lapply(1:length(links),local_holidays) %>% unlist
################DO NOT RUN#######################
local_countries=c("Canada","Us","Australia","Indonesia","Philippines","United Arab Emirates","Germany","Spain","Switzerland","Uk")

Let’s take a look at the countries that have these local holidays

paste("The Number of Countries with local holidays:",length(local_countries))
[1] "The Number of Countries with local holidays: 10"
local_countries
 [1] "Canada"               "Us"                  
 [3] "Australia"            "Indonesia"           
 [5] "Philippines"          "United Arab Emirates"
 [7] "Germany"              "Spain"               
 [9] "Switzerland"          "Uk"                  

Let’s get the number of national holidays for these countries

#Extract the index numbers for each of these countries
indices = which(country %in% local_countries)
updated_holidays= function (index){
  
table=links[index] %>% 
                read_html %>%
                html_node(".zebra") %>%
                html_table 
names(table)=names(table) %>%
  str_to_lower()
updated_table = table %>%
  filter( (grepl('holiday',`holiday type`,ignore.case =T) & 
             `where it is observed` == "" & grepl('jewish|Hindu',`holiday type`,ignore.case =T)==F) | grepl('all',`where it is observed`,ignore.case =T))
  return(c(country[index],nrow(updated_table)))
}
new_holiday = ldply(indices,updated_holidays)
names(new_holiday)=c("Country","no_of_holidays")
new_holiday=cbind(new_holiday,indices)
new_holiday

Now, let’s replace the old holiday values with the new ones

for(i in indices){
holiday_df$no_of_holidays[i]=new_holiday$no_of_holidays[new_holiday$indices==i]
  
}
holiday_df=holiday_df %>%
  mutate(no_of_holidays=as.numeric(no_of_holidays)) 
holiday_df%>%
  filter(Country == "Canada") %>%
  as_tibble()

Let’s check what we have left as outliers

holiday_df %>%
  arrange(-no_of_holidays)

Update actual holidays for the top 4 outliers

holiday_df$no_of_holidays[holiday_df$Country=="Bangladesh"]=21
holiday_df$no_of_holidays[holiday_df$Country=="India"]=18
holiday_df$no_of_holidays[holiday_df$Country=="Pakistan"]=20
holiday_df$no_of_holidays[holiday_df$Country=="Malaysia"]=20

Inspect again using our Histogram from before

ggplot(holiday_df,aes(no_of_holidays))+
  geom_histogram(fill="seagreen3",color="black")+
  theme_classic()

Let’s check out those zeros

holiday_df %>%
  arrange(no_of_holidays)

Those are not countries so we will take them out

holiday_df=holiday_df %>%
  filter(no_of_holidays>0)

Let’s also make sure that there no duplicates

holiday_df %>%
  group_by(Country) %>%
  dplyr::summarise(no_of_occurences=n()) %>%
  arrange(-no_of_occurences)

Let’s investigate Russia

holiday_df %>%
  filter(Country=="Russia")

We would remove them using the distinct() function from dplyr

holiday_df=holiday_df %>%
  distinct(Country,no_of_holidays)

Let’s check the dataframe after we have removed the duplicates

 holiday_df %>%
  group_by(Country) %>%
  dplyr::summarise(no_of_occurences=n()) %>%
  arrange(-no_of_occurences)

Let’s look at a visual map of holidays in each country on a Tableau Dashboard

Now, let’s look at Life Expectancy data from The World Bank

life_expectancy_df = read.csv("Life Expectancy.csv")
life_expectancy_df

Using tidyr’s gather() function we would collapse all these life expectancies into two columns i.e. A key-value format

life_expectancy_df = life_expectancy_df %>%
  gather(Year,life_expectancy,X2008..YR2008.:X2015..YR2015.) %>%
  select(-Country.Code)
attributes are not identical across measure variables; they will be dropped
life_expectancy_df

Let’s tidy up these two columns

life_expectancy_df$Year=substr(life_expectancy_df$Year,2,5)
life_expectancy_df=life_expectancy_df %>%
  mutate(Year=as.integer(Year)) %>%
  mutate(life_expectancy=as.numeric(life_expectancy)) %>%
    mutate(Country.Name=as.character(Country.Name)) 
NAs introduced by coercion
life_expectancy_df

Let’s check for missing values

table(is.na(life_expectancy_df))

FALSE  TRUE 
 5097   135 

Let’s remove all the NA’s with R’s complete.cases()

paste("Before NA removal:",nrow(life_expectancy_df))
[1] "Before NA removal: 1744"
life_expectancy_df=life_expectancy_df[complete.cases(life_expectancy_df),]
paste("After NA removal:",nrow(life_expectancy_df))
[1] "After NA removal: 1609"

Let’s compute the average life expectancy since 2008

life_expectancy_df=life_expectancy_df %>% 
  group_by(Country.Name) %>%
  dplyr::summarise(avg_life_expectancy=mean(life_expectancy))
life_expectancy_df

We would like to join the two tables but first let’s see what the different types of joins mean

Check for duplicates

life_expectancy_df %>%
  group_by(Country.Name) %>%
  dplyr::summarise(count=n()) %>%
  arrange(-count)

Some exploration of the distribution of Life Expectancy

ggplot(life_expectancy_df,aes(avg_life_expectancy))+
  geom_histogram(fill="violetred3",color="black")+
  theme_classic()

Let’s investigate what countries are the lower end of the life expectancy

life_expectancy_df %>%
  arrange(avg_life_expectancy)

Let’s also visualize a spatial map for Life Expectancy

We want to join the holiday and the life expectancy data frames for countries that have both numbers of holidays and life expectancy. What Join will be most suitable for this?

 life_expectancy_df=life_expectancy_df %>%
    dplyr::rename(Country=Country.Name)
holiday_and_life = inner_join(holiday_df,life_expectancy_df,by="Country")
Column `Country` joining factor and character vector, coercing into character vector
holiday_and_life

We are ready for the fun part, Machine Learning!


Let’s do some preprocessing like splitting the data into a training and test set using caret

country_data = read.csv("country_data.csv")
set.seed(1)
idx=createDataPartition(country_data$avg_life_expectancy,p=0.7,list = F)
train=country_data[idx,]
test=country_data[-idx,]
country_data
paste("Number of Observations for train is:",nrow(train),"& Number of Observations for test is:",nrow(test))
[1] "Number of Observations for train is: 104 & Number of Observations for test is: 41"

Some inital exploration of our data

p1=ggplot(country_data,aes(no_of_holidays,avg_gdp))+
  geom_point(color="violetred3",size=2)+
  ggtitle("Holidays")+
  theme_classic()
p2=ggplot(country_data,aes(Population,avg_gdp))+
  geom_point(color="#e67e22",size=2)+
  ggtitle("Population")+
  theme_classic()
p3=ggplot(country_data,aes(Health.expenditure..private....of.GDP.,avg_gdp))+
  geom_point(color="#3498db",size=2)+
  ggtitle("Health Expenditure")+
  theme_classic()
p4=ggplot(country_data,aes(Labor.force..total,avg_gdp))+
  geom_point(color="#1abc9c",size=2)+
  ggtitle("Labor Force")+
  theme_classic()
p5=ggplot(country_data,aes(Unemployment_Rate,avg_gdp))+
  geom_point(color="#34495e",size=2)+
  ggtitle("Unemployment Rate")+
  theme_classic()
p6=ggplot(country_data,aes(avg_life_expectancy,avg_gdp))+
  geom_point(color="#e74c3c",size=2)+
  ggtitle("Average Life Expectancy")+
  theme_classic()
grid.arrange(p1,p2,p3,p4,p5,p6)

Change to logarithmic scale

p1=ggplot(country_data,aes(no_of_holidays,log(avg_gdp)))+
  geom_point(color="violetred3",size=2)+
  ggtitle("Holidays")+
  theme_classic()
p2=ggplot(country_data,aes(log(Population),log(avg_gdp)))+
  geom_point(color="#e67e22",size=2)+
  ggtitle("Population")+
  theme_classic()
p3=ggplot(country_data,aes(Health.expenditure..private....of.GDP.,log(avg_gdp)))+
  geom_point(color="#3498db",size=2)+
  ggtitle("Health Expenditure")+
  theme_classic()
p4=ggplot(country_data,aes(log(Labor.force..total),log(avg_gdp)))+
  geom_point(color="#1abc9c",size=2)+
  ggtitle("Labor Force")+
  theme_classic()
p5=ggplot(country_data,aes(Unemployment_Rate,log(avg_gdp)))+
  geom_point(color="#34495e",size=2)+
  ggtitle("Unemployment Rate")+
  theme_classic()
p6=ggplot(country_data,aes(avg_life_expectancy,log(avg_gdp)))+
  geom_point(color="#e74c3c",size=2)+
  ggtitle("Average Life Expectancy")+
  theme_classic()
grid.arrange(p1,p2,p3,p4,p5,p6)

Let’s carry out model selection

models = c("lm","rf","rpart","gbm")
set.seed(7)
model_selection = function(method_name){
  if(method_name=="rf"){
 garbage <- capture.output(
   
   model <-train(x= train[,c(2,4:9)], y = train[,3], method = method_name,importance=T)
   )
  }
  else{
 garbage <- capture.output(
   
   model <-train(x= train[,c(2,4:9)], y = train[,3], method = method_name)
   )
    
  }
return(model)
}
R2_metric = function(model){
  predTest=predict(model,newdata = test)
R2 = R2(predTest,test$avg_gdp)
return(c(model[["method"]],R2))
}
model_results = lapply(models,model_selection) 
Loading required package: randomForest
randomForest 4.6-12
Type rfNews() to see new features/changes/bug fixes.

Attaching package: 㤼㸱randomForest㤼㸲

The following object is masked from 㤼㸱package:gridExtra㤼㸲:

    combine

The following object is masked from 㤼㸱package:ggplot2㤼㸲:

    margin

The following object is masked from 㤼㸱package:dplyr㤼㸲:

    combine

Loading required package: rpart
There were missing values in resampled performance measures.Loading required package: gbm
Loading required package: survival

Attaching package: 㤼㸱survival㤼㸲

The following object is masked from 㤼㸱package:caret㤼㸲:

    cluster

Loading required package: splines
Loading required package: parallel
Loaded gbm 2.1.3
models_r2 = model_results%>%
                    ldply(.,R2_metric)
names(models_r2) = c("model","r2")
models_r2 %>%
  mutate(r2=as.numeric(r2)) %>%
  arrange(-r2)

RandomForest did it best, let’s take a look at the important variables

varImp(model_results[[2]])[["importance"]] %>%
  as.data.frame() %>%
  mutate(variables=row.names(.)) %>%
  ggplot(.,aes(x=reorder(variables,Overall),y= Overall ))+
  geom_bar(stat = "identity",fill="#16a085",color="#16a085")+
  coord_flip()+
  theme_classic()+ 
  xlab("Variables")+
  ylab("Weighted Importance")+
  ggtitle("Random Forest Variable Importance")+
  geom_text(aes(label=paste0(round(Overall,1),"%"),hjust=ifelse(Overall<90,-0.1,1)))

LS0tDQp0aXRsZTogIlRoZSBBQkMtWFlaIG9mIERhdGEgU2NpZW5jZSINCm91dHB1dDogDQogIGh0bWxfbm90ZWJvb2sNCi0tLQ0KKioqIA0KDQpgYGB7ciByZXN1bHRzPSJhc2lzIiwgZWNobz1GQUxTRX0NCmNhdCgiDQo8c3R5bGU+DQpAaW1wb3J0IHVybCgnaHR0cHM6Ly9mb250cy5nb29nbGVhcGlzLmNvbS9jc3M/ZmFtaWx5PVJhbGV3YXk6NDAwLDYwMCcpOw0KDQpib2R5ew0KCWZvbnQtZmFtaWx5OiAnUmFsZXdheSc7DQp9DQoudGl0bGV7DQp0ZXh0LWFsaWduOmNlbnRlcjsNCnRleHQtdHJhbnNmb3JtOnVwcGVyY2FzZTsNCmZvbnQtd2VpZ2h0OjcwMA0KfQ0KLnJvd3sNCmRpc3BsYXk6bm9uZTsNCn0NCiN3ZS1hcmUtcmVhZHktZm9yLXRoZS1mdW4tcGFydC1tYWNoaW5lLWxlYXJuaW5new0KdGV4dC1hbGlnbjogY2VudGVyOw0KZm9udC13ZWlnaHQ6IDcwMDsNCnRleHQtdHJhbnNmb3JtOiB1cHBlcmNhc2U7DQpjb2xvcjogIzhlNDRhZDsNCn0NCjwvc3R5bGU+DQoiKQ0KYGBgDQojIyMgKipJbnN0YWxsIFBhY2thZ2VzKiogDQpgYGB7ciBJbnN0YWxsIFBhY2thZ2VzfQ0KaW5zdGFsbC5wYWNrYWdlcygicnZlc3QiLCJzdHJpbmdyIiwiZHBseXIiLCJwbHlyIiwiZ2dwbG90MiIsInRpZHlyIiwiY2FyZXQiLCJzY2FsZXMiLCJncmlkRXh0cmEiKQ0KYGBgDQoNCiMjIyAqKkxvYWQgTGlicmFyaWVzKioNCmBgYHtyIG1lc3NhZ2U9RkFMU0UgfQ0KbGlicmFyeShydmVzdCkNCmxpYnJhcnkoc3RyaW5ncikNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHBseXIpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KHRpZHlyKQ0KbGlicmFyeShjYXJldCkNCmxpYnJhcnkoc2NhbGVzKQ0KbGlicmFyeShncmlkRXh0cmEpDQpgYGANCg0KIyMjICoqQSB3ZWJzaXRlIHdpdGggaG9saWRheSBkYXRhIGlzOiB0aW1lYW5kZGF0ZS5jb20qKg0KYGBge3J9DQp1cmwgPC0gImh0dHBzOi8vd3d3LnRpbWVhbmRkYXRlLmNvbS9ob2xpZGF5cy8iDQpgYGANCg0KIyMjICoqR3JlYXQsIHNvIGhvdyBkbyB3ZSBnZXQgdGhlIGhvbGlkYXlzIGZvciBlYWNoIGNvdW50cnk/KioNClR3byBvcHRpb25zOiBFaXRoZXIgd2UgdXNlIHRoZSBjb3VudHJpZXMgbmFtZXMgdG8gbWFrZSB0aGUgVVJMcyBvciB3ZSBrbm93IGFsbCB0aGUgVVJMcyBmb3IgZWFjaCBjb3VudHJpZXMNCg0KYGBge3J9DQpsaW5rcyA8LSB1cmwgJT4lDQogcmVhZF9odG1sICU+JSANCiBodG1sX25vZGVzKCIubWFpbi1jb250ZW50LWRpdiAucm93IGEiKSAlPiUgDQogaHRtbF9hdHRyKCJocmVmIikNCmBgYA0KDQojIyMgKipMZXQncyBpbnNwZWN0IHdoYXQgd2UgaGF2ZSBnb3R0ZW4qKg0KYGBge3J9DQpoZWFkKGxpbmtzKQ0KdGFpbChsaW5rcykNCmBgYA0KIyMjICoqV2UgZ290IHRoZSBwYXRocyBmcm9tIHRoZSBiYXNlIFVSTCBhbmQgaXQgYWxzbyBzZWVtcyB0aGF0IHRoZSBmaXJzdCB0d28gbGlua3MgYXJlIG5vdCBjb3VudHJ5IHJlbGF0ZWQgc28gbGV0J3MgdGFrZSBpdCBvdXQqKg0KYGBge3J9DQpsaW5rcyA8LSBsaW5rcyBbMzpsZW5ndGgobGlua3MpXQ0KbGlua3MNCmBgYA0KDQojIyMgKipMZXQncyBhcHBlbmQgdGhlIGJhc2UgVVJMIHRvIGVhY2ggcGF0aCoqDQpgYGB7cn0NCmxpbmtzIDwtIHBhc3RlMCgiaHR0cHM6Ly93d3cudGltZWFuZGRhdGUuY29tIixsaW5rcykNCmxpbmtzWzE6MTBdDQpgYGANCg0KIyMjICoqTGV0J3MgZXh0cmFjdCB0aGUgYWN0dWFsIGNvdW50cnkgbmFtZXMgZnJvbSB0aGUgbGlua3MgdXNpbmcgc3RyaW5nciBhbmQqKiBbcmVnZXhdKGh0dHBzOi8vd3d3LnJzdHVkaW8uY29tL3dwLWNvbnRlbnQvdXBsb2Fkcy8yMDE2LzA5L1JlZ0V4Q2hlYXRzaGVldC5wZGYpDQpgYGB7cn0NCmNvdW50cnkgPC0gc3RyaW5ncjo6c3RyX3JlcGxhY2VfYWxsKGxpbmtzLCJodHRwczovL3d3dy50aW1lYW5kZGF0ZS5jb20vaG9saWRheXMvIiwiIikgJT4lDQpzdHJfcmVwbGFjZV9hbGwoLiwiWy0vXSIsIiAiKSAlPiUNCiAgc3RyaW5ncjo6c3RyX3RyaW0oKQ0KY291bnRyeVsxOjEwXQ0KYGBgDQoNCiMjIyAqKkNoYW5nZSBmcm9tIGxvd2VyIGNhc2UgdG8gVGl0bGUgQ2FzZSoqDQpgYGB7cn0NCmNvdW50cnkgPXN0cmluZ3I6OnN0cl90b190aXRsZShjb3VudHJ5KQ0KY291bnRyeVsxOjEwXQ0KYGBgDQojIyMgKipXZSBhcmUgZ29pbmcgdG8gd3JpdGUgb3VyIG93biBmdW5jdGlvbiB0byBleHRyYWN0IHRoZSBudW1iZXIgb2YgaG9saWRheXMgZm9yIGVhY2ggY291bnRyeSB1c2luZyB0aGUgaG9saWRheV9wZXJfY291bnRyeSBmdW5jdGlvbiBiZWxvdyoqDQpgYGB7cn0NCmhvbGlkYXlfcGVyX2NvdW50cnkgPSBmdW5jdGlvbihpbmRleCl7DQogIHJlcXVpcmUocnZlc3QpDQogIHJlcXVpcmUoZHBseXIpDQpob2xpZGF5cz10cnkobGlua3NbaW5kZXhdICU+JSANCiAgICAgICAgICAgICAgICAgICAgICByZWFkX2h0bWwgJT4lIA0KICAgICAgICAgICAgICAgICAgICAgIGh0bWxfbm9kZSgiLnplYnJhIikgJT4lIA0KICAgICAgICAgICAgICAgICAgICAgIGh0bWxfdGFibGUgJT4lIA0KICAgICAgICAgICAgICAgICAgICAgICBkcGx5cjo6ZmlsdGVyKChncmVwbChwYXR0ZXJuID0gIkhvbGlkYXkiLGBIb2xpZGF5IFR5cGVgLGlnbm9yZS5jYXNlPSBUKT09VCkpDQogICAgICAgICAgICAgICAgICAgICAsc2lsZW50ID0gVCkNCiAgaWYoaXMubGlzdChob2xpZGF5cykpew0KICAgIHJldHVybihjKGNvdW50cnlbaW5kZXhdLG5yb3coaG9saWRheXMpLTEpKQ0KICB9DQogIGVsc2V7DQogICAgcmV0dXJuKGMoY291bnRyeVtpbmRleF0sMCkpDQogIH0NCn0NCmBgYA0KDQojIyMgKipOb3csIHdlIHdpbGwgYXBwbHkgdGhpcyBmdW5jdGlvbiB0byBlYWNoIG9mIGxpbmtzIHdlIGhhdmUgb3VyIGEgZGF0YXNldCoqDQpgYGB7ciBldmFsPUZBTFNFfQ0KIyMjIyMjIyMjIyMjIyMjI0RPIE5PVCBSVU4jIyMjIyMjIyMjIyMjIyMjIyMjIyMjIw0KaG9saWRheV9kZj1wbHlyOjpsZHBseSgxOmxlbmd0aChsaW5rcyksaG9saWRheV9wZXJfY291bnRyeSkNCm5hbWVzKGhvbGlkYXlfZGYpID0gYygiQ291bnRyeSIsIm5vX29mX2hvbGlkYXlzIikNCiMjIyMjIyMjIyMjIyMjIyNETyBOT1QgUlVOIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCg0KYGBgDQpgYGB7cn0NCmhvbGlkYXlfZGYgPSByZWFkLmNzdigiaG9saWRheV9kZi5jc3YiKQ0KaG9saWRheV9kZiAlPiUgZHBseXI6OmFzX3RpYmJsZSgpDQpgYGANCg0KIyMjICoqV2Ugd291bGQgdXNlIGdncGxvdDIgdG8gaW5zcGVjdCBvdXIgZGF0YS4gRm9yIG1vcmUgaW5mbyBvbiBob3cgdG8gdXNlIGdncGxvdDIsIGNoZWNrIG91dCB0aGUqKiBbZ2dwbG90MiBDaGVhdHNlZXRdKGh0dHBzOi8vd3d3LnJzdHVkaW8uY29tL3dwLWNvbnRlbnQvdXBsb2Fkcy8yMDE1LzAzL2dncGxvdDItY2hlYXRzaGVldC5wZGYpIA0KYGBge3J9DQpob2xpZGF5X2RmJG5vX29mX2hvbGlkYXlzID0gYXMubnVtZXJpYyhob2xpZGF5X2RmJG5vX29mX2hvbGlkYXlzKQ0KZ2dwbG90KGhvbGlkYXlfZGYsYWVzKG5vX29mX2hvbGlkYXlzKSkrDQogIGdlb21faGlzdG9ncmFtKGZpbGw9InNlYWdyZWVuMyIsY29sb3I9ImJsYWNrIikrDQogIHRoZW1lX2NsYXNzaWMoKQ0KYGBgDQojIyMgKipMZXQncyBmaW5kIG91dCB0aGUgZXhhY3QgY291bnRyaWVzIHRoYXQgYXJlIHRoZSB0b3Agb3V0bGllcnMqKg0KYGBge3J9DQpob2xpZGF5X2RmICU+JQ0KICBhcnJhbmdlKC1ub19vZl9ob2xpZGF5cykNCmBgYA0KDQojIyMqKkludGVyZXN0aW5nLCBsZXQncyB0YWtlIGEgbG9vayBhdCBUaGUqKiBbVVNdKGh0dHBzOi8vd3d3LnRpbWVhbmRkYXRlLmNvbS9ob2xpZGF5cy91cy8pDQoNCllvdSB3b3VsZCBzZWUgdGhhdCBhIGxvdCBvZiB0aGVpciBob2xpZGF5cyBhcmUgZWl0aGVyIFN0YXRlIG9yIENvdW50eS1vYnNlcnZlZC4gRG8gdGhlc2UgdHlwZXMgb2YgaG9saWRheXMgY291bnQgaWYgeW91IGFyZSBsb29raW5nIGF0IHRoZSBuYXRpb24gYXMgYSB3aG9sZT8NCiAgDQoNCmBgYHtyfQ0KIGxvY2FsX2hvbGlkYXlzID0gZnVuY3Rpb24oaW5kZXgpIHsgIA0KICAgY29sdW1uX25hbWVzPW5hbWVzKGxpbmtzW2luZGV4XSAlPiUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByZWFkX2h0bWwgJT4lDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaHRtbF9ub2RlKCIuemVicmEiKSAlPiUNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBodG1sX3RhYmxlKSAlPiUgc3RyX3RvX2xvd2VyKCkNCiAgIGlmKCJ3aGVyZSBpdCBpcyBvYnNlcnZlZCIgJWluJWNvbHVtbl9uYW1lcyAgPT0gVCl7IA0KICAgICBjb3VudHJ5W2luZGV4XQ0KICAgfQ0KIA0KIH0NCg0KI1dlIGhhdmUgdHdvIG9wdGlvbnMgb2YgdXNpbmcgdGhpcyBmdW5jdGlvbg0KDQojT3B0aW9uIDE6IEEgZm9yIGxvb3AuIA0KI1Byb3M6IEFiaWxpdHkgdG8gc2F2ZSBpbnRlcm1lZGlhcnkgcmVzdWx0ICYgQWJpbGl0eSB0byBjcmVhdGUgY3VzdG9tIHByb2dyZXNzIHRyYWNrZXJzDQojQ29uczogU2lnbmlmaWNhbnRseSBzbG93ZXIgJiBDb2RlIGlzIGxlc3MgY29uY2lzZQ0KDQpgYGANCg0KYGBge3IgZXZhbD1GQUxTRX0NCiMjIyMjIyMjIyMjIyMjIyNETyBOT1QgUlVOIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMNCmxvY2FsX2NvdW50cmllcz1OVUxMDQpmb3IgKGkgaW4gMTpsZW5ndGgobGlua3MpKXsNCiAgaW5kPWxvY2FsX2hvbGlkYXlzKGkpDQogIGxvY2FsX2NvdW50cmllcz1hcHBlbmQobG9jYWxfY291bnRyaWVzLGluZCkNCiAgcHJpbnQoaS9sZW5ndGgobGlua3MpKQ0KfQ0KDQojT3B0aW9uIDI6IFZlY3Rvcml6YXRpb24gdXNpbmcgdGhlIGxhcHBseSBmdW5jdGlvbg0KI1Byb3M6IEZhc3QgJiBDb25jaXNlIGNvZGUNCiNDb25zOiBJbnRlcm1lZGlhcnkgcmVzdWx0cyBhcmUgbm90IHNhdmVkICYgTm8gY3VzdG9tIHByb2dyZXNzIGZ1bmN0aW9uIGFsdGhvdWdoIHNvbWUgcHJvZ3Jlc3NiYXIgZnVuY3Rpb25hbGl0aWVzIGNhbiBiZSB1c2VkIHdpdGggaXQNCmxvY2FsX2NvdW50cmllcz1sYXBwbHkoMTpsZW5ndGgobGlua3MpLGxvY2FsX2hvbGlkYXlzKSAlPiUgdW5saXN0DQojIyMjIyMjIyMjIyMjIyMjRE8gTk9UIFJVTiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjDQpgYGANCg0KYGBge3J9DQpsb2NhbF9jb3VudHJpZXM9YygiQ2FuYWRhIiwiVXMiLCJBdXN0cmFsaWEiLCJJbmRvbmVzaWEiLCJQaGlsaXBwaW5lcyIsIlVuaXRlZCBBcmFiIEVtaXJhdGVzIiwiR2VybWFueSIsIlNwYWluIiwiU3dpdHplcmxhbmQiLCJVayIpDQpgYGANCg0KIyMjICoqTGV0J3MgdGFrZSBhIGxvb2sgYXQgdGhlIGNvdW50cmllcyB0aGF0IGhhdmUgdGhlc2UgbG9jYWwgaG9saWRheXMqKg0KYGBge3J9DQpwYXN0ZSgiVGhlIE51bWJlciBvZiBDb3VudHJpZXMgd2l0aCBsb2NhbCANCiAgICAgIGhvbGlkYXlzOiIsbGVuZ3RoKGxvY2FsX2NvdW50cmllcykpDQpsb2NhbF9jb3VudHJpZXMNCmBgYA0KDQojIyMgKipMZXQncyBnZXQgdGhlIG51bWJlciBvZiBuYXRpb25hbCBob2xpZGF5cyBmb3IgdGhlc2UgY291bnRyaWVzKioNCmBgYHtyfQ0KI0V4dHJhY3QgdGhlIGluZGV4IG51bWJlcnMgZm9yIGVhY2ggb2YgdGhlc2UgY291bnRyaWVzDQppbmRpY2VzID0gd2hpY2goY291bnRyeSAlaW4lIGxvY2FsX2NvdW50cmllcykNCnVwZGF0ZWRfaG9saWRheXM9IGZ1bmN0aW9uIChpbmRleCl7DQogIA0KdGFibGU9bGlua3NbaW5kZXhdICU+JSANCiAgICAgICAgICAgICAgICByZWFkX2h0bWwgJT4lDQogICAgICAgICAgICAgICAgaHRtbF9ub2RlKCIuemVicmEiKSAlPiUNCiAgICAgICAgICAgICAgICBodG1sX3RhYmxlIA0KbmFtZXModGFibGUpPW5hbWVzKHRhYmxlKSAlPiUNCiAgc3RyX3RvX2xvd2VyKCkNCnVwZGF0ZWRfdGFibGUgPSB0YWJsZSAlPiUNCiAgZmlsdGVyKCAoZ3JlcGwoJ2hvbGlkYXknLGBob2xpZGF5IHR5cGVgLGlnbm9yZS5jYXNlID1UKSAmIA0KICAgICAgICAgICAgIGB3aGVyZSBpdCBpcyBvYnNlcnZlZGAgPT0gIiIgJiBncmVwbCgnamV3aXNofEhpbmR1JyxgaG9saWRheSB0eXBlYCxpZ25vcmUuY2FzZSA9VCk9PUYpIHwgZ3JlcGwoJ2FsbCcsYHdoZXJlIGl0IGlzIG9ic2VydmVkYCxpZ25vcmUuY2FzZSA9VCkpDQogIHJldHVybihjKGNvdW50cnlbaW5kZXhdLG5yb3codXBkYXRlZF90YWJsZSkpKQ0KfQ0KbmV3X2hvbGlkYXkgPSBsZHBseShpbmRpY2VzLHVwZGF0ZWRfaG9saWRheXMpDQpuYW1lcyhuZXdfaG9saWRheSk9YygiQ291bnRyeSIsIm5vX29mX2hvbGlkYXlzIikNCm5ld19ob2xpZGF5PWNiaW5kKG5ld19ob2xpZGF5LGluZGljZXMpDQpuZXdfaG9saWRheQ0KYGBgDQoNCiMjIyAqKk5vdywgbGV0J3MgcmVwbGFjZSB0aGUgb2xkIGhvbGlkYXkgdmFsdWVzIHdpdGggdGhlIG5ldyBvbmVzKioNCmBgYHtyfQ0KDQpmb3IoaSBpbiBpbmRpY2VzKXsNCmhvbGlkYXlfZGYkbm9fb2ZfaG9saWRheXNbaV09bmV3X2hvbGlkYXkkbm9fb2ZfaG9saWRheXNbbmV3X2hvbGlkYXkkaW5kaWNlcz09aV0NCiAgDQp9DQoNCmhvbGlkYXlfZGY9aG9saWRheV9kZiAlPiUNCiAgbXV0YXRlKG5vX29mX2hvbGlkYXlzPWFzLm51bWVyaWMobm9fb2ZfaG9saWRheXMpKSANCg0KaG9saWRheV9kZiU+JQ0KICBmaWx0ZXIoQ291bnRyeSA9PSAiQ2FuYWRhIikgJT4lDQogIGFzX3RpYmJsZSgpDQpgYGANCg0KIyMjICoqTGV0J3MgY2hlY2sgd2hhdCB3ZSBoYXZlIGxlZnQgYXMgb3V0bGllcnMqKg0KYGBge3J9DQpob2xpZGF5X2RmICU+JQ0KICBhcnJhbmdlKC1ub19vZl9ob2xpZGF5cykNCmBgYA0KDQojIyMgKipVcGRhdGUgYWN0dWFsIGhvbGlkYXlzIGZvciB0aGUgdG9wIDQgb3V0bGllcnMqKg0KYGBge3J9DQpob2xpZGF5X2RmJG5vX29mX2hvbGlkYXlzW2hvbGlkYXlfZGYkQ291bnRyeT09IkJhbmdsYWRlc2giXT0yMQ0KaG9saWRheV9kZiRub19vZl9ob2xpZGF5c1tob2xpZGF5X2RmJENvdW50cnk9PSJJbmRpYSJdPTE4DQpob2xpZGF5X2RmJG5vX29mX2hvbGlkYXlzW2hvbGlkYXlfZGYkQ291bnRyeT09IlBha2lzdGFuIl09MjANCmhvbGlkYXlfZGYkbm9fb2ZfaG9saWRheXNbaG9saWRheV9kZiRDb3VudHJ5PT0iTWFsYXlzaWEiXT0yMA0KYGBgDQoNCiMjIyAqKkluc3BlY3QgYWdhaW4gdXNpbmcgb3VyIEhpc3RvZ3JhbSBmcm9tIGJlZm9yZSoqDQpgYGB7cn0NCg0KZ2dwbG90KGhvbGlkYXlfZGYsYWVzKG5vX29mX2hvbGlkYXlzKSkrDQogIGdlb21faGlzdG9ncmFtKGZpbGw9InNlYWdyZWVuMyIsY29sb3I9ImJsYWNrIikrDQogIHRoZW1lX2NsYXNzaWMoKQ0KYGBgDQoNCiMjIyAqKkxldCdzIGNoZWNrIG91dCB0aG9zZSB6ZXJvcyoqDQpgYGB7cn0NCmhvbGlkYXlfZGYgJT4lDQogIGFycmFuZ2Uobm9fb2ZfaG9saWRheXMpDQpgYGANCg0KIyMjICoqVGhvc2UgYXJlIG5vdCBjb3VudHJpZXMgc28gd2Ugd2lsbCB0YWtlIHRoZW0gb3V0KioNCmBgYHtyfQ0KaG9saWRheV9kZj1ob2xpZGF5X2RmICU+JQ0KICBmaWx0ZXIobm9fb2ZfaG9saWRheXM+MCkNCmBgYA0KDQojIyMgKipMZXQncyBhbHNvIG1ha2Ugc3VyZSB0aGF0IHRoZXJlIG5vIGR1cGxpY2F0ZXMqKg0KYGBge3J9DQpob2xpZGF5X2RmICU+JQ0KICBncm91cF9ieShDb3VudHJ5KSAlPiUNCiAgZHBseXI6OnN1bW1hcmlzZShub19vZl9vY2N1cmVuY2VzPW4oKSkgJT4lDQogIGFycmFuZ2UoLW5vX29mX29jY3VyZW5jZXMpDQpgYGANCg0KIyMjICoqTGV0J3MgaW52ZXN0aWdhdGUgUnVzc2lhKioNCmBgYHtyfQ0KaG9saWRheV9kZiAlPiUNCiAgZmlsdGVyKENvdW50cnk9PSJSdXNzaWEiKQ0KYGBgDQoNCiMjIyoqV2Ugd291bGQgcmVtb3ZlIHRoZW0gdXNpbmcgdGhlIGRpc3RpbmN0KCkgZnVuY3Rpb24gZnJvbSBkcGx5cioqDQpgYGB7cn0NCmhvbGlkYXlfZGY9aG9saWRheV9kZiAlPiUNCiAgZGlzdGluY3QoQ291bnRyeSxub19vZl9ob2xpZGF5cykNCmBgYA0KDQojIyMgKipMZXQncyBjaGVjayB0aGUgZGF0YWZyYW1lIGFmdGVyIHdlIGhhdmUgcmVtb3ZlZCB0aGUgZHVwbGljYXRlcyoqDQpgYGB7cn0NCiBob2xpZGF5X2RmICU+JQ0KICBncm91cF9ieShDb3VudHJ5KSAlPiUNCiAgZHBseXI6OnN1bW1hcmlzZShub19vZl9vY2N1cmVuY2VzPW4oKSkgJT4lDQogIGFycmFuZ2UoLW5vX29mX29jY3VyZW5jZXMpDQpgYGANCg0KDQojIyMgKipMZXQncyBsb29rIGF0IGEgdmlzdWFsIG1hcCBvZiBob2xpZGF5cyBpbiBlYWNoIGNvdW50cnkgb24gYSoqIFtUYWJsZWF1IERhc2hib2FyZF0oaHR0cHM6Ly9wdWJsaWMudGFibGVhdS5jb20vcHJvZmlsZS9yb3NlYnVkLmFud3VyaSMhL3ZpemhvbWUvVGhlQUJDLVhZWm9mRGF0YVNjaWVuY2UvU2hlZXQxP3B1Ymxpc2g9eWVzKQ0KIVtdKE1hcC5wbmcpIA0KDQojIyMgKipOb3csIGxldCdzIGxvb2sgYXQgTGlmZSBFeHBlY3RhbmN5IGRhdGEgZnJvbSBUaGUgV29ybGQgQmFuayoqDQpgYGB7cn0NCmxpZmVfZXhwZWN0YW5jeV9kZiA9IHJlYWQuY3N2KCJMaWZlIEV4cGVjdGFuY3kuY3N2IikNCmxpZmVfZXhwZWN0YW5jeV9kZg0KYGBgDQoNCiMjIyAqKlVzaW5nIHRpZHlyJ3MgZ2F0aGVyKCkgZnVuY3Rpb24gd2Ugd291bGQgY29sbGFwc2UgYWxsIHRoZXNlIGxpZmUgZXhwZWN0YW5jaWVzIGludG8gdHdvIGNvbHVtbnMgaS5lLiBBIGtleS12YWx1ZSBmb3JtYXQqKg0KYGBge3J9DQpsaWZlX2V4cGVjdGFuY3lfZGYgPSBsaWZlX2V4cGVjdGFuY3lfZGYgJT4lDQogIGdhdGhlcihZZWFyLGxpZmVfZXhwZWN0YW5jeSxYMjAwOC4uWVIyMDA4LjpYMjAxNS4uWVIyMDE1LikgJT4lDQogIHNlbGVjdCgtQ291bnRyeS5Db2RlKQ0KbGlmZV9leHBlY3RhbmN5X2RmDQpgYGANCg0KIyMjICoqTGV0J3MgdGlkeSB1cCB0aGVzZSB0d28gY29sdW1ucyoqDQpgYGB7cn0NCmxpZmVfZXhwZWN0YW5jeV9kZiRZZWFyPXN1YnN0cihsaWZlX2V4cGVjdGFuY3lfZGYkWWVhciwyLDUpDQpsaWZlX2V4cGVjdGFuY3lfZGY9bGlmZV9leHBlY3RhbmN5X2RmICU+JQ0KICBtdXRhdGUoWWVhcj1hcy5pbnRlZ2VyKFllYXIpKSAlPiUNCiAgbXV0YXRlKGxpZmVfZXhwZWN0YW5jeT1hcy5udW1lcmljKGxpZmVfZXhwZWN0YW5jeSkpICU+JQ0KICAgIG11dGF0ZShDb3VudHJ5Lk5hbWU9YXMuY2hhcmFjdGVyKENvdW50cnkuTmFtZSkpIA0KbGlmZV9leHBlY3RhbmN5X2RmDQpgYGANCg0KIyMjICoqTGV0J3MgY2hlY2sgZm9yIG1pc3NpbmcgdmFsdWVzKioNCmBgYHtyfQ0KdGFibGUoaXMubmEobGlmZV9leHBlY3RhbmN5X2RmKSkNCg0KYGBgDQojIyMgKipMZXQncyByZW1vdmUgYWxsIHRoZSBOQSdzIHdpdGggUidzIGNvbXBsZXRlLmNhc2VzKCkqKg0KYGBge3J9DQpwYXN0ZSgiQmVmb3JlIE5BIHJlbW92YWw6Iixucm93KGxpZmVfZXhwZWN0YW5jeV9kZikpDQpsaWZlX2V4cGVjdGFuY3lfZGY9bGlmZV9leHBlY3RhbmN5X2RmW2NvbXBsZXRlLmNhc2VzKGxpZmVfZXhwZWN0YW5jeV9kZiksXQ0KcGFzdGUoIkFmdGVyIE5BIHJlbW92YWw6Iixucm93KGxpZmVfZXhwZWN0YW5jeV9kZikpDQpgYGANCiMjIyAqKkxldCdzIGNvbXB1dGUgdGhlIGF2ZXJhZ2UgbGlmZSBleHBlY3RhbmN5IHNpbmNlIDIwMDgqKg0KYGBge3J9DQpsaWZlX2V4cGVjdGFuY3lfZGY9bGlmZV9leHBlY3RhbmN5X2RmICU+JSANCiAgZ3JvdXBfYnkoQ291bnRyeS5OYW1lKSAlPiUNCiAgZHBseXI6OnN1bW1hcmlzZShhdmdfbGlmZV9leHBlY3RhbmN5PW1lYW4obGlmZV9leHBlY3RhbmN5KSkNCmxpZmVfZXhwZWN0YW5jeV9kZg0KYGBgDQoNCiMjIyAqKldlIHdvdWxkIGxpa2UgdG8gam9pbiB0aGUgdHdvIHRhYmxlcyBidXQgZmlyc3QgbGV0J3Mgc2VlIHdoYXQgdGhlIGRpZmZlcmVudCB0eXBlcyBvZiBqb2lucyBtZWFuKioNCg0KIVtdKFR5cGVzIG9mIEpvaW5zLnBuZykgDQoNCiMjIyAqKkNoZWNrIGZvciBkdXBsaWNhdGVzKioNCmBgYHtyfQ0KbGlmZV9leHBlY3RhbmN5X2RmICU+JQ0KICBncm91cF9ieShDb3VudHJ5Lk5hbWUpICU+JQ0KICBkcGx5cjo6c3VtbWFyaXNlKGNvdW50PW4oKSkgJT4lDQogIGFycmFuZ2UoLWNvdW50KQ0KYGBgDQoNCiMjIyAqKlNvbWUgZXhwbG9yYXRpb24gb2YgdGhlIGRpc3RyaWJ1dGlvbiBvZiBMaWZlIEV4cGVjdGFuY3kqKg0KYGBge3J9DQpnZ3Bsb3QobGlmZV9leHBlY3RhbmN5X2RmLGFlcyhhdmdfbGlmZV9leHBlY3RhbmN5KSkrDQogIGdlb21faGlzdG9ncmFtKGZpbGw9InZpb2xldHJlZDMiLGNvbG9yPSJibGFjayIpKw0KICB0aGVtZV9jbGFzc2ljKCkNCmBgYA0KIyMjKipMZXQncyBpbnZlc3RpZ2F0ZSB3aGF0IGNvdW50cmllcyBhcmUgdGhlIGxvd2VyIGVuZCBvZiB0aGUgbGlmZSBleHBlY3RhbmN5KioNCmBgYHtyfQ0KbGlmZV9leHBlY3RhbmN5X2RmICU+JQ0KICBhcnJhbmdlKGF2Z19saWZlX2V4cGVjdGFuY3kpDQoNCmBgYA0KDQojIyMqKkxldCdzIGFsc28gdmlzdWFsaXplIGEqKiBbc3BhdGlhbCBtYXBdKGh0dHBzOi8vcHVibGljLnRhYmxlYXUuY29tL3Byb2ZpbGUvcm9zZWJ1ZC5hbnd1cmkjIS92aXpob21lL1RoZUFCQy1YWVpvZkRhdGFTY2llbmNlL1NoZWV0Mj9wdWJsaXNoPXllcykgZm9yIExpZmUgRXhwZWN0YW5jeQ0KIVtdKE1hcDIucG5nKQ0KDQojIyMqKldlIHdhbnQgdG8gam9pbiB0aGUgaG9saWRheSBhbmQgdGhlIGxpZmUgZXhwZWN0YW5jeSBkYXRhIGZyYW1lcyBmb3IgY291bnRyaWVzIHRoYXQgaGF2ZSBib3RoIG51bWJlcnMgb2YgaG9saWRheXMgYW5kIGxpZmUgZXhwZWN0YW5jeS4gV2hhdCBKb2luIHdpbGwgYmUgbW9zdCBzdWl0YWJsZSBmb3IgdGhpcz8qKg0KYGBge3J9DQogbGlmZV9leHBlY3RhbmN5X2RmPWxpZmVfZXhwZWN0YW5jeV9kZiAlPiUNCiAgICBkcGx5cjo6cmVuYW1lKENvdW50cnk9Q291bnRyeS5OYW1lKQ0KaG9saWRheV9hbmRfbGlmZSA9IGlubmVyX2pvaW4oaG9saWRheV9kZixsaWZlX2V4cGVjdGFuY3lfZGYsYnk9IkNvdW50cnkiKQ0KaG9saWRheV9hbmRfbGlmZQ0KYGBgDQoNCiMjI1dlIGFyZSByZWFkeSBmb3IgdGhlIGZ1biBwYXJ0LCAqKk1hY2hpbmUgTGVhcm5pbmcqKiENCioqKg0KDQojIyMqKkxldCdzIGRvIHNvbWUgcHJlcHJvY2Vzc2luZyBsaWtlIHNwbGl0dGluZyB0aGUgZGF0YSBpbnRvIGEgdHJhaW5pbmcgYW5kIHRlc3Qgc2V0IHVzaW5nIGNhcmV0KioNCmBgYHtyfQ0KY291bnRyeV9kYXRhID0gcmVhZC5jc3YoImNvdW50cnlfZGF0YS5jc3YiKQ0Kc2V0LnNlZWQoMSkNCmlkeD1jcmVhdGVEYXRhUGFydGl0aW9uKGNvdW50cnlfZGF0YSRhdmdfbGlmZV9leHBlY3RhbmN5LHA9MC43LGxpc3QgPSBGKQ0KdHJhaW49Y291bnRyeV9kYXRhW2lkeCxdDQp0ZXN0PWNvdW50cnlfZGF0YVstaWR4LF0NCmNvdW50cnlfZGF0YQ0KcGFzdGUoIk51bWJlciBvZiBPYnNlcnZhdGlvbnMgZm9yIHRyYWluIGlzOiIsbnJvdyh0cmFpbiksIiYgTnVtYmVyIG9mIE9ic2VydmF0aW9ucyBmb3IgdGVzdCBpczoiLG5yb3codGVzdCkpDQoNCmBgYA0KDQojIyMqKlNvbWUgaW5pdGFsIGV4cGxvcmF0aW9uIG9mIG91ciBkYXRhKioNCmBgYHtyfQ0KDQpwMT1nZ3Bsb3QoY291bnRyeV9kYXRhLGFlcyhub19vZl9ob2xpZGF5cyxhdmdfZ2RwKSkrDQogIGdlb21fcG9pbnQoY29sb3I9InZpb2xldHJlZDMiLHNpemU9MikrDQogIGdndGl0bGUoIkhvbGlkYXlzIikrDQogIHRoZW1lX2NsYXNzaWMoKQ0KcDI9Z2dwbG90KGNvdW50cnlfZGF0YSxhZXMoUG9wdWxhdGlvbixhdmdfZ2RwKSkrDQogIGdlb21fcG9pbnQoY29sb3I9IiNlNjdlMjIiLHNpemU9MikrDQogIGdndGl0bGUoIlBvcHVsYXRpb24iKSsNCiAgdGhlbWVfY2xhc3NpYygpDQpwMz1nZ3Bsb3QoY291bnRyeV9kYXRhLGFlcyhIZWFsdGguZXhwZW5kaXR1cmUuLnByaXZhdGUuLi4ub2YuR0RQLixhdmdfZ2RwKSkrDQogIGdlb21fcG9pbnQoY29sb3I9IiMzNDk4ZGIiLHNpemU9MikrDQogIGdndGl0bGUoIkhlYWx0aCBFeHBlbmRpdHVyZSIpKw0KICB0aGVtZV9jbGFzc2ljKCkNCnA0PWdncGxvdChjb3VudHJ5X2RhdGEsYWVzKExhYm9yLmZvcmNlLi50b3RhbCxhdmdfZ2RwKSkrDQogIGdlb21fcG9pbnQoY29sb3I9IiMxYWJjOWMiLHNpemU9MikrDQogIGdndGl0bGUoIkxhYm9yIEZvcmNlIikrDQogIHRoZW1lX2NsYXNzaWMoKQ0KcDU9Z2dwbG90KGNvdW50cnlfZGF0YSxhZXMoVW5lbXBsb3ltZW50X1JhdGUsYXZnX2dkcCkpKw0KICBnZW9tX3BvaW50KGNvbG9yPSIjMzQ0OTVlIixzaXplPTIpKw0KICBnZ3RpdGxlKCJVbmVtcGxveW1lbnQgUmF0ZSIpKw0KICB0aGVtZV9jbGFzc2ljKCkNCnA2PWdncGxvdChjb3VudHJ5X2RhdGEsYWVzKGF2Z19saWZlX2V4cGVjdGFuY3ksYXZnX2dkcCkpKw0KICBnZW9tX3BvaW50KGNvbG9yPSIjZTc0YzNjIixzaXplPTIpKw0KICBnZ3RpdGxlKCJBdmVyYWdlIExpZmUgRXhwZWN0YW5jeSIpKw0KICB0aGVtZV9jbGFzc2ljKCkNCmdyaWQuYXJyYW5nZShwMSxwMixwMyxwNCxwNSxwNikNCmBgYA0KDQojIyMqKkNoYW5nZSB0byBsb2dhcml0aG1pYyBzY2FsZSoqDQpgYGB7cn0NCnAxPWdncGxvdChjb3VudHJ5X2RhdGEsYWVzKG5vX29mX2hvbGlkYXlzLGxvZyhhdmdfZ2RwKSkpKw0KICBnZW9tX3BvaW50KGNvbG9yPSJ2aW9sZXRyZWQzIixzaXplPTIpKw0KICBnZ3RpdGxlKCJIb2xpZGF5cyIpKw0KICB0aGVtZV9jbGFzc2ljKCkNCnAyPWdncGxvdChjb3VudHJ5X2RhdGEsYWVzKGxvZyhQb3B1bGF0aW9uKSxsb2coYXZnX2dkcCkpKSsNCiAgZ2VvbV9wb2ludChjb2xvcj0iI2U2N2UyMiIsc2l6ZT0yKSsNCiAgZ2d0aXRsZSgiUG9wdWxhdGlvbiIpKw0KICB0aGVtZV9jbGFzc2ljKCkNCnAzPWdncGxvdChjb3VudHJ5X2RhdGEsYWVzKEhlYWx0aC5leHBlbmRpdHVyZS4ucHJpdmF0ZS4uLi5vZi5HRFAuLGxvZyhhdmdfZ2RwKSkpKw0KICBnZW9tX3BvaW50KGNvbG9yPSIjMzQ5OGRiIixzaXplPTIpKw0KICBnZ3RpdGxlKCJIZWFsdGggRXhwZW5kaXR1cmUiKSsNCiAgdGhlbWVfY2xhc3NpYygpDQpwND1nZ3Bsb3QoY291bnRyeV9kYXRhLGFlcyhsb2coTGFib3IuZm9yY2UuLnRvdGFsKSxsb2coYXZnX2dkcCkpKSsNCiAgZ2VvbV9wb2ludChjb2xvcj0iIzFhYmM5YyIsc2l6ZT0yKSsNCiAgZ2d0aXRsZSgiTGFib3IgRm9yY2UiKSsNCiAgdGhlbWVfY2xhc3NpYygpDQpwNT1nZ3Bsb3QoY291bnRyeV9kYXRhLGFlcyhVbmVtcGxveW1lbnRfUmF0ZSxsb2coYXZnX2dkcCkpKSsNCiAgZ2VvbV9wb2ludChjb2xvcj0iIzM0NDk1ZSIsc2l6ZT0yKSsNCiAgZ2d0aXRsZSgiVW5lbXBsb3ltZW50IFJhdGUiKSsNCiAgdGhlbWVfY2xhc3NpYygpDQpwNj1nZ3Bsb3QoY291bnRyeV9kYXRhLGFlcyhhdmdfbGlmZV9leHBlY3RhbmN5LGxvZyhhdmdfZ2RwKSkpKw0KICBnZW9tX3BvaW50KGNvbG9yPSIjZTc0YzNjIixzaXplPTIpKw0KICBnZ3RpdGxlKCJBdmVyYWdlIExpZmUgRXhwZWN0YW5jeSIpKw0KICB0aGVtZV9jbGFzc2ljKCkNCmdyaWQuYXJyYW5nZShwMSxwMixwMyxwNCxwNSxwNikNCmBgYA0KDQojIyMqKkxldCdzIGNhcnJ5IG91dCBtb2RlbCBzZWxlY3Rpb24qKg0KYGBge3J9DQptb2RlbHMgPSBjKCJsbSIsInJmIiwicnBhcnQiLCJnYm0iKQ0KDQpzZXQuc2VlZCg3KQ0KbW9kZWxfc2VsZWN0aW9uID0gZnVuY3Rpb24obWV0aG9kX25hbWUpew0KICBpZihtZXRob2RfbmFtZT09InJmIil7DQogZ2FyYmFnZSA8LSBjYXB0dXJlLm91dHB1dCgNCiAgIA0KICAgbW9kZWwgPC10cmFpbih4PSB0cmFpblssYygyLDQ6OSldLCB5ID0gdHJhaW5bLDNdLCBtZXRob2QgPSBtZXRob2RfbmFtZSxpbXBvcnRhbmNlPVQpDQogICApDQogIH0NCiAgZWxzZXsNCiBnYXJiYWdlIDwtIGNhcHR1cmUub3V0cHV0KA0KICAgDQogICBtb2RlbCA8LXRyYWluKHg9IHRyYWluWyxjKDIsNDo5KV0sIHkgPSB0cmFpblssM10sIG1ldGhvZCA9IG1ldGhvZF9uYW1lKQ0KICAgKQ0KICAgIA0KICB9DQpyZXR1cm4obW9kZWwpDQoNCn0NClIyX21ldHJpYyA9IGZ1bmN0aW9uKG1vZGVsKXsNCiAgcHJlZFRlc3Q9cHJlZGljdChtb2RlbCxuZXdkYXRhID0gdGVzdCkNClIyID0gUjIocHJlZFRlc3QsdGVzdCRhdmdfZ2RwKQ0KcmV0dXJuKGMobW9kZWxbWyJtZXRob2QiXV0sUjIpKQ0KfQ0KbW9kZWxfcmVzdWx0cyA9IGxhcHBseShtb2RlbHMsbW9kZWxfc2VsZWN0aW9uKSANCm1vZGVsc19yMiA9IG1vZGVsX3Jlc3VsdHMlPiUNCiAgICAgICAgICAgICAgICAgICAgbGRwbHkoLixSMl9tZXRyaWMpDQpuYW1lcyhtb2RlbHNfcjIpID0gYygibW9kZWwiLCJyMiIpDQptb2RlbHNfcjIgJT4lDQogIG11dGF0ZShyMj1hcy5udW1lcmljKHIyKSkgJT4lDQogIGFycmFuZ2UoLXIyKQ0KYGBgDQoNCiMjIyoqUmFuZG9tRm9yZXN0IGRpZCBpdCBiZXN0LCBsZXQncyB0YWtlIGEgbG9vayBhdCB0aGUgaW1wb3J0YW50IHZhcmlhYmxlcyoqDQpgYGB7cn0NCnZhckltcChtb2RlbF9yZXN1bHRzW1syXV0pW1siaW1wb3J0YW5jZSJdXSAlPiUNCiAgYXMuZGF0YS5mcmFtZSgpICU+JQ0KICBtdXRhdGUodmFyaWFibGVzPXJvdy5uYW1lcyguKSkgJT4lDQogIGdncGxvdCguLGFlcyh4PXJlb3JkZXIodmFyaWFibGVzLE92ZXJhbGwpLHk9IE92ZXJhbGwgKSkrDQogIGdlb21fYmFyKHN0YXQgPSAiaWRlbnRpdHkiLGZpbGw9IiMxNmEwODUiLGNvbG9yPSIjMTZhMDg1IikrDQogIGNvb3JkX2ZsaXAoKSsNCiAgdGhlbWVfY2xhc3NpYygpKyANCiAgeGxhYigiVmFyaWFibGVzIikrDQogIHlsYWIoIldlaWdodGVkIEltcG9ydGFuY2UiKSsNCiAgZ2d0aXRsZSgiUmFuZG9tIEZvcmVzdCBWYXJpYWJsZSBJbXBvcnRhbmNlIikrDQogIGdlb21fdGV4dChhZXMobGFiZWw9cGFzdGUwKHJvdW5kKE92ZXJhbGwsMSksIiUiKSxoanVzdD1pZmVsc2UoT3ZlcmFsbDw5MCwtMC4xLDEpKSkNCmBgYA0KDQoNCg0K