Cleaning the Corpus
head(zomato)
[[1]]
[1] "IasAlok: RT @innovation_iitd: Unicorns or Unicorns in making founded by IIT Delhi Alumni\nFlipkart\nSnapdeal\nQuikr\nZomato\nRenew Power\nPolicy Bazaar\nSh…"
[[2]]
[1] "subho_roy05: @ZomatoIN @Zomato Delivered incomplete order last night trying to chat with support but nobody responds. Disgrace !!!"
[[3]]
[1] "LunchStopper: Order #lunchstopper food online #Swiggy #Zomato #365oranges"
[[4]]
[1] "preshikajain: RT @deepigoyal: Our delivery executives doing the right thing. #proud #zomato #delivery @deeppurpled https://t.co/VnmXpulCrA"
[[5]]
[1] "ramuubot: RT @deepigoyal: Our delivery executives doing the right thing. #proud #zomato #delivery @deeppurpled https://t.co/VnmXpulCrA"
[[6]]
[1] "gl0ryhunter: RT @Chingakutty: Zomato and Swiggy drivers remind me of the Domino's delivery guys who drove during \"30 minutes or free\" offer"
head(swiggy)
[[1]]
[1] "austinnoronha: Livemint: How #Swiggy became India’s fastest unicorn.\nhttps://t.co/73wQZRvqHm\n\nvia @GoogleNews"
[[2]]
[1] "LunchStopper: Order #lunchstopper food online #Swiggy #Zomato #365oranges"
[[3]]
[1] "IanMacKay_94: @CoachKav44 and I find a wing place in beautiful Oshawa ON... server says the place has unreal wings, so naturally… https://t.co/4b3sDlDZos"
[[4]]
[1] "rajeshar01: Insightful #success mantras from @Swiggy, running #Internet #Mobileapp #FoodDelivery #business facing #challenges… https://t.co/KHA4pYKgoc"
[[5]]
[1] "gl0ryhunter: RT @Chingakutty: Zomato and Swiggy drivers remind me of the Domino's delivery guys who drove during \"30 minutes or free\" offer"
[[6]]
[1] "Ankit0316: RT @anujrathi: How \u2066@swiggy_in\u2069 became India’s fastest unicorn! https://t.co/hZbDxOsvas"
zt <- sapply(zomato, function(x) x$getText())
st <- sapply(swiggy, function(x) x$getText())
catch.error <- function(x)
{
# Let us create a missing value for test purpose
y <- NA
# Try to catch that error (NA) which we have just created
catch_error <- tryCatch(tolower(x), error = function(e) e)
# If not an error
if(!inherits(catch_error, "error"))
y <- tolower(x)
# Check result if error exists, otherwise the function works fine
return(y)
}
cleanTweets <- function(tweet)
{
# Clean the tweet for sentiment analysis
# Remove html links, which are not required for sentiment analysis
tweet <- gsub("(f|ht) (tp) (s?) (://) (.*) [.|/] (.*)", " ", tweet)
# First we will remove the reweet entities from the sorted tweets (text)
tweet <- gsub("(RT|via) ((?:\\b\\W*@\\w+)+)", " ", tweet)
# Then remove all "#HashTags"
tweet <- gsub("#\\w+", " ", tweet)
# Then remove all "@People"
tweet <- gsub("@\\w+", " ", tweet)
# Then remove all punctuations
tweet <- gsub("[[:punct:]]", " ", tweet)
# Then remove numbers, we need only text for analysis
tweet <- gsub("[[:digit:]]", " ", tweet)
# Finally we remove all unnecessary spaces (white spaces, tabs etc)
tweet <- gsub("[ \t]{2,}", " ", tweet)
tweet <- gsub("^\\s+|\\s+$", " ", tweet)
# Convert into lowercase
tweet <- catch.error(tweet)
tweet
}
cleanTweetsAndRemoveNAs <- function(Tweets)
{
TweetsCleaned <- sapply(Tweets, cleanTweets)
# Remove the "NA" tweets from this tweet list
TweetsCleaned <- TweetsCleaned[!is.na(TweetsCleaned)]
names(TweetsCleaned) <- NULL
# Remove the repetitive tweets from this tweet list
TweetsCleaned <- unique(TweetsCleaned)
TweetsCleaned
}
length(ZomatoCleaned)
[1] 4289
length(SwiggyCleaned)
[1] 4086
Estimating Sentiment Part-A
opinion.lexicon.pos <- scan('positive-words.txt', what = 'character',
comment.char = ";")
opinion.lexicon.neg <- scan('negative-words.txt', what = 'character',
comment.char = ";")
head(opinion.lexicon.pos)
[1] "a+" "abound" "abounds" "abundance" "abundant" "accessable"
head(opinion.lexicon.neg)
[1] "2-faced" "2-faces" "abnormal" "abolish" "abominable" "abominably"
neg.words <- c(opinion.lexicon.neg, "cancellation", "wtf", "wait", "waiting" )
pos.words <- opinion.lexicon.pos
getSentimentScore <- function(sentences, words.positive, words.negative, .progress = 'None')
{
require(plyr)
require(stringr)
scores <- laply(sentences, function(sentence, words.positive, words.negative){
# Let first remove the digit, punctuation character and control characters
sentence <- gsub("[[:cntrl:]]", "", gsub("[[:punct:]]", "", gsub("\\d+", "", sentence)))
# Then lets convert all to lower sentence case
sentence <- tolower(sentence)
# Now lets split each sentence by the space delimiter
words <- unlist(str_split(sentence, "\\s+"))
# Get the boolean match of each words with the positive and negative opinion-lexicon
pos.matches <- !is.na(match(words, words.positive))
neg.matches <- !is.na(match(words, words.negative))
# Now get the score as total positive sentiment minus the total negatives
score <- sum(pos.matches) - sum(neg.matches)
return(score)
}, words.positive, words.negative, .progress = .progress)
# Return a dataframe with respective sentence and the score
return(data.frame(text = sentences, score = scores))
}
options(warn = -1)
ZomatoResults <- getSentimentScore(ZomatoCleaned, words.positive = pos.words,
words.negative = neg.words)
SwiggyResults <- getSentimentScore(SwiggyCleaned, words.positive = pos.words,
words.negative = neg.words)
library(ggplot2)
library(gridExtra)
p1 <- ggplot(ZomatoResults, aes(x = score)) + geom_bar(stat = "count", fill = "purple") +
labs(title = "Zomato Scores", x = "Scores", y = "Frequency") + xlim(c(-6, 6)) +
ylim(c(0,2500))
p2 <- ggplot(SwiggyResults, aes(x = score)) + geom_bar(stat = "count", fill = "red") +
labs(title = "Swiggy Scores", x = "Scores", y = "Frequency") + xlim(c(-6, 6)) +
ylim(c(0,2500))
grid.arrange(p1, p2, nrow = 1)

