1.Introduction

In this lesson you will learn about aggregates in R using dplyr. An aggregate statistic is a way of creating a single number that describes a group of numbers. Common aggregate statistics include mean, median, and standard deviation.

Additionally, you will learn how you can group data into different subsets based on column values. This can help narrow the focus of a summary statistic to a subset of a dataset. R you ready to get started?

Instructions

In the workspace to the right we have loaded data from ShoeFly.com, a fictional e-commerce shoe store. The data includes information regarding customer orders as well as the source of page visits to ShoeFly.com’s website.

Review the code and the output. What calculations are made, and on which subsets of the data do they occur?

Throughout the rest of this lesson you will dig into ShoeFly.com’s data to learn more about its customer’s orders and how users are discovering the website. Proceed to the next exercise to get started!

# load packages
library(readr)
library(dplyr)
# load data
orders <- read_csv("orders.csv")
page_visits <- read_csv("page_visits.csv")
# inspect data frames
orders
page_visits
# average price of order
average_price <- orders %>% 
  summarize(mean_price = mean(price, na.rm = TRUE))
average_price
# page visits by UTM source
click_source <- page_visits %>%
  group_by(utm_source) %>%
  summarize(count = n())
click_source
# page visits by UTM source and month
click_source_by_month <- page_visits %>%
  group_by(utm_source,month) %>%
  summarize(count = n())
click_source_by_month

2.Calculating Column Statistics

In this exercise, you will learn how to combine all of the values from a column for a single calculation. This can be done with the help of the dplyr function summarize(), which returns a new data frame containing the desired calculation.

Some examples of this type of calculation include:

1.The data frame customers contains the names and ages of all of your customers. You want to find the median age:

customers <- data.frame(
  name = c("Alice", "Bob", "Claire", "Daniel", "Elaine", "Frank", "Grace"),
  age = c(23, 25, 31, 35, 35, 46, 62)
)
customers %>%
  select(age)
#c(23, 25, 31, 35, 35, 46, 62)
customers %>%
  summarize(median_age = median(age))
#35

2.The data frame shipments contains address information for all shipments that you’ve sent out in the past year. You want to know how many different states you have shipped to.

shipments <- data.frame(
  states = c('CA', 'CA', 'CA', 'CA', 'NY', 'NY', 'NJ', 'NJ', 'NJ', 'NJ', 'NJ', 'NJ', 'NJ')
)
shipments %>%
  summarize(n_distinct_states = n_distinct(states))
#3

3.The data frame inventory contains a list of types of t-shirts that your company makes. You want to know the standard deviation of the prices of your inventory.

inventory <- data.frame(
  price = c(31, 23, 30, 27, 30, 22, 27, 22, 39, 27, 36)
)
inventory %>%
  summarize(sd_price = sd(price))

The general syntax for these calculations is:

df %>% summarize(var_name = command(column_name))

1.df is the data frame you are working with

2.summarize is a dplyr function that reduces multiple values to a single value

3.var_name is the name you assign to the column that stores the results of the summary function in the returned data frame

4.command is the summary function that is applied to the column by summarize()

5.column_name is the name of the column of df that is being summarized

The following table includes common summary functions that can be given as an argument to summarize():

knitr::include_graphics("C:/Users/kuoan/Desktop/R Code/Aggregates1.png")

Instructions

1.ShoeFly.com has a new batch of orders stored in the data frame orders. Inspect the first 10 rows of the data frame using head().

# load order data
orders <- read_csv("orders.csv")

# inspect orders here:
head(orders, 10)

2.Our finance department wants to know the price of the most expensive pair of shoes purchased. Save your answer to the variable most_expensive.

# define most_expensive here:
most_expensive <- orders %>%
  summarize(most_expensive =  max(price))
most_expensive

3.Woah, wait a minute! Take a look at the output of the code you just ran. The result for the most expensive pair of shoes is coming back as NA. Why is this happening?

If you scroll up in the rendered notebook to where orders.csv is loaded, you can see a warning about row 99 of the file. There is a missing column of information! It appears that the price for row 99 was not in the file, and this is causing your maximum value calculation to return NA.

Add the following as an additional argument to max() so that it removes all missing values before computing the maximum value. ” na.rm = TRUE ”

# define most_expensive here:
most_expensive <- orders %>%
  summarize(most_expensive =  max(price, na.rm = TRUE))
most_expensive

4.Our fashion department wants to know how many different colors of shoes we are selling. Save your answer to the variable num_colors.

# define num_colors here:
num_colors <- orders %>%
  summarize(num_colors = n_distinct(shoe_color, na.rm = TRUE))
num_colors 

3.Calculating Aggregate Functions I

When we have a bunch of data, we often want to calculate aggregate statistics (mean, standard deviation, median, percentiles, etc.) over certain subsets of the data.

Suppose we have a grade book with columns student, assignment_name, and grade:

knitr::include_graphics("C:/Users/kuoan/Desktop/R Code/Aggregates2.png")

We want to get an average grade for each student across all assignments. We can do this using the helpful dplyr function group_by().

For this example, we’d use the following piece of code:

” grades <- df %>% group_by(student) %>% summarize(mean_grade = mean(grade)) ”

The output might look something like this:

knitr::include_graphics("C:/Users/kuoan/Desktop/R Code/Aggregates3.png")

In general, we use the following syntax to calculate aggregates:

” df %>% group_by(column_1) %>% summarize(aggregate_name = command(column_2)) ”

1.column_1 (student in our example) is the column that we want to group_by()

2.column_2 (grade in our example) is the column that we want to apply command(), a summary function, to using summarize()

3.aggregate_name is the name assigned to the calculated aggregate

In addition to the summary functions discussed in the last exercise (mean(), median(), sd(), var(), min(), max(), IQR() and n_distinct()), another helpful summary function, especially for grouped data, is n(). n() will return the count of the rows within a group, and does not require a column as an argument. To get the count of the rows in each group of students from our example:

” grades <- df %>% group_by(student) %>% summarize(count = n()) ”

Instructions

  1. Let’s return to our orders data from ShoeFly.com.

In the previous exercise, our finance department wanted to know the most expensive shoe that we sold.

Now, they want to know the price of the most expensive shoe for each shoe_type (i.e., the price of the most expensive boot, the price of the most expensive ballet flat, etc.). Name the column that shows the most expensive shoe prices max_price.

Save your answer to the variable pricey_shoes, and view it.

# define pricey_shoes here:
pricey_shoes <- orders %>%
  group_by(shoe_type) %>%
  summarize(max_price = max(price, na.rm = TRUE))

pricey_shoes

2.The inventory team wants to know how many of each shoe_type has been sold so they can forecast inventory for the future.

Save your answer to the variable shoes_sold, and view it.

# define shoes_sold here:
shoes_sold <- orders %>%
  group_by(shoe_type) %>%
  summarize(count = n())

shoes_sold

4.Calculating Aggregate Functions II

Sometimes, we want to group by more than one column. We can do this by passing multiple column names as arguments to the group_by function.

Imagine that we run a chain of stores and have data about the number of sales at different locations on different days:

knitr::include_graphics("C:/Users/kuoan/Desktop/R Code/Aggregates4.png")

We suspect that sales are different at different locations on different days of the week. In order to test this hypothesis, we could calculate the average sales for each store on each day of the week across multiple months. The code would look like this:

” df %>% group_by(location,day_of_week) %>% summarize(mean_total_sales = mean(total_sales)) ”

And the results might look something like this:

knitr::include_graphics("C:/Users/kuoan/Desktop/R Code/Aggregates5.png")

Instructions

1.At ShoeFly.com, our Purchasing team thinks that certain shoe_type/shoe_color combinations are particularly popular this year (for example, blue ballet flats are all the rage in Paris).

Find the total number of shoes of each shoe_type/shoe_color combination purchased using group_by, summarize() and n(). Name the aggregate count column count. Save your result to the variable shoe_counts, and view it.

# define shoe_counts here:
shoe_counts <- orders %>%
  group_by(shoe_type, shoe_color) %>%
  summarize(count = n())

shoe_counts

2.The Marketing team wants to better understand the different price levels of the kinds of shoes that have been sold on the website, in particular looking at shoe_type/shoe_material combinations.

Find the mean price of each shoe_type/shoe_material combination purchased using group_by, summarize() and mean(). Assign the name mean_price to the calculated aggregate. Save your result to the variable shoe_prices, and view it.

Don’t forget to include na.rm = TRUE as an argument in the summary function that you call!

