Exercice 2.1 : Importation robuste

  1. Importation sous forme de vecteur de caractères (la première ligne contient des caractères, donc ce type est obligatoire):
vecteur <- scan("https://r-stat-sc-donnees.github.io/donnees.csv",what="",sep=";")
Read 20 items
vecteur
 [1] "individu" "taille"   "poids"    "pointure" "sexe"     "roger"    "184"      "80"       "44"       "M"        "théodule" "175,5"   
[13] "78"       "43"       "M"        "nicolas"  "158"      "72"       "42"       "M"       
  1. Récupération des noms de colonnes qui correspondent aux coordonnées 2 à 5:
nomcol <- vecteur[2:5]
nomcol
[1] "taille"   "poids"    "pointure" "sexe"    
  1. Changement des virgules en points:
vecteur <- gsub(",",".",vecteur) 
  1. Matrice brute des données (avec noms des lignes et des colonnes):
matbrut <- matrix(vecteur,nrow=4,ncol=5,byrow=TRUE)
matbrut
     [,1]       [,2]     [,3]    [,4]       [,5]  
[1,] "individu" "taille" "poids" "pointure" "sexe"
[2,] "roger"    "184"    "80"    "44"       "M"   
[3,] "théodule" "175.5"  "78"    "43"       "M"   
[4,] "nicolas"  "158"    "72"    "42"       "M"   
  1. Nom des lignes (première colonne de matbrut, sauf la première coordonnée):
nomlign <- matbrut[-1,1]
nomlign
[1] "roger"    "théodule" "nicolas" 
donnees <- matbrut[-1,-1]
  1. Data-frame des données avec le nom des lignes et des colonnes
essai <- data.frame(donnees)
summary(essai)
     X1     X2     X3    X4   
 158  :1   72:1   42:1   M:3  
 175.5:1   78:1   43:1        
 184  :1   80:1   44:1        
donnees <- data.frame(donnees)
colnames(donnees) <- nomcol
rownames(donnees) <- nomlign
summary(donnees)
   taille  poids  pointure sexe 
 158  :1   72:1   42:1     M:3  
 175.5:1   78:1   43:1          
 184  :1   80:1   44:1          

Les trois premières variables sont des facteurs, il faut donc les convertir en numérique:

donnees[,1] <- as.numeric(as.character(donnees[,1]))
donnees[,2] <- as.numeric(as.character(donnees[,2]))
donnees[,3] <- as.numeric(as.character(donnees[,3]))
summary(donnees)
     taille          poids          pointure    sexe 
 Min.   :158.0   Min.   :72.00   Min.   :42.0   M:3  
 1st Qu.:166.8   1st Qu.:75.00   1st Qu.:42.5        
 Median :175.5   Median :78.00   Median :43.0        
 Mean   :172.5   Mean   :76.67   Mean   :43.0        
 3rd Qu.:179.8   3rd Qu.:79.00   3rd Qu.:43.5        
 Max.   :184.0   Max.   :80.00   Max.   :44.0        

Exercice 2.2 Importation

test1 <- read.table("https://r-stat-sc-donnees.github.io/test1.csv",dec=",",sep=";",header=TRUE)
summary(test1)
    CLONE         B           IN             HT19             C19             HT29      
 1-105 :9   Min.   :1   Min.   :1.000   Min.   : 3.000   Min.   : 5.00   Min.   : 4.50  
 1-41  :9   1st Qu.:1   1st Qu.:2.750   1st Qu.: 7.968   1st Qu.:18.75   1st Qu.:11.38  
 18-428:9   Median :1   Median :4.500   Median : 9.055   Median :21.50   Median :12.75  
 18-429:5   Mean   :1   Mean   :4.688   Mean   : 8.556   Mean   :21.75   Mean   :11.91  
            3rd Qu.:1   3rd Qu.:7.000   3rd Qu.: 9.925   3rd Qu.:26.00   3rd Qu.:13.75  
            Max.   :1   Max.   :9.000   Max.   :11.300   Max.   :34.00   Max.   :15.25  
test1prn <- read.table("https://r-stat-sc-donnees.github.io/test1.prn",header=TRUE)
summary(test1prn)
    CLONE         B           IN             HT19             C19             HT29      
 1-105 :9   Min.   :1   Min.   :1.000   Min.   : 3.000   Min.   : 5.00   Min.   : 4.50  
 1-41  :9   1st Qu.:1   1st Qu.:3.000   1st Qu.: 8.150   1st Qu.:19.00   1st Qu.:11.50  
 18-428:9   Median :1   Median :5.000   Median : 9.110   Median :22.00   Median :12.75  
 18-429:6   Mean   :1   Mean   :4.727   Mean   : 8.595   Mean   :21.85   Mean   :11.97  
            3rd Qu.:1   3rd Qu.:7.000   3rd Qu.: 9.900   3rd Qu.:26.00   3rd Qu.:13.75  
            Max.   :1   Max.   :9.000   Max.   :11.300   Max.   :34.00   Max.   :15.25  
test2 <- read.table("https://r-stat-sc-donnees.github.io/test2.csv",sep=";",header=TRUE,na.strings="")
summary(test2)
     CLONE           B               IN         HT19             C19             HT29      
 1-105  : 18   Min.   :1.000   Min.   :1   Min.   : 0.890   Min.   : 3.00   Min.   : 1.96  
 1-41   : 18   1st Qu.:1.000   1st Qu.:3   1st Qu.: 7.350   1st Qu.:17.00   1st Qu.:10.75  
 18-428 : 18   Median :1.000   Median :5   Median : 9.320   Median :23.00   Median :12.88  
 18-429 : 18   Mean   :1.234   Mean   :5   Mean   : 8.846   Mean   :22.08   Mean   :12.20  
 18-430 : 18   3rd Qu.:1.000   3rd Qu.:7   3rd Qu.:10.500   3rd Qu.:28.00   3rd Qu.:14.00  
 (Other):908   Max.   :2.000   Max.   :9   Max.   :14.200   Max.   :37.00   Max.   :17.50  
 NA's   :  1                               NA's   :15       NA's   :20      NA's   :15     
test3 <- read.table("https://r-stat-sc-donnees.github.io/test3.csv",sep=";",header=TRUE,na.strings=".")
summary(test3)
     CLONE           B               IN         HT19              C19             HT29      
 1-105  : 18   Min.   :1.000   Min.   :1   Min.   :   0.89   Min.   : 3.00   Min.   : 1.96  
 1-41   : 18   1st Qu.:1.000   1st Qu.:3   1st Qu.:   7.35   1st Qu.:17.00   1st Qu.:10.75  
 18-428 : 18   Median :1.000   Median :5   Median :   9.32   Median :23.00   Median :12.88  
 18-429 : 18   Mean   :1.234   Mean   :5   Mean   :  10.47   Mean   :22.08   Mean   :12.20  
 18-430 : 18   3rd Qu.:1.000   3rd Qu.:7   3rd Qu.:  10.50   3rd Qu.:28.00   3rd Qu.:14.00  
 18-438 : 18   Max.   :2.000   Max.   :9   Max.   :1142.00   Max.   :37.00   Max.   :17.50  
 (Other):891                               NA's   :15        NA's   :20      NA's   :15     

