Session 02: Installing R packages, I/O + a deep dive into the data.frame class

Feedback should be send to goran.milovanovic@datakolektiv.com. These notebooks accompany the Intro to Data Science: Non-Technical Background course 2020/21.


What do we want to do today?

Data Science is, well, all about data. But data lives somewhere. Where? How do we find them? In this session we will learn about the basic I/O (Input/Output) operations in R: how to load the disk stored data written in various formats in R, and how to store them back. We will learn that sometimes it makes no difference if the dataset lives on the Internet or on our local hard drives. We will learn to perform the basic I/O operations in base R before we start meeting the friendly tidyverse packages - readr, for example - that provide improved and somewhat more comfortable to work with procedures. We will learn more about the data.frame in R: what is subsetting and is it done, how to summarize a dataset represented by a data.frame in R, how to bind dataframes together. We continue to play with lists too and start learning about simple operations on strings in base R.

0. Prerequisits.

  • Create a directory named _data in the directory where you want to store your R code for this session.
  • Go to the Inside Airbnb page and download the listings.csv csv file (under the Amsterdam, North Holland, The Netherlands section). Let’s learn something right now: .csv is short for comma separated values and represents one the most frequently observed file formats used in practice. The entries in this file are separated by commas: hence the name. The csv format has many close relatives, of which tsv - tab separated values - is probably the most famous one.
  • Open the listings.csv file in Microsoft Excel or Libre Calc and save it using the same filename but as an .xlsx file in your _data directory.

1. Read data + inspect a data frame + store data

Again, everything happens in a directory somewhere. You really need to keep the organization of your directories neat!

Remember, when we work in R, there is always something called a working directory. I know you have opened and R session in RStudio: ask yourself “Where am I?”:

getwd()
[1] "C:/Users/goran/___DataKolektiv/__EDU/01_IntroDataScience_Non-Tech/_Code/IntroDataScience_NonTech_S02"
list.dirs(getwd())
[1] "C:/Users/goran/___DataKolektiv/__EDU/01_IntroDataScience_Non-Tech/_Code/IntroDataScience_NonTech_S02"      
[2] "C:/Users/goran/___DataKolektiv/__EDU/01_IntroDataScience_Non-Tech/_Code/IntroDataScience_NonTech_S02/_data"

Ok, so I do have a _data directory in my working directory. Let’s pronounce the _data directory in R:

dataDir <- paste0(getwd(), '/_data/')
list.files(dataDir)
[1] "listings.csv"           "listings.xlsx"          "listings_selection.csv"

^^ And here is the listings.csv file. It represents the Airbnb summary information and metrics for listings in Amsterdam. We will use it to practice our data frame skills in R.

We want to read listings.csv to R and make it a data.frame. This is how we do it:

filename <- paste0(dataDir, 'listings.csv')
listings <- read.csv(file = filename, 
                    header = T, 
                    check.names = F,
                    stringsAsFactors = F)

What has just happened:

  • read.csv is an R function to read csv files from disk (or Internet, as we will see later) into the RAM memory of your machine which is put to R’s availability;
  • the file argument is the complete path to the file in your local filesystem;
  • the header argument tells R that the first row of the file contains column names;
  • the check.names argument, set to FALSE in this example, tells R not to check whether the column names are syntacticaly valid R column names for a data frame;
  • the stringsAsFactors argument, set to FALSE, is a bit annoying: the read.csv() default value for this argument is TRUE and using it that way would turn any character valued columns into a data type known as factor in R, and more often than not that is not what you want to do; never forget to set stringsASFactors = F in read.csv() if you are not sure that you want all chacter valued columns automatically converted into factors.

You can now inspect listings in RStudio from the Environment panel. We will take a look at the structure of this data frame now: head(someDataFrame, how many rows) shows us the top how many rows from the someDataFrame data frame; tail() looks at the provided number of rows found at the bottom of the data frame:

head(listings, 10)
tail(listings, 10)

We use str() to obtain a description of the data frame - its columns and the respective data types:

str(listings)
'data.frame':   18522 obs. of  16 variables:
 $ id                            : int  2818 20168 25428 27886 28871 29051 31080 41125 43109 43980 ...
 $ name                          : chr  "Quiet Garden View Room & Super Fast WiFi" "Studio with private bathroom in the centre 1" "Lovely apt in City Centre (w.lift) near Jordaan" "Romantic, stylish B&B houseboat in canal district" ...
 $ host_id                       : int  3159 59484 56142 97647 124245 124245 133488 178515 188098 65041 ...
 $ host_name                     : chr  "Daniel" "Alexander" "Joan" "Flip" ...
 $ neighbourhood_group           : logi  NA NA NA NA NA NA ...
 $ neighbourhood                 : chr  "Oostelijk Havengebied - Indische Buurt" "Centrum-Oost" "Centrum-West" "Centrum-West" ...
 $ latitude                      : num  52.4 52.4 52.4 52.4 52.4 ...
 $ longitude                     : num  4.94 4.89 4.88 4.89 4.89 ...
 $ room_type                     : chr  "Private room" "Private room" "Entire home/apt" "Private room" ...
 $ price                         : int  59 236 125 135 75 55 219 160 211 67 ...
 $ minimum_nights                : int  3 1 14 2 2 2 3 4 3 30 ...
 $ number_of_reviews             : int  278 339 5 219 336 481 32 89 60 61 ...
 $ last_review                   : chr  "2020-02-14" "2020-04-09" "2020-02-09" "2020-07-25" ...
 $ reviews_per_month             : num  1.95 2.58 0.14 2.01 2.68 4.05 0.28 0.73 4.31 0.5 ...
 $ calculated_host_listings_count: int  1 2 1 1 2 2 1 1 1 2 ...
 $ availability_365              : int  123 3 33 219 346 360 0 0 0 184 ...

Note. str() works on lists:

someList <- list(a = 1, b = 2, c = 3)
str(someList)
List of 3
 $ a: num 1
 $ b: num 2
 $ c: num 3
print(someList)
$a
[1] 1

$b
[1] 2

$c
[1] 3
names(someList)
[1] "a" "b" "c"

head() and tail() also do lists:

someList <- list(1, 'a', 'Belgrade', 3, 3.14, 909, 'R', TRUE, F, '00')
head(someList, 3)
[[1]]
[1] 1

[[2]]
[1] "a"

[[3]]
[1] "Belgrade"
tail(someList, 3)
[[1]]
[1] TRUE

[[2]]
[1] FALSE

[[3]]
[1] "00"

If we are interested in the column names of the data frame only:

colnames(listings)
 [1] "id"                             "name"                           "host_id"                       
 [4] "host_name"                      "neighbourhood_group"            "neighbourhood"                 
 [7] "latitude"                       "longitude"                      "room_type"                     
[10] "price"                          "minimum_nights"                 "number_of_reviews"             
[13] "last_review"                    "reviews_per_month"              "calculated_host_listings_count"
[16] "availability_365"              

Later we will see how to use colnames() to set our own column names on the existing data frame.

In the next step I want to change the listings data frame just a bit, by selecting only a few of its columns, and then store it to disk in the csv format but using a different file name than listings.csv:

listings_selection <- listings[, 1:3]
head(listings_selection)

Similarly as we have used the read.csv() function to read a file into a data frame, we use write.csv() to store a data frame into a file in our local filesystem:

write.csv(x = listings_selection, 
          file = paste0(dataDir, 'listings_selection.csv'))

2. More elaborated I/O + install R packages + subsetting data frames + elementary string processing

If you want to see what objects do you have instantiated in the current R session, use ls():

ls()
 [1] "d"                  "dataDir"            "filename"           "freqHosts"          "listings"           "listings_5"        
 [7] "listings_selection" "num_host_listings"  "num_hosts"          "roomType"           "roomTypes"          "someDataFrame"     
[13] "someList"           "someVector"         "string"             "strings"            "testDataFrame"      "testHosts"         
[19] "urlData"            "vec"               

Some of them we do not need anymore and we want to remove them. Note. In real Data Science practice, most of the time we really need to look carefully after memory usage, because we typically work with large datasets. The datasets that we have in this session are rather small:

dim(listings)
[1] 18522    16

listings has 18522 rows and 16 columnes, while listings_selection has…

dim(listings_selection)
[1] 18522     3

We do not need the listings_selection data frame anymore, so let’s remove it with rm():

rm(listings_selection)
ls()
 [1] "d"                 "dataDir"           "filename"          "freqHosts"         "listings"          "listings_5"       
 [7] "num_host_listings" "num_hosts"         "roomType"          "roomTypes"         "someDataFrame"     "someList"         
[13] "someVector"        "string"            "strings"           "testDataFrame"     "testHosts"         "urlData"          
[19] "vec"              

someList is really small and we do not care to remove it.

2.1 Install a package to read data from Excel

What if the listings data frame was stored as a Microsoft Excel file, with an .xlsx extension in place of .csv? Well, one thing to do would be to first convert it to csv outside R, from Excel itself for example. Why would we do that? Because we tend to be consistent in the way we code and what procedures and standards do we use in our work: for example, we can introduce a convention to keep all data as .csv files in the scope of some project. But sometimes we don’t and we simply need to grab a file with a certain extension quickly. Now, base R does not have a function to read .xlsx files. But there are R packages that provide such functions. Whenever we want to use an R package, we need to install it first. The base R function to install packages is install.packages():

install.packages('readxl')

When we want to use functions from an R package, we need to call the package by library():

library(readxl)

Now, the readxl package has a function to read Excel files:

listings <- read_excel(paste0(dataDir, "listings.xlsx"), 
                       col_names = TRUE)
head(listings)

Note how I have reused the variable name: listings. It was an existing data frame which is now overwritten by the same data from a different file. Do not forget to use ? from the R console to obtain documentation on any new functions that you need to learn: ?read_excel, for example.

Note. Do class(listings):

class(listings)
[1] "tbl_df"     "tbl"        "data.frame"

What is this: tbl_df, tbl? In short: the classes were added to the data.frame class in the read_excel() call and we will start meeting them frequently once we begin to use the tidyverse packages like readr. Nothing to worry about at this point. Let’s strip them of the listings object manually:

listings <- as.data.frame(listings)
class(listings)
[1] "data.frame"

2.2 Subsetting a data frame in base R

Now we need to learn how to subset a data frame, to slice out exactly the data that we are interested in. Data frames can be sliced by conditions set on their rows, columns, and by any combinations of conditions set on rows and columns. For example, if we are interested in only the top five rows of listings, we can do:

listings_5 <- listings[1:5, ]
listings_5

Remember head() - we can use that too:

listings_5 <- head(listings, 5)
listings_5

Again, the columns of listings:

colnames(listings)
 [1] "id"                             "name"                           "host_id"                       
 [4] "host_name"                      "neighbourhood_group"            "neighbourhood"                 
 [7] "latitude"                       "longitude"                      "room_type"                     
[10] "price"                          "minimum_nights"                 "number_of_reviews"             
[13] "last_review"                    "reviews_per_month"              "calculated_host_listings_count"
[16] "availability_365"              

And if we want to subset only the rows from 5 to 10 and only the name and room_type columns:

listings[5:10, c('host_name', 'room_type')]

Or:

listings[5:10, c(4, 9)]

Or:

listings[c(5,6,7,8,9,10), c(4, 9)]

Remember that c() puts things together in R. We have used two vectors, c(5,6,7,8,9,10), which can also be written as a sequence 5:10, and c(4,9) in which we have used column positions but we could have used column names as well like in the c('host_name', 'room_type') example to subset the listings data frame.

We can subset a data frame by imposing conditions on rows and/or columns too:

listings[listings$id > 200, c(2, 3)]

Shall we set a condition on column names perhaps?

2.3 Subsetting by columns + grepl() to perform regex match

listings[1:10, grepl("^number", colnames(listings))]
 [1] 278 339   5 219 336 481  32  89  60  61

We already now that colnames(listings) will return a character vector encompassing all column names from listings. The grepl() function operates on characters. It’s task is to check if the regular expression (regex) described by its first argument (^number in our example) matches any character sequence found in its second argument (colnames(listings) in our example). The regular expression ^number says search for anything that begins with number in the given string, so ^ is the character (precisely: a metacharacter) in the regex syntax that stands for the beginning of the string. Similarly, $ is a metacharacter that stands for the empty character at the end of the string. Let’se see what grepl() does:

string <- 'the quick brown fox jumps over the lazy dog'
grepl('^t', string)
[1] TRUE

^^ Asks if string begins with 'T'.

string <- 'the quick brown fox jumps over the lazy dog'
grepl('g$', string)
[1] TRUE

^^ Asks if string ends with 'g'.

string <- 'the quick brown fox jumps over the lazy dog'
grepl('x$', string)
[1] FALSE

^^ Asks if string ends with 'x'.

strings <- c('the quick brown fox jumps over the lazy dog', 
             'Inland Empire', 
             'Wild at Heart')
grepl('e$', strings)
[1] FALSE  TRUE FALSE

We will learn more about regex later in this course. But the previous example illustrates something more than the usage of grepl() to check for character sequences in R. Pay attention, please: we have defined a new character vector, strings, with three elements: 'the quick brown fox jumps over the lazy dog', 'Inland Empire', and 'Wild at Heart'. We have called grepl() like this: grepl('e$', strings) to ask if any of the strings in strings matches the e$ regex (and e$ asks: does it end with 'e'?). R responded by a vector of logicals: FALSE TRUE FALSE, and the length of the output vector is 3 - exactly as the length of the input string strings. In other words, grepl() is a vectorized function: it can be applied to a vector of elements, and will compute what it does on each element, pack its results back in another vector and serve them in that form! Many R functions are vectorized, and this is one the most powerful features of this beautiful programming language. We will also learn much more about vector programming and code vectorization with R in our future sessions.

A glimpse of vectorization only, the essential feature of R - which is a member of the class of vector languages or vector programming languages:

someVector <- c(1, 2, 3, 4, 5)
someVector + 10
[1] 11 12 13 14 15
someVector^2
[1]  1  4  9 16 25
someVector %% 2 == 0
[1] FALSE  TRUE FALSE  TRUE FALSE

2.4 read.csv() from the Internet

Oh, one more thing. Remember that listings live on the Internet: here. If you browse to that Inside Airbnb page and copy (right click!) the listings.csv link location - http://data.insideairbnb.com/the-netherlands/north-holland/amsterdam/2020-12-12/visualisations/listings.csv in the time of writing of this Notebook - you can obtain it from read.csv() in R like this:

urlData <- 'http://data.insideairbnb.com/the-netherlands/north-holland/amsterdam/2020-12-12/visualisations/listings.csv'
listingsOnline <- read.csv(URLencode(urlData), 
                           header = T, 
                           check.names = F, 
                           stringsAsFactors = F)
head(listingsOnline)

The URLencode() function takes care of the percent-encoding of characters in the URLs. Never forget to use it when you need an online file. There are better solutions to this than the base R URLencode() function (see: urltools package), but the base solution will do nicely as well - or at least in the beginning of your work in Data Science.

Now we have a copy of listings

rm(listingsOnline)

Only our second session and we can already read data from the local filesystem, Microsoft Excel, and the Internet!

2.5 Subsetting data frames in base R: some principles

Ok, back to data.frame. Here are some principles of data frame subsetting in R:

3. More fun with listings, some other data frames + basic visualizations w. {ggplot2}

It is time: star doing analytics with listings!

We first provide a concise overview of what is found in this dataset. Let’s see:

str(listings)
'data.frame':   18522 obs. of  16 variables:
 $ id                            : num  2818 20168 25428 27886 28871 ...
 $ name                          : chr  "Quiet Garden View Room & Super Fast WiFi" "Studio with private bathroom in the centre 1" "Lovely apt in City Centre (w.lift) near Jordaan" "Romantic, stylish B&B houseboat in canal district" ...
 $ host_id                       : num  3159 59484 56142 97647 124245 ...
 $ host_name                     : chr  "Daniel" "Alexander" "Joan" "Flip" ...
 $ neighbourhood_group           : logi  NA NA NA NA NA NA ...
 $ neighbourhood                 : chr  "Oostelijk Havengebied - Indische Buurt" "Centrum-Oost" "Centrum-West" "Centrum-West" ...
 $ latitude                      : num  52.4 52.4 52.4 52.4 52.4 ...
 $ longitude                     : num  4.94 4.89 4.88 4.89 4.89 ...
 $ room_type                     : chr  "Private room" "Private room" "Entire home/apt" "Private room" ...
 $ price                         : num  59 236 125 135 75 55 219 160 211 67 ...
 $ minimum_nights                : num  3 1 14 2 2 2 3 4 3 30 ...
 $ number_of_reviews             : num  278 339 5 219 336 481 32 89 60 61 ...
 $ last_review                   : chr  "2020-02-14" "2020-04-09" "2020-02-09" "2020-07-25" ...
 $ reviews_per_month             : num  1.95 2.58 0.14 2.01 2.68 4.05 0.28 0.73 4.31 0.5 ...
 $ calculated_host_listings_count: num  1 2 1 1 2 2 1 1 1 2 ...
 $ availability_365              : num  123 3 33 219 346 360 0 0 0 184 ...

So,

  • id is just some id of the listing,
  • name is (I guess, it’s Airbnb’s dataset) the title of the listing as it was advertised,
  • host_id is, obviously, the host id,
  • host_name is also self-explanatory,
  • neighbourhood_group has a lot of NA values, we will learn about NA soon,
  • neighbourhood seems to represent a particular neighbourhood of Amsterdam,
  • latitude and longitude are self-explanatory,
  • room_type - the room type,
  • price - we do not know the units, say EUR,
  • minimun_nights - the minimum nights for a stay in this property,
  • number_of_reviews - how many reviews did a particular listing receive,
  • last_review - the timestamp of the latest review for this listing, YYYY-MM-DD format,
  • last_review - how many reviews were received for this listing,
  • reviews_per_month - how many reviews per month, we do not know the time frame across which was this measure aggregated,
  • calculated_host_listings_count - I have no idea what this is, we will do some research on this later, and finally
  • availability_365 - how many days in a years is this available.

