Este paquete agrupa una serie de paquetes que tienen una misma lógica en su diseño y por ende funcionan en armonía.
Entre ellos usaremos principalmente ggplot para realizar gráficos, y dplyr y tidyr para realizar transformaciones sobre nuestro set de datos.
A continuación cargamos la librería a nuestro ambiente. Para ello debe estar previamente instalada en nuestra pc.
library(tidyverse)
Para mostrar el funcionamiento básico de tydyverse retomaremos el ejemplo de la clase 1, con lo cual volvemos a crear el set de datos del Indice de salarios.
INDICE <- c(100, 100, 100,
101.8, 101.2, 100.73,
102.9, 102.4, 103.2)
FECHA <- c("Oct-16", "Oct-16", "Oct-16",
"Nov-16", "Nov-16", "Nov-16",
"Dic-16", "Dic-16", "Dic-16")
GRUPO <- c("Privado_Registrado","Público","Privado_No_Registrado",
"Privado_Registrado","Público","Privado_No_Registrado",
"Privado_Registrado","Público","Privado_No_Registrado")
Datos <- data.frame(INDICE, FECHA, GRUPO)
Dplyr
El caracter principal para utilizar este paquete es %>% , pipe (de tubería).
Los %>% toman el set de datos a su izquierda, y los transforman mediante los comandos a su derecha, en los cuales los elementos de la izquierda están implícitos. En otros términos.
\(f(x,y)\) es equivalente a \(x\) %>% \(f(.,y)\)
Veamos las principales funciones que pueden utilizarse con la lógica de este paquete:
filter
Permite filtrar la tabla acorde al cumplimiento de condiciones lógicas
Datos %>%
filter(INDICE>101 , GRUPO == "Privado_Registrado")
Nótese que en este caso al separar con una , las condiciones se exige el cumplimiento de ambas. En caso de desear que se cumpla una sola condición debe utilizarse el caracter |
Datos %>%
filter(INDICE>101 | GRUPO == "Privado_Registrado")
rename
Permite renombrar una columna de la tabla. Funciona de la siguiente manera: Data %>% rename( nuevo_nombre = viejo_nombre )
Datos %>%
rename(Periodo = FECHA)
Nótese que a diferencia del ejemplo de la función filter donde utilizábamos == para comprobar una condición lógica, en este caso se utiliza sólo un = ya que lo estamos haciendo es asignar un nombre.
mutate
Permite agregar una variable a la tabla (especificando el nombre que tomará esta), que puede ser el resultado de operaciones sobre otras variables de la misma tabla.
En caso de especificar el nombre de una columna existente, el resultado de la operación realizada “sobrescribirá” la información de la columna con dicho nombre
Datos <- Datos %>%
mutate(Doble=INDICE*2)
Datos
case_when
Permite definir una variable, la cual toma un valor particular para cada condición establecida. En caso de no cumplir ninguna de las condiciones establecidas la variable tomara valor NA La sintaxis de la función es case_when( condicion lógica1 ~ valor asignado1).
Datos <- Datos %>%
mutate(Caso_cuando = case_when(GRUPO == "Privado_Registrado" ~ INDICE*2,
GRUPO == "Público" ~ INDICE*3,
GRUPO == "Privado_No_Registrado"~ INDICE*5))
Datos
select
Permite especificar la serie de columnas que se desea conservar de un DataFrame. También pueden especificarse las columnas que se desean descartar (agregándoles un -). Muy útil para agilizar el trabajo en bases de datos de gran tamaño.
Datos2 <- Datos %>%
select(INDICE, FECHA, GRUPO)
Datos2
Datos <- Datos %>%
select(-c(Doble,Caso_cuando))
Datos
arrange
Permite Ordenar la tabla por los valores de determinada/s variable/s. Es útil cuando luego deben hacerse otras operaciones que requieran del ordenamiento de la tabla
Datos <- Datos %>%
arrange(GRUPO, INDICE)
Datos
summarise
Crea una nueva tabla que resuma la información original. Para ello, Definimos las variables de resumen y las formas de agregación.
Datos %>%
summarise(Indprom = mean(INDICE))
group_by
Esta función permite realizar operaciones de forma agrupada. Lo que hace la función es “separar” a la tabla según los valores de la variable indicada y realizar las operaciones que se especificaba continuación, de manera independiente para cada una de las “subtablas”. En nuestro ejemplo, sería útil para calcular el promedio de los indices por Fecha
Datos %>%
group_by(FECHA) %>%
summarise(Indprom = mean(INDICE))
Joins
Otra implementación muy importante del paquete dplyr son las funciones para unir tablas (joins)
left_join
Veamos un ejemplo de la función left_join (una de las más utilizadas en la práctica).
Para ello crearemos previamente un Dataframe que contenga un Ponderador para cada uno de los Grupos del Dataframe Datos. Aprovecharemos el ejemplo para introducir la función weigthed.mean, y así calcular un Indice Ponderado.
Ponderadores <- data.frame(GRUPO = c("Privado_Registrado","Público","Privado_No_Registrado"),
PONDERADOR = c(50.16,29.91,19.93))
Datos_join <- Datos %>%
left_join(.,Ponderadores, by = "GRUPO")
Datos_join
Datos_Indice_Gral <- Datos_join %>%
group_by(FECHA) %>%
summarise(Indice_Gral = weighted.mean(INDICE,w = PONDERADOR))
Datos_Indice_Gral
Tidyr
El paquete tidyr esta pensado para facilitar el emprolijamiento de los datos.
Gather es una función que nos permite pasar los datos de forma horizontal a una forma vertical.
spread es una función que nos permite pasar los datos de forma vertical a una forma horizontal.
#Utilzamos un conjunto de datos que viene con la librería datasets
library(datasets)
data(iris)
iris <- iris %>%
mutate(id = 1:nrow(.)) %>% #le agrego un ID
select(id, everything()) # lo acomodo para que el id este primero.
iris
Gather y Spread
iris_vertical <- iris %>% gather(., # el . llama a lo que esta atras del %>%
key = Variables,
value = Valores,
2:5) #le indico que columnas juntar
iris_vertical
Podemos deshacer el gather con un Spread
iris_horizontal <- iris_vertical %>%
spread(. ,
key = Variables, #la llave es la variable que va a dar los nombres de columna
value = Valores) #los valores con que se llenan las celdas
iris_horizontal
Cuadros 1.1 y 1.2.
Cargamos dos librerías que usaremos respectivamente para leer y escribir archivos en excel.
library(readxl) # para leer archivos en excel
library(xlsx) # para escribir archivos en excel
Carpetas de Trabajo
Dado que vamos a trabajar utilizando información almacenada en el disco, resultará útil crear objetos que contengan las direcciones de nuestras carpetas de trabajo. A continuación veremos una formula genérica de crear estos objetos, a partir de identificar donde esta guardado el script.
# Funcion para obtener la direccion del Script Obtengo la ubicación del Script en el Disco
dir <- paste0(dirname(rstudioapi::getActiveDocumentContext()$path),"/")
# A partir del objeto dir, creo un objeto con la dirección de la carpeta que contiene los microdatos y otro con la que contendrá los resultados.
bases.dir <- paste0(dirname(dir),"/Fuentes/")
resultados.dir <- paste0(dirname(dir),"/Resultados/")
Cuadro 1.1
Creo una tabla con los niveles de: - Población - Ocupados - Desocupados - PEA - Ocupados demandantes - Subocupados (demandantes, no demandantes y total)
Estos niveles nos van a permitir calcular las tasas de forma sencilla.
####Cuadro 1.1 Principales indicadores. Total 31 aglomerados u
Cuadro_1.1a <- Individual_t117 %>%
summarise(Poblacion = sum(PONDERA),
Ocupados = sum(PONDERA[ESTADO == 1]),
Desocupados = sum(PONDERA[ESTADO == 2]),
PEA = Ocupados + Desocupados,
Ocupados_demand = sum(PONDERA[ESTADO == 1 & PP03J ==1]),
Suboc_demandante = sum(PONDERA[ESTADO == 1 & INTENSI ==1 & PP03J==1]),
Suboc_no_demand = sum(PONDERA[ESTADO == 1 & INTENSI ==1 & PP03J %in% c(2,9)]),
Subocupados = Suboc_demandante + Suboc_no_demand ,
# También podemos llamar a las variables entre comillas, incluyendo nombres compuestos
# A su vez, podemos utilizar la variable recién creada en la definción de otra varible
'Tasa Actividad' = PEA/Poblacion,
'Tasa Empleo' = Ocupados/Poblacion,
'Tasa Desocupacion' = Desocupados/PEA,
'Tasa ocupados demandantes' = Ocupados_demand/PEA,
'Tasa Subocupación' = Subocupados/PEA,
'Tasa Subocupación demandante' = Suboc_demandante/PEA,
'Tasa Subocupación no demandante' = Suboc_no_demand/PEA)
Cuadro_1.1a
Una vez que calculamos las tasas, podemos borrar los niveles con un select
Cuadro_1.1a <- Cuadro_1.1a %>%
select(-c(1:8))
Cuadro_1.1a
Con gather podemos dar vuelta la tabla para que quede como en la publicación
Cuadro_1.1a <- Cuadro_1.1a %>%
gather(Tasas, Valor, 1:ncol(.))
Cuadro_1.1a
En caso de querer expresar los resultados como porcentajes, utilizamos la función sprintf. Para ello debemos utilizar mutate para transformar la columna Valor.
Cuadro_1.1a <- Cuadro_1.1a %>%
mutate(Valor = sprintf("%1.1f%%", 100*Valor))
Cuadro_1.1a
Nótese que en este caso, para poder añadir el %, la función transforma a la variable en un Character, por ende debe tenerse en cuenta que se pierde la información del numero completo.
Cuadro 1.2
En este caso, podemos ver que simplemente agregando la función group_by podemos replicar el procedimiento para cada uno de los aglomerados. Y a su vez, podemos realizar en un solo paso los arreglos posteriores sobre nuestra tabla.
Cuadro_1.2a <- Individual_t117 %>%
group_by(AGLOMERADO) %>%
summarise(Poblacion = sum(PONDERA),
Ocupados = sum(PONDERA[ESTADO == 1]),
Desocupados = sum(PONDERA[ESTADO == 2]),
PEA = Ocupados + Desocupados,
Ocupados_demand = sum(PONDERA[ESTADO == 1 & PP03J == 1]),
Suboc_demandante = sum(PONDERA[ESTADO == 1 & INTENSI == 1 & PP03J == 1]),
Suboc_no_demand = sum(PONDERA[ESTADO == 1 & INTENSI == 1 & PP03J %in% c(2, 9)]),
Subocupados = Suboc_demandante + Suboc_no_demand,
'Tasa Actividad' = PEA/Poblacion,
'Tasa Empleo' = Ocupados/Poblacion,
'Tasa Desocupacion' = Desocupados/PEA,
'Tasa ocupados demandantes' = Ocupados_demand/PEA,
'Tasa Subocupación' = Subocupados/PEA,
'Tasa Subocupación demandante' = Suboc_demandante/PEA,
'Tasa Subocupación no demandante' = Suboc_no_demand/PEA) %>%
select(-c(2:9)) %>%
left_join(.,Aglom) %>%
select(Nom_Aglo,everything(.),-AGLOMERADO)
Joining, by = "AGLOMERADO"
Cuadro_1.2a
Exportar resultados a Excel
La función write.xlsx nos permite guardar en un Excel los resultados de nuestro procesamiento de la información. Esta función requiere que el tipo de Datos sea un DataFrame.
class(Cuadro_1.2a)
[1] "tbl_df" "tbl" "data.frame"
Como nos muestra la función class, la funciones del tidyverse al realizar las operaciones agrupadas, transforma el tipo de de los datos. Por ende, para guardar los resultados debemos reconvertir nuestra tabla en un DataFrame con la función as.data.frame.
write.xlsx entre sus parámetros requiere especificar:
- El objeto a guardar
- La ruta y nombre del archivo que será guardado
De manera opcional puede también especificarse:
- El nombre de la Hoja
- Si se desea pisar (append = FALSE) o incorporar (append = TRUE) archivos que tengan el mismo nombre
- Si deseamos que figuren o no los números de filas en nuestra tabla
write.xlsx(as.data.frame(Cuadro_1.1a), paste0(resultados.dir,"Informe Mercado de Trabajo.xlsx"),sheetName = "Cuadro 1.1", append = FALSE, row.names = FALSE)
write.xlsx(as.data.frame(Cuadro_1.2a), paste0(resultados.dir,"Informe Mercado de Trabajo.xlsx"),sheetName = "Cuadro 1.2", append = TRUE, row.names = FALSE)
LS0tDQp0aXRsZTogIkNsYXNlIDIuIFRpZHl2ZXJzZSB5IE1lcmNhZG8gZGUgVHJhYmFqbyINCmF1dGhvcjogIkd1aWRvIFdla3NsZXIgeSBEaWVnbyBLb3psb3dza2kiDQpkYXRlOiAiMTMgZGUgT2N0dWJyZSBkZSAyMDE3Ig0Kb3V0cHV0OiANCiAgaHRtbF9ub3RlYm9vazogDQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgZGVwdGg6IDMNCg0KLS0tDQoNCmBgYHtyLCB3YXJuaW5nPUZBTFNFfQ0Kcm0obGlzdD1scygpKQ0KYGBgDQoNCiMgW1RpZHl2ZXJzZV0oaHR0cHM6Ly93d3cudGlkeXZlcnNlLm9yZy8pICAgICAgICAgDQoNCkVzdGUgcGFxdWV0ZSBhZ3J1cGEgdW5hIHNlcmllIGRlIHBhcXVldGVzIHF1ZSB0aWVuZW4gdW5hIG1pc21hIGzDs2dpY2EgZW4gc3UgZGlzZcOxbyB5IHBvciBlbmRlIGZ1bmNpb25hbiBlbiBhcm1vbsOtYS4gICAgIA0KRW50cmUgZWxsb3MgdXNhcmVtb3MgcHJpbmNpcGFsbWVudGUgX19nZ3Bsb3RfXyBwYXJhIHJlYWxpemFyIGdyw6FmaWNvcywgeSBfX2RwbHlyX18geSBfX3RpZHlyX18gcGFyYSByZWFsaXphciB0cmFuc2Zvcm1hY2lvbmVzIHNvYnJlIG51ZXN0cm8gc2V0IGRlIGRhdG9zLg0KDQpBIGNvbnRpbnVhY2nDs24gY2FyZ2Ftb3MgbGEgbGlicmVyw61hIGEgbnVlc3RybyBhbWJpZW50ZS4gUGFyYSBlbGxvIGRlYmUgZXN0YXIgcHJldmlhbWVudGUgaW5zdGFsYWRhIGVuIG51ZXN0cmEgcGMuDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KYGBgDQoNClBhcmEgbW9zdHJhciBlbCBmdW5jaW9uYW1pZW50byBiw6FzaWNvIGRlIHR5ZHl2ZXJzZSByZXRvbWFyZW1vcyBlbCBlamVtcGxvIGRlIGxhIGNsYXNlIDEsIGNvbiBsbyBjdWFsIHZvbHZlbW9zIGEgY3JlYXIgZWwgc2V0IGRlIGRhdG9zIGRlbCBbSW5kaWNlIGRlIHNhbGFyaW9zXShodHRwOi8vd3d3LmluZGVjLmdvYi5hci9iYWphckN1YWRyb0VzdGFkaXN0aWNvLmFzcD9pZGM9NDAyMEIzMzQ0MDYwOTQ2MjY1NDU0MkJEMEJDMzIwRjE1MjNEQTBEQzUyQzM5NjIwMURCNERENTg2MUZGRURDOUFEMTQzNjY4MUFDODQxNzkpLg0KYGBge3J9DQpJTkRJQ0UgIDwtIGMoMTAwLCAgIDEwMCwgICAxMDAsDQogICAgICAgICAgICAgMTAxLjgsIDEwMS4yLCAxMDAuNzMsDQogICAgICAgICAgICAgMTAyLjksIDEwMi40LCAxMDMuMikNCg0KRkVDSEEgIDwtICBjKCJPY3QtMTYiLCAiT2N0LTE2IiwgIk9jdC0xNiIsDQogICAgICAgICAgICAgIk5vdi0xNiIsICJOb3YtMTYiLCAiTm92LTE2IiwNCiAgICAgICAgICAgICAiRGljLTE2IiwgIkRpYy0xNiIsICJEaWMtMTYiKQ0KDQoNCkdSVVBPICA8LSAgYygiUHJpdmFkb19SZWdpc3RyYWRvIiwiUMO6YmxpY28iLCJQcml2YWRvX05vX1JlZ2lzdHJhZG8iLA0KICAgICAgICAgICAgICJQcml2YWRvX1JlZ2lzdHJhZG8iLCJQw7pibGljbyIsIlByaXZhZG9fTm9fUmVnaXN0cmFkbyIsDQogICAgICAgICAgICAgIlByaXZhZG9fUmVnaXN0cmFkbyIsIlDDumJsaWNvIiwiUHJpdmFkb19Ob19SZWdpc3RyYWRvIikNCg0KRGF0b3MgPC0gZGF0YS5mcmFtZShJTkRJQ0UsIEZFQ0hBLCBHUlVQTykNCg0KDQpgYGANCg0KDQojIyBEcGx5cg0KDQpFbCBjYXJhY3RlciBwcmluY2lwYWwgcGFyYSB1dGlsaXphciBlc3RlIHBhcXVldGUgZXMgYGBgJT4lYGBgICwgX3BpcGVfIChkZSB0dWJlcsOtYSkuDQoNCkxvcyBgYGAlPiVgYGAgdG9tYW4gZWwgc2V0IGRlIGRhdG9zIGEgc3UgaXpxdWllcmRhLCB5IGxvcyB0cmFuc2Zvcm1hbiBtZWRpYW50ZSBsb3MgY29tYW5kb3MgYSBzdSBkZXJlY2hhLCBlbiBsb3MgY3VhbGVzIGxvcyBlbGVtZW50b3MgZGUgbGEgaXpxdWllcmRhIGVzdMOhbiBpbXBsw61jaXRvcy4gRW4gb3Ryb3MgdMOpcm1pbm9zLg0KDQokZih4LHkpJCBlcyBlcXVpdmFsZW50ZSBhICR4JCAlPiUgJGYoLix5KSQgDQoNClZlYW1vcyBsYXMgcHJpbmNpcGFsZXMgZnVuY2lvbmVzIHF1ZSBwdWVkZW4gdXRpbGl6YXJzZSBjb24gbGEgbMOzZ2ljYSBkZSBlc3RlIHBhcXVldGU6DQoNCiMjIyBmaWx0ZXINCg0KUGVybWl0ZSBmaWx0cmFyIGxhIHRhYmxhIGFjb3JkZSBhbCBjdW1wbGltaWVudG8gZGUgY29uZGljaW9uZXMgbMOzZ2ljYXMNCiANCmBgYHtyfQ0KRGF0b3MgJT4lIA0KICBmaWx0ZXIoSU5ESUNFPjEwMSAsIEdSVVBPID09ICJQcml2YWRvX1JlZ2lzdHJhZG8iKQ0KDQpgYGANCk7Ds3Rlc2UgcXVlIGVuIGVzdGUgY2FzbyBhbCBzZXBhcmFyIGNvbiB1bmEgIGBgYCxgYGAgbGFzIGNvbmRpY2lvbmVzIHNlIGV4aWdlIGVsIGN1bXBsaW1pZW50byBkZSBhbWJhcy4gRW4gY2FzbyBkZSBkZXNlYXIgcXVlIHNlIGN1bXBsYSB1bmEgc29sYSBjb25kaWNpw7NuIGRlYmUgdXRpbGl6YXJzZSBlbCBjYXJhY3RlciBgYGB8YGBgDQpgYGB7cn0NCkRhdG9zICU+JSANCiAgZmlsdGVyKElORElDRT4xMDEgfCBHUlVQTyA9PSAiUHJpdmFkb19SZWdpc3RyYWRvIikNCmBgYA0KDQojIyMgcmVuYW1lDQpQZXJtaXRlIHJlbm9tYnJhciB1bmEgY29sdW1uYSBkZSBsYSB0YWJsYS4gRnVuY2lvbmEgZGUgbGEgc2lndWllbnRlIG1hbmVyYTogDQogYGBgRGF0YSAlPiUgcmVuYW1lKCBudWV2b19ub21icmUgPSB2aWVqb19ub21icmUgKWBgYCANCmBgYHtyfQ0KRGF0b3MgJT4lIA0KICByZW5hbWUoUGVyaW9kbyA9IEZFQ0hBKQ0KYGBgDQpOw7N0ZXNlIHF1ZSBhIGRpZmVyZW5jaWEgZGVsIGVqZW1wbG8gZGUgbGEgZnVuY2nDs24gX19maWx0ZXJfXyBkb25kZSB1dGlsaXrDoWJhbW9zIF9fPT1fXyBwYXJhIGNvbXByb2JhciB1bmEgY29uZGljacOzbiBsw7NnaWNhLCBlbiBlc3RlIGNhc28gc2UgdXRpbGl6YSBzw7NsbyB1biBfXz1fXyB5YSBxdWUgbG8gZXN0YW1vcyBoYWNpZW5kbyBlcyBfYXNpZ25hcl8gdW4gbm9tYnJlLg0KDQojIyMgbXV0YXRlDQpQZXJtaXRlIGFncmVnYXIgdW5hIHZhcmlhYmxlIGEgbGEgdGFibGEgKGVzcGVjaWZpY2FuZG8gZWwgbm9tYnJlIHF1ZSB0b21hcsOhIGVzdGEpLCBxdWUgcHVlZGUgc2VyIGVsIHJlc3VsdGFkbyBkZSBvcGVyYWNpb25lcyBzb2JyZSBvdHJhcyB2YXJpYWJsZXMgZGUgbGEgbWlzbWEgdGFibGEuICAgICAgIA0KDQpFbiBjYXNvIGRlIGVzcGVjaWZpY2FyIGVsIG5vbWJyZSBkZSB1bmEgY29sdW1uYSBleGlzdGVudGUsIGVsIHJlc3VsdGFkbyBkZSBsYSBvcGVyYWNpw7NuIHJlYWxpemFkYSAic29icmVzY3JpYmlyw6EiIGxhIGluZm9ybWFjacOzbiBkZSBsYSBjb2x1bW5hIGNvbiBkaWNobyBub21icmUNCmBgYHtyfQ0KRGF0b3MgPC0gRGF0b3MgJT4lIA0KICBtdXRhdGUoRG9ibGU9SU5ESUNFKjIpDQpEYXRvcw0KYGBgDQoNCiMjIyBjYXNlX3doZW4NClBlcm1pdGUgZGVmaW5pciB1bmEgdmFyaWFibGUsIGxhIGN1YWwgdG9tYSB1biB2YWxvciBwYXJ0aWN1bGFyIHBhcmEgY2FkYSBjb25kaWNpw7NuIGVzdGFibGVjaWRhLiBFbiBjYXNvIGRlIG5vIGN1bXBsaXIgbmluZ3VuYSBkZSBsYXMgY29uZGljaW9uZXMgZXN0YWJsZWNpZGFzIGxhIHZhcmlhYmxlIHRvbWFyYSB2YWxvciBfX05BX18NCkxhIHNpbnRheGlzIGRlIGxhIGZ1bmNpw7NuIGVzIGNhc2Vfd2hlbiggX2NvbmRpY2lvbiBsw7NnaWNhMV8gX19+X18gX3ZhbG9yIGFzaWduYWRvMV8pLg0KDQpgYGB7cn0NCkRhdG9zIDwtIERhdG9zICU+JSANCiAgbXV0YXRlKENhc29fY3VhbmRvID0gY2FzZV93aGVuKEdSVVBPID09ICJQcml2YWRvX1JlZ2lzdHJhZG8iICAgfiBJTkRJQ0UqMiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIEdSVVBPID09ICJQw7pibGljbyIgICAgICAgICAgICAgIH4gSU5ESUNFKjMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBHUlVQTyA9PSAiUHJpdmFkb19Ob19SZWdpc3RyYWRvIn4gSU5ESUNFKjUpKQ0KRGF0b3MNCmBgYA0KDQojIyMgc2VsZWN0DQpQZXJtaXRlIGVzcGVjaWZpY2FyIGxhIHNlcmllIGRlIGNvbHVtbmFzIHF1ZSBzZSBkZXNlYSBjb25zZXJ2YXIgZGUgdW4gRGF0YUZyYW1lLiBUYW1iacOpbiBwdWVkZW4gZXNwZWNpZmljYXJzZSBsYXMgY29sdW1uYXMgcXVlIHNlIGRlc2VhbiBkZXNjYXJ0YXIgKGFncmVnw6FuZG9sZXMgdW4gXy1fKS4gTXV5IMO6dGlsIHBhcmEgYWdpbGl6YXIgZWwgdHJhYmFqbyBlbiBiYXNlcyBkZSBkYXRvcyBkZSBncmFuIHRhbWHDsW8uDQpgYGB7cn0NCkRhdG9zMiA8LSBEYXRvcyAlPiUgDQogIHNlbGVjdChJTkRJQ0UsIEZFQ0hBLCBHUlVQTykNCkRhdG9zMg0KDQpEYXRvcyA8LSBEYXRvcyAlPiUgDQogIHNlbGVjdCgtYyhEb2JsZSxDYXNvX2N1YW5kbykpDQpEYXRvcw0KYGBgDQoNCiMjIyBhcnJhbmdlDQpQZXJtaXRlIE9yZGVuYXIgbGEgdGFibGEgcG9yIGxvcyB2YWxvcmVzIGRlIGRldGVybWluYWRhL3MgdmFyaWFibGUvcy4gRXMgw7p0aWwgY3VhbmRvIGx1ZWdvIGRlYmVuIGhhY2Vyc2Ugb3RyYXMgb3BlcmFjaW9uZXMgcXVlIHJlcXVpZXJhbiBkZWwgb3JkZW5hbWllbnRvIGRlIGxhIHRhYmxhDQpgYGB7cn0NCkRhdG9zIDwtIERhdG9zICU+JSANCiAgYXJyYW5nZShHUlVQTywgSU5ESUNFKQ0KRGF0b3MNCmBgYA0KDQojIyMgc3VtbWFyaXNlDQpDcmVhIHVuYSBudWV2YSB0YWJsYSBxdWUgcmVzdW1hIGxhIGluZm9ybWFjacOzbiBvcmlnaW5hbC4gUGFyYSBlbGxvLCBEZWZpbmltb3MgbGFzIHZhcmlhYmxlcyBkZSByZXN1bWVuIHkgbGFzIGZvcm1hcyBkZSBhZ3JlZ2FjacOzbi4NCmBgYHtyfQ0KRGF0b3MgJT4lIA0KICBzdW1tYXJpc2UoSW5kcHJvbSA9IG1lYW4oSU5ESUNFKSkNCg0KYGBgDQoNCiMjIyBncm91cF9ieQ0KRXN0YSBmdW5jacOzbiBwZXJtaXRlIHJlYWxpemFyIG9wZXJhY2lvbmVzIGRlIGZvcm1hIGFncnVwYWRhLiBMbyBxdWUgaGFjZSBsYSBmdW5jacOzbiBlcyAic2VwYXJhciIgYSBsYSB0YWJsYSBzZWfDum4gbG9zIHZhbG9yZXMgZGUgbGEgdmFyaWFibGUgaW5kaWNhZGEgeSByZWFsaXphciBsYXMgb3BlcmFjaW9uZXMgcXVlIHNlIGVzcGVjaWZpY2FiYSAgY29udGludWFjacOzbiwgZGUgbWFuZXJhIGluZGVwZW5kaWVudGUgcGFyYSBjYWRhIHVuYSBkZSBsYXMgInN1YnRhYmxhcyIuIEVuIG51ZXN0cm8gZWplbXBsbywgc2Vyw61hIMO6dGlsIHBhcmEgY2FsY3VsYXIgZWwgcHJvbWVkaW8gZGUgbG9zIGluZGljZXMgcG9yIF9GZWNoYV8gDQpgYGB7cn0NCkRhdG9zICU+JSANCiAgZ3JvdXBfYnkoRkVDSEEpICU+JQ0KICBzdW1tYXJpc2UoSW5kcHJvbSA9IG1lYW4oSU5ESUNFKSkNCmBgYA0KDQojIyBKb2lucw0KDQpPdHJhIGltcGxlbWVudGFjacOzbiBtdXkgaW1wb3J0YW50ZSBkZWwgcGFxdWV0ZSBkcGx5ciBzb24gbGFzIGZ1bmNpb25lcyBwYXJhIHVuaXIgdGFibGFzIChqb2lucykNCg0KDQohW2Z1ZW50ZTogaHR0cDovL3JzdHVkaW8tcHVicy1zdGF0aWMuczMuYW1hem9uYXdzLmNvbS8yMjcxNzFfNjE4ZWJkY2UwYjlkNDRmM2FmNjU3MDBlODMzNTkzZGIuaHRtbF0oam9pbnMucG5nKSAgICAgICAgIA0KDQojIyNsZWZ0X2pvaW4gICAgDQoNClZlYW1vcyB1biBlamVtcGxvIGRlIGxhIGZ1bmNpw7NuIF9fbGVmdF9qb2luX18gKHVuYSBkZSBsYXMgbcOhcyB1dGlsaXphZGFzIGVuIGxhIHByw6FjdGljYSkuICAgICAgIA0KUGFyYSBlbGxvIGNyZWFyZW1vcyBwcmV2aWFtZW50ZSB1biBEYXRhZnJhbWUgcXVlIGNvbnRlbmdhIHVuIFBvbmRlcmFkb3IgcGFyYSBjYWRhIHVubyBkZSBsb3MgR3J1cG9zIGRlbCBEYXRhZnJhbWUgX0RhdG9zXy4gQXByb3ZlY2hhcmVtb3MgZWwgZWplbXBsbyBwYXJhIGludHJvZHVjaXIgbGEgZnVuY2nDs24gX193ZWlndGhlZC5tZWFuX18sIHkgYXPDrSBjYWxjdWxhciB1biBJbmRpY2UgUG9uZGVyYWRvLg0KDQpgYGB7cn0NClBvbmRlcmFkb3JlcyA8LSBkYXRhLmZyYW1lKEdSVVBPID0gYygiUHJpdmFkb19SZWdpc3RyYWRvIiwiUMO6YmxpY28iLCJQcml2YWRvX05vX1JlZ2lzdHJhZG8iKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBQT05ERVJBRE9SID0gYyg1MC4xNiwyOS45MSwxOS45MykpDQoNCkRhdG9zX2pvaW4gPC0gRGF0b3MgJT4lIA0KICBsZWZ0X2pvaW4oLixQb25kZXJhZG9yZXMsIGJ5ID0gIkdSVVBPIikNCkRhdG9zX2pvaW4NCg0KRGF0b3NfSW5kaWNlX0dyYWwgPC0gRGF0b3Nfam9pbiAlPiUgDQogIGdyb3VwX2J5KEZFQ0hBKSAlPiUgDQogIHN1bW1hcmlzZShJbmRpY2VfR3JhbCA9IHdlaWdodGVkLm1lYW4oSU5ESUNFLHcgPSBQT05ERVJBRE9SKSkNCg0KRGF0b3NfSW5kaWNlX0dyYWwNCmBgYA0KDQojIyBUaWR5cg0KDQpFbCBwYXF1ZXRlIHRpZHlyIGVzdGEgcGVuc2FkbyBwYXJhIGZhY2lsaXRhciBlbCBlbXByb2xpamFtaWVudG8gZGUgbG9zIGRhdG9zLg0KDQpfX0dhdGhlcl9fIGVzIHVuYSBmdW5jacOzbiBxdWUgbm9zIHBlcm1pdGUgcGFzYXIgbG9zIGRhdG9zIGRlIGZvcm1hIGhvcml6b250YWwgYSB1bmEgZm9ybWEgdmVydGljYWwuIA0KDQpfX3NwcmVhZF9fIGVzIHVuYSBmdW5jacOzbiBxdWUgbm9zIHBlcm1pdGUgcGFzYXIgbG9zIGRhdG9zIGRlIGZvcm1hIHZlcnRpY2FsIGEgdW5hIGZvcm1hIGhvcml6b250YWwuDQoNCiFbZnVlbnRlOiBodHRwOi8vd3d3Lmdpcy1ibG9nLmNvbS9kYXRhLW1hbmFnZW1lbnQtd2l0aC1yLXRpZHlyLXBhcnQtMS9dKHNwcmVhZFZTZ2F0aGVyLnBuZykNCg0KDQpgYGB7cn0NCiNVdGlsemFtb3MgdW4gY29uanVudG8gZGUgZGF0b3MgcXVlIHZpZW5lIGNvbiBsYSBsaWJyZXLDrWEgZGF0YXNldHMNCmxpYnJhcnkoZGF0YXNldHMpDQoNCmRhdGEoaXJpcykNCmlyaXMgPC0gaXJpcyAlPiUgDQogIG11dGF0ZShpZCA9IDE6bnJvdyguKSkgJT4lICAjbGUgYWdyZWdvIHVuIElEDQogIHNlbGVjdChpZCwgZXZlcnl0aGluZygpKSAjIGxvIGFjb21vZG8gcGFyYSBxdWUgZWwgaWQgZXN0ZSBwcmltZXJvLiANCg0KaXJpcw0KYGBgDQoNCg0KICANCiMjIyBHYXRoZXIgeSBTcHJlYWQNCg0KYGBge3J9DQppcmlzX3ZlcnRpY2FsIDwtIGlyaXMgJT4lIGdhdGhlciguLCAjIGVsIC4gbGxhbWEgYSBsbyBxdWUgZXN0YSBhdHJhcyBkZWwgJT4lIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAga2V5ICAgPSBWYXJpYWJsZXMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB2YWx1ZSA9IFZhbG9yZXMsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAyOjUpICNsZSBpbmRpY28gcXVlIGNvbHVtbmFzIGp1bnRhcg0KaXJpc192ZXJ0aWNhbA0KYGBgDQoNClBvZGVtb3MgZGVzaGFjZXIgZWwgX19nYXRoZXJfXyBjb24gdW4gX19TcHJlYWRfXw0KYGBge3J9DQppcmlzX2hvcml6b250YWwgPC0gaXJpc192ZXJ0aWNhbCAlPiUNCiAgc3ByZWFkKC4gLA0KICAgICAgICAga2V5ICAgPSBWYXJpYWJsZXMsICNsYSBsbGF2ZSBlcyBsYSB2YXJpYWJsZSBxdWUgdmEgYSBkYXIgbG9zIG5vbWJyZXMgZGUgY29sdW1uYQ0KICAgICAgICAgdmFsdWUgPSBWYWxvcmVzKSAjbG9zIHZhbG9yZXMgY29uIHF1ZSBzZSBsbGVuYW4gbGFzIGNlbGRhcw0KaXJpc19ob3Jpem9udGFsDQpgYGANCg0KDQojIFtNZXJjYWRvIGRlIFRyYWJham9dKGh0dHA6Ly93d3cuaW5kZWMuZ29iLmFyL3VwbG9hZHMvaW5mb3JtZXNkZXByZW5zYS9FUEhfY29udF8xdHJpbTE3LnBkZikNCkN1YWRyb3MgMS4xIHkgMS4yLg0KDQoNCkNhcmdhbW9zIGRvcyBsaWJyZXLDrWFzIHF1ZSB1c2FyZW1vcyByZXNwZWN0aXZhbWVudGUgcGFyYSBsZWVyIHkgZXNjcmliaXIgYXJjaGl2b3MgZW4gZXhjZWwuICAgDQoNCmBgYHtyLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZ9DQpsaWJyYXJ5KHJlYWR4bCkgIyBwYXJhIGxlZXIgYXJjaGl2b3MgZW4gZXhjZWwNCmxpYnJhcnkoeGxzeCkgICAjIHBhcmEgZXNjcmliaXIgYXJjaGl2b3MgZW4gZXhjZWwNCmBgYA0KDQojIyMjIENhcnBldGFzIGRlIFRyYWJham8NCg0KRGFkbyBxdWUgdmFtb3MgYSB0cmFiYWphciB1dGlsaXphbmRvIGluZm9ybWFjacOzbiBhbG1hY2VuYWRhIGVuIGVsIGRpc2NvLCByZXN1bHRhcsOhIMO6dGlsIGNyZWFyIG9iamV0b3MgcXVlIGNvbnRlbmdhbiBsYXMgZGlyZWNjaW9uZXMgZGUgbnVlc3RyYXMgY2FycGV0YXMgZGUgdHJhYmFqby4NCkEgY29udGludWFjacOzbiB2ZXJlbW9zIHVuYSBmb3JtdWxhIGdlbsOpcmljYSBkZSBjcmVhciBlc3RvcyBvYmpldG9zLCBhIHBhcnRpciBkZSBpZGVudGlmaWNhciBkb25kZSBlc3RhIGd1YXJkYWRvIGVsIHNjcmlwdC4NCmBgYHtyfQ0KIyBGdW5jaW9uIHBhcmEgb2J0ZW5lciBsYSBkaXJlY2Npb24gZGVsIFNjcmlwdCBPYnRlbmdvIGxhIHViaWNhY2nDs24gZGVsIFNjcmlwdCBlbiBlbCBEaXNjbw0KZGlyIDwtIHBhc3RlMChkaXJuYW1lKHJzdHVkaW9hcGk6OmdldEFjdGl2ZURvY3VtZW50Q29udGV4dCgpJHBhdGgpLCIvIikNCg0KIyBBIHBhcnRpciBkZWwgb2JqZXRvIGRpciwgY3JlbyB1biBvYmpldG8gY29uIGxhIGRpcmVjY2nDs24gZGUgbGEgY2FycGV0YSBxdWUgY29udGllbmUgbG9zIG1pY3JvZGF0b3MgeSBvdHJvIGNvbiBsYSBxdWUgY29udGVuZHLDoSBsb3MgcmVzdWx0YWRvcy4NCg0KYmFzZXMuZGlyICAgICAgPC0gIHBhc3RlMChkaXJuYW1lKGRpciksIi9GdWVudGVzLyIpDQpyZXN1bHRhZG9zLmRpciA8LSBwYXN0ZTAoZGlybmFtZShkaXIpLCIvUmVzdWx0YWRvcy8iKQ0KYGBgDQoNCiMjIyMgQ2FyZ2EgZGUgSW5mb3JtYWNpb24NCg0KTGEgZnVuY2nDs24gX19saXN0LmZpbGVzX18gbm9zIHBlcm1pdGUgb2JzZXJ2YXIgbG9zIGFyY2hpdm9zIHF1ZSBjb250aWVuZSB1bmEgZGV0ZXJtaW5hZGEgY2FycGV0YSAgICAgICAgICAgICANCg0KYGBge3J9DQpsaXN0LmZpbGVzKGJhc2VzLmRpcikNCmBgYA0KTGEgZnVuY2nDs24gX19yZWFkLnRhYmxlX18gbm9zIHBlcm1pdGUgbGV2YW50YXIgbG9zIGFyY2hpdm9zIGRlIGV4dGVuc2nDs24gIi50eHQiICAgICAgICAgICAgICAgDQpMYSBmdW5jacOzbiBfX3JlYWRfZXhjZWxfXyBub3MgcGVybWl0ZSBsZXZhbnRhciBsb3MgYXJjaGl2b3MgZGUgZXh0ZW5zacOzbiAiLnhsc3giICAgICAgICAgICAgICAgICANCg0KTGV2YW50YW1vcyBsYSBiYXNlIGluZGl2aWR1YWwgZGVsIHByaW1lciB0cmltZXN0cmUgZGUgMjAxNywgeSB1biBsaXN0YWRvIHF1ZSBjb250aWVuZSBsb3MgTm9tYnJlcyB5IEPDs2RpZ29zIGRlIGxvcyBBZ2xvbWVyYWRvcyBFUEguDQpgYGB7cn0NCkluZGl2aWR1YWxfdDExNyA8LQ0KICByZWFkLnRhYmxlKA0KICBwYXN0ZTAoYmFzZXMuZGlyLCAidXN1X2luZGl2aWR1YWxfdDExNy50eHQiKSwNCiAgc2VwID0gIjsiLA0KICBkZWMgPSAiLCIsDQogIGhlYWRlciA9IFRSVUUsDQogIGZpbGwgPSBUUlVFICkNCiAgDQogIA0KICBBZ2xvbSA8LSByZWFkX2V4Y2VsKHBhc3RlMChiYXNlcy5kaXIsICJBZ2xvbWVyYWRvcyBFUEgueGxzeCIpKQ0KYGBgDQoNCg0KDQojIyBDdWFkcm8gMS4xIA0KDQpDcmVvIHVuYSB0YWJsYSBjb24gbG9zIG5pdmVsZXMgZGU6DQotIFBvYmxhY2nDs24NCi0gT2N1cGFkb3MNCi0gRGVzb2N1cGFkb3MNCi0gUEVBDQotIE9jdXBhZG9zIGRlbWFuZGFudGVzDQotIFN1Ym9jdXBhZG9zIChkZW1hbmRhbnRlcywgbm8gZGVtYW5kYW50ZXMgeSB0b3RhbCkNCg0KRXN0b3Mgbml2ZWxlcyBub3MgdmFuIGEgcGVybWl0aXIgY2FsY3VsYXIgbGFzIHRhc2FzIGRlIGZvcm1hIHNlbmNpbGxhLiANCg0KYGBge3J9DQojIyMjQ3VhZHJvIDEuMSBQcmluY2lwYWxlcyBpbmRpY2Fkb3Jlcy4gVG90YWwgMzEgYWdsb21lcmFkb3MgdQ0KDQpDdWFkcm9fMS4xYSA8LSBJbmRpdmlkdWFsX3QxMTcgJT4lIA0KICBzdW1tYXJpc2UoUG9ibGFjaW9uICAgICAgICAgPSBzdW0oUE9OREVSQSksDQogICAgICAgICAgICBPY3VwYWRvcyAgICAgICAgICA9IHN1bShQT05ERVJBW0VTVEFETyA9PSAxXSksDQogICAgICAgICAgICBEZXNvY3VwYWRvcyAgICAgICA9IHN1bShQT05ERVJBW0VTVEFETyA9PSAyXSksDQogICAgICAgICAgICBQRUEgICAgICAgICAgICAgICA9IE9jdXBhZG9zICsgRGVzb2N1cGFkb3MsDQogICAgICAgICAgICBPY3VwYWRvc19kZW1hbmQgICA9IHN1bShQT05ERVJBW0VTVEFETyA9PSAxICYgUFAwM0ogPT0xXSksDQogICAgICAgICAgICBTdWJvY19kZW1hbmRhbnRlICA9IHN1bShQT05ERVJBW0VTVEFETyA9PSAxICYgSU5URU5TSSA9PTEgJiBQUDAzSj09MV0pLA0KICAgICAgICAgICAgU3Vib2Nfbm9fZGVtYW5kICAgPSBzdW0oUE9OREVSQVtFU1RBRE8gPT0gMSAmIElOVEVOU0kgPT0xICYgUFAwM0ogJWluJSBjKDIsOSldKSwNCiAgICAgICAgICAgIFN1Ym9jdXBhZG9zICAgICAgID0gU3Vib2NfZGVtYW5kYW50ZSArIFN1Ym9jX25vX2RlbWFuZCAsDQojIFRhbWJpw6luIHBvZGVtb3MgbGxhbWFyIGEgbGFzIHZhcmlhYmxlcyBlbnRyZSBjb21pbGxhcywgaW5jbHV5ZW5kbyBub21icmVzIGNvbXB1ZXN0b3MNCiMgQSBzdSB2ZXosIHBvZGVtb3MgdXRpbGl6YXIgbGEgdmFyaWFibGUgcmVjacOpbiBjcmVhZGEgZW4gbGEgZGVmaW5jacOzbiBkZSBvdHJhIHZhcmlibGUNCiAgICAgICAgICAgICdUYXNhIEFjdGl2aWRhZCcgICAgICAgICAgICAgICAgICA9IFBFQS9Qb2JsYWNpb24sDQogICAgICAgICAgICAnVGFzYSBFbXBsZW8nICAgICAgICAgICAgICAgICAgICAgPSBPY3VwYWRvcy9Qb2JsYWNpb24sDQogICAgICAgICAgICAnVGFzYSBEZXNvY3VwYWNpb24nICAgICAgICAgICAgICAgPSBEZXNvY3VwYWRvcy9QRUEsDQogICAgICAgICAgICAnVGFzYSBvY3VwYWRvcyBkZW1hbmRhbnRlcycgICAgICAgPSBPY3VwYWRvc19kZW1hbmQvUEVBLA0KICAgICAgICAgICAgJ1Rhc2EgU3Vib2N1cGFjacOzbicgICAgICAgICAgICAgICA9IFN1Ym9jdXBhZG9zL1BFQSwNCiAgICAgICAgICAgICdUYXNhIFN1Ym9jdXBhY2nDs24gZGVtYW5kYW50ZScgICAgPSBTdWJvY19kZW1hbmRhbnRlL1BFQSwNCiAgICAgICAgICAgICdUYXNhIFN1Ym9jdXBhY2nDs24gbm8gZGVtYW5kYW50ZScgPSBTdWJvY19ub19kZW1hbmQvUEVBKSANCkN1YWRyb18xLjFhIA0KYGBgDQoNClVuYSB2ZXogcXVlIGNhbGN1bGFtb3MgbGFzIHRhc2FzLCBwb2RlbW9zIGJvcnJhciBsb3Mgbml2ZWxlcyBjb24gdW4gX19zZWxlY3RfXw0KYGBge3J9DQpDdWFkcm9fMS4xYSA8LSBDdWFkcm9fMS4xYSAlPiUgDQogIHNlbGVjdCgtYygxOjgpKQ0KDQpDdWFkcm9fMS4xYQ0KYGBgDQoNCkNvbiBfX2dhdGhlcl9fIHBvZGVtb3MgZGFyIHZ1ZWx0YSBsYSB0YWJsYSBwYXJhIHF1ZSBxdWVkZSBjb21vIGVuIGxhIHB1YmxpY2FjacOzbg0KDQpgYGB7cn0NCg0KQ3VhZHJvXzEuMWEgPC0gQ3VhZHJvXzEuMWEgJT4lIA0KICBnYXRoZXIoVGFzYXMsIFZhbG9yLCAxOm5jb2woLikpDQoNCg0KQ3VhZHJvXzEuMWENCmBgYA0KDQpFbiBjYXNvIGRlIHF1ZXJlciBleHByZXNhciBsb3MgcmVzdWx0YWRvcyBjb21vIHBvcmNlbnRhamVzLCB1dGlsaXphbW9zIGxhIGZ1bmNpw7NuIF9fc3ByaW50Zl9fLiBQYXJhIGVsbG8gZGViZW1vcyB1dGlsaXphciBfX211dGF0ZV9fIHBhcmEgdHJhbnNmb3JtYXIgbGEgY29sdW1uYSBWYWxvci4NCmBgYHtyfQ0KQ3VhZHJvXzEuMWEgPC0gQ3VhZHJvXzEuMWEgJT4lIA0KICBtdXRhdGUoVmFsb3IgPSBzcHJpbnRmKCIlMS4xZiUlIiwgMTAwKlZhbG9yKSkNCg0KQ3VhZHJvXzEuMWENCmBgYA0KTsOzdGVzZSBxdWUgZW4gZXN0ZSBjYXNvLCBwYXJhIHBvZGVyIGHDsWFkaXIgZWwgJSwgbGEgZnVuY2nDs24gdHJhbnNmb3JtYSBhIGxhIHZhcmlhYmxlIGVuIHVuIENoYXJhY3RlciwgcG9yIGVuZGUgZGViZSB0ZW5lcnNlIGVuIGN1ZW50YSBxdWUgc2UgcGllcmRlIGxhIGluZm9ybWFjacOzbiBkZWwgbnVtZXJvIGNvbXBsZXRvLg0KDQojIyBDdWFkcm8gMS4yDQoNCkVuIGVzdGUgY2FzbywgcG9kZW1vcyB2ZXIgcXVlIHNpbXBsZW1lbnRlIGFncmVnYW5kbyBsYSBmdW5jacOzbiBfX2dyb3VwX2J5X18gcG9kZW1vcyByZXBsaWNhciBlbCBwcm9jZWRpbWllbnRvIHBhcmEgY2FkYSB1bm8gZGUgbG9zIGFnbG9tZXJhZG9zLiBZIGEgc3UgdmV6LCBwb2RlbW9zIHJlYWxpemFyIGVuIHVuIHNvbG8gcGFzbyBsb3MgYXJyZWdsb3MgcG9zdGVyaW9yZXMgc29icmUgbnVlc3RyYSB0YWJsYS4NCg0KYGBge3J9DQpDdWFkcm9fMS4yYSA8LSBJbmRpdmlkdWFsX3QxMTcgJT4lIA0KICBncm91cF9ieShBR0xPTUVSQURPKSAlPiUgDQogIHN1bW1hcmlzZShQb2JsYWNpb24gICAgICAgICA9IHN1bShQT05ERVJBKSwNCiAgICAgICAgICAgIE9jdXBhZG9zICAgICAgICAgID0gc3VtKFBPTkRFUkFbRVNUQURPID09IDFdKSwNCiAgICAgICAgICAgIERlc29jdXBhZG9zICAgICAgID0gc3VtKFBPTkRFUkFbRVNUQURPID09IDJdKSwNCiAgICAgICAgICAgIFBFQSAgICAgICAgICAgICAgID0gT2N1cGFkb3MgKyBEZXNvY3VwYWRvcywNCiAgICAgICAgICAgIE9jdXBhZG9zX2RlbWFuZCAgID0gc3VtKFBPTkRFUkFbRVNUQURPID09IDEgJiBQUDAzSiA9PSAxXSksDQogICAgICAgICAgICBTdWJvY19kZW1hbmRhbnRlICA9IHN1bShQT05ERVJBW0VTVEFETyA9PSAxICYgSU5URU5TSSA9PSAxICYgUFAwM0ogPT0gMV0pLA0KICAgICAgICAgICAgU3Vib2Nfbm9fZGVtYW5kICAgPSBzdW0oUE9OREVSQVtFU1RBRE8gPT0gMSAmIElOVEVOU0kgPT0gMSAmIFBQMDNKICVpbiUgYygyLCA5KV0pLA0KICAgICAgICAgICAgU3Vib2N1cGFkb3MgICAgICAgPSBTdWJvY19kZW1hbmRhbnRlICsgU3Vib2Nfbm9fZGVtYW5kLA0KICAgICAgICAgICAgJ1Rhc2EgQWN0aXZpZGFkJyAgICAgICAgICAgICAgICAgID0gUEVBL1BvYmxhY2lvbiwNCiAgICAgICAgICAgICdUYXNhIEVtcGxlbycgICAgICAgICAgICAgICAgICAgICA9IE9jdXBhZG9zL1BvYmxhY2lvbiwNCiAgICAgICAgICAgICdUYXNhIERlc29jdXBhY2lvbicgICAgICAgICAgICAgICA9IERlc29jdXBhZG9zL1BFQSwNCiAgICAgICAgICAgICdUYXNhIG9jdXBhZG9zIGRlbWFuZGFudGVzJyAgICAgICA9IE9jdXBhZG9zX2RlbWFuZC9QRUEsDQogICAgICAgICAgICAnVGFzYSBTdWJvY3VwYWNpw7NuJyAgICAgICAgICAgICAgID0gU3Vib2N1cGFkb3MvUEVBLA0KICAgICAgICAgICAgJ1Rhc2EgU3Vib2N1cGFjacOzbiBkZW1hbmRhbnRlJyAgICA9IFN1Ym9jX2RlbWFuZGFudGUvUEVBLA0KICAgICAgICAgICAgJ1Rhc2EgU3Vib2N1cGFjacOzbiBubyBkZW1hbmRhbnRlJyA9IFN1Ym9jX25vX2RlbWFuZC9QRUEpICU+JSANCiAgc2VsZWN0KC1jKDI6OSkpICU+JSANCiAgbGVmdF9qb2luKC4sQWdsb20pICU+JSANCiAgc2VsZWN0KE5vbV9BZ2xvLGV2ZXJ5dGhpbmcoLiksLUFHTE9NRVJBRE8pIA0KDQpDdWFkcm9fMS4yYQ0KYGBgDQoNCiMjIyMgRXhwb3J0YXIgcmVzdWx0YWRvcyBhICBFeGNlbA0KTGEgZnVuY2nDs24gX193cml0ZS54bHN4X18gbm9zIHBlcm1pdGUgZ3VhcmRhciBlbiB1biBFeGNlbCBsb3MgcmVzdWx0YWRvcyBkZSBudWVzdHJvIHByb2Nlc2FtaWVudG8gZGUgbGEgaW5mb3JtYWNpw7NuLiBFc3RhIGZ1bmNpw7NuIHJlcXVpZXJlIHF1ZSBlbCB0aXBvIGRlIERhdG9zIHNlYSB1biBEYXRhRnJhbWUuDQpgYGB7cn0NCmNsYXNzKEN1YWRyb18xLjJhKQ0KYGBgDQpDb21vIG5vcyBtdWVzdHJhIGxhIGZ1bmNpw7NuIF9fY2xhc3NfXywgbGEgZnVuY2lvbmVzIGRlbCAgX190aWR5dmVyc2VfXyBhbCByZWFsaXphciBsYXMgb3BlcmFjaW9uZXMgYWdydXBhZGFzLCB0cmFuc2Zvcm1hIGVsIHRpcG8gZGUgZGUgbG9zIGRhdG9zLiBQb3IgZW5kZSwgcGFyYSBndWFyZGFyIGxvcyByZXN1bHRhZG9zIGRlYmVtb3MgcmVjb252ZXJ0aXIgbnVlc3RyYSB0YWJsYSBlbiB1biBEYXRhRnJhbWUgY29uIGxhIGZ1bmNpw7NuIF9fYXMuZGF0YS5mcmFtZV9fLiAgICANCg0KX193cml0ZS54bHN4X18gZW50cmUgc3VzIHBhcsOhbWV0cm9zIHJlcXVpZXJlIGVzcGVjaWZpY2FyOg0KDQotIEVsIG9iamV0byBhIGd1YXJkYXINCi0gTGEgcnV0YSB5IG5vbWJyZSBkZWwgYXJjaGl2byBxdWUgc2Vyw6EgZ3VhcmRhZG8NCg0KRGUgbWFuZXJhIG9wY2lvbmFsIHB1ZWRlIHRhbWJpw6luIGVzcGVjaWZpY2Fyc2U6DQoNCi0gRWwgbm9tYnJlIGRlIGxhIEhvamENCi0gU2kgc2UgZGVzZWEgcGlzYXIgKGFwcGVuZCA9IEZBTFNFKSBvIGluY29ycG9yYXIgKGFwcGVuZCA9IFRSVUUpIGFyY2hpdm9zIHF1ZSB0ZW5nYW4gZWwgbWlzbW8gbm9tYnJlDQotIFNpIGRlc2VhbW9zIHF1ZSBmaWd1cmVuIG8gbm8gbG9zIG7Dum1lcm9zIGRlIGZpbGFzIGVuIG51ZXN0cmEgdGFibGEgICAgICAgICAgIA0KDQpgYGB7ciBldmFsPUZBTFNFfQ0Kd3JpdGUueGxzeChhcy5kYXRhLmZyYW1lKEN1YWRyb18xLjFhKSwgcGFzdGUwKHJlc3VsdGFkb3MuZGlyLCJJbmZvcm1lIE1lcmNhZG8gZGUgVHJhYmFqby54bHN4Iiksc2hlZXROYW1lID0gIkN1YWRybyAxLjEiLCBhcHBlbmQgPSBGQUxTRSwgcm93Lm5hbWVzID0gRkFMU0UpDQoNCndyaXRlLnhsc3goYXMuZGF0YS5mcmFtZShDdWFkcm9fMS4yYSksIHBhc3RlMChyZXN1bHRhZG9zLmRpciwiSW5mb3JtZSBNZXJjYWRvIGRlIFRyYWJham8ueGxzeCIpLHNoZWV0TmFtZSA9ICJDdWFkcm8gMS4yIiwgYXBwZW5kID0gVFJVRSwgcm93Lm5hbWVzID0gRkFMU0UpDQoNCmBgYA0KDQoNCiMgRWplcmNpY2lvcyBwYXJhIHByYWN0aWNhcg0KDQotIExldmFudGFyIGxhIMO6bHRpbWEgYmFzZSBpbmRpdmlkdWFsIGRlIEVQSA0KLSBDcmVhciB1biB2ZWN0b3IgbGxhbWFkbyBfX1ZhcmlhYmxlc19fIHF1ZSBjb250ZW5nYSBsb3Mgbm9tYnJlcyBkZSBsYXMgc2lndWllbnRlcyB2YXJpYWJsZXMgZGUgaW50ZXLDqXMgcGFyYSByZWFsaXphciBhbGd1bm9zIGVqZXJjaWNpb3M6DQogICAgICAtIEVkYWQsIFNleG8sIEluZ3Jlc28gZGUgbGEgb2N1cGFjacOzbiBwcmluY2lwYWwsIENhdGVnb3LDrWEgb2N1cGFjaW9uYWwsIEVTVEFETywgUE9OREVSQSB5IFBPTkRJSA0KLSBBY290YXIgbGEgQmFzZSDDum5pY2FtZW50ZSBhIGxhcyB2YXJpYWJsZXMgZGUgaW50ZXLDqXMsIHV0aWxpemFuZG8gZWwgdmVjdG9yIF9fVmFyaWFibGVzX18gDQoNCi0gQ2FsY3VsYXIgbGFzIHRhc2FzIGRlIGFjdGl2aWRhZCwgZW1wbGVvIHkgZGVzZW1wbGVvIHNlZ8O6biBzZXhvLCBwYXJhIGrDs3ZlbmVzIGVudHJlIDE4IHkgMzUgYcOxb3MNCi0gQ2FsY3VsYXIgZWwgc2FsYXJpbyBwcm9tZWRpbyBwb3Igc2V4bywgcGFyYSBkb3MgZ3J1cG9zIGRlIGVkYWQ6IDE4IGEgMzUgYcOxb3MgeSAzNiBhIDcwIGHDsW9zLiAoUmVjb3JkYXRvcmlvOiBMYSBiYXNlIGRlYmUgZmlsdHJhcnNlIHBhcmEgY29udGVuZXIgw7puaWNhbWVudGUgT0NVUEFET1MgQVNBTEFSSUFET1MpDQotIEdyYWJhciBsb3MgcmVzdWx0YWRvcyBlbiB1biBleGNlbCAoUHJldmlhbWVudGUgY3JlYW5kbyB1biBvYmpldG8gcXVlIGNvbnRlbmdhIGVsIGRpcmVjdG9yaW8gYSB1dGlsaXphcikNCi0gUmVwbGljYXIgZWwgY8OhbGN1bG8gZGUgbGFzIHRhc2FzIGFudGVyaW9yZXMgcGFyYSBkaXN0aW50b3MgdHJpbWVzdHJlcyxsZXZhbnRhbmRvIGxhcyBiYXNlcyBkZXNkZSBlbCBzZWd1bmRvIHRyaW1lc3RyZSAyMDE2IGhhc3RhIGxhIMO6bHRpbWEuDQogICAgLSBUaXBzOiBqdW50YXIgbGFzIGJhc2VzIGNvbiBlbCBjb21hbmRvIGBgYGJpbmRfcm93cygpYGBgDQogICAgLSBQcm9iYXIgY29uIGBgYGdhdGhlcigpYGBgIHkgYGBgc3ByZWFkKClgYGAgY29tbyBxdWVkYW4gbWVqb3IgbG9zIHJlc3VsdGFkb3MNCg==