home <- [配布したフォルダをコピーしたパス]
setwd(home)

R First Impression

Rは一行ごとに命令を解釈します。 コマンドプロンプトに数値と式を入力してみます

1 + 1
[1] 2

文字(変数)に値を割り当てます(代入)

x <- 1
x + x
[1] 2

Rにおける処理は関数を用いて行います。関数は複数の値を与えると一つのベクトル(値の組)を返します。 c()は複数の値を一つのベクトルにまとめて返します。

x <- c(1,2,3)
x
[1] 1 2 3

ベクトルを単位にして関数を用いると、効率的に計算できます。 sqrt()関数は平方根をします。

y <- sqrt(x)
y
[1] 1.000000 1.414214 1.732051

Rでは、一つの目的を達成するのに、大抵はいくつの方法があります。

x <- c(1, 2, 3)
x
[1] 1 2 3
x = c(3, 4, 5)
x
[1] 3 4 5
x <- 4:9
x
[1] 4 5 6 7 8 9

Rは、変わった構文でもエラーを起こさずに実行できてしまう柔軟な言語です。ただし時には、間違っていても実行できるので、注意が必要です。正しい構文を身に着けるのがよいです。 こんな使いかたはしません。

10:12 -> x
x
[1] 10 11 12
x <- c(1,2,3) -> y
x
[1] 1 2 3
y
[1] 1 2 3

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 
  • 欠損値:NA(“not available”)
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.tablereadrが開発され、実際の解析の場面では欠かせないツールとなっています。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)

Help!

困ったらヘルプを見ましょう。 - rnorm()関数を調べてみます。

?rnorm
rnorm(n, mean = 0, sd = 1)

引数(Auguments)のいくつかはデフォルトでUsageで代入されています。名前、引数の位置の順に、関数内部でマッチングします。

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