Joinning tables
Inner join
The inner_join is the key to bring tables together. To use it, you need to provide the two tables that must be joined and the columns on which they should be joined.
library(dplyr)
Registered S3 method overwritten by 'dplyr':
method from
print.rowwise_df
Attaching package: ‘dplyr’
The following objects are masked from ‘package:stats’:
filter, lag
The following objects are masked from ‘package:base’:
intersect, setdiff, setequal, union
library(readr)
library(tidyr)
parts <- read.csv("parts.csv", stringsAsFactors = FALSE)
EOF within quoted string
part_categories <- read.csv("part_categories.csv", stringsAsFactors = FALSE)
inventories <- read.csv("inventories.csv", stringsAsFactors = FALSE)
inventory_parts <- read.csv("inventory_parts.csv", stringsAsFactors = FALSE)
colors <- read.csv("colors.csv", stringsAsFactors = FALSE)
sets <- read.csv("sets.csv", stringsAsFactors = FALSE)
themes <- read.csv("themes.csv", stringsAsFactors = FALSE)
parts %>%
inner_join(part_categories, by = c("part_cat_id" = "id"), suffix = c("_part", "_category"))
We can now see the part category associated with each part, and you used the suffix argument to make sure your table’s fields are clear.
Joining parts and inventories
The LEGO data has many tables that can be joined together. Often times, some of the things you care about may be a few tables away. For now, we know that parts is a list of all LEGO parts, and a new table, inventory_parts, has some additional information about those parts, such as the color_id of each part you would find in a specific LEGO kit.
Let’s join these two tables together to observe how joining parts with inventory_parts increases the size of your table because of the one-to-many relationship that exists between these two tables.
# Combine the parts and inventory_parts tables
parts %>%
inner_join(inventory_parts, by = "part_num")
This is an example of a one-to-many relationship. Notice that the table increased in the number of rows after the join.
Joining in either direction
An inner_join works the same way with either table in either position. The table that is specified first is arbitrary, since you will end up with the same information in the resulting table either way.
# Combine the parts and inventory_parts tables
inventory_parts %>%
inner_join(parts, by = "part_num")
This is the same join as before, but the order of the tables is reversed. For an inner_join, either direction will yield a table that contains the same information! Note that the columns will appear in a different order depending on which table comes first.
Joining three tables
You can string together multiple joins with inner_join and the pipe (%>%).
We’ll now connect sets, a table that tells us about each LEGO kit, with inventories, a table that tells us the specific version of a given set, and finally to inventory_parts, a table which tells us how many of each part is available in each LEGO kit.
So, sets would tell you the name of the set, inventories would give you IDs for each of the versions of the set, and inventory_parts would tell you how many of each part would be in each version.
sets %>%
# Add inventories using an inner join
inner_join(inventories, by="set_num") %>%
# Add inventory_parts using an inner join
inner_join(inventory_parts, by= c("id" = "inventory_id"))
What’s the most common color?
Now let’s join an additional table, colors, which will tell us the color of each part in each set, so that we can answer the question, “what is the most common color of a LEGO piece?”
# Count the number of colors and sort
sets %>%
inner_join(inventories, by = "set_num") %>%
inner_join(inventory_parts, by = c("id" = "inventory_id")) %>%
inner_join(colors, by = c("color_id" = "id"), suffix = c("_set", "_color")) %>%
count(name_color, sort = TRUE)
Black and White are the two most prominent colors.
Left and Right Joins
Left joining two sets by part and color
We’ll practice your ability to do this looking at two new sets: the Millennium Falcon and Star Destroyer sets.
inventory_parts_joined <- inventories %>%
inner_join(inventory_parts, by = c("id" = "inventory_id")) %>%
select(-id, -version) %>%
arrange(desc(quantity))
millennium_falcon <- inventory_parts_joined %>%
filter(set_num == "7965-1")
star_destroyer <- inventory_parts_joined %>%
filter(set_num == "75159-1")
# Combine the star_destroyer and millennium_falcon tables
millennium_falcon %>%
left_join(star_destroyer, by= c("part_num", "color_id"), suffix = c("_falcon", "_star_destroyer"))
Using this table, and the part_num or color_id columns, you can determine the frequency that a specific part or piece appears in the Millennium Falcon and Star Destroyer sets.
Left joining two sets by color
What if we join the datasets by color alone?
# Aggregate Millennium Falcon for the total quantity in each part
millennium_falcon_colors <- millennium_falcon %>%
group_by(color_id) %>%
summarize(total_quantity = sum(quantity))
# Aggregate Star Destroyer for the total quantity in each part
star_destroyer_colors <- star_destroyer %>%
group_by(color_id) %>%
summarize(total_quantity = sum(quantity))
# Left join the Millennium Falcon colors to the Star Destroyer colors
millennium_falcon_colors %>%
left_join(star_destroyer_colors, by = "color_id", suffix = c("_falcon", "_star_destroyer"))
Sometimes, the level on which you’re comparing two sets- by piece or just by color- will require some thought and some pre-processing.
Finding an observation that doesn’t have a match
Left joins are really great for testing your assumptions about a data set and ensuring your data has integrity.
For example, the inventories table has a version column, for when a LEGO kit gets some kind of change or upgrade. It would be fair to assume that all sets (which joins well with inventories) would have at least a version 1.
inventory_version_1 <- inventories %>%
filter(version == 1)
# Join versions to sets
sets %>%
left_join(inventory_version_1, by = "set_num") %>%
# Filter for where version is na
filter(is.na(version))
It looks like there are cases where a set does not have an original version.
Counting part colors
Sometimes you’ll want to do some processing before you do a join, and prioritize keeping the the second (right) table’s rows instead.
parts %>%
# Count the part_cat_id
count(part_cat_id) %>%
# Right join part_categories
right_join(part_categories, by = c("part_cat_id" = "id")) %>%
# Filter for NA
filter(is.na(n))
Cleaning up your count
In both left and right joins, there is the opportunity for there to be NA values in the resulting table. Fortunately, the replace_na function can turn those NAs into meaningful values.
parts %>%
count(part_cat_id) %>%
right_join(part_categories, by = c("part_cat_id" = "id")) %>%
# Use replace_na to replace missing values in the n column
replace_na(list(n = 0))
Joining tables to themselves
Tables can be joined to themselves!
In the themes table, which is available for you to inspect in the console, you’ll notice there is both an id column and a parent_id column. Keeping that in mind, you can join the themes table to itself to determine the parent-child relationships that exist for different themes.
themes %>%
# Inner join the themes table
inner_join(themes, by = c("id" = "parent_id"), suffix = c("_parent", "_child")) %>%
# Filter for the "Harry Potter" parent name
filter(name_parent == "Harry Potter")
It looks like the Harry Potter parent theme has a few children, which are named after the books and movies from the series.
Joining themes to their grandchildren
We can go a step further than looking at themes and their children. Some themes actually have grandchildren: their children’s children.
Here, we can inner join themes to a filtered version of itself again to establish a between our last join’s children and their children.
# Join themes to itself again to find the grandchild relationships
themes %>%
inner_join(themes, by = c("id" = "parent_id"), suffix = c("_parent", "_child")) %>%
inner_join(themes, by = c("id_child" = "parent_id"), suffix = c("_parent", "_grandchild"))
Left-joining a table to itself
Some themes might not have any children at all, which means they won’t be included in the inner join.
themes %>%
# Left join the themes table to its own children
left_join(themes, by = c("id" = "parent_id"), suffix = c("_parent", "_child")) %>%
# Filter for themes that have no child themes
filter(is.na(id_child))
Full, Semi, and Anti Joins
Differences between Batman and Star Wars
We’ll compare two themes, each of which is made up of many sets.
inventory_parts_joined <- inventories %>%
inner_join(inventory_parts, by = c("id" = "inventory_id")) %>%
arrange(desc(quantity)) %>%
select(-id, -version)
inventory_parts_joined %>%
# Combine the sets table with inventory_parts_joined
inner_join(sets, by = c("set_num")) %>%
# Combine the themes table with your first join
inner_join(themes, by = c("theme_id" = "id"), suffix = c("_set", "_theme"))
Since each theme is made up of many sets, combining these tables is the first step towards being able to compare different themes.
Aggregating each theme
Previously, you combined tables to compare themes. Before doing this comparison, you’ll want to aggregate the data to learn more about the pieces that are a part of each theme, as well as the colors of those pieces.
inventory_sets_themes <- inventory_parts_joined %>%
inner_join(sets, by = "set_num") %>%
inner_join(themes, by = c("theme_id" = "id"), suffix = c("_set", "_theme"))
batman <- inventory_sets_themes %>%
filter(name_theme == "Batman")
star_wars <- inventory_sets_themes %>%
filter(name_theme == "Star Wars")
# Count the part number and color id, weight by quantity
batman %>%
count(part_num, color_id, wt = quantity)
star_wars %>%
count(part_num, color_id, wt = quantity)
Full-joining Batman and Star Wars LEGO parts
Now that we’ve got separate tables for the pieces in the batman and star_wars themes, we’ll want to be able to combine them to see any similarities or differences between the two themes.
batman_parts <- batman %>%
count(part_num, color_id, wt = quantity)
star_wars_parts <- star_wars %>%
count(part_num, color_id, wt = quantity)
batman_parts %>%
# Combine the star_wars_parts table
full_join(star_wars_parts, by = c("part_num", "color_id"), suffix = c("_batman", "_star_wars")) %>%
# Replace NAs with 0s in the n_batman and n_star_wars columns
replace_na(list(n_batman = 0, n_star_wars = 0))
Now, we’ve created a comprehensive table that includes the part number, color id, and quantity of each piece in the Batman and Star Wars themes!
Comparing Batman and Star Wars LEGO parts
The table you created in the last exercise includes the part number of each piece, the color id, and the number of each piece in the Star Wars and Batman themes. However, we have more information about each of these parts that we can gain by combining this table with some of the information we have in other tables.
parts_joined <- batman_parts %>%
full_join(star_wars_parts, by = c("part_num", "color_id"), suffix = c("_batman", "_star_wars")) %>%
replace_na(list(n_batman = 0, n_star_wars = 0))
parts_joined %>%
# Sort the number of star wars pieces in descending order
arrange(desc(n_star_wars)) %>%
# Join the colors table to the parts_joined table
inner_join(colors, by = c("color_id" = "id")) %>%
# Join the parts table to the previous join
inner_join(parts, by = "part_num", suffix = c("_color", "_part"))
We can now see the pieces side by side from each of these themes. Since the pieces are sorted by number of Star Wars pieces in descending order, we can see that the most common Star Wars piece is Black and has the part number 2780. While there are 392 pieces of this part in the Star Wars theme, we can also see from the table that there are 104 pieces of the same part in the Batman theme.
Something within one set but not another
We will determine which parts are in both the batwing and batmobile sets, and which sets are in one, but not the other. While answering these questions, we’ll also be determining whether or not the parts we’re looking at in both sets also have the same color in common.
batmobile <- inventory_parts_joined %>%
filter(set_num == "7784-1") %>%
select(-set_num)
batwing <- inventory_parts_joined %>%
filter(set_num == "70916-1") %>%
select(-set_num)
# Filter the batwing set for parts that are also in the batmobile set
batwing %>%
semi_join(batmobile, by = c("part_num"))
# Filter the batwing set for parts that aren't in the batmobile set
batwing %>%
anti_join(batmobile, by = c("part_num"))
Based on these joins, we now know that there are 126 parts in the batwing set that are also in the batmobile set, and 183 parts that are in the batwing set that aren’t in the batmobile set.
What colors are included in at least one set?
Besides comparing two sets directly, you could also use a filtering join like semi_join to find out which colors ever appear in any inventory part. Some of the colors could be optional, meaning they aren’t included in any sets.
# Use inventory_parts to find colors included in at least one set
colors %>%
semi_join(inventory_parts, by = c("id" = "color_id"))
Which set is missing version 1?
Each set included in the LEGO data has an associated version number. We want to understand the version we are looking at to learn more about the parts that are included. Before doing that, we should confirm that there aren’t any sets that are missing a particular version.
# Use filter() to extract version 1
version_1_inventories <- inventories %>%
filter(version == 1)
# Use anti_join() to find which set is missing a version 1
sets %>%
anti_join(version_1_inventories, by = c("set_num"))
Aggregating sets to look at their differences
To compare two individual sets, and the kinds of LEGO pieces that comprise them, we’ll need to aggregate the data into separate themes. Additionally, we’ll want to add a column so that we can understand the percentages of specific pieces that are part of each set, rather than looking at the numbers of pieces alone.
inventory_parts_themes <- inventories %>%
inner_join(inventory_parts, by = c("id" = "inventory_id")) %>%
arrange(desc(quantity)) %>%
select(-id, -version) %>%
inner_join(sets, by = "set_num") %>%
inner_join(themes, by = c("theme_id" = "id"), suffix = c("_set", "_theme")) %>%
replace_na(list(quantity = 0))
batman_colors <- inventory_parts_themes %>%
# Filter the inventory_parts_themes table for the Batman theme
filter(name_theme == "Batman") %>%
group_by(color_id) %>%
summarize(total = sum(quantity)) %>%
# Add a percent column of the total divided by the sum of the total
mutate(percent = total / sum(total))
# Filter and aggregate the Star Wars set data; add a percent column
star_wars_colors <- inventory_parts_themes %>%
filter(name_theme == "Star Wars") %>%
group_by(color_id) %>%
summarize(total = sum(quantity)) %>%
mutate(percent = total/ sum(total))
batman_colors %>%
full_join(star_wars_colors, by = "color_id", suffix = c("_batman", "_star_wars")) %>%
replace_na(list(total_batman = 0, total_star_wars = 0, percent_batman = 0, percent_star_wars = 0)) %>%
inner_join(colors, by = c("color_id" = "id")) %>%
# Create the difference and total columns
mutate(difference = percent_batman - percent_star_wars,
total = total_batman + total_star_wars) %>%
# Filter for totals greater than 200
filter(total>200)
With this combined table that contains all the information from the batman_colors and star_wars_colors tables, we can now create an informative visualization to compare the colors of these sets.
Visualizing the difference: Batman and Star Wars
We created colors_joined. Now we’ll create a bar plot with one bar for each color (name), showing the difference in percentages.
library(forcats)
colors_joined <- batman_colors %>%
full_join(star_wars_colors, by = "color_id", suffix = c("_batman", "_star_wars")) %>%
replace_na(list(total_batman = 0, total_star_wars = 0, percent_batman = 0, percent_star_wars = 0)) %>%
inner_join(colors, by = c("color_id" = "id")) %>%
mutate(difference = percent_batman - percent_star_wars,
total = total_batman + total_star_wars) %>%
filter(total >= 200) %>%
mutate(name = fct_reorder(name, difference))
# we need to add # to create our palette
colors_joined$rgb <- paste("#", colors_joined$rgb, sep="")
color_palette <- setNames(colors_joined$rgb, colors_joined$name)
library(ggplot2)
# Create a bar plot using colors_joined and the name and difference columns
ggplot(colors_joined, aes(name, difference, fill = name)) +
geom_col() +
coord_flip() +
scale_fill_manual(values = color_palette, guide = FALSE) +
labs(y = "Difference: Batman - Star Wars")

