Una vez que tenemos un set de datos para analizar y extraer información, es frecuente observar que los mismos tienen ciertas condiciones que requieren de algún tipo “preprocesamiento” a fin de garantizar mejores resultados.
Como vimos en clase, existen muchas formas de preprocesar datos, sin embargo, en esta curso nos enfocaremos en las siguientes:
Antes de empezar, siempre es conveniente activar las librerías que vamos a utilizar, en este caso el tidyverse y una librería nueva llamada lubridate.
Así también, es conveniente de forma preliminar conocer unas sintaxis adicionales propias de R, que nos facilitarán el trabajo posterior.
1. Pipes
Como nos iremos dando cuenta en las siguientes clases, al manipular (y analizar) datos, se requiere de varias instrucciones secuenciales, las cuales deben mantener un orden coherente y entendible. Una forma eficiente de atender esta problemática es mediante el uso de pipes %>%.
A continuación un ejemplo:
#Metodo tradicional de calcular el promedio de una columna de una tabla de datos
mean(iris$Sepal.Length)
[1] 5.843333
#Método usando pipes
iris$Sepal.Length %>% mean()
[1] 5.843333
Nótemos con el ejemplo que usar pipes equivale, en términos de funciones a lo siguiente:
\[ f(x,...) = y \Leftrightarrow x \rightarrow f(...) = y\] Veamos otro caso, usando ahora una función nueva llamada sample la cual nos permite extraer una muetra aleatoria de una columna en una tabla de datos
#Metodo tradicional
sample(iris$Sepal.Width, size = 25)
[1] 2.5 3.0 2.9 2.8 3.2 2.9 2.5 2.9 3.4 3.0 2.0 3.9 3.0 3.0 2.6 3.0
[17] 3.8 3.2 2.2 3.1 3.0 3.2 2.7 3.4 2.8
#Metodo con pipes
iris$Sepal.Width %>% sample(size = 25)
[1] 2.6 3.0 3.4 4.1 3.0 3.3 2.6 3.0 3.2 2.8 3.1 2.8 4.4 2.8 3.9 2.9
[17] 2.8 2.5 3.2 2.8 3.0 3.1 3.4 3.4 3.4
Combinemos ahora dos instrucciones, de forma que podamos obtener una “media muestal”, usando las funciones sample y mean:
#Metodo tradicional (forma 1)
muestra <- sample(iris$Petal.Length, size = 30)
mean(muestra)
[1] 3.583333
#Metodo tradicional (forma 2)
mean(sample(iris$Petal.Length, size = 30))
[1] 3.76
#Metodo con pipes
iris$Petal.Length %>% sample(size = 30) %>% mean()
[1] 4.11
Aquí podemos observar una importante diferencia entre los métodos tradicional y con pipes: con el método tradicional tenemos que, o bien crear una variable adicional intermedia, o acumular funciones dentro de otras lo cual puede generar confusión. Usando pipes no acumulamos objetos en nuestra memoria, y se genera un mejor contexto para el analista de datos.
Para ampliar esto veamos este caso de 3 instrucciones diferentes:
#Metodo tradicional (forma 1)
muestra <- sample(iris$Petal.Width,size = 30)
media <- mean(muestra)
sqrt(media)
[1] 1.087811
#Metodo tradicional (forma 2)
sqrt(mean(sample(iris$Petal.Width,size = 30)))
[1] 1.037625
#Metodo con pipes
iris$Petal.Width %>% sample(size = 30) %>% mean() %>% sqrt()
[1] 1.118034
En general, de aquí en adelante vamos a usar pipes, aunque si así se lo desea, para los trabajos autónomos y las tareas se pueden emplear métodos tradicionales.
2. Funciones base para manipulación
Veamos ahora una funciones base para manipulación de datos en tablas, las cuales nos permitirán dar el preprocesamiento deseado.
Para esto, vamos a importar una tabla de datos que se encuentra en la siguiente dirección: https://raw.githubusercontent.com/jsaraujo5081/clasesBI/main/starwars.csv.
df_sw <- read_csv(file = "https://raw.githubusercontent.com/jsaraujo5081/clasesBI/main/starwars.csv",
col_names = TRUE)
-- Column specification --------------------------------------------
cols(
name = col_character(),
height = col_double(),
mass = col_double(),
hair_color = col_character(),
skin_color = col_character(),
eye_color = col_character(),
birth_year = col_double(),
sex = col_character(),
gender = col_character(),
homeworld = col_character(),
species = col_character()
)
Demos una mirada a estos datos e intentemos comprender de qué se tratan:
glimpse(df_sw)
Rows: 87
Columns: 11
$ name <chr> "Luke Skywalker", "C-3PO", "R2-D2", "Darth Vade~
$ height <dbl> 172, 167, 96, 202, 150, 178, 165, 97, 183, 182,~
$ mass <dbl> 77.0, 75.0, 32.0, 136.0, 49.0, 120.0, 75.0, 32.~
$ hair_color <chr> "blond", NA, NA, "none", "brown", "brown, grey"~
$ skin_color <chr> "fair", "gold", "white, blue", "white", "light"~
$ eye_color <chr> "blue", "yellow", "red", "yellow", "brown", "bl~
$ birth_year <dbl> 19.0, 112.0, 33.0, 41.9, 19.0, 52.0, 47.0, NA, ~
$ sex <chr> "male", "none", "none", "male", "female", "male~
$ gender <chr> "masculine", "masculine", "masculine", "masculi~
$ homeworld <chr> "Tatooine", "Tatooine", "Naboo", "Tatooine", "A~
$ species <chr> "Human", "Droid", "Droid", "Human", "Human", "H~
View(df_sw)
2.1 Resumen estadístico de variables
Hasta ahora hemos usado la función summary para generar resúmenes estadísticos en tablas; sin embargo, usando ahora la función summarise vamos a tener un mayor control de lo que queremos obtener de resultado.
Por ejemplo, supongamos que deseamos obtener solamente la media y la desviación estandar de las variables numéricas height y mass, y para las variables no numéricas homeworld y species, queremos un conteo de casos únicos. Finalmente, queremos un conteo de observaciones.
df_sw %>%
summarise(prom_height = mean(height, na.rm = TRUE),
desv_height = sd(height, na.rm = TRUE),
prom_mass = mean(mass, na.rm = TRUE),
desv_mass = sd(mass, na.rm = TRUE),
uniq_homeworld = n_distinct(homeworld),
uniq_species = n_distinct(species),
Observaciones = n())
Notemos que en la sintaxis utilizada se ha incorporado el parámetro na.rm a las funciones para media y desviación estandar. De esta forma estamos diciéndole a R que al momento de calcular estos estadísticos NO considere los valores perdidos que existan en la tabla.
Adicionalmente, notemos que estamos usando funciones nuevas n_distinct y n. ¿Qué hacen estas funciones?
2.2 Tablas de frecuencia (conteos)
También podemos generar tablas de frecuencia de forma sencilla usando la función count.
Supongamos que queremos saber el conteo de casos por la variable hair_color.
df_sw %>%
count(hair_color)
Ahora, veamos el conteo no solamente por hair_color sino tambien por species.
df_sw %>%
count(species,
hair_color)
Esta tabla sería mejor verla ordenada de forma descendente, usemos para esto la función arrange como una instrucción adicional.
df_sw %>%
count(species,
hair_color) %>%
arrange(-n)
¿Qué cree que pasaría si en vez de -n pusiéramos solamente n?
2.3 Creación de nuevas variables
Uno de los aspectos más criticos en la manipulación de datos hace referencia a la capacidad que tengamos de crear nuevas variables (o en su defecto, modificar las existentes). Para esto, existe la función mutate.
Supongamos que en nuestra tabla queremos calcular el índice de masa corporar bmi, usando la siguiente fórmula:
\[ bmi = \frac{mass}{(height/100)^2} \] Para lo cual hacemos lo siguiente, y guardamos esta tabla en un nuevo data frame:
df_sw1 <- df_sw %>%
mutate(bmi = mass/((height/100)**2))
Veamos si conseguimos lo deseado:
glimpse(df_sw1)
Rows: 87
Columns: 12
$ name <chr> "Luke Skywalker", "C-3PO", "R2-D2", "Darth Vad~
$ height <dbl> 172, 167, 96, 202, 150, 178, 165, 97, 183, 182~
$ mass <dbl> 77.0, 75.0, 32.0, 136.0, 49.0, 120.0, 75.0, 32~
$ hair_color <chr> "blond", NA, NA, "none", "brown", "brown, grey~
$ skin_color <chr> "fair", "gold", "white, blue", "white", "light~
$ eye_color <chr> "blue", "yellow", "red", "yellow", "brown", "b~
$ birth_year <dbl> 19.0, 112.0, 33.0, 41.9, 19.0, 52.0, 47.0, NA,~
$ sex <chr> "male", "none", "none", "male", "female", "mal~
$ gender <chr> "masculine", "masculine", "masculine", "mascul~
$ homeworld <chr> "Tatooine", "Tatooine", "Naboo", "Tatooine", "~
$ species <chr> "Human", "Droid", "Droid", "Human", "Human", "~
$ bmi <dbl> 26.02758, 26.89232, 34.72222, 33.33007, 21.777~
View(df_sw1)
Utilicemos ahora esta nueva función en conjunto con summarise. Queremos calcular el promedio y la desviación estandar de la masa corporar, pero además queremos saber el coeficiente de variación CV dado por:
\[ CV = \frac{Desv. Est.}{Media} \]
df_sw1 %>%
summarise(media = mean(bmi, na.rm = TRUE),
desvest = sd(bmi, na.rm = TRUE)) %>%
mutate(cv = desvest/media)
Adicionemos ahora en nuestra tabla de frecuencia, ya no soalmente el conteo absoluto, sino también el conteo relativo y el conteo acumulado. Recuerde usar tanto las funciones count, arrange, mutate.
df_sw1 %>%
count(species,
hair_color) %>%
arrange(-n) %>%
mutate(n_rel = n/sum(n, na.rm = TRUE)) %>%
mutate(n_acum = cumsum(n))
Intente comprender y explicar todo lo realizado en esta sintaxis.
2.4 Agrupamiento de variables
En muchas ocasiones vamos a querer hacer tablas, conteos y creaciones de forma consolidada por alguna variable. Para este tipo de casos se usa la función group_by.
Supongamos que deseamos crear una nueva variable que evidencie la diferencia en centímetros que cada observación tiene con relación a la altura promedio de su especie (dif_height). Esto es:
\[ dif_{height} = height - \overline{height_{specie}} \]
df_sw2 <- df_sw1 %>%
group_by(species) %>%
mutate(dif_height = height - mean(height, na.rm = TRUE)) %>%
ungroup()
Notemos las siguientes cosas:
- Al usar la función group_by, le hemos dicho a R que todos los cálculos subsiguientes que realice los haga generando grupos (en este caso por la variable species). En consecuencia, la media calculada será diferente para cada grupo.
- Hemos usado una función adicional a group_by una vez concluida nuestra necesidad de agrupar. Esta función ungroup le dice a R que de allí en adelante ya no debe considerar los grupo de la variable species para los cálculos.
Demos una mirada a esta tabla creada:
glimpse(df_sw2)
Rows: 87
Columns: 13
$ name <chr> "Luke Skywalker", "C-3PO", "R2-D2", "Darth Vad~
$ height <dbl> 172, 167, 96, 202, 150, 178, 165, 97, 183, 182~
$ mass <dbl> 77.0, 75.0, 32.0, 136.0, 49.0, 120.0, 75.0, 32~
$ hair_color <chr> "blond", NA, NA, "none", "brown", "brown, grey~
$ skin_color <chr> "fair", "gold", "white, blue", "white", "light~
$ eye_color <chr> "blue", "yellow", "red", "yellow", "brown", "b~
$ birth_year <dbl> 19.0, 112.0, 33.0, 41.9, 19.0, 52.0, 47.0, NA,~
$ sex <chr> "male", "none", "none", "male", "female", "mal~
$ gender <chr> "masculine", "masculine", "masculine", "mascul~
$ homeworld <chr> "Tatooine", "Tatooine", "Naboo", "Tatooine", "~
$ species <chr> "Human", "Droid", "Droid", "Human", "Human", "~
$ bmi <dbl> 26.02758, 26.89232, 34.72222, 33.33007, 21.777~
$ dif_height <dbl> -4.645161, 35.800000, -35.200000, 25.354839, -~
View(df_sw2)
Hagamos ahora un resumen estadístico, donde tengamos la media y la mediana para bmi pero agrupando por species y por sex. REcuerde desagrupar al final.
df_sw2 %>%
group_by(species,
sex) %>%
summarise(prom = mean(bmi, na.rm = TRUE),
mediana = median(bmi, na.rm = TRUE)) %>%
ungroup()
`summarise()` has grouped output by 'species'. You can override using the `.groups` argument.
2.5 Filtros de variables
Otra forma de manipular datos es mediante la filtración de variables sobre la base de operaciones lógicas (aquellas que devuelven como resultado una opción buleana “TRUE/FALSE”). Supongamos que queremos visualizar solamente aquellas observaciones cuya altura sea mayor al promedio de su especie. Para esto usamos la función filter y aprovechemos para ordenar de forma ascendente por height.
df_sw_altos <- df_sw2 %>%
filter(dif_height > 0) %>%
arrange(height)
Veamos los resultados obtenidos
View(df_sw_altos)
Supongamos ahora que queremos visualizar solamente a aquellas observaciones cuyo planeta de origen sea “Tatooine” (el que tiene 2 soles) y sean droides.
df_sw_tatdroid <- df_sw2 %>%
filter(homeworld == "Tatooine" & species == "Droid")
Veamos si lo conseguimos:
View(df_sw_tatdroid)
Recuerde que para establecer la operación AND se utiliza el símbolo “&” y para la OR se utiliza “|”.
Otra forma de filtrar, se puede dar con las funciones top_n, top_frac, sample_n y sample_frac. 5 puntos extras al primer estudiante que me remita ejemplos usando estas 4 funciones en el dataset con el que estamos trabajando.
4.6 Selección de variables
Se pueden también seleccionar o eliminar columnas específicas de una tabla de datos, de forma que nuestro data frame final tenga lo estrictamente necesario para análisis posteriores. Con este propósito existe la función select.
Supongamos que queremos seleccionar solamente las columnas name, height y mass.
df_sw_select <- df_sw2 %>%
select(name,
height,
mass)
Veamos si logramos lo que buscábamos:
glimpse(df_sw_select)
Rows: 87
Columns: 3
$ name <chr> "Luke Skywalker", "C-3PO", "R2-D2", "Darth Vader",~
$ height <dbl> 172, 167, 96, 202, 150, 178, 165, 97, 183, 182, 18~
$ mass <dbl> 77.0, 75.0, 32.0, 136.0, 49.0, 120.0, 75.0, 32.0, ~
Ahora, veamos el caso en que queremos eliminar dos variables: birth_year y gender.
df_sw_elim <- df_sw2 %>%
select(-birth_year,
-gender)
Veamos el resultado:
glimpse(df_sw_elim)
Rows: 87
Columns: 11
$ name <chr> "Luke Skywalker", "C-3PO", "R2-D2", "Darth Vad~
$ height <dbl> 172, 167, 96, 202, 150, 178, 165, 97, 183, 182~
$ mass <dbl> 77.0, 75.0, 32.0, 136.0, 49.0, 120.0, 75.0, 32~
$ hair_color <chr> "blond", NA, NA, "none", "brown", "brown, grey~
$ skin_color <chr> "fair", "gold", "white, blue", "white", "light~
$ eye_color <chr> "blue", "yellow", "red", "yellow", "brown", "b~
$ sex <chr> "male", "none", "none", "male", "female", "mal~
$ homeworld <chr> "Tatooine", "Tatooine", "Naboo", "Tatooine", "~
$ species <chr> "Human", "Droid", "Droid", "Human", "Human", "~
$ bmi <dbl> 26.02758, 26.89232, 34.72222, 33.33007, 21.777~
$ dif_height <dbl> -4.645161, 35.800000, -35.200000, 25.354839, -~
Notemos finalmente que tanto la función select como filter se constituyen en nuevas formas de indexación de data frames. Por ejemplo, si queremos saber los nombres de las observaciones de todos aquellos cuya especie sea “Ewok”, se hace lo siguiente:
#Indexacion tradicional
df_sw2$name[which(df_sw2$species == "Ewok")]
[1] "Wicket Systri Warrick"
#Indexacion con pipes y funciones
df_sw2 %>%
filter(species == "Ewok") %>%
select(name)
LS0tDQp0aXRsZTogIk1hbmlwdWxhY2nDs24gZGUgZGF0b3MiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQpVbmEgdmV6IHF1ZSB0ZW5lbW9zIHVuIHNldCBkZSBkYXRvcyBwYXJhIGFuYWxpemFyIHkgZXh0cmFlciBpbmZvcm1hY2nDs24sIGVzIGZyZWN1ZW50ZSBvYnNlcnZhciBxdWUgbG9zIG1pc21vcyB0aWVuZW4gY2llcnRhcyBjb25kaWNpb25lcyBxdWUgcmVxdWllcmVuIGRlIGFsZ8O6biB0aXBvICJwcmVwcm9jZXNhbWllbnRvIiBhIGZpbiBkZSBnYXJhbnRpemFyIG1lam9yZXMgcmVzdWx0YWRvcy4NCg0KQ29tbyB2aW1vcyBlbiBjbGFzZSwgZXhpc3RlbiBtdWNoYXMgZm9ybWFzIGRlIHByZXByb2Nlc2FyIGRhdG9zLCBzaW4gZW1iYXJnbywgZW4gZXN0YSBjdXJzbyBub3MgZW5mb2NhcmVtb3MgZW4gbGFzIHNpZ3VpZW50ZXM6DQoNCiogQWp1c3RlIGRlIGNsYXNlcyBkZSB2YXJpYWJsZXMNCiogVHJhdGFtaWVudG8gZGUgdmFsb3JlcyBwZXJkaWRvcyB5IG91dGxpZXJzDQoqIFRyYXRhbWllbnRvIGRlIHZhcmlhYmxlcyBubyBudW3DqXJpY2FzDQoqIFNlbGVjY2nDs24gZGUgYXRyaWJ1dG9zDQoNCkFudGVzIGRlIGVtcGV6YXIsIHNpZW1wcmUgZXMgY29udmVuaWVudGUgYWN0aXZhciBsYXMgbGlicmVyw61hcyBxdWUgdmFtb3MgYSB1dGlsaXphciwgZW4gZXN0ZSBjYXNvIGVsICoqdGlkeXZlcnNlKiogeSB1bmEgbGlicmVyw61hIG51ZXZhIGxsYW1hZGEgKipsdWJyaWRhdGUqKi4NCmBgYHtyfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQojaW5zdGFsbC5wYWNrYWdlcygibHVicmlkYXRlIikNCmxpYnJhcnkobHVicmlkYXRlKQ0KYGBgDQoNCkFzw60gdGFtYmnDqW4sIGVzIGNvbnZlbmllbnRlIGRlIGZvcm1hIHByZWxpbWluYXIgY29ub2NlciB1bmFzIHNpbnRheGlzIGFkaWNpb25hbGVzIHByb3BpYXMgZGUgUiwgcXVlIG5vcyBmYWNpbGl0YXLDoW4gZWwgdHJhYmFqbyBwb3N0ZXJpb3IuDQoNCiMjIDEuIFBpcGVzDQoNCkNvbW8gbm9zIGlyZW1vcyBkYW5kbyBjdWVudGEgZW4gbGFzIHNpZ3VpZW50ZXMgY2xhc2VzLCBhbCBtYW5pcHVsYXIgKHkgYW5hbGl6YXIpIGRhdG9zLCBzZSByZXF1aWVyZSBkZSB2YXJpYXMgaW5zdHJ1Y2Npb25lcyBzZWN1ZW5jaWFsZXMsIGxhcyBjdWFsZXMgZGViZW4gbWFudGVuZXIgdW4gb3JkZW4gY29oZXJlbnRlIHkgZW50ZW5kaWJsZS4gVW5hIGZvcm1hIGVmaWNpZW50ZSBkZSBhdGVuZGVyIGVzdGEgcHJvYmxlbcOhdGljYSBlcyBtZWRpYW50ZSBlbCB1c28gZGUgcGlwZXMgKiolPiUqKi4NCg0KQSBjb250aW51YWNpw7NuIHVuIGVqZW1wbG86DQpgYGB7cn0NCiNNZXRvZG8gdHJhZGljaW9uYWwgZGUgY2FsY3VsYXIgZWwgcHJvbWVkaW8gZGUgdW5hIGNvbHVtbmEgZGUgdW5hIHRhYmxhIGRlIGRhdG9zDQptZWFuKGlyaXMkU2VwYWwuTGVuZ3RoKQ0KI03DqXRvZG8gdXNhbmRvIHBpcGVzDQppcmlzJFNlcGFsLkxlbmd0aCAlPiUgbWVhbigpDQpgYGANCg0KTsOzdGVtb3MgY29uIGVsIGVqZW1wbG8gcXVlIHVzYXIgcGlwZXMgZXF1aXZhbGUsIGVuIHTDqXJtaW5vcyBkZSBmdW5jaW9uZXMgYSBsbyBzaWd1aWVudGU6DQoNCiQkIGYoeCwuLi4pID0geSBcTGVmdHJpZ2h0YXJyb3cgeCBccmlnaHRhcnJvdyBmKC4uLikgPSB5JCQNClZlYW1vcyBvdHJvIGNhc28sIHVzYW5kbyBhaG9yYSB1bmEgZnVuY2nDs24gbnVldmEgbGxhbWFkYSAqc2FtcGxlKiBsYSBjdWFsIG5vcyBwZXJtaXRlIGV4dHJhZXIgdW5hIG11ZXRyYSBhbGVhdG9yaWEgZGUgdW5hIGNvbHVtbmEgZW4gdW5hIHRhYmxhIGRlIGRhdG9zDQoNCmBgYHtyfQ0KI01ldG9kbyB0cmFkaWNpb25hbA0Kc2FtcGxlKGlyaXMkU2VwYWwuV2lkdGgsIHNpemUgPSAyNSkNCiNNZXRvZG8gY29uIHBpcGVzDQppcmlzJFNlcGFsLldpZHRoICU+JSBzYW1wbGUoc2l6ZSA9IDI1KQ0KYGBgDQoNCkNvbWJpbmVtb3MgYWhvcmEgZG9zIGluc3RydWNjaW9uZXMsIGRlIGZvcm1hIHF1ZSBwb2RhbW9zIG9idGVuZXIgdW5hICJtZWRpYSBtdWVzdGFsIiwgdXNhbmRvIGxhcyBmdW5jaW9uZXMgKnNhbXBsZSogeSAqbWVhbio6DQpgYGB7cn0NCiNNZXRvZG8gdHJhZGljaW9uYWwgKGZvcm1hIDEpDQptdWVzdHJhIDwtIHNhbXBsZShpcmlzJFBldGFsLkxlbmd0aCwgc2l6ZSA9IDMwKQ0KbWVhbihtdWVzdHJhKQ0KDQojTWV0b2RvIHRyYWRpY2lvbmFsIChmb3JtYSAyKQ0KbWVhbihzYW1wbGUoaXJpcyRQZXRhbC5MZW5ndGgsIHNpemUgPSAzMCkpDQoNCiNNZXRvZG8gY29uIHBpcGVzDQppcmlzJFBldGFsLkxlbmd0aCAlPiUgc2FtcGxlKHNpemUgPSAzMCkgJT4lIG1lYW4oKQ0KYGBgDQoNCkFxdcOtIHBvZGVtb3Mgb2JzZXJ2YXIgdW5hIGltcG9ydGFudGUgZGlmZXJlbmNpYSBlbnRyZSBsb3MgbcOpdG9kb3MgdHJhZGljaW9uYWwgeSBjb24gcGlwZXM6IGNvbiBlbCBtw6l0b2RvIHRyYWRpY2lvbmFsIHRlbmVtb3MgcXVlLCBvIGJpZW4gY3JlYXIgdW5hIHZhcmlhYmxlIGFkaWNpb25hbCBpbnRlcm1lZGlhLCBvIGFjdW11bGFyIGZ1bmNpb25lcyBkZW50cm8gZGUgb3RyYXMgbG8gY3VhbCBwdWVkZSBnZW5lcmFyIGNvbmZ1c2nDs24uIFVzYW5kbyBwaXBlcyBubyBhY3VtdWxhbW9zIG9iamV0b3MgZW4gbnVlc3RyYSBtZW1vcmlhLCB5IHNlIGdlbmVyYSB1biBtZWpvciBjb250ZXh0byBwYXJhIGVsIGFuYWxpc3RhIGRlIGRhdG9zLg0KDQpQYXJhIGFtcGxpYXIgZXN0byB2ZWFtb3MgZXN0ZSBjYXNvIGRlIDMgaW5zdHJ1Y2Npb25lcyBkaWZlcmVudGVzOg0KYGBge3J9DQojTWV0b2RvIHRyYWRpY2lvbmFsIChmb3JtYSAxKQ0KbXVlc3RyYSA8LSBzYW1wbGUoaXJpcyRQZXRhbC5XaWR0aCxzaXplID0gMzApDQptZWRpYSA8LSBtZWFuKG11ZXN0cmEpDQpzcXJ0KG1lZGlhKQ0KDQojTWV0b2RvIHRyYWRpY2lvbmFsIChmb3JtYSAyKQ0Kc3FydChtZWFuKHNhbXBsZShpcmlzJFBldGFsLldpZHRoLHNpemUgPSAzMCkpKQ0KDQojTWV0b2RvIGNvbiBwaXBlcw0KaXJpcyRQZXRhbC5XaWR0aCAlPiUgc2FtcGxlKHNpemUgPSAzMCkgJT4lIG1lYW4oKSAlPiUgc3FydCgpDQpgYGANCg0KRW4gZ2VuZXJhbCwgZGUgYXF1w60gZW4gYWRlbGFudGUgdmFtb3MgYSB1c2FyIHBpcGVzLCBhdW5xdWUgc2kgYXPDrSBzZSBsbyBkZXNlYSwgcGFyYSBsb3MgdHJhYmFqb3MgYXV0w7Nub21vcyB5IGxhcyB0YXJlYXMgc2UgcHVlZGVuIGVtcGxlYXIgbcOpdG9kb3MgdHJhZGljaW9uYWxlcy4NCg0KIyMgMi4gRnVuY2lvbmVzIGJhc2UgcGFyYSBtYW5pcHVsYWNpw7NuDQoNClZlYW1vcyBhaG9yYSB1bmEgZnVuY2lvbmVzIGJhc2UgcGFyYSBtYW5pcHVsYWNpw7NuIGRlIGRhdG9zIGVuIHRhYmxhcywgbGFzIGN1YWxlcyBub3MgcGVybWl0aXLDoW4gZGFyIGVsIHByZXByb2Nlc2FtaWVudG8gZGVzZWFkby4NCg0KUGFyYSBlc3RvLCB2YW1vcyBhIGltcG9ydGFyIHVuYSB0YWJsYSBkZSBkYXRvcyBxdWUgc2UgZW5jdWVudHJhIGVuIGxhIHNpZ3VpZW50ZSBkaXJlY2Npw7NuOiBodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vanNhcmF1am81MDgxL2NsYXNlc0JJL21haW4vc3RhcndhcnMuY3N2Lg0KYGBge3J9DQpkZl9zdyA8LSByZWFkX2NzdihmaWxlID0gImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9qc2FyYXVqbzUwODEvY2xhc2VzQkkvbWFpbi9zdGFyd2Fycy5jc3YiLA0KICAgICAgICAgICAgICAgICAgY29sX25hbWVzID0gVFJVRSkNCmBgYA0KDQpEZW1vcyB1bmEgbWlyYWRhIGEgZXN0b3MgZGF0b3MgZSBpbnRlbnRlbW9zIGNvbXByZW5kZXIgZGUgcXXDqSBzZSB0cmF0YW46DQpgYGB7cn0NCmdsaW1wc2UoZGZfc3cpDQpWaWV3KGRmX3N3KQ0KYGBgDQoNCiMjIyAyLjEgUmVzdW1lbiBlc3RhZMOtc3RpY28gZGUgdmFyaWFibGVzDQoNCkhhc3RhIGFob3JhIGhlbW9zIHVzYWRvIGxhIGZ1bmNpw7NuICpzdW1tYXJ5KiBwYXJhIGdlbmVyYXIgcmVzw7ptZW5lcyBlc3RhZMOtc3RpY29zIGVuIHRhYmxhczsgc2luIGVtYmFyZ28sIHVzYW5kbyBhaG9yYSBsYSBmdW5jacOzbiAqc3VtbWFyaXNlKiB2YW1vcyBhIHRlbmVyIHVuIG1heW9yIGNvbnRyb2wgZGUgbG8gcXVlIHF1ZXJlbW9zIG9idGVuZXIgZGUgcmVzdWx0YWRvLg0KDQpQb3IgZWplbXBsbywgc3Vwb25nYW1vcyBxdWUgZGVzZWFtb3Mgb2J0ZW5lciBzb2xhbWVudGUgbGEgbWVkaWEgeSBsYSBkZXN2aWFjacOzbiBlc3RhbmRhciBkZSBsYXMgdmFyaWFibGVzIG51bcOpcmljYXMgKmhlaWdodCogeSAqbWFzcyosIHkgcGFyYSBsYXMgdmFyaWFibGVzIG5vIG51bcOpcmljYXMgKmhvbWV3b3JsZCogeSAqc3BlY2llcyosIHF1ZXJlbW9zIHVuIGNvbnRlbyBkZSBjYXNvcyDDum5pY29zLiBGaW5hbG1lbnRlLCBxdWVyZW1vcyB1biBjb250ZW8gZGUgb2JzZXJ2YWNpb25lcy4NCmBgYHtyfQ0KZGZfc3cgJT4lDQogIHN1bW1hcmlzZShwcm9tX2hlaWdodCA9IG1lYW4oaGVpZ2h0LCBuYS5ybSA9IFRSVUUpLA0KICAgICAgICAgICAgZGVzdl9oZWlnaHQgPSBzZChoZWlnaHQsIG5hLnJtID0gVFJVRSksDQogICAgICAgICAgICBwcm9tX21hc3MgPSBtZWFuKG1hc3MsIG5hLnJtID0gVFJVRSksDQogICAgICAgICAgICBkZXN2X21hc3MgPSBzZChtYXNzLCBuYS5ybSA9IFRSVUUpLA0KICAgICAgICAgICAgdW5pcV9ob21ld29ybGQgPSBuX2Rpc3RpbmN0KGhvbWV3b3JsZCksDQogICAgICAgICAgICB1bmlxX3NwZWNpZXMgPSBuX2Rpc3RpbmN0KHNwZWNpZXMpLA0KICAgICAgICAgICAgT2JzZXJ2YWNpb25lcyA9IG4oKSkNCmBgYA0KTm90ZW1vcyBxdWUgZW4gbGEgc2ludGF4aXMgdXRpbGl6YWRhIHNlIGhhIGluY29ycG9yYWRvIGVsIHBhcsOhbWV0cm8gKm5hLnJtKiBhIGxhcyBmdW5jaW9uZXMgcGFyYSBtZWRpYSB5IGRlc3ZpYWNpw7NuIGVzdGFuZGFyLiBEZSBlc3RhIGZvcm1hIGVzdGFtb3MgZGljacOpbmRvbGUgYSBSIHF1ZSBhbCBtb21lbnRvIGRlIGNhbGN1bGFyIGVzdG9zIGVzdGFkw61zdGljb3MgTk8gY29uc2lkZXJlIGxvcyB2YWxvcmVzIHBlcmRpZG9zIHF1ZSBleGlzdGFuIGVuIGxhIHRhYmxhLg0KDQpBZGljaW9uYWxtZW50ZSwgbm90ZW1vcyBxdWUgZXN0YW1vcyB1c2FuZG8gZnVuY2lvbmVzIG51ZXZhcyAqbl9kaXN0aW5jdCogeSAqbiouIMK/UXXDqSBoYWNlbiBlc3RhcyBmdW5jaW9uZXM/DQoNCiMjIyAyLjIgVGFibGFzIGRlIGZyZWN1ZW5jaWEgKGNvbnRlb3MpDQoNClRhbWJpw6luIHBvZGVtb3MgZ2VuZXJhciB0YWJsYXMgZGUgZnJlY3VlbmNpYSBkZSBmb3JtYSBzZW5jaWxsYSB1c2FuZG8gbGEgZnVuY2nDs24gKmNvdW50Ki4NCg0KU3Vwb25nYW1vcyBxdWUgcXVlcmVtb3Mgc2FiZXIgZWwgY29udGVvIGRlIGNhc29zIHBvciBsYSB2YXJpYWJsZSAqaGFpcl9jb2xvciouIA0KYGBge3J9DQpkZl9zdyAlPiUNCiAgY291bnQoaGFpcl9jb2xvcikNCmBgYA0KQWhvcmEsIHZlYW1vcyBlbCBjb250ZW8gbm8gc29sYW1lbnRlIHBvciAqaGFpcl9jb2xvciogc2lubyB0YW1iaWVuIHBvciAqc3BlY2llcyouDQpgYGB7cn0NCmRmX3N3ICU+JQ0KICBjb3VudChzcGVjaWVzLA0KICAgICAgICBoYWlyX2NvbG9yKQ0KYGBgDQpFc3RhIHRhYmxhIHNlcsOtYSBtZWpvciB2ZXJsYSBvcmRlbmFkYSBkZSBmb3JtYSBkZXNjZW5kZW50ZSwgdXNlbW9zIHBhcmEgZXN0byBsYSBmdW5jacOzbiAqYXJyYW5nZSogY29tbyB1bmEgaW5zdHJ1Y2Npw7NuIGFkaWNpb25hbC4NCmBgYHtyfQ0KZGZfc3cgJT4lDQogIGNvdW50KHNwZWNpZXMsDQogICAgICAgIGhhaXJfY29sb3IpICU+JQ0KICBhcnJhbmdlKC1uKSANCmBgYA0Kwr9RdcOpIGNyZWUgcXVlIHBhc2Fyw61hIHNpIGVuIHZleiBkZSAqLW4qIHB1c2nDqXJhbW9zIHNvbGFtZW50ZSAqbio/DQoNCiMjIyAyLjMgQ3JlYWNpw7NuIGRlIG51ZXZhcyB2YXJpYWJsZXMNCg0KVW5vIGRlIGxvcyBhc3BlY3RvcyBtw6FzIGNyaXRpY29zIGVuIGxhIG1hbmlwdWxhY2nDs24gZGUgZGF0b3MgaGFjZSByZWZlcmVuY2lhIGEgbGEgY2FwYWNpZGFkIHF1ZSB0ZW5nYW1vcyBkZSBjcmVhciBudWV2YXMgdmFyaWFibGVzIChvIGVuIHN1IGRlZmVjdG8sIG1vZGlmaWNhciBsYXMgZXhpc3RlbnRlcykuIFBhcmEgZXN0bywgZXhpc3RlIGxhIGZ1bmNpw7NuICptdXRhdGUqLg0KDQpTdXBvbmdhbW9zIHF1ZSBlbiBudWVzdHJhIHRhYmxhIHF1ZXJlbW9zIGNhbGN1bGFyIGVsIMOtbmRpY2UgZGUgbWFzYSBjb3Jwb3JhciAqYm1pKiwgdXNhbmRvIGxhIHNpZ3VpZW50ZSBmw7NybXVsYToNCg0KJCQgYm1pID0gXGZyYWN7bWFzc317KGhlaWdodC8xMDApXjJ9ICQkDQpQYXJhIGxvIGN1YWwgaGFjZW1vcyBsbyBzaWd1aWVudGUsIHkgZ3VhcmRhbW9zIGVzdGEgdGFibGEgZW4gdW4gbnVldm8gZGF0YSBmcmFtZToNCmBgYHtyfQ0KZGZfc3cxIDwtIGRmX3N3ICU+JQ0KICBtdXRhdGUoYm1pID0gbWFzcy8oKGhlaWdodC8xMDApKioyKSkNCmBgYA0KDQpWZWFtb3Mgc2kgY29uc2VndWltb3MgbG8gZGVzZWFkbzoNCmBgYHtyfQ0KZ2xpbXBzZShkZl9zdzEpDQpWaWV3KGRmX3N3MSkNCmBgYA0KVXRpbGljZW1vcyBhaG9yYSBlc3RhIG51ZXZhIGZ1bmNpw7NuIGVuIGNvbmp1bnRvIGNvbiAqc3VtbWFyaXNlKi4gUXVlcmVtb3MgY2FsY3VsYXIgZWwgcHJvbWVkaW8geSBsYSBkZXN2aWFjacOzbiBlc3RhbmRhciBkZSBsYSBtYXNhIGNvcnBvcmFyLCBwZXJvIGFkZW3DoXMgcXVlcmVtb3Mgc2FiZXIgZWwgY29lZmljaWVudGUgZGUgdmFyaWFjacOzbiAqQ1YqIGRhZG8gcG9yOg0KDQokJCBDViA9IFxmcmFje0Rlc3YuIEVzdC59e01lZGlhfSAkJA0KYGBge3J9DQpkZl9zdzEgJT4lDQogIHN1bW1hcmlzZShtZWRpYSA9IG1lYW4oYm1pLCBuYS5ybSA9IFRSVUUpLA0KICAgICAgICAgICAgZGVzdmVzdCA9IHNkKGJtaSwgbmEucm0gPSBUUlVFKSkgJT4lDQogIG11dGF0ZShjdiA9IGRlc3Zlc3QvbWVkaWEpDQpgYGANCkFkaWNpb25lbW9zIGFob3JhIGVuIG51ZXN0cmEgdGFibGEgZGUgZnJlY3VlbmNpYSwgeWEgbm8gc29hbG1lbnRlIGVsIGNvbnRlbyBhYnNvbHV0bywgc2lubyB0YW1iacOpbiBlbCBjb250ZW8gcmVsYXRpdm8geSBlbCBjb250ZW8gYWN1bXVsYWRvLiBSZWN1ZXJkZSB1c2FyIHRhbnRvIGxhcyBmdW5jaW9uZXMgKmNvdW50KiwgKmFycmFuZ2UqLCAqbXV0YXRlKi4NCmBgYHtyfQ0KZGZfc3cxICU+JQ0KICBjb3VudChzcGVjaWVzLA0KICAgICAgICBoYWlyX2NvbG9yKSAlPiUNCiAgYXJyYW5nZSgtbikgJT4lDQogIG11dGF0ZShuX3JlbCA9IG4vc3VtKG4sIG5hLnJtID0gVFJVRSkpICU+JQ0KICBtdXRhdGUobl9hY3VtID0gY3Vtc3VtKG4pKQ0KYGBgDQpJbnRlbnRlIGNvbXByZW5kZXIgeSBleHBsaWNhciB0b2RvIGxvIHJlYWxpemFkbyBlbiBlc3RhIHNpbnRheGlzLg0KDQojIyMgMi40IEFncnVwYW1pZW50byBkZSB2YXJpYWJsZXMNCg0KRW4gbXVjaGFzIG9jYXNpb25lcyB2YW1vcyBhIHF1ZXJlciBoYWNlciB0YWJsYXMsIGNvbnRlb3MgeSBjcmVhY2lvbmVzICBkZSBmb3JtYSBjb25zb2xpZGFkYSBwb3IgYWxndW5hIHZhcmlhYmxlLiBQYXJhIGVzdGUgdGlwbyBkZSBjYXNvcyBzZSB1c2EgbGEgZnVuY2nDs24gKmdyb3VwX2J5Ki4NCg0KU3Vwb25nYW1vcyBxdWUgZGVzZWFtb3MgY3JlYXIgdW5hIG51ZXZhIHZhcmlhYmxlIHF1ZSBldmlkZW5jaWUgbGEgZGlmZXJlbmNpYSBlbiBjZW50w61tZXRyb3MgcXVlIGNhZGEgb2JzZXJ2YWNpw7NuIHRpZW5lIGNvbiByZWxhY2nDs24gYSBsYSBhbHR1cmEgcHJvbWVkaW8gZGUgc3UgZXNwZWNpZSAoKmRpZl9oZWlnaHQqKS4gRXN0byBlczoNCg0KJCQgZGlmX3toZWlnaHR9ID0gaGVpZ2h0IC0gXG92ZXJsaW5le2hlaWdodF97c3BlY2llfX0gJCQNCg0KYGBge3J9DQpkZl9zdzIgPC0gZGZfc3cxICU+JQ0KICBncm91cF9ieShzcGVjaWVzKSAlPiUNCiAgbXV0YXRlKGRpZl9oZWlnaHQgPSBoZWlnaHQgLSBtZWFuKGhlaWdodCwgbmEucm0gPSBUUlVFKSkgJT4lDQogIHVuZ3JvdXAoKQ0KYGBgDQoNCk5vdGVtb3MgbGFzIHNpZ3VpZW50ZXMgY29zYXM6DQoNCiogQWwgdXNhciBsYSBmdW5jacOzbiAqZ3JvdXBfYnkqLCBsZSBoZW1vcyBkaWNobyBhIFIgcXVlIHRvZG9zIGxvcyBjw6FsY3Vsb3Mgc3Vic2lndWllbnRlcyBxdWUgcmVhbGljZSBsb3MgaGFnYSBnZW5lcmFuZG8gZ3J1cG9zIChlbiBlc3RlIGNhc28gcG9yIGxhIHZhcmlhYmxlICpzcGVjaWVzKikuIEVuIGNvbnNlY3VlbmNpYSwgbGEgbWVkaWEgY2FsY3VsYWRhIHNlcsOhIGRpZmVyZW50ZSBwYXJhIGNhZGEgZ3J1cG8uIA0KKiBIZW1vcyB1c2FkbyB1bmEgZnVuY2nDs24gYWRpY2lvbmFsIGEgKmdyb3VwX2J5KiB1bmEgdmV6IGNvbmNsdWlkYSBudWVzdHJhIG5lY2VzaWRhZCBkZSBhZ3J1cGFyLiBFc3RhIGZ1bmNpw7NuICp1bmdyb3VwKiBsZSBkaWNlIGEgUiBxdWUgZGUgYWxsw60gZW4gYWRlbGFudGUgeWEgbm8gZGViZSBjb25zaWRlcmFyIGxvcyBncnVwbyBkZSBsYSB2YXJpYWJsZSAqc3BlY2llcyogcGFyYSBsb3MgY8OhbGN1bG9zLg0KDQpEZW1vcyB1bmEgbWlyYWRhIGEgZXN0YSB0YWJsYSBjcmVhZGE6DQpgYGB7cn0NCmdsaW1wc2UoZGZfc3cyKQ0KVmlldyhkZl9zdzIpDQpgYGANCkhhZ2Ftb3MgYWhvcmEgdW4gcmVzdW1lbiBlc3RhZMOtc3RpY28sIGRvbmRlIHRlbmdhbW9zIGxhIG1lZGlhIHkgbGEgbWVkaWFuYSBwYXJhICpibWkqIHBlcm8gYWdydXBhbmRvIHBvciAqc3BlY2llcyogeSBwb3IgKnNleCouIFJFY3VlcmRlIGRlc2FncnVwYXIgYWwgZmluYWwuDQpgYGB7cn0NCmRmX3N3MiAlPiUNCiAgZ3JvdXBfYnkoc3BlY2llcywNCiAgICAgICAgICAgc2V4KSAlPiUNCiAgc3VtbWFyaXNlKHByb20gPSBtZWFuKGJtaSwgbmEucm0gPSBUUlVFKSwNCiAgICAgICAgICAgIG1lZGlhbmEgPSBtZWRpYW4oYm1pLCBuYS5ybSA9IFRSVUUpKSAlPiUNCiAgdW5ncm91cCgpDQpgYGANCiMjIyAyLjUgRmlsdHJvcyBkZSB2YXJpYWJsZXMNCg0KT3RyYSBmb3JtYSBkZSBtYW5pcHVsYXIgZGF0b3MgZXMgbWVkaWFudGUgbGEgZmlsdHJhY2nDs24gZGUgdmFyaWFibGVzIHNvYnJlIGxhIGJhc2UgZGUgb3BlcmFjaW9uZXMgbMOzZ2ljYXMgKGFxdWVsbGFzIHF1ZSBkZXZ1ZWx2ZW4gY29tbyByZXN1bHRhZG8gdW5hIG9wY2nDs24gYnVsZWFuYSAiVFJVRS9GQUxTRSIpLiBTdXBvbmdhbW9zIHF1ZSBxdWVyZW1vcyB2aXN1YWxpemFyIHNvbGFtZW50ZSBhcXVlbGxhcyBvYnNlcnZhY2lvbmVzIGN1eWEgYWx0dXJhIHNlYSBtYXlvciBhbCBwcm9tZWRpbyBkZSBzdSBlc3BlY2llLiBQYXJhIGVzdG8gdXNhbW9zIGxhIGZ1bmNpw7NuICpmaWx0ZXIqIHkgYXByb3ZlY2hlbW9zIHBhcmEgb3JkZW5hciBkZSBmb3JtYSBhc2NlbmRlbnRlIHBvciAqaGVpZ2h0Ki4NCmBgYHtyfQ0KZGZfc3dfYWx0b3MgPC0gZGZfc3cyICU+JQ0KICBmaWx0ZXIoZGlmX2hlaWdodCA+IDApICU+JQ0KICBhcnJhbmdlKGhlaWdodCkNCmBgYA0KDQpWZWFtb3MgbG9zIHJlc3VsdGFkb3Mgb2J0ZW5pZG9zDQpgYGB7cn0NClZpZXcoZGZfc3dfYWx0b3MpDQpgYGANCg0KU3Vwb25nYW1vcyBhaG9yYSBxdWUgcXVlcmVtb3MgdmlzdWFsaXphciBzb2xhbWVudGUgYSBhcXVlbGxhcyBvYnNlcnZhY2lvbmVzIGN1eW8gcGxhbmV0YSBkZSBvcmlnZW4gc2VhICJUYXRvb2luZSIgKGVsIHF1ZSB0aWVuZSAyIHNvbGVzKSB5IHNlYW4gZHJvaWRlcy4gDQpgYGB7cn0NCmRmX3N3X3RhdGRyb2lkIDwtIGRmX3N3MiAlPiUNCiAgZmlsdGVyKGhvbWV3b3JsZCA9PSAiVGF0b29pbmUiICYgc3BlY2llcyA9PSAiRHJvaWQiKQ0KYGBgDQoNClZlYW1vcyBzaSBsbyBjb25zZWd1aW1vczoNCmBgYHtyfQ0KVmlldyhkZl9zd190YXRkcm9pZCkNCmBgYA0KDQpSZWN1ZXJkZSBxdWUgcGFyYSBlc3RhYmxlY2VyIGxhIG9wZXJhY2nDs24gQU5EIHNlIHV0aWxpemEgZWwgc8OtbWJvbG8gIiYiIHkgcGFyYSBsYSBPUiBzZSB1dGlsaXphICJ8Ii4NCg0KT3RyYSBmb3JtYSBkZSBmaWx0cmFyLCBzZSBwdWVkZSBkYXIgY29uIGxhcyBmdW5jaW9uZXMgKnRvcF9uKiwgKnRvcF9mcmFjKiwgKnNhbXBsZV9uKiB5ICpzYW1wbGVfZnJhYyouIDUgcHVudG9zIGV4dHJhcyBhbCBwcmltZXIgZXN0dWRpYW50ZSBxdWUgbWUgcmVtaXRhIGVqZW1wbG9zIHVzYW5kbyBlc3RhcyA0IGZ1bmNpb25lcyBlbiBlbCBkYXRhc2V0IGNvbiBlbCBxdWUgZXN0YW1vcyB0cmFiYWphbmRvLg0KDQojIyMgNC42IFNlbGVjY2nDs24gZGUgdmFyaWFibGVzDQoNClNlIHB1ZWRlbiB0YW1iacOpbiBzZWxlY2Npb25hciBvIGVsaW1pbmFyIGNvbHVtbmFzIGVzcGVjw61maWNhcyBkZSB1bmEgdGFibGEgZGUgZGF0b3MsIGRlIGZvcm1hIHF1ZSBudWVzdHJvIGRhdGEgZnJhbWUgZmluYWwgdGVuZ2EgbG8gZXN0cmljdGFtZW50ZSBuZWNlc2FyaW8gcGFyYSBhbsOhbGlzaXMgcG9zdGVyaW9yZXMuIENvbiBlc3RlIHByb3DDs3NpdG8gZXhpc3RlIGxhIGZ1bmNpw7NuICpzZWxlY3QqLg0KDQpTdXBvbmdhbW9zIHF1ZSBxdWVyZW1vcyBzZWxlY2Npb25hciBzb2xhbWVudGUgbGFzIGNvbHVtbmFzICpuYW1lKiwgKmhlaWdodCogeSAqbWFzcyouDQpgYGB7cn0NCmRmX3N3X3NlbGVjdCA8LSBkZl9zdzIgJT4lDQogIHNlbGVjdChuYW1lLA0KICAgICAgICAgaGVpZ2h0LA0KICAgICAgICAgbWFzcykNCmBgYA0KDQpWZWFtb3Mgc2kgbG9ncmFtb3MgbG8gcXVlIGJ1c2PDoWJhbW9zOg0KYGBge3J9DQpnbGltcHNlKGRmX3N3X3NlbGVjdCkNCmBgYA0KDQpBaG9yYSwgdmVhbW9zIGVsIGNhc28gZW4gcXVlIHF1ZXJlbW9zIGVsaW1pbmFyIGRvcyB2YXJpYWJsZXM6ICpiaXJ0aF95ZWFyKiB5ICpnZW5kZXIqLg0KYGBge3J9DQpkZl9zd19lbGltIDwtIGRmX3N3MiAlPiUNCiAgc2VsZWN0KC1iaXJ0aF95ZWFyLA0KICAgICAgICAgLWdlbmRlcikNCmBgYA0KDQpWZWFtb3MgZWwgcmVzdWx0YWRvOg0KYGBge3J9DQpnbGltcHNlKGRmX3N3X2VsaW0pDQpgYGANCk5vdGVtb3MgZmluYWxtZW50ZSBxdWUgdGFudG8gbGEgZnVuY2nDs24gKnNlbGVjdCogY29tbyAqZmlsdGVyKiBzZSBjb25zdGl0dXllbiBlbiBudWV2YXMgZm9ybWFzIGRlIGluZGV4YWNpw7NuIGRlIGRhdGEgZnJhbWVzLiBQb3IgZWplbXBsbywgc2kgcXVlcmVtb3Mgc2FiZXIgbG9zIG5vbWJyZXMgZGUgbGFzIG9ic2VydmFjaW9uZXMgZGUgdG9kb3MgYXF1ZWxsb3MgY3V5YSBlc3BlY2llIHNlYSAiRXdvayIsIHNlIGhhY2UgbG8gc2lndWllbnRlOg0KYGBge3J9DQojSW5kZXhhY2lvbiB0cmFkaWNpb25hbA0KZGZfc3cyJG5hbWVbd2hpY2goZGZfc3cyJHNwZWNpZXMgPT0gIkV3b2siKV0NCg0KI0luZGV4YWNpb24gY29uIHBpcGVzIHkgZnVuY2lvbmVzDQpkZl9zdzIgJT4lDQogIGZpbHRlcihzcGVjaWVzID09ICJFd29rIikgJT4lDQogIHNlbGVjdChuYW1lKQ0KYGBg