file_name <- 'E:/Khole/Downloads/business1.json'
business<-jsonlite::stream_in(textConnection(readLines(file_name, n=300000)), flatten = TRUE)
Found 1 records...
Imported 1 records. Simplifying...
file_name <- 'E:Khole/Downloads/review1.json'
review<-jsonlite::stream_in(textConnection(readLines(file_name, n=280984)), flatten = TRUE)
Found 1 records...
Imported 1 records. Simplifying...
##check data types
glimpse(business)
Rows: 50,000
Columns: 58
$ business_id <chr> "K0i8UwxEYFv8mqHl7jAkrg", "o7cEZApxvuyaWpHI1d-_cg", "4nJWUXQqm8vxubgC_0AcCQ", "psKq1NDfgIoON5DAXwuTlg",~
$ name <chr> "Any Lab Test Now Glendale", "Cantine Poincaré", "Big Moe's Burgers", "Apple Store", "Gourmand's", "Que~
$ address <chr> "18205 N 51st Ave, Ste 143", "1071 Boul St-Laurent", "3517 Kennedy Road", "3265 W Market St", "5345 Can~
$ city <chr> "AZ", "Montréal", "Scarborough", "Akron", "Valley View", "Henderson", "Calgary", "Phoenix", "Phoenix", ~
$ state <chr> "AZ", "QC", "ON", "OH", "OH", "NV", "AB", "AZ", "AZ", "NC", "ON", "AZ", "AZ", "AB", "NV", "NC", "QC", "~
$ postal_code <chr> "85308", "H2Z 1J6", "M1V 4S4", "44333", "44125", "89074", "T2G 0X5", "85034", "85022", "28213", "M5C 2G~
$ latitude <dbl> 33.6523, 45.5085, 43.8230, 41.1560, 41.4151, 36.0358, 51.0425, 33.4361, 33.6068, 35.2949, 43.6507, 33.6~
$ longitude <dbl> -112.1683, -73.5603, -79.3064, -81.6377, -81.6322, -115.0877, -114.0630, -111.9950, -112.0657, -80.7475~
$ stars <dbl> 4.0, 4.0, 3.0, 2.0, 4.5, 2.0, 4.0, 1.5, 4.5, 4.0, 4.0, 4.0, 5.0, 3.5, 4.0, 3.5, 3.5, 5.0, 4.0, 3.5, 3.0~
$ review_count <int> 4, 7, 87, 4, 82, 74, 5, 698, 13, 4, 36, 23, 13, 3, 48, 7, 24, 60, 11, 3, 8, 5, 8, 9, 3, 5, 3, 30, 5, 9,~
$ is_open <int> 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1~
$ categories <chr> "Diagnostic Services, Laboratory Testing, Health & Medical", "Breweries, Food, Gastropubs, Brewpubs, Re~
$ attributes.BusinessAcceptsCreditCards <chr> "True", NA, NA, NA, "True", "True", NA, NA, "True", "True", NA, "True", NA, NA, "True", "True", NA, "Tr~
$ attributes.BikeParking <chr> NA, NA, "True", "False", "True", NA, NA, NA, "True", NA, NA, NA, NA, NA, "False", NA, "True", NA, NA, N~
$ attributes.GoodForKids <chr> NA, NA, "True", NA, "True", NA, NA, NA, NA, "True", NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,~
$ attributes.BusinessParking <chr> NA, NA, "{'garage': False, 'street': False, 'validated': False, 'lot': True, 'valet': False}", NA, "{'g~
$ attributes.RestaurantsPriceRange2 <chr> NA, NA, "1", "2", "1", NA, NA, NA, "2", "2", NA, NA, NA, NA, "3", "1", "2", NA, NA, NA, "2", NA, "1", "~
$ attributes.WiFi <chr> NA, NA, "u'no'", NA, "u'no'", NA, NA, "u'free'", NA, NA, "u'free'", NA, NA, NA, NA, NA, NA, NA, NA, NA,~
$ attributes.RestaurantsAttire <chr> NA, NA, "u'casual'", NA, "u'casual'", NA, NA, NA, NA, "'casual'", NA, NA, NA, NA, NA, NA, NA, NA, NA, N~
$ attributes.RestaurantsTakeOut <chr> NA, NA, "True", NA, "True", NA, NA, NA, NA, "True", NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,~
$ attributes.NoiseLevel <chr> NA, NA, "u'average'", NA, "'average'", NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, "u'loud'", NA, NA, N~
$ attributes.RestaurantsReservations <chr> NA, NA, "False", NA, "False", NA, NA, NA, NA, "True", NA, NA, NA, NA, NA, NA, "False", NA, NA, NA, NA, ~
$ attributes.RestaurantsGoodForGroups <chr> NA, NA, "False", NA, "False", NA, NA, NA, NA, "True", NA, NA, NA, NA, NA, NA, "True", NA, NA, NA, NA, N~
$ attributes.HasTV <chr> NA, NA, "True", NA, "True", NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, "True", NA, NA, NA, NA, NA, NA,~
$ attributes.Alcohol <chr> NA, NA, "u'none'", NA, "'beer_and_wine'", NA, NA, NA, NA, "u'full_bar'", NA, NA, NA, NA, NA, NA, "u'ful~
$ attributes.RestaurantsDelivery <chr> NA, NA, "False", NA, "False", NA, NA, NA, NA, "False", NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, ~
$ attributes.OutdoorSeating <chr> NA, NA, "False", NA, "False", NA, NA, NA, NA, "True", NA, NA, NA, NA, NA, NA, "True", NA, NA, NA, "Fals~
$ attributes.Caters <chr> NA, NA, "False", NA, "True", NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, "False", NA, N~
$ attributes.Ambience <chr> NA, NA, "{'romantic': False, 'intimate': False, 'classy': False, 'hipster': False, 'divey': False, 'tou~
$ attributes.RestaurantsTableService <chr> NA, NA, NA, NA, "False", NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA~
$ attributes.GoodForMeal <chr> NA, NA, NA, NA, "{'dessert': False, 'latenight': False, 'lunch': True, 'dinner': False, 'brunch': False~
$ attributes.ByAppointmentOnly <chr> NA, NA, NA, NA, NA, "False", NA, NA, "False", NA, NA, NA, "True", "False", "False", NA, NA, "False", NA~
$ attributes.AcceptsInsurance <chr> NA, NA, NA, NA, NA, "True", NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, "True", NA, NA, NA, NA, NA, NA,~
$ attributes.BusinessAcceptsBitcoin <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, "False", NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA~
$ attributes.DogsAllowed <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, "False", NA, NA, NA, NA, NA, NA, NA, NA, NA~
$ attributes.HappyHour <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, "True", NA, NA, NA, NA, NA, NA, NA, NA,~
$ attributes.WheelchairAccessible <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, "True", NA, NA,~
$ attributes.DriveThru <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,~
$ attributes.GoodForDancing <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,~
$ attributes.CoatCheck <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,~
$ attributes.Music <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,~
$ attributes.Corkage <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,~
$ attributes.BYOBCorkage <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,~
$ attributes.HairSpecializesIn <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,~
$ attributes.BestNights <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,~
$ attributes.Smoking <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,~
$ attributes.BYOB <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,~
$ attributes.AgesAllowed <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,~
$ attributes.DietaryRestrictions <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,~
$ attributes.RestaurantsCounterService <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,~
$ attributes.Open24Hours <chr> NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA, NA,~
$ hours.Monday <chr> "8:0-18:0", NA, "11:0-22:0", NA, "6:30-15:0", "6:0-17:0", NA, "4:0-0:0", "8:30-18:0", NA, NA, "0:0-0:0"~
$ hours.Tuesday <chr> "8:0-18:0", NA, "11:0-22:0", NA, "6:30-15:0", "6:0-17:0", NA, "4:0-0:0", "8:30-18:0", NA, NA, "0:0-0:0"~
$ hours.Wednesday <chr> "8:0-18:0", NA, "11:0-22:0", NA, "6:30-15:0", "6:0-17:0", NA, "4:0-0:0", "8:30-18:0", NA, NA, "0:0-0:0"~
$ hours.Thursday <chr> "8:0-18:0", NA, "11:0-22:0", NA, "6:30-15:0", "6:0-17:0", NA, "4:0-0:0", "8:30-18:0", NA, NA, "0:0-0:0"~
$ hours.Friday <chr> "8:0-18:0", NA, "11:0-0:0", NA, "6:30-15:0", "6:0-17:0", NA, "4:0-0:0", "8:30-18:0", NA, NA, "0:0-0:0",~
$ hours.Saturday <chr> "9:0-14:0", NA, "11:0-0:0", NA, NA, NA, NA, "4:0-0:0", "8:30-17:0", NA, NA, "0:0-0:0", NA, "10:0-16:0",~
$ hours.Sunday <chr> NA, NA, "12:0-21:0", NA, NA, NA, NA, "4:0-0:0", NA, NA, NA, "0:0-0:0", NA, NA, NA, NA, "10:0-3:0", NA, ~
### Let's examine the structure of certain columns
### We will ignore anything with "hours" or "attribute" it's the name
business %>%
select(-starts_with("hours"), -starts_with("attribute"))
### Let's count how many restaurants there are
### We will detect the term "Restaurant" using the str_detect function then retain (using "filter")only the rows that have the term restaurant.
business %>%
select(-starts_with("hours"), -starts_with("attribute")) %>%
filter(str_detect(categories, "Restaurant"))
### Let's examine rows in the Categories that have the term restaurant
### Show the full list of restaurants in the categories column
business %>%
select(categories) %>%
filter(str_detect(categories, "Restaurant"))
#*Lets separate the items in the categories list and put each item on a separate row
#we will then display the name of each restaurant and their categories in separate rows
business %>%
filter(str_detect(categories, "Restaurant")) %>%
mutate(categories=str_split(categories, ",")) %>% ## Split the elements where there is a comma.This will create a list
unnest(categories)%>%## Place each list element onto a separate row
select(name, categories)
###*We would like to group and count the number of different categories associated with restaurants. Notice that there is a space before some of the categories
###This may be problematic if we try to group and count the observations.
###Let's remove all unnecessary spaces
business %>%
filter(str_detect(categories, "Restaurant")) %>%
mutate(categories=str_split(categories, ",")) %>%
unnest(categories)%>%
select(name, categories) %>%
mutate(categories=str_trim(categories))%>% ## create a new variable called categories and remove whitespace from start and end of categories
mutate(categories=str_squish(categories)) ##create a new variable called categories and reduce repeated whitespace inside a string.
### *Let's count the number of different categories associated with restaurants and arrange them from most to least
business %>%
filter(str_detect(categories, "Restaurant")) %>%
mutate(categories=str_split(categories, ",")) %>%
unnest(categories)%>%
select(name, categories) %>%
mutate(categories=str_trim(str_squish(categories))) %>%
count(categories)%>% ## count the unique values in the categories column
arrange (desc(n)) ##sort (in descending order) a data frame by the number of items is each category
###Let's see which are the most popular types of categories in each state or province
###We will filter out the words "Restaurant" and "Food" since those are likely to be common for all the establishments
business %>%
select(-starts_with("hours"), -starts_with("attribute")) %>%
filter(str_detect(categories, "Restaurant")) %>%
mutate(categories=strsplit(categories, ",")) %>%
unnest(categories)%>%
select(name, categories) %>%
mutate(categories=str_trim(str_squish(categories))) %>%
filter (!categories %in% c("Restaurants", "Food")) %>% ## filter out multiple categories i.e. Restaurants and Food
count(categories)%>%
arrange(desc(n))
### Now let's answer the following questions
##1. Show the number of different categories other than Restaurants and food, in each state/province ?
business %>%
## filter(str_detect(categories, "Restaurant")) %>%
mutate(categories=strsplit(categories, ",")) %>%
unnest(categories)%>%
select(state, categories) %>%
mutate(categories=str_trim(str_squish(categories))) %>%
filter (!categories %in% c("Restaurants", "Food")) %>% ##filter out multiple categories
group_by(state,categories)%>%
count(categories)%>%
arrange(state,desc(n))
##2. How many establishments are there in each state that have the word Restaurants as one of their categories?
business %>%
filter(str_detect(categories, "Restaurant")) %>%
mutate(categories=strsplit(categories, ",")) %>%
unnest(categories)%>%
select(state, categories) %>%
mutate(categories=str_trim(str_squish(categories))) %>%
filter(categories =="Restaurants") %>%
group_by(state,categories)%>%
count(categories)%>%
arrange(desc(n))
#let's pivot the categories column and create a new wider dataset called business_wider that has categories as columns i.e dummyy variables from the categories column
business_wide<-business%>%
mutate(categories = strsplit(categories, ", ")) %>%
unnest(categories) %>%
arrange(categories) %>%
pivot_wider(names_from = categories,
names_prefix = "categories_",
names_repair = "universal",
values_from = categories,
values_fill = 0,
values_fn = length)
New names:
* `categories_& Probates` -> categories_..Probates
* `categories_3D Printing` -> categories_3D.Printing
* `categories_Acai Bowls` -> categories_Acai.Bowls
* `categories_Acne Treatment` -> categories_Acne.Treatment
* `categories_Active Life` -> categories_Active.Life
* ...
##2. How many establishments are there in each state that have the word Restaurants as one of their categories?
business_wide%>%
filter(categories_Restaurants==1)%>%
select(name, state )%>%
group_by(state)%>%
count(state)%>%
arrange(desc(n))
#3.How many records are there for each state/Province
business_wide%>%
select(name, state )%>%
group_by(state)%>%
count(state)%>%
arrange(desc(n))
#4. How many establishment are open
business_wide%>%
select(is_open) %>%
count(is_open)%>%
group_by(is_open)%>%
arrange(desc(n))
## 5. How many establishments are open and how many are closed in each state. Sort ascending by state and whether or not they are open
business_wide %>%
select(state, is_open) %>%
group_by(state, is_open)%>%
count(is_open)%>%
arrange(state,desc(is_open))
## 6. Show the top 10 states in terms of median star review scores. Organize them in descending order
business_wide %>%
type_convert(cols(stars = col_double()))%>% ##convert the stars column to a double
select(state,stars) %>%
group_by(state)%>%
summarize(Stars=median(stars))%>%
arrange(desc(Stars))%>%
head(10)
##7. Show the bottom 5 states in terms of median star review scores. Also show the median number of review scores that they have received
business_wide %>%
type_convert(cols(stars = col_double(), review_count = col_integer()))%>% ##convert the stars column to a double
select(state,review_count, stars) %>%
group_by(state)%>%
summarize(Median_Stars=median(stars), Number_of_Reviews=median(review_count))%>%
arrange(Median_Stars)%>%
head(5)
##8. Show the establishments with the most number of 5 star reviews (top 5)
review %>%
filter(stars == 5) %>%
group_by(business_id) %>%
summarise(Count = n()) %>%
arrange(desc(Count)) %>%
head(5)%>%
inner_join(business_wide)
Joining, by = "business_id"
### 9. Which 5 business appears the most number of times in the data set. Order the businesses by the number of time they appear
business_wide %>%
group_by(name)%>%
summarise(Count = n()) %>%
arrange(desc(Count))%>%
head(5)
## Let's analyze Starbucks given that it the most popular company in the dataset
### we will create a new tibble called StarbucksJoined_tbl that has all the Starbucks business data and their reviews
starbucksbusiness=business %>%
filter(name=="Starbucks")
StarbucksJoined_tbl <-tibble( inner_join(starbucksbusiness,review))
Joining, by = c("business_id", "stars")
#10. Show the number of Starbucks in each State
StarbucksJoined_tbl%>%
group_by(state)%>%
summarise(Count = n()) %>%
arrange(desc(Count))
## 11. What is the number and percentage of visitors to Yelp's site who find the reviews for Starbucks useful
StarbucksJoined_tbl %>%
group_by(useful) %>%
summarise(Count = n()) %>%
arrange(desc(Count)) %>%
mutate(Percentage = round(Count/sum(Count)*100,2)) %>%
head(10)
## 11. What is the number and percentage of visitors to Yelp's site who find the reviews for Starbucks funny
StarbucksJoined_tbl %>%
group_by(funny) %>%
summarise(Count = n()) %>%
arrange(desc(Count)) %>%
mutate(Percentage = round(Count/sum(Count)*100,2)) %>%
head(10)
## 11. What is the number and percentage of visitors to Yelp's site who find the reviews for Starbucks useful
StarbucksJoined_tbl %>%
group_by(cool) %>%
summarise(Count = n()) %>%
arrange(desc(Count)) %>%
mutate(Percentage = round(Count/sum(Count)*100,2)) %>%
head(10)
###Let's analyze the most negative reviews and the most positive reviews. What are customers from each group saying
### to identify the most negative and positive reviews we have to 1)assign sentiment scores to each review 2)sort the reviews scores
###1) assigning review scores and store the results in a data frame called CustomerSentiment
install.packages("textdata")
install.packages("textcat")
install.packages("tidytext")
library(textdata)
library(textcat)
library(tidytext)
get_sentiments("afinn")
CustomerSentiment<-StarbucksJoined_tbl %>%
## filter(textcat(text) == "english") %>% # considering only English text. Omit this line if you want faster processing.
unnest_tokens(word, text) %>% ##Split a text column into words/tokens
anti_join(stop_words)%>% #remove stopwords
inner_join(get_sentiments("afinn"), by = "word") %>% # join the StarbucksJoined_tbl with the afinn lexicon which has 2 columns titled word and value
group_by(review_id) %>%
summarize(sentiment = mean(value),words = n()) %>%
filter(words >= 5) # we will exclude reviews with less than 5 words
Joining, by = "word"
###2) sort the reviews scores and display desired columns(i.e. address,city, date,sentiment,text)
#### Most Negative reviews
CustomerSentiment%>%
arrange(desc(sentiment)) %>%
top_n(-10, sentiment) %>% ## get the lower sentiment scores
inner_join(StarbucksJoined_tbl, by = "review_id") %>%
select(address,city, date,sentiment,text)
#### Most Positive reviews
CustomerSentiment%>%
arrange(desc(sentiment)) %>%
top_n(10, sentiment) %>% ## get the highest sentiment scores
inner_join(StarbucksJoined_tbl, by = "review_id") %>%
select(address,city, date,sentiment,text)
library(lubridate)
### Let's add a formatted Date Column to StarbucksJoined_tbl
## We will use Lubridate reformat the date column ( which is currently stored as chr) and create new columns to multiple columns:
### date, month,day, year,hour
StarbucksDateFormatted<-StarbucksJoined_tbl%>%
mutate(date_formatted = as_date(date),
month_formatted=month(date),
day_formatted=day(date),
year_formatted=year(date),
hour_formatted=hour(date))
### Now lets create some plots for Starbucks
###1. Show the number of reviews for Starbucks over time(years) using a line chart
StarbucksDateFormatted%>%
select(year_formatted)%>%
group_by (year_formatted)%>%
summarise(NumberofReviews = n())%>%
ggplot(aes (x=year_formatted,y=NumberofReviews)) +
geom_line()

###2. Show the number of reviews by State using a column/bar chart
StarbucksDateFormatted%>%
select(state)%>%
group_by(state)%>%
ggplot(aes (x=state)) +
geom_bar()

###3. Create a donut chart that Shows the proportion of businesses that are open vs those that are closed
business %>%
group_by(is_open) %>%
summarise(Count = n())%>%
mutate(is_open=as.factor(is_open), percentage = round(Count/sum(Count)*100,2), LabelPosition = cumsum(percentage)-.1*percentage) %>%
###plot the pie chart with geom_bar() and then convert the bar into pie with the coord_polar() function. to make a donut plot we must specify the x = 2 in aes() and add the xlim() as code
ggplot(
aes(x = 2, y = percentage, fill = is_open))+
geom_bar(stat = "identity")+
coord_polar("y" ) +
geom_text(aes(y = LabelPosition, label = paste(percentage,"%", sep = "")), col = "white") +
theme_void() +
scale_fill_brewer(palette = "Dark2")+
xlim(.2,2.5)

##4. Create a column chart that shows the median number of words used in Starbucks reviews each month
StarbucksDateFormatted %>%
mutate(NumberOfWords=str_count(text, boundary("word"))) %>% ## counts the number of words since boundary is set to word
mutate(MonthName=month(ymd(date_formatted), label = TRUE)) %>%
group_by(MonthName) %>%
summarize(NumberOfWords=median(NumberOfWords)) %>%
ggplot(aes (x=MonthName, y=NumberOfWords)) +
geom_col() +
coord_flip()

