Hemos visto que con el tidytverse podemos manipular datos. En concreto, hemos aprendido las siguiente funciones que alteran tablas de datos:
- summarise: generar resúmenes estadísticos a medida.
- count: construir conteos o tablas de frecuencia.
- arrange: ordenar los datos ascendente o descendentemente a partir de una variable en específico.
- mutate crear nuevas variables a partir de las existentes o transformar alguna ya existente.
- group_by (y su cierre ungroup): consolidad en grupos a los datos.
- filter: reducir la tabla mediante una operación lógica que aplique a una variable.
- select: reducir la tabla mediante la definición de cuáles variables se deben considerar.
El desafío ahora, radica en utilizar estas funciones (y otras que iremos aprendiendo en el camino) para preprocesar una tabla real de datos.
Para esto, vamos primero a cargar las librerías tidyverse y lubridate.
library(tidyverse)
library(lubridate)
Una vez cargadas, vamos a importar en un data frame llamado df_ventas2020 los siguientes datos que se encuentran en la dirección url: https://raw.githubusercontent.com/jsaraujo5081/clasesBI/main/ventas_fabrica.csv. Vale mensionar que estos datos corresponden a las ventas mensuales en unidades y en valor de productos fabricados en una planta local.
df_ventas2020 <- read_csv(file = "https://raw.githubusercontent.com/jsaraujo5081/clasesBI/main/ventas_fabrica.csv",
col_names = T)
-- Column specification ---------------------------------------------------------
cols(
Nombre_Articulo = col_character(),
Nombre_Producto = col_character(),
Nombre_Color = col_character(),
PeriodoMes = col_double(),
Tipo = col_character(),
Unidades = col_double(),
Valor_USD = col_double()
)
Démosle una mirada a este objeto con glimpse y View.
glimpse(df_ventas2020)
Rows: 3,103
Columns: 7
$ Nombre_Articulo <chr> "Fuentes", "Fuentes", "Fuentes", "Fuentes", "Fuentes", ~
$ Nombre_Producto <chr> "FUENTE REGGIO BLANCO", "FUENTE REGGIO BLANCO", "FUENTE~
$ Nombre_Color <chr> "BLANCO", "BLANCO", "BLANCO", "BLANCO", "BLANCO", "BLAN~
$ PeriodoMes <dbl> 202001, 202002, 202003, 202004, 202005, 202006, 202007,~
$ Tipo <chr> "Continuo", "Continuo", "Continuo", "Continuo", "Contin~
$ Unidades <dbl> 551, 360, 177, 19, 123, 165, 456, 333, 404, 312, 287, 3~
$ Valor_USD <dbl> 20290.03, 12814.27, 6872.47, 431.86, 5217.57, 6875.70, ~
View(df_ventas2020)
Empecemos entonces con el preprocesamiento.
1. Ajustar las clases de las variables
Una de las primeras cosas que notamos en nuestro data frame, es que algunas variables no están en las clases adecuadas. Por ejemplo,
- Nombre_Articulo debería ser un factor, ya que si bien es un texto, su rango de opciones es acotado a 14, y la cantidad de datos por cada opción es bastante similar. Para ver esto, construyamos una tabla de frecuencias de esta variable:
df_ventas2020 %>%
count(Nombre_Articulo)
- Misma situación se evidencia con Tipo, que solamente toma dos opciones y de hecho es un buleano.
df_ventas2020 %>%
count(Tipo)
- Nombre_Color igualmente podría considerarse un factor, pero vemos que las opciones que puede tomar son muchas (17) y la cantidad de casos por opción es desbalanceada, así que por ahora lo vamos a dejar a un lado.
df_ventas2020 %>%
count(Nombre_Color)
- Unidades es numérico, pero lo más lógico es que sea un entero, puesto que no puede tomar valores decimales bajo ningún concepto.
Visto esto, usemos mutate para construir un nuevo objeto df_ventas2020_01 a partir de nuestra tabla, de modo que cambiemos la clase de las 3 variables definidas. PISTA: Utilice además las funciones as.factor y as.integer dentro del mutate.
df_ventas2020_01 <- df_ventas2020 %>%
mutate(Nombre_Articulo = as.factor(Nombre_Articulo)) %>%
mutate(Tipo = as.factor(Tipo)) %>%
mutate(Unidades = as.integer(Unidades))
Veamos con glimpse si logramos nuestro objetivo:
glimpse(df_ventas2020_01)
Rows: 3,103
Columns: 7
$ Nombre_Articulo <fct> Fuentes, Fuentes, Fuentes, Fuentes, Fuentes, Fuentes, F~
$ Nombre_Producto <chr> "FUENTE REGGIO BLANCO", "FUENTE REGGIO BLANCO", "FUENTE~
$ Nombre_Color <chr> "BLANCO", "BLANCO", "BLANCO", "BLANCO", "BLANCO", "BLAN~
$ PeriodoMes <dbl> 202001, 202002, 202003, 202004, 202005, 202006, 202007,~
$ Tipo <fct> Continuo, Continuo, Continuo, Continuo, Continuo, Conti~
$ Unidades <int> 551, 360, 177, 19, 123, 165, 456, 333, 404, 312, 287, 3~
$ Valor_USD <dbl> 20290.03, 12814.27, 6872.47, 431.86, 5217.57, 6875.70, ~
2. Tratamiento de valores perdidos y outliers
2.1 Valores perdidos
Algo muy frecuente que se nos presenta al momento de trabajar con datos reales, es la existencia de valores perdidos (comúnmente representados por los caracteres “NA”, “?” o “null”). Como analista de datos, conviene darles cierta atención a estos casos, y las dos formas más utilizas son:
- Eliminación de valores perdidos: esta alternativa es la más simple y directa pero hay que tener cuidado de que esto no implique perder información valiosa posteriormente. Para hacerlo la forma más sencilla es usando filter apoyado de otra función como is.na. En nuestro ejemplo, notemos que tanto Unidades como Valo_USDr poseen valores perdidos, por lo que creemos un nuevo objeto df_ventas2020_02 quitándolos. PISTA: recuerde que la negación de una operación lógica se expresa antecediendo el caracter “!”.
df_ventas2020_02 <- df_ventas2020_01 %>%
filter(!is.na(Unidades)) %>%
filter(!is.na(Valor_USD))
View(df_ventas2020_02)
- Reemplazo de valores perdidos: esta alternativa puede resultar más compleja, pero trae como beneficio que no se pierde potencial información de valor. Consiste en sustituir los valores perdidos por un valor referencial (comúnmente la mediana). En nuestro ejemplo, vamos a reemplazar por la mediana los valores perdidos en Unidades y dejaremos Valor_USD para tratarlo después. Para esto, vamos a agrupar nuestra tabla por Nombre_Producto, y sustituiremos los valores perdidos en cada grupo. REcuerde usar las funciones group_by, ungroup y mutate. Cree un nuevo objeto llamado df_ventas2020_03. PISTA: Empleemos otra función muy util llamada ifelse que permite evaluar condicionalmente.
df_ventas2020_03 <- df_ventas2020_01 %>%
group_by(Nombre_Producto) %>%
mutate(Unidades = ifelse(is.na(Unidades),
as.integer(median(Unidades, na.rm = TRUE)),
Unidades)) %>%
ungroup()
View(df_ventas2020_03)
2.2 Outliers (Valores atípicos)
Otra situación bastante común que se presenta es encontrarse con valores “extraños” que claramente muestran un comportamiento distinto al esperado. El 2020 fue un año marcado por casos atípicos a nivel de datos, pues en los meses de marzo, abril y mayo, la pandemia del COVID-19 ocasionó variaciones nunca antes vistas, y nuestros datos de ejemplo no son la excepción.
Para tratar estos valores, se pueden emplear los mismos métodos que en el caso de perdidos. Esto es, se los puede eliminar o bien se los puede cambiar por un valor referencial como la mediana.
Por esta ocasión, no vamos a cambiar o eliminar nada de nuestro ejemplo, pero solamente veamos que tan atípicos son nuestros datos en estos meses haciendo un resumen estadístico por PeriodoMes con la suma de Unidades y Valor_USD. Recuerde usar summarise.
df_ventas2020_03 %>%
group_by(PeriodoMes) %>%
summarise(Unidades = sum(Unidades, na.rm = TRUE),
Valor_USD = sum(Valor_USD, na.rm = TRUE))
Otros atípicos bastante comunes que se encuentran, son aquellos que se originan por un mal ingreso de los datos. En nuestro ejemplo, notemos que existen algunos registros con Unidades = 0 pero con valor de venta positivo. Esto claramente no tiene sentido. Arreglemos esto, cambiando los Valor_USD a 0 siempre que Unidades sean 0. Creemos el objeto df_ventas2020_04.
df_ventas2020_04 <- df_ventas2020_03 %>%
mutate(Valor_USD = ifelse(Unidades == 0,
0,
Valor_USD))
3. Tratamiento de variables no numéricas
Algunas variables no numéricas (texto o factores) pueden requerir de cierto tratamiento a fin de facilitar su análisis subsiguiente. Esto es extremadamente importante al momento de utilizar los datos en modelos de analítica avanzada, como los asociados a aprendizaje computacional (machine learning) puesto que la computadora no es capaz de contextualizar palabras coomo una persona.
En este sentido, muchos de los métodos de este apartado trascienden a lo planteado en este curso. sin embargo, en el aula virtual se ha colocado un breve video que explica el método más común para tratar variables no numéricas: el One-Hot Encoding por si les resulta de interés.
Por ahora, veremos un caso específico y muy usado en inteligencia de negocio que se denomina el Balanceo. Lo que buscamos aquí es reducir o limitar el número de opciones que una variable de tipo factor o texto pueda presentar, y como ejemplo haremos este tratamiento en Nombre_Color.
Como ya lo mencionamos, esta variable tiene 17 opciones diferentes, las cuales no están balanceadas. Para ilustrar esto, el color BLANCO presenta 1,404 casos, mientras que el NARANJA apenas 9.
Vamos entonces a CREAR una nueva la variable Grupo_Color de modo que si uno de los colores tiene menos de 100 observaciones, pertenecerá al grupo “OTROS”, todos los demás casos mantienen su nombre original. Utilizaremos nuevamente las funciones group_by, ungroup, mutate, e ifelse. Llamaremos al nuevo objeto que creemos df_ventas2020_05.
df_ventas2020_05 <- df_ventas2020_04 %>%
#Primero generemos un campo auxiliar que nos muestre el número de casos para cada color
group_by(Nombre_Color) %>%
mutate(Casos = n()) %>%
ungroup() %>%
#Luego, creemos la variable Grupo_Color poniendo el valor de OTROS cuando casos sea menor que 100, y el mismo nombre del color cuando esto no aplique
mutate(Grupo_Color = ifelse(Casos < 100,
"OTROS",
Nombre_Color))
Veamos lo que conseguimos:
View(df_ventas2020_04)
Construyamos una tabla de frecuencia de nuestra nueva variable para ver si en algo mejoramos el balanceo y la cantidad de opciones:
df_ventas2020_05 %>%
count(Grupo_Color)
4. Selección (o ingeniería) de atributos
Como último punto de nuestro preprocesamiento, definir cuáles son las variables que queremos mostrar en nuestra tabla de datos previo a empezar su análisis exhaustivo. Esto implica las dos cosas referidas a continuación.
4.1 Creación de nuevas variables
Para dar una mayor robustez a nuestro análisis, siempre es bueno contar con todas las variables que nos vayan a ser de utilidad. Para esto, por un lado creemos variables que sean potencialmente valiosas.
En nuestro ejemplo, notemos que podría ser útil contar con las siguientes variables:
- Mes: el campo PeriodoMes termina siendo redundante en vista que sabemos que todos los datos corresponden al Período o Año 2020.
- Fecha: sería adecuado tener un campo adicional que esté en formato fecha puesto que estamos viendo, de cierta forma, una evolución en el tiempo.
- Tarifa_Prom: siempre que estudiamos ventas, es útil saber la tarifa promedio a la cual se terminó vendiendo un producto, y esto e sfacil calcularlo con \[ Valor_{USD} / Unidades \]
Entonces, creemos el objeto df_ventas2020_06 que contenga estos nuevos atributos usando la función mutate. PISTA: Utilice la funcion str_remove para quitar el 2020 de PeriodoMes; ISOdate para obtener una fecha.
df_ventas2020_06 <- df_ventas2020_05 %>%
mutate(Mes = as.integer(str_remove(PeriodoMes, "2020"))) %>%
mutate(Fecha = ISOdate(year = 2020, month = Mes, day = 1)) %>%
mutate(Tarifa_Prom = Valor_USD/Unidades)
Veamos lo conseguido:
View(df_ventas2020_06)
Notemos que Tarifa_Prom tiene valores perdidos arrastrados por Valor_USD, y otros derivados de la división por 0. Ya lo corregiremos más adelante.
4.2 Eliminación de variables
Así como creamos variables potencialmente útiles para nuestro análisis posterior, podemos también eliminar aquellas variables que carezcan de sentido o veamos de forma anticipada que no nos serán de utilidad.
En nuestro ejemplo, las variables PeriodoMes y Casos no tienen mucho sentido. La primera porque toda la información que puede entregar ya está contenida en las nuevas variables que creamos; y la segunda, porque su existencia es puramente auxiliar. Por consiguiente vemos que su eliminación no representa ningún riesgo para el análisis.
Para eliminarlas, utilicemos la función select, creando el objeto df_ventas2020_07.
df_ventas2020_07 <- df_ventas2020_06 %>%
select(-PeriodoMes,
-Casos)
veamos el resultado final:
View(df_ventas2020_07)
OJO: Para eliminar una variable, tenemos que tener muy clara la razón detrás de nuestra decisión. No se eliminan variables sin una explicación lógica y defendible.
5. Tarea Individual
Usando lo aprendido en la clase, complete su script de forma que incluya lo siguiente, además de lo hecho con el profesor:
- Anotaciones o comentarios que faciliten la lectura del mismo.
- Transformación a factor de la variable Grupo_Color.
- Tratamiento a los valores perdidos de Tarifa_Prom
- Tratamiento a los valores perdidos de Valor_USD, usando para esto la Tarifa_Prom ya procesada.
- Transformación de la clase de Mes a factor.
- Creación de un nuevo atributo potencialmente útil en el análisis posterior.
Este script lo deben subir hasta la fecha indicada en el Aula Virtual.
LS0tDQp0aXRsZTogIk1hbmlwdWxhY2nDs24gZGUgZGF0b3MgSUkgLSBQcmVwcm9jZXNhbWllbnRvIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KSGVtb3MgdmlzdG8gcXVlIGNvbiBlbCAqKnRpZHl0dmVyc2UqKiBwb2RlbW9zIG1hbmlwdWxhciBkYXRvcy4gRW4gY29uY3JldG8sIGhlbW9zIGFwcmVuZGlkbyBsYXMgc2lndWllbnRlIGZ1bmNpb25lcyBxdWUgYWx0ZXJhbiB0YWJsYXMgZGUgZGF0b3M6DQoNCiogKnN1bW1hcmlzZSo6IGdlbmVyYXIgcmVzw7ptZW5lcyBlc3RhZMOtc3RpY29zIGEgbWVkaWRhLg0KKiAqY291bnQqOiBjb25zdHJ1aXIgY29udGVvcyBvIHRhYmxhcyBkZSBmcmVjdWVuY2lhLg0KKiAqYXJyYW5nZSo6IG9yZGVuYXIgbG9zIGRhdG9zIGFzY2VuZGVudGUgbyBkZXNjZW5kZW50ZW1lbnRlIGEgcGFydGlyIGRlIHVuYSB2YXJpYWJsZSBlbiBlc3BlY8OtZmljby4NCiogKm11dGF0ZSogY3JlYXIgbnVldmFzIHZhcmlhYmxlcyBhIHBhcnRpciBkZSBsYXMgZXhpc3RlbnRlcyBvIHRyYW5zZm9ybWFyIGFsZ3VuYSB5YSBleGlzdGVudGUuDQoqICpncm91cF9ieSogKHkgc3UgY2llcnJlICp1bmdyb3VwKik6IGNvbnNvbGlkYWQgZW4gZ3J1cG9zIGEgbG9zIGRhdG9zLg0KKiAqZmlsdGVyKjogcmVkdWNpciBsYSB0YWJsYSBtZWRpYW50ZSB1bmEgb3BlcmFjacOzbiBsw7NnaWNhIHF1ZSBhcGxpcXVlIGEgdW5hIHZhcmlhYmxlLg0KKiAqc2VsZWN0KjogcmVkdWNpciBsYSB0YWJsYSBtZWRpYW50ZSBsYSBkZWZpbmljacOzbiBkZSBjdcOhbGVzIHZhcmlhYmxlcyBzZSBkZWJlbiBjb25zaWRlcmFyLg0KDQpFbCBkZXNhZsOtbyBhaG9yYSwgcmFkaWNhIGVuIHV0aWxpemFyIGVzdGFzIGZ1bmNpb25lcyAoeSBvdHJhcyBxdWUgaXJlbW9zIGFwcmVuZGllbmRvIGVuIGVsIGNhbWlubykgcGFyYSBwcmVwcm9jZXNhciB1bmEgdGFibGEgcmVhbCBkZSBkYXRvcy4NCg0KUGFyYSBlc3RvLCB2YW1vcyBwcmltZXJvIGEgY2FyZ2FyIGxhcyBsaWJyZXLDrWFzICoqdGlkeXZlcnNlKiogeSAqKmx1YnJpZGF0ZSoqLg0KYGBge3J9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkobHVicmlkYXRlKQ0KYGBgDQoNClVuYSB2ZXogY2FyZ2FkYXMsIHZhbW9zIGEgaW1wb3J0YXIgZW4gdW4gZGF0YSBmcmFtZSBsbGFtYWRvICpkZl92ZW50YXMyMDIwKiBsb3Mgc2lndWllbnRlcyBkYXRvcyBxdWUgc2UgZW5jdWVudHJhbiBlbiBsYSBkaXJlY2Npw7NuIHVybDogaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL2pzYXJhdWpvNTA4MS9jbGFzZXNCSS9tYWluL3ZlbnRhc19mYWJyaWNhLmNzdi4NClZhbGUgbWVuc2lvbmFyIHF1ZSBlc3RvcyBkYXRvcyBjb3JyZXNwb25kZW4gYSBsYXMgdmVudGFzIG1lbnN1YWxlcyBlbiB1bmlkYWRlcyB5IGVuIHZhbG9yIGRlIHByb2R1Y3RvcyBmYWJyaWNhZG9zIGVuIHVuYSBwbGFudGEgbG9jYWwuDQoNCmBgYHtyfQ0KZGZfdmVudGFzMjAyMCA8LSByZWFkX2NzdihmaWxlID0gImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9qc2FyYXVqbzUwODEvY2xhc2VzQkkvbWFpbi92ZW50YXNfZmFicmljYS5jc3YiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xfbmFtZXMgPSBUKSANCmBgYA0KRMOpbW9zbGUgdW5hIG1pcmFkYSBhIGVzdGUgb2JqZXRvIGNvbiAqZ2xpbXBzZSogeSAqVmlldyouDQpgYGB7cn0NCmdsaW1wc2UoZGZfdmVudGFzMjAyMCkNClZpZXcoZGZfdmVudGFzMjAyMCkNCmBgYA0KRW1wZWNlbW9zIGVudG9uY2VzIGNvbiBlbCBwcmVwcm9jZXNhbWllbnRvLg0KDQojIyAxLiBBanVzdGFyIGxhcyBjbGFzZXMgZGUgbGFzIHZhcmlhYmxlcw0KDQpVbmEgZGUgbGFzIHByaW1lcmFzIGNvc2FzIHF1ZSBub3RhbW9zIGVuIG51ZXN0cm8gZGF0YSBmcmFtZSwgZXMgcXVlIGFsZ3VuYXMgdmFyaWFibGVzIG5vIGVzdMOhbiBlbiBsYXMgY2xhc2VzIGFkZWN1YWRhcy4gUG9yIGVqZW1wbG8sIA0KDQoqICpOb21icmVfQXJ0aWN1bG8qIGRlYmVyw61hIHNlciB1biBmYWN0b3IsIHlhIHF1ZSBzaSBiaWVuIGVzIHVuIHRleHRvLCBzdSByYW5nbyBkZSBvcGNpb25lcyBlcyBhY290YWRvIGEgMTQsIHkgbGEgY2FudGlkYWQgZGUgZGF0b3MgcG9yIGNhZGEgb3BjacOzbiBlcyBiYXN0YW50ZSBzaW1pbGFyLiBQYXJhIHZlciBlc3RvLCBjb25zdHJ1eWFtb3MgdW5hIHRhYmxhIGRlIGZyZWN1ZW5jaWFzIGRlIGVzdGEgdmFyaWFibGU6DQoNCmBgYHtyfQ0KZGZfdmVudGFzMjAyMCAlPiUNCiAgY291bnQoTm9tYnJlX0FydGljdWxvKQ0KYGBgDQoNCg0KKiBNaXNtYSBzaXR1YWNpw7NuIHNlIGV2aWRlbmNpYSBjb24gKlRpcG8qLCBxdWUgc29sYW1lbnRlIHRvbWEgZG9zIG9wY2lvbmVzIHkgZGUgaGVjaG8gZXMgdW4gYnVsZWFuby4NCg0KYGBge3J9DQpkZl92ZW50YXMyMDIwICU+JQ0KICBjb3VudChUaXBvKQ0KYGBgDQoNCiogKk5vbWJyZV9Db2xvciogaWd1YWxtZW50ZSBwb2Ryw61hIGNvbnNpZGVyYXJzZSB1biBmYWN0b3IsIHBlcm8gdmVtb3MgcXVlIGxhcyBvcGNpb25lcyBxdWUgcHVlZGUgdG9tYXIgc29uIG11Y2hhcyAoMTcpIHkgbGEgY2FudGlkYWQgZGUgY2Fzb3MgcG9yIG9wY2nDs24gZXMgZGVzYmFsYW5jZWFkYSwgYXPDrSBxdWUgcG9yIGFob3JhIGxvIHZhbW9zIGEgZGVqYXIgYSB1biBsYWRvLg0KDQpgYGB7cn0NCmRmX3ZlbnRhczIwMjAgJT4lDQogIGNvdW50KE5vbWJyZV9Db2xvcikNCmBgYA0KDQoqICpVbmlkYWRlcyogZXMgbnVtw6lyaWNvLCBwZXJvIGxvIG3DoXMgbMOzZ2ljbyBlcyBxdWUgc2VhIHVuIGVudGVybywgcHVlc3RvIHF1ZSBubyBwdWVkZSB0b21hciB2YWxvcmVzIGRlY2ltYWxlcyBiYWpvIG5pbmfDum4gY29uY2VwdG8uDQoNClZpc3RvIGVzdG8sIHVzZW1vcyAqbXV0YXRlKiBwYXJhIGNvbnN0cnVpciB1biBudWV2byBvYmpldG8gKmRmX3ZlbnRhczIwMjBfMDEqIGEgcGFydGlyIGRlIG51ZXN0cmEgdGFibGEsIGRlIG1vZG8gcXVlIGNhbWJpZW1vcyBsYSBjbGFzZSBkZSBsYXMgMyB2YXJpYWJsZXMgZGVmaW5pZGFzLg0KUElTVEE6IFV0aWxpY2UgYWRlbcOhcyBsYXMgZnVuY2lvbmVzICphcy5mYWN0b3IqIHkgKmFzLmludGVnZXIqIGRlbnRybyBkZWwgKm11dGF0ZSouDQoNCmBgYHtyfQ0KZGZfdmVudGFzMjAyMF8wMSA8LSBkZl92ZW50YXMyMDIwICU+JQ0KICBtdXRhdGUoTm9tYnJlX0FydGljdWxvID0gYXMuZmFjdG9yKE5vbWJyZV9BcnRpY3VsbykpICU+JQ0KICBtdXRhdGUoVGlwbyA9IGFzLmZhY3RvcihUaXBvKSkgJT4lDQogIG11dGF0ZShVbmlkYWRlcyA9IGFzLmludGVnZXIoVW5pZGFkZXMpKQ0KYGBgDQoNClZlYW1vcyBjb24gZ2xpbXBzZSBzaSBsb2dyYW1vcyBudWVzdHJvIG9iamV0aXZvOg0KYGBge3J9DQpnbGltcHNlKGRmX3ZlbnRhczIwMjBfMDEpDQpgYGANCiMjIDIuIFRyYXRhbWllbnRvIGRlIHZhbG9yZXMgcGVyZGlkb3MgeSBvdXRsaWVycw0KDQojIyMgMi4xIFZhbG9yZXMgcGVyZGlkb3MNCg0KQWxnbyBtdXkgZnJlY3VlbnRlIHF1ZSBzZSBub3MgcHJlc2VudGEgYWwgbW9tZW50byBkZSB0cmFiYWphciBjb24gZGF0b3MgcmVhbGVzLCBlcyBsYSBleGlzdGVuY2lhIGRlIHZhbG9yZXMgcGVyZGlkb3MgKGNvbcO6bm1lbnRlIHJlcHJlc2VudGFkb3MgcG9yIGxvcyBjYXJhY3RlcmVzICJOQSIsICI/IiBvICJudWxsIikuIENvbW8gYW5hbGlzdGEgZGUgZGF0b3MsIGNvbnZpZW5lIGRhcmxlcyBjaWVydGEgYXRlbmNpw7NuIGEgZXN0b3MgY2Fzb3MsIHkgbGFzIGRvcyBmb3JtYXMgbcOhcyB1dGlsaXphcyBzb246DQoNCiogRWxpbWluYWNpw7NuIGRlIHZhbG9yZXMgcGVyZGlkb3M6IGVzdGEgYWx0ZXJuYXRpdmEgZXMgbGEgbcOhcyBzaW1wbGUgeSBkaXJlY3RhIHBlcm8gaGF5IHF1ZSB0ZW5lciBjdWlkYWRvIGRlIHF1ZSBlc3RvIG5vIGltcGxpcXVlIHBlcmRlciBpbmZvcm1hY2nDs24gdmFsaW9zYSBwb3N0ZXJpb3JtZW50ZS4gUGFyYSBoYWNlcmxvIGxhIGZvcm1hIG3DoXMgc2VuY2lsbGEgZXMgdXNhbmRvICpmaWx0ZXIqIGFwb3lhZG8gZGUgb3RyYSBmdW5jacOzbiBjb21vICppcy5uYSouIEVuIG51ZXN0cm8gZWplbXBsbywgbm90ZW1vcyBxdWUgdGFudG8gKlVuaWRhZGVzKiBjb21vICpWYWxvX1VTRHIqIHBvc2VlbiB2YWxvcmVzIHBlcmRpZG9zLCBwb3IgbG8gcXVlIGNyZWVtb3MgdW4gbnVldm8gb2JqZXRvICpkZl92ZW50YXMyMDIwXzAyKiBxdWl0w6FuZG9sb3MuIFBJU1RBOiByZWN1ZXJkZSBxdWUgbGEgbmVnYWNpw7NuIGRlIHVuYSBvcGVyYWNpw7NuIGzDs2dpY2Egc2UgZXhwcmVzYSBhbnRlY2VkaWVuZG8gZWwgY2FyYWN0ZXIgIiEiLg0KDQpgYGB7cn0NCmRmX3ZlbnRhczIwMjBfMDIgPC0gZGZfdmVudGFzMjAyMF8wMSAlPiUNCiAgZmlsdGVyKCFpcy5uYShVbmlkYWRlcykpICU+JQ0KICBmaWx0ZXIoIWlzLm5hKFZhbG9yX1VTRCkpDQpgYGANCg0KYGBge3J9DQpWaWV3KGRmX3ZlbnRhczIwMjBfMDIpDQpgYGANCg0KKiBSZWVtcGxhem8gZGUgdmFsb3JlcyBwZXJkaWRvczogZXN0YSBhbHRlcm5hdGl2YSBwdWVkZSByZXN1bHRhciBtw6FzIGNvbXBsZWphLCBwZXJvIHRyYWUgY29tbyBiZW5lZmljaW8gcXVlIG5vIHNlIHBpZXJkZSBwb3RlbmNpYWwgaW5mb3JtYWNpw7NuIGRlIHZhbG9yLiBDb25zaXN0ZSBlbiBzdXN0aXR1aXIgbG9zIHZhbG9yZXMgcGVyZGlkb3MgcG9yIHVuIHZhbG9yIHJlZmVyZW5jaWFsIChjb23Dum5tZW50ZSBsYSBtZWRpYW5hKS4gRW4gbnVlc3RybyBlamVtcGxvLCB2YW1vcyBhIHJlZW1wbGF6YXIgcG9yIGxhIG1lZGlhbmEgbG9zIHZhbG9yZXMgcGVyZGlkb3MgZW4gKlVuaWRhZGVzKiB5IGRlamFyZW1vcyAqVmFsb3JfVVNEKiBwYXJhIHRyYXRhcmxvIGRlc3B1w6lzLiBQYXJhIGVzdG8sIHZhbW9zIGEgYWdydXBhciBudWVzdHJhIHRhYmxhIHBvciAqTm9tYnJlX1Byb2R1Y3RvKiwgeSBzdXN0aXR1aXJlbW9zIGxvcyB2YWxvcmVzIHBlcmRpZG9zIGVuIGNhZGEgZ3J1cG8uIFJFY3VlcmRlIHVzYXIgbGFzIGZ1bmNpb25lcyAqZ3JvdXBfYnkqLCAqdW5ncm91cCogeSAqbXV0YXRlKi4gQ3JlZSB1biBudWV2byBvYmpldG8gbGxhbWFkbyAqZGZfdmVudGFzMjAyMF8wMyouIFBJU1RBOiBFbXBsZWVtb3Mgb3RyYSBmdW5jacOzbiBtdXkgdXRpbCBsbGFtYWRhICppZmVsc2UqIHF1ZSBwZXJtaXRlIGV2YWx1YXIgY29uZGljaW9uYWxtZW50ZS4NCg0KYGBge3J9DQpkZl92ZW50YXMyMDIwXzAzIDwtIGRmX3ZlbnRhczIwMjBfMDEgJT4lDQogIGdyb3VwX2J5KE5vbWJyZV9Qcm9kdWN0bykgJT4lDQogIG11dGF0ZShVbmlkYWRlcyA9IGlmZWxzZShpcy5uYShVbmlkYWRlcyksDQogICAgICAgICAgICAgICAgICAgICAgICAgICBhcy5pbnRlZ2VyKG1lZGlhbihVbmlkYWRlcywgbmEucm0gPSBUUlVFKSksDQogICAgICAgICAgICAgICAgICAgICAgICAgICBVbmlkYWRlcykpICU+JQ0KICB1bmdyb3VwKCkNCmBgYA0KYGBge3J9DQpWaWV3KGRmX3ZlbnRhczIwMjBfMDMpDQpgYGANCg0KIyMjIDIuMiBPdXRsaWVycyAoVmFsb3JlcyBhdMOtcGljb3MpDQoNCk90cmEgc2l0dWFjacOzbiBiYXN0YW50ZSBjb23Dum4gcXVlIHNlIHByZXNlbnRhIGVzIGVuY29udHJhcnNlIGNvbiB2YWxvcmVzICJleHRyYcOxb3MiIHF1ZSBjbGFyYW1lbnRlIG11ZXN0cmFuIHVuIGNvbXBvcnRhbWllbnRvIGRpc3RpbnRvIGFsIGVzcGVyYWRvLiBFbCAyMDIwIGZ1ZSB1biBhw7FvIG1hcmNhZG8gcG9yIGNhc29zIGF0w61waWNvcyBhIG5pdmVsIGRlIGRhdG9zLCBwdWVzIGVuIGxvcyBtZXNlcyBkZSBtYXJ6bywgYWJyaWwgeSBtYXlvLCBsYSBwYW5kZW1pYSBkZWwgQ09WSUQtMTkgb2Nhc2lvbsOzIHZhcmlhY2lvbmVzIG51bmNhIGFudGVzIHZpc3RhcywgeSBudWVzdHJvcyBkYXRvcyBkZSBlamVtcGxvIG5vIHNvbiBsYSBleGNlcGNpw7NuLg0KDQpQYXJhIHRyYXRhciBlc3RvcyB2YWxvcmVzLCBzZSBwdWVkZW4gZW1wbGVhciBsb3MgbWlzbW9zIG3DqXRvZG9zIHF1ZSBlbiBlbCBjYXNvIGRlIHBlcmRpZG9zLiBFc3RvIGVzLCBzZSBsb3MgcHVlZGUgZWxpbWluYXIgbyBiaWVuIHNlIGxvcyBwdWVkZSBjYW1iaWFyIHBvciB1biB2YWxvciByZWZlcmVuY2lhbCBjb21vIGxhIG1lZGlhbmEuDQoNClBvciBlc3RhIG9jYXNpw7NuLCBubyB2YW1vcyBhIGNhbWJpYXIgbyBlbGltaW5hciBuYWRhIGRlIG51ZXN0cm8gZWplbXBsbywgcGVybyBzb2xhbWVudGUgdmVhbW9zIHF1ZSB0YW4gYXTDrXBpY29zIHNvbiBudWVzdHJvcyBkYXRvcyBlbiBlc3RvcyBtZXNlcyBoYWNpZW5kbyB1biByZXN1bWVuIGVzdGFkw61zdGljbyBwb3IgKlBlcmlvZG9NZXMqIGNvbiBsYSBzdW1hIGRlICpVbmlkYWRlcyogeSAqVmFsb3JfVVNEKi4gUmVjdWVyZGUgdXNhciAqc3VtbWFyaXNlKi4gDQoNCmBgYHtyfQ0KZGZfdmVudGFzMjAyMF8wMyAlPiUgDQogIGdyb3VwX2J5KFBlcmlvZG9NZXMpICU+JQ0KICBzdW1tYXJpc2UoVW5pZGFkZXMgPSBzdW0oVW5pZGFkZXMsIG5hLnJtID0gVFJVRSksDQogICAgICAgICAgICBWYWxvcl9VU0QgPSBzdW0oVmFsb3JfVVNELCBuYS5ybSA9IFRSVUUpKQ0KYGBgDQpPdHJvcyBhdMOtcGljb3MgYmFzdGFudGUgY29tdW5lcyBxdWUgc2UgZW5jdWVudHJhbiwgc29uIGFxdWVsbG9zIHF1ZSBzZSBvcmlnaW5hbiBwb3IgdW4gbWFsIGluZ3Jlc28gZGUgbG9zIGRhdG9zLiBFbiBudWVzdHJvIGVqZW1wbG8sIG5vdGVtb3MgcXVlIGV4aXN0ZW4gYWxndW5vcyByZWdpc3Ryb3MgY29uICpVbmlkYWRlcyogPSAwIHBlcm8gY29uIHZhbG9yIGRlIHZlbnRhIHBvc2l0aXZvLiBFc3RvIGNsYXJhbWVudGUgbm8gdGllbmUgc2VudGlkby4gQXJyZWdsZW1vcyBlc3RvLCBjYW1iaWFuZG8gbG9zICpWYWxvcl9VU0QqIGEgMCBzaWVtcHJlIHF1ZSAqVW5pZGFkZXMqIHNlYW4gMC4gQ3JlZW1vcyBlbCBvYmpldG8gKmRmX3ZlbnRhczIwMjBfMDQqLg0KDQpgYGB7cn0NCmRmX3ZlbnRhczIwMjBfMDQgPC0gZGZfdmVudGFzMjAyMF8wMyAlPiUNCiAgbXV0YXRlKFZhbG9yX1VTRCA9IGlmZWxzZShVbmlkYWRlcyA9PSAwLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDAsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgVmFsb3JfVVNEKSkNCmBgYA0KDQojIyAzLiBUcmF0YW1pZW50byBkZSB2YXJpYWJsZXMgbm8gbnVtw6lyaWNhcw0KDQpBbGd1bmFzIHZhcmlhYmxlcyBubyBudW3DqXJpY2FzICh0ZXh0byBvIGZhY3RvcmVzKSBwdWVkZW4gcmVxdWVyaXIgZGUgY2llcnRvIHRyYXRhbWllbnRvIGEgZmluIGRlIGZhY2lsaXRhciBzdSBhbsOhbGlzaXMgc3Vic2lndWllbnRlLiBFc3RvIGVzIGV4dHJlbWFkYW1lbnRlIGltcG9ydGFudGUgYWwgbW9tZW50byBkZSB1dGlsaXphciBsb3MgZGF0b3MgZW4gbW9kZWxvcyBkZSBhbmFsw610aWNhIGF2YW56YWRhLCBjb21vIGxvcyBhc29jaWFkb3MgYSBhcHJlbmRpemFqZSBjb21wdXRhY2lvbmFsIChtYWNoaW5lIGxlYXJuaW5nKSBwdWVzdG8gcXVlIGxhIGNvbXB1dGFkb3JhIG5vIGVzIGNhcGF6IGRlIGNvbnRleHR1YWxpemFyIHBhbGFicmFzIGNvb21vIHVuYSBwZXJzb25hLiANCg0KRW4gZXN0ZSBzZW50aWRvLCBtdWNob3MgZGUgbG9zIG3DqXRvZG9zIGRlIGVzdGUgYXBhcnRhZG8gdHJhc2NpZW5kZW4gYSBsbyBwbGFudGVhZG8gZW4gZXN0ZSBjdXJzby4gc2luIGVtYmFyZ28sIGVuIGVsIGF1bGEgdmlydHVhbCBzZSBoYSBjb2xvY2FkbyB1biBicmV2ZSB2aWRlbyBxdWUgZXhwbGljYSBlbCBtw6l0b2RvIG3DoXMgY29tw7puIHBhcmEgdHJhdGFyIHZhcmlhYmxlcyBubyBudW3DqXJpY2FzOiBlbCAqT25lLUhvdCBFbmNvZGluZyogcG9yIHNpIGxlcyByZXN1bHRhIGRlIGludGVyw6lzLg0KDQpQb3IgYWhvcmEsIHZlcmVtb3MgdW4gY2FzbyBlc3BlY8OtZmljbyB5IG11eSB1c2FkbyBlbiBpbnRlbGlnZW5jaWEgZGUgbmVnb2NpbyBxdWUgc2UgZGVub21pbmEgZWwgKkJhbGFuY2VvKi4gTG8gcXVlIGJ1c2NhbW9zIGFxdcOtIGVzIHJlZHVjaXIgbyBsaW1pdGFyIGVsIG7Dum1lcm8gZGUgb3BjaW9uZXMgcXVlIHVuYSB2YXJpYWJsZSBkZSB0aXBvIGZhY3RvciBvIHRleHRvIHB1ZWRhIHByZXNlbnRhciwgeSBjb21vIGVqZW1wbG8gaGFyZW1vcyBlc3RlIHRyYXRhbWllbnRvIGVuICpOb21icmVfQ29sb3IqLg0KDQpDb21vIHlhIGxvIG1lbmNpb25hbW9zLCBlc3RhIHZhcmlhYmxlIHRpZW5lIDE3IG9wY2lvbmVzIGRpZmVyZW50ZXMsIGxhcyBjdWFsZXMgbm8gZXN0w6FuIGJhbGFuY2VhZGFzLiBQYXJhIGlsdXN0cmFyIGVzdG8sIGVsIGNvbG9yIEJMQU5DTyBwcmVzZW50YSAxLDQwNCBjYXNvcywgbWllbnRyYXMgcXVlIGVsIE5BUkFOSkEgYXBlbmFzIDkuDQoNClZhbW9zIGVudG9uY2VzIGEgQ1JFQVIgdW5hIG51ZXZhIGxhIHZhcmlhYmxlICpHcnVwb19Db2xvciogZGUgbW9kbyBxdWUgc2kgdW5vIGRlIGxvcyBjb2xvcmVzIHRpZW5lIG1lbm9zIGRlIDEwMCBvYnNlcnZhY2lvbmVzLCBwZXJ0ZW5lY2Vyw6EgYWwgZ3J1cG8gIk9UUk9TIiwgdG9kb3MgbG9zIGRlbcOhcyBjYXNvcyBtYW50aWVuZW4gc3Ugbm9tYnJlIG9yaWdpbmFsLiBVdGlsaXphcmVtb3MgbnVldmFtZW50ZSBsYXMgZnVuY2lvbmVzICpncm91cF9ieSosICp1bmdyb3VwKiwgKm11dGF0ZSosIGUgKmlmZWxzZSouIExsYW1hcmVtb3MgYWwgbnVldm8gb2JqZXRvIHF1ZSBjcmVlbW9zICpkZl92ZW50YXMyMDIwXzA1Ki4gIA0KDQpgYGB7cn0NCmRmX3ZlbnRhczIwMjBfMDUgPC0gZGZfdmVudGFzMjAyMF8wNCAlPiUNCiAgI1ByaW1lcm8gZ2VuZXJlbW9zIHVuIGNhbXBvIGF1eGlsaWFyIHF1ZSBub3MgbXVlc3RyZSBlbCBuw7ptZXJvIGRlIGNhc29zIHBhcmEgY2FkYSBjb2xvcg0KICBncm91cF9ieShOb21icmVfQ29sb3IpICU+JQ0KICBtdXRhdGUoQ2Fzb3MgPSBuKCkpICU+JQ0KICB1bmdyb3VwKCkgJT4lDQogICNMdWVnbywgY3JlZW1vcyBsYSB2YXJpYWJsZSBHcnVwb19Db2xvciBwb25pZW5kbyBlbCB2YWxvciBkZSBPVFJPUyBjdWFuZG8gY2Fzb3Mgc2VhIG1lbm9yIHF1ZSAxMDAsIHkgZWwgbWlzbW8gbm9tYnJlIGRlbCBjb2xvciBjdWFuZG8gZXN0byBubyBhcGxpcXVlDQogIG11dGF0ZShHcnVwb19Db2xvciA9IGlmZWxzZShDYXNvcyA8IDEwMCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJPVFJPUyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICBOb21icmVfQ29sb3IpKQ0KYGBgDQoNClZlYW1vcyBsbyBxdWUgY29uc2VndWltb3M6DQpgYGB7cn0NClZpZXcoZGZfdmVudGFzMjAyMF8wNSkNCmBgYA0KDQpDb25zdHJ1eWFtb3MgdW5hIHRhYmxhIGRlIGZyZWN1ZW5jaWEgZGUgbnVlc3RyYSBudWV2YSB2YXJpYWJsZSBwYXJhIHZlciBzaSBlbiBhbGdvIG1lam9yYW1vcyBlbCBiYWxhbmNlbyB5IGxhIGNhbnRpZGFkIGRlIG9wY2lvbmVzOg0KYGBge3J9DQpkZl92ZW50YXMyMDIwXzA1ICU+JQ0KICBjb3VudChHcnVwb19Db2xvcikNCmBgYA0KDQojIyA0LiBTZWxlY2Npw7NuIChvIGluZ2VuaWVyw61hKSBkZSBhdHJpYnV0b3MNCg0KQ29tbyDDumx0aW1vIHB1bnRvIGRlIG51ZXN0cm8gcHJlcHJvY2VzYW1pZW50bywgZGVmaW5pciBjdcOhbGVzIHNvbiBsYXMgdmFyaWFibGVzIHF1ZSBxdWVyZW1vcyBtb3N0cmFyIGVuIG51ZXN0cmEgdGFibGEgZGUgZGF0b3MgcHJldmlvIGEgZW1wZXphciBzdSBhbsOhbGlzaXMgZXhoYXVzdGl2by4gRXN0byBpbXBsaWNhIGxhcyBkb3MgY29zYXMgcmVmZXJpZGFzIGEgY29udGludWFjacOzbi4NCg0KIyMjIDQuMSBDcmVhY2nDs24gZGUgbnVldmFzIHZhcmlhYmxlcw0KDQpQYXJhIGRhciB1bmEgbWF5b3Igcm9idXN0ZXogYSBudWVzdHJvIGFuw6FsaXNpcywgc2llbXByZSBlcyBidWVubyBjb250YXIgY29uIHRvZGFzIGxhcyB2YXJpYWJsZXMgcXVlIG5vcyB2YXlhbiBhIHNlciBkZSB1dGlsaWRhZC4gUGFyYSBlc3RvLCBwb3IgdW4gbGFkbyBjcmVlbW9zIHZhcmlhYmxlcyBxdWUgc2VhbiBwb3RlbmNpYWxtZW50ZSB2YWxpb3Nhcy4NCg0KRW4gbnVlc3RybyBlamVtcGxvLCBub3RlbW9zIHF1ZSBwb2Ryw61hIHNlciDDunRpbCBjb250YXIgY29uIGxhcyBzaWd1aWVudGVzIHZhcmlhYmxlczoNCg0KKiAqTWVzKjogZWwgY2FtcG8gKlBlcmlvZG9NZXMqIHRlcm1pbmEgc2llbmRvIHJlZHVuZGFudGUgZW4gdmlzdGEgcXVlIHNhYmVtb3MgcXVlIHRvZG9zIGxvcyBkYXRvcyBjb3JyZXNwb25kZW4gYWwgUGVyw61vZG8gbyBBw7FvIDIwMjAuDQoqICpGZWNoYSo6IHNlcsOtYSBhZGVjdWFkbyB0ZW5lciB1biBjYW1wbyBhZGljaW9uYWwgcXVlIGVzdMOpIGVuIGZvcm1hdG8gZmVjaGEgcHVlc3RvIHF1ZSBlc3RhbW9zIHZpZW5kbywgZGUgY2llcnRhIGZvcm1hLCB1bmEgZXZvbHVjacOzbiBlbiBlbCB0aWVtcG8uDQoqICpUYXJpZmFfUHJvbSo6IHNpZW1wcmUgcXVlIGVzdHVkaWFtb3MgdmVudGFzLCBlcyDDunRpbCBzYWJlciBsYSB0YXJpZmEgcHJvbWVkaW8gYSBsYSBjdWFsIHNlIHRlcm1pbsOzIHZlbmRpZW5kbyB1biBwcm9kdWN0bywgeSBlc3RvIGUgc2ZhY2lsIGNhbGN1bGFybG8gY29uICQkIFZhbG9yX3tVU0R9IC8gVW5pZGFkZXMgJCQNCg0KRW50b25jZXMsIGNyZWVtb3MgZWwgb2JqZXRvICpkZl92ZW50YXMyMDIwXzA2KiBxdWUgY29udGVuZ2EgZXN0b3MgbnVldm9zIGF0cmlidXRvcyB1c2FuZG8gbGEgZnVuY2nDs24gKm11dGF0ZSouIFBJU1RBOiBVdGlsaWNlIGxhIGZ1bmNpb24gKnN0cl9yZW1vdmUqIHBhcmEgcXVpdGFyIGVsIDIwMjAgZGUgKlBlcmlvZG9NZXMqOyAqSVNPZGF0ZSogcGFyYSBvYnRlbmVyIHVuYSBmZWNoYS4gDQoNCmBgYHtyfQ0KZGZfdmVudGFzMjAyMF8wNiA8LSBkZl92ZW50YXMyMDIwXzA1ICU+JQ0KICBtdXRhdGUoTWVzID0gYXMuaW50ZWdlcihzdHJfcmVtb3ZlKFBlcmlvZG9NZXMsICIyMDIwIikpKSAlPiUNCiAgbXV0YXRlKEZlY2hhID0gSVNPZGF0ZSh5ZWFyID0gMjAyMCwgbW9udGggPSBNZXMsIGRheSA9IDEpKSAlPiUNCiAgbXV0YXRlKFRhcmlmYV9Qcm9tID0gVmFsb3JfVVNEL1VuaWRhZGVzKQ0KYGBgDQoNClZlYW1vcyBsbyBjb25zZWd1aWRvOg0KYGBge3J9DQpWaWV3KGRmX3ZlbnRhczIwMjBfMDYpDQpgYGANCg0KTm90ZW1vcyBxdWUgKlRhcmlmYV9Qcm9tKiB0aWVuZSB2YWxvcmVzIHBlcmRpZG9zIGFycmFzdHJhZG9zIHBvciAqVmFsb3JfVVNEKiwgeSBvdHJvcyBkZXJpdmFkb3MgZGUgbGEgZGl2aXNpw7NuIHBvciAwLiBZYSBsbyBjb3JyZWdpcmVtb3MgbcOhcyBhZGVsYW50ZS4NCg0KIyMjIDQuMiBFbGltaW5hY2nDs24gZGUgdmFyaWFibGVzDQoNCkFzw60gY29tbyBjcmVhbW9zIHZhcmlhYmxlcyBwb3RlbmNpYWxtZW50ZSDDunRpbGVzIHBhcmEgbnVlc3RybyBhbsOhbGlzaXMgcG9zdGVyaW9yLCBwb2RlbW9zIHRhbWJpw6luIGVsaW1pbmFyIGFxdWVsbGFzIHZhcmlhYmxlcyBxdWUgY2FyZXpjYW4gZGUgc2VudGlkbyBvIHZlYW1vcyBkZSBmb3JtYSBhbnRpY2lwYWRhIHF1ZSBubyBub3Mgc2Vyw6FuIGRlIHV0aWxpZGFkLg0KDQpFbiBudWVzdHJvIGVqZW1wbG8sIGxhcyB2YXJpYWJsZXMgKlBlcmlvZG9NZXMqIHkgKkNhc29zKiBubyB0aWVuZW4gbXVjaG8gc2VudGlkby4gTGEgcHJpbWVyYSBwb3JxdWUgdG9kYSBsYSBpbmZvcm1hY2nDs24gcXVlIHB1ZWRlIGVudHJlZ2FyIHlhIGVzdMOhIGNvbnRlbmlkYSBlbiBsYXMgbnVldmFzIHZhcmlhYmxlcyBxdWUgY3JlYW1vczsgeSBsYSBzZWd1bmRhLCBwb3JxdWUgc3UgZXhpc3RlbmNpYSBlcyBwdXJhbWVudGUgYXV4aWxpYXIuIFBvciBjb25zaWd1aWVudGUgdmVtb3MgcXVlIHN1IGVsaW1pbmFjacOzbiBubyByZXByZXNlbnRhIG5pbmfDum4gcmllc2dvIHBhcmEgZWwgYW7DoWxpc2lzLg0KDQpQYXJhIGVsaW1pbmFybGFzLCB1dGlsaWNlbW9zIGxhIGZ1bmNpw7NuICpzZWxlY3QqLCBjcmVhbmRvIGVsIG9iamV0byAqZGZfdmVudGFzMjAyMF8wNyouDQoNCmBgYHtyfQ0KZGZfdmVudGFzMjAyMF8wNyA8LSBkZl92ZW50YXMyMDIwXzA2ICU+JQ0KICBzZWxlY3QoLVBlcmlvZG9NZXMsDQogICAgICAgICAtQ2Fzb3MpDQpgYGANCg0KdmVhbW9zIGVsIHJlc3VsdGFkbyBmaW5hbDoNCmBgYHtyfQ0KVmlldyhkZl92ZW50YXMyMDIwXzA3KQ0KYGBgDQoNCk9KTzogUGFyYSBlbGltaW5hciB1bmEgdmFyaWFibGUsIHRlbmVtb3MgcXVlIHRlbmVyIG11eSBjbGFyYSBsYSByYXrDs24gZGV0csOhcyBkZSBudWVzdHJhIGRlY2lzacOzbi4gTm8gc2UgZWxpbWluYW4gdmFyaWFibGVzIHNpbiB1bmEgZXhwbGljYWNpw7NuIGzDs2dpY2EgeSBkZWZlbmRpYmxlLg0KDQojIyA1LiBUYXJlYSBJbmRpdmlkdWFsDQoNClVzYW5kbyBsbyBhcHJlbmRpZG8gZW4gbGEgY2xhc2UsIGNvbXBsZXRlIHN1IHNjcmlwdCBkZSBmb3JtYSBxdWUgaW5jbHV5YSBsbyBzaWd1aWVudGUsIGFkZW3DoXMgZGUgbG8gaGVjaG8gY29uIGVsIHByb2Zlc29yOg0KDQoqIEFub3RhY2lvbmVzIG8gY29tZW50YXJpb3MgcXVlIGZhY2lsaXRlbiBsYSBsZWN0dXJhIGRlbCBtaXNtby4NCiogVHJhbnNmb3JtYWNpw7NuIGEgZmFjdG9yIGRlIGxhIHZhcmlhYmxlICpHcnVwb19Db2xvciouDQoqIFRyYXRhbWllbnRvIGEgbG9zIHZhbG9yZXMgcGVyZGlkb3MgZGUgKlRhcmlmYV9Qcm9tKg0KKiBUcmF0YW1pZW50byBhIGxvcyB2YWxvcmVzIHBlcmRpZG9zIGRlICpWYWxvcl9VU0QqLCB1c2FuZG8gcGFyYSBlc3RvIGxhICpUYXJpZmFfUHJvbSogeWEgcHJvY2VzYWRhLg0KKiBUcmFuc2Zvcm1hY2nDs24gZGUgbGEgY2xhc2UgZGUgKk1lcyogYSBmYWN0b3IuDQoqIENyZWFjacOzbiBkZSB1biBudWV2byBhdHJpYnV0byBwb3RlbmNpYWxtZW50ZSDDunRpbCBlbiBlbCBhbsOhbGlzaXMgcG9zdGVyaW9yLg0KDQpFc3RlIHNjcmlwdCBsbyBkZWJlbiBzdWJpciBoYXN0YSBsYSBmZWNoYSBpbmRpY2FkYSBlbiBlbCBBdWxhIFZpcnR1YWwuDQo=