Si te encontrás una y otra vez copiando la misma linea de código para realizar una operación entonces es hora de escribir una función que haga esa tarea por vos. Hasta ahora estuvimos utilizando funciones que vienen con R (lo que llamamos R base) y funciones de librerías que están disponibles para extender las funcionalidades de R. Ahora vamos a ver como definir funciones propias.

Esta función será un conjunto de comandos o líneas de código que podrá ser definida para usarla en el futuro, con un nombre (con suerte uno que represente la acción u operación para la cual la estamos creando), elementos de entrada y el resultado final.

Vamos a definir la primera función para convertir viento en nudos a viento en metros por segundo que vamos a llamar nudos_a_ms().

nudos_a_ms <- function(viento) {
  ms <- viento * 0.5144
  return(ms)
}

function define nuestra función nudos_a_ms() y al correr esas lineas de código se generará un nuevo elemento en nuestro ambiente, la función está lista para ser usada. Pero antes de probarla revisemos la receta para definir una función.

Ya vimos que necesitamos declarar que esto es una función con function(), luego le sigue el cuerpo de la función, las líneas de código que se ejecutarán, y que van entre llaves {}. Usualmente indentamos el código para que se lea mejor, al igual que pasa cuando escribimos el código para generar un gráfico. Pero estos espacios antes del comienzo de cada renglón no son necesarios para que el código funcione.

La función puede recibir uno o más elementos de entrada: los argumentos. En este caso solo recibe un argumento al que llamamos viento. Al final usamos la función return(), que devolverá un valor o elemento que resulte de los cálculos previos.

Ahora probemos calcular cuantos metros por segundos son 15 nudos usando la función.

nudos_a_ms(viento = 15)
## [1] 7.716

El elemento de entrada en este caso es un número (15) y nos devolvió también un número. Pero podríamos tener un vector de números que corresponden a la velocidad del viento en nudos y que necesitamos convertir a metros por segundos. ¿Cuál crees que será el resultado?

viento_de_ayer <- c(0, 5, 15, 2, 5, 10)

Ahora el elemento de entrada es un vector de números y por lo tanto, la función nos devolverá otro vector aplicando la operación viento * 0.5144 a cada elemento del vector.

nudos_a_ms(viento = viento_de_ayer)
## [1] 0.0000 2.5720 7.7160 1.0288 2.5720 5.1440

Desafío

Escribí una nueva función que se llame ms_a_nudos() que tome el viento en metros por segundos y lo convierta a nudos. Pista: ahora tenés que multiplicar por 1.944.

Funciones con condiciones

Ahora que ya tenemos un par de funciones escritas, es importante asegurarnos de que funcionen de la manera en la que queremos. En este caso esperamos que dado un valor numérico nos devuelva otro valor numérico. ¿Qué pasaría si recibe un caracter?

nudos_a_ms(viento = "fuerte")
## Error in viento * 0.5144: non-numeric argument to binary operator

Por supuesto, nos da error, aunque uno no muy claro. También podríamos querer que la función primero revise si el o los elementos de entrada cumplen con determinadas condiciones, por ejemplo que sea un valor numérico.

nudos_a_ms <- function(viento) {
  stopifnot(is.numeric(viento))  # ¿Es de tipo numérico?
  
  ms <- viento * 0.5144
  return(ms)
}

Ahora si el valor de entrada es “15”, la función falla.

nudos_a_ms(viento = "15")
## Error in nudos_a_ms(viento = "15"): is.numeric(viento) is not TRUE

El error es un poquito más informativo, pero podrías ser mejor! Lo bueno es que podríamos incluir más de una condición, por ejemplo que el valor de entrada viento sea menor a un valor máximo.

Desafío

A tu función ms_a_nudos() agregale alguna condición que debe cumplir el argumento de entrada y ejecutala nuevamente.

Algo importante a tener en cuenta: cualquier variable o elemento que se crea o modifica dentro del cuento de la función solo existe mientras la función se está ejecutando. Por ejemplo la variable ms que creamos adentro de la función nudos_a_ms() solo existe adentro de esa función. Podés revisar el ambiente de R y comprobar que no hay ninguna variable llamada ms aún luego de ejecutar la función nudos_a_ms() varias veces.

Aplicaciones

Hasta ahora definimos funciones que tal vez no tengan mucha aplicación, al ejecutar estas funciones tampoco guardamos el resultado en una nueva variable porque estábamos probando como funcionaban. Ahora vamos a ver como incorporar todo lo anterior a nuestro flujo de análisis.

Vamos a seguir trabajando con diamantes y el objetivo final será generar una nueva columna que contenga el volumen de cada uno de los diamantes en esa base de datos. Cómo te imaginarás vamos a usar la función mutate() para crear esta nueva columna.

library(datos)
library(dplyr)

diamantes %>% 
  mutate(volumen = x * y * z) # El volumen se calcula como ancho x alto x largo asumiendo que es un cubo
