Required packages
library(readr)
library(dplyr)
library(tidyr)
library(forecast)
library(Hmisc)
library(outliers)
library(ggplot2)
library(editrules)
Executive Summary
The report involves two datasets that provides an overview about heroes and their physical as well as power characteristics.The data sets were merged together by using an appropriate matching column for performing preprocessing tasks on the final dataset.The original dataset describing the stats of the superheroes was subsetted to avoid duplicate columns.The structure of the dataset and variable class type was analysed and it was observed that there were 9 numerical and 8 character variables.Certain character variables were then converted into factors by defining the levels and assigning appropriate labels.The dataset conforms to the tidy data principles and is already in a tidy format so no tidy tasks were performed.A new variable ’ BMI’ was created using the mutate function that determines how fit a superhero is.The data set was then scanned for any missing values and special values along with any obvious errors or consistencies using appropriate functions.The missing values for categorical variables was classified as ‘unknown/not specified’ whereas the missing values in the numerical variables were imputed by the mean of the corresponding variables. The numeric variables in the dataset is scanned for outliers by using boxplot function and the outlier values are replaced using a cap function.The outlier values lying beyond the fences are replaced with the 5th or 95th percentile value. Finally for the transformation task,z-score standardization was performed on certain variables to make the distribution more normal and scale the data range. A linear relationship was established between the height and the weight of the superhero using a scatterplot.The graph showed a positive correlation between the two variables.
Data
Superheroes Dataset
The two datasets taken for performing data preprocessing contains information about different superheroes and their stats. The first dataset contains various information about the superhero’s appearance,their body measurements etc.There are about 734 observations and 10 variables in this dataset. Variable descriptions are as follows:
Name: Name of the superhero
Gender: Gender of the superhero
Eye color: Eye color of the superhero
Race: Species of the superhero
Height: Height of the superhero(in cms)
Publisher: Comic category of the superhero
Skin color: Skin color of the superhero
Alignment: Superhero’s nature.
Weight: Weight of the superhero(in kgs)
The second dataset records the superhero attributes such as their strength level,speed level etc.There are about 611 observations and 9 variables in the dataset. Variable descriptions are as follows:
Name: Name of the superhero
Alignment: Superhero’s nature.
Intelligence:Intelligence stats of the superhero
Strength:Strength stats of the superhero
Speed: Speed stats of the superhero
Durability: Durability stats of the superhero
Power: Power level of the superhero
Combat: Combat level of the superhero
Total: Total combined stats of the superhero
Data source:
https://www.kaggle.com/claudiodavi/superhero-set
https://www.kaggle.com/magshimimsummercamp/superheroes-info-and-stats#superheroes_stats.csv
The superhero stats dataset also contains the ‘alignment’ variable and hence has been excluded from the original dataset.The two datasets have been merged on the superhero name by using the mutating join function from the dplyr package bringing together all the details regarding the superheroes.
heroes_info <-read_csv("heroes_information.csv")
head(heroes_info)
heroes_stats <- read_csv("superheroes_stats.csv")
head(heroes_stats)
heroes_stats1 <- subset(heroes_stats[,c(1,3:9)])
heroes_combined <- inner_join(heroes_info, heroes_stats1, by = "Name")
head(heroes_combined)
Understand
- The structure and the type of variable has been identified using the ‘str’ function.The dataset comprised of 8 character and 9 numeric variables. Certain character variables such as gender,hair color etc. were converted into factors by defining the levels and appropriate labels using the factor function.
str(heroes_combined)
Classes ‘spec_tbl_df’, ‘tbl_df’, ‘tbl’ and 'data.frame': 600 obs. of 17 variables:
$ Name : chr "A-Bomb" "Abe Sapien" "Abin Sur" "Abomination" ...
$ Gender : chr "Male" "Male" "Male" "Male" ...
$ Eye color : chr "yellow" "blue" "blue" "green" ...
$ Race : chr "Human" "Icthyo Sapien" "Ungaran" "Human / Radiation" ...
$ Hair color : chr "No Hair" "No Hair" "No Hair" "No Hair" ...
$ Height : num 203 191 185 203 -99 -99 185 178 191 188 ...
$ Publisher : chr "Marvel Comics" "Dark Horse Comics" "DC Comics" "Marvel Comics" ...
$ Skin color : chr "-" "blue" "red" "-" ...
$ Alignment : chr "good" "good" "good" "bad" ...
$ Weight : num 441 65 90 441 -99 -99 88 81 104 108 ...
$ Intelligence: num 38 88 50 63 88 63 NA 10 75 50 ...
$ Strength : num 100 14 90 80 100 10 NA 8 28 85 ...
$ Speed : num 17 35 53 53 83 12 NA 13 38 100 ...
$ Durability : num 80 42 64 90 99 100 NA 5 80 85 ...
$ Power : num 17 35 84 55 100 71 NA 5 72 100 ...
$ Combat : num 64 85 65 95 56 64 NA 20 95 40 ...
$ Total : num 316 299 406 436 526 320 NA 61 388 460 ...
table(sapply(heroes_combined, class))
character numeric
8 9
attributes(heroes_combined[1:17, ])
$row.names
[1] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
$class
[1] "tbl_df" "tbl" "data.frame"
$names
[1] "Name" "Gender" "Eye color" "Race" "Hair color" "Height" "Publisher" "Skin color" "Alignment"
[10] "Weight" "Intelligence" "Strength" "Speed" "Durability" "Power" "Combat" "Total"
heroes_combined$Gender <- factor(heroes_combined$Gender,levels = c('Male','Female'))
heroes_combined$`Eye color` <- factor(heroes_combined$`Eye color`,levels = c('amber','black','blue','blue / white','brown','gold','green','green / blue','grey','hazel','indigo','purple','red','silver','violet','white','white / red','yellow'
,'yellow (without irises)','yellow / blue','yellow / red'),labels=c('amber','black','blue','blue&white','brown','gold','green','green & blue', 'grey','hazel','indigo','purple','red','silver','violet','white','white & red','yellow'
,'yellow(No iris)','yellow & blue','yellow & red'))
heroes_combined$Race <- factor(heroes_combined$Race,levels=c('Alien','Alpha','Amazon','Android','Animal','Asgardian','Atlantean','Bizarro','Bolovaxian','Clone','Cosmic Entity','Cyborg','Czarnian','Dathomirian Zabrak','Demi-God','Demon','Eternal','Flora Colossus','Frost Giant','God / Eternal','Gorilla','Gungan','Human','Human / Altered', 'Human / Clone','Human / Cosmic','Human / Radiation','Human-Kree','Human-Spartoi','Human-Vulcan','Human-Vuldarian','Icthyo Sapien','Inhuman','Kaiju','Kakarantharaian','Korugaran','Kryptonian','Luphomoid','Maiar','Martian','Metahuman','Mutant','Mutant / Clone','New God','Neyaphem','Parademon','Planet','Rodian','Saiyan','Spartoi','Strontian','Symbiote','Talokite','Tamaranean','Ungaran','Vampire','Xenomorph XX121','Yautja','Yoda species','Zen-Whoberian','Zombie'))
heroes_combined$`Hair color` <- factor(heroes_combined$`Hair color`,levels=c('Auburn','Black / Blue','Blond','Blue','Brown','Brown / Black','Brown / White','Gold','Green','Grey','Indigo','Magenta','No Hair','Orange','Orange / White','Pink','Purple','Red','Red / Grey','Red / Orange','Red / White','Silver','Strawberry Blond','White','Yellow'))
heroes_combined$Publisher <- factor(heroes_combined$Publisher,levels=c('ABC Studios','Dark Horse Comics','DC Comics','George Lucas','Hanna-Barbera','HarperCollins','Icon Comics','IDW Publishing','Image Comics','J. K. Rowling','J. R. R. Tolkien','Marvel Comics','Microsoft','NBC - Heroes','Rebellion','Shueisha','Sony Pictures','South Park','Star Trek','SyFy','Team Epic TV','Titan Books','Universal Studios','Wildstorm'))
heroes_combined$`Skin color` <- factor(heroes_combined$`Skin color`,levels=c('black','blue','blue-white','gold','gray','green','grey','orange','orange / white','pink','purple','red','red / black','silver','white','yellow'))
heroes_combined$Alignment <- factor(heroes_combined$Alignment,levels=c('good','bad','neutral'))
str(heroes_combined)
Classes ‘spec_tbl_df’, ‘tbl_df’, ‘tbl’ and 'data.frame': 600 obs. of 17 variables:
$ Name : chr "A-Bomb" "Abe Sapien" "Abin Sur" "Abomination" ...
$ Gender : Factor w/ 2 levels "Male","Female": 1 1 1 1 1 1 1 1 1 1 ...
$ Eye color : Factor w/ 21 levels "amber","black",..: 18 3 3 7 3 3 3 5 NA 3 ...
$ Race : Factor w/ 61 levels "Alien","Alpha",..: 23 32 55 27 11 NA 23 23 NA NA ...
$ Hair color : Factor w/ 25 levels "Auburn","Black / Blue",..: 13 13 13 13 NA 3 3 5 NA 24 ...
$ Height : num 203 191 185 203 -99 -99 185 178 191 188 ...
$ Publisher : Factor w/ 24 levels "ABC Studios",..: 12 2 3 12 12 14 3 12 12 12 ...
$ Skin color : Factor w/ 16 levels "black","blue",..: NA 2 12 NA NA NA NA NA NA NA ...
$ Alignment : Factor w/ 3 levels "good","bad","neutral": 1 1 1 2 2 1 1 1 1 2 ...
$ Weight : num 441 65 90 441 -99 -99 88 81 104 108 ...
$ Intelligence: num 38 88 50 63 88 63 NA 10 75 50 ...
$ Strength : num 100 14 90 80 100 10 NA 8 28 85 ...
$ Speed : num 17 35 53 53 83 12 NA 13 38 100 ...
$ Durability : num 80 42 64 90 99 100 NA 5 80 85 ...
$ Power : num 17 35 84 55 100 71 NA 5 72 100 ...
$ Combat : num 64 85 65 95 56 64 NA 20 95 40 ...
$ Total : num 316 299 406 436 526 320 NA 61 388 460 ...
Tidy & Manipulate Data I
- For the dataset to be tidy,the dataset should satisfy the tidy data principles i.e
- Each variable must have its own column.
- Each observation must have its own row.
- Each value must have its own cell.
From the dataset,we can observe that each of the rules have been satisfied and hence no tidy functions need to be applied on the dataset.
Tidy & Manipulate Data II
- A new variable ‘BMI’ has been created for each superhero which involves division of weight by height variable.The BMI describes how fit the superhero is.
heroes_combined <- mutate(heroes_combined,BMI=heroes_combined$Weight/heroes_combined$Height)
head(heroes_combined)
Scan I
- The dataset is scanned for any missing values,special values and obvious errors/inconsistencies by using appropriate functions.We observe that there are multiple missing values in the factored and numeric variables with the total count being 249. From the dataset,it can also be observed that there are certain missing values coded as -99 in the height and weight variables.These values have been converted into null values by using the na_if function.
#Checking for NULL values in the dataset
colSums(is.na(heroes_combined))
Name Gender Eye color Race Hair color Height Publisher Skin color Alignment Weight Intelligence
0 24 139 299 271 0 7 556 3 0 170
Strength Speed Durability Power Combat Total BMI
169 171 172 172 170 172 0
sum(is.na(heroes_combined))
[1] 2495
#Checking for infinite and NaN values
is.special <- function(x){
if (is.numeric(x)) (is.infinite(x) | is.nan(x))
}
sapply(heroes_combined, function(x) sum(is.special(x)))
Name Gender Eye color Race Hair color Height Publisher Skin color Alignment Weight Intelligence
0 0 0 0 0 0 0 0 0 0 0
Strength Speed Durability Power Combat Total BMI
0 0 0 0 0 0 0
#Checking for obvious errors and inconsistencies
(Rule1 <- editset(c("Height >= 0","Weight >=0")))
Edit set:
num1 : 0 <= Height
num2 : 0 <= Weight
Violated <- violatedEdits(Rule1,heroes_combined)
summary(Violated)
Edit violations, 600 observations, 0 completely missing (0%):
Edit violations per record:
#Recoding -99 value as NAs
heroes_combined$Weight <- heroes_combined$Weight %>% na_if(-99)
heroes_combined$Height <- heroes_combined$Height %>% na_if(-99)
- In order to deal with the missing values in the categorical variables,the missing values have been classified as ‘unknown/not specified’ by defining a new level.As for the missing values in the numeric variables,the number of missing values is quite large and excluding the missing values would cause discrepancies in the dataset.
- The missing values have been imputed with the mean value for the numeric variables in this case using the impute function from the ‘hmisc’ package so that there is consistency in the data. We use the colsums(is.na) & sum(is.na) function to verify that there are no missing values in the dataset.
# Handling missing values for the variables
sum(is.na(heroes_combined$Gender))
[1] 24
levels(heroes_combined$Gender) = c(levels(heroes_combined$Gender), "Not Specified")
heroes_combined$Gender[is.na(heroes_combined$Gender)] <- "Not Specified"
sum(is.na(heroes_combined$`Eye color`))
[1] 139
levels(heroes_combined$`Eye color`) = c(levels(heroes_combined$`Eye color`), "Unknown")
heroes_combined$`Eye color`[is.na(heroes_combined$`Eye color`)] <- "Unknown"
sum(is.na(heroes_combined$`Race`))
[1] 299
levels(heroes_combined$`Race`) = c(levels(heroes_combined$`Race`), "Unknown")
heroes_combined$`Race`[is.na(heroes_combined$`Race`)] <- "Unknown"
sum(is.na(heroes_combined$`Hair color`))
[1] 271
levels(heroes_combined$`Hair color`) = c(levels(heroes_combined$`Hair color`), "Unknown")
heroes_combined$`Hair color`[is.na(heroes_combined$`Hair color`)] <- "Unknown"
sum(is.na(heroes_combined$`Publisher`))
[1] 7
levels(heroes_combined$`Publisher`) = c(levels(heroes_combined$`Publisher`), "Unknown")
heroes_combined$`Publisher`[is.na(heroes_combined$`Publisher`)] <- "Unknown"
sum(is.na(heroes_combined$`Skin color`))
[1] 556
levels(heroes_combined$`Skin color`) = c(levels(heroes_combined$`Skin color`), "Unknown")
heroes_combined$`Skin color`[is.na(heroes_combined$`Skin color`)] <- "Unknown"
sum(is.na(heroes_combined$`Alignment`))
[1] 3
levels(heroes_combined$`Alignment`) = c(levels(heroes_combined$`Alignment`), "Unknown")
heroes_combined$`Alignment`[is.na(heroes_combined$`Alignment`)] <- "Unknown"
#Imputing the values with Mean for numeric variables
heroes_combined$Weight<-impute(heroes_combined$Weight,fun = mean)
heroes_combined$Height<-impute(heroes_combined$Height,fun = mean)
heroes_combined$Intelligence<-impute(heroes_combined$Intelligence,fun = mean)
heroes_combined$Strength<-impute(heroes_combined$Strength,fun = mean)
heroes_combined$Speed<-impute(heroes_combined$Speed,fun = mean)
heroes_combined$Durability<-impute(heroes_combined$Durability,fun = mean)
heroes_combined$Power<-impute(heroes_combined$Power,fun = mean)
heroes_combined$Combat<-impute(heroes_combined$Combat,fun = mean)
heroes_combined$Total<-impute(heroes_combined$Total,fun = mean)
heroes_combined$BMI<-impute(heroes_combined$BMI,fun = mean)
colSums(is.na(heroes_combined))
Name Gender Eye color Race Hair color Height Publisher Skin color Alignment Weight Intelligence
0 0 0 0 0 0 0 0 0 0 0
Strength Speed Durability Power Combat Total BMI
0 0 0 0 0 0 0
sum(is.na(heroes_combined))
[1] 0
Scan II
- In order to check for outliers in the dataset,boxplots have been plotted for each numeric variable.With missing values handled effectively in the previous step,the Tukey’s method of outlier detection can be used to detect any outliers in the boxplot.From the boxplot method we observe that there are outliers in 7 numeric variables in the dataset.
- The cap function has been used to replace the outlier values that lie below the value of the lower fence (Q1 - 1.5 x IQR) with the value of 5th percentile and replace the outlier values that lie above the value of the upper fence (Q3 + 1.5 x IQR) with the value of the 95th percentile. Summary function has been used to check the summary statistics of the numeric variables before and after capping.
boxplot(as.numeric(heroes_combined$Weight),main="Boxplot of Superhero's weight",ylab="Weight",col="grey")

