Required packages


library(readr)
library(dplyr)
library(tidyr)
library(lubridate)
library(stringr)
library(knitr)
library(forecast)
library(car)

Executive Summary

The aim of this assignment is to pre-process user review data of Google Play applications to prepare it for analysis on the application’s effectiveness e.g. Correlations between Price, reviews and sentiments and to rank the apps in different categories.

The 2 data sets used, googleplaystore.csv and googleplaystore_user_reviews.csv, were imported and merged by a common variable (Apps). For a better understanding of the data set, an analysis of the structure and variable class type was conducted. Irrelevant variables were dropped to simplify the process. Then, data type conversions were carried out on some variables, some variables were factored while some ordered.

The data was already in a tidy format, hence no reshaping was needed. 3 new columns were created through the mutate function which separated the date on which the application was last updated into day, month and year for easier comparison of data by month or year.

Several missing values were identified in the data set. Rows with missing values in Ratings, Sentiment Polarity and Translated Review were removed, whereas missing values in Sizes were replaced by the mean size of their individual category using imputation. The missing values in Price were due to the applications being free, so they were replaced with a 0.

Lastly, removal of outliers and transformation were performed to try to reduce the effects of outliers on skewing the results. The capping method was used to handle the outliers. We capped outlier values with the closest 5th percentile. On the heavily right skewed, data, log10 transformation was applied to the variable to reduce the skewness before capping. This resulted in a more normal distribution and eliminated much of the perceived outliers.

Data

Data obtained from: https://www.kaggle.com/lava18/google-play-store-apps Data was scraped from Google Play App Store on over 10k apps as well as their reviews.

Datasets used: googleplaystore.csv and googleplaystore_user_reviews.csv App information is stored in the googleplaystore.csv while reviews information is stored in the googleplaystore_user_reviews.csv. Variable descriptions for each dataset are shown below.

Both datasets were imported and merged (left_join) by a common variable, ‘App’, for easier analysis. The left_join is appropriate as it matches rows from googleplaystore_user_reviews to googleplaystore, so that each review is matched to the appropriate app.

To simplify the process, variables ‘Android Ver’ and ‘Current Ver’ were removed as version history will not be useful in the analysis.

Loading the data into R.

googleplaystore <- read_csv("googleplaystore.csv")
googleplaystore_user_reviews <- read_csv("googleplaystore_user_reviews.csv")
playstoredescription <- read_csv("playstoredescription.csv")
UserReviewsdescription <- read_csv("UserReviewsdescription.csv")

Variables description in googleplaystore:

Variable Description
App Application name
Category Category the app belongs to
Rating Overall user rating of the app (as when scraped)
Reviews Number of user reviews for the app (as when scraped)
Size Size of the app (as when scraped)
Installs Number of user downloads/installs for the app (as when scraped)
Type Paid or Free
Price Price of the app (as when scraped)
Content Rating Age group the app is targeted at - Children / Mature 21+ / Adult
Genres An app can belong to multiple genres (apart from its main category). For eg, a musical family game will belong to Music, Game, Family genres.
Last Updated Date when the app was last updated on Play Store (as when scraped)
Current Ver Current version of the app available on Play Store (as when scraped)
Android Ver Min required Android version (as when scraped)

Variables description in googleplaystore_user_reviews:

Variable Description
App Application name
Translated_Review User review (Preprocessed and translated to English)
Sentiment Positive/Negative/Neutral (Preprocessed)
Sentiment_Polarity Sentiment polarity score
Sentiment_Subjectivity Sentiment subjectivity score

Joining the Data Sets

The common variable on the data set is apps, where the name of the apps are stored. To create meaningful analysis, we will join the two data sets together with on the app names. New table we will be working with will be called apps.

apps <- googleplaystore %>% left_join(googleplaystore_user_reviews, by = "App")

Understand

Checking the structure of the data

str(apps)
Classes ‘spec_tbl_df’, ‘tbl_df’, ‘tbl’ and 'data.frame':    131971 obs. of  17 variables:
 $ App                   : chr  "Photo Editor & Candy Camera & Grid & ScrapBook" "Coloring book moana" "Coloring book moana" "Coloring book moana" ...
 $ Category              : chr  "ART_AND_DESIGN" "ART_AND_DESIGN" "ART_AND_DESIGN" "ART_AND_DESIGN" ...
 $ Rating                : num  4.1 3.9 3.9 3.9 3.9 3.9 3.9 3.9 3.9 3.9 ...
 $ Reviews               : num  159 967 967 967 967 967 967 967 967 967 ...
 $ Size                  : chr  "19M" "14M" "14M" "14M" ...
 $ Installs              : chr  "10,000+" "500,000+" "500,000+" "500,000+" ...
 $ Type                  : chr  "Free" "Free" "Free" "Free" ...
 $ Price                 : chr  "0" "0" "0" "0" ...
 $ Content Rating        : chr  "Everyone" "Everyone" "Everyone" "Everyone" ...
 $ Genres                : chr  "Art & Design" "Art & Design;Pretend Play" "Art & Design;Pretend Play" "Art & Design;Pretend Play" ...
 $ Last Updated          : chr  "January 7, 2018" "January 15, 2018" "January 15, 2018" "January 15, 2018" ...
 $ Current Ver           : chr  "1.0.0" "2.0.0" "2.0.0" "2.0.0" ...
 $ Android Ver           : chr  "4.0.3 and up" "4.0.3 and up" "4.0.3 and up" "4.0.3 and up" ...
 $ Translated_Review     : chr  NA "A kid's excessive ads. The types ads allowed app, let alone kids" "It bad >:(" "like" ...
 $ Sentiment             : chr  NA "Negative" "Negative" "Neutral" ...
 $ Sentiment_Polarity    : num  NA -0.25 -0.725 0 NaN 0.5 -0.8 NaN 0 0.5 ...
 $ Sentiment_Subjectivity: num  NA 1 0.833 0 NaN ...

Removing unused variables

Version history is not useful for us. Therefore we decided to remove them as variables.

  • Android Ver
  • Current Ver
apps<-apps %>% select(-c(`Android Ver`,`Current Ver`))
colnames(apps)
 [1] "App"                    "Category"              
 [3] "Rating"                 "Reviews"               
 [5] "Size"                   "Installs"              
 [7] "Type"                   "Price"                 
 [9] "Content Rating"         "Genres"                
[11] "Last Updated"           "Translated_Review"     
[13] "Sentiment"              "Sentiment_Polarity"    
[15] "Sentiment_Subjectivity"

Converting the variable into ordered and unordered factors

  • Installs
  • Type
  • Content Ratings
  • Sentiment
  • Category
apps <- apps %>% mutate(
  Installs = factor(apps$Installs, 
                    levels = c( "0","0+","1+","5+","10+","50+","100+","500+","1,000+",
                                "5,000+","10,000+",  "50,000+", "100,000+",  "500,000+",
                                "1,000,000+","5,000,000+" ,  "10,000,000+" ,  "50,000,000+", "100,000,000+",
                                "500,000,000+","1,000,000,000+") ,
                    labels = c( "0","0+","1+","5+","10+","50+","100+","500+","1,000+",
                                "5,000+","10,000+",  "50,000+", "100,000+",  "500,000+",
                                "1,000,000+","5,000,000+" ,  "10,000,000+" ,  "50,000,000+",
                                "100,000,000+", "500,000,000+","1,000,000,000+"),
                    ordered=T),
  Type = factor(apps$Type, 
                levels = c("Free", "Paid"),
                labels = c("Free", "Paid")),
  `Content Rating`= factor(apps$`Content Rating`,
                           levels = c("Everyone", "Everyone 10+",  "Teen", "Mature 17+", "Adults only 18+"),
                           labels = c("Everyone", "Everyone 10+",  "Teen", "Mature 17+", "Adults only 18+"),
                           ordered = T ),
  Sentiment = factor(apps$Sentiment, 
                      levels = c("Negative", "Neutral", "Positive"),
                      labels = c("Negative", "Neutral", "Positive"),
                      ordered = T),
  Category = factor(apps$Category)
  )
str(apps[,c("Installs", "Type", "Content Rating", "Sentiment", "Category")]) 
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   131971 obs. of  5 variables:
 $ Installs      : Ord.factor w/ 21 levels "0"<"0+"<"1+"<..: 11 14 14 14 14 14 14 14 14 14 ...
 $ Type          : Factor w/ 2 levels "Free","Paid": 1 1 1 1 1 1 1 1 1 1 ...
 $ Content Rating: Ord.factor w/ 5 levels "Everyone"<"Everyone 10+"<..: 1 1 1 1 1 1 1 1 1 1 ...
 $ Sentiment     : Ord.factor w/ 3 levels "Negative"<"Neutral"<..: NA 1 1 2 NA 3 1 NA 2 3 ...
 $ Category      : Factor w/ 34 levels "1.9","ART_AND_DESIGN",..: 2 2 2 2 2 2 2 2 2 2 ...

Date conversion

Converting ‘Last Updated’ into date format in a new column. Then dropping the column ‘Last Updated’. This is to avoid errors if run code twice, as it would convert date variables to NA. This is to ensure integrity.

apps <- apps %>% mutate(Updated = mdy(apps$`Last Updated`))
 1 failed to parse.
apps <- apps %>% select(-`Last Updated`)

str(apps$Updated)
 Date[1:131971], format: "2018-01-07" "2018-01-15" "2018-01-15" "2018-01-15" "2018-01-15" ...

Changing Price from character to numeric

apps$Price <- substr(apps$Price,2,nchar(apps$Price)) %>% as.numeric()
NAs introduced by coercion
str(apps$Price)
 num [1:131971] NA NA NA NA NA NA NA NA NA NA ...

We note that as.numeric changes 0 to NA in the conversion process. We will impute back the 0s in the scan process.

Changing application size variable to numeric

Application sizes are either recorded in megabytes, kilobytes, or are recorded as “varies with device”. We want to convert this to numeric for better analysis hence a common unit of measurement should be used. We decided to use megabytes for this.

We first extract the numeric part from the string. Then extract the ‘M’, or ‘k’. if it is anything else, we recognise it as the ‘varies with device’ value. This we are going to allow to be NA as we do not have enough information.
Finally we are putting it all together. If it is in kilobytes, we are multiplying by 0.001 to adjust the value to be in megabytes. NAs are recorded for ‘varies with device’ value. This we will impute in the later section with the mean size of the respective category.

