Exploring Categorical Data

Contingency table review

We’ll be working with the comics dataset. This is a collection of characteristics on all of the superheroes created by Marvel and DC comics in the last 80 years.

Let’s start by creating a contingency table, which is a useful way to represent the total counts of observations that fall into each combination of the levels of categorical variables.

comics <- read.csv("comics.csv")
# Print the first rows of the data
comics

# Check levels of align
levels(comics$align)

# Check the levels of gender
levels(comics$gender)

# Create a 2-way contingency table
tab <- table(comics$align, comics$gender)
tab

Dropping levels

The contingency table revealed that there are some levels that have very low counts. To simplify the analysis, it often helps to drop such levels.

In R, this requires two steps: first filtering out any rows with the levels that have very low counts, then removing these levels from the factor variable with droplevels(). This is because the droplevels() function would keep levels that have just 1 or 2 counts; it only drops levels that don’t exist in a dataset.

# Load dplyr
library(dplyr)

# Print tab
tab

# Remove align level
comics_filtered <- comics %>%
  filter(align != "Reformed Criminals") %>%
  droplevels()

# See the result
comics_filtered

Side-by-side barcharts

While a contingency table represents the counts numerically, it’s often more useful to represent them graphically.

Here we’ll construct two side-by-side barcharts of the comics data. This shows that there can often be two or more options for presenting the same data. Passing the argument position = “dodge” to geom_bar() says that we want a side-by-side (i.e. not stacked) barchart.

# Load ggplot2
library(ggplot2)

# Create side-by-side barchart of gender by alignment
ggplot(comics, aes(x = align, fill = gender)) + 
  geom_bar(position = "dodge")

# Create side-by-side barchart of alignment by gender
ggplot(comics, aes(x = gender, fill = align)) + 
  geom_bar(position = "dodge") +
  theme(axis.text.x = element_text(angle = 90))

Among characters with “Neutral” alignment, males are the most common. In general, there is an association between gender and alignment. There are more male characters than female characters in this dataset.

Counts vs proportion

Conditional

The following code generates tables of joint and conditional proportions, respectively:

tab <- table(comics$align, comics$gender)
options(scipen = 999, digits = 3) # Print fewer digits
prop.table(tab)     # Joint proportions
prop.table(tab, 2)  # Conditional on columns

Plot count vs proportions

Bar charts can tell dramatically different stories depending on whether they represent counts or proportions and, if proportions, what the proportions are conditioned on.

# Plot of gender by align
ggplot(comics, aes(x = align, fill = gender)) +
  geom_bar()
  
# Plot proportion of gender, conditional on align
ggplot(comics, aes(x = align, fill = gender)) + 
  geom_bar(position = "fill") +
  ylab("proportion")

By adding position = “fill” to geom_bar(), we are saying we want the bars to fill the entire height of the plotting window, thus displaying proportions and not raw counts.

Distribution of one variable

Marginal barchart

If we are interested in the distribution of alignment of all superheroes, it makes sense to construct a barchart for just that single variable.

We can improve the interpretability of the plot, though, by implementing some sensible ordering. Superheroes that are “Neutral” show an alignment between “Good” and “Bad”, so it makes sense to put that bar in the middle.

# Change the order of the levels in align
comics$align <- factor(comics$align, 
                       levels = c("Bad", "Neutral", "Good"))

# Create plot of align
ggplot(comics, aes(x = align)) + 
  geom_bar()

Conditional barchart

If we want to break down the distribution of alignment based on gender, we’re looking for conditional distributions.

We could make these by creating multiple filtered datasets (one for each gender) or by faceting the plot of alignment based on gender

# Plot of alignment broken down by gender
ggplot(comics, aes(x = align)) + 
  geom_bar() +
  facet_wrap(~ gender)

Improve piechart

The piechart is a very common way to represent the distribution of a single categorical variable, but they can be more difficult to interpret than barcharts.

Dataset called pies, contains the favorite pie flavors of 98 people.

We can reorder levels

pies <- readRDS("pies.rds")
# Put levels of flavor in descending order
lev <- c("apple", "key lime", "boston creme", "blueberry", "cherry", "pumpkin", "strawberry")
pies$flavor <- factor(pies$flavor, levels = lev)
# Create barchart of flavor
ggplot(pies, aes(x = flavor)) + 
  geom_bar(fill = "chartreuse") + 
  theme(axis.text.x = element_text(angle = 90))

Exploring Numerical Data

Faceted histogram

We’ll be working with the cars dataset, which records characteristics on all of the new models of cars for sale in the US in a certain year. We will investigate the distribution of mileage across a categorial variable, but before you get there, you’ll want to familiarize yourself with the dataset.

# Load package
library(ggplot2)

cars <- read.csv("cars04.csv")

# Learn data structure
str(cars)

# Create faceted histogram
ggplot(cars, aes(x = city_mpg)) +
  geom_histogram() +
  facet_wrap(~ suv)

We faceted by the suv variable, but it’s important to note that you can facet a plot by any categorical variable using facet_wrap().

Boxplots and density plots

The mileage of a car tends to be associated with the size of its engine (as measured by the number of cylinders). To explore the relationship between these two variables, you could stick to using histograms.

A quick look at unique(cars$ncyl) shows that there are more possible levels of ncyl than we might think.

# Filter cars with 4, 6, 8 cylinders
common_cyl <- filter(cars, ncyl %in% c(4, 6, 8))

# Create box plots of city mpg by ncyl
ggplot(common_cyl, aes(x = as.factor(ncyl), y = city_mpg)) +
  geom_boxplot()

# Create overlaid density plots for same data
ggplot(common_cyl, aes(x = city_mpg, fill = as.factor(ncyl))) +
  geom_density(alpha = .3)

Distribution of one variable

Marginal and conditional histograms

Now, we will analyse the variable: horsepwr. The goal is to get a sense of the marginal distribution of this variable and then compare it to the distribution of horsepower conditional on the price of the car being less than $25,000.

# Create hist of horsepwr
cars %>%
  ggplot(aes(horsepwr)) +
  geom_histogram() +
  ggtitle("Distribution of horsepower")

# Create hist of horsepwr for affordable cars
cars %>% 
  filter(msrp < 25000) %>%
  ggplot(aes(horsepwr)) +
  geom_histogram() +
  xlim(c(90, 550)) +
  ggtitle("Distribution of horsepower for cars under $25k")

Three binwidths

Before we take these plots for granted, it’s a good idea to see how things change when we alter the binwidth. The binwidth determines how smooth our distribution will appear: the smaller the binwidth, the more jagged our distribution becomes. It’s good practice to consider several binwidths in order to detect different types of structure in your data.

# Create hist of horsepwr with binwidth of 3
cars %>%
  ggplot(aes(horsepwr)) +
  geom_histogram(binwidth = 3) +
  ggtitle("Distribution of horsepower: bindwidth 3")

# Create hist of horsepwr with binwidth of 30
cars %>%
  ggplot(aes(horsepwr)) +
  geom_histogram(binwidth = 30) +
  ggtitle("Distribution of horsepower: bindwidth 30")

