Los grafos

DISCLAIMER

Esta materia no es un curso de grafos per se. El universo del estudio de grafos es enorme en sí mismo (como pasa con muchos temas en esta materia). Por nuestra parte nos va a interesar como herramienta para visualizar y medir relaciones entre los datos. En este sentido, podemos pensar al grafo como una forma de representar datos. Dependiendo de la estrategia de representación, los grafos pueden corresponderse con listas (semiestructurados), tablas (estructurados) o pares de tablas.

¿Qué es un grafo?

Un grafo (también red) es una estructura MUY general en la cual podemos representar relaciones entre objetos. En general lo representamos a través de dos conjuntos: uno de vértices (o nodos) \(V\) y uno de ejes (o conexiones) \(E\). Los vértices representan los objetos que nos interesa estudiar (pueden ser personas, animales, productos o lo que se les ocurra) y los ejes sus relaciones (retweets o espacios laborales compartidos para personas, cadenas tróficas entre animales, ingredientes compartidos entre productos, por nombrar algunas cosas). Lo poderoso de la representación mediante grafos es que permite visualizar la estructura que da soporte a los objetos, y la existencia de una batería enorme de métodos para caracterizar esta estructura. Por ejemplo, hay herramientas para identificar la importancia de cada vértice, los grupos más fuertemente conectados en la red, conexiones que faltan, resistencia de la red a fallos y muchas otras cosas.

La estructura de la red permite incluir más información que sólo los vértices y los ejes. Esto se suele incluir como atributos de los nodos y los ejes (por ejemplo, podríamos querer incluir la edad de las personas como atributo de los vértices, o la cantidad de retweets de una persona a otra como atributo de los ejes).

En nuestro caso, vamos a hacer un análisis exploratorio sobre una red para ver las posibilidades de visualización y caracterización de las correlaciones que implica la estructura de la red sobre sus elementos.

El dataset: tragos del API

Para construir nuestro grafo vamos a usar los datos del API de tragos que vimos en clases pasadas. Por esto, nuestro primer paso va a ser descargar todos los tragos y sus ingredientes. Vamos a descargar iterativamente la información de todos los tragos con letras en letters:

require(jsonlite)
bebidas = NULL
url_base = 'http://www.thecocktaildb.com/api/json/v1/1/search.php?f='
letra = 'a'
for(letra in letters){
  print(paste('Letra:',letra))
  dwld = fromJSON(paste(url_base,letra,sep=''))$drinks
  bebidas = rbind(bebidas,dwld)
  print(paste('Había',nrow(dwld),'bebidas'))
}
[1] "Letra: a"
[1] "Había 25 bebidas"
[1] "Letra: b"
[1] "Había 25 bebidas"
[1] "Letra: c"
[1] "Había 25 bebidas"
[1] "Letra: d"
[1] "Había 18 bebidas"
[1] "Letra: e"
[1] "Había 10 bebidas"
[1] "Letra: f"
[1] "Había 24 bebidas"
[1] "Letra: g"
[1] "Había 25 bebidas"
[1] "Letra: h"
[1] "Había 17 bebidas"
[1] "Letra: i"
[1] "Había 11 bebidas"
[1] "Letra: j"
[1] "Había 13 bebidas"
[1] "Letra: k"
[1] "Había 16 bebidas"
[1] "Letra: l"
[1] "Había 22 bebidas"
[1] "Letra: m"
[1] "Había 25 bebidas"
[1] "Letra: n"
[1] "Había 6 bebidas"
[1] "Letra: o"
[1] "Había 13 bebidas"
[1] "Letra: p"
[1] "Había 24 bebidas"
[1] "Letra: q"
[1] "Había 8 bebidas"
[1] "Letra: r"
[1] "Había 24 bebidas"
[1] "Letra: s"
[1] "Había 25 bebidas"
[1] "Letra: t"
[1] "Había 25 bebidas"
[1] "Letra: u"
[1] "Había  bebidas"
[1] "Letra: v"
[1] "Había 13 bebidas"
[1] "Letra: w"
[1] "Había 11 bebidas"
[1] "Letra: x"
[1] "Había  bebidas"
[1] "Letra: y"
[1] "Había 2 bebidas"
[1] "Letra: z"
[1] "Había 13 bebidas"
bebidas
nrow(bebidas)
[1] 420

Con esto logramos descargar la información de todos nuestros tragos disponibles. El problema es que si se fijan, la información de los ingredientes de las bebidas no está presente de forma clara. Por ejemplo, la primer bebida incluye 4 ingredientes, pero tenemos 15 columnas dedicadas a ingredientes.

bebidas[1,grep('Ingredient',colnames(bebidas))]
sum(!is.na(bebidas[1,grep('Ingredient',colnames(bebidas))]))
[1] 4
length(grep('Ingredient',colnames(bebidas)))
[1] 15

Esto es algo que ocurre normalmente cuando convertimos a un formato tabular información de conexiones: dado que no todos los tragos tienen la misma cantidad de conexiones (no tienen la misma cantidad de ingredientes), nuestro dataset tiene mucho espacio vacío dentro.

¿Cómo podemos llevar esta información a un formato más cómodo? Les dejo dos ideas:

  • Construir un listado de ejes: El listado de ejes es un formato muy común para guardar información de redes, especialmente cuando todos los vertices tienen al menos una conexión. No debe confundirse con una lista como objeto de R. La representación es muy similar a un CSV: en cada fila incorporamos la información que describe un eje: su cola y su cabeza. Mirando la primer fila del dataset que descargamos, podríamos visualizarla así:
A1,Gin
A1,Grand Marnier
A1,Lemon Juice
A1,Grenadine

A la izquierda siempre tenemos un vertice “bebida” y a la derecha un vértice “ingrediente”.

  • Construir una matriz: otra representación muy habitual para grafos es la de matriz de adyacencia. En ese caso, construimos una matriz \(X\) tal que \(X_{i,j}=1\) si hay una conexión entre los vértices \(i\) y \(j\) y \(0\) si no. Esta representación es muy habitual como input de funciones debido a que es muy independiente del lenguaje particular que se use. En nuestro caso particular, si tenemos \(N_T\) tragos y \(N_I\) ingredientes, \(X \in R^{N_T \times N_I}\), y es una matriz rala, llena de ceros. Únicamente tendría 1s en las posiciones \(ij\) que conecten un trago (ingrediente) \(i\) con un ingrediente (trago) \(j\). Noten que en nuestro caso particular, no habría conexiones directas entre tragos o entre ingredientes.

Mientras que el listado de ejes comprime la información, sólo indicando las conexiones que están presentes, mientras que la matriz de adyacencia la expande indicando qué ocurre para cualquier combinación posible de vértices.

Volviendo a nuestro caso particular, veamos como llevarlo a un listado de ejes. Como ejercicio, vamos a hacer usando las herramientas de tidyverse.

require(tidyverse)

Vamos a llevar el dataset original a una lista de ejes:

print(paste('Empezamos con',nrow(bebidas),'filas'))
[1] "Empezamos con 420 filas"
beb_tidy = bebidas %>%  # Al dataframe de bebidas
  select(strDrink,strIngredient1:strIngredient15) %>% # Le seleccionamos los nombres de bebidas y sus ingredientes
  group_by(strDrink) %>% #Avisamos que queremos agrupar las operaciones por bebida
  unite('strIngredients',strIngredient1:strIngredient15,na.rm=TRUE,sep=',') #Unimos las columnas de ingredientes pegandolas con comas
print(paste('Ahora tenemos',nrow(beb_tidy),'filas y', ncol(beb_tidy),'columnas'))
[1] "Ahora tenemos 420 filas y 2 columnas"
beb_ejes = beb_tidy %>% # Al nuevo dataframe
  separate_rows(strIngredients,sep=',') # Lo separamos en filas distintas por cada ingrediente

print(paste('Obtenemos finalmente',nrow(beb_ejes),'ejes'))
[1] "Obtenemos finalmente 1698 ejes"

El único cuidado que no tuvimos hasta acá y nos puede traer problemas es que haya nombres repetidos entre bebidas y ingredientes:

intersect(beb_ejes$strDrink,beb_ejes$strIngredients)
[1] "Limeade" "Rose"   

Para sacarnos ese problema de encima, ponemos en mayúscula los nombres de las bebidas y en minúscula la de los ingredientes:

beb_ejes = beb_ejes %>% # Al dataset de ejes
  mutate(strDrink = toupper(strDrink),strIngredients=tolower(strIngredients))  # Cambiamos la columna strDrink a mayúscula y strIngredients a minúscula
intersect(beb_ejes$strDrink,beb_ejes$strIngredients)
character(0)

Con esto construido, podemos responder algunas preguntas muy sencillas, aún sin pensar todavía en la red. Por ejemplo, ¿cuál es la bebida que más ingredientes usa? ¿cuál menos? ¿cuántos ingredientes usan cada bebida?

beb_ejes %>%
  count(strDrink,name='nIngredients') %>% # Contamos cuantas veces aparece cada bebida, ese es su total de ingredientes
  arrange(desc(nIngredients)) # Lo ordenamos en orden descendente

beb_ejes %>% # Lo mismo pero en orden ascendente
  count(strDrink,name='nIngredients') %>%
  arrange(nIngredients)

beb_ejes %>%
  count(strDrink,name='nIngredients') %>% # Contamos ingredientes
  ungroup() %>% #Desagrupamos para hacer sumarios
  summarise(media=mean(nIngredients),stdev=sd(nIngredients),min=min(nIngredients),max=max(nIngredients)) # Calculamos algunas medidas

beb_ejes %>% # Hacemos un histograma de la cantidad de apariciones de cada ingrediente
  count(strDrink,name='nIngredients') %>%
  ggplot(mapping=aes(x=nIngredients)) +
  geom_histogram(binwidth = 1,fill='white',col='black') + 
  geom_boxplot(width=10,color='blue',lwd=1.5)

La bebida que más ingredientes usa es Egg Nog #4 (¿ponche de huevo?) con 11 ingredientes, y la que menos se disputa entre muchos candidatos, con 2 ingredientes. En promedio las bebidas tienen 4 ingredientes, con un desvío de 1.5 ingredientes (o sea que podemos resumir que el grueso de los datos tienen entre 2.5 y 5.5 ingredientes).

Ejercicio:

Repitan este análisis pero en vez de mirar desde el punto de vista del trago, mirenlo desde los ingredientes. ¿Hay alguna coincidencia entre los valores que obtienen?

Construyendo el grafo

Habiendo explorado brevemente lo que nos dice la lista de ejes por sí sola, avancemos con la vizualización de la estructura que resulta. Para esto vamos a aprovechar dos paquetes buenísimos de R para trabajar con redes: igraph y tidygraph. igraph tiene la mayor parte de lo que cualquier análisis de redes puede necesitar, mientras que tidygraph nos permite aprovechar igraph y tidyverse a la vez.

# Si aún no los instalaron:
# install.packages('igraph')
# install.packages('tidygraph')
require(igraph)
require(tidygraph)

Ahora vamos a armar nuestro objeto grafo usando tidygraph e igraph.

g =  beb_ejes %>%
  graph_from_data_frame(directed = FALSE) %>% # Esta función toma el listado y lo convierte en un objeto grafo
  as_tbl_graph() # Este lo mueve a formato "tidy"
