A quick welcome to the tidyverse

Lockwood Lab Meeting

TS O’Leary

December 13, 2021

Introduction

This is a quick introduction to the tidyverse for lab meeting. And because I am not the authority on these things – here are a bunch of links that you may find more useful than following my ramblings.

What is the tidyverse?

A suite of inter-related packages with a common grammar and syntax.

# Install the tidyverse
install.packages("tidyverse")

# Install 
install.packages("broom")
# Load library
require(tidyverse)
require(broom)

Tidy data

The tidyverse gets its name from the type of data that it is designed to interact with – tidy data. So let’s quickly define tidy data.

  1. Every column is a variable.
  2. Every row is an observation.
  3. Every cell is a single value.

Yikes. That’s abstract…

Messy data

An example of some messy data.

# Make up some random data
mdh_df <- tibble(gill = rnorm(15, mean = 12, sd = 2),
                 adductor = rnorm(15, mean = 18, sd = 2.5),
                 mantle = rnorm(15, mean = 6, sd = 3))

# Print the data
mdh_df
# A tibble: 15 x 3
    gill adductor mantle
   <dbl>    <dbl>  <dbl>
 1 12.3      18.4 10.4  
 2 10.1      23.2  4.37 
 3 11.8      17.8  9.54 
 4 11.5      18.0  5.63 
 5 11.0      18.8  1.55 
 6 13.5      21.0  5.01 
 7 11.6      14.5  6.65 
 8  8.71     18.7  4.95 
 9 14.0      14.1 10.1  
10 14.9      20.4 -0.159
11 12.5      19.6  1.37 
12 11.8      15.6 12.5  
13 12.9      16.6  8.57 
14 12.8      20.4  6.56 
15 11.8      18.8  5.19 

Let’s tidy it

mdh_df <- mdh_df %>%
  pivot_longer(everything(),
               names_to = "tissue",
               values_to = "iu_gfw")

mdh_df
# A tibble: 45 x 2
   tissue   iu_gfw
   <chr>     <dbl>
 1 gill      12.3 
 2 adductor  18.4 
 3 mantle    10.4 
 4 gill      10.1 
 5 adductor  23.2 
 6 mantle     4.37
 7 gill      11.8 
 8 adductor  17.8 
 9 mantle     9.54
10 gill      11.5 
# … with 35 more rows

BAM! That is tidy data. 1. Every column is a variable – tissue and enzyme activity. 2. Every row is an observation – enzyme activity in I.U./g f.w.. 3. Every cell is a single value.

Importing data

Why use readr?

I find it a lot easier to define the data structure as you import data. And it creates a tibble instead of data.frame.

# Create a .csv file to import
write_csv(iris, "iris.csv")
# Try read.csv
d.f <- read.csv("iris.csv")

str(d.f)
'data.frame':   150 obs. of  5 variables:
 $ Sepal.Length: num  5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
 $ Sepal.Width : num  3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
 $ Petal.Length: num  1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
 $ Petal.Width : num  0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
 $ Species     : chr  "setosa" "setosa" "setosa" "setosa" ...
# Try read_csv
read_csv("iris.csv")
# A tibble: 150 x 5
   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
          <dbl>       <dbl>        <dbl>       <dbl> <chr>  
 1          5.1         3.5          1.4         0.2 setosa 
 2          4.9         3            1.4         0.2 setosa 
 3          4.7         3.2          1.3         0.2 setosa 
 4          4.6         3.1          1.5         0.2 setosa 
 5          5           3.6          1.4         0.2 setosa 
 6          5.4         3.9          1.7         0.4 setosa 
 7          4.6         3.4          1.4         0.3 setosa 
 8          5           3.4          1.5         0.2 setosa 
 9          4.4         2.9          1.4         0.2 setosa 
10          4.9         3.1          1.5         0.1 setosa 
# … with 140 more rows

# Set col_type as you import data -- allows you to define level order too
d_f <- read_csv("iris.csv",
                col_types = list(Species = col_factor(c("versicolor",
                                                        "setosa", 
                                                        "virginica"))))
d_f
# A tibble: 150 x 5
   Sepal.Length Sepal.Width Petal.Length Petal.Width Species
          <dbl>       <dbl>        <dbl>       <dbl> <fct>  
 1          5.1         3.5          1.4         0.2 setosa 
 2          4.9         3            1.4         0.2 setosa 
 3          4.7         3.2          1.3         0.2 setosa 
 4          4.6         3.1          1.5         0.2 setosa 
 5          5           3.6          1.4         0.2 setosa 
 6          5.4         3.9          1.7         0.4 setosa 
 7          4.6         3.4          1.4         0.3 setosa 
 8          5           3.4          1.5         0.2 setosa 
 9          4.4         2.9          1.4         0.2 setosa 
10          4.9         3.1          1.5         0.1 setosa 
# … with 140 more rows
str(d_f)
tibble [150 × 5] (S3: spec_tbl_df/tbl_df/tbl/data.frame)
 $ Sepal.Length: num [1:150] 5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
 $ Sepal.Width : num [1:150] 3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
 $ Petal.Length: num [1:150] 1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
 $ Petal.Width : num [1:150] 0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
 $ Species     : Factor w/ 3 levels "versicolor","setosa",..: 2 2 2 2 2 2 2 2 2 2 ...
 - attr(*, "spec")=
  .. cols(
  ..   Sepal.Length = col_double(),
  ..   Sepal.Width = col_double(),
  ..   Petal.Length = col_double(),
  ..   Petal.Width = col_double(),
  ..   Species = col_factor(levels = c("versicolor", "setosa", "virginica"), ordered = FALSE, include_na = FALSE)
  .. )
glimpse(d_f)
Rows: 150
Columns: 5
$ Sepal.Length <dbl> 5.1, 4.9, 4.7, 4.6, 5.0, 5.4, 4.6, 5.0, 4.4, 4.9, 5.4, 4…
$ Sepal.Width  <dbl> 3.5, 3.0, 3.2, 3.1, 3.6, 3.9, 3.4, 3.4, 2.9, 3.1, 3.7, 3…
$ Petal.Length <dbl> 1.4, 1.4, 1.3, 1.5, 1.4, 1.7, 1.4, 1.5, 1.4, 1.5, 1.5, 1…
$ Petal.Width  <dbl> 0.2, 0.2, 0.2, 0.2, 0.2, 0.4, 0.3, 0.2, 0.2, 0.1, 0.2, 0…
$ Species      <fct> setosa, setosa, setosa, setosa, setosa, setosa, setosa, …

Wrangling data

Intro to dplyr

dplyr has a few core functions that are built to work on

Taken from the dplyr vignette.

Rows

  • filter() chooses rows based on column values.
  • slice() chooses rows based on location.
  • arrange() changes the order of the rows.

Columns

  • select() changes whether or not a column is included.
  • rename() changes the name of columns.
  • mutate() changes the values of columns and creates new columns.
  • relocate() changes the order of the columns.

Groups of rows

  • summarise() collapses a group into a single row.

Pipe %>%

From magrittr.

  • x %>% f is equivalent to f(x)
  • x %>% f(y) is equivalent to f(x, y)
  • x %>% f %>% g %>% h is equivalent to h(g(f(x)))

The argument placeholder

  • x %>% f(y, .) is equivalent to f(y, x)
  • x %>% f(y, z = .) is equivalent to f(y, z = x)

A fun example from R for data science

foo_foo <- little_bunny()
foo_foo_1 <- hop(foo_foo, through = forest)
foo_foo_2 <- scoop(foo_foo_1, up = field_mice)
foo_foo_3 <- bop(foo_foo_2, on = head)
foo_foo %>%
  hop(through = forest) %>%
  scoop(up = field_mice) %>%
  bop(on = head)

arrange

mdh_df %>%
  arrange(tissue, iu_gfw)
# A tibble: 45 x 2
   tissue   iu_gfw
   <chr>     <dbl>
 1 adductor   14.1
 2 adductor   14.5
 3 adductor   15.6
 4 adductor   16.6
 5 adductor   17.8
 6 adductor   18.0
 7 adductor   18.4
 8 adductor   18.7
 9 adductor   18.8
10 adductor   18.8
# … with 35 more rows
mdh_df %>%
  arrange(desc(tissue), desc(iu_gfw))
# A tibble: 45 x 2
   tissue iu_gfw
   <chr>   <dbl>
 1 mantle  12.5 
 2 mantle  10.4 
 3 mantle  10.1 
 4 mantle   9.54
 5 mantle   8.57
 6 mantle   6.65
 7 mantle   6.56
 8 mantle   5.63
 9 mantle   5.19
10 mantle   5.01
# … with 35 more rows

summarise

mdh_df %>%
  summarise(iu_gfw_avg = mean(iu_gfw))
# A tibble: 1 x 1
  iu_gfw_avg
       <dbl>
1       12.2

group_by

mdh_df %>%
  group_by(tissue) %>%
  summarise(iu_gfw_avg = mean(iu_gfw),
            iu_gfw_sd = sd(iu_gfw))
# A tibble: 3 x 3
  tissue   iu_gfw_avg iu_gfw_sd
  <chr>         <dbl>     <dbl>
1 adductor      18.4       2.48
2 gill          12.1       1.52
3 mantle         6.15      3.61

Warning that you must be careful about the order when reusing variable names.

# Bad order
mdh_df %>%
  group_by(tissue) %>%
  summarise(iu_gfw = mean(iu_gfw),
            sd = sd(iu_gfw))
# A tibble: 3 x 3
  tissue   iu_gfw    sd
  <chr>     <dbl> <dbl>
1 adductor  18.4     NA
2 gill      12.1     NA
3 mantle     6.15    NA
# This order works because it collapses the data into a mean last
mdh_df %>%
  group_by(tissue) %>%
  summarise(sd = sd(iu_gfw),
            iu_gfw = mean(iu_gfw))
# A tibble: 3 x 3
  tissue      sd iu_gfw
  <chr>    <dbl>  <dbl>
1 adductor  2.48  18.4 
2 gill      1.52  12.1 
3 mantle    3.61   6.15

Print out a pretty table using kableExtra.

mdh_df %>%
  group_by(tissue) %>%
  summarise(iu_gfw_avg = mean(iu_gfw),
            iu_gfw_sd = sd(iu_gfw)) %>%
  mutate(`I.U. / g f.w.` = paste(round(iu_gfw_avg, 2), 
                                 "±", 
                                 round(iu_gfw_sd, 2))) %>%
  select(tissue, `I.U. / g f.w.`) %>%
  rename("Tissue" = "tissue",
         `MDH Activity (I.U. / g f.w.)` = `I.U. / g f.w.`) %>%
  mutate(Tissue = str_to_sentence(Tissue)) %>%
  kableExtra::kable()
Tissue MDH Activity (I.U. / g f.w.)
Adductor 18.39 ± 2.48
Gill 12.09 ± 1.52
Mantle 6.15 ± 3.61

nest

ChickWeight %>%
  glimpse()
Rows: 578
Columns: 4
$ weight <dbl> 42, 51, 59, 64, 76, 93, 106, 125, 149, 171, 199, 205, 40, 49, …
$ Time   <dbl> 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 21, 0, 2, 4, 6, 8, 10, …
$ Chick  <ord> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2,…
$ Diet   <fct> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,…
ChickWeight %>%
  group_by(Chick, Diet) %>%
  nest() 
# A tibble: 50 x 3
# Groups:   Chick, Diet [50]
   Chick Diet  data             
   <ord> <fct> <list>           
 1 1     1     <tibble [12 × 2]>
 2 2     1     <tibble [12 × 2]>
 3 3     1     <tibble [12 × 2]>
 4 4     1     <tibble [12 × 2]>
 5 5     1     <tibble [12 × 2]>
 6 6     1     <tibble [12 × 2]>
 7 7     1     <tibble [12 × 2]>
 8 8     1     <tibble [11 × 2]>
 9 9     1     <tibble [12 × 2]>
10 10    1     <tibble [12 × 2]>
# … with 40 more rows
ChickWeight_nest <- ChickWeight %>%
  group_by(Chick, Diet) %>%
  nest() 

ChickWeight_nest$data[1:2]
[[1]]
# A tibble: 12 x 2
   weight  Time
    <dbl> <dbl>
 1     42     0
 2     51     2
 3     59     4
 4     64     6
 5     76     8
 6     93    10
 7    106    12
 8    125    14
 9    149    16
10    171    18
11    199    20
12    205    21

