This notebook uses the Data Science Survey on Kaggle dataset to understand the tools, preferred language and commonly used algorithms of data science practitioners in various working fields.

Load libraries

library(tidyverse)
library(ggdark)
library(viridis)
library(ggsci)
library(skimr)

Import data

data = read.csv("kagglesurvey.csv")
dim(data)
[1] 10153     5
head(data)
str(data)
'data.frame':   10153 obs. of  5 variables:
 $ Respondent                  : int  1 2 3 4 5 6 7 8 9 10 ...
 $ WorkToolsSelect             : chr  "Amazon Web services,Oracle Data Mining/ Oracle R Enterprise,Perl" "Amazon Machine Learning,Amazon Web services,Cloudera,Hadoop/Hive/Pig,Impala,Java,Mathematica,MATLAB/Octave,Micr"| __truncated__ "C/C++,Jupyter notebooks,MATLAB/Octave,Python,R,TensorFlow" "Jupyter notebooks,Python,SQL,TensorFlow" ...
 $ LanguageRecommendationSelect: chr  "F#" "Python" "Python" "Python" ...
 $ EmployerIndustry            : chr  "Internet-based" "Mix of fields" "Technology" "Academic" ...
 $ WorkAlgorithmsSelect        : chr  "Neural Networks,Random Forests,RNNs" "Bayesian Techniques,Decision Trees,Random Forests,Regression/Logistic Regression" "Bayesian Techniques,CNNs,Ensemble Methods,Neural Networks,Regression/Logistic Regression,SVMs" "Bayesian Techniques,CNNs,Decision Trees,Gradient Boosted Machines,Neural Networks,Random Forests,Regression/Log"| __truncated__ ...

Dataset features:

Missing data

# convert blanks to NA
data1 = mutate_all(data, list(~na_if(.,"")))
# missing values 
#sapply(data1, function(x) sum(is.na(x))) 
skim(data1)
── Data Summary ────────────────────────
                           Values
Name                       data1 
Number of rows             8132  
Number of columns          7     
_______________________          
Column type frequency:           
  character                4     
  numeric                  3     
________________________         
Group variables            None  

── Variable type: character ───────────────────────────────────────────────────────────────────────────────────────────────
  skim_variable                n_missing complete_rate   min   max empty n_unique whitespace
1 WorkToolsSelect                    177         0.978     1   834     0     5248          0
2 LanguageRecommendationSelect      1598         0.803     1     8     0       13          0
3 EmployerIndustry                    29         0.996     5    32     0       16          0
4 WorkAlgorithmsSelect               831         0.898     4   216     0     1420          0

── Variable type: numeric ─────────────────────────────────────────────────────────────────────────────────────────────────
  skim_variable n_missing complete_rate    mean      sd    p0   p25   p50   p75  p100 hist 
1 Respondent            0             1 4515.   2844.       1 2035. 4244. 6922. 10153 ▇▇▆▆▅
2 wt_counts             0             1    5.56    3.47     0    3     5     7     49 ▇▁▁▁▁
3 alg_count             0             1    3.28    2.43     0    1     3     5     15 ▇▅▁▁▁
# number of complete cases
data1 %>% filter(complete.cases(.)) %>% tally()
# drop obs with blanks across all columns except for ID
incomplete_df = data %>% filter(WorkToolsSelect=="", LanguageRecommendationSelect =="", EmployerIndustry =="", WorkAlgorithmsSelect =="") 
cdf = anti_join(data, incomplete_df, by="Respondent")
dim(cdf) 
[1] 9027    5
# drop obs with blanks in WorkToolsSelect, LanguageRecommendationSelect and WorkAlgorithmsSelect
incomplete_df2 = cdf %>% filter(WorkToolsSelect=="", LanguageRecommendationSelect =="", WorkAlgorithmsSelect =="") 
cdf2 = anti_join(cdf, incomplete_df2, by="Respondent")
dim(cdf2)
[1] 8132    5
data = cdf2

Tools used

# spilt string then flatten  
tools <- data  %>% 
    mutate(work_tools =str_split(WorkToolsSelect,",") )  %>% 
    unnest(work_tools)
# number of tools listed
length(unique(tools$work_tools))
[1] 50

User count of each tool

# plot (by alphabetical order)
uc_tool = tools %>% group_by(work_tools) %>% tally(sort=T) %>% mutate_if(is.character,list(~na_if(.,"")))
uc_tool %>% mutate(work_tools = fct_rev(work_tools)) %>% ggplot(aes(x=work_tools, y=n, fill=n)) + geom_col() + coord_flip() + dark_theme_minimal() + scale_fill_viridis(option="cividis") + theme(legend.position="none") + labs(x="",y="User Count", title="User count of each tool")

  • Tools with highest user count: Python > R > SQL > Jupyter notebooks
# 5 most common tools by user count
tools %>% group_by(work_tools) %>% tally(sort=T) %>% mutate_if(is.character,list(~na_if(.,""))) %>% top_n(5)
# 5 least common tools by user count
tools %>% group_by(work_tools) %>% tally(sort=T) %>% mutate_if(is.character,list(~na_if(.,""))) %>% top_n(-5)
  • There are 49 unique tools listed in the dataset, and one NA level.
  • Tools with the highest user count: Python > R > SQL > Jupyter notebooks > Tensorflow.
  • Least frequent tools by user count are DataRobot, Statistica, KNIME, Salfrod Systems and Angross.

Number of tools used per respondent

# count of tools per respondent 
data$wt_counts = lengths(strsplit(data$WorkToolsSelect,","))
summary(data$wt_counts)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   0.00    3.00    5.00    5.56    7.00   49.00 
# table 
ctools = data %>% group_by(wt_counts) %>% tally() %>% mutate(prop=round(n/sum(n),3)) %>% as.data.frame()
head(ctools)

# plot
data %>% group_by(wt_counts) %>% tally()  %>% ggplot(aes(x=wt_counts, y=n)) + geom_col() + dark_theme_minimal() + labs(x="Number of tools", y="Count", title="Number of tools listed by respondents")


# count of tools per respondent (excluding blanks)
data_tr= data %>% filter(wt_counts!=0)
summary(data_tr$wt_counts)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  1.000   3.000   5.000   5.683   7.000  49.000 
# table (exclude NA)
ctools2 = data_tr %>% group_by(wt_counts) %>% tally(sort=T) %>% mutate(prop=round(n/sum(n),2)) %>% as.data.frame()
head(ctools2)
  • 177 out of 8132 respondents did not specify any tools
  • Of those that specified one or more tools (in WorkTools Select)
    • median of 5 tools listed in a response
    • around 51% of the respondents listed three to six work tools

