主要議題:文字雲的作法

rm(list=ls(all=T))
Sys.setlocale("LC_ALL","C")
[1] "C"
library(magrittr)
library(tm)
Loading required package: NLP

Attaching package: 'NLP'

The following object is masked from 'package:ggplot2':

    annotate
library(SnowballC)
library(RColorBrewer)
library(wordcloud)
library(slam)



1. Papare the Data

1.1 字頻表 Document-Term-Matrix

Download the dataset “tweets.csv”, and load it into a data frame called “tweets” using the read.csv() function, remembering to use stringsAsFactors=FALSE when loading the data.

Next, perform the following pre-processing tasks (like we did in Unit 5), noting that we don’t stem the words in the document or remove sparse terms:

  1. Create a corpus using the Tweet variable
  2. Convert the corpus to lowercase
  3. Remove punctuation from the corpus
  4. Remove all English-language stopwords
  5. Build a document-term matrix out of the corpus
  6. Convert the document-term matrix to a data frame called allTweets
tw = read.csv('data/tweets.csv',stringsAsFactors = F)
corpus = Corpus(VectorSource(tw$Tweet))
corpus = tm_map(corpus, content_transformer(tolower))
transformation drops documents
corpus = tm_map(corpus, removePunctuation)
transformation drops documents
corpus = tm_map(corpus, removeWords, stopwords("english"))
transformation drops documents
# corpus = tm_map(corpus, stemDocument)
dtm = DocumentTermMatrix(corpus)
# dtm = removeSparseTerms(dtm, 0.995); dtm

How many unique words are there across all the documents?

dim(dtm)
[1] 1181 3780
To Stem or Not To Stem

Although we typically stem words during the text preprocessing step, we did not do so here. What is the most compelling rationale for skipping this step when visualizing text data?

  • It will be easier to read and understand the word cloud if it includes full words instead of just the word stems


2 Building a Word Cloud

2.1 The wordcloud package

Install and load the “wordcloud” package, which is needed to build word clouds.

library(wordcloud)
library(slam)

As we can read from ?wordcloud, we will need to provide the function with a vector of words and a vector of word frequencies. Which function can we apply to allTweets to get a vector of the words in our dataset, which we’ll pass as the first argument to wordcloud()?

colnames(dtm) %>% head(30)
 [1] "apple"                   "appstore"                "best"                   
 [4] "care"                    "customer"                "ever"                   
 [7] "far"                     "received"                "say"                    
[10] "service"                 "beautiful"               "fricking"               
[13] "ios"                     "smooth"                  "thanxapple"             
[16] "love"                    "iphone"                  "iphone5s"               
[19] "loving"                  "new"                     "pictwittercomxmhjcu4pcb"
[22] "thank"                   "10min"                   "phone"                  
[25] "amazing"                 "ear"                     "headphones"             
[28] "inear"                   "ive"                     "pods"                   
2.2 Word Frequency in the Corpus

Which function should we apply to allTweets to obtain the frequency of each word across all tweets?

col_sums(dtm) %>% head(12)
    apple  appstore      best      care  customer      ever       far  received 
     1297         7        12        11         7         9         3         1 
      say   service beautiful  fricking 
       11        15         2         1 
2.3 The Most Frequent Word

Use allTweets to build a word cloud. Make sure to check out the help page for wordcloud if you are not sure how to do this.

Because we are plotting a large number of words, you might get warnings that some of the words could not be fit on the page and were therefore not plotted – this is especially likely if you are using a smaller screen. You can address these warnings by plotting the words smaller. From ?wordcloud, we can see that the “scale” parameter controls the sizes of the plotted words. By default, the sizes range from 4 for the most frequent words to 0.5 for the least frequent, as denoted by the parameter “scale=c(4, 0.5)”. We could obtain a much smaller plot with, for instance, parameter “scale=c(2, 0.25)”.

What is the most common word across all the tweets (it will be the largest in the outputted word cloud)? Please type the word exactly how you see it in the word cloud. The most frequent word might not be printed if you got a warning about words being cut off – if this happened, be sure to follow the instructions in the paragraph above.

wordcloud(colnames(dtm), col_sums(dtm), scale=c(3, 0.5))

2.4 Remove the ‘Indexing’ Keyword

In the previous subproblem, we noted that there is one word with a much higher frequency than the other words. Repeat the steps to load and pre-process the corpus, this time removing the most frequent word in addition to all elements of stopwords(“english”) in the call to tm_map with removeWords. For a refresher on how to remove this additional word, see the Twitter text analytics lecture.

Replace allTweets with the document-term matrix of this new corpus – we will use this updated corpus for the remainder of the assignment.

Create a word cloud with the updated corpus.

corpus = Corpus(VectorSource(tw$Tweet))
corpus = tm_map(corpus, content_transformer(tolower))
transformation drops documents
corpus = tm_map(corpus, removePunctuation)
transformation drops documents
corpus = tm_map(corpus, removeWords, 
                c('apple',stopwords("english")) )
transformation drops documents
dtm = DocumentTermMatrix(corpus)
# iphone

What is the most common word in this new corpus (the largest word in the outputted word cloud)?

wordcloud(colnames(dtm), col_sums(dtm), scale=c(3, 0.5))



Size and Color

