Exercice 4.1 : Factorielle

  1. Construction de la fonction factorielle avec la fonction prod:
factorielle <- function(n) {
  if (n<0) stop("l'entier doit être strictement positif")
  if (n==0) return(1)
  if (floor(n)!=n) warning(paste("arrondi de",n,"en",floor(n)))
  resultat <- prod(1:n)
  return(resultat)
}
  1. Construction de la fonction factorielle avec une boucle for:
factorielle <- function(n) {
  if (n<0) stop("l'entier doit être strictement positif")
  if (n==0) return(1)
  if (floor(n)!=n){
    warning(paste("arrondi de",n,"en",floor(n)))
    n <- floor(n)
  }
  resultat <- 1
  for (i in 1:n) resultat <- resultat*i
  return(resultat)
}
factorielle(5)
[1] 120
factorielle(4.23)
[1] 24

Exercice 4.2 : Ventilation

  1. Construisons la fonction de ventilation:
ventilation <- function(Xqual,p=0.05) {
  if (!is.factor(Xqual)) stop("Xqual doit etre un facteur \n")
  modalites <- levels(Xqual)
  if (length(modalites)<=1) stop("pas assez de niveaux \n")
  tabl <- table(Xqual)
  selecti <- (tabl/sum(tabl))<p
  if (!any(selecti)) return(Xqual) else {
   lesquels <- modalites[!selecti]
   prov <- factor(Xqual[(Xqual%in%lesquels)],levels=lesquels)
   prov <- table(prov)
   proba <- prov/sum(prov)
   for (j in modalites[selecti]) {
    ## tirages dans les modalites au hasard et remplacement
    if (length(lesquels)==1){
     warning("1 seule modalite\n")
     Xqual[Xqual==j]<-lesquels
    } else Xqual[Xqual==j]<-sample(lesquels,sum(Xqual==j),
             replace=TRUE,prob=proba)
   }
   Xqualvent <- factor(as.character(Xqual))
  }
  return(Xqualvent)
 }
  1. Nous appliquons la fonction de la question précédente à chaque colonne du tableau qui est un facteur:
ventil.tab <- function (tab, seuil=0.05) {
 for (i in 1:ncol(tab)) {
  if (is.factor(tab[,i])) tab[,i]<-ventilation(tab[,i],p=seuil)
 }
 return(tab)
}

Exercice 4.3 : Ventilation sur facteur ordonné

  1. La fonction est identique ou presque à ce qui a été vu dans la correction de l’exercice 2. Nous ajoutons la sortie (return) et quelques contrôles :
ventilation.ordonnee <- function(Xqual,p=0.05) {
  if (!is.ordered(Xqual)) stop("Xqual doit etre un ordonne \n")
  modalites <- levels(Xqual)
  if (length(modalites)<=1) stop("pas assez de niveaux \n")
  tabl <- table(Xqual)
  selecti <- (tabl/sum(tabl))<p
  if (!any(selecti)) return(Xqual) else {
    numero <- which(selecti)
    while(any((tabl/sum(tabl))<p)) {
      Xqual[,i] <- ventilation(Xqual[,i])
   }
 }
 return(Xqual)
}
  1. Nous appliquons la fonction de la question précédente à chaque colonne du tableau qui est un facteur ordonné:
ventil.ordonnee.tab <- function (tab, seuil=0.05) {
 for (i in 1:ncol(tab)) {
  if (is.ordered(tab[,i])) tab[,i]<-ventilation.ordonnee(tab[,i],seuil)
 }
 return(tab)
}

Exercice 4.4 : Parallélisation

max_inc <- function(seq) {
  dseq <- seq[-1] > seq[-length(seq)]; cur_l <- 1+dseq[1]; max_l <- cur_l
  for (i in 1:(length(dseq)-1)) {
    if (dseq[i] && (dseq[i] == dseq[i+1])) {
    cur_l <- cur_l + 1;
    } else { cur_l <- 1+dseq[i+1] }
  max_l <- max(cur_l, max_l)
  }
  return(max_l)
}
  1. Utilisation de sapply et table
n <- 1000
k <- 100000
system.time({
  set.seed(234)
  res <- sapply(1:k, function(aux){max_inc(runif(n))})
})
   user  system elapsed 
 80.474   0.152  81.267 