Exercice 2.3 Importation et format date

  1. Importons le jeu de données en débutant à la troisième ligne (on saute les deux premières avec skip=2):
ski <- read.table("https://r-stat-sc-donnees.github.io/test4.csv", sep="|", skip=2, header=TRUE,
     row.names=1)
summary(ski)
      age            gender      first.time.skiing
 Min.   :24.00   Min.   :0.00   1980-05-01:1      
 1st Qu.:28.00   1st Qu.:0.00   1982-01-31:1      
 Median :32.00   Median :0.00   1992-01-15:1      
 Mean   :30.38   Mean   :0.25   2003-03-16:1      
 3rd Qu.:33.00   3rd Qu.:0.25   2005-02-26:1      
 Max.   :33.00   Max.   :1.00   2006-03-04:1      
                                (Other)   :2      
  1. Utilisons le format Date pour la dernière variable. Le format POSIXct pourrait être intéressant si la date comportait une heure, ce qui n’est pas le cas ici.
ski2<-read.table("https://r-stat-sc-donnees.github.io/test4.csv",sep="|",skip=2,header=TRUE,
     row.names=1,colClasses=c("character","numeric",
     "factor","Date"))
summary(ski2)
      age        gender first.time.skiing   
 Min.   :24.00   0:6    Min.   :1980-05-01  
 1st Qu.:28.00   1:2    1st Qu.:1989-07-20  
 Median :32.00          Median :2004-03-06  
 Mean   :30.38          Mean   :1998-06-01  
 3rd Qu.:33.00          3rd Qu.:2006-12-02  
 Max.   :33.00          Max.   :2009-03-06  

Exercice 2.4 : Importation et fusion

  1. Importations des jeux de données:
etat1 <- read.table("https://r-stat-sc-donnees.github.io/etat1.csv",sep=";",header=TRUE)
etat2 <- read.table("https://r-stat-sc-donnees.github.io/etat2.csv",sep=",",header=TRUE)
etat3 <- read.table("https://r-stat-sc-donnees.github.io/etat3.csv",row.names=1,header=TRUE)
  1. Fusion par clef (variable commune region pour etat1 et etat2, puis variable commune etat pour etat2 et le tableau précédent):
etat13 <- merge(etat1,etat3,by="region")
etat123 <- merge(etat2,etat13,by="etat")
head(etat13)
         region      etat     vote
1 North Central   Indiana Elephant
2 North Central      Iowa Elephant
3 North Central  Michigan Elephant
4 North Central Minnesota Elephant
5 North Central  Illinois Elephant
6 North Central  Nebraska Elephant
head(etat123)
        etat Population Income Illiteracy Life.Exp Murder HS.Grad Frost   Area region     vote
1    Alabama       3615   3624        2.1    69.05   15.1    41.3    20  50708  South Elephant
2     Alaska        365   6315        1.5    69.31   11.3    66.7   152 566432   West   Donkey
3    Arizona       2212   4530        1.8    70.55    7.8    58.1    15 113417   West   Donkey
4   Arkansas       2110   3378        1.9    70.66   10.1    39.9    65  51945  South Elephant
5 California      21198   5114        1.1    71.71   10.3    62.6    20 156361   West   Donkey
6   Colorado       2541   4884        0.7    72.06    6.8    63.9   166 103766   West   Donkey

2.5 Exercice : Fusion et sélection

  1. Ouvrir les deux fichiers sous excel (ou openoffice), les sauvegarder sous format texte .csv (avec openoffice, choisir comme séparateur de champs ;). Importer ensuite ces deux fichiers grâce à:
fusion1 <- read.table("https://r-stat-sc-donnees.github.io/fusion1.csv",sep=";",dec=",",header=TRUE)
summary(fusion1)
     yhat1              yhat3             yhat2             yhat4            yhat5        
 Min.   :-0.46520   Min.   :-0.6267   Min.   :-2.1563   Min.   :0.5374   Min.   :0.07706  
 1st Qu.:-0.37217   1st Qu.: 0.2906   1st Qu.:-1.1357   1st Qu.:0.9553   1st Qu.:0.17804  
 Median :-0.26458   Median : 1.1447   Median :-0.8284   Median :1.4079   Median :0.25966  
 Mean   :-0.26641   Mean   : 0.9956   Mean   :-0.9200   Mean   :1.3332   Mean   :0.27391  
 3rd Qu.:-0.17349   3rd Qu.: 1.5200   3rd Qu.:-0.6037   3rd Qu.:1.6578   3rd Qu.:0.37683  
 Max.   :-0.03419   Max.   : 2.8496   Max.   :-0.2903   Max.   :2.1196   Max.   :0.48926  
fusion2 <- read.table("https://r-stat-sc-donnees.github.io/fusion2.csv",sep=";",dec=",",header=TRUE)
summary(fusion2)
    Rhamnos             Fucos            Arabinos            Xylos            Mannos       
 Min.   :-0.53483   Min.   :-2.3026   Min.   :-0.77403   Min.   :0.4243   Min.   :0.02623  
 1st Qu.:-0.35127   1st Qu.:-2.0090   1st Qu.:-0.24294   1st Qu.:0.9477   1st Qu.:0.15794  
 Median :-0.10105   Median :-1.4524   Median :-0.03542   Median :1.1553   Median :0.49551  
 Mean   : 0.09249   Mean   :-1.4028   Mean   : 0.15860   Mean   :1.1117   Mean   :0.51873  
 3rd Qu.: 0.51844   3rd Qu.:-0.7409   3rd Qu.: 0.50681   3rd Qu.:1.3142   3rd Qu.:0.81277  
 Max.   : 1.34470   Max.   :-0.2829   Max.   : 1.39122   Max.   :1.7882   Max.   :1.14627  
  1. Conservation de deux colonnes pour chaque tableau puis création du data-frame:
fusion1 <- fusion1[,c("yhat1","yhat3")]
fusion2 <- fusion2[,c("Rhamnos","Arabinos")]
don <- cbind(fusion1,fusion2)
  1. Création des deux variables et ajout de celles-ci au data-frame don:
yres1 <- don[,"yhat1"]-don[,"Rhamnos"]
yres2 <- don[,"yhat3"]-don[,"Arabinos"]
don <- cbind.data.frame(don,yres1,yres2)
names(don)
[1] "yhat1"    "yhat3"    "Rhamnos"  "Arabinos" "yres1"    "yres2"   
head(don)
       yhat1     yhat3    Rhamnos    Arabinos       yres1       yres2