boxplot(as.numeric(heroes_combined$Height),main="Boxplot of Superhero's height",ylab="Height",col="grey")

boxplot(as.numeric(heroes_combined$Intelligence),main="Boxplot of Superhero's intelligence",ylab="Intelligence",col="grey")

boxplot(as.numeric(heroes_combined$Strength),main="Boxplot of Superhero's strength",ylab="Strength",col="grey")

boxplot(as.numeric(heroes_combined$Speed),main="Boxplot of Superhero's speed",ylab="Speed",col="grey")

boxplot(as.numeric(heroes_combined$Durability),main="Boxplot of Superhero's durability",ylab="Durability",col="grey")

boxplot(as.numeric(heroes_combined$Power),main="Boxplot of Superhero's power",ylab="Power",col="grey")

boxplot(as.numeric(heroes_combined$Combat),main="Boxplot of Superhero's combat",ylab="Combat",col="grey")

boxplot(as.numeric(heroes_combined$Total),main="Boxplot of Superhero's total stats",ylab="Total stats",col="grey")

boxplot(as.numeric(heroes_combined$BMI),main="Boxplot of Superhero's BMI",ylab="BMI",col="grey")

# Capping outliers for the 7 numeric variables that have outliers
cap <- function(x){
quantiles <- quantile( x, c(.05, 0.25, 0.75, .95 ) )
x[ x < quantiles[2] - 1.5*IQR(x) ] <- quantiles[1]
x[ x > quantiles[3] + 1.5*IQR(x) ] <- quantiles[4]
x
}
heroes_combined$Weight <- heroes_combined$Weight %>% cap()
boxplot(as.numeric(heroes_combined$Weight),main="Boxplot of Superhero's weight",ylab="Weight",col="blue")

