1. Einrichtung der Twitter-Schnittstelle

Für die Durchführung der Analysen wird ein Twitter-Account benötigt. Außerdem ist es notwendig, sich bei Twitter zu registrieren:

  1. Auf Sign in klicken, um sich mit dem eigenen Twitter-Account anzumelden
  2. Create App auswählen und die Felder entsprechend den Vorgaben ausfüllen. Bei dem Feld Callback URL diesen Wert einfügen: http://127.0.0.1:1410
  3. Die benötigten Zugangsdaten sind im Anschluss unter dem Reiter Keys and Access Tokens verfügbar
  4. Unter dem Feld Application Actions kann nun der benötigte Consumer Key und das Consumer Secret erzeugt werden

1.1 Benötigte R-Pakete installieren

Installation der benötigten R-Pakete.

install.packages(c("devtools", "rjson", "httr", "purr", "tidytext", "ggplot2", "stringr", "wordcloud", "reshape2"))
library(devtools)
install_github("geoffjentry/twitteR")

1.2 Twitter Zugriffsdaten einfügen

Wir fügen nun die Twitter-Zugriffsdaten aus Schritt 1 ein (die bereits eingefügten Werte sind Platzhalter). Die Daten sollten nun in R-Studio rechts oben im Global Environment unter Values aufgeführt sein.

api.key <-  'HWEPGcSOPjP9My3Wcz7wQqFnt'
api.secret <-  'qIpXYo1W8E6TRUAK7FyHAhAbIVYWwp5ORnPgCJCLNfTyYWMkZ8'
access.token <- '842728750160777217-qDctgf6P9PXUtOMdOGK1wQFgs1mWD82'
access.token.secret <- 'C9lqfzl5u6g4rKh8eZOcowvM5W8PGuKokTxxtXzzxvZJV'

Wenn das folgende Skript ausgeführt wird, erscheint in der Konsole diese Meldung: “Using browser based authentication” Use a local file (‘.httr-oauth’), to cache OAuth access credentials between R sessions? “1” auswählen und Enter drücken.

library(rjson)
library(httr)
library(twitteR)
setup_twitter_oauth(api.key,api.secret)
[1] "Using browser based authentication"

2.Text Mining mit R

2.1 Daten aus Twitter auslesen

Wir nutzen nun die userTimeline Funktion des twitteR Pakets um die 100 aktuellsten Tweets von Donald Trump zu laden. Damit wir damit arbeiten können, wandeln wir die Daten in einen Data Frame um.


library(dplyr)
library(purrr)

trump.tweets <- userTimeline("realDonaldTrump", n = 100)

trump.tweets.df <- tbl_df(map_df(trump.tweets, as.data.frame))

2.2 Text Mining mit tidytext

Die folgenden Inhalte orientieren sich an dem Buch Text Mining with R von Julia Silge und David Robinson. Dort finden Sie ausführliche Erklärungen zu allen folgenden Schritten.

2.2.1 Daten aus Twitter einlesen

Zuerst wird jedes Wort als einzelne Zeile in einem Data Frame (hier als trump.tweets.df2) abgespeichert.

library(tidytext)
trump.tweets.df2 <- trump.tweets.df %>%
  unnest_tokens(word, text)

2.2.2 Entfernung der Stoppwörter

Wir entfernen nun alle Stoppwörter (insb. Artikel und Konjunktionen), da diese für die Analysen irrelevant sind.

data(stop_words)
trump.tweets.df2 <- trump.tweets.df2 %>%
  anti_join(stop_words)
Joining, by = "word"

2.2.3 Analyse der Worthäufigkeiten

Nun könnne wir die häufigsten Wörter untersuchen.

trump.tweets.df2 %>%
  count(word, sort = TRUE)

2.2.4 Erweiterung der Stoppwörter

Wie wir sehen, kommen immer noch Wörter vor, die wir nicht analysieren möchten (z.B. https, t.co und amp). Diese werden nun in den Katalog der Stoppwörter mit aufgenommen. Wir erzeugen dafür einen neuen Katalog (custom.stop.words), welcher die ursprünglichen Stoppwörter (stop_words) und die von uns definierten Stoppwörter umfasst.

