# import required packages
library(caTools)
library(VIM)
library(DMwR)
library(tidyverse)
library(ROCR)
library(sqldf)
# model packages
library(randomForest)
library(e1071)
# mice package
library(mice)
# get data
path <- "/Users/cy/Desktop/dazuoye/sas/framingham_heart_disease.csv"
raw_data <- read.csv(path)
# 这边可以考虑增加变量收缩压与舒张压之差、描述收缩压、舒张压与高血压等级的变量
# reference: https://zh.wikipedia.org/wiki/%E8%A1%80%E5%A3%93
# reference: http://blog.sina.com.cn/s/blog_6406a7740102v4mz.html

# looking at the structure of data
str(raw_data)
'data.frame':   4238 obs. of  16 variables:
 $ male           : int  1 0 1 0 0 0 0 0 1 1 ...
 $ age            : int  39 46 48 61 46 43 63 45 52 43 ...
 $ education      : int  4 2 1 3 3 2 1 2 1 1 ...
 $ currentSmoker  : int  0 0 1 1 1 0 0 1 0 1 ...
 $ cigsPerDay     : int  0 0 20 30 23 0 0 20 0 30 ...
 $ BPMeds         : int  0 0 0 0 0 0 0 0 0 0 ...
 $ prevalentStroke: int  0 0 0 0 0 0 0 0 0 0 ...
 $ prevalentHyp   : int  0 0 0 1 0 1 0 0 1 1 ...
 $ diabetes       : int  0 0 0 0 0 0 0 0 0 0 ...
 $ totChol        : int  195 250 245 225 285 228 205 313 260 225 ...
 $ sysBP          : num  106 121 128 150 130 ...
 $ diaBP          : num  70 81 80 95 84 110 71 71 89 107 ...
 $ BMI            : num  27 28.7 25.3 28.6 23.1 ...
 $ heartRate      : int  80 95 75 65 85 77 60 79 76 93 ...
 $ glucose        : int  77 76 70 103 85 99 85 78 79 88 ...
 $ TenYearCHD     : int  0 0 0 1 0 0 1 0 0 0 ...
# 考虑增加变量bplevel
raw_data <- sqldf("select *, case when sysBP < 90 and diaBP < 60 then 1 
                   when (sysBP between 90 and 139) and (diaBP between 60 and 89) then 2
                   when sysBP >=140 and diaBP >= 90 then 3 else 4 end as bplevel 
                   from raw_data")
# raw_data <- sqldf("select *, case when sysBP is not null and diaBP is not null 
#                     then sysBP - diaBP else null end as sd from raw_data")
# 对变量类别进行区分
names_to_factor <- c("male", "education", "currentSmoker", "BPMeds",'prevalentStroke','prevalentHyp','diabetes','TenYearCHD', 'bplevel')
raw_data[names_to_factor] <- map(raw_data[names_to_factor], as.factor)
str(raw_data)
'data.frame':   4238 obs. of  17 variables:
 $ male           : Factor w/ 2 levels "0","1": 2 1 2 1 1 1 1 1 2 2 ...
 $ age            : int  39 46 48 61 46 43 63 45 52 43 ...
 $ education      : Factor w/ 4 levels "1","2","3","4": 4 2 1 3 3 2 1 2 1 1 ...
 $ currentSmoker  : Factor w/ 2 levels "0","1": 1 1 2 2 2 1 1 2 1 2 ...
 $ cigsPerDay     : int  0 0 20 30 23 0 0 20 0 30 ...
 $ BPMeds         : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 1 ...
 $ prevalentStroke: Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 1 ...
 $ prevalentHyp   : Factor w/ 2 levels "0","1": 1 1 1 2 1 2 1 1 2 2 ...
 $ diabetes       : Factor w/ 2 levels "0","1": 1 1 1 1 1 1 1 1 1 1 ...
 $ totChol        : int  195 250 245 225 285 228 205 313 260 225 ...
 $ sysBP          : num  106 121 128 150 130 ...
 $ diaBP          : num  70 81 80 95 84 110 71 71 89 107 ...
 $ BMI            : num  27 28.7 25.3 28.6 23.1 ...
 $ heartRate      : int  80 95 75 65 85 77 60 79 76 93 ...
 $ glucose        : int  77 76 70 103 85 99 85 78 79 88 ...
 $ TenYearCHD     : Factor w/ 2 levels "0","1": 1 1 1 2 1 1 2 1 1 1 ...
 $ bplevel        : Factor w/ 4 levels "1","2","3","4": 2 2 2 3 2 3 2 2 4 3 ...
变量名 描述
male 0 = Female; 1 = Male
age Age at exam time
education 1 = Some High School; 2 = High School or GED; 3 = Some College or Vocational School; 4 = college
currentSmoker 0 = nonsmoker; 1 = smoker
cigsPerDay number of cigarettes smoked per day (estimated average)
BPMeds 0 = Not on Blood Pressure medications; 1 = Is on Blood Pressure medications
prevalentStroke whether or not the patient had previously had a stroke
prevalentHyp whether or not the patient was hypertensive(高血压)
diabetes whether or not the patient had diabetes 0 = No; 1 = Yes
totChol total cholesterol level 总胆固醇水平 mg/dL
sysBP systolic blood pressure 收缩压 mmHg
diaBP diastolic blood pressure 舒张压 mmHg
BMI Body Mass Index calculated as: Weight (kg) / Height(meter-squared)
heartRate Beats/Min (Ventricular 心室)
glucose glucose level 糖尿病水平
bplevel 1 = 低血糖; 2 = 正常血压; 3 = 高血压; 4 = 其它 血压等级
TenYearCHD 10 year risk of coronary heart disease (Target)

数据预处理

查看&处理缺失值

# 这里我们使用mice包进行缺失值处理
aggr(raw_data, prop=T, cex.lab = 0.9, cex.axis = 0.7)

matrixplot(raw_data, cex.axis = 0.7)

Click in a column to sort by the corresponding variable.
To regain use of the VIM GUI and the R console, click outside the plot region.

由上图可以看出,除了glucose变量,其它变量的缺失比例都低于5%,而glucose变量缺失率超过了10%。对此的处理策略是保留glucose变量的缺失值,直接删除其它变量的缺失值。 现在处理glucose的缺失值,

# 处理glucose列
filtered_data <- subset(raw_data, !is.na(education) & !is.na(cigsPerDay) & !is.na(BPMeds) & !is.na(totChol) & !is.na(BMI) & !is.na(heartRate))
# 查看glucose与其它变量的线性相关性确定mice的填充策略
glucoseLog = glm(glucose ~ ., data = filtered_data)
summary(glucoseLog)

Call:
glm(formula = glucose ~ ., data = filtered_data)

Deviance Residuals: 
     Min        1Q    Median        3Q       Max  
-125.912    -8.542    -1.517     6.864   222.839  

Coefficients:
                   Estimate Std. Error t value Pr(>|t|)    
(Intercept)       79.249824  11.558924   6.856 8.28e-12 ***
male1              0.872646   0.690526   1.264 0.206404    
age                0.040159   0.043142   0.931 0.351990    
education2         0.435531   0.766161   0.568 0.569758    
education3         1.558096   0.915870   1.701 0.088987 .  
education4         0.061647   1.049741   0.059 0.953174    
currentSmoker1     0.091473   0.994228   0.092 0.926700    
cigsPerDay        -0.072377   0.042893  -1.687 0.091617 .  
BPMeds1            0.740322   1.908862   0.388 0.698161    
prevalentStroke1   1.585779   4.134256   0.384 0.701319    
prevalentHyp1     -0.974976   1.106982  -0.881 0.378510    
diabetes1         87.728554   1.936045  45.313  < 2e-16 ***
totChol           -0.001776   0.007414  -0.240 0.810712    
sysBP              0.124314   0.028570   4.351 1.39e-05 ***
diaBP             -0.119063   0.048042  -2.478 0.013246 *  
BMI                0.089097   0.084933   1.049 0.294235    
heartRate          0.114831   0.026970   4.258 2.12e-05 ***
TenYearCHD1        3.378054   0.906749   3.725 0.000198 ***
bplevel2         -19.042551  10.887942  -1.749 0.080383 .  
bplevel3         -20.095204  11.066460  -1.816 0.069473 .  
bplevel4         -19.624199  10.965356  -1.790 0.073593 .  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for gaussian family taken to be 349.1509)

    Null deviance: 2089542  on 3655  degrees of freedom
