Pre-processing of data from the chronic kidney disease dataset from Kaggle (https://www.kaggle.com/datasets/mansoordaku/ckdisease)

Initially, we will load our data into R using read.csv(). Let’s also load the library ‘tidyverse’, which we will use in our processing.

kidney_data <- read.csv('C:/Data Science YH/R Programming/Final Assignment/data/kidney_disease.csv')
library(tidyverse)

Next, let’s investigate the data structure in the dataset using the str() function.

str(kidney_data)
'data.frame':   400 obs. of  26 variables:
 $ id            : int  0 1 2 3 4 5 6 7 8 9 ...
 $ age           : num  48 7 62 48 51 60 68 24 52 53 ...
 $ bp            : num  80 50 80 70 80 90 70 NA 100 90 ...
 $ sg            : num  1.02 1.02 1.01 1 1.01 ...
 $ al            : num  1 4 2 4 2 3 0 2 3 2 ...
 $ su            : num  0 0 3 0 0 0 0 4 0 0 ...
 $ rbc           : chr  "" "" "normal" "normal" ...
 $ pc            : chr  "normal" "normal" "normal" "abnormal" ...
 $ pcc           : chr  "notpresent" "notpresent" "notpresent" "present" ...
 $ ba            : chr  "notpresent" "notpresent" "notpresent" "notpresent" ...
 $ bgr           : num  121 NA 423 117 106 74 100 410 138 70 ...
 $ bu            : num  36 18 53 56 26 25 54 31 60 107 ...
 $ sc            : num  1.2 0.8 1.8 3.8 1.4 1.1 24 1.1 1.9 7.2 ...
 $ sod           : num  NA NA NA 111 NA 142 104 NA NA 114 ...
 $ pot           : num  NA NA NA 2.5 NA 3.2 4 NA NA 3.7 ...
 $ hemo          : num  15.4 11.3 9.6 11.2 11.6 12.2 12.4 12.4 10.8 9.5 ...
 $ pcv           : chr  "44" "38" "31" "32" ...
 $ wc            : chr  "7800" "6000" "7500" "6700" ...
 $ rc            : chr  "5.2" "" "" "3.9" ...
 $ htn           : chr  "yes" "no" "no" "yes" ...
 $ dm            : chr  "yes" "no" "yes" "no" ...
 $ cad           : chr  "no" "no" "no" "no" ...
 $ appet         : chr  "good" "good" "poor" "poor" ...
 $ pe            : chr  "no" "no" "no" "yes" ...
 $ ane           : chr  "no" "no" "yes" "yes" ...
 $ classification: chr  "ckd" "ckd" "ckd" "ckd" ...

We have an id variable with type ‘integer’, and numerous variables of types ‘numeric’ and ‘character’. From the key to the dataset, we can tell that the parameters ‘pcv’, ‘rc’, and ‘wc’ should probably be numerical, let’s deal with that later. Now, let’s check for presence of NA, along with distributions in the various columns using summary().

summary(kidney_data)
       id              age              bp               sg              al              su        
 Min.   :  0.00   Min.   : 2.00   Min.   : 50.00   Min.   :1.005   Min.   :0.000   Min.   :0.0000  
 1st Qu.: 99.75   1st Qu.:42.00   1st Qu.: 70.00   1st Qu.:1.010   1st Qu.:0.000   1st Qu.:0.0000  
 Median :199.50   Median :55.00   Median : 80.00   Median :1.020   Median :0.000   Median :0.0000  
 Mean   :199.50   Mean   :51.48   Mean   : 76.47   Mean   :1.017   Mean   :1.017   Mean   :0.4501  
 3rd Qu.:299.25   3rd Qu.:64.50   3rd Qu.: 80.00   3rd Qu.:1.020   3rd Qu.:2.000   3rd Qu.:0.0000  
 Max.   :399.00   Max.   :90.00   Max.   :180.00   Max.   :1.025   Max.   :5.000   Max.   :5.0000  
                  NA's   :9       NA's   :12       NA's   :47      NA's   :46      NA's   :49      
     rbc                 pc                pcc                 ba                 bgr            bu        
 Length:400         Length:400         Length:400         Length:400         Min.   : 22   Min.   :  1.50  
 Class :character   Class :character   Class :character   Class :character   1st Qu.: 99   1st Qu.: 27.00  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character   Median :121   Median : 42.00  
                                                                             Mean   :148   Mean   : 57.43  
                                                                             3rd Qu.:163   3rd Qu.: 66.00  
                                                                             Max.   :490   Max.   :391.00  
                                                                             NA's   :44    NA's   :19      
       sc              sod             pot              hemo           pcv                 wc           
 Min.   : 0.400   Min.   :  4.5   Min.   : 2.500   Min.   : 3.10   Length:400         Length:400        
 1st Qu.: 0.900   1st Qu.:135.0   1st Qu.: 3.800   1st Qu.:10.30   Class :character   Class :character  
 Median : 1.300   Median :138.0   Median : 4.400   Median :12.65   Mode  :character   Mode  :character  
 Mean   : 3.072   Mean   :137.5   Mean   : 4.627   Mean   :12.53                                        
 3rd Qu.: 2.800   3rd Qu.:142.0   3rd Qu.: 4.900   3rd Qu.:15.00                                        
 Max.   :76.000   Max.   :163.0   Max.   :47.000   Max.   :17.80                                        
 NA's   :17       NA's   :87      NA's   :88       NA's   :52                                           
      rc                htn                 dm                cad               appet          
 Length:400         Length:400         Length:400         Length:400         Length:400        
 Class :character   Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                                               
                                                                                               
                                                                                               
                                                                                               
      pe                ane            classification    
 Length:400         Length:400         Length:400        
 Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character  
                                                         
                                                         
                                                         
                                                         

We see here that most of our numeric variables contain a significant amount of NaN values. These have to be dealt with somehow. Let’s try to replace the NA in the ‘age’ column using the following ifelse() segment. This piece of code checks for a testexpression (in this case: is.na(kidney_data$age)), and then implements either arg2 (if TRUE) or arg3 (if FALSE). This way we should change all NA in ‘age’ to the median (arg2).


kidney_data$age <- ifelse(is.na(kidney_data$age), median(kidney_data$age, na.rm=TRUE), kidney_data$age) # Replaces any NA in the 'age' column with the median value.
summary(kidney_data$age)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   2.00   42.00   55.00   51.56   64.00   90.00 

We see here that our method removed all of the NA in the ‘age’ column, with minor changes to the other distribution parameters. These minor changes are likely related to that the ‘age’ column had relatively few NA. Obviously, replacing with the median doesn’t affect the median value.

Let’s use another method for the ‘bp’ column. We will instead replace the NA with the mean. The code segment used is very similar.

kidney_data$bp <- ifelse(is.na(kidney_data$bp), mean(kidney_data$bp, na.rm=TRUE), kidney_data$bp) # Replaces any NA value in the 'bp' column with the mean value.
summary(kidney_data$bp)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  50.00   70.00   78.23   76.47   80.00  180.00 

As we can see here, now the mean is unchanged and the median is affected instead. It is important to understand that all replacements of missing data will always change your dataset. However, for many purposes, replacing or removing missing data is necessary for further processing.

Let’s revert to the original dataframe and use the median replacement method for the entire dataframe.

kidney_data <- read.csv("C:/Data Science YH/R Programming/Final Assignment/data/kidney_disease.csv")

for (column in (1:ncol(kidney_data))) { # Loops over all columns in kidney_data
kidney_data[,column] <- ifelse(is.na(kidney_data[,column]), mean(kidney_data[,column], na.rm=TRUE), kidney_data[,column]) # Replaces all NA values in the columns with the mean value.
}
summary(kidney_data)
       id              age              bp               sg              al              su        
 Min.   :  0.00   Min.   : 2.00   Min.   : 50.00   Min.   :1.005   Min.   :0.000   Min.   :0.0000  
 1st Qu.: 99.75   1st Qu.:42.00   1st Qu.: 70.00   1st Qu.:1.015   1st Qu.:0.000   1st Qu.:0.0000  
 Median :199.50   Median :54.00   Median : 78.23   Median :1.017   Median :1.000   Median :0.0000  
 Mean   :199.50   Mean   :51.48   Mean   : 76.47   Mean   :1.017   Mean   :1.017   Mean   :0.4501  
 3rd Qu.:299.25   3rd Qu.:64.00   3rd Qu.: 80.00   3rd Qu.:1.020   3rd Qu.:2.000   3rd Qu.:0.4501  
 Max.   :399.00   Max.   :90.00   Max.   :180.00   Max.   :1.025   Max.   :5.000   Max.   :5.0000  
     rbc                 pc                pcc                 ba                 bgr            bu        
 Length:400         Length:400         Length:400         Length:400         Min.   : 22   Min.   :  1.50  
 Class :character   Class :character   Class :character   Class :character   1st Qu.:101   1st Qu.: 27.00  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character   Median :126   Median : 44.00  
                                                                             Mean   :148   Mean   : 57.43  
                                                                             3rd Qu.:150   3rd Qu.: 61.75  
                                                                             Max.   :490   Max.   :391.00  
       sc              sod             pot              hemo           pcv                 wc           
 Min.   : 0.400   Min.   :  4.5   Min.   : 2.500   Min.   : 3.10   Length:400         Length:400        
 1st Qu.: 0.900   1st Qu.:135.0   1st Qu.: 4.000   1st Qu.:10.88   Class :character   Class :character  
 Median : 1.400   Median :137.5   Median : 4.627   Median :12.53   Mode  :character   Mode  :character  
 Mean   : 3.072   Mean   :137.5   Mean   : 4.627   Mean   :12.53                                        
 3rd Qu.: 3.072   3rd Qu.:141.0   3rd Qu.: 4.800   3rd Qu.:14.62                                        
 Max.   :76.000   Max.   :163.0   Max.   :47.000   Max.   :17.80                                        
      rc                htn                 dm                cad               appet          
 Length:400         Length:400         Length:400         Length:400         Length:400        
 Class :character   Class :character   Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character   Mode  :character  
                                                                                               
                                                                                               
                                                                                               
      pe                ane            classification    
 Length:400         Length:400         Length:400        
 Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character  
                                                         
                                                         
                                                         

Voila! We have succesfully cleaned out all of the NA missing data in the dataset and replaced it with the median for each column!

Next, let’s look at the character columns. We start with investigating the ‘rbc’ column.

kidney_data %>% count(rbc)

Here we can see that we have two recorded values: abnormal (47) and normal (201). There’s also a substantial amount of missing data here (152). Missing data in binary categorical parameters is in some sense trickier than for numerical parameters, or categorical parameters with many different factors, as the mean and median doesn’t necessarily tell you much. In many cases, it can be beneficial to make the parameter categorical instead of binary, by introducing a third outcome: ‘no record’. Let’s do that with our ‘rbc’ column.

kidney_data$rbc <- ifelse((kidney_data$rbc == ""), 'no record', kidney_data$rbc) # Replaces any empty values with 'no record'.
table(kidney_data$rbc)

 abnormal no record    normal 
       47       152       201 

There we go! We have now imputed a new outcome ‘no record’, for our missing data in the ‘rbc’ column. Let’s investigate the other initial character parameters (‘pc’, ‘pcc’, and ‘ba’).

kidney_data %>% count(pc)
kidney_data %>% count(pcc)
kidney_data %>% count(ba)
NA

Count() gives us a dataframe as result, which is easier to work with than a table, like we get from table(). We see here that the ‘pc’ varaiable contains a lot of missing values as well, whereas the ‘pcc’ and ‘ba’ parameters have only 4. Let’s do the same replacement for ‘pc’ as we did for ‘rbc’.

kidney_data$pc <- ifelse((kidney_data$pc == ""), 'no record', kidney_data$pc) # Replaces any empty values with 'no record'.
kidney_data %>% count(pc)

Good. Now what to do with ‘pcc’ and ‘ba’? Let’s keep them as a binary parameters, and instead remove the rows with missing data. Since there’s only 4 rows with missing data in ‘pcc’ and ‘ba’, the impact on our overall data should be small.

kidney_data <- subset(kidney_data, pcc!="") # Removes any rows with empty values.
kidney_data <- subset(kidney_data, ba!="") # Removes any rows with empty values.

kidney_data %>% count(pcc)
kidney_data %>% count(ba)

Excellent! What about the rest of the character parameters? Let’s use count() on them as well.

kidney_data %>% count(htn)
kidney_data %>% count(dm)
kidney_data %>% count(cad)
kidney_data %>% count(appet)
kidney_data %>% count(pe)
kidney_data %>% count(ane)
kidney_data %>% count(classification)

Alright. That’s a lot of parameters! First and foremost, we can see that all of these parameters have binary outcomes. However, some of them have missing data (‘htn’, ‘dm’, ‘cad’, ‘appet’, ‘pe’), and some of them have corrupted data (‘dm’, ‘cad’, ‘classification’).

Let’s deal with the corrupted data first, and start with the ‘dm’ column. First, we’ll just print it out again.

kidney_data %>% count(dm)

So there’s a lot going on here. We have 6 different outcomes, but it is evident that the only true outcomes are ‘no’ or ‘yes’. So it’s a binary parameter. Let’s use an ifelse again, and just check for presence of the characters ‘no’ or ‘yes’. The grepl() function checks if a character is present in a string or not. Here we check if ‘yes’ is present in the string, then we replace it with a clean ‘yes’. Then we do the same for ‘no’, and have a look at our column again.

kidney_data$dm <- ifelse((grepl('yes', kidney_data$dm) == TRUE), 'yes', kidney_data$dm) #Replace any char containing 'yes' with 'Yes' only.
kidney_data$dm <- ifelse((grepl('no', kidney_data$dm) == TRUE), 'no', kidney_data$dm) #Replace any char containing 'no' with 'No' only.
kidney_data %>% count(dm)

Perfect! Now we can do the same with the other two columns with corrupted characters. In the case of ‘classification’, we need to be careful. Notice how both outcomes contain ‘ckd’. This means our standard replacement function will replace every outcome with ‘ckd’. Therefore, let’s change the outcome to ‘neg’ and ‘pos’ instead, and make sure we check for ‘notckd’ first.

kidney_data$cad <- ifelse((grepl('yes', kidney_data$cad) == TRUE), 'yes', kidney_data$cad) #Replace any char containing 'yes' with 'Yes' only.
kidney_data$cad <- ifelse((grepl('no', kidney_data$cad) == TRUE), 'no', kidney_data$cad) #Replace any char containing 'no' with 'No' only.

kidney_data$classification <- ifelse((grepl('notckd', kidney_data$classification) == TRUE), 'neg',  kidney_data$classification) #Replace any char containing 'notckd' with 'neg'.
kidney_data$classification <- ifelse((grepl('ckd', kidney_data$classification) == TRUE), 'pos', kidney_data$classification) #Replace any char containing 'ckd' with 'pos'.


kidney_data %>% count(cad)
kidney_data %>% count(classification)

Excellent! Now we have corrected the corrupt data in all of our character columns. Let’s evaluate again.

kidney_data %>% count(ba)
kidney_data %>% count(htn)
kidney_data %>% count(dm)
kidney_data %>% count(cad)
kidney_data %>% count(appet)
kidney_data %>% count(pe)
kidney_data %>% count(ane)
kidney_data %>% count(classification)

It seems like all of these parameters are now binary, but some are missing values. As for ‘pcc’, there are very few rows missing, so the best solution here is probably to remove the rows with missing values. We can use the same method as for ‘pcc’.

kidney_data <- subset(kidney_data, htn!="") # Removes all rows containing missing values.
kidney_data <- subset(kidney_data, dm!="") # Removes all rows containing missing values.
kidney_data <- subset(kidney_data, cad!="") # Removes all rows containing missing values.
kidney_data <- subset(kidney_data, appet!="") # Removes all rows containing missing values.
kidney_data <- subset(kidney_data, pe!="") # Removes all rows containing missing values.
kidney_data <- subset(kidney_data, ane!="") # Removes all rows containing missing values.

nrow(kidney_data)
[1] 393

It seems like in total, we removed only 7 row! Likely, a lot of the missing data were for the same recordings.

————————————————–

Now, we have three more columns that are of type ‘character’, that seem to contain only or mostly numerical values: ‘pcv’, ‘wc’ and ‘rc’. Let’s look at their count() and see what they look like.

kidney_data %>% count(pcv)
kidney_data %>% count(wc)
kidney_data %>% count(rc)

All these parameters have lots of missing values, and they all contain corrupted values. One way of handling this corruption in this case is to search for any characters that are not numeric and replace them with ’’. Let’s try this using the gsub() function.

kidney_data$pcv <- gsub("[^0-9.-]", "", kidney_data$pcv) # Removes any non-numeric characters
kidney_data$wc <- gsub("[^0-9.-]", "", kidney_data$wc) # Removes any non-numeric characters
kidney_data$rc <- gsub("[^0-9.-]", "", kidney_data$rc) # Removes any non-numeric characters

kidney_data %>% count(rc)
kidney_data %>% count(pcv)
kidney_data %>% count(wc)

Seems like it worked fine! Now, what remains is numerical data encoded as character, including lots of missing data points. Let’s convert the three datasets into numerical, and replace the missing data with the mean.

kidney_data$pcv <- as.numeric(kidney_data$pcv) # Force the dataset into type numeric
kidney_data$rc <- as.numeric(kidney_data$rc) # Force the dataset into type numeric
kidney_data$wc <- as.numeric(kidney_data$wc) # Force the dataset into type numeric

for (column in (17:19)) {
kidney_data[,column] <- ifelse(is.na(kidney_data[,column]), mean(kidney_data[,column], na.rm=TRUE), kidney_data[,column]) # Replaces any NA values with the mean.
}

There we go! Let’s have a final look at the summary() and str() of the dataset.

summary(kidney_data)
       id             age              bp               sg              al              su        
 Min.   :  0.0   Min.   : 2.00   Min.   : 50.00   Min.   :1.005   Min.   :0.000   Min.   :0.0000  
 1st Qu.: 98.0   1st Qu.:42.00   1st Qu.: 70.00   1st Qu.:1.015   1st Qu.:0.000   1st Qu.:0.0000  
 Median :196.0   Median :54.00   Median : 80.00   Median :1.017   Median :1.000   Median :0.0000  
 Mean   :197.7   Mean   :51.52   Mean   : 76.64   Mean   :1.017   Mean   :1.035   Mean   :0.4582  
 3rd Qu.:298.0   3rd Qu.:64.00   3rd Qu.: 80.00   3rd Qu.:1.020   3rd Qu.:2.000   3rd Qu.:0.4501  
 Max.   :399.0   Max.   :90.00   Max.   :180.00   Max.   :1.025   Max.   :5.000   Max.   :5.0000  
     rbc                 pc                pcc                 ba                 bgr              bu        
 Length:393         Length:393         Length:393         Length:393         Min.   : 22.0   Min.   :  1.50  
 Class :character   Class :character   Class :character   Class :character   1st Qu.:102.0   1st Qu.: 27.00  
 Mode  :character   Mode  :character   Mode  :character   Mode  :character   Median :127.0   Median : 45.00  
                                                                             Mean   :148.9   Mean   : 57.85  
                                                                             3rd Qu.:153.0   3rd Qu.: 65.00  
                                                                             Max.   :490.0   Max.   :391.00  
       sc              sod             pot              hemo            pcv              wc       
 Min.   : 0.400   Min.   :  4.5   Min.   : 2.500   Min.   : 3.10   Min.   : 9.00   Min.   : 2200  
 1st Qu.: 1.000   1st Qu.:135.0   1st Qu.: 4.000   1st Qu.:10.80   1st Qu.:34.00   1st Qu.: 6900  
 Median : 1.400   Median :137.5   Median : 4.627   Median :12.53   Median :38.73   Median : 8408  
 Mean   : 3.114   Mean   :137.5   Mean   : 4.627   Mean   :12.49   Mean   :38.73   Mean   : 8408  
 3rd Qu.: 3.072   3rd Qu.:141.0   3rd Qu.: 4.800   3rd Qu.:14.50   3rd Qu.:44.00   3rd Qu.: 9400  
 Max.   :76.000   Max.   :163.0   Max.   :47.000   Max.   :17.80   Max.   :54.00   Max.   :26400  
       rc            htn                 dm                cad               appet          
 Min.   :2.100   Length:393         Length:393         Length:393         Length:393        
 1st Qu.:4.500   Class :character   Class :character   Class :character   Class :character  
 Median :4.682   Mode  :character   Mode  :character   Mode  :character   Mode  :character  
 Mean   :4.682                                                                              
 3rd Qu.:5.000                                                                              
 Max.   :8.000                                                                              
      pe                ane            classification    
 Length:393         Length:393         Length:393        
 Class :character   Class :character   Class :character  
 Mode  :character   Mode  :character   Mode  :character  
                                                         
                                                         
                                                         
str(kidney_data)
'data.frame':   393 obs. of  26 variables:
 $ id            : int  0 1 2 3 4 5 6 7 8 9 ...
 $ age           : num  48 7 62 48 51 60 68 24 52 53 ...
 $ bp            : num  80 50 80 70 80 ...
 $ sg            : num  1.02 1.02 1.01 1 1.01 ...
 $ al            : num  1 4 2 4 2 3 0 2 3 2 ...
 $ su            : num  0 0 3 0 0 0 0 4 0 0 ...
 $ rbc           : chr  "no record" "no record" "normal" "normal" ...
 $ pc            : chr  "normal" "normal" "normal" "abnormal" ...
 $ pcc           : chr  "notpresent" "notpresent" "notpresent" "present" ...
 $ ba            : chr  "notpresent" "notpresent" "notpresent" "notpresent" ...
 $ bgr           : num  121 148 423 117 106 ...
 $ bu            : num  36 18 53 56 26 25 54 31 60 107 ...
 $ sc            : num  1.2 0.8 1.8 3.8 1.4 1.1 24 1.1 1.9 7.2 ...
 $ sod           : num  138 138 138 111 138 ...
 $ pot           : num  4.63 4.63 4.63 2.5 4.63 ...
 $ hemo          : num  15.4 11.3 9.6 11.2 11.6 12.2 12.4 12.4 10.8 9.5 ...
 $ pcv           : num  44 38 31 32 35 39 36 44 33 29 ...
 $ wc            : num  7800 6000 7500 6700 7300 ...
 $ rc            : num  5.2 4.68 4.68 3.9 4.6 ...
 $ htn           : chr  "yes" "no" "no" "yes" ...
 $ dm            : chr  "yes" "no" "yes" "no" ...
 $ cad           : chr  "no" "no" "no" "no" ...
 $ appet         : chr  "good" "good" "poor" "poor" ...
 $ pe            : chr  "no" "no" "no" "yes" ...
 $ ane           : chr  "no" "no" "yes" "yes" ...
 $ classification: chr  "pos" "pos" "pos" "pos" ...

That’s it! The dataset is now cleaned, and ready for the next step. If the data should be used for machine learning purposes, one might consider converting the categorical parameters to either dummy variables, or in the case of binary outcomes, booleans.

Let’s finish by exprting our new cleaned-up dataset as a csv-file.

write.csv(kidney_data, 'C:/Data Science YH/R Programming/Final Assignment/data/kidney_disease_cleaned.csv')
LS0tDQp0aXRsZTogIlByZS1wcm9jZXNzaW5nIG9mIEtpZG5leSBEYXRhIHNldCINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNClByZS1wcm9jZXNzaW5nIG9mIGRhdGEgZnJvbSB0aGUgY2hyb25pYyBraWRuZXkgZGlzZWFzZSBkYXRhc2V0IGZyb20gS2FnZ2xlIChodHRwczovL3d3dy5rYWdnbGUuY29tL2RhdGFzZXRzL21hbnNvb3JkYWt1L2NrZGlzZWFzZSkNCg0KSW5pdGlhbGx5LCB3ZSB3aWxsIGxvYWQgb3VyIGRhdGEgaW50byBSIHVzaW5nIHJlYWQuY3N2KCkuDQpMZXQncyBhbHNvIGxvYWQgdGhlIGxpYnJhcnkgJ3RpZHl2ZXJzZScsIHdoaWNoIHdlIHdpbGwgdXNlIGluIG91ciBwcm9jZXNzaW5nLg0KYGBge3J9DQpraWRuZXlfZGF0YSA8LSByZWFkLmNzdignQzovRGF0YSBTY2llbmNlIFlIL1IgUHJvZ3JhbW1pbmcvRmluYWwgQXNzaWdubWVudC9kYXRhL2tpZG5leV9kaXNlYXNlLmNzdicpDQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmBgYA0KTmV4dCwgbGV0J3MgaW52ZXN0aWdhdGUgdGhlIGRhdGEgc3RydWN0dXJlIGluIHRoZSBkYXRhc2V0IHVzaW5nIHRoZSBzdHIoKSBmdW5jdGlvbi4NCmBgYHtyfQ0Kc3RyKGtpZG5leV9kYXRhKQ0KYGBgDQpXZSBoYXZlIGFuIGlkIHZhcmlhYmxlIHdpdGggdHlwZSAnaW50ZWdlcicsIGFuZCBudW1lcm91cyB2YXJpYWJsZXMgb2YgdHlwZXMgJ251bWVyaWMnIGFuZCAnY2hhcmFjdGVyJy4gRnJvbSB0aGUga2V5IHRvIHRoZSBkYXRhc2V0LCB3ZSBjYW4gdGVsbCB0aGF0IHRoZSBwYXJhbWV0ZXJzICdwY3YnLCAncmMnLCBhbmQgJ3djJyBzaG91bGQgcHJvYmFibHkgYmUgbnVtZXJpY2FsLCBsZXQncyBkZWFsIHdpdGggdGhhdCBsYXRlci4gTm93LCBsZXQncyBjaGVjayBmb3IgcHJlc2VuY2Ugb2YgTkEsIGFsb25nIHdpdGggZGlzdHJpYnV0aW9ucyBpbiB0aGUgdmFyaW91cyBjb2x1bW5zIHVzaW5nIHN1bW1hcnkoKS4NCmBgYHtyfQ0Kc3VtbWFyeShraWRuZXlfZGF0YSkNCmBgYA0KV2Ugc2VlIGhlcmUgdGhhdCBtb3N0IG9mIG91ciBudW1lcmljIHZhcmlhYmxlcyBjb250YWluIGEgc2lnbmlmaWNhbnQgYW1vdW50IG9mIE5hTiB2YWx1ZXMuIFRoZXNlIGhhdmUgdG8gYmUgZGVhbHQgd2l0aCBzb21laG93LiBMZXQncyB0cnkgdG8gcmVwbGFjZSB0aGUgTkEgaW4gdGhlICdhZ2UnIGNvbHVtbiB1c2luZyB0aGUgZm9sbG93aW5nIGlmZWxzZSgpIHNlZ21lbnQuIFRoaXMgcGllY2Ugb2YgY29kZSBjaGVja3MgZm9yIGEgdGVzdGV4cHJlc3Npb24gKGluIHRoaXMgY2FzZTogaXMubmEoa2lkbmV5X2RhdGEkYWdlKSksIGFuZCB0aGVuIGltcGxlbWVudHMgZWl0aGVyIGFyZzIgKGlmIFRSVUUpIG9yIGFyZzMgKGlmIEZBTFNFKS4gVGhpcyB3YXkgd2Ugc2hvdWxkIGNoYW5nZSBhbGwgTkEgaW4gJ2FnZScgdG8gdGhlIG1lZGlhbiAoYXJnMikuDQpgYGB7cn0NCg0Ka2lkbmV5X2RhdGEkYWdlIDwtIGlmZWxzZShpcy5uYShraWRuZXlfZGF0YSRhZ2UpLCBtZWRpYW4oa2lkbmV5X2RhdGEkYWdlLCBuYS5ybT1UUlVFKSwga2lkbmV5X2RhdGEkYWdlKSAjIFJlcGxhY2VzIGFueSBOQSBpbiB0aGUgJ2FnZScgY29sdW1uIHdpdGggdGhlIG1lZGlhbiB2YWx1ZS4NCnN1bW1hcnkoa2lkbmV5X2RhdGEkYWdlKQ0KDQpgYGANCldlIHNlZSBoZXJlIHRoYXQgb3VyIG1ldGhvZCByZW1vdmVkIGFsbCBvZiB0aGUgTkEgaW4gdGhlICdhZ2UnIGNvbHVtbiwgd2l0aCBtaW5vciBjaGFuZ2VzIHRvIHRoZSBvdGhlciBkaXN0cmlidXRpb24gcGFyYW1ldGVycy4gVGhlc2UgbWlub3IgY2hhbmdlcyBhcmUgbGlrZWx5IHJlbGF0ZWQgdG8gdGhhdCB0aGUgJ2FnZScgY29sdW1uIGhhZCByZWxhdGl2ZWx5IGZldyBOQS4gT2J2aW91c2x5LCByZXBsYWNpbmcgd2l0aCB0aGUgbWVkaWFuIGRvZXNuJ3QgYWZmZWN0IHRoZSBtZWRpYW4gdmFsdWUuDQoNCkxldCdzIHVzZSBhbm90aGVyIG1ldGhvZCBmb3IgdGhlICdicCcgY29sdW1uLiBXZSB3aWxsIGluc3RlYWQgcmVwbGFjZSB0aGUgTkEgd2l0aCB0aGUgbWVhbi4gVGhlIGNvZGUgc2VnbWVudCB1c2VkIGlzIHZlcnkgc2ltaWxhci4NCmBgYHtyfQ0Ka2lkbmV5X2RhdGEkYnAgPC0gaWZlbHNlKGlzLm5hKGtpZG5leV9kYXRhJGJwKSwgbWVhbihraWRuZXlfZGF0YSRicCwgbmEucm09VFJVRSksIGtpZG5leV9kYXRhJGJwKSAjIFJlcGxhY2VzIGFueSBOQSB2YWx1ZSBpbiB0aGUgJ2JwJyBjb2x1bW4gd2l0aCB0aGUgbWVhbiB2YWx1ZS4NCnN1bW1hcnkoa2lkbmV5X2RhdGEkYnApDQpgYGANCkFzIHdlIGNhbiBzZWUgaGVyZSwgbm93IHRoZSBtZWFuIGlzIHVuY2hhbmdlZCBhbmQgdGhlIG1lZGlhbiBpcyBhZmZlY3RlZCBpbnN0ZWFkLiBJdCBpcyBpbXBvcnRhbnQgdG8gdW5kZXJzdGFuZCB0aGF0IGFsbCByZXBsYWNlbWVudHMgb2YgbWlzc2luZyBkYXRhIHdpbGwgYWx3YXlzIGNoYW5nZSB5b3VyIGRhdGFzZXQuIEhvd2V2ZXIsIGZvciBtYW55IHB1cnBvc2VzLCByZXBsYWNpbmcgb3IgcmVtb3ZpbmcgbWlzc2luZyBkYXRhIGlzIG5lY2Vzc2FyeSBmb3IgZnVydGhlciBwcm9jZXNzaW5nLg0KDQpMZXQncyByZXZlcnQgdG8gdGhlIG9yaWdpbmFsIGRhdGFmcmFtZSBhbmQgdXNlIHRoZSBtZWRpYW4gcmVwbGFjZW1lbnQgbWV0aG9kIGZvciB0aGUgZW50aXJlIGRhdGFmcmFtZS4NCmBgYHtyfQ0Ka2lkbmV5X2RhdGEgPC0gcmVhZC5jc3YoIkM6L0RhdGEgU2NpZW5jZSBZSC9SIFByb2dyYW1taW5nL0ZpbmFsIEFzc2lnbm1lbnQvZGF0YS9raWRuZXlfZGlzZWFzZS5jc3YiKQ0KDQpmb3IgKGNvbHVtbiBpbiAoMTpuY29sKGtpZG5leV9kYXRhKSkpIHsgIyBMb29wcyBvdmVyIGFsbCBjb2x1bW5zIGluIGtpZG5leV9kYXRhDQpraWRuZXlfZGF0YVssY29sdW1uXSA8LSBpZmVsc2UoaXMubmEoa2lkbmV5X2RhdGFbLGNvbHVtbl0pLCBtZWFuKGtpZG5leV9kYXRhWyxjb2x1bW5dLCBuYS5ybT1UUlVFKSwga2lkbmV5X2RhdGFbLGNvbHVtbl0pICMgUmVwbGFjZXMgYWxsIE5BIHZhbHVlcyBpbiB0aGUgY29sdW1ucyB3aXRoIHRoZSBtZWFuIHZhbHVlLg0KfQ0Kc3VtbWFyeShraWRuZXlfZGF0YSkNCmBgYA0KVm9pbGEhIFdlIGhhdmUgc3VjY2VzZnVsbHkgY2xlYW5lZCBvdXQgYWxsIG9mIHRoZSBOQSBtaXNzaW5nIGRhdGEgaW4gdGhlIGRhdGFzZXQgYW5kIHJlcGxhY2VkIGl0IHdpdGggdGhlIG1lZGlhbiBmb3IgZWFjaCBjb2x1bW4hDQoNCk5leHQsIGxldCdzIGxvb2sgYXQgdGhlIGNoYXJhY3RlciBjb2x1bW5zLiBXZSBzdGFydCB3aXRoIGludmVzdGlnYXRpbmcgdGhlICdyYmMnIGNvbHVtbi4NCmBgYHtyfQ0Ka2lkbmV5X2RhdGEgJT4lIGNvdW50KHJiYykNCmBgYA0KSGVyZSB3ZSBjYW4gc2VlIHRoYXQgd2UgaGF2ZSB0d28gcmVjb3JkZWQgdmFsdWVzOiBhYm5vcm1hbCAoNDcpIGFuZCBub3JtYWwgKDIwMSkuIFRoZXJlJ3MgYWxzbyBhIHN1YnN0YW50aWFsIGFtb3VudCBvZiBtaXNzaW5nIGRhdGEgaGVyZSAoMTUyKS4gTWlzc2luZyBkYXRhIGluIGJpbmFyeSBjYXRlZ29yaWNhbCBwYXJhbWV0ZXJzIGlzIGluIHNvbWUgc2Vuc2UgdHJpY2tpZXIgdGhhbiBmb3IgbnVtZXJpY2FsIHBhcmFtZXRlcnMsIG9yIGNhdGVnb3JpY2FsIHBhcmFtZXRlcnMgd2l0aCBtYW55IGRpZmZlcmVudCBmYWN0b3JzLCBhcyB0aGUgbWVhbiBhbmQgbWVkaWFuIGRvZXNuJ3QgbmVjZXNzYXJpbHkgdGVsbCB5b3UgbXVjaC4gSW4gbWFueSBjYXNlcywgaXQgY2FuIGJlIGJlbmVmaWNpYWwgdG8gbWFrZSB0aGUgcGFyYW1ldGVyIGNhdGVnb3JpY2FsIGluc3RlYWQgb2YgYmluYXJ5LCBieSBpbnRyb2R1Y2luZyBhIHRoaXJkIG91dGNvbWU6ICdubyByZWNvcmQnLiBMZXQncyBkbyB0aGF0IHdpdGggb3VyICdyYmMnIGNvbHVtbi4NCmBgYHtyfQ0Ka2lkbmV5X2RhdGEkcmJjIDwtIGlmZWxzZSgoa2lkbmV5X2RhdGEkcmJjID09ICIiKSwgJ25vIHJlY29yZCcsIGtpZG5leV9kYXRhJHJiYykgIyBSZXBsYWNlcyBhbnkgZW1wdHkgdmFsdWVzIHdpdGggJ25vIHJlY29yZCcuDQp0YWJsZShraWRuZXlfZGF0YSRyYmMpDQoNCmBgYA0KVGhlcmUgd2UgZ28hIFdlIGhhdmUgbm93IGltcHV0ZWQgYSBuZXcgb3V0Y29tZSAnbm8gcmVjb3JkJywgZm9yIG91ciBtaXNzaW5nIGRhdGEgaW4gdGhlICdyYmMnIGNvbHVtbi4gTGV0J3MgaW52ZXN0aWdhdGUgdGhlIG90aGVyIGluaXRpYWwgY2hhcmFjdGVyIHBhcmFtZXRlcnMgKCdwYycsICdwY2MnLCBhbmQgJ2JhJykuDQpgYGB7cn0NCmtpZG5leV9kYXRhICU+JSBjb3VudChwYykNCmtpZG5leV9kYXRhICU+JSBjb3VudChwY2MpDQpraWRuZXlfZGF0YSAlPiUgY291bnQoYmEpDQoNCmBgYA0KQ291bnQoKSBnaXZlcyB1cyBhIGRhdGFmcmFtZSBhcyByZXN1bHQsIHdoaWNoIGlzIGVhc2llciB0byB3b3JrIHdpdGggdGhhbiBhIHRhYmxlLCBsaWtlIHdlIGdldCBmcm9tIHRhYmxlKCkuIFdlIHNlZSBoZXJlIHRoYXQgdGhlICdwYycgdmFyYWlhYmxlIGNvbnRhaW5zIGEgbG90IG9mIG1pc3NpbmcgdmFsdWVzIGFzIHdlbGwsIHdoZXJlYXMgdGhlICdwY2MnIGFuZCAnYmEnIHBhcmFtZXRlcnMgaGF2ZSBvbmx5IDQuIExldCdzIGRvIHRoZSBzYW1lIHJlcGxhY2VtZW50IGZvciAncGMnIGFzIHdlIGRpZCBmb3IgJ3JiYycuDQpgYGB7cn0NCmtpZG5leV9kYXRhJHBjIDwtIGlmZWxzZSgoa2lkbmV5X2RhdGEkcGMgPT0gIiIpLCAnbm8gcmVjb3JkJywga2lkbmV5X2RhdGEkcGMpICMgUmVwbGFjZXMgYW55IGVtcHR5IHZhbHVlcyB3aXRoICdubyByZWNvcmQnLg0Ka2lkbmV5X2RhdGEgJT4lIGNvdW50KHBjKQ0KYGBgDQpHb29kLiBOb3cgd2hhdCB0byBkbyB3aXRoICdwY2MnIGFuZCAnYmEnPyBMZXQncyBrZWVwIHRoZW0gYXMgYSBiaW5hcnkgcGFyYW1ldGVycywgYW5kIGluc3RlYWQgcmVtb3ZlIHRoZSByb3dzIHdpdGggbWlzc2luZyBkYXRhLiBTaW5jZSB0aGVyZSdzIG9ubHkgNCByb3dzIHdpdGggbWlzc2luZyBkYXRhIGluICdwY2MnIGFuZCAnYmEnLCB0aGUgaW1wYWN0IG9uIG91ciBvdmVyYWxsIGRhdGEgc2hvdWxkIGJlIHNtYWxsLg0KYGBge3J9DQpraWRuZXlfZGF0YSA8LSBzdWJzZXQoa2lkbmV5X2RhdGEsIHBjYyE9IiIpICMgUmVtb3ZlcyBhbnkgcm93cyB3aXRoIGVtcHR5IHZhbHVlcy4NCmtpZG5leV9kYXRhIDwtIHN1YnNldChraWRuZXlfZGF0YSwgYmEhPSIiKSAjIFJlbW92ZXMgYW55IHJvd3Mgd2l0aCBlbXB0eSB2YWx1ZXMuDQoNCmtpZG5leV9kYXRhICU+JSBjb3VudChwY2MpDQpraWRuZXlfZGF0YSAlPiUgY291bnQoYmEpDQpgYGANCkV4Y2VsbGVudCEgV2hhdCBhYm91dCB0aGUgcmVzdCBvZiB0aGUgY2hhcmFjdGVyIHBhcmFtZXRlcnM/IExldCdzIHVzZSBjb3VudCgpIG9uIHRoZW0gYXMgd2VsbC4NCmBgYHtyfQ0Ka2lkbmV5X2RhdGEgJT4lIGNvdW50KGh0bikNCmtpZG5leV9kYXRhICU+JSBjb3VudChkbSkNCmtpZG5leV9kYXRhICU+JSBjb3VudChjYWQpDQpraWRuZXlfZGF0YSAlPiUgY291bnQoYXBwZXQpDQpraWRuZXlfZGF0YSAlPiUgY291bnQocGUpDQpraWRuZXlfZGF0YSAlPiUgY291bnQoYW5lKQ0Ka2lkbmV5X2RhdGEgJT4lIGNvdW50KGNsYXNzaWZpY2F0aW9uKQ0KYGBgDQpBbHJpZ2h0LiBUaGF0J3MgYSBsb3Qgb2YgcGFyYW1ldGVycyEgRmlyc3QgYW5kIGZvcmVtb3N0LCB3ZSBjYW4gc2VlIHRoYXQgYWxsIG9mIHRoZXNlIHBhcmFtZXRlcnMgaGF2ZSBiaW5hcnkgb3V0Y29tZXMuIEhvd2V2ZXIsIHNvbWUgb2YgdGhlbSBoYXZlIG1pc3NpbmcgZGF0YSAoJ2h0bicsICdkbScsICdjYWQnLCAnYXBwZXQnLCAncGUnKSwgYW5kIHNvbWUgb2YgdGhlbSBoYXZlIGNvcnJ1cHRlZCBkYXRhICgnZG0nLCAnY2FkJywgJ2NsYXNzaWZpY2F0aW9uJykuDQoNCkxldCdzIGRlYWwgd2l0aCB0aGUgY29ycnVwdGVkIGRhdGEgZmlyc3QsIGFuZCBzdGFydCB3aXRoIHRoZSAnZG0nIGNvbHVtbi4gRmlyc3QsIHdlJ2xsIGp1c3QgcHJpbnQgaXQgb3V0IGFnYWluLg0KYGBge3J9DQpraWRuZXlfZGF0YSAlPiUgY291bnQoZG0pDQpgYGANClNvIHRoZXJlJ3MgYSBsb3QgZ29pbmcgb24gaGVyZS4gV2UgaGF2ZSA2IGRpZmZlcmVudCBvdXRjb21lcywgYnV0IGl0IGlzIGV2aWRlbnQgdGhhdCB0aGUgb25seSB0cnVlIG91dGNvbWVzIGFyZSAnbm8nIG9yICd5ZXMnLiBTbyBpdCdzIGEgYmluYXJ5IHBhcmFtZXRlci4gTGV0J3MgdXNlIGFuIGlmZWxzZSBhZ2FpbiwgYW5kIGp1c3QgY2hlY2sgZm9yIHByZXNlbmNlIG9mIHRoZSBjaGFyYWN0ZXJzICdubycgb3IgJ3llcycuIFRoZSBncmVwbCgpIGZ1bmN0aW9uIGNoZWNrcyBpZiBhIGNoYXJhY3RlciBpcyBwcmVzZW50IGluIGEgc3RyaW5nIG9yIG5vdC4gSGVyZSB3ZSBjaGVjayBpZiAneWVzJyBpcyBwcmVzZW50IGluIHRoZSBzdHJpbmcsIHRoZW4gd2UgcmVwbGFjZSBpdCB3aXRoIGEgY2xlYW4gJ3llcycuIFRoZW4gd2UgZG8gdGhlIHNhbWUgZm9yICdubycsIGFuZCBoYXZlIGEgbG9vayBhdCBvdXIgY29sdW1uIGFnYWluLg0KYGBge3J9IA0Ka2lkbmV5X2RhdGEkZG0gPC0gaWZlbHNlKChncmVwbCgneWVzJywga2lkbmV5X2RhdGEkZG0pID09IFRSVUUpLCAneWVzJywga2lkbmV5X2RhdGEkZG0pICNSZXBsYWNlIGFueSBjaGFyIGNvbnRhaW5pbmcgJ3llcycgd2l0aCAnWWVzJyBvbmx5Lg0Ka2lkbmV5X2RhdGEkZG0gPC0gaWZlbHNlKChncmVwbCgnbm8nLCBraWRuZXlfZGF0YSRkbSkgPT0gVFJVRSksICdubycsIGtpZG5leV9kYXRhJGRtKSAjUmVwbGFjZSBhbnkgY2hhciBjb250YWluaW5nICdubycgd2l0aCAnTm8nIG9ubHkuDQpraWRuZXlfZGF0YSAlPiUgY291bnQoZG0pDQpgYGANClBlcmZlY3QhIE5vdyB3ZSBjYW4gZG8gdGhlIHNhbWUgd2l0aCB0aGUgb3RoZXIgdHdvIGNvbHVtbnMgd2l0aCBjb3JydXB0ZWQgY2hhcmFjdGVycy4gSW4gdGhlIGNhc2Ugb2YgJ2NsYXNzaWZpY2F0aW9uJywgd2UgbmVlZCB0byBiZSBjYXJlZnVsLiBOb3RpY2UgaG93IGJvdGggb3V0Y29tZXMgY29udGFpbiAnY2tkJy4gVGhpcyBtZWFucyBvdXIgc3RhbmRhcmQgcmVwbGFjZW1lbnQgZnVuY3Rpb24gd2lsbCByZXBsYWNlIGV2ZXJ5IG91dGNvbWUgd2l0aCAnY2tkJy4gVGhlcmVmb3JlLCBsZXQncyBjaGFuZ2UgdGhlIG91dGNvbWUgdG8gJ25lZycgYW5kICdwb3MnIGluc3RlYWQsIGFuZCBtYWtlIHN1cmUgd2UgY2hlY2sgZm9yICdub3Rja2QnIGZpcnN0Lg0KYGBge3J9DQpraWRuZXlfZGF0YSRjYWQgPC0gaWZlbHNlKChncmVwbCgneWVzJywga2lkbmV5X2RhdGEkY2FkKSA9PSBUUlVFKSwgJ3llcycsIGtpZG5leV9kYXRhJGNhZCkgI1JlcGxhY2UgYW55IGNoYXIgY29udGFpbmluZyAneWVzJyB3aXRoICdZZXMnIG9ubHkuDQpraWRuZXlfZGF0YSRjYWQgPC0gaWZlbHNlKChncmVwbCgnbm8nLCBraWRuZXlfZGF0YSRjYWQpID09IFRSVUUpLCAnbm8nLCBraWRuZXlfZGF0YSRjYWQpICNSZXBsYWNlIGFueSBjaGFyIGNvbnRhaW5pbmcgJ25vJyB3aXRoICdObycgb25seS4NCg0Ka2lkbmV5X2RhdGEkY2xhc3NpZmljYXRpb24gPC0gaWZlbHNlKChncmVwbCgnbm90Y2tkJywga2lkbmV5X2RhdGEkY2xhc3NpZmljYXRpb24pID09IFRSVUUpLCAnbmVnJywgIGtpZG5leV9kYXRhJGNsYXNzaWZpY2F0aW9uKSAjUmVwbGFjZSBhbnkgY2hhciBjb250YWluaW5nICdub3Rja2QnIHdpdGggJ25lZycuDQpraWRuZXlfZGF0YSRjbGFzc2lmaWNhdGlvbiA8LSBpZmVsc2UoKGdyZXBsKCdja2QnLCBraWRuZXlfZGF0YSRjbGFzc2lmaWNhdGlvbikgPT0gVFJVRSksICdwb3MnLCBraWRuZXlfZGF0YSRjbGFzc2lmaWNhdGlvbikgI1JlcGxhY2UgYW55IGNoYXIgY29udGFpbmluZyAnY2tkJyB3aXRoICdwb3MnLg0KDQoNCmtpZG5leV9kYXRhICU+JSBjb3VudChjYWQpDQpraWRuZXlfZGF0YSAlPiUgY291bnQoY2xhc3NpZmljYXRpb24pDQpgYGANCkV4Y2VsbGVudCEgTm93IHdlIGhhdmUgY29ycmVjdGVkIHRoZSBjb3JydXB0IGRhdGEgaW4gYWxsIG9mIG91ciBjaGFyYWN0ZXIgY29sdW1ucy4gTGV0J3MgZXZhbHVhdGUgYWdhaW4uDQpgYGB7cn0NCmtpZG5leV9kYXRhICU+JSBjb3VudChiYSkNCmtpZG5leV9kYXRhICU+JSBjb3VudChodG4pDQpraWRuZXlfZGF0YSAlPiUgY291bnQoZG0pDQpraWRuZXlfZGF0YSAlPiUgY291bnQoY2FkKQ0Ka2lkbmV5X2RhdGEgJT4lIGNvdW50KGFwcGV0KQ0Ka2lkbmV5X2RhdGEgJT4lIGNvdW50KHBlKQ0Ka2lkbmV5X2RhdGEgJT4lIGNvdW50KGFuZSkNCmtpZG5leV9kYXRhICU+JSBjb3VudChjbGFzc2lmaWNhdGlvbikNCmBgYA0KSXQgc2VlbXMgbGlrZSBhbGwgb2YgdGhlc2UgcGFyYW1ldGVycyBhcmUgbm93IGJpbmFyeSwgYnV0IHNvbWUgYXJlIG1pc3NpbmcgdmFsdWVzLiBBcyBmb3IgJ3BjYycsIHRoZXJlIGFyZSB2ZXJ5IGZldyByb3dzIG1pc3NpbmcsIHNvIHRoZSBiZXN0IHNvbHV0aW9uIGhlcmUgaXMgcHJvYmFibHkgdG8gcmVtb3ZlIHRoZSByb3dzIHdpdGggbWlzc2luZyB2YWx1ZXMuIFdlIGNhbiB1c2UgdGhlIHNhbWUgbWV0aG9kIGFzIGZvciAncGNjJy4NCmBgYHtyfQ0Ka2lkbmV5X2RhdGEgPC0gc3Vic2V0KGtpZG5leV9kYXRhLCBodG4hPSIiKSAjIFJlbW92ZXMgYWxsIHJvd3MgY29udGFpbmluZyBtaXNzaW5nIHZhbHVlcy4NCmtpZG5leV9kYXRhIDwtIHN1YnNldChraWRuZXlfZGF0YSwgZG0hPSIiKSAjIFJlbW92ZXMgYWxsIHJvd3MgY29udGFpbmluZyBtaXNzaW5nIHZhbHVlcy4NCmtpZG5leV9kYXRhIDwtIHN1YnNldChraWRuZXlfZGF0YSwgY2FkIT0iIikgIyBSZW1vdmVzIGFsbCByb3dzIGNvbnRhaW5pbmcgbWlzc2luZyB2YWx1ZXMuDQpraWRuZXlfZGF0YSA8LSBzdWJzZXQoa2lkbmV5X2RhdGEsIGFwcGV0IT0iIikgIyBSZW1vdmVzIGFsbCByb3dzIGNvbnRhaW5pbmcgbWlzc2luZyB2YWx1ZXMuDQpraWRuZXlfZGF0YSA8LSBzdWJzZXQoa2lkbmV5X2RhdGEsIHBlIT0iIikgIyBSZW1vdmVzIGFsbCByb3dzIGNvbnRhaW5pbmcgbWlzc2luZyB2YWx1ZXMuDQpraWRuZXlfZGF0YSA8LSBzdWJzZXQoa2lkbmV5X2RhdGEsIGFuZSE9IiIpICMgUmVtb3ZlcyBhbGwgcm93cyBjb250YWluaW5nIG1pc3NpbmcgdmFsdWVzLg0KDQpucm93KGtpZG5leV9kYXRhKQ0KYGBgDQpJdCBzZWVtcyBsaWtlIGluIHRvdGFsLCB3ZSByZW1vdmVkIG9ubHkgNyByb3chIExpa2VseSwgYSBsb3Qgb2YgdGhlIG1pc3NpbmcgZGF0YSB3ZXJlIGZvciB0aGUgc2FtZSByZWNvcmRpbmdzLg0KDQojIyMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0NCg0KTm93LCB3ZSBoYXZlIHRocmVlIG1vcmUgY29sdW1ucyB0aGF0IGFyZSBvZiB0eXBlICdjaGFyYWN0ZXInLCB0aGF0IHNlZW0gdG8gY29udGFpbiBvbmx5IG9yIG1vc3RseSBudW1lcmljYWwgdmFsdWVzOiAncGN2JywgJ3djJyBhbmQgJ3JjJy4gTGV0J3MgbG9vayBhdCB0aGVpciBjb3VudCgpIGFuZCBzZWUgd2hhdCB0aGV5IGxvb2sgbGlrZS4NCmBgYHtyfQ0Ka2lkbmV5X2RhdGEgJT4lIGNvdW50KHBjdikNCmtpZG5leV9kYXRhICU+JSBjb3VudCh3YykNCmtpZG5leV9kYXRhICU+JSBjb3VudChyYykNCmBgYA0KQWxsIHRoZXNlIHBhcmFtZXRlcnMgaGF2ZSBsb3RzIG9mIG1pc3NpbmcgdmFsdWVzLCBhbmQgdGhleSBhbGwgY29udGFpbiBjb3JydXB0ZWQgdmFsdWVzLiBPbmUgd2F5IG9mIGhhbmRsaW5nIHRoaXMgY29ycnVwdGlvbiBpbiB0aGlzIGNhc2UgaXMgdG8gc2VhcmNoIGZvciBhbnkgY2hhcmFjdGVycyB0aGF0IGFyZSBub3QgbnVtZXJpYyBhbmQgcmVwbGFjZSB0aGVtIHdpdGggJycuIExldCdzIHRyeSB0aGlzIHVzaW5nIHRoZSBnc3ViKCkgZnVuY3Rpb24uDQpgYGB7cn0NCmtpZG5leV9kYXRhJHBjdiA8LSBnc3ViKCJbXjAtOS4tXSIsICIiLCBraWRuZXlfZGF0YSRwY3YpICMgUmVtb3ZlcyBhbnkgbm9uLW51bWVyaWMgY2hhcmFjdGVycw0Ka2lkbmV5X2RhdGEkd2MgPC0gZ3N1YigiW14wLTkuLV0iLCAiIiwga2lkbmV5X2RhdGEkd2MpICMgUmVtb3ZlcyBhbnkgbm9uLW51bWVyaWMgY2hhcmFjdGVycw0Ka2lkbmV5X2RhdGEkcmMgPC0gZ3N1YigiW14wLTkuLV0iLCAiIiwga2lkbmV5X2RhdGEkcmMpICMgUmVtb3ZlcyBhbnkgbm9uLW51bWVyaWMgY2hhcmFjdGVycw0KDQpraWRuZXlfZGF0YSAlPiUgY291bnQocmMpDQpraWRuZXlfZGF0YSAlPiUgY291bnQocGN2KQ0Ka2lkbmV5X2RhdGEgJT4lIGNvdW50KHdjKQ0KYGBgDQpTZWVtcyBsaWtlIGl0IHdvcmtlZCBmaW5lISBOb3csIHdoYXQgcmVtYWlucyBpcyBudW1lcmljYWwgZGF0YSBlbmNvZGVkIGFzIGNoYXJhY3RlciwgaW5jbHVkaW5nIGxvdHMgb2YgbWlzc2luZyBkYXRhIHBvaW50cy4gTGV0J3MgY29udmVydCB0aGUgdGhyZWUgZGF0YXNldHMgaW50byBudW1lcmljYWwsIGFuZCByZXBsYWNlIHRoZSBtaXNzaW5nIGRhdGEgd2l0aCB0aGUgbWVhbi4NCmBgYHtyfQ0Ka2lkbmV5X2RhdGEkcGN2IDwtIGFzLm51bWVyaWMoa2lkbmV5X2RhdGEkcGN2KSAjIEZvcmNlIHRoZSBkYXRhc2V0IGludG8gdHlwZSBudW1lcmljDQpraWRuZXlfZGF0YSRyYyA8LSBhcy5udW1lcmljKGtpZG5leV9kYXRhJHJjKSAjIEZvcmNlIHRoZSBkYXRhc2V0IGludG8gdHlwZSBudW1lcmljDQpraWRuZXlfZGF0YSR3YyA8LSBhcy5udW1lcmljKGtpZG5leV9kYXRhJHdjKSAjIEZvcmNlIHRoZSBkYXRhc2V0IGludG8gdHlwZSBudW1lcmljDQoNCmZvciAoY29sdW1uIGluICgxNzoxOSkpIHsNCmtpZG5leV9kYXRhWyxjb2x1bW5dIDwtIGlmZWxzZShpcy5uYShraWRuZXlfZGF0YVssY29sdW1uXSksIG1lYW4oa2lkbmV5X2RhdGFbLGNvbHVtbl0sIG5hLnJtPVRSVUUpLCBraWRuZXlfZGF0YVssY29sdW1uXSkgIyBSZXBsYWNlcyBhbnkgTkEgdmFsdWVzIHdpdGggdGhlIG1lYW4uDQp9DQpgYGANClRoZXJlIHdlIGdvISBMZXQncyBoYXZlIGEgZmluYWwgbG9vayBhdCB0aGUgc3VtbWFyeSgpIGFuZCBzdHIoKSBvZiB0aGUgZGF0YXNldC4NCmBgYHtyfQ0Kc3VtbWFyeShraWRuZXlfZGF0YSkNCnN0cihraWRuZXlfZGF0YSkNCmBgYA0KVGhhdCdzIGl0ISBUaGUgZGF0YXNldCBpcyBub3cgY2xlYW5lZCwgYW5kIHJlYWR5IGZvciB0aGUgbmV4dCBzdGVwLiBJZiB0aGUgZGF0YSBzaG91bGQgYmUgdXNlZCBmb3IgbWFjaGluZSBsZWFybmluZyBwdXJwb3Nlcywgb25lIG1pZ2h0IGNvbnNpZGVyIGNvbnZlcnRpbmcgdGhlIGNhdGVnb3JpY2FsIHBhcmFtZXRlcnMgdG8gZWl0aGVyIGR1bW15IHZhcmlhYmxlcywgb3IgaW4gdGhlIGNhc2Ugb2YgYmluYXJ5IG91dGNvbWVzLCBib29sZWFucy4NCg0KTGV0J3MgZmluaXNoIGJ5IGV4cHJ0aW5nIG91ciBuZXcgY2xlYW5lZC11cCBkYXRhc2V0IGFzIGEgY3N2LWZpbGUuDQpgYGB7cn0NCndyaXRlLmNzdihraWRuZXlfZGF0YSwgJ0M6L0RhdGEgU2NpZW5jZSBZSC9SIFByb2dyYW1taW5nL0ZpbmFsIEFzc2lnbm1lbnQvZGF0YS9raWRuZXlfZGlzZWFzZV9jbGVhbmVkLmNzdicpDQpgYGANCg0K