So far, the word clouds we’ve built have not been too visually appealing – they are crowded by having too many words displayed, and they don’t take advantage of color. One important step to building visually appealing visualizations is to experiment with the parameters available, which in this case can be viewed by typing ?wordcloud in your R console. In this problem, you should look through the help page and experiment with different parameters to answer the questions.

Below are four word clouds, each of which uses different parameter settings in the call to the wordcloud() function:

Word Cloud A:

wordcloud(colnames(dtm), col_sums(dtm), scale=c(3, 0.4),
          rot.per=0.5) 

Word Cloud B:

wordcloud(colnames(dtm), col_sums(dtm), scale=c(3, 0.4),
          min.freq=8, random.order=F)     # B

Word Cloud C:

dtm1 = dtm[tw$Avg <= -1,]
wordcloud(colnames(dtm1), col_sums(dtm1), scale=c(3, 0.4),
          colors = brewer.pal(9,"Purples")[6:9] ) # C

Word Cloud D:

wordcloud(colnames(dtm), col_sums(dtm), scale=c(3, 0.4),
  min.freq=8, random.order=F, random.color=T,
  colors = brewer.pal(9,"Purples")[6:9] ) # D

3.1

Which word cloud is based only on the negative tweets (tweets with Avg value -1 or less)?

  • Word Cloud C
3.2

Only one word cloud was created without modifying parameters min.freq or max.words. Which word cloud is this?_

  • Word Cloud A
3.3

Which word clouds were created with parameter random.order set to FALSE?

  • Word Cloud B
  • Word Cloud D
3.4

Which word cloud was built with a non-default value for parameter rot.per?

  • Word Cloud A
3.5

In Word Cloud C and Word Cloud D, we provided a color palette ranging from light purple to dark purple as the parameter colors (you will learn how to make such a color palette later in this assignment). For which word cloud was the parameter random.color set to TRUE?

  • Word Cloud D


4. Selecting a Color Palette

The use of a palette of colors can often improve the overall effect of a visualization. We can easily select our own colors when plotting; for instance, we could pass c(“red”, “green”, “blue”) as the colors parameter to wordcloud(). The RColorBrewer package, which is based on the ColorBrewer project (colorbrewer.org), provides pre-selected palettes that can lead to more visually appealing images. Though these palettes are designed specifically for coloring maps, we can also use them in our word clouds and other visualizations.

Begin by installing and loading the “RColorBrewer” package. This package may have already been installed and loaded when you installed and loaded the “wordcloud” package, in which case you don’t need to go through this additional installation step. If you obtain errors (for instance, “Error: lazy-load database ‘P’ is corrupt”) after installing and loading the RColorBrewer package and running some of the commands, try closing and re-opening R.

The function brewer.pal() returns color palettes from the ColorBrewer project when provided with appropriate parameters, and the function display.brewer.all() displays the palettes we can choose from.

4.1

Which color palette would be most appropriate for use in a word cloud for which we want to use color to indicate word frequency?

library(RColorBrewer)
display.brewer.all()

4.2

Which RColorBrewer palette name would be most appropriate to use when preparing an image for a document that must be in grayscale?

rr display.brewer.pal(7, )

4.3

n sequential palettes, sometimes there is an undesirably large contrast between the lightest and darkest colors. You can see this effect when plotting a word cloud for allTweets with parameter colors=brewer.pal(9, “Blues”), which returns a sequential blue palette with 9 colors.

Which of the following commands addresses this issue by removing the first 4 elements of the 9-color palette of blue colors? Select all that apply.

brewer.pal(9, "Blues")[c(5,6,7,8,9)] 
[1] "#6BAED6" "#4292C6" "#2171B5" "#08519C" "#08306B"
brewer.pal(9, "Blues")[c(-1,-2,-3,-4)] 
[1] "#6BAED6" "#4292C6" "#2171B5" "#08519C" "#08306B"