1 -0.3701393 1.2683995 -0.3139029 1.391224687 -0.05623645 -0.12282522
2 -0.3759516 1.3191965 -0.4696197 1.279713657  0.09366813  0.03948288
3 -0.3276299 1.2206488 -0.4609091 1.172999031  0.13327920  0.04764980
4 -0.1751021 0.8299855 -0.3368723 0.550949775  0.16177021  0.27903576
5 -0.4216596 0.1009679 -0.3376569 0.009999835 -0.08400265  0.09096808
6 -0.4193860 1.2287143 -0.3876940 1.387992918 -0.03169209 -0.15927860

Exercice 2.6 : Ventilation

  1. Calcul des fréquences :
tabl <- table(Xqual)
tabl/sum(tabl)
Xqual
          + de 80  0-10.11-20.21-30             31-40 41-50.51-60.61-70             71-80 
       0.23529412        0.10588235        0.23529412        0.05882353        0.36470588 
  1. Affichage des modalités dont l’effectif est inférieur à 5 %:
modalites <- levels(Xqual)
selecti <- (tabl/sum(tabl))<0.05
modalites[selecti]
character(0)
  1. Fréquences des modalités sans la modalité D:
lesquels <- modalites[!selecti]
prov <- factor(Xqual[(Xqual%in%lesquels)],levels=lesquels)
prov <- table(prov)
proba <- prov/sum(prov)
proba
prov
          + de 80  0-10.11-20.21-30             31-40 41-50.51-60.61-70             71-80 
       0.23529412        0.10588235        0.23529412        0.05882353        0.36470588 
  1. Sélection des individus qui prennent la modalité D, tirage aléatoire (ventilation) de leur nouvelle modalité. Le facteur Xqual possède des modalités qui sont décrites dans levels mais qui ne sont plus représentées (c’est-à-dire les modalités que l’on a ventilées, ici la modalité D). La dernière ligne permet de remettre à jour la liste des niveaux du facteur.
for (j in modalites[selecti]) {
   ## tirages dans les modalités au hasard et remplacement
   if (length(lesquels)==1) stop("1 seule modalite\n") else
   Xqual[Xqual==j] <- sample(lesquels,sum(Xqual==j),
      replace=TRUE, prob = proba)
 }
Xqualvent <- factor(as.character(Xqual))
Xqualvent
 [1] 0-10.11-20.21-30  0-10.11-20.21-30  0-10.11-20.21-30  0-10.11-20.21-30  0-10.11-20.21-30  0-10.11-20.21-30  0-10.11-20.21-30 
 [8] 0-10.11-20.21-30  0-10.11-20.21-30  31-40             31-40             31-40             31-40             31-40            
[15] 31-40             31-40             31-40             31-40             31-40             31-40             31-40            
[22] 31-40             31-40             31-40             31-40             31-40             31-40             31-40            
[29] 31-40             41-50.51-60.61-70 41-50.51-60.61-70 41-50.51-60.61-70 41-50.51-60.61-70 41-50.51-60.61-70 71-80            
[36] 71-80             71-80             71-80             71-80             71-80             71-80             71-80            
[43] 71-80             71-80             71-80             71-80             71-80             71-80             71-80            
[50] 71-80             71-80             71-80             71-80             71-80             71-80             71-80            
[57] 71-80             71-80             71-80             71-80             71-80             71-80             71-80            
[64] 71-80             71-80             + de 80           + de 80           + de 80           + de 80           + de 80          
[71] + de 80           + de 80           + de 80           + de 80           + de 80           + de 80           + de 80          
[78] + de 80           + de 80           + de 80           + de 80           + de 80           + de 80           + de 80          
[85] + de 80          
Levels: + de 80 0-10.11-20.21-30 31-40 41-50.51-60.61-70 71-80

Exercice 2.7 : Ventilation sur facteur ordonné

  1. Calcul des fréquences:
Xqual <- factor(c(rep("0-10",1),rep("11-20",3),rep("21-30",5),
     rep("31-40",20),rep("41-50",2),rep("51-60",2),rep("61-70",1),
     rep("71-80",31),rep("+ de 80",20)))
tabl <- table(Xqual)
tabl/sum(tabl)
Xqual
   + de 80       0-10      11-20      21-30      31-40      41-50      51-60      61-70      71-80 
0.23529412 0.01176471 0.03529412 0.05882353 0.23529412 0.02352941 0.02352941 0.01176471 0.36470588 
  1. Affichage des modalités à ventiler
p <- 0.05
selecti <- (tabl/sum(tabl))<p
mod <- levels(Xqual)
mod[selecti]
[1] "0-10"  "11-20" "41-50" "51-60" "61-70"
  1. Trouvons les numéros des modalités à ventiler. Ensuite tant qu’il existe une modalité dont l’effectif est inférieur à 5 %, il aut ventiler (et supprimer cette modalité de la liste des niveaux): c’est l’objet de la boucle while.
numero <- which(selecti)
while(any((tabl/sum(tabl))<p)) {
   ## prenons la premiere modalite dont l'effectif est trop faible
   j <- which(((tabl/sum(tabl))<p))[1]
   K <- length(mod)  # effectif des modalites mis à jour
   ## fusion avec modalite d'apres ou d'avant pour la derniere
   if (j<K) {
     if ((j>1)&(j<K-1)) {
       levels(Xqual) <- c(mod[1:(j-1)],paste(mod[j],
        mod[j+1],sep="."),paste(mod[j],mod[j+1],sep="."),
        mod[j+2:K])}
     if (j==1) {
       levels(Xqual) <- c(paste(mod[j],mod[j+1],sep="."),
       paste(mod[j],mod[j+1],sep="."),mod[j+2:K]) }
     if (j==(K-1))  {
       levels(Xqual) <- c(mod[1:(j-1)],paste(mod[j],
       mod[j+1],sep="."),paste(mod[j],mod[j+1],sep=".")) }
   } else {
     levels(Xqual) <- c(mod[1:(j-2)],paste(mod[j-1],
      mod[j],sep="."),paste(mod[j-1],mod[j],sep="."))
   }
   tabl <- table(Xqual) ## mise à jour de la table
   mod <- levels(Xqual)             # et des modalites
}
Xqual
 [1] 0-10.11-20.21-30  0-10.11-20.21-30  0-10.11-20.21-30  0-10.11-20.21-30  0-10.11-20.21-30  0-10.11-20.21-30  0-10.11-20.21-30 
 [8] 0-10.11-20.21-30  0-10.11-20.21-30  31-40             31-40             31-40             31-40             31-40            