table(res)
res
    4     5     6     7     8     9    10    11 
   82 30280 53828 13624  1905   248    32     1 
  1. Utilisation du package parallel
require(parallel)
system.time({
  cl <- makeCluster(detectCores()-1)    
  clusterExport(cl, varlist=c("n","k","max_inc"))
  res <- parSapply(cl, 1:k, function(aux){max_inc(runif(n))})
  stopCluster(cl)
})
   user  system elapsed 
  0.137   0.150  50.554 
table(res)
res
    4     5     6     7     8     9    10    11    12 
   78 30530 53413 13790  1909   253    23     2     2 
  1. Pour accelérer encore les temps de calcul, il faut utiliser le package Rcpp.
#include <Rcpp.h>
using namespace Rcpp;


// [[Rcpp::export]]
int max_inc_rcpp(NumericVector x) {
  int max_l = 0;
  int cur_l = 0;
  bool cur_g, prev_g;
  cur_g = x[1] > x[0];
  cur_l = 1 + cur_g;
  max_l = cur_l;
  for (int i = 1; i < (x.length()-1); i++) {
    prev_g = cur_g;
    cur_g = x[i+1] > x[i];
    if (cur_g && (prev_g == cur_g)) {
      cur_l = cur_l + 1;
    } else {
      cur_l = 1 + cur_g;
    }
    if (cur_l > max_l) { max_l = cur_l;};
  }
  return max_l;
}
system.time(res <- sapply(1:k, function(i) { max_inc_rcpp(runif(n)) }))
   user  system elapsed 
  4.650   0.020   4.698 

Exercice 4.5 :

  1. Simulons deux variables x et y:
set.seed(1234)
x <- rnorm(50)
epsilon <- rnorm(50)
y <- 2+ 0.5*x + epsilon
  1. On calule le coefficient entre x et y et le test associé, puis on compte le nombre de fois où le coefficient de corrélation entre x et y permuté est plus grand que le coefficient de corrélation observé:
cor.xy=cor(x,y)
cor.test(x,y)

    Pearson's product-moment correlation

data:  x and y
t = 2.5274, df = 48, p-value = 0.01484
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 0.07114562 0.56697017
sample estimates:
      cor 
0.3427066 
nbsim=100000

system.time({
set.seed(234)
som=0
for (i in 1:nbsim) som=som+(cor(x,y[sample(length(y))])>cor.xy)
})
   user  system elapsed 
  6.197   0.003   6.243 
print(som/nbsim*2)
[1] 0.01476

La probabilité critique obtenue par le test classique, 0.01484, est proche de celle calculée par permutation 0.01476.

  1. On utilise le package parallel et la fonction parLapply
require(parallel)
cl <- makeCluster(3)
clusterExport(cl, varlist=c("x","y","cor.xy"))
system.time({
  res <- parSapply(cl, 1:nbsim, function(aux){1*(cor(x,y[sample(length(y))])>cor.xy)})
})
   user  system elapsed 
  0.057   0.011   4.178 
stopCluster(cl)
mean(res)*2
[1] 0.0142

On multiplie par 2 le pourcentage obtenu car le test est bilatéral. On note que le temps de calcul est en gros divisé par 2.6.

  1. On relance les lignes de code avec \(rchisq\) pour générer les aléas:
set.seed(1234)
x <- rchisq(50, df=1)
epsilon <- rchisq(50, df=1)
y <- 2+ 0.5*x + epsilon

cor.xy=cor(x,y)
cor.test(x,y)

    Pearson's product-moment correlation

data:  x and y
t = 2.7855, df = 48, p-value = 0.007628
alternative hypothesis: true correlation is not equal to 0
95 percent confidence interval:
 0.1056607 0.5901113
sample estimates:
      cor 
0.3730372 
nbsim=100000

system.time({
set.seed(234)
som=0
for (i in 1:nbsim) som=som+(cor(x,y[sample(length(y))])>cor.xy)
})
   user  system elapsed 
  6.560   0.008   6.609 
print(som/nbsim*2)
[1] 0.0252
require(parallel)
cl <- makeCluster(3)
clusterExport(cl, varlist=c("x","y","cor.xy"))
system.time({
  set.seed(234)
  res <- parSapply(cl, 1:nbsim, function(aux){1*(cor(x,y[sample(length(y))])>cor.xy)})
})
   user  system elapsed 
  0.062   0.016   4.228 
