Goal

Given the iris dataset, find a ML-based way to tell which is the hardest variable to be predicted, given the other variables, and motivate the answer.

Hints

The goal is not to “build a ML system on this data”, but rather a question to be answered. So we should:

  1. understand the question (that probably includes giving an interpretation to “hardest”)
  2. design a method for answering the question
  3. implement that method
  4. provide the answer by applying the method

Possible “solution”

Step 1: design

The iris dataset is composed by 5 variables, hence the question may be answered by basically “trying to predict” each one of them and measuring how hard it is. If we intend to follow this path, we need:

  1. to be able to “try to predict”
  2. a way for measuring how hard it is

Concerning 1, and noted that we can tackle the prediction problem as a supervised learning problem, we know a learning technique (officially just one) that fits the scenario: learning of decision/regression trees. Interestingly, 4 of the variables are numeric and 1 is categorical: fortunately, we have a variant of the learning technique for each of the two cases.

Concerning 2, we note that the notion of hardness, as captured by the corresponding word in natural language, is broad. “Hard” could refer to computational effort, time needed to design a ML system for doing prediction, required knowledge, … We decide to interpret “hard to be predicted” as “degree to which, on some data, a model learned with some suitable technique correctly predicts the dependent variable”. In brief, we plan to interpret hardness as (in)effectiveness of prediction; we know several ways to measure (in)effectiveness. One is error. (I am being purposely vague here…). Note that, while in principle this “definition” is not bound to a specific dataset or to a specific learning technique, we have only one dataset an only one learning technique.

In practice, hence, we are going to build 5 models (4 regression and 1 classification models), measure their errors, and say that the hardest variable is the one for which the error is the largest. Note that the last part (the one in italic), is a key step, even if it appears trivial and it does not require any big implementation effort. Recall that we want to “design a method for answering the question”: when executed, the method is required to result in an answer. Without that step, the method execution would result in a collection of 5 numerical values, not in an answer!

Step 2: implementation

The method designed above and described in natural language can be, in principle, applied to any dataset. Again in principle, we could implement as a function that takes a dataset (a dataframe, in terms of R), and outputs the name of the hardest variable.

However, for simplicity we’ll start by doing an implementation mixed with the execution. This is done also beacuse there are still some ambiguities in the description of the method (e.g., how to measure the error?) and we will discover and address them as we meet them. Ideally, one should first fully define the method (i.e., the corresponding algorithm), possibly in the form of a piece of pseudocode.

Simplicity: implementation + execution

Load iris.

d = iris

How can we learn a tree? Using a package! There are many packages that implement (slightly different versions of) the learning technique that we saw in the lectures. My choice is for the package tree (that you probably have to install, first, using install.packages("tree")). (Another option is rpart).

require(tree)

Supervised learning conventions in R

Most of the packages for supervised learning share the following structure. They have:

  • a function for doing the learning, usually with the name of the technique itself
  • a function for doing the prediction, usually named predict() tree adheres this “convention” (see the documentation).

The learning function usually has two ways for being invoked: one in which \(\{(x^{(i)},y^{(i)})\}_i\) is specified by giving \(\{x^{(i)}\}_i\) and \(\{y^{(i)}\}_i\) separately and explicitly, the other in which a dataframe (without indication of the dependent variable) is passed together with an indication of the dependency. (Actually tree() only supports the second way). The latter is done using a data type, peculiar of R, known as formula. The syntax for specifying a formula literal (i.e., a constant value for a data type) is a ~ b+c (this is an example, rather than a formal specification of the syntax): this value means a depends on b and c. A common case is that one wants to say that a variable depends on all the other variables: this can be done with a ~ ..

Summarizing, for building a tree for predicting the Species based on the other variable on the full dataset d, we do:

tree(Species ~ ., d)
node), split, n, deviance, yval, (yprob)
      * denotes terminal node

 1) root 150 329.600 setosa ( 0.33333 0.33333 0.33333 )  
   2) Petal.Length < 2.45 50   0.000 setosa ( 1.00000 0.00000 0.00000 ) *
   3) Petal.Length > 2.45 100 138.600 versicolor ( 0.00000 0.50000 0.50000 )  
     6) Petal.Width < 1.75 54  33.320 versicolor ( 0.00000 0.90741 0.09259 )  
      12) Petal.Length < 4.95 48   9.721 versicolor ( 0.00000 0.97917 0.02083 )  
        24) Sepal.Length < 5.15 5   5.004 versicolor ( 0.00000 0.80000 0.20000 ) *
        25) Sepal.Length > 5.15 43   0.000 versicolor ( 0.00000 1.00000 0.00000 ) *
      13) Petal.Length > 4.95 6   7.638 virginica ( 0.00000 0.33333 0.66667 ) *
     7) Petal.Width > 1.75 46   9.635 virginica ( 0.00000 0.02174 0.97826 )  
      14) Petal.Length < 4.95 6   5.407 virginica ( 0.00000 0.16667 0.83333 ) *
      15) Petal.Length > 4.95 40   0.000 virginica ( 0.00000 0.00000 1.00000 ) *

Note that:

  • the formula is not quoted: it is not a string representing a dependency, it is a formula!
  • the outcome of the execution of the above command is the textual representation (on the standard output) of the learned tree [there might be an error here…]

You should also note that there are more decorations, on that tree, than the ones we expected to see. Look at the documentation for more info.

As an aside, a tree can also be plot:

t = tree(Species~., d)
plot(t)
text(t)

text() is needed to add the labels to the tree.

With the rpart package

require(rpart)
rpart(Species~., d)
n= 150 

node), split, n, loss, yval, (yprob)
      * denotes terminal node

1) root 150 100 setosa (0.33333333 0.33333333 0.33333333)  
  2) Petal.Length< 2.45 50   0 setosa (1.00000000 0.00000000 0.00000000) *
  3) Petal.Length>=2.45 100  50 versicolor (0.00000000 0.50000000 0.50000000)  
    6) Petal.Width< 1.75 54   5 versicolor (0.00000000 0.90740741 0.09259259) *
    7) Petal.Width>=1.75 46   1 virginica (0.00000000 0.02173913 0.97826087) *
t = rpart(Species~., d)
plot(t)
text(t)

Back to design+implementation

We know how to build a tree. How should we measure the error? We have some options:

  • on the learning data
  • with a static training/test division (e.g., 20% of the overall data used as test)
  • with a k-fold CV
  • with a leave-one-out CV (LOOCV), i.e., a k-fold CV with \(k=n\), \(n\) being the number of observations in the available data

The easiest (in terms of effort to be realized) option is the 1st: but we know that, when \(n_\text{min}=1\), the error on the learning data is 0 by definition. It would hence pointless to compare a set of 0s… We need to ascertain which is the default value of \(n_\text{min}\) in tree(), that requires to consume the documentation: I leave this for your enjoyment.

We go for the 2nd option. We use sample() for shuffling the set of row-indexes of d and take a subset of this set that will act as the indexes of the learning data.

indexes.learning = sample(c(1:nrow(d)))[1:(nrow(d)*0.8)]

Now we can learn the tree (let’s start with Species):

t.species = tree(Species~., d[indexes.learning,])
plot(t.species)
text(t.species)

How to measure the error?

The predict() function takes a dataframe with possibly new observations and predict the corresponding labels: the results is hence a vector.

predicted.y = predict(t.species, d[-indexes.learning,], type="class")

Note that:

  • the - preceding indexes.learning means “select all but those”
  • type="class" is needed to obtain a vector of factors, rather than a more complex thing: see the documentation of predict.tree()
  • predict() doesn’t cheat: even if d[-indexes.learning,] actually contains also the correct \(y\) values, it is not using them

Now we can compute the classification error rate by comparing predicted.y against the expected \(y\):

length(which(predicted.y!=d$Species[-indexes.learning]))/length(predicted.y)
[1] 0.03333333

Note that which() returns the indexes of a Boolean vector elements that are true.

Another way is to “compute” the confusion matrix and then obtaining the error from that. The confusion matrix shows the number misclassifications, class by class:

