# install.packages("rtweet")
library(rtweet)
library(tidyverse)
library(tm)
library(wordcloud2)

Descargas de tweets con rtweet

rt <- search_tweets(q = "metrovias OR bondi OR Subte OR autopista OR transporte público",type = "mixed", n = 18000, include_rts = FALSE,
                    lang='es')

saveRDS(rt,'../fuentes/rt.RDS')
rt <- read_rds('../fuentes/rt.RDS')
rt %>% 
  sample_n(10)
range(rt$created_at)
[1] "2019-09-13 03:04:47 UTC" "2019-09-21 18:24:56 UTC"

Nos da los tweets de los últimos nueve días, o el máximo que indicamos más reciente

rt %>%
  ts_plot("3 hours") +
  ggplot2::theme_minimal() +
  ggplot2::theme(plot.title = ggplot2::element_text(face = "bold")) +
  ggplot2::labs(
    x = NULL, y = NULL,
    title = "Frecuencia de los tweets relacionados al tránsito",
    subtitle = "Agregado a intervalos de tres horas")

  • Me quedo con el texto
texto <-  rt$text 

texto[1:10]
 [1] "▶ Dos sujetos robaron a usuarios de un vehículo de transporte público https://t.co/3c0fSjKrWF https://t.co/uGYN8CyLuZ"                                                                                                                                                                                      
 [2] "Detienen a dos menores por asalto a transporte público https://t.co/R2VNKPLV0v https://t.co/MOVow5LUiE"                                                                                                                                                                                                     
 [3] "Detienen a dos menores por asalto a transporte público\n\nhttps://t.co/6Ff1iewafX https://t.co/eRfyw8RLZy"                                                                                                                                                                                                  
 [4] "Hombres armados incendian transporte público en #Acapulco https://t.co/WPPQqGckq9 https://t.co/LRzjM0mvPO"                                                                                                                                                                                                  
 [5] "Hombres armados incendian transporte público en #Acapulco https://t.co/FbmZNWJPKN https://t.co/VSNemKS0S5"                                                                                                                                                                                                  
 [6] "Según la Veeduría Distrital, la capital necesita con urgencia reducir las emisiones de CO2, producidos principalmente por el transporte público y los vehículos de carga pesada. https://t.co/oSJYsyMRu5"                                                                                                   
 [7] "El #CMin aprueba un RD para mejorar la #accesibilidad al transporte de las personas con #discapacidad.\n\n♿️Acceso de las sillas de ruedas eléctricas y escúteres a los medios de transporte.\n🐕Afectados por epilepsia y diabetes podrán viajar en transporte público con su perro. https://t.co/MW5BTsZzgB"
 [8] "Ósea el asunto no es solo así por así, hay que tener en cuenta otras medidas como el Transporte, la eficiencia del transporte público etc. \nDejen de andar pidiendo desniveles como dulces porque aparentemente ni en Peru esa es la solución. \nCierro hilo."                                             
 [9] "@cronicaglobal TODOS queremos un transporte Público sostenible, pero lo queremos eficiente...Esperas mas de 6 minutos en hora punta no es eficiencia..."                                                                                                                                                    
[10] "Me caga cuando alguna persona se le queda viendo a mi cartera cuando la saco para pagar el transporte público. \nQue esperan ver? Un chingo de billetes? …......\nNo ven que estoy igual de jodido que ellos y por eso voy en la combi también."                                                            

Este texto es necesario limpiarlo para que sea más fácil de utilizar.

Armado del corpus con tm

  • Primero creo un objeto de tipo Corpus. Utilizamos algo distinto a los conocidos vectores of dataframes porque es un objeto optimizado para trabajar con texto. Esto nos permite que los procesos sean mucho más eficientes, y por lo tanto trabajar con grandes corpus de manera rápida
myCorpus = Corpus(VectorSource(texto))

Limpieza del Corpus

  • Con la función tm_map podemos iterar sobre el corpus aplicando una transformación sobre cada documento (se acuerdan de la librería PURRR?)

