高阶函数
将函数按照参数和返回值是否是函数进行分类,如下图。
除了 regular function,其他三种函数统称为高阶函数。
在数学上,泛函(functional)是从函数到实数的映射;在 R
中,将接受函数作为参数、返回向量的函数称为泛函。
将处理数据的代码段写入函数的函数参数中,泛函就可以代替循环/迭代 ——减少显式循环的使用是高质量代码的标志之一
新版本的 R base 包含了一些原生的泛函,但不如 purrr
包的泛函更丰富、在语法上更具有内在一致性
Filter(f, x)
keep()
, discard()
筛选
Map(f, …)
map()
依次作用
Find(f, x, right = FALSE, nomatch = NULL)
detect()
给出符合条件的第一个或最后一个元素
Position(f, x, right = FALSE, nomatch = NA_integer_)
detect_index()
给出符合条件的第一个或最后一个元素的index
Reduce(f, x, init, right = FALSE, accumulate = FALSE)
reduce()
串行操作
还有更多,见 cheatsheet
purrr
cheatsheet.pdf
并行操作的映射泛函
map*(.x, .f, ...)
map*()
使用一个向量(包括列表、数据框 )作为输入,并对其每个元素应用.f
,所得结果组合成新向量或列表后返回。...
是 .f()
的可选参数
数据框本质上是一个各列组成的 list
# 求每列平均值
df <- data.table (x = 1 : 10 , y = 11 : 20 )
df %>% map_dbl (sum)
#> x y
#> 55 155
## map 通过 ... 传递 .f 参数的优雅
l <- list (x = 1 : 5 , y = c (1 : 10 , NA ))
# 两种写法等价
l %>% map_dbl (.f = ~ mean (.x, na.rm = TRUE ))
#> x y
#> 3.0 5.5
l %>% map_dbl (mean, na.rm = TRUE )
#> x y
#> 3.0 5.5
trims <- c (0 , 0.1 , 0.2 , 0.5 )
x <- rcauchy (1000 ) # Cauchy distribution
# 两种写法等价
trims %>% map_dbl (mean, x = x)
#> [1] -0.33485780 -0.04766183 -0.01638114 0.02400590
trims %>% map_dbl (~ mean (x, trim = .))
#> [1] -0.33485780 -0.04766183 -0.01638114 0.02400590
map*()
的伪代码示意
simple_map <- function (x, f, ...) {
out <- vector ("list" , length (x))
for (i in seq_along (x)) {
out[[i]] <- f (x[[i]], ...)
}
out
}
map*()
的变体
map()
返回列表
map_lgl()
返回逻辑型 atomic 向量
map_int()
返回整型 atomic 向量
map_dbl()
返回双精度型 atomic 向量
map_chr()
返回字符型 atomic 向量
modify()
获得与输入相同类型的输出 ,如输入和输出均为数据框
map_dfr()
对各元素的运算产生若干个行向量,并把它们粘在一起,返回数据框
map_dfc()
对各元素的运算产生若干个列向量,并把它们粘在一起,返回数据框
imap()
遍历向量及其索引
walk()
,无输出,只是为了执行函数内部某些指令的操作过程(如保存、打印)
map_at(.x, .at, .f, ...)
,
map_if(.x, .p, .f, ...)
,对筛选出的部分元素执行 .f
操作,其他元素不进行任何操作,返回对象的长度与 .x 相同
匿名函数
map()
的第二个参数可以是匿名函数。
models <- mtcars %>%
split (.$ cyl) %>%
map (function (df) lm (mpg ~ wt, data = df))
purrr 给出了一种匿名函数的简写语法:
models <- mtcars %>%
split (.$ cyl) %>%
map (~ lm (mpg ~ wt, data = .)) # ~表明这是一个匿名函数, .(或.x)是代词,指传入的参数
class (models)
#> [1] "list"
#> $`4`
#>
#> Call:
#> lm(formula = mpg ~ wt, data = .)
#>
#> Coefficients:
#> (Intercept) wt
#> 39.571 -5.647
#>
#>
#> $`6`
#>
#> Call:
#> lm(formula = mpg ~ wt, data = .)
#>
#> Coefficients:
#> (Intercept) wt
#> 28.41 -2.78
#>
#>
#> $`8`
#>
#> Call:
#> lm(formula = mpg ~ wt, data = .)
#>
#> Coefficients:
#> (Intercept) wt
#> 23.868 -2.192
# 提取三个模型的 R^2
models %>%
map (summary.lm) %>%
map_dbl (~ .$ r.squared) # ~表明这是一个函数,.代指参数,故返回: 参数$r.squared
#> 4 6 8
#> 0.5086326 0.4645102 0.4229655
# 批量建模及查看结果
mtcars %>%
group_by (cyl) %>%
nest () %>%
mutate (model = map (data, ~ lm (mpg ~ wt, data = .))) %>%
mutate (result = map (model, ~ broom:: tidy (.))) %>%
unnest (result)
#> # A tibble: 6 × 8
#> # Groups: cyl [3]
#> cyl data model term estimate std.error statistic p.value
#> <dbl> <list> <list> <chr> <dbl> <dbl> <dbl> <dbl>
#> 1 6 <tibble [7 × 10]> <lm> (Interce… 28.4 4.18 6.79 1.05e-3
#> 2 6 <tibble [7 × 10]> <lm> wt -2.78 1.33 -2.08 9.18e-2
#> 3 4 <tibble [11 × 10]> <lm> (Interce… 39.6 4.35 9.10 7.77e-6
#> 4 4 <tibble [11 × 10]> <lm> wt -5.65 1.85 -3.05 1.37e-2
#> 5 8 <tibble [14 × 10]> <lm> (Interce… 23.9 3.01 7.94 4.05e-6
#> 6 8 <tibble [14 × 10]> <lm> wt -2.19 0.739 -2.97 1.18e-2
最好不要使用超过一行的匿名函数 。超过一行,最好给出函数名,便于阅读和调试。
提取每个元素的部分成分
map_*()
的第二个参数还可以是字符串(作为
name)和整数(作为索引),表示提取(extract) 每个元素的对应属性或分量的值
该语法可以避免在一连串管道的中间或最后使用[]
或$
符号,破坏语法的统一
models %>%
map (summary.lm) %>%
map_dbl ("r.squared" )
#> 4 6 8
#> 0.5086326 0.4645102 0.4229655
x <- list (list (1 , 2 , 3 ), list (4 , 5 , 6 ), list (7 , 8 , 9 ))
x %>% map_dbl (2 )
#> [1] 2 5 8
两种方式也可以混用,通过 list() 实现嵌套提取的效果
x <- list (
list (x = 1 , y = c (2 )),
list (x = 4 , y = c (5 , 6 )),
list (x = 9 , y = c (9 , 10 , 11 ))
)
map_dbl (x, list ("y" , 1 )) # 提取name为"y"的元素中的第一个元素
#> [1] 2 5 9
map2_*(.x, .y, .f)
map2()
的 .f 参数为二元函数
# 获得均值分别为 5, 10, -3 的几个正态分布
mu <- c (5 , 10 , - 3 )
mu %>%
map (rnorm, n = 5 ) %>%
# rnorm(n, mean=0, sd=1) 产生正态分布
# n=5已经给了rnorm,所以 mu 的元素传给 rnorm 只能从第二个参数开始排,也就是 mean
str ()
#> List of 3
#> $ : num [1:5] 4.11 6.63 5.38 4.94 6.63
#> $ : num [1:5] 10.5 9.83 8.98 10.74 10.74
#> $ : num [1:5] -3.14 -2.66 -2.85 -1.48 -3
# 均值、方差都不同的几个正态分布
sigma <- list (1 , 5 , 10 )
map2 (mu, sigma, rnorm, n = 5 ) %>% str ()
#> List of 3
#> $ : num [1:5] 3.78 3.86 6 2.78 4.11
#> $ : num [1:5] 5.3 15.53 9.25 6.86 7.62
#> $ : num [1:5] -2.586 -24.152 -10.802 0.221 -14.042
pmap_*(.l, .f, ...)
pmap()
可以将一个可迭代对象的列表作为第一个参数,每个可迭代对象的
index
相同的元素作为一组,传递给pmap()
的第二个参数(函数)。
map2()
是pmap()
的特例,map2(x, y, f)
等价于pmap(list(x, y), f)
生成均值、标准差和样本数量都不相同的正态分布:
n <- list (5 , 10 , 100 )
args1 <- list (n, mu, sigma)
pmap (args1, rnorm) %>% str ()
#> List of 3
#> $ : num [1:5] 2.99 4.89 3.88 5.55 4.2
#> $ : num [1:10] 9.08 10.07 7.5 5.4 16.13 ...
#> $ : num [1:100] -7.93 -9.4 -1.54 6.68 3.99 ...
为了让代码更易读,应该为列表中的几个可迭代对象命名,名称分别为rnorm()的参数的名称。这样即使list中各向量的顺序不对,rnorm()也能正确识别 。
args2 <- list (mean = mu, sd = sigma, n = n)
args2 %>%
pmap (rnorm) %>%
str ()
#> List of 3
#> $ : num [1:5] 5.33 6.95 5.39 5.06 7
#> $ : num [1:10] 13.05 1.78 14.14 9.35 14.17 ...
#> $ : num [1:100] -5.03 -15.49 -13.5 -13.06 9.37 ...
也可以将参数保存在数据框中,注意变量名称的匹配
params <- tribble (
~ mean, ~ sd, ~ n,
5 , 1 , 1 ,
10 , 5 , 3 ,
- 3 , 10 , 5
)
params %>%
pmap (rnorm)
#> [[1]]
#> [1] 5.813772
#>
#> [[2]]
#> [1] 12.44169 18.04220 10.18662
#>
#> [[3]]
#> [1] 2.591449 7.795421 -5.283498 9.821111 -2.784204
invoke_map()
多个函数的并行操作
f <- c ("runif" , "rnorm" , "rpois" )
param <- list (
list (min = - 1 , max = 1 ),
list (sd = 5 ),
list (lambda = 10 )
)
invoke_map (f, param, n = 5 ) %>% str ()
#> List of 3
#> $ : num [1:5] 0.649 0.389 0.878 0.724 -0.559
#> $ : num [1:5] -1.23 -3.32 -1.1 -1.01 -9.39
#> $ : int [1:5] 10 9 11 14 12
sim <- tribble (
~ f, ~ params,
"runif" , list (min = - 1 , max = 1 ),
"rnorm" , list (sd = 5 ),
"rpois" , list (lambda = 10 )
)
sim %>% mutate (sim = invoke_map (f, params, n = 10 ))
#> # A tibble: 3 × 3
#> f params sim
#> <chr> <list> <list>
#> 1 runif <named list [2]> <dbl [10]>
#> 2 rnorm <named list [1]> <dbl [10]>
#> 3 rpois <named list [1]> <int [10]>
imap()
遍历索引
imap_*(.x, .f)
, .f 为二元函数,其第一个参数为 .x
元素的值,第二个参数为 .x 元素的名称或 index(这很像 d3.js 中的
(d, i) => ...
)
若 x 的元素具有 names
属性,imap(x, f)
等价于map2(x, names(x), f)
;若
x 的元素没有 names
属性,imap(x, f)
等价于map2(x, seq_along(x), f)
v <- list ("a" = 1 , "b" = 2 , "c" = 3 )
imap (v, ~ paste0 (.y, ": " , .x))
#> $a
#> [1] "a: 1"
#>
#> $b
#> [1] "b: 2"
#>
#> $c
#> [1] "c: 3"
walk*()
walk(.x, .f)
, walk2()
,
pwalk()
, iwalk()
x <- list (1 , "a" , 3 )
x %>% walk (print)
#> [1] 1
#> [1] "a"
#> [1] 3
plots <- mtcars %>%
split (.$ cyl) %>%
map (~ ggplot (., aes (mpg, wt)) +
geom_point ())
fileNames <- str_c (names (plots), ".pdf" )
pwalk (list (fileNames, plots), ggsave, path = getwd () %>% str_c ("/figure/" ))
#> Saving 6 x 3.7 in image
#> Saving 6 x 3.7 in image
#> Saving 6 x 3.7 in image
# ggsave()的第一个参数是要保存的文件名,第二个参数是图形对象,path参数是文件夹路径
walk*()
会隐式返回第一个参数
.x,这使得它们非常适用于管道传输的中间步骤 ,就像不会打断管道的%T>%
一样。
*apply()
泛函族
*apply()
与map*()
对应表
lapply()
map()
sapply()
, vapply()
map_lgl()
/map_int()
/map_dbl()
/map_chr()
mapply()
map2()
apply()函数有一个功能map_*()
无法取代,那就是
matrix %>% apply(1, f)
允许将行作为元素传递给
f()
,而map_*()
在处理数据框时永远将列作为元素传递给
f()
更多内容详见 http://blog.fens.me/r-apply/
apply: 遍历矩阵
apply(X, MARGIN, FUN, ...)
,其中...
为 FUN
的可选参数
参数为矩阵或数组 。要求所有的元素都是同一种数据类型。如果误用于其它类型,如数据框,则会自动转换为矩阵 再处理
MARGIN 表示函数的作用维度,1为对行运算,2为对列运算。
如 apply(X = A, 2, FUN = sum)
,对A矩阵的列求和
lapply: 从列表到列表
lapply(list,function,...)
,对列表、dataframe的每个对象/列分别进行操作,返回一个新列表
sapply 和 vapply: 简化返回值
sapply(list,function,…,simplify=T)
,相比 lapply
的特点在于返回值可以被简化为 vector/matrix
# 意为提取数据框每一列的第一个元素,返回一个由这些元素组成的向量。
# "[["函数的用法为:"["(object, 元素位置)"
sapply (iris, "[[" , 1 )
#> Sepal.Length Sepal.Width Petal.Length Petal.Width Species
#> 5.1 3.5 1.4 0.2 1.0
vapply(x, function, fun.value, ..., USE.NAMES = TRUE)
,相比sapply可以在参数中通过fun.value设置输出形状(向量/矩阵/列表)
vapply (x, mean, FUN.VALUE = double (1 )) # 等价于 map_dbl(x, mean)
vapply()
是 sapply()
的安全升级版,如果不能按照既定模板进行输出,函数就会终止,并产生错误信息。因此,尽量不要使用
sapply()
mapply: 多元函数版
mapply(function, object1, object2, ..., SIMPLIFY = TRUE)
,多参数计算,同样可能简化计算结果导致类型不匹配的错误
mapply(f, x, y, z)
返回
list(f(x[1],y[1],z[1]), f(x[2],y[2],z[2]), ...)
set.seed (1 )
x <- 1 : 10
y <- 5 :- 4
z <- round (runif (10 , - 5 , 5 ))
z
#> [1] -2 -1 1 4 -3 4 4 2 1 -4
#> [1] 5 4 3 4 5 6 7 8 9 10
firstlist <- list (A = matrix (1 : 16 , 4 ), B = matrix (1 : 16 , 2 ))
secondlist <- list (A = matrix (1 : 16 , 4 ), B = matrix (1 : 16 , 8 ))
mapply (identical, firstlist, secondlist) # identical()意为是否严格相等
#> A B
#> TRUE FALSE
tapply: 分组统计
tapply(X, INDEX, function, ..., simplify = TRUE)
INDEX 为用于分组的索引,对 X 按 INDEX 分组操作
d <- data.frame (list (
gender = c ("M" , "M" , "F" , "M" , "F" , "F" ),
age = c (47 , 59 , 21 , 32 , 33 , 24 ),
income = c (55000 , 88000 , 32450 , 76500 , 123000 , 45650 )
))
tapply (d$ income, d$ gender, mean) # income先对gender分组,再求平均值
#> F M
#> 67033.33 73166.67
x <- 1 : 10
t <- round (runif (10 , 1 , 100 ) %% 2 )
t
#> [1] 1 0 1 1 1 0 0 1 1 2
#> 0 1 2
#> 15 30 10
rapply 递归遍历
rapply(object, function, classes = "ANY", deflt = NULL, how = c("unlist", "replace", "list"),...)
lapply()
的深度递归版,只接受list,如果list有子list,则继续遍历运用f处理
并行操作的判断泛函
predicate functional
筛选泛函
keep(.x, .p)
,筛选 .p
参数(一个函数,通常是匿名函数)的返回值为 TRUE 的元素,长度通常比 .x
短
discard(.x, .p)
,筛选 .p 参数的返回值为 FALSE
的元素
detect(.x, .f, ..., dir)
,
返回符合条件的第一个或最后一个元素
detect_index(.x, .f, ..., dir)
,
返回符合条件的第一个或最后一个元素的 index
head_while()
tail_while()
检测泛函
every(.x, .p)
, 计算出第一个 FALSE 时即返回 FALSE
some(.x, .p)
, 计算出第一个 TRUE 时即返回 TRUE
none(.x, .p)
, 是否没有元素通过检测
has_element(.x, .y)
, .x 中是否包含 .y
串行操作的递归、累计泛函
一个可迭代对象(iteratable
object),元素依次两两运算,每次运算得到一个结果,再与下一个元素运算。
递归函数purrr::reduce(.x, .f, ..., .init)
,
reduce2(.x, .y, .f, ..., .init)
直接得到最后的结果
累计函数purrr::accumulate(.x, f, ..., .init)
,
accumulate2(.x, .y, .f, ..., .init)
同时显示中间的步骤 。
# 求交集和并集
vs <- list (
c (1 , 3 , 5 , 6 , 10 ),
c (1 , 2 , 3 , 7 , 8 , 10 ),
c (1 , 2 , 3 , 4 , 8 , 9 , 10 )
)
vs %>% reduce (intersect)
#> [1] 1 3 10
#> [1] 1 3 5 6 10 2 7 8 4 9
# 求连乘积
1 : 10 %>% accumulate (` * ` )
#> [1] 1 2 6 24 120 720 5040 40320 362880
#> [10] 3628800
## config ===============================================
x <- seq (0 , 100 , 5 )
y <- seq (100 , 200 , 5 )
params <- 1 : 5 * 10
## plotting ===============================================
library (plotly)
# 图的框架
p0 <- plot_ly (type = "surface" , showscale = F)
p <- params %>%
# 1. 参数由函数工厂加工成函数list
map (~ function (a, b) 0.02 * a + 0.015 * b - 0.0008 * a * b + 0.0007 * a^ 2 - 0.0002 * b^ 2 + .) %>%
# 2. 函数list作用于x, y, 得到 matrix list
map (~ outer (x, y, .)) %>%
# 3. 所有的 matrix 依次作为截面,叠加到 p0上
reduce (
.f = function (p, m) p %>% add_surface (x = x, y = y, z = ~ m),
.init = p0
)
p
其他泛函
aggregate()
分组计算
aggregate(formula, data, FUN, ...,subset, na.action = na.omit)
# 按cut和color分组,将price作为参数传给mean(),数据来源为diamonds
aggregate (price ~ cut + color, diamonds, mean)
#> cut color price
#> 1 Fair D 4291.061
#> 2 Good D 3405.382
#> 3 Very Good D 3470.467
#> 4 Premium D 3631.293
#> 5 Ideal D 2629.095
#> 6 Fair E 3682.312
#> 7 Good E 3423.644
#> 8 Very Good E 3214.652
#> 9 Premium E 3538.914
#> 10 Ideal E 2597.550
#> 11 Fair F 3827.003
#> 12 Good F 3495.750
#> 13 Very Good F 3778.820
#> 14 Premium F 4324.890
#> 15 Ideal F 3374.939
#> 16 Fair G 4239.255
#> 17 Good G 4123.482
#> 18 Very Good G 3872.754
#> 19 Premium G 4500.742
#> 20 Ideal G 3720.706
#> 21 Fair H 5135.683
#> 22 Good H 4276.255
#> 23 Very Good H 4535.390
#> 24 Premium H 5216.707
#> 25 Ideal H 3889.335
#> 26 Fair I 4685.446
#> 27 Good I 5078.533
#> 28 Very Good I 5255.880
#> 29 Premium I 5946.181
#> 30 Ideal I 4451.970
#> 31 Fair J 4975.655
#> 32 Good J 4574.173
#> 33 Very Good J 5103.513
#> 34 Premium J 6294.592
#> 35 Ideal J 4918.186
# plyr包的each函数,可以使aggregate使用多个函数对数据进行计算
aggregate (price ~ cut, diamonds, plyr:: each (mean, median))
#> cut price.mean price.median
#> 1 Fair 4358.758 3282.000
#> 2 Good 3928.864 3050.500
#> 3 Very Good 3981.760 2648.000
#> 4 Premium 4584.258 3185.000
#> 5 Ideal 3457.542 1810.000
replicate(n, f)
多次执行并横向拼接为一个矩阵
常用于随机数据的生成
set.seed (1014 )
df <-
replicate (5 , sample (c (1 : 10 , - 99 ), 6 , replace = TRUE )) %>% # 5轮抽样形成矩阵. replicate(n, f), 即重复运行f函数n次
data.table () # 建立数据框
LS0tDQp0aXRsZTogIkZ1bmN0aW9uYWwiDQpzdWJ0aXRsZTogJycNCmF1dGhvcjogIkh1bW9vbiINCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCINCm91dHB1dDogaHRtbF9kb2N1bWVudA0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlID0gRkFMU0V9DQpzb3VyY2UoIi4uL1JtYXJrZG93bi10ZW1wbGF0ZS9SbWFya2Rvd25fY29uZmlnLlIiKQ0KDQojIyBnbG9iYWwgb3B0aW9ucyA9PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KA0KICB3aWR0aCA9IGNvbmZpZyR3aWR0aCwNCiAgZmlnLndpZHRoID0gY29uZmlnJGZpZy53aWR0aCwNCiAgZmlnLmFzcCA9IGNvbmZpZyRmaWcuYXNwLA0KICBvdXQud2lkdGggPSBjb25maWckb3V0LndpZHRoLA0KICBmaWcuYWxpZ24gPSBjb25maWckZmlnLmFsaWduLA0KICBmaWcucGF0aCA9IGNvbmZpZyRmaWcucGF0aCwNCiAgZmlnLnNob3cgPSBjb25maWckZmlnLnNob3csDQogIHdhcm4gPSBjb25maWckd2FybiwNCiAgd2FybmluZyA9IGNvbmZpZyR3YXJuaW5nLA0KICBtZXNzYWdlID0gY29uZmlnJG1lc3NhZ2UsDQogIGVjaG8gPSBjb25maWckZWNobywNCiAgZXZhbCA9IGNvbmZpZyRldmFsLA0KICB0aWR5ID0gY29uZmlnJHRpZHksDQogIGNvbW1lbnQgPSBjb25maWckY29tbWVudCwNCiAgY29sbGFwc2UgPSBjb25maWckY29sbGFwc2UsDQogIGNhY2hlID0gY29uZmlnJGNhY2hlLA0KICBjYWNoZS5jb21tZW50cyA9IGNvbmZpZyRjYWNoZS5jb21tZW50cywNCiAgYXV0b2RlcCA9IGNvbmZpZyRhdXRvZGVwDQopDQpgYGANCg0KIyMg6auY6Zi25Ye95pWwDQoNCuWwhuWHveaVsOaMieeFp+WPguaVsOWSjOi/lOWbnuWAvOaYr+WQpuaYr+WHveaVsOi/m+ihjOWIhuexu++8jOWmguS4i+WbvuOAgg0KDQrpmaTkuoYgcmVndWxhciBmdW5jdGlvbu+8jOWFtuS7luS4ieenjeWHveaVsOe7n+ensOS4uumrmOmYtuWHveaVsOOAgg0KDQohW10oaHR0cHM6Ly9kMzN3dWJyZmtpMGw2OC5jbG91ZGZyb250Lm5ldC8xZGZmODE5ZTc0M2YyODBiYmFiMWM1NWY4ZjA2M2U2MGI2YTBkMmZiLzIyNjllL2RpYWdyYW1zL2ZwLnBuZykNCg0K5Zyo5pWw5a2m5LiK77yM5rOb5Ye977yIZnVuY3Rpb25hbO+8ieaYr+S7juWHveaVsOWIsOWunuaVsOeahOaYoOWwhO+8m+WcqCBSIOS4re+8jOWwhuaOpeWPl+WHveaVsOS9nOS4uuWPguaVsOOAgei/lOWbnuWQkemHj+eahOWHveaVsOensOS4uuazm+WHveOAgg0KDQrlsIblpITnkIbmlbDmja7nmoTku6PnoIHmrrXlhpnlhaXlh73mlbDnmoTlh73mlbDlj4LmlbDkuK3vvIwqKuazm+WHveWwseWPr+S7peS7o+abv+W+queOry/ov63ku6MqKlte5rOb5Ye95LiO6L+t5LujXS0tLS0tLeWHj+WwkeaYvuW8j+W+queOr+eahOS9v+eUqOaYr+mrmOi0qOmHj+S7o+eggeeahOagh+W/l+S5i+S4gA0KDQpbXuazm+WHveS4jui/reS7o106IOS4gOS6m+aVsOWtpuS4iueahOazm+WHve+8jOWmguaxguaegemZkOOAgeaxguagueOAgeWumuenr+WIhu+8jOWunueOsOeul+azleS4remDveWMheWQq+i/reS7o+OAgg0KDQrmlrDniYjmnKznmoQgUiBiYXNlIOWMheWQq+S6huS4gOS6m+WOn+eUn+eahOazm+WHve+8jOS9huS4jeWmgiBwdXJyciDljIXnmoTms5vlh73mm7TkuLDlr4zjgIHlnKjor63ms5XkuIrmm7TlhbfmnInlhoXlnKjkuIDoh7TmgKcNCg0KfCBgUiBiYXNlYCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgYHB1cnJyOjpgICAgICAgICAgICAgICAgfCDlip/og70gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnwtLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18LS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwNCnwgYEZpbHRlcihmLCB4KWAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IGBrZWVwKClgLCBgZGlzY2FyZCgpYCAgIHwg562b6YCJICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8IGBNYXAoZiwg4oCmKWAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IGBtYXAoKWAgICAgICAgICAgICAgICAgIHwg5L6d5qyh5L2c55SoICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnwgYEZpbmQoZiwgeCwgcmlnaHQgPSBGQUxTRSwgbm9tYXRjaCA9IE5VTEwpYCAgICAgICAgICAgICB8IGBkZXRlY3QoKWAgICAgICAgICAgICAgIHwg57uZ5Ye656ym5ZCI5p2h5Lu255qE56ys5LiA5Liq5oiW5pyA5ZCO5LiA5Liq5YWD57SgICAgICAgICB8DQp8IGBQb3NpdGlvbihmLCB4LCByaWdodCA9IEZBTFNFLCBub21hdGNoID0gTkFfaW50ZWdlcl8pYCAgfCBgZGV0ZWN0X2luZGV4KClgICAgICAgICB8IOe7meWHuuespuWQiOadoeS7tueahOesrOS4gOS4quaIluacgOWQjuS4gOS4quWFg+e0oOeahGluZGV4IHwNCnwgYFJlZHVjZShmLCB4LCBpbml0LCByaWdodCA9IEZBTFNFLCBhY2N1bXVsYXRlID0gRkFMU0UpYCB8IGByZWR1Y2UoKWAgICAgICAgICAgICAgIHwg5Liy6KGM5pON5L2cICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwNCnwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8IOi/mOacieabtOWkmu+8jOingSBjaGVhdHNoZWV0IHwgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfA0KDQo8YSBocmVmPSIuLi9wZGYvY2hlYXRzaGVldC1wdXJyci5wZGYiPjxzdHJvbmc+cHVycnIgY2hlYXRzaGVldC5wZGY8L3N0cm9uZz48L2E+DQoNCjxvYmplY3QgZGF0YT0iLi4vcGRmL2NoZWF0c2hlZXQtcHVycnIucGRmIiB0eXBlPSJhcHBsaWNhdGlvbi9wZGYiIHdpZHRoPSIxMDAlIiBoZWlnaHQ9IjEwMCUiPg0KDQo8L29iamVjdD4NCg0KIyMg5bm26KGM5pON5L2c55qE5pig5bCE5rOb5Ye9DQoNCiMjIyBgbWFwKigueCwgLmYsIC4uLilgDQoNCmBtYXAqKClg5L2/55So5LiA5Liq5ZCR6YeP77yI5YyF5ous5YiX6KGo44CBKirmlbDmja7moYYqKu+8ieS9nOS4uui+k+WFpe+8jOW5tuWvueWFtuavj+S4quWFg+e0oOW6lOeUqGAuZmDvvIzmiYDlvpfnu5Pmnpznu4TlkIjmiJDmlrDlkJHph4/miJbliJfooajlkI7ov5Tlm57jgIIqKmAuLi5gIOaYryBgLmYoKWAg55qE5Y+v6YCJ5Y+C5pWwW14uLi5dKioNCg0KPiDmlbDmja7moYbmnKzotKjkuIrmmK/kuIDkuKrlkITliJfnu4TmiJDnmoQgbGlzdA0KDQpbXi4uLl06IOS5i+aJgOS7pSBgbWFwKigpYCDnmoTlj4LmlbDorr7orqHkuLogLngsIC5mIOi/meagt+eahOW9ouW8j++8jOaYr+S4uuS6humBv+WFjeS4jiAuZigpIOeahOWPguaVsCB4LCBmIOS6p+eUn+WGsueqgeOAguWQjOeQhu+8jGAqYXBwbHkoKWAg5L2/55So5LqGIEZVTiDov5nmoLfnmoTlj4LmlbDlvaLlvI/ku6Xpgb/lhY3lhrLnqoHjgIINCg0KYGBge3J9DQojIOaxguavj+WIl+W5s+Wdh+WAvA0KZGYgPC0gZGF0YS50YWJsZSh4ID0gMToxMCwgeSA9IDExOjIwKQ0KZGYgJT4lIG1hcF9kYmwoc3VtKQ0KDQoNCiMjIG1hcCDpgJrov4cgLi4uIOS8oOmAkiAuZiDlj4LmlbDnmoTkvJjpm4UNCmwgPC0gbGlzdCh4ID0gMTo1LCB5ID0gYygxOjEwLCBOQSkpDQoNCiMg5Lik56eN5YaZ5rOV562J5Lu3DQpsICU+JSBtYXBfZGJsKC5mID0gfiBtZWFuKC54LCBuYS5ybSA9IFRSVUUpKQ0KbCAlPiUgbWFwX2RibChtZWFuLCBuYS5ybSA9IFRSVUUpDQoNCg0KdHJpbXMgPC0gYygwLCAwLjEsIDAuMiwgMC41KQ0KeCA8LSByY2F1Y2h5KDEwMDApICMgQ2F1Y2h5IGRpc3RyaWJ1dGlvbg0KDQojIOS4pOenjeWGmeazleetieS7tw0KdHJpbXMgJT4lIG1hcF9kYmwobWVhbiwgeCA9IHgpDQp0cmltcyAlPiUgbWFwX2RibCh+IG1lYW4oeCwgdHJpbSA9IC4pKQ0KYGBgDQoNCiMjIyMgYG1hcCooKWDnmoTkvKrku6PnoIHnpLrmhI8NCg0KYGBge3IsIGV2YWw9RkFMU0V9DQpzaW1wbGVfbWFwIDwtIGZ1bmN0aW9uKHgsIGYsIC4uLikgew0KICBvdXQgPC0gdmVjdG9yKCJsaXN0IiwgbGVuZ3RoKHgpKQ0KICBmb3IgKGkgaW4gc2VxX2Fsb25nKHgpKSB7DQogICAgb3V0W1tpXV0gPC0gZih4W1tpXV0sIC4uLikNCiAgfQ0KICBvdXQNCn0NCmBgYA0KDQojIyMjIGBtYXAqKClg55qE5Y+Y5L2TDQoNCi0gICBgbWFwKClgIOi/lOWbnuWIl+ihqA0KLSAgIGBtYXBfbGdsKClgIOi/lOWbnumAu+i+keWeiyBhdG9taWMg5ZCR6YePDQotICAgYG1hcF9pbnQoKWAg6L+U5Zue5pW05Z6LIGF0b21pYyDlkJHph48NCi0gICBgbWFwX2RibCgpYCDov5Tlm57lj4znsr7luqblnosgYXRvbWljIOWQkemHjw0KLSAgIGBtYXBfY2hyKClgIOi/lOWbnuWtl+espuWeiyBhdG9taWMg5ZCR6YePDQotICAgYG1vZGlmeSgpYCDojrflvpcqKuS4jui+k+WFpeebuOWQjOexu+Wei+eahOi+k+WHuioq77yM5aaC6L6T5YWl5ZKM6L6T5Ye65Z2H5Li65pWw5o2u5qGGDQotICAgYG1hcF9kZnIoKWAg5a+55ZCE5YWD57Sg55qE6L+Q566X5Lqn55Sf6Iul5bmy5Liq6KGM5ZCR6YeP77yM5bm25oqK5a6D5Lus57KY5Zyo5LiA6LW377yM6L+U5Zue5pWw5o2u5qGGDQotICAgYG1hcF9kZmMoKWAg5a+55ZCE5YWD57Sg55qE6L+Q566X5Lqn55Sf6Iul5bmy5Liq5YiX5ZCR6YeP77yM5bm25oqK5a6D5Lus57KY5Zyo5LiA6LW377yM6L+U5Zue5pWw5o2u5qGGDQotICAgYGltYXAoKWAg6YGN5Y6G5ZCR6YeP5Y+K5YW257Si5byVDQotICAgYHdhbGsoKWDvvIzml6DovpPlh7rvvIzlj6rmmK/kuLrkuobmiafooYzlh73mlbDlhoXpg6jmn5DkupvmjIfku6TnmoTmk43kvZzov4fnqIvvvIjlpoLkv53lrZjjgIHmiZPljbDvvIkNCi0gICBgbWFwX2F0KC54LCAuYXQsIC5mLCAuLi4pYCwgYG1hcF9pZigueCwgLnAsIC5mLCAuLi4pYO+8jOWvueetm+mAieWHuueahOmDqOWIhuWFg+e0oOaJp+ihjCAuZiDmk43kvZzvvIzlhbbku5blhYPntKDkuI3ov5vooYzku7vkvZXmk43kvZzvvIzov5Tlm57lr7nosaHnmoTplb/luqbkuI4gLngg55u45ZCMDQoNCiMjIyMg5Yy/5ZCN5Ye95pWwDQoNCmBtYXAoKWAg55qE56ys5LqM5Liq5Y+C5pWw5Y+v5Lul5piv5Yy/5ZCN5Ye95pWw44CCDQoNCmBgYHtyfQ0KbW9kZWxzIDwtIG10Y2FycyAlPiUNCiAgc3BsaXQoLiRjeWwpICU+JQ0KICBtYXAoZnVuY3Rpb24oZGYpIGxtKG1wZyB+IHd0LCBkYXRhID0gZGYpKQ0KYGBgDQoNCnB1cnJyIOe7meWHuuS6huS4gOenjeWMv+WQjeWHveaVsOeahOeugOWGmeivreazle+8mg0KDQohW10oaW1nL0Z1bmN0aW9uLVNob3J0Y3V0cy5wbmcpDQoNCmBgYHtyfQ0KbW9kZWxzIDwtIG10Y2FycyAlPiUNCiAgc3BsaXQoLiRjeWwpICU+JQ0KICBtYXAofiBsbShtcGcgfiB3dCwgZGF0YSA9IC4pKSAjIH7ooajmmI7ov5nmmK/kuIDkuKrljL/lkI3lh73mlbAsIC4o5oiWLngp5piv5Luj6K+N77yM5oyH5Lyg5YWl55qE5Y+C5pWwDQoNCmNsYXNzKG1vZGVscykNCg0KbW9kZWxzDQoNCiMg5o+Q5Y+W5LiJ5Liq5qih5Z6L55qEIFJeMg0KbW9kZWxzICU+JQ0KICBtYXAoc3VtbWFyeS5sbSkgJT4lDQogIG1hcF9kYmwofiAuJHIuc3F1YXJlZCkgIyB+6KGo5piO6L+Z5piv5LiA5Liq5Ye95pWw77yMLuS7o+aMh+WPguaVsO+8jOaVhei/lOWbnjog5Y+C5pWwJHIuc3F1YXJlZA0KYGBgDQoNCmBgYHtyfQ0KIyDmibnph4/lu7rmqKHlj4rmn6XnnIvnu5PmnpwNCm10Y2FycyAlPiUNCiAgZ3JvdXBfYnkoY3lsKSAlPiUNCiAgbmVzdCgpICU+JQ0KICBtdXRhdGUobW9kZWwgPSBtYXAoZGF0YSwgfiBsbShtcGcgfiB3dCwgZGF0YSA9IC4pKSkgJT4lDQogIG11dGF0ZShyZXN1bHQgPSBtYXAobW9kZWwsIH4gYnJvb206OnRpZHkoLikpKSAlPiUNCiAgdW5uZXN0KHJlc3VsdCkNCmBgYA0KDQoqKuacgOWlveS4jeimgeS9v+eUqOi2hei/h+S4gOihjOeahOWMv+WQjeWHveaVsCoq44CC6LaF6L+H5LiA6KGM77yM5pyA5aW957uZ5Ye65Ye95pWw5ZCN77yM5L6/5LqO6ZiF6K+75ZKM6LCD6K+V44CCDQoNCiMjIyMg5o+Q5Y+W5q+P5Liq5YWD57Sg55qE6YOo5YiG5oiQ5YiGDQoNCmBtYXBfKigpYOeahOesrOS6jOS4quWPguaVsOi/mOWPr+S7peaYr+Wtl+espuS4su+8iOS9nOS4uiBuYW1l77yJ5ZKM5pW05pWw77yI5L2c5Li657Si5byV77yJ77yM6KGo56S6Kirmj5Dlj5bvvIhleHRyYWN077yJKirmr4/kuKrlhYPntKDnmoTlr7nlupTlsZ7mgKfmiJbliIbph4/nmoTlgLwNCg0K6K+l6K+t5rOV5Y+v5Lul6YG/5YWN5Zyo5LiA6L+e5Liy566h6YGT55qE5Lit6Ze05oiW5pyA5ZCO5L2/55SoYFtdYOaIlmAkYOespuWPt++8jOegtOWdj+ivreazleeahOe7n+S4gA0KDQpgYGB7cn0NCm1vZGVscyAlPiUNCiAgbWFwKHN1bW1hcnkubG0pICU+JQ0KICBtYXBfZGJsKCJyLnNxdWFyZWQiKQ0KDQp4IDwtIGxpc3QobGlzdCgxLCAyLCAzKSwgbGlzdCg0LCA1LCA2KSwgbGlzdCg3LCA4LCA5KSkNCnggJT4lIG1hcF9kYmwoMikNCmBgYA0KDQrkuKTnp43mlrnlvI/kuZ/lj6/ku6Xmt7fnlKjvvIzpgJrov4cgbGlzdCgpIOWunueOsOW1jOWll+aPkOWPlueahOaViOaenA0KYGBge3J9DQp4IDwtIGxpc3QoDQogIGxpc3QoeCA9IDEsIHkgPSBjKDIpKSwNCiAgbGlzdCh4ID0gNCwgeSA9IGMoNSwgNikpLA0KICBsaXN0KHggPSA5LCB5ID0gYyg5LCAxMCwgMTEpKQ0KKQ0KDQptYXBfZGJsKHgsIGxpc3QoInkiLCAxKSkgIyDmj5Dlj5ZuYW1l5Li6Inki55qE5YWD57Sg5Lit55qE56ys5LiA5Liq5YWD57SgDQpgYGANCg0KIyMjIGBtYXAyXyooLngsIC55LCAuZilgDQoNCmBtYXAyKClg55qEIC5mIOWPguaVsOS4uuS6jOWFg+WHveaVsA0KDQpgYGB7cn0NCiMg6I635b6X5Z2H5YC85YiG5Yir5Li6IDUsIDEwLCAtMyDnmoTlh6DkuKrmraPmgIHliIbluIMNCm11IDwtIGMoNSwgMTAsIC0zKQ0KbXUgJT4lDQogIG1hcChybm9ybSwgbiA9IDUpICU+JQ0KICAjIHJub3JtKG4sIG1lYW49MCwgc2Q9MSkg5Lqn55Sf5q2j5oCB5YiG5biDDQogICMgbj015bey57uP57uZ5LqGcm5vcm3vvIzmiYDku6UgbXUg55qE5YWD57Sg5Lyg57uZIHJub3JtIOWPquiDveS7juesrOS6jOS4quWPguaVsOW8gOWni+aOku+8jOS5n+WwseaYryBtZWFuDQogIHN0cigpDQpgYGANCg0KYGBge3J9DQojIOWdh+WAvOOAgeaWueW3rumDveS4jeWQjOeahOWHoOS4quato+aAgeWIhuW4gw0Kc2lnbWEgPC0gbGlzdCgxLCA1LCAxMCkNCm1hcDIobXUsIHNpZ21hLCBybm9ybSwgbiA9IDUpICU+JSBzdHIoKQ0KYGBgDQoNCiMjIyBgcG1hcF8qKC5sLCAuZiwgLi4uKWANCg0KYHBtYXAoKWDlj6/ku6XlsIbkuIDkuKrlj6/ov63ku6Plr7nosaHnmoTliJfooajkvZzkuLrnrKzkuIDkuKrlj4LmlbDvvIzmr4/kuKrlj6/ov63ku6Plr7nosaHnmoQgaW5kZXgg55u45ZCM55qE5YWD57Sg5L2c5Li65LiA57uE77yM5Lyg6YCS57uZYHBtYXAoKWDnmoTnrKzkuozkuKrlj4LmlbDvvIjlh73mlbDvvInjgIINCg0KYG1hcDIoKWDmmK9gcG1hcCgpYOeahOeJueS+i++8jGBtYXAyKHgsIHksIGYpYOetieS7t+S6jmBwbWFwKGxpc3QoeCwgeSksIGYpYA0KDQrnlJ/miJDlnYflgLzjgIHmoIflh4blt67lkozmoLfmnKzmlbDph4/pg73kuI3nm7jlkIznmoTmraPmgIHliIbluIPvvJoNCg0KYGBge3J9DQpuIDwtIGxpc3QoNSwgMTAsIDEwMCkNCmFyZ3MxIDwtIGxpc3QobiwgbXUsIHNpZ21hKQ0KcG1hcChhcmdzMSwgcm5vcm0pICU+JSBzdHIoKQ0KYGBgDQoNCiFbXShpbWcvcG1hcDEucG5nKQ0KDQrkuLrkuoborqnku6PnoIHmm7TmmJPor7vvvIzlupTor6UqKuS4uuWIl+ihqOS4reeahOWHoOS4quWPr+i/reS7o+WvueixoeWRveWQje+8jOWQjeensOWIhuWIq+S4unJub3JtKCnnmoTlj4LmlbDnmoTlkI3np7DjgILov5nmoLfljbPkvb9saXN05Lit5ZCE5ZCR6YeP55qE6aG65bqP5LiN5a+577yMcm5vcm0oKeS5n+iDveato+ehruivhuWIqyoq44CCDQoNCmBgYHtyfQ0KYXJnczIgPC0gbGlzdChtZWFuID0gbXUsIHNkID0gc2lnbWEsIG4gPSBuKQ0KYXJnczIgJT4lDQogIHBtYXAocm5vcm0pICU+JQ0KICBzdHIoKQ0KYGBgDQoNCuS5n+WPr+S7peWwhuWPguaVsOS/neWtmOWcqOaVsOaNruahhuS4re+8jOazqOaEj+WPmOmHj+WQjeensOeahOWMuemFjQ0KDQpgYGB7cn0NCnBhcmFtcyA8LSB0cmliYmxlKA0KICB+bWVhbiwgfnNkLCB+biwNCiAgNSwgMSwgMSwNCiAgMTAsIDUsIDMsDQogIC0zLCAxMCwgNQ0KKQ0KcGFyYW1zICU+JQ0KICBwbWFwKHJub3JtKQ0KYGBgDQoNCiMjIyBgaW52b2tlX21hcCgpYA0KDQrlpJrkuKrlh73mlbDnmoTlubbooYzmk43kvZwNCg0KYGBge3J9DQpmIDwtIGMoInJ1bmlmIiwgInJub3JtIiwgInJwb2lzIikNCnBhcmFtIDwtIGxpc3QoDQogIGxpc3QobWluID0gLTEsIG1heCA9IDEpLA0KICBsaXN0KHNkID0gNSksDQogIGxpc3QobGFtYmRhID0gMTApDQopDQppbnZva2VfbWFwKGYsIHBhcmFtLCBuID0gNSkgJT4lIHN0cigpDQpgYGANCg0KIVtdKGltZy9wbWFwMi5wbmcpDQoNCmBgYHtyfQ0Kc2ltIDwtIHRyaWJibGUoDQogIH5mLCB+cGFyYW1zLA0KICAicnVuaWYiLCBsaXN0KG1pbiA9IC0xLCBtYXggPSAxKSwNCiAgInJub3JtIiwgbGlzdChzZCA9IDUpLA0KICAicnBvaXMiLCBsaXN0KGxhbWJkYSA9IDEwKQ0KKQ0Kc2ltICU+JSBtdXRhdGUoc2ltID0gaW52b2tlX21hcChmLCBwYXJhbXMsIG4gPSAxMCkpDQpgYGANCg0KIyMjIGBpbWFwKClg6YGN5Y6G57Si5byVDQoNCmBpbWFwXyooLngsIC5mKWAsIC5mIOS4uuS6jOWFg+WHveaVsO+8jOWFtuesrOS4gOS4quWPguaVsOS4uiAueCDlhYPntKDnmoTlgLzvvIznrKzkuozkuKrlj4LmlbDkuLogLngg5YWD57Sg55qE5ZCN56ew5oiWIGluZGV477yI6L+Z5b6I5YOPIGQzLmpzIOS4reeahCBgKGQsIGkpID0+IC4uLmDvvIkNCg0K6IulIHgg55qE5YWD57Sg5YW35pyJIG5hbWVzIOWxnuaAp++8jGBpbWFwKHgsIGYpYOetieS7t+S6jmBtYXAyKHgsIG5hbWVzKHgpLCBmKWDvvJvoi6UgeCDnmoTlhYPntKDmsqHmnIkgbmFtZXMg5bGe5oCn77yMYGltYXAoeCwgZilg562J5Lu35LqOYG1hcDIoeCwgc2VxX2Fsb25nKHgpLCBmKWANCg0KYGBge3J9DQp2IDwtIGxpc3QoImEiID0gMSwgImIiID0gMiwgImMiID0gMykNCmltYXAodiwgfiBwYXN0ZTAoLnksICI6ICIsIC54KSkNCmBgYA0KDQojIyMgYHdhbGsqKClgDQoNCmB3YWxrKC54LCAuZilgLCBgd2FsazIoKWAsIGBwd2FsaygpYCwgYGl3YWxrKClgDQoNCmBgYHtyfQ0KeCA8LSBsaXN0KDEsICJhIiwgMykNCnggJT4lIHdhbGsocHJpbnQpDQoNCnBsb3RzIDwtIG10Y2FycyAlPiUNCiAgc3BsaXQoLiRjeWwpICU+JQ0KICBtYXAofiBnZ3Bsb3QoLiwgYWVzKG1wZywgd3QpKSArDQogICAgZ2VvbV9wb2ludCgpKQ0KZmlsZU5hbWVzIDwtIHN0cl9jKG5hbWVzKHBsb3RzKSwgIi5wZGYiKQ0KcHdhbGsobGlzdChmaWxlTmFtZXMsIHBsb3RzKSwgZ2dzYXZlLCBwYXRoID0gZ2V0d2QoKSAlPiUgc3RyX2MoIi9maWd1cmUvIikpDQojIGdnc2F2ZSgp55qE56ys5LiA5Liq5Y+C5pWw5piv6KaB5L+d5a2Y55qE5paH5Lu25ZCN77yM56ys5LqM5Liq5Y+C5pWw5piv5Zu+5b2i5a+56LGh77yMcGF0aOWPguaVsOaYr+aWh+S7tuWkuei3r+W+hA0KYGBgDQoNCmB3YWxrKigpYCDkvJrpmpDlvI/ov5Tlm57nrKzkuIDkuKrlj4LmlbAgLnjvvIzov5nkvb/lvpflroPku6zpnZ7luLjpgILnlKjkuo4qKueuoemBk+S8oOi+k+eahOS4remXtOatpemqpCoq77yM5bCx5YOP5LiN5Lya5omT5pat566h6YGT55qEYCVUPiVg5LiA5qC344CCDQoNCiMjIGAqYXBwbHkoKWDms5vlh73ml48NCg0KIVtdKGltZy9hcHBseUZhbWlseS5wbmcpDQoNCg0KfCBgKmFwcGx5KClgICAgICAgICAgICAgICB8IGBtYXAqKClgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS18DQp8IGBsYXBwbHkoKWAgICAgICAgICAgICAgfCBgbWFwKClgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8IGBzYXBwbHkoKWAsIGB2YXBwbHkoKWAgfCBgbWFwX2xnbCgpYC9gbWFwX2ludCgpYC9gbWFwX2RibCgpYC9gbWFwX2NocigpYCB8DQp8IGBtYXBwbHkoKWAgICAgICAgICAgICAgfCBgbWFwMigpYCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQp8ICAgICAgICAgICAgICAgICAgICAgICAgfCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB8DQoNCjogYCphcHBseSgpYOS4jmBtYXAqKClg5a+55bqU6KGoDQoNCioqYXBwbHkoKeWHveaVsOacieS4gOS4quWKn+iDvWBtYXBfKigpYOaXoOazleWPluS7o++8jOmCo+WwseaYryBgbWF0cml4ICU+JSBhcHBseSgxLCBmKWAg5YWB6K645bCG6KGM5L2c5Li65YWD57Sg5Lyg6YCS57uZIGBmKClg77yM6ICMYG1hcF8qKClg5Zyo5aSE55CG5pWw5o2u5qGG5pe25rC46L+c5bCG5YiX5L2c5Li65YWD57Sg5Lyg6YCS57uZIGBmKClgKioNCg0K5pu05aSa5YaF5a656K+m6KeBIDxodHRwOi8vYmxvZy5mZW5zLm1lL3ItYXBwbHkvPg0KDQojIyMjIGFwcGx5OiDpgY3ljobnn6npmLUNCg0KYGFwcGx5KFgsIE1BUkdJTiwgRlVOLCAuLi4pYO+8jOWFtuS4rWAuLi5g5Li6IEZVTiDnmoTlj6/pgInlj4LmlbANCg0K5Y+C5pWw5Li6Kirnn6npmLXmiJbmlbDnu4QqKuOAguimgeaxguaJgOacieeahOWFg+e0oOmDveaYr+WQjOS4gOenjeaVsOaNruexu+Wei+OAguWmguaenOivr+eUqOS6juWFtuWug+exu+Wei++8jOWmgioq5pWw5o2u5qGG77yM5YiZ5Lya6Ieq5Yqo6L2s5o2i5Li655+p6Zi1Kirlho3lpITnkIYNCg0KTUFSR0lOIOihqOekuuWHveaVsOeahOS9nOeUqOe7tOW6pu+8jDHkuLrlr7nooYzov5DnrpfvvIwy5Li65a+55YiX6L+Q566X44CCDQoNCuWmgiBgYXBwbHkoWCA9IEEsIDIsIEZVTiA9IHN1bSlg77yM5a+5QeefqemYteeahOWIl+axguWSjA0KDQojIyMjIGxhcHBseTog5LuO5YiX6KGo5Yiw5YiX6KGoDQoNCmBsYXBwbHkobGlzdCxmdW5jdGlvbiwuLi4pYO+8jOWvueWIl+ihqOOAgWRhdGFmcmFtZeeahOavj+S4quWvueixoS/liJfliIbliKvov5vooYzmk43kvZzvvIwqKui/lOWbnuS4gOS4quaWsOWIl+ihqCoqDQoNCiMjIyMgc2FwcGx5IOWSjCB2YXBwbHk6IOeugOWMlui/lOWbnuWAvA0KDQpgc2FwcGx5KGxpc3QsZnVuY3Rpb24s4oCmLHNpbXBsaWZ5PVQpYO+8jOebuOavlCBsYXBwbHkg55qE54m554K55Zyo5LqO6L+U5Zue5YC85Y+v5Lul6KKr566A5YyW5Li6IHZlY3Rvci9tYXRyaXgNCg0KLSAgIOm7mOiupCBzaW1wbGlmeSA9IFTvvJrov5Tlm57lgLznmoTnsbvlnovnlLHorqHnrpfnu5PmnpzlhrPlrprvvIzlpoLmnpwgZnVuY3Rpb24g6K6h566X57uT5p6c55qE6ZW/5bqm5Li6Me+8jOWImXNhcHBseeWwhmxpc3TnroDljJbkuLp2ZWN0b3LvvJvlpoLmnpzov5Tlm57nmoTliJfooajkuK3mr4/kuKrlhYPntKDnmoTplb/luqbpg73lpKfkuo4x5LiU6ZW/5bqm55u45ZCM77yM6YKj5LmIc2FwcGx55bCG5YW2566A5YyW5L2N5LiA5Liq55+p6Zi1DQoNCi0gICBzaW1wbGlmeSA9IEbvvJrov5Tlm57lgLznmoTnsbvlnovmmK9saXN077yM5q2k5pe25LiObGFwcGx55a6M5YWo55u45ZCM44CCDQoNCmBgYHtyfQ0KIyDmhI/kuLrmj5Dlj5bmlbDmja7moYbmr4/kuIDliJfnmoTnrKzkuIDkuKrlhYPntKDvvIzov5Tlm57kuIDkuKrnlLHov5nkupvlhYPntKDnu4TmiJDnmoTlkJHph4/jgIINCiMgIltbIuWHveaVsOeahOeUqOazleS4uu+8miJbIihvYmplY3QsIOWFg+e0oOS9jee9rikiDQpzYXBwbHkoaXJpcywgIltbIiwgMSkNCmBgYA0KDQpgdmFwcGx5KHgsIGZ1bmN0aW9uLCBmdW4udmFsdWUsIC4uLiwgVVNFLk5BTUVTID0gVFJVRSlg77yM55u45q+Uc2FwcGx55Y+v5Lul5Zyo5Y+C5pWw5Lit6YCa6L+HZnVuLnZhbHVl6K6+572u6L6T5Ye65b2i54q277yI5ZCR6YePL+efqemYtS/liJfooajvvIkNCg0KYGBge3IsIGV2YWw9RkFMU0V9DQp2YXBwbHkoeCwgbWVhbiwgRlVOLlZBTFVFID0gZG91YmxlKDEpKSAjIOetieS7t+S6jiBtYXBfZGJsKHgsIG1lYW4pDQpgYGANCg0KYHZhcHBseSgpYCDmmK8gYHNhcHBseSgpYCDnmoTlronlhajljYfnuqfniYjvvIzlpoLmnpzkuI3og73mjInnhafml6LlrprmqKHmnb/ov5vooYzovpPlh7rvvIzlh73mlbDlsLHkvJrnu4jmraLvvIzlubbkuqfnlJ/plJnor6/kv6Hmga/jgILlm6DmraTvvIwqKuWwvemHj+S4jeimgeS9v+eUqCBgc2FwcGx5KClgKioNCg0KIyMjIyBtYXBwbHk6IOWkmuWFg+WHveaVsOeJiA0KDQpgbWFwcGx5KGZ1bmN0aW9uLCBvYmplY3QxLCBvYmplY3QyLCAuLi4sIFNJTVBMSUZZID0gVFJVRSlg77yM5aSa5Y+C5pWw6K6h566X77yM5ZCM5qC35Y+v6IO9566A5YyW6K6h566X57uT5p6c5a+86Ie057G75Z6L5LiN5Yy56YWN55qE6ZSZ6K+vDQoNCmBtYXBwbHkoZiwgeCwgeSwgeilg6L+U5ZueIGBsaXN0KGYoeFsxXSx5WzFdLHpbMV0pLCBmKHhbMl0seVsyXSx6WzJdKSwgLi4uKWANCg0KYGBge3J9DQpzZXQuc2VlZCgxKQ0KeCA8LSAxOjEwDQp5IDwtIDU6LTQNCnogPC0gcm91bmQocnVuaWYoMTAsIC01LCA1KSkNCnoNCm1hcHBseShtYXgsIHgsIHksIHopDQpgYGANCg0KYGBge3J9DQpmaXJzdGxpc3QgPC0gbGlzdChBID0gbWF0cml4KDE6MTYsIDQpLCBCID0gbWF0cml4KDE6MTYsIDIpKQ0Kc2Vjb25kbGlzdCA8LSBsaXN0KEEgPSBtYXRyaXgoMToxNiwgNCksIEIgPSBtYXRyaXgoMToxNiwgOCkpDQptYXBwbHkoaWRlbnRpY2FsLCBmaXJzdGxpc3QsIHNlY29uZGxpc3QpICMgaWRlbnRpY2FsKCnmhI/kuLrmmK/lkKbkuKXmoLznm7jnrYkNCmBgYA0KDQojIyMjIHRhcHBseTog5YiG57uE57uf6K6hDQoNCmB0YXBwbHkoWCwgSU5ERVgsIGZ1bmN0aW9uLCAuLi4sIHNpbXBsaWZ5ID0gVFJVRSlgDQoNCklOREVYIOS4uueUqOS6juWIhue7hOeahOe0ouW8le+8jOWvuSBYIOaMiSBJTkRFWCDliIbnu4Tmk43kvZwNCg0KYGBge3J9DQpkIDwtIGRhdGEuZnJhbWUobGlzdCgNCiAgZ2VuZGVyID0gYygiTSIsICJNIiwgIkYiLCAiTSIsICJGIiwgIkYiKSwNCiAgYWdlID0gYyg0NywgNTksIDIxLCAzMiwgMzMsIDI0KSwNCiAgaW5jb21lID0gYyg1NTAwMCwgODgwMDAsIDMyNDUwLCA3NjUwMCwgMTIzMDAwLCA0NTY1MCkNCikpDQp0YXBwbHkoZCRpbmNvbWUsIGQkZ2VuZGVyLCBtZWFuKSAjIGluY29tZeWFiOWvuWdlbmRlcuWIhue7hO+8jOWGjeaxguW5s+Wdh+WAvA0KYGBgDQoNCmBgYHtyfQ0KeCA8LSAxOjEwDQp0IDwtIHJvdW5kKHJ1bmlmKDEwLCAxLCAxMDApICUlIDIpDQp0DQp0YXBwbHkoeCwgdCwgc3VtKQ0KYGBgDQoNCiMjIyMgcmFwcGx5IOmAkuW9kumBjeWOhg0KDQpgcmFwcGx5KG9iamVjdCwgZnVuY3Rpb24sIGNsYXNzZXMgPSAiQU5ZIiwgZGVmbHQgPSBOVUxMLCBob3cgPSBjKCJ1bmxpc3QiLCAicmVwbGFjZSIsICJsaXN0IiksLi4uKWANCg0KYGxhcHBseSgpYCDnmoTmt7HluqbpgJLlvZLniYjvvIzlj6rmjqXlj5dsaXN077yM5aaC5p6cbGlzdOacieWtkGxpc3TvvIzliJnnu6fnu63pgY3ljobov5DnlKhm5aSE55CGDQoNCiMjIyMgZWFwcGx5DQoNCueOr+Wig+epuumXtOmBjeWOhg0KDQojIyDlubbooYzmk43kvZznmoTliKTmlq3ms5vlh70NCg0KcHJlZGljYXRlIGZ1bmN0aW9uYWwNCg0KIyMjIOetm+mAieazm+WHvQ0KDQotICAgYGtlZXAoLngsIC5wKWDvvIznrZvpgIkgLnAg5Y+C5pWw77yI5LiA5Liq5Ye95pWw77yM6YCa5bi45piv5Yy/5ZCN5Ye95pWw77yJ55qE6L+U5Zue5YC85Li6IFRSVUUg55qE5YWD57Sg77yM6ZW/5bqm6YCa5bi45q+UIC54IOefrQ0KLSAgIGBkaXNjYXJkKC54LCAucClg77yM562b6YCJIC5wIOWPguaVsOeahOi/lOWbnuWAvOS4uiBGQUxTRSDnmoTlhYPntKANCi0gICBgZGV0ZWN0KC54LCAuZiwgLi4uLCBkaXIpYCwg6L+U5Zue56ym5ZCI5p2h5Lu255qE56ys5LiA5Liq5oiW5pyA5ZCO5LiA5Liq5YWD57SgDQotICAgYGRldGVjdF9pbmRleCgueCwgLmYsIC4uLiwgZGlyKWAsIOi/lOWbnuespuWQiOadoeS7tueahOesrOS4gOS4quaIluacgOWQjuS4gOS4quWFg+e0oOeahCBpbmRleA0KLSAgIGBoZWFkX3doaWxlKClgDQotICAgYHRhaWxfd2hpbGUoKWANCg0KIyMjIOajgOa1i+azm+WHvQ0KDQotICAgYGV2ZXJ5KC54LCAucClgLCDorqHnrpflh7rnrKzkuIDkuKogRkFMU0Ug5pe25Y2z6L+U5ZueIEZBTFNFDQotICAgYHNvbWUoLngsIC5wKWAsIOiuoeeul+WHuuesrOS4gOS4qiBUUlVFIOaXtuWNs+i/lOWbniBUUlVFDQotICAgYG5vbmUoLngsIC5wKWAsIOaYr+WQpuayoeacieWFg+e0oOmAmui/h+ajgOa1iw0KLSAgIGBoYXNfZWxlbWVudCgueCwgLnkpYCwgLngg5Lit5piv5ZCm5YyF5ZCrIC55DQoNCiMjIOS4suihjOaTjeS9nOeahOmAkuW9kuOAgee0r+iuoeazm+WHvQ0KDQrkuIDkuKrlj6/ov63ku6Plr7nosaEoaXRlcmF0YWJsZSBvYmplY3Qp77yM5YWD57Sg5L6d5qyh5Lik5Lik6L+Q566X77yM5q+P5qyh6L+Q566X5b6X5Yiw5LiA5Liq57uT5p6c77yM5YaN5LiO5LiL5LiA5Liq5YWD57Sg6L+Q566X44CCDQoNCumAkuW9kuWHveaVsGBwdXJycjo6cmVkdWNlKC54LCAuZiwgLi4uLCAuaW5pdClgLCBgcmVkdWNlMigueCwgLnksIC5mLCAuLi4sIC5pbml0KWDnm7TmjqXlvpfliLDmnIDlkI7nmoTnu5PmnpwNCg0K57Sv6K6h5Ye95pWwYHB1cnJyOjphY2N1bXVsYXRlKC54LCBmLCAuLi4sIC5pbml0KWAsIGBhY2N1bXVsYXRlMigueCwgLnksIC5mLCAuLi4sIC5pbml0KWDlkIzml7bmmL7npLrkuK3pl7TnmoTmraXpqqRbXjFdIOOAgg0KDQpbXjFdOiDkvovlpoLvvIzkuIDkuKpsaXN05Lit5YKo5a2Y552A5qC85byP5LiA6Ie055qE5pWw5o2u5qGG77yM55SocmJpbmQoKeWwhuS7luS7rOWQiOW5tuWcqOS4gOi1t++8jHJlZHVjZSgp6L+U5Zue5ZCI5bm255qE5pyA57uI57uT5p6c77yM6ICMYWNjdW11bGF0ZSgp6L+U5Zue5LiA5LiqbGlzdO+8jOavj+S4quWFg+e0oOaYr+WQiOW5tueahOafkOS4gOatpeeahOe7k+aenO+8jOWNs+WJjWvkuKrmlbDmja7moYbnmoTlkIjlubbjgIINCg0KYGBge3J9DQojIOaxguS6pOmbhuWSjOW5tumbhg0KdnMgPC0gbGlzdCgNCiAgYygxLCAzLCA1LCA2LCAxMCksDQogIGMoMSwgMiwgMywgNywgOCwgMTApLA0KICBjKDEsIDIsIDMsIDQsIDgsIDksIDEwKQ0KKQ0KdnMgJT4lIHJlZHVjZShpbnRlcnNlY3QpDQp2cyAlPiUgcmVkdWNlKHVuaW9uKQ0KDQojIOaxgui/nuS5mOenrw0KMToxMCAlPiUgYWNjdW11bGF0ZShgKmApDQpgYGANCg0KYGBge3J9DQojIyBjb25maWcgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCnggPC0gc2VxKDAsIDEwMCwgNSkNCnkgPC0gc2VxKDEwMCwgMjAwLCA1KQ0KcGFyYW1zIDwtIDE6NSAqIDEwDQoNCg0KIyMgcGxvdHRpbmcgPT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT09PT0NCmxpYnJhcnkocGxvdGx5KQ0KDQojIOWbvueahOahhuaetg0KcDAgPC0gcGxvdF9seSh0eXBlID0gInN1cmZhY2UiLCBzaG93c2NhbGUgPSBGKQ0KDQpwIDwtIHBhcmFtcyAlPiUNCiAgIyAxLiDlj4LmlbDnlLHlh73mlbDlt6XljoLliqDlt6XmiJDlh73mlbBsaXN0DQogIG1hcCh+IGZ1bmN0aW9uKGEsIGIpIDAuMDIgKiBhICsgMC4wMTUgKiBiIC0gMC4wMDA4ICogYSAqIGIgKyAwLjAwMDcgKiBhXjIgLSAwLjAwMDIgKiBiXjIgKyAuKSAlPiUNCiAgIyAyLiDlh73mlbBsaXN05L2c55So5LqOeCwgeSwg5b6X5YiwIG1hdHJpeCBsaXN0DQogIG1hcCh+IG91dGVyKHgsIHksIC4pKSAlPiUNCiAgIyAzLiDmiYDmnInnmoQgbWF0cml4IOS+neasoeS9nOS4uuaIqumdou+8jOWPoOWKoOWIsCBwMOS4ig0KICByZWR1Y2UoDQogICAgLmYgPSBmdW5jdGlvbihwLCBtKSBwICU+JSBhZGRfc3VyZmFjZSh4ID0geCwgeSA9IHksIHogPSB+bSksDQogICAgLmluaXQgPSBwMA0KICApDQpwDQpgYGANCg0KIyMg5YW25LuW5rOb5Ye9DQoNCiMjIyBgYWdncmVnYXRlKClg5YiG57uE6K6h566XDQoNCmBhZ2dyZWdhdGUoZm9ybXVsYSwgZGF0YSwgRlVOLCAuLi4sc3Vic2V0LCBuYS5hY3Rpb24gPSBuYS5vbWl0KWANCg0KYGBge3J9DQojIOaMiWN1dOWSjGNvbG9y5YiG57uE77yM5bCGcHJpY2XkvZzkuLrlj4LmlbDkvKDnu5ltZWFuKCnvvIzmlbDmja7mnaXmupDkuLpkaWFtb25kcw0KYWdncmVnYXRlKHByaWNlIH4gY3V0ICsgY29sb3IsIGRpYW1vbmRzLCBtZWFuKQ0KYGBgDQoNCmBgYHtyfQ0KIyBwbHly5YyF55qEZWFjaOWHveaVsO+8jOWPr+S7peS9v2FnZ3JlZ2F0ZeS9v+eUqOWkmuS4quWHveaVsOWvueaVsOaNrui/m+ihjOiuoeeulw0KYWdncmVnYXRlKHByaWNlIH4gY3V0LCBkaWFtb25kcywgcGx5cjo6ZWFjaChtZWFuLCBtZWRpYW4pKQ0KYGBgDQoNCiMjIyBgcmVwbGljYXRlKG4sIGYpYA0KDQrlpJrmrKHmiafooYzlubbmqKrlkJHmi7zmjqXkuLrkuIDkuKrnn6npmLUNCg0K5bi455So5LqO6ZqP5py65pWw5o2u55qE55Sf5oiQDQoNCmBgYHtyfQ0Kc2V0LnNlZWQoMTAxNCkNCmRmIDwtDQogIHJlcGxpY2F0ZSg1LCBzYW1wbGUoYygxOjEwLCAtOTkpLCA2LCByZXBsYWNlID0gVFJVRSkpICU+JSAjIDXova7mir3moLflvaLmiJDnn6npmLUuIHJlcGxpY2F0ZShuLCBmKSwg5Y2z6YeN5aSN6L+Q6KGMZuWHveaVsG7mrKENCiAgZGF0YS50YWJsZSgpICMg5bu656uL5pWw5o2u5qGGDQpgYGANCg==