Data Wrangling In this workshop we will explore data manipulation and techniques for summarizing data using the library dplyr.

#If you need to install the tidyverse use "install.packages("tidyverse"), otherwise 
library(tidyverse) # loads readr, dplyr and many others libraries useful for working with data including ggplot2

In this exercise we will learn to 1) Read data from a CSV file 2) Identify and convert data types in your data file 3) Manipulate data frames 4) Extract subsets of data using base R 5) Extract subsets of data using ‘dplyr’ 6) Add new variables to a data frame, select, sort, aggregate and summarize the data in a data frame.

Working with Data Sets Data frames are the most common and convenient data objects to work with in R. However, you may also run across matrices and lists which are conceptually not too different from a data frame and much of the logic you learn from working with data frames can be translated among different data objects.

Reminder 2 ways to read in data from a csv file stored on your computer

# base R method:
mydata <- read.csv("filename.csv", stringsAsFactors = FALSE,
                  strip.white = TRUE, na.strings = c("NA", "") )
Warning: cannot open file 'filename.csv': No such file or directoryError in file(file, "rt") : cannot open the connection

To read in data using the readr package from the tidyverse, everything should be the same as above except you use the function read_csv()

# using readr package
mydata <- read_csv("mammals.csv")
Rows: 5731 Columns: 7── Column specification ──────────────────────────────────────────────────────────────────────
Delimiter: ","
chr (6): continent, status, order, family, genus, species
dbl (1): mass.grams
ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.

Checking your Data

After you have imported your data there are a variety of tools that you can use to visualize the data frame to make sure it is what you were expecting. Here are a list of the most helpful…

glimpse(mydata)
Rows: 5,731
Columns: 7
$ continent  <chr> "AF", "AF", "AF", "AF", "AF", "AF", "AF", "AF", "AF", "AF", "AF", "AF", "…
$ status     <chr> "extant", "extant", "extant", "extant", "extant", "extant", "extinct", "e…
$ order      <chr> "Artiodactyla", "Artiodactyla", "Artiodactyla", "Artiodactyla", "Artiodac…
$ family     <chr> "Bovidae", "Bovidae", "Bovidae", "Bovidae", "Bovidae", "Bovidae", "Bovida…
$ genus      <chr> "Addax", "Aepyceros", "Alcelaphus", "Ammodorcas", "Ammotragus", "Antidorc…
$ species    <chr> "nasomaculatus", "melampus", "buselaphus", "clarkei", "lervia", "marsupia…
$ mass.grams <dbl> 70000.3, 52500.1, 171001.5, 28049.8, 48000.0, 39049.9, 34000.0, 40000.0, …

Variable types in your data

When you read your data into R, the program automatically classifies each of your variables (i.e. columns) into data types based on the objects in the column.

  1. Columns with only numbers are made into numeric or integer variables (dbl=double-precision floating-point format).

  2. Columns that have any non-numeric characters (even its supposed to be a numeric data column and there was a data entry error) is read in as either characters (read_csv) or factors (read.csv). By default, read.csv() converts character variables into factors, which can be annoying to work with. Circumvent this by specifying stringsAsFactors = FALSE in the read.csv() call.

Factors are categorical variables whose categories represent levels. These levels have names, but they additionally have a numeric interpretation. If a variable A has 3 categories “a”, “b”, and “c”, R will order the levels alphabetically, by default, and give them the corresponding numerical interpretations 1, 2, and 3. This will determine the order that the categories appear in graphs and tables.

A <- factor( c("c","a","b"))

You can always change the order of the levels. For example, if you want “c” to be first (e.g., because it refers to the control group), set the order as follows: A <- factor(A, levels = c(“c”,“a”,“b”))

A <- factor(A, levels = c("c","a","b"))

Reclassifying your data If your data have not been classified correctly you can convert among types. We will learn this below.

Saving your data frame You can also save a manipulated version of a data frame or a new set of data (e.g. from a simulation) as a csv file.

write_csv(mydata, path = "/directoryname/mammals.csv")          
Error: Cannot open file for writing:
* 'C:\directoryname\mammals.csv'

Tips for creating your Spreadsheet file so that it plays nicely with R

It is easiest to enter data using a spreadsheet and use the standard approach of columns for variables and rows for individual sampling units. Carefully considering what you should put into your spreadsheet may help save you some time and frustration.

  1. Column names should be brief but informative names for variable using plain text
  2. Detailed explanations of variables can be kept in a separate metadata (text file).
  3. You should avoid spaces in variable names – use a dot or underscore instead (e.g., mass.grams or mass_grams).
  4. Leave missing cells (i.e. no data) blank and avoid non-numeric characters in columns of numeric data. (a period, etc. does not need to be added for missing data)

If you enter in a character for missing data, R will assume that the entire column is non-numeric. Similarly, avoid using a question mark “12.67?” to indicate a number you are not sure about…add a notes column.

  1. Do not put in a zero for missing data…zeros are assumed to be data!
  2. Dates should be in international format (YYYY-MM-DD) or use separate columns for year, month and day.
  3. Avoid commas in your data set entirely, because they are column delimiters in your .csv file.

There are two common layouts for data: “Long” vs “wide” layouts.

Wide layout Plot Site species1 species2 species3 1 A 0 12 4 2 A 88 2 0 3 B 12 4 1

Long layout Plot Site Species Number 1 A 1 0 1 A 2 12 1 A 3 4 2 A 1 88 2 A 2 2 2 A 3 0 3 B 1 12 3 B 2 4 3 B 3 1 …

In general a “long” layout is recommended for conducting analyses in R, rather than a “wide” layout. However the “wide layout” can sometimes be more feasible for accurate data entry. You can convert between these, however before learning how to do that there are a some important utility function for data wrangling that you need to be familiar with.

Data wrangling with dplyr and tidyr

The package dplyr has been said to be the most perfectly suited package for streamlining workflow for real analytics. Its utility comes from the fact that it uses a combination of five primary verbs (i.e. functions or commands) and a process called chaining.

The five verbs are filter() select() mutate() arrange() summarize()

library(dplyr)
library(ggplot2)

glimpse(diamonds)
Rows: 53,940
Columns: 10
$ carat   <dbl> 0.23, 0.21, 0.23, 0.29, 0.31, 0.24, 0.24, 0.26, 0.22, 0.23, 0.30, 0.23, 0.22…
$ cut     <ord> Ideal, Premium, Good, Premium, Good, Very Good, Very Good, Very Good, Fair, …
$ color   <ord> E, E, E, I, J, J, I, H, E, H, J, J, F, J, E, E, I, J, J, J, I, E, H, J, J, G…
$ clarity <ord> SI2, SI1, VS1, VS2, SI2, VVS2, VVS1, SI1, VS2, VS1, SI1, VS1, SI1, SI2, SI2,…
$ depth   <dbl> 61.5, 59.8, 56.9, 62.4, 63.3, 62.8, 62.3, 61.9, 65.1, 59.4, 64.0, 62.8, 60.4…
$ table   <dbl> 55, 61, 65, 58, 58, 57, 57, 55, 61, 61, 55, 56, 61, 54, 62, 58, 54, 54, 56, …
$ price   <int> 326, 326, 327, 334, 335, 336, 336, 337, 337, 338, 339, 340, 342, 344, 345, 3…
$ x       <dbl> 3.95, 3.89, 4.05, 4.20, 4.34, 3.94, 3.95, 4.07, 3.87, 4.00, 4.25, 3.93, 3.88…
$ y       <dbl> 3.98, 3.84, 4.07, 4.23, 4.35, 3.96, 3.98, 4.11, 3.78, 4.05, 4.28, 3.90, 3.84…
$ z       <dbl> 2.43, 2.31, 2.31, 2.63, 2.75, 2.48, 2.47, 2.53, 2.49, 2.39, 2.73, 2.46, 2.33…

Filter

The filter() command allows you to subset a data set only retaining data for rows that you are interested in. For example, we will use one of R's built in data sets on diamond cuts to illustrate different aspects of the packages utilities. Lets load and examine the data set first.

Okay, lets use the filtering command to subset these data so that we only retain values for the cases where cut variable is "ideal".

d1=filter(diamonds,cut=="Ideal")
d1

Select Similarly we can choose only a subset of the columns (variables) in the data frame. For example, we may want to simplify things by creating a new working data frame that only has the variables of interest for a given analysis. In this case lets create a data set that only retains the variables:

“cut”,“carat”,“color”,“price”,and “clarity”.

This is easily accomplished using the select() command.


d2=select(diamonds, carat, cut, color, price, clarity)
d2

Mutate Another very common task in data analytic or any programming task is adding variables. We can do this using the mutate() command. For example, we might want to add a variable that reflects the costs of diamonds per carat of quality.

d3=mutate(diamonds,price_per=price/carat)
d3

Arrange

