Last updated: 2021-04-27 18:25:43 EST
Go to quick tables Go home
Set-up
Load the tidyverse:
The tidyverse
provides the data mpg
to demonstrate methods. mpg
has the mileage (cty
for city, hwy
for highway) for a bunch of different cars:
data("mpg") # load the data mpg
# view the first 5 rows
mpg %>%
slice_head(n=5)
| | | | | | | | | | |
---|
audi | a4 | 1.8 | 1999 | 4 | auto(l5) | f | 18 | 29 | p | |
audi | a4 | 1.8 | 1999 | 4 | manual(m5) | f | 21 | 29 | p | |
audi | a4 | 2.0 | 2008 | 4 | manual(m6) | f | 20 | 31 | p | |
audi | a4 | 2.0 | 2008 | 4 | auto(av) | f | 21 | 30 | p | |
audi | a4 | 2.8 | 1999 | 6 | auto(l5) | f | 16 | 26 | p | |
Summarizing Data
The pipe and summarise()
All summarizing operations work like this:
data %>% # the pipe: "and then"
summarise()
Inside summarise()
you can use functions like mean()
, median()
and so on.
mean
mean()
mpg %>% # take the data, THEN
summarise(mean(cty)) # summarize: mean
Note. cty
is a numerical variable.
standard deviation
sd()
mpg %>% # take the data, THEN
summarise(sd(cty)) # summarize: standard deviation
counting observations
n()
(counting)
mpg %>% # take the data, THEN
summarise(n()) # summarize: count number of observations
Why doesn’t n()
take an argument? Because it counts the number of rows, not columns.
multiple summaries
You can put as many functions as you want inside summarise()
:
mpg %>% # take the data, THEN
summarise(mean(cty), median(cty), sd(cty), n()) # summarize: mean, median, standard deviation, count
naming variables inside summarise()
You can name variables inside summarise()
using standard assignment:
mpg %>% # take the data, THEN
summarise(avg_cty = mean(cty), median_cty = median(cty)) # summarize: mean, median
Grouping (group_by()
)
group_by()
:
mpg %>% # take the data, THEN
group_by(class) %>% # group it by car class (e.g., compact, pickup) THEN
summarise(mean(cty), median(cty), sd(cty), n()) # summarize
| | | | |
---|
2seater | 15.40000 | 15 | 0.5477226 | 5 |
compact | 20.12766 | 20 | 3.3854999 | 47 |
midsize | 18.75610 | 18 | 1.9465416 | 41 |
minivan | 15.81818 | 16 | 1.8340219 | 11 |
pickup | 13.00000 | 13 | 2.0463382 | 33 |
subcompact | 20.37143 | 19 | 4.6023377 | 35 |
suv | 13.50000 | 13 | 2.4208791 | 62 |
Note. class
is a categorical variable.
Manipulating data
Selecting columns
select()
certain columns
mpg %>% # all the columns
slice_head(n=5) # view the first 5 rows
| | | | | | | | | | |
---|
audi | a4 | 1.8 | 1999 | 4 | auto(l5) | f | 18 | 29 | p | |
audi | a4 | 1.8 | 1999 | 4 | manual(m5) | f | 21 | 29 | p | |
audi | a4 | 2.0 | 2008 | 4 | manual(m6) | f | 20 | 31 | p | |
audi | a4 | 2.0 | 2008 | 4 | auto(av) | f | 21 | 30 | p | |
audi | a4 | 2.8 | 1999 | 6 | auto(l5) | f | 16 | 26 | p | |
mpg %>% # all the columns
select(manufacturer, model, hwy) %>% # select some of the columns
slice_head(n=5) # view the first 5 rows
| | | | |
---|
audi | a4 | 29 | | |
audi | a4 | 29 | | |
audi | a4 | 31 | | |
audi | a4 | 30 | | |
audi | a4 | 26 | | |
Slicing
All slice operators begin with slice_
and take an optional n=
argument that specifies the number of rows you want to see. More here.
Subsets with slice_head()
:
mpg %>%
slice_head(n=10) # view the first 10 rows
| | | | | | | | | | |
---|
audi | a4 | 1.8 | 1999 | 4 | auto(l5) | f | 18 | 29 | p | |
audi | a4 | 1.8 | 1999 | 4 | manual(m5) | f | 21 | 29 | p | |
audi | a4 | 2.0 | 2008 | 4 | manual(m6) | f | 20 | 31 | p | |
audi | a4 | 2.0 | 2008 | 4 | auto(av) | f | 21 | 30 | p | |
audi | a4 | 2.8 | 1999 | 6 | auto(l5) | f | 16 | 26 | p | |
audi | a4 | 2.8 | 1999 | 6 | manual(m5) | f | 18 | 26 | p | |
audi | a4 | 3.1 | 2008 | 6 | auto(av) | f | 18 | 27 | p | |
audi | a4 quattro | 1.8 | 1999 | 4 | manual(m5) | 4 | 18 | 26 | p | |
audi | a4 quattro | 1.8 | 1999 | 4 | auto(l5) | 4 | 16 | 25 | p | |
audi | a4 quattro | 2.0 | 2008 | 4 | manual(m6) | 4 | 20 | 28 | p | |
Maximums with slice_max()
:
mpg %>%
slice_max(hwy, n=5) # the five highest highway miles per gallon. if there are ties they all get printed
| | | | | | | | | | |
---|
volkswagen | jetta | 1.9 | 1999 | 4 | manual(m5) | f | 33 | 44 | d | |
volkswagen | new beetle | 1.9 | 1999 | 4 | manual(m5) | f | 35 | 44 | d | |
volkswagen | new beetle | 1.9 | 1999 | 4 | auto(l4) | f | 29 | 41 | d | |
toyota | corolla | 1.8 | 2008 | 4 | manual(m5) | f | 28 | 37 | r | |
honda | civic | 1.8 | 2008 | 4 | auto(l5) | f | 25 | 36 | r | |
honda | civic | 1.8 | 2008 | 4 | auto(l5) | f | 24 | 36 | c | |
Minimums with slice_min()
:
mpg %>%
slice_min(hwy, n=2) # the two lowest highway miles per gallon. if there are ties they all get printed
| | | | | | | | | |
---|
dodge | dakota pickup 4wd | 4.7 | 2008 | 8 | auto(l5) | 4 | 9 | 12 | |
dodge | durango 4wd | 4.7 | 2008 | 8 | auto(l5) | 4 | 9 | 12 | |
dodge | ram 1500 pickup 4wd | 4.7 | 2008 | 8 | auto(l5) | 4 | 9 | 12 | |
dodge | ram 1500 pickup 4wd | 4.7 | 2008 | 8 | manual(m6) | 4 | 9 | 12 | |
jeep | grand cherokee 4wd | 4.7 | 2008 | 8 | auto(l5) | 4 | 9 | 12 | |
Random samples with slice_sample()
:
mpg %>%
slice_sample(n=3) # pick three rows at random
| | | | | | | | | |
---|
pontiac | grand prix | 3.8 | 1999 | 6 | auto(l4) | f | 16 | 26 | |
dodge | ram 1500 pickup 4wd | 4.7 | 2008 | 8 | manual(m6) | 4 | 9 | 12 | |
audi | a4 quattro | 1.8 | 1999 | 4 | manual(m5) | 4 | 18 | 26 | |
Slicing and grouping
When you group_by()
a data frame slice_()
will return subsets of each group:
mpg %>%
group_by(class) %>% # group by class
slice_min(hwy, n=1) # bottom two hwy by class
| | | | | | | | | |
---|
chevrolet | corvette | 5.7 | 1999 | 8 | auto(l4) | r | 15 | 23 | |
volkswagen | jetta | 2.8 | 1999 | 6 | auto(l4) | f | 16 | 23 | |
audi | a6 quattro | 4.2 | 2008 | 8 | auto(s6) | 4 | 16 | 23 | |
dodge | caravan 2wd | 3.3 | 2008 | 6 | auto(l4) | f | 11 | 17 | |
dodge | dakota pickup 4wd | 4.7 | 2008 | 8 | auto(l5) | 4 | 9 | 12 | |
dodge | ram 1500 pickup 4wd | 4.7 | 2008 | 8 | auto(l5) | 4 | 9 | 12 | |
dodge | ram 1500 pickup 4wd | 4.7 | 2008 | 8 | manual(m6) | 4 | 9 | 12 | |
ford | mustang | 5.4 | 2008 | 8 | manual(m6) | r | 14 | 20 | |
dodge | durango 4wd | 4.7 | 2008 | 8 | auto(l5) | 4 | 9 | 12 | |
jeep | grand cherokee 4wd | 4.7 | 2008 | 8 | auto(l5) | 4 | 9 | 12 | |
If a data frame is already grouped then slice_()
will always subset by group under the hood. If you want subsets not by group then you have to ungroup()
. See below in the section on mutate()
. This often happens when you group a data frame to create a new variable.
Filtering
filter()
with Boolean logic:
==
: “equal to”
!=
: “not equal to”
>
: “greater than”
>=
: “greater than or equal to”
<
: “less than”
<=
: “less than or equal to”
&
: “and”
|
: “or”
Boolean logic is any test that returns true or false:
[1] FALSE
[1] TRUE
[1] TRUE
Some examples:
mpg %>%
filter(year == "1999") %>% # filter all cars made in 1999
slice_head(n=5) # view the first 5 rows
| | | | | | | | | | |
---|
audi | a4 | 1.8 | 1999 | 4 | auto(l5) | f | 18 | 29 | p | |
audi | a4 | 1.8 | 1999 | 4 | manual(m5) | f | 21 | 29 | p | |
audi | a4 | 2.8 | 1999 | 6 | auto(l5) | f | 16 | 26 | p | |
audi | a4 | 2.8 | 1999 | 6 | manual(m5) | f | 18 | 26 | p | |
audi | a4 quattro | 1.8 | 1999 | 4 | manual(m5) | 4 | 18 | 26 | p | |
mpg %>%
filter(hwy >= 25) %>% # filter all cars with at least 25 mpg highway
slice_head(n=5) # view the first 5 rows
| | | | | | | | | | |
---|
audi | a4 | 1.8 | 1999 | 4 | auto(l5) | f | 18 | 29 | p | |
audi | a4 | 1.8 | 1999 | 4 | manual(m5) | f | 21 | 29 | p | |
audi | a4 | 2.0 | 2008 | 4 | manual(m6) | f | 20 | 31 | p | |
audi | a4 | 2.0 | 2008 | 4 | auto(av) | f | 21 | 30 | p | |
audi | a4 | 2.8 | 1999 | 6 | auto(l5) | f | 16 | 26 | p | |
mpg %>%
filter(year == "1999" & model != "a4" & hwy >= 25) %>% # filter cars made in 1999, that aren't a4's, and have at least 25 mgp highway
slice_head(n=5) # view the first 5 rows
| | | | | | | | | | |
---|
audi | a4 quattro | 1.8 | 1999 | 4 | manual(m5) | 4 | 18 | 26 | p | |
audi | a4 quattro | 1.8 | 1999 | 4 | auto(l5) | 4 | 16 | 25 | p | |
audi | a4 quattro | 2.8 | 1999 | 6 | auto(l5) | 4 | 15 | 25 | p | |
audi | a4 quattro | 2.8 | 1999 | 6 | manual(m5) | 4 | 17 | 25 | p | |
chevrolet | corvette | 5.7 | 1999 | 8 | manual(m6) | r | 16 | 26 | p | |
Mutating
Create and modify variables (data frame columns) with mutate()
Create a new variable
For example, create a column called mean_hwy
that calculates average highway miles per gallon:
mpg %>%
mutate(mean_hwy = mean(hwy)) %>%
select(manufacturer, model, hwy, mean_hwy) %>%
slice_head(n=5)
| | | | |
---|
audi | a4 | 29 | 23.44017 | |
audi | a4 | 29 | 23.44017 | |
audi | a4 | 31 | 23.44017 | |
audi | a4 | 30 | 23.44017 | |
audi | a4 | 26 | 23.44017 | |
Create a new variable and store it as a column
To save the new variable you have to re-assign the data frame:
mpg = mpg %>%
mutate(mean_hwy = mean(hwy))
Now mpg
has a new column called mean_hwy
.
Mutating while grouping
When you create a new variable and save it to the data you re-assign the data.
When grouping with group_by()
to create a new column, the re-assigned data will be grouped, which can affect the slice_()
functions.
The solution is to ungroup()
after mutate()
.
For instance, add a column called “mean_hwy_class” that calculates average highway miles per gallon by class:
# first create the variable
mpg = mpg %>% # take the data, THEN
group_by(class) %>% # group by class, THEN
mutate(mean_hwy_class = mean(hwy)) %>% # create the new variable, THEN
ungroup() # ungroup the data
Why ungroup()
? Because otherwise slice_()
and other functions won’t return the output you expect. So better safe than sorry. When grouping to create a variable make sure you ungroup at the end.
Now we can slice in general:
mpg %>%
select(manufacturer, model, hwy, mean_hwy_class) %>%
slice_head(n=2)
| | | | |
---|
audi | a4 | 29 | 28.29787 | |
audi | a4 | 29 | 28.29787 | |
Mutating and frequency tables
Use n()
to calculate frequency and then use mutate()
to calculate relative frequency:
mpg %>%
group_by(class) %>%
summarise(frequency = n()) %>%
mutate(relative_frequency = frequency / sum(frequency))
| | | | |
---|
2seater | 5 | 0.02136752 | | |
compact | 47 | 0.20085470 | | |
midsize | 41 | 0.17521368 | | |
minivan | 11 | 0.04700855 | | |
pickup | 33 | 0.14102564 | | |
subcompact | 35 | 0.14957265 | | |
suv | 62 | 0.26495726 | | |
Visualizing data
Plotting distributions
Histograms
Histograms with ggplot
and geom_histogram()
:
mpg %>%
ggplot(aes(x = cty)) + # blank canvas: choose the data and the x-axis variable
geom_histogram() # add geom layer: distribution