3.1 Play for real: a deep dive into listings!

Ok. First, I would like to learn more about the calculated_host_listings_count column, which I did not understand immediately. I have an intuition about it: it could be the number of different listings with the same host_id/host_name.

Step 1: ask R how many unique host_id values there are in the dataset:

num_hosts <- length(unique(listings$host_id))
num_hosts
[1] 16033

The unique() function: you will be using it every now and then. It’s easy: if vec <- c(1, 2, 3, 5, 5, 7) is a vector, unique(vec) is c(1, 2, 3, 5, 7), look:

vec <- c(1, 2, 3, 5, 5, 7)
unique(vec)
[1] 1 2 3 5 7

Step 2: ask R to count how many different listings there are per unique host_id.

num_host_listings <- table(listings$host_id)
head(num_host_listings, 50)

  3159   3592   7924  12085  30390  47517  49851  56142  58458  59484  61977  62341  62658  65041  72890  77950  81046  92194 
     1      1      1      1      1      1      1      1      1      2      1      1      1      2      1      1      1      1 
 92253  96492  97647  98297  98647  98844 109257 111635 113034 124245 125667 126790 127938 133488 142145 149649 158271 166264 
     1      1      1      1      1      1      1      1      1      2      2      1      1      1      1      1      1      1 
169567 178515 178521 179452 185619 185836 186729 187728 188073 188098 190897 194523 195126 195537 
     1      1      1      1      1      1      1      1      1      1      1      1      1      1 

The table() function is your tool to obtain the frequency distribution of a variable in R. It’s easy: if vec <- c(1, 2, 3, 5, 5, 7, 7, 7) is a vector, table(vec) is:

vec <- c(1, 2, 3, 5, 5, 7, 7, 7)
table(vec)
vec
1 2 3 5 7 
1 1 1 2 3 

What is the class() of the output of table()?

class(table(vec))
[1] "table"

Yes, R has a plenty of specific types, and sometimes - and maybe most of the time - it is handy to turn them all into data frames:

freqHosts <- as.data.frame(table(listings$host_id), 
                           stringsAsFactors = F)
head(freqHosts)

The Var1 represents the host_id from the listings data frame, while Freq is its frequency: how many times does the respective value of Var1 appear in the listings data.frame?

One check:

dim(freqHosts)[1] == num_hosts
[1] TRUE

Of course, it has to be! So dim(x) where x is a data.frame returns a vector of length two, of which the first element is the number of rows in x and the second the number of columns in x. In a frequency distribution, every particular value of a discrete variables occurs only once, so of course that dim(freqHosts)[1] == num_hosts must evaluate to T.

Now, if my intuition that calculated_host_listings_count column stands for the number of different listings with the same host_id/host_name, then its values must be the same as those that I have produced by table() in freqHosts. How do we test this hypothesis?

Step 3. Extract only host_id and calculated_host_listings_count from listings; if I am right, there will be duplicated values in this selection:

testHosts <- listings[, c('host_id', 'calculated_host_listings_count')]
head(testHosts)

Ok, now: are there any duplicated rows present?

d <- duplicated(testHosts)
table(d)
d
FALSE  TRUE 
16033  2489 

Of course there are, but the result most probably does not mean too much at this point. Step by step, the duplicated() function R returns a logical vector (TRUE or FALSE):

vec <- c(1, 2, 3, 2, 2, 4, 5, 5, 7)
duplicated(vec)
[1] FALSE FALSE FALSE  TRUE  TRUE FALSE FALSE  TRUE FALSE

Note how each first appearance of an element in a vector receives FALSE - because it is not duplicated - while every subsequent appearance of the same element receives TRUE - because it is duplicated. duplicated() works for data frames too, in which case it looks at all the values across all of the rows and returns as many logicals as there are rows following the same logic: first apperance is marked as FALSE and then all repetitions are marked as TRUE:

d <- duplicated(testHosts)
head(d)
[1] FALSE FALSE FALSE FALSE FALSE  TRUE

So, what happened when I did table(d) is that R has counted how many duplicates (TRUE) there were in testHosts:

table(d)
d
FALSE  TRUE 
16033  2489 

There are 2489 duplicated values. Interesting enough, I can use the logical vector that duplicated() outputs to clean up my testHosts data frame from duplicated entries in this way:

dim(testHosts)
[1] 18522     2
testHosts <- testHosts[!duplicated(testHosts), ]
dim(testHosts)
[1] 16033     2

to keep only the 16033 rows that were never repeated.

We get back to the hypothesis: calculated_host_listings_count column stands for the number of different listings with the same host_id/host_name. If this is true, than the frequency counts in freqHosts$Freq must be the same, across the host_id values, as the values found in testHosts$calculated_host_listings_count, correct? How do we proceed to find out?

What do we have are two data frames:

head(freqHosts)

and the de-duplicated testHosts:

head(testHosts)

and do not forget that we know that Var1 in freqHosts encompasses the same values of host_id from listings as testHosts$host_id. I would know like to put freqHosts and testHosts side by side and inspect if the values of freqHosts$Var1 and testHosts$host_id are really the same.

Check one thing first:

dim(freqHosts)
[1] 16033     2
dim(testHosts)
[1] 16033     2

Of course. But the order of Var1 in freqHosts and host_id in testHosts does not seem to be the same. Let’s fix that by using order(). How does it work?

someDataFrame <- data.frame(someNum = c(5, 9, 1, 3, 4, 10), 
                            someChar = c ('a', 'b', 'c', 'd', 'e', 'f'), 
                            stringsAsFactors = F)
print(someDataFrame)

Now:

someDataFrame[order(someDataFrame$someNum), ]

Take a look at the following:

vec <- c(5, 9, 1, 3, 4, 10)
order(vec)
[1] 3 4 5 1 2 6

or

vec <- c(5, 9, 1, 3, 4, 10)
vec[order(vec)]
[1]  1  3  4  5  9 10

So, the output of order() tells us the following: which element of a vector (by position) should be placed where in order to have the original vector sorted out. This is why

someDataFrame[order(someDataFrame$someNum), ]

works: order(someDataFrame$someNum) returns an ordering of rows such that the data frame is sorted by someNum.

We now sort freqHosts and testHosts so that all host_id values are aligned (remember, Var1 in freqHosts represents hosts_id in testHosts). Before we do that, take a look at the following:

str(freqHosts)
'data.frame':   16033 obs. of  2 variables:
 $ Var1: chr  "3159" "3592" "7924" "12085" ...
 $ Freq: int  1 1 1 1 1 1 1 1 1 2 ...
str(testHosts)
'data.frame':   16033 obs. of  2 variables:
 $ host_id                       : num  3159 59484 56142 97647 124245 ...
 $ calculated_host_listings_count: num  1 2 1 1 2 1 1 1 2 1 ...

What I do not like is that Var1in freqHosts is a character, while host_id in testHosts is a numeric. Fix:

freqHosts$Var1 <- as.numeric(freqHosts$Var1)
str(freqHosts)
'data.frame':   16033 obs. of  2 variables:
 $ Var1: num  3159 3592 7924 12085 30390 ...
 $ Freq: int  1 1 1 1 1 1 1 1 1 2 ...

Ok, sort freqHosts by Var1:

freqHosts <- freqHosts[order(freqHosts$Var1), ]
head(freqHosts, 10)

and sort testHosts by host_id:

testHosts <- testHosts[order(testHosts$host_id), ]
head(testHosts, 10)

Now the two data frames should be nicely aligned. Let’s put them side by side in a new data frame:

testDataFrame <- cbind(freqHosts, testHosts)
head(testDataFrame, 40)

Are the two data frames perfectly aligned? To find out we ask how many matches there are between testDataFrame$Var1 and testDataFrame$host_id:

sum(testDataFrame$Var1 == testDataFrame$host_id)
[1] 16033

A new R function: sum(). This is how it works:

sum(c(5, 6))
[1] 11

But also:

sum(c(TRUE, FALSE, TRUE, TRUE))
[1] 3

sum() across logical vectors in R: TRUE counts as 1, FALSE counts as 0. So if we derive an index vector, say something indicating the number of matches of some kind, sum() can help us find out how many times a match was successful.

Also, the data frames should match in all positions, so dim(testDataFrame)[1] - the number of rows in testDataFrame should be the same:

dim(testDataFrame)[1]
[1] 16033

Ok, they match. Are the values in Freq and calculated_host_listings_count really the same?

sum(testDataFrame$Freq == testDataFrame$calculated_host_listings_count)
[1] 16033

Yes, the values in testDataFrame$calculated_host_listings_count and testDataFrame$Freq match perfectly, so our hypothesis about the structure of the listings data set holds!

At this point you might ask yourself: is it possible that we need to invest all this work just to figure out the meaning of one single column in a data frame? The answer is: yes, and no. To consider the ‘no’ answer first: I am doing this intentionally, to provide an exercise, a training opportunity to you. In practice, there are many finer, more elaborated, and more comfortable ways to do exactly the same in R, and you will learn a lot about them in the future sessions. As of the ‘yes’ answer: we are dealing with a very small dataset here, a one encompassing barely 16K rows and several columns. In the wild, if you start applying Data Science in R or any other language in practice, the datasets that you will be facing will probably be orders of magnitude larger. Can you inspect by eye a dataset encompassing a few hundreds of millions of rows and say tens, or hundreds of columns? Well, no. And that is why you have to be prepared to invest serious work in understanding the structure of just any dataset at hand. It is hard, it is tedious, it takes a lot of patientce, and it certainly makes Data Science less than the most sexiest profession of the 21st century.

3.2 Play for real: simple analytics & visualizations of listings!

Q1. What is the distribution of room_type in listings? Let’s see and illustrate:

roomType <- as.data.frame(table(listings$room_type))
colnames(roomType) <- c('room_type', 'frequency')
roomType <- roomType[order(roomType$frequency), ]
head(roomType)

What if we want to place the most frequent room_type in the top?

roomType <- roomType[order(-roomType$frequency), ]
head(roomType)

How many different room_type values do we observe?

roomTypes <- as.data.frame(table(listings$room_type))
colnames(roomTypes) <- c('room_type', 'count')
roomTypes

Ok, four only. Visualize this (note: we are jumping a bit ahead here, but there is a reason to it):

library(ggplot2)
ggplot(data = roomTypes, 
       aes(x = room_type, y = count)) + 
  geom_bar(stat = 'identity', fill = "cadetblue3") +
  xlab('Room type') + 
  ylab('Count') +
  ggtitle('Room type distribution in Airbnb Listings') + 
  theme_bw() + 
  theme(panel.border = element_blank())

{ggplot2} is an industry standard R package for static data visualizations (in spite of the fact that you can produce interactive visualizations with {ggplot2}). Before the end of this session, I would like to begin analyzing the {ggplot2} code: we will be using so much of it in the future.

Observe the first part:

ggplot(data = roomTypes, 
       aes(x = room_type, y = count))

This produced noting. Not really: it is an empty plot, but it does have the horizontal (x) axis as well as the vertical (Y) axis defined. The ggplot() function asks for a data argument: the data.frame encompassing the data that we want to visualize. A call to another function, aes(), is made inside of ggplot(), defining the x and y arguments: what goes on the horizontal and vertical axes of the plot. Note: when using {ggplot2}, once the dataset is determined in the data argument of ggplot() there is no need to make a reference to it in aes() or elsewhere: we can make references to its columns directly, as we did in aes(x = room_type, y = count).

What happens after the first + in the code?

ggplot(data = roomTypes, 
       aes(x = room_type, y = count)) + 
  geom_bar(stat = 'identity', fill = "cadetblue3")

The addition of geom_bar() adds a layer to the {ggplot2} plot. Any {ggplot2} visualization is produced in this way:

  • first define the data and the mapping of variables in aes() in a ggplot() call, then
  • add layers by using an appropriate geom, like geom_bar() in this example, and
  • style the plot by adding additional layers such as ggtitle() or theme().

Note how geom_bar() also has arguments that help style the plot, e.g. fill = "cadetblue3" which picked cadetblue3 as the fill color for the bar plot. More on colors in R: colors in R.

What happens next is the introduction of the x and y labels to the plot, as well as the plot title:

ggplot(data = roomTypes, 
       aes(x = room_type, y = count)) + 
  geom_bar(stat = 'identity', fill = "cadetblue3") +
  xlab('Room type') + 
  ylab('Count') +
  ggtitle('Room type distribution in Airbnb Listings')

Finally we chose to strip some default settings by using theme_bw() and style the plot by removing the panel.border in an additional theme() call: theme(panel.border = element_blank()):

ggplot(data = roomTypes, 
       aes(x = room_type, y = count)) + 
  geom_bar(stat = 'identity', fill = "cadetblue3") +
  xlab('Room type') + 
  ylab('Count') +
  ggtitle('Room type distribution in Airbnb Listings') + 
  theme_bw() + 
  theme(panel.border = element_blank())

theme() is used to access the details of a {ggplot2} plot; for example, center the plot title:

ggplot(data = roomTypes, 
       aes(x = room_type, y = count)) + 
  geom_bar(stat = 'identity', fill = "cadetblue3") +
  xlab('Room type') + 
  ylab('Count') +
  ggtitle('Room type distribution in Airbnb Listings') + 
  theme_bw() + 
  theme(panel.border = element_blank()) + 
  theme(plot.title = element_text(hjust = 0.5))

or control the behavior of the axes:

ggplot(data = roomTypes, 
       aes(x = room_type, y = count)) + 
  geom_bar(stat = 'identity', fill = "cadetblue3") +
  xlab('Room type') + 
  ylab('Count') +
  ggtitle('Room type distribution in Airbnb Listings') + 
  theme_bw() + 
  theme(panel.border = element_blank()) + 
  theme(plot.title = element_text(hjust = 0.5)) + 
  theme(axis.text.x = element_text(size = 13))

There are so many nice tricks that can be pulled with {ggplot2}, you will see. At this point, it is important to remember the following:

  • the plot is defined by the data argument in the ggplot() call, and
  • the mapping of the variables from the dataset onto the plot is defined by an aes() call inside the ggplot() call;
  • additional layers are added with +, while various geom things define the type of the plot (bar plot, in our example; we will soon learn about line plots, scatter plots, density plots, etc); finally,
  • there are ways to style the plot additionally, most of which rely on various theme() function calls.

R Markdown

R Markdown is what I have used to produce this beautiful Notebook. We will learn more about it near the end of the course, but if you already feel ready to dive deep, here’s a book: R Markdown: The Definitive Guide, Yihui Xie, J. J. Allaire, Garrett Grolemunds.

Exercises

  • E1. Produce a new column in listings: listings$endsIn_e. The type of the column should be logical (i.e. TRUE, FALSE): TRUE if host_name ends with e, FALSE otherwise. Hint: grepl(), and remember that grepl() is vectorized.

  • E2. Use table() so to produce a neighbourhood X room_type two-way contingency table and turn it into a data.frame.

  • E3. Reproduce the following chart with {gplot2} by relying on the code presented in this session + ggplot2 documentation + Hadley’s book + steal code from Stack Overflow… whatever, just try to reproduce it. Hints. In aes(), you need to define group =, and fill =; the density plot in {ggplot2} is produced by geom_density() which has an alpha = argument to set for color transparency; natural logarithm in R is log() (and then there is log10() too).

  • E5. Compute the average number_of_reviews by neighbourhood in listings. Hint: use mean(), very similar to sum(). Put the results in the data.frame with the columns named neighbourhood and average_reviews.

  • E6. What are the top five listings in listings with the best price per night ratio?

  • E7. What is the average price per night ratio across the neighbourhoods in listings?


Goran S. Milovanović

DataKolektiv, 2020/21

contact:


License: GPLv3 This Notebook is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This Notebook is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this Notebook. If not, see http://www.gnu.org/licenses/.