En este caso, para la limpieza utilizaremos las siguientes transformaciones.

  1. Pasar todo a minúscula (cómo la función que usamos no es de la librería tm tenemos que usar también content_transformer )
  2. Sacar la puntuación
  3. Sacar los números
  4. Sacar las stopwords
myCorpus = tm_map(myCorpus, content_transformer(tolower))
myCorpus = tm_map(myCorpus, removePunctuation)
myCorpus = tm_map(myCorpus, removeNumbers)
myCorpus = tm_map(myCorpus, removeWords, stopwords(kind = "es"))

También deberíamos sacar las palabras que utilizamos para descargar la información.

# metrovias OR bondi OR Subte OR autopista OR transporte público
myCorpus = tm_map(myCorpus, removeWords, c('metrovias', 'bondi','subte','autopista','transporte', 'público' ))
transformation drops documents
inspect(myCorpus[1:10])
<<SimpleCorpus>>
Metadata:  corpus specific: 1, document level (indexed): 0
Content:  documents: 10

 [1] ▶ dos sujetos robaron  usuarios   vehículo    httpstcocfsjkrwf httpstcougyncyluz                                                                                                                             
 [2] detienen  dos menores  asalto    httpstcorvnkplvv httpstcomovowluie                                                                                                                                          
 [3] detienen  dos menores  asalto   \n\nhttpstcoffiewafx httpstcoerfywrlzy                                                                                                                                       
 [4] hombres armados incendian    acapulco httpstcowppqqgckq httpstcolrzjmmvpo                                                                                                                                    
 [5] hombres armados incendian    acapulco httpstcofbmznwjpkn httpstcovsnemkss                                                                                                                                    
 [6] según  veeduría distrital  capital necesita  urgencia reducir  emisiones  co producidos principalmente       vehículos  carga pesada httpstcoosjysymru                                                       
 [7]  cmin aprueba  rd  mejorar  accesibilidad     personas  discapacidad\n\n♿️acceso   sillas  ruedas eléctricas  escúteres   medios  \n🐕afectados  epilepsia  diabetes podrán viajar      perro httpstcomwbtszzgb
 [8] ósea  asunto   solo así  así   tener  cuenta  medidas     eficiencia    etc \ndejen  andar pidiendo desniveles  dulces  aparentemente   peru    solución \ncierro hilo                                       
 [9] cronicaglobal  queremos    sostenible   queremos eficienteesperas mas   minutos  hora punta   eficiencia                                                                                                     
[10]  caga  alguna persona   queda viendo   cartera   saco  pagar    \n esperan ver  chingo  billetes …\n ven   igual  jodido      voy   combi                                                                    

podemos ver que nos quedaron unos que son la forma de representar el “enter”. Lo mejor sería eliminarlos.

También queremos sacar los links. Para eso vamos a usar expresiones regulares para definir el patron que tiene un link, y luego crearemos una función que los elimine.

Expresiones regulares

Para que sea más sencilla la construcción de la expresión regular, usamos la librería RVerbalExpressions

# devtools::install_github("VerbalExpressions/RVerbalExpressions")
library(RVerbalExpressions)

expresion <- rx() %>% 
  rx_find('http') %>% 
  rx_maybe('s') %>% 
  # rx_maybe('://') %>% #como ya lo pasamos por los otros filtros, ya no hay puntuacion
  rx_anything_but(value = ' ')

expresion
[1] "(http)(s)?([^ ]*)"

Probamos la expresion con un ejemplo

txt <- "detienen  dos menores  asalto  transporte público\n\nhttpstcoffiewafx httpstcoerfywrlzy"      
str_remove_all(txt, pattern = expresion)
[1] "detienen  dos menores  asalto  transporte público\n\n "

Lo pasamos por el corpus