Faceted histograms with facet_wrap()
:
mpg %>%
ggplot(aes(x = cty)) + # blank canvas: choose the data and the x-axis variable
geom_histogram() + # add geom layer: distribution
facet_wrap(~class) # add facetting (note the "~" before the categorical variable)

Cumulative distributions
Percentiles and cumulative distributions with ggplot
and stat_ecdf()
:
mpg %>%
ggplot(aes(x=cty)) + # blank canvas: choose the data and the x-axis variable
stat_ecdf() # add geom layer: cumulative distribution

You can add a vertical line with geom_vline()
to make it easier to see a percentile. For instance, view percent of cars with less than 15 miles per gallon:
mpg %>%
ggplot(aes(x=cty)) + # blank canvas: choose the data and the x-axis variable
stat_ecdf() + # add geom layer: cumulative distribution
geom_vline(xintercept = 15, color="red") # verticle

Box plots
geom_boxplot()
mpg %>%
ggplot(aes(x = hwy, y = class)) + # x is continuous, y is categorical
geom_boxplot()

or:
mpg %>%
ggplot(aes(x = class, y = hwy)) + # x is categorical, y is continuous
geom_boxplot()

Scatterplot
geom_point()
:
mpg %>%
ggplot(aes(x = cty, y = hwy)) +
geom_point()

Scatterplot with regression line
geom_smooth(method = "lm")
:
mpg %>%
ggplot(aes(x = cty, y = hwy)) +
geom_point() + # scatter-plot
geom_smooth(method = "lm") # regression line

Correlation and regression
Covariance
cov()
:
mpg %>%
summarise(cov(x=cty, y=hwy))
Correlation
cor()
:
mpg %>%
summarise(cor(x=cty, y=hwy))
Regression
lm()
Simple linear regression (one x variable):
y=f(x)=β0+β1(x)+ϵ In lm()
the y and x variables are separated by ~
:
mpg %>%
lm(formula = cty ~ hwy, data = .) # the "." means "use the data from the pipe %>%"
Call:
lm(formula = cty ~ hwy, data = .)
Coefficients:
(Intercept) hwy
0.8442 0.6832
Multiple linear regression (multiple x variables):
y=f(X)=f(x1,x2,…)=β0+β1(x1)+β2(x2)+⋯+ϵ In lm()
the x variables are separated by +
:
mpg %>%
lm(formula = cty ~ hwy + cyl + displ, data = .)
Call:
lm(formula = cty ~ hwy + cyl + displ, data = .)
Coefficients:
(Intercept) hwy cyl displ
6.08786 0.58092 -0.44827 -0.05935
summary()
View hypothesis tests and regression diagnostics with summary()
:
mpg %>%
lm(formula = cty ~ hwy + cyl + displ, data = .) %>%
summary()
Call:
lm(formula = cty ~ hwy + cyl + displ, data = .)
Residuals:
Min 1Q Median 3Q Max
-3.0347 -0.6012 -0.0229 0.7397 5.2573
Coefficients:
Estimate Std. Error t value Pr(>|t|)
(Intercept) 6.08786 0.83226 7.315 4.25e-12 ***
hwy 0.58092 0.02010 28.900 < 2e-16 ***
cyl -0.44827 0.13010 -3.446 0.000677 ***
displ -0.05935 0.16351 -0.363 0.716971
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
Residual standard error: 1.148 on 230 degrees of freedom
Multiple R-squared: 0.9281, Adjusted R-squared: 0.9272
F-statistic: 989.9 on 3 and 230 DF, p-value: < 2.2e-16
Plotting regression results
Use the package sJplot
. (If you forgot how to install package, see here.)
The function you want is plot_model
. The baseline plot shows the coefficients and standard errors:
mpg %>%
lm(formula = cty ~ hwy + class, data = .) %>%
plot_model(model = .)

