#Text mining and natural language processing

The goals of this week’s assignment are as follows: 1. get primary example code from chapter 2 of “Text Mining with R: A Tidy Approach” by Julia Silge and David Robinson. 2. Work with a different corpus. For this, I chose to work with a collection of works by my favorite author, Charles Dickens. I used the gutenberg project to download the text data since the data is archived. 3. We incorporated the lougharn sentiment. 4. Find most frequent words. 5. Find most important words.

Load the packges

library(tidyverse)
## Warning: package 'tidyverse' was built under R version 4.4.3
## Warning: package 'ggplot2' was built under R version 4.4.3
## Warning: package 'dplyr' was built under R version 4.4.3
library(openintro)
## Warning: package 'openintro' was built under R version 4.4.3
library(tidyverse)
library(openintro)
library(tidytext)
## Warning: package 'tidytext' was built under R version 4.4.3
library(janeaustenr)
## Warning: package 'janeaustenr' was built under R version 4.4.3
library(tidytext)
library(dplyr)
library(textdata)
## Warning: package 'textdata' was built under R version 4.4.3
library(wordcloud) 
## Warning: package 'wordcloud' was built under R version 4.4.3
library(syuzhet)  
## Warning: package 'syuzhet' was built under R version 4.4.3
library(tm) # text mining
## Warning: package 'tm' was built under R version 4.4.3
library(lexicon) # sentiment lexicons
## Warning: package 'lexicon' was built under R version 4.4.3
library(wordcloud) # visualization
library(irr) # inter-rater reliability for lexicons
library(textstem) # stemming and lemmatization (example purposes only)
## Warning: package 'textstem' was built under R version 4.4.3
## Warning: package 'koRpus.lang.en' was built under R version 4.4.3
## Warning: package 'koRpus' was built under R version 4.4.3
## Warning: package 'sylly' was built under R version 4.4.3
library(syuzhet)
library(ggplot2)
library(gutenbergr) 
## Warning: package 'gutenbergr' was built under R version 4.4.3

Download, read and combine the text Data

get_sentiments("afinn")
## # A tibble: 2,477 × 2
##    word       value
##    <chr>      <dbl>
##  1 abandon       -2
##  2 abandoned     -2
##  3 abandons      -2
##  4 abducted      -2
##  5 abduction     -2
##  6 abductions    -2
##  7 abhor         -3
##  8 abhorred      -3
##  9 abhorrent     -3
## 10 abhors        -3
## # ℹ 2,467 more rows
get_sentiments("bing")
## # A tibble: 6,786 × 2
##    word        sentiment
##    <chr>       <chr>    
##  1 2-faces     negative 
##  2 abnormal    negative 
##  3 abolish     negative 
##  4 abominable  negative 
##  5 abominably  negative 
##  6 abominate   negative 
##  7 abomination negative 
##  8 abort       negative 
##  9 aborted     negative 
## 10 aborts      negative 
## # ℹ 6,776 more rows
get_sentiments("nrc")
## # A tibble: 13,872 × 2
##    word        sentiment
##    <chr>       <chr>    
##  1 abacus      trust    
##  2 abandon     fear     
##  3 abandon     negative 
##  4 abandon     sadness  
##  5 abandoned   anger    
##  6 abandoned   fear     
##  7 abandoned   negative 
##  8 abandoned   sadness  
##  9 abandonment anger    
## 10 abandonment fear     
## # ℹ 13,862 more rows
library(janeaustenr)
library(dplyr)
library(stringr)

tidy_books <- austen_books() %>%
  group_by(book) %>%
  mutate(
    linenumber = row_number(),
    chapter = cumsum(str_detect(text, 
                                regex("^chapter [\\divxlc]", 
                                      ignore_case = TRUE)))) %>%
  ungroup() %>%
  unnest_tokens(word, text)
nrc_joy <- get_sentiments("nrc") %>% 
  filter(sentiment == "joy")

tidy_books %>%
  filter(book == "Emma") %>%
  inner_join(nrc_joy) %>%
  count(word, sort = TRUE)
## Joining with `by = join_by(word)`
## # A tibble: 301 × 2
##    word          n
##    <chr>     <int>
##  1 good        359
##  2 friend      166
##  3 hope        143
##  4 happy       125
##  5 love        117
##  6 deal         92
##  7 found        92
##  8 present      89
##  9 kind         82
## 10 happiness    76
## # ℹ 291 more rows