Preferred language

#plot
data %>% group_by(LanguageRecommendationSelect) %>% tally() %>% mutate_if(is.character,list(~na_if(.,""))) %>% ggplot(aes(x=reorder(LanguageRecommendationSelect,n), y=n)) + geom_col(width=0.8) + dark_theme_minimal() + labs(x="", y="Count", title="Preferred language") + coord_flip()

length(unique(data$LanguageRecommendationSelect)) 
[1] 14
# table (all levels)
data %>% group_by(LanguageRecommendationSelect) %>% tally(sort=T) %>% mutate_if(is.character,list(~na_if(.,""))) %>% mutate(prop=round(n/sum(n),3))
# table (excluding NA level)
data %>% filter(LanguageRecommendationSelect !="") %>% group_by(LanguageRecommendationSelect) %>% tally(sort=T) %>% mutate(prop=round(n/sum(n),3))

Working field

# plot
data %>% group_by(EmployerIndustry) %>% tally() %>% mutate_if(is.character,list(~na_if(.,""))) %>% ggplot(aes(x=reorder(EmployerIndustry,n), y=n)) + geom_col() + dark_theme_minimal() + labs(x="", y="Count", title="Working field") + coord_flip()

# summary table
length(unique(data$EmployerIndustry)) 
[1] 17
data %>% group_by(EmployerIndustry) %>% tally(sort=T) %>% mutate_if(is.character,list(~na_if(.,""))) %>% mutate(prop=round(n/sum(n),3))

Count of algorithms per response

data$alg_count = lengths(strsplit(data$WorkAlgorithmsSelect,","))
summary(data$alg_count)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  0.000   1.000   3.000   3.277   5.000  15.000 
# plot
data %>% group_by(alg_count) %>% tally() %>% ggplot(aes(x=alg_count, y=n)) + geom_col(width=0.8) + dark_theme_minimal() + labs(x="Number of algorithms per response", y="Count")

# summary table
data %>% group_by(alg_count) %>% tally() %>% mutate(prop=round(n/sum(n),3))

Algorithms user counts

# spilt then flatten  
alg <- data  %>% 
    mutate(WorkAlgorithmsSelect =str_split(WorkAlgorithmsSelect,",") )  %>% 
    unnest(WorkAlgorithmsSelect)
# unique levels
length(unique(alg$WorkAlgorithmsSelect))
[1] 16
# table
alg1 = alg %>% group_by(WorkAlgorithmsSelect) %>% tally(sort=T) %>% mutate_if(is.character,list(~na_if(.,""))) 
alg1
# plot
alg1 %>% ggplot(aes(x=reorder(WorkAlgorithmsSelect,n), y=n)) + geom_col(width=0.8) + dark_theme_minimal() + labs(x="", y="Count", title="Commonly used Algorithms") + coord_flip()

Most frequent tool in each industry

# proportion of most frequent tools in respective industry 
tools %>% filter(EmployerIndustry != "") %>% filter(work_tools != "") %>% group_by(EmployerIndustry, work_tools) %>% tally() %>% mutate(prop=round(n/sum(n),3)) %>% arrange(desc(prop), .by_group=TRUE) %>% group_by(EmployerIndustry) %>% slice(1) %>% as.data.frame()

Most frequent commonly used algorithms in each industry

# proportion of most frequent algorithm in respective industry 
alg %>% filter(EmployerIndustry != "") %>% filter(WorkAlgorithmsSelect != "") %>% group_by(EmployerIndustry, WorkAlgorithmsSelect) %>% tally() %>% mutate(prop=round(n/sum(n),3)) %>% arrange(desc(prop), .by_group=TRUE) %>% group_by(EmployerIndustry) %>% slice(1) %>% as.data.frame()
# LanguageRecommendationSelect by industry
data %>% filter(EmployerIndustry != "") %>% filter(LanguageRecommendationSelect != "") %>% group_by(EmployerIndustry, LanguageRecommendationSelect) %>% tally() %>% mutate(prop=n/sum(n)) %>% arrange(desc(prop), .by_group=TRUE) %>% group_by(EmployerIndustry) %>% slice(1) %>% as.data.frame()

# plot 
data %>% filter(LanguageRecommendationSelect !="") %>% filter(EmployerIndustry !="") %>% ggplot(aes(x=LanguageRecommendationSelect, y= fct_rev(EmployerIndustry), color=LanguageRecommendationSelect)) + geom_point() + theme_minimal() + labs(y="",x="") + scale_color_simpsons() + theme(legend.position="none", plot.background = element_rect(fill = "white"), panel.border = element_blank(), panel.grid.major = element_blank(), panel.grid.minor = element_blank()) 

Porportion of R (work_tool) in each EmployerIndustry

# table 
tools %>% filter(EmployerIndustry != "") %>% filter(work_tools != "") %>% group_by(EmployerIndustry, work_tools) %>% tally() %>% mutate(prop=round(n/sum(n),3)) %>% arrange(desc(prop), .by_group=TRUE) %>% group_by(EmployerIndustry) %>% filter(work_tools=="R") %>% arrange(desc(prop))

The fields that have the highest proportion of R (work tool) are non-profit, insurance and government, respectively.

Tools used in preferred languages groups R and Python

# table 
tools %>% filter(LanguageRecommendationSelect == "R") %>% group_by(work_tools) %>% tally(sort=T) %>% slice(1:10)
# table
tools %>% filter(LanguageRecommendationSelect == "Python") %>% group_by(work_tools) %>% tally(sort=T) %>% slice(1:10)

How many respondents use both R and Python (work tools) ? and what other work tools these respondents use?

py_r = data %>% filter(str_detect(WorkToolsSelect, 'R')) %>% filter(str_detect(WorkToolsSelect, 'Python')) %>% mutate(pr = 1)
# left join 
py_r = py_r %>% select(Respondent, pr)
data2 = left_join(data,py_r, by="Respondent") %>% mutate(pr = if_else(is.na(pr),0, pr))
Hmisc::describe(as.factor(data2$pr))
as.factor(data2$pr) 
       n  missing distinct 
    8132        0        2 
                    
Value         0    1
Frequency  4472 3660
Proportion 0.55 0.45
tools2 <- data2  %>% 
    mutate(work_tools =str_split(WorkToolsSelect,",") )  %>% 
    unnest(work_tools)

