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

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