As you can see from the plot, the Batman set has more black, yellow, and red, while the Star Wars set has more light bluish gray, white, and tan.
LS0tCnRpdGxlOiAiSm9pbmluZyBEYXRhIHdpdGggZHBseXIiCm91dHB1dDoKICBodG1sX25vdGVib29rOgogICAgdG9jOiB0cnVlCiAgICB0b2NfZmxvYXQ6IHRydWUKICAgIHRvY19jb2xsYXBzZWQ6IHRydWUKICAgIAp0b2NfZGVwdGg6IDMKLS0tCiMgSm9pbm5pbmcgdGFibGVzCgojIyBJbm5lciBqb2luCgpUaGUgaW5uZXJfam9pbiBpcyB0aGUga2V5IHRvIGJyaW5nIHRhYmxlcyB0b2dldGhlci4gVG8gdXNlIGl0LCB5b3UgbmVlZCB0byBwcm92aWRlIHRoZSB0d28gdGFibGVzIHRoYXQgbXVzdCBiZSBqb2luZWQgYW5kIHRoZSBjb2x1bW5zIG9uIHdoaWNoIHRoZXkgc2hvdWxkIGJlIGpvaW5lZC4KCmBgYHtyfQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KHRpZHlyKQpgYGAKCgpgYGB7cn0KcGFydHMgPC0gcmVhZC5jc3YoInBhcnRzLmNzdiIsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKcGFydF9jYXRlZ29yaWVzIDwtIHJlYWQuY3N2KCJwYXJ0X2NhdGVnb3JpZXMuY3N2Iiwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQppbnZlbnRvcmllcyA8LSByZWFkLmNzdigiaW52ZW50b3JpZXMuY3N2Iiwgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQppbnZlbnRvcnlfcGFydHMgPC0gcmVhZC5jc3YoImludmVudG9yeV9wYXJ0cy5jc3YiLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCmNvbG9ycyA8LSByZWFkLmNzdigiY29sb3JzLmNzdiIsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKc2V0cyA8LSByZWFkLmNzdigic2V0cy5jc3YiLCBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCnRoZW1lcyA8LSByZWFkLmNzdigidGhlbWVzLmNzdiIsIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKYGBgCmBgYHtyfQpwYXJ0cyAlPiUgCiAgICBpbm5lcl9qb2luKHBhcnRfY2F0ZWdvcmllcywgYnkgPSBjKCJwYXJ0X2NhdF9pZCIgPSAiaWQiKSwgc3VmZml4ID0gYygiX3BhcnQiLCAiX2NhdGVnb3J5IikpCmBgYApXZSBjYW4gbm93IHNlZSB0aGUgcGFydCBjYXRlZ29yeSBhc3NvY2lhdGVkIHdpdGggZWFjaCBwYXJ0LCBhbmQgeW91IHVzZWQgdGhlIHN1ZmZpeCBhcmd1bWVudCB0byBtYWtlIHN1cmUgeW91ciB0YWJsZSdzIGZpZWxkcyBhcmUgY2xlYXIuCgojIyBKb2luaW5nIHBhcnRzIGFuZCBpbnZlbnRvcmllcwoKVGhlIExFR08gZGF0YSBoYXMgbWFueSB0YWJsZXMgdGhhdCBjYW4gYmUgam9pbmVkIHRvZ2V0aGVyLiBPZnRlbiB0aW1lcywgc29tZSBvZiB0aGUgdGhpbmdzIHlvdSBjYXJlIGFib3V0IG1heSBiZSBhIGZldyB0YWJsZXMgYXdheS4gRm9yIG5vdywgd2Uga25vdyB0aGF0IHBhcnRzIGlzIGEgbGlzdCBvZiBhbGwgTEVHTyBwYXJ0cywgYW5kIGEgbmV3IHRhYmxlLCBpbnZlbnRvcnlfcGFydHMsIGhhcyBzb21lIGFkZGl0aW9uYWwgaW5mb3JtYXRpb24gYWJvdXQgdGhvc2UgcGFydHMsIHN1Y2ggYXMgdGhlIGNvbG9yX2lkIG9mIGVhY2ggcGFydCB5b3Ugd291bGQgZmluZCBpbiBhIHNwZWNpZmljIExFR08ga2l0LgoKTGV0J3Mgam9pbiB0aGVzZSB0d28gdGFibGVzIHRvZ2V0aGVyIHRvIG9ic2VydmUgaG93IGpvaW5pbmcgcGFydHMgd2l0aCBpbnZlbnRvcnlfcGFydHMgaW5jcmVhc2VzIHRoZSBzaXplIG9mIHlvdXIgdGFibGUgYmVjYXVzZSBvZiB0aGUgb25lLXRvLW1hbnkgcmVsYXRpb25zaGlwIHRoYXQgZXhpc3RzIGJldHdlZW4gdGhlc2UgdHdvIHRhYmxlcy4KYGBge3J9CiMgQ29tYmluZSB0aGUgcGFydHMgYW5kIGludmVudG9yeV9wYXJ0cyB0YWJsZXMKcGFydHMgJT4lCiAgaW5uZXJfam9pbihpbnZlbnRvcnlfcGFydHMsIGJ5ID0gInBhcnRfbnVtIikKYGBgClRoaXMgaXMgYW4gZXhhbXBsZSBvZiBhIG9uZS10by1tYW55IHJlbGF0aW9uc2hpcC4gTm90aWNlIHRoYXQgdGhlIHRhYmxlIGluY3JlYXNlZCBpbiB0aGUgbnVtYmVyIG9mIHJvd3MgYWZ0ZXIgdGhlIGpvaW4uCgojIyBKb2luaW5nIGluIGVpdGhlciBkaXJlY3Rpb24KCkFuIGlubmVyX2pvaW4gd29ya3MgdGhlIHNhbWUgd2F5IHdpdGggZWl0aGVyIHRhYmxlIGluIGVpdGhlciBwb3NpdGlvbi4gVGhlIHRhYmxlIHRoYXQgaXMgc3BlY2lmaWVkIGZpcnN0IGlzIGFyYml0cmFyeSwgc2luY2UgeW91IHdpbGwgZW5kIHVwIHdpdGggdGhlIHNhbWUgaW5mb3JtYXRpb24gaW4gdGhlIHJlc3VsdGluZyB0YWJsZSBlaXRoZXIgd2F5LgpgYGB7cn0KIyBDb21iaW5lIHRoZSBwYXJ0cyBhbmQgaW52ZW50b3J5X3BhcnRzIHRhYmxlcwppbnZlbnRvcnlfcGFydHMgJT4lCiAgaW5uZXJfam9pbihwYXJ0cywgYnkgPSAicGFydF9udW0iKQpgYGAKVGhpcyBpcyB0aGUgc2FtZSBqb2luIGFzIGJlZm9yZSwgYnV0IHRoZSBvcmRlciBvZiB0aGUgdGFibGVzIGlzIHJldmVyc2VkLiBGb3IgYW4gaW5uZXJfam9pbiwgZWl0aGVyIGRpcmVjdGlvbiB3aWxsIHlpZWxkIGEgdGFibGUgdGhhdCBjb250YWlucyB0aGUgc2FtZSBpbmZvcm1hdGlvbiEgTm90ZSB0aGF0IHRoZSBjb2x1bW5zIHdpbGwgYXBwZWFyIGluIGEgZGlmZmVyZW50IG9yZGVyIGRlcGVuZGluZyBvbiB3aGljaCB0YWJsZSBjb21lcyBmaXJzdC4KCiMjIEpvaW5pbmcgdGhyZWUgdGFibGVzCgpZb3UgY2FuIHN0cmluZyB0b2dldGhlciBtdWx0aXBsZSBqb2lucyB3aXRoIGlubmVyX2pvaW4gYW5kIHRoZSBwaXBlICglPiUpLgoKV2UnbGwgbm93IGNvbm5lY3Qgc2V0cywgYSB0YWJsZSB0aGF0IHRlbGxzIHVzIGFib3V0IGVhY2ggTEVHTyBraXQsIHdpdGggaW52ZW50b3JpZXMsIGEgdGFibGUgdGhhdCB0ZWxscyB1cyB0aGUgc3BlY2lmaWMgdmVyc2lvbiBvZiBhIGdpdmVuIHNldCwgYW5kIGZpbmFsbHkgdG8gaW52ZW50b3J5X3BhcnRzLCBhIHRhYmxlIHdoaWNoIHRlbGxzIHVzIGhvdyBtYW55IG9mIGVhY2ggcGFydCBpcyBhdmFpbGFibGUgaW4gZWFjaCBMRUdPIGtpdC4KClNvLCBzZXRzIHdvdWxkIHRlbGwgeW91IHRoZSBuYW1lIG9mIHRoZSBzZXQsIGludmVudG9yaWVzIHdvdWxkIGdpdmUgeW91IElEcyBmb3IgZWFjaCBvZiB0aGUgdmVyc2lvbnMgb2YgdGhlIHNldCwgYW5kIGludmVudG9yeV9wYXJ0cyB3b3VsZCB0ZWxsIHlvdSBob3cgbWFueSBvZiBlYWNoIHBhcnQgd291bGQgYmUgaW4gZWFjaCB2ZXJzaW9uLgpgYGB7cn0Kc2V0cyAlPiUKCSMgQWRkIGludmVudG9yaWVzIHVzaW5nIGFuIGlubmVyIGpvaW4gCglpbm5lcl9qb2luKGludmVudG9yaWVzLCBieT0ic2V0X251bSIpICU+JQoJIyBBZGQgaW52ZW50b3J5X3BhcnRzIHVzaW5nIGFuIGlubmVyIGpvaW4gCglpbm5lcl9qb2luKGludmVudG9yeV9wYXJ0cywgYnk9IGMoImlkIiA9ICJpbnZlbnRvcnlfaWQiKSkKYGBgCiMjIFdoYXQncyB0aGUgbW9zdCBjb21tb24gY29sb3I/CgpOb3cgbGV0J3Mgam9pbiBhbiBhZGRpdGlvbmFsIHRhYmxlLCBjb2xvcnMsIHdoaWNoIHdpbGwgdGVsbCB1cyB0aGUgY29sb3Igb2YgZWFjaCBwYXJ0IGluIGVhY2ggc2V0LCBzbyB0aGF0IHdlIGNhbiBhbnN3ZXIgdGhlIHF1ZXN0aW9uLCAid2hhdCBpcyB0aGUgbW9zdCBjb21tb24gY29sb3Igb2YgYSBMRUdPIHBpZWNlPyIKYGBge3J9CiMgQ291bnQgdGhlIG51bWJlciBvZiBjb2xvcnMgYW5kIHNvcnQKc2V0cyAlPiUKCWlubmVyX2pvaW4oaW52ZW50b3JpZXMsIGJ5ID0gInNldF9udW0iKSAlPiUKCWlubmVyX2pvaW4oaW52ZW50b3J5X3BhcnRzLCBieSA9IGMoImlkIiA9ICJpbnZlbnRvcnlfaWQiKSkgJT4lCglpbm5lcl9qb2luKGNvbG9ycywgYnkgPSBjKCJjb2xvcl9pZCIgPSAiaWQiKSwgc3VmZml4ID0gYygiX3NldCIsICJfY29sb3IiKSkgJT4lCiAgY291bnQobmFtZV9jb2xvciwgc29ydCA9IFRSVUUpICAKYGBgCkJsYWNrIGFuZCBXaGl0ZSBhcmUgdGhlIHR3byBtb3N0IHByb21pbmVudCBjb2xvcnMuCgojIExlZnQgYW5kIFJpZ2h0IEpvaW5zCgojIyBMZWZ0IGpvaW5pbmcgdHdvIHNldHMgYnkgcGFydCBhbmQgY29sb3IKCldlJ2xsIHByYWN0aWNlIHlvdXIgYWJpbGl0eSB0byBkbyB0aGlzIGxvb2tpbmcgYXQgdHdvIG5ldyBzZXRzOiB0aGUgTWlsbGVubml1bSBGYWxjb24gYW5kIFN0YXIgRGVzdHJveWVyIHNldHMuCmBgYHtyfQppbnZlbnRvcnlfcGFydHNfam9pbmVkIDwtIGludmVudG9yaWVzICU+JQogIGlubmVyX2pvaW4oaW52ZW50b3J5X3BhcnRzLCBieSA9IGMoImlkIiA9ICJpbnZlbnRvcnlfaWQiKSkgJT4lCiAgc2VsZWN0KC1pZCwgLXZlcnNpb24pICU+JQogIGFycmFuZ2UoZGVzYyhxdWFudGl0eSkpCmBgYAoKCmBgYHtyfQptaWxsZW5uaXVtX2ZhbGNvbiA8LSBpbnZlbnRvcnlfcGFydHNfam9pbmVkICU+JQogIGZpbHRlcihzZXRfbnVtID09ICI3OTY1LTEiKQoKc3Rhcl9kZXN0cm95ZXIgPC0gaW52ZW50b3J5X3BhcnRzX2pvaW5lZCAlPiUKICBmaWx0ZXIoc2V0X251bSA9PSAiNzUxNTktMSIpCmBgYAoKYGBge3J9CiMgQ29tYmluZSB0aGUgc3Rhcl9kZXN0cm95ZXIgYW5kIG1pbGxlbm5pdW1fZmFsY29uIHRhYmxlcwptaWxsZW5uaXVtX2ZhbGNvbiAlPiUKICBsZWZ0X2pvaW4oc3Rhcl9kZXN0cm95ZXIsIGJ5PSBjKCJwYXJ0X251bSIsICJjb2xvcl9pZCIpLCBzdWZmaXggPSBjKCJfZmFsY29uIiwgIl9zdGFyX2Rlc3Ryb3llciIpKQpgYGAKVXNpbmcgdGhpcyB0YWJsZSwgYW5kIHRoZSBwYXJ0X251bSBvciBjb2xvcl9pZCBjb2x1bW5zLCB5b3UgY2FuIGRldGVybWluZSB0aGUgZnJlcXVlbmN5IHRoYXQgYSBzcGVjaWZpYyBwYXJ0IG9yIHBpZWNlIGFwcGVhcnMgaW4gdGhlIE1pbGxlbm5pdW0gRmFsY29uIGFuZCBTdGFyIERlc3Ryb3llciBzZXRzLgoKIyMgTGVmdCBqb2luaW5nIHR3byBzZXRzIGJ5IGNvbG9yCgpXaGF0IGlmIHdlIGpvaW4gdGhlIGRhdGFzZXRzIGJ5IGNvbG9yIGFsb25lPyAKYGBge3J9CiMgQWdncmVnYXRlIE1pbGxlbm5pdW0gRmFsY29uIGZvciB0aGUgdG90YWwgcXVhbnRpdHkgaW4gZWFjaCBwYXJ0Cm1pbGxlbm5pdW1fZmFsY29uX2NvbG9ycyA8LSBtaWxsZW5uaXVtX2ZhbGNvbiAlPiUKICBncm91cF9ieShjb2xvcl9pZCkgJT4lCiAgc3VtbWFyaXplKHRvdGFsX3F1YW50aXR5ID0gc3VtKHF1YW50aXR5KSkKYGBgCmBgYHtyfQojIEFnZ3JlZ2F0ZSBTdGFyIERlc3Ryb3llciBmb3IgdGhlIHRvdGFsIHF1YW50aXR5IGluIGVhY2ggcGFydApzdGFyX2Rlc3Ryb3llcl9jb2xvcnMgPC0gc3Rhcl9kZXN0cm95ZXIgJT4lCiAgZ3JvdXBfYnkoY29sb3JfaWQpICU+JQogIHN1bW1hcml6ZSh0b3RhbF9xdWFudGl0eSA9IHN1bShxdWFudGl0eSkpCmBgYApgYGB7cn0KIyBMZWZ0IGpvaW4gdGhlIE1pbGxlbm5pdW0gRmFsY29uIGNvbG9ycyB0byB0aGUgU3RhciBEZXN0cm95ZXIgY29sb3JzCm1pbGxlbm5pdW1fZmFsY29uX2NvbG9ycyAlPiUKICBsZWZ0X2pvaW4oc3Rhcl9kZXN0cm95ZXJfY29sb3JzLCBieSA9ICJjb2xvcl9pZCIsIHN1ZmZpeCA9IGMoIl9mYWxjb24iLCAiX3N0YXJfZGVzdHJveWVyIikpCmBgYApTb21ldGltZXMsIHRoZSBsZXZlbCBvbiB3aGljaCB5b3UncmUgY29tcGFyaW5nIHR3byBzZXRzLSBieSBwaWVjZSBvciBqdXN0IGJ5IGNvbG9yLSB3aWxsIHJlcXVpcmUgc29tZSB0aG91Z2h0IGFuZCBzb21lIHByZS1wcm9jZXNzaW5nLgoKIyMgRmluZGluZyBhbiBvYnNlcnZhdGlvbiB0aGF0IGRvZXNuJ3QgaGF2ZSBhIG1hdGNoCgpMZWZ0IGpvaW5zIGFyZSByZWFsbHkgZ3JlYXQgZm9yIHRlc3RpbmcgeW91ciBhc3N1bXB0aW9ucyBhYm91dCBhIGRhdGEgc2V0IGFuZCBlbnN1cmluZyB5b3VyIGRhdGEgaGFzIGludGVncml0eS4KCkZvciBleGFtcGxlLCB0aGUgaW52ZW50b3JpZXMgdGFibGUgaGFzIGEgdmVyc2lvbiBjb2x1bW4sIGZvciB3aGVuIGEgTEVHTyBraXQgZ2V0cyBzb21lIGtpbmQgb2YgY2hhbmdlIG9yIHVwZ3JhZGUuIEl0IHdvdWxkIGJlIGZhaXIgdG8gYXNzdW1lIHRoYXQgYWxsIHNldHMgKHdoaWNoIGpvaW5zIHdlbGwgd2l0aCBpbnZlbnRvcmllcykgd291bGQgaGF2ZSBhdCBsZWFzdCBhIHZlcnNpb24gMS4KYGBge3J9CmludmVudG9yeV92ZXJzaW9uXzEgPC0gaW52ZW50b3JpZXMgJT4lCiAgZmlsdGVyKHZlcnNpb24gPT0gMSkKCiMgSm9pbiB2ZXJzaW9ucyB0byBzZXRzCnNldHMgJT4lCiAgbGVmdF9qb2luKGludmVudG9yeV92ZXJzaW9uXzEsIGJ5ID0gInNldF9udW0iKSAlPiUKICAjIEZpbHRlciBmb3Igd2hlcmUgdmVyc2lvbiBpcyBuYQogIGZpbHRlcihpcy5uYSh2ZXJzaW9uKSkKYGBgCkl0IGxvb2tzIGxpa2UgdGhlcmUgYXJlIGNhc2VzIHdoZXJlIGEgc2V0IGRvZXMgbm90IGhhdmUgYW4gb3JpZ2luYWwgdmVyc2lvbi4KCiMjIENvdW50aW5nIHBhcnQgY29sb3JzCgpTb21ldGltZXMgeW91J2xsIHdhbnQgdG8gZG8gc29tZSBwcm9jZXNzaW5nIGJlZm9yZSB5b3UgZG8gYSBqb2luLCBhbmQgcHJpb3JpdGl6ZSBrZWVwaW5nIHRoZSB0aGUgc2Vjb25kIChyaWdodCkgdGFibGUncyByb3dzIGluc3RlYWQuCmBgYHtyfQpwYXJ0cyAlPiUKCSMgQ291bnQgdGhlIHBhcnRfY2F0X2lkCgljb3VudChwYXJ0X2NhdF9pZCkgJT4lCgkjIFJpZ2h0IGpvaW4gcGFydF9jYXRlZ29yaWVzCglyaWdodF9qb2luKHBhcnRfY2F0ZWdvcmllcywgYnkgPSBjKCJwYXJ0X2NhdF9pZCIgPSAiaWQiKSkgJT4lIAogICMgRmlsdGVyIGZvciBOQQoJZmlsdGVyKGlzLm5hKG4pKQpgYGAKIyMgQ2xlYW5pbmcgdXAgeW91ciBjb3VudAoKSW4gYm90aCBsZWZ0IGFuZCByaWdodCBqb2lucywgdGhlcmUgaXMgdGhlIG9wcG9ydHVuaXR5IGZvciB0aGVyZSB0byBiZSBOQSB2YWx1ZXMgaW4gdGhlIHJlc3VsdGluZyB0YWJsZS4gRm9ydHVuYXRlbHksIHRoZSByZXBsYWNlX25hIGZ1bmN0aW9uIGNhbiB0dXJuIHRob3NlIE5BcyBpbnRvIG1lYW5pbmdmdWwgdmFsdWVzLgpgYGB7cn0KcGFydHMgJT4lCgljb3VudChwYXJ0X2NhdF9pZCkgJT4lCglyaWdodF9qb2luKHBhcnRfY2F0ZWdvcmllcywgYnkgPSBjKCJwYXJ0X2NhdF9pZCIgPSAiaWQiKSkgJT4lCgkjIFVzZSByZXBsYWNlX25hIHRvIHJlcGxhY2UgbWlzc2luZyB2YWx1ZXMgaW4gdGhlIG4gY29sdW1uCglyZXBsYWNlX25hKGxpc3QobiA9IDApKQpgYGAKIyMgSm9pbmluZyB0YWJsZXMgdG8gdGhlbXNlbHZlcwoKVGFibGVzIGNhbiBiZSBqb2luZWQgdG8gdGhlbXNlbHZlcyEKCkluIHRoZSB0aGVtZXMgdGFibGUsIHdoaWNoIGlzIGF2YWlsYWJsZSBmb3IgeW91IHRvIGluc3BlY3QgaW4gdGhlIGNvbnNvbGUsIHlvdSdsbCBub3RpY2UgdGhlcmUgaXMgYm90aCBhbiBpZCBjb2x1bW4gYW5kIGEgcGFyZW50X2lkIGNvbHVtbi4gS2VlcGluZyB0aGF0IGluIG1pbmQsIHlvdSBjYW4gam9pbiB0aGUgdGhlbWVzIHRhYmxlIHRvIGl0c2VsZiB0byBkZXRlcm1pbmUgdGhlIHBhcmVudC1jaGlsZCByZWxhdGlvbnNoaXBzIHRoYXQgZXhpc3QgZm9yIGRpZmZlcmVudCB0aGVtZXMuCgpgYGB7cn0KdGhlbWVzICU+JSAKCSMgSW5uZXIgam9pbiB0aGUgdGhlbWVzIHRhYmxlCglpbm5lcl9qb2luKHRoZW1lcywgYnkgPSBjKCJpZCIgPSAicGFyZW50X2lkIiksIHN1ZmZpeCA9IGMoIl9wYXJlbnQiLCAiX2NoaWxkIikpICU+JQoJIyBGaWx0ZXIgZm9yIHRoZSAiSGFycnkgUG90dGVyIiBwYXJlbnQgbmFtZSAKCWZpbHRlcihuYW1lX3BhcmVudCA9PSAiSGFycnkgUG90dGVyIikKYGBgCkl0IGxvb2tzIGxpa2UgdGhlIEhhcnJ5IFBvdHRlciBwYXJlbnQgdGhlbWUgaGFzIGEgZmV3IGNoaWxkcmVuLCB3aGljaCBhcmUgbmFtZWQgYWZ0ZXIgdGhlIGJvb2tzIGFuZCBtb3ZpZXMgZnJvbSB0aGUgc2VyaWVzLgoKIyMgSm9pbmluZyB0aGVtZXMgdG8gdGhlaXIgZ3JhbmRjaGlsZHJlbgoKV2UgY2FuIGdvIGEgc3RlcCBmdXJ0aGVyIHRoYW4gbG9va2luZyBhdCB0aGVtZXMgYW5kIHRoZWlyIGNoaWxkcmVuLiBTb21lIHRoZW1lcyBhY3R1YWxseSBoYXZlIGdyYW5kY2hpbGRyZW46IHRoZWlyIGNoaWxkcmVuJ3MgY2hpbGRyZW4uCgpIZXJlLCB3ZSBjYW4gaW5uZXIgam9pbiB0aGVtZXMgdG8gYSBmaWx0ZXJlZCB2ZXJzaW9uIG9mIGl0c2VsZiBhZ2FpbiB0byBlc3RhYmxpc2ggYSBiZXR3ZWVuIG91ciBsYXN0IGpvaW4ncyBjaGlsZHJlbiBhbmQgdGhlaXIgY2hpbGRyZW4uCgpgYGB7cn0KIyBKb2luIHRoZW1lcyB0byBpdHNlbGYgYWdhaW4gdG8gZmluZCB0aGUgZ3JhbmRjaGlsZCByZWxhdGlvbnNoaXBzCnRoZW1lcyAlPiUgCiAgaW5uZXJfam9pbih0aGVtZXMsIGJ5ID0gYygiaWQiID0gInBhcmVudF9pZCIpLCBzdWZmaXggPSBjKCJfcGFyZW50IiwgIl9jaGlsZCIpKSAlPiUKICBpbm5lcl9qb2luKHRoZW1lcywgYnkgPSBjKCJpZF9jaGlsZCIgPSAicGFyZW50X2lkIiksIHN1ZmZpeCA9IGMoIl9wYXJlbnQiLCAiX2dyYW5kY2hpbGQiKSkKYGBgCiMjIExlZnQtam9pbmluZyBhIHRhYmxlIHRvIGl0c2VsZgoKU29tZSB0aGVtZXMgbWlnaHQgbm90IGhhdmUgYW55IGNoaWxkcmVuIGF0IGFsbCwgd2hpY2ggbWVhbnMgdGhleSB3b24ndCBiZSBpbmNsdWRlZCBpbiB0aGUgaW5uZXIgam9pbi4KYGBge3J9CnRoZW1lcyAlPiUgCiAgIyBMZWZ0IGpvaW4gdGhlIHRoZW1lcyB0YWJsZSB0byBpdHMgb3duIGNoaWxkcmVuCiAgbGVmdF9qb2luKHRoZW1lcywgYnkgPSBjKCJpZCIgPSAicGFyZW50X2lkIiksIHN1ZmZpeCA9IGMoIl9wYXJlbnQiLCAiX2NoaWxkIikpICU+JQogICMgRmlsdGVyIGZvciB0aGVtZXMgdGhhdCBoYXZlIG5vIGNoaWxkIHRoZW1lcwogIGZpbHRlcihpcy5uYShpZF9jaGlsZCkpCmBgYAojIEZ1bGwsIFNlbWksIGFuZCBBbnRpIEpvaW5zCgojIyBEaWZmZXJlbmNlcyBiZXR3ZWVuIEJhdG1hbiBhbmQgU3RhciBXYXJzCgpXZSdsbCBjb21wYXJlIHR3byB0aGVtZXMsIGVhY2ggb2Ygd2hpY2ggaXMgbWFkZSB1cCBvZiBtYW55IHNldHMuCmBgYHtyfQoKaW52ZW50b3J5X3BhcnRzX2pvaW5lZCA8LSBpbnZlbnRvcmllcyAlPiUKICBpbm5lcl9qb2luKGludmVudG9yeV9wYXJ0cywgYnkgPSBjKCJpZCIgPSAiaW52ZW50b3J5X2lkIikpICU+JQogIGFycmFuZ2UoZGVzYyhxdWFudGl0eSkpICU+JQogIHNlbGVjdCgtaWQsIC12ZXJzaW9uKQpgYGAKYGBge3J9CmludmVudG9yeV9wYXJ0c19qb2luZWQgJT4lCiAgIyBDb21iaW5lIHRoZSBzZXRzIHRhYmxlIHdpdGggaW52ZW50b3J5X3BhcnRzX2pvaW5lZCAKICBpbm5lcl9qb2luKHNldHMsIGJ5ID0gYygic2V0X251bSIpKSAlPiUKICAjIENvbWJpbmUgdGhlIHRoZW1lcyB0YWJsZSB3aXRoIHlvdXIgZmlyc3Qgam9pbiAKICBpbm5lcl9qb2luKHRoZW1lcywgYnkgPSBjKCJ0aGVtZV9pZCIgPSAiaWQiKSwgc3VmZml4ID0gYygiX3NldCIsICJfdGhlbWUiKSkKYGBgClNpbmNlIGVhY2ggdGhlbWUgaXMgbWFkZSB1cCBvZiBtYW55IHNldHMsIGNvbWJpbmluZyB0aGVzZSB0YWJsZXMgaXMgdGhlIGZpcnN0IHN0ZXAgdG93YXJkcyBiZWluZyBhYmxlIHRvIGNvbXBhcmUgZGlmZmVyZW50IHRoZW1lcy4KCiMjIEFnZ3JlZ2F0aW5nIGVhY2ggdGhlbWUKClByZXZpb3VzbHksIHlvdSBjb21iaW5lZCB0YWJsZXMgdG8gY29tcGFyZSB0aGVtZXMuIEJlZm9yZSBkb2luZyB0aGlzIGNvbXBhcmlzb24sIHlvdSdsbCB3YW50IHRvIGFnZ3JlZ2F0ZSB0aGUgZGF0YSB0byBsZWFybiBtb3JlIGFib3V0IHRoZSBwaWVjZXMgdGhhdCBhcmUgYSBwYXJ0IG9mIGVhY2ggdGhlbWUsIGFzIHdlbGwgYXMgdGhlIGNvbG9ycyBvZiB0aG9zZSBwaWVjZXMuCmBgYHtyfQppbnZlbnRvcnlfc2V0c190aGVtZXMgPC0gaW52ZW50b3J5X3BhcnRzX2pvaW5lZCAlPiUKICBpbm5lcl9qb2luKHNldHMsIGJ5ID0gInNldF9udW0iKSAlPiUKICBpbm5lcl9qb2luKHRoZW1lcywgYnkgPSBjKCJ0aGVtZV9pZCIgPSAiaWQiKSwgc3VmZml4ID0gYygiX3NldCIsICJfdGhlbWUiKSkKCmJhdG1hbiA8LSBpbnZlbnRvcnlfc2V0c190aGVtZXMgJT4lCiAgZmlsdGVyKG5hbWVfdGhlbWUgPT0gIkJhdG1hbiIpCgpzdGFyX3dhcnMgPC0gaW52ZW50b3J5X3NldHNfdGhlbWVzICU+JQogIGZpbHRlcihuYW1lX3RoZW1lID09ICJTdGFyIFdhcnMiKQpgYGAKCmBgYHtyfQojIENvdW50IHRoZSBwYXJ0IG51bWJlciBhbmQgY29sb3IgaWQsIHdlaWdodCBieSBxdWFudGl0eQpiYXRtYW4gJT4lCiAgY291bnQocGFydF9udW0sIGNvbG9yX2lkLCB3dCA9IHF1YW50aXR5KQpzdGFyX3dhcnMgJT4lCiAgY291bnQocGFydF9udW0sIGNvbG9yX2lkLCB3dCA9IHF1YW50aXR5KQpgYGAKCiMjIEZ1bGwtam9pbmluZyBCYXRtYW4gYW5kIFN0YXIgV2FycyBMRUdPIHBhcnRzCgpOb3cgdGhhdCB3ZSd2ZSBnb3Qgc2VwYXJhdGUgdGFibGVzIGZvciB0aGUgcGllY2VzIGluIHRoZSBiYXRtYW4gYW5kIHN0YXJfd2FycyB0aGVtZXMsIHdlJ2xsIHdhbnQgdG8gYmUgYWJsZSB0byBjb21iaW5lIHRoZW0gdG8gc2VlIGFueSBzaW1pbGFyaXRpZXMgb3IgZGlmZmVyZW5jZXMgYmV0d2VlbiB0aGUgdHdvIHRoZW1lcy4KYGBge3J9CmJhdG1hbl9wYXJ0cyA8LSBiYXRtYW4gJT4lCiAgY291bnQocGFydF9udW0sIGNvbG9yX2lkLCB3dCA9IHF1YW50aXR5KQoKc3Rhcl93YXJzX3BhcnRzIDwtIHN0YXJfd2FycyAlPiUKICBjb3VudChwYXJ0X251bSwgY29sb3JfaWQsIHd0ID0gcXVhbnRpdHkpCmBgYApgYGB7cn0KYmF0bWFuX3BhcnRzICU+JQogICMgQ29tYmluZSB0aGUgc3Rhcl93YXJzX3BhcnRzIHRhYmxlIAogIGZ1bGxfam9pbihzdGFyX3dhcnNfcGFydHMsIGJ5ID0gYygicGFydF9udW0iLCAiY29sb3JfaWQiKSwgc3VmZml4ID0gYygiX2JhdG1hbiIsICJfc3Rhcl93YXJzIikpICU+JQogICMgUmVwbGFjZSBOQXMgd2l0aCAwcyBpbiB0aGUgbl9iYXRtYW4gYW5kIG5fc3Rhcl93YXJzIGNvbHVtbnMgCiAgcmVwbGFjZV9uYShsaXN0KG5fYmF0bWFuID0gMCwgbl9zdGFyX3dhcnMgPSAwKSkKYGBgCiBOb3csIHdlJ3ZlIGNyZWF0ZWQgYSBjb21wcmVoZW5zaXZlIHRhYmxlIHRoYXQgaW5jbHVkZXMgdGhlIHBhcnQgbnVtYmVyLCBjb2xvciBpZCwgYW5kIHF1YW50aXR5IG9mIGVhY2ggcGllY2UgaW4gdGhlIEJhdG1hbiBhbmQgU3RhciBXYXJzIHRoZW1lcyEKCgojIyBDb21wYXJpbmcgQmF0bWFuIGFuZCBTdGFyIFdhcnMgTEVHTyBwYXJ0cwoKVGhlIHRhYmxlIHlvdSBjcmVhdGVkIGluIHRoZSBsYXN0IGV4ZXJjaXNlIGluY2x1ZGVzIHRoZSBwYXJ0IG51bWJlciBvZiBlYWNoIHBpZWNlLCB0aGUgY29sb3IgaWQsIGFuZCB0aGUgbnVtYmVyIG9mIGVhY2ggcGllY2UgaW4gdGhlIFN0YXIgV2FycyBhbmQgQmF0bWFuIHRoZW1lcy4gSG93ZXZlciwgd2UgaGF2ZSBtb3JlIGluZm9ybWF0aW9uIGFib3V0IGVhY2ggb2YgdGhlc2UgcGFydHMgdGhhdCB3ZSBjYW4gZ2FpbiBieSBjb21iaW5pbmcgdGhpcyB0YWJsZSB3aXRoIHNvbWUgb2YgdGhlIGluZm9ybWF0aW9uIHdlIGhhdmUgaW4gb3RoZXIgdGFibGVzLgoKYGBge3J9CnBhcnRzX2pvaW5lZCA8LSBiYXRtYW5fcGFydHMgJT4lCiAgZnVsbF9qb2luKHN0YXJfd2Fyc19wYXJ0cywgYnkgPSBjKCJwYXJ0X251bSIsICJjb2xvcl9pZCIpLCBzdWZmaXggPSBjKCJfYmF0bWFuIiwgIl9zdGFyX3dhcnMiKSkgJT4lCiAgcmVwbGFjZV9uYShsaXN0KG5fYmF0bWFuID0gMCwgbl9zdGFyX3dhcnMgPSAwKSkKYGBgCmBgYHtyfQpwYXJ0c19qb2luZWQgJT4lCiAgIyBTb3J0IHRoZSBudW1iZXIgb2Ygc3RhciB3YXJzIHBpZWNlcyBpbiBkZXNjZW5kaW5nIG9yZGVyIAogIGFycmFuZ2UoZGVzYyhuX3N0YXJfd2FycykpICU+JQogICMgSm9pbiB0aGUgY29sb3JzIHRhYmxlIHRvIHRoZSBwYXJ0c19qb2luZWQgdGFibGUKICBpbm5lcl9qb2luKGNvbG9ycywgYnkgPSBjKCJjb2xvcl9pZCIgPSAiaWQiKSkgJT4lIAogICMgSm9pbiB0aGUgcGFydHMgdGFibGUgdG8gdGhlIHByZXZpb3VzIGpvaW4gCiAgaW5uZXJfam9pbihwYXJ0cywgYnkgPSAicGFydF9udW0iLCBzdWZmaXggPSBjKCJfY29sb3IiLCAiX3BhcnQiKSkgCmBgYApXZSBjYW4gbm93IHNlZSB0aGUgcGllY2VzIHNpZGUgYnkgc2lkZSBmcm9tIGVhY2ggb2YgdGhlc2UgdGhlbWVzLiBTaW5jZSB0aGUgcGllY2VzIGFyZSBzb3J0ZWQgYnkgbnVtYmVyIG9mIFN0YXIgV2FycyBwaWVjZXMgaW4gZGVzY2VuZGluZyBvcmRlciwgd2UgY2FuIHNlZSB0aGF0IHRoZSBtb3N0IGNvbW1vbiBTdGFyIFdhcnMgcGllY2UgaXMgQmxhY2sgYW5kIGhhcyB0aGUgcGFydCBudW1iZXIgMjc4MC4gV2hpbGUgdGhlcmUgYXJlIDM5MiBwaWVjZXMgb2YgdGhpcyBwYXJ0IGluIHRoZSBTdGFyIFdhcnMgdGhlbWUsIHdlIGNhbiBhbHNvIHNlZSBmcm9tIHRoZSB0YWJsZSB0aGF0IHRoZXJlIGFyZSAxMDQgcGllY2VzIG9mIHRoZSBzYW1lIHBhcnQgaW4gdGhlIEJhdG1hbiB0aGVtZS4gCgojIFNvbWV0aGluZyB3aXRoaW4gb25lIHNldCBidXQgbm90IGFub3RoZXIKCldlIHdpbGwgZGV0ZXJtaW5lIHdoaWNoIHBhcnRzIGFyZSBpbiBib3RoIHRoZSBiYXR3aW5nIGFuZCBiYXRtb2JpbGUgc2V0cywgYW5kIHdoaWNoIHNldHMgYXJlIGluIG9uZSwgYnV0IG5vdCB0aGUgb3RoZXIuIFdoaWxlIGFuc3dlcmluZyB0aGVzZSBxdWVzdGlvbnMsIHdlJ2xsIGFsc28gYmUgZGV0ZXJtaW5pbmcgd2hldGhlciBvciBub3QgdGhlIHBhcnRzIHdlJ3JlIGxvb2tpbmcgYXQgaW4gYm90aCBzZXRzIGFsc28gaGF2ZSB0aGUgc2FtZSBjb2xvciBpbiBjb21tb24uCmBgYHtyfQpiYXRtb2JpbGUgPC0gaW52ZW50b3J5X3BhcnRzX2pvaW5lZCAlPiUKICBmaWx0ZXIoc2V0X251bSA9PSAiNzc4NC0xIikgJT4lCiAgc2VsZWN0KC1zZXRfbnVtKQoKYmF0d2luZyA8LSBpbnZlbnRvcnlfcGFydHNfam9pbmVkICU+JQogIGZpbHRlcihzZXRfbnVtID09ICI3MDkxNi0xIikgJT4lCiAgc2VsZWN0KC1zZXRfbnVtKQpgYGAKYGBge3J9CiMgRmlsdGVyIHRoZSBiYXR3aW5nIHNldCBmb3IgcGFydHMgdGhhdCBhcmUgYWxzbyBpbiB0aGUgYmF0bW9iaWxlIHNldApiYXR3aW5nICU+JQogIHNlbWlfam9pbihiYXRtb2JpbGUsIGJ5ID0gYygicGFydF9udW0iKSkKCiMgRmlsdGVyIHRoZSBiYXR3aW5nIHNldCBmb3IgcGFydHMgdGhhdCBhcmVuJ3QgaW4gdGhlIGJhdG1vYmlsZSBzZXQKYmF0d2luZyAlPiUKICBhbnRpX2pvaW4oYmF0bW9iaWxlLCBieSA9IGMoInBhcnRfbnVtIikpCmBgYAogQmFzZWQgb24gdGhlc2Ugam9pbnMsIHdlIG5vdyBrbm93IHRoYXQgdGhlcmUgYXJlIDEyNiBwYXJ0cyBpbiB0aGUgYmF0d2luZyBzZXQgdGhhdCBhcmUgYWxzbyBpbiB0aGUgYmF0bW9iaWxlIHNldCwgYW5kIDE4MyBwYXJ0cyB0aGF0IGFyZSBpbiB0aGUgYmF0d2luZyBzZXQgdGhhdCBhcmVuJ3QgaW4gdGhlIGJhdG1vYmlsZSBzZXQuCgojIyBXaGF0IGNvbG9ycyBhcmUgaW5jbHVkZWQgaW4gYXQgbGVhc3Qgb25lIHNldD8KCkJlc2lkZXMgY29tcGFyaW5nIHR3byBzZXRzIGRpcmVjdGx5LCB5b3UgY291bGQgYWxzbyB1c2UgYSBmaWx0ZXJpbmcgam9pbiBsaWtlIHNlbWlfam9pbiB0byBmaW5kIG91dCB3aGljaCBjb2xvcnMgZXZlciBhcHBlYXIgaW4gYW55IGludmVudG9yeSBwYXJ0LiBTb21lIG9mIHRoZSBjb2xvcnMgY291bGQgYmUgb3B0aW9uYWwsIG1lYW5pbmcgdGhleSBhcmVuJ3QgaW5jbHVkZWQgaW4gYW55IHNldHMuCgpgYGB7cn0KIyBVc2UgaW52ZW50b3J5X3BhcnRzIHRvIGZpbmQgY29sb3JzIGluY2x1ZGVkIGluIGF0IGxlYXN0IG9uZSBzZXQKY29sb3JzICU+JQogIHNlbWlfam9pbihpbnZlbnRvcnlfcGFydHMsIGJ5ID0gYygiaWQiID0gImNvbG9yX2lkIikpCmBgYAojIyBXaGljaCBzZXQgaXMgbWlzc2luZyB2ZXJzaW9uIDE/CgpFYWNoIHNldCBpbmNsdWRlZCBpbiB0aGUgTEVHTyBkYXRhIGhhcyBhbiBhc3NvY2lhdGVkIHZlcnNpb24gbnVtYmVyLiBXZSB3YW50IHRvIHVuZGVyc3RhbmQgdGhlIHZlcnNpb24gd2UgYXJlIGxvb2tpbmcgYXQgdG8gbGVhcm4gbW9yZSBhYm91dCB0aGUgcGFydHMgdGhhdCBhcmUgaW5jbHVkZWQuIEJlZm9yZSBkb2luZyB0aGF0LCB3ZSBzaG91bGQgY29uZmlybSB0aGF0IHRoZXJlIGFyZW4ndCBhbnkgc2V0cyB0aGF0IGFyZSBtaXNzaW5nIGEgcGFydGljdWxhciB2ZXJzaW9uLgpgYGB7cn0KIyBVc2UgZmlsdGVyKCkgdG8gZXh0cmFjdCB2ZXJzaW9uIDEgCnZlcnNpb25fMV9pbnZlbnRvcmllcyA8LSBpbnZlbnRvcmllcyAlPiUKICBmaWx0ZXIodmVyc2lvbiA9PSAxKQoKIyBVc2UgYW50aV9qb2luKCkgdG8gZmluZCB3aGljaCBzZXQgaXMgbWlzc2luZyBhIHZlcnNpb24gMQpzZXRzICU+JQogIGFudGlfam9pbih2ZXJzaW9uXzFfaW52ZW50b3JpZXMsIGJ5ID0gYygic2V0X251bSIpKQpgYGAKIyMgQWdncmVnYXRpbmcgc2V0cyB0byBsb29rIGF0IHRoZWlyIGRpZmZlcmVuY2VzCgpUbyBjb21wYXJlIHR3byBpbmRpdmlkdWFsIHNldHMsIGFuZCB0aGUga2luZHMgb2YgTEVHTyBwaWVjZXMgdGhhdCBjb21wcmlzZSB0aGVtLCB3ZSdsbCBuZWVkIHRvIGFnZ3JlZ2F0ZSB0aGUgZGF0YSBpbnRvIHNlcGFyYXRlIHRoZW1lcy4gQWRkaXRpb25hbGx5LCAgd2UnbGwgd2FudCB0byBhZGQgYSBjb2x1bW4gc28gdGhhdCB3ZSBjYW4gdW5kZXJzdGFuZCB0aGUgcGVyY2VudGFnZXMgb2Ygc3BlY2lmaWMgcGllY2VzIHRoYXQgYXJlIHBhcnQgb2YgZWFjaCBzZXQsIHJhdGhlciB0aGFuIGxvb2tpbmcgYXQgdGhlIG51bWJlcnMgb2YgcGllY2VzIGFsb25lLgoKYGBge3J9CmludmVudG9yeV9wYXJ0c190aGVtZXMgPC0gaW52ZW50b3JpZXMgJT4lCiAgaW5uZXJfam9pbihpbnZlbnRvcnlfcGFydHMsIGJ5ID0gYygiaWQiID0gImludmVudG9yeV9pZCIpKSAlPiUKICBhcnJhbmdlKGRlc2MocXVhbnRpdHkpKSAlPiUKICBzZWxlY3QoLWlkLCAtdmVyc2lvbikgJT4lCiAgaW5uZXJfam9pbihzZXRzLCBieSA9ICJzZXRfbnVtIikgJT4lCiAgaW5uZXJfam9pbih0aGVtZXMsIGJ5ID0gYygidGhlbWVfaWQiID0gImlkIiksIHN1ZmZpeCA9IGMoIl9zZXQiLCAiX3RoZW1lIikpICU+JSAKICByZXBsYWNlX25hKGxpc3QocXVhbnRpdHkgPSAwKSkKYGBgCmBgYHtyfQpiYXRtYW5fY29sb3JzIDwtIGludmVudG9yeV9wYXJ0c190aGVtZXMgJT4lCiAgIyBGaWx0ZXIgdGhlIGludmVudG9yeV9wYXJ0c190aGVtZXMgdGFibGUgZm9yIHRoZSBCYXRtYW4gdGhlbWUKICBmaWx0ZXIobmFtZV90aGVtZSA9PSAiQmF0bWFuIikgJT4lCiAgZ3JvdXBfYnkoY29sb3JfaWQpICU+JQogIHN1bW1hcml6ZSh0b3RhbCA9IHN1bShxdWFudGl0eSkpICU+JQogICMgQWRkIGEgcGVyY2VudCBjb2x1bW4gb2YgdGhlIHRvdGFsIGRpdmlkZWQgYnkgdGhlIHN1bSBvZiB0aGUgdG90YWwgCiAgbXV0YXRlKHBlcmNlbnQgPSB0b3RhbCAvIHN1bSh0b3RhbCkpCgojIEZpbHRlciBhbmQgYWdncmVnYXRlIHRoZSBTdGFyIFdhcnMgc2V0IGRhdGE7IGFkZCBhIHBlcmNlbnQgY29sdW1uCnN0YXJfd2Fyc19jb2xvcnMgPC0gaW52ZW50b3J5X3BhcnRzX3RoZW1lcyAlPiUKICBmaWx0ZXIobmFtZV90aGVtZSA9PSAiU3RhciBXYXJzIikgJT4lCiAgZ3JvdXBfYnkoY29sb3JfaWQpICU+JQogIHN1bW1hcml6ZSh0b3RhbCA9IHN1bShxdWFudGl0eSkpICU+JQogIG11dGF0ZShwZXJjZW50ID0gdG90YWwvIHN1bSh0b3RhbCkpCmBgYAoKYGBge3J9CmJhdG1hbl9jb2xvcnMgJT4lCiAgZnVsbF9qb2luKHN0YXJfd2Fyc19jb2xvcnMsIGJ5ID0gImNvbG9yX2lkIiwgc3VmZml4ID0gYygiX2JhdG1hbiIsICJfc3Rhcl93YXJzIikpICU+JQogIHJlcGxhY2VfbmEobGlzdCh0b3RhbF9iYXRtYW4gPSAwLCB0b3RhbF9zdGFyX3dhcnMgPSAwLCBwZXJjZW50X2JhdG1hbiA9IDAsIHBlcmNlbnRfc3Rhcl93YXJzID0gMCkpICU+JQogIGlubmVyX2pvaW4oY29sb3JzLCBieSA9IGMoImNvbG9yX2lkIiA9ICJpZCIpKSAlPiUKICAjIENyZWF0ZSB0aGUgZGlmZmVyZW5jZSBhbmQgdG90YWwgY29sdW1ucwogIG11dGF0ZShkaWZmZXJlbmNlID0gcGVyY2VudF9iYXRtYW4gLSBwZXJjZW50X3N0YXJfd2FycywKICAgICAgICAgdG90YWwgPSB0b3RhbF9iYXRtYW4gKyB0b3RhbF9zdGFyX3dhcnMpICU+JQogICMgRmlsdGVyIGZvciB0b3RhbHMgZ3JlYXRlciB0aGFuIDIwMAogIGZpbHRlcih0b3RhbD4yMDApCmBgYApXaXRoIHRoaXMgY29tYmluZWQgdGFibGUgdGhhdCBjb250YWlucyBhbGwgdGhlIGluZm9ybWF0aW9uIGZyb20gdGhlIGJhdG1hbl9jb2xvcnMgYW5kIHN0YXJfd2Fyc19jb2xvcnMgdGFibGVzLCB3ZSBjYW4gbm93IGNyZWF0ZSBhbiBpbmZvcm1hdGl2ZSB2aXN1YWxpemF0aW9uIHRvIGNvbXBhcmUgdGhlIGNvbG9ycyBvZiB0aGVzZSBzZXRzLgoKIyMgVmlzdWFsaXppbmcgdGhlIGRpZmZlcmVuY2U6IEJhdG1hbiBhbmQgU3RhciBXYXJzCgpXZSBjcmVhdGVkIGNvbG9yc19qb2luZWQuIE5vdyB3ZSdsbCBjcmVhdGUgYSBiYXIgcGxvdCB3aXRoIG9uZSBiYXIgZm9yIGVhY2ggY29sb3IgKG5hbWUpLCBzaG93aW5nIHRoZSBkaWZmZXJlbmNlIGluIHBlcmNlbnRhZ2VzLgpgYGB7cn0KbGlicmFyeShmb3JjYXRzKQpjb2xvcnNfam9pbmVkIDwtIGJhdG1hbl9jb2xvcnMgJT4lCiAgZnVsbF9qb2luKHN0YXJfd2Fyc19jb2xvcnMsIGJ5ID0gImNvbG9yX2lkIiwgc3VmZml4ID0gYygiX2JhdG1hbiIsICJfc3Rhcl93YXJzIikpICU+JQogIHJlcGxhY2VfbmEobGlzdCh0b3RhbF9iYXRtYW4gPSAwLCB0b3RhbF9zdGFyX3dhcnMgPSAwLCAgcGVyY2VudF9iYXRtYW4gPSAwLCBwZXJjZW50X3N0YXJfd2FycyA9IDApKSAlPiUKICBpbm5lcl9qb2luKGNvbG9ycywgYnkgPSBjKCJjb2xvcl9pZCIgPSAiaWQiKSkgJT4lCiAgbXV0YXRlKGRpZmZlcmVuY2UgPSBwZXJjZW50X2JhdG1hbiAtIHBlcmNlbnRfc3Rhcl93YXJzLAogICAgICAgICB0b3RhbCA9IHRvdGFsX2JhdG1hbiArIHRvdGFsX3N0YXJfd2FycykgJT4lCiAgZmlsdGVyKHRvdGFsID49IDIwMCkgJT4lCiAgbXV0YXRlKG5hbWUgPSBmY3RfcmVvcmRlcihuYW1lLCBkaWZmZXJlbmNlKSkgCmBgYApgYGB7cn0KIyB3ZSBuZWVkIHRvIGFkZCAjIHRvIGNyZWF0ZSBvdXIgcGFsZXR0ZQpjb2xvcnNfam9pbmVkJHJnYiA8LSBwYXN0ZSgiIyIsIGNvbG9yc19qb2luZWQkcmdiLCBzZXA9IiIpCmNvbG9yX3BhbGV0dGUgPC0gc2V0TmFtZXMoY29sb3JzX2pvaW5lZCRyZ2IsIGNvbG9yc19qb2luZWQkbmFtZSkKYGBgCgoKYGBge3J9CmxpYnJhcnkoZ2dwbG90MikKIyBDcmVhdGUgYSBiYXIgcGxvdCB1c2luZyBjb2xvcnNfam9pbmVkIGFuZCB0aGUgbmFtZSBhbmQgZGlmZmVyZW5jZSBjb2x1bW5zCmdncGxvdChjb2xvcnNfam9pbmVkLCBhZXMobmFtZSwgZGlmZmVyZW5jZSwgZmlsbCA9IG5hbWUpKSArCiAgZ2VvbV9jb2woKSArCiAgY29vcmRfZmxpcCgpICsKICBzY2FsZV9maWxsX21hbnVhbCh2YWx1ZXMgPSBjb2xvcl9wYWxldHRlLCBndWlkZSA9IEZBTFNFKSArCiAgbGFicyh5ID0gIkRpZmZlcmVuY2U6IEJhdG1hbiAtIFN0YXIgV2FycyIpCmBgYApBcyB5b3UgY2FuIHNlZSBmcm9tIHRoZSBwbG90LCB0aGUgQmF0bWFuIHNldCBoYXMgbW9yZSBibGFjaywgeWVsbG93LCBhbmQgcmVkLCB3aGlsZSB0aGUgU3RhciBXYXJzIHNldCBoYXMgbW9yZSBsaWdodCBibHVpc2ggZ3JheSwgd2hpdGUsIGFuZCB0YW4uCgoKCgoKCg==