Group4:
B034020012 謝雨靜
B034020027 陳韻卉
B044012015 王譯苓
M064111025 黃威豪
M064111039 王 欣
行傳所 孟祥瑄


packages = c(
  "dplyr","ggplot2","googleVis","devtools","magrittr","slam","irlba","plotly",
  "arules","arulesViz","Matrix","recommenderlab")
There were 14 warnings (use warnings() to see them)
existing = as.character(installed.packages()[,1])
for(pkg in packages[!(packages %in% existing)]) install.packages(pkg)
rm(list=ls(all=TRUE))
LOAD = TRUE
Sys.setlocale("LC_ALL","C")
library(dplyr)
library(ggplot2)
library(googleVis)
library(Matrix)
library(slam)
library(irlba)
library(plotly)
library(arules)
library(arulesViz)
library(recommenderlab)


A. 顧客產品矩陣

Load data frame and rename

load("data/tf0.rdata")
A = A0; X = X0; Z = Z0; rm(A0,X0,Z0); gc()
           used  (Mb) gc trigger  (Mb) max used  (Mb)
Ncells  2599666 138.9    3886542 207.6  3205452 171.2
Vcells 11331894  86.5   14320023 109.3 11332917  86.5
Z = subset(Z, cust %in% A$cust)
n_distinct(Z$cust)  # 32241 有多少顧客
[1] 32241
n_distinct(Z$prod)  # 23787 有多少產品
[1] 23787

製作顧客產品矩陣其實很快、也很容易

library(Matrix)
library(slam)
cpm = xtabs(~ cust + prod, Z, sparse=T)  # customer product matrix
dim(cpm)             # 32241 23787
[1] 32241 23787
mean(cpm > 0)        # 0.00096799 這個矩陣有資料的密度 
[1] 0.0009674258

顧客產品矩陣通常是一個很稀疏的矩,陣有一些產品沒什麼人買

colSums(cpm) %>% quantile(seq(0,1,0.1))
  0%  10%  20%  30%  40%  50%  60%  70%  80%  90% 100% 
   1    1    2    4    6    8   13   20   35   76 8475 
mean(colSums(cpm) > 10) #被買超過10次的產品 在邏輯運算前面加mean 代表TRUE的比率
[1] 0.4483541

刪去購買次數小於6的產品,然後刪去沒有購買產品的顧客

cpm = cpm[, colSums(cpm) >= 6]      # remove the least frequent products
# cpm = cpm[rowSums(cpm) > 0, ]     # remove non-buying customers
cpm = cpm[, order(-colSums(cpm))]   # order product by frequency
dim(cpm)                            # 32241 23787>14621 剩下14621個產品
[1] 32241 14621
max(cpm)         # 49
[1] 49
mean(cpm > 0)    # 0.0015248
[1] 0.001524785
table(cpm@x) %>% prop.table %>% round(4) %>% head(10)

     1      2      3      4      5      6      7      8      9     10 
0.9256 0.0579 0.0108 0.0032 0.0012 0.0006 0.0003 0.0002 0.0001 0.0001 

請你用一個指令列出被購買最多次的10個產品,和它們被購買的次數。

cpm[,1:10] %>% colSums
4714981010038 4711271000014 4719090900065 4711080010112 4710114128038 4710265849066 4713985863121 
         8475          6119          2444          2249          2178          2017          1976 
4710088410139 4710583996008 4710908131589 
         1869          1840          1679 
# 

■ 在什麼前提之下,我們可以把購買這十個產品的次數當作變數,用來預測顧客在下一期會不會來購買呢?
■ 我們如何把這十個變數,併入顧客資料框呢?
■ 我們可不可以(在什麼前提之下我們可以)直接用cbind()新變數併入顧客資料框呢?
■ 我們期中競賽的資料,符合直接用cbind()併入新變數的條件嗎? 我們要如何確認這一件事呢?

只要有理由相信x對y有預測力,就可以把購買這十個產品的次數當作變數 可以使用cbind()或是merge()把這十個變數,併入顧客資料框。如果customer id排序一樣,可以直接用cbind(),否則要用merge()




# dim(cpm) #確定cpm跟A
# sum( A$cust == rownames(cpm) )  
# 要確認A跟cpm的rownames次序是不是一樣(rownames = cust)
# sum起來會變成32241個TRUE或FALSE
# 如果用尺度縮減的話 
# A = cbind(A , svd$u[ , 1:20])

B. 直接以產品的被購買頻率作為變數

以產品的被購買頻率製作(顧客)變數的時候,cpm在最前邊的(N個)欄位就是變數!

B1. 以(最常被購買的)產品的購買次數對顧客分群
nop= 400  # no. product = no. variables
k = 200   # no. cluster
set.seed(111); kg = kmeans(cpm[,1:nop], k)$cluster
table(kg) %>% as.vector %>% sort
  [1]     1     1     1     1     1     1     1     1     1     1     1     1     1     1     1     1
 [17]     1     1     1     1     1     1     2     2     2     2     2     2     3     3     3     3
 [33]     3     3     3     4     4     4     4     4     4     4     5     5     6     6     6     6
 [49]     7     7     7     8     8     8     9     9     9     9    10    10    10    10    11    11
 [65]    11    11    11    12    13    13    15    15    15    16    16    18    19    20    20    20
 [81]    20    21    22    22    22    24    24    25    25    27    28    28    32    32    35    36
 [97]    39    40    41    42    44    45    46    47    47    48    49    49    50    51    52    53
[113]    56    58    58    61    63    66    67    68    69    69    72    81    85    85    86    87
[129]    90    94    96    97    97   100   100   101   110   111   113   114   116   118   123   123
[145]   126   130   134   136   141   141   142   143   162   165   172   175   178   179   182   182
[161]   184   187   195   210   222   225   228   228   237   239   242   253   254   258   258   266
[177]   268   272   287   293   301   311   325   329   350   351   363   396   407   407   410   418
[193]   432   448   473   523   561  1156  1266 11215
B2. 各群組平均屬性

將分群結果併入顧客資料框(A)

df = A %>% inner_join(data.frame(
  cust = as.integer(rownames(cpm)), 
  kg) )
Joining, by = "cust"
head(df)  # 32241

計算各群組的平均屬性

df = data.frame(
  aggregate(. ~ kg, df[,c(2:7,10)], mean), # averages
  size = as.vector(table(kg)),   # no. customers in the group
  dummy = 2001                   # dummy column for googleViz
  )
head(df)
B3. 互動式泡泡圖
plot( gvisMotionChart(
  subset(df[,c(1,4,5,6,8,2,3,7,9)], 
         size >= 20 & size <= 1000),  # range of group size 
  "kg", "dummy", options=list(width=800, height=600) ) )
B4. 各群組的代表性產品 (Signature Product)
# use global variables: cpm, kg
Sig = function(gx, P=1000, H=10) {
  print(sprintf("Group %d: No. Customers = %d", gx, sum(kg==gx)))
  bx = cpm[,1:P]
  data.frame(n = col_sums(bx[kg==gx,])) %>%      # frequency
    mutate(
      share = round(100*n/col_sums(bx),2),       # %prod sold to this cluster
      conf = round(100*n/sum(kg==gx),2),         # %buy this product, given cluster
      base = round(100*col_sums(bx)/nrow(bx),2), # %buy this product, all cust 
      lift = round(conf/base,1),                 # conf/base  
      name = colnames(bx)                        # name of prod
    ) %>% arrange(desc(lift)) %>% head(H)
  }
Sig(130)
[1] "Group 130: No. Customers = 97"
package 'bindrcpp' was built under R version 3.4.3

■ 在什麼前提之下,我們可以把顧客購買產品的特徵向量當作變數,用來預測顧客在下一期會不會來購買呢?
■ 如果可以的話,我們如何把顧客購買產品的特徵向量,併入顧客資料框呢?
■ 我們可不可以(在什麼前提之下我們可以)直接用cbind()將特徵向量併入顧客資料框呢?
■ 我們期中競賽的資料,符合直接用cbind()併入特徵向量的條件嗎? 我們要如何確認這一件事呢?

只要有理由相信x對y有預測力,就可以把顧客購買產品的特徵向量當作變數 可以使用cbind()或是merge()把顧客購買產品的特徵向量,併入顧客資料框。 如果customer id排序一樣,可以直接用cbind(),否則要用merge() 期中競賽的資料符合直接用cbind(),只要看他的cust排序是否相同就可以確認了。




C. 使用尺度縮減方法抽取顧客(產品)的特徵向量

C1. 巨大尺度縮減 (SVD, Sigular Value Decomposition)
library(irlba)
if(LOAD) {
  load("data/svd2a.rdata")
} else {
  smx = cpm
  smx@x = pmin(smx@x, 2)            # cap at 2, similar to normalization  
  t0 = Sys.time()
  svd = irlba(smx,                  # 對稀鬆矩陣做常態化 要cap at 2才有常態化效果
              nv=400,               # length of feature vector
              maxit=800, work=800)  # nv=400有點大  一般來說不會用那麼大 maxit和work設nv*2 
  print(Sys.time() - t0)            # 1.8795 mins
  save(svd, file = "data/svd2a.rdata")
}
C2. 依特徵向量對顧客分群
set.seed(111); kg = kmeans(svd$u, 200)$cluster
table(kg) %>% as.vector %>% sort
  [1]    1    1    1    1    1    1    1    1    1    1    1    1    1    1    1    1    1    1    1
 [20]    1    1    1    1    1    1    1    1    1    1    1    1    1    1    1    1    1    1    1
 [39]    1    1    1    1    1    1    1    1    1    1    1    1    1    1    1    1    1    1    1
 [58]    2    2    2    2    2    3    4    4    5    7   10   14   30   31   32   36   38   38   39
 [77]   39   40   40   41   44   45   46   47   49   54   59   62   62   69   71   77   79   79   80
 [96]   82   82   84   87   91  101  103  109  110  111  113  117  120  123  127  127  129  132  133
[115]  134  135  136  139  141  143  143  147  147  157  159  159  160  160  160  166  168  169  172
[134]  175  180  181  181  182  183  184  184  188  190  190  193  194  195  196  198  198  200  201
[153]  201  202  202  204  204  204  207  209  209  210  213  214  216  219  219  222  225  233  234
[172]  235  236  237  237  238  239  241  248  248  248  253  256  257  258  259  261  261  264  269
[191]  277  281  285  293  305  411  612  896 1092 8987
C3. 互動式泡泡圖 (Google Motion Chart)
# clustster summary
df = inner_join(A, data.frame(         
  cust = as.integer(rownames(cpm)), kg)) %>% 
  group_by(kg) %>% summarise(
    avg_frequency = mean(f),
    avg_monetary = mean(m),
    avg_revenue_contr = mean(rev),
    group_size = n(),
    avg_recency = mean(r),
    avg_gross_profit = mean(raw)) %>% 
  ungroup %>% 
  mutate(dummy = 2001, kg = sprintf("G%03d",kg)) %>% 
  data.frame