# define shoe_prices here:
shoe_prices <- orders %>%
  group_by(shoe_type, shoe_material) %>%
  summarize(mean_price = mean(price, na.rm = TRUE))

shoe_prices

5.Combining Grouping with Filters

While group_by() is most often used with summarize() to calculate summary statistics, it can also be used with the dplyr function filter() to filter rows of a data frame based on per-group metrics.

Suppose you work at an educational technology company that offers online courses and collects user data in an enrollments data frame:

# 建立資料框
enrollments <- data.frame(
  user_id = c(1234, 1234, 4567, 4567),
  course = c("learn_r", "learn_python", "learn_r", "learn_python"),
  quiz_score = c(80, 95, 90, 55)
)

enrollments

You want to identify all the enrollments in difficult courses, which you define as courses with an average quiz_score less than 80. To filter the data frame to just these rows:

enrollments %>%
  group_by(course) %>%
  filter(mean(quiz_score) < 80)

1.group_by() groups the data frame by course into two groups: learn-r and learn-python

2.filter() will keep all the rows of the data frame whose per-group (per-course) average quiz_score is less than 80

Rather than filtering rows by the individual column values, the rows will be filtered by their group value since a summary function is used!

1.The average quiz_score for the learn-r course is 85, so all the rows of enrollments with a value of learn-r in the course column are filtered out.

2.The average quiz_score for the learn-python course is 75, so all the rows of enrollments with a value of learn-python in the course column remain.

Instructions

1.Your boss at ShoeFly.com wants to gain a better insight into the orders of the most popular shoe_types.

Group orders by shoe_type and filter to only include orders with a shoe_type that has been ordered more than 7 times. Save the result to most_pop_orders, and view it.

You can include any of the summary functions as part of an argument to filter(), including n()!

# define most_pop_orders here:
most_pop_orders <- orders %>%
  group_by(shoe_type) %>%
  filter((count = n()) > 7)

most_pop_orders

6.Combining Grouping with Mutate

group_by() can also be used with the dplyr function mutate() to add columns to a data frame that involve per-group metrics.

Consider the same educational technology company’s enrollments table from the previous exercise:

# 建立資料框
enrollments <- data.frame(
  user_id = c(1234, 1234, 4567, 4567),
  course = c("learn_r", "learn_python", "learn_r", "learn_python"),
  quiz_score = c(80, 95, 90, 55)
)

enrollments

You want to add a new column to the data frame that stores the difference between a row’s quiz_score and the average quiz_score for that row’s course. To add the column:

enrollments %>% 
  group_by(course) %>% 
  mutate(diff_from_course_mean = quiz_score - mean(quiz_score))
NA

1.group_by() groups the data frame by course into two groups: learn-r and learn-python

2.mutate() will add a new column diff_from_course_mean which is calculated as the difference between a row’s individual quiz_score and the mean(quiz_score) for that row’s group (course)

1.The average quiz_score for the learn-r course is 85, so diff_from_course_mean is calculated as quiz_score - 85 for all the rows of enrollments with a value of learn-r in the course column.

2.The average quiz_score for the learn-python course is 75, so diff_from_course_mean is calculated as quiz_score - 75 for all the rows of enrollments with a value of learn-python in the course column.

Instructions

1.You want to be able to tell how expensive each order is compared to the average price of orders with the same shoe_type.

Group orders by shoe_type and create a new column named diff_from_shoe_type_mean that stores the difference in price between an orders price and the average price of orders with the same shoe_type. Save the result to diff_from_mean, and view it.

Don’t forget to include na.rm = TRUE as an argument in the summary function you call!

# define diff_from_mean here:
diff_from_mean <- orders %>%
  group_by(shoe_type) %>%
  mutate(diff_from_shoe_type_mean = price - mean(price, na.rm = TRUE))

diff_from_mean

7.Review

This lesson introduced you to aggregates in R using dplyr. You learned:

1.How to calculate summary statistics with summarize()

2.How to perform aggregate statistics over individual rows with the same value or values using group_by()

Instructions

1.Let’s examine some more data from ShoeFly.com. This time, in addition to the orders data, we’ll be looking at data about user visits to the website, stored in the page_visits data frame. Inspect the columns of the data frames using the rendered notebook.

Find the average price of an order in the orders data frame using summarize() and the mean() summary function. Save the resulting data frame to a variable named average_price and view it.

Don’t forget to include na.rm = TRUE as an argument in the call to mean()!

# load data
orders <- read_csv("orders.csv")
page_visits <- read_csv("page_visits.csv")
# inspect data frames
head(orders)
head(page_visits)
# define average_price here:
average_price <- orders %>%
  summarize(avg_price = mean(price, na.rm = TRUE))

average_price

2.In the page_visits data frame, the column utm_source contains information about how users got to ShoeFly’s homepage. For instance, if utm_source = Facebook, then the user came to ShoeFly by clicking on an ad on Facebook.com.

Use a group_by statement to calculate how many visits came from each of the different sources. Save your answer to the variable click_source, and view it.

# define click_source here:
click_source <- page_visits %>%
  group_by(utm_source) %>%
  summarize(count = n())

click_source

3.Our Marketing department thinks that the traffic to our site has been changing over the past few months. Use group_by to calculate the number of visits to our site from each utm_source for each month. Save your answer to the variable click_source_by_month, and view it.

# define click_source_by_month here:
click_source_by_month <- page_visits %>%
  group_by(utm_source, month) %>%
  summarize(count = n())