g
# A tbl_graph: 716 nodes and 1698 edges
#
# An undirected multigraph with 1 component
#
# Node Data: 716 × 1 (active)
  name 
  <chr>
1 A1   
2 ABC  
3 ACE  
4 ADAM 
5 AT&T 
6 ACID 
# … with 710 more rows
#
# Edge Data: 1,698 × 2
   from    to
  <int> <int>
1     1   421
2     1   422
3     1   423
# … with 1,695 more rows

Fijense que llevar beb_ejes a un tbl_graph obtenemos un objeto que tiene los dos tipos de información que mencionamos antes: los nodos o vértices ( nodes ) y los ejes ( edges ) en forma de una matriz. Fijense que obtenemos varias medidas resumen desde el punto de partida: tenemos 747 vértices, y 1698 ejes.

Vamos a agregar un poquito extra de información en nuestro grafo, para que incluya el hecho de que nuestros vértices son de distinto tipo:

g = g %>% 
  activate(nodes) %>% # Así le decimos que opere sobre el dataframe de nodos
  mutate(type=ifelse(is.element(name,beb_ejes$strDrink),'Drink','Ingredient')) # Agregamos el tipo de nodo que es
g
# A tbl_graph: 716 nodes and 1698 edges
#
# A bipartite multigraph with 1 component
#
# Node Data: 716 × 2 (active)
  name  type 
  <chr> <chr>
1 A1    Drink
2 ABC   Drink
3 ACE   Drink
4 ADAM  Drink
5 AT&T  Drink
6 ACID  Drink
# … with 710 more rows
#
# Edge Data: 1,698 × 2
   from    to
  <int> <int>
1     1   421
2     1   422
3     1   423
# … with 1,695 more rows
## Podemos chusmear el resultado acá
g %>% activate(nodes) %>% as.data.frame() # Para visualizarlo entero, lo movemos a dataframe
g %>% activate(edges) %>% as.data.frame()
NA

Fijense que luego de esta operación, tidygraph ya detecto que nuestro grafo es bipartite (bipartito). Esto significa que todas las conexiones ocurren entre vértices de distinto tipo (en este caso, Drink y Ingredient).

Hagamos una primer visualización del grafo

# install.packages('ggraph') # Si aún no lo tienen instalado.
require(ggraph)

g %>% 
  ggraph() + # Pasamos el grafo a ggraph (ggplot para grafos)
  geom_edge_link(edge_width=.1,alpha=0.5) + # Indicamos grosor de los ejes y transparencia
  geom_node_point(mapping = aes(color=type,shape=type),size = 1) + # Pedimos color y forma basandose en el tipo de nodo.
  theme_graph()
Using `stress` as default layout

# Si graficamos "arriba" un tipo de nodo y "abajo" al otro:
g %>% activate(nodes) %>% 
  mutate(type=type=='Drink') %>% # La función de layout necesita que sea un vector lógico el "type"
  ggraph(layout='igraph',algorithm='bipartite') +
  geom_edge_link(edge_width=.1,alpha=0.3) +
  geom_node_point(mapping = aes(color=type,shape=type),size = 1) +
  theme_graph()

Observen como la forma en la que acomodamos los vértices (el layout del grafo) nos permite observar estructura en los datos: al acomodar los distintos tipos de vértices (Ingredient arriba y Drink) abajo, la gráfica nos sugiere que no todas las bebidas se conectan por igual a todos los ingredientes, y que no todos los ingredientes se vinculan por igual a todas las bebidas: hay “grupos” que están más conectados que otros.

Ejercicio

Usen la documentación de igraph sobre layouts para encontrar los diferentes layouts

?ggraph # Lean al final de todo en Details
?layout # Lean al final de todo en See Also

Cambiando el argumento de la función de ggraph exploren distintos layouts ¿alguno le ayuda a encontrar estructura en los datos? ¿qué observa?

Distribución de grado

Previamente calculamos la cantidad de ingregientes que cada bebida lleva. Si lo pensamos en términos de la red, esto equivale a hablar de la cantidad de conexiones que tiene cada bebida. A esta magnitud (la cantidad de conexiones que tiene un vértice) se la denomina grado de un vértice, y es la medida de centralidad en una red más común: nos dice de una forma muy simple cuan conectado está un vértice al resto. Una vez definida la red, es muy fácil calcular el grado de cada vértice usando las funciones de igraph:

degree(g,v='EGG NOG #4')
EGG NOG #4 
        11 

Podemos replicar el gráfico que armamos antes contando ejes a mano:

grados = g %>% degree(v=V(g)$type=='Drink') #Así le pedimos el grado de TODAS las BEBIDAS
ggplot(data=data.frame(grados),mapping=aes(x=grados)) + geom_histogram(binwidth = 1) # Pedimos ancho de bin =1 para que nos muestre separado cada grado

Más interesante aún, podemos buscar la distribución de grado de cada tipo de nodo (bebida o ingrediente) y ver si observamos alguna diferencia

g = g %>% activate(nodes) %>%
  mutate(grado=degree(g)) # Ahora sí, directamente agregamos una columna que sea de grados.

g %>% activate(nodes) %>% as.data.frame() %>%
  ggplot(mapping=aes(x=grado,fill=type)) +
  geom_histogram(binwidth = 1) 


g %>% activate(nodes) %>% as.data.frame() %>%
  ggplot(mapping=aes(x=grado,fill=type)) +
  geom_histogram(binwidth = 1) + xlim(c(0,10))

Estas distribuciones son claramente muy distintas: mientras que las bebidas se concentran alrededor de de 4 ingredientes, los ingredientes varían mucho más: algunos pocos son usados en una miriada de bebidas, mientras que la mayoría sólo se usa en unas pocas bebidas.

Incorporar esta información al dibujo del grafo puede ser un poco dificil, sobre todo en imágenes estáticas como estas:

g %>%
  ggraph() + 
  geom_edge_link(edge_width=.1,alpha=0.5) +
  geom_node_point(mapping = aes(color=type,shape=type,size=grado)) + # Pedimos que el size del nodo refiera al grado, y la hacemos a los más grandes más transparentes.
  theme_graph()
Using `stress` as default layout

Por eso también suele ser muy útil explorar herramientas para visualización dinámica. En esta área tenemos el paquete visNetwork

# install.packages('visNetwork')
require(visNetwork)
Loading required package: visNetwork
Registered S3 method overwritten by 'htmlwidgets':
  method           from         
  print.htmlwidget tools:rstudio

Si bien este paquete en sí mismo permite construir redes y explorarlas, desde nuestra perspectiva de usuaries de igraph nos interesa simplemente graficar nuestras redes de forma interactiva.

# visIgraph(g) esto ya nos muestra la red pero de forma poco interesante
g %>% activate(nodes) %>%
  mutate(label=name,color=ifelse(type=='Drink','red','blue'),size=grado,shape=ifelse(type=='Drink','square','triangle')) %>% #Agregamos directamente columnas con color, forma y tamaño para que los lea visNetwork
  visIgraph(layout='layout_nicely',physics=TRUE) %>% # Convertimos a visNetwork y pedimos que tenga movimiento físico
  visPhysics(barnesHut=list('damping'=1)) # Hacemos que el movimiento sea leeeento

NA

En cualquier caso, una visualización óptima cuando se incluyen muchos vértices puede ser muy dificultosa.

Red monopartita

Si nuestro objetivo es entender cómo se relacionan las bebidas entre sí a través de los ingredientes, pero en sí no nos importan los ingredientes, podemos representar la red original ( bipartita ) como una red monopartita, donde conectamos entre sí a las bebidas que comparten ingredientes. Nuevamente, acá vamos a enfocarnos en la exploración del dataset más que en las técnicas para calcularla (aunque pueden preguntar si les da curiosidad). Con igraph lo hacemos como

gD = g %>% activate(nodes) %>% 
  mutate(type=(type=='Drink')) %>% # Igraph necesita que el type sea lógico
  bipartite_projection(which='true') %>%
  as_tbl_graph()
gD
# A tbl_graph: 420 nodes and 14371 edges
#
# An undirected simple graph with 1 component
#
# Node Data: 420 × 2 (active)
  name  grado
  <chr> <dbl>
1 A1        4
2 ABC       3
3 ACE       5
4 ADAM      3
5 AT&T      3
6 ACID      2
# … with 414 more rows
#
# Edge Data: 14,371 × 3
   from    to weight
  <int> <int>  <dbl>
1     1     3      2
2     1     5      1
3     1    13      1
# … with 14,368 more rows

Fijense que igraph nos borro el atributo type (que ya no sería útil ya que la red es monopartita) y agrego un atributo weight a los ejes, que lo que mide es la cantidad de ingredientes en común entre dos bebidas. También se crearon muchísimos ejes nuevos (ahora tenemos del orden de 14000!). La visualización directa de esta red no ayuda mucho, de nuevo debido al número de nodos:

gD %>% 
  ggraph() + 
  geom_edge_link(mapping=aes(edge_width=weight,alpha=1/weight)) + # Ponemos el ancho del link en función al peso de ese link
  geom_node_point(mapping = aes(size=grado,alpha=1/grado),color='blue') +
  theme_graph()
Using `stress` as default layout

Podemos filtrar aquellos ejes que sólo tengan peso mayor a cierto valor:

gD %>% 
  activate(edges) %>%
  filter(weight>3) %>% # Nos quedamos sólo con los ejes que representen al menos tres ingredientes
  ggraph() + 
  geom_edge_link(mapping=aes(edge_width=weight,alpha=1/weight)) +
  geom_node_point(color='blue') +
  theme_graph()
Using `stress` as default layout

Podemos hacer un histograma sobre los pesos de las conexiones, para ver si las bebidas son parecidas o muy distintas entre sí:

gD %>% activate(edges) %>% as.data.frame() %>% 
  ggplot(mapping=aes(x=weight)) +
  geom_histogram(binwidth = 1,fill='white',col='black') 

Este gráfico nos muestra que la mayoría de las bebidas comparten pocos ingredientes. Sin embargo, también hay algunas conexiones grandes, que representan más de 10 ingredientes compartidos.

Ejercicio

Basandose en el histograma, prueben hacer cortes sobre los pesos de los ejes y vean las estructuras que se forman. ¿Qué representan los grupos que obtienen? Elijan un número mínimo de ingredientes compartidos para que dos bebidas sean parecidas y apoyandose en el gráfico, identifiquen que grupos de bebidas obtienen. La función clusters de igraph les puede ser muy útil. Retorna un vector con las componentes conexas de la red.

(gD %>% activate(edges) %>% filter(weight>2) %>% # Tiramos ejes chicos
   clusters())$membership %>% # Pedimos la "membresía" (a qué componente pertenecen) de los nodos
  sort() %>% head(n=15)
# Exploren el resultado de clusters!

Ejercicio (opcional para entusiastas)

Repitan el analisis sobre la red monopartita pero de ingredientes en vez de bebidas. ¿Qué representan las conexiones en esa red? ¿Cuál es el ingrediente más versatil?

Homofilia

Por último vamos a hacer un análisis de homofilia en la red de bebidas. Para eso, vamos a considerar las categorías de los distintos tragos. Necesitamos volver al dataset original para recuperar esta información

