Common Data Problems

Data types

Converting data types

We’ll be working with San Francisco bike share ride data called bike_share_rides. It contains information on start and end stations of each trip, the trip duration, and some user information.

Before beginning to analyze any dataset, it’s important to take a look at the different types of columns you’ll be working with, which you can do using glimpse().

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(assertive)
package 㤼㸱assertive㤼㸲 was built under R version 3.6.3
library(stringr)
library(ggplot2)
package 㤼㸱ggplot2㤼㸲 was built under R version 3.6.3
bike_share_rides = readRDS("bike_share_rides_ch1_1.rds")
# Glimpse at bike_share_rides
glimpse(bike_share_rides)
Observations: 35,229
Variables: 10
$ ride_id         <int> 52797, 54540, 87695, 45619, 70832, 96135, 29928, 83331, 72424, 25910, 89090, 2443...
$ date            <chr> "2017-04-15", "2017-04-19", "2017-04-14", "2017-04-03", "2017-04-10", "2017-04-18...
$ duration        <chr> "1316.15 minutes", "8.13 minutes", "24.85 minutes", "6.35 minutes", "9.8 minutes"...
$ station_A_id    <dbl> 67, 21, 16, 58, 16, 6, 5, 16, 5, 81, 30, 16, 16, 67, 21, 16, 5, 21, 67, 5, 21, 15...
$ station_A_name  <chr> "San Francisco Caltrain Station 2  (Townsend St at 4th St)", "Montgomery St BART ...
$ station_B_id    <dbl> 89, 64, 355, 368, 81, 66, 350, 91, 62, 81, 109, 10, 80, 90, 27, 50, 323, 3, 321, ...
$ station_B_name  <chr> "Division St at Potrero Ave", "5th St at Brannan St", "23rd St at Tennessee St", ...
$ bike_id         <dbl> 1974, 860, 2263, 1417, 507, 75, 388, 239, 1449, 3289, 2084, 1526, 3473, 192, 1425...
$ user_gender     <chr> "Male", "Male", "Male", "Male", "Male", "Male", "Male", "Male", "Male", "Male", "...
$ user_birth_year <dbl> 1972, 1986, 1993, 1981, 1981, 1988, 1993, 1996, 1993, 1996, 1974, 1995, 1993, 199...
# Summary of user_birth_year
summary(bike_share_rides$user_birth_year)
   Min. 1st Qu.  Median    Mean 3rd Qu.    Max. 
   1900    1979    1986    1984    1991    2001 
# Convert user_birth_year to factor: user_birth_year_fct
bike_share_rides <- bike_share_rides %>%
  mutate(user_birth_year_fct = as.factor(user_birth_year))

# Assert user_birth_year_fct is a factor
assert_is_factor(bike_share_rides$user_birth_year_fct)

# Summary of user_birth_year_fct
summary(bike_share_rides$user_birth_year_fct)
1900 1902 1923 1931 1938 1939 1941 1942 1943 1945 1946 1947 1948 1949 1950 1951 1952 1953 1954 1955 1956 1957 
   1    7    2   23    2    1    3   10    4   16    5   24    9   30   37   25   70   49   65   66  112   62 
1958 1959 1960 1961 1962 1963 1964 1965 1966 1967 1968 1969 1970 1971 1972 1973 1974 1975 1976 1977 1978 1979 
 156   99  196  161  256  237  245  349  225  363  365  331  370  548  529  527  563  601  481  541  775  876 
1980 1981 1982 1983 1984 1985 1986 1987 1988 1989 1990 1991 1992 1993 1994 1995 1996 1997 1998 1999 2000 2001 
 825 1016 1056 1262 1157 1318 1606 1672 2135 1872 2062 1582 1703 1498 1476 1185  813  358  365  348  473   30 

Looking at the new summary statistics, more riders were born in 1988 than any other year.

Trimming strings

Another common dirty data problem is having extra bits like percent signs or periods in numbers, causing them to be read in as characters. In order to be able to crunch these numbers, the extra bits need to be removed and the numbers need to be converted from character to numeric.

bike_share_rides <- bike_share_rides %>%
  # Remove 'minutes' from duration: duration_trimmed
  mutate(duration_trimmed = str_remove(duration, "minutes"),
         # Convert duration_trimmed to numeric: duration_mins
         duration_mins = as.numeric(duration_trimmed))

# Glimpse at bike_share_rides
glimpse(bike_share_rides)
Observations: 35,229
Variables: 13
$ ride_id             <int> 52797, 54540, 87695, 45619, 70832, 96135, 29928, 83331, 72424, 25910, 89090, ...
$ date                <chr> "2017-04-15", "2017-04-19", "2017-04-14", "2017-04-03", "2017-04-10", "2017-0...
$ duration            <chr> "1316.15 minutes", "8.13 minutes", "24.85 minutes", "6.35 minutes", "9.8 minu...
$ station_A_id        <dbl> 67, 21, 16, 58, 16, 6, 5, 16, 5, 81, 30, 16, 16, 67, 21, 16, 5, 21, 67, 5, 21...
$ station_A_name      <chr> "San Francisco Caltrain Station 2  (Townsend St at 4th St)", "Montgomery St B...
$ station_B_id        <dbl> 89, 64, 355, 368, 81, 66, 350, 91, 62, 81, 109, 10, 80, 90, 27, 50, 323, 3, 3...
$ station_B_name      <chr> "Division St at Potrero Ave", "5th St at Brannan St", "23rd St at Tennessee S...
$ bike_id             <dbl> 1974, 860, 2263, 1417, 507, 75, 388, 239, 1449, 3289, 2084, 1526, 3473, 192, ...
$ user_gender         <chr> "Male", "Male", "Male", "Male", "Male", "Male", "Male", "Male", "Male", "Male...
$ user_birth_year     <dbl> 1972, 1986, 1993, 1981, 1981, 1988, 1993, 1996, 1993, 1996, 1974, 1995, 1993,...
$ user_birth_year_fct <fct> 1972, 1986, 1993, 1981, 1981, 1988, 1993, 1996, 1993, 1996, 1974, 1995, 1993,...
$ duration_trimmed    <chr> "1316.15 ", "8.13 ", "24.85 ", "6.35 ", "9.8 ", "17.47 ", "16.52 ", "14.72 ",...
$ duration_mins       <dbl> 1316.15, 8.13, 24.85, 6.35, 9.80, 17.47, 16.52, 14.72, 4.12, 25.77, 17.73, 15...
# Assert duration_mins is numeric
assert_is_numeric(bike_share_rides$duration_mins)

# Calculate mean duration
mean(bike_share_rides$duration_mins)
[1] 13.06214

By removing characters and converting to a numeric type, you were able to figure out that the average ride duration is about 13 minutes - not bad for a city like San Francisco!

Range constraints

Values that are out of range can throw off an analysis, so it’s important to catch them early on.

Bikes are not allowed to be kept out for more than 24 hours, or 1440 minutes at a time, but issues with some of the bikes caused inaccurate recording of the time they were returned.

# Create breaks
breaks <- c(min(bike_share_rides$duration_mins), 0, 1440, max(bike_share_rides$duration_mins))

# Create a histogram of duration_min
ggplot(bike_share_rides, aes(duration_mins)) +
  geom_histogram(breaks = breaks)

# duration_min_const: replace vals of duration_min > 1440 with 1440
bike_share_rides <- bike_share_rides %>%
  mutate(duration_min_const = replace(duration_mins, duration_mins > 1440, 1440))

# Make sure all values of duration_min_const are between 0 and 1440
assert_all_are_in_closed_range(bike_share_rides$duration_min_const, lower = 0, upper = 1440)

The method of replacing erroneous data with the range limit works well, but you could just as easily replace these values with NAs or something else instead.

Future dates

Something has gone wrong and it looks like you have data with dates from the future, which is way outside of the date range you expected to be working with. To fix this, we’ll need to remove any rides from the dataset that have a date in the future. Before you can do this, the date column needs to be converted from a character to a Date. Having these as Date objects will make it much easier to figure out which rides are from the future, since R makes it easy to check if one Date object is before (<) or after (>) another.

library(lubridate)

Attaching package: 㤼㸱lubridate㤼㸲

The following object is masked from 㤼㸱package:base㤼㸲:

    date
# Convert date to Date type
bike_share_rides <- bike_share_rides %>%
  mutate(date = as.Date(date))

# Make sure all dates are in the past
assert_all_are_in_past(bike_share_rides$date)

# Filter for rides that occurred before or on today's date
bike_share_rides_past <- bike_share_rides %>%
  filter(date <= today())

# Make sure all dates from bike_share_rides_past are in the past
assert_all_are_in_past(bike_share_rides_past$date)

Handling data from the future like this is much easier than trying to verify the data’s correctness by time traveling.

Uniqueness constraints

Full duplicates

When multiple rows of a data frame share the same values for all columns, they’re full duplicates of each other. Removing duplicates like this is important, since having the same value repeated multiple times can alter summary statistics like the mean and median.

Each ride, including its ride_id should be unique.

# Count the number of full duplicates
sum(duplicated(bike_share_rides))
[1] 0
# Remove duplicates
bike_share_rides_unique <- distinct(bike_share_rides)
  

# Count the full duplicates in bike_share_rides_unique
sum(duplicated(bike_share_rides_unique))
[1] 0

Removing full duplicates will ensure that summary statistics aren’t altered by repeated data points.

Partial duplicates

Partial duplicates are a bit tricker to deal with than full duplicates. We’ll first identify any partial duplicates and then practice the most common technique to deal with them, which involves dropping all partial duplicates, keeping only the first.

# Find duplicated ride_ids
bike_share_rides %>% 
  # Count the number of occurrences of each ride_id
  count(ride_id) %>% 
  # Filter for rows with a count > 1
  filter(n>1)
# Remove full and partial duplicates
bike_share_rides_unique <- bike_share_rides %>%
  # Only based on ride_id instead of all cols
  distinct(ride_id, .keep_all = TRUE)
# Find duplicated ride_ids in bike_share_rides_unique
bike_share_rides_unique %>%
  # Count the number of occurrences of each ride_id
  count(ride_id) %>%
  # Filter for rows with a count > 1
  filter(n > 1)

It’s important to consider the data you’re working with before removing partial duplicates, since sometimes it’s expected that there will be partial duplicates in a dataset, such as if the same customer makes multiple purchases.

Aggregating partial duplicates

Another way of handling partial duplicates is to compute a summary statistic of the values that differ between partial duplicates, such as mean, median, maximum, or minimum. This can come in handy when you’re not sure how your data was collected and want an average, or if based on domain knowledge, you’d rather have too high of an estimate than too low of an estimate (or vice versa).

bike_share_rides %>%
  # Group by ride_id and date
  group_by(ride_id, date) %>%
  # Add duration_min_avg column
  mutate(duration_min_avg = mean(duration_mins)) %>%
  # Remove duplicates based on ride_id and date, keep all cols
  distinct(ride_id, date, .keep_all = TRUE) %>%
  # Remove duration_min column
  select(-duration_mins)

Aggregation of partial duplicates allows you to keep some information about all data points instead of keeping information about just one data point.

Categorical and text data

We’ll be working with a dataset called sfo_survey, containing survey responses from passengers taking flights from San Francisco International Airport (SFO). Participants were asked questions about the airport’s cleanliness, wait times, safety, and their overall satisfaction.

Membership constraints

Not a member

There were a few issues during data collection that resulted in some inconsistencies in the dataset. We’ll be working with the dest_size column, which categorizes the size of the destination airport that the passengers were flying to.

sfo_survey = readRDS("sfo_survey_ch2_1.rds")
dest_sizes = data.frame(dest_size = c("Small", "Medium", "Large", "Hub"),
                        passengers_per_day = c("0-20K", "20K-70K", "70K-100K", "100K+"))
head(sfo_survey)
head(dest_sizes)
# Count the number of occurrences of dest_size
sfo_survey %>%
  count(dest_size)
# Find bad dest_size rows
sfo_survey %>% 
  # Join with dest_sizes data frame to get bad dest_size rows
  anti_join(dest_sizes, by = "dest_size") %>%
  # Select id, airline, destination, and dest_size cols
  select(id, airline, destination, dest_size)
Column `dest_size` joining character vector and factor, coercing into character vector
# Remove bad dest_size rows
sfo_survey %>% 
  # Join with dest_sizes
  semi_join(dest_sizes, by = "dest_size") %>%
  # Count the number of each dest_size
  count(dest_size)
Column `dest_size` joining character vector and factor, coercing into character vector

Anti-joins can help you identify the rows that are causing issues, and semi-joins can remove the issue-causing rows. In the next lesson, you’ll learn about other ways to deal with bad values so that you don’t have to lose rows of data.

Categorical data problems

Identifying inconsistency

We’ll continue working with the sfo_survey dataset. We’ll examine the dest_size column again as well as the cleanliness column and determine what kind of issues, if any, these two categorical variables face.

# Count dest_size
sfo_survey %>%
  count(dest_size)
# Count cleanliness
sfo_survey %>%
  count(cleanliness)

Correcting inconsistency

Now that we’ve identified that dest_size has whitespace inconsistencies and cleanliness has capitalization inconsistencies, we’ll use the new tools at your disposal to fix the inconsistent values in sfo_survey instead of removing the data points entirely, which could add bias to your dataset if more than 5% of the data points need to be dropped.

# Add new columns to sfo_survey
sfo_survey <- sfo_survey %>%
  # dest_size_trimmed: dest_size without whitespace
  mutate(dest_size_trimmed = str_trim(dest_size),
         # cleanliness_lower: cleanliness converted to lowercase
         cleanliness_lower = str_to_lower(cleanliness))

# Count values of dest_size_trimmed
sfo_survey %>%
  count(dest_size_trimmed)

# Count values of cleanliness_lower
sfo_survey %>%
  count(cleanliness_lower)

We were able to convert seven-category data into four-category data, which will help your analysis go more smoothly.

Collapsing categories

One of the tablets that participants filled out the sfo_survey on was not properly configured, allowing the response for dest_region to be free text instead of a dropdown menu. This resulted in some inconsistencies in the dest_region variable that we’ll need to correct.

library(forcats)
# Count categories of dest_region
sfo_survey %>%
  count(dest_region)
# Count categories of dest_region
sfo_survey %>%
  count(dest_region)

# Categories to map to Europe
europe_categories <- c("Europ", "EU", "eur")

