library

library(ggplot2)

load dataset

data = read.table('bank2.dat')
colnames(data) = c('X1','X2','X3','X4','X5','X6')
data = data.frame(data, genuine = c(rep(1, 100), rep(0, 100)))
attach(data)
下列物件被遮斷自 data (pos = 6):

    genuine, X1, X2, X3, X4, X5, X6

下列物件被遮斷自 data (pos = 8):

    genuine, X1, X2, X3, X4, X5, X6

下列物件被遮斷自 data (pos = 9):

    genuine, X1, X2, X3, X4, X5, X6

下列物件被遮斷自 data (pos = 10):

    genuine, X1, X2, X3, X4, X5, X6

下列物件被遮斷自 data (pos = 16):

    genuine, X1, X2, X3, X4, X5, X6
head(data)

summary

print('Quartiles:')
[1] "Quartiles:"
summary(data)
       X1              X2              X3              X4               X5              X6           genuine   
 Min.   :213.8   Min.   :129.0   Min.   :129.0   Min.   : 7.200   Min.   : 7.70   Min.   :137.8   Min.   :0.0  
 1st Qu.:214.6   1st Qu.:129.9   1st Qu.:129.7   1st Qu.: 8.200   1st Qu.:10.10   1st Qu.:139.5   1st Qu.:0.0  
 Median :214.9   Median :130.2   Median :130.0   Median : 9.100   Median :10.60   Median :140.4   Median :0.5  
 Mean   :214.9   Mean   :130.1   Mean   :130.0   Mean   : 9.418   Mean   :10.65   Mean   :140.5   Mean   :0.5  
 3rd Qu.:215.1   3rd Qu.:130.4   3rd Qu.:130.2   3rd Qu.:10.600   3rd Qu.:11.20   3rd Qu.:141.5   3rd Qu.:1.0  
 Max.   :216.3   Max.   :131.0   Max.   :131.1   Max.   :12.700   Max.   :12.30   Max.   :142.4   Max.   :1.0  
print('NAs:')
[1] "NAs:"
sapply(data, function(x) sum(is.na(x)))
     X1      X2      X3      X4      X5      X6 genuine 
      0       0       0       0       0       0       0 

correlation coefficient

library(ggcorrplot)

cor_matrix = cor(data)

ggcorrplot(cor_matrix, 
           method = "circle",
           type = "full",
           lab = TRUE,
           lab_size = 3,
           colors = c("blue", "white", "red"),
           outline.color = "gray",
           legend.title = "Correlation",
           show.legend = TRUE)

pairs plot

pairs(data, upper.panel = NULL)


pairs_plot = function(x, y, x_name, y_name){
  par(mar = c(5, 5, 4, 10))
  plot(x, y, main = "Swiss bank notes", xlab = x_name, ylab = y_name)
  points(x[1:100], y[1:100], col = "blue")
  points(x[101:200], y[101:200], col = "red")
  legend("topright", legend = c("Genuine", "Counterfeit"), col = c("blue", "red"), pch = 1, xpd = TRUE, inset = c(-0.3, 0))
  
  #ggplot(data.frame(X5, X6), aes(x = X5, y = X6, color = genuine)) +
  #geom_point() +
  #labs(title = "Swiss bank notes")
}

num = combn(1:6, 2)
for (i in 1:ncol(num)) {
  x = paste0("X", num[1, ][i])
  y = paste0("X", num[2, ][i])
  pairs_plot(get(x), get(y), x, y)
}

NA
NA

box plot

name = NULL
for (i in 1:6) {
  name = c(name, paste0("X", i, "_Genuine"))
  name = c(name, paste0("X", i, "_Counterfeit"))
}

groups = list()
for (i in 1:length(name)){
  if (i %% 2 == 1) {
    groups[[name[i]]] = data[1:100, as.integer(i / 2) + 1]
  } else {
    groups[[name[i]]] = data[101:200, as.integer(i / 2)]
  }
}

means <- sapply(groups, mean)

par(mfrow=c(1,3))
for (i in seq(1, length(name) ,by = 2)){
  boxplot(groups[i:(i + 1)], names = name[i:(i + 1)], frame = TRUE, main = "Swiss bank notes", cex.axis=0.99)
  
  for (j in 0:1) {
    lines(c(j + 0.6, j + 1.4), rep(means[[name[(i + j)]]], 2), lty = "dotted", lwd = 1.2)
  }
}

histogram

data_Genuine = data[1:100, 1:6]   
data_Counterfeit = data[101:200, 1:6]

for (i in 1:6) {
  breaks_seq = seq(min(data[, i]), max(data[, i]), by = 0.1)
  min_x = floor(min(data[, i]))
  max_x = ceiling(max(data[, i]))

  hist(data_Genuine[, i],
       breaks = breaks_seq,
       col = rgb(0, 0, 1, 0.5),   
       border = "blue",
       xlim = c(min_x, max_x),
       ylim = c(0, max(table(cut(data[, i], breaks_seq))) + 5),
       main = "Histogram",
       xlab = paste0("X", i),
       axes = FALSE) 
  
  hist(data_Counterfeit[, i],
       breaks = breaks_seq,
       col = rgb(1, 0, 0, 0.5),   
       border = "red",
       add = TRUE)
  
  axis(side = 1, at = seq(min_x, max_x, by = 1))
  axis(side = 2)
  
  legend("topright",
         legend = c("Genuine", "Counterfeit", "Overlap"),
         fill = c(rgb(0, 0, 1, 0.5), rgb(1, 0, 0, 0.5), rgb(0.6, 0, 0.4, 0.7)),
         border = c("blue", "red", "purple"))
}

overlap

data_Genuine = data[data$genuine == 1, 1:6]
data_Counterfeit = data[data$genuine == 0, 1:6]

overlap_ratios = numeric(6)

par(mfrow = c(1, 1))
num_vars = ncol(data_Genuine)

for (i in 1:num_vars) {

  breaks_seq = seq(min(data[, i]), max(data[, i]), by = 0.1)

  min_x = floor(min(data[, i]))
  max_x = ceiling(max(data[, i]))

  hist_Genuine = hist(data_Genuine[, i], breaks = breaks_seq, plot = FALSE)
  hist_Counterfeit = hist(data_Counterfeit[, i], breaks = breaks_seq, plot = FALSE)

  overlap_counts = pmin(hist_Genuine$counts, hist_Counterfeit$counts)

  overlap_ratio = sum(overlap_counts) / sum(hist_Counterfeit$counts)
  overlap_ratios[i] = overlap_ratio

  plot_title = paste0("Histogram of ", colnames(data)[i], " (Overlap: ", round(overlap_ratio * 100, 2), "%)")

  hist(data_Genuine[, i],
       breaks = breaks_seq,
       col = rgb(0, 0, 1, 0.5),
       border = "blue",
       xlim = c(min_x, max_x),
       ylim = c(0, max(c(hist_Genuine$counts, hist_Counterfeit$counts)) + 5),
       main = plot_title,           
       xlab = colnames(data)[i],
       axes = FALSE)

  hist(data_Counterfeit[, i],
       breaks = breaks_seq,
       col = rgb(1, 0, 0, 0.5),
       border = "red",
       add = TRUE)

  axis(side = 1, at = seq(min_x, max_x, by = 1))
  axis(side = 2)

  legend("topright",
         legend = c("Genuine", "Counterfeit", "Overlap"),
         fill = c(rgb(0, 0, 1, 0.5), rgb(1, 0, 0, 0.5), rgb(0.6, 0, 0.4, 0.7)),
         border = c("blue", "red", "purple"))
}

kernel density

library(KernSmooth)

