
Teoría
La minería de texto (Text Mining) es el proceso de
extraer información útil, patrones o conocimiento de textos no
estructurados.
Consta de 3 etapas:
1. Obtener datos: El reconocimiento óptico de caracteres (OCR en ingles)
es una tecnología que permite convertir imágenes de texto en texto
editable. También es conocido como extracción de texto de
imagenes.
2. Explorar datos: Representación gráfica o visual de los datos para su
interpretación. Los métodos más comunes son el análisis de sentimientos,
la nube de palabras y el topic modeling.
3. Análisis predictivo: Son las técnicas y modelos estadísticos para
predecir resultados futuros. Los modelos mas usados son el Random
Forest, Redes Neuronales y Regresiones.
Instalar paquetes y descargar
librerías
# install.packages("tidyverse") # Data wrangling (Manipulación de datos)
library(tidyverse)
## ── Attaching core tidyverse packages ──────────────────────── tidyverse 2.0.0 ──
## ✔ dplyr 1.1.3 ✔ readr 2.1.4
## ✔ forcats 1.0.0 ✔ stringr 1.5.0
## ✔ ggplot2 3.5.0 ✔ tibble 3.2.1
## ✔ lubridate 1.9.2 ✔ tidyr 1.3.0
## ✔ purrr 1.0.2
## ── Conflicts ────────────────────────────────────────── tidyverse_conflicts() ──
## ✖ dplyr::filter() masks stats::filter()
## ✖ dplyr::lag() masks stats::lag()
## ℹ Use the conflicted package (<http://conflicted.r-lib.org/>) to force all conflicts to become errors
# install.packages("tesseract") # OCR (Reconocimiento optico de caracteres)
library(tesseract)
# install.packages("magick") # Trabajar con imagenes de formato PNG
library(magick)
## Linking to ImageMagick 6.9.12.93
## Enabled features: cairo, fontconfig, freetype, heic, lcms, pango, raw, rsvg, webp
## Disabled features: fftw, ghostscript, x11
# install.packages("officer") # Nos permite exportar en formatos de office (Word, etc)
library(officer)
# install.packages("pdftools") # Nos permite leer PDFs.
library(pdftools)
## Using poppler version 23.04.0
# install.packages("purrr") # Para la función map (Aplicar funcion a cada uno de los vectores)
library(purrr)
# install.packages("tm") # Text mining
library(tm)
## Loading required package: NLP
##
## Attaching package: 'NLP'
##
## The following object is masked from 'package:ggplot2':
##
## annotate
# install.packages("RColorBrewer") # Manejar colores
library(RColorBrewer)
# install.packages("wordcloud") # Nube de palabras (Las grandes se repiten mas y viceversa)
library(wordcloud)
# install.packages("topicmodels") # Modelos de temas (De que habla el texto)
library(topicmodels)
# install.packages("ggplot2") # Graficas con más diseño
library(ggplot2)
2. Exploración de datos
Analisis de frecuencias
text <- readLines("http://www.sthda.com/sthda/RDoc/example-files/martin-luther-king-i-have-a-dream-speech.txt")
corpus <- Corpus(VectorSource(text)) # Pone cada renglón en una celda del vector
#inspect(corpus)
corpus <- tm_map(corpus, content_transformer(tolower)) # Pone todo en minúsculas
## Warning in tm_map.SimpleCorpus(corpus, content_transformer(tolower)):
## transformation drops documents
corpus <- tm_map(corpus, removePunctuation)
## Warning in tm_map.SimpleCorpus(corpus, removePunctuation): transformation drops
## documents
corpus <- tm_map(corpus, removeNumbers)
## Warning in tm_map.SimpleCorpus(corpus, removeNumbers): transformation drops
## documents
corpus <- tm_map(corpus, removeWords, stopwords("en")) # Elimina palabras que no hablan del tema
## Warning in tm_map.SimpleCorpus(corpus, removeWords, stopwords("en")):
## transformation drops documents
# corpus <- tm_map(corpus, removeWords, c("dream", "will")) Elimina las palabras puntuales
tdm <- TermDocumentMatrix(corpus)
m <- as.matrix(tdm) # Cuenta las veces que aparece cada palabra
frecuencia <- sort(rowSums(m), decreasing = TRUE) # Cuenta la frecuencia de cada palabra en el texto completo
frecuencia_df <- data.frame(word=names(frecuencia), freq = frecuencia) # Convierte la frecuencia en un data frame
ggplot(head(frecuencia_df, 10), aes(x=reorder(word, -freq), y=freq)) +
geom_bar(stat = "identity", fill = "skyblue") +
geom_text(aes(label = freq), vjust = -0.5) +
labs(title= "Top 10 palabras más frecuentes", x= "Palabras", subtitle= "Discurso de Martin Luther King", y= "Frecuencia") +
ylim(0,20)

