Bienvenidxs a R4HR Club de R para RRHH

Bienvenidos al meetup de R4HR Club de R para RRHH. Nuestro primer meetup auspiciado por R Consortium.

R4HR Club de R para RRHH es una comunidad de aprendizaje del lenguaje de programación R para las personas que trabajen o quieran trabajar en Recursos Humanos. Las personas que integramos esta comunidad provenimos de distintas partes del mundo, y contamos con distintas experiencias, personalidades, habilidades e historias. De toda esta diversidad se nutre nuestra comunidad. Por eso buscamos:

  • Generar contenido en castellano.
  • Utilizar datos y un lenguaje familiar con la profesión.
  • Contar casos de uso de aplicación en RRHH.

Todas las personas que participan de nuestros eventos, comunidades y redes tienen que cumplir con nuestro código de conducta. Te invitamos a seguirnos en todas nuestras redes sociales en este link.

También pueden revisar todo nuestro contenido disponible en nuestro Google Drive, en este canal de YouTube y en nuestro repositorio de GitHub

Todo nuestro contenido es abierto, y se puede usar libremente citando la fuente 💪.

Licencia Creative Commons
Esta obra está bajo una Licencia Creative Commons Atribución 4.0 Internacional.

Si quieren correr el código de este documento es un script común de R pueden hacer lo siguiente. Esto funciona si el archivo está en la misma carpeta del proyecto.

knitr::purl("Sesion_37_Analisis_Exploratorio_Datos.Rmd")

Análisis Exploratorio de Datos

El Análisis Exploratorio de Datos (EDA por sus siglas en inglés) es una etapa fundamental en cualquier proyecto de datos, ya sea un tablero o un modelo predictivo.

Esta etapa es importante en parte porque te permite explorar preguntas iniciales y poner a prueba tus intuiciones. También es un proceso crítico porque hacemos un análisis de la calidad de los datos. El objetivo de la etapa es desarrollar un entendimiento de los datos.

Es un proceso que permite poner en práctica las habilidades de visualización, de limpieza y transformación de datos, y hacer los ajustes que sean necesarios previo al desarrollo de un proyecto.

El libro R para Ciencia de Datos escrito por Hadley Wickham y Garrett Grolemund y traducido por la comunidad de R Latinoamericana tiene un montón de información y guía para hacerlo paso a paso usando el conjuto de paquetes de tidyverse También vamos a explorar otros paquetes que hacen algunas cosas de manera muy simple.

Crear proyectos en RStudio

Trabajar con proyectos en RStudio hace que todo el trabajo sea más sencillo. Los proyectos crean una carpeta en nuestra PC en donde se almacenarán los archivos, tablas, scripts, y hace que todo sea más organizado.

Para crear un proyecto tenés podés entrar en: * File * New project

Y luego poner el nombre de la carpeta.

Instalar paquetes o librerías

Los paquetes o librerías son extensiones desarrolladas por la comunidad o por empresas que facilitan el uso de R y expanden sus capacidades. En este encuentro vamos a usar los siguientes paquetes:

Este último paquete, tidyverse es una colección de paquetes que permiten realizar muchas tareas de exploración, limpieza y transformación de datos.

Para utilizar un paquete, lo primero que tenemos que hacer es instalarlos. Eso lo hacemos con la función install.packages() y dentro del paréntesis tenemos que poner el nombre del paquete. Tengan en cuenta que:

  • R es un lenguaje case sensitive o sea que hay que prestar atención a mayúsculas y minúsculas.

  • Para instalar los paquetes hay que usar comillas

Este es un paso que hacemos una sola vez por computadora.

Para correr el código se tienen que parar en la línea de código que quieren usar y apretar las teclas Ctrl + Enter o bien el triángulo verde (como si fuera un ícono de “Play” ▶️)

# Instalar los paquetes DataExplorer, funModeling, y tidyverse
install.packages("DataExplorer")
install.packages("funModeling")
install.packages("tidyverse")

Esto lo que hace es instalar paquetes desde CRAN, que es un repositorio donde se publican los paquetes, asegurando un estándar de calidad y de documentación que hace que trabajar con cualquier paquete de CRAN sea seguro.

Para usar las funciones de los paquetes que instalamos, ahora tenemos que cargarlos. Esto lo que hace es de alguna manera “activar” el paquete y que podamos empezar sus funciones.

Para cargar un paquete tenemos que usar la función library(). Recuerden prestar atención a las mayúsculas y minúsculas. Ahora no son necesarias las comillas.

Una de las formas en las que nos damos cuenta que el paquete está instalado es cuando empezamos a escribir su nombre y nos aparece el nombre del paquete para autocompletar. Esta es una de las ventajas de trabajar en RStudio.

# Cargar los paquetes DataExplorer, funModeling, y tidyverse
library(DataExplorer)
library(funModeling)
library(tidyverse)

Los datos

Vamos a trabajar con dos versiones de la Encuesta KIWI de Sueldos de RH que hicimos el año pasado desde R4HR.

  • kiwi_ar.RDS: Una versión limpia y organizada de la encuesta. El formato .RDS es propio de R y matiene algunas características de los datos. Están filtradas únicamente las respuestas de Argentina.

  • encuesta_cruda.csv: La versión cruda de la encuesta con todas las respuestas.

Para cargar los archivos, tenemos que prestar atención:

  • La ubicación del archivo. En nuestro caso están en una carpeta llamada datos.

  • Recuerden que R es case sensitive, atención a mayúsculas y minúsculas.

  • La ubicación, el nombre y la extensión del archivo tienen que estar entre comillas.

Carguemos los datos. Para cargar el archivo .RDS vamos a usar la función de R base readRDS(). Para el archivo .csv vamos a usar la función read.csv() también de R base. Usamos esa función porque las columnas están separadas por un punto y coma (;).

# Cargar la versión limpia de la encuesta
kiwi <- readRDS("datos/kiwi_ar.RDS")

# Cargar el archivo crudo
crudo <- read.csv("datos/encuesta_cruda.csv", fileEncoding = "latin1", sep = ";")

Recuerden:

La instalación de los paquetes se hace una sola vez por computadora.

La carga de los paquetese se hace cada vez que se abre un script.

Exploración de datos con R base

R base contiene algunas funciones muy útiles para una primera exploración de datos. Vamos a practicar con la tabla kiwi.

Una de esas funciones es View(). En RStudio abre una pestaña mostrando la tabla de datos.

View(kiwi)

Nos hacemos los cancheros programando en R pero siempre miramos una tabla 😜.

Una función que nos permite ver la estructura del dataset es str().

# Usar la función str() con el data frame kiwi
str(kiwi)
## tibble [548 × 44] (S3: tbl_df/tbl/data.frame)
##  $ genero                    : chr [1:548] "Masculino" "Femenino" "Masculino" "Femenino" ...
##  $ genero_diverso            : chr [1:548] "No" "No" "Si" "No" ...
##  $ edad                      : num [1:548] 49 35 33 24 32 32 26 29 30 34 ...
##  $ discapacidad              : chr [1:548] "No tengo ninguna discapacidad" "No tengo ninguna discapacidad" "No tengo ninguna discapacidad" "No tengo ninguna discapacidad" ...
##  $ nivel_formacion           : chr [1:548] "Universitario completo" "Universitario completo" "Universitario en curso" "Universitario en curso" ...
##  $ carrera_grado             : chr [1:548] "RRHH / RRLL / RRTT" "RRHH / RRLL / RRTT" "RRHH / RRLL / RRTT" "RRHH / RRLL / RRTT" ...
##  $ tipo_universidad          : chr [1:548] "Universidad Privada" "Universidad Privada" "Universidad Pública" "Universidad Privada" ...
##  $ provincia                 : chr [1:548] "Ciudad Autónoma de Buenos Aires" "Buenos Aires" "Chaco" "Buenos Aires" ...
##  $ region                    : chr [1:548] "Centro" "Centro" "NEA" "Centro" ...
##  $ id                        : int [1:548] 1 2 3 4 5 6 7 8 9 10 ...
##  $ rubro                     : chr [1:548] "Servicios financieros; seguros" "Comercio" "Otros" "IT" ...
##  $ dotacion                  : num [1:548] 110 1433 6000 270 700 ...
##  $ origen_capital            : chr [1:548] "Nacional" "Nacional" "Multinacional" "Nacional" ...
##  $ dotacion_rh               : num [1:548] 2 10 55 7 4 25 3 9 2 10 ...
##  $ puesto                    : Factor w/ 6 levels "Administrativo",..: 6 2 3 2 6 3 2 3 4 3 ...
##  $ tipo_contratacion         : chr [1:548] "Full time" "Full time" "Full time" "Full time" ...
##  $ funcion_rh                : chr [1:548] "Generalista" "Administración de personal" "Relaciones laborales" "Reclutamiento y selección" ...
##  $ personas_a_cargo          : num [1:548] 1 0 8 0 3 0 0 2 1 0 ...
##  $ anios_en_empresa          : num [1:548] 5 0 9 1 10 2 3 5 7 2 ...
##  $ anios_en_puesto           : num [1:548] 5 0 3 1 3 2 3 3 4 2 ...
##  $ anios_experiencia         : num [1:548] 20 7 8 2 10 14 5 5 4 12 ...
##  $ sueldo_bruto              : num [1:548] 250000 55700 55000 47000 86747 ...
##  $ beneficios                : chr [1:548] "Medicina prepaga / Plan de salud, Horarios flexibles, Vacaciones extendidas, Licencias extendidas, Frutas y sna"| __truncated__ "Tarjeta de descuento" "Medicina prepaga / Plan de salud, Horarios flexibles, Abono de celular, Seguro de vida (adicionales a los de le"| __truncated__ "Medicina prepaga / Plan de salud, Seguro de vida (adicionales a los de ley), Idiomas" ...
##  $ bono                      : chr [1:548] "No recibo bono" "Menos de un sueldo" "No recibo bono" "No recibo bono" ...
##  $ ajuste                    : chr [1:548] "2 ajustes" "1 solo" "2 ajustes" "1 solo" ...
##  $ ajuste_porcentaje         : num [1:548] 23 20 50 25 50 10 15 17 25 30 ...
##  $ ajuste_mes                : chr [1:548] "Agosto" "Marzo" "Septiembre" "Septiembre" ...
##  $ otros_proyectos           : chr [1:548] "No" "No" "No" "No" ...
##  $ erp                       : chr [1:548] "Bejerman" "Enlatado propio" "SAP / SuccessFactors" "No tenemos sistema de gestión" ...
##  $ nombre_area               : chr [1:548] "Gestión de Personas" "Recursos Humanos" "Recursos Humanos" "Recursos Humanos" ...
##  $ mate                      : chr [1:548] "Si, está permitido" "Si, está permitido" "Si, está permitido" "Si, está permitido" ...
##  $ idioma_exigencia          : chr [1:548] "No" "No" "No" "No" ...
##  $ idioma_porcentaje         : num [1:548] 0 0 0 0.2 0 0.2 0 0.1 NA 0 ...
##  $ contactos_linkedin        : num [1:548] 7500 500 100 3000 1474 ...
##  $ satisfaccion              : num [1:548] 5 3 5 3 5 2 3 4 NA 2 ...
##  $ busqueda                  : chr [1:548] "No,  pero escucho ofertas" "No,  pero escucho ofertas" "No,  pero escucho ofertas" "No,  pero escucho ofertas" ...
##  $ beneficios_expectativa    : chr [1:548] "-" "Vacaciones extendidas, almuerzo,medicina prepaga" "Bono semestral" "mas home, bonificaciones, comedor" ...
##  $ rh_una_palabra            : chr [1:548] "Gestión" "Servicio" "Indispensable" "Empatia" ...
##  $ pregunta_bizarra          : chr [1:548] "-" "Si pensaba tener hijos en los próximos meses" "No recuerdo ninguna" "cual es tu signo?" ...
##  $ teletrabajo               : chr [1:548] "Si" "Voy rotando entre la oficina y el trabajo" "Si" "Voy rotando entre la oficina y el trabajo" ...
##  $ elementos                 : chr [1:548] "Computadora / Laptop, Abono de celular" "Computadora / Laptop" "Computadora / Laptop, Silla ergonómica, Abono de celular, Abono de internet" "Computadora / Laptop" ...
##  $ valoracion_gestion_empresa: num [1:548] 4 3 5 3 3 2 4 4 NA NA ...
##  $ es_lider                  : num [1:548] 1 0 0 0 1 0 0 0 1 0 ...
##  $ rangos_aumentos           : Factor w/ 5 levels "Sin aumentos",..: 4 3 5 4 5 2 3 3 4 4 ...

La función str() nos brinda esta información:

  • Qué tipo de objeto es: En este caso es un tibble que es un tipo de data frame propio de tidyverse.

  • Su tamaño: filas y columnas. ¿Se animan a decir cuántas filas y cuántas columnas tiene esta tabla?

  • El nombre de cada fila:: Después del signo $.

  • Qué tipo de variable es: chr, num, int, o Factor. No tenemos en este dataset, pero también existen las variables logical o lgl.

  • Una muestra de los primeros registros.

En el paquete tidyverse hay una función similar que se llama glimpse(). Probemos la función y veamos qué diferencias hay.