mean(ZomatoResults$score)
[1] 0.08346934
sd(ZomatoResults$score)
[1] 0.9716261
mean(SwiggyResults$score)
[1] 0.1338718
sd(SwiggyResults$score)
[1] 0.9516811
Estimating Sentiment Part-B
install.packages("Rstem",
repos = "http://www.omegahat.org/R", type="source")
require(devtools)
install_url("http://cran.r-project.org/src/contrib/Archive/sentiment/sentiment_0.2.tar.gz")
require(sentiment)
ls("package:sentiment")
library(sentiment)
# Classify emotion function returns an object of class data frame
# With seven columns (anger, disgust, fear, joy, sadness, suprise, best fit) and one row for each document
ZomatoClass <- classify_emotion(ZomatoCleaned, algorithm = "bayes", prior = 1.0)
SwiggyClass <- classify_emotion(SwiggyCleaned, algorithm = "bayes", prior = 1.0)
head(ZomatoClass)
ANGER DISGUST FEAR JOY
[1,] "1.46871776464786" "3.09234031207392" "2.06783599555953" "1.02547755260094"
[2,] "1.46871776464786" "3.09234031207392" "2.06783599555953" "1.02547755260094"
[3,] "1.46871776464786" "3.09234031207392" "2.06783599555953" "1.02547755260094"
[4,] "1.46871776464786" "3.09234031207392" "2.06783599555953" "1.02547755260094"
[5,] "1.46871776464786" "3.09234031207392" "2.06783599555953" "1.02547755260094"
[6,] "1.46871776464786" "3.09234031207392" "2.06783599555953" "1.02547755260094"
SADNESS SURPRISE BEST_FIT
[1,] "1.7277074477352" "2.78695866252273" NA
[2,] "1.7277074477352" "2.78695866252273" NA
[3,] "1.7277074477352" "2.78695866252273" NA
[4,] "1.7277074477352" "2.78695866252273" NA
[5,] "1.7277074477352" "2.78695866252273" NA
[6,] "1.7277074477352" "2.78695866252273" NA
head(SwiggyClass)
ANGER DISGUST FEAR JOY
[1,] "1.46871776464786" "3.09234031207392" "2.06783599555953" "1.02547755260094"
[2,] "1.46871776464786" "3.09234031207392" "2.06783599555953" "1.02547755260094"
[3,] "1.46871776464786" "3.09234031207392" "2.06783599555953" "1.02547755260094"
[4,] "1.46871776464786" "3.09234031207392" "2.06783599555953" "1.02547755260094"
[5,] "1.46871776464786" "3.09234031207392" "2.06783599555953" "1.02547755260094"
[6,] "1.46871776464786" "3.09234031207392" "2.06783599555953" "1.02547755260094"
SADNESS SURPRISE BEST_FIT
[1,] "1.7277074477352" "2.78695866252273" NA
[2,] "1.7277074477352" "2.78695866252273" NA
[3,] "1.7277074477352" "2.78695866252273" NA
[4,] "1.7277074477352" "2.78695866252273" NA
[5,] "1.7277074477352" "2.78695866252273" NA
[6,] "1.7277074477352" "2.78695866252273" NA
ZomatoEmotion <- ZomatoClass[, 7]
SwiggyEmotion <- SwiggyClass[, 7]
ZomatoEmotion[is.na(ZomatoEmotion)] <- "unknown"
SwiggyEmotion[is.na(SwiggyEmotion)] <- "unknown"
head(ZomatoEmotion, 20)
[1] "unknown" "unknown" "unknown" "unknown" "unknown" "unknown" "unknown" "joy"
[9] "sadness" "anger" "unknown" "sadness" "unknown" "unknown" "unknown" "unknown"
[17] "joy" "unknown" "joy" "unknown"
head(SwiggyEmotion, 20)
[1] "unknown" "unknown" "unknown" "unknown" "unknown" "unknown" "unknown" "unknown"
[9] "unknown" "unknown" "unknown" "unknown" "unknown" "unknown" "joy" "unknown"
[17] "joy" "unknown" "joy" "unknown"
ZomatoClassPol <- classify_polarity(ZomatoCleaned, algorithm = "bayes")
swiggyClassPol <- classify_polarity(SwiggyCleaned, algorithm = "bayes")
head(ZomatoClassPol)
POS NEG POS/NEG BEST_FIT
[1,] "8.78232285939751" "0.445453222112551" "19.7154772340574" "positive"
[2,] "17.2265151579293" "35.1792261323723" "0.489678627184958" "negative"
[3,] "8.78232285939751" "0.445453222112551" "19.7154772340574" "positive"
[4,] "8.78232285939751" "0.445453222112551" "19.7154772340574" "positive"
[5,] "8.78232285939751" "0.445453222112551" "19.7154772340574" "positive"
[6,] "9.47547003995745" "0.445453222112551" "21.2715265477714" "positive"
head(swiggyClassPol)
POS NEG POS/NEG BEST_FIT
[1,] "8.78232285939751" "0.445453222112551" "19.7154772340574" "positive"
[2,] "8.78232285939751" "0.445453222112551" "19.7154772340574" "positive"
[3,] "9.47547003995745" "9.47547003995745" "1" "neutral"
[4,] "9.47547003995745" "0.445453222112551" "21.2715265477714" "positive"
[5,] "8.78232285939751" "0.445453222112551" "19.7154772340574" "positive"
[6,] "8.78232285939751" "0.445453222112551" "19.7154772340574" "positive"
# We will fetch polarity category best fit for our analysis purpose
ZomatoPol <- ZomatoClassPol[, 4]
SwiggyPol <- swiggyClassPol[, 4]
# Let us create now a data frame with the above results
ZomatoDF <- data.frame(text = ZomatoCleaned, emotion = ZomatoEmotion, polarity = ZomatoPol,
stringsAsFactors = FALSE)
SwiggyDF <- data.frame(text = SwiggyCleaned, emotion = SwiggyEmotion, polarity = SwiggyPol,
stringsAsFactors = FALSE)
# Rearrange data inside the data frame by sorting it
ZomatoDF <- within(ZomatoDF, emotion <- factor(emotion, levels = names(sort(table(emotion),
decreasing = T))))
SwiggyDF <- within(SwiggyDF, emotion <- factor(emotion, levels = names(sort(table(emotion),
decreasing = T))))
head(ZomatoDF, 10)
head(SwiggyDF, 10)
plotSentiments <- function(df,title)
{
ggplot(df, aes(x = emotion)) +
geom_bar(aes(y = ..count.., fill = emotion)) +
scale_color_brewer(palette = "Dark2") +
ggtitle(title) + theme(legend.position = "right") +
ylab("Number of Tweets") +
xlab("Emotion Categories") +
ylim(c(0,4000))
}
plotSentiments(ZomatoDF,"Sentiment Analysis of Tweets on Twitter about Zomato")

