Session 03: Control flow + Functional programming in R

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?

We study a practical problem of dealing with many dataframes with identical (or nearly identical) columns: that would be similar to a set of tables with identical schemata in the world of Relational Database Management Systems (RDBMS). In the near future we will start drawing parallels between working with dataframes in R and the relationally structured tables in RDBMS like MySQL, MariaDB, PostgreSQL, and similar. While learning how to manage a set of dataframes we start introducing the R control flow: various iterations like for, repeat, and while loops, and decision making with if … else and switch. Of course, we recognize the nature of R as a functional and vectorized programming language, so we immediately show how to avoid using a loop when lapply() or any of its sisters - like Reduce() that we will introduce in this session - can do the job. We want our code to be a series of mappings from input data through various transformations onto the results, and consider everything else to be what is called a glue code. We will later show how {dplyr}, {tidyr}, and other packages are perfectly suited for that purpose.

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 following .csv files:
    1. listings.csv csv file (under the Amsterdam, North Holland, The Netherlands section);
    2. listings.csv csv file (under the Antwerp, Flemish Region, Belgium section);
    3. listings.csv csv file (under the Asheville, North Carolina, United States section);
    4. listings.csv csv file (under the Athens, Attica, Greece section).
  • Rename the files in the order of their download in the following way: (1) listings_amsterdam.csv, (2) listings_antwerp.csv, (3) listings_asheville.csv, and (4) listings_athens.csv. Remember: all files should reside in your _data directory.

1. Read several dataframes and then compose one large dataframe: for loops, lapply(), and Reduce()

All the listings_*.csv files should be found in your _data directory for this session:

dataDir <- paste0(getwd(), "/_data/")
list.files(dataDir)
[1] "listings_amsterdam.csv" "listings_antwerp.csv"   "listings_asheville.csv" "listings_athens.csv"   

Good. There is highly typical situation in Data Science in which some data collection systems have a regular data production update: for example, the datasets encompassing some observations that need to be analyzed are produced every hour on a company server, but the analysis and reporting need to take place only once a day. That means that every time a Data Scientist needs to analyze the new observations - most probably by relying on a highly automated data processing, modeling, and visualization pipeline that they have carefully designed and tested - they need to load 24 (i.e. one per hour) datasets for processing. In this example we are assuming that all such datasets have the same structure, and that means: the column names and the respective data types are always the same (we will see how to deal with the violations of this assumption later).

The first approach to acquire multiple tabular data at once in R is to use iterations. In R, we have the for loop, the repeat loop, and the while loop at our disposal. Let’s start with the for loop - probably the most common one.

# - how many files to read from /_data
num_files <- length(list.files(dataDir))
num_files
[1] 4

So there are four .csv files there. Ok:

1.1 The for loop

# - list files
files <- list.files(dataDir)
# - how many files there are:
num_files <- length(list.files(dataDir))
# - prepare a list to store the dataframes
dataSet <- list()
# - load all datasets, one by one, in a for loop:
for (i in 1:num_files) {
  dataSet[[i]] <- read.csv(paste0(dataDir, files[i]), 
                           header = T, 
                           stringsAsFactors = F)
}

The dataSet variable is now a list with four elements, each of them encompassing one dataframe: the result of a read.csv call over files[1], files[2], files[3], and files[4]. We will get back to our data soon. Let’s discuss the for loop in R first: it is simple.

The (i in 1:num_files) part of the for loop syntax tells R that i needs to take values from c(1,2,3,4) exactly in that order, because:

1:num_files
[1] 1 2 3 4

We use the i variable in the for loop to single out exactly what file name from the files vector do we need in each iteration: that is the paste0(dataDir, files[i] part that presents the first argument to read.csv(). What is found between { and } is a code block: that is what will be executed in each iteration of the for loop, all the way until i == num_files is TRUE.

In effect, R has started from i = 1 and increased i until it reached the value of num_files, each time executing what is found in the code block following the definition of the for loop, and using i as a parameter to alter what read.csv() will do. In each iteration, i was also used to index an element of the dataSet list: dataSet[[i]]. Finally, dataSet became a list with four elements, each being a dataframe holding one dataset from our _data directory.

For loops are realy simple:

for (a in 1:10) print(a)
[1] 1
[1] 2
[1] 3
[1] 4
[1] 5
[1] 6
[1] 7
[1] 8
[1] 9
[1] 10
names <- c('Maria', 'Ann', 'Peter', 'Jovan', 'Ilke')
for (x in 1:length(names)) {
  print(
    paste0("This is a beatiful name: ", 
           names[x])
  )
}
[1] "This is a beatiful name: Maria"
[1] "This is a beatiful name: Ann"
[1] "This is a beatiful name: Peter"
[1] "This is a beatiful name: Jovan"
[1] "This is a beatiful name: Ilke"

Do you remember lapply()?

1.2 The for loop vs. lapply() and when are the for loops inefficient in R

files <- list.files(dataDir)
dataSet <- lapply(paste0(dataDir, files), 
                  read.csv, header = T, stringsAsFactors = F)

Yes, we need for loops here and there, but this ^^ is the R way of doing things:

  • files is a vector encompassing the filenames of the .csv datasets that we need
  • lapply() is an R function that applies a given function - read.csv() - to all elements of the list or a vector given as its first argument (paste0(dataDir, files) in this example)
  • each function’s call result will be assigned to an output list - the result of a lapply() call
  • and the output is finally assigned to dataSet.

There is a myth saying that for loops in R are slow. Well, they can be. But using lapply() in a functional programming language like R is not just a matter of efficiency: look how code with lapply() is elegant and concise in comparison to iterations like the for loop!

When not to use the for loop in R (if you ask me: never, if you don’t have to)?

The typical context that slows down R code execution is the one in which a data structure will grow inside the loop:

numbers <- numeric()
for (i in 1:100) {
  numbers[i] <- i
}

First, there is absolutely no need to do such things in a vectorized language like R:

numbers <- 1:100

is enough, of course. The more important thing is that growing data structures in loops slows down the code execution and significantly in some situations (when the data structure is really large, for example). How can we use a more efficient for loop if we have to grow a data structure in it? Tell R how much memory is needed to hold the respective data structure:

# - list files
files <- list.files(dataDir)
# - how many files?
num_files <- length(list.files(dataDir))
# - prepare a list to store the dataframes
dataSet <- vector(mode = "list", length = num_files)
# - iterate:
for (i in 1:num_files) {
  dataSet[[i]] <- read.csv(paste0(dataDir, files[i]), 
                           header = T, 
                           stringsAsFactors = F)
}

We have used vector(mode = "list", length = num_files) to create an empty list of length num_files:

emptyList <- vector(mode = "list", length = 4)
emptyList
[[1]]
NULL

[[2]]
NULL

[[3]]
NULL

[[4]]
NULL

And NULL means this does not exist in R, different from NA in vectors and dataframes which means: we expect and element in this place but we do not observe one.

1.3 Compose one dataframe from several dataframes stored in a list: Reduce()

We know that dataSet has four elements and that each of them is an data.frame:

lapply(dataSet, class)
[[1]]
[1] "data.frame"

[[2]]
[1] "data.frame"

[[3]]
[1] "data.frame"

[[4]]
[1] "data.frame"

Now, say that the analyst needs to put all these dataframes together in a single dataframe for the purposes of statistical modeling. Take a look at the following code first and than we will discuss it in detail:

listings <- Reduce(rbind, dataSet)
dim(listings)
[1] 31852    16

Do the four datasets have 31852 rows in total?

sum(sapply(dataSet, function(x) dim(x)[1]))
[1] 31852

Yes, they do: listings is now the dataframe that we need. But what was that Reduce() thing?

“Reduce() takes a function f of two arguments and a list or vector x which is to be ‘reduced’ using f. The function is first called on the first two components of x, then with the result of that as the first argument and the third component of x as the second argument, then again with the result of the second step as first argument and the fourth component of x as the second argument etc. The process is continued until all elements of x have been processed.” - From R: Reduce() – apply’s lesser known brother, the Data Science made in Switzerland blog.

Let’ see:

numbers <- c(1, 2, 3, 4)
Reduce('^', numbers)
[1] 1

Of course, because: 1^2 = 1, 1^3 = 1, 1^4 = 1, but it does work that way, I assure you:

numbers <- c(4, 3, 2, 1)
Reduce("^", numbers)
[1] 4096

because 4^3 = 64, 64^2 = 4096, and 4096^1 = 4096. If you are interested to see the intermediate results of Reduce(), set its accumulate argument to TRUE:

numbers <- c(4, 3, 2, 1)
Reduce("^", numbers, accumulate = T)
[1]    4   64 4096 4096

So what happened in our listings <- Reduce(rbind, dataSet) call is the following: (a) dataSet is a list of four dataframes, (b) rbind is a function that takes two arguments, both dataframes, and puts them on top of each other (mind the column names!), so (c) Reduce() began by binding the first two elements of dataSet together, then used the resulting dataframe as its next input together with the third element of dataSet, and finaly used that resulting dataframe as its first argument together with the fourth element of dataSet as its second argument to produce the listings.

Note. You have noticed that I have used the ^ operator in R as if it were a function in Reduce('^', numbers). That is because it is a function in R, look:

'^'(2, 3)
[1] 8

Similarly,

'+'(2, 3)
[1] 5

That is why we call it a functional programming language - and a beautiful one indeed.

1.4 A concise overview of some R functions used in functional programming

Here is a schematic overview of some R functions that are typically used in functional programming. The one at the bottom - Map(f, ...) - we have not used yet. But we will, at some point, and it is also pretty intuitive, e.g. for two lists, x, y, and a binary function f(x,y), Map(f, x, y) returns f(x[[1]], y[[1]]), f(x[[2]], y[[2]]), .., f(x[[n]], y[[n]]). Map() can use n-ary functions, i.e. f can have as many arguments as needed.

1.5 Enter a new column into each dataframe and compose: strsplit() and gsub()

Finally, there is one problem with listings: we do have all the data in one place, but we do not know from which original dataset (Amsterdam, Antwerp, Asheville, Athens) do the data come from. Let’s fix this. Remember that we have the filenames in files:

files
[1] "listings_amsterdam.csv" "listings_antwerp.csv"   "listings_asheville.csv" "listings_athens.csv"   

What happens if…

strsplit(files[1], split = "_")
[[1]]
[1] "listings"      "amsterdam.csv"

Ok, so strsplit() takes a string (files[1]) in this example) and splits it up where it finds the character provided in its split argument (and that would be "_" in this example) - and returns a list.

Ok, and what happens if…

gsub(".csv", "", strsplit(files[1], split = "_")[[1]], fixed = T)
[1] "listings"  "amsterdam"

gsub() - a relative of grepl() will substitute its first argument by its second argument in a given string:

gsub("A", "E", "Amelia")
[1] "Emelia"

So

gsub(".csv", "", strsplit(files[1], split = "_")[[1]], fixed = T)
[1] "listings"  "amsterdam"

will first strsplit the files[1] string by "_", and we use [[1]] in order to grab the result because it returns a list; gsub() then replaces ".csv" with an empty string "", and we need to specify fixed = T to inform gsub() that we do not wish the . to be interpreted as regex (where it means: any character) but literally as a . character. The remaining thing to do is to pick up the city which is found in the second position of the output vector:

gsub(".csv", "", strsplit(files[1], split = "_")[[1]], fixed = T)[2]
[1] "amsterdam"

And we could have accomplished the same by:

gsub(".csv", "", strsplit(files[1], split = "_")[[1]][2], fixed = T)
[1] "amsterdam"

because strsplit returns a list of length two ([[1]] 'listings' 'amsterdam.csv' in this example).

So here is how we get to the cities that we need for our datasets:

cities <- sapply(files, function(x) {
  gsub(".csv", "", strsplit(x, split = "_")[[1]][2], fixed = T)
})
cities
listings_amsterdam.csv   listings_antwerp.csv listings_asheville.csv    listings_athens.csv 
           "amsterdam"              "antwerp"            "asheville"               "athens" 

It is named vector, with names returned from the sapply() call. We can unname() that:

cities <- unname(cities)
cities
[1] "amsterdam" "antwerp"   "asheville" "athens"   

Now, we need to produce listings with a new column: listings$city. How do we accomplish that?

for (i in 1:length(cities)) {
  dataSet[[i]]$city <- cities[i]
}
listings <- Reduce(rbind, dataSet)
table(listings$city)

amsterdam   antwerp asheville    athens 
    18522      1703      2172      9455 

And now we can answer questions like: what is the average price per AirBnB post per city, and similar:

library(dplyr)
package 㤼㸱dplyr㤼㸲 was built under R version 4.0.3
listings %>% 
  select(city, price) %>%
  group_by(city) %>% 
  summarise(mean_price = mean(price))
NA

Ooops, what is {dplyr}? That comes later. What I wanted to do this in base R?

city <- unique(listings$city)
mean_price <- unname(sapply(city, function(x) {
  w <- which(listings$city == x)
  return(mean(listings$price[w]))
}))
data.frame(city, mean_price)

But the beauty of {dplyr}! You will see..!

2 Beyond for: while and repeat loops, next and break

As many other programming languages, R makes use of while loops and repeat loops as well. Let’s see how do they differ from the for loop by means of very simple examples only:

counter <- 0
while (counter <= 100) {
  counter <- counter + 1
  if (counter %% 10 == 0) {
    print(counter)
  }
}
[1] 10
[1] 20
[1] 30
[1] 40
[1] 50
[1] 60
[1] 70
[1] 80
[1] 90
[1] 100

In the while loop, we begin by checking if the loop exit condition is satisfied before executing the code block in an iteration. We began by setting counter to zero; then we ask if it is less or equal than 100, and if that condition is true we execute the code block, increase the value of counter only inside the block.

We have also used our first if in R:

if (counter %% 10 == 0) {
    print(counter)
}

And this expression does not print anything because counter has already reached 101 in the while loop (why?), while the condition counter %% 10 == specifies that only if the remainder after dividing counter with 10 is equal to zero.

The repeat loop, to the contrary, begins by executing what is in its code block and stops only if a certain condition in the block is satisfied:

counter <- 0
repeat {
  counter <- counter + 1
  if (counter %% 10 == 0) {
    print(counter)
  }
 # - exit condition:
  if (counter > 100) {
    break
  }
}
[1] 10
[1] 20
[1] 30
[1] 40
[1] 50
[1] 60
[1] 70
[1] 80
[1] 90
[1] 100

Remember: you have to break the repeat loop in R somehow: specify a condition, and if the condition is satisfied, end the loop execution by break! Unless you are able to break it will run FOREVER and inflate the Universe with computing!

We can also use break in a for loop to exit altogether:

