Skalarji (angl. scalars)

R ima štiri osnovne tipe skalarjev oziroma njihovih vrednosti:

Spremenljivki v R-ju dodelimo (novo) skalarno vrednost z operatorjem <-. Gre za tradicionalni zapis, iz časov nastanka programskega okolja R. Danes R omogoča uporabo običajnega operatorja =, ki ga poznamo iz drugih programskih jezikov.

Po dodelitvi, tip spremenljivke se spremeni v tip skalarne vrednosti, ki ji jo dodelimo. Pri izračunih, ki vključujejo vrednosti in spremenljivke različnih tipov, se tipi dinamično (samodejno, v terminologiji R tudi prisilno, angl. coercion) in po potrebi prilagodijo tako, da bo izračun možno izpeljati. Prisilno prilagajanje tipov vedno poteka v smer, ki jo nakazuje vrstni red naštevanja štirih tipov zgoraj: logična vrednost se pretvori v celoštevilsko, ta nato v numerično (plavajočo vejico), ter na koncu še (seveda po potrebi) v niz znakov.

Pozor, prilagajanje tipov včasih tudi (neprijetno) preseneti, npr. 1 == "1" (operator == preveri, če sta izraza na levi in desni strani enaka). Izraz, ki bi lahko bil rezultat programske napake, R brez težav (prijavljanja napak ali opozoril) ovrednoti v TRUE in ne v morebiti bolj pričakovano vrednost FALSE (prepričaj se, da je temu tako, nato premisli in pojasni zakaj).

Tip poljubne spremenljivke, vrednosti ali izračuna, lahko preverimo s funkcijo typeof. Primer, ki ponazori samodejno pretvorbo med različnimi osnovnimi tipi:

x = 1L + TRUE
x
[1] 2
typeof(x)
[1] "integer"
x = 1L + TRUE + 1
x
[1] 3
typeof(x)
[1] "double"
x = !x
x
[1] FALSE
typeof(x)
[1] "logical"

Zamenjavo tipa vrednosti lahko sami sprožimo s funkcijami as.logical, as.integer, as.double in as.character. S temi funkcijami lahko preverimo kako R pretvarja oz. prilagaja tipe skalarjev. Med zanimivimi klici te funkcije sodijo npr. as.logical(0), as.character(FALSE), as.integer(2.8). Preden poskusite ukaze, preverite, ali znate pravilno napovedati njihovo delovanje.

Še ena, zelo pomembna skalarna vrednost je neznana vrednost NA. Bodite pozorni na naslednji dve dejstvi (zakaj je temu tako?):

NA == 0
[1] NA
NA == NA
[1] NA

Bodite pozorni: neznana vrednost NA je “kužna”. V večini primerov je vrednost izraza (izračuna), ki vključuje NA, enaka NA. Poznamo le nekaj redkih izjem, npr. NA & FALSE, NA | TRUE in NA ^ 0. Znova premisli zakaj je temu tako.

Za preverjanje ali je vrednost spremenljivke ali izraza neznana uporabimo funkcijo is.na. Na voljo so tudi štiri podobne funkcije s katerimi lahko preverimo, če je izraz nekega določenega tipa: is.logical, is.integer, is.double in is.character. Rezultati vseh teh funkcij so logične vrednosti.

Naj na tem mestu omenimo še oznako NULL, ki v R-ju nakazuje odsotnost vrednosti. Za razliko od NA, ki nakazuje prisotnost vrednosti, ki pa ni znana.

NULL == NULL
logical(0)
NULL == 0
logical(0)
NULL == NA
logical(0)

V vseh treh primerih je rezultat logical(0), kar je oznaka za prazen vektor logičnih vrednosti. Podobno kot NULL tudi prazen vektor nakazuje odsotnost vrednosti. Zadnja dva stavka omenjata podatkovni tip vektor, ki ga tudi štejemo med osnovnimi podatkovnimi tipi v R-ju. Spoznali ga bomo v nadaljevanju.


Vektorji (angl. vectors, atomic vectors)

Vektor predstavlja urejen seznam skalarjev istega tipa. Skalarje imenujemo elementi ali komponente seznama. Urejenost seznama pomeni, da je vrstni red elementov pomemben in lahko elemente naslavljamo na osnovi njihove pozicije v seznamu (urejenost namreč ne pomeni, da so elementi urejeni po naraščajoči ali padajoči vrednosti). Z drugimi besedami, znamo povedati kateri element seznama je prvi, drugi ali zadnji.

Vektorje najbolj preprosto ustvarjamo s klicem funkcije c, ki sprejme poljubno število argumentov in iz njih sestavi seznam. Dolžino vektorja, ki je enaka številu njegovih elementov, nam vrne funkcija length. Vektor dolžine 0 imenujemo prazen vektor. Že omenjena funkcija typeof pa nam vrne tip elementov vektorja (ne pozabimo, da so vsi elementi vektorja enakega tipa).

vl = c(TRUE, FALSE, TRUE, TRUE)
typeof(vl)
[1] "logical"
vi = c(-20L, -10L, 0L, 10L, 20L)
length(vi)
[1] 5
vd = c(-1, 2.5, 3)
as.integer(vd)
[1] -1  2  3
vs = c("nekaj stringov (ups, nizov znakov)", "za primer", "pa še številka", "42")
x = as.double(vs)
Warning: NAs introduced by coercion
x
[1] NA NA NA 42
is.na(x)
[1]  TRUE  TRUE  TRUE FALSE

Bodimo posebej pozorni na zadnje tri rezultate. Pri pretvarjanju nizov znakov v numerične vrednosti, Tiste nize znakov, ki se jih ne da (oziroma jih R ne zna) pretvoriti v numerične vrednosti, R pretvori v neznane vrednosti NA. Pri tem izpiše opozorilo (angl. warning) Neznane vrednosti NA vpeljane s (prisilnim) prilagajanjem tipov (angl. NAs introduced by coercion).

Poglejmo še kaj se zgodi, če poskusimo sestaviti vektor iz vrednosti različnih tipov:

x = c(TRUE, 1L)
x
[1] 1 1
typeof(x)
[1] "integer"
x = c(FALSE, 1, 2L)
x
[1] 0 1 2
typeof(x)
[1] "double"
x = c(TRUE, 1, 2.5)
x
[1] 1.0 1.0 2.5
typeof(x)
[1] "double"
x = c(FALSE, 1, 2L, "3")
x
[1] "FALSE" "1"     "2"     "3"    
typeof(x)
[1] "character"

Premislite in pojasnite zakaj so tipi teh vektorjev taki kot so?

Čeprav vektorji predstavljajo le enodimenzionalne sezname vrednosti istega tipa, z njimi lahko predstavimo tudi dvodimenzionalne matrike ali polja (tenzorje) poljubnega števila dimenzij. Zato da razumemo kako, spoznajmo najprej koncept vektorskih atributov.

Atributi vektorjev in matrike

Atributi so urejeni pari oblike (ime, vrednost), ki vektorje označijo z meta podatki. Za nastavljanje vrednosti atributov vektorja uporabljamo funkcijo attr, ki jo lahko tudi uporabimo za preverjanje vrednosti atributa. Funkcija attributes vrne vse atribute podanega vektorja.

v = c(-20L, -10L, 0L, 10L, 20L)

attr(v, "prvi_atribut") = 1
attr(v, "drugi_atribut") = 2.3

print("Prvi atribut vektorja v")
[1] "Prvi atribut vektorja v"
attr(v, "prvi_atribut")
[1] 1
print("Vsi atributi vektorja v")
[1] "Vsi atributi vektorja v"
attributes(v)
$prvi_atribut
[1] 1

$drugi_atribut
[1] 2.3

Rezultat funkcije attributes pravzaprav seznam atributov in njihovih vrednosti. R-jevsko podatkovno strukturo seznam bodo spoznali pozneje. Pozor: Vektorji v R-ju so podatkovna struktura, ki je podobna strukturi seznam v Python-u.

Funkcija structure omogoča dodajanje atributov že ob tvorjenju vektorja:

v = structure(
  c(-20L, -10L, 0L, 10L, 20L),
  prvi_atribut = 1,
  drugi_atribut = 2.3
)
v
[1] -20 -10   0  10  20
attr(,"prvi_atribut")
[1] 1
attr(,"drugi_atribut")
[1] 2.3
attributes(v)
$prvi_atribut
[1] 1

$drugi_atribut
[1] 2.3

Atributi so začasni. Mnoge funkcije in operacije nad seznami izbrišejo (pozabijo) atribute, preizkusite npr. v[1] ali sum(v):

attributes(vi[1])
NULL
attributes(sum(vi))
NULL

Vrednost izraza v[1] je enaka vrednosti prvega elementa vektorja v. Funkcija sum vrne vsoto vrednosti elementov podanega vektorja.

Dva atributa imajo v R-ju poseben pomen:

  • names je vektor nizov znakov, ki je enake dolžine kot originalni vektor. Vsak element vektorja names določa ime elementa originalnega vektorja.
  • dim je vektor celoštevilskih vrednosti, ki pretvori vektor v matriko ali polje.

Poimenovan vektor je vektor v katerem ima vsak element svoje ime. Imena elementov vektorja lahko nastavimo na vsaj dva načina: pri tvorjenju vektorja ali pa naknadno za navadni vektor z uporabo funkcije names:

x1 = c(prvi = 1, drugi = 2, tretji = 3)
x1
  prvi  drugi tretji 
     1      2      3 
x2 = c(1, 2, 3)
names(x2) = c("prvi", "drugi", "tretji")
x2
  prvi  drugi tretji 
     1      2      3 

Funkcijo names vrne imena elementov v poimenovanem seznamu. Preverite kaj vrne funkcija names za neimenovan seznam, npr. names(c(1,2,3)). Poimenovan seznam lahko spremenimo v neimenovanega (torej v vektor z elementi brez imen) s klicem funkcije unname:

x3 = unname(x2)
x3
[1] 1 2 3

Dimenzije vektorja, t.j., vrednost atributa dim določimo lahko pri tvorjenju vektorja ali naknadno z uporabo funkcije dim:

x = c(1, 2, 3, 4, 5, 6)

A1 = matrix(x, nrow = 2, ncol = 3)
A1
     [,1] [,2] [,3]
[1,]    1    3    5
[2,]    2    4    6
dim(A1)
[1] 2 3
A2 = x
dim(A2) = c(3, 2)
A2
     [,1] [,2]
[1,]    1    4
[2,]    2    5
[3,]    3    6

Tvorjenje vektorjev

Oglejmo si nekaj načinov za tvorjenje vektorjev:

  • i:j je celoštevilski vektor z elementi i, i+1, ..., j, če je j >= i oziroma elementi i, i-1, \ldots, j, če je j < i.

  • seq(from = i, to = j, by = s) je vektor numeričnih vrednosti, kjer je prvi element i, razlika med dvema zaporednima elementoma je s, zadnji element pa ne sme preseči vrednosti j. Pri različici seq(from = i, to = j, length.out = n) namesto razlike med zaporednima elementoma določimo število elementov, ki se ekvidistančno (enakomerno) razporedijo na intervalu [from, to].

  • rep(v, times = n) je vektor sestavljen tako, da elemente v ponovimo n-krat. Če je v skalar, je dolžina dobljenega vektorja n, če pa je v vektor, je dolžina dobljenega vektorja n * length(v). Pri različici rep(v, each = n) določimo kolikokrat ponovimo vsak posamezen element v.

  • sample(v, n) je vektor dolžine n (imenujemo ga vzorec), katerega elementi so naključno izbrani elementi vektorja v. Posamezen element iz v se lahko v vzorcu pojavi le enkrat. Posledica tega je, da je predpogoj za uspešno vzorčenje length(v) >= n. Različica sample(v, n, replace=T) opravi vzorčenje s ponavljanjem, kjer je posamezen element v se lahko v vzorcu pojavi tudi večkrat.

Poigraj se z naslednjimi primeri:

-1:13
 [1] -1  0  1  2  3  4  5  6  7  8  9 10 11 12 13
13:-1
 [1] 13 12 11 10  9  8  7  6  5  4  3  2  1  0 -1
13:13
[1] 13
seq(from = 0.5, to = -0.5, by = -0.1)
 [1]  0.5  0.4  0.3  0.2  0.1  0.0 -0.1 -0.2 -0.3 -0.4 -0.5
seq(from = -0.5, to = 0.5, length.out = 4)
[1] -0.5000000 -0.1666667  0.1666667  0.5000000
rep(-1:1, times = 3)
[1] -1  0  1 -1  0  1 -1  0  1
rep(-1:1, each = 3)
[1] -1 -1 -1  0  0  0  1  1  1
sample(-10:10, 12)
 [1]  2 -1  5 -9  7  3 -4  1  4 -8  6 10
sample(-10:10, 12)
 [1]  -6  10   8  -3  -5  -7 -10  -8   7   9   6   1
# sample(-10:10, 24): pojasni napako, ki jo javi ta klic funkcije sample

sample(1:6, 10, replace=T)
 [1] 4 3 2 2 2 5 6 6 2 6

Indeksiranje vektorjev

Indeks elementa v vektorju v ustreza njegovi poziciji v urejenem seznamu. Tako je indeks prvega elementa vektorja 1, drugega 2, in tako naprej, vse do indeksa zadnjega elementa. Ta je enak dolžini vektorja length(v). Pozor: to je, poleg poimenovanja vektorjev, še ena pomembna razlika med R-jem in programskim jezikom Python, ki nas pogosto zmede. V Pythonu je indeks prvega elementa seznama 0, drugega 1 in zadnjega len(v) - 1.

Posamezen element vektorja v naslovimo z izrazom v[i] za poljuben element i celoštevilskega vektorja 1:length(x). Tako naslovljenemu elementu lahko dodelimo novo vrednost z v[i] = nova_vrednost ali pa ga uporabimo v poljubnem izrazu, npr. v[i] + 2. Tako indeksiranje in naslavljanje posameznega elementa vektorja pozna tudi večina drugih programskih jezikov. Za razliko od slednjih, R ponuja še veliko drugih možnosti indeksiranja, ki jih bomo spoznali v nadaljevanju. Fleksibilnost indeksiranja vektorjev je ključnega pomena pri uporabi R-ja za podatkovno analizo.

Poglejmo štiri načine naslavljanja poljubnega števila elementov vektorja v. Vsi so oblike v[vi], kjer je v vektor katerega elemente indeksiramo in vi vektor indeksov.

  • Elementi vi so pozitivna cela števila: rezultat indeksiranja je vektor elementov vektorja v, katerih indeksi v v so elementi vektorja vi. Element i indeksa se lahko tudi večkrat ponovi v vektorju indeksov vi. To povzroči, da se tudi element v[i] pojavi večkrat v rezultatu indeksiranja.

  • Elementi vi so negativna cela števila: rezultat indeksiranja je vektor elementov v, katerih indeksi niso elementi vektorja absolutnih vrednosti elementov vi. Z drugimi besedami element -i v vektorju indeksov vi povzroči, da element v[i] ni element rezultata indeksiranja.

    Pozor: Pozitivnih in negativnih celoštevilčnih vrednosti indeksov v vektorju vi ne smemo kombinirati.

  • Elementi vi so logične vrednosti: rezultat indeksiranja je vektor elementov v za katere imajo enako-ležeči elementi vi vrednost TRUE. Običajno sta vektorja v in vi enakih dolžin. Če je vektor vi daljši od vektorja v, se pri indeksiranju upošteva le prvih length(v) njegovih elementov. Če je vektor vi krajši od vektorja v, se njegovi elementi reciklirajo, kot bo opisano v ustrenzem razdelku spodaj.

  • Elementi vi so nizi znakov: rezultat indeksiranja je vektor elementov imenovanega vektorja v, katerih imena so v seznamu vi. Če se ime i v vektorju vi pojavi večkrat, se tudi v rezultatu indeksiranja element v[i] pojavi večkrat.

Pozor: neznana vrednost NA v vektorju indeksov vi vedno vrne element NA.

Dva posebna primera indeksiranja sta v[], katerega rezultat je enak v ter v[0], katerega rezultat je prazen vektor (slednje pogosto zmede programerje v Python-u). Oznaka za prazen vektor je logical(0), integer(0), numeric(0) ali charcter(0), v odvisnosti od tipa vrednosti elementov vektorja.

Nekaj primerov, kjer je vi vektor celoštevilskih vrednosti:

v = c(1.1, -2.2, 3.3, -4.4)

v[3:1] # običajno
[1]  3.3 -2.2  1.1
v[c(1, 3, 1)] # ponovitev istega elementa
[1] 1.1 3.3 1.1
v[order(v)] # urejene vrednosti elementov
[1] -4.4 -2.2  1.1  3.3
v[order(v)] = c(1.1, 2.2, 3.3, 4.4) # indeksiranem vektorju lahko dodelimo vrednosti (!)

v
[1] 3.3 2.2 4.4 1.1
v[c(-1, -3)] # negativni indeksi
[1] 2.2 1.1
# v[-1:1] # kombiniranje negativnih in pozitivnih indeksov ni dovoljeno

In še nekaj primerov

v = c(4.2, 3.3, 5.4, 2.1)
v
[1] 4.2 3.3 5.4 2.1
v[c(TRUE, TRUE, FALSE, FALSE)] # logične vrednosti
[1] 4.2 3.3
v[v > 3] # indeksiranje z logičnim pogojem
[1] 4.2 3.3 5.4
v > 3 # kar je pravzaprav indeksiranje z logičnimi vrednostmi
[1]  TRUE  TRUE  TRUE FALSE
v[v > 3] = 3 # dodeljevanje (!)
v
[1] 3.0 3.0 3.0 2.1
v = v[v != 3] # izbira elementov, ki izpolnjujejo pogoj
v
[1] 2.1

Matrike najbolj pogosto indeksiramo z dvema vektorjema:

M = 1:20
dim(M) = c(4, 5)
colnames(M) = toupper(letters[1:ncol(M)])
M
     A B  C  D  E