# Create hist of horsepwr with binwidth of 60
cars %>%
  ggplot(aes(horsepwr)) +
  geom_histogram(binwidth = 60) +
  ggtitle("Distribution of horsepower: bindwidth 60")

Plot A is the only histogram that shows the count for cars with exactly 200 and 300 horsepower.

Box plots

Box plots for outliers

In addition to indicating the center and spread of a distribution, a box plot provides a graphical means to detect outliers. You can apply this method to the msrp column (manufacturer’s suggested retail price) to detect if there are unusually expensive or cheap cars.

# Construct box plot of msrp
cars %>%
  ggplot(aes(x = 1, y = msrp)) +
  geom_boxplot()

# Exclude outliers from data
cars_no_out <- cars %>%
  filter(msrp < 100000)

# Construct box plot of msrp using the reduced dataset
cars_no_out %>%
  ggplot(aes(x = 1, y = msrp)) +
  geom_boxplot()

Plot selection

Consider two other columns in the cars dataset: city_mpg and width. Which is the most appropriate plot for displaying the important features of their distributions? Remember, both density plots and box plots display the central tendency and spread of the data, but the box plot is more robust to outliers.

# Create plot of city_mpg
cars %>%
  ggplot(aes(x = 1, y = city_mpg)) +
  geom_boxplot()

# Create plot of width
cars %>% 
  ggplot(aes(x = width)) +
  geom_density()

Because the city_mpg variable has a much wider range with its outliers, it’s best to display its distribution as a box plot.

Visualization in higher dimensions

3 variable plot

Faceting is a valuable technique for looking at several conditional distributions at the same time. If the faceted distributions are laid out in a grid, you can consider the association between a variable and two others, one on the rows of the grid and the other on the columns.

# Facet hists using hwy mileage and ncyl
common_cyl %>%
  ggplot(aes(x = hwy_mpg)) +
  geom_histogram() +
  facet_grid(ncyl ~ suv) +
  ggtitle("Mileage by suv and ncyl")

Numerical Summaries

Measures of center

Calculate center measures

Throughout this chapter, we will use data from gapminder, which tracks demographic data in countries of the world over time. To learn more about it, you can bring up the help file with ?gapminder. We will focus on how the life expectancy differs from continent to continent. This requires that we conduct our analysis not at the country level, but aggregated up to the continent level. This is made possible by the one-two punch of group_by() and summarize(), a very powerful syntax for carrying out the same analysis on different subsets of the full dataset.

library(gapminder)
package 㤼㸱gapminder㤼㸲 was built under R version 3.6.3
# Create dataset of 2007 data
gap2007 <- filter(gapminder, year == 2007)

# Compute groupwise mean and median lifeExp
gap2007 %>%
  group_by(continent) %>%
  summarize(mean(lifeExp),
            median(lifeExp))

# Generate box plots of lifeExp for each continent
gap2007 %>%
  ggplot(aes(x = continent, y = lifeExp)) +
  geom_boxplot()

Measures of variability

Calculate spread measures

Let’s extend the powerful group_by() and summarize() syntax to measures of spread. If we’re unsure whether we’re working with symmetric or skewed distributions, it’s a good idea to consider a robust measure like IQR in addition to the usual measures of variance or standard deviation.

# Compute groupwise measures of spread
gap2007 %>%
  group_by(continent) %>%
  summarize(sd(lifeExp),
            IQR(lifeExp),
            n())

# Generate overlaid density plots
gap2007 %>%
  ggplot(aes(x = lifeExp, fill = continent)) +
  geom_density(alpha = 0.3)

Choose measures for center and spread

Consider the density plots shown here. What are the most appropriate measures to describe their centers and spreads?

gap2007 %>% 
  filter(continent == "Americas") %>% 
  ggplot(aes(lifeExp)) +
  geom_density()


gap2007 %>% 
  filter(continent == "Americas") %>% 
  ggplot(aes(pop)) +
  geom_density()

# Compute stats for lifeExp in Americas
gap2007 %>%
  filter(continent == "Americas") %>%
  summarize(mean(lifeExp),
            sd(lifeExp))

# Compute stats for population
gap2007 %>%
  summarize(median(pop),
            IQR(pop))

Like mean and standard deviation, median and IQR measure the central tendency and spread, respectively, but are robust to outliers and non-normal data.

Shape and transformations

Transformations

Highly skewed distributions can make it very difficult to learn anything from a visualization. Transformations can be helpful in revealing the more subtle structure.

# Create density plot of old variable
gap2007 %>%
  ggplot(aes(x = pop)) +
  geom_density()


# Transform the skewed pop variable
gap2007 <- gap2007 %>%
  mutate(log_pop = log(pop))

# Create density plot of new variable
gap2007 %>%
  ggplot(aes(x = log_pop)) +
  geom_density()

Outliers

Identify outliers

Consider the distribution of the life expectancies of the countries in Asia.

gap2007 %>% 
  filter(continent == "Asia") %>% 
  ggplot(aes(x = 1, y = lifeExp)) +
  geom_boxplot()

The box plot identifies one clear outlier: a country with a notably low life expectancy. Do you have a guess as to which country this might be?

gap2007 %>% 
  filter(continent == "Asia") %>% 
  arrange(lifeExp)
# Filter for Asia, add column indicating outliers
gap_asia <- gap2007 %>%
  filter(continent == "Asia") %>%
  mutate(is_outlier = lifeExp < 50)

# Remove outliers, create box plot of lifeExp
gap_asia %>%
  filter(!is_outlier) %>%
  ggplot(aes(x = 1, y = lifeExp)) +
  geom_boxplot()