Joining, by = "cust"
# Google Motion Chart
plot( gvisMotionChart(
  subset(df, group_size >= 20 & group_size <= 1200),     
  "kg", "dummy", options=list(width=800, height=600) ) )
C4. 各群組的代表性產品 (Signature Product)
Sig(162)
[1] "Group 162: No. Customers = 87"


D. 購物籃分析 Baskets Analysis

dim(cpm)   # 32241 14621
[1] 32241 14621
D1. 準備資料 (for Association Rule Analysis)關聯規則分析

bx和cpm表達的資訊其實一樣,只是格式不一樣

library(arules)
library(arulesViz) #做visualization用
# bx = subset(Z, prod %in% as.numeric(colnames(cpm)), 
#            select=c("cust","prod"))  # select product items
# bx = Z
# bx = split(bx$prod, bx$tid)          # split by transaction id
# bx = as(bx, "transactions")           # data structure for arules package
# 先從Z裡面選取 
# as.numeric(colnames(cpm)) 被購買超過六次以上的產品的集合 選row
# %in% 集合
# select=c("cust","prod")) 選col
# bx其實就是另一種格式的顧客產品
bx = as(split(Z$prod , Z$tid),'transactions')
D2. Top20 熱賣產品
itemFrequencyPlot(bx, topN=20, type="absolute", cex=0.8)

D3. 關聯規則和Apriori演算法

關聯規則(A => B)

rules = apriori(bx, parameter=list(supp=0.002, conf=0.5)) 
Apriori

Parameter specification:
 confidence minval smax arem  aval originalSupport maxtime support minlen maxlen target   ext
        0.5    0.1    1 none FALSE            TRUE       5   0.002      1     10  rules FALSE

Algorithmic control:
 filter tree heap memopt load sort verbose
    0.1 TRUE TRUE  FALSE TRUE    2    TRUE

Absolute minimum support count: 238 

set item appearances ...[0 item(s)] done [0.01s].
set transactions ...[23787 item(s), 119407 transaction(s)] done [0.20s].
sorting and recoding items ... [569 item(s)] done [0.02s].
creating transaction tree ... done [0.07s].
checking subsets of size 1 2 3 done [0.03s].
writing ... [28 rule(s)] done [0.01s].
creating S4 object  ... done [0.03s].
summary(rules)
set of 28 rules

rule length distribution (lhs + rhs):sizes
 2  3 
17 11 

   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
  2.000   2.000   2.000   2.393   3.000   3.000 

summary of quality measures:
    support           confidence          lift            count      
 Min.   :0.002102   Min.   :0.5000   Min.   : 43.24   Min.   :251.0  
 1st Qu.:0.002387   1st Qu.:0.5398   1st Qu.: 49.77   1st Qu.:285.0  
 Median :0.002826   Median :0.6044   Median : 58.58   Median :337.5  
 Mean   :0.003081   Mean   :0.6336   Mean   : 71.34   Mean   :367.9  
 3rd Qu.:0.003404   3rd Qu.:0.7139   3rd Qu.: 72.40   3rd Qu.:406.5  
 Max.   :0.005854   Max.   :0.8088   Max.   :170.92   Max.   :699.0  

mining info:
 data ntransactions support confidence
   bx        119407   0.002        0.5
# 我要找support>0.005 confidence>0.6的 這些都可以自己調 從高往低調
# 門檻設越低 找到的rule越多
# 找到28個關聯規則
D4. 檢視關聯規則

關聯規則 (A => B):

support, confidence, lift越高越好,代表關聯規則越重要

#看28個rule
options(digits=4)
inspect(rules) 
     lhs                              rhs             support  confidence lift   count
[1]  {4719090790017}               => {4719090790000} 0.002940 0.8088     170.92 351  
[2]  {4719090790000}               => {4719090790017} 0.002940 0.6212     170.92 351  
[3]  {719859796124}                => {719859796117}  0.002102 0.6972     147.61 251  
[4]  {4710011402026}               => {4710011402019} 0.002822 0.6740      90.22 337  
[5]  {4710085120697}               => {4710085120680} 0.003467 0.7753     100.41 414  
[6]  {4710011401142}               => {4710011401128} 0.002203 0.5964      43.55 263  
[7]  {4710085172702}               => {4710085172696} 0.002429 0.5400      62.00 290  
[8]  {4710085172702}               => {4710085120628} 0.002462 0.5475      48.25 294  
[9]  {4710085120710}               => {4710085120703} 0.002914 0.5613      89.84 348  
[10] {4710018004704}               => {4710018004605} 0.002990 0.5360      46.08 357  
[11] {4710011409056}               => {4710011401135} 0.003383 0.5337      68.60 404  
[12] {4710011409056}               => {4710011401128} 0.004430 0.6988      51.04 529  
[13] {4710085120093}               => {4710085120628} 0.003961 0.5267      46.42 473  
[14] {4710011401135}               => {4710011401128} 0.005854 0.7524      54.95 699  
[15] {4710085172696}               => {4710085120628} 0.004355 0.5000      44.06 520  
[16] {4710011405133}               => {4710011401128} 0.005176 0.6588      48.12 618  
[17] {4710011406123}               => {4710011401128} 0.004849 0.5920      43.24 579  
[18] {4710011401135,4710011409056} => {4710011401128} 0.002713 0.8020      58.57 324  
[19] {4710011401128,4710011409056} => {4710011401135} 0.002713 0.6125      78.72 324  
[20] {4710011405133,4710011409056} => {4710011401128} 0.002261 0.7606      55.55 270  
[21] {4710011401128,4710011409056} => {4710011405133} 0.002261 0.5104      64.97 270  
[22] {4710085120093,4710085172696} => {4710085120628} 0.002136 0.5705      50.27 255  
[23] {4710085120093,4710085120628} => {4710085172696} 0.002136 0.5391      61.90 255  
[24] {4710011401135,4710011405133} => {4710011401128} 0.002831 0.7717      56.36 338  
[25] {4710011401128,4710011405133} => {4710011401135} 0.002831 0.5469      70.30 338  
[26] {4710011401135,4710011406123} => {4710011401128} 0.002445 0.8022      58.59 292  
[27] {4710011401128,4710011406123} => {4710011401135} 0.002445 0.5043      64.82 292  
[28] {4710011405133,4710011406123} => {4710011401128} 0.002219 0.7011      51.20 265  
# lhs left hand side
# rhs right hand side
# 我們會希望support高
# 菜籃分析是看不到人的 是看一張一張單
# B基礎機率 就是在所有菜籃裡面 B出現的機率
# confidence/lift = B的基礎機率
# 若support confidence lift都高 就代表這個rule非常重要
# install.packages(
#   "https://cran.r-project.org/bin/windows/contrib/3.5/arulesViz_1.3-1.zip",
#   repos=NULL)
# install.packages("arulesViz_1.3-1.zip", repos=NULL)
# library(plotly)
# plotly_arules(rules,colors=c("red","green"),
#               marker=list(opacity=.6,size=10))
# plotly_arules(rules,method="matrix",
#               shading="lift",
#               colors=c("red", "green"))
# 
D5. 互動圖表顯示

每個點都是一個rule lift是顏色 可以做為架位擺設的參考

plot(rules,colors=c("red","green"),engine="htmlwidget",
     marker=list(opacity=.6,size=8))

#每一點都是一個rule
plot(rules,method="matrix",shading="lift",engine="htmlwidget",
     colors=c("red", "green"))

# 衡的有蠻長的一條 代表有比較多的rule指向right hand side
D6. 篩選產品、互動式關聯圖
r1 = subset(rules, subset = rhs %in% c("4719090790000"))
summary(r1)
set of 1 rules

rule length distribution (lhs + rhs):sizes
2 
1 

   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
      2       2       2       2       2       2 

summary of quality measures:
    support          confidence         lift         count    
 Min.   :0.00294   Min.   :0.809   Min.   :171   Min.   :351  
 1st Qu.:0.00294   1st Qu.:0.809   1st Qu.:171   1st Qu.:351  
 Median :0.00294   Median :0.809   Median :171   Median :351  
 Mean   :0.00294   Mean   :0.809   Mean   :171   Mean   :351  
 3rd Qu.:0.00294   3rd Qu.:0.809   3rd Qu.:171   3rd Qu.:351  
 Max.   :0.00294   Max.   :0.809   Max.   :171   Max.   :351  

mining info:
 data ntransactions support confidence
   bx        119407   0.002        0.5
plot(r1,method="graph",engine="htmlwidget",itemCol="cyan") 
#從原本rule挑出right hand side有47001..的
r2 = subset(rules, subset = rhs %in% c("4710011401135"))
summary(r2)
set of 4 rules

rule length distribution (lhs + rhs):sizes
2 3 
1 3 

   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   2.00    2.75    3.00    2.75    3.00    3.00 

summary of quality measures:
    support          confidence         lift          count    
 Min.   :0.00245   Min.   :0.504   Min.   :64.8   Min.   :292  
 1st Qu.:0.00265   1st Qu.:0.526   1st Qu.:67.7   1st Qu.:316  
 Median :0.00277   Median :0.540   Median :69.5   Median :331  
 Mean   :0.00284   Mean   :0.549   Mean   :70.6   Mean   :340  
 3rd Qu.:0.00297   3rd Qu.:0.563   3rd Qu.:72.4   3rd Qu.:354  
 Max.   :0.00338   Max.   :0.613   Max.   :78.7   Max.   :404  

mining info:
 data ntransactions support confidence
   bx        119407   0.002        0.5
plot(r2,method="graph",engine="htmlwidget",itemCol="cyan") 

# 只要我想要知道出現甚麼東西就會出現甚麼的話 就可以用關聯規則去做


E. 產品推薦 Product Recommendation

E1. 篩選顧客、產品

太少被購買的產品和購買太少產品的顧客都不適合使用Collaborative Filtering這種產品推薦方法,所以我們先對顧客和產品做一次篩選

library(recommenderlab)
rx = cpm[, colSums(cpm > 0) >= 50] #先篩掉有50個人以上買的column
rx = rx[rowSums(rx > 0) >= 20 & rowSums(rx > 0) <= 300, ] #不允許顧客不買東西 只留買超過20件產品的顧客 買超過300件的我也不要 這行是在選row(顧客)
dim(rx)  # 8846 3354
[1] 8846 3354
E2. 選擇產品評分方式

可以選擇要用

做模型。