# get proportion  
tools2 %>% filter(pr==1) %>% group_by(work_tools) %>% tally(sort=T) %>% filter(work_tools !="Python" & work_tools !="R") %>% mutate(prop_res = round(n/3660,3))
# plot proportion of 10 most frequent tools used by respondents that listed both R and Python (work tools)
tools2 %>% filter(pr==1) %>% group_by(work_tools) %>% tally(sort=T) %>% mutate(prop_res = round(n/3660,2)) %>% filter(work_tools !="Python" & work_tools !="R") %>% slice(1:10) %>% ggplot(aes(x=reorder(work_tools,prop_res), y=prop_res, fill=work_tools)) + geom_col(width=0.5) + geom_text(stat="identity",aes(label=prop_res),hjust=-0.5,size=3,color="black") + coord_flip() + theme_minimal() + labs(y="Proportion",x="Work tools") + theme(panel.border = element_blank(), panel.grid.major = element_blank(), panel.grid.minor = element_blank(), legend.position="none") + scale_fill_simpsons() + scale_y_continuous(limits=c(0,1)) 

LS0tCnRpdGxlOiAiRGF0YSBTY2llbmNlIFN1cnZleSBFREEiCmRhdGU6ICJEZWMgMjAyMCIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKVGhpcyBub3RlYm9vayB1c2VzIHRoZSBbRGF0YSBTY2llbmNlIFN1cnZleSBvbiBLYWdnbGVdKGh0dHBzOi8vd3d3LmthZ2dsZS5jb20va2luZ2FienByby9kYXRhc2NpZW5jZS1zdXJ2ZXktb24ta2FnZ2xlKSBkYXRhc2V0IHRvIHVuZGVyc3RhbmQgdGhlIHRvb2xzLCBwcmVmZXJyZWQgbGFuZ3VhZ2UgYW5kIGNvbW1vbmx5IHVzZWQgYWxnb3JpdGhtcyBvZiBkYXRhIHNjaWVuY2UgcHJhY3RpdGlvbmVycyBpbiB2YXJpb3VzIHdvcmtpbmcgZmllbGRzLiAKCgojIyMgTG9hZCBsaWJyYXJpZXMgCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShnZ2RhcmspCmxpYnJhcnkodmlyaWRpcykKbGlicmFyeShnZ3NjaSkKbGlicmFyeShza2ltcikKYGBgCgojIyMgSW1wb3J0IGRhdGEKYGBge3J9CmRhdGEgPSByZWFkLmNzdigia2FnZ2xlc3VydmV5LmNzdiIpCmRpbShkYXRhKQpoZWFkKGRhdGEpCmBgYAoKKiAxMDE1MyBzdXJ2ZXkgcmVzcG9uc2VzIGluIHRoZSBkYXRhc2V0CgpgYGB7cn0Kc3RyKGRhdGEpCmBgYAoKRGF0YXNldCBmZWF0dXJlczogCgoqIFJlc3BvbmRlbnQ6IGlkCiogV29ya1Rvb2xzU2VsZWN0OiBUb29scyB1c2VkCiogTGFuZ3VhZ2VSZWNvbW1lbmRhdGlvblNlbGVjdDogUHJlZmVycmVkIExhbmd1YWdlCiogRW1wbG95ZXJJbmR1c3RyeTogV29ya2luZyBmaWVsZHMgCiogV29ya0FsZ29yaXRobXNTZWxlY3Q6IEFsZ29yaXRobSBjb21tb25seSB1c2VkIGJ5IHJlc3BvbmRlbnRzIAoKCiMjIyBNaXNzaW5nIGRhdGEgCmBgYHtyfQojIGNvbnZlcnQgYmxhbmtzIHRvIE5BCmRhdGExID0gbXV0YXRlX2FsbChkYXRhLCBsaXN0KH5uYV9pZiguLCIiKSkpCiMgbWlzc2luZyB2YWx1ZXMgCiNzYXBwbHkoZGF0YTEsIGZ1bmN0aW9uKHgpIHN1bShpcy5uYSh4KSkpIApza2ltKGRhdGExKQojIG51bWJlciBvZiBjb21wbGV0ZSBjYXNlcwpkYXRhMSAlPiUgZmlsdGVyKGNvbXBsZXRlLmNhc2VzKC4pKSAlPiUgdGFsbHkoKQpgYGAKCmBgYHtyfQojIGRyb3Agb2JzIHdpdGggYmxhbmtzIGFjcm9zcyBhbGwgY29sdW1ucyBleGNlcHQgZm9yIElECmluY29tcGxldGVfZGYgPSBkYXRhICU+JSBmaWx0ZXIoV29ya1Rvb2xzU2VsZWN0PT0iIiwgTGFuZ3VhZ2VSZWNvbW1lbmRhdGlvblNlbGVjdCA9PSIiLCBFbXBsb3llckluZHVzdHJ5ID09IiIsIFdvcmtBbGdvcml0aG1zU2VsZWN0ID09IiIpIApjZGYgPSBhbnRpX2pvaW4oZGF0YSwgaW5jb21wbGV0ZV9kZiwgYnk9IlJlc3BvbmRlbnQiKQpkaW0oY2RmKSAKIyBkcm9wIG9icyB3aXRoIGJsYW5rcyBpbiBXb3JrVG9vbHNTZWxlY3QsIExhbmd1YWdlUmVjb21tZW5kYXRpb25TZWxlY3QgYW5kIFdvcmtBbGdvcml0aG1zU2VsZWN0CmluY29tcGxldGVfZGYyID0gY2RmICU+JSBmaWx0ZXIoV29ya1Rvb2xzU2VsZWN0PT0iIiwgTGFuZ3VhZ2VSZWNvbW1lbmRhdGlvblNlbGVjdCA9PSIiLCBXb3JrQWxnb3JpdGhtc1NlbGVjdCA9PSIiKSAKY2RmMiA9IGFudGlfam9pbihjZGYsIGluY29tcGxldGVfZGYyLCBieT0iUmVzcG9uZGVudCIpCmRpbShjZGYyKQpkYXRhID0gY2RmMgpgYGAKCiogT3V0IG9mIDEwMTUzIG9ic2VydmF0aW9ucywgdGhlcmUgYXJlOiAKICArIDU5OTEgY29tcGxldGUgY2FzZXMKICArIDkwMjcgb2JzIGhhdmUgbm8gYmxhbmtzIGFjcm9zcyBhbGwgY29sdW1ucyBleGNlcHQgZm9yIElECiAgICArIG9mIHdoaWNoLCA4MTMyIG9icyBoYXZlIG5vIGJsYW5rcyBpbiBXb3JrVG9vbHNTZWxlY3QsIExhbmd1YWdlUmVjb21tZW5kYXRpb25TZWxlY3QgYW5kIFdvcmtBbGdvcml0aG1zU2VsZWN0IAoqIFRoZSBmb2xsb3dpbmcgc2VjdGlvbnMgdXNlcyB0aGUgc3Vic2V0IGNvbnRhaW5pbmcgODEzMiBvYnMuIAoKCiMjIyBUb29scyB1c2VkCmBgYHtyfQojIHNwaWx0IHN0cmluZyB0aGVuIGZsYXR0ZW4gIAp0b29scyA8LSBkYXRhICAlPiUgCiAgICBtdXRhdGUod29ya190b29scyA9c3RyX3NwbGl0KFdvcmtUb29sc1NlbGVjdCwiLCIpICkgICU+JSAKICAgIHVubmVzdCh3b3JrX3Rvb2xzKQojIG51bWJlciBvZiB0b29scyBsaXN0ZWQKbGVuZ3RoKHVuaXF1ZSh0b29scyR3b3JrX3Rvb2xzKSkKYGBgCgojIyMjIFVzZXIgY291bnQgb2YgZWFjaCB0b29sCgpgYGB7ciwgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9NX0KIyBwbG90IChieSBhbHBoYWJldGljYWwgb3JkZXIpCnVjX3Rvb2wgPSB0b29scyAlPiUgZ3JvdXBfYnkod29ya190b29scykgJT4lIHRhbGx5KHNvcnQ9VCkgJT4lIG11dGF0ZV9pZihpcy5jaGFyYWN0ZXIsbGlzdCh+bmFfaWYoLiwiIikpKQp1Y190b29sICU+JSBtdXRhdGUod29ya190b29scyA9IGZjdF9yZXYod29ya190b29scykpICU+JSBnZ3Bsb3QoYWVzKHg9d29ya190b29scywgeT1uLCBmaWxsPW4pKSArIGdlb21fY29sKCkgKyBjb29yZF9mbGlwKCkgKyBkYXJrX3RoZW1lX21pbmltYWwoKSArIHNjYWxlX2ZpbGxfdmlyaWRpcyhvcHRpb249ImNpdmlkaXMiKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbj0ibm9uZSIpICsgbGFicyh4PSIiLHk9IlVzZXIgQ291bnQiLCB0aXRsZT0iVXNlciBjb3VudCBvZiBlYWNoIHRvb2wiKQpgYGAKCiogVG9vbHMgd2l0aCBoaWdoZXN0IHVzZXIgY291bnQ6IFB5dGhvbiA+IFIgPiBTUUwgPiBKdXB5dGVyIG5vdGVib29rcyAKCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQojIDUgbW9zdCBjb21tb24gdG9vbHMgYnkgdXNlciBjb3VudAp0b29scyAlPiUgZ3JvdXBfYnkod29ya190b29scykgJT4lIHRhbGx5KHNvcnQ9VCkgJT4lIG11dGF0ZV9pZihpcy5jaGFyYWN0ZXIsbGlzdCh+bmFfaWYoLiwiIikpKSAlPiUgdG9wX24oNSkKIyA1IGxlYXN0IGNvbW1vbiB0b29scyBieSB1c2VyIGNvdW50CnRvb2xzICU+JSBncm91cF9ieSh3b3JrX3Rvb2xzKSAlPiUgdGFsbHkoc29ydD1UKSAlPiUgbXV0YXRlX2lmKGlzLmNoYXJhY3RlcixsaXN0KH5uYV9pZiguLCIiKSkpICU+JSB0b3BfbigtNSkKYGBgCgoqIFRoZXJlIGFyZSA0OSB1bmlxdWUgdG9vbHMgbGlzdGVkIGluIHRoZSBkYXRhc2V0LCBhbmQgb25lIE5BIGxldmVsLiAKKiBUb29scyB3aXRoIHRoZSBoaWdoZXN0IHVzZXIgY291bnQ6IFB5dGhvbiA+IFIgPiBTUUwgPiBKdXB5dGVyIG5vdGVib29rcyA+IFRlbnNvcmZsb3cuCiogTGVhc3QgZnJlcXVlbnQgdG9vbHMgYnkgdXNlciBjb3VudCBhcmUgRGF0YVJvYm90LCBTdGF0aXN0aWNhLCBLTklNRSwgU2FsZnJvZCBTeXN0ZW1zIGFuZCBBbmdyb3NzLiAKCiMjIyMgTnVtYmVyIG9mIHRvb2xzIHVzZWQgcGVyIHJlc3BvbmRlbnQKCmBgYHtyfQojIGNvdW50IG9mIHRvb2xzIHBlciByZXNwb25kZW50IApkYXRhJHd0X2NvdW50cyA9IGxlbmd0aHMoc3Ryc3BsaXQoZGF0YSRXb3JrVG9vbHNTZWxlY3QsIiwiKSkKc3VtbWFyeShkYXRhJHd0X2NvdW50cykKCiMgdGFibGUgCmN0b29scyA9IGRhdGEgJT4lIGdyb3VwX2J5KHd0X2NvdW50cykgJT4lIHRhbGx5KCkgJT4lIG11dGF0ZShwcm9wPXJvdW5kKG4vc3VtKG4pLDMpKSAlPiUgYXMuZGF0YS5mcmFtZSgpCmhlYWQoY3Rvb2xzKQoKIyBwbG90CmRhdGEgJT4lIGdyb3VwX2J5KHd0X2NvdW50cykgJT4lIHRhbGx5KCkgICU+JSBnZ3Bsb3QoYWVzKHg9d3RfY291bnRzLCB5PW4pKSArIGdlb21fY29sKCkgKyBkYXJrX3RoZW1lX21pbmltYWwoKSArIGxhYnMoeD0iTnVtYmVyIG9mIHRvb2xzIiwgeT0iQ291bnQiLCB0aXRsZT0iTnVtYmVyIG9mIHRvb2xzIGxpc3RlZCBieSByZXNwb25kZW50cyIpCgojIGNvdW50IG9mIHRvb2xzIHBlciByZXNwb25kZW50IChleGNsdWRpbmcgYmxhbmtzKQpkYXRhX3RyPSBkYXRhICU+JSBmaWx0ZXIod3RfY291bnRzIT0wKQpzdW1tYXJ5KGRhdGFfdHIkd3RfY291bnRzKQoKIyB0YWJsZSAoZXhjbHVkZSBOQSkKY3Rvb2xzMiA9IGRhdGFfdHIgJT4lIGdyb3VwX2J5KHd0X2NvdW50cykgJT4lIHRhbGx5KHNvcnQ9VCkgJT4lIG11dGF0ZShwcm9wPXJvdW5kKG4vc3VtKG4pLDIpKSAlPiUgYXMuZGF0YS5mcmFtZSgpCmhlYWQoY3Rvb2xzMikKYGBgCgoqIDE3NyBvdXQgb2YgODEzMiByZXNwb25kZW50cyBkaWQgbm90IHNwZWNpZnkgYW55IHRvb2xzCiogT2YgdGhvc2UgdGhhdCBzcGVjaWZpZWQgb25lIG9yIG1vcmUgdG9vbHMgKGluIFdvcmtUb29scyBTZWxlY3QpCiAgKiBtZWRpYW4gb2YgNSB0b29scyBsaXN0ZWQgaW4gYSByZXNwb25zZQogICogYXJvdW5kIDUxJSBvZiB0aGUgcmVzcG9uZGVudHMgbGlzdGVkIHRocmVlIHRvIHNpeCB3b3JrIHRvb2xzCgoKIyMjIFByZWZlcnJlZCBsYW5ndWFnZQoKYGBge3J9CiNwbG90CmRhdGEgJT4lIGdyb3VwX2J5KExhbmd1YWdlUmVjb21tZW5kYXRpb25TZWxlY3QpICU+JSB0YWxseSgpICU+JSBtdXRhdGVfaWYoaXMuY2hhcmFjdGVyLGxpc3Qofm5hX2lmKC4sIiIpKSkgJT4lIGdncGxvdChhZXMoeD1yZW9yZGVyKExhbmd1YWdlUmVjb21tZW5kYXRpb25TZWxlY3QsbiksIHk9bikpICsgZ2VvbV9jb2wod2lkdGg9MC44KSArIGRhcmtfdGhlbWVfbWluaW1hbCgpICsgbGFicyh4PSIiLCB5PSJDb3VudCIsIHRpdGxlPSJQcmVmZXJyZWQgbGFuZ3VhZ2UiKSArIGNvb3JkX2ZsaXAoKQpgYGAKCgpgYGB7cn0KbGVuZ3RoKHVuaXF1ZShkYXRhJExhbmd1YWdlUmVjb21tZW5kYXRpb25TZWxlY3QpKSAKIyB0YWJsZSAoYWxsIGxldmVscykKZGF0YSAlPiUgZ3JvdXBfYnkoTGFuZ3VhZ2VSZWNvbW1lbmRhdGlvblNlbGVjdCkgJT4lIHRhbGx5KHNvcnQ9VCkgJT4lIG11dGF0ZV9pZihpcy5jaGFyYWN0ZXIsbGlzdCh+bmFfaWYoLiwiIikpKSAlPiUgbXV0YXRlKHByb3A9cm91bmQobi9zdW0obiksMykpCiMgdGFibGUgKGV4Y2x1ZGluZyBOQSBsZXZlbCkKZGF0YSAlPiUgZmlsdGVyKExhbmd1YWdlUmVjb21tZW5kYXRpb25TZWxlY3QgIT0iIikgJT4lIGdyb3VwX2J5KExhbmd1YWdlUmVjb21tZW5kYXRpb25TZWxlY3QpICU+JSB0YWxseShzb3J0PVQpICU+JSBtdXRhdGUocHJvcD1yb3VuZChuL3N1bShuKSwzKSkKYGBgCgoqIDE0IGxldmVscyBpbiB0aGUgdmFyaWFibGUgTGFuZ3VhZ2VSZWNjb21lbmRhdGlvblNlbGVjdCAocHJlZmVycmVkIGxhbmd1YWdlKSwgaW5jbHVkaW5nIG9uZSBOQSBsZXZlbAoqIDE1OTggb3V0IG9mIDgxMzIgcmVzcG9uZGVudHMgKDE5LjclKSBkaWQgbm90IHNwZWNpZnkgYW55IGxhbmd1YWdlIG9mIHByZWZlcmVuY2UKKiBvZiB0aG9zZSB0aGF0IHNwZWNpZmllZCBsYW5ndWFnZXM6IDYyJSBwcmVmZXIgUHl0aG9uLCAyNS42JSBwcmVmZXIgUiBhbmQgIDQuMiUgcHJlZmVyIFNRTCAgCgoKIyMjIFdvcmtpbmcgZmllbGQKYGBge3J9CiMgcGxvdApkYXRhICU+JSBncm91cF9ieShFbXBsb3llckluZHVzdHJ5KSAlPiUgdGFsbHkoKSAlPiUgbXV0YXRlX2lmKGlzLmNoYXJhY3RlcixsaXN0KH5uYV9pZiguLCIiKSkpICU+JSBnZ3Bsb3QoYWVzKHg9cmVvcmRlcihFbXBsb3llckluZHVzdHJ5LG4pLCB5PW4pKSArIGdlb21fY29sKCkgKyBkYXJrX3RoZW1lX21pbmltYWwoKSArIGxhYnMoeD0iIiwgeT0iQ291bnQiLCB0aXRsZT0iV29ya2luZyBmaWVsZCIpICsgY29vcmRfZmxpcCgpCmBgYAoKYGBge3J9CiMgc3VtbWFyeSB0YWJsZQpsZW5ndGgodW5pcXVlKGRhdGEkRW1wbG95ZXJJbmR1c3RyeSkpIApkYXRhICU+JSBncm91cF9ieShFbXBsb3llckluZHVzdHJ5KSAlPiUgdGFsbHkoc29ydD1UKSAlPiUgbXV0YXRlX2lmKGlzLmNoYXJhY3RlcixsaXN0KH5uYV9pZiguLCIiKSkpICU+JSBtdXRhdGUocHJvcD1yb3VuZChuL3N1bShuKSwzKSkKYGBgCgoqIDI5IG91dCBvZiA4MTMyIHJlc3BvbmRlbnRzIGRpZCBub3Qgc3BlY2lmeSBhIHdvcmtpbmcgZmllbGQgCiogTWFqb3JpdHkgb2YgdGhlIHJlc3BvbmRlbnRzICh+NDYlKSB3b3JrIGluIFRlY2hub2xvZ3ksIEFjYWRlbWljIG9yIEZpbmFuY2lhbCBmaWVsZHMuIAoKIyMjIENvdW50IG9mIGFsZ29yaXRobXMgcGVyIHJlc3BvbnNlCmBgYHtyfQpkYXRhJGFsZ19jb3VudCA9IGxlbmd0aHMoc3Ryc3BsaXQoZGF0YSRXb3JrQWxnb3JpdGhtc1NlbGVjdCwiLCIpKQpzdW1tYXJ5KGRhdGEkYWxnX2NvdW50KQojIHBsb3QKZGF0YSAlPiUgZ3JvdXBfYnkoYWxnX2NvdW50KSAlPiUgdGFsbHkoKSAlPiUgZ2dwbG90KGFlcyh4PWFsZ19jb3VudCwgeT1uKSkgKyBnZW9tX2NvbCh3aWR0aD0wLjgpICsgZGFya190aGVtZV9taW5pbWFsKCkgKyBsYWJzKHg9Ik51bWJlciBvZiBhbGdvcml0aG1zIHBlciByZXNwb25zZSIsIHk9IkNvdW50IikKIyBzdW1tYXJ5IHRhYmxlCmRhdGEgJT4lIGdyb3VwX2J5KGFsZ19jb3VudCkgJT4lIHRhbGx5KCkgJT4lIG11dGF0ZShwcm9wPXJvdW5kKG4vc3VtKG4pLDMpKQpgYGAKCiogTWVkaWFuIG9mIDMgYWxnb3JpdGhtcyBhbmQgbWF4aW11bSBvZiAxNSBhbGdvcml0aG1zIGxpc3RlZAoqIDgzMSBvdXQgb2YgODEzMiByZXNwb25zZXMgKDEwLjIlKSBkaWQgbm90IGxpc3QgYW55IGNvbW1vbmx5IHVzZWQgYWxnb3JpdGhtcyAKKiBBcm91bmQgNjIlIG9mIHRoZSByZXNwb25kZW50cyBsaXN0ZWQgb25lIHRvIGZvdXIgY29tbW9ubHkgdXNlZCBhbGdvcml0aG1zIAoKCiMjIyBBbGdvcml0aG1zIHVzZXIgY291bnRzIApgYGB7cn0KIyBzcGlsdCB0aGVuIGZsYXR0ZW4gIAphbGcgPC0gZGF0YSAgJT4lIAogICAgbXV0YXRlKFdvcmtBbGdvcml0aG1zU2VsZWN0ID1zdHJfc3BsaXQoV29ya0FsZ29yaXRobXNTZWxlY3QsIiwiKSApICAlPiUgCiAgICB1bm5lc3QoV29ya0FsZ29yaXRobXNTZWxlY3QpCiMgdW5pcXVlIGxldmVscwpsZW5ndGgodW5pcXVlKGFsZyRXb3JrQWxnb3JpdGhtc1NlbGVjdCkpCiMgdGFibGUKYWxnMSA9IGFsZyAlPiUgZ3JvdXBfYnkoV29ya0FsZ29yaXRobXNTZWxlY3QpICU+JSB0YWxseShzb3J0PVQpICU+JSBtdXRhdGVfaWYoaXMuY2hhcmFjdGVyLGxpc3Qofm5hX2lmKC4sIiIpKSkgCmFsZzEKYGBgCgpgYGB7cn0KIyBwbG90CmFsZzEgJT4lIGdncGxvdChhZXMoeD1yZW9yZGVyKFdvcmtBbGdvcml0aG1zU2VsZWN0LG4pLCB5PW4pKSArIGdlb21fY29sKHdpZHRoPTAuOCkgKyBkYXJrX3RoZW1lX21pbmltYWwoKSArIGxhYnMoeD0iIiwgeT0iQ291bnQiLCB0aXRsZT0iQ29tbW9ubHkgdXNlZCBBbGdvcml0aG1zIikgKyBjb29yZF9mbGlwKCkKYGBgCgoqIDE2IHVuaXF1ZSBsZXZlbHMgaW4gdGhlIHZhcmlhYmxlIFdvcmtBbGdvcml0aG1zU2VsZWN0LCBpbmNsdWRpbmcgb25lIE5BIGxldmVsCiogVGhyZWUgTW9zdCBmcmVxdWVudCBjb21tb25seSB1c2VkIGFsZ29yaXRobXMgbGlzdGVkIGFyZSAKICArIFJlZ3Jlc3Npb24vTG9naXN0aWMgUmVncmVzc2lvbiAobj00NjM2KQogICsgRGVjaXNpb24gVHJlZXMgKG49MzQ2MCkKICArIFJhbmRvbSBGb3Jlc3QgKG49MzM3OCkKKiBMZWFzdCBmcmVxdWVudCBjb21tb25seSB1c2VkIGFsZ29yaXRobXMgbGlzdGVkIGJ5IHJlc3BvbmRlbnRzIGlzIEdBTlMgKG49MjA3KQoKCiMjIyBNb3N0IGZyZXF1ZW50IHRvb2wgaW4gZWFjaCBpbmR1c3RyeQpgYGB7cn0KIyBwcm9wb3J0aW9uIG9mIG1vc3QgZnJlcXVlbnQgdG9vbHMgaW4gcmVzcGVjdGl2ZSBpbmR1c3RyeSAKdG9vbHMgJT4lIGZpbHRlcihFbXBsb3llckluZHVzdHJ5ICE9ICIiKSAlPiUgZmlsdGVyKHdvcmtfdG9vbHMgIT0gIiIpICU+JSBncm91cF9ieShFbXBsb3llckluZHVzdHJ5LCB3b3JrX3Rvb2xzKSAlPiUgdGFsbHkoKSAlPiUgbXV0YXRlKHByb3A9cm91bmQobi9zdW0obiksMykpICU+JSBhcnJhbmdlKGRlc2MocHJvcCksIC5ieV9ncm91cD1UUlVFKSAlPiUgZ3JvdXBfYnkoRW1wbG95ZXJJbmR1c3RyeSkgJT4lIHNsaWNlKDEpICU+JSBhcy5kYXRhLmZyYW1lKCkKYGBgCgoqIFB5dGhvbiBpcyB0aGUgbW9zdCBmcmVxdWVudCB0b29sIHVzZWQgYnkgcmVzcG9uZGVudHMgYWNyb3NzIHRoZSB2YXJpb3VzIHdvcmtpbmcgZmllbGRzLCBleGNlcHQgZm9yIEluc3VyYW5jZSBhbmQgTm9uX3Byb2ZpdCB3aGVyZSBSIGlzIHRoZSBtb3N0IGZyZXF1ZW50IHRvb2wgdXNlZC4gCgojIyMgTW9zdCBmcmVxdWVudCBjb21tb25seSB1c2VkIGFsZ29yaXRobXMgaW4gZWFjaCBpbmR1c3RyeQpgYGB7cn0KIyBwcm9wb3J0aW9uIG9mIG1vc3QgZnJlcXVlbnQgYWxnb3JpdGhtIGluIHJlc3BlY3RpdmUgaW5kdXN0cnkgCmFsZyAlPiUgZmlsdGVyKEVtcGxveWVySW5kdXN0cnkgIT0gIiIpICU+JSBmaWx0ZXIoV29ya0FsZ29yaXRobXNTZWxlY3QgIT0gIiIpICU+JSBncm91cF9ieShFbXBsb3llckluZHVzdHJ5LCBXb3JrQWxnb3JpdGhtc1NlbGVjdCkgJT4lIHRhbGx5KCkgJT4lIG11dGF0ZShwcm9wPXJvdW5kKG4vc3VtKG4pLDMpKSAlPiUgYXJyYW5nZShkZXNjKHByb3ApLCAuYnlfZ3JvdXA9VFJVRSkgJT4lIGdyb3VwX2J5KEVtcGxveWVySW5kdXN0cnkpICU+JSBzbGljZSgxKSAlPiUgYXMuZGF0YS5mcmFtZSgpCmBgYAoKKiBSZWdyZXNzaW9uL0xvZ2lzdGljIHJlZ3Jlc3Npb24gaXMgdGhlIG1vc3QgZnJlcXVlbnQgYWxnb3JpdGhtIGNvbW1vbmx5IHVzZWQgYWNyb3NzIHdvcmtpbmcgZmllbGRzLCBleGNlcHQgZm9yIE1pbGl0YXJ5L1NlY3VyaXR5IGZpZWxkIHdoZXJlIE5ldXJhbCBOZXR3b3JrIGlzIHRoZSBtb3N0IGZyZXF1ZW50LiAKCmBgYHtyfQojIExhbmd1YWdlUmVjb21tZW5kYXRpb25TZWxlY3QgYnkgaW5kdXN0cnkKZGF0YSAlPiUgZmlsdGVyKEVtcGxveWVySW5kdXN0cnkgIT0gIiIpICU+JSBmaWx0ZXIoTGFuZ3VhZ2VSZWNvbW1lbmRhdGlvblNlbGVjdCAhPSAiIikgJT4lIGdyb3VwX2J5KEVtcGxveWVySW5kdXN0cnksIExhbmd1YWdlUmVjb21tZW5kYXRpb25TZWxlY3QpICU+JSB0YWxseSgpICU+JSBtdXRhdGUocHJvcD1uL3N1bShuKSkgJT4lIGFycmFuZ2UoZGVzYyhwcm9wKSwgLmJ5X2dyb3VwPVRSVUUpICU+JSBncm91cF9ieShFbXBsb3llckluZHVzdHJ5KSAlPiUgc2xpY2UoMSkgJT4lIGFzLmRhdGEuZnJhbWUoKQoKIyBwbG90IApkYXRhICU+JSBmaWx0ZXIoTGFuZ3VhZ2VSZWNvbW1lbmRhdGlvblNlbGVjdCAhPSIiKSAlPiUgZmlsdGVyKEVtcGxveWVySW5kdXN0cnkgIT0iIikgJT4lIGdncGxvdChhZXMoeD1MYW5ndWFnZVJlY29tbWVuZGF0aW9uU2VsZWN0LCB5PSBmY3RfcmV2KEVtcGxveWVySW5kdXN0cnkpLCBjb2xvcj1MYW5ndWFnZVJlY29tbWVuZGF0aW9uU2VsZWN0KSkgKyBnZW9tX3BvaW50KCkgKyB0aGVtZV9taW5pbWFsKCkgKyBsYWJzKHk9IiIseD0iIikgKyBzY2FsZV9jb2xvcl9zaW1wc29ucygpICsgdGhlbWUobGVnZW5kLnBvc2l0aW9uPSJub25lIiwgcGxvdC5iYWNrZ3JvdW5kID0gZWxlbWVudF9yZWN0KGZpbGwgPSAid2hpdGUiKSwgcGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLCBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLCBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpKSAKCmBgYAoqIFB5dGhvbiwgUiBhbmQgU1FMIGFyZSBsaXN0ZWQgYXMgcmVzcG9uZGVudHMnIHByZWZlcnJlZCBsYW5ndWFnZSBhY3Jvc3MgYWxsKDE2KSB3b3JraW5nIGZpZWxkcy4gCgojIyMgUG9ycG9ydGlvbiBvZiBSICh3b3JrX3Rvb2wpIGluIGVhY2ggRW1wbG95ZXJJbmR1c3RyeQpgYGB7cn0KIyB0YWJsZSAKdG9vbHMgJT4lIGZpbHRlcihFbXBsb3llckluZHVzdHJ5ICE9ICIiKSAlPiUgZmlsdGVyKHdvcmtfdG9vbHMgIT0gIiIpICU+JSBncm91cF9ieShFbXBsb3llckluZHVzdHJ5LCB3b3JrX3Rvb2xzKSAlPiUgdGFsbHkoKSAlPiUgbXV0YXRlKHByb3A9cm91bmQobi9zdW0obiksMykpICU+JSBhcnJhbmdlKGRlc2MocHJvcCksIC5ieV9ncm91cD1UUlVFKSAlPiUgZ3JvdXBfYnkoRW1wbG95ZXJJbmR1c3RyeSkgJT4lIGZpbHRlcih3b3JrX3Rvb2xzPT0iUiIpICU+JSBhcnJhbmdlKGRlc2MocHJvcCkpCmBgYApUaGUgZmllbGRzIHRoYXQgaGF2ZSB0aGUgaGlnaGVzdCBwcm9wb3J0aW9uIG9mIFIgKHdvcmsgdG9vbCkgYXJlIG5vbi1wcm9maXQsIGluc3VyYW5jZSBhbmQgZ292ZXJubWVudCwgcmVzcGVjdGl2ZWx5LiAKCiMjIyBUb29scyB1c2VkIGluIHByZWZlcnJlZCBsYW5ndWFnZXMgZ3JvdXBzIFIgYW5kIFB5dGhvbgpgYGB7cn0KIyB0YWJsZSAKdG9vbHMgJT4lIGZpbHRlcihMYW5ndWFnZVJlY29tbWVuZGF0aW9uU2VsZWN0ID09ICJSIikgJT4lIGdyb3VwX2J5KHdvcmtfdG9vbHMpICU+JSB0YWxseShzb3J0PVQpICU+JSBzbGljZSgxOjEwKQojIHRhYmxlCnRvb2xzICU+JSBmaWx0ZXIoTGFuZ3VhZ2VSZWNvbW1lbmRhdGlvblNlbGVjdCA9PSAiUHl0aG9uIikgJT4lIGdyb3VwX2J5KHdvcmtfdG9vbHMpICU+JSB0YWxseShzb3J0PVQpICU+JSBzbGljZSgxOjEwKQpgYGAKCiogVGhlcmUgYXJlIHNvbWUgZGlmZmVyZW5jZXMgaW4gbW9zdCBmcmVxdWVudCB0b29scyB1c2VkIGJ5IHRoZSB0d28gZ3JvdXBzIAogICsgUiAocHJlZmVycmVkIGxhbmd1YWdlKSBncm91cDogbW9zdCBmcmVxdWVudCB3b3JrIHRvb2xzIHVzZWQgYXJlIFB5dGhvbiwgU1FMLCBUYWJsZWF1IGFuZCBKdXB5dGVyIE5vdGVib29rLCByZXNwZWN0aXZlbHkKICArIFB5dGhvbiAocHJlZmVycmVkIGxhbmd1YWdlKSBncm91cDogbW9zdCBmcmVxdWVudCB3b3JrIHRvb2xzIHVzZWQgYXJlIFNRTCwgSnVweXRlciBOb3RlYm9vaywgUiBhbmQgVGVuc29yRmxvdywgcmVzcGVjdGl2ZWx5CgoKIyMjIEhvdyBtYW55IHJlc3BvbmRlbnRzIHVzZSBib3RoIFIgYW5kIFB5dGhvbiAod29yayB0b29scykgPyBhbmQgd2hhdCBvdGhlciB3b3JrIHRvb2xzIHRoZXNlIHJlc3BvbmRlbnRzIHVzZT8gCmBgYHtyfQpweV9yID0gZGF0YSAlPiUgZmlsdGVyKHN0cl9kZXRlY3QoV29ya1Rvb2xzU2VsZWN0LCAnUicpKSAlPiUgZmlsdGVyKHN0cl9kZXRlY3QoV29ya1Rvb2xzU2VsZWN0LCAnUHl0aG9uJykpICU+JSBtdXRhdGUocHIgPSAxKQojIGxlZnQgam9pbiAKcHlfciA9IHB5X3IgJT4lIHNlbGVjdChSZXNwb25kZW50LCBwcikKZGF0YTIgPSBsZWZ0X2pvaW4oZGF0YSxweV9yLCBieT0iUmVzcG9uZGVudCIpICU+JSBtdXRhdGUocHIgPSBpZl9lbHNlKGlzLm5hKHByKSwwLCBwcikpCkhtaXNjOjpkZXNjcmliZShhcy5mYWN0b3IoZGF0YTIkcHIpKQpgYGAKCiogMzY2MCBvdXQgb2YgODEzMiAoNDUlKSByZXNwb25kZW50cyBsaXN0ZWQgYm90aCBSIGFuZCBQeXRob24gaW4gd29yayB0b29scyB1c2VkLiAKCmBgYHtyfQp0b29sczIgPC0gZGF0YTIgICU+JSAKICAgIG11dGF0ZSh3b3JrX3Rvb2xzID1zdHJfc3BsaXQoV29ya1Rvb2xzU2VsZWN0LCIsIikgKSAgJT4lIAogICAgdW5uZXN0KHdvcmtfdG9vbHMpCgojIGdldCBwcm9wb3J0aW9uICAKdG9vbHMyICU+JSBmaWx0ZXIocHI9PTEpICU+JSBncm91cF9ieSh3b3JrX3Rvb2xzKSAlPiUgdGFsbHkoc29ydD1UKSAlPiUgZmlsdGVyKHdvcmtfdG9vbHMgIT0iUHl0aG9uIiAmIHdvcmtfdG9vbHMgIT0iUiIpICU+JSBtdXRhdGUocHJvcF9yZXMgPSByb3VuZChuLzM2NjAsMykpCmBgYAoKYGBge3J9CiMgcGxvdCBwcm9wb3J0aW9uIG9mIDEwIG1vc3QgZnJlcXVlbnQgdG9vbHMgdXNlZCBieSByZXNwb25kZW50cyB0aGF0IGxpc3RlZCBib3RoIFIgYW5kIFB5dGhvbiAod29yayB0b29scykKdG9vbHMyICU+JSBmaWx0ZXIocHI9PTEpICU+JSBncm91cF9ieSh3b3JrX3Rvb2xzKSAlPiUgdGFsbHkoc29ydD1UKSAlPiUgbXV0YXRlKHByb3BfcmVzID0gcm91bmQobi8zNjYwLDIpKSAlPiUgZmlsdGVyKHdvcmtfdG9vbHMgIT0iUHl0aG9uIiAmIHdvcmtfdG9vbHMgIT0iUiIpICU+JSBzbGljZSgxOjEwKSAlPiUgZ2dwbG90KGFlcyh4PXJlb3JkZXIod29ya190b29scyxwcm9wX3JlcyksIHk9cHJvcF9yZXMsIGZpbGw9d29ya190b29scykpICsgZ2VvbV9jb2wod2lkdGg9MC41KSArIGdlb21fdGV4dChzdGF0PSJpZGVudGl0eSIsYWVzKGxhYmVsPXByb3BfcmVzKSxoanVzdD0tMC41LHNpemU9Myxjb2xvcj0iYmxhY2siKSArIGNvb3JkX2ZsaXAoKSArIHRoZW1lX21pbmltYWwoKSArIGxhYnMoeT0iUHJvcG9ydGlvbiIseD0iV29yayB0b29scyIpICsgdGhlbWUocGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpLCBwYW5lbC5ncmlkLm1ham9yID0gZWxlbWVudF9ibGFuaygpLCBwYW5lbC5ncmlkLm1pbm9yID0gZWxlbWVudF9ibGFuaygpLCBsZWdlbmQucG9zaXRpb249Im5vbmUiKSArIHNjYWxlX2ZpbGxfc2ltcHNvbnMoKSArIHNjYWxlX3lfY29udGludW91cyhsaW1pdHM9YygwLDEpKSAKYGBgCgoqIFNRTCwgSnVweXRlciBub3RlYm9va3MsIFRlbnNvcmZsb3csIFRhYmxlYXUgYW5kIEFtYXpvbiB3ZWIgc2VydmljZXMgYXJlIHRoZSBtb3N0IGZyZXF1ZW50IG90aGVyIHRvb2xzIGFtb25nc3QgdGhvc2UgcmVzcG9uZGVudHMgdGhhdCB1c2UgYm90aCBSIGFuZCBQeXRob24gKHRvb2xzKS4gCgoK