# Usar la función glimpse() con el data frame kiwi
glimpse(kiwi)
## Rows: 548
## Columns: 44
## $ genero                     <chr> "Masculino", "Femenino", "Masculino", "Feme…
## $ genero_diverso             <chr> "No", "No", "Si", "No", "Si", "No", "No", "…
## $ edad                       <dbl> 49, 35, 33, 24, 32, 32, 26, 29, 30, 34, 41,…
## $ discapacidad               <chr> "No tengo ninguna discapacidad", "No tengo …
## $ nivel_formacion            <chr> "Universitario completo", "Universitario co…
## $ carrera_grado              <chr> "RRHH / RRLL / RRTT", "RRHH / RRLL / RRTT",…
## $ tipo_universidad           <chr> "Universidad Privada", "Universidad Privada…
## $ provincia                  <chr> "Ciudad Autónoma de Buenos Aires", "Buenos …
## $ region                     <chr> "Centro", "Centro", "NEA", "Centro", "Centr…
## $ id                         <int> 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, …
## $ rubro                      <chr> "Servicios financieros; seguros", "Comercio…
## $ dotacion                   <dbl> 110, 1433, 6000, 270, 700, 7500, 100, 700, …
## $ origen_capital             <chr> "Nacional", "Nacional", "Multinacional", "N…
## $ dotacion_rh                <dbl> 2, 10, 55, 7, 4, 25, 3, 9, 2, 10, 1, 3, 7, …
## $ puesto                     <fct> Gerente, Analista, HRBP, Analista, Gerente,…
## $ tipo_contratacion          <chr> "Full time", "Full time", "Full time", "Ful…
## $ funcion_rh                 <chr> "Generalista", "Administración de personal"…
## $ personas_a_cargo           <dbl> 1, 0, 8, 0, 3, 0, 0, 2, 1, 0, 0, 0, 0, 0, 9…
## $ anios_en_empresa           <dbl> 5, 0, 9, 1, 10, 2, 3, 5, 7, 2, 0, 1, 4, 0, …
## $ anios_en_puesto            <dbl> 5, 0, 3, 1, 3, 2, 3, 3, 4, 2, 0, 1, 0, 2, 5…
## $ anios_experiencia          <dbl> 20, 7, 8, 2, 10, 14, 5, 5, 4, 12, 10, 13, 9…
## $ sueldo_bruto               <dbl> 250000, 55700, 55000, 47000, 86747, 85500, …
## $ beneficios                 <chr> "Medicina prepaga / Plan de salud, Horarios…
## $ bono                       <chr> "No recibo bono", "Menos de un sueldo", "No…
## $ ajuste                     <chr> "2 ajustes", "1 solo", "2 ajustes", "1 solo…
## $ ajuste_porcentaje          <dbl> 23, 20, 50, 25, 50, 10, 15, 17, 25, 30, 0, …
## $ ajuste_mes                 <chr> "Agosto", "Marzo", "Septiembre", "Septiembr…
## $ otros_proyectos            <chr> "No", "No", "No", "No", "Si, en emprendimie…
## $ erp                        <chr> "Bejerman", "Enlatado propio", "SAP / Succe…
## $ nombre_area                <chr> "Gestión de Personas", "Recursos Humanos", …
## $ mate                       <chr> "Si, está permitido", "Si, está permitido",…
## $ idioma_exigencia           <chr> "No", "No", "No", "No", "No", "No", "No", "…
## $ idioma_porcentaje          <dbl> 0.0, 0.0, 0.0, 0.2, 0.0, 0.2, 0.0, 0.1, NA,…
## $ contactos_linkedin         <dbl> 7500, 500, 100, 3000, 1474, 9240, 1000, 980…
## $ satisfaccion               <dbl> 5, 3, 5, 3, 5, 2, 3, 4, NA, 2, 5, 2, 3, 5, …
## $ busqueda                   <chr> "No,  pero escucho ofertas", "No,  pero esc…
## $ beneficios_expectativa     <chr> "-", "Vacaciones extendidas, almuerzo,medic…
## $ rh_una_palabra             <chr> "Gestión", "Servicio", "Indispensable", "Em…
## $ pregunta_bizarra           <chr> "-", "Si pensaba tener hijos en los próximo…
## $ teletrabajo                <chr> "Si", "Voy rotando entre la oficina y el tr…
## $ elementos                  <chr> "Computadora / Laptop, Abono de celular", "…
## $ valoracion_gestion_empresa <dbl> 4, 3, 5, 3, 3, 2, 4, 4, NA, NA, NA, 4, 4, 5…
## $ es_lider                   <dbl> 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 1…
## $ rangos_aumentos            <fct> Entre 21 y 30, Entre 11 y 20, Más de 30, En…

Aporta información similar, excepto por el tipo de objeto, pero glimpse() tiene una salida un poco más prolija. Además a las variables num las identifica como dbl.

Ahora probemos la función summary(). Como el data frame tiene muchas columnas vamos a seleccionar sólo 4 columnas. Una por cada tipo de variable.

# Creo una tabla con sólo 4 variables
kiwi_2 <- kiwi %>% 
  select(genero, contactos_linkedin, id, puesto) # Elijo las columnas que deseo

# Correr la función summary() con el data frame kiwi_2
summary(kiwi_2)
##     genero          contactos_linkedin       id                   puesto   
##  Length:548         Min.   :    0      Min.   :  1.0   Administrativo: 41  
##  Class :character   1st Qu.:  300      1st Qu.:137.8   Analista      :211  
##  Mode  :character   Median :  700      Median :274.5   HRBP          : 57  
##                     Mean   : 2562      Mean   :274.5   Responsable   :125  
##                     3rd Qu.: 2500      3rd Qu.:411.2   Jefe          : 66  
##                     Max.   :30000      Max.   :548.0   Gerente       : 48  
##                     NA's   :98

Con las variables de tipo character lo único que nos indica es la cantidad de filas que tiene. En cambio, con las variables de tipo Factor como es en este caso la variable puesto, nos muestra la cantidad de casos para cada rol dentro de la columna.

En cambio, con las variables numéricas, no importa si son numeric o integer, obtenemos la siguiente información:

  • Min.: el valor mínimo

  • 1st Qu.: el valor del primer cuartil

  • Median: la mediana

  • Mean: el promedio

  • 3rd Qu.: el valor del tercer cuartil

  • Max: el valor máximo

  • NA's: la cantidad de datos nulos (celdas vacías en esa columna).

La última función que veremos ahora es names(), que la información que aporta es 🥁🥁🥁 los nombres de las columnas

# Usemos la función names() con kiwi
names(kiwi)
##  [1] "genero"                     "genero_diverso"            
##  [3] "edad"                       "discapacidad"              
##  [5] "nivel_formacion"            "carrera_grado"             
##  [7] "tipo_universidad"           "provincia"                 
##  [9] "region"                     "id"                        
## [11] "rubro"                      "dotacion"                  
## [13] "origen_capital"             "dotacion_rh"               
## [15] "puesto"                     "tipo_contratacion"         
## [17] "funcion_rh"                 "personas_a_cargo"          
## [19] "anios_en_empresa"           "anios_en_puesto"           
## [21] "anios_experiencia"          "sueldo_bruto"              
## [23] "beneficios"                 "bono"                      
## [25] "ajuste"                     "ajuste_porcentaje"         
## [27] "ajuste_mes"                 "otros_proyectos"           
## [29] "erp"                        "nombre_area"               
## [31] "mate"                       "idioma_exigencia"          
## [33] "idioma_porcentaje"          "contactos_linkedin"        
## [35] "satisfaccion"               "busqueda"                  
## [37] "beneficios_expectativa"     "rh_una_palabra"            
## [39] "pregunta_bizarra"           "teletrabajo"               
## [41] "elementos"                  "valoracion_gestion_empresa"
## [43] "es_lider"                   "rangos_aumentos"
# Usemos la función names() con crudo =P
names(crudo)
##  [1] "Género"                                                                                                        
##  [2] "X.Te.identificás.como.LGBT...lesbiana..gay..bisexual..transexual..otra.minoría.sexual.."                       
##  [3] "Rango.Edad"                                                                                                    
##  [4] "Discapacidad"                                                                                                  
##  [5] "Máximo.nivel.de.formación"                                                                                     
##  [6] "Carrera"                                                                                                       
##  [7] "X.En.qué.tipo.de.universidad.estudiaste.tu.carrera.de.grado."                                                  
##  [8] "País"                                                                                                          
##  [9] "Provincia.donde.trabajas"                                                                                      
## [10] "Trabajo"                                                                                                       
## [11] "Rubro.de.la.empresa"                                                                                           
## [12] "Cantidad.de.Empleados"                                                                                         
## [13] "Origen.del.capital"                                                                                            
## [14] "X.Cuántas.personas.integran.el.área.de.RRHH."                                                                  
## [15] "X.En.qué.puesto.trabajás."                                                                                     
## [16] "Tipo.de.contratación"                                                                                          
## [17] "X.Cuál.es.tu.función.principal.en.RRHH."                                                                       
## [18] "X.Cuántas.personas.tenés.a.cargo...poné.0.si.no.tenés.gente.a.cargo."                                          
## [19] "X.Hace.cuántos.años.trabajas.en.la.empresa.donde.estás...0.para.menos.de.un.año."                              
## [20] "X.Cuántos.años.de.experiencia.tenés.en.RRHH."                                                                  
## [21] "X.Cuál.es.tu.remuneración.BRUTA.MENSUAL.en.tu.moneda.local...antes.de.impuestos.y.deducciones."                
## [22] "X.Qué.beneficios.tenés."                                                                                       
## [23] "X.Recibís.bonos."                                                                                              
## [24] "X.Tuviste.ajustes.por.inflación.en.2020."                                                                      
## [25] "X.Cuál.fue.el.porcentaje.de.aumento.acumulado.que.tuviste.en.2020."                                            
## [26] "Mes.del.último.ajuste"                                                                                         
## [27] "X.Trabajás.en.proyectos.independientes.además.de.tu.empleo."                                                   
## [28] "X.Qué.sistema.de.gestión.de.RRHH.usan.en.tu.empresa."                                                          
## [29] "X.Cómo.se.llama.el.área.en.tu.empresa."                                                                        
## [30] "X.Se.podía.tomar.mate.en.las.oficinas.de.tu.empresa...antes.del.COVID.19."                                     
## [31] "X.Te.exigieron.saber.un.idioma.extranjero..inglés..portugués..etc...para.entrar.a.trabajar.en.tu.empresa."     
## [32] "X.Qué.porcentaje.del.tiempo.usas.el.idioma.extranjero.en.tu.puesto.actual."                                    
## [33] "X.Cuántos.contactos.tenés.en.LinkedIn...poné.0.si.no.tenés.cuenta.de.LinkedIn."                                
## [34] "X.Qué.tan.satisfecho.estás.con.tu.empresa."                                                                    
## [35] "X.Estás.buscando.trabajo."                                                                                     
## [36] "X.Qué.beneficios.te.gustaría.tener."                                                                           
## [37] "Definí.a.RRHH.con.una.sola.palabra"                                                                            
## [38] "X.Cuál.es.la.pregunta.más.bizarra.que.te.han.hecho.has.hecho.en.una.entrevista."                               
## [39] "X.Estás.trabajando.desde.tu.casa."                                                                             
## [40] "X.Qué.elementos.te.proveyó.la.empresa.para.que.puedas.trabajar.desde.tu.casa."                                 
## [41] "X.Cómo.valorarías.la.gestión.de.tu.empresa.en.este.nuevo.contexto."                                            
## [42] "X.Cómo.estás.registrado.a.fiscalmente."                                                                        
## [43] "X.Hace.cuántos.años.trabajás.como.freelance."                                                                  
## [44] "X.Dónde.trabajás.habitualmente...sin.considerar.la.coyuntura.por.COVID.19."                                    
## [45] "X.Exportás.tus.servicios."                                                                                     
## [46] "Si.exportás.servicios...a.través.de.qué.medios.de.pago.recibís.los.pagos.del.exterior."                        
## [47] "X.Aceptás.pagos.en.cuotas."                                                                                    
## [48] "X.Trabajás.con.otros.freelancers.de.tu.mismo.rubro."                                                           
## [49] "X.Tu.servicio.principal.está.relacionado.con.búsqueda.y.selección."                                            
## [50] "X.Te.dedicás.principalmente.a.realizar.búsquedas.de.IT.Tecnología."                                            
## [51] "X.Trabajás.a.riesgo."                                                                                          
## [52] "X.Cuál.es.el.coeficiente.que.cobrás.por.tus.servicios."                                                        
## [53] "El.coeficiente.lo.calculás.sobre."                                                                             
## [54] "X.Ofrecés.garantía."                                                                                           
## [55] "X.Cuál.es.el.servicio.principal.que.brindas...si.brindás.más.de.un.servicio..elegí.el.que.más.ingresos.genere."
## [56] "X.Cuál.es.el.valor.hora.promedio.que.ofrecés...moneda.local."

😱, no? En las palabras de Morfeo en Matrix:

En la introducción del meetup les decíamos que el análisis exploratorio de datos es también visual. R base tiene la función plot que permite hacer gráficos. Probemos primero haciendo un gráfico por variable. La lógica de la sintaxis es la siguiente: nombre_dataframe$nombre_variable. Por ejemplo, para graficar la variable edad tengo que escribir:

plot(kiwi$edad)

# Prueben graficar la variable puesto
plot(kiwi$puesto)

# Prueben graficar la variable genero
kiwi$genero <- as.factor(kiwi$genero)

plot(kiwi$genero)

Al final del encuentro veremos otra manera de hacer gráficos.

DataExplorer

El paquete DataExplorer desarrollado por Boxuan Cui tiene tres objetivos principales:

  1. Realizar análisis exploratorios de datos.

  2. Desarrollar tareas de feature engineering

  3. Generar reportes.

Hoy solo veremos los puntos 1 y 3.

Para el análisis exploratorio, una de las primeras funciones que vemos en la documentación del paquete es la función introduce(). Usando el signo de interrogación delante de la función podemos abrir la ayuda de la función.

# Revisar la ayuda de la función introduce()
?introduce()

# Usar la función con la tabla kiwi
introduce(kiwi)
## # A tibble: 1 × 9
##    rows columns discrete_columns continuous_columns all_missing_columns
##   <int>   <int>            <int>              <int>               <int>
## 1   548      44               29                 15                   0
## # … with 4 more variables: total_missing_values <int>, complete_rows <int>,
## #   total_observations <int>, memory_usage <dbl>

Esta función nos permite ver:

  • rows: la cantidad de filas

  • columns: la cantidad de variables

  • discrete_columns: la cantidad de variables discretas

  • continuos_columns: el número de variables continuas

  • all_missing_columns: columnas que sólo contienen valores nulos

  • total_missing_values: cantidad de celdas con valores nulos

  • complete_rows: total de filas que no tienen ningún dato faltante

  • total_observations: Es la cantidad total de celdas

  • memory_usage: es el tamaño estimado del data frame en bytes.

Toda esta información también la podemos ver visualmente con la función plot_intro(). Y con la función plot_missing() podemos ver el porcentaje de datos nulos por columna, y la misma función establece si es problemático o no.

# Usar la función plot_intro()
plot_intro(kiwi)

# Usar la función plot_missing()
plot_missing(kiwi)

# Usar la función plot_missing() modificando el parámetro missing_only
plot_missing(kiwi, missing_only = TRUE)

Otra función interesante es plot_bar() que hace gráficos de barra de todas las variables discretas.

# Usar la función plot_bar() con el data frame kiwi
plot_bar(kiwi, maxcat = 20)

El primer intento no sale del todo bien porque hay respuestas con mucho texto. Probemos modificando el parámetro maxcat = 20.

Otro parámetro interesante dentro de plot_bar() es by que le indica a la función que realice los gráficos pero explicados por alguna variable de interés. Lo que hace es generar gráficos de barras apilados al 100% para ver cómo se distribuyen las proporciones de las variables.

# Repitamos el gráfico anterior pero con by = "genero"
plot_bar(kiwi, maxcat = 20, by = "genero")

Algo que siempre debemos hacer con las variables numéricas es analizar su distribución. Para eso podemos usar la función plot_histogram().

# Usar la función plot_histogram() para analizar las variables numéricas
plot_histogram(kiwi)

Este análisis ya nos permite detectar algunas anomalías, por ejemplo en las variables anios_en_empresa y anios_experiencia tenemos varios valores imposibles. ¿Pueden ver cuáles son? Un tema aparte es el id que si bien está representado por un número no deberíamos usarlo para los análisis. Una forma de resolverlo es usando la función drop_column() del paquete DataExplorer o select() de tidyverse.

# Opción 1: drop_column()
kiwi_3 <- drop_columns(kiwi, 
             ind = "id")

# Opción 2: select()
kiwi_3b <- kiwi %>% 
  select(-id)

Algo que también podemos apreciar es que las variables satisfaccion y la variable valoracion_gestion_empresa si bien están representadas por números, en realidad son variables ordinales y por lo tanto deberíamos cambiarlas a variables de tipo factor. Para eso usaremos la función update_columns().