stopCluster(cl)
2*mean(res)
[1] 0.02406

La probabilité critique obtenue avec le test classique, 0.007628, est cette fois assez différente de celle obtenue par le test de permutation. En effet, l’hypothèse de normalité n’est pas vérifiée ici et le test classique n’est pas adaptée. On aura donc plus confiance dans l’estimation de la probabilité critique obtenue par le test de permutation : 0.025

LS0tCnRpdGxlOiAiQ29ycmVjdGlvbiBkZXMgZXhlcmNpY2VzIGR1IGNoYXBpdHJlIDQiCmF1dGhvcjogIkh1c3NvbiBldCBhbC4iCmRhdGU6ICIwOS8wOS8yMDE4IgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6IDMKICAgIHRvY19mbG9hdDogeWVzCiAgaHRtbF9kb2N1bWVudDoKICAgIHRvYzogeWVzCiAgICB0b2NfZGVwdGg6ICczJwogICAgdG9jX2Zsb2F0OiB5ZXMKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBjYWNoZSA9IFRSVUUpCmBgYAoKIyMgRXhlcmNpY2UgNC4xIDogRmFjdG9yaWVsbGUKCjEuIENvbnN0cnVjdGlvbiBkZSBsYSBmb25jdGlvbiBmYWN0b3JpZWxsZSBhdmVjIGxhIGZvbmN0aW9uICpwcm9kKjoKCmBgYHtyLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KZmFjdG9yaWVsbGUgPC0gZnVuY3Rpb24obikgewogIGlmIChuPDApIHN0b3AoImwnZW50aWVyIGRvaXQgw6p0cmUgc3RyaWN0ZW1lbnQgcG9zaXRpZiIpCiAgaWYgKG49PTApIHJldHVybigxKQogIGlmIChmbG9vcihuKSE9bikgd2FybmluZyhwYXN0ZSgiYXJyb25kaSBkZSIsbiwiZW4iLGZsb29yKG4pKSkKICByZXN1bHRhdCA8LSBwcm9kKDE6bikKICByZXR1cm4ocmVzdWx0YXQpCn0KYGBgCgoyLiBDb25zdHJ1Y3Rpb24gZGUgbGEgZm9uY3Rpb24gZmFjdG9yaWVsbGUgYXZlYyB1bmUgYm91Y2xlICpmb3IqOgoKYGBge3IsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQpmYWN0b3JpZWxsZSA8LSBmdW5jdGlvbihuKSB7CiAgaWYgKG48MCkgc3RvcCgibCdlbnRpZXIgZG9pdCDDqnRyZSBzdHJpY3RlbWVudCBwb3NpdGlmIikKICBpZiAobj09MCkgcmV0dXJuKDEpCiAgaWYgKGZsb29yKG4pIT1uKXsKICAgIHdhcm5pbmcocGFzdGUoImFycm9uZGkgZGUiLG4sImVuIixmbG9vcihuKSkpCiAgICBuIDwtIGZsb29yKG4pCiAgfQogIHJlc3VsdGF0IDwtIDEKICBmb3IgKGkgaW4gMTpuKSByZXN1bHRhdCA8LSByZXN1bHRhdCppCiAgcmV0dXJuKHJlc3VsdGF0KQp9CmZhY3RvcmllbGxlKDUpCmZhY3RvcmllbGxlKDQuMjMpCmBgYAojIyBFeGVyY2ljZSA0LjIgOiBWZW50aWxhdGlvbgoKMS4gQ29uc3RydWlzb25zIGxhIGZvbmN0aW9uIGRlIHZlbnRpbGF0aW9uOgpgYGB7cixtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9CnZlbnRpbGF0aW9uIDwtIGZ1bmN0aW9uKFhxdWFsLHA9MC4wNSkgewogIGlmICghaXMuZmFjdG9yKFhxdWFsKSkgc3RvcCgiWHF1YWwgZG9pdCBldHJlIHVuIGZhY3RldXIgXG4iKQogIG1vZGFsaXRlcyA8LSBsZXZlbHMoWHF1YWwpCiAgaWYgKGxlbmd0aChtb2RhbGl0ZXMpPD0xKSBzdG9wKCJwYXMgYXNzZXogZGUgbml2ZWF1eCBcbiIpCiAgdGFibCA8LSB0YWJsZShYcXVhbCkKICBzZWxlY3RpIDwtICh0YWJsL3N1bSh0YWJsKSk8cAogIGlmICghYW55KHNlbGVjdGkpKSByZXR1cm4oWHF1YWwpIGVsc2UgewogICBsZXNxdWVscyA8LSBtb2RhbGl0ZXNbIXNlbGVjdGldCiAgIHByb3YgPC0gZmFjdG9yKFhxdWFsWyhYcXVhbCVpbiVsZXNxdWVscyldLGxldmVscz1sZXNxdWVscykKICAgcHJvdiA8LSB0YWJsZShwcm92KQogICBwcm9iYSA8LSBwcm92L3N1bShwcm92KQogICBmb3IgKGogaW4gbW9kYWxpdGVzW3NlbGVjdGldKSB7CiAgICAjIyB0aXJhZ2VzIGRhbnMgbGVzIG1vZGFsaXRlcyBhdSBoYXNhcmQgZXQgcmVtcGxhY2VtZW50CiAgICBpZiAobGVuZ3RoKGxlc3F1ZWxzKT09MSl7CiAgICAgd2FybmluZygiMSBzZXVsZSBtb2RhbGl0ZVxuIikKICAgICBYcXVhbFtYcXVhbD09al08LWxlc3F1ZWxzCiAgICB9IGVsc2UgWHF1YWxbWHF1YWw9PWpdPC1zYW1wbGUobGVzcXVlbHMsc3VtKFhxdWFsPT1qKSwKICAgICAgICAgICAgIHJlcGxhY2U9VFJVRSxwcm9iPXByb2JhKQogICB9CiAgIFhxdWFsdmVudCA8LSBmYWN0b3IoYXMuY2hhcmFjdGVyKFhxdWFsKSkKICB9CiAgcmV0dXJuKFhxdWFsdmVudCkKIH0KYGBgCjIuIE5vdXMgYXBwbGlxdW9ucyBsYSBmb25jdGlvbiBkZSBsYSBxdWVzdGlvbiBwcsOpY8OpZGVudGUgw6AgY2hhcXVlIGNvbG9ubmUgZHUgdGFibGVhdSBxdWkgZXN0IHVuIGZhY3RldXI6CmBgYHtyLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0KdmVudGlsLnRhYiA8LSBmdW5jdGlvbiAodGFiLCBzZXVpbD0wLjA1KSB7CiBmb3IgKGkgaW4gMTpuY29sKHRhYikpIHsKICBpZiAoaXMuZmFjdG9yKHRhYlssaV0pKSB0YWJbLGldPC12ZW50aWxhdGlvbih0YWJbLGldLHA9c2V1aWwpCiB9CiByZXR1cm4odGFiKQp9CmBgYAoKIyMgRXhlcmNpY2UgNC4zIDogVmVudGlsYXRpb24gc3VyIGZhY3RldXIgb3Jkb25uw6kKCjEuIExhIGZvbmN0aW9uIGVzdCBpZGVudGlxdWUgb3UgcHJlc3F1ZSDDoCBjZSBxdWkgYSDDqXTDqSB2dSBkYW5zIGxhIGNvcnJlY3Rpb24gZGUgbCdleGVyY2ljZSAyLgpOb3VzIGFqb3V0b25zIGxhIHNvcnRpZSAoKnJldHVybiopIGV0IHF1ZWxxdWVzIGNvbnRyw7RsZXMgOgpgYGB7cixtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9CnZlbnRpbGF0aW9uLm9yZG9ubmVlIDwtIGZ1bmN0aW9uKFhxdWFsLHA9MC4wNSkgewogIGlmICghaXMub3JkZXJlZChYcXVhbCkpIHN0b3AoIlhxdWFsIGRvaXQgZXRyZSB1biBvcmRvbm5lIFxuIikKICBtb2RhbGl0ZXMgPC0gbGV2ZWxzKFhxdWFsKQogIGlmIChsZW5ndGgobW9kYWxpdGVzKTw9MSkgc3RvcCgicGFzIGFzc2V6IGRlIG5pdmVhdXggXG4iKQogIHRhYmwgPC0gdGFibGUoWHF1YWwpCiAgc2VsZWN0aSA8LSAodGFibC9zdW0odGFibCkpPHAKICBpZiAoIWFueShzZWxlY3RpKSkgcmV0dXJuKFhxdWFsKSBlbHNlIHsKICAgIG51bWVybyA8LSB3aGljaChzZWxlY3RpKQogICAgd2hpbGUoYW55KCh0YWJsL3N1bSh0YWJsKSk8cCkpIHsKICAgICAgWHF1YWxbLGldIDwtIHZlbnRpbGF0aW9uKFhxdWFsWyxpXSkKICAgfQogfQogcmV0dXJuKFhxdWFsKQp9CmBgYAoyLiBOb3VzIGFwcGxpcXVvbnMgbGEgZm9uY3Rpb24gZGUgbGEgcXVlc3Rpb24gcHLDqWPDqWRlbnRlIMOgIGNoYXF1ZSBjb2xvbm5lIGR1IHRhYmxlYXUgcXVpIGVzdCB1biBmYWN0ZXVyIG9yZG9ubsOpOgpgYGB7cixtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9CnZlbnRpbC5vcmRvbm5lZS50YWIgPC0gZnVuY3Rpb24gKHRhYiwgc2V1aWw9MC4wNSkgewogZm9yIChpIGluIDE6bmNvbCh0YWIpKSB7CiAgaWYgKGlzLm9yZGVyZWQodGFiWyxpXSkpIHRhYlssaV08LXZlbnRpbGF0aW9uLm9yZG9ubmVlKHRhYlssaV0sc2V1aWwpCiB9CiByZXR1cm4odGFiKQp9CmBgYAoKIyMgRXhlcmNpY2UgNC40IDogUGFyYWxsw6lsaXNhdGlvbgoKYGBge3J9Cm1heF9pbmMgPC0gZnVuY3Rpb24oc2VxKSB7CiAgZHNlcSA8LSBzZXFbLTFdID4gc2VxWy1sZW5ndGgoc2VxKV07IGN1cl9sIDwtIDErZHNlcVsxXTsgbWF4X2wgPC0gY3VyX2wKICBmb3IgKGkgaW4gMToobGVuZ3RoKGRzZXEpLTEpKSB7CiAgICBpZiAoZHNlcVtpXSAmJiAoZHNlcVtpXSA9PSBkc2VxW2krMV0pKSB7CiAgICBjdXJfbCA8LSBjdXJfbCArIDE7CiAgICB9IGVsc2UgeyBjdXJfbCA8LSAxK2RzZXFbaSsxXSB9CiAgbWF4X2wgPC0gbWF4KGN1cl9sLCBtYXhfbCkKICB9CiAgcmV0dXJuKG1heF9sKQp9CmBgYAoKMS4gVXRpbGlzYXRpb24gZGUgc2FwcGx5IGV0IHRhYmxlCgpgYGB7cn0KbiA8LSAxMDAwCmsgPC0gMTAwMDAwCmBgYAoKYGBge3J9CnN5c3RlbS50aW1lKHsKICBzZXQuc2VlZCgyMzQpCiAgcmVzIDwtIHNhcHBseSgxOmssIGZ1bmN0aW9uKGF1eCl7bWF4X2luYyhydW5pZihuKSl9KQp9KQp0YWJsZShyZXMpCmBgYAoyLiBVdGlsaXNhdGlvbiBkdSBwYWNrYWdlIHBhcmFsbGVsCgpgYGB7cixtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9CnJlcXVpcmUocGFyYWxsZWwpCnN5c3RlbS50aW1lKHsKICBjbCA8LSBtYWtlQ2x1c3RlcihkZXRlY3RDb3JlcygpLTEpICAgIAogIGNsdXN0ZXJFeHBvcnQoY2wsIHZhcmxpc3Q9YygibiIsImsiLCJtYXhfaW5jIikpCiAgcmVzIDwtIHBhclNhcHBseShjbCwgMTprLCBmdW5jdGlvbihhdXgpe21heF9pbmMocnVuaWYobikpfSkKICBzdG9wQ2x1c3RlcihjbCkKfSkKdGFibGUocmVzKQpgYGAKCjMuIFBvdXIgYWNjZWzDqXJlciBlbmNvcmUgbGVzIHRlbXBzIGRlIGNhbGN1bCwgaWwgZmF1dCB1dGlsaXNlciBsZSBwYWNrYWdlIFJjcHAuCgpgYGB7UmNwcH0KI2luY2x1ZGUgPFJjcHAuaD4KdXNpbmcgbmFtZXNwYWNlIFJjcHA7CgoKLy8gW1tSY3BwOjpleHBvcnRdXQppbnQgbWF4X2luY19yY3BwKE51bWVyaWNWZWN0b3IgeCkgewogIGludCBtYXhfbCA9IDA7CiAgaW50IGN1cl9sID0gMDsKICBib29sIGN1cl9nLCBwcmV2X2c7CiAgY3VyX2cgPSB4WzFdID4geFswXTsKICBjdXJfbCA9IDEgKyBjdXJfZzsKICBtYXhfbCA9IGN1cl9sOwogIGZvciAoaW50IGkgPSAxOyBpIDwgKHgubGVuZ3RoKCktMSk7IGkrKykgewogICAgcHJldl9nID0gY3VyX2c7CiAgICBjdXJfZyA9IHhbaSsxXSA+IHhbaV07CiAgICBpZiAoY3VyX2cgJiYgKHByZXZfZyA9PSBjdXJfZykpIHsKICAgICAgY3VyX2wgPSBjdXJfbCArIDE7CiAgICB9IGVsc2UgewogICAgICBjdXJfbCA9IDEgKyBjdXJfZzsKICAgIH0KICAgIGlmIChjdXJfbCA+IG1heF9sKSB7IG1heF9sID0gY3VyX2w7fTsKICB9CiAgcmV0dXJuIG1heF9sOwp9CmBgYAoKYGBge3J9CnN5c3RlbS50aW1lKHJlcyA8LSBzYXBwbHkoMTprLCBmdW5jdGlvbihpKSB7IG1heF9pbmNfcmNwcChydW5pZihuKSkgfSkpCmBgYAoKIyMgRXhlcmNpY2UgNC41IDogCgoxLiBTaW11bG9ucyBkZXV4IHZhcmlhYmxlcyB4IGV0IHk6CgpgYGB7cn0Kc2V0LnNlZWQoMTIzNCkKeCA8LSBybm9ybSg1MCkKZXBzaWxvbiA8LSBybm9ybSg1MCkKeSA8LSAyKyAwLjUqeCArIGVwc2lsb24KYGBgCgoyLiBPbiBjYWx1bGUgbGUgY29lZmZpY2llbnQgZW50cmUgeCBldCB5IGV0IGxlIHRlc3QgYXNzb2Npw6ksIHB1aXMgb24gY29tcHRlIGxlIG5vbWJyZSBkZSBmb2lzIG/DuSBsZSBjb2VmZmljaWVudCBkZSAgY29ycsOpbGF0aW9uIGVudHJlIHggZXQgeSBwZXJtdXTDqSBlc3QgcGx1cyBncmFuZCBxdWUgbGUgY29lZmZpY2llbnQgZGUgY29ycsOpbGF0aW9uIG9ic2VydsOpOgoKYGBge3J9CmNvci54eT1jb3IoeCx5KQpjb3IudGVzdCh4LHkpCm5ic2ltPTEwMDAwMAoKc3lzdGVtLnRpbWUoewpzZXQuc2VlZCgyMzQpCnNvbT0wCmZvciAoaSBpbiAxOm5ic2ltKSBzb209c29tKyhjb3IoeCx5W3NhbXBsZShsZW5ndGgoeSkpXSk+Y29yLnh5KQp9KQpwcmludChzb20vbmJzaW0qMikKYGBgCkxhIHByb2JhYmlsaXTDqSBjcml0aXF1ZSBvYnRlbnVlIHBhciBsZSB0ZXN0IGNsYXNzaXF1ZSwgMC4wMTQ4NCwgZXN0IHByb2NoZSBkZSBjZWxsZSBjYWxjdWzDqWUgcGFyIHBlcm11dGF0aW9uIDAuMDE0NzYuCgozLiBPbiB1dGlsaXNlIGxlIHBhY2thZ2UgcGFyYWxsZWwgZXQgbGEgZm9uY3Rpb24gcGFyTGFwcGx5CgpgYGB7cixtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9CnJlcXVpcmUocGFyYWxsZWwpCmNsIDwtIG1ha2VDbHVzdGVyKDMpCmNsdXN0ZXJFeHBvcnQoY2wsIHZhcmxpc3Q9YygieCIsInkiLCJjb3IueHkiKSkKc3lzdGVtLnRpbWUoewogIHJlcyA8LSBwYXJTYXBwbHkoY2wsIDE6bmJzaW0sIGZ1bmN0aW9uKGF1eCl7MSooY29yKHgseVtzYW1wbGUobGVuZ3RoKHkpKV0pPmNvci54eSl9KQp9KQpzdG9wQ2x1c3RlcihjbCkKbWVhbihyZXMpKjIKYGBgCk9uIG11bHRpcGxpZSBwYXIgMiBsZSBwb3VyY2VudGFnZSBvYnRlbnUgY2FyIGxlIHRlc3QgZXN0IGJpbGF0w6lyYWwuCk9uIG5vdGUgcXVlIGxlIHRlbXBzIGRlIGNhbGN1bCBlc3QgZW4gZ3JvcyBkaXZpc8OpIHBhciAyLjYuCgo0LiBPbiByZWxhbmNlIGxlcyBsaWduZXMgZGUgY29kZSBhdmVjICRyY2hpc3EkIHBvdXIgZ8OpbsOpcmVyIGxlcyBhbMOpYXM6CgpgYGB7cn0Kc2V0LnNlZWQoMTIzNCkKeCA8LSByY2hpc3EoNTAsIGRmPTEpCmVwc2lsb24gPC0gcmNoaXNxKDUwLCBkZj0xKQp5IDwtIDIrIDAuNSp4ICsgZXBzaWxvbgoKY29yLnh5PWNvcih4LHkpCmNvci50ZXN0KHgseSkKbmJzaW09MTAwMDAwCgpzeXN0ZW0udGltZSh7CnNldC5zZWVkKDIzNCkKc29tPTAKZm9yIChpIGluIDE6bmJzaW0pIHNvbT1zb20rKGNvcih4LHlbc2FtcGxlKGxlbmd0aCh5KSldKT5jb3IueHkpCn0pCnByaW50KHNvbS9uYnNpbSoyKQoKcmVxdWlyZShwYXJhbGxlbCkKY2wgPC0gbWFrZUNsdXN0ZXIoMykKY2x1c3RlckV4cG9ydChjbCwgdmFybGlzdD1jKCJ4IiwieSIsImNvci54eSIpKQpzeXN0ZW0udGltZSh7CiAgc2V0LnNlZWQoMjM0KQogIHJlcyA8LSBwYXJTYXBwbHkoY2wsIDE6bmJzaW0sIGZ1bmN0aW9uKGF1eCl7MSooY29yKHgseVtzYW1wbGUobGVuZ3RoKHkpKV0pPmNvci54eSl9KQp9KQpzdG9wQ2x1c3RlcihjbCkKMiptZWFuKHJlcykKYGBgCkxhIHByb2JhYmlsaXTDqSBjcml0aXF1ZSBvYnRlbnVlIGF2ZWMgbGUgdGVzdCBjbGFzc2lxdWUsIDAuMDA3NjI4LCBlc3QgY2V0dGUgZm9pcyBhc3NleiBkaWZmw6lyZW50ZSBkZSBjZWxsZSBvYnRlbnVlIHBhciBsZSB0ZXN0IGRlIHBlcm11dGF0aW9uLiBFbiBlZmZldCwgbCdoeXBvdGjDqHNlIGRlIG5vcm1hbGl0w6kgbidlc3QgcGFzIHbDqXJpZmnDqWUgaWNpIGV0IGxlIHRlc3QgY2xhc3NpcXVlIG4nZXN0IHBhcyBhZGFwdMOpZS4gT24gYXVyYSBkb25jIHBsdXMgY29uZmlhbmNlIGRhbnMgbCdlc3RpbWF0aW9uIGRlIGxhIHByb2JhYmlsaXTDqSBjcml0aXF1ZSBvYnRlbnVlIHBhciBsZSB0ZXN0IGRlIHBlcm11dGF0aW9uIDogMC4wMjU=