1 Bootstrap para Algoritmos de Associação

1.1 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.

1.2 Algoritmo Apriori

Antes de seguirmos, veja o seguinte banco de dados:

Compra Leite Pão Cerveja Refrigerante
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

1.3 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

1.4 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.

1.5 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[]).

1.5.1 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])

1.6 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
NA

1.7 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
NA

1.8 Adicional : Forma Gráfica de Visualizar os resultados

Podemos visualizar essas associações, sim, de forma gráfica com


plot(resultado, method = "graph",...)

conseguimos ver quais itens ou set-itens mais se relacionam com o item de consequeência




rules <- boot_apriori(Groceries, "bottled beer", 0.08, 1, 4, 0.001)

top_5_rules <- rules[1:5]

plot(regras[1:5], method = "graph", engine = "igraph", 
      main = "Gráfico de Rede das Top 5 Regras")

1.9 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==