[15] 31-40             31-40             31-40             31-40             31-40             31-40             31-40            
[22] 31-40             31-40             31-40             31-40             31-40             31-40             31-40            
[29] 31-40             41-50.51-60.61-70 41-50.51-60.61-70 41-50.51-60.61-70 41-50.51-60.61-70 41-50.51-60.61-70 71-80            
[36] 71-80             71-80             71-80             71-80             71-80             71-80             71-80            
[43] 71-80             71-80             71-80             71-80             71-80             71-80             71-80            
[50] 71-80             71-80             71-80             71-80             71-80             71-80             71-80            
[57] 71-80             71-80             71-80             71-80             71-80             71-80             71-80            
[64] 71-80             71-80             + de 80           + de 80           + de 80           + de 80           + de 80          
[71] + de 80           + de 80           + de 80           + de 80           + de 80           + de 80           + de 80          
[78] + de 80           + de 80           + de 80           + de 80           + de 80           + de 80           + de 80          
[85] + de 80          
Levels: + de 80 0-10.11-20.21-30 31-40 41-50.51-60.61-70 71-80

Exercice 2.8 Du tableau croisé au tableau de données

  1. La création d’une table de contingence peut simplement se faire par la création de la matrice:
conting <- matrix(c(2,1,3,0,0,4),2,3)
colnames(conting) <- c("Ang","Mer","Tex")
rownames(conting) <- c("Faible","Forte")
  1. La matrice conting n’est pas un type table. Il lui manque des attributs. Aussi l’opération inverse simple (grâce à as.data.frame) n’est pas possible directement. Une solution consiste à ajouter les attributs à cette matrice. Plus simplement nous allons constituer ce tableau à la main:
tabmat <- matrix("",length(conting),3)
tabmat[,3] <- as.vector(conting)
tabmat[,2] <- rep(rownames(conting),ncol(conting))
tabmat[,1] <- rep(colnames(conting),each=nrow(conting))
  1. Mise en place du data-frame tabframe. La colonne 3, qui est transformée en facteur par la fonction dataframe doit être transformée en numérique. Ensuite, l’effectif total et le nombre de facteur sont calculés:
tabframe <- data.frame(tabmat)
tabframe[,3] <- as.numeric(as.character(tabframe[,3]))
tabframe
   X1     X2 X3
1 Ang Faible  2
2 Ang  Forte  1
3 Mer Faible  3
4 Mer  Forte  0
5 Tex Faible  0
6 Tex  Forte  4
n <- sum(tabframe[,3])
nbefac <- ncol(tabframe)-1
  1. Création de la matrice tabcomplet et du compteur :
tabcomplet <- matrix("",n,nbefac)
iter <- 1
  1. Sur les deux premières lignes, nous opérons la boucle sur toutes les lignes du tableau tabframe et le contrôle de l’effectif (non nul). Ensuite, nous répétons autant de fois que l’effectif le demande l’affectation des modalités (donc pas la dernière colonne de tabmat qui contient les effectifs) dans le nouveau tableau. L’indice des lignes de ce dernier est géré par le compteur iter.
for (i in 1:nrow(tabframe)) {
   if (tabframe[i,3]>0) {
     for (j in 1:tabframe[i,3]) {
       tabcomplet[iter,] <- tabmat[i,-ncol(tabframe)]
       iter <- iter+1
     }
   }
 }
data.frame(tabcomplet)
    X1     X2
1  Ang Faible
2  Ang Faible
3  Ang  Forte
4  Mer Faible
5  Mer Faible
6  Mer Faible
7  Tex  Forte
8  Tex  Forte
9  Tex  Forte
10 Tex  Forte

La matrice tabmat est utilisée dans l’affectation à la ligne de tabcomplet. L’affectation directe d’une ligne du data-frame n’est pas possible. En effet, un data-frame est une liste et une ligne d’un data-frame aussi et il est impossible d’affecter une liste dans une ligne de matrice, qui, elle, est un vecteur.