rx = as(rx, "realRatingMatrix")  # realRatingMatrix
bx = binarize(rx, minRating=1)   # binaryRatingMatrix
# minRating=1 就是超過1次就算1 放2就是買2次以上才是1
# 通常零售商比較care binary
# 在這裡是用bx
E3. 建立模型、產生建議 - UBCF

UBCF:User Based Collaborative Filtering

(rUBCF <- Recommender(bx[1:8800,], method = "UBCF"))
Recommender of type 'UBCF' for 'binaryRatingMatrix' 
learned using 8800 users.
# 用前面的8800去做模型 把模型放在rUBCF裡面
# UBCF 把購買習慣類似的人先集合在一起 
pred = predict(rUBCF, bx[8801:8846,], n=4) #用剩下的46個做測試 叫他每個人幫我推薦4件產品
do.call(rbind, as(pred, "list")) %>% head(15) #head可以不用打 就可以看46個顧客的結果
        [,1]            [,2]            [,3]            [,4]           
2170855 "4711271000014" "4710114128038" "4714981010038" "4713985863121"
2171265 "4719090900065" "4710254049521" "4710036008562" "4714981010038"
2171340 "723125488040"  "723125488064"  "723125485032"  "4714981010038"
2171425 "4710011401135" "4710011409056" "4711080010112" "4710011401142"
2171432 "4714981010038" "4710011406123" "4711258007371" "4710011401128"
2171555 "4719090900065" "4711271000014" "37000329169"   "4710943109352"
2171883 "4711271000014" "4710583996008" "4710291112172" "4710018004704"
2172194 "4711271000014" "4714981010038" "4710114128038" "4710114105046"
2172392 "4903111345717" "4710908131589" "4710168705056" "4711271000014"
2172569 "4711271000014" "4714981010038" "4710128030037" "4712162000038"
2172583 "4714981010038" "4710085120093" "4719090900065" "4710154015206"
2172590 "4710011406123" "4710011401142" "4710857000028" "4710011432856"
2172668 "4711271000014" "4710088620156" "4719090900065" "4712425010712"
2172705 "4711271000014" "4714981010038" "37000445111"   "37000440192"  
2172811 "4714981010038" "37000442127"   "4710719000333" "4710114128038"
E4. 建立模型、產生建議 - IBCF

IBCF:Item Based Collaborative Filtering

(rIBCF <- Recommender(bx[1:8800,], method = "IBCF"))
pred = predict(rIBCF, bx[8801:8846,], n=4)
do.call(rbind, as(pred, "list")) %>% head(15)
        [,1]            [,2]            [,3]            [,4]           
2170855 "4719090900065" "4714981010038" "4711271000014" "4712162000038"
2171265 "4719090900065" "4710015103288" "4714981010038" "4711271000014"
2171340 "37000445111"   "4710036005608" "37000442127"   "723125485032" 
2171425 "4711311617899" "4711311218836" "4710011401135" "4710011409056"
2171432 "4714981010038" "4710321791698" "4710857000042" "4710626111252"
2171555 "93432641"      "93362993"      "4710105045320" "4711271000014"
2171883 "4710670200100" "4710670200407" "4711271000014" "3228020490329"
2172194 "4714108700019" "4714108700064" "4909978199111" "20332433"     
2172392 "4710706211759" "4710908131589" "4719090900058" "4710731040614"
2172569 "4711371850243" "84501297329"   "84501293529"   "4710085121007"
2172583 "4710085172702" "4710085120093" "34000100095"   "34000231508"  
2172590 "4710011406123" "4711271000014" "4711437000162" "4710011401142"
2172668 "4711371850243" "4719090900065" "4714981010038" "4711437000117"
2172705 "37000445111"   "4710018004605" "37000442127"   "37000304593"  
2172811 "4719581980293" "4712067899287" "4719581980279" "4710908131589"
save(rIBCF, rUBCF, file="data/recommenders.rdata")
E5. 設定模型(準確性)驗證方式
set.seed(4321)
scheme = evaluationScheme(     
  bx, method="split", train = .75,  given=5)
#把整個資料split成0.75做train
#given 5 是指我留下0.25來做的testing data中 你要給我5件產品 我才能猜
E6. 設定推薦方法(參數)
algorithms = list(            
  AR53 = list(name="AR", param=list(support=0.0005, confidence=0.3)),
  AR43 = list(name="AR", param=list(support=0.0004, confidence=0.3)),
  RANDOM = list(name="RANDOM", param=NULL), #radom亂猜 有點像baseline model
  POPULAR = list(name="POPULAR", param=NULL), #popular 看哪件流行就推薦他那一件 看你沒買哪樣我就推薦給你 類似smart baseline
  UBCF = list(name="UBCF", param=NULL),  #param=NULL代表就用他們原本預設的參數
  IBCF = list(name="IBCF", param=NULL) )
E7. 建模、預測、驗證(準確性)
if(LOAD) {
  load("data/results2a.rdata")
} else {
  t0 = Sys.time()
  results = evaluate(            
    scheme, algorithms, 
    type="topNList",     # method of evaluation
    n=c(5, 10, 15, 20)   # no. recom. to be evaluated 每個人給我5個 我要猜5件10件15件20件
    )
  print(Sys.time() - t0)
  save(results, file="data/results2a.rdata")
}
## AR run fold/sample [model time/prediction time]
##   1  [4.02sec/214.6sec] 
## AR run fold/sample [model time/prediction time]
##   1  [10.49sec/538.5sec] 
## RANDOM run fold/sample [model time/prediction time]
##   1  [0sec/9.48sec] 
## POPULAR run fold/sample [model time/prediction time]
##   1  [0sec/11.09sec] 
## UBCF run fold/sample [model time/prediction time]
##   1  [0sec/75.42sec] 
## IBCF run fold/sample [model time/prediction time]
##   1  [198.2sec/1.63sec] 
## Time difference of 18.72 mins
## 這段程式要跑10幾分鐘
E8. 模型準確性比較

若推薦10個產品,AR53表現最好,True Positive Rate最高,False Positive Rate也最低 若推薦15 or 20個產品,整體來說IBCF是最好的

# load("data/results.rdata")
par(mar=c(4,4,3,2),cex=0.8)
cols = c("red", "magenta", "gray", "orange", "blue", "green")
plot(results, annotate=c(1,3), legend="topleft", pch=19, lwd=2, col=cols)
abline(v=seq(0,0.006,0.001), h=seq(0,0.08,0.01), col='lightgray', lty=2)

# ROC Curve 
# 猜得越多中的機率就越大
# 灰色表亂猜
# 橘色錶smart baseline
# TPR越高越好 FPR越低越好
# 比較各個現在5 10 15的TPR和FPR
getConfusionMatrix(results$IBCF)
[[1]]
      TP     FP    FN   TN precision  recall     TPR      FPR
5  1.116  3.884 32.97 3311    0.2231 0.03899 0.03899 0.001171
10 1.699  8.301 32.39 3307    0.1699 0.05812 0.05812 0.002503
15 2.075 12.925 32.01 3302    0.1383 0.07021 0.07021 0.003898
20 2.385 17.615 31.70 3297    0.1193 0.08002 0.08002 0.005313