`

library(tidyr)

library(dplyr)
library(tidytext)
library(tidyr)

# Perform sentiment analysis using the Bing lexicon
options(dplyr.summarise.inform = FALSE)
bing <- get_sentiments("bing") %>%
  distinct(word, sentiment)
jane_austen_sentiment <- tidy_books %>%
  inner_join(bing, by = "word") %>%
  count(book, index = linenumber %/% 80, sentiment) %>%
  pivot_wider(names_from = sentiment, values_from = n, values_fill = 0) %>%
  mutate(sentiment = positive - negative)
## Warning in inner_join(., bing, by = "word"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 435434 of `x` matches multiple rows in `y`.
## ℹ Row 5051 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.
tidy_books <- tidy_books %>% distinct()

Tidy the Data

ggplot(jane_austen_sentiment, aes(index, sentiment, fill = book)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~book, ncol = 2, scales = "free_x")

pride_prejudice <- tidy_books %>% 
  filter(book == "Pride & Prejudice")

pride_prejudice
## # A tibble: 117,077 × 4
##    book              linenumber chapter word     
##    <fct>                  <int>   <int> <chr>    
##  1 Pride & Prejudice          1       0 pride    
##  2 Pride & Prejudice          1       0 and      
##  3 Pride & Prejudice          1       0 prejudice
##  4 Pride & Prejudice          3       0 by       
##  5 Pride & Prejudice          3       0 jane     
##  6 Pride & Prejudice          3       0 austen   
##  7 Pride & Prejudice          7       1 chapter  
##  8 Pride & Prejudice          7       1 1        
##  9 Pride & Prejudice         10       1 it       
## 10 Pride & Prejudice         10       1 is       
## # ℹ 117,067 more rows
afinn <- pride_prejudice %>% 
  inner_join(get_sentiments("afinn")) %>% 
  group_by(index = linenumber %/% 80) %>% 
  summarise(sentiment = sum(value)) %>% 
  mutate(method = "AFINN")
## Joining with `by = join_by(word)`
bing_and_nrc <- bind_rows(
  pride_prejudice %>% 
    inner_join(get_sentiments("bing")) %>%
    mutate(method = "Bing et al."),
  pride_prejudice %>% 
    inner_join(get_sentiments("nrc") %>% 
                 filter(sentiment %in% c("positive", 
                                         "negative"))
    ) %>%
    mutate(method = "NRC")) %>%
  count(method, index = linenumber %/% 80, sentiment) %>%
  pivot_wider(names_from = sentiment,
              values_from = n,
              values_fill = 0) %>% 
  mutate(sentiment = positive - negative)
## Joining with `by = join_by(word)`
## Joining with `by = join_by(word)`
## Warning in inner_join(., get_sentiments("nrc") %>% filter(sentiment %in% : Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 205 of `x` matches multiple rows in `y`.
## ℹ Row 5178 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.
bind_rows(afinn, 
          bing_and_nrc) %>%
  ggplot(aes(index, sentiment, fill = method)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~method, ncol = 1, scales = "free_y")

get_sentiments("nrc") %>% 
  filter(sentiment %in% c("positive", "negative")) %>% 
  count(sentiment)
## # A tibble: 2 × 2
##   sentiment     n
##   <chr>     <int>
## 1 negative   3316
## 2 positive   2308
get_sentiments("bing") %>% 
  count(sentiment)
## # A tibble: 2 × 2
##   sentiment     n
##   <chr>     <int>
## 1 negative   4781
## 2 positive   2005
#> # A tibble: 2 × 2
#>   sentiment     n
#>   <chr>     <int>
#> 1 negative   4781
#> 2 positive   2005
bing_word_counts <- tidy_books %>%
  inner_join(get_sentiments("bing")) %>%
  count(word, sentiment, sort = TRUE) %>%
  ungroup()
## Joining with `by = join_by(word)`
## Warning in inner_join(., get_sentiments("bing")): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 414333 of `x` matches multiple rows in `y`.
## ℹ Row 5051 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.
bing_word_counts
## # A tibble: 2,585 × 3
##    word     sentiment     n
##    <chr>    <chr>     <int>
##  1 miss     negative   1792
##  2 well     positive   1480
##  3 good     positive   1360
##  4 great    positive    969
##  5 like     positive    720
##  6 better   positive    630
##  7 enough   positive    605
##  8 happy    positive    526
##  9 love     positive    480
## 10 pleasure positive    460
## # ℹ 2,575 more rows
#> # A tibble: 2,585 × 3
#>    word     sentiment     n
#>    <chr>    <chr>     <int>
#>  1 miss     negative   1855
#>  2 well     positive   1523
#>  3 good     positive   1380
#>  4 great    positive    981
#>  5 like     positive    725
#>  6 better   positive    639
#>  7 enough   positive    613
#>  8 happy    positive    534
#>  9 love     positive    495
#> 10 pleasure positive    462
#> # ℹ 2,575 more rows
bing_word_counts %>%
  group_by(sentiment) %>%
  slice_max(n, n = 10) %>% 
  ungroup() %>%
  mutate(word = reorder(word, n)) %>%
  ggplot(aes(n, word, fill = sentiment)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~sentiment, scales = "free_y") +
  labs(x = "Contribution to sentiment",
       y = NULL)

custom_stop_words <- bind_rows(tibble(word = c("miss"),  
                                      lexicon = c("custom")), 
                               stop_words)

custom_stop_words
## # A tibble: 1,150 × 2
##    word        lexicon
##    <chr>       <chr>  
##  1 miss        custom 
##  2 a           SMART  
##  3 a's         SMART  
##  4 able        SMART  
##  5 about       SMART  
##  6 above       SMART  
##  7 according   SMART  
##  8 accordingly SMART  
##  9 across      SMART  
## 10 actually    SMART  
## # ℹ 1,140 more rows
library(wordcloud)

tidy_books %>%
  anti_join(stop_words) %>%
  count(word) %>%
  with(wordcloud(word, n, max.words = 100))
## Joining with `by = join_by(word)`

library(reshape2)
## Warning: package 'reshape2' was built under R version 4.4.3
## 
## Attaching package: 'reshape2'
## The following object is masked from 'package:openintro':
## 
##     tips
## The following object is masked from 'package:tidyr':
## 
##     smiths
tidy_books %>%
  inner_join(get_sentiments("bing")) %>%
  count(word, sentiment, sort = TRUE) %>%
  acast(word ~ sentiment, value.var = "n", fill = 0) %>%
  comparison.cloud(colors = c("gray20", "gray80"),
                   max.words = 100)
## Joining with `by = join_by(word)`
## Warning in inner_join(., get_sentiments("bing")): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 414333 of `x` matches multiple rows in `y`.
## ℹ Row 5051 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.

p_and_p_sentences <- tibble(text = prideprejudice) %>% 
  unnest_tokens(sentence, text, token = "sentences")
austen_chapters <- austen_books() %>%
  group_by(book) %>%
  unnest_tokens(chapter, text, token = "regex", 
                pattern = "Chapter|CHAPTER [\\dIVXLC]") %>%
  ungroup()

austen_chapters %>% 
  group_by(book) %>% 
  summarise(chapters = n())
## # A tibble: 6 × 2
##   book                chapters
##   <fct>                  <int>
## 1 Sense & Sensibility       51
## 2 Pride & Prejudice         62
## 3 Mansfield Park            49
## 4 Emma                      56
## 5 Northanger Abbey          32
## 6 Persuasion                25

Plotting frequnecy with ggplot2

bingnegative <- get_sentiments("bing") %>% 
  filter(sentiment == "negative")

wordcounts <- tidy_books %>%
  group_by(book, chapter) %>%
  summarize(words = n())

tidy_books %>%
  semi_join(bingnegative) %>%
  group_by(book, chapter) %>%
  summarize(negativewords = n()) %>%
  left_join(wordcounts, by = c("book", "chapter")) %>%
  mutate(ratio = negativewords/words) %>%
  filter(chapter != 0) %>%
  slice_max(ratio, n = 1) %>% 
  ungroup()
## Joining with `by = join_by(word)`
## # A tibble: 6 × 5
##   book                chapter negativewords words  ratio
##   <fct>                 <int>         <int> <int>  <dbl>
## 1 Sense & Sensibility      43           161  3247 0.0496
## 2 Pride & Prejudice        34           110  1998 0.0551
## 3 Mansfield Park           46           171  3463 0.0494
## 4 Emma                     16            83  1804 0.0460
## 5 Northanger Abbey         21           149  2827 0.0527
## 6 Persuasion               24            54  1497 0.0361
# Load Loughran-McDonald sentiment lexicon
library(tidytext)
loughran_sentiments <- get_sentiments("loughran")

# Count word frequencies by sentiment using Loughran-McDonald lexicon
loughran_word_counts <- pride_prejudice %>% 
  inner_join(loughran_sentiments, by = "word") %>%  # Join by 'word'
  count(word, sentiment, sort = TRUE) %>% 
  ungroup()
## Warning in inner_join(., loughran_sentiments, by = "word"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 3 of `x` matches multiple rows in `y`.
## ℹ Row 2826 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.
# Perform sentiment analysis by binning the text into groups of 80 lines
loughran <- pride_prejudice %>% 
  inner_join(loughran_sentiments, by = "word") %>%  # Ensure proper join on 'word'
  group_by(index = linenumber %/% 80) %>%  # Group by bin of 80 lines
  summarise(sentiment = sum(as.numeric(sentiment), na.rm = TRUE)) %>%  # Sum sentiment
  mutate(method = "Loughran")  # Add method label
## Warning in inner_join(., loughran_sentiments, by = "word"): Detected an unexpected many-to-many relationship between `x` and `y`.
## ℹ Row 3 of `x` matches multiple rows in `y`.
## ℹ Row 2826 of `y` matches multiple rows in `x`.
## ℹ If a many-to-many relationship is expected, set `relationship =
##   "many-to-many"` to silence this warning.
## Warning: There were 163 warnings in `summarise()`.
## The first warning was:
## ℹ In argument: `sentiment = sum(as.numeric(sentiment), na.rm = TRUE)`.
## ℹ In group 1: `index = 0`.
## Caused by warning:
## ! NAs introduced by coercion
## ℹ Run `dplyr::last_dplyr_warnings()` to see the 162 remaining warnings.
# Combine different sentiment analysis methods including Loughran-McDonald lexicon
bind_rows(afinn, 
          bing_and_nrc,
          loughran) %>%  
  ggplot(aes(index, sentiment, fill = method)) +
  geom_col(show.legend = FALSE) +
  facet_wrap(~method, ncol = 1, scales = "free_y")

title: “Lab Name” author: “Author Name” date: “2025-04-06” output: openintro::lab_report — ### #Text mining and natural language processing

The goals of this week’s assignment are as follows: 1. get primary example code from chapter 2 of “Text Mining with R: A Tidy Approach” by Julia Silge and David Robinson. 2. Work with a different corpus. For this, I chose to work with a collection of works by my favorite author, Charles Dickens. I used the gutenberg project to download the text data since the data is archived. 3. We incorporated the lougharn sentiment. 4. Find most frequent words. 5. Find most important words.

Download, read and combine the text Data

dickens_book1 <- readLines("https://raw.githubusercontent.com/tanzil64/Data-607-Assignment-10/refs/heads/main/A%20Christmas%20Carol.txt")
# View the first few lines of the downloaded book
# Assuming you have already loaded the text file

# View the first few lines
head(dickens_book1)
## [1] "The Project Gutenberg eBook of A Christmas Carol in Prose; Being a Ghost Story of Christmas"
## [2] "    "                                                                                       
## [3] "This ebook is for the use of anyone anywhere in the United States and"                      
## [4] "most other parts of the world at no cost and with almost no restrictions"                   
## [5] "whatsoever. You may copy it, give it away or re-use it under the terms"                     
## [6] "of the Project Gutenberg License included with this ebook or online"
dickens_book2 <- readLines("https://raw.githubusercontent.com/tanzil64/Data-607-Assignment-10/refs/heads/main/Great%20Expectations.txt")
# View the first few lines of the downloaded book
# Assuming you have already loaded the text file

# View the first few lines
head(dickens_book2)
## [1] "The Project Gutenberg eBook of Great Expectations"                       
## [2] "    "                                                                    
## [3] "This ebook is for the use of anyone anywhere in the United States and"   
## [4] "most other parts of the world at no cost and with almost no restrictions"
## [5] "whatsoever. You may copy it, give it away or re-use it under the terms"  
## [6] "of the Project Gutenberg License included with this ebook or online"
# Assuming the character vectors for book texts are properly loaded as single strings:
# `dickens_book1` and `dickens_book2` contain the full texts of each book.

# Convert `dickens_book1` and `dickens_book2` into data frames/tibbles with the correct structure
dickens_book1 <- tibble(book = "A Christmas Carol", text = paste(dickens_book1, collapse = " "))
dickens_book2 <- tibble(book = "Great Expectations", text = paste(dickens_book2, collapse = " "))

# Combine the two books into one tibble
dickens_works <- bind_rows(dickens_book1, dickens_book2)

# Check the combined tibble
glimpse(dickens_works)
## Rows: 2
## Columns: 2
## $ book <chr> "A Christmas Carol", "Great Expectations"
## $ text <chr> "The Project Gutenberg eBook of A Christmas Carol in Prose; Being…