# Add a new col dest_region_collapsed
sfo_survey %>%
  # Map all categories in europe_categories to Europe
  mutate(dest_region_collapsed = fct_collapse(dest_region, 
                                     Europe = europe_categories)) %>%
  # Count categories of dest_region_collapsed
  count(dest_region_collapsed)
Unknown levels in `f`: Europ, EU, eur

You’ve reduced the number of categories from 12 to 9, and you can now be confident that 401 of the survey participants were heading to Europe.

Cleaning text data

Detecting inconsistent text data

You’ve recently received some news that the customer support team wants to ask the SFO survey participants some follow-up questions. However, the auto-dialer that the call center uses isn’t able to parse all of the phone numbers since they’re all in different formats. After some investigation, you found that some phone numbers are written with hyphens (-) and some are written with parentheses ((,)). We’ll figure out which phone numbers have these issues so that you know which ones need fixing.

customer_accounts <- readRDS("fodors.rds")
head(customer_accounts)
# Filter for rows with "-" in the phone column
customer_accounts %>%
  filter(str_detect(phone, "-"))
# Filter for rows with "(" or ")" in the phone column
customer_accounts %>%
  filter(str_detect(phone, fixed("(")) | str_detect(phone, fixed(")")))

Now that you’ve identified the inconsistencies in the phone column, it’s time to remove unnecessary characters to make the follow-up survey go as smoothly as possible.

Replacing and removing

The customer support team has requested that all phone numbers be in the format “123 456 7890”. In this exercise, you’ll use your new stringr skills to fulfill this request.

# Remove parentheses from phone column
phone_no_parens <- customer_accounts$phone %>%
  # Remove "("s
  str_remove_all(fixed("(")) %>%
  # Remove ")"s
  str_remove_all(fixed(")"))
# Add phone_no_parens as column
customer_accounts %>%
  mutate(phone_no_parens = phone_no_parens)
# Add phone_no_parens as column
customer_accounts %>%
  mutate(phone_no_parens = phone_no_parens,
  # Replace all hyphens in phone_no_parens with spaces
         phone_clean = str_replace_all(phone_no_parens, "-", " "))

Invalid phone numbers

We’ll remove any rows with invalid phone numbers.

# Check out the invalid numbers
customer_accounts %>%
  filter(str_length(phone) != 12)

# Remove rows with invalid numbers
customer_accounts %>%
  filter (str_length(phone) == 12)

Advanced Data Problems

Uniformity

Date uniformity

you work at an asset management company and you’ll be working with the accounts dataset, which contains information about each customer, the amount in their account, and the date their account was opened. Your boss has asked you to calculate some summary statistics about the average value of each account and whether the age of the account is associated with a higher or lower account value. Before you can do this, you need to make sure that the accounts dataset you’ve been given doesn’t contain any uniformity problems.

We’ll investigate the date_opened column and clean it up so that all the dates are in the same format.

accounts <- readRDS("ch3_1_accounts.rds")
head(accounts)
as.Date(accounts$date_opened)
 [1] "2003-10-19" NA           "2008-07-29" "2005-06-09" "2012-03-31" "2007-06-20" NA           "2019-06-03"
 [9] "2011-05-07" "2018-04-07" "2018-11-16" "2001-04-16" "2005-04-21" "2006-06-13" "2009-01-07" "2012-07-07"
[17] NA           NA           "2004-05-21" "2001-09-06" "2005-04-09" "2009-10-20" "2003-05-16" "2015-10-25"
[25] NA           NA           NA           "2008-12-27" "2015-11-11" "2009-02-26" "2008-12-26" NA          
[33] NA           "2005-12-13" NA           "2004-12-03" "2016-10-19" NA           "2009-10-05" "2013-07-11"
[41] "2002-03-24" "2015-10-17" NA           NA           "2019-11-12" NA           NA           "2019-10-01"
[49] "2000-08-17" "2001-04-11" NA           "2016-06-30" NA           NA           "2013-05-23" "2017-02-24"
[57] NA           "2004-11-02" "2019-03-06" "2018-09-01" NA           "2002-12-31" "2013-07-27" "2014-01-10"
[65] "2011-12-14" NA           "2008-03-01" "2018-05-07" "2017-11-23" NA           "2008-09-27" NA          
[73] "2008-01-07" NA           "2005-05-11" "2003-08-12" NA           NA           NA           "2014-11-25"
[81] NA           NA           NA           "2008-04-01" NA           "2002-10-01" "2011-03-25" "2000-07-11"
[89] "2014-10-19" NA           "2013-06-20" "2008-01-16" "2016-06-24" NA           NA           "2007-04-29"
[97] NA           NA          

Notice we have a lot of NAs. By default, as.Date() can’t convert “Month DD, YYYY” formats.

We can use parse_date_time() instead:

# Define the date formats
formats <- c("%Y-%m-%d", "%B %d, %Y")

# Convert dates to the same format
accounts %>%
  mutate(date_opened_clean = parse_date_time(date_opened, formats))

Currency uniformity

We’ll need to correct any unit differences. When we first plot the data, we’ll notice that there’s a group of very high values, and a group of relatively lower values. The bank has two different offices - one in New York, and one in Tokyo, so you suspect that the accounts managed by the Tokyo office are in Japanese yen instead of U.S. dollars. Luckily, we have a data frame called account_offices that indicates which office manages each customer’s account, so you can use this information to figure out which totals need to be converted from yen to dollars.

# Scatter plot of opening date vs total amount
accounts %>%
  ggplot(aes(x = date_opened, y = total)) +
  geom_point()

The formula to convert yen to dollars is USD = JPY / 104.

account_offices <- read.csv("account_offices.csv",stringsAsFactors=FALSE)
account_offices <- account_offices %>% 
  mutate(office = str_trim(office))
head(account_offices)
# Left join accounts and account_offices by id
accounts %>%
  left_join(account_offices, by = "id")
Column `id` joining factor and character vector, coercing into character vector
# Left join accounts to account_offices by id
accounts %>%
  left_join(account_offices, by = "id") %>%
  # Convert totals from the Tokyo office to JPY
  mutate(total_usd = ifelse(office == "Tokyo", total/104, total))
Column `id` joining factor and character vector, coercing into character vector
# Left join accounts to account_offices by id
accounts %>%
  left_join(account_offices, by = "id") %>%
  # Convert totals from the Tokyo office to JPY
  mutate(total_usd = ifelse(office == "Tokyo", total / 104, total)) %>%
  # Scatter plot of opening date vs total_usd
  ggplot(aes(x = date_opened, y = total_usd)) +
    geom_point()
Column `id` joining factor and character vector, coercing into character vector

The points in your last scatter plot all fall within a much smaller range now and you’ll be able to accurately assess the differences between accounts from different countries.

Cross field validation

Validating totals

If we have 3 fund columns, they should sum the same as the total column:

# Find invalid totals
accounts %>%
  # theoretical_total: sum of the three funds
  mutate(theoretical_total = fund_A + fund_B + fund_C) %>%
  # Find accounts where total doesn't match theoretical_total
  filter(total != theoretical_total)

By using cross field validation, you’ve been able to detect values that don’t make sense. How you choose to handle these values will depend on the dataset.

Validating age

We’ll need to validate the age of each account and see if rows with inconsistent acct_ages are the same ones that had inconsistent totals

# Find invalid acct_age
accounts %>%
  # theoretical_age: age of acct based on date_opened
  mutate(theoretical_age = floor(as.numeric(date_opened %--% today(),
"years"))) %>%
  # Filter for rows where acct_age is different from theoretical_age
  filter(theoretical_age != acct_age)
  

There are three accounts that all have ages off by one year, but none of them are the same as the accounts that had total inconsistencies, so it looks like these two bookkeeping errors may not be related.

Completeness

Three flavors of missing data: missing completely at random (MCAR), missing at random (MAR), and missing not at random (MNAR).

  • MCAR: No systematic relationship between missing data and other values
  • MAR: Systematic relationship between missind data and other observed valyus
  • MNAR: Systematic relationship between missing data and unobserved values

Visualizing missing data

Dealing with missing data is one of the most common tasks in data science. There are a variety of types of missingness, as well as a variety of types of solutions to missing data.

library(visdat)
package 㤼㸱visdat㤼㸲 was built under R version 3.6.3
vis_miss(accounts)

accounts %>%
  # missing_inv: Is inv_amount missing?
  mutate(missing_inv = is.na(inv_amount)) %>%
  # Group by missing_inv
  group_by(missing_inv) %>%
  # Calculate mean age for each missing_inv group
  summarize(avg_age = mean(age))
missing

missing

# Sort by age and visualize missing vals
accounts %>%
  arrange(age) %>%
  vis_miss()

Investigating summary statistics based on missingness is a great way to determine if data is missing completely at random or missing at random.

Treating missing data

Removing missing values:

# Create accounts_clean
accounts_clean <- accounts %>%
  # Filter to remove rows with missing cust_id
  filter(!is.na(cust_id))

Replacing missing values:

# Create accounts_clean
accounts_clean <- accounts %>%
  # Filter to remove rows with missing cust_id
  filter(!is.na(cust_id)) %>%
  # Add new col acct_amount_filled with replaced NAs
  mutate(acct_amount_filled = ifelse(is.na(acct_amount), 5 * inv_amount, acct_amount))

Assert that there are no missing values:

# Assert that cust_id has no missing vals
assert_all_are_not_na(accounts_clean$cust_id)

# Assert that acct_amount_filled has no missing vals
assert_all_are_not_na(accounts_clean$acct_amount_filled)

Record Linkage

Comparing strings

Damerau-Levenshtein distance is the minimum number of steps needed to get from String A to String B, using these operations:

  • Insertion of a new character.
  • Deletion of an existing character.
  • Substitution of an existing character.
  • Transposition of two existing consecutive characters.

Small distance, small difference

There are multiple ways to calculate how similar or different two strings are. Now we’ll practice using the stringdist package to compute string distances using various methods. It’s important to be familiar with different methods, as some methods work better on certain datasets, while others work better on other datasets.

library(stringdist)
package 㤼㸱stringdist㤼㸲 was built under R version 3.6.3

Damerau-Levenshtein distance:

# Calculate Damerau-Levenshtein distance
stringdist("las angelos", "los angeles", method = "dl")
[1] 2

Longest Common Substring (LCS):

# Calculate LCS distance
stringdist("las angelos", "los angeles", method = "lcs")
[1] 4

LCS distance only uses insertion and deletion, so it takes more operations to change a string to another.

Jaccard distance:

# Calculate Jaccard distance
stringdist("las angelos", "los angeles", method = "jaccard")
[1] 0

Fixing typos with string distance

One of the datasets you’ll be working with, zagat, is a set of restaurants in New York, Los Angeles, Atlanta, San Francisco, and Las Vegas. The data is from Zagat, a company that collects restaurant reviews, and includes the restaurant names, addresses, phone numbers, as well as other restaurant information.

The city column contains the name of the city that the restaurant is located in. However, there are a number of typos throughout the column. Your task is to map each city to one of the five correctly-spelled cities contained in the cities data frame.

zagat <- readRDS("zagat.rds")
cities <- data.frame(city_actual = c("new york", "los angeles", "atlanta", "san francisco", "las vegas"))
# Count the number of each city variation
zagat %>%
  count(city)
library(fuzzyjoin)
package 㤼㸱fuzzyjoin㤼㸲 was built under R version 3.6.3
# Join zagat and cities and look at results
zagat %>%
  # Left join based on stringdist using city and city_actual cols
  stringdist_left_join(cities, by = c("city" = "city_actual")) %>%
  # Select the name, city, and city_actual cols
  select(name, city, city_actual)

Now that you’ve created consistent spelling for each city, it will be much easier to compute summary statistics by city.

Generating and comparing pairs

Pair blocking

Zagat and Fodor’s are both companies that gather restaurant reviews. The zagat and fodors datasets both contain information about various restaurants, including addresses, phone numbers, and cuisine types. Some restaurants appear in both datasets, but don’t necessarily have the same exact name or phone number written down. We’ll work towards figuring out which restaurants appear in both datasets.

The first step towards this goal is to generate pairs of records so that you can compare them. We’ll first generate all possible pairs, and then use your newly-cleaned city column as a blocking variable.

fodors <- readRDS("fodors.rds")
# Load reclin
library(reclin)
package 㤼㸱reclin㤼㸲 was built under R version 3.6.3Loading required package: lvec
package 㤼㸱lvec㤼㸲 was built under R version 3.6.3
Attaching package: 㤼㸱lvec㤼㸲

The following object is masked from 㤼㸱package:base㤼㸲:

    order

Loading required package: ldat
package 㤼㸱ldat㤼㸲 was built under R version 3.6.3Loading required package: Rcpp

Attaching package: 㤼㸱ldat㤼㸲

The following objects are masked from 㤼㸱package:base㤼㸲:

    append, match, table, which


Attaching package: 㤼㸱reclin㤼㸲

The following object is masked from 㤼㸱package:base㤼㸲:

    identical
# Generate all possible pairs
pair_blocking(zagat, fodors)
Simple blocking
  No blocking used.
  First data set:  310 records
  Second data set: 533 records
  Total number of pairs: 165 230 pairs

ldat with 165 230 rows and 2 columns
# Generate all possible pairs
pair_blocking(zagat, fodors, blocking_var = "city")
Column `city` joining factors with different levels, coercing to character vector
Simple blocking
  Blocking variable(s): city
  First data set:  310 records
  Second data set: 533 records
  Total number of pairs: 40 532 pairs

ldat with 40 532 rows and 2 columns

By using city as a blocking variable, you were able to reduce the number of pairs you’ll need to compare from 165,230 pairs to 40,532.

Comparing pairs

Now that we’ve generated the pairs of restaurants, it’s time to compare them. We can easily customize how we perform our comparisons using the by and default_comparator arguments. There’s no right answer as to what each should be set to, so we’ll try a couple options out.

# Generate pairs
pair_blocking(zagat, fodors, blocking_var = "city") %>%
  # Compare pairs by name using lcs()
  compare_pairs(by = "name",
      default_comparator = lcs())
Column `city` joining factors with different levels, coercing to character vector
Compare
  By: name

Simple blocking
  Blocking variable(s): city
  First data set:  310 records
  Second data set: 533 records
  Total number of pairs: 40 532 pairs

ldat with 40 532 rows and 3 columns
# Generate pairs
pair_blocking(zagat, fodors, blocking_var = "city") %>%
  # Compare pairs by name, phone, addr
  compare_pairs(by = c("name", "phone", "addr"),
      default_comparator = jaro_winkler())