LS0tDQp0aXRsZTogIlByb2R1Y3QoMmEp77ya55Si5ZOB6Yq35ZSu6LOH6KiKIg0KYXV0aG9yOiAiR3JvdXA0LCAyMDE4LzA4LzA0Ig0Kb3V0cHV0OiBodG1sX25vdGVib29rDQplZGl0b3Jfb3B0aW9uczogDQogIGNodW5rX291dHB1dF90eXBlOiBpbmxpbmUNCg0KLS0tDQoNCjx1bCBjbGFzcz0ibmF2Ij4NCiAgPGxpPjxhIGhyZWY9IiNBIj7poaflrqLnlKLlk4Hnn6npmaM8L2E+PC9saT4NCiAgPGxpPjxhIGhyZWY9IiNCIj7ku6XnlKLlk4Hooqvos7zosrfpoLvnjofkvZzngrrorormlbg8L2E+PC9saT4NCiAgPGxpPjxhIGhyZWY9IiNDIj7lsLrluqbnuK7muJs8L2E+PC9saT4NCiAgPGxpPjxhIGhyZWY9IiNEIj7os7zniannsYPliIbmnpA8L2E+PC9saT4NCiAgPGxpPjxhIGhyZWY9IiNFIj7nlKLlk4HmjqjolqY8L2E+PC9saT4NCjwvdWw+DQoNCjxkaXYgY2xhc3M9ImNvbW1lbnQiPg0KICBHcm91cDQ6PGJyPg0KICBCMDM0MDIwMDEyIOisnembqOmdnDxicj4NCiAgQjAzNDAyMDAyNyDpmbPpn7vljYk8YnI+DQogIEIwNDQwMTIwMTUg546L6K2v6IuTPGJyPg0KICBNMDY0MTExMDI1IOm7g+Wogeixqjxicj4NCiAgTTA2NDExMTAzOSDnjosgIOasozxicj4NCiAg6KGM5YKz5omAICAgICDlrZ/npaXnkYQ8YnI+DQo8L2Rpdj4NCg0KPGJyPg0KDQpgYGB7cn0NCnBhY2thZ2VzID0gYygNCiAgImRwbHlyIiwiZ2dwbG90MiIsImdvb2dsZVZpcyIsImRldnRvb2xzIiwibWFncml0dHIiLCJzbGFtIiwiaXJsYmEiLCJwbG90bHkiLA0KICAiYXJ1bGVzIiwiYXJ1bGVzVml6IiwiTWF0cml4IiwicmVjb21tZW5kZXJsYWIiKQ0KZXhpc3RpbmcgPSBhcy5jaGFyYWN0ZXIoaW5zdGFsbGVkLnBhY2thZ2VzKClbLDFdKQ0KZm9yKHBrZyBpbiBwYWNrYWdlc1shKHBhY2thZ2VzICVpbiUgZXhpc3RpbmcpXSkgaW5zdGFsbC5wYWNrYWdlcyhwa2cpDQpgYGANCg0KYGBge3Igd2FybmluZz1GLCBtZXNzYWdlPUYsIGNhY2hlPUYsIGVycm9yPUZ9DQpybShsaXN0PWxzKGFsbD1UUlVFKSkNCkxPQUQgPSBUUlVFDQpTeXMuc2V0bG9jYWxlKCJMQ19BTEwiLCJDIikNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGdvb2dsZVZpcykNCmxpYnJhcnkoTWF0cml4KQ0KbGlicmFyeShzbGFtKQ0KbGlicmFyeShpcmxiYSkNCmxpYnJhcnkocGxvdGx5KQ0KbGlicmFyeShhcnVsZXMpDQpsaWJyYXJ5KGFydWxlc1ZpeikNCmxpYnJhcnkocmVjb21tZW5kZXJsYWIpDQpgYGANCjxicj48aHI+DQoNCjxkaXYgaWQ9IkEiPg0KIyMjIEEuIOmhp+WuoueUouWTgeefqemZow0KPC9kaXY+DQoNCkxvYWQgZGF0YSBmcmFtZSBhbmQgcmVuYW1lDQpgYGB7cn0NCmxvYWQoImRhdGEvdGYwLnJkYXRhIikNCkEgPSBBMDsgWCA9IFgwOyBaID0gWjA7IHJtKEEwLFgwLFowKTsgZ2MoKQ0KWiA9IHN1YnNldChaLCBjdXN0ICVpbiUgQSRjdXN0KQ0KYGBgDQoNCmBgYHtyfQ0Kbl9kaXN0aW5jdChaJGN1c3QpICAjIDMyMjQxIOacieWkmuWwkemhp+Wuog0Kbl9kaXN0aW5jdChaJHByb2QpICAjIDIzNzg3IOacieWkmuWwkeeUouWTgQ0KYGBgDQoNCuijveS9nOmhp+WuoueUouWTgeefqemZo+WFtuWvpuW+iOW/q+OAgeS5n+W+iOWuueaYkw0KYGBge3J9DQpsaWJyYXJ5KE1hdHJpeCkNCmxpYnJhcnkoc2xhbSkNCmNwbSA9IHh0YWJzKH4gY3VzdCArIHByb2QsIFosIHNwYXJzZT1UKSAgIyBjdXN0b21lciBwcm9kdWN0IG1hdHJpeA0KZGltKGNwbSkgICAgICAgICAgICAgIyAzMjI0MSAyMzc4Nw0KbWVhbihjcG0gPiAwKSAgICAgICAgIyAwLjAwMDk2Nzk5IOmAmeWAi+efqemZo+acieizh+aWmeeahOWvhuW6piANCmBgYA0KDQrpoaflrqLnlKLlk4Hnn6npmaPpgJrluLjmmK/kuIDlgIvlvojnqIDnlo/nmoTnn6nvvIzpmaPmnInkuIDkupvnlKLlk4HmspLku4DpurzkurrosrcNCmBgYHtyfQ0KY29sU3VtcyhjcG0pICU+JSBxdWFudGlsZShzZXEoMCwxLDAuMSkpDQptZWFuKGNvbFN1bXMoY3BtKSA+IDEwKSAj6KKr6LK36LaF6YGOMTDmrKHnmoTnlKLlk4Eg5Zyo6YKP6Lyv6YGL566X5YmN6Z2i5YqgbWVhbiDku6PooahUUlVF55qE5q+U546HDQpgYGANCg0K5Yiq5Y676LO86LK35qyh5pW45bCP5pa8NueahOeUouWTge+8jOeEtuW+jOWIquWOu+aykuacieizvOiyt+eUouWTgeeahOmhp+Wuog0KYGBge3J9DQpjcG0gPSBjcG1bLCBjb2xTdW1zKGNwbSkgPj0gNl0gICAgICAjIHJlbW92ZSB0aGUgbGVhc3QgZnJlcXVlbnQgcHJvZHVjdHMNCiMgY3BtID0gY3BtW3Jvd1N1bXMoY3BtKSA+IDAsIF0gICAgICMgcmVtb3ZlIG5vbi1idXlpbmcgY3VzdG9tZXJzDQpjcG0gPSBjcG1bLCBvcmRlcigtY29sU3VtcyhjcG0pKV0gICAjIG9yZGVyIHByb2R1Y3QgYnkgZnJlcXVlbmN5DQpkaW0oY3BtKSAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIDMyMjQxIDIzNzg3PjE0NjIxIOWJqeS4izE0NjIx5YCL55Si5ZOBDQpgYGANCg0KYGBge3J9DQptYXgoY3BtKSAgICAgICAgICMgNDkNCm1lYW4oY3BtID4gMCkgICAgIyAwLjAwMTUyNDgNCnRhYmxlKGNwbUB4KSAlPiUgcHJvcC50YWJsZSAlPiUgcm91bmQoNCkgJT4lIGhlYWQoMTApDQpgYGANCg0KPGJyPjxwIGNsYXNzPSJxaXoiPg0K6KuL5L2g55So5LiA5YCL5oyH5Luk5YiX5Ye66KKr6LO86LK35pyA5aSa5qyh55qEMTDlgIvnlKLlk4HvvIzlkozlroPlgJHooqvos7zosrfnmoTmrKHmlbjjgIINCjxicj48L3A+DQpgYGB7cn0NCmNwbVssMToxMF0gJT4lIGNvbFN1bXMNCiMgDQpgYGANCg0KPGJyPjxwIGNsYXNzPSJxaXoiPg0K4pagIOWcqOS7gOm6vOWJjeaPkOS5i+S4i++8jOaIkeWAkeWPr+S7peaKiuizvOiyt+mAmeWNgeWAi+eUouWTgeeahOasoeaVuOeVtuS9nOiuiuaVuO+8jOeUqOS+humgkOa4rOmhp+WuouWcqOS4i+S4gOacn+acg+S4jeacg+S+huizvOiyt+WRou+8nzxicj4NCuKWoCDmiJHlgJHlpoLkvZXmiorpgJnljYHlgIvorormlbjvvIzkvbXlhaXpoaflrqLos4fmlpnmoYblkaLvvJ8gPGJyPg0K4pagIOaIkeWAkeWPr+S4jeWPr+S7pSjlnKjku4DpurzliY3mj5DkuYvkuIvmiJHlgJHlj6/ku6Up55u05o6l55SoYGNiaW5kKClg5paw6K6K5pW45L215YWl6aGn5a6i6LOH5paZ5qGG5ZGi77yfPGJyPg0K4pagIOaIkeWAkeacn+S4reertuizveeahOizh+aWme+8jOespuWQiOebtOaOpeeUqGBjYmluZCgpYOS9teWFpeaWsOiuiuaVuOeahOaineS7tuWXju+8nyDmiJHlgJHopoHlpoLkvZXnorroqo3pgJnkuIDku7bkuovlkaLvvJ88YnI+DQo8L3A+DQoNCjxkaXYgY2xhc3M9ImNvbW1lbnQiPg0K5Y+q6KaB5pyJ55CG55Sx55u45L+heOWwjXnmnInpoJDmuKzlipvvvIzlsLHlj6/ku6Xmioros7zosrfpgJnljYHlgIvnlKLlk4HnmoTmrKHmlbjnlbbkvZzorormlbgNCuWPr+S7peS9v+eUqGBjYmluZCgpYOaIluaYr2BtZXJnZSgpYOaKiumAmeWNgeWAi+iuiuaVuO+8jOS9teWFpemhp+Wuouizh+aWmeahhuOAguWmguaenGN1c3RvbWVyIGlk5o6S5bqP5LiA5qij77yM5Y+v5Lul55u05o6l55SoYGNiaW5kKClg77yM5ZCm5YmH6KaB55SoYG1lcmdlKClgDQo8L2Rpdj4NCg0KPGJyPg0KPGJyPjxocj4NCmBgYHtyfQ0KDQojIGRpbShjcG0pICPnorrlrppjcG3ot59BDQojIHN1bSggQSRjdXN0ID09IHJvd25hbWVzKGNwbSkgKSAgDQojIOimgeeiuuiqjUHot59jcG3nmoRyb3duYW1lc+asoeW6j+aYr+S4jeaYr+S4gOaooyhyb3duYW1lcyA9IGN1c3QpDQojIHN1bei1t+S+huacg+iuiuaIkDMyMjQx5YCLVFJVReaIlkZBTFNFDQoNCmBgYA0KDQpgYGB7cn0NCg0KIyDlpoLmnpznlKjlsLrluqbnuK7muJvnmoToqbEgDQojIEEgPSBjYmluZChBICwgc3ZkJHVbICwgMToyMF0pDQpgYGANCg0KDQo8ZGl2IGlkPSJCIj4NCiMjIyBCLiDnm7TmjqXku6XnlKLlk4HnmoTooqvos7zosrfpoLvnjofkvZzngrrorormlbgNCjwvZGl2Pg0KDQoNCuS7peeUouWTgeeahOiiq+izvOiyt+mgu+eOh+ijveS9nCjpoaflrqIp6K6K5pW455qE5pmC5YCZ77yMKirmjpJgY3BtYOWcqOacgOWJjemCiueahChO5YCLKeashOS9jeWwseaYr+iuiuaVuCoqIQ0KDQojIyMjIyBCMS4g5LulKOacgOW4uOiiq+izvOiyt+eahCnnlKLlk4HnmoTos7zosrfmrKHmlbjlsI3poaflrqLliIbnvqQNCmBgYHtyfQ0Kbm9wPSA0MDAgICMgbm8uIHByb2R1Y3QgPSBuby4gdmFyaWFibGVzDQprID0gMjAwICAgIyBuby4gY2x1c3Rlcg0Kc2V0LnNlZWQoMTExKTsga2cgPSBrbWVhbnMoY3BtWywxOm5vcF0sIGspJGNsdXN0ZXINCnRhYmxlKGtnKSAlPiUgYXMudmVjdG9yICU+JSBzb3J0DQpgYGANCg0KIyMjIyMgQjIuIOWQhOe+pOe1hOW5s+Wdh+WxrOaApw0K5bCH5YiG576k57WQ5p6c5L215YWl6aGn5a6i6LOH5paZ5qGGKGBBYCkNCmBgYHtyfQ0KZGYgPSBBICU+JSBpbm5lcl9qb2luKGRhdGEuZnJhbWUoDQogIGN1c3QgPSBhcy5pbnRlZ2VyKHJvd25hbWVzKGNwbSkpLCANCiAga2cpICkNCmhlYWQoZGYpICAjIDMyMjQxDQpgYGANCg0K6KiI566X5ZCE576k57WE55qE5bmz5Z2H5bGs5oCnDQpgYGB7cn0NCmRmID0gZGF0YS5mcmFtZSgNCiAgYWdncmVnYXRlKC4gfiBrZywgZGZbLGMoMjo3LDEwKV0sIG1lYW4pLCAjIGF2ZXJhZ2VzDQogIHNpemUgPSBhcy52ZWN0b3IodGFibGUoa2cpKSwgICAjIG5vLiBjdXN0b21lcnMgaW4gdGhlIGdyb3VwDQogIGR1bW15ID0gMjAwMSAgICAgICAgICAgICAgICAgICAjIGR1bW15IGNvbHVtbiBmb3IgZ29vZ2xlVml6DQogICkNCmhlYWQoZGYpDQpgYGANCg0KIyMjIyMgQjMuIOS6kuWLleW8j+azoeazoeWclg0KYGBge3J9DQpwbG90KCBndmlzTW90aW9uQ2hhcnQoDQogIHN1YnNldChkZlssYygxLDQsNSw2LDgsMiwzLDcsOSldLCANCiAgICAgICAgIHNpemUgPj0gMjAgJiBzaXplIDw9IDEwMDApLCAgIyByYW5nZSBvZiBncm91cCBzaXplIA0KICAia2ciLCAiZHVtbXkiLCBvcHRpb25zPWxpc3Qod2lkdGg9ODAwLCBoZWlnaHQ9NjAwKSApICkNCmBgYA0KDQojIyMjIyBCNC4g5ZCE576k57WE55qE5Luj6KGo5oCn55Si5ZOBIChTaWduYXR1cmUgUHJvZHVjdCkNCmBgYHtyfQ0KIyB1c2UgZ2xvYmFsIHZhcmlhYmxlczogY3BtLCBrZw0KU2lnID0gZnVuY3Rpb24oZ3gsIFA9MTAwMCwgSD0xMCkgew0KICBwcmludChzcHJpbnRmKCJHcm91cCAlZDogTm8uIEN1c3RvbWVycyA9ICVkIiwgZ3gsIHN1bShrZz09Z3gpKSkNCiAgYnggPSBjcG1bLDE6UF0NCiAgZGF0YS5mcmFtZShuID0gY29sX3N1bXMoYnhba2c9PWd4LF0pKSAlPiUgICAgICAjIGZyZXF1ZW5jeQ0KICAgIG11dGF0ZSgNCiAgICAgIHNoYXJlID0gcm91bmQoMTAwKm4vY29sX3N1bXMoYngpLDIpLCAgICAgICAjICVwcm9kIHNvbGQgdG8gdGhpcyBjbHVzdGVyDQogICAgICBjb25mID0gcm91bmQoMTAwKm4vc3VtKGtnPT1neCksMiksICAgICAgICAgIyAlYnV5IHRoaXMgcHJvZHVjdCwgZ2l2ZW4gY2x1c3Rlcg0KICAgICAgYmFzZSA9IHJvdW5kKDEwMCpjb2xfc3VtcyhieCkvbnJvdyhieCksMiksICMgJWJ1eSB0aGlzIHByb2R1Y3QsIGFsbCBjdXN0IA0KICAgICAgbGlmdCA9IHJvdW5kKGNvbmYvYmFzZSwxKSwgICAgICAgICAgICAgICAgICMgY29uZi9iYXNlICANCiAgICAgIG5hbWUgPSBjb2xuYW1lcyhieCkgICAgICAgICAgICAgICAgICAgICAgICAjIG5hbWUgb2YgcHJvZA0KICAgICkgJT4lIGFycmFuZ2UoZGVzYyhsaWZ0KSkgJT4lIGhlYWQoSCkNCiAgfQ0KYGBgDQoNCmBgYHtyfQ0KU2lnKDEzMCkNCmBgYA0KDQo8YnI+PGRpdiBjbGFzcz0icWl6Ij4NCuKWoCDlnKjku4DpurzliY3mj5DkuYvkuIvvvIzmiJHlgJHlj6/ku6XmiorpoaflrqLos7zosrfnlKLlk4HnmoTnibnlvrXlkJHph4/nlbbkvZzorormlbjvvIznlKjkvobpoJDmuKzpoaflrqLlnKjkuIvkuIDmnJ/mnIPkuI3mnIPkvobos7zosrflkaLvvJ88YnI+DQrilqAg5aaC5p6c5Y+v5Lul55qE6Kmx77yM5oiR5YCR5aaC5L2V5oqK6aGn5a6i6LO86LK355Si5ZOB55qE54m55b615ZCR6YeP77yM5L215YWl6aGn5a6i6LOH5paZ5qGG5ZGi77yfIDxicj4NCuKWoCDmiJHlgJHlj6/kuI3lj6/ku6Uo5Zyo5LuA6bq85YmN5o+Q5LmL5LiL5oiR5YCR5Y+v5LulKeebtOaOpeeUqGBjYmluZCgpYOWwh+eJueW+teWQkemHj+S9teWFpemhp+Wuouizh+aWmeahhuWRou+8nzxicj4NCuKWoCDmiJHlgJHmnJ/kuK3nq7bos73nmoTos4fmlpnvvIznrKblkIjnm7TmjqXnlKhgY2JpbmQoKWDkvbXlhaXnibnlvrXlkJHph4/nmoTmop3ku7bll47vvJ8g5oiR5YCR6KaB5aaC5L2V56K66KqN6YCZ5LiA5Lu25LqL5ZGi77yfPGJyPg0KPC9kaXY+DQoNCjxkaXYgY2xhc3M9ImNvbW1lbnQiPg0K5Y+q6KaB5pyJ55CG55Sx55u45L+heOWwjXnmnInpoJDmuKzlipvvvIzlsLHlj6/ku6XmiorpoaflrqLos7zosrfnlKLlk4HnmoTnibnlvrXlkJHph4/nlbbkvZzorormlbgNCuWPr+S7peS9v+eUqGBjYmluZCgpYOaIluaYr2BtZXJnZSgpYOaKiumhp+WuouizvOiyt+eUouWTgeeahOeJueW+teWQkemHj++8jOS9teWFpemhp+Wuouizh+aWmeahhuOAgg0K5aaC5p6cY3VzdG9tZXIgaWTmjpLluo/kuIDmqKPvvIzlj6/ku6Xnm7TmjqXnlKhgY2JpbmQoKWDvvIzlkKbliYfopoHnlKhgbWVyZ2UoKWANCuacn+S4reertuizveeahOizh+aWmeespuWQiOebtOaOpeeUqGBjYmluZCgpYO+8jOWPquimgeeci+S7lueahGN1c3TmjpLluo/mmK/lkKbnm7jlkIzlsLHlj6/ku6Xnorroqo3kuobjgIINCjwvZGl2Pg0KDQo8YnI+DQo8YnI+PGhyPg0KDQoNCjxkaXYgaWQ9IkMiPg0KIyMjIEMuIOS9v+eUqOWwuuW6pue4rua4m+aWueazleaKveWPlumhp+WuoijnlKLlk4Ep55qE54m55b615ZCR6YePDQo8L2Rpdj4NCg0KDQojIyMjIyBDMS4g5beo5aSn5bC65bqm57iu5ribIChTVkQsIFNpZ3VsYXIgVmFsdWUgRGVjb21wb3NpdGlvbikNCg0KKiDlsI3nqIDnlo/nn6npmaPlgZrluLjmhYvljJbmmYLkuI3og73nlKjmma7pgJrnmoTluLjmhYvmlrnms5XvvIzopoHmiorlroNrZWVw5Zyo5LiA5YCL5YC85LiKDQoNCmBgYHtyfQ0KDQpsaWJyYXJ5KGlybGJhKQ0KaWYoTE9BRCkgew0KICBsb2FkKCJkYXRhL3N2ZDJhLnJkYXRhIikNCn0gZWxzZSB7DQogIHNteCA9IGNwbQ0KICBzbXhAeCA9IHBtaW4oc214QHgsIDIpICAgICAgICAgICAgIyBjYXAgYXQgMiwgc2ltaWxhciB0byBub3JtYWxpemF0aW9uICANCiAgdDAgPSBTeXMudGltZSgpDQogIHN2ZCA9IGlybGJhKHNteCwgICAgICAgICAgICAgICAgICAjIOWwjeeogOmshuefqemZo+WBmuW4uOaFi+WMliDopoFjYXAgYXQgMuaJjeacieW4uOaFi+WMluaViOaenA0KICAgICAgICAgICAgICBudj00MDAsICAgICAgICAgICAgICAgIyBsZW5ndGggb2YgZmVhdHVyZSB2ZWN0b3INCiAgICAgICAgICAgICAgbWF4aXQ9ODAwLCB3b3JrPTgwMCkgICMgbnY9NDAw5pyJ6bue5aSnICDkuIDoiKzkvoboqqrkuI3mnIPnlKjpgqPpurzlpKcgbWF4aXTlkox3b3Jr6KitbnYqMiANCiAgcHJpbnQoU3lzLnRpbWUoKSAtIHQwKSAgICAgICAgICAgICMgMS44Nzk1IG1pbnMNCiAgc2F2ZShzdmQsIGZpbGUgPSAiZGF0YS9zdmQyYS5yZGF0YSIpDQp9DQoNCmBgYA0KDQojIyMjIyBDMi4g5L6d54m55b615ZCR6YeP5bCN6aGn5a6i5YiG576kDQpgYGB7cn0NCnNldC5zZWVkKDExMSk7IGtnID0ga21lYW5zKHN2ZCR1LCAyMDApJGNsdXN0ZXINCnRhYmxlKGtnKSAlPiUgYXMudmVjdG9yICU+JSBzb3J0DQpgYGANCg0KIyMjIyMgQzMuIOS6kuWLleW8j+azoeazoeWcliAoR29vZ2xlIE1vdGlvbiBDaGFydCkNCmBgYHtSfQ0KIyBjbHVzdHN0ZXIgc3VtbWFyeQ0KZGYgPSBpbm5lcl9qb2luKEEsIGRhdGEuZnJhbWUoICAgICAgICAgDQogIGN1c3QgPSBhcy5pbnRlZ2VyKHJvd25hbWVzKGNwbSkpLCBrZykpICU+JSANCiAgZ3JvdXBfYnkoa2cpICU+JSBzdW1tYXJpc2UoDQogICAgYXZnX2ZyZXF1ZW5jeSA9IG1lYW4oZiksDQogICAgYXZnX21vbmV0YXJ5ID0gbWVhbihtKSwNCiAgICBhdmdfcmV2ZW51ZV9jb250ciA9IG1lYW4ocmV2KSwNCiAgICBncm91cF9zaXplID0gbigpLA0KICAgIGF2Z19yZWNlbmN5ID0gbWVhbihyKSwNCiAgICBhdmdfZ3Jvc3NfcHJvZml0ID0gbWVhbihyYXcpKSAlPiUgDQogIHVuZ3JvdXAgJT4lIA0KICBtdXRhdGUoZHVtbXkgPSAyMDAxLCBrZyA9IHNwcmludGYoIkclMDNkIixrZykpICU+JSANCiAgZGF0YS5mcmFtZQ0KDQojIEdvb2dsZSBNb3Rpb24gQ2hhcnQNCnBsb3QoIGd2aXNNb3Rpb25DaGFydCgNCiAgc3Vic2V0KGRmLCBncm91cF9zaXplID49IDIwICYgZ3JvdXBfc2l6ZSA8PSAxMjAwKSwgICAgIA0KICAia2ciLCAiZHVtbXkiLCBvcHRpb25zPWxpc3Qod2lkdGg9ODAwLCBoZWlnaHQ9NjAwKSApICkNCmBgYA0KDQojIyMjIyBDNC4g5ZCE576k57WE55qE5Luj6KGo5oCn55Si5ZOBIChTaWduYXR1cmUgUHJvZHVjdCkNCmBgYHtyfQ0KU2lnKDE2MikNCmBgYA0KPGJyPjxocj4NCg0KPGRpdiBpZD0iRCI+DQojIyMgRC4g6LO854mp57GD5YiG5p6QIEJhc2tldHMgQW5hbHlzaXMgDQo8L2Rpdj4NCg0KYGBge3J9DQpkaW0oY3BtKSAgICMgMzIyNDEgMTQ2MjENCmBgYA0KDQojIyMjIyBEMS4g5rqW5YKZ6LOH5paZIChmb3IgQXNzb2NpYXRpb24gUnVsZSBBbmFseXNpcynpl5zoga/opo/liYfliIbmnpANCg0KYnjlkoxjcG3ooajpgZTnmoTos4foqIrlhbblr6bkuIDmqKPvvIzlj6rmmK/moLzlvI/kuI3kuIDmqKMNCmBgYHtyfQ0KbGlicmFyeShhcnVsZXMpDQpsaWJyYXJ5KGFydWxlc1ZpeikgI+WBmnZpc3VhbGl6YXRpb27nlKgNCiMgYnggPSBzdWJzZXQoWiwgcHJvZCAlaW4lIGFzLm51bWVyaWMoY29sbmFtZXMoY3BtKSksIA0KIyAgICAgICAgICAgIHNlbGVjdD1jKCJjdXN0IiwicHJvZCIpKSAgIyBzZWxlY3QgcHJvZHVjdCBpdGVtcw0KIyBieCA9IFoNCiMgYnggPSBzcGxpdChieCRwcm9kLCBieCR0aWQpICAgICAgICAgICMgc3BsaXQgYnkgdHJhbnNhY3Rpb24gaWQNCiMgYnggPSBhcyhieCwgInRyYW5zYWN0aW9ucyIpICAgICAgICAgICAjIGRhdGEgc3RydWN0dXJlIGZvciBhcnVsZXMgcGFja2FnZQ0KIyDlhYjlvp5a6KOh6Z2i6YG45Y+WIA0KIyBhcy5udW1lcmljKGNvbG5hbWVzKGNwbSkpIOiiq+izvOiyt+i2hemBjuWFreasoeS7peS4iueahOeUouWTgeeahOmbhuWQiCDpgbhyb3cNCiMgJWluJSDpm4blkIgNCiMgc2VsZWN0PWMoImN1c3QiLCJwcm9kIikpIOmBuGNvbA0KIyBieOWFtuWvpuWwseaYr+WPpuS4gOeoruagvOW8j+eahOmhp+WuoueUouWTgQ0KYnggPSBhcyhzcGxpdChaJHByb2QgLCBaJHRpZCksJ3RyYW5zYWN0aW9ucycpDQpgYGANCg0KIyMjIyMgRDIuIFRvcDIwIOeGseizo+eUouWTgQ0KYGBge3IgZmlnLmhlaWdodD0zLCBmaWcud2lkdGg9Ny4yfQ0KaXRlbUZyZXF1ZW5jeVBsb3QoYngsIHRvcE49MjAsIHR5cGU9ImFic29sdXRlIiwgY2V4PTAuOCkNCmBgYA0KDQojIyMjIyBEMy4g6Zec6IGv6KaP5YmH5ZKMQXByaW9yaea8lOeul+azlQ0KDQrpl5zoga/opo/liYcoQSA9PiBCKQ0KDQorIHN1cHBvcnQ6IEHooqvos7zosrfnmoTmqZ/njocgKEHnmoTln7rnpI7mqZ/njocpDQorIGNvbmZpZGVuY2U6IEHooqvos7zosrfmmYLvvIxC6KKr6LO86LK355qE5qmf546HDQorIGxpZnQ6IEHooqvos7zosrfmmYLvvIxC6KKr6LO86LK355qE5qmf546H5aKe5Yqg55qE5YCN5pW4ICjoiIdC55qE5Z+656SO5qmf546H55u45q+UKQ0KKyDkuIDoiKzkvoborJtzdXBwb3J044CBY29uZmlkZW5jZeWSjGxpZnTotorpq5jnmoTpl5zoga/opo/liYfotorph43opoENCisgc3VwcG9ydOOAgWNvbmZpZGVuY2XlkoxsaWZ06Kit55qE6LaK5L2OKOmrmCnvvIzmib7liLDnmoTpl5zoga/opo/liYfotorlpJoo5bCRKQ0KKyDlu7rorbDkuIDplovlp4vmiormqJnmupboqK3kvY7vvIzlhYjmib7liLDlpJrkuIDpu57opo/liYfvvIzkuYvlvozlho3nlKhzdWJzZXTnr6npgbjlh7rnibnlrprnmoTopo/liYfkvobnnIsNCg0KDQpgYGB7cn0NCnJ1bGVzID0gYXByaW9yaShieCwgcGFyYW1ldGVyPWxpc3Qoc3VwcD0wLjAwMiwgY29uZj0wLjUpKSANCnN1bW1hcnkocnVsZXMpDQojIOaIkeimgeaJvnN1cHBvcnQ+MC4wMDUgY29uZmlkZW5jZT4wLjbnmoQg6YCZ5Lqb6YO95Y+v5Lul6Ieq5bex6Kq/IOW+numrmOW+gOS9juiqvw0KIyDploDmqrvoqK3otorkvY4g5om+5Yiw55qEcnVsZei2iuWkmg0KIyDmib7liLAyOOWAi+mXnOiBr+imj+WJhw0KYGBgDQoNCiMjIyMjIEQ0LiDmqqLoppbpl5zoga/opo/liYcNCg0K6Zec6IGv6KaP5YmHIChBID0+IEIp77yaDQoNCisgc3VwcG9ydDogQeiiq+izvOiyt+eahOapn+eOhyAoQeeahOWfuuekjuapn+eOhykNCisgY29uZmlkZW5jZTogQeiiq+izvOiyt+aZgu+8jELooqvos7zosrfnmoTmqZ/njocNCisgbGlmdDogQeiiq+izvOiyt+aZgu+8jELooqvos7zosrfnmoTmqZ/njoflop7liqDnmoTlgI3mlbggKOiIh0LnmoTln7rnpI7mqZ/njofnm7jmr5QpDQoNCiogbGhzIDogTGVmdCBIYW5kIFNpZGUNCiogcmhzIDogUmlnaHQgSGFuZCBTaWRlDQoNCnN1cHBvcnQsIGNvbmZpZGVuY2UsIGxpZnTotorpq5jotorlpb3vvIzku6Pooajpl5zoga/opo/liYfotorph43opoENCmBgYHtyfQ0KI+ecizI45YCLcnVsZQ0Kb3B0aW9ucyhkaWdpdHM9NCkNCmluc3BlY3QocnVsZXMpIA0KIyBsaHMgbGVmdCBoYW5kIHNpZGUNCiMgcmhzIHJpZ2h0IGhhbmQgc2lkZQ0KIyDmiJHlgJHmnIPluIzmnJtzdXBwb3J06auYDQojIOiPnOexg+WIhuaekOaYr+eci+S4jeWIsOS6uueahCDmmK/nnIvkuIDlvLXkuIDlvLXllq4NCiMgQuWfuuekjuapn+eOhyDlsLHmmK/lnKjmiYDmnInoj5znsYPoo6HpnaIgQuWHuuePvueahOapn+eOhw0KIyBjb25maWRlbmNlL2xpZnQgPSBC55qE5Z+656SO5qmf546HDQojIOiLpXN1cHBvcnQgY29uZmlkZW5jZSBsaWZ06YO96auYIOWwseS7o+ihqOmAmeWAi3J1bGXpnZ7luLjph43opoENCg0KYGBgDQoNCmBgYHtyfQ0KIyBpbnN0YWxsLnBhY2thZ2VzKA0KIyAgICJodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy9iaW4vd2luZG93cy9jb250cmliLzMuNS9hcnVsZXNWaXpfMS4zLTEuemlwIiwNCiMgICByZXBvcz1OVUxMKQ0KDQojIGluc3RhbGwucGFja2FnZXMoImFydWxlc1Zpel8xLjMtMS56aXAiLCByZXBvcz1OVUxMKQ0KIyBsaWJyYXJ5KHBsb3RseSkNCiMgcGxvdGx5X2FydWxlcyhydWxlcyxjb2xvcnM9YygicmVkIiwiZ3JlZW4iKSwNCiMgICAgICAgICAgICAgICBtYXJrZXI9bGlzdChvcGFjaXR5PS42LHNpemU9MTApKQ0KIyBwbG90bHlfYXJ1bGVzKHJ1bGVzLG1ldGhvZD0ibWF0cml4IiwNCiMgICAgICAgICAgICAgICBzaGFkaW5nPSJsaWZ0IiwNCiMgICAgICAgICAgICAgICBjb2xvcnM9YygicmVkIiwgImdyZWVuIikpDQojIA0KYGBgDQoNCiMjIyMjIEQ1LiDkupLli5XlnJbooajpoa/npLoNCg0K5q+P5YCL6bue6YO95piv5LiA5YCLcnVsZQ0KbGlmdOaYr+mhj+iJsg0K5Y+v5Lul5YGa54K65p625L2N5pO66Kit55qE5Y+D6ICDDQpgYGB7cn0NCnBsb3QocnVsZXMsY29sb3JzPWMoInJlZCIsImdyZWVuIiksZW5naW5lPSJodG1sd2lkZ2V0IiwNCiAgICAgbWFya2VyPWxpc3Qob3BhY2l0eT0uNixzaXplPTgpKQ0KI+avj+S4gOm7numDveaYr+S4gOWAi3J1bGUNCmBgYA0KDQpgYGB7cn0NCnBsb3QocnVsZXMsbWV0aG9kPSJtYXRyaXgiLHNoYWRpbmc9ImxpZnQiLGVuZ2luZT0iaHRtbHdpZGdldCIsDQogICAgIGNvbG9ycz1jKCJyZWQiLCAiZ3JlZW4iKSkNCiMg6KGh55qE5pyJ6KC76ZW355qE5LiA5qKdIOS7o+ihqOacieavlOi8g+WkmueahHJ1bGXmjIflkJFyaWdodCBoYW5kIHNpZGUNCmBgYA0KDQojIyMjIyBENi4g56+p6YG455Si5ZOB44CB5LqS5YuV5byP6Zec6IGv5ZyWDQpgYGB7cn0NCnIxID0gc3Vic2V0KHJ1bGVzLCBzdWJzZXQgPSByaHMgJWluJSBjKCI0NzE5MDkwNzkwMDAwIikpDQpzdW1tYXJ5KHIxKQ0KcGxvdChyMSxtZXRob2Q9ImdyYXBoIixlbmdpbmU9Imh0bWx3aWRnZXQiLGl0ZW1Db2w9ImN5YW4iKSANCmBgYA0KDQorIOazoeazoeWkp+Wwj++8mnN1cHBvcnQ6IEHooqvos7zosrfnmoTmqZ/njocgKEHnmoTln7rnpI7mqZ/njocpDQorIOazoeazoemhj+iJsu+8mmxpZnQ6IEHooqvos7zosrfmmYLvvIxC6KKr6LO86LK355qE5qmf546H5aKe5Yqg55qE5YCN5pW4ICjoiIdC55qE5Z+656SO5qmf546H55u45q+UKQ0KKyBydWxlMyA6IOaciemAmeWFqeaUr+eUouWTgeeahOipsSDosrfpgJnlgIvnmoTmqZ/njocNCg0KYGBge3J9DQoj5b6e5Y6f5pyscnVsZeaMkeWHunJpZ2h0IGhhbmQgc2lkZeaciTQ3MDAxLi7nmoQNCnIyID0gc3Vic2V0KHJ1bGVzLCBzdWJzZXQgPSByaHMgJWluJSBjKCI0NzEwMDExNDAxMTM1IikpDQpzdW1tYXJ5KHIyKQ0KcGxvdChyMixtZXRob2Q9ImdyYXBoIixlbmdpbmU9Imh0bWx3aWRnZXQiLGl0ZW1Db2w9ImN5YW4iKSANCiMg5Y+q6KaB5oiR5oOz6KaB55+l6YGT5Ye654++55Sa6bq85p2x6KW/5bCx5pyD5Ye654++55Sa6bq855qE6KmxIOWwseWPr+S7peeUqOmXnOiBr+imj+WJh+WOu+WBmg0KYGBgDQo8YnI+PGhyPg0KDQo8ZGl2IGlkPSJFIj4NCiMjIyBFLiDnlKLlk4HmjqjolqYgUHJvZHVjdCBSZWNvbW1lbmRhdGlvbg0KPC9kaXY+DQoNCiMjIyMjIEUxLiDnr6npgbjpoaflrqLjgIHnlKLlk4ENCuWkquWwkeiiq+izvOiyt+eahOeUouWTgeWSjOizvOiyt+WkquWwkeeUouWTgeeahOmhp+WuoumDveS4jemBqeWQiOS9v+eUqENvbGxhYm9yYXRpdmUgRmlsdGVyaW5n6YCZ56iu55Si5ZOB5o6o6Jam5pa55rOV77yM5omA5Lul5oiR5YCR5YWI5bCN6aGn5a6i5ZKM55Si5ZOB5YGa5LiA5qyh56+p6YG4DQpgYGB7cn0NCmxpYnJhcnkocmVjb21tZW5kZXJsYWIpDQpyeCA9IGNwbVssIGNvbFN1bXMoY3BtID4gMCkgPj0gNTBdICPlhYjnr6nmjonmnIk1MOWAi+S6uuS7peS4iuiyt+eahGNvbHVtbg0KcnggPSByeFtyb3dTdW1zKHJ4ID4gMCkgPj0gMjAgJiByb3dTdW1zKHJ4ID4gMCkgPD0gMzAwLCBdICPkuI3lhYHoqLHpoaflrqLkuI3osrfmnbHopb8g5Y+q55WZ6LK36LaF6YGOMjDku7bnlKLlk4HnmoTpoaflrqIg6LK36LaF6YGOMzAw5Lu255qE5oiR5Lmf5LiN6KaBIOmAmeihjOaYr+WcqOmBuHJvdyjpoaflrqIpDQpkaW0ocngpICAjIDg4NDYgMzM1NA0KYGBgDQoNCiMjIyMjIEUyLiDpgbjmk4fnlKLlk4HoqZXliIbmlrnlvI8NCuWPr+S7pemBuOaTh+imgeeUqA0KDQorIOizvOiyt+asoeaVuCAocmVhbFJhdGluZ01hdHJpeCkg5oiWDQorIOaYr+WQpuizvOiytyAoYmluYXJ5UmF0aW5nTWF0cml4KQ0KDQrlgZrmqKHlnovjgIINCmBgYHtyfQ0KcnggPSBhcyhyeCwgInJlYWxSYXRpbmdNYXRyaXgiKSAgIyByZWFsUmF0aW5nTWF0cml4DQpieCA9IGJpbmFyaXplKHJ4LCBtaW5SYXRpbmc9MSkgICAjIGJpbmFyeVJhdGluZ01hdHJpeA0KIyBtaW5SYXRpbmc9MSDlsLHmmK/otoXpgY4x5qyh5bCx566XMSDmlL4y5bCx5piv6LK3MuasoeS7peS4iuaJjeaYrzENCiMg6YCa5bi46Zu25ZSu5ZWG5q+U6LyDY2FyZSBiaW5hcnkNCiMg5Zyo6YCZ6KOh5piv55SoYngNCmBgYA0KDQojIyMjIyBFMy4g5bu656uL5qih5Z6L44CB55Si55Sf5bu66K2wIC0gVUJDRg0KVUJDRu+8mlVzZXIgQmFzZWQgQ29sbGFib3JhdGl2ZSBGaWx0ZXJpbmcgDQpgYGB7cn0NCihyVUJDRiA8LSBSZWNvbW1lbmRlcihieFsxOjg4MDAsXSwgbWV0aG9kID0gIlVCQ0YiKSkNCiMg55So5YmN6Z2i55qEODgwMOWOu+WBmuaooeWeiyDmiormqKHlnovmlL7lnKhyVUJDRuijoemdog0KIyBVQkNGIOaKiuizvOiyt+e/kuaFo+mhnuS8vOeahOS6uuWFiOmbhuWQiOWcqOS4gOi1tyANCmBgYA0KDQpgYGB7cn0NCnByZWQgPSBwcmVkaWN0KHJVQkNGLCBieFs4ODAxOjg4NDYsXSwgbj00KSAj55So5Ymp5LiL55qENDblgIvlgZrmuKzoqaYg5Y+r5LuW5q+P5YCL5Lq65bmr5oiR5o6o6JamNOS7tueUouWTgQ0KZG8uY2FsbChyYmluZCwgYXMocHJlZCwgImxpc3QiKSkgJT4lIGhlYWQoMTUpICNoZWFk5Y+v5Lul5LiN55So5omTIOWwseWPr+S7peecizQ25YCL6aGn5a6i55qE57WQ5p6cDQpgYGANCg0KIyMjIyMgRTQuIOW7uueri+aooeWei+OAgeeUoueUn+W7uuitsCAtIElCQ0YNCklCQ0bvvJpJdGVtIEJhc2VkIENvbGxhYm9yYXRpdmUgRmlsdGVyaW5nIA0KYGBge3J9DQoocklCQ0YgPC0gUmVjb21tZW5kZXIoYnhbMTo4ODAwLF0sIG1ldGhvZCA9ICJJQkNGIikpDQojIElCQ0Yg5oqK5L2/55So6ICF5L2c5Y2A6ZqU6K6K5pW4IOePvuWcqOWwjeeUouWTgeWIhue+pCDmnIPlgYfoqK3mr4/kuIDku7bnlKLlk4Hos7zosrfnmoTkurrlpKfmpoLpg73mmK/lkIzmqKPnmoTpgqPkupvkurrlnKjosrcg55So5o6o6KuW55qE5pa55byPDQojIOeUmum6vOaoo+eahOeUouWTgSDos7zosrfnmoTkurrmnIPnm7jlkIwg5YaN5Zue5o6o5Zue5L6G566XDQojIElCQ0Z55YGa5q+U6LyD5LmFDQojIFVCQ0bnlbbpoaflrqLmlbjph4/lvojlpKfmmYIg5Y+v6IO95pyD6LeR5LiN5YuVDQpgYGANCg0KYGBge3J9DQpwcmVkID0gcHJlZGljdChySUJDRiwgYnhbODgwMTo4ODQ2LF0sIG49NCkNCmRvLmNhbGwocmJpbmQsIGFzKHByZWQsICJsaXN0IikpICU+JSBoZWFkKDE1KQ0KYGBgDQoNCmBgYHtyfQ0Kc2F2ZShySUJDRiwgclVCQ0YsIGZpbGU9ImRhdGEvcmVjb21tZW5kZXJzLnJkYXRhIikNCmBgYA0KDQojIyMjIyBFNS4g6Kit5a6a5qih5Z6LKOa6lueiuuaApynpqZforYnmlrnlvI8NCmBgYHtyfQ0Kc2V0LnNlZWQoNDMyMSkNCnNjaGVtZSA9IGV2YWx1YXRpb25TY2hlbWUoICAgICANCiAgYngsIG1ldGhvZD0ic3BsaXQiLCB0cmFpbiA9IC43NSwgIGdpdmVuPTUpDQoj5oqK5pW05YCL6LOH5paZc3BsaXTmiJAwLjc15YGadHJhaW4NCiNnaXZlbiA1IOaYr+aMh+aIkeeVmeS4izAuMjXkvoblgZrnmoR0ZXN0aW5nIGRhdGHkuK0g5L2g6KaB57Wm5oiRNeS7tueUouWTgSDmiJHmiY3og73njJwNCmBgYA0KDQojIyMjIyBFNi4g6Kit5a6a5o6o6Jam5pa55rOVKOWPg+aVuCkNCmBgYHtyfQ0KYWxnb3JpdGhtcyA9IGxpc3QoICAgICAgICAgICAgDQogIEFSNTMgPSBsaXN0KG5hbWU9IkFSIiwgcGFyYW09bGlzdChzdXBwb3J0PTAuMDAwNSwgY29uZmlkZW5jZT0wLjMpKSwNCiAgQVI0MyA9IGxpc3QobmFtZT0iQVIiLCBwYXJhbT1saXN0KHN1cHBvcnQ9MC4wMDA0LCBjb25maWRlbmNlPTAuMykpLA0KICBSQU5ET00gPSBsaXN0KG5hbWU9IlJBTkRPTSIsIHBhcmFtPU5VTEwpLCAjcmFkb23kuoLnjJwg5pyJ6bue5YOPYmFzZWxpbmUgbW9kZWwNCiAgUE9QVUxBUiA9IGxpc3QobmFtZT0iUE9QVUxBUiIsIHBhcmFtPU5VTEwpLCAjcG9wdWxhciDnnIvlk6rku7bmtYHooYzlsLHmjqjolqbku5bpgqPkuIDku7Yg55yL5L2g5rKS6LK35ZOq5qij5oiR5bCx5o6o6Jam57Wm5L2gIOmhnuS8vHNtYXJ0IGJhc2VsaW5lDQogIFVCQ0YgPSBsaXN0KG5hbWU9IlVCQ0YiLCBwYXJhbT1OVUxMKSwgICNwYXJhbT1OVUxM5Luj6KGo5bCx55So5LuW5YCR5Y6f5pys6aCQ6Kit55qE5Y+D5pW4DQogIElCQ0YgPSBsaXN0KG5hbWU9IklCQ0YiLCBwYXJhbT1OVUxMKSApDQpgYGANCg0KIyMjIyMgRTcuIOW7uuaooeOAgemgkOa4rOOAgempl+itiSjmupbnorrmgKcpDQpgYGB7cn0NCmlmKExPQUQpIHsNCiAgbG9hZCgiZGF0YS9yZXN1bHRzMmEucmRhdGEiKQ0KfSBlbHNlIHsNCiAgdDAgPSBTeXMudGltZSgpDQogIHJlc3VsdHMgPSBldmFsdWF0ZSggICAgICAgICAgICANCiAgICBzY2hlbWUsIGFsZ29yaXRobXMsIA0KICAgIHR5cGU9InRvcE5MaXN0IiwgICAgICMgbWV0aG9kIG9mIGV2YWx1YXRpb24NCiAgICBuPWMoNSwgMTAsIDE1LCAyMCkgICAjIG5vLiByZWNvbS4gdG8gYmUgZXZhbHVhdGVkIOavj+WAi+S6uue1puaIkTXlgIsg5oiR6KaB54ycNeS7tjEw5Lu2MTXku7YyMOS7tg0KICAgICkNCiAgcHJpbnQoU3lzLnRpbWUoKSAtIHQwKQ0KICBzYXZlKHJlc3VsdHMsIGZpbGU9ImRhdGEvcmVzdWx0czJhLnJkYXRhIikNCn0NCiMjIEFSIHJ1biBmb2xkL3NhbXBsZSBbbW9kZWwgdGltZS9wcmVkaWN0aW9uIHRpbWVdDQojIyAgIDEgIFs0LjAyc2VjLzIxNC42c2VjXSANCiMjIEFSIHJ1biBmb2xkL3NhbXBsZSBbbW9kZWwgdGltZS9wcmVkaWN0aW9uIHRpbWVdDQojIyAgIDEgIFsxMC40OXNlYy81MzguNXNlY10gDQojIyBSQU5ET00gcnVuIGZvbGQvc2FtcGxlIFttb2RlbCB0aW1lL3ByZWRpY3Rpb24gdGltZV0NCiMjICAgMSAgWzBzZWMvOS40OHNlY10gDQojIyBQT1BVTEFSIHJ1biBmb2xkL3NhbXBsZSBbbW9kZWwgdGltZS9wcmVkaWN0aW9uIHRpbWVdDQojIyAgIDEgIFswc2VjLzExLjA5c2VjXSANCiMjIFVCQ0YgcnVuIGZvbGQvc2FtcGxlIFttb2RlbCB0aW1lL3ByZWRpY3Rpb24gdGltZV0NCiMjICAgMSAgWzBzZWMvNzUuNDJzZWNdIA0KIyMgSUJDRiBydW4gZm9sZC9zYW1wbGUgW21vZGVsIHRpbWUvcHJlZGljdGlvbiB0aW1lXQ0KIyMgICAxICBbMTk4LjJzZWMvMS42M3NlY10gDQojIyBUaW1lIGRpZmZlcmVuY2Ugb2YgMTguNzIgbWlucw0KIyMg6YCZ5q6156iL5byP6KaB6LeRMTDlub7liIbpkJgNCmBgYA0KDQojIyMjIyBFOC4g5qih5Z6L5rqW56K65oCn5q+U6LyDDQoNCuiLpeaOqOiWpjEw5YCL55Si5ZOB77yMQVI1M+ihqOePvuacgOWlve+8jFRydWUgUG9zaXRpdmUgUmF0ZeacgOmrmO+8jEZhbHNlIFBvc2l0aXZlIFJhdGXkuZ/mnIDkvY4NCuiLpeaOqOiWpjE1IG9yIDIw5YCL55Si5ZOB77yM5pW06auU5L6G6KqqSUJDRuaYr+acgOWlveeahA0KYGBge3IgZmlnLmhlaWdodD01LCBmaWcud2lkdGg9NX0NCiMgbG9hZCgiZGF0YS9yZXN1bHRzLnJkYXRhIikNCnBhcihtYXI9Yyg0LDQsMywyKSxjZXg9MC44KQ0KY29scyA9IGMoInJlZCIsICJtYWdlbnRhIiwgImdyYXkiLCAib3JhbmdlIiwgImJsdWUiLCAiZ3JlZW4iKQ0KcGxvdChyZXN1bHRzLCBhbm5vdGF0ZT1jKDEsMyksIGxlZ2VuZD0idG9wbGVmdCIsIHBjaD0xOSwgbHdkPTIsIGNvbD1jb2xzKQ0KYWJsaW5lKHY9c2VxKDAsMC4wMDYsMC4wMDEpLCBoPXNlcSgwLDAuMDgsMC4wMSksIGNvbD0nbGlnaHRncmF5JywgbHR5PTIpDQojIFJPQyBDdXJ2ZSANCiMg54yc5b6X6LaK5aSa5Lit55qE5qmf546H5bCx6LaK5aSnDQojIOeBsOiJsuihqOS6gueMnA0KIyDmqZjoibLpjLZzbWFydCBiYXNlbGluZQ0KIyBUUFLotorpq5jotorlpb0gRlBS6LaK5L2O6LaK5aW9DQojIOavlOi8g+WQhOWAi+ePvuWcqDUgMTAgMTXnmoRUUFLlkoxGUFINCmBgYA0KDQpgYGB7cn0NCmdldENvbmZ1c2lvbk1hdHJpeChyZXN1bHRzJElCQ0YpDQpgYGANCg0KPGJyPjxicj48aHI+PGJyPjxicj48YnI+DQoNCjxzdHlsZT4NCg0KLmNhcHRpb24gew0KICBjb2xvcjogIzc3NzsNCiAgbWFyZ2luLXRvcDogMTBweDsNCn0NCnAgY29kZSB7DQogIHdoaXRlLXNwYWNlOiBpbmhlcml0Ow0KfQ0KcHJlIHsNCiAgd29yZC1icmVhazogbm9ybWFsOw0KICB3b3JkLXdyYXA6IG5vcm1hbDsNCiAgbGluZS1oZWlnaHQ6IDE7DQp9DQpwcmUgY29kZSB7DQogIHdoaXRlLXNwYWNlOiBpbmhlcml0Ow0KfQ0KcCxsaSB7DQogIGZvbnQtZmFtaWx5OiAiVHJlYnVjaGV0IE1TIiwgIuW+rui7n+ato+m7kemrlCIsICJNaWNyb3NvZnQgSmhlbmdIZWkiOw0KfQ0KDQoucnsNCiAgbGluZS1oZWlnaHQ6IDEuMjsNCn0NCg0KLnFpeiB7DQogIGxpbmUtaGVpZ2h0OiAxLjc1Ow0KICBiYWNrZ3JvdW5kOiAjZjBmMGYwOw0KICBib3JkZXItbGVmdDogMTJweCBzb2xpZCAjY2NmZmNjOw0KICBwYWRkaW5nOiA0cHg7DQogIHBhZGRpbmctbGVmdDogMTBweDsNCiAgY29sb3I6ICMwMDk5MDA7DQp9DQoNCnRpdGxlew0KICBjb2xvcjogI2NjMDAwMDsNCiAgZm9udC1mYW1pbHk6ICJUcmVidWNoZXQgTVMiLCAi5b6u6Luf5q2j6buR6auUIiwgIk1pY3Jvc29mdCBKaGVuZ0hlaSI7DQp9DQoNCmJvZHl7DQogIGZvbnQtZmFtaWx5OiAiVHJlYnVjaGV0IE1TIiwgIuW+rui7n+ato+m7kemrlCIsICJNaWNyb3NvZnQgSmhlbmdIZWkiOw0KfQ0KDQpoMSxoMixoMyxoNCxoNXsNCiAgY29sb3I6ICMwMDY2ZmY7DQogIGZvbnQtZmFtaWx5OiAiVHJlYnVjaGV0IE1TIiwgIuW+rui7n+ato+m7kemrlCIsICJNaWNyb3NvZnQgSmhlbmdIZWkiOw0KfQ0KDQoNCmgzew0KICBjb2xvcjogIzAwODgwMDsNCiAgYmFja2dyb3VuZDogI2U2ZmZlNjsNCiAgbGluZS1oZWlnaHQ6IDI7DQogIGZvbnQtd2VpZ2h0OiBib2xkOw0KfQ0KDQpoNXsNCiAgY29sb3I6ICMwMDYwMDA7DQogIGJhY2tncm91bmQ6ICNmOGY4Zjg7DQogIGxpbmUtaGVpZ2h0OiAxLjU7DQogIGZvbnQtd2VpZ2h0OiBib2xkOw0KfQ0KDQouY29tbWVudCB7DQogICAgYm9yZGVyLXJhZGl1czogMTBweDsNCiAgICBib3JkZXI6IDJweCBzb2xpZCAjY2E4ZWZmOw0KICAgIHBhZGRpbmc6IDIwcHg7IA0KfQ0KDQoubmF2IHsNCiAgICBsaXN0LXN0eWxlLXR5cGU6IG5vbmU7DQogICAgbWFyZ2luOiAwOw0KICAgIHBhZGRpbmc6IDA7DQogICAgd2lkdGg6IDIwMHB4Ow0KICAgIGJhY2tncm91bmQtY29sb3I6ICNmMWYxZjE7DQogICAgcG9zaXRpb246IGZpeGVkOw0KICAgIGxlZnQ6IDVweDsNCn0NCg0KbGkgYSB7DQogICAgZGlzcGxheTogYmxvY2s7DQogICAgY29sb3I6ICMwMDA7DQogICAgcGFkZGluZzogOHB4IDE2cHg7DQogICAgdGV4dC1kZWNvcmF0aW9uOiBub25lOw0KfQ0KDQpsaSBhLmFjdGl2ZSB7DQogICAgYmFja2dyb3VuZC1jb2xvcjogIzRDQUY1MDsNCiAgICBjb2xvcjogd2hpdGU7DQp9DQoNCmxpIGE6aG92ZXI6bm90KC5hY3RpdmUpIHsNCiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjNTU1Ow0KICAgIGNvbG9yOiB3aGl0ZTsNCn0NCg0KPC9zdHlsZT4NCg==