Las librerías a usar.

library(jsonlite)
library(plyr)
library(dplyr)
library(ggplot2)
library(wesanderson)
library(viridisLite)
library(rgdal)
library(ggplot2)
library(rgeos)
library(reshape2)
library(arules)
library(tm)
library(wordcloud)
library(RColorBrewer)
library(arulesViz)
library(DT)
library(htmlwidgets)
library(DataExplorer)

#limpio la memoria
#rm(list=ls())
#gc()

1 Importación de archivos

1.1 Datos Json

1.2 Importación de datasets extra.

# Importamos los datos de las mediciones y de los barrios
mediciones = read.csv("./Datasets adicionales/mediciones.csv")
sucursales = read.csv("./Datasets adicionales/sucursales_barrios.csv")
productos = read.csv("./Datasets adicionales/productos_categoria.csv")
inflacion = read.csv("./Datasets adicionales/inflacion.csv")
precio_metros2 = read.csv("./barrios/precio_metro.csv")

1.3 Observamos la data, head() o glimpse()

head(productos)
glimpse(productos)
Observations: 1,016
Variables: 6
$ X_id..oid    <fct> 5cbc69be7af152186c0cd784, 5cbc69be7af1...
$ nombre       <fct> Aceite de Girasol Can?uelas 1.5 Lt, Ac...
$ Categoria    <fct> Aceite, Aceite, Aceite, Aceite, Aceite...
$ marca        <fct> CAÑUELAS, CAÑUELAS, COCINERO, COCINERO...
$ presentacion <fct> 1.5 lt, 900.0 cc, 1.5 lt, 900.0 ml, 1....
$ id           <fct> 7792180001665, 7792180001641, 77900600...
head(precios)
glimpse(precios)
Observations: 1,584,661
Variables: 6
$ `_id`    <df[,1]> <data.frame[21 x 1]>
$ producto <chr> "7790762052364", "12-1-2800000937881", "77...
$ sucursal <chr> "12-1-44", "12-1-44", "12-1-44", "12-1-44"...
$ precio   <dbl> 56.20, 76.99, 215.00, 92.87, 81.99, 70.25,...
$ fecha    <dttm> 2019-01-15 04:51:28, 2019-01-15 04:51:28,...
$ medicion <int> 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, ...

2 Inner Join

2.1 Unificamos datasets

Agregamos la info de los productos al df “precios”. Utilizamos inner join que matchea la columna producto del data frame precios y la columna ID de la columna del data frame productos

data1 = inner_join(precios, productos, by = c("producto" = "id"))

glimpse(data1)
Observations: 1,559,443
Variables: 11
$ `_id`        <df[,1]> <data.frame[21 x 1]>
$ producto     <chr> "7790762052364", "12-1-2800000937881",...
$ sucursal     <chr> "12-1-44", "12-1-44", "12-1-44", "12-1...
$ precio       <dbl> 56.20, 76.99, 215.00, 92.87, 81.99, 70...
$ fecha        <dttm> 2019-01-15 04:51:28, 2019-01-15 04:51...
$ medicion     <int> 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6,...
$ X_id..oid    <fct> 5cbc69be7af152186c0cd67e, 5cbc69be7af1...
$ nombre       <fct> Vino Rosado Seleccion Especial Santa A...
$ Categoria    <fct> Bebidas con alcohol, Conservas, Bebida...
$ marca        <fct> SANTA ANA, COTO, NIETO SANETINER, BODE...
$ presentacion <fct> 700.0 ml, 81.0 gr, 750.0 cc, 750.0 ml,...
glimpse(sucursales)
Observations: 837
Variables: 15
$ X_id..oid           <fct> 5cbc698b7af152186c0cd13f, 5cbc6...
$ sucursalTipo        <fct> Autoservicio, Autoservicio, Aut...
$ direccion           <fct> Av Dr. Ricardo Balbin 4881, San...
$ provincia           <fct> AR-C, AR-C, AR-C, AR-C, AR-C, A...
$ banderaId           <int> 1, 1, 3, 1, 1, 3, 1, 1, 1, 3, 1...
$ localidad           <fct> Capital Federal, CAPITAL FEDERA...
$ banderaDescripcion  <fct> Supermercados DIA, DEHEZA S.A.I...
$ lat                 <dbl> -34.55212, -34.55945, -34.54004...
$ comercioRazonSocial <fct> DIA Argentina S.A, Deheza S.A.I...
$ lng                 <dbl> -58.49841, -58.50503, -58.47474...
$ sucursalNombre      <fct> 480 - Saavedra, GRAL PAZ - NORT...
$ comercioId          <int> 15, 3, 10, 3, 3, 10, 15, 15, 15...
$ sucursalId          <int> 480, 1506, 675, 1507, 29, 300, ...
$ id                  <fct> 15-1-480, 3-1-1506, 10-3-675, 3...
$ barrio              <fct> SAAVEDRA, 0, SAAVEDRA, SAAVEDRA...

Agregamos la info de sucursales a “data1”, se crea un nuevo data frame, ver que ya no es necesario data1