LS0tDQp0aXRsZTogIkNvcnJlY3Rpb24gZGVzIGV4ZXJjaWNlcyBkdSBjaGFwaXRyZSAyIg0KYXV0aG9yOiAiSHVzc29uIGV0IGFsLiINCmRhdGU6ICIwOS8wOS8yMDE4Ig0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOg0KICAgIHRvYzogeWVzDQogICAgdG9jX2RlcHRoOiAzDQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogJzMnDQogICAgdG9jX2Zsb2F0OiB5ZXMNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgY2FjaGUgPSBUUlVFKQ0KYGBgDQoNCg0KIyMgRXhlcmNpY2UgMi4xIDogSW1wb3J0YXRpb24gcm9idXN0ZQ0KDQoNCjEuIEltcG9ydGF0aW9uIHNvdXMgZm9ybWUgZGUgdmVjdGV1ciBkZSBjYXJhY3TDqHJlcyAobGEgcHJlbWnDqHJlIGxpZ25lDQogIGNvbnRpZW50IGRlcyBjYXJhY3TDqHJlcywgZG9uYyBjZSB0eXBlIGVzdCBvYmxpZ2F0b2lyZSk6DQoJDQpgYGB7cixtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9DQp2ZWN0ZXVyIDwtIHNjYW4oImh0dHBzOi8vci1zdGF0LXNjLWRvbm5lZXMuZ2l0aHViLmlvL2Rvbm5lZXMuY3N2Iix3aGF0PSIiLHNlcD0iOyIpDQp2ZWN0ZXVyDQpgYGANCjIuIFLDqWN1cMOpcmF0aW9uIGRlcyBub21zIGRlIGNvbG9ubmVzIHF1aSBjb3JyZXNwb25kZW50IGF1eCBjb29yZG9ubsOpZXMgMiDDoCA1Og0KYGBge3IsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQ0Kbm9tY29sIDwtIHZlY3RldXJbMjo1XQ0Kbm9tY29sDQpgYGANCjMuIENoYW5nZW1lbnQgZGVzIHZpcmd1bGVzIGVuIHBvaW50czoNCmBgYHtyLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0NCnZlY3RldXIgPC0gZ3N1YigiLCIsIi4iLHZlY3RldXIpIA0KYGBgDQoNCjQuIE1hdHJpY2UgYnJ1dGUgZGVzIGRvbm7DqWVzIChhdmVjIG5vbXMgZGVzIGxpZ25lcyBldCBkZXMgY29sb25uZXMpOg0KYGBge3IsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQ0KbWF0YnJ1dCA8LSBtYXRyaXgodmVjdGV1cixucm93PTQsbmNvbD01LGJ5cm93PVRSVUUpDQptYXRicnV0DQpgYGANCg0KNS4gTm9tIGRlcyBsaWduZXMgKHByZW1pw6hyZSBjb2xvbm5lIGRlICptYXRicnV0Kiwgc2F1ZiBsYSBwcmVtacOocmUgY29vcmRvbm7DqWUpOg0KYGBge3IsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQ0Kbm9tbGlnbiA8LSBtYXRicnV0Wy0xLDFdDQpub21saWduDQpkb25uZWVzIDwtIG1hdGJydXRbLTEsLTFdDQpgYGANCg0KNi4gRGF0YS1mcmFtZSBkZXMgZG9ubsOpZXMgYXZlYyBsZSBub20gZGVzIGxpZ25lcyBldCBkZXMgY29sb25uZXMNCmBgYHtyLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0NCmVzc2FpIDwtIGRhdGEuZnJhbWUoZG9ubmVlcykNCnN1bW1hcnkoZXNzYWkpDQpkb25uZWVzIDwtIGRhdGEuZnJhbWUoZG9ubmVlcykNCmNvbG5hbWVzKGRvbm5lZXMpIDwtIG5vbWNvbA0Kcm93bmFtZXMoZG9ubmVlcykgPC0gbm9tbGlnbg0Kc3VtbWFyeShkb25uZWVzKQ0KYGBgDQpMZXMgdHJvaXMgcHJlbWnDqHJlcyB2YXJpYWJsZXMgc29udCBkZXMgZmFjdGV1cnMsIGlsIGZhdXQgZG9uYyBsZXMNCmNvbnZlcnRpciBlbiBudW3DqXJpcXVlOg0KYGBge3IsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQ0KZG9ubmVlc1ssMV0gPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoZG9ubmVlc1ssMV0pKQ0KZG9ubmVlc1ssMl0gPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoZG9ubmVlc1ssMl0pKQ0KZG9ubmVlc1ssM10gPC0gYXMubnVtZXJpYyhhcy5jaGFyYWN0ZXIoZG9ubmVlc1ssM10pKQ0Kc3VtbWFyeShkb25uZWVzKQ0KYGBgDQoNCiMjIEV4ZXJjaWNlIDIuMiBJbXBvcnRhdGlvbg0KYGBge3IsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQ0KdGVzdDEgPC0gcmVhZC50YWJsZSgiaHR0cHM6Ly9yLXN0YXQtc2MtZG9ubmVlcy5naXRodWIuaW8vdGVzdDEuY3N2IixkZWM9IiwiLHNlcD0iOyIsaGVhZGVyPVRSVUUpDQpzdW1tYXJ5KHRlc3QxKQ0KdGVzdDFwcm4gPC0gcmVhZC50YWJsZSgiaHR0cHM6Ly9yLXN0YXQtc2MtZG9ubmVlcy5naXRodWIuaW8vdGVzdDEucHJuIixoZWFkZXI9VFJVRSkNCnN1bW1hcnkodGVzdDFwcm4pDQp0ZXN0MiA8LSByZWFkLnRhYmxlKCJodHRwczovL3Itc3RhdC1zYy1kb25uZWVzLmdpdGh1Yi5pby90ZXN0Mi5jc3YiLHNlcD0iOyIsaGVhZGVyPVRSVUUsbmEuc3RyaW5ncz0iIikNCnN1bW1hcnkodGVzdDIpDQp0ZXN0MyA8LSByZWFkLnRhYmxlKCJodHRwczovL3Itc3RhdC1zYy1kb25uZWVzLmdpdGh1Yi5pby90ZXN0My5jc3YiLHNlcD0iOyIsaGVhZGVyPVRSVUUsbmEuc3RyaW5ncz0iLiIpDQpzdW1tYXJ5KHRlc3QzKQ0KYGBgDQoNCiMjIEV4ZXJjaWNlIDIuMyBJbXBvcnRhdGlvbiBldCBmb3JtYXQgZGF0ZQ0KDQoxLiBJbXBvcnRvbnMgbGUgamV1IGRlIGRvbm7DqWVzIGVuIGTDqWJ1dGFudCDDoCBsYSB0cm9pc2nDqG1lIGxpZ25lIChvbiBzYXV0ZSBsZXMgZGV1eCBwcmVtacOocmVzIGF2ZWMgKnNraXA9MiopOg0KYGBge3IsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQ0Kc2tpIDwtIHJlYWQudGFibGUoImh0dHBzOi8vci1zdGF0LXNjLWRvbm5lZXMuZ2l0aHViLmlvL3Rlc3Q0LmNzdiIsIHNlcD0ifCIsIHNraXA9MiwgaGVhZGVyPVRSVUUsDQogICAgIHJvdy5uYW1lcz0xKQ0Kc3VtbWFyeShza2kpDQpgYGANCjIuIFV0aWxpc29ucyBsZSBmb3JtYXQgKkRhdGUqIHBvdXIgbGEgZGVybmnDqHJlIHZhcmlhYmxlLiBMZSBmb3JtYXQgKlBPU0lYY3QqIHBvdXJyYWl0IMOqdHJlIGludMOpcmVzc2FudCBzaSBsYSBkYXRlIGNvbXBvcnRhaXQgdW5lIGhldXJlLCBjZSBxdWkgbidlc3QgcGFzIGxlIGNhcyBpY2kuDQpgYGB7cixtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9DQpza2kyPC1yZWFkLnRhYmxlKCJodHRwczovL3Itc3RhdC1zYy1kb25uZWVzLmdpdGh1Yi5pby90ZXN0NC5jc3YiLHNlcD0ifCIsc2tpcD0yLGhlYWRlcj1UUlVFLA0KICAgICByb3cubmFtZXM9MSxjb2xDbGFzc2VzPWMoImNoYXJhY3RlciIsIm51bWVyaWMiLA0KICAgICAiZmFjdG9yIiwiRGF0ZSIpKQ0Kc3VtbWFyeShza2kyKQ0KYGBgDQoNCiMjIEV4ZXJjaWNlIDIuNCA6IEltcG9ydGF0aW9uIGV0IGZ1c2lvbg0KDQoxLiBJbXBvcnRhdGlvbnMgZGVzIGpldXggZGUgZG9ubsOpZXM6DQpgYGB7cixtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9DQpldGF0MSA8LSByZWFkLnRhYmxlKCJodHRwczovL3Itc3RhdC1zYy1kb25uZWVzLmdpdGh1Yi5pby9ldGF0MS5jc3YiLHNlcD0iOyIsaGVhZGVyPVRSVUUpDQpldGF0MiA8LSByZWFkLnRhYmxlKCJodHRwczovL3Itc3RhdC1zYy1kb25uZWVzLmdpdGh1Yi5pby9ldGF0Mi5jc3YiLHNlcD0iLCIsaGVhZGVyPVRSVUUpDQpldGF0MyA8LSByZWFkLnRhYmxlKCJodHRwczovL3Itc3RhdC1zYy1kb25uZWVzLmdpdGh1Yi5pby9ldGF0My5jc3YiLHJvdy5uYW1lcz0xLGhlYWRlcj1UUlVFKQ0KYGBgDQoyLiBGdXNpb24gcGFyIGNsZWYgKHZhcmlhYmxlIGNvbW11bmUgKnJlZ2lvbiogcG91ciAqZXRhdDEqIGV0ICpldGF0MiosDQpwdWlzIHZhcmlhYmxlIGNvbW11bmUgKmV0YXQqIHBvdXIgKmV0YXQyKiBldCBsZSB0YWJsZWF1IHByw6ljw6lkZW50KToNCmBgYHtyLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0NCmV0YXQxMyA8LSBtZXJnZShldGF0MSxldGF0MyxieT0icmVnaW9uIikNCmV0YXQxMjMgPC0gbWVyZ2UoZXRhdDIsZXRhdDEzLGJ5PSJldGF0IikNCmhlYWQoZXRhdDEzKQ0KaGVhZChldGF0MTIzKQ0KYGBgDQoNCiMjIDIuNSBFeGVyY2ljZSA6IEZ1c2lvbiBldCBzw6lsZWN0aW9uDQoNCjEuIE91dnJpciBsZXMgZGV1eCBmaWNoaWVycyBzb3VzIGV4Y2VsIChvdSBvcGVub2ZmaWNlKSwNCmxlcyBzYXV2ZWdhcmRlciBzb3VzIGZvcm1hdCB0ZXh0ZSAqLmNzdiogKGF2ZWMNCm9wZW5vZmZpY2UsIGNob2lzaXIgY29tbWUgc8OpcGFyYXRldXIgZGUgY2hhbXBzICo7KikuIEltcG9ydGVyDQplbnN1aXRlIGNlcyBkZXV4IGZpY2hpZXJzIGdyw6JjZSDDoDoNCmBgYHtyLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0NCmZ1c2lvbjEgPC0gcmVhZC50YWJsZSgiaHR0cHM6Ly9yLXN0YXQtc2MtZG9ubmVlcy5naXRodWIuaW8vZnVzaW9uMS5jc3YiLHNlcD0iOyIsZGVjPSIsIixoZWFkZXI9VFJVRSkNCnN1bW1hcnkoZnVzaW9uMSkNCmZ1c2lvbjIgPC0gcmVhZC50YWJsZSgiaHR0cHM6Ly9yLXN0YXQtc2MtZG9ubmVlcy5naXRodWIuaW8vZnVzaW9uMi5jc3YiLHNlcD0iOyIsZGVjPSIsIixoZWFkZXI9VFJVRSkNCnN1bW1hcnkoZnVzaW9uMikNCmBgYA0KMi4gQ29uc2VydmF0aW9uIGRlIGRldXggY29sb25uZXMgcG91ciBjaGFxdWUgdGFibGVhdSBwdWlzIGNyw6lhdGlvbiBkdSBkYXRhLWZyYW1lOg0KYGBge3IsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQ0KZnVzaW9uMSA8LSBmdXNpb24xWyxjKCJ5aGF0MSIsInloYXQzIildDQpmdXNpb24yIDwtIGZ1c2lvbjJbLGMoIlJoYW1ub3MiLCJBcmFiaW5vcyIpXQ0KZG9uIDwtIGNiaW5kKGZ1c2lvbjEsZnVzaW9uMikNCmBgYA0KMy4gQ3LDqWF0aW9uIGRlcyBkZXV4IHZhcmlhYmxlcyBldCBham91dCBkZSBjZWxsZXMtY2kgYXUgZGF0YS1mcmFtZSAqZG9uKjoNCmBgYHtyLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0NCnlyZXMxIDwtIGRvblssInloYXQxIl0tZG9uWywiUmhhbW5vcyJdDQp5cmVzMiA8LSBkb25bLCJ5aGF0MyJdLWRvblssIkFyYWJpbm9zIl0NCmRvbiA8LSBjYmluZC5kYXRhLmZyYW1lKGRvbix5cmVzMSx5cmVzMikNCm5hbWVzKGRvbikNCmhlYWQoZG9uKQ0KYGBgDQoNCiMjIEV4ZXJjaWNlIDIuNiA6IFZlbnRpbGF0aW9uDQoNCjEuIENhbGN1bCBkZXMgZnLDqXF1ZW5jZXMgOg0KYGBge3IsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQ0KdGFibCA8LSB0YWJsZShYcXVhbCkNCnRhYmwvc3VtKHRhYmwpDQpgYGANCg0KMi4gQWZmaWNoYWdlIGRlcyBtb2RhbGl0w6lzIGRvbnQgbCdlZmZlY3RpZiBlc3QgaW5mw6lyaWV1ciDDoCA1ICU6DQpgYGB7cixtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9DQptb2RhbGl0ZXMgPC0gbGV2ZWxzKFhxdWFsKQ0Kc2VsZWN0aSA8LSAodGFibC9zdW0odGFibCkpPDAuMDUNCm1vZGFsaXRlc1tzZWxlY3RpXQ0KYGBgDQozLiBGcsOpcXVlbmNlcyBkZXMgbW9kYWxpdMOpcyBzYW5zIGxhIG1vZGFsaXTDqSAqRCo6DQpgYGB7cixtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9DQpsZXNxdWVscyA8LSBtb2RhbGl0ZXNbIXNlbGVjdGldDQpwcm92IDwtIGZhY3RvcihYcXVhbFsoWHF1YWwlaW4lbGVzcXVlbHMpXSxsZXZlbHM9bGVzcXVlbHMpDQpwcm92IDwtIHRhYmxlKHByb3YpDQpwcm9iYSA8LSBwcm92L3N1bShwcm92KQ0KcHJvYmENCmBgYA0KNC4gU8OpbGVjdGlvbiBkZXMgaW5kaXZpZHVzIHF1aSBwcmVubmVudCBsYSBtb2RhbGl0w6kgKkQqLA0KICB0aXJhZ2UgYWzDqWF0b2lyZSAodmVudGlsYXRpb24pIGRlIGxldXIgbm91dmVsbGUgbW9kYWxpdMOpLiBMZQ0KICBmYWN0ZXVyICpYcXVhbCogcG9zc8OoZGUgZGVzIG1vZGFsaXTDqXMgcXVpIHNvbnQgZMOpY3JpdGVzIGRhbnMNCiAgKmxldmVscyogbWFpcyBxdWkgbmUgc29udCBwbHVzIHJlcHLDqXNlbnTDqWVzIChjJ2VzdC3DoC1kaXJlIGxlcw0KICBtb2RhbGl0w6lzIHF1ZSBsJ29uIGEgdmVudGlsw6llcywgaWNpIGxhIG1vZGFsaXTDqSAqRCopLiBMYQ0KICBkZXJuacOocmUgbGlnbmUgcGVybWV0IGRlIHJlbWV0dHJlIMOgIGpvdXIgbGEgbGlzdGUgZGVzIG5pdmVhdXggZHUNCiAgZmFjdGV1ci4NCmBgYHtyLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0NCmZvciAoaiBpbiBtb2RhbGl0ZXNbc2VsZWN0aV0pIHsNCiAgICMjIHRpcmFnZXMgZGFucyBsZXMgbW9kYWxpdMOpcyBhdSBoYXNhcmQgZXQgcmVtcGxhY2VtZW50DQogICBpZiAobGVuZ3RoKGxlc3F1ZWxzKT09MSkgc3RvcCgiMSBzZXVsZSBtb2RhbGl0ZVxuIikgZWxzZQ0KICAgWHF1YWxbWHF1YWw9PWpdIDwtIHNhbXBsZShsZXNxdWVscyxzdW0oWHF1YWw9PWopLA0KICAgICAgcmVwbGFjZT1UUlVFLCBwcm9iID0gcHJvYmEpDQogfQ0KWHF1YWx2ZW50IDwtIGZhY3Rvcihhcy5jaGFyYWN0ZXIoWHF1YWwpKQ0KWHF1YWx2ZW50DQpgYGANCg0KIyMgRXhlcmNpY2UgMi43IDogVmVudGlsYXRpb24gc3VyIGZhY3RldXIgb3Jkb25uw6kNCg0KMS4gQ2FsY3VsIGRlcyBmcsOpcXVlbmNlczoNCmBgYHtyLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0NClhxdWFsIDwtIGZhY3RvcihjKHJlcCgiMC0xMCIsMSkscmVwKCIxMS0yMCIsMykscmVwKCIyMS0zMCIsNSksDQogICAgIHJlcCgiMzEtNDAiLDIwKSxyZXAoIjQxLTUwIiwyKSxyZXAoIjUxLTYwIiwyKSxyZXAoIjYxLTcwIiwxKSwNCiAgICAgcmVwKCI3MS04MCIsMzEpLHJlcCgiKyBkZSA4MCIsMjApKSkNCnRhYmwgPC0gdGFibGUoWHF1YWwpDQp0YWJsL3N1bSh0YWJsKQ0KYGBgDQoyLiBBZmZpY2hhZ2UgZGVzIG1vZGFsaXTDqXMgw6AgdmVudGlsZXINCmBgYHtyLG1lc3NhZ2U9RkFMU0Usd2FybmluZz1GQUxTRX0NCnAgPC0gMC4wNQ0Kc2VsZWN0aSA8LSAodGFibC9zdW0odGFibCkpPHANCm1vZCA8LSBsZXZlbHMoWHF1YWwpDQptb2Rbc2VsZWN0aV0NCmBgYA0KMy4gVHJvdXZvbnMgbGVzIG51bcOpcm9zIGRlcyBtb2RhbGl0w6lzIMOgIHZlbnRpbGVyLiBFbnN1aXRlIHRhbnQNCnF1J2lsIGV4aXN0ZSB1bmUgbW9kYWxpdMOpIGRvbnQgbCdlZmZlY3RpZiBlc3QgaW5mw6lyaWV1ciDDoCAgNSAlLCBpbA0KYXV0IHZlbnRpbGVyIChldCBzdXBwcmltZXIgY2V0dGUgbW9kYWxpdMOpIGRlIGxhIGxpc3RlIGRlcyBuaXZlYXV4KToNCmMnZXN0IGwnb2JqZXQgZGUgbGEgYm91Y2xlICp3aGlsZSouDQpgYGB7cixtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9DQpudW1lcm8gPC0gd2hpY2goc2VsZWN0aSkNCndoaWxlKGFueSgodGFibC9zdW0odGFibCkpPHApKSB7DQogICAjIyBwcmVub25zIGxhIHByZW1pZXJlIG1vZGFsaXRlIGRvbnQgbCdlZmZlY3RpZiBlc3QgdHJvcCBmYWlibGUNCiAgIGogPC0gd2hpY2goKCh0YWJsL3N1bSh0YWJsKSk8cCkpWzFdDQogICBLIDwtIGxlbmd0aChtb2QpICAjIGVmZmVjdGlmIGRlcyBtb2RhbGl0ZXMgbWlzIMOgIGpvdXINCiAgICMjIGZ1c2lvbiBhdmVjIG1vZGFsaXRlIGQnYXByZXMgb3UgZCdhdmFudCBwb3VyIGxhIGRlcm5pZXJlDQogICBpZiAoajxLKSB7DQogICAgIGlmICgoaj4xKSYoajxLLTEpKSB7DQogICAgICAgbGV2ZWxzKFhxdWFsKSA8LSBjKG1vZFsxOihqLTEpXSxwYXN0ZShtb2Rbal0sDQogICAgICAgIG1vZFtqKzFdLHNlcD0iLiIpLHBhc3RlKG1vZFtqXSxtb2RbaisxXSxzZXA9Ii4iKSwNCiAgICAgICAgbW9kW2orMjpLXSl9DQogICAgIGlmIChqPT0xKSB7DQogICAgICAgbGV2ZWxzKFhxdWFsKSA8LSBjKHBhc3RlKG1vZFtqXSxtb2RbaisxXSxzZXA9Ii4iKSwNCiAgICAgICBwYXN0ZShtb2Rbal0sbW9kW2orMV0sc2VwPSIuIiksbW9kW2orMjpLXSkgfQ0KICAgICBpZiAoaj09KEstMSkpICB7DQogICAgICAgbGV2ZWxzKFhxdWFsKSA8LSBjKG1vZFsxOihqLTEpXSxwYXN0ZShtb2Rbal0sDQogICAgICAgbW9kW2orMV0sc2VwPSIuIikscGFzdGUobW9kW2pdLG1vZFtqKzFdLHNlcD0iLiIpKSB9DQogICB9IGVsc2Ugew0KICAgICBsZXZlbHMoWHF1YWwpIDwtIGMobW9kWzE6KGotMildLHBhc3RlKG1vZFtqLTFdLA0KICAgICAgbW9kW2pdLHNlcD0iLiIpLHBhc3RlKG1vZFtqLTFdLG1vZFtqXSxzZXA9Ii4iKSkNCiAgIH0NCiAgIHRhYmwgPC0gdGFibGUoWHF1YWwpICMjIG1pc2Ugw6Agam91ciBkZSBsYSB0YWJsZQ0KICAgbW9kIDwtIGxldmVscyhYcXVhbCkgICAgICAgICAgICAgIyBldCBkZXMgbW9kYWxpdGVzDQp9DQpYcXVhbA0KYGBgDQoNCiMjIEV4ZXJjaWNlIDIuOCBEdSB0YWJsZWF1IGNyb2lzw6kgYXUgdGFibGVhdSBkZSBkb25uw6llcw0KDQoxLiBMYSBjcsOpYXRpb24gZCd1bmUgdGFibGUgZGUgY29udGluZ2VuY2UgcGV1dA0Kc2ltcGxlbWVudCBzZSBmYWlyZSBwYXIgbGEgY3LDqWF0aW9uIGRlIGxhIG1hdHJpY2U6DQpgYGB7cixtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9DQpjb250aW5nIDwtIG1hdHJpeChjKDIsMSwzLDAsMCw0KSwyLDMpDQpjb2xuYW1lcyhjb250aW5nKSA8LSBjKCJBbmciLCJNZXIiLCJUZXgiKQ0Kcm93bmFtZXMoY29udGluZykgPC0gYygiRmFpYmxlIiwiRm9ydGUiKQ0KYGBgDQoyLiBMYSBtYXRyaWNlICpjb250aW5nKiBuJ2VzdCBwYXMgdW4gdHlwZSAqdGFibGUqLiBJbCBsdWkNCm1hbnF1ZSBkZXMgYXR0cmlidXRzLiBBdXNzaSBsJ29ww6lyYXRpb24gaW52ZXJzZSBzaW1wbGUgKGdyw6JjZSDDoA0KKmFzLmRhdGEuZnJhbWUqKSBuJ2VzdCBwYXMgcG9zc2libGUgZGlyZWN0ZW1lbnQuIFVuZSBzb2x1dGlvbiBjb25zaXN0ZSDDoCBham91dGVyIGxlcyBhdHRyaWJ1dHMgw6AgY2V0dGUgbWF0cmljZS4gUGx1cyBzaW1wbGVtZW50IG5vdXMgYWxsb25zIGNvbnN0aXR1ZXIgY2UgdGFibGVhdSDDoCBsYSBtYWluOg0KYGBge3IsbWVzc2FnZT1GQUxTRSx3YXJuaW5nPUZBTFNFfQ0KdGFibWF0IDwtIG1hdHJpeCgiIixsZW5ndGgoY29udGluZyksMykNCnRhYm1hdFssM10gPC0gYXMudmVjdG9yKGNvbnRpbmcpDQp0YWJtYXRbLDJdIDwtIHJlcChyb3duYW1lcyhjb250aW5nKSxuY29sKGNvbnRpbmcpKQ0KdGFibWF0WywxXSA8LSByZXAoY29sbmFtZXMoY29udGluZyksZWFjaD1ucm93KGNvbnRpbmcpKQ0KYGBgDQoNCjMuIE1pc2UgZW4gcGxhY2UgZHUgZGF0YS1mcmFtZSAqdGFiZnJhbWUqLiBMYSBjb2xvbm5lIDMsIHF1aSBlc3QgdHJhbnNmb3Jtw6llIGVuIGZhY3RldXIgcGFyIGxhIGZvbmN0aW9uICpkYXRhZnJhbWUqIGRvaXQgw6p0cmUgdHJhbnNmb3Jtw6llIGVuIG51bcOpcmlxdWUuIEVuc3VpdGUsIGwnZWZmZWN0aWYgdG90YWwgZXQgbGUgbm9tYnJlIGRlIGZhY3RldXIgc29udCBjYWxjdWzDqXM6DQpgYGB7cixtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9DQp0YWJmcmFtZSA8LSBkYXRhLmZyYW1lKHRhYm1hdCkNCnRhYmZyYW1lWywzXSA8LSBhcy5udW1lcmljKGFzLmNoYXJhY3Rlcih0YWJmcmFtZVssM10pKQ0KdGFiZnJhbWUNCm4gPC0gc3VtKHRhYmZyYW1lWywzXSkNCm5iZWZhYyA8LSBuY29sKHRhYmZyYW1lKS0xDQpgYGANCjQuIENyw6lhdGlvbiBkZSBsYSBtYXRyaWNlICp0YWJjb21wbGV0KiBldCBkdSBjb21wdGV1ciA6DQpgYGB7cixtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9DQp0YWJjb21wbGV0IDwtIG1hdHJpeCgiIixuLG5iZWZhYykNCml0ZXIgPC0gMQ0KYGBgDQo1LiBTdXIgbGVzIGRldXggcHJlbWnDqHJlcyBsaWduZXMsIG5vdXMgb3DDqXJvbnMgbGEgYm91Y2xlIHN1ciB0b3V0ZXMNCiAgbGVzIGxpZ25lcyBkdSB0YWJsZWF1ICp0YWJmcmFtZSogZXQgbGUgY29udHLDtGxlIGRlIGwnZWZmZWN0aWYNCiAgKG5vbiBudWwpLiBFbnN1aXRlLCBub3VzIHLDqXDDqXRvbnMgYXV0YW50IGRlIGZvaXMgcXVlIGwnZWZmZWN0aWYgbGUNCiAgZGVtYW5kZSBsJ2FmZmVjdGF0aW9uIGRlcyBtb2RhbGl0w6lzIChkb25jIHBhcyBsYSBkZXJuacOocmUgY29sb25uZSBkZQ0KICAqdGFibWF0KiBxdWkgY29udGllbnQgbGVzIGVmZmVjdGlmcykgZGFucyBsZSBub3V2ZWF1DQogIHRhYmxlYXUuIEwnaW5kaWNlIGRlcyBsaWduZXMgZGUgY2UgZGVybmllciBlc3QgZ8OpcsOpIHBhciBsZSBjb21wdGV1cg0KICAqaXRlciouDQpgYGB7cixtZXNzYWdlPUZBTFNFLHdhcm5pbmc9RkFMU0V9DQpmb3IgKGkgaW4gMTpucm93KHRhYmZyYW1lKSkgew0KICAgaWYgKHRhYmZyYW1lW2ksM10+MCkgew0KICAgICBmb3IgKGogaW4gMTp0YWJmcmFtZVtpLDNdKSB7DQogICAgICAgdGFiY29tcGxldFtpdGVyLF0gPC0gdGFibWF0W2ksLW5jb2wodGFiZnJhbWUpXQ0KICAgICAgIGl0ZXIgPC0gaXRlcisxDQogICAgIH0NCiAgIH0NCiB9DQpkYXRhLmZyYW1lKHRhYmNvbXBsZXQpDQpgYGANCkxhIG1hdHJpY2UgKnRhYm1hdCogZXN0IHV0aWxpc8OpZSBkYW5zIGwnYWZmZWN0YXRpb24gw6AgbGEgbGlnbmUgZGUNCip0YWJjb21wbGV0Ki4gTCdhZmZlY3RhdGlvbiBkaXJlY3RlIGQndW5lIGxpZ25lIGR1IGRhdGEtZnJhbWUNCm4nZXN0IHBhcyBwb3NzaWJsZS4gRW4gZWZmZXQsIHVuIGRhdGEtZnJhbWUgZXN0IHVuZSBsaXN0ZSBldCB1bmUgbGlnbmUNCmQndW4gZGF0YS1mcmFtZSBhdXNzaSBldCBpbCBlc3QgaW1wb3NzaWJsZSBkJ2FmZmVjdGVyIHVuZSBsaXN0ZSBkYW5zDQp1bmUgbGlnbmUgZGUgbWF0cmljZSwgcXVpLCBlbGxlLCBlc3QgdW4gdmVjdGV1ci4NCg==