Last updated: 2021-04-27 18:25:43 EST

Go to quick tables Go home

Set-up

Load the tidyverse:

library(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)

1 Summarizing Data

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

1.1.1 mean

mean()

mpg %>% # take the data, THEN
  summarise(mean(cty)) # summarize: mean

Note. cty is a numerical variable.

1.1.2 median

median()

mpg %>% # take the data, THEN
  summarise(median(cty)) # summarize: median

1.1.3 standard deviation

sd()

mpg %>% # take the data, THEN
  summarise(sd(cty)) # summarize: standard deviation

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

1.1.5 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

1.1.6 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

1.2 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

Note. class is a categorical variable.

2 Manipulating data

2.1 Selecting columns

select() certain columns

mpg %>% # all the columns
  slice_head(n=5) # view the first 5 rows
mpg %>% # all the columns
  select(manufacturer, model, hwy) %>% # select some of the columns
  slice_head(n=5) # view the first 5 rows

2.2 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

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

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

Random samples with slice_sample():

mpg %>% 
  slice_sample(n=3) # pick three rows at random

2.2.1 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

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.

2.3 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:

2 == 3
[1] FALSE
2 != 3
[1] TRUE
2 < 3
[1] TRUE

Some examples:

mpg %>% 
  filter(year == "1999") %>% # filter all cars made in 1999
  slice_head(n=5) # view the first 5 rows
mpg %>% 
  filter(hwy >= 25) %>% # filter all cars with at least 25 mpg highway
  slice_head(n=5) # view the first 5 rows
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

2.4 Mutating

Create and modify variables (data frame columns) with mutate()

2.4.1 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)

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

2.4.3 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)

2.4.4 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))

3 Visualizing data

3.1 Plotting distributions

3.1.1 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)

3.1.2 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 

3.1.3 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() 

3.2 Scatterplot

geom_point():

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

3.2.1 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

4 Correlation and regression

4.1 Covariance

cov():

mpg %>% 
  summarise(cov(x=cty, y=hwy))

4.2 Correlation

cor():

mpg %>% 
  summarise(cor(x=cty, y=hwy))

4.3 Regression

lm()

Simple linear regression (one \(x\) variable):

\[ \begin{aligned} y &= f(x) \\ &= \beta_0 + \beta_1(x) + \epsilon \end{aligned} \] 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):

\[ \begin{aligned} y &= f(\mathbf{X}) \\ &= f(x_1, x_2, \dots) \\ &= \beta_0 + \beta_1(x_1) + \beta_2(x_2) + \dots + \epsilon \end{aligned} \] 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  

4.4 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

4.5 Plotting regression results

Use the package sJplot. (If you forgot how to install package, see here.)

library(sjPlot)

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!

5 Quick tables

5.1 R

Function Description
%>% 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

5.2 Math

Term Meaning Pronunciation Formula/Example
\(x_i\) data point \(i\) “x i”
\(n\) sample size
\(N\) population size
\(\bar{x}\) the sample mean “x bar” \(\frac{\sum_{i=1}^n x_i}{n}\)
\(\mu\) the population mean “mu”
\(s^2\) the sample variance \(\frac{\sum (x_i - \bar{x})^2}{n-1}\)
\(\sigma^2\) the population variance
\(s\) the sample standard deviation \(\sqrt{s^2}\)
\(\sigma\) the population standard deviation “sigma”
\(z_i\) z-score for observation \(i\) \(z_i = \frac{x_i - \bar{x}}{s}\)
\(\beta\) regression coefficient “beta” \(y = \beta_0 + \beta_1 x + \epsilon\)
\(\hat{\beta}\) estimated value of regression coefficient “beta hat”
\(\epsilon\) regression error “epsilon”
\(\sum\) summation operator “sum” \(\sum_{i=1}^2 x_i = x_1 + x_2\)
\(s_{xy}\) covariation between two variables \(x\) and \(y\) \(\frac{\sum (x_i - \bar{x})(y_i - \bar{y})}{n-1} \in [-\infty,\infty]\)
\(r_{xy}\) correlation between two variables \(x\) and \(y\) \(\frac{s_{xy}}{s_x s_y} \in [-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