Ú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 celkov - napríklad pri segmentácii
zákazníkov v marketingu, identifikácii podobných krajín v
makroekonomických ukazovateľoch, hodnotenízdravotných rizík,
klasifikácii biologických vzoriek či v geoinformatike pri zoskupovaní
priestorových sobjektov. Jej výhodou je, že pracuje s viacerými
premennými naraz a dokáže odhaliť vzory, ktoré by pri samotnom hodnotení
jednotlivých ukazovateľov zostali skryté. Správne zvolená metrika
vzdialenosti a metóda zhlukovania umožňujú odhaliť skryté vzťahy v
dátach, čím poskytujú cenný podklad pre rozhodovanie v rôznych
oblastiach aplikovaného výskumu.
My predstavíme zhlukovú analýzu pri analýze údajov pochádzajúcich z
meraní meteorologických premenných, konkrétne teploty vzduchu,
atmosférického tlaku, rýchlosti vetra a úhrnu zrážok. Budeme využívať
údaje za rok 2020. V Tab. 1. uvádzame celú nami
používanú databázu.
library(knitr)
library(kableExtra)
rm(list=ls())
udaje <- read.csv2("Temperature_2020.csv",dec = ",", sep = ";",fileEncoding = "Windows-1250", stringsAsFactors = FALSE)
udaje2020 <- subset(udaje, Year == 2020)
udaje2020 <- udaje2020[, c("Year", "Temperature...C.", "Air.Pressure..hPa.", "Wind.Speed..m.s.", "Precipitation..mm.")]
rownames(udaje2020) <- seq_len(nrow(udaje2020))
udaje2020 <- subset(udaje2020, select = -Year)
Table 1.
udaje2020
Hierarchická zhluková analýza pracuje s mierami vzdialenosti medzi
pozorovaniami. Aby boli tieto vzdialenosti porovnateľné, je potrebné,
aby všetky premenné boli definované na rovnakejškále. Používame pritom
tzv. z-škálovanie, pričom transformované \(z\) hodnoty (skóre) vypočítame
nasledovne
\[z = \frac{x-\mu}{\sigma}\]
kde \(\mu\) je stredná hodnota a
\(\sigma\) je štandardná odchýlka
pozorovaní \(x\). Predpokladáme pritom,
že súbor údajov už neobsahuje NA hodnoty, ktoré boli ošetrené v
predchádzajúcich krokoch.
Touto operáciou získame škálované pozorovania, pričom ich rozloženie
je znázornené nasledovne:
# =======================================================
## 1) Príprava údajov a data.frame so šlálovanými údajmi
## ======================================================
udaje_complete <- na.omit(udaje2020)
udaje_scaled <- scale(udaje_complete)
Obr. 1.
num_vars <- as.data.frame(udaje_scaled)
num_plots <- ncol(num_vars)
par(mfrow = c(ceiling(sqrt(num_plots)), ceiling(num_plots / ceiling(sqrt(num_plots)))))
par(mar = c(4, 4, 2, 1))
for (col in names(num_vars)) {
boxplot(num_vars[[col]],
main = col,
col = "lightblue",
horizontal = TRUE)
}
mtext("Boxploty numerických premenných (rok 2020)", outer = TRUE, cex = 1.3, font = 2)

