UNIVERSITA’ DEGLI STUDI DI TORINO
Laurea Magistarle in Comunicazione pubblica e Politica
Corso di studio: Social Media Analysis e Big Data
Prof. Giuseppe Tipaldo

Introduzione

L’analisi quantifica e rende visibili le parole e le associazioni di parole più utilizzate dagli utenti del social network Twitter durante i giorni con al centro la valanga che ha colpito l’hotel Rigopiano in Abruzzo il 18 gennaio 2017. Il dataset utilizzato è fornito da CELI s.r.l.


PERIODO DI RIFERIMENTO: dal 16/01/2017 al 22/01/2017
TWEETS ANALIZZATI: 162.389
CAMPIONE ESTRATTO: 1000
STRUMENTI UTILIZZATI: RStudio (library: lubridate, wordcloud, reshape2, stringi, ggplot2, scales, RWeka, tm), R Markdown (html_notebook, Knit to HTML, Knit to PDF, Knit to WORD)

Caricamento dei dati e rappresentazione

## Attivo le librerie di funzioni
library(lubridate)
library(wordcloud)
library(reshape2)
library(stringi)
library(ggplot2)
library(scales)
library(RWeka)
library(tm)
## Carico i dati
snowtest <- read.csv("snowtest.csv", sep = ";")
## Ottimizzo il timestamp
snowtest$created <- dmy_hm(snowtest$datetime)
## Ottimizzo la TIME ZONE
snowtest$created <- with_tz(snowtest$created, "Europe/Rome")
## Distribuzione dei tweets per GIORNO DELLA SETTIMANA
ggplot(data = snowtest, aes(x = wday(datetime, label = TRUE))) +
  geom_bar(aes(fill = ..count..)) +
  theme(legend.position = "none") +
  xlab("Giorno della settimana") + ylab("Numero di tweets") + 
  scale_fill_gradient(low = "midnightblue", high = "aquamarine4")

## Quantità di tweets per ORARIO
# Estraggo i giorni
snowtest$timeonly <- as.numeric(snowtest$created - trunc(snowtest$created, "days"))
class(snowtest$timeonly) <- "POSIXct"
# Distribuzione dei tweets per ORARIO
ggplot(data = snowtest, aes(x = timeonly)) +
  geom_histogram(aes(fill = ..count..)) +
  theme(legend.position = "none") +
  xlab("Orario") + ylab("Numero di tweets") + 
  scale_x_datetime(breaks = date_breaks("2 hours"), 
                   labels = date_format("%H:00")) +
  scale_fill_gradient(low = "midnightblue", high = "aquamarine4")

## Somma dei caratteri dei TWEETS
snowtest$cl_text <- as.character(snowtest$text)
snowtest$lenght <- sapply(snowtest$cl_text, function(x) nchar(x))
# Distribuzione dei tweets per numero di caratteri
ggplot(data = snowtest, aes(x = lenght)) +
  geom_histogram(aes(fill = ..count..), binwidth = 16) +
  theme(legend.position = "none") +
  xlab("Caratteri per Tweet") + ylab("Numero di tweets") + 
  scale_fill_gradient(low = "midnightblue", high = "aquamarine4")

## Estraggo un campione
data <- snowtest[sample(1:nrow(snowtest), 1000, replace=FALSE),]
## Creo le funzioni per pulire il testo
removeURL <- function(x) gsub("http:[[:alnum:]]*", "", x)
removeURL <- function(x) gsub("https:[[:alnum:]]*", "", x)
removeHashTags <- function(x) gsub("#\\S+", "", x)
removeTwitterHandles <- function(x) gsub("@\\S+", "", x)
## Creo il Corpus da normalizzare con TM Package
corpusnow <- Corpus(VectorSource(data$text)) 
corpusnow <- tm_map(corpusnow, content_transformer(function(x) iconv(x, to='UTF-8', sub='byte')), mc.cores=1)
corpusnow <- tm_map(corpusnow, content_transformer(tolower))
corpusnow <- tm_map(corpusnow, removeNumbers) 
corpusnow <- tm_map(corpusnow, removePunctuation)
corpusnow <- tm_map(corpusnow, removeURL)
corpusnow <- tm_map(corpusnow, removeTwitterHandles)
corpusnow <- tm_map(corpusnow, removeHashTags)
corpusnow <- tm_map(corpusnow, removeWords, stopwords("italian"))
corpusnow <- tm_map(corpusnow, stripWhitespace)
corpusnow <- tm_map(corpusnow, PlainTextDocument)
##  Creo la matrice dei dati
dtm <-DocumentTermMatrix(corpusnow, control=list(wordLengths=c(4, 20)))
## Verifico la "sparsità" della matrice
dtm
<<DocumentTermMatrix (documents: 1000, terms: 4191)>>
Non-/sparse entries: 10505/4180495
Sparsity           : 100%
Maximal term length: 20
Weighting          : term frequency (tf)
## Ottimizzo la matrice con al massimo il 10% di spazio vuoto.
# dtm <- removeSparseTerms(dtm, 0.1) 
## Sommo le frequenze
freq <- colSums(as.matrix(dtm))
## La lunghezza dovrebbe coincidere con il numero totale delle parole
  length(freq)
[1] 4191
## Ordino le parole in base alla frequenza (asc)
ord <- order(freq, decreasing=TRUE)
## Mostro le parole più frequenti
freq[head(ord)]
     neve terremoto rigopiano   valanga     hotel   slavina 
      684       236       200       139       103        97 
## Mostro le parole meno frequenti
freq[tail(ord)]
        wooooo          xxiii    youanimalit zaccheddufranc         zinnen          zitti 
             1              1              1              1              1              1 
## Mostro le parole che ricorrono almeno 30 volte
findFreqTerms(dtm,lowfreq=30)
 [1] "abruzzo"        "ancora"         "centro"         "dispersi"       "dopo"          
 [6] "emergenza"      "gelo"           "hotel"          "italia"         "maltempo"      