[[2]]
# A tibble: 12 x 2
   weight  Time
    <dbl> <dbl>
 1     40     0
 2     49     2
 3     58     4
 4     72     6
 5     84     8
 6    103    10
 7    122    12
 8    138    14
 9    162    16
10    187    18
11    209    20
12    215    21

broom

CHeck out the broom vignette.

[And the broom and dplyr vignette] (https://cran.r-project.org/web/packages/broom/vignettes/broom_and_dplyr.html).

tidy: constructs a tibble that summarizes the model’s statistical findings. This includes coefficients and p-values for each term in a regression, per-cluster information in clustering applications, or per-test information for multtest functions.

glance: construct a concise one-row summary of the model. This typically contains values such as R^2, adjusted R^2, and residual standard error that are computed once for the entire model.

ChickWeight %>%
  group_by(Chick, Diet) %>%
  nest() %>%
  mutate(
    fit = map(data, ~ lm(weight ~ Time, data = .x)),
    tidied = map(fit, tidy),
    glanced = map(fit, glance)
  ) %>% 
  unnest(tidied) 
# A tibble: 100 x 10
# Groups:   Chick, Diet [50]
   Chick Diet  data   fit    term  estimate std.error statistic  p.value glanced
   <ord> <fct> <list> <list> <chr>    <dbl>     <dbl>     <dbl>    <dbl> <list> 
 1 1     1     <tibb… <lm>   (Int…    24.5      6.73       3.64 4.56e- 3 <tibbl…
 2 1     1     <tibb… <lm>   Time      7.99     0.524     15.3  2.97e- 8 <tibbl…
 3 2     1     <tibb… <lm>   (Int…    24.7      4.93       5.01 5.26e- 4 <tibbl…
 4 2     1     <tibb… <lm>   Time      8.72     0.384     22.7  6.15e-10 <tibbl…
 5 3     1     <tibb… <lm>   (Int…    23.2      5.08       4.56 1.04e- 3 <tibbl…
 6 3     1     <tibb… <lm>   Time      8.49     0.396     21.5  1.08e- 9 <tibbl…
 7 4     1     <tibb… <lm>   (Int…    32.9      4.01       8.21 9.42e- 6 <tibbl…
 8 4     1     <tibb… <lm>   Time      6.09     0.312     19.5  2.70e- 9 <tibbl…
 9 5     1     <tibb… <lm>   (Int…    16.9      7.56       2.24 4.93e- 2 <tibbl…
10 5     1     <tibb… <lm>   Time     10.1      0.588     17.1  9.88e- 9 <tibbl…
# … with 90 more rows
ChickWeight %>%
  ggplot() +
  geom_line(aes(x = Time, 
                 y = weight, 
                 color = Chick)) +
  facet_wrap(~ Diet)

Integrative example

# Import data -----

# ADH activity
adh_df <- read_csv("https://raw.githubusercontent.com/tsoleary/projects/master/ADH/data/biovision/adh_abs_11022020.csv")

# NADH std curve
nadh_df <- read_csv("https://raw.githubusercontent.com/tsoleary/projects/master/ADH/data/biovision/NADH_std_11022020.csv")

NADH Std Curve

nadh_df
# A tibble: 12 x 7
    NADH  temp `0_min` `30_min` `60_min` `90_min` `120_min`
   <dbl> <dbl>   <dbl>    <dbl>    <dbl>    <dbl>     <dbl>
 1     0    28  0.0481   0.0483   0.048    0.0482    0.048 
 2     2    28  0.167    0.168    0.168    0.168     0.167 
 3     4    28  0.299    0.305    0.303    0.302     0.301 
 4     6    28  0.441    0.447    0.446    0.445     0.444 
 5     8    28  0.569    0.581    0.578    0.577     0.575 
 6    10    28  0.688    0.711    0.706    0.705     0.703 
 7     0    28  0.0485   0.0483   0.0483   0.0484    0.0483
 8     2    28  0.170    0.172    0.172    0.172     0.171 
 9     4    28  0.303    0.305    0.305    0.304     0.303 
10     6    28  0.432    0.434    0.442    0.440     0.439 
11     8    28  0.575    0.582    0.580    0.582     0.579 
12    10    28  0.708    0.723    0.721    0.719     0.717 

nadh_df <- nadh_df %>%
  pivot_longer(cols = contains("min"), 
               names_to = "mins", 
               values_to = "abs") %>%
  mutate(mins = as.numeric(str_remove_all(mins, "_min"))) 

nadh_df
# A tibble: 60 x 4
    NADH  temp  mins    abs
   <dbl> <dbl> <dbl>  <dbl>
 1     0    28     0 0.0481
 2     0    28    30 0.0483
 3     0    28    60 0.048 
 4     0    28    90 0.0482
 5     0    28   120 0.048 
 6     2    28     0 0.167 
 7     2    28    30 0.168 
 8     2    28    60 0.168 
 9     2    28    90 0.168 
10     2    28   120 0.167 
# … with 50 more rows

Standard curve linear regression

# Run linear model
std_lm <- lm(abs ~ NADH, nadh_df)

summary(std_lm)

Call:
lm(formula = abs ~ NADH, data = nadh_df)

Residuals:
       Min         1Q     Median         3Q        Max 
-0.0203362 -0.0049828 -0.0005845  0.0065411  0.0143638 

Coefficients:
             Estimate Std. Error t value Pr(>|t|)    
(Intercept) 0.0412305  0.0015183   27.16   <2e-16 ***
NADH        0.0667506  0.0002507  266.22   <2e-16 ***
---
Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1

Residual standard error: 0.006634 on 58 degrees of freedom
Multiple R-squared:  0.9992,    Adjusted R-squared:  0.9992 
F-statistic: 7.087e+04 on 1 and 58 DF,  p-value: < 2.2e-16
# Save intercept & slope values
int <- as.numeric(std_lm$coefficients[1])
slope <- as.numeric(std_lm$coefficients[2])

# Save values in a named vector
std_curve_lm <- c(int = int, 
                  slope = slope)

std_curve_lm 
       int      slope 
0.04123048 0.06675057 

Absorbance data

# ADH activity absorbance data
adh_df
# A tibble: 32 x 10
     EtOH  temp sample_vol n_flies genotype `0_min` `30_min` `60_min` `90_min`
    <dbl> <dbl>      <dbl>   <dbl> <chr>      <dbl>    <dbl>    <dbl>    <dbl>
 1 0.        28         25       1 adh-f     0.0555   0.109     0.180    0.282
 2 6.25e0    28         25       1 adh-f     0.0603   0.143     0.246    0.369
 3 1.25e1    28         25       1 adh-f     0.0659   0.195     0.347    0.517
 4 2.50e1    28         25       1 adh-f     0.0699   0.211     0.360    0.526
 5 5.00e1    28         25       1 adh-f     0.0732   0.242     0.415    0.595
 6 1.00e2    28         25       1 adh-f     0.0798   0.307     0.536    0.773
 7 2.00e2    28         25       1 adh-f     0.084    0.354     0.630    0.915
 8 1.00e3    28         25       1 adh-f     0.0868   0.375     0.666    0.970
 9 0.        28         25       1 adh-f     0.0528   0.0716    0.097    0.139
10 6.25e0    28         25       1 adh-f     0.0581   0.114     0.172    0.236
# … with 22 more rows, and 1 more variable: `120_min` <dbl>

Tidy the data

adh_df <- adh_df %>%
  pivot_longer(cols = contains("min"), 
               names_to = "mins", 
               values_to = "abs") %>%
  mutate(mins = as.numeric(str_remove_all(mins, "_min")))

adh_df
# A tibble: 160 x 7
    EtOH  temp sample_vol n_flies genotype  mins    abs
   <dbl> <dbl>      <dbl>   <dbl> <chr>    <dbl>  <dbl>
 1  0       28         25       1 adh-f        0 0.0555
 2  0       28         25       1 adh-f       30 0.109 
 3  0       28         25       1 adh-f       60 0.180 
 4  0       28         25       1 adh-f       90 0.282 
 5  0       28         25       1 adh-f      120 0.391 
 6  6.25    28         25       1 adh-f        0 0.0603
 7  6.25    28         25       1 adh-f       30 0.143 
 8  6.25    28         25       1 adh-f       60 0.246 
 9  6.25    28         25       1 adh-f       90 0.369 
10  6.25    28         25       1 adh-f      120 0.504 
# … with 150 more rows

# Add temp data with some noise
x <- adh_df %>%
  mutate(temp = 23,
         abs = abs*rnorm(160, mean = 0.9, sd = 0.02))

adh_df <- bind_rows(adh_df, x)

Plot the standard curve

ggplot(data = nadh_df, 
       aes(x = NADH, 
           y = abs)) +
  geom_smooth(method = 'lm', 
              formula = y ~ x, 
              se = FALSE) +
  geom_point() +
  ggpmisc::stat_poly_eq(formula = y ~ x, 
                        aes(label = paste(..eq.label.., ..rr.label.., 
                                          sep = "*\"; \"*")),
                        parse = TRUE, 
                        rr.digits = 5,
                        label.x = 0.85, 
                        label.y = "top") +
  theme_classic() +
  labs(y = "Absorbance @ 450 nm", 
       x = "NADH (nmol)")

Calculate enzyme activity

adh_df <- adh_df %>%
  group_by(temp, EtOH) %>%
  mutate(nmol_NADH = (abs - std_curve_lm["int"]) / std_curve_lm["slope"],
         deltaAbs = abs - abs[mins == 0],
         deltaNADH = nmol_NADH - nmol_NADH[mins == 0],
         deltaTime = mins - 0) %>%
  filter(deltaTime != 0) %>%
  mutate(dilution_factor = 150/sample_vol,
         mU_mL = (deltaNADH/(deltaTime*(sample_vol/1000)))*dilution_factor) %>%
  ungroup(EtOH) %>%
  mutate(mU_mL = mU_mL - mU_mL[EtOH == 0])

Calculating Km

adh_df %>%
  filter(mins <= 60) %>%
  group_by(temp, EtOH) %>%
  nest() %>%
  mutate(
    fit = map(data, ~ lm(abs ~ mins, data = .x)),
    tidied = map(fit, tidy),
    glanced = map(fit, glance)) 
# A tibble: 16 x 6
# Groups:   EtOH, temp [16]
      EtOH  temp data              fit    tidied           glanced          
     <dbl> <dbl> <list>            <list> <list>           <list>           
 1    0       28 <tibble [8 × 11]> <lm>   <tibble [2 × 5]> <tibble [1 × 12]>
 2    6.25    28 <tibble [8 × 11]> <lm>   <tibble [2 × 5]> <tibble [1 × 12]>
 3   12.5     28 <tibble [8 × 11]> <lm>   <tibble [2 × 5]> <tibble [1 × 12]>
 4   25       28 <tibble [8 × 11]> <lm>   <tibble [2 × 5]> <tibble [1 × 12]>
 5   50       28 <tibble [8 × 11]> <lm>   <tibble [2 × 5]> <tibble [1 × 12]>
 6  100       28 <tibble [8 × 11]> <lm>   <tibble [2 × 5]> <tibble [1 × 12]>
 7  200       28 <tibble [8 × 11]> <lm>   <tibble [2 × 5]> <tibble [1 × 12]>
 8 1000       28 <tibble [8 × 11]> <lm>   <tibble [2 × 5]> <tibble [1 × 12]>
 9    0       23 <tibble [8 × 11]> <lm>   <tibble [2 × 5]> <tibble [1 × 12]>
10    6.25    23 <tibble [8 × 11]> <lm>   <tibble [2 × 5]> <tibble [1 × 12]>
11   12.5     23 <tibble [8 × 11]> <lm>   <tibble [2 × 5]> <tibble [1 × 12]>
12   25       23 <tibble [8 × 11]> <lm>   <tibble [2 × 5]> <tibble [1 × 12]>
13   50       23 <tibble [8 × 11]> <lm>   <tibble [2 × 5]> <tibble [1 × 12]>
14  100       23 <tibble [8 × 11]> <lm>   <tibble [2 × 5]> <tibble [1 × 12]>
15  200       23 <tibble [8 × 11]> <lm>   <tibble [2 × 5]> <tibble [1 × 12]>
16 1000       23 <tibble [8 × 11]> <lm>   <tibble [2 × 5]> <tibble [1 × 12]>
adh_velo <- adh_df %>%
  filter(mins <= 60) %>%
  group_by(temp, EtOH) %>%
  nest() %>%
  mutate(
    fit = map(data, ~ lm(abs ~ mins, data = .x)),
    tidied = map(fit, tidy),
    glanced = map(fit, glance)
  ) %>% 
  unnest(tidied) %>%
  filter(term == "mins") %>%
  select(temp, EtOH, estimate) %>%
  rename(velocity = estimate) %>%
  filter(EtOH != 0) %>%
  arrange(temp, EtOH)
adh_mm <- adh_velo %>%
  ungroup(EtOH) %>%
  filter(EtOH != 0) %>%
  group_by(temp) %>%
  nest() %>%
  mutate(
    fit = map(data, ~ nls(formula = 
                            velocity ~ SSmicmen(EtOH, Vmax, Km), data = .), 
              data = .x),
    tidied = map(fit, tidy)
  ) %>%
  unnest(tidied)

adh_mm
# A tibble: 4 x 8
# Groups:   temp [2]
   temp data             fit    term  estimate std.error statistic    p.value
  <dbl> <list>           <list> <chr>    <dbl>     <dbl>     <dbl>      <dbl>
1    23 <tibble [7 × 2]> <nls>  Vmax   0.00519  0.000364     14.3  0.0000306 
2    23 <tibble [7 × 2]> <nls>  Km    30.2      7.74          3.91 0.0113    
3    28 <tibble [7 × 2]> <nls>  Vmax   0.00583  0.000291     20.1  0.00000569
4    28 <tibble [7 × 2]> <nls>  Km    29.5      5.39          5.47 0.00278   

Plot

adh_velo %>%
  mutate(temp = as.factor(temp)) %>%
  group_by(temp) %>%
  ggplot(aes(x = EtOH,
             y = velocity)) +
  geom_smooth(method = "nls", 
              aes(group = temp),
              formula = y ~ SSmicmen(x, Vmax, Km),
              color = "grey50",
              data = adh_velo,
              se = FALSE,
              show.legend = FALSE) +
  geom_point(aes(color = temp)) +
  theme_classic()

Plotting data

ggplot2

Check out the ggplot2 book.

ggplot2 creates things in a specific order of layers.

glimpse(starwars)
Rows: 87
Columns: 14
$ name       <chr> "Luke Skywalker", "C-3PO", "R2-D2", "Darth Vader", "Leia O…
$ height     <int> 172, 167, 96, 202, 150, 178, 165, 97, 183, 182, 188, 180, …
$ mass       <dbl> 77.0, 75.0, 32.0, 136.0, 49.0, 120.0, 75.0, 32.0, 84.0, 77…
$ hair_color <chr> "blond", NA, NA, "none", "brown", "brown, grey", "brown", …
$ skin_color <chr> "fair", "gold", "white, blue", "white", "light", "light", …
$ eye_color  <chr> "blue", "yellow", "red", "yellow", "brown", "blue", "blue"…
$ birth_year <dbl> 19.0, 112.0, 33.0, 41.9, 19.0, 52.0, 47.0, NA, 24.0, 57.0,…
$ sex        <chr> "male", "none", "none", "male", "female", "male", "female"…
$ gender     <chr> "masculine", "masculine", "masculine", "masculine", "femin…
$ homeworld  <chr> "Tatooine", "Tatooine", "Naboo", "Tatooine", "Alderaan", "…
$ species    <chr> "Human", "Droid", "Droid", "Human", "Human", "Human", "Hum…
$ films      <list> [<"The Empire Strikes Back", "Revenge of the Sith", "Retu…
$ vehicles   <list> [<"Snowspeeder", "Imperial Speeder Bike">, <>, <>, <>, "I…
$ starships  <list> [<"X-wing", "Imperial shuttle">, <>, <>, "TIE Advanced x1…
starwars %>%
  ggplot(aes(x = sex, y = height)) +
  geom_point() +
  geom_boxplot() +
  theme_classic()

starwars %>%
  ggplot(aes(x = sex, y = height)) +
  geom_boxplot() +
  geom_point() +
  theme_classic()

starwars %>%
  ggplot(aes(x = sex, y = height)) +
  geom_boxplot() +
  geom_jitter(width = 0.2) +
  theme_classic()

starwars %>%
  ggplot(aes(x = sex, y = height)) +
  geom_hline(aes(yintercept = 188), 
             linetype = 2, 
             color = "grey20") +
  geom_boxplot(outlier.shape = NA) +
  geom_jitter(width = 0.1, 
              size = 2,
              alpha = 0.8,
              shape = 21, 
              color = "grey20", 
              fill = "grey50") +
  scale_y_continuous(breaks = c(100, 150, 188, 200, 250),
                     labels = c("100", "150", "Thomas", "200", "250"),
                     name = "Height") +
  theme_classic()

starwars %>%
  filter(sex %in% c("male", "female")) %>%
  ggplot(aes(x = sex, y = height)) +
  geom_violin(fill = "azure2",
              color = "grey20") +
  geom_boxplot(fill = "azure2",
               width = 0.2,
               color = "grey20") +
  theme_classic()

starwars %>%
  filter(birth_year < 200) %>%
  ggplot() +
  geom_histogram(aes(x = birth_year), 
                 binwidth = 10)

starwars %>%
  filter(birth_year < 200) %>%
  ggplot() +
  geom_histogram(aes(x = birth_year),
                 color = "grey20",
                 fill = "grey50",
                 binwidth = 10) +
  theme_classic()

starwars %>%
  filter(birth_year < 200) %>%
  ggplot() +
  geom_histogram(aes(x = birth_year),
                 color = "grey20",
                 fill = "grey50",
                 binwidth = 10) +
  geom_density(aes(x = birth_year, 
                   y =  10 * ..count..),
               color = "grey20") +
  ylab("count") +
  theme_classic()

starwars %>%
  filter(mass < 1000) %>%
  mutate(species_h = ifelse(species == "Human",  "Human", "Other")) %>%
  ggplot() +
  geom_point(aes(x = height, 
                 y = mass, 
                 fill = species_h),
             size = 5,
             alpha = 0.8,
             color = "grey50",
             shape = 21) +
  theme_classic()

starwars %>%
  filter(mass < 1000) %>%
  mutate(species_h = ifelse(species == "Human",  "Human", "Other")) %>%
  arrange(desc(species_h)) %>%
  ggplot() +
  geom_point(aes(x = height, 
                 y = mass, 
                 fill = species_h),
             size = 5,
             alpha = 0.8,
             color = "grey50",
             shape = 21) +
  theme_classic()

starwars_h <- starwars %>%
  filter(mass < 1000) %>%
  mutate(species_h = ifelse(species == "Human",  "Human", "Other")) %>%
  arrange(desc(species_h))


p <- ggplot() +
  geom_point(data = starwars_h %>%
               filter(species_h != "Human"),
             aes(x = height, 
                 y = mass, 
                 fill = species_h),
             size = 1,
             alpha = 0.8,
             color = "grey50",
             shape = 21) +
    geom_point(data = starwars_h %>%
               filter(species_h == "Human"),
               aes(x = height, 
                 y = mass, 
                 fill = species_h),
             size = 3,
             alpha = 0.8,
             color = "grey50",
             shape = 21) +
  scale_fill_manual(values = c("#d66666", "#222222")) +
  theme_classic()

p

plotly::ggplotly(p, text = "text")

friends <- c("Luke Skywalker", 
             "Leia Organa",
             "Han Solo",
             "Chewbacca",
             "C-3PO",
             "R2-D2",
             "Obi-Wan Kenobi")

ggplot() +
  geom_point(data = starwars_h, 
             aes(x = height, 
                 y = mass,
                 fill = species_h),
             size = 5,
             alpha = 0.8,
             color = "grey50",
             shape = 21) +
  ggrepel::geom_label_repel(data = starwars_h %>%
                              filter(name %in% friends),
                            aes(x = height, 
                                y = mass,
                                label = name),
                            min.segment.length = 0.01) +
  theme_classic()

These are some examples of plots that I have made in R.

ADH data

World Record Progression

Creating functions

Functions are extremely useful when you are doing repetive tasks. And greating

Using snippets can help

For example when I type mfun_snip and hit tab, the following code prints out on my script.

# ------------------------------------------------------------------------------
# Function: function_name
# Description: description
# Inputs: input_description
# Outputs: output_description

function_name <- function(args) {
  # Function body
  print("Hello world!")
} 
# End function -----------------------------------------------------------------

Which is made with the following code inserted into the snippets file. This webpage walks through the process of adding snippets.

snippet mfun_snip
    # ------------------------------------------------------------------------------
    # Function: ${1:function_name}
    # Description: ${2:description}
    # Inputs: ${3:input_description}
    # Outputs: ${4:output_description}
    
    ${1:function_name} <- function(args) {
        # Function body
        print("Hello world!")
    } 
    # End function -----------------------------------------------------------------

Examples of functions

# ------------------------------------------------------------------------------
# Function: read_tidy_spec_csv
# Description: uses read_csv and tidys the data
# Inputs: .csv file 
# Outputs: data.frame

require(tidyverse)

read_tidy_spec_csv <- function(file) {
  
  read_csv(file) %>%
    pivot_longer(-c("cycle", "time", "temp"), 
                 names_to = "conc_well", 
                 values_to = "abs") %>%
    mutate(conc = as.numeric(str_replace_all(conc_well, "_.", ""))) %>%
    mutate(file = str_remove(file, ".csv")) %>%
    separate(file, into = c("enzyme", "temp_group", "date"), sep = "_")
  
} 
# End function -----------------------------------------------------------------
# ------------------------------------------------------------------------------
# Function: calc_Km_Vm
# Description: Calculate Vmax and Km for Michaelis–Menten curve
# Inputs: tibble with velocity estimates for each conc and temp_group
# Outputs: tibble with estimates for Vmax and Km

require(tidyverse)
require(broom)

calc_Km_Vm <- function(data) {
  data %>%
    filter(conc != 0 & velocity > 0) %>%
    group_by(enzyme, temp_group) %>%
    nest() %>%
    mutate(
      fit = map(data, ~ nls(formula = 
                              velocity ~ SSmicmen(conc, Vmax, Km), data = .), 
                data = .x),
      tidied = map(fit, tidy)
    ) %>%
    unnest(tidied) %>%
    mutate(r.squared = map(fit, Rsq)) %>%
    unnest(r.squared)
} 
# End function -----------------------------------------------------------------
# ------------------------------------------------------------------------------
# Function: v_test
# Description: calculate v from Hunter B. Fraser PNAS 2020 paper
# Inputs: 
#   var_par - variance of parents
#   var_p1 - variance of parent 1
#   var_p2 - variance of parent 2
#   n_p1 - number of parent 1 individuals
#   n_p2 - number of parent 2 individuals
#   var_f2 - variance of f2 hybrids
#   H2 - broad sense heritibility
#   c
# Outputs: v-test value

v_test <- function(var_par, var_p1, var_p2, n_p1, n_p2, var_f2, H2, c) {
  # Calculate the value of v
  (var_par - var_p1/(4 * n_p1) - var_p2/(4 * n_p2)) / (var_f2 * H2 * c)
} 
# End function -----------------------------------------------------------------

  1. My favorite part of the tidyverse is the final principle, which is: 4. Design for humans. “Programs must be written for people to read, and only incidentally for machines to execute.” — Hal Abelson

  2. Honestly I think that joke underestimates how important good coding style is. You can actually read “butitsuremakesthingseasiertoread” pretty easily because you are an expert reader – you’ve been at it everyday for decades – coding, probably not so much. I don’t think can overstate how important I think it is to write visually pleasing code.

  3. Warning: I have downloaded these cheat sheets and saved them for my quick access, but they may not be the most current version of the cheat sheet.

  4. Ever had a column that is actually a integer import into R as a character? readr can easily define column types as you import, among other things.

LS0tCnRpdGxlOiAiQSBxdWljayB3ZWxjb21lIHRvIHRoZSAqKnRpZHl2ZXJzZSoqIgpzdWJ0aXRsZTogIkxvY2t3b29kIExhYiBNZWV0aW5nIgpkYXRlOiAiRGVjZW1iZXIgMTMsIDIwMjEiCmF1dGhvcjogIlRTIE8nTGVhcnkiCm91dHB1dDoKICBybWRmb3JtYXRzOjpkb3duY3V0ZToKICAgIHNlbGZfY29udGFpbmVkOiB0cnVlCiAgICB0aHVtYm5haWxzOiBmYWxzZQogICAgbGlnaHRib3g6IHRydWUKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKICAgIGRvd25jdXRlX3RoZW1lOiAiY2hhb3MiCi0tLQoKYGBge3Igc2V0dXAsIGluY2x1ZGU9RkFMU0V9CmtuaXRyOjpvcHRzX2NodW5rJHNldChlY2hvID0gVFJVRSwgCiAgICAgICAgICAgICAgICAgICAgICBjb21tZW50ID0gTkEsIAogICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZSA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgd2FybmluZyA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgc3RyaXAud2hpdGUgPSBGQUxTRSkKYGBgCgojICoqSW50cm9kdWN0aW9uKioKClRoaXMgaXMgYSBxdWljayBpbnRyb2R1Y3Rpb24gdG8gdGhlIGB0aWR5dmVyc2VgIGZvciBsYWIgbWVldGluZy4gQW5kIGJlY2F1c2UgSSBhbSBub3QgdGhlIGF1dGhvcml0eSBvbiB0aGVzZSB0aGluZ3MgLS0gaGVyZSBhcmUgYSBidW5jaCBvZiBsaW5rcyB0aGF0IHlvdSBtYXkgZmluZCBtb3JlIHVzZWZ1bCB0aGFuIGZvbGxvd2luZyBteSByYW1ibGluZ3MuCgojIyBMaW5rcyAKCiMjIyBCb29rcyAmIHdlYnBhZ2VzCgogLSBbdGlkeXZlcnNlIHdlYnNpdGVdKGh0dHBzOi8vd3d3LnRpZHl2ZXJzZS5vcmcvKSAtLSBwb2tlIGFyb3VuZCB0aGlzIHdlYnNpdGUgYW5kIHlvdSBjYW4gc3R1bWJsZSBvbiBzb21lIGdvb2Qgc3R1ZmYKIC0gW1IgZm9yIGRhdGEgc2NpZW5jZV0oaHR0cHM6Ly9yNGRzLmhhZC5jby5uei9pbmRleC5odG1sKSAtLSBhIHdvbmRlcmZ1bGx5IHRob3JvdWdoIGFuZCB1c2VmdWwgYm9vayB0aGF0IGVtcGhhc2l6ZXMgdGhlIHRpZHl2ZXJzZQogLSBbUiBmb3IgR3JhZHVhdGUgU3R1ZGVudHNdKGh0dHBzOi8vYm9va2Rvd24ub3JnL3lpaF9odXluaC9HdWlkZS10by1SLUJvb2svKSAtLSB2ZXJ5IGFjY2Vzc2libGUgaW50cm9kdWN0aW9uIHRvIFIgJiB0aGUgdGlkeXZlcnNlCiAtIFtUaGUgdGlkeSB0b29scyBtYW5pZmVzdG9dKGh0dHBzOi8vbXJhbi5taWNyb3NvZnQuY29tL3dlYi9wYWNrYWdlcy90aWR5dmVyc2UvdmlnbmV0dGVzL21hbmlmZXN0by5odG1sKSAtLSB3aG8gZG9lc24ndCBsb3ZlIGEgZ29vZCBtYW5pZmVzdG8/IF5bTXkgZmF2b3JpdGUgcGFydCBvZiB0aGUgdGlkeXZlcnNlIGlzIHRoZSBmaW5hbCBwcmluY2lwbGUsIHdoaWNoIGlzOiA0LiBEZXNpZ24gZm9yIGh1bWFucy4gIlByb2dyYW1zIG11c3QgYmUgd3JpdHRlbiBmb3IgcGVvcGxlIHRvIHJlYWQsIGFuZCBvbmx5IGluY2lkZW50YWxseSBmb3IgbWFjaGluZXMgdG8gZXhlY3V0ZS4iIOKAlCBIYWwgQWJlbHNvbl0KIC0gW1RoZSB0aWR5dmVyc2Ugc3R5bGUgZ3VpZGVdKGh0dHBzOi8vc3R5bGUudGlkeXZlcnNlLm9yZy9pbmRleC5odG1sKSAtLSAiR29vZCBjb2Rpbmcgc3R5bGUgaXMgbGlrZSBjb3JyZWN0IHB1bmN0dWF0aW9uOiB5b3UgY2FuIG1hbmFnZSB3aXRob3V0IGl0LCBidXRpdHN1cmVtYWtlc3RoaW5nc2Vhc2llcnRvcmVhZC4iIF5bSG9uZXN0bHkgSSB0aGluayB0aGF0IGpva2UgdW5kZXJlc3RpbWF0ZXMgaG93IGltcG9ydGFudCBnb29kIGNvZGluZyBzdHlsZSBpcy4gWW91IGNhbiBhY3R1YWxseSByZWFkICJidXRpdHN1cmVtYWtlc3RoaW5nc2Vhc2llcnRvcmVhZCIgcHJldHR5IGVhc2lseSBiZWNhdXNlIHlvdSBhcmUgYW4gZXhwZXJ0IHJlYWRlciAtLSB5b3UndmUgYmVlbiBhdCBpdCBldmVyeWRheSBmb3IgZGVjYWRlcyAtLSBjb2RpbmcsIHByb2JhYmx5IG5vdCBzbyBtdWNoLiBJIGRvbid0IHRoaW5rIGNhbiBvdmVyc3RhdGUgaG93IGltcG9ydGFudCBJIHRoaW5rIGl0IGlzIHRvIHdyaXRlIHZpc3VhbGx5IHBsZWFzaW5nIGNvZGUuXQoKCiMjIyBDaGVhdCBzaGVldHMgXltXYXJuaW5nOiBJIGhhdmUgZG93bmxvYWRlZCB0aGVzZSBjaGVhdCBzaGVldHMgYW5kIHNhdmVkIHRoZW0gZm9yIG15IHF1aWNrIGFjY2VzcywgYnV0IHRoZXkgbWF5IG5vdCBiZSB0aGUgbW9zdCBjdXJyZW50IHZlcnNpb24gb2YgdGhlIGNoZWF0IHNoZWV0Ll0KCgojIyMjIENvcmUgdGlkeXZlcnNlCgotIFtgcmVhZHJgXShodHRwczovL3Rzb2xlYXJ5LmdpdGh1Yi5pby9jb21wX2Jpby9DaGVhdFNoZWV0cy9yZWFkci5wZGYpIC0tICoqaW1wb3J0IHlvdXIgZGF0YSoqLiBOZWVkIHRvIGltcG9ydCBkYXRhPyBDb29sIGtpZHMgPSBgcmVhZF9jc3YoKWAuIF5bRXZlciBoYWQgYSBjb2x1bW4gdGhhdCBpcyBhY3R1YWxseSBhIGludGVnZXIgaW1wb3J0IGludG8gUiBhcyBhIGNoYXJhY3Rlcj8gYHJlYWRyYCBjYW4gZWFzaWx5IGRlZmluZSBjb2x1bW4gdHlwZXMgYXMgeW91IGltcG9ydCwgYW1vbmcgb3RoZXIgdGhpbmdzLl0KLSBbYHRpZHlyYF0oaHR0cHM6Ly90c29sZWFyeS5naXRodWIuaW8vY29tcF9iaW8vQ2hlYXRTaGVldHMvdGlkeXIucGRmKSAtLSAqKnRpZHkgeW91ciBkYXRhKiouIEhlbHBzIHlvdSB0aWR5IHlvdXIgZGF0YSBxdWljay4gV2hhdCBkb2VzIHRpZHkgZGF0YSBtZWFuPyBLZWVwIHJlYWRpbmcuLi4KLSBgdGliYmxlYCAtLSAqKmEgbmV3IGRhdGEuZnJhbWUqKiAtLSBkb2Vzbid0IGhhdmUgYSBjaGVhdCBzaGVldCwganVzdCB3b3JrcyBpbiB0aGUgc2hhZG93cy4KLSBbYGRwbHlyYF0oaHR0cHM6Ly90c29sZWFyeS5naXRodWIuaW8vY29tcF9iaW8vQ2hlYXRTaGVldHMvZHBseXIucGRmKSAtLSAqKndyYW5nbGUgeW91ciBkYXRhKiouIE5lZWQgdG8gZ2V0IGEgbWVhbj8gRmluZCBhIHN0YW5kYXJkIGRldmlhdGlvbj8gTG9vayBubyBmdXJ0aGVyLgotIFtgZ2dwbG90MmBdKGh0dHBzOi8vdHNvbGVhcnkuZ2l0aHViLmlvL2NvbXBfYmlvL0NoZWF0U2hlZXRzL2dncGxvdDIucGRmKSAtLSAqKnBsb3QgeW91ciBkYXRhKiogLS0geW91IGFscmVhZHkga25vdyBnZ3Bsb3QuCi0gW2BzdHJpbmdyYF0oaHR0cHM6Ly90c29sZWFyeS5naXRodWIuaW8vY29tcF9iaW8vQ2hlYXRTaGVldHMvc3RyaW5nci5wZGYpIC0tICoqbWFpcHVsYXRlIHN0cmluZ3MqKi4gSGF2ZSBhIGJ1bmNoIG9mIHN0cmluZ3MgZm9yIHNvbWUgcmVhc29uPyBVc2UgYHN0cmluZ3JgLiAKLSBbYHB1cnJyYF0oaHR0cHM6Ly90c29sZWFyeS5naXRodWIuaW8vY29tcF9iaW8vQ2hlYXRTaGVldHMvcHVycnIucGRmKSAtLSAqKmZ1bmN0aW9uYWwgcHJvZ3JhbW1pbmcqKi4gV2FubmEgY29uc2VydmUgZW5lcmd5IGxpa2UgYSBjYXQ/IFJlcGxhY2UgYGZvcigpYCB3aXRoIGBtYXAoKWAuCi0gW2Bmb3JjYXRzYF0oaHR0cHM6Ly90c29sZWFyeS5naXRodWIuaW8vY29tcF9iaW8vQ2hlYXRTaGVldHMvZm9yY2F0cy5wZGYpIC0tICoqbWFuaXB1bGF0ZSBmYWN0b3JzKiotLSBVc2luZyBhIGJ1bmNoIG9mIGZhY3RvcnM/IFJlb3JkZXIgdGhlbSB3aXRoIGBmb3JjYXRzYC4KCiMjIyMgT3RoZXIgdXNlZnVsIHRpZHl2ZXJzZSBwYWNrYWdlcyAKCi0gYGJyb29tYCAtLSAqKmNsZWFuIG1vZGVsIG91dHB1dCoqIC0tIHRlY2huaWNhbGx5IGEgc3VicGFja2FnZSBvZiBgdGlkeW1vZGVsc2AgYSBjb3VzaW4gb2YgdGhlIHRpZHl2ZXJzZQotIGBydmVzdGAgLS0gKip3ZWIgc2NyYXBpbmcqKiAtLSBtaW5pbmcgZGF0YSBmcm9tIGEgd2Vic2l0ZQotIGBtb2RlbHJgIC0tICoqbW9kZWxsaW5nKiogLS0gc3VwcG9ydCBmb3IgbW9kZWxsaW5nIGRhdGEgaW4gdGhlIHRpZHl2ZXJzZQoKIyMgV2hhdCBpcyB0aGUgYHRpZHl2ZXJzZWA/CgpBIHN1aXRlIG9mIGludGVyLXJlbGF0ZWQgcGFja2FnZXMgd2l0aCBhIGNvbW1vbiBncmFtbWFyIGFuZCBzeW50YXguIAoKYGBge3IsIGV2YWwgPSBGQUxTRX0KIyBJbnN0YWxsIHRoZSB0aWR5dmVyc2UKaW5zdGFsbC5wYWNrYWdlcygidGlkeXZlcnNlIikKCiMgSW5zdGFsbCAKaW5zdGFsbC5wYWNrYWdlcygiYnJvb20iKQpgYGAKCmBgYHtyfQojIExvYWQgbGlicmFyeQpyZXF1aXJlKHRpZHl2ZXJzZSkKcmVxdWlyZShicm9vbSkKYGBgCgojICoqVGlkeSBkYXRhKioKClRoZSB0aWR5dmVyc2UgZ2V0cyBpdHMgbmFtZSBmcm9tIHRoZSB0eXBlIG9mIGRhdGEgdGhhdCBpdCBpcyBkZXNpZ25lZCB0byBpbnRlcmFjdCB3aXRoIC0tICoqdGlkeSBkYXRhKiouIFNvIGxldCdzIHF1aWNrbHkgZGVmaW5lIFt0aWR5IGRhdGFdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy90aWR5ci92aWduZXR0ZXMvdGlkeS1kYXRhLmh0bWwpLgoKMS4gRXZlcnkgY29sdW1uIGlzIGEgdmFyaWFibGUuCjIuIEV2ZXJ5IHJvdyBpcyBhbiBvYnNlcnZhdGlvbi4KMy4gRXZlcnkgY2VsbCBpcyBhIHNpbmdsZSB2YWx1ZS4KCllpa2VzLiBUaGF0J3MgYWJzdHJhY3QuLi4KCiMjIE1lc3N5IGRhdGEKCkFuIGV4YW1wbGUgb2Ygc29tZSBtZXNzeSBkYXRhLgoKYGBge3J9CiMgTWFrZSB1cCBzb21lIHJhbmRvbSBkYXRhCm1kaF9kZiA8LSB0aWJibGUoZ2lsbCA9IHJub3JtKDE1LCBtZWFuID0gMTIsIHNkID0gMiksCiAgICAgICAgICAgICAgICAgYWRkdWN0b3IgPSBybm9ybSgxNSwgbWVhbiA9IDE4LCBzZCA9IDIuNSksCiAgICAgICAgICAgICAgICAgbWFudGxlID0gcm5vcm0oMTUsIG1lYW4gPSA2LCBzZCA9IDMpKQoKIyBQcmludCB0aGUgZGF0YQptZGhfZGYKYGBgCgojIyBMZXQncyB0aWR5IGl0CmBgYHtyfQptZGhfZGYgPC0gbWRoX2RmICU+JQogIHBpdm90X2xvbmdlcihldmVyeXRoaW5nKCksCiAgICAgICAgICAgICAgIG5hbWVzX3RvID0gInRpc3N1ZSIsCiAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJpdV9nZnciKQoKbWRoX2RmCmBgYAoKKipCQU0qKiEgVGhhdCBpcyB0aWR5IGRhdGEuIDEuIEV2ZXJ5IGNvbHVtbiBpcyBhIHZhcmlhYmxlIC0tIHRpc3N1ZSBhbmQgZW56eW1lIGFjdGl2aXR5LiAyLiBFdmVyeSByb3cgaXMgYW4gb2JzZXJ2YXRpb24gLS0gZW56eW1lIGFjdGl2aXR5IGluIEkuVS4vZyBmLncuLiAzLiBFdmVyeSBjZWxsIGlzIGEgc2luZ2xlIHZhbHVlLgoKIyAqKkltcG9ydGluZyBkYXRhKioKCiMjIFdoeSB1c2UgYHJlYWRyYD8KCkkgZmluZCBpdCBhIGxvdCBlYXNpZXIgdG8gZGVmaW5lIHRoZSBkYXRhIHN0cnVjdHVyZSBhcyB5b3UgaW1wb3J0IGRhdGEuIEFuZCBpdCBjcmVhdGVzIGEgYHRpYmJsZWAgaW5zdGVhZCBvZiBgZGF0YS5mcmFtZWAuIAoKYGBge3J9CiMgQ3JlYXRlIGEgLmNzdiBmaWxlIHRvIGltcG9ydAp3cml0ZV9jc3YoaXJpcywgImlyaXMuY3N2IikKYGBgCgpgYGB7cn0KIyBUcnkgcmVhZC5jc3YKZC5mIDwtIHJlYWQuY3N2KCJpcmlzLmNzdiIpCgpzdHIoZC5mKQpgYGAKCgpgYGB7cn0KIyBUcnkgcmVhZF9jc3YKcmVhZF9jc3YoImlyaXMuY3N2IikKCiMgU2V0IGNvbF90eXBlIGFzIHlvdSBpbXBvcnQgZGF0YSAtLSBhbGxvd3MgeW91IHRvIGRlZmluZSBsZXZlbCBvcmRlciB0b28KZF9mIDwtIHJlYWRfY3N2KCJpcmlzLmNzdiIsCiAgICAgICAgICAgICAgICBjb2xfdHlwZXMgPSBsaXN0KFNwZWNpZXMgPSBjb2xfZmFjdG9yKGMoInZlcnNpY29sb3IiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJzZXRvc2EiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAidmlyZ2luaWNhIikpKSkKZF9mCnN0cihkX2YpCmdsaW1wc2UoZF9mKQpgYGAKCgojICoqV3JhbmdsaW5nIGRhdGEqKgoKIyMgSW50cm8gdG8gYGRwbHlyYAoKYGRwbHlyYCBoYXMgYSBmZXcgY29yZSBmdW5jdGlvbnMgdGhhdCBhcmUgYnVpbHQgdG8gd29yayBvbiAKClRha2VuIGZyb20gdGhlIFtgZHBseXJgIHZpZ25ldHRlXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvZHBseXIvdmlnbmV0dGVzL2RwbHlyLmh0bWwpLgoKCiMjIyBSb3dzCgotIGBmaWx0ZXIoKWAgY2hvb3NlcyByb3dzIGJhc2VkIG9uIGNvbHVtbiB2YWx1ZXMuCi0gYHNsaWNlKClgIGNob29zZXMgcm93cyBiYXNlZCBvbiBsb2NhdGlvbi4KLSBgYXJyYW5nZSgpYCBjaGFuZ2VzIHRoZSBvcmRlciBvZiB0aGUgcm93cy4KCiMjIyBDb2x1bW5zCgotIGBzZWxlY3QoKWAgY2hhbmdlcyB3aGV0aGVyIG9yIG5vdCBhIGNvbHVtbiBpcyBpbmNsdWRlZC4KLSBgcmVuYW1lKClgIGNoYW5nZXMgdGhlIG5hbWUgb2YgY29sdW1ucy4KLSBgbXV0YXRlKClgIGNoYW5nZXMgdGhlIHZhbHVlcyBvZiBjb2x1bW5zIGFuZCBjcmVhdGVzIG5ldyBjb2x1bW5zLgotIGByZWxvY2F0ZSgpYCBjaGFuZ2VzIHRoZSBvcmRlciBvZiB0aGUgY29sdW1ucy4KCiMjIyBHcm91cHMgb2Ygcm93cwoKLSBgc3VtbWFyaXNlKClgIGNvbGxhcHNlcyBhIGdyb3VwIGludG8gYSBzaW5nbGUgcm93LgoKIyMgUGlwZSBgJT4lYAoKRnJvbSBbYG1hZ3JpdHRyYF0oaHR0cHM6Ly9tYWdyaXR0ci50aWR5dmVyc2Uub3JnL2luZGV4Lmh0bWwpLgoKLSBgeCAlPiUgZmAgaXMgZXF1aXZhbGVudCB0byBgZih4KWAKLSBgeCAlPiUgZih5KWAgaXMgZXF1aXZhbGVudCB0byBgZih4LCB5KWAKLSBgeCAlPiUgZiAlPiUgZyAlPiUgaGAgaXMgZXF1aXZhbGVudCB0byBgaChnKGYoeCkpKWAKClRoZSBhcmd1bWVudCBwbGFjZWhvbGRlcgoKLSBgeCAlPiUgZih5LCAuKWAgaXMgZXF1aXZhbGVudCB0byBgZih5LCB4KWAKLSBgeCAlPiUgZih5LCB6ID0gLilgIGlzIGVxdWl2YWxlbnQgdG8gYGYoeSwgeiA9IHgpYAoKIyMjIEEgZnVuIGV4YW1wbGUgZnJvbSBbUiBmb3IgZGF0YSBzY2llbmNlXShodHRwczovL3I0ZHMuaGFkLmNvLm56L3BpcGVzLmh0bWwpCgpgYGB7ciwgZXZhbCA9IEZBTFNFfQpmb29fZm9vIDwtIGxpdHRsZV9idW5ueSgpCmZvb19mb29fMSA8LSBob3AoZm9vX2ZvbywgdGhyb3VnaCA9IGZvcmVzdCkKZm9vX2Zvb18yIDwtIHNjb29wKGZvb19mb29fMSwgdXAgPSBmaWVsZF9taWNlKQpmb29fZm9vXzMgPC0gYm9wKGZvb19mb29fMiwgb24gPSBoZWFkKQpgYGAKCmBgYHtyLCBldmFsID0gRkFMU0V9CmZvb19mb28gJT4lCiAgaG9wKHRocm91Z2ggPSBmb3Jlc3QpICU+JQogIHNjb29wKHVwID0gZmllbGRfbWljZSkgJT4lCiAgYm9wKG9uID0gaGVhZCkKYGBgCgoKIyMgYGFycmFuZ2VgCgpgYGB7cn0KbWRoX2RmICU+JQogIGFycmFuZ2UodGlzc3VlLCBpdV9nZncpCmBgYAoKYGBge3J9Cm1kaF9kZiAlPiUKICBhcnJhbmdlKGRlc2ModGlzc3VlKSwgZGVzYyhpdV9nZncpKQpgYGAKCiMjIGBzdW1tYXJpc2VgCgpgYGB7cn0KbWRoX2RmICU+JQogIHN1bW1hcmlzZShpdV9nZndfYXZnID0gbWVhbihpdV9nZncpKQpgYGAKCiMjIGBncm91cF9ieWAKCmBgYHtyfQptZGhfZGYgJT4lCiAgZ3JvdXBfYnkodGlzc3VlKSAlPiUKICBzdW1tYXJpc2UoaXVfZ2Z3X2F2ZyA9IG1lYW4oaXVfZ2Z3KSwKICAgICAgICAgICAgaXVfZ2Z3X3NkID0gc2QoaXVfZ2Z3KSkKYGBgCgpXYXJuaW5nIHRoYXQgeW91IG11c3QgYmUgY2FyZWZ1bCBhYm91dCB0aGUgb3JkZXIgd2hlbiByZXVzaW5nIHZhcmlhYmxlIG5hbWVzLgoKYGBge3J9CiMgQmFkIG9yZGVyCm1kaF9kZiAlPiUKICBncm91cF9ieSh0aXNzdWUpICU+JQogIHN1bW1hcmlzZShpdV9nZncgPSBtZWFuKGl1X2dmdyksCiAgICAgICAgICAgIHNkID0gc2QoaXVfZ2Z3KSkKYGBgCgoKYGBge3J9CiMgVGhpcyBvcmRlciB3b3JrcyBiZWNhdXNlIGl0IGNvbGxhcHNlcyB0aGUgZGF0YSBpbnRvIGEgbWVhbiBsYXN0Cm1kaF9kZiAlPiUKICBncm91cF9ieSh0aXNzdWUpICU+JQogIHN1bW1hcmlzZShzZCA9IHNkKGl1X2dmdyksCiAgICAgICAgICAgIGl1X2dmdyA9IG1lYW4oaXVfZ2Z3KSkKYGBgCgpQcmludCBvdXQgYSBwcmV0dHkgdGFibGUgdXNpbmcgYGthYmxlRXh0cmFgLgoKYGBge3J9Cm1kaF9kZiAlPiUKICBncm91cF9ieSh0aXNzdWUpICU+JQogIHN1bW1hcmlzZShpdV9nZndfYXZnID0gbWVhbihpdV9nZncpLAogICAgICAgICAgICBpdV9nZndfc2QgPSBzZChpdV9nZncpKSAlPiUKICBtdXRhdGUoYEkuVS4gLyBnIGYudy5gID0gcGFzdGUocm91bmQoaXVfZ2Z3X2F2ZywgMiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiwrEiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcm91bmQoaXVfZ2Z3X3NkLCAyKSkpICU+JQogIHNlbGVjdCh0aXNzdWUsIGBJLlUuIC8gZyBmLncuYCkgJT4lCiAgcmVuYW1lKCJUaXNzdWUiID0gInRpc3N1ZSIsCiAgICAgICAgIGBNREggQWN0aXZpdHkgKEkuVS4gLyBnIGYudy4pYCA9IGBJLlUuIC8gZyBmLncuYCkgJT4lCiAgbXV0YXRlKFRpc3N1ZSA9IHN0cl90b19zZW50ZW5jZShUaXNzdWUpKSAlPiUKICBrYWJsZUV4dHJhOjprYWJsZSgpCmBgYAoKIyMgYG5lc3RgCgpgYGB7cn0KQ2hpY2tXZWlnaHQgJT4lCiAgZ2xpbXBzZSgpCmBgYAoKYGBge3J9CkNoaWNrV2VpZ2h0ICU+JQogIGdyb3VwX2J5KENoaWNrLCBEaWV0KSAlPiUKICBuZXN0KCkgCmBgYAoKYGBge3J9CkNoaWNrV2VpZ2h0X25lc3QgPC0gQ2hpY2tXZWlnaHQgJT4lCiAgZ3JvdXBfYnkoQ2hpY2ssIERpZXQpICU+JQogIG5lc3QoKSAKCkNoaWNrV2VpZ2h0X25lc3QkZGF0YVsxOjJdCmBgYAoKIyMgYGJyb29tYAoKQ0hlY2sgb3V0IHRoZSBbYGJyb29tYCB2aWduZXR0ZV0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL2Jyb29tL3ZpZ25ldHRlcy9icm9vbS5odG1sKS4KCltBbmQgdGhlIGBicm9vbWAgYW5kIGBkcGx5cmAgdmlnbmV0dGVdIChodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvYnJvb20vdmlnbmV0dGVzL2Jyb29tX2FuZF9kcGx5ci5odG1sKS4KCmB0aWR5YDogY29uc3RydWN0cyBhIHRpYmJsZSB0aGF0IHN1bW1hcml6ZXMgdGhlIG1vZGVs4oCZcyBzdGF0aXN0aWNhbCBmaW5kaW5ncy4gVGhpcyBpbmNsdWRlcyBjb2VmZmljaWVudHMgYW5kIHAtdmFsdWVzIGZvciBlYWNoIHRlcm0gaW4gYSByZWdyZXNzaW9uLCBwZXItY2x1c3RlciBpbmZvcm1hdGlvbiBpbiBjbHVzdGVyaW5nIGFwcGxpY2F0aW9ucywgb3IgcGVyLXRlc3QgaW5mb3JtYXRpb24gZm9yIG11bHR0ZXN0IGZ1bmN0aW9ucy4KCmBnbGFuY2VgOiBjb25zdHJ1Y3QgYSBjb25jaXNlIG9uZS1yb3cgc3VtbWFyeSBvZiB0aGUgbW9kZWwuIFRoaXMgdHlwaWNhbGx5IGNvbnRhaW5zIHZhbHVlcyBzdWNoIGFzIFJeMiwgYWRqdXN0ZWQgUl4yLCBhbmQgcmVzaWR1YWwgc3RhbmRhcmQgZXJyb3IgdGhhdCBhcmUgY29tcHV0ZWQgb25jZSBmb3IgdGhlIGVudGlyZSBtb2RlbC4KCgpgYGB7cn0KQ2hpY2tXZWlnaHQgJT4lCiAgZ3JvdXBfYnkoQ2hpY2ssIERpZXQpICU+JQogIG5lc3QoKSAlPiUKICBtdXRhdGUoCiAgICBmaXQgPSBtYXAoZGF0YSwgfiBsbSh3ZWlnaHQgfiBUaW1lLCBkYXRhID0gLngpKSwKICAgIHRpZGllZCA9IG1hcChmaXQsIHRpZHkpLAogICAgZ2xhbmNlZCA9IG1hcChmaXQsIGdsYW5jZSkKICApICU+JSAKICB1bm5lc3QodGlkaWVkKSAKYGBgCgoKYGBge3J9CkNoaWNrV2VpZ2h0ICU+JQogIGdncGxvdCgpICsKICBnZW9tX2xpbmUoYWVzKHggPSBUaW1lLCAKICAgICAgICAgICAgICAgICB5ID0gd2VpZ2h0LCAKICAgICAgICAgICAgICAgICBjb2xvciA9IENoaWNrKSkgKwogIGZhY2V0X3dyYXAofiBEaWV0KQpgYGAKCgoKIyAqKkludGVncmF0aXZlIGV4YW1wbGUqKgoKYGBge3J9CiMgSW1wb3J0IGRhdGEgLS0tLS0KCiMgQURIIGFjdGl2aXR5CmFkaF9kZiA8LSByZWFkX2NzdigiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3Rzb2xlYXJ5L3Byb2plY3RzL21hc3Rlci9BREgvZGF0YS9iaW92aXNpb24vYWRoX2Fic18xMTAyMjAyMC5jc3YiKQoKIyBOQURIIHN0ZCBjdXJ2ZQpuYWRoX2RmIDwtIHJlYWRfY3N2KCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vdHNvbGVhcnkvcHJvamVjdHMvbWFzdGVyL0FESC9kYXRhL2Jpb3Zpc2lvbi9OQURIX3N0ZF8xMTAyMjAyMC5jc3YiKQpgYGAKCiMjIyBOQURIIFN0ZCBDdXJ2ZQoKYGBge3J9Cm5hZGhfZGYKCm5hZGhfZGYgPC0gbmFkaF9kZiAlPiUKICBwaXZvdF9sb25nZXIoY29scyA9IGNvbnRhaW5zKCJtaW4iKSwgCiAgICAgICAgICAgICAgIG5hbWVzX3RvID0gIm1pbnMiLCAKICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gImFicyIpICU+JQogIG11dGF0ZShtaW5zID0gYXMubnVtZXJpYyhzdHJfcmVtb3ZlX2FsbChtaW5zLCAiX21pbiIpKSkgCgpuYWRoX2RmCmBgYAoKIyMgU3RhbmRhcmQgY3VydmUgbGluZWFyIHJlZ3Jlc3Npb24KCmBgYHtyfQojIFJ1biBsaW5lYXIgbW9kZWwKc3RkX2xtIDwtIGxtKGFicyB+IE5BREgsIG5hZGhfZGYpCgpzdW1tYXJ5KHN0ZF9sbSkKYGBgCgpgYGB7cn0KIyBTYXZlIGludGVyY2VwdCAmIHNsb3BlIHZhbHVlcwppbnQgPC0gYXMubnVtZXJpYyhzdGRfbG0kY29lZmZpY2llbnRzWzFdKQpzbG9wZSA8LSBhcy5udW1lcmljKHN0ZF9sbSRjb2VmZmljaWVudHNbMl0pCgojIFNhdmUgdmFsdWVzIGluIGEgbmFtZWQgdmVjdG9yCnN0ZF9jdXJ2ZV9sbSA8LSBjKGludCA9IGludCwgCiAgICAgICAgICAgICAgICAgIHNsb3BlID0gc2xvcGUpCgpzdGRfY3VydmVfbG0gCmBgYAoKCiMjIEFic29yYmFuY2UgZGF0YQpgYGB7cn0KIyBBREggYWN0aXZpdHkgYWJzb3JiYW5jZSBkYXRhCmFkaF9kZgpgYGAKCiMjIyBUaWR5IHRoZSBkYXRhCgpgYGB7cn0KYWRoX2RmIDwtIGFkaF9kZiAlPiUKICBwaXZvdF9sb25nZXIoY29scyA9IGNvbnRhaW5zKCJtaW4iKSwgCiAgICAgICAgICAgICAgIG5hbWVzX3RvID0gIm1pbnMiLCAKICAgICAgICAgICAgICAgdmFsdWVzX3RvID0gImFicyIpICU+JQogIG11dGF0ZShtaW5zID0gYXMubnVtZXJpYyhzdHJfcmVtb3ZlX2FsbChtaW5zLCAiX21pbiIpKSkKCmFkaF9kZgoKIyBBZGQgdGVtcCBkYXRhIHdpdGggc29tZSBub2lzZQp4IDwtIGFkaF9kZiAlPiUKICBtdXRhdGUodGVtcCA9IDIzLAogICAgICAgICBhYnMgPSBhYnMqcm5vcm0oMTYwLCBtZWFuID0gMC45LCBzZCA9IDAuMDIpKQoKYWRoX2RmIDwtIGJpbmRfcm93cyhhZGhfZGYsIHgpCmBgYAoKIyMgUGxvdCB0aGUgc3RhbmRhcmQgY3VydmUKCmBgYHtyfQpnZ3Bsb3QoZGF0YSA9IG5hZGhfZGYsIAogICAgICAgYWVzKHggPSBOQURILCAKICAgICAgICAgICB5ID0gYWJzKSkgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICdsbScsIAogICAgICAgICAgICAgIGZvcm11bGEgPSB5IH4geCwgCiAgICAgICAgICAgICAgc2UgPSBGQUxTRSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2dwbWlzYzo6c3RhdF9wb2x5X2VxKGZvcm11bGEgPSB5IH4geCwgCiAgICAgICAgICAgICAgICAgICAgICAgIGFlcyhsYWJlbCA9IHBhc3RlKC4uZXEubGFiZWwuLiwgLi5yci5sYWJlbC4uLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc2VwID0gIipcIjsgXCIqIikpLAogICAgICAgICAgICAgICAgICAgICAgICBwYXJzZSA9IFRSVUUsIAogICAgICAgICAgICAgICAgICAgICAgICByci5kaWdpdHMgPSA1LAogICAgICAgICAgICAgICAgICAgICAgICBsYWJlbC54ID0gMC44NSwgCiAgICAgICAgICAgICAgICAgICAgICAgIGxhYmVsLnkgPSAidG9wIikgKwogIHRoZW1lX2NsYXNzaWMoKSArCiAgbGFicyh5ID0gIkFic29yYmFuY2UgQCA0NTAgbm0iLCAKICAgICAgIHggPSAiTkFESCAobm1vbCkiKQpgYGAKCiMjIENhbGN1bGF0ZSBlbnp5bWUgYWN0aXZpdHkKCmBgYHtyfQphZGhfZGYgPC0gYWRoX2RmICU+JQogIGdyb3VwX2J5KHRlbXAsIEV0T0gpICU+JQogIG11dGF0ZShubW9sX05BREggPSAoYWJzIC0gc3RkX2N1cnZlX2xtWyJpbnQiXSkgLyBzdGRfY3VydmVfbG1bInNsb3BlIl0sCiAgICAgICAgIGRlbHRhQWJzID0gYWJzIC0gYWJzW21pbnMgPT0gMF0sCiAgICAgICAgIGRlbHRhTkFESCA9IG5tb2xfTkFESCAtIG5tb2xfTkFESFttaW5zID09IDBdLAogICAgICAgICBkZWx0YVRpbWUgPSBtaW5zIC0gMCkgJT4lCiAgZmlsdGVyKGRlbHRhVGltZSAhPSAwKSAlPiUKICBtdXRhdGUoZGlsdXRpb25fZmFjdG9yID0gMTUwL3NhbXBsZV92b2wsCiAgICAgICAgIG1VX21MID0gKGRlbHRhTkFESC8oZGVsdGFUaW1lKihzYW1wbGVfdm9sLzEwMDApKSkqZGlsdXRpb25fZmFjdG9yKSAlPiUKICB1bmdyb3VwKEV0T0gpICU+JQogIG11dGF0ZShtVV9tTCA9IG1VX21MIC0gbVVfbUxbRXRPSCA9PSAwXSkKYGBgCgojIyBDYWxjdWxhdGluZyBLPHN1Yj5tPC9zdWI+CgpgYGB7cn0KYWRoX2RmICU+JQogIGZpbHRlcihtaW5zIDw9IDYwKSAlPiUKICBncm91cF9ieSh0ZW1wLCBFdE9IKSAlPiUKICBuZXN0KCkgJT4lCiAgbXV0YXRlKAogICAgZml0ID0gbWFwKGRhdGEsIH4gbG0oYWJzIH4gbWlucywgZGF0YSA9IC54KSksCiAgICB0aWRpZWQgPSBtYXAoZml0LCB0aWR5KSwKICAgIGdsYW5jZWQgPSBtYXAoZml0LCBnbGFuY2UpKSAKYGBgCgpgYGB7cn0KYWRoX3ZlbG8gPC0gYWRoX2RmICU+JQogIGZpbHRlcihtaW5zIDw9IDYwKSAlPiUKICBncm91cF9ieSh0ZW1wLCBFdE9IKSAlPiUKICBuZXN0KCkgJT4lCiAgbXV0YXRlKAogICAgZml0ID0gbWFwKGRhdGEsIH4gbG0oYWJzIH4gbWlucywgZGF0YSA9IC54KSksCiAgICB0aWRpZWQgPSBtYXAoZml0LCB0aWR5KSwKICAgIGdsYW5jZWQgPSBtYXAoZml0LCBnbGFuY2UpCiAgKSAlPiUgCiAgdW5uZXN0KHRpZGllZCkgJT4lCiAgZmlsdGVyKHRlcm0gPT0gIm1pbnMiKSAlPiUKICBzZWxlY3QodGVtcCwgRXRPSCwgZXN0aW1hdGUpICU+JQogIHJlbmFtZSh2ZWxvY2l0eSA9IGVzdGltYXRlKSAlPiUKICBmaWx0ZXIoRXRPSCAhPSAwKSAlPiUKICBhcnJhbmdlKHRlbXAsIEV0T0gpCmBgYAoKYGBge3J9CmFkaF9tbSA8LSBhZGhfdmVsbyAlPiUKICB1bmdyb3VwKEV0T0gpICU+JQogIGZpbHRlcihFdE9IICE9IDApICU+JQogIGdyb3VwX2J5KHRlbXApICU+JQogIG5lc3QoKSAlPiUKICBtdXRhdGUoCiAgICBmaXQgPSBtYXAoZGF0YSwgfiBubHMoZm9ybXVsYSA9IAogICAgICAgICAgICAgICAgICAgICAgICAgICAgdmVsb2NpdHkgfiBTU21pY21lbihFdE9ILCBWbWF4LCBLbSksIGRhdGEgPSAuKSwgCiAgICAgICAgICAgICAgZGF0YSA9IC54KSwKICAgIHRpZGllZCA9IG1hcChmaXQsIHRpZHkpCiAgKSAlPiUKICB1bm5lc3QodGlkaWVkKQoKYWRoX21tCmBgYAoKIyMgUGxvdAoKYGBge3J9CmFkaF92ZWxvICU+JQogIG11dGF0ZSh0ZW1wID0gYXMuZmFjdG9yKHRlbXApKSAlPiUKICBncm91cF9ieSh0ZW1wKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBFdE9ILAogICAgICAgICAgICAgeSA9IHZlbG9jaXR5KSkgKwogIGdlb21fc21vb3RoKG1ldGhvZCA9ICJubHMiLCAKICAgICAgICAgICAgICBhZXMoZ3JvdXAgPSB0ZW1wKSwKICAgICAgICAgICAgICBmb3JtdWxhID0geSB+IFNTbWljbWVuKHgsIFZtYXgsIEttKSwKICAgICAgICAgICAgICBjb2xvciA9ICJncmV5NTAiLAogICAgICAgICAgICAgIGRhdGEgPSBhZGhfdmVsbywKICAgICAgICAgICAgICBzZSA9IEZBTFNFLAogICAgICAgICAgICAgIHNob3cubGVnZW5kID0gRkFMU0UpICsKICBnZW9tX3BvaW50KGFlcyhjb2xvciA9IHRlbXApKSArCiAgdGhlbWVfY2xhc3NpYygpCmBgYAoKIyAqKlBsb3R0aW5nIGRhdGEqKgoKIyMgYGdncGxvdDJgCgpDaGVjayBvdXQgdGhlIFtgZ2dwbG90MmAgYm9va10oaHR0cHM6Ly9nZ3Bsb3QyLWJvb2sub3JnL2luZGV4Lmh0bWwpLiAKCmBnZ3Bsb3QyYCBjcmVhdGVzIHRoaW5ncyBpbiBhIHNwZWNpZmljIG9yZGVyIG9mIGxheWVycy4KCgpgYGB7cn0KZ2xpbXBzZShzdGFyd2FycykKYGBgCgpgYGB7cn0Kc3RhcndhcnMgJT4lCiAgZ2dwbG90KGFlcyh4ID0gc2V4LCB5ID0gaGVpZ2h0KSkgKwogIGdlb21fcG9pbnQoKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIHRoZW1lX2NsYXNzaWMoKQpgYGAKCmBgYHtyfQpzdGFyd2FycyAlPiUKICBnZ3Bsb3QoYWVzKHggPSBzZXgsIHkgPSBoZWlnaHQpKSArCiAgZ2VvbV9ib3hwbG90KCkgKwogIGdlb21fcG9pbnQoKSArCiAgdGhlbWVfY2xhc3NpYygpCmBgYAoKYGBge3J9CnN0YXJ3YXJzICU+JQogIGdncGxvdChhZXMoeCA9IHNleCwgeSA9IGhlaWdodCkpICsKICBnZW9tX2JveHBsb3QoKSArCiAgZ2VvbV9qaXR0ZXIod2lkdGggPSAwLjIpICsKICB0aGVtZV9jbGFzc2ljKCkKYGBgCgpgYGB7cn0Kc3RhcndhcnMgJT4lCiAgZ2dwbG90KGFlcyh4ID0gc2V4LCB5ID0gaGVpZ2h0KSkgKwogIGdlb21faGxpbmUoYWVzKHlpbnRlcmNlcHQgPSAxODgpLCAKICAgICAgICAgICAgIGxpbmV0eXBlID0gMiwgCiAgICAgICAgICAgICBjb2xvciA9ICJncmV5MjAiKSArCiAgZ2VvbV9ib3hwbG90KG91dGxpZXIuc2hhcGUgPSBOQSkgKwogIGdlb21faml0dGVyKHdpZHRoID0gMC4xLCAKICAgICAgICAgICAgICBzaXplID0gMiwKICAgICAgICAgICAgICBhbHBoYSA9IDAuOCwKICAgICAgICAgICAgICBzaGFwZSA9IDIxLCAKICAgICAgICAgICAgICBjb2xvciA9ICJncmV5MjAiLCAKICAgICAgICAgICAgICBmaWxsID0gImdyZXk1MCIpICsKICBzY2FsZV95X2NvbnRpbnVvdXMoYnJlYWtzID0gYygxMDAsIDE1MCwgMTg4LCAyMDAsIDI1MCksCiAgICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIjEwMCIsICIxNTAiLCAiVGhvbWFzIiwgIjIwMCIsICIyNTAiKSwKICAgICAgICAgICAgICAgICAgICAgbmFtZSA9ICJIZWlnaHQiKSArCiAgdGhlbWVfY2xhc3NpYygpCmBgYAoKYGBge3J9CnN0YXJ3YXJzICU+JQogIGZpbHRlcihzZXggJWluJSBjKCJtYWxlIiwgImZlbWFsZSIpKSAlPiUKICBnZ3Bsb3QoYWVzKHggPSBzZXgsIHkgPSBoZWlnaHQpKSArCiAgZ2VvbV92aW9saW4oZmlsbCA9ICJhenVyZTIiLAogICAgICAgICAgICAgIGNvbG9yID0gImdyZXkyMCIpICsKICBnZW9tX2JveHBsb3QoZmlsbCA9ICJhenVyZTIiLAogICAgICAgICAgICAgICB3aWR0aCA9IDAuMiwKICAgICAgICAgICAgICAgY29sb3IgPSAiZ3JleTIwIikgKwogIHRoZW1lX2NsYXNzaWMoKQpgYGAKCmBgYHtyfQpzdGFyd2FycyAlPiUKICBmaWx0ZXIoYmlydGhfeWVhciA8IDIwMCkgJT4lCiAgZ2dwbG90KCkgKwogIGdlb21faGlzdG9ncmFtKGFlcyh4ID0gYmlydGhfeWVhciksIAogICAgICAgICAgICAgICAgIGJpbndpZHRoID0gMTApCmBgYAoKYGBge3J9CnN0YXJ3YXJzICU+JQogIGZpbHRlcihiaXJ0aF95ZWFyIDwgMjAwKSAlPiUKICBnZ3Bsb3QoKSArCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHggPSBiaXJ0aF95ZWFyKSwKICAgICAgICAgICAgICAgICBjb2xvciA9ICJncmV5MjAiLAogICAgICAgICAgICAgICAgIGZpbGwgPSAiZ3JleTUwIiwKICAgICAgICAgICAgICAgICBiaW53aWR0aCA9IDEwKSArCiAgdGhlbWVfY2xhc3NpYygpCmBgYAoKYGBge3J9CnN0YXJ3YXJzICU+JQogIGZpbHRlcihiaXJ0aF95ZWFyIDwgMjAwKSAlPiUKICBnZ3Bsb3QoKSArCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKHggPSBiaXJ0aF95ZWFyKSwKICAgICAgICAgICAgICAgICBjb2xvciA9ICJncmV5MjAiLAogICAgICAgICAgICAgICAgIGZpbGwgPSAiZ3JleTUwIiwKICAgICAgICAgICAgICAgICBiaW53aWR0aCA9IDEwKSArCiAgZ2VvbV9kZW5zaXR5KGFlcyh4ID0gYmlydGhfeWVhciwgCiAgICAgICAgICAgICAgICAgICB5ID0gIDEwICogLi5jb3VudC4uKSwKICAgICAgICAgICAgICAgY29sb3IgPSAiZ3JleTIwIikgKwogIHlsYWIoImNvdW50IikgKwogIHRoZW1lX2NsYXNzaWMoKQpgYGAKCmBgYHtyfQpzdGFyd2FycyAlPiUKICBmaWx0ZXIobWFzcyA8IDEwMDApICU+JQogIG11dGF0ZShzcGVjaWVzX2ggPSBpZmVsc2Uoc3BlY2llcyA9PSAiSHVtYW4iLCAgIkh1bWFuIiwgIk90aGVyIikpICU+JQogIGdncGxvdCgpICsKICBnZW9tX3BvaW50KGFlcyh4ID0gaGVpZ2h0LCAKICAgICAgICAgICAgICAgICB5ID0gbWFzcywgCiAgICAgICAgICAgICAgICAgZmlsbCA9IHNwZWNpZXNfaCksCiAgICAgICAgICAgICBzaXplID0gNSwKICAgICAgICAgICAgIGFscGhhID0gMC44LAogICAgICAgICAgICAgY29sb3IgPSAiZ3JleTUwIiwKICAgICAgICAgICAgIHNoYXBlID0gMjEpICsKICB0aGVtZV9jbGFzc2ljKCkKYGBgCgpgYGB7cn0Kc3RhcndhcnMgJT4lCiAgZmlsdGVyKG1hc3MgPCAxMDAwKSAlPiUKICBtdXRhdGUoc3BlY2llc19oID0gaWZlbHNlKHNwZWNpZXMgPT0gIkh1bWFuIiwgICJIdW1hbiIsICJPdGhlciIpKSAlPiUKICBhcnJhbmdlKGRlc2Moc3BlY2llc19oKSkgJT4lCiAgZ2dwbG90KCkgKwogIGdlb21fcG9pbnQoYWVzKHggPSBoZWlnaHQsIAogICAgICAgICAgICAgICAgIHkgPSBtYXNzLCAKICAgICAgICAgICAgICAgICBmaWxsID0gc3BlY2llc19oKSwKICAgICAgICAgICAgIHNpemUgPSA1LAogICAgICAgICAgICAgYWxwaGEgPSAwLjgsCiAgICAgICAgICAgICBjb2xvciA9ICJncmV5NTAiLAogICAgICAgICAgICAgc2hhcGUgPSAyMSkgKwogIHRoZW1lX2NsYXNzaWMoKQpgYGAKCgpgYGB7cn0Kc3RhcndhcnNfaCA8LSBzdGFyd2FycyAlPiUKICBmaWx0ZXIobWFzcyA8IDEwMDApICU+JQogIG11dGF0ZShzcGVjaWVzX2ggPSBpZmVsc2Uoc3BlY2llcyA9PSAiSHVtYW4iLCAgIkh1bWFuIiwgIk90aGVyIikpICU+JQogIGFycmFuZ2UoZGVzYyhzcGVjaWVzX2gpKQoKCnAgPC0gZ2dwbG90KCkgKwogIGdlb21fcG9pbnQoZGF0YSA9IHN0YXJ3YXJzX2ggJT4lCiAgICAgICAgICAgICAgIGZpbHRlcihzcGVjaWVzX2ggIT0gIkh1bWFuIiksCiAgICAgICAgICAgICBhZXMoeCA9IGhlaWdodCwgCiAgICAgICAgICAgICAgICAgeSA9IG1hc3MsIAogICAgICAgICAgICAgICAgIGZpbGwgPSBzcGVjaWVzX2gpLAogICAgICAgICAgICAgc2l6ZSA9IDEsCiAgICAgICAgICAgICBhbHBoYSA9IDAuOCwKICAgICAgICAgICAgIGNvbG9yID0gImdyZXk1MCIsCiAgICAgICAgICAgICBzaGFwZSA9IDIxKSArCiAgICBnZW9tX3BvaW50KGRhdGEgPSBzdGFyd2Fyc19oICU+JQogICAgICAgICAgICAgICBmaWx0ZXIoc3BlY2llc19oID09ICJIdW1hbiIpLAogICAgICAgICAgICAgICBhZXMoeCA9IGhlaWdodCwgCiAgICAgICAgICAgICAgICAgeSA9IG1hc3MsIAogICAgICAgICAgICAgICAgIGZpbGwgPSBzcGVjaWVzX2gpLAogICAgICAgICAgICAgc2l6ZSA9IDMsCiAgICAgICAgICAgICBhbHBoYSA9IDAuOCwKICAgICAgICAgICAgIGNvbG9yID0gImdyZXk1MCIsCiAgICAgICAgICAgICBzaGFwZSA9IDIxKSArCiAgc2NhbGVfZmlsbF9tYW51YWwodmFsdWVzID0gYygiI2Q2NjY2NiIsICIjMjIyMjIyIikpICsKICB0aGVtZV9jbGFzc2ljKCkKCnAKYGBgCgpgYGB7cn0KcGxvdGx5OjpnZ3Bsb3RseShwLCB0ZXh0ID0gInRleHQiKQpgYGAKCmBgYHtyfQoKZnJpZW5kcyA8LSBjKCJMdWtlIFNreXdhbGtlciIsIAogICAgICAgICAgICAgIkxlaWEgT3JnYW5hIiwKICAgICAgICAgICAgICJIYW4gU29sbyIsCiAgICAgICAgICAgICAiQ2hld2JhY2NhIiwKICAgICAgICAgICAgICJDLTNQTyIsCiAgICAgICAgICAgICAiUjItRDIiLAogICAgICAgICAgICAgIk9iaS1XYW4gS2Vub2JpIikKCmdncGxvdCgpICsKICBnZW9tX3BvaW50KGRhdGEgPSBzdGFyd2Fyc19oLCAKICAgICAgICAgICAgIGFlcyh4ID0gaGVpZ2h0LCAKICAgICAgICAgICAgICAgICB5ID0gbWFzcywKICAgICAgICAgICAgICAgICBmaWxsID0gc3BlY2llc19oKSwKICAgICAgICAgICAgIHNpemUgPSA1LAogICAgICAgICAgICAgYWxwaGEgPSAwLjgsCiAgICAgICAgICAgICBjb2xvciA9ICJncmV5NTAiLAogICAgICAgICAgICAgc2hhcGUgPSAyMSkgKwogIGdncmVwZWw6Omdlb21fbGFiZWxfcmVwZWwoZGF0YSA9IHN0YXJ3YXJzX2ggJT4lCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGZpbHRlcihuYW1lICVpbiUgZnJpZW5kcyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBhZXMoeCA9IGhlaWdodCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgeSA9IG1hc3MsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWwgPSBuYW1lKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1pbi5zZWdtZW50Lmxlbmd0aCA9IDAuMDEpICsKICB0aGVtZV9jbGFzc2ljKCkKYGBgCgpUaGVzZSBhcmUgc29tZSBleGFtcGxlcyBvZiBwbG90cyB0aGF0IEkgaGF2ZSBtYWRlIGluIFIuCgpbQURIIGRhdGFdKGh0dHBzOi8vdHNvbGVhcnkuZ2l0aHViLmlvL3Byb2plY3RzL0FESC9zY3JpcHRzL2FkaF9kYXRhX3Rzby5odG1sKQoKW1dvcmxkIFJlY29yZCBQcm9ncmVzc2lvbl0oaHR0cHM6Ly90c29sZWFyeS5naXRodWIuaW8vdHJhY2svd29ybGRfcmVjb3JkX3Byb2dyZXNzaW9uL2F0aGxldGljc193b3JsZF9yZWNvcmRfcHJvZ3Jlc3Npb24uaHRtbCkKCiMgKipDcmVhdGluZyBmdW5jdGlvbnMqKgoKRnVuY3Rpb25zIGFyZSBleHRyZW1lbHkgdXNlZnVsIHdoZW4geW91IGFyZSBkb2luZyByZXBldGl2ZSB0YXNrcy4gQW5kIGdyZWF0aW5nCgpVc2luZyBbc25pcHBldHNdKGh0dHBzOi8vc3VwcG9ydC5yc3R1ZGlvLmNvbS9oYy9lbi11cy9hcnRpY2xlcy8yMDQ0NjM2NjgtQ29kZS1TbmlwcGV0cy1pbi10aGUtUlN0dWRpby1JREUpIGNhbiBoZWxwIAoKRm9yIGV4YW1wbGUgd2hlbiBJIHR5cGUgYG1mdW5fc25pcGAgYW5kIGhpdCBgdGFiYCwgdGhlIGZvbGxvd2luZyBjb2RlIHByaW50cyBvdXQgb24gbXkgc2NyaXB0LiAKCmBgYHtyfQojIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQojIEZ1bmN0aW9uOiBmdW5jdGlvbl9uYW1lCiMgRGVzY3JpcHRpb246IGRlc2NyaXB0aW9uCiMgSW5wdXRzOiBpbnB1dF9kZXNjcmlwdGlvbgojIE91dHB1dHM6IG91dHB1dF9kZXNjcmlwdGlvbgoKZnVuY3Rpb25fbmFtZSA8LSBmdW5jdGlvbihhcmdzKSB7CiAgIyBGdW5jdGlvbiBib2R5CiAgcHJpbnQoIkhlbGxvIHdvcmxkISIpCn0gCiMgRW5kIGZ1bmN0aW9uIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCmBgYAoKV2hpY2ggaXMgbWFkZSB3aXRoIHRoZSBmb2xsb3dpbmcgY29kZSBpbnNlcnRlZCBpbnRvIHRoZSBzbmlwcGV0cyBmaWxlLiBbVGhpcyB3ZWJwYWdlXShodHRwczovL3N1cHBvcnQucnN0dWRpby5jb20vaGMvZW4tdXMvYXJ0aWNsZXMvMjA0NDYzNjY4LUNvZGUtU25pcHBldHMtaW4tdGhlLVJTdHVkaW8tSURFKSB3YWxrcyB0aHJvdWdoIHRoZSBwcm9jZXNzIG9mIGFkZGluZyBzbmlwcGV0cy4KCmBgYApzbmlwcGV0IG1mdW5fc25pcAoJIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCSMgRnVuY3Rpb246ICR7MTpmdW5jdGlvbl9uYW1lfQoJIyBEZXNjcmlwdGlvbjogJHsyOmRlc2NyaXB0aW9ufQoJIyBJbnB1dHM6ICR7MzppbnB1dF9kZXNjcmlwdGlvbn0KCSMgT3V0cHV0czogJHs0Om91dHB1dF9kZXNjcmlwdGlvbn0KCQoJJHsxOmZ1bmN0aW9uX25hbWV9IDwtIGZ1bmN0aW9uKGFyZ3MpIHsKCQkjIEZ1bmN0aW9uIGJvZHkKCQlwcmludCgiSGVsbG8gd29ybGQhIikKCX0gCgkjIEVuZCBmdW5jdGlvbiAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpgYGAKCiMjIEV4YW1wbGVzIG9mIGZ1bmN0aW9ucwoKYGBge3J9CiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMgRnVuY3Rpb246IHJlYWRfdGlkeV9zcGVjX2NzdgojIERlc2NyaXB0aW9uOiB1c2VzIHJlYWRfY3N2IGFuZCB0aWR5cyB0aGUgZGF0YQojIElucHV0czogLmNzdiBmaWxlIAojIE91dHB1dHM6IGRhdGEuZnJhbWUKCnJlcXVpcmUodGlkeXZlcnNlKQoKcmVhZF90aWR5X3NwZWNfY3N2IDwtIGZ1bmN0aW9uKGZpbGUpIHsKICAKICByZWFkX2NzdihmaWxlKSAlPiUKICAgIHBpdm90X2xvbmdlcigtYygiY3ljbGUiLCAidGltZSIsICJ0ZW1wIiksIAogICAgICAgICAgICAgICAgIG5hbWVzX3RvID0gImNvbmNfd2VsbCIsIAogICAgICAgICAgICAgICAgIHZhbHVlc190byA9ICJhYnMiKSAlPiUKICAgIG11dGF0ZShjb25jID0gYXMubnVtZXJpYyhzdHJfcmVwbGFjZV9hbGwoY29uY193ZWxsLCAiXy4iLCAiIikpKSAlPiUKICAgIG11dGF0ZShmaWxlID0gc3RyX3JlbW92ZShmaWxlLCAiLmNzdiIpKSAlPiUKICAgIHNlcGFyYXRlKGZpbGUsIGludG8gPSBjKCJlbnp5bWUiLCAidGVtcF9ncm91cCIsICJkYXRlIiksIHNlcCA9ICJfIikKICAKfSAKIyBFbmQgZnVuY3Rpb24gLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KYGBgCgpgYGB7cn0KIyAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KIyBGdW5jdGlvbjogY2FsY19LbV9WbQojIERlc2NyaXB0aW9uOiBDYWxjdWxhdGUgVm1heCBhbmQgS20gZm9yIE1pY2hhZWxpc+KAk01lbnRlbiBjdXJ2ZQojIElucHV0czogdGliYmxlIHdpdGggdmVsb2NpdHkgZXN0aW1hdGVzIGZvciBlYWNoIGNvbmMgYW5kIHRlbXBfZ3JvdXAKIyBPdXRwdXRzOiB0aWJibGUgd2l0aCBlc3RpbWF0ZXMgZm9yIFZtYXggYW5kIEttCgpyZXF1aXJlKHRpZHl2ZXJzZSkKcmVxdWlyZShicm9vbSkKCmNhbGNfS21fVm0gPC0gZnVuY3Rpb24oZGF0YSkgewogIGRhdGEgJT4lCiAgICBmaWx0ZXIoY29uYyAhPSAwICYgdmVsb2NpdHkgPiAwKSAlPiUKICAgIGdyb3VwX2J5KGVuenltZSwgdGVtcF9ncm91cCkgJT4lCiAgICBuZXN0KCkgJT4lCiAgICBtdXRhdGUoCiAgICAgIGZpdCA9IG1hcChkYXRhLCB+IG5scyhmb3JtdWxhID0gCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHZlbG9jaXR5IH4gU1NtaWNtZW4oY29uYywgVm1heCwgS20pLCBkYXRhID0gLiksIAogICAgICAgICAgICAgICAgZGF0YSA9IC54KSwKICAgICAgdGlkaWVkID0gbWFwKGZpdCwgdGlkeSkKICAgICkgJT4lCiAgICB1bm5lc3QodGlkaWVkKSAlPiUKICAgIG11dGF0ZShyLnNxdWFyZWQgPSBtYXAoZml0LCBSc3EpKSAlPiUKICAgIHVubmVzdChyLnNxdWFyZWQpCn0gCiMgRW5kIGZ1bmN0aW9uIC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCmBgYAoKYGBge3J9CiMgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tCiMgRnVuY3Rpb246IHZfdGVzdAojIERlc2NyaXB0aW9uOiBjYWxjdWxhdGUgdiBmcm9tIEh1bnRlciBCLiBGcmFzZXIgUE5BUyAyMDIwIHBhcGVyCiMgSW5wdXRzOiAKIyAgIHZhcl9wYXIgLSB2YXJpYW5jZSBvZiBwYXJlbnRzCiMgICB2YXJfcDEgLSB2YXJpYW5jZSBvZiBwYXJlbnQgMQojICAgdmFyX3AyIC0gdmFyaWFuY2Ugb2YgcGFyZW50IDIKIyAgIG5fcDEgLSBudW1iZXIgb2YgcGFyZW50IDEgaW5kaXZpZHVhbHMKIyAgIG5fcDIgLSBudW1iZXIgb2YgcGFyZW50IDIgaW5kaXZpZHVhbHMKIyAgIHZhcl9mMiAtIHZhcmlhbmNlIG9mIGYyIGh5YnJpZHMKIyAgIEgyIC0gYnJvYWQgc2Vuc2UgaGVyaXRpYmlsaXR5CiMgICBjCiMgT3V0cHV0czogdi10ZXN0IHZhbHVlCgp2X3Rlc3QgPC0gZnVuY3Rpb24odmFyX3BhciwgdmFyX3AxLCB2YXJfcDIsIG5fcDEsIG5fcDIsIHZhcl9mMiwgSDIsIGMpIHsKICAjIENhbGN1bGF0ZSB0aGUgdmFsdWUgb2YgdgogICh2YXJfcGFyIC0gdmFyX3AxLyg0ICogbl9wMSkgLSB2YXJfcDIvKDQgKiBuX3AyKSkgLyAodmFyX2YyICogSDIgKiBjKQp9IAojIEVuZCBmdW5jdGlvbiAtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLQpgYGA=