Paula Cazali

Utilizando los documentos del libro del Quijote y la Biblia generar la matriz de Markov y el punto de estabilizacion.

Cargando las librerias necesarias:

library(tm)
library(dplyr)
library(tidyr)
library(parallel)

Cargando el libro el quijote y la biblia:

quijote <- file("quijote.txt", encoding = "UTF-8")
biblia <- file("biblia.txt", encoding = "UTF-8")

Leyendo las lineas del libro del Quijote, luego por medio de la funcion “paste” se concatenan todos los vectores, o sea todas las lineas. Y por ultimo se separa cada elemento en substring.

quijote_lines <- readLines(quijote)
quijote_words <- paste(quijote_lines,collapse=" ")
quijote_words <- strsplit(quijote_words, split = " ") %>% unlist()
head(quijote_words)
[1] "EL"        "INGENIOSO" "HIDALGO"   "DON"       "QUIJOTE"   "DE"       

Ahora haremos lo mismo pero con el libro de la biblia:

biblia_lines <- readLines(biblia)
biblia_words <- paste(biblia_lines,collapse=" ")
biblia_words <- strsplit(biblia_words, split = " ") %>% unlist()
head(biblia_words)
[1] "LA"          "SANTA"       "BIBLIA,"     "ANTIGUO"     "TESTAMENTO," "VERSIÓN"    

A cada uno de los libros se le quitaran los signos de puntuacion, los numeros, las mayusculas y todos los pronombres personales o articulos

quijote_words <- sapply(quijote_words,"removePunctuation",USE.NAMES = FALSE)
quijote_words <- sapply(quijote_words,"tolower",USE.NAMES = FALSE)
quijote_words <- sapply(quijote_words,"stripWhitespace",USE.NAMES = FALSE)
quijote_words <- sapply(quijote_words,"removeNumbers",USE.NAMES = FALSE)
quijote_words <- sapply(quijote_words,"removeWords",
                      words=stopwords('spanish'),
                      USE.NAMES = FALSE)
quijote_words <- quijote_words[quijote_words!=""]
quijote_words <- quijote_words[quijote_words!=" "]
biblia_words <- sapply(biblia_words,"removePunctuation",USE.NAMES = FALSE)
biblia_words <- sapply(biblia_words,"tolower",USE.NAMES = FALSE)
biblia_words <- sapply(biblia_words,"stripWhitespace",USE.NAMES = FALSE)
biblia_words <- sapply(biblia_words,"removeNumbers",USE.NAMES = FALSE)
biblia_words <- sapply(biblia_words,"removeWords",
                      words=stopwords('spanish'),
                      USE.NAMES = FALSE)
biblia_words <- biblia_words[biblia_words!=""]
biblia_words <- biblia_words[biblia_words!=" "]

Ahora se va a reducir el tamanio de los libros, esto con el proposito de tener una matriz que se pueda operar sin ningun problema utilizando R.

Reduciendo el tamanio del libro del Quijote:

quijote_words2 <- quijote_words[1:1000]

Reduciento del tamanio del libro de la Biblia:

biblia_words2 <- biblia_words[1:1000]

Ahora uniremos todas las palabras de cada libro en uno solo:

libro_completo <- c(quijote_words2, biblia_words2)
head(libro_completo)
[1] "ingenioso" "hidalgo"   "don"       "quijote"   "mancha"    "miguel"   

Vamos a obtener la cantidad palabras unicas que hay en el vector:

length(unique(libro_completo))
[1] 995

La funcion ngrams crea un vector de caracteres con palabras en secuencia:

bigrams <-
  lapply(ngrams(libro_completo,2), paste, collapse=" ") %>% unlist()
head(bigrams)
[1] "ingenioso hidalgo" "hidalgo don"       "don quijote"       "quijote mancha"    "mancha miguel"     "miguel cervantes" 

Convertiremos la tabla de characters en un dataframe:

bigrams <-
  table(bigrams) %>% as.data.frame()
head(bigrams)

Separando cada vector con dos palabras dentro del dataframe, se nombran por word1 y word2:

bigrams <- bigrams %>% 
  separate(bigrams,into=c("word1","word2"),sep=" ")

Mostrando una muestra de los bigramas, donde la Frecuencia sea mayor a 5.

Se colocan cada una de las word2 en las columnas y en las filas y se cuenta la frecuencia:

cadena_markov<-
  bigrams %>% 
  spread(key = word2,value = Freq,fill = 0)
head(cadena_markov)

Convirtiendo la cadena de markov a una matriz:

mm_chain<-
  as.matrix(cadena_markov[,-1])

Cambiando las filas por las word1 de la cadena de markov, dimension de la cadena:

row.names(mm_chain) <- cadena_markov$word1
dim(mm_chain)
[1] 995 995

Numero de filas y columnas para definir la matriz:

mm_chain<-
  mm_chain/rowSums(mm_chain)
nrow(mm_chain)
[1] 995
ncol(mm_chain)
[1] 995

Creando la matriz con el numero de filas, numero de columnas de la cadena de markov. Y se divide entre la suma de las frecuencias, para obtener la probabilidad:

s<-
  matrix(1,ncol=995,nrow=995)
s<-
  s/rowSums(s)

Evitando clusters no unidos:

M <- 0.85*mm_chain+0.15*s

Metodo para procesar la matriz en distintos hilos de ejecucion.

matprod.par <- function(cl, A, B){
  if (ncol(A) != nrow(B)) stop("Matrices do not conforme")
  idx   <- splitIndices(nrow(A), length(cl))
  Alist <- lapply(idx, function(ii) A[ii,,drop=FALSE])
  ## ans   <- clusterApply(cl, Alist, function(aa, B) aa %*% B, B)
  ## Same as above, but faster:
  ans   <- clusterApply(cl, Alist, get("%*%"), B)
  do.call(rbind, ans) }
(nc <- detectCores())
[1] 4
cl <- makeCluster(rep("localhost", nc))

Multiplicando la matriz por ella misma:

M1 <-
  matprod.par(cl,M,M)
M <- M %*% M
stopCluster(cl)

Vista de la Matriz luego de la multiplicacion:

Estabilidad de la Matriz multiplicandola varias veces por ella misma:

M_2 <- M
for(i in 1:25){
  c2 <- makeCluster(rep("localhost", nc))
M2 <-
  matprod.par(c2,M,M)
M_2 <- M_2 %*% M
stopCluster(c2)
}

Multiplicando la matriz 10 veces por ella misma:

Multiplicando la matriz 20 veces por ella misma:

M_2 <- M
for(i in 1:20){
  c2 <- makeCluster(rep("localhost", nc))
M2 <-
  matprod.par(c2,M,M)
M_2 <- M_2 %*% M
stopCluster(c2)
}

Multiplicando la matriz 25 veces por ella misma:

M_2 <- M
for(i in 1:25){
  c2 <- makeCluster(rep("localhost", nc))
M2 <-
  matprod.par(c2,M,M)
M_2 <- M_2 %*% M
stopCluster(c2)
}

Como se puede observar la matriz ya llego a la estabilidad, porque las probabilidades dentro de la matriz ya no estan cambiando.

LS0tDQp0aXRsZTogIkNhZGVuYXMgZGUgTWFya292IHkgVGV4dCBNaW5pbmciDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KDQohW10oZ2FsaWxlby5wbmcpDQoNCiMjIFBhdWxhIENhemFsaQ0KDQojIyMjIFV0aWxpemFuZG8gbG9zIGRvY3VtZW50b3MgZGVsIGxpYnJvIGRlbCBRdWlqb3RlIHkgbGEgQmlibGlhIGdlbmVyYXIgbGEgbWF0cml6IGRlIE1hcmtvdiB5IGVsIHB1bnRvIGRlIGVzdGFiaWxpemFjaW9uLg0KDQpDYXJnYW5kbyBsYXMgbGlicmVyaWFzIG5lY2VzYXJpYXM6DQpgYGB7cn0NCmxpYnJhcnkodG0pDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeSh0aWR5cikNCmxpYnJhcnkocGFyYWxsZWwpDQpgYGANCkNhcmdhbmRvIGVsIGxpYnJvIGVsIHF1aWpvdGUgeSBsYSBiaWJsaWE6DQpgYGB7cn0NCnF1aWpvdGUgPC0gZmlsZSgicXVpam90ZS50eHQiLCBlbmNvZGluZyA9ICJVVEYtOCIpDQpiaWJsaWEgPC0gZmlsZSgiYmlibGlhLnR4dCIsIGVuY29kaW5nID0gIlVURi04IikNCmBgYA0KTGV5ZW5kbyBsYXMgbGluZWFzIGRlbCBsaWJybyBkZWwgUXVpam90ZSwgbHVlZ28gcG9yIG1lZGlvIGRlIGxhIGZ1bmNpb24gInBhc3RlIiBzZSBjb25jYXRlbmFuIHRvZG9zIGxvcyB2ZWN0b3JlcywgbyBzZWEgdG9kYXMgbGFzIGxpbmVhcy4gWSBwb3IgdWx0aW1vIHNlIHNlcGFyYSBjYWRhIGVsZW1lbnRvIGVuIHN1YnN0cmluZy4NCmBgYHtyfQ0KcXVpam90ZV9saW5lcyA8LSByZWFkTGluZXMocXVpam90ZSkNCnF1aWpvdGVfd29yZHMgPC0gcGFzdGUocXVpam90ZV9saW5lcyxjb2xsYXBzZT0iICIpDQpxdWlqb3RlX3dvcmRzIDwtIHN0cnNwbGl0KHF1aWpvdGVfd29yZHMsIHNwbGl0ID0gIiAiKSAlPiUgdW5saXN0KCkNCmhlYWQocXVpam90ZV93b3JkcykNCmBgYA0KQWhvcmEgaGFyZW1vcyBsbyBtaXNtbyBwZXJvIGNvbiBlbCBsaWJybyBkZSBsYSBiaWJsaWE6DQpgYGB7cn0NCmJpYmxpYV9saW5lcyA8LSByZWFkTGluZXMoYmlibGlhKQ0KYmlibGlhX3dvcmRzIDwtIHBhc3RlKGJpYmxpYV9saW5lcyxjb2xsYXBzZT0iICIpDQpiaWJsaWFfd29yZHMgPC0gc3Ryc3BsaXQoYmlibGlhX3dvcmRzLCBzcGxpdCA9ICIgIikgJT4lIHVubGlzdCgpDQpoZWFkKGJpYmxpYV93b3JkcykNCmBgYA0KDQpBIGNhZGEgdW5vIGRlIGxvcyBsaWJyb3Mgc2UgbGUgcXVpdGFyYW4gbG9zIHNpZ25vcyBkZSBwdW50dWFjaW9uLCBsb3MgbnVtZXJvcywgbGFzIG1heXVzY3VsYXMgeSB0b2RvcyBsb3MgcHJvbm9tYnJlcyBwZXJzb25hbGVzIG8gYXJ0aWN1bG9zDQpgYGB7cn0NCnF1aWpvdGVfd29yZHMgPC0gc2FwcGx5KHF1aWpvdGVfd29yZHMsInJlbW92ZVB1bmN0dWF0aW9uIixVU0UuTkFNRVMgPSBGQUxTRSkNCnF1aWpvdGVfd29yZHMgPC0gc2FwcGx5KHF1aWpvdGVfd29yZHMsInRvbG93ZXIiLFVTRS5OQU1FUyA9IEZBTFNFKQ0KcXVpam90ZV93b3JkcyA8LSBzYXBwbHkocXVpam90ZV93b3Jkcywic3RyaXBXaGl0ZXNwYWNlIixVU0UuTkFNRVMgPSBGQUxTRSkNCnF1aWpvdGVfd29yZHMgPC0gc2FwcGx5KHF1aWpvdGVfd29yZHMsInJlbW92ZU51bWJlcnMiLFVTRS5OQU1FUyA9IEZBTFNFKQ0KcXVpam90ZV93b3JkcyA8LSBzYXBwbHkocXVpam90ZV93b3JkcywicmVtb3ZlV29yZHMiLA0KICAgICAgICAgICAgICAgICAgICAgIHdvcmRzPXN0b3B3b3Jkcygnc3BhbmlzaCcpLA0KICAgICAgICAgICAgICAgICAgICAgIFVTRS5OQU1FUyA9IEZBTFNFKQ0KcXVpam90ZV93b3JkcyA8LSBxdWlqb3RlX3dvcmRzW3F1aWpvdGVfd29yZHMhPSIiXQ0KcXVpam90ZV93b3JkcyA8LSBxdWlqb3RlX3dvcmRzW3F1aWpvdGVfd29yZHMhPSIgIl0NCmBgYA0KDQpgYGB7cn0NCmJpYmxpYV93b3JkcyA8LSBzYXBwbHkoYmlibGlhX3dvcmRzLCJyZW1vdmVQdW5jdHVhdGlvbiIsVVNFLk5BTUVTID0gRkFMU0UpDQpiaWJsaWFfd29yZHMgPC0gc2FwcGx5KGJpYmxpYV93b3JkcywidG9sb3dlciIsVVNFLk5BTUVTID0gRkFMU0UpDQpiaWJsaWFfd29yZHMgPC0gc2FwcGx5KGJpYmxpYV93b3Jkcywic3RyaXBXaGl0ZXNwYWNlIixVU0UuTkFNRVMgPSBGQUxTRSkNCmJpYmxpYV93b3JkcyA8LSBzYXBwbHkoYmlibGlhX3dvcmRzLCJyZW1vdmVOdW1iZXJzIixVU0UuTkFNRVMgPSBGQUxTRSkNCmJpYmxpYV93b3JkcyA8LSBzYXBwbHkoYmlibGlhX3dvcmRzLCJyZW1vdmVXb3JkcyIsDQogICAgICAgICAgICAgICAgICAgICAgd29yZHM9c3RvcHdvcmRzKCdzcGFuaXNoJyksDQogICAgICAgICAgICAgICAgICAgICAgVVNFLk5BTUVTID0gRkFMU0UpDQpiaWJsaWFfd29yZHMgPC0gYmlibGlhX3dvcmRzW2JpYmxpYV93b3JkcyE9IiJdDQpiaWJsaWFfd29yZHMgPC0gYmlibGlhX3dvcmRzW2JpYmxpYV93b3JkcyE9IiAiXQ0KYGBgDQoNCiMjIyMgQWhvcmEgc2UgdmEgYSByZWR1Y2lyIGVsIHRhbWFuaW8gZGUgbG9zIGxpYnJvcywgZXN0byBjb24gZWwgcHJvcG9zaXRvIGRlIHRlbmVyIHVuYSBtYXRyaXogcXVlIHNlIHB1ZWRhIG9wZXJhciBzaW4gbmluZ3VuIHByb2JsZW1hIHV0aWxpemFuZG8gUi4gDQoNClJlZHVjaWVuZG8gZWwgdGFtYW5pbyBkZWwgbGlicm8gZGVsIFF1aWpvdGU6DQpgYGB7cn0NCnF1aWpvdGVfd29yZHMyIDwtIHF1aWpvdGVfd29yZHNbMToxMDAwXQ0KYGBgDQoNClJlZHVjaWVudG8gZGVsIHRhbWFuaW8gZGVsIGxpYnJvIGRlIGxhIEJpYmxpYToNCmBgYHtyfQ0KYmlibGlhX3dvcmRzMiA8LSBiaWJsaWFfd29yZHNbMToxMDAwXQ0KYGBgDQoNCkFob3JhIHVuaXJlbW9zIHRvZGFzIGxhcyBwYWxhYnJhcyBkZSBjYWRhIGxpYnJvIGVuIHVubyBzb2xvOg0KDQpgYGB7cn0NCmxpYnJvX2NvbXBsZXRvIDwtIGMocXVpam90ZV93b3JkczIsIGJpYmxpYV93b3JkczIpDQpoZWFkKGxpYnJvX2NvbXBsZXRvKQ0KYGBgDQoNClZhbW9zIGEgb2J0ZW5lciBsYSBjYW50aWRhZCBwYWxhYnJhcyB1bmljYXMgcXVlIGhheSBlbiBlbCB2ZWN0b3I6DQpgYGB7cn0NCmxlbmd0aCh1bmlxdWUobGlicm9fY29tcGxldG8pKQ0KYGBgDQoNCkxhIGZ1bmNpb24gKipuZ3JhbXMqKiBjcmVhIHVuIHZlY3RvciBkZSBjYXJhY3RlcmVzIGNvbiBwYWxhYnJhcyBlbiBzZWN1ZW5jaWE6DQpgYGB7cn0NCmJpZ3JhbXMgPC0NCiAgbGFwcGx5KG5ncmFtcyhsaWJyb19jb21wbGV0bywyKSwgcGFzdGUsIGNvbGxhcHNlPSIgIikgJT4lIHVubGlzdCgpDQpoZWFkKGJpZ3JhbXMpDQpgYGANCg0KQ29udmVydGlyZW1vcyBsYSB0YWJsYSBkZSBjaGFyYWN0ZXJzIGVuIHVuIGRhdGFmcmFtZToNCmBgYHtyfQ0KYmlncmFtcyA8LQ0KICB0YWJsZShiaWdyYW1zKSAlPiUgYXMuZGF0YS5mcmFtZSgpDQpoZWFkKGJpZ3JhbXMpDQpgYGANCg0KU2VwYXJhbmRvIGNhZGEgdmVjdG9yIGNvbiBkb3MgcGFsYWJyYXMgZGVudHJvIGRlbCBkYXRhZnJhbWUsIHNlIG5vbWJyYW4gcG9yICoqd29yZDEqKiB5ICoqd29yZDIqKjoNCmBgYHtyfQ0KYmlncmFtcyA8LSBiaWdyYW1zICU+JSANCiAgc2VwYXJhdGUoYmlncmFtcyxpbnRvPWMoIndvcmQxIiwid29yZDIiKSxzZXA9IiAiKQ0KYGBgDQoNCk1vc3RyYW5kbyB1bmEgbXVlc3RyYSBkZSBsb3MgYmlncmFtYXMsIGRvbmRlIGxhIEZyZWN1ZW5jaWEgc2VhIG1heW9yIGEgNS4NCmBgYHtyfQ0Kd29yZHNfc2FtcGxlIDwtIGJpZ3JhbXMgJT4lIGZpbHRlcihGcmVxID4gNSkNCndvcmRzX3NhbXBsZQ0KYGBgDQoNClNlIGNvbG9jYW4gY2FkYSB1bmEgZGUgbGFzIHdvcmQyIGVuIGxhcyBjb2x1bW5hcyB5IGVuIGxhcyBmaWxhcyB5IHNlIGN1ZW50YSBsYSBmcmVjdWVuY2lhOg0KYGBge3J9DQpjYWRlbmFfbWFya292PC0NCiAgYmlncmFtcyAlPiUgDQogIHNwcmVhZChrZXkgPSB3b3JkMix2YWx1ZSA9IEZyZXEsZmlsbCA9IDApDQpoZWFkKGNhZGVuYV9tYXJrb3YpDQpgYGANCg0KQ29udmlydGllbmRvIGxhIGNhZGVuYSBkZSBtYXJrb3YgYSB1bmEgbWF0cml6Og0KYGBge3J9DQptbV9jaGFpbjwtDQogIGFzLm1hdHJpeChjYWRlbmFfbWFya292WywtMV0pDQpgYGANCg0KQ2FtYmlhbmRvIGxhcyBmaWxhcyBwb3IgbGFzIHdvcmQxIGRlIGxhIGNhZGVuYSBkZSBtYXJrb3YsIGRpbWVuc2lvbiBkZSBsYSBjYWRlbmE6DQpgYGB7cn0NCnJvdy5uYW1lcyhtbV9jaGFpbikgPC0gY2FkZW5hX21hcmtvdiR3b3JkMQ0KZGltKG1tX2NoYWluKQ0KYGBgDQoNCk51bWVybyBkZSBmaWxhcyB5IGNvbHVtbmFzIHBhcmEgZGVmaW5pciBsYSBtYXRyaXo6DQpgYGB7cn0NCm1tX2NoYWluPC0NCiAgbW1fY2hhaW4vcm93U3VtcyhtbV9jaGFpbikNCg0KbnJvdyhtbV9jaGFpbikNCm5jb2wobW1fY2hhaW4pDQpgYGANCg0KQ3JlYW5kbyBsYSBtYXRyaXogY29uIGVsIG51bWVybyBkZSBmaWxhcywgbnVtZXJvIGRlIGNvbHVtbmFzIGRlIGxhIGNhZGVuYSBkZSBtYXJrb3YuIFkgc2UgZGl2aWRlIGVudHJlIGxhIHN1bWEgZGUgbGFzIGZyZWN1ZW5jaWFzLCBwYXJhIG9idGVuZXIgbGEgcHJvYmFiaWxpZGFkOg0KYGBge3J9DQpzPC0NCiAgbWF0cml4KDEsbmNvbD05OTUsbnJvdz05OTUpDQpzPC0NCiAgcy9yb3dTdW1zKHMpDQpgYGANCg0KRXZpdGFuZG8gY2x1c3RlcnMgbm8gdW5pZG9zOg0KYGBge3J9DQpNIDwtIDAuODUqbW1fY2hhaW4rMC4xNSpzDQpgYGANCg0KTWV0b2RvIHBhcmEgcHJvY2VzYXIgbGEgbWF0cml6IGVuIGRpc3RpbnRvcyBoaWxvcyBkZSBlamVjdWNpb24uDQpgYGB7cn0NCm1hdHByb2QucGFyIDwtIGZ1bmN0aW9uKGNsLCBBLCBCKXsNCiAgaWYgKG5jb2woQSkgIT0gbnJvdyhCKSkgc3RvcCgiTWF0cmljZXMgZG8gbm90IGNvbmZvcm1lIikNCiAgaWR4ICAgPC0gc3BsaXRJbmRpY2VzKG5yb3coQSksIGxlbmd0aChjbCkpDQogIEFsaXN0IDwtIGxhcHBseShpZHgsIGZ1bmN0aW9uKGlpKSBBW2lpLCxkcm9wPUZBTFNFXSkNCiAgIyMgYW5zICAgPC0gY2x1c3RlckFwcGx5KGNsLCBBbGlzdCwgZnVuY3Rpb24oYWEsIEIpIGFhICUqJSBCLCBCKQ0KICAjIyBTYW1lIGFzIGFib3ZlLCBidXQgZmFzdGVyOg0KICBhbnMgICA8LSBjbHVzdGVyQXBwbHkoY2wsIEFsaXN0LCBnZXQoIiUqJSIpLCBCKQ0KICBkby5jYWxsKHJiaW5kLCBhbnMpIH0NCihuYyA8LSBkZXRlY3RDb3JlcygpKQ0KY2wgPC0gbWFrZUNsdXN0ZXIocmVwKCJsb2NhbGhvc3QiLCBuYykpDQpgYGANCg0KTXVsdGlwbGljYW5kbyBsYSBtYXRyaXogcG9yIGVsbGEgbWlzbWE6DQpgYGB7cn0NCk0xIDwtDQogIG1hdHByb2QucGFyKGNsLE0sTSkNCk0gPC0gTSAlKiUgTQ0Kc3RvcENsdXN0ZXIoY2wpDQpgYGANCg0KVmlzdGEgZGUgbGEgTWF0cml6IGx1ZWdvIGRlIGxhIG11bHRpcGxpY2FjaW9uOg0KIVtdKG1hdHJpekZvdG8uanBnKQ0KDQpFc3RhYmlsaWRhZCBkZSBsYSBNYXRyaXogbXVsdGlwbGljYW5kb2xhIHZhcmlhcyB2ZWNlcyBwb3IgZWxsYSBtaXNtYToNCg0KYGBge3J9DQpNXzIgPC0gTQ0KZm9yKGkgaW4gMToxMCl7DQogIGMyIDwtIG1ha2VDbHVzdGVyKHJlcCgibG9jYWxob3N0IiwgbmMpKQ0KTTIgPC0NCiAgbWF0cHJvZC5wYXIoYzIsTSxNKQ0KTV8yIDwtIE1fMiAlKiUgTQ0Kc3RvcENsdXN0ZXIoYzIpDQp9DQpgYGANCg0KTXVsdGlwbGljYW5kbyBsYSBtYXRyaXogMTAgdmVjZXMgcG9yIGVsbGEgbWlzbWE6DQohW10obTEwLmpwZykNCg0KTXVsdGlwbGljYW5kbyBsYSBtYXRyaXogMjAgdmVjZXMgcG9yIGVsbGEgbWlzbWE6DQpgYGB7cn0NCk1fMiA8LSBNDQpmb3IoaSBpbiAxOjIwKXsNCiAgYzIgPC0gbWFrZUNsdXN0ZXIocmVwKCJsb2NhbGhvc3QiLCBuYykpDQpNMiA8LQ0KICBtYXRwcm9kLnBhcihjMixNLE0pDQpNXzIgPC0gTV8yICUqJSBNDQpzdG9wQ2x1c3RlcihjMikNCn0NCmBgYA0KIVtdKG0yMC5qcGcpDQoNCk11bHRpcGxpY2FuZG8gbGEgbWF0cml6IDI1IHZlY2VzIHBvciBlbGxhIG1pc21hOg0KYGBge3J9DQpNXzIgPC0gTQ0KZm9yKGkgaW4gMToyNSl7DQogIGMyIDwtIG1ha2VDbHVzdGVyKHJlcCgibG9jYWxob3N0IiwgbmMpKQ0KTTIgPC0NCiAgbWF0cHJvZC5wYXIoYzIsTSxNKQ0KTV8yIDwtIE1fMiAlKiUgTQ0Kc3RvcENsdXN0ZXIoYzIpDQp9DQpgYGANCiFbXShtMjUuanBnKQ0KDQojIyMgQ29tbyBzZSBwdWVkZSBvYnNlcnZhciBsYSBtYXRyaXogeWEgbGxlZ28gYSBsYSBlc3RhYmlsaWRhZCwgcG9ycXVlIGxhcyBwcm9iYWJpbGlkYWRlcyBkZW50cm8gZGUgbGEgbWF0cml6IHlhIG5vIGVzdGFuIGNhbWJpYW5kby4NCg0K