### Load standardpackages
library(tidyverse) # Collection of all the good stuff like dplyr, ggplot2 ect.
library(magrittr) # For extra-piping operators (eg. %<>%)

This session

In this applied session, you will:

  1. Refresh basic string manipulation skills
  2. Learn how to tokenize texts and analyze these tokens
  3. Apply these skills on twitter data

Refresher: Basics of String Manupilation

We start by taking a piece of text and turning it into something that carries the meaning of the initial text but is less noisy and thus perhaps easier to “understand” by a computer.

text <- "The Eton-educated, non-binary British Iraqi had always struggled with their identity, until they discovered drag. Yet the 29 year old says the performances come at a high price"
# Transforming to lower case
text %>% str_to_lower()
# Split by '.' (=sentence)
text %>% str_split('\\.')
text %>% str_replace_all('o', 'O')
# Split by ' ' (=word)
text %>% str_remove_all('[[:punct:]]') %>% str_split(' ') %>% unlist()
text %>% str_to_lower() %>% str_remove_all('[[:punct:]]') %>% str_split(' ') 

The R NLP ecosystem

  • Most language analysis approaches are based on the analysis of texts word-by-word.
  • Here, their order might matter (word sequence models) or not (bag-of-words models), but the smallest unit of analysis is usually the word.
  • This is usually done in context of the document the word appeared in. Therefore, on first glance three types datastructures make sense:
  1. Tidy: Approach, where data is served in a 2-column document-word format (e.g., tidytext)
  2. Token lists: Creation of special objects, saved as document-token lists or corpus (e.g., tm, quanteda)
  3. Matrix: Long approach, where data is served as document-term matrix, term-frequency matrix, etc.
  • Different forms of analysis (and the packages used therefore) favor different structures, so we need to be fluent in transfering original raw-text in * These formats, as well as switching between them. (for more infos, check here).

Tidy Text Formats

  • While there exist other ecosystems to do txt analysis (e.g., tm, quanteda), I will here almost exclusively use tidytext, which is very simple yet powerful, very well documented, and works very neathly with tidymodels and the rest of the tidyverse ecosystem.
library(tidytext)
  • While we will for later applications we will use different formats, we here will limit ourselves to word token, which can do most of the simple jobs.
  • Here, we apply tidy principles to text, make word-token per document our unit of analysis.
  • Therefore, every row repreesents a word per document. This sounds like a lot of redundancy, but makes it very easy to work with compared to more complez matrix and list formats. Here, we can do our usual sumarries and visualizations pretty much out-of-the-box.
# Tidytext wants a tibble as point of departure
text_tbl <- tibble(id = 1, text = text)
# We now unnest the tokens. Notice it is by default deleting all punctuation and transforming the text to lower chars.
text_tidy <- text_tbl %>% unnest_tokens(word, text, token = 'words')
  • Overall, in NLP we are trying to represent meaning structure.
  • That means that we want to focus on the most important and “meaning-bearing elements” in text, while reducing noise.
  • Words such as “and”, “have”, “the” may have central syntactic functions but are not particularly important from a semantic perspective.
# Tidytext comes with a stopword lexicon
stop_words
text_tidy %<>%
  anti_join(stop_words, by = 'word')
text_tidy
# We now unnest the tokens. Notice it is by default deleting all punctuation and transforming the text to lower chars.
sentences_tidy <- text_tbl %>% unnest_tokens(word, text, token = 'sentences')
sentences_tidy

Your turn!

Take the following text and transform it into a list of lists with with each element being a tokenized sentence. Remove stopwords, lower all tokens and keep only (1) alpha-numeric word tokens, (2) charactewr tokens.

I’ve been called many things in my life, but never an optimist. That was fine by me. I believed pessimists lived in a constant state of pleasant surprise: if you always expected the worst, things generally turned out better than you imagined. The only real problem with pessimism, I figured, was that too much of it could accidentally turn you into an optimist.

source: https://www.theguardian.com/global/2019/nov/21/glass-half-full-how-i-learned-to-be-an-optimist-in-a-week

Trump Tweets Processing many short texts and simple stats

An introduction to NLP would not be the same without Donald’s tweets. Let’s use these tweets for some more basic NLP and let’s try to gather some insights…maybe

donald_tweets

Let’s try to use some very simple statistics on twitter data, thanks to Trump Twitter Archive

Note: We here already use precompiled data. However, you could use the rtweet package and instead work with own data on tweets of interest.

# we will load some json files
library(jsonlite)
library(tidyjson)
# download and open some Trump tweets from trump_tweet_data_archive
tmp <- tempfile()
download.file("https://github.com/bpb27/trump_tweet_data_archive/raw/master/condensed_2018.json.zip", tmp)
trump_tweets <- stream_in(unz(tmp, "condensed_2018.json"))
trump_tweets %>% glimpse()
library(lubridate) # For workin with times
trump_tweets %<>%
  mutate(created_at = paste(substr(created_at,27,30),
                      substr(created_at,5,7),
                      substr(created_at,9,10),
                      substr(created_at,12,20)) %>% 
           as_datetime())

Notye: We will not use the times of tweet for now, but feel free to discover, and maybe reconstruct something inspired by THIS AMAZING PAPER!!!

# Lets filter out retweets
trump_tweets %<>%
  filter(is_retweet == FALSE)
# LEts tokenize. Notice that there are special tokens for tweets which keep usefull special characters
trump_token <-trump_tweets %>%
  select(id_str, text) %>%
  unnest_tokens(word, text, token = "tweets")
trump_token %<>%
  anti_join(stop_words, by = 'word')
trump_token %>% count(word, sort = TRUE) %>% head(100)

Lets see who trump mentions

trump_token %>%
  filter(word %>% str_detect('@')) %>%
  count(word, sort = TRUE)

Your turn

alt text

The link below holds a datasewt with ~10k #OKBoomer tweets from the days 10-21 Nov 2019.

https://github.com/SDS-AAU/SDS-master/raw/master/M2/data/tweets_boomer.zip

What to do: * Use elements from the above code to make a list of the most common hashtags (you have to get the hashtags from the text, not using the column containing them already) * Also try to have a look at hashtags over time: Take out the 10 most common hashtags - excluding #OKBoomer - and plot their occurrence over the days in the data

