Winrate

winrate_org <- read.csv('../../results/win_data/comb_winrate.csv')
year_range_org <- winrate_org$X
winrate <- subset(winrate_org, select=-c(X))
winrate <- winrate[2:nrow(winrate),]

Threepointer percentage data

percent3p_org <- read.csv('../../results/team_data/comb_3pper.csv')
percent3p <- percent3p_org[2:nrow(percent3p_org),]
year_range <- percent3p$X
percent3p <- subset(percent3p, select=-c(X))

Threepointer attempts data

attempts3p_org <- read.csv('../../results/team_data/comb_3pattempt.csv')
attempts3p <- subset(attempts3p_org, select=-c(X))
attempts3p <- attempts3p[2:nrow(attempts3p),]

Threepointer made data

made3p_org <- read.csv('../../results/team_data/comb_3pmade.csv')
made3p <- subset(made3p_org, select=-c(X))
made3p <- made3p[2:nrow(made3p),]

Misc data

eff_fg_per <- read.csv('../../results/misc_data/comb_Effective_Field_Goal_Percentage.csv')
ft_per_fg <- read.csv('../../results/misc_data/comb_Free_Throws_Per_Field_Goal_Attempt.csv')
off_reb <- read.csv('../../results/misc_data/comb_Offensive_Rebound_Percentage.csv')
turn_per <- read.csv('../../results/misc_data/comb_Turnover_Percentage.csv')
year_range <- eff_fg_per$X
eff_fg_per <- subset(eff_fg_per, select=-c(X))
ft_per_fg <- subset(ft_per_fg, select=-c(X))
off_reb <- subset(off_reb, select=-c(X))
turn_per <- subset(turn_per, select=-c(X))

Correlation between winrate and threepoint percentage in the last decades

ind_vars <- list("winrate"=winrate, "ft_per_fg"=ft_per_fg, "eff_fg_per"=eff_fg_per, "off_reb"=off_reb, "turn_per"=turn_per, "percent3p"=percent3p, "made3p"=made3p, "attempts3p"=attempts3p)
start_yr <- 2011
end_yr <- 2016
lm_df <- data.frame(Year=rep(seq(start_yr, end_yr),30))
team_names <- colnames(ind_vars$winrate)
for (feat_name in names(ind_vars))
{
  temp <- c()
  curr_feat <- ind_vars[[feat_name]]
  yrs <- seq(match(start_yr, year_range), match(end_yr, year_range))
  for (yr_ind in yrs)
  {
    for (curr_team in team_names)
    {
      temp <- c(temp, curr_feat[[curr_team]][yr_ind])
    }
  }
  lm_df[feat_name] <- temp
}
xyplot(winrate ~ percent3p, data=lm_df, type=c("p","r"), groups=Year, auto.key=list(space="right", rows=length(unique(lm_df$Year)), title="Year"))

plot1 <- ggplot(data=lm_df, aes(x=percent3p, y=winrate, color=as.factor(Year), guide_legend(title = "Year"))) + geom_point(aes(group=Year), shape=21) + geom_smooth(aes(group=as.factor(Year)), method='lm', se=FALSE, inherit.aes = TRUE, lwd=0.5) + scale_color_discrete(name="Year") + ggtitle('Winrate vs. 3-pointer percentage')
plot2 <- ggplot(data=lm_df, aes(x=made3p, y=winrate, color=as.factor(Year), guide_legend(title = "Year"))) + geom_point(aes(group=Year), shape=21) + geom_smooth(aes(group=as.factor(Year)), method='lm', se=FALSE, inherit.aes = TRUE, lwd=0.5) + scale_color_discrete(name="Year") + ggtitle('Winrate vs. 3-pointer makes')
plot3 <- ggplot(data=lm_df, aes(x=attempts3p, y=winrate, color=as.factor(Year), guide_legend(title = "Year"))) + geom_point(aes(group=Year), shape=21) + geom_smooth(aes(group=as.factor(Year)), method='lm', se=FALSE, inherit.aes = TRUE, lwd=0.5) + scale_color_discrete(name="Year") + ggtitle('Winrate vs. 3-pointer attempts')
grid.arrange(plot1, plot2, plot3, ncol=2)

model1 <- lm(winrate ~ percent3p, data=lm_df)
plot(fitted(model1), residuals(model1))
model2 <- lm(winrate ~ percent3p*made3p, data=lm_df) # Colinearity remove
model3 <- lm(winrate ~ percent3p*made3p*attempts3p, data=lm_df)
# summary(model1)
# summary(model2)
# summary(model3)
# anova(model1, model2, model3)
model4 <- lm(winrate ~ eff_fg_per+ft_per_fg+off_reb+turn_per, data=lm_df)
summary(model4)

