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==