[11] "neve"           "persone"        "poliziadistato" "repubblicait"   "rigopiano"     
[16] "scosse"         "senza"          "slavina"        "soccorritori"   "soccorsi"      
[21] "sotto"          "terremoto"      "travolto"       "valanga"        "video"         
## Mostro le parole maggiormente correlate alla parola "neve"
findAssocs(dtm, "neve", 0.1)
$neve
      emergenza           farti httpstcoxlvruqd        iperbole      rendendosi         segui†
           0.12            0.12            0.12            0.12            0.12            0.12 
         selfie       spalavano           utili matteosalvinimi           molta       terremoto 
           0.12            0.12            0.12            0.11            0.11            0.11 
         disagi          strade 
           0.10            0.10 
## Mostro le parole maggiormente correlate alla parola "terremoto"
findAssocs(dtm, "terremoto", 0.1)
$terremoto
     tranquillitã         ginocchio            parole        campotosto             forti 
             0.22              0.18              0.18              0.17              0.17 
        alluvione            italia            scosse           abruzzo           laquila 
             0.15              0.15              0.15              0.14              0.14 
           centro             corpo           esperti             messa           passare 
             0.13              0.13              0.13              0.13              0.13 
            paura           assenza           bastava           causata          chiudere 
             0.13              0.12              0.12              0.12              0.12 
        dellaltra   dottorgiustizia httpstcoaurqickez   httpstcocbaezhd httpstcohxveyebwd 
             0.12              0.12              0.12              0.12              0.12 
          infatti           ipotesi           mancava           origine           salvano 
             0.12              0.12              0.12              0.12              0.12 
    saramenichini            sbando             aiuta           aiutano          cuccioli 
             0.12              0.12              0.11              0.11              0.11 
         disperso         enpaonlus         gentiloni              neve             aiuti 
             0.11              0.11              0.11              0.11              0.10 
## Mostro le parole maggiormente correlate alla parola "rigopiano"
findAssocs(dtm, "rigopiano", 0.2)
$rigopiano
       hotel      slavina      valanga       lhotel     travolto    dellhotel    farindola 
        0.41         0.31         0.30         0.27         0.25         0.22         0.22 
    immagini repubblicait     sommerso         vive 
        0.22         0.21         0.21         0.21 

N-Gram analysis and wordcloud

Uni-gram Frequenza

## creo la funzione di singola tokenizzazione
OnegramTokenizer <- function(x) NGramTokenizer(x,Weka_control(min = 1, max =1))
## genero la matrice con le frequenze
dtm <- DocumentTermMatrix(corpusnow, control = list(tokenize = OnegramTokenizer))
## Ordino le frequenze in maniera decrescente
freq <- sort(colSums(as.matrix(dtm)), decreasing=TRUE)
## le inserisco in un dataframe
wf <- data.frame(word=names(freq), freq=freq)
## visualizzo le parole che ricorrono almeno 60 volte
p <- ggplot(subset(wf, freq > 50), aes(word, freq))
p <- p + geom_bar(stat="identity", fill="darkred", colour="blue")
p + theme(axis.text.x=element_text(angle=45, hjust=1)) + ggtitle("Uni-Gram Frequenza") + xlab("Parole") + ylab("Frequenza") 

## Creo una tabella e la ordino in maniera decrescente
tm_unifreq <- sort(colSums(as.matrix(dtm)), decreasing=TRUE)
## trasformo la tabella in dataframe
tm_uniwordfreq <- data.frame(word=names(tm_unifreq), freq=tm_unifreq)
## mostro le 10 parole più ricorrenti
head(tm_uniwordfreq,10)
## visualizzo le parole che ricorrono almeno 14 volte
wordcloud(names(tm_unifreq), tm_unifreq, min.freq=14, max.words=50, scale=c(5, .8), colors=brewer.pal(6, "Dark2"))

Bi-Gram Frequenza

## Stessa procedura della uni-gram ma con la tokenizzazione su due parole
BigramTokenizer <- function(x) NGramTokenizer(x,Weka_control(min = 2, max = 2))
dtm2 <- DocumentTermMatrix(corpusnow, control = list(tokenize = BigramTokenizer))
freq2 <- sort(colSums(as.matrix(dtm2)), decreasing=TRUE)
wf2 <- data.frame(word=names(freq2), freq=freq2)
p2 <- ggplot(subset(wf2, freq > 14), aes(x = word, y = freq))
p2 <- p2 + geom_bar(stat="identity", fill="darkgreen", colour="blue")
p2 + theme(axis.text.x=element_text(angle=45, hjust=1)) + ggtitle("Bi-Gram Frequenza") + xlab("sentenze") + ylab("Frequenza") 

tm_bifreq <- sort(colSums(as.matrix(dtm2)), decreasing=TRUE)
tm_biwordfreq <- data.frame(word=names(tm_bifreq), freq=tm_bifreq)
head(tm_biwordfreq,10)
wordcloud(names(tm_bifreq), tm_bifreq, min.freq=14, max.words=100, scale=c(3, .1), colors=brewer.pal(6, "Dark2"))

Tri-Gram Frequenza

## Stessa procedura della uni-gram ma con la tokenizzazione su tre parole
TrigramTokenizer <- function(x) NGramTokenizer(x,Weka_control(min = 3, max = 3))
dtm3 <- DocumentTermMatrix(corpusnow, control = list(tokenize = TrigramTokenizer))
freq3 <- sort(colSums(as.matrix(dtm3)), decreasing=TRUE)
wf3 <- data.frame(word=names(freq3), freq=freq3)
p3 <- ggplot(subset(wf3, freq > 7), aes(x = word, y = freq))
p3 <- p3 + geom_bar(stat="identity", fill="darkred", colour="green")
p3 + theme(axis.text.x=element_text(angle=45, hjust=1)) + ggtitle("Tri-Gram Frequenza") + xlab("Sentenze") + ylab("Frequenza")

tm_trifreq <- sort(colSums(as.matrix(dtm3)), decreasing=TRUE)
tm_triwordfreq <- data.frame(word=names(tm_trifreq), freq=tm_trifreq)
head(tm_triwordfreq,10)
wordcloud(names(tm_trifreq), tm_trifreq, max.words=16, scale=c(1, 0.8), colors=brewer.pal(6, "Dark2"))

NA

Quadri-Gram Frequenza

## Stessa procedura della uni-gram ma con la tokenizzazione su quattro parole
QuadrigramTokenizer <- function(x) NGramTokenizer(x,Weka_control(min = 4, max = 4))
dtm4 <- DocumentTermMatrix(corpusnow, control = list(tokenize = QuadrigramTokenizer))
freq4 <- sort(colSums(as.matrix(dtm4)), decreasing=TRUE)
wf4 <- data.frame(word=names(freq4), freq=freq4)
p4 <- ggplot(subset(wf4, freq > 6), aes(x = word, y = freq))
p4 <- p4 + geom_bar(stat="identity", fill="darkred", colour="green")
p4 + theme(axis.text.x=element_text(angle=45, hjust=1)) + ggtitle("Quadri-Gram Frequenza") + xlab("sentenze") + ylab("Frequenza")

tm_quadrifreq <- sort(colSums(as.matrix(dtm4)), decreasing=TRUE)
tm_quadriwordfreq <- data.frame(word=names(tm_quadrifreq), freq=tm_quadrifreq)
head(tm_quadriwordfreq,5)
wordcloud(names(tm_quadrifreq), tm_quadrifreq, max.words=20, scale=c(1, 0.3), colors=brewer.pal(6, "Dark2"))

Five-Gram Frequenza

## Stessa procedura della uni-gram ma con la tokenizzazione su cinque parole
FivegramTokenizer <- function(x) NGramTokenizer(x,Weka_control(min = 5, max = 5))
dtm5 <- DocumentTermMatrix(corpusnow, control = list(tokenize = FivegramTokenizer))
freq5 <- sort(colSums(as.matrix(dtm5)), decreasing=TRUE)
wf5 <- data.frame(word=names(freq5), freq=freq5)
p5 <- ggplot(subset(wf5, freq > 6), aes(x = word, y = freq))
p5 <- p5 + geom_bar(stat="identity", fill="darkred", colour="green")
p5 + theme(axis.text.x=element_text(angle=45, hjust=1)) + ggtitle("Five-Gram Frequenza") + xlab("Sentenze") + ylab("Frequenza")

tm_fivefreq <- sort(colSums(as.matrix(dtm5)), decreasing=TRUE)
tm_fivewordfreq <- data.frame(word=names(tm_fivefreq), freq=tm_fivefreq)
head(tm_fivewordfreq,5)
wordcloud(names(tm_fivefreq), tm_fivefreq, max.words=20, scale=c(0.8, 0.9), colors=brewer.pal(6, "Dark2"))