plotSentiments(SwiggyDF,"Sentiment Analysis of Tweets on Twitter about Swiggy")

# Similarly we will plot distribution in the tweets
plotSentiments2 <- function(df, title)
{
ggplot(df, aes(x = polarity)) +
geom_bar(aes(y = ..count.., fill = polarity)) +
scale_color_brewer(palette = "RdGy") +
ggtitle(title) + theme(legend.position = "right") +
ylab("Number of Tweets") +
xlab("Polarity Categories") +
ylim(c(0, 4000))
}
plotSentiments2(ZomatoDF, "Polarity Analysis of Tweets on Twitter about Zomato")
plotSentiments2(SwiggyDF, "Polarity Analysis of Tweets on Twitter about Swiggy")
removeCustomeWords <- function (TweetsCleaned)
{
for(i in 1:length(TweetsCleaned))
{
TweetsCleaned[i] <- tryCatch({
TweetsCleaned[i] = removeWords(TweetsCleaned[i],
c(stopwords("english"), "care", "guys", "can",
"dis", "didn","guy" ,"booked", "plz", "order",
"ordered", "get", "hey", "also"))
TweetsCleaned[i]
}, error=function(cond)
{
TweetsCleaned[i]
}, warning=function(cond)
{
TweetsCleaned[i]
})
}
return(TweetsCleaned)
}
getWordCloud <- function(sentiment_dataframe, TweetsCleaned, Emotion)
{
emos = levels(factor(sentiment_dataframe$emotion))
n_emos = length(emos)
emo.docs = rep("", n_emos)
TweetsCleaned = removeCustomeWords(TweetsCleaned)
for (i in 1:n_emos)
{
emo.docs[i] = paste(TweetsCleaned[Emotion ==
emos[i]], collapse=" ")
}
corpus = Corpus(VectorSource(emo.docs))
tdm = TermDocumentMatrix(corpus)
tdm = as.matrix(tdm)
colnames(tdm) = emos
require(wordcloud)
suppressWarnings(comparison.cloud(tdm, colors =
brewer.pal(n_emos, "Dark2"), scale = c(3,.5), random.order = FALSE, title.size = 1.5))
}
getWordCloud(ZomatoDF, ZomatoCleaned, ZomatoEmotion)

getWordCloud(SwiggyDF, SwiggyCleaned, SwiggyEmotion)