table(predicted.y, d$Species[-indexes.learning])
            
predicted.y  setosa versicolor virginica
  setosa          8          0         0
  versicolor      0          7         0
  virginica       0          1        14

Given that matrix, the accuracy of classification is:

conf.matrix = table(predicted.y, d$Species[-indexes.learning])
sum(diag(conf.matrix))/sum(conf.matrix)
[1] 0.9666667

and the error rate can be computed as:

error.species = 1-sum(diag(conf.matrix))/sum(conf.matrix)
error.species
[1] 0.03333333

As a function

Out of simplicity, we might build a function that does all those operations together, with some parameters:

computeErrorRate = function(categorical.y.name, data, learner, p.learn = 0.8, ...) {
  indexes.learning = sample(c(1:nrow(data)))[1:(nrow(data)*p.learn)]
  model = learner(formula(paste0(categorical.y.name,"~.")), data[indexes.learning,], ...)
  predicted.y = predict(model, d[-indexes.learning, ], type="class")
  length(which(predicted.y!=d[-indexes.learning, categorical.y.name]))/length(predicted.y) 
}

Note that:

  • p.learn = 0.8 in the signature means that there is a default value for this parameter
  • ... in the signature denotes “any other parameter”, that can be then passed to other functions with ... again
  • the return statement is implicit: the returned value is the last computed outcome just before the end
  • learner here is intended to assume functions as values (try with tree or rpart)
print(computeErrorRate("Species", d, tree))
[1] 0.03333333
print(computeErrorRate("Species", d, rpart))
[1] 0.03333333

A similar function could be made for doing k-fold CV or LOOCV. Do it by yourself.

We can do nice things with such a function. For example, we can compute the error for different values of the learning-to-test data ratio (that 0.8 above):

ratio = seq(0.1, 0.99, 0.05)
error = ratio %>% map_dbl(function(r){computeErrorRate("Species", d, tree, r)})
tibble(ratio=ratio, error=error) %>% ggplot(aes(x=ratio,y=error))+geom_line()+ylim(0,1)

For regression

Now, let’s do “the same” on the first numerical variable:

names(d)
[1] "Sepal.Length" "Sepal.Width"  "Petal.Length" "Petal.Width"  "Species"     
t.sl = tree(Sepal.Length~., d[indexes.learning,])
predicted.y = predict(t.sl, d[-indexes.learning,], type="vector")
predicted.y
      19       21       26       27       36       37       44       47       71       77       81       82       84       86 
5.383333 4.796552 4.796552 5.318750 4.796552 4.796552 5.318750 5.383333 6.591667 6.204348 5.318750 5.318750 6.204348 6.591667 
      95      100      109      110      112      114      119      121      123      129      134      137      140      144 
5.828571 5.828571 6.814286 7.600000 6.204348 6.204348 7.600000 6.814286 7.600000 6.204348 6.204348 6.591667 6.591667 6.814286 
     147      148 
6.204348 6.204348 

Wait! Now the \(y\) is numerical, we cannot just check for equality, we need to compute another metric instead of the classification error. We could go for the RSS, or the MAE, or the MSE… Let’s go with the MAE (mean absolute error):

error.sl = mean(abs(predicted.y-d$Sepal.Length[-indexes.learning]))
error.sl
[1] 0.2850388

Again, we can write a function for doing everything:

computeMAE = function(numerical.y.name, data, learner, p.learn = 0.8, ...) {
  indexes.learning = sample(c(1:nrow(data)))[1:(nrow(data)*p.learn)]
  model = learner(formula(paste0(numerical.y.name,"~.")), data[indexes.learning,], ...)
  predicted.y = predict(model, d[-indexes.learning, ], type="vector")
  mean(abs(predicted.y-d[-indexes.learning, numerical.y.name]))
}

Step 3: execution and answer

Since we indeed built the functions, we can now compute the errors for all the variables:

for (variable in names(d)) {
  if (is.numeric(d[,variable])) {
    error = computeMAE(variable, d, tree)
  } else {
    error = computeErrorRate(variable, d, tree)
  }
  cat(paste(variable, error, "\n"))
}
Sepal.Length 0.273899831649832 
Sepal.Width 0.27055765993266 
Petal.Length 0.336315207780725 
Petal.Width 0.133231968810916 
Species 0.133333333333333 

Apparently, the largest error is obtained for Petal.Length, so it is the hardest variable to be predicted.

But, wait! Comparing an error rate (something that is, by definition, in \([0,1]\)) against a MAE (that is, in general, unbounded), is pointless! (As an example, try multiplying one of the numerical variable by 10 and see that the error will increase by 10 too!). This is intrinsically related to the nature of the \(y\) in the two cases: categorical and numerical. It turns out, hence, that the idea of using “the effectiveness of a model learned with default parameter” as a proxy for the hardness (actually, the opposite of the effectiveness), is wrong.

How can we overcome this limitation?

Alternative step 1: re-design

Since the problem is that we cannot compare the absolute values of the error, one possibility is to consider the complexity a model exhibits for obtaining an error of 0. The rationale is that 0 is comparable with 0, since they both represent the absence of any error, regardless of the scale. We need hence a way for measuring the complexity: for trees, this is easy, since we can simply count the number of nodes.

So summarizing, the new method for answering would be:

  1. for each variable
    1. learn a tree that obtains a 0 error
    2. count the number of nodes of the tree
  2. say that the hardest variable is the one for which the count is the greatest

Note that with the tree learning procedure that we know, one can obtain a tree with error 0 by simply setting \(n_\text{min}\) (provided that the dataset \(\{(x^{(i)},y^{(i)})\}_i\) does not contain different observations with the same \(y\)).

Alternative step 2: implementation

We need to find out:

  • how to set the \(n_\text{min}\) in tree()
  • how to count the number of nodes in a tree

By digging the documentation of tree() we find that the parameters for the learning procedure are described in the documentation of the function tree.control(). It is said that:

To produce a tree that fits the data perfectly, set mindev = 0 and minsize = 2, if the limit on tree depth allows such a tree.

By reading further, we infer that minsize is related with \(n_\text{min}\).

Let’s try. Normal tree (default flexibility, i.e., default value for minsize):

t=tree(Species~., d)
plot(t)
text(t)

Complex tree:

t=tree(Species~., d, mindev=0, minsize=2)
plot(t)
text(t)

Yes! It is larger, and also uglier… (As a consequence, it is less interpretable).

Concerning the count of nodes, by reading the documentation of tree() in the Value section, we found that a “component” of the returned value, named frame is:

A data frame with a row for each node, and row.names giving the node numbers.

So we just need to count the rows of that dataframe:

nrow(tree(Species~., d)$frame)
[1] 11

It works: check with the corresponding image above.

So let’s write a function for doing everything:

computeComplexityOfPerfectTree = function (y.name, data) {
  t = tree(formula(paste0(y.name,"~.")), data, mindev=0, minsize=2)
  nrow(t$frame)
}
computeComplexityOfPerfectTree("Species", d)
[1] 17

Alternative step 3: execution and answer

As before: Since we indeed built the functions, we can now compute the errors for all the variables:

for (variable in names(d)) {
  complexity = computeComplexityOfPerfectTree(variable, d)
  cat(paste(variable, complexity, "\n"))
}
Sepal.Length 253 
Sepal.Width 259 
Petal.Length 251 
Petal.Width 205 
Species 17 

So, according to this method, it turns out that the hardest variable is Sepal.Width, differently than with the previous method.

Note that the important thing here is not the actual answer, but the full path to the answer. Note also that this method still has the drawback of comparing apples and oranges: indeed the complexity of the obtained trees clearly shows that the values are very different for the categorical and numerical variables. This is because the former just takes 3 values, while the latter ones take many of them: when we impose the RSS=0 as the only stopping criterion, all of them basically act as class values. It is hence somehow expected that much more nodes are needed to perfecly accomodate all those class values.