for (i in 1:6) {
  
  data_Genuine = data[1:100, i]   
  data_Counterfeit = data[101:200, i]  

  fh1 = bkde(data_Genuine, kernel = "biweight")  
  fh2 = bkde(data_Counterfeit, kernel = "biweight")  

  x_min = floor(min(c(fh1$x, fh2$x)))
  x_max = ceiling(max(c(fh1$x, fh2$x)))
  y_max = max(c(fh1$y, fh2$y)) * 1.1

  x_ticks <- seq(x_min, x_max, by = 1)

  plot(fh1, type = "l", lwd = 2,
       xlab = "Counterfeit / Genuine", 
       ylab = paste0("Density estimates for X", i, sep = ""),
       col = "blue", main = paste0("Swiss bank notes - X", i),
       xlim = c(x_min, x_max), ylim = c(0, y_max),
       axes = FALSE) 
  
  lines(fh2, lty = "dotted", lwd = 2, col = "red")

  axis(side = 1, at = x_ticks, labels = x_ticks)
  axis(side = 2)
  box()

  legend("topright",
         legend = c("Genuine", "Counterfeit"),
         col = c("blue", "red"),
         lty = c("solid", "dotted"),
         lwd = 2,
         cex = 0.8)
}

3D scatterplot

library(lattice)
library(grid)

groups = data[, 7]

pch_types = c(1, 1)              
colors = c("blue", "red")        
pch_group = pch_types[ifelse(groups == 0, 1, 2)]
col_group = colors[ifelse(groups == 0, 1, 2)]

combos = combn(1:6, 3)

n_rows = 1        
n_cols = 1        
plots_per_page = n_rows * n_cols   

for (page in seq(1, ncol(combos), by = plots_per_page)) {
  grid.newpage()

  for (i in 0:(plots_per_page - 1)) {
    index = page + i
    if (index > ncol(combos)) break

    x_name = paste0("X", combos[1, index])
    y_name = paste0("X", combos[2, index])
    z_name = paste0("X", combos[3, index])

    x_data = data[[x_name]]
    y_data = data[[y_name]]
    z_data = data[[z_name]]

    p = cloud(z_data ~ x_data * y_data,
          pch = pch_group,
          col = col_group,
          cex = 1.2,
          ticktype = "detailed",
          main = paste("Swiss bank notes:", x_name, y_name, z_name),
          screen = list(z = -90, x = -90, y = 45),
          scales = list(arrows = FALSE, col = "black", distance = 1, cex = 0.5),
          xlab = list(x_name, rot = -10, cex = 1.2),
          ylab = list(y_name, rot = 10, cex = 1.2),
          zlab = list(z_name, rot = 90, cex = 1.1),

          par.settings = list(
            axis.line = list(col = "black"),    
            box.3d = list(
              col = c(
                "black", "transparent", "transparent","black", "black", "transparent", "transparent", "transparent",   
                "transparent", "transparent", "transparent", "transparent"))),

          key = list(
            space = "right",
            points = list(pch = pch_types, col = colors, cex = 1.5),
            text = list(c("Genuine", "Counterfeit")),
            border = FALSE
          )
        )

    print(p, split = c((i %% n_cols) + 1, (i %/% n_cols) + 1, n_cols, n_rows), more = TRUE)
  }
}

3d plotly scatter plot

library(plotly)
library(htmlwidgets)

# 假設你的 data 是一個 data.frame,包含 X1, X2, X6 和 groups 列

# 取出 X1, X2 和 X6 變數
x_data = data$X1
y_data = data$X2
z_data = data$X6
groups = data[, 7]

# 將 groups 轉換為因子,這樣我們可以用顏色區分
groups = factor(groups, levels = c(0, 1), labels = c("Genuine", "Counterfeit"))

# 計算 x, y, z 軸的範圍
x_range <- range(x_data)
y_range <- range(y_data)
z_range <- range(z_data)

# 將 x, y, z 軸數據縮放到 [0, 1] 範圍
x_scaled = (x_data - x_range[1]) / (x_range[2] - x_range[1])
y_scaled = (y_data - y_range[1]) / (y_range[2] - y_range[1])
z_scaled = (z_data - z_range[1]) / (z_range[2] - z_range[1])

# 計算刻度點,並將其四捨五入到小數點後 1 位
tick_positions = seq(0, 1, length.out = 5)
tick_labels_x = round(seq(x_range[1], x_range[2], length.out = 5), 1)
tick_labels_y = round(seq(y_range[1], y_range[2], length.out = 5), 1)
tick_labels_z = round(seq(z_range[1], z_range[2], length.out = 5), 1)

# 每個點的 hover 標籤,顯示原始數據值
hover_text <- paste0(
  "X1: ", round(x_data, 1), "<br>",
  "X2: ", round(y_data, 1), "<br>",
  "X6: ", round(z_data, 1)
)

# 使用 plotly 生成 3D 散點圖
fig <- plot_ly(
  x = x_scaled,
  y = y_scaled,
  z = z_scaled,
  color = groups,
  colors = c("blue", "red"),
  type = "scatter3d",
  mode = "markers",
  marker = list(size = 5),
  text = hover_text,  # 設定 hover 的文字
  hoverinfo = "text"  # 顯示 text 而不是縮放後的座標
)

# 添加標題和標籤,並設定軸範圍與標籤
fig <- fig %>% layout(
  title = "3D Scatter Plot of X1, X2, X6",
  scene = list(
    xaxis = list(title = "X1", range = c(0, 1), tickvals = tick_positions, ticktext = tick_labels_x),
    yaxis = list(title = "X2", range = c(0, 1), tickvals = tick_positions, ticktext = tick_labels_y),
    zaxis = list(title = "X6", range = c(0, 1), tickvals = tick_positions, ticktext = tick_labels_z)
  )
)

# 輸出為 HTML 檔案
saveWidget(fig, "3d_scatter_plot.html")

Andrews’ Curves

library(tourr)

# x:全部 200 筆資料
x = data[1:200, ]

# 初始化 y(做 zero-one scaling)
y = NULL

for (i in 1:6) {
  z = (x[, i] - min(x[, i])) / (max(x[, i]) - min(x[, i]))  # zero-one scaling
  y = cbind(y, z)
}

# 定義類別
Type = data[, 7]  # 前 100 筆為 Type 1,後 100 筆為 Type 2
f = as.integer(Type)

# 定義 t 軸範圍
grid = seq(0, 2 * pi, length = 1000)

# 初始化 plot,畫第一筆曲線
plot(grid, 
     andrews(y[1, ])(grid), 
     type = "l", 
     lwd = 1.2, 
     main = "Andrews' curves (All Bank data)", 
     axes = FALSE, 
     frame = TRUE, 
     ylim = c(-0.5, 0.6), 
     ylab = "", 
     xlab = "", 
     col = ifelse(f[1] == 1, "blue", "red"), 
     lty = ifelse(f[1] == 1, "solid", "dotted"))

# 繪製剩餘曲線
for (i in 2:200) {
  lines(grid, 
        andrews(y[i, ])(grid), 
        col = ifelse(f[i] == 1, "blue", "red"), 
        lwd = 1.2, 
        lty = ifelse(f[i] == 1, "solid", "dotted"))
}

# 增加座標軸
axis(side = 2, at = seq(-0.5, 0.6, 0.2), labels = seq(-0.5, 0.6, 0.2))
axis(side = 1, at = seq(0, 7, 1), labels = seq(0, 7, 1))

# 加上圖例
legend("topright", 
       legend = c("Genuine", "Counterfeit"), 
       col = c("blue", "red"), 
       lwd = 1.5, 
       lty = c("solid", "dotted"))

#Logistic Regression

library(caTools)
library(ROCR)

# summary
set.seed(123456)
split = sample.split(data,SplitRatio = 0.7)
train_reg = subset(data, split =='TRUE')
test_reg = subset(data, split == 'FALSE')

logistic_model = glm(factor(genuine) ~ X1+X2+X3+X4+X5+X6, data = train_reg,
                     family = binomial(link = 'logit'), 
                     control = list(maxit=1000))
警告: glm.fit:擬合機率算出來是數值零或一
logistic_model