LS0tDQp0aXRsZTogPGNlbnRlcj48c3BhbiBzdHlsZSA9ICJjb2xvcjpwdXJwbGUiPlRXSVRURVIgU0VOVElNRU5UIEFOQUxZU0lTPC9zcGFuPjwvY2VudGVyPg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KPGJyPg0KDQojIyBJbnN0YWxsIFBhY2thZ2VzDQoNCjxicj4NCg0KYGBge3J9DQppbnN0YWxsX2dpdGh1YigiZ2VvZmZqZW50cnkvdHdpdHRlUiIpDQpgYGANCg0KPGJyPg0KDQojIyBJbXBvcnRpbmcgUGFja2FnZXMNCg0KPGJyPg0KDQpgYGB7cn0NCmxpYnJhcnkoZGV2dG9vbHMpDQpsaWJyYXJ5KHJqc29uKQ0KbGlicmFyeShodHRyKQ0KbGlicmFyeShiaXQ2NCkNCmxpYnJhcnkodHdpdHRlUikNCmBgYA0KDQo8YnI+DQoNCiMjIFR3aXR0ZXIgQXV0aGVudGljYXRpb24NCg0KPGJyPg0KDQpgYGB7cn0NCmFwaV9rZXkgPC0gIkgzTElCMTNJcXc3SDVTcWZJU05CT2RJQ3IiDQphcGlfc2VjcmV0IDwtICJVcnUwZlY3bEtBaUZHOVpYeTZMN1IzaklNVXpMU0dIMldwbGhGdTJuTXVUOHNjNENuVyINCmFjY2Vzc190b2tlbiA8LSAiMzIzNjAwODU1NS1QM2JKVWZyNkVsUmZucmdCUU1mUnowQjFmQ1FLWDlGV3pDaXEzWlAiDQphY2Nlc3NfdG9rZW5fc2VjcmV0IDwtICI0WHBwMXg5SXVjN1BLNllIYktLanZoV0xoSGFKTmtPYVd2d2lNSEZzdzl3N1QiDQpzZXR1cF90d2l0dGVyX29hdXRoKGFwaV9rZXksIGFwaV9zZWNyZXQsIGFjY2Vzc190b2tlbiwgYWNjZXNzX3Rva2VuX3NlY3JldCkNCmBgYA0KDQo8YnI+DQoNCiMjIENvbGxlY3RpbmcgVHdlZXRzIGZvciBab21hdG8NCg0KPGJyPg0KDQpgYGB7cn0NCnpvbWF0byA8LSBzZWFyY2hUd2l0dGVyKCJab21hdG8iLG4gPSA1MDAwLCBsYW5nID0gImVuIikNCmxlbmd0aCh6b21hdG8pDQpgYGANCg0KPGJyPg0KDQojIyBDb2xsZWN0aW5nIFR3ZWV0cyBmb3IgU3dpZ2d5DQoNCjxicj4NCg0KYGBge3J9DQpzd2lnZ3kgPC0gc2VhcmNoVHdpdHRlcigiU3dpZ2d5IiwgbiA9IDUwMDAsIGxhbmcgPSAiZW4iKQ0KbGVuZ3RoKHN3aWdneSkNCmBgYA0KDQo8YnI+DQoNCiMjIENsZWFuaW5nIHRoZSBDb3JwdXMNCg0KPGJyPg0KDQpgYGB7cn0NCmhlYWQoem9tYXRvKQ0KaGVhZChzd2lnZ3kpDQpgYGANCg0KYGBge3J9DQp6dCA8LSBzYXBwbHkoem9tYXRvLCBmdW5jdGlvbih4KSB4JGdldFRleHQoKSkNCnN0IDwtIHNhcHBseShzd2lnZ3ksIGZ1bmN0aW9uKHgpIHgkZ2V0VGV4dCgpKQ0KYGBgDQoNCmBgYHtyfQ0KY2F0Y2guZXJyb3IgPC0gZnVuY3Rpb24oeCkNCnsNCiAgIyBMZXQgdXMgY3JlYXRlIGEgbWlzc2luZyB2YWx1ZSBmb3IgdGVzdCBwdXJwb3NlDQogIHkgPC0gTkENCiAgIyBUcnkgdG8gY2F0Y2ggdGhhdCBlcnJvciAoTkEpIHdoaWNoIHdlIGhhdmUganVzdCBjcmVhdGVkDQogIGNhdGNoX2Vycm9yIDwtIHRyeUNhdGNoKHRvbG93ZXIoeCksIGVycm9yID0gZnVuY3Rpb24oZSkgZSkNCiAgIyBJZiBub3QgYW4gZXJyb3INCiAgaWYoIWluaGVyaXRzKGNhdGNoX2Vycm9yLCAiZXJyb3IiKSkNCiAgICB5IDwtIHRvbG93ZXIoeCkNCiAgIyBDaGVjayByZXN1bHQgaWYgZXJyb3IgZXhpc3RzLCBvdGhlcndpc2UgdGhlIGZ1bmN0aW9uIHdvcmtzIGZpbmUNCiAgcmV0dXJuKHkpDQp9DQoNCmNsZWFuVHdlZXRzIDwtIGZ1bmN0aW9uKHR3ZWV0KQ0Kew0KICAjIENsZWFuIHRoZSB0d2VldCBmb3Igc2VudGltZW50IGFuYWx5c2lzDQogICMgUmVtb3ZlIGh0bWwgbGlua3MsIHdoaWNoIGFyZSBub3QgcmVxdWlyZWQgZm9yIHNlbnRpbWVudCBhbmFseXNpcw0KICB0d2VldCA8LSBnc3ViKCIoZnxodCkgKHRwKSAocz8pICg6Ly8pICguKikgWy58L10gKC4qKSIsICIgIiwgdHdlZXQpDQogICMgRmlyc3Qgd2Ugd2lsbCByZW1vdmUgdGhlIHJld2VldCBlbnRpdGllcyBmcm9tIHRoZSBzb3J0ZWQgdHdlZXRzICh0ZXh0KQ0KICB0d2VldCA8LSBnc3ViKCIoUlR8dmlhKSAoKD86XFxiXFxXKkBcXHcrKSspIiwgIiAiLCB0d2VldCkNCiAgIyBUaGVuIHJlbW92ZSBhbGwgIiNIYXNoVGFncyINCiAgdHdlZXQgPC0gZ3N1YigiI1xcdysiLCAiICIsIHR3ZWV0KQ0KICAjIFRoZW4gcmVtb3ZlIGFsbCAiQFBlb3BsZSINCiAgdHdlZXQgPC0gZ3N1YigiQFxcdysiLCAiICIsIHR3ZWV0KQ0KICAjIFRoZW4gcmVtb3ZlIGFsbCBwdW5jdHVhdGlvbnMNCiAgdHdlZXQgPC0gZ3N1YigiW1s6cHVuY3Q6XV0iLCAiICIsIHR3ZWV0KQ0KICAjIFRoZW4gcmVtb3ZlIG51bWJlcnMsIHdlIG5lZWQgb25seSB0ZXh0IGZvciBhbmFseXNpcw0KICB0d2VldCA8LSBnc3ViKCJbWzpkaWdpdDpdXSIsICIgIiwgdHdlZXQpDQogICMgRmluYWxseSB3ZSByZW1vdmUgYWxsIHVubmVjZXNzYXJ5IHNwYWNlcyAod2hpdGUgc3BhY2VzLCB0YWJzIGV0YykNCiAgdHdlZXQgPC0gZ3N1YigiWyBcdF17Mix9IiwgIiAiLCB0d2VldCkNCiAgdHdlZXQgPC0gZ3N1YigiXlxccyt8XFxzKyQiLCAiICIsIHR3ZWV0KQ0KICAjIENvbnZlcnQgaW50byBsb3dlcmNhc2UgDQogIHR3ZWV0IDwtIGNhdGNoLmVycm9yKHR3ZWV0KQ0KICB0d2VldA0KfQ0KYGBgDQoNCmBgYHtyfQ0KY2xlYW5Ud2VldHNBbmRSZW1vdmVOQXMgPC0gZnVuY3Rpb24oVHdlZXRzKQ0Kew0KICBUd2VldHNDbGVhbmVkIDwtIHNhcHBseShUd2VldHMsIGNsZWFuVHdlZXRzKQ0KICAjIFJlbW92ZSB0aGUgIk5BIiB0d2VldHMgZnJvbSB0aGlzIHR3ZWV0IGxpc3QNCiAgVHdlZXRzQ2xlYW5lZCA8LSBUd2VldHNDbGVhbmVkWyFpcy5uYShUd2VldHNDbGVhbmVkKV0NCiAgbmFtZXMoVHdlZXRzQ2xlYW5lZCkgPC0gTlVMTA0KICAjIFJlbW92ZSB0aGUgcmVwZXRpdGl2ZSB0d2VldHMgZnJvbSB0aGlzIHR3ZWV0IGxpc3QNCiAgVHdlZXRzQ2xlYW5lZCA8LSB1bmlxdWUoVHdlZXRzQ2xlYW5lZCkNCiAgVHdlZXRzQ2xlYW5lZA0KfQ0KYGBgDQoNCmBgYHtyfQ0KWm9tYXRvQ2xlYW5lZCA8LSBjbGVhblR3ZWV0c0FuZFJlbW92ZU5Bcyh6dCkNCmxlbmd0aChab21hdG9DbGVhbmVkKQ0KYGBgDQoNCmBgYHtyfQ0KU3dpZ2d5Q2xlYW5lZCA8LSBjbGVhblR3ZWV0c0FuZFJlbW92ZU5BcyhzdCkNCmxlbmd0aChTd2lnZ3lDbGVhbmVkKQ0KYGBgDQoNCjxicj4NCg0KIyMgRXN0aW1hdGluZyBTZW50aW1lbnQgUGFydC1BDQoNCjxicj4NCg0KYGBge3J9DQpvcGluaW9uLmxleGljb24ucG9zIDwtIHNjYW4oJ3Bvc2l0aXZlLXdvcmRzLnR4dCcsIHdoYXQgPSAnY2hhcmFjdGVyJywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb21tZW50LmNoYXIgPSAiOyIpDQpvcGluaW9uLmxleGljb24ubmVnIDwtIHNjYW4oJ25lZ2F0aXZlLXdvcmRzLnR4dCcsIHdoYXQgPSAnY2hhcmFjdGVyJywNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb21tZW50LmNoYXIgPSAiOyIpDQpgYGANCg0KYGBge3J9DQpoZWFkKG9waW5pb24ubGV4aWNvbi5wb3MpDQpoZWFkKG9waW5pb24ubGV4aWNvbi5uZWcpDQpgYGANCg0KYGBge3J9DQpuZWcud29yZHMgPC0gYyhvcGluaW9uLmxleGljb24ubmVnLCAiY2FuY2VsbGF0aW9uIiwgInd0ZiIsICJ3YWl0IiwgIndhaXRpbmciICkNCnBvcy53b3JkcyA8LSBvcGluaW9uLmxleGljb24ucG9zDQpgYGANCg0KYGBge3J9DQpnZXRTZW50aW1lbnRTY29yZSA8LSBmdW5jdGlvbihzZW50ZW5jZXMsIHdvcmRzLnBvc2l0aXZlLCB3b3Jkcy5uZWdhdGl2ZSwgLnByb2dyZXNzID0gJ05vbmUnKQ0Kew0KICByZXF1aXJlKHBseXIpDQogIHJlcXVpcmUoc3RyaW5ncikNCiAgDQogIHNjb3JlcyA8LSBsYXBseShzZW50ZW5jZXMsIGZ1bmN0aW9uKHNlbnRlbmNlLCB3b3Jkcy5wb3NpdGl2ZSwgd29yZHMubmVnYXRpdmUpew0KICAgIA0KICAgICMgTGV0IGZpcnN0IHJlbW92ZSB0aGUgZGlnaXQsIHB1bmN0dWF0aW9uIGNoYXJhY3RlciBhbmQgY29udHJvbCBjaGFyYWN0ZXJzDQogICAgc2VudGVuY2UgPC0gZ3N1YigiW1s6Y250cmw6XV0iLCAiIiwgZ3N1YigiW1s6cHVuY3Q6XV0iLCAiIiwgZ3N1YigiXFxkKyIsICIiLCBzZW50ZW5jZSkpKQ0KICAgIA0KICAgICMgVGhlbiBsZXRzIGNvbnZlcnQgYWxsIHRvIGxvd2VyIHNlbnRlbmNlIGNhc2UNCiAgICBzZW50ZW5jZSA8LSB0b2xvd2VyKHNlbnRlbmNlKQ0KICAgIA0KICAgICMgTm93IGxldHMgc3BsaXQgZWFjaCBzZW50ZW5jZSBieSB0aGUgc3BhY2UgZGVsaW1pdGVyDQogICAgd29yZHMgPC0gdW5saXN0KHN0cl9zcGxpdChzZW50ZW5jZSwgIlxccysiKSkNCiAgICANCiAgICAjIEdldCB0aGUgYm9vbGVhbiBtYXRjaCBvZiBlYWNoIHdvcmRzIHdpdGggdGhlIHBvc2l0aXZlIGFuZCBuZWdhdGl2ZSBvcGluaW9uLWxleGljb24NCiAgICBwb3MubWF0Y2hlcyA8LSAhaXMubmEobWF0Y2god29yZHMsIHdvcmRzLnBvc2l0aXZlKSkNCiAgICBuZWcubWF0Y2hlcyA8LSAhaXMubmEobWF0Y2god29yZHMsIHdvcmRzLm5lZ2F0aXZlKSkNCiAgICANCiAgICAjIE5vdyBnZXQgdGhlIHNjb3JlIGFzIHRvdGFsIHBvc2l0aXZlIHNlbnRpbWVudCBtaW51cyB0aGUgdG90YWwgbmVnYXRpdmVzDQogICAgc2NvcmUgPC0gc3VtKHBvcy5tYXRjaGVzKSAtIHN1bShuZWcubWF0Y2hlcykNCiAgICANCiAgICByZXR1cm4oc2NvcmUpDQogIH0sIHdvcmRzLnBvc2l0aXZlLCB3b3Jkcy5uZWdhdGl2ZSwgLnByb2dyZXNzID0gLnByb2dyZXNzKQ0KICANCiAgIyBSZXR1cm4gYSBkYXRhZnJhbWUgd2l0aCByZXNwZWN0aXZlIHNlbnRlbmNlIGFuZCB0aGUgc2NvcmUNCiAgcmV0dXJuKGRhdGEuZnJhbWUodGV4dCA9IHNlbnRlbmNlcywgc2NvcmUgPSBzY29yZXMpKQ0KfQ0KYGBgDQoNCmBgYHtyfQ0Kb3B0aW9ucyh3YXJuID0gLTEpDQpab21hdG9SZXN1bHRzIDwtIGdldFNlbnRpbWVudFNjb3JlKFpvbWF0b0NsZWFuZWQsIHdvcmRzLnBvc2l0aXZlID0gcG9zLndvcmRzLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd29yZHMubmVnYXRpdmUgPSBuZWcud29yZHMpDQpTd2lnZ3lSZXN1bHRzIDwtIGdldFNlbnRpbWVudFNjb3JlKFN3aWdneUNsZWFuZWQsIHdvcmRzLnBvc2l0aXZlID0gcG9zLndvcmRzLCANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgd29yZHMubmVnYXRpdmUgPSBuZWcud29yZHMpDQpgYGANCg0KYGBge3J9DQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KGdyaWRFeHRyYSkNCnAxIDwtIGdncGxvdChab21hdG9SZXN1bHRzLCBhZXMoeCA9IHNjb3JlKSkgKyBnZW9tX2JhcihzdGF0ID0gImNvdW50IiwgZmlsbCA9ICJwdXJwbGUiKSArIA0KICBsYWJzKHRpdGxlID0gIlpvbWF0byBTY29yZXMiLCB4ID0gIlNjb3JlcyIsIHkgPSAiRnJlcXVlbmN5IikgKyB4bGltKGMoLTYsIDYpKSArIA0KICB5bGltKGMoMCwyNTAwKSkNCnAyIDwtIGdncGxvdChTd2lnZ3lSZXN1bHRzLCBhZXMoeCA9IHNjb3JlKSkgKyBnZW9tX2JhcihzdGF0ID0gImNvdW50IiwgZmlsbCA9ICJyZWQiKSArIA0KICBsYWJzKHRpdGxlID0gIlN3aWdneSBTY29yZXMiLCB4ID0gIlNjb3JlcyIsIHkgPSAiRnJlcXVlbmN5IikgKyB4bGltKGMoLTYsIDYpKSArIA0KICB5bGltKGMoMCwyNTAwKSkNCmdyaWQuYXJyYW5nZShwMSwgcDIsIG5yb3cgPSAxKQ0KYGBgDQoNCmBgYHtyfQ0KbWVhbihab21hdG9SZXN1bHRzJHNjb3JlKQ0Kc2QoWm9tYXRvUmVzdWx0cyRzY29yZSkNCm1lYW4oU3dpZ2d5UmVzdWx0cyRzY29yZSkNCnNkKFN3aWdneVJlc3VsdHMkc2NvcmUpDQpgYGANCg0KPGJyPg0KDQojIyBFc3RpbWF0aW5nIFNlbnRpbWVudCBQYXJ0LUINCg0KPGJyPg0KDQpgYGB7cn0NCmluc3RhbGwucGFja2FnZXMoIlJzdGVtIiwNCnJlcG9zID0gImh0dHA6Ly93d3cub21lZ2FoYXQub3JnL1IiLCB0eXBlPSJzb3VyY2UiKQ0KcmVxdWlyZShkZXZ0b29scykNCmluc3RhbGxfdXJsKCJodHRwOi8vY3Jhbi5yLXByb2plY3Qub3JnL3NyYy9jb250cmliL0FyY2hpdmUvc2VudGltZW50L3NlbnRpbWVudF8wLjIudGFyLmd6IikNCnJlcXVpcmUoc2VudGltZW50KQ0KbHMoInBhY2thZ2U6c2VudGltZW50IikNCmBgYA0KDQpgYGB7cn0NCmxpYnJhcnkoc2VudGltZW50KQ0KYGBgDQoNCmBgYHtyfQ0KIyBDbGFzc2lmeSBlbW90aW9uIGZ1bmN0aW9uIHJldHVybnMgYW4gb2JqZWN0IG9mIGNsYXNzIGRhdGEgZnJhbWUgDQojIFdpdGggc2V2ZW4gY29sdW1ucyAoYW5nZXIsIGRpc2d1c3QsIGZlYXIsIGpveSwgc2FkbmVzcywgc3VwcmlzZSwgYmVzdCBmaXQpIGFuZCBvbmUgcm93IGZvciBlYWNoIGRvY3VtZW50DQoNClpvbWF0b0NsYXNzIDwtIGNsYXNzaWZ5X2Vtb3Rpb24oWm9tYXRvQ2xlYW5lZCwgYWxnb3JpdGhtID0gImJheWVzIiwgcHJpb3IgPSAxLjApDQpTd2lnZ3lDbGFzcyA8LSBjbGFzc2lmeV9lbW90aW9uKFN3aWdneUNsZWFuZWQsIGFsZ29yaXRobSA9ICJiYXllcyIsIHByaW9yID0gMS4wKQ0KYGBgDQoNCmBgYHtyfQ0KaGVhZChab21hdG9DbGFzcykNCmhlYWQoU3dpZ2d5Q2xhc3MpDQpgYGANCg0KDQpgYGB7cn0NClpvbWF0b0Vtb3Rpb24gPC0gWm9tYXRvQ2xhc3NbLCA3XQ0KU3dpZ2d5RW1vdGlvbiA8LSBTd2lnZ3lDbGFzc1ssIDddDQpgYGANCg0KYGBge3J9DQpab21hdG9FbW90aW9uW2lzLm5hKFpvbWF0b0Vtb3Rpb24pXSA8LSAidW5rbm93biINClN3aWdneUVtb3Rpb25baXMubmEoU3dpZ2d5RW1vdGlvbildIDwtICJ1bmtub3duIg0KYGBgDQoNCmBgYHtyfQ0KaGVhZChab21hdG9FbW90aW9uLCAyMCkNCmhlYWQoU3dpZ2d5RW1vdGlvbiwgMjApDQpgYGANCg0KYGBge3J9DQpab21hdG9DbGFzc1BvbCA8LSBjbGFzc2lmeV9wb2xhcml0eShab21hdG9DbGVhbmVkLCBhbGdvcml0aG0gPSAiYmF5ZXMiKQ0Kc3dpZ2d5Q2xhc3NQb2wgPC0gY2xhc3NpZnlfcG9sYXJpdHkoU3dpZ2d5Q2xlYW5lZCwgYWxnb3JpdGhtID0gImJheWVzIikNCmBgYA0KDQpgYGB7cn0NCmhlYWQoWm9tYXRvQ2xhc3NQb2wpDQpgYGANCg0KYGBge3J9DQpoZWFkKHN3aWdneUNsYXNzUG9sKQ0KYGBgDQoNCmBgYHtyfQ0KIyBXZSB3aWxsIGZldGNoIHBvbGFyaXR5IGNhdGVnb3J5IGJlc3QgZml0IGZvciBvdXIgYW5hbHlzaXMgcHVycG9zZQ0KWm9tYXRvUG9sIDwtIFpvbWF0b0NsYXNzUG9sWywgNF0NClN3aWdneVBvbCA8LSBzd2lnZ3lDbGFzc1BvbFssIDRdDQpgYGANCg0KYGBge3J9DQojIExldCB1cyBjcmVhdGUgbm93IGEgZGF0YSBmcmFtZSB3aXRoIHRoZSBhYm92ZSByZXN1bHRzDQpab21hdG9ERiA8LSBkYXRhLmZyYW1lKHRleHQgPSBab21hdG9DbGVhbmVkLCBlbW90aW9uID0gWm9tYXRvRW1vdGlvbiwgcG9sYXJpdHkgPSBab21hdG9Qb2wsDQogICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkNClN3aWdneURGIDwtIGRhdGEuZnJhbWUodGV4dCA9IFN3aWdneUNsZWFuZWQsIGVtb3Rpb24gPSBTd2lnZ3lFbW90aW9uLCBwb2xhcml0eSA9IFN3aWdneVBvbCwNCiAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQ0KYGBgDQoNCmBgYHtyfQ0KIyBSZWFycmFuZ2UgZGF0YSBpbnNpZGUgdGhlIGRhdGEgZnJhbWUgYnkgc29ydGluZyBpdA0KWm9tYXRvREYgPC0gd2l0aGluKFpvbWF0b0RGLCBlbW90aW9uIDwtIGZhY3RvcihlbW90aW9uLCBsZXZlbHMgPSBuYW1lcyhzb3J0KHRhYmxlKGVtb3Rpb24pLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBkZWNyZWFzaW5nID0gVCkpKSkNClN3aWdneURGIDwtIHdpdGhpbihTd2lnZ3lERiwgZW1vdGlvbiA8LSBmYWN0b3IoZW1vdGlvbiwgbGV2ZWxzID0gbmFtZXMoc29ydCh0YWJsZShlbW90aW9uKSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZGVjcmVhc2luZyA9IFQpKSkpDQpgYGANCg0KYGBge3J9DQpoZWFkKFpvbWF0b0RGLCAxMCkNCmhlYWQoU3dpZ2d5REYsIDEwKQ0KYGBgDQoNCg0KYGBge3J9DQpwbG90U2VudGltZW50cyA8LSBmdW5jdGlvbihkZix0aXRsZSkNCnsNCiAgZ2dwbG90KGRmLCBhZXMoeCA9IGVtb3Rpb24pKSArDQogICAgZ2VvbV9iYXIoYWVzKHkgPSAuLmNvdW50Li4sIGZpbGwgPSBlbW90aW9uKSkgKyANCiAgICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJEYXJrMiIpICsgDQogICAgZ2d0aXRsZSh0aXRsZSkgKyB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAicmlnaHQiKSArIA0KICAgIHlsYWIoIk51bWJlciBvZiBUd2VldHMiKSArDQogICAgeGxhYigiRW1vdGlvbiBDYXRlZ29yaWVzIikgKyANCiAgICB5bGltKGMoMCw0MDAwKSkNCn0NCmBgYA0KDQpgYGB7cn0NCnBsb3RTZW50aW1lbnRzKFpvbWF0b0RGLCJTZW50aW1lbnQgQW5hbHlzaXMgb2YgVHdlZXRzIG9uIFR3aXR0ZXIgYWJvdXQgWm9tYXRvIikNCmBgYA0KDQpgYGB7cn0NCnBsb3RTZW50aW1lbnRzKFN3aWdneURGLCJTZW50aW1lbnQgQW5hbHlzaXMgb2YgVHdlZXRzIG9uIFR3aXR0ZXIgYWJvdXQgU3dpZ2d5IikNCmBgYA0KDQpgYGB7cn0NCiMgU2ltaWxhcmx5IHdlIHdpbGwgcGxvdCBkaXN0cmlidXRpb24gaW4gdGhlIHR3ZWV0cyANCnBsb3RTZW50aW1lbnRzMiA8LSBmdW5jdGlvbihkZiwgdGl0bGUpDQp7DQogIGdncGxvdChkZiwgYWVzKHggPSBwb2xhcml0eSkpICsNCiAgICBnZW9tX2JhcihhZXMoeSA9IC4uY291bnQuLiwgZmlsbCA9IHBvbGFyaXR5KSkgKyANCiAgICBzY2FsZV9jb2xvcl9icmV3ZXIocGFsZXR0ZSA9ICJSZEd5IikgKyANCiAgICBnZ3RpdGxlKHRpdGxlKSArIHRoZW1lKGxlZ2VuZC5wb3NpdGlvbiA9ICJyaWdodCIpICsgDQogICAgeWxhYigiTnVtYmVyIG9mIFR3ZWV0cyIpICsNCiAgICB4bGFiKCJQb2xhcml0eSBDYXRlZ29yaWVzIikgKw0KICAgIHlsaW0oYygwLCA0MDAwKSkNCn0NCmBgYA0KDQpgYGB7cn0NCnBsb3RTZW50aW1lbnRzMihab21hdG9ERiwgIlBvbGFyaXR5IEFuYWx5c2lzIG9mIFR3ZWV0cyBvbiBUd2l0dGVyIGFib3V0IFpvbWF0byIpDQpgYGANCg0KYGBge3J9DQpwbG90U2VudGltZW50czIoU3dpZ2d5REYsICJQb2xhcml0eSBBbmFseXNpcyBvZiBUd2VldHMgb24gVHdpdHRlciBhYm91dCBTd2lnZ3kiKQ0KYGBgDQoNCmBgYHtyfQ0KcmVtb3ZlQ3VzdG9tZVdvcmRzIDwtIGZ1bmN0aW9uIChUd2VldHNDbGVhbmVkKSANCnsNCiAgICBmb3IoaSBpbiAxOmxlbmd0aChUd2VldHNDbGVhbmVkKSkNCiAgICB7DQogICAgICAgIFR3ZWV0c0NsZWFuZWRbaV0gPC0gdHJ5Q2F0Y2goew0KICAgICAgICBUd2VldHNDbGVhbmVkW2ldID0gcmVtb3ZlV29yZHMoVHdlZXRzQ2xlYW5lZFtpXSwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjKHN0b3B3b3JkcygiZW5nbGlzaCIpLCAiY2FyZSIsICJndXlzIiwgImNhbiIsIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgImRpcyIsICJkaWRuIiwiZ3V5IiAsImJvb2tlZCIsICJwbHoiLCAib3JkZXIiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIm9yZGVyZWQiLCAiZ2V0IiwgImhleSIsICJhbHNvIikpDQogICAgICAgIFR3ZWV0c0NsZWFuZWRbaV0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB9LCBlcnJvcj1mdW5jdGlvbihjb25kKSANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHsNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgVHdlZXRzQ2xlYW5lZFtpXQ0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSwgd2FybmluZz1mdW5jdGlvbihjb25kKSANCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgew0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFR3ZWV0c0NsZWFuZWRbaV0NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfSkNCiAgICB9DQogICAgcmV0dXJuKFR3ZWV0c0NsZWFuZWQpDQp9DQpgYGANCg0KYGBge3J9DQpnZXRXb3JkQ2xvdWQgPC0gZnVuY3Rpb24oc2VudGltZW50X2RhdGFmcmFtZSwgVHdlZXRzQ2xlYW5lZCwgRW1vdGlvbikgDQp7DQogICAgZW1vcyA9IGxldmVscyhmYWN0b3Ioc2VudGltZW50X2RhdGFmcmFtZSRlbW90aW9uKSkNCiAgICBuX2Vtb3MgPSBsZW5ndGgoZW1vcykNCiAgICBlbW8uZG9jcyA9IHJlcCgiIiwgbl9lbW9zKQ0KICAgIFR3ZWV0c0NsZWFuZWQgPSByZW1vdmVDdXN0b21lV29yZHMoVHdlZXRzQ2xlYW5lZCkNCiAgICBmb3IgKGkgaW4gMTpuX2Vtb3MpDQogICAgew0KICAgICAgICBlbW8uZG9jc1tpXSA9IHBhc3RlKFR3ZWV0c0NsZWFuZWRbRW1vdGlvbiA9PQ0KICAgICAgICBlbW9zW2ldXSwgY29sbGFwc2U9IiAiKQ0KICAgIH0NCiAgICBjb3JwdXMgPSBDb3JwdXMoVmVjdG9yU291cmNlKGVtby5kb2NzKSkNCiAgICB0ZG0gPSBUZXJtRG9jdW1lbnRNYXRyaXgoY29ycHVzKQ0KICAgIHRkbSA9IGFzLm1hdHJpeCh0ZG0pDQogICAgY29sbmFtZXModGRtKSA9IGVtb3MNCiAgICByZXF1aXJlKHdvcmRjbG91ZCkNCiAgc3VwcHJlc3NXYXJuaW5ncyhjb21wYXJpc29uLmNsb3VkKHRkbSwgY29sb3JzID0NCiAgYnJld2VyLnBhbChuX2Vtb3MsICJEYXJrMiIpLCBzY2FsZSA9IGMoMywuNSksIHJhbmRvbS5vcmRlciA9IEZBTFNFLCB0aXRsZS5zaXplID0gMS41KSkNCn0NCmBgYA0KDQpgYGB7cn0NCmdldFdvcmRDbG91ZChab21hdG9ERiwgWm9tYXRvQ2xlYW5lZCwgWm9tYXRvRW1vdGlvbikNCmBgYA0KDQpgYGB7cn0NCmdldFdvcmRDbG91ZChTd2lnZ3lERiwgU3dpZ2d5Q2xlYW5lZCwgU3dpZ2d5RW1vdGlvbikNCmBgYA0KDQo=