knitr::opts_chunk$set(
    echo = TRUE,
    message = FALSE,
    warning = FALSE
)

Spracované a inšpirované Notebookom Jason Locklin: Introduction to R for Education Data Analysis and Visualization

Práca s údajmi

Tradičná práca s databázou

Pre prácu s údajmi (databázou) používame najčastejšie dátový typ .data.frame.. Je to tabuľka, ktorá pozostáva zo stĺpcov rozličných typov. Jeden riadok pritom predstavuje jeden záznam databázy.

Príklad

Majme údaje o žiakoch, ktoré predstavujú tri premenné - Meno, Vek a Body:

# Working with data frames

  Meno = c("Jana", "Jozef", "Mária")
  Vek = c(10, 11, 9)
  Body = c(85, 92, 78)

Tieto tri premenné nie sú zatiaľ nijako prepojené, predstavujú izolované stĺpce tabuľky. Do tabuľky ich spojíme nasledovne

udaje <- data.frame(Meno,Vek,Body)
print(udaje)

Vysvetlenie: DataFrame má tri stĺpce: Meno, Vek a Body. Niektoré operácie s údajmi organizovanými v .data.frame. sú uvedené nasledovne

print(udaje$Vek)                 # takto adresujeme jednotlivé premenné v data.frame
[1] 10 11  9
print(mean(udaje$Vek))           # priemerny vek
[1] 10
print(udaje[Meno=="Jozef",])     # adresovanie celého riadku
print(udaje[3,])                 # ina moznost adresovania celeho riadku
print(udaje[,2:3])               # vypisanie druheho a tretieho stlpca tabulky
print(udaje[1,1])                # vypisanie jednej bunky tabulky
[1] "Jana"
summary(udaje)                   # zakladna deskriptivna statistika celej tabulky
     Meno                Vek            Body     
 Length:3           Min.   : 9.0   Min.   :78.0  
 Class :character   1st Qu.: 9.5   1st Qu.:81.5  
 Mode  :character   Median :10.0   Median :85.0  
                    Mean   :10.0   Mean   :85.0  
                    3rd Qu.:10.5   3rd Qu.:88.5  
                    Max.   :11.0   Max.   :92.0  

Ak chceme pridať k tabuľke dodatočný stĺpec, potom to robíme nasledovne

MaAuto <- c(TRUE,FALSE,TRUE)
udaje <- cbind(udaje,MaAuto)
print(udaje)

Ak chceme pridať riadok, potom

# New record (must match column order/types)
novy.riadok <- data.frame(Meno = "Diana", Vek = 22, Body = 42,MaAuto = FALSE)

# Append
udaje <- rbind(udaje, novy.riadok)
print(udaje)

Tidyverse - moderná práca s údajmi

Tidyverse je súbor knižníc, ktoré majú zjednodušiť prácu s údajmi. Majú jednotný komunikačný štandard, vzájomne sa doplňujú.

# Load tidyverse
library(tidyverse)

dplyr - pre manipuláciu s údajmi

.dplyr. poskytuje základné možnosti manipulácie s údajmi, ako napr.:

  1. filter(): vyberá riadky

  2. select(): vyberá stĺpce

  3. mutate(): vytvára nové stĺpce tabuľky

  4. arrange(): triedi riadky

  5. summarise(): sumarizuje

V nasledovnej ukážke využijeme tzv. .pipes. %>% alebo %<% umožňuje posielať výsledky z jednej funkcie priamo do volanie nasledovnej funkcie. To umožňuje ľahšiu čitateľnosť kódov, konvencia sa ujala a má široké použitie.



# výber a následné triedenie
udaje %>%
  filter(Body > 50) %>%     # vybera zaznamy s poctom bodov viac, ako 50
  arrange(desc(Body))       # vysledny subor triedi zostupne podla premennej Body

# Zoskupí and sumarizuje
udaje %>%
  group_by(MaAuto) %>%      # zoskupi zaznamy podla premennej MaAuto a vypocita za kazdu skupinu jej primer Body
  summarise(                # a taktiez spocita pocetnosti oboch skupin
    avg.Body = mean(Body),
    count = n()
  )

# Vytváranie novej premennej
udaje %>%
  mutate(
    grade = case_when(     # vytvara novu premennu grade pod2a nasledovnej rela4nej schemy
      Body >= 90 ~ "A",
      Body >= 80 ~ "B",
      Body >= 70 ~ "C",
      TRUE ~ "D"
    )
  )

Import údajov z otv. databáz

  1. Mendeley Data Tuto sa dostaneme z Mendeley Data, kde si údaje viete voľne stiahnúť. Údaje sa vzťahujú k už publikovaným článkom vo vydavateľstve Elsevier. Výber sa dá urobiť jednoducho zadaním kľúčových slov.
  2. Kaggle Data Tuto sa dostaneme z Kaggle Datasets, kde si údaje viete voľne stiahnúť. Údaje sa vzťahujú k projektom podporovaným Kaggle. Výber sa dá urobiť jednoducho zadaním kľúčových slov.
  3. Databázy knižníc R - .library(datasets). alebo .library(wooldridge). ale aj iné - stačí si dať príkaz data()
library(datasets)
# datasets available in the 'datasets' package - nasledovne kody za mna urobil Chat GPT
ds <- as.data.frame(utils::data(package = "datasets")$results)[, c("Item","Title")]
knitr::kable(head(ds, 20), col.names = c("Dataset", "Title"))   # prvych 20 databaz
Dataset Title
AirPassengers Monthly Airline Passenger Numbers 1949-1960
BJsales Sales Data with Leading Indicator
BJsales.lead (BJsales) Sales Data with Leading Indicator
BOD Biochemical Oxygen Demand
CO2 Carbon Dioxide Uptake in Grass Plants
ChickWeight Weight versus age of chicks on different diets
DNase Elisa assay of DNase
EuStockMarkets Daily Closing Prices of Major European Stock Indices, 1991-1998
Formaldehyde Determination of Formaldehyde
HairEyeColor Hair and Eye Color of Statistics Students
Harman23.cor Harman Example 2.3
Harman74.cor Harman Example 7.4
Indometh Pharmacokinetics of Indomethacin
InsectSprays Effectiveness of Insect Sprays
JohnsonJohnson Quarterly Earnings per Johnson & Johnson Share
LakeHuron Level of Lake Huron 1875-1972
LifeCycleSavings Intercountry Life-Cycle Savings Data
Loblolly Growth of Loblolly Pine Trees
Nile Flow of the River Nile
Orange Growth of Orange Trees
# kniznica datasets obsahuje databazu nazvanu CO2. Mozeme sa na nu odvolavat nasledovne, ako napr. 
head(CO2)
Grouped Data: uptake ~ conc | Plant
  Plant   Type  Treatment conc uptake
1   Qn1 Quebec nonchilled   95   16.0
2   Qn1 Quebec nonchilled  175   30.4
3   Qn1 Quebec nonchilled  250   34.8
4   Qn1 Quebec nonchilled  350   37.2
5   Qn1 Quebec nonchilled  500   35.3
6   Qn1 Quebec nonchilled  675   39.2

Môžeme použiť aj databázu určenú pre ekobometriu - package Wooldridge