unit_size<-str_extract(apps$Size,"[aA-zZ]") 
value_size <- substr(apps$Size,start = 1,stop=(nchar(apps$Size)-1)) %>% as.numeric()
NAs introduced by coercion
conversion <-function(x,y) {ifelse(x=="M",y,ifelse(x=="k",y*0.001,NA))}
size<-conversion(unit_size,value_size)
apps<-apps %>% mutate(Size=size)

class(apps$Size)
[1] "numeric"
summary(apps$Size)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max.    NA's 
   0.01   10.00   24.00   33.33   52.00  100.00   48407 

Check on data structure that all variables are in the right class.

str(apps)
Classes ‘spec_tbl_df’, ‘tbl_df’, ‘tbl’ and 'data.frame':    131971 obs. of  15 variables:
 $ App                   : chr  "Photo Editor & Candy Camera & Grid & ScrapBook" "Coloring book moana" "Coloring book moana" "Coloring book moana" ...
 $ Category              : Factor w/ 34 levels "1.9","ART_AND_DESIGN",..: 2 2 2 2 2 2 2 2 2 2 ...
 $ Rating                : num  4.1 3.9 3.9 3.9 3.9 3.9 3.9 3.9 3.9 3.9 ...
 $ Reviews               : num  159 967 967 967 967 967 967 967 967 967 ...
 $ Size                  : num  19 14 14 14 14 14 14 14 14 14 ...
 $ Installs              : Ord.factor w/ 21 levels "0"<"0+"<"1+"<..: 11 14 14 14 14 14 14 14 14 14 ...
 $ Type                  : Factor w/ 2 levels "Free","Paid": 1 1 1 1 1 1 1 1 1 1 ...
 $ Price                 : num  NA NA NA NA NA NA NA NA NA NA ...
 $ Content Rating        : Ord.factor w/ 5 levels "Everyone"<"Everyone 10+"<..: 1 1 1 1 1 1 1 1 1 1 ...
 $ Genres                : chr  "Art & Design" "Art & Design;Pretend Play" "Art & Design;Pretend Play" "Art & Design;Pretend Play" ...
 $ Translated_Review     : chr  NA "A kid's excessive ads. The types ads allowed app, let alone kids" "It bad >:(" "like" ...
 $ Sentiment             : Ord.factor w/ 3 levels "Negative"<"Neutral"<..: NA 1 1 2 NA 3 1 NA 2 3 ...
 $ Sentiment_Polarity    : num  NA -0.25 -0.725 0 NaN 0.5 -0.8 NaN 0 0.5 ...
 $ Sentiment_Subjectivity: num  NA 1 0.833 0 NaN ...
 $ Updated               : Date, format: "2018-01-07" "2018-01-15" ...
  • Apps are the name of each app. So okay to keep as character data.
  • Genres are okay to keep as character data.
  • Sentiment_Polarity and Sentiment_Subjectivity are out analysis variables and they should be in numerical.
  • All other variable we have changed into the correct class.

Tidy & Manipulate Data I

The data is already in a tidy format since:
1. All variables have a column. - Each column relates to an attribute of the app.
2. All observations have row - ie. each row relates to an app and an individual review.
3. Each value is in a cell.

head(apps, 6)

Tidy & Manipulate Data II

If analysis on when is the app updated have an effect on the sentiment of the reviews, it will be useful to have the Year, Month, and Day of when last reviewed in separate columns for analysis.

Creating the Year, Month and Day column for updated values.

apps <- apps %>% mutate(Day = day(apps$Updated), 
                  Month = month(apps$Updated), 
                  Year = year(apps$Updated))
str(apps[,c("Day", "Month", "Year")])
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   131971 obs. of  3 variables:
 $ Day  : int  7 15 15 15 15 15 15 15 15 15 ...
 $ Month: num  1 1 1 1 1 1 1 1 1 1 ...
 $ Year : num  2018 2018 2018 2018 2018 ...

Scan I

Checing for missing values(NA, Infinite and NaN).

Checking the total number of missing and special values and displaying them to handle one by one.

n <- colSums(is.na(apps)) %>% as.data.frame()
names(n) <- "NA"

i <- sapply(apps, is.infinite) %>% as.data.frame() %>% 
colSums()

nan <- sapply(apps, is.nan) %>% as.data.frame() %>% 
colSums()

x <- n %>% mutate(Infinite = i, Nan = nan) 
row.names(x) <- colnames(apps)
x
NA

Checking why reviews and installs have only one NA

apps[which(is.na(apps$Reviews)),]

Looks to have the values in the wrong columns. It is likely to be an error from the scraping. Since we have a large data sample, we will deal with it by deleting.

apps <- apps[-which(is.na(apps$Reviews)),]
sum(is.na(apps$Reviews))
[1] 0

For missing values that are in ratings, we will deal with them by removing all rows with missing values. Since our analysis are on rating sentiments. The apps that does not have any ratings is not useful to be included.

apps <- apps[-which(is.na(apps$Rating)),]
sum(is.na(apps$Rating))
[1] 0

When converting Price into numeric, 0 was changed to NA. As, 0 is still a valid price, and it does add value to the information, we are Imputing NA in price with 0.


apps$Price[which(is.na(apps$Price))] <- 0
sum(is.na(apps$Price))
[1] 0

For the Sizes variable, there was a value called “Varies with device”. When changing into numeric format, this have become NA.

We will impute these NA with the average size of apps of their individual category.

apps <- apps %>% 
  group_by(Category) %>% 
  mutate(Size = ifelse(is.na(Size), 
                           mean(Size,na.rm = T),
                           Size)) %>% ungroup()

# Checking if Size is imputed 
sum(is.na(apps$Size))
[1] 0
# Displaying sizes by categories 
apps %>% 
  group_by(Category) %>% summarise(mean=round(mean(Size),2))
NA

Translated Review is where the reviews are collected. An app user can leave or not leave a written review after giving a rating. If no rating is given, then it is recorded as NaN. Some, non recorded reviews are recorded as NA here. Since we are going to analyse the sentiments, we will look at only reviews are left. Therefore we will deal with missing vallues in Translated_Review by removing them.

apps <- apps[-which(is.na(apps$Translated_Review)),]
sum(is.na(apps$Translated_Review))
[1] 0

Sentiment Polarity is one of the variables for analysis. Therefore it is good to have a data set with none missing values here, Removing rows with missing values in Sentiment Polarity. Also not that, is.na here also includes NaNs which was created for any apps that had a review but didn’t leave any text. We will be excluding these from our analysis.

apps <- apps[-which(is.na(apps$Sentiment_Polarity)),]
sum(is.na(apps$Sentiment_Polarity))
[1] 0

Final missing value check:

Checking for NA, Inf and Nan.

n <- colSums(is.na(apps)) %>% as.data.frame()
names(n) <- "NA"

i <- sapply(apps, is.infinite) %>% as.data.frame() %>% 
colSums()

nan <- sapply(apps, is.nan) %>% as.data.frame() %>% 
colSums()

x <- n %>% mutate(Infinite = i, Nan = nan) 
row.names(x) <- colnames(apps)
x
NA

We have dealt with all the missing, infinite and Nan values.

Scan II

Identify numeric data

check_numeric <-sapply(apps, is.numeric) %>% as.data.frame()
names(check_numeric) <-"Numeric"
check<-check_numeric %>% mutate(Variable=colnames(apps),Numeric=Numeric) 
check<-check%>% filter(Numeric==T) %>% select(Variable,Numeric)
check

We can see numeric data are
* Rating
* Reviews
* Size
* Price
* Sentiment_Polarity
* Sentiment_Subjectivity

We will ignore the variables Day, Month and Year as these have been created by us and it’s not relevant to check for outliers

Checking for outliers in them:

par(mfrow=c(2,3))
Boxplot(apps$Rating, main="Rating")
 [1] 42886 42887 42888 42889 42890 42891 42892 42893 42894 42895
Boxplot(apps$Reviews, main="Reviews")
 [1] 43462 43463 43464 43465 43466 43467 43468 43469 43470 43471
Boxplot(apps$Size, main="Size")
 [1] 20255 20256 20257 20258 20259 20260 20261 20262 20263 20264
Boxplot(apps$Sentiment_Polarity, main= "Sentiment Polarity")
 [1]  11  33 116 168 292 300 515 673 715 717  22  44  77 112 163 264 266 337
[19] 473 474
Boxplot(apps$Sentiment_Subjectivity, main = "Sentiment Subjectivity")
 [1]   3   6  26  29  46  72  80  81  84 102

For price, we group the data by Type of app (Free/Paid) and check for outliers as the data would otherwise be heavily skewed due to large number of free apps


Boxplot(apps$Price~apps$Type, main = "Price grouped by type of app")
 [1] "49836" "49837" "49838" "49839" "49840" "49841" "49842" "49843" "49844"
[10] "49845"

Reviews looks to be severly right skewed.

hist(apps$Reviews, main="Reviews")

It will make better sense if we do a transformation of the data before capping the outliers in case of doing loosing too much information.

Capping the Outliers for:

  • Rating
  • Size
  • Sentiment_Polarity
  • Sentiment_Subjectivity

We are capping them within the 95%. As it makes sense for these variables to still have the outlier value creating an effect. Just the effect should not be excessive.

cap <- function(x){
quantiles <- quantile( x,probs =  c(0.05, 0.25, 0.75, 0.95),na.rm=TRUE)
x[ x < quantiles[2] - 1.5*IQR(x,na.rm=T) ] <- quantiles[1]
x[ x > quantiles[3] + 1.5*IQR(x,na.rm=T) ] <- quantiles[4]
x
}
apps[,c("Rating", "Size","Sentiment_Polarity", "Sentiment_Subjectivity")] <- sapply(apps[,c("Rating", "Size","Sentiment_Polarity", "Sentiment_Subjectivity")], cap) %>% 
  as.data.frame()

Capping the Outliers for Price grouped by Type (Paid apps get capped among paid apps only)


apps <- apps %>% 
  group_by(Type) %>% 
  mutate(Price = cap(Price)) %>% ungroup()

Checking if Outliers are capped:

par(mfrow=c(2,2), pty = "s" )
Boxplot(apps$Rating, main="Rating")
Boxplot(apps$Size, main="Size")
Boxplot(apps$Sentiment_Polarity, main= "Sentiment Polarity")
Boxplot(apps$Sentiment_Subjectivity, main = "Sentiment Subjectivity")
 [1]   3   6  26  29  46  72  80  81  84 102

It is seen that the outliers remain in Sentiment Subjectivity even after capping to the nearest 5th quantile

Checking outliers for Price

Boxplot(apps$Price~apps$Type, main = "Price groupd by type of app")
 [1] "49836" "49837" "49838" "49839" "49840" "49841" "49842" "49843" "49844"
[10] "49845"

It is seen that due to the number of highly priced apps the outliers remain even after capping to the nearest quantile

Transform

We see from the previous section that number of reviews is heavily skewed.


hist(apps$Reviews, main="Reviews")

So for heavily right skewed, we apply a log 10 transformation to get it to more normally distributed.

apps <- apps %>% mutate(Reviews_t = log10(apps$Reviews))

Checking the distribution of the transformed variable (reviews)


hist(apps$Reviews_t, main="Log10(Reviews)")


Boxplot(apps$Reviews_t, main="log10(Reviews)")

We also note that by applying the transformation, all the outliers are removed also.

Summary

Arranging the columns and the rows(according to Category and ranking) and check the structure of the final data.


apps<-apps %>% select(App,Category,Genres,Size,Updated,Year,Month,Day,Type,Price,`Content Rating`,Reviews,Rating,Installs,
                        Translated_Review ,Sentiment,Sentiment_Polarity,  Sentiment_Subjectivity,Reviews_t)

#arranging the data according to Categories (A-Z) and ratings (high to low) within these categories.
apps<-apps %>% arrange(Category,desc(Rating))

head(apps,10)

#checking the structure of the final data
str(apps)
Classes ‘tbl_df’, ‘tbl’ and 'data.frame':   72566 obs. of  19 variables:
 $ App                   : chr  "Colorfit - Drawing & Coloring" "Colorfit - Drawing & Coloring" "Colorfit - Drawing & Coloring" "Colorfit - Drawing & Coloring" ...
 $ Category              : Factor w/ 34 levels "1.9","ART_AND_DESIGN",..: 2 2 2 2 2 2 2 2 2 2 ...
 $ Genres                : chr  "Art & Design;Creativity" "Art & Design;Creativity" "Art & Design;Creativity" "Art & Design;Creativity" ...
 $ Size                  : num  25 25 25 25 25 25 25 25 25 25 ...
 $ Updated               : Date, format: "2017-10-11" "2017-10-11" ...
 $ Year                  : num  2017 2017 2017 2017 2017 ...
 $ Month                 : num  10 10 10 10 10 10 10 10 10 10 ...
 $ Day                   : int  11 11 11 11 11 11 11 11 11 11 ...
 $ Type                  : Factor w/ 2 levels "Free","Paid": 1 1 1 1 1 1 1 1 1 1 ...
 $ Price                 : num  0 0 0 0 0 0 0 0 0 0 ...
 $ Content Rating        : Ord.factor w/ 5 levels "Everyone"<"Everyone 10+"<..: 1 1 1 1 1 1 1 1 1 1 ...
 $ Reviews               : num  20260 20260 20260 20260 20260 ...
 $ Rating                : num  4.7 4.7 4.7 4.7 4.7 4.7 4.7 4.7 4.7 4.7 ...
 $ Installs              : Ord.factor w/ 21 levels "0"<"0+"<"1+"<..: 14 14 14 14 14 14 14 14 14 14 ...
 $ Translated_Review     : chr  "Good luck getting pictures free. Everyday supposed collect diamonds color pictures free. I rarely get diamonds "| __truncated__ "This terrible - there's way get back color chooser brush/paint screen. The way I get back go picture I'm workin"| __truncated__ "I love I'd like see pictures every beautiful thing world. Great job. Should maybe play games watch videos recei"| __truncated__ "I really liked first got really buggy. The worst part I took lot time shading went save changed colors blue! Ve"| __truncated__ ...
 $ Sentiment             : Ord.factor w/ 3 levels "Negative"<"Neutral"<..: 3 1 3 1 3 1 3 3 3 1 ...
 $ Sentiment_Polarity    : num  0.15 -0.25 0.5375 -0.1542 0.0833 ...
 $ Sentiment_Subjectivity: num  0.721 0.375 0.613 0.568 0.567 ...
 $ Reviews_t             : num  4.31 4.31 4.31 4.31 4.31 ...

After arranging the columns and row, check the structure of the final data. We see that our data is in the right format, ordered properly, tidy and does not have any missing values or outliers and is ready for analysis all irrelevant variables have been dropped. The data is now ready for analysis to understand how Apps are doing in different categories, type(Free/Paid), different price ranges by analysing rating, sentiments, sentiment polarity, number of installs, Content rating etc.

Below we present some basic analysis that could be helpful in understanding the data:

Finding the top 5 rated categories in the app store

ratings <- apps %>% group_by(Category) %>% summarise("Avg rating"=round(mean(Rating),2)) 
ratings<-ratings%>% arrange (desc(`Avg rating`))
head(ratings,5)

Finding the top 5 rated apps in the app store

apprating<-apps %>% group_by(App)  %>% summarise(rating=mean(Rating))
apprating<- apprating %>% arrange(desc(rating))
head(apprating,5)

Sentiment Polarity versus Last updated year

boxplot(Sentiment_Polarity~Year,data = apps,
        main="Sentiment spread based on last updated Year",
        xlab = "Year last updated",
        ylab = "Average Sentiment",
        col=(c("lightblue") ))

Sentiment Polarity versus Price

boxplot(Sentiment_Polarity~Price, data = apps,
        main="Sentiment spread based on Price",
        xlab = "Price of App",
        ylab = "Average Sentiment",
        col=(c("gold")))