data2 = inner_join(data1, sucursales, by = c("sucursal" = "id"))
glimpse(data2)
Observations: 1,559,443
Variables: 25
$ `_id`               <df[,1]> <data.frame[21 x 1]>
$ producto            <chr> "7790762052364", "12-1-28000009...
$ sucursal            <chr> "12-1-44", "12-1-44", "12-1-44"...
$ precio              <dbl> 56.20, 76.99, 215.00, 92.87, 81...
$ fecha               <dttm> 2019-01-15 04:51:28, 2019-01-1...
$ medicion            <int> 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6...
$ X_id..oid.x         <fct> 5cbc69be7af152186c0cd67e, 5cbc6...
$ nombre              <fct> Vino Rosado Seleccion Especial ...
$ Categoria           <fct> Bebidas con alcohol, Conservas,...
$ marca               <fct> SANTA ANA, COTO, NIETO SANETINE...
$ presentacion        <fct> 700.0 ml, 81.0 gr, 750.0 cc, 75...
$ X_id..oid.y         <fct> 5cbc698b7af152186c0cd187, 5cbc6...
$ sucursalTipo        <fct> Supermercado, Supermercado, Sup...
$ direccion           <fct> Av. Monroe 3284, Av. Monroe 328...
$ provincia           <fct> AR-C, AR-C, AR-C, AR-C, AR-C, A...
$ banderaId           <int> 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1...
$ localidad           <fct> Belgrano, Belgrano, Belgrano, B...
$ banderaDescripcion  <fct> COTO CICSA, COTO CICSA, COTO CI...
$ lat                 <dbl> -34.56358, -34.56358, -34.56358...
$ comercioRazonSocial <fct> Coto Centro Integral de Comerci...
$ lng                 <dbl> -58.46841, -58.46841, -58.46841...
$ sucursalNombre      <fct> MONROE , MONROE , MONROE , MONR...
$ comercioId          <int> 12, 12, 12, 12, 12, 12, 12, 12,...
$ sucursalId          <int> 44, 44, 44, 44, 44, 44, 44, 44,...
$ barrio              <fct> COGHLAN, COGHLAN, COGHLAN, COGH...

Borramos data1, nos quedamos con data2

rm(data1)
colnames(data2)
 [1] "_id"                 "producto"           
 [3] "sucursal"            "precio"             
 [5] "fecha"               "medicion"           
 [7] "X_id..oid.x"         "nombre"             
 [9] "Categoria"           "marca"              
[11] "presentacion"        "X_id..oid.y"        
[13] "sucursalTipo"        "direccion"          
[15] "provincia"           "banderaId"          
[17] "localidad"           "banderaDescripcion" 
[19] "lat"                 "comercioRazonSocial"
[21] "lng"                 "sucursalNombre"     
[23] "comercioId"          "sucursalId"         
[25] "barrio"             
data2[7]
#Solo me interesa el id de precio

2.2 Unión de todos los datasets

Realizo con la función selectm la columnas dentro de mi data2 que serviran para conformar mi dataset llamado precios claros. Cada consulta estará determinada por el ID del df Precios.


preciosclaros = select(data2, `_id`, "producto", "nombre", "Categoria", "marca", "presentacion", "precio", "medicion", "sucursal", "sucursalTipo", "banderaDescripcion", "comercioRazonSocial", "direccion", "barrio")
#Para liberar espacio puedo quedarme solo con este dataset PRECIOSCLAROS
rm(precios)
rm(data2)
glimpse(preciosclaros)
Observations: 1,559,443
Variables: 14
$ `_id`               <df[,1]> <data.frame[21 x 1]>
$ producto            <chr> "7790762052364", "12-1-28000009...
$ nombre              <fct> Vino Rosado Seleccion Especial ...
$ Categoria           <fct> Bebidas con alcohol, Conservas,...
$ marca               <fct> SANTA ANA, COTO, NIETO SANETINE...
$ presentacion        <fct> 700.0 ml, 81.0 gr, 750.0 cc, 75...
$ precio              <dbl> 56.20, 76.99, 215.00, 92.87, 81...
$ medicion            <int> 6, 6, 6, 6, 6, 6, 6, 6, 6, 6, 6...
$ sucursal            <chr> "12-1-44", "12-1-44", "12-1-44"...
$ sucursalTipo        <fct> Supermercado, Supermercado, Sup...
$ banderaDescripcion  <fct> COTO CICSA, COTO CICSA, COTO CI...
$ comercioRazonSocial <fct> Coto Centro Integral de Comerci...
$ direccion           <fct> Av. Monroe 3284, Av. Monroe 328...
$ barrio              <fct> COGHLAN, COGHLAN, COGHLAN, COGH...
#Auto explorer Precios claros
#introduce(preciosclaros)
#create_report(preciosclaros)
# Tratamiento de precios

Necesito pasar los precios de productos por sucursal a formato columnar. Cada fila representará un producto de una sucursal con diez columnas asociadas a los precios en cada medición.

Cabe que esta forma es la que tenemos más asociada al ahora de trabajar la data, sin embargo distintos algoritmos de predicción puedan requerir otra manera de organizar la data. Por ejemplo: Historia clinica a lo largo de la vida.

2.3 Funcion Dcast, RESHAPE

Reshape significa cambiar la forma de nuestro dataset, esto es determinar las filas y las columnas, que variables están en función de cual otra. Claramente esto debe tener sentido. Nos interesa en este momento ver los precios en funcion de cada medición. Es decir, nos interesa agrupar cada producto por sucursal, por presentacion, por barrio, según la medición en que fue tomado el dato.

