Processing math: 100%
  • Set-up
  • 1 Summarizing Data
    • 1.1 The pipe and summarise()
      • 1.1.1 mean
      • 1.1.2 median
      • 1.1.3 standard deviation
      • 1.1.4 counting observations
      • 1.1.5 multiple summaries
      • 1.1.6 naming variables inside summarise()
    • 1.2 Grouping (group_by())
  • 2 Manipulating data
    • 2.1 Selecting columns
    • 2.2 Slicing
      • 2.2.1 Slicing and grouping
    • 2.3 Filtering
    • 2.4 Mutating
      • 2.4.1 Create a new variable
      • 2.4.2 Create a new variable and store it as a column
      • 2.4.3 Mutating while grouping
      • 2.4.4 Mutating and frequency tables
  • 3 Visualizing data
    • 3.1 Plotting distributions
      • 3.1.1 Histograms
      • 3.1.2 Cumulative distributions
      • 3.1.3 Box plots
    • 3.2 Scatterplot
      • 3.2.1 Scatterplot with regression line
  • 4 Correlation and regression
    • 4.1 Covariance
    • 4.2 Correlation
    • 4.3 Regression
    • 4.4 summary()
    • 4.5 Plotting regression results
  • 5 Quick tables

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)
ABCDEFGHIJ0123456789
manufacturer
<chr>
model
<chr>
displ
<dbl>
year
<int>
cyl
<int>
trans
<chr>
drv
<chr>
cty
<int>
hwy
<int>
fl
<chr>
audia41.819994auto(l5)f1829p
audia41.819994manual(m5)f2129p
audia42.020084manual(m6)f2031p
audia42.020084auto(av)f2130p
audia42.819996auto(l5)f1626p

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
ABCDEFGHIJ0123456789
mean(cty)
<dbl>
16.85897

Note. cty is a numerical variable.

1.1.2 median

median()

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

1.1.3 standard deviation

sd()

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

1.1.4 counting observations

n() (counting)

mpg %>% # take the data, THEN
  summarise(n()) # summarize: count number of observations
ABCDEFGHIJ0123456789
n()
<int>
234

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
ABCDEFGHIJ0123456789
mean(cty)
<dbl>
median(cty)
<dbl>
sd(cty)
<dbl>
n()
<int>
16.85897174.255946234

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
ABCDEFGHIJ0123456789
avg_cty
<dbl>
median_cty
<dbl>
16.8589717

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
ABCDEFGHIJ0123456789
class
<chr>
mean(cty)
<dbl>
median(cty)
<dbl>
sd(cty)
<dbl>
n()
<int>
2seater15.40000150.54772265
compact20.12766203.385499947
midsize18.75610181.946541641
minivan15.81818161.834021911
pickup13.00000132.046338233
subcompact20.37143194.602337735
suv13.50000132.420879162

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
ABCDEFGHIJ0123456789
manufacturer
<chr>
model
<chr>
displ
<dbl>
year
<int>
cyl
<int>
trans
<chr>
drv
<chr>
cty
<int>
hwy
<int>
fl
<chr>
audia41.819994auto(l5)f1829p
audia41.819994manual(m5)f2129p
audia42.020084manual(m6)f2031p
audia42.020084auto(av)f2130p
audia42.819996auto(l5)f1626p
mpg %>% # all the columns
  select(manufacturer, model, hwy) %>% # select some of the columns
  slice_head(n=5) # view the first 5 rows
ABCDEFGHIJ0123456789
manufacturer
<chr>
model
<chr>
hwy
<int>
audia429
audia429
audia431
audia430
audia426

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
ABCDEFGHIJ0123456789
manufacturer
<chr>
model
<chr>
displ
<dbl>
year
<int>
cyl
<int>
trans
<chr>
drv
<chr>
cty
<int>
hwy
<int>
fl
<chr>
audia41.819994auto(l5)f1829p
audia41.819994manual(m5)f2129p
audia42.020084manual(m6)f2031p
audia42.020084auto(av)f2130p
audia42.819996auto(l5)f1626p
audia42.819996manual(m5)f1826p
audia43.120086auto(av)f1827p
audia4 quattro1.819994manual(m5)41826p
audia4 quattro1.819994auto(l5)41625p
audia4 quattro2.020084manual(m6)42028p

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
ABCDEFGHIJ0123456789
manufacturer
<chr>
model
<chr>
displ
<dbl>
year
<int>
cyl
<int>
trans
<chr>
drv
<chr>
cty
<int>
hwy
<int>
fl
<chr>
volkswagenjetta1.919994manual(m5)f3344d
volkswagennew beetle1.919994manual(m5)f3544d
volkswagennew beetle1.919994auto(l4)f2941d
toyotacorolla1.820084manual(m5)f2837r
hondacivic1.820084auto(l5)f2536r
hondacivic1.820084auto(l5)f2436c

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
ABCDEFGHIJ0123456789
manufacturer
<chr>
model
<chr>
displ
<dbl>
year
<int>
cyl
<int>
trans
<chr>
drv
<chr>
cty
<int>
hwy
<int>
dodgedakota pickup 4wd4.720088auto(l5)4912
dodgedurango 4wd4.720088auto(l5)4912
dodgeram 1500 pickup 4wd4.720088auto(l5)4912
dodgeram 1500 pickup 4wd4.720088manual(m6)4912
jeepgrand cherokee 4wd4.720088auto(l5)4912

