Chapter 12 Factors with forcats
Factors are used to work with categorical variables. Variables that have a fixed and known set of possible values. They are also useful when you want to dislay character vectors in a non-alphabetical order.
Prerequisites
library(tidyverse) library(forcats)
Functions and packages:
forcats factor fct_inorder levels readr::parse_factor fct_reorder fct_relevel fct_reorder2 fct_infreq fct_rev fct_recode fct_lump fct_collapse
Creating Factors
Using factors instead of strings (like when used in months) saves you from typos, and can be sorted properly.
library(tidyverse)
library(forcats)
x1 <- c("Dec","Apr","Jan","Mar")
x2 <- c("Dec","Apr","Jam","Mar")
sort(x1)
[1] "Apr" "Dec" "Jan" "Mar"
month_levels <- c("Jan","Feb", "Mar","Apr", "May","Jun","Jul","Aug","Sep","Oct","Nov","Dec")
y1 <- factor(x1, levels = month_levels)
y1
[1] Dec Apr Jan Mar
Levels: Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
Any values not in the set will be silently converted to NA
y1 <- factor(x2, levels = month_levels)
y1
[1] Dec Apr <NA> Mar
Levels: Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
If you want an error, you can use readr::parse_factor()
y1 <- parse_factor(x2, levels = month_levels)
1 parsing failure.
row col expected actual
3 -- value in level set Jam
y1
[1] Dec Apr <NA> Mar
attr(,"problems")
Levels: Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec
You can use unique() to preserve the order of appearance in the factors, or after the fact, with fct_inorder()
f1 <- factor(x1, levels = unique(x1))
f2 <- x1 %>% factor() %>% fct_inorder()
f1
[1] Dec Apr Jan Mar
Levels: Dec Apr Jan Mar
f2
[1] Dec Apr Jan Mar
Levels: Dec Apr Jan Mar
You can set the valid levels directly with levels()
levels(f2)
[1] "Dec" "Apr" "Jan" "Mar"
General Social Survey
Sample data from the general social survey (forcats::gss_cat) conducted by NORC at the Univesirty of Chicago. The survye has thousands of questions, so in gss_cat we selected a handful that will illustrate some common challenges you’ll encounter when working with factors:
gss_cat
Get more information about gss_cat with the ?
? gss_cat
gss_cat %>%
count(race)
ggplot(gss_cat, aes(race))+
geom_bar()

By default ggplot2 will drop levels that don’t have any values. You can force them to display with:
ggplot(gss_cat, aes(race)) +
geom_bar()+
scale_x_discrete(drop=FALSE)

When working with factors, the two most common operations are chanign the order of the levels and changing hte values of the levels. Those operations are described later.
Exercises
Explore the distribution of rincome (reported income). What makes the default bar chart hard to understand? How could you improve the plot?
ggplot(gss_cat, aes(rincome)) +
geom_bar() +
scale_x_discrete(drop = FALSE)

The default bar chart labels are too squished to read. One solution is to change the angle of the labels
ggplot(gss_cat, aes(rincome)) +
geom_bar() +
scale_x_discrete(drop = FALSE) +
theme(axis.text.x = element_text(angle = 90))

But that’s not natural either, because text is vertical, and we read horizontally. So with long labels, it is better to flip it.
ggplot(gss_cat, aes(rincome)) +
geom_bar() +
scale_x_discrete(drop = FALSE) +
coord_flip()

What is the most common relig in this survey? What’s the most common partyid?
gss_cat %>%
count(relig) %>%
arrange(-n) %>%
head(3)
gss_cat %>%
count(partyid) %>%
arrange(-n) %>%
head(3)
Which relig does denom (denomination) apply to? How can you find out with a table? How can you find out with a visualisation?
levels(gss_cat$denom)
[1] "No answer" "Don't know" "No denomination" "Other"
[5] "Episcopal" "Presbyterian-dk wh" "Presbyterian, merged" "Other presbyterian"
[9] "United pres ch in us" "Presbyterian c in us" "Lutheran-dk which" "Evangelical luth"
[13] "Other lutheran" "Wi evan luth synod" "Lutheran-mo synod" "Luth ch in america"
[17] "Am lutheran" "Methodist-dk which" "Other methodist" "United methodist"
[21] "Afr meth ep zion" "Afr meth episcopal" "Baptist-dk which" "Other baptists"
[25] "Southern baptist" "Nat bapt conv usa" "Nat bapt conv of am" "Am bapt ch in usa"
[29] "Am baptist asso" "Not applicable"
gss_cat %>%
filter(!denom %in% c("No answer", "Other", "Don't know", "Not applicable",
"No denomination")) %>%
count(relig)
This is also clear in a scatter plot of relig vs. denom where the points are proportional to the size of the number of answers (since otherwise there would be overplotting).
gss_cat %>%
count(relig, denom) %>%
ggplot(aes(x = relig, y = denom, size = n)) +
geom_point() +
theme(axis.text.x = element_text(angle = 90))

Modifying Factor Order
We can reorder the levels of a factor with the fct_reorder(). It takes 3 arguments: f the factor whose levels you want to modify x, a numeric vector that you want to use to reorder the levels, fun, function that’s used if there armultiple values of x for each value of f. The default value is median.
relig <- gss_cat %>%
group_by(relig) %>%
summarize(
age = mean(age, na.rm=TRUE),
tvhours = mean(tvhours, na.rm = TRUE),
n =n()
)
ggplot(relig, aes(tvhours, relig)) +
geom_point()

It is difficult to see a pattern here. Use fct_reorder to arrange the results
ggplot(relig, aes(tvhours, fct_reorder(relig, tvhours))) +
geom_point()

It is best practice to move out the fct_reorder of AES and into mutate()
relig %>%
mutate(relig = fct_reorder(relig, tvhours)) %>%
ggplot(aes(tvhours, relig)) +
geom_point()

rincomes <- gss_cat %>%
group_by(rincome) %>%
summarize(
age = mean(age, na.rm=TRUE),
tvhours = mean(tvhours, na.rm=TRUE),
n = n()
)
ggplot(rincomes, aes(age,fct_reorder(rincome,age))) +
geom_point()

Sometimes, arbitrarily reordering the levels might not be a good idea. Reserve fct_reorder() for factors whose levels are arbitrarily ordered. However, it does make sense to pull “NOt applicable” to the front with the other special levels. You can use fct_relevel(). It takes a factor, f, an dthen any number of levels that you want to move to the front of the line.
ggplot(
rincome,
aes(age, fct_relevel(rincome, "Not applicable"))) +
geom_point()