Plan of attack:

  • Convert the timestamp into a datetime
  • Calculate the occurence of the specific hashtags (itentified by a trailing #) in the chosen timespan (here: Days)
  • Plot (days on x, n on y)

Go!

Endnotes

Main reference

  • R for Data Science (Grolemund & Wickham)
    • Chapter 14: To refresh simple string manipulations
  • Julia Silge and David Robinson (2020). Text Mining with R: A Tidy Approach, O’Reilly. Online available here
    • Chapter 1: Introduction to the tidy text format

Packages & Ecosystem

further: * rtweet: R interface to the twitter API.

Suggestions for further study

Session Info

sessionInfo()
LS0tCnRpdGxlOiAnSW50cm9kdWN0aW9uIHRvIE5hdHVyYWwtbGFuZ3VhZ2UtUHJvY2Vzc2luZyAoUiknCmF1dGhvcjogIkRhbmllbCBTLiBIYWluIChkc2hAYnVzaW5lc3MuYWF1LmRrKSIKZGF0ZTogIlVwZGF0ZWQgYHIgZm9ybWF0KFN5cy50aW1lKCksICclQiAlZCwgJVknKWAiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgY29kZV9mb2xkaW5nOiBzaG93CiAgICBkZl9wcmludDogcGFnZWQKICAgIHRvYzogdHJ1ZQogICAgdG9jX2RlcHRoOiAyCiAgICB0b2NfZmxvYXQ6CiAgICAgIGNvbGxhcHNlZDogZmFsc2UKICAgIHRoZW1lOiBmbGF0bHkKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0KIyMjIEdlbmVyaWMgcHJlYW1ibGUKcm0obGlzdD1scygpKQpTeXMuc2V0ZW52KExBTkcgPSAiZW4iKSAjIEZvciBlbmdsaXNoIGxhbmd1YWdlCm9wdGlvbnMoc2NpcGVuID0gNSkgIyBUbyBkZWFjdGl2YXRlIGFubm95aW5nIHNjaWVudGlmaWMgbnVtYmVyIG5vdGF0aW9uCgojIyMgS25pdHIgb3B0aW9ucwpsaWJyYXJ5KGtuaXRyKSAjIEZvciBkaXNwbGF5IG9mIHRoZSBtYXJrZG93bgprbml0cjo6b3B0c19jaHVuayRzZXQod2FybmluZz1GQUxTRSwKICAgICAgICAgICAgICAgICAgICAgbWVzc2FnZT1GQUxTRSwKICAgICAgICAgICAgICAgICAgICAgY29tbWVudD1GQUxTRSwgCiAgICAgICAgICAgICAgICAgICAgIGZpZy5hbGlnbj0iY2VudGVyIgogICAgICAgICAgICAgICAgICAgICApCmBgYAoKYGBge3J9CiMjIyBMb2FkIHN0YW5kYXJkcGFja2FnZXMKbGlicmFyeSh0aWR5dmVyc2UpICMgQ29sbGVjdGlvbiBvZiBhbGwgdGhlIGdvb2Qgc3R1ZmYgbGlrZSBkcGx5ciwgZ2dwbG90MiBlY3QuCmxpYnJhcnkobWFncml0dHIpICMgRm9yIGV4dHJhLXBpcGluZyBvcGVyYXRvcnMgKGVnLiAlPD4lKQpgYGAKCiMjIyBUaGlzIHNlc3Npb24KCkluIHRoaXMgYXBwbGllZCBzZXNzaW9uLCB5b3Ugd2lsbDoKCjEuIFJlZnJlc2ggYmFzaWMgc3RyaW5nIG1hbmlwdWxhdGlvbiBza2lsbHMKMi4gTGVhcm4gaG93IHRvIHRva2VuaXplIHRleHRzIGFuZCBhbmFseXplIHRoZXNlIHRva2VucwozLiBBcHBseSB0aGVzZSBza2lsbHMgb24gdHdpdHRlciBkYXRhCgoKIyBSZWZyZXNoZXI6IEJhc2ljcyBvZiBTdHJpbmcgTWFudXBpbGF0aW9uCgpXZSBzdGFydCBieSB0YWtpbmcgYSBwaWVjZSBvZiB0ZXh0IGFuZCB0dXJuaW5nIGl0IGludG8gc29tZXRoaW5nIHRoYXQgY2FycmllcyB0aGUgbWVhbmluZyBvZiB0aGUgaW5pdGlhbCB0ZXh0IGJ1dCBpcyBsZXNzIG5vaXN5IGFuZCB0aHVzIHBlcmhhcHMgZWFzaWVyIHRvICJ1bmRlcnN0YW5kIiBieSBhIGNvbXB1dGVyLgoKYGBge3J9CnRleHQgPC0gIlRoZSBFdG9uLWVkdWNhdGVkLCBub24tYmluYXJ5IEJyaXRpc2ggSXJhcWkgaGFkIGFsd2F5cyBzdHJ1Z2dsZWQgd2l0aCB0aGVpciBpZGVudGl0eSwgdW50aWwgdGhleSBkaXNjb3ZlcmVkIGRyYWcuIFlldCB0aGUgMjkgeWVhciBvbGQgc2F5cyB0aGUgcGVyZm9ybWFuY2VzIGNvbWUgYXQgYSBoaWdoIHByaWNlIgpgYGAKCmBgYHtyfQojIFRyYW5zZm9ybWluZyB0byBsb3dlciBjYXNlCnRleHQgJT4lIHN0cl90b19sb3dlcigpCmBgYAoKYGBge3J9CiMgU3BsaXQgYnkgJy4nICg9c2VudGVuY2UpCnRleHQgJT4lIHN0cl9zcGxpdCgnXFwuJykKYGBgCgpgYGB7cn0KdGV4dCAlPiUgc3RyX3JlcGxhY2VfYWxsKCdvJywgJ08nKQpgYGAKCmBgYHtyfQojIFNwbGl0IGJ5ICcgJyAoPXdvcmQpCnRleHQgJT4lIHN0cl9yZW1vdmVfYWxsKCdbWzpwdW5jdDpdXScpICU+JSBzdHJfc3BsaXQoJyAnKSAlPiUgdW5saXN0KCkKYGBgCgpgYGB7cn0KdGV4dCAlPiUgc3RyX3RvX2xvd2VyKCkgJT4lIHN0cl9yZW1vdmVfYWxsKCdbWzpwdW5jdDpdXScpICU+JSBzdHJfc3BsaXQoJyAnKSAKYGBgCgojIFRoZSBSIE5MUCBlY29zeXN0ZW0gCgoqIE1vc3QgbGFuZ3VhZ2UgYW5hbHlzaXMgYXBwcm9hY2hlcyBhcmUgYmFzZWQgb24gdGhlIGFuYWx5c2lzIG9mIHRleHRzIHdvcmQtYnktd29yZC4gCiogSGVyZSwgdGhlaXIgb3JkZXIgbWlnaHQgbWF0dGVyICh3b3JkIHNlcXVlbmNlIG1vZGVscykgb3Igbm90IChiYWctb2Ytd29yZHMgbW9kZWxzKSwgYnV0IHRoZSBzbWFsbGVzdCB1bml0IG9mIGFuYWx5c2lzIGlzIHVzdWFsbHkgdGhlIHdvcmQuIAoqIFRoaXMgaXMgdXN1YWxseSBkb25lIGluIGNvbnRleHQgb2YgdGhlIGRvY3VtZW50IHRoZSB3b3JkIGFwcGVhcmVkIGluLiBUaGVyZWZvcmUsIG9uIGZpcnN0IGdsYW5jZSB0aHJlZSB0eXBlcyBkYXRhc3RydWN0dXJlcyBtYWtlIHNlbnNlOgoKMS4gKipUaWR5OioqICBBcHByb2FjaCwgd2hlcmUgZGF0YSBpcyBzZXJ2ZWQgaW4gYSAyLWNvbHVtbiBkb2N1bWVudC13b3JkIGZvcm1hdCAoZS5nLiwgYHRpZHl0ZXh0YCkKMi4gKipUb2tlbiBsaXN0czoqKiBDcmVhdGlvbiBvZiBzcGVjaWFsIG9iamVjdHMsIHNhdmVkIGFzIGRvY3VtZW50LXRva2VuIGxpc3RzIG9yIGNvcnB1cyAoZS5nLiwgYHRtYCwgYHF1YW50ZWRhYCkKMy4gKipNYXRyaXg6KiogTG9uZyBhcHByb2FjaCwgd2hlcmUgZGF0YSBpcyBzZXJ2ZWQgYXMgZG9jdW1lbnQtdGVybSBtYXRyaXgsIHRlcm0tZnJlcXVlbmN5IG1hdHJpeCwgZXRjLgoKKiBEaWZmZXJlbnQgZm9ybXMgb2YgYW5hbHlzaXMgKGFuZCB0aGUgcGFja2FnZXMgdXNlZCB0aGVyZWZvcmUpIGZhdm9yIGRpZmZlcmVudCBzdHJ1Y3R1cmVzLCBzbyB3ZSBuZWVkIHRvIGJlIGZsdWVudCBpbiB0cmFuc2ZlcmluZyBvcmlnaW5hbCByYXctdGV4dCBpbiAqIFRoZXNlIGZvcm1hdHMsIGFzIHdlbGwgYXMgc3dpdGNoaW5nIGJldHdlZW4gdGhlbS4gKGZvciBtb3JlIGluZm9zLCBjaGVjayBbaGVyZV0oaHR0cHM6Ly93d3cudGlkeXRleHRtaW5pbmcuY29tL2R0bS5odG1sKSkuCgohW10oaHR0cHM6Ly9zZHMtYWF1LmdpdGh1Yi5pby9TRFMtbWFzdGVyLzAwX21lZGlhL25scF90aWR5d29ya2Zsb3cucG5nKQoKIyMgVGlkeSBUZXh0IEZvcm1hdHMKCiogV2hpbGUgdGhlcmUgZXhpc3Qgb3RoZXIgZWNvc3lzdGVtcyB0byBkbyB0eHQgYW5hbHlzaXMgKGUuZy4sIGB0bWAsIGBxdWFudGVkYWApLCBJIHdpbGwgaGVyZSBhbG1vc3QgZXhjbHVzaXZlbHkgdXNlIGB0aWR5dGV4dGAsIHdoaWNoIGlzIHZlcnkgc2ltcGxlIHlldCBwb3dlcmZ1bCwgdmVyeSB3ZWxsIGRvY3VtZW50ZWQsIGFuZCB3b3JrcyB2ZXJ5IG5lYXRobHkgd2l0aCBgdGlkeW1vZGVsc2AgYW5kIHRoZSByZXN0IG9mIHRoZSBgdGlkeXZlcnNlYCBlY29zeXN0ZW0uCgoKYGBge3J9CmxpYnJhcnkodGlkeXRleHQpCmBgYAoKKiBXaGlsZSB3ZSB3aWxsIGZvciBsYXRlciBhcHBsaWNhdGlvbnMgd2Ugd2lsbCB1c2UgZGlmZmVyZW50IGZvcm1hdHMsIHdlIGhlcmUgd2lsbCBsaW1pdCBvdXJzZWx2ZXMgdG8gd29yZCB0b2tlbiwgd2hpY2ggY2FuIGRvIG1vc3Qgb2YgdGhlIHNpbXBsZSBqb2JzLgoqIEhlcmUsIHdlIGFwcGx5IHRpZHkgcHJpbmNpcGxlcyB0byB0ZXh0LCBtYWtlIHdvcmQtdG9rZW4gcGVyIGRvY3VtZW50IG91ciB1bml0IG9mIGFuYWx5c2lzLgoqIFRoZXJlZm9yZSwgZXZlcnkgcm93IHJlcHJlZXNlbnRzIGEgd29yZCBwZXIgZG9jdW1lbnQuClRoaXMgc291bmRzIGxpa2UgYSBsb3Qgb2YgcmVkdW5kYW5jeSwgYnV0IG1ha2VzIGl0IHZlcnkgZWFzeSB0byB3b3JrIHdpdGggY29tcGFyZWQgdG8gbW9yZSBjb21wbGV6IG1hdHJpeCBhbmQgbGlzdCBmb3JtYXRzLiBIZXJlLCB3ZSBjYW4gZG8gb3VyIHVzdWFsIHN1bWFycmllcyBhbmQgdmlzdWFsaXphdGlvbnMgcHJldHR5IG11Y2ggb3V0LW9mLXRoZS1ib3guCgpgYGB7cn0KIyBUaWR5dGV4dCB3YW50cyBhIHRpYmJsZSBhcyBwb2ludCBvZiBkZXBhcnR1cmUKdGV4dF90YmwgPC0gdGliYmxlKGlkID0gMSwgdGV4dCA9IHRleHQpCmBgYAoKYGBge3J9CiMgV2Ugbm93IHVubmVzdCB0aGUgdG9rZW5zLiBOb3RpY2UgaXQgaXMgYnkgZGVmYXVsdCBkZWxldGluZyBhbGwgcHVuY3R1YXRpb24gYW5kIHRyYW5zZm9ybWluZyB0aGUgdGV4dCB0byBsb3dlciBjaGFycy4KdGV4dF90aWR5IDwtIHRleHRfdGJsICU+JSB1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHQsIHRva2VuID0gJ3dvcmRzJykKYGBgCgoqIE92ZXJhbGwsIGluIE5MUCB3ZSBhcmUgdHJ5aW5nIHRvIHJlcHJlc2VudCBtZWFuaW5nIHN0cnVjdHVyZS4gCiogVGhhdCBtZWFucyB0aGF0IHdlIHdhbnQgdG8gZm9jdXMgb24gdGhlIG1vc3QgaW1wb3J0YW50IGFuZCAibWVhbmluZy1iZWFyaW5nIGVsZW1lbnRzIiBpbiB0ZXh0LCB3aGlsZSByZWR1Y2luZyBub2lzZS4gCiogV29yZHMgc3VjaCBhcyAiYW5kIiwgImhhdmUiLCAidGhlIiBtYXkgaGF2ZSBjZW50cmFsIHN5bnRhY3RpYyBmdW5jdGlvbnMgYnV0IGFyZSBub3QgcGFydGljdWxhcmx5IGltcG9ydGFudCBmcm9tIGEgc2VtYW50aWMgcGVyc3BlY3RpdmUuCgpgYGB7cn0KIyBUaWR5dGV4dCBjb21lcyB3aXRoIGEgc3RvcHdvcmQgbGV4aWNvbgpzdG9wX3dvcmRzCmBgYAoKYGBge3J9CnRleHRfdGlkeSAlPD4lCiAgYW50aV9qb2luKHN0b3Bfd29yZHMsIGJ5ID0gJ3dvcmQnKQpgYGAKCmBgYHtyfQp0ZXh0X3RpZHkKYGBgCgoKYGBge3J9CiMgV2Ugbm93IHVubmVzdCB0aGUgdG9rZW5zLiBOb3RpY2UgaXQgaXMgYnkgZGVmYXVsdCBkZWxldGluZyBhbGwgcHVuY3R1YXRpb24gYW5kIHRyYW5zZm9ybWluZyB0aGUgdGV4dCB0byBsb3dlciBjaGFycy4Kc2VudGVuY2VzX3RpZHkgPC0gdGV4dF90YmwgJT4lIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCwgdG9rZW4gPSAnc2VudGVuY2VzJykKYGBgCgpgYGB7cn0Kc2VudGVuY2VzX3RpZHkKYGBgCgojIyBZb3VyIHR1cm4hCgohW10oaHR0cHM6Ly9tZWRpYS5naXBoeS5jb20vbWVkaWEvOXJ3RmZtQjJxSjBtRXNta2ZqL2dpcGh5LmdpZikKClRha2UgdGhlIGZvbGxvd2luZyB0ZXh0IGFuZCB0cmFuc2Zvcm0gaXQgaW50byBhIGxpc3Qgb2YgbGlzdHMgd2l0aCB3aXRoIGVhY2ggZWxlbWVudCBiZWluZyBhIHRva2VuaXplZCBzZW50ZW5jZS4gUmVtb3ZlIHN0b3B3b3JkcywgbG93ZXIgYWxsIHRva2VucyBhbmQga2VlcCBvbmx5ICgxKSBhbHBoYS1udW1lcmljIHdvcmQgdG9rZW5zLCAoMikgY2hhcmFjdGV3ciB0b2tlbnMuCgpgSeKAmXZlIGJlZW4gY2FsbGVkIG1hbnkgdGhpbmdzIGluIG15IGxpZmUsIGJ1dCBuZXZlciBhbiBvcHRpbWlzdC4gVGhhdCB3YXMgZmluZSBieSBtZS4gSSBiZWxpZXZlZCBwZXNzaW1pc3RzIGxpdmVkIGluIGEgY29uc3RhbnQgc3RhdGUgb2YgcGxlYXNhbnQgc3VycHJpc2U6IGlmIHlvdSBhbHdheXMgZXhwZWN0ZWQgdGhlIHdvcnN0LCB0aGluZ3MgZ2VuZXJhbGx5IHR1cm5lZCBvdXQgYmV0dGVyIHRoYW4geW91IGltYWdpbmVkLiBUaGUgb25seSByZWFsIHByb2JsZW0gd2l0aCBwZXNzaW1pc20sIEkgZmlndXJlZCwgd2FzIHRoYXQgdG9vIG11Y2ggb2YgaXQgY291bGQgYWNjaWRlbnRhbGx5IHR1cm4geW91IGludG8gYW4gb3B0aW1pc3QuYAoKc291cmNlOiBodHRwczovL3d3dy50aGVndWFyZGlhbi5jb20vZ2xvYmFsLzIwMTkvbm92LzIxL2dsYXNzLWhhbGYtZnVsbC1ob3ctaS1sZWFybmVkLXRvLWJlLWFuLW9wdGltaXN0LWluLWEtd2VlawoKIyBUcnVtcCBUd2VldHMgUHJvY2Vzc2luZyBtYW55IHNob3J0IHRleHRzIGFuZCBzaW1wbGUgc3RhdHMKCkFuIGludHJvZHVjdGlvbiB0byBOTFAgd291bGQgbm90IGJlIHRoZSBzYW1lIHdpdGhvdXQgRG9uYWxkJ3MgdHdlZXRzLiBMZXQncyB1c2UgdGhlc2UgdHdlZXRzIGZvciBzb21lIG1vcmUgYmFzaWMgTkxQIGFuZCBsZXQncyB0cnkgdG8gZ2F0aGVyIHNvbWUgaW5zaWdodHMuLi5tYXliZQoKIVtkb25hbGRfdHdlZXRzXShodHRwczovL2kuY2RuLmNubi5jb20vY25uL2ludGVyYWN0aXZlLzIwMTcvcG9saXRpY3MvdHJ1bXAtdHdlZXRzL21lZGlhL3RydW1wLXR3ZWV0cy1oZHItMDIuanBnKQoKTGV0J3MgdHJ5IHRvIHVzZSBzb21lIHZlcnkgc2ltcGxlIHN0YXRpc3RpY3Mgb24gdHdpdHRlciBkYXRhLCB0aGFua3MgdG8gW1RydW1wIFR3aXR0ZXIgQXJjaGl2ZV0oaHR0cDovL3d3dy50cnVtcHR3aXR0ZXJhcmNoaXZlLmNvbSkKCioqTm90ZToqKiBXZSBoZXJlIGFscmVhZHkgdXNlIHByZWNvbXBpbGVkIGRhdGEuIEhvd2V2ZXIsIHlvdSBjb3VsZCB1c2UgdGhlIFtgcnR3ZWV0YF0oaHR0cHM6Ly9naXRodWIuY29tL3JvcGVuc2NpL3J0d2VldCkgcGFja2FnZSBhbmQgaW5zdGVhZCB3b3JrIHdpdGggb3duIGRhdGEgb24gdHdlZXRzIG9mIGludGVyZXN0LgoKYGBge3J9CiMgd2Ugd2lsbCBsb2FkIHNvbWUganNvbiBmaWxlcwpsaWJyYXJ5KGpzb25saXRlKQpsaWJyYXJ5KHRpZHlqc29uKQpgYGAKCgpgYGB7cn0KIyBkb3dubG9hZCBhbmQgb3BlbiBzb21lIFRydW1wIHR3ZWV0cyBmcm9tIHRydW1wX3R3ZWV0X2RhdGFfYXJjaGl2ZQp0bXAgPC0gdGVtcGZpbGUoKQpkb3dubG9hZC5maWxlKCJodHRwczovL2dpdGh1Yi5jb20vYnBiMjcvdHJ1bXBfdHdlZXRfZGF0YV9hcmNoaXZlL3Jhdy9tYXN0ZXIvY29uZGVuc2VkXzIwMTguanNvbi56aXAiLCB0bXApCnRydW1wX3R3ZWV0cyA8LSBzdHJlYW1faW4odW56KHRtcCwgImNvbmRlbnNlZF8yMDE4Lmpzb24iKSkKYGBgCgpgYGB7cn0KdHJ1bXBfdHdlZXRzICU+JSBnbGltcHNlKCkKYGBgCgpgYGB7cn0KbGlicmFyeShsdWJyaWRhdGUpICMgRm9yIHdvcmtpbiB3aXRoIHRpbWVzCnRydW1wX3R3ZWV0cyAlPD4lCiAgbXV0YXRlKGNyZWF0ZWRfYXQgPSBwYXN0ZShzdWJzdHIoY3JlYXRlZF9hdCwyNywzMCksCiAgICAgICAgICAgICAgICAgICAgICBzdWJzdHIoY3JlYXRlZF9hdCw1LDcpLAogICAgICAgICAgICAgICAgICAgICAgc3Vic3RyKGNyZWF0ZWRfYXQsOSwxMCksCiAgICAgICAgICAgICAgICAgICAgICBzdWJzdHIoY3JlYXRlZF9hdCwxMiwyMCkpICU+JSAKICAgICAgICAgICBhc19kYXRldGltZSgpKQpgYGAKCk5vdHllOiBXZSB3aWxsIG5vdCB1c2UgdGhlIHRpbWVzIG9mIHR3ZWV0IGZvciBub3csIGJ1dCBmZWVsIGZyZWUgdG8gZGlzY292ZXIsIGFuZCBtYXliZSByZWNvbnN0cnVjdCBzb21ldGhpbmcgaW5zcGlyZWQgYnkgW1RISVMgQU1BWklORyBQQVBFUiEhIV0oaHR0cHM6Ly93d3cubmNiaS5ubG0ubmloLmdvdi9wbWMvYXJ0aWNsZXMvUE1DNzUxODExOS8pCgoKYGBge3J9CiMgTGV0cyBmaWx0ZXIgb3V0IHJldHdlZXRzCnRydW1wX3R3ZWV0cyAlPD4lCiAgZmlsdGVyKGlzX3JldHdlZXQgPT0gRkFMU0UpCmBgYAoKCmBgYHtyfQojIExFdHMgdG9rZW5pemUuIE5vdGljZSB0aGF0IHRoZXJlIGFyZSBzcGVjaWFsIHRva2VucyBmb3IgdHdlZXRzIHdoaWNoIGtlZXAgdXNlZnVsbCBzcGVjaWFsIGNoYXJhY3RlcnMKdHJ1bXBfdG9rZW4gPC10cnVtcF90d2VldHMgJT4lCiAgc2VsZWN0KGlkX3N0ciwgdGV4dCkgJT4lCiAgdW5uZXN0X3Rva2Vucyh3b3JkLCB0ZXh0LCB0b2tlbiA9ICJ0d2VldHMiKQpgYGAKCmBgYHtyfQp0cnVtcF90b2tlbiAlPD4lCiAgYW50aV9qb2luKHN0b3Bfd29yZHMsIGJ5ID0gJ3dvcmQnKQpgYGAKCgpgYGB7cn0KdHJ1bXBfdG9rZW4gJT4lIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFKSAlPiUgaGVhZCgxMDApCmBgYAoKTGV0cyBzZWUgd2hvIHRydW1wIG1lbnRpb25zCgpgYGB7cn0KdHJ1bXBfdG9rZW4gJT4lCiAgZmlsdGVyKHdvcmQgJT4lIHN0cl9kZXRlY3QoJ0AnKSkgJT4lCiAgY291bnQod29yZCwgc29ydCA9IFRSVUUpCmBgYAoKIyMgWW91ciB0dXJuCgohW2FsdCB0ZXh0XShodHRwczovL21lZGlhLmdpcGh5LmNvbS9tZWRpYS9KSVg5dDJqMFpUTjlTL2dpcGh5LmdpZikKClRoZSBsaW5rIGJlbG93IGhvbGRzIGEgZGF0YXNld3Qgd2l0aCB+MTBrICNPS0Jvb21lciB0d2VldHMgZnJvbSB0aGUgZGF5cyAxMC0yMSBOb3YgMjAxOS4KCmh0dHBzOi8vZ2l0aHViLmNvbS9TRFMtQUFVL1NEUy1tYXN0ZXIvcmF3L21hc3Rlci9NMi9kYXRhL3R3ZWV0c19ib29tZXIuemlwCgpXaGF0IHRvIGRvOiAKKiBVc2UgZWxlbWVudHMgZnJvbSB0aGUgYWJvdmUgY29kZSB0byBtYWtlIGEgbGlzdCBvZiB0aGUgbW9zdCBjb21tb24gaGFzaHRhZ3MgKHlvdSBoYXZlIHRvIGdldCB0aGUgaGFzaHRhZ3MgZnJvbSB0aGUgdGV4dCwgbm90IHVzaW5nIHRoZSBjb2x1bW4gY29udGFpbmluZyB0aGVtIGFscmVhZHkpCiogQWxzbyB0cnkgdG8gaGF2ZSBhIGxvb2sgYXQgaGFzaHRhZ3Mgb3ZlciB0aW1lOiBUYWtlIG91dCB0aGUgMTAgbW9zdCBjb21tb24gaGFzaHRhZ3MgLSBleGNsdWRpbmcgI09LQm9vbWVyIC0gYW5kIHBsb3QgdGhlaXIgb2NjdXJyZW5jZSBvdmVyIHRoZSBkYXlzIGluIHRoZSBkYXRhCgpQbGFuIG9mIGF0dGFjazoKCiogICBDb252ZXJ0IHRoZSB0aW1lc3RhbXAgaW50byBhIGRhdGV0aW1lCiogICBDYWxjdWxhdGUgdGhlIG9jY3VyZW5jZSBvZiB0aGUgc3BlY2lmaWMgaGFzaHRhZ3MgKGl0ZW50aWZpZWQgYnkgYSB0cmFpbGluZyBgI2ApIGluIHRoZSBjaG9zZW4gdGltZXNwYW4gKGhlcmU6IERheXMpCiogICBQbG90IChkYXlzIG9uIHgsIG4gb24geSkKCkdvIQoKIyBFbmRub3RlcwoKIyMjIE1haW4gcmVmZXJlbmNlCgoqIFIgZm9yIERhdGEgU2NpZW5jZSAoR3JvbGVtdW5kICYgV2lja2hhbSkKICAgKiBbQ2hhcHRlciAxNF0oaHR0cHM6Ly9yNGRzLmhhZC5jby5uei9zdHJpbmdzLmh0bWwpOiBUbyByZWZyZXNoIHNpbXBsZSBzdHJpbmcgbWFuaXB1bGF0aW9ucwoqIEp1bGlhIFNpbGdlIGFuZCBEYXZpZCBSb2JpbnNvbiAoMjAyMCkuIFRleHQgTWluaW5nIHdpdGggUjogQSBUaWR5IEFwcHJvYWNoLCBP4oCZUmVpbGx5LiBPbmxpbmUgYXZhaWxhYmxlIFtoZXJlXShodHRwczovL3d3dy50aWR5dGV4dG1pbmluZy5jb20vKQogICAqIFtDaGFwdGVyIDFdKGh0dHBzOi8vd3d3LnRpZHl0ZXh0bWluaW5nLmNvbS90aWR5dGV4dC5odG1sKTogSW50cm9kdWN0aW9uIHRvIHRoZSB0aWR5IHRleHQgZm9ybWF0CgojIyMgUGFja2FnZXMgJiBFY29zeXN0ZW0KCiogW2B0aWR5dGV4dGBdKGh0dHBzOi8vZ2l0aHViLmNvbS9qdWxpYXNpbGdlL3RpZHl0ZXh0KQoKZnVydGhlcjogCiogW2BydHdlZXRgXShodHRwczovL2dpdGh1Yi5jb20vcm9wZW5zY2kvcnR3ZWV0KTogUiBpbnRlcmZhY2UgdG8gdGhlIHR3aXR0ZXIgQVBJLgoKIyMjIFN1Z2dlc3Rpb25zIGZvciBmdXJ0aGVyIHN0dWR5CgoqIERhdGFDYW1wICghTW9zdCBjb3Vyc2VzIGhhdmUgc29tZXdoYXQgb3V0ZGF0ZWQgZWNvc3lzdGVtcykKICAgKiBbSW50cm9kdWN0aW9uIHRvIFRleHQgQW5hbHlzaXMgaW4gUl0oaHR0cHM6Ly9sZWFybi5kYXRhY2FtcC5jb20vY291cnNlcy9pbnRyb2R1Y3Rpb24tdG8tdGV4dC1hbmFseXNpcy1pbi1yKTogVGhlIGJhc2ljcyBvZiB0ZXh0IGFuYWx5c2lzIGluIFIgICAgICAKICAgKiBbSW50cm9kdWN0aW9uIHRvIE5hdHVyYWwgTGFuZ3VhZ2UgUHJvY2Vzc2luZyBpbiBSXShodHRwczovL2xlYXJuLmRhdGFjYW1wLmNvbS9jb3Vyc2VzL2ludHJvZHVjdGlvbi10by1uYXR1cmFsLWxhbmd1YWdlLXByb2Nlc3NpbmctaW4tcik6IFNvbWUgcmVmcmVzaGVyIHBsdXMgbW9yZSBhZHZhbmNlZCBhcHBsaWNhdGlvbnMgaW4gdGhlIGVuZC4gICAgICAgICAKICAKIyMjIFNlc3Npb24gSW5mbwoKYGBge3J9CnNlc3Npb25JbmZvKCkKYGBgCgoKCgoK