Residual deviance: 1269164  on 3635  degrees of freedom
  (331 observations deleted due to missingness)
AIC: 31806

Number of Fisher Scoring iterations: 2
# mice包填充,排除不重要的变量。至于为什么不选diaBP,主要是后面的相关性分析中,这两个变量会造成多重共线性
mice_mod <- mice(filtered_data[, names(filtered_data) %in% c('diabetes', 'sysBP', 'heartRate', 'TenYearCHD', 'glucose')], m=5, method = "pmm", maxit = 50, seed=2333, print = FALSE)
#查看填充结果
summary(mice_mod)
Class: mids
Number of multiple imputations:  5 
Imputation methods:
  diabetes      sysBP  heartRate    glucose TenYearCHD 
        ""         ""         ""      "pmm"         "" 
PredictorMatrix:
           diabetes sysBP heartRate glucose TenYearCHD
diabetes          0     1         1       1          1
sysBP             1     0         1       1          1
heartRate         1     1         0       1          1
glucose           1     1         1       0          1
TenYearCHD        1     1         1       1          0
# 查看原始数据和插补后的数据分布情况
densityplot(mice_mod)

stripplot(mice_mod, pch=12)

# 填充数据
mice_output <- complete(mice_mod, 4)
filtered_data$glucose <- mice_output$glucose
sum(is.na(filtered_data))
[1] 0
completed_data <- filtered_data

删除重复行

# 查看有无重复行并删除重复行
sum(duplicated(completed_data))
[1] 0
completed_data <- completed_data[!duplicated(completed_data), ]

查看离群点

# look into outliers
ggplot(completed_data)+geom_boxplot(aes(factor(1),age))

ggplot(completed_data)+geom_boxplot(aes(factor(1),cigsPerDay))

ggplot(completed_data)+geom_boxplot(aes(factor(1),totChol))

ggplot(completed_data)+geom_boxplot(aes(factor(1),sysBP))

ggplot(completed_data)+geom_boxplot(aes(factor(1),diaBP))

ggplot(completed_data)+geom_boxplot(aes(factor(1),BMI))

ggplot(completed_data)+geom_boxplot(aes(factor(1),heartRate))

ggplot(completed_data)+geom_boxplot(aes(factor(1),glucose))

# 查看cigsPerDay
cigs_sub <- completed_data[which(completed_data$cigsPerDay>=60),]
# 查看totChol,删除异常点
tot_sub <- completed_data[which(completed_data$totChol>=500),]
# 查看sysBP, 删除异常点
sysbp_sub <- completed_data[which(completed_data$sysBP>=250),]
# 查看BMI
bmi_sub <- completed_data[which(completed_data$BMI>=50),]

totChol: 查看网络资料,总胆固醇水平大于240mg/dl已属于非常高,故删去水平值为600mg/dl的记录。 sysBP: 去掉收缩压为295mg/dl的记录

# 删除各变量离群点
completed_data <- completed_data[which(completed_data$totChol!=600),]
completed_data <- completed_data[which(completed_data$sysBP!=295),]
# 分类型变量列联分析
ggplot(completed_data)+geom_boxplot(aes(factor(1),age,fill=completed_data$TenYearCHD))

ggplot(completed_data)+geom_boxplot(aes(factor(1),completed_data$totChol,fill=completed_data$TenYearCHD))

completed_data %>% filter(glucose < 200) %>% 
ggplot(aes(x=factor(1),y=glucose,fill=TenYearCHD))+geom_boxplot(aes(x=factor(1),y=glucose,fill=TenYearCHD))

ggplot(completed_data)+geom_boxplot(aes(factor(1),completed_data$heartRate,fill=completed_data$TenYearCHD))

ggplot(completed_data)+geom_boxplot(aes(factor(1),completed_data$BMI,fill=completed_data$TenYearCHD))

ggplot(completed_data)+geom_boxplot(aes(factor(1),completed_data$diaBP,fill=completed_data$TenYearCHD))

ggplot(completed_data)+geom_boxplot(aes(factor(1),completed_data$sysBP,fill=completed_data$TenYearCHD))

由图像知,glucose和hearRate变量有不显著的风险

table1=table(completed_data$male,completed_data$TenYearCHD)
chisq.test(table1)

    Pearson's Chi-squared test with Yates' continuity correction

data:  table1
X-squared = 33.573, df = 1, p-value = 6.863e-09
table1
   
       0    1
  0 1987  271
  1 1405  322
table2=table(completed_data$education,completed_data$TenYearCHD)
chisq.test(table2)

    Pearson's Chi-squared test

data:  table2
X-squared = 31.018, df = 3, p-value = 8.425e-07
table3=table(completed_data$currentSmoker,completed_data$TenYearCHD)
chisq.test(table3)

    Pearson's Chi-squared test with Yates' continuity correction

data:  table3
X-squared = 2.0634, df = 1, p-value = 0.1509
table4=table(completed_data$BPMeds,completed_data$TenYearCHD)
chisq.test(table4)

    Pearson's Chi-squared test with Yates' continuity correction

data:  table4
X-squared = 30.92, df = 1, p-value = 2.689e-08
table5=table(completed_data$prevalentStroke,completed_data$TenYearCHD)
chisq.test(table5)
Chi-squared近似算法有可能不准

    Pearson's Chi-squared test with Yates' continuity correction

data:  table5
X-squared = 6.4451, df = 1, p-value = 0.01113
table6=table(completed_data$prevalentHyp,completed_data$TenYearCHD)
chisq.test(table6)

    Pearson's Chi-squared test with Yates' continuity correction