myCorpus = tm_map(myCorpus, content_transformer(function(x) str_remove_all(x, pattern = expresion)))
transformation drops documents
myCorpus = tm_map(myCorpus, content_transformer(function(x) str_remove_all(x, pattern = '\n')))
transformation drops documents
inspect(myCorpus[1:10])
<<SimpleCorpus>>
Metadata:  corpus specific: 1, document level (indexed): 0
Content:  documents: 10

 [1] ▶ dos sujetos robaron  usuarios   vehículo                                                                                                                                            
 [2] detienen  dos menores  asalto                                                                                                                                                         
 [3] detienen  dos menores  asalto                                                                                                                                                         
 [4] hombres armados incendian    acapulco                                                                                                                                                 
 [5] hombres armados incendian    acapulco                                                                                                                                                 
 [6] según  veeduría distrital  capital necesita  urgencia reducir  emisiones  co producidos principalmente       vehículos  carga pesada                                                  
 [7]  cmin aprueba  rd  mejorar  accesibilidad     personas  discapacidad♿️acceso   sillas  ruedas eléctricas  escúteres   medios  🐕afectados  epilepsia  diabetes podrán viajar      perro 
 [8] ósea  asunto   solo así  así   tener  cuenta  medidas     eficiencia    etc dejen  andar pidiendo desniveles  dulces  aparentemente   peru    solución cierro hilo                    
 [9] cronicaglobal  queremos    sostenible   queremos eficienteesperas mas   minutos  hora punta   eficiencia                                                                              
[10]  caga  alguna persona   queda viendo   cartera   saco  pagar     esperan ver  chingo  billetes … ven   igual  jodido      voy   combi                                                 

Creamos una matriz de Término-documento

myDTM = DocumentTermMatrix(myCorpus, control = list(minWordLength = 1))
inspect(myDTM)
<<DocumentTermMatrix (documents: 17697, terms: 43484)>>
Non-/sparse entries: 227647/769308701
Sparsity           : 100%
Maximal term length: 137
Weighting          : term frequency (tf)
Sample             :
       Terms
Docs    así ciudad gente metro movilidad publico ser servicio sistema uso
  10446   0      1     0     0         0       1   0        0       0   0
  11678   0      0     0     0         0       0   0        0       0   0
  11689   0      0     0     0         0       0   0        0       0   0
  12916   0      0     0     0         0       0   0        0       0   0
  13169   0      0     1     0         0       0   0        0       0   0
  13736   0      0     0     1         0       0   0        0       0   0
  16210   0      0     0     0         0       1   0        0       0   0
  2359    0      0     0     0         0       0   0        0       0   0
  3539    0      0     0     0         0       0   0        0       0   0
  5963    0      0     0     0         2       0   0        0       0   0
palabras_frecuentes <- findMostFreqTerms(myDTM,n = 25, INDEX = rep(1,nDocs(myDTM)))[[1]]

palabras_frecuentes
  publico     gente    ciudad     metro  servicio       ser movilidad       así   sistema       uso 
     1425      1081      1026       934       890       737       692       583       572       572 
      hoy     mejor       día      solo     hacer  personas   mejorar     menos      hace      usar 
      560       557       553       533       532       512       498       495       491       485 
     bien     puede       vez   calidad     tener 
      461       447       446       443       441 
palabras_frecuentes <- tibble(word = names(palabras_frecuentes), freq =palabras_frecuentes)

wordcloud2(palabras_frecuentes, shuffle = FALSE)

Topic Modeling

necesito eliminar los documentos vacíos (que luego de la limpieza quedaron sin ningúna palabra)

ui = unique(myDTM$i)
dtm = myDTM[ui,]