[1,] 1 5  9 13 17
[2,] 2 6 10 14 18
[3,] 3 7 11 15 19
[4,] 4 8 12 16 20
M[1:2, ] # izbira vrstic, vsi stolpci
     A B  C  D  E
[1,] 1 5  9 13 17
[2,] 2 6 10 14 18
M[1, ] # ena vrstica, R jo samodejno spremeni v vektor
 A  B  C  D  E 
 1  5  9 13 17 
M[1, , drop = FALSE] # eno-vrstična matrika, R-ju preprečimo samodejno pretvorbo v vektor
     A B C  D  E
[1,] 1 5 9 13 17
M[, c(2,1)] # izbira stolpcev, vse vrstice - bodite pozorni na vrstni red
     B A
[1,] 5 1
[2,] 6 2
[3,] 7 3
[4,] 8 4
M[1, 1] # izbira enega elementa matrike, rezultat je skalar
A 
1 
M[1, 1, drop = FALSE] # rezultat je matrika dimenzij 1x1
     A
[1,] 1

Lahko pa jih indeksiramo tudi z navadnim vektorjem ali matriko, v obeh primerih je rezultat vektor:

M
     A B  C  D  E
[1,] 1 5  9 13 17
[2,] 2 6 10 14 18
[3,] 3 7 11 15 19
[4,] 4 8 12 16 20
M[c(4, 15)]
[1]  4 15
M[c(-4, -15)]
 [1]  1  2  3  5  6  7  8  9 10 11 12 13 14 16 17 18 19 20
MI = matrix(
  c(
    1, 1,
    3, 1,
    2, 5
  ),
  ncol = 2, byrow = TRUE
)
dim(MI) = c(3, 2)
M[MI]
[1]  1  3 18

Računanje z vektorji in recikliranje elementov vektorja

Vektorji so pravzaprav osnovni tip podatkov v R-ju, tako osnovni, da skalarje obravnavamo kot posebna vrsta vektorjev z enim elementom. Zato nad vektorji lahko izvajamo operacije z osnovnimi aritmetičnimi operatorji in matematičnimi funkcijami. V nadaljevanju si bomo ogledali posebnosti uporabe operatorjev in funkcij nad vektorji.

Osnovni operatorji

Za izvajanje osnovnih aritmetičnih operacij so v R-ju na voljo aritmetični operatorji popisani v spodnji tabeli. V vseh primerih se oznake vektorjev začnejo z v, oznake skalarjev s s, oznake matrik z m in oznake logičnih vrednosti z l.

Operator (oznaka) Pomen (ime) Primer(i) uporabe
+ seštevanje s1 + s2, v1 + v2, v + s
- odštevanje v1 - v2, v - s
* množenje v1 * v2, v * s
/ deljenje v1 * v2, v / s
%/% celoštevilsko deljenje v1 %/% v2, v1 %/% s
%% ostanek pri celoštevilskem deljenju v1 %% v2, v1 %% s, s %% v1
^ potenca v1 ^ v2, v ^ s, s ^ v
%*% matrično množenje m1 %*% m2

Kot vidimo iz primerov v zgornji tabeli, operanda podanega operatorja sta lahko vektorja, skalarja ali pa kombinacija vektorja in skalarja. Preden pojasnimo pravila za izvajanje operatorjev nad vektorji, si bomo ogledali še tabelo operatorjev primerjave (prvih šest vrstic) in logičnih operatorjev (zadnjih pet vrstic).

Operator (oznaka) Pomen (ime) Primer(i) uporabe
== enaki vrednosti s1 == s2, v1 == s, v1 == v2
!= različni vrednosti s1 != s2, v1 != s, v1 != v2
< vrednost manjša s1 < s2, v1 < s, v1 < v2
<= vrednost manjša ali enaka s1 <= s2, v1 <= s, v1 <= v2
> vrednost večja s1 > s2, v1 > s, v1 > v2
>= vrednost večja ali enaka s1 >= s2, v1 >= s, v1 >= v2
& konjunkcija, logični in s1 & s2, v1 & v2, v & s
| disjunkcija, logični ali s1 | s2, v1 | v2, v | s
! negacija, logični ne ! s, ! v
&& konjunkcija dveh logičnih vrednosti l1 && l2
|| disjunkcija dveh logičnih vrednosti l1 || l2

Zadnja dva operatorja (&& in ||) sta posebna, ker oba obravnavata svoja operanda kot skalarni logični vrednosti in je tako rezultat operacije skalarna logična vrednost TRUE ali FALSE.

Koristen operator, ki ne sodi v zgoraj opisane kategorije, je %in%, ki ga uporabljamo v izrazih oblike s %in% v zato, da preverimo, če je skalar s element vektorja v. Izraz vrne TRUE v primeru, ko je s element vektorja v. Sicer pa vrne FALSE.

Recikliranje elementov vektorja

Kombiniranje različnih tipov operandov (t.j., vektorjev in skalarjev ali pa vektorjev različnih dolžin) ima svoje omejitve, ki jih moramo upoštevati pri pisanju pravilnih izrazov v R-ju.

Pri izvajanju operacije v1 op v2, morata vektorja v1 in v2, ki nastopata kot operanda (primerjalnega, logičnega ali aritmetičnega) operatorja op, izpolnjevati enega izmed naslednjih dveh pogojev.

  1. Vektorja sta enakih dimenzij, t.j., velja dim(v1) == dim(v2). V primeru navadnih vektorjev, ki niso večdimenzionalni (matrike ali polja oziroma tenzorji), to pomeni, da sta enakih dolžin length(v1) == length(v2). Če sta matriki, to pomeni, da morata biti enakih dimenzij. Izjema pri matrikah je matrično množenje, pri katerem moramo upoštevati običajno omejitev da mora imeti prva matrika toliko stolpcev, kot ima druga matrika vrstic.

    Če je ta pogoj izpolnjen, je rezulat v izvajanja operacije enakih dimenzij kot operanda. Prvi element dobimo z uporabo operanda na prvih elementih operandov, t.j., prvi element rezultata je v[1] = v1[1] op v2[1].

    Ta pogoj izpolnjuje tudi običajna operacija med dvema skalarjema oblike s1 op s2, kjer sta očitno oba operanda dimenzije oziroma dolžine 1.

  2. Dolžina daljšega vektorja (označimo ga z v1) je večkratnik dolžine krajšega v2, t.j., velja length(v1) == length(v2) * k, kjer je k naravno število večje od 1. V tem primeru R pred izvajanjem operacije predela krajši vektor v dalšega tako, da elemente krajšega vektorja reciklira k-krat.

    Poseben primer tega pogoja je operacija vektorja s skalarjem (in skalarja z vektorjem), kajti, kot smo povedali že na začetku tega razdelka, je skalar pravzaprav vektor dolžine 1. V tem primeru podano skalarno vrednost recikliramo tolikokrat, kot je dolžina daljšega operanda, torej vektorja.

Zato, da bolje razumeš recikliranje, se poigraj z naslednjimi primeri:

v = -4:4

v + TRUE # pojasni rezultat, ne pozabi na prisilno prilagajanje tipov
[1] -3 -2 -1  0  1  2  3  4  5
v > 0 # premisli kako R reciklira vrednosti v tem izračunu,
[1] FALSE FALSE FALSE FALSE FALSE  TRUE  TRUE  TRUE  TRUE
v < 1:-1 # kako v tem,
[1]  TRUE  TRUE  TRUE  TRUE FALSE FALSE FALSE FALSE FALSE
v < 1:-2 # in zakaj tukaj dobimo opozorilo
Warning: longer object length is not a multiple of shorter object length
[1]  TRUE  TRUE  TRUE FALSE  TRUE FALSE FALSE FALSE FALSE
v * (-1:1)
[1]  4  0 -2  1  0  1 -2  0  4
v / (-1:1) # pojasni ta rezultat
[1]    4 -Inf   -2    1  NaN    1   -2  Inf    4

Osnovne funkcije nad skalarji in vektorji

V R-ju pogosto uporabljamo dve obliki funkcij, ki kot argument sprejmejo vektor.

  1. Običajne matematične funkcije oblike \(f: \mathbb{R} \to \mathbb{R}\), kot so npr. sin, cos, log, log10 ali exp. Ko tem funkcijam podamo za argument vektor v, je rezultat vektor vrednosti funkcije na posameznih elementih vektorja v.

  2. Matematične funkcije oblike \(f: \mathbb{R}^* \to \mathbb{R}\), torej funkcije, ki poljubno število numeričnih argumentov združijo v skalarno vrednost. Ko tem funkcijam podamo kot argument vektor v, je rezultat skalarna vrednost. Primeri takih funkcij so sum, mean, sd in length, ki vrnejo vsoto, povprečno vrednost, standardni odklon in število elementov vektorja.

Pogosto nam koristijo tudi dve funkciji nad nizi znakov: * nchar(x), ki kot rezultat vrne število znakov v podanem nizu znakov x. * paste(x1, x2, ..., xn), ki kot rezultat vrne en niz znakov dobljen s stikom podanih nizov znakov x1, x2, …, xn z vmesnimi presledki. Različica te funkcije paste0 opravi stik podanih nizov brez vmesnih presledkov.

In še nekaj koristnih funkcij za vektorje: * sort(v) vrne vektor z elementi vektorja v urejenimi v naraščajočem vrstnem redu. Različica klica sort(v, decreasing=TRUE) uredi elemente vektorja v padajočem vrstnem redu. * unique(v) vrne vektor elementov v brez ponavljanja, rezultat je torej vektor v katerega se vsak element pojavi le enkrat. * which(v), kjer je v vektor logičnih vrednosti, vrne celoštevilski vektor indeksov elementov v, ki imajo vrednost TRUE. Če je v poimenovan vektor, vrne vektor z imeni teh elementov.

Primeri

Poglejmo si nekaj primerov tvorjenja seznamov in operacij nad njimi.

Tvorjenje vektorjev z : in primeri operacij, ki kombinirajo vektorje in skalarje:

1:5
[1] 1 2 3 4 5
5:-5
 [1]  5  4  3  2  1  0 -1 -2 -3 -4 -5
2 ^ (0:5)
[1]  1  2  4  8 16 32
(0:5) ^ 2
[1]  0  1  4  9 16 25
(0:5) ^ (5:0)
[1] 0 1 8 9 4 1
x = 10
(2:(x -1))
[1] 2 3 4 5 6 7 8 9

Tvorjenje vektorjev z seq in funkcije:

seq(0, 1, 0.1)
 [1] 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0
seq(0, 1, length.out=6)
[1] 0.0 0.2 0.4 0.6 0.8 1.0
seq(0, 1, length.out=6) * seq(0, 1, length.out=3)
[1] 0.0 0.1 0.4 0.0 0.4 1.0
sin(seq(0, pi, length.out=5))
[1] 0.000000e+00 7.071068e-01 1.000000e+00 7.071068e-01 1.224647e-16
mean(0:100)
[1] 50

Ponavljanje z rep:

rep(-1:1, 3)
[1] -1  0  1 -1  0  1 -1  0  1
rep(-1:1, each=2)
[1] -1 -1  0  0  1  1
unique(rep(-1:1, 3))
[1] -1  0  1

In še naključno vzorčenje s ponavljanjem in brez:

sort(sample(-5:5, 5, replace=F))
[1] -5 -1  1  3  4
sort(sample(-5:5, 5, replace=T))
[1] 0 4 5 5 5

Posebne vrste vektorjev

Faktor (angl. factor) je vektor z elementi, ki imajo vrednosti iz vnaprej znane, relativno majhne množice možnih vrednosti. To množico določa atribut faktorja levels.

Faktorje tvorimo iz vektorjev s funkcijo factor:

x = factor(c("a", "b", "b", "a"))
x
[1] a b b a
Levels: a b
typeof(x)
[1] "integer"
attributes(x)
$levels
[1] "a" "b"

$class
[1] "factor"

Kot vidimo so faktorji pravzaprav celoštevilski vektorji. Nabor možnih vrednosti lahko nastavimo tudi naknadno, kar nam pomaga pri analizi podatkov. Tako, npr. faktorju x iz zgornje kode lahko dodamo možno vrednost "c", kar spremeni obnašanje funkcije table, ki prešteva frekvence pojavitev različnih vrednosti v faktorju (ali vektorju):

table(x)
x
a b 
2 2 
levels(x) = c(levels(x), "c")
table(x)
x
a b c 
2 2 0 

Možne vrednosti faktorjev so v splošnem neurejen. Če so možne vrednosti elementov urejene, potem je faktor urejen. Urejene faktorje tvorimo s funkcijo ordered:

ocene = ordered(
  c("dobro", "odlično", "prav dobro", "zadostno", "zadostno"),
  levels = c("zadostno", "dobro", "prav dobro", "odlično")
)

ocene
[1] dobro      odlično    prav dobro zadostno   zadostno  
Levels: zadostno < dobro < prav dobro < odlično

Bodite pozorni na zadnjo vrstico izhoda, ki z uporabo znaka < pove kakšna je urejenost možnih vrednosti elementov.

Datumski vektor je nadgradnja vektorjev numeričnih vrednosti (plavajoče vejice). Tvorimo jih lahko na več načinov. S funkcijami as.Date, če nas čas/ ura ne zanima, ali as.POSIXct, če hočemo datumu pridružiti tudi uro:

now = as.Date(Sys.Date())
now
[1] "2024-10-06"
future = as.Date(Sys.Date() + 360)
future
[1] "2025-10-01"
duration = future - now
typeof(duration)
[1] "double"
duration
Time difference of 360 days
now = as.POSIXct(Sys.time(), )

Bodite pozorni na izpis zgornjega primera Časovna razlika 360 dni: razlika dveh datumov je poseben tip datumskega vektorja, ki ustreza časovnemu intervalu. Tvorimo ga lahko tudi sami s funkcijo as.difftime in določamo enote:

duration = as.difftime(1, units="weeks")
now + duration
[1] "2024-10-13 17:35:17 CEST"

Seznami (angl. lists)

Za razliko od vektorjev, seznami v R-ju lahko vsebujejo elemente, ki imajo vrednosti različnih tipov. Zato rečemo, da so seznami posplošitev vektorjev in jih imenujemo tudi splošni (generični) vektorji (angl. generic vectors). Pozor: Seznami v Python-u so pravzaprav podobni vektorjem (in ne seznamom) v R-ju.

Sezname lahko tvorimo s funkcijo list:

l1 = list(
  1:3,
  "a",
  c(TRUE, FALSE, TRUE),
  c(2.3, 5.9)
)

l1
[[1]]
[1] 1 2 3

[[2]]
[1] "a"

[[3]]
[1]  TRUE FALSE  TRUE

[[4]]
[1] 2.3 5.9
typeof(l1)
[1] "list"
length(l1)
[1] 4

Bodimo pozorni na dejstvo, ki ga kaže zadnji primer, da ima seznam štiri elemente. Vektorji (in ne njihovi posamezni elementi) so elementi seznama. Kot bomo videli pozneje so lahko tudi seznami elementi seznama.

Tako kot vektorji, so lahko tudi seznami poimenovani:

oseba = list(
  ime = "Aleksandra",
  spol = "ž",
  starost = "21"
)
oseba
$ime
[1] "Aleksandra"

$spol
[1] "ž"

$starost
[1] "21"

Sezname lahko gnezdimo, kar pomeni, da je lahko seznam element seznama. Tukaj je primer nenavadnega seznama z enim elementom:

l2 = list(list(list(1))) # res čuden seznam
l2
[[1]]
[[1]][[1]]
[[1]][[1]][[1]]
[1] 1

Različne sezname lahko kombiniramo/ sestavljamo s funkcijo c ali list:

l3 = list(list(1, 2), c(3, 4))
l3
[[1]]
[[1]][[1]]
[1] 1

[[1]][[2]]
[1] 2


[[2]]
[1] 3 4
l4 = c(list(1, 2), c(3, 4))
l4
[[1]]
[1] 1

[[2]]
[1] 2

[[3]]
[1] 3

[[4]]
[1] 4

V zgornjih primerih bodite pozorni na razliko med seznamoma l3 in l4. Pozorno ju primerjajte, poglejte kakšna je dolžina vsakega in pojasnite zakaj pride do te razlike. Pri katerem opazujemo gnezdenje seznamov?

Če si želimo vektor pretvoriti v seznam, uporabimo funkcijo as.list. Rezultat funkcije je seznam, ki ima enako število elementov kot vektor in vsak element vektorja postane element seznama. Poglejmo še en primer, ki ponazori razliko med funkcijama as.list in list:

l5 = as.list(-2:2)
l5
[[1]]
[1] -2

[[2]]
[1] -1

[[3]]
[1] 0

[[4]]
[1] 1

[[5]]
[1] 2
l6 = list(-2:2)
l6
[[1]]
[1] -2 -1  0  1  2

Za pretvorbo seznama v vektor uporabljamo funkcijo unlist. Pri tem moramo biti pozorni na to, da morajo biti vsi elementi vektorja istega tipa. Če so elementi seznama različnih tipov, bo R med izračunom rezultata funkcije unlist opravil prisilno pretvorbo tipov. Poglejmo primere:

unlist(l1)
[1] "1"     "2"     "3"     "a"     "TRUE"  "FALSE" "TRUE"  "2.3"   "5.9"  
unlist(l2)
[1] 1
unlist(l3)
[1] 1 2 3 4
unlist(l4)
[1] 1 2 3 4
unlist(l5)
[1] -2 -1  0  1  2
unlist(l6)
[1] -2 -1  0  1  2

Pojasni zakaj sta rezultata l3 in l4 (ter l5 in l6) enaka.

Indeksiranje seznamov

Ko indeksiramo seznam s tako, kot indeksiramo vektorje, torej indeks postavimo v enojne oklepaje, na primer s[1] ali s[2:3], je rezultat indeksiranja vedno seznam, četudi seznam z enim elementom. Če želimo z indeksiranjem seznama dobiti njegove posamezne elemente, moramo uporabljati dvojne oglate oklepaje. Prvi element seznama torej dobimo z izrazom s[[1]].