custom.stop.words <- bind_rows(data_frame(word = c("https", "t.co", "amp"), 
                                          lexicon = c("custom", "custom", "custom")),
                               stop_words)
trump.tweets.df2 <- trump.tweets.df2 %>%
  anti_join(custom.stop.words)
Joining, by = "word"
head(custom.stop.words)

2.2.5 Visualisierung der Worthäufigkeiten.

Visualisierung der Worthäufigkeiten mit ggplot2.

library(ggplot2)
trump.tweets.df2 %>%
  count(word, sort = TRUE) %>%
  filter(n > 5) %>%
  mutate(word = reorder(word, n)) %>%
  ggplot(aes(word, n)) +
  geom_col() +
  xlab(NULL) +
  coord_flip()

2.2.6 Sentimentanalyse

2.2.6.1 Postitive Wörter

Nutzung des NRC-Lexikon, um die positiven Wörter in den Tweets zu bestimmen.

library(stringr)
nrc.positive <- get_sentiments("nrc") %>% 
  filter(sentiment == "positive")
trump.tweets.df2 %>%
  inner_join(nrc.positive) %>%
  count(word, sort = TRUE)
Joining, by = "word"

2.2.6.2 Negative Wörter

Die gleiche Vorgehensweise können wir für negative Wörter nutzen.

nrc.negative <- get_sentiments("nrc") %>% 
  filter(sentiment == "negative")
trump.tweets.df2 %>%
  inner_join(nrc.negative) %>%
  count(word, sort = TRUE)
Joining, by = "word"

2.2.7 Übersicht über positive und negative Wörter

Wir nutzen nun das Bing-Lexikon um sowohl positive als auch negative Wörter in einer Tabelle anzeigen zu lassen.

bing.word.counts <- trump.tweets.df2 %>%
  inner_join(get_sentiments("bing")) %>%
  count(word, sentiment, sort = TRUE) %>%
  ungroup()
Joining, by = "word"
bing.word.counts

2.2.8 Visualisierung der Sentimentanalyse mit Balkendiagrammen

Visualisierung der Sentimentanalyse mit ggplot2.

bing.word.counts %>%
  group_by(sentiment) %>%
  top_n(10) %>%
  ungroup() %>%
  mutate(word = reorder(word, n)) %>%
  ggplot(aes(word, n, fill = sentiment)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~sentiment, scales = "free_y") +
  labs(y = "Contribution to sentiment",
       x = NULL) +
  coord_flip()
Selecting by n

2.2.9 Visualisierung der Sentimentanalyse mit Wortwolken

Visualisierung der Sentinemtanalyse mit einer Wortwolke.

library(wordcloud)
Lade nötiges Paket: RColorBrewer
trump.tweets.df2 %>%
  anti_join(stop_words) %>%
  anti_join(custom.stop.words) %>% 
  count(word) %>%
  with(wordcloud(word, n, max.words = 10))
Joining, by = "word"
Joining, by = "word"

Unterteilung der Wortwolke in negative und positive Wörter.

library(reshape2)
trump.tweets.df2 %>%
  inner_join(get_sentiments("bing")) %>%
  count(word, sentiment, sort = TRUE) %>%
  acast(word ~ sentiment, value.var = "n", fill = 0) %>%
  comparison.cloud(colors = c("#F8766D", "#00BFC4"),
                   max.words = 30)
Joining, by = "word"