To plot predicted values you can set type = "pred"
and then choose which coefficient to plot by setting terms
:
mpg %>%
lm(formula = cty ~ hwy + class, data = .) %>%
plot_model(model = ., type = "pred", terms = c("hwy"))

The terms
argument accepts multiple terms:
mpg %>%
lm(formula = cty ~ hwy + class, data = .) %>%
plot_model(model = ., type = "pred", terms = c("hwy", "class"))

This is useful when you have an interaction effect (e.g., hwy*class
):
mpg %>%
lm(formula = cty ~ hwy + class + hwy*class, data = .) %>%
plot_model(model = ., type = "pred", terms = c("hwy", "class"))

sJplot
uses ggplot
so you can add ggplot
stuff to it, like a different theme and titles:
mpg %>%
lm(formula = cty ~ hwy + class + hwy*class, data = .) %>%
plot_model(model = ., type = "pred", terms = c("hwy", "class")) +
labs(x = "Highway miles per gallon", y = "Predicted city miles per gallon",
title = "A very interesting linear model", subtitle = "So interesting") +
theme_minimal()

The package has tons of great features. Check out the website!
Quick tables
%>% |
Pipe operator (“and then”) |
summarise() |
Summarize a vector or multiple vectors from a data frame |
mean() |
Calculate the mean of a vector |
median() |
Calculate the median of a vector |
sd() |
Calculate the standard deviation of a vector |
n() |
Count the number of observations. Takes no argument. |
group_by() |
Group observations by a categorical variable |
select() |
Select certain columns |
slice_() |
Slice rows from the data |
slice_head(n=5) |
View the head (first five rows) of the data. n = can be any number. |
slice_max(var, n=5) |
View the rows with the 5 highest values of column “var” |
slice_min(var, n=5) |
View the rows with the 5 lowest values of column “var” |
slice_sample(n=5) |
Draw 5 rows at random |
filter() |
Filter observations |
mutate() |
Create a new vector |
cov() |
Calculate the covariation between two variables |
cor() |
Calculate the correlation between two variables |
lm() |
Estimate a linear regression |
ifelse() |
Create a vector based on a “True/False” test |
ggplot() |
Create a base plot |
geom_histogram() |
Histogram |
stat_ecdf() |
Cumulative distribution plot |
geom_boxplot() |
Boxplot |
geom_point() |
Scatter plot |
geom_smooth(method = “lm”) |
Regression line |
xi |
data point i |
“x i” |
|
n |
sample size |
|
|
N |
population size |
|
|
ˉx |
the sample mean |
“x bar” |
∑ni=1xin |
μ |
the population mean |
“mu” |
|
s2 |
the sample variance |
|
∑(xi−ˉx)2n−1 |
σ2 |
the population variance |
|
|
s |
the sample standard deviation |
|
√s2 |
σ |
the population standard deviation |
“sigma” |
|
zi |
z-score for observation i |
|
zi=xi−ˉxs |
β |
regression coefficient |
“beta” |
y=β0+β1x+ϵ |
ˆβ |
estimated value of regression coefficient |
“beta hat” |
|
ϵ |
regression error |
“epsilon” |
|
∑ |
summation operator |
“sum” |
∑2i=1xi=x1+x2 |
sxy |
covariation between two variables x and y |
|
∑(xi−ˉx)(yi−ˉy)n−1∈[−∞,∞] |
rxy |
correlation between two variables x and y |
|
sxysxsy∈[−1,1] |
LS0tCnRpdGxlOiAiUiBhbmQgdGlkeXZlcnNlIGNvZGUgY2hlY2twb2ludHMiCnN1YnRpdGxlOiAiU3VmZm9sa0Vjb24iCmF1dGhvcjogIiIKZGF0ZTogCm91dHB1dDogCiAgaHRtbF9ub3RlYm9vazoKICAgIG51bWJlcl9zZWN0aW9uczogdHJ1ZQogICAgdGhlbWU6IHJlYWRhYmxlCiAgICBoaWdobGlnaHQ6IHB5Z21lbnRzCiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogCiAgICAgIGNvbGxhcHNlZDogeWVzCi0tLQoKIAo8YSBocmVmPSJodHRwczovL2xyZGVnZWVzdC5naXRodWIuaW8vc3RhdHMvY29kZV9jaGVja3BvaW50cy5uYi5odG1sIiBjbGFzcz0iYnRuIGJ0bi13YXJuaW5nIj4qKkxhc3QgdXBkYXRlZDoqKiBgciBTeXMudGltZSgpYCBFU1QgPC9hPiAKCjxhIGhyZWY9IiN0YWJsZSIgY2xhc3M9ImJ0biBidG4tc3VjY2VzcyI+KipHbyB0byBxdWljayB0YWJsZXMqKjwvYT4KPGEgaHJlZj0iaHR0cHM6Ly9zdWZmb2xrZWNvbi5naXRodWIuaW8vIiBjbGFzcz0iYnRuIGJ0bi1kZWZhdWx0Ij5HbyBob21lPC9hPgoKIyBTZXQtdXAgey19CgpMb2FkIHRoZSB0aWR5dmVyc2U6CgpgYGB7ciBzZXR1cCwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbGlicmFyeSh0aWR5dmVyc2UpIApgYGAKClRoZSBgdGlkeXZlcnNlYCBwcm92aWRlcyB0aGUgZGF0YSBgbXBnYCB0byBkZW1vbnN0cmF0ZSBtZXRob2RzLiBgbXBnYCBoYXMgdGhlIG1pbGVhZ2UgKGBjdHlgIGZvciBjaXR5LCBgaHd5YCBmb3IgaGlnaHdheSkgZm9yIGEgYnVuY2ggb2YgZGlmZmVyZW50IGNhcnM6CgpgYGB7ciBsb2FkIGRhdGF9CmRhdGEoIm1wZyIpICMgbG9hZCB0aGUgZGF0YSBtcGcKCiMgdmlldyB0aGUgZmlyc3QgNSByb3dzCm1wZyAlPiUgCiAgc2xpY2VfaGVhZChuPTUpCmBgYAoKIyBTdW1tYXJpemluZyBEYXRhIAoKIyMgVGhlIHBpcGUgYW5kIGBzdW1tYXJpc2UoKWAKCkFsbCBzdW1tYXJpemluZyBvcGVyYXRpb25zIHdvcmsgbGlrZSB0aGlzOiAKCmBgYHtyLCBldmFsPUZBTFNFfQpkYXRhICU+JSAjIHRoZSBwaXBlOiAiYW5kIHRoZW4iCiAgc3VtbWFyaXNlKCkgCmBgYAoKCiogdGhlIHBpcGUgKGAlPiVgLCAiYW5kIHRoZW4iKQoqIFtgc3VtbWFyaXNlKClgXShodHRwczovL2RwbHlyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL3N1bW1hcmlzZS5odG1sKQoKSW5zaWRlIGBzdW1tYXJpc2UoKWAgeW91IGNhbiB1c2UgZnVuY3Rpb25zIGxpa2UgYG1lYW4oKWAsIGBtZWRpYW4oKWAgYW5kIHNvIG9uLgoKCiMjIyBtZWFuCgpgbWVhbigpYAoKYGBge3IgbWVhbn0KbXBnICU+JSAjIHRha2UgdGhlIGRhdGEsIFRIRU4KICBzdW1tYXJpc2UobWVhbihjdHkpKSAjIHN1bW1hcml6ZTogbWVhbgpgYGAKCioqTm90ZS4qKiBgY3R5YCBpcyBhIG51bWVyaWNhbCB2YXJpYWJsZS4KCiMjIyBtZWRpYW4KCmBtZWRpYW4oKWAKCmBgYHtyIG1lZGlhbn0KbXBnICU+JSAjIHRha2UgdGhlIGRhdGEsIFRIRU4KICBzdW1tYXJpc2UobWVkaWFuKGN0eSkpICMgc3VtbWFyaXplOiBtZWRpYW4KYGBgCgojIyMgc3RhbmRhcmQgZGV2aWF0aW9uCgpgc2QoKWAKCmBgYHtyIHNkfQptcGcgJT4lICMgdGFrZSB0aGUgZGF0YSwgVEhFTgogIHN1bW1hcmlzZShzZChjdHkpKSAjIHN1bW1hcml6ZTogc3RhbmRhcmQgZGV2aWF0aW9uCmBgYAoKIyMjIGNvdW50aW5nIG9ic2VydmF0aW9ucwoKW2BuKClgIChjb3VudGluZyldKGh0dHBzOi8vZHBseXIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2Uvbi5odG1sKQoKCmBgYHtyIGNvdW50fQptcGcgJT4lICMgdGFrZSB0aGUgZGF0YSwgVEhFTgogIHN1bW1hcmlzZShuKCkpICMgc3VtbWFyaXplOiBjb3VudCBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zCmBgYAoKKipXaHkgZG9lc24ndCBgbigpYCB0YWtlIGFuIGFyZ3VtZW50PyoqIEJlY2F1c2UgaXQgY291bnRzIHRoZSBudW1iZXIgb2Ygcm93cywgbm90IGNvbHVtbnMuCgojIyMgbXVsdGlwbGUgc3VtbWFyaWVzCgpZb3UgY2FuIHB1dCBhcyBtYW55IGZ1bmN0aW9ucyBhcyB5b3Ugd2FudCBpbnNpZGUgYHN1bW1hcmlzZSgpYDoKCmBgYHtyIHN1bW1hcmlzZX0KbXBnICU+JSAjIHRha2UgdGhlIGRhdGEsIFRIRU4KICBzdW1tYXJpc2UobWVhbihjdHkpLCBtZWRpYW4oY3R5KSwgc2QoY3R5KSwgbigpKSAjIHN1bW1hcml6ZTogbWVhbiwgbWVkaWFuLCBzdGFuZGFyZCBkZXZpYXRpb24sIGNvdW50CmBgYAoKIyMjIG5hbWluZyB2YXJpYWJsZXMgaW5zaWRlIGBzdW1tYXJpc2UoKWAKCllvdSBjYW4gbmFtZSB2YXJpYWJsZXMgaW5zaWRlIGBzdW1tYXJpc2UoKWAgdXNpbmcgc3RhbmRhcmQgYXNzaWdubWVudDoKCmBgYHtyIG5hbWUgc3VtbWFyaXNlfQptcGcgJT4lICMgdGFrZSB0aGUgZGF0YSwgVEhFTgogIHN1bW1hcmlzZShhdmdfY3R5ID0gbWVhbihjdHkpLCBtZWRpYW5fY3R5ID0gbWVkaWFuKGN0eSkpICMgc3VtbWFyaXplOiBtZWFuLCBtZWRpYW4KYGBgCgoKIyMgR3JvdXBpbmcgKGBncm91cF9ieSgpYCkgIAoKW2Bncm91cF9ieSgpYF0oaHR0cHM6Ly9kcGx5ci50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS9ncm91cF9ieS5odG1sKToKCmBgYHtyIGdyb3VwX2J5LCBtZXNzYWdlPUZBTFNFfQptcGcgJT4lICMgdGFrZSB0aGUgZGF0YSwgVEhFTgogIGdyb3VwX2J5KGNsYXNzKSAlPiUgIyBncm91cCBpdCBieSBjYXIgY2xhc3MgKGUuZy4sIGNvbXBhY3QsIHBpY2t1cCkgVEhFTgogIHN1bW1hcmlzZShtZWFuKGN0eSksIG1lZGlhbihjdHkpLCBzZChjdHkpLCBuKCkpICMgc3VtbWFyaXplCmBgYAoKKipOb3RlLioqIGBjbGFzc2AgaXMgYSBjYXRlZ29yaWNhbCB2YXJpYWJsZS4KCiMgTWFuaXB1bGF0aW5nIGRhdGEKCiMjIFNlbGVjdGluZyBjb2x1bW5zCgpgc2VsZWN0KClgIGNlcnRhaW4gY29sdW1ucwoKYGBge3Igbm8gc2VsZWN0fQptcGcgJT4lICMgYWxsIHRoZSBjb2x1bW5zCiAgc2xpY2VfaGVhZChuPTUpICMgdmlldyB0aGUgZmlyc3QgNSByb3dzCmBgYAoKCmBgYHtyIHllcyBzZWxlY3R9Cm1wZyAlPiUgIyBhbGwgdGhlIGNvbHVtbnMKICBzZWxlY3QobWFudWZhY3R1cmVyLCBtb2RlbCwgaHd5KSAlPiUgIyBzZWxlY3Qgc29tZSBvZiB0aGUgY29sdW1ucwogIHNsaWNlX2hlYWQobj01KSAjIHZpZXcgdGhlIGZpcnN0IDUgcm93cwpgYGAKCiMjIFNsaWNpbmcKCkFsbCBzbGljZSBvcGVyYXRvcnMgYmVnaW4gd2l0aCBgc2xpY2VfYCBhbmQgdGFrZSBhbiBvcHRpb25hbCBgbj1gIGFyZ3VtZW50IHRoYXQgc3BlY2lmaWVzIHRoZSBudW1iZXIgb2Ygcm93cyB5b3Ugd2FudCB0byBzZWUuIE1vcmUgW2hlcmVdKGh0dHBzOi8vZHBseXIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2Uvc2xpY2UuaHRtbCkuIAoKU3Vic2V0cyB3aXRoIGBzbGljZV9oZWFkKClgOgoKYGBge3Igc2xpY2VfaGVhZH0KbXBnICU+JSAKICBzbGljZV9oZWFkKG49MTApICMgdmlldyB0aGUgZmlyc3QgMTAgcm93cwpgYGAKCgpNYXhpbXVtcyB3aXRoIGBzbGljZV9tYXgoKWA6CgpgYGB7ciBzbGljZV9tYXh9Cm1wZyAlPiUgCiAgc2xpY2VfbWF4KGh3eSwgbj01KSAjIHRoZSBmaXZlIGhpZ2hlc3QgaGlnaHdheSBtaWxlcyBwZXIgZ2FsbG9uLiBpZiB0aGVyZSBhcmUgdGllcyB0aGV5IGFsbCBnZXQgcHJpbnRlZApgYGAKCk1pbmltdW1zIHdpdGggYHNsaWNlX21pbigpYDoKCmBgYHtyIHNsaWNlX21pbn0KbXBnICU+JSAKICBzbGljZV9taW4oaHd5LCBuPTIpICMgdGhlIHR3byBsb3dlc3QgaGlnaHdheSBtaWxlcyBwZXIgZ2FsbG9uLiBpZiB0aGVyZSBhcmUgdGllcyB0aGV5IGFsbCBnZXQgcHJpbnRlZApgYGAKClJhbmRvbSBzYW1wbGVzIHdpdGggYHNsaWNlX3NhbXBsZSgpYDoKCmBgYHtyIHNsaWNlX3NhbXBsZX0KbXBnICU+JSAKICBzbGljZV9zYW1wbGUobj0zKSAjIHBpY2sgdGhyZWUgcm93cyBhdCByYW5kb20KYGBgCgojIyMgU2xpY2luZyBhbmQgZ3JvdXBpbmcKCldoZW4geW91IGBncm91cF9ieSgpYCBhIGRhdGEgZnJhbWUgYHNsaWNlXygpYCB3aWxsIHJldHVybiBzdWJzZXRzIG9mIGVhY2ggZ3JvdXA6CgpgYGB7ciBncm91cF9ieSBhbmQgc2xpY2VffQptcGcgJT4lIAogIGdyb3VwX2J5KGNsYXNzKSAlPiUgIyBncm91cCBieSBjbGFzcyAKICBzbGljZV9taW4oaHd5LCBuPTEpICMgYm90dG9tIHR3byBod3kgYnkgY2xhc3MKYGBgCgpJZiBhIGRhdGEgZnJhbWUgaXMgYWxyZWFkeSBncm91cGVkIHRoZW4gYHNsaWNlXygpYCB3aWxsIGFsd2F5cyBzdWJzZXQgYnkgZ3JvdXAgdW5kZXIgdGhlIGhvb2QuIElmIHlvdSB3YW50IHN1YnNldHMgbm90IGJ5IGdyb3VwIHRoZW4geW91IGhhdmUgdG8gYHVuZ3JvdXAoKWAuIFNlZSBiZWxvdyBpbiB0aGUgc2VjdGlvbiBvbiBgbXV0YXRlKClgLiBUaGlzIG9mdGVuIGhhcHBlbnMgd2hlbiB5b3UgZ3JvdXAgYSBkYXRhIGZyYW1lIHRvIGNyZWF0ZSBhIG5ldyB2YXJpYWJsZS4gCgojIyBGaWx0ZXJpbmcKCltgZmlsdGVyKClgXShodHRwczovL2RwbHlyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL2ZpbHRlci5odG1sKSB3aXRoIEJvb2xlYW4gbG9naWM6CgoqIGA9PWA6ICJlcXVhbCB0byIKKiBgIT1gOiAibm90IGVxdWFsIHRvIgoqIGA+YDogImdyZWF0ZXIgdGhhbiIKKiBgPj1gOiAiZ3JlYXRlciB0aGFuIG9yIGVxdWFsIHRvIgoqIGA8YDogImxlc3MgdGhhbiIKKiBgPD1gOiAibGVzcyB0aGFuIG9yIGVxdWFsIHRvIgoqIGAmYDogImFuZCIKKiBgfGA6ICJvciIKCkJvb2xlYW4gbG9naWMgaXMgYW55IHRlc3QgdGhhdCByZXR1cm5zIHRydWUgb3IgZmFsc2U6CgpgYGB7ciBib29sZWFuIGVxdWFscyB0b30KMiA9PSAzCmBgYAoKYGBge3IgYm9vbGVhbiBub3QgZXF1YWxzIHRvfQoyICE9IDMKYGBgCgpgYGB7ciBib29sZWFuIGxlc3MgdGhhbn0KMiA8IDMKYGBgCgpTb21lIGV4YW1wbGVzOgoKYGBge3IgZmlsdGVyIGVxdWFscyB0b30KbXBnICU+JSAKICBmaWx0ZXIoeWVhciA9PSAiMTk5OSIpICU+JSAjIGZpbHRlciBhbGwgY2FycyBtYWRlIGluIDE5OTkKICBzbGljZV9oZWFkKG49NSkgIyB2aWV3IHRoZSBmaXJzdCA1IHJvd3MKYGBgCgpgYGB7ciBmaWx0ZXIgZ3JlYXRlciB0aGFuIG9yIGVxdWFscyB0b30KbXBnICU+JSAKICBmaWx0ZXIoaHd5ID49IDI1KSAlPiUgIyBmaWx0ZXIgYWxsIGNhcnMgd2l0aCBhdCBsZWFzdCAyNSBtcGcgaGlnaHdheQogIHNsaWNlX2hlYWQobj01KSAjIHZpZXcgdGhlIGZpcnN0IDUgcm93cwpgYGAKCgpgYGB7ciBmaWx0ZXIgbWFueSBjb25kaXRpb25zfQptcGcgJT4lIAogIGZpbHRlcih5ZWFyID09ICIxOTk5IiAmIG1vZGVsICE9ICJhNCIgJiBod3kgPj0gMjUpICU+JSAjIGZpbHRlciBjYXJzIG1hZGUgaW4gMTk5OSwgdGhhdCBhcmVuJ3QgYTQncywgYW5kIGhhdmUgYXQgbGVhc3QgMjUgbWdwIGhpZ2h3YXkKICBzbGljZV9oZWFkKG49NSkgIyB2aWV3IHRoZSBmaXJzdCA1IHJvd3MKYGBgCgoKIyMgTXV0YXRpbmcKCkNyZWF0ZSBhbmQgbW9kaWZ5IHZhcmlhYmxlcyAoZGF0YSBmcmFtZSBjb2x1bW5zKSB3aXRoIFtgbXV0YXRlKClgXShodHRwczovL2RwbHlyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL211dGF0ZS5odG1sKQoKIyMjIENyZWF0ZSBhIG5ldyB2YXJpYWJsZQoKRm9yIGV4YW1wbGUsIGNyZWF0ZSBhIGNvbHVtbiBjYWxsZWQgYG1lYW5faHd5YCB0aGF0IGNhbGN1bGF0ZXMgYXZlcmFnZSBoaWdod2F5IG1pbGVzIHBlciBnYWxsb246CgpgYGB7ciBtdXRhdGUgZG9udCBhZGQgY29sdW1ufQptcGcgJT4lIAogIG11dGF0ZShtZWFuX2h3eSA9IG1lYW4oaHd5KSkgJT4lIAogIHNlbGVjdChtYW51ZmFjdHVyZXIsIG1vZGVsLCBod3ksIG1lYW5faHd5KSAlPiUgCiAgc2xpY2VfaGVhZChuPTUpCmBgYAoKIyMjIENyZWF0ZSBhIG5ldyB2YXJpYWJsZSBhbmQgc3RvcmUgaXQgYXMgYSBjb2x1bW4KClRvIHNhdmUgdGhlIG5ldyB2YXJpYWJsZSB5b3UgaGF2ZSB0byByZS1hc3NpZ24gdGhlIGRhdGEgZnJhbWU6CgpgYGB7ciBtdXRhdGUgeWVzIGFkZCBjb2x1bW59Cm1wZyA9IG1wZyAlPiUgCiAgbXV0YXRlKG1lYW5faHd5ID0gbWVhbihod3kpKSAKYGBgCgpOb3cgYG1wZ2AgaGFzIGEgbmV3IGNvbHVtbiBjYWxsZWQgYG1lYW5faHd5YC4KCiMjIyBNdXRhdGluZyB3aGlsZSBncm91cGluZwoKV2hlbiB5b3UgY3JlYXRlIGEgbmV3IHZhcmlhYmxlIGFuZCBzYXZlIGl0IHRvIHRoZSBkYXRhIHlvdSByZS1hc3NpZ24gdGhlIGRhdGEuIAoKV2hlbiBncm91cGluZyB3aXRoIGBncm91cF9ieSgpYCB0byBjcmVhdGUgYSBuZXcgY29sdW1uLCB0aGUgcmUtYXNzaWduZWQgZGF0YSB3aWxsIGJlIGdyb3VwZWQsIHdoaWNoIGNhbiBhZmZlY3QgdGhlIGBzbGljZV8oKWAgZnVuY3Rpb25zLiAKClRoZSBzb2x1dGlvbiBpcyB0byBgdW5ncm91cCgpYCBhZnRlciBgbXV0YXRlKClgLiAKCkZvciBpbnN0YW5jZSwgYWRkIGEgY29sdW1uIGNhbGxlZCAibWVhbl9od3lfY2xhc3MiIHRoYXQgY2FsY3VsYXRlcyBhdmVyYWdlIGhpZ2h3YXkgbWlsZXMgcGVyIGdhbGxvbiBieSBjbGFzczoKCmBgYHtyIGdyb3VwX2J5IGFuZCBtdXRhdGUsIG1lc3NhZ2U9RkFMU0V9CiMgZmlyc3QgY3JlYXRlIHRoZSB2YXJpYWJsZQptcGcgPSBtcGcgJT4lICMgdGFrZSB0aGUgZGF0YSwgVEhFTgogIGdyb3VwX2J5KGNsYXNzKSAlPiUgIyBncm91cCBieSBjbGFzcywgVEhFTgogIG11dGF0ZShtZWFuX2h3eV9jbGFzcyA9IG1lYW4oaHd5KSkgJT4lICMgY3JlYXRlIHRoZSBuZXcgdmFyaWFibGUsIFRIRU4KICB1bmdyb3VwKCkgIyB1bmdyb3VwIHRoZSBkYXRhCmBgYAoKV2h5IGB1bmdyb3VwKClgPyBCZWNhdXNlIG90aGVyd2lzZSBgc2xpY2VfKClgIGFuZCBvdGhlciBmdW5jdGlvbnMgd29uJ3QgcmV0dXJuIHRoZSBvdXRwdXQgeW91IGV4cGVjdC4gU28gYmV0dGVyIHNhZmUgdGhhbiBzb3JyeS4gV2hlbiBncm91cGluZyAqdG8gY3JlYXRlIGEgdmFyaWFibGUqIG1ha2Ugc3VyZSB5b3UgdW5ncm91cCBhdCB0aGUgZW5kLgoKTm93IHdlIGNhbiBzbGljZSBpbiBnZW5lcmFsOgoKYGBge3IgdmlldyBncm91cF9ieSBtdXRhdGUgb3V0cHV0fQptcGcgJT4lIAogIHNlbGVjdChtYW51ZmFjdHVyZXIsIG1vZGVsLCBod3ksIG1lYW5faHd5X2NsYXNzKSAlPiUgCiAgc2xpY2VfaGVhZChuPTIpCmBgYAoKCiMjIyBNdXRhdGluZyBhbmQgZnJlcXVlbmN5IHRhYmxlcwoKVXNlIGBuKClgIHRvIGNhbGN1bGF0ZSBmcmVxdWVuY3kgYW5kIHRoZW4gdXNlIGBtdXRhdGUoKWAgdG8gY2FsY3VsYXRlIHJlbGF0aXZlIGZyZXF1ZW5jeToKCmBgYHtyIGZyZXF1ZW5jeSBhbmQgcmVsYXRpdmUgZnJlcXVlbmN5LCBtZXNzYWdlPUZBTFNFfQptcGcgJT4lIAogIGdyb3VwX2J5KGNsYXNzKSAlPiUgCiAgc3VtbWFyaXNlKGZyZXF1ZW5jeSA9IG4oKSkgJT4lIAogIG11dGF0ZShyZWxhdGl2ZV9mcmVxdWVuY3kgPSBmcmVxdWVuY3kgLyBzdW0oZnJlcXVlbmN5KSkKYGBgCgojIFZpc3VhbGl6aW5nIGRhdGEKCiMjIFBsb3R0aW5nIGRpc3RyaWJ1dGlvbnMgCgojIyMgSGlzdG9ncmFtcwoKSGlzdG9ncmFtcyB3aXRoIGBnZ3Bsb3RgIGFuZCBbYGdlb21faGlzdG9ncmFtKClgXShodHRwczovL2dncGxvdDIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2UvZ2VvbV9oaXN0b2dyYW0uaHRtbCk6CgpgYGB7ciBoaXN0b2dyYW19Cm1wZyAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gY3R5KSkgKyAjIGJsYW5rIGNhbnZhczogY2hvb3NlIHRoZSBkYXRhIGFuZCB0aGUgeC1heGlzIHZhcmlhYmxlCiAgZ2VvbV9oaXN0b2dyYW0oKSAjIGFkZCBnZW9tIGxheWVyOiBkaXN0cmlidXRpb24KYGBgCgpGYWNldGVkIGhpc3RvZ3JhbXMgd2l0aCBbYGZhY2V0X3dyYXAoKWBdKGh0dHBzOi8vZ2dwbG90Mi50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS9mYWNldF93cmFwLmh0bWwpOgoKYGBge3IgZmFjZXR0ZWQgaGlzdG9ncmFtfQptcGcgJT4lIAogIGdncGxvdChhZXMoeCA9IGN0eSkpICsgIyBibGFuayBjYW52YXM6IGNob29zZSB0aGUgZGF0YSBhbmQgdGhlIHgtYXhpcyB2YXJpYWJsZQogIGdlb21faGlzdG9ncmFtKCkgKyAjIGFkZCBnZW9tIGxheWVyOiAgZGlzdHJpYnV0aW9uCiAgZmFjZXRfd3JhcCh+Y2xhc3MpICMgYWRkIGZhY2V0dGluZyAobm90ZSB0aGUgIn4iIGJlZm9yZSB0aGUgY2F0ZWdvcmljYWwgdmFyaWFibGUpCmBgYAoKIyMjIEN1bXVsYXRpdmUgZGlzdHJpYnV0aW9ucwoKUGVyY2VudGlsZXMgYW5kIGN1bXVsYXRpdmUgZGlzdHJpYnV0aW9ucyB3aXRoIGBnZ3Bsb3RgIGFuZCBbYHN0YXRfZWNkZigpYF0oaHR0cHM6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL3N0YXRfZWNkZi5odG1sKToKCmBgYHtyIHBlcmNlbnRpbGUgKGN1bXVsYXRpdmUgZGlzdHJpYnV0aW9uKX0KbXBnICU+JSAKICBnZ3Bsb3QoYWVzKHg9Y3R5KSkgKyAgIyBibGFuayBjYW52YXM6IGNob29zZSB0aGUgZGF0YSBhbmQgdGhlIHgtYXhpcyB2YXJpYWJsZQogIHN0YXRfZWNkZigpICMgYWRkIGdlb20gbGF5ZXI6IGN1bXVsYXRpdmUgZGlzdHJpYnV0aW9uCmBgYAoKWW91IGNhbiBhZGQgYSB2ZXJ0aWNhbCBsaW5lIHdpdGggYGdlb21fdmxpbmUoKWAgdG8gbWFrZSBpdCBlYXNpZXIgdG8gc2VlIGEgcGVyY2VudGlsZS4gRm9yIGluc3RhbmNlLCB2aWV3IHBlcmNlbnQgb2YgY2FycyB3aXRoIGxlc3MgdGhhbiAxNSBtaWxlcyBwZXIgZ2FsbG9uOgoKYGBge3IgcGVyY2VudGlsZSAoY3VtdWxhdGl2ZSBkaXN0cmlidXRpb24pIHdpdGggdmxpbmV9Cm1wZyAlPiUgCiAgZ2dwbG90KGFlcyh4PWN0eSkpICsgICMgYmxhbmsgY2FudmFzOiBjaG9vc2UgdGhlIGRhdGEgYW5kIHRoZSB4LWF4aXMgdmFyaWFibGUKICBzdGF0X2VjZGYoKSArICMgYWRkIGdlb20gbGF5ZXI6IGN1bXVsYXRpdmUgZGlzdHJpYnV0aW9uCiAgZ2VvbV92bGluZSh4aW50ZXJjZXB0ID0gMTUsIGNvbG9yPSJyZWQiKSAjIHZlcnRpY2xlIApgYGAKCiMjIyBCb3ggcGxvdHMKCltgZ2VvbV9ib3hwbG90KClgXShodHRwczovL2dncGxvdDIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2UvZ2VvbV9ib3hwbG90Lmh0bWwpCgpgYGB7ciBib3ggcGxvdCB4IGNvbnRpbnVvdXN9Cm1wZyAlPiUgCiAgZ2dwbG90KGFlcyh4ID0gaHd5LCB5ID0gY2xhc3MpKSArICMgeCBpcyBjb250aW51b3VzLCB5IGlzIGNhdGVnb3JpY2FsCiAgZ2VvbV9ib3hwbG90KCkgCmBgYAoKb3I6IAoKYGBge3IgYm94IHBsb3QgeCBjYXRlZ29yaWNhbH0KbXBnICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBjbGFzcywgeSA9IGh3eSkpICsgIyB4IGlzIGNhdGVnb3JpY2FsLCB5IGlzIGNvbnRpbnVvdXMKICBnZW9tX2JveHBsb3QoKSAKYGBgCgoKIyMgU2NhdHRlcnBsb3QKCmBnZW9tX3BvaW50KClgOgoKYGBge3IgZ2VvbV9wb2ludH0KbXBnICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBjdHksIHkgPSBod3kpKSArIAogIGdlb21fcG9pbnQoKSAKYGBgCgojIyMgU2NhdHRlcnBsb3Qgd2l0aCByZWdyZXNzaW9uIGxpbmUKCltgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIilgXShodHRwczovL2dncGxvdDIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2UvZ2VvbV9zbW9vdGguaHRtbCk6CgpgYGB7ciBnZW9tX3Ntb290aH0KbXBnICU+JSAKICBnZ3Bsb3QoYWVzKHggPSBjdHksIHkgPSBod3kpKSArIAogIGdlb21fcG9pbnQoKSArICMgc2NhdHRlci1wbG90CiAgZ2VvbV9zbW9vdGgobWV0aG9kID0gImxtIikgIyByZWdyZXNzaW9uIGxpbmUKYGBgCgojIENvcnJlbGF0aW9uIGFuZCByZWdyZXNzaW9uCgojIyBDb3ZhcmlhbmNlCgpgY292KClgOgoKYGBge3IgY292fQptcGcgJT4lIAogIHN1bW1hcmlzZShjb3YoeD1jdHksIHk9aHd5KSkKYGBgCgojIyBDb3JyZWxhdGlvbgoKYGNvcigpYDoKCmBgYHtyIGNvcn0KbXBnICU+JSAKICBzdW1tYXJpc2UoY29yKHg9Y3R5LCB5PWh3eSkpCmBgYAoKCiMjIFJlZ3Jlc3Npb24KCmBsbSgpYAoKU2ltcGxlIGxpbmVhciByZWdyZXNzaW9uIChvbmUgJHgkIHZhcmlhYmxlKToKCiQkClxiZWdpbnthbGlnbmVkfQp5ICY9IGYoeCkgXFwKICAmPSBcYmV0YV8wICsgXGJldGFfMSh4KSArIFxlcHNpbG9uClxlbmR7YWxpZ25lZH0KJCQKSW4gYGxtKClgIHRoZSAkeSQgYW5kICR4JCB2YXJpYWJsZXMgYXJlIHNlcGFyYXRlZCBieSBgfmA6CgpgYGB7ciBsbTF9Cm1wZyAlPiUgCiAgbG0oZm9ybXVsYSA9IGN0eSB+IGh3eSwgZGF0YSA9IC4pICMgdGhlICIuIiBtZWFucyAidXNlIHRoZSBkYXRhIGZyb20gdGhlIHBpcGUgJT4lIgpgYGAKCk11bHRpcGxlIGxpbmVhciByZWdyZXNzaW9uIChtdWx0aXBsZSAkeCQgdmFyaWFibGVzKToKCiQkClxiZWdpbnthbGlnbmVkfQp5ICY9IGYoXG1hdGhiZntYfSkgXFwKICAmPSBmKHhfMSwgeF8yLCBcZG90cykgXFwKICAmPSBcYmV0YV8wICsgXGJldGFfMSh4XzEpICsgXGJldGFfMih4XzIpICsgXGRvdHMgKyBcZXBzaWxvbgpcZW5ke2FsaWduZWR9CiQkCkluIGBsbSgpYCB0aGUgJHgkIHZhcmlhYmxlcyBhcmUgc2VwYXJhdGVkIGJ5IGArYDoKCmBgYHtyIGxtMn0KbXBnICU+JSAKICBsbShmb3JtdWxhID0gY3R5IH4gaHd5ICsgY3lsICsgZGlzcGwsIGRhdGEgPSAuKQpgYGAKCiMjIGBzdW1tYXJ5KClgCgpWaWV3IGh5cG90aGVzaXMgdGVzdHMgYW5kIHJlZ3Jlc3Npb24gZGlhZ25vc3RpY3Mgd2l0aCBgc3VtbWFyeSgpYDoKCmBgYHtyIHN1bW1hcnkyfQptcGcgJT4lIAogIGxtKGZvcm11bGEgPSBjdHkgfiBod3kgKyBjeWwgKyBkaXNwbCwgZGF0YSA9IC4pICU+JSAKICBzdW1tYXJ5KCkKYGBgCgojIyBQbG90dGluZyByZWdyZXNzaW9uIHJlc3VsdHMKClVzZSB0aGUgcGFja2FnZSBgc0pwbG90YC4gKElmIHlvdSBmb3Jnb3QgaG93IHRvIGluc3RhbGwgcGFja2FnZSwgc2VlIFtoZXJlXShodHRwczovL2xyZGVnZWVzdC5naXRodWIuaW8vc3RhdHMvci5odG1sIzNfSW5zdGFsbF9wYWNrYWdlcykuKQoKYGBge3IgbGlicmFyeSBzanBsb3QsIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkoc2pQbG90KQpgYGAKClRoZSBmdW5jdGlvbiB5b3Ugd2FudCBpcyBgcGxvdF9tb2RlbGAuIFRoZSBiYXNlbGluZSBwbG90IHNob3dzIHRoZSBjb2VmZmljaWVudHMgYW5kIHN0YW5kYXJkIGVycm9yczoKCmBgYHtyIHNqUGxvdCBkZW1vIGNvZWZmaWNpZW50c30KbXBnICU+JSAKICBsbShmb3JtdWxhID0gY3R5IH4gaHd5ICsgY2xhc3MsIGRhdGEgPSAuKSAlPiUgCiAgcGxvdF9tb2RlbChtb2RlbCA9IC4pCmBgYAoKVG8gcGxvdCBwcmVkaWN0ZWQgdmFsdWVzIHlvdSBjYW4gc2V0IGB0eXBlID0gInByZWQiYCBhbmQgdGhlbiBjaG9vc2Ugd2hpY2ggY29lZmZpY2llbnQgdG8gcGxvdCBieSBzZXR0aW5nIGB0ZXJtc2A6CgpgYGB7ciBzalBsb3QgZGVtbyBwcmVkaWN0ZWQgdmFsdWVzfQptcGcgJT4lIAogIGxtKGZvcm11bGEgPSBjdHkgfiBod3kgKyBjbGFzcywgZGF0YSA9IC4pICU+JSAKICBwbG90X21vZGVsKG1vZGVsID0gLiwgdHlwZSA9ICJwcmVkIiwgdGVybXMgPSBjKCJod3kiKSkKYGBgCgpUaGUgYHRlcm1zYCBhcmd1bWVudCBhY2NlcHRzIG11bHRpcGxlIHRlcm1zOgoKYGBge3Igc2pQbG90IGRlbW8gcHJlZGljdGVkIHZhbHVlcyBtdWx0aXBsZSB0ZXJtc30KbXBnICU+JSAKICBsbShmb3JtdWxhID0gY3R5IH4gaHd5ICsgY2xhc3MsIGRhdGEgPSAuKSAlPiUgCiAgcGxvdF9tb2RlbChtb2RlbCA9IC4sIHR5cGUgPSAicHJlZCIsIHRlcm1zID0gYygiaHd5IiwgImNsYXNzIikpCmBgYAoKVGhpcyBpcyB1c2VmdWwgd2hlbiB5b3UgaGF2ZSBhbiBpbnRlcmFjdGlvbiBlZmZlY3QgKGUuZy4sIGBod3kqY2xhc3NgKToKCmBgYHtyIHNqUGxvdCBkZW1vIHByZWRpY3RlZCB2YWx1ZXMgaW50ZXJhY3Rpb259Cm1wZyAlPiUgCiAgbG0oZm9ybXVsYSA9IGN0eSB+IGh3eSArIGNsYXNzICsgaHd5KmNsYXNzLCBkYXRhID0gLikgJT4lIAogIHBsb3RfbW9kZWwobW9kZWwgPSAuLCB0eXBlID0gInByZWQiLCB0ZXJtcyA9IGMoImh3eSIsICJjbGFzcyIpKQpgYGAKCmBzSnBsb3RgIHVzZXMgYGdncGxvdGAgc28geW91IGNhbiBhZGQgYGdncGxvdGAgc3R1ZmYgdG8gaXQsIGxpa2UgYSBkaWZmZXJlbnQgdGhlbWUgYW5kIHRpdGxlczoKCmBgYHtyIHNqUGxvdCBkZW1vIHByZWRpY3RlZCB2YWx1ZXMgaW50ZXJhY3Rpb24gc3BpZmZ5fQptcGcgJT4lIAogIGxtKGZvcm11bGEgPSBjdHkgfiBod3kgKyBjbGFzcyArIGh3eSpjbGFzcywgZGF0YSA9IC4pICU+JSAKICBwbG90X21vZGVsKG1vZGVsID0gLiwgdHlwZSA9ICJwcmVkIiwgdGVybXMgPSBjKCJod3kiLCAiY2xhc3MiKSkgKyAKICBsYWJzKHggPSAiSGlnaHdheSBtaWxlcyBwZXIgZ2FsbG9uIiwgeSA9ICJQcmVkaWN0ZWQgY2l0eSBtaWxlcyBwZXIgZ2FsbG9uIiwKICAgICAgIHRpdGxlID0gIkEgdmVyeSBpbnRlcmVzdGluZyBsaW5lYXIgbW9kZWwiLCBzdWJ0aXRsZSA9ICJTbyBpbnRlcmVzdGluZyIpICsgCiAgdGhlbWVfbWluaW1hbCgpCmBgYAoKVGhlIHBhY2thZ2UgaGFzIHRvbnMgb2YgZ3JlYXQgZmVhdHVyZXMuIENoZWNrIG91dCB0aGUgW3dlYnNpdGUhXShodHRwczovL3N0cmVuZ2VqYWNrZS5naXRodWIuaW8vc2pQbG90LykKCiMgUXVpY2sgdGFibGVzIHsjdGFibGUgLnRhYnNldH0KCiMjIFIKCioqRnVuY3Rpb24qKnwqKkRlc2NyaXB0aW9uKioKLS0tLS18LS0tLS0KWzxjb2RlPiU+JTwvY29kZT5dKGh0dHBzOi8vbWFncml0dHIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2UvcGlwZS5odG1sKXxQaXBlIG9wZXJhdG9yICgiYW5kIHRoZW4iKQpbPGNvZGU+c3VtbWFyaXNlKCk8L2NvZGU+XShodHRwczovL2RwbHlyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL3N1bW1hcmlzZS5odG1sKXxTdW1tYXJpemUgYSB2ZWN0b3Igb3IgbXVsdGlwbGUgdmVjdG9ycyBmcm9tIGEgZGF0YSBmcmFtZQpbPGNvZGU+bWVhbigpPC9jb2RlPl0oaHR0cHM6Ly93d3cucmRvY3VtZW50YXRpb24ub3JnL3BhY2thZ2VzL2Jhc2UvdmVyc2lvbnMvMy42LjIvdG9waWNzL21lYW4pfENhbGN1bGF0ZSB0aGUgbWVhbiBvZiBhIHZlY3RvcgpbPGNvZGU+bWVkaWFuKCk8L2NvZGU+XShodHRwczovL3d3dy5yZG9jdW1lbnRhdGlvbi5vcmcvcGFja2FnZXMvc3RhdHMvdmVyc2lvbnMvMy42LjIvdG9waWNzL21lZGlhbil8Q2FsY3VsYXRlIHRoZSBtZWRpYW4gb2YgYSB2ZWN0b3IKWzxjb2RlPnNkKCk8L2NvZGU+XShodHRwczovL3d3dy5yZG9jdW1lbnRhdGlvbi5vcmcvcGFja2FnZXMvc3RhdHMvdmVyc2lvbnMvMy42LjIvdG9waWNzL3NkKXxDYWxjdWxhdGUgdGhlIHN0YW5kYXJkIGRldmlhdGlvbiBvZiBhIHZlY3RvcgpbPGNvZGU+bigpPC9jb2RlPl0oaHR0cHM6Ly9kcGx5ci50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS9uLmh0bWwpfENvdW50IHRoZSBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zLiBUYWtlcyBubyBhcmd1bWVudC4KWzxjb2RlPmdyb3VwXF9ieSgpPC9jb2RlPl0oaHR0cHM6Ly9kcGx5ci50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS9ncm91cFxfYnkuaHRtbCl8R3JvdXAgb2JzZXJ2YXRpb25zIGJ5IGEgY2F0ZWdvcmljYWwgdmFyaWFibGUKWzxjb2RlPnNlbGVjdCgpPC9jb2RlPl0oaHR0cHM6Ly9kcGx5ci50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS9zZWxlY3QuaHRtbCl8U2VsZWN0IGNlcnRhaW4gY29sdW1ucwpbPGNvZGU+c2xpY2VcXygpPC9jb2RlPl0oaHR0cHM6Ly9kcGx5ci50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS9zbGljZS5odG1sKXxTbGljZSByb3dzIGZyb20gdGhlIGRhdGEKPGNvZGU+c2xpY2VcX2hlYWQobj01KTwvY29kZT58VmlldyB0aGUgaGVhZCAoZmlyc3QgZml2ZSByb3dzKSBvZiB0aGUgZGF0YS4gPGNvZGU+biA9IDwvY29kZT4gY2FuIGJlIGFueSBudW1iZXIuCjxjb2RlPnNsaWNlXF9tYXgodmFyLCBuPTUpPC9jb2RlPnxWaWV3IHRoZSByb3dzIHdpdGggdGhlIDUgaGlnaGVzdCB2YWx1ZXMgb2YgY29sdW1uICJ2YXIiCjxjb2RlPnNsaWNlXF9taW4odmFyLCBuPTUpPC9jb2RlPnxWaWV3IHRoZSByb3dzIHdpdGggdGhlIDUgbG93ZXN0IHZhbHVlcyBvZiBjb2x1bW4gInZhciIKPGNvZGU+c2xpY2VcX3NhbXBsZShuPTUpPC9jb2RlPnxEcmF3IDUgcm93cyBhdCByYW5kb20KWzxjb2RlPmZpbHRlcigpPC9jb2RlPl0oaHR0cHM6Ly9kcGx5ci50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS9maWx0ZXIuaHRtbCl8RmlsdGVyIG9ic2VydmF0aW9ucwpbPGNvZGU+bXV0YXRlKCk8L2NvZGU+XShodHRwczovL2RwbHlyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL211dGF0ZS5odG1sKXxDcmVhdGUgYSBuZXcgdmVjdG9yCls8Y29kZT5jb3YoKTwvY29kZT5dKGh0dHBzOi8vd3d3LnJkb2N1bWVudGF0aW9uLm9yZy9wYWNrYWdlcy9wYmRETUFUL3ZlcnNpb25zLzAuNS0xL3RvcGljcy9jb3ZhcmlhbmNlKXxDYWxjdWxhdGUgdGhlIGNvdmFyaWF0aW9uIGJldHdlZW4gdHdvIHZhcmlhYmxlcwpbPGNvZGU+Y29yKCk8L2NvZGU+XShodHRwczovL3d3dy5yZG9jdW1lbnRhdGlvbi5vcmcvcGFja2FnZXMvc3RhdHMvdmVyc2lvbnMvMy42LjIvdG9waWNzL2Nvcil8Q2FsY3VsYXRlIHRoZSBjb3JyZWxhdGlvbiBiZXR3ZWVuIHR3byB2YXJpYWJsZXMKWzxjb2RlPmxtKCk8L2NvZGU+XShodHRwczovL3d3dy5yZG9jdW1lbnRhdGlvbi5vcmcvcGFja2FnZXMvc3RhdHMvdmVyc2lvbnMvMy42LjIvdG9waWNzL2xtKXxFc3RpbWF0ZSBhIGxpbmVhciByZWdyZXNzaW9uCls8Y29kZT5pZmVsc2UoKTwvY29kZT5dKGh0dHBzOi8vd3d3LnJkb2N1bWVudGF0aW9uLm9yZy9wYWNrYWdlcy9iYXNlL3ZlcnNpb25zLzMuNi4yL3RvcGljcy9pZmVsc2UpfENyZWF0ZSBhIHZlY3RvciBiYXNlZCBvbiBhICJUcnVlL0ZhbHNlIiB0ZXN0Cls8Y29kZT5nZ3Bsb3QoKTwvY29kZT5dKGh0dHBzOi8vZ2dwbG90Mi50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS9nZ3Bsb3QuaHRtbCl8Q3JlYXRlIGEgYmFzZSBwbG90Cls8Y29kZT5nZW9tXF9oaXN0b2dyYW0oKTwvY29kZT5dKGh0dHBzOi8vZ2dwbG90Mi50aWR5dmVyc2Uub3JnL3JlZmVyZW5jZS9nZW9tXF9oaXN0b2dyYW0uaHRtbCl8SGlzdG9ncmFtCls8Y29kZT5zdGF0XF9lY2RmKCk8L2NvZGU+XShodHRwczovL2dncGxvdDIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2Uvc3RhdFxfZWNkZi5odG1sKXxDdW11bGF0aXZlIGRpc3RyaWJ1dGlvbiBwbG90Cls8Y29kZT5nZW9tXF9ib3hwbG90KCk8L2NvZGU+XShodHRwczovL2dncGxvdDIudGlkeXZlcnNlLm9yZy9yZWZlcmVuY2UvZ2VvbVxfYm94cGxvdC5odG1sKXxCb3hwbG90Cls8Y29kZT5nZW9tXF9wb2ludCgpPC9jb2RlPl0oaHR0cHM6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL2dlb21cX3BvaW50Lmh0bWwpfFNjYXR0ZXIgcGxvdApbPGNvZGU+Z2VvbVxfc21vb3RoKG1ldGhvZCA9ICJsbSIpPC9jb2RlPl0oaHR0cHM6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL2dlb21cX3Ntb290aC5odG1sKXxSZWdyZXNzaW9uIGxpbmUKCiMjIE1hdGgKCioqVGVybSoqfCoqTWVhbmluZyoqfCoqUHJvbnVuY2lhdGlvbioqfCoqRm9ybXVsYS9FeGFtcGxlKioKOi0tLS0tOnwtLS0tLXw6LS0tLS06fDotLS0tLToKJHhfaSR8ZGF0YSBwb2ludCAkaSR8InggaSIgfCAKJG4kfHNhbXBsZSBzaXplfCB8IAokTiR8cG9wdWxhdGlvbiBzaXplfCB8IAokXGJhcnt4fSR8dGhlIHNhbXBsZSBtZWFufCJ4IGJhciJ8JFxmcmFje1xzdW1fe2k9MX1ebiB4X2l9e259JAokXG11JHx0aGUgcG9wdWxhdGlvbiBtZWFufCJtdSJ8IAokc14yJHx0aGUgc2FtcGxlIHZhcmlhbmNlfCB8JFxmcmFje1xzdW0gKHhfaSAtIFxiYXJ7eH0pXjJ9e24tMX0kCiRcc2lnbWFeMiR8dGhlIHBvcHVsYXRpb24gdmFyaWFuY2V8IHwgCiRzJHx0aGUgc2FtcGxlIHN0YW5kYXJkIGRldmlhdGlvbnwgfCRcc3FydHtzXjJ9JAokXHNpZ21hJHx0aGUgcG9wdWxhdGlvbiBzdGFuZGFyZCBkZXZpYXRpb258InNpZ21hInwgCiR6X2kkfHotc2NvcmUgZm9yIG9ic2VydmF0aW9uICRpJHwgfCR6X2kgPSBcZnJhY3t4X2kgLSBcYmFye3h9fXtzfSQKJFxiZXRhJHxyZWdyZXNzaW9uIGNvZWZmaWNpZW50fCJiZXRhInwkeSA9IFxiZXRhXzAgKyBcYmV0YV8xIHggKyBcZXBzaWxvbiQKJFxoYXR7XGJldGF9JHxlc3RpbWF0ZWQgdmFsdWUgb2YgcmVncmVzc2lvbiBjb2VmZmljaWVudHwiYmV0YSBoYXQifCAKJFxlcHNpbG9uJHxyZWdyZXNzaW9uIGVycm9yfCJlcHNpbG9uInwgCiRcc3VtJHxzdW1tYXRpb24gb3BlcmF0b3J8InN1bSJ8JFxzdW1fe2k9MX1eMiB4X2kgID0geF8xICsgeF8yJAokc197eHl9JHxjb3ZhcmlhdGlvbiBiZXR3ZWVuIHR3byB2YXJpYWJsZXMgJHgkIGFuZCAkeSR8IHwkXGZyYWN7XHN1bSAoeF9pIC0gXGJhcnt4fSkoeV9pIC0gXGJhcnt5fSl9e24tMX0gXGluIFstXGluZnR5LFxpbmZ0eV0kCiRyX3t4eX0kfGNvcnJlbGF0aW9uIGJldHdlZW4gdHdvIHZhcmlhYmxlcyAkeCQgYW5kICR5JHwgfCRcZnJhY3tzX3t4eX19e3NfeCBzX3l9IFxpbiBbLTEsMV0k