LS0tCnRpdGxlOiAiTGFiIDI6IHRyZWUgb24gaXJpcyIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKIyBHb2FsCgpHaXZlbiB0aGUgYGlyaXNgIGRhdGFzZXQsIGZpbmQgYSBNTC1iYXNlZCB3YXkgdG8gdGVsbCB3aGljaCBpcyB0aGUgaGFyZGVzdCB2YXJpYWJsZSB0byBiZSBwcmVkaWN0ZWQsIGdpdmVuIHRoZSBvdGhlciB2YXJpYWJsZXMsIGFuZCBtb3RpdmF0ZSB0aGUgYW5zd2VyLgoKIyMjIEhpbnRzCgpUaGUgZ29hbCBpcyBub3QgdG8gImJ1aWxkIGEgTUwgc3lzdGVtIG9uIHRoaXMgZGF0YSIsIGJ1dCByYXRoZXIgYSBxdWVzdGlvbiB0byBiZSBhbnN3ZXJlZC4KU28gd2Ugc2hvdWxkOgoKMC4gdW5kZXJzdGFuZCB0aGUgcXVlc3Rpb24gKHRoYXQgcHJvYmFibHkgaW5jbHVkZXMgZ2l2aW5nIGFuIGludGVycHJldGF0aW9uIHRvICJoYXJkZXN0IikKMS4gZGVzaWduIGEgbWV0aG9kIGZvciBhbnN3ZXJpbmcgdGhlIHF1ZXN0aW9uCjIuIGltcGxlbWVudCB0aGF0IG1ldGhvZAozLiBwcm92aWRlIHRoZSBhbnN3ZXIgYnkgYXBwbHlpbmcgdGhlIG1ldGhvZAoKIyBQb3NzaWJsZSAic29sdXRpb24iCgojIyBTdGVwIDE6IGRlc2lnbgoKVGhlIGBpcmlzYCBkYXRhc2V0IGlzIGNvbXBvc2VkIGJ5IDUgdmFyaWFibGVzLCBoZW5jZSB0aGUgcXVlc3Rpb24gbWF5IGJlIGFuc3dlcmVkIGJ5IGJhc2ljYWxseSAidHJ5aW5nIHRvIHByZWRpY3QiIGVhY2ggb25lIG9mIHRoZW0gYW5kIG1lYXN1cmluZyBob3cgaGFyZCBpdCBpcy4KSWYgd2UgaW50ZW5kIHRvIGZvbGxvdyB0aGlzIHBhdGgsIHdlIG5lZWQ6CgoxLiB0byBiZSBhYmxlIHRvICJ0cnkgdG8gcHJlZGljdCIKMi4gYSB3YXkgZm9yIG1lYXN1cmluZyBob3cgaGFyZCBpdCBpcwoKQ29uY2VybmluZyAxLCBhbmQgbm90ZWQgdGhhdCB3ZSBjYW4gdGFja2xlIHRoZSBwcmVkaWN0aW9uIHByb2JsZW0gYXMgYSBzdXBlcnZpc2VkIGxlYXJuaW5nIHByb2JsZW0sIHdlIGtub3cgYSBsZWFybmluZyB0ZWNobmlxdWUgKG9mZmljaWFsbHkganVzdCBvbmUpIHRoYXQgZml0cyB0aGUgc2NlbmFyaW86IGxlYXJuaW5nIG9mIGRlY2lzaW9uL3JlZ3Jlc3Npb24gdHJlZXMuCkludGVyZXN0aW5nbHksIDQgb2YgdGhlIHZhcmlhYmxlcyBhcmUgbnVtZXJpYyBhbmQgMSBpcyBjYXRlZ29yaWNhbDogZm9ydHVuYXRlbHksIHdlIGhhdmUgYSB2YXJpYW50IG9mIHRoZSBsZWFybmluZyB0ZWNobmlxdWUgZm9yIGVhY2ggb2YgdGhlIHR3byBjYXNlcy4KCkNvbmNlcm5pbmcgMiwgd2Ugbm90ZSB0aGF0IHRoZSBub3Rpb24gb2YgaGFyZG5lc3MsIGFzIGNhcHR1cmVkIGJ5IHRoZSBjb3JyZXNwb25kaW5nIHdvcmQgaW4gbmF0dXJhbCBsYW5ndWFnZSwgaXMgYnJvYWQuICJIYXJkIiBjb3VsZCByZWZlciB0byBjb21wdXRhdGlvbmFsIGVmZm9ydCwgdGltZSBuZWVkZWQgdG8gZGVzaWduIGEgTUwgc3lzdGVtIGZvciBkb2luZyBwcmVkaWN0aW9uLCByZXF1aXJlZCBrbm93bGVkZ2UsIC4uLgpXZSBkZWNpZGUgdG8gaW50ZXJwcmV0ICJoYXJkIHRvIGJlIHByZWRpY3RlZCIgYXMgImRlZ3JlZSB0byB3aGljaCwgb24gc29tZSBkYXRhLCBhIG1vZGVsIGxlYXJuZWQgd2l0aCBzb21lIHN1aXRhYmxlIHRlY2huaXF1ZSBjb3JyZWN0bHkgcHJlZGljdHMgdGhlIGRlcGVuZGVudCB2YXJpYWJsZSIuCkluIGJyaWVmLCB3ZSBwbGFuIHRvIGludGVycHJldCBoYXJkbmVzcyBhcyAoaW4pZWZmZWN0aXZlbmVzcyBvZiBwcmVkaWN0aW9uOyB3ZSBrbm93IHNldmVyYWwgd2F5cyB0byBtZWFzdXJlIChpbillZmZlY3RpdmVuZXNzLiBPbmUgaXMgKmVycm9yKi4gKEkgYW0gYmVpbmcgcHVycG9zZWx5IHZhZ3VlIGhlcmUuLi4pLgpOb3RlIHRoYXQsIHdoaWxlIGluIHByaW5jaXBsZSB0aGlzICJkZWZpbml0aW9uIiBpcyBub3QgYm91bmQgdG8gYSBzcGVjaWZpYyBkYXRhc2V0IG9yIHRvIGEgc3BlY2lmaWMgbGVhcm5pbmcgdGVjaG5pcXVlLCB3ZSBoYXZlIG9ubHkgb25lIGRhdGFzZXQgYW4gb25seSBvbmUgbGVhcm5pbmcgdGVjaG5pcXVlLgoKSW4gcHJhY3RpY2UsIGhlbmNlLCB3ZSBhcmUgZ29pbmcgdG8gYnVpbGQgNSBtb2RlbHMgKDQgcmVncmVzc2lvbiBhbmQgMSBjbGFzc2lmaWNhdGlvbiBtb2RlbHMpLCBtZWFzdXJlIHRoZWlyIGVycm9ycywgYW5kICpzYXkgdGhhdCB0aGUgaGFyZGVzdCB2YXJpYWJsZSBpcyB0aGUgb25lIGZvciB3aGljaCB0aGUgZXJyb3IgaXMgdGhlIGxhcmdlc3QqLgpOb3RlIHRoYXQgdGhlIGxhc3QgcGFydCAodGhlIG9uZSBpbiBpdGFsaWMpLCBpcyBhIGtleSBzdGVwLCBldmVuIGlmIGl0IGFwcGVhcnMgdHJpdmlhbCBhbmQgaXQgZG9lcyBub3QgcmVxdWlyZSBhbnkgYmlnIGltcGxlbWVudGF0aW9uIGVmZm9ydC4KUmVjYWxsIHRoYXQgd2Ugd2FudCB0byAiZGVzaWduIGEgbWV0aG9kIGZvciBhbnN3ZXJpbmcgdGhlIHF1ZXN0aW9uIjogd2hlbiBleGVjdXRlZCwgdGhlIG1ldGhvZCBpcyByZXF1aXJlZCB0byByZXN1bHQgaW4gYW4gYW5zd2VyLgpXaXRob3V0IHRoYXQgc3RlcCwgdGhlIG1ldGhvZCBleGVjdXRpb24gd291bGQgcmVzdWx0IGluIGEgY29sbGVjdGlvbiBvZiA1IG51bWVyaWNhbCB2YWx1ZXMsIG5vdCBpbiBhbiBhbnN3ZXIhCgojIyBTdGVwIDI6IGltcGxlbWVudGF0aW9uCgpUaGUgbWV0aG9kIGRlc2lnbmVkIGFib3ZlIGFuZCBkZXNjcmliZWQgaW4gbmF0dXJhbCBsYW5ndWFnZSBjYW4gYmUsIGluIHByaW5jaXBsZSwgYXBwbGllZCB0byBhbnkgZGF0YXNldC4KQWdhaW4gaW4gcHJpbmNpcGxlLCB3ZSBjb3VsZCBpbXBsZW1lbnQgYXMgYSBmdW5jdGlvbiB0aGF0IHRha2VzIGEgZGF0YXNldCAoYSBkYXRhZnJhbWUsIGluIHRlcm1zIG9mIFIpLCBhbmQgb3V0cHV0cyB0aGUgbmFtZSBvZiB0aGUgaGFyZGVzdCB2YXJpYWJsZS4KCkhvd2V2ZXIsIGZvciBzaW1wbGljaXR5IHdlJ2xsIHN0YXJ0IGJ5IGRvaW5nIGFuIGltcGxlbWVudGF0aW9uIG1peGVkIHdpdGggdGhlIGV4ZWN1dGlvbi4KVGhpcyBpcyBkb25lIGFsc28gYmVhY3VzZSB0aGVyZSBhcmUgc3RpbGwgc29tZSBhbWJpZ3VpdGllcyBpbiB0aGUgZGVzY3JpcHRpb24gb2YgdGhlIG1ldGhvZCAoZS5nLiwgaG93IHRvIG1lYXN1cmUgdGhlIGVycm9yPykgYW5kIHdlIHdpbGwgZGlzY292ZXIgYW5kIGFkZHJlc3MgdGhlbSBhcyB3ZSBtZWV0IHRoZW0uCklkZWFsbHksIG9uZSBzaG91bGQgZmlyc3QgZnVsbHkgZGVmaW5lIHRoZSBtZXRob2QgKGkuZS4sIHRoZSBjb3JyZXNwb25kaW5nIGFsZ29yaXRobSksIHBvc3NpYmx5IGluIHRoZSBmb3JtIG9mIGEgcGllY2Ugb2YgcHNldWRvY29kZS4KCiMjIyBTaW1wbGljaXR5OiBpbXBsZW1lbnRhdGlvbiArIGV4ZWN1dGlvbgoKTG9hZCBgaXJpc2AuCmBgYHtyfQpkID0gaXJpcwpgYGAKCkhvdyBjYW4gd2UgbGVhcm4gYSB0cmVlPyBVc2luZyBhIHBhY2thZ2UhClRoZXJlIGFyZSBtYW55IHBhY2thZ2VzIHRoYXQgaW1wbGVtZW50IChzbGlnaHRseSBkaWZmZXJlbnQgdmVyc2lvbnMgb2YpIHRoZSBsZWFybmluZyB0ZWNobmlxdWUgdGhhdCB3ZSBzYXcgaW4gdGhlIGxlY3R1cmVzLgpNeSBjaG9pY2UgaXMgZm9yIHRoZSBwYWNrYWdlIGB0cmVlYCAodGhhdCB5b3UgcHJvYmFibHkgaGF2ZSB0byBpbnN0YWxsLCBmaXJzdCwgdXNpbmcgYGluc3RhbGwucGFja2FnZXMoInRyZWUiKWApLgooQW5vdGhlciBvcHRpb24gaXMgYHJwYXJ0YCkuCmBgYHtyfQpyZXF1aXJlKHRyZWUpCmBgYAoKIyMjIyBTdXBlcnZpc2VkIGxlYXJuaW5nIGNvbnZlbnRpb25zIGluIFIKCk1vc3Qgb2YgdGhlIHBhY2thZ2VzIGZvciBzdXBlcnZpc2VkIGxlYXJuaW5nIHNoYXJlIHRoZSBmb2xsb3dpbmcgc3RydWN0dXJlLgpUaGV5IGhhdmU6CgotIGEgZnVuY3Rpb24gZm9yIGRvaW5nIHRoZSBsZWFybmluZywgdXN1YWxseSB3aXRoIHRoZSBuYW1lIG9mIHRoZSB0ZWNobmlxdWUgaXRzZWxmCi0gYSBmdW5jdGlvbiBmb3IgZG9pbmcgdGhlIHByZWRpY3Rpb24sIHVzdWFsbHkgbmFtZWQgYHByZWRpY3QoKWAKYHRyZWVgIGFkaGVyZXMgdGhpcyAiY29udmVudGlvbiIgKHNlZSB0aGUgZG9jdW1lbnRhdGlvbikuCgpUaGUgbGVhcm5pbmcgZnVuY3Rpb24gdXN1YWxseSBoYXMgdHdvIHdheXMgZm9yIGJlaW5nIGludm9rZWQ6IG9uZSBpbiB3aGljaCAkXHsoeF57KGkpfSx5XnsoaSl9KVx9X2kkIGlzIHNwZWNpZmllZCBieSBnaXZpbmcgJFx7eF57KGkpfVx9X2kkIGFuZCAkXHt5XnsoaSl9XH1faSQgc2VwYXJhdGVseSBhbmQgZXhwbGljaXRseSwgdGhlIG90aGVyIGluIHdoaWNoIGEgZGF0YWZyYW1lICh3aXRob3V0IGluZGljYXRpb24gb2YgdGhlIGRlcGVuZGVudCB2YXJpYWJsZSkgaXMgcGFzc2VkIHRvZ2V0aGVyIHdpdGggKmFuIGluZGljYXRpb24gb2YgdGhlIGRlcGVuZGVuY3kqLgooQWN0dWFsbHkgYHRyZWUoKWAgb25seSBzdXBwb3J0cyB0aGUgc2Vjb25kIHdheSkuClRoZSBsYXR0ZXIgaXMgZG9uZSB1c2luZyBhIGRhdGEgdHlwZSwgcGVjdWxpYXIgb2YgUiwga25vd24gYXMgKipmb3JtdWxhKiouClRoZSBzeW50YXggZm9yIHNwZWNpZnlpbmcgYSBmb3JtdWxhIGxpdGVyYWwgKGkuZS4sIGEgY29uc3RhbnQgdmFsdWUgZm9yIGEgZGF0YSB0eXBlKSBpcyBgYSB+IGIrY2AgKHRoaXMgaXMgYW4gZXhhbXBsZSwgcmF0aGVyIHRoYW4gYSBmb3JtYWwgc3BlY2lmaWNhdGlvbiBvZiB0aGUgc3ludGF4KTogdGhpcyB2YWx1ZSBtZWFucyBgYWAgZGVwZW5kcyBvbiBgYmAgYW5kIGBjYC4KQSBjb21tb24gY2FzZSBpcyB0aGF0IG9uZSB3YW50cyB0byBzYXkgdGhhdCBhIHZhcmlhYmxlIGRlcGVuZHMgb24gYWxsIHRoZSBvdGhlciB2YXJpYWJsZXM6IHRoaXMgY2FuIGJlIGRvbmUgd2l0aCBgYSB+IC5gLgoKU3VtbWFyaXppbmcsIGZvciBidWlsZGluZyBhIHRyZWUgZm9yIHByZWRpY3RpbmcgdGhlIFNwZWNpZXMgYmFzZWQgb24gdGhlIG90aGVyIHZhcmlhYmxlIG9uIHRoZSBmdWxsIGRhdGFzZXQgYGRgLCB3ZSBkbzoKYGBge3J9CnRyZWUoU3BlY2llcyB+IC4sIGQpCmBgYApOb3RlIHRoYXQ6CgotIHRoZSBmb3JtdWxhIGlzIG5vdCBxdW90ZWQ6IGl0IGlzIG5vdCBhIHN0cmluZyByZXByZXNlbnRpbmcgYSBkZXBlbmRlbmN5LCBpdCBpcyBhIGZvcm11bGEhCi0gdGhlIG91dGNvbWUgb2YgdGhlIGV4ZWN1dGlvbiBvZiB0aGUgYWJvdmUgY29tbWFuZCBpcyB0aGUgdGV4dHVhbCByZXByZXNlbnRhdGlvbiAob24gdGhlIHN0YW5kYXJkIG91dHB1dCkgb2YgdGhlIGxlYXJuZWQgdHJlZSBbdGhlcmUgbWlnaHQgYmUgYW4gZXJyb3IgaGVyZS4uLl0KCllvdSBzaG91bGQgYWxzbyBub3RlIHRoYXQgdGhlcmUgYXJlIG1vcmUgZGVjb3JhdGlvbnMsIG9uIHRoYXQgdHJlZSwgdGhhbiB0aGUgb25lcyB3ZSBleHBlY3RlZCB0byBzZWUuCkxvb2sgYXQgdGhlIGRvY3VtZW50YXRpb24gZm9yIG1vcmUgaW5mby4KCkFzIGFuIGFzaWRlLCBhIHRyZWUgY2FuIGFsc28gYmUgcGxvdDoKYGBge3J9CnQgPSB0cmVlKFNwZWNpZXN+LiwgZCkKcGxvdCh0KQp0ZXh0KHQpCmBgYApgdGV4dCgpYCBpcyBuZWVkZWQgdG8gYWRkIHRoZSBsYWJlbHMgdG8gdGhlIHRyZWUuCgojIyMjIFdpdGggdGhlIGBycGFydGAgcGFja2FnZQoKYGBge3J9CnJlcXVpcmUocnBhcnQpCmBgYApgYGB7cn0KcnBhcnQoU3BlY2llc34uLCBkKQpgYGAKCmBgYHtyfQp0ID0gcnBhcnQoU3BlY2llc34uLCBkKQpwbG90KHQpCnRleHQodCkKYGBgCgoKIyMjIEJhY2sgdG8gZGVzaWduK2ltcGxlbWVudGF0aW9uCgpXZSBrbm93IGhvdyB0byBidWlsZCBhIHRyZWUuCkhvdyBzaG91bGQgd2UgbWVhc3VyZSB0aGUgKmVycm9yKj8KV2UgaGF2ZSBzb21lIG9wdGlvbnM6CgotIG9uIHRoZSBsZWFybmluZyBkYXRhCi0gd2l0aCBhIHN0YXRpYyB0cmFpbmluZy90ZXN0IGRpdmlzaW9uIChlLmcuLCAyMCUgb2YgdGhlIG92ZXJhbGwgZGF0YSB1c2VkIGFzIHRlc3QpCi0gd2l0aCBhIGstZm9sZCBDVgotIHdpdGggYSAqKmxlYXZlLW9uZS1vdXQqKiBDViAoTE9PQ1YpLCBpLmUuLCBhIGstZm9sZCBDViB3aXRoICRrPW4kLCAkbiQgYmVpbmcgdGhlIG51bWJlciBvZiBvYnNlcnZhdGlvbnMgaW4gdGhlIGF2YWlsYWJsZSBkYXRhCgpUaGUgZWFzaWVzdCAoaW4gdGVybXMgb2YgZWZmb3J0IHRvIGJlIHJlYWxpemVkKSBvcHRpb24gaXMgdGhlIDFzdDogYnV0IHdlIGtub3cgdGhhdCwgd2hlbiAkbl9cdGV4dHttaW59PTEkLCB0aGUgZXJyb3Igb24gdGhlIGxlYXJuaW5nIGRhdGEgaXMgMCBieSBkZWZpbml0aW9uLgpJdCB3b3VsZCBoZW5jZSBwb2ludGxlc3MgdG8gY29tcGFyZSBhIHNldCBvZiAwcy4uLgpXZSBuZWVkIHRvIGFzY2VydGFpbiB3aGljaCBpcyB0aGUgZGVmYXVsdCB2YWx1ZSBvZiAkbl9cdGV4dHttaW59JCBpbiBgdHJlZSgpYCwgdGhhdCByZXF1aXJlcyB0byBjb25zdW1lIHRoZSBkb2N1bWVudGF0aW9uOiBJIGxlYXZlIHRoaXMgZm9yIHlvdXIgZW5qb3ltZW50LgoKV2UgZ28gZm9yIHRoZSAybmQgb3B0aW9uLgpXZSB1c2UgYHNhbXBsZSgpYCBmb3Igc2h1ZmZsaW5nIHRoZSBzZXQgb2Ygcm93LWluZGV4ZXMgb2YgYGRgIGFuZCB0YWtlIGEgc3Vic2V0IG9mIHRoaXMgc2V0IHRoYXQgd2lsbCBhY3QgYXMgdGhlIGluZGV4ZXMgb2YgdGhlIGxlYXJuaW5nIGRhdGEuCmBgYHtyfQppbmRleGVzLmxlYXJuaW5nID0gc2FtcGxlKGMoMTpucm93KGQpKSlbMToobnJvdyhkKSowLjgpXQpgYGAKCk5vdyB3ZSBjYW4gbGVhcm4gdGhlIHRyZWUgKGxldCdzIHN0YXJ0IHdpdGggU3BlY2llcyk6CmBgYHtyfQp0LnNwZWNpZXMgPSB0cmVlKFNwZWNpZXN+LiwgZFtpbmRleGVzLmxlYXJuaW5nLF0pCnBsb3QodC5zcGVjaWVzKQp0ZXh0KHQuc3BlY2llcykKYGBgCgojIyMjIEhvdyB0byBtZWFzdXJlIHRoZSBlcnJvcj8KClRoZSBgcHJlZGljdCgpYCBmdW5jdGlvbiB0YWtlcyBhIGRhdGFmcmFtZSB3aXRoIHBvc3NpYmx5IG5ldyBvYnNlcnZhdGlvbnMgYW5kIHByZWRpY3QgdGhlIGNvcnJlc3BvbmRpbmcgbGFiZWxzOiB0aGUgcmVzdWx0cyBpcyBoZW5jZSBhIHZlY3Rvci4KYGBge3J9CnByZWRpY3RlZC55ID0gcHJlZGljdCh0LnNwZWNpZXMsIGRbLWluZGV4ZXMubGVhcm5pbmcsXSwgdHlwZT0iY2xhc3MiKQpgYGAKTm90ZSB0aGF0OgoKLSB0aGUgYC1gIHByZWNlZGluZyBgaW5kZXhlcy5sZWFybmluZ2AgbWVhbnMgInNlbGVjdCBhbGwgYnV0IHRob3NlIgotIGB0eXBlPSJjbGFzcyJgIGlzIG5lZWRlZCB0byBvYnRhaW4gYSB2ZWN0b3Igb2YgZmFjdG9ycywgcmF0aGVyIHRoYW4gYSBtb3JlIGNvbXBsZXggdGhpbmc6IHNlZSB0aGUgZG9jdW1lbnRhdGlvbiBvZiBgcHJlZGljdC50cmVlKClgCi0gYHByZWRpY3QoKWAgZG9lc24ndCBjaGVhdDogZXZlbiBpZiBgZFstaW5kZXhlcy5sZWFybmluZyxdYCBhY3R1YWxseSBjb250YWlucyBhbHNvIHRoZSBjb3JyZWN0ICR5JCB2YWx1ZXMsIGl0IGlzIG5vdCB1c2luZyB0aGVtCgpOb3cgd2UgY2FuIGNvbXB1dGUgdGhlICpjbGFzc2lmaWNhdGlvbiBlcnJvciogcmF0ZSBieSBjb21wYXJpbmcgYHByZWRpY3RlZC55YCBhZ2FpbnN0IHRoZSBleHBlY3RlZCAkeSQ6CmBgYHtyfQpsZW5ndGgod2hpY2gocHJlZGljdGVkLnkhPWQkU3BlY2llc1staW5kZXhlcy5sZWFybmluZ10pKS9sZW5ndGgocHJlZGljdGVkLnkpCmBgYApOb3RlIHRoYXQgYHdoaWNoKClgIHJldHVybnMgdGhlIGluZGV4ZXMgb2YgYSBCb29sZWFuIHZlY3RvciBlbGVtZW50cyB0aGF0IGFyZSB0cnVlLgoKQW5vdGhlciB3YXkgaXMgdG8gImNvbXB1dGUiIHRoZSAqKmNvbmZ1c2lvbiBtYXRyaXgqKiBhbmQgdGhlbiBvYnRhaW5pbmcgdGhlIGVycm9yIGZyb20gdGhhdC4KVGhlIGNvbmZ1c2lvbiBtYXRyaXggc2hvd3MgdGhlIG51bWJlciBtaXNjbGFzc2lmaWNhdGlvbnMsIGNsYXNzIGJ5IGNsYXNzOgpgYGB7cn0KdGFibGUocHJlZGljdGVkLnksIGQkU3BlY2llc1staW5kZXhlcy5sZWFybmluZ10pCmBgYApHaXZlbiB0aGF0IG1hdHJpeCwgdGhlIGFjY3VyYWN5IG9mIGNsYXNzaWZpY2F0aW9uIGlzOgpgYGB7cn0KY29uZi5tYXRyaXggPSB0YWJsZShwcmVkaWN0ZWQueSwgZCRTcGVjaWVzWy1pbmRleGVzLmxlYXJuaW5nXSkKc3VtKGRpYWcoY29uZi5tYXRyaXgpKS9zdW0oY29uZi5tYXRyaXgpCmBgYAphbmQgdGhlIGVycm9yIHJhdGUgY2FuIGJlIGNvbXB1dGVkIGFzOgpgYGB7cn0KZXJyb3Iuc3BlY2llcyA9IDEtc3VtKGRpYWcoY29uZi5tYXRyaXgpKS9zdW0oY29uZi5tYXRyaXgpCmVycm9yLnNwZWNpZXMKYGBgCgojIyMjIEFzIGEgZnVuY3Rpb24KCk91dCBvZiBzaW1wbGljaXR5LCB3ZSBtaWdodCBidWlsZCBhIGZ1bmN0aW9uIHRoYXQgZG9lcyBhbGwgdGhvc2Ugb3BlcmF0aW9ucyB0b2dldGhlciwgd2l0aCBzb21lIHBhcmFtZXRlcnM6CmBgYHtyfQpjb21wdXRlRXJyb3JSYXRlID0gZnVuY3Rpb24oY2F0ZWdvcmljYWwueS5uYW1lLCBkYXRhLCBsZWFybmVyLCBwLmxlYXJuID0gMC44LCAuLi4pIHsKICBpbmRleGVzLmxlYXJuaW5nID0gc2FtcGxlKGMoMTpucm93KGRhdGEpKSlbMToobnJvdyhkYXRhKSpwLmxlYXJuKV0KICBtb2RlbCA9IGxlYXJuZXIoZm9ybXVsYShwYXN0ZTAoY2F0ZWdvcmljYWwueS5uYW1lLCJ+LiIpKSwgZGF0YVtpbmRleGVzLmxlYXJuaW5nLF0sIC4uLikKICBwcmVkaWN0ZWQueSA9IHByZWRpY3QobW9kZWwsIGRbLWluZGV4ZXMubGVhcm5pbmcsIF0sIHR5cGU9ImNsYXNzIikKICBsZW5ndGgod2hpY2gocHJlZGljdGVkLnkhPWRbLWluZGV4ZXMubGVhcm5pbmcsIGNhdGVnb3JpY2FsLnkubmFtZV0pKS9sZW5ndGgocHJlZGljdGVkLnkpIAp9CmBgYApOb3RlIHRoYXQ6CgotIGBwLmxlYXJuID0gMC44YCBpbiB0aGUgc2lnbmF0dXJlIG1lYW5zIHRoYXQgdGhlcmUgaXMgYSBkZWZhdWx0IHZhbHVlIGZvciB0aGlzIHBhcmFtZXRlcgotIGAuLi5gIGluIHRoZSBzaWduYXR1cmUgZGVub3RlcyAiYW55IG90aGVyIHBhcmFtZXRlciIsIHRoYXQgY2FuIGJlIHRoZW4gcGFzc2VkIHRvIG90aGVyIGZ1bmN0aW9ucyB3aXRoIGAuLi5gIGFnYWluCi0gdGhlIHJldHVybiBzdGF0ZW1lbnQgaXMgaW1wbGljaXQ6IHRoZSByZXR1cm5lZCB2YWx1ZSBpcyB0aGUgbGFzdCBjb21wdXRlZCBvdXRjb21lIGp1c3QgYmVmb3JlIHRoZSBlbmQKLSBgbGVhcm5lcmAgaGVyZSBpcyBpbnRlbmRlZCB0byBhc3N1bWUgZnVuY3Rpb25zIGFzIHZhbHVlcyAodHJ5IHdpdGggYHRyZWVgIG9yIGBycGFydGApCgpgYGB7cn0KcHJpbnQoY29tcHV0ZUVycm9yUmF0ZSgiU3BlY2llcyIsIGQsIHRyZWUpKQpwcmludChjb21wdXRlRXJyb3JSYXRlKCJTcGVjaWVzIiwgZCwgcnBhcnQpKQpgYGAKCkEgc2ltaWxhciBmdW5jdGlvbiBjb3VsZCBiZSBtYWRlIGZvciBkb2luZyBrLWZvbGQgQ1Ygb3IgTE9PQ1YuCkRvIGl0IGJ5IHlvdXJzZWxmLgoKV2UgY2FuIGRvIG5pY2UgdGhpbmdzIHdpdGggc3VjaCBhIGZ1bmN0aW9uLgpGb3IgZXhhbXBsZSwgd2UgY2FuIGNvbXB1dGUgdGhlIGVycm9yIGZvciBkaWZmZXJlbnQgdmFsdWVzIG9mIHRoZSBsZWFybmluZy10by10ZXN0IGRhdGEgcmF0aW8gKHRoYXQgMC44IGFib3ZlKToKYGBge3IsZWNobz1GfQpyZXF1aXJlKHRpZHl2ZXJzZSkKYGBgCmBgYHtyfQpyYXRpbyA9IHNlcSgwLjEsIDAuOTksIDAuMDUpCmVycm9yID0gcmF0aW8gJT4lIG1hcF9kYmwoZnVuY3Rpb24ocil7Y29tcHV0ZUVycm9yUmF0ZSgiU3BlY2llcyIsIGQsIHRyZWUsIHIpfSkKdGliYmxlKHJhdGlvPXJhdGlvLCBlcnJvcj1lcnJvcikgJT4lIGdncGxvdChhZXMoeD1yYXRpbyx5PWVycm9yKSkrZ2VvbV9saW5lKCkreWxpbSgwLDEpCmBgYAoKIyMjIyBGb3IgcmVncmVzc2lvbgoKTm93LCBsZXQncyBkbyAidGhlIHNhbWUiIG9uIHRoZSBmaXJzdCBudW1lcmljYWwgdmFyaWFibGU6CmBgYHtyfQpuYW1lcyhkKQpgYGAKCmBgYHtyfQp0LnNsID0gdHJlZShTZXBhbC5MZW5ndGh+LiwgZFtpbmRleGVzLmxlYXJuaW5nLF0pCnByZWRpY3RlZC55ID0gcHJlZGljdCh0LnNsLCBkWy1pbmRleGVzLmxlYXJuaW5nLF0sIHR5cGU9InZlY3RvciIpCnByZWRpY3RlZC55CmBgYAoKV2FpdCEgTm93IHRoZSAkeSQgaXMgbnVtZXJpY2FsLCB3ZSBjYW5ub3QganVzdCBjaGVjayBmb3IgZXF1YWxpdHksIHdlIG5lZWQgdG8gY29tcHV0ZSBhbm90aGVyIG1ldHJpYyBpbnN0ZWFkIG9mIHRoZSBjbGFzc2lmaWNhdGlvbiBlcnJvci4KV2UgY291bGQgZ28gZm9yIHRoZSBSU1MsIG9yIHRoZSBNQUUsIG9yIHRoZSBNU0UuLi4KTGV0J3MgZ28gd2l0aCB0aGUgTUFFICgqbWVhbiBhYnNvbHV0ZSBlcnJvciopOgpgYGB7cn0KZXJyb3Iuc2wgPSBtZWFuKGFicyhwcmVkaWN0ZWQueS1kJFNlcGFsLkxlbmd0aFstaW5kZXhlcy5sZWFybmluZ10pKQplcnJvci5zbApgYGAKCkFnYWluLCB3ZSBjYW4gd3JpdGUgYSBmdW5jdGlvbiBmb3IgZG9pbmcgZXZlcnl0aGluZzoKYGBge3J9CmNvbXB1dGVNQUUgPSBmdW5jdGlvbihudW1lcmljYWwueS5uYW1lLCBkYXRhLCBsZWFybmVyLCBwLmxlYXJuID0gMC44LCAuLi4pIHsKICBpbmRleGVzLmxlYXJuaW5nID0gc2FtcGxlKGMoMTpucm93KGRhdGEpKSlbMToobnJvdyhkYXRhKSpwLmxlYXJuKV0KICBtb2RlbCA9IGxlYXJuZXIoZm9ybXVsYShwYXN0ZTAobnVtZXJpY2FsLnkubmFtZSwifi4iKSksIGRhdGFbaW5kZXhlcy5sZWFybmluZyxdLCAuLi4pCiAgcHJlZGljdGVkLnkgPSBwcmVkaWN0KG1vZGVsLCBkWy1pbmRleGVzLmxlYXJuaW5nLCBdLCB0eXBlPSJ2ZWN0b3IiKQogIG1lYW4oYWJzKHByZWRpY3RlZC55LWRbLWluZGV4ZXMubGVhcm5pbmcsIG51bWVyaWNhbC55Lm5hbWVdKSkKfQpgYGAKCiMjIFN0ZXAgMzogZXhlY3V0aW9uIGFuZCBhbnN3ZXIKClNpbmNlIHdlIGluZGVlZCBidWlsdCB0aGUgZnVuY3Rpb25zLCB3ZSBjYW4gbm93IGNvbXB1dGUgdGhlIGVycm9ycyBmb3IgYWxsIHRoZSB2YXJpYWJsZXM6CmBgYHtyfQpmb3IgKHZhcmlhYmxlIGluIG5hbWVzKGQpKSB7CiAgaWYgKGlzLm51bWVyaWMoZFssdmFyaWFibGVdKSkgewogICAgZXJyb3IgPSBjb21wdXRlTUFFKHZhcmlhYmxlLCBkLCB0cmVlKQogIH0gZWxzZSB7CiAgICBlcnJvciA9IGNvbXB1dGVFcnJvclJhdGUodmFyaWFibGUsIGQsIHRyZWUpCiAgfQogIGNhdChwYXN0ZSh2YXJpYWJsZSwgZXJyb3IsICJcbiIpKQp9CmBgYAoKQXBwYXJlbnRseSwgdGhlIGxhcmdlc3QgZXJyb3IgaXMgb2J0YWluZWQgZm9yIFBldGFsLkxlbmd0aCwgc28gaXQgaXMgdGhlIGhhcmRlc3QgdmFyaWFibGUgdG8gYmUgcHJlZGljdGVkLgoKQnV0LCB3YWl0IQoqKkNvbXBhcmluZyBhbiBlcnJvciByYXRlIChzb21ldGhpbmcgdGhhdCBpcywgYnkgZGVmaW5pdGlvbiwgaW4gJFswLDFdJCkgYWdhaW5zdCBhIE1BRSAodGhhdCBpcywgaW4gZ2VuZXJhbCwgdW5ib3VuZGVkKSwgaXMgcG9pbnRsZXNzISoqCihBcyBhbiBleGFtcGxlLCB0cnkgbXVsdGlwbHlpbmcgb25lIG9mIHRoZSBudW1lcmljYWwgdmFyaWFibGUgYnkgMTAgYW5kIHNlZSB0aGF0IHRoZSBlcnJvciB3aWxsIGluY3JlYXNlIGJ5IDEwIHRvbyEpLgpUaGlzIGlzIGludHJpbnNpY2FsbHkgcmVsYXRlZCB0byB0aGUgbmF0dXJlIG9mIHRoZSAkeSQgaW4gdGhlIHR3byBjYXNlczogY2F0ZWdvcmljYWwgYW5kIG51bWVyaWNhbC4KSXQgdHVybnMgb3V0LCBoZW5jZSwgdGhhdCB0aGUgaWRlYSBvZiB1c2luZyAidGhlIGVmZmVjdGl2ZW5lc3Mgb2YgYSBtb2RlbCBsZWFybmVkIHdpdGggZGVmYXVsdCBwYXJhbWV0ZXIiIGFzIGEgcHJveHkgZm9yIHRoZSBoYXJkbmVzcyAoYWN0dWFsbHksIHRoZSBvcHBvc2l0ZSBvZiB0aGUgZWZmZWN0aXZlbmVzcyksIGlzIHdyb25nLgoKSG93IGNhbiB3ZSBvdmVyY29tZSB0aGlzIGxpbWl0YXRpb24/CgojIyBBbHRlcm5hdGl2ZSBzdGVwIDE6IHJlLWRlc2lnbgoKU2luY2UgdGhlIHByb2JsZW0gaXMgdGhhdCB3ZSBjYW5ub3QgY29tcGFyZSB0aGUgYWJzb2x1dGUgdmFsdWVzIG9mIHRoZSBlcnJvciwgb25lIHBvc3NpYmlsaXR5IGlzIHRvIGNvbnNpZGVyIHRoZSBjb21wbGV4aXR5IGEgbW9kZWwgZXhoaWJpdHMgZm9yIG9idGFpbmluZyBhbiBlcnJvciBvZiAwLgpUaGUgcmF0aW9uYWxlIGlzIHRoYXQgMCBpcyBjb21wYXJhYmxlIHdpdGggMCwgc2luY2UgdGhleSBib3RoIHJlcHJlc2VudCB0aGUgYWJzZW5jZSBvZiBhbnkgZXJyb3IsIHJlZ2FyZGxlc3Mgb2YgdGhlIHNjYWxlLgpXZSBuZWVkIGhlbmNlIGEgd2F5IGZvciBtZWFzdXJpbmcgdGhlIGNvbXBsZXhpdHk6IGZvciB0cmVlcywgdGhpcyBpcyBlYXN5LCBzaW5jZSB3ZSBjYW4gc2ltcGx5IGNvdW50IHRoZSBudW1iZXIgb2Ygbm9kZXMuCgpTbyBzdW1tYXJpemluZywgdGhlIG5ldyBtZXRob2QgZm9yIGFuc3dlcmluZyB3b3VsZCBiZToKCjEuIGZvciBlYWNoIHZhcmlhYmxlCiAgICBhLiBsZWFybiBhIHRyZWUgdGhhdCBvYnRhaW5zIGEgMCBlcnJvciAKICAgIGIuIGNvdW50IHRoZSBudW1iZXIgb2Ygbm9kZXMgb2YgdGhlIHRyZWUKMi4gc2F5IHRoYXQgdGhlIGhhcmRlc3QgdmFyaWFibGUgaXMgdGhlIG9uZSBmb3Igd2hpY2ggdGhlIGNvdW50IGlzIHRoZSBncmVhdGVzdAoKTm90ZSB0aGF0IHdpdGggdGhlIHRyZWUgbGVhcm5pbmcgcHJvY2VkdXJlIHRoYXQgd2Uga25vdywgb25lIGNhbiBvYnRhaW4gYSB0cmVlIHdpdGggZXJyb3IgMCBieSBzaW1wbHkgc2V0dGluZyAkbl9cdGV4dHttaW59JCAocHJvdmlkZWQgdGhhdCB0aGUgZGF0YXNldCAkXHsoeF57KGkpfSx5XnsoaSl9KVx9X2kkIGRvZXMgbm90IGNvbnRhaW4gZGlmZmVyZW50IG9ic2VydmF0aW9ucyB3aXRoIHRoZSBzYW1lICR5JCkuCgojIyBBbHRlcm5hdGl2ZSBzdGVwIDI6IGltcGxlbWVudGF0aW9uCgpXZSBuZWVkIHRvIGZpbmQgb3V0OgoKLSBob3cgdG8gc2V0IHRoZSAkbl9cdGV4dHttaW59JCBpbiBgdHJlZSgpYAotIGhvdyB0byBjb3VudCB0aGUgbnVtYmVyIG9mIG5vZGVzIGluIGEgdHJlZQoKQnkgZGlnZ2luZyB0aGUgZG9jdW1lbnRhdGlvbiBvZiBgdHJlZSgpYCB3ZSBmaW5kIHRoYXQgdGhlIHBhcmFtZXRlcnMgZm9yIHRoZSBsZWFybmluZyBwcm9jZWR1cmUgYXJlIGRlc2NyaWJlZCBpbiB0aGUgZG9jdW1lbnRhdGlvbiBvZiB0aGUgZnVuY3Rpb24gYHRyZWUuY29udHJvbCgpYC4KSXQgaXMgc2FpZCB0aGF0OgoKPiBUbyBwcm9kdWNlIGEgdHJlZSB0aGF0IGZpdHMgdGhlIGRhdGEgcGVyZmVjdGx5LCBzZXQgYG1pbmRldiA9IDBgIGFuZCBgbWluc2l6ZSA9IDJgLCBpZiB0aGUgbGltaXQgb24gdHJlZSBkZXB0aCBhbGxvd3Mgc3VjaCBhIHRyZWUuCgpCeSByZWFkaW5nIGZ1cnRoZXIsIHdlIGluZmVyIHRoYXQgYG1pbnNpemVgIGlzIHJlbGF0ZWQgd2l0aCAkbl9cdGV4dHttaW59JC4KCkxldCdzIHRyeS4KTm9ybWFsIHRyZWUgKGRlZmF1bHQgZmxleGliaWxpdHksIGkuZS4sIGRlZmF1bHQgdmFsdWUgZm9yIGBtaW5zaXplYCk6CmBgYHtyfQp0PXRyZWUoU3BlY2llc34uLCBkKQpwbG90KHQpCnRleHQodCkKYGBgCgpDb21wbGV4IHRyZWU6CmBgYHtyfQp0PXRyZWUoU3BlY2llc34uLCBkLCBtaW5kZXY9MCwgbWluc2l6ZT0yKQpwbG90KHQpCnRleHQodCkKYGBgClllcyEKSXQgaXMgbGFyZ2VyLCBhbmQgYWxzbyB1Z2xpZXIuLi4KKEFzIGEgY29uc2VxdWVuY2UsIGl0IGlzIGxlc3MgaW50ZXJwcmV0YWJsZSkuCgpDb25jZXJuaW5nIHRoZSBjb3VudCBvZiBub2RlcywgYnkgcmVhZGluZyB0aGUgZG9jdW1lbnRhdGlvbiBvZiBgdHJlZSgpYCBpbiB0aGUgVmFsdWUgc2VjdGlvbiwgd2UgZm91bmQgdGhhdCBhICJjb21wb25lbnQiIG9mIHRoZSByZXR1cm5lZCB2YWx1ZSwgbmFtZWQgYGZyYW1lYCBpczoKCj4gQSBkYXRhIGZyYW1lIHdpdGggYSByb3cgZm9yIGVhY2ggbm9kZSwgYW5kIGByb3cubmFtZXNgIGdpdmluZyB0aGUgbm9kZSBudW1iZXJzLgoKU28gd2UganVzdCBuZWVkIHRvIGNvdW50IHRoZSByb3dzIG9mIHRoYXQgZGF0YWZyYW1lOgpgYGB7cn0KbnJvdyh0cmVlKFNwZWNpZXN+LiwgZCkkZnJhbWUpCmBgYAoKSXQgd29ya3M6IGNoZWNrIHdpdGggdGhlIGNvcnJlc3BvbmRpbmcgaW1hZ2UgYWJvdmUuCgpTbyBsZXQncyB3cml0ZSBhIGZ1bmN0aW9uIGZvciBkb2luZyBldmVyeXRoaW5nOgpgYGB7cn0KY29tcHV0ZUNvbXBsZXhpdHlPZlBlcmZlY3RUcmVlID0gZnVuY3Rpb24gKHkubmFtZSwgZGF0YSkgewogIHQgPSB0cmVlKGZvcm11bGEocGFzdGUwKHkubmFtZSwifi4iKSksIGRhdGEsIG1pbmRldj0wLCBtaW5zaXplPTIpCiAgbnJvdyh0JGZyYW1lKQp9CmNvbXB1dGVDb21wbGV4aXR5T2ZQZXJmZWN0VHJlZSgiU3BlY2llcyIsIGQpCmBgYAoKIyMgQWx0ZXJuYXRpdmUgc3RlcCAzOiBleGVjdXRpb24gYW5kIGFuc3dlcgoKQXMgYmVmb3JlOgpTaW5jZSB3ZSBpbmRlZWQgYnVpbHQgdGhlIGZ1bmN0aW9ucywgd2UgY2FuIG5vdyBjb21wdXRlIHRoZSBlcnJvcnMgZm9yIGFsbCB0aGUgdmFyaWFibGVzOgpgYGB7cn0KZm9yICh2YXJpYWJsZSBpbiBuYW1lcyhkKSkgewogIGNvbXBsZXhpdHkgPSBjb21wdXRlQ29tcGxleGl0eU9mUGVyZmVjdFRyZWUodmFyaWFibGUsIGQpCiAgY2F0KHBhc3RlKHZhcmlhYmxlLCBjb21wbGV4aXR5LCAiXG4iKSkKfQpgYGAKClNvLCBhY2NvcmRpbmcgdG8gdGhpcyBtZXRob2QsIGl0IHR1cm5zIG91dCB0aGF0IHRoZSBoYXJkZXN0IHZhcmlhYmxlIGlzIFNlcGFsLldpZHRoLCBkaWZmZXJlbnRseSB0aGFuIHdpdGggdGhlIHByZXZpb3VzIG1ldGhvZC4KCk5vdGUgdGhhdCB0aGUgaW1wb3J0YW50IHRoaW5nIGhlcmUgaXMgbm90IHRoZSBhY3R1YWwgYW5zd2VyLCBidXQgdGhlIGZ1bGwgcGF0aCB0byB0aGUgYW5zd2VyLgpOb3RlIGFsc28gdGhhdCB0aGlzIG1ldGhvZCBzdGlsbCBoYXMgdGhlIGRyYXdiYWNrIG9mIGNvbXBhcmluZyBhcHBsZXMgYW5kIG9yYW5nZXM6IGluZGVlZCB0aGUgY29tcGxleGl0eSBvZiB0aGUgb2J0YWluZWQgdHJlZXMgY2xlYXJseSBzaG93cyB0aGF0IHRoZSB2YWx1ZXMgYXJlIHZlcnkgZGlmZmVyZW50IGZvciB0aGUgY2F0ZWdvcmljYWwgYW5kIG51bWVyaWNhbCB2YXJpYWJsZXMuClRoaXMgaXMgYmVjYXVzZSB0aGUgZm9ybWVyIGp1c3QgdGFrZXMgMyB2YWx1ZXMsIHdoaWxlIHRoZSBsYXR0ZXIgb25lcyB0YWtlIG1hbnkgb2YgdGhlbTogd2hlbiB3ZSBpbXBvc2UgdGhlIFJTUz0wIGFzIHRoZSBvbmx5IHN0b3BwaW5nIGNyaXRlcmlvbiwgYWxsIG9mIHRoZW0gYmFzaWNhbGx5IGFjdCBhcyBjbGFzcyB2YWx1ZXMuIEl0IGlzIGhlbmNlIHNvbWVob3cgZXhwZWN0ZWQgdGhhdCBtdWNoIG1vcmUgbm9kZXMgYXJlIG5lZWRlZCB0byBwZXJmZWNseSBhY2NvbW9kYXRlIGFsbCB0aG9zZSBjbGFzcyB2YWx1ZXMu