Úvod

Klastrová (zhluková) analýza patrí medzi najpoužívanejšie metódy exploratívnej štatistiky. V praxi sa využíva všade tam, kde je potrebné rozdeliť pozorovania do homogénnych skupín, napríklad pri segmentácii zákazníkov v marketingu, identifikácii podobných regiónov podľa ekonomických ukazovateľov, hodnotení zdravotných alebo bezpečnostných rizík, či pri analýze dopravných nehôd. Jej výhodou je schopnosť pracovať s viacerými premennými súčasne a odhaliť vzory v dátach, ktoré by pri samostatnom hodnotení jednotlivých ukazovateľov mohli zostať skryté. Vhodná voľba metriky vzdialenosti a metódy zhlukovania umožňuje identifikovať podobné skupiny pozorovaní a poskytuje tak cenný podklad pre ďalšiu analýzu a rozhodovanie.

V tejto časti práce aplikujeme zhlukovú analýzu na prierezové údaje o dopravných nehodách, pričom cieľom je identifikovať skupiny nehôd s podobnými charakteristikami. Analýza vychádza z vybraných premenných, ako je počet zranení pri nehode, počet zúčastnených vozidiel, čas nehody, mesiac a deň v týždni. V Tab. 1 je uvedená databáza použitá v zhlukovej analýze.

library(knitr)
library(kableExtra)
# PRÍPRAVA DÁT PRE ZHLUKOVÚ ANALÝZU – DOPRAVNÉ NEHODY


rm(list = ls())

udaje <- read.csv("premavka.csv.csv", stringsAsFactors = FALSE)

# výber premenných vhodných pre klastrovanie
udaje_cluster <- udaje[, c(
  "injuries_total",
  "num_units",
  "crash_hour",
  "crash_month",
  "crash_day_of_week"
)]

# odstránenie chýbajúcich hodnôt
udaje_cluster <- na.omit(udaje_cluster)

# náhodný výber podvzorky 
set.seed(123)
udaje_cluster <- udaje_cluster[sample(nrow(udaje_cluster), 5000), ]

udaje_scaled <- scale(udaje_cluster)

summary(udaje_scaled)
 injuries_total      num_units         crash_hour       crash_month       crash_day_of_week 
 Min.   :-0.4791   Min.   :-2.8462   Min.   :-2.3857   Min.   :-1.69345   Min.   :-1.62337  
 1st Qu.:-0.4791   1st Qu.:-0.1606   1st Qu.:-0.7820   1st Qu.:-0.81753   1st Qu.:-0.60424  
 Median :-0.4791   Median :-0.1606   Median : 0.1089   Median : 0.05839   Median :-0.09468  
 Mean   : 0.0000   Mean   : 0.0000   Mean   : 0.0000   Mean   : 0.00000   Mean   : 0.00000  
 3rd Qu.: 0.8229   3rd Qu.:-0.1606   3rd Qu.: 0.6435   3rd Qu.: 0.93432   3rd Qu.: 0.92445  
 Max.   : 9.9369   Max.   :13.2673   Max.   : 1.7126   Max.   : 1.51827   Max.   : 1.43402  

Table 1.

# sumár vybraných premenných
summary(udaje_cluster)
 injuries_total    num_units      crash_hour     crash_month   crash_day_of_week
 Min.   :0.000   Min.   :1.00   Min.   : 0.00   Min.   : 1.0   Min.   :1.000    
 1st Qu.:0.000   1st Qu.:2.00   1st Qu.: 9.00   1st Qu.: 4.0   1st Qu.:3.000    
 Median :0.000   Median :2.00   Median :14.00   Median : 7.0   Median :4.000    
 Mean   :0.368   Mean   :2.06   Mean   :13.39   Mean   : 6.8   Mean   :4.186    
 3rd Qu.:1.000   3rd Qu.:2.00   3rd Qu.:17.00   3rd Qu.:10.0   3rd Qu.:6.000    
 Max.   :8.000   Max.   :7.00   Max.   :23.00   Max.   :12.0   Max.   :7.000    

Hierarchická zhluková analýza pracuje s mierami vzdialenosti medzi jednotlivými dopravnými nehodami. Aby boli tieto vzdialenosti porovnateľné, je potrebné, aby všetky použité premenné boli definované na rovnakej škále. Z tohto dôvodu aplikujeme tzv. z-škálovanie premenných.

Transformované hodnoty (z-skóre) vypočítame podľa vzorca

\[z = \frac{x-\mu}{\sigma}\]

kde \(\mu\) je stredná hodnota a \(\sigma\) je štandardná odchýlka pozorovaní \(x\). Predpokladáme pritom, že analyzovaný súbor údajov už neobsahuje chýbajúce hodnoty (NA), ktoré boli ošetrené v predchádzajúcich krokoch analýzy.

Touto transformáciou zabezpečíme, že premenné ako počet zranení, počet zúčastnených vozidiel, čas nehody, mesiac nehody a deň v týždni majú rovnakú váhu pri výpočte vzdialeností. Výsledkom sú štandardizované pozorovania, ktorých rozdelenie je znázornené na nasledujúcom grafe.

# =======================================================
## 1) Príprava údajov a data.frame so šlálovanými údajmi
## ======================================================

# výber premenných použitých v zhlukovej analýze
udaje_cluster <- udaje[, c("injuries_total",
                           "num_units",
                           "crash_hour",
                           "crash_month",
                           "crash_day_of_week")]

# odstránenie chýbajúcich hodnôt (ak by sa ešte vyskytovali)
udaje_cluster_complete <- na.omit(udaje_cluster)

# z-škálovanie premenných
udaje_scaled <- scale(udaje_cluster_complete)

Obr. 1.

# premena škálovaných údajov na data.frame
num_vars <- as.data.frame(udaje_scaled)
num_plots <- ncol(num_vars)

# rozloženie grafov
par(mfrow = c(ceiling(sqrt(num_plots)),
              ceiling(num_plots / ceiling(sqrt(num_plots)))))
par(mar = c(4, 4, 2, 1))

# vykreslenie boxplotov
for (col in names(num_vars)) {
  boxplot(num_vars[[col]],
          main = col,
          col = "lightblue",
          horizontal = TRUE)
}

mtext("Boxploty škálovaných premenných dopravných nehôd",
      outer = TRUE, cex = 1.3, font = 2)

NA
NA

Tentokrát odľahlé hodnoty nevylúčime, nakoľko predstavujú špecifické dopravné nehody, ktoré sú prirodzenou súčasťou analyzovaného súboru údajov a môžu niesť dôležitú informačnú hodnotu.

Pri zhlukovej analýze je dôležité analyzovať korelačnú maticu použitých premenných. Vysoká korelácia medzi premennými môže viesť k ich neúmernému zvýhodneniu pri tvorbe zhlukov. Zvyčajne sa preto pri korelácii vyššej ako 0,8 alebo 0,9 jedna z korelovaných premenných vylučuje.

V Tab. 2. sa však takto vysoké hodnoty korelácie medzi analyzovanými premennými nevyskytujú, a preto nie je potrebné pristúpiť k ich redukcii.

V prípade výskytu väčšieho počtu silne korelovaných premenných by bolo vhodné zvážiť transformáciu dát pomocou analýzy hlavných komponentov (Principal Component Analysis).

Tab. 2

