Analýza teplôt vybraných miest - rok 2000
Úvod
Predkladaná analýza sa zameriava na skúmanie teplotných údajov
9 vybraných európskych miest za rok
2000. Sledované premenné zahŕňajú priemernú, maximálnu
a minimálnu teplotu, ktoré charakterizujú teplotné pomery jednotlivých
miest.
Cieľom analýzy je identifikovať teplotné rozdiely medzi mestami,
porovnať ich teplotné profily a zoskupiť mestá podľa podobnosti
teplotných podmienok.
Načítanie knižníc a príprava údajov
library(tidyverse)
library(ggplot2)
library(dplyr)
library(cluster)
library(factoextra)
# Načítanie údajov
df <- read.csv("weather_prediction_dataset.csv")
# Transformácia stĺpca DATE na dátum
df$DATE <- as.Date(as.character(df$DATE), format = "%Y%m%d")
# Výber údajov len za rok 2000
df_2000 <- df %>%
filter(format(DATE, "%Y") == "2000")
# Výber 9 miest (bez Budapešti)
vybrate_mesta <- c('BASEL', 'DE_BILT', 'DRESDEN', 'HEATHROW',
'MUENCHEN', 'OSLO', 'ROMA', 'STOCKHOLM', 'TOURS')
Priemer za rok pre každé mesto
Na základe údajov za rok 2000 sme vypočítali priemerné hodnoty
teplotných ukazovateľov pre jednotlivé mestá.
# Získanie všetkých teplotných stĺpcov pre vybrané mestá
teplotne_stlpce <- character()
for(mesto in vybrate_mesta) {
teplotne_stlpce <- c(teplotne_stlpce,
grep(paste0(mesto, "_temp_"), names(df_2000), value = TRUE))
}
# Výber iba teplotných údajov
df_teploty <- df_2000[, c("DATE", "MONTH", teplotne_stlpce)]
# Výpočet priemerných hodnôt pre každé mesto
priemerne_teploty <- df_teploty %>%
summarise(across(contains("_temp_mean"), ~ round(mean(., na.rm = TRUE), 2))) %>%
pivot_longer(everything(), names_to = "Mesto", values_to = "Priemerna_teplota") %>%
mutate(Mesto = str_remove(Mesto, "_temp_mean")) %>%
arrange(desc(Priemerna_teplota))
maximalne_teploty <- df_teploty %>%
summarise(across(contains("_temp_max"), ~ round(mean(., na.rm = TRUE), 2))) %>%
pivot_longer(everything(), names_to = "Mesto", values_to = "Maximalna_teplota") %>%
mutate(Mesto = str_remove(Mesto, "_temp_max"))
minimalne_teploty <- df_teploty %>%
summarise(across(contains("_temp_min"), ~ round(mean(., na.rm = TRUE), 2))) %>%
pivot_longer(everything(), names_to = "Mesto", values_to = "Minimalna_teplota") %>%
mutate(Mesto = str_remove(Mesto, "_temp_min"))
# Spojenie všetkých údajov
data_mesta <- priemerne_teploty %>%
left_join(maximalne_teploty, by = "Mesto") %>%
left_join(minimalne_teploty, by = "Mesto")
print(data_mesta)
Analýza priemerov ukazuje, že Roma má najvyššie
priemerné, maximálne aj minimálne teploty, zatiaľ čo
Oslo má najnižšie hodnoty vo všetkých troch
kategóriách.
Korelačná matica
Korelačná matica vypočítaná pre teplotné ukazovatele ukazuje vzťahy
medzi nimi.
# Výber iba priemerných teplôt pre koreláciu
teplotne_data <- df_teploty %>%
select(contains('_temp_mean'))
# Výpočet korelačnej matice
cor_matrix <- cor(teplotne_data, use = "complete.obs")
cor_matrix_rounded <- round(cor_matrix, 2)
# Pomenovanie riadkov a stĺpcov
mesta_nazvy <- str_remove(colnames(teplotne_data), "_temp_mean")
rownames(cor_matrix_rounded) <- mesta_nazvy
colnames(cor_matrix_rounded) <- mesta_nazvy
print(cor_matrix_rounded)
BASEL DE_BILT DRESDEN HEATHROW MUENCHEN OSLO ROMA STOCKHOLM TOURS
BASEL 1.00 0.92 0.93 0.89 0.97 0.85 0.88 0.85 0.92
DE_BILT 0.92 1.00 0.91 0.92 0.90 0.86 0.81 0.85 0.91
DRESDEN 0.93 0.91 1.00 0.84 0.96 0.86 0.84 0.87 0.85
HEATHROW 0.89 0.92 0.84 1.00 0.86 0.84 0.81 0.82 0.94
MUENCHEN 0.97 0.90 0.96 0.86 1.00 0.84 0.86 0.84 0.88
OSLO 0.85 0.86 0.86 0.84 0.84 1.00 0.85 0.95 0.83
ROMA 0.88 0.81 0.84 0.81 0.86 0.85 1.00 0.86 0.84
STOCKHOLM 0.85 0.85 0.87 0.82 0.84 0.95 0.86 1.00 0.83
TOURS 0.92 0.91 0.85 0.94 0.88 0.83 0.84 0.83 1.00
Z matice vyplýva, že teploty medzi mestami sú vysoko kladne
korelované. Najvyššia korelácia je medzi MUENCHEN a
BASEL (r = 0.97), čo naznačuje veľmi podobné teplotné trendy.
Naopak, najnižšia korelácia je medzi ROMA a DE_BILT a
ROMA a HEATHROW (r = 0.81), čo odráža rozdielne
geografické polohy.
Matica vzdialenosti medzi mestami
Matica vzdialeností ukazuje, do akej miery sa teplotné profily miest
navzájom líšia.
# Výpočet euklidovskej vzdialenosti
dist_matrix <- dist(t(teplotne_data), method = "euclidean")
dist_matrix <- as.matrix(dist_matrix)
dist_matrix_rounded <- round(dist_matrix, 2)
rownames(dist_matrix_rounded) <- mesta_nazvy
colnames(dist_matrix_rounded) <- mesta_nazvy
print(dist_matrix_rounded)
BASEL DE_BILT DRESDEN HEATHROW MUENCHEN OSLO ROMA STOCKHOLM TOURS
BASEL 0.00 52.74 56.86 57.21 42.23 102.87 97.71 92.97 48.77
DE_BILT 52.74 0.00 61.45 42.84 63.19 88.58 119.56 80.74 52.74
DRESDEN 56.86 61.45 0.00 80.73 40.92 88.36 126.13 78.54 81.05
HEATHROW 57.21 42.84 80.73 0.00 76.04 100.74 109.37 92.47 40.42
MUENCHEN 42.23 63.19 40.92 76.04 0.00 93.43 122.21 85.83 73.99
OSLO 102.87 88.58 88.36 100.74 93.43 0.00 168.71 42.12 111.55
ROMA 97.71 119.56 126.13 109.37 122.21 168.71 0.00 155.18 95.23
STOCKHOLM 92.97 80.74 78.54 92.47 85.83 42.12 155.18 0.00 101.64
TOURS 48.77 52.74 81.05 40.42 73.99 111.55 95.23 101.64 0.00
Najväčšia vzdialenosť je medzi Romou a Oslom
(168.71), čo potvrdzuje výrazné teplotné rozdiely medzi týmito mestami.
Naopak, najmenšia vzdialenosť je medzi HEATHROW a TOURS
(40.42), čo naznačuje veľmi podobné teplotné podmienky.
Hierarchické zhlukovanie (Wardova metóda)
Na základe dendrogramu hierarchického zhlukovania možno pozorovať
nasledujúce:
# Z-škálovanie údajov
data_scaled <- scale(teplotne_data)
# Hierarchické zhlukovanie
hc <- hclust(dist(t(data_scaled), method = "euclidean"), method = "ward.D2")
# Vizualizácia dendrogramu
plot(hc, labels = mesta_nazvy, main = "Hierarchické zhlukovanie teplôt - Wardova metóda (2000)", cex = 0.8)
rect.hclust(hc, k = 4, border = "red")