Call:
lm(formula = winrate ~ eff_fg_per + ft_per_fg + off_reb + turn_per, 
    data = lm_df)

Residuals:
     Min       1Q   Median       3Q      Max 
-0.22761 -0.07189  0.00061  0.05744  0.26287 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) -2.43993    0.24565   -9.93  < 2e-16 ***
eff_fg_per   6.06255    0.39428   15.38  < 2e-16 ***
ft_per_fg    0.62854    0.31156    2.02    0.045 *  
off_reb      0.01426    0.00300    4.75  4.2e-06 ***
turn_per    -0.04250    0.00833   -5.10  8.8e-07 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.0985 on 175 degrees of freedom
Multiple R-squared:  0.622, Adjusted R-squared:  0.613 
F-statistic:   72 on 4 and 175 DF,  p-value: <2e-16
model5 <- lm(winrate ~ ft_per_fg+off_reb+turn_per+percent3p, data=lm_df)
#cv.lm(model4, m=3, data = lm_df)
summary(model5)

Call:
lm(formula = winrate ~ ft_per_fg + off_reb + turn_per + percent3p, 
    data = lm_df)

Residuals:
    Min      1Q  Median      3Q     Max 
-0.3716 -0.0715  0.0012  0.0869  0.2353 

Coefficients:
            Estimate Std. Error t value Pr(>|t|)    
(Intercept) -1.16414    0.26158   -4.45  1.5e-05 ***
ft_per_fg    1.70574    0.37952    4.49  1.3e-05 ***
off_reb      0.00597    0.00361    1.65   0.1001    
turn_per    -0.03444    0.01045   -3.30   0.0012 ** 
percent3p    4.56375    0.48506    9.41  < 2e-16 ***
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.123 on 175 degrees of freedom
Multiple R-squared:  0.41,  Adjusted R-squared:  0.396 
F-statistic: 30.4 on 4 and 175 DF,  p-value: <2e-16
par(mfrow=c(2,2))

plot(model4)

anova(model1, model4, model5)
Analysis of Variance Table

Model 1: winrate ~ percent3p
Model 2: winrate ~ eff_fg_per + ft_per_fg + off_reb + turn_per
Model 3: winrate ~ ft_per_fg + off_reb + turn_per + percent3p
  Res.Df  RSS Df Sum of Sq    F Pr(>F)    
1    178 3.14                             
2    175 1.70  3     1.440 49.5 <2e-16 ***
3    175 2.65  0    -0.952                
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

ANNOVA

Random forest