Nube de palabras
# El procesamiento de datos antes de la nue de palabras es igual que en el análisis de frecuencias, desde importar el texto hasta convertir la frecuencia en un data frame "frecuencia_df"
set.seed(123)
wordcloud(word = frecuencia_df$word, freq = frecuencia_df$freq, min.freq = 1, random.order = FALSE, colors = brewer.pal(8, "Spectral"))

Ejercicio 2. Novela IT
Analisis de frecuencias
text1 <- read_docx("/Users/hectordelagarzatrevino/Library/CloudStorage/GoogleDrive-a01177960@tec.mx/Mi unidad/LIT/Sexto semestre/Inteligencia Artificial con Impacto Empresarial/Modulo 2/Sesion 6/EsoCapitulo3.docx")
texto_sin_acentos <- iconv(text1, to = "ASCII//TRANSLIT")
corpus1 <- Corpus(VectorSource(texto_sin_acentos)) # Pone cada renglón en una celda del vector
#inspect(corpus)
corpus1 <- tm_map(corpus1, content_transformer(tolower)) # Pone todo en minúsculas
## Warning in tm_map.SimpleCorpus(corpus1, content_transformer(tolower)):
## transformation drops documents
corpus1 <- tm_map(corpus1, removePunctuation)
## Warning in tm_map.SimpleCorpus(corpus1, removePunctuation): transformation
## drops documents
corpus1 <- tm_map(corpus1, removeNumbers)
## Warning in tm_map.SimpleCorpus(corpus1, removeNumbers): transformation drops
## documents
corpus1 <- tm_map(corpus1, removeWords, stopwords("spanish")) # Elimina palabras que no hablan del tema
## Warning in tm_map.SimpleCorpus(corpus1, removeWords, stopwords("spanish")):
## transformation drops documents
# corpus <- tm_map(corpus, removeWords, c("dream", "will")) Elimina las palabras puntuales
tdm1 <- TermDocumentMatrix(corpus1)
m1 <- as.matrix(tdm1) # Cuenta las veces que aparece cada palabra
frecuencia1 <- sort(rowSums(m1), decreasing = TRUE) # Cuenta la frecuencia de cada palabra en el texto completo
frecuencia_df1 <- data.frame(word=names(frecuencia1), freq = frecuencia1) # Convierte la frecuencia en un data frame
ggplot(head(frecuencia_df1, 10), aes(x=reorder(word, -freq), y=freq)) +
geom_bar(stat = "identity", fill = "skyblue") +
geom_text(aes(label = freq), vjust = -0.5) +
labs(title= "Top 10 palabras más frecuentes", x= "Palabras", subtitle= "Capitulo 3 de IT", y= "Frecuencia") +
ylim(0,70)

Nube de palabras
# El procesamiento de datos antes de la nue de palabras es igual que en el análisis de frecuencias, desde importar el texto hasta convertir la frecuencia en un data frame "frecuencia_df"
set.seed(123)
wordcloud(word = frecuencia_df1$word, freq = frecuencia_df1$freq, min.freq = 1, random.order = FALSE, colors = brewer.pal(8, "Spectral"))