cor_mat <- cor(udaje_scaled, use="pairwise.complete.obs")
cor_mat <- round(cor_mat,2)
print(cor_mat)
                  injuries_total num_units crash_hour crash_month crash_day_of_week
injuries_total              1.00      0.16      -0.02        0.01             -0.02
num_units                   0.16      1.00       0.02        0.00              0.00
crash_hour                 -0.02      0.02       1.00        0.00              0.06
crash_month                 0.01      0.00       0.00        1.00             -0.01
crash_day_of_week          -0.02      0.00       0.06       -0.01              1.00

Každému riadku v databáze zodpovedá jedna dopravná nehoda. Vzdialenosť medzi dvoma nehodami i a j definujeme pomocou euklidovskej vzdialenosti

\[ d^{ij} = \sqrt{\sum_k (x^i_k - x^j_k)^2} \]

kde \(x^i_k\) predstavuje hodnotu \(k\)tej premennej (počet zranení, počet zúčastnených vozidiel, hodina nehody, mesiac nehody a deň v týždni) pri \(i\)tej nehode. Tento typ vzdialenosti označujeme ako euklidovskú vzdialenosť.

Vzdialenosti medzi jednotlivými nehodami sú súhrnne zachytené v matici vzdialeností uvedenej v Tab. 3. Keďže ide o veľký súbor pozorovaní, matica vzdialeností slúži najmä ako vstup pre následnú hierarchickú zhlukovú analýzu, ktorá umožňuje identifikovať skupiny podobných dopravných nehôd na základe zvolených charakteristík.

Tab. 3


## ============================
## 3) Distance matrix
## ============================

set.seed(123)
udaje_cluster <- udaje_cluster[sample(nrow(udaje_cluster), min(5000, nrow(udaje_cluster))), ]

# škálovanie
udaje_scaled <- scale(udaje_cluster)

# euklidovská vzdialenosť
dist_mat <- dist(udaje_scaled, method = "euclidean")

# prehľadnosť: prvých 10x10
round(as.matrix(dist_mat)[1:10, 1:10], 2)
       182735 188942 134058 124022 160997 103065 124507 199365 193627 45404
182735   0.00   2.61   2.77   1.15   6.70   1.80   2.81   1.07   3.81  1.25
188942   2.61   0.00   2.38   1.88   5.85   1.68   2.09   2.21   2.55  2.72
134058   2.77   2.38   0.00   2.99   6.88   3.16   3.76   2.69   4.04  2.69
124022   1.15   1.88   2.99   0.00   6.27   1.03   2.09   1.32   3.29  1.86
160997   6.70   5.85   6.88   6.27   0.00   6.09   6.11   6.33   5.47  6.48
103065   1.80   1.68   3.16   1.03   6.09   0.00   1.07   1.29   3.12  2.39
124507   2.81   2.09   3.76   2.09   6.11   1.07   0.00   2.11   3.40  3.34
199365   1.07   2.21   2.69   1.32   6.33   1.29   2.11   0.00   3.20  1.37
193627   3.81   2.55   4.04   3.29   5.47   3.12   3.40   3.20   0.00  3.14
45404    1.25   2.72   2.69   1.86   6.48   2.39   3.34   1.37   3.14  0.00

Matica vzdialeností vyjadruje mieru podobnosti medzi jednotlivými pozorovaniami – čím je hodnota nižšia, tým sú si pozorovania podobnejšie. Pre lepšiu prehľadnosť je zobrazený len výrez matice pre prvé pozorovania.

Princíp hierarchického zhlukovania (Wardova metóda)

Zhlukovanie v prípade Wardovej metódy prebieha zdola smerom nahor, t.j. začíname s jednočlennými klastrami, ktoré postupne zlučujeme. Táto metóda patrí teda medzi aglomeratívne hierarchické metódy. Minimalizuje nárast vnútornej variability pri spojení dvoch klastrov, pričom využíva nasledovné výpočty:

Wardová metóda minimalizuje sumu štvorcov chýb (Error sum of Squares - ESS)

\[ESS(C) = \sum_{i \in C} \lVert x_i - \bar{x}_C \rVert^2\] kde \(C\) je zvažovaný klaster (zhluk). V každom kroku zlučovania dvoch klasterov, Wardova metóda hľadá minimálny prírastok sumy štvorcov chýb (\(\Delta ESS\)), pričom

\[\Delta ESS = ESS(A \cup B) - ESS(A) - ESS(B)\] Dvojica zhlukov, ktoré tejto podmienke o minimalizácii vyhovuje, je následne zlúčená a prechádza sa k ďalšiemu kkroku. To spravidla vedie k vytváraniu homogénnych zhlukov, pričom nedochádza k odtrhávaniu odľahlých hodnôt tak, ako pri iných zhlukovacích metódach.

Obr. 2. Hierarchické zhlukovanie - dendogram. Červená čiara určuje rez definujúci tri klastre.

## ============================
## 4) Hierarchical klastering
## ============================

# vzorka riadkov, ktorú použijeme v clusteringu
set.seed(123)
idx <- sample(seq_len(nrow(udaje_cluster_complete)),
              min(5000, nrow(udaje_cluster_complete)))

udaje_sample <- udaje_cluster_complete[idx, ]

# škálovanie len na vzorke
udaje_scaled <- scale(udaje_sample)

# vzdialenosti + zhlukovanie
dist_mat <- dist(udaje_scaled, method = "euclidean")
hc <- hclust(dist_mat, method = "ward.D2")

# 
labels_nehody <- paste0("Nehoda_", idx)

plot(hc, labels = FALSE,
     main = "Hierarchické zhlukovanie dopravných nehôd (Ward.D2)",
     xlab = "", sub = "")

k <- 3
h_cut <- hc$height[length(hc$height) - (k - 1)]
abline(h = h_cut, col = "red", lwd = 2, lty = 2)


klaster_membership <- cutree(hc, k = k)

udaje_klasters <- data.frame(
  nehoda_id = labels_nehody,
  udaje_sample,
  klaster = factor(klaster_membership)
)

Na obrázku je zobrazený dendrogram hierarchického zhlukovania dopravných nehôd pomocou Wardovej metódy. Na základe rezu dendrogramu (červená čiara) boli identifikované tri klastre, ktoré združujú nehody s podobnými charakteristikami. Výsledky naznačujú existenciu troch odlišných skupín dopravných nehôd v dátach, pričom zhluky sú relatívne homogénne a bez výrazného vplyvu odľahlých pozorovaní.

Tab.4. Príslušnosť krajín do klastrov.

data_prac <- data.frame(
  nehoda_id = udaje_klasters$nehoda_id,
  klaster   = udaje_klasters$klaster
)

data_prac
NA

Klastrová analýza rozdelila dopravné nehody do troch klastrov, ktoré reprezentujú skupiny nehôd s podobnými charakteristikami. Jednotlivé klastre sa líšia kombináciou sledovaných premenných, pričom každý klaster združuje nehody s podobným priebehom a časovými alebo kvantitatívnymi znakmi.

Deskriptívne štatistiky výsledkov

Na základe Tab. 5 môžeme konštatovať, že väčšina premenných vykazuje relatívne nízku vnútroklastrovú variabilitu, čo naznačuje dobrú separáciu klastrov. Najväčší príspevok k rozlíšeniu klastrov má premenná num_units, zatiaľ čo premenné crash_hour a crash_day_of_week prispievajú k diferenciácii klastrov v menšej miere.

Tab. 5. Vysvetlenie vnútroklastrovej variability z hľadiska jednotlivých premenných