Príslušnosť miest do klastrov
clusters <- cutree(hc, k = 4)
city_data$cluster <- as.factor(clusters)
print(city_data[, c("Mesto", "cluster")])
Mestá sa rozdeľujú do 4 hlavných klastrov:
Klaster 1: BASEL, DRESDEN, MUENCHEN -
stredoeurópske vnútrozemské mestá s kontinentálnym vplyvom
Klaster 2: DE_BILT, HEATHROW, TOURS -
západoeurópske mestá s oceánskym a prímorským vplyvom
Klaster 3: OSLO, STOCKHOLM - severské mestá s
chladnejším podnebím
Klaster 4: ROMA - jediné stredomorské mesto s
výrazne teplejším podnebím
Deskriptívne štatistiky výsledkov
Vnútro- a medziklastrová variabilita
data_matrix <- as.matrix(teplotne_data)
# Celková variabilita (TSS)
celkova_stredna_hodnota <- mean(data_matrix)
tss <- sum((data_matrix - celkova_stredna_hodnota)^2)
# Vnútroklastrová variabilita (WSS)
wss <- 0
for(k in 1:4) {
cluster_indices <- which(clusters == k)
if(length(cluster_indices) > 0) {
cluster_data <- data_matrix[, cluster_indices, drop = FALSE]
cluster_stredna_hodnota <- mean(cluster_data)
wss <- wss + sum((cluster_data - cluster_stredna_hodnota)^2)
}
}
bss <- tss - wss
prop_between <- bss / tss
variabilita <- data.frame(
Celkova_variancia = round(tss, 2),
Vnutroklastrová_variancia = round(wss, 2),
Medziklastrová_variancia = round(bss, 2),
Podiel_medzi = round(prop_between, 3)
)
print(variabilita)
Na základe výsledkov môžeme konštatovať, že celková variabilita
teplotných údajov je 149046.4. Z tejto celkovej variability je 14821.29
(9.9%) vysvetlených rozdelením miest do klastrov,
zatiaľčo 134225.1 (90.1%) variability zostáva v rámci
jednotlivých klastrov.
Podiel medziklastrovej variability 0.099 naznačuje, že zhlukovanie
vysvetľuje približne 10% celkovej variability údajov. Táto hodnota je
typická pre klimatologické údaje, kde mestá v rámci tej istej
geografickej oblasti majú podobné teplotné trendy, no stále existujú
výrazné rozdiely spôsobené lokálnymi vplyvmi, nadmorskou výškou a inými
faktormi.
Relatívne nízka medziklastrová variabilita potvrdzuje, že európske
mestá majú do určitej miery podobné teplotné profily, no identifikované
klastre napriek tomu zachytávajú významné regionálne rozdiely v
teplotných pomeroch.
Centroidy klastrov
# Vytvorenie kompletného data frame s klástrami
clusters <- cutree(hc, k = 4)
final_data <- data.frame(
Mesto = c("BASEL", "DE_BILT", "DRESDEN", "HEATHROW", "MUENCHEN", "OSLO", "ROMA", "STOCKHOLM", "TOURS"),
Klaster = as.factor(clusters)
) %>%
left_join(data_mesta, by = "Mesto")
# Výpočet centroidov
centroids <- final_data %>%
group_by(Klaster) %>%
summarise(
Pocet_miest = n(),
Priemerna_teplota = round(mean(Priemerna_teplota), 2),
Maximalna_teplota = round(mean(Maximalna_teplota), 2),
Minimalna_teplota = round(mean(Minimalna_teplota), 2),
.groups = 'drop'
)
print(centroids)
Na základe centroidov klastrov môžeme pozorovať charakteristiky
jednotlivých klastrov:
Klaster 1 (3 mestá): Priemerná teplota 10.98°C,
maximálna 15.22°C, minimálna 7.17°C - stredoeurópske vnútrozemské mestá
so strednými teplotami
Klaster 2 (3 mestá): Priemerná teplota 11.60°C,
maximálna 15.42°C, minimálna 7.72°C - západoeurópske mestá s mierne
vyššími teplotami vďaka oceánskemu vplyvu
Klaster 3 (2 mestá): Priemerná teplota 8.15°C,
maximálna 11.48°C, minimálna 5.24°C - severské mestá s výrazne nižšími
teplotami
Klaster 4 (1 mesto): Priemerná teplota 15.82°C,
maximálna 21.90°C, minimálna 11.47°C - stredomorské mesto s najvyššími
teplotami
Rozdiely medzi klastrami sú výrazné - medzi najteplejším a
najchladnejším klastrom je rozdiel 7.67°C v priemernej teplote. Klastre
dobre odrážajú geografickú polohu a klimatické pomery jednotlivých
miest.
Záver
Na základe vykonanej analýzy teplotných údajov 9
vybraných európskych miest za rok 2000 možno
konštatovať, že identifikované klastre dobre odrážajú geografickú a
klimatickú rozmanitosť Európy.
Zhluková analýza úspešne rozdelila mestá do 4
homogénnych skupín podľa ich teplotných charakteristík. Najvýraznejšie
rozdiely sú medzi stredomorským klimatom (Roma) a severskými oblasťami
(Oslo, Stockholm), pričom rozdiel v priemerných teplotách dosahuje
takmer 8°C. Stredoeurópske a západoeurópske mestá
tvoria dve samostatné skupiny so podobnými, no mierne odlišnými
teplotnými profilmi.
Hodnota medziklastrovej variability 9.9% naznačuje,
že napriek určitej podobnosti teplotných trendov medzi európskymi
mestami, identifikované klastre zachytávajú významné regionálne
rozdiely. Tieto výsledky poskytujú cenný vhľad do klimatických pomerov
Európy a môžu slúžiť ako podklad pre ďalšie klimatologické štúdie alebo
regionálne plánovanie.
LS0tDQp0aXRsZTogIsOabG9oYV84Ig0KYXV0aG9yOiAiQmMuIEtyeXN0eW5hIFZhc3lseW5hIg0KZGF0ZTogIk5vdmVtYmVyIDIwMjUiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgdGhlbWU6IHVuaXRlZA0KICAgIGhpZ2hsaWdodDogdGFuZ28NCiAgcGRmX2RvY3VtZW50Og0KICAgIHRvYzogdHJ1ZQ0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogdHJ1ZQ0KICAgIGRmX3ByaW50OiBwYWdlZA0KZWRpdG9yX29wdGlvbnM6DQogIG1hcmtkb3duOg0KICAgIHdyYXA6IDcyDQotLS0NCg0KIyBBbmFsw716YSB0ZXBsw7R0IHZ5YnJhbsO9Y2ggbWllc3QgLSByb2sgMjAwMA0KDQojIyDDmnZvZA0KUHJlZGtsYWRhbsOhIGFuYWzDvXphIHNhIHphbWVyaWF2YSBuYSBza8O6bWFuaWUgdGVwbG90bsO9Y2ggw7pkYWpvdiAqKjkqKiB2eWJyYW7DvWNoIGV1csOzcHNreWNoIG1pZXN0IHphIHJvayAqKjIwMDAqKi4gU2xlZG92YW7DqSBwcmVtZW5uw6kgemFoxZXFiGFqw7ogcHJpZW1lcm7DuiwgbWF4aW3DoWxudSBhIG1pbmltw6FsbnUgdGVwbG90dSwga3RvcsOpIGNoYXJha3Rlcml6dWrDuiB0ZXBsb3Ruw6kgcG9tZXJ5IGplZG5vdGxpdsO9Y2ggbWllc3QuDQoNCkNpZcS+b20gYW5hbMO9enkgamUgaWRlbnRpZmlrb3ZhxaUgdGVwbG90bsOpIHJvemRpZWx5IG1lZHppIG1lc3RhbWksIHBvcm92bmHFpSBpY2ggdGVwbG90bsOpIHByb2ZpbHkgYSB6b3NrdXBpxaUgbWVzdMOhIHBvZMS+YSBwb2RvYm5vc3RpIHRlcGxvdG7DvWNoIHBvZG1pZW5vay4NCg0KIyMgTmHEjcOtdGFuaWUga25pxb5uw61jIGEgcHLDrXByYXZhIMO6ZGFqb3YNCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShjbHVzdGVyKQ0KbGlicmFyeShmYWN0b2V4dHJhKQ0KDQojIE5hxI3DrXRhbmllIMO6ZGFqb3YNCmRmIDwtIHJlYWQuY3N2KCJ3ZWF0aGVyX3ByZWRpY3Rpb25fZGF0YXNldC5jc3YiKQ0KDQojIFRyYW5zZm9ybcOhY2lhIHN0xLpwY2EgREFURSBuYSBkw6F0dW0NCmRmJERBVEUgPC0gYXMuRGF0ZShhcy5jaGFyYWN0ZXIoZGYkREFURSksIGZvcm1hdCA9ICIlWSVtJWQiKQ0KDQojIFbDvWJlciDDumRham92IGxlbiB6YSByb2sgMjAwMA0KZGZfMjAwMCA8LSBkZiAlPiUNCiAgZmlsdGVyKGZvcm1hdChEQVRFLCAiJVkiKSA9PSAiMjAwMCIpDQoNCiMgVsO9YmVyIDkgbWllc3QNCnZ5YnJhdGVfbWVzdGEgPC0gYygnQkFTRUwnLCAnREVfQklMVCcsICdEUkVTREVOJywgJ0hFQVRIUk9XJywgDQogICAgICAgICAgICAgICAgICAgJ01VRU5DSEVOJywgJ09TTE8nLCAnUk9NQScsICdTVE9DS0hPTE0nLCAnVE9VUlMnKQ0KYGBgDQoNCiMjIFByaWVtZXIgemEgcm9rIHByZSBrYcW+ZMOpIG1lc3RvDQoNCk5hIHrDoWtsYWRlIMO6ZGFqb3YgemEgcm9rIDIwMDAgc21lIHZ5cG/EjcOtdGFsaSBwcmllbWVybsOpIGhvZG5vdHkgdGVwbG90bsO9Y2ggdWthem92YXRlxL5vdiBwcmUgamVkbm90bGl2w6kgbWVzdMOhLiANCg0KYGBge3J9DQojIFrDrXNrYW5pZSB2xaFldGvDvWNoIHRlcGxvdG7DvWNoIHN0xLpwY292IHByZSB2eWJyYW7DqSBtZXN0w6ENCnRlcGxvdG5lX3N0bHBjZSA8LSBjaGFyYWN0ZXIoKQ0KZm9yKG1lc3RvIGluIHZ5YnJhdGVfbWVzdGEpIHsNCiAgdGVwbG90bmVfc3RscGNlIDwtIGModGVwbG90bmVfc3RscGNlLCANCiAgICAgICAgICAgICAgICAgICAgICAgZ3JlcChwYXN0ZTAobWVzdG8sICJfdGVtcF8iKSwgbmFtZXMoZGZfMjAwMCksIHZhbHVlID0gVFJVRSkpDQp9DQoNCiMgVsO9YmVyIGliYSB0ZXBsb3Ruw71jaCDDumRham92DQpkZl90ZXBsb3R5IDwtIGRmXzIwMDBbLCBjKCJEQVRFIiwgIk1PTlRIIiwgdGVwbG90bmVfc3RscGNlKV0NCg0KIyBWw71wb8SNZXQgcHJpZW1lcm7DvWNoIGhvZG7DtHQgcHJlIGthxb5kw6kgbWVzdG8NCnByaWVtZXJuZV90ZXBsb3R5IDwtIGRmX3RlcGxvdHkgJT4lDQogIHN1bW1hcmlzZShhY3Jvc3MoY29udGFpbnMoIl90ZW1wX21lYW4iKSwgfiByb3VuZChtZWFuKC4sIG5hLnJtID0gVFJVRSksIDIpKSkgJT4lDQogIHBpdm90X2xvbmdlcihldmVyeXRoaW5nKCksIG5hbWVzX3RvID0gIk1lc3RvIiwgdmFsdWVzX3RvID0gIlByaWVtZXJuYV90ZXBsb3RhIikgJT4lDQogIG11dGF0ZShNZXN0byA9IHN0cl9yZW1vdmUoTWVzdG8sICJfdGVtcF9tZWFuIikpICU+JQ0KICBhcnJhbmdlKGRlc2MoUHJpZW1lcm5hX3RlcGxvdGEpKQ0KDQptYXhpbWFsbmVfdGVwbG90eSA8LSBkZl90ZXBsb3R5ICU+JQ0KICBzdW1tYXJpc2UoYWNyb3NzKGNvbnRhaW5zKCJfdGVtcF9tYXgiKSwgfiByb3VuZChtZWFuKC4sIG5hLnJtID0gVFJVRSksIDIpKSkgJT4lDQogIHBpdm90X2xvbmdlcihldmVyeXRoaW5nKCksIG5hbWVzX3RvID0gIk1lc3RvIiwgdmFsdWVzX3RvID0gIk1heGltYWxuYV90ZXBsb3RhIikgJT4lDQogIG11dGF0ZShNZXN0byA9IHN0cl9yZW1vdmUoTWVzdG8sICJfdGVtcF9tYXgiKSkNCg0KbWluaW1hbG5lX3RlcGxvdHkgPC0gZGZfdGVwbG90eSAlPiUNCiAgc3VtbWFyaXNlKGFjcm9zcyhjb250YWlucygiX3RlbXBfbWluIiksIH4gcm91bmQobWVhbiguLCBuYS5ybSA9IFRSVUUpLCAyKSkpICU+JQ0KICBwaXZvdF9sb25nZXIoZXZlcnl0aGluZygpLCBuYW1lc190byA9ICJNZXN0byIsIHZhbHVlc190byA9ICJNaW5pbWFsbmFfdGVwbG90YSIpICU+JQ0KICBtdXRhdGUoTWVzdG8gPSBzdHJfcmVtb3ZlKE1lc3RvLCAiX3RlbXBfbWluIikpDQoNCiMgU3BvamVuaWUgdsWhZXRrw71jaCDDumRham92DQpkYXRhX21lc3RhIDwtIHByaWVtZXJuZV90ZXBsb3R5ICU+JQ0KICBsZWZ0X2pvaW4obWF4aW1hbG5lX3RlcGxvdHksIGJ5ID0gIk1lc3RvIikgJT4lDQogIGxlZnRfam9pbihtaW5pbWFsbmVfdGVwbG90eSwgYnkgPSAiTWVzdG8iKQ0KDQpwcmludChkYXRhX21lc3RhKQ0KYGBgDQoNCkFuYWzDvXphIHByaWVtZXJvdiB1a2F6dWplLCDFvmUgKipSb21hKiogbcOhIG5hanZ5xaHFoWllIHByaWVtZXJuw6ksIG1heGltw6FsbmUgYWogbWluaW3DoWxuZSB0ZXBsb3R5LCB6YXRpYcS+IMSNbyAqKk9zbG8qKiBtw6EgbmFqbmnFvsWhaWUgaG9kbm90eSB2byB2xaFldGvDvWNoIHRyb2NoIGthdGVnw7NyacOhY2guIA0KDQojIyBLb3JlbGHEjW7DoSBtYXRpY2ENCg0KS29yZWxhxI1uw6EgbWF0aWNhIHZ5cG/EjcOtdGFuw6EgcHJlIHRlcGxvdG7DqSB1a2F6b3ZhdGVsZSB1a2F6dWplIHZ6xaVhaHkgbWVkemkgbmltaS4NCg0KYGBge3J9DQojIFbDvWJlciBpYmEgcHJpZW1lcm7DvWNoIHRlcGzDtHQgcHJlIGtvcmVsw6FjaXUNCnRlcGxvdG5lX2RhdGEgPC0gZGZfdGVwbG90eSAlPiUNCiAgc2VsZWN0KGNvbnRhaW5zKCdfdGVtcF9tZWFuJykpDQoNCiMgVsO9cG/EjWV0IGtvcmVsYcSNbmVqIG1hdGljZQ0KY29yX21hdHJpeCA8LSBjb3IodGVwbG90bmVfZGF0YSwgdXNlID0gImNvbXBsZXRlLm9icyIpDQpjb3JfbWF0cml4X3JvdW5kZWQgPC0gcm91bmQoY29yX21hdHJpeCwgMikNCg0KIyBQb21lbm92YW5pZSByaWFka292IGEgc3TEunBjb3YNCm1lc3RhX25henZ5IDwtIHN0cl9yZW1vdmUoY29sbmFtZXModGVwbG90bmVfZGF0YSksICJfdGVtcF9tZWFuIikNCnJvd25hbWVzKGNvcl9tYXRyaXhfcm91bmRlZCkgPC0gbWVzdGFfbmF6dnkNCmNvbG5hbWVzKGNvcl9tYXRyaXhfcm91bmRlZCkgPC0gbWVzdGFfbmF6dnkNCg0KcHJpbnQoY29yX21hdHJpeF9yb3VuZGVkKQ0KYGBgDQpaIG1hdGljZSB2eXBsw712YSwgxb5lIHRlcGxvdHkgbWVkemkgbWVzdGFtaSBzw7ogdnlzb2tvIGtsYWRuZSBrb3JlbG92YW7DqS4gTmFqdnnFocWhaWEga29yZWzDoWNpYSBqZSBtZWR6aSAqKk1VRU5DSEVOIGEgQkFTRUwqKiAociA9IDAuOTcpLCDEjW8gbmF6bmHEjXVqZSB2ZcS+bWkgcG9kb2Juw6kgdGVwbG90bsOpIHRyZW5keS4gTmFvcGFrLCBuYWpuacW+xaFpYSBrb3JlbMOhY2lhIGplIG1lZHppICoqUk9NQSBhIERFX0JJTFQqKiBhICoqUk9NQSBhIEhFQVRIUk9XKiogKHIgPSAwLjgxKSwgxI1vIG9kcsOhxb5hIHJvemRpZWxuZSBnZW9ncmFmaWNrw6kgcG9sb2h5Lg0KDQojIyBNYXRpY2EgdnpkaWFsZW5vc3RpIG1lZHppIG1lc3RhbWkNCg0KTWF0aWNhIHZ6ZGlhbGVub3N0w60gdWthenVqZSwgZG8gYWtlaiBtaWVyeSBzYSB0ZXBsb3Ruw6kgcHJvZmlseSBtaWVzdCBuYXZ6w6Fqb20gbMOtxaFpYS4NCg0KYGBge3J9DQojIFbDvXBvxI1ldCBldWtsaWRvdnNrZWogdnpkaWFsZW5vc3RpDQpkaXN0X21hdHJpeCA8LSBkaXN0KHQodGVwbG90bmVfZGF0YSksIG1ldGhvZCA9ICJldWNsaWRlYW4iKQ0KZGlzdF9tYXRyaXggPC0gYXMubWF0cml4KGRpc3RfbWF0cml4KQ0KZGlzdF9tYXRyaXhfcm91bmRlZCA8LSByb3VuZChkaXN0X21hdHJpeCwgMikNCg0Kcm93bmFtZXMoZGlzdF9tYXRyaXhfcm91bmRlZCkgPC0gbWVzdGFfbmF6dnkNCmNvbG5hbWVzKGRpc3RfbWF0cml4X3JvdW5kZWQpIDwtIG1lc3RhX25henZ5DQoNCnByaW50KGRpc3RfbWF0cml4X3JvdW5kZWQpDQpgYGANCk5hanbDpMSNxaFpYSB2emRpYWxlbm9zxaUgamUgbWVkemkgKipSb21vdSBhIE9zbG9tKiogKDE2OC43MSksIMSNbyBwb3R2cmR6dWplIHbDvXJhem7DqSB0ZXBsb3Ruw6kgcm96ZGllbHkgbWVkemkgdMO9bWl0byBtZXN0YW1pLiBOYW9wYWssIG5ham1lbsWhaWEgdnpkaWFsZW5vc8WlIGplIG1lZHppICoqSEVBVEhST1cgYSBUT1VSUyoqICg0MC40MiksIMSNbyBuYXpuYcSNdWplIHZlxL5taSBwb2RvYm7DqSB0ZXBsb3Ruw6kgcG9kbWllbmt5Lg0KDQojIyBIaWVyYXJjaGlja8OpIHpobHVrb3ZhbmllIChXYXJkb3ZhIG1ldMOzZGEpDQoNCk5hIHrDoWtsYWRlIGRlbmRyb2dyYW11IGhpZXJhcmNoaWNrw6lobyB6aGx1a292YW5pYSBtb8W+bm8gcG96b3JvdmHFpSBuYXNsZWR1asO6Y2U6DQoNCmBgYHtyfQ0KIyBaLcWha8OhbG92YW5pZSDDumRham92DQpkYXRhX3NjYWxlZCA8LSBzY2FsZSh0ZXBsb3RuZV9kYXRhKQ0KDQojIEhpZXJhcmNoaWNrw6kgemhsdWtvdmFuaWUNCmhjIDwtIGhjbHVzdChkaXN0KHQoZGF0YV9zY2FsZWQpLCBtZXRob2QgPSAiZXVjbGlkZWFuIiksIG1ldGhvZCA9ICJ3YXJkLkQyIikNCg0KIyBWaXp1YWxpesOhY2lhIGRlbmRyb2dyYW11DQpwbG90KGhjLCBsYWJlbHMgPSBtZXN0YV9uYXp2eSwgbWFpbiA9ICJIaWVyYXJjaGlja8OpIHpobHVrb3ZhbmllIHRlcGzDtHQgLSBXYXJkb3ZhIG1ldMOzZGEgKDIwMDApIiwgY2V4ID0gMC44KQ0KcmVjdC5oY2x1c3QoaGMsIGsgPSA0LCBib3JkZXIgPSAicmVkIikNCmBgYA0KDQojIyBQcsOtc2x1xaFub3PFpSBtaWVzdCBkbyBrbGFzdHJvdg0KYGBge3J9DQpjbHVzdGVycyA8LSBjdXRyZWUoaGMsIGsgPSA0KQ0KY2l0eV9kYXRhJGNsdXN0ZXIgPC0gYXMuZmFjdG9yKGNsdXN0ZXJzKQ0KDQpwcmludChjaXR5X2RhdGFbLCBjKCJNZXN0byIsICJjbHVzdGVyIildKQ0KYGBgDQpNZXN0w6Egc2Egcm96ZGXEvnVqw7ogZG8gNCBobGF2bsO9Y2gga2xhc3Ryb3Y6DQoNCi0gKipLbGFzdGVyIDEqKjogQkFTRUwsIERSRVNERU4sIE1VRU5DSEVOIC0gc3RyZWRvZXVyw7Nwc2tlIHZuw7p0cm96ZW1za8OpIG1lc3TDoSBzIGtvbnRpbmVudMOhbG55bSB2cGx5dm9tDQoNCi0gKipLbGFzdGVyIDIqKjogREVfQklMVCwgSEVBVEhST1csIFRPVVJTIC0gesOhcGFkb2V1csOzcHNrZSBtZXN0w6EgcyBvY2XDoW5za3ltIGEgcHLDrW1vcnNrw71tIHZwbHl2b20NCg0KLSAqKktsYXN0ZXIgMyoqOiBPU0xPLCBTVE9DS0hPTE0gLSBzZXZlcnNrw6kgbWVzdMOhIHMgY2hsYWRuZWrFocOtbSBwb2RuZWLDrW0NCg0KLSAqKktsYXN0ZXIgNCoqOiBST01BIC0gamVkaW7DqSBzdHJlZG9tb3Jza8OpIG1lc3RvIHMgdsO9cmF6bmUgdGVwbGVqxaHDrW0gcG9kbmViw61tDQoNCiMjIERlc2tyaXB0w612bmUgxaF0YXRpc3Rpa3kgdsO9c2xlZGtvdg0KDQojIyMgVm7DunRyby0gYSBtZWR6aWtsYXN0cm92w6EgdmFyaWFiaWxpdGENCg0KYGBge3J9DQpkYXRhX21hdHJpeCA8LSBhcy5tYXRyaXgodGVwbG90bmVfZGF0YSkNCg0KIyBDZWxrb3bDoSB2YXJpYWJpbGl0YSAoVFNTKQ0KY2Vsa292YV9zdHJlZG5hX2hvZG5vdGEgPC0gbWVhbihkYXRhX21hdHJpeCkNCnRzcyA8LSBzdW0oKGRhdGFfbWF0cml4IC0gY2Vsa292YV9zdHJlZG5hX2hvZG5vdGEpXjIpDQoNCiMgVm7DunRyb2tsYXN0cm92w6EgdmFyaWFiaWxpdGEgKFdTUykNCndzcyA8LSAwDQpmb3IoayBpbiAxOjQpIHsNCiAgY2x1c3Rlcl9pbmRpY2VzIDwtIHdoaWNoKGNsdXN0ZXJzID09IGspDQogIGlmKGxlbmd0aChjbHVzdGVyX2luZGljZXMpID4gMCkgew0KICAgIGNsdXN0ZXJfZGF0YSA8LSBkYXRhX21hdHJpeFssIGNsdXN0ZXJfaW5kaWNlcywgZHJvcCA9IEZBTFNFXQ0KICAgIGNsdXN0ZXJfc3RyZWRuYV9ob2Rub3RhIDwtIG1lYW4oY2x1c3Rlcl9kYXRhKQ0KICAgIHdzcyA8LSB3c3MgKyBzdW0oKGNsdXN0ZXJfZGF0YSAtIGNsdXN0ZXJfc3RyZWRuYV9ob2Rub3RhKV4yKQ0KICB9DQp9DQoNCmJzcyA8LSB0c3MgLSB3c3MNCnByb3BfYmV0d2VlbiA8LSBic3MgLyB0c3MNCg0KdmFyaWFiaWxpdGEgPC0gZGF0YS5mcmFtZSgNCiAgQ2Vsa292YV92YXJpYW5jaWEgPSByb3VuZCh0c3MsIDIpLA0KICBWbnV0cm9rbGFzdHJvdsOhX3ZhcmlhbmNpYSA9IHJvdW5kKHdzcywgMiksDQogIE1lZHppa2xhc3Ryb3bDoV92YXJpYW5jaWEgPSByb3VuZChic3MsIDIpLA0KICBQb2RpZWxfbWVkemkgPSByb3VuZChwcm9wX2JldHdlZW4sIDMpDQopDQoNCnByaW50KHZhcmlhYmlsaXRhKQ0KYGBgDQpOYSB6w6FrbGFkZSB2w71zbGVka292IG3DtMW+ZW1lIGtvbsWhdGF0b3ZhxaUsIMW+ZSBjZWxrb3bDoSB2YXJpYWJpbGl0YSB0ZXBsb3Ruw71jaCDDumRham92IGplIDE0OTA0Ni40LiBaIHRlanRvIGNlbGtvdmVqIHZhcmlhYmlsaXR5IGplIDE0ODIxLjI5ICgqKjkuOSUqKikgdnlzdmV0bGVuw71jaCByb3pkZWxlbsOtbSBtaWVzdCBkbyBrbGFzdHJvdiwgemF0aWHEvsSNbyAxMzQyMjUuMSAoKio5MC4xJSoqKSB2YXJpYWJpbGl0eSB6b3N0w6F2YSB2IHLDoW1jaSBqZWRub3RsaXbDvWNoIGtsYXN0cm92Lg0KDQpQb2RpZWwgbWVkemlrbGFzdHJvdmVqIHZhcmlhYmlsaXR5IDAuMDk5IG5hem5hxI11amUsIMW+ZSB6aGx1a292YW5pZSB2eXN2ZXTEvnVqZSBwcmlibGnFvm5lIDEwJSBjZWxrb3ZlaiB2YXJpYWJpbGl0eSDDumRham92LiBUw6F0byBob2Rub3RhIGplIHR5cGlja8OhIHByZSBrbGltYXRvbG9naWNrw6kgw7pkYWplLCBrZGUgbWVzdMOhIHYgcsOhbWNpIHRlaiBpc3RlaiBnZW9ncmFmaWNrZWogb2JsYXN0aSBtYWrDuiBwb2RvYm7DqSB0ZXBsb3Ruw6kgdHJlbmR5LCBubyBzdMOhbGUgZXhpc3R1asO6IHbDvXJhem7DqSByb3pkaWVseSBzcMO0c29iZW7DqSBsb2vDoWxueW1pIHZwbHl2bWksIG5hZG1vcnNrb3UgdsO9xaFrb3UgYSBpbsO9bWkgZmFrdG9ybWkuDQoNClJlbGF0w612bmUgbsOtemthIG1lZHppa2xhc3Ryb3bDoSB2YXJpYWJpbGl0YSBwb3R2cmR6dWplLCDFvmUgZXVyw7Nwc2tlIG1lc3TDoSBtYWrDuiBkbyB1csSNaXRlaiBtaWVyeSBwb2RvYm7DqSB0ZXBsb3Ruw6kgcHJvZmlseSwgbm8gaWRlbnRpZmlrb3ZhbsOpIGtsYXN0cmUgbmFwcmllayB0b211IHphY2h5dMOhdmFqw7ogdsO9em5hbW7DqSByZWdpb27DoWxuZSByb3pkaWVseSB2IHRlcGxvdG7DvWNoIHBvbWVyb2NoLg0KDQojIyMgQ2VudHJvaWR5IGtsYXN0cm92DQpgYGB7cn0NCmNsdXN0ZXJzIDwtIGN1dHJlZShoYywgayA9IDQpDQoNCmZpbmFsX2RhdGEgPC0gZGF0YS5mcmFtZSgNCiAgTWVzdG8gPSBjKCJCQVNFTCIsICJERV9CSUxUIiwgIkRSRVNERU4iLCAiSEVBVEhST1ciLCAiTVVFTkNIRU4iLCAiT1NMTyIsICJST01BIiwgIlNUT0NLSE9MTSIsICJUT1VSUyIpLA0KICBLbGFzdGVyID0gYXMuZmFjdG9yKGNsdXN0ZXJzKQ0KKSAlPiUNCiAgbGVmdF9qb2luKGRhdGFfbWVzdGEsIGJ5ID0gIk1lc3RvIikNCg0KY2VudHJvaWRzIDwtIGZpbmFsX2RhdGEgJT4lDQogIGdyb3VwX2J5KEtsYXN0ZXIpICU+JQ0KICBzdW1tYXJpc2UoDQogICAgUG9jZXRfbWllc3QgPSBuKCksDQogICAgUHJpZW1lcm5hX3RlcGxvdGEgPSByb3VuZChtZWFuKFByaWVtZXJuYV90ZXBsb3RhKSwgMiksDQogICAgTWF4aW1hbG5hX3RlcGxvdGEgPSByb3VuZChtZWFuKE1heGltYWxuYV90ZXBsb3RhKSwgMiksDQogICAgTWluaW1hbG5hX3RlcGxvdGEgPSByb3VuZChtZWFuKE1pbmltYWxuYV90ZXBsb3RhKSwgMiksDQogICAgLmdyb3VwcyA9ICdkcm9wJw0KICApDQoNCnByaW50KGNlbnRyb2lkcykNCmBgYA0KTmEgesOha2xhZGUgY2VudHJvaWRvdiBrbGFzdHJvdiBtw7TFvmVtZSBwb3pvcm92YcWlIGNoYXJha3RlcmlzdGlreSBqZWRub3RsaXbDvWNoIGtsYXN0cm92Og0KDQotICoqS2xhc3RlciAxKiogKDMgbWVzdMOhKTogUHJpZW1lcm7DoSB0ZXBsb3RhIDEwLjk4wrBDLCBtYXhpbcOhbG5hIDE1LjIywrBDLCBtaW5pbcOhbG5hIDcuMTfCsEMgLSBzdHJlZG9ldXLDs3Bza2Ugdm7DunRyb3plbXNrw6kgbWVzdMOhIHNvIHN0cmVkbsO9bWkgdGVwbG90YW1pDQoNCi0gKipLbGFzdGVyIDIqKiAoMyBtZXN0w6EpOiBQcmllbWVybsOhIHRlcGxvdGEgMTEuNjDCsEMsIG1heGltw6FsbmEgMTUuNDLCsEMsIG1pbmltw6FsbmEgNy43MsKwQyAtIHrDoXBhZG9ldXLDs3Bza2UgbWVzdMOhIHMgbWllcm5lIHZ5xaHFocOtbWkgdGVwbG90YW1pIHbEj2FrYSBvY2XDoW5za2VtdSB2cGx5dnUNCg0KLSAqKktsYXN0ZXIgMyoqICgyIG1lc3TDoSk6IFByaWVtZXJuw6EgdGVwbG90YSA4LjE1wrBDLCBtYXhpbcOhbG5hIDExLjQ4wrBDLCBtaW5pbcOhbG5hIDUuMjTCsEMgLSBzZXZlcnNrw6kgbWVzdMOhIHMgdsO9cmF6bmUgbmnFvsWhw61taSB0ZXBsb3RhbWkNCg0KLSAqKktsYXN0ZXIgNCoqICgxIG1lc3RvKTogUHJpZW1lcm7DoSB0ZXBsb3RhIDE1LjgywrBDLCBtYXhpbcOhbG5hIDIxLjkwwrBDLCBtaW5pbcOhbG5hIDExLjQ3wrBDIC0gc3RyZWRvbW9yc2vDqSBtZXN0byBzIG5hanZ5xaHFocOtbWkgdGVwbG90YW1pDQoNClJvemRpZWx5IG1lZHppIGtsYXN0cmFtaSBzw7ogdsO9cmF6bsOpIC0gbWVkemkgbmFqdGVwbGVqxaHDrW0gYSBuYWpjaGxhZG5lasWhw61tIGtsYXN0cm9tIGplIHJvemRpZWwgNy42N8KwQyB2IHByaWVtZXJuZWogdGVwbG90ZS4gS2xhc3RyZSBkb2JyZSBvZHLDocW+YWrDuiBnZW9ncmFmaWNrw7ogcG9sb2h1IGEga2xpbWF0aWNrw6kgcG9tZXJ5IGplZG5vdGxpdsO9Y2ggbWllc3QuDQoNCiMjIFrDoXZlcg0KDQpOYSB6w6FrbGFkZSB2eWtvbmFuZWogYW5hbMO9enkgdGVwbG90bsO9Y2ggw7pkYWpvdiAqKjkqKiB2eWJyYW7DvWNoIGV1csOzcHNreWNoIG1pZXN0IHphIHJvayAqKjIwMDAqKiBtb8W+bm8ga29uxaF0YXRvdmHFpSwgxb5lIGlkZW50aWZpa292YW7DqSBrbGFzdHJlIGRvYnJlIG9kcsOhxb5hasO6IGdlb2dyYWZpY2vDuiBhIGtsaW1hdGlja8O6IHJvem1hbml0b3PFpSBFdXLDs3B5LiANCg0KWmhsdWtvdsOhIGFuYWzDvXphIMO6c3BlxaFuZSByb3pkZWxpbGEgbWVzdMOhIGRvICoqNCoqIGhvbW9nw6lubnljaCBza3Vww61uIHBvZMS+YSBpY2ggdGVwbG90bsO9Y2ggY2hhcmFrdGVyaXN0w61rLiBOYWp2w71yYXpuZWrFoWllIHJvemRpZWx5IHPDuiBtZWR6aSBzdHJlZG9tb3Jza8O9bSBrbGltYXRvbSAoUm9tYSkgYSBzZXZlcnNrw71taSBvYmxhc8WlYW1pIChPc2xvLCBTdG9ja2hvbG0pLCBwcmnEjW9tIHJvemRpZWwgdiBwcmllbWVybsO9Y2ggdGVwbG90w6FjaCBkb3NhaHVqZSB0YWttZXIgKio4wrBDKiouIFN0cmVkb2V1csOzcHNrZSBhIHrDoXBhZG9ldXLDs3Bza2UgbWVzdMOhIHR2b3JpYSBkdmUgc2Ftb3N0YXRuw6kgc2t1cGlueSBzbyBwb2RvYm7DvW1pLCBubyBtaWVybmUgb2RsacWhbsO9bWkgdGVwbG90bsO9bWkgcHJvZmlsbWkuDQoNCkhvZG5vdGEgbWVkemlrbGFzdHJvdmVqIHZhcmlhYmlsaXR5ICoqOS45JSoqIG5hem5hxI11amUsIMW+ZSBuYXByaWVrIHVyxI1pdGVqIHBvZG9ibm9zdGkgdGVwbG90bsO9Y2ggdHJlbmRvdiBtZWR6aSBldXLDs3Bza3ltaSBtZXN0YW1pLCBpZGVudGlmaWtvdmFuw6kga2xhc3RyZSB6YWNoeXTDoXZhasO6IHbDvXpuYW1uw6kgcmVnaW9uw6FsbmUgcm96ZGllbHkuIFRpZXRvIHbDvXNsZWRreSBwb3NreXR1asO6IGNlbm7DvSB2aMS+YWQgZG8ga2xpbWF0aWNrw71jaCBwb21lcm92IEV1csOzcHkgYSBtw7TFvnUgc2zDusW+acWlIGFrbyBwb2RrbGFkIHByZSDEj2FsxaFpZSBrbGltYXRvbG9naWNrw6kgxaF0w7pkaWUgYWxlYm8gcmVnaW9uw6FsbmUgcGzDoW5vdmFuaWUuDQoNCg==