data:  table6
X-squared = 120.91, df = 1, p-value < 2.2e-16
table7=table(completed_data$diabetes,completed_data$TenYearCHD)
chisq.test(table7)

    Pearson's Chi-squared test with Yates' continuity correction

data:  table7
X-squared = 28.074, df = 1, p-value = 1.168e-07
table8=table(completed_data$bplevel,completed_data$TenYearCHD)
chisq.test(table8)
Chi-squared近似算法有可能不准

    Pearson's Chi-squared test

data:  table8
X-squared = 127.61, df = 3, p-value < 2.2e-16
require(GGally)
载入需要的程辑包:GGally

载入程辑包:‘GGally’

The following object is masked from ‘package:dplyr’:

    nasa
full_data <- completed_data
ggpairs(full_data[,c(2,5,10:15)])

diaBP和sysBP有多重共线性的危险。 (解释下它的实际意义)

currentSmoker变量可能不显著,下面进入模型 ### model

# 划分数据集
full_data <- completed_data
set.seed(500)
split = sample.split(full_data$TenYearCHD, SplitRatio = 0.70)

train = subset(full_data, split==TRUE)
test = subset(full_data, split==FALSE)

逻辑回归

# Logistic Regression Model - Using all variables
fulldataLog = glm(TenYearCHD ~ ., data = train, family=binomial)
summary(fulldataLog)

Call:
glm(formula = TenYearCHD ~ ., family = binomial, data = train)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-1.9425  -0.5856  -0.4301  -0.2918   2.8495  

Coefficients:
                   Estimate Std. Error z value Pr(>|z|)    
(Intercept)      -4.9507254  1.6527924  -2.995  0.00274 ** 
male1             0.5175404  0.1256533   4.119 3.81e-05 ***
age               0.0566658  0.0078252   7.241 4.44e-13 ***
education2       -0.3009831  0.1439599  -2.091  0.03655 *  
education3       -0.0789876  0.1673850  -0.472  0.63700    
education4       -0.1223822  0.1886704  -0.649  0.51656    
currentSmoker1    0.0737162  0.1795557   0.411  0.68140    
cigsPerDay        0.0148933  0.0073097   2.037  0.04160 *  
BPMeds1           0.4818730  0.2658025   1.813  0.06985 .  
prevalentStroke1 -0.1777638  0.7073075  -0.251  0.80156    
prevalentHyp1     0.1362271  0.1869772   0.729  0.46626    
diabetes1        -0.0482980  0.3944725  -0.122  0.90255    
totChol           0.0020074  0.0013064   1.537  0.12440    
sysBP             0.0160675  0.0047292   3.398  0.00068 ***
diaBP            -0.0003059  0.0084883  -0.036  0.97125    
BMI               0.0087659  0.0150549   0.582  0.56039    
heartRate        -0.0020778  0.0047899  -0.434  0.66445    
glucose           0.0072516  0.0027270   2.659  0.00783 ** 
bplevel2         -3.4026272  1.4687897  -2.317  0.02052 *  
bplevel3         -3.4861383  1.5108769  -2.307  0.02103 *  
bplevel4         -3.3888626  1.4863921  -2.280  0.02261 *  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 2346.2  on 2788  degrees of freedom
Residual deviance: 2087.0  on 2768  degrees of freedom
AIC: 2129

Number of Fisher Scoring iterations: 5
fulldataLog = glm(TenYearCHD ~ male + age + cigsPerDay + sysBP + glucose + bplevel, data = train, family=binomial)
summary(fulldataLog)

Call:
glm(formula = TenYearCHD ~ male + age + cigsPerDay + sysBP + 
    glucose + bplevel, family = binomial, data = train)

Deviance Residuals: 
    Min       1Q   Median       3Q      Max  
-2.0141  -0.5899  -0.4362  -0.3017   2.7885  

Coefficients:
             Estimate Std. Error z value Pr(>|z|)    
(Intercept) -5.054670   1.477346  -3.421 0.000623 ***
male1        0.512933   0.120903   4.242 2.21e-05 ***
age          0.061775   0.007360   8.394  < 2e-16 ***
cigsPerDay   0.016747   0.004855   3.449 0.000562 ***
sysBP        0.018185   0.003993   4.554 5.27e-06 ***
glucose      0.006832   0.002086   3.275 0.001056 ** 
bplevel2    -3.333911   1.402487  -2.377 0.017447 *  
bplevel3    -3.334127   1.432283  -2.328 0.019921 *  
bplevel4    -3.252923   1.417910  -2.294 0.021781 *  
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 2346.2  on 2788  degrees of freedom
Residual deviance: 2098.7  on 2780  degrees of freedom
AIC: 2116.7

Number of Fisher Scoring iterations: 5
predictTest = predict(fulldataLog, type="response", newdata=test)
glm_table <- table(test$TenYearCHD, predictTest > 0.5)
ACCU = sum(diag(glm_table)) / sum(glm_table)
ACCU
[1] 0.8528428

随机森林

set.seed(22)
rf_model <- randomForest(TenYearCHD ~ ., data = train)
# get importance
importance <- importance(rf_model)
importance
                MeanDecreaseGini
male                  13.6772726
age                   82.5789715
education             29.4778562
currentSmoker          6.7740002
cigsPerDay            35.8671039
BPMeds                 5.4417504
prevalentStroke        0.6992751
prevalentHyp          10.1434309
diabetes               3.6040110
totChol               86.0396068
sysBP                 94.9900044
diaBP                 77.9668212
BMI                   90.6283445
heartRate             66.3866265
glucose               82.3949862
bplevel               13.6431956
# 选择重要的因素
set.seed(33)
rf_model <- randomForest(TenYearCHD ~ age + totChol + sysBP + diaBP + BMI + heartRate + glucose, data = train, proximity = TRUE)
# show model error
plot(rf_model)
legend('topright', colnames(rf_model$err.rate), col=1:3, fill=1:3)

# get importance
importance <- importance(rf_model)
varImportance <- data.frame(Variables = row.names(importance), 
                            Importance = round(importance[ ,'MeanDecreaseGini'], 2))
rankImportance <- varImportance %>% mutate(Rank = paste0('#', dense_rank(desc(Importance))))
ggplot(rankImportance, aes(x = reorder(Variables, Importance),
                           y = importance, fill = Importance)) + 
  geom_bar(stat = 'identity') + 
  geom_text(aes(x = Variables, y = 0.5, label = Rank),
            hjust=0, vjust=0.55, size=4, colour='red') + 
  labs(x = 'Variables') + 
  coord_flip() + 
  theme_bw()

# 这里有患病风险的error不降反升,需要探究其中原因
# 绘制分类图像
# MDSplot(rf_model, fac, k=2, palette=NULL, pch=20)
pred<-predict(rf_model,newdata=test)  
pred_out_1<-predict(object=rf_model,newdata=test,type="prob")  #输出概率
table <- table(pred,test$TenYearCHD)  
sum(diag(table))/sum(table)  #预测准确率
[1] 0.84699
plot(margin(rf_model,test$TenYearCHD))
minimal value for n is 3, returning requested palette with 3 different levels