Another type of reordering is useful when you are coloring the lines on a plot. fct_reorder2() reorders the factor by the y values associated with the largest x values. This makes the plot easier to read because th eline colors line up with the legend.
by_age <- gss_cat %>%
filter(!is.na(age)) %>%
group_by(age, marital) %>%
count() %>%
mutate(prop = n / sum(n))
ggplot(by_age, aes(age, prop, color = marital)) +
geom_line(na.rm = TRUE)

ggplot(by_age, aes(age, prop, color = fct_reorder2(marital,age,prop))) +
geom_line() +
labs(color = "marital")

Finally for bar plots, you can use fct_infreq() to order levels in increasing frequeny. This is the simplest type of reordering because it doesnt need any extra variables. You may want to combine with fct_rev()
gss_cat %>%
mutate(marital = marital %>% fct_infreq() %>% fct_rev()) %>%
ggplot(aes(marital)) +
geom_bar()

Exercise
There are some suspiciously high numbers in tvhours. Is the mean a good summary?
summary(gss_cat[["tvhours"]])
Min. 1st Qu. Median Mean 3rd Qu. Max. NA's
0.000 1.000 2.000 2.981 4.000 24.000 10146
gss_cat %>%
filter(!is.na(tvhours)) %>%
ggplot(aes(x = tvhours)) +
geom_histogram(binwidth = 1)

For each factor in gss_cat identify whether the order of the levels is arbitrary or principled. The following piece of code uses functions covered in Ch 21, to print out the names of only the factors
keep(gss_cat, is.factor) %>% names()
[1] "marital" "race" "rincome" "partyid" "relig" "denom"
levels(gss_cat[["marital"]])
[1] "No answer" "Never married" "Separated" "Divorced" "Widowed" "Married"
gss_cat %>%
ggplot(aes(x = marital)) +
geom_bar()

levels(gss_cat$race)
[1] "Other" "Black" "White" "Not applicable"
gss_cat %>%
ggplot(aes(race)) +
geom_bar(drop = FALSE)
Ignoring unknown parameters: drop

The levels of rincome are ordered in decreasing order of the income; however the placement of “No answer”, “Don’t know”, and “Refused” before, and “Not applicable” after the income levels is arbitrary. It would be better to place all the missing income level categories either before or after all the known values.
levels(gss_cat$rincome)
[1] "No answer" "Don't know" "Refused" "$25000 or more" "$20000 - 24999" "$15000 - 19999"
[7] "$10000 - 14999" "$8000 to 9999" "$7000 to 7999" "$6000 to 6999" "$5000 to 5999" "$4000 to 4999"
[13] "$3000 to 3999" "$1000 to 2999" "Lt $1000" "Not applicable"
The levels of relig is arbitrary: there is no natural ordering, and they don’t appear to be ordered by stats within the dataset.
levels(gss_cat$relig)
[1] "No answer" "Don't know" "Inter-nondenominational" "Native american"
[5] "Christian" "Orthodox-christian" "Moslem/islam" "Other eastern"
[9] "Hinduism" "Buddhism" "Other" "None"
[13] "Jewish" "Catholic" "Protestant" "Not applicable"
# horizontal bar
gss_cat %>%
ggplot(aes(relig)) +
geom_bar() +
coord_flip()

Why did moving “Not applicable” to the front of the levels move it to the bottom of the plot? Because that gives the level “Not applicable” an integer value of 1.
Modifying Factor Levels
More powerful than changing the order of the levels is changing their values. Use fct_recode()
gss_cat %>%
count(partyid)
Let us change these to be longer and use a parallel construction
gss_cat %>%
mutate(partyid = fct_recode(partyid,
"Republican, strong" = "Strong republican",
"Republican, weak" = "Not str republican",
"Independent, near rep" = "Ind,near rep",
"Independent, near dem" = "Ind,near dem",
"Democrat, weak" = "Not str democrat",
"Democrat, strong" = "Strong democrat")) %>%
count(partyid)
Fct_recode() will leave levels that aren’t explicitly mentioned as is, and will warn you if you accidentally refer to a level that doesnt exist. To combine groups, you can assign multiple old levels to the same new level.
gss_cat %>%
mutate(partyid = fct_recode(partyid,
"Republican, strong" = "Strong republican",
"Republican, weak" = "Not str republican",
"Independent, near rep" = "Ind,near rep",
"Independent, near dem" = "Ind,near dem",
"Democrat, weak" = "Not str democrat",
"Democrat, strong" = "Strong democrat",
"Other" = "No answer",
"Other" = "Don't know",
"Other" = "Other party")) %>%
count(partyid)
If you want to collapse a lot of levels, fct_collapse() is a useful variant of fct_recode(). For each new variable, you can provide a vector of old levels.
gss_cat %>%
mutate(partyid = fct_collapse(partyid,
other = c("No answer", "Don't know", "Other party"),
rep = c("Strong republican", "Not str republican"),
ind = c("Ind,near rep", "Independent", "Ind,near dem"),
dem = c("Not str democrat", "Strong democrat")
)) %>%
count(partyid)
Sometimes you just want to lump together all the small groups to make a plot or table simpler. That is the job of fct_lump().
gss_cat %>%
mutate(relig = fct_lump(relig)) %>%
count(relig)
The default behavior is to progressively lump together the smallest groups, ensuring that the aggregate is still the smallest group. If we want to control the level of lumping, use the n parameter to specify how many groups (excluding other) we want to keep
gss_cat %>%
mutate(relig = fct_lump(relig, n =10)) %>%
count(relig, sort = TRUE) %>%
print(n = Inf)
Exercises
How have the proportions of people identifying as Democrat, Republican, and Independent changed over time?
levels(gss_cat$partyid)
[1] "No answer" "Don't know" "Other party" "Strong republican"
[5] "Not str republican" "Ind,near rep" "Independent" "Ind,near dem"
[9] "Not str democrat" "Strong democrat"
gss_cat %>%
mutate(partyid =
fct_collapse(partyid,
other = c("No answer", "Don't know", "Other party"),
rep = c("Strong republican", "Not str republican"),
ind = c("Ind,near rep", "Independent", "Ind,near dem"),
dem = c("Not str democrat", "Strong democrat"))) %>%
count(year, partyid) %>%
group_by(year) %>%
mutate(p = n / sum(n)) %>%
ggplot(aes(x = year, y = p,
colour = fct_reorder2(partyid, year, p))) +
geom_point() +
geom_line() +
labs(colour = "Party ID.")

