Ú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, klasifikácii biologických vzoriek, pri
práci s finančnými aktívami či pri odporúčacích systémoch vo filmovom
priemysle. Jej výhodou je, že pracuje s viacerými premennými naraz a
dokáže odhaliť vzory, ktoré by pri posudzovaní jednotlivých ukazovateľov
samostatne zostali skryté.
V tejto práci ilustrujeme zhlukovú analýzu na databáze filmov z
rebríčka IMDb. Filmy budeme zoskupovať na základe IMDb
hodnotenia, dĺžky (v minútach) a výšky
rozpočtu. Cieľom je zistiť, či vieme na základe týchto troch
ukazovateľov identifikovať prirodzené skupiny filmov – napríklad
vysokorozpočtové „blockbustery“ oproti menším komornejším filmom – a aké
sú typické vlastnosti vytvorených klastrov.
Keďže pôvodná databáza obsahuje pomerne veľa filmov a úplné názvy
filmov by v grafoch a tabuľkách zhoršovali prehľadnosť, urobíme dve
zjednodušenia:
- Analyzujeme náhodne vybraných 20 filmov z rebríčka.
Vďaka tomu zostanú výstupy prehľadné, ale zároveň bude možné dobre
sledovať, ako algoritmus zhlukovania funguje.
- Filmy budeme v analytických tabuľkách a grafoch označovať
len ich umiestnením v rebríčku (premenná „Umiestnenie“). Plné
názvy filmov ponecháme len v úvodnej tabuľke, ktorá opisuje použitú
vzorku. V ďalšej analýze preto čitateľ pracuje najmä s číselnými
identifikátormi filmov.
V ďalšom texte budeme pracovať s údajmi načítanými zo súboru
data_ekonometria.csv. V Tab. 1 uvádzame
náhodne vybranú vzorku 20 filmov, ktorá vstupuje do zhlukovej
analýzy.
library(knitr)
library(kableExtra)
library(dplyr)
rm(list = ls())
# Načítanie údajov o filmoch
filmy <- read.csv(
"data_ekonometria.csv",
sep = ";",
stringsAsFactors = FALSE,
check.names = FALSE # ponechá pôvodné názvy stĺpcov s medzerami
)
# Pre didaktické účely vyberieme náhodnú vzorku 20 filmov
set.seed(123) # aby bol výber reprodukovateľný
filmy_sample <- filmy %>%
dplyr::slice_sample(n = 20)
# Prevod textových premenných na numerické -----------------------------
# IMDb hodnotenie je vo formáte s čiarkou ako desatinným oddeľovačom (napr. "9,3")
filmy_sample$IMDb_hodnotenie_num <- as.numeric(sub(",", ".", filmy_sample$`IMDb hodnotenie`))
# Dĺžka v minútach – premenujeme na jednoduchší názov
filmy_sample$Dlzka <- filmy_sample$`Dĺžka (min)`
# Rozpočet je zapísaný s medzerami ako oddeľovačmi tisícov (napr. "25 000 000")
filmy_sample$Rozpocet <- as.numeric(gsub(" ", "", filmy_sample$`Rozpočet [$]`))
# Výber premenných vhodných na zhlukovanie (A: IMDb + dĺžka + rozpočet)
filmy_num <- filmy_sample[, c("IMDb_hodnotenie_num", "Dlzka", "Rozpocet")]
# Ako identifikátor použijeme umiestnenie filmu v rebríčku IMDb
rownames(filmy_num) <- filmy_sample$Umiestnenie
# Odstránenie prípadných riadkov s chýbajúcimi hodnotami
filmy_num_complete <- na.omit(filmy_num)
# Úvodná prehľadová tabuľka (ukazujeme aj názov filmu)
tab1 <- filmy_sample[, c("Umiestnenie", "Názov", "Rok",
"Žáner", "IMDb hodnotenie", "Dĺžka (min)", "Rozpočet [$]")]
kable(tab1,
caption = "Tab. 1: Náhodne vybraných 20 filmov z rebríčka IMDb") %>%
kable_styling(full_width = FALSE)
Tab. 1: Náhodne vybraných 20 filmov z rebríčka IMDb
| Umiestnenie |
Názov |
Rok |
Žáner |
IMDb hodnotenie |
Dĺžka (min) |
Rozpočet [$] |
| 159 |
Gone with the Wind |
1939 |
Dráma; Romantický; Vojnový |
8,2 |
238 |
3 850 000 |
| 207 |
Tokyo Story |
1953 |
Dráma |
8,1 |
136 |
120 000 |
| 179 |
Klaus |
2019 |
Animovaný; Dobrodružný; Komédia |
8,1 |
96 |
40 000 000 |
| 14 |
Inception |
2010 |
Akčný; Dobrodružný; Sci-Fi |
8,8 |
148 |
160 000 000 |
| 195 |
Sherlock Jr. |
1924 |
Akčný; Komédia; Romantický |
8,1 |
45 |
200 000 |
| 170 |
Fargo |
1996 |
Krimi; Triler |
8,1 |
98 |
7 000 000 |
| 50 |
Cinema Paradiso |
1988 |
Dráma; Romantický |
8,5 |
155 |
5 000 000 |
| 118 |
Die Hard |
1988 |
Akčný; Triler |
8,2 |
132 |
33 000 000 |
| 43 |
Casablanca |
1942 |
Dráma; Romantický; Vojnový |
8,5 |
162 |
1 000 000 |
| 229 |
La haine |
1995 |
Krimi; Dráma |
8,1 |
98 |
2 600 000 |
| 247 |
The Help |
2011 |
Dráma |
8,0 |
146 |
25 000 000 |
| 243 |
Persona |
1966 |
Dráma; Triler |
8,1 |
85 |
78 000 |
| 153 |
The Thing |
1982 |
Horor; Mysteriózny; Sci-Fi |
8,2 |
109 |
15 000 000 |
| 90 |
Eternal Sunshine of the Spotless Mind |
2004 |
Dráma; Romantický; Sci-Fi |
8,3 |
108 |
20 000 000 |
| 91 |
2001: A Space Odyssey |
1968 |
Dobrodružný; Sci-Fi |
8,3 |
149 |
10 500 000 |
| 197 |
Mr. Smith Goes to Washington |
1939 |
Komédia; Dráma |
8,1 |
129 |
1 500 000 |
| 236 |
Amores perros |
2000 |
Dráma; Triler |
8,1 |
154 |
2 400 000 |
| 185 |
The Grand Budapest Hotel |
2014 |
Dobrodružný; Komédia; Krimi |
8,1 |
99 |
25 000 000 |
| 92 |
Reservoir Dogs |
1992 |
Krimi; Triler |
8,3 |
99 |
3 000 000 |
| 137 |
Pan's Labyrinth |
2006 |
Dráma; Fantazijný; Vojnový |
8,2 |
118 |
19 000 000 |
V ďalšej analýze pracujeme už iba s číselným identifikátorom
filmu – umiestnením v rebríčku IMDb. Všetky tabuľky a grafy
preto používajú namiesto názvu filmu číslo Umiestnenie, aby
zostali prehľadné. Plné názvy filmov je možné spätne dohľadať v Tab.
1.
Škálovanie premenných a boxploty
Hierarchická zhluková analýza pracuje s mierami vzdialenosti medzi
pozorovaniami. Aby boli tieto vzdialenosti porovnateľné, je vhodné, aby
všetky premenné boli na rovnakej škále. Preto použijeme tzv.
z-škálovanie, pričom transformované \(z\) hodnoty (skóre) vypočítame:
\[
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 chýbajúce hodnoty, ktoré sme ošetrili v
predchádzajúcom kroku.
Touto operáciou získame škálované pozorovania, ktorých rozloženie pre
jednotlivé premenné znázorňujeme pomocou boxplotov.
# =======================================================
# 1) Príprava údajov a data.frame so škálovanými údajmi
# =======================================================
filmy_scaled <- scale(filmy_num_complete)
Obr. 1. Boxploty škálovaných numerických premenných (IMDb
hodnotenie, dĺžka a rozpočet)
num_vars <- as.data.frame(filmy_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 (filmy)", outer = TRUE, cex = 1.3, font = 2)

Prípadné odľahlé hodnoty (napríklad extrémne vysoký rozpočet alebo
dĺžka) nebudeme vylučovať, keďže predstavujú konkrétne
filmy, ktoré sú z hľadiska interpretácie zaujímavé.
Korelačná matica premenných
Pri zhlukovej analýze je dôležitá korelačná matica premenných. Vysoká
korelácia môže zvýhodňovať niektoré premenné pri tvorbe klastrov. Pri
veľmi vysokej korelácii (napr. nad 0,8 alebo 0,9) by sme uvažovali o
vylúčení jednej z dvojice premenných alebo o použití analýzy hlavných
komponentov.
V Tab. 2 uvádzame korelačnú maticu troch použitých
premenných: IMDb hodnotenia, dĺžky a rozpočtu.
cor_mat <- cor(filmy_scaled, use = "pairwise.complete.obs")
cor_mat <- round(cor_mat, 2)
kable(cor_mat,
caption = "Tab. 2: Korelačná matica škálovaných premenných") %>%
kable_styling(full_width = FALSE)
Tab. 2: Korelačná matica škálovaných premenných
| |
IMDb_hodnotenie_num |
Dlzka |
Rozpocet |
| IMDb_hodnotenie_num |
1.00 |
0.32 |
0.63 |
| Dlzka |
0.32 |
1.00 |
0.09 |
| Rozpocet |
0.63 |
0.09 |
1.00 |
V našom prípade pracujeme iba s tromi premennými, ktoré typicky
nebývajú extrémne silno korelované (hodnotenie, dĺžka a rozpočet), takže
nie je nutné žiadnu z nich vylučovať.
Matica vzdialeností
Každému filmu zodpovedá jeden riadok pozorovaní. Vzdialenosť medzi
filmami \(i\) a \(j\) je pri použití Euklidovskej
vzdialenosti definovaná:
\[
d^{ij} = \sqrt{\sum_k (x^i_k - x^j_k)^2},
\]
kde \(x^i_k\) je \(k\)-ta premenná (IMDb hodnotenie, dĺžka,
rozpočet) pre film \(i\). Tento typ
vzdialenosti nazývame Euklidovská vzdialenosť.
Vzdialenosti medzi jednotlivými filmami sa súhrnne vyjadrujú v matici
vzdialenosti, ktorá je uvedená v Tab. 3.
Interpretácia je nasledovná: čím je hodnota v matici väčšia, tým sú
si dva filmy z hľadiska zvolených premenných menej podobné (líšia sa
napríklad v rozpočte, dĺžke alebo hodnotení). Naopak, malé vzdialenosti
znamenajú podobnosť. V tabuľke používame ako identifikátor
umiestnenie filmu v rebríčku (Umiestnenie), aby bola
matica prehľadná.
# ============================
# 3) Distance matrix
# ============================
dist_mat <- round(dist(filmy_scaled, method = "euclidean"), 2)
as.matrix(dist_mat)[1:10, 1:10] %>%
kable(caption = "Tab. 3: Časť matice Euklidovských vzdialeností medzi filmami (podľa umiestnenia v rebríčku)") %>%
kable_styling(full_width = FALSE)
Tab. 3: Časť matice Euklidovských vzdialeností medzi filmami (podľa umiestnenia v rebríčku)
| |
159 |
207 |
179 |
14 |
195 |
170 |
50 |
118 |
43 |
229 |
| 159 |
0.00 |
2.63 |
3.76 |
5.88 |
4.90 |
3.57 |
2.62 |
2.80 |
2.48 |
3.57 |
| 207 |
2.63 |
0.00 |
1.51 |
5.83 |
2.30 |
0.98 |
2.15 |
1.07 |
2.20 |
0.96 |
| 179 |
3.76 |
1.51 |
0.00 |
5.17 |
1.71 |
0.93 |
2.75 |
1.07 |
2.90 |
1.06 |
| 14 |
5.88 |
5.83 |
5.17 |
0.00 |
6.37 |
5.81 |
4.66 |
4.79 |
4.78 |
5.90 |
| 195 |
4.90 |
2.30 |
1.71 |
6.37 |
0.00 |
1.35 |
3.48 |
2.44 |
3.62 |
1.34 |
| 170 |
3.57 |
0.98 |
0.93 |
5.81 |
1.35 |
0.00 |
2.54 |
1.25 |
2.65 |
0.12 |
| 50 |
2.62 |
2.15 |
2.75 |
4.66 |
3.48 |
2.54 |
0.00 |
1.85 |
0.21 |
2.54 |
| 118 |
2.80 |
1.07 |
1.07 |
4.79 |
2.44 |
1.25 |
1.85 |
0.00 |
1.97 |
1.32 |
| 43 |
2.48 |
2.20 |
2.90 |
4.78 |
3.62 |
2.65 |
0.21 |
1.97 |
0.00 |
2.65 |
| 229 |
3.57 |
0.96 |
1.06 |
5.90 |
1.34 |
0.12 |
2.54 |
1.32 |
2.65 |
0.00 |
(Pre prehľadnosť zobrazujeme len časť matice – prvých 10 filmov v
riadkoch aj stĺpcoch.)
Princíp hierarchického zhlukovania (Wardova metóda)
Zhlukovanie pri Wardovej metóde prebieha zdola smerom
nahor – začíname s jednočlennými klastrami a postupne zlučujeme
dvojice klastrov. 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. V
každom kroku zlučovania dvoch klastrov Wardova metóda hľadá minimálny
prírastok sumy štvorcov chýb:
\[
\Delta ESS = ESS(A \cup B) - ESS(A) - ESS(B).
\]
Dvojica klastrov, ktorá minimalizuje \(\Delta ESS\), je zlúčená a proces
pokračuje. To spravidla vedie k vytváraniu relatívne homogénnych
zhlukov, pričom nedochádza k „odtrhnutiu“ extrémnych hodnôt tak výrazne,
ako pri niektorých iný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(filmy_scaled), # zobrazujeme len umiestnenie filmu
main = "Hierarchické zhlukovanie filmov (Ward.D2)",
xlab = "Umiestnenie filmu v rebríčku IMDb",
sub = "")
k <- 3 # počet klastrov
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)
filmy_klasters <- data.frame(
Umiestnenie = rownames(filmy_num_complete),
filmy_num_complete,
klaster = factor(klaster_membership)
)
Tab. 4. Príslušnosť filmov do klastrov (podľa umiestnenia v
rebríčku)
kable(filmy_klasters[, c("Umiestnenie", "klaster")],
caption = "Tab. 4: Príslušnosť filmov do klastrov (identifikátorom je umiestnenie v rebríčku)") %>%
kable_styling(full_width = FALSE)
Tab. 4: Príslušnosť filmov do klastrov (identifikátorom je umiestnenie v rebríčku)
| |
Umiestnenie |
klaster |
| 159 |
159 |
1 |
| 207 |
207 |
2 |
| 179 |
179 |
2 |
| 14 |
14 |
3 |
| 195 |
195 |
2 |
| 170 |
170 |
2 |
| 50 |
50 |
1 |
| 118 |
118 |
2 |
| 43 |
43 |
1 |
| 229 |
229 |
2 |
| 247 |
247 |
2 |
| 243 |
243 |
2 |
| 153 |
153 |
2 |
| 90 |
90 |
2 |
| 91 |
91 |
2 |
| 197 |
197 |
2 |
| 236 |
236 |
2 |
| 185 |
185 |
2 |
| 92 |
92 |
2 |
| 137 |
137 |
2 |
Vykonaná klastrová analýza klasifikuje filmy do troch klastrov. Každý
klaster združuje filmy, ktoré sú si podobné z hľadiska kombinácie troch
ukazovateľov: hodnotenia, dĺžky a rozpočtu. Vzhľadom na to, že filmy
označujeme iba ich umiestnením v rebríčku, sú tabuľky a grafy prehľadné,
pričom podrobné názvy filmov je možné dohľadať v Tab. 1.
Deskriptívne štatistiky – rozklad variability
Zaujíma nás, aká je variabilita jednotlivých premenných vo vnútri a
medzi klastrami. Použijeme rozklad variability na:
- TSS – celkovú sumu štvorcov odchýlok,
- WSS – vnútroklastrovú variabilitu,
- BSS – medzi-klastrovú variabilitu.
Na základe Tab. 5 môžeme posúdiť, ako dobre
jednotlivé premenné prispievajú k odlíšeniu klastrov. Čím je podiel
medzi-klastrovej variability na celkovej variabilite vyšší, tým lepšie
daná premenná pomáha oddeľovať zhluky.
# ============================
# 5) Variability measures
# ============================
ssq <- function(x, m) sum((x - m)^2)
var_names <- colnames(filmy_scaled)
TSS <- sapply(var_names, function(v) ssq(filmy_scaled[, v], mean(filmy_scaled[, v])))
WSS <- sapply(var_names, function(v) {
x <- filmy_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
)
# Zaokrúhľujeme iba numerické stĺpce
ss_table_round <- ss_table
ss_table_round[, sapply(ss_table_round, is.numeric)] <-
round(ss_table_round[, sapply(ss_table_round, is.numeric)], 3)
kable(ss_table_round,
caption = "Tab. 5: Rozklad variability (celková, vnútri klastrov a medzi klastrami)") %>%
kable_styling(full_width = FALSE)
Tab. 5: Rozklad variability (celková, vnútri klastrov a medzi klastrami)
| |
Variable |
TSS |
WSS |
BSS |
Prop_Between |
| IMDb_hodnotenie_num |
IMDb_hodnotenie_num |
19 |
4.942 |
14.058 |
0.740 |
| Dlzka |
Dlzka |
19 |
10.216 |
8.784 |
0.462 |
| Rozpocet |
Rozpocet |
19 |
2.005 |
16.995 |
0.894 |
Na základe tabuľky vidíme, že jednotlivé premenné sa líšia v tom, akú
časť variability dokážeme vysvetliť rozdielmi medzi klastrami. Premenné
s vyšším podielom medzi-klastrovej variability (vyššia hodnota
Prop_Between) lepšie separujú jednotlivé skupiny
filmov.
Centroidy – priemerné hodnoty premenných v klastroch
Záverečnú interpretáciu klastrov urobíme na základe tzv.
centroidov, teda priemerných hodnôt sledovaných
premenných v jednotlivých klastroch.
Tab. 6. Centroidy – priemerné hodnoty IMDb hodnotenia, dĺžky
a rozpočtu podľa klastrov
descriptives <- filmy_klasters %>%
group_by(klaster) %>%
summarise(
IMDb_hodnotenie_mean = mean(IMDb_hodnotenie_num, na.rm = TRUE),
Dlzka_mean = mean(Dlzka, na.rm = TRUE),
Rozpocet_mean = mean(Rozpocet, na.rm = TRUE)
)
# zaokrúhľujeme iba numerické stĺpce, nie stĺpec "klaster"
descriptives_round <- descriptives
descriptives_round[, sapply(descriptives_round, is.numeric)] <-
round(descriptives_round[, sapply(descriptives_round, is.numeric)], 2)
kable(descriptives_round,
caption = "Tab. 6: Priemerné hodnoty sledovaných premenných v jednotlivých klastroch") %>%
kable_styling(full_width = FALSE)
Tab. 6: Priemerné hodnoty sledovaných premenných v jednotlivých klastroch
| klaster |
IMDb_hodnotenie_mean |
Dlzka_mean |
Rozpocet_mean |
| 1 |
8.40 |
185.00 |
3283333 |
| 2 |
8.15 |
112.56 |
12774875 |
| 3 |
8.80 |
148.00 |
160000000 |
Porovnaním centroidov môžeme charakterizovať jednotlivé klastre.
Klaster, ktorý má najvyšší priemerný rozpočet a dĺžku, môžeme
interpretovať ako skupinu veľkých, produkčne náročných filmov (často ide
o tzv. „blockbustery“). Naopak, klaster s nižším priemerným rozpočtom a
kratšou dĺžkou môže reprezentovať skôr menšie, komornejšie snímky.
Hodnotenie IMDb nám zároveň umožňuje rozlíšiť, ktorý klaster má v
priemere vyššiu divácku odozvu.
Záver
Predložená analýza sa zaoberá zhlukovaním filmov na základe troch
ukazovateľov: IMDb hodnotenia, dĺžky filmu a výšky
rozpočtu. Na základe hierarchickej zhlukovej analýzy (Wardova
metóda) sme identifikovali tri klastre, ktoré združujú filmy s podobnými
charakteristikami.
Z praktických dôvodov sme pracovali iba s náhodne vybranou
vzorkou 20 filmov z rebríčka IMDb a filmy sme v grafoch a
tabuľkách označovali iba ich umiestnením v rebríčku.
Tým sme dosiahli dobrú prehľadnosť výstupov, pričom plné názvy filmov je
možné spätne dohľadať v úvodnej tabuľke.
Analýza ukázala, že:
- zvolená kombinácia premenných umožňuje vytvoriť zhluky filmov, ktoré
sa líšia najmä z hľadiska rozpočtu a dĺžky,
- priemerné hodnoty (centroidy) klastrov umožňujú interpretovať
jednotlivé skupiny ako viac či menej nákladné a dlhé filmy s rôznym
priemerným hodnotením,
- rozklad variability naznačuje, ktoré premenné najviac prispievajú k
oddeleniu klastrov.
Takýto prístup je využiteľný napríklad pri segmentácii
filmov v rámci filmového štúdia, streamovacej platformy či pri
odporúčacích systémoch. Na základe príslušnosti filmu ku klastru je
možné lepšie cieliť marketing, plánovať rozpočty budúcich projektov
alebo odporúčať divákom filmy s podobným profilom. Zhluková analýza tak
predstavuje užitočný nástroj pri rozhodovaní v prostredí audiovizuálneho
priemyslu.
LS0tCnRpdGxlOiAiWmhsdWtvdsOhIGFuYWzDvXphIGZpbG1vdiIKYXV0aG9yOiAiRGlhbmEgxI51cmlhbsSNaWtvdsOhICh6YSBwb21vY2kgQ2hhdEdQVCkiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIHRoZW1lOiB1bml0ZWQKICAgIGhpZ2hsaWdodDogdGFuZ28KICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB0cnVlCiAgICBkZl9wcmludDogcGFnZWQKZWRpdG9yX29wdGlvbnM6CiAgbWFya2Rvd246CiAgICB3cmFwOiA3MgotLS0KCmBgYHtyIHNldHVwMSwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KAogIGVjaG8gICAgPSBUUlVFLAogIG1lc3NhZ2UgPSBUUlVFLAogIHdhcm5pbmcgPSBGQUxTRSwKICBlcnJvciAgID0gRkFMU0UKKQpgYGAKCiMjIMOadm9kCgpLbGFzdHJvdsOhICh6aGx1a292w6EpIGFuYWzDvXphIHBhdHLDrSBtZWR6aSBuYWpwb3XFvsOtdmFuZWrFoWllIG1ldMOzZHkgZXhwbG9yYXTDrXZuZWogxaF0YXRpc3Rpa3kuIFYgcHJheGkgc2Egdnl1xb7DrXZhIHbFoWFkZSB0YW0sIGtkZSBqZSBwb3RyZWJuw6kgcm96ZGVsacWlIHBvem9yb3ZhbmlhIGRvIGhvbW9nw6lubnljaCBjZWxrb3Yg4oCTIG5hcHLDrWtsYWQgcHJpIHNlZ21lbnTDoWNpaSB6w6FrYXpuw61rb3YgdiBtYXJrZXRpbmd1LCBpZGVudGlmaWvDoWNpaSBwb2RvYm7DvWNoIGtyYWrDrW4gdiBtYWtyb2Vrb25vbWlja8O9Y2ggdWthem92YXRlxL5vY2gsIGtsYXNpZmlrw6FjaWkgYmlvbG9naWNrw71jaCB2em9yaWVrLCBwcmkgcHLDoWNpIHMgZmluYW7EjW7DvW1pIGFrdMOtdmFtaSDEjWkgcHJpIG9kcG9yw7rEjWFjw61jaCBzeXN0w6ltb2NoIHZvIGZpbG1vdm9tIHByaWVteXNsZS4gSmVqIHbDvWhvZG91IGplLCDFvmUgcHJhY3VqZSBzIHZpYWNlcsO9bWkgcHJlbWVubsO9bWkgbmFyYXogYSBkb2vDocW+ZSBvZGhhbGnFpSB2em9yeSwga3RvcsOpIGJ5IHByaSBwb3N1ZHpvdmFuw60gamVkbm90bGl2w71jaCB1a2F6b3ZhdGXEvm92IHNhbW9zdGF0bmUgem9zdGFsaSBza3J5dMOpLgoKViB0ZWp0byBwcsOhY2kgaWx1c3RydWplbWUgemhsdWtvdsO6IGFuYWzDvXp1IG5hIGRhdGFiw6F6ZSBmaWxtb3YgeiByZWJyw63EjWthIElNRGIuIEZpbG15IGJ1ZGVtZSB6b3NrdXBvdmHFpSBuYSB6w6FrbGFkZSAqKklNRGIgaG9kbm90ZW5pYSoqLCAqKmTEusW+a3kgKHYgbWluw7p0YWNoKSoqIGEgKip2w73FoWt5IHJvenBvxI10dSoqLiBDaWXEvm9tIGplIHppc3RpxaUsIMSNaSB2aWVtZSBuYSB6w6FrbGFkZSB0w71jaHRvIHRyb2NoIHVrYXpvdmF0ZcS+b3YgaWRlbnRpZmlrb3ZhxaUgcHJpcm9kemVuw6kgc2t1cGlueSBmaWxtb3Yg4oCTIG5hcHLDrWtsYWQgdnlzb2tvcm96cG/EjXRvdsOpIOKAnmJsb2NrYnVzdGVyeeKAnCBvcHJvdGkgbWVuxaHDrW0ga29tb3JuZWrFocOtbSBmaWxtb20g4oCTIGEgYWvDqSBzw7ogdHlwaWNrw6kgdmxhc3Rub3N0aSB2eXR2b3JlbsO9Y2gga2xhc3Ryb3YuCgpLZcSPxb5lIHDDtHZvZG7DoSBkYXRhYsOhemEgb2JzYWh1amUgcG9tZXJuZSB2ZcS+YSBmaWxtb3YgYSDDunBsbsOpIG7DoXp2eSBmaWxtb3YgYnkgdiBncmFmb2NoIGEgdGFidcS+a8OhY2ggemhvcsWhb3ZhbGkgcHJlaMS+YWRub3PFpSwgdXJvYsOtbWUgZHZlIHpqZWRub2R1xaFlbmlhOgoKMS4gKipBbmFseXp1amVtZSBuw6Fob2RuZSB2eWJyYW7DvWNoIDIwIGZpbG1vdioqIHogcmVicsOtxI1rYS4gVsSPYWthIHRvbXUgem9zdGFuw7ogdsO9c3R1cHkgcHJlaMS+YWRuw6ksIGFsZSB6w6Fyb3ZlxYggYnVkZSBtb8W+bsOpIGRvYnJlIHNsZWRvdmHFpSwgYWtvIGFsZ29yaXRtdXMgemhsdWtvdmFuaWEgZnVuZ3VqZS4KMi4gKipGaWxteSBidWRlbWUgdiBhbmFseXRpY2vDvWNoIHRhYnXEvmvDoWNoIGEgZ3JhZm9jaCBvem5hxI1vdmHFpSBsZW4gaWNoIHVtaWVzdG5lbsOtbSB2IHJlYnLDrcSNa3UgKHByZW1lbm7DoSDigJ5VbWllc3RuZW5pZeKAnCkqKi4gUGxuw6kgbsOhenZ5IGZpbG1vdiBwb25lY2jDoW1lIGxlbiB2IMO6dm9kbmVqIHRhYnXEvmtlLCBrdG9yw6Egb3Bpc3VqZSBwb3XFvml0w7ogdnpvcmt1LiBWIMSPYWzFoWVqIGFuYWzDvXplIHByZXRvIMSNaXRhdGXEviBwcmFjdWplIG5ham3DpCBzIMSNw61zZWxuw71taSBpZGVudGlmaWvDoXRvcm1pIGZpbG1vdi4KClYgxI9hbMWhb20gdGV4dGUgYnVkZW1lIHByYWNvdmHFpSBzIMO6ZGFqbWkgbmHEjcOtdGFuw71taSB6byBzw7pib3J1IGBkYXRhX2Vrb25vbWV0cmlhLmNzdmAuIFYgKipUYWIuIDEqKiB1dsOhZHphbWUgbsOhaG9kbmUgdnlicmFuw7ogdnpvcmt1IDIwIGZpbG1vdiwga3RvcsOhIHZzdHVwdWplIGRvIHpobHVrb3ZlaiBhbmFsw716eS4KCmBgYHtyIHNldHVwMiwgbWVzc2FnZT1GQUxTRX0KbGlicmFyeShrbml0cikKbGlicmFyeShrYWJsZUV4dHJhKQpsaWJyYXJ5KGRwbHlyKQpgYGAKCmBgYHtyIGRhdGFfcHJlcGFyYXRpb259CnJtKGxpc3QgPSBscygpKQoKIyBOYcSNw610YW5pZSDDumRham92IG8gZmlsbW9jaApmaWxteSA8LSByZWFkLmNzdigKICAiZGF0YV9la29ub21ldHJpYS5jc3YiLAogIHNlcCA9ICI7IiwKICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UsCiAgY2hlY2submFtZXMgPSBGQUxTRSAgICMgcG9uZWNow6EgcMO0dm9kbsOpIG7DoXp2eSBzdMS6cGNvdiBzIG1lZHplcmFtaQopCgojIFByZSBkaWRha3RpY2vDqSDDusSNZWx5IHZ5YmVyaWVtZSBuw6Fob2Ruw7ogdnpvcmt1IDIwIGZpbG1vdgpzZXQuc2VlZCgxMjMpICAjIGFieSBib2wgdsO9YmVyIHJlcHJvZHVrb3ZhdGXEvm7DvQpmaWxteV9zYW1wbGUgPC0gZmlsbXkgJT4lCiAgZHBseXI6OnNsaWNlX3NhbXBsZShuID0gMjApCgojIFByZXZvZCB0ZXh0b3bDvWNoIHByZW1lbm7DvWNoIG5hIG51bWVyaWNrw6kgLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0KCiMgSU1EYiBob2Rub3RlbmllIGplIHZvIGZvcm3DoXRlIHMgxI1pYXJrb3UgYWtvIGRlc2F0aW5uw71tIG9kZGXEvm92YcSNb20gKG5hcHIuICI5LDMiKQpmaWxteV9zYW1wbGUkSU1EYl9ob2Rub3RlbmllX251bSA8LSBhcy5udW1lcmljKHN1YigiLCIsICIuIiwgZmlsbXlfc2FtcGxlJGBJTURiIGhvZG5vdGVuaWVgKSkKCiMgRMS6xb5rYSB2IG1pbsO6dGFjaCDigJMgcHJlbWVudWplbWUgbmEgamVkbm9kdWNoxaHDrSBuw6F6b3YKZmlsbXlfc2FtcGxlJERsemthIDwtIGZpbG15X3NhbXBsZSRgRMS6xb5rYSAobWluKWAKCiMgUm96cG/EjWV0IGplIHphcMOtc2Fuw70gcyBtZWR6ZXJhbWkgYWtvIG9kZGXEvm92YcSNbWkgdGlzw61jb3YgKG5hcHIuICIyNSAwMDAgMDAwIikKZmlsbXlfc2FtcGxlJFJvenBvY2V0IDwtIGFzLm51bWVyaWMoZ3N1YigiICIsICIiLCBmaWxteV9zYW1wbGUkYFJvenBvxI1ldCBbJF1gKSkKCiMgVsO9YmVyIHByZW1lbm7DvWNoIHZob2Ruw71jaCBuYSB6aGx1a292YW5pZSAoQTogSU1EYiArIGTEusW+a2EgKyByb3pwb8SNZXQpCmZpbG15X251bSA8LSBmaWxteV9zYW1wbGVbLCBjKCJJTURiX2hvZG5vdGVuaWVfbnVtIiwgIkRsemthIiwgIlJvenBvY2V0IildCgojIEFrbyBpZGVudGlmaWvDoXRvciBwb3XFvmlqZW1lIHVtaWVzdG5lbmllIGZpbG11IHYgcmVicsOtxI1rdSBJTURiCnJvd25hbWVzKGZpbG15X251bSkgPC0gZmlsbXlfc2FtcGxlJFVtaWVzdG5lbmllCgojIE9kc3Ryw6FuZW5pZSBwcsOtcGFkbsO9Y2ggcmlhZGtvdiBzIGNow71iYWrDumNpbWkgaG9kbm90YW1pCmZpbG15X251bV9jb21wbGV0ZSA8LSBuYS5vbWl0KGZpbG15X251bSkKCiMgw5p2b2Ruw6EgcHJlaMS+YWRvdsOhIHRhYnXEvmthICh1a2F6dWplbWUgYWogbsOhem92IGZpbG11KQp0YWIxIDwtIGZpbG15X3NhbXBsZVssIGMoIlVtaWVzdG5lbmllIiwgIk7DoXpvdiIsICJSb2siLAogICAgICAgICAgICAgICAgICAgICAgICAgIsW9w6FuZXIiLCAiSU1EYiBob2Rub3RlbmllIiwgIkTEusW+a2EgKG1pbikiLCAiUm96cG/EjWV0IFskXSIpXQoKa2FibGUodGFiMSwKICAgICAgY2FwdGlvbiA9ICJUYWIuIDE6IE7DoWhvZG5lIHZ5YnJhbsO9Y2ggMjAgZmlsbW92IHogcmVicsOtxI1rYSBJTURiIikgJT4lCiAga2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRkFMU0UpCmBgYAoKViDEj2FsxaFlaiBhbmFsw716ZSBwcmFjdWplbWUgdcW+IGliYSBzICoqxI3DrXNlbG7DvW0gaWRlbnRpZmlrw6F0b3JvbSBmaWxtdSDigJMgdW1pZXN0bmVuw61tIHYgcmVicsOtxI1rdSBJTURiKiouIFbFoWV0a3kgdGFidcS+a3kgYSBncmFmeSBwcmV0byBwb3XFvsOtdmFqw7ogbmFtaWVzdG8gbsOhenZ1IGZpbG11IMSNw61zbG8gYFVtaWVzdG5lbmllYCwgYWJ5IHpvc3RhbGkgcHJlaMS+YWRuw6kuIFBsbsOpIG7DoXp2eSBmaWxtb3YgamUgbW/Fvm7DqSBzcMOkdG5lIGRvaMS+YWRhxaUgdiBUYWIuIDEuCgotLS0KCiMjIMWga8OhbG92YW5pZSBwcmVtZW5uw71jaCBhIGJveHBsb3R5CgpIaWVyYXJjaGlja8OhIHpobHVrb3bDoSBhbmFsw716YSBwcmFjdWplIHMgbWllcmFtaSB2emRpYWxlbm9zdGkgbWVkemkgcG96b3JvdmFuaWFtaS4gQWJ5IGJvbGkgdGlldG8gdnpkaWFsZW5vc3RpIHBvcm92bmF0ZcS+bsOpLCBqZSB2aG9kbsOpLCBhYnkgdsWhZXRreSBwcmVtZW5uw6kgYm9saSBuYSByb3ZuYWtlaiDFoWvDoWxlLiBQcmV0byBwb3XFvmlqZW1lIHR6di4gKip6LcWha8OhbG92YW5pZSoqLCBwcmnEjW9tIHRyYW5zZm9ybW92YW7DqSAkeiQgaG9kbm90eSAoc2vDs3JlKSB2eXBvxI3DrXRhbWU6CgokJAp6ID0gXGZyYWN7eCAtIFxtdX17XHNpZ21hfSwKJCQKCmtkZSAkXG11JCBqZSBzdHJlZG7DoSBob2Rub3RhIGEgJFxzaWdtYSQgamUgxaF0YW5kYXJkbsOhIG9kY2jDvWxrYSBwb3pvcm92YW7DrSAkeCQuIFByZWRwb2tsYWTDoW1lIHByaXRvbSwgxb5lIHPDumJvciDDumRham92IHXFviBuZW9ic2FodWplIGNow71iYWrDumNlIGhvZG5vdHksIGt0b3LDqSBzbWUgb8WhZXRyaWxpIHYgcHJlZGNow6FkemFqw7pjb20ga3Jva3UuCgpUb3V0byBvcGVyw6FjaW91IHrDrXNrYW1lIMWha8OhbG92YW7DqSBwb3pvcm92YW5pYSwga3RvcsO9Y2ggcm96bG/FvmVuaWUgcHJlIGplZG5vdGxpdsOpIHByZW1lbm7DqSB6bsOhem9yxYh1amVtZSBwb21vY291IGJveHBsb3Rvdi4KCmBgYHtyIHNjYWxpbmd9CiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQojIDEpIFByw61wcmF2YSDDumRham92IGEgZGF0YS5mcmFtZSBzbyDFoWvDoWxvdmFuw71taSDDumRham1pCiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQoKZmlsbXlfc2NhbGVkIDwtIHNjYWxlKGZpbG15X251bV9jb21wbGV0ZSkKYGBgCgoqKk9ici4gMS4gQm94cGxvdHkgxaFrw6Fsb3ZhbsO9Y2ggbnVtZXJpY2vDvWNoIHByZW1lbm7DvWNoIChJTURiIGhvZG5vdGVuaWUsIGTEusW+a2EgYSByb3pwb8SNZXQpKioKCmBgYHtyIGJveHBsb3RzLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBmaWcuc2hvdz0naG9sZCd9Cm51bV92YXJzIDwtIGFzLmRhdGEuZnJhbWUoZmlsbXlfc2NhbGVkKQpudW1fcGxvdHMgPC0gbmNvbChudW1fdmFycykKCnBhcihtZnJvdyA9IGMoY2VpbGluZyhzcXJ0KG51bV9wbG90cykpLCBjZWlsaW5nKG51bV9wbG90cyAvIGNlaWxpbmcoc3FydChudW1fcGxvdHMpKSkpKQpwYXIobWFyID0gYyg0LCA0LCAyLCAxKSkKCmZvciAoY29sIGluIG5hbWVzKG51bV92YXJzKSkgewogIGJveHBsb3QobnVtX3ZhcnNbW2NvbF1dLAogICAgICAgICAgbWFpbiA9IGNvbCwKICAgICAgICAgIGNvbCA9ICJsaWdodGJsdWUiLAogICAgICAgICAgaG9yaXpvbnRhbCA9IFRSVUUpCn0KCm10ZXh0KCJCb3hwbG90eSBudW1lcmlja8O9Y2ggcHJlbWVubsO9Y2ggKGZpbG15KSIsIG91dGVyID0gVFJVRSwgY2V4ID0gMS4zLCBmb250ID0gMikKYGBgCgpQcsOtcGFkbsOpIG9kxL5haGzDqSBob2Rub3R5IChuYXByw61rbGFkIGV4dHLDqW1uZSB2eXNva8O9IHJvenBvxI1ldCBhbGVibyBkxLrFvmthKSAqKm5lYnVkZW1lIHZ5bHXEjW92YcWlKiosIGtlxI/FvmUgcHJlZHN0YXZ1asO6IGtvbmtyw6l0bmUgZmlsbXksIGt0b3LDqSBzw7ogeiBoxL5hZGlza2EgaW50ZXJwcmV0w6FjaWUgemF1asOtbWF2w6kuCgotLS0KCiMjIEtvcmVsYcSNbsOhIG1hdGljYSBwcmVtZW5uw71jaAoKUHJpIHpobHVrb3ZlaiBhbmFsw716ZSBqZSBkw7RsZcW+aXTDoSBrb3JlbGHEjW7DoSBtYXRpY2EgcHJlbWVubsO9Y2guIFZ5c29rw6Ega29yZWzDoWNpYSBtw7TFvmUgenbDvWhvZMWIb3ZhxaUgbmlla3RvcsOpIHByZW1lbm7DqSBwcmkgdHZvcmJlIGtsYXN0cm92LiBQcmkgdmXEvm1pIHZ5c29rZWoga29yZWzDoWNpaSAobmFwci4gbmFkIDAsOCBhbGVibyAwLDkpIGJ5IHNtZSB1dmHFvm92YWxpIG8gdnlsw7rEjWVuw60gamVkbmVqIHogZHZvamljZSBwcmVtZW5uw71jaCBhbGVibyBvIHBvdcW+aXTDrSBhbmFsw716eSBobGF2bsO9Y2gga29tcG9uZW50b3YuCgpWICoqVGFiLiAyKiogdXbDoWR6YW1lIGtvcmVsYcSNbsO6IG1hdGljdSB0cm9jaCBwb3XFvml0w71jaCBwcmVtZW5uw71jaDogSU1EYiBob2Rub3RlbmlhLCBkxLrFvmt5IGEgcm96cG/EjXR1LgoKYGBge3IgY29ycmVsYXRpb259CmNvcl9tYXQgPC0gY29yKGZpbG15X3NjYWxlZCwgdXNlID0gInBhaXJ3aXNlLmNvbXBsZXRlLm9icyIpCmNvcl9tYXQgPC0gcm91bmQoY29yX21hdCwgMikKCmthYmxlKGNvcl9tYXQsCiAgICAgIGNhcHRpb24gPSAiVGFiLiAyOiBLb3JlbGHEjW7DoSBtYXRpY2EgxaFrw6Fsb3ZhbsO9Y2ggcHJlbWVubsO9Y2giKSAlPiUKICBrYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGQUxTRSkKYGBgCgpWIG5hxaFvbSBwcsOtcGFkZSBwcmFjdWplbWUgaWJhIHMgdHJvbWkgcHJlbWVubsO9bWksIGt0b3LDqSB0eXBpY2t5IG5lYsO9dmFqw7ogZXh0csOpbW5lIHNpbG5vIGtvcmVsb3ZhbsOpIChob2Rub3RlbmllLCBkxLrFvmthIGEgcm96cG/EjWV0KSwgdGFrxb5lIG5pZSBqZSBudXRuw6kgxb5pYWRudSB6IG5pY2ggdnlsdcSNb3ZhxaUuCgotLS0KCiMjIE1hdGljYSB2emRpYWxlbm9zdMOtCgpLYcW+ZMOpbXUgZmlsbXUgem9kcG92ZWTDoSBqZWRlbiByaWFkb2sgcG96b3JvdmFuw60uIFZ6ZGlhbGVub3PFpSBtZWR6aSBmaWxtYW1pICRpJCBhICRqJCBqZSBwcmkgcG91xb5pdMOtIEV1a2xpZG92c2tlaiB2emRpYWxlbm9zdGkgZGVmaW5vdmFuw6E6CgokJApkXntpan0gPSBcc3FydHtcc3VtX2sgKHheaV9rIC0geF5qX2spXjJ9LAokJAoKa2RlICR4XmlfayQgamUgJGskLXRhIHByZW1lbm7DoSAoSU1EYiBob2Rub3RlbmllLCBkxLrFvmthLCByb3pwb8SNZXQpIHByZSBmaWxtICRpJC4gVGVudG8gdHlwIHZ6ZGlhbGVub3N0aSBuYXrDvXZhbWUgKipFdWtsaWRvdnNrw6EgdnpkaWFsZW5vc8WlKiouIFZ6ZGlhbGVub3N0aSBtZWR6aSBqZWRub3RsaXbDvW1pIGZpbG1hbWkgc2Egc8O6aHJubmUgdnlqYWRydWrDuiB2IG1hdGljaSB2emRpYWxlbm9zdGksIGt0b3LDoSBqZSB1dmVkZW7DoSB2ICoqVGFiLiAzKiouCgpJbnRlcnByZXTDoWNpYSBqZSBuYXNsZWRvdm7DoTogxI3DrW0gamUgaG9kbm90YSB2IG1hdGljaSB2w6TEjcWhaWEsIHTDvW0gc8O6IHNpIGR2YSBmaWxteSB6IGjEvmFkaXNrYSB6dm9sZW7DvWNoIHByZW1lbm7DvWNoIG1lbmVqIHBvZG9ibsOpIChsw63FoWlhIHNhIG5hcHLDrWtsYWQgdiByb3pwb8SNdGUsIGTEusW+a2UgYWxlYm8gaG9kbm90ZW7DrSkuIE5hb3BhaywgbWFsw6kgdnpkaWFsZW5vc3RpIHpuYW1lbmFqw7ogcG9kb2Jub3PFpS4gViB0YWJ1xL5rZSBwb3XFvsOtdmFtZSBha28gaWRlbnRpZmlrw6F0b3IgKip1bWllc3RuZW5pZSBmaWxtdSB2IHJlYnLDrcSNa3UgKFVtaWVzdG5lbmllKSoqLCBhYnkgYm9sYSBtYXRpY2EgcHJlaMS+YWRuw6EuCgpgYGB7ciBkaXN0YW5jZV9tYXRyaXh9CiMgPT09PT09PT09PT09PT09PT09PT09PT09PT09PQojIDMpIERpc3RhbmNlIG1hdHJpeAojID09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCmRpc3RfbWF0IDwtIHJvdW5kKGRpc3QoZmlsbXlfc2NhbGVkLCBtZXRob2QgPSAiZXVjbGlkZWFuIiksIDIpCgphcy5tYXRyaXgoZGlzdF9tYXQpWzE6MTAsIDE6MTBdICU+JQogIGthYmxlKGNhcHRpb24gPSAiVGFiLiAzOiDEjGFzxaUgbWF0aWNlIEV1a2xpZG92c2vDvWNoIHZ6ZGlhbGVub3N0w60gbWVkemkgZmlsbWFtaSAocG9kxL5hIHVtaWVzdG5lbmlhIHYgcmVicsOtxI1rdSkiKSAlPiUKICBrYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGQUxTRSkKYGBgCgooUHJlIHByZWjEvmFkbm9zxaUgem9icmF6dWplbWUgbGVuIMSNYXPFpSBtYXRpY2Ug4oCTIHBydsO9Y2ggMTAgZmlsbW92IHYgcmlhZGtvY2ggYWogc3TEunBjb2NoLikKCi0tLQoKIyMgUHJpbmPDrXAgaGllcmFyY2hpY2vDqWhvIHpobHVrb3ZhbmlhIChXYXJkb3ZhIG1ldMOzZGEpCgpaaGx1a292YW5pZSBwcmkgV2FyZG92ZWogbWV0w7NkZSBwcmViaWVoYSAqKnpkb2xhIHNtZXJvbSBuYWhvcioqIOKAkyB6YcSNw61uYW1lIHMgamVkbm/EjWxlbm7DvW1pIGtsYXN0cmFtaSBhIHBvc3R1cG5lIHpsdcSNdWplbWUgZHZvamljZSBrbGFzdHJvdi4gVMOhdG8gbWV0w7NkYSBwYXRyw60gbWVkemkgKiphZ2xvbWVyYXTDrXZuZSBoaWVyYXJjaGlja8OpIG1ldMOzZHkqKi4gTWluaW1hbGl6dWplIG7DoXJhc3Qgdm7DunRvcm5laiB2YXJpYWJpbGl0eSBwcmkgc3BvamVuw60gZHZvY2gga2xhc3Ryb3YsIHByacSNb20gdnl1xb7DrXZhIG5hc2xlZG92bsOpIHbDvXBvxI10eToKCldhcmRvdmEgbWV0w7NkYSBtaW5pbWFsaXp1amUgc3VtdSDFoXR2b3Jjb3YgY2jDvWIgKEVycm9yIFN1bSBvZiBTcXVhcmVzIOKAkyBFU1MpOgoKJCQKRVNTKEMpID0gXHN1bV97aSBcaW4gQ30gXGxWZXJ0IHhfaSAtIFxiYXJ7eH1fQyBcclZlcnReMiwKJCQKCmtkZSAkQyQgamUgenZhxb5vdmFuw70ga2xhc3Rlci4gViBrYcW+ZG9tIGtyb2t1IHpsdcSNb3ZhbmlhIGR2b2NoIGtsYXN0cm92IFdhcmRvdmEgbWV0w7NkYSBoxL5hZMOhIG1pbmltw6FsbnkgcHLDrXJhc3RvayBzdW15IMWhdHZvcmNvdiBjaMO9YjoKCiQkClxEZWx0YSBFU1MgPSBFU1MoQSBcY3VwIEIpIC0gRVNTKEEpIC0gRVNTKEIpLgokJAoKRHZvamljYSBrbGFzdHJvdiwga3RvcsOhIG1pbmltYWxpenVqZSAkXERlbHRhIEVTUyQsIGplIHpsw7rEjWVuw6EgYSBwcm9jZXMgcG9rcmHEjXVqZS4gVG8gc3ByYXZpZGxhIHZlZGllIGsgdnl0dsOhcmFuaXUgcmVsYXTDrXZuZSBob21vZ8Opbm55Y2ggemhsdWtvdiwgcHJpxI1vbSBuZWRvY2jDoWR6YSBrIOKAnm9kdHJobnV0aXXigJwgZXh0csOpbW55Y2ggaG9kbsO0dCB0YWsgdsO9cmF6bmUsIGFrbyBwcmkgbmlla3RvcsO9Y2ggaW7DvWNoIG1ldMOzZGFjaC4KCioqT2JyLiAyLiBIaWVyYXJjaGlja8OpIHpobHVrb3ZhbmllIOKAkyBkZW5kcm9ncmFtLiDEjGVydmVuw6EgxI1pYXJhIHVyxI11amUgcmV6IGRlZmludWrDumNpIHRyaSBrbGFzdHJlLioqCgpgYGB7ciBoaWVyYXJjaGljYWxfY2x1c3RlcmluZ30KIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09CiMgNCkgSGllcmFyY2hpY2FsIGNsdXN0ZXJpbmcKIyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09CgpoYyA8LSBoY2x1c3QoZGlzdF9tYXQsIG1ldGhvZCA9ICJ3YXJkLkQyIikKCnBsb3QoaGMsCiAgICAgbGFiZWxzID0gcm93bmFtZXMoZmlsbXlfc2NhbGVkKSwgICAjIHpvYnJhenVqZW1lIGxlbiB1bWllc3RuZW5pZSBmaWxtdQogICAgIG1haW4gPSAiSGllcmFyY2hpY2vDqSB6aGx1a292YW5pZSBmaWxtb3YgKFdhcmQuRDIpIiwKICAgICB4bGFiID0gIlVtaWVzdG5lbmllIGZpbG11IHYgcmVicsOtxI1rdSBJTURiIiwKICAgICBzdWIgPSAiIikKCmsgPC0gMyAgIyBwb8SNZXQga2xhc3Ryb3YKaF9jdXQgPC0gaGMkaGVpZ2h0W2xlbmd0aChoYyRoZWlnaHQpIC0gKGsgLSAxKV0KYWJsaW5lKGggPSBoX2N1dCwgY29sID0gInJlZCIsIGx3ZCA9IDIsIGx0eSA9IDIpCgprbGFzdGVyX21lbWJlcnNoaXAgPC0gY3V0cmVlKGhjLCBrID0gaykKCmZpbG15X2tsYXN0ZXJzIDwtIGRhdGEuZnJhbWUoCiAgVW1pZXN0bmVuaWUgPSByb3duYW1lcyhmaWxteV9udW1fY29tcGxldGUpLAogIGZpbG15X251bV9jb21wbGV0ZSwKICBrbGFzdGVyID0gZmFjdG9yKGtsYXN0ZXJfbWVtYmVyc2hpcCkKKQpgYGAKCioqVGFiLiA0LiBQcsOtc2x1xaFub3PFpSBmaWxtb3YgZG8ga2xhc3Ryb3YgKHBvZMS+YSB1bWllc3RuZW5pYSB2IHJlYnLDrcSNa3UpKioKCmBgYHtyIGNsdXN0ZXJzX3RhYmxlfQprYWJsZShmaWxteV9rbGFzdGVyc1ssIGMoIlVtaWVzdG5lbmllIiwgImtsYXN0ZXIiKV0sCiAgICAgIGNhcHRpb24gPSAiVGFiLiA0OiBQcsOtc2x1xaFub3PFpSBmaWxtb3YgZG8ga2xhc3Ryb3YgKGlkZW50aWZpa8OhdG9yb20gamUgdW1pZXN0bmVuaWUgdiByZWJyw63EjWt1KSIpICU+JQogIGthYmxlX3N0eWxpbmcoZnVsbF93aWR0aCA9IEZBTFNFKQpgYGAKClZ5a29uYW7DoSBrbGFzdHJvdsOhIGFuYWzDvXphIGtsYXNpZmlrdWplIGZpbG15IGRvIHRyb2NoIGtsYXN0cm92LiBLYcW+ZMO9IGtsYXN0ZXIgemRydcW+dWplIGZpbG15LCBrdG9yw6kgc8O6IHNpIHBvZG9ibsOpIHogaMS+YWRpc2thIGtvbWJpbsOhY2llIHRyb2NoIHVrYXpvdmF0ZcS+b3Y6IGhvZG5vdGVuaWEsIGTEusW+a3kgYSByb3pwb8SNdHUuIFZ6aMS+YWRvbSBuYSB0bywgxb5lIGZpbG15IG96bmHEjXVqZW1lIGliYSBpY2ggdW1pZXN0bmVuw61tIHYgcmVicsOtxI1rdSwgc8O6IHRhYnXEvmt5IGEgZ3JhZnkgcHJlaMS+YWRuw6ksIHByacSNb20gcG9kcm9ibsOpIG7DoXp2eSBmaWxtb3YgamUgbW/Fvm7DqSBkb2jEvmFkYcWlIHYgVGFiLiAxLgoKLS0tCgojIyBEZXNrcmlwdMOtdm5lIMWhdGF0aXN0aWt5IOKAkyByb3prbGFkIHZhcmlhYmlsaXR5CgpaYXVqw61tYSBuw6FzLCBha8OhIGplIHZhcmlhYmlsaXRhIGplZG5vdGxpdsO9Y2ggcHJlbWVubsO9Y2ggdm8gdm7DunRyaSBhIG1lZHppIGtsYXN0cmFtaS4gUG91xb5pamVtZSByb3prbGFkIHZhcmlhYmlsaXR5IG5hOgoKLSAqKlRTUyoqIOKAkyBjZWxrb3bDuiBzdW11IMWhdHZvcmNvdiBvZGNow71sb2ssCi0gKipXU1MqKiDigJMgdm7DunRyb2tsYXN0cm92w7ogdmFyaWFiaWxpdHUsCi0gKipCU1MqKiDigJMgbWVkemkta2xhc3Ryb3bDuiB2YXJpYWJpbGl0dS4KCk5hIHrDoWtsYWRlICoqVGFiLiA1KiogbcO0xb5lbWUgcG9zw7pkacWlLCBha28gZG9icmUgamVkbm90bGl2w6kgcHJlbWVubsOpIHByaXNwaWV2YWrDuiBrIG9kbMOtxaFlbml1IGtsYXN0cm92LiDEjMOtbSBqZSBwb2RpZWwgbWVkemkta2xhc3Ryb3ZlaiB2YXJpYWJpbGl0eSBuYSBjZWxrb3ZlaiB2YXJpYWJpbGl0ZSB2ecWhxaHDrSwgdMO9bSBsZXDFoWllIGRhbsOhIHByZW1lbm7DoSBwb23DoWhhIG9kZGXEvm92YcWlIHpobHVreS4KCmBgYHtyIHZhcmlhYmlsaXR5X21lYXN1cmVzfQojID09PT09PT09PT09PT09PT09PT09PT09PT09PT0KIyA1KSBWYXJpYWJpbGl0eSBtZWFzdXJlcwojID09PT09PT09PT09PT09PT09PT09PT09PT09PT0KCnNzcSA8LSBmdW5jdGlvbih4LCBtKSBzdW0oKHggLSBtKV4yKQoKdmFyX25hbWVzIDwtIGNvbG5hbWVzKGZpbG15X3NjYWxlZCkKClRTUyA8LSBzYXBwbHkodmFyX25hbWVzLCBmdW5jdGlvbih2KSBzc3EoZmlsbXlfc2NhbGVkWywgdl0sIG1lYW4oZmlsbXlfc2NhbGVkWywgdl0pKSkKCldTUyA8LSBzYXBwbHkodmFyX25hbWVzLCBmdW5jdGlvbih2KSB7CiAgeCA8LSBmaWxteV9zY2FsZWRbLCB2XQogIHRhcHBseSh4LCBrbGFzdGVyX21lbWJlcnNoaXAsIGZ1bmN0aW9uKHopIHNzcSh6LCBtZWFuKHopKSkgfD4gc3VtKCkKfSkKCkJTUyA8LSBUU1MgLSBXU1MKCnNzX3RhYmxlIDwtIGRhdGEuZnJhbWUoCiAgVmFyaWFibGUgICAgID0gdmFyX25hbWVzLAogIFRTUyAgICAgICAgICA9IFRTUywKICBXU1MgICAgICAgICAgPSBXU1MsCiAgQlNTICAgICAgICAgID0gQlNTLAogIFByb3BfQmV0d2VlbiA9IEJTUyAvIFRTUwopCgojIFphb2tyw7poxL51amVtZSBpYmEgbnVtZXJpY2vDqSBzdMS6cGNlCnNzX3RhYmxlX3JvdW5kIDwtIHNzX3RhYmxlCnNzX3RhYmxlX3JvdW5kWywgc2FwcGx5KHNzX3RhYmxlX3JvdW5kLCBpcy5udW1lcmljKV0gPC0KICByb3VuZChzc190YWJsZV9yb3VuZFssIHNhcHBseShzc190YWJsZV9yb3VuZCwgaXMubnVtZXJpYyldLCAzKQoKa2FibGUoc3NfdGFibGVfcm91bmQsCiAgICAgIGNhcHRpb24gPSAiVGFiLiA1OiBSb3prbGFkIHZhcmlhYmlsaXR5IChjZWxrb3bDoSwgdm7DunRyaSBrbGFzdHJvdiBhIG1lZHppIGtsYXN0cmFtaSkiKSAlPiUKICBrYWJsZV9zdHlsaW5nKGZ1bGxfd2lkdGggPSBGQUxTRSkKYGBgCgpOYSB6w6FrbGFkZSB0YWJ1xL5reSB2aWTDrW1lLCDFvmUgamVkbm90bGl2w6kgcHJlbWVubsOpIHNhIGzDrcWhaWEgdiB0b20sIGFrw7ogxI1hc8WlIHZhcmlhYmlsaXR5IGRva8Ohxb5lbWUgdnlzdmV0bGnFpSByb3pkaWVsbWkgbWVkemkga2xhc3RyYW1pLiBQcmVtZW5uw6kgcyB2ecWhxaHDrW0gcG9kaWVsb20gbWVkemkta2xhc3Ryb3ZlaiB2YXJpYWJpbGl0eSAodnnFocWhaWEgaG9kbm90YSBgUHJvcF9CZXR3ZWVuYCkgbGVwxaFpZSBzZXBhcnVqw7ogamVkbm90bGl2w6kgc2t1cGlueSBmaWxtb3YuCgotLS0KCiMjIENlbnRyb2lkeSDigJMgcHJpZW1lcm7DqSBob2Rub3R5IHByZW1lbm7DvWNoIHYga2xhc3Ryb2NoCgpaw6F2ZXJlxI1uw7ogaW50ZXJwcmV0w6FjaXUga2xhc3Ryb3YgdXJvYsOtbWUgbmEgesOha2xhZGUgdHp2LiAqKmNlbnRyb2lkb3YqKiwgdGVkYSBwcmllbWVybsO9Y2ggaG9kbsO0dCBzbGVkb3ZhbsO9Y2ggcHJlbWVubsO9Y2ggdiBqZWRub3RsaXbDvWNoIGtsYXN0cm9jaC4KCioqVGFiLiA2LiBDZW50cm9pZHkg4oCTIHByaWVtZXJuw6kgaG9kbm90eSBJTURiIGhvZG5vdGVuaWEsIGTEusW+a3kgYSByb3pwb8SNdHUgcG9kxL5hIGtsYXN0cm92KioKCmBgYHtyIGNlbnRyb2lkc30KZGVzY3JpcHRpdmVzIDwtIGZpbG15X2tsYXN0ZXJzICU+JQogIGdyb3VwX2J5KGtsYXN0ZXIpICU+JQogIHN1bW1hcmlzZSgKICAgIElNRGJfaG9kbm90ZW5pZV9tZWFuID0gbWVhbihJTURiX2hvZG5vdGVuaWVfbnVtLCBuYS5ybSA9IFRSVUUpLAogICAgRGx6a2FfbWVhbiAgICAgICAgICAgPSBtZWFuKERsemthLCBuYS5ybSA9IFRSVUUpLAogICAgUm96cG9jZXRfbWVhbiAgICAgICAgPSBtZWFuKFJvenBvY2V0LCBuYS5ybSA9IFRSVUUpCiAgKQoKIyB6YW9rcsO6aMS+dWplbWUgaWJhIG51bWVyaWNrw6kgc3TEunBjZSwgbmllIHN0xLpwZWMgImtsYXN0ZXIiCmRlc2NyaXB0aXZlc19yb3VuZCA8LSBkZXNjcmlwdGl2ZXMKZGVzY3JpcHRpdmVzX3JvdW5kWywgc2FwcGx5KGRlc2NyaXB0aXZlc19yb3VuZCwgaXMubnVtZXJpYyldIDwtCiAgcm91bmQoZGVzY3JpcHRpdmVzX3JvdW5kWywgc2FwcGx5KGRlc2NyaXB0aXZlc19yb3VuZCwgaXMubnVtZXJpYyldLCAyKQoKa2FibGUoZGVzY3JpcHRpdmVzX3JvdW5kLAogICAgICBjYXB0aW9uID0gIlRhYi4gNjogUHJpZW1lcm7DqSBob2Rub3R5IHNsZWRvdmFuw71jaCBwcmVtZW5uw71jaCB2IGplZG5vdGxpdsO9Y2gga2xhc3Ryb2NoIikgJT4lCiAga2FibGVfc3R5bGluZyhmdWxsX3dpZHRoID0gRkFMU0UpCmBgYAoKUG9yb3ZuYW7DrW0gY2VudHJvaWRvdiBtw7TFvmVtZSBjaGFyYWt0ZXJpem92YcWlIGplZG5vdGxpdsOpIGtsYXN0cmUuIEtsYXN0ZXIsIGt0b3LDvSBtw6EgbmFqdnnFocWhw60gcHJpZW1lcm7DvSByb3pwb8SNZXQgYSBkxLrFvmt1LCBtw7TFvmVtZSBpbnRlcnByZXRvdmHFpSBha28gc2t1cGludSB2ZcS+a8O9Y2gsIHByb2R1a8SNbmUgbsOhcm/EjW7DvWNoIGZpbG1vdiAoxI1hc3RvIGlkZSBvIHR6di4g4oCeYmxvY2tidXN0ZXJ54oCcKS4gTmFvcGFrLCBrbGFzdGVyIHMgbmnFvsWhw61tIHByaWVtZXJuw71tIHJvenBvxI10b20gYSBrcmF0xaFvdSBkxLrFvmtvdSBtw7TFvmUgcmVwcmV6ZW50b3ZhxaUgc2vDtHIgbWVuxaFpZSwga29tb3JuZWrFoWllIHNuw61ta3kuIEhvZG5vdGVuaWUgSU1EYiBuw6FtIHrDoXJvdmXFiCB1bW/FvsWIdWplIHJvemzDrcWhacWlLCBrdG9yw70ga2xhc3RlciBtw6EgdiBwcmllbWVyZSB2ecWhxaFpdSBkaXbDoWNrdSBvZG96dnUuCgotLS0KCiMjIFrDoXZlcgoKUHJlZGxvxb5lbsOhIGFuYWzDvXphIHNhIHphb2JlcsOhIHpobHVrb3ZhbsOtbSBmaWxtb3YgbmEgesOha2xhZGUgdHJvY2ggdWthem92YXRlxL5vdjogKipJTURiIGhvZG5vdGVuaWEsIGTEusW+a3kgZmlsbXUgYSB2w73FoWt5IHJvenBvxI10dSoqLiBOYSB6w6FrbGFkZSBoaWVyYXJjaGlja2VqIHpobHVrb3ZlaiBhbmFsw716eSAoV2FyZG92YSBtZXTDs2RhKSBzbWUgaWRlbnRpZmlrb3ZhbGkgdHJpIGtsYXN0cmUsIGt0b3LDqSB6ZHJ1xb51asO6IGZpbG15IHMgcG9kb2Juw71taSBjaGFyYWt0ZXJpc3Rpa2FtaS4KClogcHJha3RpY2vDvWNoIGTDtHZvZG92IHNtZSBwcmFjb3ZhbGkgaWJhIHMgKipuw6Fob2RuZSB2eWJyYW5vdSB2em9ya291IDIwIGZpbG1vdioqIHogcmVicsOtxI1rYSBJTURiIGEgZmlsbXkgc21lIHYgZ3JhZm9jaCBhIHRhYnXEvmvDoWNoIG96bmHEjW92YWxpIGliYSBpY2ggKip1bWllc3RuZW7DrW0gdiByZWJyw63EjWt1KiouIFTDvW0gc21lIGRvc2lhaGxpIGRvYnLDuiBwcmVoxL5hZG5vc8WlIHbDvXN0dXBvdiwgcHJpxI1vbSBwbG7DqSBuw6F6dnkgZmlsbW92IGplIG1vxb5uw6kgc3DDpHRuZSBkb2jEvmFkYcWlIHYgw7p2b2RuZWogdGFidcS+a2UuCgpBbmFsw716YSB1a8OhemFsYSwgxb5lOgoKLSB6dm9sZW7DoSBrb21iaW7DoWNpYSBwcmVtZW5uw71jaCB1bW/FvsWIdWplIHZ5dHZvcmnFpSB6aGx1a3kgZmlsbW92LCBrdG9yw6kgc2EgbMOtxaFpYSBuYWptw6QgeiBoxL5hZGlza2Egcm96cG/EjXR1IGEgZMS6xb5reSwKLSBwcmllbWVybsOpIGhvZG5vdHkgKGNlbnRyb2lkeSkga2xhc3Ryb3YgdW1vxb7FiHVqw7ogaW50ZXJwcmV0b3ZhxaUgamVkbm90bGl2w6kgc2t1cGlueSBha28gdmlhYyDEjWkgbWVuZWogbsOha2xhZG7DqSBhIGRsaMOpIGZpbG15IHMgcsO0em55bSBwcmllbWVybsO9bSBob2Rub3RlbsOtbSwKLSByb3prbGFkIHZhcmlhYmlsaXR5IG5hem5hxI11amUsIGt0b3LDqSBwcmVtZW5uw6kgbmFqdmlhYyBwcmlzcGlldmFqw7ogayBvZGRlbGVuaXUga2xhc3Ryb3YuCgpUYWvDvXRvIHByw61zdHVwIGplIHZ5dcW+aXRlxL5uw70gbmFwcsOta2xhZCBwcmkgKipzZWdtZW50w6FjaWkgZmlsbW92KiogdiByw6FtY2kgZmlsbW92w6lobyDFoXTDumRpYSwgc3RyZWFtb3ZhY2VqIHBsYXRmb3JteSDEjWkgcHJpIG9kcG9yw7rEjWFjw61jaCBzeXN0w6ltb2NoLiBOYSB6w6FrbGFkZSBwcsOtc2x1xaFub3N0aSBmaWxtdSBrdSBrbGFzdHJ1IGplIG1vxb5uw6kgbGVwxaFpZSBjaWVsacWlIG1hcmtldGluZywgcGzDoW5vdmHFpSByb3pwb8SNdHkgYnVkw7pjaWNoIHByb2pla3RvdiBhbGVibyBvZHBvcsO6xI1hxaUgZGl2w6Frb20gZmlsbXkgcyBwb2RvYm7DvW0gcHJvZmlsb20uIFpobHVrb3bDoSBhbmFsw716YSB0YWsgcHJlZHN0YXZ1amUgdcW+aXRvxI1uw70gbsOhc3Ryb2ogcHJpIHJvemhvZG92YW7DrSB2IHByb3N0cmVkw60gYXVkaW92aXp1w6FsbmVobyBwcmllbXlzbHUuCgo=