# install.packages("wooldridge")
library(wooldridge)
ds <- as.data.frame(utils::data(package = "wooldridge")$results)[, c("Item","Title")]
knitr::kable(head(ds, 20), col.names = c("Dataset", "Title"))
Dataset Title
admnrev admnrev
affairs affairs
airfare airfare
alcohol alcohol
apple apple
approval approval
athlet1 athlet1
athlet2 athlet2
attend attend
audit audit
barium barium
beauty beauty
benefits benefits
beveridge beveridge
big9salary big9salary
bwght bwght
bwght2 bwght2
campus campus
card card
catholic catholic

Import údajov z .csv alebo .xls

Ja som si zvolil údaje z [Abosede Tiamiyu: Environmental, Social, and Governance Reporting Evidencing Firm Performance in Emerging Economy]{https://data.mendeley.com/datasets/7k8pjhsrwb/1}. Na stránke sa nachádza súbor .Dataset ESG and Firm Performance.xlsx., ktorý som si stiahol a exportoval do formátu csv. Ako oddeľovač položiek som si zvolil bodkočiarku (semicolon ;), vyžívam desatinnú bodku a nie čiarku a tiež textové premenné uvádzam apostrofmi “. V prvom riadku sa nachádzajú názvy stĺpcov, ktoré neskôr budú vystupovať ako premenné. Tie obsahujú medzery, čo je v zázve premennej neprípustné a nahradil som ich podtrhovátkom”.”.

Náhľad na xls databázu otvorenú v tabuľkovom procesore
Náhľad na xls databázu otvorenú v tabuľkovom procesore
Náhľad na csv databázu otvorenú v tabuľkovom procesore
Náhľad na csv databázu otvorenú v tabuľkovom procesore

Potom už stačí importovať údaje do .data.frame., a to nasledovne

udaje <- read.csv2("udaje/Dataset ESG and Firm Performance.csv",header=TRUE,sep=";",dec=".")
head(udaje)                                             # niekolko prvych riadkov
colnames(udaje)                                         # nazvy premennych
 [1] "YEARS"                          "COMPANIES"                      "EXCHANGE.SECTOR"               
 [4] "PRIMARY.BUSINESS"               "TOBIN.Q"                        "MARKET.CAPITALIZATION"         
 [7] "RETURN.ON.ASSETS"               "DEBT.TO.ASSET"                  "FIRM.SIZE"                     
[10] "SOCIAL.DISCLOSURE.INDEX"        "ENVIRONMENTAL.DISCLOSURE.INDEX" "GOVERNANCE.DISCLOSURE.INDEX"   
[13] "ESG.INDEX"                     

Grafy

ggplot2 - knižnica pre grafy

Výber a následné triedenie

library(dplyr)

udaje.2013 <- udaje %>%
  filter(YEARS == 2013) %>%
  select(RETURN.ON.ASSETS, ESG.INDEX, DEBT.TO.ASSET, FIRM.SIZE)

Knižnica .ggplot2. je v súčasnosti najčastejšie používaná grafická knižnica, pričom predpripravené kódy k jednotlivým obrázkom si viete nájsť v R Graph Gallery. Tu si uvedieme jednoduchšie z nich.

Scatter plot

# Basic scatter plot
library(ggplot2)
ggplot(udaje.2013, aes(x = FIRM.SIZE, y = ESG.INDEX)) +            # specifikacia osi
  geom_point() +                                                   # typ grafu - scatterplot
  theme_minimal() +
  labs(title = "ESG index", x = "Veľkosť firmy", y = "Score")      # oznacenie osi

Boxplot

# Bar plot with grouping
library(ggplot2)

library(ggplot2)

ggplot(udaje, aes(x = factor(YEARS), y = ESG.INDEX)) +        # specifikacia osi
  geom_boxplot(fill = "lightblue", color = "darkblue") +      # typ grafu - boxplot
  labs(                                                       # oznacenie osi, nazov grafu
    title = "ESG Index by Years",
    x = "Year",
    y = "ESG Index"
  ) +
  theme_minimal()

Základné štatistiky.

knitr - tabuľka

library(dplyr)
library(knitr)

# Summarise basic statistics
esg.stats <- udaje %>%
  filter(YEARS %in% 2013:2016) %>%
  group_by(YEARS) %>%
  summarise(
    n     = n(),
    mean  = mean(ESG.INDEX, na.rm = TRUE),
    sd    = sd(ESG.INDEX, na.rm = TRUE),
    min   = min(ESG.INDEX, na.rm = TRUE),
    q25   = quantile(ESG.INDEX, 0.25, na.rm = TRUE),
    median= median(ESG.INDEX, na.rm = TRUE),
    q75   = quantile(ESG.INDEX, 0.75, na.rm = TRUE),
    max   = max(ESG.INDEX, na.rm = TRUE),
    .groups = "drop"
  )

# Create knitr table
kable(esg.stats, digits = 2, caption = "Basic statistics of ESG Index (2013–2016)")
Basic statistics of ESG Index (2013–2016)
YEARS n mean sd min q25 median q75 max
2013 76 0.23 0.10 0 0.15 0.22 0.29 0.58
2014 76 0.24 0.11 0 0.16 0.24 0.31 0.57
2015 76 0.25 0.12 0 0.17 0.25 0.31 0.65
2016 76 0.26 0.12 0 0.17 0.27 0.32 0.64

alebo krajšie tabuľky s pomocou .kableExtra.:

library(dplyr)
library(knitr)
library(kableExtra)

# Summarise basic statistics
esg.stats <- udaje %>%
  filter(YEARS %in% 2013:2016) %>%
  group_by(YEARS) %>%
  summarise(
    n      = n(),
    mean   = mean(ESG.INDEX, na.rm = TRUE),
    sd     = sd(ESG.INDEX, na.rm = TRUE),
    min    = min(ESG.INDEX, na.rm = TRUE),
    q25    = quantile(ESG.INDEX, 0.25, na.rm = TRUE),
    median = median(ESG.INDEX, na.rm = TRUE),
    q75    = quantile(ESG.INDEX, 0.75, na.rm = TRUE),
    max    = max(ESG.INDEX, na.rm = TRUE),
    .groups = "drop"
  )

# Create styled kableExtra table
esg.stats %>%
  kable(digits = 2, caption = "Basic statistics of ESG Index (2013–2016)") %>%
  kable_styling(full_width = FALSE, bootstrap_options = c("striped", "hover", "condensed")) %>%
  column_spec(1, bold = TRUE) %>%          # make years bold
  row_spec(0, bold = TRUE, background = "#f2f2f2") %>%  # style header row
  add_header_above(c(" " = 2, "ESG Index Statistics" = 7))
Basic statistics of ESG Index (2013–2016)
ESG Index Statistics
YEARS n mean sd min q25 median q75 max
2013 76 0.23 0.10 0 0.15 0.22 0.29 0.58
2014 76 0.24 0.11 0 0.16 0.24 0.31 0.57
2015 76 0.25 0.12 0 0.17 0.25 0.31 0.65
2016 76 0.26 0.12 0 0.17 0.27 0.32 0.64

t-test: Porovnanie priemeru ESG indexu v rokoch 2013 a 2015

t.test.result <- t.test(
  udaje$ESG.INDEX[udaje$YEARS == 2013],
  udaje$ESG.INDEX[udaje$YEARS == 2015]
)

print(t.test.result)

    Welch Two Sample t-test

data:  udaje$ESG.INDEX[udaje$YEARS == 2013] and udaje$ESG.INDEX[udaje$YEARS == 2015]
t = -1.2984, df = 147.86, p-value = 0.1962
alternative hypothesis: true difference in means is not equal to 0
95 percent confidence interval:
 -0.05906743  0.01222533
sample estimates:
mean of x mean of y 
0.2276316 0.2510526 

ANOVA: Comparing Reading Scores Across Programs

anova.result <- aov(ESG.INDEX ~ YEARS, data = udaje)
summary(anova.result)
             Df Sum Sq Mean Sq F value   Pr(>F)    
YEARS         1   0.62  0.6202   30.04 5.75e-08 ***
Residuals   758  15.65  0.0206                     
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Linear Regression: Predicting Math Scores

model <- lm(ESG.INDEX ~ RETURN.ON.ASSETS + FIRM.SIZE + DEBT.TO.ASSET, data = udaje.2013)
summary(model)

Call:
lm(formula = ESG.INDEX ~ RETURN.ON.ASSETS + FIRM.SIZE + DEBT.TO.ASSET, 
    data = udaje.2013)

Residuals:
      Min        1Q    Median        3Q       Max 
-0.189450 -0.054050 -0.001079  0.049273  0.237250 

Coefficients:
                   Estimate Std. Error t value Pr(>|t|)    
(Intercept)      -3.080e-01  8.210e-02  -3.751 0.000354 ***
RETURN.ON.ASSETS  1.668e-04  9.115e-05   1.830 0.071345 .  
FIRM.SIZE         8.080e-02  1.136e-02   7.115 6.78e-10 ***
DEBT.TO.ASSET    -6.201e-04  4.205e-04  -1.475 0.144688    
---
Signif. codes:  0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1

Residual standard error: 0.08095 on 72 degrees of freedom
Multiple R-squared:  0.4217,    Adjusted R-squared:  0.3976 
F-statistic:  17.5 on 3 and 72 DF,  p-value: 1.24e-08
# install.packages(c("broom", "kableExtra", "dplyr", "stringr"))
library(broom)
library(dplyr)
library(kableExtra)
library(stringr)

# Your model (already fitted)
# model <- lm(ESG.INDEX ~ RETURN.ON.ASSETS + FIRM.SIZE + DEBT.TO.ASSET, data = udaje.2013)

coef.tbl <- tidy(model, conf.int = TRUE) %>%
  mutate(
    term = recode(term,
      "(Intercept)" = "Intercept",
      "RETURN.ON.ASSETS" = "Return on Assets",
      "FIRM.SIZE" = "Firm Size",
      "DEBT.TO.ASSET" = "Debt to Asset"
    ),
    stars = case_when(
      p.value < 0.001 ~ "***",
      p.value < 0.01  ~ "**",
      p.value < 0.05  ~ "*",
      p.value < 0.1   ~ "·",
      TRUE            ~ ""
    )
  ) %>%
  transmute(
    Term = term,
    Estimate = estimate,
    `Std. Error` = std.error,
    `t value` = statistic,
    `p value` = p.value,
    `95% CI` = str_c("[", round(conf.low, 3), ", ", round(conf.high, 3), "]"),
    Sig = stars
  )

coef.tbl %>%
  kable(
    digits = 3,
    caption = "OLS Regression Coefficients (ESG.INDEX ~ RETURN.ON.ASSETS + FIRM.SIZE + DEBT.TO.ASSET)"
  ) %>%
  kable_styling(full_width = FALSE, bootstrap_options = c("striped", "hover", "condensed")) %>%
  column_spec(1, bold = TRUE) %>%
  row_spec(0, bold = TRUE, background = "#f2f2f2") %>%
  footnote(
    general = "Signif. codes: *** p<0.001, ** p<0.01, * p<0.05, · p<0.1.",
    threeparttable = TRUE
  )
OLS Regression Coefficients (ESG.INDEX ~ RETURN.ON.ASSETS + FIRM.SIZE + DEBT.TO.ASSET)
Term Estimate Std. Error t value p value 95% CI Sig
Intercept -0.308 0.082 -3.751 0.000 [-0.472, -0.144] ***
Return on Assets 0.000 0.000 1.830 0.071 [0, 0] ·
Firm Size 0.081 0.011 7.115 0.000 [0.058, 0.103] ***
Debt to Asset -0.001 0.000 -1.475 0.145 [-0.001, 0]
Note:
Signif. codes: *** p<0.001, ** p<0.01, * p<0.05, · p<0.1.
fit.tbl <- glance(model) %>%
  transmute(
    `R-squared` = r.squared,
    `Adj. R-squared` = adj.r.squared,
    `F-statistic` = statistic,
    `F p-value` = p.value,
    `AIC` = AIC,
    `BIC` = BIC,
    `Num. obs.` = nobs
  )

fit.tbl %>%
  kable(digits = 3, caption = "Model Fit Statistics") %>%
  kable_styling(full_width = FALSE, bootstrap_options = c("condensed"))
Model Fit Statistics
R-squared Adj. R-squared F-statistic F p-value AIC BIC Num. obs.
0.422 0.398 17.5 0 -160.553 -148.899 76

Info zdroje pre ďalšie štúdium

R Project

Posit

Community Resources

LS0tCnRpdGxlOiAiUHLDoWNhIHMgZGF0YWLDoXpvdSIKYXV0aG9yOiAiVmxhZGltw61yIEdhemRhICA8YnI+CihzIHZ5dcW+aXTDrW0gdmVyZWpuZSBkb3N0dXBuw71jaCBrw7Nkb3YpIgpkYXRlOiAiU2VwdGVtYmVyIDIwMjUiCm91dHB1dDogCiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICB0aGVtZTogdW5pdGVkCiAgICBoaWdobGlnaHQ6IHRhbmdvCmVkaXRvcl9vcHRpb25zOiAKICBtYXJrZG93bjogCiAgICB3cmFwOiA3MgotLS0KCmBgYHtyfQprbml0cjo6b3B0c19jaHVuayRzZXQoCiAgICBlY2hvID0gVFJVRSwKICAgIG1lc3NhZ2UgPSBGQUxTRSwKICAgIHdhcm5pbmcgPSBGQUxTRQopCmBgYAoKU3ByYWNvdmFuw6kgYSBpbsWhcGlyb3ZhbsOpIE5vdGVib29rb20KW0phc29uIExvY2tsaW46IEludHJvZHVjdGlvbiB0byBSIGZvciBFZHVjYXRpb24gRGF0YSBBbmFseXNpcyBhbmQgVmlzdWFsaXphdGlvbl0oaHR0cDovL3JwdWJzLmNvbS9qYXNvbkwvTGFuZEwpe3RhcmdldD0iX2JsYW5rIiByZWw9Im5vb3BlbmVyIn0KCgojIFByw6FjYSBzIMO6ZGFqbWkKCiMjIFRyYWRpxI1uw6EgcHLDoWNhIHMgZGF0YWLDoXpvdQoKUHJlIHByw6FjdSBzIMO6ZGFqbWkgKGRhdGFiw6F6b3UpIHBvdcW+w612YW1lIG5hasSNYXN0ZWrFoWllIGTDoXRvdsO9IHR5cCAuZGF0YS5mcmFtZS4uIEplIHRvIHRhYnXEvmthLCBrdG9yw6EgcG96b3N0w6F2YSB6byBzdMS6cGNvdiByb3psacSNbsO9Y2ggdHlwb3YuIEplZGVuIHJpYWRvayBwcml0b20gcHJlZHN0YXZ1amUgamVkZW4gesOhem5hbSBkYXRhYsOhenkuCgojIyMgUHLDrWtsYWQKCk1ham1lIMO6ZGFqZSBvIMW+aWFrb2NoLCBrdG9yw6kgcHJlZHN0YXZ1asO6IHRyaSBwcmVtZW5uw6kgLSBNZW5vLCBWZWsgYSBCb2R5OgoKYGBge3J9CiMgV29ya2luZyB3aXRoIGRhdGEgZnJhbWVzCgogIE1lbm8gPSBjKCJKYW5hIiwgIkpvemVmIiwgIk3DoXJpYSIpCiAgVmVrID0gYygxMCwgMTEsIDkpCiAgQm9keSA9IGMoODUsIDkyLCA3OCkKYGBgCgpUaWV0byB0cmkgcHJlbWVubsOpIG5pZSBzw7ogemF0aWHEviBuaWpha28gcHJlcG9qZW7DqSwgcHJlZHN0YXZ1asO6IGl6b2xvdmFuw6kgc3TEunBjZSB0YWJ1xL5reS4gRG8gdGFidcS+a3kgaWNoIHNwb2rDrW1lIG5hc2xlZG92bmUKCmBgYHtyfQp1ZGFqZSA8LSBkYXRhLmZyYW1lKE1lbm8sVmVrLEJvZHkpCnByaW50KHVkYWplKQpgYGAKCgpWeXN2ZXRsZW5pZTogRGF0YUZyYW1lIG3DoSB0cmkgc3TEunBjZTogTWVubywgVmVrIGEgQm9keS4gTmlla3RvcsOpIG9wZXLDoWNpZSBzIMO6ZGFqbWkgb3JnYW5pem92YW7DvW1pIHYgLmRhdGEuZnJhbWUuIHPDuiB1dmVkZW7DqSBuYXNsZWRvdm5lCgpgYGB7cn0KcHJpbnQodWRhamUkVmVrKSAgICAgICAgICAgICAgICAgIyB0YWt0byBhZHJlc3VqZW1lIGplZG5vdGxpdsOpIHByZW1lbm7DqSB2IGRhdGEuZnJhbWUKcHJpbnQobWVhbih1ZGFqZSRWZWspKSAgICAgICAgICAgIyBwcmllbWVybnkgdmVrCnByaW50KHVkYWplW01lbm89PSJKb3plZiIsXSkgICAgICMgYWRyZXNvdmFuaWUgY2Vsw6lobyByaWFka3UKcHJpbnQodWRhamVbMyxdKSAgICAgICAgICAgICAgICAgIyBpbmEgbW96bm9zdCBhZHJlc292YW5pYSBjZWxlaG8gcmlhZGt1CnByaW50KHVkYWplWywyOjNdKSAgICAgICAgICAgICAgICMgdnlwaXNhbmllIGRydWhlaG8gYSB0cmV0aWVobyBzdGxwY2EgdGFidWxreQpwcmludCh1ZGFqZVsxLDFdKSAgICAgICAgICAgICAgICAjIHZ5cGlzYW5pZSBqZWRuZWogYnVua3kgdGFidWxreQpzdW1tYXJ5KHVkYWplKSAgICAgICAgICAgICAgICAgICAjIHpha2xhZG5hIGRlc2tyaXB0aXZuYSBzdGF0aXN0aWthIGNlbGVqIHRhYnVsa3kKYGBgCgpBayBjaGNlbWUgcHJpZGHFpSBrIHRhYnXEvmtlIGRvZGF0b8SNbsO9IHN0xLpwZWMsIHBvdG9tIHRvIHJvYsOtbWUgbmFzbGVkb3ZuZQoKYGBge3J9Ck1hQXV0byA8LSBjKFRSVUUsRkFMU0UsVFJVRSkKdWRhamUgPC0gY2JpbmQodWRhamUsTWFBdXRvKQpwcmludCh1ZGFqZSkKYGBgCgpBayBjaGNlbWUgcHJpZGHFpSByaWFkb2ssIHBvdG9tCgpgYGB7cn0KIyBOZXcgcmVjb3JkIChtdXN0IG1hdGNoIGNvbHVtbiBvcmRlci90eXBlcykKbm92eS5yaWFkb2sgPC0gZGF0YS5mcmFtZShNZW5vID0gIkRpYW5hIiwgVmVrID0gMjIsIEJvZHkgPSA0MixNYUF1dG8gPSBGQUxTRSkKCiMgQXBwZW5kCnVkYWplIDwtIHJiaW5kKHVkYWplLCBub3Z5LnJpYWRvaykKcHJpbnQodWRhamUpCmBgYAoKCgojIyBUaWR5dmVyc2UgLSBtb2Rlcm7DoSBwcsOhY2EgcyDDumRham1pCgpUaWR5dmVyc2UgamUgc8O6Ym9yIGtuacW+bsOtYywga3RvcsOpIG1hasO6IHpqZWRub2R1xaFpxaUgcHLDoWN1IHMgw7pkYWptaS4gTWFqw7ogamVkbm90bsO9IGtvbXVuaWthxI1uw70gxaF0YW5kYXJkLCB2esOham9tbmUgc2EgZG9wbMWIdWrDui4KCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CiMgTG9hZCB0aWR5dmVyc2UKbGlicmFyeSh0aWR5dmVyc2UpCmBgYAoKIyMjICBkcGx5ciAtIHByZSBtYW5pcHVsw6FjaXUgcyDDumRham1pCgouZHBseXIuIHBvc2t5dHVqZSB6w6FrbGFkbsOpIG1vxb5ub3N0aSBtYW5pcHVsw6FjaWUgcyDDumRham1pLCBha28gbmFwci46IAoKMS4gZmlsdGVyKCk6IHZ5YmVyw6EgcmlhZGt5IAoKMS4gc2VsZWN0KCk6IHZ5YmVyw6Egc3TEunBjZSAKCjEuIG11dGF0ZSgpOiB2eXR2w6FyYSBub3bDqSBzdMS6cGNlIHRhYnXEvmt5IAoKMS4gYXJyYW5nZSgpOiB0cmllZGkgcmlhZGt5IAoKMS4gc3VtbWFyaXNlKCk6IHN1bWFyaXp1amUKClYgbmFzbGVkb3ZuZWogdWvDocW+a2Ugdnl1xb5pamVtZSB0enYuIC5waXBlcy4gJT4lIGFsZWJvICU8JSB1bW/FvsWIdWplIHBvc2llbGHFpSB2w71zbGVka3kgeiBqZWRuZWogZnVua2NpZSBwcmlhbW8gZG8gdm9sYW5pZSBuYXNsZWRvdm5laiBmdW5rY2llLiBUbyB1bW/FvsWIdWplIMS+YWjFoWl1IMSNaXRhdGXEvm5vc8WlIGvDs2Rvdiwga29udmVuY2lhIHNhIHVqYWxhIGEgbcOhIMWhaXJva8OpIHBvdcW+aXRpZS4KCgpgYGB7cn0KCgojIHbDvWJlciBhIG7DoXNsZWRuw6kgdHJpZWRlbmllCnVkYWplICU+JQogIGZpbHRlcihCb2R5ID4gNTApICU+JSAgICAgIyB2eWJlcmEgemF6bmFteSBzIHBvY3RvbSBib2RvdiB2aWFjLCBha28gNTAKICBhcnJhbmdlKGRlc2MoQm9keSkpICAgICAgICMgdnlzbGVkbnkgc3Vib3IgdHJpZWRpIHpvc3R1cG5lIHBvZGxhIHByZW1lbm5laiBCb2R5CgojIFpvc2t1cMOtIGFuZCBzdW1hcml6dWplCnVkYWplICU+JQogIGdyb3VwX2J5KE1hQXV0bykgJT4lICAgICAgIyB6b3NrdXBpIHphem5hbXkgcG9kbGEgcHJlbWVubmVqIE1hQXV0byBhIHZ5cG9jaXRhIHphIGthemR1IHNrdXBpbnUgamVqIHByaW1lciBCb2R5CiAgc3VtbWFyaXNlKCAgICAgICAgICAgICAgICAjIGEgdGFrdGlleiBzcG9jaXRhIHBvY2V0bm9zdGkgb2JvY2ggc2t1cGluCiAgICBhdmcuQm9keSA9IG1lYW4oQm9keSksCiAgICBjb3VudCA9IG4oKQogICkKCiMgVnl0dsOhcmFuaWUgbm92ZWogcHJlbWVubmVqCnVkYWplICU+JQogIG11dGF0ZSgKICAgIGdyYWRlID0gY2FzZV93aGVuKCAgICAgIyB2eXR2YXJhIG5vdnUgcHJlbWVubnUgZ3JhZGUgcG9kMmEgbmFzbGVkb3ZuZWogcmVsYTRuZWogc2NoZW15CiAgICAgIEJvZHkgPj0gOTAgfiAiQSIsCiAgICAgIEJvZHkgPj0gODAgfiAiQiIsCiAgICAgIEJvZHkgPj0gNzAgfiAiQyIsCiAgICAgIFRSVUUgfiAiRCIKICAgICkKICApCmBgYAoKIyMgSW1wb3J0IMO6ZGFqb3YgeiBvdHYuIGRhdGFiw6F6CgoKMS4gKk1lbmRlbGV5IERhdGEqIFR1dG8gc2EgZG9zdGFuZW1lIHogW01lbmRlbGV5IERhdGFdKGh0dHBzOi8vZGF0YS5tZW5kZWxleS5jb20vKXt0YXJnZXQ9Ii5ibGFuayIgcmVsPSJub29wZW5lciJ9LCBrZGUgc2kgw7pkYWplIHZpZXRlIHZvxL5uZSBzdGlhaG7DusWlLiDDmmRhamUgc2EgdnrFpWFodWrDuiBrIHXFviBwdWJsaWtvdmFuw71tIMSNbMOhbmtvbSB2byB2eWRhdmF0ZcS+c3R2ZSBFbHNldmllci4gVsO9YmVyIHNhIGTDoSB1cm9iacWlIGplZG5vZHVjaG8gemFkYW7DrW0ga8S+w7rEjW92w71jaCBzbG92LgoyLiAqS2FnZ2xlIERhdGEqICAgVHV0byBzYSBkb3N0YW5lbWUgeiBbS2FnZ2xlIERhdGFzZXRzXShodHRwczovL3d3dy5rYWdnbGUuY29tL2RhdGFzZXRzKXt0YXJnZXQ9Ii5ibGFuayIgcmVsPSJub29wZW5lciJ9LCBrZGUgc2kgw7pkYWplIHZpZXRlIHZvxL5uZSBzdGlhaG7DusWlLiDDmmRhamUgc2EgdnrFpWFodWrDuiBrIHByb2pla3RvbSBwb2Rwb3JvdmFuw71tIEthZ2dsZS4gVsO9YmVyIHNhIGTDoSB1cm9iacWlIGplZG5vZHVjaG8gemFkYW7DrW0ga8S+w7rEjW92w71jaCBzbG92LgozLiBEYXRhYsOhenkga25pxb5uw61jIFIgLSAubGlicmFyeShkYXRhc2V0cykuIGFsZWJvIC5saWJyYXJ5KHdvb2xkcmlkZ2UpLiBhbGUgYWogaW7DqSAtIHN0YcSNw60gc2kgZGHFpSBwcsOta2F6IGRhdGEoKQoKYGBge3J9CmxpYnJhcnkoZGF0YXNldHMpCiMgZGF0YXNldHMgYXZhaWxhYmxlIGluIHRoZSAnZGF0YXNldHMnIHBhY2thZ2UgLSBuYXNsZWRvdm5lIGtvZHkgemEgbW5hIHVyb2JpbCBDaGF0IEdQVApkcyA8LSBhcy5kYXRhLmZyYW1lKHV0aWxzOjpkYXRhKHBhY2thZ2UgPSAiZGF0YXNldHMiKSRyZXN1bHRzKVssIGMoIkl0ZW0iLCJUaXRsZSIpXQprbml0cjo6a2FibGUoaGVhZChkcywgMjApLCBjb2wubmFtZXMgPSBjKCJEYXRhc2V0IiwgIlRpdGxlIikpICAgIyBwcnZ5Y2ggMjAgZGF0YWJhegojIGtuaXpuaWNhIGRhdGFzZXRzIG9ic2FodWplIGRhdGFiYXp1IG5henZhbnUgQ08yLiBNb3plbWUgc2EgbmEgbnUgb2R2b2xhdmF0IG5hc2xlZG92bmUsIGFrbyBuYXByLiAKaGVhZChDTzIpCmBgYAoKTcO0xb5lbWUgcG91xb5pxaUgYWogZGF0YWLDoXp1IHVyxI1lbsO6IHByZSBla29ib21ldHJpdSAtIHBhY2thZ2UgV29vbGRyaWRnZQoKYGBge3J9CiMgaW5zdGFsbC5wYWNrYWdlcygid29vbGRyaWRnZSIpCmxpYnJhcnkod29vbGRyaWRnZSkKZHMgPC0gYXMuZGF0YS5mcmFtZSh1dGlsczo6ZGF0YShwYWNrYWdlID0gIndvb2xkcmlkZ2UiKSRyZXN1bHRzKVssIGMoIkl0ZW0iLCJUaXRsZSIpXQprbml0cjo6a2FibGUoaGVhZChkcywgMjApLCBjb2wubmFtZXMgPSBjKCJEYXRhc2V0IiwgIlRpdGxlIikpCmBgYAoKIyMjIEltcG9ydCDDumRham92IHogLmNzdiBhbGVibyAueGxzCgpKYSBzb20gc2kgenZvbGlsIMO6ZGFqZSB6IFtBYm9zZWRlIFRpYW1peXU6IEVudmlyb25tZW50YWwsIFNvY2lhbCwgYW5kIEdvdmVybmFuY2UgUmVwb3J0aW5nIEV2aWRlbmNpbmcgRmlybSBQZXJmb3JtYW5jZSBpbiBFbWVyZ2luZyBFY29ub215XXtodHRwczovL2RhdGEubWVuZGVsZXkuY29tL2RhdGFzZXRzLzdrOHBqaHNyd2IvMX0uIE5hIHN0csOhbmtlIHNhIG5hY2jDoWR6YSBzw7pib3IgLkRhdGFzZXQgRVNHIGFuZCBGaXJtIFBlcmZvcm1hbmNlLnhsc3guLCBrdG9yw70gc29tIHNpIHN0aWFob2wgYSBleHBvcnRvdmFsIGRvIGZvcm3DoXR1IGNzdi4gQWtvIG9kZGXEvm92YcSNIHBvbG/FvmllayBzb20gc2kgenZvbGlsIGJvZGtvxI1pYXJrdSAoc2VtaWNvbG9uIDspLCB2ecW+w612YW0gZGVzYXRpbm7DuiBib2RrdSBhIG5pZSDEjWlhcmt1IGEgdGllxb4gdGV4dG92w6kgcHJlbWVubsOpIHV2w6FkemFtIGFwb3N0cm9mbWkgIi4gViBwcnZvbSByaWFka3Ugc2EgbmFjaMOhZHphasO6IG7DoXp2eSBzdMS6cGNvdiwga3RvcsOpIG5lc2vDtHIgYnVkw7ogdnlzdHVwb3ZhxaUgYWtvIHByZW1lbm7DqS4gVGllIG9ic2FodWrDuiBtZWR6ZXJ5LCDEjW8gamUgdiB6w6F6dmUgcHJlbWVubmVqIG5lcHLDrXB1c3Ruw6kgYSBuYWhyYWRpbCBzb20gaWNoIHBvZHRyaG92w6F0a29tICIuIi4gIAoKIVtOw6FoxL5hZCBuYSB4bHMgZGF0YWLDoXp1IG90dm9yZW7DuiB2IHRhYnXEvmtvdm9tIHByb2Nlc29yZV0ob2JyYXpreS91ZGFqZXhscy5qcGcpe3dpZHRoPTEwMCV9CgohW07DoWjEvmFkIG5hIGNzdiBkYXRhYsOhenUgb3R2b3JlbsO6IHYgdGFidcS+a292b20gcHJvY2Vzb3JlXShvYnJhemt5L3VkYWplY3N2LmpwZyl7d2lkdGg9MTAwJX0KClBvdG9tIHXFviBzdGHEjcOtIGltcG9ydG92YcWlIMO6ZGFqZSBkbyAuZGF0YS5mcmFtZS4sIGEgdG8gbmFzbGVkb3ZuZQoKYGBge3J9CnVkYWplIDwtIHJlYWQuY3N2MigidWRhamUvRGF0YXNldCBFU0cgYW5kIEZpcm0gUGVyZm9ybWFuY2UuY3N2IixoZWFkZXI9VFJVRSxzZXA9IjsiLGRlYz0iLiIpCmhlYWQodWRhamUpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBuaWVrb2xrbyBwcnZ5Y2ggcmlhZGtvdgpjb2xuYW1lcyh1ZGFqZSkgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgbmF6dnkgcHJlbWVubnljaApgYGAKCiMjIEdyYWZ5CgoKIyMjIGdncGxvdDIgLSBrbmnFvm5pY2EgcHJlIGdyYWZ5CgpWw71iZXIgYSBuw6FzbGVkbsOpIHRyaWVkZW5pZQpgYGB7cn0KbGlicmFyeShkcGx5cikKCnVkYWplLjIwMTMgPC0gdWRhamUgJT4lCiAgZmlsdGVyKFlFQVJTID09IDIwMTMpICU+JQogIHNlbGVjdChSRVRVUk4uT04uQVNTRVRTLCBFU0cuSU5ERVgsIERFQlQuVE8uQVNTRVQsIEZJUk0uU0laRSkKYGBgCgpLbmnFvm5pY2EgLmdncGxvdDIuIGplIHYgc8O6xI1hc25vc3RpIG5hasSNYXN0ZWrFoWllIHBvdcW+w612YW7DoSBncmFmaWNrw6Ega25pxb5uaWNhLCBwcmnEjW9tIHByZWRwcmlwcmF2ZW7DqSBrw7NkeSBrIGplZG5vdGxpdsO9bSBvYnLDoXprb20gc2kgdmlldGUgbsOhanPFpSB2IFtSIEdyYXBoIEdhbGxlcnldKGh0dHBzOi8vci1ncmFwaC1nYWxsZXJ5LmNvbS8pLiBUdSBzaSB1dmVkaWVtZSBqZWRub2R1Y2jFoWllIHogbmljaC4KCiMjIyMgU2NhdHRlciBwbG90CgpgYGB7cn0KIyBCYXNpYyBzY2F0dGVyIHBsb3QKbGlicmFyeShnZ3Bsb3QyKQpnZ3Bsb3QodWRhamUuMjAxMywgYWVzKHggPSBGSVJNLlNJWkUsIHkgPSBFU0cuSU5ERVgpKSArICAgICAgICAgICAgIyBzcGVjaWZpa2FjaWEgb3NpCiAgZ2VvbV9wb2ludCgpICsgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIHR5cCBncmFmdSAtIHNjYXR0ZXJwbG90CiAgdGhlbWVfbWluaW1hbCgpICsKICBsYWJzKHRpdGxlID0gIkVTRyBpbmRleCIsIHggPSAiVmXEvmtvc8WlIGZpcm15IiwgeSA9ICJTY29yZSIpICAgICAgIyBvem5hY2VuaWUgb3NpCmBgYAoKIyMjIyBCb3hwbG90CgpgYGB7cn0KIyBCYXIgcGxvdCB3aXRoIGdyb3VwaW5nCmxpYnJhcnkoZ2dwbG90MikKCmxpYnJhcnkoZ2dwbG90MikKCmdncGxvdCh1ZGFqZSwgYWVzKHggPSBmYWN0b3IoWUVBUlMpLCB5ID0gRVNHLklOREVYKSkgKyAgICAgICAgIyBzcGVjaWZpa2FjaWEgb3NpCiAgZ2VvbV9ib3hwbG90KGZpbGwgPSAibGlnaHRibHVlIiwgY29sb3IgPSAiZGFya2JsdWUiKSArICAgICAgIyB0eXAgZ3JhZnUgLSBib3hwbG90CiAgbGFicyggICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBvem5hY2VuaWUgb3NpLCBuYXpvdiBncmFmdQogICAgdGl0bGUgPSAiRVNHIEluZGV4IGJ5IFllYXJzIiwKICAgIHggPSAiWWVhciIsCiAgICB5ID0gIkVTRyBJbmRleCIKICApICsKICB0aGVtZV9taW5pbWFsKCkKYGBgCgojIFrDoWtsYWRuw6kgxaF0YXRpc3Rpa3kuIAoKCiMjIGtuaXRyIC0gdGFidcS+a2EKCmBgYHtyfQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGtuaXRyKQoKIyBTdW1tYXJpc2UgYmFzaWMgc3RhdGlzdGljcwplc2cuc3RhdHMgPC0gdWRhamUgJT4lCiAgZmlsdGVyKFlFQVJTICVpbiUgMjAxMzoyMDE2KSAlPiUKICBncm91cF9ieShZRUFSUykgJT4lCiAgc3VtbWFyaXNlKAogICAgbiAgICAgPSBuKCksCiAgICBtZWFuICA9IG1lYW4oRVNHLklOREVYLCBuYS5ybSA9IFRSVUUpLAogICAgc2QgICAgPSBzZChFU0cuSU5ERVgsIG5hLnJtID0gVFJVRSksCiAgICBtaW4gICA9IG1pbihFU0cuSU5ERVgsIG5hLnJtID0gVFJVRSksCiAgICBxMjUgICA9IHF1YW50aWxlKEVTRy5JTkRFWCwgMC4yNSwgbmEucm0gPSBUUlVFKSwKICAgIG1lZGlhbj0gbWVkaWFuKEVTRy5JTkRFWCwgbmEucm0gPSBUUlVFKSwKICAgIHE3NSAgID0gcXVhbnRpbGUoRVNHLklOREVYLCAwLjc1LCBuYS5ybSA9IFRSVUUpLAogICAgbWF4ICAgPSBtYXgoRVNHLklOREVYLCBuYS5ybSA9IFRSVUUpLAogICAgLmdyb3VwcyA9ICJkcm9wIgogICkKCiMgQ3JlYXRlIGtuaXRyIHRhYmxlCmthYmxlKGVzZy5zdGF0cywgZGlnaXRzID0gMiwgY2FwdGlvbiA9ICJCYXNpYyBzdGF0aXN0aWNzIG9mIEVTRyBJbmRleCAoMjAxM+KAkzIwMTYpIikKYGBgCgphbGVibyBrcmFqxaFpZSB0YWJ1xL5reSBzIHBvbW9jb3UgLmthYmxlRXh0cmEuOgoKYGBge3J9CmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoa25pdHIpCmxpYnJhcnkoa2FibGVFeHRyYSkKCiMgU3VtbWFyaXNlIGJhc2ljIHN0YXRpc3RpY3MKZXNnLnN0YXRzIDwtIHVkYWplICU+JQogIGZpbHRlcihZRUFSUyAlaW4lIDIwMTM6MjAxNikgJT4lCiAgZ3JvdXBfYnkoWUVBUlMpICU+JQogIHN1bW1hcmlzZSgKICAgIG4gICAgICA9IG4oKSwKICAgIG1lYW4gICA9IG1lYW4oRVNHLklOREVYLCBuYS5ybSA9IFRSVUUpLAogICAgc2QgICAgID0gc2QoRVNHLklOREVYLCBuYS5ybSA9IFRSVUUpLAogICAgbWluICAgID0gbWluKEVTRy5JTkRFWCwgbmEucm0gPSBUUlVFKSwKICAgIHEyNSAgICA9IHF1YW50aWxlKEVTRy5JTkRFWCwgMC4yNSwgbmEucm0gPSBUUlVFKSwKICAgIG1lZGlhbiA9IG1lZGlhbihFU0cuSU5ERVgsIG5hLnJtID0gVFJVRSksCiAgICBxNzUgICAgPSBxdWFudGlsZShFU0cuSU5ERVgsIDAuNzUsIG5hLnJtID0gVFJVRSksCiAgICBtYXggICAgPSBtYXgoRVNHLklOREVYLCBuYS5ybSA9IFRSVUUpLAogICAgLmdyb3VwcyA9ICJkcm9wIgogICkKCiMgQ3JlYXRlIHN0eWxlZCBrYWJsZUV4dHJhIHRhYmxlCmVzZy5zdGF0cyAlPiUKICBrYWJsZShkaWdpdHMgPSAyLCBjYXB0aW9uID0gIkJhc2ljIHN0YXRpc3RpY3Mgb2YgRVNHIEluZGV4ICgyMDEz4oCTMjAxNikiKSAlPiUKICBrYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGQUxTRSwgYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJzdHJpcGVkIiwgImhvdmVyIiwgImNvbmRlbnNlZCIpKSAlPiUKICBjb2x1bW5fc3BlYygxLCBib2xkID0gVFJVRSkgJT4lICAgICAgICAgICMgbWFrZSB5ZWFycyBib2xkCiAgcm93X3NwZWMoMCwgYm9sZCA9IFRSVUUsIGJhY2tncm91bmQgPSAiI2YyZjJmMiIpICU+JSAgIyBzdHlsZSBoZWFkZXIgcm93CiAgYWRkX2hlYWRlcl9hYm92ZShjKCIgIiA9IDIsICJFU0cgSW5kZXggU3RhdGlzdGljcyIgPSA3KSkKYGBgCgoKCiMjIyMgdC10ZXN0OiBQb3Jvdm5hbmllIHByaWVtZXJ1IEVTRyBpbmRleHUgdiByb2tvY2ggMjAxMyBhIDIwMTUKCmBgYHtyfQp0LnRlc3QucmVzdWx0IDwtIHQudGVzdCgKICB1ZGFqZSRFU0cuSU5ERVhbdWRhamUkWUVBUlMgPT0gMjAxM10sCiAgdWRhamUkRVNHLklOREVYW3VkYWplJFlFQVJTID09IDIwMTVdCikKCnByaW50KHQudGVzdC5yZXN1bHQpCmBgYAoKCiMjIyMgQU5PVkE6IENvbXBhcmluZyBSZWFkaW5nIFNjb3JlcyBBY3Jvc3MgUHJvZ3JhbXMKCmBgYHtyfQphbm92YS5yZXN1bHQgPC0gYW92KEVTRy5JTkRFWCB+IFlFQVJTLCBkYXRhID0gdWRhamUpCnN1bW1hcnkoYW5vdmEucmVzdWx0KQpgYGAKCiMjIyMgTGluZWFyIFJlZ3Jlc3Npb246IFByZWRpY3RpbmcgTWF0aCBTY29yZXMKCmBgYHtyfQptb2RlbCA8LSBsbShFU0cuSU5ERVggfiBSRVRVUk4uT04uQVNTRVRTICsgRklSTS5TSVpFICsgREVCVC5UTy5BU1NFVCwgZGF0YSA9IHVkYWplLjIwMTMpCnN1bW1hcnkobW9kZWwpCmBgYAoKCgpgYGB7cn0KIyBpbnN0YWxsLnBhY2thZ2VzKGMoImJyb29tIiwgImthYmxlRXh0cmEiLCAiZHBseXIiLCAic3RyaW5nciIpKQpsaWJyYXJ5KGJyb29tKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KGthYmxlRXh0cmEpCmxpYnJhcnkoc3RyaW5ncikKCiMgWW91ciBtb2RlbCAoYWxyZWFkeSBmaXR0ZWQpCiMgbW9kZWwgPC0gbG0oRVNHLklOREVYIH4gUkVUVVJOLk9OLkFTU0VUUyArIEZJUk0uU0laRSArIERFQlQuVE8uQVNTRVQsIGRhdGEgPSB1ZGFqZS4yMDEzKQoKY29lZi50YmwgPC0gdGlkeShtb2RlbCwgY29uZi5pbnQgPSBUUlVFKSAlPiUKICBtdXRhdGUoCiAgICB0ZXJtID0gcmVjb2RlKHRlcm0sCiAgICAgICIoSW50ZXJjZXB0KSIgPSAiSW50ZXJjZXB0IiwKICAgICAgIlJFVFVSTi5PTi5BU1NFVFMiID0gIlJldHVybiBvbiBBc3NldHMiLAogICAgICAiRklSTS5TSVpFIiA9ICJGaXJtIFNpemUiLAogICAgICAiREVCVC5UTy5BU1NFVCIgPSAiRGVidCB0byBBc3NldCIKICAgICksCiAgICBzdGFycyA9IGNhc2Vfd2hlbigKICAgICAgcC52YWx1ZSA8IDAuMDAxIH4gIioqKiIsCiAgICAgIHAudmFsdWUgPCAwLjAxICB+ICIqKiIsCiAgICAgIHAudmFsdWUgPCAwLjA1ICB+ICIqIiwKICAgICAgcC52YWx1ZSA8IDAuMSAgIH4gIsK3IiwKICAgICAgVFJVRSAgICAgICAgICAgIH4gIiIKICAgICkKICApICU+JQogIHRyYW5zbXV0ZSgKICAgIFRlcm0gPSB0ZXJtLAogICAgRXN0aW1hdGUgPSBlc3RpbWF0ZSwKICAgIGBTdGQuIEVycm9yYCA9IHN0ZC5lcnJvciwKICAgIGB0IHZhbHVlYCA9IHN0YXRpc3RpYywKICAgIGBwIHZhbHVlYCA9IHAudmFsdWUsCiAgICBgOTUlIENJYCA9IHN0cl9jKCJbIiwgcm91bmQoY29uZi5sb3csIDMpLCAiLCAiLCByb3VuZChjb25mLmhpZ2gsIDMpLCAiXSIpLAogICAgU2lnID0gc3RhcnMKICApCgpjb2VmLnRibCAlPiUKICBrYWJsZSgKICAgIGRpZ2l0cyA9IDMsCiAgICBjYXB0aW9uID0gIk9MUyBSZWdyZXNzaW9uIENvZWZmaWNpZW50cyAoRVNHLklOREVYIH4gUkVUVVJOLk9OLkFTU0VUUyArIEZJUk0uU0laRSArIERFQlQuVE8uQVNTRVQpIgogICkgJT4lCiAga2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRkFMU0UsIGJvb3RzdHJhcF9vcHRpb25zID0gYygic3RyaXBlZCIsICJob3ZlciIsICJjb25kZW5zZWQiKSkgJT4lCiAgY29sdW1uX3NwZWMoMSwgYm9sZCA9IFRSVUUpICU+JQogIHJvd19zcGVjKDAsIGJvbGQgPSBUUlVFLCBiYWNrZ3JvdW5kID0gIiNmMmYyZjIiKSAlPiUKICBmb290bm90ZSgKICAgIGdlbmVyYWwgPSAiU2lnbmlmLiBjb2RlczogKioqIHA8MC4wMDEsICoqIHA8MC4wMSwgKiBwPDAuMDUsIMK3IHA8MC4xLiIsCiAgICB0aHJlZXBhcnR0YWJsZSA9IFRSVUUKICApCmBgYAoKYGBge3J9CmZpdC50YmwgPC0gZ2xhbmNlKG1vZGVsKSAlPiUKICB0cmFuc211dGUoCiAgICBgUi1zcXVhcmVkYCA9IHIuc3F1YXJlZCwKICAgIGBBZGouIFItc3F1YXJlZGAgPSBhZGouci5zcXVhcmVkLAogICAgYEYtc3RhdGlzdGljYCA9IHN0YXRpc3RpYywKICAgIGBGIHAtdmFsdWVgID0gcC52YWx1ZSwKICAgIGBBSUNgID0gQUlDLAogICAgYEJJQ2AgPSBCSUMsCiAgICBgTnVtLiBvYnMuYCA9IG5vYnMKICApCgpmaXQudGJsICU+JQogIGthYmxlKGRpZ2l0cyA9IDMsIGNhcHRpb24gPSAiTW9kZWwgRml0IFN0YXRpc3RpY3MiKSAlPiUKICBrYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGQUxTRSwgYm9vdHN0cmFwX29wdGlvbnMgPSBjKCJjb25kZW5zZWQiKSkKYGBgCgoKCgoKCiMgSW5mbyB6ZHJvamUgcHJlIMSPYWzFoWllIMWhdMO6ZGl1bQoKIyMjIyBSIFByb2plY3QKCi0gICBbUiBQcm9qZWN0IEhvbWVwYWdlXShodHRwczovL3d3dy5yLXByb2plY3Qub3JnLykgLSBCYXNlIFIgZG93bmxvYWRzLAogICAgbmV3cywgYW5kIGxlYXJuaW5nIHJlc291cmNlcwoKIyMjIyBQb3NpdAoKLSAgIFtSIFN0dWRpbyBEZXNrdG9wXShodHRwczovL3Bvc2l0LmNvL2Rvd25sb2FkL3JzdHVkaW8tZGVza3RvcC8pIC0KICAgIEZlYXR1cmUgcmljaCBlbnZpcm9ubWVudCBmb3Igd29ya2luZyB3aXRoIGRhdGEgaW4gUgotICAgW1IgZm9yIERhdGEgU2NpZW5jZV0oaHR0cHM6Ly9yNGRzLmhhZC5jby5uei8pIC0gQ29tcHJlaGVuc2l2ZSBvbmxpbmUKICAgIGJvb2sKLSAgIFtSU3R1ZGlvCiAgICBDaGVhdHNoZWV0c10oaHR0cHM6Ly93d3cucnN0dWRpby5jb20vcmVzb3VyY2VzL2NoZWF0c2hlZXRzLykgLSBRdWljawogICAgcmVmZXJlbmNlIGd1aWRlcwoKIyMjIyBDb21tdW5pdHkgUmVzb3VyY2VzCgotICAgW1ItYmxvZ2dlcnNdKGh0dHBzOi8vd3d3LnItYmxvZ2dlcnMuY29tLykgLSBCbG9nIGFnZ3JlZ2F0b3IgZm9yIFIKICAgIG5ld3MgYW5kIHR1dG9yaWFscwotICAgW1N0YWNrIE92ZXJmbG93IC0gUgogICAgdGFnXShodHRwczovL3N0YWNrb3ZlcmZsb3cuY29tL3F1ZXN0aW9ucy90YWdnZWQvcikgLSBRJkEgY29tbXVuaXR5Ci0gICBbcmRyci5pbyBTbmlwcGV0c10oaHR0cHM6Ly9yZHJyLmlvL3NuaXBwZXRzLykgLSBUZXN0IFIgY29kZSBzbmlwcGV0cwogICAgb25saW5lCi0gICBbQ291cnNlcmEgLSBSCiAgICBQcm9ncmFtbWluZ10oaHR0cHM6Ly93d3cuY291cnNlcmEub3JnL2xlYXJuL3ItcHJvZ3JhbW1pbmcpIC0gT25saW5lCiAgICBjb3Vyc2UKLSAgIFtEYXRhQ2FtcCAtIEludHJvZHVjdGlvbiB0bwogICAgUl0oaHR0cHM6Ly93d3cuZGF0YWNhbXAuY29tL2NvdXJzZXMvZnJlZS1pbnRyb2R1Y3Rpb24tdG8tcikgLQogICAgSW50ZXJhY3RpdmUgbGVhcm5pbmcgcGxhdGZvcm0KCgo=