SVM支持向量机

# 参考资料 http://studio.galaxystatistics.com/report/svm/article3/
# 先进行模型调优
tuned <- tune.svm(TenYearCHD ~ .,data = train,gamma = 10^(-6:-1),cost = 10^(1:2))
summary(tuned)

Parameter tuning of ‘svm’:

- sampling method: 10-fold cross validation 

- best parameters:

- best performance: 0.1459503 

- Detailed performance results:
# 使用turning函数得到最佳参数设置支持向量机
model.tuned <- svm(TenYearCHD ~ ., data=train, gamma=tuned$best.parameters$gamma, cost=tuned$best.parameters$cost)
summary(model.tuned)

Call:
svm(formula = TenYearCHD ~ ., data = train, gamma = tuned$best.parameters$gamma, 
    cost = tuned$best.parameters$cost)


Parameters:
   SVM-Type:  C-classification 
 SVM-Kernel:  radial 
       cost:  100 

Number of Support Vectors:  989

 ( 578 411 )


Number of Classes:  2 

Levels: 
 0 1
# 调用predict函数基于刚配置好的SVM模型进行类标号的预测:
svm.tuned.pred <- predict(model.tuned, test[,!names(test) %in% c("TenYearCHD")])
svm.tuned.table <- table(svm.tuned.pred, test$TenYearCHD)
svm.tuned.table
              
svm.tuned.pred    0    1
             0 1011  172
             1    7    6
accuracy.test.svm <- sum(diag(svm.tuned.table))/sum(svm.tuned.table)
list('测试集预测混淆矩阵'=svm.tuned.table, '测试集预测正确率'=accuracy.test.svm)
$测试集预测混淆矩阵
              
svm.tuned.pred    0    1
             0 1011  172
             1    7    6

$测试集预测正确率
[1] 0.8503344

模型诊断

根据上面三个模型的结果,可以看出预测结果的类别数量分布非常不均衡

sum(full_data$TenYearCHD == 1)
[1] 593
sum(full_data$TenYearCHD == 0)
[1] 3392
# 针对这一现象,需要采取措施平衡数据集
LS0tCnRpdGxlOiAiaGVhcnQgZGlzZWFzZSBwcmVkaWN0aW9uIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgpgYGB7cn0KIyBpbXBvcnQgcmVxdWlyZWQgcGFja2FnZXMKbGlicmFyeShjYVRvb2xzKQpsaWJyYXJ5KFZJTSkKbGlicmFyeShETXdSKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeShST0NSKQpsaWJyYXJ5KHNxbGRmKQojIG1vZGVsIHBhY2thZ2VzCmxpYnJhcnkocmFuZG9tRm9yZXN0KQpsaWJyYXJ5KGUxMDcxKQojIG1pY2UgcGFja2FnZQpsaWJyYXJ5KG1pY2UpCmBgYAoKCmBgYHtyfQojIGdldCBkYXRhCnBhdGggPC0gIi9Vc2Vycy9jeS9EZXNrdG9wL2RhenVveWUvc2FzL2ZyYW1pbmdoYW1faGVhcnRfZGlzZWFzZS5jc3YiCnJhd19kYXRhIDwtIHJlYWQuY3N2KHBhdGgpCmBgYAoKYGBge3J9CiMg6L+Z6L655Y+v5Lul6ICD6JmR5aKe5Yqg5Y+Y6YeP5pS257yp5Y6L5LiO6IiS5byg5Y6L5LmL5beu44CB5o+P6L+w5pS257yp5Y6L44CB6IiS5byg5Y6L5LiO6auY6KGA5Y6L562J57qn55qE5Y+Y6YePCiMgcmVmZXJlbmNlOiBodHRwczovL3poLndpa2lwZWRpYS5vcmcvd2lraS8lRTglQTElODAlRTUlQTMlOTMKIyByZWZlcmVuY2U6IGh0dHA6Ly9ibG9nLnNpbmEuY29tLmNuL3MvYmxvZ182NDA2YTc3NDAxMDJ2NG16Lmh0bWwKCiMgbG9va2luZyBhdCB0aGUgc3RydWN0dXJlIG9mIGRhdGEKc3RyKHJhd19kYXRhKQojIOiAg+iZkeWinuWKoOWPmOmHj2JwbGV2ZWwKcmF3X2RhdGEgPC0gc3FsZGYoInNlbGVjdCAqLCBjYXNlIHdoZW4gc3lzQlAgPCA5MCBhbmQgZGlhQlAgPCA2MCB0aGVuIDEgCiAgICAgICAgICAgICAgICAgICB3aGVuIChzeXNCUCBiZXR3ZWVuIDkwIGFuZCAxMzkpIGFuZCAoZGlhQlAgYmV0d2VlbiA2MCBhbmQgODkpIHRoZW4gMgogICAgICAgICAgICAgICAgICAgd2hlbiBzeXNCUCA+PTE0MCBhbmQgZGlhQlAgPj0gOTAgdGhlbiAzIGVsc2UgNCBlbmQgYXMgYnBsZXZlbCAKICAgICAgICAgICAgICAgICAgIGZyb20gcmF3X2RhdGEiKQojIHJhd19kYXRhIDwtIHNxbGRmKCJzZWxlY3QgKiwgY2FzZSB3aGVuIHN5c0JQIGlzIG5vdCBudWxsIGFuZCBkaWFCUCBpcyBub3QgbnVsbCAKIyAgICAgICAgICAgICAgICAgICAgIHRoZW4gc3lzQlAgLSBkaWFCUCBlbHNlIG51bGwgZW5kIGFzIHNkIGZyb20gcmF3X2RhdGEiKQojIOWvueWPmOmHj+exu+WIq+i/m+ihjOWMuuWIhgpuYW1lc190b19mYWN0b3IgPC0gYygibWFsZSIsICJlZHVjYXRpb24iLCAiY3VycmVudFNtb2tlciIsICJCUE1lZHMiLCdwcmV2YWxlbnRTdHJva2UnLCdwcmV2YWxlbnRIeXAnLCdkaWFiZXRlcycsJ1RlblllYXJDSEQnLCAnYnBsZXZlbCcpCnJhd19kYXRhW25hbWVzX3RvX2ZhY3Rvcl0gPC0gbWFwKHJhd19kYXRhW25hbWVzX3RvX2ZhY3Rvcl0sIGFzLmZhY3RvcikKc3RyKHJhd19kYXRhKQpgYGAK5Y+Y6YeP5ZCNfOaPj+i/sAotLXw6LS06fC0tOgptYWxlfDAgPSBGZW1hbGU7IDEgPSBNYWxlCmFnZXxBZ2UgYXQgZXhhbSB0aW1lCmVkdWNhdGlvbnwxID0gU29tZSBIaWdoIFNjaG9vbDsgMiA9IEhpZ2ggU2Nob29sIG9yIEdFRDsgMyA9IFNvbWUgQ29sbGVnZSBvciBWb2NhdGlvbmFsIFNjaG9vbDsgNCA9IGNvbGxlZ2UKY3VycmVudFNtb2tlcnwwID0gbm9uc21va2VyOyAxID0gc21va2VyCmNpZ3NQZXJEYXl8bnVtYmVyIG9mIGNpZ2FyZXR0ZXMgc21va2VkIHBlciBkYXkgKGVzdGltYXRlZCBhdmVyYWdlKQpCUE1lZHN8MCA9IE5vdCBvbiBCbG9vZCBQcmVzc3VyZSBtZWRpY2F0aW9uczsgMSA9IElzIG9uIEJsb29kIFByZXNzdXJlIG1lZGljYXRpb25zCnByZXZhbGVudFN0cm9rZXx3aGV0aGVyIG9yIG5vdCB0aGUgcGF0aWVudCBoYWQgcHJldmlvdXNseSBoYWQgYSBzdHJva2UKcHJldmFsZW50SHlwfHdoZXRoZXIgb3Igbm90IHRoZSBwYXRpZW50IHdhcyBoeXBlcnRlbnNpdmUo6auY6KGA5Y6LKQpkaWFiZXRlc3x3aGV0aGVyIG9yIG5vdCB0aGUgcGF0aWVudCBoYWQgZGlhYmV0ZXMgMCA9IE5vOyAxID0gWWVzCnRvdENob2x8dG90YWwgY2hvbGVzdGVyb2wgbGV2ZWwg5oC76IOG5Zu66YaH5rC05bmzIG1nL2RMCnN5c0JQfHN5c3RvbGljIGJsb29kIHByZXNzdXJlIOaUtue8qeWOiyBtbUhnCmRpYUJQfGRpYXN0b2xpYyBibG9vZCBwcmVzc3VyZSDoiJLlvKDljosgbW1IZwpCTUl8Qm9keSBNYXNzIEluZGV4IGNhbGN1bGF0ZWQgYXM6IFdlaWdodCAoa2cpIC8gSGVpZ2h0KG1ldGVyLXNxdWFyZWQpCmhlYXJ0UmF0ZXxCZWF0cy9NaW4gKFZlbnRyaWN1bGFyIOW/g+WupCkKZ2x1Y29zZXxnbHVjb3NlIGxldmVsIOezluWwv+eXheawtOW5swpicGxldmVsfDEgPSDkvY7ooYDns5Y7IDIgPSDmraPluLjooYDljos7IDMgPSDpq5jooYDljos7IDQgPSDlhbblroMg6KGA5Y6L562J57qnClRlblllYXJDSER8MTAgeWVhciByaXNrIG9mIGNvcm9uYXJ5IGhlYXJ0IGRpc2Vhc2UgKFRhcmdldCkKCgoKIyMjIOaVsOaNrumihOWkhOeQhgojIyMjIOafpeeciyblpITnkIbnvLrlpLHlgLwKYGBge3J9CiMg6L+Z6YeM5oiR5Lus5L2/55SobWljZeWMhei/m+ihjOe8uuWkseWAvOWkhOeQhgphZ2dyKHJhd19kYXRhLCBwcm9wPVQsIGNleC5sYWIgPSAwLjksIGNleC5heGlzID0gMC43KQptYXRyaXhwbG90KHJhd19kYXRhLCBjZXguYXhpcyA9IDAuNykKYGBgCueUseS4iuWbvuWPr+S7peeci+WHuu+8jOmZpOS6hmdsdWNvc2Xlj5jph4/vvIzlhbblroPlj5jph4/nmoTnvLrlpLHmr5Tkvovpg73kvY7kuo41Je+8jOiAjGdsdWNvc2Xlj5jph4/nvLrlpLHnjofotoXov4fkuoYxMCXjgILlr7nmraTnmoTlpITnkIbnrZbnlaXmmK/kv53nlZlnbHVjb3Nl5Y+Y6YeP55qE57y65aSx5YC877yM55u05o6l5Yig6Zmk5YW25a6D5Y+Y6YeP55qE57y65aSx5YC844CCCueOsOWcqOWkhOeQhmdsdWNvc2XnmoTnvLrlpLHlgLzvvIwKCmBgYHtyfQojIOWkhOeQhmdsdWNvc2XliJcKZmlsdGVyZWRfZGF0YSA8LSBzdWJzZXQocmF3X2RhdGEsICFpcy5uYShlZHVjYXRpb24pICYgIWlzLm5hKGNpZ3NQZXJEYXkpICYgIWlzLm5hKEJQTWVkcykgJiAhaXMubmEodG90Q2hvbCkgJiAhaXMubmEoQk1JKSAmICFpcy5uYShoZWFydFJhdGUpKQojIOafpeeci2dsdWNvc2XkuI7lhbblroPlj5jph4/nmoTnur/mgKfnm7jlhbPmgKfnoa7lrpptaWNl55qE5aGr5YWF562W55WlCmdsdWNvc2VMb2cgPSBnbG0oZ2x1Y29zZSB+IC4sIGRhdGEgPSBmaWx0ZXJlZF9kYXRhKQpzdW1tYXJ5KGdsdWNvc2VMb2cpCmBgYAoKYGBge3J9CiMgbWljZeWMheWhq+WFhe+8jOaOkumZpOS4jemHjeimgeeahOWPmOmHj+OAguiHs+S6juS4uuS7gOS5iOS4jemAiWRpYUJQ77yM5Li76KaB5piv5ZCO6Z2i55qE55u45YWz5oCn5YiG5p6Q5Lit77yM6L+Z5Lik5Liq5Y+Y6YeP5Lya6YCg5oiQ5aSa6YeN5YWx57q/5oCnCm1pY2VfbW9kIDwtIG1pY2UoZmlsdGVyZWRfZGF0YVssIG5hbWVzKGZpbHRlcmVkX2RhdGEpICVpbiUgYygnZGlhYmV0ZXMnLCAnc3lzQlAnLCAnaGVhcnRSYXRlJywgJ1RlblllYXJDSEQnLCAnZ2x1Y29zZScpXSwgbT01LCBtZXRob2QgPSAicG1tIiwgbWF4aXQgPSA1MCwgc2VlZD0yMzMzLCBwcmludCA9IEZBTFNFKQoj5p+l55yL5aGr5YWF57uT5p6cCnN1bW1hcnkobWljZV9tb2QpCiMg5p+l55yL5Y6f5aeL5pWw5o2u5ZKM5o+S6KGl5ZCO55qE5pWw5o2u5YiG5biD5oOF5Ya1CmRlbnNpdHlwbG90KG1pY2VfbW9kKQpzdHJpcHBsb3QobWljZV9tb2QsIHBjaD0xMikKIyDloavlhYXmlbDmja4KbWljZV9vdXRwdXQgPC0gY29tcGxldGUobWljZV9tb2QsIDQpICMg6KeC5a+f5Zu+5YOP6YCJ5oup5q+P5qyh6L+t5Luj55qE56ys5Zub57uE5YC8CmZpbHRlcmVkX2RhdGEkZ2x1Y29zZSA8LSBtaWNlX291dHB1dCRnbHVjb3NlCnN1bShpcy5uYShmaWx0ZXJlZF9kYXRhKSkKY29tcGxldGVkX2RhdGEgPC0gZmlsdGVyZWRfZGF0YQpgYGAKCiMjIyMg5Yig6Zmk6YeN5aSN6KGMCmBgYHtyfQojIOafpeeci+acieaXoOmHjeWkjeihjOW5tuWIoOmZpOmHjeWkjeihjApzdW0oZHVwbGljYXRlZChjb21wbGV0ZWRfZGF0YSkpCmNvbXBsZXRlZF9kYXRhIDwtIGNvbXBsZXRlZF9kYXRhWyFkdXBsaWNhdGVkKGNvbXBsZXRlZF9kYXRhKSwgXQpgYGAKCiMjIyMg5p+l55yL56a7576k54K5CmBgYHtyfQojIGxvb2sgaW50byBvdXRsaWVycwpnZ3Bsb3QoY29tcGxldGVkX2RhdGEpK2dlb21fYm94cGxvdChhZXMoZmFjdG9yKDEpLGFnZSkpCmdncGxvdChjb21wbGV0ZWRfZGF0YSkrZ2VvbV9ib3hwbG90KGFlcyhmYWN0b3IoMSksY2lnc1BlckRheSkpCmdncGxvdChjb21wbGV0ZWRfZGF0YSkrZ2VvbV9ib3hwbG90KGFlcyhmYWN0b3IoMSksdG90Q2hvbCkpCmdncGxvdChjb21wbGV0ZWRfZGF0YSkrZ2VvbV9ib3hwbG90KGFlcyhmYWN0b3IoMSksc3lzQlApKQpnZ3Bsb3QoY29tcGxldGVkX2RhdGEpK2dlb21fYm94cGxvdChhZXMoZmFjdG9yKDEpLGRpYUJQKSkKZ2dwbG90KGNvbXBsZXRlZF9kYXRhKStnZW9tX2JveHBsb3QoYWVzKGZhY3RvcigxKSxCTUkpKQpnZ3Bsb3QoY29tcGxldGVkX2RhdGEpK2dlb21fYm94cGxvdChhZXMoZmFjdG9yKDEpLGhlYXJ0UmF0ZSkpCmdncGxvdChjb21wbGV0ZWRfZGF0YSkrZ2VvbV9ib3hwbG90KGFlcyhmYWN0b3IoMSksZ2x1Y29zZSkpCmBgYApgYGB7cn0KIyDmn6XnnItjaWdzUGVyRGF5CmNpZ3Nfc3ViIDwtIGNvbXBsZXRlZF9kYXRhW3doaWNoKGNvbXBsZXRlZF9kYXRhJGNpZ3NQZXJEYXk+PTYwKSxdCiMg5p+l55yLdG90Q2hvbO+8jOWIoOmZpOW8guW4uOeCuQp0b3Rfc3ViIDwtIGNvbXBsZXRlZF9kYXRhW3doaWNoKGNvbXBsZXRlZF9kYXRhJHRvdENob2w+PTUwMCksXQojIOafpeeci3N5c0JQLCDliKDpmaTlvILluLjngrkKc3lzYnBfc3ViIDwtIGNvbXBsZXRlZF9kYXRhW3doaWNoKGNvbXBsZXRlZF9kYXRhJHN5c0JQPj0yNTApLF0KIyDmn6XnnItCTUkKYm1pX3N1YiA8LSBjb21wbGV0ZWRfZGF0YVt3aGljaChjb21wbGV0ZWRfZGF0YSRCTUk+PTUwKSxdCmBgYAp0b3RDaG9sOiDmn6XnnIvnvZHnu5zotYTmlpnvvIzmgLvog4blm7rphofmsLTlubPlpKfkuo4yNDBtZy9kbOW3suWxnuS6jumdnuW4uOmrmO+8jOaVheWIoOWOu+awtOW5s+WAvOS4ujYwMG1nL2Rs55qE6K6w5b2V44CCCnN5c0JQOiDljrvmjonmlLbnvKnljovkuLoyOTVtZy9kbOeahOiusOW9lQoKYGBge3J9CiMg5Yig6Zmk5ZCE5Y+Y6YeP56a7576k54K5CmNvbXBsZXRlZF9kYXRhIDwtIGNvbXBsZXRlZF9kYXRhW3doaWNoKGNvbXBsZXRlZF9kYXRhJHRvdENob2whPTYwMCksXQpjb21wbGV0ZWRfZGF0YSA8LSBjb21wbGV0ZWRfZGF0YVt3aGljaChjb21wbGV0ZWRfZGF0YSRzeXNCUCE9Mjk1KSxdCmBgYAoKYGBge3J9CiMg5YiG57G75Z6L5Y+Y6YeP5YiX6IGU5YiG5p6QCmdncGxvdChjb21wbGV0ZWRfZGF0YSkrZ2VvbV9ib3hwbG90KGFlcyhmYWN0b3IoMSksYWdlLGZpbGw9Y29tcGxldGVkX2RhdGEkVGVuWWVhckNIRCkpCmdncGxvdChjb21wbGV0ZWRfZGF0YSkrZ2VvbV9ib3hwbG90KGFlcyhmYWN0b3IoMSksY29tcGxldGVkX2RhdGEkdG90Q2hvbCxmaWxsPWNvbXBsZXRlZF9kYXRhJFRlblllYXJDSEQpKQpjb21wbGV0ZWRfZGF0YSAlPiUgZmlsdGVyKGdsdWNvc2UgPCAyMDApICU+JSAKZ2dwbG90KGFlcyh4PWZhY3RvcigxKSx5PWdsdWNvc2UsZmlsbD1UZW5ZZWFyQ0hEKSkrZ2VvbV9ib3hwbG90KGFlcyh4PWZhY3RvcigxKSx5PWdsdWNvc2UsZmlsbD1UZW5ZZWFyQ0hEKSkKZ2dwbG90KGNvbXBsZXRlZF9kYXRhKStnZW9tX2JveHBsb3QoYWVzKGZhY3RvcigxKSxjb21wbGV0ZWRfZGF0YSRoZWFydFJhdGUsZmlsbD1jb21wbGV0ZWRfZGF0YSRUZW5ZZWFyQ0hEKSkKZ2dwbG90KGNvbXBsZXRlZF9kYXRhKStnZW9tX2JveHBsb3QoYWVzKGZhY3RvcigxKSxjb21wbGV0ZWRfZGF0YSRCTUksZmlsbD1jb21wbGV0ZWRfZGF0YSRUZW5ZZWFyQ0hEKSkKZ2dwbG90KGNvbXBsZXRlZF9kYXRhKStnZW9tX2JveHBsb3QoYWVzKGZhY3RvcigxKSxjb21wbGV0ZWRfZGF0YSRkaWFCUCxmaWxsPWNvbXBsZXRlZF9kYXRhJFRlblllYXJDSEQpKQpnZ3Bsb3QoY29tcGxldGVkX2RhdGEpK2dlb21fYm94cGxvdChhZXMoZmFjdG9yKDEpLGNvbXBsZXRlZF9kYXRhJHN5c0JQLGZpbGw9Y29tcGxldGVkX2RhdGEkVGVuWWVhckNIRCkpCmBgYArnlLHlm77lg4/nn6XvvIxnbHVjb3Nl5ZKMaGVhclJhdGXlj5jph4/mnInkuI3mmL7okZfnmoTpo47pmakKYGBge3J9CnRhYmxlMT10YWJsZShjb21wbGV0ZWRfZGF0YSRtYWxlLGNvbXBsZXRlZF9kYXRhJFRlblllYXJDSEQpCmNoaXNxLnRlc3QodGFibGUxKQp0YWJsZTEKdGFibGUyPXRhYmxlKGNvbXBsZXRlZF9kYXRhJGVkdWNhdGlvbixjb21wbGV0ZWRfZGF0YSRUZW5ZZWFyQ0hEKQpjaGlzcS50ZXN0KHRhYmxlMikKdGFibGUzPXRhYmxlKGNvbXBsZXRlZF9kYXRhJGN1cnJlbnRTbW9rZXIsY29tcGxldGVkX2RhdGEkVGVuWWVhckNIRCkKY2hpc3EudGVzdCh0YWJsZTMpCnRhYmxlND10YWJsZShjb21wbGV0ZWRfZGF0YSRCUE1lZHMsY29tcGxldGVkX2RhdGEkVGVuWWVhckNIRCkKY2hpc3EudGVzdCh0YWJsZTQpCnRhYmxlNT10YWJsZShjb21wbGV0ZWRfZGF0YSRwcmV2YWxlbnRTdHJva2UsY29tcGxldGVkX2RhdGEkVGVuWWVhckNIRCkKY2hpc3EudGVzdCh0YWJsZTUpCnRhYmxlNj10YWJsZShjb21wbGV0ZWRfZGF0YSRwcmV2YWxlbnRIeXAsY29tcGxldGVkX2RhdGEkVGVuWWVhckNIRCkKY2hpc3EudGVzdCh0YWJsZTYpCnRhYmxlNz10YWJsZShjb21wbGV0ZWRfZGF0YSRkaWFiZXRlcyxjb21wbGV0ZWRfZGF0YSRUZW5ZZWFyQ0hEKQpjaGlzcS50ZXN0KHRhYmxlNykKdGFibGU4PXRhYmxlKGNvbXBsZXRlZF9kYXRhJGJwbGV2ZWwsY29tcGxldGVkX2RhdGEkVGVuWWVhckNIRCkKY2hpc3EudGVzdCh0YWJsZTgpCmBgYApgYGB7cn0KcmVxdWlyZShHR2FsbHkpCmZ1bGxfZGF0YSA8LSBjb21wbGV0ZWRfZGF0YQpnZ3BhaXJzKGZ1bGxfZGF0YVssYygyLDUsMTA6MTUpXSkKYGBgCmRpYUJQ5ZKMc3lzQlDmnInlpJrph43lhbHnur/mgKfnmoTljbHpmanjgIIK77yI6Kej6YeK5LiL5a6D55qE5a6e6ZmF5oSP5LmJ77yJCgpjdXJyZW50U21va2Vy5Y+Y6YeP5Y+v6IO95LiN5pi+6JGX77yM5LiL6Z2i6L+b5YWl5qih5Z6LCiMjIyBtb2RlbApgYGB7cn0KIyDliJLliIbmlbDmja7pm4YKZnVsbF9kYXRhIDwtIGNvbXBsZXRlZF9kYXRhCnNldC5zZWVkKDUwMCkKc3BsaXQgPSBzYW1wbGUuc3BsaXQoZnVsbF9kYXRhJFRlblllYXJDSEQsIFNwbGl0UmF0aW8gPSAwLjcwKQoKdHJhaW4gPSBzdWJzZXQoZnVsbF9kYXRhLCBzcGxpdD09VFJVRSkKdGVzdCA9IHN1YnNldChmdWxsX2RhdGEsIHNwbGl0PT1GQUxTRSkKYGBgCgojIyMjIOmAu+i+keWbnuW9kgpgYGB7cn0KIyBMb2dpc3RpYyBSZWdyZXNzaW9uIE1vZGVsIC0gVXNpbmcgYWxsIHZhcmlhYmxlcwpmdWxsZGF0YUxvZyA9IGdsbShUZW5ZZWFyQ0hEIH4gLiwgZGF0YSA9IHRyYWluLCBmYW1pbHk9Ymlub21pYWwpCnN1bW1hcnkoZnVsbGRhdGFMb2cpCmBgYApgYGB7cn0KZnVsbGRhdGFMb2cgPSBnbG0oVGVuWWVhckNIRCB+IG1hbGUgKyBhZ2UgKyBjaWdzUGVyRGF5ICsgc3lzQlAgKyBnbHVjb3NlICsgYnBsZXZlbCwgZGF0YSA9IHRyYWluLCBmYW1pbHk9Ymlub21pYWwpCnN1bW1hcnkoZnVsbGRhdGFMb2cpCnByZWRpY3RUZXN0ID0gcHJlZGljdChmdWxsZGF0YUxvZywgdHlwZT0icmVzcG9uc2UiLCBuZXdkYXRhPXRlc3QpCmdsbV90YWJsZSA8LSB0YWJsZSh0ZXN0JFRlblllYXJDSEQsIHByZWRpY3RUZXN0ID4gMC41KQpgYGAKYGBge3J9CkFDQ1UgPSBzdW0oZGlhZyhnbG1fdGFibGUpKSAvIHN1bShnbG1fdGFibGUpCkFDQ1UKYGBgCgojIyMjIOmaj+acuuajruaelwpgYGB7cn0Kc2V0LnNlZWQoMjIpCnJmX21vZGVsIDwtIHJhbmRvbUZvcmVzdChUZW5ZZWFyQ0hEIH4gLiwgZGF0YSA9IHRyYWluKQojIGdldCBpbXBvcnRhbmNlCmltcG9ydGFuY2UgPC0gaW1wb3J0YW5jZShyZl9tb2RlbCkKaW1wb3J0YW5jZQpgYGAKYGBge3J9CiMg6YCJ5oup6YeN6KaB55qE5Zug57SgCnNldC5zZWVkKDMzKQpyZl9tb2RlbCA8LSByYW5kb21Gb3Jlc3QoVGVuWWVhckNIRCB+IGFnZSArIHRvdENob2wgKyBzeXNCUCArIGRpYUJQICsgQk1JICsgaGVhcnRSYXRlICsgZ2x1Y29zZSwgZGF0YSA9IHRyYWluLCBwcm94aW1pdHkgPSBUUlVFKQojIHNob3cgbW9kZWwgZXJyb3IKcGxvdChyZl9tb2RlbCkKbGVnZW5kKCd0b3ByaWdodCcsIGNvbG5hbWVzKHJmX21vZGVsJGVyci5yYXRlKSwgY29sPTE6MywgZmlsbD0xOjMpCiMgZ2V0IGltcG9ydGFuY2UKaW1wb3J0YW5jZSA8LSBpbXBvcnRhbmNlKHJmX21vZGVsKQp2YXJJbXBvcnRhbmNlIDwtIGRhdGEuZnJhbWUoVmFyaWFibGVzID0gcm93Lm5hbWVzKGltcG9ydGFuY2UpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIEltcG9ydGFuY2UgPSByb3VuZChpbXBvcnRhbmNlWyAsJ01lYW5EZWNyZWFzZUdpbmknXSwgMikpCnJhbmtJbXBvcnRhbmNlIDwtIHZhckltcG9ydGFuY2UgJT4lIG11dGF0ZShSYW5rID0gcGFzdGUwKCcjJywgZGVuc2VfcmFuayhkZXNjKEltcG9ydGFuY2UpKSkpCmdncGxvdChyYW5rSW1wb3J0YW5jZSwgYWVzKHggPSByZW9yZGVyKFZhcmlhYmxlcywgSW1wb3J0YW5jZSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIHkgPSBpbXBvcnRhbmNlLCBmaWxsID0gSW1wb3J0YW5jZSkpICsgCiAgZ2VvbV9iYXIoc3RhdCA9ICdpZGVudGl0eScpICsgCiAgZ2VvbV90ZXh0KGFlcyh4ID0gVmFyaWFibGVzLCB5ID0gMC41LCBsYWJlbCA9IFJhbmspLAogICAgICAgICAgICBoanVzdD0wLCB2anVzdD0wLjU1LCBzaXplPTQsIGNvbG91cj0ncmVkJykgKyAKICBsYWJzKHggPSAnVmFyaWFibGVzJykgKyAKICBjb29yZF9mbGlwKCkgKyAKICB0aGVtZV9idygpCiMg6L+Z6YeM5pyJ5oKj55eF6aOO6Zmp55qEZXJyb3LkuI3pmY3lj43ljYfvvIzpnIDopoHmjqLnqbblhbbkuK3ljp/lm6AKYGBgCmBgYHtyfQojIOe7mOWItuWIhuexu+WbvuWDjwojIE1EU3Bsb3QocmZfbW9kZWwsIGZhYywgaz0yLCBwYWxldHRlPU5VTEwsIHBjaD0yMCkKcHJlZDwtcHJlZGljdChyZl9tb2RlbCxuZXdkYXRhPXRlc3QpICAKcHJlZF9vdXRfMTwtcHJlZGljdChvYmplY3Q9cmZfbW9kZWwsbmV3ZGF0YT10ZXN0LHR5cGU9InByb2IiKSAgI+i+k+WHuuamgueOhwp0YWJsZSA8LSB0YWJsZShwcmVkLHRlc3QkVGVuWWVhckNIRCkgIApzdW0oZGlhZyh0YWJsZSkpL3N1bSh0YWJsZSkgICPpooTmtYvlh4bnoa7njocKcGxvdChtYXJnaW4ocmZfbW9kZWwsdGVzdCRUZW5ZZWFyQ0hEKSkgIyDop4LmtYvlgLzooqvliKTmlq3mraPnoa7nmoTmpoLnjoflm74KYGBgCgojIyMjIFNWTeaUr+aMgeWQkemHj+acugpgYGB7cn0KIyDlj4LogIPotYTmlpkgaHR0cDovL3N0dWRpby5nYWxheHlzdGF0aXN0aWNzLmNvbS9yZXBvcnQvc3ZtL2FydGljbGUzLwojIOWFiOi/m+ihjOaooeWei+iwg+S8mAp0dW5lZCA8LSB0dW5lLnN2bShUZW5ZZWFyQ0hEIH4gLixkYXRhID0gdHJhaW4sZ2FtbWEgPSAxMF4oLTY6LTEpLGNvc3QgPSAxMF4oMToyKSkKc3VtbWFyeSh0dW5lZCkKIyDkvb/nlKh0dXJuaW5n5Ye95pWw5b6X5Yiw5pyA5L2z5Y+C5pWw6K6+572u5pSv5oyB5ZCR6YeP5py6Cm1vZGVsLnR1bmVkIDwtIHN2bShUZW5ZZWFyQ0hEIH4gLiwgZGF0YT10cmFpbiwgZ2FtbWE9dHVuZWQkYmVzdC5wYXJhbWV0ZXJzJGdhbW1hLCBjb3N0PXR1bmVkJGJlc3QucGFyYW1ldGVycyRjb3N0KQpzdW1tYXJ5KG1vZGVsLnR1bmVkKQpgYGAKYGBge3J9CiMg6LCD55SocHJlZGljdOWHveaVsOWfuuS6juWImumFjee9ruWlveeahFNWTeaooeWei+i/m+ihjOexu+agh+WPt+eahOmihOa1i++8mgpzdm0udHVuZWQucHJlZCA8LSBwcmVkaWN0KG1vZGVsLnR1bmVkLCB0ZXN0WywhbmFtZXModGVzdCkgJWluJSBjKCJUZW5ZZWFyQ0hEIildKQpzdm0udHVuZWQudGFibGUgPC0gdGFibGUoc3ZtLnR1bmVkLnByZWQsIHRlc3QkVGVuWWVhckNIRCkKc3ZtLnR1bmVkLnRhYmxlCmBgYApgYGB7cn0KYWNjdXJhY3kudGVzdC5zdm0gPC0gc3VtKGRpYWcoc3ZtLnR1bmVkLnRhYmxlKSkvc3VtKHN2bS50dW5lZC50YWJsZSkKbGlzdCgn5rWL6K+V6ZuG6aKE5rWL5re35reG55+p6Zi1Jz1zdm0udHVuZWQudGFibGUsICfmtYvor5Xpm4bpooTmtYvmraPnoa7njocnPWFjY3VyYWN5LnRlc3Quc3ZtKQpgYGAKCiMjIyDmqKHlnovor4rmlq0K5qC55o2u5LiK6Z2i5LiJ5Liq5qih5Z6L55qE57uT5p6c77yM5Y+v5Lul55yL5Ye66aKE5rWL57uT5p6c55qE57G75Yir5pWw6YeP5YiG5biD6Z2e5bi45LiN5Z2H6KGhCmBgYHtyfQpzdW0oZnVsbF9kYXRhJFRlblllYXJDSEQgPT0gMSkKc3VtKGZ1bGxfZGF0YSRUZW5ZZWFyQ0hEID09IDApCiMg6ZKI5a+56L+Z5LiA546w6LGh77yM6ZyA6KaB6YeH5Y+W5o6q5pa95bmz6KGh5pWw5o2u6ZuGCmBgYAoKCgoKCgoK