LS0tDQp0aXRsZTogVGV4dCBtaW5pbmcgZSBuLUdyYW0gYW5hbGlzaSBkZWwgdGVzdG8gZGVpIHR3ZWV0cyBlc3RyYXR0aSBuZWwgcGVyaW9kbyBpbW1lZGlhdGFtZW50ZSBwcmVjZWRlbnRlIGUgc3VjY2Vzc2l2byBhbGxhIHZhbGFuZ2EgY2hlIGhhIGNvbHBpdG8gbCdob3RlbCBSaWdvcGlhbm8gaW4gQWJydXp6byBpbCAxOCBHZW5uYWlvIDIwMTcNCmF1dGhvcjogIkVuem8gQWxiYW5lc2UiDQpkYXRlOiAiMzAvMDYvMjAxNyINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazogZGVmYXVsdA0KICBodG1sX2RvY3VtZW50OiBkZWZhdWx0DQogIHBkZl9kb2N1bWVudDogZGVmYXVsdA0KICB3b3JkX2RvY3VtZW50OiBkZWZhdWx0DQotLS0NClVOSVZFUlNJVEEnIERFR0xJIFNUVURJIERJIFRPUklOTw0KPGJyPkxhdXJlYSBNYWdpc3RhcmxlIGluIENvbXVuaWNhemlvbmUgcHViYmxpY2EgZSBQb2xpdGljYQ0KPGJyPkNvcnNvIGRpIHN0dWRpbzogU29jaWFsIE1lZGlhIEFuYWx5c2lzIGUgQmlnIERhdGENCjxicj5Qcm9mLiBHaXVzZXBwZSBUaXBhbGRvDQoNCg0KDQo8aDM+SW50cm9kdXppb25lPC9oMz4NCg0KPGk+TCdhbmFsaXNpIHF1YW50aWZpY2EgZSByZW5kZSB2aXNpYmlsaSBsZSBwYXJvbGUgZSBsZSBhc3NvY2lhemlvbmkgZGkgcGFyb2xlIHBp+SB1dGlsaXp6YXRlIGRhZ2xpIHV0ZW50aSBkZWwgc29jaWFsIG5ldHdvcmsgVHdpdHRlciBkdXJhbnRlIGkgZ2lvcm5pIGNvbiBhbCBjZW50cm8gbGEgdmFsYW5nYSBjaGUgaGEgY29scGl0byBsJ2hvdGVsIFJpZ29waWFubyBpbiBBYnJ1enpvIGlsIDE4IGdlbm5haW8gMjAxNy4gSWwgZGF0YXNldCB1dGlsaXp6YXRvIOggZm9ybml0byBkYSBDRUxJIHMuci5sLjwvaT4NCg0KPGJyPlBFUklPRE8gREkgUklGRVJJTUVOVE86IDxiPmRhbCAxNi8wMS8yMDE3IGFsIDIyLzAxLzIwMTc8L2I+DQo8YnI+VFdFRVRTIEFOQUxJWlpBVEk6IDxiPjE2Mi4zODk8L2I+DQo8YnI+Q0FNUElPTkUgRVNUUkFUVE86IDxiPjEwMDA8L2I+DQo8YnI+U1RSVU1FTlRJIFVUSUxJWlpBVEk6IDxiPlJTdHVkaW88L2I+IChsaWJyYXJ5OiBsdWJyaWRhdGUsIHdvcmRjbG91ZCwgcmVzaGFwZTIsIHN0cmluZ2ksIGdncGxvdDIsIHNjYWxlcywgUldla2EsIHRtKSwgPGI+UiBNYXJrZG93bjwvYj4gKGh0bWxfbm90ZWJvb2ssIEtuaXQgdG8gSFRNTCwgS25pdCB0byBQREYsIEtuaXQgdG8gV09SRCkNCg0KPGgzPkNhcmljYW1lbnRvIGRlaSBkYXRpIGUgcmFwcHJlc2VudGF6aW9uZTwvaDM+DQoNCmBgYHtyfQ0KIyMgQXR0aXZvIGxlIGxpYnJlcmllIGRpIGZ1bnppb25pDQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCmxpYnJhcnkod29yZGNsb3VkKQ0KbGlicmFyeShyZXNoYXBlMikNCmxpYnJhcnkoc3RyaW5naSkNCmxpYnJhcnkoZ2dwbG90MikNCmxpYnJhcnkoc2NhbGVzKQ0KbGlicmFyeShSV2VrYSkNCmxpYnJhcnkodG0pDQpgYGANCmBgYHtyfQ0KIyMgQ2FyaWNvIGkgZGF0aQ0Kc25vd3Rlc3QgPC0gcmVhZC5jc3YoInNub3d0ZXN0LmNzdiIsIHNlcCA9ICI7IikNCmBgYA0KYGBge3J9DQojIyBPdHRpbWl6em8gaWwgdGltZXN0YW1wDQpzbm93dGVzdCRjcmVhdGVkIDwtIGRteV9obShzbm93dGVzdCRkYXRldGltZSkNCmBgYA0KYGBge3J9DQojIyBPdHRpbWl6em8gbGEgVElNRSBaT05FDQpzbm93dGVzdCRjcmVhdGVkIDwtIHdpdGhfdHooc25vd3Rlc3QkY3JlYXRlZCwgIkV1cm9wZS9Sb21lIikNCmBgYA0KYGBge3J9DQojIyBEaXN0cmlidXppb25lIGRlaSB0d2VldHMgcGVyIEdJT1JOTyBERUxMQSBTRVRUSU1BTkENCmdncGxvdChkYXRhID0gc25vd3Rlc3QsIGFlcyh4ID0gd2RheShkYXRldGltZSwgbGFiZWwgPSBUUlVFKSkpICsNCiAgZ2VvbV9iYXIoYWVzKGZpbGwgPSAuLmNvdW50Li4pKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKw0KICB4bGFiKCJHaW9ybm8gZGVsbGEgc2V0dGltYW5hIikgKyB5bGFiKCJOdW1lcm8gZGkgdHdlZXRzIikgKyANCiAgc2NhbGVfZmlsbF9ncmFkaWVudChsb3cgPSAibWlkbmlnaHRibHVlIiwgaGlnaCA9ICJhcXVhbWFyaW5lNCIpDQpgYGANCmBgYHtyfQ0KIyMgUXVhbnRpdOAgZGkgdHdlZXRzIHBlciBPUkFSSU8NCiMgRXN0cmFnZ28gaSBnaW9ybmkNCnNub3d0ZXN0JHRpbWVvbmx5IDwtIGFzLm51bWVyaWMoc25vd3Rlc3QkY3JlYXRlZCAtIHRydW5jKHNub3d0ZXN0JGNyZWF0ZWQsICJkYXlzIikpDQpjbGFzcyhzbm93dGVzdCR0aW1lb25seSkgPC0gIlBPU0lYY3QiDQpgYGANCmBgYHtyfQ0KIyBEaXN0cmlidXppb25lIGRlaSB0d2VldHMgcGVyIE9SQVJJTw0KZ2dwbG90KGRhdGEgPSBzbm93dGVzdCwgYWVzKHggPSB0aW1lb25seSkpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYWVzKGZpbGwgPSAuLmNvdW50Li4pKSArDQogIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJub25lIikgKw0KICB4bGFiKCJPcmFyaW8iKSArIHlsYWIoIk51bWVybyBkaSB0d2VldHMiKSArIA0KICBzY2FsZV94X2RhdGV0aW1lKGJyZWFrcyA9IGRhdGVfYnJlYWtzKCIyIGhvdXJzIiksIA0KICAgICAgICAgICAgICAgICAgIGxhYmVscyA9IGRhdGVfZm9ybWF0KCIlSDowMCIpKSArDQogIHNjYWxlX2ZpbGxfZ3JhZGllbnQobG93ID0gIm1pZG5pZ2h0Ymx1ZSIsIGhpZ2ggPSAiYXF1YW1hcmluZTQiKQ0KYGBgDQpgYGB7cn0NCiMjIFNvbW1hIGRlaSBjYXJhdHRlcmkgZGVpIFRXRUVUUw0Kc25vd3Rlc3QkY2xfdGV4dCA8LSBhcy5jaGFyYWN0ZXIoc25vd3Rlc3QkdGV4dCkNCnNub3d0ZXN0JGxlbmdodCA8LSBzYXBwbHkoc25vd3Rlc3QkY2xfdGV4dCwgZnVuY3Rpb24oeCkgbmNoYXIoeCkpDQpgYGANCmBgYHtyfQ0KIyBEaXN0cmlidXppb25lIGRlaSB0d2VldHMgcGVyIG51bWVybyBkaSBjYXJhdHRlcmkNCmdncGxvdChkYXRhID0gc25vd3Rlc3QsIGFlcyh4ID0gbGVuZ2h0KSkgKw0KICBnZW9tX2hpc3RvZ3JhbShhZXMoZmlsbCA9IC4uY291bnQuLiksIGJpbndpZHRoID0gMTYpICsNCiAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArDQogIHhsYWIoIkNhcmF0dGVyaSBwZXIgVHdlZXQiKSArIHlsYWIoIk51bWVybyBkaSB0d2VldHMiKSArIA0KICBzY2FsZV9maWxsX2dyYWRpZW50KGxvdyA9ICJtaWRuaWdodGJsdWUiLCBoaWdoID0gImFxdWFtYXJpbmU0IikNCmBgYA0KYGBge3J9DQojIyBFc3RyYWdnbyB1biBjYW1waW9uZQ0KZGF0YSA8LSBzbm93dGVzdFtzYW1wbGUoMTpucm93KHNub3d0ZXN0KSwgMTAwMCwgcmVwbGFjZT1GQUxTRSksXQ0KYGBgDQpgYGB7cn0NCiMjIENyZW8gbGUgZnVuemlvbmkgcGVyIHB1bGlyZSBpbCB0ZXN0bw0KcmVtb3ZlVVJMIDwtIGZ1bmN0aW9uKHgpIGdzdWIoImh0dHA6W1s6YWxudW06XV0qIiwgIiIsIHgpDQpyZW1vdmVVUkxTIDwtIGZ1bmN0aW9uKHgpIGdzdWIoImh0dHBzOltbOmFsbnVtOl1dKiIsICIiLCB4KQ0KcmVtb3ZlSGFzaFRhZ3MgPC0gZnVuY3Rpb24oeCkgZ3N1YigiI1xcUysiLCAiIiwgeCkNCnJlbW92ZVR3aXR0ZXJIYW5kbGVzIDwtIGZ1bmN0aW9uKHgpIGdzdWIoIkBcXFMrIiwgIiIsIHgpDQpgYGANCmBgYHtyfQ0KIyMgQ3JlbyBpbCBDb3JwdXMgZGEgbm9ybWFsaXp6YXJlIGNvbiBUTSBQYWNrYWdlDQpjb3JwdXNub3cgPC0gQ29ycHVzKFZlY3RvclNvdXJjZShkYXRhJHRleHQpKSANCmNvcnB1c25vdyA8LSB0bV9tYXAoY29ycHVzbm93LCBjb250ZW50X3RyYW5zZm9ybWVyKGZ1bmN0aW9uKHgpIGljb252KHgsIHRvPSdVVEYtOCcsIHN1Yj0nYnl0ZScpKSwgbWMuY29yZXM9MSkNCmNvcnB1c25vdyA8LSB0bV9tYXAoY29ycHVzbm93LCBjb250ZW50X3RyYW5zZm9ybWVyKHRvbG93ZXIpKQ0KY29ycHVzbm93IDwtIHRtX21hcChjb3JwdXNub3csIHJlbW92ZU51bWJlcnMpIA0KY29ycHVzbm93IDwtIHRtX21hcChjb3JwdXNub3csIHJlbW92ZVB1bmN0dWF0aW9uKQ0KY29ycHVzbm93IDwtIHRtX21hcChjb3JwdXNub3csIHJlbW92ZVVSTCkNCmNvcnB1c25vdyA8LSB0bV9tYXAoY29ycHVzbm93LCByZW1vdmVUd2l0dGVySGFuZGxlcykNCmNvcnB1c25vdyA8LSB0bV9tYXAoY29ycHVzbm93LCByZW1vdmVIYXNoVGFncykNCmNvcnB1c25vdyA8LSB0bV9tYXAoY29ycHVzbm93LCByZW1vdmVXb3Jkcywgc3RvcHdvcmRzKCJpdGFsaWFuIikpDQpjb3JwdXNub3cgPC0gdG1fbWFwKGNvcnB1c25vdywgc3RyaXBXaGl0ZXNwYWNlKQ0KY29ycHVzbm93IDwtIHRtX21hcChjb3JwdXNub3csIFBsYWluVGV4dERvY3VtZW50KQ0KYGBgDQpgYGB7cn0NCiMjICBDcmVvIGxhIG1hdHJpY2UgZGVpIGRhdGkNCmR0bSA8LURvY3VtZW50VGVybU1hdHJpeChjb3JwdXNub3csIGNvbnRyb2w9bGlzdCh3b3JkTGVuZ3Rocz1jKDQsIDIwKSkpDQpgYGANCmBgYHtyfQ0KIyMgVmVyaWZpY28gbGEgInNwYXJzaXTgIiBkZWxsYSBtYXRyaWNlDQpkdG0NCmBgYA0KYGBge3J9DQojIyBPdHRpbWl6em8gbGEgbWF0cmljZSBjb24gYWwgbWFzc2ltbyBpbCAxMCUgZGkgc3BhemlvIHZ1b3RvLg0KIyBkdG0gPC0gcmVtb3ZlU3BhcnNlVGVybXMoZHRtLCAwLjEpIA0KYGBgDQpgYGB7cn0NCiMjIFNvbW1vIGxlIGZyZXF1ZW56ZQ0KZnJlcSA8LSBjb2xTdW1zKGFzLm1hdHJpeChkdG0pKQ0KYGBgDQpgYGB7cn0NCiMjIExhIGx1bmdoZXp6YSBkb3ZyZWJiZSBjb2luY2lkZXJlIGNvbiBpbCBudW1lcm8gdG90YWxlIGRlbGxlIHBhcm9sZQ0KICBsZW5ndGgoZnJlcSkNCmBgYA0KYGBge3J9DQojIyBPcmRpbm8gbGUgcGFyb2xlIGluIGJhc2UgYWxsYSBmcmVxdWVuemEgKGFzYykNCm9yZCA8LSBvcmRlcihmcmVxLCBkZWNyZWFzaW5nPVRSVUUpDQpgYGANCmBgYHtyfQ0KIyMgTW9zdHJvIGxlIHBhcm9sZSBwafkgZnJlcXVlbnRpDQpmcmVxW2hlYWQob3JkKV0NCmBgYA0KYGBge3J9DQojIyBNb3N0cm8gbGUgcGFyb2xlIG1lbm8gZnJlcXVlbnRpDQpmcmVxW3RhaWwob3JkKV0NCmBgYA0KYGBge3J9DQojIyBNb3N0cm8gbGUgcGFyb2xlIGNoZSByaWNvcnJvbm8gYWxtZW5vIDMwIHZvbHRlDQpmaW5kRnJlcVRlcm1zKGR0bSxsb3dmcmVxPTMwKQ0KYGBgDQpgYGB7cn0NCiMjIE1vc3RybyBsZSBwYXJvbGUgbWFnZ2lvcm1lbnRlIGNvcnJlbGF0ZSBhbGxhIHBhcm9sYSAibmV2ZSINCmZpbmRBc3NvY3MoZHRtLCAibmV2ZSIsIDAuMSkNCmBgYA0KYGBge3J9DQojIyBNb3N0cm8gbGUgcGFyb2xlIG1hZ2dpb3JtZW50ZSBjb3JyZWxhdGUgYWxsYSBwYXJvbGEgInRlcnJlbW90byINCmZpbmRBc3NvY3MoZHRtLCAidGVycmVtb3RvIiwgMC4xKQ0KYGBgDQpgYGB7cn0NCiMjIE1vc3RybyBsZSBwYXJvbGUgbWFnZ2lvcm1lbnRlIGNvcnJlbGF0ZSBhbGxhIHBhcm9sYSAicmlnb3BpYW5vIg0KZmluZEFzc29jcyhkdG0sICJyaWdvcGlhbm8iLCAwLjIpDQpgYGANCjxoMz5OLUdyYW0gYW5hbHlzaXMgYW5kIHdvcmRjbG91ZDwvaDM+DQoNCjxoND5VbmktZ3JhbSBGcmVxdWVuemE8L2g0Pg0KDQpgYGB7cn0NCiMjIGNyZW8gbGEgZnVuemlvbmUgZGkgc2luZ29sYSB0b2tlbml6emF6aW9uZQ0KT25lZ3JhbVRva2VuaXplciA8LSBmdW5jdGlvbih4KSBOR3JhbVRva2VuaXplcih4LFdla2FfY29udHJvbChtaW4gPSAxLCBtYXggPTEpKQ0KYGBgDQpgYGB7cn0NCiMjIGdlbmVybyBsYSBtYXRyaWNlIGNvbiBsZSBmcmVxdWVuemUNCmR0bSA8LSBEb2N1bWVudFRlcm1NYXRyaXgoY29ycHVzbm93LCBjb250cm9sID0gbGlzdCh0b2tlbml6ZSA9IE9uZWdyYW1Ub2tlbml6ZXIpKQ0KYGBgDQpgYGB7cn0NCiMjIE9yZGlubyBsZSBmcmVxdWVuemUgaW4gbWFuaWVyYSBkZWNyZXNjZW50ZQ0KZnJlcSA8LSBzb3J0KGNvbFN1bXMoYXMubWF0cml4KGR0bSkpLCBkZWNyZWFzaW5nPVRSVUUpDQpgYGANCmBgYHtyfQ0KIyMgbGUgaW5zZXJpc2NvIGluIHVuIGRhdGFmcmFtZQ0Kd2YgPC0gZGF0YS5mcmFtZSh3b3JkPW5hbWVzKGZyZXEpLCBmcmVxPWZyZXEpDQpgYGANCmBgYHtyfQ0KIyMgdmlzdWFsaXp6byBsZSBwYXJvbGUgY2hlIHJpY29ycm9ubyBhbG1lbm8gNjAgdm9sdGUNCnAgPC0gZ2dwbG90KHN1YnNldCh3ZiwgZnJlcSA+IDUwKSwgYWVzKHdvcmQsIGZyZXEpKQ0KcCA8LSBwICsgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiLCBmaWxsPSJkYXJrcmVkIiwgY29sb3VyPSJibHVlIikNCnAgKyB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X3RleHQoYW5nbGU9NDUsIGhqdXN0PTEpKSArIGdndGl0bGUoIlVuaS1HcmFtIEZyZXF1ZW56YSIpICsgeGxhYigiUGFyb2xlIikgKyB5bGFiKCJGcmVxdWVuemEiKSANCmBgYA0KYGBge3J9DQojIyBDcmVvIHVuYSB0YWJlbGxhIGUgbGEgb3JkaW5vIGluIG1hbmllcmEgZGVjcmVzY2VudGUNCnRtX3VuaWZyZXEgPC0gc29ydChjb2xTdW1zKGFzLm1hdHJpeChkdG0pKSwgZGVjcmVhc2luZz1UUlVFKQ0KYGBgDQpgYGB7cn0NCiMjIHRyYXNmb3JtbyBsYSB0YWJlbGxhIGluIGRhdGFmcmFtZQ0KdG1fdW5pd29yZGZyZXEgPC0gZGF0YS5mcmFtZSh3b3JkPW5hbWVzKHRtX3VuaWZyZXEpLCBmcmVxPXRtX3VuaWZyZXEpDQpgYGANCmBgYHtyfQ0KIyMgbW9zdHJvIGkgbnVtZXJpKGZyZXF1ZW56YSkgZGVsbGUgMTAgcGFyb2xlIHBp+SByaWNvcnJlbnRpDQpoZWFkKHRtX3VuaXdvcmRmcmVxLDEwKQ0KYGBgDQpgYGB7cn0NCiMjIHZpc3VhbGl6em8gbGUgcGFyb2xlIGNoZSByaWNvcnJvbm8gYWxtZW5vIDE0IHZvbHRlDQp3b3JkY2xvdWQobmFtZXModG1fdW5pZnJlcSksIHRtX3VuaWZyZXEsIG1pbi5mcmVxPTE0LCBtYXgud29yZHM9NTAsIHNjYWxlPWMoNSwgLjgpLCBjb2xvcnM9YnJld2VyLnBhbCg2LCAiRGFyazIiKSkNCmBgYA0KPGg0PkJpLUdyYW0gRnJlcXVlbnphPC9oND4NCg0KYGBge3J9DQojIyBTdGVzc2EgcHJvY2VkdXJhIGRlbGxhIHVuaS1ncmFtIG1hIGNvbiBsYSB0b2tlbml6emF6aW9uZSBzdSBkdWUgcGFyb2xlDQpCaWdyYW1Ub2tlbml6ZXIgPC0gZnVuY3Rpb24oeCkgTkdyYW1Ub2tlbml6ZXIoeCxXZWthX2NvbnRyb2wobWluID0gMiwgbWF4ID0gMikpDQpkdG0yIDwtIERvY3VtZW50VGVybU1hdHJpeChjb3JwdXNub3csIGNvbnRyb2wgPSBsaXN0KHRva2VuaXplID0gQmlncmFtVG9rZW5pemVyKSkNCmZyZXEyIDwtIHNvcnQoY29sU3Vtcyhhcy5tYXRyaXgoZHRtMikpLCBkZWNyZWFzaW5nPVRSVUUpDQp3ZjIgPC0gZGF0YS5mcmFtZSh3b3JkPW5hbWVzKGZyZXEyKSwgZnJlcT1mcmVxMikNCmBgYA0KYGBge3J9DQpwMiA8LSBnZ3Bsb3Qoc3Vic2V0KHdmMiwgZnJlcSA+IDE0KSwgYWVzKHggPSB3b3JkLCB5ID0gZnJlcSkpDQpwMiA8LSBwMiArIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IiwgZmlsbD0iZGFya2dyZWVuIiwgY29sb3VyPSJibHVlIikNCnAyICsgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KGFuZ2xlPTQ1LCBoanVzdD0xKSkgKyBnZ3RpdGxlKCJCaS1HcmFtIEZyZXF1ZW56YSIpICsgeGxhYigic2VudGVuemUiKSArIHlsYWIoIkZyZXF1ZW56YSIpIA0KYGBgDQpgYGB7cn0NCnRtX2JpZnJlcSA8LSBzb3J0KGNvbFN1bXMoYXMubWF0cml4KGR0bTIpKSwgZGVjcmVhc2luZz1UUlVFKQ0KdG1fYml3b3JkZnJlcSA8LSBkYXRhLmZyYW1lKHdvcmQ9bmFtZXModG1fYmlmcmVxKSwgZnJlcT10bV9iaWZyZXEpDQpgYGANCmBgYHtyfQ0KaGVhZCh0bV9iaXdvcmRmcmVxLDEwKQ0KYGBgDQpgYGB7cn0NCndvcmRjbG91ZChuYW1lcyh0bV9iaWZyZXEpLCB0bV9iaWZyZXEsIG1pbi5mcmVxPTE0LCBtYXgud29yZHM9MTAwLCBzY2FsZT1jKDMsIC4xKSwgY29sb3JzPWJyZXdlci5wYWwoNiwgIkRhcmsyIikpDQpgYGANCjxoND5UcmktR3JhbSBGcmVxdWVuemE8L2g0Pg0KDQpgYGB7cn0NCiMjIFN0ZXNzYSBwcm9jZWR1cmEgZGVsbGEgdW5pLWdyYW0gbWEgY29uIGxhIHRva2VuaXp6YXppb25lIHN1IHRyZSBwYXJvbGUNClRyaWdyYW1Ub2tlbml6ZXIgPC0gZnVuY3Rpb24oeCkgTkdyYW1Ub2tlbml6ZXIoeCxXZWthX2NvbnRyb2wobWluID0gMywgbWF4ID0gMykpDQpkdG0zIDwtIERvY3VtZW50VGVybU1hdHJpeChjb3JwdXNub3csIGNvbnRyb2wgPSBsaXN0KHRva2VuaXplID0gVHJpZ3JhbVRva2VuaXplcikpDQpmcmVxMyA8LSBzb3J0KGNvbFN1bXMoYXMubWF0cml4KGR0bTMpKSwgZGVjcmVhc2luZz1UUlVFKQ0Kd2YzIDwtIGRhdGEuZnJhbWUod29yZD1uYW1lcyhmcmVxMyksIGZyZXE9ZnJlcTMpDQpgYGANCmBgYHtyfQ0KcDMgPC0gZ2dwbG90KHN1YnNldCh3ZjMsIGZyZXEgPiA3KSwgYWVzKHggPSB3b3JkLCB5ID0gZnJlcSkpDQpwMyA8LSBwMyArIGdlb21fYmFyKHN0YXQ9ImlkZW50aXR5IiwgZmlsbD0iZGFya3JlZCIsIGNvbG91cj0iZ3JlZW4iKQ0KcDMgKyB0aGVtZShheGlzLnRleHQueD1lbGVtZW50X3RleHQoYW5nbGU9NDUsIGhqdXN0PTEpKSArIGdndGl0bGUoIlRyaS1HcmFtIEZyZXF1ZW56YSIpICsgeGxhYigiU2VudGVuemUiKSArIHlsYWIoIkZyZXF1ZW56YSIpDQpgYGANCmBgYHtyfQ0KdG1fdHJpZnJlcSA8LSBzb3J0KGNvbFN1bXMoYXMubWF0cml4KGR0bTMpKSwgZGVjcmVhc2luZz1UUlVFKQ0KdG1fdHJpd29yZGZyZXEgPC0gZGF0YS5mcmFtZSh3b3JkPW5hbWVzKHRtX3RyaWZyZXEpLCBmcmVxPXRtX3RyaWZyZXEpDQpgYGANCmBgYHtyfQ0KaGVhZCh0bV90cml3b3JkZnJlcSwxMCkNCmBgYA0KYGBge3J9DQp3b3JkY2xvdWQobmFtZXModG1fdHJpZnJlcSksIHRtX3RyaWZyZXEsIG1heC53b3Jkcz0xNiwgc2NhbGU9YygxLCAwLjgpLCBjb2xvcnM9YnJld2VyLnBhbCg2LCAiRGFyazIiKSkNCiAgDQpgYGANCjxoND5RdWFkcmktR3JhbSBGcmVxdWVuemE8L2g0Pg0KDQpgYGB7cn0NCiMjIFN0ZXNzYSBwcm9jZWR1cmEgZGVsbGEgdW5pLWdyYW0gbWEgY29uIGxhIHRva2VuaXp6YXppb25lIHN1IHF1YXR0cm8gcGFyb2xlDQpRdWFkcmlncmFtVG9rZW5pemVyIDwtIGZ1bmN0aW9uKHgpIE5HcmFtVG9rZW5pemVyKHgsV2VrYV9jb250cm9sKG1pbiA9IDQsIG1heCA9IDQpKQ0KZHRtNCA8LSBEb2N1bWVudFRlcm1NYXRyaXgoY29ycHVzbm93LCBjb250cm9sID0gbGlzdCh0b2tlbml6ZSA9IFF1YWRyaWdyYW1Ub2tlbml6ZXIpKQ0KZnJlcTQgPC0gc29ydChjb2xTdW1zKGFzLm1hdHJpeChkdG00KSksIGRlY3JlYXNpbmc9VFJVRSkNCndmNCA8LSBkYXRhLmZyYW1lKHdvcmQ9bmFtZXMoZnJlcTQpLCBmcmVxPWZyZXE0KQ0KYGBgDQpgYGB7cn0NCnA0IDwtIGdncGxvdChzdWJzZXQod2Y0LCBmcmVxID4gNiksIGFlcyh4ID0gd29yZCwgeSA9IGZyZXEpKQ0KcDQgPC0gcDQgKyBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsIGZpbGw9ImRhcmtyZWQiLCBjb2xvdXI9ImdyZWVuIikNCnA0ICsgdGhlbWUoYXhpcy50ZXh0Lng9ZWxlbWVudF90ZXh0KGFuZ2xlPTQ1LCBoanVzdD0xKSkgKyBnZ3RpdGxlKCJRdWFkcmktR3JhbSBGcmVxdWVuemEiKSArIHhsYWIoInNlbnRlbnplIikgKyB5bGFiKCJGcmVxdWVuemEiKQ0KYGBgDQpgYGB7cn0NCnRtX3F1YWRyaWZyZXEgPC0gc29ydChjb2xTdW1zKGFzLm1hdHJpeChkdG00KSksIGRlY3JlYXNpbmc9VFJVRSkNCnRtX3F1YWRyaXdvcmRmcmVxIDwtIGRhdGEuZnJhbWUod29yZD1uYW1lcyh0bV9xdWFkcmlmcmVxKSwgZnJlcT10bV9xdWFkcmlmcmVxKQ0KYGBgDQpgYGB7cn0NCmhlYWQodG1fcXVhZHJpd29yZGZyZXEsNSkNCmBgYA0KYGBge3J9DQp3b3JkY2xvdWQobmFtZXModG1fcXVhZHJpZnJlcSksIHRtX3F1YWRyaWZyZXEsIG1heC53b3Jkcz0yMCwgc2NhbGU9YygxLCAwLjMpLCBjb2xvcnM9YnJld2VyLnBhbCg2LCAiRGFyazIiKSkNCmBgYA0KPGg0PkZpdmUtR3JhbSBGcmVxdWVuemE8L2g0Pg0KDQpgYGB7cn0NCiMjIFN0ZXNzYSBwcm9jZWR1cmEgZGVsbGEgdW5pLWdyYW0gbWEgY29uIGxhIHRva2VuaXp6YXppb25lIHN1IGNpbnF1ZSBwYXJvbGUNCkZpdmVncmFtVG9rZW5pemVyIDwtIGZ1bmN0aW9uKHgpIE5HcmFtVG9rZW5pemVyKHgsV2VrYV9jb250cm9sKG1pbiA9IDUsIG1heCA9IDUpKQ0KZHRtNSA8LSBEb2N1bWVudFRlcm1NYXRyaXgoY29ycHVzbm93LCBjb250cm9sID0gbGlzdCh0b2tlbml6ZSA9IEZpdmVncmFtVG9rZW5pemVyKSkNCmZyZXE1IDwtIHNvcnQoY29sU3Vtcyhhcy5tYXRyaXgoZHRtNSkpLCBkZWNyZWFzaW5nPVRSVUUpDQp3ZjUgPC0gZGF0YS5mcmFtZSh3b3JkPW5hbWVzKGZyZXE1KSwgZnJlcT1mcmVxNSkNCmBgYA0KYGBge3J9DQpwNSA8LSBnZ3Bsb3Qoc3Vic2V0KHdmNSwgZnJlcSA+IDYpLCBhZXMoeCA9IHdvcmQsIHkgPSBmcmVxKSkNCnA1IDwtIHA1ICsgZ2VvbV9iYXIoc3RhdD0iaWRlbnRpdHkiLCBmaWxsPSJkYXJrcmVkIiwgY29sb3VyPSJncmVlbiIpDQpwNSArIHRoZW1lKGF4aXMudGV4dC54PWVsZW1lbnRfdGV4dChhbmdsZT00NSwgaGp1c3Q9MSkpICsgZ2d0aXRsZSgiRml2ZS1HcmFtIEZyZXF1ZW56YSIpICsgeGxhYigiU2VudGVuemUiKSArIHlsYWIoIkZyZXF1ZW56YSIpDQpgYGANCmBgYHtyfQ0KdG1fZml2ZWZyZXEgPC0gc29ydChjb2xTdW1zKGFzLm1hdHJpeChkdG01KSksIGRlY3JlYXNpbmc9VFJVRSkNCnRtX2ZpdmV3b3JkZnJlcSA8LSBkYXRhLmZyYW1lKHdvcmQ9bmFtZXModG1fZml2ZWZyZXEpLCBmcmVxPXRtX2ZpdmVmcmVxKQ0KYGBgDQpgYGB7cn0NCmhlYWQodG1fZml2ZXdvcmRmcmVxLDUpDQpgYGANCmBgYHtyfQ0Kd29yZGNsb3VkKG5hbWVzKHRtX2ZpdmVmcmVxKSwgdG1fZml2ZWZyZXEsIG1heC53b3Jkcz0yMCwgc2NhbGU9YygwLjgsIDAuOSksIGNvbG9ycz1icmV3ZXIucGFsKDYsICJEYXJrMiIpKQ0KYGBg