`

Tidy the Data

tidy_dickens <- dickens_works %>%
  unnest_tokens(word, text) %>%       # Tokenize into words
  anti_join(stop_words, by = "word") 

Counting the words

dickens_frequency <- tidy_dickens %>%
  count(word)

Plotting frequnecy with ggplot2

library(dplyr)
library(ggplot2)

# Filter the top 7 words with the highest frequencies
dickens_top_words <- dickens_frequency %>%
  filter(n > 5) %>%
  slice_max(n, n = 7)

# Plot the top 7 words
ggplot(dickens_top_words, aes(x = reorder(word, n), y = n)) + 
  geom_col() +
  xlab("Words") +
  ylab("Frequency") +
  theme(axis.text.x = element_text(angle = 45, hjust = 1))  # Rotate x-axis labels for better readability

Get Sentiments

get_sentiments("afinn")
## # A tibble: 2,477 × 2
##    word       value
##    <chr>      <dbl>
##  1 abandon       -2
##  2 abandoned     -2
##  3 abandons      -2
##  4 abducted      -2
##  5 abduction     -2
##  6 abductions    -2
##  7 abhor         -3
##  8 abhorred      -3
##  9 abhorrent     -3
## 10 abhors        -3
## # ℹ 2,467 more rows
get_sentiments("bing")
## # A tibble: 6,786 × 2
##    word        sentiment
##    <chr>       <chr>    
##  1 2-faces     negative 
##  2 abnormal    negative 
##  3 abolish     negative 
##  4 abominable  negative 
##  5 abominably  negative 
##  6 abominate   negative 
##  7 abomination negative 
##  8 abort       negative 
##  9 aborted     negative 
## 10 aborts      negative 
## # ℹ 6,776 more rows
get_sentiments("nrc")
## # A tibble: 13,872 × 2
##    word        sentiment
##    <chr>       <chr>    
##  1 abacus      trust    
##  2 abandon     fear     
##  3 abandon     negative 
##  4 abandon     sadness  
##  5 abandoned   anger    
##  6 abandoned   fear     
##  7 abandoned   negative 
##  8 abandoned   sadness  
##  9 abandonment anger    
## 10 abandonment fear     
## # ℹ 13,862 more rows
nrc_joy <- get_sentiments("nrc") %>%
  filter(sentiment == "joy")
head(nrc_joy)
## # A tibble: 6 × 2
##   word          sentiment
##   <chr>         <chr>    
## 1 absolution    joy      
## 2 abundance     joy      
## 3 abundant      joy      
## 4 accolade      joy      
## 5 accompaniment joy      
## 6 accomplish    joy

Here I filtered out all of the words related to positive feelings from the nrc lexicon.

Next, I need to find the joyful words in Charles Dickens works.

nrc joy Here we take all of the words that are related to joy from Dickens works.

nrc_joy_dickens <- tidy_dickens %>%
  inner_join(get_sentiments("nrc") %>% filter(sentiment == "joy"), by = "word") %>%
  count(word, sort = TRUE)
head(nrc_joy_dickens)
## # A tibble: 6 × 2
##   word       n
##   <chr>  <int>
## 1 found    160
## 2 hope     106
## 3 money     94
## 4 child     78
## 5 friend    71
## 6 love      68

#affrin joy This will pull out the words from Dickens works that afrinn relate as positive.

affin_joy <- get_sentiments("afinn") %>%
  filter(value > 0)


dickens_afinn_joy <- tidy_dickens %>%
  inner_join(affin_joy, by = "word") %>%
  count(word, sort = TRUE)

#Bing joy Here we find positive Dickens words using the lexicon Bing.

bing_joy <- get_sentiments("bing")%>%
  filter(sentiment == "positive")

dickens_bing_joy <- tidy_dickens %>%
  inner_join(bing_joy, by = "word") %>%
  count(word, sort = TRUE)

#nrc Joy Here we will use a lexicon not used in the examples of in the book.

nrc_joy <- get_sentiments("nrc")%>%
  filter(sentiment == "positive")

dickens_nrc_joy <- tidy_dickens %>%
  inner_join(bing_joy, by = "word") %>%
  count(word, sort = TRUE)

#Loughran Joy Here we will use a lexicon not used in the examples of in the book.

loughran_joy <- get_sentiments("loughran")%>%
  filter(sentiment == "positive")

dickens_loughran_joy <- tidy_dickens %>%
  inner_join(loughran_joy, by = "word")%>%
  count(word, sort = TRUE)

#Graphing the top words This is great way to compare how the sentiments work

library(ggplot2)
library(dplyr)

# Keep only the top 10 words for each sentiment
graph_dickens_afinn_top10 <- dickens_afinn_joy %>%
  top_n(10, n)

graph_dickens_bing_top10 <- dickens_afinn_joy %>%
  top_n(10, n)

graph_dickens_loughran_top10 <- dickens_afinn_joy %>%
  top_n(10, n)

graph_dickens_nrc_top10 <- dickens_afinn_joy %>%
  top_n(10, n)

# Plot the graph with the top 10 words
ggplot() +
  geom_point(data = graph_dickens_afinn_top10, aes(x = word, y = n), color = "red") +
  geom_line(data = graph_dickens_afinn_top10, aes(x = word, y = n, group = 1), color = "red") +
  
  geom_point(data = graph_dickens_bing_top10, aes(x = word, y = n), color = "black") +
  geom_line(data = graph_dickens_bing_top10, aes(x = word, y = n, group = 1), color = "black") +
  
  geom_point(data = graph_dickens_loughran_top10, aes(x = word, y = n), color = "green") +
  geom_line(data = graph_dickens_loughran_top10, aes(x = word, y = n, group = 1), color = "green") +
  
  geom_point(data = graph_dickens_nrc_top10, aes(x = word, y = n), color = "blue") +
  geom_line(data = graph_dickens_nrc_top10, aes(x = word, y = n, group = 1), color = "blue") +
  
  theme_minimal() +
  labs(title = "Top 10 Words by Sentiment Score (Outliers Removed)",
       x = "Words",
       y = "Frequency") +
  theme(axis.text.x = element_text(angle = 90, hjust = 1))

#now to pull out word frequencies per book

book_words <- tidy_dickens %>%
  count(book, word, sort = TRUE)

# Step 2: Calculate total number of words per book
total_words <- book_words %>%
  group_by(book) %>%
  summarize(total = sum(n), .groups = "drop")

# Step 3: Join total word count into the main data
book_words <- left_join(book_words, total_words, by = "book")

# View result
head(book_words)
## # A tibble: 6 × 4
##   book               word        n total
##   <chr>              <chr>   <int> <int>
## 1 Great Expectations joe       692 58721
## 2 Great Expectations miss      383 58721
## 3 Great Expectations time      373 58721
## 4 Great Expectations pip       326 58721
## 5 Great Expectations looked    325 58721
## 6 A Christmas Carol  scrooge   314 11259

#To find which words are the most important we use the tf_idf.

freq_by_rank <- book_words %>%
  group_by(book) %>%
  mutate(rank = row_number(),
         `term frequency` = n/total)%>%
  ungroup()
freq_by_rank
## # A tibble: 14,918 × 6
##    book               word        n total  rank `term frequency`
##    <chr>              <chr>   <int> <int> <int>            <dbl>
##  1 Great Expectations joe       692 58721     1          0.0118 
##  2 Great Expectations miss      383 58721     2          0.00652
##  3 Great Expectations time      373 58721     3          0.00635
##  4 Great Expectations pip       326 58721     4          0.00555
##  5 Great Expectations looked    325 58721     5          0.00553
##  6 A Christmas Carol  scrooge   314 11259     1          0.0279 
##  7 Great Expectations herbert   290 58721     6          0.00494
##  8 Great Expectations don’t     285 58721     7          0.00485
##  9 Great Expectations hand      270 58721     8          0.00460
## 10 Great Expectations wemmick   256 58721     9          0.00436
## # ℹ 14,908 more rows
book_tf_idf <- book_words %>%
  bind_tf_idf(word, book, n)
book_tf_idf %>%
  arrange(desc(tf_idf))
## # A tibble: 14,918 × 7
##    book               word          n total      tf   idf  tf_idf
##    <chr>              <chr>     <int> <int>   <dbl> <dbl>   <dbl>
##  1 A Christmas Carol  scrooge     314 11259 0.0279  0.693 0.0193 
##  2 Great Expectations pip         326 58721 0.00555 0.693 0.00385
##  3 Great Expectations herbert     290 58721 0.00494 0.693 0.00342
##  4 Great Expectations don’t       285 58721 0.00485 0.693 0.00336
##  5 Great Expectations wemmick     256 58721 0.00436 0.693 0.00302
##  6 A Christmas Carol  bob          49 11259 0.00435 0.693 0.00302
##  7 A Christmas Carol  scrooge's    48 11259 0.00426 0.693 0.00296
##  8 Great Expectations havisham    243 58721 0.00414 0.693 0.00287
##  9 Great Expectations estella     237 58721 0.00404 0.693 0.00280
## 10 Great Expectations biddy       228 58721 0.00388 0.693 0.00269
## # ℹ 14,908 more rows
# Step 1: Count words per book
word_counts <- tidy_dickens %>%
  count(book, word, sort = TRUE)

# Step 2: Apply tf-idf
dickens_tf_idf <- word_counts %>%
  bind_tf_idf(word, book, n) %>%
  arrange(desc(tf_idf))

# Step 3: View top distinctive words
head(dickens_tf_idf, 10)
## # A tibble: 10 × 6
##    book               word          n      tf   idf  tf_idf
##    <chr>              <chr>     <int>   <dbl> <dbl>   <dbl>
##  1 A Christmas Carol  scrooge     314 0.0279  0.693 0.0193 
##  2 Great Expectations pip         326 0.00555 0.693 0.00385
##  3 Great Expectations herbert     290 0.00494 0.693 0.00342
##  4 Great Expectations don’t       285 0.00485 0.693 0.00336
##  5 Great Expectations wemmick     256 0.00436 0.693 0.00302
##  6 A Christmas Carol  bob          49 0.00435 0.693 0.00302
##  7 A Christmas Carol  scrooge's    48 0.00426 0.693 0.00296
##  8 Great Expectations havisham    243 0.00414 0.693 0.00287
##  9 Great Expectations estella     237 0.00404 0.693 0.00280
## 10 Great Expectations biddy       228 0.00388 0.693 0.00269

#Conclusions

In conclusion, the same words that are most used happen to also be considered to be the most important words. I wonder how this would change if Project Gutenberg had more of Kurt vonnegut’s books. I imagine that the characters wouldn’t be the top words in regards to frequency or importance.

LS0tDQp0aXRsZTogIkRhdGEgNjA3IEFzc2lnbm1lbnQgMTAiDQphdXRob3I6ICJNZC4gVGFuemlsIEVoc2FuIg0KZGF0ZTogImByIFN5cy5EYXRlKClgIg0Kb3V0cHV0OiBvcGVuaW50cm86OmxhYl9yZXBvcnQNCi0tLQ0KIyMjICNUZXh0IG1pbmluZyBhbmQgbmF0dXJhbCBsYW5ndWFnZSBwcm9jZXNzaW5nDQoNClRoZSBnb2FscyBvZiB0aGlzIHdlZWvigJlzIGFzc2lnbm1lbnQgYXJlIGFzIGZvbGxvd3M6IDEuIGdldCBwcmltYXJ5IGV4YW1wbGUgY29kZSBmcm9tIGNoYXB0ZXIgMiBvZiDigJxUZXh0IE1pbmluZyB3aXRoIFI6IEEgVGlkeSBBcHByb2FjaOKAnSBieSBKdWxpYSBTaWxnZSBhbmQgRGF2aWQgUm9iaW5zb24uDQoyLiBXb3JrIHdpdGggYSBkaWZmZXJlbnQgY29ycHVzLiBGb3IgdGhpcywgSSBjaG9zZSB0byB3b3JrIHdpdGggYSBjb2xsZWN0aW9uIG9mIHdvcmtzIGJ5IG15IGZhdm9yaXRlIGF1dGhvciwgQ2hhcmxlcyBEaWNrZW5zLiBJIHVzZWQgdGhlIGd1dGVuYmVyZyBwcm9qZWN0IHRvIGRvd25sb2FkIHRoZSAgdGV4dCBkYXRhIHNpbmNlIHRoZSBkYXRhIGlzIGFyY2hpdmVkLg0KMy4gV2UgaW5jb3Jwb3JhdGVkIHRoZSBsb3VnaGFybiBzZW50aW1lbnQuDQo0LiBGaW5kIG1vc3QgZnJlcXVlbnQgd29yZHMuDQo1LiBGaW5kIG1vc3QgaW1wb3J0YW50IHdvcmRzLg0KDQoNCiMjIyBMb2FkIHRoZSBwYWNrZ2VzDQpgYGB7ciBsb2FkLXBhY2thZ2VzLCBtZXNzYWdlPUZBTFNFfQ0KbGlicmFyeSh0aWR5dmVyc2UpDQpsaWJyYXJ5KG9wZW5pbnRybykNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShvcGVuaW50cm8pDQpsaWJyYXJ5KHRpZHl0ZXh0KQ0KbGlicmFyeShqYW5lYXVzdGVucikNCmxpYnJhcnkodGlkeXRleHQpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeSh0ZXh0ZGF0YSkNCmxpYnJhcnkod29yZGNsb3VkKSANCmxpYnJhcnkoc3l1emhldCkgIA0KbGlicmFyeSh0bSkgIyB0ZXh0IG1pbmluZw0KbGlicmFyeShsZXhpY29uKSAjIHNlbnRpbWVudCBsZXhpY29ucw0KbGlicmFyeSh3b3JkY2xvdWQpICMgdmlzdWFsaXphdGlvbg0KbGlicmFyeShpcnIpICMgaW50ZXItcmF0ZXIgcmVsaWFiaWxpdHkgZm9yIGxleGljb25zDQpsaWJyYXJ5KHRleHRzdGVtKSAjIHN0ZW1taW5nIGFuZCBsZW1tYXRpemF0aW9uIChleGFtcGxlIHB1cnBvc2VzIG9ubHkpDQpsaWJyYXJ5KHN5dXpoZXQpDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGd1dGVuYmVyZ3IpIA0KYGBgDQoNCiMjIyBEb3dubG9hZCwgcmVhZCBhbmQgY29tYmluZSAgdGhlIHRleHQgRGF0YQ0KDQoNCg0KDQpgYGB7cn0NCmdldF9zZW50aW1lbnRzKCJhZmlubiIpDQpnZXRfc2VudGltZW50cygiYmluZyIpDQpnZXRfc2VudGltZW50cygibnJjIikNCmBgYA0KDQoNCg0KDQpgYGB7cn0NCg0KbGlicmFyeShqYW5lYXVzdGVucikNCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KHN0cmluZ3IpDQoNCnRpZHlfYm9va3MgPC0gYXVzdGVuX2Jvb2tzKCkgJT4lDQogIGdyb3VwX2J5KGJvb2spICU+JQ0KICBtdXRhdGUoDQogICAgbGluZW51bWJlciA9IHJvd19udW1iZXIoKSwNCiAgICBjaGFwdGVyID0gY3Vtc3VtKHN0cl9kZXRlY3QodGV4dCwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlZ2V4KCJeY2hhcHRlciBbXFxkaXZ4bGNdIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlnbm9yZV9jYXNlID0gVFJVRSkpKSkgJT4lDQogIHVuZ3JvdXAoKSAlPiUNCiAgdW5uZXN0X3Rva2Vucyh3b3JkLCB0ZXh0KQ0KYGBgDQoNCg0KDQoNCmBgYHtyfQ0KbnJjX2pveSA8LSBnZXRfc2VudGltZW50cygibnJjIikgJT4lIA0KICBmaWx0ZXIoc2VudGltZW50ID09ICJqb3kiKQ0KDQp0aWR5X2Jvb2tzICU+JQ0KICBmaWx0ZXIoYm9vayA9PSAiRW1tYSIpICU+JQ0KICBpbm5lcl9qb2luKG5yY19qb3kpICU+JQ0KICBjb3VudCh3b3JkLCBzb3J0ID0gVFJVRSkNCg0KDQpgYGANCmANCg0KDQoNCmBgYHtyfQ0KbGlicmFyeSh0aWR5cikNCg0KbGlicmFyeShkcGx5cikNCmxpYnJhcnkodGlkeXRleHQpDQpsaWJyYXJ5KHRpZHlyKQ0KDQojIFBlcmZvcm0gc2VudGltZW50IGFuYWx5c2lzIHVzaW5nIHRoZSBCaW5nIGxleGljb24NCm9wdGlvbnMoZHBseXIuc3VtbWFyaXNlLmluZm9ybSA9IEZBTFNFKQ0KYmluZyA8LSBnZXRfc2VudGltZW50cygiYmluZyIpICU+JQ0KICBkaXN0aW5jdCh3b3JkLCBzZW50aW1lbnQpDQpqYW5lX2F1c3Rlbl9zZW50aW1lbnQgPC0gdGlkeV9ib29rcyAlPiUNCiAgaW5uZXJfam9pbihiaW5nLCBieSA9ICJ3b3JkIikgJT4lDQogIGNvdW50KGJvb2ssIGluZGV4ID0gbGluZW51bWJlciAlLyUgODAsIHNlbnRpbWVudCkgJT4lDQogIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBzZW50aW1lbnQsIHZhbHVlc19mcm9tID0gbiwgdmFsdWVzX2ZpbGwgPSAwKSAlPiUNCiAgbXV0YXRlKHNlbnRpbWVudCA9IHBvc2l0aXZlIC0gbmVnYXRpdmUpDQp0aWR5X2Jvb2tzIDwtIHRpZHlfYm9va3MgJT4lIGRpc3RpbmN0KCkNCg0KYGBgDQoNCg0KDQoNCiMjIyBUaWR5IHRoZSBEYXRhDQoNCmBgYHtyfQ0KZ2dwbG90KGphbmVfYXVzdGVuX3NlbnRpbWVudCwgYWVzKGluZGV4LCBzZW50aW1lbnQsIGZpbGwgPSBib29rKSkgKw0KICBnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArDQogIGZhY2V0X3dyYXAofmJvb2ssIG5jb2wgPSAyLCBzY2FsZXMgPSAiZnJlZV94IikNCmBgYA0KDQpgYGB7cn0NCnByaWRlX3ByZWp1ZGljZSA8LSB0aWR5X2Jvb2tzICU+JSANCiAgZmlsdGVyKGJvb2sgPT0gIlByaWRlICYgUHJlanVkaWNlIikNCg0KcHJpZGVfcHJlanVkaWNlDQoNCmBgYA0KDQoNCmBgYHtyfQ0KYWZpbm4gPC0gcHJpZGVfcHJlanVkaWNlICU+JSANCiAgaW5uZXJfam9pbihnZXRfc2VudGltZW50cygiYWZpbm4iKSkgJT4lIA0KICBncm91cF9ieShpbmRleCA9IGxpbmVudW1iZXIgJS8lIDgwKSAlPiUgDQogIHN1bW1hcmlzZShzZW50aW1lbnQgPSBzdW0odmFsdWUpKSAlPiUgDQogIG11dGF0ZShtZXRob2QgPSAiQUZJTk4iKQ0KDQpiaW5nX2FuZF9ucmMgPC0gYmluZF9yb3dzKA0KICBwcmlkZV9wcmVqdWRpY2UgJT4lIA0KICAgIGlubmVyX2pvaW4oZ2V0X3NlbnRpbWVudHMoImJpbmciKSkgJT4lDQogICAgbXV0YXRlKG1ldGhvZCA9ICJCaW5nIGV0IGFsLiIpLA0KICBwcmlkZV9wcmVqdWRpY2UgJT4lIA0KICAgIGlubmVyX2pvaW4oZ2V0X3NlbnRpbWVudHMoIm5yYyIpICU+JSANCiAgICAgICAgICAgICAgICAgZmlsdGVyKHNlbnRpbWVudCAlaW4lIGMoInBvc2l0aXZlIiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJuZWdhdGl2ZSIpKQ0KICAgICkgJT4lDQogICAgbXV0YXRlKG1ldGhvZCA9ICJOUkMiKSkgJT4lDQogIGNvdW50KG1ldGhvZCwgaW5kZXggPSBsaW5lbnVtYmVyICUvJSA4MCwgc2VudGltZW50KSAlPiUNCiAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IHNlbnRpbWVudCwNCiAgICAgICAgICAgICAgdmFsdWVzX2Zyb20gPSBuLA0KICAgICAgICAgICAgICB2YWx1ZXNfZmlsbCA9IDApICU+JSANCiAgbXV0YXRlKHNlbnRpbWVudCA9IHBvc2l0aXZlIC0gbmVnYXRpdmUpDQpgYGANCmBgYHtyfQ0KYmluZF9yb3dzKGFmaW5uLCANCiAgICAgICAgICBiaW5nX2FuZF9ucmMpICU+JQ0KICBnZ3Bsb3QoYWVzKGluZGV4LCBzZW50aW1lbnQsIGZpbGwgPSBtZXRob2QpKSArDQogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsNCiAgZmFjZXRfd3JhcCh+bWV0aG9kLCBuY29sID0gMSwgc2NhbGVzID0gImZyZWVfeSIpDQpgYGANCmBgYHtyfQ0KZ2V0X3NlbnRpbWVudHMoIm5yYyIpICU+JSANCiAgZmlsdGVyKHNlbnRpbWVudCAlaW4lIGMoInBvc2l0aXZlIiwgIm5lZ2F0aXZlIikpICU+JSANCiAgY291bnQoc2VudGltZW50KQ0KYGBgDQoNCg0KYGBge3J9DQpnZXRfc2VudGltZW50cygiYmluZyIpICU+JSANCiAgY291bnQoc2VudGltZW50KQ0KIz4gIyBBIHRpYmJsZTogMiDDlyAyDQojPiAgIHNlbnRpbWVudCAgICAgbg0KIz4gICA8Y2hyPiAgICAgPGludD4NCiM+IDEgbmVnYXRpdmUgICA0NzgxDQojPiAyIHBvc2l0aXZlICAgMjAwNQ0KYGBgDQoNCmBgYHtyfQ0KYmluZ193b3JkX2NvdW50cyA8LSB0aWR5X2Jvb2tzICU+JQ0KICBpbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJiaW5nIikpICU+JQ0KICBjb3VudCh3b3JkLCBzZW50aW1lbnQsIHNvcnQgPSBUUlVFKSAlPiUNCiAgdW5ncm91cCgpDQoNCmJpbmdfd29yZF9jb3VudHMNCiM+ICMgQSB0aWJibGU6IDIsNTg1IMOXIDMNCiM+ICAgIHdvcmQgICAgIHNlbnRpbWVudCAgICAgbg0KIz4gICAgPGNocj4gICAgPGNocj4gICAgIDxpbnQ+DQojPiAgMSBtaXNzICAgICBuZWdhdGl2ZSAgIDE4NTUNCiM+ICAyIHdlbGwgICAgIHBvc2l0aXZlICAgMTUyMw0KIz4gIDMgZ29vZCAgICAgcG9zaXRpdmUgICAxMzgwDQojPiAgNCBncmVhdCAgICBwb3NpdGl2ZSAgICA5ODENCiM+ICA1IGxpa2UgICAgIHBvc2l0aXZlICAgIDcyNQ0KIz4gIDYgYmV0dGVyICAgcG9zaXRpdmUgICAgNjM5DQojPiAgNyBlbm91Z2ggICBwb3NpdGl2ZSAgICA2MTMNCiM+ICA4IGhhcHB5ICAgIHBvc2l0aXZlICAgIDUzNA0KIz4gIDkgbG92ZSAgICAgcG9zaXRpdmUgICAgNDk1DQojPiAxMCBwbGVhc3VyZSBwb3NpdGl2ZSAgICA0NjINCiM+ICMg4oS5IDIsNTc1IG1vcmUgcm93cw0KYGBgDQoNCg0KDQpgYGB7cn0NCmJpbmdfd29yZF9jb3VudHMgJT4lDQogIGdyb3VwX2J5KHNlbnRpbWVudCkgJT4lDQogIHNsaWNlX21heChuLCBuID0gMTApICU+JSANCiAgdW5ncm91cCgpICU+JQ0KICBtdXRhdGUod29yZCA9IHJlb3JkZXIod29yZCwgbikpICU+JQ0KICBnZ3Bsb3QoYWVzKG4sIHdvcmQsIGZpbGwgPSBzZW50aW1lbnQpKSArDQogIGdlb21fY29sKHNob3cubGVnZW5kID0gRkFMU0UpICsNCiAgZmFjZXRfd3JhcCh+c2VudGltZW50LCBzY2FsZXMgPSAiZnJlZV95IikgKw0KICBsYWJzKHggPSAiQ29udHJpYnV0aW9uIHRvIHNlbnRpbWVudCIsDQogICAgICAgeSA9IE5VTEwpDQpgYGANCg0KDQpgYGB7cn0NCmN1c3RvbV9zdG9wX3dvcmRzIDwtIGJpbmRfcm93cyh0aWJibGUod29yZCA9IGMoIm1pc3MiKSwgIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBsZXhpY29uID0gYygiY3VzdG9tIikpLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdG9wX3dvcmRzKQ0KDQpjdXN0b21fc3RvcF93b3Jkcw0KYGBgDQoNCg0KYGBge3J9DQpsaWJyYXJ5KHdvcmRjbG91ZCkNCg0KdGlkeV9ib29rcyAlPiUNCiAgYW50aV9qb2luKHN0b3Bfd29yZHMpICU+JQ0KICBjb3VudCh3b3JkKSAlPiUNCiAgd2l0aCh3b3JkY2xvdWQod29yZCwgbiwgbWF4LndvcmRzID0gMTAwKSkNCmBgYA0KDQoNCg0KYGBge3J9DQpsaWJyYXJ5KHJlc2hhcGUyKQ0KDQp0aWR5X2Jvb2tzICU+JQ0KICBpbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJiaW5nIikpICU+JQ0KICBjb3VudCh3b3JkLCBzZW50aW1lbnQsIHNvcnQgPSBUUlVFKSAlPiUNCiAgYWNhc3Qod29yZCB+IHNlbnRpbWVudCwgdmFsdWUudmFyID0gIm4iLCBmaWxsID0gMCkgJT4lDQogIGNvbXBhcmlzb24uY2xvdWQoY29sb3JzID0gYygiZ3JheTIwIiwgImdyYXk4MCIpLA0KICAgICAgICAgICAgICAgICAgIG1heC53b3JkcyA9IDEwMCkNCmBgYA0KDQoNCg0KYGBge3J9DQpwX2FuZF9wX3NlbnRlbmNlcyA8LSB0aWJibGUodGV4dCA9IHByaWRlcHJlanVkaWNlKSAlPiUgDQogIHVubmVzdF90b2tlbnMoc2VudGVuY2UsIHRleHQsIHRva2VuID0gInNlbnRlbmNlcyIpDQpgYGANCg0KYGBge3J9DQphdXN0ZW5fY2hhcHRlcnMgPC0gYXVzdGVuX2Jvb2tzKCkgJT4lDQogIGdyb3VwX2J5KGJvb2spICU+JQ0KICB1bm5lc3RfdG9rZW5zKGNoYXB0ZXIsIHRleHQsIHRva2VuID0gInJlZ2V4IiwgDQogICAgICAgICAgICAgICAgcGF0dGVybiA9ICJDaGFwdGVyfENIQVBURVIgW1xcZElWWExDXSIpICU+JQ0KICB1bmdyb3VwKCkNCg0KYXVzdGVuX2NoYXB0ZXJzICU+JSANCiAgZ3JvdXBfYnkoYm9vaykgJT4lIA0KICBzdW1tYXJpc2UoY2hhcHRlcnMgPSBuKCkpDQpgYGANCg0KIyMjIFBsb3R0aW5nIGZyZXF1bmVjeSB3aXRoIGdncGxvdDINCmBgYHtyfQ0KYmluZ25lZ2F0aXZlIDwtIGdldF9zZW50aW1lbnRzKCJiaW5nIikgJT4lIA0KICBmaWx0ZXIoc2VudGltZW50ID09ICJuZWdhdGl2ZSIpDQoNCndvcmRjb3VudHMgPC0gdGlkeV9ib29rcyAlPiUNCiAgZ3JvdXBfYnkoYm9vaywgY2hhcHRlcikgJT4lDQogIHN1bW1hcml6ZSh3b3JkcyA9IG4oKSkNCg0KdGlkeV9ib29rcyAlPiUNCiAgc2VtaV9qb2luKGJpbmduZWdhdGl2ZSkgJT4lDQogIGdyb3VwX2J5KGJvb2ssIGNoYXB0ZXIpICU+JQ0KICBzdW1tYXJpemUobmVnYXRpdmV3b3JkcyA9IG4oKSkgJT4lDQogIGxlZnRfam9pbih3b3JkY291bnRzLCBieSA9IGMoImJvb2siLCAiY2hhcHRlciIpKSAlPiUNCiAgbXV0YXRlKHJhdGlvID0gbmVnYXRpdmV3b3Jkcy93b3JkcykgJT4lDQogIGZpbHRlcihjaGFwdGVyICE9IDApICU+JQ0KICBzbGljZV9tYXgocmF0aW8sIG4gPSAxKSAlPiUgDQogIHVuZ3JvdXAoKQ0KICAgICAgICAgICAgICANCmBgYA0KDQpgYGB7cn0NCiMgTG9hZCBMb3VnaHJhbi1NY0RvbmFsZCBzZW50aW1lbnQgbGV4aWNvbg0KbGlicmFyeSh0aWR5dGV4dCkNCmxvdWdocmFuX3NlbnRpbWVudHMgPC0gZ2V0X3NlbnRpbWVudHMoImxvdWdocmFuIikNCg0KIyBDb3VudCB3b3JkIGZyZXF1ZW5jaWVzIGJ5IHNlbnRpbWVudCB1c2luZyBMb3VnaHJhbi1NY0RvbmFsZCBsZXhpY29uDQpsb3VnaHJhbl93b3JkX2NvdW50cyA8LSBwcmlkZV9wcmVqdWRpY2UgJT4lIA0KICBpbm5lcl9qb2luKGxvdWdocmFuX3NlbnRpbWVudHMsIGJ5ID0gIndvcmQiKSAlPiUgICMgSm9pbiBieSAnd29yZCcNCiAgY291bnQod29yZCwgc2VudGltZW50LCBzb3J0ID0gVFJVRSkgJT4lIA0KICB1bmdyb3VwKCkNCg0KIyBQZXJmb3JtIHNlbnRpbWVudCBhbmFseXNpcyBieSBiaW5uaW5nIHRoZSB0ZXh0IGludG8gZ3JvdXBzIG9mIDgwIGxpbmVzDQpsb3VnaHJhbiA8LSBwcmlkZV9wcmVqdWRpY2UgJT4lIA0KICBpbm5lcl9qb2luKGxvdWdocmFuX3NlbnRpbWVudHMsIGJ5ID0gIndvcmQiKSAlPiUgICMgRW5zdXJlIHByb3BlciBqb2luIG9uICd3b3JkJw0KICBncm91cF9ieShpbmRleCA9IGxpbmVudW1iZXIgJS8lIDgwKSAlPiUgICMgR3JvdXAgYnkgYmluIG9mIDgwIGxpbmVzDQogIHN1bW1hcmlzZShzZW50aW1lbnQgPSBzdW0oYXMubnVtZXJpYyhzZW50aW1lbnQpLCBuYS5ybSA9IFRSVUUpKSAlPiUgICMgU3VtIHNlbnRpbWVudA0KICBtdXRhdGUobWV0aG9kID0gIkxvdWdocmFuIikgICMgQWRkIG1ldGhvZCBsYWJlbA0KDQpgYGANCmBgYHtyfQ0KIyBDb21iaW5lIGRpZmZlcmVudCBzZW50aW1lbnQgYW5hbHlzaXMgbWV0aG9kcyBpbmNsdWRpbmcgTG91Z2hyYW4tTWNEb25hbGQgbGV4aWNvbg0KYmluZF9yb3dzKGFmaW5uLCANCiAgICAgICAgICBiaW5nX2FuZF9ucmMsDQogICAgICAgICAgbG91Z2hyYW4pICU+JSAgDQogIGdncGxvdChhZXMoaW5kZXgsIHNlbnRpbWVudCwgZmlsbCA9IG1ldGhvZCkpICsNCiAgZ2VvbV9jb2woc2hvdy5sZWdlbmQgPSBGQUxTRSkgKw0KICBmYWNldF93cmFwKH5tZXRob2QsIG5jb2wgPSAxLCBzY2FsZXMgPSAiZnJlZV95IikNCg0KYGBgDQotLS0NCnRpdGxlOiAiTGFiIE5hbWUiDQphdXRob3I6ICJBdXRob3IgTmFtZSINCmRhdGU6ICJgciBTeXMuRGF0ZSgpYCINCm91dHB1dDogb3BlbmludHJvOjpsYWJfcmVwb3J0DQotLS0NCiMjIyAjVGV4dCBtaW5pbmcgYW5kIG5hdHVyYWwgbGFuZ3VhZ2UgcHJvY2Vzc2luZw0KDQpUaGUgZ29hbHMgb2YgdGhpcyB3ZWVr4oCZcyBhc3NpZ25tZW50IGFyZSBhcyBmb2xsb3dzOiAxLiBnZXQgcHJpbWFyeSBleGFtcGxlIGNvZGUgZnJvbSBjaGFwdGVyIDIgb2Yg4oCcVGV4dCBNaW5pbmcgd2l0aCBSOiBBIFRpZHkgQXBwcm9hY2jigJ0gYnkgSnVsaWEgU2lsZ2UgYW5kIERhdmlkIFJvYmluc29uLg0KMi4gV29yayB3aXRoIGEgZGlmZmVyZW50IGNvcnB1cy4gRm9yIHRoaXMsIEkgY2hvc2UgdG8gd29yayB3aXRoIGEgY29sbGVjdGlvbiBvZiB3b3JrcyBieSBteSBmYXZvcml0ZSBhdXRob3IsIENoYXJsZXMgRGlja2Vucy4gSSB1c2VkIHRoZSBndXRlbmJlcmcgcHJvamVjdCB0byBkb3dubG9hZCB0aGUgIHRleHQgZGF0YSBzaW5jZSB0aGUgZGF0YSBpcyBhcmNoaXZlZC4NCjMuIFdlIGluY29ycG9yYXRlZCB0aGUgbG91Z2hhcm4gc2VudGltZW50Lg0KNC4gRmluZCBtb3N0IGZyZXF1ZW50IHdvcmRzLg0KNS4gRmluZCBtb3N0IGltcG9ydGFudCB3b3Jkcy4NCg0KDQoNCg0KIyMjIERvd25sb2FkLCByZWFkIGFuZCBjb21iaW5lICB0aGUgdGV4dCBEYXRhDQoNCg0KDQoNCmBgYHtyfQ0KZGlja2Vuc19ib29rMSA8LSByZWFkTGluZXMoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS90YW56aWw2NC9EYXRhLTYwNy1Bc3NpZ25tZW50LTEwL3JlZnMvaGVhZHMvbWFpbi9BJTIwQ2hyaXN0bWFzJTIwQ2Fyb2wudHh0IikNCiMgVmlldyB0aGUgZmlyc3QgZmV3IGxpbmVzIG9mIHRoZSBkb3dubG9hZGVkIGJvb2sNCiMgQXNzdW1pbmcgeW91IGhhdmUgYWxyZWFkeSBsb2FkZWQgdGhlIHRleHQgZmlsZQ0KDQojIFZpZXcgdGhlIGZpcnN0IGZldyBsaW5lcw0KaGVhZChkaWNrZW5zX2Jvb2sxKQ0KYGBgDQoNCg0KDQoNCmBgYHtyfQ0KDQpkaWNrZW5zX2Jvb2syIDwtIHJlYWRMaW5lcygiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3RhbnppbDY0L0RhdGEtNjA3LUFzc2lnbm1lbnQtMTAvcmVmcy9oZWFkcy9tYWluL0dyZWF0JTIwRXhwZWN0YXRpb25zLnR4dCIpDQojIFZpZXcgdGhlIGZpcnN0IGZldyBsaW5lcyBvZiB0aGUgZG93bmxvYWRlZCBib29rDQojIEFzc3VtaW5nIHlvdSBoYXZlIGFscmVhZHkgbG9hZGVkIHRoZSB0ZXh0IGZpbGUNCg0KIyBWaWV3IHRoZSBmaXJzdCBmZXcgbGluZXMNCmhlYWQoZGlja2Vuc19ib29rMikNCmBgYA0KDQoNCg0KDQpgYGB7cn0NCiMgQXNzdW1pbmcgdGhlIGNoYXJhY3RlciB2ZWN0b3JzIGZvciBib29rIHRleHRzIGFyZSBwcm9wZXJseSBsb2FkZWQgYXMgc2luZ2xlIHN0cmluZ3M6DQojIGBkaWNrZW5zX2Jvb2sxYCBhbmQgYGRpY2tlbnNfYm9vazJgIGNvbnRhaW4gdGhlIGZ1bGwgdGV4dHMgb2YgZWFjaCBib29rLg0KDQojIENvbnZlcnQgYGRpY2tlbnNfYm9vazFgIGFuZCBgZGlja2Vuc19ib29rMmAgaW50byBkYXRhIGZyYW1lcy90aWJibGVzIHdpdGggdGhlIGNvcnJlY3Qgc3RydWN0dXJlDQpkaWNrZW5zX2Jvb2sxIDwtIHRpYmJsZShib29rID0gIkEgQ2hyaXN0bWFzIENhcm9sIiwgdGV4dCA9IHBhc3RlKGRpY2tlbnNfYm9vazEsIGNvbGxhcHNlID0gIiAiKSkNCmRpY2tlbnNfYm9vazIgPC0gdGliYmxlKGJvb2sgPSAiR3JlYXQgRXhwZWN0YXRpb25zIiwgdGV4dCA9IHBhc3RlKGRpY2tlbnNfYm9vazIsIGNvbGxhcHNlID0gIiAiKSkNCg0KIyBDb21iaW5lIHRoZSB0d28gYm9va3MgaW50byBvbmUgdGliYmxlDQpkaWNrZW5zX3dvcmtzIDwtIGJpbmRfcm93cyhkaWNrZW5zX2Jvb2sxLCBkaWNrZW5zX2Jvb2syKQ0KDQojIENoZWNrIHRoZSBjb21iaW5lZCB0aWJibGUNCmdsaW1wc2UoZGlja2Vuc193b3JrcykNCg0KDQpgYGANCmANCg0KDQoNCg0KDQoNCg0KIyMjIFRpZHkgdGhlIERhdGENCg0KYGBge3J9DQp0aWR5X2RpY2tlbnMgPC0gZGlja2Vuc193b3JrcyAlPiUNCiAgdW5uZXN0X3Rva2Vucyh3b3JkLCB0ZXh0KSAlPiUgICAgICAgIyBUb2tlbml6ZSBpbnRvIHdvcmRzDQogIGFudGlfam9pbihzdG9wX3dvcmRzLCBieSA9ICJ3b3JkIikgDQpgYGANCg0KDQoNCg0KDQoNCiMjIyBDb3VudGluZyB0aGUgd29yZHMNCg0KDQpgYGB7cn0NCmRpY2tlbnNfZnJlcXVlbmN5IDwtIHRpZHlfZGlja2VucyAlPiUNCiAgY291bnQod29yZCkNCmBgYA0KDQojIyMgUGxvdHRpbmcgZnJlcXVuZWN5IHdpdGggZ2dwbG90Mg0KDQpgYGB7cn0NCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGdncGxvdDIpDQoNCiMgRmlsdGVyIHRoZSB0b3AgNyB3b3JkcyB3aXRoIHRoZSBoaWdoZXN0IGZyZXF1ZW5jaWVzDQpkaWNrZW5zX3RvcF93b3JkcyA8LSBkaWNrZW5zX2ZyZXF1ZW5jeSAlPiUNCiAgZmlsdGVyKG4gPiA1KSAlPiUNCiAgc2xpY2VfbWF4KG4sIG4gPSA3KQ0KDQojIFBsb3QgdGhlIHRvcCA3IHdvcmRzDQpnZ3Bsb3QoZGlja2Vuc190b3Bfd29yZHMsIGFlcyh4ID0gcmVvcmRlcih3b3JkLCBuKSwgeSA9IG4pKSArIA0KICBnZW9tX2NvbCgpICsNCiAgeGxhYigiV29yZHMiKSArDQogIHlsYWIoIkZyZXF1ZW5jeSIpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA0NSwgaGp1c3QgPSAxKSkgICMgUm90YXRlIHgtYXhpcyBsYWJlbHMgZm9yIGJldHRlciByZWFkYWJpbGl0eQ0KDQpgYGANCg0KIyMjIEdldCBTZW50aW1lbnRzDQoNCg0KDQpgYGB7cn0NCmdldF9zZW50aW1lbnRzKCJhZmlubiIpDQpgYGANCg0KYGBge3J9DQpnZXRfc2VudGltZW50cygiYmluZyIpDQpgYGANCg0KYGBge3J9DQpnZXRfc2VudGltZW50cygibnJjIikNCmBgYA0KDQpgYGB7cn0NCm5yY19qb3kgPC0gZ2V0X3NlbnRpbWVudHMoIm5yYyIpICU+JQ0KICBmaWx0ZXIoc2VudGltZW50ID09ICJqb3kiKQ0KaGVhZChucmNfam95KQ0KYGBgDQoNCg0KSGVyZSBJIGZpbHRlcmVkIG91dCBhbGwgb2YgdGhlIHdvcmRzIHJlbGF0ZWQgdG8gcG9zaXRpdmUgZmVlbGluZ3MgZnJvbSB0aGUgbnJjIGxleGljb24uDQoNCk5leHQsIEkgbmVlZCB0byBmaW5kIHRoZSBqb3lmdWwgd29yZHMgaW4gQ2hhcmxlcyBEaWNrZW5zIHdvcmtzLg0KDQpucmMgam95IEhlcmUgd2UgdGFrZSBhbGwgb2YgdGhlIHdvcmRzIHRoYXQgYXJlIHJlbGF0ZWQgdG8gam95IGZyb20gRGlja2VucyB3b3Jrcy4NCg0KYGBge3J9DQpucmNfam95X2RpY2tlbnMgPC0gdGlkeV9kaWNrZW5zICU+JQ0KICBpbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJucmMiKSAlPiUgZmlsdGVyKHNlbnRpbWVudCA9PSAiam95IiksIGJ5ID0gIndvcmQiKSAlPiUNCiAgY291bnQod29yZCwgc29ydCA9IFRSVUUpDQoNCmBgYA0KDQoNCg0KDQoNCmBgYHtyfQ0KaGVhZChucmNfam95X2RpY2tlbnMpDQpgYGANCg0KDQoNCg0KDQoNCiNhZmZyaW4gam95IFRoaXMgd2lsbCBwdWxsIG91dCB0aGUgd29yZHMgZnJvbSBEaWNrZW5zIHdvcmtzIHRoYXQgYWZyaW5uIHJlbGF0ZSBhcyBwb3NpdGl2ZS4NCmBgYHtyfQ0KYWZmaW5fam95IDwtIGdldF9zZW50aW1lbnRzKCJhZmlubiIpICU+JQ0KICBmaWx0ZXIodmFsdWUgPiAwKQ0KDQoNCmRpY2tlbnNfYWZpbm5fam95IDwtIHRpZHlfZGlja2VucyAlPiUNCiAgaW5uZXJfam9pbihhZmZpbl9qb3ksIGJ5ID0gIndvcmQiKSAlPiUNCiAgY291bnQod29yZCwgc29ydCA9IFRSVUUpDQpgYGANCg0KDQoNCiNCaW5nIGpveSBIZXJlIHdlIGZpbmQgcG9zaXRpdmUgRGlja2VucyB3b3JkcyB1c2luZyB0aGUgbGV4aWNvbiBCaW5nLg0KYGBge3J9DQpiaW5nX2pveSA8LSBnZXRfc2VudGltZW50cygiYmluZyIpJT4lDQogIGZpbHRlcihzZW50aW1lbnQgPT0gInBvc2l0aXZlIikNCg0KZGlja2Vuc19iaW5nX2pveSA8LSB0aWR5X2RpY2tlbnMgJT4lDQogIGlubmVyX2pvaW4oYmluZ19qb3ksIGJ5ID0gIndvcmQiKSAlPiUNCiAgY291bnQod29yZCwgc29ydCA9IFRSVUUpDQpgYGANCg0KDQojbnJjIEpveSBIZXJlIHdlIHdpbGwgdXNlIGEgbGV4aWNvbiBub3QgdXNlZCBpbiB0aGUgZXhhbXBsZXMgb2YgaW4gdGhlIGJvb2suDQoNCmBgYHtyfQ0KbnJjX2pveSA8LSBnZXRfc2VudGltZW50cygibnJjIiklPiUNCiAgZmlsdGVyKHNlbnRpbWVudCA9PSAicG9zaXRpdmUiKQ0KDQpkaWNrZW5zX25yY19qb3kgPC0gdGlkeV9kaWNrZW5zICU+JQ0KICBpbm5lcl9qb2luKGJpbmdfam95LCBieSA9ICJ3b3JkIikgJT4lDQogIGNvdW50KHdvcmQsIHNvcnQgPSBUUlVFKQ0KYGBgDQoNCg0KI0xvdWdocmFuIEpveSBIZXJlIHdlIHdpbGwgdXNlIGEgbGV4aWNvbiBub3QgdXNlZCBpbiB0aGUgZXhhbXBsZXMgb2YgaW4gdGhlIGJvb2suDQpgYGB7cn0NCmxvdWdocmFuX2pveSA8LSBnZXRfc2VudGltZW50cygibG91Z2hyYW4iKSU+JQ0KICBmaWx0ZXIoc2VudGltZW50ID09ICJwb3NpdGl2ZSIpDQoNCmRpY2tlbnNfbG91Z2hyYW5fam95IDwtIHRpZHlfZGlja2VucyAlPiUNCiAgaW5uZXJfam9pbihsb3VnaHJhbl9qb3ksIGJ5ID0gIndvcmQiKSU+JQ0KICBjb3VudCh3b3JkLCBzb3J0ID0gVFJVRSkNCmBgYA0KDQoNCg0KDQojR3JhcGhpbmcgdGhlIHRvcCB3b3JkcyBUaGlzIGlzIGdyZWF0IHdheSB0byBjb21wYXJlIGhvdyB0aGUgc2VudGltZW50cyB3b3JrDQoNCmBgYHtyfQ0KbGlicmFyeShnZ3Bsb3QyKQ0KbGlicmFyeShkcGx5cikNCg0KIyBLZWVwIG9ubHkgdGhlIHRvcCAxMCB3b3JkcyBmb3IgZWFjaCBzZW50aW1lbnQNCmdyYXBoX2RpY2tlbnNfYWZpbm5fdG9wMTAgPC0gZGlja2Vuc19hZmlubl9qb3kgJT4lDQogIHRvcF9uKDEwLCBuKQ0KDQpncmFwaF9kaWNrZW5zX2JpbmdfdG9wMTAgPC0gZGlja2Vuc19hZmlubl9qb3kgJT4lDQogIHRvcF9uKDEwLCBuKQ0KDQpncmFwaF9kaWNrZW5zX2xvdWdocmFuX3RvcDEwIDwtIGRpY2tlbnNfYWZpbm5fam95ICU+JQ0KICB0b3BfbigxMCwgbikNCg0KZ3JhcGhfZGlja2Vuc19ucmNfdG9wMTAgPC0gZGlja2Vuc19hZmlubl9qb3kgJT4lDQogIHRvcF9uKDEwLCBuKQ0KDQojIFBsb3QgdGhlIGdyYXBoIHdpdGggdGhlIHRvcCAxMCB3b3Jkcw0KZ2dwbG90KCkgKw0KICBnZW9tX3BvaW50KGRhdGEgPSBncmFwaF9kaWNrZW5zX2FmaW5uX3RvcDEwLCBhZXMoeCA9IHdvcmQsIHkgPSBuKSwgY29sb3IgPSAicmVkIikgKw0KICBnZW9tX2xpbmUoZGF0YSA9IGdyYXBoX2RpY2tlbnNfYWZpbm5fdG9wMTAsIGFlcyh4ID0gd29yZCwgeSA9IG4sIGdyb3VwID0gMSksIGNvbG9yID0gInJlZCIpICsNCiAgDQogIGdlb21fcG9pbnQoZGF0YSA9IGdyYXBoX2RpY2tlbnNfYmluZ190b3AxMCwgYWVzKHggPSB3b3JkLCB5ID0gbiksIGNvbG9yID0gImJsYWNrIikgKw0KICBnZW9tX2xpbmUoZGF0YSA9IGdyYXBoX2RpY2tlbnNfYmluZ190b3AxMCwgYWVzKHggPSB3b3JkLCB5ID0gbiwgZ3JvdXAgPSAxKSwgY29sb3IgPSAiYmxhY2siKSArDQogIA0KICBnZW9tX3BvaW50KGRhdGEgPSBncmFwaF9kaWNrZW5zX2xvdWdocmFuX3RvcDEwLCBhZXMoeCA9IHdvcmQsIHkgPSBuKSwgY29sb3IgPSAiZ3JlZW4iKSArDQogIGdlb21fbGluZShkYXRhID0gZ3JhcGhfZGlja2Vuc19sb3VnaHJhbl90b3AxMCwgYWVzKHggPSB3b3JkLCB5ID0gbiwgZ3JvdXAgPSAxKSwgY29sb3IgPSAiZ3JlZW4iKSArDQogIA0KICBnZW9tX3BvaW50KGRhdGEgPSBncmFwaF9kaWNrZW5zX25yY190b3AxMCwgYWVzKHggPSB3b3JkLCB5ID0gbiksIGNvbG9yID0gImJsdWUiKSArDQogIGdlb21fbGluZShkYXRhID0gZ3JhcGhfZGlja2Vuc19ucmNfdG9wMTAsIGFlcyh4ID0gd29yZCwgeSA9IG4sIGdyb3VwID0gMSksIGNvbG9yID0gImJsdWUiKSArDQogIA0KICB0aGVtZV9taW5pbWFsKCkgKw0KICBsYWJzKHRpdGxlID0gIlRvcCAxMCBXb3JkcyBieSBTZW50aW1lbnQgU2NvcmUgKE91dGxpZXJzIFJlbW92ZWQpIiwNCiAgICAgICB4ID0gIldvcmRzIiwNCiAgICAgICB5ID0gIkZyZXF1ZW5jeSIpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCwgaGp1c3QgPSAxKSkNCg0KYGBgDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQoNCg0KDQojbm93IHRvIHB1bGwgb3V0IHdvcmQgZnJlcXVlbmNpZXMgcGVyIGJvb2sNCmBgYHtyfQ0KYm9va193b3JkcyA8LSB0aWR5X2RpY2tlbnMgJT4lDQogIGNvdW50KGJvb2ssIHdvcmQsIHNvcnQgPSBUUlVFKQ0KDQojIFN0ZXAgMjogQ2FsY3VsYXRlIHRvdGFsIG51bWJlciBvZiB3b3JkcyBwZXIgYm9vaw0KdG90YWxfd29yZHMgPC0gYm9va193b3JkcyAlPiUNCiAgZ3JvdXBfYnkoYm9vaykgJT4lDQogIHN1bW1hcml6ZSh0b3RhbCA9IHN1bShuKSwgLmdyb3VwcyA9ICJkcm9wIikNCg0KIyBTdGVwIDM6IEpvaW4gdG90YWwgd29yZCBjb3VudCBpbnRvIHRoZSBtYWluIGRhdGENCmJvb2tfd29yZHMgPC0gbGVmdF9qb2luKGJvb2tfd29yZHMsIHRvdGFsX3dvcmRzLCBieSA9ICJib29rIikNCg0KIyBWaWV3IHJlc3VsdA0KaGVhZChib29rX3dvcmRzKQ0KYGBgDQojVG8gZmluZCB3aGljaCB3b3JkcyBhcmUgdGhlIG1vc3QgaW1wb3J0YW50IHdlIHVzZSB0aGUgdGZfaWRmLg0KYGBge3J9DQpmcmVxX2J5X3JhbmsgPC0gYm9va193b3JkcyAlPiUNCiAgZ3JvdXBfYnkoYm9vaykgJT4lDQogIG11dGF0ZShyYW5rID0gcm93X251bWJlcigpLA0KICAgICAgICAgYHRlcm0gZnJlcXVlbmN5YCA9IG4vdG90YWwpJT4lDQogIHVuZ3JvdXAoKQ0KZnJlcV9ieV9yYW5rDQpgYGANCmBgYHtyfQ0KYm9va190Zl9pZGYgPC0gYm9va193b3JkcyAlPiUNCiAgYmluZF90Zl9pZGYod29yZCwgYm9vaywgbikNCmJvb2tfdGZfaWRmICU+JQ0KICBhcnJhbmdlKGRlc2ModGZfaWRmKSkNCmBgYA0KDQoNCmBgYHtyfQ0KIyBTdGVwIDE6IENvdW50IHdvcmRzIHBlciBib29rDQp3b3JkX2NvdW50cyA8LSB0aWR5X2RpY2tlbnMgJT4lDQogIGNvdW50KGJvb2ssIHdvcmQsIHNvcnQgPSBUUlVFKQ0KDQojIFN0ZXAgMjogQXBwbHkgdGYtaWRmDQpkaWNrZW5zX3RmX2lkZiA8LSB3b3JkX2NvdW50cyAlPiUNCiAgYmluZF90Zl9pZGYod29yZCwgYm9vaywgbikgJT4lDQogIGFycmFuZ2UoZGVzYyh0Zl9pZGYpKQ0KDQojIFN0ZXAgMzogVmlldyB0b3AgZGlzdGluY3RpdmUgd29yZHMNCmhlYWQoZGlja2Vuc190Zl9pZGYsIDEwKQ0KYGBgDQoNCg0KDQoNCg0KDQoNCg0KI0NvbmNsdXNpb25zDQoNCkluIGNvbmNsdXNpb24sIHRoZSBzYW1lIHdvcmRzIHRoYXQgYXJlIG1vc3QgdXNlZCBoYXBwZW4gdG8gYWxzbyBiZSBjb25zaWRlcmVkIHRvIGJlIHRoZSBtb3N0IGltcG9ydGFudCB3b3Jkcy4gSSB3b25kZXIgaG93IHRoaXMgd291bGQgY2hhbmdlIGlmIFByb2plY3QgR3V0ZW5iZXJnIGhhZCBtb3JlIG9mIEt1cnQgdm9ubmVndXTigJlzIGJvb2tzLiBJIGltYWdpbmUgdGhhdCB0aGUgY2hhcmFjdGVycyB3b3VsZG7igJl0IGJlIHRoZSB0b3Agd29yZHMgaW4gcmVnYXJkcyB0byBmcmVxdWVuY3kgb3IgaW1wb3J0YW5jZS4NCg0K