### 5. Create a chart that shows the relationship between funny and useful reviews of Starbucks.
StarbucksDateFormatted%>%
ggplot(aes(x=funny, y=useful)) +
geom_point()+
geom_smooth(method=lm)

### 6. Create a wordcloud to show the words that are used the most frequently in reviews
library(wordcloud)
Loading required package: RColorBrewer
createWordCloud = function(x)
{
StarbucksDateFormatted %>%
unnest_tokens(word, text) %>%
filter(!word %in% stop_words$word, !word %in% 'starbucks') %>%
### filter(!word = 'starbucks') %>%
count(word,sort = TRUE) %>%
ungroup() %>%
head(30) %>%
with(wordcloud(word, n, max.words = 30,colors=brewer.pal(8, "Dark2")))
}
createWordCloud(review)

### 7.Sentiment Analysis : The Positive and negative words associate with Starbucks reviews
positiveWordsBarGraph <- function(SC) {
contributions <- SC %>%
unnest_tokens(word, text) %>%
count(word,sort = TRUE) %>%
ungroup() %>%
inner_join(get_sentiments("afinn"), by = "word") %>%
group_by(word) %>%
summarize(contribution = sum(value), n=n())
contributions %>%
top_n(20, abs(contribution)) %>%
mutate(word = reorder(word, contribution)) %>%
head(20) %>%
ggplot(aes(word, contribution, fill = contribution > 0)) +
geom_col(show.legend = FALSE) +
coord_flip() + theme_bw()
}
positiveWordsBarGraph(StarbucksJoined_tbl)