dim(myDTM)
[1] 17697 43484
dim(dtm)
[1] 17677 43484
lda_fit <- LDA(dtm, k = 10,method = "Gibbs", control = list(delta=0.6,seed = 1234))
lda_fit
saveRDS(lda_fit,'../resultados/lda_fit.rds')
Terms <- terms(lda_fit, 10)
Terms
      Topic 1    Topic 2     Topic 3    Topic 4        Topic 5   Topic 6       Topic 7     Topic 8    
 [1,] "mejorar"  "movilidad" "servicio" "ciudad"       "día"     "gente"       "pasajeros" "publico"  
 [2,] "hace"     "uso"       "ser"      "pasaje"       "cada"    "metro"       "vía"       "seguridad"
 [3,] "puede"    "coche"     "madrid"   "bogotá"       "hoy"     "enciudad"    "unidades"  "casa"     
 [4,] "medio"    "vehículos" "mejor"    "sabe"         "vez"     "luisdejesus" "usuarios"  "auto"     
 [5,] "centro"   "semana"    "personas" "claudialopez" "hora"    "viajar"      "aumento"   "atención" 
 [6,] "años"     "bici"      "debe"     "parís"        "trabajo" "ahora"       "tarifa"    "llegar"   
 [7,] "tránsito" "bicicleta" "buen"     "tan"          "menos"   "caminar"     "sep"       "espacio"  
 [8,] "algún"    "ciudad"    "vas"      "huelga"       "bien"    "país"        "unidad"    "luego"    
 [9,] "utilizar" "privado"   "debería"  "reforma"      "horas"   "crisis"      "falta"     "seguro"   
[10,] "evitar"   "millones"  "defensa"  "precio"       "voy"     "caracas"     "dos"       "cuenta"   
      Topic 9        Topic 10
 [1,] "sistema"      "usar"  
 [2,] "calidad"      "solo"  
 [3,] "hacer"        "mejor" 
 [4,] "problema"     "van"   
 [5,] "gobierno"     "mal"   
 [6,] "tener"        "así"   
 [7,] "parte"        "buses" 
 [8,] "claudiashein" "vida"  
 [9,] "cdmx"         "menos" 