LS0tDQp0aXRsZTogSW50cm8gdG8gRGF0YSBTY2llbmNlIChOb24tVGVjaG5pY2FsIEJhY2tncm91bmQsIFIpIC0gU2Vzc2lvbjAyDQphdXRob3I6DQotIG5hbWU6IEdvcmFuIFMuIE1pbG92YW5vdmnEhywgUGhEDQogIGFmZmlsaWF0aW9uOiBEYXRhS29sZWt0aXYsIENoaWVmIFNjaWVudGlzdCAmIE93bmVyOyBEYXRhIFNjaWVudGlzdCBmb3IgV2lraWRhdGEsIFdNREUNCmFic3RyYWN0OiANCm91dHB1dDoNCiAgaHRtbF9kb2N1bWVudDoNCiAgICB0b2M6IHllcw0KICAgIHRvY19kZXB0aDogNQ0KICBodG1sX25vdGVib29rOg0KICAgIGNvZGVfZm9sZGluZzogc2hvdw0KICAgIHRoZW1lOiBzcGFjZWxhYg0KICAgIHRvYzogeWVzDQogICAgdG9jX2Zsb2F0OiB5ZXMNCiAgICB0b2NfZGVwdGg6IDUNCi0tLQ0KDQohW10oLi4vX2ltZy9ES19Mb2dvXzEwMC5wbmcpDQoNCioqKg0KIyBTZXNzaW9uIDAyOiBJbnN0YWxsaW5nIFIgcGFja2FnZXMsIEkvTyArIGEgZGVlcCBkaXZlIGludG8gdGhlIGBkYXRhLmZyYW1lYCBjbGFzcyANCioqRmVlZGJhY2sqKiBzaG91bGQgYmUgc2VuZCB0byBgZ29yYW4ubWlsb3Zhbm92aWNAZGF0YWtvbGVrdGl2LmNvbWAuIA0KVGhlc2Ugbm90ZWJvb2tzIGFjY29tcGFueSB0aGUgSW50cm8gdG8gRGF0YSBTY2llbmNlOiBOb24tVGVjaG5pY2FsIEJhY2tncm91bmQgY291cnNlIDIwMjAvMjEuDQoNCioqKg0KDQojIyMgV2hhdCBkbyB3ZSB3YW50IHRvIGRvIHRvZGF5Pw0KDQpEYXRhIFNjaWVuY2UgaXMsIHdlbGwsIGFsbCBhYm91dCBkYXRhLiBCdXQgZGF0YSBsaXZlcyBzb21ld2hlcmUuIFdoZXJlPyBIb3cgZG8gd2UgZmluZCB0aGVtPyBJbiB0aGlzIHNlc3Npb24gd2Ugd2lsbCBsZWFybiBhYm91dCB0aGUgYmFzaWMgSS9PIChJbnB1dC9PdXRwdXQpIG9wZXJhdGlvbnMgaW4gUjogaG93IHRvIGxvYWQgdGhlIGRpc2sgc3RvcmVkIGRhdGEgd3JpdHRlbiBpbiB2YXJpb3VzIGZvcm1hdHMgaW4gUiwgYW5kIGhvdyB0byBzdG9yZSB0aGVtIGJhY2suIFdlIHdpbGwgbGVhcm4gdGhhdCBzb21ldGltZXMgaXQgbWFrZXMgbm8gZGlmZmVyZW5jZSBpZiB0aGUgZGF0YXNldCBsaXZlcyBvbiB0aGUgSW50ZXJuZXQgb3Igb24gb3VyIGxvY2FsIGhhcmQgZHJpdmVzLiBXZSB3aWxsIGxlYXJuIHRvIHBlcmZvcm0gdGhlIGJhc2ljIEkvTyBvcGVyYXRpb25zIGluIGJhc2UgUiBiZWZvcmUgd2Ugc3RhcnQgbWVldGluZyB0aGUgZnJpZW5kbHkgW3RpZHl2ZXJzZV0oaHR0cHM6Ly93d3cudGlkeXZlcnNlLm9yZy8pIHBhY2thZ2VzIC0gW3JlYWRyXShodHRwczovL3JlYWRyLnRpZHl2ZXJzZS5vcmcvKSwgZm9yIGV4YW1wbGUgLSB0aGF0IHByb3ZpZGUgaW1wcm92ZWQgYW5kIHNvbWV3aGF0IG1vcmUgY29tZm9ydGFibGUgdG8gd29yayB3aXRoIHByb2NlZHVyZXMuDQpXZSB3aWxsIGxlYXJuIG1vcmUgYWJvdXQgdGhlIGBkYXRhLmZyYW1lYCBpbiBSOiB3aGF0IGlzIHN1YnNldHRpbmcgYW5kIGlzIGl0IGRvbmUsIGhvdyB0byBzdW1tYXJpemUgYSBkYXRhc2V0IHJlcHJlc2VudGVkIGJ5IGEgYGRhdGEuZnJhbWVgIGluIFIsIGhvdyB0byBiaW5kIGRhdGFmcmFtZXMgdG9nZXRoZXIuIFdlIGNvbnRpbnVlIHRvIHBsYXkgd2l0aCBsaXN0cyB0b28gYW5kIHN0YXJ0IGxlYXJuaW5nIGFib3V0IHNpbXBsZSBvcGVyYXRpb25zIG9uIHN0cmluZ3MgaW4gYmFzZSBSLiANCg0KDQojIyMgMC4gUHJlcmVxdWlzaXRzLg0KDQotIENyZWF0ZSBhIGRpcmVjdG9yeSBuYW1lZCBgX2RhdGFgIGluIHRoZSBkaXJlY3Rvcnkgd2hlcmUgeW91IHdhbnQgdG8gc3RvcmUgeW91ciBSIGNvZGUgZm9yIHRoaXMgc2Vzc2lvbi4gDQotIEdvIHRvIHRoZSBbSW5zaWRlIEFpcmJuYl0oaHR0cDovL2luc2lkZWFpcmJuYi5jb20vZ2V0LXRoZS1kYXRhLmh0bWwpIHBhZ2UgYW5kIGRvd25sb2FkIHRoZSBbYGxpc3RpbmdzLmNzdmBdKGh0dHA6Ly9kYXRhLmluc2lkZWFpcmJuYi5jb20vdGhlLW5ldGhlcmxhbmRzL25vcnRoLWhvbGxhbmQvYW1zdGVyZGFtLzIwMjAtMTItMTIvdmlzdWFsaXNhdGlvbnMvbGlzdGluZ3MuY3N2KSBgY3N2YCBmaWxlICh1bmRlciB0aGUgKkFtc3RlcmRhbSwgTm9ydGggSG9sbGFuZCwgVGhlIE5ldGhlcmxhbmRzKiBzZWN0aW9uKS4gTGV0J3MgbGVhcm4gc29tZXRoaW5nIHJpZ2h0IG5vdzogYC5jc3ZgIGlzIHNob3J0IGZvciAqKmNvbW1hIHNlcGFyYXRlZCB2YWx1ZXMqKiBhbmQgcmVwcmVzZW50cyBvbmUgdGhlIG1vc3QgZnJlcXVlbnRseSBvYnNlcnZlZCBmaWxlIGZvcm1hdHMgdXNlZCBpbiBwcmFjdGljZS4gVGhlIGVudHJpZXMgaW4gdGhpcyBmaWxlIGFyZSBzZXBhcmF0ZWQgYnkgY29tbWFzOiBoZW5jZSB0aGUgbmFtZS4gVGhlIGBjc3ZgIGZvcm1hdCBoYXMgbWFueSBjbG9zZSByZWxhdGl2ZXMsIG9mIHdoaWNoIGB0c3ZgIC0gKip0YWIgc2VwYXJhdGVkIHZhbHVlcyoqIC0gaXMgcHJvYmFibHkgdGhlIG1vc3QgZmFtb3VzIG9uZS4gDQotIE9wZW4gdGhlIGBsaXN0aW5ncy5jc3ZgIGZpbGUgaW4gTWljcm9zb2Z0IEV4Y2VsIG9yIExpYnJlIENhbGMgYW5kIHNhdmUgaXQgdXNpbmcgdGhlIHNhbWUgZmlsZW5hbWUgYnV0IGFzIGFuIGAueGxzeGAgZmlsZSBpbiB5b3VyIGBfZGF0YWAgZGlyZWN0b3J5Lg0KDQoNCiMjIyAxLiBSZWFkIGRhdGEgKyBpbnNwZWN0IGEgZGF0YSBmcmFtZSArIHN0b3JlIGRhdGENCg0KQWdhaW4sIGV2ZXJ5dGhpbmcgaGFwcGVucyBpbiBhIGRpcmVjdG9yeSBzb21ld2hlcmUuIFlvdSByZWFsbHkgbmVlZCB0byBrZWVwIHRoZSBvcmdhbml6YXRpb24gb2YgeW91ciBkaXJlY3RvcmllcyBuZWF0IQ0KDQpSZW1lbWJlciwgd2hlbiB3ZSB3b3JrIGluIFIsIHRoZXJlIGlzIGFsd2F5cyBzb21ldGhpbmcgY2FsbGVkIGEgKip3b3JraW5nIGRpcmVjdG9yeSoqLiBJIGtub3cgeW91IGhhdmUgb3BlbmVkIGFuZCBSIHNlc3Npb24gaW4gUlN0dWRpbzogYXNrIHlvdXJzZWxmICJXaGVyZSBhbSBJPyI6DQoNCmBgYCB7ciBlY2hvID0gVH0NCmdldHdkKCkNCmBgYA0KDQpgYGAge3IgZWNobyA9IFR9DQpsaXN0LmRpcnMoZ2V0d2QoKSkNCmBgYA0KDQpPaywgc28gSSBkbyBoYXZlIGEgYF9kYXRhYCBkaXJlY3RvcnkgaW4gbXkgd29ya2luZyBkaXJlY3RvcnkuIExldCdzIHByb25vdW5jZSB0aGUgYF9kYXRhYCBkaXJlY3RvcnkgaW4gUjoNCg0KYGBgIHtyIGVjaG8gPSBUfQ0KZGF0YURpciA8LSBwYXN0ZTAoZ2V0d2QoKSwgJy9fZGF0YS8nKQ0KbGlzdC5maWxlcyhkYXRhRGlyKQ0KYGBgDQpeXiBBbmQgaGVyZSBpcyB0aGUgYGxpc3RpbmdzLmNzdmAgZmlsZS4gSXQgcmVwcmVzZW50cyB0aGUgQWlyYm5iIHN1bW1hcnkgaW5mb3JtYXRpb24gYW5kIG1ldHJpY3MgZm9yIGxpc3RpbmdzIGluIEFtc3RlcmRhbS4gV2Ugd2lsbCB1c2UgaXQgdG8gcHJhY3RpY2Ugb3VyIGRhdGEgZnJhbWUgc2tpbGxzIGluIFIuDQoNCldlIHdhbnQgdG8gcmVhZCBgbGlzdGluZ3MuY3N2YCB0byBSIGFuZCBtYWtlIGl0IGEgYGRhdGEuZnJhbWVgLiBUaGlzIGlzIGhvdyB3ZSBkbyBpdDoNCg0KYGBge3IgZWNobyA9IFR9DQpmaWxlbmFtZSA8LSBwYXN0ZTAoZGF0YURpciwgJ2xpc3RpbmdzLmNzdicpDQpsaXN0aW5ncyA8LSByZWFkLmNzdihmaWxlID0gZmlsZW5hbWUsIA0KICAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBULCANCiAgICAgICAgICAgICAgICAgICAgY2hlY2submFtZXMgPSBGLA0KICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikNCmBgYA0KDQpXaGF0IGhhcyBqdXN0IGhhcHBlbmVkOg0KDQotIGByZWFkLmNzdmAgaXMgYW4gUiBmdW5jdGlvbiB0byByZWFkIGBjc3ZgIGZpbGVzIGZyb20gZGlzayAob3IgSW50ZXJuZXQsIGFzIHdlIHdpbGwgc2VlIGxhdGVyKSBpbnRvIHRoZSBSQU0gbWVtb3J5IG9mIHlvdXIgbWFjaGluZSB3aGljaCBpcyBwdXQgdG8gUidzIGF2YWlsYWJpbGl0eTsNCi0gdGhlIGBmaWxlYCBhcmd1bWVudCBpcyB0aGUgY29tcGxldGUgcGF0aCB0byB0aGUgZmlsZSBpbiB5b3VyIGxvY2FsIGZpbGVzeXN0ZW07DQotIHRoZSBgaGVhZGVyYCBhcmd1bWVudCB0ZWxscyBSIHRoYXQgdGhlIGZpcnN0IHJvdyBvZiB0aGUgZmlsZSBjb250YWlucyBjb2x1bW4gbmFtZXM7DQotIHRoZSBgY2hlY2submFtZXNgIGFyZ3VtZW50LCBzZXQgdG8gYEZBTFNFYCBpbiB0aGlzIGV4YW1wbGUsIHRlbGxzIFIgbm90IHRvIGNoZWNrIHdoZXRoZXIgdGhlIGNvbHVtbiBuYW1lcyBhcmUgc3ludGFjdGljYWx5IHZhbGlkIFIgY29sdW1uIG5hbWVzIGZvciBhIGRhdGEgZnJhbWU7DQotIHRoZSBgc3RyaW5nc0FzRmFjdG9yc2AgYXJndW1lbnQsIHNldCB0byBgRkFMU0VgLCBpcyBhIGJpdCBhbm5veWluZzogdGhlIGByZWFkLmNzdigpYCBkZWZhdWx0IHZhbHVlIGZvciB0aGlzIGFyZ3VtZW50IGlzIGBUUlVFYCBhbmQgdXNpbmcgaXQgdGhhdCB3YXkgd291bGQgdHVybiBhbnkgY2hhcmFjdGVyIHZhbHVlZCBjb2x1bW5zIGludG8gYSBkYXRhIHR5cGUga25vd24gYXMgYGZhY3RvcmAgaW4gUiwgYW5kIG1vcmUgb2Z0ZW4gdGhhbiBub3QgdGhhdCBpcyBub3Qgd2hhdCB5b3Ugd2FudCB0byBkbzsgbmV2ZXIgZm9yZ2V0IHRvIHNldCBgc3RyaW5nc0FTRmFjdG9ycyA9IEZgIGluIGByZWFkLmNzdigpYCBpZiB5b3UgYXJlIG5vdCBzdXJlIHRoYXQgeW91IHdhbnQgYWxsIGNoYWN0ZXIgdmFsdWVkIGNvbHVtbnMgYXV0b21hdGljYWxseSBjb252ZXJ0ZWQgaW50byBmYWN0b3JzLg0KDQoNCllvdSBjYW4gbm93IGluc3BlY3QgYGxpc3RpbmdzYCBpbiBSU3R1ZGlvIGZyb20gdGhlICoqRW52aXJvbm1lbnQqKiBwYW5lbC4gV2Ugd2lsbCB0YWtlIGEgbG9vayBhdCB0aGUgc3RydWN0dXJlIG9mIHRoaXMgZGF0YSBmcmFtZSBub3c6IGBoZWFkKHNvbWVEYXRhRnJhbWUsIGhvdyBtYW55IHJvd3MpYCBzaG93cyB1cyB0aGUgdG9wIGBob3cgbWFueSByb3dzYCBmcm9tIHRoZSBgc29tZURhdGFGcmFtZWAgZGF0YSBmcmFtZTsgYHRhaWwoKWAgbG9va3MgYXQgdGhlIHByb3ZpZGVkIG51bWJlciBvZiByb3dzIGZvdW5kIGF0IHRoZSBib3R0b20gb2YgdGhlIGRhdGEgZnJhbWU6DQoNCg0KYGBge3IgZWNobyA9IFR9DQpoZWFkKGxpc3RpbmdzLCAxMCkNCmBgYA0KDQpgYGB7ciBlY2hvID0gVH0NCnRhaWwobGlzdGluZ3MsIDEwKQ0KYGBgDQoNCldlIHVzZSBgc3RyKClgIHRvIG9idGFpbiBhIGRlc2NyaXB0aW9uIG9mIHRoZSBkYXRhIGZyYW1lIC0gaXRzIGNvbHVtbnMgYW5kIHRoZSByZXNwZWN0aXZlIGRhdGEgdHlwZXM6DQoNCmBgYHtyIGVjaG8gPSBUfQ0Kc3RyKGxpc3RpbmdzKQ0KYGBgDQoNCioqTm90ZS4qKiBgc3RyKClgIHdvcmtzIG9uIGxpc3RzOg0KDQpgYGB7ciBlY2hvID0gVH0NCnNvbWVMaXN0IDwtIGxpc3QoYSA9IDEsIGIgPSAyLCBjID0gMykNCnN0cihzb21lTGlzdCkNCmBgYA0KDQpgYGB7ciBlY2hvID0gVH0NCnByaW50KHNvbWVMaXN0KQ0KYGBgDQoNCmBgYHtyIGVjaG8gPSBUfQ0KbmFtZXMoc29tZUxpc3QpDQpgYGANCg0KYGhlYWQoKWAgYW5kIGB0YWlsKClgIGFsc28gZG8gbGlzdHM6DQoNCmBgYHtyIGVjaG8gPSBUfQ0Kc29tZUxpc3QgPC0gbGlzdCgxLCAnYScsICdCZWxncmFkZScsIDMsIDMuMTQsIDkwOSwgJ1InLCBUUlVFLCBGLCAnMDAnKQ0KaGVhZChzb21lTGlzdCwgMykNCmBgYA0KDQpgYGB7ciBlY2hvID0gVH0NCnRhaWwoc29tZUxpc3QsIDMpDQpgYGANCg0KSWYgd2UgYXJlIGludGVyZXN0ZWQgaW4gdGhlIGNvbHVtbiBuYW1lcyBvZiB0aGUgZGF0YSBmcmFtZSBvbmx5Og0KDQpgYGB7ciBlY2hvID0gVH0NCmNvbG5hbWVzKGxpc3RpbmdzKQ0KYGBgDQoNCkxhdGVyIHdlIHdpbGwgc2VlIGhvdyB0byB1c2UgYGNvbG5hbWVzKClgIHRvIHNldCBvdXIgb3duIGNvbHVtbiBuYW1lcyBvbiB0aGUgZXhpc3RpbmcgZGF0YSBmcmFtZS4NCg0KSW4gdGhlIG5leHQgc3RlcCBJIHdhbnQgdG8gY2hhbmdlIHRoZSBgbGlzdGluZ3NgIGRhdGEgZnJhbWUganVzdCBhIGJpdCwgYnkgc2VsZWN0aW5nIG9ubHkgYSBmZXcgb2YgaXRzIGNvbHVtbnMsIGFuZCB0aGVuIHN0b3JlIGl0IHRvIGRpc2sgaW4gdGhlIGBjc3ZgIGZvcm1hdCBidXQgdXNpbmcgYSBkaWZmZXJlbnQgZmlsZSBuYW1lIHRoYW4gYGxpc3RpbmdzLmNzdmA6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KbGlzdGluZ3Nfc2VsZWN0aW9uIDwtIGxpc3RpbmdzWywgMTozXQ0KaGVhZChsaXN0aW5nc19zZWxlY3Rpb24pDQpgYGANCg0KU2ltaWxhcmx5IGFzIHdlIGhhdmUgdXNlZCB0aGUgYHJlYWQuY3N2KClgIGZ1bmN0aW9uIHRvIHJlYWQgYSBmaWxlIGludG8gYSBkYXRhIGZyYW1lLCB3ZSB1c2UgYHdyaXRlLmNzdigpYCB0byBzdG9yZSBhIGRhdGEgZnJhbWUgaW50byBhIGZpbGUgaW4gb3VyIGxvY2FsIGZpbGVzeXN0ZW06DQoNCmBgYHtyIGVjaG8gPSBUfQ0Kd3JpdGUuY3N2KHggPSBsaXN0aW5nc19zZWxlY3Rpb24sIA0KICAgICAgICAgIGZpbGUgPSBwYXN0ZTAoZGF0YURpciwgJ2xpc3RpbmdzX3NlbGVjdGlvbi5jc3YnKSkNCmBgYA0KDQojIyMgMi4gTW9yZSBlbGFib3JhdGVkIEkvTyArIGluc3RhbGwgUiBwYWNrYWdlcyArIHN1YnNldHRpbmcgZGF0YSBmcmFtZXMgKyBlbGVtZW50YXJ5IHN0cmluZyBwcm9jZXNzaW5nDQoNCklmIHlvdSB3YW50IHRvIHNlZSB3aGF0IG9iamVjdHMgZG8geW91IGhhdmUgaW5zdGFudGlhdGVkIGluIHRoZSBjdXJyZW50IFIgc2Vzc2lvbiwgdXNlIGBscygpYDoNCg0KYGBge3IgZWNobyA9IFR9DQpscygpDQpgYGANCg0KU29tZSBvZiB0aGVtIHdlIGRvIG5vdCBuZWVkIGFueW1vcmUgYW5kIHdlIHdhbnQgdG8gcmVtb3ZlIHRoZW0uICoqTm90ZS4qKiBJbiByZWFsIERhdGEgU2NpZW5jZSBwcmFjdGljZSwgbW9zdCBvZiB0aGUgdGltZSB3ZSByZWFsbHkgbmVlZCB0byBsb29rIGNhcmVmdWxseSBhZnRlciBtZW1vcnkgdXNhZ2UsIGJlY2F1c2Ugd2UgdHlwaWNhbGx5IHdvcmsgd2l0aCBsYXJnZSBkYXRhc2V0cy4gVGhlIGRhdGFzZXRzIHRoYXQgd2UgaGF2ZSBpbiB0aGlzIHNlc3Npb24gYXJlIHJhdGhlciBzbWFsbDoNCg0KYGBge3IgZWNobyA9IFR9DQpkaW0obGlzdGluZ3MpDQpgYGANCmBsaXN0aW5nc2AgaGFzIDE4NTIyIHJvd3MgYW5kIDE2IGNvbHVtbmVzLCB3aGlsZSBgbGlzdGluZ3Nfc2VsZWN0aW9uYCBoYXMuLi4NCg0KYGBge3IgZWNobyA9IFR9DQpkaW0obGlzdGluZ3Nfc2VsZWN0aW9uKQ0KYGBgDQpXZSBkbyBub3QgbmVlZCB0aGUgYGxpc3RpbmdzX3NlbGVjdGlvbmAgZGF0YSBmcmFtZSBhbnltb3JlLCBzbyBsZXQncyByZW1vdmUgaXQgd2l0aCBgcm0oKWA6DQoNCmBgYHtyIGVjaG8gPSBUfQ0Kcm0obGlzdGluZ3Nfc2VsZWN0aW9uKQ0KbHMoKQ0KYGBgDQpgc29tZUxpc3RgIGlzIHJlYWxseSBzbWFsbCBhbmQgd2UgZG8gbm90IGNhcmUgdG8gcmVtb3ZlIGl0Lg0KDQojIyMjIDIuMSBJbnN0YWxsIGEgcGFja2FnZSB0byByZWFkIGRhdGEgZnJvbSBFeGNlbA0KDQpXaGF0IGlmIHRoZSBgbGlzdGluZ3NgIGRhdGEgZnJhbWUgd2FzIHN0b3JlZCBhcyBhIE1pY3Jvc29mdCBFeGNlbCBmaWxlLCB3aXRoIGFuIGAueGxzeGAgZXh0ZW5zaW9uIGluIHBsYWNlIG9mIGAuY3N2YD8gV2VsbCwgb25lIHRoaW5nIHRvIGRvIHdvdWxkIGJlIHRvIGZpcnN0IGNvbnZlcnQgaXQgdG8gYGNzdmAgb3V0c2lkZSBSLCBmcm9tIEV4Y2VsIGl0c2VsZiBmb3IgZXhhbXBsZS4gV2h5IHdvdWxkIHdlIGRvIHRoYXQ/IEJlY2F1c2Ugd2UgdGVuZCB0byBiZSBjb25zaXN0ZW50IGluIHRoZSB3YXkgd2UgY29kZSBhbmQgd2hhdCBwcm9jZWR1cmVzIGFuZCBzdGFuZGFyZHMgZG8gd2UgdXNlIGluIG91ciB3b3JrOiBmb3IgZXhhbXBsZSwgd2UgY2FuIGludHJvZHVjZSBhIGNvbnZlbnRpb24gdG8ga2VlcCBhbGwgZGF0YSBhcyBgLmNzdmAgZmlsZXMgaW4gdGhlIHNjb3BlIG9mIHNvbWUgcHJvamVjdC4gQnV0IHNvbWV0aW1lcyB3ZSBkb24ndCBhbmQgd2Ugc2ltcGx5IG5lZWQgdG8gZ3JhYiBhIGZpbGUgd2l0aCBhIGNlcnRhaW4gZXh0ZW5zaW9uIHF1aWNrbHkuIA0KTm93LCBiYXNlIFIgZG9lcyBub3QgaGF2ZSBhIGZ1bmN0aW9uIHRvIHJlYWQgYC54bHN4YCBmaWxlcy4gQnV0IHRoZXJlIGFyZSBSICoqcGFja2FnZXMqKiB0aGF0IHByb3ZpZGUgc3VjaCBmdW5jdGlvbnMuIFdoZW5ldmVyIHdlIHdhbnQgdG8gdXNlIGFuIFIgcGFja2FnZSwgd2UgbmVlZCB0byBpbnN0YWxsIGl0IGZpcnN0LiBUaGUgYmFzZSBSIGZ1bmN0aW9uIHRvIGluc3RhbGwgcGFja2FnZXMgaXMgYGluc3RhbGwucGFja2FnZXMoKWA6DQoNCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRiwgd2FybmluZyA9IEYsIGV2YWwgPSBGfQ0KaW5zdGFsbC5wYWNrYWdlcygncmVhZHhsJykNCmBgYA0KDQpXaGVuIHdlIHdhbnQgdG8gdXNlIGZ1bmN0aW9ucyBmcm9tIGFuIFIgcGFja2FnZSwgd2UgbmVlZCB0byBjYWxsIHRoZSBwYWNrYWdlIGJ5IGBsaWJyYXJ5KClgOg0KDQpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQ0KbGlicmFyeShyZWFkeGwpDQpgYGANCg0KTm93LCB0aGUgYHJlYWR4bGAgcGFja2FnZSBoYXMgYSBmdW5jdGlvbiB0byByZWFkIEV4Y2VsIGZpbGVzOg0KDQpgYGB7ciBlY2hvID0gVH0NCmxpc3RpbmdzIDwtIHJlYWRfZXhjZWwocGFzdGUwKGRhdGFEaXIsICJsaXN0aW5ncy54bHN4IiksIA0KICAgICAgICAgICAgICAgICAgICAgICBjb2xfbmFtZXMgPSBUUlVFKQ0KaGVhZChsaXN0aW5ncykNCmBgYA0KDQpOb3RlIGhvdyBJIGhhdmUgcmV1c2VkIHRoZSB2YXJpYWJsZSBuYW1lOiBgbGlzdGluZ3NgLiBJdCB3YXMgYW4gZXhpc3RpbmcgZGF0YSBmcmFtZSB3aGljaCBpcyBub3cgb3ZlcndyaXR0ZW4gYnkgdGhlIHNhbWUgZGF0YSBmcm9tIGEgZGlmZmVyZW50IGZpbGUuIERvIG5vdCBmb3JnZXQgdG8gdXNlIGA/YCBmcm9tIHRoZSBSIGNvbnNvbGUgdG8gb2J0YWluIGRvY3VtZW50YXRpb24gb24gYW55IG5ldyBmdW5jdGlvbnMgdGhhdCB5b3UgbmVlZCB0byBsZWFybjogYD9yZWFkX2V4Y2VsYCwgZm9yIGV4YW1wbGUuIA0KDQoqKk5vdGUuKiogRG8gYGNsYXNzKGxpc3RpbmdzKWA6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KY2xhc3MobGlzdGluZ3MpDQpgYGANCldoYXQgaXMgdGhpczogYHRibF9kZmAsIGB0YmxgPyBJbiBzaG9ydDogdGhlIGNsYXNzZXMgd2VyZSBhZGRlZCB0byB0aGUgYGRhdGEuZnJhbWVgIGNsYXNzIGluIHRoZSBgcmVhZF9leGNlbCgpYCBjYWxsIGFuZCB3ZSB3aWxsIHN0YXJ0IG1lZXRpbmcgdGhlbSBmcmVxdWVudGx5IG9uY2Ugd2UgYmVnaW4gdG8gdXNlIHRoZSBgdGlkeXZlcnNlYCBwYWNrYWdlcyBsaWtlIGByZWFkcmAuIE5vdGhpbmcgdG8gd29ycnkgYWJvdXQgYXQgdGhpcyBwb2ludC4gTGV0J3Mgc3RyaXAgdGhlbSBvZiB0aGUgYGxpc3RpbmdzYCBvYmplY3QgbWFudWFsbHk6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KbGlzdGluZ3MgPC0gYXMuZGF0YS5mcmFtZShsaXN0aW5ncykNCmNsYXNzKGxpc3RpbmdzKQ0KYGBgDQoNCg0KIyMjIyAyLjIgU3Vic2V0dGluZyBhIGRhdGEgZnJhbWUgaW4gYmFzZSBSDQoNCk5vdyB3ZSBuZWVkIHRvIGxlYXJuIGhvdyB0byAqKnN1YnNldCoqIGEgZGF0YSBmcmFtZSwgdG8gc2xpY2Ugb3V0IGV4YWN0bHkgdGhlIGRhdGEgdGhhdCB3ZSBhcmUgaW50ZXJlc3RlZCBpbi4gRGF0YSBmcmFtZXMgY2FuIGJlIHNsaWNlZCBieSBjb25kaXRpb25zIHNldCBvbiB0aGVpciByb3dzLCBjb2x1bW5zLCBhbmQgYnkgYW55IGNvbWJpbmF0aW9ucyBvZiBjb25kaXRpb25zIHNldCBvbiByb3dzIGFuZCBjb2x1bW5zLiBGb3IgZXhhbXBsZSwgaWYgd2UgYXJlIGludGVyZXN0ZWQgaW4gb25seSB0aGUgdG9wIGZpdmUgcm93cyBvZiBsaXN0aW5ncywgd2UgY2FuIGRvOg0KDQpgYGB7ciBlY2hvID0gVH0NCmxpc3RpbmdzXzUgPC0gbGlzdGluZ3NbMTo1LCBdDQpsaXN0aW5nc181DQpgYGANClJlbWVtYmVyIGBoZWFkKClgIC0gd2UgY2FuIHVzZSB0aGF0IHRvbzoNCg0KYGBge3IgZWNobyA9IFR9DQpsaXN0aW5nc181IDwtIGhlYWQobGlzdGluZ3MsIDUpDQpsaXN0aW5nc181DQpgYGANCg0KQWdhaW4sIHRoZSBjb2x1bW5zIG9mIGBsaXN0aW5nc2A6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KY29sbmFtZXMobGlzdGluZ3MpDQpgYGANCg0KQW5kIGlmIHdlIHdhbnQgdG8gc3Vic2V0IG9ubHkgdGhlIHJvd3MgZnJvbSA1IHRvIDEwIGFuZCBvbmx5IHRoZSBgbmFtZWAgYW5kIGByb29tX3R5cGVgIGNvbHVtbnM6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KbGlzdGluZ3NbNToxMCwgYygnaG9zdF9uYW1lJywgJ3Jvb21fdHlwZScpXQ0KYGBgDQoNCk9yOg0KDQpgYGB7ciBlY2hvID0gVH0NCmxpc3RpbmdzWzU6MTAsIGMoNCwgOSldDQpgYGANCg0KT3I6IA0KDQpgYGB7ciBlY2hvID0gVH0NCmxpc3RpbmdzW2MoNSw2LDcsOCw5LDEwKSwgYyg0LCA5KV0NCmBgYA0KDQpSZW1lbWJlciB0aGF0IGBjKClgIHB1dHMgdGhpbmdzIHRvZ2V0aGVyIGluIFIuIFdlIGhhdmUgdXNlZCB0d28gdmVjdG9ycywgYGMoNSw2LDcsOCw5LDEwKWAsIHdoaWNoIGNhbiBhbHNvIGJlIHdyaXR0ZW4gYXMgYSBzZXF1ZW5jZSBgNToxMGAsIGFuZCBgYyg0LDkpYCBpbiB3aGljaCB3ZSBoYXZlIHVzZWQgY29sdW1uIHBvc2l0aW9ucyBidXQgd2UgY291bGQgaGF2ZSB1c2VkIGNvbHVtbiBuYW1lcyBhcyB3ZWxsIGxpa2UgaW4gdGhlIGBjKCdob3N0X25hbWUnLCAncm9vbV90eXBlJylgIGV4YW1wbGUgdG8gc3Vic2V0IHRoZSBgbGlzdGluZ3NgIGRhdGEgZnJhbWUuDQoNCldlIGNhbiBzdWJzZXQgYSBkYXRhIGZyYW1lIGJ5IGltcG9zaW5nIGNvbmRpdGlvbnMgb24gcm93cyBhbmQvb3IgY29sdW1ucyB0b286DQoNCmBgYHtyIGVjaG8gPSBUfQ0KbGlzdGluZ3NbbGlzdGluZ3MkaWQgPiAyMDAsIGMoMiwgMyldDQpgYGANCg0KU2hhbGwgd2Ugc2V0IGEgY29uZGl0aW9uIG9uIGNvbHVtbiBuYW1lcyBwZXJoYXBzPw0KDQojIyMjIDIuMyBTdWJzZXR0aW5nIGJ5IGNvbHVtbnMgKyBgZ3JlcGwoKWAgdG8gcGVyZm9ybSByZWdleCBtYXRjaA0KDQpgYGB7ciBlY2hvID0gVH0NCmxpc3RpbmdzWzE6MTAsIGdyZXBsKCJebnVtYmVyIiwgY29sbmFtZXMobGlzdGluZ3MpKV0NCmBgYA0KDQpXZSBhbHJlYWR5IG5vdyB0aGF0IGBjb2xuYW1lcyhsaXN0aW5ncylgIHdpbGwgcmV0dXJuIGEgY2hhcmFjdGVyIHZlY3RvciBlbmNvbXBhc3NpbmcgYWxsIGNvbHVtbiBuYW1lcyBmcm9tIGBsaXN0aW5nc2AuIFRoZSBgZ3JlcGwoKWAgZnVuY3Rpb24gb3BlcmF0ZXMgb24gY2hhcmFjdGVycy4gSXQncyB0YXNrIGlzIHRvIGNoZWNrIGlmIHRoZSBbcmVndWxhciBleHByZXNzaW9uIChyZWdleCldKGh0dHBzOi8vc3RhdC5ldGh6LmNoL1ItbWFudWFsL1ItZGV2ZWwvbGlicmFyeS9iYXNlL2h0bWwvcmVnZXguaHRtbCkgZGVzY3JpYmVkIGJ5IGl0cyBmaXJzdCBhcmd1bWVudCAoYF5udW1iZXJgIGluIG91ciBleGFtcGxlKSBtYXRjaGVzIGFueSBjaGFyYWN0ZXIgc2VxdWVuY2UgZm91bmQgaW4gaXRzIHNlY29uZCBhcmd1bWVudCAoYGNvbG5hbWVzKGxpc3RpbmdzKWAgaW4gb3VyIGV4YW1wbGUpLiBUaGUgcmVndWxhciBleHByZXNzaW9uIGBebnVtYmVyYCBzYXlzIHNlYXJjaCBmb3IgYW55dGhpbmcgdGhhdCBiZWdpbnMgd2l0aCBgbnVtYmVyYCBpbiB0aGUgZ2l2ZW4gc3RyaW5nLCBzbyBgXmAgaXMgdGhlIGNoYXJhY3RlciAocHJlY2lzZWx5OiBhIG1ldGFjaGFyYWN0ZXIpIGluIHRoZSByZWdleCBzeW50YXggdGhhdCBzdGFuZHMgZm9yIHRoZSBiZWdpbm5pbmcgb2YgdGhlIHN0cmluZy4gU2ltaWxhcmx5LCBgJGAgaXMgYSBtZXRhY2hhcmFjdGVyIHRoYXQgc3RhbmRzIGZvciB0aGUgZW1wdHkgY2hhcmFjdGVyIGF0IHRoZSBlbmQgb2YgdGhlIHN0cmluZy4gTGV0J3NlIHNlZSB3aGF0IGBncmVwbCgpYCBkb2VzOg0KDQpgYGB7ciBlY2hvID0gVH0NCnN0cmluZyA8LSAndGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZycNCmdyZXBsKCdedCcsIHN0cmluZykNCmBgYA0KXl4gQXNrcyBpZiBgc3RyaW5nYCBiZWdpbnMgd2l0aCBgJ1QnYC4NCg0KYGBge3IgZWNobyA9IFR9DQpzdHJpbmcgPC0gJ3RoZSBxdWljayBicm93biBmb3gganVtcHMgb3ZlciB0aGUgbGF6eSBkb2cnDQpncmVwbCgnZyQnLCBzdHJpbmcpDQpgYGANCl5eIEFza3MgaWYgYHN0cmluZ2AgZW5kcyB3aXRoIGAnZydgLg0KDQpgYGB7ciBlY2hvID0gVH0NCnN0cmluZyA8LSAndGhlIHF1aWNrIGJyb3duIGZveCBqdW1wcyBvdmVyIHRoZSBsYXp5IGRvZycNCmdyZXBsKCd4JCcsIHN0cmluZykNCmBgYA0KXl4gQXNrcyBpZiBgc3RyaW5nYCBlbmRzIHdpdGggYCd4J2AuDQoNCmBgYHtyIGVjaG8gPSBUfQ0Kc3RyaW5ncyA8LSBjKCd0aGUgcXVpY2sgYnJvd24gZm94IGp1bXBzIG92ZXIgdGhlIGxhenkgZG9nJywgDQogICAgICAgICAgICAgJ0lubGFuZCBFbXBpcmUnLCANCiAgICAgICAgICAgICAnV2lsZCBhdCBIZWFydCcpDQpncmVwbCgnZSQnLCBzdHJpbmdzKQ0KYGBgDQoNCldlIHdpbGwgbGVhcm4gbW9yZSBhYm91dCBgcmVnZXhgIGxhdGVyIGluIHRoaXMgY291cnNlLiBCdXQgdGhlIHByZXZpb3VzIGV4YW1wbGUgaWxsdXN0cmF0ZXMgc29tZXRoaW5nIG1vcmUgdGhhbiB0aGUgdXNhZ2Ugb2YgYGdyZXBsKClgIHRvIGNoZWNrIGZvciBjaGFyYWN0ZXIgc2VxdWVuY2VzIGluIFIuIFBheSBhdHRlbnRpb24sIHBsZWFzZTogd2UgaGF2ZSBkZWZpbmVkIGEgbmV3IGNoYXJhY3RlciB2ZWN0b3IsIGBzdHJpbmdzYCwgd2l0aCB0aHJlZSBlbGVtZW50czogYCd0aGUgcXVpY2sgYnJvd24gZm94IGp1bXBzIG92ZXIgdGhlIGxhenkgZG9nJ2AsIGAnSW5sYW5kIEVtcGlyZSdgLCBhbmQgYCdXaWxkIGF0IEhlYXJ0J2AuIFdlIGhhdmUgY2FsbGVkIGBncmVwbCgpYCBsaWtlIHRoaXM6IGBncmVwbCgnZSQnLCBzdHJpbmdzKWAgdG8gYXNrIGlmICoqYW55Kiogb2YgdGhlIHN0cmluZ3MgaW4gYHN0cmluZ3NgIG1hdGNoZXMgdGhlIGBlJGAgcmVnZXggKGFuZCBgZSRgIGFza3M6IGRvZXMgaXQgZW5kIHdpdGggYCdlJ2A/KS4gUiByZXNwb25kZWQgYnkgYSB2ZWN0b3Igb2YgbG9naWNhbHM6IGAgRkFMU0UgIFRSVUUgRkFMU0VgLCBhbmQgdGhlIGxlbmd0aCBvZiB0aGUgb3V0cHV0IHZlY3RvciBpcyAzIC0gZXhhY3RseSBhcyB0aGUgbGVuZ3RoIG9mIHRoZSBpbnB1dCBzdHJpbmcgYHN0cmluZ3NgLiBJbiBvdGhlciB3b3JkcywgYGdyZXBsKClgIGlzIGEgKip2ZWN0b3JpemVkIGZ1bmN0aW9uKio6IGl0IGNhbiBiZSBhcHBsaWVkIHRvIGEgdmVjdG9yIG9mIGVsZW1lbnRzLCBhbmQgd2lsbCBjb21wdXRlIHdoYXQgaXQgZG9lcyBvbiBlYWNoIGVsZW1lbnQsIHBhY2sgaXRzIHJlc3VsdHMgYmFjayBpbiBhbm90aGVyIHZlY3RvciBhbmQgc2VydmUgdGhlbSBpbiB0aGF0IGZvcm0hICoqTWFueSoqIFIgZnVuY3Rpb25zIGFyZSB2ZWN0b3JpemVkLCBhbmQgdGhpcyBpcyBvbmUgdGhlIG1vc3QgcG93ZXJmdWwgZmVhdHVyZXMgb2YgdGhpcyBiZWF1dGlmdWwgcHJvZ3JhbW1pbmcgbGFuZ3VhZ2UuIFdlIHdpbGwgYWxzbyBsZWFybiBtdWNoIG1vcmUgYWJvdXQgdmVjdG9yIHByb2dyYW1taW5nIGFuZCBjb2RlIHZlY3Rvcml6YXRpb24gd2l0aCBSIGluIG91ciBmdXR1cmUgc2Vzc2lvbnMuDQoNCkEgZ2xpbXBzZSBvZiB2ZWN0b3JpemF0aW9uIG9ubHksIHRoZSBlc3NlbnRpYWwgZmVhdHVyZSBvZiBSIC0gd2hpY2ggaXMgYSBtZW1iZXIgb2YgdGhlIGNsYXNzIG9mICp2ZWN0b3IgbGFuZ3VhZ2VzKiBvciAqdmVjdG9yIHByb2dyYW1taW5nIGxhbmd1YWdlcyo6DQoNCmBgYHtyIGVjaG8gPSBUfQ0Kc29tZVZlY3RvciA8LSBjKDEsIDIsIDMsIDQsIDUpDQpzb21lVmVjdG9yICsgMTANCmBgYA0KYGBge3IgZWNobyA9IFR9DQpzb21lVmVjdG9yXjINCmBgYA0KYGBge3IgZWNobyA9IFR9DQpzb21lVmVjdG9yICUlIDIgPT0gMA0KYGBgDQoNCiMjIyMgMi40IGByZWFkLmNzdigpYCBmcm9tIHRoZSBJbnRlcm5ldA0KDQpPaCwgb25lIG1vcmUgdGhpbmcuIFJlbWVtYmVyIHRoYXQgYGxpc3RpbmdzYCBsaXZlIG9uIHRoZSBJbnRlcm5ldDogW2hlcmVdKGh0dHA6Ly9pbnNpZGVhaXJibmIuY29tL2dldC10aGUtZGF0YS5odG1sKS4gSWYgeW91IGJyb3dzZSB0byB0aGF0IEluc2lkZSBBaXJibmIgcGFnZSBhbmQgY29weSAocmlnaHQgY2xpY2shKSB0aGUgYGxpc3RpbmdzLmNzdmAgbGluayBsb2NhdGlvbiAtIGBodHRwOi8vZGF0YS5pbnNpZGVhaXJibmIuY29tL3RoZS1uZXRoZXJsYW5kcy9ub3J0aC1ob2xsYW5kL2Ftc3RlcmRhbS8yMDIwLTEyLTEyL3Zpc3VhbGlzYXRpb25zL2xpc3RpbmdzLmNzdmAgaW4gdGhlIHRpbWUgb2Ygd3JpdGluZyBvZiB0aGlzIE5vdGVib29rIC0geW91IGNhbiBvYnRhaW4gaXQgZnJvbSBgcmVhZC5jc3YoKWAgaW4gUiBsaWtlIHRoaXM6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KdXJsRGF0YSA8LSAnaHR0cDovL2RhdGEuaW5zaWRlYWlyYm5iLmNvbS90aGUtbmV0aGVybGFuZHMvbm9ydGgtaG9sbGFuZC9hbXN0ZXJkYW0vMjAyMC0xMi0xMi92aXN1YWxpc2F0aW9ucy9saXN0aW5ncy5jc3YnDQpsaXN0aW5nc09ubGluZSA8LSByZWFkLmNzdihVUkxlbmNvZGUodXJsRGF0YSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICBjaGVjay5uYW1lcyA9IEYsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpDQpoZWFkKGxpc3RpbmdzT25saW5lKQ0KYGBgDQoNClRoZSBgVVJMZW5jb2RlKClgIGZ1bmN0aW9uIHRha2VzIGNhcmUgb2YgdGhlIHBlcmNlbnQtZW5jb2Rpbmcgb2YgY2hhcmFjdGVycyBpbiB0aGUgVVJMcy4gTmV2ZXIgZm9yZ2V0IHRvIHVzZSBpdCB3aGVuIHlvdSBuZWVkIGFuIG9ubGluZSBmaWxlLiBUaGVyZSBhcmUgYmV0dGVyIHNvbHV0aW9ucyB0byB0aGlzIHRoYW4gdGhlIGJhc2UgUiBgVVJMZW5jb2RlKClgIGZ1bmN0aW9uIChzZWU6IFt1cmx0b29sc10oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3VybHRvb2xzL2luZGV4Lmh0bWwpIHBhY2thZ2UpLCBidXQgdGhlIGJhc2Ugc29sdXRpb24gd2lsbCBkbyBuaWNlbHkgYXMgd2VsbCAtIG9yIGF0IGxlYXN0IGluIHRoZSBiZWdpbm5pbmcgb2YgeW91ciB3b3JrIGluIERhdGEgU2NpZW5jZS4NCg0KTm93IHdlIGhhdmUgYSBjb3B5IG9mIGBsaXN0aW5nc2AuLi4NCg0KYGBge3IgZWNobyA9IFR9DQpybShsaXN0aW5nc09ubGluZSkNCmBgYA0KDQpPbmx5IG91ciBzZWNvbmQgc2Vzc2lvbiBhbmQgd2UgY2FuIGFscmVhZHkgcmVhZCBkYXRhIGZyb20gdGhlIGxvY2FsIGZpbGVzeXN0ZW0sIE1pY3Jvc29mdCBFeGNlbCwgYW5kIHRoZSBJbnRlcm5ldCENCg0KIyMjIyAyLjUgU3Vic2V0dGluZyBkYXRhIGZyYW1lcyBpbiBiYXNlIFI6IHNvbWUgcHJpbmNpcGxlcw0KDQpPaywgYmFjayB0byBgZGF0YS5mcmFtZWAuIEhlcmUgYXJlICpzb21lIHByaW5jaXBsZXMqIG9mIGRhdGEgZnJhbWUgc3Vic2V0dGluZyBpbiBSOg0KDQohW10oLi4vX2ltZy9TMDJfMDFfRGF0YUZyYW1lcy5qcGVnKQ0KDQojIyMgMy4gTW9yZSBmdW4gd2l0aCBgbGlzdGluZ3NgLCBzb21lIG90aGVyIGRhdGEgZnJhbWVzICsgYmFzaWMgdmlzdWFsaXphdGlvbnMgdy4ge2dncGxvdDJ9DQoNCkl0IGlzIHRpbWU6IHN0YXIgZG9pbmcgYW5hbHl0aWNzIHdpdGggYGxpc3RpbmdzYCENCg0KV2UgZmlyc3QgcHJvdmlkZSBhIGNvbmNpc2Ugb3ZlcnZpZXcgb2Ygd2hhdCBpcyBmb3VuZCBpbiB0aGlzIGRhdGFzZXQuIExldCdzIHNlZToNCg0KYGBge3IgZWNobyA9IFR9DQpzdHIobGlzdGluZ3MpDQpgYGANCg0KU28sDQoNCi0gYGlkYCBpcyBqdXN0IHNvbWUgaWQgb2YgdGhlIGxpc3RpbmcsDQotIGBuYW1lYCBpcyAoSSBndWVzcywgaXQncyBBaXJibmIncyBkYXRhc2V0KSB0aGUgdGl0bGUgb2YgdGhlIGxpc3RpbmcgYXMgaXQgd2FzIGFkdmVydGlzZWQsIA0KLSBgaG9zdF9pZGAgaXMsIG9idmlvdXNseSwgdGhlIGhvc3QgaWQsDQotIGBob3N0X25hbWVgIGlzIGFsc28gc2VsZi1leHBsYW5hdG9yeSwNCi0gYG5laWdoYm91cmhvb2RfZ3JvdXBgIGhhcyBhIGxvdCBvZiBgTkFgIHZhbHVlcywgd2Ugd2lsbCBsZWFybiBhYm91dCBgTkFgIHNvb24sDQotIGBuZWlnaGJvdXJob29kYCBzZWVtcyB0byByZXByZXNlbnQgYSBwYXJ0aWN1bGFyIG5laWdoYm91cmhvb2Qgb2YgQW1zdGVyZGFtLA0KLSBgbGF0aXR1ZGVgIGFuZCBgbG9uZ2l0dWRlYCBhcmUgc2VsZi1leHBsYW5hdG9yeSwNCi0gYHJvb21fdHlwZWAgLSB0aGUgcm9vbSB0eXBlLA0KLSBgcHJpY2VgIC0gd2UgZG8gbm90IGtub3cgdGhlIHVuaXRzLCBzYXkgRVVSLA0KLSBgbWluaW11bl9uaWdodHNgIC0gdGhlIG1pbmltdW0gbmlnaHRzIGZvciBhIHN0YXkgaW4gdGhpcyBwcm9wZXJ0eSwNCi0gYG51bWJlcl9vZl9yZXZpZXdzYCAtIGhvdyBtYW55IHJldmlld3MgZGlkIGEgcGFydGljdWxhciBsaXN0aW5nIHJlY2VpdmUsDQotIGBsYXN0X3Jldmlld2AgLSB0aGUgdGltZXN0YW1wIG9mIHRoZSBsYXRlc3QgcmV2aWV3IGZvciB0aGlzIGxpc3RpbmcsIGBZWVlZLU1NLUREYCBmb3JtYXQsDQotIGBsYXN0X3Jldmlld2AgLSBob3cgbWFueSByZXZpZXdzIHdlcmUgcmVjZWl2ZWQgZm9yIHRoaXMgbGlzdGluZywNCi0gYHJldmlld3NfcGVyX21vbnRoYCAtIGhvdyBtYW55IHJldmlld3MgcGVyIG1vbnRoLCB3ZSBkbyBub3Qga25vdyB0aGUgdGltZSBmcmFtZSBhY3Jvc3Mgd2hpY2ggd2FzIHRoaXMgbWVhc3VyZSBhZ2dyZWdhdGVkLA0KLSBgY2FsY3VsYXRlZF9ob3N0X2xpc3RpbmdzX2NvdW50YCAtIEkgaGF2ZSBubyBpZGVhIHdoYXQgdGhpcyBpcywgd2Ugd2lsbCBkbyBzb21lIHJlc2VhcmNoIG9uIHRoaXMgbGF0ZXIsIGFuZCBmaW5hbGx5DQotIGBhdmFpbGFiaWxpdHlfMzY1YCAtIGhvdyBtYW55IGRheXMgaW4gYSB5ZWFycyBpcyB0aGlzIGF2YWlsYWJsZS4NCg0KDQojIyMjIDMuMSBQbGF5IGZvciByZWFsOiBhIGRlZXAgZGl2ZSBpbnRvIGBsaXN0aW5nc2AhDQoNCk9rLiBGaXJzdCwgSSB3b3VsZCBsaWtlIHRvIGxlYXJuIG1vcmUgYWJvdXQgdGhlIGBjYWxjdWxhdGVkX2hvc3RfbGlzdGluZ3NfY291bnRgIGNvbHVtbiwgd2hpY2ggSSBkaWQgbm90IHVuZGVyc3RhbmQgaW1tZWRpYXRlbHkuIEkgaGF2ZSBhbiAqaW50dWl0aW9uKiBhYm91dCBpdDogaXQgY291bGQgYmUgdGhlIG51bWJlciBvZiBkaWZmZXJlbnQgbGlzdGluZ3Mgd2l0aCB0aGUgc2FtZSBgaG9zdF9pZGAvYGhvc3RfbmFtZWAuIA0KDQpTdGVwIDE6IGFzayBSIGhvdyBtYW55ICoqdW5pcXVlKiogYGhvc3RfaWRgIHZhbHVlcyB0aGVyZSBhcmUgaW4gdGhlIGRhdGFzZXQ6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KbnVtX2hvc3RzIDwtIGxlbmd0aCh1bmlxdWUobGlzdGluZ3MkaG9zdF9pZCkpDQpudW1faG9zdHMNCmBgYA0KDQpUaGUgYHVuaXF1ZSgpYCBmdW5jdGlvbjogeW91IHdpbGwgYmUgdXNpbmcgaXQgZXZlcnkgbm93IGFuZCB0aGVuLiBJdCdzIGVhc3k6IGlmIGB2ZWMgPC0gYygxLCAyLCAzLCA1LCA1LCA3KWAgaXMgYSB2ZWN0b3IsIGB1bmlxdWUodmVjKWAgaXMgYGMoMSwgMiwgMywgNSwgNylgLCBsb29rOg0KDQpgYGB7ciBlY2hvID0gVH0NCnZlYyA8LSBjKDEsIDIsIDMsIDUsIDUsIDcpDQp1bmlxdWUodmVjKQ0KYGBgDQoNClN0ZXAgMjogYXNrIFIgdG8gY291bnQgaG93IG1hbnkgZGlmZmVyZW50IGxpc3RpbmdzIHRoZXJlIGFyZSAqcGVyIHVuaXF1ZSogYGhvc3RfaWRgLg0KDQpgYGB7ciBlY2hvID0gVH0NCm51bV9ob3N0X2xpc3RpbmdzIDwtIHRhYmxlKGxpc3RpbmdzJGhvc3RfaWQpDQpoZWFkKG51bV9ob3N0X2xpc3RpbmdzLCA1MCkNCmBgYA0KDQpUaGUgYHRhYmxlKClgIGZ1bmN0aW9uIGlzIHlvdXIgdG9vbCB0byBvYnRhaW4gdGhlIGZyZXF1ZW5jeSBkaXN0cmlidXRpb24gb2YgYSB2YXJpYWJsZSBpbiBSLiBJdCdzIGVhc3k6IGlmIGB2ZWMgPC0gYygxLCAyLCAzLCA1LCA1LCA3LCA3LCA3KWAgaXMgYSB2ZWN0b3IsIGB0YWJsZSh2ZWMpYCBpczoNCg0KYGBge3IgZWNobyA9ICBUfQ0KdmVjIDwtIGMoMSwgMiwgMywgNSwgNSwgNywgNywgNykNCnRhYmxlKHZlYykNCmBgYA0KDQpXaGF0IGlzIHRoZSBgY2xhc3MoKWAgb2YgdGhlIG91dHB1dCBvZiBgdGFibGUoKWA/DQoNCmBgYHtyIGVjaG8gPSBUfQ0KY2xhc3ModGFibGUodmVjKSkNCmBgYA0KDQpZZXMsIFIgaGFzIGEgcGxlbnR5IG9mIHNwZWNpZmljIHR5cGVzLCBhbmQgc29tZXRpbWVzIC0gYW5kIG1heWJlIG1vc3Qgb2YgdGhlIHRpbWUgLSBpdCBpcyBoYW5keSB0byB0dXJuIHRoZW0gYWxsIGludG8gZGF0YSBmcmFtZXM6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KZnJlcUhvc3RzIDwtIGFzLmRhdGEuZnJhbWUodGFibGUobGlzdGluZ3MkaG9zdF9pZCksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpDQpoZWFkKGZyZXFIb3N0cykNCmBgYA0KDQpUaGUgYFZhcjFgIHJlcHJlc2VudHMgdGhlIGBob3N0X2lkYCBmcm9tIHRoZSBgbGlzdGluZ3NgIGRhdGEgZnJhbWUsIHdoaWxlIGBGcmVxYCBpcyBpdHMgZnJlcXVlbmN5OiBob3cgbWFueSB0aW1lcyBkb2VzIHRoZSByZXNwZWN0aXZlIHZhbHVlIG9mIGBWYXIxYCBhcHBlYXIgaW4gdGhlIGBsaXN0aW5nc2AgZGF0YS5mcmFtZT8NCg0KT25lIGNoZWNrOg0KDQpgYGB7ciBlY2hvID0gVH0NCmRpbShmcmVxSG9zdHMpWzFdID09IG51bV9ob3N0cw0KYGBgDQoNCk9mIGNvdXJzZSwgaXQgaGFzIHRvIGJlISBTbyBgZGltKHgpYCB3aGVyZSBgeGAgaXMgYSBgZGF0YS5mcmFtZWAgcmV0dXJucyBhIHZlY3RvciBvZiBsZW5ndGggdHdvLCBvZiB3aGljaCB0aGUgZmlyc3QgZWxlbWVudCBpcyB0aGUgbnVtYmVyIG9mIHJvd3MgaW4gYHhgIGFuZCB0aGUgc2Vjb25kIHRoZSBudW1iZXIgb2YgY29sdW1ucyBpbiBgeGAuIEluIGEgZnJlcXVlbmN5IGRpc3RyaWJ1dGlvbiwgZXZlcnkgcGFydGljdWxhciB2YWx1ZSBvZiBhIGRpc2NyZXRlIHZhcmlhYmxlcyBvY2N1cnMgb25seSBvbmNlLCBzbyBvZiBjb3Vyc2UgdGhhdCBgZGltKGZyZXFIb3N0cylbMV0gPT0gbnVtX2hvc3RzYCBtdXN0IGV2YWx1YXRlIHRvIGBUYC4NCg0KTm93LCBpZiBteSBpbnR1aXRpb24gdGhhdCBgY2FsY3VsYXRlZF9ob3N0X2xpc3RpbmdzX2NvdW50YCBjb2x1bW4gc3RhbmRzIGZvciB0aGUgbnVtYmVyIG9mIGRpZmZlcmVudCBsaXN0aW5ncyB3aXRoIHRoZSBzYW1lIGBob3N0X2lkYC9gaG9zdF9uYW1lYCwgdGhlbiBpdHMgdmFsdWVzIG11c3QgYmUgdGhlIHNhbWUgYXMgdGhvc2UgdGhhdCBJIGhhdmUgcHJvZHVjZWQgYnkgYHRhYmxlKClgIGluIGBmcmVxSG9zdHNgLiBIb3cgZG8gd2UgdGVzdCB0aGlzIGh5cG90aGVzaXM/DQoNClN0ZXAgMy4gRXh0cmFjdCBvbmx5IGBob3N0X2lkYCBhbmQgYGNhbGN1bGF0ZWRfaG9zdF9saXN0aW5nc19jb3VudGAgZnJvbSBgbGlzdGluZ3NgOyBpZiBJIGFtIHJpZ2h0LCB0aGVyZSB3aWxsIGJlIGR1cGxpY2F0ZWQgdmFsdWVzIGluIHRoaXMgc2VsZWN0aW9uOg0KDQpgYGB7ciBlY2hvID0gVH0NCnRlc3RIb3N0cyA8LSBsaXN0aW5nc1ssIGMoJ2hvc3RfaWQnLCAnY2FsY3VsYXRlZF9ob3N0X2xpc3RpbmdzX2NvdW50JyldDQpoZWFkKHRlc3RIb3N0cykNCmBgYA0KDQpPaywgbm93OiBhcmUgdGhlcmUgYW55IGR1cGxpY2F0ZWQgcm93cyBwcmVzZW50Pw0KDQpgYGB7ciBlY2hvID0gVH0NCmQgPC0gZHVwbGljYXRlZCh0ZXN0SG9zdHMpDQp0YWJsZShkKQ0KYGBgDQoNCk9mIGNvdXJzZSB0aGVyZSBhcmUsIGJ1dCB0aGUgcmVzdWx0IG1vc3QgcHJvYmFibHkgZG9lcyBub3QgbWVhbiB0b28gbXVjaCBhdCB0aGlzIHBvaW50LiBTdGVwIGJ5IHN0ZXAsIHRoZSBgZHVwbGljYXRlZCgpYCBmdW5jdGlvbiBSIHJldHVybnMgYSBsb2dpY2FsIHZlY3RvciAoYFRSVUVgIG9yIGBGQUxTRWApOg0KDQpgYGB7ciBlY2hvID0gVH0NCnZlYyA8LSBjKDEsIDIsIDMsIDIsIDIsIDQsIDUsIDUsIDcpDQpkdXBsaWNhdGVkKHZlYykNCmBgYA0KDQpOb3RlIGhvdyBlYWNoICpmaXJzdCBhcHBlYXJhbmNlKiBvZiBhbiBlbGVtZW50IGluIGEgdmVjdG9yIHJlY2VpdmVzIGBGQUxTRWAgLSBiZWNhdXNlIGl0IGlzIG5vdCBkdXBsaWNhdGVkIC0gd2hpbGUgZXZlcnkgc3Vic2VxdWVudCBhcHBlYXJhbmNlIG9mIHRoZSBzYW1lIGVsZW1lbnQgcmVjZWl2ZXMgYFRSVUVgIC0gYmVjYXVzZSBpdCBpcyBkdXBsaWNhdGVkLiBgZHVwbGljYXRlZCgpYCB3b3JrcyBmb3IgZGF0YSBmcmFtZXMgdG9vLCBpbiB3aGljaCBjYXNlIGl0IGxvb2tzIGF0IGFsbCB0aGUgdmFsdWVzIGFjcm9zcyBhbGwgb2YgdGhlIHJvd3MgYW5kIHJldHVybnMgYXMgbWFueSBsb2dpY2FscyBhcyB0aGVyZSBhcmUgcm93cyBmb2xsb3dpbmcgdGhlIHNhbWUgbG9naWM6IGZpcnN0IGFwcGVyYW5jZSBpcyBtYXJrZWQgYXMgYEZBTFNFYCBhbmQgdGhlbiBhbGwgcmVwZXRpdGlvbnMgYXJlIG1hcmtlZCBhcyBgVFJVRWA6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KZCA8LSBkdXBsaWNhdGVkKHRlc3RIb3N0cykNCmhlYWQoZCkNCmBgYA0KDQpTbywgd2hhdCBoYXBwZW5lZCB3aGVuIEkgZGlkIGB0YWJsZShkKWAgaXMgdGhhdCBSIGhhcyBjb3VudGVkIGhvdyBtYW55IGR1cGxpY2F0ZXMgKGBUUlVFYCkgdGhlcmUgd2VyZSBpbiBgdGVzdEhvc3RzYDoNCg0KYGBge3IgZWNobyA9IFR9DQp0YWJsZShkKQ0KYGBgDQpUaGVyZSBhcmUgYDI0ODlgIGR1cGxpY2F0ZWQgdmFsdWVzLiBJbnRlcmVzdGluZyBlbm91Z2gsIEkgY2FuIHVzZSB0aGUgbG9naWNhbCB2ZWN0b3IgdGhhdCBgZHVwbGljYXRlZCgpYCBvdXRwdXRzIHRvIGNsZWFuIHVwIG15IGB0ZXN0SG9zdHNgIGRhdGEgZnJhbWUgZnJvbSBkdXBsaWNhdGVkIGVudHJpZXMgaW4gdGhpcyB3YXk6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KZGltKHRlc3RIb3N0cykNCnRlc3RIb3N0cyA8LSB0ZXN0SG9zdHNbIWR1cGxpY2F0ZWQodGVzdEhvc3RzKSwgXQ0KZGltKHRlc3RIb3N0cykNCmBgYA0KdG8ga2VlcCBvbmx5IHRoZSBgMTYwMzNgIHJvd3MgdGhhdCB3ZXJlIG5ldmVyIHJlcGVhdGVkLg0KDQpXZSBnZXQgYmFjayB0byB0aGUgaHlwb3RoZXNpczogYGNhbGN1bGF0ZWRfaG9zdF9saXN0aW5nc19jb3VudGAgY29sdW1uIHN0YW5kcyBmb3IgdGhlIG51bWJlciBvZiBkaWZmZXJlbnQgbGlzdGluZ3Mgd2l0aCB0aGUgc2FtZSBgaG9zdF9pZGAvYGhvc3RfbmFtZWAuIElmIHRoaXMgaXMgdHJ1ZSwgdGhhbiB0aGUgZnJlcXVlbmN5IGNvdW50cyBpbiBgZnJlcUhvc3RzJEZyZXFgIG11c3QgYmUgdGhlIHNhbWUsIGFjcm9zcyB0aGUgYGhvc3RfaWRgIHZhbHVlcywgYXMgdGhlIHZhbHVlcyBmb3VuZCBpbiBgdGVzdEhvc3RzJGNhbGN1bGF0ZWRfaG9zdF9saXN0aW5nc19jb3VudGAsIGNvcnJlY3Q/IEhvdyBkbyB3ZSBwcm9jZWVkIHRvIGZpbmQgb3V0Pw0KDQpXaGF0IGRvIHdlIGhhdmUgYXJlIHR3byBkYXRhIGZyYW1lczoNCg0KYGBge3IgZWNobyA9IFR9DQpoZWFkKGZyZXFIb3N0cykNCmBgYA0KDQphbmQgdGhlIGRlLWR1cGxpY2F0ZWQgYHRlc3RIb3N0c2A6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KaGVhZCh0ZXN0SG9zdHMpDQpgYGANCg0KYW5kIGRvIG5vdCBmb3JnZXQgdGhhdCB3ZSBrbm93IHRoYXQgYFZhcjFgIGluIGBmcmVxSG9zdHNgIGVuY29tcGFzc2VzIHRoZSBzYW1lIHZhbHVlcyBvZiBgaG9zdF9pZGAgZnJvbSBgbGlzdGluZ3NgIGFzIGB0ZXN0SG9zdHMkaG9zdF9pZGAuIEkgd291bGQga25vdyBsaWtlIHRvIHB1dCBgZnJlcUhvc3RzYCBhbmQgYHRlc3RIb3N0c2Agc2lkZSBieSBzaWRlIGFuZCBpbnNwZWN0IGlmIHRoZSB2YWx1ZXMgb2YgYGZyZXFIb3N0cyRWYXIxYCBhbmQgYHRlc3RIb3N0cyRob3N0X2lkYCBhcmUgcmVhbGx5IHRoZSBzYW1lLiANCg0KQ2hlY2sgb25lIHRoaW5nIGZpcnN0Og0KDQpgYGB7ciBlY2hvID0gVH0NCmRpbShmcmVxSG9zdHMpDQpkaW0odGVzdEhvc3RzKQ0KYGBgDQpPZiBjb3Vyc2UuIEJ1dCB0aGUgb3JkZXIgb2YgYFZhcjFgIGluIGBmcmVxSG9zdHNgIGFuZCBgaG9zdF9pZGAgaW4gYHRlc3RIb3N0c2AgZG9lcyBub3Qgc2VlbSB0byBiZSB0aGUgc2FtZS4gTGV0J3MgZml4IHRoYXQgYnkgdXNpbmcgYG9yZGVyKClgLiBIb3cgZG9lcyBpdCB3b3JrPw0KDQpgYGB7ciBlY2hvID0gVH0NCnNvbWVEYXRhRnJhbWUgPC0gZGF0YS5mcmFtZShzb21lTnVtID0gYyg1LCA5LCAxLCAzLCA0LCAxMCksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNvbWVDaGFyID0gYyAoJ2EnLCAnYicsICdjJywgJ2QnLCAnZScsICdmJyksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQ0KcHJpbnQoc29tZURhdGFGcmFtZSkNCmBgYA0KDQpOb3c6DQoNCmBgYHtyIGVjaG8gPSBUfQ0Kc29tZURhdGFGcmFtZVtvcmRlcihzb21lRGF0YUZyYW1lJHNvbWVOdW0pLCBdDQpgYGANCg0KVGFrZSBhIGxvb2sgYXQgdGhlIGZvbGxvd2luZzoNCg0KYGBge3IgZWNobyA9IFR9DQp2ZWMgPC0gYyg1LCA5LCAxLCAzLCA0LCAxMCkNCm9yZGVyKHZlYykNCmBgYA0Kb3INCg0KYGBge3IgZWNobyA9IFR9DQp2ZWMgPC0gYyg1LCA5LCAxLCAzLCA0LCAxMCkNCnZlY1tvcmRlcih2ZWMpXQ0KYGBgDQoNClNvLCB0aGUgb3V0cHV0IG9mIGBvcmRlcigpYCB0ZWxscyB1cyB0aGUgZm9sbG93aW5nOiB3aGljaCBlbGVtZW50IG9mIGEgdmVjdG9yIChieSBwb3NpdGlvbikgc2hvdWxkIGJlIHBsYWNlZCB3aGVyZSBpbiBvcmRlciB0byBoYXZlIHRoZSBvcmlnaW5hbCB2ZWN0b3Igc29ydGVkIG91dC4gVGhpcyBpcyB3aHkNCg0KYGBge3IgZWNobyA9IFR9DQpzb21lRGF0YUZyYW1lW29yZGVyKHNvbWVEYXRhRnJhbWUkc29tZU51bSksIF0NCmBgYA0KDQp3b3JrczogYG9yZGVyKHNvbWVEYXRhRnJhbWUkc29tZU51bSlgIHJldHVybnMgYW4gb3JkZXJpbmcgb2Ygcm93cyBzdWNoIHRoYXQgdGhlIGRhdGEgZnJhbWUgaXMgc29ydGVkIGJ5IGBzb21lTnVtYC4NCg0KV2Ugbm93IHNvcnQgYGZyZXFIb3N0c2AgYW5kIGB0ZXN0SG9zdHNgIHNvIHRoYXQgYWxsIGBob3N0X2lkYCB2YWx1ZXMgYXJlIGFsaWduZWQgKHJlbWVtYmVyLCBgVmFyMWAgaW4gYGZyZXFIb3N0c2AgcmVwcmVzZW50cyBgaG9zdHNfaWRgIGluIGB0ZXN0SG9zdHNgKS4gQmVmb3JlIHdlIGRvIHRoYXQsIHRha2UgYSBsb29rIGF0IHRoZSBmb2xsb3dpbmc6DQoNCmBgYHtyIGVjaG8gPSBUfQ0Kc3RyKGZyZXFIb3N0cykNCnN0cih0ZXN0SG9zdHMpDQpgYGANCldoYXQgSSBkbyBub3QgbGlrZSBpcyB0aGF0IGBWYXIxYGluIGBmcmVxSG9zdHNgIGlzIGEgYGNoYXJhY3RlcmAsIHdoaWxlIGBob3N0X2lkYCBpbiBgdGVzdEhvc3RzYCBpcyBhIG51bWVyaWMuIEZpeDoNCg0KYGBge3IgZWNobyA9IFR9DQpmcmVxSG9zdHMkVmFyMSA8LSBhcy5udW1lcmljKGZyZXFIb3N0cyRWYXIxKQ0Kc3RyKGZyZXFIb3N0cykNCmBgYA0KT2ssIHNvcnQgZnJlcUhvc3RzIGJ5IGBWYXIxYDoNCg0KYGBge3IgZWNobyA9IFR9DQpmcmVxSG9zdHMgPC0gZnJlcUhvc3RzW29yZGVyKGZyZXFIb3N0cyRWYXIxKSwgXQ0KaGVhZChmcmVxSG9zdHMsIDEwKQ0KYGBgDQoNCmFuZCBzb3J0IGB0ZXN0SG9zdHNgIGJ5IGBob3N0X2lkYDoNCg0KYGBge3IgZWNobyA9IFR9DQp0ZXN0SG9zdHMgPC0gdGVzdEhvc3RzW29yZGVyKHRlc3RIb3N0cyRob3N0X2lkKSwgXQ0KaGVhZCh0ZXN0SG9zdHMsIDEwKQ0KYGBgDQoNCk5vdyB0aGUgdHdvIGRhdGEgZnJhbWVzIHNob3VsZCBiZSBuaWNlbHkgYWxpZ25lZC4gTGV0J3MgcHV0IHRoZW0gc2lkZSBieSBzaWRlIGluIGEgbmV3IGRhdGEgZnJhbWU6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KdGVzdERhdGFGcmFtZSA8LSBjYmluZChmcmVxSG9zdHMsIHRlc3RIb3N0cykNCmhlYWQodGVzdERhdGFGcmFtZSwgNDApDQpgYGANCg0KQXJlIHRoZSB0d28gZGF0YSBmcmFtZXMgcGVyZmVjdGx5IGFsaWduZWQ/IFRvIGZpbmQgb3V0IHdlIGFzayBob3cgbWFueSBtYXRjaGVzIHRoZXJlIGFyZSBiZXR3ZWVuIGB0ZXN0RGF0YUZyYW1lJFZhcjFgIGFuZCBgdGVzdERhdGFGcmFtZSRob3N0X2lkYDoNCg0KYGBge3IgZWNobyA9IFR9DQpzdW0odGVzdERhdGFGcmFtZSRWYXIxID09IHRlc3REYXRhRnJhbWUkaG9zdF9pZCkNCmBgYA0KDQpBIG5ldyBSIGZ1bmN0aW9uOiBgc3VtKClgLiBUaGlzIGlzIGhvdyBpdCB3b3JrczoNCg0KYGBge3IgZWNobyA9IFR9DQpzdW0oYyg1LCA2KSkNCmBgYA0KDQpCdXQgYWxzbzoNCg0KYGBge3IgZWNobyA9IFR9DQpzdW0oYyhUUlVFLCBGQUxTRSwgVFJVRSwgVFJVRSkpDQpgYGANCg0KYHN1bSgpYCBhY3Jvc3MgbG9naWNhbCB2ZWN0b3JzIGluIFI6IGBUUlVFYCBjb3VudHMgYXMgYDFgLCBgRkFMU0VgIGNvdW50cyBhcyBgMGAuIFNvIGlmIHdlIGRlcml2ZSBhbiBpbmRleCB2ZWN0b3IsIHNheSBzb21ldGhpbmcgaW5kaWNhdGluZyB0aGUgbnVtYmVyIG9mIG1hdGNoZXMgb2Ygc29tZSBraW5kLCBgc3VtKClgIGNhbiBoZWxwIHVzIGZpbmQgb3V0IGhvdyBtYW55IHRpbWVzIGEgbWF0Y2ggd2FzIHN1Y2Nlc3NmdWwuDQoNCkFsc28sIHRoZSBkYXRhIGZyYW1lcyBzaG91bGQgbWF0Y2ggaW4gYWxsIHBvc2l0aW9ucywgc28gYGRpbSh0ZXN0RGF0YUZyYW1lKVsxXWAgLSB0aGUgbnVtYmVyIG9mIHJvd3MgaW4gYHRlc3REYXRhRnJhbWVgIHNob3VsZCBiZSB0aGUgc2FtZToNCg0KYGBge3IgZWNobyA9IFR9DQpkaW0odGVzdERhdGFGcmFtZSlbMV0NCmBgYA0KDQpPaywgdGhleSBtYXRjaC4gQXJlIHRoZSB2YWx1ZXMgaW4gYEZyZXFgIGFuZCBgY2FsY3VsYXRlZF9ob3N0X2xpc3RpbmdzX2NvdW50YCByZWFsbHkgdGhlIHNhbWU/DQoNCmBgYHtyIGVjaG8gPSBUfQ0Kc3VtKHRlc3REYXRhRnJhbWUkRnJlcSA9PSB0ZXN0RGF0YUZyYW1lJGNhbGN1bGF0ZWRfaG9zdF9saXN0aW5nc19jb3VudCkNCmBgYA0KWWVzLCB0aGUgdmFsdWVzIGluIGB0ZXN0RGF0YUZyYW1lJGNhbGN1bGF0ZWRfaG9zdF9saXN0aW5nc19jb3VudGAgYW5kIGB0ZXN0RGF0YUZyYW1lJEZyZXFgIG1hdGNoICpwZXJmZWN0bHkqLCBzbyBvdXIgaHlwb3RoZXNpcyBhYm91dCB0aGUgc3RydWN0dXJlIG9mIHRoZSBgbGlzdGluZ3NgIGRhdGEgc2V0IGhvbGRzIQ0KDQo+IEF0IHRoaXMgcG9pbnQgeW91IG1pZ2h0IGFzayB5b3Vyc2VsZjogaXMgaXQgcG9zc2libGUgdGhhdCB3ZSBuZWVkIHRvIGludmVzdCBhbGwgdGhpcyB3b3JrIGp1c3QgdG8gZmlndXJlIG91dCB0aGUgbWVhbmluZyBvZiBvbmUgc2luZ2xlIGNvbHVtbiBpbiBhIGRhdGEgZnJhbWU/IFRoZSBhbnN3ZXIgaXM6IHllcywgYW5kIG5vLiBUbyBjb25zaWRlciB0aGUgJ25vJyBhbnN3ZXIgZmlyc3Q6IEkgYW0gZG9pbmcgdGhpcyBpbnRlbnRpb25hbGx5LCB0byBwcm92aWRlIGFuIGV4ZXJjaXNlLCBhIHRyYWluaW5nIG9wcG9ydHVuaXR5IHRvIHlvdS4gSW4gcHJhY3RpY2UsIHRoZXJlIGFyZSBtYW55IGZpbmVyLCBtb3JlIGVsYWJvcmF0ZWQsIGFuZCBtb3JlIGNvbWZvcnRhYmxlIHdheXMgdG8gZG8gZXhhY3RseSB0aGUgc2FtZSBpbiBSLCBhbmQgeW91IHdpbGwgbGVhcm4gYSBsb3QgYWJvdXQgdGhlbSBpbiB0aGUgZnV0dXJlIHNlc3Npb25zLiBBcyBvZiB0aGUgJ3llcycgYW5zd2VyOiB3ZSBhcmUgZGVhbGluZyB3aXRoIGEgdmVyeSBzbWFsbCBkYXRhc2V0IGhlcmUsIGEgb25lIGVuY29tcGFzc2luZyBiYXJlbHkgMTZLIHJvd3MgYW5kIHNldmVyYWwgY29sdW1ucy4gSW4gdGhlIHdpbGQsIGlmIHlvdSBzdGFydCBhcHBseWluZyBEYXRhIFNjaWVuY2UgaW4gUiBvciBhbnkgb3RoZXIgbGFuZ3VhZ2UgaW4gcHJhY3RpY2UsIHRoZSBkYXRhc2V0cyB0aGF0IHlvdSB3aWxsIGJlIGZhY2luZyB3aWxsIHByb2JhYmx5IGJlIG9yZGVycyBvZiBtYWduaXR1ZGUgbGFyZ2VyLiBDYW4geW91IGluc3BlY3QgYnkgZXllIGEgZGF0YXNldCBlbmNvbXBhc3NpbmcgYSBmZXcgaHVuZHJlZHMgb2YgbWlsbGlvbnMgb2Ygcm93cyBhbmQgc2F5IHRlbnMsIG9yIGh1bmRyZWRzIG9mIGNvbHVtbnM/IFdlbGwsIG5vLiBBbmQgdGhhdCBpcyB3aHkgeW91IGhhdmUgdG8gYmUgcHJlcGFyZWQgdG8gaW52ZXN0IHNlcmlvdXMgd29yayBpbiB1bmRlcnN0YW5kaW5nIHRoZSBzdHJ1Y3R1cmUgb2YganVzdCBhbnkgZGF0YXNldCBhdCBoYW5kLiBJdCBpcyBoYXJkLCBpdCBpcyB0ZWRpb3VzLCBpdCB0YWtlcyBhIGxvdCBvZiBwYXRpZW50Y2UsIGFuZCBpdCBjZXJ0YWlubHkgbWFrZXMgRGF0YSBTY2llbmNlIGxlc3MgdGhhbiB0aGUgbW9zdCBzZXhpZXN0IHByb2Zlc3Npb24gb2YgdGhlIDIxc3QgY2VudHVyeS4NCg0KIyMjIyAzLjIgUGxheSBmb3IgcmVhbDogc2ltcGxlIGFuYWx5dGljcyAmIHZpc3VhbGl6YXRpb25zIG9mIGBsaXN0aW5nc2AhDQoNCioqUTEuKiogV2hhdCBpcyB0aGUgZGlzdHJpYnV0aW9uIG9mIGByb29tX3R5cGVgIGluIGBsaXN0aW5nc2A/IExldCdzIHNlZSBhbmQgaWxsdXN0cmF0ZToNCg0KYGBge3IgZWNobyA9IFR9DQpyb29tVHlwZSA8LSBhcy5kYXRhLmZyYW1lKHRhYmxlKGxpc3RpbmdzJHJvb21fdHlwZSkpDQpjb2xuYW1lcyhyb29tVHlwZSkgPC0gYygncm9vbV90eXBlJywgJ2ZyZXF1ZW5jeScpDQpyb29tVHlwZSA8LSByb29tVHlwZVtvcmRlcihyb29tVHlwZSRmcmVxdWVuY3kpLCBdDQpoZWFkKHJvb21UeXBlKQ0KYGBgDQpXaGF0IGlmIHdlIHdhbnQgdG8gcGxhY2UgdGhlIG1vc3QgZnJlcXVlbnQgYHJvb21fdHlwZWAgaW4gdGhlIHRvcD8NCg0KYGBge3IgZWNobyA9IFR9DQpyb29tVHlwZSA8LSByb29tVHlwZVtvcmRlcigtcm9vbVR5cGUkZnJlcXVlbmN5KSwgXQ0KaGVhZChyb29tVHlwZSkNCmBgYA0KSG93IG1hbnkgZGlmZmVyZW50IGByb29tX3R5cGVgIHZhbHVlcyBkbyB3ZSBvYnNlcnZlPw0KDQpgYGB7ciBlY2hvID0gVH0NCnJvb21UeXBlcyA8LSBhcy5kYXRhLmZyYW1lKHRhYmxlKGxpc3RpbmdzJHJvb21fdHlwZSkpDQpjb2xuYW1lcyhyb29tVHlwZXMpIDwtIGMoJ3Jvb21fdHlwZScsICdjb3VudCcpDQpyb29tVHlwZXMNCmBgYA0KT2ssIGZvdXIgb25seS4gVmlzdWFsaXplIHRoaXMgKCoqbm90ZToqKiB3ZSBhcmUganVtcGluZyBhIGJpdCBhaGVhZCBoZXJlLCBidXQgdGhlcmUgaXMgYSByZWFzb24gdG8gaXQpOg0KDQpgYGB7ciBlY2hvID0gVCwgbWVzc2FnZSA9IEYsIHdhcm5pbmcgPSBGfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KZ2dwbG90KGRhdGEgPSByb29tVHlwZXMsIA0KICAgICAgIGFlcyh4ID0gcm9vbV90eXBlLCB5ID0gY291bnQpKSArIA0KICBnZW9tX2JhcihzdGF0ID0gJ2lkZW50aXR5JywgZmlsbCA9ICJjYWRldGJsdWUzIikgKw0KICB4bGFiKCdSb29tIHR5cGUnKSArIA0KICB5bGFiKCdDb3VudCcpICsNCiAgZ2d0aXRsZSgnUm9vbSB0eXBlIGRpc3RyaWJ1dGlvbiBpbiBBaXJibmIgTGlzdGluZ3MnKSArIA0KICB0aGVtZV9idygpICsgDQogIHRoZW1lKHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSkNCmBgYA0KDQpbe2dncGxvdDJ9XShodHRwczovL2dncGxvdDIudGlkeXZlcnNlLm9yZy8pIGlzIGFuIGluZHVzdHJ5IHN0YW5kYXJkIFIgcGFja2FnZSBmb3IgKnN0YXRpYyogZGF0YSB2aXN1YWxpemF0aW9ucyAoaW4gc3BpdGUgb2YgdGhlIGZhY3QgdGhhdCB5b3UgY2FuIHByb2R1Y2UgaW50ZXJhY3RpdmUgdmlzdWFsaXphdGlvbnMgd2l0aCB7Z2dwbG90Mn0pLiBCZWZvcmUgdGhlIGVuZCBvZiB0aGlzIHNlc3Npb24sIEkgd291bGQgbGlrZSB0byBiZWdpbiBhbmFseXppbmcgdGhlIHtnZ3Bsb3QyfSBjb2RlOiB3ZSB3aWxsIGJlIHVzaW5nIHNvIG11Y2ggb2YgaXQgaW4gdGhlIGZ1dHVyZS4NCg0KT2JzZXJ2ZSB0aGUgZmlyc3QgcGFydDoNCg0KYGBge3IgZWNobyA9IFR9DQpnZ3Bsb3QoZGF0YSA9IHJvb21UeXBlcywgDQogICAgICAgYWVzKHggPSByb29tX3R5cGUsIHkgPSBjb3VudCkpDQpgYGANCg0KVGhpcyBwcm9kdWNlZCAqbm90aW5nKi4gTm90IHJlYWxseTogaXQgaXMgYW4gZW1wdHkgcGxvdCwgYnV0IGl0IGRvZXMgaGF2ZSB0aGUgaG9yaXpvbnRhbCAoKngqKSBheGlzIGFzIHdlbGwgYXMgdGhlIHZlcnRpY2FsICgqWSopIGF4aXMgZGVmaW5lZC4gVGhlIGBnZ3Bsb3QoKWAgZnVuY3Rpb24gYXNrcyBmb3IgYSBgZGF0YWAgYXJndW1lbnQ6IHRoZSBgZGF0YS5mcmFtZWAgZW5jb21wYXNzaW5nIHRoZSBkYXRhIHRoYXQgd2Ugd2FudCB0byB2aXN1YWxpemUuIEEgY2FsbCB0byBhbm90aGVyIGZ1bmN0aW9uLCBgYWVzKClgLCBpcyBtYWRlIGluc2lkZSBvZiBgZ2dwbG90KClgLCBkZWZpbmluZyB0aGUgYHhgIGFuZCBgeWAgYXJndW1lbnRzOiB3aGF0IGdvZXMgb24gdGhlIGhvcml6b250YWwgYW5kIHZlcnRpY2FsIGF4ZXMgb2YgdGhlIHBsb3QuICpOb3RlOiogd2hlbiB1c2luZyB7Z2dwbG90Mn0sIG9uY2UgdGhlIGRhdGFzZXQgaXMgZGV0ZXJtaW5lZCBpbiB0aGUgYGRhdGFgIGFyZ3VtZW50IG9mIGBnZ3Bsb3QoKWAgdGhlcmUgaXMgbm8gbmVlZCB0byBtYWtlIGEgcmVmZXJlbmNlIHRvIGl0IGluIGBhZXMoKWAgb3IgZWxzZXdoZXJlOiB3ZSBjYW4gbWFrZSByZWZlcmVuY2VzIHRvIGl0cyBjb2x1bW5zIGRpcmVjdGx5LCBhcyB3ZSBkaWQgaW4gYGFlcyh4ID0gcm9vbV90eXBlLCB5ID0gY291bnQpYC4NCg0KV2hhdCBoYXBwZW5zIGFmdGVyIHRoZSBmaXJzdCBgK2AgaW4gdGhlIGNvZGU/DQoNCmBgYHtyIGVjaG8gPSBUfQ0KZ2dwbG90KGRhdGEgPSByb29tVHlwZXMsIA0KICAgICAgIGFlcyh4ID0gcm9vbV90eXBlLCB5ID0gY291bnQpKSArIA0KICBnZW9tX2JhcihzdGF0ID0gJ2lkZW50aXR5JywgZmlsbCA9ICJjYWRldGJsdWUzIikNCmBgYA0KVGhlIGFkZGl0aW9uIG9mIGBnZW9tX2JhcigpYCBhZGRzIGEgKipsYXllcioqIHRvIHRoZSB7Z2dwbG90Mn0gcGxvdC4gQW55IHtnZ3Bsb3QyfSB2aXN1YWxpemF0aW9uIGlzIHByb2R1Y2VkIGluIHRoaXMgd2F5OiANCg0KLSBmaXJzdCBkZWZpbmUgdGhlIGBkYXRhYCBhbmQgdGhlIG1hcHBpbmcgb2YgdmFyaWFibGVzIGluIGBhZXMoKWAgaW4gYSBgZ2dwbG90KClgIGNhbGwsIHRoZW4NCi0gYWRkIGxheWVycyBieSB1c2luZyBhbiBhcHByb3ByaWF0ZSAqKmdlb20qKiwgbGlrZSBgZ2VvbV9iYXIoKWAgaW4gdGhpcyBleGFtcGxlLCBhbmQNCi0gc3R5bGUgdGhlIHBsb3QgYnkgYWRkaW5nIGFkZGl0aW9uYWwgbGF5ZXJzIHN1Y2ggYXMgYGdndGl0bGUoKWAgb3IgYHRoZW1lKClgLg0KDQpOb3RlIGhvdyBgZ2VvbV9iYXIoKWAgYWxzbyBoYXMgYXJndW1lbnRzIHRoYXQgaGVscCBzdHlsZSB0aGUgcGxvdCwgZS5nLiBgZmlsbCA9ICJjYWRldGJsdWUzImAgd2hpY2ggcGlja2VkIGBjYWRldGJsdWUzYCBhcyB0aGUgZmlsbCBjb2xvciBmb3IgdGhlIGJhciBwbG90LiBNb3JlIG9uIGNvbG9ycyBpbiBSOiBbY29sb3JzIGluIFJdKGh0dHBzOi8vd3d3LnItZ3JhcGgtZ2FsbGVyeS5jb20vNDItY29sb3JzLW5hbWVzLmh0bWwpLg0KDQpXaGF0IGhhcHBlbnMgbmV4dCBpcyB0aGUgaW50cm9kdWN0aW9uIG9mIHRoZSBgeGAgYW5kIGB5YCBsYWJlbHMgdG8gdGhlIHBsb3QsIGFzIHdlbGwgYXMgdGhlIHBsb3QgdGl0bGU6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KZ2dwbG90KGRhdGEgPSByb29tVHlwZXMsIA0KICAgICAgIGFlcyh4ID0gcm9vbV90eXBlLCB5ID0gY291bnQpKSArIA0KICBnZW9tX2JhcihzdGF0ID0gJ2lkZW50aXR5JywgZmlsbCA9ICJjYWRldGJsdWUzIikgKw0KICB4bGFiKCdSb29tIHR5cGUnKSArIA0KICB5bGFiKCdDb3VudCcpICsNCiAgZ2d0aXRsZSgnUm9vbSB0eXBlIGRpc3RyaWJ1dGlvbiBpbiBBaXJibmIgTGlzdGluZ3MnKQ0KYGBgDQpGaW5hbGx5IHdlIGNob3NlIHRvIHN0cmlwIHNvbWUgZGVmYXVsdCBzZXR0aW5ncyBieSB1c2luZyBgdGhlbWVfYncoKWAgYW5kIHN0eWxlIHRoZSBwbG90IGJ5IHJlbW92aW5nIHRoZSBgcGFuZWwuYm9yZGVyYCBpbiBhbiBhZGRpdGlvbmFsIGB0aGVtZSgpYCBjYWxsOiBgdGhlbWUocGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpKWA6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KZ2dwbG90KGRhdGEgPSByb29tVHlwZXMsIA0KICAgICAgIGFlcyh4ID0gcm9vbV90eXBlLCB5ID0gY291bnQpKSArIA0KICBnZW9tX2JhcihzdGF0ID0gJ2lkZW50aXR5JywgZmlsbCA9ICJjYWRldGJsdWUzIikgKw0KICB4bGFiKCdSb29tIHR5cGUnKSArIA0KICB5bGFiKCdDb3VudCcpICsNCiAgZ2d0aXRsZSgnUm9vbSB0eXBlIGRpc3RyaWJ1dGlvbiBpbiBBaXJibmIgTGlzdGluZ3MnKSArIA0KICB0aGVtZV9idygpICsgDQogIHRoZW1lKHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSkNCmBgYA0KYHRoZW1lKClgIGlzIHVzZWQgdG8gYWNjZXNzIHRoZSBkZXRhaWxzIG9mIGEge2dncGxvdDJ9IHBsb3Q7IGZvciBleGFtcGxlLCBjZW50ZXIgdGhlIHBsb3QgdGl0bGU6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KZ2dwbG90KGRhdGEgPSByb29tVHlwZXMsIA0KICAgICAgIGFlcyh4ID0gcm9vbV90eXBlLCB5ID0gY291bnQpKSArIA0KICBnZW9tX2JhcihzdGF0ID0gJ2lkZW50aXR5JywgZmlsbCA9ICJjYWRldGJsdWUzIikgKw0KICB4bGFiKCdSb29tIHR5cGUnKSArIA0KICB5bGFiKCdDb3VudCcpICsNCiAgZ2d0aXRsZSgnUm9vbSB0eXBlIGRpc3RyaWJ1dGlvbiBpbiBBaXJibmIgTGlzdGluZ3MnKSArIA0KICB0aGVtZV9idygpICsgDQogIHRoZW1lKHBhbmVsLmJvcmRlciA9IGVsZW1lbnRfYmxhbmsoKSkgKyANCiAgdGhlbWUocGxvdC50aXRsZSA9IGVsZW1lbnRfdGV4dChoanVzdCA9IDAuNSkpDQpgYGANCm9yIGNvbnRyb2wgdGhlIGJlaGF2aW9yIG9mIHRoZSBheGVzOg0KDQpgYGB7ciBlY2hvID0gVH0NCmdncGxvdChkYXRhID0gcm9vbVR5cGVzLCANCiAgICAgICBhZXMoeCA9IHJvb21fdHlwZSwgeSA9IGNvdW50KSkgKyANCiAgZ2VvbV9iYXIoc3RhdCA9ICdpZGVudGl0eScsIGZpbGwgPSAiY2FkZXRibHVlMyIpICsNCiAgeGxhYignUm9vbSB0eXBlJykgKyANCiAgeWxhYignQ291bnQnKSArDQogIGdndGl0bGUoJ1Jvb20gdHlwZSBkaXN0cmlidXRpb24gaW4gQWlyYm5iIExpc3RpbmdzJykgKyANCiAgdGhlbWVfYncoKSArIA0KICB0aGVtZShwYW5lbC5ib3JkZXIgPSBlbGVtZW50X2JsYW5rKCkpICsgDQogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSArIA0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChzaXplID0gMTMpKQ0KYGBgDQpUaGVyZSBhcmUgc28gbWFueSBuaWNlIHRyaWNrcyB0aGF0IGNhbiBiZSBwdWxsZWQgd2l0aCB7Z2dwbG90Mn0sIHlvdSB3aWxsIHNlZS4gQXQgdGhpcyBwb2ludCwgaXQgaXMgaW1wb3J0YW50IHRvIHJlbWVtYmVyIHRoZSBmb2xsb3dpbmc6DQoNCi0gdGhlIHBsb3QgaXMgZGVmaW5lZCBieSB0aGUgYGRhdGFgIGFyZ3VtZW50IGluIHRoZSBgZ2dwbG90KClgIGNhbGwsIGFuZCANCi0gdGhlIG1hcHBpbmcgb2YgdGhlIHZhcmlhYmxlcyBmcm9tIHRoZSBkYXRhc2V0IG9udG8gdGhlIHBsb3QgaXMgZGVmaW5lZCBieSBhbiBgYWVzKClgIGNhbGwgaW5zaWRlIHRoZSBgZ2dwbG90KClgIGNhbGw7DQotIGFkZGl0aW9uYWwgbGF5ZXJzIGFyZSBhZGRlZCB3aXRoIGArYCwgd2hpbGUgdmFyaW91cyBgZ2VvbWAgdGhpbmdzIGRlZmluZSB0aGUgdHlwZSBvZiB0aGUgcGxvdCAoYmFyIHBsb3QsIGluIG91ciBleGFtcGxlOyB3ZSB3aWxsIHNvb24gbGVhcm4gYWJvdXQgbGluZSBwbG90cywgc2NhdHRlciBwbG90cywgZGVuc2l0eSBwbG90cywgZXRjKTsgZmluYWxseSwgDQotIHRoZXJlIGFyZSB3YXlzIHRvIHN0eWxlIHRoZSBwbG90IGFkZGl0aW9uYWxseSwgbW9zdCBvZiB3aGljaCByZWx5IG9uIHZhcmlvdXMgYHRoZW1lKClgIGZ1bmN0aW9uIGNhbGxzLg0KDQojIyMgRnVydGhlciBSZWFkaW5ncw0KDQotIFtDaGFwdGVyIDEgU3RhcnRpbmcgb3V0IGluIFIgZnJvbSBJbnRyb2R1Y3Rpb24gdG8gUiwgdmVyc2lvbl0oaHR0cHM6Ly9tb25hc2hkYXRhZmx1ZW5jeS5naXRodWIuaW8vci1pbnRyby0yL3N0YXJ0aW5nLW91dC1pbi1yLmh0bWwjc2F2aW5nLWNvZGUtaW4tYW4tci1zY3JpcHQpDQotIFtDaGFwdGVyIERhdGEgVHlwZXMgYW5kIFN0cnVjdHVyZXMgZnJvbSBQcm9ncmFtbWluZyB3aXRoIFJdKGh0dHBzOi8vc3djYXJwZW50cnkuZ2l0aHViLmlvL3Itbm92aWNlLWluZmxhbW1hdGlvbi8xMy1zdXBwLWRhdGEtc3RydWN0dXJlcy8pDQotIFtDaGFwdGVyIDQgV29ya2Zsb3c6IGJhc2ljcyBmcm9tIFIgZm9yIERhdGEgU2NpZW5jZV0oaHR0cHM6Ly9yNGRzLmhhZC5jby5uei93b3JrZmxvdy1iYXNpY3MuaHRtbCkNCg0KIyMjIEhpZ2hseSBSZWNvbW1lbmRlZCBUbyBEbw0KDQotIFJlYWQgW0NoYXB0ZXJzIDEgdG8gMyBmcm9tIFIgZm9yIERhdGEgU2NpZW5jZSwgSGFkbGV5IFdpY2toYW0gJiBHYXJyZXR0IEdyb2xlbXVuZF0oaHR0cHM6Ly9yNGRzLmhhZC5jby5uei8pDQotIFJlYWQgW0NoYXB0ZXJzIDEgdG8gNSBmcm9tIE5vcm1hbiBNYXRsb2Zm4oCZcyBUaGUgQXJ0IG9mIFIgUHJvZ3JhbW1pbmddKGh0dHBzOi8vd3d3Lmdvb2dsZS5jb20vc2VhcmNoP2NsaWVudD1maXJlZm94LWItZCZjaGFubmVsPXRyb3cyJnN4c3JmPUFMZUtrMDNUX3FMQ016UklDWVdqNVVIcUZCSG52ZlY2VXclM0ExNjA3MjIwNzk1NTUzJmVpPU96N01YOGFlSVlLNmt3V2M1SmJnQXcmcT1Ob3JtYW4rTWF0bG9mZitUaGUrQXJ0K29mK1IrUHJvZ3JhbW1pbmcrcGRmJm9xPU5vcm1hbitNYXRsb2ZmK1RoZStBcnQrb2YrUitQcm9ncmFtbWluZytwZGYmZ3NfbGNwPUNnWndjM2t0WVdJUUF6SUZDQUFReVFNNkJ3Z2pFTWtERUNjNkFnZ3VVSms5V09kQVlLUkJhQUJ3QUhnQWdBR0hBWWdCOVFPU0FRTXdMalNZQVFDZ0FRR3FBUWRuZDNNdGQybDZ3QUVCJnNjbGllbnQ9cHN5LWFiJnZlZD0wYWhVS0V3aUdxT0NFcExqdEFoVUMzYVFLSFJ5eUJUd1E0ZFVEQ0F3JnVhY3Q9NSkNCg0KIyMjIFIgTWFya2Rvd24NCg0KW1IgTWFya2Rvd25dKGh0dHBzOi8vcm1hcmtkb3duLnJzdHVkaW8uY29tLykgaXMgd2hhdCBJIGhhdmUgdXNlZCB0byBwcm9kdWNlIHRoaXMgYmVhdXRpZnVsIE5vdGVib29rLiBXZSB3aWxsIGxlYXJuIG1vcmUgYWJvdXQgaXQgbmVhciB0aGUgZW5kIG9mIHRoZSBjb3Vyc2UsIGJ1dCBpZiB5b3UgYWxyZWFkeSBmZWVsIHJlYWR5IHRvIGRpdmUgZGVlcCwgaGVyZSdzIGEgYm9vazogW1IgTWFya2Rvd246IFRoZSBEZWZpbml0aXZlIEd1aWRlLCBZaWh1aSBYaWUsIEouIEouIEFsbGFpcmUsIEdhcnJldHQgR3JvbGVtdW5kcy5dKGh0dHBzOi8vYm9va2Rvd24ub3JnL3lpaHVpL3JtYXJrZG93bi8pIA0KDQojIyMgRXhlcmNpc2VzDQoNCi0gKipFMS4qKiBQcm9kdWNlIGEgbmV3IGNvbHVtbiBpbiBgbGlzdGluZ3NgOiBgbGlzdGluZ3MkZW5kc0luX2VgLiBUaGUgdHlwZSBvZiB0aGUgY29sdW1uIHNob3VsZCBiZSBsb2dpY2FsIChpLmUuIGBUUlVFYCwgYEZBTFNFYCk6IGBUUlVFYCBpZiBgaG9zdF9uYW1lYCBlbmRzIHdpdGggYGVgLCBgRkFMU0VgIG90aGVyd2lzZS4gSGludDogYGdyZXBsKClgLCBhbmQgcmVtZW1iZXIgdGhhdCBgZ3JlcGwoKWAgaXMgdmVjdG9yaXplZC4NCg0KLSAqKkUyLioqIFVzZSBgdGFibGUoKWAgc28gdG8gcHJvZHVjZSBhIGBuZWlnaGJvdXJob29kYCBYIGByb29tX3R5cGVgIHR3by13YXkgY29udGluZ2VuY3kgdGFibGUgYW5kIHR1cm4gaXQgaW50byBhIGBkYXRhLmZyYW1lYC4NCg0KLSAqKkUzLioqIFJlcHJvZHVjZSB0aGUgZm9sbG93aW5nIGNoYXJ0IHdpdGgge2dwbG90Mn0gYnkgcmVseWluZyBvbiB0aGUgY29kZSBwcmVzZW50ZWQgaW4gdGhpcyBzZXNzaW9uICsgW2dncGxvdDIgZG9jdW1lbnRhdGlvbl0oaHR0cHM6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvKSArIEhhZGxleSdzIGJvb2sgKyBzdGVhbCBjb2RlIGZyb20gU3RhY2sgT3ZlcmZsb3cuLi4gd2hhdGV2ZXIsIGp1c3QgdHJ5IHRvIHJlcHJvZHVjZSBpdC4gKipIaW50cy4qKiBJbiBgYWVzKClgLCB5b3UgbmVlZCB0byBkZWZpbmUgYGdyb3VwID0gYCwgYW5kIGBmaWxsID0gYDsgdGhlIGRlbnNpdHkgcGxvdCBpbiB7Z2dwbG90Mn0gaXMgcHJvZHVjZWQgYnkgYGdlb21fZGVuc2l0eSgpYCB3aGljaCBoYXMgYW4gYGFscGhhID1gIGFyZ3VtZW50IHRvIHNldCBmb3IgY29sb3IgdHJhbnNwYXJlbmN5OyBuYXR1cmFsIGxvZ2FyaXRobSBpbiBSIGlzIGBsb2coKWAgKGFuZCB0aGVuIHRoZXJlIGlzIGBsb2cxMCgpYCB0b28pLg0KDQoNCmBgYHtyIGVjaG8gPSBGfQ0KZ2dwbG90KGxpc3RpbmdzLCANCiAgICAgICBhZXMoeCA9IGxvZyhwcmljZSksIA0KICAgICAgICAgICBmaWxsID0gcm9vbV90eXBlLCANCiAgICAgICAgICAgZ3JvdXAgPSByb29tX3R5cGUpKSArIA0KICBnZW9tX2RlbnNpdHkoYWxwaGEgPSAuMTUsIGNvbG9yID0gImJsYWNrIikgKyANCiAgZ2d0aXRsZSgiUHJpY2UgdnMgUmV2aWV3cyBwZXIgTW9udGgsIHBlciBSb29tIFR5cGUiKSArIA0KICB4bGFiKCdsb2coUHJpY2UpJykgKyANCiAgeWxhYignRGVuc2l0eScpICsgDQogIHRoZW1lX2J3KCkgKyANCiAgdGhlbWUocGFuZWwuYm9yZGVyID0gZWxlbWVudF9ibGFuaygpKQ0KYGBgDQoNCi0gKipFNS4qKiBDb21wdXRlIHRoZSBhdmVyYWdlIGBudW1iZXJfb2ZfcmV2aWV3c2AgYnkgYG5laWdoYm91cmhvb2RgIGluIGBsaXN0aW5nc2AuICoqSGludDoqKiB1c2UgYG1lYW4oKWAsIHZlcnkgc2ltaWxhciB0byBgc3VtKClgLiBQdXQgdGhlIHJlc3VsdHMgaW4gdGhlIGBkYXRhLmZyYW1lYCB3aXRoIHRoZSBjb2x1bW5zIG5hbWVkIGBuZWlnaGJvdXJob29kYCBhbmQgYGF2ZXJhZ2VfcmV2aWV3c2AuDQoNCi0gKipFNi4qKiBXaGF0IGFyZSB0aGUgdG9wIGZpdmUgbGlzdGluZ3MgaW4gYGxpc3RpbmdzYCB3aXRoIHRoZSBiZXN0IHByaWNlIHBlciBuaWdodCByYXRpbz8NCg0KLSAqKkU3LioqIFdoYXQgaXMgdGhlIGF2ZXJhZ2UgcHJpY2UgcGVyIG5pZ2h0IHJhdGlvIGFjcm9zcyB0aGUgbmVpZ2hib3VyaG9vZHMgaW4gYGxpc3RpbmdzYD8NCg0KDQoNCioqKg0KR29yYW4gUy4gTWlsb3Zhbm92acSHDQoNCkRhdGFLb2xla3RpdiwgMjAyMC8yMQ0KDQpjb250YWN0OiBnb3Jhbi5taWxvdmFub3ZpY0BkYXRha29sZWt0aXYuY29tDQoNCiFbXSguLi9faW1nL0RLX0xvZ29fMTAwLnBuZykNCg0KKioqDQpMaWNlbnNlOiBbR1BMdjNdKGh0dHA6Ly93d3cuZ251Lm9yZy9saWNlbnNlcy9ncGwtMy4wLnR4dCkNClRoaXMgTm90ZWJvb2sgaXMgZnJlZSBzb2Z0d2FyZTogeW91IGNhbiByZWRpc3RyaWJ1dGUgaXQgYW5kL29yIG1vZGlmeSBpdCB1bmRlciB0aGUgdGVybXMgb2YgdGhlIEdOVSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlIGFzIHB1Ymxpc2hlZCBieSB0aGUgRnJlZSBTb2Z0d2FyZSBGb3VuZGF0aW9uLCBlaXRoZXIgdmVyc2lvbiAzIG9mIHRoZSBMaWNlbnNlLCBvciAoYXQgeW91ciBvcHRpb24pIGFueSBsYXRlciB2ZXJzaW9uLg0KVGhpcyBOb3RlYm9vayBpcyBkaXN0cmlidXRlZCBpbiB0aGUgaG9wZSB0aGF0IGl0IHdpbGwgYmUgdXNlZnVsLCBidXQgV0lUSE9VVCBBTlkgV0FSUkFOVFk7IHdpdGhvdXQgZXZlbiB0aGUgaW1wbGllZCB3YXJyYW50eSBvZiBNRVJDSEFOVEFCSUxJVFkgb3IgRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UuICBTZWUgdGhlIEdOVSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlIGZvciBtb3JlIGRldGFpbHMuDQpZb3Ugc2hvdWxkIGhhdmUgcmVjZWl2ZWQgYSBjb3B5IG9mIHRoZSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBhbG9uZyB3aXRoIHRoaXMgTm90ZWJvb2suIElmIG5vdCwgc2VlIDxodHRwOi8vd3d3LmdudS5vcmcvbGljZW5zZXMvPi4NCg0KKioqDQoNCg==