## # A tibble: 53,940 x 11
##    precio quilate corte color claridad profundidad tabla     x     y     z
##     <int>   <dbl> <ord> <ord> <ord>          <dbl> <dbl> <dbl> <dbl> <dbl>
##  1    326   0.23  Ideal E     SI2             61.5    55  3.95  3.98  2.43
##  2    326   0.21  Prem… E     SI1             59.8    61  3.89  3.84  2.31
##  3    327   0.23  Bueno E     VS1             56.9    65  4.05  4.07  2.31
##  4    334   0.290 Prem… I     VS2             62.4    58  4.2   4.23  2.63
##  5    335   0.31  Bueno J     SI2             63.3    58  4.34  4.35  2.75
##  6    336   0.24  Muy … J     VVS2            62.8    57  3.94  3.96  2.48
##  7    336   0.24  Muy … I     VVS1            62.3    57  3.95  3.98  2.47
##  8    337   0.26  Muy … H     SI1             61.9    55  4.07  4.11  2.53
##  9    337   0.22  Regu… E     VS2             65.1    61  3.87  3.78  2.49
## 10    338   0.23  Muy … H     VS1             59.4    61  4     4.05  2.39
## # … with 53,930 more rows, and 1 more variable: volumen <dbl>

El cálculo del volumen es bastante directo y en principio no parece ser necesario escribir una nueva función para hacer esto. Sin embargo, si vamos a hacer esto muchas veces, tal vez sea conveniente definir una función volumen() que haga el cálculo. Al mismo tiempo el código es mucho más claro!

Definamos esta función que ahora requiere 3 argumentos:

volumen <- function(ancho, alto, largo) {
  stopifnot(is.numeric(ancho), is.numeric(alto), is.numeric(largo))
  
  total <- ancho * alto * largo
  return(total)
}

Ahora con nuestra función definida podemos calcular el volumen:

diamantes %>% 
  mutate(volumen = volumen(x, y, z))
## # A tibble: 53,940 x 11
##    precio quilate corte color claridad profundidad tabla     x     y     z
##     <int>   <dbl> <ord> <ord> <ord>          <dbl> <dbl> <dbl> <dbl> <dbl>
##  1    326   0.23  Ideal E     SI2             61.5    55  3.95  3.98  2.43
##  2    326   0.21  Prem… E     SI1             59.8    61  3.89  3.84  2.31
##  3    327   0.23  Bueno E     VS1             56.9    65  4.05  4.07  2.31
##  4    334   0.290 Prem… I     VS2             62.4    58  4.2   4.23  2.63
##  5    335   0.31  Bueno J     SI2             63.3    58  4.34  4.35  2.75
##  6    336   0.24  Muy … J     VVS2            62.8    57  3.94  3.96  2.48
##  7    336   0.24  Muy … I     VVS1            62.3    57  3.95  3.98  2.47
##  8    337   0.26  Muy … H     SI1             61.9    55  4.07  4.11  2.53
##  9    337   0.22  Regu… E     VS2             65.1    61  3.87  3.78  2.49
## 10    338   0.23  Muy … H     VS1             59.4    61  4     4.05  2.39
## # … with 53,930 more rows, and 1 more variable: volumen <dbl>

Y tal vez necesitemos el volumen promedio para algún tipo de corte.

diamantes %>% 
  mutate(volumen = volumen(x, y, z)) %>% 
  filter(corte == "Ideal") %>% 
  summarise(volumen_promedio = mean(volumen))
## # A tibble: 1 x 1
##   volumen_promedio
##              <dbl>
## 1             115.

Como siempre en R, hay muchas maneras de resolver cada problema. Otra posible opción sería definir una función que en un solo paso nos devuelva el volumen promedio de los diamantes de algún corte en particular.

volumen_promedio <- function(datos, este_corte) {
  datos %>% 
    filter(corte == este_corte) %>% 
    summarise(volumen_promedio = mean(x * y * z)) 
}
volumen_promedio(diamantes, "Ideal")
## # A tibble: 1 x 1
##   volumen_promedio
##              <dbl>
## 1             115.

Ahora la nueva función volumen_promedio() requiere 2 argumentos, el data.frame con los datos y una cadena de caracteres indicando el tipo de corte. Es posible que hayas notado que no incluimos la función return(), esto es porque R devuelve el último elemento que se generó, en este caso el resultado final de esa cadena de comandos y que coincide con la salida que esperamos.

Desafío

Al igual que vimos en las funciones de R base o por ejemplo de tidyverse, los argumentos de las funciones definidas por nosotros, también pueden tener valores por defecto. Esto quiere decir que podríamos querer que la función volumen_promedio() calcule el volumen promedio para el corte “Regular” a menos que le indiquemos otra cosa.

El valor por defecto de un argumento debe ser definido al momento de crear la función. Probá cambiar corte por corte = "Regular" como argumento de la función volumen_promedio() y ejecutala nuevamente indicando y omitiendo el tipo de corte en distintos casos.

Documentación y organización

Nombrar funciones (y cualquier otro elemento) es todo un desafío, necesitamos que se informativo pero al mismo tiempo corto y simple de escribir. Por ahora estuvimos haciendo un trabajo aproximadamente aceptable pero si volvemos a revisar estas funciones dentro de un mes o dentro de un año tal vez no recordemos exactamente como funciona una función que creamos. Tal vez imaginemos que va a calcular el volumen, ¿pero los elementos de entrada serán vectores o un data.frame? ¿Que tipo de elemento devuelve?

Por esa razón nunca está de más documentar la función, es decir, describir para que funciona, que elementos requiere, que es lo que devuelve y por que no, un ejemplo. No necesitamos generar una documentación tan detallada como la que encontramos en la ayuda de cada función de dplyr, pero es bueno tener cierto orden.

Y a medida que ganemos práctica y experiencia programando, el abanico de funciones propias que simplifican el trabajo van a ir creciendo. Y si bien podemos copiarlas y pegarlas una y otra vez en cada archivo que usemos y en cada análisis que generemos, esto puede ser trabajoso y las múltiples copias de la misma función ponen en riesgo que nuestro trabajo sea reproducible.