NA
How could you collapse rincome into a small set of categories? Group all the non-responses into one category, and then group other categories into a smaller number. Since there is a clear ordering, we wouldn’t want to use something like fct_lump.
library("stringr")
gss_cat %>%
mutate(rincome =
fct_collapse(
rincome,
`Unknown` = c("No answer", "Don't know", "Refused", "Not applicable"),
`Lt $5000` = c("Lt $1000", str_c("$", c("1000", "3000", "4000"),
" to ", c("2999", "3999", "4999"))),
`$5000 to 10000` = str_c("$", c("5000", "6000", "7000", "8000"),
" to ", c("5999", "6999", "7999", "9999"))
)) %>%
ggplot(aes(x = rincome)) +
geom_bar() +
coord_flip()

LS0tDQp0aXRsZTogIlIgRm9yIERhdGEgU2NpZW5jZSBDaGFwdGVyIDEyIg0Kb3V0cHV0OiBodG1sX25vdGVib29rDQotLS0NCg0KPGgxPiBDaGFwdGVyIDEyIEZhY3RvcnMgd2l0aCBmb3JjYXRzIDwvaDE+DQoNCkZhY3RvcnMgYXJlIHVzZWQgdG8gd29yayB3aXRoIGNhdGVnb3JpY2FsIHZhcmlhYmxlcy4gVmFyaWFibGVzIHRoYXQgaGF2ZSBhIGZpeGVkIGFuZCBrbm93biBzZXQgb2YgcG9zc2libGUgdmFsdWVzLiBUaGV5IGFyZSBhbHNvIHVzZWZ1bCB3aGVuIHlvdSB3YW50IHRvIGRpc2xheSBjaGFyYWN0ZXIgdmVjdG9ycyBpbiBhIG5vbi1hbHBoYWJldGljYWwgb3JkZXIuDQoNCjxoMj4gUHJlcmVxdWlzaXRlcyA8L2gyPg0KbGlicmFyeSh0aWR5dmVyc2UpIDwvYnI+DQpsaWJyYXJ5KGZvcmNhdHMpIDwvcD4NCg0KPGgzPiBGdW5jdGlvbnMgYW5kIHBhY2thZ2VzOjwvaDM+DQoNCjxiPmZvcmNhdHM8L2I+PC9icj4NCmZhY3RvcjwvYnI+DQpmY3RfaW5vcmRlcjwvYnI+DQpsZXZlbHM8L2JyPg0KcmVhZHI6OnBhcnNlX2ZhY3RvcjwvYnI+DQpmY3RfcmVvcmRlcjwvYnI+DQpmY3RfcmVsZXZlbDwvYnI+DQpmY3RfcmVvcmRlcjI8L2JyPg0KZmN0X2luZnJlcTwvYnI+DQpmY3RfcmV2PC9icj4NCmZjdF9yZWNvZGU8L2JyPg0KZmN0X2x1bXA8L2JyPg0KZmN0X2NvbGxhcHNlPC9wPg0KDQo8aDI+IENyZWF0aW5nIEZhY3RvcnMgPC9oMj4NCg0KVXNpbmcgZmFjdG9ycyBpbnN0ZWFkIG9mIHN0cmluZ3MgKGxpa2Ugd2hlbiB1c2VkIGluIG1vbnRocykgc2F2ZXMgeW91IGZyb20gdHlwb3MsIGFuZCBjYW4gYmUgc29ydGVkIHByb3Blcmx5Lg0KDQpgYGB7cn0NCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShmb3JjYXRzKQ0KDQp4MSA8LSBjKCJEZWMiLCJBcHIiLCJKYW4iLCJNYXIiKQ0KeDIgPC0gYygiRGVjIiwiQXByIiwiSmFtIiwiTWFyIikNCnNvcnQoeDEpDQoNCm1vbnRoX2xldmVscyA8LSAgYygiSmFuIiwiRmViIiwgIk1hciIsIkFwciIsICJNYXkiLCJKdW4iLCJKdWwiLCJBdWciLCJTZXAiLCJPY3QiLCJOb3YiLCJEZWMiKQ0KDQp5MSA8LSBmYWN0b3IoeDEsIGxldmVscyA9IG1vbnRoX2xldmVscykNCnkxDQoNCmBgYA0KDQpBbnkgdmFsdWVzIG5vdCBpbiB0aGUgc2V0IHdpbGwgYmUgc2lsZW50bHkgY29udmVydGVkIHRvIE5BDQoNCmBgYHtyfQ0KeTEgPC0gZmFjdG9yKHgyLCBsZXZlbHMgPSBtb250aF9sZXZlbHMpDQp5MQ0KYGBgDQoNCklmIHlvdSB3YW50IGFuIGVycm9yLCB5b3UgY2FuIHVzZSByZWFkcjo6cGFyc2VfZmFjdG9yKCkNCg0KYGBge3J9DQp5MSA8LSBwYXJzZV9mYWN0b3IoeDIsIGxldmVscyA9IG1vbnRoX2xldmVscykNCnkxDQpgYGANCg0KWW91IGNhbiB1c2UgdW5pcXVlKCkgdG8gcHJlc2VydmUgdGhlIG9yZGVyIG9mIGFwcGVhcmFuY2UgaW4gdGhlIGZhY3RvcnMsIG9yIGFmdGVyIHRoZSBmYWN0LCB3aXRoIGZjdF9pbm9yZGVyKCkNCg0KYGBge3J9DQpmMSA8LSBmYWN0b3IoeDEsIGxldmVscyA9IHVuaXF1ZSh4MSkpDQpmMiA8LSAgeDEgJT4lIGZhY3RvcigpICU+JSBmY3RfaW5vcmRlcigpDQpmMQ0KZjINCg0KYGBgDQoNCllvdSBjYW4gc2V0IHRoZSB2YWxpZCBsZXZlbHMgZGlyZWN0bHkgd2l0aCBsZXZlbHMoKQ0KDQpgYGB7cn0NCmxldmVscyhmMikNCmBgYA0KDQo8aDI+IEdlbmVyYWwgU29jaWFsIFN1cnZleSA8L2gyPg0KU2FtcGxlIGRhdGEgZnJvbSB0aGUgZ2VuZXJhbCBzb2NpYWwgc3VydmV5IChmb3JjYXRzOjpnc3NfY2F0KSBjb25kdWN0ZWQgYnkgTk9SQyBhdCB0aGUgVW5pdmVzaXJ0eSBvZiBDaGljYWdvLiBUaGUgc3VydnllIGhhcyB0aG91c2FuZHMgb2YgcXVlc3Rpb25zLCBzbyBpbiBnc3NfY2F0IHdlIHNlbGVjdGVkIGEgaGFuZGZ1bCB0aGF0IHdpbGwgaWxsdXN0cmF0ZSBzb21lIGNvbW1vbiBjaGFsbGVuZ2VzIHlvdSdsbCBlbmNvdW50ZXIgd2hlbiB3b3JraW5nIHdpdGggZmFjdG9yczoNCg0KYGBge3J9DQpnc3NfY2F0DQoNCmBgYA0KR2V0IG1vcmUgaW5mb3JtYXRpb24gYWJvdXQgZ3NzX2NhdCB3aXRoIHRoZSA/DQoNCmBgYHtyfQ0KP2dzc19jYXQNCmBgYA0KDQoNCmBgYHtyfQ0KZ3NzX2NhdCAlPiUNCiAgY291bnQocmFjZSkNCmBgYA0KDQogDQpgYGB7cn0NCmdncGxvdChnc3NfY2F0LCBhZXMocmFjZSkpICsNCiAgZ2VvbV9iYXIoKQ0KDQpgYGANCkJ5IGRlZmF1bHQgZ2dwbG90MiB3aWxsIGRyb3AgbGV2ZWxzIHRoYXQgZG9uJ3QgaGF2ZSBhbnkgdmFsdWVzLiBZb3UgY2FuIGZvcmNlIHRoZW0gdG8gZGlzcGxheSB3aXRoOg0KDQpgYGB7cn0NCmdncGxvdChnc3NfY2F0LCBhZXMocmFjZSkpICsNCiAgZ2VvbV9iYXIoKSArDQogIHNjYWxlX3hfZGlzY3JldGUoZHJvcCA9IEZBTFNFKQ0KYGBgDQoNCg0KV2hlbiB3b3JraW5nIHdpdGggZmFjdG9ycywgdGhlIHR3byBtb3N0IGNvbW1vbiBvcGVyYXRpb25zIGFyZSBjaGFuaWduIHRoZSBvcmRlciBvZiB0aGUgbGV2ZWxzIGFuZCBjaGFuZ2luZyBodGUgdmFsdWVzIG9mIHRoZSBsZXZlbHMuIFRob3NlIG9wZXJhdGlvbnMgYXJlIGRlc2NyaWJlZCBsYXRlci4NCg0KPGgzPiBFeGVyY2lzZXMgPC9oMz4NCg0KRXhwbG9yZSB0aGUgZGlzdHJpYnV0aW9uIG9mIHJpbmNvbWUgKHJlcG9ydGVkIGluY29tZSkuIFdoYXQgbWFrZXMgdGhlIGRlZmF1bHQgYmFyIGNoYXJ0IGhhcmQgdG8gdW5kZXJzdGFuZD8gSG93IGNvdWxkIHlvdSBpbXByb3ZlIHRoZSBwbG90Pw0KDQpgYGB7cn0NCmdncGxvdChnc3NfY2F0LCBhZXMocmluY29tZSkpICsNCiAgZ2VvbV9iYXIoKSArDQogIHNjYWxlX3hfZGlzY3JldGUoZHJvcCA9IEZBTFNFKQ0KYGBgDQoNClRoZSBkZWZhdWx0IGJhciBjaGFydCBsYWJlbHMgYXJlIHRvbyBzcXVpc2hlZCB0byByZWFkLiBPbmUgc29sdXRpb24gaXMgdG8gY2hhbmdlIHRoZSBhbmdsZSBvZiB0aGUgbGFiZWxzDQoNCmBgYHtyfQ0KZ2dwbG90KGdzc19jYXQsIGFlcyhyaW5jb21lKSkgKw0KICBnZW9tX2JhcigpICsNCiAgc2NhbGVfeF9kaXNjcmV0ZShkcm9wID0gRkFMU0UpICsNCiAgdGhlbWUoYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSA5MCkpDQpgYGANCg0KDQoNCkJ1dCB0aGF0J3Mgbm90IG5hdHVyYWwgZWl0aGVyLCBiZWNhdXNlIHRleHQgaXMgdmVydGljYWwsIGFuZCB3ZSByZWFkIGhvcml6b250YWxseS4gU28gd2l0aCBsb25nIGxhYmVscywgaXQgaXMgYmV0dGVyIHRvIGZsaXAgaXQuDQoNCmBgYHtyfQ0KZ2dwbG90KGdzc19jYXQsIGFlcyhyaW5jb21lKSkgKw0KICBnZW9tX2JhcigpICsNCiAgc2NhbGVfeF9kaXNjcmV0ZShkcm9wID0gRkFMU0UpICsNCiAgY29vcmRfZmxpcCgpDQpgYGANCg0KV2hhdCBpcyB0aGUgbW9zdCBjb21tb24gcmVsaWcgaW4gdGhpcyBzdXJ2ZXk/IFdoYXQncyB0aGUgbW9zdCBjb21tb24gcGFydHlpZD8NCg0KYGBge3J9DQpnc3NfY2F0ICU+JQ0KICBjb3VudChyZWxpZykgJT4lDQogIGFycmFuZ2UoLW4pICU+JQ0KICBoZWFkKDMpDQpgYGANCg0KYGBge3J9DQpnc3NfY2F0ICU+JQ0KICBjb3VudChwYXJ0eWlkKSAlPiUNCiAgYXJyYW5nZSgtbikgJT4lDQogIGhlYWQoMykNCmBgYA0KV2hpY2ggcmVsaWcgZG9lcyBkZW5vbSAoZGVub21pbmF0aW9uKSBhcHBseSB0bz8gSG93IGNhbiB5b3UgZmluZCBvdXQgd2l0aCBhIHRhYmxlPyBIb3cgY2FuIHlvdSBmaW5kIG91dCB3aXRoIGEgdmlzdWFsaXNhdGlvbj8NCg0KDQoNCmBgYHtyfQ0KbGV2ZWxzKGdzc19jYXQkZGVub20pDQoNCmBgYA0KDQoNCmBgYHtyfQ0KZ3NzX2NhdCAlPiUNCiAgZmlsdGVyKCFkZW5vbSAlaW4lIGMoIk5vIGFuc3dlciIsICJPdGhlciIsICJEb24ndCBrbm93IiwgIk5vdCBhcHBsaWNhYmxlIiwNCiAgICAgICAgICAgICAgICAgICAgICAgIk5vIGRlbm9taW5hdGlvbiIpKSAlPiUNCiAgY291bnQocmVsaWcpDQpgYGANCg0KDQpUaGlzIGlzIGFsc28gY2xlYXIgaW4gYSBzY2F0dGVyIHBsb3Qgb2YgcmVsaWcgdnMuIGRlbm9tIHdoZXJlIHRoZSBwb2ludHMgYXJlIHByb3BvcnRpb25hbCB0byB0aGUgc2l6ZSBvZiB0aGUgbnVtYmVyIG9mIGFuc3dlcnMgKHNpbmNlIG90aGVyd2lzZSB0aGVyZSB3b3VsZCBiZSBvdmVycGxvdHRpbmcpLg0KDQpgYGB7cn0NCmdzc19jYXQgJT4lDQogIGNvdW50KHJlbGlnLCBkZW5vbSkgJT4lDQogIGdncGxvdChhZXMoeCA9IHJlbGlnLCB5ID0gZGVub20sIHNpemUgPSBuKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICB0aGVtZShheGlzLnRleHQueCA9IGVsZW1lbnRfdGV4dChhbmdsZSA9IDkwKSkNCmBgYA0KDQo8aDI+IE1vZGlmeWluZyBGYWN0b3IgT3JkZXIgPC9oMj4NCldlIGNhbiByZW9yZGVyIHRoZSBsZXZlbHMgb2YgYSBmYWN0b3Igd2l0aCB0aGUgZmN0X3Jlb3JkZXIoKS4gSXQgdGFrZXMgMyBhcmd1bWVudHM6IA0KZiB0aGUgZmFjdG9yIHdob3NlIGxldmVscyB5b3Ugd2FudCB0byBtb2RpZnkgPC9icj4NCngsIGEgbnVtZXJpYyB2ZWN0b3IgdGhhdCB5b3Ugd2FudCB0byB1c2UgdG8gcmVvcmRlciB0aGUgbGV2ZWxzLCA8L2JyPg0KZnVuLCBmdW5jdGlvbiB0aGF0J3MgdXNlZCBpZiB0aGVyZSBhcm11bHRpcGxlIHZhbHVlcyBvZiB4IGZvciBlYWNoIHZhbHVlIG9mIGYuIFRoZSBkZWZhdWx0IHZhbHVlIGlzIG1lZGlhbi4NCg0KYGBge3J9DQpyZWxpZyA8LSAgZ3NzX2NhdCAlPiUNCiAgZ3JvdXBfYnkocmVsaWcpICU+JQ0KICBzdW1tYXJpemUoDQogICAgYWdlID0gbWVhbihhZ2UsIG5hLnJtPVRSVUUpLA0KICAgIHR2aG91cnMgPSBtZWFuKHR2aG91cnMsIG5hLnJtID0gVFJVRSksDQogICAgbiA9bigpDQogICkNCg0KZ2dwbG90KHJlbGlnLCBhZXModHZob3VycywgcmVsaWcpKSArDQogIGdlb21fcG9pbnQoKQ0KYGBgDQoNCkl0IGlzIGRpZmZpY3VsdCB0byBzZWUgYSBwYXR0ZXJuIGhlcmUuIFVzZSBmY3RfcmVvcmRlciB0byBhcnJhbmdlIHRoZSByZXN1bHRzIA0KDQoNCmBgYHtyfQ0KZ2dwbG90KHJlbGlnLCBhZXModHZob3VycywgZmN0X3Jlb3JkZXIocmVsaWcsIHR2aG91cnMpKSkgKw0KICBnZW9tX3BvaW50KCkNCmBgYA0KDQpJdCBpcyBiZXN0IHByYWN0aWNlIHRvIG1vdmUgb3V0IHRoZSBmY3RfcmVvcmRlciBvZiBBRVMgYW5kIGludG8gbXV0YXRlKCkNCg0KYGBge3J9DQpyZWxpZyAlPiUNCiAgbXV0YXRlKHJlbGlnID0gZmN0X3Jlb3JkZXIocmVsaWcsIHR2aG91cnMpKSAlPiUNCiAgZ2dwbG90KGFlcyh0dmhvdXJzLCByZWxpZykpICsNCiAgZ2VvbV9wb2ludCgpDQoNCmBgYA0KDQoNCmBgYHtyfQ0KcmluY29tZXMgPC0gIGdzc19jYXQgJT4lDQogIGdyb3VwX2J5KHJpbmNvbWUpICU+JQ0KICBzdW1tYXJpemUoDQogICAgYWdlID0gbWVhbihhZ2UsIG5hLnJtID1UUlVFKSwNCiAgICB0dmhvdXJzID0gbWVhbih0dmhvdXJzLCBuYS5ybSA9VFJVRSksDQogICAgbiA9IG4oKQ0KICApDQpnZ3Bsb3QocmluY29tZXMsIGFlcyhhZ2UsZmN0X3Jlb3JkZXIocmluY29tZSxhZ2UpKSkgKw0KICAgIGdlb21fcG9pbnQoKQ0KDQpgYGANCg0KDQoNClNvbWV0aW1lcywgYXJiaXRyYXJpbHkgcmVvcmRlcmluZyB0aGUgbGV2ZWxzIG1pZ2h0IG5vdCBiZSBhIGdvb2QgaWRlYS4gUmVzZXJ2ZSBmY3RfcmVvcmRlcigpIGZvciBmYWN0b3JzIHdob3NlIGxldmVscyBhcmUgYXJiaXRyYXJpbHkgb3JkZXJlZC4gSG93ZXZlciwgaXQgZG9lcyBtYWtlIHNlbnNlIHRvIHB1bGwgIk5PdCBhcHBsaWNhYmxlIiB0byB0aGUgZnJvbnQgd2l0aCB0aGUgb3RoZXIgc3BlY2lhbCBsZXZlbHMuIFlvdSBjYW4gdXNlIGZjdF9yZWxldmVsKCkuIEl0IHRha2VzIGEgZmFjdG9yLCBmLCBhbiBkdGhlbiBhbnkgbnVtYmVyIG9mIGxldmVscyB0aGF0IHlvdSB3YW50IHRvIG1vdmUgdG8gdGhlIGZyb250IG9mIHRoZSBsaW5lLg0KDQpgYGB7cn0NCmdncGxvdCgNCiAgcmluY29tZSwNCiAgYWVzKGFnZSwgZmN0X3JlbGV2ZWwocmluY29tZSwgIk5vdCBhcHBsaWNhYmxlIikpKSArDQogIGdlb21fcG9pbnQoKQ0KYGBgDQoNCkFub3RoZXIgdHlwZSBvZiByZW9yZGVyaW5nIGlzIHVzZWZ1bCB3aGVuIHlvdSBhcmUgY29sb3JpbmcgdGhlIGxpbmVzIG9uIGEgcGxvdC4gZmN0X3Jlb3JkZXIyKCkgcmVvcmRlcnMgdGhlIGZhY3RvciBieSB0aGUgeSB2YWx1ZXMgYXNzb2NpYXRlZCB3aXRoIHRoZSBsYXJnZXN0IHggdmFsdWVzLiBUaGlzIG1ha2VzIHRoZSBwbG90IGVhc2llciB0byByZWFkIGJlY2F1c2UgdGggZWxpbmUgY29sb3JzIGxpbmUgdXAgd2l0aCB0aGUgbGVnZW5kLiANCg0KYGBge3J9DQpieV9hZ2UgPC0gIGdzc19jYXQgJT4lDQogIGZpbHRlcighaXMubmEoYWdlKSkgJT4lDQogIGdyb3VwX2J5KGFnZSwgbWFyaXRhbCkgJT4lDQogIGNvdW50KCkgJT4lDQogIG11dGF0ZShwcm9wID0gbiAvIHN1bShuKSkNCg0KZ2dwbG90KGJ5X2FnZSwgYWVzKGFnZSwgcHJvcCwgY29sb3IgPSBtYXJpdGFsKSkgKw0KICBnZW9tX2xpbmUobmEucm0gPSBUUlVFKQ0KDQpnZ3Bsb3QoYnlfYWdlLCBhZXMoYWdlLCBwcm9wLCBjb2xvciA9IGZjdF9yZW9yZGVyMihtYXJpdGFsLGFnZSxwcm9wKSkpICsNCiAgZ2VvbV9saW5lKCkgKyANCiAgbGFicyhjb2xvciA9ICJtYXJpdGFsIikNCmBgYA0KDQoNCkZpbmFsbHkgZm9yIGJhciBwbG90cywgeW91IGNhbiB1c2UgZmN0X2luZnJlcSgpIHRvIG9yZGVyIGxldmVscyBpbiBpbmNyZWFzaW5nIGZyZXF1ZW55LiBUaGlzIGlzIHRoZSBzaW1wbGVzdCB0eXBlIG9mIHJlb3JkZXJpbmcgYmVjYXVzZSBpdCBkb2VzbnQgbmVlZCBhbnkgZXh0cmEgdmFyaWFibGVzLiBZb3UgbWF5IHdhbnQgdG8gY29tYmluZSB3aXRoIGZjdF9yZXYoKQ0KDQpgYGB7cn0NCmdzc19jYXQgJT4lDQogIG11dGF0ZShtYXJpdGFsID0gbWFyaXRhbCAlPiUgZmN0X2luZnJlcSgpICU+JSBmY3RfcmV2KCkpICU+JQ0KICBnZ3Bsb3QoYWVzKG1hcml0YWwpKSArDQogIGdlb21fYmFyKCkNCmBgYA0KDQoNCjxoMz4gRXhlcmNpc2UgPGgzPg0KVGhlcmUgYXJlIHNvbWUgc3VzcGljaW91c2x5IGhpZ2ggbnVtYmVycyBpbiB0dmhvdXJzLiBJcyB0aGUgbWVhbiBhIGdvb2Qgc3VtbWFyeT8NCg0KYGBge3J9DQpzdW1tYXJ5KGdzc19jYXRbWyJ0dmhvdXJzIl1dKQ0KYGBgDQoNCg0KDQpgYGB7cn0NCmdzc19jYXQgJT4lDQogIGZpbHRlcighaXMubmEodHZob3VycykpICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSB0dmhvdXJzKSkgKw0KICBnZW9tX2hpc3RvZ3JhbShiaW53aWR0aCA9IDEpDQpgYGANCg0KDQpGb3IgZWFjaCBmYWN0b3IgaW4gZ3NzX2NhdCBpZGVudGlmeSB3aGV0aGVyIHRoZSBvcmRlciBvZiB0aGUgbGV2ZWxzIGlzIGFyYml0cmFyeSBvciBwcmluY2lwbGVkLg0KVGhlIGZvbGxvd2luZyBwaWVjZSBvZiBjb2RlIHVzZXMgZnVuY3Rpb25zIGNvdmVyZWQgaW4gQ2ggMjEsIHRvIHByaW50IG91dCB0aGUgbmFtZXMgb2Ygb25seSB0aGUgZmFjdG9ycw0KDQoNCmBgYHtyfQ0KIyB0byBwcmludCBvdXQgdGhlIG5hbWVzIG9mIG9ubHkgdGhlIGZhY3RvcnMNCiMgc2VlIGNoYXB0ZXIgMjENCmtlZXAoZ3NzX2NhdCwgaXMuZmFjdG9yKSAlPiUgbmFtZXMoKQ0KDQpgYGANCg0KYGBge3J9DQpsZXZlbHMoZ3NzX2NhdFtbIm1hcml0YWwiXV0pDQoNCmBgYA0KDQoNCmBgYHtyfQ0KZ3NzX2NhdCAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gbWFyaXRhbCkpICsNCiAgZ2VvbV9iYXIoKQ0KYGBgDQoNCg0KDQpgYGB7cn0NCmxldmVscyhnc3NfY2F0JHJhY2UpDQpgYGANCg0KYGBge3J9DQpnc3NfY2F0ICU+JQ0KICBnZ3Bsb3QoYWVzKHJhY2UpKSArDQogIGdlb21fYmFyKGRyb3AgPSBGQUxTRSkNCmBgYA0KDQoNClRoZSBsZXZlbHMgb2YgcmluY29tZSBhcmUgb3JkZXJlZCBpbiBkZWNyZWFzaW5nIG9yZGVyIG9mIHRoZSBpbmNvbWU7IGhvd2V2ZXIgdGhlIHBsYWNlbWVudCBvZiAiTm8gYW5zd2VyIiwgIkRvbid0IGtub3ciLCBhbmQgIlJlZnVzZWQiIGJlZm9yZSwgYW5kICJOb3QgYXBwbGljYWJsZSIgYWZ0ZXIgdGhlIGluY29tZSBsZXZlbHMgaXMgYXJiaXRyYXJ5LiBJdCB3b3VsZCBiZSBiZXR0ZXIgdG8gcGxhY2UgYWxsIHRoZSBtaXNzaW5nIGluY29tZSBsZXZlbCBjYXRlZ29yaWVzIGVpdGhlciBiZWZvcmUgb3IgYWZ0ZXIgYWxsIHRoZSBrbm93biB2YWx1ZXMuDQoNCmBgYHtyfQ0KDQpsZXZlbHMoZ3NzX2NhdCRyaW5jb21lKQ0KYGBgDQoNClRoZSBsZXZlbHMgb2YgcmVsaWcgaXMgYXJiaXRyYXJ5OiB0aGVyZSBpcyBubyBuYXR1cmFsIG9yZGVyaW5nLCBhbmQgdGhleSBkb24ndCBhcHBlYXIgdG8gYmUgb3JkZXJlZCBieSBzdGF0cyB3aXRoaW4gdGhlIGRhdGFzZXQuDQoNCmBgYHtyfQ0KbGV2ZWxzKGdzc19jYXQkcmVsaWcpDQpgYGANCg0KDQpgYGB7cn0NCiMgaG9yaXpvbnRhbCBiYXINCmdzc19jYXQgJT4lDQogIGdncGxvdChhZXMocmVsaWcpKSArDQogIGdlb21fYmFyKCkgKw0KICBjb29yZF9mbGlwKCkNCmBgYA0KDQpXaHkgZGlkIG1vdmluZyAiTm90IGFwcGxpY2FibGUiIHRvIHRoZSBmcm9udCBvZiB0aGUgbGV2ZWxzIG1vdmUgaXQgdG8gdGhlIGJvdHRvbSBvZiB0aGUgcGxvdD8gPC9icj4NCkJlY2F1c2UgdGhhdCBnaXZlcyB0aGUgbGV2ZWwgIk5vdCBhcHBsaWNhYmxlIiBhbiBpbnRlZ2VyIHZhbHVlIG9mIDEuIDwvcD4NCg0KPGgyPiBNb2RpZnlpbmcgRmFjdG9yIExldmVscyA8L2gyPg0KDQpNb3JlIHBvd2VyZnVsIHRoYW4gY2hhbmdpbmcgdGhlIG9yZGVyIG9mIHRoZSBsZXZlbHMgaXMgY2hhbmdpbmcgdGhlaXIgdmFsdWVzLiBVc2UgZmN0X3JlY29kZSgpIA0KDQpgYGB7cn0NCmdzc19jYXQgJT4lDQogIGNvdW50KHBhcnR5aWQpDQpgYGANCg0KDQpMZXQgdXMgY2hhbmdlIHRoZXNlIHRvIGJlIGxvbmdlciBhbmQgdXNlIGEgcGFyYWxsZWwgY29uc3RydWN0aW9uIA0KDQpgYGB7cn0NCmdzc19jYXQgJT4lDQogIG11dGF0ZShwYXJ0eWlkID0gZmN0X3JlY29kZShwYXJ0eWlkLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlJlcHVibGljYW4sIHN0cm9uZyIgPSAiU3Ryb25nIHJlcHVibGljYW4iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIlJlcHVibGljYW4sIHdlYWsiID0gIk5vdCBzdHIgcmVwdWJsaWNhbiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiSW5kZXBlbmRlbnQsIG5lYXIgcmVwIiA9ICJJbmQsbmVhciByZXAiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkluZGVwZW5kZW50LCBuZWFyIGRlbSIgPSAiSW5kLG5lYXIgZGVtIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJEZW1vY3JhdCwgd2VhayIgPSAiTm90IHN0ciBkZW1vY3JhdCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiRGVtb2NyYXQsIHN0cm9uZyIgPSAiU3Ryb25nIGRlbW9jcmF0IikpICU+JQ0KICBjb3VudChwYXJ0eWlkKQ0KYGBgDQoNCg0KRmN0X3JlY29kZSgpIHdpbGwgbGVhdmUgbGV2ZWxzIHRoYXQgYXJlbid0IGV4cGxpY2l0bHkgbWVudGlvbmVkIGFzIGlzLCBhbmQgd2lsbCB3YXJuIHlvdSBpZiB5b3UgYWNjaWRlbnRhbGx5IHJlZmVyIHRvIGEgbGV2ZWwgdGhhdCBkb2VzbnQgZXhpc3QuIFRvIGNvbWJpbmUgZ3JvdXBzLCB5b3UgY2FuIGFzc2lnbiBtdWx0aXBsZSBvbGQgbGV2ZWxzIHRvIHRoZSBzYW1lIG5ldyBsZXZlbC4NCg0KYGBge3J9DQpnc3NfY2F0ICU+JQ0KbXV0YXRlKHBhcnR5aWQgPSBmY3RfcmVjb2RlKHBhcnR5aWQsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiUmVwdWJsaWNhbiwgc3Ryb25nIiA9ICJTdHJvbmcgcmVwdWJsaWNhbiIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiUmVwdWJsaWNhbiwgd2VhayIgPSAiTm90IHN0ciByZXB1YmxpY2FuIiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJJbmRlcGVuZGVudCwgbmVhciByZXAiID0gIkluZCxuZWFyIHJlcCIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiSW5kZXBlbmRlbnQsIG5lYXIgZGVtIiA9ICJJbmQsbmVhciBkZW0iLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIkRlbW9jcmF0LCB3ZWFrIiA9ICJOb3Qgc3RyIGRlbW9jcmF0IiwNCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJEZW1vY3JhdCwgc3Ryb25nIiA9ICJTdHJvbmcgZGVtb2NyYXQiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIk90aGVyIiA9ICJObyBhbnN3ZXIiLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICJPdGhlciIgPSAiRG9uJ3Qga25vdyIsDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgIk90aGVyIiA9ICJPdGhlciBwYXJ0eSIpKSAlPiUNCiAgY291bnQocGFydHlpZCkgIA0KYGBgDQoNCklmIHlvdSB3YW50IHRvIGNvbGxhcHNlIGEgbG90IG9mIGxldmVscywgZmN0X2NvbGxhcHNlKCkgaXMgYSB1c2VmdWwgdmFyaWFudCBvZiBmY3RfcmVjb2RlKCkuIEZvciBlYWNoIG5ldyB2YXJpYWJsZSwgeW91IGNhbiBwcm92aWRlIGEgdmVjdG9yIG9mIG9sZCBsZXZlbHMuIA0KDQpgYGB7cn0NCmdzc19jYXQgJT4lDQogIG11dGF0ZShwYXJ0eWlkID0gZmN0X2NvbGxhcHNlKHBhcnR5aWQsDQogICAgICAgIG90aGVyID0gYygiTm8gYW5zd2VyIiwgIkRvbid0IGtub3ciLCAiT3RoZXIgcGFydHkiKSwNCiAgICAgICAgcmVwID0gYygiU3Ryb25nIHJlcHVibGljYW4iLCAiTm90IHN0ciByZXB1YmxpY2FuIiksDQogICAgICAgIGluZCA9IGMoIkluZCxuZWFyIHJlcCIsICJJbmRlcGVuZGVudCIsICJJbmQsbmVhciBkZW0iKSwNCiAgICAgICAgZGVtID0gYygiTm90IHN0ciBkZW1vY3JhdCIsICJTdHJvbmcgZGVtb2NyYXQiKQ0KKSkgJT4lDQogIGNvdW50KHBhcnR5aWQpDQpgYGANCg0KU29tZXRpbWVzIHlvdSBqdXN0IHdhbnQgdG8gbHVtcCB0b2dldGhlciBhbGwgdGhlIHNtYWxsIGdyb3VwcyB0byBtYWtlIGEgcGxvdCBvciB0YWJsZSBzaW1wbGVyLiBUaGF0IGlzIHRoZSBqb2Igb2YgZmN0X2x1bXAoKS4NCg0KYGBge3J9DQpnc3NfY2F0ICU+JQ0KICBtdXRhdGUocmVsaWcgPSBmY3RfbHVtcChyZWxpZykpICU+JQ0KICBjb3VudChyZWxpZykNCmBgYA0KDQpUaGUgZGVmYXVsdCBiZWhhdmlvciBpcyB0byBwcm9ncmVzc2l2ZWx5IGx1bXAgdG9nZXRoZXIgdGhlIHNtYWxsZXN0IGdyb3VwcywgZW5zdXJpbmcgdGhhdCB0aGUgYWdncmVnYXRlIGlzIHN0aWxsIHRoZSBzbWFsbGVzdCBncm91cC4gSWYgd2Ugd2FudCB0byBjb250cm9sIHRoZSBsZXZlbCBvZiBsdW1waW5nLCB1c2UgdGhlIG4gcGFyYW1ldGVyIHRvIHNwZWNpZnkgaG93IG1hbnkgZ3JvdXBzIChleGNsdWRpbmcgb3RoZXIpIHdlIHdhbnQgdG8ga2VlcA0KDQpgYGB7cn0NCmdzc19jYXQgJT4lDQogIG11dGF0ZShyZWxpZyA9IGZjdF9sdW1wKHJlbGlnLCBuID0xMCkpICU+JQ0KICBjb3VudChyZWxpZywgc29ydCA9IFRSVUUpICU+JQ0KICBwcmludChuID0gSW5mKQ0KYGBgDQoNCjxoMz4gRXhlcmNpc2VzIDwvaDM+DQoNCkhvdyBoYXZlIHRoZSBwcm9wb3J0aW9ucyBvZiBwZW9wbGUgaWRlbnRpZnlpbmcgYXMgRGVtb2NyYXQsIFJlcHVibGljYW4sIGFuZCBJbmRlcGVuZGVudCBjaGFuZ2VkIG92ZXIgdGltZT8NCmBgYHtyfQ0KDQpsZXZlbHMoZ3NzX2NhdCRwYXJ0eWlkKQ0KDQpgYGANCg0KYGBge3J9DQpnc3NfY2F0ICU+JSANCiAgbXV0YXRlKHBhcnR5aWQgPSANCiAgICAgICAgICAgZmN0X2NvbGxhcHNlKHBhcnR5aWQsDQogICAgICAgICAgICAgICAgICAgICAgICBvdGhlciA9IGMoIk5vIGFuc3dlciIsICJEb24ndCBrbm93IiwgIk90aGVyIHBhcnR5IiksDQogICAgICAgICAgICAgICAgICAgICAgICByZXAgPSBjKCJTdHJvbmcgcmVwdWJsaWNhbiIsICJOb3Qgc3RyIHJlcHVibGljYW4iKSwNCiAgICAgICAgICAgICAgICAgICAgICAgIGluZCA9IGMoIkluZCxuZWFyIHJlcCIsICJJbmRlcGVuZGVudCIsICJJbmQsbmVhciBkZW0iKSwNCiAgICAgICAgICAgICAgICAgICAgICAgIGRlbSA9IGMoIk5vdCBzdHIgZGVtb2NyYXQiLCAiU3Ryb25nIGRlbW9jcmF0IikpKSAlPiUNCiAgY291bnQoeWVhciwgcGFydHlpZCkgICU+JQ0KICBncm91cF9ieSh5ZWFyKSAlPiUNCiAgbXV0YXRlKHAgPSBuIC8gc3VtKG4pKSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0geWVhciwgeSA9IHAsDQogICAgICAgICAgICAgY29sb3VyID0gZmN0X3Jlb3JkZXIyKHBhcnR5aWQsIHllYXIsIHApKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX2xpbmUoKSArDQogIGxhYnMoY29sb3VyID0gIlBhcnR5IElELiIpDQogIA0KDQoNCmBgYA0KDQpIb3cgY291bGQgeW91IGNvbGxhcHNlIHJpbmNvbWUgaW50byBhIHNtYWxsIHNldCBvZiBjYXRlZ29yaWVzPyA8L2JyPg0KR3JvdXAgYWxsIHRoZSBub24tcmVzcG9uc2VzIGludG8gb25lIGNhdGVnb3J5LCBhbmQgdGhlbiBncm91cCBvdGhlciBjYXRlZ29yaWVzIGludG8gYSBzbWFsbGVyIG51bWJlci4gU2luY2UgdGhlcmUgaXMgYSBjbGVhciBvcmRlcmluZywgd2Ugd291bGRuJ3Qgd2FudCB0byB1c2Ugc29tZXRoaW5nIGxpa2UgZmN0X2x1bXAuIDwvcD4NCg0KYGBge3J9DQpsaWJyYXJ5KCJzdHJpbmdyIikNCmdzc19jYXQgJT4lDQogIG11dGF0ZShyaW5jb21lID0gDQogICAgICAgICAgIGZjdF9jb2xsYXBzZSgNCiAgICAgICAgICAgICByaW5jb21lLA0KICAgICAgICAgICAgIGBVbmtub3duYCA9IGMoIk5vIGFuc3dlciIsICJEb24ndCBrbm93IiwgIlJlZnVzZWQiLCAiTm90IGFwcGxpY2FibGUiKSwNCiAgICAgICAgICAgICBgTHQgJDUwMDBgID0gYygiTHQgJDEwMDAiLCBzdHJfYygiJCIsIGMoIjEwMDAiLCAiMzAwMCIsICI0MDAwIiksDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIiB0byAiLCBjKCIyOTk5IiwgIjM5OTkiLCAiNDk5OSIpKSksDQogICAgICAgICAgICAgYCQ1MDAwIHRvIDEwMDAwYCA9IHN0cl9jKCIkIiwgYygiNTAwMCIsICI2MDAwIiwgIjcwMDAiLCAiODAwMCIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAiIHRvICIsIGMoIjU5OTkiLCAiNjk5OSIsICI3OTk5IiwgIjk5OTkiKSkNCiAgICAgICAgICAgKSkgJT4lDQogIGdncGxvdChhZXMoeCA9IHJpbmNvbWUpKSArDQogIGdlb21fYmFyKCkgKyANCiAgY29vcmRfZmxpcCgpDQpgYGANCg0KDQoNCg==