Ú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 regiónov z hľadiska
socioekonomických ukazovateľov, hodnotení zdravotných rizík,
klasifikácii biologických vzoriek či v geoinformatike pri zoskupovaní
priestorových objektov. Jej výhodou je, že pracuje s viacerými
premennými naraz a dokáže odhaliť vzory, ktoré by pri samostatnom
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.
V tejto práci predstavíme zhlukovú analýzu pri analýze údajov o
počte obyvateľov okresov Slovenskej republiky z
hľadiska ich mesačného vývoja v roku 2024. Pracujeme s
databázou, ktorá obsahuje okres, obec a počet obyvateľov v jednotlivých
mesiacoch roku 2024 (od januára po december). Údaje najskôr agregujeme
na úroveň okresov a následne náhodne vyberieme 10
okresov, na ktorých demonštrujeme celý postup hierarchického
zhlukovania. V Tab. 1 uvádzame celú nami používanú
databázu 10 okresov.
library(knitr)
library(kableExtra)
library(dplyr)
rm(list = ls())
# Načítanie údajov z databázy
# Predpoklad: súbor "Databaza.csv" je v rovnakom priečinku ako tento Rmd súbor.
# Používame separátor ";" keďže ide o CSV v európskom formáte.
udaje_raw <- read.csv("Databaza.csv",
sep = ";",
stringsAsFactors = FALSE)
# R automaticky dopĺňa X pred názvy stĺpcov začínajúcich číslom,
# preto ho odstránime, aby sme mali späť názvy 2024M01, 2024M02, ...
names(udaje_raw) <- sub("^X", "", names(udaje_raw))
# Premenovanie mesiacov na bežné názvy
mesiace_map <- c(
"2024M01" = "Január",
"2024M02" = "Február",
"2024M03" = "Marec",
"2024M04" = "Apríl",
"2024M05" = "Máj",
"2024M06" = "Jún",
"2024M07" = "Júl",
"2024M08" = "August",
"2024M09" = "September",
"2024M10" = "Október",
"2024M11" = "November",
"2024M12" = "December"
)
# premenujeme len stĺpce s mesiacmi
names(udaje_raw)[match(names(mesiace_map), names(udaje_raw))] <- mesiace_map
month_names <- unname(mesiace_map) # v poradí január–december
# Agregácia na úroveň okresov (súčet obyvateľov za všetky obce)
udaje_okresy <- udaje_raw %>%
group_by(Okres) %>%
summarise(across(all_of(month_names), sum, na.rm = TRUE))
# Náhodný výber 10 okresov
set.seed(123) # pre reprodukovateľnosť
okresy_vyber <- sample(unique(udaje_okresy$Okres), 10)
udaje_vyber <- subset(udaje_okresy, Okres %in% okresy_vyber)
# Usporiadanie podľa názvu okresu (nie je nutné, len pre prehľadnosť)
udaje_vyber <- udaje_vyber[order(udaje_vyber$Okres), ]
Tab. 1. Vybrané okresy a ich počet obyvateľov v
jednotlivých mesiacoch roku 2024
kable(udaje_vyber, caption = "Vybrané okresy a ich počet obyvateľov (Január–December 2024)") %>%
kable_styling(full_width = FALSE)
Vybrané okresy a ich počet obyvateľov (Január–December 2024)
| Okres |
Január |
Február |
Marec |
Apríl |
Máj |
Jún |
Júl |
August |
September |
Október |
November |
December |
| Okres Bratislava I |
47375 |
47391 |
47408 |
47411 |
47484 |
47503 |
47488 |
47525 |
47564 |
47565 |
47632 |
47634 |
| Okres Bratislava II |
125980 |
125975 |
126022 |
126047 |
126178 |
126245 |
126393 |
126435 |
126457 |
126481 |
126522 |
126632 |
| Okres Bratislava III |
77594 |
77678 |
77718 |
77736 |
77746 |
77817 |
77872 |
77912 |
77936 |
77918 |
77998 |
78070 |
| Okres Bratislava IV |
105043 |
105011 |
104992 |
105029 |
105039 |
105064 |
105066 |
105076 |
105140 |
105120 |
105165 |
105118 |
| Okres Bratislava V |
122048 |
122009 |
121976 |
121993 |
121936 |
121934 |
121918 |
121871 |
121869 |
121864 |
121836 |
121885 |
| Okres Dunajská Streda |
127025 |
127173 |
127217 |
127264 |
127307 |
127397 |
127460 |
127512 |
127576 |
127692 |
127772 |
127803 |
| Okres Hlohovec |
27236 |
27182 |
27133 |
27125 |
27084 |
27056 |
27027 |
27004 |
26961 |
26921 |
26904 |
26876 |
| Okres Malacky |
79689 |
79717 |
79761 |
79782 |
79849 |
79899 |
79923 |
79930 |
79917 |
79935 |
79961 |
79994 |
| Okres Pezinok |
69953 |
69936 |
69926 |
69945 |
69972 |
70006 |
70021 |
70070 |
70055 |
70067 |
70099 |
70105 |
| Okres Senec |
105075 |
105281 |
105448 |
105640 |
105848 |
106062 |
106188 |
106382 |
106488 |
106649 |
106781 |
106872 |
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. V našom prípade
sú premennými mesačné počty obyvateľov (Január až
December). Použijeme 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 škálovanými údajmi
## ======================================================
# Vyberieme len numerické stĺpce s mesačnými údajmi (Január–December)
udaje10 <- udaje_vyber[, month_names]
# Názvy riadkov nastavíme na názvy okresov
rownames(udaje10) <- udaje_vyber$Okres
# Odstránime prípadné chýbajúce hodnoty
udaje_complete <- na.omit(udaje10)
# Škálovanie (z-škóre) jednotlivých premenných
udaje_scaled <- scale(udaje_complete)
Obr. 1. Boxploty škálovaných numerických premenných
(mesiace roku 2024)
num_vars <- as.data.frame(udaje_scaled)
num_plots <- ncol(num_vars)
# 12 mesiacov = 3x4 mriežka
par(mfrow = c(3, 4),
mar = c(4, 4, 2, 1),
oma = c(0, 0, 3, 0))
for (col in names(num_vars)) {
boxplot(num_vars[[col]],
main = col,
col = "lightblue",
horizontal = TRUE)
}
mtext("Boxploty numerických premenných (mesiace roku 2024)",
outer = TRUE, cex = 1.3, font = 2)