LS0tCnRpdGxlOiAiVGV4dCBtaW5pbmciCmF1dGhvcjogIkjDqWN0b3IgR3VhZGFsdXBlIGRlIGxhIEdhcnphIFRyZXZpw7FvIC0gQTAxMTc3OTYwIgpkYXRlOiAiMjAyNC0wMi0yNiIKb3V0cHV0OiAKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiBUUlVFCiAgICB0b2NfZmxvYXQ6IFRSVUUKICAgIGNvZGVfZG93bmxvYWQ6IFRSVUUKICAgIHRoZW1lOiBjZXJ1bGVhbgogICAgaGlnaGxpZ2h0OiBrYXRlCi0tLQoKIVtdKC9Vc2Vycy9oZWN0b3JkZWxhZ2FyemF0cmV2aW5vL0xpYnJhcnkvQ2xvdWRTdG9yYWdlL0dvb2dsZURyaXZlLWEwMTE3Nzk2MEB0ZWMubXgvTWkgdW5pZGFkL0xJVC9TZXh0byBzZW1lc3RyZS9JbnRlbGlnZW5jaWEgQXJ0aWZpY2lhbCBjb24gSW1wYWN0byBFbXByZXNhcmlhbC9Nb2R1bG8gMi9TZXNpb24gNi85ODM1ODEwXzE2MTkxNzUxNDZfVGV4dCBNaW5pbmcuanBnKQoKIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsdWU7Ij5UZW9yw61hPC9zcGFuPgpMYSAqKm1pbmVyw61hIGRlIHRleHRvIChUZXh0IE1pbmluZykqKiBlcyBlbCBwcm9jZXNvIGRlIGV4dHJhZXIgaW5mb3JtYWNpw7NuIMO6dGlsLCBwYXRyb25lcyBvIGNvbm9jaW1pZW50byBkZSB0ZXh0b3Mgbm8gZXN0cnVjdHVyYWRvcy4gIAoKKipDb25zdGEgZGUgMyBldGFwYXM6KiogIAoxLiBPYnRlbmVyIGRhdG9zOiBFbCByZWNvbm9jaW1pZW50byDDs3B0aWNvIGRlIGNhcmFjdGVyZXMgKE9DUiBlbiBpbmdsZXMpIGVzIHVuYSB0ZWNub2xvZ8OtYSBxdWUgcGVybWl0ZSBjb252ZXJ0aXIgaW3DoWdlbmVzIGRlIHRleHRvIGVuIHRleHRvIGVkaXRhYmxlLiBUYW1iacOpbiBlcyBjb25vY2lkbyBjb21vICoqZXh0cmFjY2nDs24gZGUgdGV4dG8gZGUgaW1hZ2VuZXMqKi4gIAoyLiBFeHBsb3JhciBkYXRvczogUmVwcmVzZW50YWNpw7NuIGdyw6FmaWNhIG8gdmlzdWFsIGRlIGxvcyBkYXRvcyBwYXJhIHN1IGludGVycHJldGFjacOzbi4gTG9zIG3DqXRvZG9zIG3DoXMgY29tdW5lcyBzb24gZWwgYW7DoWxpc2lzIGRlIHNlbnRpbWllbnRvcywgbGEgbnViZSBkZSBwYWxhYnJhcyB5IGVsIHRvcGljIG1vZGVsaW5nLiAgCjMuIEFuw6FsaXNpcyBwcmVkaWN0aXZvOiBTb24gbGFzIHTDqWNuaWNhcyB5IG1vZGVsb3MgZXN0YWTDrXN0aWNvcyBwYXJhIHByZWRlY2lyIHJlc3VsdGFkb3MgZnV0dXJvcy4gTG9zIG1vZGVsb3MgbWFzIHVzYWRvcyBzb24gZWwgUmFuZG9tIEZvcmVzdCwgUmVkZXMgTmV1cm9uYWxlcyB5IFJlZ3Jlc2lvbmVzLiAgCgoKIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsdWU7Ij5JbnN0YWxhciBwYXF1ZXRlcyB5IGRlc2NhcmdhciBsaWJyZXLDrWFzPC9zcGFuPgpgYGB7cn0KIyBpbnN0YWxsLnBhY2thZ2VzKCJ0aWR5dmVyc2UiKSAjIERhdGEgd3JhbmdsaW5nIChNYW5pcHVsYWNpw7NuIGRlIGRhdG9zKQpsaWJyYXJ5KHRpZHl2ZXJzZSkKIyBpbnN0YWxsLnBhY2thZ2VzKCJ0ZXNzZXJhY3QiKSAjIE9DUiAoUmVjb25vY2ltaWVudG8gb3B0aWNvIGRlIGNhcmFjdGVyZXMpCmxpYnJhcnkodGVzc2VyYWN0KQojIGluc3RhbGwucGFja2FnZXMoIm1hZ2ljayIpICMgVHJhYmFqYXIgY29uIGltYWdlbmVzIGRlIGZvcm1hdG8gUE5HCmxpYnJhcnkobWFnaWNrKQojIGluc3RhbGwucGFja2FnZXMoIm9mZmljZXIiKSAjIE5vcyBwZXJtaXRlIGV4cG9ydGFyIGVuIGZvcm1hdG9zIGRlIG9mZmljZSAoV29yZCwgZXRjKQpsaWJyYXJ5KG9mZmljZXIpCiMgaW5zdGFsbC5wYWNrYWdlcygicGRmdG9vbHMiKSAjIE5vcyBwZXJtaXRlIGxlZXIgUERGcy4KbGlicmFyeShwZGZ0b29scykKIyBpbnN0YWxsLnBhY2thZ2VzKCJwdXJyciIpICMgUGFyYSBsYSBmdW5jacOzbiBtYXAgKEFwbGljYXIgZnVuY2lvbiBhIGNhZGEgdW5vIGRlIGxvcyB2ZWN0b3JlcykKbGlicmFyeShwdXJycikKIyBpbnN0YWxsLnBhY2thZ2VzKCJ0bSIpICMgVGV4dCBtaW5pbmcKbGlicmFyeSh0bSkKIyBpbnN0YWxsLnBhY2thZ2VzKCJSQ29sb3JCcmV3ZXIiKSAjIE1hbmVqYXIgY29sb3JlcwpsaWJyYXJ5KFJDb2xvckJyZXdlcikKIyBpbnN0YWxsLnBhY2thZ2VzKCJ3b3JkY2xvdWQiKSAjIE51YmUgZGUgcGFsYWJyYXMgKExhcyBncmFuZGVzIHNlIHJlcGl0ZW4gbWFzIHkgdmljZXZlcnNhKQpsaWJyYXJ5KHdvcmRjbG91ZCkKIyBpbnN0YWxsLnBhY2thZ2VzKCJ0b3BpY21vZGVscyIpICMgTW9kZWxvcyBkZSB0ZW1hcyAoRGUgcXVlIGhhYmxhIGVsIHRleHRvKQpsaWJyYXJ5KHRvcGljbW9kZWxzKQojIGluc3RhbGwucGFja2FnZXMoImdncGxvdDIiKSAjIEdyYWZpY2FzIGNvbiBtw6FzIGRpc2XDsW8KbGlicmFyeShnZ3Bsb3QyKQpgYGAKCiMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibHVlOyI+MS4gT2J0ZW5lciBEYXRvcyBtZWRpYW50ZSBPQ1I8L3NwYW4+CgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsdWU7Ij5EZSBpbWFnZW4gUE5HIGEgdGV4dG8gV09SRDwvc3Bhbj4KYGBge3J9CiMgaW1hZ2VuMSA8LSBpbWFnZV9yZWFkKCIvVXNlcnMvaGVjdG9yZGVsYWdhcnphdHJldmluby9MaWJyYXJ5L0Nsb3VkU3RvcmFnZS9Hb29nbGVEcml2ZS1hMDExNzc5NjBAdGVjLm14L01pIHVuaWRhZC9MSVQvU2V4dG8gc2VtZXN0cmUvSW50ZWxpZ2VuY2lhIEFydGlmaWNpYWwgY29uIEltcGFjdG8gRW1wcmVzYXJpYWwvTW9kdWxvIDIvU2VzaW9uIDYvaW1hZ2VuMS5QTkciKQojIHRleHRvMSA8LSBvY3IoaW1hZ2VuMSkKIyB0ZXh0bzEKIyBkb2MxIDwtIHJlYWRfZG9jeCgpICMgQ3JlYSBkb2N1bWVudG8gZGUgd29yZCBlbiBibGFuY28KIyBkb2MxIDwtIGRvYzEgJT4lIGJvZHlfYWRkX3Bhcih0ZXh0bzEsIHN0eWxlPSJOb3JtYWwiKSAjIFBlZ2EgZWwgdGV4dG8gZW4gZWwgd29yZAojIHByaW50KGRvYzEsIHRhcmdldCA9ICJUZXh0bzEuZG9jeCIpICMgRGVzY2FyZ2EgZWwgYXJjaGl2byBlbiB3b3JkIGVuIGxhIGNvbXB1dGFkb3JhCmBgYAoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibHVlOyI+SW1hZ2VuIFBORyBhIFRleHRvIFdPUkQgZW4gZXNwYcOxb2w8L3NwYW4+CgpbQ29uc3VsdGFyIGlkaW9tYXMgZGlzcG9uaWJsZXNdKGh0dHBzOi8vdGVzc2VyYWN0LW9jci5naXRodWIuaW8vdGVzc2RvYy9EYXRhLUZpbGVzLWluLWRpZmZlcmVudC12ZXJzaW9ucy5odG1sKQpgYGB7cn0KIyBpbWFnZW4yIDwtIGltYWdlX3JlYWQoIi9Vc2Vycy9oZWN0b3JkZWxhZ2FyemF0cmV2aW5vL0xpYnJhcnkvQ2xvdWRTdG9yYWdlL0dvb2dsZURyaXZlLWEwMTE3Nzk2MEB0ZWMubXgvTWkgdW5pZGFkL0xJVC9TZXh0byBzZW1lc3RyZS9JbnRlbGlnZW5jaWEgQXJ0aWZpY2lhbCBjb24gSW1wYWN0byBFbXByZXNhcmlhbC9Nb2R1bG8gMi9TZXNpb24gNi9pbWFnZW4yLlBORyIpCiMgdGVzc2VyYWN0X2Rvd25sb2FkKCJzcGEiKQojIHRleHRvMiA8LSBvY3IoaW1hZ2VuMiwgZW5naW5lID0gdGVzc2VyYWN0KCJzcGEiKSkKIyB0ZXh0bzIKIyBkb2MyIDwtIHJlYWRfZG9jeCgpICMgQ3JlYSBkb2N1bWVudG8gZGUgd29yZCBlbiBibGFuY28KIyBkb2MyIDwtIGRvYzIgJT4lIGJvZHlfYWRkX3Bhcih0ZXh0bzIsIHN0eWxlPSJOb3JtYWwiKSAjIFBlZ2EgZWwgdGV4dG8gZW4gZWwgd29yZAojIHByaW50KGRvYzIsIHRhcmdldCA9ICJUZXh0bzIuZG9jeCIpICMgRGVzY2FyZ2EgZWwgYXJjaGl2byBlbiB3b3JkIGVuIGxhIGNvbXB1dGFkb3JhCmBgYAoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibHVlOyI+RGUgUERGIGEgdGV4dG8gZW4gV09SRDwvc3Bhbj4KYGBge3J9CiMgcGRmMSA8LSBwZGZfY29udmVydCgiL1VzZXJzL2hlY3RvcmRlbGFnYXJ6YXRyZXZpbm8vTGlicmFyeS9DbG91ZFN0b3JhZ2UvR29vZ2xlRHJpdmUtYTAxMTc3OTYwQHRlYy5teC9NaSB1bmlkYWQvTElUL1NleHRvIHNlbWVzdHJlL0ludGVsaWdlbmNpYSBBcnRpZmljaWFsIGNvbiBJbXBhY3RvIEVtcHJlc2FyaWFsL01vZHVsbyAyL1Nlc2lvbiA2L3BkZjEucGRmIiwgZHBpID0gNjAwKSAlPiUgbWFwKG9jcikKYGBgCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsdWU7Ij5BY3RpdmlkYWQgMS4gTm92ZWxhICJJVCI8L3NwYW4+CgojIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibHVlOyI+RGUgUERGIGEgaW1hZ2VuIFBORzwvc3Bhbj4KYGBge3J9CiMgcGRmZXNvIDwtIHBkZl9jb252ZXJ0KCIvVXNlcnMvaGVjdG9yZGVsYWdhcnphdHJldmluby9MaWJyYXJ5L0Nsb3VkU3RvcmFnZS9Hb29nbGVEcml2ZS1hMDExNzc5NjBAdGVjLm14L01pIHVuaWRhZC9MSVQvU2V4dG8gc2VtZXN0cmUvSW50ZWxpZ2VuY2lhIEFydGlmaWNpYWwgY29uIEltcGFjdG8gRW1wcmVzYXJpYWwvTW9kdWxvIDIvU2VzaW9uIDYvZXNvMy5wZGYiLCBkcGkgPSA2MDApICU+JSBtYXAob2NyKQpgYGAKCiMjIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsdWU7Ij5EZSBpbWFnZW4gUE5HIGEgdGV4dG8gV09SRDwvc3Bhbj4KYGBge3J9CiMgaW1hZ2VuMyA8LSBpbWFnZV9yZWFkKCIvVXNlcnMvaGVjdG9yZGVsYWdhcnphdHJldmluby9MaWJyYXJ5L0Nsb3VkU3RvcmFnZS9Hb29nbGVEcml2ZS1hMDExNzc5NjBAdGVjLm14L01pIHVuaWRhZC9MSVQvU2V4dG8gc2VtZXN0cmUvSW50ZWxpZ2VuY2lhIEFydGlmaWNpYWwgY29uIEltcGFjdG8gRW1wcmVzYXJpYWwvTW9kdWxvIDIvU2VzaW9uIDYvZXNvM18xLlBORyIpCiMgdGVzc2VyYWN0X2Rvd25sb2FkKCJzcGEiKQojIHRleHRvMyA8LSBvY3IoaW1hZ2VuMywgZW5naW5lID0gdGVzc2VyYWN0KCJzcGEiKSkKCiMgaW1hZ2VuNCA8LSBpbWFnZV9yZWFkKCIvVXNlcnMvaGVjdG9yZGVsYWdhcnphdHJldmluby9MaWJyYXJ5L0Nsb3VkU3RvcmFnZS9Hb29nbGVEcml2ZS1hMDExNzc5NjBAdGVjLm14L01pIHVuaWRhZC9MSVQvU2V4dG8gc2VtZXN0cmUvSW50ZWxpZ2VuY2lhIEFydGlmaWNpYWwgY29uIEltcGFjdG8gRW1wcmVzYXJpYWwvTW9kdWxvIDIvU2VzaW9uIDYvZXNvM18yLlBORyIpCiMgdGVzc2VyYWN0X2Rvd25sb2FkKCJzcGEiKQojIHRleHRvNCA8LSBvY3IoaW1hZ2VuNCwgZW5naW5lID0gdGVzc2VyYWN0KCJzcGEiKSkKCiMgaW1hZ2VuNSA8LSBpbWFnZV9yZWFkKCIvVXNlcnMvaGVjdG9yZGVsYWdhcnphdHJldmluby9MaWJyYXJ5L0Nsb3VkU3RvcmFnZS9Hb29nbGVEcml2ZS1hMDExNzc5NjBAdGVjLm14L01pIHVuaWRhZC9MSVQvU2V4dG8gc2VtZXN0cmUvSW50ZWxpZ2VuY2lhIEFydGlmaWNpYWwgY29uIEltcGFjdG8gRW1wcmVzYXJpYWwvTW9kdWxvIDIvU2VzaW9uIDYvZXNvM18zLlBORyIpCiMgdGVzc2VyYWN0X2Rvd25sb2FkKCJzcGEiKQojIHRleHRvNSA8LSBvY3IoaW1hZ2VuNSwgZW5naW5lID0gdGVzc2VyYWN0KCJzcGEiKSkKCiMgQWdyZWdhciBsb3MgdGV4dG9zIGFsIGRvY3VtZW50byBkZSBXb3JkCiMgQ29tcGxldG8gPC0gcmVhZF9kb2N4KCkKCiMgQ29tcGxldG8gPC0gQ29tcGxldG8gJT4lCiMgIGJvZHlfYWRkX3Bhcih0ZXh0bzMsIHN0eWxlID0gIk5vcm1hbCIpICU+JQojICBib2R5X2FkZF9wYXIodGV4dG80LCBzdHlsZSA9ICJOb3JtYWwiKSAlPiUKIyAgYm9keV9hZGRfcGFyKHRleHRvNSwgc3R5bGUgPSAiTm9ybWFsIikKCiMgcHJpbnQoQ29tcGxldG8sIHRhcmdldCA9ICJFc29DYXBpdHVsbzMuZG9jeCIpCmBgYAoKIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsdWU7Ij4yLiBFeHBsb3JhY2nDs24gZGUgZGF0b3M8L3NwYW4+CgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsdWU7Ij5BbmFsaXNpcyBkZSBmcmVjdWVuY2lhczwvc3Bhbj4KYGBge3J9CnRleHQgPC0gcmVhZExpbmVzKCJodHRwOi8vd3d3LnN0aGRhLmNvbS9zdGhkYS9SRG9jL2V4YW1wbGUtZmlsZXMvbWFydGluLWx1dGhlci1raW5nLWktaGF2ZS1hLWRyZWFtLXNwZWVjaC50eHQiKQoKY29ycHVzIDwtIENvcnB1cyhWZWN0b3JTb3VyY2UodGV4dCkpICMgUG9uZSBjYWRhIHJlbmdsw7NuIGVuIHVuYSBjZWxkYSBkZWwgdmVjdG9yCiNpbnNwZWN0KGNvcnB1cykKY29ycHVzIDwtIHRtX21hcChjb3JwdXMsIGNvbnRlbnRfdHJhbnNmb3JtZXIodG9sb3dlcikpICMgUG9uZSB0b2RvIGVuIG1pbsO6c2N1bGFzCmNvcnB1cyA8LSB0bV9tYXAoY29ycHVzLCByZW1vdmVQdW5jdHVhdGlvbikKY29ycHVzIDwtIHRtX21hcChjb3JwdXMsIHJlbW92ZU51bWJlcnMpCmNvcnB1cyA8LSB0bV9tYXAoY29ycHVzLCByZW1vdmVXb3Jkcywgc3RvcHdvcmRzKCJlbiIpKSAjIEVsaW1pbmEgcGFsYWJyYXMgcXVlIG5vIGhhYmxhbiBkZWwgdGVtYQojIGNvcnB1cyA8LSB0bV9tYXAoY29ycHVzLCByZW1vdmVXb3JkcywgYygiZHJlYW0iLCAid2lsbCIpKSBFbGltaW5hIGxhcyBwYWxhYnJhcyBwdW50dWFsZXMKCnRkbSA8LSBUZXJtRG9jdW1lbnRNYXRyaXgoY29ycHVzKSAKbSA8LSBhcy5tYXRyaXgodGRtKSAjIEN1ZW50YSBsYXMgdmVjZXMgcXVlIGFwYXJlY2UgY2FkYSBwYWxhYnJhCgpmcmVjdWVuY2lhIDwtIHNvcnQocm93U3VtcyhtKSwgZGVjcmVhc2luZyA9IFRSVUUpICMgQ3VlbnRhIGxhIGZyZWN1ZW5jaWEgZGUgY2FkYSBwYWxhYnJhIGVuIGVsIHRleHRvIGNvbXBsZXRvIAoKZnJlY3VlbmNpYV9kZiA8LSBkYXRhLmZyYW1lKHdvcmQ9bmFtZXMoZnJlY3VlbmNpYSksIGZyZXEgPSBmcmVjdWVuY2lhKSAjIENvbnZpZXJ0ZSBsYSBmcmVjdWVuY2lhIGVuIHVuIGRhdGEgZnJhbWUKCmdncGxvdChoZWFkKGZyZWN1ZW5jaWFfZGYsIDEwKSwgYWVzKHg9cmVvcmRlcih3b3JkLCAtZnJlcSksIHk9ZnJlcSkpICsKICAgICAgICAgICAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAic2t5Ymx1ZSIpICsKICAgICAgICAgICAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IGZyZXEpLCB2anVzdCA9IC0wLjUpICsKICAgICAgICAgICAgbGFicyh0aXRsZT0gIlRvcCAxMCBwYWxhYnJhcyBtw6FzIGZyZWN1ZW50ZXMiLCB4PSAiUGFsYWJyYXMiLCBzdWJ0aXRsZT0gIkRpc2N1cnNvIGRlIE1hcnRpbiBMdXRoZXIgS2luZyIsIHk9ICJGcmVjdWVuY2lhIikgKwogICAgICAgICAgICB5bGltKDAsMjApCmBgYAoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibHVlOyI+TnViZSBkZSBwYWxhYnJhczwvc3Bhbj4KYGBge3J9CiMgRWwgcHJvY2VzYW1pZW50byBkZSBkYXRvcyBhbnRlcyBkZSBsYSBudWUgZGUgcGFsYWJyYXMgZXMgaWd1YWwgcXVlIGVuIGVsIGFuw6FsaXNpcyBkZSBmcmVjdWVuY2lhcywgZGVzZGUgaW1wb3J0YXIgZWwgdGV4dG8gaGFzdGEgY29udmVydGlyIGxhIGZyZWN1ZW5jaWEgZW4gdW4gZGF0YSBmcmFtZSAiZnJlY3VlbmNpYV9kZiIKc2V0LnNlZWQoMTIzKQp3b3JkY2xvdWQod29yZCA9IGZyZWN1ZW5jaWFfZGYkd29yZCwgZnJlcSA9IGZyZWN1ZW5jaWFfZGYkZnJlcSwgbWluLmZyZXEgPSAxLCByYW5kb20ub3JkZXIgPSBGQUxTRSwgY29sb3JzID0gYnJld2VyLnBhbCg4LCAiU3BlY3RyYWwiKSkKYGBgCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsdWU7Ij5FamVyY2ljaW8gMi4gTm92ZWxhIElUPC9zcGFuPgoKIyMjIDxzcGFuIHN0eWxlPSJjb2xvcjogYmx1ZTsiPkFuYWxpc2lzIGRlIGZyZWN1ZW5jaWFzPC9zcGFuPgpgYGB7cn0KdGV4dDEgPC0gcmVhZF9kb2N4KCIvVXNlcnMvaGVjdG9yZGVsYWdhcnphdHJldmluby9MaWJyYXJ5L0Nsb3VkU3RvcmFnZS9Hb29nbGVEcml2ZS1hMDExNzc5NjBAdGVjLm14L01pIHVuaWRhZC9MSVQvU2V4dG8gc2VtZXN0cmUvSW50ZWxpZ2VuY2lhIEFydGlmaWNpYWwgY29uIEltcGFjdG8gRW1wcmVzYXJpYWwvTW9kdWxvIDIvU2VzaW9uIDYvRXNvQ2FwaXR1bG8zLmRvY3giKQoKdGV4dG9fc2luX2FjZW50b3MgPC0gaWNvbnYodGV4dDEsIHRvID0gIkFTQ0lJLy9UUkFOU0xJVCIpCgpjb3JwdXMxIDwtIENvcnB1cyhWZWN0b3JTb3VyY2UodGV4dG9fc2luX2FjZW50b3MpKSAjIFBvbmUgY2FkYSByZW5nbMOzbiBlbiB1bmEgY2VsZGEgZGVsIHZlY3RvcgojaW5zcGVjdChjb3JwdXMpCmNvcnB1czEgPC0gdG1fbWFwKGNvcnB1czEsIGNvbnRlbnRfdHJhbnNmb3JtZXIodG9sb3dlcikpICMgUG9uZSB0b2RvIGVuIG1pbsO6c2N1bGFzCmNvcnB1czEgPC0gdG1fbWFwKGNvcnB1czEsIHJlbW92ZVB1bmN0dWF0aW9uKQpjb3JwdXMxIDwtIHRtX21hcChjb3JwdXMxLCByZW1vdmVOdW1iZXJzKQpjb3JwdXMxIDwtIHRtX21hcChjb3JwdXMxLCByZW1vdmVXb3Jkcywgc3RvcHdvcmRzKCJzcGFuaXNoIikpICMgRWxpbWluYSBwYWxhYnJhcyBxdWUgbm8gaGFibGFuIGRlbCB0ZW1hCiMgY29ycHVzIDwtIHRtX21hcChjb3JwdXMsIHJlbW92ZVdvcmRzLCBjKCJkcmVhbSIsICJ3aWxsIikpIEVsaW1pbmEgbGFzIHBhbGFicmFzIHB1bnR1YWxlcwoKdGRtMSA8LSBUZXJtRG9jdW1lbnRNYXRyaXgoY29ycHVzMSkgCm0xIDwtIGFzLm1hdHJpeCh0ZG0xKSAjIEN1ZW50YSBsYXMgdmVjZXMgcXVlIGFwYXJlY2UgY2FkYSBwYWxhYnJhCgpmcmVjdWVuY2lhMSA8LSBzb3J0KHJvd1N1bXMobTEpLCBkZWNyZWFzaW5nID0gVFJVRSkgIyBDdWVudGEgbGEgZnJlY3VlbmNpYSBkZSBjYWRhIHBhbGFicmEgZW4gZWwgdGV4dG8gY29tcGxldG8gCgpmcmVjdWVuY2lhX2RmMSA8LSBkYXRhLmZyYW1lKHdvcmQ9bmFtZXMoZnJlY3VlbmNpYTEpLCBmcmVxID0gZnJlY3VlbmNpYTEpICMgQ29udmllcnRlIGxhIGZyZWN1ZW5jaWEgZW4gdW4gZGF0YSBmcmFtZQoKZ2dwbG90KGhlYWQoZnJlY3VlbmNpYV9kZjEsIDEwKSwgYWVzKHg9cmVvcmRlcih3b3JkLCAtZnJlcSksIHk9ZnJlcSkpICsKICAgICAgICAgICAgZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIsIGZpbGwgPSAic2t5Ymx1ZSIpICsKICAgICAgICAgICAgZ2VvbV90ZXh0KGFlcyhsYWJlbCA9IGZyZXEpLCB2anVzdCA9IC0wLjUpICsKICAgICAgICAgICAgbGFicyh0aXRsZT0gIlRvcCAxMCBwYWxhYnJhcyBtw6FzIGZyZWN1ZW50ZXMiLCB4PSAiUGFsYWJyYXMiLCBzdWJ0aXRsZT0gIkNhcGl0dWxvIDMgZGUgSVQiLCB5PSAiRnJlY3VlbmNpYSIpICsKICAgICAgICAgICAgeWxpbSgwLDcwKQpgYGAKCiMjIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsdWU7Ij5OdWJlIGRlIHBhbGFicmFzPC9zcGFuPgpgYGB7cn0KIyBFbCBwcm9jZXNhbWllbnRvIGRlIGRhdG9zIGFudGVzIGRlIGxhIG51ZSBkZSBwYWxhYnJhcyBlcyBpZ3VhbCBxdWUgZW4gZWwgYW7DoWxpc2lzIGRlIGZyZWN1ZW5jaWFzLCBkZXNkZSBpbXBvcnRhciBlbCB0ZXh0byBoYXN0YSBjb252ZXJ0aXIgbGEgZnJlY3VlbmNpYSBlbiB1biBkYXRhIGZyYW1lICJmcmVjdWVuY2lhX2RmIgpzZXQuc2VlZCgxMjMpCndvcmRjbG91ZCh3b3JkID0gZnJlY3VlbmNpYV9kZjEkd29yZCwgZnJlcSA9IGZyZWN1ZW5jaWFfZGYxJGZyZXEsIG1pbi5mcmVxID0gMSwgcmFuZG9tLm9yZGVyID0gRkFMU0UsIGNvbG9ycyA9IGJyZXdlci5wYWwoOCwgIlNwZWN0cmFsIikpCmBgYAoK