[10,] "autos"        "bien"  
topicmodels_json_ldavis <- function(fitted, dtm){
    svd_tsne <- function(x) tsne(svd(x)$u)

    # Find required quantities
    phi <- as.matrix(posterior(fitted)$terms)
    theta <- as.matrix(posterior(fitted)$topics)
    vocab <- colnames(phi)
    term_freq <- slam::col_sums(dtm)

    # Convert to json
    json_lda <- LDAvis::createJSON(phi = phi, theta = theta,
                            vocab = vocab,
                            mds.method = svd_tsne,
                            plot.opts = list(xlab="tsne", ylab=""),
                            doc.length = as.vector(table(dtm$i)),
                            term.frequency = term_freq)

    return(json_lda)
}
json_res <- topicmodels_json_ldavis(lda_fit, dtm)
sigma summary: Min. : 33554432 |1st Qu. : 33554432 |Median : 33554432 |Mean : 33554432 |3rd Qu. : 33554432 |Max. : 33554432 |
Epoch: Iteration #100 error is: 12.8836114015732
Epoch: Iteration #200 error is: 0.545783211585834
Epoch: Iteration #300 error is: 0.316667061007205
Epoch: Iteration #400 error is: 0.283525281163514
Epoch: Iteration #500 error is: 0.2697498397927
Epoch: Iteration #600 error is: 0.26385292509649
Epoch: Iteration #700 error is: 0.257183697971322
Epoch: Iteration #800 error is: 0.253367621152415
Epoch: Iteration #900 error is: 0.252756387349672
Epoch: Iteration #1000 error is: 0.252670353596606
serVis(json_res)
Loading required namespace: servr
To stop the server, run servr::daemon_stop(1) or restart your R session
Serving the directory /tmp/RtmpxDpSCC/file263b7d5d0dfb at http://127.0.0.1:4321
LS0tCnRpdGxlOiAgTWluZXLDrWEgZGUgVGV4dG9zCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB5ZXMKICAgIHRvY19mbG9hdDogeWVzCmRhdGU6ICIiCnN1YnRpdGxlOiBQcsOhY3RpY2EgR3VpYWRhCi0tLQoKCgpgYGB7ciBtZXNzYWdlPUZBTFNFLCB3YXJuaW5nPUZBTFNFfQojIGluc3RhbGwucGFja2FnZXMoInJ0d2VldCIpCmxpYnJhcnkocnR3ZWV0KQpsaWJyYXJ5KHRpZHl2ZXJzZSkKbGlicmFyeSh0bSkKbGlicmFyeSh3b3JkY2xvdWQyKQpsaWJyYXJ5KHRvcGljbW9kZWxzKQpsaWJyYXJ5KExEQXZpcykKbGlicmFyeSh0c25lKQpgYGAKCiMjIyBEZXNjYXJnYXMgZGUgdHdlZXRzIGNvbiBgcnR3ZWV0YAoKCmBgYHtyIGV2YWw9RkFMU0V9CnJ0IDwtIHNlYXJjaF90d2VldHMocSA9ICJtZXRyb3ZpYXMgT1IgYm9uZGkgT1IgU3VidGUgT1IgYXV0b3Bpc3RhIE9SIHRyYW5zcG9ydGUgcMO6YmxpY28iLHR5cGUgPSAibWl4ZWQiLCBuID0gMTgwMDAsIGluY2x1ZGVfcnRzID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgbGFuZz0nZXMnKQoKc2F2ZVJEUyhydCwnLi4vZnVlbnRlcy9ydC5SRFMnKQpgYGAKCmBgYHtyfQpydCA8LSByZWFkX3JkcygnLi4vZnVlbnRlcy9ydC5SRFMnKQpgYGAKCgpgYGB7cn0KcnQgJT4lIAogIHNhbXBsZV9uKDEwKQpgYGAKCmBgYHtyfQpyYW5nZShydCRjcmVhdGVkX2F0KQpgYGAKCk5vcyBkYSBsb3MgdHdlZXRzIGRlIGxvcyDDumx0aW1vcyBudWV2ZSBkw61hcywgbyBlbCBtw6F4aW1vIHF1ZSBpbmRpY2Ftb3MgbcOhcyByZWNpZW50ZQoKYGBge3J9CnJ0ICU+JQogIHRzX3Bsb3QoIjMgaG91cnMiKSArCiAgZ2dwbG90Mjo6dGhlbWVfbWluaW1hbCgpICsKICBnZ3Bsb3QyOjp0aGVtZShwbG90LnRpdGxlID0gZ2dwbG90Mjo6ZWxlbWVudF90ZXh0KGZhY2UgPSAiYm9sZCIpKSArCiAgZ2dwbG90Mjo6bGFicygKICAgIHggPSBOVUxMLCB5ID0gTlVMTCwKICAgIHRpdGxlID0gIkZyZWN1ZW5jaWEgZGUgbG9zIHR3ZWV0cyByZWxhY2lvbmFkb3MgYWwgdHLDoW5zaXRvIiwKICAgIHN1YnRpdGxlID0gIkFncmVnYWRvIGEgaW50ZXJ2YWxvcyBkZSB0cmVzIGhvcmFzIikKYGBgCgotIE1lIHF1ZWRvIGNvbiBlbCB0ZXh0bwoKYGBge3J9CnRleHRvIDwtICBydCR0ZXh0IAoKdGV4dG9bMToxMF0KYGBgCgpFc3RlIHRleHRvIGVzIG5lY2VzYXJpbyBsaW1waWFybG8gcGFyYSBxdWUgc2VhIG3DoXMgZsOhY2lsIGRlIHV0aWxpemFyLgoKIyMjIEFybWFkbyBkZWwgY29ycHVzIGNvbiBgdG1gCgotIFByaW1lcm8gY3JlbyB1biBvYmpldG8gZGUgdGlwbyBDb3JwdXMuIFV0aWxpemFtb3MgYWxnbyBkaXN0aW50byBhIGxvcyBjb25vY2lkb3MgdmVjdG9yZXMgb2YgZGF0YWZyYW1lcyBwb3JxdWUgZXMgdW4gb2JqZXRvIG9wdGltaXphZG8gcGFyYSB0cmFiYWphciBjb24gdGV4dG8uIEVzdG8gbm9zIHBlcm1pdGUgcXVlIGxvcyBwcm9jZXNvcyBzZWFuIG11Y2hvIG3DoXMgZWZpY2llbnRlcywgeSBwb3IgbG8gdGFudG8gdHJhYmFqYXIgY29uIGdyYW5kZXMgY29ycHVzIGRlIG1hbmVyYSByw6FwaWRhCgpgYGB7cn0KbXlDb3JwdXMgPSBDb3JwdXMoVmVjdG9yU291cmNlKHRleHRvKSkKYGBgCgoKIyMjIExpbXBpZXphIGRlbCBDb3JwdXMKCi0gQ29uIGxhIGZ1bmNpw7NuIGB0bV9tYXBgIHBvZGVtb3MgaXRlcmFyIHNvYnJlIGVsIGNvcnB1cyBhcGxpY2FuZG8gdW5hIHRyYW5zZm9ybWFjacOzbiBzb2JyZSBjYWRhIGRvY3VtZW50byAoc2UgYWN1ZXJkYW4gZGUgbGEgbGlicmVyw61hIGBQVVJSUmA/KQoKRW4gZXN0ZSBjYXNvLCBwYXJhIGxhIGxpbXBpZXphIHV0aWxpemFyZW1vcyBsYXMgc2lndWllbnRlcyB0cmFuc2Zvcm1hY2lvbmVzLgoKMS4gUGFzYXIgdG9kbyBhIG1pbsO6c2N1bGEgKGPDs21vIGxhIGZ1bmNpw7NuIHF1ZSB1c2Ftb3Mgbm8gZXMgZGUgbGEgbGlicmVyw61hIGB0bWAgdGVuZW1vcyBxdWUgdXNhciB0YW1iacOpbiBgY29udGVudF90cmFuc2Zvcm1lcmAgKQoyLiBTYWNhciBsYSBwdW50dWFjacOzbgozLiBTYWNhciBsb3MgbsO6bWVyb3MKNC4gU2FjYXIgbGFzIHN0b3B3b3JkcwpgYGB7ciB3YXJuaW5nPUZBTFNFfQpteUNvcnB1cyA9IHRtX21hcChteUNvcnB1cywgY29udGVudF90cmFuc2Zvcm1lcih0b2xvd2VyKSkKbXlDb3JwdXMgPSB0bV9tYXAobXlDb3JwdXMsIHJlbW92ZVB1bmN0dWF0aW9uKQpteUNvcnB1cyA9IHRtX21hcChteUNvcnB1cywgcmVtb3ZlTnVtYmVycykKbXlDb3JwdXMgPSB0bV9tYXAobXlDb3JwdXMsIHJlbW92ZVdvcmRzLCBzdG9wd29yZHMoa2luZCA9ICJlcyIpKQpgYGAKClRhbWJpw6luIGRlYmVyw61hbW9zIHNhY2FyIGxhcyBwYWxhYnJhcyBxdWUgdXRpbGl6YW1vcyBwYXJhIGRlc2NhcmdhciBsYSBpbmZvcm1hY2nDs24uCgpgYGB7cn0KIyBtZXRyb3ZpYXMgT1IgYm9uZGkgT1IgU3VidGUgT1IgYXV0b3Bpc3RhIE9SIHRyYW5zcG9ydGUgcMO6YmxpY28KbXlDb3JwdXMgPSB0bV9tYXAobXlDb3JwdXMsIHJlbW92ZVdvcmRzLCBjKCdtZXRyb3ZpYXMnLCAnYm9uZGknLCdzdWJ0ZScsJ2F1dG9waXN0YScsJ3RyYW5zcG9ydGUnLCAncMO6YmxpY28nICkpCmBgYAoKCmBgYHtyfQppbnNwZWN0KG15Q29ycHVzWzE6MTBdKQpgYGAKCnBvZGVtb3MgdmVyIHF1ZSBub3MgcXVlZGFyb24gdW5vcyBfX1xuX18gcXVlIHNvbiBsYSBmb3JtYSBkZSByZXByZXNlbnRhciBlbCAiZW50ZXIiLiBMbyBtZWpvciBzZXLDrWEgZWxpbWluYXJsb3MuCgpUYW1iacOpbiBxdWVyZW1vcyBzYWNhciBsb3MgbGlua3MuIFBhcmEgZXNvIHZhbW9zIGEgdXNhciBleHByZXNpb25lcyByZWd1bGFyZXMgcGFyYSBkZWZpbmlyIGVsIHBhdHJvbiBxdWUgdGllbmUgdW4gbGluaywgeSBsdWVnbyBjcmVhcmVtb3MgdW5hIGZ1bmNpw7NuIHF1ZSBsb3MgZWxpbWluZS4KCiMjIyMgRXhwcmVzaW9uZXMgcmVndWxhcmVzCgpQYXJhIHF1ZSBzZWEgbcOhcyBzZW5jaWxsYSBsYSBjb25zdHJ1Y2Npw7NuIGRlIGxhIGV4cHJlc2nDs24gcmVndWxhciwgdXNhbW9zIGxhIGxpYnJlcsOtYSBbUlZlcmJhbEV4cHJlc3Npb25zXShodHRwczovL3J2ZXJiYWxleHByZXNzaW9ucy5uZXRsaWZ5LmNvbS9pbmRleC5odG1sKQoKYGBge3J9CiMgZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJWZXJiYWxFeHByZXNzaW9ucy9SVmVyYmFsRXhwcmVzc2lvbnMiKQpsaWJyYXJ5KFJWZXJiYWxFeHByZXNzaW9ucykKCmV4cHJlc2lvbiA8LSByeCgpICU+JSAKICByeF9maW5kKCdodHRwJykgJT4lIAogIHJ4X21heWJlKCdzJykgJT4lIAogICMgcnhfbWF5YmUoJzovLycpICU+JSAjY29tbyB5YSBsbyBwYXNhbW9zIHBvciBsb3Mgb3Ryb3MgZmlsdHJvcywgeWEgbm8gaGF5IHB1bnR1YWNpb24KICByeF9hbnl0aGluZ19idXQodmFsdWUgPSAnICcpCgpleHByZXNpb24KCmBgYAoKUHJvYmFtb3MgbGEgZXhwcmVzaW9uIGNvbiB1biBlamVtcGxvCmBgYHtyfQp0eHQgPC0gImRldGllbmVuICBkb3MgbWVub3JlcyAgYXNhbHRvICB0cmFuc3BvcnRlIHDDumJsaWNvXG5cbmh0dHBzdGNvZmZpZXdhZnggaHR0cHN0Y29lcmZ5d3JsenkiICAgICAgCnN0cl9yZW1vdmVfYWxsKHR4dCwgcGF0dGVybiA9IGV4cHJlc2lvbikKYGBgCgpMbyBwYXNhbW9zIHBvciBlbCBjb3JwdXMKCmBgYHtyfQpteUNvcnB1cyA9IHRtX21hcChteUNvcnB1cywgY29udGVudF90cmFuc2Zvcm1lcihmdW5jdGlvbih4KSBzdHJfcmVtb3ZlX2FsbCh4LCBwYXR0ZXJuID0gZXhwcmVzaW9uKSkpCm15Q29ycHVzID0gdG1fbWFwKG15Q29ycHVzLCBjb250ZW50X3RyYW5zZm9ybWVyKGZ1bmN0aW9uKHgpIHN0cl9yZW1vdmVfYWxsKHgsIHBhdHRlcm4gPSAnXG4nKSkpCgpgYGAKCmBgYHtyfQppbnNwZWN0KG15Q29ycHVzWzE6MTBdKQpgYGAKCgpDcmVhbW9zIHVuYSBtYXRyaXogZGUgVMOpcm1pbm8tZG9jdW1lbnRvCgpgYGB7cn0KbXlEVE0gPSBEb2N1bWVudFRlcm1NYXRyaXgobXlDb3JwdXMsIGNvbnRyb2wgPSBsaXN0KG1pbldvcmRMZW5ndGggPSAxKSkKYGBgCgoKCmBgYHtyfQppbnNwZWN0KG15RFRNKQpgYGAKCmBgYHtyfQpwYWxhYnJhc19mcmVjdWVudGVzIDwtIGZpbmRNb3N0RnJlcVRlcm1zKG15RFRNLG4gPSAyNSwgSU5ERVggPSByZXAoMSxuRG9jcyhteURUTSkpKVtbMV1dCgpwYWxhYnJhc19mcmVjdWVudGVzCmBgYAoKCmBgYHtyfQpwYWxhYnJhc19mcmVjdWVudGVzIDwtIHRpYmJsZSh3b3JkID0gbmFtZXMocGFsYWJyYXNfZnJlY3VlbnRlcyksIGZyZXEgPXBhbGFicmFzX2ZyZWN1ZW50ZXMpCgp3b3JkY2xvdWQyKHBhbGFicmFzX2ZyZWN1ZW50ZXMsIHNodWZmbGUgPSBGQUxTRSkKYGBgCgojIyMgVG9waWMgTW9kZWxpbmcKCgpuZWNlc2l0byBlbGltaW5hciBsb3MgZG9jdW1lbnRvcyB2YWPDrW9zIChxdWUgbHVlZ28gZGUgbGEgbGltcGllemEgcXVlZGFyb24gc2luIG5pbmfDum5hIHBhbGFicmEpCgpgYGB7cn0KdWkgPSB1bmlxdWUobXlEVE0kaSkKZHRtID0gbXlEVE1bdWksXQoKZGltKG15RFRNKQpkaW0oZHRtKQpgYGAKCgpgYGB7ciBldmFsPUZBTFNFfQpsZGFfZml0IDwtIExEQShkdG0sIGsgPSAxMCxtZXRob2QgPSAiR2liYnMiLCBjb250cm9sID0gbGlzdChkZWx0YT0wLjYsc2VlZCA9IDEyMzQpKQpsZGFfZml0CmBgYAoKCmBgYHtyfQpzYXZlUkRTKGxkYV9maXQsJy4uL3Jlc3VsdGFkb3MvbGRhX2ZpdC5yZHMnKQpgYGAKCgpgYGB7cn0KVGVybXMgPC0gdGVybXMobGRhX2ZpdCwgMTApClRlcm1zCmBgYAoKCmBgYHtyfQp0b3BpY21vZGVsc19qc29uX2xkYXZpcyA8LSBmdW5jdGlvbihmaXR0ZWQsIGR0bSl7CiAgICBzdmRfdHNuZSA8LSBmdW5jdGlvbih4KSB0c25lKHN2ZCh4KSR1KQoKICAgICMgRmluZCByZXF1aXJlZCBxdWFudGl0aWVzCiAgICBwaGkgPC0gYXMubWF0cml4KHBvc3RlcmlvcihmaXR0ZWQpJHRlcm1zKQogICAgdGhldGEgPC0gYXMubWF0cml4KHBvc3RlcmlvcihmaXR0ZWQpJHRvcGljcykKICAgIHZvY2FiIDwtIGNvbG5hbWVzKHBoaSkKICAgIHRlcm1fZnJlcSA8LSBzbGFtOjpjb2xfc3VtcyhkdG0pCgogICAgIyBDb252ZXJ0IHRvIGpzb24KICAgIGpzb25fbGRhIDwtIExEQXZpczo6Y3JlYXRlSlNPTihwaGkgPSBwaGksIHRoZXRhID0gdGhldGEsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB2b2NhYiA9IHZvY2FiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgbWRzLm1ldGhvZCA9IHN2ZF90c25lLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgcGxvdC5vcHRzID0gbGlzdCh4bGFiPSJ0c25lIiwgeWxhYj0iIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBkb2MubGVuZ3RoID0gYXMudmVjdG9yKHRhYmxlKGR0bSRpKSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXJtLmZyZXF1ZW5jeSA9IHRlcm1fZnJlcSkKCiAgICByZXR1cm4oanNvbl9sZGEpCn0KYGBgCgpgYGB7cn0KanNvbl9yZXMgPC0gdG9waWNtb2RlbHNfanNvbl9sZGF2aXMobGRhX2ZpdCwgZHRtKQoKc2VyVmlzKGpzb25fcmVzKQpgYGA=