###8. Show all the Starbucks locations on a map
### we will use the starbucksbusiness data set that we created earlier because it does not have repeating reviews each record is unique
library(leaflet)
### this function will color each location based on the star rating it has
pal <- colorFactor(c("purple", "red", "orange", "black","blue"),
domain = unique(starbucksbusiness$stars))
### this draws the map
map <- leaflet(starbucksbusiness) %>%
addProviderTiles("CartoDB.Positron") %>%
addCircleMarkers(
color = ~pal(starbucksbusiness$stars),
stroke = FALSE, fillOpacity = 0.5,
lat = starbucksbusiness$latitude,
lng = starbucksbusiness$longitude,
clusterOptions = markerClusterOptions(),
popup = as.character(starbucksbusiness$address))
map
#Let's join the business and review tables so that we can create a dashboard
business_reviews <- inner_join(business,review)
LS0tDQp0aXRsZTogIlIgTm90ZWJvb2siDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KLS0tDQp0aXRsZTogIkV4cGxvcmluZyB0aGUgWWVscCBEYXRhc2V0Ig0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KDQpgYGB7cn0NCmZpbGVfbmFtZSA8LSAnRTovS2hvbGUvRG93bmxvYWRzL2J1c2luZXNzMS5qc29uJw0KYnVzaW5lc3M8LWpzb25saXRlOjpzdHJlYW1faW4odGV4dENvbm5lY3Rpb24ocmVhZExpbmVzKGZpbGVfbmFtZSwgbj0zMDAwMDApKSwgZmxhdHRlbiA9IFRSVUUpDQpgYGANCg0KYGBge3J9DQpmaWxlX25hbWUgPC0gJ0U6S2hvbGUvRG93bmxvYWRzL3JldmlldzEuanNvbicNCnJldmlldzwtanNvbmxpdGU6OnN0cmVhbV9pbih0ZXh0Q29ubmVjdGlvbihyZWFkTGluZXMoZmlsZV9uYW1lLCBuPTI4MDk4NCkpLCBmbGF0dGVuID0gVFJVRSkNCmBgYA0KDQpgYGB7cn0NCiMjY2hlY2sgZGF0YSB0eXBlcw0KZ2xpbXBzZShidXNpbmVzcykNCmBgYA0KDQpgYGB7cn0NCiMjIyBMZXQncyBleGFtaW5lIHRoZSBzdHJ1Y3R1cmUgb2YgY2VydGFpbiBjb2x1bW5zDQojIyMgV2Ugd2lsbCBpZ25vcmUgYW55dGhpbmcgd2l0aCAiaG91cnMiIG9yICJhdHRyaWJ1dGUiIGl0J3MgdGhlIG5hbWUNCmJ1c2luZXNzICU+JQ0Kc2VsZWN0KC1zdGFydHNfd2l0aCgiaG91cnMiKSwgLXN0YXJ0c193aXRoKCJhdHRyaWJ1dGUiKSkNCmBgYA0KDQpgYGB7cn0NCiMjIyBMZXQncyBjb3VudCBob3cgbWFueSByZXN0YXVyYW50cyB0aGVyZSBhcmUNCiMjIyBXZSB3aWxsIGRldGVjdCB0aGUgdGVybSAiUmVzdGF1cmFudCIgdXNpbmcgdGhlIHN0cl9kZXRlY3QgZnVuY3Rpb24gdGhlbiByZXRhaW4gKHVzaW5nICJmaWx0ZXIiKW9ubHkgdGhlIHJvd3MgdGhhdCBoYXZlIHRoZSB0ZXJtIHJlc3RhdXJhbnQuDQpidXNpbmVzcyAlPiUNCiBzZWxlY3QoLXN0YXJ0c193aXRoKCJob3VycyIpLCAtc3RhcnRzX3dpdGgoImF0dHJpYnV0ZSIpKSAlPiUNCiBmaWx0ZXIoc3RyX2RldGVjdChjYXRlZ29yaWVzLCAiUmVzdGF1cmFudCIpKQ0KYGBgDQpgYGB7cn0NCiMjIyBMZXQncyBleGFtaW5lIHJvd3MgaW4gdGhlIENhdGVnb3JpZXMgdGhhdCBoYXZlIHRoZSB0ZXJtIHJlc3RhdXJhbnQgDQojIyMgU2hvdyB0aGUgZnVsbCBsaXN0IG9mIHJlc3RhdXJhbnRzIGluIHRoZSBjYXRlZ29yaWVzIGNvbHVtbg0KYnVzaW5lc3MgJT4lDQogc2VsZWN0KGNhdGVnb3JpZXMpICU+JQ0KIGZpbHRlcihzdHJfZGV0ZWN0KGNhdGVnb3JpZXMsICJSZXN0YXVyYW50IikpDQpgYGANCmBgYHtyfQ0KIypMZXRzIHNlcGFyYXRlIHRoZSBpdGVtcyBpbiB0aGUgY2F0ZWdvcmllcyBsaXN0IGFuZCBwdXQgZWFjaCBpdGVtIG9uIGEgc2VwYXJhdGUgcm93DQojd2Ugd2lsbCB0aGVuIGRpc3BsYXkgdGhlIG5hbWUgb2YgZWFjaCByZXN0YXVyYW50IGFuZCB0aGVpciBjYXRlZ29yaWVzIGluIHNlcGFyYXRlIHJvd3MNCmJ1c2luZXNzICU+JQ0KIGZpbHRlcihzdHJfZGV0ZWN0KGNhdGVnb3JpZXMsICJSZXN0YXVyYW50IikpICU+JQ0KIG11dGF0ZShjYXRlZ29yaWVzPXN0cl9zcGxpdChjYXRlZ29yaWVzLCAiLCIpKSAlPiUgIyMgU3BsaXQgdGhlIGVsZW1lbnRzIHdoZXJlIHRoZXJlIGlzIGEgY29tbWEuVGhpcyB3aWxsIGNyZWF0ZSBhIGxpc3QNCiB1bm5lc3QoY2F0ZWdvcmllcyklPiUjIyBQbGFjZSBlYWNoIGxpc3QgZWxlbWVudCBvbnRvIGEgc2VwYXJhdGUgcm93DQogc2VsZWN0KG5hbWUsIGNhdGVnb3JpZXMpDQpgYGANCmBgYHtyfQ0KIyMjKldlIHdvdWxkIGxpa2UgdG8gZ3JvdXAgYW5kIGNvdW50IHRoZSBudW1iZXIgb2YgZGlmZmVyZW50IGNhdGVnb3JpZXMgYXNzb2NpYXRlZCB3aXRoIHJlc3RhdXJhbnRzLiBOb3RpY2UgdGhhdCB0aGVyZSBpcyBhIHNwYWNlIGJlZm9yZSBzb21lIG9mIHRoZSBjYXRlZ29yaWVzDQojIyNUaGlzIG1heSBiZSBwcm9ibGVtYXRpYyBpZiB3ZSB0cnkgdG8gZ3JvdXAgYW5kIGNvdW50IHRoZSBvYnNlcnZhdGlvbnMuDQojIyNMZXQncyByZW1vdmUgYWxsIHVubmVjZXNzYXJ5IHNwYWNlcw0KYnVzaW5lc3MgJT4lDQogZmlsdGVyKHN0cl9kZXRlY3QoY2F0ZWdvcmllcywgIlJlc3RhdXJhbnQiKSkgJT4lDQogbXV0YXRlKGNhdGVnb3JpZXM9c3RyX3NwbGl0KGNhdGVnb3JpZXMsICIsIikpICU+JQ0KIHVubmVzdChjYXRlZ29yaWVzKSU+JQ0KIHNlbGVjdChuYW1lLCBjYXRlZ29yaWVzKSAlPiUNCiBtdXRhdGUoY2F0ZWdvcmllcz1zdHJfdHJpbShjYXRlZ29yaWVzKSklPiUgIyMgY3JlYXRlIGEgbmV3IHZhcmlhYmxlIGNhbGxlZCBjYXRlZ29yaWVzIGFuZCByZW1vdmUgd2hpdGVzcGFjZSBmcm9tIHN0YXJ0IGFuZCBlbmQgb2YgY2F0ZWdvcmllcw0KIG11dGF0ZShjYXRlZ29yaWVzPXN0cl9zcXVpc2goY2F0ZWdvcmllcykpICMjY3JlYXRlIGEgbmV3IHZhcmlhYmxlIGNhbGxlZCBjYXRlZ29yaWVzIGFuZCByZWR1Y2UgcmVwZWF0ZWQgd2hpdGVzcGFjZSBpbnNpZGUgYSBzdHJpbmcuDQpgYGANCmBgYHtyfQ0KIyMjICpMZXQncyBjb3VudCB0aGUgbnVtYmVyIG9mIGRpZmZlcmVudCBjYXRlZ29yaWVzIGFzc29jaWF0ZWQgd2l0aCByZXN0YXVyYW50cyBhbmQgYXJyYW5nZSB0aGVtIGZyb20gbW9zdCB0byBsZWFzdA0KYnVzaW5lc3MgJT4lDQogZmlsdGVyKHN0cl9kZXRlY3QoY2F0ZWdvcmllcywgIlJlc3RhdXJhbnQiKSkgJT4lDQogbXV0YXRlKGNhdGVnb3JpZXM9c3RyX3NwbGl0KGNhdGVnb3JpZXMsICIsIikpICU+JQ0KIHVubmVzdChjYXRlZ29yaWVzKSU+JQ0KIHNlbGVjdChuYW1lLCBjYXRlZ29yaWVzKSAlPiUNCiBtdXRhdGUoY2F0ZWdvcmllcz1zdHJfdHJpbShzdHJfc3F1aXNoKGNhdGVnb3JpZXMpKSkgJT4lDQogY291bnQoY2F0ZWdvcmllcyklPiUgIyMgY291bnQgdGhlIHVuaXF1ZSB2YWx1ZXMgaW4gdGhlIGNhdGVnb3JpZXMgY29sdW1uDQogYXJyYW5nZSAoZGVzYyhuKSkgIyNzb3J0IChpbiBkZXNjZW5kaW5nIG9yZGVyKSBhIGRhdGEgZnJhbWUgYnkgdGhlIG51bWJlciBvZiBpdGVtcyBpcyBlYWNoIGNhdGVnb3J5DQpgYGANCg0KYGBge3J9DQojIyNMZXQncyBzZWUgd2hpY2ggYXJlIHRoZSBtb3N0IHBvcHVsYXIgdHlwZXMgb2YgY2F0ZWdvcmllcyBpbiBlYWNoIHN0YXRlIG9yIHByb3ZpbmNlDQojIyNXZSB3aWxsIGZpbHRlciBvdXQgdGhlIHdvcmRzICJSZXN0YXVyYW50IiBhbmQgIkZvb2QiIHNpbmNlIHRob3NlIGFyZSBsaWtlbHkgdG8gYmUgY29tbW9uIGZvciBhbGwgdGhlIGVzdGFibGlzaG1lbnRzDQpidXNpbmVzcyAlPiUgDQogIHNlbGVjdCgtc3RhcnRzX3dpdGgoImhvdXJzIiksIC1zdGFydHNfd2l0aCgiYXR0cmlidXRlIikpICU+JQ0KIGZpbHRlcihzdHJfZGV0ZWN0KGNhdGVnb3JpZXMsICJSZXN0YXVyYW50IikpICU+JQ0KIG11dGF0ZShjYXRlZ29yaWVzPXN0cnNwbGl0KGNhdGVnb3JpZXMsICIsIikpICU+JQ0KIHVubmVzdChjYXRlZ29yaWVzKSU+JQ0KIHNlbGVjdChuYW1lLCBjYXRlZ29yaWVzKSAlPiUNCiBtdXRhdGUoY2F0ZWdvcmllcz1zdHJfdHJpbShzdHJfc3F1aXNoKGNhdGVnb3JpZXMpKSkgJT4lDQpmaWx0ZXIgKCFjYXRlZ29yaWVzICVpbiUgYygiUmVzdGF1cmFudHMiLCAiRm9vZCIpKSAlPiUgIyMgZmlsdGVyIG91dCBtdWx0aXBsZSBjYXRlZ29yaWVzIGkuZS4gUmVzdGF1cmFudHMgYW5kIEZvb2QNCiBjb3VudChjYXRlZ29yaWVzKSU+JQ0KIGFycmFuZ2UoZGVzYyhuKSkNCmBgYA0KDQpgYGB7cn0NCiMjIyBOb3cgbGV0J3MgYW5zd2VyIHRoZSBmb2xsb3dpbmcgcXVlc3Rpb25zDQoNCiMjMS4gU2hvdyB0aGUgbnVtYmVyIG9mIGRpZmZlcmVudCBjYXRlZ29yaWVzIG90aGVyIHRoYW4gUmVzdGF1cmFudHMgYW5kIGZvb2QsIGluIGVhY2ggc3RhdGUvcHJvdmluY2UgPw0KDQoNCmJ1c2luZXNzICU+JQ0KIyMgZmlsdGVyKHN0cl9kZXRlY3QoY2F0ZWdvcmllcywgIlJlc3RhdXJhbnQiKSkgJT4lDQogbXV0YXRlKGNhdGVnb3JpZXM9c3Ryc3BsaXQoY2F0ZWdvcmllcywgIiwiKSkgJT4lDQogdW5uZXN0KGNhdGVnb3JpZXMpJT4lDQogc2VsZWN0KHN0YXRlLCBjYXRlZ29yaWVzKSAlPiUNCiBtdXRhdGUoY2F0ZWdvcmllcz1zdHJfdHJpbShzdHJfc3F1aXNoKGNhdGVnb3JpZXMpKSkgJT4lDQogZmlsdGVyICghY2F0ZWdvcmllcyAlaW4lIGMoIlJlc3RhdXJhbnRzIiwgIkZvb2QiKSkgJT4lICMjZmlsdGVyIG91dCBtdWx0aXBsZSBjYXRlZ29yaWVzDQogZ3JvdXBfYnkoc3RhdGUsY2F0ZWdvcmllcyklPiUNCiBjb3VudChjYXRlZ29yaWVzKSU+JQ0KIGFycmFuZ2Uoc3RhdGUsZGVzYyhuKSkNCmBgYA0KYGBge3J9DQojIzIuIEhvdyBtYW55IGVzdGFibGlzaG1lbnRzIGFyZSB0aGVyZSBpbiBlYWNoIHN0YXRlIHRoYXQgaGF2ZSB0aGUgd29yZCBSZXN0YXVyYW50cyBhcyBvbmUgb2YgdGhlaXIgY2F0ZWdvcmllcz8NCmJ1c2luZXNzICU+JQ0KIGZpbHRlcihzdHJfZGV0ZWN0KGNhdGVnb3JpZXMsICJSZXN0YXVyYW50IikpICU+JQ0KIG11dGF0ZShjYXRlZ29yaWVzPXN0cnNwbGl0KGNhdGVnb3JpZXMsICIsIikpICU+JQ0KIHVubmVzdChjYXRlZ29yaWVzKSU+JQ0KIHNlbGVjdChzdGF0ZSwgY2F0ZWdvcmllcykgJT4lDQogbXV0YXRlKGNhdGVnb3JpZXM9c3RyX3RyaW0oc3RyX3NxdWlzaChjYXRlZ29yaWVzKSkpICU+JQ0KIGZpbHRlcihjYXRlZ29yaWVzID09IlJlc3RhdXJhbnRzIikgJT4lDQogZ3JvdXBfYnkoc3RhdGUsY2F0ZWdvcmllcyklPiUNCiBjb3VudChjYXRlZ29yaWVzKSU+JQ0KIGFycmFuZ2UoZGVzYyhuKSkNCmBgYA0KDQpgYGB7cn0NCg0KI2xldCdzIHBpdm90IHRoZSBjYXRlZ29yaWVzIGNvbHVtbiBhbmQgY3JlYXRlIGEgbmV3IHdpZGVyIGRhdGFzZXQgY2FsbGVkIGJ1c2luZXNzX3dpZGVyIHRoYXQgaGFzIGNhdGVnb3JpZXMgYXMgY29sdW1ucyBpLmUgZHVtbXl5IHZhcmlhYmxlcyBmcm9tIHRoZSBjYXRlZ29yaWVzIGNvbHVtbg0KDQpidXNpbmVzc193aWRlPC1idXNpbmVzcyU+JQ0KICAgICAgIG11dGF0ZShjYXRlZ29yaWVzID0gc3Ryc3BsaXQoY2F0ZWdvcmllcywgIiwgIikpICU+JQ0KICAgICB1bm5lc3QoY2F0ZWdvcmllcykgJT4lIA0KICAgICBhcnJhbmdlKGNhdGVnb3JpZXMpICU+JSAgDQogICAgIHBpdm90X3dpZGVyKG5hbWVzX2Zyb20gPSBjYXRlZ29yaWVzLA0KICAgICAgICAgICAgbmFtZXNfcHJlZml4ID0gImNhdGVnb3JpZXNfIiwNCiAgICAgICAgICAgIG5hbWVzX3JlcGFpciA9ICJ1bml2ZXJzYWwiLCANCiAgICAgICAgICAgIHZhbHVlc19mcm9tID0gY2F0ZWdvcmllcywgDQogICAgICAgICAgICB2YWx1ZXNfZmlsbCA9IDAsIA0KICAgICAgICAgICAgdmFsdWVzX2ZuID0gbGVuZ3RoKQ0KICANCmBgYA0KDQpgYGB7cn0NCiMjMi4gSG93IG1hbnkgZXN0YWJsaXNobWVudHMgYXJlIHRoZXJlIGluIGVhY2ggc3RhdGUgdGhhdCBoYXZlIHRoZSB3b3JkIFJlc3RhdXJhbnRzIGFzIG9uZSBvZiB0aGVpciBjYXRlZ29yaWVzPw0KDQpidXNpbmVzc193aWRlJT4lDQogIGZpbHRlcihjYXRlZ29yaWVzX1Jlc3RhdXJhbnRzPT0xKSU+JQ0KICBzZWxlY3QobmFtZSwgc3RhdGUgKSU+JQ0KZ3JvdXBfYnkoc3RhdGUpJT4lDQpjb3VudChzdGF0ZSklPiUNCmFycmFuZ2UoZGVzYyhuKSkNCmBgYA0KYGBge3J9DQojMy5Ib3cgbWFueSByZWNvcmRzIGFyZSB0aGVyZSBmb3IgZWFjaCBzdGF0ZS9Qcm92aW5jZQ0KYnVzaW5lc3Nfd2lkZSU+JQ0KICBzZWxlY3QobmFtZSwgc3RhdGUgKSU+JQ0KZ3JvdXBfYnkoc3RhdGUpJT4lDQpjb3VudChzdGF0ZSklPiUNCmFycmFuZ2UoZGVzYyhuKSkNCmBgYA0KYGBge3J9DQojNC4gSG93IG1hbnkgZXN0YWJsaXNobWVudCBhcmUgb3Blbg0KICBidXNpbmVzc193aWRlJT4lDQogIHNlbGVjdChpc19vcGVuKSAlPiUNCiAgY291bnQoaXNfb3BlbiklPiUNCiAgZ3JvdXBfYnkoaXNfb3BlbiklPiUNCiAgYXJyYW5nZShkZXNjKG4pKQ0KYGBgDQpgYGB7cn0NCiMjIDUuIEhvdyBtYW55IGVzdGFibGlzaG1lbnRzIGFyZSBvcGVuIGFuZCBob3cgbWFueSBhcmUgY2xvc2VkIGluIGVhY2ggc3RhdGUuIFNvcnQgYXNjZW5kaW5nIGJ5IHN0YXRlIGFuZCB3aGV0aGVyIG9yIG5vdCB0aGV5IGFyZSBvcGVuDQoNCmJ1c2luZXNzX3dpZGUgJT4lDQogICBzZWxlY3Qoc3RhdGUsIGlzX29wZW4pICU+JQ0KICBncm91cF9ieShzdGF0ZSwgaXNfb3BlbiklPiUNCiAgY291bnQoaXNfb3BlbiklPiUNCiAgYXJyYW5nZShzdGF0ZSxkZXNjKGlzX29wZW4pKQ0KYGBgDQpgYGB7cn0NCiMjIDYuIFNob3cgdGhlIHRvcCAxMCBzdGF0ZXMgaW4gdGVybXMgb2YgbWVkaWFuIHN0YXIgcmV2aWV3IHNjb3Jlcy4gT3JnYW5pemUgdGhlbSBpbiBkZXNjZW5kaW5nIG9yZGVyDQpidXNpbmVzc193aWRlICU+JQ0KICB0eXBlX2NvbnZlcnQoY29scyhzdGFycyA9IGNvbF9kb3VibGUoKSkpJT4lICMjY29udmVydCB0aGUgc3RhcnMgY29sdW1uIHRvIGEgZG91YmxlDQogIHNlbGVjdChzdGF0ZSxzdGFycykgJT4lDQogIGdyb3VwX2J5KHN0YXRlKSU+JQ0KICBzdW1tYXJpemUoU3RhcnM9bWVkaWFuKHN0YXJzKSklPiUNCiAgYXJyYW5nZShkZXNjKFN0YXJzKSklPiUNCiAgaGVhZCgxMCkNCmBgYA0KDQpgYGB7cn0NCiMjNy4gU2hvdyB0aGUgYm90dG9tIDUgc3RhdGVzIGluIHRlcm1zIG9mIG1lZGlhbiBzdGFyIHJldmlldyBzY29yZXMuIEFsc28gc2hvdyB0aGUgbWVkaWFuIG51bWJlciBvZiByZXZpZXcgc2NvcmVzIHRoYXQgdGhleSBoYXZlIHJlY2VpdmVkDQpidXNpbmVzc193aWRlICU+JQ0KICB0eXBlX2NvbnZlcnQoY29scyhzdGFycyA9IGNvbF9kb3VibGUoKSwgcmV2aWV3X2NvdW50ID0gY29sX2ludGVnZXIoKSkpJT4lICMjY29udmVydCB0aGUgc3RhcnMgY29sdW1uIHRvIGEgZG91YmxlDQogICBzZWxlY3Qoc3RhdGUscmV2aWV3X2NvdW50LCBzdGFycykgJT4lDQogIGdyb3VwX2J5KHN0YXRlKSU+JQ0KICBzdW1tYXJpemUoTWVkaWFuX1N0YXJzPW1lZGlhbihzdGFycyksIE51bWJlcl9vZl9SZXZpZXdzPW1lZGlhbihyZXZpZXdfY291bnQpKSU+JQ0KICBhcnJhbmdlKE1lZGlhbl9TdGFycyklPiUNCiAgaGVhZCg1KQ0KYGBgDQpgYGB7cn0NCiMjOC4gU2hvdyB0aGUgZXN0YWJsaXNobWVudHMgd2l0aCB0aGUgbW9zdCBudW1iZXIgb2YgNSBzdGFyIHJldmlld3MgKHRvcCA1KQ0KcmV2aWV3ICU+JQ0KICBmaWx0ZXIoc3RhcnMgPT0gNSkgJT4lDQogIGdyb3VwX2J5KGJ1c2luZXNzX2lkKSAlPiUNCiAgc3VtbWFyaXNlKENvdW50ID0gbigpKSAlPiUNCiAgYXJyYW5nZShkZXNjKENvdW50KSkgJT4lDQogIGhlYWQoNSklPiUNCmlubmVyX2pvaW4oYnVzaW5lc3Nfd2lkZSkNCg0KYGBgDQoNCmBgYHtyfQ0KIyMjIDkuIFdoaWNoIDUgYnVzaW5lc3MgYXBwZWFycyB0aGUgbW9zdCBudW1iZXIgb2YgdGltZXMgaW4gdGhlIGRhdGEgc2V0LiBPcmRlciB0aGUgYnVzaW5lc3NlcyBieSB0aGUgbnVtYmVyIG9mIHRpbWUgdGhleSBhcHBlYXINCmJ1c2luZXNzX3dpZGUgJT4lDQogIGdyb3VwX2J5KG5hbWUpJT4lDQogIHN1bW1hcmlzZShDb3VudCA9IG4oKSkgJT4lDQogIGFycmFuZ2UoZGVzYyhDb3VudCkpJT4lDQogIGhlYWQoNSkNCmBgYA0KYGBge3J9DQojIyBMZXQncyBhbmFseXplIFN0YXJidWNrcyAgZ2l2ZW4gdGhhdCBpdCB0aGUgbW9zdCBwb3B1bGFyIGNvbXBhbnkgaW4gdGhlIGRhdGFzZXQNCiMjIyB3ZSB3aWxsIGNyZWF0ZSBhIG5ldyB0aWJibGUgY2FsbGVkIFN0YXJidWNrc0pvaW5lZF90YmwgdGhhdCBoYXMgYWxsIHRoZSBTdGFyYnVja3MgYnVzaW5lc3MgZGF0YSBhbmQgdGhlaXIgcmV2aWV3cw0Kc3RhcmJ1Y2tzYnVzaW5lc3M9YnVzaW5lc3MgJT4lDQogIGZpbHRlcihuYW1lPT0iU3RhcmJ1Y2tzIikNClN0YXJidWNrc0pvaW5lZF90YmwgPC10aWJibGUoIGlubmVyX2pvaW4oc3RhcmJ1Y2tzYnVzaW5lc3MscmV2aWV3KSkNCmBgYA0KYGBge3J9DQojMTAuIFNob3cgdGhlIG51bWJlciBvZiBTdGFyYnVja3MgaW4gZWFjaCBTdGF0ZQ0KU3RhcmJ1Y2tzSm9pbmVkX3RibCU+JQ0KICBncm91cF9ieShzdGF0ZSklPiUNCiAgc3VtbWFyaXNlKENvdW50ID0gbigpKSAlPiUNCiAgYXJyYW5nZShkZXNjKENvdW50KSkNCmBgYA0KDQpgYGB7cn0NCiMjIDExLiBXaGF0IGlzIHRoZSBudW1iZXIgYW5kIHBlcmNlbnRhZ2UgIG9mIHZpc2l0b3JzIHRvIFllbHAncyBzaXRlIHdobyBmaW5kIHRoZSByZXZpZXdzIGZvciBTdGFyYnVja3MgdXNlZnVsIA0KU3RhcmJ1Y2tzSm9pbmVkX3RibCAlPiUNCiAgZ3JvdXBfYnkodXNlZnVsKSAlPiUNCiAgc3VtbWFyaXNlKENvdW50ID0gbigpKSAlPiUNCiAgYXJyYW5nZShkZXNjKENvdW50KSkgJT4lDQogIG11dGF0ZShQZXJjZW50YWdlID0gcm91bmQoQ291bnQvc3VtKENvdW50KSoxMDAsMikpICU+JQ0KICBoZWFkKDEwKQ0KYGBgDQoNCmBgYHtyfQ0KIyMgMTEuIFdoYXQgaXMgdGhlIG51bWJlciBhbmQgcGVyY2VudGFnZSAgb2YgdmlzaXRvcnMgdG8gWWVscCdzIHNpdGUgd2hvIGZpbmQgdGhlIHJldmlld3MgZm9yIFN0YXJidWNrcyBmdW5ueSANClN0YXJidWNrc0pvaW5lZF90YmwgJT4lDQogIGdyb3VwX2J5KGZ1bm55KSAlPiUNCiAgc3VtbWFyaXNlKENvdW50ID0gbigpKSAlPiUNCiAgYXJyYW5nZShkZXNjKENvdW50KSkgJT4lDQogIG11dGF0ZShQZXJjZW50YWdlID0gcm91bmQoQ291bnQvc3VtKENvdW50KSoxMDAsMikpICU+JQ0KICBoZWFkKDEwKQ0KYGBgDQoNCmBgYHtyfQ0KIyMgMTEuIFdoYXQgaXMgdGhlIG51bWJlciBhbmQgcGVyY2VudGFnZSAgb2YgdmlzaXRvcnMgdG8gWWVscCdzIHNpdGUgd2hvIGZpbmQgdGhlIHJldmlld3MgZm9yIFN0YXJidWNrcyB1c2VmdWwgDQpTdGFyYnVja3NKb2luZWRfdGJsICU+JQ0KICBncm91cF9ieShjb29sKSAlPiUNCiAgc3VtbWFyaXNlKENvdW50ID0gbigpKSAlPiUNCiAgYXJyYW5nZShkZXNjKENvdW50KSkgJT4lDQogIG11dGF0ZShQZXJjZW50YWdlID0gcm91bmQoQ291bnQvc3VtKENvdW50KSoxMDAsMikpICU+JQ0KICBoZWFkKDEwKQ0KYGBgDQoNCmBgYHtyfQ0KIyMjTGV0J3MgYW5hbHl6ZSB0aGUgbW9zdCBuZWdhdGl2ZSByZXZpZXdzIGFuZCB0aGUgbW9zdCBwb3NpdGl2ZSByZXZpZXdzLiBXaGF0IGFyZSBjdXN0b21lcnMgZnJvbSBlYWNoIGdyb3VwIHNheWluZw0KIyMjIHRvIGlkZW50aWZ5IHRoZSBtb3N0IG5lZ2F0aXZlIGFuZCBwb3NpdGl2ZSByZXZpZXdzIHdlIGhhdmUgdG8gMSlhc3NpZ24gc2VudGltZW50IHNjb3JlcyB0byBlYWNoIHJldmlldyAyKXNvcnQgdGhlIHJldmlld3Mgc2NvcmVzDQojIyMxKSBhc3NpZ25pbmcgcmV2aWV3IHNjb3JlcyBhbmQgc3RvcmUgdGhlIHJlc3VsdHMgaW4gYSBkYXRhIGZyYW1lIGNhbGxlZCBDdXN0b21lclNlbnRpbWVudA0KaW5zdGFsbC5wYWNrYWdlcygidGV4dGRhdGEiKQ0KaW5zdGFsbC5wYWNrYWdlcygidGV4dGNhdCIpDQppbnN0YWxsLnBhY2thZ2VzKCJ0aWR5dGV4dCIpDQpsaWJyYXJ5KHRleHRkYXRhKQ0KbGlicmFyeSh0ZXh0Y2F0KQ0KbGlicmFyeSh0aWR5dGV4dCkNCg0KYGBgDQoNCmBgYHtyfQ0KZ2V0X3NlbnRpbWVudHMoImFmaW5uIikNCmBgYA0KDQoNCmBgYHtyfQ0KQ3VzdG9tZXJTZW50aW1lbnQ8LVN0YXJidWNrc0pvaW5lZF90YmwgJT4lDQogICMjIGZpbHRlcih0ZXh0Y2F0KHRleHQpID09ICJlbmdsaXNoIikgJT4lICMgY29uc2lkZXJpbmcgb25seSBFbmdsaXNoIHRleHQuIE9taXQgdGhpcyBsaW5lIGlmIHlvdSB3YW50IGZhc3RlciBwcm9jZXNzaW5nLg0KICB1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHQpICU+JSAgIyNTcGxpdCBhIHRleHQgY29sdW1uIGludG8gd29yZHMvdG9rZW5zDQogICBhbnRpX2pvaW4oc3RvcF93b3JkcyklPiUgICNyZW1vdmUgc3RvcHdvcmRzDQogICBpbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJhZmlubiIpLCBieSA9ICJ3b3JkIikgJT4lICMgam9pbiB0aGUgU3RhcmJ1Y2tzSm9pbmVkX3RibCB3aXRoIHRoZSBhZmlubiBsZXhpY29uIHdoaWNoIGhhcyAyIGNvbHVtbnMgdGl0bGVkIHdvcmQgYW5kIHZhbHVlIA0KICAgZ3JvdXBfYnkocmV2aWV3X2lkKSAlPiUNCiAgc3VtbWFyaXplKHNlbnRpbWVudCA9IG1lYW4odmFsdWUpLHdvcmRzID0gbigpKSAlPiUNCiAgZmlsdGVyKHdvcmRzID49IDUpICMgd2Ugd2lsbCBleGNsdWRlIHJldmlld3Mgd2l0aCBsZXNzIHRoYW4gNSB3b3Jkcw0KYGBgDQpgYGB7cn0NCiMjIzIpIHNvcnQgdGhlIHJldmlld3Mgc2NvcmVzIGFuZCBkaXNwbGF5IGRlc2lyZWQgY29sdW1ucyhpLmUuIGFkZHJlc3MsY2l0eSwgZGF0ZSxzZW50aW1lbnQsdGV4dCkNCiMjIyMgTW9zdCBOZWdhdGl2ZSByZXZpZXdzDQpDdXN0b21lclNlbnRpbWVudCU+JQ0KICBhcnJhbmdlKGRlc2Moc2VudGltZW50KSkgJT4lDQogIHRvcF9uKC0xMCwgc2VudGltZW50KSAlPiUgICMjIGdldCB0aGUgbG93ZXIgc2VudGltZW50IHNjb3Jlcw0KICBpbm5lcl9qb2luKFN0YXJidWNrc0pvaW5lZF90YmwsIGJ5ID0gInJldmlld19pZCIpICU+JQ0KICBzZWxlY3QoYWRkcmVzcyxjaXR5LCBkYXRlLHNlbnRpbWVudCx0ZXh0KQ0KYGBgDQoNCmBgYHtyfQ0KIyMjIyBNb3N0IFBvc2l0aXZlIHJldmlld3MNCkN1c3RvbWVyU2VudGltZW50JT4lDQogIGFycmFuZ2UoZGVzYyhzZW50aW1lbnQpKSAlPiUNCiAgdG9wX24oMTAsIHNlbnRpbWVudCkgJT4lICMjIGdldCB0aGUgaGlnaGVzdCBzZW50aW1lbnQgc2NvcmVzDQogIGlubmVyX2pvaW4oU3RhcmJ1Y2tzSm9pbmVkX3RibCwgYnkgPSAicmV2aWV3X2lkIikgJT4lDQogIHNlbGVjdChhZGRyZXNzLGNpdHksIGRhdGUsc2VudGltZW50LHRleHQpDQpgYGANCg0KYGBge3J9DQpsaWJyYXJ5KGx1YnJpZGF0ZSkNCmBgYA0KDQpgYGB7cn0NCiMjIyBMZXQncyBhZGQgYSBmb3JtYXR0ZWQgRGF0ZSBDb2x1bW4gdG8gU3RhcmJ1Y2tzSm9pbmVkX3RibA0KIyMgV2Ugd2lsbCB1c2UgTHVicmlkYXRlIHJlZm9ybWF0IHRoZSBkYXRlIGNvbHVtbiAoIHdoaWNoIGlzIGN1cnJlbnRseSBzdG9yZWQgYXMgY2hyKSBhbmQgY3JlYXRlIG5ldyBjb2x1bW5zIHRvIG11bHRpcGxlIGNvbHVtbnM6DQojIyMgZGF0ZSwgbW9udGgsZGF5LCB5ZWFyLGhvdXINClN0YXJidWNrc0RhdGVGb3JtYXR0ZWQ8LVN0YXJidWNrc0pvaW5lZF90YmwlPiUNCiBtdXRhdGUoZGF0ZV9mb3JtYXR0ZWQgPSBhc19kYXRlKGRhdGUpLA0KIG1vbnRoX2Zvcm1hdHRlZD1tb250aChkYXRlKSwNCiBkYXlfZm9ybWF0dGVkPWRheShkYXRlKSwNCiB5ZWFyX2Zvcm1hdHRlZD15ZWFyKGRhdGUpLA0KIGhvdXJfZm9ybWF0dGVkPWhvdXIoZGF0ZSkpDQpgYGANCg0KYGBge3J9DQojIyMgTm93IGxldHMgY3JlYXRlIHNvbWUgcGxvdHMgZm9yIFN0YXJidWNrcw0KDQojIyMxLiBTaG93IHRoZSBudW1iZXIgb2YgcmV2aWV3cyBmb3IgU3RhcmJ1Y2tzIG92ZXIgdGltZSh5ZWFycykgdXNpbmcgYSBsaW5lIGNoYXJ0DQpTdGFyYnVja3NEYXRlRm9ybWF0dGVkJT4lDQogIHNlbGVjdCh5ZWFyX2Zvcm1hdHRlZCklPiUNCiAgZ3JvdXBfYnkgKHllYXJfZm9ybWF0dGVkKSU+JQ0KICBzdW1tYXJpc2UoTnVtYmVyb2ZSZXZpZXdzID0gbigpKSU+JQ0KICBnZ3Bsb3QoYWVzICh4PXllYXJfZm9ybWF0dGVkLHk9TnVtYmVyb2ZSZXZpZXdzKSkgKw0KICBnZW9tX2xpbmUoKQ0KYGBgDQoNCmBgYHtyfQ0KIyMjMi4gU2hvdyB0aGUgbnVtYmVyIG9mIHJldmlld3MgYnkgU3RhdGUgdXNpbmcgYSBjb2x1bW4vYmFyIGNoYXJ0DQpTdGFyYnVja3NEYXRlRm9ybWF0dGVkJT4lDQogIHNlbGVjdChzdGF0ZSklPiUNCiAgZ3JvdXBfYnkoc3RhdGUpJT4lDQogIGdncGxvdChhZXMgKHg9c3RhdGUpKSArDQogIGdlb21fYmFyKCkNCmBgYA0KDQpgYGB7cn0NCiMjIzMuIENyZWF0ZSBhIGRvbnV0IGNoYXJ0IHRoYXQgU2hvd3MgdGhlIHByb3BvcnRpb24gb2YgYnVzaW5lc3NlcyB0aGF0IGFyZSBvcGVuIHZzIHRob3NlIHRoYXQgYXJlIGNsb3NlZA0KYnVzaW5lc3MgJT4lDQogZ3JvdXBfYnkoaXNfb3BlbikgJT4lDQogc3VtbWFyaXNlKENvdW50ID0gbigpKSU+JQ0KIG11dGF0ZShpc19vcGVuPWFzLmZhY3Rvcihpc19vcGVuKSwgcGVyY2VudGFnZSA9IHJvdW5kKENvdW50L3N1bShDb3VudCkqMTAwLDIpLCBMYWJlbFBvc2l0aW9uID0gY3Vtc3VtKHBlcmNlbnRhZ2UpLS4xKnBlcmNlbnRhZ2UpICU+JQ0KICMjI3Bsb3QgdGhlIHBpZSBjaGFydCB3aXRoIGdlb21fYmFyKCkgYW5kIHRoZW4gY29udmVydCB0aGUgYmFyIGludG8gcGllIHdpdGggdGhlIGNvb3JkX3BvbGFyKCkgZnVuY3Rpb24uIHRvIG1ha2UgYSBkb251dCBwbG90IHdlIG11c3Qgc3BlY2lmeSB0aGUgeCA9IDIgaW4gYWVzKCkgYW5kIGFkZCB0aGUgeGxpbSgpIGFzIGNvZGUgDQogZ2dwbG90KA0KIGFlcyh4ID0gMiwgeSA9IHBlcmNlbnRhZ2UsIGZpbGwgPSBpc19vcGVuKSkrDQogZ2VvbV9iYXIoc3RhdCA9ICJpZGVudGl0eSIpKw0KIGNvb3JkX3BvbGFyKCJ5IiApICsNCiBnZW9tX3RleHQoYWVzKHkgPSBMYWJlbFBvc2l0aW9uLCBsYWJlbCA9IHBhc3RlKHBlcmNlbnRhZ2UsIiUiLCBzZXAgPSAiIikpLCBjb2wgPSAid2hpdGUiKSArDQogdGhlbWVfdm9pZCgpICsNCiBzY2FsZV9maWxsX2JyZXdlcihwYWxldHRlID0gIkRhcmsyIikrDQogeGxpbSguMiwyLjUpDQpgYGANCg0KYGBge3J9DQojIzQuIENyZWF0ZSBhIGNvbHVtbiBjaGFydCB0aGF0IHNob3dzIHRoZSBtZWRpYW4gbnVtYmVyIG9mIHdvcmRzIHVzZWQgaW4gU3RhcmJ1Y2tzIHJldmlld3MgZWFjaCBtb250aA0KU3RhcmJ1Y2tzRGF0ZUZvcm1hdHRlZCAlPiUNCiAgbXV0YXRlKE51bWJlck9mV29yZHM9c3RyX2NvdW50KHRleHQsIGJvdW5kYXJ5KCJ3b3JkIikpKSAlPiUgIyMgY291bnRzIHRoZSBudW1iZXIgb2Ygd29yZHMgc2luY2UgYm91bmRhcnkgaXMgc2V0IHRvIHdvcmQNCiAgbXV0YXRlKE1vbnRoTmFtZT1tb250aCh5bWQoZGF0ZV9mb3JtYXR0ZWQpLCBsYWJlbCA9IFRSVUUpKSAlPiUNCiAgZ3JvdXBfYnkoTW9udGhOYW1lKSAlPiUNCiAgc3VtbWFyaXplKE51bWJlck9mV29yZHM9bWVkaWFuKE51bWJlck9mV29yZHMpKSAlPiUNCiAgZ2dwbG90KGFlcyAoeD1Nb250aE5hbWUsIHk9TnVtYmVyT2ZXb3JkcykpICsNCiAgZ2VvbV9jb2woKSArDQogIGNvb3JkX2ZsaXAoKQ0KYGBgDQpgYGB7cn0NCiMjIyA1LiBDcmVhdGUgYSBjaGFydCB0aGF0IHNob3dzIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiBmdW5ueSBhbmQgdXNlZnVsIHJldmlld3Mgb2YgU3RhcmJ1Y2tzLg0KU3RhcmJ1Y2tzRGF0ZUZvcm1hdHRlZCU+JQ0KICBnZ3Bsb3QoYWVzKHg9ZnVubnksIHk9dXNlZnVsKSkgKyANCiAgZ2VvbV9wb2ludCgpKw0KICBnZW9tX3Ntb290aChtZXRob2Q9bG0pDQpgYGANCg0KDQpgYGB7cn0NCiMjIyA2LiBDcmVhdGUgYSB3b3JkY2xvdWQgdG8gc2hvdyB0aGUgd29yZHMgdGhhdCBhcmUgdXNlZCB0aGUgbW9zdCBmcmVxdWVudGx5IGluIHJldmlld3MNCg0KbGlicmFyeSh3b3JkY2xvdWQpDQpgYGANCg0KYGBge3J9DQpjcmVhdGVXb3JkQ2xvdWQgPSBmdW5jdGlvbih4KQ0Kew0KICBTdGFyYnVja3NEYXRlRm9ybWF0dGVkICU+JQ0KICAgIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCkgJT4lDQogICAgDQogICAgZmlsdGVyKCF3b3JkICVpbiUgc3RvcF93b3JkcyR3b3JkLCAhd29yZCAlaW4lICdzdGFyYnVja3MnKSAlPiUNCiAgICAjIyMgZmlsdGVyKCF3b3JkID0gJ3N0YXJidWNrcycpICU+JQ0KICAgIGNvdW50KHdvcmQsc29ydCA9IFRSVUUpICU+JQ0KICAgIHVuZ3JvdXAoKSAlPiUNCiAgICBoZWFkKDMwKSAlPiUNCiAgICB3aXRoKHdvcmRjbG91ZCh3b3JkLCBuLCBtYXgud29yZHMgPSAzMCxjb2xvcnM9YnJld2VyLnBhbCg4LCAiRGFyazIiKSkpDQp9DQoNCmNyZWF0ZVdvcmRDbG91ZChyZXZpZXcpDQpgYGANCg0KYGBge3J9DQojIyMgNy5TZW50aW1lbnQgQW5hbHlzaXMgOiBUaGUgUG9zaXRpdmUgYW5kIG5lZ2F0aXZlIHdvcmRzIGFzc29jaWF0ZSB3aXRoIFN0YXJidWNrcyByZXZpZXdzDQoNCnBvc2l0aXZlV29yZHNCYXJHcmFwaCA8LSBmdW5jdGlvbihTQykgew0KICBjb250cmlidXRpb25zIDwtIFNDICU+JQ0KICAgIHVubmVzdF90b2tlbnMod29yZCwgdGV4dCkgJT4lDQogICAgY291bnQod29yZCxzb3J0ID0gVFJVRSkgJT4lDQogICAgdW5ncm91cCgpICU+JQ0KICAgIA0KICAgIGlubmVyX2pvaW4oZ2V0X3NlbnRpbWVudHMoImFmaW5uIiksIGJ5ID0gIndvcmQiKSAlPiUNCiAgICBncm91cF9ieSh3b3JkKSAlPiUNCiAgICBzdW1tYXJpemUoY29udHJpYnV0aW9uID0gc3VtKHZhbHVlKSwgbj1uKCkpDQogIA0KICBjb250cmlidXRpb25zICU+JQ0KICAgIHRvcF9uKDIwLCBhYnMoY29udHJpYnV0aW9uKSkgJT4lDQogICAgbXV0YXRlKHdvcmQgPSByZW9yZGVyKHdvcmQsIGNvbnRyaWJ1dGlvbikpICU+JQ0KICAgIGhlYWQoMjApICU+JQ0KICAgIGdncGxvdChhZXMod29yZCwgY29udHJpYnV0aW9uLCBmaWxsID0gY29udHJpYnV0aW9uID4gMCkpICsNCiAgICBnZW9tX2NvbChzaG93LmxlZ2VuZCA9IEZBTFNFKSArDQogICAgY29vcmRfZmxpcCgpICsgdGhlbWVfYncoKQ0KfQ0KDQpwb3NpdGl2ZVdvcmRzQmFyR3JhcGgoU3RhcmJ1Y2tzSm9pbmVkX3RibCkNCmBgYA0KDQpgYGB7cn0NCiMjIzguIFNob3cgYWxsIHRoZSBTdGFyYnVja3MgbG9jYXRpb25zIG9uIGEgbWFwDQojIyMgd2Ugd2lsbCB1c2UgdGhlIHN0YXJidWNrc2J1c2luZXNzIGRhdGEgc2V0IHRoYXQgd2UgY3JlYXRlZCBlYXJsaWVyIGJlY2F1c2UgaXQgZG9lcyBub3QgaGF2ZSByZXBlYXRpbmcgcmV2aWV3cyBlYWNoIHJlY29yZCBpcyB1bmlxdWUNCmxpYnJhcnkobGVhZmxldCkNCiMjIyB0aGlzIGZ1bmN0aW9uIHdpbGwgY29sb3IgZWFjaCBsb2NhdGlvbiBiYXNlZCBvbiB0aGUgc3RhciByYXRpbmcgaXQgaGFzDQpwYWwgPC0gY29sb3JGYWN0b3IoYygicHVycGxlIiwgInJlZCIsICJvcmFuZ2UiLCAiYmxhY2siLCJibHVlIiksDQogICAgICAgICAgICAgICAgICAgZG9tYWluID0gdW5pcXVlKHN0YXJidWNrc2J1c2luZXNzJHN0YXJzKSkNCiMjIyB0aGlzIGRyYXdzIHRoZSBtYXANCm1hcCA8LSBsZWFmbGV0KHN0YXJidWNrc2J1c2luZXNzKSAlPiUNCiAgYWRkUHJvdmlkZXJUaWxlcygiQ2FydG9EQi5Qb3NpdHJvbiIpICU+JQ0KICBhZGRDaXJjbGVNYXJrZXJzKA0KICAgIGNvbG9yID0gfnBhbChzdGFyYnVja3NidXNpbmVzcyRzdGFycyksDQogICAgc3Ryb2tlID0gRkFMU0UsIGZpbGxPcGFjaXR5ID0gMC41LA0KICAgIGxhdCA9IHN0YXJidWNrc2J1c2luZXNzJGxhdGl0dWRlLA0KICAgIGxuZyA9IHN0YXJidWNrc2J1c2luZXNzJGxvbmdpdHVkZSwNCiAgICBjbHVzdGVyT3B0aW9ucyA9IG1hcmtlckNsdXN0ZXJPcHRpb25zKCksDQogICAgcG9wdXAgPSBhcy5jaGFyYWN0ZXIoc3RhcmJ1Y2tzYnVzaW5lc3MkYWRkcmVzcykpDQptYXANCmBgYA0KDQoNCmBgYHtyfQ0KI0xldCdzIGpvaW4gdGhlIGJ1c2luZXNzIGFuZCByZXZpZXcgdGFibGVzIHNvIHRoYXQgd2UgY2FuIGNyZWF0ZSBhIGRhc2hib2FyZA0KYnVzaW5lc3NfcmV2aWV3cyA8LSBpbm5lcl9qb2luKGJ1c2luZXNzLHJldmlldykNCmBgYA==