Tentokrát odľahlé hodnoty nevylučujeme, nakoľko definujú konkrétny
okres. Napríklad okres s výrazne vyšším počtom obyvateľov je z pohľadu
analýzy zaujímavý a chceme ho v dátach zachovať.
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 často vylúčime jednu z korelovaných
premenných. V Tab. 2 ukážeme korelačnú maticu medzi
jednotlivými mesiacmi roku 2024. Ak by sa medzi mesiacmi vyskytovali
veľmi vysoké korelácie, bolo by možné uvažovať napríklad o znížení
dimenzie pomocou analýzy hlavných komponentov.
V prípade, ak máme väčší počet významne korelovaných premenných, sa
odporúča transformácia pomocou Analýzy hlavných komponentov (Principal
Component Analysis).
Tab. 2. Korelačná matica medzi mesiacmi roku
2024
cor_mat <- cor(udaje_scaled, use = "pairwise.complete.obs")
cor_mat <- round(cor_mat, 2)
print(cor_mat)
Január Február Marec Apríl Máj Jún Júl August September Október November December
Január 1 1 1 1 1 1 1 1 1 1 1 1
Február 1 1 1 1 1 1 1 1 1 1 1 1
Marec 1 1 1 1 1 1 1 1 1 1 1 1
Apríl 1 1 1 1 1 1 1 1 1 1 1 1
Máj 1 1 1 1 1 1 1 1 1 1 1 1
Jún 1 1 1 1 1 1 1 1 1 1 1 1
Júl 1 1 1 1 1 1 1 1 1 1 1 1
August 1 1 1 1 1 1 1 1 1 1 1 1
September 1 1 1 1 1 1 1 1 1 1 1 1
Október 1 1 1 1 1 1 1 1 1 1 1 1
November 1 1 1 1 1 1 1 1 1 1 1 1
December 1 1 1 1 1 1 1 1 1 1 1 1
Každému okresu zodpovedá jeden riadok pozorovaní. Vzdialenosť medzi
okresmi \(i\) a \(j\) je:
\[
d^{ij} = \sqrt{\sum_k (x^i_k - x^j_k)^2}
\]
kde \(x^i_k\) je \(k\)-tá premenná vstupujúca do výpočtu (v
našom prípade ide o mesačné počty obyvateľov: Január, Február, …,
December) okresu \(i\). Tento typ
vzdialenosti nazývame Euklidovská vzdialenosť. Vzdialenosti medzi
jednotlivými okresmi sa súhrnne vyjadrujú aj v matici vzdialenosti, čo
je v našom prípade uvedené v Tab. 3. Analýzou tejto
tabuľky môžeme identifikovať dvojice okresov, ktoré sú si podľa priebehu
počtu obyvateľov v roku 2024 veľmi podobné (malá vzdialenosť), resp.
veľmi odlišné (veľká vzdialenosť).
Tab. 3. Euklidovská matica vzdialeností medzi
okresmi
## ============================
## 3) Distance matrix
## ============================
# ponecháme pôvodné názvy okresov ako rownames
dist_mat <- round(dist(udaje_scaled, method = "euclidean"), 2)
dist_mat
1 2 3 4 5 6 7 8 9
2 7.94
3 3.06 4.88
4 5.80 2.14 2.75
5 7.50 0.44 4.44 1.70
6 8.06 0.12 5.00 2.25 0.56
7 2.06 10.00 5.12 7.86 9.56 10.12
8 3.26 4.68 0.20 2.54 4.24 4.79 5.32
9 2.27 5.67 0.79 3.53 5.23 5.79 4.33 0.99
10 5.90 2.04 2.85 0.11 1.60 2.15 7.96 2.64 3.63
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í medzi aglomeratívne hierarchické metódy. Minimalizuje
nárast vnútornej variability pri spojení dvoch klastrov, pričom využíva
nasledovné výpočty:
Wardova 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 klastrov 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 kroku. To spravidla vedie k
vytváraniu relatívne homogénnych zhlukov, pričom nedochádza k
odtrhávaniu odľahlých hodnôt tak, ako pri niektorých iných zhlukovacích
metódach.
Obr. 2. Hierarchické zhlukovanie – dendrogram.
Červená čiara určuje rez definujúci tri klastre.
## ============================
## 4) Hierarchical clustering
## ============================
hc <- hclust(dist_mat, method = "ward.D2")
plot(hc, labels = rownames(udaje_scaled),
main = "Hierarchical clustering of districts (Ward.D2)",
xlab = "", sub = "")
# počet klastrov
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(
Okres = rownames(udaje_complete),
udaje_complete,
klaster = factor(klaster_membership)
)
Tab. 4. Príslušnosť okresov do klastrov
data_prac <- data.frame(
Okres = udaje_klasters$Okres,
klaster = udaje_klasters$klaster
)
data_prac
Vykonaná klastrová analýza klasifikuje okresy do troch klastrov.
Zloženie klastrov odráža jednak úroveň počtu obyvateľov, ale aj
podobnosť priebehu počas jednotlivých mesiacov roku 2024. V jednom
klastri môžeme nájsť skôr väčšie okresy s podobným počtom
obyvateľov, v inom skôr menšie okresy,
prípadne okresy s odlišnou dynamikou (mierny nárast alebo pokles).
Interpretácia konkrétnych klastrov závisí od výsledných hodnôt a je
možné ju doplniť napríklad mapovým zobrazením alebo porovnaním s ďalšími
charakteristikami okresov.
Deskriptívne štatistiky výsledkov
Na základe Tab. 5 môžeme posúdiť, ako dobre
jednotlivé mesiace (premenné) prispievajú k rozlišovaniu medzi
klastrami. Vnútroklastrová variabilita (WSS) vyjadruje, nakoľko sú
okresy v rámci jedného klastra podobné, zatiaľ čo medzi-klastrová
variabilita (BSS) vyjadruje rozdiely medzi klastrami. Podiel
medzi-klastrovej variability na celkovej variabilite (stĺpec
Prop_Between) nám ukazuje, do akej miery daný mesiac
prispieva k separácii klastrov.
Tab. 5. Vysvetlenie vnútroklastrovej variability z
hľadiska jednotlivých premenných (mesiacov)
## ============================
## 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
Vyššie hodnoty podielu Prop_Between znamenajú, že daný
mesiac výraznejšie odlišuje jednotlivé klastre. Mesiace s nižšou
hodnotou tohto podielu predstavujú obdobia, v ktorých sú okresy medzi
sebou relatívne podobné a daná premenná nie je takým silným
„separátorom“.
Na záver sa pozrieme na centroidy, t. j. priemerné hodnoty
sledovaných premenných v jednotlivých klastroch. Tie nám poskytujú
prehľadný opis typického okresu v každom klastri.
# Vytvoríme dátový rámec s pôvodnými (neškálovanými) hodnotami a priradeným klastrom
udaje_complete_klaster <- data.frame(
Okres = rownames(udaje_complete),
udaje_complete,
klaster = factor(klaster_membership)
)
Tab. 6. Centroidy – priemerné počty obyvateľov v
jednotlivých mesiacoch podľa klastrov
descriptives <- udaje_complete_klaster %>%
group_by(klaster) %>%
summarise(
across(
.cols = where(is.numeric),
.fns = list(mean = ~ mean(.x, na.rm = TRUE)),
.names = "{.col}_{.fn}"
)
)
descriptives
Pri pohľade na centroidy vidíme rozdiely v priemernom počte
obyvateľov v jednotlivých klastroch. Jeden klaster môže zahŕňať skôr
väčšie okresy s vyššími priemernými hodnotami počas celého roka, iný
skôr menšie okresy s nižšími hodnotami. Zaujímavé sú aj prípadné
rozdiely medzi mesiacmi – napríklad mierne nárasty alebo poklesy v
niektorých obdobiach roka, ktoré môžu súvisieť s migráciou, sezónnymi
vplyvmi alebo administratívnymi zmenami (napr. „prihlásenie“
obyvateľov).
LS0tCnRpdGxlOiAiWmhsdWtvdsOhIGFuYWzDvXphIChDbHVzdGVyIEFuYWx5c2lzKSIKYXV0aG9yOiAiRmlsaXAgSnVya8OhxI1layAoemEgcG9tb2NpIENoYXRHUFQpIgpvdXRwdXQ6IAogIGh0bWxfbm90ZWJvb2s6CiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogdHJ1ZQogICAgdGhlbWU6IHVuaXRlZAogICAgaGlnaGxpZ2h0OiB0YW5nbwplZGl0b3Jfb3B0aW9uczogCiAgbWFya2Rvd246IAogICAgd3JhcDogNzIKLS0tCgotLS0tLS0tLS0KCmBgYHtyIHNldHVwMSwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KAogIGVjaG8gICAgPSBUUlVFLCAgICAjIHpvYnJhenVqIGvDs2QKICBtZXNzYWdlID0gVFJVRSwgICAgIyB6b2JyYXp1aiBzcHLDoXZ5IHogYmFsw61rb3YKICB3YXJuaW5nID0gRkFMU0UsICAgIyBwb3Rsw6HEjWFqIHdhcm5pbmdzCiAgZXJyb3IgICA9IEZBTFNFICAgICMgcG90bMOhxI1haiBjaHlieQopCmBgYAoKIyMgw5p2b2QKCktsYXN0cm92w6EgKHpobHVrb3bDoSkgYW5hbMO9emEgcGF0csOtIG1lZHppIG5hanBvdcW+w612YW5lasWhaWUgbWV0w7NkeSBleHBsb3JhdMOtdm5laiDFoXRhdGlzdGlreS4gViBwcmF4aSBzYSB2eXXFvsOtdmEgdsWhYWRlIHRhbSwga2RlIGplIHBvdHJlYm7DqSByb3pkZWxpxaUgcG96b3JvdmFuaWEgZG8gaG9tb2fDqW5ueWNoIGNlbGtvdiDigJMgbmFwcsOta2xhZCBwcmkgc2VnbWVudMOhY2lpIHrDoWthem7DrWtvdiB2IG1hcmtldGluZ3UsIGlkZW50aWZpa8OhY2lpIHBvZG9ibsO9Y2ggcmVnacOzbm92IHogaMS+YWRpc2thIHNvY2lvZWtvbm9taWNrw71jaCB1a2F6b3ZhdGXEvm92LCBob2Rub3RlbsOtIHpkcmF2b3Ruw71jaCByaXrDrWssIGtsYXNpZmlrw6FjaWkgYmlvbG9naWNrw71jaCB2em9yaWVrIMSNaSB2IGdlb2luZm9ybWF0aWtlIHByaSB6b3NrdXBvdmFuw60gcHJpZXN0b3JvdsO9Y2ggb2JqZWt0b3YuIEplaiB2w71ob2RvdSBqZSwgxb5lIHByYWN1amUgcyB2aWFjZXLDvW1pIHByZW1lbm7DvW1pIG5hcmF6IGEgZG9rw6HFvmUgb2RoYWxpxaUgdnpvcnksIGt0b3LDqSBieSBwcmkgc2Ftb3N0YXRub20gaG9kbm90ZW7DrSBqZWRub3RsaXbDvWNoIHVrYXpvdmF0ZcS+b3Ygem9zdGFsaSBza3J5dMOpLiBTcHLDoXZuZSB6dm9sZW7DoSBtZXRyaWthIHZ6ZGlhbGVub3N0aSBhIG1ldMOzZGEgemhsdWtvdmFuaWEgdW1vxb7FiHVqw7ogb2RoYWxpxaUgc2tyeXTDqSB2esWlYWh5IHYgZMOhdGFjaCwgxI3DrW0gcG9za3l0dWrDuiBjZW5uw70gcG9ka2xhZCBwcmUgcm96aG9kb3ZhbmllIHYgcsO0em55Y2ggb2JsYXN0aWFjaCBhcGxpa292YW7DqWhvIHbDvXNrdW11LgoKViB0ZWp0byBwcsOhY2kgcHJlZHN0YXbDrW1lIHpobHVrb3bDuiBhbmFsw716dSBwcmkgYW5hbMO9emUgw7pkYWpvdiBvICoqcG/EjXRlIG9ieXZhdGXEvm92IG9rcmVzb3YgU2xvdmVuc2tlaiByZXB1Ymxpa3kqKiB6IGjEvmFkaXNrYSBpY2ggKiptZXNhxI1uw6lobyB2w712b2phIHYgcm9rdSAyMDI0KiouIFByYWN1amVtZSBzIGRhdGFiw6F6b3UsIGt0b3LDoSBvYnNhaHVqZSBva3Jlcywgb2JlYyBhIHBvxI1ldCBvYnl2YXRlxL5vdiB2IGplZG5vdGxpdsO9Y2ggbWVzaWFjb2NoIHJva3UgMjAyNCAob2QgamFudcOhcmEgcG8gZGVjZW1iZXIpLiDDmmRhamUgbmFqc2vDtHIgYWdyZWd1amVtZSBuYSDDunJvdmXFiCBva3Jlc292IGEgbsOhc2xlZG5lIG7DoWhvZG5lIHZ5YmVyaWVtZSAqKjEwIG9rcmVzb3YqKiwgbmEga3RvcsO9Y2ggZGVtb27FoXRydWplbWUgY2Vsw70gcG9zdHVwIGhpZXJhcmNoaWNrw6lobyB6aGx1a292YW5pYS4gViAqKlRhYi4gMSoqIHV2w6FkemFtZSBjZWzDuiBuYW1pIHBvdcW+w612YW7DuiBkYXRhYsOhenUgMTAgb2tyZXNvdi4KCmBgYHtyIHNldHVwMiwgbWVzc2FnZT1GQUxTRX0KbGlicmFyeShrbml0cikKbGlicmFyeShrYWJsZUV4dHJhKQpsaWJyYXJ5KGRwbHlyKQpgYGAKCmBgYHtyfQpybShsaXN0ID0gbHMoKSkKCiMgTmHEjcOtdGFuaWUgw7pkYWpvdiB6IGRhdGFiw6F6eQojIFByZWRwb2tsYWQ6IHPDumJvciAiRGF0YWJhemEuY3N2IiBqZSB2IHJvdm5ha29tIHByaWXEjWlua3UgYWtvIHRlbnRvIFJtZCBzw7pib3IuCiMgUG91xb7DrXZhbWUgc2VwYXLDoXRvciAiOyIga2XEj8W+ZSBpZGUgbyBDU1YgdiBldXLDs3Bza29tIGZvcm3DoXRlLgp1ZGFqZV9yYXcgPC0gcmVhZC5jc3YoIkRhdGFiYXphLmNzdiIsCiAgICAgICAgICAgICAgICAgICAgICBzZXAgPSAiOyIsCiAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCgojIFIgYXV0b21hdGlja3kgZG9wxLrFiGEgWCBwcmVkIG7DoXp2eSBzdMS6cGNvdiB6YcSNw61uYWrDumNpY2ggxI3DrXNsb20sCiMgcHJldG8gaG8gb2RzdHLDoW5pbWUsIGFieSBzbWUgbWFsaSBzcMOkxaUgbsOhenZ5IDIwMjRNMDEsIDIwMjRNMDIsIC4uLgpuYW1lcyh1ZGFqZV9yYXcpIDwtIHN1YigiXlgiLCAiIiwgbmFtZXModWRhamVfcmF3KSkKCiMgUHJlbWVub3ZhbmllIG1lc2lhY292IG5hIGJlxb5uw6kgbsOhenZ5Cm1lc2lhY2VfbWFwIDwtIGMoCiAgIjIwMjRNMDEiID0gIkphbnXDoXIiLAogICIyMDI0TTAyIiA9ICJGZWJydcOhciIsCiAgIjIwMjRNMDMiID0gIk1hcmVjIiwKICAiMjAyNE0wNCIgPSAiQXByw61sIiwKICAiMjAyNE0wNSIgPSAiTcOhaiIsCiAgIjIwMjRNMDYiID0gIkrDum4iLAogICIyMDI0TTA3IiA9ICJKw7psIiwKICAiMjAyNE0wOCIgPSAiQXVndXN0IiwKICAiMjAyNE0wOSIgPSAiU2VwdGVtYmVyIiwKICAiMjAyNE0xMCIgPSAiT2t0w7NiZXIiLAogICIyMDI0TTExIiA9ICJOb3ZlbWJlciIsCiAgIjIwMjRNMTIiID0gIkRlY2VtYmVyIgopCgojIHByZW1lbnVqZW1lIGxlbiBzdMS6cGNlIHMgbWVzaWFjbWkKbmFtZXModWRhamVfcmF3KVttYXRjaChuYW1lcyhtZXNpYWNlX21hcCksIG5hbWVzKHVkYWplX3JhdykpXSA8LSBtZXNpYWNlX21hcAptb250aF9uYW1lcyA8LSB1bm5hbWUobWVzaWFjZV9tYXApICAjIHYgcG9yYWTDrSBqYW51w6Fy4oCTZGVjZW1iZXIKCiMgQWdyZWfDoWNpYSBuYSDDunJvdmXFiCBva3Jlc292IChzw7rEjWV0IG9ieXZhdGXEvm92IHphIHbFoWV0a3kgb2JjZSkKdWRhamVfb2tyZXN5IDwtIHVkYWplX3JhdyAlPiUKICBncm91cF9ieShPa3JlcykgJT4lCiAgc3VtbWFyaXNlKGFjcm9zcyhhbGxfb2YobW9udGhfbmFtZXMpLCBzdW0sIG5hLnJtID0gVFJVRSkpCgojIE7DoWhvZG7DvSB2w71iZXIgMTAgb2tyZXNvdgpzZXQuc2VlZCgxMjMpICAjIHByZSByZXByb2R1a292YXRlxL5ub3PFpQpva3Jlc3lfdnliZXIgPC0gc2FtcGxlKHVuaXF1ZSh1ZGFqZV9va3Jlc3kkT2tyZXMpLCAxMCkKCnVkYWplX3Z5YmVyIDwtIHN1YnNldCh1ZGFqZV9va3Jlc3ksIE9rcmVzICVpbiUgb2tyZXN5X3Z5YmVyKQoKIyBVc3BvcmlhZGFuaWUgcG9kxL5hIG7DoXp2dSBva3Jlc3UgKG5pZSBqZSBudXRuw6ksIGxlbiBwcmUgcHJlaMS+YWRub3PFpSkKdWRhamVfdnliZXIgPC0gdWRhamVfdnliZXJbb3JkZXIodWRhamVfdnliZXIkT2tyZXMpLCBdCmBgYAoKKipUYWIuIDEuKiogVnlicmFuw6kgb2tyZXN5IGEgaWNoIHBvxI1ldCBvYnl2YXRlxL5vdiB2IGplZG5vdGxpdsO9Y2ggbWVzaWFjb2NoIHJva3UgMjAyNAoKYGBge3J9CmthYmxlKHVkYWplX3Z5YmVyLCBjYXB0aW9uID0gIlZ5YnJhbsOpIG9rcmVzeSBhIGljaCBwb8SNZXQgb2J5dmF0ZcS+b3YgKEphbnXDoXLigJNEZWNlbWJlciAyMDI0KSIpICU+JQogIGthYmxlX3N0eWxpbmcoZnVsbF93aWR0aCA9IEZBTFNFKQpgYGAKCkhpZXJhcmNoaWNrw6EgemhsdWtvdsOhIGFuYWzDvXphIHByYWN1amUgcyBtaWVyYW1pIHZ6ZGlhbGVub3N0aSBtZWR6aSBwb3pvcm92YW5pYW1pLiBBYnkgYm9saSB0aWV0byB2emRpYWxlbm9zdGkgcG9yb3ZuYXRlxL5uw6ksIGplIHBvdHJlYm7DqSwgYWJ5IHbFoWV0a3kgcHJlbWVubsOpIGJvbGkgZGVmaW5vdmFuw6kgbmEgcm92bmFrZWogxaFrw6FsZS4gViBuYcWhb20gcHLDrXBhZGUgc8O6IHByZW1lbm7DvW1pICoqbWVzYcSNbsOpIHBvxI10eSBvYnl2YXRlxL5vdiAoSmFudcOhciBhxb4gRGVjZW1iZXIpKiouIFBvdcW+aWplbWUgdHp2LiB6LcWha8OhbG92YW5pZSwgcHJpxI1vbSB0cmFuc2Zvcm1vdmFuw6kgJHokIGhvZG5vdHkgKHNrw7NyZSkgdnlwb8SNw610YW1lIG5hc2xlZG92bmUKCiQkeiA9IFxmcmFje3gtXG11fXtcc2lnbWF9JCQKCmtkZSAkXG11JCBqZSBzdHJlZG7DoSBob2Rub3RhIGEgJFxzaWdtYSQgamUgxaF0YW5kYXJkbsOhIG9kY2jDvWxrYSBwb3pvcm92YW7DrSAkeCQuIFByZWRwb2tsYWTDoW1lIHByaXRvbSwgxb5lIHPDumJvciDDumRham92IHXFviBuZW9ic2FodWplIE5BIGhvZG5vdHksIGt0b3LDqSBib2xpIG/FoWV0cmVuw6kgdiBwcmVkY2jDoWR6YWrDumNpY2gga3Jva29jaC4KClRvdXRvIG9wZXLDoWNpb3UgesOtc2thbWUgxaFrw6Fsb3ZhbsOpIHBvem9yb3ZhbmlhLCBwcmnEjW9tIGljaCByb3psb8W+ZW5pZSBqZSB6bsOhem9ybmVuw6kgbmFzbGVkb3ZuZS4KCmBgYHtyfQojID09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyMgMSkgUHLDrXByYXZhIMO6ZGFqb3YgYSBkYXRhLmZyYW1lIHNvIMWha8OhbG92YW7DvW1pIMO6ZGFqbWkKIyMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09CgojIFZ5YmVyaWVtZSBsZW4gbnVtZXJpY2vDqSBzdMS6cGNlIHMgbWVzYcSNbsO9bWkgw7pkYWptaSAoSmFudcOhcuKAk0RlY2VtYmVyKQp1ZGFqZTEwIDwtIHVkYWplX3Z5YmVyWywgbW9udGhfbmFtZXNdCgojIE7DoXp2eSByaWFka292IG5hc3RhdsOtbWUgbmEgbsOhenZ5IG9rcmVzb3YKcm93bmFtZXModWRhamUxMCkgPC0gdWRhamVfdnliZXIkT2tyZXMKCiMgT2RzdHLDoW5pbWUgcHLDrXBhZG7DqSBjaMO9YmFqw7pjZSBob2Rub3R5CnVkYWplX2NvbXBsZXRlIDwtIG5hLm9taXQodWRhamUxMCkKCiMgxaBrw6Fsb3ZhbmllICh6LcWha8OzcmUpIGplZG5vdGxpdsO9Y2ggcHJlbWVubsO9Y2gKdWRhamVfc2NhbGVkIDwtIHNjYWxlKHVkYWplX2NvbXBsZXRlKQpgYGAKCioqT2JyLiAxLioqIEJveHBsb3R5IMWha8OhbG92YW7DvWNoIG51bWVyaWNrw71jaCBwcmVtZW5uw71jaCAobWVzaWFjZSByb2t1IDIwMjQpCgpgYGB7ciBib3hwbG90cywgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KbnVtX3ZhcnMgIDwtIGFzLmRhdGEuZnJhbWUodWRhamVfc2NhbGVkKQpudW1fcGxvdHMgPC0gbmNvbChudW1fdmFycykKCiMgMTIgbWVzaWFjb3YgPSAzeDQgbXJpZcW+a2EKcGFyKG1mcm93ID0gYygzLCA0KSwKICAgIG1hciAgPSBjKDQsIDQsIDIsIDEpLAogICAgb21hICA9IGMoMCwgMCwgMywgMCkpCgpmb3IgKGNvbCBpbiBuYW1lcyhudW1fdmFycykpIHsKICBib3hwbG90KG51bV92YXJzW1tjb2xdXSwKICAgICAgICAgIG1haW4gICAgICAgPSBjb2wsCiAgICAgICAgICBjb2wgICAgICAgID0gImxpZ2h0Ymx1ZSIsCiAgICAgICAgICBob3Jpem9udGFsID0gVFJVRSkKfQoKbXRleHQoIkJveHBsb3R5IG51bWVyaWNrw71jaCBwcmVtZW5uw71jaCAobWVzaWFjZSByb2t1IDIwMjQpIiwKICAgICAgb3V0ZXIgPSBUUlVFLCBjZXggPSAxLjMsIGZvbnQgPSAyKQpgYGAKClRlbnRva3LDoXQgb2TEvmFobMOpIGhvZG5vdHkgbmV2eWx1xI11amVtZSwgbmFrb8S+a28gZGVmaW51asO6IGtvbmtyw6l0bnkgb2tyZXMuIE5hcHLDrWtsYWQgb2tyZXMgcyB2w71yYXpuZSB2ecWhxaHDrW0gcG/EjXRvbSBvYnl2YXRlxL5vdiBqZSB6IHBvaMS+YWR1IGFuYWzDvXp5IHphdWrDrW1hdsO9IGEgY2hjZW1lIGhvIHYgZMOhdGFjaCB6YWNob3ZhxaUuCgotLS0KClByaSB6aGx1a292ZWogYW5hbMO9emUgamUgZMO0bGXFvml0w6Ega29yZWxhxI1uw6EgbWF0aWNhIHByZW1lbm7DvWNoLiBWeXNva8OhIGtvcmVsw6FjaWEgenbDvWhvZMWIdWplIHByaSB6aGx1a292ZWogYW5hbMO9emUga29yZWxvdmFuw6kgcHJlbWVubsOpLiBQcmV0byBwcmkga29yZWzDoWNpaSBuYWQgMCw4IGFsZWJvIDAsOSDEjWFzdG8gdnlsw7rEjWltZSBqZWRudSB6IGtvcmVsb3ZhbsO9Y2ggcHJlbWVubsO9Y2guIFYgKipUYWIuIDIqKiB1a8Ohxb5lbWUga29yZWxhxI1uw7ogbWF0aWN1IG1lZHppIGplZG5vdGxpdsO9bWkgbWVzaWFjbWkgcm9rdSAyMDI0LiBBayBieSBzYSBtZWR6aSBtZXNpYWNtaSB2eXNreXRvdmFsaSB2ZcS+bWkgdnlzb2vDqSBrb3JlbMOhY2llLCBib2xvIGJ5IG1vxb5uw6kgdXZhxb5vdmHFpSBuYXByw61rbGFkIG8gem7DrcW+ZW7DrSBkaW1lbnppZSBwb21vY291IGFuYWzDvXp5IGhsYXZuw71jaCBrb21wb25lbnRvdi4KCj4gViBwcsOtcGFkZSwgYWsgbcOhbWUgdsOkxI3FocOtIHBvxI1ldCB2w716bmFtbmUga29yZWxvdmFuw71jaCBwcmVtZW5uw71jaCwgc2Egb2Rwb3LDusSNYSB0cmFuc2Zvcm3DoWNpYSBwb21vY291IEFuYWzDvXp5IGhsYXZuw71jaCBrb21wb25lbnRvdiAoUHJpbmNpcGFsIENvbXBvbmVudCBBbmFseXNpcykuIAoKKipUYWIuIDIuKiogS29yZWxhxI1uw6EgbWF0aWNhIG1lZHppIG1lc2lhY21pIHJva3UgMjAyNAoKYGBge3J9CmNvcl9tYXQgPC0gY29yKHVkYWplX3NjYWxlZCwgdXNlID0gInBhaXJ3aXNlLmNvbXBsZXRlLm9icyIpCmNvcl9tYXQgPC0gcm91bmQoY29yX21hdCwgMikKcHJpbnQoY29yX21hdCkKYGBgCgpLYcW+ZMOpbXUgb2tyZXN1IHpvZHBvdmVkw6EgamVkZW4gcmlhZG9rIHBvem9yb3ZhbsOtLiBWemRpYWxlbm9zxaUgbWVkemkgb2tyZXNtaSAkaSQgYSAkaiQgamU6CgokJApkXntpan0gPSBcc3FydHtcc3VtX2sgKHheaV9rIC0geF5qX2spXjJ9CiQkCgprZGUgJHheaV9rJCBqZSAkayQtdMOhIHByZW1lbm7DoSB2c3R1cHVqw7pjYSBkbyB2w71wb8SNdHUgKHYgbmHFoW9tIHByw61wYWRlIGlkZSBvIG1lc2HEjW7DqSBwb8SNdHkgb2J5dmF0ZcS+b3Y6IEphbnXDoXIsIEZlYnJ1w6FyLCAuLi4sIERlY2VtYmVyKSBva3Jlc3UgJGkkLiBUZW50byB0eXAgdnpkaWFsZW5vc3RpIG5hesO9dmFtZSBFdWtsaWRvdnNrw6EgdnpkaWFsZW5vc8WlLiBWemRpYWxlbm9zdGkgbWVkemkgamVkbm90bGl2w71taSBva3Jlc21pIHNhIHPDumhybm5lIHZ5amFkcnVqw7ogYWogdiBtYXRpY2kgdnpkaWFsZW5vc3RpLCDEjW8gamUgdiBuYcWhb20gcHLDrXBhZGUgdXZlZGVuw6kgdiAqKlRhYi4gMyoqLiBBbmFsw716b3UgdGVqdG8gdGFidcS+a3kgbcO0xb5lbWUgaWRlbnRpZmlrb3ZhxaUgZHZvamljZSBva3Jlc292LCBrdG9yw6kgc8O6IHNpIHBvZMS+YSBwcmllYmVodSBwb8SNdHUgb2J5dmF0ZcS+b3YgdiByb2t1IDIwMjQgdmXEvm1pIHBvZG9ibsOpIChtYWzDoSB2emRpYWxlbm9zxaUpLCByZXNwLiB2ZcS+bWkgb2RsacWhbsOpICh2ZcS+a8OhIHZ6ZGlhbGVub3PFpSkuCgoqKlRhYi4gMy4qKiBFdWtsaWRvdnNrw6EgbWF0aWNhIHZ6ZGlhbGVub3N0w60gbWVkemkgb2tyZXNtaQoKYGBge3J9CiMjID09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyMgMykgRGlzdGFuY2UgbWF0cml4CiMjID09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyBwb25lY2jDoW1lIHDDtHZvZG7DqSBuw6F6dnkgb2tyZXNvdiBha28gcm93bmFtZXMKZGlzdF9tYXQgPC0gcm91bmQoZGlzdCh1ZGFqZV9zY2FsZWQsIG1ldGhvZCA9ICJldWNsaWRlYW4iKSwgMikKZGlzdF9tYXQKYGBgCgojIyBQcmluY8OtcCBoaWVyYXJjaGlja8OpaG8gemhsdWtvdmFuaWEgKFdhcmRvdmEgbWV0w7NkYSkKClpobHVrb3ZhbmllIHYgcHLDrXBhZGUgV2FyZG92ZWogbWV0w7NkeSBwcmViaWVoYSB6ZG9sYSBzbWVyb20gbmFob3IsIHQuIGouIHphxI3DrW5hbWUgcyBqZWRub8SNbGVubsO9bWkga2xhc3RyYW1pLCBrdG9yw6kgcG9zdHVwbmUgemx1xI11amVtZS4gVMOhdG8gbWV0w7NkYSBwYXRyw60gbWVkemkgYWdsb21lcmF0w612bmUgaGllcmFyY2hpY2vDqSBtZXTDs2R5LiBNaW5pbWFsaXp1amUgbsOhcmFzdCB2bsO6dG9ybmVqIHZhcmlhYmlsaXR5IHByaSBzcG9qZW7DrSBkdm9jaCBrbGFzdHJvdiwgcHJpxI1vbSB2eXXFvsOtdmEgbmFzbGVkb3Zuw6kgdsO9cG/EjXR5OgoKV2FyZG92YSBtZXTDs2RhIG1pbmltYWxpenVqZSBzdW11IMWhdHZvcmNvdiBjaMO9YiAoRXJyb3IgU3VtIG9mIFNxdWFyZXMg4oCTIEVTUykKCiQkRVNTKEMpID0gXHN1bV97aSBcaW4gQ30gXGxWZXJ0IHhfaSAtIFxiYXJ7eH1fQyBcclZlcnReMiwkJAoKa2RlICRDJCBqZSB6dmHFvm92YW7DvSBrbGFzdGVyICh6aGx1aykuIFYga2HFvmRvbSBrcm9rdSB6bHXEjW92YW5pYSBkdm9jaCBrbGFzdHJvdiBXYXJkb3ZhIG1ldMOzZGEgaMS+YWTDoSBtaW5pbcOhbG55IHByw61yYXN0b2sgc3VteSDFoXR2b3Jjb3YgY2jDvWIgKCRcRGVsdGEgRVNTJCksIHByacSNb20KCiQkXERlbHRhIEVTUyA9IEVTUyhBIFxjdXAgQikgLSBFU1MoQSkgLSBFU1MoQikuJCQKCkR2b2ppY2EgemhsdWtvdiwga3RvcsOhIHRlanRvIHBvZG1pZW5rZSBvIG1pbmltYWxpesOhY2lpIHZ5aG92dWplLCBqZSBuw6FzbGVkbmUgemzDusSNZW7DoSBhIHByZWNow6FkemEgc2EgayDEj2FsxaFpZW11IGtyb2t1LiBUbyBzcHJhdmlkbGEgdmVkaWUgayB2eXR2w6FyYW5pdSByZWxhdMOtdm5lIGhvbW9nw6lubnljaCB6aGx1a292LCBwcmnEjW9tIG5lZG9jaMOhZHphIGsgb2R0cmjDoXZhbml1IG9kxL5haGzDvWNoIGhvZG7DtHQgdGFrLCBha28gcHJpIG5pZWt0b3LDvWNoIGluw71jaCB6aGx1a292YWPDrWNoIG1ldMOzZGFjaC4KCioqT2JyLiAyLioqIEhpZXJhcmNoaWNrw6kgemhsdWtvdmFuaWUg4oCTIGRlbmRyb2dyYW0uIMSMZXJ2ZW7DoSDEjWlhcmEgdXLEjXVqZSByZXogZGVmaW51asO6Y2kgdHJpIGtsYXN0cmUuCgpgYGB7cn0KIyMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PQojIyA0KSBIaWVyYXJjaGljYWwgY2x1c3RlcmluZwojIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09CgpoYyA8LSBoY2x1c3QoZGlzdF9tYXQsIG1ldGhvZCA9ICJ3YXJkLkQyIikKCnBsb3QoaGMsIGxhYmVscyA9IHJvd25hbWVzKHVkYWplX3NjYWxlZCksCiAgICAgbWFpbiA9ICJIaWVyYXJjaGljYWwgY2x1c3RlcmluZyBvZiBkaXN0cmljdHMgKFdhcmQuRDIpIiwKICAgICB4bGFiID0gIiIsIHN1YiA9ICIiKQoKIyBwb8SNZXQga2xhc3Ryb3YKayA8LSAzCmhfY3V0IDwtIGhjJGhlaWdodFtsZW5ndGgoaGMkaGVpZ2h0KSAtIChrIC0gMSldCmFibGluZShoID0gaF9jdXQsIGNvbCA9ICJyZWQiLCBsd2QgPSAyLCBsdHkgPSAyKQoKa2xhc3Rlcl9tZW1iZXJzaGlwIDwtIGN1dHJlZShoYywgayA9IGspCgp1ZGFqZV9rbGFzdGVycyA8LSBkYXRhLmZyYW1lKAogIE9rcmVzID0gcm93bmFtZXModWRhamVfY29tcGxldGUpLAogIHVkYWplX2NvbXBsZXRlLAogIGtsYXN0ZXIgPSBmYWN0b3Ioa2xhc3Rlcl9tZW1iZXJzaGlwKQopCmBgYAoKKipUYWIuIDQuKiogUHLDrXNsdcWhbm9zxaUgb2tyZXNvdiBkbyBrbGFzdHJvdgoKYGBge3J9CmRhdGFfcHJhYyA8LSBkYXRhLmZyYW1lKAogIE9rcmVzID0gdWRhamVfa2xhc3RlcnMkT2tyZXMsCiAga2xhc3RlciA9IHVkYWplX2tsYXN0ZXJzJGtsYXN0ZXIKKQpkYXRhX3ByYWMKYGBgCgpWeWtvbmFuw6Ega2xhc3Ryb3bDoSBhbmFsw716YSBrbGFzaWZpa3VqZSBva3Jlc3kgZG8gdHJvY2gga2xhc3Ryb3YuIFpsb8W+ZW5pZSBrbGFzdHJvdiBvZHLDocW+YSBqZWRuYWsgw7pyb3ZlxYggcG/EjXR1IG9ieXZhdGXEvm92LCBhbGUgYWogcG9kb2Jub3PFpSBwcmllYmVodSBwb8SNYXMgamVkbm90bGl2w71jaCBtZXNpYWNvdiByb2t1IDIwMjQuIFYgamVkbm9tIGtsYXN0cmkgbcO0xb5lbWUgbsOhanPFpSBza8O0ciAqKnbDpMSNxaFpZSBva3Jlc3kgcyBwb2RvYm7DvW0gcG/EjXRvbSBvYnl2YXRlxL5vdioqLCB2IGlub20gc2vDtHIgKiptZW7FoWllIG9rcmVzeSoqLCBwcsOtcGFkbmUgb2tyZXN5IHMgb2RsacWhbm91IGR5bmFtaWtvdSAobWllcm55IG7DoXJhc3QgYWxlYm8gcG9rbGVzKS4gSW50ZXJwcmV0w6FjaWEga29ua3LDqXRueWNoIGtsYXN0cm92IHrDoXZpc8OtIG9kIHbDvXNsZWRuw71jaCBob2Ruw7R0IGEgamUgbW/Fvm7DqSBqdSBkb3BsbmnFpSBuYXByw61rbGFkIG1hcG92w71tIHpvYnJhemVuw61tIGFsZWJvIHBvcm92bmFuw61tIHMgxI9hbMWhw61taSBjaGFyYWt0ZXJpc3Rpa2FtaSBva3Jlc292LgoKIyMgRGVza3JpcHTDrXZuZSDFoXRhdGlzdGlreSB2w71zbGVka292CgpOYSB6w6FrbGFkZSAqKlRhYi4gNSoqIG3DtMW+ZW1lIHBvc8O6ZGnFpSwgYWtvIGRvYnJlIGplZG5vdGxpdsOpIG1lc2lhY2UgKHByZW1lbm7DqSkgcHJpc3BpZXZhasO6IGsgcm96bGnFoW92YW5pdSBtZWR6aSBrbGFzdHJhbWkuIFZuw7p0cm9rbGFzdHJvdsOhIHZhcmlhYmlsaXRhIChXU1MpIHZ5amFkcnVqZSwgbmFrb8S+a28gc8O6IG9rcmVzeSB2IHLDoW1jaSBqZWRuw6lobyBrbGFzdHJhIHBvZG9ibsOpLCB6YXRpYcS+IMSNbyBtZWR6aS1rbGFzdHJvdsOhIHZhcmlhYmlsaXRhIChCU1MpIHZ5amFkcnVqZSByb3pkaWVseSBtZWR6aSBrbGFzdHJhbWkuIFBvZGllbCBtZWR6aS1rbGFzdHJvdmVqIHZhcmlhYmlsaXR5IG5hIGNlbGtvdmVqIHZhcmlhYmlsaXRlIChzdMS6cGVjIGBQcm9wX0JldHdlZW5gKSBuw6FtIHVrYXp1amUsIGRvIGFrZWogbWllcnkgZGFuw70gbWVzaWFjIHByaXNwaWV2YSBrIHNlcGFyw6FjaWkga2xhc3Ryb3YuCgoqKlRhYi4gNS4qKiBWeXN2ZXRsZW5pZSB2bsO6dHJva2xhc3Ryb3ZlaiB2YXJpYWJpbGl0eSB6IGjEvmFkaXNrYSBqZWRub3RsaXbDvWNoIHByZW1lbm7DvWNoIChtZXNpYWNvdikKCmBgYHtyfQojIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09CiMjIDUpIFZhcmlhYmlsaXR5IG1lYXN1cmVzCiMjID09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCnNzcSA8LSBmdW5jdGlvbih4LCBtKSBzdW0oKHggLSBtKV4yKQoKdmFyX25hbWVzIDwtIGNvbG5hbWVzKHVkYWplX3NjYWxlZCkKClRTUyA8LSBzYXBwbHkodmFyX25hbWVzLCBmdW5jdGlvbih2KSBzc3EodWRhamVfc2NhbGVkWywgdl0sIG1lYW4odWRhamVfc2NhbGVkWywgdl0pKSkKCldTUyA8LSBzYXBwbHkodmFyX25hbWVzLCBmdW5jdGlvbih2KSB7CiAgeCA8LSB1ZGFqZV9zY2FsZWRbLCB2XQogIHRhcHBseSh4LCBrbGFzdGVyX21lbWJlcnNoaXAsIGZ1bmN0aW9uKHopIHNzcSh6LCBtZWFuKHopKSkgfD4gc3VtKCkKfSkKCkJTUyA8LSBUU1MgLSBXU1MKCnNzX3RhYmxlIDwtIGRhdGEuZnJhbWUoCiAgVmFyaWFibGUgPSB2YXJfbmFtZXMsCiAgVFNTID0gVFNTLAogIFdTUyA9IFdTUywKICBCU1MgPSBCU1MsCiAgUHJvcF9CZXR3ZWVuID0gQlNTIC8gVFNTCikKCnNzX3RhYmxlCmBgYAoKVnnFocWhaWUgaG9kbm90eSBwb2RpZWx1IGBQcm9wX0JldHdlZW5gIHpuYW1lbmFqw7osIMW+ZSBkYW7DvSBtZXNpYWMgdsO9cmF6bmVqxaFpZSBvZGxpxaF1amUgamVkbm90bGl2w6kga2xhc3RyZS4gTWVzaWFjZSBzIG5pxb7FoW91IGhvZG5vdG91IHRvaHRvIHBvZGllbHUgcHJlZHN0YXZ1asO6IG9iZG9iaWEsIHYga3RvcsO9Y2ggc8O6IG9rcmVzeSBtZWR6aSBzZWJvdSByZWxhdMOtdm5lIHBvZG9ibsOpIGEgZGFuw6EgcHJlbWVubsOhIG5pZSBqZSB0YWvDvW0gc2lsbsO9bSDigJ5zZXBhcsOhdG9yb23igJwuCgotLS0KCk5hIHrDoXZlciBzYSBwb3pyaWVtZSBuYSBjZW50cm9pZHksIHQuIGouIHByaWVtZXJuw6kgaG9kbm90eSBzbGVkb3ZhbsO9Y2ggcHJlbWVubsO9Y2ggdiBqZWRub3RsaXbDvWNoIGtsYXN0cm9jaC4gVGllIG7DoW0gcG9za3l0dWrDuiBwcmVoxL5hZG7DvSBvcGlzIHR5cGlja8OpaG8gb2tyZXN1IHYga2HFvmRvbSBrbGFzdHJpLgoKYGBge3J9CiMgVnl0dm9yw61tZSBkw6F0b3bDvSByw6FtZWMgcyBww7R2b2Ruw71taSAobmXFoWvDoWxvdmFuw71taSkgaG9kbm90YW1pIGEgcHJpcmFkZW7DvW0ga2xhc3Ryb20KdWRhamVfY29tcGxldGVfa2xhc3RlciA8LSBkYXRhLmZyYW1lKAogIE9rcmVzID0gcm93bmFtZXModWRhamVfY29tcGxldGUpLAogIHVkYWplX2NvbXBsZXRlLAogIGtsYXN0ZXIgPSBmYWN0b3Ioa2xhc3Rlcl9tZW1iZXJzaGlwKQopCmBgYAoKKipUYWIuIDYuKiogQ2VudHJvaWR5IOKAkyBwcmllbWVybsOpIHBvxI10eSBvYnl2YXRlxL5vdiB2IGplZG5vdGxpdsO9Y2ggbWVzaWFjb2NoIHBvZMS+YSBrbGFzdHJvdgoKYGBge3J9CmRlc2NyaXB0aXZlcyA8LSB1ZGFqZV9jb21wbGV0ZV9rbGFzdGVyICU+JQogIGdyb3VwX2J5KGtsYXN0ZXIpICU+JQogIHN1bW1hcmlzZSgKICAgIGFjcm9zcygKICAgICAgLmNvbHMgPSB3aGVyZShpcy5udW1lcmljKSwKICAgICAgLmZucyAgPSBsaXN0KG1lYW4gPSB+IG1lYW4oLngsIG5hLnJtID0gVFJVRSkpLAogICAgICAubmFtZXMgPSAiey5jb2x9X3suZm59IgogICAgKQogICkKCmRlc2NyaXB0aXZlcwpgYGAKClByaSBwb2jEvmFkZSBuYSBjZW50cm9pZHkgdmlkw61tZSByb3pkaWVseSB2IHByaWVtZXJub20gcG/EjXRlIG9ieXZhdGXEvm92IHYgamVkbm90bGl2w71jaCBrbGFzdHJvY2guIEplZGVuIGtsYXN0ZXIgbcO0xb5lIHphaMWVxYhhxaUgc2vDtHIgdsOkxI3FoWllIG9rcmVzeSBzIHZ5xaHFocOtbWkgcHJpZW1lcm7DvW1pIGhvZG5vdGFtaSBwb8SNYXMgY2Vsw6lobyByb2thLCBpbsO9IHNrw7RyIG1lbsWhaWUgb2tyZXN5IHMgbmnFvsWhw61taSBob2Rub3RhbWkuIFphdWrDrW1hdsOpIHPDuiBhaiBwcsOtcGFkbsOpIHJvemRpZWx5IG1lZHppIG1lc2lhY21pIOKAkyBuYXByw61rbGFkIG1pZXJuZSBuw6FyYXN0eSBhbGVibyBwb2tsZXN5IHYgbmlla3RvcsO9Y2ggb2Jkb2JpYWNoIHJva2EsIGt0b3LDqSBtw7TFvnUgc8O6dmlzaWXFpSBzIG1pZ3LDoWNpb3UsIHNlesOzbm55bWkgdnBseXZtaSBhbGVibyBhZG1pbmlzdHJhdMOtdm55bWkgem1lbmFtaSAobmFwci4g4oCecHJpaGzDoXNlbmll4oCcIG9ieXZhdGXEvm92KS4KCiMjIFrDoXZlcgoKUHJlZGxvxb5lbsOhIGFuYWzDvXphIHNhIHphb2JlcsOhICoqcG/EjXRvbSBvYnl2YXRlxL5vdiB2eWJyYW7DvWNoIG9rcmVzb3YgU2xvdmVuc2tlaiByZXB1Ymxpa3kqKiB2IHrDoXZpc2xvc3RpIG9kIGljaCAqKm1lc2HEjW7DqWhvIHbDvXZvamEgdiByb2t1IDIwMjQqKi4gTmEgesOha2xhZGUgaGllcmFyY2hpY2tlaiB6aGx1a292ZWogYW5hbMO9enkgc21lIG9rcmVzeSByb3pkZWxpbGkgZG8gdHJvY2gga2xhc3Ryb3YuIEtsYXN0cmUgc2EgbMOtxaFpYSBuYWptw6Qgw7pyb3bFiG91IHByaWVtZXJuw6lobyBwb8SNdHUgb2J5dmF0ZcS+b3YgYSBwb2RvYm5vc8Wlb3UgcHJpZWJlaHUgdiDEjWFzZS4gCgpUYWvDoXRvIGFuYWzDvXphIG3DtMW+ZSBzbMO6xb5pxaUgYWtvIHBvZGtsYWQgcHJlOgoKLSBwbMOhbm92YW5pZSB2ZXJlam7DvWNoIHNsdcW+aWViICjFoWtvbHksIHpkcmF2b3Ruw61jdNCy0L4sIGRvcHJhdmEpLCAgCi0gcHJpZXN0b3JvdsOpIHBsw6Fub3ZhbmllIGEgcm96dm9qIMO6emVtaWEsICAKLSBwb3Jvdm7DoXZhbmllIHR5cGlja3kg4oCebWFsw71jaOKAnCwg4oCec3RyZWRuw71jaOKAnCBhIOKAnnbDpMSNxaHDrWNo4oCcIG9rcmVzb3YgcG9kxL5hIHp2b2xlbsO9Y2ggdWthem92YXRlxL5vdi4KClpobHVrb3bDoSBhbmFsw716YSB0ZWRhIHVtb8W+xYh1amUgaWRlbnRpZmlrb3ZhxaUgc2t1cGlueSBwb2RvYm7DvWNoIG9rcmVzb3YgYSBuw6FzbGVkbmUgY2llbGVuw71tIHNww7Rzb2JvbSBhbmFseXpvdmHFpSBhIHBsw6Fub3ZhxaUgdmVyZWpuw6kgcG9saXRpa3kgxI1pIHJvenZvam92w6kgcHJvamVrdHkgcG9kxL5hIGljaCBwcsOtc2x1xaFub3N0aSBrdSBrbGFzdHJvbS4K