Call:  glm(formula = factor(genuine) ~ X1 + X2 + X3 + X4 + X5 + X6, 
    family = binomial(link = "logit"), data = train_reg, control = list(maxit = 1000))

Coefficients:
(Intercept)           X1           X2           X3           X4           X5           X6  
  -5619.203       15.488        4.479      -17.391      -15.337      -22.686       30.986  

Degrees of Freedom: 113 Total (i.e. Null);  107 Residual
Null Deviance:      158 
Residual Deviance: 4.51e-10     AIC: 14
summary(logistic_model)

Call:
glm(formula = factor(genuine) ~ X1 + X2 + X3 + X4 + X5 + X6, 
    family = binomial(link = "logit"), data = train_reg, control = list(maxit = 1000))

Coefficients:
              Estimate Std. Error z value Pr(>|z|)
(Intercept) -5.619e+03  2.773e+08       0        1
X1           1.549e+01  2.267e+05       0        1
X2           4.479e+00  8.012e+05       0        1
X3          -1.739e+01  1.287e+06       0        1
X4          -1.534e+01  3.596e+05       0        1
X5          -2.269e+01  1.828e+05       0        1
X6           3.099e+01  1.682e+05       0        1

(Dispersion parameter for binomial family taken to be 1)

    Null deviance: 1.5800e+02  on 113  degrees of freedom
Residual deviance: 4.5102e-10  on 107  degrees of freedom
AIC: 14

Number of Fisher Scoring iterations: 27
predict_reg = predict(logistic_model,test_reg,type='response')
predict_reg
           1            2            7            8            9           14           15           16 
1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 
          21           22           23           28           29           30           35           36 
1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 
          37           42           43           44           49           50           51           56 
1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 
          57           58           63           64           65           70           71           72 
1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 8.748549e-11 9.986445e-01 1.000000e+00 
          77           78           79           84           85           86           91           92 
1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 
          93           98           99          100          105          106          107          112 
1.000000e+00 1.000000e+00 1.000000e+00 1.000000e+00 2.220446e-16 2.220446e-16 2.220446e-16 2.220446e-16 
         113          114          119          120          121          126          127          128 
2.220446e-16 2.220446e-16 2.220446e-16 2.220446e-16 2.220446e-16 2.220446e-16 2.220446e-16 2.220446e-16 
         133          134          135          140          141          142          147          148 
2.220446e-16 2.220446e-16 2.220446e-16 2.220446e-16 2.220446e-16 2.220446e-16 2.220446e-16 2.220446e-16 
         149          154          155          156          161          162          163          168 
2.220446e-16 2.220446e-16 2.220446e-16 2.220446e-16 2.220446e-16 2.220446e-16 2.220446e-16 2.220446e-16 
         169          170          175          176          177          182          183          184 
2.220446e-16 2.220446e-16 2.220446e-16 2.220446e-16 2.220446e-16 2.220446e-16 2.220446e-16 2.220446e-16 
         189          190          191          196          197          198 
2.220446e-16 2.220446e-16 2.220446e-16 2.220446e-16 2.220446e-16 2.220446e-16 
predict_reg = ifelse(predict_reg >0.5,1,0)
table(test_reg$genuine,predict_reg)
   predict_reg
     0  1
  0 42  0
  1  1 43
missing_classerr = mean(predict_reg!=test_reg$genuine)
print(paste('Accuracy=',1-missing_classerr))
[1] "Accuracy= 0.988372093023256"
ROCPred = prediction(predict_reg, test_reg$genuine)
ROCPer = performance(ROCPred, measure = 'tpr',x.measure = 'fpr')

auc = performance(ROCPred,measure='auc')
auc = auc@y.values[[1]]
auc
[1] 0.9886364
plot(ROCPer)

plot(ROCPer,colourise=TRUE,print.cuttoffs.at=seq(0.1,by=0.1), col=2, lwd=2,
     main='ROC Curve')
abline(a=0,b=1)
auc = round(auc,4)
legend(.6,.4, auc,title = 'AUC',cex=1)

Random Forest

library(randomForest)

# summary
set.seed(12345)
data$genuine = as.factor(data$genuine)
split = sample.split(data,SplitRatio = 0.6)
train_rf = subset(data, split =='TRUE')
test_rf = subset(data, split == 'FALSE')
rf = randomForest(genuine~ X1+X2+X3+X4+X5+X6,data =train_rf,ntree=500)
print(rf)

Call:
 randomForest(formula = genuine ~ X1 + X2 + X3 + X4 + X5 + X6,      data = train_rf, ntree = 500) 
               Type of random forest: classification
                     Number of trees: 500
No. of variables tried at each split: 2

        OOB estimate of  error rate: 0.88%
Confusion matrix:
   0  1 class.error
0 56  0  0.00000000
1  1 56  0.01754386
set.seed(12345)
importance(rf)
   MeanDecreaseGini
X1         1.078579
X2         2.833452
X3         4.902606
X4        14.906531
X5         5.751345
X6        26.515655
varImpPlot(rf)

pred1 = predict(rf,test_rf, type='prob')
perf = prediction(pred1[,2], test_rf$genuine)
auc = performance(perf, 'auc')
auc_value = auc@y.values[[1]]  # Extract the numeric AUC value

pred3 = performance(perf, 'tpr', 'fpr')

# Plot the ROC curve
plot(pred3, main="ROC Curve for Random Forest", col=2, lwd=2)
abline(a=0, b=1, lwd=2, lty=2, col='gray')

# Add AUC value to the plot
text(0.6, 0.2, paste("AUC =", round(auc_value, 3)), col=2, cex=1.2)