LS0tCnRpdGxlOiAnVGV4dCBNaW5pbmc6IFR3aXR0ZXIgRGF0ZW5hbmFseXNlIG1pdCBSJwphdXRob3I6ICJQcm9mLiBEci4gSmFuIEtpcmVueiwgSG9jaHNjaHVsZSBkZXIgTWVkaWVuIFN0dXR0Z2FydCIKb3V0cHV0OgogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQKICBodG1sX2RvY3VtZW50OiBkZWZhdWx0Ci0tLQoKCiMgMS4gRWlucmljaHR1bmcgZGVyIFR3aXR0ZXItU2Nobml0dHN0ZWxsZQoKRsO8ciBkaWUgRHVyY2hmw7xocnVuZyBkZXIgQW5hbHlzZW4gd2lyZCBlaW4gVHdpdHRlci1BY2NvdW50IGJlbsO2dGlndC4gCkF1w59lcmRlbSBpc3QgZXMgbm90d2VuZGlnLCBzaWNoIGJlaSBUd2l0dGVyIHp1IFtyZWdpc3RyaWVyZW5dKGh0dHBzOi8vYXBwcy50d2l0dGVyLmNvbSk6CiAgCjEuIEF1ZiAqU2lnbiBpbioga2xpY2tlbiwgdW0gc2ljaCBtaXQgZGVtIGVpZ2VuZW4gVHdpdHRlci1BY2NvdW50IGFuenVtZWxkZW4gCjIuICpDcmVhdGUgQXBwKiBhdXN3w6RobGVuIHVuZCBkaWUgRmVsZGVyIGVudHNwcmVjaGVuZCBkZW4gVm9yZ2FiZW4gYXVzZsO8bGxlbi4gQmVpIGRlbSBGZWxkICpDYWxsYmFjayBVUkwqIGRpZXNlbiBXZXJ0IGVpbmbDvGdlbjogaHR0cDovLzEyNy4wLjAuMToxNDEwIAo0LiBEaWUgYmVuw7Z0aWd0ZW4gWnVnYW5nc2RhdGVuIHNpbmQgaW0gQW5zY2hsdXNzIHVudGVyIGRlbSBSZWl0ZXIgKktleXMgYW5kIEFjY2VzcyBUb2tlbnMqIHZlcmbDvGdiYXIgCjMuIFVudGVyIGRlbSBGZWxkICpBcHBsaWNhdGlvbiBBY3Rpb25zKiBrYW5uIG51biBkZXIgYmVuw7Z0aWd0ZSBDb25zdW1lciBLZXkgdW5kIGRhcyBDb25zdW1lciBTZWNyZXQgZXJ6ZXVndCB3ZXJkZW4gICAgICAgICAKCiMjIDEuMSBCZW7DtnRpZ3RlIFItUGFrZXRlIGluc3RhbGxpZXJlbgoKSW5zdGFsbGF0aW9uIGRlciBiZW7DtnRpZ3RlbiBSLVBha2V0ZS4gCgpgYGB7cn0KaW5zdGFsbC5wYWNrYWdlcyhjKCJkZXZ0b29scyIsICJyanNvbiIsICJodHRyIiwgInB1cnIiLCAidGlkeXRleHQiLCAiZ2dwbG90MiIsICJzdHJpbmdyIiwgIndvcmRjbG91ZCIsICJyZXNoYXBlMiIpKQpsaWJyYXJ5KGRldnRvb2xzKQppbnN0YWxsX2dpdGh1YigiZ2VvZmZqZW50cnkvdHdpdHRlUiIpCgpgYGAKCiMjIDEuMiBUd2l0dGVyIFp1Z3JpZmZzZGF0ZW4gZWluZsO8Z2VuCgpXaXIgZsO8Z2VuIG51biBkaWUgVHdpdHRlci1adWdyaWZmc2RhdGVuIGF1cyBTY2hyaXR0IDEgZWluIChkaWUgYmVyZWl0cyBlaW5nZWbDvGd0ZW4gV2VydGUgc2luZCBQbGF0emhhbHRlcikuIApEaWUgRGF0ZW4gc29sbHRlbiBudW4gaW4gKlItU3R1ZGlvKiByZWNodHMgb2JlbiBpbSAqR2xvYmFsIEVudmlyb25tZW50KiB1bnRlciBWYWx1ZXMgYXVmZ2Vmw7xocnQgc2Vpbi4KCmBgYHtyfQoKYXBpLmtleSA8LSAgJ0hXRVBHY1NPUGpQOU15M1djejd3UXFGbnQnCmFwaS5zZWNyZXQgPC0gICdxSXBYWW8xVzhFNlRSVUFLN0Z5SEFoQWJJVllXd3A1T1JuUGdDSkNMTmZUeVlXTWtaOCcKYWNjZXNzLnRva2VuIDwtICc4NDI3Mjg3NTAxNjA3NzcyMTctcURjdGdmNlA5UFhVdE9NZE9HSzF3UUZnczFtV0Q4MicKYWNjZXNzLnRva2VuLnNlY3JldCA8LSAnQzlscWZ6bDV1Nmc0cktoOGVaT2Nvd3ZNNVc4UEd1S29rVHh4dFh6enh2WkpWJwoKYGBgCgpXZW5uIGRhcyBmb2xnZW5kZSBTa3JpcHQgYXVzZ2Vmw7xocnQgd2lyZCwgZXJzY2hlaW50IGluIGRlciBLb25zb2xlIGRpZXNlIE1lbGR1bmc6ICJVc2luZyBicm93c2VyIGJhc2VkIGF1dGhlbnRpY2F0aW9uIiBVc2UgYSBsb2NhbCBmaWxlICgnLmh0dHItb2F1dGgnKSwgdG8gY2FjaGUgT0F1dGggYWNjZXNzIGNyZWRlbnRpYWxzIGJldHdlZW4gUiBzZXNzaW9ucz8gIjEiIGF1c3fDpGhsZW4gdW5kIEVudGVyIGRyw7xja2VuLiAKCmBgYHtyfQoKbGlicmFyeShyanNvbikKbGlicmFyeShodHRyKQpsaWJyYXJ5KHR3aXR0ZVIpCgpzZXR1cF90d2l0dGVyX29hdXRoKGFwaS5rZXksYXBpLnNlY3JldCkKCmBgYAoKIyAyLlRleHQgTWluaW5nIG1pdCBSCgojIyAyLjEgRGF0ZW4gYXVzIFR3aXR0ZXIgYXVzbGVzZW4KCldpciBudXR6ZW4gbnVuIGRpZSAqdXNlclRpbWVsaW5lKiBGdW5rdGlvbiBkZXMgdHdpdHRlUiBQYWtldHMgdW0gZGllIDEwMCBha3R1ZWxsc3RlbiBUd2VldHMgdm9uIERvbmFsZCBUcnVtcCB6dSBsYWRlbi4gCkRhbWl0IHdpciBkYW1pdCBhcmJlaXRlbiBrw7ZubmVuLCB3YW5kZWxuIHdpciBkaWUgRGF0ZW4gaW4gZWluZW4gW0RhdGEgRnJhbWVdKGh0dHA6Ly93d3cuci10dXRvci5jb20vci1pbnRyb2R1Y3Rpb24vZGF0YS1mcmFtZSkgdW0uCgpgYGB7cn0KCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkocHVycnIpCgp0cnVtcC50d2VldHMgPC0gdXNlclRpbWVsaW5lKCJyZWFsRG9uYWxkVHJ1bXAiLCBuID0gMTAwKQoKdHJ1bXAudHdlZXRzLmRmIDwtIHRibF9kZihtYXBfZGYodHJ1bXAudHdlZXRzLCBhcy5kYXRhLmZyYW1lKSkKCgpgYGAKCiMjIDIuMiBUZXh0IE1pbmluZyBtaXQgdGlkeXRleHQKCkRpZSBmb2xnZW5kZW4gSW5oYWx0ZSBvcmllbnRpZXJlbiBzaWNoIGFuIGRlbSBCdWNoIFtUZXh0IE1pbmluZyB3aXRoIFJdKGh0dHA6Ly90aWR5dGV4dG1pbmluZy5jb20vaW5kZXguaHRtbCkgdm9uIEp1bGlhIFNpbGdlIHVuZCBEYXZpZCBSb2JpbnNvbi4gRG9ydCBmaW5kZW4gU2llIGF1c2bDvGhybGljaGUgRXJrbMOkcnVuZ2VuIHp1IGFsbGVuIGZvbGdlbmRlbiBTY2hyaXR0ZW4uCgojIyMgMi4yLjEgRGF0ZW4gYXVzIFR3aXR0ZXIgZWlubGVzZW4KClp1ZXJzdCB3aXJkIGplZGVzIFdvcnQgYWxzIGVpbnplbG5lIFplaWxlIGluIGVpbmVtIERhdGEgRnJhbWUgKGhpZXIgYWxzIHRydW1wLnR3ZWV0cy5kZjIpIGFiZ2VzcGVpY2hlcnQuCgpgYGB7cn0KbGlicmFyeSh0aWR5dGV4dCkKCnRydW1wLnR3ZWV0cy5kZjIgPC0gdHJ1bXAudHdlZXRzLmRmICU+JQogIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCkKCmBgYAoKIyMjIDIuMi4yIEVudGZlcm51bmcgZGVyIFN0b3Bwd8O2cnRlcgoKV2lyIGVudGZlcm5lbiBudW4gYWxsZSBTdG9wcHfDtnJ0ZXIgKGluc2IuIEFydGlrZWwgdW5kIEtvbmp1bmt0aW9uZW4pLCBkYSBkaWVzZSBmw7xyIGRpZSBBbmFseXNlbiBpcnJlbGV2YW50IHNpbmQuICAKCmBgYHtyfQoKZGF0YShzdG9wX3dvcmRzKQoKdHJ1bXAudHdlZXRzLmRmMiA8LSB0cnVtcC50d2VldHMuZGYyICU+JQogIGFudGlfam9pbihzdG9wX3dvcmRzKQoKYGBgCgojIyMgMi4yLjMgQW5hbHlzZSBkZXIgV29ydGjDpHVmaWdrZWl0ZW4KCk51biBrw7Zubm5lIHdpciBkaWUgaMOkdWZpZ3N0ZW4gV8O2cnRlciB1bnRlcnN1Y2hlbi4KCmBgYHtyfQp0cnVtcC50d2VldHMuZGYyICU+JQogIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFKQoKYGBgCgojIyMgMi4yLjQgRXJ3ZWl0ZXJ1bmcgZGVyIFN0b3Bwd8O2cnRlcgoKV2llIHdpciBzZWhlbiwga29tbWVuIGltbWVyIG5vY2ggV8O2cnRlciB2b3IsIGRpZSB3aXIgbmljaHQgYW5hbHlzaWVyZW4gbcO2Y2h0ZW4gKHouQi4gaHR0cHMsIHQuY28gdW5kIGFtcCkuIApEaWVzZSB3ZXJkZW4gbnVuIGluIGRlbiBLYXRhbG9nIGRlciBTdG9wcHfDtnJ0ZXIgbWl0IGF1Zmdlbm9tbWVuLiBXaXIgZXJ6ZXVnZW4gZGFmw7xyIGVpbmVuIG5ldWVuIEthdGFsb2cgKGN1c3RvbS5zdG9wLndvcmRzKSwgCndlbGNoZXIgZGllIHVyc3Byw7xuZ2xpY2hlbiBTdG9wcHfDtnJ0ZXIgKHN0b3Bfd29yZHMpIHVuZCBkaWUgdm9uIHVucyBkZWZpbmllcnRlbiBTdG9wcHfDtnJ0ZXIgdW1mYXNzdC4gCgpgYGB7cn0KCmN1c3RvbS5zdG9wLndvcmRzIDwtIGJpbmRfcm93cyhkYXRhX2ZyYW1lKHdvcmQgPSBjKCJodHRwcyIsICJ0LmNvIiwgImFtcCIpLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGV4aWNvbiA9IGMoImN1c3RvbSIsICJjdXN0b20iLCAiY3VzdG9tIikpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RvcF93b3JkcykKCnRydW1wLnR3ZWV0cy5kZjIgPC0gdHJ1bXAudHdlZXRzLmRmMiAlPiUKICBhbnRpX2pvaW4oY3VzdG9tLnN0b3Aud29yZHMpCgpoZWFkKGN1c3RvbS5zdG9wLndvcmRzKQoKYGBgCgojIyMgMi4yLjUgVmlzdWFsaXNpZXJ1bmcgZGVyIFdvcnRow6R1Zmlna2VpdGVuLgoKVmlzdWFsaXNpZXJ1bmcgZGVyIFdvcnRow6R1Zmlna2VpdGVuIG1pdCAqZ2dwbG90MiouCgpgYGB7cn0KCmxpYnJhcnkoZ2dwbG90MikKCnRydW1wLnR3ZWV0cy5kZjIgJT4lCiAgY291bnQod29yZCwgc29ydCA9IFRSVUUpICU+JQogIGZpbHRlcihuID4gNSkgJT4lCiAgbXV0YXRlKHdvcmQgPSByZW9yZGVyKHdvcmQsIG4pKSAlPiUKICBnZ3Bsb3QoYWVzKHdvcmQsIG4pKSArCiAgZ2VvbV9jb2woKSArCiAgeGxhYihOVUxMKSArCiAgY29vcmRfZmxpcCgpCgpgYGAKCiMjIyAyLjIuNiBTZW50aW1lbnRhbmFseXNlCgojIyMjIDIuMi42LjEgUG9zdGl0aXZlIFfDtnJ0ZXIKCk51dHp1bmcgZGVzIFtOUkMtTGV4aWtvbl0oaHR0cDovL3NhaWZtb2hhbW1hZC5jb20vV2ViUGFnZXMvTlJDLUVtb3Rpb24tTGV4aWNvbi5odG0pLCB1bSBkaWUgcG9zaXRpdmVuIFfDtnJ0ZXIgaW4gZGVuIApUd2VldHMgenUgYmVzdGltbWVuLgoKCmBgYHtyfQoKbGlicmFyeShzdHJpbmdyKQoKbnJjLnBvc2l0aXZlIDwtIGdldF9zZW50aW1lbnRzKCJucmMiKSAlPiUgCiAgZmlsdGVyKHNlbnRpbWVudCA9PSAicG9zaXRpdmUiKQoKdHJ1bXAudHdlZXRzLmRmMiAlPiUKICBpbm5lcl9qb2luKG5yYy5wb3NpdGl2ZSkgJT4lCiAgY291bnQod29yZCwgc29ydCA9IFRSVUUpCgpgYGAKCiMjIyMgMi4yLjYuMiBOZWdhdGl2ZSBXw7ZydGVyCgpEaWUgZ2xlaWNoZSBWb3JnZWhlbnN3ZWlzZSBrw7ZubmVuIHdpciBmw7xyIG5lZ2F0aXZlIFfDtnJ0ZXIgbnV0emVuLgoKYGBge3J9CgpucmMubmVnYXRpdmUgPC0gZ2V0X3NlbnRpbWVudHMoIm5yYyIpICU+JSAKICBmaWx0ZXIoc2VudGltZW50ID09ICJuZWdhdGl2ZSIpCgp0cnVtcC50d2VldHMuZGYyICU+JQogIGlubmVyX2pvaW4obnJjLm5lZ2F0aXZlKSAlPiUKICBjb3VudCh3b3JkLCBzb3J0ID0gVFJVRSkKCmBgYAoKIyMjIDIuMi43IMOcYmVyc2ljaHQgw7xiZXIgcG9zaXRpdmUgdW5kIG5lZ2F0aXZlIFfDtnJ0ZXIKCldpciBudXR6ZW4gbnVuIGRhcyBbQmluZy1MZXhpa29uXShodHRwczovL3d3dy5jcy51aWMuZWR1L35saXViL0ZCUy9zZW50aW1lbnQtYW5hbHlzaXMuaHRtbCkgdW0gc293b2hsIHBvc2l0aXZlIGFscyBhdWNoIApuZWdhdGl2ZSBXw7ZydGVyIGluIGVpbmVyIFRhYmVsbGUgYW56ZWlnZW4genUgbGFzc2VuLgoKYGBge3J9CgpiaW5nLndvcmQuY291bnRzIDwtIHRydW1wLnR3ZWV0cy5kZjIgJT4lCiAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygiYmluZyIpKSAlPiUKICBjb3VudCh3b3JkLCBzZW50aW1lbnQsIHNvcnQgPSBUUlVFKSAlPiUKICB1bmdyb3VwKCkKCmJpbmcud29yZC5jb3VudHMKCmBgYAoKIyMjIDIuMi44IFZpc3VhbGlzaWVydW5nIGRlciBTZW50aW1lbnRhbmFseXNlIG1pdCBCYWxrZW5kaWFncmFtbWVuCgpWaXN1YWxpc2llcnVuZyBkZXIgU2VudGltZW50YW5hbHlzZSBtaXQgKmdncGxvdDIqLgoKCmBgYHtyfQoKYmluZy53b3JkLmNvdW50cyAlPiUKICBncm91cF9ieShzZW50aW1lbnQpICU+JQogIHRvcF9uKDEwKSAlPiUKICB1bmdyb3VwKCkgJT4lCiAgbXV0YXRlKHdvcmQgPSByZW9yZGVyKHdvcmQsIG4pKSAlPiUKICBnZ3Bsb3QoYWVzKHdvcmQsIG4sIGZpbGwgPSBzZW50aW1lbnQpKSArCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogIGZhY2V0X3dyYXAofnNlbnRpbWVudCwgc2NhbGVzID0gImZyZWVfeSIpICsKICBsYWJzKHkgPSAiQ29udHJpYnV0aW9uIHRvIHNlbnRpbWVudCIsCiAgICAgICB4ID0gTlVMTCkgKwogIGNvb3JkX2ZsaXAoKQoKYGBgCgojIyMgMi4yLjkgVmlzdWFsaXNpZXJ1bmcgZGVyIFNlbnRpbWVudGFuYWx5c2UgbWl0IFdvcnR3b2xrZW4KClZpc3VhbGlzaWVydW5nIGRlciBTZW50aW5lbXRhbmFseXNlIG1pdCBlaW5lciBXb3J0d29sa2UuCgpgYGB7cn0KbGlicmFyeSh3b3JkY2xvdWQpCgp0cnVtcC50d2VldHMuZGYyICU+JQogIGFudGlfam9pbihzdG9wX3dvcmRzKSAlPiUKICBhbnRpX2pvaW4oY3VzdG9tLnN0b3Aud29yZHMpICU+JSAKICBjb3VudCh3b3JkKSAlPiUKICB3aXRoKHdvcmRjbG91ZCh3b3JkLCBuLCBtYXgud29yZHMgPSAxMCkpCgpgYGAKClVudGVydGVpbHVuZyBkZXIgV29ydHdvbGtlIGluIG5lZ2F0aXZlIHVuZCBwb3NpdGl2ZSBXw7ZydGVyLgoKYGBge3J9CmxpYnJhcnkocmVzaGFwZTIpCgp0cnVtcC50d2VldHMuZGYyICU+JQogIGlubmVyX2pvaW4oZ2V0X3NlbnRpbWVudHMoImJpbmciKSkgJT4lCiAgY291bnQod29yZCwgc2VudGltZW50LCBzb3J0ID0gVFJVRSkgJT4lCiAgYWNhc3Qod29yZCB+IHNlbnRpbWVudCwgdmFsdWUudmFyID0gIm4iLCBmaWxsID0gMCkgJT4lCiAgY29tcGFyaXNvbi5jbG91ZChjb2xvcnMgPSBjKCIjRjg3NjZEIiwgIiMwMEJGQzQiKSwKICAgICAgICAgICAgICAgICAgIG1heC53b3JkcyA9IDMwKQpgYGAKCg==