La función dcast es un melt en donde que toma la forma LHS ~ RHS, ex: var1 + var2 ~ var3. El orden de las variables es esencial. Los valores LHS (antes del newflow ~ representan las filas y luego representan las columnas.

preciosclarosdcast = dcast(preciosclaros, producto + nombre + Categoria + marca + presentacion + sucursal + sucursalTipo + banderaDescripcion + comercioRazonSocial + direccion + barrio ~ medicion, fun.aggregate=NULL, value.var="precio")
head(preciosclarosdcast)

2.4 Faltantes

#is.na(preciosclarosdcast) # indica cuáles de los elementos son NA (TRUE)

# Para saber la cantidad exacta de NAs que están presentes en los datos 
sum(is.na(preciosclarosdcast))
[1] 0
#complete.cases(preciosclarosdcast) # Me devuelve los índices de los registros que NO tienen NAs
length(which(!complete.cases(preciosclarosdcast))) # Me permite saber cuántos registros tengo, sin hacer la cuenta
[1] 53170
# Tengo 53170 registros que tienen al menos 1 NA
colnames(preciosclarosdcast)[colSums(is.na(preciosclarosdcast)) > 0] # Obtengo las columnas que tienen un al menos NA. Mediciones sin datos.
 [1] "1"  "2"  "3"  "4"  "5"  "6"  "7"  "8"  "9"  "10"

2.5 Funcion Fun Modeling

funModeling::status(preciosclarosdcast)

2.6 Sustitucion por medias

#Hago una copia del dataset para trabajar
df = preciosclarosdcast
#La medicion faltante en el barrio de Coghlan
df[2:2,]
NA
#selecciono aquel que tiene tipos de datos numericos. Para eso hago una funcion llamada numerico que me devolvera true por cada indice que sea numero, luego se la aplico al df. Observo que solo hay faltantes en mis mediciones
numerico<- sapply(df, is.numeric)
df2 = df[numerico]
head(df2)

for(i in 1:ncol(df2)) {
  df2[ , i][is.na(df2[ , i])] <- mean(df2[ , i], na.rm = TRUE)
}
head(df2) # Check first 6 rows after substitution by mean
NA
df[numerico] = df2

df[2:2,]
NA

2.7 Libreria ZOO

dfmanera2 = preciosclarosdcast
# Solo necesito las columnas numericas
library(zoo)
numerico<- sapply(dfmanera2 , is.numeric)
dfmanera2 [numerico] <- lapply(dfmanera2 [numerico], na.aggregate)

dfmanera2 [2:2,]
NA

2.8 Eliminar a los casos que contengan faltantes

dfomit = preciosclarosdcast
# Eliminamos toda la fila por contener un faltante
dfomit = na.omit(dfomit)

dfomit [2:2,]

nrow(dfomit)
[1] 111422
nrow(preciosclarosdcast)
[1] 164592

2.9 Otro tipo de reemplazo

¿El periodo de la medicion 1 a la 10 pueden haber sido varios meses, no sería correcta reemplazarla por el promedio de las dos siguientes?

Medicion 1-2-3 son de Noviembre 2018 Medicion 4-5 son de Diciembre 2018 Medicion 6-7 son de Enero 2019 Medicion 8-9-10 son de Febrero

¿Que pasa si hago un promedio mensual? No va a haber más faltantes?


# Promedios por periodo y total
preciosclarosdcast =
(
preciosclarosdcast %>%
  mutate(periodo1 = rowMeans(select(., "1","2","3")), periodo2 = rowMeans(select(., "4","5")), periodo3 = rowMeans(select(., "6","7")), periodo4 = rowMeans(select(., "8","9","10")), promedio = rowMeans(select(., "1","2","3","4","5","6","7","8","9","10")))
)

Podria imputar las mediciones sigueintes a las que tengo NA, es decir, si en la medicion 1 tengo un NA, porque no imputar directamente el valor siguiente dado que la diferencia intra mensual es menor.

# Reemplaza los NA en la medición 1 por el valor de la medición 2
i = which(is.na(preciosclarosdcast$`1`)) # Devuelve número de filas con NA
preciosclarosdcast[i,12] = preciosclarosdcast[i,13] # reemplaza los nulos en la primer medición por el valor en la segunda

En el caso de los valores siguientes que tienen un adyacente, podría promediar el valor anterior (j-1) y el valor siguiente (j+1), es decir, mi precio de la medicion con NA 5, seria un promedio entre la 4 y la 6.

# Reemplaza NA entre la columna 13 y la 20 (valores d las mediciones 2 a 9) por el promedio de sus adyacentes
for (j in 0:7) {
  ii = which(is.na(preciosclarosdcast[,13+j])) 
    preciosclarosdcast[ii,13+j] = (preciosclarosdcast[ii,13+j-1]+preciosclarosdcast[ii,13+j+1])/2
}
# Reemplazo los NA de la medición 10 por el valor de la medición 9
iii = which(is.na(preciosclarosdcast$`10`))
preciosclarosdcast[iii,21] = preciosclarosdcast[iii,20]
sum(is.na(preciosclarosdcast))
[1] 156041

Por ultimo si me quedo algun faltante que no reemplace de esta manera, podría eliminarlo

# Eliminamos toda la fila por contener un faltante
preciosclarosdcast = na.omit(preciosclarosdcast)

nrow(preciosclarosdcast)
[1] 111422
nrow(dfomit)
[1] 111422

3 Discretizaciones

# Renombro las columnas de las mediciones
preciosclarosdcast =
(
  preciosclarosdcast %>% rename(
    "m1" = "1",
    "m2" = "2",
    "m3" = "3",
    "m4" = "4",
    "m5" = "5",
    "m6" = "6",
    "m7" = "7",
    "m8" = "8",
    "m9" = "9",
    "m10" = "10"
        )
)

3.1 Calculo variaciones intra periodo

# Variaciones intra periodo y total
preciosclarosdcast = (
preciosclarosdcast %>%
dplyr::mutate(variacion1 = (m3-m1)/m1, variacion2 = (m5-m4)/m4, variacion3 = (m7-m6)/m6, variacion4 = (m10-m8)/m8, variacionT = (m10-m1)/m1)
)
LS0tDQp0aXRsZTogIk1pc3NpbmcgUHJlY2lvcyINCmF1dGhvcjogIkZlZGVyaWNvIE1vcmVubyINCmRhdGU6ICIxMi8yLzIwMjAiDQoNCm91dHB1dDogDQogIGh0bWxfbm90ZWJvb2s6DQogICAgdG9jOiB0cnVlDQogICAgdG9jX2Zsb2F0OiB0cnVlDQogICAgdG9jX2NvbGxhcHNlZDogdHJ1ZSANCiAgICB0b2NfZGVwdGg6IDQNCiAgICBudW1iZXJfc2VjdGlvbnM6IHRydWUNCiAgICB0aGVtZTogc3BhY2VsYWINCi0tLQ0KPHN0eWxlPg0KYm9keSB7DQp0ZXh0LWFsaWduOiBqdXN0aWZ5fQ0KDQoubGlzdC1ncm91cC1pdGVtLmFjdGl2ZSwgLmxpc3QtZ3JvdXAtaXRlbS5hY3RpdmU6Zm9jdXMsIC5saXN0LWdyb3VwLWl0ZW0uYWN0aXZlOmhvdmVyIHsNCiAgICBiYWNrZ3JvdW5kLWNvbG9yOiAjREQ4RDE7DQp9DQo8L3N0eWxlPg0KDQoNCg0KTGFzIGxpYnJlcsOtYXMgYSB1c2FyLjxicj4NCmBgYHtyIExpYnJlcsOtYXN9DQpsaWJyYXJ5KGpzb25saXRlKQ0KbGlicmFyeShwbHlyKQ0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkod2VzYW5kZXJzb24pDQpsaWJyYXJ5KHZpcmlkaXNMaXRlKQ0KbGlicmFyeShyZ2RhbCkNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkocmdlb3MpDQpsaWJyYXJ5KHJlc2hhcGUyKQ0KbGlicmFyeShhcnVsZXMpDQpsaWJyYXJ5KHRtKQ0KbGlicmFyeSh3b3JkY2xvdWQpDQpsaWJyYXJ5KFJDb2xvckJyZXdlcikNCmxpYnJhcnkoYXJ1bGVzVml6KQ0KbGlicmFyeShEVCkNCmxpYnJhcnkoaHRtbHdpZGdldHMpDQpsaWJyYXJ5KERhdGFFeHBsb3JlcikNCg0KI2xpbXBpbyBsYSBtZW1vcmlhDQojcm0obGlzdD1scygpKQ0KI2djKCkNCg0KYGBgDQoNCiMgSW1wb3J0YWNpw7NuIGRlIGFyY2hpdm9zDQoNCiMjIERhdG9zIEpzb24NCmBgYHtyIEltcG9ydGFjacOzbiBkZSBhcmNoaXZvcyBqc29uLCBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFLCBpbmNsdWRlPUZBTFNFfQ0KI1NlIGltcG9ydGEgdW5hIHZleiBwb3JxdWUgbGEgZGF0YSBlcyBtdXkgcGVzYWRhLiBQYXJhIGVzbyBtb2RpZmljYW1vcyBsbyBxdWUgaGF5IGRlbnRybyBkZSB7fSwgc2UgcHVlZGUgbW9kaWZpY2FyIGNvbiBlbCBib3RvbiBkZSBjb25maWd1cmFjaW9uLg0KIyBDYXJnYXIgRGlyZWN0YW1lbnRlIGVsIEVudmVyb21lbnQgQ2xhc2UgTWlzc2luZy5SREFUQSANCiMgc29uIDEsNW1pbGxvbmVzIGRlIHByZWNpb3MNCnByZWNpb3MgPSBzdHJlYW1faW4oZmlsZSgicHJlY2lvcy5qc29uIixvcGVuPSJyIikpDQpzdWN1cnNhbGVzID0gc3RyZWFtX2luKGZpbGUoInN1Y3Vyc2FsZXMuanNvbiIsb3Blbj0iciIpKQ0KcHJvZHVjdG9zID0gc3RyZWFtX2luKGZpbGUoInByb2R1Y3Rvcy5qc29uIixvcGVuPSJyIikpDQpgYGANCg0KDQoNCiMjIEltcG9ydGFjacOzbiBkZSBkYXRhc2V0cyBleHRyYS4NCmBgYHtyIEltcG9ydGFjacOzbiBkZSBhcmNoaXZvcyBjc3Z9DQojIEltcG9ydGFtb3MgbG9zIGRhdG9zIGRlIGxhcyBtZWRpY2lvbmVzIHkgZGUgbG9zIGJhcnJpb3MNCm1lZGljaW9uZXMgPSByZWFkLmNzdigiLi9EYXRhc2V0cyBhZGljaW9uYWxlcy9tZWRpY2lvbmVzLmNzdiIpDQpzdWN1cnNhbGVzID0gcmVhZC5jc3YoIi4vRGF0YXNldHMgYWRpY2lvbmFsZXMvc3VjdXJzYWxlc19iYXJyaW9zLmNzdiIpDQpwcm9kdWN0b3MgPSByZWFkLmNzdigiLi9EYXRhc2V0cyBhZGljaW9uYWxlcy9wcm9kdWN0b3NfY2F0ZWdvcmlhLmNzdiIpDQppbmZsYWNpb24gPSByZWFkLmNzdigiLi9EYXRhc2V0cyBhZGljaW9uYWxlcy9pbmZsYWNpb24uY3N2IikNCnByZWNpb19tZXRyb3MyID0gcmVhZC5jc3YoIi4vYmFycmlvcy9wcmVjaW9fbWV0cm8uY3N2IikNCmBgYA0KDQojIyBPYnNlcnZhbW9zIGxhIGRhdGEsIGhlYWQoKSBvIGdsaW1wc2UoKSANCmBgYHtyfQ0KaGVhZChwcm9kdWN0b3MpDQpgYGANCg0KYGBge3J9DQpnbGltcHNlKHByb2R1Y3RvcykNCmBgYA0KDQpgYGB7cn0NCmhlYWQocHJlY2lvcykNCmBgYA0KDQoNCmBgYHtyfQ0KZ2xpbXBzZShwcmVjaW9zKQ0KYGBgDQoNCiMgSW5uZXIgSm9pbg0KDQojIyBVbmlmaWNhbW9zIGRhdGFzZXRzDQoNCkFncmVnYW1vcyBsYSBpbmZvIGRlIGxvcyBwcm9kdWN0b3MgYWwgZGYgInByZWNpb3MiLiBVdGlsaXphbW9zIGlubmVyIGpvaW4gcXVlIG1hdGNoZWEgIGxhIGNvbHVtbmEgcHJvZHVjdG8gZGVsIGRhdGEgZnJhbWUgcHJlY2lvcyB5IGxhIGNvbHVtbmEgSUQgZGUgbGEgY29sdW1uYSBkZWwgZGF0YSBmcmFtZSBwcm9kdWN0b3MNCmBgYHtyIHdhcm5pbmc9RkFMU0V9DQpkYXRhMSA9IGlubmVyX2pvaW4ocHJlY2lvcywgcHJvZHVjdG9zLCBieSA9IGMoInByb2R1Y3RvIiA9ICJpZCIpKQ0KDQpnbGltcHNlKGRhdGExKQ0KYGBgDQpgYGB7cn0NCmdsaW1wc2Uoc3VjdXJzYWxlcykNCmBgYA0KDQpBZ3JlZ2Ftb3MgbGEgaW5mbyBkZSBzdWN1cnNhbGVzIGEgImRhdGExIiwgc2UgY3JlYSB1biBudWV2byBkYXRhIGZyYW1lLCB2ZXIgcXVlIHlhIG5vIGVzIG5lY2VzYXJpbyBkYXRhMQ0KYGBge3Igd2FybmluZz1GQUxTRX0NCmRhdGEyID0gaW5uZXJfam9pbihkYXRhMSwgc3VjdXJzYWxlcywgYnkgPSBjKCJzdWN1cnNhbCIgPSAiaWQiKSkNCmdsaW1wc2UoZGF0YTIpDQoNCmBgYA0KDQpCb3JyYW1vcyBkYXRhMSwgbm9zIHF1ZWRhbW9zIGNvbiBkYXRhMg0KYGBge3J9DQpybShkYXRhMSkNCmBgYA0KDQpgYGB7cn0NCmNvbG5hbWVzKGRhdGEyKQ0KYGBgDQoNCmBgYHtyfQ0KZGF0YTJbN10NCiNTb2xvIG1lIGludGVyZXNhIGVsIGlkIGRlIHByZWNpbw0KYGBgDQoNCg0KIyMgVW5pw7NuIGRlIHRvZG9zIGxvcyBkYXRhc2V0cw0KDQpSZWFsaXpvIGNvbiBsYSBmdW5jacOzbiBzZWxlY3RtIGxhIGNvbHVtbmFzIGRlbnRybyBkZSBtaSBkYXRhMiBxdWUgc2VydmlyYW4gcGFyYSBjb25mb3JtYXIgbWkgZGF0YXNldCBsbGFtYWRvIHByZWNpb3MgY2xhcm9zLiBDYWRhIGNvbnN1bHRhIGVzdGFyw6EgZGV0ZXJtaW5hZGEgcG9yIGVsIElEIGRlbCBkZiBQcmVjaW9zLg0KYGBge3IgVW5pw7NuIGRlIHRvZGFzIGxhcyB0YWJsYXN9DQoNCnByZWNpb3NjbGFyb3MgPSBzZWxlY3QoZGF0YTIsIGBfaWRgLCAicHJvZHVjdG8iLCAibm9tYnJlIiwgIkNhdGVnb3JpYSIsICJtYXJjYSIsICJwcmVzZW50YWNpb24iLCAicHJlY2lvIiwgIm1lZGljaW9uIiwgInN1Y3Vyc2FsIiwgInN1Y3Vyc2FsVGlwbyIsICJiYW5kZXJhRGVzY3JpcGNpb24iLCAiY29tZXJjaW9SYXpvblNvY2lhbCIsICJkaXJlY2Npb24iLCAiYmFycmlvIikNCg0KYGBgDQoNCmBgYHtyfQ0KI1BhcmEgbGliZXJhciBlc3BhY2lvIHB1ZWRvIHF1ZWRhcm1lIHNvbG8gY29uIGVzdGUgZGF0YXNldCBQUkVDSU9TQ0xBUk9TDQpybShwcmVjaW9zKQ0Kcm0oZGF0YTIpDQpgYGANCg0KDQpgYGB7cn0NCmdsaW1wc2UocHJlY2lvc2NsYXJvcykNCmBgYA0KDQpgYGB7cn0NCiNBdXRvIGV4cGxvcmVyIFByZWNpb3MgY2xhcm9zDQojaW50cm9kdWNlKHByZWNpb3NjbGFyb3MpDQojY3JlYXRlX3JlcG9ydChwcmVjaW9zY2xhcm9zKQ0KDQpgYGANCg0KIyBUcmF0YW1pZW50byBkZSBwcmVjaW9zPC9oMj4NCg0KTmVjZXNpdG8gcGFzYXIgbG9zIHByZWNpb3MgZGUgcHJvZHVjdG9zIHBvciBzdWN1cnNhbCBhIGZvcm1hdG8gY29sdW1uYXIuIENhZGEgZmlsYSByZXByZXNlbnRhcsOhIHVuIHByb2R1Y3RvIGRlIHVuYSBzdWN1cnNhbCBjb24gZGlleiBjb2x1bW5hcyBhc29jaWFkYXMgYSBsb3MgcHJlY2lvcyBlbiBjYWRhIG1lZGljacOzbi48YnI+DQoNCkNhYmUgcXVlIGVzdGEgZm9ybWEgZXMgbGEgcXVlIHRlbmVtb3MgbcOhcyBhc29jaWFkYSBhbCBhaG9yYSBkZSB0cmFiYWphciBsYSBkYXRhLCBzaW4gZW1iYXJnbyBkaXN0aW50b3MgYWxnb3JpdG1vcyBkZSBwcmVkaWNjacOzbiBwdWVkYW4gcmVxdWVyaXIgb3RyYSBtYW5lcmEgZGUgb3JnYW5pemFyIGxhIGRhdGEuIFBvciBlamVtcGxvOiBIaXN0b3JpYSBjbGluaWNhIGEgbG8gbGFyZ28gZGUgbGEgdmlkYS4NCg0KIyMgRnVuY2lvbiBEY2FzdCwgUkVTSEFQRQ0KDQpSZXNoYXBlIHNpZ25pZmljYSBjYW1iaWFyIGxhIGZvcm1hIGRlIG51ZXN0cm8gZGF0YXNldCwgZXN0byBlcyBkZXRlcm1pbmFyIGxhcyBmaWxhcyB5IGxhcyBjb2x1bW5hcywgcXVlIHZhcmlhYmxlcyBlc3TDoW4gZW4gZnVuY2nDs24gZGUgY3VhbCBvdHJhLiANCkNsYXJhbWVudGUgZXN0byBkZWJlIHRlbmVyIHNlbnRpZG8uIE5vcyBpbnRlcmVzYSBlbiBlc3RlIG1vbWVudG8gdmVyIGxvcyBwcmVjaW9zIGVuIGZ1bmNpb24gZGUgY2FkYSBtZWRpY2nDs24uIEVzIGRlY2lyLCBub3MgaW50ZXJlc2EgYWdydXBhciBjYWRhIHByb2R1Y3RvIHBvciBzdWN1cnNhbCwgcG9yIHByZXNlbnRhY2lvbiwgcG9yIGJhcnJpbywgc2Vnw7puIGxhIG1lZGljacOzbiBlbiBxdWUgZnVlIHRvbWFkbyBlbCBkYXRvLg0KDQpMYSBmdW5jacOzbiBkY2FzdCBlcyB1biBtZWx0IGVuIGRvbmRlIHF1ZSB0b21hIGxhIGZvcm1hIGBMSFMgfiBSSFNgLCBleDogYHZhcjEgKyB2YXIyIH4gdmFyM2AuICBFbCBvcmRlbiBkZSBsYXMgdmFyaWFibGVzIGVzIGVzZW5jaWFsLiBMb3MgdmFsb3JlcyBMSFMgKGFudGVzIGRlbCBuZXdmbG93IH4gcmVwcmVzZW50YW4gbGFzIGZpbGFzIHkgbHVlZ28gcmVwcmVzZW50YW4gbGFzIGNvbHVtbmFzLiANCmBgYHtyIE1lZGljaW9uZXMgZW4gZm9ybWF0byBjb2x1bW5hcn0NCnByZWNpb3NjbGFyb3NkY2FzdCA9IGRjYXN0KHByZWNpb3NjbGFyb3MsIHByb2R1Y3RvICsgbm9tYnJlICsgQ2F0ZWdvcmlhICsgbWFyY2EgKyBwcmVzZW50YWNpb24gKyBzdWN1cnNhbCArIHN1Y3Vyc2FsVGlwbyArIGJhbmRlcmFEZXNjcmlwY2lvbiArIGNvbWVyY2lvUmF6b25Tb2NpYWwgKyBkaXJlY2Npb24gKyBiYXJyaW8gfiBtZWRpY2lvbiwgZnVuLmFnZ3JlZ2F0ZT1OVUxMLCB2YWx1ZS52YXI9InByZWNpbyIpDQpgYGANCg0KYGBge3J9DQpoZWFkKHByZWNpb3NjbGFyb3NkY2FzdCkNCmBgYA0KIyMgRmFsdGFudGVzDQoNCmBgYHtyfQ0KI2lzLm5hKHByZWNpb3NjbGFyb3NkY2FzdCkgIyBpbmRpY2EgY3XDoWxlcyBkZSBsb3MgZWxlbWVudG9zIHNvbiBOQSAoVFJVRSkNCg0KIyBQYXJhIHNhYmVyIGxhIGNhbnRpZGFkIGV4YWN0YSBkZSBOQXMgcXVlIGVzdMOhbiBwcmVzZW50ZXMgZW4gbG9zIGRhdG9zIA0Kc3VtKGlzLm5hKHByZWNpb3NjbGFyb3NkY2FzdCkpDQpgYGANCg0KDQpgYGB7cn0NCiNjb21wbGV0ZS5jYXNlcyhwcmVjaW9zY2xhcm9zZGNhc3QpICMgTWUgZGV2dWVsdmUgbG9zIMOtbmRpY2VzIGRlIGxvcyByZWdpc3Ryb3MgcXVlIE5PIHRpZW5lbiBOQXMNCmxlbmd0aCh3aGljaCghY29tcGxldGUuY2FzZXMocHJlY2lvc2NsYXJvc2RjYXN0KSkpICMgTWUgcGVybWl0ZSBzYWJlciBjdcOhbnRvcyByZWdpc3Ryb3MgdGVuZ28sIHNpbiBoYWNlciBsYSBjdWVudGENCg0KIyBUZW5nbyA1MzE3MCByZWdpc3Ryb3MgcXVlIHRpZW5lbiBhbCBtZW5vcyAxIE5BDQpjb2xuYW1lcyhwcmVjaW9zY2xhcm9zZGNhc3QpW2NvbFN1bXMoaXMubmEocHJlY2lvc2NsYXJvc2RjYXN0KSkgPiAwXSAjIE9idGVuZ28gbGFzIGNvbHVtbmFzIHF1ZSB0aWVuZW4gdW4gYWwgbWVub3MgTkEuIE1lZGljaW9uZXMgc2luIGRhdG9zLg0KDQoNCmBgYA0KIyMgRnVuY2lvbiBGdW4gTW9kZWxpbmcNCmBgYHtyfQ0KZnVuTW9kZWxpbmc6OnN0YXR1cyhwcmVjaW9zY2xhcm9zZGNhc3QpDQpgYGANCg0KDQoNCiMjIFN1c3RpdHVjaW9uIHBvciBtZWRpYXMNCmBgYHtyfQ0KI0hhZ28gdW5hIGNvcGlhIGRlbCBkYXRhc2V0IHBhcmEgdHJhYmFqYXINCmRmID0gcHJlY2lvc2NsYXJvc2RjYXN0DQpgYGANCg0KYGBge3J9DQojTGEgbWVkaWNpb24gZmFsdGFudGUgZW4gZWwgYmFycmlvIGRlIENvZ2hsYW4NCmRmWzI6MixdDQoNCmBgYA0KDQpgYGB7cn0NCiNzZWxlY2Npb25vIGFxdWVsIHF1ZSB0aWVuZSB0aXBvcyBkZSBkYXRvcyBudW1lcmljb3MuIFBhcmEgZXNvIGhhZ28gdW5hIGZ1bmNpb24gbGxhbWFkYSBudW1lcmljbyBxdWUgbWUgZGV2b2x2ZXJhIHRydWUgcG9yIGNhZGEgaW5kaWNlIHF1ZSBzZWEgbnVtZXJvLCBsdWVnbyBzZSBsYSBhcGxpY28gYWwgZGYuIE9ic2Vydm8gcXVlIHNvbG8gaGF5IGZhbHRhbnRlcyBlbiBtaXMgbWVkaWNpb25lcw0KbnVtZXJpY288LSBzYXBwbHkoZGYsIGlzLm51bWVyaWMpDQpkZjIgPSBkZltudW1lcmljb10NCmhlYWQoZGYyKQ0KYGBgDQoNCg0KYGBge3J9DQoNCmZvcihpIGluIDE6bmNvbChkZjIpKSB7DQogIGRmMlsgLCBpXVtpcy5uYShkZjJbICwgaV0pXSA8LSBtZWFuKGRmMlsgLCBpXSwgbmEucm0gPSBUUlVFKQ0KfQ0KaGVhZChkZjIpICMgQ2hlY2sgZmlyc3QgNiByb3dzIGFmdGVyIHN1YnN0aXR1dGlvbiBieSBtZWFuDQoNCmBgYA0KDQpgYGB7cn0NCmRmW251bWVyaWNvXSA9IGRmMg0KDQpkZlsyOjIsXQ0KDQpgYGANCg0KIyMgTGlicmVyaWEgWk9PDQpgYGB7cn0NCmRmbWFuZXJhMiA9IHByZWNpb3NjbGFyb3NkY2FzdA0KIyBTb2xvIG5lY2VzaXRvIGxhcyBjb2x1bW5hcyBudW1lcmljYXMNCmxpYnJhcnkoem9vKQ0KbnVtZXJpY288LSBzYXBwbHkoZGZtYW5lcmEyICwgaXMubnVtZXJpYykNCmRmbWFuZXJhMiBbbnVtZXJpY29dIDwtIGxhcHBseShkZm1hbmVyYTIgW251bWVyaWNvXSwgbmEuYWdncmVnYXRlKQ0KDQpkZm1hbmVyYTIgWzI6MixdDQoNCmBgYA0KDQojIyBFbGltaW5hciBhIGxvcyBjYXNvcyBxdWUgY29udGVuZ2FuIGZhbHRhbnRlcw0KYGBge3IgRWxpbWluYWNpw7NuIGRlIGZhbHRhbnRlc30NCmRmb21pdCA9IHByZWNpb3NjbGFyb3NkY2FzdA0KIyBFbGltaW5hbW9zIHRvZGEgbGEgZmlsYSBwb3IgY29udGVuZXIgdW4gZmFsdGFudGUNCmRmb21pdCA9IG5hLm9taXQoZGZvbWl0KQ0KDQpkZm9taXQgWzI6MixdDQoNCm5yb3coZGZvbWl0KQ0KbnJvdyhwcmVjaW9zY2xhcm9zZGNhc3QpDQoNCmBgYA0KDQojIyBPdHJvIHRpcG8gZGUgcmVlbXBsYXpvDQoNCg0KDQrCv0VsIHBlcmlvZG8gZGUgbGEgbWVkaWNpb24gMSBhIGxhIDEwIHB1ZWRlbiBoYWJlciBzaWRvIHZhcmlvcyBtZXNlcywgbm8gc2Vyw61hIGNvcnJlY3RhIHJlZW1wbGF6YXJsYSBwb3IgZWwgcHJvbWVkaW8gZGUgbGFzIGRvcyBzaWd1aWVudGVzPw0KDQogTWVkaWNpb24gMS0yLTMgc29uIGRlIE5vdmllbWJyZSAyMDE4DQogTWVkaWNpb24gNC01IHNvbiBkZSBEaWNpZW1icmUgMjAxOA0KIE1lZGljaW9uIDYtNyBzb24gZGUgIEVuZXJvIDIwMTkNCiBNZWRpY2lvbiA4LTktMTAgc29uIGRlIEZlYnJlcm8NCiANCsK/UXVlIHBhc2Egc2kgaGFnbyB1biBwcm9tZWRpbyBtZW5zdWFsPyBObyB2YSBhIGhhYmVyIG3DoXMgZmFsdGFudGVzPw0KDQpgYGB7ciBQcm9tZWRpb3MgcG9yIHBlcmlvZG8geSB0b3RhbH0NCg0KIyBQcm9tZWRpb3MgcG9yIHBlcmlvZG8geSB0b3RhbA0KcHJlY2lvc2NsYXJvc2RjYXN0ID0NCigNCnByZWNpb3NjbGFyb3NkY2FzdCAlPiUNCiAgbXV0YXRlKHBlcmlvZG8xID0gcm93TWVhbnMoc2VsZWN0KC4sICIxIiwiMiIsIjMiKSksIHBlcmlvZG8yID0gcm93TWVhbnMoc2VsZWN0KC4sICI0IiwiNSIpKSwgcGVyaW9kbzMgPSByb3dNZWFucyhzZWxlY3QoLiwgIjYiLCI3IikpLCBwZXJpb2RvNCA9IHJvd01lYW5zKHNlbGVjdCguLCAiOCIsIjkiLCIxMCIpKSwgcHJvbWVkaW8gPSByb3dNZWFucyhzZWxlY3QoLiwgIjEiLCIyIiwiMyIsIjQiLCI1IiwiNiIsIjciLCI4IiwiOSIsIjEwIikpKQ0KKQ0KYGBgDQoNClBvZHJpYSBpbXB1dGFyIGxhcyBtZWRpY2lvbmVzIHNpZ3VlaW50ZXMgYSBsYXMgcXVlIHRlbmdvIE5BLCBlcyBkZWNpciwgc2kgZW4gbGEgbWVkaWNpb24gMSB0ZW5nbyB1biBOQSwgcG9ycXVlIG5vIGltcHV0YXIgZGlyZWN0YW1lbnRlIGVsIHZhbG9yIHNpZ3VpZW50ZSBkYWRvIHF1ZSBsYSBkaWZlcmVuY2lhIGludHJhIG1lbnN1YWwgZXMgbWVub3IuPGJyPg0KDQpgYGB7cn0NCiMgUmVlbXBsYXphIGxvcyBOQSBlbiBsYSBtZWRpY2nDs24gMSBwb3IgZWwgdmFsb3IgZGUgbGEgbWVkaWNpw7NuIDINCmkgPSB3aGljaChpcy5uYShwcmVjaW9zY2xhcm9zZGNhc3QkYDFgKSkgIyBEZXZ1ZWx2ZSBuw7ptZXJvIGRlIGZpbGFzIGNvbiBOQQ0KcHJlY2lvc2NsYXJvc2RjYXN0W2ksMTJdID0gcHJlY2lvc2NsYXJvc2RjYXN0W2ksMTNdICMgcmVlbXBsYXphIGxvcyBudWxvcyBlbiBsYSBwcmltZXIgbWVkaWNpw7NuIHBvciBlbCB2YWxvciBlbiBsYSBzZWd1bmRhDQpgYGANCg0KRW4gZWwgY2FzbyBkZSBsb3MgdmFsb3JlcyBzaWd1aWVudGVzIHF1ZSB0aWVuZW4gdW4gYWR5YWNlbnRlLCBwb2Ryw61hIHByb21lZGlhciBlbCB2YWxvciBhbnRlcmlvciAoai0xKSB5IGVsIHZhbG9yIHNpZ3VpZW50ZSAoaisxKSwgZXMgZGVjaXIsIG1pIHByZWNpbyBkZSBsYSBtZWRpY2lvbiBjb24gTkEgNSwgc2VyaWEgdW4gcHJvbWVkaW8gZW50cmUgbGEgNCB5IGxhIDYuDQoNCmBgYHtyIFJlZW1wbGF6byBkZSBOQSBwb3IgZWwgcHJvbWVkaW8gZGUgYWR5YWNlbnRlc30NCiMgUmVlbXBsYXphIE5BIGVudHJlIGxhIGNvbHVtbmEgMTMgeSBsYSAyMCAodmFsb3JlcyBkIGxhcyBtZWRpY2lvbmVzIDIgYSA5KSBwb3IgZWwgcHJvbWVkaW8gZGUgc3VzIGFkeWFjZW50ZXMNCmZvciAoaiBpbiAwOjcpIHsNCiAgaWkgPSB3aGljaChpcy5uYShwcmVjaW9zY2xhcm9zZGNhc3RbLDEzK2pdKSkgDQoJcHJlY2lvc2NsYXJvc2RjYXN0W2lpLDEzK2pdID0gKHByZWNpb3NjbGFyb3NkY2FzdFtpaSwxMytqLTFdK3ByZWNpb3NjbGFyb3NkY2FzdFtpaSwxMytqKzFdKS8yDQp9DQpgYGANCg0KYGBge3IgUmVlbXBsYXpvIGRlIE5BIGVuIG0xMCBwb3IgZWwgdmFsb3IgZGUgbTl9DQojIFJlZW1wbGF6byBsb3MgTkEgZGUgbGEgbWVkaWNpw7NuIDEwIHBvciBlbCB2YWxvciBkZSBsYSBtZWRpY2nDs24gOQ0KaWlpID0gd2hpY2goaXMubmEocHJlY2lvc2NsYXJvc2RjYXN0JGAxMGApKQ0KcHJlY2lvc2NsYXJvc2RjYXN0W2lpaSwyMV0gPSBwcmVjaW9zY2xhcm9zZGNhc3RbaWlpLDIwXQ0KYGBgDQoNCmBgYHtyfQ0Kc3VtKGlzLm5hKHByZWNpb3NjbGFyb3NkY2FzdCkpDQpgYGANCg0KUG9yIHVsdGltbyBzaSBtZSBxdWVkbyBhbGd1biBmYWx0YW50ZSBxdWUgbm8gcmVlbXBsYWNlIGRlIGVzdGEgbWFuZXJhLCBwb2Ryw61hIGVsaW1pbmFybG8NCmBgYHtyIH0NCiMgRWxpbWluYW1vcyB0b2RhIGxhIGZpbGEgcG9yIGNvbnRlbmVyIHVuIGZhbHRhbnRlDQpwcmVjaW9zY2xhcm9zZGNhc3QgPSBuYS5vbWl0KHByZWNpb3NjbGFyb3NkY2FzdCkNCg0KbnJvdyhwcmVjaW9zY2xhcm9zZGNhc3QpDQpucm93KGRmb21pdCkNCg0KYGBgDQoNCiMgRGlzY3JldGl6YWNpb25lcw0KDQpgYGB7ciBSZW5vbWJybyBsYXMgY29sdW1uYXN9DQojIFJlbm9tYnJvIGxhcyBjb2x1bW5hcyBkZSBsYXMgbWVkaWNpb25lcw0KcHJlY2lvc2NsYXJvc2RjYXN0ID0NCigNCiAgcHJlY2lvc2NsYXJvc2RjYXN0ICU+JSByZW5hbWUoDQogICAgIm0xIiA9ICIxIiwNCiAgICAibTIiID0gIjIiLA0KICAgICJtMyIgPSAiMyIsDQogICAgIm00IiA9ICI0IiwNCiAgICAibTUiID0gIjUiLA0KICAgICJtNiIgPSAiNiIsDQogICAgIm03IiA9ICI3IiwNCiAgICAibTgiID0gIjgiLA0KICAgICJtOSIgPSAiOSIsDQogICAgIm0xMCIgPSAiMTAiDQogICAgICAgICkNCikNCmBgYA0KDQoNCiMjIENhbGN1bG8gdmFyaWFjaW9uZXMgaW50cmEgcGVyaW9kbw0KYGBge3IgVmFyaWFjacOzbiBpbnRyYSBwZXJpb2RvIHkgdG90YWx9DQojIFZhcmlhY2lvbmVzIGludHJhIHBlcmlvZG8geSB0b3RhbA0KcHJlY2lvc2NsYXJvc2RjYXN0ID0gKA0KcHJlY2lvc2NsYXJvc2RjYXN0ICU+JQ0KZHBseXI6Om11dGF0ZSh2YXJpYWNpb24xID0gKG0zLW0xKS9tMSwgdmFyaWFjaW9uMiA9IChtNS1tNCkvbTQsIHZhcmlhY2lvbjMgPSAobTctbTYpL202LCB2YXJpYWNpb240ID0gKG0xMC1tOCkvbTgsIHZhcmlhY2lvblQgPSAobTEwLW0xKS9tMSkNCikNCmBgYA0KDQo=