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:
- to be able to “try to predict”
- 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:
- for each variable
- learn a tree that obtains a 0 error
- count the number of nodes of the tree
- 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