#set.seed(415)
get_randforest <- function(start_yr, end_yr, ind_var1, ind_var2, ind_var3, ind_var4, ind_var5, ind_var6)
{
    winrate.list <- c()
    indvar1.list <- c()
    indvar2.list <- c()
    indvar3.list <- c()
    indvar4.list <- c()
    indvar5.list <- c()
    indvar6.list <- c()
    xx.list <- setNames(split(ind_var1, seq(nrow(ind_var1))), year_range)
    yy.list <- setNames(split(ind_var2, seq(nrow(ind_var2))), year_range)
    aa.list <- setNames(split(ind_var3, seq(nrow(ind_var3))), year_range)
    bb.list <- setNames(split(ind_var4, seq(nrow(ind_var4))), year_range)
    cc.list <- setNames(split(ind_var5, seq(nrow(ind_var5))), year_range)
    dd.list <- setNames(split(ind_var6, seq(nrow(ind_var6))), year_range)
    yz.list <- setNames(split(winrate, seq(nrow(winrate))), year_range)
    # Change the year range to get models for different time ranges
    yrs <- seq(match(start_yr, year_range), match(end_yr, year_range))
    for (yr_ind in yrs)
    {
        indvar1.list <- c(indvar1.list, as.numeric(xx.list[[yr_ind]]))
        indvar2.list <- c(indvar2.list, as.numeric(yy.list[[yr_ind]]))
        indvar3.list <- c(indvar3.list, as.numeric(aa.list[[yr_ind]]))
        indvar4.list <- c(indvar4.list, as.numeric(bb.list[[yr_ind]]))
        indvar5.list <- c(indvar5.list, as.numeric(cc.list[[yr_ind]]))
        indvar6.list <- c(indvar6.list, as.numeric(dd.list[[yr_ind]]))
        winrate.list <- c(winrate.list, as.numeric(yz.list[[yr_ind]]))
    }
    smp_size <- floor(0.8 * length(winrate.list))
    train_ind <- sample(seq_len(length(winrate.list)), size=smp_size)
    #Training set
    ft_per_fg <- indvar1.list[train_ind]
    eff_fg_per <- indvar1.list[train_ind]
    off_reb <- indvar1.list[train_ind]
    turn_per <- indvar1.list[train_ind]
    made3p <- indvar1.list[train_ind]
    percent3p <- indvar1.list[train_ind]
    winrate <- winrate.list[train_ind]
    train_data <- data.frame(ft_per_fg=ft_per_fg, eff_fg_per=eff_fg_per, off_reb=off_reb, turn_per=turn_per, made3p=made3p, percent3p=percent3p, winrate=winrate)
    # Testing set
    ft_per_fg <- indvar1.list[-train_ind]
    eff_fg_per <- indvar1.list[-train_ind]
    off_reb <- indvar1.list[-train_ind]
    turn_per <- indvar1.list[-train_ind]
    made3p <- indvar1.list[-train_ind]
    percent3p <- indvar1.list[-train_ind]
    winrate <- winrate.list[-train_ind]
    test_data <- data.frame(ft_per_fg=ft_per_fg, eff_fg_per=eff_fg_per, off_reb=off_reb, turn_per=turn_per, made3p=made3p, percent3p=percent3p, winrate=winrate)
    # Check if these are in order
    model <- randomForest(winrate~ft_per_fg+eff_fg_per+off_reb+turn_per+made3p+percent3p, data=train_data, importance=TRUE, ntree=3000)
    prediction <- predict(model, test_data, OOB=TRUE)
    pred_score = mean((test_data$winrate - prediction)^2)
    return(list('model'=model, 'train_data'=train_data, 'prediction'=pred_score))
}
forest_stuff <- get_randforest(2006, 2016, ft_per_fg, eff_fg_per, off_reb, turn_per, made3p, percent3p)
varImpPlot(forest_stuff$model)
print(forest_stuff$prediction)

Don’t really need to do CV since randomForest already partitions randomly

require(ggplot2)
rf <- randomForest(winrate ~ eff_fg_per+ft_per_fg+off_reb+turn_per+percent3p+made3p+attempts3p, data=lm_df, importance=TRUE, ntree=1000)
imp <- importance(rf)
imp = data.frame(type=rownames(imp), importance(rf), check.names=F)
imp$type = reorder(imp$type, imp$`%IncMSE`)
ggplot(data=imp, aes(x=type, y=`%IncMSE`)) + geom_bar(stat='identity', fill='black') + geom_hline(yintercept=abs(min(imp$`%IncMSE`)), col=2, linetype='dashed') + coord_flip()