Poglejmo nekaj primerov:

l1[2:3] # seznam z dvema elementoma
[[1]]
[1] "a"

[[2]]
[1]  TRUE FALSE  TRUE
l1[-4] # seznam s tremi elementi
[[1]]
[1] 1 2 3

[[2]]
[1] "a"

[[3]]
[1]  TRUE FALSE  TRUE
l1[1] # seznam z enim elementom
[[1]]
[1] 1 2 3
typeof(l1[1])
[1] "list"
l1[[1]] # prvi element seznama
[1] 1 2 3
typeof(l1[[1]])
[1] "integer"

Opomba: dvojne oglate oklepaje lahko uporabljamo tudi za dostop do posameznih elementov vektorja. Veliko programerjev v R-ju se poslužuje dvojnih oklepajev pri naslavljanju posameznih elementov vektorja (in seveda seznama) zato, da je koda bolj pregledna. Z dvojnimi oklepaji namreč poudarimo, da želimo z indeksiranjem nasloviti točno en element vektorja. Ko pa uporabljamo indeksiranje za dostop do večjega (ali poljubnega) števila elementov, potem moramo uporabljati enojne oklepaje.

V poimenovanih seznamih lahko posamezne elemente naslovimo na več načinov:

oseba[[1]]
[1] "Aleksandra"
oseba[["ime"]]
[1] "Aleksandra"
oseba$ime
[1] "Aleksandra"

Prvi način z uporabo znaka $ je najbolj običajen in pogosto uporabljen.


Naloge

Premisleki brez poganjanja R

  1. Naštej po par primerov izrazov z numeričnimi vrednostmi Inf, -Inf in NaN.
  2. Kakšen je tip elementov vektorja c(1:5, FALSE)?
  3. Kaj je rezultat klicev length(c(1:5, 5:1)) in lenght("abc")?
  4. Premisli kako dolga bosta vektorja 1:5 + 1 in 1:(5 + 1).
  5. Kaj izračuna izraz sum(v == 3) za podan vektor v? Kako je vrednost tega izraza povezana z vrednostjo izraza which(v == 3)?
  6. Seznam x = list(a=c(1,1), b=2, c=list(c(3,3,3),c(4))). Kakšne so vrednosti izrazov x[1], x[[2]] in x[[3]][1]? Naštej čim več načinov za pridobivanje vrednosti drugega elementa seznama x.

Programerske naloge

  1. Denimo, da smo Alešu, Barbari, Cirilu in Darji izmerili višine v centimetrih (180, 165, 160, 193) in teže v kilogramih (87, 58, 65, 100). Naredi dva vektorja v in t z izmerjenimi podatki. Indeks telesne mase (ITM) izračunamo po formuli \[ \text{ITM} = \frac{\text{teža v kilogramih}}{(\text{višina v metrih})^2}. \] Izračunaj poimenovan vektor vrednosti ITM za štiri osebe, kjer so imena elementov dejanska imena oseb (Aleš, Barbara, Ciril in Darja). Nato izračunaj vektor naravnih logaritmov vrednosti ITM. Na koncu izpiši vektor imen oseb, ki imajo ITM večji od 25, ter izračunaj njihov povprečni ITM.

  2. Sestavi vektorje z naslednjimi elementi:

    1. \(3^1/1, 3^2/2, \ldots, 3^{50}/50\);
    2. “A1”, “A2”, \(\ldots\), “A50”;
    3. \(e^x \sin x\) izračunanimi v točkah \(x = 3, 3.1, 3.2, \ldots, 6\).

  3. Podano je naravno število x > 2. Zapiši izraz v R-ju, katerega rezultat je TRUE, če je x praštevilo in FALSE sicer. Namig: izraz gradi postopoma. Najprej izračunaj vektor ostankov deljenja x s vsemi števili od 2 do x - 1. Nato preveri, če je 0 element tega seznama.

  4. Izračunaj dva vektorja x in y dolžine 250 tako, da s ponavljanjem naključno izbiraš cela števila iz intervala \([0, 999]\). Označimo z \(x_1, x_2, \ldots, x_{250}\) in \(y_1, y_2, \ldots, y_{250}\) elemente seznamov x in y. Izračunaj:

    1. Vektor z elementi \(y_2 - x_1, y_3 - x_2, \ldots, y_{250} - x_{249}\).
    2. Vsoto \(\sum_{i=1}^{249} e^{- x_{i+1}} / (x_i + 10)\).

  5. Sestavi vektor tipa faktor z naborom možnih vrednosti A, B in C. Vektor naj ima naključno izbranih 15 elementov, po natanko pet (5) iz vsake kategorije.

  6. Na FMF pogosto ocenjujemo dosežke študentov na izpit z odstotnimi točkami: študentka lahko doseže od 0 do 100 odstotnih točk (vključno). Dosežek v odstotnih točkah (%) nato prevedemo v oceno s predpisom iz spodnje tabele.

    dosežek v % ocena
    večji od 90 odlično (10)
    večji od 80 in največ 90 prav dobro (9)
    večji od 70 in največ 80 prav dobro (8)
    večji od 60 in največ 70 dobro (7)
    večji od 50 in največ 60 zadostno (6)
    največ 50 nezadostno (5)
    1. Sestavi vektor ot z naključnimi dosežki dvajsetih študentov;
    2. Pretvori dosežke iz vektorja ot v vektor o tipa urejen faktor, katerih elementi so ocene študentov izračunane po zgornjem predpisu.

  7. V programu za urejanje preglednic Excel (in podobno v programih Google Sheets ali Numbers) so stolpci poimenovani z velikimi črkami angleške abecede (od A do Z), nato sledijo stolpci poimenovani s dvema črkama (od AA, prek AB, AC, in tako naprej, do ZY in ZZ), nato še tisti s tremi črkami (od AAA do ZZZ).

    Zadnji stolpec v preglednici je poimenovan XFD. Napiši program v R-ju, ki izračuna koliko stolpcev ima preglednica v programu Excel.

    Namig: uporabiš lahko LETTERS, t.j., vektor velikih črk v angleški abecedi, ter klice funkcij sort in outer (pomagaj si z ?outer) v kombinaciji s funkcijo paste0 za izračun vektorja z imeni stolpcev. Nato s klicem funkcije which ugotovite indeks stolpca XFD v tem vektorju.

  8. Vse vektorje in skalarje, izračunane v prvi nalogi zgoraj (nalogi o indeksu telesne mase), sestavi v seznam resitev s polji imena, visine, ITM, logITM, ITM.v.25, imena.ITM.v.25 in ITM.v.25.povprecje.


Prvo različico tega zvezka sem oktobra 2021 pripravil Ljupčo Todorovski v jeziku R Markdown. Ideje za primere sem si v veliki meri izposojal iz učbenika (Wickham 2019), vse morebitne napake v primerih so izključno moje. Zvezek sem nazadnje spreminjal oktobra 2024.

V zvezkih z zapiski s predavanj pri tem predmetu je . oznaka za decimalno vejico in , ločilo med tisočicami, kar v slovenščini nenavadno (ali pa kar narobe). Tako je 3/2 = 1.5 (in ne 1,5) in je 1e6 = 1000000 = 1,000,000 (in ne 1.000.000).