Random samples with slice_sample():

mpg %>% 
  slice_sample(n=3) # pick three rows at random
ABCDEFGHIJ0123456789
manufacturer
<chr>
model
<chr>
displ
<dbl>
year
<int>
cyl
<int>
trans
<chr>
drv
<chr>
cty
<int>
hwy
<int>
pontiacgrand prix3.819996auto(l4)f1626
dodgeram 1500 pickup 4wd4.720088manual(m6)4912
audia4 quattro1.819994manual(m5)41826

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
ABCDEFGHIJ0123456789
manufacturer
<chr>
model
<chr>
displ
<dbl>
year
<int>
cyl
<int>
trans
<chr>
drv
<chr>
cty
<int>
hwy
<int>
chevroletcorvette5.719998auto(l4)r1523
volkswagenjetta2.819996auto(l4)f1623
audia6 quattro4.220088auto(s6)41623
dodgecaravan 2wd3.320086auto(l4)f1117
dodgedakota pickup 4wd4.720088auto(l5)4912
dodgeram 1500 pickup 4wd4.720088auto(l5)4912
dodgeram 1500 pickup 4wd4.720088manual(m6)4912
fordmustang5.420088manual(m6)r1420
dodgedurango 4wd4.720088auto(l5)4912
jeepgrand cherokee 4wd4.720088auto(l5)4912

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
ABCDEFGHIJ0123456789
manufacturer
<chr>
model
<chr>
displ
<dbl>
year
<int>
cyl
<int>
trans
<chr>
drv
<chr>
cty
<int>
hwy
<int>
fl
<chr>
audia41.819994auto(l5)f1829p
audia41.819994manual(m5)f2129p
audia42.819996auto(l5)f1626p
audia42.819996manual(m5)f1826p
audia4 quattro1.819994manual(m5)41826p
mpg %>% 
  filter(hwy >= 25) %>% # filter all cars with at least 25 mpg highway
  slice_head(n=5) # view the first 5 rows
ABCDEFGHIJ0123456789
manufacturer
<chr>
model
<chr>
displ
<dbl>
year
<int>
cyl
<int>
trans
<chr>
drv
<chr>
cty
<int>
hwy
<int>
fl
<chr>
audia41.819994auto(l5)f1829p
audia41.819994manual(m5)f2129p
audia42.020084manual(m6)f2031p
audia42.020084auto(av)f2130p
audia42.819996auto(l5)f1626p
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
ABCDEFGHIJ0123456789
manufacturer
<chr>
model
<chr>
displ
<dbl>
year
<int>
cyl
<int>
trans
<chr>
drv
<chr>
cty
<int>
hwy
<int>
fl
<chr>
audia4 quattro1.819994manual(m5)41826p
audia4 quattro1.819994auto(l5)41625p
audia4 quattro2.819996auto(l5)41525p
audia4 quattro2.819996manual(m5)41725p
chevroletcorvette5.719998manual(m6)r1626p

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)
ABCDEFGHIJ0123456789
manufacturer
<chr>
model
<chr>
hwy
<int>
mean_hwy
<dbl>
audia42923.44017
audia42923.44017
audia43123.44017
audia43023.44017
audia42623.44017

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)
ABCDEFGHIJ0123456789
manufacturer
<chr>
model
<chr>
hwy
<int>
mean_hwy_class
<dbl>
audia42928.29787
audia42928.29787

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))
ABCDEFGHIJ0123456789
class
<chr>
frequency
<int>
relative_frequency
<dbl>
2seater50.02136752
compact470.20085470
midsize410.17521368
minivan110.04700855
pickup330.14102564
subcompact350.14957265
suv620.26495726

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))
ABCDEFGHIJ0123456789
cov(x = cty, y = hwy)
<dbl>
24.22543

4.2 Correlation

cor():

mpg %>% 
  summarise(cor(x=cty, y=hwy))
ABCDEFGHIJ0123456789
cor(x = cty, y = hwy)
<dbl>
0.9559159

4.3 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  

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

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
Term Meaning Pronunciation Formula/Example
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)2n1
σ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)n1[,]
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