## ============================
## 5) Variability measures
## ============================

# funkcia na výpočet sumy štvorcov
ssq <- function(x, m) sum((x - m)^2)

var_names <- colnames(udaje_scaled)

# Total Sum of Squares
TSS <- sapply(var_names, function(v) {
  ssq(udaje_scaled[, v], mean(udaje_scaled[, v]))
})

# Within-cluster Sum of Squares
WSS <- sapply(var_names, function(v) {
  x <- udaje_scaled[, v]
  sum(tapply(x, klaster_membership, function(z) ssq(z, mean(z))))
})

# Between-cluster Sum of Squares
BSS <- TSS - WSS

ss_table <- data.frame(
  Variable = var_names,
  TSS = TSS,
  WSS = WSS,
  BSS = BSS,
  Prop_Between = BSS / TSS
)

ss_table
NA

Tabuľka sumarizuje rozklad variability jednotlivých premenných na celkovú variabilitu (TSS), variabilitu vnútri klastrov (WSS) a variabilitu medzi klastrami (BSS). Podiel variability vysvetlenej klastrami naznačuje, ktoré premenné najviac prispievajú k rozlíšeniu jednotlivých zhlukov dopravných nehôd.

# spojenie pôvodných (neoškálovaných) údajov vzorky s klastrami
udaje_klasters_final <- data.frame(
  udaje_sample,
  klaster = factor(klaster_membership)
)

# kontrola
head(udaje_klasters_final)

Ku každej dopravnej nehode vo vybranej vzorke bola následne priradená príslušnosť ku klastru získaná hierarchickou zhlukovou analýzou. Táto tabuľka slúži ako podklad pre ďalšiu interpretáciu charakteristík jednotlivých zhlukov.

Tab. 6. Centroidy - priemerné hodnoty sledovaných premenných

library(dplyr)

descriptives <- udaje_klasters_final %>%
  group_by(klaster) %>%
  summarise(
    across(
      .cols = where(is.numeric),
      .fns = ~mean(.x, na.rm = TRUE),
      .names = "{.col}_mean"
    )
  )

descriptives
NA

Tab. 6. uvádza priemerné hodnoty sledovaných premenných v jednotlivých klastroch dopravných nehôd. Z výsledkov vyplýva, že tretí klaster je charakterizovaný najvyšším priemerným počtom zranení a zároveň vyšším počtom zúčastnených vozidiel, čo naznačuje závažnejšie nehody. Prvý klaster naopak zahŕňa menej závažné nehody s nižším priemerným počtom zranení a vozidiel. Druhý klaster predstavuje prechodnú skupinu s hodnotami medzi prvým a tretím klastrom.

Záver

Predložená analýza sa zameriava na zhlukovú analýzu dopravných nehôd na základe vybraných charakteristík, ako sú počet zranení, počet zúčastnených vozidiel a časové aspekty nehody. Pomocou hierarchickej zhlukovej analýzy (Wardova metóda) boli dopravné nehody rozdelené do troch klastrov, ktoré reprezentujú skupiny nehôd s podobným priebehom a mierou závažnosti. Výsledky poukazujú na existenciu menej závažných nehôd, prechodných prípadov a skupiny nehôd s vyšším počtom zranení a vozidiel. Uvedená analýza môže slúžiť ako podklad pre lepšie pochopenie štruktúry dopravných nehôd a pre cielené návrhy opatrení v oblasti dopravnej bezpečnosti.