Column `city` joining factors with different levels, coercing to character vector
Compare
  By: name, phone, addr

Simple blocking
  Blocking variable(s): city
  First data set:  310 records
  Second data set: 533 records
  Total number of pairs: 40 532 pairs

ldat with 40 532 rows and 5 columns

Choosing a comparator and the columns to compare is highly dataset-dependent, so it’s best to try out different combinations to see which works best on the dataset you’re working with.

Scoring and linking

Record linkage requires a number of steps that can be difficult to keep straight.

  • Clean the datasets
  • Generate pairs of records
  • Compare separate columns of each pair
  • Score pairs using summing or probability
  • Select pairs that are matches based on their score
  • Link the datasets together

We’ve cleaned up the city column of zagat using string similarity, as well as generated and compared pairs of restaurants from zagat and fodors. The end is near - all that’s left to do is score and select pairs and link the data together, and we’ll be able to begin your analysis in no time!

# Create pairs
pair_blocking(zagat, fodors, blocking_var = "city") %>%
  # Compare pairs
  compare_pairs(by = "name", default_comparator = jaro_winkler()) %>%
  # Score pairs
  score_problink()
Column `city` joining factors with different levels, coercing to character vector
Compare
  By: name

Simple blocking
  Blocking variable(s): city
  First data set:  310 records
  Second data set: 533 records
  Total number of pairs: 40 532 pairs

ldat with 40 532 rows and 4 columns
# Create pairs
pair_blocking(zagat, fodors, blocking_var = "city") %>%
  # Compare pairs
  compare_pairs(by = "name", default_comparator = jaro_winkler()) %>%
  # Score pairs
  score_problink() %>%
  # Select pairs
  select_n_to_m()
Column `city` joining factors with different levels, coercing to character vector
Compare
  By: name

Simple blocking
  Blocking variable(s): city
  First data set:  310 records
  Second data set: 533 records
  Total number of pairs: 40 532 pairs

ldat with 40 532 rows and 5 columns
# Create pairs
pair_blocking(zagat, fodors, blocking_var = "city") %>%
  # Compare pairs
  compare_pairs(by = "name", default_comparator = jaro_winkler()) %>%
  # Score pairs
  score_problink() %>%
  # Select pairs
  select_n_to_m() %>%
  # Link data 
  link()
Column `city` joining factors with different levels, coercing to character vector

Now that your two datasets are merged, you can use the data to figure out if there are certain characteristics that make a restaurant more likely to be reviewed by Zagat or Fodor’s.

