Podatkovne tabele (angl. Data Frames)
Podatkovne tabele (ali pa v dobesednem prevodu okviri) so pomembna
nadgradnja osnovnega tipa seznamov: tabela je pravzaprav predstavljena
kot seznam njenih stolpcev (tudi vsak stolpec tabele je seznam).
Namenjene so shranjevanju množice podatkov (angl. data set), ki
so predmet analize.
Pogosta navada pri analizi podatkov je, da vrstice ustrezajo
opazovanim enotam (angl. units). Enote lahko
imenujemo tudi primerki oz. primeri (angl. examples). V tem
primeru stolpci ustrezajo opazovanim ali izmerjenim
lastnostim (angl. attributes)
enot, ki jih imenujemo spremenljivke
(angl. variables). Spremenljivka torej podaja in tudi vsebuje
vse opazovane ali izmerjene vrednosti ustreznega atributa enote.
Poglejmo si primer preproste podatkovne tabele v kateri so enote
osebe:
osebe_df = data.frame(
ime = c("Mojca", "Janez", "Špela", "Vito"),
spol = factor(c("ž", "m", "ž", "m"), levels=c("m", "ž")),
starost = c(30, 48, 63, 27)
)
osebe_df
Podatkovna tabela vsebuje podatke o štirih opazovanih enotah
(osebah). Vsaka oseba je opisana s tremi lastnostmi, ime,
spol in starost, torej podatkovna množica ima tri
spremenljivke z istimi imeni, ime, spol in
starost.
Domena spremenljivke je množica možnih vrednosti te
spremenljivke. Tako je, na primer, domena spremenljivke spol
množica {m, ž}, domena spremenljivke
starost pa množica naravnih števil. Že takoj je jasno, da
so lahko spremenljivke dveh tipov:
- kvantitativne spremenljvike imajo (lahko tudi neskončno)
domeno številčnih vrednosti,
- kvalitativne spremenljivke imajo končno domeno, t.j.,
množico vnaprej znanih vrednosti.
Vrednosti kvantitativnih spremenljivk so tipa integer ali
dobule, vrednosti kvalitativnih pa so tipa factor ali
string.
Preverimo sedaj kakšnega tipa je podatkovna tabela v R-ju in kakšne
so lastnosti tega tipa:
typeof(osebe_df)
[1] "list"
attributes(osebe_df)
$names
[1] "ime" "spol" "starost"
$class
[1] "data.frame"
$row.names
[1] 1 2 3 4
Dva pomembna atributa podatkovne tabele sta:
names je vektor nizov znakov, ki ustrezajo imenom
stolpcev.
row.namesje običajno vektor celih števil oblike
1:n, kjer je n število vrstic v tabeli. Lahko
bi bil tudi vektor nizov znakov, kar omogoča poimenovanje vrstic. A
običajno se tega ne poslužujemo: če imajo vrstice imena in torej imajo
imena nekakšen podatkovni pomen, potem imenom namenimo (prvi) stolpec v
tabeli.
Vrednosti teh atributov lahko dobimo s funkcijami
rownames in colnames (slednjo lahko zamenjamo
z names, a temu se načrtno izogibamo, da bo koda bolj
berljiva). Število vrstic in stolpcev dobimo s funkcijama
nrow in ncol:
paste("Dimenzije tabele osebe_df:",
nrow(osebe_df), "vrstice in",
ncol(osebe_df), "stolpci."
)
[1] "Dimenzije tabele osebe_df: 4 vrstice in 3 stolpci."
rownames(osebe_df)
[1] "1" "2" "3" "4"
colnames(osebe_df)
[1] "ime" "spol" "starost"
Stolpci v tabeli so seznami, kar omogoča veliko fleksibilnost pri
tipih elementov tabele: vsak element je lahko svojega tipa. Kljub temu,
za lažjo analizo in vizualizacijo podatkov se običajno omejimo na take
tabele, kjer ima vsak stolpec elemente istega tipa.
Podatkovne tabele tipa tibble
Osnovni podatkovni tip data.frame v R-ju je lahko
nadležen, predvsem zato, ker upošteva vse navade R-ja glede prisile in
dinamičnega prilagajanja tipov. Tako, na primer, če je rezultat
indeksiranja stolpcev v tabeli en sam stolpec, ga bo R avtomatsko
spremenil v vektor ali seznam - v primeru dveh ali več stolpcev bo pa
rezultat še vedno tabela.
Zato pogosto uporabljamo podatkovne tabele tipa tibble,
ki jih implementira istoimenski
CRAN paket:
library(tibble)
osebe_dft = as_tibble(osebe_df)
osebe_dft
V nadaljevanju si oglejmo nekaj razlik med tibble in
data.frame.
Imena stolpcev v tibble so bolj fleksibilna, kot tista v
data.frame, predvsem pa se ne spremenijo kar samodejno brez
opozoril:
colnames(data.frame(`1` = c(1,2,3)))
[1] "X1"
colnames(tibble(`1` = c(1,2,3)))
[1] "1"
Pri kreiranju tabele tipa tibble so stolpci, ki
vsebujejo nize znakov, samodejno pretvorijo v faktorje, kar je lahko
prednost ali slabost. Preverite še enkrat primer tabele zgoraj in
opazili boste, da smo za pretvorbo stolpca v faktor poskrbeli sami med
ustvarjanjem podatkovne tabele.
Nadalje, podatkovni tip tibble ne omogoča poimenovanje
vrstic v tabeli. Kot smo zapisali zgoraj je poimenovanje stolpcev itak
nezaželeno.
Nenazadnje, tibble omogoča sklicevanje na prejšnje
stolpce pri tvorjenju novih:
starost_v_letih = function(datum_rojstva) {
as.integer(
as.numeric(difftime(Sys.Date(), datum_rojstva, units="weeks")) / 52.25
)
}
osebe_dft = tibble(
ime = c("Mojca", "Janez", "Špela", "Vito"),
spol = factor(c("ž", "m", "ž", "m"), levels=c("ž", "m")),
datum.rojstva = as.Date(c("1991-09-15", "1973-02-04", "1958-07-30", "1995-05-27")),
starost = starost_v_letih(datum.rojstva)
)
osebe_dft
Analizirajte pozorno funkcijo za izračun starosti osebe v letih na
osnovi podanega datuma rojstva.
Indeksiranje podatkovnih tabel
Podatkovne tabele, tako kot matrike, lahko indeksiramo z navajanjem
indeksa vrstice i in nato indeksa stolpca j,
torej df[i, j]. Če kakšnega izmed teh dveh indeksov pustimo
praznega, dobimo kot rezultat vse vrstice oz. stolpce podatkovne tabele.
Pozor: rezultat indeksiranja je tabela, le če uporabljamo podatkovne
tabele tipa tibble ali pa nastavimo argument
drop=FALSE:
osebe_dft[c(1,2), ]
osebe_dft[c(1,1,1,2), ]
osebe_dft[, -3]
osebe_dft[c(1,2), "spol"]
osebe_df[c(1,2), "spol"] # opazuj razliko s prejšnjim rezultatom
[1] ž m
Levels: m ž
osebe_df[c(1,2), "spol", drop=FALSE]
Če drop=FALSE ne nastavimo in je df
podatkovna tabela tipa data frame, se bo tip rezultata
(prisilno) prilagodil v vektor (če je rezultat indeksiranja ena vrstica
ali en stolpec tabele) oziroma skalar (če je rezultat indeksiranja
vrednost ene celice v tabeli).
Za indeksiranje podatkovnih tabel lahko uporabljamo vse načine
indeksiranja, ki jih poznamo iz indeksiranja vektorjev in seznamov. Če
si zraven zapomnimo, da je tabela pravzaprav seznam stolpcev, vemo kako
pridemo do vsebine enega stolpca na tri različne načine (najbolj pogosto
uporabljamo zadnjega, ki je kratek in hkrati berljiv):
osebe_dft[[1]]
[1] "Mojca" "Janez" "Špela" "Vito"
osebe_dft[["starost"]]
[1] 31 49 64 27
osebe_dft$datum.rojstva
[1] "1991-09-15" "1973-02-04" "1958-07-30" "1995-05-27"
V nadaljevanju si oglejmo še nekaj načinov indeksiranja, ki jih
poznamo še od prej, a tukaj jih ponazorimo na podatkovnih tabelah.
Lahko indeksiramo z logičnim pogojem zato, da dobimo podmnožico
vrstic, ki pogoju ustrezajo:
osebe_dft[osebe_dft$starost > 45, ]
osebe_dft$starost > 45 # da ugotovimo zakaj indeksiranje zgoraj pravilno deluje
[1] FALSE TRUE TRUE FALSE
Z indeksiranjem si lahko pomagamo tudi pri brisanju stolpcev (ali
vrstic) tabele:
osebe_dft1 = osebe_dft[, -3]
osebe_dft1
Z indeksiranjem lahko tudi urejamo vrstice v tabeli v naraščajočem
ali padajočem vrstnem redu, tako da v indksu uporabimo funkcijo
order:
order(osebe_dft$starost)
[1] 4 1 2 3
osebe_dft[order(osebe_dft$starost), ]
osebe_dft[order(osebe_dft$starost, decreasing = TRUE), ]
osebe_dft[order(osebe_dft$spol), ]
osebe_dft[order(osebe_dft$spol, -osebe_dft$starost), ]
osebe_dft[, order(colnames(osebe_dft))]
Z indeksiranjem tudi enostavno vzorčimo primere s ponavaljanjem ali
brez
osebe_dft[sample(nrow(osebe_dft), 2), ]
osebe_dft[sample(nrow(osebe_dft), 10, replace=T), ]
Od podatkov do podatkovnih tabel
Podatki o opazovanih enotah lahko zbiramo na različne načine. Lahko
jih pripravimo iz lastnih meritev in jih sami vnašamo v R v obliki
vektorjev in seznamov. Lahko jih pridobimo v obliki preglednic Excel iz
nekega standardnega vira statističnih podatkov, kot je podatkovna baza SiStat Statističnega
urada Republike Slovenije ali pa podatkovna baza
Eurostat, evropskega statističnega urada. Spletna enciklopedija Wikipedia je tudi bogat vir
podatkovnih tabel, kot je na primer tabela
s podatki o bruto domačem proizvodu držav.
Kot bomo spoznali v nadaljevanju, R ponuja priročne funkcije za
branje podatkov iz datotek s preglednicami Excel ali iz podatkovnih
tabel v spletnih enciklopediji Wikipedia. Pridobivanje takih podatkov in
njihovo branje oziroma transformacija v R-jevske podatkovne tabele je
zato relativno enostavno. Ker se običajno zaplete je, da imajo podatki
pridobljeni iz različnih podatkovnih virov različno
strukturo.
Struktura in pomen podatkov
Poglejmo si preprosti primer. Denimo, da opazujemo nekaj objektov,
pravokotnikov in kvadratov in pri tem merimo njihove dimenzije.
Rezultate meritev smo si zapisali v naslednji seznam:
objekti = list(
o1 = list("kvadrat", 3),
o2 = list("pravokotnik", c(2, 7)),
o3 = list("pravokotnik", c(9, 4)),
o4 = list("kvadrat", 8),
o5 = list("pravokotnik", c(6, 5))
)
Preden se lotimo analize podatkov o opisanih objektih, bi jih morali
pretvoriti v podatkovno tabelo. A podatkovne tabele so lahko različnih
oblik, ki jim rečemo tudi podatkovne strukture (angl. data
structures) in vsaka ta oblika oziroma struktura bi lahko bila
sprejemljiv način zapisa podatkov v podatkovno tabelo. Poglejmo si nekaj
primerov podatovnih tabel za podane objekte.
Prva podatkovna tabela umesti objekte v vrstice tabele, tri njihove
lastnosti v stolpce:
oblika_objekta = function(o) {
o[[1]]
}
dimenzija_objekta = function(o) {
paste0(o[[2]], collapse="-")
}
objekti_df1 = data.frame(
ime = names(objekti),
oblika = sapply(objekti, FUN=oblika_objekta),
dimenzija = sapply(objekti, FUN=dimenzija_objekta)
)
objekti_df1
Ta podatkovna struktura določa tudi pomen podatkov
(angl. data semantics): enota opazovanja je objekt, njegove
lastnosti merimo s tremi spremenljivkami, ime,
oblika in dimenzija.
Predstaviti dimenzijo posameznega objekta kot niz znakov ni ravno
dobra popotnica za morebitne nadaljnje izračune pri analizi podatkov.
Predstavljajte si, da bi si želeli v naslednjem koraku analize
izračunati površino posameznega objekta. Zato bi potrebovali širino in
višino objekta posebej. Da to naredimo, bi lahko podatkom pripisali
drugo strukturo oziroma podatkovno tabelo:
visina_objekta = function(o) {
ifelse(length(o[[2]]) == 1, o[[2]], o[[2]][1])
}
sirina_objekta = function(o) {
ifelse(length(o[[2]]) == 1, o[[2]], o[[2]][2])
}
objekti_df2 = data.frame(
ime = names(objekti),
oblika = sapply(objekti, FUN=oblika_objekta),
visina = sapply(objekti, FUN=visina_objekta),
sirina = sapply(objekti, FUN=sirina_objekta)
)
objekti_df2
Tako podatkovno tabelo bi po vsej verjetnosti pripravili, če bi
uporabljali program Excel. Pomen podatkov v tej podatkovni tabeli je
podoben kot pomen v prejšnji, le da tokrat ima vsaka enota štiri
lastnosti oziroma spremenljivke ime, oblika,
visina in sirina.
Obe podatkovni tabeli pripravljeni zgoraj domnevajo, da so objekti
zapisane po vrsticah, kar je običajna domneva pri načrtovanju strukture
podatkov: vrstice v tabeli ustrezajo posameznim enotam (objektom).
Vsekakor bi pa lahko pripravili podatkovno tabelo, kjer bi objekti bili
spravljeni v stolpce, njihove lastnosti pa v vrstice:
objekti_df3 = data.frame(
"X" = c("ime", "oblika", "visina", "sirina"),
o1 = c("o1", "kvadrat", 3, 3),
o2 = c("o2", "pravokotnik", 2, 7),
o3 = c("o3", "pravokotnik", 9, 4),
o4 = c("o4", "kvadrat", 8, 8),
o5 = c("o5", "pravokotnik", 6, 5)
)
objekti_df3
S tem vprašanjem se soočamo na začetku vsakega projekta analize
podatkov. Odgovorov na to vprašanje, kot kaže zgornji primer, veliko.
Čeprav je primer zelo enostaven. Še več bi jih bilo, če bi obravnavali
realističen primer bolj zapletenih podatkov.
Ali obstaja standarden odgovor na to vprašanje? Obstaja. Morebiti ne
ravno standard, temveč dogovor, ki mu rečemo urejeni podatki
(angl. tidy data). To je dogovor o standardni obliki
zapisovanja podatkov v podatkovne tabele, ki omogoča njihovo enostavno
analizo s pomočjo funkcij iz zbirke paketov, ki jih vsebuje knjižnica tidyverse.
Urejeni podatki (angl. tidy data)
Dogovor o urejenih podatkih določa tri enostavna pravila za
podatkovne tabele.
- Vsaka spremenljivka tvori stolpec (rečemo lahko tudi ustreza
stolpcu).
- Vsako opazovanje tvori vrstico.
- Vsak tip opazovane enote tvori tabelo.
Pri načrtovanju naših podatkovnih tabel začnemo s premislekom o
tretjem pravilu. Ključno vprašanje pri tem premisleku je Kateri
tipi opazovanih enot se pojavljajo v naših podatkih? Glede na
to, kakšen je odgovor na to vprašanje, lahko za objekte iz našega
primera sestavimo tabele urjenih podatkov na dva načina.
Prvi način: dva tipa opazovanih enot
V našem primeru je očiten tip opazovane enote objekt. A njega
opazujemo na dva načina, po eni strani opazujemo kvalitativno lastnost
objekta oblika. Drugi način opazovanja je kvantitativna
dimenzija objekta, ki jo merimo skozi dva različna vidika
širine in višine. Ker imamo dva načina opazovanja objektov, lahko
pripravimo dve tabeli:
- tabela
oblika bo vsakemu objektu pripisala obliko;
- tabela
dimenzija bo vsakemu paru objekta in vidika
dimenzije (širine ali višine) pripisala izmerjeno vrednost tega vidika
dimenzije.
Tabela oblika ima torej dve kvalitativni
spremenljivki:
objekt, ki določi ime objekta in ima torej domeno
{o1, o2, o3, o4 in
o5};
vrednost, ki določi obliko objekta in ima domeno
{kvadrat, pravokotnik}.
Med njima obstaja pomembna razlika. Prva je fiksna
spremenljivka, katere vrednost določa predmet opazovanja, v
našem primeru objekt. Imenujemo jo tudi dimenzijska
spremenljivka. Druga je merjena spremenljivka:
njeno vrednost določimo z opazovanjem ali merjenjem izbranega predmeta
opazovanja, v našem primeru z opazovanjem (ugotavljanjem) oblike
objekta. Imenujemo jo tudi vrednostna
spremenljivka.
Sestavimo torej prvo tabelo oblika:
oblika_df = data.frame(
objekt = names(objekti),
vrednost = sapply(objekti, FUN=oblika_objekta)
)
oblika_df
Druga tabela dimenzija ima dve kvalitativni in eno
kvantitativno spremenljivko:
objekt, ki ima enako domeno kot zgoraj, torej
{o1, o2, o3, o4 in
o5};
vidik, ki ima domeno {širina,
višina};
vrednost, ki poda vrednost izbranega vidika dimenzije
(višine ali širine) z opazovani objekt.
V tej tabeli sta prvi dve spremenljivki dimenzijski (fiksni), saj
njihovi vrednosti določata za kateri objekt in katero meritev
opazovanega objekta gre, ne beleži pa vrednosti merjenja. Zadnja
spremenljivka je pa merjena (vrednostna), saj beleži vrednosti
opravljenih merjenj izbranega vidika dimenzije objekta.
dimenzija_df = expand.grid(
objekt = names(objekti),
vidik = c("širina", "višina"),
vrednost = NA
)
dimenzija_df$vrednost = c(
sapply(objekti, FUN=sirina_objekta),
sapply(objekti, FUN=visina_objekta)
)
dimenzija_df
Funkcija expand.grid v kodi zgoraj poskrbi za to, da
sestavimo podatkovno tabelo kot kartezični produkt domen dimenzijskih
spremenljivk objekt in vidik, kjer so vse
vrednosti merjene spremenljivke enake NA. Ker je moč domene
spremenljivke objekt enaka 5, moč domene vidik
2, in je moč (začasne) domene merjene spremenljivke
vrednost 1, ima podatkovna tabela, ki je rezultat klica
funkcije expand.grid 5 * 2 * 1 = 10
vrstic.
Takoj zatem izračunamo še vrednosti zadnje spremenljivke
vrednost v tabeli. Pri tem upoštevamo, da se prvih pet
vrstic v kartezičnem produktu nanaša na širino, preostalih pet na višino
objektov.
Običajni vrstni red stolpcev v tabeli z urejenimi
podatki je tak, da dimenzijskim spremenljivkam sledijo
merjene. Vrstice so pa običajno
urejene po naraščajoči vrednosti prve spremenljivke,
nato druge, tretje in tako naprej.
Stolpci v tabeli dimeznija sledijo običajnemu vrstnemu
redu, vrstice pa ne. Lahko jih uredimo takole:
dimenzija_df = dimenzija_df[order(dimenzija_df$objekt, dimenzija_df$vidik), ]
dimenzija_df
POZOR! V primeru, ko so podatki razdeljeni v več podatkovnih tabel,
moramo poskrbeti, da si te tabele delijo vsaj kakšno dimenzijsko
spremenljivko, ki nam bo omogočila, da zberemo podatke o eni opazovani
enoti skupaj. V zgornjem primeru je taka spremenljivka
objekt, ki nam omogoča da povežemo podatek o obliki objekta
(iz tabele oblika) s podatki o njegovih dimenzijah
(zapisanimi v tabeli dimenzija).
Drugi način: trije tipi opazovanih enot
Pri sestavljanju tabele z urejenimi podatki dimenzija
smo domnevali, da sta širina in višina le dva različna vidika meritve
dimenzije objekta. Če bi širino in višino določili kot dva različna tipa
opazovanih enot, bi končali s tremi tabelami urejenih podatkov. Prva
tabela oblika bi ostala enaka kot prej, tabelo
dimenzija bi zamenjali z dvema tabelama sirina
in visina. Obe tabeli bi imeli enak nabor dveh spremenljivk
objekt in vrednost:
sirina_df = data.frame(
objekt = names(objekti),
vrednost = sapply(objekti, FUN=sirina_objekta)
)
sirina_df
visina_df = data.frame(
objekt = names(objekti),
vrednost = sapply(objekti, FUN=visina_objekta)
)
visina_df
Za vsako podatkovno tabelo z urejenimi podatki, ki ima \(d+1\) stolpcev, velja, da prvih \(d\) stolpcev ustreza dimenzijskim (fiksnim)
spremenljivkam z domenami \(D_1, D_2, \ldots,
D_{d}\), zadnji stolpec pa merjeni (vrednostni) spremenljivki z
domeno \(D_{d+1}\). Lahko torej rečemo,
da urejeni podatki podajo tabelarično definicijo funkcije \(f: D_1 \times D_2 \times \ldots \times D_{d} \to
D_{d+1}\). Funkcija \(f\) za
podane vrednosti dimenzijskih spremenljivk vrne vrednost merjene
spremenljivke.
Naj na koncu omenimo še eno možnost za podatkovne tabele z urejenimi
podatki. Če imamo \(m\) tabel z istimi
dimenzijskimi spremenljivkami z domenami \(D_1, D_2, \ldots, D_d\), jih lahko združimo
v eno podatkovno tabelo z urejenimi podatki, ki ima \(d\) dimenzijskih in \(m\) merjenih spremenljivk. Taka tabela
definira funkcijo \(g: D_1 \times D_2 \times
\ldots \times D_{d} \to D_{d+1} \times D_{d+2} \times \ldots \times
D_{d+m}\), ki za podane vrednosti dimenzijskih spremenljivk vrne
vrednosti vseh \(m\)-tih merjenih
spremenljivk hkrati.
V zgornjem primeru z objekti, vse tri podatkovne tabele z urejenimi
podatki oblika, visina in sirina
imajo isto dimenzijsko spremenljivko objekt in jih torej
lahko združimo v eno podatkovno tabelo objekti z eno
dimenzijsko spremenljivko objekt in tremi merjenimi
spremenljivkami oblika, sirina in
visina takole:
objekti_df4 = data.frame(
objekt = names(objekti),
oblika = sapply(objekti, FUN=oblika_objekta),
sirina = sapply(objekti, FUN=sirina_objekta),
visina = sapply(objekti, FUN=visina_objekta)
)
objekti_df4
Ta zadnja različica podatkovne tabele tudi sledi standardom urejenih
podatkov tidy data, pri čemer predpostavimo, da imamo opravka z
enim tipom opazovane enote objekt, različne načine opazovanja in meritev
enot (objektov) pa določimo kot različne lastnosti opazovanih enot
oziroma spremenljivke.
Standardi poimenovanj
Imena tabel in spremenljivk (stolpcev) naj bodo zapisane z malimi
črkami. Če je ime sestavljeno iz več besed, vmesne presledke zamenjamo z
_, na primer ime_in_priimek, ne pa
ime in priimek. V imenih tabel in spremenljivk se izogibamo
šumnikom: č, š in ž zamenjamo z c, s in z, na primer,
zarisce namesto žarišče.
Vrednosti v tabeli zapišemo tako, kot želimo oziroma tako, kot so
podane, z velikimi in malimi črkami, s šumniki in presledki. Pri tem
moramo biti pozorni na kodiranje znakov, ki ga uporablja R. Preverimo ga
lahko s klicem funkcije localeToCharset(): zaželena
nastavitev je "UTF-8". Pri branju podatkov iz besedilnih
ali Excel-ovih datotek moramo poskrbeti, da se podatkovne vrednosti med
uvozom pretvorijo v kodiranje znakov, ki ga uporablja R, sicer bodo
šumniki (in kakšni drugi znaki) zapisani napačno.
Osnove tidyverse: Uvoz in izvoz podatkovnih tabel
Paket, ki omogoča pripravo in analizo urejenih podatkov, opisanih v
prejšnjem poglavju omogoča zbirka paketov, ki jih vsebuje knjižnica tidyverse. V tem
poglavju bomo spoznali funkcije za uvoz in izvoz podatkovnih tabel, ki
jih ponuja ta knjižnica. Najprej bomo uvozili knjižnico:
library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
method from
print.tbl_lazy
print.tbl_sql
── Attaching packages ───────────────────────────────────────── tidyverse 1.3.1 ──
✓ ggplot2 3.3.5 ✓ dplyr 1.0.7
✓ tidyr 1.1.4 ✓ stringr 1.4.0
✓ readr 2.1.2 ✓ forcats 0.5.1
✓ purrr 0.3.4
Warning: package ‘readr’ was built under R version 4.0.5── Conflicts ──────────────────────────────────────────── tidyverse_conflicts() ──
x dplyr::filter() masks stats::filter()
x dplyr::lag() masks stats::lag()
Excel-ove preglednice
V R-ju lahko beremo podatke iz Excel-ovih preglednic z uporabo ukaza
read_excel v formatu XLS ali XLSX, ki se določi na osnovi
podaljška podanega imena datoteke. Če hočemo ugotoviti kateri listi
(angl. sheets) so na voljo v datoteki, uporabimo funkcijo
excel_sheets. Obe funkciji kot prvi argument sprejmeta ime
datoteke z Excel preglednico.
Nekaj koristnih parametrov funkcije read_excel:
sheet s privzeto vrednostjo NULL določi
list preglednice, ki ga hočemo prebrati. Vrednost je lahko celoštevilčna
(podamo pozicijo lista v preglednici) ali niz znakov (podamo ime lista v
preglednici). Če podana vrednost ne določi lista, funkcija prebere
podatke iz prvega lista preglednice.
range s privzeto vrednostjo NULL določi
obseg celic preglednice, katere vrednosti hočemo uvoziti v R.
Uporabljamo Excel-ov način določanja obsega, npr. "A1:C11"
ali pa "Rezultati!A1:Q1024". V primeru podanega obsega,
funkcija uvozi tudi prazne vrstice in stolpce iz tega obsega.
col_names, guess_max in
col_types imajo isti pomen kot istoimenski argumenti
funkcije read_csv opisane v prejšnjem razdelku.
Pregled funkcij za uvoz in izvoz podatkovnih tabel, ki jih ponuja
tidyverse, je na voljo v plonk
listu Data import with the tidyverse.
LS0tCnRpdGxlOiAnUG9kYXRrb3ZuZSB0YWJlbGUgaW4gdXJlamVuaSBwb2RhdGtpJwpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdAogIHBkZl9kb2N1bWVudDogZGVmYXVsdAotLS0KCiMgUG9kYXRrb3ZuZSB0YWJlbGUgKGFuZ2wuIF9EYXRhIEZyYW1lc18pCgpQb2RhdGtvdm5lIHRhYmVsZSAoYWxpIHBhIHYgZG9iZXNlZG5lbSBwcmV2b2R1IG9rdmlyaSkgc28gcG9tZW1ibmEgbmFkZ3JhZG5qYSBvc25vdm5lZ2EgdGlwYSBzZXpuYW1vdjogdGFiZWxhIGplIHByYXZ6YXByYXYgcHJlZHN0YXZsamVuYSBrb3Qgc2V6bmFtIG5qZW5paCBzdG9scGNldiAodHVkaSB2c2FrIHN0b2xwZWMgdGFiZWxlIGplIHNlem5hbSkuIE5hbWVuamVuZSBzbyBzaHJhbmpldmFuanUgbW5vxb5pY2UgcG9kYXRrb3YgKGFuZ2wuIF9kYXRhIHNldF8pLCBraSBzbyBwcmVkbWV0IGFuYWxpemUuCgpQb2dvc3RhIG5hdmFkYSBwcmkgYW5hbGl6aSBwb2RhdGtvdiBqZSwgZGEgdnJzdGljZSB1c3RyZXpham8gb3Bhem92YW5pbSAqKmVub3RhbSoqIChhbmdsLiBfdW5pdHNfKS4gRW5vdGUgbGFoa28gaW1lbnVqZW1vIHR1ZGkgcHJpbWVya2kgb3ouIHByaW1lcmkgKGFuZ2wuIF9leGFtcGxlc18pLiBWIHRlbSBwcmltZXJ1IHN0b2xwY2kgdXN0cmV6YWpvIG9wYXpvdmFuaW0gYWxpIGl6bWVyamVuaW0gKipsYXN0bm9zdGltKiogKGFuZ2wuIF9hdHRyaWJ1dGVzXykgKiplbm90KiosIGtpIGppaCBpbWVudWplbW8gKipzcHJlbWVubGppdmtlKiogKGFuZ2wuIF92YXJpYWJsZXNfKS4gU3ByZW1lbmxqaXZrYSB0b3JlaiBwb2RhamEgaW4gdHVkaSB2c2VidWplIHZzZSBvcGF6b3ZhbmUgYWxpIGl6bWVyamVuZSB2cmVkbm9zdGkgdXN0cmV6bmVnYSBhdHJpYnV0YSBlbm90ZS4KClBvZ2xlam1vIHNpIHByaW1lciBwcmVwcm9zdGUgcG9kYXRrb3ZuZSB0YWJlbGUgdiBrYXRlcmkgc28gZW5vdGUgb3NlYmU6CmBgYHtyfQpvc2ViZV9kZiA9IGRhdGEuZnJhbWUoCiAgaW1lID0gYygiTW9qY2EiLCAiSmFuZXoiLCAixaBwZWxhIiwgIlZpdG8iKSwKICBzcG9sID0gZmFjdG9yKGMoIsW+IiwgIm0iLCAixb4iLCAibSIpLCBsZXZlbHM9YygibSIsICLFviIpKSwKICBzdGFyb3N0ID0gYygzMCwgNDgsIDYzLCAyNykKKQpvc2ViZV9kZgpgYGAKClBvZGF0a292bmEgdGFiZWxhIHZzZWJ1amUgcG9kYXRrZSBvIMWhdGlyaWggb3Bhem92YW5paCBlbm90YWggKG9zZWJhaCkuIFZzYWthIG9zZWJhIGplIG9waXNhbmEgcyB0cmVtaSBsYXN0bm9zdG1pLCBfaW1lXywgX3Nwb2xfIGluIF9zdGFyb3N0XywgdG9yZWogcG9kYXRrb3ZuYSBtbm/FvmljYSBpbWEgdHJpIHNwcmVtZW5saml2a2UgeiBpc3RpbWkgaW1lbmksIGBpbWVgLCBgc3BvbGAgaW4gYHN0YXJvc3RgLgoKKipEb21lbmEgc3ByZW1lbmxqaXZrZSoqIGplIG1ub8W+aWNhIG1vxb5uaWggdnJlZG5vc3RpIHRlIHNwcmVtZW5saml2a2UuIFRha28gamUsIG5hIHByaW1lciwgZG9tZW5hIHNwcmVtZW5saml2a2UgX3Nwb2xfIG1ub8W+aWNhIHtgbWAsIGDFvmB9LCBkb21lbmEgc3ByZW1lbmxqaXZrZSBgc3Rhcm9zdGAgcGEgbW5vxb5pY2EgbmFyYXZuaWggxaF0ZXZpbC4gxb1lIHRha29qIGplIGphc25vLCBkYSBzbyBsYWhrbyBzcHJlbWVubGppdmtlIGR2ZWggdGlwb3Y6CgogICogKmt2YW50aXRhdGl2bmUgc3ByZW1lbmxqdmlrZSogaW1ham8gKGxhaGtvIHR1ZGkgbmVza29uxI1ubykgZG9tZW5vIMWhdGV2aWzEjW5paCB2cmVkbm9zdGksCiAgKiAqa3ZhbGl0YXRpdm5lIHNwcmVtZW5saml2a2UqIGltYWpvIGtvbsSNbm8gZG9tZW5vLCB0LmouLCBtbm/FvmljbyB2bmFwcmVqIHpuYW5paCB2cmVkbm9zdGkuCiAgICAKVnJlZG5vc3RpIGt2YW50aXRhdGl2bmloIHNwcmVtZW5saml2ayBzbyB0aXBhIF9pbnRlZ2VyXyBhbGkgX2RvYnVsZV8sIHZyZWRub3N0aSBrdmFsaXRhdGl2bmloIHBhIHNvIHRpcGEgX2ZhY3Rvcl8gYWxpIF9zdHJpbmdfLgoKUHJldmVyaW1vIHNlZGFqIGtha8WhbmVnYSB0aXBhIGplIHBvZGF0a292bmEgdGFiZWxhIHYgUi1qdSBpbiBrYWvFoW5lIHNvIGxhc3Rub3N0aSB0ZWdhIHRpcGE6CmBgYHtyfQp0eXBlb2Yob3NlYmVfZGYpCmF0dHJpYnV0ZXMob3NlYmVfZGYpCmBgYAoKRHZhIHBvbWVtYm5hIGF0cmlidXRhIHBvZGF0a292bmUgdGFiZWxlIHN0YToKCiogYG5hbWVzYCBqZSB2ZWt0b3Igbml6b3Ygem5ha292LCBraSB1c3RyZXpham8gaW1lbm9tIHN0b2xwY2V2LgoqIGByb3cubmFtZXNgamUgb2JpxI1ham5vIHZla3RvciBjZWxpaCDFoXRldmlsIG9ibGlrZSBgMTpuYCwga2plciBqZSBgbmAgxaF0ZXZpbG8gdnJzdGljIHYgdGFiZWxpLiBMYWhrbyBiaSBiaWwgdHVkaSB2ZWt0b3Igbml6b3Ygem5ha292LCBrYXIgb21vZ2/EjWEgcG9pbWVub3ZhbmplIHZyc3RpYy4gQSBvYmnEjWFqbm8gc2UgdGVnYSBuZSBwb3NsdcW+dWplbW86IMSNZSBpbWFqbyB2cnN0aWNlIGltZW5hIGluIHRvcmVqIGltYWpvIGltZW5hIG5la2FrxaFlbiBwb2RhdGtvdm5pIHBvbWVuLCBwb3RlbSBpbWVub20gbmFtZW5pbW8gKHBydmkpIHN0b2xwZWMgdiB0YWJlbGkuCgpWcmVkbm9zdGkgdGVoIGF0cmlidXRvdiBsYWhrbyBkb2JpbW8gcyBmdW5rY2lqYW1pIGByb3duYW1lc2AgaW4gYGNvbG5hbWVzYCAoc2xlZG5qbyBsYWhrbyB6YW1lbmphbW8geiBgbmFtZXNgLCBhIHRlbXUgc2UgbmHEjXJ0bm8gaXpvZ2liYW1vLCBkYSBibyBrb2RhIGJvbGogYmVybGppdmEpLiDFoHRldmlsbyB2cnN0aWMgaW4gc3RvbHBjZXYgZG9iaW1vIHMgZnVua2NpamFtYSBgbnJvd2AgaW4gYG5jb2xgOgpgYGB7cn0KcGFzdGUoIkRpbWVuemlqZSB0YWJlbGUgb3NlYmVfZGY6IiwKICAgIG5yb3cob3NlYmVfZGYpLCAidnJzdGljZSBpbiIsCiAgICBuY29sKG9zZWJlX2RmKSwgInN0b2xwY2kuIgopCnJvd25hbWVzKG9zZWJlX2RmKQpjb2xuYW1lcyhvc2ViZV9kZikKYGBgCgpTdG9scGNpIHYgdGFiZWxpIHNvIHNlem5hbWksIGthciBvbW9nb8SNYSB2ZWxpa28gZmxla3NpYmlsbm9zdCBwcmkgdGlwaWggZWxlbWVudG92IHRhYmVsZTogdnNhayBlbGVtZW50IGplIGxhaGtvIHN2b2plZ2EgdGlwYS4gS2xqdWIgdGVtdSwgemEgbGHFvmpvIGFuYWxpem8gaW4gdml6dWFsaXphY2lqbyBwb2RhdGtvdiBzZSBvYmnEjWFqbm8gb21lamltbyBuYSB0YWtlIHRhYmVsZSwga2plciBpbWEgdnNhayBzdG9scGVjIGVsZW1lbnRlIGlzdGVnYSB0aXBhLgoKIyMgUG9kYXRrb3ZuZSB0YWJlbGUgdGlwYSBgdGliYmxlYAoKT3Nub3ZuaSBwb2RhdGtvdm5pIHRpcCBgZGF0YS5mcmFtZWAgdiBSLWp1IGplIGxhaGtvIG5hZGxlxb5lbiwgcHJlZHZzZW0gemF0bywga2VyIHVwb8WhdGV2YSB2c2UgbmF2YWRlIFItamEgZ2xlZGUgcHJpc2lsZSBpbiBkaW5hbWnEjW5lZ2EgcHJpbGFnYWphbmphIHRpcG92LiBUYWtvLCBuYSBwcmltZXIsIMSNZSBqZSByZXp1bHRhdCBpbmRla3NpcmFuamEgc3RvbHBjZXYgdiB0YWJlbGkgZW4gc2FtIHN0b2xwZWMsIGdhIGJvIFIgYXZ0b21hdHNrbyBzcHJlbWVuaWwgdiB2ZWt0b3IgYWxpIHNlem5hbSAtIHYgcHJpbWVydSBkdmVoIGFsaSB2ZcSNIHN0b2xwY2V2IGJvIHBhIHJlenVsdGF0IMWhZSB2ZWRubyB0YWJlbGEuCgpaYXRvIHBvZ29zdG8gdXBvcmFibGphbW8gcG9kYXRrb3ZuZSB0YWJlbGUgdGlwYSBgdGliYmxlYCwga2kgamloIGltcGxlbWVudGlyYSBbaXN0b2ltZW5za2kgQ1JBTiBwYWtldF0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3RpYmJsZS9pbmRleC5odG1sKToKYGBge3J9CmxpYnJhcnkodGliYmxlKQoKb3NlYmVfZGZ0ID0gYXNfdGliYmxlKG9zZWJlX2RmKQpvc2ViZV9kZnQKYGBgCgpWIG5hZGFsamV2YW5qdSBzaSBvZ2xlam1vIG5la2FqIHJhemxpayBtZWQgYHRpYmJsZWAgaW4gYGRhdGEuZnJhbWVgLgoKSW1lbmEgc3RvbHBjZXYgdiBgdGliYmxlYCBzbyBib2xqIGZsZWtzaWJpbG5hLCBrb3QgdGlzdGEgdiBgZGF0YS5mcmFtZWAsIHByZWR2c2VtIHBhIHNlIG5lIHNwcmVtZW5pam8ga2FyIHNhbW9kZWpubyBicmV6IG9wb3pvcmlsOgpgYGB7cn0KY29sbmFtZXMoZGF0YS5mcmFtZShgMWAgPSBjKDEsMiwzKSkpCmNvbG5hbWVzKHRpYmJsZShgMWAgPSBjKDEsMiwzKSkpCmBgYAoKUHJpIGtyZWlyYW5qdSB0YWJlbGUgdGlwYSBgdGliYmxlYCBzbyBzdG9scGNpLCBraSB2c2VidWplam8gbml6ZSB6bmFrb3YsIHNhbW9kZWpubyBwcmV0dm9yaWpvIHYgZmFrdG9yamUsIGthciBqZSBsYWhrbyBwcmVkbm9zdCBhbGkgc2xhYm9zdC4gUHJldmVyaXRlIMWhZSBlbmtyYXQgcHJpbWVyIHRhYmVsZSB6Z29yYWogaW4gb3BhemlsaSBib3N0ZSwgZGEgc21vIHphIHByZXR2b3JibyBzdG9scGNhIHYgZmFrdG9yIHBvc2tyYmVsaSBzYW1pIG1lZCB1c3R2YXJqYW5qZW0gcG9kYXRrb3ZuZSB0YWJlbGUuCgpOYWRhbGplLCBwb2RhdGtvdm5pIHRpcCBgdGliYmxlYCBuZSBvbW9nb8SNYSBwb2ltZW5vdmFuamUgdnJzdGljIHYgdGFiZWxpLiBLb3Qgc21vIHphcGlzYWxpIHpnb3JhaiBqZSBwb2ltZW5vdmFuamUgc3RvbHBjZXYgaXRhayBuZXphxb5lbGVuby4KCk5lbmF6YWRuamUsIGB0aWJibGVgIG9tb2dvxI1hIHNrbGljZXZhbmplIG5hIHByZWrFoW5qZSBzdG9scGNlIHByaSB0dm9yamVuanUgbm92aWg6CmBgYHtyfQpzdGFyb3N0X3ZfbGV0aWggPSBmdW5jdGlvbihkYXR1bV9yb2pzdHZhKSB7CiAgYXMuaW50ZWdlcigKICAgIGFzLm51bWVyaWMoZGlmZnRpbWUoU3lzLkRhdGUoKSwgZGF0dW1fcm9qc3R2YSwgdW5pdHM9IndlZWtzIikpIC8gNTIuMjUKICApCn0KCm9zZWJlX2RmdCA9IHRpYmJsZSgKICBpbWUgPSBjKCJNb2pjYSIsICJKYW5leiIsICLFoHBlbGEiLCAiVml0byIpLAogIHNwb2wgPSBmYWN0b3IoYygixb4iLCAibSIsICLFviIsICJtIiksIGxldmVscz1jKCLFviIsICJtIikpLAogIGRhdHVtLnJvanN0dmEgPSBhcy5EYXRlKGMoIjE5OTEtMDktMTUiLCAiMTk3My0wMi0wNCIsICIxOTU4LTA3LTMwIiwgIjE5OTUtMDUtMjciKSksCiAgc3Rhcm9zdCA9IHN0YXJvc3Rfdl9sZXRpaChkYXR1bS5yb2pzdHZhKQopCm9zZWJlX2RmdApgYGAKCkFuYWxpemlyYWp0ZSBwb3pvcm5vIGZ1bmtjaWpvIHphIGl6cmHEjXVuIHN0YXJvc3RpIG9zZWJlIHYgbGV0aWggbmEgb3Nub3ZpIHBvZGFuZWdhIGRhdHVtYSByb2pzdHZhLgoKIyMgSW5kZWtzaXJhbmplIHBvZGF0a292bmloIHRhYmVsCgpQb2RhdGtvdm5lIHRhYmVsZSwgdGFrbyBrb3QgbWF0cmlrZSwgbGFoa28gaW5kZWtzaXJhbW8geiBuYXZhamFuamVtIGluZGVrc2EgdnJzdGljZSBgaWAgaW4gbmF0byBpbmRla3NhIHN0b2xwY2EgYGpgLCB0b3JlaiBgYGRmW2ksIGpdYGAuIMSMZSBrYWvFoW5lZ2EgaXptZWQgdGVoIGR2ZWggaW5kZWtzb3YgcHVzdGltbyBwcmF6bmVnYSwgZG9iaW1vIGtvdCByZXp1bHRhdCB2c2UgdnJzdGljZSBvei4gc3RvbHBjZSBwb2RhdGtvdm5lIHRhYmVsZS4gUG96b3I6IHJlenVsdGF0IGluZGVrc2lyYW5qYSBqZSB0YWJlbGEsIGxlIMSNZSB1cG9yYWJsamFtbyBwb2RhdGtvdm5lIHRhYmVsZSB0aXBhIGB0aWJibGVgIGFsaSBwYSBuYXN0YXZpbW8gYXJndW1lbnQgYGRyb3A9RkFMU0VgOgpgYGB7cn0Kb3NlYmVfZGZ0W2MoMSwyKSwgXQpvc2ViZV9kZnRbYygxLDEsMSwyKSwgXQoKb3NlYmVfZGZ0WywgLTNdCgoKb3NlYmVfZGZ0W2MoMSwyKSwgInNwb2wiXQpvc2ViZV9kZltjKDEsMiksICJzcG9sIl0gIyBvcGF6dWogcmF6bGlrbyBzIHByZWrFoW5qaW0gcmV6dWx0YXRvbQpvc2ViZV9kZltjKDEsMiksICJzcG9sIiwgZHJvcD1GQUxTRV0KYGBgCgrEjGUgYGRyb3A9RkFMU0VgIG5lIG5hc3RhdmltbyBpbiBqZSBgZGZgIHBvZGF0a292bmEgdGFiZWxhIHRpcGEgX2RhdGEgZnJhbWVfLCBzZSBibyB0aXAgcmV6dWx0YXRhIChwcmlzaWxubykgcHJpbGFnb2RpbCB2IHZla3RvciAoxI1lIGplIHJlenVsdGF0IGluZGVrc2lyYW5qYSBlbmEgdnJzdGljYSBhbGkgZW4gc3RvbHBlYyB0YWJlbGUpIG96aXJvbWEgc2thbGFyICjEjWUgamUgcmV6dWx0YXQgaW5kZWtzaXJhbmphIHZyZWRub3N0IGVuZSBjZWxpY2UgdiB0YWJlbGkpLgoKWmEgaW5kZWtzaXJhbmplIHBvZGF0a292bmloIHRhYmVsIGxhaGtvIHVwb3JhYmxqYW1vIHZzZSBuYcSNaW5lIGluZGVrc2lyYW5qYSwga2kgamloIHBvem5hbW8gaXogaW5kZWtzaXJhbmphIHZla3RvcmpldiBpbiBzZXpuYW1vdi4gxIxlIHNpIHpyYXZlbiB6YXBvbW5pbW8sIGRhIGplIHRhYmVsYSBwcmF2emFwcmF2IHNlem5hbSBzdG9scGNldiwgdmVtbyBrYWtvIHByaWRlbW8gZG8gdnNlYmluZSBlbmVnYSBzdG9scGNhIG5hIHRyaSByYXpsacSNbmUgbmHEjWluZSAobmFqYm9saiBwb2dvc3RvIHVwb3JhYmxqYW1vIHphZG5qZWdhLCBraSBqZSBrcmF0ZWsgaW4gaGtyYXRpIGJlcmxqaXYpOgpgYGB7cn0Kb3NlYmVfZGZ0W1sxXV0Kb3NlYmVfZGZ0W1sic3Rhcm9zdCJdXQpvc2ViZV9kZnQkZGF0dW0ucm9qc3R2YQpgYGAKClYgbmFkYWxqZXZhbmp1IHNpIG9nbGVqbW8gxaFlIG5la2FqIG5hxI1pbm92IGluZGVrc2lyYW5qYSwga2kgamloIHBvem5hbW8gxaFlIG9kIHByZWosIGEgdHVrYWogamloIHBvbmF6b3JpbW8gbmEgcG9kYXRrb3ZuaWggdGFiZWxhaC4KCkxhaGtvIGluZGVrc2lyYW1vIHogbG9nacSNbmltIHBvZ29qZW0gemF0bywgZGEgZG9iaW1vIHBvZG1ub8W+aWNvIHZyc3RpYywga2kgcG9nb2p1IHVzdHJlemFqbzoKYGBge3J9Cm9zZWJlX2RmdFtvc2ViZV9kZnQkc3Rhcm9zdCA+IDQ1LCBdCm9zZWJlX2RmdCRzdGFyb3N0ID4gNDUgIyBkYSB1Z290b3ZpbW8gemFrYWogaW5kZWtzaXJhbmplIHpnb3JhaiBwcmF2aWxubyBkZWx1amUKYGBgCgpaIGluZGVrc2lyYW5qZW0gc2kgbGFoa28gcG9tYWdhbW8gdHVkaSBwcmkgYnJpc2FuanUgc3RvbHBjZXYgKGFsaSB2cnN0aWMpIHRhYmVsZToKYGBge3J9Cm9zZWJlX2RmdDEgPSBvc2ViZV9kZnRbLCAtM10Kb3NlYmVfZGZ0MQpgYGAKClogaW5kZWtzaXJhbmplbSBsYWhrbyB0dWRpIHVyZWphbW8gdnJzdGljZSB2IHRhYmVsaSB2IG5hcmHFocSNYWpvxI1lbSBhbGkgcGFkYWpvxI1lbSB2cnN0bmVtIHJlZHUsIHRha28gZGEgdiBpbmRrc3UgdXBvcmFiaW1vIGZ1bmtjaWpvIGBvcmRlcmA6CmBgYHtyfQpvcmRlcihvc2ViZV9kZnQkc3Rhcm9zdCkKb3NlYmVfZGZ0W29yZGVyKG9zZWJlX2RmdCRzdGFyb3N0KSwgXQoKb3NlYmVfZGZ0W29yZGVyKG9zZWJlX2RmdCRzdGFyb3N0LCBkZWNyZWFzaW5nID0gVFJVRSksIF0KCm9zZWJlX2RmdFtvcmRlcihvc2ViZV9kZnQkc3BvbCksIF0Kb3NlYmVfZGZ0W29yZGVyKG9zZWJlX2RmdCRzcG9sLCAtb3NlYmVfZGZ0JHN0YXJvc3QpLCBdCgpvc2ViZV9kZnRbLCBvcmRlcihjb2xuYW1lcyhvc2ViZV9kZnQpKV0KYGBgCgpaIGluZGVrc2lyYW5qZW0gdHVkaSBlbm9zdGF2bm8gdnpvcsSNaW1vIHByaW1lcmUgcyBwb25hdmFsamFuamVtIGFsaSBicmV6CmBgYHtyfQpvc2ViZV9kZnRbc2FtcGxlKG5yb3cob3NlYmVfZGZ0KSwgMiksIF0Kb3NlYmVfZGZ0W3NhbXBsZShucm93KG9zZWJlX2RmdCksIDEwLCByZXBsYWNlPVQpLCBdCmBgYAoKCi0tLQoKIyBPZCBwb2RhdGtvdiBkbyBwb2RhdGtvdm5paCB0YWJlbAoKUG9kYXRraSBvIG9wYXpvdmFuaWggZW5vdGFoIGxhaGtvIHpiaXJhbW8gbmEgcmF6bGnEjW5lIG5hxI1pbmUuIExhaGtvIGppaCBwcmlwcmF2aW1vIGl6IGxhc3RuaWggbWVyaXRldiBpbiBqaWggc2FtaSB2bmHFoWFtbyB2IFIgdiBvYmxpa2kgdmVrdG9yamV2IGluIHNlem5hbW92LiBMYWhrbyBqaWggcHJpZG9iaW1vIHYgb2JsaWtpIHByZWdsZWRuaWMgRXhjZWwgaXogbmVrZWdhIHN0YW5kYXJkbmVnYSB2aXJhIHN0YXRpc3RpxI1uaWggcG9kYXRrb3YsIGtvdCBqZSBbcG9kYXRrb3ZuYSBiYXphIFNpU3RhdF0oaHR0cHM6Ly9weHdlYi5zdGF0LnNpLykgU3RhdGlzdGnEjW5lZ2EgdXJhZGEgUmVwdWJsaWtlIFNsb3ZlbmlqZSBhbGkgcGEgW3BvZGF0a292bmEgYmF6YSBFdXJvc3RhdF0oaHR0cHM6Ly9lYy5ldXJvcGEuZXUvZXVyb3N0YXQvZGF0YS9kYXRhYmFzZSksIGV2cm9wc2tlZ2Egc3RhdGlzdGnEjW5lZ2EgdXJhZGEuIFNwbGV0bmEgZW5jaWtsb3BlZGlqYSBbV2lraXBlZGlhXShodHRwczovL3d3dy53aWtpcGVkaWEub3JnLykgamUgdHVkaSBib2dhdCB2aXIgcG9kYXRrb3ZuaWggdGFiZWwsIGtvdCBqZSBuYSBwcmltZXIgW3RhYmVsYSBzIHBvZGF0a2kgbyBicnV0byBkb21hxI1lbSBwcm9penZvZHUgZHLFvmF2XShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9MaXN0X29mX2NvdW50cmllc19ieV9HRFBfKG5vbWluYWwpKS4KCktvdCBib21vIHNwb3puYWxpIHYgbmFkYWxqZXZhbmp1LCBSIHBvbnVqYSBwcmlyb8SNbmUgZnVua2NpamUgemEgYnJhbmplIHBvZGF0a292IGl6IGRhdG90ZWsgcyBwcmVnbGVkbmljYW1pIEV4Y2VsIGFsaSBpeiBwb2RhdGtvdm5paCB0YWJlbCB2IHNwbGV0bmloIGVuY2lrbG9wZWRpamkgV2lraXBlZGlhLiBQcmlkb2JpdmFuamUgdGFraWggcG9kYXRrb3YgaW4gbmppaG92byBicmFuamUgb3ppcm9tYSB0cmFuc2Zvcm1hY2lqYSB2IFItamV2c2tlIHBvZGF0a292bmUgdGFiZWxlIGplIHphdG8gcmVsYXRpdm5vIGVub3N0YXZuby4gS2VyIHNlIG9iacSNYWpubyB6YXBsZXRlIGplLCBkYSBpbWFqbyBwb2RhdGtpIHByaWRvYmxqZW5pIGl6IHJhemxpxI1uaWggcG9kYXRrb3ZuaWggdmlyb3YgcmF6bGnEjW5vIF9zdHJ1a3R1cm9fLgoKIyMgU3RydWt0dXJhIGluIHBvbWVuIHBvZGF0a292CgpQb2dsZWptbyBzaSBwcmVwcm9zdGkgcHJpbWVyLiBEZW5pbW8sIGRhIG9wYXp1amVtbyBuZWthaiBvYmpla3RvdiwgcHJhdm9rb3RuaWtvdiBpbiBrdmFkcmF0b3YgaW4gcHJpIHRlbSBtZXJpbW8gbmppaG92ZSBkaW1lbnppamUuIFJlenVsdGF0ZSBtZXJpdGV2IHNtbyBzaSB6YXBpc2FsaSB2IG5hc2xlZG5qaSBzZXpuYW06CmBgYHtyfQpvYmpla3RpID0gbGlzdCgKICBvMSA9IGxpc3QoImt2YWRyYXQiLCAzKSwKICBvMiA9IGxpc3QoInByYXZva290bmlrIiwgYygyLCA3KSksCiAgbzMgPSBsaXN0KCJwcmF2b2tvdG5payIsIGMoOSwgNCkpLAogIG80ID0gbGlzdCgia3ZhZHJhdCIsIDgpLAogIG81ID0gbGlzdCgicHJhdm9rb3RuaWsiLCBjKDYsIDUpKQopCmBgYAoKUHJlZGVuIHNlIGxvdGltbyBhbmFsaXplIHBvZGF0a292IG8gb3Bpc2FuaWggb2JqZWt0aWgsIGJpIGppaCBtb3JhbGkgcHJldHZvcml0aSB2IHBvZGF0a292bm8gdGFiZWxvLiBBIHBvZGF0a292bmUgdGFiZWxlIHNvIGxhaGtvIHJhemxpxI1uaWggb2JsaWssIGtpIGppbSByZcSNZW1vIHR1ZGkgX3BvZGF0a292bmUgc3RydWt0dXJlXyAoYW5nbC4gX2RhdGEgc3RydWN0dXJlc18pIGluIHZzYWthIHRhIG9ibGlrYSBvemlyb21hIHN0cnVrdHVyYSBiaSBsYWhrbyBiaWxhIHNwcmVqZW1saml2IG5hxI1pbiB6YXBpc2EgcG9kYXRrb3YgdiBwb2RhdGtvdm5vIHRhYmVsby4gUG9nbGVqbW8gc2kgbmVrYWogcHJpbWVyb3YgcG9kYXRvdm5paCB0YWJlbCB6YSBwb2RhbmUgb2JqZWt0ZS4KClBydmEgcG9kYXRrb3ZuYSB0YWJlbGEgdW1lc3RpIG9iamVrdGUgdiB2cnN0aWNlIHRhYmVsZSwgdHJpIG5qaWhvdmUgbGFzdG5vc3RpIHYgc3RvbHBjZToKYGBge3J9Cm9ibGlrYV9vYmpla3RhID0gZnVuY3Rpb24obykgewogIG9bWzFdXQp9CgpkaW1lbnppamFfb2JqZWt0YSA9IGZ1bmN0aW9uKG8pIHsKICBwYXN0ZTAob1tbMl1dLCBjb2xsYXBzZT0iLSIpCn0KCm9iamVrdGlfZGYxID0gZGF0YS5mcmFtZSgKICBpbWUgPSBuYW1lcyhvYmpla3RpKSwKICBvYmxpa2EgPSBzYXBwbHkob2JqZWt0aSwgRlVOPW9ibGlrYV9vYmpla3RhKSwKICBkaW1lbnppamEgPSBzYXBwbHkob2JqZWt0aSwgRlVOPWRpbWVuemlqYV9vYmpla3RhKQopCm9iamVrdGlfZGYxCmBgYAoKVGEgcG9kYXRrb3ZuYSBzdHJ1a3R1cmEgZG9sb8SNYSB0dWRpICoqcG9tZW4gcG9kYXRrb3YqKiAoYW5nbC4gX2RhdGEgc2VtYW50aWNzXyk6IGVub3RhIG9wYXpvdmFuamEgamUgb2JqZWt0LCBuamVnb3ZlIGxhc3Rub3N0aSBtZXJpbW8gcyB0cmVtaSBzcHJlbWVubGppdmthbWksIGBpbWVgLCBgb2JsaWthYCBpbiBgZGltZW56aWphYC4KClByZWRzdGF2aXRpIGRpbWVuemlqbyBwb3NhbWV6bmVnYSBvYmpla3RhIGtvdCBuaXogem5ha292IG5pIHJhdm5vIGRvYnJhIHBvcG90bmljYSB6YSBtb3JlYml0bmUgbmFkYWxqbmplIGl6cmHEjXVuZSBwcmkgYW5hbGl6aSBwb2RhdGtvdi4gUHJlZHN0YXZsamFqdGUgc2ksIGRhIGJpIHNpIMW+ZWxlbGkgdiBuYXNsZWRuamVtIGtvcmFrdSBhbmFsaXplIGl6cmHEjXVuYXRpIHBvdnLFoWlubyBwb3NhbWV6bmVnYSBvYmpla3RhLiBaYXRvIGJpIHBvdHJlYm92YWxpIMWhaXJpbm8gaW4gdmnFoWlubyBvYmpla3RhIHBvc2ViZWouIERhIHRvIG5hcmVkaW1vLCBiaSBsYWhrbyBwb2RhdGtvbSBwcmlwaXNhbGkgZHJ1Z28gc3RydWt0dXJvIG96aXJvbWEgcG9kYXRrb3ZubyB0YWJlbG86CmBgYHtyfQp2aXNpbmFfb2JqZWt0YSA9IGZ1bmN0aW9uKG8pIHsKICBpZmVsc2UobGVuZ3RoKG9bWzJdXSkgPT0gMSwgb1tbMl1dLCBvW1syXV1bMV0pCn0KCnNpcmluYV9vYmpla3RhID0gZnVuY3Rpb24obykgewogIGlmZWxzZShsZW5ndGgob1tbMl1dKSA9PSAxLCBvW1syXV0sIG9bWzJdXVsyXSkKfQoKb2JqZWt0aV9kZjIgPSBkYXRhLmZyYW1lKAogIGltZSA9IG5hbWVzKG9iamVrdGkpLAogIG9ibGlrYSA9IHNhcHBseShvYmpla3RpLCBGVU49b2JsaWthX29iamVrdGEpLAogIHZpc2luYSA9IHNhcHBseShvYmpla3RpLCBGVU49dmlzaW5hX29iamVrdGEpLAogIHNpcmluYSA9IHNhcHBseShvYmpla3RpLCBGVU49c2lyaW5hX29iamVrdGEpCikKb2JqZWt0aV9kZjIKYGBgCgpUYWtvIHBvZGF0a292bm8gdGFiZWxvIGJpIHBvIHZzZWogdmVyamV0bm9zdGkgcHJpcHJhdmlsaSwgxI1lIGJpIHVwb3JhYmxqYWxpIHByb2dyYW0gRXhjZWwuIFBvbWVuIHBvZGF0a292IHYgdGVqIHBvZGF0a292bmkgdGFiZWxpIGplIHBvZG9iZW4ga290IHBvbWVuIHYgcHJlasWhbmppLCBsZSBkYSB0b2tyYXQgaW1hIHZzYWthIGVub3RhIMWhdGlyaSBsYXN0bm9zdGkgb3ppcm9tYSBzcHJlbWVubGppdmtlIGBpbWVgLCBgb2JsaWthYCwgYHZpc2luYWAgaW4gYHNpcmluYWAuCgpPYmUgcG9kYXRrb3ZuaSB0YWJlbGkgcHJpcHJhdmxqZW5pIHpnb3JhaiBkb21uZXZham8sIGRhIHNvIG9iamVrdGkgemFwaXNhbmUgcG8gdnJzdGljYWgsIGthciBqZSBvYmnEjWFqbmEgZG9tbmV2YSBwcmkgbmHEjXJ0b3Zhbmp1IHN0cnVrdHVyZSBwb2RhdGtvdjogdnJzdGljZSB2IHRhYmVsaSB1c3RyZXpham8gcG9zYW1lem5pbSBlbm90YW0gKG9iamVrdG9tKS4gVnNla2Frb3IgYmkgcGEgbGFoa28gcHJpcHJhdmlsaSBwb2RhdGtvdm5vIHRhYmVsbywga2plciBiaSBvYmpla3RpIGJpbGkgc3ByYXZsamVuaSB2IHN0b2xwY2UsIG5qaWhvdmUgbGFzdG5vc3RpIHBhIHYgdnJzdGljZToKYGBge3J9Cm9iamVrdGlfZGYzID0gZGF0YS5mcmFtZSgKICAiWCIgPSBjKCJpbWUiLCAib2JsaWthIiwgInZpc2luYSIsICJzaXJpbmEiKSwKICBvMSA9IGMoIm8xIiwgImt2YWRyYXQiLCAzLCAzKSwKICBvMiA9IGMoIm8yIiwgInByYXZva290bmlrIiwgMiwgNyksCiAgbzMgPSBjKCJvMyIsICJwcmF2b2tvdG5payIsIDksIDQpLAogIG80ID0gYygibzQiLCAia3ZhZHJhdCIsIDgsIDgpLAogIG81ID0gYygibzUiLCAicHJhdm9rb3RuaWsiLCA2LCA1KQopCm9iamVrdGlfZGYzCmBgYAoKUyB0ZW0gdnByYcWhYW5qZW0gc2Ugc29vxI1hbW8gbmEgemHEjWV0a3UgdnNha2VnYSBwcm9qZWt0YSBhbmFsaXplIHBvZGF0a292LiBPZGdvdm9yb3YgbmEgdG8gdnByYcWhYW5qZSwga290IGthxb5lIHpnb3JuamkgcHJpbWVyLCB2ZWxpa28uIMSMZXByYXYgamUgcHJpbWVyIHplbG8gZW5vc3RhdmVuLiDFoGUgdmXEjSBiaSBqaWggYmlsbywgxI1lIGJpIG9icmF2bmF2YWxpIHJlYWxpc3RpxI1lbiBwcmltZXIgYm9saiB6YXBsZXRlbmloIHBvZGF0a292LgoKQWxpIG9ic3RhamEgc3RhbmRhcmRlbiBvZGdvdm9yIG5hIHRvIHZwcmHFoWFuamU/IE9ic3RhamEuIE1vcmViaXRpIG5lIHJhdm5vIHN0YW5kYXJkLCB0ZW12ZcSNIGRvZ292b3IsIGtpIG11IHJlxI1lbW8gKnVyZWplbmkgcG9kYXRraSogKGFuZ2wuIF90aWR5IGRhdGFfKS4gVG8gamUgZG9nb3ZvciBvIHN0YW5kYXJkbmkgb2JsaWtpIHphcGlzb3ZhbmphIHBvZGF0a292IHYgcG9kYXRrb3ZuZSB0YWJlbGUsIGtpIG9tb2dvxI1hIG5qaWhvdm8gZW5vc3Rhdm5vIGFuYWxpem8gcyBwb21vxI1qbyBmdW5rY2lqIGl6IHpiaXJrZSBwYWtldG92LCBraSBqaWggdnNlYnVqZSBrbmppxb5uaWNhIFtgdGlkeXZlcnNlYF0oaHR0cHM6Ly93d3cudGlkeXZlcnNlLm9yZy8pLgoKIyMgVXJlamVuaSBwb2RhdGtpIChhbmdsLiBfdGlkeSBkYXRhXykKCkRvZ292b3IgbyB1cmVqZW5paCBwb2RhdGtpaCBkb2xvxI1hICoqdHJpIGVub3N0YXZuYSBwcmF2aWxhIHphIHBvZGF0a292bmUgdGFiZWxlKiouCgoxLiBWc2FrYSBzcHJlbWVubGppdmthIHR2b3JpIHN0b2xwZWMgKHJlxI1lbW8gbGFoa28gdHVkaSB1c3RyZXphIHN0b2xwY3UpLgoxLiBWc2FrbyBvcGF6b3ZhbmplIHR2b3JpIHZyc3RpY28uCjEuIFZzYWsgdGlwIG9wYXpvdmFuZSBlbm90ZSB0dm9yaSB0YWJlbG8uCgpQcmkgbmHEjXJ0b3Zhbmp1IG5hxaFpaCBwb2RhdGtvdm5paCB0YWJlbCB6YcSNbmVtbyBzIHByZW1pc2xla29tIG8gdHJldGplbSBwcmF2aWx1LiBLbGp1xI1ubyB2cHJhxaFhbmplIHByaSB0ZW0gcHJlbWlzbGVrdSBqZSAqKkthdGVyaSB0aXBpIG9wYXpvdmFuaWggZW5vdCBzZSBwb2phdmxqYWpvIHYgbmHFoWloIHBvZGF0a2loPyoqIEdsZWRlIG5hIHRvLCBrYWvFoWVuIGplIG9kZ292b3IgbmEgdG8gdnByYcWhYW5qZSwgbGFoa28gemEgb2JqZWt0ZSBpeiBuYcWhZWdhIHByaW1lcmEgc2VzdGF2aW1vIHRhYmVsZSB1cmplbmloIHBvZGF0a292IG5hIGR2YSBuYcSNaW5hLgoKIyMjIFBydmkgbmHEjWluOiBkdmEgdGlwYSBvcGF6b3ZhbmloIGVub3QKClYgbmHFoWVtIHByaW1lcnUgamUgb8SNaXRlbiB0aXAgb3Bhem92YW5lIGVub3RlIG9iamVrdC4gQSBuamVnYSBvcGF6dWplbW8gbmEgZHZhIG5hxI1pbmEsIHBvIGVuaSBzdHJhbmkgb3BhenVqZW1vIGt2YWxpdGF0aXZubyBsYXN0bm9zdCBvYmpla3RhIF9vYmxpa2FfLiBEcnVnaSBuYcSNaW4gb3Bhem92YW5qYSBqZSBrdmFudGl0YXRpdm5hIF9kaW1lbnppamFfIG9iamVrdGEsIGtpIGpvIG1lcmltbyBza296aSBkdmEgcmF6bGnEjW5hIHZpZGlrYSDFoWlyaW5lIGluIHZpxaFpbmUuIEtlciBpbWFtbyBkdmEgbmHEjWluYSBvcGF6b3ZhbmphIG9iamVrdG92LCBsYWhrbyBwcmlwcmF2aW1vIGR2ZSB0YWJlbGk6CgoqIHRhYmVsYSBgb2JsaWthYCBibyB2c2FrZW11IG9iamVrdHUgcHJpcGlzYWxhIG9ibGlrbzsKKiB0YWJlbGEgYGRpbWVuemlqYWAgYm8gdnNha2VtdSBwYXJ1IG9iamVrdGEgaW4gdmlkaWthIGRpbWVuemlqZSAoxaFpcmluZSBhbGkgdmnFoWluZSkgcHJpcGlzYWxhIGl6bWVyamVubyB2cmVkbm9zdCB0ZWdhIHZpZGlrYSBkaW1lbnppamUuCgpUYWJlbGEgYG9ibGlrYWAgaW1hIHRvcmVqIGR2ZSBrdmFsaXRhdGl2bmkgc3ByZW1lbmxqaXZraToKCiogYG9iamVrdGAsIGtpIGRvbG/EjWkgaW1lIG9iamVrdGEgaW4gaW1hIHRvcmVqIGRvbWVubyB7YG8xYCwgYG8yYCwgYG8zYCwgYG80YCBpbiBgbzVgfTsKKiBgdnJlZG5vc3RgLCBraSBkb2xvxI1pIG9ibGlrbyBvYmpla3RhIGluIGltYSBkb21lbm8ge2BrdmFkcmF0YCwgYHByYXZva290bmlrYH0uCgpNZWQgbmppbWEgb2JzdGFqYSBwb21lbWJuYSByYXpsaWthLiBQcnZhIGplICoqZmlrc25hIHNwcmVtZW5saml2a2EqKiwga2F0ZXJlIHZyZWRub3N0IGRvbG/EjWEgcHJlZG1ldCBvcGF6b3ZhbmphLCB2IG5hxaFlbSBwcmltZXJ1IG9iamVrdC4gSW1lbnVqZW1vIGpvIHR1ZGkgKipkaW1lbnppanNrYSBzcHJlbWVubGppdmthKiouIERydWdhIGplICoqbWVyamVuYSBzcHJlbWVubGppdmthKio6IG5qZW5vIHZyZWRub3N0IGRvbG/EjWltbyB6IG9wYXpvdmFuamVtIGFsaSBtZXJqZW5qZW0gaXpicmFuZWdhIHByZWRtZXRhIG9wYXpvdmFuamEsIHYgbmHFoWVtIHByaW1lcnUgeiBvcGF6b3ZhbmplbSAodWdvdGF2bGphbmplbSkgb2JsaWtlIG9iamVrdGEuIEltZW51amVtbyBqbyB0dWRpICoqdnJlZG5vc3RuYSBzcHJlbWVubGppdmthKiouCgpTZXN0YXZpbW8gdG9yZWogcHJ2byB0YWJlbG8gYG9ibGlrYWA6CmBgYHtyfQpvYmxpa2FfZGYgPSBkYXRhLmZyYW1lKAogIG9iamVrdCA9IG5hbWVzKG9iamVrdGkpLAogIHZyZWRub3N0ID0gc2FwcGx5KG9iamVrdGksIEZVTj1vYmxpa2Ffb2JqZWt0YSkKKQpvYmxpa2FfZGYKYGBgCgpEcnVnYSB0YWJlbGEgYGRpbWVuemlqYWAgaW1hIGR2ZSBrdmFsaXRhdGl2bmkgaW4gZW5vIGt2YW50aXRhdGl2bm8gc3ByZW1lbmxqaXZrbzoKCiogYG9iamVrdGAsIGtpIGltYSBlbmFrbyBkb21lbm8ga290IHpnb3JhaiwgdG9yZWoge2BvMWAsIGBvMmAsIGBvM2AsIGBvNGAgaW4gYG81YH07CiogYHZpZGlrYCwga2kgaW1hIGRvbWVubyB7YMWhaXJpbmFgLCBgdmnFoWluYWB9OwoqIGB2cmVkbm9zdGAsIGtpIHBvZGEgdnJlZG5vc3QgaXpicmFuZWdhIHZpZGlrYSBkaW1lbnppamUgKHZpxaFpbmUgYWxpIMWhaXJpbmUpIHogb3Bhem92YW5pIG9iamVrdC4KClYgdGVqIHRhYmVsaSBzdGEgcHJ2aSBkdmUgc3ByZW1lbmxqaXZraSBkaW1lbnppanNraSAoZmlrc25pKSwgc2FqIG5qaWhvdmkgdnJlZG5vc3RpIGRvbG/EjWF0YSB6YSBrYXRlcmkgb2JqZWt0IGluIGthdGVybyBtZXJpdGV2IG9wYXpvdmFuZWdhIG9iamVrdGEgZ3JlLCBuZSBiZWxlxb5pIHBhIHZyZWRub3N0aSBtZXJqZW5qYS4gWmFkbmphIHNwcmVtZW5saml2a2EgamUgcGEgbWVyamVuYSAodnJlZG5vc3RuYSksIHNhaiBiZWxlxb5pIHZyZWRub3N0aSBvcHJhdmxqZW5paCBtZXJqZW5qIGl6YnJhbmVnYSB2aWRpa2EgZGltZW56aWplIG9iamVrdGEuCgpgYGB7cn0KZGltZW56aWphX2RmID0gZXhwYW5kLmdyaWQoCiAgb2JqZWt0ID0gbmFtZXMob2JqZWt0aSksCiAgdmlkaWsgPSBjKCLFoWlyaW5hIiwgInZpxaFpbmEiKSwKICB2cmVkbm9zdCA9IE5BCikKZGltZW56aWphX2RmJHZyZWRub3N0ID0gYygKICBzYXBwbHkob2JqZWt0aSwgRlVOPXNpcmluYV9vYmpla3RhKSwKICBzYXBwbHkob2JqZWt0aSwgRlVOPXZpc2luYV9vYmpla3RhKQopCmRpbWVuemlqYV9kZgpgYGAKCkZ1bmtjaWphIGBleHBhbmQuZ3JpZGAgdiBrb2RpIHpnb3JhaiBwb3NrcmJpIHphIHRvLCBkYSBzZXN0YXZpbW8gcG9kYXRrb3ZubyB0YWJlbG8ga290IGthcnRlemnEjW5pIHByb2R1a3QgZG9tZW4gZGltZW56aWpza2loIHNwcmVtZW5saml2ayBgb2JqZWt0YCBpbiBgdmlkaWtgLCBramVyIHNvIHZzZSB2cmVkbm9zdGkgbWVyamVuZSBzcHJlbWVubGppdmtlIGVuYWtlIGBOQWAuIEtlciBqZSBtb8SNIGRvbWVuZSBzcHJlbWVubGppdmtlIGBvYmpla3RgIGVuYWthIDUsIG1vxI0gZG9tZW5lIGB2aWRpa2AgMiwgaW4gamUgbW/EjSAoemHEjWFzbmUpIGRvbWVuZSBtZXJqZW5lIHNwcmVtZW5saml2a2UgYHZyZWRub3N0YCAxLCBpbWEgcG9kYXRrb3ZuYSB0YWJlbGEsIGtpIGplIHJlenVsdGF0IGtsaWNhIGZ1bmtjaWplIGBleHBhbmQuZ3JpZGAgYDUgKiAyICogMSA9IDEwYCB2cnN0aWMuCgpUYWtvaiB6YXRlbSBpenJhxI11bmFtbyDFoWUgdnJlZG5vc3RpIHphZG5qZSBzcHJlbWVubGppdmtlIGB2cmVkbm9zdGAgdiB0YWJlbGkuIFByaSB0ZW0gdXBvxaF0ZXZhbW8sIGRhIHNlIHBydmloIHBldCB2cnN0aWMgdiBrYXJ0ZXppxI1uZW0gcHJvZHVrdHUgbmFuYcWhYSBuYSDFoWlyaW5vLCBwcmVvc3RhbGloIHBldCBuYSB2acWhaW5vIG9iamVrdG92LgoKT2JpxI1ham5pICoqdnJzdG5pIHJlZCBzdG9scGNldiB2IHRhYmVsaSB6IHVyZWplbmltaSBwb2RhdGtpKiogamUgdGFrLCBkYSAqKmRpbWVuemlqc2tpbSBzcHJlbWVubGppdmthbSBzbGVkaWpvIG1lcmplbmUqKi4gKipWcnN0aWNlKiogc28gcGEgb2JpxI1ham5vICoqdXJlamVuZSBwbyBuYXJhxaHEjWFqb8SNaSB2cmVkbm9zdGkgcHJ2ZSBzcHJlbWVubGppdmtlKiosIG5hdG8gZHJ1Z2UsIHRyZXRqZSBpbiB0YWtvIG5hcHJlai4KClN0b2xwY2kgdiB0YWJlbGkgYGRpbWV6bmlqYWAgc2xlZGlqbyBvYmnEjWFqbmVtdSB2cnN0bmVtdSByZWR1LCB2cnN0aWNlIHBhIG5lLiBMYWhrbyBqaWggdXJlZGltbyB0YWtvbGU6CmBgYHtyfQpkaW1lbnppamFfZGYgPSBkaW1lbnppamFfZGZbb3JkZXIoZGltZW56aWphX2RmJG9iamVrdCwgZGltZW56aWphX2RmJHZpZGlrKSwgXQpkaW1lbnppamFfZGYKYGBgCgpQT1pPUiEgViBwcmltZXJ1LCBrbyBzbyBwb2RhdGtpIHJhemRlbGplbmkgdiB2ZcSNIHBvZGF0a292bmloIHRhYmVsLCBtb3JhbW8gcG9za3JiZXRpLCBkYSBzaSB0ZSB0YWJlbGUgZGVsaWpvIHZzYWoga2FrxaFubyBkaW1lbnppanNrbyBzcHJlbWVubGppdmtvLCBraSBuYW0gYm8gb21vZ2/EjWlsYSwgZGEgemJlcmVtbyBwb2RhdGtlIG8gZW5pIG9wYXpvdmFuaSBlbm90aSBza3VwYWouIFYgemdvcm5qZW0gcHJpbWVydSBqZSB0YWthIHNwcmVtZW5saml2a2EgYG9iamVrdGAsIGtpIG5hbSBvbW9nb8SNYSBkYSBwb3Zlxb5lbW8gcG9kYXRlayBvIG9ibGlraSBvYmpla3RhIChpeiB0YWJlbGUgYG9ibGlrYWApIHMgcG9kYXRraSBvIG5qZWdvdmloIGRpbWVuemlqYWggKHphcGlzYW5pbWkgdiB0YWJlbGkgYGRpbWVuemlqYWApLgoKIyMjIERydWdpIG5hxI1pbjogdHJpamUgdGlwaSBvcGF6b3ZhbmloIGVub3QKClByaSBzZXN0YXZsamFuanUgdGFiZWxlIHogdXJlamVuaW1pIHBvZGF0a2kgYGRpbWVuemlqYWAgc21vIGRvbW5ldmFsaSwgZGEgc3RhIMWhaXJpbmEgaW4gdmnFoWluYSBsZSBkdmEgcmF6bGnEjW5hIHZpZGlrYSBtZXJpdHZlIGRpbWVuemlqZSBvYmpla3RhLiDEjGUgYmkgxaFpcmlubyBpbiB2acWhaW5vIGRvbG/EjWlsaSBrb3QgZHZhIHJhemxpxI1uYSB0aXBhIG9wYXpvdmFuaWggZW5vdCwgYmkga29uxI1hbGkgcyB0cmVtaSB0YWJlbGFtaSB1cmVqZW5paCBwb2RhdGtvdi4gUHJ2YSB0YWJlbGEgYG9ibGlrYWAgYmkgb3N0YWxhIGVuYWthIGtvdCBwcmVqLCB0YWJlbG8gYGRpbWVuemlqYWAgYmkgemFtZW5qYWxpIHogZHZlbWEgdGFiZWxhbWEgYHNpcmluYWAgaW4gYHZpc2luYWAuIE9iZSB0YWJlbGkgYmkgaW1lbGkgZW5hayBuYWJvciBkdmVoIHNwcmVtZW5saml2ayBgb2JqZWt0YCBpbiBgdnJlZG5vc3RgOgpgYGB7cn0Kc2lyaW5hX2RmID0gZGF0YS5mcmFtZSgKICBvYmpla3QgPSBuYW1lcyhvYmpla3RpKSwKICB2cmVkbm9zdCA9IHNhcHBseShvYmpla3RpLCBGVU49c2lyaW5hX29iamVrdGEpCikKc2lyaW5hX2RmCgp2aXNpbmFfZGYgPSBkYXRhLmZyYW1lKAogIG9iamVrdCA9IG5hbWVzKG9iamVrdGkpLAogIHZyZWRub3N0ID0gc2FwcGx5KG9iamVrdGksIEZVTj12aXNpbmFfb2JqZWt0YSkKKQp2aXNpbmFfZGYKYGBgCgpaYSB2c2FrbyBwb2RhdGtvdm5vIHRhYmVsbyB6IHVyZWplbmltaSBwb2RhdGtpLCBraSBpbWEgJGQrMSQgc3RvbHBjZXYsIHZlbGphLCBkYSBwcnZpaCAkZCQgc3RvbHBjZXYgdXN0cmV6YSBkaW1lbnppanNraW0gKGZpa3NuaW0pIHNwcmVtZW5saml2a2FtIHogZG9tZW5hbWkgJERfMSwgRF8yLCBcbGRvdHMsIERfe2R9JCwgemFkbmppIHN0b2xwZWMgcGEgbWVyamVuaSAodnJlZG5vc3RuaSkgc3ByZW1lbmxqaXZraSB6IGRvbWVubyAkRF97ZCsxfSQuIExhaGtvIHRvcmVqIHJlxI1lbW8sIGRhIHVyZWplbmkgcG9kYXRraSBwb2Rham8gdGFiZWxhcmnEjW5vIGRlZmluaWNpam8gZnVua2NpamUgJGY6IERfMSBcdGltZXMgRF8yIFx0aW1lcyBcbGRvdHMgXHRpbWVzIERfe2R9IFx0byBEX3tkKzF9JC4gRnVua2NpamEgJGYkIHphIHBvZGFuZSB2cmVkbm9zdGkgZGltZW56aWpza2loIHNwcmVtZW5saml2ayB2cm5lIHZyZWRub3N0IG1lcmplbmUgc3ByZW1lbmxqaXZrZS4KCk5haiBuYSBrb25jdSBvbWVuaW1vIMWhZSBlbm8gbW/Fvm5vc3QgemEgcG9kYXRrb3ZuZSB0YWJlbGUgeiB1cmVqZW5pbWkgcG9kYXRraS4gxIxlIGltYW1vICRtJCB0YWJlbCB6IGlzdGltaSBkaW1lbnppanNraW1pIHNwcmVtZW5saml2a2FtaSB6IGRvbWVuYW1pICREXzEsIERfMiwgXGxkb3RzLCBEX2QkLCBqaWggbGFoa28gemRydcW+aW1vIHYgZW5vIHBvZGF0a292bm8gdGFiZWxvIHogdXJlamVuaW1pIHBvZGF0a2ksIGtpIGltYSAkZCQgZGltZW56aWpza2loIGluICRtJCBtZXJqZW5paCBzcHJlbWVubGppdmsuIFRha2EgdGFiZWxhIGRlZmluaXJhIGZ1bmtjaWpvICRnOiBEXzEgXHRpbWVzIERfMiBcdGltZXMgXGxkb3RzIFx0aW1lcyBEX3tkfSBcdG8gRF97ZCsxfSBcdGltZXMgRF97ZCsyfSBcdGltZXMgXGxkb3RzIFx0aW1lcyBEX3tkK219JCwga2kgemEgcG9kYW5lIHZyZWRub3N0aSBkaW1lbnppanNraWggc3ByZW1lbmxqaXZrIHZybmUgdnJlZG5vc3RpIHZzZWggJG0kLXRpaCBtZXJqZW5paCBzcHJlbWVubGppdmsgaGtyYXRpLgoKViB6Z29ybmplbSBwcmltZXJ1IHogb2JqZWt0aSwgdnNlIHRyaSBwb2RhdGtvdm5lIHRhYmVsZSB6IHVyZWplbmltaSBwb2RhdGtpIGBvYmxpa2FgLCBgdmlzaW5hYCBpbiBgc2lyaW5hYCBpbWFqbyBpc3RvIGRpbWVuemlqc2tvIHNwcmVtZW5saml2a28gYG9iamVrdGAgaW4gamloIHRvcmVqIGxhaGtvIHpkcnXFvmltbyB2IGVubyBwb2RhdGtvdm5vIHRhYmVsbyBgb2JqZWt0aWAgeiBlbm8gZGltZW56aWpza28gc3ByZW1lbmxqaXZrbyBgb2JqZWt0YCBpbiB0cmVtaSBtZXJqZW5pbWkgc3ByZW1lbmxqaXZrYW1pIGBvYmxpa2FgLCBgc2lyaW5hYCBpbiBgdmlzaW5hYCB0YWtvbGU6CmBgYHtyfQpvYmpla3RpX2RmNCA9IGRhdGEuZnJhbWUoCiAgb2JqZWt0ID0gbmFtZXMob2JqZWt0aSksCiAgb2JsaWthID0gc2FwcGx5KG9iamVrdGksIEZVTj1vYmxpa2Ffb2JqZWt0YSksCiAgc2lyaW5hID0gc2FwcGx5KG9iamVrdGksIEZVTj1zaXJpbmFfb2JqZWt0YSksCiAgdmlzaW5hID0gc2FwcGx5KG9iamVrdGksIEZVTj12aXNpbmFfb2JqZWt0YSkKKQpvYmpla3RpX2RmNApgYGAKClRhIHphZG5qYSByYXpsacSNaWNhIHBvZGF0a292bmUgdGFiZWxlIHR1ZGkgc2xlZGkgc3RhbmRhcmRvbSB1cmVqZW5paCBwb2RhdGtvdiBfdGlkeSBkYXRhXywgcHJpIMSNZW1lciBwcmVkcG9zdGF2aW1vLCBkYSBpbWFtbyBvcHJhdmthIHogZW5pbSB0aXBvbSBvcGF6b3ZhbmUgZW5vdGUgb2JqZWt0LCByYXpsacSNbmUgbmHEjWluZSBvcGF6b3ZhbmphIGluIG1lcml0ZXYgZW5vdCAob2JqZWt0b3YpIHBhIGRvbG/EjWltbyBrb3QgcmF6bGnEjW5lIGxhc3Rub3N0aSBvcGF6b3ZhbmloIGVub3Qgb3ppcm9tYSBzcHJlbWVubGppdmtlLgoKCiMjIFN0YW5kYXJkaSBwb2ltZW5vdmFuagoKSW1lbmEgdGFiZWwgaW4gc3ByZW1lbmxqaXZrIChzdG9scGNldikgbmFqIGJvZG8gemFwaXNhbmUgeiBtYWxpbWkgxI1ya2FtaS4gxIxlIGplIGltZSBzZXN0YXZsamVubyBpeiB2ZcSNIGJlc2VkLCB2bWVzbmUgcHJlc2xlZGtlIHphbWVuamFtbyB6IGBfYCwgbmEgcHJpbWVyIGBpbWVfaW5fcHJpaW1la2AsIG5lIHBhIGBpbWUgaW4gcHJpaW1la2AuIFYgaW1lbmloIHRhYmVsIGluIHNwcmVtZW5saml2ayBzZSBpem9naWJhbW8gxaF1bW5pa29tOiDEjSwgxaEgaW4gxb4gemFtZW5qYW1vIHogYywgcyBpbiB6LCBuYSBwcmltZXIsIGB6YXJpc2NlYCBuYW1lc3RvIGDFvmFyacWhxI1lYC4KClZyZWRub3N0aSB2IHRhYmVsaSB6YXBpxaFlbW8gdGFrbywga290IMW+ZWxpbW8gb3ppcm9tYSB0YWtvLCBrb3Qgc28gcG9kYW5lLCB6IHZlbGlraW1pIGluIG1hbGltaSDEjXJrYW1pLCBzIMWhdW1uaWtpIGluIHByZXNsZWRraS4gUHJpIHRlbSBtb3JhbW8gYml0aSBwb3pvcm5pIG5hIGtvZGlyYW5qZSB6bmFrb3YsIGtpIGdhIHVwb3JhYmxqYSBSLiBQcmV2ZXJpbW8gZ2EgbGFoa28gcyBrbGljZW0gZnVua2NpamUgYGxvY2FsZVRvQ2hhcnNldCgpYDogemHFvmVsZW5hIG5hc3Rhdml0ZXYgamUgYCJVVEYtOCJgLiBQcmkgYnJhbmp1IHBvZGF0a292IGl6IGJlc2VkaWxuaWggYWxpIEV4Y2VsLW92aWggZGF0b3RlayBtb3JhbW8gcG9za3JiZXRpLCBkYSBzZSBwb2RhdGtvdm5lIHZyZWRub3N0aSBtZWQgdXZvem9tIHByZXR2b3Jpam8gdiBrb2RpcmFuamUgem5ha292LCBraSBnYSB1cG9yYWJsamEgUiwgc2ljZXIgYm9kbyDFoXVtbmlraSAoaW4ga2FrxaFuaSBkcnVnaSB6bmFraSkgemFwaXNhbmkgbmFwYcSNbm8uCgoKLS0tCgojIE9zbm92ZSBgdGlkeXZlcnNlYDogVXZveiBpbiBpenZveiBwb2RhdGtvdm5paCB0YWJlbAoKUGFrZXQsIGtpIG9tb2dvxI1hIHByaXByYXZvIGluIGFuYWxpem8gdXJlamVuaWggcG9kYXRrb3YsIG9waXNhbmloIHYgcHJlasWhbmplbSBwb2dsYXZqdSBvbW9nb8SNYSB6Ymlya2EgcGFrZXRvdiwga2kgamloIHZzZWJ1amUga25qacW+bmljYSBbYHRpZHl2ZXJzZWBdKGh0dHBzOi8vd3d3LnRpZHl2ZXJzZS5vcmcvKS4gViB0ZW0gcG9nbGF2anUgYm9tbyBzcG96bmFsaSBmdW5rY2lqZSB6YSB1dm96IGluIGl6dm96IHBvZGF0a292bmloIHRhYmVsLCBraSBqaWggcG9udWphIHRhIGtuamnFvm5pY2EuIE5hanByZWogYm9tbyB1dm96aWxpIGtuamnFvm5pY286CmBgYHtyfQpsaWJyYXJ5KHRpZHl2ZXJzZSkKYGBgCgojIyBGb3JtYXQgQ1NWCgpEYXRvdGVrYSB2IGZvcm1hdHUgQ1NWIHZzZWJ1amUgYmVzZWRpbG5pIHphcGlzIHBvZGF0a292bmUgdGFiZWxlLCBramVyIGplIHZzYWthIHZyc3RpY2EgaXogdGFiZWxlIHphcGlzbmEga290IHZyc3RpY2EgdiBkYXRvdGVraSwgcG9zYW1lem5lIHZyZWRub3N0aSB2IHN0b2xwY2loIChjZWxpY2FoKSB0YWJlbGUgc28gbG/EjWVuZSB6IHZlamljbyBgLGAuCgpGdW5rY2lqYSwga2kgb21vZ2/EjWEgc2hyYW5qZXZhbmplIHBvZGF0a292bmUgdGFiZWxlIHYgZm9ybWF0dSBDU1YgKGFuZ2wuIF9Db21tYS1TZXBhcmF0ZWQgVmFsdWVzXykgamUgYHdyaXRlX2NzdmAuIFphIHBvZGFubyBwb2RhdGtvdm5vIHRhYmVsbyBpbiBpbWUgZGF0b3Rla2UsIGZ1bmtjaWphIHNocmFuaSBwb2RhdGtlIGl6IHBvZGF0a292bmUgdGFiZWxlIHYgZGF0b3Rla28gZm9ybWF0YSBDU1YgcyBwb2RhbmltIGltZW5vbToKYGBge3J9CndyaXRlX2NzdihvYmpla3RpX2RmNCwgIm9iamVrdGkuY3N2IikKYGBgCgpOZWthaiBrb3Jpc3RuaWggYXJndW1lbnRvdiBmdW5rY2lqZSBgd3JpdGVfY3N2YDoKCiogYG5hYCBzIHByaXZ6ZXRvIHZyZWRub3N0am8gYCJOQSJgIHBvZGEgbml6IHpuYWtvdiB6YSB6YXBpc292YW5qZSBuZXpuYW5paCB2cmVkbm9zdGkuCiogYGFwcGVuZGAgcyBwcml2emV0byB2cmVkbm9zdGpvIGBGQUxTRWAgamUgbG9nacSNbmEgdnJlZG5vc3QsIGtpIGRvbG/EjWkgYWxpIG5haiBwb2RhdGtpIHBvdm96aWpvIHByZWrFoW5qbyB2c2ViaW5vIG9ic3RvamXEjWUgZGF0b3Rla2UgKHZyZWRub3N0IGBGQUxTRWApIGFsaSBuYWogc2UgcG9kYXRraSB6YXBpxaFlam8gdiBuYWRhbGpldmFuanUgb2JzdG9qZcSNaWggdnJzdGljIHYgZGF0b3Rla2kgKHZyZWRub3N0IGBUUlVFYCkuCiogYGNvbF9uYW1lc2AgcyBwcml2emV0byB2cmVkbm9zdGpvIGAhYXBwZW5kYCBqZSBsb2dpxI1uYSB2cmVkbm9zdCwga2kgZG9sb8SNaSBhbGkgbmFqIHNlIHYgcHJ2aSB2cnN0aWNpIGRhdG90ZWtlIHphcGnFoWVqbyBpbWVuYSBzdG9scGNldi4KCkZ1bmtjaWphIGByZWFkX2NzdmAgcHJlYmVyZSBwb2RhdGtvdm5vIHRhYmVsbyBpeiBkYXRvdGVrZSBwb2RhbmVnYSBpbWVuYSwga2plciBzbyBwb2RhdGtpIHphcGlzYW5pIHYgZm9ybWF0dSBDU1YuIEtvcmlzdGVuIGFyZ3VtZW50IGZ1bmtjaWplIGplIGBjb2xfbmFtZXNgIHMgcHJpdnpldG8gdnJlZG5vc3RqbyBgVFJVRWAsIGtpIGRvbG/EjWkgYWxpIHBydmkgdnJzdGkgZGF0b3Rla2UgdnNlYnVqZSBpbWVuYSBzdG9scGNldi4KCkRydWdpIHBvZ29zdG8gdXBvcmFibGplbmkgYXJndW1lbnRpIGZ1bmtjaWplIGByZWFkX2NzdmAgdnBsaXZham8gbmEgdGlwZSBzdG9scGNldiB2IHBvZGF0a292bmkgdGFiZWxpIHogdXZvxb5lbmltaSBzdG9scGNpLiBGdW5rY2lqYSBuYW1yZcSNIHNhbW9kZWpubyB1Z290b3ZpIGthdGVyZWdhIHRpcGEgc28gcG9kYXRraSB2IHN0b2xwY3UgdGFrbywgZGEgcHJlZ2xlZGEgcHJ2aWggMTAwMCAodGlzb8SNKSB2cnN0aWMgcHJlZ2xlZG5pY2UuIFRvIGxhaGtvIHNwcmVtZW5pbW8gbmEgZHZhIG5hxI1pbmEsIG9waXNhbmEgdiBuYWRhbGpldmFuanUuCgoqIFogbmFzdGF2aXR2aWpvIGFyZ3VtZW50YSBgZ3Vlc3NfbWF4YCAocHJpdnpldGEgdnJlZG5vc3QgMTAwMCksIGtpIGRvbG/EjWkgxaF0ZXZpbG8gdnJzdGljLCBraSBqaWggcHJlZ2xlZGEgZnVua2NpamEgcHJlZGVuIHNlIG9kbG/EjWkgbyB0aXB1IHZyZWRub3N0aSB2IHN0b2xwY3UuIMSMZSDFvmVsaW1vLCBkYSBmdW5rY2lqYSBwcmVnbGVkYSB2c2UgcmF6cG9sb8W+bGppdmUgdnJlZG5vc3RpLCB1cG9yYWJpbW8gYGd1ZXNzX21heD1JbmZgLgoKKiBaIG5hc3Rhdml0dmlqbyBhcmd1bWVudGEgYGNvbF90eXBlc2AsIGtpIGxhaGtvIHBvdm96aSBzYW1vZGVqbm8gdWdvdGF2bGphbmplIHRpcG92IHN0b2xwY2V2LiBWcmVkbm9zdCBqZSBsYWhrbyB2ZWt0b3Igbml6b3Ygem5ha292IHogbmFzbGVkbmppbWkgdnJlZG5vc3RtaToKCiAgICAqIGAic2tpcCJgOiB2cmVkbm9zdGkgc3RvbHBjYSBuZSB1dmHFvmFtbzsKICAgICogYCJndWVzcyJgOiB0aXAgdnJlZG5vc3RpIGRvbG/EjWkgZnVua2NpamEgc2Ftb2Rlam5vOwogICAgKiBgImxpc3QiYDogdGlwIHZyZWRub3N0aSB2IHN0b3BsY3Ugc2Ugc3ByZW1pbmphOwogICAgKiBgImxvZ2ljYWwiYCwgYCJudW1lcmljImAsIGAidGV4dCJgIGFsaSBgImRhdGUiYDogdnJlZG5vc3RpIHYgc3RvbHBjdSBzbyBsb2dpxI1uZSwgbnVtZXJpxI1uZSwgbml6aSB6bmFrb3YgYWxpIGRhdHVtaS4KCiAgICBWIHByaW1lcnUsIGRhIHphIHZyZWRub3N0IGFyZ3VtZW50YSBgY29sX3R5cGVzYCBwb2RhbW8gbml6IHpuYWtvdiwgcG90ZW0gdGlwIHBvc2FtZXpuZSBrb2xvbmUgdXN0cmV6YSBlbmVtdSB6bmFrdSB2IG5penUuIFBvbWVuIHpuYWtvdiBqZSBgLWAgYWxpIGBfYCB6YSBgc2tpcGAsIGA/YCB6YSBgZ3Vlc3NgLCBgbmAgemEgYG51bWVyaWNgLCBgY2AgemEgYHRleHRgIGluIGBEYCB6YSBgZGF0dW1gLiBDZWxvdGVuIHNlem5hbSBtb8W+bmloIHRpcG92IGluIG9rcmFqxaFhdiBkb2JpbW8geiBgP3JlYWRfY3N2YCBpbiBgP3JlYWRyOjpjb2xzYC4KICAgIAojIyBFeGNlbC1vdmUgcHJlZ2xlZG5pY2UKClYgUi1qdSBsYWhrbyBiZXJlbW8gcG9kYXRrZSBpeiBFeGNlbC1vdmloIHByZWdsZWRuaWMgeiB1cG9yYWJvIHVrYXphIGByZWFkX2V4Y2VsYCB2IGZvcm1hdHUgWExTIGFsaSBYTFNYLCBraSBzZSBkb2xvxI1pIG5hIG9zbm92aSBwb2RhbGrFoWthIHBvZGFuZWdhIGltZW5hIGRhdG90ZWtlLiDEjGUgaG/EjWVtbyB1Z290b3ZpdGkga2F0ZXJpIGxpc3RpIChhbmdsLiBfc2hlZXRzXykgc28gbmEgdm9sam8gdiBkYXRvdGVraSwgdXBvcmFiaW1vIGZ1bmtjaWpvIGBleGNlbF9zaGVldHNgLiBPYmUgZnVua2Npamkga290IHBydmkgYXJndW1lbnQgc3ByZWptZXRhIGltZSBkYXRvdGVrZSB6IEV4Y2VsIHByZWdsZWRuaWNvLgoKTmVrYWoga29yaXN0bmloIHBhcmFtZXRyb3YgZnVua2NpamUgYHJlYWRfZXhjZWxgOgoKKiBgc2hlZXRgIHMgcHJpdnpldG8gdnJlZG5vc3RqbyBgTlVMTGAgZG9sb8SNaSBsaXN0IHByZWdsZWRuaWNlLCBraSBnYSBob8SNZW1vIHByZWJyYXRpLiBWcmVkbm9zdCBqZSBsYWhrbyBjZWxvxaF0ZXZpbMSNbmEgKHBvZGFtbyBwb3ppY2lqbyBsaXN0YSB2IHByZWdsZWRuaWNpKSBhbGkgbml6IHpuYWtvdiAocG9kYW1vIGltZSBsaXN0YSB2IHByZWdsZWRuaWNpKS4gxIxlIHBvZGFuYSB2cmVkbm9zdCBuZSBkb2xvxI1pIGxpc3RhLCBmdW5rY2lqYSBwcmViZXJlIHBvZGF0a2UgaXogcHJ2ZWdhIGxpc3RhIHByZWdsZWRuaWNlLgoqIGByYW5nZWAgcyBwcml2emV0byB2cmVkbm9zdGpvIGBOVUxMYCBkb2xvxI1pIG9ic2VnIGNlbGljIHByZWdsZWRuaWNlLCBrYXRlcmUgdnJlZG5vc3RpIGhvxI1lbW8gdXZveml0aSB2IFIuIFVwb3JhYmxqYW1vIEV4Y2VsLW92IG5hxI1pbiBkb2xvxI1hbmphIG9ic2VnYSwgbnByLiBgIkExOkMxMSJgIGFsaSBwYSBgIlJlenVsdGF0aSFBMTpRMTAyNCJgLiBWIHByaW1lcnUgcG9kYW5lZ2Egb2JzZWdhLCBmdW5rY2lqYSB1dm96aSB0dWRpIHByYXpuZSB2cnN0aWNlIGluIHN0b2xwY2UgaXogdGVnYSBvYnNlZ2EuCiogYGNvbF9uYW1lc2AsIGBndWVzc19tYXhgIGluIGBjb2xfdHlwZXNgIGltYWpvIGlzdGkgcG9tZW4ga290IGlzdG9pbWVuc2tpIGFyZ3VtZW50aSBmdW5rY2lqZSBgcmVhZF9jc3ZgIG9waXNhbmUgdiBwcmVqxaFuamVtIHJhemRlbGt1LgoKClByZWdsZWQgZnVua2NpaiB6YSB1dm96IGluIGl6dm96IHBvZGF0a292bmloIHRhYmVsLCBraSBqaWggcG9udWphIGB0aWR5dmVyc2VgLCBqZSBuYSB2b2xqbyB2IFtwbG9uayBsaXN0dSBfRGF0YSBpbXBvcnQgd2l0aCB0aGUgdGlkeXZlcnNlX10oaHR0cHM6Ly9naXRodWIuY29tL3JzdHVkaW8vY2hlYXRzaGVldHMvYmxvYi9tYWluL2RhdGEtaW1wb3J0LnBkZikuCgotLS0KCiMgTmFsb2dlCgoxLiBEZW5pbW8sIGRhIHNtbyBBbGXFoXUsIEJhcmJhcmksIENpcmlsdSBpbiBEYXJqaSBpem1lcmlsaSB2acWhaW5lIHYgY2VudGltZXRyaWggKDE4MCwgMTY1LCAxNjAsIDE5MykgaW4gdGXFvmUgdiBraWxvZ3JhbWloICg4NywgNTgsIDY1LCAxMDApLiBOYXRvIHNtbyB6YSB2c2FrbyBvc2VibyBpenJhxI11bmFsaSBpbmRla3MgdGVsZXNuZSBtYXNlIChJVE0pIHBvIGZvcm11bGkKJCQgXHRleHR7SVRNfSA9IFxmcmFje1x0ZXh0e3Rlxb5hIHYga2lsb2dyYW1paH19eyhcdGV4dHt2acWhaW5hIHYgbWV0cmlofSleMn0uICQkCkl6IGl6bWVyamVuaWggaXogaXpyYcSNdW5hbmloIHBvZGF0a2Ugc2VzdGF2aSBwb2RhdGtvdm5vIHRhYmVsbyB6IHVyZWplbmltaSBwb2RhdGtpIHRpcGEgX3RpZHkgZGF0YV8uIFByaSB0ZW0gZG9icm8gcHJlbWlzbGkga2FrxaFuZSBtb8W+bm9zdGkgaW1hxaEgbmEgdm9sam86IGthaiBzbyB0aXBpIGVub3QsIHNwcmVtZW5saml2a2UgaW4gdnJzdGljZSB2IHRhYmVsYWggeiB1cmVqZW5pbWkgcG9kYXRraS4KCjEuIFYgZGF0b3Rla2kgW2BpemlkaS54bHN4YF0oLi9pemlkaS54bHN4KSBqZSBwcmVnbGVkbmljYSBzIHBvZGF0a2kgbyBudW1lcmnEjW5paCBpemlkaWggdXBvcmFiZSBkdmVoIHJhemxpxI1uaWggdGVyYXBpaiBuYSB0cmVoIHBhY2llbnRpaC4gUHJhem5lIGNlbGljZSB1c3RyZXpham8gbmV6bmFuaW0gdnJlZG5vc3RpbSBpemlkb3YuIE5hcGnFoWkgcHJvZ3JhbSB2IFItanUsIGtpIHByZWJlcmUgcG9kYXRrZSBpeiBwcmVnbGVkbmljZSBpbiBpeiBuamloIHNlc3RhdmkgcG9kYXRrb3ZubyB0YWJlbG8geiB1cmVqZW5pbWkgcG9kYXRraS4gTmFtaWc6IHJhYmnFoSBzZXN0YXZpdGkgZW5vIHRhYmVsbyB6IGR2ZW1hIGRpbWVuemlqc2tpbWEgaW4gZW5vIG1lcmplbm8gc3ByZW1lbmxqaXZrby4KCjEuIFByb2Zlc29yaWNhLCBraSBuYSBGTUYgaXp2YWphIGR2ZS1zZW1lc3Ryc2tpIHByZWRtZXQsIHNpIGplIHYgZGF0b3Rla2kgW2ByZXp1bHRhdGkta29sb2t2aWpldi54bHN4YF0oLi9yZXp1bHRhdGkta29sb2t2aWpldi54bHN4KSB6YXBpc292YWxhIHJlenVsdGF0ZSBrb2xva3ZpamV2IHYgcHJ2ZW0gaW4gZHJ1Z2VtIHNlbWVzdHJ1LiBOYXBpxaFpIHByb2dyYW0gdiBSLWp1LCBraSBwcmViZXJlIHBvZGF0a2UgaXogdnNlaCBsaXN0b3YgcHJlZ2xlZG5pY2U6IG5haiBwcm9ncmFtIHNhbSB1Z290b3ZpIGtvbGlrbyBsaXN0b3YgaW1hIHByZWdsZWRuaWNhLgoKICAgIE5hdG8gcHJlbWlzbGkga2F0ZXJlIHZzZSBlbm90ZSBvcGF6b3ZhbmphIHJhYmltbyB6YXRvLCBkYSBwcm9mZXNvcmljaSBwb21hZ2FtbyBrb3Jla3RubyBwcmV0dm9yaXRpIHBvZGF0a2UgbyByZXp1bHRhdGloIGtvbG9rdmlqZXYgdiBwb2RhdGtvdm5lIHRhYmVsZSB6IHVyZWplbmltaSBwb2RhdGtpIHRpcGEgX3RpZHkgZGF0YV8sIGtpIGJpIGppIG9tb2dvxI1hbGUgYm9sasWhbyBhbmFsaXpvIHBvZGF0a292LiBSb8SNbm8gYWxpIHMgcHJvZ3JhbW9tIHYgUi1qdSBzZXN0YXZpIHRlIHBvZGF0a292bmUgdGFiZWxlLgoKLS0tCgpadmV6ZWsgc2VtIG9rdG9icmEgMjAyMiBwcmlwcmF2aWwgW0xqdXDEjW8gVG9kb3JvdnNraV0oaHR0cDovL2t0Lmlqcy5zaS9+bGp1cGNvLykgdiBqZXppa3UgW1IgTWFya2Rvd25dKGh0dHA6Ly9ybWFya2Rvd24ucnN0dWRpby5jb20pLiBJZGVqZSB6YSBwcmltZXJlIHNlbSBzaSB2IHZla2lraSBtZXJpIGl6cG9zb2phbCBpeiB1xI1iZW5pa2EgWyhXaWNraGFtIDIwMTkpXShodHRwczovL2Fkdi1yLmhhZGxleS5uei8pLCB2c2UgbW9yZWJpdG5lIG5hcGFrZSB2IHByaW1lcmloIHNvIGl6a2xqdcSNbm8gbW9qZS4KClZlxI0gbyBwcmF2aWxpaCB1cmVqYW5qYSBwb2RhdGtvdiB2IHBvZGF0a292bmUgdGFiZWxlIGxhaGtvIHByZWJlcmV0ZSB2IMSNbGFua3UgV2lnaGFtLCBILiAoMjAxNCkgVGlkeSBEYXRhLiBfSm9ydW5hbCBvZiBTdGF0aXN0aWNhbCBTb2Z0d2FyZSA1OSgxMClfOiAxLTIzLiBET0k6IFsxMC4xODYzNy9qc3MudjA1OS5pMTBdKGh0dHBzOi8vZG9pLm9yZy8xMC4xODYzNy9qc3MudjA1OS5pMTApLg==