# Sobrescribir un objeto usando la función update_columns()
kiwi_4 <- update_columns(kiwi_3,                                          # data frame
                         c("satisfaccion", "valoracion_gestion_empresa"), # columnas a modificar
                         what = as.factor)                                # a qué tipo

# Probar los cambios 
summary(kiwi_4$satisfaccion)
##    1    2    3    4    5 NA's 
##   30   61  184  136   50   87

Este paquete tiene varias opciones de gráficos. Prueben crear un nuevo data frame con 4 variables numéricas y una variable categórica usando la función select() y probar las siguientes funciones.

Para probar las funciones pueden usar algunos de los bloques de código anteriores o bien abrir uno nuevo apretando las teclas Ctrl + Alt + I. Prueben modificar los gráficos modificando el parámetro by con la variable categórica que hayan elegido.

  • plot_qq()

  • plot_scatterplot()

  • plot_boxplot()

Hay varias funciones muy interesantes para descubrir que están explicadas en la página del paquete.

La última función que vamos a ver de este paquete se llama create_report() y prepárense para 🤯.

# Usar la función create_report()
create_report(kiwi)

Prueben crear un data frame con 5 variables. Una de ellas tiene que ser genero el resto pueden ser dos categóricas y dos numéricas por ejemplo y corran la función de la siguiente manera: create_report(kiwi_4, y = "genero").

funModeling

funModeling es un paquete desarrollado por Pablo Casas, a quien tuvimos el placer de tener en R4HR en la sesión 9. Si todavía están explorando qué lenguajes estudiar pueden explorar sus cursos gratuitos de la Escuela de Datos Vivos. Respecto del paquete, Pablo escribió un libro explicando sus funciones y mucho más, que se llama Libro Vivo de Ciencia de Datos.

Las salidas de este paquete generan mucha información así que para los fines prácticos de este encuentro, seleccionemos 7 variables:

# Seleccionemos 7 variables
kiwi_7 <- kiwi %>% 
  select(sueldo_bruto, puesto, rubro, anios_experiencia, satisfaccion, dotacion, contactos_linkedin)

La primera función que vamos a explorar se llama status(). Probemosla con nuestro nuevo data frame reducido.

# Usar la función status()
status(kiwi_7)
##                              variable q_zeros    p_zeros q_na      p_na q_inf
## sueldo_bruto             sueldo_bruto       0 0.00000000    0 0.0000000     0
## puesto                         puesto       0 0.00000000    0 0.0000000     0
## rubro                           rubro       0 0.00000000    0 0.0000000     0
## anios_experiencia   anios_experiencia       8 0.01459854    0 0.0000000     0
## satisfaccion             satisfaccion       0 0.00000000   87 0.1587591     0
## dotacion                     dotacion       0 0.00000000    0 0.0000000     0
## contactos_linkedin contactos_linkedin      20 0.03649635   98 0.1788321     0
##                    p_inf      type unique
## sueldo_bruto           0   numeric    241
## puesto                 0    factor      6
## rubro                  0 character     27
## anios_experiencia      0   numeric     35
## satisfaccion           0   numeric      5
## dotacion               0   numeric    163
## contactos_linkedin     0   numeric    167

Esta función nos aporta la siguiente información:

  • La cantidad de 0, NA y valores infinitos: q_zeros, q_na, y q_inf respectivamente y el porcentaje que representan.

  • Que tipo de variable contiene cada columna: type

  • La cantidad de valores únicos que contiene la variable: unique.

Este último dato es importante especialmente en variables de tipo factor o character porque nos da una idea de la cardinalidad de la variable. Por ejemplo la variable puesto tiene 6 valores únicos (Administrativo, Analista, HRBP, Responsable, Jefe, Gerente), su cardinalidad es mucho menor que el de la variable rubro donde tenemos 27 valores únicos.

Esto nos sirve para identificar variables que podemos utilizar para agrupar, lo cual también puede hacerse con variables numéricas como satisfaccion, ya que al tener tan pocos valores únicos, nos da una idea de que puede ser una variable ordinal que podríamos convertir a factor por ejemplo.

Otra función muy útil para analizar variables categóricas u ordinales es la función freq(). Veamos que hace:

# Usar la función freq() en kiwi_7
freq(kiwi_7)

##           puesto frequency percentage cumulative_perc
## 1       Analista       211      38.50           38.50
## 2    Responsable       125      22.81           61.31
## 3           Jefe        66      12.04           73.35
## 4           HRBP        57      10.40           83.75
## 5        Gerente        48       8.76           92.51
## 6 Administrativo        41       7.48          100.00

##                                                                  rubro
## 1                                                                   IT
## 2                                                                Otros
## 3                                                   Servicios de salud
## 4                                                             Comercio
## 5                                        Alimentación; bebidas; tabaco
## 6                                             Servicios de consultoría
## 7                                                         Construcción
## 8                                                      Función pública
## 9                                               Industria autopartista
## 10                                             Servicios profesionales
## 11                                      Servicios financieros; seguros
## 12                                    Hotelería, restauración, turismo
## 13 Transporte (incluyendo aviación civil; ferrocarriles por carretera)
## 14                                                           Oil & gas
## 15                                                                Agro
## 16                         Servicios públicos (agua;gas; electricidad)
## 17                                   Textiles; vestido; cuero; calzado
## 18                                                           Educación
## 19                                                 Industrias químicas
## 20                                               Bancos; banca online;
## 21                                      Minería (carbón, otra minería)
## 22                       Servicios de correos, y de telecomunicaciones
## 23                           Medios de comunicación; cultura; gráficos
## 24                                       Producción de metales básicos
## 25                                       Transporte marítimo; puertos;
## 26                               Silvicultura; madera; celulosa; papel
## 27                                                 Ingeniería mecánica
##    frequency percentage cumulative_perc
## 1         81      14.78           14.78
## 2         80      14.60           29.38
## 3         59      10.77           40.15
## 4         39       7.12           47.27
## 5         35       6.39           53.66
## 6         31       5.66           59.32
## 7         23       4.20           63.52
## 8         19       3.47           66.99
## 9         19       3.47           70.46
## 10        19       3.47           73.93
## 11        18       3.28           77.21
## 12        14       2.55           79.76
## 13        14       2.55           82.31
## 14        13       2.37           84.68
## 15        11       2.01           86.69
## 16        11       2.01           88.70
## 17        11       2.01           90.71
## 18        10       1.82           92.53
## 19         8       1.46           93.99
## 20         6       1.09           95.08
## 21         6       1.09           96.17
## 22         6       1.09           97.26
## 23         5       0.91           98.17
## 24         4       0.73           98.90
## 25         3       0.55           99.45
## 26         2       0.36           99.81
## 27         1       0.18          100.00
## [1] "Variables processed: puesto, rubro"

Esta función nos provee una tabla de frecuencias, es decir, la cantidad de veces que aparece cada valor (es decir su frecuencia), el porcentaje que representa, y el porcentaje acumulado de cada valor (si quieren cancherear frente a un/a estadístico/a esta última columna esto se llama frecuencia acumulada relativa).

Para las variables núméricas vamos a ver dos funciones. La primera se llama plot_num() y la siguiente es profiling_num().

# Correr la función plot_num() en kiwi_7
plot_num(kiwi_7)

En estos gráficos lo que vemos es la distribución de las variables numéricas en forma de histogramas. O al menos esa es la intención, porque por la presencia de los outliers no podemos apreciar mucho la distribución de los datos con este tipo de gráficos.

Probemos ahora la función profiling_num().

# Usar la función profiling_num() en kiwi_7
profiling_num(kiwi_7)
##             variable         mean      std_dev variation_coef   p_01    p_05
## 1       sueldo_bruto 90869.195858 1.041184e+05      1.1458047 121.38 34030.1
## 2  anios_experiencia    10.001825 3.240968e+01      3.2403770   0.00     2.0
## 3       satisfaccion     3.249458 1.030315e+00      0.3170729   1.00     1.0
## 4           dotacion  3292.917883 2.473126e+04      7.5104402  10.00    30.0
## 5 contactos_linkedin  2562.097778 4.724548e+03      1.8440154   0.00     8.9
##    p_25  p_50   p_75   p_95   p_99   skewness   kurtosis   iqr
## 1 55000 73300 102000 192600 290000 14.3985038 276.655621 47000
## 2     4     7     12     20     30 21.5063064 487.351842     8
## 3     3     3      4      5      5 -0.2737854   2.765024     1
## 4   115   300   1100  10000  35070 15.1973173 242.732520   985
## 5   300   700   2500  11275  25000  3.4600918  16.772339  2200
##                 range_98        range_80
## 1       [121.38, 290000] [40000, 150000]
## 2                [0, 30]         [2, 15]
## 3                 [1, 5]          [2, 5]
## 4 [10, 35069.9999999995]    [50.7, 4000]
## 5             [0, 25000]      [50, 7000]

Acá tenemos un montón de información:

  • Lo primero que vemos es el promedio (mean) y el desvío estándar (std_dev).

  • Luego vemos el coeficiente de variación (variation_coef) que es el resultado de dividir el desvío estándar por el promedio.

  • Luego vemos el valor en los percentiles 1, 5, 25, 50, 75, 95, 99.

  • Luego vemos el sesgo (skewness) que tiene que ver con la simetría de la distribución de los datos y el kurtosis que a grosso modo indica la presencia de valores extremos.

  • El rango intercuartil (iqr) que es la diferencia entre el tercer cuartil menos el primer cuartil. O lo que es lo mismo, el percentil 75 menos el percentil 25.

  • Después tenemos dos rangos, entre los percentiles 1 y 99, y entre los percentiles 10 y 90.

Es mucha información estadística, ¿no? Pero veamos para qué nos puede servir. Los resultados de profiling_num() los podemos guardar en una tabla y usarlo para limpiar nuestros datos.

# Crear un objeto con el análisis de profiling_num()
var_num <- profiling_num(kiwi_7)

# Ver el contenido de var_num
var_num   # Apretar Ctrl + Enter
##             variable         mean      std_dev variation_coef   p_01    p_05
## 1       sueldo_bruto 90869.195858 1.041184e+05      1.1458047 121.38 34030.1
## 2  anios_experiencia    10.001825 3.240968e+01      3.2403770   0.00     2.0
## 3       satisfaccion     3.249458 1.030315e+00      0.3170729   1.00     1.0
## 4           dotacion  3292.917883 2.473126e+04      7.5104402  10.00    30.0
## 5 contactos_linkedin  2562.097778 4.724548e+03      1.8440154   0.00     8.9
##    p_25  p_50   p_75   p_95   p_99   skewness   kurtosis   iqr
## 1 55000 73300 102000 192600 290000 14.3985038 276.655621 47000
## 2     4     7     12     20     30 21.5063064 487.351842     8
## 3     3     3      4      5      5 -0.2737854   2.765024     1
## 4   115   300   1100  10000  35070 15.1973173 242.732520   985
## 5   300   700   2500  11275  25000  3.4600918  16.772339  2200
##                 range_98        range_80
## 1       [121.38, 290000] [40000, 150000]
## 2                [0, 30]         [2, 15]
## 3                 [1, 5]          [2, 5]
## 4 [10, 35069.9999999995]    [50.7, 4000]
## 5             [0, 25000]      [50, 7000]

Ahora tenemos una tabla, un data frame, de la cual podemos usar sus celdas. En particular ahora nos interesan de la fila de sueldo_bruto las celdas correspondientes a los percentiles 5 y 95 para utilizar en un filtro. Para lograr eso tenemos que seguir la siguiente lógica: nombre_dataframe[fila,columna].

# Guardamos el valor de los percentiles 5 y 95 en un objeto cada uno.
p05 <- var_num[1,6]   # El valor de la fila 1, columna 6
p95 <- var_num[1,10]  # El valor de la fila 1, columna 10
  
# Veamos el contenido de ambos valores
p05
## [1] 34030.1
p95
## [1] 192600

Ahora podemos usar esos valores como filtros de la variable sueldo_bruto.

# Filtramos las filas que estén por encima de p05 y por debajo de p95
kiwi_8 <- kiwi_7 %>%
  filter(between(sueldo_bruto,   # Columna a filtrar
         p05,                    # Umbral mínimo
         p95))                    # Umbral máximo

# Probemos la función summary() en kiwi_8
summary(kiwi_8)
##   sueldo_bruto               puesto       rubro           anios_experiencia
##  Min.   : 34086   Administrativo: 34   Length:492         Min.   :  0.000  
##  1st Qu.: 55550   Analista      :197   Class :character   1st Qu.:  4.000  
##  Median : 73300   HRBP          : 52   Mode  :character   Median :  7.000  
##  Mean   : 81748   Responsable   :122                      Mean   :  9.905  
##  3rd Qu.: 98125   Jefe          : 59                      3rd Qu.: 11.000  
##  Max.   :190000   Gerente       : 28                      Max.   :746.000  
##                                                                            
##   satisfaccion     dotacion        contactos_linkedin
##  Min.   :1.00   Min.   :     4.0   Min.   :    0     
##  1st Qu.:3.00   1st Qu.:   119.2   1st Qu.:  300     
##  Median :3.00   Median :   300.0   Median :  700     
##  Mean   :3.26   Mean   :  2672.4   Mean   : 2400     
##  3rd Qu.:4.00   3rd Qu.:  1025.0   3rd Qu.: 2400     
##  Max.   :5.00   Max.   :400000.0   Max.   :30000     
##  NA's   :73                        NA's   :82
# Corramos la función plot_num() en kiwi_8
plot_num(kiwi_8)

Explorando intuiciones

Dijimos que el Análisis Exploratorio de Datos sirve también para explorar intuiciones. Una de ellas está relacionada con la satisfacción, ¿está relacionada con el salario? Veamos cómo podemos responder a ello.

Sabemos que la columna satisfaccion tiene campos nulos, por lo que tenemos que filtrarlos.

# Filtramos todo lo que no sea un NA de la variable satisfaccion
kiwi_9 <- kiwi_8 %>% 
  filter(!is.na(satisfaccion)) # El signo ! es el operador lógico NOT

# Probemos hacer un boxplot
ggplot(kiwi_9, aes(x = factor(satisfaccion), y = sueldo_bruto)) +
  geom_boxplot()

Seguir aprendiendo

Pueden probar en este documento cambiando variables, usar otro dataset, ya sea el dataset crudo o alguno propio. Cuando terminen, corran este documento con el botón de knit o bien con las teclas Ctrl + Shift + K. Eso va a generar un documento que pueden publicar en línea y compartir en sus redes lo que aprendieron.

En los resultados que publicamos de la Encuesta KIWI de Sueldos de RH pueden ver cómo utilizamos todas estas técnicas para limpiar el archivo crudo que usamos en esta sesión y que pueden replicar por su cuenta.

Aprovechen el contenido de nuestro Google Drive y de nuestro repositorio de GitHub. Particularmente busquen las sesiones relacionadas con dplyr, ggplot2, y forcats que son complementos excelentes de este encuentro.

Súmense a nuestro canal de Slack y participen haciendo consultas y compartiendo contenido de valor.

LS0tCnRpdGxlOiAiQW7DoWxpc2lzIEV4cGxvcmF0b3JpbyBkZSBEYXRvcyIKYXV0aG9yOiAiU2VyZ2lvIHwgUjRIUiBDbHViIGRlIFIgcGFyYSBSUkhIIgpkYXRlOiAiMzEvOC8yMDIxIgpvdXRwdXQ6IAogIGh0bWxfZG9jdW1lbnQ6CiAgICB0aGVtZTogbHVtZW4KICAgIGhpZ2hsaWdodDogcHlnbWVudHMKICAgIHRvYzogdHJ1ZQogICAgdG9jX2Zsb2F0OiB0cnVlCiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKICAgIGNvZGVfZG93bmxvYWQ6IHRydWUKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBtZXNzYWdlID0gRkFMU0UsIHdhcm5pbmcgPSBGQUxTRSwgb3V0LndpZHRoID0gIjgwJSIsIGZpZy5yZXRpbmEgPSAzKQpgYGAKCiFbXShodHRwczovL3NlY3VyZS5tZWV0dXBzdGF0aWMuY29tL3Bob3Rvcy9ldmVudC84LzUvZS9kL2NsZWFuXzQ5ODM5NDI4NS5qcGVnKQoKIyBCaWVudmVuaWR4cyBhIFI0SFIgQ2x1YiBkZSBSIHBhcmEgUlJISAoKQmllbnZlbmlkb3MgYWwgbWVldHVwIGRlIFI0SFIgQ2x1YiBkZSBSIHBhcmEgUlJISC4gTnVlc3RybyBwcmltZXIgbWVldHVwIGF1c3BpY2lhZG8gcG9yIFtSIENvbnNvcnRpdW1dKGh0dHBzOi8vd3d3LnItY29uc29ydGl1bS5vcmcvKS4KCioqUjRIUiBDbHViIGRlIFIgcGFyYSBSUkhIKiogZXMgdW5hIGNvbXVuaWRhZCBkZSBhcHJlbmRpemFqZSBkZWwgbGVuZ3VhamUgZGUgcHJvZ3JhbWFjacOzbiBSIHBhcmEgbGFzIHBlcnNvbmFzIHF1ZSB0cmFiYWplbiBvIHF1aWVyYW4gdHJhYmFqYXIgZW4gUmVjdXJzb3MgSHVtYW5vcy4gTGFzIHBlcnNvbmFzIHF1ZSBpbnRlZ3JhbW9zIGVzdGEgY29tdW5pZGFkIHByb3Zlbmltb3MgZGUgZGlzdGludGFzIHBhcnRlcyBkZWwgbXVuZG8sIHkgY29udGFtb3MgY29uIGRpc3RpbnRhcyBleHBlcmllbmNpYXMsIHBlcnNvbmFsaWRhZGVzLCBoYWJpbGlkYWRlcyBlIGhpc3Rvcmlhcy4gRGUgdG9kYSBlc3RhIGRpdmVyc2lkYWQgc2UgbnV0cmUgbnVlc3RyYSBjb211bmlkYWQuIFBvciBlc28gYnVzY2Ftb3M6CgotICAgR2VuZXJhciBjb250ZW5pZG8gZW4gY2FzdGVsbGFuby4KLSAgIFV0aWxpemFyIGRhdG9zIHkgdW4gbGVuZ3VhamUgZmFtaWxpYXIgY29uIGxhIHByb2Zlc2nDs24uCi0gICBDb250YXIgY2Fzb3MgZGUgdXNvIGRlIGFwbGljYWNpw7NuIGVuIFJSSEguCgpUb2RhcyBsYXMgcGVyc29uYXMgcXVlIHBhcnRpY2lwYW4gZGUgbnVlc3Ryb3MgZXZlbnRvcywgY29tdW5pZGFkZXMgeSByZWRlcyB0aWVuZW4gcXVlIGN1bXBsaXIgY29uIG51ZXN0cm8gW2PDs2RpZ28gZGUgY29uZHVjdGFdKGh0dHBzOi8vcjRoci5jbHViL2NvZGlnby1kZS1jb25kdWN0YS8pLiBUZSBpbnZpdGFtb3MgYSBzZWd1aXJub3MgZW4gdG9kYXMgbnVlc3RyYXMgcmVkZXMgc29jaWFsZXMgZW4gW2VzdGUgbGlua10oaHR0cHM6Ly9saW5rdHIuZWUvcjRocmNsdWIpLgoKVGFtYmnDqW4gcHVlZGVuIHJldmlzYXIgdG9kbyBudWVzdHJvIGNvbnRlbmlkbyBkaXNwb25pYmxlIGVuIFtudWVzdHJvIEdvb2dsZSBEcml2ZV0oaHR0cHM6Ly9kcml2ZS5nb29nbGUuY29tL2RyaXZlL2ZvbGRlcnMvMVFjazN6X3Q2WExSWGIydmJOLTAwOTMxRGdkSloweXNlP3VzcD1zaGFyaW5nKSwgZW4gW2VzdGUgY2FuYWwgZGUgWW91VHViZV0oaHR0cHM6Ly95b3V0dWJlLmNvbS9wbGF5bGlzdD9saXN0PVBMWnVWeXRVSnJ4UWxjcXU2bC1QM291NHZWMm1SSlUyS2EpIHkgZW4gbnVlc3RybyBbcmVwb3NpdG9yaW8gZGUgR2l0SHViXShodHRwczovL2dpdGh1Yi5jb20vcjRoci9jbHViX2RlX3IpCgpUb2RvIG51ZXN0cm8gY29udGVuaWRvIGVzIGFiaWVydG8sIHkgc2UgcHVlZGUgdXNhciBsaWJyZW1lbnRlIGNpdGFuZG8gbGEgZnVlbnRlIPCfkqouCgo8YSByZWw9ImxpY2Vuc2UiIGhyZWY9Imh0dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LzQuMC8iPjxpbWcgc3JjPSJodHRwczovL2kuY3JlYXRpdmVjb21tb25zLm9yZy9sL2J5LzQuMC84OHgzMS5wbmciIGFsdD0iTGljZW5jaWEgQ3JlYXRpdmUgQ29tbW9ucyIgc3R5bGU9ImJvcmRlci13aWR0aDowIi8+PC9hPjxiciAvPkVzdGEgb2JyYSBlc3TDoSBiYWpvIHVuYSA8YSByZWw9ImxpY2Vuc2UiIGhyZWY9Imh0dHA6Ly9jcmVhdGl2ZWNvbW1vbnMub3JnL2xpY2Vuc2VzL2J5LzQuMC8iPkxpY2VuY2lhIENyZWF0aXZlIENvbW1vbnMgQXRyaWJ1Y2nDs24gNC4wIEludGVybmFjaW9uYWw8L2E+LgoKU2kgcXVpZXJlbiBjb3JyZXIgZWwgY8OzZGlnbyBkZSBlc3RlIGRvY3VtZW50byBlcyB1biBzY3JpcHQgY29tw7puIGRlIFIgcHVlZGVuIGhhY2VyIGxvIHNpZ3VpZW50ZS4gRXN0byBmdW5jaW9uYSBzaSBlbCBhcmNoaXZvIGVzdMOhIGVuIGxhIG1pc21hIGNhcnBldGEgZGVsIHByb3llY3RvLgoKYGBge3IgcHJldmlhLCBldmFsPUZBTFNFfQprbml0cjo6cHVybCgiU2VzaW9uXzM3X0FuYWxpc2lzX0V4cGxvcmF0b3Jpb19EYXRvcy5SbWQiKQpgYGAKCiMgQW7DoWxpc2lzIEV4cGxvcmF0b3JpbyBkZSBEYXRvcwoKRWwgKipBbsOhbGlzaXMgRXhwbG9yYXRvcmlvIGRlIERhdG9zKiogKEVEQSBwb3Igc3VzIHNpZ2xhcyBlbiBpbmdsw6lzKSBlcyB1bmEgZXRhcGEgZnVuZGFtZW50YWwgZW4gY3VhbHF1aWVyIHByb3llY3RvIGRlIGRhdG9zLCB5YSBzZWEgdW4gdGFibGVybyBvIHVuIG1vZGVsbyBwcmVkaWN0aXZvLgoKRXN0YSBldGFwYSBlcyBpbXBvcnRhbnRlIGVuIHBhcnRlIHBvcnF1ZSB0ZSBwZXJtaXRlIGV4cGxvcmFyIHByZWd1bnRhcyBpbmljaWFsZXMgeSBwb25lciBhIHBydWViYSB0dXMgaW50dWljaW9uZXMuIFRhbWJpw6luIGVzIHVuIHByb2Nlc28gY3LDrXRpY28gcG9ycXVlIGhhY2Vtb3MgdW4gYW7DoWxpc2lzIGRlIGxhIGNhbGlkYWQgZGUgbG9zIGRhdG9zLiBFbCBvYmpldGl2byBkZSBsYSBldGFwYSBlcyBkZXNhcnJvbGxhciB1biBlbnRlbmRpbWllbnRvIGRlIGxvcyBkYXRvcy4KCkVzIHVuIHByb2Nlc28gcXVlIHBlcm1pdGUgcG9uZXIgZW4gcHLDoWN0aWNhIGxhcyBoYWJpbGlkYWRlcyBkZSB2aXN1YWxpemFjacOzbiwgZGUgbGltcGllemEgeSB0cmFuc2Zvcm1hY2nDs24gZGUgZGF0b3MsIHkgaGFjZXIgbG9zIGFqdXN0ZXMgcXVlIHNlYW4gbmVjZXNhcmlvcyBwcmV2aW8gYWwgZGVzYXJyb2xsbyBkZSB1biBwcm95ZWN0by4KCkVsIGxpYnJvIFsqKlIgcGFyYSBDaWVuY2lhIGRlIERhdG9zKipdKGh0dHBzOi8vZXMucjRkcy5oYWRsZXkubnovYW4lQzMlQTFsaXNpcy1leHBsb3JhdG9yaW8tZGUtZGF0b3MtZWRhLmh0bWwpIGVzY3JpdG8gcG9yIEhhZGxleSBXaWNraGFtIHkgR2FycmV0dCBHcm9sZW11bmQgeSB0cmFkdWNpZG8gcG9yIGxhIGNvbXVuaWRhZCBkZSBSIExhdGlub2FtZXJpY2FuYSB0aWVuZSB1biBtb250w7NuIGRlIGluZm9ybWFjacOzbiB5IGd1w61hIHBhcmEgaGFjZXJsbyBwYXNvIGEgcGFzbyB1c2FuZG8gZWwgY29uanV0byBkZSBwYXF1ZXRlcyBkZSB0aWR5dmVyc2UgVGFtYmnDqW4gdmFtb3MgYSBleHBsb3JhciBvdHJvcyBwYXF1ZXRlcyBxdWUgaGFjZW4gYWxndW5hcyBjb3NhcyBkZSBtYW5lcmEgbXV5IHNpbXBsZS4KCiMjIENyZWFyIHByb3llY3RvcyBlbiBSU3R1ZGlvCgpUcmFiYWphciBjb24gcHJveWVjdG9zIGVuIFJTdHVkaW8gaGFjZSBxdWUgdG9kbyBlbCB0cmFiYWpvIHNlYSBtw6FzIHNlbmNpbGxvLiBMb3MgcHJveWVjdG9zIGNyZWFuIHVuYSBjYXJwZXRhIGVuIG51ZXN0cmEgUEMgZW4gZG9uZGUgc2UgYWxtYWNlbmFyw6FuIGxvcyBhcmNoaXZvcywgdGFibGFzLCBzY3JpcHRzLCB5IGhhY2UgcXVlIHRvZG8gc2VhIG3DoXMgb3JnYW5pemFkby4KClBhcmEgY3JlYXIgdW4gcHJveWVjdG8gdGVuw6lzIHBvZMOpcyBlbnRyYXIgZW46IFwqICpGaWxlKiBcKiAqTmV3IHByb2plY3QqCgpZIGx1ZWdvIHBvbmVyIGVsIG5vbWJyZSBkZSBsYSBjYXJwZXRhLgoKIVtdKEFyY2hpdm9zL25ld19wcm9qZWN0LnBuZyl7d2lkdGg9IjMzNyJ9CgohW10oQXJjaGl2b3MvbmV3X3Byb2plY3RvLnBuZykKCiMjIEluc3RhbGFyIHBhcXVldGVzIG8gbGlicmVyw61hcwoKTG9zIHBhcXVldGVzIG8gbGlicmVyw61hcyBzb24gZXh0ZW5zaW9uZXMgZGVzYXJyb2xsYWRhcyBwb3IgbGEgY29tdW5pZGFkIG8gcG9yIGVtcHJlc2FzIHF1ZSBmYWNpbGl0YW4gZWwgdXNvIGRlIFIgeSBleHBhbmRlbiBzdXMgY2FwYWNpZGFkZXMuIEVuIGVzdGUgZW5jdWVudHJvIHZhbW9zIGEgdXNhciBsb3Mgc2lndWllbnRlcyBwYXF1ZXRlczoKCi0gICBgRGF0YUV4cGxvcmVyYDogRGVzYXJyb2xsYWRvIHBvciBCb3h1YW4gQ3VpLCBbTGluayBhIHN1IHdlYl0oaHR0cHM6Ly9DUkFOLlItcHJvamVjdC5vcmcvcGFja2FnZT1EYXRhRXhwbG9yZXIpLgoKLSAgIGBmdW5Nb2RlbGluZ2A6IERlc2Fycm9sbGFkbyBwb3IgUGFibG8gQ2FzYXMuIFtMaW5rIGFsIGxpYnJvXShodHRwczovL2xpYnJvdml2b2RlY2llbmNpYWRlZGF0b3MuYWkvKS4KCi0gICBgdGlkeXZlcnNlYDogRGVzYXJyb2xsYWRvIHBvciBIYWRsZXkgV2lja2hhbSB5IG11Y2hvcyBtw6FzLiBbTGluayBhIGxhIHdlYl0oaHR0cHM6Ly93d3cudGlkeXZlcnNlLm9yZy8pLgoKRXN0ZSDDumx0aW1vIHBhcXVldGUsIGB0aWR5dmVyc2VgIGVzIHVuYSBjb2xlY2Npw7NuIGRlIHBhcXVldGVzIHF1ZSBwZXJtaXRlbiByZWFsaXphciBtdWNoYXMgdGFyZWFzIGRlIGV4cGxvcmFjacOzbiwgbGltcGllemEgeSB0cmFuc2Zvcm1hY2nDs24gZGUgZGF0b3MuCgpQYXJhIHV0aWxpemFyIHVuIHBhcXVldGUsIGxvIHByaW1lcm8gcXVlIHRlbmVtb3MgcXVlIGhhY2VyIGVzIGluc3RhbGFybG9zLiBFc28gbG8gaGFjZW1vcyBjb24gbGEgZnVuY2nDs24gYGluc3RhbGwucGFja2FnZXMoKWAgeSBkZW50cm8gZGVsIHBhcsOpbnRlc2lzIHRlbmVtb3MgcXVlIHBvbmVyIGVsIG5vbWJyZSBkZWwgcGFxdWV0ZS4gKipUZW5nYW4gZW4gY3VlbnRhIHF1ZToqKgoKLSAgIFIgZXMgdW4gbGVuZ3VhamUgKmNhc2Ugc2Vuc2l0aXZlKiBvIHNlYSBxdWUgaGF5IHF1ZSBwcmVzdGFyIGF0ZW5jacOzbiBhIG1hecO6c2N1bGFzIHkgbWluw7pzY3VsYXMuCgotICAgUGFyYSBpbnN0YWxhciBsb3MgcGFxdWV0ZXMgaGF5IHF1ZSB1c2FyIGNvbWlsbGFzCgpFc3RlIGVzIHVuIHBhc28gcXVlIGhhY2Vtb3MgdW5hIHNvbGEgdmV6IHBvciBjb21wdXRhZG9yYS4KClBhcmEgY29ycmVyIGVsIGPDs2RpZ28gc2UgdGllbmVuIHF1ZSBwYXJhciBlbiBsYSBsw61uZWEgZGUgY8OzZGlnbyBxdWUgcXVpZXJlbiB1c2FyIHkgYXByZXRhciBsYXMgdGVjbGFzIGBDdHJsYCArIGBFbnRlcmAgbyBiaWVuIGVsIHRyacOhbmd1bG8gdmVyZGUgKGNvbW8gc2kgZnVlcmEgdW4gw61jb25vIGRlICoiUGxheSIqIOKWtu+4jykKCmBgYHtyIHBhcXVldGVzLCBldmFsID0gRkFMU0V9CiMgSW5zdGFsYXIgbG9zIHBhcXVldGVzIERhdGFFeHBsb3JlciwgZnVuTW9kZWxpbmcsIHkgdGlkeXZlcnNlCmluc3RhbGwucGFja2FnZXMoIkRhdGFFeHBsb3JlciIpCmluc3RhbGwucGFja2FnZXMoImZ1bk1vZGVsaW5nIikKaW5zdGFsbC5wYWNrYWdlcygidGlkeXZlcnNlIikKYGBgCgpFc3RvIGxvIHF1ZSBoYWNlIGVzIGluc3RhbGFyIHBhcXVldGVzIGRlc2RlIFtDUkFOXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy8pLCBxdWUgZXMgdW4gcmVwb3NpdG9yaW8gZG9uZGUgc2UgcHVibGljYW4gbG9zIHBhcXVldGVzLCBhc2VndXJhbmRvIHVuIGVzdMOhbmRhciBkZSBjYWxpZGFkIHkgZGUgZG9jdW1lbnRhY2nDs24gcXVlIGhhY2UgcXVlIHRyYWJhamFyIGNvbiBjdWFscXVpZXIgcGFxdWV0ZSBkZSBDUkFOIHNlYSBzZWd1cm8uCgpQYXJhIHVzYXIgbGFzIGZ1bmNpb25lcyBkZSBsb3MgcGFxdWV0ZXMgcXVlIGluc3RhbGFtb3MsIGFob3JhIHRlbmVtb3MgcXVlICoqY2FyZ2FybG9zLioqIEVzdG8gbG8gcXVlIGhhY2UgZXMgZGUgYWxndW5hIG1hbmVyYSAqImFjdGl2YXIiKiBlbCBwYXF1ZXRlIHkgcXVlIHBvZGFtb3MgZW1wZXphciBzdXMgZnVuY2lvbmVzLgoKUGFyYSBjYXJnYXIgdW4gcGFxdWV0ZSB0ZW5lbW9zIHF1ZSB1c2FyIGxhIGZ1bmNpw7NuIGBsaWJyYXJ5KClgLiBSZWN1ZXJkZW4gcHJlc3RhciBhdGVuY2nDs24gYSBsYXMgbWF5w7pzY3VsYXMgeSBtaW7DunNjdWxhcy4gQWhvcmEgbm8gc29uIG5lY2VzYXJpYXMgbGFzIGNvbWlsbGFzLgoKVW5hIGRlIGxhcyBmb3JtYXMgZW4gbGFzIHF1ZSBub3MgZGFtb3MgY3VlbnRhIHF1ZSBlbCBwYXF1ZXRlIGVzdMOhIGluc3RhbGFkbyBlcyBjdWFuZG8gZW1wZXphbW9zIGEgZXNjcmliaXIgc3Ugbm9tYnJlIHkgbm9zIGFwYXJlY2UgZWwgbm9tYnJlIGRlbCBwYXF1ZXRlIHBhcmEgYXV0b2NvbXBsZXRhci4gRXN0YSBlcyB1bmEgZGUgbGFzIHZlbnRhamFzIGRlIHRyYWJhamFyIGVuIFJTdHVkaW8uCgpgYGB7ciBjYXJnYS1wYXF1ZXRlc30KIyBDYXJnYXIgbG9zIHBhcXVldGVzIERhdGFFeHBsb3JlciwgZnVuTW9kZWxpbmcsIHkgdGlkeXZlcnNlCmxpYnJhcnkoRGF0YUV4cGxvcmVyKQpsaWJyYXJ5KGZ1bk1vZGVsaW5nKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKCmBgYAoKIyMgTG9zIGRhdG9zCgpWYW1vcyBhIHRyYWJhamFyIGNvbiBkb3MgdmVyc2lvbmVzIGRlIGxhIFtFbmN1ZXN0YSBLSVdJIGRlIFN1ZWxkb3MgZGUgUkhdKGh0dHBzOi8vcnB1YnMuY29tL0RhdGE0SFIvZW5jdWVzdGEta2l3aS0yMDIwKSBxdWUgaGljaW1vcyBlbCBhw7FvIHBhc2FkbyBkZXNkZSBSNEhSLgoKLSAgIGBraXdpX2FyLlJEU2A6IFVuYSB2ZXJzacOzbiBsaW1waWEgeSBvcmdhbml6YWRhIGRlIGxhIGVuY3Vlc3RhLiBFbCBmb3JtYXRvIGAuUkRTYCBlcyBwcm9waW8gZGUgUiB5IG1hdGllbmUgYWxndW5hcyBjYXJhY3RlcsOtc3RpY2FzIGRlIGxvcyBkYXRvcy4gRXN0w6FuIGZpbHRyYWRhcyDDum5pY2FtZW50ZSBsYXMgcmVzcHVlc3RhcyBkZSBBcmdlbnRpbmEuCgotICAgYGVuY3Vlc3RhX2NydWRhLmNzdmA6IExhIHZlcnNpw7NuIGNydWRhIGRlIGxhIGVuY3Vlc3RhIGNvbiB0b2RhcyBsYXMgcmVzcHVlc3Rhcy4KClBhcmEgY2FyZ2FyIGxvcyBhcmNoaXZvcywgdGVuZW1vcyBxdWUgcHJlc3RhciBhdGVuY2nDs246CgotICAgTGEgdWJpY2FjacOzbiBkZWwgYXJjaGl2by4gRW4gbnVlc3RybyBjYXNvIGVzdMOhbiBlbiB1bmEgY2FycGV0YSBsbGFtYWRhIGBkYXRvc2AuCgotICAgUmVjdWVyZGVuIHF1ZSBSIGVzIGNhc2Ugc2Vuc2l0aXZlLCBhdGVuY2nDs24gYSBtYXnDunNjdWxhcyB5IG1pbsO6c2N1bGFzLgoKLSAgIExhIHViaWNhY2nDs24sIGVsIG5vbWJyZSB5IGxhIGV4dGVuc2nDs24gZGVsIGFyY2hpdm8gdGllbmVuIHF1ZSBlc3RhciBlbnRyZSBjb21pbGxhcy4KCkNhcmd1ZW1vcyBsb3MgZGF0b3MuIFBhcmEgY2FyZ2FyIGVsIGFyY2hpdm8gYC5SRFNgIHZhbW9zIGEgdXNhciBsYSBmdW5jacOzbiBkZSBSIGJhc2UgYHJlYWRSRFMoKWAuIFBhcmEgZWwgYXJjaGl2byBgLmNzdmAgdmFtb3MgYSB1c2FyIGxhIGZ1bmNpw7NuIGByZWFkLmNzdigpYCB0YW1iacOpbiBkZSBSIGJhc2UuIFVzYW1vcyBlc2EgZnVuY2nDs24gcG9ycXVlIGxhcyBjb2x1bW5hcyBlc3TDoW4gc2VwYXJhZGFzIHBvciB1biBwdW50byB5IGNvbWEgKGA7YCkuCgpgYGB7ciBkYXRvc30KIyBDYXJnYXIgbGEgdmVyc2nDs24gbGltcGlhIGRlIGxhIGVuY3Vlc3RhCmtpd2kgPC0gcmVhZFJEUygiZGF0b3Mva2l3aV9hci5SRFMiKQoKIyBDYXJnYXIgZWwgYXJjaGl2byBjcnVkbwpjcnVkbyA8LSByZWFkLmNzdigiZGF0b3MvZW5jdWVzdGFfY3J1ZGEuY3N2IiwgZmlsZUVuY29kaW5nID0gImxhdGluMSIsIHNlcCA9ICI7IikKYGBgCgo+IFJlY3VlcmRlbjoKPgo+IExhIGluc3RhbGFjacOzbiBkZSBsb3MgcGFxdWV0ZXMgc2UgaGFjZSAqKnVuYSBzb2xhIHZleioqIHBvciBjb21wdXRhZG9yYS4KPgo+IExhIGNhcmdhIGRlIGxvcyBwYXF1ZXRlc2Ugc2UgaGFjZSAqKmNhZGEgdmV6IHF1ZSBzZSBhYnJlKiogdW4gc2NyaXB0LgoKIyBFeHBsb3JhY2nDs24gZGUgZGF0b3MgY29uIFIgYmFzZQoKUiBiYXNlIGNvbnRpZW5lIGFsZ3VuYXMgZnVuY2lvbmVzIG11eSDDunRpbGVzIHBhcmEgdW5hIHByaW1lcmEgZXhwbG9yYWNpw7NuIGRlIGRhdG9zLiBWYW1vcyBhIHByYWN0aWNhciBjb24gbGEgdGFibGEgYGtpd2lgLgoKVW5hIGRlIGVzYXMgZnVuY2lvbmVzIGVzIGBWaWV3KClgLiBFbiBSU3R1ZGlvIGFicmUgdW5hIHBlc3Rhw7FhIG1vc3RyYW5kbyBsYSB0YWJsYSBkZSBkYXRvcy4KCmBgYHtyIGVkYS1iYXNlMSwgZXZhbD1GQUxTRX0KVmlldyhraXdpKQpgYGAKCk5vcyBoYWNlbW9zIGxvcyBjYW5jaGVyb3MgcHJvZ3JhbWFuZG8gZW4gUiBwZXJvIHNpZW1wcmUgbWlyYW1vcyB1bmEgdGFibGEg8J+YnC4KClVuYSBmdW5jacOzbiBxdWUgbm9zIHBlcm1pdGUgdmVyIGxhICplc3RydWN0dXJhKiBkZWwgZGF0YXNldCBlcyBgc3RyKClgLgoKYGBge3IgZWRhLWJhc2UyfQojIFVzYXIgbGEgZnVuY2nDs24gc3RyKCkgY29uIGVsIGRhdGEgZnJhbWUga2l3aQpzdHIoa2l3aSkKYGBgCgpMYSBmdW5jacOzbiBgc3RyKClgIG5vcyBicmluZGEgZXN0YSBpbmZvcm1hY2nDs246CgotICAgUXXDqSB0aXBvIGRlIG9iamV0byBlczogRW4gZXN0ZSBjYXNvIGVzIHVuIGB0aWJibGVgIHF1ZSBlcyB1biB0aXBvIGRlIGRhdGEgZnJhbWUgcHJvcGlvIGRlIGB0aWR5dmVyc2VgLgoKLSAgIFN1IHRhbWHDsW86IGZpbGFzIHkgY29sdW1uYXMuIMK/U2UgYW5pbWFuIGEgZGVjaXIgY3XDoW50YXMgZmlsYXMgeSBjdcOhbnRhcyBjb2x1bW5hcyB0aWVuZSBlc3RhIHRhYmxhPwoKLSAgIEVsIG5vbWJyZSBkZSBjYWRhIGZpbGE6OiBEZXNwdcOpcyBkZWwgc2lnbm8gYCRgLgoKLSAgIFF1w6kgdGlwbyBkZSB2YXJpYWJsZSBlczogYGNocmAsIGBudW1gLCBgaW50YCwgbyBgRmFjdG9yYC4gTm8gdGVuZW1vcyBlbiBlc3RlIGRhdGFzZXQsIHBlcm8gdGFtYmnDqW4gZXhpc3RlbiBsYXMgdmFyaWFibGVzIGBsb2dpY2FsYCBvIGBsZ2xgLgoKLSAgIFVuYSBtdWVzdHJhIGRlIGxvcyBwcmltZXJvcyByZWdpc3Ryb3MuCgpFbiBlbCBwYXF1ZXRlIGB0aWR5dmVyc2VgIGhheSB1bmEgZnVuY2nDs24gc2ltaWxhciBxdWUgc2UgbGxhbWEgYGdsaW1wc2UoKWAuIFByb2JlbW9zIGxhIGZ1bmNpw7NuIHkgdmVhbW9zIHF1w6kgZGlmZXJlbmNpYXMgaGF5LgoKYGBge3IgZWRhLWJhc2UzfQojIFVzYXIgbGEgZnVuY2nDs24gZ2xpbXBzZSgpIGNvbiBlbCBkYXRhIGZyYW1lIGtpd2kKZ2xpbXBzZShraXdpKQpgYGAKCkFwb3J0YSBpbmZvcm1hY2nDs24gc2ltaWxhciwgZXhjZXB0byBwb3IgZWwgdGlwbyBkZSBvYmpldG8sIHBlcm8gYGdsaW1wc2UoKWAgdGllbmUgdW5hIHNhbGlkYSB1biBwb2NvIG3DoXMgcHJvbGlqYS4gQWRlbcOhcyBhIGxhcyB2YXJpYWJsZXMgYG51bWAgbGFzIGlkZW50aWZpY2EgY29tbyBgZGJsYC4KCkFob3JhIHByb2JlbW9zIGxhIGZ1bmNpw7NuIGBzdW1tYXJ5KClgLiBDb21vIGVsIGRhdGEgZnJhbWUgdGllbmUgbXVjaGFzIGNvbHVtbmFzIHZhbW9zIGEgc2VsZWNjaW9uYXIgc8OzbG8gNCBjb2x1bW5hcy4gVW5hIHBvciBjYWRhIHRpcG8gZGUgdmFyaWFibGUuCgpgYGB7ciBlZGEtYmFzZTR9CiMgQ3JlbyB1bmEgdGFibGEgY29uIHPDs2xvIDQgdmFyaWFibGVzCmtpd2lfMiA8LSBraXdpICU+JSAKICBzZWxlY3QoZ2VuZXJvLCBjb250YWN0b3NfbGlua2VkaW4sIGlkLCBwdWVzdG8pICMgRWxpam8gbGFzIGNvbHVtbmFzIHF1ZSBkZXNlbwoKIyBDb3JyZXIgbGEgZnVuY2nDs24gc3VtbWFyeSgpIGNvbiBlbCBkYXRhIGZyYW1lIGtpd2lfMgpzdW1tYXJ5KGtpd2lfMikKYGBgCgpDb24gbGFzIHZhcmlhYmxlcyBkZSB0aXBvIGBjaGFyYWN0ZXJgIGxvIMO6bmljbyBxdWUgbm9zIGluZGljYSBlcyBsYSBjYW50aWRhZCBkZSBmaWxhcyBxdWUgdGllbmUuIEVuIGNhbWJpbywgY29uIGxhcyB2YXJpYWJsZXMgZGUgdGlwbyBgRmFjdG9yYCBjb21vIGVzIGVuIGVzdGUgY2FzbyBsYSB2YXJpYWJsZSBgcHVlc3RvYCwgbm9zIG11ZXN0cmEgbGEgY2FudGlkYWQgZGUgY2Fzb3MgcGFyYSBjYWRhIHJvbCBkZW50cm8gZGUgbGEgY29sdW1uYS4KCkVuIGNhbWJpbywgY29uIGxhcyB2YXJpYWJsZXMgbnVtw6lyaWNhcywgbm8gaW1wb3J0YSBzaSBzb24gYG51bWVyaWNgIG8gYGludGVnZXJgLCBvYnRlbmVtb3MgbGEgc2lndWllbnRlIGluZm9ybWFjacOzbjoKCi0gICBgTWluLmA6IGVsIHZhbG9yIG3DrW5pbW8KCi0gICBgMXN0IFF1LmA6IGVsIHZhbG9yIGRlbCBwcmltZXIgY3VhcnRpbAoKLSAgIGBNZWRpYW5gOiBsYSBtZWRpYW5hCgotICAgYE1lYW5gOiBlbCBwcm9tZWRpbwoKLSAgIGAzcmQgUXUuYDogZWwgdmFsb3IgZGVsIHRlcmNlciBjdWFydGlsCgotICAgYE1heGA6IGVsIHZhbG9yIG3DoXhpbW8KCi0gICBgTkEnc2A6IGxhIGNhbnRpZGFkIGRlIGRhdG9zIG51bG9zIChjZWxkYXMgdmFjw61hcyBlbiBlc2EgY29sdW1uYSkuCgpMYSDDumx0aW1hIGZ1bmNpw7NuIHF1ZSB2ZXJlbW9zIGFob3JhIGVzIGBuYW1lcygpYCwgcXVlIGxhIGluZm9ybWFjacOzbiBxdWUgYXBvcnRhIGVzIPCfpYHwn6WB8J+lgSBsb3Mgbm9tYnJlcyBkZSBsYXMgY29sdW1uYXMKCmBgYHtyIGVkYS1iYXNlNX0KIyBVc2Vtb3MgbGEgZnVuY2nDs24gbmFtZXMoKSBjb24ga2l3aQpuYW1lcyhraXdpKQojIFVzZW1vcyBsYSBmdW5jacOzbiBuYW1lcygpIGNvbiBjcnVkbyA9UApuYW1lcyhjcnVkbykKYGBgCgrwn5ixLCBubz8gRW4gbGFzIHBhbGFicmFzIGRlIE1vcmZlbyBlbiBNYXRyaXg6CgohW10oaHR0cHM6Ly9tZW1lZ2VuZXJhdG9yLm5ldC9pbWcvaW5zdGFuY2VzLzczMDk3NTg4LmpwZyl7d2lkdGg9IjMwMCJ9CgpFbiBsYSBpbnRyb2R1Y2Npw7NuIGRlbCBtZWV0dXAgbGVzIGRlY8OtYW1vcyBxdWUgZWwgYW7DoWxpc2lzIGV4cGxvcmF0b3JpbyBkZSBkYXRvcyBlcyB0YW1iacOpbiB2aXN1YWwuIFIgYmFzZSB0aWVuZSBsYSBmdW5jacOzbiBgcGxvdGAgcXVlIHBlcm1pdGUgaGFjZXIgZ3LDoWZpY29zLiBQcm9iZW1vcyBwcmltZXJvIGhhY2llbmRvIHVuIGdyw6FmaWNvIHBvciB2YXJpYWJsZS4gTGEgbMOzZ2ljYSBkZSBsYSBzaW50YXhpcyBlcyBsYSBzaWd1aWVudGU6IGBub21icmVfZGF0YWZyYW1lJG5vbWJyZV92YXJpYWJsZWAuIFBvciBlamVtcGxvLCBwYXJhIGdyYWZpY2FyIGxhIHZhcmlhYmxlIGBlZGFkYCB0ZW5nbyBxdWUgZXNjcmliaXI6CgpgYGB7ciBlZGEtYmFzZTZ9CnBsb3Qoa2l3aSRlZGFkKQoKIyBQcnVlYmVuIGdyYWZpY2FyIGxhIHZhcmlhYmxlIHB1ZXN0bwpwbG90KGtpd2kkcHVlc3RvKQojIFBydWViZW4gZ3JhZmljYXIgbGEgdmFyaWFibGUgZ2VuZXJvCmtpd2kkZ2VuZXJvIDwtIGFzLmZhY3RvcihraXdpJGdlbmVybykKCnBsb3Qoa2l3aSRnZW5lcm8pCgpgYGAKCkFsIGZpbmFsIGRlbCBlbmN1ZW50cm8gdmVyZW1vcyBvdHJhIG1hbmVyYSBkZSBoYWNlciBncsOhZmljb3MuCgojIERhdGFFeHBsb3JlcgoKIVtdKGh0dHBzOi8vZ2l0aHViLmNvbS9ib3h1YW5jdWkvRGF0YUV4cGxvcmVyL3Jhdy9tYXN0ZXIvbWFuL2ZpZ3VyZXMvbG9nby5wbmcpe3dpZHRoPSIxNDIifQoKRWwgcGFxdWV0ZSBgRGF0YUV4cGxvcmVyYCBkZXNhcnJvbGxhZG8gcG9yIFtCb3h1YW4gQ3VpXShodHRwczovL3d3dy5saW5rZWRpbi5jb20vaW4vYm94dWFuY3VpLykgdGllbmUgdHJlcyBvYmpldGl2b3MgcHJpbmNpcGFsZXM6CgoxLiAgUmVhbGl6YXIgYW7DoWxpc2lzIGV4cGxvcmF0b3Jpb3MgZGUgZGF0b3MuCgoyLiAgRGVzYXJyb2xsYXIgdGFyZWFzIGRlIGZlYXR1cmUgZW5naW5lZXJpbmcKCjMuICBHZW5lcmFyIHJlcG9ydGVzLgoKSG95IHNvbG8gdmVyZW1vcyBsb3MgcHVudG9zIDEgeSAzLgoKUGFyYSBlbCBhbsOhbGlzaXMgZXhwbG9yYXRvcmlvLCB1bmEgZGUgbGFzIHByaW1lcmFzIGZ1bmNpb25lcyBxdWUgdmVtb3MgZW4gbGEgW2RvY3VtZW50YWNpw7NuIGRlbCBwYXF1ZXRlXShodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvRGF0YUV4cGxvcmVyL3ZpZ25ldHRlcy9kYXRhZXhwbG9yZXItaW50cm8uaHRtbCNleHBsb3JhdG9yeS1kYXRhLWFuYWx5c2lzKSBlcyBsYSBmdW5jacOzbiBgaW50cm9kdWNlKClgLiBVc2FuZG8gZWwgc2lnbm8gZGUgaW50ZXJyb2dhY2nDs24gZGVsYW50ZSBkZSBsYSBmdW5jacOzbiBwb2RlbW9zIGFicmlyIGxhIGF5dWRhIGRlIGxhIGZ1bmNpw7NuLgoKYGBge3IgZGUtMX0KIyBSZXZpc2FyIGxhIGF5dWRhIGRlIGxhIGZ1bmNpw7NuIGludHJvZHVjZSgpCj9pbnRyb2R1Y2UoKQoKIyBVc2FyIGxhIGZ1bmNpw7NuIGNvbiBsYSB0YWJsYSBraXdpCmludHJvZHVjZShraXdpKQpgYGAKCkVzdGEgZnVuY2nDs24gbm9zIHBlcm1pdGUgdmVyOgoKLSAgIGByb3dzYDogbGEgY2FudGlkYWQgZGUgZmlsYXMKCi0gICBgY29sdW1uc2A6IGxhIGNhbnRpZGFkIGRlIHZhcmlhYmxlcwoKLSAgIGBkaXNjcmV0ZV9jb2x1bW5zYDogbGEgY2FudGlkYWQgZGUgdmFyaWFibGVzIGRpc2NyZXRhcwoKLSAgIGBjb250aW51b3NfY29sdW1uc2A6IGVsIG7Dum1lcm8gZGUgW3ZhcmlhYmxlcyBjb250aW51YXNdKGh0dHBzOi8vYm9va2Rvd24ub3JnL2FxdWludGVsYS9FQkUvdmFyaWFibGVzLWRpc2NyZXRhcy15LWNvbnRpbnVhcy5odG1sKQoKLSAgIGBhbGxfbWlzc2luZ19jb2x1bW5zYDogY29sdW1uYXMgcXVlIHPDs2xvIGNvbnRpZW5lbiB2YWxvcmVzIG51bG9zCgotICAgYHRvdGFsX21pc3NpbmdfdmFsdWVzYDogY2FudGlkYWQgZGUgY2VsZGFzIGNvbiB2YWxvcmVzIG51bG9zCgotICAgYGNvbXBsZXRlX3Jvd3NgOiB0b3RhbCBkZSBmaWxhcyBxdWUgbm8gdGllbmVuIG5pbmfDum4gZGF0byBmYWx0YW50ZQoKLSAgIGB0b3RhbF9vYnNlcnZhdGlvbnNgOiBFcyBsYSBjYW50aWRhZCB0b3RhbCBkZSBjZWxkYXMKCi0gICBgbWVtb3J5X3VzYWdlYDogZXMgZWwgdGFtYcOxbyBlc3RpbWFkbyBkZWwgZGF0YSBmcmFtZSBlbiBieXRlcy4KClRvZGEgZXN0YSBpbmZvcm1hY2nDs24gdGFtYmnDqW4gbGEgcG9kZW1vcyB2ZXIgdmlzdWFsbWVudGUgY29uIGxhIGZ1bmNpw7NuIGBwbG90X2ludHJvKClgLiBZIGNvbiBsYSBmdW5jacOzbiBgcGxvdF9taXNzaW5nKClgIHBvZGVtb3MgdmVyIGVsIHBvcmNlbnRhamUgZGUgZGF0b3MgbnVsb3MgcG9yIGNvbHVtbmEsIHkgbGEgbWlzbWEgZnVuY2nDs24gZXN0YWJsZWNlIHNpIGVzIHByb2JsZW3DoXRpY28gbyBuby4KCmBgYHtyIGRlLTJ9CiMgVXNhciBsYSBmdW5jacOzbiBwbG90X2ludHJvKCkKcGxvdF9pbnRybyhraXdpKQoKIyBVc2FyIGxhIGZ1bmNpw7NuIHBsb3RfbWlzc2luZygpCnBsb3RfbWlzc2luZyhraXdpKQoKIyBVc2FyIGxhIGZ1bmNpw7NuIHBsb3RfbWlzc2luZygpIG1vZGlmaWNhbmRvIGVsIHBhcsOhbWV0cm8gbWlzc2luZ19vbmx5CnBsb3RfbWlzc2luZyhraXdpLCBtaXNzaW5nX29ubHkgPSBUUlVFKQoKYGBgCgpPdHJhIGZ1bmNpw7NuIGludGVyZXNhbnRlIGVzIGBwbG90X2JhcigpYCBxdWUgaGFjZSBncsOhZmljb3MgZGUgYmFycmEgZGUgdG9kYXMgbGFzIHZhcmlhYmxlcyBkaXNjcmV0YXMuCgpgYGB7ciBkZS0zfQojIFVzYXIgbGEgZnVuY2nDs24gcGxvdF9iYXIoKSBjb24gZWwgZGF0YSBmcmFtZSBraXdpCnBsb3RfYmFyKGtpd2ksIG1heGNhdCA9IDIwKQpgYGAKCkVsIHByaW1lciBpbnRlbnRvIG5vIHNhbGUgZGVsIHRvZG8gYmllbiBwb3JxdWUgaGF5IHJlc3B1ZXN0YXMgY29uIG11Y2hvIHRleHRvLiBQcm9iZW1vcyBtb2RpZmljYW5kbyBlbCBwYXLDoW1ldHJvIGBtYXhjYXQgPSAyMGAuCgpPdHJvIHBhcsOhbWV0cm8gaW50ZXJlc2FudGUgZGVudHJvIGRlIGBwbG90X2JhcigpYCBlcyBgYnlgIHF1ZSBsZSBpbmRpY2EgYSBsYSBmdW5jacOzbiBxdWUgcmVhbGljZSBsb3MgZ3LDoWZpY29zIHBlcm8gZXhwbGljYWRvcyBwb3IgYWxndW5hIHZhcmlhYmxlIGRlIGludGVyw6lzLiBMbyBxdWUgaGFjZSBlcyBnZW5lcmFyIGdyw6FmaWNvcyBkZSBiYXJyYXMgYXBpbGFkb3MgYWwgMTAwJSBwYXJhIHZlciBjw7NtbyBzZSBkaXN0cmlidXllbiBsYXMgcHJvcG9yY2lvbmVzIGRlIGxhcyB2YXJpYWJsZXMuCgpgYGB7ciBkZS00fQojIFJlcGl0YW1vcyBlbCBncsOhZmljbyBhbnRlcmlvciBwZXJvIGNvbiBieSA9ICJnZW5lcm8iCnBsb3RfYmFyKGtpd2ksIG1heGNhdCA9IDIwLCBieSA9ICJnZW5lcm8iKQoKCmBgYAoKQWxnbyBxdWUgc2llbXByZSBkZWJlbW9zIGhhY2VyIGNvbiBsYXMgdmFyaWFibGVzIG51bcOpcmljYXMgZXMgYW5hbGl6YXIgc3UgZGlzdHJpYnVjacOzbi4gUGFyYSBlc28gcG9kZW1vcyB1c2FyIGxhIGZ1bmNpw7NuIGBwbG90X2hpc3RvZ3JhbSgpYC4KCmBgYHtyIGRlLTV9CiMgVXNhciBsYSBmdW5jacOzbiBwbG90X2hpc3RvZ3JhbSgpIHBhcmEgYW5hbGl6YXIgbGFzIHZhcmlhYmxlcyBudW3DqXJpY2FzCnBsb3RfaGlzdG9ncmFtKGtpd2kpCmBgYAoKRXN0ZSBhbsOhbGlzaXMgeWEgbm9zIHBlcm1pdGUgZGV0ZWN0YXIgYWxndW5hcyBhbm9tYWzDrWFzLCBwb3IgZWplbXBsbyBlbiBsYXMgdmFyaWFibGVzIGBhbmlvc19lbl9lbXByZXNhYCB5IGBhbmlvc19leHBlcmllbmNpYWAgdGVuZW1vcyB2YXJpb3MgdmFsb3JlcyBpbXBvc2libGVzLiDCv1B1ZWRlbiB2ZXIgY3XDoWxlcyBzb24/IFVuIHRlbWEgYXBhcnRlIGVzIGVsIGBpZGAgcXVlIHNpIGJpZW4gZXN0w6EgcmVwcmVzZW50YWRvIHBvciB1biBuw7ptZXJvIG5vIGRlYmVyw61hbW9zIHVzYXJsbyBwYXJhIGxvcyBhbsOhbGlzaXMuIFVuYSBmb3JtYSBkZSByZXNvbHZlcmxvIGVzIHVzYW5kbyBsYSBmdW5jacOzbiBgZHJvcF9jb2x1bW4oKWAgZGVsIHBhcXVldGUgYERhdGFFeHBsb3JlcmAgbyBgc2VsZWN0KClgIGRlIGB0aWR5dmVyc2VgLgoKYGBge3IgZGUtNn0KIyBPcGNpw7NuIDE6IGRyb3BfY29sdW1uKCkKa2l3aV8zIDwtIGRyb3BfY29sdW1ucyhraXdpLCAKICAgICAgICAgICAgIGluZCA9ICJpZCIpCgojIE9wY2nDs24gMjogc2VsZWN0KCkKa2l3aV8zYiA8LSBraXdpICU+JSAKICBzZWxlY3QoLWlkKQpgYGAKCkFsZ28gcXVlIHRhbWJpw6luIHBvZGVtb3MgYXByZWNpYXIgZXMgcXVlIGxhcyB2YXJpYWJsZXMgYHNhdGlzZmFjY2lvbmAgeSBsYSB2YXJpYWJsZSBgdmFsb3JhY2lvbl9nZXN0aW9uX2VtcHJlc2FgIHNpIGJpZW4gZXN0w6FuIHJlcHJlc2VudGFkYXMgcG9yIG7Dum1lcm9zLCBlbiByZWFsaWRhZCBzb24gdmFyaWFibGVzICpvcmRpbmFsZXMqIHkgcG9yIGxvIHRhbnRvIGRlYmVyw61hbW9zIGNhbWJpYXJsYXMgYSB2YXJpYWJsZXMgZGUgdGlwbyBmYWN0b3IuIFBhcmEgZXNvIHVzYXJlbW9zIGxhIGZ1bmNpw7NuIGB1cGRhdGVfY29sdW1ucygpYC4KCmBgYHtyIGRlLTd9CiMgU29icmVzY3JpYmlyIHVuIG9iamV0byB1c2FuZG8gbGEgZnVuY2nDs24gdXBkYXRlX2NvbHVtbnMoKQpraXdpXzQgPC0gdXBkYXRlX2NvbHVtbnMoa2l3aV8zLCAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgZGF0YSBmcmFtZQogICAgICAgICAgICAgICAgICAgICAgICAgYygic2F0aXNmYWNjaW9uIiwgInZhbG9yYWNpb25fZ2VzdGlvbl9lbXByZXNhIiksICMgY29sdW1uYXMgYSBtb2RpZmljYXIKICAgICAgICAgICAgICAgICAgICAgICAgIHdoYXQgPSBhcy5mYWN0b3IpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIGEgcXXDqSB0aXBvCgojIFByb2JhciBsb3MgY2FtYmlvcyAKc3VtbWFyeShraXdpXzQkc2F0aXNmYWNjaW9uKQpgYGAKCkVzdGUgcGFxdWV0ZSB0aWVuZSB2YXJpYXMgb3BjaW9uZXMgZGUgZ3LDoWZpY29zLiBQcnVlYmVuIGNyZWFyIHVuIG51ZXZvIGRhdGEgZnJhbWUgY29uIDQgdmFyaWFibGVzIG51bcOpcmljYXMgeSB1bmEgdmFyaWFibGUgY2F0ZWfDs3JpY2EgdXNhbmRvIGxhIGZ1bmNpw7NuIGBzZWxlY3QoKWAgeSBwcm9iYXIgbGFzIHNpZ3VpZW50ZXMgZnVuY2lvbmVzLgoKUGFyYSBwcm9iYXIgbGFzIGZ1bmNpb25lcyBwdWVkZW4gdXNhciBhbGd1bm9zIGRlIGxvcyBibG9xdWVzIGRlIGPDs2RpZ28gYW50ZXJpb3JlcyBvIGJpZW4gYWJyaXIgdW5vIG51ZXZvIGFwcmV0YW5kbyBsYXMgdGVjbGFzIGBDdHJsYCArIGBBbHRgICsgYElgLiBQcnVlYmVuIG1vZGlmaWNhciBsb3MgZ3LDoWZpY29zIG1vZGlmaWNhbmRvIGVsIHBhcsOhbWV0cm8gYGJ5YCBjb24gbGEgdmFyaWFibGUgY2F0ZWfDs3JpY2EgcXVlIGhheWFuIGVsZWdpZG8uCgotICAgYHBsb3RfcXEoKWAKCi0gICBgcGxvdF9zY2F0dGVycGxvdCgpYAoKLSAgIGBwbG90X2JveHBsb3QoKWAKCkhheSB2YXJpYXMgZnVuY2lvbmVzIG11eSBpbnRlcmVzYW50ZXMgcGFyYSBkZXNjdWJyaXIgcXVlIGVzdMOhbiBleHBsaWNhZGFzIGVuIGxhIFtww6FnaW5hIGRlbCBwYXF1ZXRlLl0oaHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL0RhdGFFeHBsb3Jlci92aWduZXR0ZXMvZGF0YWV4cGxvcmVyLWludHJvLmh0bWwjZXhwbG9yYXRvcnktZGF0YS1hbmFseXNpcykKCkxhIMO6bHRpbWEgZnVuY2nDs24gcXVlIHZhbW9zIGEgdmVyIGRlIGVzdGUgcGFxdWV0ZSBzZSBsbGFtYSBgY3JlYXRlX3JlcG9ydCgpYCB5IHByZXDDoXJlbnNlIHBhcmEg8J+kry4KCmBgYHtyIGRlLTgsIGV2YWw9RkFMU0V9CiMgVXNhciBsYSBmdW5jacOzbiBjcmVhdGVfcmVwb3J0KCkKY3JlYXRlX3JlcG9ydChraXdpKQpgYGAKClBydWViZW4gY3JlYXIgdW4gZGF0YSBmcmFtZSBjb24gNSB2YXJpYWJsZXMuIFVuYSBkZSBlbGxhcyB0aWVuZSBxdWUgc2VyIGBnZW5lcm9gIGVsIHJlc3RvIHB1ZWRlbiBzZXIgZG9zIGNhdGVnw7NyaWNhcyB5IGRvcyBudW3DqXJpY2FzIHBvciBlamVtcGxvIHkgY29ycmFuIGxhIGZ1bmNpw7NuIGRlIGxhIHNpZ3VpZW50ZSBtYW5lcmE6IGBjcmVhdGVfcmVwb3J0KGtpd2lfNCwgeSA9ICJnZW5lcm8iKS5gCgojIGZ1bk1vZGVsaW5nCgohW10oaHR0cHM6Ly9jYW1vLmdpdGh1YnVzZXJjb250ZW50LmNvbS81MjQxMWMzYWIyMDc5MDEwZGE3MWI2NzQ5ZDQ1OTEyZWVlNzg5MDIwNzg5NWRmZjc4ZWJlOTFmMjM5ZTkxNTIxLzY4NzQ3NDcwNzMzYTJmMmY3MzMzMmU2MTZkNjE3YTZmNmU2MTc3NzMyZTYzNmY2ZDJmNjQ2MTc0NjE3MzYzNjk2NTZlNjM2NTY4NjU3MjZmNjU3MzJlNjM2ZjZkMmY2OTZkNjcyZjYyNmM2ZjY3MmY2Njc1NmU0ZDZmNjQ2NTZjNjk2ZTY3NWY2YzZmNjc2ZjVmNjg3MTJlNzA2ZTY3KXt3aWR0aD0iMTc2In0KCmBmdW5Nb2RlbGluZ2AgZXMgdW4gcGFxdWV0ZSBkZXNhcnJvbGxhZG8gcG9yIFtQYWJsbyBDYXNhc10oaHR0cHM6Ly93d3cubGlua2VkaW4uY29tL2luL3BjYXNhcy8pLCBhIHF1aWVuIHR1dmltb3MgZWwgcGxhY2VyIGRlIHRlbmVyIGVuIFI0SFIgZW4gbGEgW3Nlc2nDs24gOV0oaHR0cHM6Ly9kcml2ZS5nb29nbGUuY29tL2ZpbGUvZC8xUDh2Wk44amhsYXZWOTk1d2ppWEs1NGpGMGtkYzJHRnQvdmlldz91c3A9c2hhcmluZykuIFNpIHRvZGF2w61hIGVzdMOhbiBleHBsb3JhbmRvIHF1w6kgbGVuZ3VhamVzIGVzdHVkaWFyIHB1ZWRlbiBleHBsb3JhciBzdXMgY3Vyc29zIGdyYXR1aXRvcyBkZSBsYSBbRXNjdWVsYSBkZSBEYXRvcyBWaXZvc10oaHR0cHM6Ly9lc2N1ZWxhZGVkYXRvc3Zpdm9zLmFpLykuIFJlc3BlY3RvIGRlbCBwYXF1ZXRlLCBQYWJsbyBlc2NyaWJpw7MgdW4gbGlicm8gZXhwbGljYW5kbyBzdXMgZnVuY2lvbmVzIHkgbXVjaG8gbcOhcywgcXVlIHNlIGxsYW1hIFtMaWJybyBWaXZvIGRlIENpZW5jaWEgZGUgRGF0b3NdKGh0dHBzOi8vbGlicm92aXZvZGVjaWVuY2lhZGVkKS4KCkxhcyBzYWxpZGFzIGRlIGVzdGUgcGFxdWV0ZSBnZW5lcmFuIG11Y2hhIGluZm9ybWFjacOzbiBhc8OtIHF1ZSBwYXJhIGxvcyBmaW5lcyBwcsOhY3RpY29zIGRlIGVzdGUgZW5jdWVudHJvLCBzZWxlY2Npb25lbW9zIDcgdmFyaWFibGVzOgoKYGBge3IgZm0tMX0KIyBTZWxlY2Npb25lbW9zIDcgdmFyaWFibGVzCmtpd2lfNyA8LSBraXdpICU+JSAKICBzZWxlY3Qoc3VlbGRvX2JydXRvLCBwdWVzdG8sIHJ1YnJvLCBhbmlvc19leHBlcmllbmNpYSwgc2F0aXNmYWNjaW9uLCBkb3RhY2lvbiwgY29udGFjdG9zX2xpbmtlZGluKQpgYGAKCkxhIHByaW1lcmEgZnVuY2nDs24gcXVlIHZhbW9zIGEgZXhwbG9yYXIgc2UgbGxhbWEgYHN0YXR1cygpYC4gUHJvYmVtb3NsYSBjb24gbnVlc3RybyBudWV2byBkYXRhIGZyYW1lIHJlZHVjaWRvLgoKYGBge3IgZm0tMn0KIyBVc2FyIGxhIGZ1bmNpw7NuIHN0YXR1cygpCnN0YXR1cyhraXdpXzcpCmBgYAoKRXN0YSBmdW5jacOzbiBub3MgYXBvcnRhIGxhIHNpZ3VpZW50ZSBpbmZvcm1hY2nDs246CgotICAgTGEgY2FudGlkYWQgZGUgYDBgLCBgTkFgIHkgdmFsb3JlcyBpbmZpbml0b3M6IGBxX3plcm9zYCwgYHFfbmEsYCB5IGBxX2luZmAgcmVzcGVjdGl2YW1lbnRlIHkgZWwgcG9yY2VudGFqZSBxdWUgcmVwcmVzZW50YW4uCgotICAgUXVlIHRpcG8gZGUgdmFyaWFibGUgY29udGllbmUgY2FkYSBjb2x1bW5hOiBgdHlwZWAKCi0gICBMYSBjYW50aWRhZCBkZSB2YWxvcmVzIMO6bmljb3MgcXVlIGNvbnRpZW5lIGxhIHZhcmlhYmxlOiBgdW5pcXVlYC4KCkVzdGUgw7psdGltbyBkYXRvIGVzIGltcG9ydGFudGUgZXNwZWNpYWxtZW50ZSBlbiB2YXJpYWJsZXMgZGUgdGlwbyBgZmFjdG9yYCBvIGBjaGFyYWN0ZXJgIHBvcnF1ZSBub3MgZGEgdW5hIGlkZWEgZGUgbGEgKmNhcmRpbmFsaWRhZCogZGUgbGEgdmFyaWFibGUuIFBvciBlamVtcGxvIGxhIHZhcmlhYmxlIGBwdWVzdG9gIHRpZW5lIDYgdmFsb3JlcyDDum5pY29zICgqQWRtaW5pc3RyYXRpdm8sIEFuYWxpc3RhLCBIUkJQLCBSZXNwb25zYWJsZSwgSmVmZSwgR2VyZW50ZSopLCBzdSBjYXJkaW5hbGlkYWQgZXMgbXVjaG8gbWVub3IgcXVlIGVsIGRlIGxhIHZhcmlhYmxlIGBydWJyb2AgZG9uZGUgdGVuZW1vcyAyNyB2YWxvcmVzIMO6bmljb3MuCgpFc3RvIG5vcyBzaXJ2ZSBwYXJhIGlkZW50aWZpY2FyIHZhcmlhYmxlcyBxdWUgcG9kZW1vcyB1dGlsaXphciBwYXJhIGFncnVwYXIsIGxvIGN1YWwgdGFtYmnDqW4gcHVlZGUgaGFjZXJzZSBjb24gdmFyaWFibGVzIG51bcOpcmljYXMgY29tbyBgc2F0aXNmYWNjaW9uYCwgeWEgcXVlIGFsIHRlbmVyIHRhbiBwb2NvcyB2YWxvcmVzIMO6bmljb3MsIG5vcyBkYSB1bmEgaWRlYSBkZSBxdWUgcHVlZGUgc2VyIHVuYSB2YXJpYWJsZSBvcmRpbmFsIHF1ZSBwb2Ryw61hbW9zIGNvbnZlcnRpciBhIGBmYWN0b3JgIHBvciBlamVtcGxvLgoKT3RyYSBmdW5jacOzbiBtdXkgw7p0aWwgcGFyYSBhbmFsaXphciB2YXJpYWJsZXMgY2F0ZWfDs3JpY2FzIHUgb3JkaW5hbGVzIGVzIGxhIGZ1bmNpw7NuIGBmcmVxKClgLiBWZWFtb3MgcXVlIGhhY2U6CgpgYGB7ciBmbS0zfQojIFVzYXIgbGEgZnVuY2nDs24gZnJlcSgpIGVuIGtpd2lfNwpmcmVxKGtpd2lfNykKYGBgCgpFc3RhIGZ1bmNpw7NuIG5vcyBwcm92ZWUgdW5hIHRhYmxhIGRlIGZyZWN1ZW5jaWFzLCBlcyBkZWNpciwgbGEgY2FudGlkYWQgZGUgdmVjZXMgcXVlIGFwYXJlY2UgY2FkYSB2YWxvciAoZXMgZGVjaXIgc3UgZnJlY3VlbmNpYSksIGVsIHBvcmNlbnRhamUgcXVlIHJlcHJlc2VudGEsIHkgZWwgcG9yY2VudGFqZSBhY3VtdWxhZG8gZGUgY2FkYSB2YWxvciAoc2kgcXVpZXJlbiBjYW5jaGVyZWFyIGZyZW50ZSBhIHVuL2EgZXN0YWTDrXN0aWNvL2EgZXN0YSDDumx0aW1hIGNvbHVtbmEgZXN0byBzZSBsbGFtYSAqZnJlY3VlbmNpYSBhY3VtdWxhZGEgcmVsYXRpdmEqKS4KClBhcmEgbGFzIHZhcmlhYmxlcyBuw7ptw6lyaWNhcyB2YW1vcyBhIHZlciBkb3MgZnVuY2lvbmVzLiBMYSBwcmltZXJhIHNlIGxsYW1hIGBwbG90X251bSgpYCB5IGxhIHNpZ3VpZW50ZSBlcyBgcHJvZmlsaW5nX251bSgpYC4KCmBgYHtyIGZtLTR9CiMgQ29ycmVyIGxhIGZ1bmNpw7NuIHBsb3RfbnVtKCkgZW4ga2l3aV83CnBsb3RfbnVtKGtpd2lfNykKYGBgCgpFbiBlc3RvcyBncsOhZmljb3MgbG8gcXVlIHZlbW9zIGVzIGxhIGRpc3RyaWJ1Y2nDs24gZGUgbGFzIHZhcmlhYmxlcyBudW3DqXJpY2FzIGVuIGZvcm1hIGRlICoqaGlzdG9ncmFtYXMuKiogTyBhbCBtZW5vcyBlc2EgZXMgbGEgaW50ZW5jacOzbiwgcG9ycXVlIHBvciBsYSBwcmVzZW5jaWEgZGUgbG9zICoqb3V0bGllcnMqKiBubyBwb2RlbW9zIGFwcmVjaWFyIG11Y2hvIGxhIGRpc3RyaWJ1Y2nDs24gZGUgbG9zIGRhdG9zIGNvbiBlc3RlIHRpcG8gZGUgZ3LDoWZpY29zLgoKUHJvYmVtb3MgYWhvcmEgbGEgZnVuY2nDs24gYHByb2ZpbGluZ19udW0oKWAuCgpgYGB7ciBmbS01fQojIFVzYXIgbGEgZnVuY2nDs24gcHJvZmlsaW5nX251bSgpIGVuIGtpd2lfNwpwcm9maWxpbmdfbnVtKGtpd2lfNykKCmBgYAoKQWPDoSB0ZW5lbW9zIHVuIG1vbnTDs24gZGUgaW5mb3JtYWNpw7NuOgoKLSAgIExvIHByaW1lcm8gcXVlIHZlbW9zIGVzIGVsIHByb21lZGlvIChgbWVhbilgIHkgZWwgZGVzdsOtbyBlc3TDoW5kYXIgKGBzdGRfZGV2YCkuCgotICAgTHVlZ28gdmVtb3MgZWwgY29lZmljaWVudGUgZGUgdmFyaWFjacOzbiAoYHZhcmlhdGlvbl9jb2VmYCkgcXVlIGVzIGVsIHJlc3VsdGFkbyBkZSBkaXZpZGlyIGVsIGRlc3bDrW8gZXN0w6FuZGFyIHBvciBlbCBwcm9tZWRpby4KCi0gICBMdWVnbyB2ZW1vcyBlbCB2YWxvciBlbiBsb3MgcGVyY2VudGlsZXMgMSwgNSwgMjUsIDUwLCA3NSwgOTUsIDk5LgoKLSAgIEx1ZWdvIHZlbW9zIGVsIHNlc2dvIChgc2tld25lc3NgKSBxdWUgdGllbmUgcXVlIHZlciBjb24gbGEgc2ltZXRyw61hIGRlIGxhIGRpc3RyaWJ1Y2nDs24gZGUgbG9zIGRhdG9zIHkgZWwgYGt1cnRvc2lzYCBxdWUgYSBncm9zc28gbW9kbyBpbmRpY2EgbGEgcHJlc2VuY2lhIGRlIHZhbG9yZXMgZXh0cmVtb3MuCgotICAgRWwgcmFuZ28gaW50ZXJjdWFydGlsIChgaXFyYCkgcXVlIGVzIGxhIGRpZmVyZW5jaWEgZW50cmUgZWwgdGVyY2VyIGN1YXJ0aWwgKm1lbm9zKiBlbCBwcmltZXIgY3VhcnRpbC4gTyBsbyBxdWUgZXMgbG8gbWlzbW8sIGVsIHBlcmNlbnRpbCA3NSBtZW5vcyBlbCBwZXJjZW50aWwgMjUuCgotICAgRGVzcHXDqXMgdGVuZW1vcyBkb3MgcmFuZ29zLCBlbnRyZSBsb3MgcGVyY2VudGlsZXMgMSB5IDk5LCB5IGVudHJlIGxvcyBwZXJjZW50aWxlcyAxMCB5IDkwLgoKRXMgbXVjaGEgaW5mb3JtYWNpw7NuIGVzdGFkw61zdGljYSwgwr9ubz8gUGVybyB2ZWFtb3MgcGFyYSBxdcOpIG5vcyBwdWVkZSBzZXJ2aXIuIExvcyByZXN1bHRhZG9zIGRlIGBwcm9maWxpbmdfbnVtKClgIGxvcyBwb2RlbW9zIGd1YXJkYXIgZW4gdW5hIHRhYmxhIHkgdXNhcmxvIHBhcmEgbGltcGlhciBudWVzdHJvcyBkYXRvcy4KCmBgYHtyIGZtLTZ9CiMgQ3JlYXIgdW4gb2JqZXRvIGNvbiBlbCBhbsOhbGlzaXMgZGUgcHJvZmlsaW5nX251bSgpCnZhcl9udW0gPC0gcHJvZmlsaW5nX251bShraXdpXzcpCgojIFZlciBlbCBjb250ZW5pZG8gZGUgdmFyX251bQp2YXJfbnVtICAgIyBBcHJldGFyIEN0cmwgKyBFbnRlcgpgYGAKCkFob3JhIHRlbmVtb3MgdW5hIHRhYmxhLCB1biBkYXRhIGZyYW1lLCBkZSBsYSBjdWFsIHBvZGVtb3MgdXNhciBzdXMgY2VsZGFzLiBFbiBwYXJ0aWN1bGFyIGFob3JhIG5vcyBpbnRlcmVzYW4gZGUgbGEgZmlsYSBkZSBgc3VlbGRvX2JydXRvYCBsYXMgY2VsZGFzIGNvcnJlc3BvbmRpZW50ZXMgYSBsb3MgcGVyY2VudGlsZXMgNSB5IDk1IHBhcmEgdXRpbGl6YXIgZW4gdW4gZmlsdHJvLiBQYXJhIGxvZ3JhciBlc28gdGVuZW1vcyBxdWUgc2VndWlyIGxhIHNpZ3VpZW50ZSBsw7NnaWNhOiBgbm9tYnJlX2RhdGFmcmFtZVtmaWxhLGNvbHVtbmFdYC4KCmBgYHtyIGZtLTd9CiMgR3VhcmRhbW9zIGVsIHZhbG9yIGRlIGxvcyBwZXJjZW50aWxlcyA1IHkgOTUgZW4gdW4gb2JqZXRvIGNhZGEgdW5vLgpwMDUgPC0gdmFyX251bVsxLDZdICAgIyBFbCB2YWxvciBkZSBsYSBmaWxhIDEsIGNvbHVtbmEgNgpwOTUgPC0gdmFyX251bVsxLDEwXSAgIyBFbCB2YWxvciBkZSBsYSBmaWxhIDEsIGNvbHVtbmEgMTAKICAKIyBWZWFtb3MgZWwgY29udGVuaWRvIGRlIGFtYm9zIHZhbG9yZXMKcDA1CnA5NQpgYGAKCkFob3JhIHBvZGVtb3MgdXNhciBlc29zIHZhbG9yZXMgY29tbyAqKmZpbHRyb3MqKiBkZSBsYSB2YXJpYWJsZSBgc3VlbGRvX2JydXRvYC4KCmBgYHtyIGZtLTh9CiMgRmlsdHJhbW9zIGxhcyBmaWxhcyBxdWUgZXN0w6luIHBvciBlbmNpbWEgZGUgcDA1IHkgcG9yIGRlYmFqbyBkZSBwOTUKa2l3aV84IDwtIGtpd2lfNyAlPiUKICBmaWx0ZXIoYmV0d2VlbihzdWVsZG9fYnJ1dG8sICAgIyBDb2x1bW5hIGEgZmlsdHJhcgogICAgICAgICBwMDUsICAgICAgICAgICAgICAgICAgICAjIFVtYnJhbCBtw61uaW1vCiAgICAgICAgIHA5NSkpICAgICAgICAgICAgICAgICAgICAjIFVtYnJhbCBtw6F4aW1vCgojIFByb2JlbW9zIGxhIGZ1bmNpw7NuIHN1bW1hcnkoKSBlbiBraXdpXzgKc3VtbWFyeShraXdpXzgpCgojIENvcnJhbW9zIGxhIGZ1bmNpw7NuIHBsb3RfbnVtKCkgZW4ga2l3aV84CnBsb3RfbnVtKGtpd2lfOCkKYGBgCgojIEV4cGxvcmFuZG8gaW50dWljaW9uZXMKCkRpamltb3MgcXVlIGVsIEFuw6FsaXNpcyBFeHBsb3JhdG9yaW8gZGUgRGF0b3Mgc2lydmUgdGFtYmnDqW4gcGFyYSBleHBsb3JhciBpbnR1aWNpb25lcy4gVW5hIGRlIGVsbGFzIGVzdMOhIHJlbGFjaW9uYWRhIGNvbiBsYSBzYXRpc2ZhY2Npw7NuLCDCv2VzdMOhIHJlbGFjaW9uYWRhIGNvbiBlbCBzYWxhcmlvPyBWZWFtb3MgY8OzbW8gcG9kZW1vcyByZXNwb25kZXIgYSBlbGxvLgoKU2FiZW1vcyBxdWUgbGEgY29sdW1uYSBgc2F0aXNmYWNjaW9uYCB0aWVuZSBjYW1wb3MgbnVsb3MsIHBvciBsbyBxdWUgdGVuZW1vcyBxdWUgZmlsdHJhcmxvcy4KCmBgYHtyIGludHVpY2lvbi0xfQojIEZpbHRyYW1vcyB0b2RvIGxvIHF1ZSBubyBzZWEgdW4gTkEgZGUgbGEgdmFyaWFibGUgc2F0aXNmYWNjaW9uCmtpd2lfOSA8LSBraXdpXzggJT4lIAogIGZpbHRlcighaXMubmEoc2F0aXNmYWNjaW9uKSkgIyBFbCBzaWdubyAhIGVzIGVsIG9wZXJhZG9yIGzDs2dpY28gTk9UCgojIFByb2JlbW9zIGhhY2VyIHVuIGJveHBsb3QKZ2dwbG90KGtpd2lfOSwgYWVzKHggPSBmYWN0b3Ioc2F0aXNmYWNjaW9uKSwgeSA9IHN1ZWxkb19icnV0bykpICsKICBnZW9tX2JveHBsb3QoKQpgYGAKCiMjIFNlZ3VpciBhcHJlbmRpZW5kbwoKUHVlZGVuIHByb2JhciBlbiBlc3RlIGRvY3VtZW50byBjYW1iaWFuZG8gdmFyaWFibGVzLCB1c2FyIG90cm8gZGF0YXNldCwgeWEgc2VhIGVsIGRhdGFzZXQgYGNydWRvYCBvIGFsZ3VubyBwcm9waW8uIEN1YW5kbyB0ZXJtaW5lbiwgY29ycmFuIGVzdGUgZG9jdW1lbnRvIGNvbiBlbCBib3TDs24gZGUgYGtuaXRgIG8gYmllbiBjb24gbGFzIHRlY2xhcyBgQ3RybGAgKyBgU2hpZnRgICsgYEtgLiBFc28gdmEgYSBnZW5lcmFyIHVuIGRvY3VtZW50byBxdWUgcHVlZGVuIHB1YmxpY2FyIGVuIGzDrW5lYSB5IGNvbXBhcnRpciBlbiBzdXMgcmVkZXMgbG8gcXVlIGFwcmVuZGllcm9uLgoKRW4gbG9zIHJlc3VsdGFkb3MgcXVlIHB1YmxpY2Ftb3MgZGUgbGEgW0VuY3Vlc3RhIEtJV0kgZGUgU3VlbGRvcyBkZSBSSF0oaHR0cHM6Ly9ycHVicy5jb20vRGF0YTRIUi9lbmN1ZXN0YS1raXdpLTIwMjApIHB1ZWRlbiB2ZXIgY8OzbW8gdXRpbGl6YW1vcyB0b2RhcyBlc3RhcyB0w6ljbmljYXMgcGFyYSBsaW1waWFyIGVsIGFyY2hpdm8gYGNydWRvYCBxdWUgdXNhbW9zIGVuIGVzdGEgc2VzacOzbiB5IHF1ZSBwdWVkZW4gcmVwbGljYXIgcG9yIHN1IGN1ZW50YS4KCkFwcm92ZWNoZW4gZWwgY29udGVuaWRvIGRlIG51ZXN0cm8gW0dvb2dsZSBEcml2ZV0oaHR0cHM6Ly9kcml2ZS5nb29nbGUuY29tL2RyaXZlL2ZvbGRlcnMvMVFjazN6X3Q2WExSWGIydmJOLTAwOTMxRGdkSloweXNlP3VzcD1zaGFyaW5nKSB5IGRlIG51ZXN0cm8gW3JlcG9zaXRvcmlvIGRlIEdpdEh1Yl0oaHR0cHM6Ly9naXRodWIuY29tL3I0aHIvY2x1Yl9kZV9yKS4gUGFydGljdWxhcm1lbnRlIGJ1c3F1ZW4gbGFzIHNlc2lvbmVzIHJlbGFjaW9uYWRhcyBjb24gZHBseXIsIGdncGxvdDIsIHkgZm9yY2F0cyBxdWUgc29uIGNvbXBsZW1lbnRvcyBleGNlbGVudGVzIGRlIGVzdGUgZW5jdWVudHJvLgoKU8O6bWVuc2UgYSBudWVzdHJvIFtjYW5hbCBkZSBTbGFja10oaHR0cHM6Ly9qb2luLnNsYWNrLmNvbS90L3I0aHIvc2hhcmVkX2ludml0ZS96dC1vY3JpeXg1ZS1ucVh1d1dlRHlPS20ySUNVSmpodTZnKSB5IHBhcnRpY2lwZW4gaGFjaWVuZG8gY29uc3VsdGFzIHkgY29tcGFydGllbmRvIGNvbnRlbmlkbyBkZSB2YWxvci4K