LS0tDQp0aXRsZTogIkFTNy0zIOaWh+Wtl+mbsiINCmF1dGhvcjogIumZs+aAoeWuiSwgTTA2NDExMjAxNCINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCjxicj4NCg0KKirkuLvopoHorbDpoYzvvJrmloflrZfpm7LnmoTkvZzms5UqKg0KDQpgYGB7ciBlY2hvPVQsIG1lc3NhZ2U9RiwgY2FjaGU9Riwgd2FybmluZz1GfQ0Kcm0obGlzdD1scyhhbGw9VCkpDQpTeXMuc2V0bG9jYWxlKCJMQ19BTEwiLCJDIikNCmxpYnJhcnkobWFncml0dHIpDQpsaWJyYXJ5KHRtKQ0KbGlicmFyeShTbm93YmFsbEMpDQpsaWJyYXJ5KFJDb2xvckJyZXdlcikNCmxpYnJhcnkod29yZGNsb3VkKQ0KbGlicmFyeShzbGFtKQ0KYGBgDQo8YnI+DQoNCi0gLSAtDQoNCiMjIyAxLiBQYXBhcmUgdGhlIERhdGENCg0KIyMjIyMgMS4xIOWtl+mgu+ihqCBEb2N1bWVudC1UZXJtLU1hdHJpeA0KRG93bmxvYWQgdGhlIGRhdGFzZXQgInR3ZWV0cy5jc3YiLCBhbmQgbG9hZCBpdCBpbnRvIGEgZGF0YSBmcmFtZSBjYWxsZWQgInR3ZWV0cyIgdXNpbmcgdGhlIHJlYWQuY3N2KCkgZnVuY3Rpb24sIHJlbWVtYmVyaW5nIHRvIHVzZSBzdHJpbmdzQXNGYWN0b3JzPUZBTFNFIHdoZW4gbG9hZGluZyB0aGUgZGF0YS4NCg0KTmV4dCwgcGVyZm9ybSB0aGUgZm9sbG93aW5nIHByZS1wcm9jZXNzaW5nIHRhc2tzIChsaWtlIHdlIGRpZCBpbiBVbml0IDUpLCBub3RpbmcgdGhhdCB3ZSBkb24ndCBzdGVtIHRoZSB3b3JkcyBpbiB0aGUgZG9jdW1lbnQgb3IgcmVtb3ZlIHNwYXJzZSB0ZXJtczoNCg0KMSkgQ3JlYXRlIGEgY29ycHVzIHVzaW5nIHRoZSBUd2VldCB2YXJpYWJsZQ0KMikgQ29udmVydCB0aGUgY29ycHVzIHRvIGxvd2VyY2FzZQ0KMykgUmVtb3ZlIHB1bmN0dWF0aW9uIGZyb20gdGhlIGNvcnB1cw0KNCkgUmVtb3ZlIGFsbCBFbmdsaXNoLWxhbmd1YWdlIHN0b3B3b3Jkcw0KNSkgQnVpbGQgYSBkb2N1bWVudC10ZXJtIG1hdHJpeCBvdXQgb2YgdGhlIGNvcnB1cw0KNikgQ29udmVydCB0aGUgZG9jdW1lbnQtdGVybSBtYXRyaXggdG8gYSBkYXRhIGZyYW1lIGNhbGxlZCBhbGxUd2VldHMNCg0KYGBge3J9DQp0dyA9IHJlYWQuY3N2KCdkYXRhL3R3ZWV0cy5jc3YnLHN0cmluZ3NBc0ZhY3RvcnMgPSBGKQ0KY29ycHVzID0gQ29ycHVzKFZlY3RvclNvdXJjZSh0dyRUd2VldCkpDQpjb3JwdXMgPSB0bV9tYXAoY29ycHVzLCBjb250ZW50X3RyYW5zZm9ybWVyKHRvbG93ZXIpKQ0KY29ycHVzID0gdG1fbWFwKGNvcnB1cywgcmVtb3ZlUHVuY3R1YXRpb24pDQpjb3JwdXMgPSB0bV9tYXAoY29ycHVzLCByZW1vdmVXb3Jkcywgc3RvcHdvcmRzKCJlbmdsaXNoIikpDQojIGNvcnB1cyA9IHRtX21hcChjb3JwdXMsIHN0ZW1Eb2N1bWVudCkNCmR0bSA9IERvY3VtZW50VGVybU1hdHJpeChjb3JwdXMpDQojIGR0bSA9IHJlbW92ZVNwYXJzZVRlcm1zKGR0bSwgMC45OTUpOyBkdG0NCmBgYA0KDQpfSG93IG1hbnkgdW5pcXVlIHdvcmRzIGFyZSB0aGVyZSBhY3Jvc3MgYWxsIHRoZSBkb2N1bWVudHM/Xw0KYGBge3J9DQpkaW0oZHRtKQ0KYGBgDQoNCiMjIyMjIFRvIFN0ZW0gb3IgTm90IFRvIFN0ZW0NCkFsdGhvdWdoIHdlIHR5cGljYWxseSBzdGVtIHdvcmRzIGR1cmluZyB0aGUgdGV4dCBwcmVwcm9jZXNzaW5nIHN0ZXAsIHdlIGRpZCBub3QgZG8gc28gaGVyZS4gX1doYXQgaXMgdGhlIG1vc3QgY29tcGVsbGluZyByYXRpb25hbGUgZm9yIHNraXBwaW5nIHRoaXMgc3RlcCB3aGVuIHZpc3VhbGl6aW5nIHRleHQgZGF0YT9fDQoNCisgSXQgd2lsbCBiZSBlYXNpZXIgdG8gcmVhZCBhbmQgdW5kZXJzdGFuZCB0aGUgd29yZCBjbG91ZCBpZiBpdCBpbmNsdWRlcyBmdWxsIHdvcmRzIGluc3RlYWQgb2YganVzdCB0aGUgd29yZCBzdGVtcyANCg0KPGJyPjxocj4NCg0KIyMjIDIgQnVpbGRpbmcgYSBXb3JkIENsb3VkDQoNCiMjIyMjIDIuMSBUaGUgYHdvcmRjbG91ZGAgcGFja2FnZQ0KSW5zdGFsbCBhbmQgbG9hZCB0aGUgIndvcmRjbG91ZCIgcGFja2FnZSwgd2hpY2ggaXMgbmVlZGVkIHRvIGJ1aWxkIHdvcmQgY2xvdWRzLg0KYGBge3J9DQpsaWJyYXJ5KHdvcmRjbG91ZCkNCmxpYnJhcnkoc2xhbSkNCmBgYA0KQXMgd2UgY2FuIHJlYWQgZnJvbSA/d29yZGNsb3VkLCB3ZSB3aWxsIG5lZWQgdG8gcHJvdmlkZSB0aGUgZnVuY3Rpb24gd2l0aCBhIHZlY3RvciBvZiB3b3JkcyBhbmQgYSB2ZWN0b3Igb2Ygd29yZCBmcmVxdWVuY2llcy4gX1doaWNoIGZ1bmN0aW9uIGNhbiB3ZSBhcHBseSB0byBhbGxUd2VldHMgdG8gZ2V0IGEgdmVjdG9yIG9mIHRoZSB3b3JkcyBpbiBvdXIgZGF0YXNldCwgd2hpY2ggd2UnbGwgcGFzcyBhcyB0aGUgZmlyc3QgYXJndW1lbnQgdG8gYHdvcmRjbG91ZCgpYD9fDQpgYGB7cn0NCmNvbG5hbWVzKGR0bSkgJT4lIGhlYWQoMzApICNjb2xuYW1lcw0KYGBgDQoNCiMjIyMjIDIuMiBXb3JkIEZyZXF1ZW5jeSBpbiB0aGUgQ29ycHVzDQpfV2hpY2ggZnVuY3Rpb24gc2hvdWxkIHdlIGFwcGx5IHRvIGFsbFR3ZWV0cyB0byBvYnRhaW4gdGhlIGZyZXF1ZW5jeSBvZiBlYWNoIHdvcmQgYWNyb3NzIGFsbCB0d2VldHM/Xw0KYGBge3J9DQpjb2xfc3VtcyhkdG0pICU+JSBoZWFkKDEyKSAjY29sU3Vtcw0KYGBgDQoNCiMjIyMjIDIuMyBUaGUgTW9zdCBGcmVxdWVudCBXb3JkDQpVc2UgYWxsVHdlZXRzIHRvIGJ1aWxkIGEgd29yZCBjbG91ZC4gTWFrZSBzdXJlIHRvIGNoZWNrIG91dCB0aGUgaGVscCBwYWdlIGZvciB3b3JkY2xvdWQgaWYgeW91IGFyZSBub3Qgc3VyZSBob3cgdG8gZG8gdGhpcy4NCg0KQmVjYXVzZSB3ZSBhcmUgcGxvdHRpbmcgYSBsYXJnZSBudW1iZXIgb2Ygd29yZHMsIHlvdSBtaWdodCBnZXQgd2FybmluZ3MgdGhhdCBzb21lIG9mIHRoZSB3b3JkcyBjb3VsZCBub3QgYmUgZml0IG9uIHRoZSBwYWdlIGFuZCB3ZXJlIHRoZXJlZm9yZSBub3QgcGxvdHRlZCAtLSB0aGlzIGlzIGVzcGVjaWFsbHkgbGlrZWx5IGlmIHlvdSBhcmUgdXNpbmcgYSBzbWFsbGVyIHNjcmVlbi4gWW91IGNhbiBhZGRyZXNzIHRoZXNlIHdhcm5pbmdzIGJ5IHBsb3R0aW5nIHRoZSB3b3JkcyBzbWFsbGVyLiBGcm9tIGA/d29yZGNsb3VkYCwgd2UgY2FuIHNlZSB0aGF0IHRoZSAic2NhbGUiIHBhcmFtZXRlciBjb250cm9scyB0aGUgc2l6ZXMgb2YgdGhlIHBsb3R0ZWQgd29yZHMuIEJ5IGRlZmF1bHQsIHRoZSBzaXplcyByYW5nZSBmcm9tIDQgZm9yIHRoZSBtb3N0IGZyZXF1ZW50IHdvcmRzIHRvIDAuNSBmb3IgdGhlIGxlYXN0IGZyZXF1ZW50LCBhcyBkZW5vdGVkIGJ5IHRoZSBwYXJhbWV0ZXIgInNjYWxlPWMoNCwgMC41KSIuIFdlIGNvdWxkIG9idGFpbiBhIG11Y2ggc21hbGxlciBwbG90IHdpdGgsIGZvciBpbnN0YW5jZSwgcGFyYW1ldGVyICJzY2FsZT1jKDIsIDAuMjUpIi4NCg0KX1doYXQgaXMgdGhlIG1vc3QgY29tbW9uIHdvcmQgYWNyb3NzIGFsbCB0aGUgdHdlZXRzXyAoaXQgd2lsbCBiZSB0aGUgbGFyZ2VzdCBpbiB0aGUgb3V0cHV0dGVkIHdvcmQgY2xvdWQpPyBQbGVhc2UgdHlwZSB0aGUgd29yZCBleGFjdGx5IGhvdyB5b3Ugc2VlIGl0IGluIHRoZSB3b3JkIGNsb3VkLiBUaGUgbW9zdCBmcmVxdWVudCB3b3JkIG1pZ2h0IG5vdCBiZSBwcmludGVkIGlmIHlvdSBnb3QgYSB3YXJuaW5nIGFib3V0IHdvcmRzIGJlaW5nIGN1dCBvZmYgLS0gaWYgdGhpcyBoYXBwZW5lZCwgYmUgc3VyZSB0byBmb2xsb3cgdGhlIGluc3RydWN0aW9ucyBpbiB0aGUgcGFyYWdyYXBoIGFib3ZlLg0KYGBge3IgZmlnLmhlaWdodD02LCBmaWcud2lkdGg9Nn0NCndvcmRjbG91ZChjb2xuYW1lcyhkdG0pLCBjb2xfc3VtcyhkdG0pLCBzY2FsZT1jKDMsIDAuNSkpICNhcHBsZQ0KYGBgDQoNCiMjIyMjIDIuNCBSZW1vdmUgdGhlICdJbmRleGluZycgS2V5d29yZA0KSW4gdGhlIHByZXZpb3VzIHN1YnByb2JsZW0sIHdlIG5vdGVkIHRoYXQgdGhlcmUgaXMgb25lIHdvcmQgd2l0aCBhIG11Y2ggaGlnaGVyIGZyZXF1ZW5jeSB0aGFuIHRoZSBvdGhlciB3b3Jkcy4gUmVwZWF0IHRoZSBzdGVwcyB0byBsb2FkIGFuZCBwcmUtcHJvY2VzcyB0aGUgY29ycHVzLCB0aGlzIHRpbWUgcmVtb3ZpbmcgdGhlIG1vc3QgZnJlcXVlbnQgd29yZCBpbiBhZGRpdGlvbiB0byBhbGwgZWxlbWVudHMgb2Ygc3RvcHdvcmRzKCJlbmdsaXNoIikgaW4gdGhlIGNhbGwgdG8gdG1fbWFwIHdpdGggcmVtb3ZlV29yZHMuIEZvciBhIHJlZnJlc2hlciBvbiBob3cgdG8gcmVtb3ZlIHRoaXMgYWRkaXRpb25hbCB3b3JkLCBzZWUgdGhlIFR3aXR0ZXIgdGV4dCBhbmFseXRpY3MgbGVjdHVyZS4NCg0KUmVwbGFjZSBhbGxUd2VldHMgd2l0aCB0aGUgZG9jdW1lbnQtdGVybSBtYXRyaXggb2YgdGhpcyBuZXcgY29ycHVzIC0tIHdlIHdpbGwgdXNlIHRoaXMgdXBkYXRlZCBjb3JwdXMgZm9yIHRoZSByZW1haW5kZXIgb2YgdGhlIGFzc2lnbm1lbnQuDQoNCkNyZWF0ZSBhIHdvcmQgY2xvdWQgd2l0aCB0aGUgdXBkYXRlZCBjb3JwdXMuIA0KYGBge3J9DQpjb3JwdXMgPSBDb3JwdXMoVmVjdG9yU291cmNlKHR3JFR3ZWV0KSkNCmNvcnB1cyA9IHRtX21hcChjb3JwdXMsIGNvbnRlbnRfdHJhbnNmb3JtZXIodG9sb3dlcikpDQpjb3JwdXMgPSB0bV9tYXAoY29ycHVzLCByZW1vdmVQdW5jdHVhdGlvbikNCmNvcnB1cyA9IHRtX21hcChjb3JwdXMsIHJlbW92ZVdvcmRzLCANCiAgICAgICAgICAgICAgICBjKCdhcHBsZScsc3RvcHdvcmRzKCJlbmdsaXNoIikpICkNCmR0bSA9IERvY3VtZW50VGVybU1hdHJpeChjb3JwdXMpDQojIGlwaG9uZQ0KYGBgDQpfV2hhdCBpcyB0aGUgbW9zdCBjb21tb24gd29yZCBpbiB0aGlzIG5ldyBjb3JwdXMgKHRoZSBsYXJnZXN0IHdvcmQgaW4gdGhlIG91dHB1dHRlZCB3b3JkIGNsb3VkKT9fDQpgYGB7ciBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD02fQ0Kd29yZGNsb3VkKGNvbG5hbWVzKGR0bSksIGNvbF9zdW1zKGR0bSksIHNjYWxlPWMoMywgMC41KSkNCmBgYA0KPGJyPjxocj4NCg0KIyMjIFNpemUgYW5kIENvbG9yDQpTbyBmYXIsIHRoZSB3b3JkIGNsb3VkcyB3ZSd2ZSBidWlsdCBoYXZlIG5vdCBiZWVuIHRvbyB2aXN1YWxseSBhcHBlYWxpbmcgLS0gdGhleSBhcmUgY3Jvd2RlZCBieSBoYXZpbmcgdG9vIG1hbnkgd29yZHMgZGlzcGxheWVkLCBhbmQgdGhleSBkb24ndCB0YWtlIGFkdmFudGFnZSBvZiBjb2xvci4gT25lIGltcG9ydGFudCBzdGVwIHRvIGJ1aWxkaW5nIHZpc3VhbGx5IGFwcGVhbGluZyB2aXN1YWxpemF0aW9ucyBpcyB0byBleHBlcmltZW50IHdpdGggdGhlIHBhcmFtZXRlcnMgYXZhaWxhYmxlLCB3aGljaCBpbiB0aGlzIGNhc2UgY2FuIGJlIHZpZXdlZCBieSB0eXBpbmcgP3dvcmRjbG91ZCBpbiB5b3VyIFIgY29uc29sZS4gSW4gdGhpcyBwcm9ibGVtLCB5b3Ugc2hvdWxkIGxvb2sgdGhyb3VnaCB0aGUgaGVscCBwYWdlIGFuZCBleHBlcmltZW50IHdpdGggZGlmZmVyZW50IHBhcmFtZXRlcnMgdG8gYW5zd2VyIHRoZSBxdWVzdGlvbnMuDQoNCkJlbG93IGFyZSBmb3VyIHdvcmQgY2xvdWRzLCBlYWNoIG9mIHdoaWNoIHVzZXMgZGlmZmVyZW50IHBhcmFtZXRlciBzZXR0aW5ncyBpbiB0aGUgY2FsbCB0byB0aGUgd29yZGNsb3VkKCkgZnVuY3Rpb246DQoNCioqV29yZCBDbG91ZCBBOioqDQpgYGB7ciBmaWcuaGVpZ2h0PTYsIGZpZy53aWR0aD02fQ0Kd29yZGNsb3VkKGNvbG5hbWVzKGR0bSksIGNvbF9zdW1zKGR0bSksIHNjYWxlPWMoMywgMC40KSwNCiAgICAgICAgICByb3QucGVyPTAuNSkgDQpgYGANCg0KKipXb3JkIENsb3VkIEI6KioNCmBgYHtyIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTR9DQp3b3JkY2xvdWQoY29sbmFtZXMoZHRtKSwgY29sX3N1bXMoZHRtKSwgc2NhbGU9YygzLCAwLjQpLA0KICAgICAgICAgIG1pbi5mcmVxPTgsIHJhbmRvbS5vcmRlcj1GKSAgICAgIyBCDQpgYGANCg0KKipXb3JkIENsb3VkIEM6KioNCmBgYHtyIGZpZy5oZWlnaHQ9NCwgZmlnLndpZHRoPTR9DQpkdG0xID0gZHRtW3R3JEF2ZyA8PSAtMSxdDQp3b3JkY2xvdWQoY29sbmFtZXMoZHRtMSksIGNvbF9zdW1zKGR0bTEpLCBzY2FsZT1jKDMsIDAuNCksDQogICAgICAgICAgY29sb3JzID0gYnJld2VyLnBhbCg5LCJQdXJwbGVzIilbNjo5XSApICMgQw0KYGBgDQoNCioqV29yZCBDbG91ZCBEOioqDQpgYGB7ciBmaWcuaGVpZ2h0PTQsIGZpZy53aWR0aD00fQ0Kd29yZGNsb3VkKGNvbG5hbWVzKGR0bSksIGNvbF9zdW1zKGR0bSksIHNjYWxlPWMoMywgMC40KSwNCiAgbWluLmZyZXE9OCwgcmFuZG9tLm9yZGVyPUYsIHJhbmRvbS5jb2xvcj1ULA0KICBjb2xvcnMgPSBicmV3ZXIucGFsKDksIlB1cnBsZXMiKVs2OjldICkgIyBEDQpgYGANCg0KIyMjIyMgMy4xIA0KX1doaWNoIHdvcmQgY2xvdWQgaXMgYmFzZWQgb25seSBvbiB0aGUgbmVnYXRpdmUgdHdlZXRzICh0d2VldHMgd2l0aCBBdmcgdmFsdWUgLTEgb3IgbGVzcyk/Xw0KDQorIFdvcmQgQ2xvdWQgQyANCg0KIyMjIyMgMy4yIA0KT25seSBvbmUgd29yZCBjbG91ZCB3YXMgY3JlYXRlZCB3aXRob3V0IG1vZGlmeWluZyBwYXJhbWV0ZXJzIG1pbi5mcmVxIG9yIG1heC53b3Jkcy4gV2hpY2ggd29yZCBjbG91ZCBpcyB0aGlzP18NCg0KKyBXb3JkIENsb3VkIEENCg0KIyMjIyMgMy4zDQpfV2hpY2ggd29yZCBjbG91ZHMgd2VyZSBjcmVhdGVkIHdpdGggcGFyYW1ldGVyIHJhbmRvbS5vcmRlciBzZXQgdG8gRkFMU0U/Xw0KDQorIFdvcmQgQ2xvdWQgQg0KKyBXb3JkIENsb3VkIEQNCg0KIyMjIyMgMy40DQpfV2hpY2ggd29yZCBjbG91ZCB3YXMgYnVpbHQgd2l0aCBhIG5vbi1kZWZhdWx0IHZhbHVlIGZvciBwYXJhbWV0ZXIgcm90LnBlcj9fDQoNCisgV29yZCBDbG91ZCBBDQoNCiMjIyMjIDMuNQ0KSW4gV29yZCBDbG91ZCBDIGFuZCBXb3JkIENsb3VkIEQsIHdlIHByb3ZpZGVkIGEgY29sb3IgcGFsZXR0ZSByYW5naW5nIGZyb20gbGlnaHQgcHVycGxlIHRvIGRhcmsgcHVycGxlIGFzIHRoZSBwYXJhbWV0ZXIgY29sb3JzICh5b3Ugd2lsbCBsZWFybiBob3cgdG8gbWFrZSBzdWNoIGEgY29sb3IgcGFsZXR0ZSBsYXRlciBpbiB0aGlzIGFzc2lnbm1lbnQpLiBGb3IgX3doaWNoIHdvcmQgY2xvdWQgd2FzIHRoZSBwYXJhbWV0ZXIgYHJhbmRvbS5jb2xvcmAgc2V0IHRvIGBUUlVFYD9fDQoNCisgV29yZCBDbG91ZCBEDQoNCjxicj48aHI+DQoNCiMjIyA0LiBTZWxlY3RpbmcgYSBDb2xvciBQYWxldHRlDQpUaGUgdXNlIG9mIGEgcGFsZXR0ZSBvZiBjb2xvcnMgY2FuIG9mdGVuIGltcHJvdmUgdGhlIG92ZXJhbGwgZWZmZWN0IG9mIGEgdmlzdWFsaXphdGlvbi4gV2UgY2FuIGVhc2lseSBzZWxlY3Qgb3VyIG93biBjb2xvcnMgd2hlbiBwbG90dGluZzsgZm9yIGluc3RhbmNlLCB3ZSBjb3VsZCBwYXNzIGMoInJlZCIsICJncmVlbiIsICJibHVlIikgYXMgdGhlIGNvbG9ycyBwYXJhbWV0ZXIgdG8gd29yZGNsb3VkKCkuIFRoZSBSQ29sb3JCcmV3ZXIgcGFja2FnZSwgd2hpY2ggaXMgYmFzZWQgb24gdGhlIENvbG9yQnJld2VyIHByb2plY3QgKGNvbG9yYnJld2VyLm9yZyksIHByb3ZpZGVzIHByZS1zZWxlY3RlZCBwYWxldHRlcyB0aGF0IGNhbiBsZWFkIHRvIG1vcmUgdmlzdWFsbHkgYXBwZWFsaW5nIGltYWdlcy4gVGhvdWdoIHRoZXNlIHBhbGV0dGVzIGFyZSBkZXNpZ25lZCBzcGVjaWZpY2FsbHkgZm9yIGNvbG9yaW5nIG1hcHMsIHdlIGNhbiBhbHNvIHVzZSB0aGVtIGluIG91ciB3b3JkIGNsb3VkcyBhbmQgb3RoZXIgdmlzdWFsaXphdGlvbnMuDQoNCkJlZ2luIGJ5IGluc3RhbGxpbmcgYW5kIGxvYWRpbmcgdGhlICJSQ29sb3JCcmV3ZXIiIHBhY2thZ2UuIFRoaXMgcGFja2FnZSBtYXkgaGF2ZSBhbHJlYWR5IGJlZW4gaW5zdGFsbGVkIGFuZCBsb2FkZWQgd2hlbiB5b3UgaW5zdGFsbGVkIGFuZCBsb2FkZWQgdGhlICJ3b3JkY2xvdWQiIHBhY2thZ2UsIGluIHdoaWNoIGNhc2UgeW91IGRvbid0IG5lZWQgdG8gZ28gdGhyb3VnaCB0aGlzIGFkZGl0aW9uYWwgaW5zdGFsbGF0aW9uIHN0ZXAuIElmIHlvdSBvYnRhaW4gZXJyb3JzIChmb3IgaW5zdGFuY2UsICJFcnJvcjogbGF6eS1sb2FkIGRhdGFiYXNlICdQJyBpcyBjb3JydXB0IikgYWZ0ZXIgaW5zdGFsbGluZyBhbmQgbG9hZGluZyB0aGUgUkNvbG9yQnJld2VyIHBhY2thZ2UgYW5kIHJ1bm5pbmcgc29tZSBvZiB0aGUgY29tbWFuZHMsIHRyeSBjbG9zaW5nIGFuZCByZS1vcGVuaW5nIFIuDQoNClRoZSBmdW5jdGlvbiBicmV3ZXIucGFsKCkgcmV0dXJucyBjb2xvciBwYWxldHRlcyBmcm9tIHRoZSBDb2xvckJyZXdlciBwcm9qZWN0IHdoZW4gcHJvdmlkZWQgd2l0aCBhcHByb3ByaWF0ZSBwYXJhbWV0ZXJzLCBhbmQgdGhlIGZ1bmN0aW9uIGRpc3BsYXkuYnJld2VyLmFsbCgpIGRpc3BsYXlzIHRoZSBwYWxldHRlcyB3ZSBjYW4gY2hvb3NlIGZyb20uDQoNCiMjIyMjIDQuMSANCl9XaGljaCBjb2xvciBwYWxldHRlIHdvdWxkIGJlIG1vc3QgYXBwcm9wcmlhdGUgZm9yIHVzZSBpbiBhIHdvcmQgY2xvdWQgZm9yIHdoaWNoIHdlIHdhbnQgdG8gdXNlIGNvbG9yIHRvIGluZGljYXRlIHdvcmQgZnJlcXVlbmN5P18NCmBgYHtyIGZpZy5oZWlnaHQ9OH0NCmxpYnJhcnkoUkNvbG9yQnJld2VyKQ0KZGlzcGxheS5icmV3ZXIuYWxsKCkNCg0KIyBZbE9yUmQNCmBgYA0KDQojIyMjIyA0LjINCldoaWNoIFJDb2xvckJyZXdlciBwYWxldHRlIG5hbWUgd291bGQgYmUgbW9zdCBhcHByb3ByaWF0ZSB0byB1c2Ugd2hlbiBwcmVwYXJpbmcgYW4gaW1hZ2UgZm9yIGEgZG9jdW1lbnQgdGhhdCBtdXN0IGJlIGluIGdyYXlzY2FsZT8NCmBgYHtyfQ0KZGlzcGxheS5icmV3ZXIucGFsKDcsICJHcmV5cyIpDQojIEdyZXlzDQpgYGANCg0KIyMjIyMgNC4zIA0KbiBzZXF1ZW50aWFsIHBhbGV0dGVzLCBzb21ldGltZXMgdGhlcmUgaXMgYW4gdW5kZXNpcmFibHkgbGFyZ2UgY29udHJhc3QgYmV0d2VlbiB0aGUgbGlnaHRlc3QgYW5kIGRhcmtlc3QgY29sb3JzLiBZb3UgY2FuIHNlZSB0aGlzIGVmZmVjdCB3aGVuIHBsb3R0aW5nIGEgd29yZCBjbG91ZCBmb3IgYWxsVHdlZXRzIHdpdGggcGFyYW1ldGVyIGNvbG9ycz1icmV3ZXIucGFsKDksICJCbHVlcyIpLCB3aGljaCByZXR1cm5zIGEgc2VxdWVudGlhbCBibHVlIHBhbGV0dGUgd2l0aCA5IGNvbG9ycy4NCg0KX1doaWNoIG9mIHRoZSBmb2xsb3dpbmcgY29tbWFuZHMgYWRkcmVzc2VzIHRoaXMgaXNzdWUgYnkgcmVtb3ZpbmcgdGhlIGZpcnN0IDQgZWxlbWVudHMgb2YgdGhlIDktY29sb3IgcGFsZXR0ZSBvZiBibHVlIGNvbG9ycz8gU2VsZWN0IGFsbCB0aGF0IGFwcGx5Ll8NCg0KYGBge3J9DQpicmV3ZXIucGFsKDksICJCbHVlcyIpW2MoNSw2LDcsOCw5KV0gDQpgYGANCg0KYGBge3J9DQpicmV3ZXIucGFsKDksICJCbHVlcyIpW2MoLTEsLTIsLTMsLTQpXSANCmBgYA0KPGJyPg0KDQotIC0gLQ0KDQo8YnI+PGJyPjxicj48YnI+PGJyPg0KDQo8c3R5bGU+DQouY2FwdGlvbiB7DQogIGNvbG9yOiAjNzc3Ow0KICBtYXJnaW4tdG9wOiAxMHB4Ow0KfQ0KcCBjb2RlIHsNCiAgd2hpdGUtc3BhY2U6IGluaGVyaXQ7DQp9DQpwcmUgew0KICB3b3JkLWJyZWFrOiBub3JtYWw7DQogIHdvcmQtd3JhcDogbm9ybWFsOw0KICBsaW5lLWhlaWdodDogMTsNCn0NCnByZSBjb2RlIHsNCiAgd2hpdGUtc3BhY2U6IGluaGVyaXQ7DQp9DQpwLGxpIHsNCiAgZm9udC1mYW1pbHk6ICJUcmVidWNoZXQgTVMiLCAi5b6u6Luf5q2j6buR6auUIiwgIk1pY3Jvc29mdCBKaGVuZ0hlaSI7DQp9DQoNCi5yew0KICBsaW5lLWhlaWdodDogMS4yOw0KfQ0KDQp0aXRsZXsNCiAgY29sb3I6ICNjYzAwMDA7DQogIGZvbnQtZmFtaWx5OiAiVHJlYnVjaGV0IE1TIiwgIuW+rui7n+ato+m7kemrlCIsICJNaWNyb3NvZnQgSmhlbmdIZWkiOw0KfQ0KDQpib2R5ew0KICBmb250LWZhbWlseTogIlRyZWJ1Y2hldCBNUyIsICLlvq7ou5/mraPpu5Hpq5QiLCAiTWljcm9zb2Z0IEpoZW5nSGVpIjsNCn0NCg0KaDEsaDIsaDMsaDQsaDV7DQogIGNvbG9yOiAjMDA4ODAwOw0KICBmb250LWZhbWlseTogIlRyZWJ1Y2hldCBNUyIsICLlvq7ou5/mraPpu5Hpq5QiLCAiTWljcm9zb2Z0IEpoZW5nSGVpIjsNCn0NCg0KaDN7DQogIGNvbG9yOiAjYjM2YjAwOw0KICBiYWNrZ3JvdW5kOiAjZmZlMGIzOw0KICBsaW5lLWhlaWdodDogMjsNCiAgZm9udC13ZWlnaHQ6IGJvbGQ7DQp9DQoNCmg1ew0KICBjb2xvcjogIzAwNjAwMDsNCiAgYmFja2dyb3VuZDogI2ZmZmZlMDsNCiAgbGluZS1oZWlnaHQ6IDI7DQogIGZvbnQtd2VpZ2h0OiBib2xkOw0KfQ0KDQplbXsNCiAgY29sb3I6ICMwMDAwYzA7DQogIGJhY2tncm91bmQ6ICNmMGYwZjA7DQogIH0NCjwvc3R5bGU+DQoNCg==