RにおけるData型:Vector型とリスト型
Atomic ベクトル
- 整数、数値(実数)、複素数値、論理値、文字、バイト
people <- c("Lori", "Yubo", "Greg", "Nitesh", "Valerie", "Herve")
people
[1] "Lori" "Yubo" "Greg" "Nitesh" "Valerie" "Herve"
- Atomic vectorは
名前
をつけることができます。
population <- c(Buffalo=259000, Rochester=210000, `New York`=8400000)
population
Buffalo Rochester New York
259000 210000 8400000
log10(population)
Buffalo Rochester New York
5.413300 5.322219 6.924279
truthiness <- c(TRUE, FALSE, NA)
truthiness
[1] TRUE FALSE NA
- 論理操作:
&
(and), |
(or), !
(not)
!truthiness
[1] FALSE TRUE NA
truthiness | !truthiness
[1] TRUE TRUE NA
truthiness & !truthiness
[1] FALSE FALSE NA
- 実数:
Inf
(無限大), NaN
(not-a-number; e.g., 0/0)
undefined_numeric_values <- c(NA, 0/0, NaN, Inf, -Inf)
undefined_numeric_values
[1] NA NaN NaN Inf -Inf
sqrt(undefined_numeric_values)
NaNs produced
[1] NA NaN NaN Inf NaN
toupper(people)
[1] "LORI" "YUBO" "GREG" "NITESH" "VALERIE" "HERVE"
substr(people,1,3)
[1] "Lor" "Yub" "Gre" "Nit" "Val" "Her"
gsub("L","S",people)
[1] "Sori" "Yubo" "Greg" "Nitesh" "Valerie" "Herve"
- Rはエコなやつなんです(より長いベクトルに合わせるときに、短いベクトルをリサイクルする)
x <- 1:3
x * 2 # '2' (vector of length 1) recycled to c(2, 2, 2)
[1] 2 4 6
x + 2
[1] 3 4 5
Rでは、操作を入れ子にして記述することもできます。コードが一行でシンプルに書けますが、慣れないうちに多用すると、混乱や間違いの原因にもなります。
substr(tolower(people), 1, 3)
[1] "lor" "yub" "gre" "nit" "val" "her"
population[population < 1000000]
Buffalo Rochester
259000 210000
リスト
- リストは他のベクトルをいくつでも持つことができます。リスト中に別のリストを含めることもできます。
frenemies = list(
friends=c("Larry", "Richard", "Vivian"),
enemies=c("Dick", "Mike")
)
frenemies
$friends
[1] "Larry" "Richard" "Vivian"
$enemies
[1] "Dick" "Mike"
[
は一つのリストを取り出します。[[
はリストの要素を取り出します。
frenemies[1]
$friends
[1] "Larry" "Richard" "Vivian"
frenemies[c("enemies", "friends")]
$enemies
[1] "Dick" "Mike"
$friends
[1] "Larry" "Richard" "Vivian"
frenemies[["enemies"]]
[1] "Dick" "Mike"
また、リストの各要素はリスト名$名前
を用いて取り出すこともできます。
frenemies$friends
[1] "Larry" "Richard" "Vivian"
因子型(Factors)
- 文字列のようなベクトルですが、水準を表しています。
sex = factor(c("Male", "Male", "Female"),
levels=c("Female", "Male", "Hermaphrodite"))
sex
[1] Male Male Female
Levels: Female Male Hermaphrodite
sex == "Female"
[1] FALSE FALSE TRUE
table(sex)
sex
Female Male Hermaphrodite
1 2 0
sex[sex == "Female"]
[1] Female
Levels: Female Male Hermaphrodite
クラス:行列とデータフレーム
変数と変数は、より高度な形式(ベクトルではなく、2列以上の表形式)で互いに関連性を持ちます。
x = rnorm(1000) # 1000 random normal deviates
y = x + rnorm(1000) # another 1000 deviates, as a function of x
plot(y ~ x) # relationship bewteen x and y
表形式のデータは行列(Matrix)あるいはデータフレーム(Data frame)という形で扱うことができる。
行列
mat <- matrix(c(x,y),ncol=2,dimnames=list(NULL,c("X","Y")))
head(mat)
X Y
[1,] 1.0496814 0.963251706
[2,] 0.8600534 1.940403074
[3,] 0.3656297 1.164705295
[4,] 0.3202156 0.002081173
[5,] -0.2924875 -2.318154511
[6,] -1.5436153 -2.296778112
plot(Y~X,mat)
もしすべてのデータを見たいのであれば、view(mat)と打てば、新しいウィンドウが立ち上がります。データを要約したい場合には、summary(mat)
とします。
summary(mat)
X Y
Min. :-3.12043 Min. :-4.274855
1st Qu.:-0.68976 1st Qu.:-0.940847
Median :-0.03524 Median : 0.005398
Mean :-0.03643 Mean :-0.017844
3rd Qu.: 0.63622 3rd Qu.: 0.909239
Max. : 3.46710 Max. : 4.345877
行列の操作は座標を指定するように簡単に行えます。
mat[1,1] # element of (row,col) = (1,1) in the matrix
X
1.049681
mat[c(1,3),c(1,2)] #element of (row,col) = (1,1),(1,2),(3,1),(3,2) in the matrix
X Y
[1,] 1.0496814 0.9632517
[2,] 0.3656297 1.1647053
mat[1,] # first row
X Y
1.0496814 0.9632517
mat[1:10,] #first 10 rows
X Y
[1,] 1.0496814 0.963251706
[2,] 0.8600534 1.940403074
[3,] 0.3656297 1.164705295
[4,] 0.3202156 0.002081173
[5,] -0.2924875 -2.318154511
[6,] -1.5436153 -2.296778112
[7,] 0.6615876 -0.660558236
[8,] 0.5548444 -0.121794431
[9,] -0.6271999 -0.868178332
[10,] 0.1774791 -0.070211599
条件でフィルタリングすることもできます。
cond <- mat[,"X"] > 0 #condition where "X" is more than zero
positivex <- mat[cond,] #filtering corresponding to the condition
head(mat)
X Y
[1,] 1.0496814 0.963251706
[2,] 0.8600534 1.940403074
[3,] 0.3656297 1.164705295
[4,] 0.3202156 0.002081173
[5,] -0.2924875 -2.318154511
[6,] -1.5436153 -2.296778112
もっと複雑な条件でフィルタリングもできます。
cond1 <- mat[,"X"] > 0
cond2 <- mat[,"Y"] - mat[,"X"]^2 > 0
cond <- cond1 & cond2
complexcond <- mat[cond,]
head(complexcond)
X Y
[1,] 0.86005342 1.9404031
[2,] 0.36562974 1.1647053
[3,] 0.82350611 2.0236474
[4,] 0.30753063 2.0293793
[5,] 0.96978649 1.7120020
[6,] 0.03745899 0.4013019
plot(Y ~ X,positivex)
データフレーム
データフレームは行列とかなり似ています。基本的な使い方はほとんど行列と変わりません。
df <- data.frame(X=x,Y=y)
head(df)
plot(Y~X,df)
summary(df)
X Y
Min. :-3.12043 Min. :-4.274855
1st Qu.:-0.68976 1st Qu.:-0.940847
Median :-0.03524 Median : 0.005398
Mean :-0.03643 Mean :-0.017844
3rd Qu.: 0.63622 3rd Qu.: 0.909239
Max. : 3.46710 Max. : 4.345877
データフレームの要素にアクセスするには、行列と同じ指定方法以外に、データフレーム名$列名
という方法も用意されています。
positivex2 <- df[df$X > 0, ]
head(positivex2)
plot(Y~X,positivex2)
データフレームが行列と異なる点は、行列では列ごとに異なる異なるデータ型は持てませんが、データフレームでは、異なるデータ型を持つことができる点です。
x2 <- rnorm(100,0,1)
y2 <- rep(c("Apple","Grape","Cherry","Peach"),each=25)
mat2 <- matrix(c(x2,y2),ncol=2,dimnames=list(NULL,c("X","Y")))
head(mat2)
X Y
[1,] "-0.584383679476397" "Apple"
[2,] "-0.246258530438461" "Apple"
[3,] "-0.189676786790332" "Apple"
[4,] "3.24066540150374" "Apple"
[5,] "0.38272967770064" "Apple"
[6,] "0.107349391835546" "Apple"
""
で囲われているのは、文字列を意味しています。つまり、行列では二つのデータが混在できず数値型は文字型に変換されたということです。
sum(mat2[,"X"])
df2 <- data.frame(X=x2,Y=y2)
head(df2)
sum(df2$X)
[1] 9.055403
より大きく複雑な形式のデータになると、データフレームの柔軟性が必要になることもあります。必要に応じて使い分けてください。
- Rは自己内省型(introspective)で、あなたは誰ですかと尋ねれば親切に答えてくれます
class(df)
[1] "data.frame"
dim(df)
[1] 1000 2
colnames(df)
[1] "X" "Y"
これらの関数を用いて、列名を変更することも可能です。
df3 <- df
colnames(df3) <- c("A","B")
head(df3)
散布図を描くと線形モデルを当てはめたくなるでしょう(線形回帰)。 - Rではformula
を用いて、変数間の関係を表します。 - 変数は、二番目の入力(引数)に指定します。
fit <- lm(Y ~ X, df)
点を可視化して、回帰の直線を表示します
plot(Y ~ X, df)
abline(fit,col="red", lwd=3)
fitをANOVA(分散分析)形式に要約します。
anova(fit)
Analysis of Variance Table
Response: Y
Df Sum Sq Mean Sq F value Pr(>F)
X 1 1022.61 1022.61 1086 < 2.2e-16 ***
Residuals 998 939.76 0.94
---
Signif. codes: 0 ‘***’ 0.001 ‘**’ 0.01 ‘*’ 0.05 ‘.’ 0.1 ‘ ’ 1
ところで、fitは何者でしょうか。
class(fit)
[1] "lm"
また、fitに対して用いることのできる関数には何があるでしょうか。
methods(class=class(fit))
[1] add1 alias anova case.names coerce confint cooks.distance deviance dfbeta dfbetas drop1
[12] dummy.coef effects extractAIC family formula hatvalues influence initialize kappa labels logLik
[23] model.frame model.matrix nobs plot predict print proj qr residuals rstandard rstudent
[34] show simulate slotsFromS3 summary variable.names vcov
see '?methods' for accessing help and source code
データの読み込みと書き込み
実際のデータ解析では外部からデータを読み込んだり、書き込んで保存するということが頻繁にあります。 Rでの基本的な入出力関数はread.table()
とwrite.table()
です。
まず、練習準備のためデータを書き出してみましょう。x =
は書き出すデータ、file =
は書き出す先のファイル名(パスを指定しなければ、現在のディレクトリに保存)、row.names =
とcol.names =
は行あるいは列の名前を書きだすか否か、sep =
は区切り文字でタブ区切りにする場合は"\t"
, コンマ区切りにする場合は","
を指定。
write.table(x = df,file = "test.txt",row.names = F,col.names = T, sep = "\t")
現在のディレクトリに本当にファイルが作られたかを確かめてみましょう。
any(list.files() == "test.txt")
[1] TRUE
次に、先ほど書き出したデータを読み込みます。file =
は読み込むファイル名(パスを指定しなければ、現在のディレクトリに保存)、header =
は先頭行を列名として用いるか否か、sep =
は区切り文字、row.names =
はデータに行の名前が含まれている場合には、何列目に行の名前があるのかを数値で指定します。今回は、行に名前を付けていない場合はこのオプションは無視してもよい(例示のため、対応する列はないことを表すNULLとしました)
input_base <- read.table(file = "test.txt",header = TRUE,sep = "\t",row.names = NULL)
read.tableで読み込むと、自動的にデータフレームとして読み込まれます。
class(input_base)
[1] "data.frame"
ところで、大規模データを読み込むには、これらの二つの関数では、読み込みと書き出しに膨大な時間を要します。read.table()
とwrite.table()
は~数十MBくらいのデータならストレスなく読み込めますが、それ以上か数GB~十数GBのデータを読み込む場合には、覚悟が必要になります。近年、二つの超高速の入出力パッケージdata.table
とreadr
が開発され、実際の解析の場面では欠かせないツールとなっています。data.table
では高速に読み込むfread()
関数があり、readr
では高速に書き出すwrite_tsv()
関数があります。fread()
関数は筆者のベンチマークでread.table()
関数のおよそ50倍、write_tsv()
関数はwrite_table()
関数の6倍程度の速度まで向上しました。
write.tsv()
関数を使ってみましょう。x =
はデータ、path =
にはファイル名、col_names =
は、列名を書き出すか否かです。
library(readr)
write_tsv(x = df,path = "test2.txt",col_names = TRUE)
any(list.files() == "test2.txt")
[1] TRUE
次にfread()
関数を使ってみましょう。
library(data.table)
input_fread <- fread(input = "test2.txt",header = TRUE,sep = "\t")
data.tableにより読み込まれたデータは、特殊なクラスになっています。これを通常のデータフレームとして扱いたい場合は、as.data.frame()
関数で、強制的にデータフレーム型に変換します。
class(input_fread)
[1] "data.table" "data.frame"
input_fread_df <- as.data.frame(input_fread)
class(input_fread_df)
[1] "data.frame"
データの読み込みは単純なように見えますが、実はデータ解析の手順において難しい部分の一つで、常にトラブルがつきものです。その理由の一つは、データ形式が単純ではないことが多いからです。間違った値が入っていたり、文字形式(エンコーディング)が原因で文字化けをしていることが原因で読み込みにエラーが起きることもあります。それらは別の方法で綺麗な形に直すことも時には必要です。あるいは、データと同じファイルの最初の20行くらいは説明が書かれていて、それ以降に数値データが入っている場合などは、読み込みの時に、何行目以降を読み込むということをコンピューターに教える必要があります。おそらく、皆さんがデータ解析の現場に出る機会があったとすれば、最初につまずくところはデータの読み込みです。
fread
関数のもう少し高度な使い方を見ておきましょう。 実際にありえるちょっと面倒なデータを作ってみます。ここは、ただ実行して出来たデータがどんなものなか眺めてみてください。全体で、105行10列のデータです。最初の5行では謎の呪文が唱えられており、次の100行には数値データが入っていますが、欠損値を表している(?)と思われる“kessonn”という文字列が入っています。さらに、6行目には、リンゴと桃の違いを調べたかったのか、“Apple”と“Peach”という文字に連番が振られています。解析者を拷問しようとしているとしか思えないデータです。もし、近くに解析者がいたらよくいたわってあげてください。“dirtydata.txt”に保存しました。
x1 <- matrix(rep("hoge",5*10), nrow = 5, ncol = 10)
x2 <- c(paste0("Apple",1:5),paste0("Peach",1:5))
x3 <- matrix(rnorm(100*10),nrow = 100, ncol = 10)
x3[sample(length(x3),100)] <- "kesson"
dirty_data <- rbind(x1,x2,x3)
dirty_data <- as.data.frame(dirty_data)
dirty_data[1:10,]
write_tsv(dirty_data,path = "dirtydata.txt",col_names = FALSE)
さて、まず数値データを読みだそうと思います。つまり、最初の呪文は無視しましょう。 skip =
で何行目までを飛ばして読み込むかを表します。header=F
, skip = 5
とすると最初の5行を飛ばして6行目から読み込みます。
input_fread2 <- as.data.frame(fread("dirtydata.txt",header = FALSE, skip = 5, sep = "\t"))
head(input_fread2)
1列目は列名にしたいので、6行目だけを列名として読み込み、7行目以降を読みましょう。まずは7行目以降を読み込みます。Rでは欠損値はNA
なので、na.strings =
に欠損値を表している文字を指定して、kessonをNAに置き換えることします。
input_fread3 <- as.data.frame(fread("dirtydata.txt",header = FALSE, skip = 6, sep = "\t",na.strings = "kesson"))
head(input_fread3)
次に6行目のみを読み込みます。skip = 5
, nrows = 1
とすれば、5行目までを飛ばして、6行目から1行だけ読み込みます。また、便利のため読み込んだデータをベクトル型に変換しています。
col_names <- fread("dirtydata.txt",header = FALSE, skip = 5, nrows= 1)
col_names <- as.vector(as.matrix(col_names))
col_names
[1] "Apple1" "Apple2" "Apple3" "Apple4" "Apple5" "Peach1" "Peach2" "Peach3" "Peach4" "Peach5"
これを列名に代入します。
colnames(input_fread3) <- col_names
head(input_fread3)
きれいになりました!今回は、行を飛ばす操作をしましたが、列方向でもできます。skip =
は何列目を飛ばすかを指定し、select =
は何列目を選択するかをベクトルで与えます。 Peachだけを選択してみましょう。最初の5列を飛ばしていもいいし、6列名から10列目までを選んでもよいです。まずは最初の5列を飛ばして読み込みます。
input_fread4 <- as.data.frame(fread("dirtydata.txt",header = FALSE, skip = 6, sep = "\t",na.strings = "kesson", drop = 1:5))
colnames(input_fread4) <- col_names[6:10]
head(input_fread4)
同じ結果ですが、6列目から10列目をよみます。
input_fread5 <- as.data.frame(fread("dirtydata.txt",header = FALSE, skip = 6, sep = "\t",na.strings = "kesson", select = 6:10))
colnames(input_fread5) <- col_names[6:10]
head(input_fread5)
LS0tDQp0aXRsZTogIlKT/JblIg0KYXV0aG9yOiAiT3JpZ2luYWxseSBjcmVhdGVkIGJ5IE1hcnRpbiBNb3JnYW4gYW5kIExvcmkgU2hlcGhlcmQgKE1vZGlmaWVkICYgSW50ZXJwcmV0ZWQgYnkgWXVzdWtlIE1BVFNVSSkiDQphZmZpbGlhdGlvbjogIpa8jMOJrpHlineDVoNYg2WDgJC2laiKd5WqluwiDQpkYXRlOiAijcWPSY1YkFaT+oFGMjAxN5RONYyOMTeT+iINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCmBgYHtyLGV2YWw9Rn0NCmhvbWUgPC0gW5R6lXqCtYK9g3SDSIOLg1+C8INSg3OBW4K1gr2DcINYXQ0Kc2V0d2QoaG9tZSkNCmBgYA0KDQojUiBGaXJzdCBJbXByZXNzaW9uDQpSgs2I6o1zgrKCxoLJlr2X34LwifCO34K1gtyCt4FCDQqDUoN9g5ODaIN2g42Dk4N2g2eCyZCUkmyCxo6ugvCT/JfNgrWCxILdgtyCtw0KYGBge3J9DQoxICsgMQ0KYGBgDQoNCg0KlbaOmiiVz5CUKYLJkmyC8IqEguiTloLEgtyCtyiR45P8KQ0KYGBge3J9DQp4IDwtIDENCnggKyB4DQpgYGANCg0KUoLJgqiCr4Lpj4iXnYLNYIrWkJRggvCXcIKigsSNc4KigtyCt4FCitaQlILNlaGQlILMkmyC8JdegqaC6YLGiOqCwoLMg3iDToNng4uBaZJsgsyRZ4FqgvCV1IK1gtyCt4FCDQpgYygpYILNlaGQlILMkmyC8IjqgsKCzIN4g06DZ4OLgsmC3ILGgt+CxJXUgrWC3IK3gUINCg0KYGBge3J9DQp4IDwtIGMoMSwyLDMpDQp4DQpgYGANCg0Kg3iDToNng4uC8JJQiMqCyYK1gsSK1pCUgvCXcIKigumCxoFBjPiXppNJgsmMdo5agsWCq4LcgreBQg0KYHNxcnQoKWCK1pCUgs2VvZX7jaqC8IK1gtyCt4FCDQpgYGB7cn0NCnkgPC0gc3FydCh4KQ0KeQ0KYGBgDQoNCg0KUoLFgs2BQYjqgsKCzJbak0mC8JJCkKyCt4LpgsyCyYFBkeWS74LNgqKCrYLCgsyV+5ZAgqqCoILogtyCt4FCDQoNCmBgYHtyfQ0KeCA8LSBjKDEsIDIsIDMpDQp4DQpgYGANCg0KYGBge3J9DQp4ID0gYygzLCA0LCA1KQ0KeA0KYGBgDQoNCmBgYHtyfQ0KeCA8LSA0OjkNCngNCmBgYA0KDQpSgs2BQZXPgu2CwYK9jVyVtoLFguCDR4OJgVuC8ItOgrGCs4K4gsmOwI1zgsWCq4LEgrWC3IKkj1+T7oLIjL6M6oLFgreBQoK9gr6CtY6egsmCzYFBitSI4YLBgsSCooLEguCOwI1zgsWCq4LpgsyCxYFBko2I04KqlUuXdoLFgreBQpCzgrWCoo1clbaC8JBngsmShYKvgumCzIKqguaCooLFgreBQg0KgrGC8YLIjmeCooKpgr2CzYK1gtyCuYLxgUINCmBgYHtyfQ0KMTA6MTIgLT4geA0KeA0KYGBgDQpgYGB7cn0NCnggPC0gYygxLDIsMykgLT4geQ0KeA0KeQ0KYGBgDQoNCg0KI1KCyYKogq+C6URhdGGMXoFGVmVjdG9yjF6CxoOKg1iDZ4xeDQojI0F0b21pYyCDeINOg2eDiw0KLSCQrpCUgUGQlJJsgWmOwJCUgWqBQZWhkWaQlJJsgUGYX5edkmyBQZW2jpqBQYNvg0ODZw0KYGBge3J9DQpwZW9wbGUgPC0gYygiTG9yaSIsICJZdWJvIiwgIkdyZWciLCAiTml0ZXNoIiwgIlZhbGVyaWUiLCAiSGVydmUiKQ0KcGVvcGxlDQpgYGANCg0KLSBBdG9taWMgdmVjdG9ygs1glryRT2CC8ILCgq+C6YKxgsaCqoLFgquC3IK3gUINCmBgYHtyfQ0KcG9wdWxhdGlvbiA8LSBjKEJ1ZmZhbG89MjU5MDAwLCBSb2NoZXN0ZXI9MjEwMDAwLCBgTmV3IFlvcmtgPTg0MDAwMDApDQpwb3B1bGF0aW9uDQpgYGANCg0KDQpgYGB7cn0NCmxvZzEwKHBvcHVsYXRpb24pDQpgYGANCg0KLSCMh5G5kmyBRmBOQWAoIm5vdCBhdmFpbGFibGUiKQ0KDQpgYGB7cn0NCnRydXRoaW5lc3MgPC0gYyhUUlVFLCBGQUxTRSwgTkEpDQp0cnV0aGluZXNzDQpgYGANCg0KLSCYX5edkYCN7IFGYCZgKGFuZCksIGB8YChvciksIGAhYChub3QpDQpgYGB7cn0NCiF0cnV0aGluZXNzDQpgYGANCg0KYGBge3J9DQp0cnV0aGluZXNzIHwgIXRydXRoaW5lc3MNCmBgYA0KDQpgYGB7cn0NCnRydXRoaW5lc3MgJiAhdHJ1dGhpbmVzcw0KYGBgDQoNCg0KLSCOwJCUgUZgSW5mYCiWs4zAkeUpLCBgTmFOYChub3QtYS1udW1iZXI7IGUuZy4sIDAvMCkNCmBgYHtyfQ0KdW5kZWZpbmVkX251bWVyaWNfdmFsdWVzIDwtIGMoTkEsIDAvMCwgTmFOLCBJbmYsIC1JbmYpDQp1bmRlZmluZWRfbnVtZXJpY192YWx1ZXMNCmBgYA0KDQpgYGB7cn0NCnNxcnQodW5kZWZpbmVkX251bWVyaWNfdmFsdWVzKQ0KYGBgDQoNCi0glbaOmpfxgsmRzoK3gumRgI3sDQoNCmBgYHtyfQ0KdG91cHBlcihwZW9wbGUpDQpgYGANCg0KDQoNCmBgYHtyfQ0Kc3Vic3RyKHBlb3BsZSwxLDMpDQpgYGANCg0KDQoNCmBgYHtyfQ0KZ3N1YigiTCIsIlMiLHBlb3BsZSkNCmBgYA0KDQoNCg0KLSBSgs2DR4NSgsiC4oLCgsiC8YLFgrcoguaC6JK3gqKDeINOg2eDi4LJjYeC7YK5gumCxoKrgsmBQZJagqKDeINOg2eDi4Lwg4qDVINDg06Di4K3gukpDQpgYGB7cn0NCnggPC0gMTozDQp4ICogMiAgICAgICAgICAgICMgJzInICh2ZWN0b3Igb2YgbGVuZ3RoIDEpIHJlY3ljbGVkIHRvIGMoMiwgMiwgMikNCnggKyAyDQpgYGANCg0KUoLFgs2BQZGAjeyC8JP8guqOcYLJgrWCxItMj3GCt4LpgrGCxoLggsWCq4LcgreBQoNSgVuDaIKqiOqNc4LFg1aDk4N2g4uCyY+Rgq+C3IK3gqqBQYq1guqCyIKigqSCv4LJkb2XcIK3gumCxoFBjayXkILiitSI4YKigsyMtIj2gsmC4ILIguiC3IK3gUINCmBgYHtyfQ0Kc3Vic3RyKHRvbG93ZXIocGVvcGxlKSwgMSwgMykNCmBgYA0KDQpgYGB7cn0NCnBvcHVsYXRpb25bcG9wdWxhdGlvbiA8IDEwMDAwMDBdDQpgYGANCg0KIyODioNYg2cNCi0gg4qDWINngs2RvILMg3iDToNng4uC8IKigq2CwoLFguCOnYLCgrGCxoKqgsWCq4LcgreBQoOKg1iDZ5KGgsmVyoLMg4qDWINngvCK3ILfgumCsYLGguCCxYKrgtyCt4FCDQoNCmBgYHtyfQ0KZnJlbmVtaWVzID0gbGlzdCgNCiAgICBmcmllbmRzPWMoIkxhcnJ5IiwgIlJpY2hhcmQiLCAiVml2aWFuIiksDQogICAgZW5lbWllcz1jKCJEaWNrIiwgIk1pa2UiKQ0KKQ0KZnJlbmVtaWVzDQpgYGANCg0KYFtggs2I6oLCgsyDioNYg2eC8I7mguiPb4K1gtyCt4FCYFtbYILNg4qDWINngsyXdpFmgvCO5oLoj2+CtYLcgreBQg0KYGBge3J9DQpmcmVuZW1pZXNbMV0NCmBgYA0KDQpgYGB7cn0NCmZyZW5lbWllc1tjKCJlbmVtaWVzIiwgImZyaWVuZHMiKV0NCmBgYA0KDQoNCmBgYHtyfQ0KZnJlbmVtaWVzW1siZW5lbWllcyJdXQ0KYGBgDQoNCoLcgr2BQYOKg1iDZ4LMimWXdpFmgs1gg4qDWINnlrwklryRT2CC8JdwgqKCxI7mguiPb4K3grGCxoLggsWCq4LcgreBQg0KYGBge3J9DQpmcmVuZW1pZXMkZnJpZW5kcw0KYGBgDQoNCg0KIyOI9o5xjF4oRmFjdG9ycykNCi0glbaOmpfxgsyC5oKkgsiDeINOg2eDi4LFgreCqoFBkIWPgILwlVyCtYLEgqKC3IK3gUINCg0KYGBge3J9DQpzZXggPSBmYWN0b3IoYygiTWFsZSIsICJNYWxlIiwgIkZlbWFsZSIpLA0KICAgICAgICAgICAgIGxldmVscz1jKCJGZW1hbGUiLCAiTWFsZSIsICJIZXJtYXBocm9kaXRlIikpDQpzZXgNCmBgYA0KDQpgYGB7cn0NCnNleCA9PSAiRmVtYWxlIg0KYGBgDQoNCmBgYHtyfQ0KdGFibGUoc2V4KQ0KYGBgDQoNCmBgYHtyfQ0Kc2V4W3NleCA9PSAiRmVtYWxlIl0NCmBgYA0KDQojg06DiYNYgUaNc5fxgsaDZoFbg16DdIOMgVuDgA0KDQqVz5CUgsaVz5CUgs2BQYLmguiNgpN4gsiMYI6ugWmDeINOg2eDi4LFgs2CyIKtgUGCUZfxiMiP44LMlVyMYI6ugWqCxYzdgqKCyYrWmEGQq4Lwjp2Cv4LcgreBQg0KDQpgYGB7cn0NCnggPSBybm9ybSgxMDAwKSAgICAgICAjIDEwMDAgcmFuZG9tIG5vcm1hbCBkZXZpYXRlcw0KeSA9IHggKyBybm9ybSgxMDAwKSAgICMgYW5vdGhlciAxMDAwIGRldmlhdGVzLCBhcyBhIGZ1bmN0aW9uIG9mIHgNCnBsb3QoeSB+IHgpICAgICAgICAgICAjIHJlbGF0aW9uc2hpcCBiZXd0ZWVuIHggYW5kIHkNCmBgYA0KDQqVXIxgjq6CzINmgVuDXoLNjXOX8ShNYXRyaXgpgqCC6YKigs2DZoFbg16DdIOMgVuDgChEYXRhIGZyYW1lKYLGgqKCpIxggsWItYKkgrGCxoKqgsWCq4LpgUINCg0KIyONc5fxDQpgYGB7cn0NCm1hdCA8LSBtYXRyaXgoYyh4LHkpLG5jb2w9MixkaW1uYW1lcz1saXN0KE5VTEwsYygiWCIsIlkiKSkpDQpoZWFkKG1hdCkNCmBgYA0KDQpgYGB7cn0NCnBsb3QoWX5YLG1hdCkNCmBgYA0KDQqC4IK1greC14LEgsyDZoFbg16C8Iypgr2CooLMgsWCoILqgs6BQXZpZXcobWF0KYLGkcWCxILOgUGQVoK1gqKDRYNCg5ODaINFgqqXp4K/j+OCqoLogtyCt4FCg2aBW4NegvCXdpbxgrWCvYKij+qNh4LJgs2BQWBzdW1tYXJ5KG1hdClggsaCtYLcgreBQg0KYGBge3J9DQpzdW1tYXJ5KG1hdCkNCmBgYA0KDQoNCo1zl/GCzJGAjeyCzY3AlVeC8I53kuiCt4LpguaCpILJisiSUILJjXOCpoLcgreBQg0KYGBge3J9DQptYXRbMSwxXSAjIGVsZW1lbnQgb2YgKHJvdyxjb2wpID0gKDEsMSkgaW4gdGhlIG1hdHJpeA0KbWF0W2MoMSwzKSxjKDEsMildICNlbGVtZW50IG9mIChyb3csY29sKSA9ICgxLDEpLCgxLDIpLCgzLDEpLCgzLDIpIGluIHRoZSBtYXRyaXgNCm1hdFsxLF0gIyBmaXJzdCByb3cNCm1hdFsxOjEwLF0gI2ZpcnN0IDEwIHJvd3MNCmBgYA0KDQqP8IyPgsWDdINCg4uDXoOKg5ODT4K3gumCsYLGguCCxYKrgtyCt4FCDQpgYGB7cn0NCmNvbmQgPC0gbWF0WywiWCJdID4gMCAjY29uZGl0aW9uIHdoZXJlICJYIiBpcyBtb3JlIHRoYW4gemVybw0KcG9zaXRpdmV4IDwtIG1hdFtjb25kLF0gI2ZpbHRlcmluZyBjb3JyZXNwb25kaW5nIHRvIHRoZSBjb25kaXRpb24NCmhlYWQobWF0KQ0KYGBgDQqC4ILBgsaVoY5HgsiP8IyPgsWDdINCg4uDXoOKg5ODT4LggsWCq4LcgreBQg0KYGBge3J9DQpjb25kMSA8LSBtYXRbLCJYIl0gPiAwDQpjb25kMiA8LSBtYXRbLCJZIl0gLSBtYXRbLCJYIl1eMiA+IDANCmNvbmQgPC0gY29uZDEgJiBjb25kMg0KY29tcGxleGNvbmQgPC0gbWF0W2NvbmQsXQ0KaGVhZChjb21wbGV4Y29uZCkNCmBgYA0KDQpgYGB7cn0NCnBsb3QoWSB+IFgscG9zaXRpdmV4KQ0KYGBgDQoNCg0KIyODZoFbg16DdIOMgVuDgA0Kg2aBW4Neg3SDjIFbg4CCzY1zl/GCxoKpgsiC6I6XgsSCooLcgreBQorulnuTSYLIjmeCopX7gs2C2YLGgvGCx41zl/GCxpXPgu2C6ILcgrmC8YFCDQpgYGB7cn0NCmRmIDwtICBkYXRhLmZyYW1lKFg9eCxZPXkpDQpoZWFkKGRmKQ0KYGBgDQoNCmBgYHtyfQ0KcGxvdChZflgsZGYpDQpgYGANCg0KYGBge3J9DQpzdW1tYXJ5KGRmKQ0KYGBgDQoNCoNmgVuDXoN0g4yBW4OAgsyXdpFmgsmDQYNOg1qDWIK3gumCyYLNgUGNc5fxgsaTr4K2jneS6JX7lkCIyIpPgsmBQWCDZoFbg16DdIOMgVuDgJa8JJfxlrxggsaCooKklfuWQILgl3CI04KzguqCxIKigtyCt4FCDQpgYGB7cn0NCnBvc2l0aXZleDIgPC0gZGZbZGYkWCA+IDAsIF0NCmhlYWQocG9zaXRpdmV4MikNCmBgYA0KDQpgYGB7cn0NCnBsb3QoWX5YLHBvc2l0aXZleDIpDQpgYGANCg0Kg2aBW4Neg3SDjIFbg4CCqo1zl/GCxojZgsiC6ZNfgs2BQY1zl/GCxYLNl/GCsoLGgsmI2YLIgumI2YLIgumDZoFbg16MXoLNjp2CxILcgrmC8YKqgUGDZoFbg16DdIOMgVuDgILFgs2BQYjZgsiC6YNmgVuDXoxegvCOnYLCgrGCxoKqgsWCq4Lpk1+CxYK3gUINCmBgYHtyfQ0KeDIgPC0gcm5vcm0oMTAwLDAsMSkNCnkyIDwtIHJlcChjKCJBcHBsZSIsIkdyYXBlIiwiQ2hlcnJ5IiwiUGVhY2giKSxlYWNoPTI1KQ0KDQptYXQyIDwtIG1hdHJpeChjKHgyLHkyKSxuY29sPTIsZGltbmFtZXM9bGlzdChOVUxMLGMoIlgiLCJZIikpKQ0KaGVhZChtYXQyKQ0KYGBgDQoNCmAiImCCxYjNgu2C6oLEgqKC6YLMgs2BQZW2jpqX8YLwiNOWoYK1gsSCooLcgreBQoLCgtyC6IFBjXOX8YLFgs2T8YLCgsyDZoFbg16Cqo2sjd2CxYKrgriQlJJsjF6CzZW2jpqMXoLJlc+Kt4KzguqCvYLGgqKCpIKxgsaCxYK3gUINCg0KDQpgYGB7cixldmFsPUZ9DQpzdW0obWF0MlssIlgiXSkNCmBgYA0KDQpgYGB7cn0NCmRmMiA8LSBkYXRhLmZyYW1lKFg9eDIsWT15MikNCmhlYWQoZGYyKQ0KYGBgDQoNCmBgYHtyfQ0Kc3VtKGRmMiRYKQ0KYGBgDQoNCoLmguiR5YKrgq2VoY5HgsiMYI6ugsyDZoFbg16CyYLIgumCxoFBg2aBW4Neg3SDjIFbg4CCzI9fk+6Qq4KqlUuXdoLJgsiC6YKxgsaC4IKgguiC3IK3gUKVS5d2gsmJnoK2gsSOZ4KilaqCr4LEgq2CvoKzgqKBQg0KDQoNCi0gUoLNjqmMyJPgj8iMXihpbnRyb3NwZWN0aXZlKYLFgUGCoILIgr2CzZJOgsWCt4KpgsaQcYLLguqCzpBlkNiCyZOagqaCxIKtguqC3IK3DQpgYGB7cn0NCmNsYXNzKGRmKQ0KZGltKGRmKQ0KY29sbmFtZXMoZGYpDQpgYGANCg0KgrGC6oLngsyK1pCUgvCXcIKigsSBQZfxlryC8JXPjViCt4LpgrGCxoLgicKUXILFgreBQg0KYGBge3J9DQpkZjMgPC0gZGYNCmNvbG5hbWVzKGRmMykgPC0gYygiQSIsIkIiKQ0KaGVhZChkZjMpDQpgYGANCg0KDQqOVZV6kH2C8JVggq2CxpD8jGCDgoNmg4uC8JOWgsSCzYLfgr2CrYLIgumCxYK1guWCpIFpkPyMYInxi0GBaoFCDQotIFKCxYLNYGZvcm11bGFggvCXcIKigsSBQZXPkJSK1ILMitaMV4LwlVyCtYLcgreBQg0KLSCVz5CUgs2BQZPxlNSW2oLMk/yXzSiI+JCUKYLJjneS6IK1gtyCt4FCDQoNCmBgYHtyfQ0KZml0IDwtIGxtKFkgfiBYLCBkZikNCmBgYA0KDQqTX4LwicKOi4m7grWCxIFBifGLQYLMkryQ/ILwlVyOpoK1gtyCtw0KDQpgYGB7cn0NCnBsb3QoWSB+IFgsIGRmKQ0KYWJsaW5lKGZpdCxjb2w9InJlZCIsIGx3ZD0zKQ0KYGBgDQoNCg0KZml0gvBBTk9WQSiVqo5VlaqQzSmMYI6ugsmXdpbxgrWC3IK3gUINCmBgYHtyfQ0KYW5vdmEoZml0KQ0KYGBgDQoNCoLGgrGC64LFgUFmaXSCzYm9jtKCxYK1guWCpIKpgUINCmBgYHtyfQ0KY2xhc3MoZml0KQ0KYGBgDQoNCoLcgr2BQWZpdILJkc6CtYLEl3CCooLpgrGCxoLMgsWCq4LpitaQlILJgs2JvYKqgqCC6YLFgrWC5YKkgqmBQg0KYGBge3J9DQptZXRob2RzKGNsYXNzPWNsYXNzKGZpdCkpDQpgYGANCg0KI4NmgVuDXoLMk8eC3Y2egt2Cxo+RgquNnoLdDQqOwI3bgsyDZoFbg16J8JDNgsWCzYpPlZSCqYLng2aBW4NegvCTx4LdjZ6C8YK+guiBQY+RgquNnoLxgsWV25G2greC6YLGgqKCpIKxgsaCqpVwlMmCyYKgguiC3IK3gUINClKCxYLMiu6We5NJgsiT/I9vl82K1pCUgs1gcmVhZC50YWJsZSgpYILGYHdyaXRlLnRhYmxlKClggsWCt4FCDQoNCoLcgriBQZf7j0uPgJT1gsyCvYLfg2aBW4NegvCPkYKrj2+CtYLEgt2C3IK1guWCpIFCYHggPSBggs2PkYKrj2+Ct4NmgVuDXoFBYGZpbGUgPSBggs2PkYKrj2+Ct5DmgsyDdINAg0ODi5a8gWmDcINYgvCOd5LogrWCyIKvguqCzoFBjLuN3YLMg2aDQoOMg06DZ4OKgsmV25G2gWqBQWByb3cubmFtZXMgPSBggsZgY29sLm5hbWVzID0gYILNjXOCoILpgqKCzZfxgsyWvJFPgvCPkYKrgr6Ct4KplNuCqYFBYHNlcCA9IGCCzYvmkNiC6JW2jpqCxYNeg3WL5pDYguiCyYK3gumP6o2Hgs1gIlx0ImAsIINSg5ODfYvmkNiC6ILJgreC6Y/qjYeCzWAiLCJggvCOd5LogUINCmBgYHtyfQ0Kd3JpdGUudGFibGUoeCA9IGRmLGZpbGUgPSAidGVzdC50eHQiLHJvdy5uYW1lcyA9IEYsY29sLm5hbWVzID0gVCwgc2VwID0gIlx0IikNCmBgYA0KDQqMu43dgsyDZoNCg4yDToNng4qCyZZ7k5aCyYN0g0CDQ4OLgqqN7ILnguqCvYKpgvCKbYKpgt+CxILdgtyCtYLlgqSBQg0KYGBge3J9DQphbnkobGlzdC5maWxlcygpID09ICJ0ZXN0LnR4dCIpDQpgYGANCg0Kjp+CyYFBkOaC2YLHj5GCq49vgrWCvYNmgVuDXoLwk8eC3Y2egt2C3IK3gUJgZmlsZSA9IGCCzZPHgt2NnoLeg3SDQINDg4uWvIFpg3CDWILwjneS6IK1gsiCr4Lqgs6BQYy7jd2CzINmg0KDjINOg2eDioLJlduRtoFqgUFgaGVhZGVyID0gYILNkOaTqo1zgvCX8Za8gsaCtYLEl3CCooLpgqmU24KpgUFgc2VwID0gYILNi+aQ2ILolbaOmoFBYHJvdy5uYW1lcyA9IGCCzYNmgVuDXoLJjXOCzJa8kU+CqorcgtyC6oLEgqKC6Y/qjYeCyYLNgUGJvZfxltqCyY1zgsyWvJFPgqqCoILpgsyCqYLwkJSSbILFjneS6IK1gtyCt4FCjaGJ8YLNgUGNc4LJlryRT4LwlXSCr4LEgqKCyIKij+qNh4LNgrGCzINJg3aDVoOHg5OCzZazjouCtYLEguCC5oKigWmX4Y6mgsyCvYLfgUGRzomegreC6Zfxgs2CyIKigrGCxoLwlVyCt05VTEyCxoK1gtyCtYK9gWoNCmBgYHtyfQ0KaW5wdXRfYmFzZSA8LSByZWFkLnRhYmxlKGZpbGUgPSAidGVzdC50eHQiLGhlYWRlciA9IFRSVUUsc2VwID0gIlx0Iixyb3cubmFtZXMgPSBOVUxMKQ0KYGBgDQoNCg0KcmVhZC50YWJsZYLFk8eC3Y2egt6CxoFBjqmTrpNJgsmDZoFbg16DdIOMgVuDgILGgrWCxJPHgt2NnoLcguqC3IK3gUINCg0KYGBge3J9DQpjbGFzcyhpbnB1dF9iYXNlKQ0KYGBgDQoNCg0KgsaCsYLrgsWBQZHli0uWzYNmgVuDXoLwk8eC3Y2egt6CyYLNgUGCsYLqgueCzJPxgsKCzIrWkJSCxYLNgUGTx4LdjZ6C3YLGj5GCq49vgrWCyZZjkeWCyI6eitSC8Jd2grWC3IK3gUJgcmVhZC50YWJsZSgpYILGYHdyaXRlLnRhYmxlKClggs2BYJCUj1xNQoKtgueCooLMg2aBW4NegsiC54NYg2eDjINYgsiCrZPHgt2NnoLfgtyCt4KqgUGCu4LqiMiP44KpkJRHQn6PXJCUR0KCzINmgVuDXoLwk8eC3Y2egt6P6o2HgsmCzYFBim+M5YKqlUuXdoLJgsiC6ILcgreBQovflE6BQZPxgsKCzJK0jYKRrILMk/yPb5fNg3CDYoNQgVuDV2BkYXRhLnRhYmxlYILGYHJlYWRyYIKqikqUrYKzguqBQY7AjduCzInwkM2CzI/qlsqCxYLNjIeCqYK5gsiCooNjgVuDi4LGgsiCwYLEgqKC3IK3gUJgZGF0YS50YWJsZWCCxYLNjYKRrILJk8eC3Y2egt5gZnJlYWQoKWCK1pCUgqqCoILogUFgcmVhZHJggsWCzY2CkayCyY+RgquPb4K3YHdyaXRlX3RzdigpYIrWkJSCqoKgguiC3IK3gUJgZnJlYWQoKWCK1pCUgs2VTY7SgsyDeIOTg2CDfYFbg06CxWByZWFkLnRhYmxlKClgitaQlILMgqiC5oK7NTCUe4FBYHdyaXRlX3RzdigpYIrWkJSCzWB3cml0ZV90YWJsZSgpYIrWkJSCzDaUe5L2k3iCzJGsk3iC3ILFjPyP44K1gtyCtYK9gUINCg0KDQpgd3JpdGUudHN2KClgitaQlILwjmeCwYLEgt2C3IK1guWCpIFCYHggPSBggs2DZoFbg16BQWBwYXRoID0gYILJgs2DdINAg0ODi5a8gUFgY29sX25hbWVzID0gYILNgUGX8Za8gvCPkYKrj2+Ct4KplNuCqYLFgreBQg0KDQpgYGB7cn0NCmxpYnJhcnkocmVhZHIpDQp3cml0ZV90c3YoeCA9IGRmLHBhdGggPSAidGVzdDIudHh0Iixjb2xfbmFtZXMgPSBUUlVFKQ0KYGBgDQoNCmBgYHtyfQ0KYW55KGxpc3QuZmlsZXMoKSA9PSAidGVzdDIudHh0IikNCmBgYA0KDQqOn4LJYGZyZWFkKClgitaQlILwjmeCwYLEgt2C3IK1guWCpIFCDQpgYGB7cn0NCmxpYnJhcnkoZGF0YS50YWJsZSkNCmlucHV0X2ZyZWFkIDwtIGZyZWFkKGlucHV0ID0gInRlc3QyLnR4dCIsaGVhZGVyID0gVFJVRSxzZXAgPSAiXHQiKQ0KYGBgDQoNCmRhdGEudGFibGWCyYLmguiTx4LdjZ6C3ILqgr2DZoFbg16CzYFBk8GO6oLIg06DiYNYgsmCyILBgsSCooLcgreBQoKxguqC8JLKj+2CzINmgVuDXoN0g4yBW4OAgsaCtYLEiLWCooK9gqKP6o2Hgs2BQWBhcy5kYXRhLmZyYW1lKClgitaQlILFgUGLrZCnk0mCyYNmgVuDXoN0g4yBW4OAjF6CyZXPireCtYLcgreBQg0KYGBge3J9DQpjbGFzcyhpbnB1dF9mcmVhZCkNCmBgYA0KDQpgYGB7cn0NCmlucHV0X2ZyZWFkX2RmIDwtIGFzLmRhdGEuZnJhbWUoaW5wdXRfZnJlYWQpDQpgYGANCg0KYGBge3J9DQpjbGFzcyhpbnB1dF9mcmVhZF9kZikNCmBgYA0KDQqDZoFbg16CzJPHgt2NnoLdgs2SUI+DgsiC5oKkgsmMqYKmgtyCt4KqgUGOwILNg2aBW4NeifCQzYLMjuiPh4LJgqiCooLEk++CtYKilZSVqoLMiOqCwoLFgUGP7YLJg2eDiYN1g4uCqoLCgquC4ILMgsWCt4FCgruCzJedl1KCzIjqgsKCzYFBg2aBW4NejGCOroKqklCPg4LFgs2CyIKigrGCxoKqkb2CooKpgueCxYK3gUKK1IjhgsGCvZJsgqqT/ILBgsSCooK9guiBQZW2jpqMYI6ugWmDR4OTg1KBW4Nmg0KDk4NPgWqCqoy0iPaCxZW2jpqJu4KvgvCCtYLEgqKC6YKxgsaCqoy0iPaCxZPHgt2NnoLdgsmDR4OJgVuCqotOgquC6YKxgsaC4IKgguiC3IK3gUKCu4LqgueCzZXKgsyV+5ZAgsXjWZftgsiMYILJkryCt4KxgsaC4I6egsmCzZVLl3aCxYK3gUKCoILpgqKCzYFBg2aBW4NegsaTr4K2g3SDQINDg4uCzI3Fj4mCzDIwjXOCrYLngqKCzZDglr6Cqo+RgqmC6oLEgqKCxIFBgruC6ojIjX6CyZCUkmyDZoFbg16CqpP8gsGCxIKigumP6o2HgsiCx4LNgUGTx4LdjZ6C3YLMjp6CyYFBib2Nc5baiMiNfoLwk8eC3Y2egt6CxoKigqSCsYLGgvCDUoOTg3ODhYFbg16BW4LJi7OCpoLplUuXdoKqgqCC6ILcgreBQoKogruC54KtgUGKRoKzgvGCqoNmgVuDXonwkM2CzIy7j+qCyY9vgumLQInvgqqCoILBgr2CxoK3guqCzoFBjcWPiYLJgsKC3IK4gq2CxoKxguuCzYNmgVuDXoLMk8eC3Y2egt2CxYK3gUINCg0KYGZyZWFkYIrWkJSCzILggqSPrYK1jYKTeILIjmeCopX7gvCMqYLEgqiCq4LcgrWC5YKkgUINCo7AjduCyYKgguiCpoLpgr+C5YLBgsaWypN8gsiDZoFbg16C8I3sgsGCxILdgtyCt4FCgrGCsYLNgUGCvYK+jsCNc4K1gsSPb5eIgr2DZoFbg16CqoLHgvGCyILggsyCyIKpkq2C34LEgt2CxIKtgr6Cs4KigUKRU5HMgsWBQTEwNY1zMTCX8YLMg2aBW4NegsWCt4FCjcWPiYLMNY1zgsWCzZPkgsyO9JW2gqqPpYKmgueC6oLEgqiC6IFBjp+CzDEwMI1zgsmCzZCUkmyDZoFbg16CqpP8gsGCxIKigtyCt4KqgUGMh5G5kmyC8JVcgrWCxIKigukoPymCxo52gu2C6oLpImtlc3Nvbm4igsaCooKklbaOmpfxgqqT/ILBgsSCooLcgreBQoKzgueCyYFBNo1zltqCyYLNgUGDioOTg1OCxpONgsyI4YKigvCSsoLXgr2CqYLBgr2CzIKpgUEiQXBwbGUigsYiUGVhY2gigsaCooKklbaOmoLJmEGU1IKqkFWC54LqgsSCooLcgreBQonwkM2O0oLwjYmW4oK1guaCpILGgrWCxIKigumCxoK1gqmOdoKmgsiCooNmgVuDXoLFgreBQoLggrWBQYvfgq2CyYnwkM2O0oKqgqKCvYLnguaCrYKigr2C7YLBgsSCoIKwgsSCrYK+grOCooFCImRpcnR5ZGF0YS50eHQigsmV25G2grWC3IK1gr2BQg0KDQpgYGB7cn0NCngxIDwtIG1hdHJpeChyZXAoImhvZ2UiLDUqMTApLCBucm93ID0gNSwgbmNvbCA9IDEwKQ0KeDIgPC0gYyhwYXN0ZTAoIkFwcGxlIiwxOjUpLHBhc3RlMCgiUGVhY2giLDE6NSkpDQp4MyA8LSBtYXRyaXgocm5vcm0oMTAwKjEwKSxucm93ID0gMTAwLCBuY29sID0gMTApDQp4M1tzYW1wbGUobGVuZ3RoKHgzKSwxMDApXSA8LSAia2Vzc29uIg0KZGlydHlfZGF0YSA8LSByYmluZCh4MSx4Mix4MykNCmRpcnR5X2RhdGEgPC0gYXMuZGF0YS5mcmFtZShkaXJ0eV9kYXRhKQ0KZGlydHlfZGF0YVsxOjEwLF0NCndyaXRlX3RzdihkaXJ0eV9kYXRhLHBhdGggPSAiZGlydHlkYXRhLnR4dCIsY29sX25hbWVzID0gRkFMU0UpDQpgYGANCg0KgrOCxIFBgtyCuJCUkmyDZoFbg16C8JPHgt2CvoK7gqSCxo52gqKC3IK3gUKCwoLcguiBQY3Fj4mCzI70lbaCzZazjouCtYLcgrWC5YKkgUINCmBza2lwID0gYILFib2Nc5bagtyCxYLwlPKCzoK1gsSTx4LdjZ6C3oKpgvCVXIK1gtyCt4FCYGhlYWRlcj1GYCwgYHNraXAgPSA1YILGgreC6YLGjcWPiYLMglSNc4LwlPKCzoK1gsSCVY1zltqCqYLnk8eC3Y2egt2C3IK3gUINCg0KYGBge3J9DQppbnB1dF9mcmVhZDIgPC0gYXMuZGF0YS5mcmFtZShmcmVhZCgiZGlydHlkYXRhLnR4dCIsaGVhZGVyID0gRkFMU0UsIHNraXAgPSA1LCBzZXAgPSAiXHQiKSkNCmhlYWQoaW5wdXRfZnJlYWQyKQ0KYGBgDQoNCjGX8Zbags2X8Za8gsmCtYK9gqKCzILFgUE2jXOW2oK+gq+C8JfxlryCxoK1gsSTx4LdjZ6C3YFBglaNc5baiMiNfoLwk8eC3YLcgrWC5YKkgUKC3IK4gs2CVo1zltqIyI1+gvCTx4LdjZ6C3YLcgreBQlKCxYLNjIeRuZJsgs1gTkFggsiCzILFgUFgbmEuc3RyaW5ncyA9YCCCyYyHkbmSbILwlVyCtYLEgqKC6ZW2jpqC8I53kuiCtYLEgUFrZXNzb26C8E5BgsmSdYKrireCpoLpgrGCxoK1gtyCt4FCDQoNCmBgYHtyfQ0KaW5wdXRfZnJlYWQzIDwtIGFzLmRhdGEuZnJhbWUoZnJlYWQoImRpcnR5ZGF0YS50eHQiLGhlYWRlciA9IEZBTFNFLCBza2lwID0gNiwgc2VwID0gIlx0IixuYS5zdHJpbmdzID0gImtlc3NvbiIpKQ0KaGVhZChpbnB1dF9mcmVhZDMpDQpgYGANCg0KDQqOn4LJglWNc5bagsyC3YLwk8eC3Y2egt2C3IK3gUJgc2tpcCA9IDVgLCBgbnJvd3MgPSAxYILGgreC6oLOgUE1jXOW2oLcgsWC8JTygs6CtYLEgUE2jXOW2oKpgucxjXOCvoKvk8eC3Y2egt2C3IK3gUKC3IK9gUGV1peYgsyCvYLfk8eC3Y2egvGCvoNmgVuDXoLwg3iDToNng4uMXoLJlc+Kt4K1gsSCooLcgreBQg0KYGBge3J9DQpjb2xfbmFtZXMgPC0gZnJlYWQoImRpcnR5ZGF0YS50eHQiLGhlYWRlciA9IEZBTFNFLCBza2lwID0gNSwgbnJvd3M9IDEpDQpjb2xfbmFtZXMgPC0gYXMudmVjdG9yKGFzLm1hdHJpeChjb2xfbmFtZXMpKQ0KY29sX25hbWVzDQpgYGANCoKxguqC8JfxlryCyZHjk/yCtYLcgreBQg0KYGBge3J9DQpjb2xuYW1lcyhpbnB1dF9mcmVhZDMpIDwtIGNvbF9uYW1lcw0KaGVhZChpbnB1dF9mcmVhZDMpDQpgYGANCg0KgquC6oKigsmCyILogtyCtYK9gUmNoYnxgs2BQY1zgvCU8oLOgreRgI3sgvCCtYLcgrWCvYKqgUGX8ZX7jPyCxYLggsWCq4LcgreBQmBza2lwID1ggs2JvZfxltqC8JTygs6Ct4KpgvCOd5LogrWBQWBzZWxlY3QgPSBggs2JvZfxltqC8JFJkfCCt4LpgqmC8IN4g06DZ4OLgsWXXoKmgtyCt4FCDQpQZWFjaIK+gq+C8JFJkfCCtYLEgt2C3IK1guWCpIFCjcWPiYLMglSX8YLwlPKCzoK1gsSCooLggqKCooK1gUE2l/GWvIKpgucxMJfxltqC3ILFgvCRSYLxgsWC4ILmgqKCxYK3gUKC3IK4gs2NxY+JgsyCVJfxgvCU8oLOgrWCxJPHgt2NnoLdgtyCt4FCDQoNCmBgYHtyfQ0KaW5wdXRfZnJlYWQ0IDwtIGFzLmRhdGEuZnJhbWUoZnJlYWQoImRpcnR5ZGF0YS50eHQiLGhlYWRlciA9IEZBTFNFLCBza2lwID0gNiwgc2VwID0gIlx0IixuYS5zdHJpbmdzID0gImtlc3NvbiIsIGRyb3AgPSAxOjUpKQ0KY29sbmFtZXMoaW5wdXRfZnJlYWQ0KSA8LSBjb2xfbmFtZXNbNjoxMF0NCmhlYWQoaW5wdXRfZnJlYWQ0KQ0KYGBgDQoNCpOvgraMi4nKgsWCt4KqgUGCVZfxltqCqYLnglCCT5fxltqC8ILmgt2C3IK3gUINCmBgYHtyfQ0KaW5wdXRfZnJlYWQ1IDwtIGFzLmRhdGEuZnJhbWUoZnJlYWQoImRpcnR5ZGF0YS50eHQiLGhlYWRlciA9IEZBTFNFLCBza2lwID0gNiwgc2VwID0gIlx0IixuYS5zdHJpbmdzID0gImtlc3NvbiIsIHNlbGVjdCA9IDY6MTApKQ0KY29sbmFtZXMoaW5wdXRfZnJlYWQ1KSA8LSBjb2xfbmFtZXNbNjoxMF0NCmhlYWQoaW5wdXRfZnJlYWQ1KQ0KYGBgDQoNCiNIZWxwIQ0KjaKCwYK9gueDd4OLg3aC8IypgtyCtYLlgqSBQg0KLSBgcm5vcm0oKWCK1pCUgvCSsoLXgsSC3YLcgreBQg0KYGBge3J9DQo/cm5vcm0NCmBgYA0KLSBgVXNhZ2Vgg1qDToNWg4eDk4LFgs2BQYrWkJSC8ILHgsyC5oKkgsmOZ5dwgreC6YLMgqmCqo+RgqmC6oLEgqKC3IK3gUINCmBgYHtyLGV2YWw9Rn0NCnJub3JtKG4sIG1lYW4gPSAwLCBzZCA9IDEpDQpgYGANCoj4kJQoQXVndW1lbnRzKYLMgqKCrYLCgqmCzYNmg3SDSIOLg2eCxVVzYWdlgsWR45P8grOC6oLEgqKC3IK3gUKWvJFPgUGI+JCUgsyIypJ1gsyPh4LJgUGK1pCUk+CVlILFg32DYoNgg5ODT4K1gtyCt4FCDQoNCi0gYEF1Z3VtZW50YINag06DVoOHg5OCxYLNgUGK1pCUgqqCx4LMguaCpILIiPiQlILwgsaC6YKpgqqQ4Ja+grOC6oLEgqKC3IK3gUINCi0gYFZhbHVlYINag06DVoOHg5OCxYLNgUGK1pCUgsmC5oLoldSCs4LqgumSbILJgsKCooLEkOCWvoKzguqCxIKigtyCt4FCDQotIGBFeGFtcGxlYINag06DVoOHg5OCxYLNgUGOZ4KilfuCzJfhgvCQ4Ja+grWCxIKigtyCt4FCDQotIJG9gq2CzI/qjYeBQY7AkZWCzIrukWKCxoLIgsGCxIKigumTnYx2juiWQILMmF+Vto5Rj8aBQYtaj3CV8Y2QgUGK1phBgrWCvYrWkJSCyILHgqqNxYzjgsmPkYKpguqCxIKigtyCt4FCDQoNCg0K