table(bebidas$strCategory) # Estas son las categorías

                Beer             Cocktail                Cocoa         Coffee / Tea     Homemade Liqueur Milk / Float / Shake 
                   8                   88                    5                   23                    6                    6 
      Ordinary Drink        Other/Unknown  Punch / Party Drink                 Shot    Soft Drink / Soda 
                 198                   23                   23                   34                    6 
gD = gD %>% activate(nodes) %>%
  mutate(Category=bebidas$strCategory[match(toupper(bebidas$strDrink),name)]) # Agregamos las categorías haciendo un match con el dataframe original
gD %>% activate(nodes) %>% as.data.frame() # Para explorarlo

Con esta información, la pregunta es: ¿Las bebidas de igual categoría comparten más ingredientes que con las de otras categorías?

Para calcular esto nos vamos a volver a ensuciar un poco las manos codeando:

cates = unique(bebidas$strCategory) # Agarramos las categorías
M = matrix(0,nrow=length(cates),ncol=length(cates)) # En esta matriz vamos a guardar el peso de las relaciones entre categorías
ejes = gD %>% activate(edges) %>% as.data.frame() # Agarramos los ejes
nodos = gD %>% activate(nodes) %>% as.data.frame() # y nodos
colnames(M) = rownames(M) = cates # Ponemos nombres a la matriz

for(i in 1:nrow(ejes)){ # Recorremos todos los ejes
  eje = ejes[i,] # Agarramos cada fila de ejes
  Cfrom = nodos[eje$from,'Category'] # Vemos la categoría del punto de partida
  Cto = nodos[eje$to,'Category'] # Igual pero de punto de llegada
  M[min(Cfrom,Cto),max(Cfrom,Cto)] = M[min(Cfrom,Cto),max(Cfrom,Cto)] + eje$weight # Guardamos el valor actualizado, sumado lo nuevo.
  M[max(Cfrom,Cto),min(Cfrom,Cto)] = M[min(Cfrom,Cto),max(Cfrom,Cto)]
}
M
                     Cocktail Shot Ordinary Drink Other/Unknown Coffee / Tea Beer Punch / Party Drink Soft Drink / Soda
Cocktail                 2324  288           3770           286          133   58                 410                48
Shot                      288  108            594            54           87   27                 102                10
Ordinary Drink           3770  594           5287           656          308  124                 958               128
Other/Unknown             286   54            656           119           96   15                 181                32
Coffee / Tea              133   87            308            96          198    4                 105                 5
Beer                       58   27            124            15            4    8                  24                 8
Punch / Party Drink       410  102            958           181          105   24                 131                23
Soft Drink / Soda          48   10            128            32            5    8                  23                 5
Homemade Liqueur           90   37            209            66          118    3                  72                 0
Cocoa                      19   11             45            20           35    0                  35                 0
Milk / Float / Shake       26   33             73            17           34    2                  20                 2
                     Homemade Liqueur Cocoa Milk / Float / Shake
Cocktail                           90    19                   26
Shot                               37    11                   33
Ordinary Drink                    209    45                   73
Other/Unknown                      66    20                   17
Coffee / Tea                      118    35                   34
Beer                                3     0                    2
Punch / Party Drink                72    35                   20
Soft Drink / Soda                   0     0                    2
Homemade Liqueur                   28    17                    1
Cocoa                              17    13                   20
Milk / Float / Shake                1    20                   10

Ejercicio

Exploren y visualicen esta matriz. ¿Qué representa la diagonal en esta matriz? ¿Y los otros elementos? ¿Cuales son las categorías más relacionadas? ¿Hay alguna categoría que se vincule más con otras que con sí misma? ¿Cuál se relaciona en menor proporción?