LS0tDQp0aXRsZTogIk1BVEgyMzQ5IFNlbWVzdGVyIDEsIDIwMTkiDQphdXRob3I6ICJZaW5hbiBaaGFuZywgVGFubWF5IE5hZ2ksIFNhYnJpbmEgTG9oIg0Kc3VidGl0bGU6IEFzc2lnbm1lbnQgMw0Kb3V0cHV0Og0KICBodG1sX25vdGVib29rOiBkZWZhdWx0DQotLS0NCg0KIyMgUmVxdWlyZWQgcGFja2FnZXMgDQoNCg0KYGBge3Igc2V0dXB9DQoNCmxpYnJhcnkocmVhZHIpDQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeSh0aWR5cikNCmxpYnJhcnkobHVicmlkYXRlKQ0KbGlicmFyeShzdHJpbmdyKQ0KbGlicmFyeShrbml0cikNCmxpYnJhcnkoZm9yZWNhc3QpDQpsaWJyYXJ5KGNhcikNCg0KYGBgDQoNCg0KIyBFeGVjdXRpdmUgU3VtbWFyeSANClRoZSBhaW0gb2YgdGhpcyBhc3NpZ25tZW50IGlzIHRvIHByZS1wcm9jZXNzIHVzZXIgcmV2aWV3IGRhdGEgb2YgR29vZ2xlIFBsYXkgYXBwbGljYXRpb25zIHRvIHByZXBhcmUgaXQgZm9yIGFuYWx5c2lzIG9uIHRoZSBhcHBsaWNhdGlvbuKAmXMgZWZmZWN0aXZlbmVzcyBlLmcuIENvcnJlbGF0aW9ucyBiZXR3ZWVuIFByaWNlLCByZXZpZXdzIGFuZCBzZW50aW1lbnRzIGFuZCB0byByYW5rIHRoZSBhcHBzIGluIGRpZmZlcmVudCBjYXRlZ29yaWVzLg0KDQpUaGUgMiBkYXRhIHNldHMgdXNlZCwgZ29vZ2xlcGxheXN0b3JlLmNzdiBhbmQgZ29vZ2xlcGxheXN0b3JlX3VzZXJfcmV2aWV3cy5jc3YsIHdlcmUgaW1wb3J0ZWQgYW5kIG1lcmdlZCBieSBhIGNvbW1vbiB2YXJpYWJsZSAoQXBwcykuIEZvciBhIGJldHRlciB1bmRlcnN0YW5kaW5nIG9mIHRoZSBkYXRhIHNldCwgYW4gYW5hbHlzaXMgb2YgdGhlIHN0cnVjdHVyZSBhbmQgdmFyaWFibGUgY2xhc3MgdHlwZSB3YXMgY29uZHVjdGVkLiBJcnJlbGV2YW50IHZhcmlhYmxlcyB3ZXJlIGRyb3BwZWQgdG8gc2ltcGxpZnkgdGhlIHByb2Nlc3MuIFRoZW4sIGRhdGEgdHlwZSBjb252ZXJzaW9ucyB3ZXJlIGNhcnJpZWQgb3V0IG9uIHNvbWUgdmFyaWFibGVzLCBzb21lIHZhcmlhYmxlcyB3ZXJlIGZhY3RvcmVkIHdoaWxlIHNvbWUgb3JkZXJlZC4NCg0KVGhlIGRhdGEgd2FzIGFscmVhZHkgaW4gYSB0aWR5IGZvcm1hdCwgaGVuY2Ugbm8gcmVzaGFwaW5nIHdhcyBuZWVkZWQuIDMgbmV3IGNvbHVtbnMgd2VyZSBjcmVhdGVkIHRocm91Z2ggdGhlIG11dGF0ZSBmdW5jdGlvbiB3aGljaCBzZXBhcmF0ZWQgdGhlIGRhdGUgb24gd2hpY2ggdGhlIGFwcGxpY2F0aW9uIHdhcyBsYXN0IHVwZGF0ZWQgaW50byBkYXksIG1vbnRoIGFuZCB5ZWFyIGZvciBlYXNpZXIgY29tcGFyaXNvbiBvZiBkYXRhIGJ5IG1vbnRoIG9yIHllYXIuDQoNClNldmVyYWwgbWlzc2luZyB2YWx1ZXMgd2VyZSBpZGVudGlmaWVkIGluIHRoZSBkYXRhIHNldC4gUm93cyB3aXRoIG1pc3NpbmcgdmFsdWVzIGluIFJhdGluZ3MsIFNlbnRpbWVudCBQb2xhcml0eSBhbmQgVHJhbnNsYXRlZCBSZXZpZXcgd2VyZSByZW1vdmVkLCB3aGVyZWFzIG1pc3NpbmcgdmFsdWVzIGluIFNpemVzIHdlcmUgcmVwbGFjZWQgYnkgdGhlIG1lYW4gc2l6ZSBvZiB0aGVpciBpbmRpdmlkdWFsIGNhdGVnb3J5IHVzaW5nIGltcHV0YXRpb24uIFRoZSBtaXNzaW5nIHZhbHVlcyBpbiBQcmljZSB3ZXJlIGR1ZSB0byB0aGUgYXBwbGljYXRpb25zIGJlaW5nIGZyZWUsIHNvIHRoZXkgd2VyZSByZXBsYWNlZCB3aXRoIGEgMC4NCg0KTGFzdGx5LCByZW1vdmFsIG9mIG91dGxpZXJzIGFuZCB0cmFuc2Zvcm1hdGlvbiB3ZXJlIHBlcmZvcm1lZCB0byB0cnkgdG8gcmVkdWNlIHRoZSBlZmZlY3RzIG9mIG91dGxpZXJzIG9uIHNrZXdpbmcgdGhlIHJlc3VsdHMuIFRoZSBjYXBwaW5nIG1ldGhvZCB3YXMgdXNlZCB0byBoYW5kbGUgdGhlIG91dGxpZXJzLiAgV2UgY2FwcGVkIG91dGxpZXIgdmFsdWVzIHdpdGggdGhlIGNsb3Nlc3QgNXRoIHBlcmNlbnRpbGUuICBPbiB0aGUgaGVhdmlseSByaWdodCBza2V3ZWQsIGRhdGEsIGxvZzEwIHRyYW5zZm9ybWF0aW9uIHdhcyBhcHBsaWVkIHRvIHRoZSB2YXJpYWJsZSB0byByZWR1Y2UgdGhlIHNrZXduZXNzIGJlZm9yZSBjYXBwaW5nLiAgVGhpcyByZXN1bHRlZCBpbiBhIG1vcmUgbm9ybWFsIGRpc3RyaWJ1dGlvbiBhbmQgZWxpbWluYXRlZCBtdWNoIG9mIHRoZSBwZXJjZWl2ZWQgb3V0bGllcnMuDQoNCg0KIyBEYXRhIA0KRGF0YSBvYnRhaW5lZCBmcm9tOiBodHRwczovL3d3dy5rYWdnbGUuY29tL2xhdmExOC9nb29nbGUtcGxheS1zdG9yZS1hcHBzDQpEYXRhIHdhcyBzY3JhcGVkIGZyb20gR29vZ2xlIFBsYXkgQXBwIFN0b3JlIG9uIG92ZXIgMTBrIGFwcHMgYXMgd2VsbCBhcyB0aGVpciByZXZpZXdzLg0KDQpEYXRhc2V0cyB1c2VkOiBnb29nbGVwbGF5c3RvcmUuY3N2IGFuZCBnb29nbGVwbGF5c3RvcmVfdXNlcl9yZXZpZXdzLmNzdg0KQXBwIGluZm9ybWF0aW9uIGlzIHN0b3JlZCBpbiB0aGUgZ29vZ2xlcGxheXN0b3JlLmNzdiB3aGlsZSByZXZpZXdzIGluZm9ybWF0aW9uIGlzIHN0b3JlZCBpbiB0aGUgZ29vZ2xlcGxheXN0b3JlX3VzZXJfcmV2aWV3cy5jc3YuIFZhcmlhYmxlIGRlc2NyaXB0aW9ucyBmb3IgZWFjaCBkYXRhc2V0IGFyZSBzaG93biBiZWxvdy4NCg0KQm90aCBkYXRhc2V0cyB3ZXJlIGltcG9ydGVkIGFuZCBtZXJnZWQgKGxlZnRfam9pbikgYnkgYSBjb21tb24gdmFyaWFibGUsIOKAmEFwcOKAmSwgZm9yIGVhc2llciBhbmFseXNpcy4gVGhlIGxlZnRfam9pbiBpcyBhcHByb3ByaWF0ZSBhcyBpdCBtYXRjaGVzIHJvd3MgZnJvbSBnb29nbGVwbGF5c3RvcmVfdXNlcl9yZXZpZXdzIHRvIGdvb2dsZXBsYXlzdG9yZSwgc28gdGhhdCBlYWNoIHJldmlldyBpcyBtYXRjaGVkIHRvIHRoZSBhcHByb3ByaWF0ZSBhcHAuDQoNClRvIHNpbXBsaWZ5IHRoZSBwcm9jZXNzLCB2YXJpYWJsZXMg4oCYQW5kcm9pZCBWZXLigJkgYW5kIOKAmEN1cnJlbnQgVmVy4oCZIHdlcmUgcmVtb3ZlZCBhcyB2ZXJzaW9uIGhpc3Rvcnkgd2lsbCBub3QgYmUgdXNlZnVsIGluIHRoZSBhbmFseXNpcy4gDQoNCiMjIExvYWRpbmcgdGhlIGRhdGEgaW50byBSLg0KYGBge3IgZWNobz1UUlVFfQ0KZ29vZ2xlcGxheXN0b3JlIDwtIHJlYWRfY3N2KCJnb29nbGVwbGF5c3RvcmUuY3N2IikNCmdvb2dsZXBsYXlzdG9yZV91c2VyX3Jldmlld3MgPC0gcmVhZF9jc3YoImdvb2dsZXBsYXlzdG9yZV91c2VyX3Jldmlld3MuY3N2IikNCnBsYXlzdG9yZWRlc2NyaXB0aW9uIDwtIHJlYWRfY3N2KCJwbGF5c3RvcmVkZXNjcmlwdGlvbi5jc3YiKQ0KVXNlclJldmlld3NkZXNjcmlwdGlvbiA8LSByZWFkX2NzdigiVXNlclJldmlld3NkZXNjcmlwdGlvbi5jc3YiKQ0KYGBgDQoNCiMjIFZhcmlhYmxlcyBkZXNjcmlwdGlvbiBpbiBnb29nbGVwbGF5c3RvcmU6DQpgYGB7ciwgZWNobz1GQUxTRX0NCmthYmxlKHBsYXlzdG9yZWRlc2NyaXB0aW9uKQ0KYGBgDQoNCiMjIFZhcmlhYmxlcyBkZXNjcmlwdGlvbiBpbiBnb29nbGVwbGF5c3RvcmVfdXNlcl9yZXZpZXdzOg0KYGBge3IsIGVjaG89RkFMU0V9DQprYWJsZShVc2VyUmV2aWV3c2Rlc2NyaXB0aW9uKQ0KYGBgDQoNCiMjIEpvaW5pbmcgdGhlIERhdGEgU2V0cw0KDQpUaGUgY29tbW9uIHZhcmlhYmxlIG9uIHRoZSBkYXRhIHNldCBpcyBhcHBzLCB3aGVyZSB0aGUgbmFtZSBvZiB0aGUgYXBwcyBhcmUgc3RvcmVkLiBUbyBjcmVhdGUgbWVhbmluZ2Z1bCBhbmFseXNpcywgd2Ugd2lsbCBqb2luIHRoZSB0d28gZGF0YSBzZXRzIHRvZ2V0aGVyIHdpdGggb24gdGhlIGFwcCBuYW1lcy4gICBOZXcgdGFibGUgd2Ugd2lsbCBiZSB3b3JraW5nIHdpdGggd2lsbCBiZSBjYWxsZWQgYXBwcy4NCg0KYGBge3J9DQphcHBzIDwtIGdvb2dsZXBsYXlzdG9yZSAlPiUgbGVmdF9qb2luKGdvb2dsZXBsYXlzdG9yZV91c2VyX3Jldmlld3MsIGJ5ID0gIkFwcCIpDQpgYGANCg0KIyMgVW5kZXJzdGFuZCANCg0KQ2hlY2tpbmcgdGhlIHN0cnVjdHVyZSBvZiB0aGUgZGF0YQ0KDQpgYGB7cn0NCnN0cihhcHBzKQ0KYGBgDQoNCg0KIyMgUmVtb3ZpbmcgdW51c2VkIHZhcmlhYmxlcyANCg0KVmVyc2lvbiBoaXN0b3J5IGlzIG5vdCB1c2VmdWwgZm9yIHVzLiBUaGVyZWZvcmUgd2UgZGVjaWRlZCB0byByZW1vdmUgdGhlbSBhcyB2YXJpYWJsZXMuDQoNCiogQW5kcm9pZCBWZXIgIA0KKiBDdXJyZW50IFZlciAgDQoNCmBgYHtyfQ0KYXBwczwtYXBwcyAlPiUgc2VsZWN0KC1jKGBBbmRyb2lkIFZlcmAsYEN1cnJlbnQgVmVyYCkpDQpjb2xuYW1lcyhhcHBzKQ0KYGBgDQoNCiMjIENvbnZlcnRpbmcgdGhlIHZhcmlhYmxlIGludG8gb3JkZXJlZCBhbmQgdW5vcmRlcmVkIGZhY3RvcnMNCg0KKiBJbnN0YWxscyAgDQoqIFR5cGUgICAgDQoqIENvbnRlbnQgUmF0aW5ncyAgIA0KKiBTZW50aW1lbnQgICANCiogQ2F0ZWdvcnkgIA0KDQpgYGB7cn0NCmFwcHMgPC0gYXBwcyAlPiUgbXV0YXRlKA0KICBJbnN0YWxscyA9IGZhY3RvcihhcHBzJEluc3RhbGxzLCANCiAgICAgICAgICAgICAgICAgICAgbGV2ZWxzID0gYyggIjAiLCIwKyIsIjErIiwiNSsiLCIxMCsiLCI1MCsiLCIxMDArIiwiNTAwKyIsIjEsMDAwKyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICI1LDAwMCsiLCIxMCwwMDArIiwgICI1MCwwMDArIiwgIjEwMCwwMDArIiwgICI1MDAsMDAwKyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICIxLDAwMCwwMDArIiwiNSwwMDAsMDAwKyIgLCAgIjEwLDAwMCwwMDArIiAsICAiNTAsMDAwLDAwMCsiLCAiMTAwLDAwMCwwMDArIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjUwMCwwMDAsMDAwKyIsIjEsMDAwLDAwMCwwMDArIikgLA0KICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCAiMCIsIjArIiwiMSsiLCI1KyIsIjEwKyIsIjUwKyIsIjEwMCsiLCI1MDArIiwiMSwwMDArIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjUsMDAwKyIsIjEwLDAwMCsiLCAgIjUwLDAwMCsiLCAiMTAwLDAwMCsiLCAgIjUwMCwwMDArIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIjEsMDAwLDAwMCsiLCI1LDAwMCwwMDArIiAsICAiMTAsMDAwLDAwMCsiICwgICI1MCwwMDAsMDAwKyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICIxMDAsMDAwLDAwMCsiLCAiNTAwLDAwMCwwMDArIiwiMSwwMDAsMDAwLDAwMCsiKSwNCiAgICAgICAgICAgICAgICAgICAgb3JkZXJlZD1UKSwNCiAgVHlwZSA9IGZhY3RvcihhcHBzJFR5cGUsIA0KICAgICAgICAgICAgICAgIGxldmVscyA9IGMoIkZyZWUiLCAiUGFpZCIpLA0KICAgICAgICAgICAgICAgIGxhYmVscyA9IGMoIkZyZWUiLCAiUGFpZCIpKSwNCiAgYENvbnRlbnQgUmF0aW5nYD0gZmFjdG9yKGFwcHMkYENvbnRlbnQgUmF0aW5nYCwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGMoIkV2ZXJ5b25lIiwgIkV2ZXJ5b25lIDEwKyIsICAiVGVlbiIsICJNYXR1cmUgMTcrIiwgIkFkdWx0cyBvbmx5IDE4KyIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFiZWxzID0gYygiRXZlcnlvbmUiLCAiRXZlcnlvbmUgMTArIiwgICJUZWVuIiwgIk1hdHVyZSAxNysiLCAiQWR1bHRzIG9ubHkgMTgrIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICBvcmRlcmVkID0gVCApLA0KICBTZW50aW1lbnQgPSBmYWN0b3IoYXBwcyRTZW50aW1lbnQsIA0KICAgICAgICAgICAgICAgICAgICAgIGxldmVscyA9IGMoIk5lZ2F0aXZlIiwgIk5ldXRyYWwiLCAiUG9zaXRpdmUiKSwNCiAgICAgICAgICAgICAgICAgICAgICBsYWJlbHMgPSBjKCJOZWdhdGl2ZSIsICJOZXV0cmFsIiwgIlBvc2l0aXZlIiksDQogICAgICAgICAgICAgICAgICAgICAgb3JkZXJlZCA9IFQpLA0KICBDYXRlZ29yeSA9IGZhY3RvcihhcHBzJENhdGVnb3J5KQ0KICApDQpzdHIoYXBwc1ssYygiSW5zdGFsbHMiLCAiVHlwZSIsICJDb250ZW50IFJhdGluZyIsICJTZW50aW1lbnQiLCAiQ2F0ZWdvcnkiKV0pIA0KDQpgYGANCg0KIyMgRGF0ZSBjb252ZXJzaW9uDQoNCkNvbnZlcnRpbmcgJ0xhc3QgVXBkYXRlZCcgaW50byBkYXRlIGZvcm1hdCBpbiBhIG5ldyBjb2x1bW4uIFRoZW4gZHJvcHBpbmcgdGhlIGNvbHVtbiAnTGFzdCBVcGRhdGVkJy4gVGhpcyBpcyB0byBhdm9pZCBlcnJvcnMgaWYgcnVuIGNvZGUgdHdpY2UsIGFzIGl0IHdvdWxkIGNvbnZlcnQgZGF0ZSB2YXJpYWJsZXMgdG8gTkEuICBUaGlzIGlzIHRvIGVuc3VyZSBpbnRlZ3JpdHkuDQoNCmBgYHtyfQ0KYXBwcyA8LSBhcHBzICU+JSBtdXRhdGUoVXBkYXRlZCA9IG1keShhcHBzJGBMYXN0IFVwZGF0ZWRgKSkNCmFwcHMgPC0gYXBwcyAlPiUgc2VsZWN0KC1gTGFzdCBVcGRhdGVkYCkNCg0Kc3RyKGFwcHMkVXBkYXRlZCkNCmBgYA0KDQojIyBDaGFuZ2luZyBQcmljZSBmcm9tIGNoYXJhY3RlciB0byBudW1lcmljDQoNCmBgYHtyfQ0KYXBwcyRQcmljZSA8LSBzdWJzdHIoYXBwcyRQcmljZSwyLG5jaGFyKGFwcHMkUHJpY2UpKSAlPiUgYXMubnVtZXJpYygpDQoNCnN0cihhcHBzJFByaWNlKQ0KYGBgDQoNCldlIG5vdGUgdGhhdCBhcy5udW1lcmljIGNoYW5nZXMgMCB0byBOQSBpbiB0aGUgY29udmVyc2lvbiBwcm9jZXNzLiAgIFdlIHdpbGwgaW1wdXRlIGJhY2sgdGhlIDBzIGluIHRoZSBzY2FuIHByb2Nlc3MuDQoNCiMjIENoYW5naW5nIGFwcGxpY2F0aW9uIHNpemUgdmFyaWFibGUgdG8gbnVtZXJpYyANCkFwcGxpY2F0aW9uIHNpemVzIGFyZSBlaXRoZXIgcmVjb3JkZWQgaW4gbWVnYWJ5dGVzLCBraWxvYnl0ZXMsIG9yIGFyZSByZWNvcmRlZCBhcyAidmFyaWVzIHdpdGggZGV2aWNlIi4gICBXZSB3YW50IHRvIGNvbnZlcnQgdGhpcyB0byBudW1lcmljIGZvciBiZXR0ZXIgYW5hbHlzaXMgaGVuY2UgYSBjb21tb24gdW5pdCBvZiBtZWFzdXJlbWVudCBzaG91bGQgYmUgdXNlZC4gICBXZSBkZWNpZGVkIHRvIHVzZSBtZWdhYnl0ZXMgZm9yIHRoaXMuICAgDQoNCldlIGZpcnN0IGV4dHJhY3QgdGhlIG51bWVyaWMgcGFydCBmcm9tIHRoZSBzdHJpbmcuICBUaGVuIGV4dHJhY3QgdGhlIOKAmE3igJksIG9yIOKAmGvigJkuICBpZiBpdCBpcyBhbnl0aGluZyBlbHNlLCB3ZSByZWNvZ25pc2UgaXQgYXMgdGhlIOKAmHZhcmllcyB3aXRoIGRldmljZeKAmSB2YWx1ZS4gIFRoaXMgd2UgYXJlIGdvaW5nIHRvIGFsbG93IHRvIGJlIE5BIGFzIHdlIGRvIG5vdCBoYXZlIGVub3VnaCBpbmZvcm1hdGlvbi4gICANCkZpbmFsbHkgd2UgYXJlIHB1dHRpbmcgaXQgYWxsIHRvZ2V0aGVyLiAgSWYgaXQgaXMgaW4ga2lsb2J5dGVzLCB3ZSBhcmUgbXVsdGlwbHlpbmcgYnkgMC4wMDEgdG8gYWRqdXN0IHRoZSB2YWx1ZSB0byBiZSBpbiBtZWdhYnl0ZXMuICAgTkFzIGFyZSByZWNvcmRlZCBmb3Ig4oCYdmFyaWVzIHdpdGggZGV2aWNl4oCZIHZhbHVlLiAgVGhpcyB3ZSB3aWxsIGltcHV0ZSBpbiB0aGUgbGF0ZXIgc2VjdGlvbiB3aXRoIHRoZSBtZWFuIHNpemUgb2YgdGhlIHJlc3BlY3RpdmUgY2F0ZWdvcnkuDQoNCg0KYGBge3J9DQp1bml0X3NpemU8LXN0cl9leHRyYWN0KGFwcHMkU2l6ZSwiW2FBLXpaXSIpIA0KdmFsdWVfc2l6ZSA8LSBzdWJzdHIoYXBwcyRTaXplLHN0YXJ0ID0gMSxzdG9wPShuY2hhcihhcHBzJFNpemUpLTEpKSAlPiUgYXMubnVtZXJpYygpDQoNCmNvbnZlcnNpb24gPC1mdW5jdGlvbih4LHkpIHtpZmVsc2UoeD09Ik0iLHksaWZlbHNlKHg9PSJrIix5KjAuMDAxLE5BKSl9DQpzaXplPC1jb252ZXJzaW9uKHVuaXRfc2l6ZSx2YWx1ZV9zaXplKQ0KYXBwczwtYXBwcyAlPiUgbXV0YXRlKFNpemU9c2l6ZSkNCg0KY2xhc3MoYXBwcyRTaXplKQ0Kc3VtbWFyeShhcHBzJFNpemUpDQoNCmBgYA0KDQojIyBDaGVjayBvbiBkYXRhIHN0cnVjdHVyZSB0aGF0IGFsbCB2YXJpYWJsZXMgYXJlIGluIHRoZSByaWdodCBjbGFzcy4NCg0KYGBge3J9DQpzdHIoYXBwcykNCmBgYA0KDQoqIEFwcHMgYXJlIHRoZSBuYW1lIG9mIGVhY2ggYXBwLiAgU28gb2theSB0byBrZWVwIGFzIGNoYXJhY3RlciBkYXRhLiAgDQoqIEdlbnJlcyBhcmUgb2theSB0byBrZWVwIGFzIGNoYXJhY3RlciBkYXRhLiAgDQoqIFNlbnRpbWVudF9Qb2xhcml0eSBhbmQgU2VudGltZW50X1N1YmplY3Rpdml0eSBhcmUgb3V0IGFuYWx5c2lzIHZhcmlhYmxlcyBhbmQgdGhleSBzaG91bGQgYmUgaW4gbnVtZXJpY2FsLiAgIA0KKiBBbGwgb3RoZXIgdmFyaWFibGUgd2UgaGF2ZSBjaGFuZ2VkIGludG8gdGhlIGNvcnJlY3QgY2xhc3MuICANCg0KDQojCVRpZHkgJiBNYW5pcHVsYXRlIERhdGEgSSANCg0KVGhlIGRhdGEgaXMgYWxyZWFkeSBpbiBhIHRpZHkgZm9ybWF0IHNpbmNlOiAgDQoxLiBBbGwgdmFyaWFibGVzIGhhdmUgYSBjb2x1bW4uIC0gRWFjaCBjb2x1bW4gcmVsYXRlcyB0byBhbiBhdHRyaWJ1dGUgb2YgdGhlIGFwcC4gICAgDQoyLiBBbGwgb2JzZXJ2YXRpb25zIGhhdmUgcm93IC0gaWUuIGVhY2ggcm93IHJlbGF0ZXMgdG8gYW4gYXBwIGFuZCBhbiBpbmRpdmlkdWFsIHJldmlldy4gIA0KMy4gRWFjaCB2YWx1ZSBpcyBpbiBhIGNlbGwuICAgDQoNCmBgYHtyfQ0KaGVhZChhcHBzLCA2KQ0KYGBgDQoNCiMJVGlkeSAmIE1hbmlwdWxhdGUgRGF0YSBJSSANCg0KSWYgYW5hbHlzaXMgb24gd2hlbiBpcyB0aGUgYXBwIHVwZGF0ZWQgaGF2ZSBhbiBlZmZlY3Qgb24gdGhlIHNlbnRpbWVudCBvZiB0aGUgcmV2aWV3cywgaXQgd2lsbCBiZSB1c2VmdWwgdG8gaGF2ZSB0aGUgWWVhciwgTW9udGgsIGFuZCBEYXkgb2Ygd2hlbiBsYXN0IHJldmlld2VkIGluIHNlcGFyYXRlIGNvbHVtbnMgZm9yIGFuYWx5c2lzLiAgDQoNCkNyZWF0aW5nIHRoZSBZZWFyLCBNb250aCBhbmQgRGF5IGNvbHVtbiBmb3IgdXBkYXRlZCB2YWx1ZXMuDQoNCmBgYHtyfQ0KYXBwcyA8LSBhcHBzICU+JSBtdXRhdGUoRGF5ID0gZGF5KGFwcHMkVXBkYXRlZCksIA0KICAgICAgICAgICAgICAgICAgTW9udGggPSBtb250aChhcHBzJFVwZGF0ZWQpLCANCiAgICAgICAgICAgICAgICAgIFllYXIgPSB5ZWFyKGFwcHMkVXBkYXRlZCkpDQpzdHIoYXBwc1ssYygiRGF5IiwgIk1vbnRoIiwgIlllYXIiKV0pDQpgYGANCg0KDQojCVNjYW4gSSANCg0KQ2hlY2luZyBmb3IgbWlzc2luZyB2YWx1ZXMoTkEsIEluZmluaXRlIGFuZCBOYU4pLg0KDQpDaGVja2luZyB0aGUgdG90YWwgbnVtYmVyIG9mIG1pc3NpbmcgYW5kIHNwZWNpYWwgdmFsdWVzIGFuZCBkaXNwbGF5aW5nIHRoZW0gdG8gaGFuZGxlIG9uZSBieSBvbmUuDQpgYGB7cn0NCm4gPC0gY29sU3Vtcyhpcy5uYShhcHBzKSkgJT4lIGFzLmRhdGEuZnJhbWUoKQ0KbmFtZXMobikgPC0gIk5BIg0KDQppIDwtIHNhcHBseShhcHBzLCBpcy5pbmZpbml0ZSkgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgDQpjb2xTdW1zKCkNCg0KbmFuIDwtIHNhcHBseShhcHBzLCBpcy5uYW4pICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIA0KY29sU3VtcygpDQoNCnggPC0gbiAlPiUgbXV0YXRlKEluZmluaXRlID0gaSwgTmFuID0gbmFuKSANCnJvdy5uYW1lcyh4KSA8LSBjb2xuYW1lcyhhcHBzKQ0KeA0KDQpgYGANCg0KDQpDaGVja2luZyB3aHkgcmV2aWV3cyBhbmQgaW5zdGFsbHMgaGF2ZSBvbmx5IG9uZSBOQQ0KYGBge3J9DQphcHBzW3doaWNoKGlzLm5hKGFwcHMkUmV2aWV3cykpLF0NCmBgYA0KTG9va3MgdG8gaGF2ZSB0aGUgdmFsdWVzIGluIHRoZSB3cm9uZyBjb2x1bW5zLiAgSXQgaXMgbGlrZWx5IHRvIGJlIGFuIGVycm9yIGZyb20gdGhlIHNjcmFwaW5nLiAgU2luY2Ugd2UgaGF2ZSBhIGxhcmdlIGRhdGEgc2FtcGxlLCB3ZSB3aWxsIGRlYWwgd2l0aCBpdCBieSBkZWxldGluZy4gDQpgYGB7cn0NCmFwcHMgPC0gYXBwc1std2hpY2goaXMubmEoYXBwcyRSZXZpZXdzKSksXQ0Kc3VtKGlzLm5hKGFwcHMkUmV2aWV3cykpDQpgYGANCg0KRm9yIG1pc3NpbmcgdmFsdWVzIHRoYXQgYXJlIGluIHJhdGluZ3MsIHdlIHdpbGwgZGVhbCB3aXRoIHRoZW0gYnkgcmVtb3ZpbmcgYWxsIHJvd3Mgd2l0aCBtaXNzaW5nIHZhbHVlcy4gIFNpbmNlIG91ciBhbmFseXNpcyBhcmUgb24gcmF0aW5nIHNlbnRpbWVudHMuICBUaGUgYXBwcyB0aGF0IGRvZXMgbm90IGhhdmUgYW55IHJhdGluZ3MgaXMgbm90IHVzZWZ1bCB0byBiZSBpbmNsdWRlZC4NCg0KYGBge3J9DQphcHBzIDwtIGFwcHNbLXdoaWNoKGlzLm5hKGFwcHMkUmF0aW5nKSksXQ0Kc3VtKGlzLm5hKGFwcHMkUmF0aW5nKSkNCmBgYA0KDQpXaGVuIGNvbnZlcnRpbmcgUHJpY2UgaW50byBudW1lcmljLCAwIHdhcyBjaGFuZ2VkIHRvIE5BLiAgQXMsIDAgaXMgc3RpbGwgYSB2YWxpZCBwcmljZSwgYW5kIGl0IGRvZXMgYWRkIHZhbHVlIHRvIHRoZSBpbmZvcm1hdGlvbiwgd2UgYXJlIEltcHV0aW5nIE5BIGluIHByaWNlIHdpdGggMC4NCmBgYHtyfQ0KDQphcHBzJFByaWNlW3doaWNoKGlzLm5hKGFwcHMkUHJpY2UpKV0gPC0gMA0Kc3VtKGlzLm5hKGFwcHMkUHJpY2UpKQ0KDQpgYGANCg0KRm9yIHRoZSBTaXplcyB2YXJpYWJsZSwgdGhlcmUgd2FzIGEgdmFsdWUgY2FsbGVkICJWYXJpZXMgd2l0aCBkZXZpY2UiLiAgV2hlbiBjaGFuZ2luZyBpbnRvIG51bWVyaWMgZm9ybWF0LCB0aGlzIGhhdmUgYmVjb21lIE5BLiAgDQoNCldlIHdpbGwgaW1wdXRlIHRoZXNlIE5BIHdpdGggdGhlIGF2ZXJhZ2Ugc2l6ZSBvZiBhcHBzIG9mIHRoZWlyIGluZGl2aWR1YWwgY2F0ZWdvcnkuDQoNCmBgYHtyfQ0KYXBwcyA8LSBhcHBzICU+JSANCiAgZ3JvdXBfYnkoQ2F0ZWdvcnkpICU+JSANCiAgbXV0YXRlKFNpemUgPSBpZmVsc2UoaXMubmEoU2l6ZSksIA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgbWVhbihTaXplLG5hLnJtID0gVCksDQogICAgICAgICAgICAgICAgICAgICAgICAgICBTaXplKSkgJT4lIHVuZ3JvdXAoKQ0KDQojIENoZWNraW5nIGlmIFNpemUgaXMgaW1wdXRlZCANCnN1bShpcy5uYShhcHBzJFNpemUpKQ0KDQojIERpc3BsYXlpbmcgc2l6ZXMgYnkgY2F0ZWdvcmllcyANCmFwcHMgJT4lIA0KICBncm91cF9ieShDYXRlZ29yeSkgJT4lIHN1bW1hcmlzZShtZWFuPXJvdW5kKG1lYW4oU2l6ZSksMikpDQoNCmBgYA0KDQpUcmFuc2xhdGVkIFJldmlldyBpcyB3aGVyZSB0aGUgcmV2aWV3cyBhcmUgY29sbGVjdGVkLiAgQW4gYXBwIHVzZXIgY2FuIGxlYXZlIG9yIG5vdCBsZWF2ZSBhIHdyaXR0ZW4gcmV2aWV3IGFmdGVyIGdpdmluZyBhIHJhdGluZy4gSWYgbm8gcmF0aW5nIGlzIGdpdmVuLCB0aGVuIGl0IGlzIHJlY29yZGVkIGFzIE5hTi4gU29tZSwgbm9uIHJlY29yZGVkIHJldmlld3MgYXJlIHJlY29yZGVkIGFzIE5BIGhlcmUuICBTaW5jZSB3ZSBhcmUgZ29pbmcgdG8gYW5hbHlzZSB0aGUgc2VudGltZW50cywgd2Ugd2lsbCBsb29rIGF0IG9ubHkgcmV2aWV3cyBhcmUgbGVmdC4gIFRoZXJlZm9yZSB3ZSB3aWxsIGRlYWwgd2l0aCBtaXNzaW5nIHZhbGx1ZXMgaW4gVHJhbnNsYXRlZF9SZXZpZXcgYnkgcmVtb3ZpbmcgdGhlbS4NCg0KYGBge3J9DQphcHBzIDwtIGFwcHNbLXdoaWNoKGlzLm5hKGFwcHMkVHJhbnNsYXRlZF9SZXZpZXcpKSxdDQpzdW0oaXMubmEoYXBwcyRUcmFuc2xhdGVkX1JldmlldykpDQpgYGANCg0KU2VudGltZW50IFBvbGFyaXR5IGlzIG9uZSBvZiB0aGUgdmFyaWFibGVzIGZvciBhbmFseXNpcy4gIFRoZXJlZm9yZSBpdCBpcyBnb29kIHRvIGhhdmUgYSBkYXRhIHNldCB3aXRoIG5vbmUgbWlzc2luZyB2YWx1ZXMgaGVyZSwgIFJlbW92aW5nIHJvd3Mgd2l0aCBtaXNzaW5nIHZhbHVlcyBpbiBTZW50aW1lbnQgUG9sYXJpdHkuICAgQWxzbyBub3QgdGhhdCwgaXMubmEgaGVyZSBhbHNvIGluY2x1ZGVzIE5hTnMgd2hpY2ggd2FzIGNyZWF0ZWQgZm9yIGFueSBhcHBzIHRoYXQgaGFkIGEgcmV2aWV3IGJ1dCBkaWRuJ3QgbGVhdmUgYW55IHRleHQuICBXZSB3aWxsIGJlIGV4Y2x1ZGluZyB0aGVzZSBmcm9tIG91ciBhbmFseXNpcy4NCg0KYGBge3J9DQphcHBzIDwtIGFwcHNbLXdoaWNoKGlzLm5hKGFwcHMkU2VudGltZW50X1BvbGFyaXR5KSksXQ0Kc3VtKGlzLm5hKGFwcHMkU2VudGltZW50X1BvbGFyaXR5KSkNCg0KYGBgDQoNCg0KIyBGaW5hbCBtaXNzaW5nIHZhbHVlIGNoZWNrOg0KDQojIyBDaGVja2luZyBmb3IgTkEsIEluZiBhbmQgTmFuLg0KDQpgYGB7cn0NCm4gPC0gY29sU3Vtcyhpcy5uYShhcHBzKSkgJT4lIGFzLmRhdGEuZnJhbWUoKQ0KbmFtZXMobikgPC0gIk5BIg0KDQppIDwtIHNhcHBseShhcHBzLCBpcy5pbmZpbml0ZSkgJT4lIGFzLmRhdGEuZnJhbWUoKSAlPiUgDQpjb2xTdW1zKCkNCg0KbmFuIDwtIHNhcHBseShhcHBzLCBpcy5uYW4pICU+JSBhcy5kYXRhLmZyYW1lKCkgJT4lIA0KY29sU3VtcygpDQoNCnggPC0gbiAlPiUgbXV0YXRlKEluZmluaXRlID0gaSwgTmFuID0gbmFuKSANCnJvdy5uYW1lcyh4KSA8LSBjb2xuYW1lcyhhcHBzKQ0KeA0KDQpgYGANCg0KV2UgaGF2ZSBkZWFsdCB3aXRoIGFsbCB0aGUgbWlzc2luZywgaW5maW5pdGUgYW5kIE5hbiB2YWx1ZXMuDQoNCg0KIwlTY2FuIElJDQoNCklkZW50aWZ5IG51bWVyaWMgZGF0YQ0KDQpgYGB7cn0NCmNoZWNrX251bWVyaWMgPC1zYXBwbHkoYXBwcywgaXMubnVtZXJpYykgJT4lIGFzLmRhdGEuZnJhbWUoKQ0KbmFtZXMoY2hlY2tfbnVtZXJpYykgPC0iTnVtZXJpYyINCmNoZWNrPC1jaGVja19udW1lcmljICU+JSBtdXRhdGUoVmFyaWFibGU9Y29sbmFtZXMoYXBwcyksTnVtZXJpYz1OdW1lcmljKSANCmNoZWNrPC1jaGVjayU+JSBmaWx0ZXIoTnVtZXJpYz09VCkgJT4lIHNlbGVjdChWYXJpYWJsZSxOdW1lcmljKQ0KY2hlY2sNCmBgYA0KDQpXZSBjYW4gc2VlIG51bWVyaWMgZGF0YSBhcmUgIA0KKiBSYXRpbmcgIA0KKiBSZXZpZXdzICANCiogU2l6ZSAgDQoqIFByaWNlICANCiogU2VudGltZW50X1BvbGFyaXR5ICANCiogU2VudGltZW50X1N1YmplY3Rpdml0eSAgDQoNCldlIHdpbGwgaWdub3JlIHRoZSB2YXJpYWJsZXMgRGF5LCBNb250aCBhbmQgWWVhciBhcyB0aGVzZSBoYXZlIGJlZW4gY3JlYXRlZCBieSB1cyBhbmQgaXQncyBub3QgcmVsZXZhbnQgdG8gY2hlY2sgZm9yIG91dGxpZXJzIA0KDQpDaGVja2luZyBmb3Igb3V0bGllcnMgaW4gdGhlbToNCg0KYGBge3J9DQpwYXIobWZyb3c9YygyLDMpKQ0KQm94cGxvdChhcHBzJFJhdGluZywgbWFpbj0iUmF0aW5nIikNCkJveHBsb3QoYXBwcyRSZXZpZXdzLCBtYWluPSJSZXZpZXdzIikNCkJveHBsb3QoYXBwcyRTaXplLCBtYWluPSJTaXplIikNCkJveHBsb3QoYXBwcyRTZW50aW1lbnRfUG9sYXJpdHksIG1haW49ICJTZW50aW1lbnQgUG9sYXJpdHkiKQ0KQm94cGxvdChhcHBzJFNlbnRpbWVudF9TdWJqZWN0aXZpdHksIG1haW4gPSAiU2VudGltZW50IFN1YmplY3Rpdml0eSIpDQpgYGANCg0KDQpGb3IgcHJpY2UsIHdlIGdyb3VwIHRoZSBkYXRhIGJ5IFR5cGUgb2YgYXBwIChGcmVlL1BhaWQpIGFuZCBjaGVjayBmb3Igb3V0bGllcnMgYXMgdGhlIGRhdGEgd291bGQgb3RoZXJ3aXNlIGJlIGhlYXZpbHkgc2tld2VkIGR1ZSB0byBsYXJnZSBudW1iZXIgb2YgZnJlZSBhcHBzIA0KYGBge3J9DQoNCkJveHBsb3QoYXBwcyRQcmljZX5hcHBzJFR5cGUsIG1haW4gPSAiUHJpY2UgZ3JvdXBlZCBieSB0eXBlIG9mIGFwcCIpDQoNCmBgYA0KDQpSZXZpZXdzIGxvb2tzIHRvIGJlIHNldmVybHkgcmlnaHQgc2tld2VkLiAgDQoNCmBgYHtyfQ0KaGlzdChhcHBzJFJldmlld3MsIG1haW49IlJldmlld3MiKQ0KDQpgYGANCg0KSXQgd2lsbCBtYWtlIGJldHRlciBzZW5zZSBpZiB3ZSBkbyBhIHRyYW5zZm9ybWF0aW9uIG9mIHRoZSBkYXRhIGJlZm9yZSBjYXBwaW5nIHRoZSBvdXRsaWVycyBpbiBjYXNlIG9mIGRvaW5nIGxvb3NpbmcgdG9vIG11Y2ggaW5mb3JtYXRpb24uDQoNCg0KIyMgQ2FwcGluZyB0aGUgT3V0bGllcnMgZm9yOiAgDQoqIFJhdGluZyAgDQoqIFNpemUgIA0KKiBTZW50aW1lbnRfUG9sYXJpdHkNCiogU2VudGltZW50X1N1YmplY3Rpdml0eSAgDQoNCldlIGFyZSBjYXBwaW5nIHRoZW0gd2l0aGluIHRoZSA5NSUuICBBcyBpdCBtYWtlcyBzZW5zZSBmb3IgdGhlc2UgdmFyaWFibGVzIHRvIHN0aWxsIGhhdmUgdGhlIG91dGxpZXIgdmFsdWUgY3JlYXRpbmcgYW4gZWZmZWN0LiAgSnVzdCB0aGUgZWZmZWN0IHNob3VsZCBub3QgYmUgZXhjZXNzaXZlLiAgDQoNCmBgYHtyfQ0KY2FwIDwtIGZ1bmN0aW9uKHgpew0KcXVhbnRpbGVzIDwtIHF1YW50aWxlKCB4LHByb2JzID0gIGMoMC4wNSwgMC4yNSwgMC43NSwgMC45NSksbmEucm09VFJVRSkNCnhbIHggPCBxdWFudGlsZXNbMl0gLSAxLjUqSVFSKHgsbmEucm09VCkgXSA8LSBxdWFudGlsZXNbMV0NCnhbIHggPiBxdWFudGlsZXNbM10gKyAxLjUqSVFSKHgsbmEucm09VCkgXSA8LSBxdWFudGlsZXNbNF0NCngNCn0NCmFwcHNbLGMoIlJhdGluZyIsICJTaXplIiwiU2VudGltZW50X1BvbGFyaXR5IiwgIlNlbnRpbWVudF9TdWJqZWN0aXZpdHkiKV0gPC0gc2FwcGx5KGFwcHNbLGMoIlJhdGluZyIsICJTaXplIiwiU2VudGltZW50X1BvbGFyaXR5IiwgIlNlbnRpbWVudF9TdWJqZWN0aXZpdHkiKV0sIGNhcCkgJT4lIA0KICBhcy5kYXRhLmZyYW1lKCkNCg0KDQpgYGANCg0KQ2FwcGluZyB0aGUgT3V0bGllcnMgZm9yIFByaWNlIGdyb3VwZWQgYnkgVHlwZSAoUGFpZCBhcHBzIGdldCBjYXBwZWQgYW1vbmcgcGFpZCBhcHBzIG9ubHkpDQoNCmBgYHtyfQ0KDQphcHBzIDwtIGFwcHMgJT4lIA0KICBncm91cF9ieShUeXBlKSAlPiUgDQogIG11dGF0ZShQcmljZSA9IGNhcChQcmljZSkpICU+JSB1bmdyb3VwKCkNCg0KDQpgYGANCg0KDQoNCiMjIyBDaGVja2luZyBpZiBPdXRsaWVycyBhcmUgY2FwcGVkOg0KDQpgYGB7cn0NCnBhcihtZnJvdz1jKDIsMiksIHB0eSA9ICJzIiApDQpCb3hwbG90KGFwcHMkUmF0aW5nLCBtYWluPSJSYXRpbmciKQ0KQm94cGxvdChhcHBzJFNpemUsIG1haW49IlNpemUiKQ0KQm94cGxvdChhcHBzJFNlbnRpbWVudF9Qb2xhcml0eSwgbWFpbj0gIlNlbnRpbWVudCBQb2xhcml0eSIpDQpCb3hwbG90KGFwcHMkU2VudGltZW50X1N1YmplY3Rpdml0eSwgbWFpbiA9ICJTZW50aW1lbnQgU3ViamVjdGl2aXR5IikNCmBgYA0KSXQgaXMgc2VlbiB0aGF0IHRoZSBvdXRsaWVycyByZW1haW4gaW4gU2VudGltZW50IFN1YmplY3Rpdml0eSBldmVuIGFmdGVyIGNhcHBpbmcgdG8gdGhlIG5lYXJlc3QgNXRoIHF1YW50aWxlIA0KDQojIyMgQ2hlY2tpbmcgb3V0bGllcnMgZm9yIFByaWNlIA0KYGBge3J9DQpCb3hwbG90KGFwcHMkUHJpY2V+YXBwcyRUeXBlLCBtYWluID0gIlByaWNlIGdyb3VwZCBieSB0eXBlIG9mIGFwcCIpDQpgYGANCkl0IGlzIHNlZW4gdGhhdCBkdWUgdG8gdGhlIG51bWJlciBvZiBoaWdobHkgcHJpY2VkIGFwcHMgdGhlIG91dGxpZXJzIHJlbWFpbiBldmVuIGFmdGVyIGNhcHBpbmcgdG8gdGhlIG5lYXJlc3QgcXVhbnRpbGUgDQoNCg0KIyMJVHJhbnNmb3JtIA0KDQpXZSBzZWUgZnJvbSB0aGUgcHJldmlvdXMgc2VjdGlvbiB0aGF0IG51bWJlciBvZiByZXZpZXdzIGlzIGhlYXZpbHkgc2tld2VkLg0KICANCmBgYHtyfQ0KDQpoaXN0KGFwcHMkUmV2aWV3cywgbWFpbj0iUmV2aWV3cyIpDQoNCmBgYA0KDQpTbyBmb3IgaGVhdmlseSByaWdodCBza2V3ZWQsIHdlIGFwcGx5IGEgbG9nIDEwIHRyYW5zZm9ybWF0aW9uIHRvIGdldCBpdCB0byBtb3JlIG5vcm1hbGx5IGRpc3RyaWJ1dGVkLg0KDQpgYGB7cn0NCmFwcHMgPC0gYXBwcyAlPiUgbXV0YXRlKFJldmlld3NfdCA9IGxvZzEwKGFwcHMkUmV2aWV3cykpDQoNCmBgYA0KDQojIyMgQ2hlY2tpbmcgdGhlIGRpc3RyaWJ1dGlvbiBvZiB0aGUgdHJhbnNmb3JtZWQgdmFyaWFibGUgKHJldmlld3MpDQoNCmBgYHtyfQ0KDQpoaXN0KGFwcHMkUmV2aWV3c190LCBtYWluPSJMb2cxMChSZXZpZXdzKSIpDQoNCmBgYA0KDQpgYGB7cn0NCg0KQm94cGxvdChhcHBzJFJldmlld3NfdCwgbWFpbj0ibG9nMTAoUmV2aWV3cykiKQ0KDQpgYGANCg0KV2UgYWxzbyBub3RlIHRoYXQgYnkgYXBwbHlpbmcgdGhlIHRyYW5zZm9ybWF0aW9uLCBhbGwgdGhlIG91dGxpZXJzIGFyZSByZW1vdmVkIGFsc28uDQoNCg0KIyBTdW1tYXJ5DQoNCkFycmFuZ2luZyB0aGUgY29sdW1ucyBhbmQgdGhlIHJvd3MoYWNjb3JkaW5nIHRvIENhdGVnb3J5IGFuZCByYW5raW5nKSAgYW5kIGNoZWNrIHRoZSBzdHJ1Y3R1cmUgb2YgdGhlIGZpbmFsIGRhdGEuDQpgYGB7cn0NCg0KYXBwczwtYXBwcyAlPiUgc2VsZWN0KEFwcCxDYXRlZ29yeSxHZW5yZXMsU2l6ZSxVcGRhdGVkLFllYXIsTW9udGgsRGF5LFR5cGUsUHJpY2UsYENvbnRlbnQgUmF0aW5nYCxSZXZpZXdzLFJhdGluZyxJbnN0YWxscywNCiAgICAgICAgICAgICAgICAgICAgICAgIFRyYW5zbGF0ZWRfUmV2aWV3ICxTZW50aW1lbnQsU2VudGltZW50X1BvbGFyaXR5LCAgU2VudGltZW50X1N1YmplY3Rpdml0eSxSZXZpZXdzX3QpDQoNCiNhcnJhbmdpbmcgdGhlIGRhdGEgYWNjb3JkaW5nIHRvIENhdGVnb3JpZXMgKEEtWikgYW5kIHJhdGluZ3MgKGhpZ2ggdG8gbG93KSB3aXRoaW4gdGhlc2UgY2F0ZWdvcmllcy4NCmFwcHM8LWFwcHMgJT4lIGFycmFuZ2UoQ2F0ZWdvcnksZGVzYyhSYXRpbmcpKQ0KDQpoZWFkKGFwcHMsMTApDQoNCiNjaGVja2luZyB0aGUgc3RydWN0dXJlIG9mIHRoZSBmaW5hbCBkYXRhDQpzdHIoYXBwcykNCg0KYGBgDQoNCkFmdGVyIGFycmFuZ2luZyB0aGUgY29sdW1ucyBhbmQgcm93LCBjaGVjayB0aGUgc3RydWN0dXJlIG9mIHRoZSBmaW5hbCBkYXRhLiBXZSBzZWUgdGhhdCBvdXIgZGF0YSBpcyBpbiB0aGUgcmlnaHQgZm9ybWF0LCBvcmRlcmVkIHByb3Blcmx5LCB0aWR5IGFuZCBkb2VzIG5vdCBoYXZlIGFueSBtaXNzaW5nIHZhbHVlcyBvciBvdXRsaWVycyBhbmQgaXMgcmVhZHkgZm9yIGFuYWx5c2lzIGFsbCBpcnJlbGV2YW50IHZhcmlhYmxlcyBoYXZlIGJlZW4gZHJvcHBlZC4gVGhlIGRhdGEgaXMgbm93IHJlYWR5IGZvciBhbmFseXNpcyB0byB1bmRlcnN0YW5kIGhvdyBBcHBzIGFyZSBkb2luZyBpbiBkaWZmZXJlbnQgY2F0ZWdvcmllcywgdHlwZShGcmVlL1BhaWQpLCBkaWZmZXJlbnQgcHJpY2UgcmFuZ2VzIGJ5IGFuYWx5c2luZyByYXRpbmcsIHNlbnRpbWVudHMsIHNlbnRpbWVudCBwb2xhcml0eSwgbnVtYmVyIG9mIGluc3RhbGxzLCBDb250ZW50IHJhdGluZyBldGMuICAgDQoNCkJlbG93IHdlIHByZXNlbnQgc29tZSBiYXNpYyBhbmFseXNpcyB0aGF0IGNvdWxkIGJlIGhlbHBmdWwgaW4gdW5kZXJzdGFuZGluZyB0aGUgZGF0YToNCg0KIyMjIEZpbmRpbmcgdGhlIHRvcCA1IHJhdGVkIGNhdGVnb3JpZXMgaW4gdGhlIGFwcCBzdG9yZSANCmBgYHtyfQ0KcmF0aW5ncyA8LSBhcHBzICU+JSBncm91cF9ieShDYXRlZ29yeSkgJT4lIHN1bW1hcmlzZSgiQXZnIHJhdGluZyI9cm91bmQobWVhbihSYXRpbmcpLDIpKSANCnJhdGluZ3M8LXJhdGluZ3MlPiUgYXJyYW5nZSAoZGVzYyhgQXZnIHJhdGluZ2ApKQ0KaGVhZChyYXRpbmdzLDUpDQpgYGANCg0KIyMjIEZpbmRpbmcgdGhlIHRvcCA1IHJhdGVkIGFwcHMgaW4gdGhlIGFwcCBzdG9yZSANCmBgYHtyfQ0KYXBwcmF0aW5nPC1hcHBzICU+JSBncm91cF9ieShBcHApICAlPiUgc3VtbWFyaXNlKHJhdGluZz1tZWFuKFJhdGluZykpDQphcHByYXRpbmc8LSBhcHByYXRpbmcgJT4lIGFycmFuZ2UoZGVzYyhyYXRpbmcpKQ0KaGVhZChhcHByYXRpbmcsNSkNCmBgYA0KDQojIyMgU2VudGltZW50IFBvbGFyaXR5IHZlcnN1cyBMYXN0IHVwZGF0ZWQgeWVhcg0KYGBge3J9DQpib3hwbG90KFNlbnRpbWVudF9Qb2xhcml0eX5ZZWFyLGRhdGEgPSBhcHBzLA0KICAgICAgICBtYWluPSJTZW50aW1lbnQgc3ByZWFkIGJhc2VkIG9uIGxhc3QgdXBkYXRlZCBZZWFyIiwNCiAgICAgICAgeGxhYiA9ICJZZWFyIGxhc3QgdXBkYXRlZCIsDQogICAgICAgIHlsYWIgPSAiQXZlcmFnZSBTZW50aW1lbnQiLA0KICAgICAgICBjb2w9KGMoImxpZ2h0Ymx1ZSIpICkpDQpgYGANCg0KIyMjIFNlbnRpbWVudCBQb2xhcml0eSB2ZXJzdXMgUHJpY2UNCmBgYHtyfQ0KYm94cGxvdChTZW50aW1lbnRfUG9sYXJpdHl+UHJpY2UsIGRhdGEgPSBhcHBzLA0KICAgICAgICBtYWluPSJTZW50aW1lbnQgc3ByZWFkIGJhc2VkIG9uIFByaWNlIiwNCiAgICAgICAgeGxhYiA9ICJQcmljZSBvZiBBcHAiLA0KICAgICAgICB5bGFiID0gIkF2ZXJhZ2UgU2VudGltZW50IiwNCiAgICAgICAgY29sPShjKCJnb2xkIikpKQ0KYGBgDQoNCg0K