heroes_combined$Height <- heroes_combined$Height %>% cap()
boxplot(as.numeric(heroes_combined$Height ),main="Boxplot of Superhero's height",ylab="Height",col="blue")

heroes_combined$Intelligence <- heroes_combined$Intelligence %>% cap()
boxplot(as.numeric(heroes_combined$Intelligence),main="Boxplot of Superhero's intelligence",ylab="Intelligence",col="blue")

heroes_combined$Speed <- heroes_combined$Speed %>% cap()
boxplot(as.numeric(heroes_combined$Speed),main="Boxplot of Superhero's Speed",ylab="Speed",col="blue")

heroes_combined$Combat <- heroes_combined$Combat %>% cap()
boxplot(as.numeric(heroes_combined$Combat),main="Boxplot of Superhero's Combat",ylab="Combat",col="blue")

heroes_combined$Total <- heroes_combined$Total %>% cap()
boxplot(as.numeric(heroes_combined$Total),main="Boxplot of Superhero's Total stats",ylab="Total Stats",col="blue")

heroes_combined$BMI <- heroes_combined$BMI %>% cap()
boxplot(as.numeric(heroes_combined$BMI),main="Boxplot of Superhero's BMI",ylab="BMI",col="blue")

#Summarizing the numeric variables after capping
summary(heroes_combined[, c(6, 10:18)])
172 values imputed to 187.8131
180 values imputed to 114.2619
170 values imputed to 63.03721
169 values imputed to 41.35499
171 values imputed to 38.70629
172 values imputed to 59.89019
172 values imputed to 57.62617
170 values imputed to 60.77674
172 values imputed to 321.9346
Height Weight Intelligence Strength Speed Durability Power Combat
Min. :163.0 Min. : 16.0 Min. : 25.00 Min. : 4.00 Min. : 8.00 Min. : 5.00 Min. : 5.00 Min. :28.00
1st Qu.:178.0 1st Qu.: 74.0 1st Qu.: 50.00 1st Qu.: 12.00 1st Qu.:25.00 1st Qu.: 42.00 1st Qu.: 43.00 1st Qu.:56.00
Median :187.8 Median :101.0 Median : 63.04 Median : 41.35 Median :38.71 Median : 59.89 Median : 57.63 Median :60.78
Mean :184.5 Mean :106.3 Mean : 63.54 Mean : 41.35 Mean :38.32 Mean : 59.89 Mean : 57.63 Mean :60.98
3rd Qu.:188.0 3rd Qu.:114.3 3rd Qu.: 75.00 3rd Qu.: 55.00 3rd Qu.:42.00 3rd Qu.: 80.00 3rd Qu.: 69.00 3rd Qu.:70.00
Max. :211.0 Max. :249.1 Max. :100.00 Max. :100.00 Max. :83.00 Max. :120.00 Max. :100.00 Max. :95.00
Total BMI
Min. :147.0 Min. :-0.4648
1st Qu.:271.8 1st Qu.: 0.4000
Median :321.9 Median : 0.5277
Mean :320.7 Mean : 0.6718
3rd Qu.:359.0 3rd Qu.: 1.0000
Max. :491.1 Max. : 1.8182
LS0tCnRpdGxlOiAiTUFUSDIzNDkgU2VtZXN0ZXIgMiwgMjAxOSIKYXV0aG9yOiAiQXNod2luIEFsZXggRmVybmFuZGVzIC0gUzM4MDM1ODE8YnI+IFN1bWVldCBDaGluZGFsaWEgLSBTMzc3NDY4NTxicj5BZGFyc2ggS3VtYXIgRGFzIC0gUzM3NjY1NzgiIApzdWJ0aXRsZTogQXNzaWdubWVudCAzCm91dHB1dDoKICBodG1sX25vdGVib29rOiBkZWZhdWx0Ci0tLQojIyBSZXF1aXJlZCBwYWNrYWdlcyAKYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHRpZHlyKQpsaWJyYXJ5KGZvcmVjYXN0KQpsaWJyYXJ5KEhtaXNjKQpsaWJyYXJ5KG91dGxpZXJzKQpsaWJyYXJ5KGdncGxvdDIpCmxpYnJhcnkoZWRpdHJ1bGVzKQpgYGAKIyMgRXhlY3V0aXZlIFN1bW1hcnkgClRoZSByZXBvcnQgaW52b2x2ZXMgdHdvIGRhdGFzZXRzIHRoYXQgcHJvdmlkZXMgYW4gb3ZlcnZpZXcgYWJvdXQgaGVyb2VzIGFuZCB0aGVpciBwaHlzaWNhbCBhcyB3ZWxsIGFzIHBvd2VyIGNoYXJhY3RlcmlzdGljcy5UaGUgZGF0YSBzZXRzIHdlcmUgbWVyZ2VkIHRvZ2V0aGVyIGJ5IHVzaW5nIGFuIGFwcHJvcHJpYXRlIG1hdGNoaW5nIGNvbHVtbiBmb3IgcGVyZm9ybWluZyBwcmVwcm9jZXNzaW5nIHRhc2tzIG9uIHRoZSBmaW5hbCBkYXRhc2V0LlRoZSBvcmlnaW5hbCBkYXRhc2V0IGRlc2NyaWJpbmcgdGhlIHN0YXRzIG9mIHRoZSBzdXBlcmhlcm9lcyB3YXMgc3Vic2V0dGVkIHRvIGF2b2lkIGR1cGxpY2F0ZSBjb2x1bW5zLlRoZSBzdHJ1Y3R1cmUgb2YgdGhlIGRhdGFzZXQgYW5kIHZhcmlhYmxlIGNsYXNzIHR5cGUgd2FzIGFuYWx5c2VkIGFuZCBpdCB3YXMgb2JzZXJ2ZWQgdGhhdCB0aGVyZSB3ZXJlIDkgbnVtZXJpY2FsIGFuZCA4IGNoYXJhY3RlciB2YXJpYWJsZXMuQ2VydGFpbiBjaGFyYWN0ZXIgdmFyaWFibGVzIHdlcmUgdGhlbiBjb252ZXJ0ZWQgaW50byBmYWN0b3JzIGJ5IGRlZmluaW5nIHRoZSBsZXZlbHMgYW5kIGFzc2lnbmluZyBhcHByb3ByaWF0ZSBsYWJlbHMuVGhlIGRhdGFzZXQgY29uZm9ybXMgdG8gdGhlIHRpZHkgZGF0YSBwcmluY2lwbGVzIGFuZCBpcyBhbHJlYWR5IGluIGEgdGlkeSBmb3JtYXQgc28gbm8gdGlkeSB0YXNrcyB3ZXJlIHBlcmZvcm1lZC5BIG5ldyB2YXJpYWJsZSAnIEJNSScgd2FzIGNyZWF0ZWQgdXNpbmcgdGhlIG11dGF0ZSBmdW5jdGlvbiB0aGF0IGRldGVybWluZXMgaG93IGZpdCBhIHN1cGVyaGVybyBpcy5UaGUgZGF0YSBzZXQgd2FzIHRoZW4gc2Nhbm5lZCBmb3IgYW55IG1pc3NpbmcgdmFsdWVzIGFuZCBzcGVjaWFsIHZhbHVlcyBhbG9uZyB3aXRoIGFueSBvYnZpb3VzIGVycm9ycyBvciBjb25zaXN0ZW5jaWVzIHVzaW5nIGFwcHJvcHJpYXRlIGZ1bmN0aW9ucy5UaGUgbWlzc2luZyB2YWx1ZXMgZm9yIGNhdGVnb3JpY2FsIHZhcmlhYmxlcyB3YXMgY2xhc3NpZmllZCBhcyAndW5rbm93bi9ub3Qgc3BlY2lmaWVkJyB3aGVyZWFzIHRoZSBtaXNzaW5nIHZhbHVlcyBpbiB0aGUgbnVtZXJpY2FsIHZhcmlhYmxlcyB3ZXJlIGltcHV0ZWQgYnkgdGhlIG1lYW4gb2YgdGhlIGNvcnJlc3BvbmRpbmcgdmFyaWFibGVzLiAKVGhlIG51bWVyaWMgdmFyaWFibGVzIGluIHRoZSBkYXRhc2V0IGlzIHNjYW5uZWQgZm9yIG91dGxpZXJzIGJ5IHVzaW5nIGJveHBsb3QgZnVuY3Rpb24gYW5kIHRoZSBvdXRsaWVyIHZhbHVlcyBhcmUgcmVwbGFjZWQgdXNpbmcgYSBjYXAgZnVuY3Rpb24uVGhlIG91dGxpZXIgdmFsdWVzIGx5aW5nIGJleW9uZCB0aGUgZmVuY2VzIGFyZSByZXBsYWNlZCB3aXRoIHRoZSA1dGggb3IgOTV0aCBwZXJjZW50aWxlIHZhbHVlLiBGaW5hbGx5IGZvciB0aGUgdHJhbnNmb3JtYXRpb24gdGFzayx6LXNjb3JlIHN0YW5kYXJkaXphdGlvbiB3YXMgcGVyZm9ybWVkIG9uIGNlcnRhaW4gdmFyaWFibGVzIHRvIG1ha2UgdGhlIGRpc3RyaWJ1dGlvbiBtb3JlIG5vcm1hbCBhbmQgc2NhbGUgdGhlIGRhdGEgcmFuZ2UuIEEgbGluZWFyIHJlbGF0aW9uc2hpcCB3YXMgZXN0YWJsaXNoZWQgYmV0d2VlbiB0aGUgaGVpZ2h0IGFuZCB0aGUgd2VpZ2h0IG9mIHRoZSBzdXBlcmhlcm8gdXNpbmcgYSBzY2F0dGVycGxvdC5UaGUgZ3JhcGggc2hvd2VkIGEgcG9zaXRpdmUgY29ycmVsYXRpb24gYmV0d2VlbiB0aGUgdHdvIHZhcmlhYmxlcy4gCgojIyBEYXRhIAoqKlN1cGVyaGVyb2VzIERhdGFzZXQqKjxicj4KVGhlIHR3byBkYXRhc2V0cyB0YWtlbiBmb3IgcGVyZm9ybWluZyBkYXRhIHByZXByb2Nlc3NpbmcgY29udGFpbnMgaW5mb3JtYXRpb24gYWJvdXQgZGlmZmVyZW50IHN1cGVyaGVyb2VzIGFuZCB0aGVpciBzdGF0cy4KVGhlIGZpcnN0IGRhdGFzZXQgY29udGFpbnMgdmFyaW91cyBpbmZvcm1hdGlvbiBhYm91dCB0aGUgc3VwZXJoZXJvJ3MgYXBwZWFyYW5jZSx0aGVpciBib2R5IG1lYXN1cmVtZW50cyBldGMuVGhlcmUgYXJlIGFib3V0IDczNCBvYnNlcnZhdGlvbnMgYW5kIDEwIHZhcmlhYmxlcyBpbiB0aGlzIGRhdGFzZXQuClZhcmlhYmxlIGRlc2NyaXB0aW9ucyBhcmUgYXMgZm9sbG93czo8YnI+Ck5hbWU6IE5hbWUgb2YgdGhlIHN1cGVyaGVybzxicj4KR2VuZGVyOiBHZW5kZXIgb2YgdGhlIHN1cGVyaGVybzxicj4KRXllIGNvbG9yOiBFeWUgY29sb3Igb2YgdGhlIHN1cGVyaGVybzxicj4KUmFjZTogU3BlY2llcyBvZiB0aGUgc3VwZXJoZXJvIDxicj4KSGVpZ2h0OiBIZWlnaHQgb2YgdGhlIHN1cGVyaGVybyhpbiBjbXMpPGJyPgpQdWJsaXNoZXI6IENvbWljIGNhdGVnb3J5IG9mIHRoZSBzdXBlcmhlcm8gPGJyPgpTa2luIGNvbG9yOiBTa2luIGNvbG9yIG9mIHRoZSBzdXBlcmhlcm8gPGJyPgpBbGlnbm1lbnQ6IFN1cGVyaGVybydzIG5hdHVyZS48YnI+CldlaWdodDogV2VpZ2h0IG9mIHRoZSBzdXBlcmhlcm8oaW4ga2dzKTxicj4KClRoZSBzZWNvbmQgZGF0YXNldCByZWNvcmRzIHRoZSBzdXBlcmhlcm8gYXR0cmlidXRlcyBzdWNoIGFzIHRoZWlyIHN0cmVuZ3RoIGxldmVsLHNwZWVkIGxldmVsIGV0Yy5UaGVyZSBhcmUgYWJvdXQgNjExIG9ic2VydmF0aW9ucyBhbmQgOSB2YXJpYWJsZXMgaW4gdGhlIGRhdGFzZXQuClZhcmlhYmxlIGRlc2NyaXB0aW9ucyBhcmUgYXMgZm9sbG93czo8YnI+Ck5hbWU6IE5hbWUgb2YgdGhlIHN1cGVyaGVybzxicj4KQWxpZ25tZW50OiBTdXBlcmhlcm8ncyBuYXR1cmUuPGJyPgpJbnRlbGxpZ2VuY2U6SW50ZWxsaWdlbmNlIHN0YXRzIG9mIHRoZSBzdXBlcmhlcm88YnI+ClN0cmVuZ3RoOlN0cmVuZ3RoIHN0YXRzIG9mIHRoZSBzdXBlcmhlcm8gPGJyPgpTcGVlZDogU3BlZWQgc3RhdHMgb2YgdGhlIHN1cGVyaGVybzxicj4KRHVyYWJpbGl0eTogRHVyYWJpbGl0eSBzdGF0cyBvZiB0aGUgc3VwZXJoZXJvIDxicj4KUG93ZXI6IFBvd2VyIGxldmVsIG9mIHRoZSBzdXBlcmhlcm8gPGJyPgpDb21iYXQ6IENvbWJhdCBsZXZlbCBvZiB0aGUgc3VwZXJoZXJvPGJyPgpUb3RhbDogVG90YWwgY29tYmluZWQgc3RhdHMgb2YgdGhlIHN1cGVyaGVybzxicj4KCipEYXRhIHNvdXJjZToqPGJyPiBodHRwczovL3d3dy5rYWdnbGUuY29tL2NsYXVkaW9kYXZpL3N1cGVyaGVyby1zZXQ8YnI+Cmh0dHBzOi8vd3d3LmthZ2dsZS5jb20vbWFnc2hpbWltc3VtbWVyY2FtcC9zdXBlcmhlcm9lcy1pbmZvLWFuZC1zdGF0cyNzdXBlcmhlcm9lc19zdGF0cy5jc3Y8YnI+CgpUaGUgc3VwZXJoZXJvIHN0YXRzIGRhdGFzZXQgYWxzbyBjb250YWlucyB0aGUgJ2FsaWdubWVudCcgdmFyaWFibGUgYW5kIGhlbmNlIGhhcyBiZWVuIGV4Y2x1ZGVkIGZyb20gdGhlIG9yaWdpbmFsIGRhdGFzZXQuVGhlIHR3byBkYXRhc2V0cyBoYXZlIGJlZW4gbWVyZ2VkIG9uIHRoZSBzdXBlcmhlcm8gbmFtZSBieSB1c2luZyB0aGUgbXV0YXRpbmcgam9pbiBmdW5jdGlvbiBmcm9tIHRoZSBkcGx5ciBwYWNrYWdlIGJyaW5naW5nIHRvZ2V0aGVyIGFsbCB0aGUgZGV0YWlscyByZWdhcmRpbmcgdGhlIHN1cGVyaGVyb2VzLgpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cmhlcm9lc19pbmZvIDwtcmVhZF9jc3YoImhlcm9lc19pbmZvcm1hdGlvbi5jc3YiKSAKaGVhZChoZXJvZXNfaW5mbykKaGVyb2VzX3N0YXRzIDwtIHJlYWRfY3N2KCJzdXBlcmhlcm9lc19zdGF0cy5jc3YiKQpoZWFkKGhlcm9lc19zdGF0cykKaGVyb2VzX3N0YXRzMSA8LSBzdWJzZXQoaGVyb2VzX3N0YXRzWyxjKDEsMzo5KV0pCmhlcm9lc19jb21iaW5lZCA8LSBpbm5lcl9qb2luKGhlcm9lc19pbmZvLCBoZXJvZXNfc3RhdHMxLCBieSA9ICJOYW1lIikKaGVhZChoZXJvZXNfY29tYmluZWQpCmBgYAoKIyMgVW5kZXJzdGFuZCAKKiBUaGUgc3RydWN0dXJlIGFuZCB0aGUgdHlwZSBvZiB2YXJpYWJsZSBoYXMgYmVlbiBpZGVudGlmaWVkIHVzaW5nIHRoZSAnc3RyJyBmdW5jdGlvbi5UaGUgZGF0YXNldCBjb21wcmlzZWQgb2YgOCBjaGFyYWN0ZXIgYW5kIDkgbnVtZXJpYyB2YXJpYWJsZXMuCkNlcnRhaW4gY2hhcmFjdGVyIHZhcmlhYmxlcyBzdWNoIGFzIGdlbmRlcixoYWlyIGNvbG9yIGV0Yy4gd2VyZSBjb252ZXJ0ZWQgaW50byBmYWN0b3JzIGJ5IGRlZmluaW5nIHRoZSBsZXZlbHMgYW5kIGFwcHJvcHJpYXRlIGxhYmVscyB1c2luZyB0aGUgZmFjdG9yIGZ1bmN0aW9uLgoKCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0Kc3RyKGhlcm9lc19jb21iaW5lZCkKdGFibGUoc2FwcGx5KGhlcm9lc19jb21iaW5lZCwgY2xhc3MpKQphdHRyaWJ1dGVzKGhlcm9lc19jb21iaW5lZFsxOjE3LCBdKQpoZXJvZXNfY29tYmluZWQkR2VuZGVyIDwtIGZhY3RvcihoZXJvZXNfY29tYmluZWQkR2VuZGVyLGxldmVscyA9IGMoJ01hbGUnLCdGZW1hbGUnKSkKaGVyb2VzX2NvbWJpbmVkJGBFeWUgY29sb3JgIDwtIGZhY3RvcihoZXJvZXNfY29tYmluZWQkYEV5ZSBjb2xvcmAsbGV2ZWxzID0gYygnYW1iZXInLCdibGFjaycsJ2JsdWUnLCdibHVlIC8gd2hpdGUnLCdicm93bicsJ2dvbGQnLCdncmVlbicsJ2dyZWVuIC8gYmx1ZScsJ2dyZXknLCdoYXplbCcsJ2luZGlnbycsJ3B1cnBsZScsJ3JlZCcsJ3NpbHZlcicsJ3Zpb2xldCcsJ3doaXRlJywnd2hpdGUgLyByZWQnLCd5ZWxsb3cnCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAsJ3llbGxvdyAod2l0aG91dCBpcmlzZXMpJywneWVsbG93IC8gYmx1ZScsJ3llbGxvdyAvIHJlZCcpLGxhYmVscz1jKCdhbWJlcicsJ2JsYWNrJywnYmx1ZScsJ2JsdWUmd2hpdGUnLCdicm93bicsJ2dvbGQnLCdncmVlbicsJ2dyZWVuICYgYmx1ZScsCSdncmV5JywnaGF6ZWwnLCdpbmRpZ28nLCdwdXJwbGUnLCdyZWQnLCdzaWx2ZXInLCd2aW9sZXQnLCd3aGl0ZScsJ3doaXRlICYgcmVkJywneWVsbG93JwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLCd5ZWxsb3coTm8gaXJpcyknLCd5ZWxsb3cgJiBibHVlJywneWVsbG93ICYgcmVkJykpCmhlcm9lc19jb21iaW5lZCRSYWNlIDwtIGZhY3RvcihoZXJvZXNfY29tYmluZWQkUmFjZSxsZXZlbHM9YygnQWxpZW4nLCdBbHBoYScsJ0FtYXpvbicsJ0FuZHJvaWQnLCdBbmltYWwnLCdBc2dhcmRpYW4nLCdBdGxhbnRlYW4nLCdCaXphcnJvJywnQm9sb3ZheGlhbicsJ0Nsb25lJywnQ29zbWljIEVudGl0eScsJ0N5Ym9yZycsJ0N6YXJuaWFuJywnRGF0aG9taXJpYW4gWmFicmFrJywnRGVtaS1Hb2QnLCdEZW1vbicsJ0V0ZXJuYWwnLCdGbG9yYSBDb2xvc3N1cycsJ0Zyb3N0IEdpYW50JywnR29kIC8gRXRlcm5hbCcsJ0dvcmlsbGEnLCdHdW5nYW4nLCdIdW1hbicsJ0h1bWFuIC8gQWx0ZXJlZCcsICdIdW1hbiAvIENsb25lJywnSHVtYW4gLyBDb3NtaWMnLCdIdW1hbiAvIFJhZGlhdGlvbicsJ0h1bWFuLUtyZWUnLCdIdW1hbi1TcGFydG9pJywnSHVtYW4tVnVsY2FuJywnSHVtYW4tVnVsZGFyaWFuJywnSWN0aHlvIFNhcGllbicsJ0luaHVtYW4nLCdLYWlqdScsJ0tha2FyYW50aGFyYWlhbicsJ0tvcnVnYXJhbicsJ0tyeXB0b25pYW4nLCdMdXBob21vaWQnLCdNYWlhcicsJ01hcnRpYW4nLCdNZXRhaHVtYW4nLCdNdXRhbnQnLCdNdXRhbnQgLyBDbG9uZScsJ05ldyBHb2QnLCdOZXlhcGhlbScsJ1BhcmFkZW1vbicsJ1BsYW5ldCcsJ1JvZGlhbicsJ1NhaXlhbicsJ1NwYXJ0b2knLCdTdHJvbnRpYW4nLCdTeW1iaW90ZScsJ1RhbG9raXRlJywnVGFtYXJhbmVhbicsJ1VuZ2FyYW4nLCdWYW1waXJlJywnWGVub21vcnBoIFhYMTIxJywnWWF1dGphJywnWW9kYSBzcGVjaWVzJywnWmVuLVdob2JlcmlhbicsJ1pvbWJpZScpKQpoZXJvZXNfY29tYmluZWQkYEhhaXIgY29sb3JgIDwtIGZhY3RvcihoZXJvZXNfY29tYmluZWQkYEhhaXIgY29sb3JgLGxldmVscz1jKCdBdWJ1cm4nLCdCbGFjayAvIEJsdWUnLCdCbG9uZCcsJ0JsdWUnLCdCcm93bicsJ0Jyb3duIC8gQmxhY2snLCdCcm93biAvIFdoaXRlJywnR29sZCcsJ0dyZWVuJywnR3JleScsJ0luZGlnbycsJ01hZ2VudGEnLCdObyBIYWlyJywnT3JhbmdlJywnT3JhbmdlIC8gV2hpdGUnLCdQaW5rJywnUHVycGxlJywnUmVkJywnUmVkIC8gR3JleScsJ1JlZCAvIE9yYW5nZScsJ1JlZCAvIFdoaXRlJywnU2lsdmVyJywnU3RyYXdiZXJyeSBCbG9uZCcsJ1doaXRlJywnWWVsbG93JykpCmhlcm9lc19jb21iaW5lZCRQdWJsaXNoZXIgPC0gZmFjdG9yKGhlcm9lc19jb21iaW5lZCRQdWJsaXNoZXIsbGV2ZWxzPWMoJ0FCQyBTdHVkaW9zJywnRGFyayBIb3JzZSBDb21pY3MnLCdEQyBDb21pY3MnLCdHZW9yZ2UgTHVjYXMnLCdIYW5uYS1CYXJiZXJhJywnSGFycGVyQ29sbGlucycsJ0ljb24gQ29taWNzJywnSURXIFB1Ymxpc2hpbmcnLCdJbWFnZSBDb21pY3MnLCdKLiBLLiBSb3dsaW5nJywnSi4gUi4gUi4gVG9sa2llbicsJ01hcnZlbCBDb21pY3MnLCdNaWNyb3NvZnQnLCdOQkMgLSBIZXJvZXMnLCdSZWJlbGxpb24nLCdTaHVlaXNoYScsJ1NvbnkgUGljdHVyZXMnLCdTb3V0aCBQYXJrJywnU3RhciBUcmVrJywnU3lGeScsJ1RlYW0gRXBpYyBUVicsJ1RpdGFuIEJvb2tzJywnVW5pdmVyc2FsIFN0dWRpb3MnLCdXaWxkc3Rvcm0nKSkKaGVyb2VzX2NvbWJpbmVkJGBTa2luIGNvbG9yYCA8LSBmYWN0b3IoaGVyb2VzX2NvbWJpbmVkJGBTa2luIGNvbG9yYCxsZXZlbHM9YygnYmxhY2snLCdibHVlJywnYmx1ZS13aGl0ZScsJ2dvbGQnLCdncmF5JywnZ3JlZW4nLCdncmV5Jywnb3JhbmdlJywnb3JhbmdlIC8gd2hpdGUnLCdwaW5rJywncHVycGxlJywncmVkJywncmVkIC8gYmxhY2snLCdzaWx2ZXInLCd3aGl0ZScsJ3llbGxvdycpKSAgCgpoZXJvZXNfY29tYmluZWQkQWxpZ25tZW50IDwtIGZhY3RvcihoZXJvZXNfY29tYmluZWQkQWxpZ25tZW50LGxldmVscz1jKCdnb29kJywnYmFkJywnbmV1dHJhbCcpKQoKc3RyKGhlcm9lc19jb21iaW5lZCkKYGBgCgoKIyMJVGlkeSAmIE1hbmlwdWxhdGUgRGF0YSBJIAoqIEZvciB0aGUgZGF0YXNldCB0byBiZSB0aWR5LHRoZSBkYXRhc2V0IHNob3VsZCBzYXRpc2Z5IHRoZSB0aWR5IGRhdGEgcHJpbmNpcGxlcyBpLmU8YnI+CjEuIEVhY2ggdmFyaWFibGUgbXVzdCBoYXZlIGl0cyBvd24gY29sdW1uLjxicj4KMi4gRWFjaCBvYnNlcnZhdGlvbiBtdXN0IGhhdmUgaXRzIG93biByb3cuPGJyPgozLiBFYWNoIHZhbHVlIG11c3QgaGF2ZSBpdHMgb3duIGNlbGwuPGJyPgpGcm9tIHRoZSBkYXRhc2V0LHdlIGNhbiBvYnNlcnZlIHRoYXQgZWFjaCBvZiB0aGUgcnVsZXMgaGF2ZSBiZWVuIHNhdGlzZmllZCBhbmQgaGVuY2Ugbm8gdGlkeSBmdW5jdGlvbnMgbmVlZCB0byBiZSBhcHBsaWVkIG9uIHRoZSBkYXRhc2V0LgpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CgpgYGAKCiMjCVRpZHkgJiBNYW5pcHVsYXRlIERhdGEgSUkgCgoqIEEgbmV3IHZhcmlhYmxlICdCTUknIGhhcyBiZWVuIGNyZWF0ZWQgZm9yIGVhY2ggc3VwZXJoZXJvIHdoaWNoIGludm9sdmVzIGRpdmlzaW9uIG9mIHdlaWdodCBieSBoZWlnaHQgdmFyaWFibGUuVGhlIEJNSSBkZXNjcmliZXMgaG93IGZpdCB0aGUgc3VwZXJoZXJvIGlzLiAKCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KaGVyb2VzX2NvbWJpbmVkIDwtIG11dGF0ZShoZXJvZXNfY29tYmluZWQsQk1JPWhlcm9lc19jb21iaW5lZCRXZWlnaHQvaGVyb2VzX2NvbWJpbmVkJEhlaWdodCkKaGVhZChoZXJvZXNfY29tYmluZWQpCmBgYAoKCiMjCVNjYW4gSSAKKiBUaGUgZGF0YXNldCBpcyBzY2FubmVkIGZvciBhbnkgbWlzc2luZyB2YWx1ZXMsc3BlY2lhbCB2YWx1ZXMgYW5kIG9idmlvdXMgZXJyb3JzL2luY29uc2lzdGVuY2llcyBieSB1c2luZyBhcHByb3ByaWF0ZSBmdW5jdGlvbnMuV2Ugb2JzZXJ2ZSB0aGF0IHRoZXJlIGFyZSBtdWx0aXBsZSBtaXNzaW5nIHZhbHVlcyBpbiB0aGUgZmFjdG9yZWQgYW5kIG51bWVyaWMgdmFyaWFibGVzIHdpdGggdGhlIHRvdGFsIGNvdW50IGJlaW5nIDI0OS4gRnJvbSB0aGUgZGF0YXNldCxpdCBjYW4gYWxzbyBiZSBvYnNlcnZlZCB0aGF0IHRoZXJlIGFyZSBjZXJ0YWluIG1pc3NpbmcgdmFsdWVzIGNvZGVkIGFzIC05OSBpbiB0aGUgaGVpZ2h0IGFuZCB3ZWlnaHQgdmFyaWFibGVzLlRoZXNlIHZhbHVlcyBoYXZlIGJlZW4gY29udmVydGVkIGludG8gbnVsbCB2YWx1ZXMgYnkgdXNpbmcgdGhlIG5hX2lmIGZ1bmN0aW9uLgpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiNDaGVja2luZyBmb3IgTlVMTCB2YWx1ZXMgaW4gdGhlIGRhdGFzZXQKY29sU3Vtcyhpcy5uYShoZXJvZXNfY29tYmluZWQpKQpzdW0oaXMubmEoaGVyb2VzX2NvbWJpbmVkKSkKCiNDaGVja2luZyBmb3IgaW5maW5pdGUgYW5kIE5hTiB2YWx1ZXMKaXMuc3BlY2lhbCA8LSBmdW5jdGlvbih4KXsKICBpZiAoaXMubnVtZXJpYyh4KSkgKGlzLmluZmluaXRlKHgpIHwgaXMubmFuKHgpKQp9CnNhcHBseShoZXJvZXNfY29tYmluZWQsIGZ1bmN0aW9uKHgpIHN1bShpcy5zcGVjaWFsKHgpKSkKYGBgCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KI0NoZWNraW5nIGZvciBvYnZpb3VzIGVycm9ycyBhbmQgaW5jb25zaXN0ZW5jaWVzIAooUnVsZTEgPC0gZWRpdHNldChjKCJIZWlnaHQgPj0gMCIsIldlaWdodCA+PTAiKSkpClZpb2xhdGVkIDwtIHZpb2xhdGVkRWRpdHMoUnVsZTEsaGVyb2VzX2NvbWJpbmVkKSAKc3VtbWFyeShWaW9sYXRlZCkKCiNSZWNvZGluZyAtOTkgdmFsdWUgYXMgTkFzCmhlcm9lc19jb21iaW5lZCRXZWlnaHQgPC0gaGVyb2VzX2NvbWJpbmVkJFdlaWdodCAlPiUgbmFfaWYoLTk5KQpoZXJvZXNfY29tYmluZWQkSGVpZ2h0IDwtIGhlcm9lc19jb21iaW5lZCRIZWlnaHQgJT4lIG5hX2lmKC05OSkKYGBgCiogSW4gb3JkZXIgdG8gZGVhbCB3aXRoIHRoZSBtaXNzaW5nIHZhbHVlcyBpbiB0aGUgY2F0ZWdvcmljYWwgdmFyaWFibGVzLHRoZSBtaXNzaW5nIHZhbHVlcyBoYXZlIGJlZW4gY2xhc3NpZmllZCBhcyAndW5rbm93bi9ub3Qgc3BlY2lmaWVkJyBieSBkZWZpbmluZyBhIG5ldyBsZXZlbC5BcyBmb3IgdGhlIG1pc3NpbmcgdmFsdWVzIGluIHRoZSBudW1lcmljIHZhcmlhYmxlcyx0aGUgbnVtYmVyIG9mIG1pc3NpbmcgdmFsdWVzIGlzIHF1aXRlIGxhcmdlIGFuZCBleGNsdWRpbmcgdGhlIG1pc3NpbmcgdmFsdWVzIHdvdWxkIGNhdXNlIGRpc2NyZXBhbmNpZXMgaW4gdGhlIGRhdGFzZXQuIAoqIFRoZSBtaXNzaW5nIHZhbHVlcyBoYXZlIGJlZW4gaW1wdXRlZCB3aXRoIHRoZSBtZWFuIHZhbHVlIGZvciB0aGUgbnVtZXJpYyB2YXJpYWJsZXMgaW4gdGhpcyBjYXNlIHVzaW5nIHRoZSBpbXB1dGUgZnVuY3Rpb24gZnJvbSB0aGUgJ2htaXNjJyBwYWNrYWdlIHNvIHRoYXQgdGhlcmUgaXMgY29uc2lzdGVuY3kgaW4gdGhlIGRhdGEuIFdlIHVzZSB0aGUgY29sc3Vtcyhpcy5uYSkgJiBzdW0oaXMubmEpIGZ1bmN0aW9uIHRvIHZlcmlmeSB0aGF0IHRoZXJlIGFyZSBubyBtaXNzaW5nIHZhbHVlcyBpbiB0aGUgZGF0YXNldC4KYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBIYW5kbGluZyBtaXNzaW5nIHZhbHVlcyBmb3IgdGhlIHZhcmlhYmxlcwpzdW0oaXMubmEoaGVyb2VzX2NvbWJpbmVkJEdlbmRlcikpICAKbGV2ZWxzKGhlcm9lc19jb21iaW5lZCRHZW5kZXIpID0gYyhsZXZlbHMoaGVyb2VzX2NvbWJpbmVkJEdlbmRlciksICJOb3QgU3BlY2lmaWVkIikKaGVyb2VzX2NvbWJpbmVkJEdlbmRlcltpcy5uYShoZXJvZXNfY29tYmluZWQkR2VuZGVyKV0gPC0gIk5vdCBTcGVjaWZpZWQiCgpzdW0oaXMubmEoaGVyb2VzX2NvbWJpbmVkJGBFeWUgY29sb3JgKSkgCmxldmVscyhoZXJvZXNfY29tYmluZWQkYEV5ZSBjb2xvcmApID0gYyhsZXZlbHMoaGVyb2VzX2NvbWJpbmVkJGBFeWUgY29sb3JgKSwgIlVua25vd24iKQpoZXJvZXNfY29tYmluZWQkYEV5ZSBjb2xvcmBbaXMubmEoaGVyb2VzX2NvbWJpbmVkJGBFeWUgY29sb3JgKV0gPC0gIlVua25vd24iCgpzdW0oaXMubmEoaGVyb2VzX2NvbWJpbmVkJGBSYWNlYCkpIApsZXZlbHMoaGVyb2VzX2NvbWJpbmVkJGBSYWNlYCkgPSBjKGxldmVscyhoZXJvZXNfY29tYmluZWQkYFJhY2VgKSwgIlVua25vd24iKQpoZXJvZXNfY29tYmluZWQkYFJhY2VgW2lzLm5hKGhlcm9lc19jb21iaW5lZCRgUmFjZWApXSA8LSAiVW5rbm93biIKCnN1bShpcy5uYShoZXJvZXNfY29tYmluZWQkYEhhaXIgY29sb3JgKSkgCmxldmVscyhoZXJvZXNfY29tYmluZWQkYEhhaXIgY29sb3JgKSA9IGMobGV2ZWxzKGhlcm9lc19jb21iaW5lZCRgSGFpciBjb2xvcmApLCAiVW5rbm93biIpCmhlcm9lc19jb21iaW5lZCRgSGFpciBjb2xvcmBbaXMubmEoaGVyb2VzX2NvbWJpbmVkJGBIYWlyIGNvbG9yYCldIDwtICJVbmtub3duIgoKc3VtKGlzLm5hKGhlcm9lc19jb21iaW5lZCRgUHVibGlzaGVyYCkpIApsZXZlbHMoaGVyb2VzX2NvbWJpbmVkJGBQdWJsaXNoZXJgKSA9IGMobGV2ZWxzKGhlcm9lc19jb21iaW5lZCRgUHVibGlzaGVyYCksICJVbmtub3duIikKaGVyb2VzX2NvbWJpbmVkJGBQdWJsaXNoZXJgW2lzLm5hKGhlcm9lc19jb21iaW5lZCRgUHVibGlzaGVyYCldIDwtICJVbmtub3duIgoKc3VtKGlzLm5hKGhlcm9lc19jb21iaW5lZCRgU2tpbiBjb2xvcmApKSAKbGV2ZWxzKGhlcm9lc19jb21iaW5lZCRgU2tpbiBjb2xvcmApID0gYyhsZXZlbHMoaGVyb2VzX2NvbWJpbmVkJGBTa2luIGNvbG9yYCksICJVbmtub3duIikKaGVyb2VzX2NvbWJpbmVkJGBTa2luIGNvbG9yYFtpcy5uYShoZXJvZXNfY29tYmluZWQkYFNraW4gY29sb3JgKV0gPC0gIlVua25vd24iCgpzdW0oaXMubmEoaGVyb2VzX2NvbWJpbmVkJGBBbGlnbm1lbnRgKSkgCmxldmVscyhoZXJvZXNfY29tYmluZWQkYEFsaWdubWVudGApID0gYyhsZXZlbHMoaGVyb2VzX2NvbWJpbmVkJGBBbGlnbm1lbnRgKSwgIlVua25vd24iKQpoZXJvZXNfY29tYmluZWQkYEFsaWdubWVudGBbaXMubmEoaGVyb2VzX2NvbWJpbmVkJGBBbGlnbm1lbnRgKV0gPC0gIlVua25vd24iCgojSW1wdXRpbmcgdGhlIHZhbHVlcyB3aXRoIE1lYW4gZm9yIG51bWVyaWMgdmFyaWFibGVzCmhlcm9lc19jb21iaW5lZCRXZWlnaHQ8LWltcHV0ZShoZXJvZXNfY29tYmluZWQkV2VpZ2h0LGZ1biA9IG1lYW4pCmhlcm9lc19jb21iaW5lZCRIZWlnaHQ8LWltcHV0ZShoZXJvZXNfY29tYmluZWQkSGVpZ2h0LGZ1biA9IG1lYW4pCmhlcm9lc19jb21iaW5lZCRJbnRlbGxpZ2VuY2U8LWltcHV0ZShoZXJvZXNfY29tYmluZWQkSW50ZWxsaWdlbmNlLGZ1biA9IG1lYW4pCmhlcm9lc19jb21iaW5lZCRTdHJlbmd0aDwtaW1wdXRlKGhlcm9lc19jb21iaW5lZCRTdHJlbmd0aCxmdW4gPSBtZWFuKQpoZXJvZXNfY29tYmluZWQkU3BlZWQ8LWltcHV0ZShoZXJvZXNfY29tYmluZWQkU3BlZWQsZnVuID0gbWVhbikKaGVyb2VzX2NvbWJpbmVkJER1cmFiaWxpdHk8LWltcHV0ZShoZXJvZXNfY29tYmluZWQkRHVyYWJpbGl0eSxmdW4gPSBtZWFuKQpoZXJvZXNfY29tYmluZWQkUG93ZXI8LWltcHV0ZShoZXJvZXNfY29tYmluZWQkUG93ZXIsZnVuID0gbWVhbikKaGVyb2VzX2NvbWJpbmVkJENvbWJhdDwtaW1wdXRlKGhlcm9lc19jb21iaW5lZCRDb21iYXQsZnVuID0gbWVhbikKaGVyb2VzX2NvbWJpbmVkJFRvdGFsPC1pbXB1dGUoaGVyb2VzX2NvbWJpbmVkJFRvdGFsLGZ1biA9IG1lYW4pCmhlcm9lc19jb21iaW5lZCRCTUk8LWltcHV0ZShoZXJvZXNfY29tYmluZWQkQk1JLGZ1biA9IG1lYW4pCgpjb2xTdW1zKGlzLm5hKGhlcm9lc19jb21iaW5lZCkpCnN1bShpcy5uYShoZXJvZXNfY29tYmluZWQpKQpgYGAKCiMjCVNjYW4gSUkKKiBJbiBvcmRlciB0byBjaGVjayBmb3Igb3V0bGllcnMgaW4gdGhlIGRhdGFzZXQsYm94cGxvdHMgaGF2ZSBiZWVuIHBsb3R0ZWQgZm9yIGVhY2ggbnVtZXJpYyB2YXJpYWJsZS5XaXRoIG1pc3NpbmcgdmFsdWVzIGhhbmRsZWQgZWZmZWN0aXZlbHkgaW4gdGhlIHByZXZpb3VzIHN0ZXAsdGhlIFR1a2V54oCZcyBtZXRob2Qgb2Ygb3V0bGllciBkZXRlY3Rpb24gY2FuIGJlIHVzZWQgdG8gZGV0ZWN0IGFueSBvdXRsaWVycyBpbiB0aGUgYm94cGxvdC5Gcm9tIHRoZSBib3hwbG90IG1ldGhvZCB3ZSBvYnNlcnZlIHRoYXQgdGhlcmUgYXJlIG91dGxpZXJzIGluIDcgbnVtZXJpYyB2YXJpYWJsZXMgaW4gdGhlIGRhdGFzZXQuCiogVGhlIGNhcCBmdW5jdGlvbiBoYXMgYmVlbiB1c2VkIHRvIHJlcGxhY2UgdGhlIG91dGxpZXIgdmFsdWVzIHRoYXQgbGllIGJlbG93IHRoZSB2YWx1ZSBvZiB0aGUgbG93ZXIgZmVuY2UgKFExIC0gMS41IHggSVFSKSB3aXRoIHRoZSB2YWx1ZSBvZiA1dGggcGVyY2VudGlsZSBhbmQgcmVwbGFjZSB0aGUgb3V0bGllciB2YWx1ZXMgdGhhdCBsaWUgYWJvdmUgdGhlIHZhbHVlIG9mIHRoZSB1cHBlciBmZW5jZSAoUTMgKyAxLjUgeCBJUVIpIHdpdGggdGhlIHZhbHVlIG9mIHRoZSA5NXRoIHBlcmNlbnRpbGUuClN1bW1hcnkgZnVuY3Rpb24gaGFzIGJlZW4gdXNlZCB0byBjaGVjayB0aGUgc3VtbWFyeSBzdGF0aXN0aWNzIG9mIHRoZSBudW1lcmljIHZhcmlhYmxlcyBiZWZvcmUgYW5kIGFmdGVyIGNhcHBpbmcuCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KYm94cGxvdChhcy5udW1lcmljKGhlcm9lc19jb21iaW5lZCRXZWlnaHQpLG1haW49IkJveHBsb3Qgb2YgU3VwZXJoZXJvJ3Mgd2VpZ2h0Iix5bGFiPSJXZWlnaHQiLGNvbD0iZ3JleSIpCmJveHBsb3QoYXMubnVtZXJpYyhoZXJvZXNfY29tYmluZWQkSGVpZ2h0KSxtYWluPSJCb3hwbG90IG9mIFN1cGVyaGVybydzIGhlaWdodCIseWxhYj0iSGVpZ2h0Iixjb2w9ImdyZXkiKQpib3hwbG90KGFzLm51bWVyaWMoaGVyb2VzX2NvbWJpbmVkJEludGVsbGlnZW5jZSksbWFpbj0iQm94cGxvdCBvZiBTdXBlcmhlcm8ncyBpbnRlbGxpZ2VuY2UiLHlsYWI9IkludGVsbGlnZW5jZSIsY29sPSJncmV5IikKYm94cGxvdChhcy5udW1lcmljKGhlcm9lc19jb21iaW5lZCRTdHJlbmd0aCksbWFpbj0iQm94cGxvdCBvZiBTdXBlcmhlcm8ncyBzdHJlbmd0aCIseWxhYj0iU3RyZW5ndGgiLGNvbD0iZ3JleSIpCmJveHBsb3QoYXMubnVtZXJpYyhoZXJvZXNfY29tYmluZWQkU3BlZWQpLG1haW49IkJveHBsb3Qgb2YgU3VwZXJoZXJvJ3Mgc3BlZWQiLHlsYWI9IlNwZWVkIixjb2w9ImdyZXkiKQpib3hwbG90KGFzLm51bWVyaWMoaGVyb2VzX2NvbWJpbmVkJER1cmFiaWxpdHkpLG1haW49IkJveHBsb3Qgb2YgU3VwZXJoZXJvJ3MgZHVyYWJpbGl0eSIseWxhYj0iRHVyYWJpbGl0eSIsY29sPSJncmV5IikKYm94cGxvdChhcy5udW1lcmljKGhlcm9lc19jb21iaW5lZCRQb3dlciksbWFpbj0iQm94cGxvdCBvZiBTdXBlcmhlcm8ncyBwb3dlciIseWxhYj0iUG93ZXIiLGNvbD0iZ3JleSIpCmJveHBsb3QoYXMubnVtZXJpYyhoZXJvZXNfY29tYmluZWQkQ29tYmF0KSxtYWluPSJCb3hwbG90IG9mIFN1cGVyaGVybydzIGNvbWJhdCIseWxhYj0iQ29tYmF0Iixjb2w9ImdyZXkiKQpib3hwbG90KGFzLm51bWVyaWMoaGVyb2VzX2NvbWJpbmVkJFRvdGFsKSxtYWluPSJCb3hwbG90IG9mIFN1cGVyaGVybydzIHRvdGFsIHN0YXRzIix5bGFiPSJUb3RhbCBzdGF0cyIsY29sPSJncmV5IikKYm94cGxvdChhcy5udW1lcmljKGhlcm9lc19jb21iaW5lZCRCTUkpLG1haW49IkJveHBsb3Qgb2YgU3VwZXJoZXJvJ3MgQk1JIix5bGFiPSJCTUkiLGNvbD0iZ3JleSIpCmBgYAoKYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIENhcHBpbmcgb3V0bGllcnMgZm9yIHRoZSA3IG51bWVyaWMgdmFyaWFibGVzIHRoYXQgaGF2ZSBvdXRsaWVycwoKY2FwIDwtIGZ1bmN0aW9uKHgpewogIHF1YW50aWxlcyA8LSBxdWFudGlsZSggeCwgYyguMDUsIDAuMjUsIDAuNzUsIC45NSApICkKICB4WyB4IDwgcXVhbnRpbGVzWzJdIC0gMS41KklRUih4KSBdIDwtIHF1YW50aWxlc1sxXQogIHhbIHggPiBxdWFudGlsZXNbM10gKyAxLjUqSVFSKHgpIF0gPC0gcXVhbnRpbGVzWzRdCiAgeAp9Cmhlcm9lc19jb21iaW5lZCRXZWlnaHQgPC0gaGVyb2VzX2NvbWJpbmVkJFdlaWdodCAlPiUgY2FwKCkKYm94cGxvdChhcy5udW1lcmljKGhlcm9lc19jb21iaW5lZCRXZWlnaHQpLG1haW49IkJveHBsb3Qgb2YgU3VwZXJoZXJvJ3Mgd2VpZ2h0Iix5bGFiPSJXZWlnaHQiLGNvbD0iYmx1ZSIpCgpoZXJvZXNfY29tYmluZWQkSGVpZ2h0IDwtIGhlcm9lc19jb21iaW5lZCRIZWlnaHQgJT4lIGNhcCgpCmJveHBsb3QoYXMubnVtZXJpYyhoZXJvZXNfY29tYmluZWQkSGVpZ2h0ICksbWFpbj0iQm94cGxvdCBvZiBTdXBlcmhlcm8ncyBoZWlnaHQiLHlsYWI9IkhlaWdodCIsY29sPSJibHVlIikKCmhlcm9lc19jb21iaW5lZCRJbnRlbGxpZ2VuY2UgPC0gaGVyb2VzX2NvbWJpbmVkJEludGVsbGlnZW5jZSAlPiUgY2FwKCkKYm94cGxvdChhcy5udW1lcmljKGhlcm9lc19jb21iaW5lZCRJbnRlbGxpZ2VuY2UpLG1haW49IkJveHBsb3Qgb2YgU3VwZXJoZXJvJ3MgaW50ZWxsaWdlbmNlIix5bGFiPSJJbnRlbGxpZ2VuY2UiLGNvbD0iYmx1ZSIpCgpoZXJvZXNfY29tYmluZWQkU3BlZWQgPC0gaGVyb2VzX2NvbWJpbmVkJFNwZWVkICU+JSBjYXAoKQpib3hwbG90KGFzLm51bWVyaWMoaGVyb2VzX2NvbWJpbmVkJFNwZWVkKSxtYWluPSJCb3hwbG90IG9mIFN1cGVyaGVybydzIFNwZWVkIix5bGFiPSJTcGVlZCIsY29sPSJibHVlIikKCmhlcm9lc19jb21iaW5lZCRDb21iYXQgPC0gaGVyb2VzX2NvbWJpbmVkJENvbWJhdCAlPiUgY2FwKCkKYm94cGxvdChhcy5udW1lcmljKGhlcm9lc19jb21iaW5lZCRDb21iYXQpLG1haW49IkJveHBsb3Qgb2YgU3VwZXJoZXJvJ3MgQ29tYmF0Iix5bGFiPSJDb21iYXQiLGNvbD0iYmx1ZSIpCgpoZXJvZXNfY29tYmluZWQkVG90YWwgPC0gaGVyb2VzX2NvbWJpbmVkJFRvdGFsICU+JSBjYXAoKQpib3hwbG90KGFzLm51bWVyaWMoaGVyb2VzX2NvbWJpbmVkJFRvdGFsKSxtYWluPSJCb3hwbG90IG9mIFN1cGVyaGVybydzIFRvdGFsIHN0YXRzIix5bGFiPSJUb3RhbCBTdGF0cyIsY29sPSJibHVlIikKCmhlcm9lc19jb21iaW5lZCRCTUkgPC0gaGVyb2VzX2NvbWJpbmVkJEJNSSAlPiUgY2FwKCkKYm94cGxvdChhcy5udW1lcmljKGhlcm9lc19jb21iaW5lZCRCTUkpLG1haW49IkJveHBsb3Qgb2YgU3VwZXJoZXJvJ3MgQk1JIix5bGFiPSJCTUkiLGNvbD0iYmx1ZSIpCgojU3VtbWFyaXppbmcgdGhlIG51bWVyaWMgdmFyaWFibGVzIGFmdGVyIGNhcHBpbmcKc3VtbWFyeShoZXJvZXNfY29tYmluZWRbLCBjKDYsIDEwOjE4KV0pCgpgYGAKCgojIwlUcmFuc2Zvcm0gCgoqIFotc2NvcmUgdHJhbnNmb3JtYXRpb25zIGhhdmUgYmVlbiBhcHBsaWVkIG9uIHRoZSBIZWlnaHQgdmFyaWFibGUgc28gdGhhdCB0aGUgdmFsdWVzIGFyZSBjZW50cmVkIHRvd2FyZHMgdGhlIG1lYW4gYW5kIGFyZSBzY2FsZWQgZG93biB0byBhICBjb21tb24gcmFuZ2UgaW4gb3JkZXIgdG8gbm9ybWFsaXplIHRoZSBkYXRhLiBUaGUgc2NhbGUgZnVuY3Rpb24gaXMgdXNlZCB0byBwZXJmb3JtIHRoZSB6LXNjb3JlIHRyYW5zZm9ybWF0aW9uIHdpdGggY2VudGVyIGFuZCBzY2FsZSBhcmd1bWVudHMgc2V0IGFzIHRydWUuIAoqIEEgbGluZWFyIHJlbGF0aW9uc2hpcCBjYW4gYmUgb2JzZXJ2ZWQgYmV0d2VlbiB0aGUgaGVpZ2h0IGFuZCB3ZWlnaHQgb2YgdGhlIHN1cGVyaGVybyBieSBwbG90dGluZyBhIHNjYXR0ZXJwbG90LkZyb20gdGhlIGdyYXBoLHdlIGNhbiBzZWUgdGhhdCB0aGVyZSBpcyBhIHBvc2l0aXZlIGNvLXJlbGF0aW9uIGJldHdlZW4gaGVpZ2h0IGFuZCB3ZWlnaHQgb2YgdGhlIHN1cGVyaGVyby4gIApgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9Cmhpc3QoaGVyb2VzX2NvbWJpbmVkJEhlaWdodCxtYWluPSJIaXN0b2dyYW0gb2YgU3VwZXJoZXJvJ3MgSGVpZ2h0IikKY2VudHJlX2hlaWdodCA8LSBzY2FsZShoZXJvZXNfY29tYmluZWQkSGVpZ2h0LCBjZW50ZXIgPSBUUlVFLCBzY2FsZSA9IFRSVUUpCmhpc3QoY2VudHJlX2hlaWdodCxtYWluPSJIaXN0b2dyYW0gb2YgU3VwZXJoZXJvJ3MgSGVpZ2h0KFotc2NvcmUgVHJhbnNmb3JtZWQpIikKCnBsb3QoaGVyb2VzX2NvbWJpbmVkJEhlaWdodCxoZXJvZXNfY29tYmluZWQkV2VpZ2h0LCBtYWluPSJTdXBlcmhlcm9lcyBIZWlnaHQgdi9zIFdlaWdodCIsCiAgIHhsYWI9IiBIZWlnaHQgIiwgeWxhYj0iIFdlaWdodCAiLHBjaD0xOSx4bGltPWMoMTYwLDIxNSkpCmFibGluZShsbShoZXJvZXNfY29tYmluZWQkV2VpZ2h0fmhlcm9lc19jb21iaW5lZCRIZWlnaHQpLCBjb2w9InJlZCIpIApsaW5lcyhsb3dlc3MoaGVyb2VzX2NvbWJpbmVkJEhlaWdodCxoZXJvZXNfY29tYmluZWQkV2VpZ2h0KSwgY29sID0gImJsdWUiKQoKYGBgCgoKCgo8YnI+Cjxicj4K