Tentokrát odľahlé hodnoty nevylúčime, nakoľko predstavujú skutočné
namerané pozorovania a sú súčasťou reálnych podmienok merania.
Teplota vzduchu (Temperature…C.): Rozdelenie teplôt je pomerne
symetrické, bez extrémnych odľahlých bodov. Medián sa nachádza približne
uprostred intervalu a teploty sú rozptýlené rovnomerne. Hodnoty
nevykazujú výrazné anomálie.
Atmosférický tlak (Air.Pressure..hPa.): Atmosférický tlak má
mierne asymetrické rozdelenie, no bez odľahlých pozorovaní. Väčšina
hodnôt sa sústreďuje okolo mediánu a variabilita je primeraná. Údaje
pôsobia stabilne a bez extrémnych výkyvov.
Rýchlosť vetra (Wind.Speed..m.s.): Pri rýchlosti vetra sa
vyskytujú dva zreteľné odľahlé body – jeden veľmi nízky a jeden veľmi
vysoký. Tieto hodnoty však predstavujú reálne namerané situácie (napr.
náhle zosilnenie vetra alebo bezvetrie), preto ich ponechávame v
analýze. Ostatné hodnoty sú sústredené v úzkom intervale okolo mediánu,
čo naznačuje nízku variabilitu väčšiny meraní.
Úhrn zrážok (Precipitation..mm.): Zrážky vykazujú širšie rozptyl
než ostatné premenné, čo je typické pre túto veličinu, keďže zrážky môžu
byť veľmi variabilné. Rozdelenie je bez odľahlých bodov a medián sa
nachádza v spodnej časti boxu, čo naznačuje, že väčšina meraní mala
relatívne nízke úhrny zrážok.
Pri zhlukovej analýze je dôležitá korelačná matica premenných. Vysoká
korelácia zvýhodňuje pri zhlukovej analýze korelované premenné. Preto
pri korelácii nad 0,8 alebo 0.9 vylúčime jednu z korelovaných
premenných. V Tab. 2. sa však takáto vysoká korelácia
nenachádza, preto sa nemusíme ďalej s problémom zaoberať. > V
prípade, ak máme väčší počet významne korelovaných premenných, sa
odporúča i transformácia 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)
Temperature...C. Air.Pressure..hPa. Wind.Speed..m.s.
Temperature...C. 1.00 -0.17 -0.23
Air.Pressure..hPa. -0.17 1.00 0.07
Wind.Speed..m.s. -0.23 0.07 1.00
Precipitation..mm. 0.54 -0.48 -0.42
Precipitation..mm.
Temperature...C. 0.54
Air.Pressure..hPa. -0.48
Wind.Speed..m.s. -0.42
Precipitation..mm. 1.00
Každému pozorovaniu zodpovedá jeden riadok meraných hodnôt.
Vzdialenosť medzi pozorovaniami \(i\) a
\(j\) je:
\[
d^{ij} = \sqrt{\sum_k (x^i_k - x^j_k)^2}
\] kde \(x^i_k\) je hodnota
\(k\)tej premennej (Temperature…C.,
Air.Pressure..hPa., Wind.Speed..m.s., Precipitation..mm.) pre
pozorovanie \(i\). Tento typ
vzdialenosti nazývame aj Euklidovská vzdialenosť. Vzdialenosti medzi
jednotlivými pozorovaniami sa súhrnne vyjadrujú aj v matici
vzdialeností, čo je v našom prípade uvedené v Tab.3..
Analýzou tejto tabuľky zistíme, že najväčšia vzdialenosť je medzi
mesiacmi 8 a 11, čo znamená, že tieto mesiace sa podľa meteorologických
ukazovateľov najviac líšia – majú odlišnú teplotu, tlak, zrážky aj
rýchlosť vetra. Naopak, najmenšia vzdialenosť je medzi mesiacmi 4 a 5,
ktoré sú si veľmi podobné a vykazujú takmer rovnaké klimatické
podmienky. Tieto rozdiely môžeme vysvetliť sezónnosťou – letné mesiace
sa prirodzene výrazne odlišujú od jesenných či zimných mesiacov, zatiaľ
čo susediace jarné mesiace sú si klimaticky veľmi blízke.
Tab. 3
## ============================
## 3) Distance matrix
## ============================
rownames(udaje_scaled) <- rownames(udaje_complete)
dist_mat <- round(dist(udaje_scaled, method = "euclidean"), 2)
dist_mat
1 2 3 4 5 6 7 8 9 10 11
2 3.92
3 1.93 4.42
4 1.61 3.34 1.96
5 1.81 2.91 2.44 1.06
6 3.83 2.89 3.36 2.76 2.27
7 2.96 3.54 2.97 1.47 1.52 2.16
8 4.11 3.09 3.81 2.98 2.40 0.66 2.10
9 3.49 2.25 3.67 2.69 1.84 1.26 2.31 1.23
10 3.17 3.33 2.09 2.34 2.32 1.57 2.48 2.17 2.36
11 0.77 3.90 2.50 1.54 1.66 3.89 2.71 4.06 3.42 3.51
12 3.20 3.46 2.43 2.42 3.08 3.32 3.15 3.83 3.77 2.26 3.54
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
## ============================
hc <- hclust(dist_mat, method = "ward.D2")
plot(hc, labels = rownames(udaje_scaled),
main = "Hierarchical klastering of observations (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(
Observation = rownames(udaje_complete),
udaje_complete,
klaster = factor(klaster_membership)
)
Tab.4. Príslušnosť pozorovaní do klastrov.
data_prac <- data.frame( Observation = rownames(udaje_klasters),
klaster = udaje_klasters$klaster)
colnames(data_prac) <- c("Observation","klaster")
data_prac
Na základe vykonanej zhlukovej analýzy boli mesiace rozdelené do
troch klastrov. Z výslednej tabuľky klastrov vidíme toto rozdelenie:
klaster 1: mesiace 1, 4, 5, 7, 11 klaster 2: mesiace 2, 6, 8, 9 klaster
3: mesiace 3, 10, 12. Tieto klastre reprezentujú mesiace, ktoré majú
podobné hodnoty teploty, tlaku, rýchlosti vetra a zrážok. Klaster 1
združuje mesiace, ktoré majú podobné a mierne stabilné meteorologické
podmienky — ide najmä o mesiace s podobnými teplotami a rýchlosťou
vetra. Klaster 2 zahŕňa mesiace s vyššou podobnosťou najmä v úrovni
zrážok a tlaku vzduchu, pričom tieto mesiace sa od klastrov 1 a 3
odlišujú práve kombináciou týchto ukazovateľov. Klaster 3 obsahuje
mesiace, ktoré majú odlišný profil — či už z hľadiska teploty alebo
zrážok — a preto sa od zvyšných mesiacov jasne oddeľujú. Výsledkom je
teda logické rozdelenie mesiacov do troch skupín, kde každý klaster
spája mesiace s podobnými meteorologickými charakteristikami.
Deskriptívne štatistiky výsledkov
Na základe Tab. 5 možno vyhodnotiť separačnú silu
jednotlivých premenných vo vytvorených klastroch nasledovne: Premenná
Precipitation..mm. má najvyšší podiel medzi-klastrového rozptylu
(Prop_Between ≈ 0.80), čo znamená, že veľmi dobre odlišuje jednotlivé
klastre – zrážky sú teda jedným z najsilnejších faktorov, podľa ktorých
sa mesiace prirodzene zoskupujú. Podobne aj Wind.Speed..m.s. vykazuje
vysokú hodnotu Prop_Between (≈ 0.65), čo naznačuje, že rýchlosť vetra sa
medzi klastrami významne líši a predstavuje dobrý separátor. Premenná
Air.Pressure..hPa. má strednú separačnú schopnosť (≈ 0.58), čo znamená,
že tlak vzduchu prispieva k odlišovaniu klastrov, ale už menej výrazne
ako predchádzajúce dve premenné. Najslabším separátorom je
Temperature…C., ktorej podiel medzi-klastrového rozptylu je najnižší (≈
0.29). Znamená to, že teplota sa síce medzi mesiacmi líši, ale nie
dostatočne na to, aby zásadne prispievala k vytváraniu odlišných
klastrov. Najviac k rozlíšeniu klastrov prispievajú zrážky a rýchlosť
vetra, zatiaľ čo teplota má v tomto type analýzy iba slabý separačný
efekt.
Tab. 5. Vysvetlenie vnútroklastrovej variability z
hľadiska jednotlivých premenných
## ============================
## 5) Variability measures
## ============================
ssq <- function(x, m) sum((x - m)^2)
var_names <- colnames(udaje_scaled)
TSS <- sapply(var_names, function(v) ssq(udaje_scaled[, v], mean(udaje_scaled[, v])))
WSS <- sapply(var_names, function(v) {
x <- udaje_scaled[, v]
tapply(x, klaster_membership, function(z) ssq(z, mean(z))) |> sum()
})
BSS <- TSS - WSS
ss_table <- data.frame(
Variable = var_names,
TSS = TSS,
WSS = WSS,
BSS = BSS,
Prop_Between = BSS / TSS
)
ss_table
attach(udaje2020)
udaje2020 <- data.frame(cbind(udaje2020,udaje_klasters$klaster))
colnames(udaje2020) <- c("Temperature_C","AirPressure_hPa","WindSpeed_ms","Precipitation_mm","klaster")
Tab. 6. Centroidy - priemerné hodnoty sledovaných
premenných
library(dplyr)
descriptives <- udaje_klasters %>%
group_by(klaster) %>%
summarise(
across(
.cols = where(is.numeric),
.fns = list(
mean = ~mean(.x, na.rm = TRUE)
),
.names = "{.col}_{.fn}"
)
)
descriptives
NA
Prvý klaster má mierne nadpriemernú teplotu, najvyšší priemerný
atmosférický tlak (≈ 993.8 hPa) a strednú úroveň rýchlosti vetra. Druhý
klaster je charakterizovaný najvyššou priemernou teplotou (≈ 14.8 °C),
nízkym tlakom a najnižšou rýchlosťou vetra spomedzi všetkých klastrov.
Tretí klaster má najnižšie priemerné teploty, najnižší atmosférický tlak
a zároveň najvyššiu priemernú rýchlosť vetra, čo naznačuje odlišné
poveternostné podmienky oproti zvyšným dvom klastrom. Z toho vyplýva, že
najdôležitejším rozlišovacím faktorom medzi klastrami je teplota, zatiaľ
čo tlak vzduchu a rýchlosť vetra dopĺňajú charakteristiku jednotlivých
skupín.
Záver
Predložená analýza sa zaoberá zoskupovaním jednotlivých mesiacov roka
na základe ich priemerných meteorologických charakteristík – konkrétne
teploty vzduchu, atmosférického tlaku, rýchlosti vetra a množstva
zrážok. Na základe týchto premenných boli mesiace rozdelené do troch
klastrov, pričom každý klaster predstavuje odlišný typ poveternostných
podmienok. Takto získaná klasifikácia umožňuje lepšie pochopiť sezónne
rozdiely v počasí, identifikovať mesiace s podobnými klimatickými
profilmi a môže slúžiť ako podklad pri plánovaní aktivít závislých od
počasia – napríklad pri energetickom plánovaní, poľnohospodárstve či
hodnotení klimatických trendov.
LS0tCnRpdGxlOiAiWmhsdWtvdsOhIGFuYWzDvXphIChrbGFzdGVyIEFuYWx5c2lzKSIKYXV0aG9yOiAiVmVyb25pa2EgUml6c255b3ZzemvDoSAoemEgcG9tb2NpIENoYXRHUFQpIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgotLS0tLS0tLS0KCmBgYHtyIHNldHVwMSwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KAogIGVjaG8gICAgPSBUUlVFLCAgICMgZG8gbm90IHNob3cgY29kZQogIG1lc3NhZ2UgPSBUUlVFLCAgICMgc3VwcHJlc3MgcGFja2FnZS9zeXN0ZW0gbWVzc2FnZXMKICB3YXJuaW5nID0gRkFMU0UsICAgIyBzdXBwcmVzcyB3YXJuaW5ncwogIGVycm9yICAgPSBGQUxTRSAgICAjIHN1cHByZXNzIGVycm9yIG91dHB1dAopCmBgYAoKIyMgw5p2b2QKCktsYXN0cm92w6EgKHpobHVrb3bDoSkgYW5hbMO9emEgcGF0csOtIG1lZHppIG5hanBvdcW+w612YW5lasWhaWUgbWV0w7NkeSBleHBsb3JhdMOtdm5laiDFoXRhdGlzdGlreS4gViBwcmF4aSBzYSB2eXXFvsOtdmEgdsWhYWRlIHRhbSwga2RlIGplIHBvdHJlYm7DqSByb3pkZWxpxaUgcG96b3JvdmFuaWEgZG8gaG9tb2fDqW5ueWNoIGNlbGtvdiAtIG5hcHLDrWtsYWQgcHJpIHNlZ21lbnTDoWNpaSB6w6FrYXpuw61rb3YgdiBtYXJrZXRpbmd1LCBpZGVudGlmaWvDoWNpaSBwb2RvYm7DvWNoIGtyYWrDrW4gdiBtYWtyb2Vrb25vbWlja8O9Y2ggdWthem92YXRlxL5vY2gsIGhvZG5vdGVuw616ZHJhdm90bsO9Y2ggcml6w61rLCBrbGFzaWZpa8OhY2lpIGJpb2xvZ2lja8O9Y2ggdnpvcmllayDEjWkgdiBnZW9pbmZvcm1hdGlrZSBwcmkgem9za3Vwb3ZhbsOtIHByaWVzdG9yb3bDvWNoIHNvYmpla3Rvdi4gSmVqIHbDvWhvZG91IGplLCDFvmUgcHJhY3VqZSBzIHZpYWNlcsO9bWkgcHJlbWVubsO9bWkgbmFyYXogYSBkb2vDocW+ZSBvZGhhbGnFpSB2em9yeSwga3RvcsOpIGJ5IHByaSBzYW1vdG5vbSBob2Rub3RlbsOtIGplZG5vdGxpdsO9Y2ggdWthem92YXRlxL5vdiB6b3N0YWxpIHNrcnl0w6kuIFNwcsOhdm5lIHp2b2xlbsOhIG1ldHJpa2EgdnpkaWFsZW5vc3RpIGEgbWV0w7NkYSB6aGx1a292YW5pYSB1bW/FvsWIdWrDuiBvZGhhbGnFpSBza3J5dMOpIHZ6xaVhaHkgdiBkw6F0YWNoLCDEjcOtbSBwb3NreXR1asO6IGNlbm7DvSBwb2RrbGFkIHByZSByb3pob2RvdmFuaWUgdiByw7R6bnljaCBvYmxhc3RpYWNoIGFwbGlrb3ZhbsOpaG8gdsO9c2t1bXUuCgpNeSBwcmVkc3RhdsOtbWUgemhsdWtvdsO6IGFuYWzDvXp1IHByaSBhbmFsw716ZSDDumRham92IHBvY2jDoWR6YWrDumNpY2ggeiBtZXJhbsOtIG1ldGVvcm9sb2dpY2vDvWNoIHByZW1lbm7DvWNoLCBrb25rcsOpdG5lIHRlcGxvdHkgdnpkdWNodSwgYXRtb3Nmw6lyaWNrw6lobyB0bGFrdSwgcsO9Y2hsb3N0aSB2ZXRyYSBhIMO6aHJudSB6csOhxb5vay4gQnVkZW1lIHZ5dcW+w612YcWlIMO6ZGFqZSB6YSByb2sgMjAyMC4gViAqKlRhYi4gMS4qKiB1dsOhZHphbWUgY2Vsw7ogbmFtaSBwb3XFvsOtdmFuw7ogZGF0YWLDoXp1LgoKYGBge3Igc2V0dXAyLCBtZXNzYWdlPUZBTFNFfQpsaWJyYXJ5KGtuaXRyKQpsaWJyYXJ5KGthYmxlRXh0cmEpCmBgYAoKCmBgYHtyfQpybShsaXN0PWxzKCkpCnVkYWplIDwtIHJlYWQuY3N2MigiVGVtcGVyYXR1cmVfMjAyMC5jc3YiLGRlYyA9ICIsIiwgc2VwID0gIjsiLGZpbGVFbmNvZGluZyA9ICJXaW5kb3dzLTEyNTAiLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCgp1ZGFqZTIwMjAgPC0gc3Vic2V0KHVkYWplLCBZZWFyID09IDIwMjApCgp1ZGFqZTIwMjAgPC0gdWRhamUyMDIwWywgYygiWWVhciIsICJUZW1wZXJhdHVyZS4uLkMuIiwgIkFpci5QcmVzc3VyZS4uaFBhLiIsICJXaW5kLlNwZWVkLi5tLnMuIiwgIlByZWNpcGl0YXRpb24uLm1tLiIpXQpyb3duYW1lcyh1ZGFqZTIwMjApIDwtIHNlcV9sZW4obnJvdyh1ZGFqZTIwMjApKQp1ZGFqZTIwMjAgPC0gc3Vic2V0KHVkYWplMjAyMCwgc2VsZWN0ID0gLVllYXIpCmBgYAoKCioqVGFibGUgMS4qKgpgYGB7cn0KdWRhamUyMDIwCmBgYAoKSGllcmFyY2hpY2vDoSB6aGx1a292w6EgYW5hbMO9emEgcHJhY3VqZSBzIG1pZXJhbWkgdnpkaWFsZW5vc3RpIG1lZHppIHBvem9yb3ZhbmlhbWkuIEFieSBib2xpIHRpZXRvIHZ6ZGlhbGVub3N0aSBwb3Jvdm5hdGXEvm7DqSwgamUgcG90cmVibsOpLCBhYnkgdsWhZXRreSBwcmVtZW5uw6kgYm9saSBkZWZpbm92YW7DqSBuYSByb3ZuYWtlasWha8OhbGUuIFBvdcW+w612YW1lIHByaXRvbSB0enYuIHotxaFrw6Fsb3ZhbmllLCBwcmnEjW9tIHRyYW5zZm9ybW92YW7DqSAkeiQgaG9kbm90eSAoc2vDs3JlKSB2eXBvxI3DrXRhbWUgbmFzbGVkb3ZuZQoKJCR6ID0gXGZyYWN7eC1cbXV9e1xzaWdtYX0kJAoKa2RlICRcbXUkIGplIHN0cmVkbsOhIGhvZG5vdGEgYSAkXHNpZ21hJCBqZSDFoXRhbmRhcmRuw6Egb2RjaMO9bGthIHBvem9yb3ZhbsOtICR4JC4gUHJlZHBva2xhZMOhbWUgcHJpdG9tLCDFvmUgc8O6Ym9yIMO6ZGFqb3YgdcW+IG5lb2JzYWh1amUgTkEgaG9kbm90eSwga3RvcsOpIGJvbGkgb8WhZXRyZW7DqSB2IHByZWRjaMOhZHphasO6Y2ljaCBrcm9rb2NoLgoKVG91dG8gb3BlcsOhY2lvdSB6w61za2FtZSDFoWvDoWxvdmFuw6kgcG96b3JvdmFuaWEsIHByacSNb20gaWNoIHJvemxvxb5lbmllIGplIHpuw6F6b3JuZW7DqSBuYXNsZWRvdm5lOgoKCmBgYHtyfQojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyMgMSkgUHLDrXByYXZhIMO6ZGFqb3YgYSBkYXRhLmZyYW1lIHNvIMWhbMOhbG92YW7DvW1pIMO6ZGFqbWkKIyMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09Cgp1ZGFqZV9jb21wbGV0ZSA8LSBuYS5vbWl0KHVkYWplMjAyMCkKdWRhamVfc2NhbGVkIDwtIHNjYWxlKHVkYWplX2NvbXBsZXRlKQpgYGAKCgoqKk9ici4gMS4qKgpgYGB7ciBib3hwbG90cywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRSwgZmlnLnNob3c9J2hvbGQnfQpudW1fdmFycyA8LSBhcy5kYXRhLmZyYW1lKHVkYWplX3NjYWxlZCkKbnVtX3Bsb3RzIDwtIG5jb2wobnVtX3ZhcnMpCgpwYXIobWZyb3cgPSBjKGNlaWxpbmcoc3FydChudW1fcGxvdHMpKSwgY2VpbGluZyhudW1fcGxvdHMgLyBjZWlsaW5nKHNxcnQobnVtX3Bsb3RzKSkpKSkKcGFyKG1hciA9IGMoNCwgNCwgMiwgMSkpCgpmb3IgKGNvbCBpbiBuYW1lcyhudW1fdmFycykpIHsKICBib3hwbG90KG51bV92YXJzW1tjb2xdXSwKICAgICAgICAgIG1haW4gPSBjb2wsCiAgICAgICAgICBjb2wgPSAibGlnaHRibHVlIiwKICAgICAgICAgIGhvcml6b250YWwgPSBUUlVFKQp9CgptdGV4dCgiQm94cGxvdHkgbnVtZXJpY2vDvWNoIHByZW1lbm7DvWNoIChyb2sgMjAyMCkiLCBvdXRlciA9IFRSVUUsIGNleCA9IDEuMywgZm9udCA9IDIpCmBgYAoKVGVudG9rcsOhdCBvZMS+YWhsw6kgaG9kbm90eSBuZXZ5bMO6xI1pbWUsIG5ha2/EvmtvIHByZWRzdGF2dWrDuiBza3V0b8SNbsOpIG5hbWVyYW7DqSBwb3pvcm92YW5pYSBhIHPDuiBzw7rEjWFzxaVvdSByZcOhbG55Y2ggcG9kbWllbm9rIG1lcmFuaWEuCgoxLiBUZXBsb3RhIHZ6ZHVjaHUgKFRlbXBlcmF0dXJl4oCmQy4pOiBSb3pkZWxlbmllIHRlcGzDtHQgamUgcG9tZXJuZSBzeW1ldHJpY2vDqSwgYmV6IGV4dHLDqW1ueWNoIG9kxL5haGzDvWNoIGJvZG92LiBNZWRpw6FuIHNhIG5hY2jDoWR6YSBwcmlibGnFvm5lIHVwcm9zdHJlZCBpbnRlcnZhbHUgYSB0ZXBsb3R5IHPDuiByb3pwdMO9bGVuw6kgcm92bm9tZXJuZS4gSG9kbm90eSBuZXZ5a2F6dWrDuiB2w71yYXpuw6kgYW5vbcOhbGllLgoKMi4gQXRtb3Nmw6lyaWNrw70gdGxhayAoQWlyLlByZXNzdXJlLi5oUGEuKTogQXRtb3Nmw6lyaWNrw70gdGxhayBtw6EgbWllcm5lIGFzeW1ldHJpY2vDqSByb3pkZWxlbmllLCBubyBiZXogb2TEvmFobMO9Y2ggcG96b3JvdmFuw60uIFbDpMSNxaFpbmEgaG9kbsO0dCBzYSBzw7pzdHJlxI91amUgb2tvbG8gbWVkacOhbnUgYSB2YXJpYWJpbGl0YSBqZSBwcmltZXJhbsOhLiDDmmRhamUgcMO0c29iaWEgc3RhYmlsbmUgYSBiZXogZXh0csOpbW55Y2ggdsO9a3l2b3YuCgozLiBSw71jaGxvc8WlIHZldHJhIChXaW5kLlNwZWVkLi5tLnMuKTogUHJpIHLDvWNobG9zdGkgdmV0cmEgc2Egdnlza3l0dWrDuiBkdmEgenJldGXEvm7DqSBvZMS+YWhsw6kgYm9keSDigJMgamVkZW4gdmXEvm1pIG7DrXpreSBhIGplZGVuIHZlxL5taSB2eXNva8O9LiBUaWV0byBob2Rub3R5IHbFoWFrIHByZWRzdGF2dWrDuiByZcOhbG5lIG5hbWVyYW7DqSBzaXR1w6FjaWUgKG5hcHIuIG7DoWhsZSB6b3NpbG5lbmllIHZldHJhIGFsZWJvIGJlenZldHJpZSksIHByZXRvIGljaCBwb25lY2jDoXZhbWUgdiBhbmFsw716ZS4gT3N0YXRuw6kgaG9kbm90eSBzw7ogc8O6c3RyZWRlbsOpIHYgw7p6a29tIGludGVydmFsZSBva29sbyBtZWRpw6FudSwgxI1vIG5hem5hxI11amUgbsOtemt1IHZhcmlhYmlsaXR1IHbDpMSNxaFpbnkgbWVyYW7DrS4KCjQuIMOaaHJuIHpyw6HFvm9rIChQcmVjaXBpdGF0aW9uLi5tbS4pOiBacsOhxb5reSB2eWthenVqw7ogxaFpcsWhaWUgcm96cHR5bCBuZcW+IG9zdGF0bsOpIHByZW1lbm7DqSwgxI1vIGplIHR5cGlja8OpIHByZSB0w7p0byB2ZWxpxI1pbnUsIGtlxI/FvmUgenLDocW+a3kgbcO0xb51IGJ5xaUgdmXEvm1pIHZhcmlhYmlsbsOpLiBSb3pkZWxlbmllIGplIGJleiBvZMS+YWhsw71jaCBib2RvdiBhIG1lZGnDoW4gc2EgbmFjaMOhZHphIHYgc3BvZG5laiDEjWFzdGkgYm94dSwgxI1vIG5hem5hxI11amUsIMW+ZSB2w6TEjcWhaW5hIG1lcmFuw60gbWFsYSByZWxhdMOtdm5lIG7DrXprZSDDumhybnkgenLDocW+b2suCgoKClByaSB6aGx1a292ZWogYW5hbMO9emUgamUgZMO0bGXFvml0w6Ega29yZWxhxI1uw6EgbWF0aWNhIHByZW1lbm7DvWNoLiBWeXNva8OhIGtvcmVsw6FjaWEgenbDvWhvZMWIdWplIHByaSB6aGx1a292ZWogYW5hbMO9emUga29yZWxvdmFuw6kgcHJlbWVubsOpLiBQcmV0byBwcmkga29yZWzDoWNpaSBuYWQgMCw4IGFsZWJvIDAuOSB2eWzDusSNaW1lIGplZG51IHoga29yZWxvdmFuw71jaCBwcmVtZW5uw71jaC4gViAqKlRhYi4gMi4qKiBzYSB2xaFhayB0YWvDoXRvIHZ5c29rw6Ega29yZWzDoWNpYSBuZW5hY2jDoWR6YSwgcHJldG8gc2EgbmVtdXPDrW1lIMSPYWxlaiBzIHByb2Jsw6ltb20gemFvYmVyYcWlLiAKPiBWIHByw61wYWRlLCBhayBtw6FtZSB2w6TEjcWhw60gcG/EjWV0IHbDvXpuYW1uZSBrb3JlbG92YW7DvWNoIHByZW1lbm7DvWNoLCBzYSBvZHBvcsO6xI1hIGkgdHJhbnNmb3Jtw6FjaWEgcG9tb2NvdSBBbmFsw716eSBobGF2bsO9Y2gga29tcG9uZW50b3YgKFByaW5jaXBhbCBDb21wb25lbnQgQW5hbHlzaXMpIAoKKipUYWIuIDIqKgpgYGB7cn0KY29yX21hdCA8LSBjb3IodWRhamVfc2NhbGVkLCB1c2U9InBhaXJ3aXNlLmNvbXBsZXRlLm9icyIpCmNvcl9tYXQgPC0gcm91bmQoY29yX21hdCwyKQpwcmludChjb3JfbWF0KQpgYGAKS2HFvmTDqW11IHBvem9yb3Zhbml1IHpvZHBvdmVkw6EgamVkZW4gcmlhZG9rIG1lcmFuw71jaCBob2Ruw7R0LiBWemRpYWxlbm9zxaUgbWVkemkgcG96b3JvdmFuaWFtaSAkaSQgYSAkaiQgamU6CgokJApkXntpan0gPSBcc3FydHtcc3VtX2sgKHheaV9rIC0geF5qX2spXjJ9CiQkCmtkZSAkeF5pX2skIGplIGhvZG5vdGEgJGskdGVqIHByZW1lbm5laiAoVGVtcGVyYXR1cmXigKZDLiwgQWlyLlByZXNzdXJlLi5oUGEuLCBXaW5kLlNwZWVkLi5tLnMuLCBQcmVjaXBpdGF0aW9uLi5tbS4pIHByZSBwb3pvcm92YW5pZSAkaSQuICBUZW50byB0eXAgdnpkaWFsZW5vc3RpIG5hesO9dmFtZSBhaiBFdWtsaWRvdnNrw6EgdnpkaWFsZW5vc8WlLiBWemRpYWxlbm9zdGkgbWVkemkgamVkbm90bGl2w71taSBwb3pvcm92YW5pYW1pIHNhIHPDumhybm5lIHZ5amFkcnVqw7ogYWogdiBtYXRpY2kgdnpkaWFsZW5vc3TDrSwgxI1vIGplIHYgbmHFoW9tIHByw61wYWRlIHV2ZWRlbsOpIHYgKipUYWIuMy4qKi4gQW5hbMO9em91IHRlanRvIHRhYnXEvmt5IHppc3TDrW1lLCDFvmUgbmFqdsOkxI3FoWlhIHZ6ZGlhbGVub3PFpSBqZSBtZWR6aSBtZXNpYWNtaSA4IGEgMTEsIMSNbyB6bmFtZW7DoSwgxb5lIHRpZXRvIG1lc2lhY2Ugc2EgcG9kxL5hIG1ldGVvcm9sb2dpY2vDvWNoIHVrYXpvdmF0ZcS+b3YgbmFqdmlhYyBsw63FoWlhIOKAkyBtYWrDuiBvZGxpxaFuw7ogdGVwbG90dSwgdGxhaywgenLDocW+a3kgYWogcsO9Y2hsb3PFpSB2ZXRyYS4gTmFvcGFrLCBuYWptZW7FoWlhIHZ6ZGlhbGVub3PFpSBqZSBtZWR6aSBtZXNpYWNtaSA0IGEgNSwga3RvcsOpIHPDuiBzaSB2ZcS+bWkgcG9kb2Juw6kgYSB2eWthenVqw7ogdGFrbWVyIHJvdm5ha8OpIGtsaW1hdGlja8OpIHBvZG1pZW5reS4gVGlldG8gcm96ZGllbHkgbcO0xb5lbWUgdnlzdmV0bGnFpSBzZXrDs25ub3PFpW91IOKAkyBsZXRuw6kgbWVzaWFjZSBzYSBwcmlyb2R6ZW5lIHbDvXJhem5lIG9kbGnFoXVqw7ogb2QgamVzZW5uw71jaCDEjWkgemltbsO9Y2ggbWVzaWFjb3YsIHphdGlhxL4gxI1vIHN1c2VkaWFjZSBqYXJuw6kgbWVzaWFjZSBzw7ogc2kga2xpbWF0aWNreSB2ZcS+bWkgYmzDrXprZS4KCioqVGFiLiAzKioKYGBge3J9CiMjID09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyMgMykgRGlzdGFuY2UgbWF0cml4CiMjID09PT09PT09PT09PT09PT09PT09PT09PT09PT0Kcm93bmFtZXModWRhamVfc2NhbGVkKSA8LSByb3duYW1lcyh1ZGFqZV9jb21wbGV0ZSkKZGlzdF9tYXQgPC0gcm91bmQoZGlzdCh1ZGFqZV9zY2FsZWQsIG1ldGhvZCA9ICJldWNsaWRlYW4iKSwgMikKZGlzdF9tYXQKYGBgCgojIyBQcmluY8OtcCBoaWVyYXJjaGlja8OpaG8gemhsdWtvdmFuaWEgKFdhcmRvdmEgbWV0w7NkYSkKClpobHVrb3ZhbmllIHYgcHLDrXBhZGUgV2FyZG92ZWogbWV0w7NkeSBwcmViaWVoYSB6ZG9sYSBzbWVyb20gbmFob3IsIHQuai4gemHEjcOtbmFtZSBzIGplZG5vxI1sZW5uw71taSBrbGFzdHJhbWksIGt0b3LDqSBwb3N0dXBuZSB6bHXEjXVqZW1lLiBUw6F0byBtZXTDs2RhIHBhdHLDrSB0ZWRhIG1lZHppIGFnbG9tZXJhdMOtdm5lIGhpZXJhcmNoaWNrw6kgbWV0w7NkeS4gTWluaW1hbGl6dWplIG7DoXJhc3Qgdm7DunRvcm5laiB2YXJpYWJpbGl0eSBwcmkgc3BvamVuw60gZHZvY2gga2xhc3Ryb3YsIHByacSNb20gdnl1xb7DrXZhIG5hc2xlZG92bsOpIHbDvXBvxI10eToKCldhcmRvdsOhIG1ldMOzZGEgbWluaW1hbGl6dWplIHN1bXUgxaF0dm9yY292IGNow71iIChFcnJvciBzdW0gb2YgU3F1YXJlcyAtIEVTUykKCiQkRVNTKEMpID0gXHN1bV97aSBcaW4gQ30gXGxWZXJ0IHhfaSAtIFxiYXJ7eH1fQyBcclZlcnReMiQkCmtkZSAkQyQgamUgenZhxb5vdmFuw70ga2xhc3RlciAoemhsdWspLiBWIGthxb5kb20ga3Jva3Ugemx1xI1vdmFuaWEgZHZvY2gga2xhc3Rlcm92LCBXYXJkb3ZhIG1ldMOzZGEgaMS+YWTDoSBtaW5pbcOhbG55IHByw61yYXN0b2sgc3VteSDFoXR2b3Jjb3YgY2jDvWIgKCRcRGVsdGEgRVNTJCksIHByacSNb20KCiQkXERlbHRhIEVTUyA9IEVTUyhBIFxjdXAgQikgLSBFU1MoQSkgLSBFU1MoQikkJApEdm9qaWNhIHpobHVrb3YsIGt0b3LDqSB0ZWp0byBwb2RtaWVua2UgbyBtaW5pbWFsaXrDoWNpaSB2eWhvdnVqZSwgamUgbsOhc2xlZG5lIHpsw7rEjWVuw6EgYSBwcmVjaMOhZHphIHNhIGsgxI9hbMWhaWVtdSBra3Jva3UuIFRvIHNwcmF2aWRsYSB2ZWRpZSBrIHZ5dHbDoXJhbml1IGhvbW9nw6lubnljaCB6aGx1a292LCBwcmnEjW9tIG5lZG9jaMOhZHphIGsgb2R0cmjDoXZhbml1IG9kxL5haGzDvWNoIGhvZG7DtHQgdGFrLCBha28gcHJpIGluw71jaCB6aGx1a292YWPDrWNoIG1ldMOzZGFjaC4KCgoqKk9ici4gMi4qKiBIaWVyYXJjaGlja8OpIHpobHVrb3ZhbmllIC0gZGVuZG9ncmFtLiDEjGVydmVuw6EgxI1pYXJhIHVyxI11amUgcmV6IGRlZmludWrDumNpIHRyaSBrbGFzdHJlLgpgYGB7cn0KIyMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PQojIyA0KSBIaWVyYXJjaGljYWwga2xhc3RlcmluZwojIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09CgpoYyA8LSBoY2x1c3QoZGlzdF9tYXQsIG1ldGhvZCA9ICJ3YXJkLkQyIikKCnBsb3QoaGMsIGxhYmVscyA9IHJvd25hbWVzKHVkYWplX3NjYWxlZCksCiAgICAgbWFpbiA9ICJIaWVyYXJjaGljYWwga2xhc3RlcmluZyBvZiBvYnNlcnZhdGlvbnMgKFdhcmQuRDIpIiwKICAgICB4bGFiID0gIiIsIHN1YiA9ICIiKQoKayA8LSAzCmhfY3V0IDwtIGhjJGhlaWdodFtsZW5ndGgoaGMkaGVpZ2h0KSAtIChrIC0gMSldCmFibGluZShoID0gaF9jdXQsIGNvbCA9ICJyZWQiLCBsd2QgPSAyLCBsdHkgPSAyKQoKa2xhc3Rlcl9tZW1iZXJzaGlwIDwtIGN1dHJlZShoYywgayA9IGspCgp1ZGFqZV9rbGFzdGVycyA8LSBkYXRhLmZyYW1lKAogIE9ic2VydmF0aW9uID0gcm93bmFtZXModWRhamVfY29tcGxldGUpLAogIHVkYWplX2NvbXBsZXRlLAogIGtsYXN0ZXIgPSBmYWN0b3Ioa2xhc3Rlcl9tZW1iZXJzaGlwKQopCmBgYAoKCioqVGFiLjQuKiogIFByw61zbHXFoW5vc8WlIHBvem9yb3ZhbsOtIGRvIGtsYXN0cm92LgpgYGB7cn0KZGF0YV9wcmFjIDwtIGRhdGEuZnJhbWUoIE9ic2VydmF0aW9uID0gcm93bmFtZXModWRhamVfa2xhc3RlcnMpLAogIGtsYXN0ZXIgICAgID0gdWRhamVfa2xhc3RlcnMka2xhc3RlcikKY29sbmFtZXMoZGF0YV9wcmFjKSA8LSBjKCJPYnNlcnZhdGlvbiIsImtsYXN0ZXIiKQpkYXRhX3ByYWMKYGBgCgpOYSB6w6FrbGFkZSB2eWtvbmFuZWogemhsdWtvdmVqIGFuYWzDvXp5IGJvbGkgbWVzaWFjZSByb3pkZWxlbsOpIGRvIHRyb2NoIGtsYXN0cm92LgpaIHbDvXNsZWRuZWogdGFidcS+a3kga2xhc3Ryb3Ygdmlkw61tZSB0b3RvIHJvemRlbGVuaWU6CmtsYXN0ZXIgMTogbWVzaWFjZSAxLCA0LCA1LCA3LCAxMQprbGFzdGVyIDI6IG1lc2lhY2UgMiwgNiwgOCwgOQprbGFzdGVyIDM6IG1lc2lhY2UgMywgMTAsIDEyLgpUaWV0byBrbGFzdHJlIHJlcHJlemVudHVqw7ogbWVzaWFjZSwga3RvcsOpIG1hasO6IHBvZG9ibsOpIGhvZG5vdHkgdGVwbG90eSwgdGxha3UsIHLDvWNobG9zdGkgdmV0cmEgYSB6csOhxb5vay4KS2xhc3RlciAxIHpkcnXFvnVqZSBtZXNpYWNlLCBrdG9yw6kgbWFqw7ogcG9kb2Juw6kgYSBtaWVybmUgc3RhYmlsbsOpIG1ldGVvcm9sb2dpY2vDqSBwb2RtaWVua3kg4oCUIGlkZSBuYWptw6QgbyBtZXNpYWNlIHMgcG9kb2Juw71taSB0ZXBsb3RhbWkgYSByw71jaGxvc8Wlb3UgdmV0cmEuCktsYXN0ZXIgMiB6YWjFlcWIYSBtZXNpYWNlIHMgdnnFocWhb3UgcG9kb2Jub3PFpW91IG5ham3DpCB2IMO6cm92bmkgenLDocW+b2sgYSB0bGFrdSB2emR1Y2h1LCBwcmnEjW9tIHRpZXRvIG1lc2lhY2Ugc2Egb2Qga2xhc3Ryb3YgMSBhIDMgb2RsacWhdWrDuiBwcsOhdmUga29tYmluw6FjaW91IHTDvWNodG8gdWthem92YXRlxL5vdi4KS2xhc3RlciAzIG9ic2FodWplIG1lc2lhY2UsIGt0b3LDqSBtYWrDuiBvZGxpxaFuw70gcHJvZmlsIOKAlCDEjWkgdcW+IHogaMS+YWRpc2thIHRlcGxvdHkgYWxlYm8genLDocW+b2sg4oCUIGEgcHJldG8gc2Egb2QgenZ5xaFuw71jaCBtZXNpYWNvdiBqYXNuZSBvZGRlxL51asO6LgpWw71zbGVka29tIGplIHRlZGEgbG9naWNrw6kgcm96ZGVsZW5pZSBtZXNpYWNvdiBkbyB0cm9jaCBza3Vww61uLCBrZGUga2HFvmTDvSBrbGFzdGVyIHNww6FqYSBtZXNpYWNlIHMgcG9kb2Juw71taSBtZXRlb3JvbG9naWNrw71taSBjaGFyYWt0ZXJpc3Rpa2FtaS4KCiMjIERlc2tyaXB0w612bmUgxaF0YXRpc3Rpa3kgdsO9c2xlZGtvdgoKTmEgesOha2xhZGUgKipUYWIuIDUqKiBtb8W+bm8gdnlob2Rub3RpxaUgc2VwYXJhxI1uw7ogc2lsdSBqZWRub3RsaXbDvWNoIHByZW1lbm7DvWNoIHZvIHZ5dHZvcmVuw71jaCBrbGFzdHJvY2ggbmFzbGVkb3ZuZToKUHJlbWVubsOhIFByZWNpcGl0YXRpb24uLm1tLiBtw6EgbmFqdnnFocWhw60gcG9kaWVsIG1lZHppLWtsYXN0cm92w6lobyByb3pwdHlsdSAoUHJvcF9CZXR3ZWVuIOKJiCAwLjgwKSwgxI1vIHpuYW1lbsOhLCDFvmUgdmXEvm1pIGRvYnJlIG9kbGnFoXVqZSBqZWRub3RsaXbDqSBrbGFzdHJlIOKAkyB6csOhxb5reSBzw7ogdGVkYSBqZWRuw71tIHogbmFqc2lsbmVqxaHDrWNoIGZha3Rvcm92LCBwb2TEvmEga3RvcsO9Y2ggc2EgbWVzaWFjZSBwcmlyb2R6ZW5lIHpvc2t1cHVqw7ouClBvZG9ibmUgYWogV2luZC5TcGVlZC4ubS5zLiB2eWthenVqZSB2eXNva8O6IGhvZG5vdHUgUHJvcF9CZXR3ZWVuICjiiYggMC42NSksIMSNbyBuYXpuYcSNdWplLCDFvmUgcsO9Y2hsb3PFpSB2ZXRyYSBzYSBtZWR6aSBrbGFzdHJhbWkgdsO9em5hbW5lIGzDrcWhaSBhIHByZWRzdGF2dWplIGRvYnLDvSBzZXBhcsOhdG9yLgpQcmVtZW5uw6EgQWlyLlByZXNzdXJlLi5oUGEuIG3DoSBzdHJlZG7DuiBzZXBhcmHEjW7DuiBzY2hvcG5vc8WlICjiiYggMC41OCksIMSNbyB6bmFtZW7DoSwgxb5lIHRsYWsgdnpkdWNodSBwcmlzcGlldmEgayBvZGxpxaFvdmFuaXUga2xhc3Ryb3YsIGFsZSB1xb4gbWVuZWogdsO9cmF6bmUgYWtvIHByZWRjaMOhZHphasO6Y2UgZHZlIHByZW1lbm7DqS4KTmFqc2xhYsWhw61tIHNlcGFyw6F0b3JvbSBqZSBUZW1wZXJhdHVyZS4uLkMuLCBrdG9yZWogcG9kaWVsIG1lZHppLWtsYXN0cm92w6lobyByb3pwdHlsdSBqZSBuYWpuacW+xaHDrSAo4omIIDAuMjkpLiBabmFtZW7DoSB0bywgxb5lIHRlcGxvdGEgc2Egc8OtY2UgbWVkemkgbWVzaWFjbWkgbMOtxaFpLCBhbGUgbmllIGRvc3RhdG/EjW5lIG5hIHRvLCBhYnkgesOhc2FkbmUgcHJpc3BpZXZhbGEgayB2eXR2w6FyYW5pdSBvZGxpxaFuw71jaCBrbGFzdHJvdi4KTmFqdmlhYyBrIHJvemzDrcWhZW5pdSBrbGFzdHJvdiBwcmlzcGlldmFqw7ogenLDocW+a3kgYSByw71jaGxvc8WlIHZldHJhLCB6YXRpYcS+IMSNbyB0ZXBsb3RhIG3DoSB2IHRvbXRvIHR5cGUgYW5hbMO9enkgaWJhIHNsYWLDvSBzZXBhcmHEjW7DvSBlZmVrdC4KCgoqKlRhYi4gNS4qKiBWeXN2ZXRsZW5pZSB2bsO6dHJva2xhc3Ryb3ZlaiB2YXJpYWJpbGl0eSB6IGjEvmFkaXNrYSBqZWRub3RsaXbDvWNoIHByZW1lbm7DvWNoCgpgYGB7cn0KIyMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PQojIyA1KSBWYXJpYWJpbGl0eSBtZWFzdXJlcwojIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09Cgpzc3EgPC0gZnVuY3Rpb24oeCwgbSkgc3VtKCh4IC0gbSleMikKCnZhcl9uYW1lcyA8LSBjb2xuYW1lcyh1ZGFqZV9zY2FsZWQpCgpUU1MgPC0gc2FwcGx5KHZhcl9uYW1lcywgZnVuY3Rpb24odikgc3NxKHVkYWplX3NjYWxlZFssIHZdLCBtZWFuKHVkYWplX3NjYWxlZFssIHZdKSkpCgpXU1MgPC0gc2FwcGx5KHZhcl9uYW1lcywgZnVuY3Rpb24odikgewogIHggPC0gdWRhamVfc2NhbGVkWywgdl0KICB0YXBwbHkoeCwga2xhc3Rlcl9tZW1iZXJzaGlwLCBmdW5jdGlvbih6KSBzc3EoeiwgbWVhbih6KSkpIHw+IHN1bSgpCn0pCgpCU1MgPC0gVFNTIC0gV1NTCgpzc190YWJsZSA8LSBkYXRhLmZyYW1lKAogIFZhcmlhYmxlID0gdmFyX25hbWVzLAogIFRTUyA9IFRTUywKICBXU1MgPSBXU1MsCiAgQlNTID0gQlNTLAogIFByb3BfQmV0d2VlbiA9IEJTUyAvIFRTUwopCgpzc190YWJsZQpgYGAKCmBgYHtyfQphdHRhY2godWRhamUyMDIwKQp1ZGFqZTIwMjAgPC0gZGF0YS5mcmFtZShjYmluZCh1ZGFqZTIwMjAsdWRhamVfa2xhc3RlcnMka2xhc3RlcikpCmNvbG5hbWVzKHVkYWplMjAyMCkgPC0gYygiVGVtcGVyYXR1cmVfQyIsIkFpclByZXNzdXJlX2hQYSIsIldpbmRTcGVlZF9tcyIsIlByZWNpcGl0YXRpb25fbW0iLCJrbGFzdGVyIikKYGBgCgoKKipUYWIuIDYuKiogQ2VudHJvaWR5IC0gcHJpZW1lcm7DqSBob2Rub3R5IHNsZWRvdmFuw71jaCBwcmVtZW5uw71jaApgYGB7cn0KbGlicmFyeShkcGx5cikKCmRlc2NyaXB0aXZlcyA8LSB1ZGFqZV9rbGFzdGVycyAlPiUKICBncm91cF9ieShrbGFzdGVyKSAlPiUKICBzdW1tYXJpc2UoCiAgICBhY3Jvc3MoCiAgICAgIC5jb2xzID0gd2hlcmUoaXMubnVtZXJpYyksCiAgICAgIC5mbnMgPSBsaXN0KAogICAgICAgIG1lYW4gPSB+bWVhbigueCwgbmEucm0gPSBUUlVFKQogICAgICApLAogICAgICAubmFtZXMgPSAiey5jb2x9X3suZm59IgogICAgKQogICkKZGVzY3JpcHRpdmVzCgpgYGAKClBydsO9IGtsYXN0ZXIgbcOhIG1pZXJuZSBuYWRwcmllbWVybsO6IHRlcGxvdHUsIG5hanZ5xaHFocOtIHByaWVtZXJuw70gYXRtb3Nmw6lyaWNrw70gdGxhayAo4omIIDk5My44IGhQYSkgYSBzdHJlZG7DuiDDunJvdmXFiCByw71jaGxvc3RpIHZldHJhLgpEcnVow70ga2xhc3RlciBqZSBjaGFyYWt0ZXJpem92YW7DvSBuYWp2ecWhxaFvdSBwcmllbWVybm91IHRlcGxvdG91ICjiiYggMTQuOCDCsEMpLCBuw616a3ltIHRsYWtvbSBhIG5ham5pxb7FoW91IHLDvWNobG9zxaVvdSB2ZXRyYSBzcG9tZWR6aSB2xaFldGvDvWNoIGtsYXN0cm92LgpUcmV0w60ga2xhc3RlciBtw6EgbmFqbmnFvsWhaWUgcHJpZW1lcm7DqSB0ZXBsb3R5LCBuYWpuacW+xaHDrSBhdG1vc2bDqXJpY2vDvSB0bGFrIGEgesOhcm92ZcWIIG5hanZ5xaHFoWl1IHByaWVtZXJuw7ogcsO9Y2hsb3PFpSB2ZXRyYSwgxI1vIG5hem5hxI11amUgb2RsacWhbsOpIHBvdmV0ZXJub3N0bsOpIHBvZG1pZW5reSBvcHJvdGkgenZ5xaFuw71tIGR2b20ga2xhc3Ryb20uClogdG9obyB2eXBsw712YSwgxb5lIG5hamTDtGxlxb5pdGVqxaHDrW0gcm96bGnFoW92YWPDrW0gZmFrdG9yb20gbWVkemkga2xhc3RyYW1pIGplIHRlcGxvdGEsIHphdGlhxL4gxI1vIHRsYWsgdnpkdWNodSBhIHLDvWNobG9zxaUgdmV0cmEgZG9wxLrFiGFqw7ogY2hhcmFrdGVyaXN0aWt1IGplZG5vdGxpdsO9Y2ggc2t1cMOtbi4gCgojIyBaw6F2ZXIKClByZWRsb8W+ZW7DoSBhbmFsw716YSBzYSB6YW9iZXLDoSB6b3NrdXBvdmFuw61tIGplZG5vdGxpdsO9Y2ggbWVzaWFjb3Ygcm9rYSBuYSB6w6FrbGFkZSBpY2ggcHJpZW1lcm7DvWNoIG1ldGVvcm9sb2dpY2vDvWNoIGNoYXJha3RlcmlzdMOtayDigJMga29ua3LDqXRuZSB0ZXBsb3R5IHZ6ZHVjaHUsIGF0bW9zZsOpcmlja8OpaG8gdGxha3UsIHLDvWNobG9zdGkgdmV0cmEgYSBtbm/FvnN0dmEgenLDocW+b2suIE5hIHrDoWtsYWRlIHTDvWNodG8gcHJlbWVubsO9Y2ggYm9saSBtZXNpYWNlIHJvemRlbGVuw6kgZG8gdHJvY2gga2xhc3Ryb3YsIHByacSNb20ga2HFvmTDvSBrbGFzdGVyIHByZWRzdGF2dWplIG9kbGnFoW7DvSB0eXAgcG92ZXRlcm5vc3Ruw71jaCBwb2RtaWVub2suClRha3RvIHrDrXNrYW7DoSBrbGFzaWZpa8OhY2lhIHVtb8W+xYh1amUgbGVwxaFpZSBwb2Nob3BpxaUgc2V6w7NubmUgcm96ZGllbHkgdiBwb8SNYXPDrSwgaWRlbnRpZmlrb3ZhxaUgbWVzaWFjZSBzIHBvZG9ibsO9bWkga2xpbWF0aWNrw71taSBwcm9maWxtaSBhIG3DtMW+ZSBzbMO6xb5pxaUgYWtvIHBvZGtsYWQgcHJpIHBsw6Fub3ZhbsOtIGFrdGl2w610IHrDoXZpc2zDvWNoIG9kIHBvxI1hc2lhIOKAkyBuYXByw61rbGFkIHByaSBlbmVyZ2V0aWNrb20gcGzDoW5vdmFuw60sIHBvxL5ub2hvc3BvZMOhcnN0dmUgxI1pIGhvZG5vdGVuw60ga2xpbWF0aWNrw71jaCB0cmVuZG92Lg==