for (counter in 1:100) {
  if (counter %% 10 == 0) {
    print(counter)
  }
  # - exit condition
  if (counter > 50) {
    break
  }
}
[1] 10
[1] 20
[1] 30
[1] 40
[1] 50

The loop would iterate until counter reaches 100, as specified in for (counter in 1:100), but we have introduced an exit condition by if (counter > 50) {break} so it did not.

Finally, there is next, telling the loop to abandon the execution of the current iteration and start the next one if some condition is satisfied:

for (counter in 1:100) {
  
  if (!(counter %% 10 == 0)) {
    next
  } else {
    print(counter)
  }
  
  print("Remainder is zero!")
  
}
[1] 10
[1] "Remainder is zero!"
[1] 20
[1] "Remainder is zero!"
[1] 30
[1] "Remainder is zero!"
[1] 40
[1] "Remainder is zero!"
[1] 50
[1] "Remainder is zero!"
[1] 60
[1] "Remainder is zero!"
[1] 70
[1] "Remainder is zero!"
[1] 80
[1] "Remainder is zero!"
[1] 90
[1] "Remainder is zero!"
[1] 100
[1] "Remainder is zero!"

The result is similar to what we have seen before, but the code is modified. The ! unary operator in R is negation, so (!(counter %% 10 == 0)) means: the remainder of division by ten of counter is not equal to zero. And if that is so, we want to skip the current iteration (and that is why print("Remainder is zero!") does not execute) and start the next one. Of course, break and next can be combined in a single loop.

Also, we have used to if ... else syntax to make a decision in R. We will next discuss decisions in control flow more closely.

3 Decisions: if...else, switch(), and ifelse()

3.1 if...else is simple

The usage of if ... else in R is really simple. Let’s begin with:

num_rows <- dim(listings)[1]

if (num_rows >= 10000) {
  print("Listings have more than 10,000 rows!")
} else {
  print("Listings is a very small dataset.")
}
[1] "Listings have more than 10,000 rows!"

The if ... else constructs can be nested:

num_rows <- dim(listings)[1]
num_cols <- dim(listings)[2]

if (num_rows >= 10000) {
  print("Listings have more than 10,000 rows!")
  if (num_cols > 10) {
    print("And it has more than ten columns!")
  } else {
    print("But it has less than ten columns!")
  }
} else {
  print("Listings is a very small dataset.")
  if (num_cols > 10) {
    print("And it has more than ten columns!")
  } else {
    print("But it has less than ten columns!")
  }
}
[1] "Listings have more than 10,000 rows!"
[1] "And it has more than ten columns!"

And we can chain if ... else indefinitely:

this_number <- 5
if (this_number > 10) {
  print("This number is less than ten...")
} else if (this_number < 5) {
  print("This number is less than five.")
}

Nothing happens, of course, because this_number is exactly five. However,

this_number <- 5
if (this_number > 10) {
  print("This number is less than ten...")
} else if (this_number <= 5) {
  print("This number is less or equal to five.")
}
[1] "This number is less or equal to five."

3.2 switch() for multiple checks + ifelse() for binary decisions

Is this_animal a dog, an elephant, a cat, or a tiger?

this_animal <- 'tiger'

switch(this_animal,
       "dog" = "It's a dog!",
       "elephant" = "It's an elephant!", 
       "cat" = "Meow!", 
       "tiger" = "A tiger? In Africa?")
[1] "A tiger? In Africa?"

Let’s analyze how this works. First, this_animal was set to 'tiger'. Then it was used as an expression in switch() which searches through the list of provided alternatives and returns the result from the match. In this case, a match between this_animal and "tiger" determined that the response should be "A tiger? In Africa?"

Let’s invoke a "Meow! response from a "cat":

this_animal <- 'cat'
switch(this_animal,
       dog = "It's a dog!",
       elephant = "It's an elephant!", 
       cat = "Meow!", 
       tiger = "A tiger? In Africa?")
[1] "Meow!"

And let’s try with some numbers:

this_number <- 1
switch(this_number,
       "It's the first one!",
       "It's the second one!", 
       "It's the third one one!",
       "It's the fourth one!")
[1] "It's the first one!"
this_number <- 4
switch(this_number,
       "It's the first one!",
       "It's the second one!", 
       "It's the third one one!",
       "It's the fourth one!")
[1] "It's the fourth one!"

You can use code blocks in switch(), of course:

some_expression = 'hey'
switch(some_expression, 
       hey = { 
         print(2 + 2)
         print('Hey!') 
         },
       hi = { 
         print(5 + 5)
         print('Hi!')
         },
       {
         print(6 * 3)
         print('Default case!')
         }
       )
[1] 4
[1] "Hey!"

The default case is triggered if switch() cannot find a match:

some_expression = 'Ciao'
switch(some_expression, 
       hey = { 
         print(2 + 2)
         print('Hey!') 
         },
       hi = { 
         print(5 + 5)
         print('Hi!')
         },
       {
         print(6 * 3)
         print('Default case!')
         }
       )
[1] 18
[1] "Default case!"

A practical application of switch()? Let’s check if all dataframes in dataSet have the same column names and report accordingly:

columns <- lapply(dataSet, colnames)
columns
[[1]]
 [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"               "city"                          

[[2]]
 [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"               "city"                          

[[3]]
 [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"               "city"                          

[[4]]
 [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"               "city"                          

Learn about identical():

identical(columns[[1]], columns[[2]])
[1] TRUE

Are all pairs in columns identical? Learn about combn():

combn(1:4, m = 2)
     [,1] [,2] [,3] [,4] [,5] [,6]
[1,]    1    1    1    2    2    3
[2,]    2    3    4    3    4    4

Let’s coordinate now:

search <- combn(1:length(columns), 2)
apply(search, 2, function(x) {
  identical(columns[x[1]], columns[x[2]])
})
[1] TRUE TRUE TRUE TRUE TRUE TRUE

This was our first usage of apply(): take a matrix like search, apply(X, MARGIN, FUN) where we apply FUN across rows with MARGIN = 1 and across columns with MARGIN = 2. To obtain row sums, for example:

apply(search, 1, sum)
[1] 10 20

And to obtain sums across the columns:

apply(search, 2, sum)
[1] 3 4 5 5 6 7

And of course R has functions for this: rowSums() and colSums() - you can try them out if you wish.

So, let’s report if all of the column names of the dataframes in dataSet are identical:

columns <- lapply(dataSet, colnames)
search <- combn(1:length(columns), 2)
check <- apply(search, 2, function(x) {
  identical(columns[x[1]], columns[x[2]])
})
num_pairs <- dim(search)[2]
dataFramesCheck <- ifelse(num_pairs == sum(check), 1, 2)
switch(dataFramesCheck, 
       "All dataframes have the same columns; you can proceed with code execution.", 
       "Inconsistent column names; try using datatable::rbindlist() with fill = T.")
[1] "All dataframes have the same columns; you can proceed with code execution."

NOTE. the ifelse() function in R is new. It is a vectorized function that first checks for the condition in its first argument (which needs to evaluate to TRUE or FALSE), and then if TRUE executes the expression found in its second argument, and otherwise executes what is found in its third argument:

ifelse(10 < 5, 
       "I do not understand basic arithmetics.", 
       "Ok I got at least that one right."
       )
[1] "Ok I got at least that one right."

NOTE. Obviously, we could have used just ifelse() for this task:

columns <- lapply(dataSet, colnames)
search <- combn(1:length(columns), 2)
check <- apply(search, 2, function(x) {
  identical(columns[x[1]], columns[x[2]])
})
num_pairs <- dim(search)[2]
ifelse(num_pairs == sum(check), 
       "All dataframes have the same columns; you can proceed with code execution.", 
       "Inconsistent column names; try using datatable::rbindlist() with fill = T.")
[1] "All dataframes have the same columns; you can proceed with code execution."

SUMMARY. We have learned about the basics of R programming in this session. The focus was twofold: (a) we have learned about the control flow, which is similar in R and in almost any other programming language: for, while, repeat loops, and decision making with if ... else, switch(), and ifelse(); on the other hand, we have touched upon functional programming in R with apply(), lapply(), sapply(), Reduce(). Do not forget: we really love functional programming in R. It makes the code more consistent and elegant, if anything. Functional programming is perfectly suited for Data Science projects because it allows us to formulate and solve the problem as a chain of functional transformations across the input data. An ideal Data Science code - not something that can be always achieved - is nothing more than that.


Important sources, documentation, etc.

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. In the beginning of this session we have used the for loop to load four datasets into a list of dataframes in R. Do the same, but use the repeat loop instead.

  • E2. Same as E1 but use the while loop in place of repeat.

  • E3. Produce a new column of logicals (TRUE, FALSE), listings$priceClass in listings that is TRUE if listings$price is above or equal to its median, and FALSE if it is strictly lower than its median. Hint: Use ifelse to create the new column. Try to summarize your data by using summary() on listings$price. What does summary(listings) look like?

  • E4. Produce a new column in listings: listings$priceNightsRation, which is the ratio of listings$minimum_nights and listings$price.

  • E5. Take a look at the following R code:

set <- 1:100
for (i in 1:length(set)) {
  if(i == 100) {
    break
    } else {
      if ((i == 1)) {
        z = set[i] + set[i+1]
        print(z)
        } else {
          z = z + set[i+1]
          print(z)
        }
    }
  }

Use Reduce() to accomplish the same in a one-liner. Remember the accumulate argument!


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/.


LS0tDQp0aXRsZTogSW50cm8gdG8gRGF0YSBTY2llbmNlIChOb24tVGVjaG5pY2FsIEJhY2tncm91bmQsIFIpIC0gU2Vzc2lvbjAzDQphdXRob3I6DQotIG5hbWU6IEdvcmFuIFMuIE1pbG92YW5vdmnEhywgUGhEDQogIGFmZmlsaWF0aW9uOiBEYXRhS29sZWt0aXYsIENoaWVmIFNjaWVudGlzdCAmIE93bmVyOyBEYXRhIFNjaWVudGlzdCBmb3IgV2lraWRhdGEsIFdNREUNCmFic3RyYWN0OiANCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cNCiAgICB0aGVtZTogc3BhY2VsYWINCiAgICB0b2M6IHllcw0KICAgIHRvY19mbG9hdDogeWVzDQogICAgdG9jX2RlcHRoOiA1DQogIGh0bWxfZG9jdW1lbnQ6DQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZGVwdGg6IDUNCi0tLQ0KDQohW10oLi4vX2ltZy9ES19Mb2dvXzEwMC5wbmcpDQoNCioqKg0KIyBTZXNzaW9uIDAzOiBDb250cm9sIGZsb3cgKyBGdW5jdGlvbmFsIHByb2dyYW1taW5nIGluIFIgDQoqKkZlZWRiYWNrKiogc2hvdWxkIGJlIHNlbmQgdG8gYGdvcmFuLm1pbG92YW5vdmljQGRhdGFrb2xla3Rpdi5jb21gLiANClRoZXNlIG5vdGVib29rcyBhY2NvbXBhbnkgdGhlIEludHJvIHRvIERhdGEgU2NpZW5jZTogTm9uLVRlY2huaWNhbCBCYWNrZ3JvdW5kIGNvdXJzZSAyMDIwLzIxLg0KDQoqKioNCg0KIyMjIFdoYXQgZG8gd2Ugd2FudCB0byBkbyB0b2RheT8NCg0KV2Ugc3R1ZHkgYSBwcmFjdGljYWwgcHJvYmxlbSBvZiBkZWFsaW5nIHdpdGggbWFueSBkYXRhZnJhbWVzIHdpdGggaWRlbnRpY2FsIChvciBuZWFybHkgaWRlbnRpY2FsKSBjb2x1bW5zOiB0aGF0IHdvdWxkIGJlIHNpbWlsYXIgdG8gYSBzZXQgb2YgdGFibGVzIHdpdGggaWRlbnRpY2FsIHNjaGVtYXRhIGluIHRoZSB3b3JsZCBvZiBbUmVsYXRpb25hbCBEYXRhYmFzZSBNYW5hZ2VtZW50IFN5c3RlbXMgKFJEQk1TKV0oaHR0cHM6Ly9lbi53aWtpcGVkaWEub3JnL3dpa2kvUmVsYXRpb25hbF9kYXRhYmFzZSkuIEluIHRoZSBuZWFyIGZ1dHVyZSB3ZSB3aWxsIHN0YXJ0IGRyYXdpbmcgcGFyYWxsZWxzIGJldHdlZW4gd29ya2luZyB3aXRoIGRhdGFmcmFtZXMgaW4gUiBhbmQgdGhlIHJlbGF0aW9uYWxseSBzdHJ1Y3R1cmVkIHRhYmxlcyBpbiBSREJNUyBsaWtlIFtNeVNRTF0oaHR0cHM6Ly93d3cubXlzcWwuY29tLyksIFtNYXJpYURCXShodHRwczovL21hcmlhZGIub3JnLyksIFtQb3N0Z3JlU1FMXShodHRwczovL3d3dy5wb3N0Z3Jlc3FsLm9yZy8pLCBhbmQgc2ltaWxhci4gV2hpbGUgbGVhcm5pbmcgaG93IHRvIG1hbmFnZSBhIHNldCBvZiBkYXRhZnJhbWVzIHdlIHN0YXJ0IGludHJvZHVjaW5nIHRoZSBSICpjb250cm9sIGZsb3cqOiB2YXJpb3VzIGl0ZXJhdGlvbnMgbGlrZSAqZm9yKiwgKnJlcGVhdCosIGFuZCAqd2hpbGUqIGxvb3BzLCBhbmQgZGVjaXNpb24gbWFraW5nIHdpdGggKmlmIC4uLiBlbHNlKiBhbmQgKnN3aXRjaCouIE9mIGNvdXJzZSwgd2UgcmVjb2duaXplIHRoZSBuYXR1cmUgb2YgUiBhcyBhIGZ1bmN0aW9uYWwgYW5kIHZlY3Rvcml6ZWQgcHJvZ3JhbW1pbmcgbGFuZ3VhZ2UsIHNvIHdlIGltbWVkaWF0ZWx5IHNob3cgaG93IHRvIGF2b2lkIHVzaW5nIGEgbG9vcCB3aGVuIGBsYXBwbHkoKWAgb3IgYW55IG9mIGl0cyBzaXN0ZXJzIC0gbGlrZSBgUmVkdWNlKClgIHRoYXQgd2Ugd2lsbCBpbnRyb2R1Y2UgaW4gdGhpcyBzZXNzaW9uIC0gY2FuIGRvIHRoZSBqb2IuIFdlIHdhbnQgb3VyIGNvZGUgdG8gYmUgYSBzZXJpZXMgb2YgbWFwcGluZ3MgZnJvbSBpbnB1dCBkYXRhIHRocm91Z2ggdmFyaW91cyB0cmFuc2Zvcm1hdGlvbnMgb250byB0aGUgcmVzdWx0cywgYW5kIGNvbnNpZGVyIGV2ZXJ5dGhpbmcgZWxzZSB0byBiZSB3aGF0IGlzIGNhbGxlZCAqYSBnbHVlIGNvZGUqLiBXZSB3aWxsIGxhdGVyIHNob3cgaG93IHtkcGx5cn0sIHt0aWR5cn0sIGFuZCBvdGhlciBwYWNrYWdlcyBhcmUgcGVyZmVjdGx5IHN1aXRlZCBmb3IgdGhhdCBwdXJwb3NlLiANCg0KDQojIyMgMC4gUHJlcmVxdWlzaXRzLg0KDQotIENyZWF0ZSBhIGRpcmVjdG9yeSBuYW1lZCBgX2RhdGFgIGluIHRoZSBkaXJlY3Rvcnkgd2hlcmUgeW91IHdhbnQgdG8gc3RvcmUgeW91ciBSIGNvZGUgZm9yIHRoaXMgc2Vzc2lvbi4gDQotIEdvIHRvIHRoZSBbSW5zaWRlIEFpcmJuYl0oaHR0cDovL2luc2lkZWFpcmJuYi5jb20vZ2V0LXRoZS1kYXRhLmh0bWwpIHBhZ2UgYW5kIGRvd25sb2FkIHRoZSBmb2xsb3dpbmcgYC5jc3ZgIGZpbGVzOg0KICAoMSkgW2BsaXN0aW5ncy5jc3ZgXShodHRwOi8vZGF0YS5pbnNpZGVhaXJibmIuY29tL3RoZS1uZXRoZXJsYW5kcy9ub3J0aC1ob2xsYW5kL2Ftc3RlcmRhbS8yMDIwLTEyLTEyL3Zpc3VhbGlzYXRpb25zL2xpc3RpbmdzLmNzdikgYGNzdmAgZmlsZSAodW5kZXIgdGhlICpBbXN0ZXJkYW0sIE5vcnRoIEhvbGxhbmQsIFRoZSBOZXRoZXJsYW5kcyogc2VjdGlvbik7DQogICgyKSBbYGxpc3RpbmdzLmNzdmBdKGh0dHA6Ly9kYXRhLmluc2lkZWFpcmJuYi5jb20vYmVsZ2l1bS92bGcvYW50d2VycC8yMDIwLTEwLTI2L3Zpc3VhbGlzYXRpb25zL2xpc3RpbmdzLmNzdikgYGNzdmAgZmlsZSAodW5kZXIgdGhlICpBbnR3ZXJwLCBGbGVtaXNoIFJlZ2lvbiwgQmVsZ2l1bSogc2VjdGlvbik7DQogICgzKSBbYGxpc3RpbmdzLmNzdmBdKGh0dHA6Ly9kYXRhLmluc2lkZWFpcmJuYi5jb20vdW5pdGVkLXN0YXRlcy9uYy9hc2hldmlsbGUvMjAyMC0xMS0wOC92aXN1YWxpc2F0aW9ucy9saXN0aW5ncy5jc3YpIGBjc3ZgIGZpbGUgKHVuZGVyIHRoZSAqQXNoZXZpbGxlLCBOb3J0aCBDYXJvbGluYSwgVW5pdGVkIFN0YXRlcyogc2VjdGlvbik7DQogICg0KSBbYGxpc3RpbmdzLmNzdmBdKGh0dHA6Ly9kYXRhLmluc2lkZWFpcmJuYi5jb20vZ3JlZWNlL2F0dGljYS9hdGhlbnMvMjAyMC0xMC0yNS92aXN1YWxpc2F0aW9ucy9saXN0aW5ncy5jc3YpIGBjc3ZgIGZpbGUgKHVuZGVyIHRoZSAqQXRoZW5zLCBBdHRpY2EsIEdyZWVjZSogc2VjdGlvbikuDQotIFJlbmFtZSB0aGUgZmlsZXMgaW4gdGhlIG9yZGVyIG9mIHRoZWlyIGRvd25sb2FkIGluIHRoZSBmb2xsb3dpbmcgd2F5OiAoMSkgYGxpc3RpbmdzX2Ftc3RlcmRhbS5jc3ZgLCAoMikgYGxpc3RpbmdzX2FudHdlcnAuY3N2YCwgKDMpIGBsaXN0aW5nc19hc2hldmlsbGUuY3N2YCwgYW5kICg0KSBgbGlzdGluZ3NfYXRoZW5zLmNzdmAuIFJlbWVtYmVyOiBhbGwgZmlsZXMgc2hvdWxkIHJlc2lkZSBpbiB5b3VyIGBfZGF0YWAgZGlyZWN0b3J5Lg0KDQoNCiMjIyAxLiBSZWFkIHNldmVyYWwgZGF0YWZyYW1lcyBhbmQgdGhlbiBjb21wb3NlIG9uZSBsYXJnZSBkYXRhZnJhbWU6IGBmb3JgIGxvb3BzLCBgbGFwcGx5KClgLCBhbmQgYFJlZHVjZSgpYA0KDQpBbGwgdGhlIGBsaXN0aW5nc18qLmNzdmAgZmlsZXMgc2hvdWxkIGJlIGZvdW5kIGluIHlvdXIgYF9kYXRhYCBkaXJlY3RvcnkgZm9yIHRoaXMgc2Vzc2lvbjoNCg0KDQpgYGB7ciBlY2hvID0gVH0NCmRhdGFEaXIgPC0gcGFzdGUwKGdldHdkKCksICIvX2RhdGEvIikNCmxpc3QuZmlsZXMoZGF0YURpcikNCmBgYA0KDQpHb29kLiBUaGVyZSBpcyBoaWdobHkgdHlwaWNhbCBzaXR1YXRpb24gaW4gRGF0YSBTY2llbmNlIGluIHdoaWNoIHNvbWUgZGF0YSBjb2xsZWN0aW9uIHN5c3RlbXMgaGF2ZSBhIHJlZ3VsYXIgZGF0YSBwcm9kdWN0aW9uIHVwZGF0ZTogZm9yIGV4YW1wbGUsIHRoZSBkYXRhc2V0cyBlbmNvbXBhc3Npbmcgc29tZSBvYnNlcnZhdGlvbnMgdGhhdCBuZWVkIHRvIGJlIGFuYWx5emVkIGFyZSBwcm9kdWNlZCBldmVyeSBob3VyIG9uIGEgY29tcGFueSBzZXJ2ZXIsIGJ1dCB0aGUgYW5hbHlzaXMgYW5kIHJlcG9ydGluZyBuZWVkIHRvIHRha2UgcGxhY2Ugb25seSBvbmNlIGEgZGF5LiBUaGF0IG1lYW5zIHRoYXQgZXZlcnkgdGltZSBhIERhdGEgU2NpZW50aXN0IG5lZWRzIHRvIGFuYWx5emUgdGhlIG5ldyBvYnNlcnZhdGlvbnMgLSBtb3N0IHByb2JhYmx5IGJ5IHJlbHlpbmcgb24gYSBoaWdobHkgYXV0b21hdGVkIGRhdGEgcHJvY2Vzc2luZywgbW9kZWxpbmcsIGFuZCB2aXN1YWxpemF0aW9uIHBpcGVsaW5lIHRoYXQgdGhleSBoYXZlIGNhcmVmdWxseSBkZXNpZ25lZCBhbmQgdGVzdGVkIC0gdGhleSBuZWVkIHRvIGxvYWQgMjQgKGkuZS4gb25lIHBlciBob3VyKSBkYXRhc2V0cyBmb3IgcHJvY2Vzc2luZy4gSW4gdGhpcyBleGFtcGxlIHdlIGFyZSBhc3N1bWluZyB0aGF0IGFsbCBzdWNoIGRhdGFzZXRzIGhhdmUgdGhlIHNhbWUgc3RydWN0dXJlLCBhbmQgdGhhdCBtZWFuczogdGhlIGNvbHVtbiBuYW1lcyBhbmQgdGhlIHJlc3BlY3RpdmUgZGF0YSB0eXBlcyBhcmUgYWx3YXlzIHRoZSBzYW1lICh3ZSB3aWxsIHNlZSBob3cgdG8gZGVhbCB3aXRoIHRoZSB2aW9sYXRpb25zIG9mIHRoaXMgYXNzdW1wdGlvbiBsYXRlcikuDQoNClRoZSBmaXJzdCBhcHByb2FjaCB0byBhY3F1aXJlIG11bHRpcGxlIHRhYnVsYXIgZGF0YSBhdCBvbmNlIGluIFIgaXMgdG8gdXNlICppdGVyYXRpb25zKi4gSW4gUiwgd2UgaGF2ZSB0aGUgYGZvcmAgbG9vcCwgdGhlIGByZXBlYXRgIGxvb3AsIGFuZCB0aGUgYHdoaWxlYCBsb29wIGF0IG91ciBkaXNwb3NhbC4gTGV0J3Mgc3RhcnQgd2l0aCB0aGUgYGZvcmAgbG9vcCAtIHByb2JhYmx5IHRoZSBtb3N0IGNvbW1vbiBvbmUuDQoNCmBgYHtyIGVjaG8gPSBUfQ0KIyAtIGhvdyBtYW55IGZpbGVzIHRvIHJlYWQgZnJvbSAvX2RhdGENCm51bV9maWxlcyA8LSBsZW5ndGgobGlzdC5maWxlcyhkYXRhRGlyKSkNCm51bV9maWxlcw0KYGBgDQpTbyB0aGVyZSBhcmUgZm91ciBgLmNzdmAgZmlsZXMgdGhlcmUuIE9rOg0KDQojIyMjIDEuMSBUaGUgYGZvcmAgbG9vcA0KDQpgYGB7ciBlY2hvID0gVH0NCiMgLSBsaXN0IGZpbGVzDQpmaWxlcyA8LSBsaXN0LmZpbGVzKGRhdGFEaXIpDQojIC0gaG93IG1hbnkgZmlsZXMgdGhlcmUgYXJlOg0KbnVtX2ZpbGVzIDwtIGxlbmd0aChsaXN0LmZpbGVzKGRhdGFEaXIpKQ0KIyAtIHByZXBhcmUgYSBsaXN0IHRvIHN0b3JlIHRoZSBkYXRhZnJhbWVzDQpkYXRhU2V0IDwtIGxpc3QoKQ0KIyAtIGxvYWQgYWxsIGRhdGFzZXRzLCBvbmUgYnkgb25lLCBpbiBhIGZvciBsb29wOg0KZm9yIChpIGluIDE6bnVtX2ZpbGVzKSB7DQogIGRhdGFTZXRbW2ldXSA8LSByZWFkLmNzdihwYXN0ZTAoZGF0YURpciwgZmlsZXNbaV0pLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IFQsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEYpDQp9DQpgYGANCg0KVGhlIGBkYXRhU2V0YCB2YXJpYWJsZSBpcyBub3cgYSBsaXN0IHdpdGggZm91ciBlbGVtZW50cywgZWFjaCBvZiB0aGVtIGVuY29tcGFzc2luZyBvbmUgZGF0YWZyYW1lOiB0aGUgcmVzdWx0IG9mIGEgYHJlYWQuY3N2YCBjYWxsIG92ZXIgYGZpbGVzWzFdYCwgYGZpbGVzWzJdYCwgYGZpbGVzWzNdYCwgYW5kIGBmaWxlc1s0XWAuIFdlIHdpbGwgZ2V0IGJhY2sgdG8gb3VyIGRhdGEgc29vbi4gTGV0J3MgZGlzY3VzcyB0aGUgYGZvcmAgbG9vcCBpbiBSIGZpcnN0OiBpdCBpcyBzaW1wbGUuDQoNClRoZSBgKGkgaW4gMTpudW1fZmlsZXMpYCBwYXJ0IG9mIHRoZSBgZm9yYCBsb29wIHN5bnRheCB0ZWxscyBSIHRoYXQgYGlgIG5lZWRzIHRvIHRha2UgdmFsdWVzIGZyb20gYGMoMSwyLDMsNClgIGV4YWN0bHkgaW4gdGhhdCBvcmRlciwgYmVjYXVzZToNCg0KYGBge3IgZWNobyA9IFR9DQoxOm51bV9maWxlcw0KYGBgDQpXZSB1c2UgdGhlIGBpYCB2YXJpYWJsZSBpbiB0aGUgYGZvcmAgbG9vcCB0byBzaW5nbGUgb3V0IGV4YWN0bHkgd2hhdCBmaWxlIG5hbWUgZnJvbSB0aGUgYGZpbGVzYCB2ZWN0b3IgZG8gd2UgbmVlZCBpbiBlYWNoIGl0ZXJhdGlvbjogdGhhdCBpcyB0aGUgYHBhc3RlMChkYXRhRGlyLCBmaWxlc1tpXWAgcGFydCB0aGF0IHByZXNlbnRzIHRoZSBmaXJzdCBhcmd1bWVudCB0byBgcmVhZC5jc3YoKWAuIFdoYXQgaXMgZm91bmQgYmV0d2VlbiBge2AgYW5kIGB9YCBpcyBhICoqY29kZSBibG9jayoqOiB0aGF0IGlzIHdoYXQgd2lsbCBiZSBleGVjdXRlZCBpbiBlYWNoIGl0ZXJhdGlvbiBvZiB0aGUgYGZvcmAgbG9vcCwgYWxsIHRoZSB3YXkgdW50aWwgYGkgPT0gbnVtX2ZpbGVzYCBpcyBgVFJVRWAuDQoNCkluIGVmZmVjdCwgUiBoYXMgc3RhcnRlZCBmcm9tIGBpID0gMWAgYW5kIGluY3JlYXNlZCBgaWAgdW50aWwgaXQgcmVhY2hlZCB0aGUgdmFsdWUgb2YgYG51bV9maWxlc2AsIGVhY2ggdGltZSBleGVjdXRpbmcgd2hhdCBpcyBmb3VuZCBpbiB0aGUgY29kZSBibG9jayBmb2xsb3dpbmcgdGhlIGRlZmluaXRpb24gb2YgdGhlIGBmb3JgIGxvb3AsIGFuZCB1c2luZyBgaWAgYXMgYSBwYXJhbWV0ZXIgdG8gYWx0ZXIgd2hhdCBgcmVhZC5jc3YoKWAgd2lsbCBkby4gSW4gZWFjaCBpdGVyYXRpb24sIGBpYCB3YXMgYWxzbyB1c2VkIHRvIGluZGV4IGFuIGVsZW1lbnQgb2YgdGhlIGBkYXRhU2V0YCBsaXN0OiBgZGF0YVNldFtbaV1dYC4gRmluYWxseSwgYGRhdGFTZXRgIGJlY2FtZSBhIGxpc3Qgd2l0aCBmb3VyIGVsZW1lbnRzLCBlYWNoIGJlaW5nIGEgZGF0YWZyYW1lIGhvbGRpbmcgb25lIGRhdGFzZXQgZnJvbSBvdXIgYF9kYXRhYCBkaXJlY3RvcnkuDQoNCmBGb3JgIGxvb3BzIGFyZSByZWFseSBzaW1wbGU6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KZm9yIChhIGluIDE6MTApIHByaW50KGEpDQpgYGANCg0KYGBge3IgZWNobyA9IFR9DQpuYW1lcyA8LSBjKCdNYXJpYScsICdBbm4nLCAnUGV0ZXInLCAnSm92YW4nLCAnSWxrZScpDQpmb3IgKHggaW4gMTpsZW5ndGgobmFtZXMpKSB7DQogIHByaW50KA0KICAgIHBhc3RlMCgiVGhpcyBpcyBhIGJlYXRpZnVsIG5hbWU6ICIsIA0KICAgICAgICAgICBuYW1lc1t4XSkNCiAgKQ0KfQ0KYGBgDQoNCkRvIHlvdSByZW1lbWJlciBgbGFwcGx5KClgPw0KDQojIyMjIDEuMiBUaGUgYGZvcmAgbG9vcCB2cy4gYGxhcHBseSgpYCBhbmQgd2hlbiBhcmUgdGhlIGBmb3JgIGxvb3BzIGluZWZmaWNpZW50IGluIFINCg0KYGBge3IgZWNobyA9IFR9DQpmaWxlcyA8LSBsaXN0LmZpbGVzKGRhdGFEaXIpDQpkYXRhU2V0IDwtIGxhcHBseShwYXN0ZTAoZGF0YURpciwgZmlsZXMpLCANCiAgICAgICAgICAgICAgICAgIHJlYWQuY3N2LCBoZWFkZXIgPSBULCBzdHJpbmdzQXNGYWN0b3JzID0gRikNCmBgYA0KDQpZZXMsIHdlIG5lZWQgYGZvcmAgbG9vcHMgaGVyZSBhbmQgdGhlcmUsIGJ1dCAqKnRoaXMgXl4gaXMgdGhlIFIgd2F5IG9mIGRvaW5nIHRoaW5ncyoqOg0KDQotIGBmaWxlc2AgaXMgYSB2ZWN0b3IgZW5jb21wYXNzaW5nIHRoZSBmaWxlbmFtZXMgb2YgdGhlIGAuY3N2YCBkYXRhc2V0cyB0aGF0IHdlIG5lZWQNCi0gYGxhcHBseSgpYCBpcyBhbiBSIGZ1bmN0aW9uIHRoYXQgYXBwbGllcyBhIGdpdmVuIGZ1bmN0aW9uIC0gYHJlYWQuY3N2KClgIC0gdG8gYWxsIGVsZW1lbnRzIG9mIHRoZSBsaXN0IG9yIGEgdmVjdG9yIGdpdmVuIGFzIGl0cyBmaXJzdCBhcmd1bWVudCAoYHBhc3RlMChkYXRhRGlyLCBmaWxlcylgIGluIHRoaXMgZXhhbXBsZSkNCi0gZWFjaCBmdW5jdGlvbidzIGNhbGwgcmVzdWx0IHdpbGwgYmUgYXNzaWduZWQgdG8gYW4gb3V0cHV0IGxpc3QgLSB0aGUgcmVzdWx0IG9mIGEgYGxhcHBseSgpYCBjYWxsDQotIGFuZCB0aGUgb3V0cHV0IGlzIGZpbmFsbHkgYXNzaWduZWQgdG8gYGRhdGFTZXRgLg0KDQpUaGVyZSBpcyBhIG15dGggc2F5aW5nIHRoYXQgYGZvcmAgbG9vcHMgaW4gUiBhcmUgc2xvdy4gV2VsbCwgdGhleSBjYW4gYmUuIEJ1dCB1c2luZyBgbGFwcGx5KClgIGluIGEgZnVuY3Rpb25hbCBwcm9ncmFtbWluZyBsYW5ndWFnZSBsaWtlIFIgaXMgbm90IGp1c3QgYSBtYXR0ZXIgb2YgZWZmaWNpZW5jeTogbG9vayBob3cgY29kZSB3aXRoIGBsYXBwbHkoKWAgaXMgZWxlZ2FudCBhbmQgY29uY2lzZSBpbiBjb21wYXJpc29uIHRvIGl0ZXJhdGlvbnMgbGlrZSB0aGUgYGZvcmAgbG9vcCENCg0KV2hlbiAqKm5vdCB0byoqIHVzZSB0aGUgYGZvcmAgbG9vcCBpbiBSIChpZiB5b3UgYXNrIG1lOiBuZXZlciwgaWYgeW91IGRvbid0IGhhdmUgdG8pPw0KDQpUaGUgdHlwaWNhbCBjb250ZXh0IHRoYXQgc2xvd3MgZG93biBSIGNvZGUgZXhlY3V0aW9uIGlzIHRoZSBvbmUgaW4gd2hpY2ggYSBkYXRhIHN0cnVjdHVyZSB3aWxsICoqZ3JvdyoqIGluc2lkZSB0aGUgbG9vcDoNCg0KYGBge3IgZWNobyA9IFR9DQpudW1iZXJzIDwtIG51bWVyaWMoKQ0KZm9yIChpIGluIDE6MTAwKSB7DQogIG51bWJlcnNbaV0gPC0gaQ0KfQ0KYGBgDQoNCkZpcnN0LCB0aGVyZSBpcyBhYnNvbHV0ZWx5IG5vIG5lZWQgdG8gZG8gc3VjaCB0aGluZ3MgaW4gYSB2ZWN0b3JpemVkIGxhbmd1YWdlIGxpa2UgUjoNCg0KYGBgIHtyIGVjaG8gPSBUfQ0KbnVtYmVycyA8LSAxOjEwMA0KYGBgDQoNCmlzIGVub3VnaCwgb2YgY291cnNlLiBUaGUgbW9yZSBpbXBvcnRhbnQgdGhpbmcgaXMgdGhhdCBncm93aW5nIGRhdGEgc3RydWN0dXJlcyBpbiBsb29wcyBzbG93cyBkb3duIHRoZSBjb2RlIGV4ZWN1dGlvbiBhbmQgc2lnbmlmaWNhbnRseSBpbiBzb21lIHNpdHVhdGlvbnMgKHdoZW4gdGhlIGRhdGEgc3RydWN0dXJlIGlzIHJlYWxseSBsYXJnZSwgZm9yIGV4YW1wbGUpLiBIb3cgY2FuIHdlIHVzZSBhIG1vcmUgZWZmaWNpZW50IGBmb3JgIGxvb3AgaWYgd2UgaGF2ZSB0byBncm93IGEgZGF0YSBzdHJ1Y3R1cmUgaW4gaXQ/IFRlbGwgUiBob3cgbXVjaCBtZW1vcnkgaXMgbmVlZGVkIHRvIGhvbGQgdGhlIHJlc3BlY3RpdmUgZGF0YSBzdHJ1Y3R1cmU6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KIyAtIGxpc3QgZmlsZXMNCmZpbGVzIDwtIGxpc3QuZmlsZXMoZGF0YURpcikNCiMgLSBob3cgbWFueSBmaWxlcz8NCm51bV9maWxlcyA8LSBsZW5ndGgobGlzdC5maWxlcyhkYXRhRGlyKSkNCiMgLSBwcmVwYXJlIGEgbGlzdCB0byBzdG9yZSB0aGUgZGF0YWZyYW1lcw0KZGF0YVNldCA8LSB2ZWN0b3IobW9kZSA9ICJsaXN0IiwgbGVuZ3RoID0gbnVtX2ZpbGVzKQ0KIyAtIGl0ZXJhdGU6DQpmb3IgKGkgaW4gMTpudW1fZmlsZXMpIHsNCiAgZGF0YVNldFtbaV1dIDwtIHJlYWQuY3N2KHBhc3RlMChkYXRhRGlyLCBmaWxlc1tpXSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gVCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRikNCn0NCmBgYA0KDQpXZSBoYXZlIHVzZWQgYHZlY3Rvcihtb2RlID0gImxpc3QiLCBsZW5ndGggPSBudW1fZmlsZXMpYCB0byBjcmVhdGUgYW4gZW1wdHkgbGlzdCBvZiBsZW5ndGggYG51bV9maWxlc2A6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KZW1wdHlMaXN0IDwtIHZlY3Rvcihtb2RlID0gImxpc3QiLCBsZW5ndGggPSA0KQ0KZW1wdHlMaXN0DQpgYGANCkFuZCBgTlVMTGAgbWVhbnMgKnRoaXMgZG9lcyBub3QgZXhpc3QqIGluIFIsIGRpZmZlcmVudCBmcm9tIGBOQWAgaW4gdmVjdG9ycyBhbmQgZGF0YWZyYW1lcyB3aGljaCBtZWFuczogd2UgZXhwZWN0IGFuZCBlbGVtZW50IGluIHRoaXMgcGxhY2UgYnV0IHdlIGRvIG5vdCBvYnNlcnZlIG9uZS4NCg0KIyMjIyAxLjMgQ29tcG9zZSBvbmUgZGF0YWZyYW1lIGZyb20gc2V2ZXJhbCBkYXRhZnJhbWVzIHN0b3JlZCBpbiBhIGxpc3Q6IGBSZWR1Y2UoKWANCg0KV2Uga25vdyB0aGF0IGBkYXRhU2V0YCBoYXMgZm91ciBlbGVtZW50cyBhbmQgdGhhdCBlYWNoIG9mIHRoZW0gaXMgYW4gYGRhdGEuZnJhbWVgOg0KDQpgYGB7ciBlY2hvID0gVH0NCmxhcHBseShkYXRhU2V0LCBjbGFzcykNCmBgYA0KDQpOb3csIHNheSB0aGF0IHRoZSBhbmFseXN0IG5lZWRzIHRvIHB1dCBhbGwgdGhlc2UgZGF0YWZyYW1lcyB0b2dldGhlciBpbiBhIHNpbmdsZSBkYXRhZnJhbWUgZm9yIHRoZSBwdXJwb3NlcyBvZiBzdGF0aXN0aWNhbCBtb2RlbGluZy4gVGFrZSBhIGxvb2sgYXQgdGhlIGZvbGxvd2luZyBjb2RlIGZpcnN0IGFuZCB0aGFuIHdlIHdpbGwgZGlzY3VzcyBpdCBpbiBkZXRhaWw6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KbGlzdGluZ3MgPC0gUmVkdWNlKHJiaW5kLCBkYXRhU2V0KQ0KZGltKGxpc3RpbmdzKQ0KYGBgDQpEbyB0aGUgZm91ciBkYXRhc2V0cyBoYXZlIGAzMTg1MmAgcm93cyBpbiB0b3RhbD8NCg0KYGBge3IgZWNobyA9IFR9DQpzdW0oc2FwcGx5KGRhdGFTZXQsIGZ1bmN0aW9uKHgpIGRpbSh4KVsxXSkpDQpgYGANClllcywgdGhleSBkbzogYGxpc3RpbmdzYCBpcyBub3cgdGhlIGRhdGFmcmFtZSB0aGF0IHdlIG5lZWQuIEJ1dCB3aGF0IHdhcyB0aGF0IGBSZWR1Y2UoKWAgdGhpbmc/DQoNCj4gIlJlZHVjZSgpIHRha2VzIGEgZnVuY3Rpb24gZiBvZiB0d28gYXJndW1lbnRzIGFuZCBhIGxpc3Qgb3IgdmVjdG9yIHggd2hpY2ggaXMgdG8gYmUg4oCYcmVkdWNlZOKAmSB1c2luZyBmLiBUaGUgZnVuY3Rpb24gaXMgZmlyc3QgY2FsbGVkIG9uIHRoZSBmaXJzdCB0d28gY29tcG9uZW50cyBvZiB4LCB0aGVuIHdpdGggdGhlIHJlc3VsdCBvZiB0aGF0IGFzIHRoZSBmaXJzdCBhcmd1bWVudCBhbmQgdGhlIHRoaXJkIGNvbXBvbmVudCBvZiB4IGFzIHRoZSBzZWNvbmQgYXJndW1lbnQsIHRoZW4gYWdhaW4gd2l0aCB0aGUgcmVzdWx0IG9mIHRoZSBzZWNvbmQgc3RlcCBhcyBmaXJzdCBhcmd1bWVudCBhbmQgdGhlIGZvdXJ0aCBjb21wb25lbnQgb2YgeCBhcyB0aGUgc2Vjb25kIGFyZ3VtZW50IGV0Yy4gVGhlIHByb2Nlc3MgaXMgY29udGludWVkIHVudGlsIGFsbCBlbGVtZW50cyBvZiB4IGhhdmUgYmVlbiBwcm9jZXNzZWQuIiAtIEZyb20gKipSOiBSZWR1Y2UoKSDigJMgYXBwbHnigJlzIGxlc3NlciBrbm93biBicm90aGVyKiosIHRoZSBbRGF0YSBTY2llbmNlIG1hZGUgaW4gU3dpdHplcmxhbmRdKGh0dHBzOi8vYmxvZy56aGF3LmNoL2RhdGFzY2llbmNlL3ItcmVkdWNlLWFwcGx5cy1sZXNzZXIta25vd24tYnJvdGhlci8pIGJsb2cuDQoNCkxldCcgc2VlOg0KDQpgYGB7ciBlY2hvID0gVH0NCm51bWJlcnMgPC0gYygxLCAyLCAzLCA0KQ0KUmVkdWNlKCdeJywgbnVtYmVycykNCmBgYA0KT2YgY291cnNlLCBiZWNhdXNlOiBgMV4yID0gMWAsIGAxXjMgPSAxYCwgYDFeNCA9IDFgLCBidXQgKippdCBkb2VzIHdvcmsgdGhhdCB3YXkqKiwgSSBhc3N1cmUgeW91Og0KDQpgYGB7ciBlY2hvID0gVH0NCm51bWJlcnMgPC0gYyg0LCAzLCAyLCAxKQ0KUmVkdWNlKCJeIiwgbnVtYmVycykNCmBgYA0KYmVjYXVzZSBgNF4zID0gNjRgLCBgNjReMiA9IDQwOTZgLCBhbmQgYDQwOTZeMSA9IDQwOTZgLiBJZiB5b3UgYXJlIGludGVyZXN0ZWQgdG8gc2VlIHRoZSBpbnRlcm1lZGlhdGUgcmVzdWx0cyBvZiBgUmVkdWNlKClgLCBzZXQgaXRzIGBhY2N1bXVsYXRlYCBhcmd1bWVudCB0byBgVFJVRWA6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KbnVtYmVycyA8LSBjKDQsIDMsIDIsIDEpDQpSZWR1Y2UoIl4iLCBudW1iZXJzLCBhY2N1bXVsYXRlID0gVCkNCmBgYA0KU28gd2hhdCBoYXBwZW5lZCBpbiBvdXIgYGxpc3RpbmdzIDwtIFJlZHVjZShyYmluZCwgZGF0YVNldClgIGNhbGwgaXMgdGhlIGZvbGxvd2luZzogKGEpIGBkYXRhU2V0YCBpcyBhIGxpc3Qgb2YgZm91ciBkYXRhZnJhbWVzLCAoYikgYHJiaW5kYCBpcyBhIGZ1bmN0aW9uIHRoYXQgdGFrZXMgdHdvIGFyZ3VtZW50cywgYm90aCBkYXRhZnJhbWVzLCBhbmQgcHV0cyB0aGVtIG9uIHRvcCBvZiBlYWNoIG90aGVyIChtaW5kIHRoZSBjb2x1bW4gbmFtZXMhKSwgc28gKGMpIGBSZWR1Y2UoKWAgYmVnYW4gYnkgYmluZGluZyB0aGUgZmlyc3QgdHdvIGVsZW1lbnRzIG9mIGBkYXRhU2V0YCB0b2dldGhlciwgdGhlbiB1c2VkIHRoZSByZXN1bHRpbmcgZGF0YWZyYW1lIGFzIGl0cyBuZXh0IGlucHV0IHRvZ2V0aGVyIHdpdGggdGhlIHRoaXJkIGVsZW1lbnQgb2YgYGRhdGFTZXRgLCBhbmQgZmluYWx5IHVzZWQgdGhhdCByZXN1bHRpbmcgZGF0YWZyYW1lIGFzIGl0cyBmaXJzdCBhcmd1bWVudCB0b2dldGhlciB3aXRoIHRoZSBmb3VydGggZWxlbWVudCBvZiBgZGF0YVNldGAgYXMgaXRzIHNlY29uZCBhcmd1bWVudCB0byBwcm9kdWNlIHRoZSBgbGlzdGluZ3NgLg0KDQoqKk5vdGUuKiogWW91IGhhdmUgbm90aWNlZCB0aGF0IEkgaGF2ZSB1c2VkIHRoZSBgXmAgb3BlcmF0b3IgaW4gUiBhcyBpZiBpdCB3ZXJlIGEgZnVuY3Rpb24gaW4gYFJlZHVjZSgnXicsIG51bWJlcnMpYC4gVGhhdCBpcyBiZWNhdXNlIGl0IGlzIGEgZnVuY3Rpb24gaW4gUiwgbG9vazoNCg0KYGBge3IgZWNobyA9IFR9DQonXicoMiwgMykNCmBgYA0KU2ltaWxhcmx5LCANCg0KYGBge3IgZWNobyA9IFR9DQonKycoMiwgMykNCmBgYA0KDQpUaGF0IGlzIHdoeSB3ZSBjYWxsIGl0IGEgKmZ1bmN0aW9uYWwqIHByb2dyYW1taW5nIGxhbmd1YWdlIC0gYW5kIGEgYmVhdXRpZnVsIG9uZSBpbmRlZWQuDQoNCiMjIyMgMS40IEEgY29uY2lzZSBvdmVydmlldyBvZiBzb21lIFIgZnVuY3Rpb25zIHVzZWQgaW4gZnVuY3Rpb25hbCBwcm9ncmFtbWluZw0KDQpIZXJlIGlzIGEgc2NoZW1hdGljIG92ZXJ2aWV3IG9mIHNvbWUgUiBmdW5jdGlvbnMgdGhhdCBhcmUgdHlwaWNhbGx5IHVzZWQgaW4gZnVuY3Rpb25hbCBwcm9ncmFtbWluZy4gVGhlIG9uZSBhdCB0aGUgYm90dG9tIC0gYE1hcChmLCAuLi4pYCAtIHdlIGhhdmUgbm90IHVzZWQgeWV0LiBCdXQgd2Ugd2lsbCwgYXQgc29tZSBwb2ludCwgYW5kIGl0IGlzIGFsc28gcHJldHR5IGludHVpdGl2ZSwgZS5nLiBmb3IgdHdvIGxpc3RzLCBgeGAsIGB5YCwgYW5kIGEgYmluYXJ5IGZ1bmN0aW9uIGBmKHgseWApLCBgTWFwKGYsIHgsIHkpYCByZXR1cm5zIGBmKHhbWzFdXSwgeVtbMV1dKSwgZih4W1syXV0sIHlbWzJdXSksIC4uLCBmKHhbW25dXSwgeVtbbl1dKWAuIGBNYXAoKWAgY2FuIHVzZSBuLWFyeSBmdW5jdGlvbnMsIGkuZS4gYGZgIGNhbiBoYXZlIGFzIG1hbnkgYXJndW1lbnRzIGFzIG5lZWRlZC4NCg0KIVtdKC4uL19pbWcvUzAzXzAxX0Z1bmN0aW9uYWxQcm9ncmFtbWluZy5qcGVnKQ0KDQojIyMjIDEuNSBFbnRlciBhIG5ldyBjb2x1bW4gaW50byBlYWNoIGRhdGFmcmFtZSBhbmQgY29tcG9zZTogYHN0cnNwbGl0KClgIGFuZCBgZ3N1YigpYA0KDQpGaW5hbGx5LCB0aGVyZSBpcyBvbmUgcHJvYmxlbSB3aXRoIGBsaXN0aW5nc2A6IHdlIGRvIGhhdmUgYWxsIHRoZSBkYXRhIGluIG9uZSBwbGFjZSwgYnV0IHdlIGRvIG5vdCBrbm93IGZyb20gd2hpY2ggb3JpZ2luYWwgZGF0YXNldCAoQW1zdGVyZGFtLCBBbnR3ZXJwLCBBc2hldmlsbGUsIEF0aGVucykgZG8gdGhlIGRhdGEgY29tZSBmcm9tLiBMZXQncyBmaXggdGhpcy4gUmVtZW1iZXIgdGhhdCB3ZSBoYXZlIHRoZSBmaWxlbmFtZXMgaW4gYGZpbGVzYDoNCg0KYGBge3IgZWNobyA9IFR9DQpmaWxlcw0KYGBgDQoNCldoYXQgaGFwcGVucyBpZi4uLg0KDQoNCmBgYHtyIGVjaG8gPSBUfQ0Kc3Ryc3BsaXQoZmlsZXNbMV0sIHNwbGl0ID0gIl8iKQ0KYGBgDQoNCk9rLCBzbyBgc3Ryc3BsaXQoKWAgdGFrZXMgYSBzdHJpbmcgKGBmaWxlc1sxXWApIGluIHRoaXMgZXhhbXBsZSkgYW5kIHNwbGl0cyBpdCB1cCB3aGVyZSBpdCBmaW5kcyB0aGUgY2hhcmFjdGVyIHByb3ZpZGVkIGluIGl0cyBgc3BsaXRgIGFyZ3VtZW50IChhbmQgdGhhdCB3b3VsZCBiZSBgIl8iYCBpbiB0aGlzIGV4YW1wbGUpIC0gYW5kIHJldHVybnMgYSBsaXN0Lg0KDQpPaywgYW5kIHdoYXQgaGFwcGVucyBpZi4uLg0KDQpgYGB7ciBlY2hvID0gVH0NCmdzdWIoIi5jc3YiLCAiIiwgc3Ryc3BsaXQoZmlsZXNbMV0sIHNwbGl0ID0gIl8iKVtbMV1dLCBmaXhlZCA9IFQpDQpgYGANCmBnc3ViKClgIC0gYSByZWxhdGl2ZSBvZiBgZ3JlcGwoKWAgd2lsbCBzdWJzdGl0dXRlIGl0cyBmaXJzdCBhcmd1bWVudCBieSBpdHMgc2Vjb25kIGFyZ3VtZW50IGluIGEgZ2l2ZW4gc3RyaW5nOg0KDQpgYGB7ciBlY2hvID0gVH0NCmdzdWIoIkEiLCAiRSIsICJBbWVsaWEiKQ0KYGBgDQpTbyANCg0KYGBge3IgZWNobyA9IFR9DQpnc3ViKCIuY3N2IiwgIiIsIHN0cnNwbGl0KGZpbGVzWzFdLCBzcGxpdCA9ICJfIilbWzFdXSwgZml4ZWQgPSBUKQ0KYGBgDQoNCndpbGwgZmlyc3QgYHN0cnNwbGl0YCB0aGUgYGZpbGVzWzFdYCBzdHJpbmcgYnkgIl8iLCBhbmQgd2UgdXNlIFtbMV1dIGluIG9yZGVyIHRvIGdyYWIgdGhlIHJlc3VsdCBiZWNhdXNlIGl0IHJldHVybnMgYSBsaXN0OyBgZ3N1YigpYCB0aGVuIHJlcGxhY2VzIGAiLmNzdiJgIHdpdGggYW4gZW1wdHkgc3RyaW5nIGAiImAsIGFuZCB3ZSBuZWVkIHRvIHNwZWNpZnkgYGZpeGVkID0gVGAgdG8gaW5mb3JtIGBnc3ViKClgIHRoYXQgd2UgZG8gbm90IHdpc2ggdGhlIGAuYCB0byBiZSBpbnRlcnByZXRlZCBhcyBgcmVnZXhgICh3aGVyZSBpdCBtZWFuczogKmFueSBjaGFyYWN0ZXIqKSBidXQgbGl0ZXJhbGx5IGFzIGEgYC5gIGNoYXJhY3Rlci4gVGhlIHJlbWFpbmluZyB0aGluZyB0byBkbyBpcyB0byBwaWNrIHVwIHRoZSBjaXR5IHdoaWNoIGlzIGZvdW5kIGluIHRoZSBzZWNvbmQgcG9zaXRpb24gb2YgdGhlIG91dHB1dCB2ZWN0b3I6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KZ3N1YigiLmNzdiIsICIiLCBzdHJzcGxpdChmaWxlc1sxXSwgc3BsaXQgPSAiXyIpW1sxXV0sIGZpeGVkID0gVClbMl0NCmBgYA0KQW5kIHdlIGNvdWxkIGhhdmUgYWNjb21wbGlzaGVkIHRoZSBzYW1lIGJ5Og0KDQpgYGB7ciBlY2hvID0gVH0NCmdzdWIoIi5jc3YiLCAiIiwgc3Ryc3BsaXQoZmlsZXNbMV0sIHNwbGl0ID0gIl8iKVtbMV1dWzJdLCBmaXhlZCA9IFQpDQpgYGANCmJlY2F1c2UgYHN0cnNwbGl0YCByZXR1cm5zIGEgbGlzdCBvZiBsZW5ndGggdHdvIChgW1sxXV0gJ2xpc3RpbmdzJyAnYW1zdGVyZGFtLmNzdidgIGluIHRoaXMgZXhhbXBsZSkuDQoNClNvIGhlcmUgaXMgaG93IHdlIGdldCB0byB0aGUgY2l0aWVzIHRoYXQgd2UgbmVlZCBmb3Igb3VyIGRhdGFzZXRzOg0KDQpgYGB7ciBlY2hvID0gVH0NCmNpdGllcyA8LSBzYXBwbHkoZmlsZXMsIGZ1bmN0aW9uKHgpIHsNCiAgZ3N1YigiLmNzdiIsICIiLCBzdHJzcGxpdCh4LCBzcGxpdCA9ICJfIilbWzFdXVsyXSwgZml4ZWQgPSBUKQ0KfSkNCmNpdGllcw0KYGBgDQpJdCBpcyBuYW1lZCB2ZWN0b3IsIHdpdGggbmFtZXMgcmV0dXJuZWQgZnJvbSB0aGUgYHNhcHBseSgpYCBjYWxsLiBXZSBjYW4gYHVubmFtZSgpYCB0aGF0Og0KDQpgYGB7ciBlY2hvID0gVH0NCmNpdGllcyA8LSB1bm5hbWUoY2l0aWVzKQ0KY2l0aWVzDQpgYGANCk5vdywgd2UgbmVlZCB0byBwcm9kdWNlIGBsaXN0aW5nc2Agd2l0aCBhIG5ldyBjb2x1bW46IGBsaXN0aW5ncyRjaXR5YC4gSG93IGRvIHdlIGFjY29tcGxpc2ggdGhhdD8NCg0KYGBge3IgZWNobyA9IFR9DQpmb3IgKGkgaW4gMTpsZW5ndGgoY2l0aWVzKSkgew0KICBkYXRhU2V0W1tpXV0kY2l0eSA8LSBjaXRpZXNbaV0NCn0NCmxpc3RpbmdzIDwtIFJlZHVjZShyYmluZCwgZGF0YVNldCkNCnRhYmxlKGxpc3RpbmdzJGNpdHkpDQpgYGANCg0KQW5kIG5vdyB3ZSBjYW4gYW5zd2VyIHF1ZXN0aW9ucyBsaWtlOiB3aGF0IGlzIHRoZSBhdmVyYWdlIHByaWNlIHBlciBBaXJCbkIgcG9zdCBwZXIgY2l0eSwgYW5kIHNpbWlsYXI6DQoNCmBgYHtyIGVjaG8gPSBULCBtZXNzYWdlID0gRn0NCmxpYnJhcnkoZHBseXIpDQoNCmxpc3RpbmdzICU+JSANCiAgc2VsZWN0KGNpdHksIHByaWNlKSAlPiUNCiAgZ3JvdXBfYnkoY2l0eSkgJT4lIA0KICBzdW1tYXJpc2UobWVhbl9wcmljZSA9IG1lYW4ocHJpY2UpKQ0KDQpgYGANCk9vb3BzLCB3aGF0IGlzIGB7ZHBseXJ9YD8gVGhhdCBjb21lcyBsYXRlci4gV2hhdCBJIHdhbnRlZCB0byBkbyB0aGlzIGluIGJhc2UgUj8NCg0KYGBge3IgZWNobyA9IFR9DQpjaXR5IDwtIHVuaXF1ZShsaXN0aW5ncyRjaXR5KQ0KbWVhbl9wcmljZSA8LSB1bm5hbWUoc2FwcGx5KGNpdHksIGZ1bmN0aW9uKHgpIHsNCiAgdyA8LSB3aGljaChsaXN0aW5ncyRjaXR5ID09IHgpDQogIHJldHVybihtZWFuKGxpc3RpbmdzJHByaWNlW3ddKSkNCn0pKQ0KZGF0YS5mcmFtZShjaXR5LCBtZWFuX3ByaWNlKQ0KYGBgDQpCdXQgdGhlIGJlYXV0eSBvZiB7ZHBseXJ9ISBZb3Ugd2lsbCBzZWUuLiENCg0KIyMjIDIgQmV5b25kIGBmb3JgOiBgd2hpbGVgIGFuZCBgcmVwZWF0YCBsb29wcywgYG5leHRgIGFuZCBgYnJlYWtgDQoNCkFzIG1hbnkgb3RoZXIgcHJvZ3JhbW1pbmcgbGFuZ3VhZ2VzLCBSIG1ha2VzIHVzZSBvZiBgd2hpbGVgIGxvb3BzIGFuZCBgcmVwZWF0YCBsb29wcyBhcyB3ZWxsLiBMZXQncyBzZWUgaG93IGRvIHRoZXkgZGlmZmVyIGZyb20gdGhlIGBmb3JgIGxvb3AgYnkgbWVhbnMgb2YgdmVyeSBzaW1wbGUgZXhhbXBsZXMgb25seTogDQoNCmBgYHtyIGVjaG8gPSBUfQ0KY291bnRlciA8LSAwDQp3aGlsZSAoY291bnRlciA8PSAxMDApIHsNCiAgY291bnRlciA8LSBjb3VudGVyICsgMQ0KICBpZiAoY291bnRlciAlJSAxMCA9PSAwKSB7DQogICAgcHJpbnQoY291bnRlcikNCiAgfQ0KfQ0KYGBgDQoNCkluIHRoZSBgd2hpbGVgIGxvb3AsIHdlIGJlZ2luIGJ5IGNoZWNraW5nIGlmIHRoZSBsb29wICpleGl0IGNvbmRpdGlvbiogaXMgc2F0aXNmaWVkICpiZWZvcmUqIGV4ZWN1dGluZyB0aGUgY29kZSBibG9jayBpbiBhbiBpdGVyYXRpb24uIFdlIGJlZ2FuIGJ5IHNldHRpbmcgYGNvdW50ZXJgIHRvIHplcm87IHRoZW4gd2UgYXNrIGlmIGl0IGlzIGxlc3Mgb3IgZXF1YWwgdGhhbiBgMTAwYCwgYW5kIGlmIHRoYXQgY29uZGl0aW9uIGlzIHRydWUgd2UgZXhlY3V0ZSB0aGUgY29kZSBibG9jaywgaW5jcmVhc2UgdGhlIHZhbHVlIG9mIGBjb3VudGVyYCBvbmx5ICppbnNpZGUqIHRoZSBibG9jay4NCg0KV2UgaGF2ZSBhbHNvIHVzZWQgb3VyIGZpcnN0IGBpZmAgaW4gUjoNCg0KYGBge3IgZWNobyA9IFR9DQppZiAoY291bnRlciAlJSAxMCA9PSAwKSB7DQogICAgcHJpbnQoY291bnRlcikNCn0NCmBgYA0KDQpBbmQgdGhpcyBleHByZXNzaW9uIGRvZXMgbm90IHByaW50IGFueXRoaW5nIGJlY2F1c2UgYGNvdW50ZXJgIGhhcyBhbHJlYWR5IHJlYWNoZWQgYDEwMWAgaW4gdGhlIGB3aGlsZWAgbG9vcCAod2h5PyksIHdoaWxlIHRoZSBjb25kaXRpb24gYGNvdW50ZXIgJSUgMTAgPT0gYCBzcGVjaWZpZXMgdGhhdCBvbmx5IGlmIHRoZSByZW1haW5kZXIgYWZ0ZXIgZGl2aWRpbmcgYGNvdW50ZXJgIHdpdGggYDEwYCBpcyBlcXVhbCB0byB6ZXJvLg0KDQpUaGUgYHJlcGVhdGAgbG9vcCwgdG8gdGhlIGNvbnRyYXJ5LCBiZWdpbnMgYnkgZXhlY3V0aW5nIHdoYXQgaXMgaW4gaXRzIGNvZGUgYmxvY2sgYW5kIHN0b3BzIG9ubHkgaWYgYSBjZXJ0YWluIGNvbmRpdGlvbiBpbiB0aGUgYmxvY2sgaXMgc2F0aXNmaWVkOg0KDQpgYGB7ciBlY2hvID0gVH0NCmNvdW50ZXIgPC0gMA0KcmVwZWF0IHsNCiAgY291bnRlciA8LSBjb3VudGVyICsgMQ0KICBpZiAoY291bnRlciAlJSAxMCA9PSAwKSB7DQogICAgcHJpbnQoY291bnRlcikNCiAgfQ0KICMgLSBleGl0IGNvbmRpdGlvbjoNCiAgaWYgKGNvdW50ZXIgPiAxMDApIHsNCiAgICBicmVhaw0KICB9DQp9DQpgYGANCg0KUmVtZW1iZXI6IHlvdSAqKmhhdmUgdG8qKiBgYnJlYWtgIHRoZSBgcmVwZWF0YCBsb29wIGluIFIgc29tZWhvdzogc3BlY2lmeSBhIGNvbmRpdGlvbiwgYW5kIGlmIHRoZSBjb25kaXRpb24gaXMgc2F0aXNmaWVkLCBlbmQgdGhlIGxvb3AgZXhlY3V0aW9uIGJ5IGBicmVha2AhIFVubGVzcyB5b3UgYXJlIGFibGUgdG8gYnJlYWsgaXQgd2lsbCBydW4gRk9SRVZFUiBhbmQgaW5mbGF0ZSB0aGUgVW5pdmVyc2Ugd2l0aCBjb21wdXRpbmchDQoNCldlIGNhbiBhbHNvIHVzZSBgYnJlYWtgIGluIGEgYGZvcmAgbG9vcCB0byBleGl0IGFsdG9nZXRoZXI6IA0KDQpgYGB7ciBlY2hvID0gVH0NCmZvciAoY291bnRlciBpbiAxOjEwMCkgew0KICBpZiAoY291bnRlciAlJSAxMCA9PSAwKSB7DQogICAgcHJpbnQoY291bnRlcikNCiAgfQ0KICAjIC0gZXhpdCBjb25kaXRpb24NCiAgaWYgKGNvdW50ZXIgPiA1MCkgew0KICAgIGJyZWFrDQogIH0NCn0NCmBgYA0KVGhlIGxvb3Agd291bGQgaXRlcmF0ZSB1bnRpbCBgY291bnRlcmAgcmVhY2hlcyBgMTAwYCwgYXMgc3BlY2lmaWVkIGluIGBmb3IgKGNvdW50ZXIgaW4gMToxMDApYCwgYnV0IHdlIGhhdmUgaW50cm9kdWNlZCBhbiBleGl0IGNvbmRpdGlvbiBieSBgaWYgKGNvdW50ZXIgPiA1MCkge2JyZWFrfWAgc28gaXQgZGlkIG5vdC4NCg0KRmluYWxseSwgdGhlcmUgaXMgYG5leHRgLCB0ZWxsaW5nIHRoZSBsb29wIHRvIGFiYW5kb24gdGhlIGV4ZWN1dGlvbiBvZiB0aGUgY3VycmVudCBpdGVyYXRpb24gYW5kIHN0YXJ0IHRoZSBuZXh0IG9uZSBpZiBzb21lIGNvbmRpdGlvbiBpcyBzYXRpc2ZpZWQ6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KZm9yIChjb3VudGVyIGluIDE6MTAwKSB7DQogIA0KICBpZiAoIShjb3VudGVyICUlIDEwID09IDApKSB7DQogICAgbmV4dA0KICB9IGVsc2Ugew0KICAgIHByaW50KGNvdW50ZXIpDQogIH0NCiAgDQogIHByaW50KCJSZW1haW5kZXIgaXMgemVybyEiKQ0KICANCn0NCmBgYA0KDQpUaGUgcmVzdWx0IGlzIHNpbWlsYXIgdG8gd2hhdCB3ZSBoYXZlIHNlZW4gYmVmb3JlLCBidXQgdGhlIGNvZGUgaXMgbW9kaWZpZWQuIFRoZSBgIWAgdW5hcnkgb3BlcmF0b3IgaW4gUiBpcyAqbmVnYXRpb24qLCBzbyBgKCEoY291bnRlciAlJSAxMCA9PSAwKSlgIG1lYW5zOiB0aGUgcmVtYWluZGVyIG9mIGRpdmlzaW9uIGJ5IHRlbiBvZiBgY291bnRlcmAgaXMgbm90IGVxdWFsIHRvIHplcm8uIEFuZCBpZiB0aGF0IGlzIHNvLCB3ZSB3YW50IHRvIHNraXAgdGhlIGN1cnJlbnQgaXRlcmF0aW9uIChhbmQgdGhhdCBpcyB3aHkgYHByaW50KCJSZW1haW5kZXIgaXMgemVybyEiKWAgZG9lcyBub3QgZXhlY3V0ZSkgYW5kIHN0YXJ0IHRoZSBuZXh0IG9uZS4gT2YgY291cnNlLCBgYnJlYWtgIGFuZCBgbmV4dGAgY2FuIGJlIGNvbWJpbmVkIGluIGEgc2luZ2xlIGxvb3AuDQoNCkFsc28sIHdlIGhhdmUgdXNlZCB0byBgaWYgLi4uIGVsc2VgIHN5bnRheCB0byBtYWtlIGEgZGVjaXNpb24gaW4gUi4gV2Ugd2lsbCBuZXh0IGRpc2N1c3MgZGVjaXNpb25zIGluIGNvbnRyb2wgZmxvdyBtb3JlIGNsb3NlbHkuDQoNCiMjIyAzIERlY2lzaW9uczogYGlmLi4uZWxzZWAsIGBzd2l0Y2goKWAsIGFuZCBgaWZlbHNlKClgDQoNCiMjIyMgMy4xIGBpZi4uLmVsc2VgIGlzIHNpbXBsZQ0KDQpUaGUgdXNhZ2Ugb2YgYGlmIC4uLiBlbHNlYCBpbiBSIGlzIHJlYWxseSBzaW1wbGUuIExldCdzIGJlZ2luIHdpdGg6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KbnVtX3Jvd3MgPC0gZGltKGxpc3RpbmdzKVsxXQ0KDQppZiAobnVtX3Jvd3MgPj0gMTAwMDApIHsNCiAgcHJpbnQoIkxpc3RpbmdzIGhhdmUgbW9yZSB0aGFuIDEwLDAwMCByb3dzISIpDQp9IGVsc2Ugew0KICBwcmludCgiTGlzdGluZ3MgaXMgYSB2ZXJ5IHNtYWxsIGRhdGFzZXQuIikNCn0NCmBgYA0KDQpUaGUgYGlmIC4uLiBlbHNlYCBjb25zdHJ1Y3RzIGNhbiBiZSBuZXN0ZWQ6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KbnVtX3Jvd3MgPC0gZGltKGxpc3RpbmdzKVsxXQ0KbnVtX2NvbHMgPC0gZGltKGxpc3RpbmdzKVsyXQ0KDQppZiAobnVtX3Jvd3MgPj0gMTAwMDApIHsNCiAgcHJpbnQoIkxpc3RpbmdzIGhhdmUgbW9yZSB0aGFuIDEwLDAwMCByb3dzISIpDQogIGlmIChudW1fY29scyA+IDEwKSB7DQogICAgcHJpbnQoIkFuZCBpdCBoYXMgbW9yZSB0aGFuIHRlbiBjb2x1bW5zISIpDQogIH0gZWxzZSB7DQogICAgcHJpbnQoIkJ1dCBpdCBoYXMgbGVzcyB0aGFuIHRlbiBjb2x1bW5zISIpDQogIH0NCn0gZWxzZSB7DQogIHByaW50KCJMaXN0aW5ncyBpcyBhIHZlcnkgc21hbGwgZGF0YXNldC4iKQ0KICBpZiAobnVtX2NvbHMgPiAxMCkgew0KICAgIHByaW50KCJBbmQgaXQgaGFzIG1vcmUgdGhhbiB0ZW4gY29sdW1ucyEiKQ0KICB9IGVsc2Ugew0KICAgIHByaW50KCJCdXQgaXQgaGFzIGxlc3MgdGhhbiB0ZW4gY29sdW1ucyEiKQ0KICB9DQp9DQoNCmBgYA0KQW5kIHdlIGNhbiBjaGFpbiBgaWYgLi4uIGVsc2VgIGluZGVmaW5pdGVseToNCg0KYGBge3IgZWNobyA9IFR9DQp0aGlzX251bWJlciA8LSA1DQppZiAodGhpc19udW1iZXIgPiAxMCkgew0KICBwcmludCgiVGhpcyBudW1iZXIgaXMgbGVzcyB0aGFuIHRlbi4uLiIpDQp9IGVsc2UgaWYgKHRoaXNfbnVtYmVyIDwgNSkgew0KICBwcmludCgiVGhpcyBudW1iZXIgaXMgbGVzcyB0aGFuIGZpdmUuIikNCn0NCmBgYA0KDQpOb3RoaW5nIGhhcHBlbnMsIG9mIGNvdXJzZSwgYmVjYXVzZSBgdGhpc19udW1iZXJgIGlzIGV4YWN0bHkgZml2ZS4gSG93ZXZlciwNCg0KYGBge3IgZWNobyA9IFR9DQp0aGlzX251bWJlciA8LSA1DQppZiAodGhpc19udW1iZXIgPiAxMCkgew0KICBwcmludCgiVGhpcyBudW1iZXIgaXMgbGVzcyB0aGFuIHRlbi4uLiIpDQp9IGVsc2UgaWYgKHRoaXNfbnVtYmVyIDw9IDUpIHsNCiAgcHJpbnQoIlRoaXMgbnVtYmVyIGlzIGxlc3Mgb3IgZXF1YWwgdG8gZml2ZS4iKQ0KfQ0KYGBgDQojIyMjIDMuMiBgc3dpdGNoKClgIGZvciBtdWx0aXBsZSBjaGVja3MgKyBgaWZlbHNlKClgIGZvciBiaW5hcnkgZGVjaXNpb25zDQoNCklzIGB0aGlzX2FuaW1hbGAgYSBkb2csIGFuIGVsZXBoYW50LCBhIGNhdCwgb3IgYSB0aWdlcj8NCg0KYGBge3IgZWNobyA9IFR9DQp0aGlzX2FuaW1hbCA8LSAndGlnZXInDQoNCnN3aXRjaCh0aGlzX2FuaW1hbCwNCiAgICAgICAiZG9nIiA9ICJJdCdzIGEgZG9nISIsDQogICAgICAgImVsZXBoYW50IiA9ICJJdCdzIGFuIGVsZXBoYW50ISIsIA0KICAgICAgICJjYXQiID0gIk1lb3chIiwgDQogICAgICAgInRpZ2VyIiA9ICJBIHRpZ2VyPyBJbiBBZnJpY2E/IikNCmBgYA0KDQpMZXQncyBhbmFseXplIGhvdyB0aGlzIHdvcmtzLiBGaXJzdCwgYHRoaXNfYW5pbWFsYCB3YXMgc2V0IHRvIGAndGlnZXInYC4gVGhlbiBpdCB3YXMgdXNlZCBhcyBhbiBleHByZXNzaW9uIGluIGBzd2l0Y2goKWAgd2hpY2ggc2VhcmNoZXMgdGhyb3VnaCB0aGUgbGlzdCBvZiBwcm92aWRlZCBhbHRlcm5hdGl2ZXMgYW5kIHJldHVybnMgdGhlIHJlc3VsdCBmcm9tIHRoZSBtYXRjaC4gSW4gdGhpcyBjYXNlLCBhIG1hdGNoIGJldHdlZW4gYHRoaXNfYW5pbWFsYCBhbmQgYCJ0aWdlciJgIGRldGVybWluZWQgdGhhdCB0aGUgcmVzcG9uc2Ugc2hvdWxkIGJlIGAiQSB0aWdlcj8gSW4gQWZyaWNhPyJgDQoNCkxldCdzIGludm9rZSBhIGAiTWVvdyFgIHJlc3BvbnNlIGZyb20gYSBgImNhdCJgOg0KDQpgYGB7ciBlY2hvID0gVH0NCnRoaXNfYW5pbWFsIDwtICdjYXQnDQpzd2l0Y2godGhpc19hbmltYWwsDQogICAgICAgZG9nID0gIkl0J3MgYSBkb2chIiwNCiAgICAgICBlbGVwaGFudCA9ICJJdCdzIGFuIGVsZXBoYW50ISIsIA0KICAgICAgIGNhdCA9ICJNZW93ISIsIA0KICAgICAgIHRpZ2VyID0gIkEgdGlnZXI/IEluIEFmcmljYT8iKQ0KYGBgDQpBbmQgbGV0J3MgdHJ5IHdpdGggc29tZSBudW1iZXJzOg0KDQpgYGB7ciBlY2hvID0gVH0NCnRoaXNfbnVtYmVyIDwtIDENCnN3aXRjaCh0aGlzX251bWJlciwNCiAgICAgICAiSXQncyB0aGUgZmlyc3Qgb25lISIsDQogICAgICAgIkl0J3MgdGhlIHNlY29uZCBvbmUhIiwgDQogICAgICAgIkl0J3MgdGhlIHRoaXJkIG9uZSBvbmUhIiwNCiAgICAgICAiSXQncyB0aGUgZm91cnRoIG9uZSEiKQ0KYGBgDQoNCmBgYHtyIGVjaG8gPSBUfQ0KdGhpc19udW1iZXIgPC0gNA0Kc3dpdGNoKHRoaXNfbnVtYmVyLA0KICAgICAgICJJdCdzIHRoZSBmaXJzdCBvbmUhIiwNCiAgICAgICAiSXQncyB0aGUgc2Vjb25kIG9uZSEiLCANCiAgICAgICAiSXQncyB0aGUgdGhpcmQgb25lIG9uZSEiLA0KICAgICAgICJJdCdzIHRoZSBmb3VydGggb25lISIpDQpgYGANCllvdSBjYW4gdXNlIGNvZGUgYmxvY2tzIGluIGBzd2l0Y2goKWAsIG9mIGNvdXJzZToNCg0KYGBge3IgZWNobyA9IFR9DQpzb21lX2V4cHJlc3Npb24gPSAnaGV5Jw0Kc3dpdGNoKHNvbWVfZXhwcmVzc2lvbiwgDQogICAgICAgaGV5ID0geyANCiAgICAgICAgIHByaW50KDIgKyAyKQ0KICAgICAgICAgcHJpbnQoJ0hleSEnKSANCiAgICAgICAgIH0sDQogICAgICAgaGkgPSB7IA0KICAgICAgICAgcHJpbnQoNSArIDUpDQogICAgICAgICBwcmludCgnSGkhJykNCiAgICAgICAgIH0sDQogICAgICAgew0KICAgICAgICAgcHJpbnQoNiAqIDMpDQogICAgICAgICBwcmludCgnRGVmYXVsdCBjYXNlIScpDQogICAgICAgICB9DQogICAgICAgKQ0KYGBgDQpUaGUgZGVmYXVsdCBjYXNlIGlzIHRyaWdnZXJlZCBpZiBgc3dpdGNoKClgIGNhbm5vdCBmaW5kIGEgbWF0Y2g6DQoNCmBgYHtyIGVjaG8gPSBUfQ0Kc29tZV9leHByZXNzaW9uID0gJ0NpYW8nDQpzd2l0Y2goc29tZV9leHByZXNzaW9uLCANCiAgICAgICBoZXkgPSB7IA0KICAgICAgICAgcHJpbnQoMiArIDIpDQogICAgICAgICBwcmludCgnSGV5IScpIA0KICAgICAgICAgfSwNCiAgICAgICBoaSA9IHsgDQogICAgICAgICBwcmludCg1ICsgNSkNCiAgICAgICAgIHByaW50KCdIaSEnKQ0KICAgICAgICAgfSwNCiAgICAgICB7DQogICAgICAgICBwcmludCg2ICogMykNCiAgICAgICAgIHByaW50KCdEZWZhdWx0IGNhc2UhJykNCiAgICAgICAgIH0NCiAgICAgICApDQpgYGANCkEgcHJhY3RpY2FsIGFwcGxpY2F0aW9uIG9mIGBzd2l0Y2goKWA/IExldCdzIGNoZWNrIGlmIGFsbCBkYXRhZnJhbWVzIGluIGBkYXRhU2V0YCBoYXZlIHRoZSBzYW1lIGNvbHVtbiBuYW1lcyBhbmQgcmVwb3J0IGFjY29yZGluZ2x5Og0KDQpgYGB7ciBlY2hvID0gVH0NCmNvbHVtbnMgPC0gbGFwcGx5KGRhdGFTZXQsIGNvbG5hbWVzKQ0KY29sdW1ucw0KYGBgDQoNCkxlYXJuIGFib3V0IGBpZGVudGljYWwoKWA6DQoNCmBgYHtyIGVjaG8gPSBUfQ0KaWRlbnRpY2FsKGNvbHVtbnNbWzFdXSwgY29sdW1uc1tbMl1dKQ0KYGBgDQoNCkFyZSBhbGwgcGFpcnMgaW4gYGNvbHVtbnNgIGlkZW50aWNhbD8gTGVhcm4gYWJvdXQgYGNvbWJuKClgOg0KDQpgYGB7ciBlY2hvID0gVH0NCmNvbWJuKDE6NCwgbSA9IDIpDQpgYGANCkxldCdzIGNvb3JkaW5hdGUgbm93Og0KDQpgYGB7ciBlY2hvID0gVH0NCnNlYXJjaCA8LSBjb21ibigxOmxlbmd0aChjb2x1bW5zKSwgMikNCmFwcGx5KHNlYXJjaCwgMiwgZnVuY3Rpb24oeCkgew0KICBpZGVudGljYWwoY29sdW1uc1t4WzFdXSwgY29sdW1uc1t4WzJdXSkNCn0pDQpgYGANClRoaXMgd2FzIG91ciBmaXJzdCB1c2FnZSBvZiBgYXBwbHkoKWA6IHRha2UgYSBtYXRyaXggbGlrZSBgc2VhcmNoYCwgYXBwbHkoYFhgLCBgTUFSR0lOYCwgYEZVTmApIHdoZXJlIHdlIGFwcGx5IGBGVU5gIGFjcm9zcyByb3dzIHdpdGggYE1BUkdJTiA9IDFgIGFuZCBhY3Jvc3MgY29sdW1ucyB3aXRoIGBNQVJHSU4gPSAyYC4gVG8gb2J0YWluIHJvdyBzdW1zLCBmb3IgZXhhbXBsZToNCg0KYGBge3IgZWNobyA9IFR9DQphcHBseShzZWFyY2gsIDEsIHN1bSkNCmBgYA0KQW5kIHRvIG9idGFpbiBzdW1zIGFjcm9zcyB0aGUgY29sdW1uczoNCg0KYGBge3IgZWNobyA9IFR9DQphcHBseShzZWFyY2gsIDIsIHN1bSkNCmBgYA0KQW5kIG9mIGNvdXJzZSBSIGhhcyBmdW5jdGlvbnMgZm9yIHRoaXM6IGByb3dTdW1zKClgIGFuZCBgY29sU3VtcygpYCAtIHlvdSBjYW4gdHJ5IHRoZW0gb3V0IGlmIHlvdSB3aXNoLg0KDQpTbywgbGV0J3MgcmVwb3J0IGlmIGFsbCBvZiB0aGUgY29sdW1uIG5hbWVzIG9mIHRoZSBkYXRhZnJhbWVzIGluIGBkYXRhU2V0YCBhcmUgaWRlbnRpY2FsOg0KDQpgYGB7ciBlY2hvID0gVH0NCmNvbHVtbnMgPC0gbGFwcGx5KGRhdGFTZXQsIGNvbG5hbWVzKQ0Kc2VhcmNoIDwtIGNvbWJuKDE6bGVuZ3RoKGNvbHVtbnMpLCAyKQ0KY2hlY2sgPC0gYXBwbHkoc2VhcmNoLCAyLCBmdW5jdGlvbih4KSB7DQogIGlkZW50aWNhbChjb2x1bW5zW3hbMV1dLCBjb2x1bW5zW3hbMl1dKQ0KfSkNCm51bV9wYWlycyA8LSBkaW0oc2VhcmNoKVsyXQ0KZGF0YUZyYW1lc0NoZWNrIDwtIGlmZWxzZShudW1fcGFpcnMgPT0gc3VtKGNoZWNrKSwgMSwgMikNCnN3aXRjaChkYXRhRnJhbWVzQ2hlY2ssIA0KICAgICAgICJBbGwgZGF0YWZyYW1lcyBoYXZlIHRoZSBzYW1lIGNvbHVtbnM7IHlvdSBjYW4gcHJvY2VlZCB3aXRoIGNvZGUgZXhlY3V0aW9uLiIsIA0KICAgICAgICJJbmNvbnNpc3RlbnQgY29sdW1uIG5hbWVzOyB0cnkgdXNpbmcgZGF0YXRhYmxlOjpyYmluZGxpc3QoKSB3aXRoIGZpbGwgPSBULiIpDQpgYGANCioqTk9URS4qKiB0aGUgYGlmZWxzZSgpYCBmdW5jdGlvbiBpbiBSIGlzIG5ldy4gSXQgaXMgYSB2ZWN0b3JpemVkIGZ1bmN0aW9uIHRoYXQgZmlyc3QgY2hlY2tzIGZvciB0aGUgY29uZGl0aW9uIGluIGl0cyBmaXJzdCBhcmd1bWVudCAod2hpY2ggbmVlZHMgdG8gZXZhbHVhdGUgdG8gYFRSVUVgIG9yIGBGQUxTRWApLCBhbmQgdGhlbiBpZiBgVFJVRWAgZXhlY3V0ZXMgdGhlIGV4cHJlc3Npb24gZm91bmQgaW4gaXRzIHNlY29uZCBhcmd1bWVudCwgYW5kIG90aGVyd2lzZSBleGVjdXRlcyB3aGF0IGlzIGZvdW5kIGluIGl0cyB0aGlyZCBhcmd1bWVudDoNCg0KYGBge3IgZWNobyA9IFR9DQppZmVsc2UoMTAgPCA1LCANCiAgICAgICAiSSBkbyBub3QgdW5kZXJzdGFuZCBiYXNpYyBhcml0aG1ldGljcy4iLCANCiAgICAgICAiT2sgSSBnb3QgYXQgbGVhc3QgdGhhdCBvbmUgcmlnaHQuIg0KICAgICAgICkNCmBgYA0KKipOT1RFLioqIE9idmlvdXNseSwgd2UgY291bGQgaGF2ZSB1c2VkIGp1c3QgYGlmZWxzZSgpYCBmb3IgdGhpcyB0YXNrOg0KDQpgYGB7ciBlY2hvID0gVH0NCmNvbHVtbnMgPC0gbGFwcGx5KGRhdGFTZXQsIGNvbG5hbWVzKQ0Kc2VhcmNoIDwtIGNvbWJuKDE6bGVuZ3RoKGNvbHVtbnMpLCAyKQ0KY2hlY2sgPC0gYXBwbHkoc2VhcmNoLCAyLCBmdW5jdGlvbih4KSB7DQogIGlkZW50aWNhbChjb2x1bW5zW3hbMV1dLCBjb2x1bW5zW3hbMl1dKQ0KfSkNCm51bV9wYWlycyA8LSBkaW0oc2VhcmNoKVsyXQ0KaWZlbHNlKG51bV9wYWlycyA9PSBzdW0oY2hlY2spLCANCiAgICAgICAiQWxsIGRhdGFmcmFtZXMgaGF2ZSB0aGUgc2FtZSBjb2x1bW5zOyB5b3UgY2FuIHByb2NlZWQgd2l0aCBjb2RlIGV4ZWN1dGlvbi4iLCANCiAgICAgICAiSW5jb25zaXN0ZW50IGNvbHVtbiBuYW1lczsgdHJ5IHVzaW5nIGRhdGF0YWJsZTo6cmJpbmRsaXN0KCkgd2l0aCBmaWxsID0gVC4iKQ0KYGBgDQoNCg0KLS0tDQoNCioqU1VNTUFSWS4qKiBXZSBoYXZlIGxlYXJuZWQgYWJvdXQgdGhlIGJhc2ljcyBvZiBSIHByb2dyYW1taW5nIGluIHRoaXMgc2Vzc2lvbi4gVGhlIGZvY3VzIHdhcyB0d29mb2xkOiAoYSkgd2UgaGF2ZSBsZWFybmVkIGFib3V0IHRoZSBjb250cm9sIGZsb3csIHdoaWNoIGlzIHNpbWlsYXIgaW4gUiBhbmQgaW4gYWxtb3N0IGFueSBvdGhlciBwcm9ncmFtbWluZyBsYW5ndWFnZTogYGZvcmAsIGB3aGlsZWAsIGByZXBlYXRgIGxvb3BzLCBhbmQgZGVjaXNpb24gbWFraW5nIHdpdGggYGlmIC4uLiBlbHNlYCwgYHN3aXRjaCgpYCwgYW5kIGBpZmVsc2UoKWA7IG9uIHRoZSBvdGhlciBoYW5kLCB3ZSBoYXZlIHRvdWNoZWQgdXBvbiBmdW5jdGlvbmFsIHByb2dyYW1taW5nIGluIFIgd2l0aCBgYXBwbHkoKWAsIGBsYXBwbHkoKWAsIGBzYXBwbHkoKWAsIGBSZWR1Y2UoKWAuIERvIG5vdCBmb3JnZXQ6IHdlIHJlYWxseSBsb3ZlIGZ1bmN0aW9uYWwgcHJvZ3JhbW1pbmcgaW4gUi4gSXQgbWFrZXMgdGhlIGNvZGUgbW9yZSBjb25zaXN0ZW50IGFuZCBlbGVnYW50LCBpZiBhbnl0aGluZy4gRnVuY3Rpb25hbCBwcm9ncmFtbWluZyBpcyBwZXJmZWN0bHkgc3VpdGVkIGZvciBEYXRhIFNjaWVuY2UgcHJvamVjdHMgYmVjYXVzZSBpdCBhbGxvd3MgdXMgdG8gZm9ybXVsYXRlIGFuZCBzb2x2ZSB0aGUgcHJvYmxlbSBhcyBhIGNoYWluIG9mIGZ1bmN0aW9uYWwgdHJhbnNmb3JtYXRpb25zIGFjcm9zcyB0aGUgaW5wdXQgZGF0YS4gQW4gaWRlYWwgRGF0YSBTY2llbmNlIGNvZGUgLSBub3Qgc29tZXRoaW5nIHRoYXQgY2FuIGJlIGFsd2F5cyBhY2hpZXZlZCAtIGlzIG5vdGhpbmcgbW9yZSB0aGFuIHRoYXQuDQoNCi0tLQ0KDQojIyMgRnVydGhlciBSZWFkaW5ncw0KDQotIFtDaGFwdGVycyA3LjEgdG8gNy42IGZyb20gTm9ybWFuIE1hdGxvZmbigJlzIFRoZSBBcnQgb2YgUiBQcm9ncmFtbWluZ10oaHR0cHM6Ly93d3cuZ29vZ2xlLmNvbS9zZWFyY2g/Y2xpZW50PWZpcmVmb3gtYi1kJmNoYW5uZWw9dHJvdzImc3hzcmY9QUxlS2swM1RfcUxDTXpSSUNZV2o1VUhxRkJIbnZmVjZVdyUzQTE2MDcyMjA3OTU1NTMmZWk9T3o3TVg4YWVJWUs2a3dXYzVKYmdBdyZxPU5vcm1hbitNYXRsb2ZmK1RoZStBcnQrb2YrUitQcm9ncmFtbWluZytwZGYmb3E9Tm9ybWFuK01hdGxvZmYrVGhlK0FydCtvZitSK1Byb2dyYW1taW5nK3BkZiZnc19sY3A9Q2dad2Mza3RZV0lRQXpJRkNBQVF5UU02QndnakVNa0RFQ2M2QWdndVVKazlXT2RBWUtSQmFBQndBSGdBZ0FHSEFZZ0I5UU9TQVFNd0xqU1lBUUNnQVFHcUFRZG5kM010ZDJsNndBRUImc2NsaWVudD1wc3ktYWImdmVkPTBhaFVLRXdpR3FPQ0VwTGp0QWhVQzNhUUtIUnl5QlR3UTRkVURDQXcmdWFjdD01KQ0KLSBbSG93IHRvIHdyaXRlIHRoZSBmaXJzdCBmb3IgbG9vcCBpbiBSIGZyb20gUi1ibG9nZ2Vyc10oaHR0cHM6Ly93d3cuci1ibG9nZ2Vycy5jb20vMjAxNS8xMi9ob3ctdG8td3JpdGUtdGhlLWZpcnN0LWZvci1sb29wLWluLXIvKQ0KLSBbV2hpbGUgTG9vcCBpbiBSIHdpdGggRXhhbXBsZSBmcm9tIEd1cnU5OV0oaHR0cHM6Ly93d3cuZ3VydTk5LmNvbS9yLXdoaWxlLWxvb3AuaHRtbCkNCi0gW1IgLSBSZXBlYXQgTG9vcCBmcm9tIFR1dG9yaWFscyBQb2ludF0oaHR0cHM6Ly93d3cudHV0b3JpYWxzcG9pbnQuY29tL3Ivcl9yZXBlYXRfbG9vcC5odG0pDQoNCiMjIyBIaWdobHkgUmVjb21tZW5kZWQgVG8gRG8NCg0KLSBbQ2hhcHRlciAxOSBGdW5jdGlvbnMgZnJvbSBSIGZvciBEYXRhIFNjaWVuY2VdKGh0dHBzOi8vcjRkcy5oYWQuY28ubnovZnVuY3Rpb25zLmh0bWwpDQotIFtDaGFwdGVyIDIwIFZlY3RvcnMgZnJvbSBSIGZvciBEYXRhIFNjaWVuY2VdKGh0dHBzOi8vcjRkcy5oYWQuY28ubnovdmVjdG9ycy5odG1sKQ0KLSBbQ2hhcHRlciAyMSBJdGVyYXRpb25zIGZyb20gUiBmb3IgRGF0YSBTY2llbmNlXShodHRwczovL3I0ZHMuaGFkLmNvLm56L2l0ZXJhdGlvbi5odG1sKQ0KDQoNCiMjIyBJbXBvcnRhbnQgc291cmNlcywgZG9jdW1lbnRhdGlvbiwgZXRjLg0KDQotIFtRdWljay1SXShodHRwczovL3d3dy5zdGF0bWV0aG9kcy5uZXQvKSAtIEFuIGV4Y2VsbGVudCBzZXQgb2YgY29uY2lzZSBSIHR1dG9yaWFscy4NCi0gW0NSQU46IFRoZSBDb21wcmVoZW5zaXZlIFIgQXJjaGl2ZSBOZXR3b3JrXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy8pIC0gVGhpcyBpcyB3ZXJlIHRoZSBvZmZpY2lhbCB2ZXJzaW9ucyBvZiBSIHBhY2thZ2VzIGxpdmUNCi0gW0NvbG9ycyBpbiBSIChQREYpXShodHRwczovL3d3dy5nb29nbGUuY29tL3VybD9zYT10JnJjdD1qJnE9JmVzcmM9cyZzb3VyY2U9d2ViJmNkPSZ2ZWQ9MmFoVUtFd2pYaE5QSXVfVHRBaFZTa01NS0hSeDZBM1FRRmpBQmVnUUlBaEFDJnVybD1odHRwJTNBJTJGJTJGd3d3LnN0YXQuY29sdW1iaWEuZWR1JTJGfnR6aGVuZyUyRmZpbGVzJTJGUmNvbG9yLnBkZiZ1c2c9QU92VmF3MVhJbi1td1o3M1JnRzhieGg0TGFuNCkNCi0gW2dncGxvdDIgUmVmZXJlbmNlXShodHRwczovL2dncGxvdDIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2UvKQ0KLSBbUkRvY3VtZW50YXRpb246IFNlYXJjaCBhbGwgMjEsMjE3IENSQU4sIEJpb2NvbmR1Y3RvciBhbmQgR2l0SHViIHBhY2thZ2VzXShodHRwczovL3d3dy5yZG9jdW1lbnRhdGlvbi5vcmcvKQ0KDQojIyMgUiBNYXJrZG93bg0KDQpbUiBNYXJrZG93bl0oaHR0cHM6Ly9ybWFya2Rvd24ucnN0dWRpby5jb20vKSBpcyB3aGF0IEkgaGF2ZSB1c2VkIHRvIHByb2R1Y2UgdGhpcyBiZWF1dGlmdWwgTm90ZWJvb2suIFdlIHdpbGwgbGVhcm4gbW9yZSBhYm91dCBpdCBuZWFyIHRoZSBlbmQgb2YgdGhlIGNvdXJzZSwgYnV0IGlmIHlvdSBhbHJlYWR5IGZlZWwgcmVhZHkgdG8gZGl2ZSBkZWVwLCBoZXJlJ3MgYSBib29rOiBbUiBNYXJrZG93bjogVGhlIERlZmluaXRpdmUgR3VpZGUsIFlpaHVpIFhpZSwgSi4gSi4gQWxsYWlyZSwgR2FycmV0dCBHcm9sZW11bmRzLl0oaHR0cHM6Ly9ib29rZG93bi5vcmcveWlodWkvcm1hcmtkb3duLykgDQoNCiMjIyBFeGVyY2lzZXMNCg0KLSAqKkUxLioqIEluIHRoZSBiZWdpbm5pbmcgb2YgdGhpcyBzZXNzaW9uIHdlIGhhdmUgdXNlZCB0aGUgYGZvcmAgbG9vcCB0byBsb2FkIGZvdXIgZGF0YXNldHMgaW50byBhIGxpc3Qgb2YgZGF0YWZyYW1lcyBpbiBSLiBEbyB0aGUgc2FtZSwgYnV0IHVzZSB0aGUgYHJlcGVhdGAgbG9vcCBpbnN0ZWFkLg0KDQotICoqRTIuKiogU2FtZSBhcyAqKkUxKiogYnV0IHVzZSB0aGUgYHdoaWxlYCBsb29wIGluIHBsYWNlIG9mIGByZXBlYXRgLg0KDQotICoqRTMuKiogUHJvZHVjZSBhIG5ldyBjb2x1bW4gb2YgbG9naWNhbHMgKGBUUlVFYCwgYEZBTFNFYCksIGBsaXN0aW5ncyRwcmljZUNsYXNzYCBpbiBgbGlzdGluZ3NgIHRoYXQgaXMgYFRSVUVgIGlmIGBsaXN0aW5ncyRwcmljZWAgaXMgYWJvdmUgb3IgZXF1YWwgdG8gaXRzIG1lZGlhbiwgYW5kIGBGQUxTRWAgaWYgaXQgaXMgc3RyaWN0bHkgbG93ZXIgdGhhbiBpdHMgbWVkaWFuLiAqKkhpbnQ6KiogVXNlIGBpZmVsc2VgIHRvIGNyZWF0ZSB0aGUgbmV3IGNvbHVtbi4gVHJ5IHRvIHN1bW1hcml6ZSB5b3VyIGRhdGEgYnkgdXNpbmcgYHN1bW1hcnkoKWAgb24gYGxpc3RpbmdzJHByaWNlYC4gV2hhdCBkb2VzIGBzdW1tYXJ5KGxpc3RpbmdzKWAgbG9vayBsaWtlPw0KDQotICoqRTQuKiogUHJvZHVjZSBhIG5ldyBjb2x1bW4gaW4gYGxpc3RpbmdzYDogYGxpc3RpbmdzJHByaWNlTmlnaHRzUmF0aW9uYCwgd2hpY2ggaXMgdGhlIHJhdGlvIG9mIGBsaXN0aW5ncyRtaW5pbXVtX25pZ2h0c2AgYW5kIGBsaXN0aW5ncyRwcmljZWAuDQoNCi0gKipFNS4qKiBUYWtlIGEgbG9vayBhdCB0aGUgZm9sbG93aW5nIFIgY29kZToNCg0KYGBge3IgZWNobyA9IFQsIGV2YWwgPSBGfQ0Kc2V0IDwtIDE6MTAwDQpmb3IgKGkgaW4gMTpsZW5ndGgoc2V0KSkgew0KICBpZihpID09IDEwMCkgew0KICAgIGJyZWFrDQogICAgfSBlbHNlIHsNCiAgICAgIGlmICgoaSA9PSAxKSkgew0KICAgICAgICB6ID0gc2V0W2ldICsgc2V0W2krMV0NCiAgICAgICAgcHJpbnQoeikNCiAgICAgICAgfSBlbHNlIHsNCiAgICAgICAgICB6ID0geiArIHNldFtpKzFdDQogICAgICAgICAgcHJpbnQoeikNCiAgICAgICAgfQ0KICAgIH0NCiAgfQ0KYGBgDQoNClVzZSBgUmVkdWNlKClgIHRvIGFjY29tcGxpc2ggdGhlIHNhbWUgaW4gYSBvbmUtbGluZXIuICoqUmVtZW1iZXIqKiB0aGUgYGFjY3VtdWxhdGVgIGFyZ3VtZW50IQ0KDQoNCioqKg0KR29yYW4gUy4gTWlsb3Zhbm92acSHDQoNCkRhdGFLb2xla3RpdiwgMjAyMC8yMQ0KDQpjb250YWN0OiBnb3Jhbi5taWxvdmFub3ZpY0BkYXRha29sZWt0aXYuY29tDQoNCiFbXSguLi9faW1nL0RLX0xvZ29fMTAwLnBuZykNCg0KKioqDQpMaWNlbnNlOiBbR1BMdjNdKGh0dHA6Ly93d3cuZ251Lm9yZy9saWNlbnNlcy9ncGwtMy4wLnR4dCkNClRoaXMgTm90ZWJvb2sgaXMgZnJlZSBzb2Z0d2FyZTogeW91IGNhbiByZWRpc3RyaWJ1dGUgaXQgYW5kL29yIG1vZGlmeSBpdCB1bmRlciB0aGUgdGVybXMgb2YgdGhlIEdOVSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlIGFzIHB1Ymxpc2hlZCBieSB0aGUgRnJlZSBTb2Z0d2FyZSBGb3VuZGF0aW9uLCBlaXRoZXIgdmVyc2lvbiAzIG9mIHRoZSBMaWNlbnNlLCBvciAoYXQgeW91ciBvcHRpb24pIGFueSBsYXRlciB2ZXJzaW9uLg0KVGhpcyBOb3RlYm9vayBpcyBkaXN0cmlidXRlZCBpbiB0aGUgaG9wZSB0aGF0IGl0IHdpbGwgYmUgdXNlZnVsLCBidXQgV0lUSE9VVCBBTlkgV0FSUkFOVFk7IHdpdGhvdXQgZXZlbiB0aGUgaW1wbGllZCB3YXJyYW50eSBvZiBNRVJDSEFOVEFCSUxJVFkgb3IgRklUTkVTUyBGT1IgQSBQQVJUSUNVTEFSIFBVUlBPU0UuICBTZWUgdGhlIEdOVSBHZW5lcmFsIFB1YmxpYyBMaWNlbnNlIGZvciBtb3JlIGRldGFpbHMuDQpZb3Ugc2hvdWxkIGhhdmUgcmVjZWl2ZWQgYSBjb3B5IG9mIHRoZSBHTlUgR2VuZXJhbCBQdWJsaWMgTGljZW5zZSBhbG9uZyB3aXRoIHRoaXMgTm90ZWJvb2suIElmIG5vdCwgc2VlIDxodHRwOi8vd3d3LmdudS5vcmcvbGljZW5zZXMvPi4NCg0KKioqDQoNCg==