LS0tDQp0aXRsZTogIkRhdGEgQ2xlYW5pbmcgaW4gUiINCm91dHB1dDoNCiAgaHRtbF9ub3RlYm9vazoNCiAgICB0b2M6IHRydWUNCiAgICB0b2NfZmxvYXQ6IHRydWUNCiAgICB0b2NfY29sbGFwc2VkOiBmYWxzZQ0KICAgIA0KdG9jX2RlcHRoOiAzDQotLS0NCiMgQ29tbW9uIERhdGEgUHJvYmxlbXMNCg0KIyMgRGF0YSB0eXBlcw0KDQojIyMgQ29udmVydGluZyBkYXRhIHR5cGVzDQoNCldlJ2xsIGJlIHdvcmtpbmcgd2l0aCBTYW4gRnJhbmNpc2NvIGJpa2Ugc2hhcmUgcmlkZSBkYXRhIGNhbGxlZCBiaWtlX3NoYXJlX3JpZGVzLiBJdCBjb250YWlucyBpbmZvcm1hdGlvbiBvbiBzdGFydCBhbmQgZW5kIHN0YXRpb25zIG9mIGVhY2ggdHJpcCwgdGhlIHRyaXAgZHVyYXRpb24sIGFuZCBzb21lIHVzZXIgaW5mb3JtYXRpb24uDQoNCkJlZm9yZSBiZWdpbm5pbmcgdG8gYW5hbHl6ZSBhbnkgZGF0YXNldCwgaXQncyBpbXBvcnRhbnQgdG8gdGFrZSBhIGxvb2sgYXQgdGhlIGRpZmZlcmVudCB0eXBlcyBvZiBjb2x1bW5zIHlvdSdsbCBiZSB3b3JraW5nIHdpdGgsIHdoaWNoIHlvdSBjYW4gZG8gdXNpbmcgZ2xpbXBzZSgpLg0KDQpgYGB7cn0NCmxpYnJhcnkoZHBseXIpDQpsaWJyYXJ5KGFzc2VydGl2ZSkNCmxpYnJhcnkoc3RyaW5ncikNCmxpYnJhcnkoZ2dwbG90MikNCmBgYA0KYGBge3J9DQpiaWtlX3NoYXJlX3JpZGVzID0gcmVhZFJEUygiYmlrZV9zaGFyZV9yaWRlc19jaDFfMS5yZHMiKQ0KYGBgDQoNCg0KDQpgYGB7cn0NCiMgR2xpbXBzZSBhdCBiaWtlX3NoYXJlX3JpZGVzDQpnbGltcHNlKGJpa2Vfc2hhcmVfcmlkZXMpDQoNCiMgU3VtbWFyeSBvZiB1c2VyX2JpcnRoX3llYXINCnN1bW1hcnkoYmlrZV9zaGFyZV9yaWRlcyR1c2VyX2JpcnRoX3llYXIpDQpgYGANCmBgYHtyfQ0KIyBDb252ZXJ0IHVzZXJfYmlydGhfeWVhciB0byBmYWN0b3I6IHVzZXJfYmlydGhfeWVhcl9mY3QNCmJpa2Vfc2hhcmVfcmlkZXMgPC0gYmlrZV9zaGFyZV9yaWRlcyAlPiUNCiAgbXV0YXRlKHVzZXJfYmlydGhfeWVhcl9mY3QgPSBhcy5mYWN0b3IodXNlcl9iaXJ0aF95ZWFyKSkNCg0KIyBBc3NlcnQgdXNlcl9iaXJ0aF95ZWFyX2ZjdCBpcyBhIGZhY3Rvcg0KYXNzZXJ0X2lzX2ZhY3RvcihiaWtlX3NoYXJlX3JpZGVzJHVzZXJfYmlydGhfeWVhcl9mY3QpDQoNCiMgU3VtbWFyeSBvZiB1c2VyX2JpcnRoX3llYXJfZmN0DQpzdW1tYXJ5KGJpa2Vfc2hhcmVfcmlkZXMkdXNlcl9iaXJ0aF95ZWFyX2ZjdCkNCmBgYA0KIExvb2tpbmcgYXQgdGhlIG5ldyBzdW1tYXJ5IHN0YXRpc3RpY3MsIG1vcmUgcmlkZXJzIHdlcmUgYm9ybiBpbiAxOTg4IHRoYW4gYW55IG90aGVyIHllYXIuDQoNCiMjIyBUcmltbWluZyBzdHJpbmdzDQoNCkFub3RoZXIgY29tbW9uIGRpcnR5IGRhdGEgcHJvYmxlbSBpcyBoYXZpbmcgZXh0cmEgYml0cyBsaWtlIHBlcmNlbnQgc2lnbnMgb3IgcGVyaW9kcyBpbiBudW1iZXJzLCBjYXVzaW5nIHRoZW0gdG8gYmUgcmVhZCBpbiBhcyBjaGFyYWN0ZXJzLiBJbiBvcmRlciB0byBiZSBhYmxlIHRvIGNydW5jaCB0aGVzZSBudW1iZXJzLCB0aGUgZXh0cmEgYml0cyBuZWVkIHRvIGJlIHJlbW92ZWQgYW5kIHRoZSBudW1iZXJzIG5lZWQgdG8gYmUgY29udmVydGVkIGZyb20gY2hhcmFjdGVyIHRvIG51bWVyaWMuDQpgYGB7cn0NCmJpa2Vfc2hhcmVfcmlkZXMgPC0gYmlrZV9zaGFyZV9yaWRlcyAlPiUNCiAgIyBSZW1vdmUgJ21pbnV0ZXMnIGZyb20gZHVyYXRpb246IGR1cmF0aW9uX3RyaW1tZWQNCiAgbXV0YXRlKGR1cmF0aW9uX3RyaW1tZWQgPSBzdHJfcmVtb3ZlKGR1cmF0aW9uLCAibWludXRlcyIpLA0KICAgICAgICAgIyBDb252ZXJ0IGR1cmF0aW9uX3RyaW1tZWQgdG8gbnVtZXJpYzogZHVyYXRpb25fbWlucw0KICAgICAgICAgZHVyYXRpb25fbWlucyA9IGFzLm51bWVyaWMoZHVyYXRpb25fdHJpbW1lZCkpDQoNCiMgR2xpbXBzZSBhdCBiaWtlX3NoYXJlX3JpZGVzDQpnbGltcHNlKGJpa2Vfc2hhcmVfcmlkZXMpDQoNCiMgQXNzZXJ0IGR1cmF0aW9uX21pbnMgaXMgbnVtZXJpYw0KYXNzZXJ0X2lzX251bWVyaWMoYmlrZV9zaGFyZV9yaWRlcyRkdXJhdGlvbl9taW5zKQ0KDQojIENhbGN1bGF0ZSBtZWFuIGR1cmF0aW9uDQptZWFuKGJpa2Vfc2hhcmVfcmlkZXMkZHVyYXRpb25fbWlucykNCmBgYA0KQnkgcmVtb3ZpbmcgY2hhcmFjdGVycyBhbmQgY29udmVydGluZyB0byBhIG51bWVyaWMgdHlwZSwgeW91IHdlcmUgYWJsZSB0byBmaWd1cmUgb3V0IHRoYXQgdGhlIGF2ZXJhZ2UgcmlkZSBkdXJhdGlvbiBpcyBhYm91dCAxMyBtaW51dGVzIC0gbm90IGJhZCBmb3IgYSBjaXR5IGxpa2UgU2FuIEZyYW5jaXNjbyENCg0KIyMgUmFuZ2UgY29uc3RyYWludHMNCg0KVmFsdWVzIHRoYXQgYXJlIG91dCBvZiByYW5nZSBjYW4gdGhyb3cgb2ZmIGFuIGFuYWx5c2lzLCBzbyBpdCdzIGltcG9ydGFudCB0byBjYXRjaCB0aGVtIGVhcmx5IG9uLiANCg0KQmlrZXMgYXJlIG5vdCBhbGxvd2VkIHRvIGJlIGtlcHQgb3V0IGZvciBbbW9yZSB0aGFuIDI0IGhvdXJzXShodHRwczovL2hlbHAuYmF5d2hlZWxzLmNvbS9oYy9lbi11cy9hcnRpY2xlcy8zNjAwMzM3OTA5MzItSG93LWxvbmctY2FuLUkta2VlcC1hLWJpa2Utb3V0LSksIG9yIDE0NDAgbWludXRlcyBhdCBhIHRpbWUsIGJ1dCBpc3N1ZXMgd2l0aCBzb21lIG9mIHRoZSBiaWtlcyBjYXVzZWQgaW5hY2N1cmF0ZSByZWNvcmRpbmcgb2YgdGhlIHRpbWUgdGhleSB3ZXJlIHJldHVybmVkLg0KYGBge3J9DQojIENyZWF0ZSBicmVha3MNCmJyZWFrcyA8LSBjKG1pbihiaWtlX3NoYXJlX3JpZGVzJGR1cmF0aW9uX21pbnMpLCAwLCAxNDQwLCBtYXgoYmlrZV9zaGFyZV9yaWRlcyRkdXJhdGlvbl9taW5zKSkNCg0KIyBDcmVhdGUgYSBoaXN0b2dyYW0gb2YgZHVyYXRpb25fbWluDQpnZ3Bsb3QoYmlrZV9zaGFyZV9yaWRlcywgYWVzKGR1cmF0aW9uX21pbnMpKSArDQogIGdlb21faGlzdG9ncmFtKGJyZWFrcyA9IGJyZWFrcykNCmBgYA0KYGBge3J9DQojIGR1cmF0aW9uX21pbl9jb25zdDogcmVwbGFjZSB2YWxzIG9mIGR1cmF0aW9uX21pbiA+IDE0NDAgd2l0aCAxNDQwDQpiaWtlX3NoYXJlX3JpZGVzIDwtIGJpa2Vfc2hhcmVfcmlkZXMgJT4lDQogIG11dGF0ZShkdXJhdGlvbl9taW5fY29uc3QgPSByZXBsYWNlKGR1cmF0aW9uX21pbnMsIGR1cmF0aW9uX21pbnMgPiAxNDQwLCAxNDQwKSkNCg0KIyBNYWtlIHN1cmUgYWxsIHZhbHVlcyBvZiBkdXJhdGlvbl9taW5fY29uc3QgYXJlIGJldHdlZW4gMCBhbmQgMTQ0MA0KYXNzZXJ0X2FsbF9hcmVfaW5fY2xvc2VkX3JhbmdlKGJpa2Vfc2hhcmVfcmlkZXMkZHVyYXRpb25fbWluX2NvbnN0LCBsb3dlciA9IDAsIHVwcGVyID0gMTQ0MCkNCmBgYA0KVGhlIG1ldGhvZCBvZiByZXBsYWNpbmcgZXJyb25lb3VzIGRhdGEgd2l0aCB0aGUgcmFuZ2UgbGltaXQgd29ya3Mgd2VsbCwgYnV0IHlvdSBjb3VsZCBqdXN0IGFzIGVhc2lseSByZXBsYWNlIHRoZXNlIHZhbHVlcyB3aXRoIE5BcyBvciBzb21ldGhpbmcgZWxzZSBpbnN0ZWFkLg0KDQojIyMgRnV0dXJlIGRhdGVzDQoNClNvbWV0aGluZyBoYXMgZ29uZSB3cm9uZyBhbmQgaXQgbG9va3MgbGlrZSB5b3UgaGF2ZSBkYXRhIHdpdGggZGF0ZXMgZnJvbSB0aGUgZnV0dXJlLCB3aGljaCBpcyB3YXkgb3V0c2lkZSBvZiB0aGUgZGF0ZSByYW5nZSB5b3UgZXhwZWN0ZWQgdG8gYmUgd29ya2luZyB3aXRoLiBUbyBmaXggdGhpcywgd2UnbGwgbmVlZCB0byByZW1vdmUgYW55IHJpZGVzIGZyb20gdGhlIGRhdGFzZXQgdGhhdCBoYXZlIGEgZGF0ZSBpbiB0aGUgZnV0dXJlLiBCZWZvcmUgeW91IGNhbiBkbyB0aGlzLCB0aGUgZGF0ZSBjb2x1bW4gbmVlZHMgdG8gYmUgY29udmVydGVkIGZyb20gYSBjaGFyYWN0ZXIgdG8gYSBEYXRlLiBIYXZpbmcgdGhlc2UgYXMgRGF0ZSBvYmplY3RzIHdpbGwgbWFrZSBpdCBtdWNoIGVhc2llciB0byBmaWd1cmUgb3V0IHdoaWNoIHJpZGVzIGFyZSBmcm9tIHRoZSBmdXR1cmUsIHNpbmNlIFIgbWFrZXMgaXQgZWFzeSB0byBjaGVjayBpZiBvbmUgRGF0ZSBvYmplY3QgaXMgYmVmb3JlICg8KSBvciBhZnRlciAoPikgYW5vdGhlci4NCmBgYHtyfQ0KbGlicmFyeShsdWJyaWRhdGUpDQojIENvbnZlcnQgZGF0ZSB0byBEYXRlIHR5cGUNCmJpa2Vfc2hhcmVfcmlkZXMgPC0gYmlrZV9zaGFyZV9yaWRlcyAlPiUNCiAgbXV0YXRlKGRhdGUgPSBhcy5EYXRlKGRhdGUpKQ0KDQojIE1ha2Ugc3VyZSBhbGwgZGF0ZXMgYXJlIGluIHRoZSBwYXN0DQphc3NlcnRfYWxsX2FyZV9pbl9wYXN0KGJpa2Vfc2hhcmVfcmlkZXMkZGF0ZSkNCg0KIyBGaWx0ZXIgZm9yIHJpZGVzIHRoYXQgb2NjdXJyZWQgYmVmb3JlIG9yIG9uIHRvZGF5J3MgZGF0ZQ0KYmlrZV9zaGFyZV9yaWRlc19wYXN0IDwtIGJpa2Vfc2hhcmVfcmlkZXMgJT4lDQogIGZpbHRlcihkYXRlIDw9IHRvZGF5KCkpDQoNCiMgTWFrZSBzdXJlIGFsbCBkYXRlcyBmcm9tIGJpa2Vfc2hhcmVfcmlkZXNfcGFzdCBhcmUgaW4gdGhlIHBhc3QNCmFzc2VydF9hbGxfYXJlX2luX3Bhc3QoYmlrZV9zaGFyZV9yaWRlc19wYXN0JGRhdGUpDQpgYGANCkhhbmRsaW5nIGRhdGEgZnJvbSB0aGUgZnV0dXJlIGxpa2UgdGhpcyBpcyBtdWNoIGVhc2llciB0aGFuIHRyeWluZyB0byB2ZXJpZnkgdGhlIGRhdGEncyBjb3JyZWN0bmVzcyBieSB0aW1lIHRyYXZlbGluZy4NCg0KIyMgVW5pcXVlbmVzcyBjb25zdHJhaW50cw0KDQojIyMgRnVsbCBkdXBsaWNhdGVzDQoNCldoZW4gbXVsdGlwbGUgcm93cyBvZiBhIGRhdGEgZnJhbWUgc2hhcmUgdGhlIHNhbWUgdmFsdWVzIGZvciBhbGwgY29sdW1ucywgdGhleSdyZSBmdWxsIGR1cGxpY2F0ZXMgb2YgZWFjaCBvdGhlci4gUmVtb3ZpbmcgZHVwbGljYXRlcyBsaWtlIHRoaXMgaXMgaW1wb3J0YW50LCBzaW5jZSBoYXZpbmcgdGhlIHNhbWUgdmFsdWUgcmVwZWF0ZWQgbXVsdGlwbGUgdGltZXMgY2FuIGFsdGVyIHN1bW1hcnkgc3RhdGlzdGljcyBsaWtlIHRoZSBtZWFuIGFuZCBtZWRpYW4uDQoNCkVhY2ggcmlkZSwgaW5jbHVkaW5nIGl0cyByaWRlX2lkIHNob3VsZCBiZSB1bmlxdWUuDQpgYGB7cn0NCiMgQ291bnQgdGhlIG51bWJlciBvZiBmdWxsIGR1cGxpY2F0ZXMNCnN1bShkdXBsaWNhdGVkKGJpa2Vfc2hhcmVfcmlkZXMpKQ0KDQojIFJlbW92ZSBkdXBsaWNhdGVzDQpiaWtlX3NoYXJlX3JpZGVzX3VuaXF1ZSA8LSBkaXN0aW5jdChiaWtlX3NoYXJlX3JpZGVzKQ0KICANCg0KIyBDb3VudCB0aGUgZnVsbCBkdXBsaWNhdGVzIGluIGJpa2Vfc2hhcmVfcmlkZXNfdW5pcXVlDQpzdW0oZHVwbGljYXRlZChiaWtlX3NoYXJlX3JpZGVzX3VuaXF1ZSkpDQpgYGANClJlbW92aW5nIGZ1bGwgZHVwbGljYXRlcyB3aWxsIGVuc3VyZSB0aGF0IHN1bW1hcnkgc3RhdGlzdGljcyBhcmVuJ3QgYWx0ZXJlZCBieSByZXBlYXRlZCBkYXRhIHBvaW50cy4NCiANCiMjIyBQYXJ0aWFsIGR1cGxpY2F0ZXMNCg0KUGFydGlhbCBkdXBsaWNhdGVzIGFyZSBhIGJpdCB0cmlja2VyIHRvIGRlYWwgd2l0aCB0aGFuIGZ1bGwgZHVwbGljYXRlcy4gV2UnbGwgZmlyc3QgaWRlbnRpZnkgYW55IHBhcnRpYWwgZHVwbGljYXRlcyBhbmQgdGhlbiBwcmFjdGljZSB0aGUgbW9zdCBjb21tb24gdGVjaG5pcXVlIHRvIGRlYWwgd2l0aCB0aGVtLCB3aGljaCBpbnZvbHZlcyBkcm9wcGluZyBhbGwgcGFydGlhbCBkdXBsaWNhdGVzLCBrZWVwaW5nIG9ubHkgdGhlIGZpcnN0Lg0KYGBge3J9DQojIEZpbmQgZHVwbGljYXRlZCByaWRlX2lkcw0KYmlrZV9zaGFyZV9yaWRlcyAlPiUgDQogICMgQ291bnQgdGhlIG51bWJlciBvZiBvY2N1cnJlbmNlcyBvZiBlYWNoIHJpZGVfaWQNCiAgY291bnQocmlkZV9pZCkgJT4lIA0KICAjIEZpbHRlciBmb3Igcm93cyB3aXRoIGEgY291bnQgPiAxDQogIGZpbHRlcihuPjEpDQpgYGANCmBgYHtyfQ0KIyBSZW1vdmUgZnVsbCBhbmQgcGFydGlhbCBkdXBsaWNhdGVzDQpiaWtlX3NoYXJlX3JpZGVzX3VuaXF1ZSA8LSBiaWtlX3NoYXJlX3JpZGVzICU+JQ0KICAjIE9ubHkgYmFzZWQgb24gcmlkZV9pZCBpbnN0ZWFkIG9mIGFsbCBjb2xzDQogIGRpc3RpbmN0KHJpZGVfaWQsIC5rZWVwX2FsbCA9IFRSVUUpDQpgYGANCmBgYHtyfQ0KIyBGaW5kIGR1cGxpY2F0ZWQgcmlkZV9pZHMgaW4gYmlrZV9zaGFyZV9yaWRlc191bmlxdWUNCmJpa2Vfc2hhcmVfcmlkZXNfdW5pcXVlICU+JQ0KICAjIENvdW50IHRoZSBudW1iZXIgb2Ygb2NjdXJyZW5jZXMgb2YgZWFjaCByaWRlX2lkDQogIGNvdW50KHJpZGVfaWQpICU+JQ0KICAjIEZpbHRlciBmb3Igcm93cyB3aXRoIGEgY291bnQgPiAxDQogIGZpbHRlcihuID4gMSkNCmBgYA0KSXQncyBpbXBvcnRhbnQgdG8gY29uc2lkZXIgdGhlIGRhdGEgeW91J3JlIHdvcmtpbmcgd2l0aCBiZWZvcmUgcmVtb3ZpbmcgcGFydGlhbCBkdXBsaWNhdGVzLCBzaW5jZSBzb21ldGltZXMgaXQncyBleHBlY3RlZCB0aGF0IHRoZXJlIHdpbGwgYmUgcGFydGlhbCBkdXBsaWNhdGVzIGluIGEgZGF0YXNldCwgc3VjaCBhcyBpZiB0aGUgc2FtZSBjdXN0b21lciBtYWtlcyBtdWx0aXBsZSBwdXJjaGFzZXMuDQoNCiMjIyMgQWdncmVnYXRpbmcgcGFydGlhbCBkdXBsaWNhdGVzDQoNCkFub3RoZXIgd2F5IG9mIGhhbmRsaW5nIHBhcnRpYWwgZHVwbGljYXRlcyBpcyB0byBjb21wdXRlIGEgc3VtbWFyeSBzdGF0aXN0aWMgb2YgdGhlIHZhbHVlcyB0aGF0IGRpZmZlciBiZXR3ZWVuIHBhcnRpYWwgZHVwbGljYXRlcywgc3VjaCBhcyBtZWFuLCBtZWRpYW4sIG1heGltdW0sIG9yIG1pbmltdW0uIFRoaXMgY2FuIGNvbWUgaW4gaGFuZHkgd2hlbiB5b3UncmUgbm90IHN1cmUgaG93IHlvdXIgZGF0YSB3YXMgY29sbGVjdGVkIGFuZCB3YW50IGFuIGF2ZXJhZ2UsIG9yIGlmIGJhc2VkIG9uIGRvbWFpbiBrbm93bGVkZ2UsIHlvdSdkIHJhdGhlciBoYXZlIHRvbyBoaWdoIG9mIGFuIGVzdGltYXRlIHRoYW4gdG9vIGxvdyBvZiBhbiBlc3RpbWF0ZSAob3IgdmljZSB2ZXJzYSkuDQpgYGB7cn0NCmJpa2Vfc2hhcmVfcmlkZXMgJT4lDQogICMgR3JvdXAgYnkgcmlkZV9pZCBhbmQgZGF0ZQ0KICBncm91cF9ieShyaWRlX2lkLCBkYXRlKSAlPiUNCiAgIyBBZGQgZHVyYXRpb25fbWluX2F2ZyBjb2x1bW4NCiAgbXV0YXRlKGR1cmF0aW9uX21pbl9hdmcgPSBtZWFuKGR1cmF0aW9uX21pbnMpKSAlPiUNCiAgIyBSZW1vdmUgZHVwbGljYXRlcyBiYXNlZCBvbiByaWRlX2lkIGFuZCBkYXRlLCBrZWVwIGFsbCBjb2xzDQogIGRpc3RpbmN0KHJpZGVfaWQsIGRhdGUsIC5rZWVwX2FsbCA9IFRSVUUpICU+JQ0KICAjIFJlbW92ZSBkdXJhdGlvbl9taW4gY29sdW1uDQogIHNlbGVjdCgtZHVyYXRpb25fbWlucykNCmBgYA0KQWdncmVnYXRpb24gb2YgcGFydGlhbCBkdXBsaWNhdGVzIGFsbG93cyB5b3UgdG8ga2VlcCBzb21lIGluZm9ybWF0aW9uIGFib3V0IGFsbCBkYXRhIHBvaW50cyBpbnN0ZWFkIG9mIGtlZXBpbmcgaW5mb3JtYXRpb24gYWJvdXQganVzdCBvbmUgZGF0YSBwb2ludC4NCg0KIyBDYXRlZ29yaWNhbCBhbmQgdGV4dCBkYXRhDQoNCldlJ2xsIGJlIHdvcmtpbmcgd2l0aCBhIGRhdGFzZXQgY2FsbGVkIHNmb19zdXJ2ZXksIGNvbnRhaW5pbmcgc3VydmV5IHJlc3BvbnNlcyBmcm9tIHBhc3NlbmdlcnMgdGFraW5nIGZsaWdodHMgZnJvbSBTYW4gRnJhbmNpc2NvIEludGVybmF0aW9uYWwgQWlycG9ydCAoU0ZPKS4gUGFydGljaXBhbnRzIHdlcmUgYXNrZWQgcXVlc3Rpb25zIGFib3V0IHRoZSBhaXJwb3J0J3MgY2xlYW5saW5lc3MsIHdhaXQgdGltZXMsIHNhZmV0eSwgYW5kIHRoZWlyIG92ZXJhbGwgc2F0aXNmYWN0aW9uLg0KDQojIyBNZW1iZXJzaGlwIGNvbnN0cmFpbnRzDQoNCiMjIyBOb3QgYSBtZW1iZXINCg0KVGhlcmUgd2VyZSBhIGZldyBpc3N1ZXMgZHVyaW5nIGRhdGEgY29sbGVjdGlvbiB0aGF0IHJlc3VsdGVkIGluIHNvbWUgaW5jb25zaXN0ZW5jaWVzIGluIHRoZSBkYXRhc2V0LiBXZSdsbCBiZSB3b3JraW5nIHdpdGggdGhlIGRlc3Rfc2l6ZSBjb2x1bW4sIHdoaWNoIGNhdGVnb3JpemVzIHRoZSBzaXplIG9mIHRoZSBkZXN0aW5hdGlvbiBhaXJwb3J0IHRoYXQgdGhlIHBhc3NlbmdlcnMgd2VyZSBmbHlpbmcgdG8uIA0KYGBge3J9DQpzZm9fc3VydmV5ID0gcmVhZFJEUygic2ZvX3N1cnZleV9jaDJfMS5yZHMiKQ0KZGVzdF9zaXplcyA9IGRhdGEuZnJhbWUoZGVzdF9zaXplID0gYygiU21hbGwiLCAiTWVkaXVtIiwgIkxhcmdlIiwgIkh1YiIpLA0KICAgICAgICAgICAgICAgICAgICAgICAgcGFzc2VuZ2Vyc19wZXJfZGF5ID0gYygiMC0yMEsiLCAiMjBLLTcwSyIsICI3MEstMTAwSyIsICIxMDBLKyIpKQ0KYGBgDQpgYGB7cn0NCmhlYWQoc2ZvX3N1cnZleSkNCmhlYWQoZGVzdF9zaXplcykNCmBgYA0KYGBge3J9DQojIENvdW50IHRoZSBudW1iZXIgb2Ygb2NjdXJyZW5jZXMgb2YgZGVzdF9zaXplDQpzZm9fc3VydmV5ICU+JQ0KICBjb3VudChkZXN0X3NpemUpDQpgYGANCmBgYHtyfQ0KIyBGaW5kIGJhZCBkZXN0X3NpemUgcm93cw0Kc2ZvX3N1cnZleSAlPiUgDQogICMgSm9pbiB3aXRoIGRlc3Rfc2l6ZXMgZGF0YSBmcmFtZSB0byBnZXQgYmFkIGRlc3Rfc2l6ZSByb3dzDQogIGFudGlfam9pbihkZXN0X3NpemVzLCBieSA9ICJkZXN0X3NpemUiKSAlPiUNCiAgIyBTZWxlY3QgaWQsIGFpcmxpbmUsIGRlc3RpbmF0aW9uLCBhbmQgZGVzdF9zaXplIGNvbHMNCiAgc2VsZWN0KGlkLCBhaXJsaW5lLCBkZXN0aW5hdGlvbiwgZGVzdF9zaXplKQ0KYGBgDQpgYGB7cn0NCiMgUmVtb3ZlIGJhZCBkZXN0X3NpemUgcm93cw0Kc2ZvX3N1cnZleSAlPiUgDQogICMgSm9pbiB3aXRoIGRlc3Rfc2l6ZXMNCiAgc2VtaV9qb2luKGRlc3Rfc2l6ZXMsIGJ5ID0gImRlc3Rfc2l6ZSIpICU+JQ0KICAjIENvdW50IHRoZSBudW1iZXIgb2YgZWFjaCBkZXN0X3NpemUNCiAgY291bnQoZGVzdF9zaXplKQ0KYGBgDQpBbnRpLWpvaW5zIGNhbiBoZWxwIHlvdSBpZGVudGlmeSB0aGUgcm93cyB0aGF0IGFyZSBjYXVzaW5nIGlzc3VlcywgYW5kIHNlbWktam9pbnMgY2FuIHJlbW92ZSB0aGUgaXNzdWUtY2F1c2luZyByb3dzLiBJbiB0aGUgbmV4dCBsZXNzb24sIHlvdSdsbCBsZWFybiBhYm91dCBvdGhlciB3YXlzIHRvIGRlYWwgd2l0aCBiYWQgdmFsdWVzIHNvIHRoYXQgeW91IGRvbid0IGhhdmUgdG8gbG9zZSByb3dzIG9mIGRhdGEuDQoNCiMjIENhdGVnb3JpY2FsIGRhdGEgcHJvYmxlbXMNCg0KIyMjIElkZW50aWZ5aW5nIGluY29uc2lzdGVuY3kNCg0KV2UnbGwgY29udGludWUgd29ya2luZyB3aXRoIHRoZSBzZm9fc3VydmV5IGRhdGFzZXQuIFdlJ2xsIGV4YW1pbmUgdGhlIGRlc3Rfc2l6ZSBjb2x1bW4gYWdhaW4gYXMgd2VsbCBhcyB0aGUgY2xlYW5saW5lc3MgY29sdW1uIGFuZCBkZXRlcm1pbmUgd2hhdCBraW5kIG9mIGlzc3VlcywgaWYgYW55LCB0aGVzZSB0d28gY2F0ZWdvcmljYWwgdmFyaWFibGVzIGZhY2UuDQpgYGB7cn0NCiMgQ291bnQgZGVzdF9zaXplDQpzZm9fc3VydmV5ICU+JQ0KICBjb3VudChkZXN0X3NpemUpDQpgYGANCmBgYHtyfQ0KIyBDb3VudCBjbGVhbmxpbmVzcw0Kc2ZvX3N1cnZleSAlPiUNCiAgY291bnQoY2xlYW5saW5lc3MpDQpgYGANCg0KDQojIyMgQ29ycmVjdGluZyBpbmNvbnNpc3RlbmN5DQoNCk5vdyB0aGF0IHdlJ3ZlIGlkZW50aWZpZWQgdGhhdCBkZXN0X3NpemUgaGFzIHdoaXRlc3BhY2UgaW5jb25zaXN0ZW5jaWVzIGFuZCBjbGVhbmxpbmVzcyBoYXMgY2FwaXRhbGl6YXRpb24gaW5jb25zaXN0ZW5jaWVzLCB3ZSdsbCB1c2UgdGhlIG5ldyB0b29scyBhdCB5b3VyIGRpc3Bvc2FsIHRvIGZpeCB0aGUgaW5jb25zaXN0ZW50IHZhbHVlcyBpbiBzZm9fc3VydmV5IGluc3RlYWQgb2YgcmVtb3ZpbmcgdGhlIGRhdGEgcG9pbnRzIGVudGlyZWx5LCB3aGljaCBjb3VsZCBhZGQgYmlhcyB0byB5b3VyIGRhdGFzZXQgaWYgbW9yZSB0aGFuIDUlIG9mIHRoZSBkYXRhIHBvaW50cyBuZWVkIHRvIGJlIGRyb3BwZWQuDQoNCmBgYHtyfQ0KIyBBZGQgbmV3IGNvbHVtbnMgdG8gc2ZvX3N1cnZleQ0Kc2ZvX3N1cnZleSA8LSBzZm9fc3VydmV5ICU+JQ0KICAjIGRlc3Rfc2l6ZV90cmltbWVkOiBkZXN0X3NpemUgd2l0aG91dCB3aGl0ZXNwYWNlDQogIG11dGF0ZShkZXN0X3NpemVfdHJpbW1lZCA9IHN0cl90cmltKGRlc3Rfc2l6ZSksDQogICAgICAgICAjIGNsZWFubGluZXNzX2xvd2VyOiBjbGVhbmxpbmVzcyBjb252ZXJ0ZWQgdG8gbG93ZXJjYXNlDQogICAgICAgICBjbGVhbmxpbmVzc19sb3dlciA9IHN0cl90b19sb3dlcihjbGVhbmxpbmVzcykpDQoNCiMgQ291bnQgdmFsdWVzIG9mIGRlc3Rfc2l6ZV90cmltbWVkDQpzZm9fc3VydmV5ICU+JQ0KICBjb3VudChkZXN0X3NpemVfdHJpbW1lZCkNCg0KIyBDb3VudCB2YWx1ZXMgb2YgY2xlYW5saW5lc3NfbG93ZXINCnNmb19zdXJ2ZXkgJT4lDQogIGNvdW50KGNsZWFubGluZXNzX2xvd2VyKQ0KYGBgDQpXZSB3ZXJlIGFibGUgdG8gY29udmVydCBzZXZlbi1jYXRlZ29yeSBkYXRhIGludG8gZm91ci1jYXRlZ29yeSBkYXRhLCB3aGljaCB3aWxsIGhlbHAgeW91ciBhbmFseXNpcyBnbyBtb3JlIHNtb290aGx5Lg0KDQojIyMgQ29sbGFwc2luZyBjYXRlZ29yaWVzDQoNCk9uZSBvZiB0aGUgdGFibGV0cyB0aGF0IHBhcnRpY2lwYW50cyBmaWxsZWQgb3V0IHRoZSBzZm9fc3VydmV5IG9uIHdhcyBub3QgcHJvcGVybHkgY29uZmlndXJlZCwgYWxsb3dpbmcgdGhlIHJlc3BvbnNlIGZvciBkZXN0X3JlZ2lvbiB0byBiZSBmcmVlIHRleHQgaW5zdGVhZCBvZiBhIGRyb3Bkb3duIG1lbnUuIFRoaXMgcmVzdWx0ZWQgaW4gc29tZSBpbmNvbnNpc3RlbmNpZXMgaW4gdGhlIGRlc3RfcmVnaW9uIHZhcmlhYmxlIHRoYXQgd2UnbGwgbmVlZCB0byBjb3JyZWN0Lg0KDQpgYGB7cn0NCmxpYnJhcnkoZm9yY2F0cykNCmBgYA0KYGBge3J9DQojIENvdW50IGNhdGVnb3JpZXMgb2YgZGVzdF9yZWdpb24NCnNmb19zdXJ2ZXkgJT4lDQogIGNvdW50KGRlc3RfcmVnaW9uKQ0KYGBgDQpgYGB7cn0NCiMgQ291bnQgY2F0ZWdvcmllcyBvZiBkZXN0X3JlZ2lvbg0Kc2ZvX3N1cnZleSAlPiUNCiAgY291bnQoZGVzdF9yZWdpb24pDQoNCiMgQ2F0ZWdvcmllcyB0byBtYXAgdG8gRXVyb3BlDQpldXJvcGVfY2F0ZWdvcmllcyA8LSBjKCJFdXJvcCIsICJFVSIsICJldXIiKQ0KDQojIEFkZCBhIG5ldyBjb2wgZGVzdF9yZWdpb25fY29sbGFwc2VkDQpzZm9fc3VydmV5ICU+JQ0KICAjIE1hcCBhbGwgY2F0ZWdvcmllcyBpbiBldXJvcGVfY2F0ZWdvcmllcyB0byBFdXJvcGUNCiAgbXV0YXRlKGRlc3RfcmVnaW9uX2NvbGxhcHNlZCA9IGZjdF9jb2xsYXBzZShkZXN0X3JlZ2lvbiwgDQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgRXVyb3BlID0gZXVyb3BlX2NhdGVnb3JpZXMpKSAlPiUNCiAgIyBDb3VudCBjYXRlZ29yaWVzIG9mIGRlc3RfcmVnaW9uX2NvbGxhcHNlZA0KICBjb3VudChkZXN0X3JlZ2lvbl9jb2xsYXBzZWQpDQpgYGANCllvdSd2ZSByZWR1Y2VkIHRoZSBudW1iZXIgb2YgY2F0ZWdvcmllcyBmcm9tIDEyIHRvIDksIGFuZCB5b3UgY2FuIG5vdyBiZSBjb25maWRlbnQgdGhhdCA0MDEgb2YgdGhlIHN1cnZleSBwYXJ0aWNpcGFudHMgd2VyZSBoZWFkaW5nIHRvIEV1cm9wZS4NCg0KIyMgQ2xlYW5pbmcgdGV4dCBkYXRhDQoNCiMjIyBEZXRlY3RpbmcgaW5jb25zaXN0ZW50IHRleHQgZGF0YQ0KDQpZb3UndmUgcmVjZW50bHkgcmVjZWl2ZWQgc29tZSBuZXdzIHRoYXQgdGhlIGN1c3RvbWVyIHN1cHBvcnQgdGVhbSB3YW50cyB0byBhc2sgdGhlIFNGTyBzdXJ2ZXkgcGFydGljaXBhbnRzIHNvbWUgZm9sbG93LXVwIHF1ZXN0aW9ucy4gSG93ZXZlciwgdGhlIGF1dG8tZGlhbGVyIHRoYXQgdGhlIGNhbGwgY2VudGVyIHVzZXMgaXNuJ3QgYWJsZSB0byBwYXJzZSBhbGwgb2YgdGhlIHBob25lIG51bWJlcnMgc2luY2UgdGhleSdyZSBhbGwgaW4gZGlmZmVyZW50IGZvcm1hdHMuIEFmdGVyIHNvbWUgaW52ZXN0aWdhdGlvbiwgeW91IGZvdW5kIHRoYXQgc29tZSBwaG9uZSBudW1iZXJzIGFyZSB3cml0dGVuIHdpdGggaHlwaGVucyAoLSkgYW5kIHNvbWUgYXJlIHdyaXR0ZW4gd2l0aCBwYXJlbnRoZXNlcyAoKCwpKS4gV2UnbGwgZmlndXJlIG91dCB3aGljaCBwaG9uZSBudW1iZXJzIGhhdmUgdGhlc2UgaXNzdWVzIHNvIHRoYXQgeW91IGtub3cgd2hpY2ggb25lcyBuZWVkIGZpeGluZy4NCmBgYHtyfQ0KY3VzdG9tZXJfYWNjb3VudHMgPC0gcmVhZFJEUygiZm9kb3JzLnJkcyIpDQpgYGANCmBgYHtyfQ0KaGVhZChjdXN0b21lcl9hY2NvdW50cykNCmBgYA0KYGBge3J9DQojIEZpbHRlciBmb3Igcm93cyB3aXRoICItIiBpbiB0aGUgcGhvbmUgY29sdW1uDQpjdXN0b21lcl9hY2NvdW50cyAlPiUNCiAgZmlsdGVyKHN0cl9kZXRlY3QocGhvbmUsICItIikpDQpgYGANCmBgYHtyfQ0KIyBGaWx0ZXIgZm9yIHJvd3Mgd2l0aCAiKCIgb3IgIikiIGluIHRoZSBwaG9uZSBjb2x1bW4NCmN1c3RvbWVyX2FjY291bnRzICU+JQ0KICBmaWx0ZXIoc3RyX2RldGVjdChwaG9uZSwgZml4ZWQoIigiKSkgfCBzdHJfZGV0ZWN0KHBob25lLCBmaXhlZCgiKSIpKSkNCmBgYA0KIE5vdyB0aGF0IHlvdSd2ZSBpZGVudGlmaWVkIHRoZSBpbmNvbnNpc3RlbmNpZXMgaW4gdGhlIHBob25lIGNvbHVtbiwgaXQncyB0aW1lIHRvIHJlbW92ZSB1bm5lY2Vzc2FyeSBjaGFyYWN0ZXJzIHRvIG1ha2UgdGhlIGZvbGxvdy11cCBzdXJ2ZXkgZ28gYXMgc21vb3RobHkgYXMgcG9zc2libGUuDQoNCiMjIyBSZXBsYWNpbmcgYW5kIHJlbW92aW5nDQoNClRoZSBjdXN0b21lciBzdXBwb3J0IHRlYW0gaGFzIHJlcXVlc3RlZCB0aGF0IGFsbCBwaG9uZSBudW1iZXJzIGJlIGluIHRoZSBmb3JtYXQgIjEyMyA0NTYgNzg5MCIuIEluIHRoaXMgZXhlcmNpc2UsIHlvdSdsbCB1c2UgeW91ciBuZXcgc3RyaW5nciBza2lsbHMgdG8gZnVsZmlsbCB0aGlzIHJlcXVlc3QuDQoNCmBgYHtyfQ0KIyBSZW1vdmUgcGFyZW50aGVzZXMgZnJvbSBwaG9uZSBjb2x1bW4NCnBob25lX25vX3BhcmVucyA8LSBjdXN0b21lcl9hY2NvdW50cyRwaG9uZSAlPiUNCiAgIyBSZW1vdmUgIigicw0KICBzdHJfcmVtb3ZlX2FsbChmaXhlZCgiKCIpKSAlPiUNCiAgIyBSZW1vdmUgIikicw0KICBzdHJfcmVtb3ZlX2FsbChmaXhlZCgiKSIpKQ0KYGBgDQpgYGB7cn0NCiMgQWRkIHBob25lX25vX3BhcmVucyBhcyBjb2x1bW4NCmN1c3RvbWVyX2FjY291bnRzICU+JQ0KICBtdXRhdGUocGhvbmVfbm9fcGFyZW5zID0gcGhvbmVfbm9fcGFyZW5zKQ0KYGBgDQpgYGB7cn0NCiMgQWRkIHBob25lX25vX3BhcmVucyBhcyBjb2x1bW4NCmN1c3RvbWVyX2FjY291bnRzICU+JQ0KICBtdXRhdGUocGhvbmVfbm9fcGFyZW5zID0gcGhvbmVfbm9fcGFyZW5zLA0KICAjIFJlcGxhY2UgYWxsIGh5cGhlbnMgaW4gcGhvbmVfbm9fcGFyZW5zIHdpdGggc3BhY2VzDQogICAgICAgICBwaG9uZV9jbGVhbiA9IHN0cl9yZXBsYWNlX2FsbChwaG9uZV9ub19wYXJlbnMsICItIiwgIiAiKSkNCmBgYA0KIyMjIEludmFsaWQgcGhvbmUgbnVtYmVycw0KDQpXZSdsbCByZW1vdmUgYW55IHJvd3Mgd2l0aCBpbnZhbGlkIHBob25lIG51bWJlcnMuIA0KDQpgYGB7cn0NCiMgQ2hlY2sgb3V0IHRoZSBpbnZhbGlkIG51bWJlcnMNCmN1c3RvbWVyX2FjY291bnRzICU+JQ0KICBmaWx0ZXIoc3RyX2xlbmd0aChwaG9uZSkgIT0gMTIpDQoNCiMgUmVtb3ZlIHJvd3Mgd2l0aCBpbnZhbGlkIG51bWJlcnMNCmN1c3RvbWVyX2FjY291bnRzICU+JQ0KICBmaWx0ZXIgKHN0cl9sZW5ndGgocGhvbmUpID09IDEyKQ0KYGBgDQojIEFkdmFuY2VkIERhdGEgUHJvYmxlbXMNCg0KIyMgVW5pZm9ybWl0eQ0KDQojIyMgRGF0ZSB1bmlmb3JtaXR5DQoNCnlvdSB3b3JrIGF0IGFuIGFzc2V0IG1hbmFnZW1lbnQgY29tcGFueSBhbmQgeW91J2xsIGJlIHdvcmtpbmcgd2l0aCB0aGUgYWNjb3VudHMgZGF0YXNldCwgd2hpY2ggY29udGFpbnMgaW5mb3JtYXRpb24gYWJvdXQgZWFjaCBjdXN0b21lciwgdGhlIGFtb3VudCBpbiB0aGVpciBhY2NvdW50LCBhbmQgdGhlIGRhdGUgdGhlaXIgYWNjb3VudCB3YXMgb3BlbmVkLiBZb3VyIGJvc3MgaGFzIGFza2VkIHlvdSB0byBjYWxjdWxhdGUgc29tZSBzdW1tYXJ5IHN0YXRpc3RpY3MgYWJvdXQgdGhlIGF2ZXJhZ2UgdmFsdWUgb2YgZWFjaCBhY2NvdW50IGFuZCB3aGV0aGVyIHRoZSBhZ2Ugb2YgdGhlIGFjY291bnQgaXMgYXNzb2NpYXRlZCB3aXRoIGEgaGlnaGVyIG9yIGxvd2VyIGFjY291bnQgdmFsdWUuIEJlZm9yZSB5b3UgY2FuIGRvIHRoaXMsIHlvdSBuZWVkIHRvIG1ha2Ugc3VyZSB0aGF0IHRoZSBhY2NvdW50cyBkYXRhc2V0IHlvdSd2ZSBiZWVuIGdpdmVuIGRvZXNuJ3QgY29udGFpbiBhbnkgdW5pZm9ybWl0eSBwcm9ibGVtcy4NCg0KV2UnbGwgaW52ZXN0aWdhdGUgdGhlIGRhdGVfb3BlbmVkIGNvbHVtbiBhbmQgY2xlYW4gaXQgdXAgc28gdGhhdCBhbGwgdGhlIGRhdGVzIGFyZSBpbiB0aGUgc2FtZSBmb3JtYXQuDQpgYGB7cn0NCmFjY291bnRzIDwtIHJlYWRSRFMoImNoM18xX2FjY291bnRzLnJkcyIpDQpoZWFkKGFjY291bnRzKQ0KYGBgDQpgYGB7cn0NCmFzLkRhdGUoYWNjb3VudHMkZGF0ZV9vcGVuZWQpDQpgYGANCk5vdGljZSB3ZSBoYXZlIGEgbG90IG9mIE5Bcy4gQnkgZGVmYXVsdCwgYXMuRGF0ZSgpIGNhbid0IGNvbnZlcnQgIk1vbnRoIERELCBZWVlZIiBmb3JtYXRzLg0KDQpXZSBjYW4gdXNlIHBhcnNlX2RhdGVfdGltZSgpIGluc3RlYWQ6DQpgYGB7cn0NCiMgRGVmaW5lIHRoZSBkYXRlIGZvcm1hdHMNCmZvcm1hdHMgPC0gYygiJVktJW0tJWQiLCAiJUIgJWQsICVZIikNCg0KIyBDb252ZXJ0IGRhdGVzIHRvIHRoZSBzYW1lIGZvcm1hdA0KYWNjb3VudHMgJT4lDQogIG11dGF0ZShkYXRlX29wZW5lZF9jbGVhbiA9IHBhcnNlX2RhdGVfdGltZShkYXRlX29wZW5lZCwgZm9ybWF0cykpDQpgYGANCg0KIyMjIEN1cnJlbmN5IHVuaWZvcm1pdHkNCg0KV2UnbGwgbmVlZCB0byBjb3JyZWN0IGFueSB1bml0IGRpZmZlcmVuY2VzLiBXaGVuIHdlIGZpcnN0IHBsb3QgdGhlIGRhdGEsIHdlJ2xsIG5vdGljZSB0aGF0IHRoZXJlJ3MgYSBncm91cCBvZiB2ZXJ5IGhpZ2ggdmFsdWVzLCBhbmQgYSBncm91cCBvZiByZWxhdGl2ZWx5IGxvd2VyIHZhbHVlcy4gVGhlIGJhbmsgaGFzIHR3byBkaWZmZXJlbnQgb2ZmaWNlcyAtIG9uZSBpbiBOZXcgWW9yaywgYW5kIG9uZSBpbiBUb2t5bywgc28geW91IHN1c3BlY3QgdGhhdCB0aGUgYWNjb3VudHMgbWFuYWdlZCBieSB0aGUgVG9reW8gb2ZmaWNlIGFyZSBpbiBKYXBhbmVzZSB5ZW4gaW5zdGVhZCBvZiBVLlMuIGRvbGxhcnMuIEx1Y2tpbHksIHdlIGhhdmUgYSBkYXRhIGZyYW1lIGNhbGxlZCBhY2NvdW50X29mZmljZXMgdGhhdCBpbmRpY2F0ZXMgd2hpY2ggb2ZmaWNlIG1hbmFnZXMgZWFjaCBjdXN0b21lcidzIGFjY291bnQsIHNvIHlvdSBjYW4gdXNlIHRoaXMgaW5mb3JtYXRpb24gdG8gZmlndXJlIG91dCB3aGljaCB0b3RhbHMgbmVlZCB0byBiZSBjb252ZXJ0ZWQgZnJvbSB5ZW4gdG8gZG9sbGFycy4NCmBgYHtyfQ0KIyBTY2F0dGVyIHBsb3Qgb2Ygb3BlbmluZyBkYXRlIHZzIHRvdGFsIGFtb3VudA0KYWNjb3VudHMgJT4lDQogIGdncGxvdChhZXMoeCA9IGRhdGVfb3BlbmVkLCB5ID0gdG90YWwpKSArDQogIGdlb21fcG9pbnQoKQ0KYGBgDQoNClRoZSBmb3JtdWxhIHRvIGNvbnZlcnQgeWVuIHRvIGRvbGxhcnMgaXMgVVNEID0gSlBZIC8gMTA0Lg0KYGBge3J9DQphY2NvdW50X29mZmljZXMgPC0gcmVhZC5jc3YoImFjY291bnRfb2ZmaWNlcy5jc3YiLHN0cmluZ3NBc0ZhY3RvcnM9RkFMU0UpDQphY2NvdW50X29mZmljZXMgPC0gYWNjb3VudF9vZmZpY2VzICU+JSANCiAgbXV0YXRlKG9mZmljZSA9IHN0cl90cmltKG9mZmljZSkpDQpoZWFkKGFjY291bnRfb2ZmaWNlcykNCmBgYA0KYGBge3J9DQojIExlZnQgam9pbiBhY2NvdW50cyBhbmQgYWNjb3VudF9vZmZpY2VzIGJ5IGlkDQphY2NvdW50cyAlPiUNCiAgbGVmdF9qb2luKGFjY291bnRfb2ZmaWNlcywgYnkgPSAiaWQiKQ0KYGBgDQpgYGB7cn0NCiMgTGVmdCBqb2luIGFjY291bnRzIHRvIGFjY291bnRfb2ZmaWNlcyBieSBpZA0KYWNjb3VudHMgJT4lDQogIGxlZnRfam9pbihhY2NvdW50X29mZmljZXMsIGJ5ID0gImlkIikgJT4lDQogICMgQ29udmVydCB0b3RhbHMgZnJvbSB0aGUgVG9reW8gb2ZmaWNlIHRvIEpQWQ0KICBtdXRhdGUodG90YWxfdXNkID0gaWZlbHNlKG9mZmljZSA9PSAiVG9reW8iLCB0b3RhbC8xMDQsIHRvdGFsKSkNCmBgYA0KYGBge3J9DQojIExlZnQgam9pbiBhY2NvdW50cyB0byBhY2NvdW50X29mZmljZXMgYnkgaWQNCmFjY291bnRzICU+JQ0KICBsZWZ0X2pvaW4oYWNjb3VudF9vZmZpY2VzLCBieSA9ICJpZCIpICU+JQ0KICAjIENvbnZlcnQgdG90YWxzIGZyb20gdGhlIFRva3lvIG9mZmljZSB0byBKUFkNCiAgbXV0YXRlKHRvdGFsX3VzZCA9IGlmZWxzZShvZmZpY2UgPT0gIlRva3lvIiwgdG90YWwgLyAxMDQsIHRvdGFsKSkgJT4lDQogICMgU2NhdHRlciBwbG90IG9mIG9wZW5pbmcgZGF0ZSB2cyB0b3RhbF91c2QNCiAgZ2dwbG90KGFlcyh4ID0gZGF0ZV9vcGVuZWQsIHkgPSB0b3RhbF91c2QpKSArDQogICAgZ2VvbV9wb2ludCgpDQpgYGANCiBUaGUgcG9pbnRzIGluIHlvdXIgbGFzdCBzY2F0dGVyIHBsb3QgYWxsIGZhbGwgd2l0aGluIGEgbXVjaCBzbWFsbGVyIHJhbmdlIG5vdyBhbmQgeW91J2xsIGJlIGFibGUgdG8gYWNjdXJhdGVseSBhc3Nlc3MgdGhlIGRpZmZlcmVuY2VzIGJldHdlZW4gYWNjb3VudHMgZnJvbSBkaWZmZXJlbnQgY291bnRyaWVzLg0KDQojIyBDcm9zcyBmaWVsZCB2YWxpZGF0aW9uDQoNCiMjIyBWYWxpZGF0aW5nIHRvdGFscw0KDQpJZiB3ZSBoYXZlIDMgZnVuZCBjb2x1bW5zLCB0aGV5IHNob3VsZCBzdW0gdGhlIHNhbWUgYXMgdGhlIHRvdGFsIGNvbHVtbjoNCg0KICAgICMgRmluZCBpbnZhbGlkIHRvdGFscw0KICAgIGFjY291bnRzICU+JQ0KICAgICAgIyB0aGVvcmV0aWNhbF90b3RhbDogc3VtIG9mIHRoZSB0aHJlZSBmdW5kcw0KICAgICAgbXV0YXRlKHRoZW9yZXRpY2FsX3RvdGFsID0gZnVuZF9BICsgZnVuZF9CICsgZnVuZF9DKSAlPiUNCiAgICAgICMgRmluZCBhY2NvdW50cyB3aGVyZSB0b3RhbCBkb2Vzbid0IG1hdGNoIHRoZW9yZXRpY2FsX3RvdGFsDQogICAgICBmaWx0ZXIodG90YWwgIT0gdGhlb3JldGljYWxfdG90YWwpDQoNCkJ5IHVzaW5nIGNyb3NzIGZpZWxkIHZhbGlkYXRpb24sIHlvdSd2ZSBiZWVuIGFibGUgdG8gZGV0ZWN0IHZhbHVlcyB0aGF0IGRvbid0IG1ha2Ugc2Vuc2UuIEhvdyB5b3UgY2hvb3NlIHRvIGhhbmRsZSB0aGVzZSB2YWx1ZXMgd2lsbCBkZXBlbmQgb24gdGhlIGRhdGFzZXQuDQoNCiMjIyBWYWxpZGF0aW5nIGFnZQ0KDQpXZSdsbCBuZWVkIHRvIHZhbGlkYXRlIHRoZSBhZ2Ugb2YgZWFjaCBhY2NvdW50IGFuZCBzZWUgaWYgcm93cyB3aXRoIGluY29uc2lzdGVudCBhY2N0X2FnZXMgYXJlIHRoZSBzYW1lIG9uZXMgdGhhdCBoYWQgaW5jb25zaXN0ZW50IHRvdGFscw0KDQogICAgIyBGaW5kIGludmFsaWQgYWNjdF9hZ2UNCiAgICBhY2NvdW50cyAlPiUNCiAgICAgICMgdGhlb3JldGljYWxfYWdlOiBhZ2Ugb2YgYWNjdCBiYXNlZCBvbiBkYXRlX29wZW5lZA0KICAgICAgbXV0YXRlKHRoZW9yZXRpY2FsX2FnZSA9IGZsb29yKGFzLm51bWVyaWMoZGF0ZV9vcGVuZWQgJS0tJSB0b2RheSgpLA0KICAgICJ5ZWFycyIpKSkgJT4lDQogICAgICAjIEZpbHRlciBmb3Igcm93cyB3aGVyZSBhY2N0X2FnZSBpcyBkaWZmZXJlbnQgZnJvbSB0aGVvcmV0aWNhbF9hZ2UNCiAgICAgIGZpbHRlcih0aGVvcmV0aWNhbF9hZ2UgIT0gYWNjdF9hZ2UpDQogICAgICANClRoZXJlIGFyZSB0aHJlZSBhY2NvdW50cyB0aGF0IGFsbCBoYXZlIGFnZXMgb2ZmIGJ5IG9uZSB5ZWFyLCBidXQgbm9uZSBvZiB0aGVtIGFyZSB0aGUgc2FtZSBhcyB0aGUgYWNjb3VudHMgdGhhdCBoYWQgdG90YWwgaW5jb25zaXN0ZW5jaWVzLCBzbyBpdCBsb29rcyBsaWtlIHRoZXNlIHR3byBib29ra2VlcGluZyBlcnJvcnMgbWF5IG5vdCBiZSByZWxhdGVkLg0KDQojIyBDb21wbGV0ZW5lc3MNCg0KVGhyZWUgZmxhdm9ycyBvZiBtaXNzaW5nIGRhdGE6IG1pc3NpbmcgY29tcGxldGVseSBhdCByYW5kb20gKE1DQVIpLCBtaXNzaW5nIGF0IHJhbmRvbSAoTUFSKSwgYW5kIG1pc3Npbmcgbm90IGF0IHJhbmRvbSAoTU5BUikuDQoNCi0gTUNBUjogTm8gc3lzdGVtYXRpYyByZWxhdGlvbnNoaXAgYmV0d2VlbiBtaXNzaW5nIGRhdGEgYW5kIG90aGVyIHZhbHVlcw0KLSBNQVI6IFN5c3RlbWF0aWMgcmVsYXRpb25zaGlwIGJldHdlZW4gbWlzc2luZCBkYXRhIGFuZCBvdGhlciBvYnNlcnZlZCB2YWx5dXMNCi0gTU5BUjogU3lzdGVtYXRpYyByZWxhdGlvbnNoaXAgYmV0d2VlbiBtaXNzaW5nIGRhdGEgYW5kIHVub2JzZXJ2ZWQgdmFsdWVzDQoNCiMjIyBWaXN1YWxpemluZyBtaXNzaW5nIGRhdGENCg0KRGVhbGluZyB3aXRoIG1pc3NpbmcgZGF0YSBpcyBvbmUgb2YgdGhlIG1vc3QgY29tbW9uIHRhc2tzIGluIGRhdGEgc2NpZW5jZS4gVGhlcmUgYXJlIGEgdmFyaWV0eSBvZiB0eXBlcyBvZiBtaXNzaW5nbmVzcywgYXMgd2VsbCBhcyBhIHZhcmlldHkgb2YgdHlwZXMgb2Ygc29sdXRpb25zIHRvIG1pc3NpbmcgZGF0YS4NCg0KYGBge3J9DQpsaWJyYXJ5KHZpc2RhdCkNCnZpc19taXNzKGFjY291bnRzKQ0KYGBgDQoNCiAgICBhY2NvdW50cyAlPiUNCiAgICAgICMgbWlzc2luZ19pbnY6IElzIGludl9hbW91bnQgbWlzc2luZz8NCiAgICAgIG11dGF0ZShtaXNzaW5nX2ludiA9IGlzLm5hKGludl9hbW91bnQpKSAlPiUNCiAgICAgICMgR3JvdXAgYnkgbWlzc2luZ19pbnYNCiAgICAgIGdyb3VwX2J5KG1pc3NpbmdfaW52KSAlPiUNCiAgICAgICMgQ2FsY3VsYXRlIG1lYW4gYWdlIGZvciBlYWNoIG1pc3NpbmdfaW52IGdyb3VwDQogICAgICBzdW1tYXJpemUoYXZnX2FnZSA9IG1lYW4oYWdlKSkNCg0KIVttaXNzaW5nXShpbWFnZXMvbWlzc2luZ19pbnYucG5nKQ0KDQogICAgIyBTb3J0IGJ5IGFnZSBhbmQgdmlzdWFsaXplIG1pc3NpbmcgdmFscw0KICAgIGFjY291bnRzICU+JQ0KICAgICAgYXJyYW5nZShhZ2UpICU+JQ0KICAgICAgdmlzX21pc3MoKQ0KDQogSW52ZXN0aWdhdGluZyBzdW1tYXJ5IHN0YXRpc3RpY3MgYmFzZWQgb24gbWlzc2luZ25lc3MgaXMgYSBncmVhdCB3YXkgdG8gZGV0ZXJtaW5lIGlmIGRhdGEgaXMgbWlzc2luZyBjb21wbGV0ZWx5IGF0IHJhbmRvbSBvciBtaXNzaW5nIGF0IHJhbmRvbS4NCg0KIyMjIFRyZWF0aW5nIG1pc3NpbmcgZGF0YQ0KDQpSZW1vdmluZyBtaXNzaW5nIHZhbHVlczoNCg0KICAgICMgQ3JlYXRlIGFjY291bnRzX2NsZWFuDQogICAgYWNjb3VudHNfY2xlYW4gPC0gYWNjb3VudHMgJT4lDQogICAgICAjIEZpbHRlciB0byByZW1vdmUgcm93cyB3aXRoIG1pc3NpbmcgY3VzdF9pZA0KICAgICAgZmlsdGVyKCFpcy5uYShjdXN0X2lkKSkNCg0KUmVwbGFjaW5nIG1pc3NpbmcgdmFsdWVzOg0KDQogICAgIyBDcmVhdGUgYWNjb3VudHNfY2xlYW4NCiAgICBhY2NvdW50c19jbGVhbiA8LSBhY2NvdW50cyAlPiUNCiAgICAgICMgRmlsdGVyIHRvIHJlbW92ZSByb3dzIHdpdGggbWlzc2luZyBjdXN0X2lkDQogICAgICBmaWx0ZXIoIWlzLm5hKGN1c3RfaWQpKSAlPiUNCiAgICAgICMgQWRkIG5ldyBjb2wgYWNjdF9hbW91bnRfZmlsbGVkIHdpdGggcmVwbGFjZWQgTkFzDQogICAgICBtdXRhdGUoYWNjdF9hbW91bnRfZmlsbGVkID0gaWZlbHNlKGlzLm5hKGFjY3RfYW1vdW50KSwgNSAqIGludl9hbW91bnQsIGFjY3RfYW1vdW50KSkNCg0KQXNzZXJ0IHRoYXQgdGhlcmUgYXJlIG5vIG1pc3NpbmcgdmFsdWVzOg0KDQogICAgIyBBc3NlcnQgdGhhdCBjdXN0X2lkIGhhcyBubyBtaXNzaW5nIHZhbHMNCiAgICBhc3NlcnRfYWxsX2FyZV9ub3RfbmEoYWNjb3VudHNfY2xlYW4kY3VzdF9pZCkNCg0KICAgICMgQXNzZXJ0IHRoYXQgYWNjdF9hbW91bnRfZmlsbGVkIGhhcyBubyBtaXNzaW5nIHZhbHMNCiAgICBhc3NlcnRfYWxsX2FyZV9ub3RfbmEoYWNjb3VudHNfY2xlYW4kYWNjdF9hbW91bnRfZmlsbGVkKQ0KICAgIA0KICANCiMgUmVjb3JkIExpbmthZ2UNCg0KIyMgQ29tcGFyaW5nIHN0cmluZ3MNCg0KRGFtZXJhdS1MZXZlbnNodGVpbiBkaXN0YW5jZSBpcyB0aGUgbWluaW11bSBudW1iZXIgb2Ygc3RlcHMgbmVlZGVkIHRvIGdldCBmcm9tIFN0cmluZyBBIHRvIFN0cmluZyBCLCB1c2luZyB0aGVzZSBvcGVyYXRpb25zOg0KDQotIEluc2VydGlvbiBvZiBhIG5ldyBjaGFyYWN0ZXIuDQotIERlbGV0aW9uIG9mIGFuIGV4aXN0aW5nIGNoYXJhY3Rlci4NCi0gU3Vic3RpdHV0aW9uIG9mIGFuIGV4aXN0aW5nIGNoYXJhY3Rlci4NCi0gVHJhbnNwb3NpdGlvbiBvZiB0d28gZXhpc3RpbmcgY29uc2VjdXRpdmUgY2hhcmFjdGVycy4NCg0KIyMjIFNtYWxsIGRpc3RhbmNlLCBzbWFsbCBkaWZmZXJlbmNlDQoNClRoZXJlIGFyZSBtdWx0aXBsZSB3YXlzIHRvIGNhbGN1bGF0ZSBob3cgc2ltaWxhciBvciBkaWZmZXJlbnQgdHdvIHN0cmluZ3MgYXJlLiBOb3cgd2UnbGwgcHJhY3RpY2UgdXNpbmcgdGhlIHN0cmluZ2Rpc3QgcGFja2FnZSB0byBjb21wdXRlIHN0cmluZyBkaXN0YW5jZXMgdXNpbmcgdmFyaW91cyBtZXRob2RzLiBJdCdzIGltcG9ydGFudCB0byBiZSBmYW1pbGlhciB3aXRoIGRpZmZlcmVudCBtZXRob2RzLCBhcyBzb21lIG1ldGhvZHMgd29yayBiZXR0ZXIgb24gY2VydGFpbiBkYXRhc2V0cywgd2hpbGUgb3RoZXJzIHdvcmsgYmV0dGVyIG9uIG90aGVyIGRhdGFzZXRzLg0KYGBge3J9DQpsaWJyYXJ5KHN0cmluZ2Rpc3QpDQpgYGANCkRhbWVyYXUtTGV2ZW5zaHRlaW4gZGlzdGFuY2U6DQpgYGB7cn0NCiMgQ2FsY3VsYXRlIERhbWVyYXUtTGV2ZW5zaHRlaW4gZGlzdGFuY2UNCnN0cmluZ2Rpc3QoImxhcyBhbmdlbG9zIiwgImxvcyBhbmdlbGVzIiwgbWV0aG9kID0gImRsIikNCmBgYA0KTG9uZ2VzdCBDb21tb24gU3Vic3RyaW5nIChMQ1MpOg0KYGBge3J9DQojIENhbGN1bGF0ZSBMQ1MgZGlzdGFuY2UNCnN0cmluZ2Rpc3QoImxhcyBhbmdlbG9zIiwgImxvcyBhbmdlbGVzIiwgbWV0aG9kID0gImxjcyIpDQpgYGANCkxDUyBkaXN0YW5jZSBvbmx5IHVzZXMgaW5zZXJ0aW9uIGFuZCBkZWxldGlvbiwgc28gaXQgdGFrZXMgbW9yZSBvcGVyYXRpb25zIHRvIGNoYW5nZSBhIHN0cmluZyB0byBhbm90aGVyLg0KDQpKYWNjYXJkIGRpc3RhbmNlOg0KYGBge3J9DQojIENhbGN1bGF0ZSBKYWNjYXJkIGRpc3RhbmNlDQpzdHJpbmdkaXN0KCJsYXMgYW5nZWxvcyIsICJsb3MgYW5nZWxlcyIsIG1ldGhvZCA9ICJqYWNjYXJkIikNCmBgYA0KIyMjIEZpeGluZyB0eXBvcyB3aXRoIHN0cmluZyBkaXN0YW5jZQ0KDQpPbmUgb2YgdGhlIGRhdGFzZXRzIHlvdSdsbCBiZSB3b3JraW5nIHdpdGgsIHphZ2F0LCBpcyBhIHNldCBvZiByZXN0YXVyYW50cyBpbiBOZXcgWW9yaywgTG9zIEFuZ2VsZXMsIEF0bGFudGEsIFNhbiBGcmFuY2lzY28sIGFuZCBMYXMgVmVnYXMuIFRoZSBkYXRhIGlzIGZyb20gWmFnYXQsIGEgY29tcGFueSB0aGF0IGNvbGxlY3RzIHJlc3RhdXJhbnQgcmV2aWV3cywgYW5kIGluY2x1ZGVzIHRoZSByZXN0YXVyYW50IG5hbWVzLCBhZGRyZXNzZXMsIHBob25lIG51bWJlcnMsIGFzIHdlbGwgYXMgb3RoZXIgcmVzdGF1cmFudCBpbmZvcm1hdGlvbi4NCg0KVGhlIGNpdHkgY29sdW1uIGNvbnRhaW5zIHRoZSBuYW1lIG9mIHRoZSBjaXR5IHRoYXQgdGhlIHJlc3RhdXJhbnQgaXMgbG9jYXRlZCBpbi4gSG93ZXZlciwgdGhlcmUgYXJlIGEgbnVtYmVyIG9mIHR5cG9zIHRocm91Z2hvdXQgdGhlIGNvbHVtbi4gWW91ciB0YXNrIGlzIHRvIG1hcCBlYWNoIGNpdHkgdG8gb25lIG9mIHRoZSBmaXZlIGNvcnJlY3RseS1zcGVsbGVkIGNpdGllcyBjb250YWluZWQgaW4gdGhlIGNpdGllcyBkYXRhIGZyYW1lLg0KYGBge3J9DQp6YWdhdCA8LSByZWFkUkRTKCJ6YWdhdC5yZHMiKQ0KYGBgDQpgYGB7cn0NCmNpdGllcyA8LSBkYXRhLmZyYW1lKGNpdHlfYWN0dWFsID0gYygibmV3IHlvcmsiLCAibG9zIGFuZ2VsZXMiLCAiYXRsYW50YSIsICJzYW4gZnJhbmNpc2NvIiwgImxhcyB2ZWdhcyIpKQ0KYGBgDQpgYGB7cn0NCiMgQ291bnQgdGhlIG51bWJlciBvZiBlYWNoIGNpdHkgdmFyaWF0aW9uDQp6YWdhdCAlPiUNCiAgY291bnQoY2l0eSkNCmBgYA0KYGBge3J9DQpsaWJyYXJ5KGZ1enp5am9pbikNCiMgSm9pbiB6YWdhdCBhbmQgY2l0aWVzIGFuZCBsb29rIGF0IHJlc3VsdHMNCnphZ2F0ICU+JQ0KICAjIExlZnQgam9pbiBiYXNlZCBvbiBzdHJpbmdkaXN0IHVzaW5nIGNpdHkgYW5kIGNpdHlfYWN0dWFsIGNvbHMNCiAgc3RyaW5nZGlzdF9sZWZ0X2pvaW4oY2l0aWVzLCBieSA9IGMoImNpdHkiID0gImNpdHlfYWN0dWFsIikpICU+JQ0KICAjIFNlbGVjdCB0aGUgbmFtZSwgY2l0eSwgYW5kIGNpdHlfYWN0dWFsIGNvbHMNCiAgc2VsZWN0KG5hbWUsIGNpdHksIGNpdHlfYWN0dWFsKQ0KYGBgDQpOb3cgdGhhdCB5b3UndmUgY3JlYXRlZCBjb25zaXN0ZW50IHNwZWxsaW5nIGZvciBlYWNoIGNpdHksIGl0IHdpbGwgYmUgbXVjaCBlYXNpZXIgdG8gY29tcHV0ZSBzdW1tYXJ5IHN0YXRpc3RpY3MgYnkgY2l0eS4NCg0KDQojIyBHZW5lcmF0aW5nIGFuZCBjb21wYXJpbmcgcGFpcnMNCiANCiMjIyBMaW5rIG9yIGpvaW4/DQogDQpTaW1pbGFyIHRvIGpvaW5zLCByZWNvcmQgbGlua2FnZSBpcyB0aGUgYWN0IG9mIGxpbmtpbmcgZGF0YSBmcm9tIGRpZmZlcmVudCBzb3VyY2VzIHJlZ2FyZGluZyB0aGUgc2FtZSBlbnRpdHkuIEJ1dCB1bmxpa2Ugam9pbnMsIHJlY29yZCBsaW5rYWdlIGRvZXMgbm90IHJlcXVpcmUgZXhhY3QgbWF0Y2hlcyBiZXR3ZWVuIGRpZmZlcmVudCBwYWlycyBvZiBkYXRhLCBhbmQgaW5zdGVhZCBjYW4gZmluZCBjbG9zZSBtYXRjaGVzIHVzaW5nIHN0cmluZyBzaW1pbGFyaXR5LiBUaGlzIGlzIHdoeSByZWNvcmQgbGlua2FnZSBpcyBlZmZlY3RpdmUgd2hlbiB0aGVyZSBhcmUgbm8gY29tbW9uIHVuaXF1ZSBrZXlzIGJldHdlZW4gdGhlIGRhdGEgc291cmNlcyB5b3UgY2FuIHJlbHkgdXBvbiB3aGVuIGxpbmtpbmcgZGF0YSBzb3VyY2VzIHN1Y2ggYXMgYSB1bmlxdWUgaWRlbnRpZmllci4NCg0KIyMjIFBhaXIgYmxvY2tpbmcNCg0KWmFnYXQgYW5kIEZvZG9yJ3MgYXJlIGJvdGggY29tcGFuaWVzIHRoYXQgZ2F0aGVyIHJlc3RhdXJhbnQgcmV2aWV3cy4gVGhlIHphZ2F0IGFuZCBmb2RvcnMgZGF0YXNldHMgYm90aCBjb250YWluIGluZm9ybWF0aW9uIGFib3V0IHZhcmlvdXMgcmVzdGF1cmFudHMsIGluY2x1ZGluZyBhZGRyZXNzZXMsIHBob25lIG51bWJlcnMsIGFuZCBjdWlzaW5lIHR5cGVzLiBTb21lIHJlc3RhdXJhbnRzIGFwcGVhciBpbiBib3RoIGRhdGFzZXRzLCBidXQgZG9uJ3QgbmVjZXNzYXJpbHkgaGF2ZSB0aGUgc2FtZSBleGFjdCBuYW1lIG9yIHBob25lIG51bWJlciB3cml0dGVuIGRvd24uIFdlJ2xsIHdvcmsgdG93YXJkcyBmaWd1cmluZyBvdXQgd2hpY2ggcmVzdGF1cmFudHMgYXBwZWFyIGluIGJvdGggZGF0YXNldHMuDQoNClRoZSBmaXJzdCBzdGVwIHRvd2FyZHMgdGhpcyBnb2FsIGlzIHRvIGdlbmVyYXRlIHBhaXJzIG9mIHJlY29yZHMgc28gdGhhdCB5b3UgY2FuIGNvbXBhcmUgdGhlbS4gV2UnbGwgZmlyc3QgZ2VuZXJhdGUgYWxsIHBvc3NpYmxlIHBhaXJzLCBhbmQgdGhlbiB1c2UgeW91ciBuZXdseS1jbGVhbmVkIGNpdHkgY29sdW1uIGFzIGEgYmxvY2tpbmcgdmFyaWFibGUuDQpgYGB7cn0NCmZvZG9ycyA8LSByZWFkUkRTKCJmb2RvcnMucmRzIikNCmBgYA0KDQpgYGB7cn0NCiMgTG9hZCByZWNsaW4NCmxpYnJhcnkocmVjbGluKQ0KYGBgDQpgYGB7cn0NCiMgR2VuZXJhdGUgYWxsIHBvc3NpYmxlIHBhaXJzDQpwYWlyX2Jsb2NraW5nKHphZ2F0LCBmb2RvcnMpDQpgYGANCmBgYHtyfQ0KIyBHZW5lcmF0ZSBhbGwgcG9zc2libGUgcGFpcnMNCnBhaXJfYmxvY2tpbmcoemFnYXQsIGZvZG9ycywgYmxvY2tpbmdfdmFyID0gImNpdHkiKQ0KYGBgDQoNCkJ5IHVzaW5nIGNpdHkgYXMgYSBibG9ja2luZyB2YXJpYWJsZSwgeW91IHdlcmUgYWJsZSB0byByZWR1Y2UgdGhlIG51bWJlciBvZiBwYWlycyB5b3UnbGwgbmVlZCB0byBjb21wYXJlIGZyb20gMTY1LDIzMCBwYWlycyB0byA0MCw1MzIuDQoNCg0KIyMjIENvbXBhcmluZyBwYWlycw0KDQpOb3cgdGhhdCB3ZSd2ZSBnZW5lcmF0ZWQgdGhlIHBhaXJzIG9mIHJlc3RhdXJhbnRzLCBpdCdzIHRpbWUgdG8gY29tcGFyZSB0aGVtLiBXZSBjYW4gZWFzaWx5IGN1c3RvbWl6ZSBob3cgd2UgcGVyZm9ybSBvdXIgY29tcGFyaXNvbnMgdXNpbmcgdGhlIGJ5IGFuZCBkZWZhdWx0X2NvbXBhcmF0b3IgYXJndW1lbnRzLiBUaGVyZSdzIG5vIHJpZ2h0IGFuc3dlciBhcyB0byB3aGF0IGVhY2ggc2hvdWxkIGJlIHNldCB0bywgc28gd2UnbGwgdHJ5IGEgY291cGxlIG9wdGlvbnMgb3V0Lg0KDQpgYGB7cn0NCiMgR2VuZXJhdGUgcGFpcnMNCnBhaXJfYmxvY2tpbmcoemFnYXQsIGZvZG9ycywgYmxvY2tpbmdfdmFyID0gImNpdHkiKSAlPiUNCiAgIyBDb21wYXJlIHBhaXJzIGJ5IG5hbWUgdXNpbmcgbGNzKCkNCiAgY29tcGFyZV9wYWlycyhieSA9ICJuYW1lIiwNCiAgICAgIGRlZmF1bHRfY29tcGFyYXRvciA9IGxjcygpKQ0KYGBgDQpgYGB7cn0NCiMgR2VuZXJhdGUgcGFpcnMNCnBhaXJfYmxvY2tpbmcoemFnYXQsIGZvZG9ycywgYmxvY2tpbmdfdmFyID0gImNpdHkiKSAlPiUNCiAgIyBDb21wYXJlIHBhaXJzIGJ5IG5hbWUsIHBob25lLCBhZGRyDQogIGNvbXBhcmVfcGFpcnMoYnkgPSBjKCJuYW1lIiwgInBob25lIiwgImFkZHIiKSwNCiAgICAgIGRlZmF1bHRfY29tcGFyYXRvciA9IGphcm9fd2lua2xlcigpKQ0KYGBgDQpDaG9vc2luZyBhIGNvbXBhcmF0b3IgYW5kIHRoZSBjb2x1bW5zIHRvIGNvbXBhcmUgaXMgaGlnaGx5IGRhdGFzZXQtZGVwZW5kZW50LCBzbyBpdCdzIGJlc3QgdG8gdHJ5IG91dCBkaWZmZXJlbnQgY29tYmluYXRpb25zIHRvIHNlZSB3aGljaCB3b3JrcyBiZXN0IG9uIHRoZSBkYXRhc2V0IHlvdSdyZSB3b3JraW5nIHdpdGguDQoNCiMjIFNjb3JpbmcgYW5kIGxpbmtpbmcNCg0KUmVjb3JkIGxpbmthZ2UgcmVxdWlyZXMgYSBudW1iZXIgb2Ygc3RlcHMgdGhhdCBjYW4gYmUgZGlmZmljdWx0IHRvIGtlZXAgc3RyYWlnaHQuDQoNCi0gQ2xlYW4gdGhlIGRhdGFzZXRzDQotIEdlbmVyYXRlIHBhaXJzIG9mIHJlY29yZHMNCi0gQ29tcGFyZSBzZXBhcmF0ZSBjb2x1bW5zIG9mIGVhY2ggcGFpcg0KLSBTY29yZSBwYWlycyB1c2luZyBzdW1taW5nIG9yIHByb2JhYmlsaXR5DQotIFNlbGVjdCBwYWlycyB0aGF0IGFyZSBtYXRjaGVzIGJhc2VkIG9uIHRoZWlyIHNjb3JlDQotIExpbmsgdGhlIGRhdGFzZXRzIHRvZ2V0aGVyDQoNCldlJ3ZlIGNsZWFuZWQgdXAgdGhlIGNpdHkgY29sdW1uIG9mIHphZ2F0IHVzaW5nIHN0cmluZyBzaW1pbGFyaXR5LCBhcyB3ZWxsIGFzIGdlbmVyYXRlZCBhbmQgY29tcGFyZWQgcGFpcnMgb2YgcmVzdGF1cmFudHMgZnJvbSB6YWdhdCBhbmQgZm9kb3JzLiBUaGUgZW5kIGlzIG5lYXIgLSBhbGwgdGhhdCdzIGxlZnQgdG8gZG8gaXMgc2NvcmUgYW5kIHNlbGVjdCBwYWlycyBhbmQgbGluayB0aGUgZGF0YSB0b2dldGhlciwgYW5kIHdlJ2xsIGJlIGFibGUgdG8gYmVnaW4geW91ciBhbmFseXNpcyBpbiBubyB0aW1lIQ0KYGBge3J9DQojIENyZWF0ZSBwYWlycw0KcGFpcl9ibG9ja2luZyh6YWdhdCwgZm9kb3JzLCBibG9ja2luZ192YXIgPSAiY2l0eSIpICU+JQ0KICAjIENvbXBhcmUgcGFpcnMNCiAgY29tcGFyZV9wYWlycyhieSA9ICJuYW1lIiwgZGVmYXVsdF9jb21wYXJhdG9yID0gamFyb193aW5rbGVyKCkpICU+JQ0KICAjIFNjb3JlIHBhaXJzDQogIHNjb3JlX3Byb2JsaW5rKCkNCmBgYA0KYGBge3J9DQojIENyZWF0ZSBwYWlycw0KcGFpcl9ibG9ja2luZyh6YWdhdCwgZm9kb3JzLCBibG9ja2luZ192YXIgPSAiY2l0eSIpICU+JQ0KICAjIENvbXBhcmUgcGFpcnMNCiAgY29tcGFyZV9wYWlycyhieSA9ICJuYW1lIiwgZGVmYXVsdF9jb21wYXJhdG9yID0gamFyb193aW5rbGVyKCkpICU+JQ0KICAjIFNjb3JlIHBhaXJzDQogIHNjb3JlX3Byb2JsaW5rKCkgJT4lDQogICMgU2VsZWN0IHBhaXJzDQogIHNlbGVjdF9uX3RvX20oKQ0KYGBgDQpgYGB7cn0NCiMgQ3JlYXRlIHBhaXJzDQpwYWlyX2Jsb2NraW5nKHphZ2F0LCBmb2RvcnMsIGJsb2NraW5nX3ZhciA9ICJjaXR5IikgJT4lDQogICMgQ29tcGFyZSBwYWlycw0KICBjb21wYXJlX3BhaXJzKGJ5ID0gIm5hbWUiLCBkZWZhdWx0X2NvbXBhcmF0b3IgPSBqYXJvX3dpbmtsZXIoKSkgJT4lDQogICMgU2NvcmUgcGFpcnMNCiAgc2NvcmVfcHJvYmxpbmsoKSAlPiUNCiAgIyBTZWxlY3QgcGFpcnMNCiAgc2VsZWN0X25fdG9fbSgpICU+JQ0KICAjIExpbmsgZGF0YSANCiAgbGluaygpDQpgYGANCk5vdyB0aGF0IHlvdXIgdHdvIGRhdGFzZXRzIGFyZSBtZXJnZWQsIHlvdSBjYW4gdXNlIHRoZSBkYXRhIHRvIGZpZ3VyZSBvdXQgaWYgdGhlcmUgYXJlIGNlcnRhaW4gY2hhcmFjdGVyaXN0aWNzIHRoYXQgbWFrZSBhIHJlc3RhdXJhbnQgbW9yZSBsaWtlbHkgdG8gYmUgcmV2aWV3ZWQgYnkgWmFnYXQgb3IgRm9kb3Incy4NCg==