arrange() works like an ordering command, but using `arrange()’makes the syntax much simpler.

Lets make up a data set to illustrate this function.

scramble=data.frame(num_var = c(2,3,5,1,4))
arrange(scramble,num_var)
arrange(scramble,desc(num_var))

Summarize Finally the summarize() command does exactly what it sounds like it does…it allows you to generate summaries or summarized versions of the data. For example we can use it to calculate a simple mean using our subset data set from above.

summarize(diamonds, avg_price = mean(price, na.rm = TRUE))

group_by

We can also use this to generate more complicated summaries of the data using a sub function called group_by(). For example we can go back to the original diamonds data set and summarize the data by calculating means according to all cut types.

head(diamonds)
d1=group_by(diamonds,cut,color)
summarize(d1, avg_price = mean(price, na.rm = TRUE),sd.price=sd(price,na.rm=TRUE))
`summarise()` has grouped output by 'cut'. You can override using the `.groups` argument.

Chains or Pipes The real power of this library is not fully realized however until you start chaining commands together. You can chain together different verbs of dplyr using the %>% operator. All this operator does is allow you to connect commands together so that the output of one command becomes the input for the next down a chain. For example we can do all the steps above on the diamonds data set in a single chain of commands.

groups
function (x) 
{
    UseMethod("groups")
}
<bytecode: 0x00000175f87ccf28>
<environment: namespace:dplyr>
final.diamonds= diamonds %>%
                filter(cut=="Ideal") %>%
                select(carat, cut, color, price, clarity) %>%
                mutate(price_per_carat = price/carat)

This chained set of syntax literally says: – “Take the diamonds data set’” – “Then filter it, keeping only the rows where ‘cut’ equals ‘Ideal’” – “Then select specific variables, ‘carat’, ‘cut’, ‘color’, ‘price, ‘clarity’” – “Then create a new variable, ‘price_per_carat’ using ‘mutate()’”

Finally, dplyr can also be a powerful tool for data exploration when paired with ggplot. Its power comes from the fact that you can chain together dplyr commands and ggplot commands. For example we can create a box plot for just the ideal diamonds by

  diamonds %>%                                        # Start with the 'diamonds' dataset
  filter(cut == "Ideal") %>%                        # Then, filter down to rows where cut == Ideal
  ggplot(aes(x=color,y=price)) +                     # Then, plot using ggplot
  geom_boxplot()      


diamonds %>%                                        # Start with the 'diamonds' dataset
  filter(cut == "Premium") %>%                        # Then, filter down to rows where cut == Ideal
  ggplot(aes(x=carat,y=price)) +                     # Then, plot using ggplot
  geom_point()

or a histogram by

diamonds %>%                                        # Start with the 'diamonds' dataset
  filter(cut == "Ideal") %>%                        # Then, filter down to rows where cut == Ideal
  ggplot(aes(price)) +                            # Then, plot using ggplot
    geom_histogram() +                              # and plot histograms
    facet_wrap(~ color)                             # in a 'small multiple' plot, broken out by 'color'

Wide to Long and Back again In R most functions expect data to be in a long format rather than a wide format, however ease of data entry and data formatted for some other statistical software (e.g. SPSS) may result in data in the wide format. To deal with this problem there are some nifty methods using the gather() and spread() functions from the tidyr library.

First, we will generate (simulate) data that is collected in wide and in long format. The data are exactly the same for both formats. Then we can use dplyr to convert each to the other format below.

First we will make the wide formatted data….take note on the generation of these data…

wide <- read.table(header=TRUE, text='
 subject sex control cond1 cond2
       1   M     7.9  12.3  10.7
       2   F     6.3  10.6  11.1
       3   F     9.5  13.1  13.8
       4   M    11.5  13.4  12.9
')

Challenge use dplyr to turn subject into a factor)

# Make sure the subject column is a factor

And now the same data in the long format.

long <- read.table(header=TRUE, text='
 subject sex condition measurement
       1   M   control         7.9
       1   M     cond1        12.3
       1   M     cond2        10.7
       2   F   control         6.3
       2   F     cond1        10.6
       2   F     cond2        11.1
       3   F   control         9.5
       3   F     cond1        13.1
       3   F     cond2        13.8
       4   M   control        11.5
       4   M     cond1        13.4
       4   M     cond2        12.9
')

Challenge use dplyr to turn subject into a factor)

# Make sure the subject column is a factor

To convert the data from wide to long format we will use the gather function. This function has several key pieces of input (arguments) that are needed. These are

gather(data,key,value,...sources,factor key) # - data: Data object # - key: Name of new key column (made from names of data columns) # - value: Name of new value column # - …: Names of source columns that contain values # - factor_key: Treat the new key column as a factor (instead of character vector)

So to implement we just need to run:

require(tidyr)
wide_to_long <- gather(wide, condition, measurement, control:cond2, factor_key=TRUE)
wide_to_long

In this example, the source columns that are gathered are specified with control:cond2. This means to use all the columns, positional, between control and cond2. Another way of doing it is to name the columns individually, as in:

wide_to_long <- gather(wide, condition, measurement, control, cond1, cond2, factor_key=TRUE)
wide_to_long

In some cases you may want to use gather() internally in some larger function and so you will want to use variables containing column names rather than the column names themselves. To do this use the gather_() function instead.

keycol <- "condition"
valuecol <- "measurement"
gathercols <- c("control", "cond1", "cond2")

gather_(wide, keycol, valuecol, gathercols)

To convert our data from the long format to the wide format we do the same steps except we use the spread function instead of the gather function.

long_to_wide <- spread(wide_to_long, condition, measurement)
long_to_wide

Contrasting base R and dplyr

Below are some useful data manipulation approaches using base R functionality and the new functions. It is useful to learn both because the base R version gives a more intuitive understanding of what th code is actually doing, where as the new functionality of dply provides some efficient tools. I often use these interchangeably depending on the application, I prefer one or the other.

Transform a column (variable) within a data frame

For example, log transform a variable named mass.grams and save the result as a new variable named logsize in the data frame. (log yields the natural log, whereas the function log10 yields log base 10.)

mydata$logsize <- log(mydata$body_mass_kg)            
Error in log(mydata$body_mass_kg) : 
  non-numeric argument to mathematical function

Delete a variable from data frame

For example, to delete the variable species from mydata, use

mydata2 <- select(mydata, -name) # dplyr method
Error in `select()`:
! Can't subset columns that don't exist.
✖ Column `name` doesn't exist.
Backtrace:
  1. dplyr::select(mydata, -name)
  2. dplyr:::select.data.frame(mydata, -name)
  5. tidyselect::eval_select(expr(c(...)), .data)
  6. tidyselect:::eval_select_impl(...)
 15. tidyselect:::vars_select_eval(...)
     ...
 18. tidyselect:::reduce_sels(node, data_mask, context_mask, init = init)
 19. tidyselect:::walk_data_tree(new, data_mask, context_mask)
 20. tidyselect:::as_indices_sel_impl(...)
 21. tidyselect:::as_indices_impl(x, vars, call = call, strict = strict)
 22. tidyselect:::chr_as_locations(x, vars, call = call)

Extract a data subset

There are several ways. One is to use indicators inside square brackets using the following format: mydata[rows, columns]. Be sure to look after each line below to see what the commands did to the data set

newdata <- mydata[ , c(2,3)]   # all rows, columns 2 and 3 only;
newdata <- mydata[ , -1]       # all rows, leave out first column
newdata <- mydata[1:3, 1:2]    # first three rows, first two columns

The subset command in base R is easy to use to extract rows and columns. Use the select argument to select columns (variables). For example, to pull out rows corresponding to continent AF with logsize < 25, and the three variables, “continent”,“status”,“order” you could use the following.

newdata <- subset(mydata, body_mass_kg>= 10 & brain_mass_g < 250)
Error in eval(e, x, parent.frame()) : object 'body_mass_kg' not found

**Sort and order the rows*

To re-order the rows of a data frame mydata to correspond to the sorted order of one of its variables, say x, use

mydata.x <- mydata[order(mydata$x), ]  # base R
Error in base::"order"(...) : argument 1 is not a vector

Challenge1

Write a single command (which can span multiple lines and includes pipes) that will produce a data frame from the diamonds data that only has the values for color “J” of the variables price, cut and carat.

Challenge 2

Calculate the average price per color which has the highest average price and which has the shortest average price?

Challenge 3

In one piped or chained set of code filter generate a plot that plots the means and CIs of a new variable called unit_price that is the price per carat but only for diamonds that were rated as Ideal, Premium, or Very Good with each color of diamond depicted in a different color and in a diffrent panel.

LS0tDQp0aXRsZTogIkRhdGEgV3JhbmdsaW5nIg0KYXV0aG9yOiAiTWljaGFlbCBXLiBNY0NveSINCmRhdGU6ICJMYXN0IHVwZGF0ZWQgb24gYHIgZm9ybWF0KFN5cy50aW1lKCksICclZCAlQiwgJVknKWAiDQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6IGRlZmF1bHQNCiAgcGRmX2RvY3VtZW50OiBkZWZhdWx0DQotLS0NCg0KDQoqKkRhdGEgV3JhbmdsaW5nKioNCkluIHRoaXMgd29ya3Nob3Agd2Ugd2lsbCBleHBsb3JlIGRhdGEgbWFuaXB1bGF0aW9uIGFuZCB0ZWNobmlxdWVzIGZvciBzdW1tYXJpemluZyBkYXRhIHVzaW5nIHRoZSBsaWJyYXJ5IGBkcGx5cmAuIA0KDQpgYGB7cn0NCiNJZiB5b3UgbmVlZCB0byBpbnN0YWxsIHRoZSB0aWR5dmVyc2UgdXNlICJpbnN0YWxsLnBhY2thZ2VzKCJ0aWR5dmVyc2UiKSwgb3RoZXJ3aXNlIA0KbGlicmFyeSh0aWR5dmVyc2UpICMgbG9hZHMgcmVhZHIsIGRwbHlyIGFuZCBtYW55IG90aGVycyBsaWJyYXJpZXMgdXNlZnVsIGZvciB3b3JraW5nIHdpdGggZGF0YSBpbmNsdWRpbmcgZ2dwbG90Mg0KYGBgDQoNCkluIHRoaXMgZXhlcmNpc2Ugd2Ugd2lsbCBsZWFybiB0byANCiAgMSkgUmVhZCBkYXRhIGZyb20gYSBDU1YgZmlsZQ0KICAyKSBJZGVudGlmeSBhbmQgY29udmVydCBkYXRhIHR5cGVzIGluIHlvdXIgZGF0YSBmaWxlDQogIDMpIE1hbmlwdWxhdGUgZGF0YSBmcmFtZXMNCiAgNCkgRXh0cmFjdCBzdWJzZXRzIG9mIGRhdGEgdXNpbmcgYmFzZSBSDQogIDUpIEV4dHJhY3Qgc3Vic2V0cyBvZiBkYXRhIHVzaW5nICdkcGx5cicNCiAgNikgQWRkIG5ldyB2YXJpYWJsZXMgdG8gYSBkYXRhIGZyYW1lLCBzZWxlY3QsIHNvcnQsIGFnZ3JlZ2F0ZSBhbmQgc3VtbWFyaXplIHRoZSBkYXRhIGluIGEgZGF0YSBmcmFtZS4gIA0KICANCipXb3JraW5nIHdpdGggRGF0YSBTZXRzKg0KRGF0YSBmcmFtZXMgYXJlIHRoZSBtb3N0IGNvbW1vbiBhbmQgY29udmVuaWVudCBkYXRhIG9iamVjdHMgdG8gd29yayB3aXRoIGluIFIuIEhvd2V2ZXIsIHlvdSBtYXkgYWxzbyBydW4gYWNyb3NzIG1hdHJpY2VzIGFuZCBsaXN0cyB3aGljaCBhcmUgY29uY2VwdHVhbGx5IG5vdCB0b28gZGlmZmVyZW50IGZyb20gYSBkYXRhIGZyYW1lIGFuZCBtdWNoIG9mIHRoZSBsb2dpYyB5b3UgbGVhcm4gZnJvbSB3b3JraW5nIHdpdGggZGF0YSBmcmFtZXMgY2FuIGJlIHRyYW5zbGF0ZWQgYW1vbmcgZGlmZmVyZW50IGRhdGEgb2JqZWN0cy4NCg0KKlJlbWluZGVyKiAyIHdheXMgdG8gcmVhZCBpbiBkYXRhIGZyb20gYSBjc3YgZmlsZSBzdG9yZWQgb24geW91ciBjb21wdXRlcg0KYGBge3J9DQojIGJhc2UgUiBtZXRob2Q6DQpteWRhdGEgPC0gcmVhZC5jc3YoImZpbGVuYW1lLmNzdiIsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSwNCiAgICAgICAgICAgICAgICAgIHN0cmlwLndoaXRlID0gVFJVRSwgbmEuc3RyaW5ncyA9IGMoIk5BIiwgIiIpICkNCiMgdXNpbmcgcmVhZHIgcGFja2FnZQ0KbXlkYXRhIDwtIHJlYWRfY3N2KCIvZGlyZWN0b3J5bmFtZS9maWxlbmFtZS5jc3YiLCBuYSA9IGMoIk5BIiwgIiIpKQ0KYGBgDQoNClRvIHJlYWQgaW4gZGF0YSB1c2luZyB0aGUgcmVhZHIgcGFja2FnZSBmcm9tIHRoZSB0aWR5dmVyc2UsIGV2ZXJ5dGhpbmcgc2hvdWxkIGJlIHRoZSBzYW1lIGFzIGFib3ZlIGV4Y2VwdCB5b3UgdXNlIHRoZSBmdW5jdGlvbiByZWFkX2NzdigpDQoNCmBgYHtyfQ0KIyB1c2luZyByZWFkciBwYWNrYWdlDQpteWRhdGEgPC0gcmVhZF9jc3YoIm1hbW1hbHMuY3N2IikNCg0KYGBgDQoNCipDaGVja2luZyB5b3VyIERhdGEqDQoNCkFmdGVyIHlvdSBoYXZlIGltcG9ydGVkIHlvdXIgZGF0YSB0aGVyZSBhcmUgYSB2YXJpZXR5IG9mIHRvb2xzIHRoYXQgeW91IGNhbiB1c2UgdG8gdmlzdWFsaXplIHRoZSBkYXRhIGZyYW1lIHRvIG1ha2Ugc3VyZSBpdCBpcyB3aGF0IHlvdSB3ZXJlIGV4cGVjdGluZy4gSGVyZSBhcmUgYSBsaXN0IG9mIHRoZSBtb3N0IGhlbHBmdWwuLi4NCg0KYGBge3J9DQpnbGltcHNlKG15ZGF0YSkNCmBgYA0KDQoqVmFyaWFibGUgdHlwZXMgaW4geW91ciBkYXRhKg0KDQpXaGVuIHlvdSByZWFkIHlvdXIgZGF0YSBpbnRvIFIsIHRoZSBwcm9ncmFtIGF1dG9tYXRpY2FsbHkgY2xhc3NpZmllcyBlYWNoIG9mIHlvdXIgdmFyaWFibGVzIChpLmUuIGNvbHVtbnMpIGludG8gZGF0YSB0eXBlcyBiYXNlZCBvbiB0aGUgb2JqZWN0cyBpbiB0aGUgY29sdW1uLiANCg0KMS4gQ29sdW1ucyB3aXRoIG9ubHkgbnVtYmVycyBhcmUgbWFkZSBpbnRvIG51bWVyaWMgb3IgaW50ZWdlciB2YXJpYWJsZXMgKGRibD1kb3VibGUtcHJlY2lzaW9uIGZsb2F0aW5nLXBvaW50IGZvcm1hdCkuDQoNCjIuIENvbHVtbnMgdGhhdCBoYXZlIGFueSBub24tbnVtZXJpYyBjaGFyYWN0ZXJzIChldmVuIGl0cyBzdXBwb3NlZCB0byBiZSBhIG51bWVyaWMgZGF0YSBjb2x1bW4gYW5kIHRoZXJlIHdhcyBhIGRhdGEgZW50cnkgZXJyb3IpIGlzIHJlYWQgaW4gYXMgZWl0aGVyIGNoYXJhY3RlcnMgKHJlYWRfY3N2KSAgb3IgZmFjdG9ycyAocmVhZC5jc3YpLiBCeSBkZWZhdWx0LCByZWFkLmNzdigpIGNvbnZlcnRzIGNoYXJhY3RlciB2YXJpYWJsZXMgaW50byBmYWN0b3JzLCB3aGljaCBjYW4gYmUgYW5ub3lpbmcgdG8gd29yayB3aXRoLiBDaXJjdW12ZW50IHRoaXMgYnkgc3BlY2lmeWluZyBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UgaW4gdGhlIHJlYWQuY3N2KCkgY2FsbC4NCg0KRmFjdG9ycyBhcmUgY2F0ZWdvcmljYWwgdmFyaWFibGVzIHdob3NlIGNhdGVnb3JpZXMgcmVwcmVzZW50IGxldmVscy4gVGhlc2UgbGV2ZWxzIGhhdmUgbmFtZXMsIGJ1dCB0aGV5IGFkZGl0aW9uYWxseSBoYXZlIGEgbnVtZXJpYyBpbnRlcnByZXRhdGlvbi4gSWYgYSB2YXJpYWJsZSBBIGhhcyAzIGNhdGVnb3JpZXMg4oCcYeKAnSwg4oCcYuKAnSwgYW5kIOKAnGPigJ0sIFIgd2lsbCBvcmRlciB0aGUgbGV2ZWxzIGFscGhhYmV0aWNhbGx5LCBieSBkZWZhdWx0LCBhbmQgZ2l2ZSB0aGVtIHRoZSBjb3JyZXNwb25kaW5nIG51bWVyaWNhbCBpbnRlcnByZXRhdGlvbnMgMSwgMiwgYW5kIDMuIFRoaXMgd2lsbCBkZXRlcm1pbmUgdGhlIG9yZGVyIHRoYXQgdGhlIGNhdGVnb3JpZXMgYXBwZWFyIGluIGdyYXBocyBhbmQgdGFibGVzLiANCg0KYGBge3J9DQpBIDwtIGZhY3RvciggYygiYyIsImEiLCJiIikpDQoNCmBgYA0KDQpZb3UgY2FuIGFsd2F5cyBjaGFuZ2UgdGhlIG9yZGVyIG9mIHRoZSBsZXZlbHMuIEZvciBleGFtcGxlLCBpZiB5b3Ugd2FudCDigJxj4oCdIHRvIGJlIGZpcnN0IChlLmcuLCBiZWNhdXNlIGl0IHJlZmVycyB0byB0aGUgY29udHJvbCBncm91cCksIHNldCB0aGUgb3JkZXIgYXMgZm9sbG93czogIEEgPC0gZmFjdG9yKEEsIGxldmVscyA9IGMoImMiLCJhIiwiYiIpKQ0KDQpgYGB7cn0NCkEgPC0gZmFjdG9yKEEsIGxldmVscyA9IGMoImMiLCJhIiwiYiIpKQ0KYGBgDQoNCipSZWNsYXNzaWZ5aW5nIHlvdXIgZGF0YSoNCklmIHlvdXIgZGF0YSBoYXZlIG5vdCBiZWVuIGNsYXNzaWZpZWQgY29ycmVjdGx5IHlvdSBjYW4gY29udmVydCBhbW9uZyB0eXBlcy4gIFdlIHdpbGwgbGVhcm4gdGhpcyBiZWxvdy4NCg0KKlNhdmluZyB5b3VyIGRhdGEgZnJhbWUqDQpZb3UgY2FuIGFsc28gc2F2ZSBhIG1hbmlwdWxhdGVkIHZlcnNpb24gb2YgYSBkYXRhIGZyYW1lIG9yIGEgbmV3IHNldCBvZiBkYXRhIChlLmcuIGZyb20gYSBzaW11bGF0aW9uKSBhcyBhIGNzdiBmaWxlLg0KDQpgYGB7cn0NCiMgYmFzZSBSDQp3cml0ZS5jc3YobXlkYXRhLCBmaWxlPSIvZGlyZWN0b3J5bmFtZS9tYW1tYWxzLmNzdiIsIHJvd25hbWVzID0gRkFMU0UpDQp3cml0ZV9jc3YobXlkYXRhLCBwYXRoID0gIi9kaXJlY3RvcnluYW1lL21hbW1hbHMuY3N2IikgICAgICAgICAgDQpgYGANCg0KKipUaXBzIGZvciBjcmVhdGluZyB5b3VyIFNwcmVhZHNoZWV0IGZpbGUgc28gdGhhdCBpdCBwbGF5cyBuaWNlbHkgd2l0aCBSKioNCg0KSXQgaXMgZWFzaWVzdCB0byBlbnRlciBkYXRhIHVzaW5nIGEgc3ByZWFkc2hlZXQgYW5kIHVzZSB0aGUgc3RhbmRhcmQgYXBwcm9hY2ggb2YgY29sdW1ucyBmb3IgdmFyaWFibGVzIGFuZCByb3dzIGZvciBpbmRpdmlkdWFsIHNhbXBsaW5nIHVuaXRzLiBDYXJlZnVsbHkgY29uc2lkZXJpbmcgd2hhdCB5b3Ugc2hvdWxkIHB1dCBpbnRvIHlvdXIgc3ByZWFkc2hlZXQgbWF5IGhlbHAgc2F2ZSB5b3Ugc29tZSB0aW1lIGFuZCBmcnVzdHJhdGlvbi4NCg0KICBhLiBDb2x1bW4gbmFtZXMgc2hvdWxkIGJlIGJyaWVmIGJ1dCBpbmZvcm1hdGl2ZSBuYW1lcyBmb3IgdmFyaWFibGUgdXNpbmcgcGxhaW4gdGV4dCANCiAgYi4gRGV0YWlsZWQgZXhwbGFuYXRpb25zIG9mIHZhcmlhYmxlcyBjYW4gYmUga2VwdCBpbiBhIHNlcGFyYXRlIG1ldGFkYXRhICh0ZXh0IGZpbGUpLiANCiAgYy4gWW91IHNob3VsZCBhdm9pZCBzcGFjZXMgaW4gdmFyaWFibGUgbmFtZXMg4oCTIHVzZSBhIGRvdCBvciB1bmRlcnNjb3JlIGluc3RlYWQgKGUuZy4sIG1hc3MuZ3JhbXMgb3IgbWFzc19ncmFtcykuIA0KICBkLiBMZWF2ZSBtaXNzaW5nIGNlbGxzIChpLmUuIG5vIGRhdGEpIGJsYW5rIGFuZCBhdm9pZCBub24tbnVtZXJpYyBjaGFyYWN0ZXJzIGluIGNvbHVtbnMgb2YgbnVtZXJpYyBkYXRhLg0KICAgIChhIHBlcmlvZCwgZXRjLiBkb2VzIG5vdCBuZWVkIHRvIGJlIGFkZGVkIGZvciBtaXNzaW5nIGRhdGEpDQoNCklmIHlvdSBlbnRlciBpbiBhIGNoYXJhY3RlciBmb3IgbWlzc2luZyBkYXRhLCBSIHdpbGwgYXNzdW1lIHRoYXQgdGhlIGVudGlyZSBjb2x1bW4gaXMgbm9uLW51bWVyaWMuIFNpbWlsYXJseSwgYXZvaWQgdXNpbmcgYSBxdWVzdGlvbiBtYXJrIOKAnDEyLjY3P+KAnSB0byBpbmRpY2F0ZSBhIG51bWJlciB5b3UgYXJlIG5vdCBzdXJlIGFib3V0Li4uYWRkIGEgbm90ZXMgY29sdW1uLiANCiAgDQogIGUuIERvIG5vdCBwdXQgaW4gYSB6ZXJvIGZvciBtaXNzaW5nIGRhdGEuLi56ZXJvcyBhcmUgYXNzdW1lZCB0byBiZSBkYXRhIQ0KICBmLiBEYXRlcyBzaG91bGQgYmUgaW4gaW50ZXJuYXRpb25hbCBmb3JtYXQgKFlZWVktTU0tREQpIG9yIHVzZSBzZXBhcmF0ZSBjb2x1bW5zIGZvciB5ZWFyLCBtb250aCBhbmQgZGF5Lg0KICBnLiBBdm9pZCBjb21tYXMgaW4geW91ciBkYXRhIHNldCBlbnRpcmVseSwgYmVjYXVzZSB0aGV5IGFyZSBjb2x1bW4gZGVsaW1pdGVycyBpbiB5b3VyIC5jc3YgZmlsZS4NCg0KVGhlcmUgYXJlIHR3byBjb21tb24gbGF5b3V0cyBmb3IgZGF0YTogIkxvbmciIHZzICJ3aWRlIiBsYXlvdXRzLiAgDQoNCipXaWRlIGxheW91dCoNClBsb3QgICAgU2l0ZSAgICAgIHNwZWNpZXMxICAgc3BlY2llczIgICBzcGVjaWVzMw0KIDEgICAgICAgIEEgICAgICAgICAgIDAgICAgICAgICAgMTIgICAgICAgICA0DQogMiAgICAgICAgQSAgICAgICAgICA4OCAgICAgICAgICAgMiAgICAgICAgIDANCiAzICAgICAgICBCICAgICAgICAgIDEyICAgICAgICAgICA0ICAgICAgICAgMSAgIA0KLi4uDQoNCipMb25nIGxheW91dCoNClBsb3QgICBTaXRlICBTcGVjaWVzIE51bWJlcg0KIDEgICAgICBBICAgICAgMSAgICAgIDANCiAxICAgICAgQSAgICAgIDIgICAgIDEyDQogMSAgICAgIEEgICAgICAzICAgICAgNA0KIDIgICAgICBBICAgICAgMSAgICAgODgNCiAyICAgICAgQSAgICAgIDIgICAgICAyDQogMiAgICAgIEEgICAgICAzICAgICAgMA0KIDMgICAgICBCICAgICAgMSAgICAgMTINCiAzICAgICAgQiAgICAgIDIgICAgICA0DQogMyAgICAgIEIgICAgICAzICAgICAgMQ0KLi4uDQoNCkluIGdlbmVyYWwgYSDigJxsb25n4oCdIGxheW91dCBpcyByZWNvbW1lbmRlZCBmb3IgY29uZHVjdGluZyBhbmFseXNlcyBpbiBSLCByYXRoZXIgdGhhbiBhIOKAnHdpZGXigJ0gbGF5b3V0LiBIb3dldmVyIHRoZSAid2lkZSBsYXlvdXQiIGNhbiBzb21ldGltZXMgYmUgbW9yZSBmZWFzaWJsZSBmb3IgYWNjdXJhdGUgZGF0YSBlbnRyeS4gWW91IGNhbiBjb252ZXJ0IGJldHdlZW4gdGhlc2UsIGhvd2V2ZXIgYmVmb3JlIGxlYXJuaW5nIGhvdyB0byBkbyB0aGF0IHRoZXJlIGFyZSBhIHNvbWUgaW1wb3J0YW50IHV0aWxpdHkgZnVuY3Rpb24gZm9yIGRhdGEgd3JhbmdsaW5nIHRoYXQgeW91IG5lZWQgdG8gYmUgZmFtaWxpYXIgd2l0aC4gDQoNCg0KKipEYXRhIHdyYW5nbGluZyB3aXRoIGRwbHlyIGFuZCB0aWR5cioqDQoNClRoZSBwYWNrYWdlIGBkcGx5cmAgaGFzIGJlZW4gc2FpZCB0byBiZSB0aGUgbW9zdCBwZXJmZWN0bHkgc3VpdGVkIHBhY2thZ2UgZm9yIHN0cmVhbWxpbmluZyB3b3JrZmxvdyBmb3IgcmVhbCBhbmFseXRpY3MuICBJdHMgdXRpbGl0eSBjb21lcyBmcm9tIHRoZSBmYWN0IHRoYXQgaXQgdXNlcyBhIGNvbWJpbmF0aW9uIG9mIGZpdmUgcHJpbWFyeSB2ZXJicyAoaS5lLiBmdW5jdGlvbnMgb3IgY29tbWFuZHMpIGFuZCBhIHByb2Nlc3MgY2FsbGVkIGNoYWluaW5nLg0KDQpUaGUgZml2ZSB2ZXJicyBhcmUNCmBmaWx0ZXIoKWANCmBzZWxlY3QoKWANCmBtdXRhdGUoKWANCmBhcnJhbmdlKClgDQpgc3VtbWFyaXplKClgDQoNCg0KYGBge3J9DQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShnZ3Bsb3QyKQ0KDQpnbGltcHNlKGRpYW1vbmRzKQ0KYGBgDQoNCioqRmlsdGVyKioNCg0KVGhlIGBmaWx0ZXIoKWAgY29tbWFuZCBhbGxvd3MgeW91IHRvIHN1YnNldCBhIGRhdGEgc2V0IG9ubHkgcmV0YWluaW5nIGRhdGEgZm9yIHJvd3MgdGhhdCB5b3UgYXJlIGludGVyZXN0ZWQgaW4uICBGb3IgZXhhbXBsZSwgd2Ugd2lsbCB1c2Ugb25lIG9mIGBSJ3NgIGJ1aWx0IGluIGRhdGEgc2V0cyBvbiBkaWFtb25kIGN1dHMgdG8gaWxsdXN0cmF0ZSBkaWZmZXJlbnQgYXNwZWN0cyBvZiB0aGUgcGFja2FnZXMgdXRpbGl0aWVzLiBMZXRzIGxvYWQgYW5kIGV4YW1pbmUgdGhlIGRhdGEgc2V0IGZpcnN0Lg0KDQpPa2F5LCBsZXRzIHVzZSB0aGUgZmlsdGVyaW5nIGNvbW1hbmQgdG8gc3Vic2V0IHRoZXNlIGRhdGEgc28gdGhhdCB3ZSBvbmx5IHJldGFpbiB2YWx1ZXMgZm9yIHRoZSBjYXNlcyB3aGVyZSBgY3V0YCB2YXJpYWJsZSBpcyBgImlkZWFsImAuDQoNCmBgYHtyfQ0KZDE9ZmlsdGVyKGRpYW1vbmRzLGN1dD09IklkZWFsIikNCmQxDQpgYGANCg0KKipTZWxlY3QqKg0KU2ltaWxhcmx5IHdlIGNhbiBjaG9vc2Ugb25seSBhIHN1YnNldCBvZiB0aGUgY29sdW1ucyAodmFyaWFibGVzKSBpbiB0aGUgZGF0YSBmcmFtZS4gRm9yIGV4YW1wbGUsIHdlIG1heSB3YW50IHRvIHNpbXBsaWZ5IHRoaW5ncyBieSBjcmVhdGluZyBhIG5ldyB3b3JraW5nIGRhdGEgZnJhbWUgdGhhdCBvbmx5IGhhcyB0aGUgdmFyaWFibGVzIG9mIGludGVyZXN0IGZvciBhIGdpdmVuIGFuYWx5c2lzLiAgSW4gdGhpcyBjYXNlIGxldHMgY3JlYXRlIGEgZGF0YSBzZXQgdGhhdCBvbmx5IHJldGFpbnMgdGhlIHZhcmlhYmxlczoNCg0KImN1dCIsImNhcmF0IiwiY29sb3IiLCJwcmljZSIsYW5kICJjbGFyaXR5Ii4gIA0KDQpUaGlzIGlzIGVhc2lseSBhY2NvbXBsaXNoZWQgdXNpbmcgdGhlIGBzZWxlY3QoKWAgY29tbWFuZC4NCg0KYGBge3J9DQoNCmQyPXNlbGVjdChkaWFtb25kcywgY2FyYXQsIGN1dCwgY29sb3IsIHByaWNlLCBjbGFyaXR5KQ0KZDINCmBgYA0KDQoqKk11dGF0ZSoqDQpBbm90aGVyIHZlcnkgY29tbW9uIHRhc2sgaW4gZGF0YSBhbmFseXRpYyBvciBhbnkgcHJvZ3JhbW1pbmcgdGFzayBpcyBhZGRpbmcgdmFyaWFibGVzLiAgV2UgY2FuIGRvIHRoaXMgdXNpbmcgdGhlIGBtdXRhdGUoKWAgY29tbWFuZC4gIEZvciBleGFtcGxlLCB3ZSBtaWdodCB3YW50IHRvIGFkZCBhIHZhcmlhYmxlIHRoYXQgcmVmbGVjdHMgdGhlIGNvc3RzIG9mIGRpYW1vbmRzIHBlciBjYXJhdCBvZiBxdWFsaXR5Lg0KDQpgYGB7cn0NCmQzPW11dGF0ZShkaWFtb25kcyxwcmljZV9wZXI9cHJpY2UvY2FyYXQpDQpkMw0KYGBgDQoNCioqQXJyYW5nZSoqDQoNCmBhcnJhbmdlKClgIHdvcmtzIGxpa2UgYW4gb3JkZXJpbmcgY29tbWFuZCwgYnV0IHVzaW5nIGBhcnJhbmdlKCknbWFrZXMgdGhlIHN5bnRheCBtdWNoIHNpbXBsZXIuICANCg0KTGV0cyBtYWtlIHVwIGEgZGF0YSBzZXQgdG8gaWxsdXN0cmF0ZSB0aGlzIGZ1bmN0aW9uLg0KDQpgYGB7cn0NCnNjcmFtYmxlPWRhdGEuZnJhbWUobnVtX3ZhciA9IGMoMiwzLDUsMSw0KSkNCmFycmFuZ2Uoc2NyYW1ibGUsbnVtX3ZhcikNCmFycmFuZ2Uoc2NyYW1ibGUsZGVzYyhudW1fdmFyKSkNCmBgYA0KDQoqKlN1bW1hcml6ZSoqDQpGaW5hbGx5IHRoZSBzdW1tYXJpemUoKSBjb21tYW5kIGRvZXMgZXhhY3RseSB3aGF0IGl0IHNvdW5kcyBsaWtlIGl0IGRvZXMuLi5pdCBhbGxvd3MgeW91IHRvIGdlbmVyYXRlIHN1bW1hcmllcyBvciBzdW1tYXJpemVkIHZlcnNpb25zIG9mIHRoZSBkYXRhLiAgRm9yIGV4YW1wbGUgd2UgY2FuIHVzZSBpdCB0byBjYWxjdWxhdGUgYSBzaW1wbGUgbWVhbiB1c2luZyBvdXIgc3Vic2V0IGRhdGEgc2V0IGZyb20gYWJvdmUuDQoNCmBgYHtyfQ0Kc3VtbWFyaXplKGRpYW1vbmRzLCBhdmdfcHJpY2UgPSBtZWFuKHByaWNlLCBuYS5ybSA9IFRSVUUpKQ0KYGBgDQoNCioqZ3JvdXBfYnkqKg0KDQpXZSBjYW4gYWxzbyB1c2UgdGhpcyB0byBnZW5lcmF0ZSBtb3JlIGNvbXBsaWNhdGVkIHN1bW1hcmllcyBvZiB0aGUgZGF0YSB1c2luZyBhIHN1YiBmdW5jdGlvbiBjYWxsZWQgYGdyb3VwX2J5KClgLiAgRm9yIGV4YW1wbGUgd2UgY2FuIGdvIGJhY2sgdG8gdGhlIG9yaWdpbmFsIGRpYW1vbmRzIGRhdGEgc2V0IGFuZCBzdW1tYXJpemUgdGhlIGRhdGEgYnkgY2FsY3VsYXRpbmcgbWVhbnMgYWNjb3JkaW5nIHRvIGFsbCBjdXQgdHlwZXMuDQoNCmBgYHtyfQ0KaGVhZChkaWFtb25kcykNCmQxPWdyb3VwX2J5KGRpYW1vbmRzLGN1dCxjb2xvcikNCnN1bW1hcml6ZShkMSwgYXZnX3ByaWNlID0gbWVhbihwcmljZSwgbmEucm0gPSBUUlVFKSxzZC5wcmljZT1zZChwcmljZSxuYS5ybT1UUlVFKSkNCmBgYA0KDQoqKkNoYWlucyBvciBQaXBlcyoqDQpUaGUgcmVhbCBwb3dlciBvZiB0aGlzIGxpYnJhcnkgaXMgbm90IGZ1bGx5IHJlYWxpemVkIGhvd2V2ZXIgdW50aWwgeW91IHN0YXJ0IGBjaGFpbmluZ2AgY29tbWFuZHMgdG9nZXRoZXIuICBZb3UgY2FuIGNoYWluIHRvZ2V0aGVyIGRpZmZlcmVudCB2ZXJicyBvZiBgZHBseXJgIHVzaW5nIHRoZSBgJT4lYCBvcGVyYXRvci4gIEFsbCB0aGlzIG9wZXJhdG9yIGRvZXMgaXMgYWxsb3cgeW91IHRvIGNvbm5lY3QgY29tbWFuZHMgdG9nZXRoZXIgc28gdGhhdCB0aGUgb3V0cHV0IG9mIG9uZSBjb21tYW5kIGJlY29tZXMgdGhlIGlucHV0IGZvciB0aGUgbmV4dCBkb3duIGEgY2hhaW4uICBGb3IgZXhhbXBsZSB3ZSBjYW4gZG8gYWxsIHRoZSBzdGVwcyBhYm92ZSBvbiB0aGUgZGlhbW9uZHMgZGF0YSBzZXQgaW4gYSAgc2luZ2xlIGNoYWluIG9mIGNvbW1hbmRzLg0KDQpgYGB7cn0NCmdyb3Vwcw0KZmluYWwuZGlhbW9uZHM9IGRpYW1vbmRzICU+JQ0KICAgICAgICAgICAgICAgIGZpbHRlcihjdXQ9PSJJZGVhbCIpICU+JQ0KICAgICAgICAgICAgICAgIHNlbGVjdChjYXJhdCwgY3V0LCBjb2xvciwgcHJpY2UsIGNsYXJpdHkpICU+JQ0KICAgICAgICAgICAgICAgIG11dGF0ZShwcmljZV9wZXJfY2FyYXQgPSBwcmljZS9jYXJhdCkNCmBgYA0KVGhpcyBjaGFpbmVkIHNldCBvZiBzeW50YXggbGl0ZXJhbGx5IHNheXM6DQrigJMg4oCcVGFrZSB0aGUgZGlhbW9uZHMgZGF0YSBzZXTigJkg4oCdDQrigJMg4oCcVGhlbiBmaWx0ZXIgaXQsIGtlZXBpbmcgb25seSB0aGUgcm93cyB3aGVyZSDigJhjdXTigJkgZXF1YWxzIOKAmElkZWFs4oCZIOKAnQ0K4oCTIOKAnFRoZW4gc2VsZWN0IHNwZWNpZmljIHZhcmlhYmxlcywg4oCYY2FyYXTigJksIOKAmGN1dOKAmSwg4oCYY29sb3LigJksIOKAmHByaWNlLCDigJhjbGFyaXR54oCZIOKAnQ0K4oCTIOKAnFRoZW4gY3JlYXRlIGEgbmV3IHZhcmlhYmxlLCDigJhwcmljZV9wZXJfY2FyYXTigJkgdXNpbmcg4oCYbXV0YXRlKCnigJkg4oCdDQoNCkZpbmFsbHksIGBkcGx5cmAgY2FuIGFsc28gYmUgYSBwb3dlcmZ1bCB0b29sIGZvciBkYXRhIGV4cGxvcmF0aW9uIHdoZW4gcGFpcmVkIHdpdGggYGdncGxvdGAuICBJdHMgcG93ZXIgY29tZXMgZnJvbSB0aGUgZmFjdCB0aGF0IHlvdSBjYW4gY2hhaW4gdG9nZXRoZXIgZHBseXIgY29tbWFuZHMgYW5kIGdncGxvdCBjb21tYW5kcy4gRm9yIGV4YW1wbGUgd2UgY2FuIGNyZWF0ZSBhIGJveCBwbG90IGZvciBqdXN0IHRoZSBpZGVhbCBkaWFtb25kcyBieQ0KDQpgYGB7cn0NCiAgZGlhbW9uZHMgJT4lICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgU3RhcnQgd2l0aCB0aGUgJ2RpYW1vbmRzJyBkYXRhc2V0DQogIGZpbHRlcihjdXQgPT0gIklkZWFsIikgJT4lICAgICAgICAgICAgICAgICAgICAgICAgIyBUaGVuLCBmaWx0ZXIgZG93biB0byByb3dzIHdoZXJlIGN1dCA9PSBJZGVhbA0KICBnZ3Bsb3QoYWVzKHg9Y29sb3IseT1wcmljZSkpICsgICAgICAgICAgICAgICAgICAgICAjIFRoZW4sIHBsb3QgdXNpbmcgZ2dwbG90DQogIGdlb21fYm94cGxvdCgpICAgICAgDQoNCmRpYW1vbmRzICU+JSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIFN0YXJ0IHdpdGggdGhlICdkaWFtb25kcycgZGF0YXNldA0KICBmaWx0ZXIoY3V0ID09ICJQcmVtaXVtIikgJT4lICAgICAgICAgICAgICAgICAgICAgICAgIyBUaGVuLCBmaWx0ZXIgZG93biB0byByb3dzIHdoZXJlIGN1dCA9PSBJZGVhbA0KICBnZ3Bsb3QoYWVzKHg9Y2FyYXQseT1wcmljZSkpICsgICAgICAgICAgICAgICAgICAgICAjIFRoZW4sIHBsb3QgdXNpbmcgZ2dwbG90DQogIGdlb21fcG9pbnQoKQ0KDQpgYGANCg0Kb3IgYSBoaXN0b2dyYW0gYnkNCg0KYGBge3J9DQpkaWFtb25kcyAlPiUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIyBTdGFydCB3aXRoIHRoZSAnZGlhbW9uZHMnIGRhdGFzZXQNCiAgZmlsdGVyKGN1dCA9PSAiSWRlYWwiKSAlPiUgICAgICAgICAgICAgICAgICAgICAgICAjIFRoZW4sIGZpbHRlciBkb3duIHRvIHJvd3Mgd2hlcmUgY3V0ID09IElkZWFsDQogIGdncGxvdChhZXMocHJpY2UpKSArICAgICAgICAgICAgICAgICAgICAgICAgICAgICMgVGhlbiwgcGxvdCB1c2luZyBnZ3Bsb3QNCiAgICBnZW9tX2hpc3RvZ3JhbSgpICsgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIGFuZCBwbG90IGhpc3RvZ3JhbXMNCiAgICBmYWNldF93cmFwKH4gY29sb3IpICAgICAgICAgICAgICAgICAgICAgICAgICAgICAjIGluIGEgJ3NtYWxsIG11bHRpcGxlJyBwbG90LCBicm9rZW4gb3V0IGJ5ICdjb2xvcicNCmBgYA0KDQoqKldpZGUgdG8gTG9uZyBhbmQgQmFjayBhZ2FpbioqDQpJbiBSIG1vc3QgZnVuY3Rpb25zIGV4cGVjdCBkYXRhIHRvIGJlIGluIGEgbG9uZyBmb3JtYXQgcmF0aGVyIHRoYW4gYSB3aWRlIGZvcm1hdCwgaG93ZXZlciBlYXNlIG9mIGRhdGEgZW50cnkgYW5kIGRhdGEgZm9ybWF0dGVkIGZvciBzb21lIG90aGVyIHN0YXRpc3RpY2FsIHNvZnR3YXJlIChlLmcuIFNQU1MpIG1heSByZXN1bHQgaW4gZGF0YSBpbiB0aGUgd2lkZSBmb3JtYXQuIFRvIGRlYWwgd2l0aCB0aGlzIHByb2JsZW0gdGhlcmUgYXJlIHNvbWUgbmlmdHkgbWV0aG9kcyB1c2luZyB0aGUgYGdhdGhlcigpYCBhbmQgYHNwcmVhZCgpYCBmdW5jdGlvbnMgZnJvbSB0aGUgYHRpZHlyYCBsaWJyYXJ5Lg0KDQpGaXJzdCwgIHdlIHdpbGwgZ2VuZXJhdGUgKHNpbXVsYXRlKSBkYXRhIHRoYXQgaXMgY29sbGVjdGVkIGluIHdpZGUgYW5kIGluIGxvbmcgZm9ybWF0LiAgVGhlIGRhdGEgYXJlIGV4YWN0bHkgdGhlIHNhbWUgZm9yIGJvdGggZm9ybWF0cy4gVGhlbiB3ZSBjYW4gdXNlIGRwbHlyIHRvIGNvbnZlcnQgZWFjaCB0byB0aGUgb3RoZXIgZm9ybWF0IGJlbG93Lg0KDQpGaXJzdCB3ZSB3aWxsIG1ha2UgdGhlIHdpZGUgZm9ybWF0dGVkIGRhdGEuLi4udGFrZSBub3RlIG9uIHRoZSBnZW5lcmF0aW9uIG9mIHRoZXNlIGRhdGEuLi4NCg0KYGBge3J9DQp3aWRlIDwtIHJlYWQudGFibGUoaGVhZGVyPVRSVUUsIHRleHQ9Jw0KIHN1YmplY3Qgc2V4IGNvbnRyb2wgY29uZDEgY29uZDINCiAgICAgICAxICAgTSAgICAgNy45ICAxMi4zICAxMC43DQogICAgICAgMiAgIEYgICAgIDYuMyAgMTAuNiAgMTEuMQ0KICAgICAgIDMgICBGICAgICA5LjUgIDEzLjEgIDEzLjgNCiAgICAgICA0ICAgTSAgICAxMS41ICAxMy40ICAxMi45DQonKQ0KDQpgYGANCioqQ2hhbGxlbmdlIHVzZSBkcGx5ciB0byB0dXJuIHN1YmplY3QgaW50byBhIGZhY3RvcikqKg0KYGBge3J9DQojIE1ha2Ugc3VyZSB0aGUgc3ViamVjdCBjb2x1bW4gaXMgYSBmYWN0b3INCg0KYGBgDQoNCkFuZCBub3cgdGhlIHNhbWUgZGF0YSBpbiB0aGUgbG9uZyBmb3JtYXQuDQoNCmBgYHtyfQ0KbG9uZyA8LSByZWFkLnRhYmxlKGhlYWRlcj1UUlVFLCB0ZXh0PScNCiBzdWJqZWN0IHNleCBjb25kaXRpb24gbWVhc3VyZW1lbnQNCiAgICAgICAxICAgTSAgIGNvbnRyb2wgICAgICAgICA3LjkNCiAgICAgICAxICAgTSAgICAgY29uZDEgICAgICAgIDEyLjMNCiAgICAgICAxICAgTSAgICAgY29uZDIgICAgICAgIDEwLjcNCiAgICAgICAyICAgRiAgIGNvbnRyb2wgICAgICAgICA2LjMNCiAgICAgICAyICAgRiAgICAgY29uZDEgICAgICAgIDEwLjYNCiAgICAgICAyICAgRiAgICAgY29uZDIgICAgICAgIDExLjENCiAgICAgICAzICAgRiAgIGNvbnRyb2wgICAgICAgICA5LjUNCiAgICAgICAzICAgRiAgICAgY29uZDEgICAgICAgIDEzLjENCiAgICAgICAzICAgRiAgICAgY29uZDIgICAgICAgIDEzLjgNCiAgICAgICA0ICAgTSAgIGNvbnRyb2wgICAgICAgIDExLjUNCiAgICAgICA0ICAgTSAgICAgY29uZDEgICAgICAgIDEzLjQNCiAgICAgICA0ICAgTSAgICAgY29uZDIgICAgICAgIDEyLjkNCicpDQpgYGANCg0KKipDaGFsbGVuZ2UgdXNlIGRwbHlyIHRvIHR1cm4gc3ViamVjdCBpbnRvIGEgZmFjdG9yKSoqDQpgYGB7cn0NCiMgTWFrZSBzdXJlIHRoZSBzdWJqZWN0IGNvbHVtbiBpcyBhIGZhY3Rvcg0KDQpgYGANCg0KVG8gY29udmVydCB0aGUgZGF0YSBmcm9tIHdpZGUgdG8gbG9uZyBmb3JtYXQgd2Ugd2lsbCB1c2UgdGhlIGBnYXRoZXJgIGZ1bmN0aW9uLiAgVGhpcyBmdW5jdGlvbiBoYXMgc2V2ZXJhbCBrZXkgcGllY2VzIG9mIGlucHV0IChhcmd1bWVudHMpIHRoYXQgYXJlIG5lZWRlZC4gVGhlc2UgYXJlDQoNCmBnYXRoZXIoZGF0YSxrZXksdmFsdWUsLi4uc291cmNlcyxmYWN0b3Iga2V5KWANCiAgIyAtIGRhdGE6IERhdGEgb2JqZWN0DQogICMgLSBrZXk6IE5hbWUgb2YgbmV3IGtleSBjb2x1bW4gKG1hZGUgZnJvbSBuYW1lcyBvZiBkYXRhIGNvbHVtbnMpDQogICMgLSB2YWx1ZTogTmFtZSBvZiBuZXcgdmFsdWUgY29sdW1uDQogICMgLSAuLi46IE5hbWVzIG9mIHNvdXJjZSBjb2x1bW5zIHRoYXQgY29udGFpbiB2YWx1ZXMNCiAgIyAtIGZhY3Rvcl9rZXk6IFRyZWF0IHRoZSBuZXcga2V5IGNvbHVtbiBhcyBhIGZhY3RvciAoaW5zdGVhZCBvZiBjaGFyYWN0ZXIgdmVjdG9yKQ0KICANClNvIHRvIGltcGxlbWVudCB3ZSBqdXN0IG5lZWQgdG8gcnVuOg0KYGBge3J9DQpyZXF1aXJlKHRpZHlyKQ0Kd2lkZV90b19sb25nIDwtIGdhdGhlcih3aWRlLCBjb25kaXRpb24sIG1lYXN1cmVtZW50LCBjb250cm9sOmNvbmQyLCBmYWN0b3Jfa2V5PVRSVUUpDQp3aWRlX3RvX2xvbmcNCmBgYA0KDQpJbiB0aGlzIGV4YW1wbGUsIHRoZSBzb3VyY2UgY29sdW1ucyB0aGF0IGFyZSBnYXRoZXJlZCBhcmUgc3BlY2lmaWVkIHdpdGggYGNvbnRyb2w6Y29uZDJgLiBUaGlzIG1lYW5zIHRvIHVzZSBhbGwgdGhlIGNvbHVtbnMsIHBvc2l0aW9uYWwsIGJldHdlZW4gYGNvbnRyb2xgIGFuZCBgY29uZDJgLiBBbm90aGVyIHdheSBvZiBkb2luZyBpdCBpcyB0byBuYW1lIHRoZSBjb2x1bW5zIGluZGl2aWR1YWxseSwgYXMgaW46DQoNCmBgYHtyfQ0Kd2lkZV90b19sb25nIDwtIGdhdGhlcih3aWRlLCBjb25kaXRpb24sIG1lYXN1cmVtZW50LCBjb250cm9sLCBjb25kMSwgY29uZDIsIGZhY3Rvcl9rZXk9VFJVRSkNCndpZGVfdG9fbG9uZw0KYGBgDQoNCkluIHNvbWUgY2FzZXMgeW91IG1heSB3YW50IHRvIHVzZSBgZ2F0aGVyKClgIGludGVybmFsbHkgaW4gc29tZSBsYXJnZXIgZnVuY3Rpb24gYW5kIHNvIHlvdSB3aWxsIHdhbnQgdG8gdXNlIHZhcmlhYmxlcyBjb250YWluaW5nIGNvbHVtbiBuYW1lcyByYXRoZXIgdGhhbiB0aGUgY29sdW1uIG5hbWVzIHRoZW1zZWx2ZXMuIFRvIGRvIHRoaXMgdXNlIHRoZSBgZ2F0aGVyXygpYCBmdW5jdGlvbiBpbnN0ZWFkLg0KDQpgYGB7cn0NCmtleWNvbCA8LSAiY29uZGl0aW9uIg0KdmFsdWVjb2wgPC0gIm1lYXN1cmVtZW50Ig0KZ2F0aGVyY29scyA8LSBjKCJjb250cm9sIiwgImNvbmQxIiwgImNvbmQyIikNCg0KZ2F0aGVyXyh3aWRlLCBrZXljb2wsIHZhbHVlY29sLCBnYXRoZXJjb2xzKQ0KYGBgDQoNClRvIGNvbnZlcnQgb3VyIGRhdGEgZnJvbSB0aGUgbG9uZyBmb3JtYXQgdG8gdGhlIHdpZGUgZm9ybWF0IHdlIGRvIHRoZSBzYW1lIHN0ZXBzIGV4Y2VwdCB3ZSB1c2UgdGhlIGBzcHJlYWRgIGZ1bmN0aW9uIGluc3RlYWQgb2YgdGhlIGBnYXRoZXJgIGZ1bmN0aW9uLg0KDQpgYGB7cn0NCmxvbmdfdG9fd2lkZSA8LSBzcHJlYWQod2lkZV90b19sb25nLCBjb25kaXRpb24sIG1lYXN1cmVtZW50KQ0KbG9uZ190b193aWRlDQpgYGANCg0KKipDb250cmFzdGluZyBiYXNlIFIgYW5kIGRwbHlyKioNCg0KQmVsb3cgYXJlIHNvbWUgdXNlZnVsIGRhdGEgbWFuaXB1bGF0aW9uIGFwcHJvYWNoZXMgdXNpbmcgYmFzZSBSIGZ1bmN0aW9uYWxpdHkgYW5kIHRoZSBuZXcgZnVuY3Rpb25zLiBJdCBpcyB1c2VmdWwgdG8gbGVhcm4gYm90aCBiZWNhdXNlIHRoZSBiYXNlIFIgdmVyc2lvbiBnaXZlcyBhIG1vcmUgaW50dWl0aXZlIHVuZGVyc3RhbmRpbmcgb2Ygd2hhdCB0aCBjb2RlIGlzIGFjdHVhbGx5IGRvaW5nLCB3aGVyZSBhcyB0aGUgbmV3IGZ1bmN0aW9uYWxpdHkgb2YgZHBseSBwcm92aWRlcyBzb21lIGVmZmljaWVudCB0b29scy4gIEkgb2Z0ZW4gdXNlIHRoZXNlIGludGVyY2hhbmdlYWJseSBkZXBlbmRpbmcgb24gdGhlIGFwcGxpY2F0aW9uLCBJIHByZWZlciBvbmUgb3IgdGhlIG90aGVyLg0KDQoqVHJhbnNmb3JtIGEgY29sdW1uICh2YXJpYWJsZSkgd2l0aGluIGEgZGF0YSBmcmFtZSoNCg0KRm9yIGV4YW1wbGUsIGxvZyB0cmFuc2Zvcm0gYSB2YXJpYWJsZSBuYW1lZCBtYXNzLmdyYW1zIGFuZCBzYXZlIHRoZSByZXN1bHQgYXMgYSBuZXcgdmFyaWFibGUgbmFtZWQgbG9nc2l6ZSBpbiB0aGUgZGF0YSBmcmFtZS4gKGxvZyB5aWVsZHMgdGhlIG5hdHVyYWwgbG9nLCB3aGVyZWFzIHRoZSBmdW5jdGlvbiBsb2cxMCB5aWVsZHMgbG9nIGJhc2UgMTAuKQ0KDQpgYGB7cn0NCm15ZGF0YSRsb2dzaXplIDwtIGxvZyhteWRhdGEkYm9keV9tYXNzX2tnKSAgICAgICAgICAgIA0Kc3RyKG15ZGF0YSkNCm15ZGF0YSA8LSBtdXRhdGUobXlkYXRhLCBsb2cxMHNpemUgPSBsb2cxMChib2R5X21hc3Nfa2cpKSANCnN0cihteWRhdGEpDQoNCmBgYA0KDQoqRGVsZXRlIGEgdmFyaWFibGUgZnJvbSBkYXRhIGZyYW1lKg0KDQpGb3IgZXhhbXBsZSwgdG8gZGVsZXRlIHRoZSB2YXJpYWJsZSBzcGVjaWVzIGZyb20gbXlkYXRhLCB1c2UNCmBgYHtyfQ0KbXlkYXRhMiA8LSBzZWxlY3QobXlkYXRhLCAtbmFtZSkgIyBkcGx5ciBtZXRob2QNCm15ZGF0YTINCmBgYA0KDQoqRXh0cmFjdCBhIGRhdGEgc3Vic2V0Kg0KDQpUaGVyZSBhcmUgc2V2ZXJhbCB3YXlzLiBPbmUgaXMgdG8gdXNlIGluZGljYXRvcnMgaW5zaWRlIHNxdWFyZSBicmFja2V0cyB1c2luZyB0aGUgZm9sbG93aW5nIGZvcm1hdDogbXlkYXRhW3Jvd3MsIGNvbHVtbnNdLiBCZSBzdXJlIHRvIGxvb2sgYWZ0ZXIgZWFjaCBsaW5lIGJlbG93IHRvIHNlZSB3aGF0IHRoZSBjb21tYW5kcyBkaWQgdG8gdGhlIGRhdGEgc2V0DQoNCmBgYHtyfQ0KbmV3ZGF0YSA8LSBteWRhdGFbICwgYygyLDMpXSAgICMgYWxsIHJvd3MsIGNvbHVtbnMgMiBhbmQgMyBvbmx5Ow0KbmV3ZGF0YSA8LSBteWRhdGFbICwgLTFdICAgICAgICMgYWxsIHJvd3MsIGxlYXZlIG91dCBmaXJzdCBjb2x1bW4NCm5ld2RhdGEgPC0gbXlkYXRhWzE6MywgMToyXSAgICAjIGZpcnN0IHRocmVlIHJvd3MsIGZpcnN0IHR3byBjb2x1bW5zDQpgYGANCg0KDQpUaGUgc3Vic2V0IGNvbW1hbmQgaW4gYmFzZSBSIGlzIGVhc3kgdG8gdXNlIHRvIGV4dHJhY3Qgcm93cyBhbmQgY29sdW1ucy4gVXNlIHRoZSBzZWxlY3QgYXJndW1lbnQgdG8gc2VsZWN0IGNvbHVtbnMgKHZhcmlhYmxlcykuIEZvciBleGFtcGxlLCB0byBwdWxsIG91dCByb3dzIGNvcnJlc3BvbmRpbmcgdG8gY29udGluZW50IEFGIHdpdGggbG9nc2l6ZSA8IDI1LCBhbmQgdGhlIHRocmVlIHZhcmlhYmxlcywgImNvbnRpbmVudCIsInN0YXR1cyIsIm9yZGVyIiB5b3UgY291bGQgIHVzZSB0aGUgZm9sbG93aW5nLg0KDQpgYGB7cn0NCm5ld2RhdGEgPC0gc3Vic2V0KG15ZGF0YSwgYm9keV9tYXNzX2tnPj0gMTAgJiBicmFpbl9tYXNzX2cgPCAyNTApDQpuZXdkYXRhDQpgYGANCg0KKipTb3J0IGFuZCBvcmRlciB0aGUgcm93cyoNCg0KVG8gcmUtb3JkZXIgdGhlIHJvd3Mgb2YgYSBkYXRhIGZyYW1lIG15ZGF0YSB0byBjb3JyZXNwb25kIHRvIHRoZSBzb3J0ZWQgb3JkZXIgb2Ygb25lIG9mIGl0cyB2YXJpYWJsZXMsIHNheSB4LCB1c2UNCg0KYGBge3J9DQpteWRhdGEueCA8LSBteWRhdGFbb3JkZXIobXlkYXRhJHgpLCBdICAjIGJhc2UgUg0KbXlkYXRhLnggPC0gYXJyYW5nZShteWRhdGEsIDEpICAgICAgICAgIyBkcGx5ciBtZXRob2QNCmBgYA0KDQoNCioqQ2hhbGxlbmdlMSoqDQoNCldyaXRlIGEgc2luZ2xlIGNvbW1hbmQgKHdoaWNoIGNhbiBzcGFuIG11bHRpcGxlIGxpbmVzIGFuZCBpbmNsdWRlcyBwaXBlcykgdGhhdCB3aWxsIHByb2R1Y2UgYSBkYXRhIGZyYW1lIGZyb20gdGhlIGRpYW1vbmRzIGRhdGEgdGhhdCBvbmx5IGhhcyB0aGUgdmFsdWVzIGZvciBjb2xvciAiSiIgb2YgdGhlIHZhcmlhYmxlcyBwcmljZSwgY3V0IGFuZCBjYXJhdC4gDQoNCmBgYHtyfQ0KDQoNCmBgYA0KKipDaGFsbGVuZ2UgMioqDQoNCkNhbGN1bGF0ZSB0aGUgYXZlcmFnZSBwcmljZSBwZXIgY29sb3Igd2hpY2ggaGFzIHRoZSBoaWdoZXN0IGF2ZXJhZ2UgcHJpY2UgYW5kIHdoaWNoIGhhcyB0aGUgc2hvcnRlc3QgYXZlcmFnZSBwcmljZT8NCg0KYGBge3J9DQoNCmBgYA0KKipDaGFsbGVuZ2UgMyoqDQoNCkluIG9uZSBwaXBlZCBvciBjaGFpbmVkIHNldCBvZiBjb2RlIGZpbHRlciBnZW5lcmF0ZSBhIHBsb3QgdGhhdCBwbG90cyB0aGUgbWVhbnMgYW5kIENJcyBvZiBhIG5ldyB2YXJpYWJsZSBjYWxsZWQgdW5pdF9wcmljZSB0aGF0IGlzIHRoZSBwcmljZSBwZXIgY2FyYXQgYnV0IG9ubHkgZm9yIGRpYW1vbmRzIHRoYXQgd2VyZSByYXRlZCBhcyBJZGVhbCwgUHJlbWl1bSwgb3IgVmVyeSBHb29kIHdpdGggZWFjaCBjb2xvciBvZiBkaWFtb25kIGRlcGljdGVkIGluIGEgZGlmZmVyZW50IGNvbG9yIGFuZCBpbiBhIGRpZmZyZW50IHBhbmVsLiANCg0KYGBge3J9DQoNCmBgYA0KDQo=