LS0tDQp0aXRsZTogInN3aXNzIGJhbmsgbm90ZXMiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCmRhdGU6ICIyMDI1LTAzLTE3Ig0KYXV0aG9yOiAiSHN1LCBZYW8tQ2hpaCwgV2FuZywgWHVhbi1DaHVuLCBTaW4sIFdlbi1MZWUiDQotLS0NCg0KIyBsaWJyYXJ5DQpgYGB7cn0NCmxpYnJhcnkoZ2dwbG90MikNCmBgYA0KDQoNCiMgbG9hZCBkYXRhc2V0DQpgYGB7cn0NCmRhdGEgPSByZWFkLnRhYmxlKCdiYW5rMi5kYXQnKQ0KY29sbmFtZXMoZGF0YSkgPSBjKCdYMScsJ1gyJywnWDMnLCdYNCcsJ1g1JywnWDYnKQ0KZGF0YSA9IGRhdGEuZnJhbWUoZGF0YSwgZ2VudWluZSA9IGMocmVwKDEsIDEwMCksIHJlcCgwLCAxMDApKSkNCmF0dGFjaChkYXRhKQ0KaGVhZChkYXRhKQ0KYGBgDQoNCg0KIyBzdW1tYXJ5DQpgYGB7cn0NCnByaW50KCdRdWFydGlsZXM6JykNCnN1bW1hcnkoZGF0YSkNCg0KcHJpbnQoJ05BczonKQ0Kc2FwcGx5KGRhdGEsIGZ1bmN0aW9uKHgpIHN1bShpcy5uYSh4KSkpDQpgYGANCg0KDQojIGNvcnJlbGF0aW9uIGNvZWZmaWNpZW50DQpgYGB7cn0NCmxpYnJhcnkoZ2djb3JycGxvdCkNCg0KY29yX21hdHJpeCA9IGNvcihkYXRhKQ0KDQpnZ2NvcnJwbG90KGNvcl9tYXRyaXgsIA0KICAgICAgICAgICBtZXRob2QgPSAiY2lyY2xlIiwNCiAgICAgICAgICAgdHlwZSA9ICJmdWxsIiwNCiAgICAgICAgICAgbGFiID0gVFJVRSwNCiAgICAgICAgICAgbGFiX3NpemUgPSAzLA0KICAgICAgICAgICBjb2xvcnMgPSBjKCJibHVlIiwgIndoaXRlIiwgInJlZCIpLA0KICAgICAgICAgICBvdXRsaW5lLmNvbG9yID0gImdyYXkiLA0KICAgICAgICAgICBsZWdlbmQudGl0bGUgPSAiQ29ycmVsYXRpb24iLA0KICAgICAgICAgICBzaG93LmxlZ2VuZCA9IFRSVUUpDQpgYGANCg0KDQoNCiMgcGFpcnMgcGxvdA0KYGBge3J9DQpwYWlycyhkYXRhLCB1cHBlci5wYW5lbCA9IE5VTEwpDQoNCnBhaXJzX3Bsb3QgPSBmdW5jdGlvbih4LCB5LCB4X25hbWUsIHlfbmFtZSl7DQogIHBhcihtYXIgPSBjKDUsIDUsIDQsIDEwKSkNCiAgcGxvdCh4LCB5LCBtYWluID0gIlN3aXNzIGJhbmsgbm90ZXMiLCB4bGFiID0geF9uYW1lLCB5bGFiID0geV9uYW1lKQ0KICBwb2ludHMoeFsxOjEwMF0sIHlbMToxMDBdLCBjb2wgPSAiYmx1ZSIpDQogIHBvaW50cyh4WzEwMToyMDBdLCB5WzEwMToyMDBdLCBjb2wgPSAicmVkIikNCiAgbGVnZW5kKCJ0b3ByaWdodCIsIGxlZ2VuZCA9IGMoIkdlbnVpbmUiLCAiQ291bnRlcmZlaXQiKSwgY29sID0gYygiYmx1ZSIsICJyZWQiKSwgcGNoID0gMSwgeHBkID0gVFJVRSwgaW5zZXQgPSBjKC0wLjMsIDApKQ0KICANCiAgI2dncGxvdChkYXRhLmZyYW1lKFg1LCBYNiksIGFlcyh4ID0gWDUsIHkgPSBYNiwgY29sb3IgPSBnZW51aW5lKSkgKw0KICAjZ2VvbV9wb2ludCgpICsNCiAgI2xhYnModGl0bGUgPSAiU3dpc3MgYmFuayBub3RlcyIpDQp9DQoNCm51bSA9IGNvbWJuKDE6NiwgMikNCmZvciAoaSBpbiAxOm5jb2wobnVtKSkgew0KICB4ID0gcGFzdGUwKCJYIiwgbnVtWzEsIF1baV0pDQogIHkgPSBwYXN0ZTAoIlgiLCBudW1bMiwgXVtpXSkNCiAgcGFpcnNfcGxvdChnZXQoeCksIGdldCh5KSwgeCwgeSkNCn0NCg0KDQpgYGANCg0KIyBib3ggcGxvdA0KYGBge3J9DQpuYW1lID0gTlVMTA0KZm9yIChpIGluIDE6Nikgew0KICBuYW1lID0gYyhuYW1lLCBwYXN0ZTAoIlgiLCBpLCAiX0dlbnVpbmUiKSkNCiAgbmFtZSA9IGMobmFtZSwgcGFzdGUwKCJYIiwgaSwgIl9Db3VudGVyZmVpdCIpKQ0KfQ0KDQpncm91cHMgPSBsaXN0KCkNCmZvciAoaSBpbiAxOmxlbmd0aChuYW1lKSl7DQogIGlmIChpICUlIDIgPT0gMSkgew0KICAgIGdyb3Vwc1tbbmFtZVtpXV1dID0gZGF0YVsxOjEwMCwgYXMuaW50ZWdlcihpIC8gMikgKyAxXQ0KICB9IGVsc2Ugew0KICAgIGdyb3Vwc1tbbmFtZVtpXV1dID0gZGF0YVsxMDE6MjAwLCBhcy5pbnRlZ2VyKGkgLyAyKV0NCiAgfQ0KfQ0KDQptZWFucyA8LSBzYXBwbHkoZ3JvdXBzLCBtZWFuKQ0KDQpwYXIobWZyb3c9YygxLDMpKQ0KZm9yIChpIGluIHNlcSgxLCBsZW5ndGgobmFtZSkgLGJ5ID0gMikpew0KICBib3hwbG90KGdyb3Vwc1tpOihpICsgMSldLCBuYW1lcyA9IG5hbWVbaTooaSArIDEpXSwgZnJhbWUgPSBUUlVFLCBtYWluID0gIlN3aXNzIGJhbmsgbm90ZXMiLCBjZXguYXhpcz0wLjk5KQ0KICANCiAgZm9yIChqIGluIDA6MSkgew0KICAgIGxpbmVzKGMoaiArIDAuNiwgaiArIDEuNCksIHJlcChtZWFuc1tbbmFtZVsoaSArIGopXV1dLCAyKSwgbHR5ID0gImRvdHRlZCIsIGx3ZCA9IDEuMikNCiAgfQ0KfQ0KYGBgDQoNCiMgaGlzdG9ncmFtDQpgYGB7cn0NCmRhdGFfR2VudWluZSA9IGRhdGFbMToxMDAsIDE6Nl0gICANCmRhdGFfQ291bnRlcmZlaXQgPSBkYXRhWzEwMToyMDAsIDE6Nl0NCg0KZm9yIChpIGluIDE6Nikgew0KICBicmVha3Nfc2VxID0gc2VxKG1pbihkYXRhWywgaV0pLCBtYXgoZGF0YVssIGldKSwgYnkgPSAwLjEpDQogIG1pbl94ID0gZmxvb3IobWluKGRhdGFbLCBpXSkpDQogIG1heF94ID0gY2VpbGluZyhtYXgoZGF0YVssIGldKSkNCg0KICBoaXN0KGRhdGFfR2VudWluZVssIGldLA0KICAgICAgIGJyZWFrcyA9IGJyZWFrc19zZXEsDQogICAgICAgY29sID0gcmdiKDAsIDAsIDEsIDAuNSksICAgDQogICAgICAgYm9yZGVyID0gImJsdWUiLA0KICAgICAgIHhsaW0gPSBjKG1pbl94LCBtYXhfeCksDQogICAgICAgeWxpbSA9IGMoMCwgbWF4KHRhYmxlKGN1dChkYXRhWywgaV0sIGJyZWFrc19zZXEpKSkgKyA1KSwNCiAgICAgICBtYWluID0gIkhpc3RvZ3JhbSIsDQogICAgICAgeGxhYiA9IHBhc3RlMCgiWCIsIGkpLA0KICAgICAgIGF4ZXMgPSBGQUxTRSkgDQogIA0KICBoaXN0KGRhdGFfQ291bnRlcmZlaXRbLCBpXSwNCiAgICAgICBicmVha3MgPSBicmVha3Nfc2VxLA0KICAgICAgIGNvbCA9IHJnYigxLCAwLCAwLCAwLjUpLCAgIA0KICAgICAgIGJvcmRlciA9ICJyZWQiLA0KICAgICAgIGFkZCA9IFRSVUUpDQogIA0KICBheGlzKHNpZGUgPSAxLCBhdCA9IHNlcShtaW5feCwgbWF4X3gsIGJ5ID0gMSkpDQogIGF4aXMoc2lkZSA9IDIpDQogIA0KICBsZWdlbmQoInRvcHJpZ2h0IiwNCiAgICAgICAgIGxlZ2VuZCA9IGMoIkdlbnVpbmUiLCAiQ291bnRlcmZlaXQiLCAiT3ZlcmxhcCIpLA0KICAgICAgICAgZmlsbCA9IGMocmdiKDAsIDAsIDEsIDAuNSksIHJnYigxLCAwLCAwLCAwLjUpLCByZ2IoMC42LCAwLCAwLjQsIDAuNykpLA0KICAgICAgICAgYm9yZGVyID0gYygiYmx1ZSIsICJyZWQiLCAicHVycGxlIikpDQp9DQpgYGANCg0KIyBvdmVybGFwIA0KYGBge3J9DQpkYXRhX0dlbnVpbmUgPSBkYXRhW2RhdGEkZ2VudWluZSA9PSAxLCAxOjZdDQpkYXRhX0NvdW50ZXJmZWl0ID0gZGF0YVtkYXRhJGdlbnVpbmUgPT0gMCwgMTo2XQ0KDQpvdmVybGFwX3JhdGlvcyA9IG51bWVyaWMoNikNCg0KcGFyKG1mcm93ID0gYygxLCAxKSkNCm51bV92YXJzID0gbmNvbChkYXRhX0dlbnVpbmUpDQoNCmZvciAoaSBpbiAxOm51bV92YXJzKSB7DQoNCiAgYnJlYWtzX3NlcSA9IHNlcShtaW4oZGF0YVssIGldKSwgbWF4KGRhdGFbLCBpXSksIGJ5ID0gMC4xKQ0KDQogIG1pbl94ID0gZmxvb3IobWluKGRhdGFbLCBpXSkpDQogIG1heF94ID0gY2VpbGluZyhtYXgoZGF0YVssIGldKSkNCg0KICBoaXN0X0dlbnVpbmUgPSBoaXN0KGRhdGFfR2VudWluZVssIGldLCBicmVha3MgPSBicmVha3Nfc2VxLCBwbG90ID0gRkFMU0UpDQogIGhpc3RfQ291bnRlcmZlaXQgPSBoaXN0KGRhdGFfQ291bnRlcmZlaXRbLCBpXSwgYnJlYWtzID0gYnJlYWtzX3NlcSwgcGxvdCA9IEZBTFNFKQ0KDQogIG92ZXJsYXBfY291bnRzID0gcG1pbihoaXN0X0dlbnVpbmUkY291bnRzLCBoaXN0X0NvdW50ZXJmZWl0JGNvdW50cykNCg0KICBvdmVybGFwX3JhdGlvID0gc3VtKG92ZXJsYXBfY291bnRzKSAvIHN1bShoaXN0X0NvdW50ZXJmZWl0JGNvdW50cykNCiAgb3ZlcmxhcF9yYXRpb3NbaV0gPSBvdmVybGFwX3JhdGlvDQoNCiAgcGxvdF90aXRsZSA9IHBhc3RlMCgiSGlzdG9ncmFtIG9mICIsIGNvbG5hbWVzKGRhdGEpW2ldLCAiIChPdmVybGFwOiAiLCByb3VuZChvdmVybGFwX3JhdGlvICogMTAwLCAyKSwgIiUpIikNCg0KICBoaXN0KGRhdGFfR2VudWluZVssIGldLA0KICAgICAgIGJyZWFrcyA9IGJyZWFrc19zZXEsDQogICAgICAgY29sID0gcmdiKDAsIDAsIDEsIDAuNSksDQogICAgICAgYm9yZGVyID0gImJsdWUiLA0KICAgICAgIHhsaW0gPSBjKG1pbl94LCBtYXhfeCksDQogICAgICAgeWxpbSA9IGMoMCwgbWF4KGMoaGlzdF9HZW51aW5lJGNvdW50cywgaGlzdF9Db3VudGVyZmVpdCRjb3VudHMpKSArIDUpLA0KICAgICAgIG1haW4gPSBwbG90X3RpdGxlLCAgICAgICAgICAgDQogICAgICAgeGxhYiA9IGNvbG5hbWVzKGRhdGEpW2ldLA0KICAgICAgIGF4ZXMgPSBGQUxTRSkNCg0KICBoaXN0KGRhdGFfQ291bnRlcmZlaXRbLCBpXSwNCiAgICAgICBicmVha3MgPSBicmVha3Nfc2VxLA0KICAgICAgIGNvbCA9IHJnYigxLCAwLCAwLCAwLjUpLA0KICAgICAgIGJvcmRlciA9ICJyZWQiLA0KICAgICAgIGFkZCA9IFRSVUUpDQoNCiAgYXhpcyhzaWRlID0gMSwgYXQgPSBzZXEobWluX3gsIG1heF94LCBieSA9IDEpKQ0KICBheGlzKHNpZGUgPSAyKQ0KDQogIGxlZ2VuZCgidG9wcmlnaHQiLA0KICAgICAgICAgbGVnZW5kID0gYygiR2VudWluZSIsICJDb3VudGVyZmVpdCIsICJPdmVybGFwIiksDQogICAgICAgICBmaWxsID0gYyhyZ2IoMCwgMCwgMSwgMC41KSwgcmdiKDEsIDAsIDAsIDAuNSksIHJnYigwLjYsIDAsIDAuNCwgMC43KSksDQogICAgICAgICBib3JkZXIgPSBjKCJibHVlIiwgInJlZCIsICJwdXJwbGUiKSkNCn0NCmBgYA0KDQoNCg0KIyBrZXJuZWwgZGVuc2l0eQ0KYGBge3J9DQpsaWJyYXJ5KEtlcm5TbW9vdGgpDQoNCmZvciAoaSBpbiAxOjYpIHsNCiAgDQogIGRhdGFfR2VudWluZSA9IGRhdGFbMToxMDAsIGldICAgDQogIGRhdGFfQ291bnRlcmZlaXQgPSBkYXRhWzEwMToyMDAsIGldICANCg0KICBmaDEgPSBia2RlKGRhdGFfR2VudWluZSwga2VybmVsID0gImJpd2VpZ2h0IikgIA0KICBmaDIgPSBia2RlKGRhdGFfQ291bnRlcmZlaXQsIGtlcm5lbCA9ICJiaXdlaWdodCIpICANCg0KICB4X21pbiA9IGZsb29yKG1pbihjKGZoMSR4LCBmaDIkeCkpKQ0KICB4X21heCA9IGNlaWxpbmcobWF4KGMoZmgxJHgsIGZoMiR4KSkpDQogIHlfbWF4ID0gbWF4KGMoZmgxJHksIGZoMiR5KSkgKiAxLjENCg0KICB4X3RpY2tzIDwtIHNlcSh4X21pbiwgeF9tYXgsIGJ5ID0gMSkNCg0KICBwbG90KGZoMSwgdHlwZSA9ICJsIiwgbHdkID0gMiwNCiAgICAgICB4bGFiID0gIkNvdW50ZXJmZWl0IC8gR2VudWluZSIsIA0KICAgICAgIHlsYWIgPSBwYXN0ZTAoIkRlbnNpdHkgZXN0aW1hdGVzIGZvciBYIiwgaSwgc2VwID0gIiIpLA0KICAgICAgIGNvbCA9ICJibHVlIiwgbWFpbiA9IHBhc3RlMCgiU3dpc3MgYmFuayBub3RlcyAtIFgiLCBpKSwNCiAgICAgICB4bGltID0gYyh4X21pbiwgeF9tYXgpLCB5bGltID0gYygwLCB5X21heCksDQogICAgICAgYXhlcyA9IEZBTFNFKSANCiAgDQogIGxpbmVzKGZoMiwgbHR5ID0gImRvdHRlZCIsIGx3ZCA9IDIsIGNvbCA9ICJyZWQiKQ0KDQogIGF4aXMoc2lkZSA9IDEsIGF0ID0geF90aWNrcywgbGFiZWxzID0geF90aWNrcykNCiAgYXhpcyhzaWRlID0gMikNCiAgYm94KCkNCg0KICBsZWdlbmQoInRvcHJpZ2h0IiwNCiAgICAgICAgIGxlZ2VuZCA9IGMoIkdlbnVpbmUiLCAiQ291bnRlcmZlaXQiKSwNCiAgICAgICAgIGNvbCA9IGMoImJsdWUiLCAicmVkIiksDQogICAgICAgICBsdHkgPSBjKCJzb2xpZCIsICJkb3R0ZWQiKSwNCiAgICAgICAgIGx3ZCA9IDIsDQogICAgICAgICBjZXggPSAwLjgpDQp9DQpgYGANCg0KDQoNCg0KIyAzRCBzY2F0dGVycGxvdA0KYGBge3J9DQpsaWJyYXJ5KGxhdHRpY2UpDQpsaWJyYXJ5KGdyaWQpDQoNCmdyb3VwcyA9IGRhdGFbLCA3XQ0KDQpwY2hfdHlwZXMgPSBjKDEsIDEpICAgICAgICAgICAgICANCmNvbG9ycyA9IGMoImJsdWUiLCAicmVkIikgICAgICAgIA0KcGNoX2dyb3VwID0gcGNoX3R5cGVzW2lmZWxzZShncm91cHMgPT0gMCwgMSwgMildDQpjb2xfZ3JvdXAgPSBjb2xvcnNbaWZlbHNlKGdyb3VwcyA9PSAwLCAxLCAyKV0NCg0KY29tYm9zID0gY29tYm4oMTo2LCAzKQ0KDQpuX3Jvd3MgPSAxICAgICAgICANCm5fY29scyA9IDEgICAgICAgIA0KcGxvdHNfcGVyX3BhZ2UgPSBuX3Jvd3MgKiBuX2NvbHMgICANCg0KZm9yIChwYWdlIGluIHNlcSgxLCBuY29sKGNvbWJvcyksIGJ5ID0gcGxvdHNfcGVyX3BhZ2UpKSB7DQogIGdyaWQubmV3cGFnZSgpDQoNCiAgZm9yIChpIGluIDA6KHBsb3RzX3Blcl9wYWdlIC0gMSkpIHsNCiAgICBpbmRleCA9IHBhZ2UgKyBpDQogICAgaWYgKGluZGV4ID4gbmNvbChjb21ib3MpKSBicmVhaw0KDQogICAgeF9uYW1lID0gcGFzdGUwKCJYIiwgY29tYm9zWzEsIGluZGV4XSkNCiAgICB5X25hbWUgPSBwYXN0ZTAoIlgiLCBjb21ib3NbMiwgaW5kZXhdKQ0KICAgIHpfbmFtZSA9IHBhc3RlMCgiWCIsIGNvbWJvc1szLCBpbmRleF0pDQoNCiAgICB4X2RhdGEgPSBkYXRhW1t4X25hbWVdXQ0KICAgIHlfZGF0YSA9IGRhdGFbW3lfbmFtZV1dDQogICAgel9kYXRhID0gZGF0YVtbel9uYW1lXV0NCg0KICAgIHAgPSBjbG91ZCh6X2RhdGEgfiB4X2RhdGEgKiB5X2RhdGEsDQogICAgICAgICAgcGNoID0gcGNoX2dyb3VwLA0KICAgICAgICAgIGNvbCA9IGNvbF9ncm91cCwNCiAgICAgICAgICBjZXggPSAxLjIsDQogICAgICAgICAgdGlja3R5cGUgPSAiZGV0YWlsZWQiLA0KICAgICAgICAgIG1haW4gPSBwYXN0ZSgiU3dpc3MgYmFuayBub3RlczoiLCB4X25hbWUsIHlfbmFtZSwgel9uYW1lKSwNCiAgICAgICAgICBzY3JlZW4gPSBsaXN0KHogPSAtOTAsIHggPSAtOTAsIHkgPSA0NSksDQogICAgICAgICAgc2NhbGVzID0gbGlzdChhcnJvd3MgPSBGQUxTRSwgY29sID0gImJsYWNrIiwgZGlzdGFuY2UgPSAxLCBjZXggPSAwLjUpLA0KICAgICAgICAgIHhsYWIgPSBsaXN0KHhfbmFtZSwgcm90ID0gLTEwLCBjZXggPSAxLjIpLA0KICAgICAgICAgIHlsYWIgPSBsaXN0KHlfbmFtZSwgcm90ID0gMTAsIGNleCA9IDEuMiksDQogICAgICAgICAgemxhYiA9IGxpc3Qoel9uYW1lLCByb3QgPSA5MCwgY2V4ID0gMS4xKSwNCg0KICAgICAgICAgIHBhci5zZXR0aW5ncyA9IGxpc3QoDQogICAgICAgICAgICBheGlzLmxpbmUgPSBsaXN0KGNvbCA9ICJibGFjayIpLCAgICANCiAgICAgICAgICAgIGJveC4zZCA9IGxpc3QoDQogICAgICAgICAgICAgIGNvbCA9IGMoDQogICAgICAgICAgICAgICAgImJsYWNrIiwgInRyYW5zcGFyZW50IiwgInRyYW5zcGFyZW50IiwiYmxhY2siLCAiYmxhY2siLCAidHJhbnNwYXJlbnQiLCAidHJhbnNwYXJlbnQiLCAidHJhbnNwYXJlbnQiLCAgIA0KICAgICAgICAgICAgICAgICJ0cmFuc3BhcmVudCIsICJ0cmFuc3BhcmVudCIsICJ0cmFuc3BhcmVudCIsICJ0cmFuc3BhcmVudCIpKSksDQoNCiAgICAgICAgICBrZXkgPSBsaXN0KA0KICAgICAgICAgICAgc3BhY2UgPSAicmlnaHQiLA0KICAgICAgICAgICAgcG9pbnRzID0gbGlzdChwY2ggPSBwY2hfdHlwZXMsIGNvbCA9IGNvbG9ycywgY2V4ID0gMS41KSwNCiAgICAgICAgICAgIHRleHQgPSBsaXN0KGMoIkdlbnVpbmUiLCAiQ291bnRlcmZlaXQiKSksDQogICAgICAgICAgICBib3JkZXIgPSBGQUxTRQ0KICAgICAgICAgICkNCiAgICAgICAgKQ0KDQogICAgcHJpbnQocCwgc3BsaXQgPSBjKChpICUlIG5fY29scykgKyAxLCAoaSAlLyUgbl9jb2xzKSArIDEsIG5fY29scywgbl9yb3dzKSwgbW9yZSA9IFRSVUUpDQogIH0NCn0NCmBgYA0KDQojIDNkIHBsb3RseSBzY2F0dGVyIHBsb3QNCg0KYGBge3J9DQpsaWJyYXJ5KHBsb3RseSkNCmxpYnJhcnkoaHRtbHdpZGdldHMpDQoNCiMg5YGH6Kit5L2g55qEIGRhdGEg5piv5LiA5YCLIGRhdGEuZnJhbWXvvIzljIXlkKsgWDEsIFgyLCBYNiDlkowgZ3JvdXBzIOWIlw0KDQojIOWPluWHuiBYMSwgWDIg5ZKMIFg2IOiuiuaVuA0KeF9kYXRhID0gZGF0YSRYMQ0KeV9kYXRhID0gZGF0YSRYMg0Kel9kYXRhID0gZGF0YSRYNg0KZ3JvdXBzID0gZGF0YVssIDddDQoNCiMg5bCHIGdyb3VwcyDovYnmj5vngrrlm6DlrZDvvIzpgJnmqKPmiJHlgJHlj6/ku6XnlKjpoY/oibLljYDliIYNCmdyb3VwcyA9IGZhY3Rvcihncm91cHMsIGxldmVscyA9IGMoMCwgMSksIGxhYmVscyA9IGMoIkdlbnVpbmUiLCAiQ291bnRlcmZlaXQiKSkNCg0KIyDoqIjnrpcgeCwgeSwgeiDou7jnmoTnr4TlnI0NCnhfcmFuZ2UgPC0gcmFuZ2UoeF9kYXRhKQ0KeV9yYW5nZSA8LSByYW5nZSh5X2RhdGEpDQp6X3JhbmdlIDwtIHJhbmdlKHpfZGF0YSkNCg0KIyDlsIcgeCwgeSwgeiDou7jmlbjmk5rnuK7mlL7liLAgWzAsIDFdIOevhOWcjQ0KeF9zY2FsZWQgPSAoeF9kYXRhIC0geF9yYW5nZVsxXSkgLyAoeF9yYW5nZVsyXSAtIHhfcmFuZ2VbMV0pDQp5X3NjYWxlZCA9ICh5X2RhdGEgLSB5X3JhbmdlWzFdKSAvICh5X3JhbmdlWzJdIC0geV9yYW5nZVsxXSkNCnpfc2NhbGVkID0gKHpfZGF0YSAtIHpfcmFuZ2VbMV0pIC8gKHpfcmFuZ2VbMl0gLSB6X3JhbmdlWzFdKQ0KDQojIOioiOeul+WIu+W6pum7nu+8jOS4puWwh+WFtuWbm+aNqOS6lOWFpeWIsOWwj+aVuOm7nuW+jCAxIOS9jQ0KdGlja19wb3NpdGlvbnMgPSBzZXEoMCwgMSwgbGVuZ3RoLm91dCA9IDUpDQp0aWNrX2xhYmVsc194ID0gcm91bmQoc2VxKHhfcmFuZ2VbMV0sIHhfcmFuZ2VbMl0sIGxlbmd0aC5vdXQgPSA1KSwgMSkNCnRpY2tfbGFiZWxzX3kgPSByb3VuZChzZXEoeV9yYW5nZVsxXSwgeV9yYW5nZVsyXSwgbGVuZ3RoLm91dCA9IDUpLCAxKQ0KdGlja19sYWJlbHNfeiA9IHJvdW5kKHNlcSh6X3JhbmdlWzFdLCB6X3JhbmdlWzJdLCBsZW5ndGgub3V0ID0gNSksIDEpDQoNCiMg5q+P5YCL6bue55qEIGhvdmVyIOaomeexpO+8jOmhr+ekuuWOn+Wni+aVuOaTmuWAvA0KaG92ZXJfdGV4dCA8LSBwYXN0ZTAoDQogICJYMTogIiwgcm91bmQoeF9kYXRhLCAxKSwgIjxicj4iLA0KICAiWDI6ICIsIHJvdW5kKHlfZGF0YSwgMSksICI8YnI+IiwNCiAgIlg2OiAiLCByb3VuZCh6X2RhdGEsIDEpDQopDQoNCiMg5L2/55SoIHBsb3RseSDnlJ/miJAgM0Qg5pWj6bue5ZyWDQpmaWcgPC0gcGxvdF9seSgNCiAgeCA9IHhfc2NhbGVkLA0KICB5ID0geV9zY2FsZWQsDQogIHogPSB6X3NjYWxlZCwNCiAgY29sb3IgPSBncm91cHMsDQogIGNvbG9ycyA9IGMoImJsdWUiLCAicmVkIiksDQogIHR5cGUgPSAic2NhdHRlcjNkIiwNCiAgbW9kZSA9ICJtYXJrZXJzIiwNCiAgbWFya2VyID0gbGlzdChzaXplID0gNSksDQogIHRleHQgPSBob3Zlcl90ZXh0LCAgIyDoqK3lrpogaG92ZXIg55qE5paH5a2XDQogIGhvdmVyaW5mbyA9ICJ0ZXh0IiAgIyDpoa/npLogdGV4dCDogIzkuI3mmK/nuK7mlL7lvoznmoTluqfmqJkNCikNCg0KIyDmt7vliqDmqJnpoYzlkozmqJnnsaTvvIzkuKboqK3lrprou7jnr4TlnI3oiIfmqJnnsaQNCmZpZyA8LSBmaWcgJT4lIGxheW91dCgNCiAgdGl0bGUgPSAiM0QgU2NhdHRlciBQbG90IG9mIFgxLCBYMiwgWDYiLA0KICBzY2VuZSA9IGxpc3QoDQogICAgeGF4aXMgPSBsaXN0KHRpdGxlID0gIlgxIiwgcmFuZ2UgPSBjKDAsIDEpLCB0aWNrdmFscyA9IHRpY2tfcG9zaXRpb25zLCB0aWNrdGV4dCA9IHRpY2tfbGFiZWxzX3gpLA0KICAgIHlheGlzID0gbGlzdCh0aXRsZSA9ICJYMiIsIHJhbmdlID0gYygwLCAxKSwgdGlja3ZhbHMgPSB0aWNrX3Bvc2l0aW9ucywgdGlja3RleHQgPSB0aWNrX2xhYmVsc195KSwNCiAgICB6YXhpcyA9IGxpc3QodGl0bGUgPSAiWDYiLCByYW5nZSA9IGMoMCwgMSksIHRpY2t2YWxzID0gdGlja19wb3NpdGlvbnMsIHRpY2t0ZXh0ID0gdGlja19sYWJlbHNfeikNCiAgKQ0KKQ0KDQojIOi8uOWHuueCuiBIVE1MIOaqlOahiA0Kc2F2ZVdpZGdldChmaWcsICIzZF9zY2F0dGVyX3Bsb3QuaHRtbCIpDQoNCmBgYA0KDQoNCg0KDQoNCiMgQW5kcmV3cycgQ3VydmVzDQpgYGB7cn0NCmxpYnJhcnkodG91cnIpDQoNCiMgeO+8muWFqOmDqCAyMDAg562G6LOH5paZDQp4ID0gZGF0YVsxOjIwMCwgXQ0KDQojIOWIneWni+WMliB577yI5YGaIHplcm8tb25lIHNjYWxpbmfvvIkNCnkgPSBOVUxMDQoNCmZvciAoaSBpbiAxOjYpIHsNCiAgeiA9ICh4WywgaV0gLSBtaW4oeFssIGldKSkgLyAobWF4KHhbLCBpXSkgLSBtaW4oeFssIGldKSkgICMgemVyby1vbmUgc2NhbGluZw0KICB5ID0gY2JpbmQoeSwgeikNCn0NCg0KIyDlrprnvqnpoZ7liKUNClR5cGUgPSBkYXRhWywgN10gICMg5YmNIDEwMCDnrYbngrogVHlwZSAx77yM5b6MIDEwMCDnrYbngrogVHlwZSAyDQpmID0gYXMuaW50ZWdlcihUeXBlKQ0KDQojIOWumue+qSB0IOi7uOevhOWcjQ0KZ3JpZCA9IHNlcSgwLCAyICogcGksIGxlbmd0aCA9IDEwMDApDQoNCiMg5Yid5aeL5YyWIHBsb3TvvIznlavnrKzkuIDnrYbmm7Lnt5oNCnBsb3QoZ3JpZCwgDQogICAgIGFuZHJld3MoeVsxLCBdKShncmlkKSwgDQogICAgIHR5cGUgPSAibCIsIA0KICAgICBsd2QgPSAxLjIsIA0KICAgICBtYWluID0gIkFuZHJld3MnIGN1cnZlcyAoQWxsIEJhbmsgZGF0YSkiLCANCiAgICAgYXhlcyA9IEZBTFNFLCANCiAgICAgZnJhbWUgPSBUUlVFLCANCiAgICAgeWxpbSA9IGMoLTAuNSwgMC42KSwgDQogICAgIHlsYWIgPSAiIiwgDQogICAgIHhsYWIgPSAiIiwgDQogICAgIGNvbCA9IGlmZWxzZShmWzFdID09IDEsICJibHVlIiwgInJlZCIpLCANCiAgICAgbHR5ID0gaWZlbHNlKGZbMV0gPT0gMSwgInNvbGlkIiwgImRvdHRlZCIpKQ0KDQojIOe5quijveWJqemkmOabsue3mg0KZm9yIChpIGluIDI6MjAwKSB7DQogIGxpbmVzKGdyaWQsIA0KICAgICAgICBhbmRyZXdzKHlbaSwgXSkoZ3JpZCksIA0KICAgICAgICBjb2wgPSBpZmVsc2UoZltpXSA9PSAxLCAiYmx1ZSIsICJyZWQiKSwgDQogICAgICAgIGx3ZCA9IDEuMiwgDQogICAgICAgIGx0eSA9IGlmZWxzZShmW2ldID09IDEsICJzb2xpZCIsICJkb3R0ZWQiKSkNCn0NCg0KIyDlop7liqDluqfmqJnou7gNCmF4aXMoc2lkZSA9IDIsIGF0ID0gc2VxKC0wLjUsIDAuNiwgMC4yKSwgbGFiZWxzID0gc2VxKC0wLjUsIDAuNiwgMC4yKSkNCmF4aXMoc2lkZSA9IDEsIGF0ID0gc2VxKDAsIDcsIDEpLCBsYWJlbHMgPSBzZXEoMCwgNywgMSkpDQoNCiMg5Yqg5LiK5ZyW5L6LDQpsZWdlbmQoInRvcHJpZ2h0IiwgDQogICAgICAgbGVnZW5kID0gYygiR2VudWluZSIsICJDb3VudGVyZmVpdCIpLCANCiAgICAgICBjb2wgPSBjKCJibHVlIiwgInJlZCIpLCANCiAgICAgICBsd2QgPSAxLjUsIA0KICAgICAgIGx0eSA9IGMoInNvbGlkIiwgImRvdHRlZCIpKQ0KYGBgDQoNCg0KI0xvZ2lzdGljIFJlZ3Jlc3Npb24NCmBgYHtyfQ0KbGlicmFyeShjYVRvb2xzKQ0KbGlicmFyeShST0NSKQ0KDQojIHN1bW1hcnkNCnNldC5zZWVkKDEyMzQ1NikNCnNwbGl0ID0gc2FtcGxlLnNwbGl0KGRhdGEsU3BsaXRSYXRpbyA9IDAuNykNCnRyYWluX3JlZyA9IHN1YnNldChkYXRhLCBzcGxpdCA9PSdUUlVFJykNCnRlc3RfcmVnID0gc3Vic2V0KGRhdGEsIHNwbGl0ID09ICdGQUxTRScpDQoNCmxvZ2lzdGljX21vZGVsID0gZ2xtKGZhY3RvcihnZW51aW5lKSB+IFgxK1gyK1gzK1g0K1g1K1g2LCBkYXRhID0gdHJhaW5fcmVnLA0KICAgICAgICAgICAgICAgICAgICAgZmFtaWx5ID0gYmlub21pYWwobGluayA9ICdsb2dpdCcpLCANCiAgICAgICAgICAgICAgICAgICAgIGNvbnRyb2wgPSBsaXN0KG1heGl0PTEwMDApKQ0KDQpsb2dpc3RpY19tb2RlbA0Kc3VtbWFyeShsb2dpc3RpY19tb2RlbCkNCmBgYA0KDQpgYGB7cn0NCnByZWRpY3RfcmVnID0gcHJlZGljdChsb2dpc3RpY19tb2RlbCx0ZXN0X3JlZyx0eXBlPSdyZXNwb25zZScpDQpwcmVkaWN0X3JlZw0KYGBgDQoNCmBgYHtyfQ0KcHJlZGljdF9yZWcgPSBpZmVsc2UocHJlZGljdF9yZWcgPjAuNSwxLDApDQp0YWJsZSh0ZXN0X3JlZyRnZW51aW5lLHByZWRpY3RfcmVnKQ0KbWlzc2luZ19jbGFzc2VyciA9IG1lYW4ocHJlZGljdF9yZWchPXRlc3RfcmVnJGdlbnVpbmUpDQpwcmludChwYXN0ZSgnQWNjdXJhY3k9JywxLW1pc3NpbmdfY2xhc3NlcnIpKQ0KDQpST0NQcmVkID0gcHJlZGljdGlvbihwcmVkaWN0X3JlZywgdGVzdF9yZWckZ2VudWluZSkNClJPQ1BlciA9IHBlcmZvcm1hbmNlKFJPQ1ByZWQsIG1lYXN1cmUgPSAndHByJyx4Lm1lYXN1cmUgPSAnZnByJykNCg0KYXVjID0gcGVyZm9ybWFuY2UoUk9DUHJlZCxtZWFzdXJlPSdhdWMnKQ0KYXVjID0gYXVjQHkudmFsdWVzW1sxXV0NCmF1Yw0KDQpwbG90KFJPQ1BlcikNCnBsb3QoUk9DUGVyLGNvbG91cmlzZT1UUlVFLHByaW50LmN1dHRvZmZzLmF0PXNlcSgwLjEsYnk9MC4xKSwgY29sPTIsIGx3ZD0yLA0KICAgICBtYWluPSdST0MgQ3VydmUnKQ0KYWJsaW5lKGE9MCxiPTEpDQphdWMgPSByb3VuZChhdWMsNCkNCmxlZ2VuZCguNiwuNCwgYXVjLHRpdGxlID0gJ0FVQycsY2V4PTEpDQpgYGANCg0KDQoNCg0KIyBSYW5kb20gRm9yZXN0DQpgYGB7cn0NCmxpYnJhcnkocmFuZG9tRm9yZXN0KQ0KDQojIHN1bW1hcnkNCnNldC5zZWVkKDEyMzQ1KQ0KZGF0YSRnZW51aW5lID0gYXMuZmFjdG9yKGRhdGEkZ2VudWluZSkNCnNwbGl0ID0gc2FtcGxlLnNwbGl0KGRhdGEsU3BsaXRSYXRpbyA9IDAuNikNCnRyYWluX3JmID0gc3Vic2V0KGRhdGEsIHNwbGl0ID09J1RSVUUnKQ0KdGVzdF9yZiA9IHN1YnNldChkYXRhLCBzcGxpdCA9PSAnRkFMU0UnKQ0KcmYgPSByYW5kb21Gb3Jlc3QoZ2VudWluZX4gWDErWDIrWDMrWDQrWDUrWDYsZGF0YSA9dHJhaW5fcmYsbnRyZWU9NTAwKQ0KcHJpbnQocmYpDQpgYGANCg0KYGBge3J9DQpzZXQuc2VlZCgxMjM0NSkNCmltcG9ydGFuY2UocmYpDQp2YXJJbXBQbG90KHJmKQ0KYGBgDQoNCmBgYHtyfQ0KcHJlZDEgPSBwcmVkaWN0KHJmLHRlc3RfcmYsIHR5cGU9J3Byb2InKQ0KcGVyZiA9IHByZWRpY3Rpb24ocHJlZDFbLDJdLCB0ZXN0X3JmJGdlbnVpbmUpDQphdWMgPSBwZXJmb3JtYW5jZShwZXJmLCAnYXVjJykNCmF1Y192YWx1ZSA9IGF1Y0B5LnZhbHVlc1tbMV1dICAjIEV4dHJhY3QgdGhlIG51bWVyaWMgQVVDIHZhbHVlDQoNCnByZWQzID0gcGVyZm9ybWFuY2UocGVyZiwgJ3RwcicsICdmcHInKQ0KDQojIFBsb3QgdGhlIFJPQyBjdXJ2ZQ0KcGxvdChwcmVkMywgbWFpbj0iUk9DIEN1cnZlIGZvciBSYW5kb20gRm9yZXN0IiwgY29sPTIsIGx3ZD0yKQ0KYWJsaW5lKGE9MCwgYj0xLCBsd2Q9MiwgbHR5PTIsIGNvbD0nZ3JheScpDQoNCiMgQWRkIEFVQyB2YWx1ZSB0byB0aGUgcGxvdA0KdGV4dCgwLjYsIDAuMiwgcGFzdGUoIkFVQyA9Iiwgcm91bmQoYXVjX3ZhbHVlLCAzKSksIGNvbD0yLCBjZXg9MS4yKQ0KYGBgDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQo=