Hemos visto que con el tidytverse podemos manipular datos. En concreto, hemos aprendido las siguiente funciones que alteran tablas de datos:

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,

df_ventas2020 %>%
  count(Nombre_Articulo)
df_ventas2020 %>%
  count(Tipo)
df_ventas2020 %>%
  count(Nombre_Color)

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:

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=