click_source_by_month
LS0tDQp0aXRsZTogIkFnZ3JlZ2F0ZXMgaW4gUiINCmF1dGhvcjogIkFubmFiZWwgS3VvIg0KZGF0ZTogImByIGZvcm1hdChTeXMudGltZSgpLCAnJVktJW0tJWQgJUg6JU0nKWAiDQpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sNCi0tLQ0KIyAxLkludHJvZHVjdGlvbg0KDQpJbiB0aGlzIGxlc3NvbiB5b3Ugd2lsbCBsZWFybiBhYm91dCBhZ2dyZWdhdGVzIGluIFIgdXNpbmcgZHBseXIuIEFuIGFnZ3JlZ2F0ZSBzdGF0aXN0aWMgaXMgYSB3YXkgb2YgY3JlYXRpbmcgYSBzaW5nbGUgbnVtYmVyIHRoYXQgZGVzY3JpYmVzIGEgZ3JvdXAgb2YgbnVtYmVycy4gQ29tbW9uIGFnZ3JlZ2F0ZSBzdGF0aXN0aWNzIGluY2x1ZGUgbWVhbiwgbWVkaWFuLCBhbmQgc3RhbmRhcmQgZGV2aWF0aW9uLg0KDQpBZGRpdGlvbmFsbHksIHlvdSB3aWxsIGxlYXJuIGhvdyB5b3UgY2FuIGdyb3VwIGRhdGEgaW50byBkaWZmZXJlbnQgc3Vic2V0cyBiYXNlZCBvbiBjb2x1bW4gdmFsdWVzLiBUaGlzIGNhbiBoZWxwIG5hcnJvdyB0aGUgZm9jdXMgb2YgYSBzdW1tYXJ5IHN0YXRpc3RpYyB0byBhIHN1YnNldCBvZiBhIGRhdGFzZXQuIFIgeW91IHJlYWR5IHRvIGdldCBzdGFydGVkPw0KDQojIyBJbnN0cnVjdGlvbnMNCg0KSW4gdGhlIHdvcmtzcGFjZSB0byB0aGUgcmlnaHQgd2UgaGF2ZSBsb2FkZWQgZGF0YSBmcm9tIFNob2VGbHkuY29tLCBhIGZpY3Rpb25hbCBlLWNvbW1lcmNlIHNob2Ugc3RvcmUuIFRoZSBkYXRhIGluY2x1ZGVzIGluZm9ybWF0aW9uIHJlZ2FyZGluZyBjdXN0b21lciBvcmRlcnMgYXMgd2VsbCBhcyB0aGUgc291cmNlIG9mIHBhZ2UgdmlzaXRzIHRvIFNob2VGbHkuY29t4oCZcyB3ZWJzaXRlLg0KDQpSZXZpZXcgdGhlIGNvZGUgYW5kIHRoZSBvdXRwdXQuIFdoYXQgY2FsY3VsYXRpb25zIGFyZSBtYWRlLCBhbmQgb24gd2hpY2ggc3Vic2V0cyBvZiB0aGUgZGF0YSBkbyB0aGV5IG9jY3VyPw0KDQpUaHJvdWdob3V0IHRoZSByZXN0IG9mIHRoaXMgbGVzc29uIHlvdSB3aWxsIGRpZyBpbnRvIFNob2VGbHkuY29t4oCZcyBkYXRhIHRvIGxlYXJuIG1vcmUgYWJvdXQgaXRzIGN1c3RvbWVy4oCZcyBvcmRlcnMgYW5kIGhvdyB1c2VycyBhcmUgZGlzY292ZXJpbmcgdGhlIHdlYnNpdGUuIFByb2NlZWQgdG8gdGhlIG5leHQgZXhlcmNpc2UgdG8gZ2V0IHN0YXJ0ZWQhDQoNCmBgYHtyIG1lc3NhZ2UgPSBGQUxTRX0NCiMgbG9hZCBwYWNrYWdlcw0KbGlicmFyeShyZWFkcikNCmxpYnJhcnkoZHBseXIpDQpgYGANCg0KYGBge3IgbWVzc2FnZSA9IEZBTFNFfQ0KIyBsb2FkIGRhdGENCm9yZGVycyA8LSByZWFkX2Nzdigib3JkZXJzLmNzdiIpDQpwYWdlX3Zpc2l0cyA8LSByZWFkX2NzdigicGFnZV92aXNpdHMuY3N2IikNCmBgYA0KDQpgYGB7cn0NCiMgaW5zcGVjdCBkYXRhIGZyYW1lcw0Kb3JkZXJzDQpwYWdlX3Zpc2l0cw0KYGBgDQoNCmBgYHtyfQ0KIyBhdmVyYWdlIHByaWNlIG9mIG9yZGVyDQphdmVyYWdlX3ByaWNlIDwtIG9yZGVycyAlPiUgDQogIHN1bW1hcml6ZShtZWFuX3ByaWNlID0gbWVhbihwcmljZSwgbmEucm0gPSBUUlVFKSkNCmF2ZXJhZ2VfcHJpY2UNCmBgYA0KDQpgYGB7cn0NCiMgcGFnZSB2aXNpdHMgYnkgVVRNIHNvdXJjZQ0KY2xpY2tfc291cmNlIDwtIHBhZ2VfdmlzaXRzICU+JQ0KICBncm91cF9ieSh1dG1fc291cmNlKSAlPiUNCiAgc3VtbWFyaXplKGNvdW50ID0gbigpKQ0KY2xpY2tfc291cmNlDQpgYGANCg0KYGBge3J9DQojIHBhZ2UgdmlzaXRzIGJ5IFVUTSBzb3VyY2UgYW5kIG1vbnRoDQpjbGlja19zb3VyY2VfYnlfbW9udGggPC0gcGFnZV92aXNpdHMgJT4lDQogIGdyb3VwX2J5KHV0bV9zb3VyY2UsbW9udGgpICU+JQ0KICBzdW1tYXJpemUoY291bnQgPSBuKCkpDQpjbGlja19zb3VyY2VfYnlfbW9udGgNCmBgYA0KDQojIDIuQ2FsY3VsYXRpbmcgQ29sdW1uIFN0YXRpc3RpY3MNCg0KSW4gdGhpcyBleGVyY2lzZSwgeW91IHdpbGwgbGVhcm4gaG93IHRvIGNvbWJpbmUgYWxsIG9mIHRoZSB2YWx1ZXMgZnJvbSBhIGNvbHVtbiBmb3IgYSBzaW5nbGUgY2FsY3VsYXRpb24uIFRoaXMgY2FuIGJlIGRvbmUgd2l0aCB0aGUgaGVscCBvZiB0aGUgZHBseXIgZnVuY3Rpb24gc3VtbWFyaXplKCksIHdoaWNoIHJldHVybnMgYSBuZXcgZGF0YSBmcmFtZSBjb250YWluaW5nIHRoZSBkZXNpcmVkIGNhbGN1bGF0aW9uLg0KDQpTb21lIGV4YW1wbGVzIG9mIHRoaXMgdHlwZSBvZiBjYWxjdWxhdGlvbiBpbmNsdWRlOg0KDQoxLlRoZSBkYXRhIGZyYW1lIGN1c3RvbWVycyBjb250YWlucyB0aGUgbmFtZXMgYW5kIGFnZXMgb2YgYWxsIG9mIHlvdXIgY3VzdG9tZXJzLiBZb3Ugd2FudCB0byBmaW5kIHRoZSBtZWRpYW4gYWdlOg0KDQpgYGB7cn0NCmN1c3RvbWVycyA8LSBkYXRhLmZyYW1lKA0KICBuYW1lID0gYygiQWxpY2UiLCAiQm9iIiwgIkNsYWlyZSIsICJEYW5pZWwiLCAiRWxhaW5lIiwgIkZyYW5rIiwgIkdyYWNlIiksDQogIGFnZSA9IGMoMjMsIDI1LCAzMSwgMzUsIDM1LCA0NiwgNjIpDQopDQpgYGANCg0KYGBge3J9DQpjdXN0b21lcnMgJT4lDQogIHNlbGVjdChhZ2UpDQojYygyMywgMjUsIDMxLCAzNSwgMzUsIDQ2LCA2MikNCmN1c3RvbWVycyAlPiUNCiAgc3VtbWFyaXplKG1lZGlhbl9hZ2UgPSBtZWRpYW4oYWdlKSkNCiMzNQ0KYGBgDQoNCg0KMi5UaGUgZGF0YSBmcmFtZSBzaGlwbWVudHMgY29udGFpbnMgYWRkcmVzcyBpbmZvcm1hdGlvbiBmb3IgYWxsIHNoaXBtZW50cyB0aGF0IHlvdeKAmXZlIHNlbnQgb3V0IGluIHRoZSBwYXN0IHllYXIuIFlvdSB3YW50IHRvIGtub3cgaG93IG1hbnkgZGlmZmVyZW50IHN0YXRlcyB5b3UgaGF2ZSBzaGlwcGVkIHRvLg0KDQpgYGB7cn0NCnNoaXBtZW50cyA8LSBkYXRhLmZyYW1lKA0KICBzdGF0ZXMgPSBjKCdDQScsICdDQScsICdDQScsICdDQScsICdOWScsICdOWScsICdOSicsICdOSicsICdOSicsICdOSicsICdOSicsICdOSicsICdOSicpDQopDQoNCmBgYA0KDQpgYGB7cn0NCnNoaXBtZW50cyAlPiUNCiAgc3VtbWFyaXplKG5fZGlzdGluY3Rfc3RhdGVzID0gbl9kaXN0aW5jdChzdGF0ZXMpKQ0KIzMNCmBgYA0KDQozLlRoZSBkYXRhIGZyYW1lIGludmVudG9yeSBjb250YWlucyBhIGxpc3Qgb2YgdHlwZXMgb2YgdC1zaGlydHMgdGhhdCB5b3VyIGNvbXBhbnkgbWFrZXMuIFlvdSB3YW50IHRvIGtub3cgdGhlIHN0YW5kYXJkIGRldmlhdGlvbiBvZiB0aGUgcHJpY2VzIG9mIHlvdXIgaW52ZW50b3J5Lg0KDQpgYGB7cn0NCmludmVudG9yeSA8LSBkYXRhLmZyYW1lKA0KICBwcmljZSA9IGMoMzEsIDIzLCAzMCwgMjcsIDMwLCAyMiwgMjcsIDIyLCAzOSwgMjcsIDM2KQ0KKQ0KYGBgDQoNCmBgYHtyfQ0KaW52ZW50b3J5ICU+JQ0KICBzdW1tYXJpemUoc2RfcHJpY2UgPSBzZChwcmljZSkpDQpgYGANCg0KVGhlIGdlbmVyYWwgc3ludGF4IGZvciB0aGVzZSBjYWxjdWxhdGlvbnMgaXM6DQoNCmRmICU+JQ0KICBzdW1tYXJpemUodmFyX25hbWUgPSBjb21tYW5kKGNvbHVtbl9uYW1lKSkNCg0KMS5kZiBpcyB0aGUgZGF0YSBmcmFtZSB5b3UgYXJlIHdvcmtpbmcgd2l0aA0KDQoyLnN1bW1hcml6ZSBpcyBhIGRwbHlyIGZ1bmN0aW9uIHRoYXQgcmVkdWNlcyBtdWx0aXBsZSB2YWx1ZXMgdG8gYSBzaW5nbGUgdmFsdWUNCg0KMy52YXJfbmFtZSBpcyB0aGUgbmFtZSB5b3UgYXNzaWduIHRvIHRoZSBjb2x1bW4gdGhhdCBzdG9yZXMgdGhlIHJlc3VsdHMgb2YgdGhlIHN1bW1hcnkgZnVuY3Rpb24gaW4gdGhlIHJldHVybmVkIGRhdGEgZnJhbWUNCg0KNC5jb21tYW5kIGlzIHRoZSBzdW1tYXJ5IGZ1bmN0aW9uIHRoYXQgaXMgYXBwbGllZCB0byB0aGUgY29sdW1uIGJ5IHN1bW1hcml6ZSgpDQoNCjUuY29sdW1uX25hbWUgaXMgdGhlIG5hbWUgb2YgdGhlIGNvbHVtbiBvZiBkZiB0aGF0IGlzIGJlaW5nIHN1bW1hcml6ZWQNCg0KVGhlIGZvbGxvd2luZyB0YWJsZSBpbmNsdWRlcyBjb21tb24gc3VtbWFyeSBmdW5jdGlvbnMgdGhhdCBjYW4gYmUgZ2l2ZW4gYXMgYW4gYXJndW1lbnQgdG8gc3VtbWFyaXplKCk6DQoNCmBgYHtyIEFnZ3JlZ2F0ZXMxLCBvdXQud2lkdGg9IjYwJSJ9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiQzovVXNlcnMva3VvYW4vRGVza3RvcC9SIENvZGUvQWdncmVnYXRlczEucG5nIikNCmBgYA0KDQojIyBJbnN0cnVjdGlvbnMNCg0KMS5TaG9lRmx5LmNvbSBoYXMgYSBuZXcgYmF0Y2ggb2Ygb3JkZXJzIHN0b3JlZCBpbiB0aGUgZGF0YSBmcmFtZSBvcmRlcnMuIEluc3BlY3QgdGhlIGZpcnN0IDEwIHJvd3Mgb2YgdGhlIGRhdGEgZnJhbWUgdXNpbmcgaGVhZCgpLg0KDQpgYGB7ciBtZXNzYWdlID0gRkFMU0V9DQojIGxvYWQgb3JkZXIgZGF0YQ0Kb3JkZXJzIDwtIHJlYWRfY3N2KCJvcmRlcnMuY3N2IikNCg0KIyBpbnNwZWN0IG9yZGVycyBoZXJlOg0KaGVhZChvcmRlcnMsIDEwKQ0KYGBgDQoNCjIuT3VyIGZpbmFuY2UgZGVwYXJ0bWVudCB3YW50cyB0byBrbm93IHRoZSBwcmljZSBvZiB0aGUgbW9zdCBleHBlbnNpdmUgcGFpciBvZiBzaG9lcyBwdXJjaGFzZWQuIFNhdmUgeW91ciBhbnN3ZXIgdG8gdGhlIHZhcmlhYmxlIG1vc3RfZXhwZW5zaXZlLg0KDQpgYGB7cn0NCiMgZGVmaW5lIG1vc3RfZXhwZW5zaXZlIGhlcmU6DQptb3N0X2V4cGVuc2l2ZSA8LSBvcmRlcnMgJT4lDQogIHN1bW1hcml6ZShtb3N0X2V4cGVuc2l2ZSA9ICBtYXgocHJpY2UpKQ0KbW9zdF9leHBlbnNpdmUNCmBgYA0KDQozLldvYWgsIHdhaXQgYSBtaW51dGUhIFRha2UgYSBsb29rIGF0IHRoZSBvdXRwdXQgb2YgdGhlIGNvZGUgeW91IGp1c3QgcmFuLiBUaGUgcmVzdWx0IGZvciB0aGUgbW9zdCBleHBlbnNpdmUgcGFpciBvZiBzaG9lcyBpcyBjb21pbmcgYmFjayBhcyBOQS4gV2h5IGlzIHRoaXMgaGFwcGVuaW5nPw0KDQpJZiB5b3Ugc2Nyb2xsIHVwIGluIHRoZSByZW5kZXJlZCBub3RlYm9vayB0byB3aGVyZSBvcmRlcnMuY3N2IGlzIGxvYWRlZCwgeW91IGNhbiBzZWUgYSB3YXJuaW5nIGFib3V0IHJvdyA5OSBvZiB0aGUgZmlsZS4gVGhlcmUgaXMgYSBtaXNzaW5nIGNvbHVtbiBvZiBpbmZvcm1hdGlvbiEgSXQgYXBwZWFycyB0aGF0IHRoZSBwcmljZSBmb3Igcm93IDk5IHdhcyBub3QgaW4gdGhlIGZpbGUsIGFuZCB0aGlzIGlzIGNhdXNpbmcgeW91ciBtYXhpbXVtIHZhbHVlIGNhbGN1bGF0aW9uIHRvIHJldHVybiBOQS4NCg0KQWRkIHRoZSBmb2xsb3dpbmcgYXMgYW4gYWRkaXRpb25hbCBhcmd1bWVudCB0byBtYXgoKSBzbyB0aGF0IGl0IHJlbW92ZXMgYWxsIG1pc3NpbmcgdmFsdWVzIGJlZm9yZSBjb21wdXRpbmcgdGhlIG1heGltdW0gdmFsdWUuDQoiDQpuYS5ybSA9IFRSVUUNCiINCg0KYGBge3J9DQojIGRlZmluZSBtb3N0X2V4cGVuc2l2ZSBoZXJlOg0KbW9zdF9leHBlbnNpdmUgPC0gb3JkZXJzICU+JQ0KICBzdW1tYXJpemUobW9zdF9leHBlbnNpdmUgPSAgbWF4KHByaWNlLCBuYS5ybSA9IFRSVUUpKQ0KbW9zdF9leHBlbnNpdmUNCmBgYA0KDQo0Lk91ciBmYXNoaW9uIGRlcGFydG1lbnQgd2FudHMgdG8ga25vdyBob3cgbWFueSBkaWZmZXJlbnQgY29sb3JzIG9mIHNob2VzIHdlIGFyZSBzZWxsaW5nLiBTYXZlIHlvdXIgYW5zd2VyIHRvIHRoZSB2YXJpYWJsZSBudW1fY29sb3JzLg0KDQpgYGB7cn0NCiMgZGVmaW5lIG51bV9jb2xvcnMgaGVyZToNCm51bV9jb2xvcnMgPC0gb3JkZXJzICU+JQ0KICBzdW1tYXJpemUobnVtX2NvbG9ycyA9IG5fZGlzdGluY3Qoc2hvZV9jb2xvciwgbmEucm0gPSBUUlVFKSkNCm51bV9jb2xvcnMgDQpgYGANCg0KIyAzLkNhbGN1bGF0aW5nIEFnZ3JlZ2F0ZSBGdW5jdGlvbnMgSQ0KDQpXaGVuIHdlIGhhdmUgYSBidW5jaCBvZiBkYXRhLCB3ZSBvZnRlbiB3YW50IHRvIGNhbGN1bGF0ZSBhZ2dyZWdhdGUgc3RhdGlzdGljcyAobWVhbiwgc3RhbmRhcmQgZGV2aWF0aW9uLCBtZWRpYW4sIHBlcmNlbnRpbGVzLCBldGMuKSBvdmVyIGNlcnRhaW4gc3Vic2V0cyBvZiB0aGUgZGF0YS4NCg0KU3VwcG9zZSB3ZSBoYXZlIGEgZ3JhZGUgYm9vayB3aXRoIGNvbHVtbnMgc3R1ZGVudCwgYXNzaWdubWVudF9uYW1lLCBhbmQgZ3JhZGU6DQoNCmBgYHtyIEFnZ3JlZ2F0ZXMyLCBvdXQud2lkdGg9IjYwJSJ9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiQzovVXNlcnMva3VvYW4vRGVza3RvcC9SIENvZGUvQWdncmVnYXRlczIucG5nIikNCmBgYA0KDQpXZSB3YW50IHRvIGdldCBhbiBhdmVyYWdlIGdyYWRlIGZvciBlYWNoIHN0dWRlbnQgYWNyb3NzIGFsbCBhc3NpZ25tZW50cy4gV2UgY2FuIGRvIHRoaXMgdXNpbmcgdGhlIGhlbHBmdWwgZHBseXIgZnVuY3Rpb24gZ3JvdXBfYnkoKS4NCg0KRm9yIHRoaXMgZXhhbXBsZSwgd2XigJlkIHVzZSB0aGUgZm9sbG93aW5nIHBpZWNlIG9mIGNvZGU6DQoNCiINCmdyYWRlcyA8LSBkZiAlPiUNCiAgZ3JvdXBfYnkoc3R1ZGVudCkgJT4lDQogIHN1bW1hcml6ZShtZWFuX2dyYWRlID0gbWVhbihncmFkZSkpDQoiDQoNCg0KVGhlIG91dHB1dCBtaWdodCBsb29rIHNvbWV0aGluZyBsaWtlIHRoaXM6DQoNCmBgYHtyIEFnZ3JlZ2F0ZXMzLCBvdXQud2lkdGg9IjYwJSJ9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiQzovVXNlcnMva3VvYW4vRGVza3RvcC9SIENvZGUvQWdncmVnYXRlczMucG5nIikNCmBgYA0KDQoNCkluIGdlbmVyYWwsIHdlIHVzZSB0aGUgZm9sbG93aW5nIHN5bnRheCB0byBjYWxjdWxhdGUgYWdncmVnYXRlczoNCg0KIg0KZGYgJT4lDQogIGdyb3VwX2J5KGNvbHVtbl8xKSAlPiUNCiAgc3VtbWFyaXplKGFnZ3JlZ2F0ZV9uYW1lID0gY29tbWFuZChjb2x1bW5fMikpDQoiDQoNCjEuY29sdW1uXzEgKHN0dWRlbnQgaW4gb3VyIGV4YW1wbGUpIGlzIHRoZSBjb2x1bW4gdGhhdCB3ZSB3YW50IHRvIGdyb3VwX2J5KCkNCg0KMi5jb2x1bW5fMiAoZ3JhZGUgaW4gb3VyIGV4YW1wbGUpIGlzIHRoZSBjb2x1bW4gdGhhdCB3ZSB3YW50IHRvIGFwcGx5IGNvbW1hbmQoKSwgYSBzdW1tYXJ5IGZ1bmN0aW9uLCB0byB1c2luZyBzdW1tYXJpemUoKQ0KDQozLmFnZ3JlZ2F0ZV9uYW1lIGlzIHRoZSBuYW1lIGFzc2lnbmVkIHRvIHRoZSBjYWxjdWxhdGVkIGFnZ3JlZ2F0ZQ0KDQpJbiBhZGRpdGlvbiB0byB0aGUgc3VtbWFyeSBmdW5jdGlvbnMgZGlzY3Vzc2VkIGluIHRoZSBsYXN0IGV4ZXJjaXNlIChtZWFuKCksIG1lZGlhbigpLCBzZCgpLCB2YXIoKSwgbWluKCksIG1heCgpLCBJUVIoKSBhbmQgbl9kaXN0aW5jdCgpKSwgYW5vdGhlciBoZWxwZnVsIHN1bW1hcnkgZnVuY3Rpb24sIGVzcGVjaWFsbHkgZm9yIGdyb3VwZWQgZGF0YSwgaXMgbigpLiBuKCkgd2lsbCByZXR1cm4gdGhlIGNvdW50IG9mIHRoZSByb3dzIHdpdGhpbiBhIGdyb3VwLCBhbmQgZG9lcyBub3QgcmVxdWlyZSBhIGNvbHVtbiBhcyBhbiBhcmd1bWVudC4gVG8gZ2V0IHRoZSBjb3VudCBvZiB0aGUgcm93cyBpbiBlYWNoIGdyb3VwIG9mIHN0dWRlbnRzIGZyb20gb3VyIGV4YW1wbGU6DQoNCiINCmdyYWRlcyA8LSBkZiAlPiUNCiAgZ3JvdXBfYnkoc3R1ZGVudCkgJT4lDQogIHN1bW1hcml6ZShjb3VudCA9IG4oKSkNCiINCg0KIyMgSW5zdHJ1Y3Rpb25zDQoNCjEuDQpMZXTigJlzIHJldHVybiB0byBvdXIgb3JkZXJzIGRhdGEgZnJvbSBTaG9lRmx5LmNvbS4NCg0KSW4gdGhlIHByZXZpb3VzIGV4ZXJjaXNlLCBvdXIgZmluYW5jZSBkZXBhcnRtZW50IHdhbnRlZCB0byBrbm93IHRoZSBtb3N0IGV4cGVuc2l2ZSBzaG9lIHRoYXQgd2Ugc29sZC4NCg0KTm93LCB0aGV5IHdhbnQgdG8ga25vdyB0aGUgcHJpY2Ugb2YgdGhlIG1vc3QgZXhwZW5zaXZlIHNob2UgZm9yIGVhY2ggc2hvZV90eXBlIChpLmUuLCB0aGUgcHJpY2Ugb2YgdGhlIG1vc3QgZXhwZW5zaXZlIGJvb3QsIHRoZSBwcmljZSBvZiB0aGUgbW9zdCBleHBlbnNpdmUgYmFsbGV0IGZsYXQsIGV0Yy4pLiBOYW1lIHRoZSBjb2x1bW4gdGhhdCBzaG93cyB0aGUgbW9zdCBleHBlbnNpdmUgc2hvZSBwcmljZXMgbWF4X3ByaWNlLg0KDQpTYXZlIHlvdXIgYW5zd2VyIHRvIHRoZSB2YXJpYWJsZSBwcmljZXlfc2hvZXMsIGFuZCB2aWV3IGl0Lg0KDQpgYGB7cn0NCiMgZGVmaW5lIHByaWNleV9zaG9lcyBoZXJlOg0KcHJpY2V5X3Nob2VzIDwtIG9yZGVycyAlPiUNCiAgZ3JvdXBfYnkoc2hvZV90eXBlKSAlPiUNCiAgc3VtbWFyaXplKG1heF9wcmljZSA9IG1heChwcmljZSwgbmEucm0gPSBUUlVFKSkNCg0KcHJpY2V5X3Nob2VzDQpgYGANCg0KMi5UaGUgaW52ZW50b3J5IHRlYW0gd2FudHMgdG8ga25vdyBob3cgbWFueSBvZiBlYWNoIHNob2VfdHlwZSBoYXMgYmVlbiBzb2xkIHNvIHRoZXkgY2FuIGZvcmVjYXN0IGludmVudG9yeSBmb3IgdGhlIGZ1dHVyZS4NCg0KU2F2ZSB5b3VyIGFuc3dlciB0byB0aGUgdmFyaWFibGUgc2hvZXNfc29sZCwgYW5kIHZpZXcgaXQuDQoNCmBgYHtyfQ0KIyBkZWZpbmUgc2hvZXNfc29sZCBoZXJlOg0Kc2hvZXNfc29sZCA8LSBvcmRlcnMgJT4lDQogIGdyb3VwX2J5KHNob2VfdHlwZSkgJT4lDQogIHN1bW1hcml6ZShjb3VudCA9IG4oKSkNCg0Kc2hvZXNfc29sZA0KYGBgDQoNCiMgNC5DYWxjdWxhdGluZyBBZ2dyZWdhdGUgRnVuY3Rpb25zIElJDQoNClNvbWV0aW1lcywgd2Ugd2FudCB0byBncm91cCBieSBtb3JlIHRoYW4gb25lIGNvbHVtbi4gV2UgY2FuIGRvIHRoaXMgYnkgcGFzc2luZyBtdWx0aXBsZSBjb2x1bW4gbmFtZXMgYXMgYXJndW1lbnRzIHRvIHRoZSBncm91cF9ieSBmdW5jdGlvbi4NCg0KSW1hZ2luZSB0aGF0IHdlIHJ1biBhIGNoYWluIG9mIHN0b3JlcyBhbmQgaGF2ZSBkYXRhIGFib3V0IHRoZSBudW1iZXIgb2Ygc2FsZXMgYXQgZGlmZmVyZW50IGxvY2F0aW9ucyBvbiBkaWZmZXJlbnQgZGF5czoNCg0KDQpgYGB7ciBBZ2dyZWdhdGVzNCwgb3V0LndpZHRoPSI2MCUifQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoIkM6L1VzZXJzL2t1b2FuL0Rlc2t0b3AvUiBDb2RlL0FnZ3JlZ2F0ZXM0LnBuZyIpDQpgYGANCg0KV2Ugc3VzcGVjdCB0aGF0IHNhbGVzIGFyZSBkaWZmZXJlbnQgYXQgZGlmZmVyZW50IGxvY2F0aW9ucyBvbiBkaWZmZXJlbnQgZGF5cyBvZiB0aGUgd2Vlay4gSW4gb3JkZXIgdG8gdGVzdCB0aGlzIGh5cG90aGVzaXMsIHdlIGNvdWxkIGNhbGN1bGF0ZSB0aGUgYXZlcmFnZSBzYWxlcyBmb3IgZWFjaCBzdG9yZSBvbiBlYWNoIGRheSBvZiB0aGUgd2VlayBhY3Jvc3MgbXVsdGlwbGUgbW9udGhzLiBUaGUgY29kZSB3b3VsZCBsb29rIGxpa2UgdGhpczoNCg0KIg0KZGYgJT4lDQogIGdyb3VwX2J5KGxvY2F0aW9uLGRheV9vZl93ZWVrKSAlPiUNCiAgc3VtbWFyaXplKG1lYW5fdG90YWxfc2FsZXMgPSBtZWFuKHRvdGFsX3NhbGVzKSkNCiINCg0KQW5kIHRoZSByZXN1bHRzIG1pZ2h0IGxvb2sgc29tZXRoaW5nIGxpa2UgdGhpczoNCg0KYGBge3IgQWdncmVnYXRlczUsIG91dC53aWR0aD0iNjAlIn0NCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKCJDOi9Vc2Vycy9rdW9hbi9EZXNrdG9wL1IgQ29kZS9BZ2dyZWdhdGVzNS5wbmciKQ0KYGBgDQoNCiMjIEluc3RydWN0aW9ucw0KMS5BdCBTaG9lRmx5LmNvbSwgb3VyIFB1cmNoYXNpbmcgdGVhbSB0aGlua3MgdGhhdCBjZXJ0YWluIHNob2VfdHlwZS9zaG9lX2NvbG9yIGNvbWJpbmF0aW9ucyBhcmUgcGFydGljdWxhcmx5IHBvcHVsYXIgdGhpcyB5ZWFyIChmb3IgZXhhbXBsZSwgYmx1ZSBiYWxsZXQgZmxhdHMgYXJlIGFsbCB0aGUgcmFnZSBpbiBQYXJpcykuDQoNCkZpbmQgdGhlIHRvdGFsIG51bWJlciBvZiBzaG9lcyBvZiBlYWNoIHNob2VfdHlwZS9zaG9lX2NvbG9yIGNvbWJpbmF0aW9uIHB1cmNoYXNlZCB1c2luZyBncm91cF9ieSwgc3VtbWFyaXplKCkgYW5kIG4oKS4gTmFtZSB0aGUgYWdncmVnYXRlIGNvdW50IGNvbHVtbiBjb3VudC4gU2F2ZSB5b3VyIHJlc3VsdCB0byB0aGUgdmFyaWFibGUgc2hvZV9jb3VudHMsIGFuZCB2aWV3IGl0Lg0KDQpgYGB7cn0NCiMgZGVmaW5lIHNob2VfY291bnRzIGhlcmU6DQpzaG9lX2NvdW50cyA8LSBvcmRlcnMgJT4lDQogIGdyb3VwX2J5KHNob2VfdHlwZSwgc2hvZV9jb2xvcikgJT4lDQogIHN1bW1hcml6ZShjb3VudCA9IG4oKSkNCg0Kc2hvZV9jb3VudHMNCmBgYA0KDQoyLlRoZSBNYXJrZXRpbmcgdGVhbSB3YW50cyB0byBiZXR0ZXIgdW5kZXJzdGFuZCB0aGUgZGlmZmVyZW50IHByaWNlIGxldmVscyBvZiB0aGUga2luZHMgb2Ygc2hvZXMgdGhhdCBoYXZlIGJlZW4gc29sZCBvbiB0aGUgd2Vic2l0ZSwgaW4gcGFydGljdWxhciBsb29raW5nIGF0IHNob2VfdHlwZS9zaG9lX21hdGVyaWFsIGNvbWJpbmF0aW9ucy4NCg0KRmluZCB0aGUgbWVhbiBwcmljZSBvZiBlYWNoIHNob2VfdHlwZS9zaG9lX21hdGVyaWFsIGNvbWJpbmF0aW9uIHB1cmNoYXNlZCB1c2luZyBncm91cF9ieSwgc3VtbWFyaXplKCkgYW5kIG1lYW4oKS4gQXNzaWduIHRoZSBuYW1lIG1lYW5fcHJpY2UgdG8gdGhlIGNhbGN1bGF0ZWQgYWdncmVnYXRlLiBTYXZlIHlvdXIgcmVzdWx0IHRvIHRoZSB2YXJpYWJsZSBzaG9lX3ByaWNlcywgYW5kIHZpZXcgaXQuDQoNCkRvbuKAmXQgZm9yZ2V0IHRvIGluY2x1ZGUgbmEucm0gPSBUUlVFIGFzIGFuIGFyZ3VtZW50IGluIHRoZSBzdW1tYXJ5IGZ1bmN0aW9uIHRoYXQgeW91IGNhbGwhDQoNCmBgYHtyfQ0KIyBkZWZpbmUgc2hvZV9wcmljZXMgaGVyZToNCnNob2VfcHJpY2VzIDwtIG9yZGVycyAlPiUNCiAgZ3JvdXBfYnkoc2hvZV90eXBlLCBzaG9lX21hdGVyaWFsKSAlPiUNCiAgc3VtbWFyaXplKG1lYW5fcHJpY2UgPSBtZWFuKHByaWNlLCBuYS5ybSA9IFRSVUUpKQ0KDQpzaG9lX3ByaWNlcw0KYGBgDQoNCiMgNS5Db21iaW5pbmcgR3JvdXBpbmcgd2l0aCBGaWx0ZXJzDQoNCldoaWxlIGdyb3VwX2J5KCkgaXMgbW9zdCBvZnRlbiB1c2VkIHdpdGggc3VtbWFyaXplKCkgdG8gY2FsY3VsYXRlIHN1bW1hcnkgc3RhdGlzdGljcywgaXQgY2FuIGFsc28gYmUgdXNlZCB3aXRoIHRoZSBkcGx5ciBmdW5jdGlvbiBmaWx0ZXIoKSB0byBmaWx0ZXIgcm93cyBvZiBhIGRhdGEgZnJhbWUgYmFzZWQgb24gcGVyLWdyb3VwIG1ldHJpY3MuDQoNClN1cHBvc2UgeW91IHdvcmsgYXQgYW4gZWR1Y2F0aW9uYWwgdGVjaG5vbG9neSBjb21wYW55IHRoYXQgb2ZmZXJzIG9ubGluZSBjb3Vyc2VzIGFuZCBjb2xsZWN0cyB1c2VyIGRhdGEgaW4gYW4gZW5yb2xsbWVudHMgZGF0YSBmcmFtZToNCmBgYHtyfQ0KIyDlu7rnq4vos4fmlpnmoYYNCmVucm9sbG1lbnRzIDwtIGRhdGEuZnJhbWUoDQogIHVzZXJfaWQgPSBjKDEyMzQsIDEyMzQsIDQ1NjcsIDQ1NjcpLA0KICBjb3Vyc2UgPSBjKCJsZWFybl9yIiwgImxlYXJuX3B5dGhvbiIsICJsZWFybl9yIiwgImxlYXJuX3B5dGhvbiIpLA0KICBxdWl6X3Njb3JlID0gYyg4MCwgOTUsIDkwLCA1NSkNCikNCg0KZW5yb2xsbWVudHMNCmBgYA0KDQpZb3Ugd2FudCB0byBpZGVudGlmeSBhbGwgdGhlIGVucm9sbG1lbnRzIGluIGRpZmZpY3VsdCBjb3Vyc2VzLCB3aGljaCB5b3UgZGVmaW5lIGFzIGNvdXJzZXMgd2l0aCBhbiBhdmVyYWdlIHF1aXpfc2NvcmUgbGVzcyB0aGFuIDgwLiBUbyBmaWx0ZXIgdGhlIGRhdGEgZnJhbWUgdG8ganVzdCB0aGVzZSByb3dzOg0KYGBge3J9DQplbnJvbGxtZW50cyAlPiUNCiAgZ3JvdXBfYnkoY291cnNlKSAlPiUNCiAgZmlsdGVyKG1lYW4ocXVpel9zY29yZSkgPCA4MCkNCmBgYA0KDQoxLmdyb3VwX2J5KCkgZ3JvdXBzIHRoZSBkYXRhIGZyYW1lIGJ5IGNvdXJzZSBpbnRvIHR3byBncm91cHM6IGxlYXJuLXIgYW5kIGxlYXJuLXB5dGhvbg0KDQoyLmZpbHRlcigpIHdpbGwga2VlcCBhbGwgdGhlIHJvd3Mgb2YgdGhlIGRhdGEgZnJhbWUgd2hvc2UgcGVyLWdyb3VwIChwZXItY291cnNlKSBhdmVyYWdlIHF1aXpfc2NvcmUgaXMgbGVzcyB0aGFuIDgwDQoNClJhdGhlciB0aGFuIGZpbHRlcmluZyByb3dzIGJ5IHRoZSBpbmRpdmlkdWFsIGNvbHVtbiB2YWx1ZXMsIHRoZSByb3dzIHdpbGwgYmUgZmlsdGVyZWQgYnkgdGhlaXIgZ3JvdXAgdmFsdWUgc2luY2UgYSBzdW1tYXJ5IGZ1bmN0aW9uIGlzIHVzZWQhIA0KDQoxLlRoZSBhdmVyYWdlIHF1aXpfc2NvcmUgZm9yIHRoZSBsZWFybi1yIGNvdXJzZSBpcyA4NSwgc28gYWxsIHRoZSByb3dzIG9mIGVucm9sbG1lbnRzIHdpdGggYSB2YWx1ZSBvZiBsZWFybi1yIGluIHRoZSBjb3Vyc2UgY29sdW1uIGFyZSBmaWx0ZXJlZCBvdXQuDQoNCjIuVGhlIGF2ZXJhZ2UgcXVpel9zY29yZSBmb3IgdGhlIGxlYXJuLXB5dGhvbiBjb3Vyc2UgaXMgNzUsIHNvIGFsbCB0aGUgcm93cyBvZiBlbnJvbGxtZW50cyB3aXRoIGEgdmFsdWUgb2YgbGVhcm4tcHl0aG9uIGluIHRoZSBjb3Vyc2UgY29sdW1uIHJlbWFpbi4NCg0KIyMgSW5zdHJ1Y3Rpb25zDQoNCjEuWW91ciBib3NzIGF0IFNob2VGbHkuY29tIHdhbnRzIHRvIGdhaW4gYSBiZXR0ZXIgaW5zaWdodCBpbnRvIHRoZSBvcmRlcnMgb2YgdGhlIG1vc3QgcG9wdWxhciBzaG9lX3R5cGVzLg0KDQpHcm91cCBvcmRlcnMgYnkgc2hvZV90eXBlIGFuZCBmaWx0ZXIgdG8gb25seSBpbmNsdWRlIG9yZGVycyB3aXRoIGEgc2hvZV90eXBlIHRoYXQgaGFzIGJlZW4gb3JkZXJlZCBtb3JlIHRoYW4gNyB0aW1lcy4gU2F2ZSB0aGUgcmVzdWx0IHRvIG1vc3RfcG9wX29yZGVycywgYW5kIHZpZXcgaXQuDQoNCllvdSBjYW4gaW5jbHVkZSBhbnkgb2YgdGhlIHN1bW1hcnkgZnVuY3Rpb25zIGFzIHBhcnQgb2YgYW4gYXJndW1lbnQgdG8gZmlsdGVyKCksIGluY2x1ZGluZyBuKCkhDQoNCmBgYHtyfQ0KIyBkZWZpbmUgbW9zdF9wb3Bfb3JkZXJzIGhlcmU6DQptb3N0X3BvcF9vcmRlcnMgPC0gb3JkZXJzICU+JQ0KICBncm91cF9ieShzaG9lX3R5cGUpICU+JQ0KICBmaWx0ZXIoKGNvdW50ID0gbigpKSA+IDcpDQoNCm1vc3RfcG9wX29yZGVycw0KYGBgDQoNCiMgNi5Db21iaW5pbmcgR3JvdXBpbmcgd2l0aCBNdXRhdGUNCg0KZ3JvdXBfYnkoKSBjYW4gYWxzbyBiZSB1c2VkIHdpdGggdGhlIGRwbHlyIGZ1bmN0aW9uIG11dGF0ZSgpIHRvIGFkZCBjb2x1bW5zIHRvIGEgZGF0YSBmcmFtZSB0aGF0IGludm9sdmUgcGVyLWdyb3VwIG1ldHJpY3MuDQoNCkNvbnNpZGVyIHRoZSBzYW1lIGVkdWNhdGlvbmFsIHRlY2hub2xvZ3kgY29tcGFueeKAmXMgZW5yb2xsbWVudHMgdGFibGUgZnJvbSB0aGUgcHJldmlvdXMgZXhlcmNpc2U6DQoNCmBgYHtyfQ0KIyDlu7rnq4vos4fmlpnmoYYNCmVucm9sbG1lbnRzIDwtIGRhdGEuZnJhbWUoDQogIHVzZXJfaWQgPSBjKDEyMzQsIDEyMzQsIDQ1NjcsIDQ1NjcpLA0KICBjb3Vyc2UgPSBjKCJsZWFybl9yIiwgImxlYXJuX3B5dGhvbiIsICJsZWFybl9yIiwgImxlYXJuX3B5dGhvbiIpLA0KICBxdWl6X3Njb3JlID0gYyg4MCwgOTUsIDkwLCA1NSkNCikNCg0KZW5yb2xsbWVudHMNCmBgYA0KDQpZb3Ugd2FudCB0byBhZGQgYSBuZXcgY29sdW1uIHRvIHRoZSBkYXRhIGZyYW1lIHRoYXQgc3RvcmVzIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gYSByb3figJlzIHF1aXpfc2NvcmUgYW5kIHRoZSBhdmVyYWdlIHF1aXpfc2NvcmUgZm9yIHRoYXQgcm934oCZcyBjb3Vyc2UuIFRvIGFkZCB0aGUgY29sdW1uOg0KDQpgYGB7cn0NCmVucm9sbG1lbnRzICU+JSANCiAgZ3JvdXBfYnkoY291cnNlKSAlPiUgDQogIG11dGF0ZShkaWZmX2Zyb21fY291cnNlX21lYW4gPSBxdWl6X3Njb3JlIC0gbWVhbihxdWl6X3Njb3JlKSkNCg0KYGBgDQoNCjEuZ3JvdXBfYnkoKSBncm91cHMgdGhlIGRhdGEgZnJhbWUgYnkgY291cnNlIGludG8gdHdvIGdyb3VwczogbGVhcm4tciBhbmQgbGVhcm4tcHl0aG9uDQoNCjIubXV0YXRlKCkgd2lsbCBhZGQgYSBuZXcgY29sdW1uIGRpZmZfZnJvbV9jb3Vyc2VfbWVhbiB3aGljaCBpcyBjYWxjdWxhdGVkIGFzIHRoZSBkaWZmZXJlbmNlIGJldHdlZW4gYSByb3figJlzIGluZGl2aWR1YWwgcXVpel9zY29yZSBhbmQgdGhlIG1lYW4ocXVpel9zY29yZSkgZm9yIHRoYXQgcm934oCZcyBncm91cCAoY291cnNlKQ0KDQoNCjEuVGhlIGF2ZXJhZ2UgcXVpel9zY29yZSBmb3IgdGhlIGxlYXJuLXIgY291cnNlIGlzIDg1LCBzbyBkaWZmX2Zyb21fY291cnNlX21lYW4gaXMgY2FsY3VsYXRlZCBhcyBxdWl6X3Njb3JlIC0gODUgZm9yIGFsbCB0aGUgcm93cyBvZiBlbnJvbGxtZW50cyB3aXRoIGEgdmFsdWUgb2YgbGVhcm4tciBpbiB0aGUgY291cnNlIGNvbHVtbi4NCg0KMi5UaGUgYXZlcmFnZSBxdWl6X3Njb3JlIGZvciB0aGUgbGVhcm4tcHl0aG9uIGNvdXJzZSBpcyA3NSwgc28gZGlmZl9mcm9tX2NvdXJzZV9tZWFuIGlzIGNhbGN1bGF0ZWQgYXMgcXVpel9zY29yZSAtIDc1IGZvciBhbGwgdGhlIHJvd3Mgb2YgZW5yb2xsbWVudHMgd2l0aCBhIHZhbHVlIG9mIGxlYXJuLXB5dGhvbiBpbiB0aGUgY291cnNlIGNvbHVtbi4NCg0KIyMgSW5zdHJ1Y3Rpb25zDQoNCjEuWW91IHdhbnQgdG8gYmUgYWJsZSB0byB0ZWxsIGhvdyBleHBlbnNpdmUgZWFjaCBvcmRlciBpcyBjb21wYXJlZCB0byB0aGUgYXZlcmFnZSBwcmljZSBvZiBvcmRlcnMgd2l0aCB0aGUgc2FtZSBzaG9lX3R5cGUuDQoNCkdyb3VwIG9yZGVycyBieSBzaG9lX3R5cGUgYW5kIGNyZWF0ZSBhIG5ldyBjb2x1bW4gbmFtZWQgZGlmZl9mcm9tX3Nob2VfdHlwZV9tZWFuIHRoYXQgc3RvcmVzIHRoZSBkaWZmZXJlbmNlIGluIHByaWNlIGJldHdlZW4gYW4gb3JkZXJzIHByaWNlIGFuZCB0aGUgYXZlcmFnZSBwcmljZSBvZiBvcmRlcnMgd2l0aCB0aGUgc2FtZSBzaG9lX3R5cGUuIFNhdmUgdGhlIHJlc3VsdCB0byBkaWZmX2Zyb21fbWVhbiwgYW5kIHZpZXcgaXQuDQoNCkRvbuKAmXQgZm9yZ2V0IHRvIGluY2x1ZGUgbmEucm0gPSBUUlVFIGFzIGFuIGFyZ3VtZW50IGluIHRoZSBzdW1tYXJ5IGZ1bmN0aW9uIHlvdSBjYWxsIQ0KDQpgYGB7cn0NCiMgZGVmaW5lIGRpZmZfZnJvbV9tZWFuIGhlcmU6DQpkaWZmX2Zyb21fbWVhbiA8LSBvcmRlcnMgJT4lDQogIGdyb3VwX2J5KHNob2VfdHlwZSkgJT4lDQogIG11dGF0ZShkaWZmX2Zyb21fc2hvZV90eXBlX21lYW4gPSBwcmljZSAtIG1lYW4ocHJpY2UsIG5hLnJtID0gVFJVRSkpDQoNCmRpZmZfZnJvbV9tZWFuDQpgYGANCg0KIyA3LlJldmlldw0KDQpUaGlzIGxlc3NvbiBpbnRyb2R1Y2VkIHlvdSB0byBhZ2dyZWdhdGVzIGluIFIgdXNpbmcgZHBseXIuIFlvdSBsZWFybmVkOg0KDQoxLkhvdyB0byBjYWxjdWxhdGUgc3VtbWFyeSBzdGF0aXN0aWNzIHdpdGggc3VtbWFyaXplKCkNCg0KMi5Ib3cgdG8gcGVyZm9ybSBhZ2dyZWdhdGUgc3RhdGlzdGljcyBvdmVyIGluZGl2aWR1YWwgcm93cyB3aXRoIHRoZSBzYW1lIHZhbHVlIG9yIHZhbHVlcyB1c2luZyBncm91cF9ieSgpDQoNCiMjIEluc3RydWN0aW9ucw0KMS5MZXTigJlzIGV4YW1pbmUgc29tZSBtb3JlIGRhdGEgZnJvbSBTaG9lRmx5LmNvbS4gVGhpcyB0aW1lLCBpbiBhZGRpdGlvbiB0byB0aGUgb3JkZXJzIGRhdGEsIHdl4oCZbGwgYmUgbG9va2luZyBhdCBkYXRhIGFib3V0IHVzZXIgdmlzaXRzIHRvIHRoZSB3ZWJzaXRlLCBzdG9yZWQgaW4gdGhlIHBhZ2VfdmlzaXRzIGRhdGEgZnJhbWUuIEluc3BlY3QgdGhlIGNvbHVtbnMgb2YgdGhlIGRhdGEgZnJhbWVzIHVzaW5nIHRoZSByZW5kZXJlZCBub3RlYm9vay4NCg0KRmluZCB0aGUgYXZlcmFnZSBwcmljZSBvZiBhbiBvcmRlciBpbiB0aGUgb3JkZXJzIGRhdGEgZnJhbWUgdXNpbmcgc3VtbWFyaXplKCkgYW5kIHRoZSBtZWFuKCkgc3VtbWFyeSBmdW5jdGlvbi4gU2F2ZSB0aGUgcmVzdWx0aW5nIGRhdGEgZnJhbWUgdG8gYSB2YXJpYWJsZSBuYW1lZCBhdmVyYWdlX3ByaWNlIGFuZCB2aWV3IGl0Lg0KDQpEb27igJl0IGZvcmdldCB0byBpbmNsdWRlIG5hLnJtID0gVFJVRSBhcyBhbiBhcmd1bWVudCBpbiB0aGUgY2FsbCB0byBtZWFuKCkhDQoNCmBgYHtyIG1lc3NhZ2UgPSBGQUxTRX0NCiMgbG9hZCBkYXRhDQpvcmRlcnMgPC0gcmVhZF9jc3YoIm9yZGVycy5jc3YiKQ0KcGFnZV92aXNpdHMgPC0gcmVhZF9jc3YoInBhZ2VfdmlzaXRzLmNzdiIpDQpgYGANCg0KYGBge3J9DQojIGluc3BlY3QgZGF0YSBmcmFtZXMNCmhlYWQob3JkZXJzKQ0KaGVhZChwYWdlX3Zpc2l0cykNCmBgYA0KDQpgYGB7cn0NCiMgZGVmaW5lIGF2ZXJhZ2VfcHJpY2UgaGVyZToNCmF2ZXJhZ2VfcHJpY2UgPC0gb3JkZXJzICU+JQ0KICBzdW1tYXJpemUoYXZnX3ByaWNlID0gbWVhbihwcmljZSwgbmEucm0gPSBUUlVFKSkNCg0KYXZlcmFnZV9wcmljZQ0KYGBgDQoNCjIuSW4gdGhlIHBhZ2VfdmlzaXRzIGRhdGEgZnJhbWUsIHRoZSBjb2x1bW4gdXRtX3NvdXJjZSBjb250YWlucyBpbmZvcm1hdGlvbiBhYm91dCBob3cgdXNlcnMgZ290IHRvIFNob2VGbHnigJlzIGhvbWVwYWdlLiBGb3IgaW5zdGFuY2UsIGlmIHV0bV9zb3VyY2UgPSBGYWNlYm9vaywgdGhlbiB0aGUgdXNlciBjYW1lIHRvIFNob2VGbHkgYnkgY2xpY2tpbmcgb24gYW4gYWQgb24gRmFjZWJvb2suY29tLg0KDQpVc2UgYSBncm91cF9ieSBzdGF0ZW1lbnQgdG8gY2FsY3VsYXRlIGhvdyBtYW55IHZpc2l0cyBjYW1lIGZyb20gZWFjaCBvZiB0aGUgZGlmZmVyZW50IHNvdXJjZXMuIFNhdmUgeW91ciBhbnN3ZXIgdG8gdGhlIHZhcmlhYmxlIGNsaWNrX3NvdXJjZSwgYW5kIHZpZXcgaXQuDQoNCmBgYHtyfQ0KIyBkZWZpbmUgY2xpY2tfc291cmNlIGhlcmU6DQpjbGlja19zb3VyY2UgPC0gcGFnZV92aXNpdHMgJT4lDQogIGdyb3VwX2J5KHV0bV9zb3VyY2UpICU+JQ0KICBzdW1tYXJpemUoY291bnQgPSBuKCkpDQoNCmNsaWNrX3NvdXJjZQ0KYGBgDQoNCjMuT3VyIE1hcmtldGluZyBkZXBhcnRtZW50IHRoaW5rcyB0aGF0IHRoZSB0cmFmZmljIHRvIG91ciBzaXRlIGhhcyBiZWVuIGNoYW5naW5nIG92ZXIgdGhlIHBhc3QgZmV3IG1vbnRocy4gVXNlIGdyb3VwX2J5IHRvIGNhbGN1bGF0ZSB0aGUgbnVtYmVyIG9mIHZpc2l0cyB0byBvdXIgc2l0ZSBmcm9tIGVhY2ggdXRtX3NvdXJjZSBmb3IgZWFjaCBtb250aC4gU2F2ZSB5b3VyIGFuc3dlciB0byB0aGUgdmFyaWFibGUgY2xpY2tfc291cmNlX2J5X21vbnRoLCBhbmQgdmlldyBpdC4NCg0KYGBge3J9DQojIGRlZmluZSBjbGlja19zb3VyY2VfYnlfbW9udGggaGVyZToNCmNsaWNrX3NvdXJjZV9ieV9tb250aCA8LSBwYWdlX3Zpc2l0cyAlPiUNCiAgZ3JvdXBfYnkodXRtX3NvdXJjZSwgbW9udGgpICU+JQ0KICBzdW1tYXJpemUoY291bnQgPSBuKCkpDQoNCmNsaWNrX3NvdXJjZV9ieV9tb250aA0KYGBg