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

  1. 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.

  2. 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.

  3. 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í.

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