Bootstrap para
Algoritmos de Associação
Introdução
A Mineração de Regras de Associação (também
chamada de Aprendizado de Regras de
Associação) é uma técnica comum usada para identificar a
interdependência desconhecida dos dados e
descobrir as regras entre esses itens.
Um bom algoritmo de mineração de padrões em dados são
aqueles que capturam regras de associação
entre itens ou variáveis. Essas regras são como “if-then”, onde “se X,
então Y”, representando as interdependências
buscadas por esses algoritmos. A mais comum e utilizada nas tarefas
atuais é o Algoritmo Apriori, foco de estudo
deste documento.
Algoritmo
Apriori
Antes de seguirmos, veja o seguinte banco de dados:
| 1 |
0 |
1 |
1 |
0 |
| 2 |
1 |
1 |
0 |
0 |
| 3 |
0 |
0 |
1 |
1 |
| 4 |
1 |
0 |
0 |
1 |
O objetivo de um algoritmo de associação é
encontrar regras de alta frequência em bancos
de dados como o nosso, por exemplo:
- “Quem compra leite compra pão”
- “Quem compra cerveja compra refrigerante”
Essas regras ajudam a definir inúmeras estratégias de
comércio e até marketing,
descobrindo regras escondidas e prováveis do comportamento
do consumidor, entre outras possibilidades, além de serem
de fácil interpretação
Para isso, dispomos de dados onde as
entradas (linhas) são as compras, usuários,
dias, etc., e as colunas representam as
covariáveis de interesse, cujos valores serão
binários: 1 para sim e 0 para
não
Com isso, podemos identificar associações de alta probabilidade, que
chamamos de Confiança, onde \(P(B|A)\) ou “Probabilidade de A dado que B
ocorreu”. Mas no nosso contexto, chamamos de A a nossa
consequência e B o nosso
antecedente
Como validar e
definir boas regras?
Dada a grande quantidade de dados de uma base e sua característica
esparsa, costuma-se restringir o número de regras cujos subconjuntos de
itens \(S\) das \(d\) variáveis (por exemplo, \(S = {2, 4, 10}\)) tenham
frequência ou
probabilidade (chamado de suporte) maior que
um limiar \(t\). Isso limita a
quantidade de regras a serem analisadas
A partir disso, o algoritmo Apriori
identifica os conjuntos de itens mais frequentes e, com base neles, gera
as regras de associação. Ele inicia com um
conjunto simples, verifica a frequência dos itens e, caso sejam de alta
frequência (ou seja, cuja frequência mínima foi definida em \(t\)), usa os itens mais frequentes para
formar novos conjuntos cada vez maiores, até que não haja mais conjuntos
possíveis ou que novos conjuntos tenham uma frequência menor que o
limiar definido
Também é possível definir um número mínimo e máximo de itens nos
subconjuntos \(S\). Aumentá-lo ajuda a
descobrir regras de maior frequência, principalmente em datasets mais
extensos, cujos conjuntos menores terão, quase sempre, uma frequência
bem pequena
Levantamento ou
Lift
Há um outro output importante em regras de associação que denominamos
de “Lift” ou
“Levantamento”, que indica quanto o aumento de
A irá aumentar as chances de B. Ou seja, indica um efeito de
substituição, se menor que 1, e
complementar, se maior que 1. A estimativa
criada segue algo como \(P(B|A) /
P(B)\).
Caso seu Lift seja 1, isso indica que os
itens são independentes entre si.
Sobre o projeto
No meu exemplo, vamos usar dados já produzidos e tratados do dataset
Groceries do pacote arules do R.
Vamos explorar como montar nosso Algoritmo
Apriori e, posteriormente, aplicar um
bootstrap no algoritmo para gerar
robustez ao processo.
A função apriori básica é dada por:
regras <- apriori(data,
parameter = list(supp = 0.005, conf = 0.5, maxlen = 3))
arules::inspect(regras[1:5]) #mostrando apenas as primeiras 5 linhas
Onde "supp" é o suporte, ou
limiar \(t\), "conf"
representa a nossa probabilidade mínima
buscada, e "maxlen" define o tamanho
máximo do suporte.
Para inspecionarmos, usamos a função
arules::inspect(modelo[]).
Por que
Bootstrap?
Bootstrap é uma técnica de
reamostragemcom reposição de uma amostra. Esse
processo gera robustez nos resultados e cria
uma maior confiabilidade de que essas regras
se manterão para novos datasets. Portanto, o
Bootstrap é um dos métodos mais eficientes e
poderosos no campo estatístico para mitigar
possíveis erros e regressões à média. No entanto, lembre-se de que, para
aplicá-lo corretamente, sua amostra deve ser bem
representativa e relativamente
grande.
#Carregando nossos dados
data("Groceries")
class(Groceries)
[1] "transactions"
attr(,"package")
[1] "arules"
#criando nosso algoritmo do pacote Arules (apriori)
regras <- apriori(Groceries, parameter = list(supp = 0.005, conf = 0.5,
maxlen = 3),
control = list(verbose = FALSE))
arules::inspect(regras[1:5])
Output do Inspect -
Tabela de Associação
O output gerado pela função inspect() do pacote
arules mostra as regras de associação
extraídas, com as seguintes colunas:
- lhs: Representa os antecedentes da
regra, ou seja, o conjunto de itens que devem ser consumidos antes para
que a consequência ocorra.
- rhs: Refere-se às consequências da
regra, ou seja, os itens que tendem a ser consumidos quando os itens no
lhs são consumidos.
- support: Indica a frequência com
que a associação entre o conjunto de lhs e
rhs ocorre nas transações. É a proporção de transações
que contêm tanto o lhs quanto o
rhs.
- confidence: Reflete a
probabilidade de que o rhs seja
consumido quando o lhs já foi consumido. Em outras
palavras, a confidence é a probabilidade condicional de
observar rhs dado lhs.
- lift: Mede a força da associação entre os itens,
considerando a probabilidade de ocorrência dos itens em
relação à sua ocorrência isolada. Um lift maior que 1
indica que os itens no lhs e rhs são
complementares, ou seja, um aumento na frequência de
consumo de um item aumenta a probabilidade de consumir o outro. Um
lift menor que 1 indica que os itens são
substitutivos, ou seja, o aumento no consumo de um item
diminui a probabilidade de consumir o outro.
Conseguimos, por exemplo, notar que
quase 1% das compras pessoas que compram fermento em pó acabam comprando
leite com 52% de probabilidade e são produtos
complementares
Podemos organizar a tabela de resultados do
algoritmo com o parâmetro "by" e também
aumentar o número de linhas exibidas.
regras <- sort(regras, by = "confidence", decreasing = TRUE)
arules:: inspect(regras[1:8])
Também é possível escolher mostrar apenas regras com algum
antecedente específico utilizando o parâmetro
"lhs", ou com consequência
específica através do parâmetro "rhs". No nosso caso, vamos
mostrar todos os resultados com Cerveja como
consequência
regras <- apriori(data = Groceries,
parameter = list(supp = 0.001, conf = 0.08),
appearance = list(default = "lhs",
rhs = "bottled beer"),
control = list(verbose = FALSE))
regras <- sort(regras, by = "confidence", decreasing = TRUE)
arules:: inspect(regras[1:5])
tabela_regras <- as(regras[1:5], "data.frame")
# Criar tabela estilizada com ajustes
gt_table <- tabela_regras %>%
dplyr::select(rules, support, confidence, lift) %>%
mutate(
support = round(support * 100, 4), # Transformar em % e arredondar para 4 casas
confidence = round(confidence * 100, 2), # Transformar em % e arredondar para 2 casas
lift = round(lift, 2) # Arredondar lift para 2 casas
) %>%
rename(
"Regras" = rules,
"Suporte (%)" = support,
"Confiança (%)" = confidence,
"Lift" = lift
) %>%
gt() %>%
tab_header(
title = "Regras de Associação com Cerveja como Consequência",
subtitle = "Base de Dados: Groceries"
) %>%
tab_style(
style = cell_borders(
sides = "bottom",
color = "tomato",
weight = px(3)
), # Adicionar borda inferior para simular sublinhado
locations = cells_body(rows = 1) # Aplicar apenas à primeira linha
) %>%
tab_style(
style = cell_text(weight = "bold"), # Negrito nos nomes das colunas
locations = cells_column_labels()
)
# Exibir tabela
gt_table
| Regras de Associação com Cerveja como Consequência |
| Base de Dados: Groceries |
| Regras |
Suporte (%) |
Confiança (%) |
Lift |
| {liquor,red/blush wine} => {bottled beer} |
0.1932 |
90.48 |
11.24 |
| {soda,liquor} => {bottled beer} |
0.1220 |
57.14 |
7.10 |
| {liquor} => {bottled beer} |
0.4677 |
42.20 |
5.24 |
| {herbs,bottled water} => {bottled beer} |
0.1220 |
40.00 |
4.97 |
| {whole milk,soups} => {bottled beer} |
0.1118 |
37.93 |
4.71 |
Bootstrap
Vamos criar uma função para implementar o
algoritmo utilizando dados
amostrados. Em seguida, reescreveremos essa
função utilizando um laço for, capturando os
resultados e calculando os
intervalosdeconfiança para cada
métrica de interesse
set.seed(15)
library(tidyr)
library(Hmisc)
boot_apriori <- function(data, rhs, confidence, minlen, maxlen, supp){
# Criando a amostra bootstrap
bootstrap_sample <- function(data) {
sampled_ids <- sample(seq_along(data), replace = TRUE)
sampled_transactions <- data[sampled_ids]
return(sampled_transactions)
}
boot_data <- bootstrap_sample(data = data)
#Ajuste de parâmetros
boot_rules_eh <- apriori(boot_data,
parameter = list(confidence = confidence, minlen=minlen,
maxlen = maxlen, supp = supp),
appearance = list(default = "lhs",
rhs = rhs),
control = list(verbose = FALSE))
## Encontrando regras redundantes e excluindo elas
boot_rules_eh.sorted <- sort(boot_rules_eh, by = "confidence")
boot_subset.matrix<-is.subset(boot_rules_eh.sorted,boot_rules_eh.sorted)
boot_subset.matrix[lower.tri(boot_subset.matrix,diag=T)]<-F
if(length(boot_subset.matrix) > 0){
boot_redundant<-colSums(boot_subset.matrix)>=1
boot_rules_eh.pruned<-boot_rules_eh.sorted[!boot_redundant]
} else {
boot_rules_eh.pruned <- boot_rules_eh
}
return(boot_rules_eh.pruned)
}
n_iterations <- 100
# Lista para armazenar os resultados de cada iteração
results_list <- vector("list", n_iterations)
#Laço for - lembrandoq
for (i in 1:n_iterations) {
rules <- boot_apriori(Groceries,"bottled beer",0.08, 1, 4, 0.001)
# Transformar as regras em data frame e armazenar métricas de interesse
rules_df <- as(rules, "data.frame")
rules_df$lhs <- labels(lhs(rules))
rules_df$rhs <- labels(rhs(rules))
results_list[[i]] <- rules_df
}
# Combinar resultados em um único data frame
results_final <- bind_rows(results_list)
# Calcular estatísticas com IC para cada regra usando a biblioteca Hmisc
results_summary <- results_final %>%
group_by(lhs, rhs) %>%
summarise(
N = n(),
confidence_ci = list(smean.cl.normal(confidence)),
support_ci = list(smean.cl.normal(support)),
lift_ci = list(smean.cl.normal(lift))
) %>%
unnest_wider(confidence_ci, names_sep = "_") %>%
unnest_wider(support_ci, names_sep = "_") %>%
unnest_wider(lift_ci, names_sep = "_")
# Exibir resultados
results_summary[1:10] #exibingo as 10 primeiras linhas
Referências
Izbicki, R., & dos Santos, T. M. (2021). *Aprendizado de Máquina:
Uma Abordagem Estatística*. Parte III, Capítulo 12.
University of Virginia Library. (n.d.). *Bootstrapped Association
Rule Mining in R*. Disponível em: https://library.virginia.edu/data/articles/bootstrapped-association-rule-mining-r
CIRP Conference. (2021). Association rules mining in R for product
performance management in Industry 4.0 products for an assembly oriented
product family identification. *11th CIRP Conference on Industrial
Product-Service Systems*. Disponível em: ScienceDirect.
LS0tDQp0aXRsZTogIkFzc29jaWF0aW9uIFJ1bGVzIg0KYXV0aG9yOiAiTHVpcyBILiBCLiBWZWxvc28iDQpkYXRlOiAiMjAyNS0wMS0wMiINCm91dHB1dDogDQogIGh0bWxfbm90ZWJvb2s6IA0KICAgIHRvY19kZXB0aDogNg0KICAgIGhpZ2hsaWdodDogdGFuZ28NCiAgICB0aGVtZTogam91cm5hbA0KICAgIGZpZ193aWR0aDogMTINCiAgICBmaWdfaGVpZ2h0OiA1DQogICAgZmlnX2NhcHRpb246IHRydWUNCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUNCiAgICB0b2M6IHRydWUNCi0tLQ0KDQpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0NCg0KbGlicmFyeShhcnVsZXMpDQpsaWJyYXJ5KGFydWxlc1ZpeikNCmxpYnJhcnkoZ3QpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGRwbHlyKQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQ0KDQpgYGANCg0KIyBCb290c3RyYXAgcGFyYSAqQWxnb3JpdG1vcyBkZSBBc3NvY2lhw6fDo28qDQoNCiMjIEludHJvZHXDp8Ojbw0KDQpBICoqKk1pbmVyYcOnw6NvIGRlIFJlZ3JhcyBkZSBBc3NvY2lhw6fDo28qKiogKHRhbWLDqW0gY2hhbWFkYSBkZSAqKipBcHJlbmRpemFkbyBkZSBSZWdyYXMgZGUgQXNzb2NpYcOnw6NvKioqKSDDqSB1bWEgdMOpY25pY2EgY29tdW0gdXNhZGEgcGFyYSBpZGVudGlmaWNhciBhICoqKmludGVyZGVwZW5kw6puY2lhIGRlc2NvbmhlY2lkYSoqKiBkb3MgZGFkb3MgZSBkZXNjb2JyaXIgYXMgcmVncmFzIGVudHJlIGVzc2VzIGl0ZW5zLg0KDQpVbSBib20gYWxnb3JpdG1vIGRlICptaW5lcmHDp8OjbyBkZSBwYWRyw7VlcyogZW0gZGFkb3Mgc8OjbyBhcXVlbGVzIHF1ZSBjYXB0dXJhbSAqKipyZWdyYXMqKiBkZSBhc3NvY2lhw6fDo28qIGVudHJlIGl0ZW5zIG91IHZhcmnDoXZlaXMuIEVzc2FzIHJlZ3JhcyBzw6NvIGNvbW8gImlmLXRoZW4iLCBvbmRlICJzZSBYLCBlbnTDo28gWSIsIHJlcHJlc2VudGFuZG8gYXMgKioqaW50ZXJkZXBlbmTDqm5jaWFzKioqIGJ1c2NhZGFzIHBvciBlc3NlcyBhbGdvcml0bW9zLiBBIG1haXMgY29tdW0gZSB1dGlsaXphZGEgbmFzIHRhcmVmYXMgYXR1YWlzIMOpIG8gKioqQWxnb3JpdG1vIEFwcmlvcmkqKiosIGZvY28gZGUgZXN0dWRvIGRlc3RlIGRvY3VtZW50by4NCg0KIyMgQWxnb3JpdG1vIEFwcmlvcmkNCg0KQW50ZXMgZGUgc2VndWlybW9zLCB2ZWphIG8gc2VndWludGUgKmJhbmNvIGRlIGRhZG9zKjoNCg0KfCBDb21wcmEgfCBMZWl0ZSB8IFDDo28gfCBDZXJ2ZWphIHwgUmVmcmlnZXJhbnRlIHwNCnwtLS0tLS0tLXwtLS0tLS0tfC0tLS0tfC0tLS0tLS0tLXwtLS0tLS0tLS0tLS0tLXwNCnwgMSAgICAgIHwgMCAgICAgfCAxICAgfCAxICAgICAgIHwgMCAgICAgICAgICAgIHwNCnwgMiAgICAgIHwgMSAgICAgfCAxICAgfCAwICAgICAgIHwgMCAgICAgICAgICAgIHwNCnwgMyAgICAgIHwgMCAgICAgfCAwICAgfCAxICAgICAgIHwgMSAgICAgICAgICAgIHwNCnwgNCAgICAgIHwgMSAgICAgfCAwICAgfCAwICAgICAgIHwgMSAgICAgICAgICAgIHwNCg0KTyBvYmpldGl2byBkZSB1bSAqKiphbGdvcml0bW8gZGUgYXNzb2NpYcOnw6NvKioqIMOpIGVuY29udHJhciAqKipyZWdyYXMgZGUgYWx0YSBmcmVxdcOqbmNpYSoqKiBlbSBiYW5jb3MgZGUgZGFkb3MgY29tbyBvIG5vc3NvLCBwb3IgZXhlbXBsbzoNCg0KLSAgICJRdWVtIGNvbXByYSBsZWl0ZSBjb21wcmEgcMOjbyINCi0gICAiUXVlbSBjb21wcmEgY2VydmVqYSBjb21wcmEgcmVmcmlnZXJhbnRlIg0KDQpFc3NhcyByZWdyYXMgYWp1ZGFtIGEgZGVmaW5pciBpbsO6bWVyYXMgKioqZXN0cmF0w6lnaWFzIGRlIGNvbcOpcmNpbyoqKiAqKmUgYXTDqSAqbWFya2V0aW5nKioqLCBkZXNjb2JyaW5kbyByZWdyYXMgZXNjb25kaWRhcyBlIHByb3bDoXZlaXMgZG8gKioqY29tcG9ydGFtZW50byBkbyBjb25zdW1pZG9yKioqLCBlbnRyZSBvdXRyYXMgcG9zc2liaWxpZGFkZXMsIGFsw6ltIGRlIHNlcmVtIGRlIGbDoWNpbCAqKippbnRlcnByZXRhw6fDo28qKioNCg0KUGFyYSBpc3NvLCBkaXNwb21vcyBkZSBkYWRvcyBvbmRlIGFzICoqKmVudHJhZGFzKioqIChsaW5oYXMpIHPDo28gYXMgY29tcHJhcywgdXN1w6FyaW9zLCBkaWFzLCBldGMuLCBlIGFzICpjb2x1bmFzKiByZXByZXNlbnRhbSBhcyAqKipjb3ZhcmnDoXZlaXMqKiBkZSBpbnRlcmVzc2UqLCBjdWpvcyB2YWxvcmVzIHNlcsOjbyBiaW7DoXJpb3M6IDEgcGFyYSAqKipzaW0qKiogZSAwIHBhcmEgKioqbsOjbyoqKg0KDQpDb20gaXNzbywgcG9kZW1vcyBpZGVudGlmaWNhciBhc3NvY2lhw6fDtWVzIGRlIGFsdGEgcHJvYmFiaWxpZGFkZSwgcXVlIGNoYW1hbW9zIGRlICoqKkNvbmZpYW7Dp2EqKiosIG9uZGUgJFAoQnxBKSQgb3UgIlByb2JhYmlsaWRhZGUgZGUgQSBkYWRvIHF1ZSBCIG9jb3JyZXUiLiBNYXMgbm8gbm9zc28gY29udGV4dG8sIGNoYW1hbW9zIGRlIEEgYSBub3NzYSAqKipjb25zZXF1w6puY2lhKioqIGUgQiBvIG5vc3NvICoqKmFudGVjZWRlbnRlKioqDQoNCiMjIENvbW8gdmFsaWRhciBlIGRlZmluaXIgYm9hcyByZWdyYXM/DQoNCkRhZGEgYSBncmFuZGUgcXVhbnRpZGFkZSBkZSBkYWRvcyBkZSB1bWEgYmFzZSBlIHN1YSBjYXJhY3RlcsOtc3RpY2EgZXNwYXJzYSwgY29zdHVtYS1zZSByZXN0cmluZ2lyIG8gbsO6bWVybyBkZSByZWdyYXMgY3Vqb3Mgc3ViY29uanVudG9zIGRlIGl0ZW5zICRTJCBkYXMgJGQkIHZhcmnDoXZlaXMgKHBvciBleGVtcGxvLCAkUyA9IHsyLCA0LCAxMH0kKSB0ZW5oYW0gKioqZnJlcXXDqm5jaWEqKiogKipvdSAqcHJvYmFiaWxpZGFkZSoqKiAoY2hhbWFkbyBkZSAqc3Vwb3J0ZSopIG1haW9yIHF1ZSB1bSBsaW1pYXIgJHQkLiBJc3NvIGxpbWl0YSBhIHF1YW50aWRhZGUgZGUgcmVncmFzIGEgc2VyZW0gYW5hbGlzYWRhcw0KDQpBIHBhcnRpciBkaXNzbywgbyBhbGdvcml0bW8gKioqQXByaW9yaSoqKiBpZGVudGlmaWNhIG9zIGNvbmp1bnRvcyBkZSBpdGVucyBtYWlzIGZyZXF1ZW50ZXMgZSwgY29tIGJhc2UgbmVsZXMsIGdlcmEgYXMgKioqcmVncmFzIGRlIGFzc29jaWHDp8OjbyoqKi4gRWxlIGluaWNpYSBjb20gdW0gY29uanVudG8gc2ltcGxlcywgdmVyaWZpY2EgYSBmcmVxdcOqbmNpYSBkb3MgaXRlbnMgZSwgY2FzbyBzZWphbSBkZSBhbHRhIGZyZXF1w6puY2lhIChvdSBzZWphLCBjdWphIGZyZXF1w6puY2lhIG3DrW5pbWEgZm9pIGRlZmluaWRhIGVtICR0JCksIHVzYSBvcyBpdGVucyBtYWlzIGZyZXF1ZW50ZXMgcGFyYSBmb3JtYXIgbm92b3MgY29uanVudG9zIGNhZGEgdmV6IG1haW9yZXMsIGF0w6kgcXVlIG7Do28gaGFqYSBtYWlzIGNvbmp1bnRvcyBwb3Nzw612ZWlzIG91IHF1ZSBub3ZvcyBjb25qdW50b3MgdGVuaGFtIHVtYSBmcmVxdcOqbmNpYSBtZW5vciBxdWUgbyBsaW1pYXIgZGVmaW5pZG8NCg0KVGFtYsOpbSDDqSBwb3Nzw612ZWwgZGVmaW5pciB1bSBuw7ptZXJvIG3DrW5pbW8gZSBtw6F4aW1vIGRlIGl0ZW5zIG5vcyBzdWJjb25qdW50b3MgJFMkLiBBdW1lbnTDoS1sbyBhanVkYSBhIGRlc2NvYnJpciByZWdyYXMgZGUgbWFpb3IgZnJlcXXDqm5jaWEsIHByaW5jaXBhbG1lbnRlIGVtIGRhdGFzZXRzIG1haXMgZXh0ZW5zb3MsIGN1am9zIGNvbmp1bnRvcyBtZW5vcmVzIHRlcsOjbywgcXVhc2Ugc2VtcHJlLCB1bWEgZnJlcXXDqm5jaWEgYmVtIHBlcXVlbmENCg0KIyMgTGV2YW50YW1lbnRvIG91ICpMaWZ0Kg0KDQpIw6EgdW0gb3V0cm8gb3V0cHV0IGltcG9ydGFudGUgZW0gcmVncmFzIGRlIGFzc29jaWHDp8OjbyBxdWUgZGVub21pbmFtb3MgZGUgIioqKkxpZnQqKioiIG91ICIqKipMZXZhbnRhbWVudG8qKioiLCBxdWUgaW5kaWNhIHF1YW50byBvIGF1bWVudG8gZGUgQSBpcsOhIGF1bWVudGFyIGFzIGNoYW5jZXMgZGUgQi4gT3Ugc2VqYSwgaW5kaWNhIHVtIGVmZWl0byBkZSAqKipzdWJzdGl0dWnDp8OjbyoqKiwgc2UgbWVub3IgcXVlIDEsIGUgKioqY29tcGxlbWVudGFyKioqLCBzZSBtYWlvciBxdWUgMS4gQSBlc3RpbWF0aXZhIGNyaWFkYSBzZWd1ZSBhbGdvIGNvbW8gJFAoQnxBKSAvIFAoQikkLg0KDQpDYXNvIHNldSAqKipMaWZ0KioqIHNlamEgMSwgaXNzbyBpbmRpY2EgcXVlIG9zIGl0ZW5zIHPDo28gKioqaW5kZXBlbmRlbnRlcyoqKiBlbnRyZSBzaS4NCg0KIyMgU29icmUgbyBwcm9qZXRvDQoNCk5vIG1ldSBleGVtcGxvLCB2YW1vcyB1c2FyIGRhZG9zIGrDoSBwcm9kdXppZG9zIGUgdHJhdGFkb3MgZG8gZGF0YXNldCAqKipHcm9jZXJpZXMqKiogZG8gcGFjb3RlIGBhcnVsZXNgIGRvIFIuIFZhbW9zIGV4cGxvcmFyIGNvbW8gbW9udGFyIG5vc3NvICoqKkFsZ29yaXRtbyBBcHJpb3JpKioqIGUsIHBvc3Rlcmlvcm1lbnRlLCBhcGxpY2FyIHVtICoqKmJvb3RzdHJhcCoqKiBubyBhbGdvcml0bW8gcGFyYSBnZXJhciAqKipyb2J1c3RleioqKiBhbyBwcm9jZXNzby4NCg0KQSBmdW7Dp8OjbyBhcHJpb3JpIGLDoXNpY2Egw6kgZGFkYSBwb3I6DQoNCmBgYCByDQpyZWdyYXMgPC0gYXByaW9yaShkYXRhLCANCiAgICAgICAgICBwYXJhbWV0ZXIgPSBsaXN0KHN1cHAgPSAwLjAwNSwgY29uZiA9IDAuNSwgbWF4bGVuID0gMykpDQoNCmFydWxlczo6aW5zcGVjdChyZWdyYXNbMTo1XSkgI21vc3RyYW5kbyBhcGVuYXMgYXMgcHJpbWVpcmFzIDUgbGluaGFzDQpgYGANCg0KT25kZSBgInN1cHAiYCDDqSBvICoqKnN1cG9ydGUqKiosIG91IGxpbWlhciAkdCQsIGAiY29uZiJgIHJlcHJlc2VudGEgYSBub3NzYSAqKipwcm9iYWJpbGlkYWRlIG3DrW5pbWEqKiogYnVzY2FkYSwgZSBgIm1heGxlbiJgIGRlZmluZSBvICoqKnRhbWFuaG8gbcOheGltbyoqKiBkbyBzdXBvcnRlLg0KDQpQYXJhIGluc3BlY2lvbmFybW9zLCB1c2Ftb3MgYSBmdW7Dp8OjbyBgYXJ1bGVzOjppbnNwZWN0KG1vZGVsb1tdKWAuDQoNCiMjIyBQb3IgcXVlICpCb290c3RyYXAqPw0KDQoqKipCb290c3RyYXAqKiogw6kgdW1hIHTDqWNuaWNhIGRlICoqKnJlYW1vc3RyYWdlbWNvbSByZXBvc2nDp8OjbyoqKiBkZSB1bWEgYW1vc3RyYS4gRXNzZSBwcm9jZXNzbyBnZXJhICoqKnJvYnVzdGV6KioqIG5vcyByZXN1bHRhZG9zIGUgY3JpYSB1bWEgbWFpb3IgKioqY29uZmlhYmlsaWRhZGUqKiogZGUgcXVlIGVzc2FzIHJlZ3JhcyBzZSBtYW50ZXLDo28gcGFyYSBub3ZvcyBkYXRhc2V0cy4gUG9ydGFudG8sIG8gKioqQm9vdHN0cmFwKioqIMOpIHVtIGRvcyBtw6l0b2RvcyBtYWlzIGVmaWNpZW50ZXMgZSBwb2Rlcm9zb3Mgbm8gY2FtcG8gKioqZXN0YXTDrXN0aWNvKioqIHBhcmEgbWl0aWdhciBwb3Nzw612ZWlzIGVycm9zIGUgcmVncmVzc8O1ZXMgw6AgbcOpZGlhLiBObyBlbnRhbnRvLCBsZW1icmUtc2UgZGUgcXVlLCBwYXJhIGFwbGljw6EtbG8gY29ycmV0YW1lbnRlLCBzdWEgYW1vc3RyYSBkZXZlIHNlciAqKipiZW0gcmVwcmVzZW50YXRpdmEqKiogKiplICpyZWxhdGl2YW1lbnRlIGdyYW5kZSoqKi4NCg0KYGBge3IgY2FycywgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KDQojQ2FycmVnYW5kbyBub3Nzb3MgZGFkb3MNCmRhdGEoIkdyb2NlcmllcyIpDQoNCmNsYXNzKEdyb2NlcmllcykNCg0KI2NyaWFuZG8gbm9zc28gYWxnb3JpdG1vIGRvIHBhY290ZSBBcnVsZXMgKGFwcmlvcmkpDQpyZWdyYXMgPC0gYXByaW9yaShHcm9jZXJpZXMsIHBhcmFtZXRlciA9IGxpc3Qoc3VwcCA9IDAuMDA1LCBjb25mID0gMC41LA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heGxlbiA9IDMpLCANCiAgICAgICAgICAgICAgICAgIGNvbnRyb2wgPSBsaXN0KHZlcmJvc2UgPSBGQUxTRSkpDQoNCmFydWxlczo6aW5zcGVjdChyZWdyYXNbMTo1XSkNCmBgYA0KDQojIyBPdXRwdXQgZG8gSW5zcGVjdCAtIFRhYmVsYSBkZSBBc3NvY2lhw6fDo28NCg0KTyBvdXRwdXQgZ2VyYWRvIHBlbGEgZnVuw6fDo28gYGluc3BlY3QoKWAgZG8gcGFjb3RlIGBhcnVsZXNgIG1vc3RyYSBhcyAqKnJlZ3JhcyBkZSBhc3NvY2lhw6fDo28qKiBleHRyYcOtZGFzLCBjb20gYXMgc2VndWludGVzIGNvbHVuYXM6DQoNCi0gICAqKmxocyoqOiBSZXByZXNlbnRhIG9zICoqYW50ZWNlZGVudGVzKiogZGEgcmVncmEsIG91IHNlamEsIG8gY29uanVudG8gZGUgaXRlbnMgcXVlIGRldmVtIHNlciBjb25zdW1pZG9zIGFudGVzIHBhcmEgcXVlIGEgY29uc2VxdcOqbmNpYSBvY29ycmEuDQotICAgKipyaHMqKjogUmVmZXJlLXNlIMOgcyAqKmNvbnNlcXXDqm5jaWFzKiogZGEgcmVncmEsIG91IHNlamEsIG9zIGl0ZW5zIHF1ZSB0ZW5kZW0gYSBzZXIgY29uc3VtaWRvcyBxdWFuZG8gb3MgaXRlbnMgbm8gKipsaHMqKiBzw6NvIGNvbnN1bWlkb3MuDQotICAgKipzdXBwb3J0Kio6IEluZGljYSBhICoqZnJlcXXDqm5jaWEqKiBjb20gcXVlIGEgYXNzb2NpYcOnw6NvIGVudHJlIG8gY29uanVudG8gZGUgKipsaHMqKiBlICoqcmhzKiogb2NvcnJlIG5hcyB0cmFuc2HDp8O1ZXMuIMOJIGEgcHJvcG9yw6fDo28gZGUgdHJhbnNhw6fDtWVzIHF1ZSBjb250w6ptIHRhbnRvIG8gKipsaHMqKiBxdWFudG8gbyAqKnJocyoqLg0KLSAgICoqY29uZmlkZW5jZSoqOiBSZWZsZXRlIGEgKipwcm9iYWJpbGlkYWRlKiogZGUgcXVlIG8gKipyaHMqKiBzZWphIGNvbnN1bWlkbyBxdWFuZG8gbyAqKmxocyoqIGrDoSBmb2kgY29uc3VtaWRvLiBFbSBvdXRyYXMgcGFsYXZyYXMsIGEgKipjb25maWRlbmNlKiogw6kgYSBwcm9iYWJpbGlkYWRlIGNvbmRpY2lvbmFsIGRlIG9ic2VydmFyICoqcmhzKiogZGFkbyAqKmxocyoqLg0KLSAgICoqbGlmdCoqOiBNZWRlIGEgZm9yw6dhIGRhIGFzc29jaWHDp8OjbyBlbnRyZSBvcyBpdGVucywgY29uc2lkZXJhbmRvIGEgKipwcm9iYWJpbGlkYWRlKiogZGUgb2NvcnLDqm5jaWEgZG9zIGl0ZW5zIGVtIHJlbGHDp8OjbyDDoCBzdWEgb2NvcnLDqm5jaWEgaXNvbGFkYS4gVW0gKipsaWZ0KiogbWFpb3IgcXVlIDEgaW5kaWNhIHF1ZSBvcyBpdGVucyBubyAqKmxocyoqIGUgKipyaHMqKiBzw6NvICoqY29tcGxlbWVudGFyZXMqKiwgb3Ugc2VqYSwgdW0gYXVtZW50byBuYSBmcmVxdcOqbmNpYSBkZSBjb25zdW1vIGRlIHVtIGl0ZW0gYXVtZW50YSBhIHByb2JhYmlsaWRhZGUgZGUgY29uc3VtaXIgbyBvdXRyby4gVW0gKipsaWZ0KiogbWVub3IgcXVlIDEgaW5kaWNhIHF1ZSBvcyBpdGVucyBzw6NvICoqc3Vic3RpdHV0aXZvcyoqLCBvdSBzZWphLCBvIGF1bWVudG8gbm8gY29uc3VtbyBkZSB1bSBpdGVtIGRpbWludWkgYSBwcm9iYWJpbGlkYWRlIGRlIGNvbnN1bWlyIG8gb3V0cm8uDQoNCj4gWyoqQ29uc2VndWltb3MsIHBvciBleGVtcGxvLCBub3RhciBxdWUgcXVhc2UgMSUgZGFzIGNvbXByYXMgcGVzc29hcyBxdWUgY29tcHJhbSBmZXJtZW50byBlbSBww7MgYWNhYmFtIGNvbXByYW5kbyBsZWl0ZSBjb20gNTIlIGRlIHByb2JhYmlsaWRhZGUgZSBzw6NvIHByb2R1dG9zIGNvbXBsZW1lbnRhcmVzKipde3N0eWxlPSJjb2xvcjogYmx1ZSJ9DQoNClBvZGVtb3Mgb3JnYW5pemFyIGEgKioqdGFiZWxhIGRlIHJlc3VsdGFkb3MgZG8gYWxnb3JpdG1vKioqIGNvbSBvIHBhcsOibWV0cm8gYCJieSJgIGUgdGFtYsOpbSBhdW1lbnRhciBvIG7Dum1lcm8gZGUgKioqbGluaGFzKioqIGV4aWJpZGFzLg0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpyZWdyYXMgPC0gc29ydChyZWdyYXMsIGJ5ID0gImNvbmZpZGVuY2UiLCBkZWNyZWFzaW5nID0gVFJVRSkNCg0KYXJ1bGVzOjogaW5zcGVjdChyZWdyYXNbMTo4XSkNCmBgYA0KDQpUYW1iw6ltIMOpIHBvc3PDrXZlbCBlc2NvbGhlciBtb3N0cmFyIGFwZW5hcyByZWdyYXMgY29tIGFsZ3VtICoqKmFudGVjZWRlbnRlKioqIGVzcGVjw61maWNvIHV0aWxpemFuZG8gbyBwYXLDom1ldHJvIGAibGhzImAsIG91IGNvbSAqKipjb25zZXF1w6puY2lhKioqIGVzcGVjw61maWNhIGF0cmF2w6lzIGRvIHBhcsOibWV0cm8gYCJyaHMiYC4gTm8gbm9zc28gY2FzbywgdmFtb3MgbW9zdHJhciB0b2RvcyBvcyByZXN1bHRhZG9zIGNvbSAqKipDZXJ2ZWphKioqIGNvbW8gY29uc2VxdcOqbmNpYQ0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQpyZWdyYXMgPC0gYXByaW9yaShkYXRhID0gR3JvY2VyaWVzLA0KICAgICAgICAgICAgICAgICAgcGFyYW1ldGVyID0gbGlzdChzdXBwID0gMC4wMDEsIGNvbmYgPSAwLjA4KSwNCiAgICAgICAgICAgICAgICAgIGFwcGVhcmFuY2UgPSBsaXN0KGRlZmF1bHQgPSAibGhzIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByaHMgPSAiYm90dGxlZCBiZWVyIiksDQogICAgICAgICAgICAgICAgICBjb250cm9sID0gbGlzdCh2ZXJib3NlID0gRkFMU0UpKQ0KDQpyZWdyYXMgPC0gc29ydChyZWdyYXMsIGJ5ID0gImNvbmZpZGVuY2UiLCBkZWNyZWFzaW5nID0gVFJVRSkNCmFydWxlczo6IGluc3BlY3QocmVncmFzWzE6NV0pDQpgYGANCg0KYGBge3IgZWNobz1UUlVFLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQ0KDQp0YWJlbGFfcmVncmFzIDwtIGFzKHJlZ3Jhc1sxOjVdLCAiZGF0YS5mcmFtZSIpDQoNCg0KIyBDcmlhciB0YWJlbGEgZXN0aWxpemFkYSBjb20gYWp1c3Rlcw0KZ3RfdGFibGUgPC0gdGFiZWxhX3JlZ3JhcyAlPiUNCiAgZHBseXI6OnNlbGVjdChydWxlcywgc3VwcG9ydCwgY29uZmlkZW5jZSwgbGlmdCkgJT4lDQogIG11dGF0ZSgNCiAgICBzdXBwb3J0ID0gcm91bmQoc3VwcG9ydCAqIDEwMCwgNCksICMgVHJhbnNmb3JtYXIgZW0gJSBlIGFycmVkb25kYXIgcGFyYSA0IGNhc2FzDQogICAgY29uZmlkZW5jZSA9IHJvdW5kKGNvbmZpZGVuY2UgKiAxMDAsIDIpLCAjIFRyYW5zZm9ybWFyIGVtICUgZSBhcnJlZG9uZGFyIHBhcmEgMiBjYXNhcw0KICAgIGxpZnQgPSByb3VuZChsaWZ0LCAyKSAjIEFycmVkb25kYXIgbGlmdCBwYXJhIDIgY2FzYXMNCiAgKSAlPiUNCiAgcmVuYW1lKA0KICAgICJSZWdyYXMiID0gcnVsZXMsDQogICAgIlN1cG9ydGUgKCUpIiA9IHN1cHBvcnQsDQogICAgIkNvbmZpYW7Dp2EgKCUpIiA9IGNvbmZpZGVuY2UsDQogICAgIkxpZnQiID0gbGlmdA0KICApICU+JQ0KICBndCgpICU+JQ0KICB0YWJfaGVhZGVyKA0KICAgIHRpdGxlID0gIlJlZ3JhcyBkZSBBc3NvY2lhw6fDo28gY29tIENlcnZlamEgY29tbyBDb25zZXF1w6puY2lhIiwNCiAgICBzdWJ0aXRsZSA9ICJCYXNlIGRlIERhZG9zOiBHcm9jZXJpZXMiDQogICkgJT4lDQogIHRhYl9zdHlsZSgNCiAgICBzdHlsZSA9IGNlbGxfYm9yZGVycygNCiAgICAgIHNpZGVzID0gImJvdHRvbSIsDQogICAgICBjb2xvciA9ICJ0b21hdG8iLA0KICAgICAgd2VpZ2h0ID0gcHgoMykNCiAgICApLCAjIEFkaWNpb25hciBib3JkYSBpbmZlcmlvciBwYXJhIHNpbXVsYXIgc3VibGluaGFkbw0KICAgIGxvY2F0aW9ucyA9IGNlbGxzX2JvZHkocm93cyA9IDEpICMgQXBsaWNhciBhcGVuYXMgw6AgcHJpbWVpcmEgbGluaGENCiAgKSAlPiUNCiAgdGFiX3N0eWxlKA0KICAgIHN0eWxlID0gY2VsbF90ZXh0KHdlaWdodCA9ICJib2xkIiksICMgTmVncml0byBub3Mgbm9tZXMgZGFzIGNvbHVuYXMNCiAgICBsb2NhdGlvbnMgPSBjZWxsc19jb2x1bW5fbGFiZWxzKCkNCiAgKQ0KDQojIEV4aWJpciB0YWJlbGENCmd0X3RhYmxlDQoNCmBgYA0KDQojIyBCb290c3RyYXANCg0KVmFtb3MgY3JpYXIgdW1hIGZ1bsOnw6NvIHBhcmEgaW1wbGVtZW50YXIgbyAqKiphbGdvcml0bW8qKiogdXRpbGl6YW5kbyBkYWRvcyAqKiphbW9zdHJhZG9zKioqLiBFbSBzZWd1aWRhLCByZWVzY3JldmVyZW1vcyBlc3NhIGZ1bsOnw6NvIHV0aWxpemFuZG8gdW0gbGHDp28gYGZvcmAsIGNhcHR1cmFuZG8gb3MgKioqcmVzdWx0YWRvcyoqKiBlIGNhbGN1bGFuZG8gb3MgKioqaW50ZXJ2YWxvc2RlY29uZmlhbsOnYSoqKiBwYXJhIGNhZGEgKioqbcOpdHJpY2EqKiogZGUgaW50ZXJlc3NlDQoNCmBgYHtyIGVjaG89VFJVRSwgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0NCg0Kc2V0LnNlZWQoMTUpDQoNCmxpYnJhcnkodGlkeXIpDQpsaWJyYXJ5KEhtaXNjKQ0KDQpib290X2FwcmlvcmkgPC0gZnVuY3Rpb24oZGF0YSwgcmhzLCBjb25maWRlbmNlLCBtaW5sZW4sIG1heGxlbiwgc3VwcCl7DQogICMgQ3JpYW5kbyBhIGFtb3N0cmEgYm9vdHN0cmFwDQogIGJvb3RzdHJhcF9zYW1wbGUgPC0gZnVuY3Rpb24oZGF0YSkgew0KICAgIHNhbXBsZWRfaWRzIDwtIHNhbXBsZShzZXFfYWxvbmcoZGF0YSksIHJlcGxhY2UgPSBUUlVFKSANCiAgICBzYW1wbGVkX3RyYW5zYWN0aW9ucyA8LSBkYXRhW3NhbXBsZWRfaWRzXSANCiAgICByZXR1cm4oc2FtcGxlZF90cmFuc2FjdGlvbnMpDQogIH0NCiAgDQogIA0KICBib290X2RhdGEgPC0gYm9vdHN0cmFwX3NhbXBsZShkYXRhID0gZGF0YSkNCg0KICAjQWp1c3RlIGRlIHBhcsOibWV0cm9zDQogIGJvb3RfcnVsZXNfZWggPC0gYXByaW9yaShib290X2RhdGEsDQogICAgICAgICAgICAgICAgICAgICAgcGFyYW1ldGVyID0gbGlzdChjb25maWRlbmNlID0gY29uZmlkZW5jZSwgbWlubGVuPW1pbmxlbiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heGxlbiA9IG1heGxlbiwgc3VwcCA9IHN1cHApLA0KICAgICAgICAgICAgICAgICAgICAgIGFwcGVhcmFuY2UgPSBsaXN0KGRlZmF1bHQgPSAibGhzIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICByaHMgPSByaHMpLA0KICAgICAgICAgICAgICAgICAgICAgIGNvbnRyb2wgPSBsaXN0KHZlcmJvc2UgPSBGQUxTRSkpDQogIA0KICAjIyBFbmNvbnRyYW5kbyByZWdyYXMgcmVkdW5kYW50ZXMgZSBleGNsdWluZG8gZWxhcw0KICBib290X3J1bGVzX2VoLnNvcnRlZCA8LSBzb3J0KGJvb3RfcnVsZXNfZWgsIGJ5ID0gImNvbmZpZGVuY2UiKQ0KICBib290X3N1YnNldC5tYXRyaXg8LWlzLnN1YnNldChib290X3J1bGVzX2VoLnNvcnRlZCxib290X3J1bGVzX2VoLnNvcnRlZCkgDQogIGJvb3Rfc3Vic2V0Lm1hdHJpeFtsb3dlci50cmkoYm9vdF9zdWJzZXQubWF0cml4LGRpYWc9VCldPC1GDQogIGlmKGxlbmd0aChib290X3N1YnNldC5tYXRyaXgpID4gMCl7DQogICAgYm9vdF9yZWR1bmRhbnQ8LWNvbFN1bXMoYm9vdF9zdWJzZXQubWF0cml4KT49MQ0KICAgIGJvb3RfcnVsZXNfZWgucHJ1bmVkPC1ib290X3J1bGVzX2VoLnNvcnRlZFshYm9vdF9yZWR1bmRhbnRdDQogIH0gZWxzZSB7DQogICAgYm9vdF9ydWxlc19laC5wcnVuZWQgPC0gYm9vdF9ydWxlc19laA0KICB9DQogIHJldHVybihib290X3J1bGVzX2VoLnBydW5lZCkNCn0NCg0KDQpuX2l0ZXJhdGlvbnMgPC0gMTAwDQoNCiMgTGlzdGEgcGFyYSBhcm1hemVuYXIgb3MgcmVzdWx0YWRvcyBkZSBjYWRhIGl0ZXJhw6fDo28NCnJlc3VsdHNfbGlzdCA8LSB2ZWN0b3IoImxpc3QiLCBuX2l0ZXJhdGlvbnMpDQoNCiNMYcOnbyBmb3IgLSBsZW1icmFuZG9xIA0KZm9yIChpIGluIDE6bl9pdGVyYXRpb25zKSB7DQogIHJ1bGVzIDwtIGJvb3RfYXByaW9yaShHcm9jZXJpZXMsImJvdHRsZWQgYmVlciIsMC4wOCwgMSwgNCwgMC4wMDEpDQogICMgVHJhbnNmb3JtYXIgYXMgcmVncmFzIGVtIGRhdGEgZnJhbWUgZSBhcm1hemVuYXIgbcOpdHJpY2FzIGRlIGludGVyZXNzZQ0KICBydWxlc19kZiA8LSBhcyhydWxlcywgImRhdGEuZnJhbWUiKQ0KICBydWxlc19kZiRsaHMgPC0gbGFiZWxzKGxocyhydWxlcykpDQogIHJ1bGVzX2RmJHJocyA8LSBsYWJlbHMocmhzKHJ1bGVzKSkNCiAgcmVzdWx0c19saXN0W1tpXV0gPC0gcnVsZXNfZGYNCn0NCg0KIyBDb21iaW5hciByZXN1bHRhZG9zIGVtIHVtIMO6bmljbyBkYXRhIGZyYW1lDQpyZXN1bHRzX2ZpbmFsIDwtIGJpbmRfcm93cyhyZXN1bHRzX2xpc3QpDQoNCg0KDQojIENhbGN1bGFyIGVzdGF0w61zdGljYXMgY29tIElDIHBhcmEgY2FkYSByZWdyYSB1c2FuZG8gYSBiaWJsaW90ZWNhIEhtaXNjDQpyZXN1bHRzX3N1bW1hcnkgPC0gcmVzdWx0c19maW5hbCAlPiUNCiAgZ3JvdXBfYnkobGhzLCByaHMpICU+JQ0KICBzdW1tYXJpc2UoDQogICAgTiA9IG4oKSwNCiAgICBjb25maWRlbmNlX2NpID0gbGlzdChzbWVhbi5jbC5ub3JtYWwoY29uZmlkZW5jZSkpLA0KICAgIHN1cHBvcnRfY2kgPSBsaXN0KHNtZWFuLmNsLm5vcm1hbChzdXBwb3J0KSksDQogICAgbGlmdF9jaSA9IGxpc3Qoc21lYW4uY2wubm9ybWFsKGxpZnQpKQ0KICApICU+JQ0KICB1bm5lc3Rfd2lkZXIoY29uZmlkZW5jZV9jaSwgbmFtZXNfc2VwID0gIl8iKSAlPiUNCiAgdW5uZXN0X3dpZGVyKHN1cHBvcnRfY2ksIG5hbWVzX3NlcCA9ICJfIikgJT4lDQogIHVubmVzdF93aWRlcihsaWZ0X2NpLCBuYW1lc19zZXAgPSAiXyIpDQoNCiMgRXhpYmlyIHJlc3VsdGFkb3MNCnJlc3VsdHNfc3VtbWFyeVsxOjEwXSAjZXhpYmluZ28gYXMgMTAgcHJpbWVpcmFzIGxpbmhhcw0KDQpgYGANCg0KIyMgQWRpY2lvbmFsIDogRm9ybWEgR3LDoWZpY2EgZGUgVmlzdWFsaXphciBvcyByZXN1bHRhZG9zDQoNClBvZGVtb3MgdmlzdWFsaXphciBlc3NhcyBhc3NvY2lhw6fDtWVzLCBzaW0sIGRlIGZvcm1hIGdyw6FmaWNhIGNvbQ0KDQpgYGAgcg0KDQpwbG90KHJlc3VsdGFkbywgbWV0aG9kID0gImdyYXBoIiwuLi4pDQpgYGANCg0KY29uc2VndWltb3MgdmVyIHF1YWlzIGl0ZW5zIG91IHNldC1pdGVucyBtYWlzIHNlIHJlbGFjaW9uYW0gY29tIG8gaXRlbSBkZSBjb25zZXF1ZcOqbmNpYQ0KDQpgYGB7ciBlY2hvPVRSVUUsIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9DQoNCg0KDQpydWxlcyA8LSBib290X2FwcmlvcmkoR3JvY2VyaWVzLCAiYm90dGxlZCBiZWVyIiwgMC4wOCwgMSwgNCwgMC4wMDEpDQoNCnRvcF81X3J1bGVzIDwtIHJ1bGVzWzE6NV0NCg0KcGxvdChyZWdyYXNbMTo1XSwgbWV0aG9kID0gImdyYXBoIiwgZW5naW5lID0gImlncmFwaCIsIA0KICAgICAgbWFpbiA9ICJHcsOhZmljbyBkZSBSZWRlIGRhcyBUb3AgNSBSZWdyYXMiKQ0KYGBgDQoNCiMjIFJlZmVyw6puY2lhcw0KDQpJemJpY2tpLCBSLiwgJiBkb3MgU2FudG9zLCBULiBNLiAoMjAyMSkuIFwqQXByZW5kaXphZG8gZGUgTcOhcXVpbmE6IFVtYSBBYm9yZGFnZW0gRXN0YXTDrXN0aWNhXCouIFBhcnRlIElJSSwgQ2Fww610dWxvIDEyLg0KDQpVbml2ZXJzaXR5IG9mIFZpcmdpbmlhIExpYnJhcnkuIChuLmQuKS4gXCpCb290c3RyYXBwZWQgQXNzb2NpYXRpb24gUnVsZSBNaW5pbmcgaW4gUlwqLiBEaXNwb27DrXZlbCBlbTogPGh0dHBzOi8vbGlicmFyeS52aXJnaW5pYS5lZHUvZGF0YS9hcnRpY2xlcy9ib290c3RyYXBwZWQtYXNzb2NpYXRpb24tcnVsZS1taW5pbmctcj4NCg0KQ0lSUCBDb25mZXJlbmNlLiAoMjAyMSkuIEFzc29jaWF0aW9uIHJ1bGVzIG1pbmluZyBpbiBSIGZvciBwcm9kdWN0IHBlcmZvcm1hbmNlIG1hbmFnZW1lbnQgaW4gSW5kdXN0cnkgNC4wIHByb2R1Y3RzIGZvciBhbiBhc3NlbWJseSBvcmllbnRlZCBwcm9kdWN0IGZhbWlseSBpZGVudGlmaWNhdGlvbi4gXCoxMXRoIENJUlAgQ29uZmVyZW5jZSBvbiBJbmR1c3RyaWFsIFByb2R1Y3QtU2VydmljZSBTeXN0ZW1zXCouIERpc3BvbsOtdmVsIGVtOiBbU2NpZW5jZURpcmVjdF0oaHR0cHM6Ly9kb2kub3JnL1hYWFhYKS4NCg==