LS0tCnRpdGxlOiAiVGhyZWUgcG9pbnRlciB3aW4tcmF0ZSBhbmFseXNpcyIKb3V0cHV0OgogIHBkZl9kb2N1bWVudDogZGVmYXVsdAogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKLS0tCgpgYGB7ciwgZWNobz1GQUxTRSwgbWVzc2FnZT1GQUxTRX0KbGlicmFyeShnZ3Bsb3QyKQpsaWJyYXJ5KHJhbmRvbUZvcmVzdCkKbGlicmFyeShsYXR0aWNlKQpsaWJyYXJ5KGdyaWRFeHRyYSkKbGlicmFyeShEQUFHKQpgYGAKKldpbnJhdGUqCmBgYHtyfQp3aW5yYXRlX29yZyA8LSByZWFkLmNzdignLi4vLi4vcmVzdWx0cy93aW5fZGF0YS9jb21iX3dpbnJhdGUuY3N2JykKeWVhcl9yYW5nZV9vcmcgPC0gd2lucmF0ZV9vcmckWAp3aW5yYXRlIDwtIHN1YnNldCh3aW5yYXRlX29yZywgc2VsZWN0PS1jKFgpKQp3aW5yYXRlIDwtIHdpbnJhdGVbMjpucm93KHdpbnJhdGUpLF0KYGBgCgoqVGhyZWVwb2ludGVyIHBlcmNlbnRhZ2UgZGF0YSoKYGBge3J9CnBlcmNlbnQzcF9vcmcgPC0gcmVhZC5jc3YoJy4uLy4uL3Jlc3VsdHMvdGVhbV9kYXRhL2NvbWJfM3BwZXIuY3N2JykKcGVyY2VudDNwIDwtIHBlcmNlbnQzcF9vcmdbMjpucm93KHBlcmNlbnQzcF9vcmcpLF0KeWVhcl9yYW5nZSA8LSBwZXJjZW50M3AkWApwZXJjZW50M3AgPC0gc3Vic2V0KHBlcmNlbnQzcCwgc2VsZWN0PS1jKFgpKQpgYGAKCipUaHJlZXBvaW50ZXIgYXR0ZW1wdHMgZGF0YSoKYGBge3J9CmF0dGVtcHRzM3Bfb3JnIDwtIHJlYWQuY3N2KCcuLi8uLi9yZXN1bHRzL3RlYW1fZGF0YS9jb21iXzNwYXR0ZW1wdC5jc3YnKQphdHRlbXB0czNwIDwtIHN1YnNldChhdHRlbXB0czNwX29yZywgc2VsZWN0PS1jKFgpKQphdHRlbXB0czNwIDwtIGF0dGVtcHRzM3BbMjpucm93KGF0dGVtcHRzM3ApLF0KYGBgCgoqVGhyZWVwb2ludGVyIG1hZGUgZGF0YSoKYGBge3J9Cm1hZGUzcF9vcmcgPC0gcmVhZC5jc3YoJy4uLy4uL3Jlc3VsdHMvdGVhbV9kYXRhL2NvbWJfM3BtYWRlLmNzdicpCm1hZGUzcCA8LSBzdWJzZXQobWFkZTNwX29yZywgc2VsZWN0PS1jKFgpKQptYWRlM3AgPC0gbWFkZTNwWzI6bnJvdyhtYWRlM3ApLF0KYGBgCgoqTWlzYyBkYXRhKgpgYGB7cn0KZWZmX2ZnX3BlciA8LSByZWFkLmNzdignLi4vLi4vcmVzdWx0cy9taXNjX2RhdGEvY29tYl9FZmZlY3RpdmVfRmllbGRfR29hbF9QZXJjZW50YWdlLmNzdicpCmZ0X3Blcl9mZyA8LSByZWFkLmNzdignLi4vLi4vcmVzdWx0cy9taXNjX2RhdGEvY29tYl9GcmVlX1Rocm93c19QZXJfRmllbGRfR29hbF9BdHRlbXB0LmNzdicpCm9mZl9yZWIgPC0gcmVhZC5jc3YoJy4uLy4uL3Jlc3VsdHMvbWlzY19kYXRhL2NvbWJfT2ZmZW5zaXZlX1JlYm91bmRfUGVyY2VudGFnZS5jc3YnKQp0dXJuX3BlciA8LSByZWFkLmNzdignLi4vLi4vcmVzdWx0cy9taXNjX2RhdGEvY29tYl9UdXJub3Zlcl9QZXJjZW50YWdlLmNzdicpCnllYXJfcmFuZ2UgPC0gZWZmX2ZnX3BlciRYCmVmZl9mZ19wZXIgPC0gc3Vic2V0KGVmZl9mZ19wZXIsIHNlbGVjdD0tYyhYKSkKZnRfcGVyX2ZnIDwtIHN1YnNldChmdF9wZXJfZmcsIHNlbGVjdD0tYyhYKSkKb2ZmX3JlYiA8LSBzdWJzZXQob2ZmX3JlYiwgc2VsZWN0PS1jKFgpKQp0dXJuX3BlciA8LSBzdWJzZXQodHVybl9wZXIsIHNlbGVjdD0tYyhYKSkKYGBgCgoqQ29ycmVsYXRpb24gYmV0d2VlbiB3aW5yYXRlIGFuZCB0aHJlZXBvaW50IHBlcmNlbnRhZ2UgaW4gdGhlIGxhc3QgZGVjYWRlcyoKYGBge3J9CmluZF92YXJzIDwtIGxpc3QoIndpbnJhdGUiPXdpbnJhdGUsICJmdF9wZXJfZmciPWZ0X3Blcl9mZywgImVmZl9mZ19wZXIiPWVmZl9mZ19wZXIsICJvZmZfcmViIj1vZmZfcmViLCAidHVybl9wZXIiPXR1cm5fcGVyLCAicGVyY2VudDNwIj1wZXJjZW50M3AsICJtYWRlM3AiPW1hZGUzcCwgImF0dGVtcHRzM3AiPWF0dGVtcHRzM3ApCnN0YXJ0X3lyIDwtIDIwMTEKZW5kX3lyIDwtIDIwMTYKbG1fZGYgPC0gZGF0YS5mcmFtZShZZWFyPXJlcChzZXEoc3RhcnRfeXIsIGVuZF95ciksMzApKQp0ZWFtX25hbWVzIDwtIGNvbG5hbWVzKGluZF92YXJzJHdpbnJhdGUpCmZvciAoZmVhdF9uYW1lIGluIG5hbWVzKGluZF92YXJzKSkKewogIHRlbXAgPC0gYygpCiAgY3Vycl9mZWF0IDwtIGluZF92YXJzW1tmZWF0X25hbWVdXQogIHlycyA8LSBzZXEobWF0Y2goc3RhcnRfeXIsIHllYXJfcmFuZ2UpLCBtYXRjaChlbmRfeXIsIHllYXJfcmFuZ2UpKQogIGZvciAoeXJfaW5kIGluIHlycykKICB7CiAgICBmb3IgKGN1cnJfdGVhbSBpbiB0ZWFtX25hbWVzKQogICAgewogICAgICB0ZW1wIDwtIGModGVtcCwgY3Vycl9mZWF0W1tjdXJyX3RlYW1dXVt5cl9pbmRdKQogICAgfQogIH0KICBsbV9kZltmZWF0X25hbWVdIDwtIHRlbXAKfQp4eXBsb3Qod2lucmF0ZSB+IHBlcmNlbnQzcCwgZGF0YT1sbV9kZiwgdHlwZT1jKCJwIiwiciIpLCBncm91cHM9WWVhciwgYXV0by5rZXk9bGlzdChzcGFjZT0icmlnaHQiLCByb3dzPWxlbmd0aCh1bmlxdWUobG1fZGYkWWVhcikpLCB0aXRsZT0iWWVhciIpKQpwbG90MSA8LSBnZ3Bsb3QoZGF0YT1sbV9kZiwgYWVzKHg9cGVyY2VudDNwLCB5PXdpbnJhdGUsIGNvbG9yPWFzLmZhY3RvcihZZWFyKSwgZ3VpZGVfbGVnZW5kKHRpdGxlID0gIlllYXIiKSkpICsgZ2VvbV9wb2ludChhZXMoZ3JvdXA9WWVhciksIHNoYXBlPTIxKSArIGdlb21fc21vb3RoKGFlcyhncm91cD1hcy5mYWN0b3IoWWVhcikpLCBtZXRob2Q9J2xtJywgc2U9RkFMU0UsIGluaGVyaXQuYWVzID0gVFJVRSwgbHdkPTAuNSkgKyBzY2FsZV9jb2xvcl9kaXNjcmV0ZShuYW1lPSJZZWFyIikgKyBnZ3RpdGxlKCdXaW5yYXRlIHZzLiAzLXBvaW50ZXIgcGVyY2VudGFnZScpCnBsb3QyIDwtIGdncGxvdChkYXRhPWxtX2RmLCBhZXMoeD1tYWRlM3AsIHk9d2lucmF0ZSwgY29sb3I9YXMuZmFjdG9yKFllYXIpLCBndWlkZV9sZWdlbmQodGl0bGUgPSAiWWVhciIpKSkgKyBnZW9tX3BvaW50KGFlcyhncm91cD1ZZWFyKSwgc2hhcGU9MjEpICsgZ2VvbV9zbW9vdGgoYWVzKGdyb3VwPWFzLmZhY3RvcihZZWFyKSksIG1ldGhvZD0nbG0nLCBzZT1GQUxTRSwgaW5oZXJpdC5hZXMgPSBUUlVFLCBsd2Q9MC41KSArIHNjYWxlX2NvbG9yX2Rpc2NyZXRlKG5hbWU9IlllYXIiKSArIGdndGl0bGUoJ1dpbnJhdGUgdnMuIDMtcG9pbnRlciBtYWtlcycpCnBsb3QzIDwtIGdncGxvdChkYXRhPWxtX2RmLCBhZXMoeD1hdHRlbXB0czNwLCB5PXdpbnJhdGUsIGNvbG9yPWFzLmZhY3RvcihZZWFyKSwgZ3VpZGVfbGVnZW5kKHRpdGxlID0gIlllYXIiKSkpICsgZ2VvbV9wb2ludChhZXMoZ3JvdXA9WWVhciksIHNoYXBlPTIxKSArIGdlb21fc21vb3RoKGFlcyhncm91cD1hcy5mYWN0b3IoWWVhcikpLCBtZXRob2Q9J2xtJywgc2U9RkFMU0UsIGluaGVyaXQuYWVzID0gVFJVRSwgbHdkPTAuNSkgKyBzY2FsZV9jb2xvcl9kaXNjcmV0ZShuYW1lPSJZZWFyIikgKyBnZ3RpdGxlKCdXaW5yYXRlIHZzLiAzLXBvaW50ZXIgYXR0ZW1wdHMnKQpncmlkLmFycmFuZ2UocGxvdDEsIHBsb3QyLCBwbG90MywgbmNvbD0yKQpgYGAKYGBge3J9Cm1vZGVsMSA8LSBsbSh3aW5yYXRlIH4gcGVyY2VudDNwLCBkYXRhPWxtX2RmKQpwbG90KGZpdHRlZChtb2RlbDEpLCByZXNpZHVhbHMobW9kZWwxKSkKbW9kZWwyIDwtIGxtKHdpbnJhdGUgfiBwZXJjZW50M3AqbWFkZTNwLCBkYXRhPWxtX2RmKSAjIENvbGluZWFyaXR5IHJlbW92ZQptb2RlbDMgPC0gbG0od2lucmF0ZSB+IHBlcmNlbnQzcCptYWRlM3AqYXR0ZW1wdHMzcCwgZGF0YT1sbV9kZikKIyBzdW1tYXJ5KG1vZGVsMSkKIyBzdW1tYXJ5KG1vZGVsMikKIyBzdW1tYXJ5KG1vZGVsMykKIyBhbm92YShtb2RlbDEsIG1vZGVsMiwgbW9kZWwzKQptb2RlbDQgPC0gbG0od2lucmF0ZSB+IGVmZl9mZ19wZXIrZnRfcGVyX2ZnK29mZl9yZWIrdHVybl9wZXIsIGRhdGE9bG1fZGYpCnN1bW1hcnkobW9kZWw0KQptb2RlbDUgPC0gbG0od2lucmF0ZSB+IGZ0X3Blcl9mZytvZmZfcmViK3R1cm5fcGVyK3BlcmNlbnQzcCwgZGF0YT1sbV9kZikKI2N2LmxtKG1vZGVsNCwgbT0zLCBkYXRhID0gbG1fZGYpCnN1bW1hcnkobW9kZWw0KQpwYXIobWZyb3c9YygyLDIpKQpwbG90KG1vZGVsNCkKYW5vdmEobW9kZWwxLCBtb2RlbDQsIG1vZGVsNSkKYGBgCgoKCkFOTk9WQQoKCipSYW5kb20gZm9yZXN0KgpgYGB7cn0KI3NldC5zZWVkKDQxNSkKZ2V0X3JhbmRmb3Jlc3QgPC0gZnVuY3Rpb24oc3RhcnRfeXIsIGVuZF95ciwgaW5kX3ZhcjEsIGluZF92YXIyLCBpbmRfdmFyMywgaW5kX3ZhcjQsIGluZF92YXI1LCBpbmRfdmFyNikKewogICAgd2lucmF0ZS5saXN0IDwtIGMoKQogICAgaW5kdmFyMS5saXN0IDwtIGMoKQogICAgaW5kdmFyMi5saXN0IDwtIGMoKQogICAgaW5kdmFyMy5saXN0IDwtIGMoKQogICAgaW5kdmFyNC5saXN0IDwtIGMoKQogICAgaW5kdmFyNS5saXN0IDwtIGMoKQogICAgaW5kdmFyNi5saXN0IDwtIGMoKQogICAgeHgubGlzdCA8LSBzZXROYW1lcyhzcGxpdChpbmRfdmFyMSwgc2VxKG5yb3coaW5kX3ZhcjEpKSksIHllYXJfcmFuZ2UpCiAgICB5eS5saXN0IDwtIHNldE5hbWVzKHNwbGl0KGluZF92YXIyLCBzZXEobnJvdyhpbmRfdmFyMikpKSwgeWVhcl9yYW5nZSkKICAgIGFhLmxpc3QgPC0gc2V0TmFtZXMoc3BsaXQoaW5kX3ZhcjMsIHNlcShucm93KGluZF92YXIzKSkpLCB5ZWFyX3JhbmdlKQogICAgYmIubGlzdCA8LSBzZXROYW1lcyhzcGxpdChpbmRfdmFyNCwgc2VxKG5yb3coaW5kX3ZhcjQpKSksIHllYXJfcmFuZ2UpCiAgICBjYy5saXN0IDwtIHNldE5hbWVzKHNwbGl0KGluZF92YXI1LCBzZXEobnJvdyhpbmRfdmFyNSkpKSwgeWVhcl9yYW5nZSkKICAgIGRkLmxpc3QgPC0gc2V0TmFtZXMoc3BsaXQoaW5kX3ZhcjYsIHNlcShucm93KGluZF92YXI2KSkpLCB5ZWFyX3JhbmdlKQogICAgeXoubGlzdCA8LSBzZXROYW1lcyhzcGxpdCh3aW5yYXRlLCBzZXEobnJvdyh3aW5yYXRlKSkpLCB5ZWFyX3JhbmdlKQogICAgIyBDaGFuZ2UgdGhlIHllYXIgcmFuZ2UgdG8gZ2V0IG1vZGVscyBmb3IgZGlmZmVyZW50IHRpbWUgcmFuZ2VzCiAgICB5cnMgPC0gc2VxKG1hdGNoKHN0YXJ0X3lyLCB5ZWFyX3JhbmdlKSwgbWF0Y2goZW5kX3lyLCB5ZWFyX3JhbmdlKSkKICAgIGZvciAoeXJfaW5kIGluIHlycykKICAgIHsKICAgICAgICBpbmR2YXIxLmxpc3QgPC0gYyhpbmR2YXIxLmxpc3QsIGFzLm51bWVyaWMoeHgubGlzdFtbeXJfaW5kXV0pKQogICAgICAgIGluZHZhcjIubGlzdCA8LSBjKGluZHZhcjIubGlzdCwgYXMubnVtZXJpYyh5eS5saXN0W1t5cl9pbmRdXSkpCiAgICAgICAgaW5kdmFyMy5saXN0IDwtIGMoaW5kdmFyMy5saXN0LCBhcy5udW1lcmljKGFhLmxpc3RbW3lyX2luZF1dKSkKICAgICAgICBpbmR2YXI0Lmxpc3QgPC0gYyhpbmR2YXI0Lmxpc3QsIGFzLm51bWVyaWMoYmIubGlzdFtbeXJfaW5kXV0pKQogICAgICAgIGluZHZhcjUubGlzdCA8LSBjKGluZHZhcjUubGlzdCwgYXMubnVtZXJpYyhjYy5saXN0W1t5cl9pbmRdXSkpCiAgICAgICAgaW5kdmFyNi5saXN0IDwtIGMoaW5kdmFyNi5saXN0LCBhcy5udW1lcmljKGRkLmxpc3RbW3lyX2luZF1dKSkKICAgICAgICB3aW5yYXRlLmxpc3QgPC0gYyh3aW5yYXRlLmxpc3QsIGFzLm51bWVyaWMoeXoubGlzdFtbeXJfaW5kXV0pKQogICAgfQogICAgc21wX3NpemUgPC0gZmxvb3IoMC44ICogbGVuZ3RoKHdpbnJhdGUubGlzdCkpCiAgICB0cmFpbl9pbmQgPC0gc2FtcGxlKHNlcV9sZW4obGVuZ3RoKHdpbnJhdGUubGlzdCkpLCBzaXplPXNtcF9zaXplKQogICAgI1RyYWluaW5nIHNldAogICAgZnRfcGVyX2ZnIDwtIGluZHZhcjEubGlzdFt0cmFpbl9pbmRdCiAgICBlZmZfZmdfcGVyIDwtIGluZHZhcjEubGlzdFt0cmFpbl9pbmRdCiAgICBvZmZfcmViIDwtIGluZHZhcjEubGlzdFt0cmFpbl9pbmRdCiAgICB0dXJuX3BlciA8LSBpbmR2YXIxLmxpc3RbdHJhaW5faW5kXQogICAgbWFkZTNwIDwtIGluZHZhcjEubGlzdFt0cmFpbl9pbmRdCiAgICBwZXJjZW50M3AgPC0gaW5kdmFyMS5saXN0W3RyYWluX2luZF0KICAgIHdpbnJhdGUgPC0gd2lucmF0ZS5saXN0W3RyYWluX2luZF0KICAgIHRyYWluX2RhdGEgPC0gZGF0YS5mcmFtZShmdF9wZXJfZmc9ZnRfcGVyX2ZnLCBlZmZfZmdfcGVyPWVmZl9mZ19wZXIsIG9mZl9yZWI9b2ZmX3JlYiwgdHVybl9wZXI9dHVybl9wZXIsIG1hZGUzcD1tYWRlM3AsIHBlcmNlbnQzcD1wZXJjZW50M3AsIHdpbnJhdGU9d2lucmF0ZSkKICAgICMgVGVzdGluZyBzZXQKICAgIGZ0X3Blcl9mZyA8LSBpbmR2YXIxLmxpc3RbLXRyYWluX2luZF0KICAgIGVmZl9mZ19wZXIgPC0gaW5kdmFyMS5saXN0Wy10cmFpbl9pbmRdCiAgICBvZmZfcmViIDwtIGluZHZhcjEubGlzdFstdHJhaW5faW5kXQogICAgdHVybl9wZXIgPC0gaW5kdmFyMS5saXN0Wy10cmFpbl9pbmRdCiAgICBtYWRlM3AgPC0gaW5kdmFyMS5saXN0Wy10cmFpbl9pbmRdCiAgICBwZXJjZW50M3AgPC0gaW5kdmFyMS5saXN0Wy10cmFpbl9pbmRdCiAgICB3aW5yYXRlIDwtIHdpbnJhdGUubGlzdFstdHJhaW5faW5kXQogICAgdGVzdF9kYXRhIDwtIGRhdGEuZnJhbWUoZnRfcGVyX2ZnPWZ0X3Blcl9mZywgZWZmX2ZnX3Blcj1lZmZfZmdfcGVyLCBvZmZfcmViPW9mZl9yZWIsIHR1cm5fcGVyPXR1cm5fcGVyLCBtYWRlM3A9bWFkZTNwLCBwZXJjZW50M3A9cGVyY2VudDNwLCB3aW5yYXRlPXdpbnJhdGUpCiAgICAjIENoZWNrIGlmIHRoZXNlIGFyZSBpbiBvcmRlcgogICAgbW9kZWwgPC0gcmFuZG9tRm9yZXN0KHdpbnJhdGV+ZnRfcGVyX2ZnK2VmZl9mZ19wZXIrb2ZmX3JlYit0dXJuX3BlcittYWRlM3ArcGVyY2VudDNwLCBkYXRhPXRyYWluX2RhdGEsIGltcG9ydGFuY2U9VFJVRSwgbnRyZWU9MzAwMCkKICAgIHByZWRpY3Rpb24gPC0gcHJlZGljdChtb2RlbCwgdGVzdF9kYXRhLCBPT0I9VFJVRSkKICAgIHByZWRfc2NvcmUgPSBtZWFuKCh0ZXN0X2RhdGEkd2lucmF0ZSAtIHByZWRpY3Rpb24pXjIpCiAgICByZXR1cm4obGlzdCgnbW9kZWwnPW1vZGVsLCAndHJhaW5fZGF0YSc9dHJhaW5fZGF0YSwgJ3ByZWRpY3Rpb24nPXByZWRfc2NvcmUpKQp9CmZvcmVzdF9zdHVmZiA8LSBnZXRfcmFuZGZvcmVzdCgyMDA2LCAyMDE2LCBmdF9wZXJfZmcsIGVmZl9mZ19wZXIsIG9mZl9yZWIsIHR1cm5fcGVyLCBtYWRlM3AsIHBlcmNlbnQzcCkKdmFySW1wUGxvdChmb3Jlc3Rfc3R1ZmYkbW9kZWwpCnByaW50KGZvcmVzdF9zdHVmZiRwcmVkaWN0aW9uKQpgYGAKRG9uJ3QgcmVhbGx5IG5lZWQgdG8gZG8gQ1Ygc2luY2UgcmFuZG9tRm9yZXN0IGFscmVhZHkgcGFydGl0aW9ucyByYW5kb21seQoKYGBge3J9CnJlcXVpcmUoZ2dwbG90MikKcmYgPC0gcmFuZG9tRm9yZXN0KHdpbnJhdGUgfiBlZmZfZmdfcGVyK2Z0X3Blcl9mZytvZmZfcmViK3R1cm5fcGVyK3BlcmNlbnQzcCttYWRlM3ArYXR0ZW1wdHMzcCwgZGF0YT1sbV9kZiwgaW1wb3J0YW5jZT1UUlVFLCBudHJlZT0xMDAwKQppbXAgPC0gaW1wb3J0YW5jZShyZikKaW1wID0gZGF0YS5mcmFtZSh0eXBlPXJvd25hbWVzKGltcCksIGltcG9ydGFuY2UocmYpLCBjaGVjay5uYW1lcz1GKQppbXAkdHlwZSA9IHJlb3JkZXIoaW1wJHR5cGUsIGltcCRgJUluY01TRWApCmdncGxvdChkYXRhPWltcCwgYWVzKHg9dHlwZSwgeT1gJUluY01TRWApKSArIGdlb21fYmFyKHN0YXQ9J2lkZW50aXR5JywgZmlsbD0nYmxhY2snKSArIGdlb21faGxpbmUoeWludGVyY2VwdD1hYnMobWluKGltcCRgJUluY01TRWApKSwgY29sPTIsIGxpbmV0eXBlPSdkYXNoZWQnKSArIGNvb3JkX2ZsaXAoKQpgYGAK