LS0tCnRpdGxlOiAiWmhsdWtvdsOhIGFuYWzDvXphIChrbGFzdGVyIEFuYWx5c2lzKSIKYXV0aG9yOiAiQmFyYm9yYSBLb3ByZG92YSAoemEgcG9tb2NpIENoYXRHUFQpIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgotLS0tLS0tLS0KCmBgYHtyIHNldHVwMSwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KAogIGVjaG8gICAgPSBUUlVFLCAgICMgZG8gbm90IHNob3cgY29kZQogIG1lc3NhZ2UgPSBUUlVFLCAgICMgc3VwcHJlc3MgcGFja2FnZS9zeXN0ZW0gbWVzc2FnZXMKICB3YXJuaW5nID0gRkFMU0UsICAgIyBzdXBwcmVzcyB3YXJuaW5ncwogIGVycm9yICAgPSBGQUxTRSAgICAjIHN1cHByZXNzIGVycm9yIG91dHB1dAopCmBgYAoKIyMgw5p2b2QKCktsYXN0cm92w6EgKHpobHVrb3bDoSkgYW5hbMO9emEgcGF0csOtIG1lZHppIG5hanBvdcW+w612YW5lasWhaWUgbWV0w7NkeSBleHBsb3JhdMOtdm5laiDFoXRhdGlzdGlreS4gViBwcmF4aSBzYSB2eXXFvsOtdmEgdsWhYWRlIHRhbSwga2RlIGplIHBvdHJlYm7DqSByb3pkZWxpxaUgcG96b3JvdmFuaWEgZG8gaG9tb2fDqW5ueWNoIHNrdXDDrW4sIG5hcHLDrWtsYWQgcHJpIHNlZ21lbnTDoWNpaSB6w6FrYXpuw61rb3YgdiBtYXJrZXRpbmd1LCBpZGVudGlmaWvDoWNpaSBwb2RvYm7DvWNoIHJlZ2nDs25vdiBwb2TEvmEgZWtvbm9taWNrw71jaCB1a2F6b3ZhdGXEvm92LCBob2Rub3RlbsOtIHpkcmF2b3Ruw71jaCBhbGVibyBiZXpwZcSNbm9zdG7DvWNoIHJpesOtaywgxI1pIHByaSBhbmFsw716ZSBkb3ByYXZuw71jaCBuZWjDtGQuIEplaiB2w71ob2RvdSBqZSBzY2hvcG5vc8WlIHByYWNvdmHFpSBzIHZpYWNlcsO9bWkgcHJlbWVubsO9bWkgc8O6xI1hc25lIGEgb2RoYWxpxaUgdnpvcnkgdiBkw6F0YWNoLCBrdG9yw6kgYnkgcHJpIHNhbW9zdGF0bm9tIGhvZG5vdGVuw60gamVkbm90bGl2w71jaCB1a2F6b3ZhdGXEvm92IG1vaGxpIHpvc3RhxaUgc2tyeXTDqS4gVmhvZG7DoSB2b8S+YmEgbWV0cmlreSB2emRpYWxlbm9zdGkgYSBtZXTDs2R5IHpobHVrb3ZhbmlhIHVtb8W+xYh1amUgaWRlbnRpZmlrb3ZhxaUgcG9kb2Juw6kgc2t1cGlueSBwb3pvcm92YW7DrSBhIHBvc2t5dHVqZSB0YWsgY2VubsO9IHBvZGtsYWQgcHJlIMSPYWzFoWl1IGFuYWzDvXp1IGEgcm96aG9kb3ZhbmllLgoKViB0ZWp0byDEjWFzdGkgcHLDoWNlIGFwbGlrdWplbWUgemhsdWtvdsO6IGFuYWzDvXp1IG5hIHByaWVyZXpvdsOpIMO6ZGFqZSBvIGRvcHJhdm7DvWNoIG5laG9kw6FjaCwgcHJpxI1vbSBjaWXEvm9tIGplIGlkZW50aWZpa292YcWlIHNrdXBpbnkgbmVow7RkIHMgcG9kb2Juw71taSBjaGFyYWt0ZXJpc3Rpa2FtaS4gQW5hbMO9emEgdnljaMOhZHphIHogdnlicmFuw71jaCBwcmVtZW5uw71jaCwgYWtvIGplIHBvxI1ldCB6cmFuZW7DrSBwcmkgbmVob2RlLCBwb8SNZXQgesO6xI1hc3RuZW7DvWNoIHZvemlkaWVsLCDEjWFzIG5laG9keSwgbWVzaWFjIGEgZGXFiCB2IHTDvcW+ZG5pLiBWICoqVGFiLiAxKiogamUgdXZlZGVuw6EgZGF0YWLDoXphIHBvdcW+aXTDoSB2IHpobHVrb3ZlaiBhbmFsw716ZS4KCgpgYGB7ciBzZXR1cDIsIG1lc3NhZ2U9RkFMU0V9CmxpYnJhcnkoa25pdHIpCmxpYnJhcnkoa2FibGVFeHRyYSkKYGBgCgoKYGBge3J9CiMgUFLDjVBSQVZBIETDgVQgUFJFIFpITFVLT1bDmiBBTkFMw51aVSDigJMgRE9QUkFWTsOJIE5FSE9EWQoKCnJtKGxpc3QgPSBscygpKQoKdWRhamUgPC0gcmVhZC5jc3YoInByZW1hdmthLmNzdi5jc3YiLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCgojIHbDvWJlciBwcmVtZW5uw71jaCB2aG9kbsO9Y2ggcHJlIGtsYXN0cm92YW5pZQp1ZGFqZV9jbHVzdGVyIDwtIHVkYWplWywgYygKICAiaW5qdXJpZXNfdG90YWwiLAogICJudW1fdW5pdHMiLAogICJjcmFzaF9ob3VyIiwKICAiY3Jhc2hfbW9udGgiLAogICJjcmFzaF9kYXlfb2Zfd2VlayIKKV0KCiMgb2RzdHLDoW5lbmllIGNow71iYWrDumNpY2ggaG9kbsO0dAp1ZGFqZV9jbHVzdGVyIDwtIG5hLm9taXQodWRhamVfY2x1c3RlcikKCiMgbsOhaG9kbsO9IHbDvWJlciBwb2R2em9ya3kgCnNldC5zZWVkKDEyMykKdWRhamVfY2x1c3RlciA8LSB1ZGFqZV9jbHVzdGVyW3NhbXBsZShucm93KHVkYWplX2NsdXN0ZXIpLCA1MDAwKSwgXQoKdWRhamVfc2NhbGVkIDwtIHNjYWxlKHVkYWplX2NsdXN0ZXIpCgpzdW1tYXJ5KHVkYWplX3NjYWxlZCkKCmBgYAoKCioqVGFibGUgMS4qKgpgYGB7cn0KIyBzdW3DoXIgdnlicmFuw71jaCBwcmVtZW5uw71jaApzdW1tYXJ5KHVkYWplX2NsdXN0ZXIpCgpgYGAKCkhpZXJhcmNoaWNrw6EgemhsdWtvdsOhIGFuYWzDvXphIHByYWN1amUgcyBtaWVyYW1pIHZ6ZGlhbGVub3N0aSBtZWR6aSBqZWRub3RsaXbDvW1pIGRvcHJhdm7DvW1pIG5laG9kYW1pLiBBYnkgYm9saSB0aWV0byB2emRpYWxlbm9zdGkgcG9yb3ZuYXRlxL5uw6ksIGplIHBvdHJlYm7DqSwgYWJ5IHbFoWV0a3kgcG91xb5pdMOpIHByZW1lbm7DqSBib2xpIGRlZmlub3ZhbsOpIG5hIHJvdm5ha2VqIMWha8OhbGUuIFogdG9odG8gZMO0dm9kdSBhcGxpa3VqZW1lIHR6di4gei3FoWvDoWxvdmFuaWUgcHJlbWVubsO9Y2guCgpUcmFuc2Zvcm1vdmFuw6kgaG9kbm90eSAoei1za8OzcmUpIHZ5cG/EjcOtdGFtZSBwb2TEvmEgdnpvcmNhCgokJHogPSBcZnJhY3t4LVxtdX17XHNpZ21hfSQkCgprZGUgJFxtdSQgamUgc3RyZWRuw6EgaG9kbm90YSBhICRcc2lnbWEkIGplIMWhdGFuZGFyZG7DoSBvZGNow71sa2EgcG96b3JvdmFuw60gJHgkLiBQcmVkcG9rbGFkw6FtZSBwcml0b20sIMW+ZSBhbmFseXpvdmFuw70gc8O6Ym9yIMO6ZGFqb3YgdcW+IG5lb2JzYWh1amUgY2jDvWJhasO6Y2UgaG9kbm90eSAoTkEpLCBrdG9yw6kgYm9saSBvxaFldHJlbsOpIHYgcHJlZGNow6FkemFqw7pjaWNoIGtyb2tvY2ggYW5hbMO9enkuCgpUb3V0byB0cmFuc2Zvcm3DoWNpb3UgemFiZXpwZcSNw61tZSwgxb5lIHByZW1lbm7DqSBha28gcG/EjWV0IHpyYW5lbsOtLCBwb8SNZXQgesO6xI1hc3RuZW7DvWNoIHZvemlkaWVsLCDEjWFzIG5laG9keSwgbWVzaWFjIG5laG9keSBhIGRlxYggdiB0w73FvmRuaSBtYWrDuiByb3ZuYWvDuiB2w6FodSBwcmkgdsO9cG/EjXRlIHZ6ZGlhbGVub3N0w60uIFbDvXNsZWRrb20gc8O6IMWhdGFuZGFyZGl6b3ZhbsOpIHBvem9yb3ZhbmlhLCBrdG9yw71jaCByb3pkZWxlbmllIGplIHpuw6F6b3JuZW7DqSBuYSBuYXNsZWR1asO6Y29tIGdyYWZlLgoKCmBgYHtyfQojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyMgMSkgUHLDrXByYXZhIMO6ZGFqb3YgYSBkYXRhLmZyYW1lIHNvIMWhbMOhbG92YW7DvW1pIMO6ZGFqbWkKIyMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CgojIHbDvWJlciBwcmVtZW5uw71jaCBwb3XFvml0w71jaCB2IHpobHVrb3ZlaiBhbmFsw716ZQp1ZGFqZV9jbHVzdGVyIDwtIHVkYWplWywgYygiaW5qdXJpZXNfdG90YWwiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAibnVtX3VuaXRzIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgImNyYXNoX2hvdXIiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAiY3Jhc2hfbW9udGgiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAiY3Jhc2hfZGF5X29mX3dlZWsiKV0KCiMgb2RzdHLDoW5lbmllIGNow71iYWrDumNpY2ggaG9kbsO0dCAoYWsgYnkgc2EgZcWhdGUgdnlza3l0b3ZhbGkpCnVkYWplX2NsdXN0ZXJfY29tcGxldGUgPC0gbmEub21pdCh1ZGFqZV9jbHVzdGVyKQoKIyB6LcWha8OhbG92YW5pZSBwcmVtZW5uw71jaAp1ZGFqZV9zY2FsZWQgPC0gc2NhbGUodWRhamVfY2x1c3Rlcl9jb21wbGV0ZSkKYGBgCgoKKipPYnIuIDEuKioKCmBgYHtyIGJveHBsb3RzLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuc2hvdz0naG9sZCd9CiMgcHJlbWVuYSDFoWvDoWxvdmFuw71jaCDDumRham92IG5hIGRhdGEuZnJhbWUKbnVtX3ZhcnMgPC0gYXMuZGF0YS5mcmFtZSh1ZGFqZV9zY2FsZWQpCm51bV9wbG90cyA8LSBuY29sKG51bV92YXJzKQoKIyByb3psb8W+ZW5pZSBncmFmb3YKcGFyKG1mcm93ID0gYyhjZWlsaW5nKHNxcnQobnVtX3Bsb3RzKSksCiAgICAgICAgICAgICAgY2VpbGluZyhudW1fcGxvdHMgLyBjZWlsaW5nKHNxcnQobnVtX3Bsb3RzKSkpKSkKcGFyKG1hciA9IGMoNCwgNCwgMiwgMSkpCgojIHZ5a3Jlc2xlbmllIGJveHBsb3Rvdgpmb3IgKGNvbCBpbiBuYW1lcyhudW1fdmFycykpIHsKICBib3hwbG90KG51bV92YXJzW1tjb2xdXSwKICAgICAgICAgIG1haW4gPSBjb2wsCiAgICAgICAgICBjb2wgPSAibGlnaHRibHVlIiwKICAgICAgICAgIGhvcml6b250YWwgPSBUUlVFKQp9CgptdGV4dCgiQm94cGxvdHkgxaFrw6Fsb3ZhbsO9Y2ggcHJlbWVubsO9Y2ggZG9wcmF2bsO9Y2ggbmVow7RkIiwKICAgICAgb3V0ZXIgPSBUUlVFLCBjZXggPSAxLjMsIGZvbnQgPSAyKQoKCmBgYAoKVGVudG9rcsOhdCBvZMS+YWhsw6kgaG9kbm90eSBuZXZ5bMO6xI1pbWUsIG5ha2/EvmtvIHByZWRzdGF2dWrDuiDFoXBlY2lmaWNrw6kgZG9wcmF2bsOpIG5laG9keSwga3RvcsOpIHPDuiBwcmlyb2R6ZW5vdSBzw7rEjWFzxaVvdSBhbmFseXpvdmFuw6lobyBzw7pib3J1IMO6ZGFqb3YgYSBtw7TFvnUgbmllc8WlIGTDtGxlxb5pdMO6IGluZm9ybWHEjW7DuiBob2Rub3R1LgoKUHJpIHpobHVrb3ZlaiBhbmFsw716ZSBqZSBkw7RsZcW+aXTDqSBhbmFseXpvdmHFpSBrb3JlbGHEjW7DuiBtYXRpY3UgcG91xb5pdMO9Y2ggcHJlbWVubsO9Y2guIFZ5c29rw6Ega29yZWzDoWNpYSBtZWR6aSBwcmVtZW5uw71taSBtw7TFvmUgdmllc8WlIGsgaWNoIG5lw7ptZXJuw6ltdSB6dsO9aG9kbmVuaXUgcHJpIHR2b3JiZSB6aGx1a292LiBadnnEjWFqbmUgc2EgcHJldG8gcHJpIGtvcmVsw6FjaWkgdnnFocWhZWogYWtvIDAsOCBhbGVibyAwLDkgamVkbmEgeiBrb3JlbG92YW7DvWNoIHByZW1lbm7DvWNoIHZ5bHXEjXVqZS4KClYgKipUYWIuIDIuKiogc2EgdsWhYWsgdGFrdG8gdnlzb2vDqSBob2Rub3R5IGtvcmVsw6FjaWUgbWVkemkgYW5hbHl6b3ZhbsO9bWkgcHJlbWVubsO9bWkgbmV2eXNreXR1asO6LCBhIHByZXRvIG5pZSBqZSBwb3RyZWJuw6kgcHJpc3TDunBpxaUgayBpY2ggcmVkdWtjaWkuCgpWIHByw61wYWRlIHbDvXNreXR1IHbDpMSNxaFpZWhvIHBvxI10dSBzaWxuZSBrb3JlbG92YW7DvWNoIHByZW1lbm7DvWNoIGJ5IGJvbG8gdmhvZG7DqSB6dsOhxb5pxaUgdHJhbnNmb3Jtw6FjaXUgZMOhdCBwb21vY291IGFuYWzDvXp5IGhsYXZuw71jaCBrb21wb25lbnRvdiAoUHJpbmNpcGFsIENvbXBvbmVudCBBbmFseXNpcykuCgoKKipUYWIuIDIqKgpgYGB7cn0KY29yX21hdCA8LSBjb3IodWRhamVfc2NhbGVkLCB1c2U9InBhaXJ3aXNlLmNvbXBsZXRlLm9icyIpCmNvcl9tYXQgPC0gcm91bmQoY29yX21hdCwyKQpwcmludChjb3JfbWF0KQpgYGAKCkthxb5kw6ltdSByaWFka3UgdiBkYXRhYsOhemUgem9kcG92ZWTDoSBqZWRuYSBkb3ByYXZuw6EgbmVob2RhLiBWemRpYWxlbm9zxaUgbWVkemkgZHZvbWEgbmVob2RhbWkgaSBhIGogZGVmaW51amVtZSBwb21vY291IGV1a2xpZG92c2tlaiB2emRpYWxlbm9zdGkKCiQkCmRee2lqfSA9IFxzcXJ0e1xzdW1fayAoeF5pX2sgLSB4XmpfayleMn0KJCQKCmtkZSAkeF5pX2skIHByZWRzdGF2dWplIGhvZG5vdHUgJGskdGVqIHByZW1lbm5laiAocG/EjWV0IHpyYW5lbsOtLCBwb8SNZXQgesO6xI1hc3RuZW7DvWNoIHZvemlkaWVsLCBob2RpbmEgbmVob2R5LCBtZXNpYWMgbmVob2R5IGEgZGXFiCB2IHTDvcW+ZG5pKSBwcmkgJGkkdGVqIG5laG9kZS4gVGVudG8gdHlwIHZ6ZGlhbGVub3N0aSBvem5hxI11amVtZSBha28gZXVrbGlkb3Zza8O6IHZ6ZGlhbGVub3PFpS4KClZ6ZGlhbGVub3N0aSBtZWR6aSBqZWRub3RsaXbDvW1pIG5laG9kYW1pIHPDuiBzw7pocm5uZSB6YWNoeXRlbsOpIHYgbWF0aWNpIHZ6ZGlhbGVub3N0w60gdXZlZGVuZWogdiAqKlRhYi4gMy4qKiBLZcSPxb5lIGlkZSBvIHZlxL5rw70gc8O6Ym9yIHBvem9yb3ZhbsOtLCBtYXRpY2EgdnpkaWFsZW5vc3TDrSBzbMO6xb5pIG5ham3DpCBha28gdnN0dXAgcHJlIG7DoXNsZWRuw7ogaGllcmFyY2hpY2vDuiB6aGx1a292w7ogYW5hbMO9enUsIGt0b3LDoSB1bW/FvsWIdWplIGlkZW50aWZpa292YcWlIHNrdXBpbnkgcG9kb2Juw71jaCBkb3ByYXZuw71jaCBuZWjDtGQgbmEgesOha2xhZGUgenZvbGVuw71jaCBjaGFyYWt0ZXJpc3TDrWsuCgoKKipUYWIuIDMqKgoKYGBge3J9CgojIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09CiMjIDMpIERpc3RhbmNlIG1hdHJpeAojIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09CgpzZXQuc2VlZCgxMjMpCnVkYWplX2NsdXN0ZXIgPC0gdWRhamVfY2x1c3RlcltzYW1wbGUobnJvdyh1ZGFqZV9jbHVzdGVyKSwgbWluKDUwMDAsIG5yb3codWRhamVfY2x1c3RlcikpKSwgXQoKIyDFoWvDoWxvdmFuaWUKdWRhamVfc2NhbGVkIDwtIHNjYWxlKHVkYWplX2NsdXN0ZXIpCgojIGV1a2xpZG92c2vDoSB2emRpYWxlbm9zxaUKZGlzdF9tYXQgPC0gZGlzdCh1ZGFqZV9zY2FsZWQsIG1ldGhvZCA9ICJldWNsaWRlYW4iKQoKIyBwcmVoxL5hZG5vc8WlOiBwcnbDvWNoIDEweDEwCnJvdW5kKGFzLm1hdHJpeChkaXN0X21hdClbMToxMCwgMToxMF0sIDIpCgpgYGAKTWF0aWNhIHZ6ZGlhbGVub3N0w60gdnlqYWRydWplIG1pZXJ1IHBvZG9ibm9zdGkgbWVkemkgamVkbm90bGl2w71taSBwb3pvcm92YW5pYW1pIOKAkyDEjcOtbSBqZSBob2Rub3RhIG5pxb7FoWlhLCB0w71tIHPDuiBzaSBwb3pvcm92YW5pYSBwb2RvYm5lasWhaWUuIFByZSBsZXDFoWl1IHByZWjEvmFkbm9zxaUgamUgem9icmF6ZW7DvSBsZW4gdsO9cmV6IG1hdGljZSBwcmUgcHJ2w6kgcG96b3JvdmFuaWEuCgoKIyMgUHJpbmPDrXAgaGllcmFyY2hpY2vDqWhvIHpobHVrb3ZhbmlhIChXYXJkb3ZhIG1ldMOzZGEpCgpaaGx1a292YW5pZSB2IHByw61wYWRlIFdhcmRvdmVqIG1ldMOzZHkgcHJlYmllaGEgemRvbGEgc21lcm9tIG5haG9yLCB0LmouIHphxI3DrW5hbWUgcyBqZWRub8SNbGVubsO9bWkga2xhc3RyYW1pLCBrdG9yw6kgcG9zdHVwbmUgemx1xI11amVtZS4gVMOhdG8gbWV0w7NkYSBwYXRyw60gdGVkYSBtZWR6aSBhZ2xvbWVyYXTDrXZuZSBoaWVyYXJjaGlja8OpIG1ldMOzZHkuIE1pbmltYWxpenVqZSBuw6FyYXN0IHZuw7p0b3JuZWogdmFyaWFiaWxpdHkgcHJpIHNwb2plbsOtIGR2b2NoIGtsYXN0cm92LCBwcmnEjW9tIHZ5dcW+w612YSBuYXNsZWRvdm7DqSB2w71wb8SNdHk6CgpXYXJkb3bDoSBtZXTDs2RhIG1pbmltYWxpenVqZSBzdW11IMWhdHZvcmNvdiBjaMO9YiAoRXJyb3Igc3VtIG9mIFNxdWFyZXMgLSBFU1MpCgokJEVTUyhDKSA9IFxzdW1fe2kgXGluIEN9IFxsVmVydCB4X2kgLSBcYmFye3h9X0MgXHJWZXJ0XjIkJAprZGUgJEMkIGplIHp2YcW+b3ZhbsO9IGtsYXN0ZXIgKHpobHVrKS4gViBrYcW+ZG9tIGtyb2t1IHpsdcSNb3ZhbmlhIGR2b2NoIGtsYXN0ZXJvdiwgV2FyZG92YSBtZXTDs2RhIGjEvmFkw6EgbWluaW3DoWxueSBwcsOtcmFzdG9rIHN1bXkgxaF0dm9yY292IGNow71iICgkXERlbHRhIEVTUyQpLCBwcmnEjW9tCgokJFxEZWx0YSBFU1MgPSBFU1MoQSBcY3VwIEIpIC0gRVNTKEEpIC0gRVNTKEIpJCQKRHZvamljYSB6aGx1a292LCBrdG9yw6kgdGVqdG8gcG9kbWllbmtlIG8gbWluaW1hbGl6w6FjaWkgdnlob3Z1amUsIGplIG7DoXNsZWRuZSB6bMO6xI1lbsOhIGEgcHJlY2jDoWR6YSBzYSBrIMSPYWzFoWllbXUga2tyb2t1LiBUbyBzcHJhdmlkbGEgdmVkaWUgayB2eXR2w6FyYW5pdSBob21vZ8Opbm55Y2ggemhsdWtvdiwgcHJpxI1vbSBuZWRvY2jDoWR6YSBrIG9kdHJow6F2YW5pdSBvZMS+YWhsw71jaCBob2Ruw7R0IHRhaywgYWtvIHByaSBpbsO9Y2ggemhsdWtvdmFjw61jaCBtZXTDs2RhY2guCgoKKipPYnIuIDIuKiogSGllcmFyY2hpY2vDqSB6aGx1a292YW5pZSAtIGRlbmRvZ3JhbS4gxIxlcnZlbsOhIMSNaWFyYSB1csSNdWplIHJleiBkZWZpbnVqw7pjaSB0cmkga2xhc3RyZS4KCmBgYHtyfQojIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09CiMjIDQpIEhpZXJhcmNoaWNhbCBrbGFzdGVyaW5nCiMjID09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCiMgdnpvcmthIHJpYWRrb3YsIGt0b3LDuiBwb3XFvmlqZW1lIHYgY2x1c3RlcmluZ3UKc2V0LnNlZWQoMTIzKQppZHggPC0gc2FtcGxlKHNlcV9sZW4obnJvdyh1ZGFqZV9jbHVzdGVyX2NvbXBsZXRlKSksCiAgICAgICAgICAgICAgbWluKDUwMDAsIG5yb3codWRhamVfY2x1c3Rlcl9jb21wbGV0ZSkpKQoKdWRhamVfc2FtcGxlIDwtIHVkYWplX2NsdXN0ZXJfY29tcGxldGVbaWR4LCBdCgojIMWha8OhbG92YW5pZSBsZW4gbmEgdnpvcmtlCnVkYWplX3NjYWxlZCA8LSBzY2FsZSh1ZGFqZV9zYW1wbGUpCgojIHZ6ZGlhbGVub3N0aSArIHpobHVrb3ZhbmllCmRpc3RfbWF0IDwtIGRpc3QodWRhamVfc2NhbGVkLCBtZXRob2QgPSAiZXVjbGlkZWFuIikKaGMgPC0gaGNsdXN0KGRpc3RfbWF0LCBtZXRob2QgPSAid2FyZC5EMiIpCgojIApsYWJlbHNfbmVob2R5IDwtIHBhc3RlMCgiTmVob2RhXyIsIGlkeCkKCnBsb3QoaGMsIGxhYmVscyA9IEZBTFNFLAogICAgIG1haW4gPSAiSGllcmFyY2hpY2vDqSB6aGx1a292YW5pZSBkb3ByYXZuw71jaCBuZWjDtGQgKFdhcmQuRDIpIiwKICAgICB4bGFiID0gIiIsIHN1YiA9ICIiKQoKayA8LSAzCmhfY3V0IDwtIGhjJGhlaWdodFtsZW5ndGgoaGMkaGVpZ2h0KSAtIChrIC0gMSldCmFibGluZShoID0gaF9jdXQsIGNvbCA9ICJyZWQiLCBsd2QgPSAyLCBsdHkgPSAyKQoKa2xhc3Rlcl9tZW1iZXJzaGlwIDwtIGN1dHJlZShoYywgayA9IGspCgp1ZGFqZV9rbGFzdGVycyA8LSBkYXRhLmZyYW1lKAogIG5laG9kYV9pZCA9IGxhYmVsc19uZWhvZHksCiAgdWRhamVfc2FtcGxlLAogIGtsYXN0ZXIgPSBmYWN0b3Ioa2xhc3Rlcl9tZW1iZXJzaGlwKQopCgpgYGAKTmEgb2Jyw6F6a3UgamUgem9icmF6ZW7DvSBkZW5kcm9ncmFtIGhpZXJhcmNoaWNrw6lobyB6aGx1a292YW5pYSBkb3ByYXZuw71jaCBuZWjDtGQgcG9tb2NvdSBXYXJkb3ZlaiBtZXTDs2R5LiBOYSB6w6FrbGFkZSByZXp1IGRlbmRyb2dyYW11ICjEjWVydmVuw6EgxI1pYXJhKSBib2xpIGlkZW50aWZpa292YW7DqSB0cmkga2xhc3RyZSwga3RvcsOpIHpkcnXFvnVqw7ogbmVob2R5IHMgcG9kb2Juw71taSBjaGFyYWt0ZXJpc3Rpa2FtaS4gVsO9c2xlZGt5IG5hem5hxI11asO6IGV4aXN0ZW5jaXUgdHJvY2ggb2RsacWhbsO9Y2ggc2t1cMOtbiBkb3ByYXZuw71jaCBuZWjDtGQgdiBkw6F0YWNoLCBwcmnEjW9tIHpobHVreSBzw7ogcmVsYXTDrXZuZSBob21vZ8Opbm5lIGEgYmV6IHbDvXJhem7DqWhvIHZwbHl2dSBvZMS+YWhsw71jaCBwb3pvcm92YW7DrS4KCgoqKlRhYi40LioqICBQcsOtc2x1xaFub3PFpSBrcmFqw61uIGRvIGtsYXN0cm92LgoKYGBge3J9CmRhdGFfcHJhYyA8LSBkYXRhLmZyYW1lKAogIG5laG9kYV9pZCA9IHVkYWplX2tsYXN0ZXJzJG5laG9kYV9pZCwKICBrbGFzdGVyICAgPSB1ZGFqZV9rbGFzdGVycyRrbGFzdGVyCikKCmRhdGFfcHJhYwoKYGBgCktsYXN0cm92w6EgYW5hbMO9emEgcm96ZGVsaWxhIGRvcHJhdm7DqSBuZWhvZHkgZG8gdHJvY2gga2xhc3Ryb3YsIGt0b3LDqSByZXByZXplbnR1asO6IHNrdXBpbnkgbmVow7RkIHMgcG9kb2Juw71taSBjaGFyYWt0ZXJpc3Rpa2FtaS4gSmVkbm90bGl2w6kga2xhc3RyZSBzYSBsw63FoWlhIGtvbWJpbsOhY2lvdSBzbGVkb3ZhbsO9Y2ggcHJlbWVubsO9Y2gsIHByacSNb20ga2HFvmTDvSBrbGFzdGVyIHpkcnXFvnVqZSBuZWhvZHkgcyBwb2RvYm7DvW0gcHJpZWJlaG9tIGEgxI1hc292w71taSBhbGVibyBrdmFudGl0YXTDrXZueW1pIHpuYWttaS4KCiMjIERlc2tyaXB0w612bmUgxaF0YXRpc3Rpa3kgdsO9c2xlZGtvdgoKTmEgesOha2xhZGUgKipUYWIuIDUqKiBtw7TFvmVtZSBrb27FoXRhdG92YcWlLCDFvmUgdsOkxI3FoWluYSBwcmVtZW5uw71jaCB2eWthenVqZSByZWxhdMOtdm5lIG7DrXprdSB2bsO6dHJva2xhc3Ryb3bDuiB2YXJpYWJpbGl0dSwgxI1vIG5hem5hxI11amUgZG9icsO6IHNlcGFyw6FjaXUga2xhc3Ryb3YuIE5hanbDpMSNxaHDrSBwcsOtc3Bldm9rIGsgcm96bMOtxaFlbml1IGtsYXN0cm92IG3DoSBwcmVtZW5uw6EgbnVtX3VuaXRzLCB6YXRpYcS+IMSNbyBwcmVtZW5uw6kgY3Jhc2hfaG91ciBhIGNyYXNoX2RheV9vZl93ZWVrIHByaXNwaWV2YWrDuiBrIGRpZmVyZW5jacOhY2lpIGtsYXN0cm92IHYgbWVuxaFlaiBtaWVyZS4KCgoqKlRhYi4gNS4qKiBWeXN2ZXRsZW5pZSB2bsO6dHJva2xhc3Ryb3ZlaiB2YXJpYWJpbGl0eSB6IGjEvmFkaXNrYSBqZWRub3RsaXbDvWNoIHByZW1lbm7DvWNoCgpgYGB7cn0KIyMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PQojIyA1KSBWYXJpYWJpbGl0eSBtZWFzdXJlcwojIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09CgojIGZ1bmtjaWEgbmEgdsO9cG/EjWV0IHN1bXkgxaF0dm9yY292CnNzcSA8LSBmdW5jdGlvbih4LCBtKSBzdW0oKHggLSBtKV4yKQoKdmFyX25hbWVzIDwtIGNvbG5hbWVzKHVkYWplX3NjYWxlZCkKCiMgVG90YWwgU3VtIG9mIFNxdWFyZXMKVFNTIDwtIHNhcHBseSh2YXJfbmFtZXMsIGZ1bmN0aW9uKHYpIHsKICBzc3EodWRhamVfc2NhbGVkWywgdl0sIG1lYW4odWRhamVfc2NhbGVkWywgdl0pKQp9KQoKIyBXaXRoaW4tY2x1c3RlciBTdW0gb2YgU3F1YXJlcwpXU1MgPC0gc2FwcGx5KHZhcl9uYW1lcywgZnVuY3Rpb24odikgewogIHggPC0gdWRhamVfc2NhbGVkWywgdl0KICBzdW0odGFwcGx5KHgsIGtsYXN0ZXJfbWVtYmVyc2hpcCwgZnVuY3Rpb24oeikgc3NxKHosIG1lYW4oeikpKSkKfSkKCiMgQmV0d2Vlbi1jbHVzdGVyIFN1bSBvZiBTcXVhcmVzCkJTUyA8LSBUU1MgLSBXU1MKCnNzX3RhYmxlIDwtIGRhdGEuZnJhbWUoCiAgVmFyaWFibGUgPSB2YXJfbmFtZXMsCiAgVFNTID0gVFNTLAogIFdTUyA9IFdTUywKICBCU1MgPSBCU1MsCiAgUHJvcF9CZXR3ZWVuID0gQlNTIC8gVFNTCikKCnNzX3RhYmxlCgpgYGAKVGFidcS+a2Egc3VtYXJpenVqZSByb3prbGFkIHZhcmlhYmlsaXR5IGplZG5vdGxpdsO9Y2ggcHJlbWVubsO9Y2ggbmEgY2Vsa292w7ogdmFyaWFiaWxpdHUgKFRTUyksIHZhcmlhYmlsaXR1IHZuw7p0cmkga2xhc3Ryb3YgKFdTUykgYSB2YXJpYWJpbGl0dSBtZWR6aSBrbGFzdHJhbWkgKEJTUykuIFBvZGllbCB2YXJpYWJpbGl0eSB2eXN2ZXRsZW5laiBrbGFzdHJhbWkgbmF6bmHEjXVqZSwga3RvcsOpIHByZW1lbm7DqSBuYWp2aWFjIHByaXNwaWV2YWrDuiBrIHJvemzDrcWhZW5pdSBqZWRub3RsaXbDvWNoIHpobHVrb3YgZG9wcmF2bsO9Y2ggbmVow7RkLgoKCgoKYGBge3J9CiMgc3BvamVuaWUgcMO0dm9kbsO9Y2ggKG5lb8Wha8OhbG92YW7DvWNoKSDDumRham92IHZ6b3JreSBzIGtsYXN0cmFtaQp1ZGFqZV9rbGFzdGVyc19maW5hbCA8LSBkYXRhLmZyYW1lKAogIHVkYWplX3NhbXBsZSwKICBrbGFzdGVyID0gZmFjdG9yKGtsYXN0ZXJfbWVtYmVyc2hpcCkKKQoKIyBrb250cm9sYQpoZWFkKHVkYWplX2tsYXN0ZXJzX2ZpbmFsKQpgYGAKS3Uga2HFvmRlaiBkb3ByYXZuZWogbmVob2RlIHZvIHZ5YnJhbmVqIHZ6b3JrZSBib2xhIG7DoXNsZWRuZSBwcmlyYWRlbsOhIHByw61zbHXFoW5vc8WlIGt1IGtsYXN0cnUgesOtc2thbsOhIGhpZXJhcmNoaWNrb3UgemhsdWtvdm91IGFuYWzDvXpvdS4gVMOhdG8gdGFidcS+a2Egc2zDusW+aSBha28gcG9ka2xhZCBwcmUgxI9hbMWhaXUgaW50ZXJwcmV0w6FjaXUgY2hhcmFrdGVyaXN0w61rIGplZG5vdGxpdsO9Y2ggemhsdWtvdi4KCgoqKlRhYi4gNi4qKiBDZW50cm9pZHkgLSBwcmllbWVybsOpIGhvZG5vdHkgc2xlZG92YW7DvWNoIHByZW1lbm7DvWNoCmBgYHtyfQpsaWJyYXJ5KGRwbHlyKQoKZGVzY3JpcHRpdmVzIDwtIHVkYWplX2tsYXN0ZXJzX2ZpbmFsICU+JQogIGdyb3VwX2J5KGtsYXN0ZXIpICU+JQogIHN1bW1hcmlzZSgKICAgIGFjcm9zcygKICAgICAgLmNvbHMgPSB3aGVyZShpcy5udW1lcmljKSwKICAgICAgLmZucyA9IH5tZWFuKC54LCBuYS5ybSA9IFRSVUUpLAogICAgICAubmFtZXMgPSAiey5jb2x9X21lYW4iCiAgICApCiAgKQoKZGVzY3JpcHRpdmVzCgpgYGAKCioqVGFiLiA2LioqIHV2w6FkemEgcHJpZW1lcm7DqSBob2Rub3R5IHNsZWRvdmFuw71jaCBwcmVtZW5uw71jaCB2IGplZG5vdGxpdsO9Y2gga2xhc3Ryb2NoIGRvcHJhdm7DvWNoIG5laMO0ZC4gWiB2w71zbGVka292IHZ5cGzDvXZhLCDFvmUgdHJldMOtIGtsYXN0ZXIgamUgY2hhcmFrdGVyaXpvdmFuw70gbmFqdnnFocWhw61tIHByaWVtZXJuw71tIHBvxI10b20genJhbmVuw60gYSB6w6Fyb3ZlxYggdnnFocWhw61tIHBvxI10b20gesO6xI1hc3RuZW7DvWNoIHZvemlkaWVsLCDEjW8gbmF6bmHEjXVqZSB6w6F2YcW+bmVqxaFpZSBuZWhvZHkuIFBydsO9IGtsYXN0ZXIgbmFvcGFrIHphaMWVxYhhIG1lbmVqIHrDoXZhxb5uw6kgbmVob2R5IHMgbmnFvsWhw61tIHByaWVtZXJuw71tIHBvxI10b20genJhbmVuw60gYSB2b3ppZGllbC4gRHJ1aMO9IGtsYXN0ZXIgcHJlZHN0YXZ1amUgcHJlY2hvZG7DuiBza3VwaW51IHMgaG9kbm90YW1pIG1lZHppIHBydsO9bSBhIHRyZXTDrW0ga2xhc3Ryb20uCgoKIyMgWsOhdmVyCgpQcmVkbG/FvmVuw6EgYW5hbMO9emEgc2EgemFtZXJpYXZhIG5hIHpobHVrb3bDuiBhbmFsw716dSBkb3ByYXZuw71jaCBuZWjDtGQgbmEgesOha2xhZGUgdnlicmFuw71jaCBjaGFyYWt0ZXJpc3TDrWssIGFrbyBzw7ogcG/EjWV0IHpyYW5lbsOtLCBwb8SNZXQgesO6xI1hc3RuZW7DvWNoIHZvemlkaWVsIGEgxI1hc292w6kgYXNwZWt0eSBuZWhvZHkuIFBvbW9jb3UgaGllcmFyY2hpY2tlaiB6aGx1a292ZWogYW5hbMO9enkgKFdhcmRvdmEgbWV0w7NkYSkgYm9saSBkb3ByYXZuw6kgbmVob2R5IHJvemRlbGVuw6kgZG8gdHJvY2gga2xhc3Ryb3YsIGt0b3LDqSByZXByZXplbnR1asO6IHNrdXBpbnkgbmVow7RkIHMgcG9kb2Juw71tIHByaWViZWhvbSBhIG1pZXJvdSB6w6F2YcW+bm9zdGkuIFbDvXNsZWRreSBwb3VrYXp1asO6IG5hIGV4aXN0ZW5jaXUgbWVuZWogesOhdmHFvm7DvWNoIG5laMO0ZCwgcHJlY2hvZG7DvWNoIHByw61wYWRvdiBhIHNrdXBpbnkgbmVow7RkIHMgdnnFocWhw61tIHBvxI10b20genJhbmVuw60gYSB2b3ppZGllbC4gVXZlZGVuw6EgYW5hbMO9emEgbcO0xb5lIHNsw7rFvmnFpSBha28gcG9ka2xhZCBwcmUgbGVwxaFpZSBwb2Nob3BlbmllIMWhdHJ1a3TDunJ5IGRvcHJhdm7DvWNoIG5laMO0ZCBhIHByZSBjaWVsZW7DqSBuw6F2cmh5IG9wYXRyZW7DrSB2IG9ibGFzdGkgZG9wcmF2bmVqIGJlenBlxI1ub3N0aS4KCg==