LS0tCnRpdGxlOiAiVW4gcGVxdWXDsW8gdmlzdGF6byBhICoqR1JBRk9TKioiCmF1dGhvcjogIkFyaWVsIFNhbGdhZG8iCmRhdGU6ICJTZXB0aWVtYnJlIDA3IDIwMjEiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgZGZfcHJpbnQ6IHBhZ2VkCiAgICB0b2M6IHllcwogIGh0bWxfbm90ZWJvb2s6CiAgICB0aGVtZTogbHVtZW4KICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwpzdWJ0aXRsZTogIkxhYm9yYXRvcmlvIGRlIERhdG9zIgotLS0KCiMgTG9zIGdyYWZvcwoKIyMgRElTQ0xBSU1FUgpFc3RhIG1hdGVyaWEgX25vIGVzXyB1biBjdXJzbyBkZSBncmFmb3MgcGVyIHNlLiBFbCB1bml2ZXJzbyBkZWwgZXN0dWRpbyBkZSBncmFmb3MgZXMgZW5vcm1lIGVuIHPDrSBtaXNtbyAoY29tbyBwYXNhIGNvbiBtdWNob3MgdGVtYXMgZW4gZXN0YSBtYXRlcmlhKS4gUG9yIG51ZXN0cmEgcGFydGUgbm9zIHZhIGEgaW50ZXJlc2FyIGNvbW8gaGVycmFtaWVudGEgcGFyYSB2aXN1YWxpemFyIHkgbWVkaXIgcmVsYWNpb25lcyBlbnRyZSBsb3MgZGF0b3MuIEVuIGVzdGUgc2VudGlkbywgcG9kZW1vcyBwZW5zYXIgYWwgZ3JhZm8gY29tbyB1bmEgZm9ybWEgZGUgcmVwcmVzZW50YXIgZGF0b3MuIERlcGVuZGllbmRvIGRlIGxhIGVzdHJhdGVnaWEgZGUgcmVwcmVzZW50YWNpw7NuLCBsb3MgZ3JhZm9zIHB1ZWRlbiBjb3JyZXNwb25kZXJzZSBjb24gbGlzdGFzIChzZW1pZXN0cnVjdHVyYWRvcyksIHRhYmxhcyAoZXN0cnVjdHVyYWRvcykgbyBwYXJlcyBkZSB0YWJsYXMuCgojIyDCv1F1w6kgZXMgdW4gZ3JhZm8/CgpVbiBncmFmbyAodGFtYmnDqW4gX3JlZF8pIGVzIHVuYSBlc3RydWN0dXJhIF9NVVlfIGdlbmVyYWwgZW4gbGEgY3VhbCBwb2RlbW9zIHJlcHJlc2VudGFyIHJlbGFjaW9uZXMgZW50cmUgb2JqZXRvcy4gRW4gZ2VuZXJhbCBsbyByZXByZXNlbnRhbW9zIGEgdHJhdsOpcyBkZSBkb3MgY29uanVudG9zOiB1bm8gZGUgdsOpcnRpY2VzIChvIG5vZG9zKSAkViQgeSB1bm8gZGUgZWplcyAobyBjb25leGlvbmVzKSAkRSQuIExvcyB2w6lydGljZXMgcmVwcmVzZW50YW4gbG9zIG9iamV0b3MgcXVlIG5vcyBpbnRlcmVzYSBlc3R1ZGlhciAocHVlZGVuIHNlciBwZXJzb25hcywgYW5pbWFsZXMsIHByb2R1Y3RvcyBvIGxvIHF1ZSBzZSBsZXMgb2N1cnJhKSB5IGxvcyBlamVzIHN1cyByZWxhY2lvbmVzIChyZXR3ZWV0cyBvIGVzcGFjaW9zIGxhYm9yYWxlcyBjb21wYXJ0aWRvcyBwYXJhIHBlcnNvbmFzLCBjYWRlbmFzIHRyw7NmaWNhcyBlbnRyZSBhbmltYWxlcywgaW5ncmVkaWVudGVzIGNvbXBhcnRpZG9zIGVudHJlIHByb2R1Y3RvcywgcG9yIG5vbWJyYXIgYWxndW5hcyBjb3NhcykuIExvIHBvZGVyb3NvIGRlIGxhIHJlcHJlc2VudGFjacOzbiBtZWRpYW50ZSBncmFmb3MgZXMgcXVlIHBlcm1pdGUgdmlzdWFsaXphciBsYSBlc3RydWN0dXJhIHF1ZSBkYSBzb3BvcnRlIGEgbG9zIG9iamV0b3MsIHkgbGEgZXhpc3RlbmNpYSBkZSB1bmEgYmF0ZXLDrWEgZW5vcm1lIGRlIG3DqXRvZG9zIHBhcmEgY2FyYWN0ZXJpemFyIGVzdGEgZXN0cnVjdHVyYS4gUG9yIGVqZW1wbG8sIGhheSBoZXJyYW1pZW50YXMgcGFyYSBpZGVudGlmaWNhciBsYSBpbXBvcnRhbmNpYSBkZSBjYWRhIHbDqXJ0aWNlLCBsb3MgZ3J1cG9zIG3DoXMgZnVlcnRlbWVudGUgY29uZWN0YWRvcyBlbiBsYSByZWQsIGNvbmV4aW9uZXMgcXVlIGZhbHRhbiwgcmVzaXN0ZW5jaWEgZGUgbGEgcmVkIGEgZmFsbG9zIHkgbXVjaGFzIG90cmFzIGNvc2FzLgoKTGEgZXN0cnVjdHVyYSBkZSBsYSByZWQgcGVybWl0ZSBpbmNsdWlyIG3DoXMgaW5mb3JtYWNpw7NuIHF1ZSBzw7NsbyBsb3MgdsOpcnRpY2VzIHkgbG9zIGVqZXMuIEVzdG8gc2Ugc3VlbGUgaW5jbHVpciBjb21vIGF0cmlidXRvcyBkZSBsb3Mgbm9kb3MgeSBsb3MgZWplcyAocG9yIGVqZW1wbG8sIHBvZHLDrWFtb3MgcXVlcmVyIGluY2x1aXIgbGEgZWRhZCBkZSBsYXMgcGVyc29uYXMgY29tbyBhdHJpYnV0byBkZSBsb3MgdsOpcnRpY2VzLCBvIGxhIGNhbnRpZGFkIGRlIHJldHdlZXRzIGRlIHVuYSBwZXJzb25hIGEgb3RyYSBjb21vIGF0cmlidXRvIGRlIGxvcyBlamVzKS4KCkVuIG51ZXN0cm8gY2FzbywgdmFtb3MgYSBoYWNlciB1biBhbsOhbGlzaXMgZXhwbG9yYXRvcmlvIHNvYnJlIHVuYSByZWQgcGFyYSB2ZXIgbGFzIHBvc2liaWxpZGFkZXMgZGUgdmlzdWFsaXphY2nDs24geSBjYXJhY3Rlcml6YWNpw7NuIGRlIGxhcyBjb3JyZWxhY2lvbmVzIHF1ZSBpbXBsaWNhIGxhIGVzdHJ1Y3R1cmEgZGUgbGEgcmVkIHNvYnJlIHN1cyBlbGVtZW50b3MuIAoKIyMgRWwgZGF0YXNldDogdHJhZ29zIGRlbCBBUEkKClBhcmEgY29uc3RydWlyIG51ZXN0cm8gZ3JhZm8gdmFtb3MgYSB1c2FyIGxvcyBkYXRvcyBkZWwgQVBJIGRlIHRyYWdvcyBxdWUgdmltb3MgZW4gY2xhc2VzIHBhc2FkYXMuIFBvciBlc3RvLCBudWVzdHJvIHByaW1lciBwYXNvIHZhIGEgc2VyIGRlc2NhcmdhciB0b2RvcyBsb3MgdHJhZ29zIHkgc3VzIGluZ3JlZGllbnRlcy4gVmFtb3MgYSBkZXNjYXJnYXIgaXRlcmF0aXZhbWVudGUgbGEgaW5mb3JtYWNpw7NuIGRlIHRvZG9zIGxvcyB0cmFnb3MgY29uIGxldHJhcyBlbiBgbGV0dGVyc2A6CgpgYGB7cn0KcmVxdWlyZShqc29ubGl0ZSkKYmViaWRhcyA9IE5VTEwKdXJsX2Jhc2UgPSAnaHR0cDovL3d3dy50aGVjb2NrdGFpbGRiLmNvbS9hcGkvanNvbi92MS8xL3NlYXJjaC5waHA/Zj0nCmxldHJhID0gJ2EnCmZvcihsZXRyYSBpbiBsZXR0ZXJzKXsKICBwcmludChwYXN0ZSgnTGV0cmE6JyxsZXRyYSkpCiAgZHdsZCA9IGZyb21KU09OKHBhc3RlKHVybF9iYXNlLGxldHJhLHNlcD0nJykpJGRyaW5rcwogIGJlYmlkYXMgPSByYmluZChiZWJpZGFzLGR3bGQpCiAgcHJpbnQocGFzdGUoJ0hhYsOtYScsbnJvdyhkd2xkKSwnYmViaWRhcycpKQp9CmJlYmlkYXMKbnJvdyhiZWJpZGFzKQpgYGAKCkNvbiBlc3RvIGxvZ3JhbW9zIGRlc2NhcmdhciBsYSBpbmZvcm1hY2nDs24gZGUgdG9kb3MgbnVlc3Ryb3MgdHJhZ29zIGRpc3BvbmlibGVzLiBFbCBwcm9ibGVtYSBlcyBxdWUgc2kgc2UgZmlqYW4sIGxhIGluZm9ybWFjacOzbiBkZSBsb3MgaW5ncmVkaWVudGVzIGRlIGxhcyBiZWJpZGFzIG5vIGVzdMOhIHByZXNlbnRlIGRlIGZvcm1hIGNsYXJhLiBQb3IgZWplbXBsbywgbGEgcHJpbWVyIGJlYmlkYSBpbmNsdXllIDQgaW5ncmVkaWVudGVzLCBwZXJvIHRlbmVtb3MgMTUgY29sdW1uYXMgZGVkaWNhZGFzIGEgaW5ncmVkaWVudGVzLgoKYGBge3J9CmJlYmlkYXNbMSxncmVwKCdJbmdyZWRpZW50Jyxjb2xuYW1lcyhiZWJpZGFzKSldCnN1bSghaXMubmEoYmViaWRhc1sxLGdyZXAoJ0luZ3JlZGllbnQnLGNvbG5hbWVzKGJlYmlkYXMpKV0pKQpsZW5ndGgoZ3JlcCgnSW5ncmVkaWVudCcsY29sbmFtZXMoYmViaWRhcykpKQpgYGAKRXN0byBlcyBhbGdvIHF1ZSBvY3VycmUgbm9ybWFsbWVudGUgY3VhbmRvIGNvbnZlcnRpbW9zIGEgdW4gZm9ybWF0byB0YWJ1bGFyIGluZm9ybWFjacOzbiBkZSBjb25leGlvbmVzOiBkYWRvIHF1ZSBubyB0b2RvcyBsb3MgdHJhZ29zIHRpZW5lbiBsYSBtaXNtYSBjYW50aWRhZCBkZSBjb25leGlvbmVzIChubyB0aWVuZW4gbGEgbWlzbWEgY2FudGlkYWQgZGUgaW5ncmVkaWVudGVzKSwgbnVlc3RybyBkYXRhc2V0IHRpZW5lIG11Y2hvIGVzcGFjaW8gdmFjw61vIGRlbnRyby4KCsK/Q8OzbW8gcG9kZW1vcyBsbGV2YXIgZXN0YSBpbmZvcm1hY2nDs24gYSB1biBmb3JtYXRvIG3DoXMgY8OzbW9kbz8gTGVzIGRlam8gZG9zIGlkZWFzOgoKKiAqKkNvbnN0cnVpciB1biBsaXN0YWRvIGRlIGVqZXMqKjogRWwgbGlzdGFkbyBkZSBlamVzIGVzIHVuIGZvcm1hdG8gbXV5IGNvbcO6biBwYXJhIGd1YXJkYXIgaW5mb3JtYWNpw7NuIGRlIHJlZGVzLCBlc3BlY2lhbG1lbnRlIGN1YW5kbyB0b2RvcyBsb3MgdmVydGljZXMgdGllbmVuIGFsIG1lbm9zIHVuYSBjb25leGnDs24uIE5vIGRlYmUgY29uZnVuZGlyc2UgY29uIHVuYSBfbGlzdGFfIGNvbW8gb2JqZXRvIGRlIGBSYC4gTGEgcmVwcmVzZW50YWNpw7NuIGVzIG11eSBzaW1pbGFyIGEgdW4gQ1NWOiBlbiBjYWRhIGZpbGEgaW5jb3Jwb3JhbW9zIGxhIGluZm9ybWFjacOzbiBxdWUgZGVzY3JpYmUgdW4gZWplOiBzdSBfY29sYV8geSBzdSBfY2FiZXphXy4gTWlyYW5kbyBsYSBwcmltZXIgZmlsYSBkZWwgZGF0YXNldCBxdWUgZGVzY2FyZ2Ftb3MsIHBvZHLDrWFtb3MgdmlzdWFsaXphcmxhIGFzw606CgpgYGB7ciBlY2hvPUZBTFNFfQpjYXQocGFzdGUwKHBhc3RlKGJlYmlkYXMkc3RyRHJpbmtbMV0sYmViaWRhc1sxLDE4OjIxXSxzZXA9JywnKSxjb2xsYXBzZT0nXG4nKSkKYGBgCkEgbGEgaXpxdWllcmRhIHNpZW1wcmUgdGVuZW1vcyB1biB2ZXJ0aWNlICJiZWJpZGEiIHkgYSBsYSBkZXJlY2hhIHVuIHbDqXJ0aWNlICJpbmdyZWRpZW50ZSIuCgoqICoqQ29uc3RydWlyIHVuYSBtYXRyaXoqKjogb3RyYSByZXByZXNlbnRhY2nDs24gbXV5IGhhYml0dWFsIHBhcmEgZ3JhZm9zIGVzIGxhIGRlIF9tYXRyaXogZGUgYWR5YWNlbmNpYV8uIEVuIGVzZSBjYXNvLCBjb25zdHJ1aW1vcyB1bmEgbWF0cml6ICRYJCB0YWwgcXVlICRYX3tpLGp9PTEkIHNpIGhheSB1bmEgY29uZXhpw7NuIGVudHJlIGxvcyB2w6lydGljZXMgJGkkIHkgJGokIHkgJDAkIHNpIG5vLiBFc3RhIHJlcHJlc2VudGFjacOzbiBlcyBtdXkgaGFiaXR1YWwgY29tbyBpbnB1dCBkZSBmdW5jaW9uZXMgZGViaWRvIGEgcXVlIGVzIG11eSBpbmRlcGVuZGllbnRlIGRlbCBsZW5ndWFqZSBwYXJ0aWN1bGFyIHF1ZSBzZSB1c2UuIEVuIG51ZXN0cm8gY2FzbyBwYXJ0aWN1bGFyLCBzaSB0ZW5lbW9zICROX1QkIHRyYWdvcyB5ICROX0kkIGluZ3JlZGllbnRlcywgJFggXGluIFJee05fVCBcdGltZXMgTl9JfSQsIHkgZXMgdW5hIG1hdHJpeiByYWxhLCBsbGVuYSBkZSBjZXJvcy4gw5puaWNhbWVudGUgdGVuZHLDrWEgMXMgZW4gbGFzIHBvc2ljaW9uZXMgJGlqJCBxdWUgY29uZWN0ZW4gdW4gdHJhZ28gKGluZ3JlZGllbnRlKSAkaSQgY29uIHVuIGluZ3JlZGllbnRlICh0cmFnbykgJGokLiBOb3RlbiBxdWUgZW4gbnVlc3RybyBjYXNvIHBhcnRpY3VsYXIsIG5vIGhhYnLDrWEgY29uZXhpb25lcyBkaXJlY3RhcyBlbnRyZSB0cmFnb3MgbyBlbnRyZSBpbmdyZWRpZW50ZXMuCgpNaWVudHJhcyBxdWUgZWwgbGlzdGFkbyBkZSBlamVzIF9jb21wcmltZV8gbGEgaW5mb3JtYWNpw7NuLCBzw7NsbyBpbmRpY2FuZG8gbGFzIGNvbmV4aW9uZXMgcXVlIGVzdMOhbiBwcmVzZW50ZXMsIG1pZW50cmFzIHF1ZSBsYSBtYXRyaXogZGUgYWR5YWNlbmNpYSBsYSBfZXhwYW5kZV8gaW5kaWNhbmRvIHF1w6kgb2N1cnJlIHBhcmEgY3VhbHF1aWVyIGNvbWJpbmFjacOzbiBwb3NpYmxlIGRlIHbDqXJ0aWNlcy4KClZvbHZpZW5kbyBhIG51ZXN0cm8gY2FzbyBwYXJ0aWN1bGFyLCB2ZWFtb3MgY29tbyBsbGV2YXJsbyBhIHVuIGxpc3RhZG8gZGUgZWplcy4gQ29tbyBlamVyY2ljaW8sIHZhbW9zIGEgaGFjZXIgdXNhbmRvIGxhcyBoZXJyYW1pZW50YXMgZGUgYHRpZHl2ZXJzZWAuCgpgYGB7cn0KcmVxdWlyZSh0aWR5dmVyc2UpCmBgYAoKVmFtb3MgYSBsbGV2YXIgZWwgZGF0YXNldCBvcmlnaW5hbCBhIHVuYSBsaXN0YSBkZSBlamVzOgpgYGB7cn0KcHJpbnQocGFzdGUoJ0VtcGV6YW1vcyBjb24nLG5yb3coYmViaWRhcyksJ2ZpbGFzJykpCgpiZWJfdGlkeSA9IGJlYmlkYXMgJT4lICAjIEFsIGRhdGFmcmFtZSBkZSBiZWJpZGFzCiAgc2VsZWN0KHN0ckRyaW5rLHN0ckluZ3JlZGllbnQxOnN0ckluZ3JlZGllbnQxNSkgJT4lICMgTGUgc2VsZWNjaW9uYW1vcyBsb3Mgbm9tYnJlcyBkZSBiZWJpZGFzIHkgc3VzIGluZ3JlZGllbnRlcwogIGdyb3VwX2J5KHN0ckRyaW5rKSAlPiUgI0F2aXNhbW9zIHF1ZSBxdWVyZW1vcyBhZ3J1cGFyIGxhcyBvcGVyYWNpb25lcyBwb3IgYmViaWRhCiAgdW5pdGUoJ3N0ckluZ3JlZGllbnRzJyxzdHJJbmdyZWRpZW50MTpzdHJJbmdyZWRpZW50MTUsbmEucm09VFJVRSxzZXA9JywnKSAjVW5pbW9zIGxhcyBjb2x1bW5hcyBkZSBpbmdyZWRpZW50ZXMgcGVnYW5kb2xhcyBjb24gY29tYXMKcHJpbnQocGFzdGUoJ0Fob3JhIHRlbmVtb3MnLG5yb3coYmViX3RpZHkpLCdmaWxhcyB5JywgbmNvbChiZWJfdGlkeSksJ2NvbHVtbmFzJykpCmBgYAoKCmBgYHtyfQpiZWJfZWplcyA9IGJlYl90aWR5ICU+JSAjIEFsIG51ZXZvIGRhdGFmcmFtZQogIHNlcGFyYXRlX3Jvd3Moc3RySW5ncmVkaWVudHMsc2VwPScsJykgIyBMbyBzZXBhcmFtb3MgZW4gZmlsYXMgZGlzdGludGFzIHBvciBjYWRhIGluZ3JlZGllbnRlCgpwcmludChwYXN0ZSgnT2J0ZW5lbW9zIGZpbmFsbWVudGUnLG5yb3coYmViX2VqZXMpLCdlamVzJykpCmBgYAoKCkVsIMO6bmljbyBjdWlkYWRvIHF1ZSBubyB0dXZpbW9zIGhhc3RhIGFjw6EgeSBub3MgcHVlZGUgdHJhZXIgcHJvYmxlbWFzIGVzIHF1ZSBoYXlhIG5vbWJyZXMgcmVwZXRpZG9zIGVudHJlIGJlYmlkYXMgeSBpbmdyZWRpZW50ZXM6CgoKYGBge3J9CmludGVyc2VjdChiZWJfZWplcyRzdHJEcmluayxiZWJfZWplcyRzdHJJbmdyZWRpZW50cykKYGBgCgoKUGFyYSBzYWNhcm5vcyBlc2UgcHJvYmxlbWEgZGUgZW5jaW1hLCBwb25lbW9zIGVuIG1hecO6c2N1bGEgbG9zIG5vbWJyZXMgZGUgbGFzIGJlYmlkYXMgeSBlbiBtaW7DunNjdWxhIGxhIGRlIGxvcyBpbmdyZWRpZW50ZXM6CgoKYGBge3J9CmJlYl9lamVzID0gYmViX2VqZXMgJT4lICMgQWwgZGF0YXNldCBkZSBlamVzCiAgbXV0YXRlKHN0ckRyaW5rID0gdG91cHBlcihzdHJEcmluayksc3RySW5ncmVkaWVudHM9dG9sb3dlcihzdHJJbmdyZWRpZW50cykpICAjIENhbWJpYW1vcyBsYSBjb2x1bW5hIHN0ckRyaW5rIGEgbWF5w7pzY3VsYSB5IHN0ckluZ3JlZGllbnRzIGEgbWluw7pzY3VsYQppbnRlcnNlY3QoYmViX2VqZXMkc3RyRHJpbmssYmViX2VqZXMkc3RySW5ncmVkaWVudHMpCmBgYAoKCkNvbiBlc3RvIGNvbnN0cnVpZG8sIHBvZGVtb3MgcmVzcG9uZGVyIGFsZ3VuYXMgcHJlZ3VudGFzIG11eSBzZW5jaWxsYXMsIGHDum4gc2luIHBlbnNhciB0b2RhdsOtYSBlbiBsYSByZWQuIFBvciBlamVtcGxvLCDCv2N1w6FsIGVzIGxhIGJlYmlkYSBxdWUgbcOhcyBpbmdyZWRpZW50ZXMgdXNhPyDCv2N1w6FsIG1lbm9zPyDCv2N1w6FudG9zIGluZ3JlZGllbnRlcyB1c2FuIGNhZGEgYmViaWRhPwoKYGBge3J9CmJlYl9lamVzICU+JQogIGNvdW50KHN0ckRyaW5rLG5hbWU9J25JbmdyZWRpZW50cycpICU+JSAjIENvbnRhbW9zIGN1YW50YXMgdmVjZXMgYXBhcmVjZSBjYWRhIGJlYmlkYSwgZXNlIGVzIHN1IHRvdGFsIGRlIGluZ3JlZGllbnRlcwogIGFycmFuZ2UoZGVzYyhuSW5ncmVkaWVudHMpKSAjIExvIG9yZGVuYW1vcyBlbiBvcmRlbiBkZXNjZW5kZW50ZQoKYmViX2VqZXMgJT4lICMgTG8gbWlzbW8gcGVybyBlbiBvcmRlbiBhc2NlbmRlbnRlCiAgY291bnQoc3RyRHJpbmssbmFtZT0nbkluZ3JlZGllbnRzJykgJT4lCiAgYXJyYW5nZShuSW5ncmVkaWVudHMpCgpiZWJfZWplcyAlPiUKICBjb3VudChzdHJEcmluayxuYW1lPSduSW5ncmVkaWVudHMnKSAlPiUgIyBDb250YW1vcyBpbmdyZWRpZW50ZXMKICB1bmdyb3VwKCkgJT4lICNEZXNhZ3J1cGFtb3MgcGFyYSBoYWNlciBzdW1hcmlvcwogIHN1bW1hcmlzZShtZWRpYT1tZWFuKG5JbmdyZWRpZW50cyksc3RkZXY9c2QobkluZ3JlZGllbnRzKSxtaW49bWluKG5JbmdyZWRpZW50cyksbWF4PW1heChuSW5ncmVkaWVudHMpKSAjIENhbGN1bGFtb3MgYWxndW5hcyBtZWRpZGFzCgpiZWJfZWplcyAlPiUgIyBIYWNlbW9zIHVuIGhpc3RvZ3JhbWEgZGUgbGEgY2FudGlkYWQgZGUgYXBhcmljaW9uZXMgZGUgY2FkYSBpbmdyZWRpZW50ZQogIGNvdW50KHN0ckRyaW5rLG5hbWU9J25JbmdyZWRpZW50cycpICU+JQogIGdncGxvdChtYXBwaW5nPWFlcyh4PW5JbmdyZWRpZW50cykpICsKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDEsZmlsbD0nd2hpdGUnLGNvbD0nYmxhY2snKSArIAogIGdlb21fYm94cGxvdCh3aWR0aD0xMCxjb2xvcj0nYmx1ZScsbHdkPTEuNSkKCmBgYAoKTGEgYmViaWRhIHF1ZSBtw6FzIGluZ3JlZGllbnRlcyB1c2EgZXMgKipFZ2cgTm9nICM0KiogXyjCv3BvbmNoZSBkZSBodWV2bz8pXyBjb24gMTEgaW5ncmVkaWVudGVzLCB5IGxhIHF1ZSBtZW5vcyBzZSBkaXNwdXRhIGVudHJlIG11Y2hvcyBjYW5kaWRhdG9zLCBjb24gMiBpbmdyZWRpZW50ZXMuIEVuIHByb21lZGlvIGxhcyBiZWJpZGFzIHRpZW5lbiA0IGluZ3JlZGllbnRlcywgY29uIHVuIGRlc3bDrW8gZGUgMS41IGluZ3JlZGllbnRlcyAobyBzZWEgcXVlIHBvZGVtb3MgcmVzdW1pciBxdWUgZWwgZ3J1ZXNvIGRlIGxvcyBkYXRvcyB0aWVuZW4gZW50cmUgMi41IHkgNS41IGluZ3JlZGllbnRlcykuCgoKIyMjIyBFamVyY2ljaW86CgpSZXBpdGFuIGVzdGUgYW7DoWxpc2lzIHBlcm8gZW4gdmV6IGRlIG1pcmFyIGRlc2RlIGVsIHB1bnRvIGRlIHZpc3RhIGRlbCB0cmFnbywgbWlyZW5sbyBkZXNkZSBsb3MgaW5ncmVkaWVudGVzLiDCv0hheSBhbGd1bmEgY29pbmNpZGVuY2lhIGVudHJlIGxvcyB2YWxvcmVzIHF1ZSBvYnRpZW5lbj8KCgojIyMgQ29uc3RydXllbmRvIGVsIGdyYWZvCgpIYWJpZW5kbyBleHBsb3JhZG8gYnJldmVtZW50ZSBsbyBxdWUgbm9zIGRpY2UgbGEgbGlzdGEgZGUgZWplcyBwb3Igc8OtIHNvbGEsIGF2YW5jZW1vcyBjb24gbGEgdml6dWFsaXphY2nDs24gZGUgbGEgZXN0cnVjdHVyYSBxdWUgcmVzdWx0YS4gUGFyYSBlc3RvIHZhbW9zIGEgYXByb3ZlY2hhciBkb3MgcGFxdWV0ZXMgYnVlbsOtc2ltb3MgZGUgYFJgIHBhcmEgdHJhYmFqYXIgY29uIHJlZGVzOiBgaWdyYXBoYCB5IGB0aWR5Z3JhcGhgLiBgaWdyYXBoYCB0aWVuZSBsYSBtYXlvciBwYXJ0ZSBkZSBsbyBxdWUgY3VhbHF1aWVyIGFuw6FsaXNpcyBkZSByZWRlcyBwdWVkZSBuZWNlc2l0YXIsIG1pZW50cmFzIHF1ZSBgdGlkeWdyYXBoYCBub3MgcGVybWl0ZSBhcHJvdmVjaGFyIGBpZ3JhcGhgIHkgYHRpZHl2ZXJzZWAgYSBsYSB2ZXouCgpgYGB7cn0KIyBTaSBhw7puIG5vIGxvcyBpbnN0YWxhcm9uOgojIGluc3RhbGwucGFja2FnZXMoJ2lncmFwaCcpCiMgaW5zdGFsbC5wYWNrYWdlcygndGlkeWdyYXBoJykKcmVxdWlyZShpZ3JhcGgpCnJlcXVpcmUodGlkeWdyYXBoKQoKYGBgCkFob3JhIHZhbW9zIGEgYXJtYXIgbnVlc3RybyBvYmpldG8gX2dyYWZvXyB1c2FuZG8gYHRpZHlncmFwaGAgZSBgaWdyYXBoYC4KCmBgYHtyfQpnID0gIGJlYl9lamVzICU+JQogIGdyYXBoX2Zyb21fZGF0YV9mcmFtZShkaXJlY3RlZCA9IEZBTFNFKSAlPiUgIyBFc3RhIGZ1bmNpw7NuIHRvbWEgZWwgbGlzdGFkbyB5IGxvIGNvbnZpZXJ0ZSBlbiB1biBvYmpldG8gZ3JhZm8KICBhc190YmxfZ3JhcGgoKSAjIEVzdGUgbG8gbXVldmUgYSBmb3JtYXRvICJ0aWR5IgpnCmBgYAoKRmlqZW5zZSBxdWUgbGxldmFyIGBiZWJfZWplc2AgYSB1biBgdGJsX2dyYXBoYCBvYnRlbmVtb3MgdW4gb2JqZXRvIHF1ZSB0aWVuZSBsb3MgZG9zIHRpcG9zIGRlIGluZm9ybWFjacOzbiBxdWUgbWVuY2lvbmFtb3MgYW50ZXM6IGxvcyBub2RvcyBvIHbDqXJ0aWNlcyAoIF9ub2Rlc18gKSB5IGxvcyBlamVzICggX2VkZ2VzXyApIGVuIGZvcm1hIGRlIHVuYSBtYXRyaXouIEZpamVuc2UgcXVlIG9idGVuZW1vcyB2YXJpYXMgbWVkaWRhcyByZXN1bWVuIGRlc2RlIGVsIHB1bnRvIGRlIHBhcnRpZGE6IHRlbmVtb3MgNzQ3IHbDqXJ0aWNlcywgeSAxNjk4IGVqZXMuCgpWYW1vcyBhIGFncmVnYXIgdW4gcG9xdWl0byBleHRyYSBkZSBpbmZvcm1hY2nDs24gZW4gbnVlc3RybyBncmFmbywgcGFyYSBxdWUgaW5jbHV5YSBlbCBoZWNobyBkZSBxdWUgbnVlc3Ryb3MgdsOpcnRpY2VzIHNvbiBkZSBkaXN0aW50byB0aXBvOgoKYGBge3J9CmcgPSBnICU+JSAKICBhY3RpdmF0ZShub2RlcykgJT4lICMgQXPDrSBsZSBkZWNpbW9zIHF1ZSBvcGVyZSBzb2JyZSBlbCBkYXRhZnJhbWUgZGUgbm9kb3MKICBtdXRhdGUodHlwZT1pZmVsc2UoaXMuZWxlbWVudChuYW1lLGJlYl9lamVzJHN0ckRyaW5rKSwnRHJpbmsnLCdJbmdyZWRpZW50JykpICMgQWdyZWdhbW9zIGVsIHRpcG8gZGUgbm9kbyBxdWUgZXMKZwpgYGAKYGBge3J9CiMjIFBvZGVtb3MgY2h1c21lYXIgZWwgcmVzdWx0YWRvIGFjw6EKZyAlPiUgYWN0aXZhdGUobm9kZXMpICU+JSBhcy5kYXRhLmZyYW1lKCkgIyBQYXJhIHZpc3VhbGl6YXJsbyBlbnRlcm8sIGxvIG1vdmVtb3MgYSBkYXRhZnJhbWUKZyAlPiUgYWN0aXZhdGUoZWRnZXMpICU+JSBhcy5kYXRhLmZyYW1lKCkKCmBgYAoKRmlqZW5zZSBxdWUgbHVlZ28gZGUgZXN0YSBvcGVyYWNpw7NuLCBgdGlkeWdyYXBoYCB5YSBkZXRlY3RvIHF1ZSBudWVzdHJvIGdyYWZvIGVzIGBiaXBhcnRpdGVgIChiaXBhcnRpdG8pLiBFc3RvIHNpZ25pZmljYSBxdWUgdG9kYXMgbGFzIGNvbmV4aW9uZXMgb2N1cnJlbiBlbnRyZSB2w6lydGljZXMgZGUgZGlzdGludG8gdGlwbyAoZW4gZXN0ZSBjYXNvLCBgRHJpbmtgIHkgYEluZ3JlZGllbnRgKS4KCkhhZ2Ftb3MgdW5hIHByaW1lciB2aXN1YWxpemFjacOzbiBkZWwgZ3JhZm8KCmBgYHtyfQojIGluc3RhbGwucGFja2FnZXMoJ2dncmFwaCcpICMgU2kgYcO6biBubyBsbyB0aWVuZW4gaW5zdGFsYWRvLgpyZXF1aXJlKGdncmFwaCkKCmcgJT4lIAogIGdncmFwaCgpICsgIyBQYXNhbW9zIGVsIGdyYWZvIGEgZ2dyYXBoIChnZ3Bsb3QgcGFyYSBncmFmb3MpCiAgZ2VvbV9lZGdlX2xpbmsoZWRnZV93aWR0aD0uMSxhbHBoYT0wLjUpICsgIyBJbmRpY2Ftb3MgZ3Jvc29yIGRlIGxvcyBlamVzIHkgdHJhbnNwYXJlbmNpYQogIGdlb21fbm9kZV9wb2ludChtYXBwaW5nID0gYWVzKGNvbG9yPXR5cGUsc2hhcGU9dHlwZSksc2l6ZSA9IDEpICsgIyBQZWRpbW9zIGNvbG9yIHkgZm9ybWEgYmFzYW5kb3NlIGVuIGVsIHRpcG8gZGUgbm9kby4KICB0aGVtZV9ncmFwaCgpCgojIFNpIGdyYWZpY2Ftb3MgImFycmliYSIgdW4gdGlwbyBkZSBub2RvIHkgImFiYWpvIiBhbCBvdHJvOgpnICU+JSBhY3RpdmF0ZShub2RlcykgJT4lIAogIG11dGF0ZSh0eXBlPXR5cGU9PSdEcmluaycpICU+JSAjIExhIGZ1bmNpw7NuIGRlIGxheW91dCBuZWNlc2l0YSBxdWUgc2VhIHVuIHZlY3RvciBsw7NnaWNvIGVsICJ0eXBlIgogIGdncmFwaChsYXlvdXQ9J2lncmFwaCcsYWxnb3JpdGhtPSdiaXBhcnRpdGUnKSArCiAgZ2VvbV9lZGdlX2xpbmsoZWRnZV93aWR0aD0uMSxhbHBoYT0wLjMpICsKICBnZW9tX25vZGVfcG9pbnQobWFwcGluZyA9IGFlcyhjb2xvcj10eXBlLHNoYXBlPXR5cGUpLHNpemUgPSAxKSArCiAgdGhlbWVfZ3JhcGgoKQpgYGAKCk9ic2VydmVuIGNvbW8gbGEgZm9ybWEgZW4gbGEgcXVlIGFjb21vZGFtb3MgbG9zIHbDqXJ0aWNlcyAoZWwgX2xheW91dF8gZGVsIGdyYWZvKSBub3MgcGVybWl0ZSBvYnNlcnZhciBlc3RydWN0dXJhIGVuIGxvcyBkYXRvczogYWwgYWNvbW9kYXIgbG9zIGRpc3RpbnRvcyB0aXBvcyBkZSB2w6lydGljZXMgKGBJbmdyZWRpZW50YCBhcnJpYmEgeSBgRHJpbmtgKSBhYmFqbywgbGEgZ3LDoWZpY2Egbm9zIHN1Z2llcmUgcXVlICBubyB0b2RhcyBsYXMgYmViaWRhcyBzZSBjb25lY3RhbiBwb3IgaWd1YWwgYSB0b2RvcyBsb3MgaW5ncmVkaWVudGVzLCB5IHF1ZSBubyB0b2RvcyBsb3MgaW5ncmVkaWVudGVzIHNlIHZpbmN1bGFuIHBvciBpZ3VhbCBhIHRvZGFzIGxhcyBiZWJpZGFzOiBoYXkgImdydXBvcyIgcXVlIGVzdMOhbiBtw6FzIGNvbmVjdGFkb3MgcXVlIG90cm9zLiAKCgojIyMjIEVqZXJjaWNpbwpVc2VuIGxhIGRvY3VtZW50YWNpw7NuIGRlIGBpZ3JhcGhgIHNvYnJlIGxheW91dHMgcGFyYSBlbmNvbnRyYXIgbG9zIGRpZmVyZW50ZXMgbGF5b3V0cwoKYGBge3J9Cj9nZ3JhcGggIyBMZWFuIGFsIGZpbmFsIGRlIHRvZG8gZW4gRGV0YWlscwo/bGF5b3V0ICMgTGVhbiBhbCBmaW5hbCBkZSB0b2RvIGVuIFNlZSBBbHNvCmBgYApDYW1iaWFuZG8gZWwgYXJndW1lbnRvIGRlIGxhIGZ1bmNpw7NuIGRlIGBnZ3JhcGhgIGV4cGxvcmVuIGRpc3RpbnRvcyBsYXlvdXRzIMK/YWxndW5vIGxlIGF5dWRhIGEgZW5jb250cmFyIGVzdHJ1Y3R1cmEgZW4gbG9zIGRhdG9zPyDCv3F1w6kgb2JzZXJ2YT8KCgojIyBEaXN0cmlidWNpw7NuIGRlIGdyYWRvCgpQcmV2aWFtZW50ZSBjYWxjdWxhbW9zIGxhIGNhbnRpZGFkIGRlIGluZ3JlZ2llbnRlcyBxdWUgY2FkYSBiZWJpZGEgbGxldmEuIFNpIGxvIHBlbnNhbW9zIGVuIHTDqXJtaW5vcyBkZSBsYSByZWQsIGVzdG8gZXF1aXZhbGUgYSBoYWJsYXIgZGUgbGEgY2FudGlkYWQgZGUgX2NvbmV4aW9uZXNfIHF1ZSB0aWVuZSBjYWRhIGJlYmlkYS4gQSBlc3RhIG1hZ25pdHVkIChsYSBjYW50aWRhZCBkZSBjb25leGlvbmVzIHF1ZSB0aWVuZSB1biB2w6lydGljZSkgc2UgbGEgZGVub21pbmEgX2dyYWRvXyBkZSB1biB2w6lydGljZSwgeSBlcyBsYSBtZWRpZGEgZGUgX2NlbnRyYWxpZGFkXyBlbiB1bmEgcmVkIG3DoXMgY29tw7puOiBub3MgZGljZSBkZSB1bmEgZm9ybWEgbXV5IHNpbXBsZSBjdWFuIGNvbmVjdGFkbyBlc3TDoSB1biB2w6lydGljZSBhbCByZXN0by4gVW5hIHZleiBkZWZpbmlkYSBsYSByZWQsIGVzIG11eSBmw6FjaWwgY2FsY3VsYXIgZWwgZ3JhZG8gZGUgY2FkYSB2w6lydGljZSB1c2FuZG8gbGFzIGZ1bmNpb25lcyBkZSBgaWdyYXBoYDoKCmBgYHtyfQpnICU+JSBkZWdyZWUodj0nRUdHIE5PRyAjNCcpICMgcG9kZW1vcyBwZWRpciBkZSB1biBub2RvIGVuIHBhcnRpY3VsYXIKIyBUb2RhcyBlc3RhcyBmdW5jaW9uZXMgdGFtYmnDqW4gbGFzIHB1ZWRlbiB1c2FyIGNvbW8gCmRlZ3JlZShnLHY9J0VHRyBOT0cgIzQnKQpgYGAKUG9kZW1vcyByZXBsaWNhciBlbCBncsOhZmljbyBxdWUgYXJtYW1vcyBhbnRlcyBjb250YW5kbyBlamVzIGEgbWFubzoKCmBgYHtyfQpncmFkb3MgPSBnICU+JSBkZWdyZWUodj1WKGcpJHR5cGU9PSdEcmluaycpICNBc8OtIGxlIHBlZGltb3MgZWwgZ3JhZG8gZGUgVE9EQVMgbGFzIEJFQklEQVMKZ2dwbG90KGRhdGE9ZGF0YS5mcmFtZShncmFkb3MpLG1hcHBpbmc9YWVzKHg9Z3JhZG9zKSkgKyBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDEpICMgUGVkaW1vcyBhbmNobyBkZSBiaW4gPTEgcGFyYSBxdWUgbm9zIG11ZXN0cmUgc2VwYXJhZG8gY2FkYSBncmFkbwpgYGAKTcOhcyBpbnRlcmVzYW50ZSBhw7puLCBwb2RlbW9zIGJ1c2NhciBsYSBkaXN0cmlidWNpw7NuIGRlIGdyYWRvIGRlIGNhZGEgdGlwbyBkZSBub2RvIChiZWJpZGEgbyBpbmdyZWRpZW50ZSkgeSB2ZXIgc2kgb2JzZXJ2YW1vcyBhbGd1bmEgZGlmZXJlbmNpYQoKYGBge3J9CmcgPSBnICU+JSBhY3RpdmF0ZShub2RlcykgJT4lCiAgbXV0YXRlKGdyYWRvPWRlZ3JlZShnKSkgIyBBaG9yYSBzw60sIGRpcmVjdGFtZW50ZSBhZ3JlZ2Ftb3MgdW5hIGNvbHVtbmEgcXVlIHNlYSBkZSBncmFkb3MuCgpnICU+JSBhY3RpdmF0ZShub2RlcykgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUKICBnZ3Bsb3QobWFwcGluZz1hZXMoeD1ncmFkbyxmaWxsPXR5cGUpKSArCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAxKSAKCmcgJT4lIGFjdGl2YXRlKG5vZGVzKSAlPiUgYXMuZGF0YS5mcmFtZSgpICU+JQogIGdncGxvdChtYXBwaW5nPWFlcyh4PWdyYWRvLGZpbGw9dHlwZSkpICsKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDEpICsgeGxpbShjKDAsMTApKQoKYGBgCkVzdGFzIGRpc3RyaWJ1Y2lvbmVzIHNvbiBjbGFyYW1lbnRlIG11eSBkaXN0aW50YXM6IG1pZW50cmFzIHF1ZSBsYXMgYmViaWRhcyBzZSBjb25jZW50cmFuIGFscmVkZWRvciBkZSBkZSA0IGluZ3JlZGllbnRlcywgbG9zIGluZ3JlZGllbnRlcyB2YXLDrWFuIG11Y2hvIG3DoXM6IGFsZ3Vub3MgcG9jb3Mgc29uIHVzYWRvcyBlbiB1bmEgbWlyaWFkYSBkZSBiZWJpZGFzLCBtaWVudHJhcyBxdWUgbGEgbWF5b3LDrWEgc8OzbG8gc2UgdXNhIGVuIHVuYXMgcG9jYXMgYmViaWRhcy4gCgpJbmNvcnBvcmFyIGVzdGEgaW5mb3JtYWNpw7NuIGFsIGRpYnVqbyBkZWwgZ3JhZm8gcHVlZGUgc2VyIHVuIHBvY28gZGlmaWNpbCwgc29icmUgdG9kbyBlbiBpbcOhZ2VuZXMgZXN0w6F0aWNhcyBjb21vIGVzdGFzOgoKYGBge3J9CnJlcXVpcmUoZ2dyYXBoKQoKZyAlPiUKICBnZ3JhcGgoKSArIAogIGdlb21fZWRnZV9saW5rKGVkZ2Vfd2lkdGg9LjEsYWxwaGE9MC41KSArCiAgZ2VvbV9ub2RlX3BvaW50KG1hcHBpbmcgPSBhZXMoY29sb3I9dHlwZSxzaGFwZT10eXBlLHNpemU9Z3JhZG8pKSArICMgUGVkaW1vcyBxdWUgZWwgc2l6ZSBkZWwgbm9kbyByZWZpZXJhIGFsIGdyYWRvLCB5IGxhIGhhY2Vtb3MgYSBsb3MgbcOhcyBncmFuZGVzIG3DoXMgdHJhbnNwYXJlbnRlcy4KICB0aGVtZV9ncmFwaCgpCmBgYAoKUG9yIGVzbyB0YW1iacOpbiBzdWVsZSBzZXIgbXV5IMO6dGlsIGV4cGxvcmFyIGhlcnJhbWllbnRhcyBwYXJhIHZpc3VhbGl6YWNpw7NuIGRpbsOhbWljYS4gRW4gZXN0YSDDoXJlYSB0ZW5lbW9zIGVsIHBhcXVldGUgYHZpc05ldHdvcmtgCgpgYGB7cn0KIyBpbnN0YWxsLnBhY2thZ2VzKCd2aXNOZXR3b3JrJykKcmVxdWlyZSh2aXNOZXR3b3JrKQpgYGAKClNpIGJpZW4gZXN0ZSBwYXF1ZXRlIGVuIHPDrSBtaXNtbyBwZXJtaXRlIGNvbnN0cnVpciByZWRlcyB5IGV4cGxvcmFybGFzLCBkZXNkZSBudWVzdHJhIHBlcnNwZWN0aXZhIGRlIHVzdWFyaWVzIGRlIGBpZ3JhcGhgIG5vcyBpbnRlcmVzYSBzaW1wbGVtZW50ZSBncmFmaWNhciBudWVzdHJhcyByZWRlcyBkZSBmb3JtYSBpbnRlcmFjdGl2YS4KCmBgYHtyfQojIHZpc0lncmFwaChnKSBlc3RvIHlhIG5vcyBtdWVzdHJhIGxhIHJlZCBwZXJvIGRlIGZvcm1hIHBvY28gaW50ZXJlc2FudGUKZyAlPiUgYWN0aXZhdGUobm9kZXMpICU+JQogIG11dGF0ZShsYWJlbD1uYW1lLGNvbG9yPWlmZWxzZSh0eXBlPT0nRHJpbmsnLCdyZWQnLCdibHVlJyksc2l6ZT1ncmFkbyxzaGFwZT1pZmVsc2UodHlwZT09J0RyaW5rJywnc3F1YXJlJywndHJpYW5nbGUnKSkgJT4lICNBZ3JlZ2Ftb3MgZGlyZWN0YW1lbnRlIGNvbHVtbmFzIGNvbiBjb2xvciwgZm9ybWEgeSB0YW1hw7FvIHBhcmEgcXVlIGxvcyBsZWEgdmlzTmV0d29yawogIHZpc0lncmFwaChsYXlvdXQ9J2xheW91dF9uaWNlbHknLHBoeXNpY3M9VFJVRSkgJT4lICMgQ29udmVydGltb3MgYSB2aXNOZXR3b3JrIHkgcGVkaW1vcyBxdWUgdGVuZ2EgbW92aW1pZW50byBmw61zaWNvCiAgdmlzUGh5c2ljcyhiYXJuZXNIdXQ9bGlzdCgnZGFtcGluZyc9MSkpICMgSGFjZW1vcyBxdWUgZWwgbW92aW1pZW50byBzZWEgbGVlZWVudG8KCmBgYAoKRW4gY3VhbHF1aWVyIGNhc28sIHVuYSB2aXN1YWxpemFjacOzbiDDs3B0aW1hIGN1YW5kbyBzZSBpbmNsdXllbiBtdWNob3MgdsOpcnRpY2VzIHB1ZWRlIHNlciBtdXkgZGlmaWN1bHRvc2EuCgojIyBSZWQgbW9ub3BhcnRpdGEKClNpIG51ZXN0cm8gb2JqZXRpdm8gZXMgZW50ZW5kZXIgY8OzbW8gc2UgcmVsYWNpb25hbiBsYXMgYmViaWRhcyBlbnRyZSBzw60gX2EgdHJhdsOpc18gZGUgbG9zIGluZ3JlZGllbnRlcywgcGVybyBlbiBzw60gbm8gbm9zIGltcG9ydGFuIGxvcyBpbmdyZWRpZW50ZXMsIHBvZGVtb3MgcmVwcmVzZW50YXIgbGEgcmVkIG9yaWdpbmFsICggX2JpcGFydGl0YV8gKSBjb21vIHVuYSByZWQgX21vbm9wYXJ0aXRhXywgZG9uZGUgY29uZWN0YW1vcyBlbnRyZSBzw60gYSBsYXMgYmViaWRhcyBxdWUgY29tcGFydGVuIGluZ3JlZGllbnRlcy4gTnVldmFtZW50ZSwgYWPDoSB2YW1vcyBhIGVuZm9jYXJub3MgZW4gbGEgZXhwbG9yYWNpw7NuIGRlbCBkYXRhc2V0IG3DoXMgcXVlIGVuIGxhcyB0w6ljbmljYXMgcGFyYSBjYWxjdWxhcmxhIChhdW5xdWUgcHVlZGVuIHByZWd1bnRhciBzaSBsZXMgZGEgY3VyaW9zaWRhZCkuIENvbiBgaWdyYXBoYCBsbyBoYWNlbW9zIGNvbW8KCmBgYHtyfQpnRCA9IGcgJT4lIGFjdGl2YXRlKG5vZGVzKSAlPiUgCiAgbXV0YXRlKHR5cGU9KHR5cGU9PSdEcmluaycpKSAlPiUgIyBJZ3JhcGggbmVjZXNpdGEgcXVlIGVsIHR5cGUgc2VhIGzDs2dpY28KICBiaXBhcnRpdGVfcHJvamVjdGlvbih3aGljaD0ndHJ1ZScpICU+JQogIGFzX3RibF9ncmFwaCgpCmdECmBgYApGaWplbnNlIHF1ZSBgaWdyYXBoYCBub3MgYm9ycm8gZWwgYXRyaWJ1dG8gYHR5cGVgIChxdWUgeWEgbm8gc2Vyw61hIMO6dGlsIHlhIHF1ZSBsYSByZWQgZXMgbW9ub3BhcnRpdGEpIHkgYWdyZWdvIHVuIGF0cmlidXRvIGB3ZWlnaHRgIGEgbG9zIGVqZXMsIHF1ZSBsbyBxdWUgbWlkZSBlcyBsYSBjYW50aWRhZCBkZSBpbmdyZWRpZW50ZXMgZW4gY29tw7puIGVudHJlIGRvcyBiZWJpZGFzLiBUYW1iacOpbiBzZSBjcmVhcm9uIG11Y2jDrXNpbW9zIGVqZXMgbnVldm9zIChhaG9yYSB0ZW5lbW9zIGRlbCBvcmRlbiBkZSAxNDAwMCEpLiBMYSB2aXN1YWxpemFjacOzbiBkaXJlY3RhIGRlIGVzdGEgcmVkIG5vIGF5dWRhIG11Y2hvLCBkZSBudWV2byBkZWJpZG8gYWwgbsO6bWVybyBkZSBub2RvczoKCmBgYHtyfQpnRCAlPiUgCiAgZ2dyYXBoKCkgKyAKICBnZW9tX2VkZ2VfbGluayhtYXBwaW5nPWFlcyhlZGdlX3dpZHRoPXdlaWdodCxhbHBoYT0xL3dlaWdodCkpICsgIyBQb25lbW9zIGVsIGFuY2hvIGRlbCBsaW5rIGVuIGZ1bmNpw7NuIGFsIHBlc28gZGUgZXNlIGxpbmsKICBnZW9tX25vZGVfcG9pbnQobWFwcGluZyA9IGFlcyhzaXplPWdyYWRvLGFscGhhPTEvZ3JhZG8pLGNvbG9yPSdibHVlJykgKwogIHRoZW1lX2dyYXBoKCkKYGBgCgpQb2RlbW9zIGZpbHRyYXIgYXF1ZWxsb3MgZWplcyBxdWUgc8OzbG8gdGVuZ2FuIHBlc28gbWF5b3IgYSBjaWVydG8gdmFsb3I6CgpgYGB7cn0KZ0QgJT4lIAogIGFjdGl2YXRlKGVkZ2VzKSAlPiUKICBmaWx0ZXIod2VpZ2h0PjMpICU+JSAjIE5vcyBxdWVkYW1vcyBzw7NsbyBjb24gbG9zIGVqZXMgcXVlIHJlcHJlc2VudGVuIGFsIG1lbm9zIHRyZXMgaW5ncmVkaWVudGVzCiAgZ2dyYXBoKCkgKyAKICBnZW9tX2VkZ2VfbGluayhtYXBwaW5nPWFlcyhlZGdlX3dpZHRoPXdlaWdodCxhbHBoYT0xL3dlaWdodCkpICsKICBnZW9tX25vZGVfcG9pbnQoY29sb3I9J2JsdWUnKSArCiAgdGhlbWVfZ3JhcGgoKQpgYGAKClBvZGVtb3MgaGFjZXIgdW4gaGlzdG9ncmFtYSBzb2JyZSBsb3MgcGVzb3MgZGUgbGFzIGNvbmV4aW9uZXMsIHBhcmEgdmVyIHNpIGxhcyBiZWJpZGFzIHNvbiBwYXJlY2lkYXMgbyBtdXkgZGlzdGludGFzIGVudHJlIHPDrToKCmBgYHtyfQpnRCAlPiUgYWN0aXZhdGUoZWRnZXMpICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIAogIGdncGxvdChtYXBwaW5nPWFlcyh4PXdlaWdodCkpICsKICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDEsZmlsbD0nd2hpdGUnLGNvbD0nYmxhY2snKSAKYGBgCkVzdGUgZ3LDoWZpY28gbm9zIG11ZXN0cmEgcXVlIGxhIG1heW9yw61hIGRlIGxhcyBiZWJpZGFzIGNvbXBhcnRlbiBwb2NvcyBpbmdyZWRpZW50ZXMuIFNpbiBlbWJhcmdvLCB0YW1iacOpbiBoYXkgYWxndW5hcyBjb25leGlvbmVzIGdyYW5kZXMsIHF1ZSByZXByZXNlbnRhbiBtw6FzIGRlIDEwIGluZ3JlZGllbnRlcyBjb21wYXJ0aWRvcy4KCiMjIyMgRWplcmNpY2lvCkJhc2FuZG9zZSBlbiBlbCBoaXN0b2dyYW1hLCBwcnVlYmVuIGhhY2VyIGNvcnRlcyBzb2JyZSBsb3MgcGVzb3MgZGUgbG9zIGVqZXMgeSB2ZWFuIGxhcyBlc3RydWN0dXJhcyBxdWUgc2UgZm9ybWFuLiDCv1F1w6kgcmVwcmVzZW50YW4gbG9zIGdydXBvcyBxdWUgb2J0aWVuZW4/IEVsaWphbiB1biBuw7ptZXJvIG3DrW5pbW8gZGUgaW5ncmVkaWVudGVzIGNvbXBhcnRpZG9zIHBhcmEgcXVlIGRvcyBiZWJpZGFzIHNlYW4gX3BhcmVjaWRhc18geSBhcG95YW5kb3NlIGVuIGVsIGdyw6FmaWNvLCBpZGVudGlmaXF1ZW4gcXVlIGdydXBvcyBkZSBiZWJpZGFzIG9idGllbmVuLiBMYSBmdW5jacOzbiBgY2x1c3RlcnNgIGRlIGBpZ3JhcGhgIGxlcyBwdWVkZSBzZXIgbXV5IMO6dGlsLiBSZXRvcm5hIHVuIHZlY3RvciBjb24gbGFzIGNvbXBvbmVudGVzIGNvbmV4YXMgZGUgbGEgcmVkLgoKYGBge3J9CihnRCAlPiUgYWN0aXZhdGUoZWRnZXMpICU+JSBmaWx0ZXIod2VpZ2h0PjIpICU+JSAjIFRpcmFtb3MgZWplcyBjaGljb3MKICAgY2x1c3RlcnMoKSkkbWVtYmVyc2hpcCAlPiUgIyBQZWRpbW9zIGxhICJtZW1icmVzw61hIiAoYSBxdcOpIGNvbXBvbmVudGUgcGVydGVuZWNlbikgZGUgbG9zIG5vZG9zCiAgc29ydCgpICU+JSBoZWFkKG49MTUpCiMgRXhwbG9yZW4gZWwgcmVzdWx0YWRvIGRlIGNsdXN0ZXJzIQpgYGAKCiMjIyMgRWplcmNpY2lvIChvcGNpb25hbCBwYXJhIGVudHVzaWFzdGFzKQoKUmVwaXRhbiBlbCBhbmFsaXNpcyBzb2JyZSBsYSByZWQgbW9ub3BhcnRpdGEgcGVybyBkZSBpbmdyZWRpZW50ZXMgZW4gdmV6IGRlIGJlYmlkYXMuIMK/UXXDqSByZXByZXNlbnRhbiBsYXMgY29uZXhpb25lcyBlbiBlc2EgcmVkPyDCv0N1w6FsIGVzIGVsIGluZ3JlZGllbnRlIG3DoXMgdmVyc2F0aWw/CgoKIyMgSG9tb2ZpbGlhCgpQb3Igw7psdGltbyB2YW1vcyBhIGhhY2VyIHVuIGFuw6FsaXNpcyBkZSBfaG9tb2ZpbGlhXyBlbiBsYSByZWQgZGUgYmViaWRhcy4gUGFyYSBlc28sIHZhbW9zIGEgY29uc2lkZXJhciBsYXMgY2F0ZWdvcsOtYXMgZGUgbG9zIGRpc3RpbnRvcyB0cmFnb3MuIE5lY2VzaXRhbW9zIHZvbHZlciBhbCBkYXRhc2V0IG9yaWdpbmFsIHBhcmEgcmVjdXBlcmFyIGVzdGEgaW5mb3JtYWNpw7NuCgpgYGB7cn0KdGFibGUoYmViaWRhcyRzdHJDYXRlZ29yeSkgIyBFc3RhcyBzb24gbGFzIGNhdGVnb3LDrWFzCgpnRCA9IGdEICU+JSBhY3RpdmF0ZShub2RlcykgJT4lCiAgbXV0YXRlKENhdGVnb3J5PWJlYmlkYXMkc3RyQ2F0ZWdvcnlbbWF0Y2godG91cHBlcihiZWJpZGFzJHN0ckRyaW5rKSxuYW1lKV0pICMgQWdyZWdhbW9zIGxhcyBjYXRlZ29yw61hcyBoYWNpZW5kbyB1biBtYXRjaCBjb24gZWwgZGF0YWZyYW1lIG9yaWdpbmFsCmdEICU+JSBhY3RpdmF0ZShub2RlcykgJT4lIGFzLmRhdGEuZnJhbWUoKSAjIFBhcmEgZXhwbG9yYXJsbwpgYGAKQ29uIGVzdGEgaW5mb3JtYWNpw7NuLCBsYSBwcmVndW50YSBlczogwr9MYXMgYmViaWRhcyBkZSBpZ3VhbCBjYXRlZ29yw61hIGNvbXBhcnRlbiBtw6FzIGluZ3JlZGllbnRlcyBxdWUgY29uIGxhcyBkZSBvdHJhcyBjYXRlZ29yw61hcz8KClBhcmEgY2FsY3VsYXIgZXN0byBub3MgdmFtb3MgYSB2b2x2ZXIgYSBlbnN1Y2lhciB1biBwb2NvIGxhcyBtYW5vcyBjb2RlYW5kbzoKCmBgYHtyfQpjYXRlcyA9IHVuaXF1ZShiZWJpZGFzJHN0ckNhdGVnb3J5KSAjIEFnYXJyYW1vcyBsYXMgY2F0ZWdvcsOtYXMKTSA9IG1hdHJpeCgwLG5yb3c9bGVuZ3RoKGNhdGVzKSxuY29sPWxlbmd0aChjYXRlcykpICMgRW4gZXN0YSBtYXRyaXogdmFtb3MgYSBndWFyZGFyIGVsIHBlc28gZGUgbGFzIHJlbGFjaW9uZXMgZW50cmUgY2F0ZWdvcsOtYXMKZWplcyA9IGdEICU+JSBhY3RpdmF0ZShlZGdlcykgJT4lIGFzLmRhdGEuZnJhbWUoKSAjIEFnYXJyYW1vcyBsb3MgZWplcwpub2RvcyA9IGdEICU+JSBhY3RpdmF0ZShub2RlcykgJT4lIGFzLmRhdGEuZnJhbWUoKSAjIHkgbm9kb3MKY29sbmFtZXMoTSkgPSByb3duYW1lcyhNKSA9IGNhdGVzICMgUG9uZW1vcyBub21icmVzIGEgbGEgbWF0cml6Cgpmb3IoaSBpbiAxOm5yb3coZWplcykpeyAjIFJlY29ycmVtb3MgdG9kb3MgbG9zIGVqZXMKICBlamUgPSBlamVzW2ksXSAjIEFnYXJyYW1vcyBjYWRhIGZpbGEgZGUgZWplcwogIENmcm9tID0gbm9kb3NbZWplJGZyb20sJ0NhdGVnb3J5J10gIyBWZW1vcyBsYSBjYXRlZ29yw61hIGRlbCBwdW50byBkZSBwYXJ0aWRhCiAgQ3RvID0gbm9kb3NbZWplJHRvLCdDYXRlZ29yeSddICMgSWd1YWwgcGVybyBkZSBwdW50byBkZSBsbGVnYWRhCiAgTVttaW4oQ2Zyb20sQ3RvKSxtYXgoQ2Zyb20sQ3RvKV0gPSBNW21pbihDZnJvbSxDdG8pLG1heChDZnJvbSxDdG8pXSArIGVqZSR3ZWlnaHQgIyBHdWFyZGFtb3MgZWwgdmFsb3IgYWN0dWFsaXphZG8sIHN1bWFkbyBsbyBudWV2by4KICBNW21heChDZnJvbSxDdG8pLG1pbihDZnJvbSxDdG8pXSA9IE1bbWluKENmcm9tLEN0byksbWF4KENmcm9tLEN0byldCn0KTQpgYGAKIyMjIyBFamVyY2ljaW8KCkV4cGxvcmVuIHkgdmlzdWFsaWNlbiBlc3RhIG1hdHJpei4gwr9RdcOpIHJlcHJlc2VudGEgbGEgZGlhZ29uYWwgZW4gZXN0YSBtYXRyaXo/IMK/WSBsb3Mgb3Ryb3MgZWxlbWVudG9zPyDCv0N1YWxlcyBzb24gbGFzIGNhdGVnb3LDrWFzIG3DoXMgcmVsYWNpb25hZGFzPyDCv0hheSBhbGd1bmEgY2F0ZWdvcsOtYSBxdWUgc2UgdmluY3VsZSBtw6FzIGNvbiBvdHJhcyBxdWUgY29uIHPDrSBtaXNtYT8gwr9DdcOhbCBzZSByZWxhY2lvbmEgZW4gbWVub3IgcHJvcG9yY2nDs24/CgoKCg==