Question 1
A company is considering using RFM segments to target its rollout.
The mailing under consideration costs €0.50 to send; if customers
respond, they spend on average €50, of which €15 is margin.
It conducted a test of customers in the file, ebeer_test.csv;
the customers not in the test can be found in ebeer_rollout.csv.
For this analysis, create 3 RFM groups (instead of 5 like we did in
class).
What is the average recency of the group that is most recent,
i.e., the customers who purchased most recently?
Provide your answer with one decimal separated
by a dot, not a comma (e.g. 0.1).
ebeer_test$Rgroup <- quantcut(ebeer_test$R, q = 3)
Alternatively:
# Create Quantiles
ntiles <- function(x, bins) {
quantiles = seq(from=0, to = 1, length.out=bins+1)
cut(ecdf(x)(x), breaks = quantiles, labels=F)
}
ebeer_test$Rgroup <- ntiles(ebeer_test$R, bins=3)
recency_stats <- ebeer_test %>%
group_by(Rgroup) %>%
summarise(n = n(),
mean_R = mean(R), sd_R = sd(R),
resp_p = mean(respmail, na.rm=TRUE))
recency_stats %>%
kbl() %>%
kable_styling()
Rgroup
|
n
|
mean_R
|
sd_R
|
resp_p
|
1
|
756
|
5.01
|
2.27
|
0.122
|
2
|
926
|
11.94
|
1.66
|
0.132
|
3
|
847
|
22.64
|
6.94
|
0.131
|
## The Average Recency of Group [2:10] is 5
Question 2
What is the response probability of that group?
Provide your answer with two decimals separated
by a dot, not a comma (e.g. 0.17).
## The Response Rate of Group [2:10] is 0.12
Question 3
Now do the full RFM analysis.
How many RFM groups are there?
Provide your answer with zero decimals
(e.g. 17).
The Code:
ntiles <- function(x, bins) {
quantiles = seq(from=0, to = 1, length.out=bins+1)
cut(ecdf(x)(x),breaks=quantiles, labels=F)
}
ebeer_test$Rgroup <- ntiles(ebeer_test$R, bins=3)
dt = data.table(ebeer_test)
nbins = 3
dt[, RFgroup := paste0(as.character(Rgroup), as.character(ntiles(F, bins = nbins))), by = c('Rgroup')]
dt[, RFMgroup := paste0(as.character(RFgroup), as.character(ntiles(M, bins = nbins))), by = c('RFgroup')]
# put it back to data.frame
ebeer_test = data.frame(dt)
# change it to a factor variable
ebeer_test$RFMgroup<-as.factor(ebeer_test$RFMgroup)
Number of RFM Groups:
length(unique(ebeer_test$RFMgroup))
## [1] 27
Question 4
How many of these segments are profitable to send
to?
Provide your answer with zero decimals
(e.g. 12).
Let’s calculate the response rates for each segment:
respRFM <- ebeer_test %>%
group_by(RFMgroup) %>%
summarise(n_resp= sum(respmail, na.rm = TRUE),
n_mail= sum(mailing, na.rm = TRUE)) %>%
mutate(resp_rate = n_resp/n_mail) %>%
arrange(desc(resp_rate))
head(respRFM) %>%
kbl() %>%
kable_styling()
RFMgroup
|
n_resp
|
n_mail
|
resp_rate
|
331
|
37
|
101
|
0.366
|
232
|
40
|
111
|
0.360
|
231
|
33
|
111
|
0.297
|
132
|
28
|
96
|
0.292
|
333
|
30
|
103
|
0.291
|
332
|
28
|
103
|
0.272
|
Breakeven Point:
c <- 0.5
m <- 15
brk <- c/m
How many to target?:
sum(respRFM$resp_rate >= brk)
## [1] 17
respRFM <- as.data.frame(respRFM)
bp <- barplot(respRFM[,4],
main="response by RFM group",
xlab="RFM Group", ylab="average response", xaxt="n")
axis(1, at = bp[,1], labels=respRFM[,1], cex.axis=0.7, las=2)
abline(h=brk)
text(85, brk, "breakeven", cex=1, pos=3, col="black")
Question 5
What fraction of total variation in responses is explained by
the RFM model?
Provide your answer with three decimals
separated by a dot, not a comma (e.g. 0.127)
RFM_model <- lm(respmail ~ RFMgroup, data = ebeer_test)
round(summary(RFM_model)$r.squared,3)
## [1] 0.153
Question 6
How many customers in the roll-out sample should be
targeted?
Provide your answer with zero decimals
(e.g. 12).
Let’s consider now the roll-out data. We need first to create the
segments:
ntiles <- function(x, bins) {
quantiles = seq(from=0, to = 1, length.out=bins+1)
cut(ecdf(x)(x),breaks=quantiles, labels=F)
}
ebeer_rollout$Rgroup <- ntiles(ebeer_rollout$R, bins=3)
dt = data.table(ebeer_rollout)
nbins = 3
dt[, RFgroup := paste0(as.character(Rgroup), as.character(ntiles(F, bins = nbins))), by = c('Rgroup')]
dt[, RFMgroup := paste0(as.character(RFgroup), as.character(ntiles(M, bins = nbins))), by = c('RFgroup')]
# put it back to data.frame
ebeer_rollout = data.frame(dt)
# change it to a factor variable
ebeer_rollout$RFMgroup<-as.factor(ebeer_rollout$RFMgroup)
We want to predict their response rates:
ebeer_rollout$RFMpred <- predict(RFM_model, ebeer_rollout)
sum(ebeer_rollout$RFMpred >= brk)
## [1] 1564
rollout_respRFM <- ebeer_rollout %>%
group_by(RFMgroup) %>%
summarise(n_resp= sum(respmail, na.rm = TRUE),
n_mail= sum(mailing, na.rm = TRUE)) %>%
mutate(resp_rate = n_resp/n_mail) %>%
arrange(desc(resp_rate))
rollout_respRFM <- as.data.frame(rollout_respRFM)
bp <- barplot(rollout_respRFM[,4],
main="response by RFM group",
xlab="RFM Group", ylab="average response", xaxt="n")
axis(1, at = bp[,1], labels=rollout_respRFM[,1], cex.axis=0.7, las=2)
abline(h=brk)
text(85, brk, "breakeven", cex=1, pos=3, col="black")
Question 7
What is the return on investment of rolling out to those
segments?
Provide your answer with zero
decimals without the percent sign (e.g. 120).
Profit per Consumer:
ebeer_rollout <- ebeer_rollout %>%
mutate(RFMprofit = case_when(RFMpred >= brk ~ RFMpred*m-c, TRUE ~ 0))
Sum of Profits:
sum_profit <- sum(ebeer_rollout$RFMprofit)
sum_profit
## [1] 3592
Costs per Consumer:
ebeer_rollout <- ebeer_rollout %>%
mutate(RFMcost = case_when(RFMpred >= brk ~ c, TRUE ~ 0))
Sum of Costs:
sum_cost <- sum(ebeer_rollout$RFMcost)
sum_cost
## [1] 782
ROI:
## The ROI is 459 %
Question 8
Use a beta-binomial model to shrink the segment estimates in the test
data.
How many extra segments would you target using this method
that you wouldn’t otherwise target?
Provide your answer with zero decimals
(e.g. 12).
First, we need the non-response data:
respRFM <- respRFM %>%
mutate(n_nonresp = n_mail-n_resp) %>%
relocate(n_nonresp, .after=n_resp)
Likelihood Function to fit the data and find prior response
rate:
fit <- vglm(cbind(respRFM$n_resp,respRFM$n_nonresp) ~ 1, betabinomialff, trace=TRUE)
## VGLM linear loop 1 : loglikelihood = -87.958729
## VGLM linear loop 2 : loglikelihood = -87.944974
## VGLM linear loop 3 : loglikelihood = -87.944815
## VGLM linear loop 4 : loglikelihood = -87.944812
## VGLM linear loop 5 : loglikelihood = -87.944812
a <- Coef(fit)[[1]]
b <- Coef(fit)[[2]]
## (a,b)=( 0.388 , 3.11 )
We know can estimate the posterior response rates:
post_mean_resp <- (a+respRFM$n_resp)/(a+b+respRFM$n_mail)
respRFM <- cbind(respRFM, post_mean_resp)
plot(respRFM$resp_rate, xaxt="n",col="red",xlab="RFM segments",ylab="response rate and posterior mean response rate")
points(respRFM$post_mean_resp, col='blue')
legend('topleft',legend=c("estimate response rate", "posterior expected response rate"),col=c("red","blue"), pch=1)
axis(1, at = 1:27, labels=respRFM$RFMgroup, cex.axis=0.7, las=2)
abline(h=brk)
text(25, brk, "breakeven", cex=1, pos=3, col="black")
In the end, how many extra segments should we
target?
sum(respRFM$post_mean_resp >= brk) - sum(respRFM$resp_rate >= brk)
## [1] 1
LS0tDQp0aXRsZTogIlF1aXogMjogUkZNIEFuYWx5c2lzIg0KYXV0aG9yOiAiRGFuaWVsIFJlZGVsIg0KZGF0ZTogIjIwMjMtMDEtMjQiDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogVFJVRQ0KICAgIHRvY19mbG9hdDogVFJVRQ0KICAgIGNvZGVfZG93bmxvYWQ6IFRSVUUNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSkNCnJtKGxpc3QgPSBscygpKQ0KDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkoZGF0YS50YWJsZSkNCmxpYnJhcnkoa2FibGVFeHRyYSkNCmxpYnJhcnkoZ3Rvb2xzKQ0KbGlicmFyeShWR0FNKQ0KbGlicmFyeShyZWFkcikNCmBgYA0KDQpgYGB7ciwgaW5jbHVkZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCmViZWVyX3Rlc3QgPC0gcmVhZF9jc3YoIkM6L1VzZXJzL2Rhbm55L09uZURyaXZlL0Fuw6FsaXNpcyBDdWFudGl0YXRpdm8geSBFY29ub21ldHLDrWEvTWFya2V0aW5nIEFuYWx5dGljcy9DdXN0b21lciBBbmFseXRpY3MvUXVpemVzLzIgUkZNL2ViZWVyX3Rlc3QuY3N2IikNCg0KZWJlZXJfcm9sbG91dCA8LSByZWFkX2NzdigiQzovVXNlcnMvZGFubnkvT25lRHJpdmUvQW7DoWxpc2lzIEN1YW50aXRhdGl2byB5IEVjb25vbWV0csOtYS9NYXJrZXRpbmcgQW5hbHl0aWNzL0N1c3RvbWVyIEFuYWx5dGljcy9RdWl6ZXMvMiBSRk0vZWJlZXJfcm9sbG91dC5jc3YiKQ0KYGBgDQoNCiMgUXVlc3Rpb24gMQ0KDQpBIGNvbXBhbnkgaXMgY29uc2lkZXJpbmcgdXNpbmcgUkZNIHNlZ21lbnRzIHRvIHRhcmdldCBpdHMgcm9sbG91dC4gVGhlIG1haWxpbmcgdW5kZXIgY29uc2lkZXJhdGlvbiBjb3N0cyDigqwwLjUwIHRvIHNlbmQ7IGlmIGN1c3RvbWVycyByZXNwb25kLCB0aGV5IHNwZW5kIG9uIGF2ZXJhZ2Ug4oKsNTAsIG9mIHdoaWNoIOKCrDE1IGlzIG1hcmdpbi4NCg0KSXQgY29uZHVjdGVkIGEgdGVzdCBvZiBjdXN0b21lcnMgaW4gdGhlIGZpbGUswqBbZWJlZXJfdGVzdC5jc3ZdKGh0dHBzOi8vdGlsYnVyZ3VuaXZlcnNpdHkuaW5zdHJ1Y3R1cmUuY29tL2NvdXJzZXMvMTA5MTkvZmlsZXMvMTk0ODk3NT93cmFwPTEpOyB0aGUgY3VzdG9tZXJzIG5vdCBpbiB0aGUgdGVzdCBjYW4gYmUgZm91bmQgaW7CoFtlYmVlcl9yb2xsb3V0LmNzdl0oaHR0cHM6Ly90aWxidXJndW5pdmVyc2l0eS5pbnN0cnVjdHVyZS5jb20vY291cnNlcy8xMDkxOS9maWxlcy8xOTQ4OTc3P3dyYXA9MSkuIEZvciB0aGlzIGFuYWx5c2lzLCBjcmVhdGUgMyBSRk0gZ3JvdXBzIChpbnN0ZWFkIG9mIDUgbGlrZSB3ZSBkaWQgaW4gY2xhc3MpLsKgDQoNCioqV2hhdCBpcyB0aGUgYXZlcmFnZSByZWNlbmN5IG9mIHRoZSBncm91cCB0aGF0IGlzIG1vc3QgcmVjZW50LCBpLmUuLCB0aGUgY3VzdG9tZXJzIHdobyBwdXJjaGFzZWQgbW9zdCByZWNlbnRseT8qKsKgDQoNCipQcm92aWRlIHlvdXIgYW5zd2VyIHdpdGjCoCoqb25lKiogZGVjaW1hbCBzZXBhcmF0ZWQgYnkgYSBkb3QsIG5vdCBhIGNvbW1hIChlLmcuIDAuMSkuKg0KDQpgYGB7cn0NCmViZWVyX3Rlc3QkUmdyb3VwIDwtIHF1YW50Y3V0KGViZWVyX3Rlc3QkUiwgcSA9IDMpDQpgYGANCg0KQWx0ZXJuYXRpdmVseToNCg0KYGBge3J9DQojIENyZWF0ZSBRdWFudGlsZXMgDQpudGlsZXMgPC0gZnVuY3Rpb24oeCwgYmlucykgew0KICBxdWFudGlsZXMgPSBzZXEoZnJvbT0wLCB0byA9IDEsIGxlbmd0aC5vdXQ9YmlucysxKQ0KICBjdXQoZWNkZih4KSh4KSwgYnJlYWtzID0gcXVhbnRpbGVzLCBsYWJlbHM9RikNCn0NCmViZWVyX3Rlc3QkUmdyb3VwIDwtIG50aWxlcyhlYmVlcl90ZXN0JFIsIGJpbnM9MykgIA0KYGBgDQoNCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQ0KcmVjZW5jeV9zdGF0cyA8LSBlYmVlcl90ZXN0ICU+JSANCiAgZ3JvdXBfYnkoUmdyb3VwKSAlPiUgDQogIHN1bW1hcmlzZShuID0gbigpLCANCiAgICAgICAgICAgIG1lYW5fUiA9IG1lYW4oUiksIHNkX1IgPSBzZChSKSwgDQogICAgICAgICAgICByZXNwX3AgPSBtZWFuKHJlc3BtYWlsLCBuYS5ybT1UUlVFKSkNCnJlY2VuY3lfc3RhdHMgJT4lIA0KICBrYmwoKSAlPiUNCiAga2FibGVfc3R5bGluZygpDQpgYGANCg0KYGBge3IsIGVjaG89RkFMU0V9DQpjYXQoIlRoZSBBdmVyYWdlIFJlY2VuY3kgb2YgR3JvdXAgWzI6MTBdIGlzIiwgcm91bmQocmVjZW5jeV9zdGF0cyRtZWFuX1JbMV0sMSkgKQ0KYGBgDQoNCiMgUXVlc3Rpb24gMg0KDQoqKldoYXQgaXMgdGhlIHJlc3BvbnNlIHByb2JhYmlsaXR5IG9mIHRoYXQgZ3JvdXA/KirCoA0KDQoqUHJvdmlkZSB5b3VyIGFuc3dlciB3aXRowqAqKnR3byoqwqBkZWNpbWFscyBzZXBhcmF0ZWQgYnkgYSBkb3QsIG5vdCBhIGNvbW1hIChlLmcuIDAuMTcpLioNCg0KYGBge3IsIGVjaG89RkFMU0V9DQpjYXQoIlRoZSBSZXNwb25zZSBSYXRlIG9mIEdyb3VwIFsyOjEwXSBpcyIsIHJvdW5kKHJlY2VuY3lfc3RhdHMkcmVzcF9wWzFdLDIpICkNCmBgYA0KDQojIFF1ZXN0aW9uIDMNCg0KTm93IGRvIHRoZSBmdWxsIFJGTSBhbmFseXNpcy7CoA0KDQoqKkhvdyBtYW55IFJGTSBncm91cHMgYXJlIHRoZXJlKio/DQoNCipQcm92aWRlIHlvdXIgYW5zd2VyIHdpdGjCoCoqemVybyoqwqBkZWNpbWFscyAoZS5nLiAxNykuKg0KDQpUaGUgQ29kZToNCg0KYGBge3J9DQpudGlsZXMgPC0gZnVuY3Rpb24oeCwgYmlucykgew0KICBxdWFudGlsZXMgPSBzZXEoZnJvbT0wLCB0byA9IDEsIGxlbmd0aC5vdXQ9YmlucysxKQ0KICBjdXQoZWNkZih4KSh4KSxicmVha3M9cXVhbnRpbGVzLCBsYWJlbHM9RikNCn0NCg0KZWJlZXJfdGVzdCRSZ3JvdXAgPC0gbnRpbGVzKGViZWVyX3Rlc3QkUiwgYmlucz0zKSAgDQoNCg0KZHQgPSBkYXRhLnRhYmxlKGViZWVyX3Rlc3QpDQpuYmlucyA9IDMNCmR0WywgUkZncm91cCA6PSBwYXN0ZTAoYXMuY2hhcmFjdGVyKFJncm91cCksIGFzLmNoYXJhY3RlcihudGlsZXMoRiwgYmlucyA9IG5iaW5zKSkpLCBieSA9IGMoJ1Jncm91cCcpXQ0KZHRbLCBSRk1ncm91cCA6PSBwYXN0ZTAoYXMuY2hhcmFjdGVyKFJGZ3JvdXApLCBhcy5jaGFyYWN0ZXIobnRpbGVzKE0sIGJpbnMgPSBuYmlucykpKSwgYnkgPSBjKCdSRmdyb3VwJyldDQoNCiMgcHV0IGl0IGJhY2sgdG8gZGF0YS5mcmFtZQ0KZWJlZXJfdGVzdCA9IGRhdGEuZnJhbWUoZHQpDQoNCiMgY2hhbmdlIGl0IHRvIGEgZmFjdG9yIHZhcmlhYmxlDQplYmVlcl90ZXN0JFJGTWdyb3VwPC1hcy5mYWN0b3IoZWJlZXJfdGVzdCRSRk1ncm91cCkNCmBgYA0KDQoqKk51bWJlciBvZiBSRk0gR3JvdXBzOioqDQoNCmBgYHtyfQ0KbGVuZ3RoKHVuaXF1ZShlYmVlcl90ZXN0JFJGTWdyb3VwKSkNCmBgYA0KDQojIFF1ZXN0aW9uIDQNCg0KKipIb3cgbWFueSBvZiB0aGVzZSBzZWdtZW50cyBhcmUgcHJvZml0YWJsZSB0byBzZW5kIHRvPyoqwqANCg0KKlByb3ZpZGUgeW91ciBhbnN3ZXIgd2l0aMKgKip6ZXJvKirCoGRlY2ltYWxzIChlLmcuIDEyKS4qDQoNCkxldCdzIGNhbGN1bGF0ZSB0aGUgcmVzcG9uc2UgcmF0ZXMgZm9yIGVhY2ggc2VnbWVudDoNCg0KYGBge3IsIHdhcm5pbmc9RkFMU0V9DQpyZXNwUkZNIDwtIGViZWVyX3Rlc3QgJT4lIA0KICBncm91cF9ieShSRk1ncm91cCkgJT4lIA0KICBzdW1tYXJpc2Uobl9yZXNwPSBzdW0ocmVzcG1haWwsIG5hLnJtID0gVFJVRSksIA0KICAgICAgICAgICAgbl9tYWlsPSBzdW0obWFpbGluZywgbmEucm0gPSBUUlVFKSkgJT4lIA0KICBtdXRhdGUocmVzcF9yYXRlID0gbl9yZXNwL25fbWFpbCkgJT4lIA0KICBhcnJhbmdlKGRlc2MocmVzcF9yYXRlKSkNCmhlYWQocmVzcFJGTSkgJT4lIA0KICBrYmwoKSAlPiUNCiAga2FibGVfc3R5bGluZygpDQpgYGANCg0KKipCcmVha2V2ZW4gUG9pbnQqKjoNCg0KYGBge3J9DQpjIDwtIDAuNQ0KbSA8LSAxNQ0KYnJrIDwtIGMvbQ0KYGBgDQoNClsqKkhvdyBtYW55IHRvIHRhcmdldD8qKl17LnVuZGVybGluZX06DQoNCmBgYHtyfQ0Kc3VtKHJlc3BSRk0kcmVzcF9yYXRlID49IGJyaykNCmBgYA0KDQpgYGB7cn0NCnJlc3BSRk0gPC0gYXMuZGF0YS5mcmFtZShyZXNwUkZNKQ0KDQpicCA8LSBiYXJwbG90KHJlc3BSRk1bLDRdLCANCiAgICAgICAgICAgICAgbWFpbj0icmVzcG9uc2UgYnkgUkZNIGdyb3VwIiwgDQogICAgICAgICAgICAgIHhsYWI9IlJGTSBHcm91cCIsIHlsYWI9ImF2ZXJhZ2UgcmVzcG9uc2UiLCB4YXh0PSJuIikNCmF4aXMoMSwgYXQgPSBicFssMV0sIGxhYmVscz1yZXNwUkZNWywxXSwgY2V4LmF4aXM9MC43LCBsYXM9MikNCg0KYWJsaW5lKGg9YnJrKQ0KdGV4dCg4NSwgYnJrLCAiYnJlYWtldmVuIiwgY2V4PTEsIHBvcz0zLCBjb2w9ImJsYWNrIikNCmBgYA0KDQojIFF1ZXN0aW9uIDUNCg0KKipXaGF0IGZyYWN0aW9uIG9mIHRvdGFsIHZhcmlhdGlvbiBpbiByZXNwb25zZXMgaXMgZXhwbGFpbmVkIGJ5IHRoZSBSRk0gbW9kZWw/KirCoA0KDQoqUHJvdmlkZSB5b3VyIGFuc3dlciB3aXRowqAqKnRocmVlKiogZGVjaW1hbHMgc2VwYXJhdGVkIGJ5IGEgZG90LCBub3QgYSBjb21tYSAoZS5nLiAwLjEyNykqDQoNCmBgYHtyfQ0KUkZNX21vZGVsIDwtIGxtKHJlc3BtYWlsIH4gUkZNZ3JvdXAsIGRhdGEgPSBlYmVlcl90ZXN0KQ0Kcm91bmQoc3VtbWFyeShSRk1fbW9kZWwpJHIuc3F1YXJlZCwzKQ0KYGBgDQoNCiMgUXVlc3Rpb24gNg0KDQoqKkhvdyBtYW55IGN1c3RvbWVycyBpbiB0aGUgcm9sbC1vdXQgc2FtcGxlIHNob3VsZCBiZSB0YXJnZXRlZD8qKsKgDQoNCipQcm92aWRlIHlvdXIgYW5zd2VyIHdpdGjCoCoqemVybyoqwqBkZWNpbWFscyAoZS5nLiAxMikuKg0KDQpMZXQncyBjb25zaWRlciBub3cgdGhlIHJvbGwtb3V0IGRhdGEuIFdlIG5lZWQgZmlyc3QgdG8gKmNyZWF0ZSB0aGUgc2VnbWVudHMqOg0KDQpgYGB7cn0NCm50aWxlcyA8LSBmdW5jdGlvbih4LCBiaW5zKSB7DQogIHF1YW50aWxlcyA9IHNlcShmcm9tPTAsIHRvID0gMSwgbGVuZ3RoLm91dD1iaW5zKzEpDQogIGN1dChlY2RmKHgpKHgpLGJyZWFrcz1xdWFudGlsZXMsIGxhYmVscz1GKQ0KfQ0KDQplYmVlcl9yb2xsb3V0JFJncm91cCA8LSBudGlsZXMoZWJlZXJfcm9sbG91dCRSLCBiaW5zPTMpICANCg0KDQpkdCA9IGRhdGEudGFibGUoZWJlZXJfcm9sbG91dCkNCm5iaW5zID0gMw0KZHRbLCBSRmdyb3VwIDo9IHBhc3RlMChhcy5jaGFyYWN0ZXIoUmdyb3VwKSwgYXMuY2hhcmFjdGVyKG50aWxlcyhGLCBiaW5zID0gbmJpbnMpKSksIGJ5ID0gYygnUmdyb3VwJyldDQpkdFssIFJGTWdyb3VwIDo9IHBhc3RlMChhcy5jaGFyYWN0ZXIoUkZncm91cCksIGFzLmNoYXJhY3RlcihudGlsZXMoTSwgYmlucyA9IG5iaW5zKSkpLCBieSA9IGMoJ1JGZ3JvdXAnKV0NCg0KIyBwdXQgaXQgYmFjayB0byBkYXRhLmZyYW1lDQplYmVlcl9yb2xsb3V0ID0gZGF0YS5mcmFtZShkdCkNCg0KIyBjaGFuZ2UgaXQgdG8gYSBmYWN0b3IgdmFyaWFibGUNCmViZWVyX3JvbGxvdXQkUkZNZ3JvdXA8LWFzLmZhY3RvcihlYmVlcl9yb2xsb3V0JFJGTWdyb3VwKQ0KYGBgDQoNCldlIHdhbnQgdG8gKnByZWRpY3QgdGhlaXIgcmVzcG9uc2UgcmF0ZXMqOg0KDQpgYGB7cn0NCmViZWVyX3JvbGxvdXQkUkZNcHJlZCA8LSBwcmVkaWN0KFJGTV9tb2RlbCwgZWJlZXJfcm9sbG91dCkNCnN1bShlYmVlcl9yb2xsb3V0JFJGTXByZWQgPj0gYnJrKQ0KYGBgDQoNCmBgYHtyfQ0Kcm9sbG91dF9yZXNwUkZNIDwtIGViZWVyX3JvbGxvdXQgJT4lIA0KICBncm91cF9ieShSRk1ncm91cCkgJT4lIA0KICBzdW1tYXJpc2Uobl9yZXNwPSBzdW0ocmVzcG1haWwsIG5hLnJtID0gVFJVRSksIA0KICAgICAgICAgICAgbl9tYWlsPSBzdW0obWFpbGluZywgbmEucm0gPSBUUlVFKSkgJT4lIA0KICBtdXRhdGUocmVzcF9yYXRlID0gbl9yZXNwL25fbWFpbCkgJT4lIA0KICBhcnJhbmdlKGRlc2MocmVzcF9yYXRlKSkNCmBgYA0KDQpgYGB7cn0NCnJvbGxvdXRfcmVzcFJGTSA8LSBhcy5kYXRhLmZyYW1lKHJvbGxvdXRfcmVzcFJGTSkNCg0KYnAgPC0gYmFycGxvdChyb2xsb3V0X3Jlc3BSRk1bLDRdLCANCiAgICAgICAgICAgICAgbWFpbj0icmVzcG9uc2UgYnkgUkZNIGdyb3VwIiwgDQogICAgICAgICAgICAgIHhsYWI9IlJGTSBHcm91cCIsIHlsYWI9ImF2ZXJhZ2UgcmVzcG9uc2UiLCB4YXh0PSJuIikNCmF4aXMoMSwgYXQgPSBicFssMV0sIGxhYmVscz1yb2xsb3V0X3Jlc3BSRk1bLDFdLCBjZXguYXhpcz0wLjcsIGxhcz0yKQ0KDQphYmxpbmUoaD1icmspDQp0ZXh0KDg1LCBicmssICJicmVha2V2ZW4iLCBjZXg9MSwgcG9zPTMsIGNvbD0iYmxhY2siKQ0KYGBgDQoNCiMgUXVlc3Rpb24gNw0KDQoqKldoYXQgaXMgdGhlIHJldHVybiBvbiBpbnZlc3RtZW50IG9mIHJvbGxpbmcgb3V0IHRvIHRob3NlIHNlZ21lbnRzPyoqwqANCg0KKlByb3ZpZGUgeW91ciBhbnN3ZXIgd2l0aMKgKip6ZXJvKiogZGVjaW1hbHPCoCoqd2l0aG91dCoqwqB0aGUgcGVyY2VudCBzaWduIChlLmcuIDEyMCkuKg0KDQoqKlByb2ZpdCBwZXIgQ29uc3VtZXIqKjoNCg0KYGBge3J9DQplYmVlcl9yb2xsb3V0IDwtIGViZWVyX3JvbGxvdXQgJT4lIA0KCW11dGF0ZShSRk1wcm9maXQgPSBjYXNlX3doZW4oUkZNcHJlZCA+PSBicmsgfiBSRk1wcmVkKm0tYywgVFJVRSB+IDApKQ0KYGBgDQoNCioqU3VtIG9mIFByb2ZpdHMqKjoNCg0KYGBge3J9DQpzdW1fcHJvZml0IDwtIHN1bShlYmVlcl9yb2xsb3V0JFJGTXByb2ZpdCkNCnN1bV9wcm9maXQNCmBgYA0KDQoqKkNvc3RzIHBlciBDb25zdW1lcioqOg0KDQpgYGB7cn0NCmViZWVyX3JvbGxvdXQgPC0gZWJlZXJfcm9sbG91dCAlPiUgDQoJbXV0YXRlKFJGTWNvc3QgPSBjYXNlX3doZW4oUkZNcHJlZCA+PSBicmsgfiBjLCBUUlVFIH4gMCkpDQpgYGANCg0KKipTdW0gb2YgQ29zdHMqKjoNCg0KYGBge3J9DQpzdW1fY29zdCA8LSBzdW0oZWJlZXJfcm9sbG91dCRSRk1jb3N0KQ0Kc3VtX2Nvc3QgDQpgYGANCg0KKipST0kqKjoNCg0KYGBge3IsIGVjaG89RkFMU0V9DQpjYXQoIlRoZSBST0kgaXMiLCByb3VuZChzdW1fcHJvZml0IC8gc3VtX2Nvc3QqMTAwLCAwKSwgIiUiKQ0KYGBgDQoNCiMgUXVlc3Rpb24gOA0KDQpVc2UgYSBiZXRhLWJpbm9taWFsIG1vZGVsIHRvIHNocmluayB0aGUgc2VnbWVudCBlc3RpbWF0ZXMgaW4gdGhlIHRlc3QgZGF0YS4gwqANCg0KKipIb3cgbWFueSBleHRyYSBzZWdtZW50cyB3b3VsZCB5b3UgdGFyZ2V0IHVzaW5nIHRoaXMgbWV0aG9kIHRoYXQgeW91IHdvdWxkbid0IG90aGVyd2lzZSB0YXJnZXQ/KioNCg0KKlByb3ZpZGUgeW91ciBhbnN3ZXIgd2l0aMKgKip6ZXJvKiogZGVjaW1hbHMgKGUuZy4gMTIpLioNCg0KRmlyc3QsIHdlIG5lZWQgdGhlIG5vbi1yZXNwb25zZSBkYXRhOg0KDQpgYGB7cn0NCnJlc3BSRk0gPC0gcmVzcFJGTSAlPiUgDQogIG11dGF0ZShuX25vbnJlc3AgPSBuX21haWwtbl9yZXNwKSAlPiUgDQogIHJlbG9jYXRlKG5fbm9ucmVzcCwgLmFmdGVyPW5fcmVzcCkNCmBgYA0KDQpMaWtlbGlob29kIEZ1bmN0aW9uIHRvIGZpdCB0aGUgZGF0YSBhbmQgZmluZCAqKnByaW9yIHJlc3BvbnNlIHJhdGUqKjoNCg0KYGBge3J9DQpmaXQgPC0gdmdsbShjYmluZChyZXNwUkZNJG5fcmVzcCxyZXNwUkZNJG5fbm9ucmVzcCkgfiAxLCBiZXRhYmlub21pYWxmZiwgdHJhY2U9VFJVRSkNCmEgPC0gQ29lZihmaXQpW1sxXV0NCmIgPC0gQ29lZihmaXQpW1syXV0NCmBgYA0KDQpgYGB7ciwgZWNobz1GQUxTRX0NCmNhdCgiKGEsYik9KCIsYSwiLCIsIGIsICIpIikNCmBgYA0KDQpXZSBrbm93IGNhbiBlc3RpbWF0ZSB0aGUgcG9zdGVyaW9yIHJlc3BvbnNlIHJhdGVzOg0KDQpgYGB7cn0NCnBvc3RfbWVhbl9yZXNwIDwtIChhK3Jlc3BSRk0kbl9yZXNwKS8oYStiK3Jlc3BSRk0kbl9tYWlsKQ0KcmVzcFJGTSA8LSBjYmluZChyZXNwUkZNLCBwb3N0X21lYW5fcmVzcCkNCmBgYA0KDQpgYGB7cn0NCnBsb3QocmVzcFJGTSRyZXNwX3JhdGUsIHhheHQ9Im4iLGNvbD0icmVkIix4bGFiPSJSRk0gc2VnbWVudHMiLHlsYWI9InJlc3BvbnNlIHJhdGUgYW5kIHBvc3RlcmlvciBtZWFuIHJlc3BvbnNlIHJhdGUiKQ0KcG9pbnRzKHJlc3BSRk0kcG9zdF9tZWFuX3Jlc3AsIGNvbD0nYmx1ZScpDQpsZWdlbmQoJ3RvcGxlZnQnLGxlZ2VuZD1jKCJlc3RpbWF0ZSByZXNwb25zZSByYXRlIiwgInBvc3RlcmlvciBleHBlY3RlZCByZXNwb25zZSByYXRlIiksY29sPWMoInJlZCIsImJsdWUiKSwgcGNoPTEpDQpheGlzKDEsIGF0ID0gMToyNywgbGFiZWxzPXJlc3BSRk0kUkZNZ3JvdXAsIGNleC5heGlzPTAuNywgbGFzPTIpDQphYmxpbmUoaD1icmspDQp0ZXh0KDI1LCBicmssICJicmVha2V2ZW4iLCBjZXg9MSwgcG9zPTMsIGNvbD0iYmxhY2siKQ0KYGBgDQoNCkluIHRoZSBlbmQsICoqKmhvdyBtYW55IGV4dHJhIHNlZ21lbnRzIHNob3VsZCB3ZSB0YXJnZXQ/KioqDQoNCmBgYHtyfQ0Kc3VtKHJlc3BSRk0kcG9zdF9tZWFuX3Jlc3AgPj0gYnJrKSAtIHN1bShyZXNwUkZNJHJlc3BfcmF0ZSA+PSBicmspDQpgYGANCg==