Chapter 3 Data Transformation with dplyr

You will learn how to transform your data with dplyr package.

Pre requisites

We will be working with data from the nycflights13 package, and use ggplot2 to help us understand the data. So we will need to load the library for nycflights13 and tidyverse.

There will be a conflict error message when you load tidyverse after dplyr. Dplyr overwrites some functions in base R. If you want to use the base version of these functions after loading dplyr, you will need to use thier full names: stats::filter() and stats::lag().

Explore Nycflights data

# you may need to install.packages("nycflights13")
library(nycflights13)
library(tidyverse)
# flights contain 336,776 records of flights that departed from NY in 2013
?flights
# to see the whole dataset use the function view()
View(flights)
Tibbles are data frames, but slightly tweaked to work better in the tidyverse. Each column has different abbreviation under them:
  • int - integer
  • dbl - doubles or real numbers
  • chr - character vector or strings
  • dttm - date time
  • lgl - logical
  • fctr - factors used by R to represent categorical values
  • Dplyr Basisc

    Five Key Dplr functions for data manipulation:
  • filter() to filter rows or data points
  • arrange() to sort reorder the data
  • mutate() to create new variables with functions of existing variables
  • summarize() to collapse many values down to a single summary
  • group_by() changes the scope of each function from operating on the entire dataset to operating on a group by group basis
  • All verbs work similarly.
    1. The first argument is a data frame
    2. The subsequent arguments describe what to do with the data frame, using the variable names (without quotes)
    3. The sesult is a new data frame.

    # filter rows
    filter(flights, month==1, day ==1)

    The underlying data is never modified. To save use assignment operator [ <- ]

    Comparisons

    use the == for equal, >,>=, <,<=!=

    Logical operators

  • & for AND
  • | for OR
  • ! for NOT
  • The following code will find all flights that departed in November or December

    filter(flights, month==11 | month==12)

    or use the x %in% y approach

    filter(flights, month %in% c(11,12))
    See also https://en.wikipedia.org/wiki/De_Morgan%27s_laws
    In propositional logic and boolean algebra, De Morgan’s laws[1][2][3] are a pair of transformation rules that are both valid rules of inference. They are named after Augustus De Morgan, a 19th-century British mathematician. The rules allow the expression of conjunctions and disjunctions purely in terms of each other via negation.

    The rules can be expressed in English as:
    the negation of a conjunction is the disjunction of the negations; and
    the negation of a disjunction is the conjunction of the negations;

    or
    the complement of the union of two sets is the same as the intersection of their complements; and
    the complement of the intersection of two sets is the same as the union of their complements.

    For example, if you were to find flights that weren’t delayed (on arrival or departure) by more than two hours, you could use either of hte following two filters:

    filter(flights, !(arr_delay>120 | dep_delay>120))

    or

    filter(flights, arr_delay<=120, dep_delay<=120)

    Examples

    filter(mtcars, cyl == 8)
    filter(mtcars, cyl < 6)

    Multiple criteria

    filter(mtcars, cyl < 6 & vs == 1)
    filter(mtcars, cyl < 6 | vs == 1)

    Multiple arguments are equivalent to and

    filter(mtcars, cyl < 6, vs == 1)
    There are also && and || but it will taught later on under “Conditional execution”

    Missing Values

    Missing values or “NAs” (not available) represent an unknown value so missing values are “contagious”. Almost any operation involving an unknown value will also be unkown.

    NA > 5
    [1] NA
    10 == NA
    [1] NA
    NA + 10
    [1] NA
    # but this one is confusing
    NA == NA
    [1] NA

    To test of NA, use is.na()

    x <-  NA
    y <-  NA
    is.na(x)
    [1] TRUE

    Filter() only includes rows where the condition is TRUE. It excludes both FALSE and NA. So if you want to preserve missing values, ask for them explicitly

    In this case, the NA is not listed.

    df <- tibble(x=c(1,NA,3))
    filter(df,x>1)

    In this case, the NA is listed.

    filter(df, is.na(x) | x>1)

    Exercises
    1. find all flights that: a. had an arrival delay of two hours or more:

    filter(flights, arr_delay>120)
    1. flew to Houston (IAH or HOU)
    filter(flights, dest %in% c("IAH","HOU"))
    1. Were operated by United, American or Delta
    filter(flights, carrier %in% c("UA","AA","DL"))
    1. Departed in summer
    filter(flights, month>6 & month<10)
    1. Arrived more than 2 hours late, but didn’t leave late
    filter(flights, arr_delay>120, dep_delay<2)
    1. Were delayed by at least an hour, but made up over 30 minutes in flight
    filter(flights, arr_delay<=90, dep_delay>120)
    1. Departed between midnight and 6am inclusive
    filter(flights, dep_time>=000, dep_time<=600)

    Use between() to simplify the code:

    filter(flights, between(dep_time,000,600))
    1. How many flights have a missing dep_time? What other variables are missing? What might these rows represent?
    # list missing dep_time
    filter(flights, is.na(dep_time))

    Answer: dep_delay, arr_time, arr_delay are all missing. This might represent cancelled flights.

    Now we can also sort in descending order using the Arrange() verb.

    arrange(filter(flights, is.na(dep_time)),desc(month))

    Arrange Rows with arrange()

    Arrange(data, arg1, arg2, arg3…) If you provide more than one column name, each additional column will be used to break ties in the values of preceding columns. You can also use desc(arg) to reorder a column in descending order.

    arrange(flights, year, month, day)
    arrange(flights, year, desc(month), day)

    Missing values are always sorted at the end. To sort missing values at the start, use is.na()

    arrange(flights, dep_time,is.na(dep_time))
    Note: Couldnt get NA to show up in the view.

    Select columns with select()

    Select() allows you to zoom in on a useful subset using operations based on the names of the variables.

    # show only year, month and day
    select(flights, year, month, day)

    So notice that there are only 3 columns now.

    # select all columns between year and day inclusive
    select(flights, year:day)
    # select all columns EXCEPT those from year to day 
    select(flights, -(year:day))

    Helper functions within select()

  • starts_with(“abc”)
  • ends_with(“abc”)
  • contains(“ijk”)
  • matches(“(.)\1”)
  • num_range(“x”,1:3) matches x1,x2,x3
  • see ?select for more details.

    Rename

    While you can use select() to rename variables, it is not useful because it drops all of the variables not explicitly mentioned. For this, use the rename() that keeps all the variables that aren’t explicitly mentioned.

    rename(flights, DepartureTime = dep_time)

    What happens when we use a variable mulitple times in a select call?

    select(flights, origin, dest, air_time, origin)
    select(flights, contains("TIME"))

    Add new variables with Mutate()

    Use the mutate function when you want to add new variables that are a function of existing columns.
    Mutate always adds new variables to the end of the dataset.

    flights_sml <-  select(flights, year:day, ends_with("delay"), distance, air_time)
    mutate(flights_sml, gain=arr_delay -dep_delay, speed= distance /air_time * 60)

    Note: gain and speed are the new columns generated by mutate()
    If you only want to keep the newly generated variables use transmut()

    transmute(flights, gain=arr_delay -dep_delay, speed= distance /air_time * 60)

    Useful Creation functions

    Modular arithemetic (%/% and %%)

    Integer Division (%/%) and remainder (%%) You can use this in the NYC Flightst

    library(tidyverse)
    library(nycflights13)
    transmute(flights, dep_time, hour=dep_time %/% 100, minute=dep_time %% 100)

    Logs log(),log2() , log10()

    Logarithms are an incredibly useful transformation for dealling with data that ranges across multiple orders of magnitude. They also convert multiplicative relationships to additive

    h3> Offsets lead() and Lag() allowyou to refer to leading or lagging values. This can be used to compute running differences x-lag(x) or find when values change (x!=lag(x)). They are most useful in conjunction with group_by()

    x<-1:10
    # display x values
    x
     [1]  1  2  3  4  5  6  7  8  9 10
    # display lag(x)
    lag(x)
     [1] NA  1  2  3  4  5  6  7  8  9
    # display lead(x)
    lead(x)
     [1]  2  3  4  5  6  7  8  9 10 NA

    Cummulative and rolling aggregates

    Functions for running sums, products, mins, and maxes: cumsum(), cumprod(), cummin(), cummax() and dplyr provides cummean() for cummulative means. Try also the RcppRoll package

    # print x again
    x
     [1]  1  2  3  4  5  6  7  8  9 10
    # what is the cummulative sum of x?
    cumsum(x)
     [1]  1  3  6 10 15 21 28 36 45 55
    # what is the cummulative mean of x?
    cummean(x)
     [1] 1.0 1.5 2.0 2.5 3.0 3.5 4.0 4.5 5.0 5.5

    Ranking

    Try min_rank() smallest values= smallest ranks

    # set up variable
    y <- c(1,2,2,NA,3,4)
    # display Y values
    y
    [1]  1  2  2 NA  3  4
    # display rank
    min_rank(y)
    [1]  1  2  2 NA  4  5
    min_rank(desc(y))
    [1]  5  3  3 NA  2  1

    If rank() doesnt do what you need, look at the variants row_number(), dense_rank(), percent_rank(), cume_dist(), and ntile().

    y 
    [1]  1  2  2 NA  3  4
    row_number(y)
    [1]  1  2  3 NA  4  5
    dense_rank(y)
    [1]  1  2  2 NA  3  4
    cume_dist(y)
    [1] 0.2 0.6 0.6  NA 0.8 1.0
    percent_rank(y)
    [1] 0.00 0.25 0.25   NA 0.75 1.00

    Grouped Summaries with Summarize()

    summarize() collapses a data frame to a single row.

    library(nycflights13)
    summarize(flights, delay=mean(dep_delay, na.rm=TRUE))

    Enhance summarize() with group_by to get summaries by group of data.

    library(nycflights13)
    by_day <- group_by(flights, year, month, day)
    summarize(by_day, delay=mean(dep_delay, na.rm=TRUE))

    Then we can combine multiple operations with the pipe
    Suppose we want to find the relationship between the distance and average delay for each location.

    # Old method without using Pipes
    library(nycflights13)
    by_dest <- group_by(flights,dest)
    delay <- summarize(by_dest, count=n(), dist=mean(distance, na.rm=TRUE), delay=mean(arr_delay, na.rm = TRUE))
    delay <- filter(delay, count>20, dest !="HNL")
    delay
    # let us plot the values to see the correlation
    ggplot(data=delay, mapping=aes(x=dist, y=delay))+
      geom_point(aes(size=count), aplpha=1/3)+
      geom_smooth(se=FALSE)
    Ignoring unknown parameters: aplpha

    Now, let us see how Pipe (%>%) will make the code more efficient:

    delays <- flights %>% 
      group_by(dest) %>% 
      summarize(count=n(),  dist=mean(distance, na.rm=TRUE),  delay=mean(arr_delay, na.rm=TRUE))%>% 
      filter(count>20, dest !="HNL")
    ggplot(data=delay, mapping=aes(x=dist, y=delay))+
      geom_point(aes(size=count), alpha=1/3)+
      geom_smooth(se=FALSE)

    A good way to think of Pipe (%>%) is by reading it as “then”. Behind the scenes, x %>% f(y) means f(x,y)
    x %>% f(y) %>% g(z) turns into g(f(x,y),z)
    Read left to right, top to bottom.

    Missing Values

    na.rm argument is use to take care of missing values.

    # see how many missing values there are
    flights %>% group_by(year,month,day) %>%
      summarize(mean=mean(dep_delay))

    Fortunately, all aggregate functions have the na.rm argument (NA remove) prior to computation

    flights %>% group_by(year,month,day) %>%
      summarize(mean=mean(dep_delay, na.rm =TRUE))

    Taking care of cancelled flights

    not_cancelled <-  flights %>%
      filter(!is.na(dep_delay), !is.na(arr_delay))
    not_cancelled %>%
      group_by(year,month, day) %>%
      summarize(mean=mean(dep_delay))
    # no need to use na.rm since all missing values have been filter out already

    Counts

    Its a good idea to include either a count n() or a count of nonmissing values sum(!is.na(x)) so that you know the sample size.

    For example this below shows an average delay of 5 hours!

    delays <- not_cancelled %>%
      group_by(tailnum) %>%
      summarize(delay=mean(arr_delay))
    ggplot(data=delays, mapping=aes(x=delay))+
      geom_freqpoly(binwidth=10)

    We can get insight if we draw a scatterplot of number of lights versus average delay:

    delays <- not_cancelled %>%
      group_by(tailnum) %>%
      summarize(delay=mean(arr_delay, na.rm=TRUE), n= n())
    ggplot(data=delays, mapping=aes(x=n, y=delay))+
      geom_point(alpha=1/10)

    There are wider variations when there are lesser flights than when there are a lot of flights. So get more details, let us filter out the wild patterns when n>25

    delays %>%
      filter(n>25) %>%
      ggplot(mapping=aes(x=n, y=delay))+
      geom_point(alpha=1/10)

    Look at avverage performance of baseball batters to numbe of times they’re at a bat. Using Lahman package to compute the batting average (number of hits/number of attempts) of every majore league baseball player.

    # you need to install the Lahman package first for this to work 
    batting <- as_tibble(Lahman::Batting)
    batters <-  batting %>%
      group_by(playerID) %>%
      summarize(
        ba=sum(H, na.rm=TRUE)/sum(AB, na.rm=TRUE),
        ab=sum(AB, na.rm=TRUE)
      )
    batters %>%
      filter(ab >100) %>%
      ggplot(mapping=aes(x=ab, y=ba))+
      geom_point()+
      geom_smooth(se=FALSE)

    NA

    The implication, if you naively sort on desc(ba) the pople with the best batting averages are clearly lucky…not skilled.

    batters %>%
      arrange(desc(ba))

    Useful Summary Functions

    Measures of location

    Median is a value where 50% of x is above it, and 50% is below it. Try combining aggregations with subsetting

    not_cancelled %>%
      group_by(year, month, day) %>%
      summarize(
        avg_delay1=mean(arr_delay),
        avg_delay2=mean(arr_delay[arr_delay>0])
      )

    measures of spread

    Mean squared deviation, or standard deviation or SD for short is the standard measure of spread. The interquartile range IQR() and median absoute deviation mad(x) are robust equivalents that may be more useful if you have outliers.

    # why is distance to some destinations more variable than to others?
    not_cancelled %>%
      group_by(dest) %>%
      summarise(distance_sd=sd(distance))%>%
      arrange(desc(distance_sd))

    Measures of rank min(x), quantile(x,0.25), max(x)

    Qunatiles are a generalization of the median. Quantile(x,0.25) will find a value of x that is greater than 25% of the values and less than the remaining 75%

    # when do the first and last flights leave each day?
    not_cancelled %>%
      group_by(year, month,day) %>%
      summarize(first=min(dep_time), last=max(dep_time))

    Measures of poistion first(x), nth(x,2) last(x)

    # find the first and last departure for each day
    not_cancelled %>%
      group_by(year, month,day) %>%
      summarize(first_dep=first(dep_time), last_dep=last(dep_time))

    These functions are complementary to filtering on ranks

    not_cancelled %>%
      group_by(year, month,day) %>%
      mutate(r=min_rank(desc(dep_time))) %>%
      filter(r %in% range(r))

    To count the number of distinct values

    not_cancelled %>%
      group_by(dest) %>%
      summarize(carriers=n_distinct(carrier)) %>%
      arrange(desc(carriers))

    You can use a weight variable. You can use this to count the total number of miles a plane flew.

    not_cancelled %>%
      count(tailnum,wt=distance)

    Counts and proportions of logical values

    When you want to count the number of trues (1) and false(0)

    not_cancelled %>%
      group_by(year, month, day) %>%
      summarize(n_early =sum(dep_time<500))

    Grouping Multiple Variables

    When you group by multiple variables , each summary peels off one level of the grouping. That makes it easy to progressively roll up a dataset.

    daily <- group_by(flights, year, month, day)
    (per_day <- summarize(daily, flights=n()))
    (per_month <-  summarize(per_day, flights=sum(flights)))
    # per year
    (per_year <- summarize(per_month, flights=sum(flights)))

    To remove groupings, use Ungroup()

    daily %>%
      ungroup() %>%
      summarize(flights=n())

    Grouped Mutates and Filters

    Grouping is most useful in conjunction with summarize(), but you can also do convenient operations with mutate() and filter()

    # find the worst members of each group
    flights_sml %>%
      group_by(year, month, day) %>%
      filter(rank(desc(arr_delay))<10)
    # find all groups bigger than a threshold
    popular_dests <- flights %>%
      group_by(dest) %>%
      filter(n() >365)
    # display
    popular_dests
    # standardie to compute per group metrics
    popular_dests <- flights %>%
       filter(arr_delay>0) %>%
      mutate(prop_delay=arr_delay /sum(arr_delay)) %>%
      select(year:day,dest,arr_delay, prop_delay)
    # display
    popular_dests
    LS0tDQp0aXRsZTogIlIgZm9yIERhdGEgU2NpZW5jZUNoYXB0ZXIgMyINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCjxoMT4gQ2hhcHRlciAzIERhdGEgVHJhbnNmb3JtYXRpb24gd2l0aCBkcGx5ciA8L2gxPg0KWW91IHdpbGwgbGVhcm4gaG93IHRvIHRyYW5zZm9ybSB5b3VyIGRhdGEgd2l0aCBkcGx5ciBwYWNrYWdlLg0KPGgyPiBQcmUgcmVxdWlzaXRlcyA8L2gyPg0KV2Ugd2lsbCBiZSB3b3JraW5nIHdpdGggZGF0YSBmcm9tIHRoZSBueWNmbGlnaHRzMTMgcGFja2FnZSwgYW5kIHVzZSBnZ3Bsb3QyIHRvIGhlbHAgdXMgdW5kZXJzdGFuZCB0aGUgZGF0YS4gU28gd2Ugd2lsbCBuZWVkIHRvIGxvYWQgdGhlIGxpYnJhcnkgZm9yIG55Y2ZsaWdodHMxMyBhbmQgdGlkeXZlcnNlLjwvcD4NCg0KVGhlcmUgd2lsbCBiZSBhIGNvbmZsaWN0IGVycm9yIG1lc3NhZ2Ugd2hlbiB5b3UgbG9hZCB0aWR5dmVyc2UgYWZ0ZXIgZHBseXIuIERwbHlyIG92ZXJ3cml0ZXMgc29tZSBmdW5jdGlvbnMgaW4gYmFzZSBSLiBJZiB5b3Ugd2FudCB0byB1c2UgdGhlIGJhc2UgdmVyc2lvbiBvZiB0aGVzZSBmdW5jdGlvbnMgYWZ0ZXIgbG9hZGluZyBkcGx5ciwgeW91IHdpbGwgbmVlZCB0byB1c2UgdGhpZXIgZnVsbCBuYW1lczogc3RhdHM6OmZpbHRlcigpIGFuZCBzdGF0czo6bGFnKCkuIDwvcD4NCg0KPGgyPiBFeHBsb3JlIE55Y2ZsaWdodHMgZGF0YSA8L2gyPg0KDQpgYGB7cn0NCiMgeW91IG1heSBuZWVkIHRvIGluc3RhbGwucGFja2FnZXMoIm55Y2ZsaWdodHMxMyIpDQoNCmxpYnJhcnkobnljZmxpZ2h0czEzKQ0KbGlicmFyeSh0aWR5dmVyc2UpDQoNCiMgZmxpZ2h0cyBjb250YWluIDMzNiw3NzYgcmVjb3JkcyBvZiBmbGlnaHRzIHRoYXQgZGVwYXJ0ZWQgZnJvbSBOWSBpbiAyMDEzDQo/ZmxpZ2h0cw0KDQojIHRvIHNlZSB0aGUgd2hvbGUgZGF0YXNldCB1c2UgdGhlIGZ1bmN0aW9uIHZpZXcoKQ0KVmlldyhmbGlnaHRzKQ0KIyBpdCBwcmludHMgYXMgYSB0aWJibGUNCmBgYA0KDQpUaWJibGVzIGFyZSBkYXRhIGZyYW1lcywgYnV0IHNsaWdodGx5IHR3ZWFrZWQgdG8gd29yayBiZXR0ZXIgaW4gdGhlIHRpZHl2ZXJzZS4gDQpFYWNoIGNvbHVtbiBoYXMgZGlmZmVyZW50IGFiYnJldmlhdGlvbiB1bmRlciB0aGVtOiA8L2JyPg0KPGxpPmludCAtIGludGVnZXIgPC9saT4NCjxsaT5kYmwgLSBkb3VibGVzIG9yIHJlYWwgbnVtYmVycyA8L2xpPg0KPGxpPmNociAtIGNoYXJhY3RlciB2ZWN0b3Igb3Igc3RyaW5ncyA8L2xpPg0KPGxpPmR0dG0gLSBkYXRlIHRpbWU8L2xpPg0KPGxpPmxnbCAtIGxvZ2ljYWwgPC9saT4NCjxsaT5mY3RyIC0gZmFjdG9ycyB1c2VkIGJ5IFIgdG8gcmVwcmVzZW50IGNhdGVnb3JpY2FsIHZhbHVlcyA8L2xpPg0KDQo8aDI+IERwbHlyIEJhc2lzYyA8L2gyPg0KDQpGaXZlIEtleSBEcGxyIGZ1bmN0aW9ucyAgZm9yIGRhdGEgbWFuaXB1bGF0aW9uOiA8L2JyPg0KPGxpPiBmaWx0ZXIoKSB0byBmaWx0ZXIgcm93cyBvciBkYXRhIHBvaW50czwvbGk+DQo8bGk+IGFycmFuZ2UoKSB0byBzb3J0IHJlb3JkZXIgdGhlIGRhdGE8L2xpPg0KPGxpPiBtdXRhdGUoKSB0byBjcmVhdGUgbmV3IHZhcmlhYmxlcyB3aXRoIGZ1bmN0aW9ucyBvZiBleGlzdGluZyB2YXJpYWJsZXM8L2xpPg0KPGxpPiBzdW1tYXJpemUoKSB0byBjb2xsYXBzZSBtYW55IHZhbHVlcyBkb3duIHRvIGEgc2luZ2xlIHN1bW1hcnkgPC9saT4NCjxsaT4gZ3JvdXBfYnkoKSBjaGFuZ2VzIHRoZSBzY29wZSBvZiBlYWNoIGZ1bmN0aW9uIGZyb20gb3BlcmF0aW5nIG9uIHRoZSBlbnRpcmUgZGF0YXNldCB0byBvcGVyYXRpbmcgb24gYSBncm91cCBieSBncm91cCBiYXNpczwvbGk+IDwvcD4NCg0KQWxsIHZlcmJzIHdvcmsgc2ltaWxhcmx5LiA8L2JyPg0KMS4gVGhlIGZpcnN0IGFyZ3VtZW50IGlzIGEgZGF0YSBmcmFtZTwvYnI+DQoyLiBUaGUgc3Vic2VxdWVudCBhcmd1bWVudHMgZGVzY3JpYmUgd2hhdCB0byBkbyB3aXRoIHRoZSBkYXRhIGZyYW1lLCB1c2luZyB0aGUgdmFyaWFibGUgbmFtZXMgKHdpdGhvdXQgcXVvdGVzKTwvYnI+DQozLiBUaGUgc2VzdWx0IGlzIGEgbmV3IGRhdGEgZnJhbWUuIDwvcD4NCg0KYGBge3J9DQojIGZpbHRlciByb3dzDQoNCmZpbHRlcihmbGlnaHRzLCBtb250aD09MSwgZGF5ID09MSkNCg0KYGBgDQoNClRoZSB1bmRlcmx5aW5nIGRhdGEgaXMgbmV2ZXIgbW9kaWZpZWQuIFRvIHNhdmUgdXNlIGFzc2lnbm1lbnQgb3BlcmF0b3IgWyA8LSBdDQoNCjxoMj4gQ29tcGFyaXNvbnMgPC9oMj4NCnVzZSB0aGUgPT0gZm9yIGVxdWFsLCA+LD49LCA8LDw9IT0NCjwvYnI+DQoNCg0KPGgyPiBMb2dpY2FsIG9wZXJhdG9ycyA8L2gyPg0KPGxpPiYgZm9yIEFORCA8L2xpPg0KPGxpPnwgZm9yIE9SIDwvbGk+DQo8bGk+ISBmb3IgTk9UIDwvbGk+DQo8L3A+DQoNClRoZSBmb2xsb3dpbmcgY29kZSB3aWxsIGZpbmQgYWxsIGZsaWdodHMgdGhhdCBkZXBhcnRlZCBpbiBOb3ZlbWJlciBvciBEZWNlbWJlcg0KDQpgYGB7cn0NCmZpbHRlcihmbGlnaHRzLCBtb250aD09MTEgfCBtb250aD09MTIpDQpgYGANCg0Kb3IgdXNlIHRoZSB4ICVpbiUgeSBhcHByb2FjaA0KDQpgYGB7cn0NCmZpbHRlcihmbGlnaHRzLCBtb250aCAlaW4lIGMoMTEsMTIpKQ0KYGBgDQoNClNlZSBhbHNvIGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0RlX01vcmdhbiUyN3NfbGF3cyA8L2JyPg0KSW4gcHJvcG9zaXRpb25hbCBsb2dpYyBhbmQgYm9vbGVhbiBhbGdlYnJhLCBEZSBNb3JnYW4ncyBsYXdzWzFdWzJdWzNdIGFyZSBhIHBhaXIgb2YgdHJhbnNmb3JtYXRpb24gcnVsZXMgdGhhdCBhcmUgYm90aCB2YWxpZCBydWxlcyBvZiBpbmZlcmVuY2UuIFRoZXkgYXJlIG5hbWVkIGFmdGVyIEF1Z3VzdHVzIERlIE1vcmdhbiwgYSAxOXRoLWNlbnR1cnkgQnJpdGlzaCBtYXRoZW1hdGljaWFuLiBUaGUgcnVsZXMgYWxsb3cgdGhlIGV4cHJlc3Npb24gb2YgY29uanVuY3Rpb25zIGFuZCBkaXNqdW5jdGlvbnMgcHVyZWx5IGluIHRlcm1zIG9mIGVhY2ggb3RoZXIgdmlhIG5lZ2F0aW9uLjwvcD4NCg0KVGhlIHJ1bGVzIGNhbiBiZSBleHByZXNzZWQgaW4gRW5nbGlzaCBhczo8L2JyPg0KdGhlIG5lZ2F0aW9uIG9mIGEgY29uanVuY3Rpb24gaXMgdGhlIGRpc2p1bmN0aW9uIG9mIHRoZSBuZWdhdGlvbnM7IGFuZDwvYnI+DQp0aGUgbmVnYXRpb24gb2YgYSBkaXNqdW5jdGlvbiBpcyB0aGUgY29uanVuY3Rpb24gb2YgdGhlIG5lZ2F0aW9uczs8L3A+DQpvcjwvYnI+DQp0aGUgY29tcGxlbWVudCBvZiB0aGUgdW5pb24gb2YgdHdvIHNldHMgaXMgdGhlIHNhbWUgYXMgdGhlIGludGVyc2VjdGlvbiBvZiB0aGVpciBjb21wbGVtZW50czsgYW5kPC9icj4NCnRoZSBjb21wbGVtZW50IG9mIHRoZSBpbnRlcnNlY3Rpb24gb2YgdHdvIHNldHMgaXMgdGhlIHNhbWUgYXMgdGhlIHVuaW9uIG9mIHRoZWlyIGNvbXBsZW1lbnRzLjwvYnI+DQoNCjxpbWcgc3JjPSJodHRwczovL3VwbG9hZC53aWtpbWVkaWEub3JnL3dpa2lwZWRpYS9jb21tb25zL3RodW1iLzAvMDYvRGVtb3JnYW5sYXdzLnN2Zy8zMzBweC1EZW1vcmdhbmxhd3Muc3ZnLnBuZyIgPg0KDQpGb3IgZXhhbXBsZSwgaWYgeW91IHdlcmUgdG8gZmluZCBmbGlnaHRzIHRoYXQgd2VyZW4ndCBkZWxheWVkIChvbiBhcnJpdmFsIG9yIGRlcGFydHVyZSkgYnkgbW9yZSB0aGFuIHR3byBob3VycywgeW91IGNvdWxkIHVzZSBlaXRoZXIgb2YgaHRlIGZvbGxvd2luZyB0d28gZmlsdGVyczoNCg0KYGBge3J9DQpmaWx0ZXIoZmxpZ2h0cywgIShhcnJfZGVsYXk+MTIwIHwgZGVwX2RlbGF5PjEyMCkpDQpgYGANCg0Kb3INCg0KYGBge3J9DQpmaWx0ZXIoZmxpZ2h0cywgYXJyX2RlbGF5PD0xMjAsIGRlcF9kZWxheTw9MTIwKQ0KYGBgDQpFeGFtcGxlcyA8L2JyPg0KDQpmaWx0ZXIobXRjYXJzLCBjeWwgPT0gOCk8L2JyPg0KZmlsdGVyKG10Y2FycywgY3lsIDwgNik8L2JyPg0KDQojIE11bHRpcGxlIGNyaXRlcmlhPC9icj4NCmZpbHRlcihtdGNhcnMsIGN5bCA8IDYgJiB2cyA9PSAxKTwvYnI+DQpmaWx0ZXIobXRjYXJzLCBjeWwgPCA2IHwgdnMgPT0gMSk8L2JyPg0KDQojIE11bHRpcGxlIGFyZ3VtZW50cyBhcmUgZXF1aXZhbGVudCB0byBhbmQ8L2JyPg0KZmlsdGVyKG10Y2FycywgY3lsIDwgNiwgdnMgPT0gMSk8L2JyPg0KVGhlcmUgYXJlIGFsc28gJiYgYW5kIHx8IGJ1dCBpdCB3aWxsIHRhdWdodCBsYXRlciBvbiB1bmRlciAiQ29uZGl0aW9uYWwgZXhlY3V0aW9uIg0KPC9wPg0KDQoNCjxoMj4gTWlzc2luZyBWYWx1ZXMgPC9oMj4NCk1pc3NpbmcgdmFsdWVzIG9yICJOQXMiIChub3QgYXZhaWxhYmxlKSByZXByZXNlbnQgYW4gdW5rbm93biB2YWx1ZSBzbyBtaXNzaW5nIHZhbHVlcyBhcmUgImNvbnRhZ2lvdXMiLiBBbG1vc3QgYW55IG9wZXJhdGlvbiBpbnZvbHZpbmcgYW4gdW5rbm93biB2YWx1ZSB3aWxsIGFsc28gYmUgdW5rb3duLg0KDQpgYGB7cn0NCk5BID4gNQ0KMTAgPT0gTkENCk5BICsgMTANCg0KIyBidXQgdGhpcyBvbmUgaXMgY29uZnVzaW5nDQpOQSA9PSBOQQ0KDQoNCmBgYA0KDQpUbyB0ZXN0IG9mIE5BLCB1c2UgaXMubmEoKQ0KDQpgYGB7cn0NCnggPC0gIE5BDQp5IDwtICBOQQ0KDQppcy5uYSh4KQ0KDQoNCmBgYA0KDQpGaWx0ZXIoKSBvbmx5IGluY2x1ZGVzIHJvd3Mgd2hlcmUgdGhlIGNvbmRpdGlvbiBpcyBUUlVFLiBJdCBleGNsdWRlcyBib3RoIEZBTFNFIGFuZCBOQS4gU28gaWYgeW91IHdhbnQgdG8gcHJlc2VydmUgbWlzc2luZyB2YWx1ZXMsIGFzayBmb3IgdGhlbSBleHBsaWNpdGx5DQoNCkluIHRoaXMgY2FzZSwgdGhlIE5BIGlzIG5vdCBsaXN0ZWQuDQoNCmBgYHtyfQ0KZGYgPC0gdGliYmxlKHg9YygxLE5BLDMpKQ0KZmlsdGVyKGRmLHg+MSkNCmBgYA0KDQoNCkluIHRoaXMgY2FzZSwgdGhlIE5BIGlzIGxpc3RlZC4NCg0KYGBge3J9DQpmaWx0ZXIoZGYsIGlzLm5hKHgpIHwgeD4xKQ0KYGBgDQoNCkV4ZXJjaXNlcyA8L2JyPg0KMS4gZmluZCBhbGwgZmxpZ2h0cyB0aGF0Og0KYS4gaGFkIGFuIGFycml2YWwgZGVsYXkgb2YgdHdvIGhvdXJzIG9yIG1vcmU6DQoNCmBgYHtyfQ0KDQpmaWx0ZXIoZmxpZ2h0cywgYXJyX2RlbGF5PjEyMCkNCg0KYGBgDQoNCmIuIGZsZXcgdG8gSG91c3RvbiAoSUFIIG9yIEhPVSkNCg0KYGBge3J9DQoNCmZpbHRlcihmbGlnaHRzLCBkZXN0ICVpbiUgYygiSUFIIiwiSE9VIikpDQoNCmBgYA0KDQpjLiBXZXJlIG9wZXJhdGVkIGJ5IFVuaXRlZCwgQW1lcmljYW4gb3IgRGVsdGENCmBgYHtyfQ0KZmlsdGVyKGZsaWdodHMsIGNhcnJpZXIgJWluJSBjKCJVQSIsIkFBIiwiREwiKSkNCmBgYA0KDQpkLiBEZXBhcnRlZCBpbiBzdW1tZXINCg0KYGBge3J9DQpmaWx0ZXIoZmxpZ2h0cywgbW9udGg+NiAmIG1vbnRoPDEwKQ0KYGBgDQoNCmUuIEFycml2ZWQgbW9yZSB0aGFuIDIgaG91cnMgbGF0ZSwgYnV0IGRpZG4ndCBsZWF2ZSBsYXRlDQoNCmBgYHtyfQ0KZmlsdGVyKGZsaWdodHMsIGFycl9kZWxheT4xMjAsIGRlcF9kZWxheTwyKQ0KYGBgDQoNCmYuIFdlcmUgZGVsYXllZCBieSBhdCBsZWFzdCBhbiBob3VyLCBidXQgbWFkZSB1cCBvdmVyIDMwIG1pbnV0ZXMgaW4gZmxpZ2h0DQoNCmBgYHtyfQ0KZmlsdGVyKGZsaWdodHMsIGFycl9kZWxheTw9OTAsIGRlcF9kZWxheT4xMjApDQpgYGANCg0KZy4gRGVwYXJ0ZWQgYmV0d2VlbiBtaWRuaWdodCBhbmQgNmFtIGluY2x1c2l2ZQ0KYGBge3J9DQpmaWx0ZXIoZmxpZ2h0cywgZGVwX3RpbWU+PTAwMCwgZGVwX3RpbWU8PTYwMCkNCmBgYA0KDQpVc2UgPGI+YmV0d2VlbigpPC9iPiB0byBzaW1wbGlmeSB0aGUgY29kZToNCmBgYHtyfQ0KZmlsdGVyKGZsaWdodHMsIGJldHdlZW4oZGVwX3RpbWUsMDAwLDYwMCkpDQpgYGANCg0KMy4gSG93IG1hbnkgZmxpZ2h0cyBoYXZlIGEgbWlzc2luZyBkZXBfdGltZT8gV2hhdCBvdGhlciB2YXJpYWJsZXMgYXJlIG1pc3Npbmc/IFdoYXQgbWlnaHQgdGhlc2Ugcm93cyByZXByZXNlbnQ/DQoNCmBgYHtyfQ0KIyBsaXN0IG1pc3NpbmcgZGVwX3RpbWUNCmZpbHRlcihmbGlnaHRzLCBpcy5uYShkZXBfdGltZSkpDQpgYGANCg0KQW5zd2VyOiBkZXBfZGVsYXksIGFycl90aW1lLCBhcnJfZGVsYXkgYXJlIGFsbCBtaXNzaW5nLiBUaGlzIG1pZ2h0IHJlcHJlc2VudCBjYW5jZWxsZWQgZmxpZ2h0cy4NCg0KTm93IHdlIGNhbiBhbHNvIHNvcnQgaW4gZGVzY2VuZGluZyBvcmRlciB1c2luZyB0aGUgQXJyYW5nZSgpIHZlcmIuDQoNCmBgYHtyfQ0KYXJyYW5nZShmaWx0ZXIoZmxpZ2h0cywgaXMubmEoZGVwX3RpbWUpKSxkZXNjKG1vbnRoKSkNCmBgYA0KDQoNCjxoMj4gQXJyYW5nZSBSb3dzIHdpdGggYXJyYW5nZSgpIDwvaDI+DQpBcnJhbmdlKGRhdGEsIGFyZzEsIGFyZzIsIGFyZzMuLi4pDQpJZiB5b3UgcHJvdmlkZSBtb3JlIHRoYW4gb25lIGNvbHVtbiBuYW1lLCBlYWNoIGFkZGl0aW9uYWwgY29sdW1uIHdpbGwgYmUgdXNlZCB0byBicmVhayB0aWVzIGluIHRoZSB2YWx1ZXMgb2YgcHJlY2VkaW5nIGNvbHVtbnMuIFlvdSBjYW4gYWxzbyB1c2UgZGVzYyhhcmcpIHRvIHJlb3JkZXIgYSBjb2x1bW4gaW4gZGVzY2VuZGluZyBvcmRlci4NCg0KYGBge3J9DQphcnJhbmdlKGZsaWdodHMsIHllYXIsIG1vbnRoLCBkYXkpDQpgYGANCg0KYGBge3J9DQphcnJhbmdlKGZsaWdodHMsIHllYXIsIGRlc2MobW9udGgpLCBkYXkpDQpgYGANCk1pc3NpbmcgdmFsdWVzIGFyZSBhbHdheXMgc29ydGVkIGF0IHRoZSBlbmQuIA0KVG8gc29ydCBtaXNzaW5nIHZhbHVlcyBhdCB0aGUgc3RhcnQsIHVzZSBpcy5uYSgpDQoNCmBgYHtyfQ0KYXJyYW5nZShmbGlnaHRzLCBkZXBfdGltZSxpcy5uYShkZXBfdGltZSkpDQpgYGANCg0KTm90ZTogQ291bGRudCBnZXQgTkEgdG8gc2hvdyB1cCBpbiB0aGUgdmlldy4NCjxoMj4gU2VsZWN0IGNvbHVtbnMgd2l0aCBzZWxlY3QoKSA8L2gyPg0KU2VsZWN0KCkgYWxsb3dzIHlvdSB0byB6b29tIGluIG9uIGEgdXNlZnVsIHN1YnNldCB1c2luZyBvcGVyYXRpb25zIGJhc2VkIG9uIHRoZSBuYW1lcyBvZiB0aGUgdmFyaWFibGVzLg0KDQpgYGB7cn0NCiMgc2hvdyBvbmx5IHllYXIsIG1vbnRoIGFuZCBkYXkNCnNlbGVjdChmbGlnaHRzLCB5ZWFyLCBtb250aCwgZGF5KQ0KYGBgDQoNClNvIG5vdGljZSB0aGF0IHRoZXJlIGFyZSBvbmx5IDMgY29sdW1ucyBub3cuIA0KDQpgYGB7cn0NCiMgc2VsZWN0IGFsbCBjb2x1bW5zIGJldHdlZW4geWVhciBhbmQgZGF5IGluY2x1c2l2ZQ0Kc2VsZWN0KGZsaWdodHMsIHllYXI6ZGF5KQ0KYGBgDQoNCmBgYHtyfQ0KIyBzZWxlY3QgYWxsIGNvbHVtbnMgRVhDRVBUIHRob3NlIGZyb20geWVhciB0byBkYXkgDQpzZWxlY3QoZmxpZ2h0cywgLSh5ZWFyOmRheSkpDQpgYGANCg0KPGgzPiBIZWxwZXIgZnVuY3Rpb25zIHdpdGhpbiBzZWxlY3QoKSA8L2gzPg0KPGxpPnN0YXJ0c193aXRoKCJhYmMiKTwvbGk+DQo8bGk+ZW5kc193aXRoKCJhYmMiKTwvbGk+DQo8bGk+Y29udGFpbnMoImlqayIpIDwvbGk+DQo8bGk+bWF0Y2hlcygiKC4pXFwxIikgPC9saT4NCjxsaT5udW1fcmFuZ2UoIngiLDE6MykgbWF0Y2hlcyB4MSx4Mix4MyAgPC9saT4gPC9wPg0KDQpzZWUgP3NlbGVjdCBmb3IgbW9yZSBkZXRhaWxzLiANCg0KPGgzPiBSZW5hbWUgPC9oMz4NCldoaWxlIHlvdSBjYW4gdXNlIHNlbGVjdCgpIHRvIHJlbmFtZSB2YXJpYWJsZXMsIGl0IGlzIG5vdCB1c2VmdWwgYmVjYXVzZSBpdCBkcm9wcyBhbGwgb2YgdGhlIHZhcmlhYmxlcyBub3QgZXhwbGljaXRseSBtZW50aW9uZWQuIEZvciB0aGlzLCB1c2UgdGhlIHJlbmFtZSgpIHRoYXQga2VlcHMgYWxsIHRoZSB2YXJpYWJsZXMgdGhhdCBhcmVuJ3QgZXhwbGljaXRseSBtZW50aW9uZWQuDQoNCmBgYHtyfQ0KcmVuYW1lKGZsaWdodHMsIERlcGFydHVyZVRpbWUgPSBkZXBfdGltZSkNCmBgYA0KDQoNCg0KV2hhdCBoYXBwZW5zIHdoZW4gd2UgdXNlIGEgdmFyaWFibGUgbXVsaXRwbGUgdGltZXMgaW4gYSBzZWxlY3QgY2FsbD8NCmBgYHtyfQ0Kc2VsZWN0KGZsaWdodHMsIG9yaWdpbiwgZGVzdCwgYWlyX3RpbWUsIG9yaWdpbikNCmBgYA0KDQpgYGB7cn0NCnNlbGVjdChmbGlnaHRzLCBjb250YWlucygiVElNRSIpKQ0KYGBgDQoNCjxoMj4gQWRkIG5ldyB2YXJpYWJsZXMgd2l0aCBNdXRhdGUoKSA8L2gyPg0KVXNlIHRoZSBtdXRhdGUgZnVuY3Rpb24gd2hlbiB5b3Ugd2FudCB0byBhZGQgbmV3IHZhcmlhYmxlcyB0aGF0IGFyZSBhIGZ1bmN0aW9uIG9mIGV4aXN0aW5nIGNvbHVtbnMuIDwvYnI+DQpNdXRhdGUgYWx3YXlzIGFkZHMgbmV3IHZhcmlhYmxlcyB0byB0aGUgZW5kIG9mIHRoZSBkYXRhc2V0Lg0KDQpgYGB7cn0NCmZsaWdodHNfc21sIDwtICBzZWxlY3QoZmxpZ2h0cywgeWVhcjpkYXksIGVuZHNfd2l0aCgiZGVsYXkiKSwgZGlzdGFuY2UsIGFpcl90aW1lKQ0KbXV0YXRlKGZsaWdodHNfc21sLCBnYWluPWFycl9kZWxheSAtZGVwX2RlbGF5LCBzcGVlZD0gZGlzdGFuY2UgL2Fpcl90aW1lICogNjApDQpgYGANCg0KTm90ZTogZ2FpbiBhbmQgc3BlZWQgYXJlIHRoZSBuZXcgY29sdW1ucyBnZW5lcmF0ZWQgYnkgbXV0YXRlKCkNCjwvYnI+DQpJZiB5b3Ugb25seSB3YW50IHRvIGtlZXAgdGhlIG5ld2x5IGdlbmVyYXRlZCB2YXJpYWJsZXMgdXNlIHRyYW5zbXV0KCkNCg0KYGBge3J9DQp0cmFuc211dGUoZmxpZ2h0cywgZ2Fpbj1hcnJfZGVsYXkgLWRlcF9kZWxheSwgc3BlZWQ9IGRpc3RhbmNlIC9haXJfdGltZSAqIDYwKQ0KYGBgDQoNCg0KPGgyPiBVc2VmdWwgQ3JlYXRpb24gZnVuY3Rpb25zIDwvaDI+DQoNCjxoMz5Nb2R1bGFyIGFyaXRoZW1ldGljICglLyUgYW5kICUlKSA8L2gzPg0KSW50ZWdlciBEaXZpc2lvbiAoJS8lKSBhbmQgcmVtYWluZGVyICglJSkgWW91IGNhbiB1c2UgdGhpcyBpbiB0aGUgTllDIEZsaWdodHN0IA0KDQpgYGB7cn0NCiMgY29udmVydCBkZXBfdGltZSBpdG8gaG91cnMgYW5kIG1pbnV0ZXMNCmxpYnJhcnkodGlkeXZlcnNlKQ0KbGlicmFyeShueWNmbGlnaHRzMTMpDQp0cmFuc211dGUoZmxpZ2h0cywgZGVwX3RpbWUsIGhvdXI9ZGVwX3RpbWUgJS8lIDEwMCwgbWludXRlPWRlcF90aW1lICUlIDEwMCkNCg0KYGBgDQo8aDM+IExvZ3MgbG9nKCksbG9nMigpICwgbG9nMTAoKTwvaDM+DQpMb2dhcml0aG1zIGFyZSBhbiBpbmNyZWRpYmx5IHVzZWZ1bCB0cmFuc2Zvcm1hdGlvbiBmb3IgZGVhbGxpbmcgd2l0aCBkYXRhIHRoYXQgcmFuZ2VzIGFjcm9zcyBtdWx0aXBsZSBvcmRlcnMgb2YgbWFnbml0dWRlLiBUaGV5IGFsc28gY29udmVydCBtdWx0aXBsaWNhdGl2ZSByZWxhdGlvbnNoaXBzIHRvIGFkZGl0aXZlIDwvUD4NCg0KPmgzPiBPZmZzZXRzIDwvaDM+DQpsZWFkKCkgYW5kIExhZygpIGFsbG93eW91IHRvIHJlZmVyIHRvIGxlYWRpbmcgb3IgbGFnZ2luZyB2YWx1ZXMuIFRoaXMgY2FuIGJlIHVzZWQgdG8gY29tcHV0ZSBydW5uaW5nIGRpZmZlcmVuY2VzICB4LWxhZyh4KSBvciBmaW5kIHdoZW4gdmFsdWVzIGNoYW5nZSAoeCE9bGFnKHgpKS4gIFRoZXkgYXJlIG1vc3QgdXNlZnVsIGluIGNvbmp1bmN0aW9uIHdpdGggZ3JvdXBfYnkoKSANCjwvcD4NCg0KYGBge3J9DQp4PC0xOjEwDQojIGRpc3BsYXkgeCB2YWx1ZXMNCngNCiMgZGlzcGxheSBsYWcoeCkNCmxhZyh4KQ0KIyBkaXNwbGF5IGxlYWQoeCkNCmxlYWQoeCkNCg0KYGBgDQoNCjxoMz4gQ3VtbXVsYXRpdmUgYW5kIHJvbGxpbmcgYWdncmVnYXRlcyA8L2gzPg0KRnVuY3Rpb25zIGZvciBydW5uaW5nIHN1bXMsIHByb2R1Y3RzLCBtaW5zLCBhbmQgbWF4ZXM6IGN1bXN1bSgpLCBjdW1wcm9kKCksIGN1bW1pbigpLCBjdW1tYXgoKSBhbmQgZHBseXIgcHJvdmlkZXMgY3VtbWVhbigpIGZvciBjdW1tdWxhdGl2ZSBtZWFucy4gVHJ5IGFsc28gdGhlIFJjcHBSb2xsIHBhY2thZ2UNCg0KYGBge3J9DQojIHByaW50IHggYWdhaW4NCngNCiMgd2hhdCBpcyB0aGUgY3VtbXVsYXRpdmUgc3VtIG9mIHg/DQpjdW1zdW0oeCkNCiMgd2hhdCBpcyB0aGUgY3VtbXVsYXRpdmUgbWVhbiBvZiB4Pw0KY3VtbWVhbih4KQ0KYGBgDQoNCg0KPGgzPiBSYW5raW5nPC9oMz4NClRyeSBtaW5fcmFuaygpIHNtYWxsZXN0IHZhbHVlcz0gc21hbGxlc3QgcmFua3MNCg0KYGBge3J9DQoNCiMgc2V0IHVwIHZhcmlhYmxlDQp5IDwtIGMoMSwyLDIsTkEsMyw0KQ0KIyBkaXNwbGF5IFkgdmFsdWVzDQp5DQojIGRpc3BsYXkgcmFuaw0KbWluX3JhbmsoeSkNCm1pbl9yYW5rKGRlc2MoeSkpDQoNCmBgYA0KDQoNCklmIHJhbmsoKSBkb2VzbnQgZG8gd2hhdCB5b3UgbmVlZCwgbG9vayBhdCB0aGUgdmFyaWFudHMgcm93X251bWJlcigpLCBkZW5zZV9yYW5rKCksIHBlcmNlbnRfcmFuaygpLCBjdW1lX2Rpc3QoKSwgYW5kIG50aWxlKCkuDQoNCmBgYHtyfQ0KeSANCnJvd19udW1iZXIoeSkNCmRlbnNlX3JhbmsoeSkNCmN1bWVfZGlzdCh5KQ0KcGVyY2VudF9yYW5rKHkpDQpgYGANCg0KPGgyPiBHcm91cGVkIFN1bW1hcmllcyB3aXRoIFN1bW1hcml6ZSgpIDwvaDI+DQpzdW1tYXJpemUoKSBjb2xsYXBzZXMgYSBkYXRhIGZyYW1lIHRvIGEgc2luZ2xlIHJvdy4gDQoNCmBgYHtyfQ0KbGlicmFyeShueWNmbGlnaHRzMTMpDQpzdW1tYXJpemUoZmxpZ2h0cywgZGVsYXk9bWVhbihkZXBfZGVsYXksIG5hLnJtPVRSVUUpKQ0KYGBgDQoNCg0KRW5oYW5jZSBzdW1tYXJpemUoKSB3aXRoIGdyb3VwX2J5IHRvIGdldCBzdW1tYXJpZXMgYnkgZ3JvdXAgb2YgZGF0YS4NCg0KYGBge3J9DQpsaWJyYXJ5KG55Y2ZsaWdodHMxMykNCmJ5X2RheSA8LSBncm91cF9ieShmbGlnaHRzLCB5ZWFyLCBtb250aCwgZGF5KQ0Kc3VtbWFyaXplKGJ5X2RheSwgZGVsYXk9bWVhbihkZXBfZGVsYXksIG5hLnJtPVRSVUUpKQ0KYGBgDQoNClRoZW4gd2UgY2FuIGNvbWJpbmUgbXVsdGlwbGUgb3BlcmF0aW9ucyB3aXRoIHRoZSBwaXBlIDwvYnI+DQpTdXBwb3NlIHdlIHdhbnQgdG8gZmluZCB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gdGhlIGRpc3RhbmNlIGFuZCBhdmVyYWdlIGRlbGF5IGZvciBlYWNoIGxvY2F0aW9uLiANCg0KYGBge3J9DQojIE9sZCBtZXRob2Qgd2l0aG91dCB1c2luZyBQaXBlcw0KbGlicmFyeShueWNmbGlnaHRzMTMpDQpieV9kZXN0IDwtIGdyb3VwX2J5KGZsaWdodHMsZGVzdCkNCmRlbGF5IDwtIHN1bW1hcml6ZShieV9kZXN0LCBjb3VudD1uKCksIGRpc3Q9bWVhbihkaXN0YW5jZSwgbmEucm09VFJVRSksIGRlbGF5PW1lYW4oYXJyX2RlbGF5LCBuYS5ybSA9IFRSVUUpKQ0KIyBmaWx0ZXIgb3V0IEhOTCBob25vbHVsdSBhcyBpdCBpcyB0d2ljZSBhcyBmYXIgYXdheSBhcyB0aGUgbmV4dCBjbG9zZXN0IGFpcnBvcnQNCmRlbGF5IDwtIGZpbHRlcihkZWxheSwgY291bnQ+MjAsIGRlc3QgIT0iSE5MIikNCmRlbGF5DQpgYGANCg0KYGBge3J9DQojIGxldCB1cyBwbG90IHRoZSB2YWx1ZXMgdG8gc2VlIHRoZSBjb3JyZWxhdGlvbg0KZ2dwbG90KGRhdGE9ZGVsYXksIG1hcHBpbmc9YWVzKHg9ZGlzdCwgeT1kZWxheSkpKw0KICBnZW9tX3BvaW50KGFlcyhzaXplPWNvdW50KSwgYWxwaGE9MS8zKSsNCiAgZ2VvbV9zbW9vdGgoc2U9RkFMU0UpDQoNCmBgYA0KDQpOb3csIGxldCB1cyBzZWUgaG93IFBpcGUgPGI+KCU+JSk8L2I+IHdpbGwgbWFrZSB0aGUgY29kZSBtb3JlIGVmZmljaWVudDoNCg0KYGBge3J9DQpkZWxheXMgPC0gZmxpZ2h0cyAlPiUgDQogIGdyb3VwX2J5KGRlc3QpICU+JSANCiAgc3VtbWFyaXplKGNvdW50PW4oKSwgIGRpc3Q9bWVhbihkaXN0YW5jZSwgbmEucm09VFJVRSksICBkZWxheT1tZWFuKGFycl9kZWxheSwgbmEucm09VFJVRSkpJT4lIA0KICBmaWx0ZXIoY291bnQ+MjAsIGRlc3QgIT0iSE5MIikNCg0KZ2dwbG90KGRhdGE9ZGVsYXksIG1hcHBpbmc9YWVzKHg9ZGlzdCwgeT1kZWxheSkpKw0KICBnZW9tX3BvaW50KGFlcyhzaXplPWNvdW50KSwgYWxwaGE9MS8zKSsNCiAgZ2VvbV9zbW9vdGgoc2U9RkFMU0UpDQpgYGANCg0KQSBnb29kIHdheSB0byB0aGluayBvZiBQaXBlIDxiPiglPiUpPC9iPiBpcyBieSByZWFkaW5nIGl0IGFzICJ0aGVuIi4NCkJlaGluZCB0aGUgc2NlbmVzLCB4ICU+JSBmKHkpIG1lYW5zIGYoeCx5KSA8L2JyPg0KeCAlPiUgZih5KSAlPiUgZyh6KSB0dXJucyBpbnRvIGcoZih4LHkpLHopIDwvYnI+DQpSZWFkIGxlZnQgdG8gcmlnaHQsIHRvcCB0byBib3R0b20uIA0KDQo8aDI+IE1pc3NpbmcgVmFsdWVzIDwvaDI+DQpuYS5ybSBhcmd1bWVudCBpcyB1c2UgdG8gdGFrZSBjYXJlIG9mIG1pc3NpbmcgdmFsdWVzLiANCg0KYGBge3J9DQojIHNlZSBob3cgbWFueSBtaXNzaW5nIHZhbHVlcyB0aGVyZSBhcmUNCmZsaWdodHMgJT4lIGdyb3VwX2J5KHllYXIsbW9udGgsZGF5KSAlPiUNCiAgc3VtbWFyaXplKG1lYW49bWVhbihkZXBfZGVsYXkpKQ0KDQpgYGANCg0KDQpGb3J0dW5hdGVseSwgYWxsIGFnZ3JlZ2F0ZSBmdW5jdGlvbnMgaGF2ZSB0aGUgbmEucm0gYXJndW1lbnQgKE5BIHJlbW92ZSkgcHJpb3IgdG8gY29tcHV0YXRpb24NCg0KYGBge3J9DQpmbGlnaHRzICU+JSBncm91cF9ieSh5ZWFyLG1vbnRoLGRheSkgJT4lDQogIHN1bW1hcml6ZShtZWFuPW1lYW4oZGVwX2RlbGF5LCBuYS5ybSA9VFJVRSkpDQpgYGANCg0KVGFraW5nIGNhcmUgb2YgY2FuY2VsbGVkIGZsaWdodHMNCg0KYGBge3J9DQpub3RfY2FuY2VsbGVkIDwtICBmbGlnaHRzICU+JQ0KICBmaWx0ZXIoIWlzLm5hKGRlcF9kZWxheSksICFpcy5uYShhcnJfZGVsYXkpKQ0KDQpub3RfY2FuY2VsbGVkICU+JQ0KICBncm91cF9ieSh5ZWFyLG1vbnRoLCBkYXkpICU+JQ0KICBzdW1tYXJpemUobWVhbj1tZWFuKGRlcF9kZWxheSkpDQojIG5vIG5lZWQgdG8gdXNlIG5hLnJtIHNpbmNlIGFsbCBtaXNzaW5nIHZhbHVlcyBoYXZlIGJlZW4gZmlsdGVyIG91dCBhbHJlYWR5DQpgYGANCg0KPGgyPiBDb3VudHMgPC9oMj4NCkl0cyBhIGdvb2QgaWRlYSB0byBpbmNsdWRlIGVpdGhlciBhIGNvdW50IG4oKSBvciBhIGNvdW50IG9mIG5vbm1pc3NpbmcgdmFsdWVzIHN1bSghaXMubmEoeCkpIHNvIHRoYXQgeW91IGtub3cgdGhlIHNhbXBsZSBzaXplLiANCg0KRm9yIGV4YW1wbGUgdGhpcyBiZWxvdyBzaG93cyBhbiBhdmVyYWdlIGRlbGF5IG9mIDUgaG91cnMhDQoNCmBgYHtyfQ0KZGVsYXlzIDwtIG5vdF9jYW5jZWxsZWQgJT4lDQogIGdyb3VwX2J5KHRhaWxudW0pICU+JQ0KICBzdW1tYXJpemUoZGVsYXk9bWVhbihhcnJfZGVsYXkpKQ0KDQpnZ3Bsb3QoZGF0YT1kZWxheXMsIG1hcHBpbmc9YWVzKHg9ZGVsYXkpKSsNCiAgZ2VvbV9mcmVxcG9seShiaW53aWR0aD0xMCkNCmBgYA0KDQpXZSBjYW4gZ2V0IGluc2lnaHQgaWYgd2UgZHJhdyBhIHNjYXR0ZXJwbG90IG9mIG51bWJlciBvZiBsaWdodHMgdmVyc3VzIGF2ZXJhZ2UgZGVsYXk6DQoNCmBgYHtyfQ0KZGVsYXlzIDwtIG5vdF9jYW5jZWxsZWQgJT4lDQogIGdyb3VwX2J5KHRhaWxudW0pICU+JQ0KICBzdW1tYXJpemUoZGVsYXk9bWVhbihhcnJfZGVsYXksIG5hLnJtPVRSVUUpLCBuPSBuKCkpDQoNCmdncGxvdChkYXRhPWRlbGF5cywgbWFwcGluZz1hZXMoeD1uLCB5PWRlbGF5KSkrDQogIGdlb21fcG9pbnQoYWxwaGE9MS8xMCkNCmBgYA0KVGhlcmUgYXJlIHdpZGVyIHZhcmlhdGlvbnMgd2hlbiB0aGVyZSBhcmUgbGVzc2VyIGZsaWdodHMgdGhhbiB3aGVuIHRoZXJlIGFyZSBhIGxvdCBvZiBmbGlnaHRzLg0KU28gZ2V0IG1vcmUgZGV0YWlscywgbGV0IHVzIGZpbHRlciBvdXQgdGhlIHdpbGQgcGF0dGVybnMgd2hlbiBuPjI1DQoNCmBgYHtyfQ0KZGVsYXlzICU+JQ0KICBmaWx0ZXIobj4yNSkgJT4lDQogIGdncGxvdChtYXBwaW5nPWFlcyh4PW4sIHk9ZGVsYXkpKSsNCiAgZ2VvbV9wb2ludChhbHBoYT0xLzEwKQ0KDQpgYGANCg0KTG9vayBhdCBhdnZlcmFnZSBwZXJmb3JtYW5jZSBvZiBiYXNlYmFsbCBiYXR0ZXJzIHRvIG51bWJlIG9mIHRpbWVzIHRoZXkncmUgYXQgYSBiYXQuIFVzaW5nIExhaG1hbiBwYWNrYWdlIHRvIGNvbXB1dGUgdGhlIGJhdHRpbmcgYXZlcmFnZSAobnVtYmVyIG9mIGhpdHMvbnVtYmVyIG9mIGF0dGVtcHRzKSBvZiBldmVyeSBtYWpvcmUgbGVhZ3VlIGJhc2ViYWxsIHBsYXllci4NCg0KYGBge3J9DQojIHlvdSBuZWVkIHRvIGluc3RhbGwgdGhlIExhaG1hbiBwYWNrYWdlIGZpcnN0IGZvciB0aGlzIHRvIHdvcmsgDQpiYXR0aW5nIDwtIGFzX3RpYmJsZShMYWhtYW46OkJhdHRpbmcpDQoNCmJhdHRlcnMgPC0gIGJhdHRpbmcgJT4lDQogIGdyb3VwX2J5KHBsYXllcklEKSAlPiUNCiAgc3VtbWFyaXplKA0KICAgIGJhPXN1bShILCBuYS5ybT1UUlVFKS9zdW0oQUIsIG5hLnJtPVRSVUUpLA0KICAgIGFiPXN1bShBQiwgbmEucm09VFJVRSkNCiAgKQ0KDQpiYXR0ZXJzICU+JQ0KICBmaWx0ZXIoYWIgPjEwMCkgJT4lDQogIGdncGxvdChtYXBwaW5nPWFlcyh4PWFiLCB5PWJhKSkrDQogIGdlb21fcG9pbnQoKSsNCiAgZ2VvbV9zbW9vdGgoc2U9RkFMU0UpDQoNCiAgICANCmBgYA0KVGhlIGltcGxpY2F0aW9uLCBpZiB5b3UgbmFpdmVseSBzb3J0IG9uIGRlc2MoYmEpIHRoZSBwb3BsZSB3aXRoIHRoZSBiZXN0IGJhdHRpbmcgYXZlcmFnZXMgYXJlIGNsZWFybHkgbHVja3kuLi5ub3Qgc2tpbGxlZC4NCg0KDQpgYGB7cn0NCmJhdHRlcnMgJT4lDQogIGFycmFuZ2UoZGVzYyhiYSkpDQpgYGANCg0KPGgyPiBVc2VmdWwgU3VtbWFyeSBGdW5jdGlvbnMgPC9oMj4NCg0KDQoNCg0KDQoNCjxoMz4gTWVhc3VyZXMgb2YgbG9jYXRpb24gIDwvaDM+DQpNZWRpYW4gaXMgYSB2YWx1ZSB3aGVyZSA1MCUgb2YgeCBpcyBhYm92ZSBpdCwgYW5kIDUwJSBpcyBiZWxvdyBpdC4gDQpUcnkgY29tYmluaW5nIGFnZ3JlZ2F0aW9ucyB3aXRoIHN1YnNldHRpbmcNCg0KYGBge3J9DQpub3RfY2FuY2VsbGVkICU+JQ0KICBncm91cF9ieSh5ZWFyLCBtb250aCwgZGF5KSAlPiUNCiAgc3VtbWFyaXplKA0KICAgIGF2Z19kZWxheTE9bWVhbihhcnJfZGVsYXkpLA0KICAgIGF2Z19kZWxheTI9bWVhbihhcnJfZGVsYXlbYXJyX2RlbGF5PjBdKQ0KICApDQpgYGANCg0KPGgzPiBtZWFzdXJlcyBvZiBzcHJlYWQgPC9oMz4NCk1lYW4gc3F1YXJlZCBkZXZpYXRpb24sIG9yIHN0YW5kYXJkIGRldmlhdGlvbiBvciBTRCBmb3Igc2hvcnQgaXMgdGhlIHN0YW5kYXJkIG1lYXN1cmUgb2Ygc3ByZWFkLiBUaGUgaW50ZXJxdWFydGlsZSByYW5nZSBJUVIoKSBhbmQgbWVkaWFuIGFic291dGUgZGV2aWF0aW9uIG1hZCh4KSBhcmUgcm9idXN0IGVxdWl2YWxlbnRzIHRoYXQgbWF5IGJlIG1vcmUgdXNlZnVsIGlmIHlvdSBoYXZlIG91dGxpZXJzLg0KDQpgYGB7cn0NCiMgd2h5IGlzIGRpc3RhbmNlIHRvIHNvbWUgZGVzdGluYXRpb25zIG1vcmUgdmFyaWFibGUgdGhhbiB0byBvdGhlcnM/DQpub3RfY2FuY2VsbGVkICU+JQ0KICBncm91cF9ieShkZXN0KSAlPiUNCiAgc3VtbWFyaXNlKGRpc3RhbmNlX3NkPXNkKGRpc3RhbmNlKSklPiUNCiAgYXJyYW5nZShkZXNjKGRpc3RhbmNlX3NkKSkNCmBgYA0KDQo8aDM+IE1lYXN1cmVzIG9mIHJhbmsgbWluKHgpLCBxdWFudGlsZSh4LDAuMjUpLCBtYXgoeCkgPC9oMz4NClF1bmF0aWxlcyBhcmUgYSBnZW5lcmFsaXphdGlvbiBvZiB0aGUgbWVkaWFuLiBRdWFudGlsZSh4LDAuMjUpIHdpbGwgZmluZCBhIHZhbHVlIG9mIHggdGhhdCBpcyBncmVhdGVyIHRoYW4gMjUlIG9mIHRoZSB2YWx1ZXMgYW5kIGxlc3MgdGhhbiB0aGUgcmVtYWluaW5nIDc1JQ0KDQpgYGB7cn0NCiMgd2hlbiBkbyB0aGUgZmlyc3QgYW5kIGxhc3QgZmxpZ2h0cyBsZWF2ZSBlYWNoIGRheT8NCm5vdF9jYW5jZWxsZWQgJT4lDQogIGdyb3VwX2J5KHllYXIsIG1vbnRoLGRheSkgJT4lDQogIHN1bW1hcml6ZShmaXJzdD1taW4oZGVwX3RpbWUpLCBsYXN0PW1heChkZXBfdGltZSkpDQpgYGANCg0KPGgzPiBNZWFzdXJlcyBvZiBwb2lzdGlvbiBmaXJzdCh4KSwgbnRoKHgsMikgbGFzdCh4KSA8L2gzPg0KYGBge3J9DQojIGZpbmQgdGhlIGZpcnN0IGFuZCBsYXN0IGRlcGFydHVyZSBmb3IgZWFjaCBkYXkNCm5vdF9jYW5jZWxsZWQgJT4lDQogIGdyb3VwX2J5KHllYXIsIG1vbnRoLGRheSkgJT4lDQogIHN1bW1hcml6ZShmaXJzdF9kZXA9Zmlyc3QoZGVwX3RpbWUpLCBsYXN0X2RlcD1sYXN0KGRlcF90aW1lKSkNCmBgYA0KDQpUaGVzZSBmdW5jdGlvbnMgYXJlIGNvbXBsZW1lbnRhcnkgdG8gZmlsdGVyaW5nIG9uIHJhbmtzIA0KDQpgYGB7cn0NCm5vdF9jYW5jZWxsZWQgJT4lDQogIGdyb3VwX2J5KHllYXIsIG1vbnRoLGRheSkgJT4lDQogIG11dGF0ZShyPW1pbl9yYW5rKGRlc2MoZGVwX3RpbWUpKSkgJT4lDQogIGZpbHRlcihyICVpbiUgcmFuZ2UocikpDQoNCmBgYA0KDQpUbyBjb3VudCB0aGUgbnVtYmVyIG9mIGRpc3RpbmN0IHZhbHVlcw0KDQpgYGB7cn0NCm5vdF9jYW5jZWxsZWQgJT4lDQogIGdyb3VwX2J5KGRlc3QpICU+JQ0KICBzdW1tYXJpemUoY2FycmllcnM9bl9kaXN0aW5jdChjYXJyaWVyKSkgJT4lDQogIGFycmFuZ2UoZGVzYyhjYXJyaWVycykpDQpgYGANCg0KWW91IGNhbiB1c2UgYSB3ZWlnaHQgdmFyaWFibGUuIFlvdSBjYW4gdXNlIHRoaXMgdG8gY291bnQgdGhlIHRvdGFsIG51bWJlciBvZiBtaWxlcyBhIHBsYW5lIGZsZXcuDQoNCmBgYHtyfQ0Kbm90X2NhbmNlbGxlZCAlPiUNCiAgY291bnQodGFpbG51bSx3dD1kaXN0YW5jZSkNCmBgYA0KDQo8aDM+IENvdW50cyBhbmQgcHJvcG9ydGlvbnMgb2YgbG9naWNhbCB2YWx1ZXMgPC9oMz4NCldoZW4geW91IHdhbnQgdG8gY291bnQgdGhlIG51bWJlciBvZiB0cnVlcyAoMSkgYW5kIGZhbHNlKDApDQoNCmBgYHtyfQ0Kbm90X2NhbmNlbGxlZCAlPiUNCiAgZ3JvdXBfYnkoeWVhciwgbW9udGgsIGRheSkgJT4lDQogIHN1bW1hcml6ZShuX2Vhcmx5ID1zdW0oZGVwX3RpbWU8NTAwKSkNCmBgYA0KDQo8aDM+IEdyb3VwaW5nIE11bHRpcGxlIFZhcmlhYmxlcyA8L2gzPg0KV2hlbiB5b3UgZ3JvdXAgYnkgbXVsdGlwbGUgdmFyaWFibGVzICwgZWFjaCBzdW1tYXJ5IHBlZWxzIG9mZiBvbmUgbGV2ZWwgb2YgdGhlIGdyb3VwaW5nLiBUaGF0IG1ha2VzIGl0IGVhc3kgdG8gcHJvZ3Jlc3NpdmVseSByb2xsIHVwIGEgZGF0YXNldC4gDQoNCmBgYHtyfQ0KZGFpbHkgPC0gZ3JvdXBfYnkoZmxpZ2h0cywgeWVhciwgbW9udGgsIGRheSkNCihwZXJfZGF5IDwtIHN1bW1hcml6ZShkYWlseSwgZmxpZ2h0cz1uKCkpKQ0KYGBgDQogDQpgYGB7cn0NCihwZXJfbW9udGggPC0gIHN1bW1hcml6ZShwZXJfZGF5LCBmbGlnaHRzPXN1bShmbGlnaHRzKSkpDQpgYGANCg0KYGBge3J9DQojIHBlciB5ZWFyDQoocGVyX3llYXIgPC0gc3VtbWFyaXplKHBlcl9tb250aCwgZmxpZ2h0cz1zdW0oZmxpZ2h0cykpKQ0KYGBgDQoNClRvIHJlbW92ZSBncm91cGluZ3MsIHVzZSBVbmdyb3VwKCkNCg0KYGBge3J9DQpkYWlseSAlPiUNCiAgdW5ncm91cCgpICU+JQ0KICBzdW1tYXJpemUoZmxpZ2h0cz1uKCkpDQpgYGANCg0KPGgyPiBHcm91cGVkIE11dGF0ZXMgYW5kIEZpbHRlcnMgPC9oMj4NCkdyb3VwaW5nIGlzIG1vc3QgdXNlZnVsIGluIGNvbmp1bmN0aW9uIHdpdGggc3VtbWFyaXplKCksIGJ1dCB5b3UgY2FuIGFsc28gZG8gY29udmVuaWVudCBvcGVyYXRpb25zIHdpdGggbXV0YXRlKCkgYW5kIGZpbHRlcigpDQoNCmBgYHtyfQ0KIyBmaW5kIHRoZSB3b3JzdCBtZW1iZXJzIG9mIGVhY2ggZ3JvdXANCmZsaWdodHNfc21sICU+JQ0KICBncm91cF9ieSh5ZWFyLCBtb250aCwgZGF5KSAlPiUNCiAgZmlsdGVyKHJhbmsoZGVzYyhhcnJfZGVsYXkpKTwxMCkNCmBgYA0KDQpgYGB7cn0NCiMgZmluZCBhbGwgZ3JvdXBzIGJpZ2dlciB0aGFuIGEgdGhyZXNob2xkDQpwb3B1bGFyX2Rlc3RzIDwtIGZsaWdodHMgJT4lDQogIGdyb3VwX2J5KGRlc3QpICU+JQ0KICBmaWx0ZXIobigpID4zNjUpDQojIGRpc3BsYXkNCnBvcHVsYXJfZGVzdHMNCg0KYGBgDQoNCmBgYHtyfQ0KIyBzdGFuZGFyZGllIHRvIGNvbXB1dGUgcGVyIGdyb3VwIG1ldHJpY3MNCnBvcHVsYXJfZGVzdHMgPC0gZmxpZ2h0cyAlPiUNCiAgIGZpbHRlcihhcnJfZGVsYXk+MCkgJT4lDQogIG11dGF0ZShwcm9wX2RlbGF5PWFycl9kZWxheSAvc3VtKGFycl9kZWxheSkpICU+JQ0KICBzZWxlY3QoeWVhcjpkYXksZGVzdCxhcnJfZGVsYXksIHByb3BfZGVsYXkpDQojIGRpc3BsYXkNCnBvcHVsYXJfZGVzdHMNCmBgYA0KDQo=