LS0tCnRpdGxlOiAnVXZvZCB2IFI6IE9zbm92bmkgcG9kYXRrb3ZuaSB0aXBpJwpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdAogIHBkZl9kb2N1bWVudDogZGVmYXVsdAotLS0KCiMgU2thbGFyamkgKGFuZ2wuIF9zY2FsYXJzXykKClIgaW1hIMWhdGlyaSBvc25vdm5lIHRpcGUgc2thbGFyamV2IG96aXJvbWEgbmppaG92aWggdnJlZG5vc3RpOgoKKiAqKkxvZ2nEjW5pKiogdnJlZG5vc3RpIChhbmdsLiBfbG9naWNhbF8pIHN0YSBgVFJVRWAgaW4gYEZBTFNFYCBhbGkgbmEga3JhdGtvIGBUYCBpbiBgRmAuCgoqICoqQ2Vsb8WhdGV2aWxza2UqKiAoYW5nbC4gX2ludGVnZXJfKSB2cmVkbm9zdGkgemFwacWhZW1vIGtvdCDFoXRldmlsa28sIGtpIGppIHNsZWRpIHNpbWJvbCBgTGAsIG5wci4gYDEyMzQ1NkxgLiDEjGUgamUgY2Vsb8WhdGV2aWxza2EgdnJlZG5vc3QgcHJldmVsaWthIGFsaSBwcmVtYWpobmEgKHBvIGFic29sdXRuaSB2cmVkbm9zdGkgdmXEjWphIG9kIGAuTWFjaGluZSRpbnRlZ2VyLm1heGAsIG5hIG1vamVtIHJhxI11bmFsbmlrdSBqZSB0byAyLDE0Nyw0ODMsNjQ3KSBhbGkgc21vIGppIHBvbW90b21hIGRvZGFsaSBkZWNpbWFsa2UgKGluIHRvcmVqIG5pIHZlxI0gY2Vsb8WhdGV2aWxza2EpLCBqbyBSIGF2dG9tYXRza28gcHJldHZvcmkgdiBudW1lcmnEjW5vIHZyZWRub3N0IChnbGVqIG5hc2xlZG5qbyBhbGluZWpvKS4KCiogKipOdW1lcmnEjW5lKiogdnJlZG5vc3RpIChhbmdsLiBfZG91YmxlXykgdiBbKipwbGF2YWpvxI1pIHZlamljaSoqXShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9GbG9hdGluZy1wb2ludF9hcml0aG1ldGljKSAoYW5nbC4gX2Zsb2F0aW5nIHBvaW50XykgbGFoa28gemFwacWhZW1vIHYgbmF2YWRuaSBgMTIzLjQ1NmAgYWxpIHpuYW5zdHZlbmkgbm90YWNpamkgYDEuMjM0NTZlMmAuIFRyaSBwb3NlYm5lIG51bWVyacSNbmUgdnJlZG5vc3RpIHNvIGAtSW5mYCwgbnByLiBgLTEvMGAsIGBJbmZgLCBucHIuIGAxLzBgLCBpbiBgTmFOYCwgbnByLiBgMC8wYC4KCiogKipOaXplIHpuYWtvdioqIChhbmdsLiBfc3RyaW5nXykgemFwacWhZW1vIG1lZCBlbm9qbmUgYCdgIGFsaSBkdm9qbmUgYCJgIG5hcmVrb3ZhamUuIMSMZSBuaXogem5ha292IHZzZWJ1amUgbmFyZWtvdmFqZSwgc2xlZG5qZSB6YXBpxaFlbW8gcyBwcmVkcG9ubyBgXGAsIG5wci4gYCJUbyBqZSBwcmltZXIgbml6YSB6bmFrb3YgeiBcIm5hcmVrb3ZhamlcIiJgLiBEb2zFvmluYSBuaXphIHpuYWtvdiBqZSDFoXRldmlsbyB6bmFrb3YgdiBuaXp1LiBQcmF6ZW4gbml6IHpuYWtvdiBgIiJgIGltYSBkb2zFvmlubyAwLgoKU3ByZW1lbmxqaXZraSB2IFItanUgZG9kZWxpbW8gKG5vdm8pIHNrYWxhcm5vIHZyZWRub3N0IHogb3BlcmF0b3JqZW0gYDwtYC4gR3JlIHphIHRyYWRpY2lvbmFsbmkgemFwaXMsIGl6IMSNYXNvdiBuYXN0YW5rYSBwcm9ncmFtc2tlZ2Egb2tvbGphIFIuIERhbmVzIFIgb21vZ2/EjWEgdXBvcmFibyBvYmnEjWFqbmVnYSBvcGVyYXRvcmphIGA9YCwga2kgZ2EgcG96bmFtbyBpeiBkcnVnaWggcHJvZ3JhbXNraWggamV6aWtvdi4KClBvIGRvZGVsaXR2aSwgdGlwIHNwcmVtZW5saml2a2Ugc2Ugc3ByZW1lbmkgdiB0aXAgc2thbGFybmUgdnJlZG5vc3RpLCBraSBqaSBqbyBkb2RlbGltby4gUHJpIGl6cmHEjXVuaWgsIGtpIHZrbGp1xI11amVqbyB2cmVkbm9zdGkgaW4gc3ByZW1lbmxqaXZrZSByYXpsacSNbmloIHRpcG92LCBzZSB0aXBpIGRpbmFtacSNbm8gKHNhbW9kZWpubywgdiB0ZXJtaW5vbG9naWppIFIgdHVkaSBwcmlzaWxubywgYW5nbC4gX2NvZXJjaW9uXykgaW4gcG8gcG90cmViaSBwcmlsYWdvZGlqbyB0YWtvLCBkYSBibyBpenJhxI11biBtb8W+bm8gaXpwZWxqYXRpLiBfUHJpc2lsbm8gcHJpbGFnYWphbmplIHRpcG92XyB2ZWRubyBwb3Rla2EgdiBzbWVyLCBraSBqbyBuYWthenVqZSB2cnN0bmkgcmVkIG5hxaF0ZXZhbmphIMWhdGlyaWggdGlwb3YgemdvcmFqOiBsb2dpxI1uYSB2cmVkbm9zdCBzZSBwcmV0dm9yaSB2IGNlbG/FoXRldmlsc2tvLCB0YSBuYXRvIHYgbnVtZXJpxI1ubyAocGxhdmFqb8SNbyB2ZWppY28pLCB0ZXIgbmEga29uY3UgxaFlIChzZXZlZGEgcG8gcG90cmViaSkgdiBuaXogem5ha292LgoKKipQb3pvcioqLCBwcmlsYWdhamFuamUgdGlwb3YgdsSNYXNpaCB0dWRpIChuZXByaWpldG5vKSBwcmVzZW5ldGksIG5wci4gYDEgPT0gIjEiYCAob3BlcmF0b3IgYD09YCBwcmV2ZXJpLCDEjWUgc3RhIGl6cmF6YSBuYSBsZXZpIGluIGRlc25pIHN0cmFuaSBlbmFrYSkuIEl6cmF6LCBraSBiaSBsYWhrbyBiaWwgcmV6dWx0YXQgcHJvZ3JhbXNrZSBuYXBha2UsIFIgYnJleiB0ZcW+YXYgKHByaWphdmxqYW5qYSBuYXBhayBhbGkgb3Bvem9yaWwpIG92cmVkbm90aSB2IGBUUlVFYCBpbiBuZSB2IG1vcmViaXRpIGJvbGogcHJpxI1ha292YW5vIHZyZWRub3N0IGBGQUxTRWAgKHByZXByacSNYWogc2UsIGRhIGplIHRlbXUgdGFrbywgbmF0byBwcmVtaXNsaSBpbiBwb2phc25pIHpha2FqKS4KClRpcCBwb2xqdWJuZSBzcHJlbWVubGppdmtlLCB2cmVkbm9zdGkgYWxpIGl6cmHEjXVuYSwgbGFoa28gcHJldmVyaW1vIHMgZnVua2Npam8gYHR5cGVvZmAuIFByaW1lciwga2kgcG9uYXpvcmkgc2Ftb2Rlam5vIHByZXR2b3JibyBtZWQgcmF6bGnEjW5pbWkgb3Nub3ZuaW1pIHRpcGk6CmBgYHtyfQp4ID0gMUwgKyBUUlVFCngKdHlwZW9mKHgpCgp4ID0gMUwgKyBUUlVFICsgMQp4CnR5cGVvZih4KQoKeCA9ICF4CngKdHlwZW9mKHgpCmBgYAoKWmFtZW5qYXZvIHRpcGEgdnJlZG5vc3RpIGxhaGtvIHNhbWkgc3Byb8W+aW1vIHMgZnVua2NpamFtaSBgYXMubG9naWNhbGAsIGBhcy5pbnRlZ2VyYCwgYGFzLmRvdWJsZWAgaW4gYGFzLmNoYXJhY3RlcmAuIFMgdGVtaSBmdW5rY2lqYW1pIGxhaGtvIHByZXZlcmltbyBrYWtvIFIgcHJldHZhcmphIG96LiBwcmlsYWdhamEgdGlwZSBza2FsYXJqZXYuIE1lZCB6YW5pbWl2aW1pIGtsaWNpIHRlIGZ1bmtjaWplIHNvZGlqbyBucHIuICBgYXMubG9naWNhbCgwKWAsIGBhcy5jaGFyYWN0ZXIoRkFMU0UpYCwgYGFzLmludGVnZXIoMi44KWAuIF9fUHJlZGVuIHBvc2t1c2l0ZSB1a2F6ZSwgcHJldmVyaXRlLCBhbGkgem5hdGUgcHJhdmlsbm8gbmFwb3ZlZGF0aSBuamlob3ZvIGRlbG92YW5qZS5fXwoKxaBlIGVuYSwgemVsbyBwb21lbWJuYSBza2FsYXJuYSB2cmVkbm9zdCBqZSAqKm5lem5hbmEgdnJlZG5vc3QqKiBgTkFgLiBCb2RpdGUgcG96b3JuaSBuYSBuYXNsZWRuamkgZHZlIGRlanN0dmkgKHpha2FqIGplIHRlbXUgdGFrbz8pOgpgYGB7cn0KTkEgPT0gMApOQSA9PSBOQQpgYGAKCioqQm9kaXRlIHBvem9ybmkqKjogbmV6bmFuYSB2cmVkbm9zdCBgTkFgIGplICJrdcW+bmEiLiBWIHZlxI1pbmkgcHJpbWVyb3YgamUgdnJlZG5vc3QgaXpyYXphIChpenJhxI11bmEpLCBraSB2a2xqdcSNdWplIGBOQWAsIGVuYWthIGBOQWAuIFBvem5hbW8gbGUgbmVrYWogcmVka2loIGl6amVtLCBucHIuIGBOQSAmIEZBTFNFYCwgYE5BIHwgVFJVRWAgaW4gYE5BIF4gMGAuIFpub3ZhIHByZW1pc2xpIHpha2FqIGplIHRlbXUgdGFrby4KClphIHByZXZlcmphbmplIGFsaSBqZSB2cmVkbm9zdCBzcHJlbWVubGppdmtlIGFsaSBpenJhemEgbmV6bmFuYSB1cG9yYWJpbW8gZnVua2Npam8gYGlzLm5hYC4gTmEgdm9sam8gc28gdHVkaSDFoXRpcmkgcG9kb2JuZSBmdW5rY2lqZSBzIGthdGVyaW1pIGxhaGtvIHByZXZlcmltbywgxI1lIGplIGl6cmF6IG5la2VnYSBkb2xvxI1lbmVnYSB0aXBhOiBgaXMubG9naWNhbGAsIGBpcy5pbnRlZ2VyYCwgYGlzLmRvdWJsZWAgaW4gYGlzLmNoYXJhY3RlcmAuIFJlenVsdGF0aSB2c2VoIHRlaCBmdW5rY2lqIHNvIGxvZ2nEjW5lIHZyZWRub3N0aS4KCk5haiBuYSB0ZW0gbWVzdHUgb21lbmltbyDFoWUgb3puYWtvIGBOVUxMYCwga2kgdiBSLWp1IG5ha2F6dWplIG9kc290bm9zdCB2cmVkbm9zdGkuIFphIHJhemxpa28gb2QgYE5BYCwga2kgbmFrYXp1amUgcHJpc290bm9zdCB2cmVkbm9zdGksIGtpIHBhIG5pIHpuYW5hLgpgYGB7cn0KTlVMTCA9PSBOVUxMCk5VTEwgPT0gMApOVUxMID09IE5BCmBgYAoKViB2c2VoIHRyZWggcHJpbWVyaWggamUgcmV6dWx0YXQgYGxvZ2ljYWwoMClgLCBrYXIgamUgb3puYWthIHphIHByYXplbiB2ZWt0b3IgbG9nacSNbmloIHZyZWRub3N0aS4gUG9kb2JubyBrb3QgYE5VTExgIHR1ZGkgcHJhemVuIHZla3RvciBuYWthenVqZSBvZHNvdG5vc3QgdnJlZG5vc3RpLiBaYWRuamEgZHZhIHN0YXZrYSBvbWVuamF0YSBwb2RhdGtvdm5pIHRpcCB2ZWt0b3IsIGtpIGdhIHR1ZGkgxaF0ZWplbW8gbWVkIG9zbm92bmltaSBwb2RhdGtvdm5pbWkgdGlwaSB2IFItanUuIFNwb3puYWxpIGdhIGJvbW8gdiBuYWRhbGpldmFuanUuCgotLS0KCiMgVmVrdG9yamkgKGFuZ2wuIF92ZWN0b3JzXywgX2F0b21pYyB2ZWN0b3JzXykKClZla3RvciBwcmVkc3RhdmxqYSBfdXJlamVuXyBzZXpuYW0gc2thbGFyamV2ICoqaXN0ZWdhIHRpcGEqKi4gU2thbGFyamUgaW1lbnVqZW1vICplbGVtZW50aSogYWxpICprb21wb25lbnRlKiBzZXpuYW1hLiBfVXJlamVub3N0XyBzZXpuYW1hIHBvbWVuaSwgZGEgamUgdnJzdG5pIHJlZCBlbGVtZW50b3YgcG9tZW1iZW4gaW4gbGFoa28gZWxlbWVudGUgbmFzbGF2bGphbW8gbmEgb3Nub3ZpIG5qaWhvdmUgcG96aWNpamUgdiBzZXpuYW11ICh1cmVqZW5vc3QgbmFtcmXEjSBuZSBwb21lbmksIGRhIHNvIGVsZW1lbnRpIHVyZWplbmkgcG8gbmFyYcWhxI1ham/EjWkgYWxpIHBhZGFqb8SNaSB2cmVkbm9zdGkpLiBaIGRydWdpbWkgYmVzZWRhbWksIHpuYW1vIHBvdmVkYXRpIGthdGVyaSBlbGVtZW50IHNlem5hbWEgamUgcHJ2aSwgZHJ1Z2kgYWxpIHphZG5qaS4KClZla3RvcmplIG5hamJvbGogcHJlcHJvc3RvIHVzdHZhcmphbW8gcyBrbGljZW0gZnVua2NpamUgYGNgLCBraSBzcHJlam1lIHBvbGp1Ym5vIMWhdGV2aWxvIGFyZ3VtZW50b3YgaW4gaXogbmppaCBzZXN0YXZpIHNlem5hbS4gRG9sxb5pbm8gdmVrdG9yamEsIGtpIGplIGVuYWthIMWhdGV2aWx1IG5qZWdvdmloIGVsZW1lbnRvdiwgbmFtIHZybmUgZnVua2NpamEgYGxlbmd0aGAuIFZla3RvciBkb2zFvmluZSAwIGltZW51amVtbyBwcmF6ZW4gdmVrdG9yLiDFvWUgb21lbmplbmEgZnVua2NpamEgYHR5cGVvZmAgcGEgbmFtIHZybmUgdGlwIGVsZW1lbnRvdiB2ZWt0b3JqYSAobmUgcG96YWJpbW8sIGRhIHNvIHZzaSBlbGVtZW50aSB2ZWt0b3JqYSBlbmFrZWdhIHRpcGEpLgpgYGB7cn0KdmwgPSBjKFRSVUUsIEZBTFNFLCBUUlVFLCBUUlVFKQp0eXBlb2YodmwpCgp2aSA9IGMoLTIwTCwgLTEwTCwgMEwsIDEwTCwgMjBMKQpsZW5ndGgodmkpCgp2ZCA9IGMoLTEsIDIuNSwgMykKYXMuaW50ZWdlcih2ZCkKCnZzID0gYygibmVrYWogc3RyaW5nb3YgKHVwcywgbml6b3Ygem5ha292KSIsICJ6YSBwcmltZXIiLCAicGEgxaFlIMWhdGV2aWxrYSIsICI0MiIpCnggPSBhcy5kb3VibGUodnMpCngKaXMubmEoeCkKYGBgCgpCb2RpbW8gcG9zZWJlaiBwb3pvcm5pIG5hIHphZG5qZSB0cmkgcmV6dWx0YXRlLiBQcmkgcHJldHZhcmphbmp1IG5pem92IHpuYWtvdiB2IG51bWVyacSNbmUgdnJlZG5vc3RpLCBUaXN0ZSBuaXplIHpuYWtvdiwga2kgc2UgamloIG5lIGRhIChvemlyb21hIGppaCBSIG5lIHpuYSkgcHJldHZvcml0aSB2IG51bWVyacSNbmUgdnJlZG5vc3RpLCBSIHByZXR2b3JpIHYgbmV6bmFuZSB2cmVkbm9zdGkgYE5BYC4gUHJpIHRlbSBpenBpxaFlIG9wb3pvcmlsbyAoYW5nbC4gX3dhcm5pbmdfKSBgTmV6bmFuZSB2cmVkbm9zdGkgTkEgdnBlbGphbmUgcyAocHJpc2lsbmltKSBwcmlsYWdhamFuamVtIHRpcG92YCAoYW5nbC4gX19OQXMgaW50cm9kdWNlZCBieSBjb2VyY2lvbl9fKS4KClBvZ2xlam1vIMWhZSBrYWogc2UgemdvZGksIMSNZSBwb3NrdXNpbW8gc2VzdGF2aXRpIHZla3RvciBpeiB2cmVkbm9zdGkgcmF6bGnEjW5paCB0aXBvdjoKYGBge3J9CnggPSBjKFRSVUUsIDFMKQp4CnR5cGVvZih4KQoKeCA9IGMoRkFMU0UsIDEsIDJMKQp4CnR5cGVvZih4KQoKeCA9IGMoVFJVRSwgMSwgMi41KQp4CnR5cGVvZih4KQoKeCA9IGMoRkFMU0UsIDEsIDJMLCAiMyIpCngKdHlwZW9mKHgpCmBgYAoKUHJlbWlzbGl0ZSBpbiBwb2phc25pdGUgemFrYWogc28gdGlwaSB0ZWggdmVrdG9yamV2IHRha2kga290IHNvPwoKxIxlcHJhdiB2ZWt0b3JqaSBwcmVkc3RhdmxqYWpvIGxlIGVub2RpbWVuemlvbmFsbmUgc2V6bmFtZSB2cmVkbm9zdGkgaXN0ZWdhIHRpcGEsIHogbmppbWkgbGFoa28gcHJlZHN0YXZpbW8gdHVkaSBkdm9kaW1lbnppb25hbG5lIG1hdHJpa2UgYWxpIHBvbGphICh0ZW56b3JqZSkgcG9sanVibmVnYSDFoXRldmlsYSBkaW1lbnppai4gWmF0byBkYSByYXp1bWVtbyBrYWtvLCBzcG96bmFqbW8gbmFqcHJlaiBrb25jZXB0IHZla3RvcnNraWggYXRyaWJ1dG92LgoKIyMgQXRyaWJ1dGkgdmVrdG9yamV2IGluIG1hdHJpa2UKCkF0cmlidXRpIHNvIHVyZWplbmkgcGFyaSBvYmxpa2UgKGltZSwgdnJlZG5vc3QpLCBraSB2ZWt0b3JqZSBvem5hxI1pam8geiBfbWV0YV8gcG9kYXRraS4gWmEgbmFzdGF2bGphbmplIHZyZWRub3N0aSBhdHJpYnV0b3YgdmVrdG9yamEgdXBvcmFibGphbW8gZnVua2Npam8gYGF0dHJgLCBraSBqbyBsYWhrbyB0dWRpIHVwb3JhYmltbyB6YSBwcmV2ZXJqYW5qZSB2cmVkbm9zdGkgYXRyaWJ1dGEuIEZ1bmtjaWphIGBhdHRyaWJ1dGVzYCB2cm5lIHZzZSBhdHJpYnV0ZSBwb2RhbmVnYSB2ZWt0b3JqYS4KYGBge3J9CnYgPSBjKC0yMEwsIC0xMEwsIDBMLCAxMEwsIDIwTCkKCmF0dHIodiwgInBydmlfYXRyaWJ1dCIpID0gMQphdHRyKHYsICJkcnVnaV9hdHJpYnV0IikgPSAyLjMKCnByaW50KCJQcnZpIGF0cmlidXQgdmVrdG9yamEgdiIpCmF0dHIodiwgInBydmlfYXRyaWJ1dCIpCgpwcmludCgiVnNpIGF0cmlidXRpIHZla3RvcmphIHYiKQphdHRyaWJ1dGVzKHYpCmBgYAoKUmV6dWx0YXQgZnVua2NpamUgYGF0dHJpYnV0ZXNgIHByYXZ6YXByYXYgX3Nlem5hbV8gYXRyaWJ1dG92IGluIG5qaWhvdmloIHZyZWRub3N0aS4gUi1qZXZza28gcG9kYXRrb3ZubyBzdHJ1a3R1cm8gc2V6bmFtIGJvZG8gc3Bvem5hbGkgcG96bmVqZS4gKipQb3pvcioqOiAqKlZla3RvcmppIHYgUi1qdSBzbyBwb2RhdGtvdm5hIHN0cnVrdHVyYSwga2kgamUgcG9kb2JuYSBzdHJ1a3R1cmkgc2V6bmFtIHYgUHl0aG9uLXUqKi4KCkZ1bmtjaWphIGBzdHJ1Y3R1cmVgIG9tb2dvxI1hIGRvZGFqYW5qZSBhdHJpYnV0b3Ygxb5lIG9iIHR2b3JqZW5qdSB2ZWt0b3JqYToKYGBge3J9CnYgPSBzdHJ1Y3R1cmUoCiAgYygtMjBMLCAtMTBMLCAwTCwgMTBMLCAyMEwpLAogIHBydmlfYXRyaWJ1dCA9IDEsCiAgZHJ1Z2lfYXRyaWJ1dCA9IDIuMwopCnYKYXR0cmlidXRlcyh2KQpgYGAKCkF0cmlidXRpIHNvIHphxI1hc25pLiBNbm9nZSBmdW5rY2lqZSBpbiBvcGVyYWNpamUgbmFkIHNlem5hbWkgaXpicmnFoWVqbyAocG96YWJpam8pIGF0cmlidXRlLCBwcmVpemt1c2l0ZSBucHIuIGB2WzFdYCBhbGkgYHN1bSh2KWA6CmBgYHtyfQphdHRyaWJ1dGVzKHZpWzFdKQphdHRyaWJ1dGVzKHN1bSh2aSkpCmBgYAoKVnJlZG5vc3QgaXpyYXphIGB2WzFdYCBqZSBlbmFrYSB2cmVkbm9zdGkgcHJ2ZWdhIGVsZW1lbnRhIHZla3RvcmphIGB2YC4gRnVua2NpamEgYHN1bWAgdnJuZSB2c290byB2cmVkbm9zdGkgZWxlbWVudG92IHBvZGFuZWdhIHZla3RvcmphLgoKRHZhIGF0cmlidXRhIGltYWpvIHYgUi1qdSBwb3NlYmVuIHBvbWVuOgoKKiBgbmFtZXNgIGplIHZla3RvciBuaXpvdiB6bmFrb3YsIGtpIGplIGVuYWtlIGRvbMW+aW5lIGtvdCBvcmlnaW5hbG5pIHZla3Rvci4gVnNhayBlbGVtZW50IHZla3RvcmphIGBuYW1lc2AgZG9sb8SNYSAqKmltZSBlbGVtZW50YSoqIG9yaWdpbmFsbmVnYSB2ZWt0b3JqYS4KKiBgZGltYCBqZSB2ZWt0b3IgY2Vsb8WhdGV2aWxza2loIHZyZWRub3N0aSwga2kgcHJldHZvcmkgdmVrdG9yIHYgbWF0cmlrbyBhbGkgcG9samUuCgoqKlBvaW1lbm92YW4gdmVrdG9yKiogamUgdmVrdG9yIHYga2F0ZXJlbSBpbWEgdnNhayBlbGVtZW50IHN2b2plIGltZS4gSW1lbmEgZWxlbWVudG92IHZla3RvcmphIGxhaGtvIG5hc3RhdmltbyBuYSB2c2FqIGR2YSBuYcSNaW5hOiBwcmkgdHZvcmplbmp1IHZla3RvcmphIGFsaSBwYSBuYWtuYWRubyB6YSBuYXZhZG5pIHZla3RvciB6IHVwb3JhYm8gZnVua2NpamUgYG5hbWVzYDoKYGBge3J9CngxID0gYyhwcnZpID0gMSwgZHJ1Z2kgPSAyLCB0cmV0amkgPSAzKQp4MQoKeDIgPSBjKDEsIDIsIDMpCm5hbWVzKHgyKSA9IGMoInBydmkiLCAiZHJ1Z2kiLCAidHJldGppIikKeDIKYGBgCgpGdW5rY2lqbyBgbmFtZXNgIHZybmUgaW1lbmEgZWxlbWVudG92IHYgcG9pbWVub3ZhbmVtIHNlem5hbXUuIFByZXZlcml0ZSBrYWogdnJuZSBmdW5rY2lqYSBgbmFtZXNgIHphIG5laW1lbm92YW4gc2V6bmFtLCBucHIuIGBuYW1lcyhjKDEsMiwzKSlgLiBQb2ltZW5vdmFuIHNlem5hbSBsYWhrbyBzcHJlbWVuaW1vIHYgbmVpbWVub3ZhbmVnYSAodG9yZWogdiB2ZWt0b3IgeiBlbGVtZW50aSBicmV6IGltZW4pIHMga2xpY2VtIGZ1bmtjaWplIGB1bm5hbWVgOgpgYGB7cn0KeDMgPSB1bm5hbWUoeDIpCngzCmBgYAoKRGltZW56aWplIHZla3RvcmphLCB0LmouLCB2cmVkbm9zdCBhdHJpYnV0YSBgZGltYCBkb2xvxI1pbW8gbGFoa28gcHJpIHR2b3JqZW5qdSB2ZWt0b3JqYSBhbGkgbmFrbmFkbm8geiB1cG9yYWJvIGZ1bmtjaWplIGBkaW1gOgpgYGB7cn0KeCA9IGMoMSwgMiwgMywgNCwgNSwgNikKCkExID0gbWF0cml4KHgsIG5yb3cgPSAyLCBuY29sID0gMykKQTEKZGltKEExKQoKQTIgPSB4CmRpbShBMikgPSBjKDMsIDIpCkEyCmBgYAoKCiMjIFR2b3JqZW5qZSB2ZWt0b3JqZXYKCk9nbGVqbW8gc2kgbmVrYWogbmHEjWlub3YgemEgdHZvcmplbmplIHZla3RvcmpldjoKCiogYGk6amAgamUgY2Vsb8WhdGV2aWxza2kgdmVrdG9yIHogZWxlbWVudGkgYGksIGkrMSwgLi4uLCBqYCwgxI1lIGplIGBqID49IGlgIG96aXJvbWEgZWxlbWVudGkgYGksIGktMSwgXGxkb3RzLCBqYCwgxI1lIGplIGBqIDwgaWAuCgoqIGBzZXEoZnJvbSA9IGksIHRvID0gaiwgYnkgPSBzKWAgamUgdmVrdG9yIG51bWVyacSNbmloIHZyZWRub3N0aSwga2plciBqZSBwcnZpIGVsZW1lbnQgYGlgLCAgcmF6bGlrYSBtZWQgZHZlbWEgemFwb3JlZG5pbWEgZWxlbWVudG9tYSBqZSBgc2AsIHphZG5qaSBlbGVtZW50IHBhIG5lIHNtZSBwcmVzZcSNaSB2cmVkbm9zdGkgYGpgLiBQcmkgcmF6bGnEjWljaSBgc2VxKGZyb20gPSBpLCB0byA9IGosIGxlbmd0aC5vdXQgPSBuKWAgbmFtZXN0byByYXpsaWtlIG1lZCB6YXBvcmVkbmltYSBlbGVtZW50b21hIGRvbG/EjWltbyDFoXRldmlsbyBlbGVtZW50b3YsIGtpIHNlIGVrdmlkaXN0YW7EjW5vIChlbmFrb21lcm5vKSByYXpwb3JlZGlqbyBuYSBpbnRlcnZhbHUgW2Bmcm9tYCwgYHRvYF0uCgoqIGByZXAodiwgdGltZXMgPSBuKWAgamUgdmVrdG9yIHNlc3RhdmxqZW4gdGFrbywgZGEgZWxlbWVudGUgYHZgIHBvbm92aW1vIGBuYC1rcmF0LiDEjGUgamUgYHZgIHNrYWxhciwgamUgZG9sxb5pbmEgZG9ibGplbmVnYSB2ZWt0b3JqYSBgbmAsIMSNZSBwYSBqZSBgdmAgdmVrdG9yLCBqZSBkb2zFvmluYSBkb2JsamVuZWdhIHZla3RvcmphIGBuICogbGVuZ3RoKHYpYC4gUHJpIHJhemxpxI1pY2kgYHJlcCh2LCBlYWNoID0gbilgIGRvbG/EjWltbyBrb2xpa29rcmF0IHBvbm92aW1vIHZzYWsgcG9zYW1lemVuIGVsZW1lbnQgYHZgLgoKKiBgc2FtcGxlKHYsIG4pYCBqZSB2ZWt0b3IgZG9sxb5pbmUgYG5gIChpbWVudWplbW8gZ2EgdnpvcmVjKSwga2F0ZXJlZ2EgZWxlbWVudGkgc28gbmFrbGp1xI1ubyBpemJyYW5pIGVsZW1lbnRpIHZla3RvcmphIGB2YC4gUG9zYW1lemVuIGVsZW1lbnQgaXogYHZgIHNlIGxhaGtvIHYgdnpvcmN1IHBvamF2aSBsZSBlbmtyYXQuIFBvc2xlZGljYSB0ZWdhIGplLCBkYSBqZSBwcmVkcG9nb2ogemEgdXNwZcWhbm8gdnpvcsSNZW5qZSBgbGVuZ3RoKHYpID49IG5gLiBSYXpsacSNaWNhIGBzYW1wbGUodiwgbiwgcmVwbGFjZT1UKWAgb3ByYXZpIHZ6b3LEjWVuamUgcyBwb25hdmxqYW5qZW0sIGtqZXIgamUgcG9zYW1lemVuIGVsZW1lbnQgYHZgIHNlIGxhaGtvIHYgdnpvcmN1IHBvamF2aSB0dWRpIHZlxI1rcmF0LgoKUG9pZ3JhaiBzZSB6IG5hc2xlZG5qaW1pIHByaW1lcmk6CmBgYHtyfQotMToxMwoxMzotMQoxMzoxMwoKc2VxKGZyb20gPSAwLjUsIHRvID0gLTAuNSwgYnkgPSAtMC4xKQpzZXEoZnJvbSA9IC0wLjUsIHRvID0gMC41LCBsZW5ndGgub3V0ID0gNCkKCnJlcCgtMToxLCB0aW1lcyA9IDMpCnJlcCgtMToxLCBlYWNoID0gMykKCnNhbXBsZSgtMTA6MTAsIDEyKQpzYW1wbGUoLTEwOjEwLCAxMikKIyBzYW1wbGUoLTEwOjEwLCAyNCk6IHBvamFzbmkgbmFwYWtvLCBraSBqbyBqYXZpIHRhIGtsaWMgZnVua2NpamUgc2FtcGxlCgpzYW1wbGUoMTo2LCAxMCwgcmVwbGFjZT1UKQpgYGAKIyMgSW5kZWtzaXJhbmplIHZla3RvcmpldgoKSW5kZWtzIGVsZW1lbnRhIHYgdmVrdG9yanUgYHZgIHVzdHJlemEgbmplZ292aSBwb3ppY2lqaSB2IHVyZWplbmVtIHNlem5hbXUuIFRha28gamUgaW5kZWtzIHBydmVnYSBlbGVtZW50YSB2ZWt0b3JqYSAxLCBkcnVnZWdhIDIsIGluIHRha28gbmFwcmVqLCB2c2UgZG8gaW5kZWtzYSB6YWRuamVnYSBlbGVtZW50YS4gVGEgamUgZW5hayBkb2zFvmluaSB2ZWt0b3JqYSBgbGVuZ3RoKHYpYC4gKipQb3pvcioqOiB0byBqZSwgcG9sZWcgcG9pbWVub3ZhbmphIHZla3RvcmpldiwgxaFlIGVuYSBwb21lbWJuYSByYXpsaWthIG1lZCBSLWplbSBpbiBwcm9ncmFtc2tpbSBqZXppa29tIFB5dGhvbiwga2kgbmFzIHBvZ29zdG8gem1lZGUuIFYgUHl0aG9udSBqZSBpbmRla3MgcHJ2ZWdhIGVsZW1lbnRhIHNlem5hbWEgMCwgZHJ1Z2VnYSAxIGluIHphZG5qZWdhIGBsZW4odikgLSAxYC4KClBvc2FtZXplbiBlbGVtZW50IHZla3RvcmphIGB2YCBuYXNsb3ZpbW8geiBpenJhem9tIGB2W2ldYCB6YSBwb2xqdWJlbiBlbGVtZW50IGBpYCBjZWxvxaF0ZXZpbHNrZWdhIHZla3RvcmphIGAxOmxlbmd0aCh4KWAuIFRha28gbmFzbG92bGplbmVtdSBlbGVtZW50dSBsYWhrbyBkb2RlbGltbyBub3ZvIHZyZWRub3N0IHogYHZbaV0gPSBub3ZhX3ZyZWRub3N0YCBhbGkgcGEgZ2EgdXBvcmFiaW1vIHYgcG9sanVibmVtIGl6cmF6dSwgbnByLiBgdltpXSArIDJgLiBUYWtvIGluZGVrc2lyYW5qZSBpbiBuYXNsYXZsamFuamUgcG9zYW1lem5lZ2EgZWxlbWVudGEgdmVrdG9yamEgcG96bmEgdHVkaSB2ZcSNaW5hIGRydWdpaCBwcm9ncmFtc2tpaCBqZXppa292LiBaYSByYXpsaWtvIG9kIHNsZWRuamloLCBSIHBvbnVqYSDFoWUgdmVsaWtvIGRydWdpaCBtb8W+bm9zdGkgaW5kZWtzaXJhbmphLCBraSBqaWggYm9tbyBzcG96bmFsaSB2IG5hZGFsamV2YW5qdS4gRmxla3NpYmlsbm9zdCBpbmRla3NpcmFuamEgdmVrdG9yamV2IGplIGtsanXEjW5lZ2EgcG9tZW5hIHByaSB1cG9yYWJpIFItamEgemEgcG9kYXRrb3ZubyBhbmFsaXpvLgoKUG9nbGVqbW8gKirFoXRpcmkgbmHEjWluZSBuYXNsYXZsamFuamEqKiBwb2xqdWJuZWdhIMWhdGV2aWxhIGVsZW1lbnRvdiB2ZWt0b3JqYSBgdmAuIFZzaSBzbyBvYmxpa2UgYHZbdmldYCwga2plciBqZSBgdmAgdmVrdG9yIGthdGVyZWdhIGVsZW1lbnRlIGluZGVrc2lyYW1vIGluIGB2aWAgdmVrdG9yIGluZGVrc292LgoKKiAqKkVsZW1lbnRpIGB2aWAgc28gcG96aXRpdm5hIGNlbGEgxaF0ZXZpbGEqKjogcmV6dWx0YXQgaW5kZWtzaXJhbmphIGplIHZla3RvciBlbGVtZW50b3YgdmVrdG9yamEgYHZgLCBrYXRlcmloIGluZGVrc2kgdiBgdmAgc28gZWxlbWVudGkgdmVrdG9yamEgYHZpYC4gRWxlbWVudCBgaWAgaW5kZWtzYSBzZSBsYWhrbyB0dWRpIHZlxI1rcmF0IHBvbm92aSB2IHZla3Rvcmp1IGluZGVrc292IGB2aWAuIFRvIHBvdnpyb8SNaSwgZGEgc2UgdHVkaSBlbGVtZW50IGB2W2ldYCBwb2phdmkgdmXEjWtyYXQgdiByZXp1bHRhdHUgaW5kZWtzaXJhbmphLgoKKiAqKkVsZW1lbnRpIGB2aWAgc28gbmVnYXRpdm5hIGNlbGEgxaF0ZXZpbGEqKjogcmV6dWx0YXQgaW5kZWtzaXJhbmphIGplIHZla3RvciBlbGVtZW50b3YgYHZgLCBrYXRlcmloIGluZGVrc2kgKipuaXNvKiogZWxlbWVudGkgdmVrdG9yamEgYWJzb2x1dG5paCB2cmVkbm9zdGkgZWxlbWVudG92IGB2aWAuIFogZHJ1Z2ltaSBiZXNlZGFtaSBlbGVtZW50IGAtaWAgdiB2ZWt0b3JqdSBpbmRla3NvdiBgdmlgIHBvdnpyb8SNaSwgZGEgZWxlbWVudCBgdltpXWAgKipuaSoqIGVsZW1lbnQgcmV6dWx0YXRhIGluZGVrc2lyYW5qYS48YnI+PGJyPioqUG96b3IqKjogUG96aXRpdm5paCBpbiBuZWdhdGl2bmloIGNlbG/FoXRldmlsxI1uaWggdnJlZG5vc3RpIGluZGVrc292IHYgdmVrdG9yanUgYHZpYCBuZSBzbWVtbyBrb21iaW5pcmF0aS4KCiogKipFbGVtZW50aSBgdmlgIHNvIGxvZ2nEjW5lIHZyZWRub3N0aSoqOiByZXp1bHRhdCBpbmRla3NpcmFuamEgamUgdmVrdG9yIGVsZW1lbnRvdiBgdmAgemEga2F0ZXJlIGltYWpvIGVuYWtvLWxlxb5lxI1pIGVsZW1lbnRpIGB2aWAgdnJlZG5vc3QgYFRSVUVgLiBPYmnEjWFqbm8gc3RhIHZla3RvcmphIGB2YCBpbiBgdmlgIGVuYWtpaCBkb2zFvmluLiDEjGUgamUgdmVrdG9yIGB2aWAgZGFsasWhaSBvZCB2ZWt0b3JqYSBgdmAsIHNlIHByaSBpbmRla3NpcmFuanUgdXBvxaF0ZXZhIGxlIHBydmloIGBsZW5ndGgodilgIG5qZWdvdmloIGVsZW1lbnRvdi4gxIxlIGplIHZla3RvciBgdmlgIGtyYWrFoWkgb2QgdmVrdG9yamEgYHZgLCBzZSBuamVnb3ZpIGVsZW1lbnRpIF9yZWNpa2xpcmFqb18sIGtvdCBibyBvcGlzYW5vIHYgdXN0cmVuemVtIHJhemRlbGt1IHNwb2Rhai4KCiogKipFbGVtZW50aSBgdmlgIHNvIG5pemkgem5ha292Kio6IHJlenVsdGF0IGluZGVrc2lyYW5qYSBqZSB2ZWt0b3IgZWxlbWVudG92ICoqaW1lbm92YW5lZ2EqKiB2ZWt0b3JqYSBgdmAsIGthdGVyaWggaW1lbmEgc28gdiBzZXpuYW11IGB2aWAuIMSMZSBzZSBpbWUgYGlgIHYgdmVrdG9yanUgYHZpYCBwb2phdmkgdmXEjWtyYXQsIHNlIHR1ZGkgdiByZXp1bHRhdHUgaW5kZWtzaXJhbmphIGVsZW1lbnQgYHZbaV1gIHBvamF2aSB2ZcSNa3JhdC4KCioqUG96b3IqKjogbmV6bmFuYSB2cmVkbm9zdCBgTkFgIHYgdmVrdG9yanUgaW5kZWtzb3YgYHZpYCB2ZWRubyB2cm5lIGVsZW1lbnQgYE5BYC4KCkR2YSBwb3NlYm5hIHByaW1lcmEgaW5kZWtzaXJhbmphIHN0YSBgdltdYCwga2F0ZXJlZ2EgcmV6dWx0YXQgamUgZW5hayBgdmAgdGVyIGB2WzBdYCwga2F0ZXJlZ2EgcmV6dWx0YXQgamUgcHJhemVuIHZla3RvciAoc2xlZG5qZSBwb2dvc3RvIHptZWRlIHByb2dyYW1lcmplIHYgUHl0aG9uLXUpLiAqKk96bmFrYSB6YSBwcmF6ZW4gdmVrdG9yKiogamUgYGxvZ2ljYWwoMClgLCBgaW50ZWdlcigwKWAsIGBudW1lcmljKDApYCBhbGkgYGNoYXJjdGVyKDApYCwgdiBvZHZpc25vc3RpIG9kIHRpcGEgdnJlZG5vc3RpIGVsZW1lbnRvdiB2ZWt0b3JqYS4KCk5la2FqIHByaW1lcm92LCBramVyIGplIGB2aWAgdmVrdG9yIGNlbG/FoXRldmlsc2tpaCB2cmVkbm9zdGk6CmBgYHtyfQp2ID0gYygxLjEsIC0yLjIsIDMuMywgLTQuNCkKCnZbMzoxXSAjIG9iacSNYWpubwp2W2MoMSwgMywgMSldICMgcG9ub3ZpdGV2IGlzdGVnYSBlbGVtZW50YQp2W29yZGVyKHYpXSAjIHVyZWplbmUgdnJlZG5vc3RpIGVsZW1lbnRvdgoKdltvcmRlcih2KV0gPSBjKDEuMSwgMi4yLCAzLjMsIDQuNCkgIyBpbmRla3NpcmFuZW0gdmVrdG9yanUgbGFoa28gZG9kZWxpbW8gdnJlZG5vc3RpICghKQoKdgp2W2MoLTEsIC0zKV0gIyBuZWdhdGl2bmkgaW5kZWtzaQojIHZbLTE6MV0gIyBrb21iaW5pcmFuamUgbmVnYXRpdm5paCBpbiBwb3ppdGl2bmloIGluZGVrc292IG5pIGRvdm9samVubwpgYGAKCkluIMWhZSBuZWthaiBwcmltZXJvdgpgYGB7cn0KdiA9IGMoNC4yLCAzLjMsIDUuNCwgMi4xKQp2Cgp2W2MoVFJVRSwgVFJVRSwgRkFMU0UsIEZBTFNFKV0gIyBsb2dpxI1uZSB2cmVkbm9zdGkKCnZbdiA+IDNdICMgaW5kZWtzaXJhbmplIHogbG9nacSNbmltIHBvZ29qZW0KdiA+IDMgIyBrYXIgamUgcHJhdnphcHJhdiBpbmRla3NpcmFuamUgeiBsb2dpxI1uaW1pIHZyZWRub3N0bWkKCnZbdiA+IDNdID0gMyAjIGRvZGVsamV2YW5qZSAoISkKdgp2ID0gdlt2ICE9IDNdICMgaXpiaXJhIGVsZW1lbnRvdiwga2kgaXpwb2xuanVqZWpvIHBvZ29qCnYKYGBgCgpNYXRyaWtlIG5hamJvbGogcG9nb3N0byBpbmRla3NpcmFtbyB6IGR2ZW1hIHZla3RvcmplbWE6CmBgYHtyfQpNID0gMToyMApkaW0oTSkgPSBjKDQsIDUpCmNvbG5hbWVzKE0pID0gdG91cHBlcihsZXR0ZXJzWzE6bmNvbChNKV0pCk0KCk1bMToyLCBdICMgaXpiaXJhIHZyc3RpYywgdnNpIHN0b2xwY2kKCk1bMSwgXSAjIGVuYSB2cnN0aWNhLCBSIGpvIHNhbW9kZWpubyBzcHJlbWVuaSB2IHZla3RvcgpNWzEsICwgZHJvcCA9IEZBTFNFXSAjIGVuby12cnN0acSNbmEgbWF0cmlrYSwgUi1qdSBwcmVwcmXEjWltbyBzYW1vZGVqbm8gcHJldHZvcmJvIHYgdmVrdG9yCgpNWywgYygyLDEpXSAjIGl6YmlyYSBzdG9scGNldiwgdnNlIHZyc3RpY2UgLSBib2RpdGUgcG96b3JuaSBuYSB2cnN0bmkgcmVkCgpNWzEsIDFdICMgaXpiaXJhIGVuZWdhIGVsZW1lbnRhIG1hdHJpa2UsIHJlenVsdGF0IGplIHNrYWxhcgpNWzEsIDEsIGRyb3AgPSBGQUxTRV0gIyByZXp1bHRhdCBqZSBtYXRyaWthIGRpbWVuemlqIDF4MQpgYGAKCkxhaGtvIHBhIGppaCBpbmRla3NpcmFtbyB0dWRpIHogbmF2YWRuaW0gdmVrdG9yamVtIGFsaSBtYXRyaWtvLCB2IG9iZWggcHJpbWVyaWggamUgcmV6dWx0YXQgdmVrdG9yOgpgYGB7cn0KTQpNW2MoNCwgMTUpXQpNW2MoLTQsIC0xNSldCgpNSSA9IG1hdHJpeCgKICBjKAogICAgMSwgMSwKICAgIDMsIDEsCiAgICAyLCA1CiAgKSwKICBuY29sID0gMiwgYnlyb3cgPSBUUlVFCikKZGltKE1JKSA9IGMoMywgMikKTVtNSV0KYGBgCgoKIyMgUmHEjXVuYW5qZSB6IHZla3RvcmppIGluIHJlY2lrbGlyYW5qZSBlbGVtZW50b3YgdmVrdG9yamEKClZla3RvcmppIHNvIHByYXZ6YXByYXYgb3Nub3ZuaSB0aXAgcG9kYXRrb3YgdiBSLWp1LCB0YWtvIG9zbm92bmksIGRhICoqc2thbGFyamUgb2JyYXZuYXZhbW8ga290IHBvc2VibmEgdnJzdGEgdmVrdG9yamV2IHogZW5pbSBlbGVtZW50b20qKi4gWmF0byBuYWQgdmVrdG9yamkgbGFoa28gaXp2YWphbW8gb3BlcmFjaWplIHogb3Nub3ZuaW1pIGFyaXRtZXRpxI1uaW1pIG9wZXJhdG9yamkgaW4gbWF0ZW1hdGnEjW5pbWkgZnVua2NpamFtaS4gViBuYWRhbGpldmFuanUgc2kgYm9tbyBvZ2xlZGFsaSBwb3NlYm5vc3RpIHVwb3JhYmUgb3BlcmF0b3JqZXYgaW4gZnVua2NpaiBuYWQgdmVrdG9yamkuCgojIyMgT3Nub3ZuaSBvcGVyYXRvcmppCgpaYSBpenZhamFuamUgb3Nub3ZuaWggYXJpdG1ldGnEjW5paCBvcGVyYWNpaiBzbyB2IFItanUgbmEgdm9sam8gKiphcml0bWV0acSNbmkgb3BlcmF0b3JqaSoqIHBvcGlzYW5pIHYgc3BvZG5qaSB0YWJlbGkuIFYgdnNlaCBwcmltZXJpaCBzZSBvem5ha2UgdmVrdG9yamV2IHphxI1uZWpvIHogYHZgLCBvem5ha2Ugc2thbGFyamV2IHMgYHNgLCBvem5ha2UgbWF0cmlrIHogYG1gIGluIG96bmFrZSBsb2dpxI1uaWggdnJlZG5vc3RpIHogYGxgLgoKfCBPcGVyYXRvciAob3puYWthKSB8IFBvbWVuIChpbWUpIHwgUHJpbWVyKGkpIHVwb3JhYmUgfAp8Oi0tLXw6LS0tfDotLS18CnwgYCtgIHwgc2XFoXRldmFuamUgfCBgczEgKyBzMmAsIGB2MSArIHYyYCwgYHYgKyBzYCB8CnwgYC1gIHwgb2TFoXRldmFuamUgfCBgdjEgLSB2MmAsIGB2IC0gc2AgfAp8IGAqYCB8IG1ub8W+ZW5qZSB8IGB2MSAqIHYyYCwgYHYgKiBzYCB8CnwgYC9gIHwgZGVsamVuamUgfCBgdjEgKiB2MmAsIGB2IC8gc2AgfAp8IGAlLyVgIHwgY2Vsb8WhdGV2aWxza28gZGVsamVuamUgfCBgdjEgJS8lIHYyYCwgYHYxICUvJSBzYCB8CnwgYCUlYCB8IG9zdGFuZWsgcHJpIGNlbG/FoXRldmlsc2tlbSBkZWxqZW5qdSB8IGB2MSAlJSB2MmAsIGB2MSAlJSBzYCwgYHMgJSUgdjFgIHwKfCBgXmAgfCBwb3RlbmNhIHwgYHYxIF4gdjJgLCBgdiBeIHMgYCwgYHMgXiB2YHwKfCBgJSolYCB8IG1hdHJpxI1ubyBtbm/FvmVuamUgfCBgbTEgJSolIG0yYCB8CgpLb3QgdmlkaW1vIGl6IHByaW1lcm92IHYgemdvcm5qaSB0YWJlbGksIG9wZXJhbmRhIHBvZGFuZWdhIG9wZXJhdG9yamEgc3RhIGxhaGtvIHZla3RvcmphLCBza2FsYXJqYSBhbGkgcGEga29tYmluYWNpamEgdmVrdG9yamEgaW4gc2thbGFyamEuIFByZWRlbiBwb2phc25pbW8gcHJhdmlsYSB6YSBpenZhamFuamUgb3BlcmF0b3JqZXYgbmFkIHZla3RvcmppLCBzaSBib21vIG9nbGVkYWxpIMWhZSB0YWJlbG8gKipvcGVyYXRvcmpldiBwcmltZXJqYXZlKiogKHBydmloIMWhZXN0IHZyc3RpYykgaW4gICoqbG9nacSNbmloIG9wZXJhdG9yamV2KiogKHphZG5qaWggcGV0IHZyc3RpYykuCgp8IE9wZXJhdG9yIChvem5ha2EpIHwgUG9tZW4gKGltZSkgfCBQcmltZXIoaSkgdXBvcmFiZSB8Cnw6LS0tfDotLS18Oi0tLXwKfCBgPT1gIHwgZW5ha2kgdnJlZG5vc3RpIHwgYHMxID09IHMyYCwgYHYxID09IHNgLCBgdjEgPT0gdjJgIHwKfCBgIT1gIHwgcmF6bGnEjW5pIHZyZWRub3N0aSB8IGBzMSAhPSBzMmAsIGB2MSAhPSBzYCwgYHYxICE9IHYyYCB8CnwgYDxgIHwgdnJlZG5vc3QgbWFuasWhYSB8IGBzMSA8IHMyYCwgYHYxIDwgc2AsIGB2MSA8IHYyYCB8CnwgYDw9YCB8IHZyZWRub3N0IG1hbmrFoWEgYWxpIGVuYWthIHwgYHMxIDw9IHMyYCwgYHYxIDw9IHNgLCBgdjEgPD0gdjJgIHwKfCBgPmAgfCB2cmVkbm9zdCB2ZcSNamEgfCBgczEgPiBzMmAsIGB2MSA+IHNgLCBgdjEgPiB2MmAgfAp8IGA+PWAgfCB2cmVkbm9zdCB2ZcSNamEgYWxpIGVuYWthIHwgYHMxID49IHMyYCwgYHYxID49IHNgLCBgdjEgPj0gdjJgIHwKfCBgJmAgfCBrb25qdW5rY2lqYSwgbG9nacSNbmkgX2luXyB8IGBzMSAmIHMyYCwgYHYxICYgdjJgLCBgdiAmIHNgIHwKfCBgfGAgfCBkaXNqdW5rY2lqYSwgbG9nacSNbmkgX2FsaV8gfCBgczEgfCBzMmAsIGB2MSB8IHYyYCwgYHYgfCBzYCB8CnwgYCFgIHwgbmVnYWNpamEsIGxvZ2nEjW5pIF9uZV8gfCBgISBzYCwgYCEgdmAgfAp8IGAmJmAgfCBrb25qdW5rY2lqYSBkdmVoIGxvZ2nEjW5paCB2cmVkbm9zdGkgfCBgbDEgJiYgbDJgIHwKfCBgfHxgIHwgZGlzanVua2NpamEgZHZlaCBsb2dpxI1uaWggdnJlZG5vc3RpIHwgYGwxIHx8IGwyYCB8CgpaYWRuamEgZHZhIG9wZXJhdG9yamEgKGAmJmAgaW4gYHx8YCkgc3RhIHBvc2VibmEsIGtlciBvYmEgb2JyYXZuYXZhdGEgc3ZvamEgb3BlcmFuZGEga290IHNrYWxhcm5pIGxvZ2nEjW5pIHZyZWRub3N0aSBpbiBqZSB0YWtvIHJlenVsdGF0IG9wZXJhY2lqZSBza2FsYXJuYSBsb2dpxI1uYSB2cmVkbm9zdCBgVFJVRWAgYWxpIGBGQUxTRWAuCgpLb3Jpc3RlbiBvcGVyYXRvciwga2kgbmUgc29kaSB2IHpnb3JhaiBvcGlzYW5lIGthdGVnb3JpamUsIGplIGAlaW4lYCwga2kgZ2EgdXBvcmFibGphbW8gdiBpenJhemloIG9ibGlrZSBgcyAlaW4lIHZgIHphdG8sIGRhIHByZXZlcmltbywgxI1lIGplIHNrYWxhciBgc2AgZWxlbWVudCB2ZWt0b3JqYSBgdmAuIEl6cmF6IHZybmUgYFRSVUVgIHYgcHJpbWVydSwga28gamUgYHNgIGVsZW1lbnQgdmVrdG9yamEgYHZgLiBTaWNlciBwYSB2cm5lIGBGQUxTRWAuCgojIyMgUmVjaWtsaXJhbmplIGVsZW1lbnRvdiB2ZWt0b3JqYQoKS29tYmluaXJhbmplIHJhemxpxI1uaWggdGlwb3Ygb3BlcmFuZG92ICh0LmouLCB2ZWt0b3JqZXYgaW4gc2thbGFyamV2IGFsaSBwYSB2ZWt0b3JqZXYgcmF6bGnEjW5paCBkb2zFvmluKSBpbWEgc3ZvamUgb21laml0dmUsIGtpIGppaCBtb3JhbW8gdXBvxaF0ZXZhdGkgcHJpIHBpc2FuanUgcHJhdmlsbmloIGl6cmF6b3YgdiBSLWp1LgoKUHJpIGl6dmFqYW5qdSBvcGVyYWNpamUgYHYxIG9wIHYyYCwgbW9yYXRhIHZla3RvcmphIGB2MWAgaW4gYHYyYCwga2kgbmFzdG9wYXRhIGtvdCBvcGVyYW5kYSAocHJpbWVyamFsbmVnYSwgbG9nacSNbmVnYSBhbGkgYXJpdG1ldGnEjW5lZ2EpIG9wZXJhdG9yamEgYG9wYCwgaXpwb2xuamV2YXRpIGVuZWdhIGl6bWVkIG5hc2xlZG5qaWggZHZlaCBwb2dvamV2LgoKICAxLiBWZWt0b3JqYSBzdGEgZW5ha2loIGRpbWVuemlqLCB0LmouLCB2ZWxqYSBgZGltKHYxKSA9PSBkaW0odjIpYC4gViBwcmltZXJ1IG5hdmFkbmloIHZla3Rvcmpldiwga2kgbmlzbyB2ZcSNZGltZW56aW9uYWxuaSAobWF0cmlrZSBhbGkgcG9samEgb3ppcm9tYSB0ZW56b3JqaSksIHRvIHBvbWVuaSwgZGEgc3RhIGVuYWtpaCBkb2zFvmluIGBsZW5ndGgodjEpID09IGxlbmd0aCh2MilgLiDEjGUgc3RhIG1hdHJpa2ksIHRvIHBvbWVuaSwgZGEgbW9yYXRhIGJpdGkgZW5ha2loIGRpbWVuemlqLiBJemplbWEgcHJpIG1hdHJpa2FoIGplIG1hdHJpxI1ubyBtbm/FvmVuamUsIHByaSBrYXRlcmVtIG1vcmFtbyB1cG/FoXRldmF0aSBvYmnEjWFqbm8gb21laml0ZXYgZGEgbW9yYSBpbWV0aSBwcnZhIG1hdHJpa2EgdG9saWtvIHN0b2xwY2V2LCBrb3QgaW1hIGRydWdhIG1hdHJpa2EgdnJzdGljLjxicj48YnI+xIxlIGplIHRhIHBvZ29qIGl6cG9sbmplbiwgamUgcmV6dWxhdCBgdmAgaXp2YWphbmphIG9wZXJhY2lqZSBlbmFraWggZGltZW56aWoga290IG9wZXJhbmRhLiBQcnZpIGVsZW1lbnQgZG9iaW1vIHogdXBvcmFibyBvcGVyYW5kYSBuYSBwcnZpaCBlbGVtZW50aWggb3BlcmFuZG92LCB0LmouLCBwcnZpIGVsZW1lbnQgcmV6dWx0YXRhIGplIGB2WzFdID0gdjFbMV0gb3AgdjJbMV1gLjxicj48YnI+VGEgcG9nb2ogaXpwb2xuanVqZSB0dWRpIG9iacSNYWpuYSBvcGVyYWNpamEgbWVkIGR2ZW1hIHNrYWxhcmplbWEgb2JsaWtlIGBzMSBvcCBzMmAsIGtqZXIgc3RhIG/EjWl0bm8gb2JhIG9wZXJhbmRhIGRpbWVuemlqZSBvemlyb21hIGRvbMW+aW5lIDEuCgogIDEuIERvbMW+aW5hIGRhbGrFoWVnYSB2ZWt0b3JqYSAob3puYcSNaW1vIGdhIHogYHYxYCkgamUgdmXEjWtyYXRuaWsgZG9sxb5pbmUga3JhasWhZWdhIGB2MmAsIHQuai4sIHZlbGphIGBsZW5ndGgodjEpID09IGxlbmd0aCh2MikgKiBrYCwga2plciBqZSBga2AgbmFyYXZubyDFoXRldmlsbyB2ZcSNamUgb2QgMS4gViB0ZW0gcHJpbWVydSBSIHByZWQgaXp2YWphbmplbSBvcGVyYWNpamUgcHJlZGVsYSBrcmFqxaFpIHZla3RvciB2IGRhbMWhZWdhIHRha28sIGRhIGVsZW1lbnRlIGtyYWrFoWVnYSB2ZWt0b3JqYSAqcmVjaWtsaXJhKiBga2Ata3JhdC48YnI+PGJyPlBvc2ViZW4gcHJpbWVyIHRlZ2EgcG9nb2phIGplIG9wZXJhY2lqYSB2ZWt0b3JqYSBzIHNrYWxhcmplbSAoaW4gc2thbGFyamEgeiB2ZWt0b3JqZW0pLCBrYWp0aSwga290IHNtbyBwb3ZlZGFsaSDFvmUgbmEgemHEjWV0a3UgdGVnYSByYXpkZWxrYSwgamUgc2thbGFyIHByYXZ6YXByYXYgdmVrdG9yIGRvbMW+aW5lIDEuIFYgdGVtIHByaW1lcnUgcG9kYW5vIHNrYWxhcm5vIHZyZWRub3N0ICpyZWNpa2xpcmFtbyogdG9saWtva3JhdCwga290IGplIGRvbMW+aW5hIGRhbGrFoWVnYSBvcGVyYW5kYSwgdG9yZWogdmVrdG9yamEuCiAgClphdG8sIGRhIGJvbGplIHJhenVtZcWhIHJlY2lrbGlyYW5qZSwgc2UgcG9pZ3JhaiB6IG5hc2xlZG5qaW1pIHByaW1lcmk6CmBgYHtyfQp2ID0gLTQ6NAoKdiArIFRSVUUgIyBwb2phc25pIHJlenVsdGF0LCBuZSBwb3phYmkgbmEgcHJpc2lsbm8gcHJpbGFnYWphbmplIHRpcG92Cgp2ID4gMCAjIHByZW1pc2xpIGtha28gUiByZWNpa2xpcmEgdnJlZG5vc3RpIHYgdGVtIGl6cmHEjXVudSwKdiA8IDE6LTEgIyBrYWtvIHYgdGVtLAp2IDwgMTotMiAjIGluIHpha2FqIHR1a2FqIGRvYmltbyBvcG96b3JpbG8KCnYgKiAoLTE6MSkKdiAvICgtMToxKSAjIHBvamFzbmkgdGEgcmV6dWx0YXQKYGBgCiAgCgojIyBPc25vdm5lIGZ1bmtjaWplIG5hZCBza2FsYXJqaSBpbiB2ZWt0b3JqaQoKViBSLWp1IHBvZ29zdG8gdXBvcmFibGphbW8gZHZlIG9ibGlraSBmdW5rY2lqLCBraSBrb3QgYXJndW1lbnQgc3ByZWptZWpvIHZla3Rvci4KCiAgMS4gT2JpxI1ham5lIG1hdGVtYXRpxI1uZSBmdW5rY2lqZSBvYmxpa2UgJGY6IFxtYXRoYmJ7Un0gXHRvIFxtYXRoYmJ7Un0kLCBrb3Qgc28gbnByLiBgc2luYCwgYGNvc2AsIGBsb2dgLCBgbG9nMTBgIGFsaSBgZXhwYC4gS28gdGVtIGZ1bmtjaWphbSBwb2RhbW8gemEgYXJndW1lbnQgdmVrdG9yIGB2YCwgamUgcmV6dWx0YXQgdmVrdG9yIHZyZWRub3N0aSBmdW5rY2lqZSBuYSBwb3NhbWV6bmloIGVsZW1lbnRpaCB2ZWt0b3JqYSBgdmAuCiAgCiAgMS4gTWF0ZW1hdGnEjW5lIGZ1bmtjaWplIG9ibGlrZSAkZjogXG1hdGhiYntSfV4qIFx0byBcbWF0aGJie1J9JCwgdG9yZWogZnVua2NpamUsIGtpIHBvbGp1Ym5vIMWhdGV2aWxvIG51bWVyacSNbmloIGFyZ3VtZW50b3YgX3pkcnXFvmlqb18gdiBza2FsYXJubyB2cmVkbm9zdC4gS28gdGVtIGZ1bmtjaWphbSBwb2RhbW8ga290IGFyZ3VtZW50IHZla3RvciBgdmAsIGplIHJlenVsdGF0IHNrYWxhcm5hIHZyZWRub3N0LiBQcmltZXJpIHRha2loIGZ1bmtjaWogc28gYHN1bWAsIGBtZWFuYCwgYHNkYCBpbiBgbGVuZ3RoYCwga2kgdnJuZWpvIHZzb3RvLCBwb3ZwcmXEjW5vIHZyZWRub3N0LCBzdGFuZGFyZG5pIG9ka2xvbiBpbiDFoXRldmlsbyBlbGVtZW50b3YgdmVrdG9yamEuCiAgClBvZ29zdG8gbmFtIGtvcmlzdGlqbyB0dWRpIGR2ZSBmdW5rY2lqaSBuYWQgbml6aSB6bmFrb3Y6CiAgKiBgbmNoYXIoeClgLCBraSBrb3QgcmV6dWx0YXQgdnJuZSDFoXRldmlsbyB6bmFrb3YgdiBwb2RhbmVtIG5penUgem5ha292IGB4YC4KICAqIGBwYXN0ZSh4MSwgeDIsIC4uLiwgeG4pYCwga2kga290IHJlenVsdGF0IHZybmUgZW4gbml6IHpuYWtvdiBkb2JsamVuIHMgc3Rpa29tIHBvZGFuaWggbml6b3Ygem5ha292IGB4MWAsIGB4MmAsIC4uLiwgYHhuYCB6IHZtZXNuaW1pIHByZXNsZWRraS4gUmF6bGnEjWljYSB0ZSBmdW5rY2lqZSBgcGFzdGUwYCBvcHJhdmkgc3RpayBwb2RhbmloIG5pem92IGJyZXogdm1lc25paCBwcmVzbGVka292LgogIApJbiDFoWUgbmVrYWoga29yaXN0bmloIGZ1bmtjaWogemEgdmVrdG9yamU6CiAgKiBgc29ydCh2KWAgdnJuZSB2ZWt0b3IgeiBlbGVtZW50aSB2ZWt0b3JqYSBgdmAgdXJlamVuaW1pIHYgX25hcmHFocSNYWpvxI1lbV8gdnJzdG5lbSByZWR1LiBSYXpsacSNaWNhIGtsaWNhIGBzb3J0KHYsIGRlY3JlYXNpbmc9VFJVRSlgIHVyZWRpIGVsZW1lbnRlIHZla3RvcmphIHYgX3BhZGFqb8SNZW1fIHZyc3RuZW0gcmVkdS4KICAqIGB1bmlxdWUodilgIHZybmUgdmVrdG9yIGVsZW1lbnRvdiBgdmAgYnJleiBwb25hdmxqYW5qYSwgcmV6dWx0YXQgamUgdG9yZWogdmVrdG9yIHYga2F0ZXJlZ2Egc2UgdnNhayBlbGVtZW50IHBvamF2aSBsZSBlbmtyYXQuCiAgKiBgd2hpY2godilgLCBramVyIGplIGB2YCB2ZWt0b3IgbG9nacSNbmloIHZyZWRub3N0aSwgdnJuZSBjZWxvxaF0ZXZpbHNraSB2ZWt0b3IgaW5kZWtzb3YgZWxlbWVudG92IGB2YCwga2kgaW1ham8gdnJlZG5vc3QgYFRSVUVgLiDEjGUgamUgYHZgIHBvaW1lbm92YW4gdmVrdG9yLCB2cm5lIHZla3RvciB6IGltZW5pIHRlaCBlbGVtZW50b3YuCgojIyMgUHJpbWVyaQoKUG9nbGVqbW8gc2kgbmVrYWogcHJpbWVyb3YgdHZvcmplbmphIHNlem5hbW92IGluIG9wZXJhY2lqIG5hZCBuamltaS4KClR2b3JqZW5qZSB2ZWt0b3JqZXYgeiBgOmAgaW4gcHJpbWVyaSBvcGVyYWNpaiwga2kga29tYmluaXJham8gdmVrdG9yamUgaW4gc2thbGFyamU6CmBgYHtyfQoxOjUKNTotNQoKMiBeICgwOjUpCigwOjUpIF4gMgooMDo1KSBeICg1OjApCgp4ID0gMTAKKDI6KHggLTEpKQpgYGAKClR2b3JqZW5qZSB2ZWt0b3JqZXYgeiBgc2VxYCBpbiBmdW5rY2lqZToKYGBge3J9CnNlcSgwLCAxLCAwLjEpCnNlcSgwLCAxLCBsZW5ndGgub3V0PTYpCgpzZXEoMCwgMSwgbGVuZ3RoLm91dD02KSAqIHNlcSgwLCAxLCBsZW5ndGgub3V0PTMpCgpzaW4oc2VxKDAsIHBpLCBsZW5ndGgub3V0PTUpKQptZWFuKDA6MTAwKQpgYGAKClBvbmF2bGphbmplIHogYHJlcGA6CmBgYHtyfQpyZXAoLTE6MSwgMykKcmVwKC0xOjEsIGVhY2g9MikKCnVuaXF1ZShyZXAoLTE6MSwgMykpCmBgYAoKSW4gxaFlIG5ha2xqdcSNbm8gdnpvcsSNZW5qZSBzIHBvbmF2bGphbmplbSBpbiBicmV6OgpgYGB7cn0Kc29ydChzYW1wbGUoLTU6NSwgNSwgcmVwbGFjZT1GKSkKc29ydChzYW1wbGUoLTU6NSwgNSwgcmVwbGFjZT1UKSkKYGBgCgoKCiMjIFBvc2VibmUgdnJzdGUgdmVrdG9yamV2CgoqKkZha3RvcioqIChhbmdsLiBfZmFjdG9yXykgamUgdmVrdG9yIHogZWxlbWVudGksIGtpIGltYWpvIHZyZWRub3N0aSBpeiB2bmFwcmVqIHpuYW5lLCByZWxhdGl2bm8gbWFqaG5lIG1ub8W+aWNlIG1vxb5uaWggdnJlZG5vc3RpLiBUbyBtbm/FvmljbyBkb2xvxI1hIGF0cmlidXQgZmFrdG9yamEgYGxldmVsc2AuCgpGYWt0b3JqZSB0dm9yaW1vIGl6IHZla3RvcmpldiBzIGZ1bmtjaWpvIGBmYWN0b3JgOgpgYGB7cn0KeCA9IGZhY3RvcihjKCJhIiwgImIiLCAiYiIsICJhIikpCngKdHlwZW9mKHgpCmF0dHJpYnV0ZXMoeCkKYGBgCgpLb3QgdmlkaW1vIHNvIGZha3RvcmppIHByYXZ6YXByYXYgY2Vsb8WhdGV2aWxza2kgdmVrdG9yamkuIE5hYm9yIG1vxb5uaWggdnJlZG5vc3RpIGxhaGtvIG5hc3RhdmltbyB0dWRpIG5ha25hZG5vLCBrYXIgbmFtIHBvbWFnYSBwcmkgYW5hbGl6aSBwb2RhdGtvdi4gVGFrbywgbnByLiBmYWt0b3JqdSBgeGAgaXogemdvcm5qZSBrb2RlIGxhaGtvIGRvZGFtbyBtb8W+bm8gdnJlZG5vc3QgYCJjImAsIGthciBzcHJlbWVuaSBvYm5hxaFhbmplIGZ1bmtjaWplIGB0YWJsZWAsIGtpIHByZcWhdGV2YSBmcmVrdmVuY2UgcG9qYXZpdGV2IHJhemxpxI1uaWggdnJlZG5vc3RpIHYgZmFrdG9yanUgKGFsaSB2ZWt0b3JqdSk6CmBgYHtyfQp0YWJsZSh4KQoKbGV2ZWxzKHgpID0gYyhsZXZlbHMoeCksICJjIikKdGFibGUoeCkKYGBgCgpNb8W+bmUgdnJlZG5vc3RpIGZha3RvcmpldiBzbyB2IHNwbG/FoW5lbSBuZXVyZWplbi4gxIxlIHNvIG1vxb5uZSB2cmVkbm9zdGkgZWxlbWVudG92IHVyZWplbmUsIHBvdGVtIGplIGZha3RvciB1cmVqZW4uIFVyZWplbmUgZmFrdG9yamUgdHZvcmltbyBzIGZ1bmtjaWpvIGBvcmRlcmVkYDoKYGBge3J9Cm9jZW5lID0gb3JkZXJlZCgKICBjKCJkb2JybyIsICJvZGxpxI1ubyIsICJwcmF2IGRvYnJvIiwgInphZG9zdG5vIiwgInphZG9zdG5vIiksCiAgbGV2ZWxzID0gYygiemFkb3N0bm8iLCAiZG9icm8iLCAicHJhdiBkb2JybyIsICJvZGxpxI1ubyIpCikKCm9jZW5lCmBgYAoKQm9kaXRlIHBvem9ybmkgbmEgemFkbmpvIHZyc3RpY28gaXpob2RhLCBraSB6IHVwb3JhYm8gem5ha2EgYDxgIHBvdmUga2FrxaFuYSBqZSB1cmVqZW5vc3QgbW/Fvm5paCB2cmVkbm9zdGkgZWxlbWVudG92LgoKKipEYXR1bXNraSB2ZWt0b3IqKiBqZSBuYWRncmFkbmphIHZla3RvcmpldiBudW1lcmnEjW5paCB2cmVkbm9zdGkgKHBsYXZham/EjWUgdmVqaWNlKS4gVHZvcmltbyBqaWggbGFoa28gbmEgdmXEjSBuYcSNaW5vdi4gUyBmdW5rY2lqYW1pIGBhcy5EYXRlYCwgxI1lIG5hcyDEjWFzLyB1cmEgbmUgemFuaW1hLCBhbGkgYGFzLlBPU0lYY3RgLCDEjWUgaG/EjWVtbyBkYXR1bXUgcHJpZHJ1xb5pdGkgdHVkaSB1cm86CmBgYHtyfQpub3cgPSBhcy5EYXRlKFN5cy5EYXRlKCkpCm5vdwoKZnV0dXJlID0gYXMuRGF0ZShTeXMuRGF0ZSgpICsgMzYwKQpmdXR1cmUKCmR1cmF0aW9uID0gZnV0dXJlIC0gbm93CnR5cGVvZihkdXJhdGlvbikKZHVyYXRpb24KCm5vdyA9IGFzLlBPU0lYY3QoU3lzLnRpbWUoKSwgKQpgYGAKCkJvZGl0ZSBwb3pvcm5pIG5hIGl6cGlzIHpnb3JuamVnYSBwcmltZXJhIGDEjGFzb3ZuYSByYXpsaWthIDM2MCBkbmlgOiByYXpsaWthIGR2ZWggZGF0dW1vdiBqZSBwb3NlYmVuIHRpcCBkYXR1bXNrZWdhIHZla3RvcmphLCBraSB1c3RyZXphIMSNYXNvdm5lbXUgaW50ZXJ2YWx1LiBUdm9yaW1vIGdhIGxhaGtvIHR1ZGkgc2FtaSBzIGZ1bmtjaWpvIGBhcy5kaWZmdGltZWAgaW4gZG9sb8SNYW1vIGVub3RlOgpgYGB7cn0KZHVyYXRpb24gPSBhcy5kaWZmdGltZSgxLCB1bml0cz0id2Vla3MiKQpub3cgKyBkdXJhdGlvbgpgYGAKCgotLS0KCiMgU2V6bmFtaSAoYW5nbC4gX2xpc3RzXykKClphIHJhemxpa28gb2QgdmVrdG9yamV2LCBzZXpuYW1pIHYgUi1qdSBsYWhrbyB2c2VidWplam8gZWxlbWVudGUsIGtpIGltYWpvICoqdnJlZG5vc3RpIHJhemxpxI1uaWggdGlwb3YqKi4gWmF0byByZcSNZW1vLCBkYSBzbyBzZXpuYW1pIHBvc3Bsb8WhaXRldiB2ZWt0b3JqZXYgaW4gamloIGltZW51amVtbyB0dWRpIHNwbG/FoW5pIChnZW5lcmnEjW5pKSB2ZWt0b3JqaSAoYW5nbC4gX2dlbmVyaWMgdmVjdG9yc18pLiAqKlBvem9yKio6IFNlem5hbWkgdiBQeXRob24tdSBzbyBwcmF2emFwcmF2IHBvZG9ibmkgdmVrdG9yamVtIChpbiBuZSBzZXpuYW1vbSkgdiBSLWp1LgoKU2V6bmFtZSBsYWhrbyB0dm9yaW1vIHMgZnVua2Npam8gYGxpc3RgOgpgYGB7cn0KbDEgPSBsaXN0KAogIDE6MywKICAiYSIsCiAgYyhUUlVFLCBGQUxTRSwgVFJVRSksCiAgYygyLjMsIDUuOSkKKQoKbDEKdHlwZW9mKGwxKQoKbGVuZ3RoKGwxKQpgYGAKQm9kaW1vIHBvem9ybmkgbmEgZGVqc3R2bywga2kgZ2Ega2HFvmUgemFkbmppIHByaW1lciwgZGEgaW1hIHNlem5hbSDFoXRpcmkgZWxlbWVudGUuIFZla3RvcmppIChpbiBuZSBuamlob3ZpIHBvc2FtZXpuaSBlbGVtZW50aSkgc28gZWxlbWVudGkgc2V6bmFtYS4gS290IGJvbW8gdmlkZWxpIHBvem5lamUgc28gbGFoa28gdHVkaSBzZXpuYW1pIGVsZW1lbnRpIHNlem5hbWEuCgoKVGFrbyBrb3QgdmVrdG9yamksIHNvIGxhaGtvIHR1ZGkgc2V6bmFtaSBwb2ltZW5vdmFuaToKYGBge3J9Cm9zZWJhID0gbGlzdCgKICBpbWUgPSAiQWxla3NhbmRyYSIsCiAgc3BvbCA9ICLFviIsCiAgc3Rhcm9zdCA9ICIyMSIKKQpvc2ViYQpgYGAKClNlem5hbWUgbGFoa28gZ25lemRpbW8sIGthciBwb21lbmksIGRhIGplIGxhaGtvIHNlem5hbSBlbGVtZW50IHNlem5hbWEuIFR1a2FqIGplIHByaW1lciBuZW5hdmFkbmVnYSBzZXpuYW1hIHogZW5pbSBlbGVtZW50b206CmBgYHtyfQpsMiA9IGxpc3QobGlzdChsaXN0KDEpKSkgIyByZXMgxI11ZGVuIHNlem5hbQpsMgpgYGAKClJhemxpxI1uZSBzZXpuYW1lIGxhaGtvIGtvbWJpbmlyYW1vLyBzZXN0YXZsamFtbyBzIGZ1bmtjaWpvIGBjYCBhbGkgYGxpc3RgOgpgYGB7cn0KbDMgPSBsaXN0KGxpc3QoMSwgMiksIGMoMywgNCkpCmwzCmw0ID0gYyhsaXN0KDEsIDIpLCBjKDMsIDQpKQpsNApgYGAKClYgemdvcm5qaWggcHJpbWVyaWggYm9kaXRlIHBvem9ybmkgbmEgcmF6bGlrbyBtZWQgc2V6bmFtb21hIGBsM2AgaW4gYGw0YC4gUG96b3JubyBqdSBwcmltZXJqYWp0ZSwgcG9nbGVqdGUga2FrxaFuYSBqZSBkb2zFvmluYSB2c2FrZWdhIGluIHBvamFzbml0ZSB6YWthaiBwcmlkZSBkbyB0ZSByYXpsaWtlLiBQcmkga2F0ZXJlbSBvcGF6dWplbW8gZ25lemRlbmplIHNlem5hbW92PwoKxIxlIHNpIMW+ZWxpbW8gdmVrdG9yIHByZXR2b3JpdGkgdiBzZXpuYW0sIHVwb3JhYmltbyBmdW5rY2lqbyBgYXMubGlzdGAuIFJlenVsdGF0IGZ1bmtjaWplIGplIHNlem5hbSwga2kgaW1hIGVuYWtvIMWhdGV2aWxvIGVsZW1lbnRvdiBrb3QgdmVrdG9yIGluIHZzYWsgZWxlbWVudCB2ZWt0b3JqYSBwb3N0YW5lIGVsZW1lbnQgc2V6bmFtYS4gUG9nbGVqbW8gxaFlIGVuIHByaW1lciwga2kgcG9uYXpvcmkgcmF6bGlrbyBtZWQgZnVua2NpamFtYSBgYXMubGlzdGAgaW4gYGxpc3RgOgpgYGB7cn0KbDUgPSBhcy5saXN0KC0yOjIpCmw1CgpsNiA9IGxpc3QoLTI6MikKbDYKYGBgCgpaYSBwcmV0dm9yYm8gc2V6bmFtYSB2IHZla3RvciB1cG9yYWJsamFtbyBmdW5rY2lqbyBgdW5saXN0YC4gUHJpIHRlbSBtb3JhbW8gYml0aSBwb3pvcm5pIG5hIHRvLCBkYSBtb3Jham8gYml0aSB2c2kgZWxlbWVudGkgdmVrdG9yamEgaXN0ZWdhIHRpcGEuIMSMZSBzbyBlbGVtZW50aSBzZXpuYW1hIHJhemxpxI1uaWggdGlwb3YsIGJvIFIgbWVkIGl6cmHEjXVub20gcmV6dWx0YXRhIGZ1bmtjaWplIGB1bmxpc3RgIG9wcmF2aWwgcHJpc2lsbm8gcHJldHZvcmJvIHRpcG92LiBQb2dsZWptbyBwcmltZXJlOgpgYGB7cn0KdW5saXN0KGwxKQp1bmxpc3QobDIpCnVubGlzdChsMykKdW5saXN0KGw0KQp1bmxpc3QobDUpCnVubGlzdChsNikKYGBgCgpQb2phc25pIHpha2FqIHN0YSByZXp1bHRhdGEgYGwzYCBpbiBgbDRgICh0ZXIgYGw1YCBpbiBgbDZgKSBlbmFrYS4KCiMjIEluZGVrc2lyYW5qZSBzZXpuYW1vdgoKS28gaW5kZWtzaXJhbW8gc2V6bmFtIGBzYCB0YWtvLCBrb3QgaW5kZWtzaXJhbW8gdmVrdG9yamUsIHRvcmVqIGluZGVrcyBwb3N0YXZpbW8gdiBlbm9qbmUgb2tsZXBhamUsIG5hIHByaW1lciBgc1sxXWAgYWxpIGBzWzI6M11gLCBqZSByZXp1bHRhdCBpbmRla3NpcmFuamEgKip2ZWRubyoqIHNlem5hbSwgxI1ldHVkaSBzZXpuYW0geiBlbmltIGVsZW1lbnRvbS4gxIxlIMW+ZWxpbW8geiBpbmRla3NpcmFuamVtIHNlem5hbWEgZG9iaXRpIG5qZWdvdmUgcG9zYW1lem5lIGVsZW1lbnRlLCBtb3JhbW8gdXBvcmFibGphdGkgZHZvam5lIG9nbGF0ZSBva2xlcGFqZS4gUHJ2aSBlbGVtZW50IHNlem5hbWEgdG9yZWogZG9iaW1vIHogaXpyYXpvbSBgc1tbMV1dYC4KClBvZ2xlam1vIG5la2FqIHByaW1lcm92OgpgYGB7cn0KbDFbMjozXSAjIHNlem5hbSB6IGR2ZW1hIGVsZW1lbnRvbWEKbDFbLTRdICMgc2V6bmFtIHMgdHJlbWkgZWxlbWVudGkKCmwxWzFdICMgc2V6bmFtIHogZW5pbSBlbGVtZW50b20KdHlwZW9mKGwxWzFdKQoKbDFbWzFdXSAjIHBydmkgZWxlbWVudCBzZXpuYW1hCnR5cGVvZihsMVtbMV1dKQpgYGAKCioqT3BvbWJhKio6IGR2b2puZSBvZ2xhdGUgb2tsZXBhamUgbGFoa28gdXBvcmFibGphbW8gdHVkaSB6YSBkb3N0b3AgZG8gcG9zYW1lem5paCBlbGVtZW50b3YgKip2ZWt0b3JqYSoqLiBWZWxpa28gcHJvZ3JhbWVyamV2IHYgUi1qdSBzZSBwb3NsdcW+dWplIGR2b2puaWggb2tsZXBhamV2IHByaSBuYXNsYXZsamFuanUgcG9zYW1lem5paCBlbGVtZW50b3YgdmVrdG9yamEgKGluIHNldmVkYSBzZXpuYW1hKSB6YXRvLCBkYSBqZSBrb2RhIGJvbGogcHJlZ2xlZG5hLiBaIGR2b2puaW1pIG9rbGVwYWppIG5hbXJlxI0gcG91ZGFyaW1vLCBkYSDFvmVsaW1vIHogaW5kZWtzaXJhbmplbSBuYXNsb3ZpdGkgdG/EjW5vIGVuIGVsZW1lbnQgdmVrdG9yamEuIEtvIHBhIHVwb3JhYmxqYW1vIGluZGVrc2lyYW5qZSB6YSBkb3N0b3AgZG8gdmXEjWplZ2EgKGFsaSBwb2xqdWJuZWdhKSDFoXRldmlsYSBlbGVtZW50b3YsIHBvdGVtIG1vcmFtbyB1cG9yYWJsamF0aSBlbm9qbmUgb2tsZXBhamUuCgpWIHBvaW1lbm92YW5paCBzZXpuYW1paCBsYWhrbyBwb3NhbWV6bmUgZWxlbWVudGUgbmFzbG92aW1vIG5hIHZlxI0gbmHEjWlub3Y6CmBgYHtyfQpvc2ViYVtbMV1dCm9zZWJhW1siaW1lIl1dCm9zZWJhJGltZQpgYGAKClBydmkgbmHEjWluIHogdXBvcmFibyB6bmFrYSBgJGAgamUgbmFqYm9saiBvYmnEjWFqZW4gaW4gcG9nb3N0byB1cG9yYWJsamVuLgoKLS0tCgojIE5hbG9nZQoKIyMgUHJlbWlzbGVraSBicmV6IHBvZ2FuamFuamEgUgoKMS4gTmHFoXRlaiBwbyBwYXIgcHJpbWVyb3YgaXpyYXpvdiB6IG51bWVyacSNbmltaSB2cmVkbm9zdG1pIGBJbmZgLCBgLUluZmAgaW4gYE5hTmAuCjEuIEtha8WhZW4gamUgdGlwIGVsZW1lbnRvdiB2ZWt0b3JqYSBgYygxOjUsIEZBTFNFKWA/CjEuIEthaiBqZSByZXp1bHRhdCBrbGljZXYgYGxlbmd0aChjKDE6NSwgNToxKSlgIGluIGBsZW5naHQoImFiYyIpYD8KMS4gUHJlbWlzbGkga2FrbyBkb2xnYSBib3N0YSB2ZWt0b3JqYSBgMTo1ICsgMWAgaW4gYDE6KDUgKyAxKWAuCjEuIEthaiBpenJhxI11bmEgaXpyYXogYHN1bSh2ID09IDMpYCB6YSBwb2RhbiB2ZWt0b3IgYHZgPyBLYWtvIGplIHZyZWRub3N0IHRlZ2EgaXpyYXphIHBvdmV6YW5hIHogdnJlZG5vc3RqbyBpenJhemEgYHdoaWNoKHYgPT0gMylgPwoxLiBTZXpuYW0gYHggPSBsaXN0KGE9YygxLDEpLCBiPTIsIGM9bGlzdChjKDMsMywzKSxjKDQpKSlgLiBLYWvFoW5lIHNvIHZyZWRub3N0aSBpenJhem92IGB4WzFdYCwgYHhbWzJdXWAgaW4gYHhbWzNdXVsxXWA/IE5hxaF0ZWogxI1pbSB2ZcSNIG5hxI1pbm92IHphIHByaWRvYml2YW5qZSB2cmVkbm9zdGkgZHJ1Z2VnYSBlbGVtZW50YSBzZXpuYW1hIGB4YC4KCiMjIFByb2dyYW1lcnNrZSBuYWxvZ2UKCjEuIERlbmltbywgZGEgc21vIEFsZcWhdSwgQmFyYmFyaSwgQ2lyaWx1IGluIERhcmppIGl6bWVyaWxpIHZpxaFpbmUgdiBjZW50aW1ldHJpaCAoMTgwLCAxNjUsIDE2MCwgMTkzKSBpbiB0ZcW+ZSB2IGtpbG9ncmFtaWggKDg3LCA1OCwgNjUsIDEwMCkuIE5hcmVkaSBkdmEgdmVrdG9yamEgYHZgIGluIGB0YCB6IGl6bWVyamVuaW1pIHBvZGF0a2kuIEluZGVrcyB0ZWxlc25lIG1hc2UgKElUTSkgaXpyYcSNdW5hbW8gcG8gZm9ybXVsaQokJCBcdGV4dHtJVE19ID0gXGZyYWN7XHRleHR7dGXFvmEgdiBraWxvZ3JhbWlofX17KFx0ZXh0e3ZpxaFpbmEgdiBtZXRyaWh9KV4yfS4gJCQKSXpyYcSNdW5haiBfcG9pbWVub3Zhbl8gdmVrdG9yIHZyZWRub3N0aSBJVE0gemEgxaF0aXJpIG9zZWJlLCBramVyIHNvIGltZW5hIGVsZW1lbnRvdiBkZWphbnNrYSBpbWVuYSBvc2ViIChBbGXFoSwgQmFyYmFyYSwgQ2lyaWwgaW4gRGFyamEpLiBOYXRvIGl6cmHEjXVuYWogdmVrdG9yIG5hcmF2bmloIGxvZ2FyaXRtb3YgdnJlZG5vc3RpIElUTS4gTmEga29uY3UgaXpwacWhaSB2ZWt0b3IgaW1lbiBvc2ViLCBraSBpbWFqbyBJVE0gdmXEjWppIG9kIDI1LCB0ZXIgaXpyYcSNdW5haiBuamlob3YgcG92cHJlxI1uaSBJVE0uCgoxLiBTZXN0YXZpIHZla3RvcmplIHogbmFzbGVkbmppbWkgZWxlbWVudGk6CgogICAgYS4gJDNeMS8xLCAzXjIvMiwgXGxkb3RzLCAzXns1MH0vNTAkOwogICAgYS4gIkExIiwgIkEyIiwgJFxsZG90cyQsICJBNTAiOwogICAgYS4gJGVeeCBcc2luIHgkIGl6cmHEjXVuYW5pbWkgdiB0b8SNa2FoICR4ID0gMywgMy4xLCAzLjIsIFxsZG90cywgNiQuPGJyPjxicj4KCjEuIFBvZGFubyBqZSBuYXJhdm5vIMWhdGV2aWxvIGB4ID4gMmAuIFphcGnFoWkgaXpyYXogdiBSLWp1LCBrYXRlcmVnYSByZXp1bHRhdCBqZSBgVFJVRWAsIMSNZSBqZSBgeGAgcHJhxaF0ZXZpbG8gaW4gYEZBTFNFYCBzaWNlci4gTmFtaWc6IGl6cmF6IGdyYWRpIHBvc3RvcG9tYS4gTmFqcHJlaiBpenJhxI11bmFqIHZla3RvciBvc3RhbmtvdiBkZWxqZW5qYSBgeGAgcyB2c2VtaSDFoXRldmlsaSBvZCBgMmAgZG8gYHggLSAxYC4gTmF0byBwcmV2ZXJpLCDEjWUgamUgYDBgIGVsZW1lbnQgdGVnYSBzZXpuYW1hLgoKMS4gSXpyYcSNdW5haiBkdmEgdmVrdG9yamEgYHhgIGluIGB5YCBkb2zFvmluZSAyNTAgdGFrbywgZGEgcyBwb25hdmxqYW5qZW0gbmFrbGp1xI1ubyBpemJpcmHFoSBjZWxhIMWhdGV2aWxhIGl6IGludGVydmFsYSAkWzAsIDk5OV0kLiBPem5hxI1pbW8geiAkeF8xLCB4XzIsIFxsZG90cywgeF97MjUwfSQgaW4gJHlfMSwgeV8yLCBcbGRvdHMsIHlfezI1MH0kIGVsZW1lbnRlIHNlem5hbW92IGB4YCBpbiBgeWAuIEl6cmHEjXVuYWo6CgogICAgYS4gVmVrdG9yIHogZWxlbWVudGkgJHlfMiAtIHhfMSwgeV8zIC0geF8yLCBcbGRvdHMsIHlfezI1MH0gLSB4X3syNDl9JC4KICAgIGEuIFZzb3RvICRcc3VtX3tpPTF9XnsyNDl9IGVeey0geF97aSsxfX0gLyAoeF9pICsgMTApJC48YnI+PGJyPgoKMS4gU2VzdGF2aSB2ZWt0b3IgdGlwYSBmYWt0b3IgeiBuYWJvcm9tIG1vxb5uaWggdnJlZG5vc3RpIGBBYCwgYEJgIGluIGBDYC4gVmVrdG9yIG5haiBpbWEgbmFrbGp1xI1ubyBpemJyYW5paCAxNSBlbGVtZW50b3YsIHBvIG5hdGFua28gcGV0ICg1KSBpeiB2c2FrZSBrYXRlZ29yaWplLgoKMS4gTmEgRk1GIHBvZ29zdG8gb2Nlbmp1amVtbyBkb3Nlxb5rZSDFoXR1ZGVudG92IG5hIGl6cGl0IHogb2RzdG90bmltaSB0b8SNa2FtaTogxaF0dWRlbnRrYSBsYWhrbyBkb3Nlxb5lIG9kIDAgZG8gMTAwIG9kc3RvdG5paCB0b8SNayAodmtsanXEjW5vKS4gRG9zZcW+ZWsgdiBvZHN0b3RuaWggdG/EjWthaCAoJSkgbmF0byBwcmV2ZWRlbW8gdiBvY2VubyBzIHByZWRwaXNvbSBpeiBzcG9kbmplIHRhYmVsZS4KCiAgICB8IGRvc2XFvmVrIHYgJSB8IG9jZW5hIHwKICAgIHw6LS0tfC0tLTp8CiAgICB8IHZlxI1qaSBvZCA5MCB8IG9kbGnEjW5vICgxMCkgfAogICAgfCB2ZcSNamkgb2QgODAgaW4gbmFqdmXEjSA5MCB8IHByYXYgZG9icm8gKDkpIHwKICAgIHwgdmXEjWppIG9kIDcwIGluIG5hanZlxI0gODAgfCBwcmF2IGRvYnJvICg4KSB8CiAgICB8IHZlxI1qaSBvZCA2MCBpbiBuYWp2ZcSNIDcwIHwgZG9icm8gKDcpIHwKICAgIHwgdmXEjWppIG9kIDUwIGluIG5hanZlxI0gNjAgfCB6YWRvc3RubyAoNikgfAogICAgfCBuYWp2ZcSNIDUwIHwgbmV6YWRvc3RubyAoNSkgfAoKICAgIGEuIFNlc3RhdmkgdmVrdG9yIGBvdGAgeiBuYWtsanXEjW5pbWkgZG9zZcW+a2kgZHZhanNldGloIMWhdHVkZW50b3Y7CiAgICBhLiBQcmV0dm9yaSBkb3Nlxb5rZSBpeiB2ZWt0b3JqYSBgb3RgIHYgdmVrdG9yIGBvYCB0aXBhIHVyZWplbiBmYWt0b3IsIGthdGVyaWggZWxlbWVudGkgc28gb2NlbmUgxaF0dWRlbnRvdiBpenJhxI11bmFuZSBwbyB6Z29ybmplbSBwcmVkcGlzdS48YnI+PGJyPgoKMS4gViBwcm9ncmFtdSB6YSB1cmVqYW5qZSBwcmVnbGVkbmljIEV4Y2VsIChpbiBwb2RvYm5vIHYgcHJvZ3JhbWloIEdvb2dsZSBTaGVldHMgYWxpIE51bWJlcnMpIHNvIHN0b2xwY2kgcG9pbWVub3ZhbmkgeiB2ZWxpa2ltaSDEjXJrYW1pIGFuZ2xlxaFrZSBhYmVjZWRlIChvZCBBIGRvIFopLCBuYXRvIHNsZWRpam8gc3RvbHBjaSBwb2ltZW5vdmFuaSBzIGR2ZW1hIMSNcmthbWEgKG9kIEFBLCBwcmVrIEFCLCBBQywgaW4gdGFrbyBuYXByZWosIGRvIFpZIGluIFpaKSwgbmF0byDFoWUgdGlzdGkgcyB0cmVtaSDEjXJrYW1pIChvZCBBQUEgZG8gWlpaKS48YnI+PGJyPlphZG5qaSBzdG9scGVjIHYgcHJlZ2xlZG5pY2kgamUgcG9pbWVub3ZhbiBYRkQuIE5hcGnFoWkgcHJvZ3JhbSB2IFItanUsIGtpIGl6cmHEjXVuYSBrb2xpa28gc3RvbHBjZXYgaW1hIHByZWdsZWRuaWNhIHYgcHJvZ3JhbXUgRXhjZWwuPGJyPjxicj5OYW1pZzogdXBvcmFiacWhIGxhaGtvIGBMRVRURVJTYCwgdC5qLiwgdmVrdG9yIHZlbGlraWggxI1yayB2IGFuZ2xlxaFraSBhYmVjZWRpLCB0ZXIga2xpY2UgZnVua2NpaiBgc29ydGAgaW4gYG91dGVyYCAocG9tYWdhaiBzaSB6IGA/b3V0ZXJgKSB2IGtvbWJpbmFjaWppIHMgZnVua2Npam8gYHBhc3RlMGAgemEgaXpyYcSNdW4gdmVrdG9yamEgeiBpbWVuaSBzdG9scGNldi4gTmF0byBzIGtsaWNlbSBmdW5rY2lqZSBgd2hpY2hgIHVnb3Rvdml0ZSBpbmRla3Mgc3RvbHBjYSBgWEZEYCB2IHRlbSB2ZWt0b3JqdS4KCjEuIFZzZSB2ZWt0b3JqZSBpbiBza2FsYXJqZSwgaXpyYcSNdW5hbmUgdiBwcnZpIG5hbG9naSB6Z29yYWogKG5hbG9naSBvIGluZGVrc3UgdGVsZXNuZSBtYXNlKSwgc2VzdGF2aSB2IHNlem5hbSBgcmVzaXRldmAgcyBwb2xqaSBgaW1lbmFgLCBgdmlzaW5lYCwgYElUTWAsIGBsb2dJVE1gLCBgSVRNLnYuMjVgLCBgaW1lbmEuSVRNLnYuMjVgIGluIGBJVE0udi4yNS5wb3ZwcmVjamVgLgoKLS0tCgpQcnZvIHJhemxpxI1pY28gdGVnYSB6dmV6a2Egc2VtIG9rdG9icmEgMjAyMSBwcmlwcmF2aWwgW0xqdXDEjW8gVG9kb3JvdnNraV0oaHR0cHM6Ly9rdC5panMuc2kvfmxqdXBjby8pIHYgamV6aWt1IFtSIE1hcmtkb3duXShodHRwczovL3JtYXJrZG93bi5yc3R1ZGlvLmNvbSkuIElkZWplIHphIHByaW1lcmUgc2VtIHNpIHYgdmVsaWtpIG1lcmkgaXpwb3NvamFsIGl6IHXEjWJlbmlrYSBbKFdpY2toYW0gMjAxOSldKGh0dHBzOi8vYWR2LXIuaGFkbGV5Lm56LyksIHZzZSBtb3JlYml0bmUgbmFwYWtlIHYgcHJpbWVyaWggc28gaXprbGp1xI1ubyBtb2plLiBadmV6ZWsgc2VtIG5hemFkbmplIHNwcmVtaW5qYWwgb2t0b2JyYSAyMDI0LgoKViB6dmV6a2loIHogemFwaXNraSBzIHByZWRhdmFuaiBwcmkgdGVtIHByZWRtZXR1IGplIGAuYCBvem5ha2EgemEgZGVjaW1hbG5vIHZlamljbyBpbiBgLGAgbG/EjWlsbyBtZWQgdGlzb8SNaWNhbWksIGthciB2IHNsb3ZlbsWhxI1pbmkgbmVuYXZhZG5vIChhbGkgcGEga2FyIG5hcm9iZSkuIFRha28gamUgMy8yID0gMS41IChpbiBuZSAxLDUpIGluIGplIDFlNiA9IDEwMDAwMDAgPSAxLDAwMCwwMDAgKGluIG5lIDEuMDAwLjAwMCku