LS0tDQp0aXRsZTogIkV4cGxvcmF0b3J5IERhdGEgQW5hbHlzaXMgaW4gUiINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICB0b2NfY29sbGFwc2VkOiBmYWxzZQ0KICAgIA0KdG9jX2RlcHRoOiAzDQotLS0NCiMgRXhwbG9yaW5nIENhdGVnb3JpY2FsIERhdGENCg0KIyMgQ29udGluZ2VuY3kgdGFibGUgcmV2aWV3DQoNCldlJ2xsIGJlIHdvcmtpbmcgd2l0aCB0aGUgY29taWNzIGRhdGFzZXQuIFRoaXMgaXMgYSBjb2xsZWN0aW9uIG9mIGNoYXJhY3RlcmlzdGljcyBvbiBhbGwgb2YgdGhlIHN1cGVyaGVyb2VzIGNyZWF0ZWQgYnkgTWFydmVsIGFuZCBEQyBjb21pY3MgaW4gdGhlIGxhc3QgODAgeWVhcnMuDQoNCkxldCdzIHN0YXJ0IGJ5IGNyZWF0aW5nIGEgY29udGluZ2VuY3kgdGFibGUsIHdoaWNoIGlzIGEgdXNlZnVsIHdheSB0byByZXByZXNlbnQgdGhlIHRvdGFsIGNvdW50cyBvZiBvYnNlcnZhdGlvbnMgdGhhdCBmYWxsIGludG8gZWFjaCBjb21iaW5hdGlvbiBvZiB0aGUgbGV2ZWxzIG9mIGNhdGVnb3JpY2FsIHZhcmlhYmxlcy4NCmBgYHtyfQ0KY29taWNzIDwtIHJlYWQuY3N2KCJjb21pY3MuY3N2IikNCmBgYA0KDQpgYGB7cn0NCiMgUHJpbnQgdGhlIGZpcnN0IHJvd3Mgb2YgdGhlIGRhdGENCmNvbWljcw0KDQojIENoZWNrIGxldmVscyBvZiBhbGlnbg0KbGV2ZWxzKGNvbWljcyRhbGlnbikNCg0KIyBDaGVjayB0aGUgbGV2ZWxzIG9mIGdlbmRlcg0KbGV2ZWxzKGNvbWljcyRnZW5kZXIpDQoNCiMgQ3JlYXRlIGEgMi13YXkgY29udGluZ2VuY3kgdGFibGUNCnRhYiA8LSB0YWJsZShjb21pY3MkYWxpZ24sIGNvbWljcyRnZW5kZXIpDQp0YWINCmBgYA0KIyMgRHJvcHBpbmcgbGV2ZWxzDQoNClRoZSBjb250aW5nZW5jeSB0YWJsZSByZXZlYWxlZCB0aGF0IHRoZXJlIGFyZSBzb21lIGxldmVscyB0aGF0IGhhdmUgdmVyeSBsb3cgY291bnRzLiBUbyBzaW1wbGlmeSB0aGUgYW5hbHlzaXMsIGl0IG9mdGVuIGhlbHBzIHRvIGRyb3Agc3VjaCBsZXZlbHMuDQoNCkluIFIsIHRoaXMgcmVxdWlyZXMgdHdvIHN0ZXBzOiBmaXJzdCBmaWx0ZXJpbmcgb3V0IGFueSByb3dzIHdpdGggdGhlIGxldmVscyB0aGF0IGhhdmUgdmVyeSBsb3cgY291bnRzLCB0aGVuIHJlbW92aW5nIHRoZXNlIGxldmVscyBmcm9tIHRoZSBmYWN0b3IgdmFyaWFibGUgd2l0aCBkcm9wbGV2ZWxzKCkuIFRoaXMgaXMgYmVjYXVzZSB0aGUgZHJvcGxldmVscygpIGZ1bmN0aW9uIHdvdWxkIGtlZXAgbGV2ZWxzIHRoYXQgaGF2ZSBqdXN0IDEgb3IgMiBjb3VudHM7IGl0IG9ubHkgZHJvcHMgbGV2ZWxzIHRoYXQgZG9uJ3QgZXhpc3QgaW4gYSBkYXRhc2V0Lg0KYGBge3J9DQojIExvYWQgZHBseXINCmxpYnJhcnkoZHBseXIpDQoNCiMgUHJpbnQgdGFiDQp0YWINCg0KIyBSZW1vdmUgYWxpZ24gbGV2ZWwNCmNvbWljc19maWx0ZXJlZCA8LSBjb21pY3MgJT4lDQogIGZpbHRlcihhbGlnbiAhPSAiUmVmb3JtZWQgQ3JpbWluYWxzIikgJT4lDQogIGRyb3BsZXZlbHMoKQ0KDQojIFNlZSB0aGUgcmVzdWx0DQpjb21pY3NfZmlsdGVyZWQNCmBgYA0KIyMgU2lkZS1ieS1zaWRlIGJhcmNoYXJ0cw0KDQpXaGlsZSBhIGNvbnRpbmdlbmN5IHRhYmxlIHJlcHJlc2VudHMgdGhlIGNvdW50cyBudW1lcmljYWxseSwgaXQncyBvZnRlbiBtb3JlIHVzZWZ1bCB0byByZXByZXNlbnQgdGhlbSBncmFwaGljYWxseS4NCg0KSGVyZSB3ZSdsbCBjb25zdHJ1Y3QgdHdvIHNpZGUtYnktc2lkZSBiYXJjaGFydHMgb2YgdGhlIGNvbWljcyBkYXRhLiBUaGlzIHNob3dzIHRoYXQgdGhlcmUgY2FuIG9mdGVuIGJlIHR3byBvciBtb3JlIG9wdGlvbnMgZm9yIHByZXNlbnRpbmcgdGhlIHNhbWUgZGF0YS4gUGFzc2luZyB0aGUgYXJndW1lbnQgcG9zaXRpb24gPSAiZG9kZ2UiIHRvIGdlb21fYmFyKCkgc2F5cyB0aGF0IHdlIHdhbnQgYSBzaWRlLWJ5LXNpZGUgKGkuZS4gbm90IHN0YWNrZWQpIGJhcmNoYXJ0Lg0KYGBge3J9DQojIExvYWQgZ2dwbG90Mg0KbGlicmFyeShnZ3Bsb3QyKQ0KDQojIENyZWF0ZSBzaWRlLWJ5LXNpZGUgYmFyY2hhcnQgb2YgZ2VuZGVyIGJ5IGFsaWdubWVudA0KZ2dwbG90KGNvbWljcywgYWVzKHggPSBhbGlnbiwgZmlsbCA9IGdlbmRlcikpICsgDQogIGdlb21fYmFyKHBvc2l0aW9uID0gImRvZGdlIikNCg0KIyBDcmVhdGUgc2lkZS1ieS1zaWRlIGJhcmNoYXJ0IG9mIGFsaWdubWVudCBieSBnZW5kZXINCmdncGxvdChjb21pY3MsIGFlcyh4ID0gZ2VuZGVyLCBmaWxsID0gYWxpZ24pKSArIA0KICBnZW9tX2Jhcihwb3NpdGlvbiA9ICJkb2RnZSIpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCkpDQpgYGANCkFtb25nIGNoYXJhY3RlcnMgd2l0aCAiTmV1dHJhbCIgYWxpZ25tZW50LCBtYWxlcyBhcmUgdGhlIG1vc3QgY29tbW9uLg0KSW4gZ2VuZXJhbCwgdGhlcmUgaXMgYW4gYXNzb2NpYXRpb24gYmV0d2VlbiBnZW5kZXIgYW5kIGFsaWdubWVudC4NClRoZXJlIGFyZSBtb3JlIG1hbGUgY2hhcmFjdGVycyB0aGFuIGZlbWFsZSBjaGFyYWN0ZXJzIGluIHRoaXMgZGF0YXNldC4NCg0KIyMgQ291bnRzIHZzIHByb3BvcnRpb24NCg0KIyMjIENvbmRpdGlvbmFsIA0KDQpUaGUgZm9sbG93aW5nIGNvZGUgZ2VuZXJhdGVzIHRhYmxlcyBvZiBqb2ludCBhbmQgY29uZGl0aW9uYWwgcHJvcG9ydGlvbnMsIHJlc3BlY3RpdmVseToNCmBgYHtyfQ0KdGFiIDwtIHRhYmxlKGNvbWljcyRhbGlnbiwgY29taWNzJGdlbmRlcikNCm9wdGlvbnMoc2NpcGVuID0gOTk5LCBkaWdpdHMgPSAzKSAjIFByaW50IGZld2VyIGRpZ2l0cw0KcHJvcC50YWJsZSh0YWIpICAgICAjIEpvaW50IHByb3BvcnRpb25zDQpwcm9wLnRhYmxlKHRhYiwgMikgICMgQ29uZGl0aW9uYWwgb24gY29sdW1ucw0KYGBgDQojIyMgUGxvdCBjb3VudCB2cyBwcm9wb3J0aW9ucw0KDQpCYXIgY2hhcnRzIGNhbiB0ZWxsIGRyYW1hdGljYWxseSBkaWZmZXJlbnQgc3RvcmllcyBkZXBlbmRpbmcgb24gd2hldGhlciB0aGV5IHJlcHJlc2VudCBjb3VudHMgb3IgcHJvcG9ydGlvbnMgYW5kLCBpZiBwcm9wb3J0aW9ucywgd2hhdCB0aGUgcHJvcG9ydGlvbnMgYXJlIGNvbmRpdGlvbmVkIG9uLg0KYGBge3J9DQojIFBsb3Qgb2YgZ2VuZGVyIGJ5IGFsaWduDQpnZ3Bsb3QoY29taWNzLCBhZXMoeCA9IGFsaWduLCBmaWxsID0gZ2VuZGVyKSkgKw0KICBnZW9tX2JhcigpDQogIA0KIyBQbG90IHByb3BvcnRpb24gb2YgZ2VuZGVyLCBjb25kaXRpb25hbCBvbiBhbGlnbg0KZ2dwbG90KGNvbWljcywgYWVzKHggPSBhbGlnbiwgZmlsbCA9IGdlbmRlcikpICsgDQogIGdlb21fYmFyKHBvc2l0aW9uID0gImZpbGwiKSArDQogIHlsYWIoInByb3BvcnRpb24iKQ0KYGBgDQpCeSBhZGRpbmcgcG9zaXRpb24gPSAiZmlsbCIgdG8gZ2VvbV9iYXIoKSwgd2UgYXJlIHNheWluZyB3ZSB3YW50IHRoZSBiYXJzIHRvIGZpbGwgdGhlIGVudGlyZSBoZWlnaHQgb2YgdGhlIHBsb3R0aW5nIHdpbmRvdywgdGh1cyBkaXNwbGF5aW5nIHByb3BvcnRpb25zIGFuZCBub3QgcmF3IGNvdW50cy4NCg0KIyMgRGlzdHJpYnV0aW9uIG9mIG9uZSB2YXJpYWJsZQ0KDQojIyMgTWFyZ2luYWwgYmFyY2hhcnQNCg0KSWYgd2UgYXJlIGludGVyZXN0ZWQgaW4gdGhlIGRpc3RyaWJ1dGlvbiBvZiBhbGlnbm1lbnQgb2YgYWxsIHN1cGVyaGVyb2VzLCBpdCBtYWtlcyBzZW5zZSB0byBjb25zdHJ1Y3QgYSBiYXJjaGFydCBmb3IganVzdCB0aGF0IHNpbmdsZSB2YXJpYWJsZS4NCg0KV2UgY2FuIGltcHJvdmUgdGhlIGludGVycHJldGFiaWxpdHkgb2YgdGhlIHBsb3QsIHRob3VnaCwgYnkgaW1wbGVtZW50aW5nIHNvbWUgc2Vuc2libGUgb3JkZXJpbmcuIFN1cGVyaGVyb2VzIHRoYXQgYXJlICJOZXV0cmFsIiBzaG93IGFuIGFsaWdubWVudCBiZXR3ZWVuICJHb29kIiBhbmQgIkJhZCIsIHNvIGl0IG1ha2VzIHNlbnNlIHRvIHB1dCB0aGF0IGJhciBpbiB0aGUgbWlkZGxlLg0KYGBge3J9DQojIENoYW5nZSB0aGUgb3JkZXIgb2YgdGhlIGxldmVscyBpbiBhbGlnbg0KY29taWNzJGFsaWduIDwtIGZhY3Rvcihjb21pY3MkYWxpZ24sIA0KICAgICAgICAgICAgICAgICAgICAgICBsZXZlbHMgPSBjKCJCYWQiLCAiTmV1dHJhbCIsICJHb29kIikpDQoNCiMgQ3JlYXRlIHBsb3Qgb2YgYWxpZ24NCmdncGxvdChjb21pY3MsIGFlcyh4ID0gYWxpZ24pKSArIA0KICBnZW9tX2JhcigpDQpgYGANCiMjIyBDb25kaXRpb25hbCBiYXJjaGFydA0KDQpJZiB3ZSB3YW50IHRvIGJyZWFrIGRvd24gdGhlIGRpc3RyaWJ1dGlvbiBvZiBhbGlnbm1lbnQgYmFzZWQgb24gZ2VuZGVyLCB3ZSdyZSBsb29raW5nIGZvciBjb25kaXRpb25hbCBkaXN0cmlidXRpb25zLg0KDQpXZSBjb3VsZCBtYWtlIHRoZXNlIGJ5IGNyZWF0aW5nIG11bHRpcGxlIGZpbHRlcmVkIGRhdGFzZXRzIChvbmUgZm9yIGVhY2ggZ2VuZGVyKSBvciBieSBmYWNldGluZyB0aGUgcGxvdCBvZiBhbGlnbm1lbnQgYmFzZWQgb24gZ2VuZGVyDQpgYGB7cn0NCiMgUGxvdCBvZiBhbGlnbm1lbnQgYnJva2VuIGRvd24gYnkgZ2VuZGVyDQpnZ3Bsb3QoY29taWNzLCBhZXMoeCA9IGFsaWduKSkgKyANCiAgZ2VvbV9iYXIoKSArDQogIGZhY2V0X3dyYXAofiBnZW5kZXIpDQpgYGANCiMjIyBJbXByb3ZlIHBpZWNoYXJ0DQoNClRoZSBwaWVjaGFydCBpcyBhIHZlcnkgY29tbW9uIHdheSB0byByZXByZXNlbnQgdGhlIGRpc3RyaWJ1dGlvbiBvZiBhIHNpbmdsZSBjYXRlZ29yaWNhbCB2YXJpYWJsZSwgYnV0IHRoZXkgY2FuIGJlIG1vcmUgZGlmZmljdWx0IHRvIGludGVycHJldCB0aGFuIGJhcmNoYXJ0cy4NCg0KRGF0YXNldCBjYWxsZWQgcGllcywgY29udGFpbnMgdGhlIGZhdm9yaXRlIHBpZSBmbGF2b3JzIG9mIDk4IHBlb3BsZS4NCg0KV2UgY2FuIHJlb3JkZXIgbGV2ZWxzDQoNCmBgYHtyfQ0KcGllcyA8LSByZWFkUkRTKCJwaWVzLnJkcyIpDQojIFB1dCBsZXZlbHMgb2YgZmxhdm9yIGluIGRlc2NlbmRpbmcgb3JkZXINCmxldiA8LSBjKCJhcHBsZSIsICJrZXkgbGltZSIsICJib3N0b24gY3JlbWUiLCAiYmx1ZWJlcnJ5IiwgImNoZXJyeSIsICJwdW1wa2luIiwgInN0cmF3YmVycnkiKQ0KcGllcyRmbGF2b3IgPC0gZmFjdG9yKHBpZXMkZmxhdm9yLCBsZXZlbHMgPSBsZXYpDQpgYGANCmBgYHtyfQ0KIyBDcmVhdGUgYmFyY2hhcnQgb2YgZmxhdm9yDQpnZ3Bsb3QocGllcywgYWVzKHggPSBmbGF2b3IpKSArIA0KICBnZW9tX2JhcihmaWxsID0gImNoYXJ0cmV1c2UiKSArIA0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKSkNCmBgYA0KIyBFeHBsb3JpbmcgTnVtZXJpY2FsIERhdGENCg0KIyMgRmFjZXRlZCBoaXN0b2dyYW0NCg0KV2UnbGwgYmUgd29ya2luZyB3aXRoIHRoZSBjYXJzIGRhdGFzZXQsIHdoaWNoIHJlY29yZHMgY2hhcmFjdGVyaXN0aWNzIG9uIGFsbCBvZiB0aGUgbmV3IG1vZGVscyBvZiBjYXJzIGZvciBzYWxlIGluIHRoZSBVUyBpbiBhIGNlcnRhaW4geWVhci4gV2Ugd2lsbCBpbnZlc3RpZ2F0ZSB0aGUgZGlzdHJpYnV0aW9uIG9mIG1pbGVhZ2UgYWNyb3NzIGEgY2F0ZWdvcmlhbCB2YXJpYWJsZSwgYnV0IGJlZm9yZSB5b3UgZ2V0IHRoZXJlLCB5b3UnbGwgd2FudCB0byBmYW1pbGlhcml6ZSB5b3Vyc2VsZiB3aXRoIHRoZSBkYXRhc2V0Lg0KYGBge3J9DQojIExvYWQgcGFja2FnZQ0KbGlicmFyeShnZ3Bsb3QyKQ0KDQpjYXJzIDwtIHJlYWQuY3N2KCJjYXJzMDQuY3N2IikNCg0KIyBMZWFybiBkYXRhIHN0cnVjdHVyZQ0Kc3RyKGNhcnMpDQoNCiMgQ3JlYXRlIGZhY2V0ZWQgaGlzdG9ncmFtDQpnZ3Bsb3QoY2FycywgYWVzKHggPSBjaXR5X21wZykpICsNCiAgZ2VvbV9oaXN0b2dyYW0oKSArDQogIGZhY2V0X3dyYXAofiBzdXYpDQpgYGANCldlIGZhY2V0ZWQgYnkgdGhlIHN1diB2YXJpYWJsZSwgYnV0IGl0J3MgaW1wb3J0YW50IHRvIG5vdGUgdGhhdCB5b3UgY2FuIGZhY2V0IGEgcGxvdCBieSBhbnkgY2F0ZWdvcmljYWwgdmFyaWFibGUgdXNpbmcgZmFjZXRfd3JhcCgpLg0KDQojIyBCb3hwbG90cyBhbmQgZGVuc2l0eSBwbG90cw0KDQpUaGUgbWlsZWFnZSBvZiBhIGNhciB0ZW5kcyB0byBiZSBhc3NvY2lhdGVkIHdpdGggdGhlIHNpemUgb2YgaXRzIGVuZ2luZSAoYXMgbWVhc3VyZWQgYnkgdGhlIG51bWJlciBvZiBjeWxpbmRlcnMpLiBUbyBleHBsb3JlIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGVzZSB0d28gdmFyaWFibGVzLCB5b3UgY291bGQgc3RpY2sgdG8gdXNpbmcgaGlzdG9ncmFtcy4NCg0KQSBxdWljayBsb29rIGF0IHVuaXF1ZShjYXJzJG5jeWwpIHNob3dzIHRoYXQgdGhlcmUgYXJlIG1vcmUgcG9zc2libGUgbGV2ZWxzIG9mIG5jeWwgdGhhbiB3ZSBtaWdodCB0aGluay4NCmBgYHtyfQ0KIyBGaWx0ZXIgY2FycyB3aXRoIDQsIDYsIDggY3lsaW5kZXJzDQpjb21tb25fY3lsIDwtIGZpbHRlcihjYXJzLCBuY3lsICVpbiUgYyg0LCA2LCA4KSkNCg0KIyBDcmVhdGUgYm94IHBsb3RzIG9mIGNpdHkgbXBnIGJ5IG5jeWwNCmdncGxvdChjb21tb25fY3lsLCBhZXMoeCA9IGFzLmZhY3RvcihuY3lsKSwgeSA9IGNpdHlfbXBnKSkgKw0KICBnZW9tX2JveHBsb3QoKQ0KDQojIENyZWF0ZSBvdmVybGFpZCBkZW5zaXR5IHBsb3RzIGZvciBzYW1lIGRhdGENCmdncGxvdChjb21tb25fY3lsLCBhZXMoeCA9IGNpdHlfbXBnLCBmaWxsID0gYXMuZmFjdG9yKG5jeWwpKSkgKw0KICBnZW9tX2RlbnNpdHkoYWxwaGEgPSAuMykNCmBgYA0KIyMgRGlzdHJpYnV0aW9uIG9mIG9uZSB2YXJpYWJsZQ0KDQojIyMgTWFyZ2luYWwgYW5kIGNvbmRpdGlvbmFsIGhpc3RvZ3JhbXMNCg0KTm93LCB3ZSB3aWxsIGFuYWx5c2UgdGhlIHZhcmlhYmxlOiBob3JzZXB3ci4gVGhlIGdvYWwgaXMgdG8gZ2V0IGEgc2Vuc2Ugb2YgdGhlIG1hcmdpbmFsIGRpc3RyaWJ1dGlvbiBvZiB0aGlzIHZhcmlhYmxlIGFuZCB0aGVuIGNvbXBhcmUgaXQgdG8gdGhlIGRpc3RyaWJ1dGlvbiBvZiBob3JzZXBvd2VyIGNvbmRpdGlvbmFsIG9uIHRoZSBwcmljZSBvZiB0aGUgY2FyIGJlaW5nIGxlc3MgdGhhbiAkMjUsMDAwLg0KYGBge3J9DQojIENyZWF0ZSBoaXN0IG9mIGhvcnNlcHdyDQpjYXJzICU+JQ0KICBnZ3Bsb3QoYWVzKGhvcnNlcHdyKSkgKw0KICBnZW9tX2hpc3RvZ3JhbSgpICsNCiAgZ2d0aXRsZSgiRGlzdHJpYnV0aW9uIG9mIGhvcnNlcG93ZXIiKQ0KDQojIENyZWF0ZSBoaXN0IG9mIGhvcnNlcHdyIGZvciBhZmZvcmRhYmxlIGNhcnMNCmNhcnMgJT4lIA0KICBmaWx0ZXIobXNycCA8IDI1MDAwKSAlPiUNCiAgZ2dwbG90KGFlcyhob3JzZXB3cikpICsNCiAgZ2VvbV9oaXN0b2dyYW0oKSArDQogIHhsaW0oYyg5MCwgNTUwKSkgKw0KICBnZ3RpdGxlKCJEaXN0cmlidXRpb24gb2YgaG9yc2Vwb3dlciBmb3IgY2FycyB1bmRlciAkMjVrIikNCmBgYA0KIyMjIFRocmVlIGJpbndpZHRocw0KDQpCZWZvcmUgd2UgdGFrZSB0aGVzZSBwbG90cyBmb3IgZ3JhbnRlZCwgaXQncyBhIGdvb2QgaWRlYSB0byBzZWUgaG93IHRoaW5ncyBjaGFuZ2Ugd2hlbiB3ZSBhbHRlciB0aGUgYmlud2lkdGguIFRoZSBiaW53aWR0aCBkZXRlcm1pbmVzIGhvdyBzbW9vdGggb3VyIGRpc3RyaWJ1dGlvbiB3aWxsIGFwcGVhcjogdGhlIHNtYWxsZXIgdGhlIGJpbndpZHRoLCB0aGUgbW9yZSBqYWdnZWQgb3VyIGRpc3RyaWJ1dGlvbiBiZWNvbWVzLiBJdCdzIGdvb2QgcHJhY3RpY2UgdG8gY29uc2lkZXIgc2V2ZXJhbCBiaW53aWR0aHMgaW4gb3JkZXIgdG8gZGV0ZWN0IGRpZmZlcmVudCB0eXBlcyBvZiBzdHJ1Y3R1cmUgaW4geW91ciBkYXRhLg0KYGBge3J9DQojIENyZWF0ZSBoaXN0IG9mIGhvcnNlcHdyIHdpdGggYmlud2lkdGggb2YgMw0KY2FycyAlPiUNCiAgZ2dwbG90KGFlcyhob3JzZXB3cikpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAzKSArDQogIGdndGl0bGUoIkRpc3RyaWJ1dGlvbiBvZiBob3JzZXBvd2VyOiBiaW5kd2lkdGggMyIpDQoNCiMgQ3JlYXRlIGhpc3Qgb2YgaG9yc2Vwd3Igd2l0aCBiaW53aWR0aCBvZiAzMA0KY2FycyAlPiUNCiAgZ2dwbG90KGFlcyhob3JzZXB3cikpICsNCiAgZ2VvbV9oaXN0b2dyYW0oYmlud2lkdGggPSAzMCkgKw0KICBnZ3RpdGxlKCJEaXN0cmlidXRpb24gb2YgaG9yc2Vwb3dlcjogYmluZHdpZHRoIDMwIikNCg0KIyBDcmVhdGUgaGlzdCBvZiBob3JzZXB3ciB3aXRoIGJpbndpZHRoIG9mIDYwDQpjYXJzICU+JQ0KICBnZ3Bsb3QoYWVzKGhvcnNlcHdyKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDYwKSArDQogIGdndGl0bGUoIkRpc3RyaWJ1dGlvbiBvZiBob3JzZXBvd2VyOiBiaW5kd2lkdGggNjAiKQ0KYGBgDQpQbG90IEEgaXMgdGhlIG9ubHkgaGlzdG9ncmFtIHRoYXQgc2hvd3MgdGhlIGNvdW50IGZvciBjYXJzIHdpdGggZXhhY3RseSAyMDAgYW5kIDMwMCBob3JzZXBvd2VyLg0KDQojIyBCb3ggcGxvdHMNCg0KIyMjIEJveCBwbG90cyBmb3Igb3V0bGllcnMNCg0KSW4gYWRkaXRpb24gdG8gaW5kaWNhdGluZyB0aGUgY2VudGVyIGFuZCBzcHJlYWQgb2YgYSBkaXN0cmlidXRpb24sIGEgYm94IHBsb3QgcHJvdmlkZXMgYSBncmFwaGljYWwgbWVhbnMgdG8gZGV0ZWN0IG91dGxpZXJzLiBZb3UgY2FuIGFwcGx5IHRoaXMgbWV0aG9kIHRvIHRoZSBtc3JwIGNvbHVtbiAobWFudWZhY3R1cmVyJ3Mgc3VnZ2VzdGVkIHJldGFpbCBwcmljZSkgdG8gZGV0ZWN0IGlmIHRoZXJlIGFyZSB1bnVzdWFsbHkgZXhwZW5zaXZlIG9yIGNoZWFwIGNhcnMuDQpgYGB7cn0NCiMgQ29uc3RydWN0IGJveCBwbG90IG9mIG1zcnANCmNhcnMgJT4lDQogIGdncGxvdChhZXMoeCA9IDEsIHkgPSBtc3JwKSkgKw0KICBnZW9tX2JveHBsb3QoKQ0KDQojIEV4Y2x1ZGUgb3V0bGllcnMgZnJvbSBkYXRhDQpjYXJzX25vX291dCA8LSBjYXJzICU+JQ0KICBmaWx0ZXIobXNycCA8IDEwMDAwMCkNCg0KIyBDb25zdHJ1Y3QgYm94IHBsb3Qgb2YgbXNycCB1c2luZyB0aGUgcmVkdWNlZCBkYXRhc2V0DQpjYXJzX25vX291dCAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gMSwgeSA9IG1zcnApKSArDQogIGdlb21fYm94cGxvdCgpDQpgYGANCiMjIyBQbG90IHNlbGVjdGlvbg0KDQpDb25zaWRlciB0d28gb3RoZXIgY29sdW1ucyBpbiB0aGUgY2FycyBkYXRhc2V0OiBjaXR5X21wZyBhbmQgd2lkdGguIFdoaWNoIGlzIHRoZSBtb3N0IGFwcHJvcHJpYXRlIHBsb3QgZm9yIGRpc3BsYXlpbmcgdGhlIGltcG9ydGFudCBmZWF0dXJlcyBvZiB0aGVpciBkaXN0cmlidXRpb25zPyBSZW1lbWJlciwgYm90aCBkZW5zaXR5IHBsb3RzIGFuZCBib3ggcGxvdHMgZGlzcGxheSB0aGUgY2VudHJhbCB0ZW5kZW5jeSBhbmQgc3ByZWFkIG9mIHRoZSBkYXRhLCBidXQgdGhlIGJveCBwbG90IGlzIG1vcmUgcm9idXN0IHRvIG91dGxpZXJzLg0KYGBge3J9DQojIENyZWF0ZSBwbG90IG9mIGNpdHlfbXBnDQpjYXJzICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSAxLCB5ID0gY2l0eV9tcGcpKSArDQogIGdlb21fYm94cGxvdCgpDQoNCiMgQ3JlYXRlIHBsb3Qgb2Ygd2lkdGgNCmNhcnMgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSB3aWR0aCkpICsNCiAgZ2VvbV9kZW5zaXR5KCkNCmBgYA0KQmVjYXVzZSB0aGUgY2l0eV9tcGcgdmFyaWFibGUgaGFzIGEgbXVjaCB3aWRlciByYW5nZSB3aXRoIGl0cyBvdXRsaWVycywgaXQncyBiZXN0IHRvIGRpc3BsYXkgaXRzIGRpc3RyaWJ1dGlvbiBhcyBhIGJveCBwbG90Lg0KDQojIyBWaXN1YWxpemF0aW9uIGluIGhpZ2hlciBkaW1lbnNpb25zDQoNCiMjIyAzIHZhcmlhYmxlIHBsb3QNCg0KRmFjZXRpbmcgaXMgYSB2YWx1YWJsZSB0ZWNobmlxdWUgZm9yIGxvb2tpbmcgYXQgc2V2ZXJhbCBjb25kaXRpb25hbCBkaXN0cmlidXRpb25zIGF0IHRoZSBzYW1lIHRpbWUuIElmIHRoZSBmYWNldGVkIGRpc3RyaWJ1dGlvbnMgYXJlIGxhaWQgb3V0IGluIGEgZ3JpZCwgeW91IGNhbiBjb25zaWRlciB0aGUgYXNzb2NpYXRpb24gYmV0d2VlbiBhIHZhcmlhYmxlIGFuZCB0d28gb3RoZXJzLCBvbmUgb24gdGhlIHJvd3Mgb2YgdGhlIGdyaWQgYW5kIHRoZSBvdGhlciBvbiB0aGUgY29sdW1ucy4NCmBgYHtyfQ0KIyBGYWNldCBoaXN0cyB1c2luZyBod3kgbWlsZWFnZSBhbmQgbmN5bA0KY29tbW9uX2N5bCAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gaHd5X21wZykpICsNCiAgZ2VvbV9oaXN0b2dyYW0oKSArDQogIGZhY2V0X2dyaWQobmN5bCB+IHN1dikgKw0KICBnZ3RpdGxlKCJNaWxlYWdlIGJ5IHN1diBhbmQgbmN5bCIpDQpgYGANCg0KIyBOdW1lcmljYWwgU3VtbWFyaWVzDQoNCiMjIE1lYXN1cmVzIG9mIGNlbnRlcg0KDQojIyMgQ2FsY3VsYXRlIGNlbnRlciBtZWFzdXJlcw0KDQpUaHJvdWdob3V0IHRoaXMgY2hhcHRlciwgd2Ugd2lsbCB1c2UgZGF0YSBmcm9tIGdhcG1pbmRlciwgd2hpY2ggdHJhY2tzIGRlbW9ncmFwaGljIGRhdGEgaW4gY291bnRyaWVzIG9mIHRoZSB3b3JsZCBvdmVyIHRpbWUuIFRvIGxlYXJuIG1vcmUgYWJvdXQgaXQsIHlvdSBjYW4gYnJpbmcgdXAgdGhlIGhlbHAgZmlsZSB3aXRoID9nYXBtaW5kZXIuDQpXZSB3aWxsIGZvY3VzIG9uIGhvdyB0aGUgbGlmZSBleHBlY3RhbmN5IGRpZmZlcnMgZnJvbSBjb250aW5lbnQgdG8gY29udGluZW50LiBUaGlzIHJlcXVpcmVzIHRoYXQgd2UgY29uZHVjdCBvdXIgYW5hbHlzaXMgbm90IGF0IHRoZSBjb3VudHJ5IGxldmVsLCBidXQgYWdncmVnYXRlZCB1cCB0byB0aGUgY29udGluZW50IGxldmVsLiBUaGlzIGlzIG1hZGUgcG9zc2libGUgYnkgdGhlIG9uZS10d28gcHVuY2ggb2YgZ3JvdXBfYnkoKSBhbmQgc3VtbWFyaXplKCksIGEgdmVyeSBwb3dlcmZ1bCBzeW50YXggZm9yIGNhcnJ5aW5nIG91dCB0aGUgc2FtZSBhbmFseXNpcyBvbiBkaWZmZXJlbnQgc3Vic2V0cyBvZiB0aGUgZnVsbCBkYXRhc2V0Lg0KDQpgYGB7cn0NCmxpYnJhcnkoZ2FwbWluZGVyKQ0KYGBgDQpgYGB7cn0NCiMgQ3JlYXRlIGRhdGFzZXQgb2YgMjAwNyBkYXRhDQpnYXAyMDA3IDwtIGZpbHRlcihnYXBtaW5kZXIsIHllYXIgPT0gMjAwNykNCg0KIyBDb21wdXRlIGdyb3Vwd2lzZSBtZWFuIGFuZCBtZWRpYW4gbGlmZUV4cA0KZ2FwMjAwNyAlPiUNCiAgZ3JvdXBfYnkoY29udGluZW50KSAlPiUNCiAgc3VtbWFyaXplKG1lYW4obGlmZUV4cCksDQogICAgICAgICAgICBtZWRpYW4obGlmZUV4cCkpDQoNCiMgR2VuZXJhdGUgYm94IHBsb3RzIG9mIGxpZmVFeHAgZm9yIGVhY2ggY29udGluZW50DQpnYXAyMDA3ICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBjb250aW5lbnQsIHkgPSBsaWZlRXhwKSkgKw0KICBnZW9tX2JveHBsb3QoKQ0KYGBgDQojIyBNZWFzdXJlcyBvZiB2YXJpYWJpbGl0eQ0KDQojIyMgQ2FsY3VsYXRlIHNwcmVhZCBtZWFzdXJlcw0KDQpMZXQncyBleHRlbmQgdGhlIHBvd2VyZnVsIGdyb3VwX2J5KCkgYW5kIHN1bW1hcml6ZSgpIHN5bnRheCB0byBtZWFzdXJlcyBvZiBzcHJlYWQuIElmIHdlJ3JlIHVuc3VyZSB3aGV0aGVyIHdlJ3JlIHdvcmtpbmcgd2l0aCBzeW1tZXRyaWMgb3Igc2tld2VkIGRpc3RyaWJ1dGlvbnMsIGl0J3MgYSBnb29kIGlkZWEgdG8gY29uc2lkZXIgYSByb2J1c3QgbWVhc3VyZSBsaWtlIElRUiBpbiBhZGRpdGlvbiB0byB0aGUgdXN1YWwgbWVhc3VyZXMgb2YgdmFyaWFuY2Ugb3Igc3RhbmRhcmQgZGV2aWF0aW9uLg0KYGBge3J9DQojIENvbXB1dGUgZ3JvdXB3aXNlIG1lYXN1cmVzIG9mIHNwcmVhZA0KZ2FwMjAwNyAlPiUNCiAgZ3JvdXBfYnkoY29udGluZW50KSAlPiUNCiAgc3VtbWFyaXplKHNkKGxpZmVFeHApLA0KICAgICAgICAgICAgSVFSKGxpZmVFeHApLA0KICAgICAgICAgICAgbigpKQ0KDQojIEdlbmVyYXRlIG92ZXJsYWlkIGRlbnNpdHkgcGxvdHMNCmdhcDIwMDcgJT4lDQogIGdncGxvdChhZXMoeCA9IGxpZmVFeHAsIGZpbGwgPSBjb250aW5lbnQpKSArDQogIGdlb21fZGVuc2l0eShhbHBoYSA9IDAuMykNCmBgYA0KIyMjIENob29zZSBtZWFzdXJlcyBmb3IgY2VudGVyIGFuZCBzcHJlYWQNCg0KQ29uc2lkZXIgdGhlIGRlbnNpdHkgcGxvdHMgc2hvd24gaGVyZS4gV2hhdCBhcmUgdGhlIG1vc3QgYXBwcm9wcmlhdGUgbWVhc3VyZXMgdG8gZGVzY3JpYmUgdGhlaXIgY2VudGVycyBhbmQgc3ByZWFkcz8NCmBgYHtyfQ0KZ2FwMjAwNyAlPiUgDQogIGZpbHRlcihjb250aW5lbnQgPT0gIkFtZXJpY2FzIikgJT4lIA0KICBnZ3Bsb3QoYWVzKGxpZmVFeHApKSArDQogIGdlb21fZGVuc2l0eSgpDQoNCmdhcDIwMDcgJT4lIA0KICBmaWx0ZXIoY29udGluZW50ID09ICJBbWVyaWNhcyIpICU+JSANCiAgZ2dwbG90KGFlcyhwb3ApKSArDQogIGdlb21fZGVuc2l0eSgpDQpgYGANCmBgYHtyfQ0KIyBDb21wdXRlIHN0YXRzIGZvciBsaWZlRXhwIGluIEFtZXJpY2FzDQpnYXAyMDA3ICU+JQ0KICBmaWx0ZXIoY29udGluZW50ID09ICJBbWVyaWNhcyIpICU+JQ0KICBzdW1tYXJpemUobWVhbihsaWZlRXhwKSwNCiAgICAgICAgICAgIHNkKGxpZmVFeHApKQ0KDQojIENvbXB1dGUgc3RhdHMgZm9yIHBvcHVsYXRpb24NCmdhcDIwMDcgJT4lDQogIHN1bW1hcml6ZShtZWRpYW4ocG9wKSwNCiAgICAgICAgICAgIElRUihwb3ApKQ0KYGBgDQpMaWtlIG1lYW4gYW5kIHN0YW5kYXJkIGRldmlhdGlvbiwgbWVkaWFuIGFuZCBJUVIgbWVhc3VyZSB0aGUgY2VudHJhbCB0ZW5kZW5jeSBhbmQgc3ByZWFkLCByZXNwZWN0aXZlbHksIGJ1dCBhcmUgcm9idXN0IHRvIG91dGxpZXJzIGFuZCBub24tbm9ybWFsIGRhdGEuDQoNCiMjIFNoYXBlIGFuZCB0cmFuc2Zvcm1hdGlvbnMNCg0KIyMjIFRyYW5zZm9ybWF0aW9ucw0KDQoNCkhpZ2hseSBza2V3ZWQgZGlzdHJpYnV0aW9ucyBjYW4gbWFrZSBpdCB2ZXJ5IGRpZmZpY3VsdCB0byBsZWFybiBhbnl0aGluZyBmcm9tIGEgdmlzdWFsaXphdGlvbi4gVHJhbnNmb3JtYXRpb25zIGNhbiBiZSBoZWxwZnVsIGluIHJldmVhbGluZyB0aGUgbW9yZSBzdWJ0bGUgc3RydWN0dXJlLg0KYGBge3J9DQojIENyZWF0ZSBkZW5zaXR5IHBsb3Qgb2Ygb2xkIHZhcmlhYmxlDQpnYXAyMDA3ICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBwb3ApKSArDQogIGdlb21fZGVuc2l0eSgpDQoNCiMgVHJhbnNmb3JtIHRoZSBza2V3ZWQgcG9wIHZhcmlhYmxlDQpnYXAyMDA3IDwtIGdhcDIwMDcgJT4lDQogIG11dGF0ZShsb2dfcG9wID0gbG9nKHBvcCkpDQoNCiMgQ3JlYXRlIGRlbnNpdHkgcGxvdCBvZiBuZXcgdmFyaWFibGUNCmdhcDIwMDcgJT4lDQogIGdncGxvdChhZXMoeCA9IGxvZ19wb3ApKSArDQogIGdlb21fZGVuc2l0eSgpDQpgYGANCiMjIE91dGxpZXJzDQoNCiMjIyBJZGVudGlmeSBvdXRsaWVycw0KDQpDb25zaWRlciB0aGUgZGlzdHJpYnV0aW9uIG9mIHRoZSBsaWZlIGV4cGVjdGFuY2llcyBvZiB0aGUgY291bnRyaWVzIGluIEFzaWEuIA0KDQpgYGB7cn0NCmdhcDIwMDcgJT4lIA0KICBmaWx0ZXIoY29udGluZW50ID09ICJBc2lhIikgJT4lIA0KICBnZ3Bsb3QoYWVzKHggPSAxLCB5ID0gbGlmZUV4cCkpICsNCiAgZ2VvbV9ib3hwbG90KCkNCmBgYA0KVGhlIGJveCBwbG90IGlkZW50aWZpZXMgb25lIGNsZWFyIG91dGxpZXI6IGEgY291bnRyeSB3aXRoIGEgbm90YWJseSBsb3cgbGlmZSBleHBlY3RhbmN5LiBEbyB5b3UgaGF2ZSBhIGd1ZXNzIGFzIHRvIHdoaWNoIGNvdW50cnkgdGhpcyBtaWdodCBiZT8NCmBgYHtyfQ0KZ2FwMjAwNyAlPiUgDQogIGZpbHRlcihjb250aW5lbnQgPT0gIkFzaWEiKSAlPiUgDQogIGFycmFuZ2UobGlmZUV4cCkNCmBgYA0KYGBge3J9DQojIEZpbHRlciBmb3IgQXNpYSwgYWRkIGNvbHVtbiBpbmRpY2F0aW5nIG91dGxpZXJzDQpnYXBfYXNpYSA8LSBnYXAyMDA3ICU+JQ0KICBmaWx0ZXIoY29udGluZW50ID09ICJBc2lhIikgJT4lDQogIG11dGF0ZShpc19vdXRsaWVyID0gbGlmZUV4cCA8IDUwKQ0KDQojIFJlbW92ZSBvdXRsaWVycywgY3JlYXRlIGJveCBwbG90IG9mIGxpZmVFeHANCmdhcF9hc2lhICU+JQ0KICBmaWx0ZXIoIWlzX291dGxpZXIpICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSAxLCB5ID0gbGlmZUV4cCkpICsNCiAgZ2VvbV9ib3hwbG90KCkNCmBgYA0K