Existe una alternativa mucho más ordenada, es posible guardar todas las funciones en un único archivo de texto o script de R y cargar esas funciones al comienzo del análisis de la misma manera que cargamos los paquetes. Pero a diferencia de los paquetes que se cargan con library(), un script se carga con la función source().

Desafío

  1. Generá un nuevo archivo que se llame funciones_utiles.R. Podés hacerlo desde el menú File → New File → R Script o con el atajo Ctrl + Shift + N
  2. Copiá las funciones volumen() y volumen_promedio() y agregá una breve descripción junto a cada una usando comentarios (la línea debe comenzar con #).
  3. Guardá el archivo dentro de la carpeta del proyecto.
  4. En el archivo .Rmd donde venís trabajando, agregá una línea en el primer bloque de código para cargar estas funciones source("funciones_utiles.R").
LS0tCnRpdGxlOiAiRnVuY2lvbmVzIgpvdXRwdXQ6IAogIGh0bWxfZG9jdW1lbnQ6CiAgICBjb2RlX2Rvd25sb2FkOiB0cnVlCiAgICB0b2M6IHRydWUKICAgIHRvY19mbG9hdDogZmFsc2UKICAgIGhpZ2hsaWdodDogdGFuZ28KLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpgYGAKClNpIHRlIGVuY29udHLDoXMgdW5hIHkgb3RyYSB2ZXogY29waWFuZG8gbGEgbWlzbWEgbGluZWEgZGUgY8OzZGlnbyBwYXJhIHJlYWxpemFyIHVuYSBvcGVyYWNpw7NuIGVudG9uY2VzIGVzIGhvcmEgZGUgZXNjcmliaXIgdW5hIGZ1bmNpw7NuIHF1ZSBoYWdhIGVzYSB0YXJlYSBwb3Igdm9zLiBIYXN0YSBhaG9yYSBlc3R1dmltb3MgdXRpbGl6YW5kbyBmdW5jaW9uZXMgcXVlIHZpZW5lbiBjb24gUiAobG8gcXVlIGxsYW1hbW9zICpSIGJhc2UqKSB5IGZ1bmNpb25lcyBkZSBsaWJyZXLDrWFzIHF1ZSBlc3TDoW4gZGlzcG9uaWJsZXMgcGFyYSBleHRlbmRlciBsYXMgZnVuY2lvbmFsaWRhZGVzIGRlIFIuIEFob3JhIHZhbW9zIGEgdmVyIGNvbW8gZGVmaW5pciBmdW5jaW9uZXMgcHJvcGlhcy4KCkVzdGEgZnVuY2nDs24gc2Vyw6EgdW4gY29uanVudG8gZGUgY29tYW5kb3MgbyBsw61uZWFzIGRlIGPDs2RpZ28gcXVlIHBvZHLDoSBzZXIgZGVmaW5pZGEgcGFyYSB1c2FybGEgZW4gZWwgZnV0dXJvLCBjb24gdW4gbm9tYnJlIChjb24gc3VlcnRlIHVubyBxdWUgcmVwcmVzZW50ZSBsYSBhY2Npw7NuIHUgb3BlcmFjacOzbiBwYXJhIGxhIGN1YWwgbGEgZXN0YW1vcyBjcmVhbmRvKSwgZWxlbWVudG9zIGRlIGVudHJhZGEgeSBlbCByZXN1bHRhZG8gZmluYWwuIAoKVmFtb3MgYSBkZWZpbmlyIGxhIHByaW1lcmEgZnVuY2nDs24gcGFyYSBjb252ZXJ0aXIgdmllbnRvIGVuICpudWRvcyogYSB2aWVudG8gZW4gKm1ldHJvcyBwb3Igc2VndW5kbyogcXVlIHZhbW9zIGEgbGxhbWFyIGBudWRvc19hX21zKClgLiAKCmBgYHtyfQpudWRvc19hX21zIDwtIGZ1bmN0aW9uKHZpZW50bykgewogIG1zIDwtIHZpZW50byAqIDAuNTE0NAogIHJldHVybihtcykKfQpgYGAKCmBmdW5jdGlvbmAgZGVmaW5lIG51ZXN0cmEgZnVuY2nDs24gYG51ZG9zX2FfbXMoKWAgeSBhbCBjb3JyZXIgZXNhcyBsaW5lYXMgZGUgY8OzZGlnbyBzZSBnZW5lcmFyw6EgdW4gbnVldm8gZWxlbWVudG8gZW4gbnVlc3RybyBhbWJpZW50ZSwgbGEgZnVuY2nDs24gZXN0w6EgbGlzdGEgcGFyYSBzZXIgdXNhZGEuIFBlcm8gYW50ZXMgZGUgcHJvYmFybGEgcmV2aXNlbW9zIGxhICpyZWNldGEqIHBhcmEgZGVmaW5pciB1bmEgZnVuY2nDs24uIAoKWWEgdmltb3MgcXVlIG5lY2VzaXRhbW9zIGRlY2xhcmFyIHF1ZSBlc3RvIGVzIHVuYSBmdW5jacOzbiBjb24gYGZ1bmN0aW9uKClgLCBsdWVnbyBsZSBzaWd1ZSBlbCAqY3VlcnBvKiBkZSBsYSBmdW5jacOzbiwgbGFzIGzDrW5lYXMgZGUgY8OzZGlnbyBxdWUgc2UgZWplY3V0YXLDoW4sIHkgcXVlIHZhbiBlbnRyZSBsbGF2ZXMgYHt9YC4gVXN1YWxtZW50ZSAqaW5kZW50YW1vcyogZWwgY8OzZGlnbyBwYXJhIHF1ZSBzZSBsZWEgbWVqb3IsIGFsIGlndWFsIHF1ZSBwYXNhIGN1YW5kbyBlc2NyaWJpbW9zIGVsIGPDs2RpZ28gcGFyYSBnZW5lcmFyIHVuIGdyw6FmaWNvLiBQZXJvIGVzdG9zIGVzcGFjaW9zIGFudGVzIGRlbCBjb21pZW56byBkZSBjYWRhIHJlbmdsw7NuIG5vIHNvbiBuZWNlc2FyaW9zIHBhcmEgcXVlIGVsIGPDs2RpZ28gZnVuY2lvbmUuIAoKTGEgZnVuY2nDs24gcHVlZGUgcmVjaWJpciB1bm8gbyBtw6FzIGVsZW1lbnRvcyBkZSBlbnRyYWRhOiBsb3MgYXJndW1lbnRvcy4gRW4gZXN0ZSBjYXNvIHNvbG8gcmVjaWJlIHVuIGFyZ3VtZW50byBhbCBxdWUgbGxhbWFtb3MgYHZpZW50b2AuIEFsIGZpbmFsIHVzYW1vcyBsYSBmdW5jacOzbiBgcmV0dXJuKClgLCBxdWUgKmRldm9sdmVyw6EqIHVuIHZhbG9yIG8gZWxlbWVudG8gcXVlIHJlc3VsdGUgZGUgbG9zIGPDoWxjdWxvcyBwcmV2aW9zLiAKCkFob3JhIHByb2JlbW9zIGNhbGN1bGFyIGN1YW50b3MgbWV0cm9zIHBvciBzZWd1bmRvcyBzb24gMTUgbnVkb3MgdXNhbmRvIGxhIGZ1bmNpw7NuLgoKYGBge3J9Cm51ZG9zX2FfbXModmllbnRvID0gMTUpCmBgYAoKRWwgZWxlbWVudG8gZGUgZW50cmFkYSBlbiBlc3RlIGNhc28gZXMgdW4gbsO6bWVybyAoMTUpIHkgbm9zIGRldm9sdmnDsyB0YW1iacOpbiB1biBuw7ptZXJvLiBQZXJvIHBvZHLDrWFtb3MgdGVuZXIgdW4gdmVjdG9yIGRlIG7Dum1lcm9zIHF1ZSBjb3JyZXNwb25kZW4gYSBsYSB2ZWxvY2lkYWQgZGVsIHZpZW50byBlbiBudWRvcyB5IHF1ZSBuZWNlc2l0YW1vcyBjb252ZXJ0aXIgYSBtZXRyb3MgcG9yIHNlZ3VuZG9zLiDCv0N1w6FsIGNyZWVzIHF1ZSBzZXLDoSBlbCByZXN1bHRhZG8/CgpgYGB7cn0KdmllbnRvX2RlX2F5ZXIgPC0gYygwLCA1LCAxNSwgMiwgNSwgMTApCmBgYAoKQWhvcmEgZWwgZWxlbWVudG8gZGUgZW50cmFkYSBlcyB1biB2ZWN0b3IgZGUgbsO6bWVyb3MgeSBwb3IgbG8gdGFudG8sIGxhIGZ1bmNpw7NuIG5vcyBkZXZvbHZlcsOhIG90cm8gdmVjdG9yIGFwbGljYW5kbyBsYSBvcGVyYWNpw7NuIGB2aWVudG8gKiAwLjUxNDRgIGEgY2FkYSBlbGVtZW50byBkZWwgdmVjdG9yLgoKYGBge3J9Cm51ZG9zX2FfbXModmllbnRvID0gdmllbnRvX2RlX2F5ZXIpCmBgYAoKOjo6IHsuYWxlcnQgLmFsZXJ0LWluZm99CioqRGVzYWbDrW8qKgoKRXNjcmliw60gdW5hIG51ZXZhIGZ1bmNpw7NuIHF1ZSBzZSBsbGFtZSBgbXNfYV9udWRvcygpYCBxdWUgdG9tZSBlbCB2aWVudG8gZW4gbWV0cm9zIHBvciBzZWd1bmRvcyB5IGxvIGNvbnZpZXJ0YSBhIG51ZG9zLiBQaXN0YTogYWhvcmEgdGVuw6lzIHF1ZSBtdWx0aXBsaWNhciBwb3IgMS45NDQuCjo6OgoKIyMgRnVuY2lvbmVzIGNvbiBjb25kaWNpb25lcwoKQWhvcmEgcXVlIHlhIHRlbmVtb3MgdW4gcGFyIGRlIGZ1bmNpb25lcyBlc2NyaXRhcywgZXMgaW1wb3J0YW50ZSBhc2VndXJhcm5vcyBkZSBxdWUgZnVuY2lvbmVuIGRlIGxhIG1hbmVyYSBlbiBsYSBxdWUgcXVlcmVtb3MuIEVuIGVzdGUgY2FzbyBlc3BlcmFtb3MgcXVlIGRhZG8gdW4gdmFsb3IgKm51bcOpcmljbyogbm9zIGRldnVlbHZhIG90cm8gdmFsb3IgKm51bcOpcmljbyouIMK/UXXDqSBwYXNhcsOtYSBzaSByZWNpYmUgdW4gY2FyYWN0ZXI/IAoKYGBge3IgZXJyb3I9VFJVRX0KbnVkb3NfYV9tcyh2aWVudG8gPSAiZnVlcnRlIikKYGBgCgpQb3Igc3VwdWVzdG8sIG5vcyBkYSBlcnJvciwgYXVucXVlIHVubyBubyBtdXkgY2xhcm8uIFRhbWJpw6luIHBvZHLDrWFtb3MgcXVlcmVyIHF1ZSBsYSBmdW5jacOzbiBwcmltZXJvIHJldmlzZSBzaSBlbCBvIGxvcyBlbGVtZW50b3MgZGUgZW50cmFkYSBjdW1wbGVuIGNvbiBkZXRlcm1pbmFkYXMgY29uZGljaW9uZXMsIHBvciBlamVtcGxvIHF1ZSBzZWEgdW4gdmFsb3IgbnVtw6lyaWNvLgoKYGBge3J9Cm51ZG9zX2FfbXMgPC0gZnVuY3Rpb24odmllbnRvKSB7CiAgc3RvcGlmbm90KGlzLm51bWVyaWModmllbnRvKSkgICMgwr9FcyBkZSB0aXBvIG51bcOpcmljbz8KICAKICBtcyA8LSB2aWVudG8gKiAwLjUxNDQKICByZXR1cm4obXMpCn0KYGBgCgpBaG9yYSBzaSBlbCB2YWxvciBkZSBlbnRyYWRhIGVzICIxNSIsIGxhIGZ1bmNpw7NuIGZhbGxhLgoKYGBge3IgZXJyb3I9VFJVRX0KbnVkb3NfYV9tcyh2aWVudG8gPSAiMTUiKQpgYGAKCkVsIGVycm9yIGVzIHVuIHBvcXVpdG8gbcOhcyBpbmZvcm1hdGl2bywgcGVybyBwb2Ryw61hcyBzZXIgbWVqb3IhIExvIGJ1ZW5vIGVzIHF1ZSBwb2Ryw61hbW9zIGluY2x1aXIgbcOhcyBkZSB1bmEgY29uZGljacOzbiwgcG9yIGVqZW1wbG8gcXVlIGVsIHZhbG9yIGRlIGVudHJhZGEgYHZpZW50b2Agc2VhIG1lbm9yIGEgdW4gdmFsb3IgbcOheGltby4KCjo6OiB7LmFsZXJ0IC5hbGVydC1pbmZvfQoqKkRlc2Fmw61vKioKCkEgdHUgZnVuY2nDs24gYG1zX2FfbnVkb3MoKWAgYWdyZWdhbGUgYWxndW5hIGNvbmRpY2nDs24gcXVlIGRlYmUgY3VtcGxpciBlbCBhcmd1bWVudG8gZGUgZW50cmFkYSB5IGVqZWN1dGFsYSBudWV2YW1lbnRlLgo6OjoKCgo6Ojogey5hbGVydCAuYWxlcnQtc3VjY2Vzc30KCkFsZ28gaW1wb3J0YW50ZSBhIHRlbmVyIGVuIGN1ZW50YTogY3VhbHF1aWVyIHZhcmlhYmxlIG8gZWxlbWVudG8gcXVlIHNlIGNyZWEgbyBtb2RpZmljYSBkZW50cm8gZGVsIGN1ZW50byBkZSBsYSBmdW5jacOzbiAqKnNvbG8gZXhpc3RlIG1pZW50cmFzIGxhIGZ1bmNpw7NuIHNlIGVzdMOhIGVqZWN1dGFuZG8qKi4gUG9yIGVqZW1wbG8gbGEgdmFyaWFibGUgYG1zYCBxdWUgY3JlYW1vcyBhZGVudHJvIGRlIGxhIGZ1bmNpw7NuIGBudWRvc19hX21zKClgIHNvbG8gZXhpc3RlIGFkZW50cm8gZGUgZXNhIGZ1bmNpw7NuLiBQb2TDqXMgcmV2aXNhciBlbCBhbWJpZW50ZSBkZSBSIHkgY29tcHJvYmFyIHF1ZSBubyBoYXkgbmluZ3VuYSB2YXJpYWJsZSBsbGFtYWRhIGBtc2AgYcO6biBsdWVnbyBkZSBlamVjdXRhciBsYSBmdW5jacOzbiBgbnVkb3NfYV9tcygpYCB2YXJpYXMgdmVjZXMuCjo6OgoKIyMgQXBsaWNhY2lvbmVzCgpIYXN0YSBhaG9yYSBkZWZpbmltb3MgZnVuY2lvbmVzIHF1ZSB0YWwgdmV6IG5vIHRlbmdhbiBtdWNoYSBhcGxpY2FjacOzbiwgYWwgZWplY3V0YXIgZXN0YXMgZnVuY2lvbmVzIHRhbXBvY28gZ3VhcmRhbW9zIGVsIHJlc3VsdGFkbyBlbiB1bmEgbnVldmEgdmFyaWFibGUgcG9ycXVlIGVzdMOhYmFtb3MgcHJvYmFuZG8gY29tbyBmdW5jaW9uYWJhbi4gQWhvcmEgdmFtb3MgYSB2ZXIgY29tbyBpbmNvcnBvcmFyIHRvZG8gbG8gYW50ZXJpb3IgYSBudWVzdHJvIGZsdWpvIGRlIGFuw6FsaXNpcy4KClZhbW9zIGEgc2VndWlyIHRyYWJhamFuZG8gY29uIGBkaWFtYW50ZXNgIHkgZWwgb2JqZXRpdm8gZmluYWwgc2Vyw6EgZ2VuZXJhciB1bmEgbnVldmEgY29sdW1uYSBxdWUgY29udGVuZ2EgZWwgdm9sdW1lbiBkZSBjYWRhIHVubyBkZSBsb3MgZGlhbWFudGVzIGVuIGVzYSBiYXNlIGRlIGRhdG9zLiBDw7NtbyB0ZSBpbWFnaW5hcsOhcyB2YW1vcyBhIHVzYXIgbGEgZnVuY2nDs24gYG11dGF0ZSgpYCBwYXJhIGNyZWFyIGVzdGEgbnVldmEgY29sdW1uYS4gCgpgYGB7cn0KbGlicmFyeShkYXRvcykKbGlicmFyeShkcGx5cikKCmRpYW1hbnRlcyAlPiUgCiAgbXV0YXRlKHZvbHVtZW4gPSB4ICogeSAqIHopICMgRWwgdm9sdW1lbiBzZSBjYWxjdWxhIGNvbW8gYW5jaG8geCBhbHRvIHggbGFyZ28gYXN1bWllbmRvIHF1ZSBlcyB1biBjdWJvCmBgYAoKRWwgY8OhbGN1bG8gZGVsIHZvbHVtZW4gZXMgYmFzdGFudGUgZGlyZWN0byB5IGVuIHByaW5jaXBpbyBubyBwYXJlY2Ugc2VyIG5lY2VzYXJpbyBlc2NyaWJpciB1bmEgbnVldmEgZnVuY2nDs24gcGFyYSBoYWNlciBlc3RvLiBTaW4gZW1iYXJnbywgc2kgdmFtb3MgYSBoYWNlciBlc3RvIG11Y2hhcyB2ZWNlcywgdGFsIHZleiBzZWEgY29udmVuaWVudGUgZGVmaW5pciB1bmEgZnVuY2nDs24gYHZvbHVtZW4oKWAgcXVlIGhhZ2EgZWwgY8OhbGN1bG8uIEFsIG1pc21vIHRpZW1wbyBlbCBjw7NkaWdvIGVzIG11Y2hvIG3DoXMgY2xhcm8hCgpEZWZpbmFtb3MgZXN0YSBmdW5jacOzbiBxdWUgYWhvcmEgcmVxdWllcmUgMyBhcmd1bWVudG9zOgoKYGBge3J9CnZvbHVtZW4gPC0gZnVuY3Rpb24oYW5jaG8sIGFsdG8sIGxhcmdvKSB7CiAgc3RvcGlmbm90KGlzLm51bWVyaWMoYW5jaG8pLCBpcy5udW1lcmljKGFsdG8pLCBpcy5udW1lcmljKGxhcmdvKSkKICAKICB0b3RhbCA8LSBhbmNobyAqIGFsdG8gKiBsYXJnbwogIHJldHVybih0b3RhbCkKfQpgYGAKCkFob3JhIGNvbiBudWVzdHJhIGZ1bmNpw7NuIGRlZmluaWRhIHBvZGVtb3MgY2FsY3VsYXIgZWwgdm9sdW1lbjoKCmBgYHtyfQpkaWFtYW50ZXMgJT4lIAogIG11dGF0ZSh2b2x1bWVuID0gdm9sdW1lbih4LCB5LCB6KSkKYGBgCgpZIHRhbCB2ZXogbmVjZXNpdGVtb3MgZWwgdm9sdW1lbiBwcm9tZWRpbyBwYXJhIGFsZ8O6biB0aXBvIGRlIGNvcnRlLgoKYGBge3J9CmRpYW1hbnRlcyAlPiUgCiAgbXV0YXRlKHZvbHVtZW4gPSB2b2x1bWVuKHgsIHksIHopKSAlPiUgCiAgZmlsdGVyKGNvcnRlID09ICJJZGVhbCIpICU+JSAKICBzdW1tYXJpc2Uodm9sdW1lbl9wcm9tZWRpbyA9IG1lYW4odm9sdW1lbikpCmBgYAoKCkNvbW8gc2llbXByZSBlbiBSLCBoYXkgbXVjaGFzIG1hbmVyYXMgZGUgcmVzb2x2ZXIgY2FkYSBwcm9ibGVtYS4gT3RyYSBwb3NpYmxlIG9wY2nDs24gc2Vyw61hIGRlZmluaXIgdW5hIGZ1bmNpw7NuIHF1ZSBlbiB1biBzb2xvIHBhc28gbm9zIGRldnVlbHZhIGVsIHZvbHVtZW4gcHJvbWVkaW8gZGUgbG9zIGRpYW1hbnRlcyBkZSBhbGfDum4gY29ydGUgZW4gcGFydGljdWxhci4KCmBgYHtyfQp2b2x1bWVuX3Byb21lZGlvIDwtIGZ1bmN0aW9uKGRhdG9zLCBlc3RlX2NvcnRlKSB7CiAgZGF0b3MgJT4lIAogICAgZmlsdGVyKGNvcnRlID09IGVzdGVfY29ydGUpICU+JSAKICAgIHN1bW1hcmlzZSh2b2x1bWVuX3Byb21lZGlvID0gbWVhbih4ICogeSAqIHopKSAKfQpgYGAKCmBgYHtyfQp2b2x1bWVuX3Byb21lZGlvKGRpYW1hbnRlcywgIklkZWFsIikKYGBgCgpBaG9yYSBsYSBudWV2YSBmdW5jacOzbiBgdm9sdW1lbl9wcm9tZWRpbygpYCByZXF1aWVyZSAyIGFyZ3VtZW50b3MsIGVsIGRhdGEuZnJhbWUgY29uIGxvcyBkYXRvcyB5IHVuYSBjYWRlbmEgZGUgY2FyYWN0ZXJlcyBpbmRpY2FuZG8gZWwgdGlwbyBkZSBjb3J0ZS4gRXMgcG9zaWJsZSBxdWUgaGF5YXMgbm90YWRvIHF1ZSBubyBpbmNsdWltb3MgbGEgZnVuY2nDs24gYHJldHVybigpYCwgZXN0byBlcyBwb3JxdWUgUiAqZGV2dWVsdmUqIGVsIMO6bHRpbW8gZWxlbWVudG8gcXVlIHNlIGdlbmVyw7MsIGVuIGVzdGUgY2FzbyBlbCByZXN1bHRhZG8gZmluYWwgZGUgZXNhIGNhZGVuYSBkZSBjb21hbmRvcyB5IHF1ZSBjb2luY2lkZSBjb24gbGEgc2FsaWRhIHF1ZSBlc3BlcmFtb3MuIAoKOjo6IHsuYWxlcnQgLmFsZXJ0LWluZm99CioqRGVzYWbDrW8qKgoKQWwgaWd1YWwgcXVlIHZpbW9zIGVuIGxhcyBmdW5jaW9uZXMgZGUgUiBiYXNlIG8gcG9yIGVqZW1wbG8gZGUgdGlkeXZlcnNlLCBsb3MgYXJndW1lbnRvcyBkZSBsYXMgZnVuY2lvbmVzIGRlZmluaWRhcyBwb3Igbm9zb3Ryb3MsIHRhbWJpw6luIHB1ZWRlbiB0ZW5lciB2YWxvcmVzIHBvciBkZWZlY3RvLiBFc3RvIHF1aWVyZSBkZWNpciBxdWUgcG9kcsOtYW1vcyBxdWVyZXIgcXVlIGxhIGZ1bmNpw7NuIGB2b2x1bWVuX3Byb21lZGlvKClgIGNhbGN1bGUgZWwgdm9sdW1lbiBwcm9tZWRpbyBwYXJhIGVsIGNvcnRlICJSZWd1bGFyIiAqYSBtZW5vcyBxdWUqIGxlIGluZGlxdWVtb3Mgb3RyYSBjb3NhLiAKCkVsIHZhbG9yIHBvciBkZWZlY3RvIGRlIHVuIGFyZ3VtZW50byBkZWJlIHNlciBkZWZpbmlkbyBhbCBtb21lbnRvIGRlIGNyZWFyIGxhIGZ1bmNpw7NuLiBQcm9iw6EgY2FtYmlhciBgY29ydGVgIHBvciBgY29ydGUgPSAiUmVndWxhciJgIGNvbW8gYXJndW1lbnRvIGRlIGxhIGZ1bmNpw7NuIGB2b2x1bWVuX3Byb21lZGlvKClgIHkgZWplY3V0YWxhIG51ZXZhbWVudGUgaW5kaWNhbmRvIHkgb21pdGllbmRvIGVsIHRpcG8gZGUgY29ydGUgZW4gZGlzdGludG9zIGNhc29zLgo6OjoKCiMjIERvY3VtZW50YWNpw7NuIHkgb3JnYW5pemFjacOzbgoKTm9tYnJhciBmdW5jaW9uZXMgKHkgY3VhbHF1aWVyIG90cm8gZWxlbWVudG8pIGVzIHRvZG8gdW4gZGVzYWbDrW8sIG5lY2VzaXRhbW9zIHF1ZSBzZSBpbmZvcm1hdGl2byBwZXJvIGFsIG1pc21vIHRpZW1wbyBjb3J0byB5IHNpbXBsZSBkZSBlc2NyaWJpci4gUG9yIGFob3JhIGVzdHV2aW1vcyBoYWNpZW5kbyB1biB0cmFiYWpvIGFwcm94aW1hZGFtZW50ZSBhY2VwdGFibGUgcGVybyBzaSB2b2x2ZW1vcyBhIHJldmlzYXIgZXN0YXMgZnVuY2lvbmVzIGRlbnRybyBkZSB1biBtZXMgbyBkZW50cm8gZGUgdW4gYcOxbyB0YWwgdmV6IG5vIHJlY29yZGVtb3MgZXhhY3RhbWVudGUgY29tbyBmdW5jaW9uYSB1bmEgZnVuY2nDs24gcXVlIGNyZWFtb3MuIFRhbCB2ZXogaW1hZ2luZW1vcyBxdWUgdmEgYSBjYWxjdWxhciBlbCB2b2x1bWVuLCDCv3Blcm8gbG9zIGVsZW1lbnRvcyBkZSBlbnRyYWRhIHNlcsOhbiB2ZWN0b3JlcyBvIHVuIGRhdGEuZnJhbWU/IMK/UXVlIHRpcG8gZGUgZWxlbWVudG8gZGV2dWVsdmU/CgpQb3IgZXNhIHJhesOzbiBudW5jYSBlc3TDoSBkZSBtw6FzIGRvY3VtZW50YXIgbGEgZnVuY2nDs24sIGVzIGRlY2lyLCBkZXNjcmliaXIgcGFyYSBxdWUgZnVuY2lvbmEsIHF1ZSBlbGVtZW50b3MgcmVxdWllcmUsIHF1ZSBlcyBsbyBxdWUgZGV2dWVsdmUgeSBwb3IgcXVlIG5vLCB1biBlamVtcGxvLiBObyBuZWNlc2l0YW1vcyBnZW5lcmFyIHVuYSBkb2N1bWVudGFjacOzbiB0YW4gZGV0YWxsYWRhIGNvbW8gbGEgcXVlIGVuY29udHJhbW9zIGVuIGxhIGF5dWRhIGRlIGNhZGEgZnVuY2nDs24gZGUgZHBseXIsIHBlcm8gZXMgYnVlbm8gdGVuZXIgY2llcnRvIG9yZGVuLiAKClkgYSBtZWRpZGEgcXVlIGdhbmVtb3MgcHLDoWN0aWNhIHkgZXhwZXJpZW5jaWEgcHJvZ3JhbWFuZG8sIGVsIGFiYW5pY28gZGUgZnVuY2lvbmVzIHByb3BpYXMgcXVlIHNpbXBsaWZpY2FuIGVsIHRyYWJham8gdmFuIGEgaXIgY3JlY2llbmRvLiBZIHNpIGJpZW4gcG9kZW1vcyBjb3BpYXJsYXMgeSBwZWdhcmxhcyB1bmEgeSBvdHJhIHZleiBlbiBjYWRhIGFyY2hpdm8gcXVlIHVzZW1vcyB5IGVuIGNhZGEgYW7DoWxpc2lzIHF1ZSBnZW5lcmVtb3MsIGVzdG8gcHVlZGUgc2VyIHRyYWJham9zbyB5IGxhcyBtw7psdGlwbGVzIGNvcGlhcyBkZSBsYSBtaXNtYSBmdW5jacOzbiBwb25lbiBlbiByaWVzZ28gcXVlIG51ZXN0cm8gdHJhYmFqbyBzZWEgcmVwcm9kdWNpYmxlLiAKCkV4aXN0ZSB1bmEgYWx0ZXJuYXRpdmEgbXVjaG8gbcOhcyBvcmRlbmFkYSwgZXMgcG9zaWJsZSBndWFyZGFyIHRvZGFzIGxhcyBmdW5jaW9uZXMgZW4gdW4gw7puaWNvIGFyY2hpdm8gZGUgdGV4dG8gbyAqc2NyaXB0KiBkZSBSIHkgY2FyZ2FyIGVzYXMgZnVuY2lvbmVzIGFsIGNvbWllbnpvIGRlbCBhbsOhbGlzaXMgZGUgbGEgbWlzbWEgbWFuZXJhIHF1ZSBjYXJnYW1vcyBsb3MgcGFxdWV0ZXMuIFBlcm8gYSBkaWZlcmVuY2lhIGRlIGxvcyBwYXF1ZXRlcyBxdWUgc2UgY2FyZ2FuIGNvbiBgbGlicmFyeSgpYCwgdW4gKnNjcmlwdCogc2UgY2FyZ2EgY29uIGxhIGZ1bmNpw7NuIGBzb3VyY2UoKWAuCgo6Ojogey5hbGVydCAuYWxlcnQtaW5mb30KKipEZXNhZsOtbyoqCgoxLiBHZW5lcsOhIHVuIG51ZXZvIGFyY2hpdm8gcXVlIHNlIGxsYW1lIGBmdW5jaW9uZXNfdXRpbGVzLlJgLiBQb2TDqXMgaGFjZXJsbyBkZXNkZSBlbCBtZW7DuiBGaWxlIOKGkiBOZXcgRmlsZSDihpIgUiBTY3JpcHQgbyBjb24gZWwgYXRham8gQ3RybCArIFNoaWZ0ICsgTgoyLiBDb3Bpw6EgbGFzIGZ1bmNpb25lcyBgdm9sdW1lbigpYCB5IGB2b2x1bWVuX3Byb21lZGlvKClgIHkgYWdyZWfDoSB1bmEgYnJldmUgZGVzY3JpcGNpw7NuIGp1bnRvIGEgY2FkYSB1bmEgdXNhbmRvIGNvbWVudGFyaW9zIChsYSBsw61uZWEgZGViZSBjb21lbnphciBjb24gYCNgKS4KMy4gR3VhcmTDoSBlbCBhcmNoaXZvIGRlbnRybyBkZSBsYSBjYXJwZXRhIGRlbCBwcm95ZWN0by4gCjQuIEVuIGVsIGFyY2hpdm8gLlJtZCBkb25kZSB2ZW7DrXMgdHJhYmFqYW5kbywgYWdyZWfDoSB1bmEgbMOtbmVhIGVuIGVsIHByaW1lciBibG9xdWUgZGUgY8OzZGlnbyBwYXJhIGNhcmdhciBlc3RhcyBmdW5jaW9uZXMgYHNvdXJjZSgiZnVuY2lvbmVzX3V0aWxlcy5SIilgLiAKCjo6OgoKCgo8ZGl2IGNsYXNzPSJidG4tZ3JvdXAiIHJvbGU9Imdyb3VwIiBhcmlhLWxhYmVsPSJOYXZlZ2FjacOzbiI+CiAgPGEgaHJlZj0gIjA4LWRwbHlyLXRpZHlyLUlJIiBjbGFzcyA9ICJidG4gYnRuLXByaW1hcnkiPkFudGVyaW9yPC9hPgogIDxhIGhyZWY9ICIxMC1yZXBvcnRlcy1JSS5odG1sIiBjbGFzcyA9ICJidG4gYnRuLXByaW1hcnkiPlNpZ3VpZW50ZTwvYT4KPC9kaXY+