Ú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==